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:

PriorityTrancheDescription
1Senior Debt HoldersIf 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.
2Investor DistributionsPro-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.
3Platform Fee1% distribution fee to the platform. This is taken after investors are paid - the platform never takes its fee before investors receive their distributions.
4Excess to Treasury SeriesAny 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:

InstructionParametersAuthorizationDescription
init_waterfalloffering_id, tranches[]Issuer NFT + Platform Operator approvalDefines 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_revenueoffering_id, amount, sourcePaying Agent NFTDeposits 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_waterfalloffering_idPaying Agent NFT + Trustee authenticationExecutes 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_distributionoffering_id, epochInvestor wallet signatureInvestor 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.
reconcileoffering_id, epoch, clearstream_dataTrustee NFTCross-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 TypeFrequencyTrigger
Post-DistributionPer epochTrustee-initiated after Clearstream confirms corporate action settlement
Supply InvariantNightlyAutomated comparison of lockbox PDA state vs. Clearstream position report
Full AuditQuarterlyComprehensive 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:

EndpointMethodDescription
/v1/investor/distributionsGETLists 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/claimPOSTClaims 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:

EndpointMethodDescription
/v1/offerings/:id/distributionsGETLists 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/waterfallGETReturns the waterfall configuration for the offering - tranche priorities, allocation rules, and current escrow balance.
/v1/offerings/:id/distributions/unclaimedGETReturns 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:

FrequencyEpoch CadenceTypical Use Case
Monthly12 epochs per yearReal estate rental income, recurring revenue assets
Quarterly4 epochs per yearStandard fund distributions, most common for Reg D offerings
Semi-annually2 epochs per yearCoupon payments on debt instruments
Annually1 epoch per yearYear-end profit distributions, special dividends
On-demandAs neededAsset 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.