Skip to content

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:

  1. Paying Agent initiates — The Paying Agent (appointed by the Trustee, holding a Paying Agent role NFT) submits the execute_waterfall instruction with the offering ID.
  2. 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.
  3. On-chain execution — The program snapshots token balances, runs the waterfall calculation, creates the DistributionRecord, and emits the DistributionPaid event. 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:

  1. The investor connects their wallet and calls claim_distribution(offering_id, epoch).
  2. The program reads the DistributionRecord for the specified epoch and retrieves the amount_per_token value.
  3. The program reads the investor's InvestorPosition PDA to determine their token balance at the snapshot_slot.
  4. The program calculates the payout: amount_per_token × investor_balance.
  5. The program checks the claimed_bitmap — if the investor's bit is already set, the instruction returns without transferring funds.
  6. 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_waterfall runs, the program identifies tokens locked in the CrossConversion lockbox and calculates the distribution amount for those positions.
  • The Clearstream adapter service receives the DistributionPaid event 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 reconcile instruction 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:

  1. The Trustee obtains the Clearstream position report for the offering's ISIN, showing each investor's distribution receipt.
  2. The Clearstream adapter generates a SHA-256 hash of the position report and submits it as clearstream_data.
  3. The program compares the total distributed on-chain (from the DistributionRecord) with the total reported by Clearstream.
  4. If the amounts match, the DistributionRecord status is updated to Reconciled.
  5. If a discrepancy is detected, the status is set to Disputed, a ComplianceViolation event 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.