Skip to main content

Pools

A pool is the core primitive in Token Studio. It represents a tokenized real-world asset as a smart contract on-chain, with a configurable share structure, access rules, and distribution parameters.

Pool Types

Pools are categorized by their access type -- the mechanism through which investors acquire shares.

Buy Pools

The most common pool type. Shares are listed at a fixed price and investors purchase them directly through the marketplace.

  • Access code: p (purchase)
  • Use case: Standard investment offerings where shares have a defined price
  • Flow: Creator sets price per share, investors buy on marketplace, shares transfer on-chain

Request / Airdrop Pools

Shares are distributed by the pool creator, either in response to user requests or as unsolicited airdrops.

  • Access code: rtj (request to join)
  • Use case: Community rewards, early access programs, selective distribution
  • Flow: User requests access, admin approves, shares assigned by creator

Crowdfunding Pools

Investors contribute funds to a pool, and shares are allocated proportionally to contributions once the funding target is reached.

  • Access code: Crowdfunding variant
  • Use case: Collective investment in an asset, community-funded projects
  • Flow: Users contribute funds, pool reaches target, shares allocated proportionally
  • Display: Shows "Contributors" count instead of "Members"

Staking Pools

Users stake tokens to earn pool shares over time.

  • Access code: stk (staking)
  • Use case: Yield farming, loyalty programs, long-term holder incentives
  • Flow: User stakes tokens into pool, earns shares based on staking duration/amount

QR Code Pools

Shares distributed through QR code scanning, useful for physical events or location-based distribution.

  • Access code: qr
  • Use case: Conference attendance tokens, physical location check-ins
  • Flow: Creator generates QR codes, users scan to receive shares

Share System

Every pool has exactly 10,000 shares, representing 100% ownership. This basis-point system provides fine-grained control over ownership fractions.

Share Arithmetic

SharesPercentageDescription
10,000100.00%Full ownership
5,00050.00%Half ownership
1,00010.00%Tenth ownership
1001.00%One percent
100.10%Ten basis points
10.01%One basis point (minimum)

Share Distribution Example

Total: 10,000 shares (100%)

Creator retains: 3,000 shares (30%)
Investor A buys: 2,000 shares (20%)
Investor B buys: 1,500 shares (15%)
Investor C buys: 500 shares (5%)
Available for sale: 3,000 shares (30%)

How Shares Relate to Distributions

When a distribution of $1,000 occurs:

HolderSharesOwnershipReceives
Creator3,00030%$300.00
Investor A2,00020%$200.00
Investor B1,50015%$150.00
Investor C5005%$50.00

(Remaining 30% of shares are unallocated; their distribution portion stays in the contract.)

On-Chain Representation

On Stellar Soroban, shares are stored as i128 values in the smart contract. The sync script reads these values and converts them:

On-chain value: "0100"  -->  Parsed as decimal: 100 shares  -->  1% ownership
caution

Stellar stores i128 values as decimal strings with leading zeros (e.g., "0100" = 100). These must be parsed as decimal, not hexadecimal. parseInt("0100", 10) = 100 (correct), parseInt("0100", 16) = 256 (wrong).

Pool Data Structure

Pool metadata is stored as compressed JSON in the pool_data column. The keys are abbreviated to minimize storage.

Compressed Key Mapping

KeyFull NameTypeDescription
nanamestringPool display name
tktickerstringShort ticker symbol
mpmax_participantsnumberMaximum number of shareholders
accaccessstringAccess visibility: pr (private) or pb (public)
waccway_to_accessstringAccess type: rtj, p, qr, stk
dtdistribution_typestringtr (trusted), bt (business), dam
eaprestimated_aprnumberProjected annual percentage return
ttdtoken_to_distributestringAddress of token used for distributions
trptoken_purchase_requiredstringToken required to purchase shares
ccdcondition_codestringAccess condition code
ccdvcode_valuestringAccess code value
cgpgitcoin_passportbooleanRequire Gitcoin Passport
tgpgitcoin_thresholdnumberMinimum Gitcoin Passport score
cprcondition_promotestringPromotion condition
epexpirationstringPool expiration date

Raw vs. Formatted Data

// Raw (as stored in database)
{
"na": "Solar Farm Alpha",
"tk": "SFA",
"mp": 100,
"acc": "pb",
"wacc": "p",
"dt": "tr",
"eapr": 12.5,
"ttd": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
}

// Formatted (after poolUtils.formatJsonPoolData())
{
"name": "Solar Farm Alpha",
"ticker": "SFA",
"max_participants": 100,
"access": "public",
"way_to_access": "purchase",
"distribution_type": "trusted",
"estimated_apr": 12.5,
"token_to_distribute": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC"
}

Pool Creation Flow

Step-by-Step Process

  1. Connect Wallet

    • Freighter for Stellar pools
    • MetaMask for EVM pools
  2. Configure Pool Parameters

    • Enter name, ticker, description
    • Select access type (buy, request, crowdfunding, etc.)
    • Set maximum participants
    • Choose distribution token
    • Set estimated APR
  3. Define Share Structure

    • Allocate creator shares
    • Set shares available for purchase/distribution
    • Configure pricing (for buy pools)
  4. Deploy Contract

    • Frontend constructs the transaction
    • User signs with their wallet
    • Contract deployed to selected blockchain
    • Transaction polled for confirmation
  5. Post-Deployment

    • Pool record created in database
    • pool_users entry created for the creator
    • Pool appears in creator's dashboard
    • Pool visible on explore page (if public)

Deployment on Stellar

For Stellar pools, the deployment process involves:

  1. WASM contract code is pre-uploaded (hash stored in networks table)
  2. Frontend calls backend to construct a deploy transaction using the WASM hash
  3. User signs with Freighter
  4. Backend submits to Soroban RPC
  5. Backend polls for confirmation (Soroban RPC first, Horizon fallback)
  6. Contract address stored as pool identifier

Deployment on EVM

For EVM pools:

  1. Compiled contract bytecode included in frontend
  2. User deploys directly from MetaMask
  3. Transaction hash tracked until confirmation
  4. Deployed contract address stored as pool identifier

Pool Lifecycle

 Created          Active          Distributing       Completed
| | | |
v v v v
+--------+ +---------+ +------------+ +-----------+
| Deploy |---->| Shares |---->| Revenue |---->| All shares|
| Contract| | Issued | | Distributed| | Claimed |
+--------+ +---------+ +------------+ +-----------+
| |
v v
+-----------+ +------------+
| Marketplace| | New Round |
| Trading | | Created |
+-----------+ +------------+

Pool States

StateDescription
CreatedContract deployed, no shares issued yet
ActiveShares issued and held by investors
DistributingActive distribution round with pending claims
SyncingBlockchain events being synced (display only, not for creator pools)
DeletedSoft-deleted, excluded from all queries

Distribution Types

The distribution_type field determines the trust model for the pool:

TypeCodeDescription
TrustedtrCreator is trusted to distribute fairly; basic pool model
BusinessbtBusiness-grade with additional verification and reporting
DAMdamDecentralized Asset Management -- additional governance rules

Database Schema

pools Table

ColumnTypeDescription
addressVARCHAR (PK)Smart contract address
pool_dataJSONCompressed pool metadata
chain_idVARCHARNetwork identifier
creator_addressVARCHARWallet address of the pool creator
deletedBOOLEANSoft delete flag
created_atTIMESTAMPCreation timestamp
updated_atTIMESTAMPLast update timestamp

pool_users Table

ColumnTypeDescription
pool_idVARCHAR (FK)References pools.address
user_addressVARCHARInvestor wallet address
sharesINTEGERNumber of shares held
roleVARCHARUser role in this pool
info

A user only sees a pool in their dashboard if they have a corresponding pool_users record. This table is updated by the Stellar sync script based on on-chain events -- it should not be manually modified.

Querying Pools

All Active Pools

SELECT * FROM pools
WHERE deleted IS NOT TRUE
ORDER BY created_at DESC;

Pools by Network

SELECT * FROM pools
WHERE chain_id = '10'
AND deleted IS NOT TRUE;

Pools for a Specific User

SELECT p.* FROM pools p
JOIN pool_users pu ON p.address = pu.pool_id
WHERE LOWER(pu.user_address) = LOWER('G...')
AND p.deleted IS NOT TRUE;

Address Case Sensitivity

Addresses may be stored with inconsistent casing. Always use case-insensitive comparison:

WHERE LOWER(address) = LOWER('<addr>')

Or with Sequelize:

where: Sequelize.where(
Sequelize.fn('LOWER', Sequelize.col('address')),
address.toLowerCase()
)