/** * Regulated provider — the seam where regulated-asset trades are gated. * * Every consumer of @luxfi/exchange that wants to list regulated assets * (stocks, bonds, private companies, real estate, ...) configures a * `RegulatedProvider` at mount time. When a user attempts to trade a * regulated asset, the Exchange UI: * * 1. Checks the user's KYC + accreditation state (cached in store). * 2. If unverified, redirects to `provider.endpoints[chainId].onboardingUrl` * with a return-to URL so the user lands back on the trade screen. * 3. After verification, the trade routes through the provider's adapter * contract (`adapter` address) which enforces transfer restrictions * on-chain (ERC-3643 / IRegulatedProvider interface). * * Multiple providers can be registered at runtime; the Exchange selects by * chain id from `endpoints`. Implementations: any KYC + ATS + custody stack * that exposes the on-chain adapter + off-chain onboarding URL. * * Example: * * * * Then a regulated featured token routes through the provider automatically: * * featured={[ * { chainId: REGULATED_MAINNET, address: '0x...', symbol: 'PRIV-A', * class: AssetClass.VENTURE_CAPITAL, offering: 'reg_cf' }, * ]} */ import type { Address } from 'viem' /** Per-chain regulated-asset endpoints supplied by a RegulatedProvider. */ export interface RegulatedEndpoint { /** On-chain ERC-3643 IRegulatedProvider adapter (asset transfer gate). */ adapter: Address /** ProviderRouter — multi-provider router on this chain. */ router: Address /** Off-chain KYC/AML onboarding URL the UI redirects unverified users to. */ onboardingUrl: string /** Optional: per-chain override for the verification status API. */ verificationStatusUrl?: string } /** A regulated-asset provider config. */ export interface RegulatedProvider { /** Display name (used in onboarding redirect prompts: "Continue to {name}"). */ name: string /** Per-chain endpoints. Trades on a chain not in this map fall back to public DEX. */ endpoints: Record /** Branding for the provider redirect screen (overrides default provider logo). */ brandLogoUrl?: string } /** Result of a verification status check. */ export interface VerificationStatus { kyc: 'unstarted' | 'pending' | 'approved' | 'rejected' accredited: 'unstarted' | 'pending' | 'approved' | 'rejected' | 'expired' /** Sanctions-screening pass/fail. */ sanctions: 'pass' | 'fail' | 'unknown' /** Per-asset-class trading enabled by the provider. */ enabledClasses: string[] } /** Whether a user can trade a given asset right now. */ export function canTrade( status: VerificationStatus, assetClass: string, offering: string, ): { ok: boolean; reason?: string } { if (status.sanctions === 'fail') return { ok: false, reason: 'sanctions' } if (status.kyc !== 'approved') return { ok: false, reason: 'kyc' } if (offering === 'reg_d_506c' && status.accredited !== 'approved') { return { ok: false, reason: 'accreditation' } } if (!status.enabledClasses.includes(assetClass)) { return { ok: false, reason: 'class_not_enabled' } } return { ok: true } }