Skip to content

Investors API

One grain per investor — portfolio, distributions, trading, tax documents, and governance in a single sandboxed process.

Overview

The Investor Self-Service Grain is an Instance-type grain — one per investor, provisioned automatically when an investor signs up on the platform. It is the investor's personal dashboard, API surface, and data store, implemented in Go+HTMX as a native Sandstorm grain. Every piece of investor-specific state — portfolio positions, distribution history, trade listings, wallet connections — lives inside this grain's encrypted journal. No shared database, no centralized user table, no way for one investor's grain to read another investor's data.

The grain does not hold tokens or execute on-chain transactions directly. It aggregates data from Offering Grains via Powerbox capabilities, surfaces it through an HTMX-rendered UI and a JSON API, and delegates on-chain operations to the appropriate programs. When an investor claims a distribution, the grain submits the claim_distribution instruction to the sails_distributions program. When an investor requests a CrossConversion, the grain calls the requestCrossConversion method on the Offering Grain's Cap'n Proto interface. The grain is the coordinator — the blockchain is the settlement layer.

The Investors API is exposed through the platform's API gateway at api.sails.to. Authentication uses Solana wallet signature — the investor signs a challenge with their wallet, and the gateway verifies the signature before routing requests to the investor's grain. All endpoints return JSON.

Investor Self-Service Grain

The grain provides eight core capabilities, each mapping to a section of the investor dashboard and a set of API endpoints:

Capability Description Data Source
View Portfolio All offerings the investor holds security tokens in, with current balances, valuations, and yield metrics. InvestorView capabilities from Offering Grains
Track Distributions & Yields Historical and pending distributions, cumulative yield, and per-epoch payout details. DistributionRecord PDAs on-chain
Request CrossConversion Convert on-chain tokens to bankable ISIN-identified securities via Clearstream, or reverse the conversion. CrossConversion Operator Grain + Trustee authentication
Manage Wallet Connections Link and unlink Solana wallets, set a primary wallet for distributions, and view wallet-level token balances. Grain journal (encrypted)
Download Tax Documents K-1 forms, distribution statements, and year-end tax summaries generated from on-chain records. Compliance Grain + on-chain distribution records
Participate in DAO Governance Vote on proposals, view voting history, and delegate voting weight. GovernanceAPI via Powerbox
View Cap Table Position Ownership percentage, token count, and position relative to total outstanding supply for each offering. Offering Grain cap table snapshots
List Tokens for OTC Sale Create sell listings for OTC secondary trading through the multi-broker network. Broker Grain via Powerbox

Portfolio Endpoints

The portfolio endpoints provide a unified view of an investor's holdings across all offerings on the platform. Data is aggregated in real time from InvestorView capabilities granted by each Offering Grain the investor participates in.

Endpoint Method Description
/v1/investor/portfolio GET Returns all offerings the investor holds tokens in. Each entry includes offering name, series ID, token balance, nominal value per token, current valuation, unrealized yield, and offering status. Supports pagination via ?page= and ?per_page= query parameters.
/v1/investor/portfolio/:offering_id GET Returns detailed position information for a single offering: token balance, ownership percentage (tokens held ÷ total outstanding), lock-up expiration date, distribution history for this offering, CrossConversion status (how many tokens are on-chain vs. bankable), and the investor's accreditation tier at time of subscription.
/v1/investor/portfolio/summary GET Aggregated portfolio metrics: total valuation across all holdings, total distributions received (lifetime), weighted average yield, number of active offerings, and pending (unclaimed) distribution amount.

Position Detail

The position detail response for a single offering includes everything an investor needs to understand their holding:

GET /v1/investor/portfolio/:offering_id

{ “offering_id”: “series-alpha-2024”, “offering_name”: “Alpine Real Estate Fund I”, “series_id”: “series-alpha”, “token_mint”: “7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgHkV”, “token_balance”: 5000, “total_supply”: 1000000, “ownership_percentage”: 0.50, “nominal_value_per_token”: 100, “current_valuation”: 500000, “lock_up_expires_at”: “2025-06-15T00:00:00Z”, “crossconversion_status”: { “on_chain”: 3000, “bankable”: 2000, “isin_code”: “LU0123456789” }, “yield”: { “cumulative_distributions”: 22500, “annualized_yield”: 4.5, “last_distribution_epoch”: 7, “next_distribution_expected”: “2025-04-01T00:00:00Z” }, “distributions”: [ { “epoch”: 7, “amount”: 3125, “status”: “claimed”, “claimed_at”: “2025-01-15T14:30:00Z” }, { “epoch”: 6, “amount”: 3125, “status”: “claimed”, “claimed_at”: “2024-10-12T09:15:00Z” } ] }

Ownership Percentage

Ownership percentage is calculated as the investor’s token balance divided by the total outstanding supply for the offering — not the max supply, but the actual minted and uncancelled supply. This percentage changes when new tokens are minted (dilution) or when tokens are burned (concentration). The grain recalculates this value on every request by querying the Offering Grain’s cap table, ensuring it always reflects the current state of the ledger.

Yield Tracking

Yield is tracked per offering and across the entire portfolio. Per-offering yield is the sum of all distributions claimed, divided by the investor’s cost basis (tokens × nominal value at subscription). Portfolio-level yield is the weighted average across all holdings. The grain stores distribution claim receipts in its journal and cross-references them with on-chain DistributionRecord PDAs to ensure consistency. If the grain’s journal and the on-chain record disagree, the on-chain record is authoritative.

CrossConversion Requests

CrossConversion allows investors to move between on-chain token ownership and bankable custody via Clearstream. The Investor Grain exposes this through a simple API — the complexity of lockbox management, Trustee authentication, and Clearstream settlement is handled by the CrossConversion Operator Grain behind the scenes.

Endpoint Method Description
/v1/investor/crossconversion POST Submits a new CrossConversion request. Requires offering_id, direction (to_bankable or to_onchain), and amount. Returns a request ID and initial status. The request enters a pending state until Trustee authentication is obtained.
/v1/investor/crossconversion/:request_id GET Returns the current status of a CrossConversion request: pending_trustee, locking, awaiting_clearstream, settled, or failed. Includes timestamps for each state transition and the Trustee’s authentication signature once obtained.
/v1/investor/crossconversion GET Lists all CrossConversion requests for the investor, filterable by status and offering_id. Supports pagination.

The requestCrossConversion Method

Under the hood, the Investor Grain calls the requestCrossConversion method on the Offering Grain’s Cap’n Proto OfferingAPI interface. This is a Powerbox-mediated call — the Investor Grain must hold a valid InvestorView capability for the offering, and the Offering Grain validates the request against the investor’s position and the offering’s CrossConversion configuration.

# From sails/offering.capnp

interface OfferingAPI { requestCrossConversion @2 (direction :ConversionDirection, amount :UInt64) -> (request :ConversionRequest);

direction: toBankable | toOnChain

amount: number of tokens to convert

Returns a ConversionRequest with request ID and initial status

}

The conversion flow proceeds as follows:

  1. Investor submits request — The Investor Grain validates that the investor has sufficient unlocked tokens (for to_bankable) or sufficient bankable position (for to_onchain), then calls requestCrossConversion on the Offering Grain.
  2. Offering Grain queues request — The Offering Grain records the request and notifies the CrossConversion Operator Grain and the Trustee Dashboard Grain via Powerbox events.
  3. Trustee authenticates — The Trustee reviews and signs the conversion request using their Trustee NFT. For conversions under the configurable threshold, this is a 1-of-1 signature. For large conversions exceeding $1M, a 2-of-3 keyholder ceremony is required.
  4. On-chain execution — For to_bankable: the sails_crossconversion program locks the investor’s tokens in the lockbox PDA and emits a CrossConversionRequested event. For to_onchain: the program verifies the Clearstream cancellation proof and unlocks tokens from the lockbox.
  5. Clearstream settlement — The CrossConversion Operator Grain initiates the corresponding action with Clearstream: crediting the investor’s custodial account (for to_bankable) or cancelling the ISIN position (for to_onchain).
  6. Status update — The Investor Grain receives a Powerbox notification when the conversion settles and updates the request status to settled.

The 1:1 supply invariant — tokens_locked == isin_outstanding — is enforced at every step. The lockbox contract will reject any lock or unlock operation that would violate this invariant, and the nightly reconciliation engine verifies it independently by comparing on-chain lockbox state with Clearstream position reports.

Secondary Trading

Investors can list their security tokens for sale on the OTC secondary market. Sails.to operates a multi-broker network — trades are not matched by the platform directly but by licensed broker-dealers who hold Broker role NFTs. The Investor Grain provides the listing interface; the Broker Grain handles matching, compliance checks, and settlement.

Endpoint Method Description
/v1/investor/listings POST Creates a new sell listing. Requires offering_id, amount (tokens to sell), and price_per_token. The listing is published to the multi-broker network. Returns a listing ID and status.
/v1/investor/listings GET Lists all active, matched, settled, and cancelled listings for the investor. Supports filtering by status and offering_id.
/v1/investor/listings/:listing_id GET Returns detail for a single listing: current status, matched buyer (if any), settlement progress, and broker information.
/v1/investor/listings/:listing_id DELETE Cancels an active listing. Only possible if the listing has not yet been matched with a buyer. Returns success or an error if the listing is already in settlement.

How Trades Match Through the Broker Grain

When an investor creates a sell listing, the Investor Grain publishes it to the Broker Grain network via the Cap’n Proto BrokerAPI interface:

# From sails/broker.capnp

interface BrokerAPI { listForSecondaryTrading @1 (offering :Text, amount :UInt64, price :UInt64) -> (listing :TradeListing); matchTrade @2 (listing :TradeListing, buyer :InvestorCredential) -> (settlement :TradeSettlement); }

  1. Listing published — The Investor Grain calls listForSecondaryTrading on the Broker Grain. The listing becomes visible to all brokers in the network who are authorized to trade the offering.
  2. Buyer identified — A broker identifies an interested buyer from their client roster. The buyer must hold a valid KYC Credential NFT with the appropriate investor classification and jurisdiction for the offering.
  3. Compliance check — The Broker Grain verifies both parties: the seller has sufficient unlocked tokens, the buyer has a valid KYC credential, both wallets pass the Transfer Hook compliance checks (jurisdiction whitelist, accreditation tier, lock-up period), and the transfer would not violate the offering’s ComplianceConfig maximum investor count.
  4. Trade matched — The Broker Grain calls matchTrade, which initiates the on-chain transfer_with_compliance instruction on the sails_securities program. The Transfer Hook enforces all compliance rules at the smart contract level.
  5. Settlement — On successful transfer, the Broker Grain notifies both the seller’s and buyer’s Investor Grains via Powerbox. The seller’s grain updates the listing status to settled and adjusts the portfolio. The buyer’s grain adds the new position.

The platform does not operate an order book or matching engine. Each trade is a bilateral OTC transaction mediated by a licensed broker. This structure is deliberate — it ensures every secondary trade has a responsible broker-dealer who has performed their own suitability analysis, as required for Reg D securities.

Tax & Statements

The Investor Grain provides access to tax documents and distribution statements generated from on-chain records. Data is pulled from DistributionRecord PDAs, cap table snapshots, and the investor’s position history — all on-chain, all auditable, all immutable.

Endpoint Method Description
/v1/investor/tax/k1/:tax_year GET Downloads the K-1 form for the specified tax year. Each DAO Series LLC is a pass-through entity — income, deductions, and credits flow through to investors proportional to their token holdings. The K-1 is generated by the Compliance Grain from on-chain distribution records and cap table snapshots at each epoch.
/v1/investor/tax/statements GET Lists all available distribution statements. Each statement covers one distribution epoch for one offering and includes the gross distribution amount, any withholding, the net amount paid, and the payment method (on-chain claim or Clearstream corporate action). Supports filtering by tax_year and offering_id.
/v1/investor/tax/statements/:statement_id GET Downloads a specific distribution statement as a PDF. Includes the offering details, epoch number, waterfall tranche breakdown, the investor’s pro-rata calculation, and the on-chain transaction signature for verification.
/v1/investor/tax/summary/:tax_year GET Returns a year-end tax summary aggregating all distributions across all offerings: total ordinary income, total capital gains (if any), total withholding, and the investor’s aggregate cost basis. Intended for tax preparation use.

K-1 Generation

Each offering on Sails.to is structured as a Series within a Wyoming DAO Series LLC. As a pass-through entity, the LLC does not pay federal income tax — instead, each investor receives a Schedule K-1 (Form 1065) reflecting their share of the Series’ income, deductions, and credits for the tax year.

The K-1 is generated by the Compliance Grain using the following data sources:

  • Distribution records — Every DistributionRecord PDA for the offering during the tax year, capturing the amount_per_token and the investor’s claimed amount per epoch.
  • Cap table snapshots — The investor’s token balance at each epoch’s snapshot_slot, determining their pro-rata share. If the investor acquired or disposed of tokens mid-year, the K-1 reflects the weighted average ownership across all epochs.
  • Offering classification — The tax treatment depends on the offering type: rental income for real estate offerings, interest income for debt instruments, dividend income for equity offerings. This classification is stored in the OfferingConfig and does not change after offering initialization.

K-1s are typically available by March 15 following the tax year, in accordance with IRS partnership return filing deadlines. The Investor Grain sends a notification when the K-1 is ready for download.

Distribution Statements

Distribution statements are generated per epoch, per offering. Each statement provides a complete audit trail from revenue deposit through waterfall execution to the investor’s individual payout. The statement includes the on-chain transaction signature for the execute_waterfall instruction and the investor’s claim_distribution instruction — anyone can independently verify the amounts on the Solana blockchain.

For investors holding positions on the bankable side via Clearstream, the distribution statement also includes the Clearstream corporate action reference number, allowing the investor to cross-reference with their custodial account statement.

Powerbox Integration

The Investor Grain does not operate in isolation. It receives capabilities from other grains via the Powerbox — the same claim-token-to-sturdyRef lifecycle that governs all inter-grain communication on the platform. The grain’s functionality is directly determined by which capabilities it holds.

InvestorView from Offering Grains

When an investor subscribes to an offering, the Offering Grain issues an InvestorView capability to the Investor Grain via Powerbox. This capability provides read access to the investor’s position within that offering: token balance, distribution history, cap table position, and CrossConversion status. The Investor Grain claims this capability and stores the resulting sturdyRef in its journal for persistent access across sessions.

The InvestorView capability is scoped — it only exposes the requesting investor’s own position, not the full cap table or other investors’ data. An investor who holds tokens in five offerings will hold five separate InvestorView capabilities, one from each Offering Grain. This is how the portfolio view is assembled: the Investor Grain iterates over its InvestorView sturdyRefs and aggregates the responses.

Powerbox Capability Flow:

Offering Grain A ──(InvestorView)──► Investor Grain Offering Grain B ──(InvestorView)──► │ Offering Grain C ──(InvestorView)──► │ │ Aggregates into /v1/investor/portfolio

KYC Status from KYC Grains

The Investor Grain holds a read-only getStatus and getCredential capability on the investor’s KYC Grain. This allows the Investor Grain to display the current KYC verification status, credential expiration date, and investor classification directly in the dashboard. When the KYC credential is approaching expiration (60 days or fewer), the Investor Grain surfaces a re-verification prompt.

The Investor Grain cannot modify the KYC state — it holds read-only capabilities. If the investor needs to re-verify, the Investor Grain calls startVerification on the KYCVerifier interface to spawn a new KYC Grain instance. The KYC Grain’s HTMX-rendered UI is embedded directly in the investor dashboard via iframe, so the investor completes the verification flow without leaving their Self-Service Grain.

Broker Assistance for Trades

When an investor creates a sell listing for secondary trading, the Investor Grain must request assistance from a Broker Grain. The Investor Grain does not hold a BrokerAPI capability by default — it requests one through Powerbox at the time of listing creation. The Platform Operator pre-authorizes a set of Broker Grains for each offering, and the Powerbox routes the request to an eligible broker.

Powerbox Capability Flow:

Investor Grain ──(Powerbox request: BrokerAPI)──► Powerbox Router │ Routes to eligible Broker Grain(s) │ Broker Grain ──(BrokerAPI capability)──► Investor Grain │ │ │ Investor calls: │ │ listForSecondaryTrading() │ │ │ │ Broker matches buyer, calls: │ │ matchTrade() → on-chain settlement │ │ │ └──(TradeSettlement notification)──► Investor Grain

The Broker Grain retains the BrokerAPI capability for the duration of the listing. Once the trade settles or the listing is cancelled, the capability is released. This ensures that brokers only have access to the investor’s trading intent for the specific listings they are servicing — no persistent surveillance, no access to the investor’s broader portfolio or personal data.

Governance Participation

For offerings with DAO governance enabled, the Investor Grain receives a GovernanceAPI capability from the DAO Manager Grain. This capability allows the investor to view active proposals, cast votes weighted by their token holdings, and view voting results. The Investor Grain calls vote on the GovernanceAPI interface, which records the vote on-chain and updates the proposal’s tally. Voting weight is determined by the investor’s token balance at a governance snapshot slot — the same snapshot mechanism used for distribution calculations.