Distributions API
Waterfall-based revenue distribution — from revenue receipt to investor payout, enforced on-chain.
Overview
Distributions on Sails.to are waterfall-based and enforced on-chain. The sails_distributions program is a dedicated Solana Anchor program that handles everything from revenue receipt to investor payout. It is separate from the sails_securities token program — distributions are a first-class concern with their own instruction set, account structures, and authorization model.
The design principle is simple: investors get paid before the platform. Revenue flows through a priority structure defined at offering creation, and every step is recorded on-chain. There are no off-chain side agreements, no manual overrides, no way to redirect funds outside the waterfall without Trustee authentication. The Paying Agent executes the waterfall; the Trustee authenticates it; the blockchain enforces it.
The Waterfall Model
Every offering on Sails.to defines a waterfall — a priority structure that determines the order in which revenue is distributed. The waterfall is configured when the offering is initialized via the init_waterfall instruction and cannot be modified after investors have committed capital.
The waterfall executes in strict priority order. Each tranche must be fully satisfied before the next tranche receives any funds:
| Priority | Tranche | Description |
|---|---|---|
| 1 | Senior Debt Holders | If the offering has a senior debt component, these holders are paid first — fixed interest or coupon payments as defined in the offering terms. This tranche is optional and only present for structured offerings. |
| 2 | Investor Distributions | Pro-rata distribution to all security token holders based on their token balance at the epoch snapshot. This is the core payout — every investor receives their proportional share of remaining revenue. |
| 3 | Platform Fee | 1% distribution fee to the platform. This is taken after investors are paid — the platform never takes its fee before investors receive their distributions. |
| 4 | Excess to Treasury Series | Any remaining revenue after all tranches are satisfied flows to the Operating Series treasury. This excess can be reinvested, held as reserves, or distributed in future epochs. |
The investor-first design is non-negotiable. The platform fee sits at priority 3 — below investor distributions. If revenue in a given epoch is insufficient to fully satisfy the investor tranche, the platform receives zero fees for that epoch. This alignment of incentives is encoded in the smart contract, not in a terms-of-service document.
Program Instructions
The sails_distributions program exposes five instructions. Each instruction enforces role-based authorization via the NFT hierarchy — you cannot call these instructions without holding the correct role NFT:
| Instruction | Parameters | Authorization | Description |
|---|---|---|---|
init_waterfall |
offering_id, tranches[] |
Issuer NFT + Platform Operator approval | Defines the waterfall structure for an offering. Each tranche specifies a priority level, recipient type, and allocation rule (fixed amount, percentage, or pro-rata). Creates the waterfall configuration PDA. Must be called before any revenue can be deposited. Immutable after first investor subscription. |
deposit_revenue |
offering_id, amount, source |
Paying Agent NFT | Deposits revenue into the offering's distribution escrow from the Operating Series. The source field records the origin of funds (rental income, interest payment, asset sale, etc.) for audit trail purposes. Revenue accumulates until execute_waterfall is called. |
execute_waterfall |
offering_id |
Paying Agent NFT + Trustee authentication | Executes the waterfall for the current epoch. Takes a snapshot of all token holder balances, calculates the pro-rata allocation per token, satisfies each tranche in priority order, and creates a DistributionRecord PDA. Emits a DistributionPaid event. Requires dual authorization — the Paying Agent initiates, the Trustee authenticates. |
claim_distribution |
offering_id, epoch |
Investor wallet signature | Investor pulls their allocation for a specific epoch. Reads the DistributionRecord to determine the amount_per_token, multiplies by the investor's token balance at the epoch snapshot, transfers the funds to the investor's wallet, and sets the investor's bit in the claimed_bitmap. Idempotent — calling twice for the same epoch has no effect. |
reconcile |
offering_id, epoch, clearstream_data |
Trustee NFT | Cross-checks on-chain distribution records with bankable side data from Clearstream. The Trustee submits a hash of the Clearstream position report, and the program verifies that the total distributed on-chain matches the total distributed via corporate actions on the bankable side. Any discrepancy is flagged and logged. |
Authorization Flow
The dual-authorization model for execute_waterfall deserves emphasis. This is not a single-signer operation:
- Paying Agent initiates — The Paying Agent (appointed by the Trustee, holding a Paying Agent role NFT) submits the
execute_waterfallinstruction with the offering ID. - Trustee authenticates — The Trustee (holding a Trustee role NFT) co-signs the transaction. The program verifies both NFTs before executing. For standard distributions, this is a 1-of-1 Trustee NFT signature. For large distributions exceeding a configurable threshold, a 2-of-3 keyholder ceremony is required.
- On-chain execution — The program snapshots token balances, runs the waterfall calculation, creates the
DistributionRecord, and emits theDistributionPaidevent. Funds are placed in escrow for investor claiming.
Distribution Records
Every executed waterfall creates a DistributionRecord — a Program Derived Address that stores the complete state of a single distribution epoch:
DistributionRecord PDA
Seeds: ["distribution", offering_id, epoch]
├── offering_id: Pubkey // The offering this distribution belongs to
├── epoch: u32 // Sequential distribution number (0, 1, 2, ...)
├── amount_per_token: u64 // Lamports (or smallest unit) per token for this epoch
├── total_distributed: u64 // Total amount allocated across all tranches
├── snapshot_slot: u64 // Solana slot at which token balances were snapped
├── claimed_bitmap: Vec<u8> // Bit array tracking which investors have claimed
├── created_at: i64 // Timestamp of waterfall execution
├── trustee_signature: Pubkey // Trustee who authenticated this distribution
└── status: enum { Active, Reconciled, Disputed }
The Claimed Bitmap
The claimed_bitmap is a compact bit array where each bit corresponds to an investor position index. When an investor calls claim_distribution, the program sets their bit to 1. This design has three advantages:
- Space efficiency: A single byte tracks 8 investors. An offering with 2,000 investors requires only 250 bytes for the bitmap — far cheaper than creating a separate PDA per investor per epoch.
- Idempotency: The program checks the bitmap before transferring funds. If the investor's bit is already set, the instruction returns success without transferring anything. Double-claiming is impossible.
- Audit visibility: Anyone can read the bitmap to see exactly which investors have claimed and which have not. Unclaimed distributions are immediately visible to the Paying Agent and Trustee for follow-up.
Investor position indices are assigned sequentially when tokens are first minted to a wallet. The mapping from wallet address to position index is stored in the InvestorPosition PDA and does not change — even if the investor transfers all their tokens and later reacquires them, they retain their original index.
Claiming Distributions
Distributions on Sails.to use a pull-based model. The execute_waterfall instruction calculates allocations and records them on-chain, but it does not push funds to investors. Instead, each investor calls claim_distribution to pull their allocation when they are ready.
On-Chain Holders
For investors holding security tokens directly in their Solana wallet, the claim process is straightforward:
- The investor connects their wallet and calls
claim_distribution(offering_id, epoch). - The program reads the
DistributionRecordfor the specified epoch and retrieves theamount_per_tokenvalue. - The program reads the investor's
InvestorPositionPDA to determine their token balance at thesnapshot_slot. - The program calculates the payout:
amount_per_token × investor_balance. - The program checks the
claimed_bitmap— if the investor's bit is already set, the instruction returns without transferring funds. - The program transfers the calculated amount from the distribution escrow to the investor's wallet, sets the bitmap bit, and emits a claim event.
Bankable / Clearstream Holders
Investors who hold their position on the bankable side via Clearstream (through CrossConversion) do not claim distributions on-chain. Instead, distributions to these holders are processed as corporate actions through Clearstream's settlement infrastructure:
- When
execute_waterfallruns, the program identifies tokens locked in the CrossConversion lockbox and calculates the distribution amount for those positions. - The Clearstream adapter service receives the
DistributionPaidevent and initiates a corporate action (ISO 20022 message) to distribute funds to the corresponding ISIN holders. - Clearstream settles the distribution to each investor's custodial account according to its standard settlement cycle.
- The
reconcileinstruction is then used to verify that the on-chain and bankable distributions match.
This dual-track claiming model is what makes CrossSecurities work — the same offering can pay both on-chain and traditional finance investors from a single waterfall execution.
Reconciliation
Reconciliation is the process of cross-checking on-chain distribution records with the bankable side. This is critical for offerings that have investors on both sides of the CrossConversion bridge.
The Reconcile Instruction
After a distribution epoch has been executed and Clearstream has settled the corresponding corporate action, the Trustee calls the reconcile instruction:
- The Trustee obtains the Clearstream position report for the offering's ISIN, showing each investor's distribution receipt.
- The Clearstream adapter generates a SHA-256 hash of the position report and submits it as
clearstream_data. - The program compares the total distributed on-chain (from the
DistributionRecord) with the total reported by Clearstream. - If the amounts match, the
DistributionRecordstatus is updated toReconciled. - If a discrepancy is detected, the status is set to
Disputed, aComplianceViolationevent is emitted, and the DAO Manager Grain is alerted for investigation.
Reconciliation Schedule
The reconciliation engine runs on a configurable schedule — typically within 48 hours of each distribution execution. For high-frequency distributions (monthly), the nightly reconciliation job compares the on-chain lockbox state with Clearstream holdings and flags any drift. A discrepancy in the supply invariant (tokens_locked == isin_outstanding) triggers an immediate alert to the Trustee and platform operators.
| Reconciliation Type | Frequency | Trigger |
|---|---|---|
| Post-Distribution | Per epoch | Trustee-initiated after Clearstream confirms corporate action settlement |
| Supply Invariant | Nightly | Automated comparison of lockbox PDA state vs. Clearstream position report |
| Full Audit | Quarterly | Comprehensive reconciliation across all offerings, all epochs, all investors — generates the formal audit report |
API Endpoints
The Distributions API is exposed through the platform's API gateway at api.sails.to. Authentication uses Solana wallet signature + NFT verification. All endpoints return JSON.
Investor Endpoints
Authenticated with investor wallet signature:
| Endpoint | Method | Description |
|---|---|---|
/v1/investor/distributions |
GET |
Lists all distributions across all offerings the investor holds. Returns epoch, amount, status (claimable / claimed / pending), and offering details. Supports pagination and filtering by offering ID or status. |
/v1/investor/distributions/:epoch/claim |
POST |
Claims a specific distribution. Submits the claim_distribution instruction on behalf of the investor. Returns the transaction signature and claimed amount. Idempotent — returns success if already claimed. |
Issuer Endpoints
Authenticated with Issuer NFT:
| Endpoint | Method | Description |
|---|---|---|
/v1/offerings/:id/distributions |
GET |
Lists all distribution epochs for the offering. Returns epoch number, total distributed, amount per token, claim progress (claimed count / total investors), reconciliation status, and timestamps. |
/v1/offerings/:id/distributions/waterfall |
GET |
Returns the waterfall configuration for the offering — tranche priorities, allocation rules, and current escrow balance. |
/v1/offerings/:id/distributions/unclaimed |
GET |
Returns a list of investors with unclaimed distributions for the offering, grouped by epoch. Used by issuers and paying agents to follow up with investors who have not yet claimed. |
Distribution Frequency
The distribution frequency is configured per offering in the OfferingConfig via the distributionFrequency field. Supported options:
| Frequency | Epoch Cadence | Typical Use Case |
|---|---|---|
| Monthly | 12 epochs per year | Real estate rental income, recurring revenue assets |
| Quarterly | 4 epochs per year | Standard fund distributions, most common for Reg D offerings |
| Semi-annually | 2 epochs per year | Coupon payments on debt instruments |
| Annually | 1 epoch per year | Year-end profit distributions, special dividends |
| On-demand | As needed | Asset sale proceeds, liquidation events, ad-hoc distributions |
The frequency setting determines when the Paying Agent is expected to execute the waterfall, but it does not enforce timing at the program level — the execute_waterfall instruction can be called at any time, subject to the dual-authorization requirement. The frequency is a business-logic convention, not a smart contract constraint.