Compliance Framework
Compliance encoded in smart contracts and enforced at the protocol level — because paper policies don't stop non-compliant transfers.
KYC Credential System
Every investor on the Sails.to platform carries a KYC Credential NFT — an on-chain attestation of their verified identity, classification, and regulatory status. This NFT contains zero personally identifiable information. No names, no addresses, no document images. Only cryptographic proofs and classification flags that the smart contract needs to enforce compliance rules.
The KYC Credential NFT metadata structure:
KYC NFT Metadata (on-chain, no PII):
├── investor_class: enum { Accredited, Professional, QualifiedPurchaser, Retail }
├── jurisdiction_hash: [u8; 32] // SHA256 of ISO country code, anonymized
├── reg_exemption: enum { RegD506b, RegD506c, RegS, RegA, RegCF }
├── verification_level: enum { Basic, Enhanced, InstitutionalEDD }
├── issued_by: Pubkey // Licensed KYC provider's license NFT
├── issued_at: i64 // Unix timestamp
├── expires_at: i64 // Credential expiration
├── aml_clear: bool // Anti-money laundering clearance
└── pep_clear: bool // Politically exposed person clearance
The investor_class determines which offerings an investor can access. An Accredited Investor can participate in Reg D 506(b) and 506(c) offerings. A Professional Investor can access institutional tranches. A Retail investor is limited to Reg A and Reg CF offerings. The smart contract checks this on every mint, every transfer, every distribution claim.
The jurisdiction_hash is a SHA-256 hash of the investor's ISO country code — anonymized so that the blockchain reveals nothing about the investor's location, but the Transfer Hook can still enforce jurisdiction whitelists by comparing hashes.
Credentials expire. When expires_at passes, the investor's KYC is no longer valid, and the smart contract will reject any new minting or transfer operations until a fresh credential is issued. The platform does not wait for expiration to bite — the KYC Grain tracks expiration windows and triggers re-verification workflows proactively.
Compliance Configuration
Every offering on the platform carries a ComplianceConfig PDA — a Program Derived Address account that encodes the regulatory constraints for that specific offering. This is compliance as data, stored on-chain, immutable once set (modifiable only by the Issuer with Platform Operator approval):
ComplianceConfig PDA Fields:
├── offering_id: Pubkey // The offering this config governs
├── allowed_jurisdictions: Vec<[u8; 32]> // SHA256 hashes of allowed ISO codes
├── min_investment: u64 // Minimum investment amount (in cents)
├── lock_up_days: u32 // Mandatory holding period before transfer
├── max_investors: u32 // Maximum number of investors (e.g., 2000 for Reg D)
├── accreditation_required: bool // Whether accredited status is mandatory
├── reg_exemption: enum // Which regulatory exemption applies
├── features: u64 // Bitmask for feature flags (pause, etc.)
└── version: u8 // PDA version for migration support
The max_investors field is critical for Reg D offerings — 506(b) limits non-accredited investors to 35, while the total investor count must remain under SEC thresholds. The smart contract tracks the current investor count in the OfferingState PDA and rejects any mint that would exceed the limit.
The lock_up_days field enforces mandatory holding periods. For Reg D securities, this is typically 6-12 months. The transfer_with_compliance instruction checks the investor's locked_until timestamp against the current slot time — if the lock-up hasn't expired, the transfer is rejected at the protocol level.
Transfer Enforcement
Every token transfer on the platform passes through compliance enforcement. There are no unverified transfers. The transfer_with_compliance instruction performs the following checks before allowing any movement of security tokens:
- KYC Validity: Both the sender and receiver must hold valid, unexpired KYC Credential NFTs. If either credential has expired or been revoked, the transfer is rejected.
- Lock-Up Period: The sender's
locked_untiltimestamp must be in the past. If the mandatory holding period hasn't elapsed, the transfer is rejected. - Jurisdiction Whitelist: The receiver's
jurisdiction_hashmust appear in the offering'sallowed_jurisdictionslist. A Reg S offering restricted to non-US investors will reject any transfer to a US-jurisdiction wallet. - Accreditation Tier: The receiver must meet the offering's accreditation requirements. A Reg D 506(c) offering requires the receiver to be an Accredited Investor or above.
- Investor Count: The transfer must not cause the offering to exceed its
max_investorslimit (relevant when the receiver is a new investor, not an existing holder).
These checks are enforced by the SPL-2022 Transfer Hook — a program extension that the Solana runtime invokes on every token transfer. Non-compliant transfers are not logged and ignored; they are rejected. The tokens do not move. See the Transfer Rules documentation for the complete enforcement mechanism.
Regulatory Reporting
Compliance is not just enforcement — it is reporting. The platform generates the following regulatory filings and reports, automated where possible, human-reviewed where required:
| Report | Authority | Frequency | Description |
|---|---|---|---|
| Form D | SEC (EDGAR) | Per offering + annual amendment | Notice of exempt offering. Generated as XML validated against the SEC EDGAR schema. Filed within 15 days of first sale. |
| Blue Sky Filings | State regulators | Per state, per offering | State-level securities exemption filings. The platform tracks per-state exemptions and investor counts to ensure compliance with each state's requirements. |
| AML/SAR | FinCEN | As needed | Suspicious Activity Reports. Automated flagging based on transaction patterns, with human compliance officer review before filing. |
| K-1 | IRS | Annual | Partner's share of income for LLC pass-through taxation. Generated from on-chain distribution records and cap table snapshots at tax year end. |
| Reg S Compliance | SEC | Ongoing | Non-US investor tracking, flowback restrictions, and distribution compliance period monitoring. |
| Cap Table Snapshots | Internal / Auditors | On demand | Ownership snapshots pulled directly from on-chain state. Immutable, verifiable, and exportable in standard formats. |
The Regulatory Reporting Grain (a compliance-grain in Go) automates the generation of these reports. Form D XML is validated against the SEC EDGAR schema before submission. Blue Sky filings track per-state investor counts. AML/SAR flagging uses rule-based detection with human review — the system flags, a compliance officer decides.
Audit Trail
Every action across every grain and smart contract is logged. This is not optional, not configurable, not something that can be turned off for performance reasons. The audit trail is the regulatory backbone of the platform:
AuditEvent {
id, // Unique event identifier
timestamp, // Precise event time
severity, // info, warning, critical
category, // auth, transfer, compliance, governance
actor_id, // Wallet address
actor_type, // NFT role (Operator, Trustee, Broker, etc.)
action, // What was done
resource, // What it was done to
resource_type, // Offering, investor, distribution, etc.
success, // Whether the action succeeded
error_message, // Why it failed (if applicable)
grain_id, // Which grain processed the action
offering_id, // Which offering was affected
series_id, // Which Series LLC
metadata // Additional context (JSON)
}
Seven-year retention — required by SEC regulations for broker-dealer records and investment adviser records. Every audit event is written to the grain's append-only journal, encrypted at rest with AES-256, and replicated for durability. After the 7-year period, automated purge with legal hold override ensures data is retained only as long as required.
The audit trail is not just for regulators. It is the system's memory. Grain journals support deterministic replay — given the same sequence of audit events, the grain reconstructs the identical state. This is how disaster recovery works: restore the journal, replay the events, verify the state. No backup snapshots needed. The log is the truth.