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
| Shares | Percentage | Description |
|---|---|---|
| 10,000 | 100.00% | Full ownership |
| 5,000 | 50.00% | Half ownership |
| 1,000 | 10.00% | Tenth ownership |
| 100 | 1.00% | One percent |
| 10 | 0.10% | Ten basis points |
| 1 | 0.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:
| Holder | Shares | Ownership | Receives |
|---|---|---|---|
| Creator | 3,000 | 30% | $300.00 |
| Investor A | 2,000 | 20% | $200.00 |
| Investor B | 1,500 | 15% | $150.00 |
| Investor C | 500 | 5% | $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
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
| Key | Full Name | Type | Description |
|---|---|---|---|
na | name | string | Pool display name |
tk | ticker | string | Short ticker symbol |
mp | max_participants | number | Maximum number of shareholders |
acc | access | string | Access visibility: pr (private) or pb (public) |
wacc | way_to_access | string | Access type: rtj, p, qr, stk |
dt | distribution_type | string | tr (trusted), bt (business), dam |
eapr | estimated_apr | number | Projected annual percentage return |
ttd | token_to_distribute | string | Address of token used for distributions |
trp | token_purchase_required | string | Token required to purchase shares |
ccd | condition_code | string | Access condition code |
ccdv | code_value | string | Access code value |
cgp | gitcoin_passport | boolean | Require Gitcoin Passport |
tgp | gitcoin_threshold | number | Minimum Gitcoin Passport score |
cpr | condition_promote | string | Promotion condition |
ep | expiration | string | Pool 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
-
Connect Wallet
- Freighter for Stellar pools
- MetaMask for EVM pools
-
Configure Pool Parameters
- Enter name, ticker, description
- Select access type (buy, request, crowdfunding, etc.)
- Set maximum participants
- Choose distribution token
- Set estimated APR
-
Define Share Structure
- Allocate creator shares
- Set shares available for purchase/distribution
- Configure pricing (for buy pools)
-
Deploy Contract
- Frontend constructs the transaction
- User signs with their wallet
- Contract deployed to selected blockchain
- Transaction polled for confirmation
-
Post-Deployment
- Pool record created in database
pool_usersentry 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:
- WASM contract code is pre-uploaded (hash stored in
networkstable) - Frontend calls backend to construct a
deploytransaction using the WASM hash - User signs with Freighter
- Backend submits to Soroban RPC
- Backend polls for confirmation (Soroban RPC first, Horizon fallback)
- Contract address stored as pool identifier
Deployment on EVM
For EVM pools:
- Compiled contract bytecode included in frontend
- User deploys directly from MetaMask
- Transaction hash tracked until confirmation
- 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
| State | Description |
|---|---|
| Created | Contract deployed, no shares issued yet |
| Active | Shares issued and held by investors |
| Distributing | Active distribution round with pending claims |
| Syncing | Blockchain events being synced (display only, not for creator pools) |
| Deleted | Soft-deleted, excluded from all queries |
Distribution Types
The distribution_type field determines the trust model for the pool:
| Type | Code | Description |
|---|---|---|
| Trusted | tr | Creator is trusted to distribute fairly; basic pool model |
| Business | bt | Business-grade with additional verification and reporting |
| DAM | dam | Decentralized Asset Management -- additional governance rules |
Database Schema
pools Table
| Column | Type | Description |
|---|---|---|
address | VARCHAR (PK) | Smart contract address |
pool_data | JSON | Compressed pool metadata |
chain_id | VARCHAR | Network identifier |
creator_address | VARCHAR | Wallet address of the pool creator |
deleted | BOOLEAN | Soft delete flag |
created_at | TIMESTAMP | Creation timestamp |
updated_at | TIMESTAMP | Last update timestamp |
pool_users Table
| Column | Type | Description |
|---|---|---|
pool_id | VARCHAR (FK) | References pools.address |
user_address | VARCHAR | Investor wallet address |
shares | INTEGER | Number of shares held |
role | VARCHAR | User role in this pool |
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()
)