RWT Engine
✅ Ready to Dev
This contract specification has passed business logic audit, technical review, cross-program integration check, and naming standardization. A developer can implement from this document.
Key Concepts
12 Instructions
Mint, admin mint, adjust capital, claim yield, swap, authority/manager/config management, pause
2 State Accounts
RwtVault, RwtDistributionConfig
2 PDA Seeds
rwt_vault, dist_config_rwt
NAV Pricing
total_invested_capital— sum of all USDC deposited + OT position values (in USDC equivalent, 6 decimals)total_rwt_supply— total RWT tokens in circulation- Initial NAV = $1.00 (1,000,000 lamports)
- NAV recalculates automatically after every state-changing operation (mint, claim, adjust)
- Guard: if
total_rwt_supply == 0, NAV = INITIAL_NAV (prevents division by zero) - Guard:
adjust_capitalcannot reducetotal_invested_capitalbelowMIN_CAPITAL_FLOOR(1 lamport) to prevent NAV = 0 with supply > 0 - NAV can only decrease via
adjust_capital(writedown). Increases come from yield (claim_yield) and mint fees - Mint fee NAV boost: 0.5% vault fee from each mint goes to capital accumulator, increasing total_invested_capital while the user receives RWT calculated on net_deposit only. This means NAV increases slightly with every mint — benefiting existing holders
- No redeem/burn. RWT cannot be redeemed back to USDC via the contract. Users sell RWT on the DEX. This is a design decision — vault capital stays deployed
- Book value, not market value. NAV reflects accounting value, not real-time market prices.
vault_swapdoes not change total_invested_capital (swap is value-neutral in accounting). Market price divergence is corrected viaadjust_capital(writedown) triggered by governance based on off-chain price analysis
Compound Growth Loop
OT Positions Earn Yield
OT tokens held by vault receive yield via Yield Distribution streams. OT revenue (USDC) is converted to RWT by the YD contract (swap on DEX + mint at NAV), then distributed as RWT streams. Vault is an OT holder like any other wallet.
Claim RWT from YD
Permissionless crank calls
claim_yield. Vault PDA signs merkle claim → receives RWT.Split Claimed RWT
70% stays in vault (NAV growth). 15% → ARL Treasury. 15% → Liquidity Nexus.NAV increase:
total_invested_capital += book_value_share * nav_book_value / 1,000,000Three Roles
🏛️ Authority
Team Multisig (after bootstrap)
admin_mint_rwt— mint backed by OTadjust_capital— decrease NAVupdate_vault_manager— change managerupdate_distribution_config— change yield splitpropose_authority_transfer
🤖 Manager
AI Agent wallet (automated trading bot)
vault_swap— swap any token via DEX
update_vault_manager.🛑 Pause Authority
Team Multisig (Squads)
pause_mint— emergency stopunpause_mint— resume
Instructions
Initialization
initialize_vault
initialize_vault
Create the RWT vault with all required accounts. Called once.Parameters:
Caller: Deployer (one-time)Accounts:
| Parameter | Type | Description |
|---|---|---|
initial_authority | Pubkey | Initial governance authority (deployer, then transferred to Team Multisig) |
pause_authority | Pubkey | Emergency pause signer (Team Multisig, immutable) |
areal_fee_destination | Pubkey | Where 0.5% mint fee goes — Areal Finance (static, immutable) |
liquidity_destination | Pubkey | Where 15% yield goes (crank RWT ATA — crank then routes to Nexus via nexus_deposit) |
protocol_revenue_destination | Pubkey | Where 15% yield goes (ARL Treasury ATA) |
deployer(signer, mut) — pays for all account creationrwt_vault(init) — PDA seed:["rwt_vault"]dist_config(init) — PDA seed:["dist_config_rwt"]rwt_mint(init) — new SPL token mint, authority = rwt_vault PDAcapital_accumulator_ata(init) — USDC ATA, authority = rwt_vault PDAusdc_mint(readonly) — USDC mint address (for creating Capital Accumulator ATA)token_program,system_program,associated_token_program
RwtVaultPDA — main vault state, mint authority for RWTRwtDistributionConfigPDA — yield split configuration- RWT SPL Token Mint — the RWT token (6 decimals), mint authority = RwtVault PDA
- Capital Accumulator ATA — USDC ATA owned by RwtVault PDA
nav_book_value = 1,000,000($1.00)total_invested_capital = 0total_rwt_supply = 0mint_paused = false- Distribution: 70% book value / 15% liquidity / 15% protocol revenue
User Minting
mint_rwt
mint_rwt
User deposits USDC and receives RWT at the current NAV price. 1% fee is split: 0.5% stays in vault (increases NAV), 0.5% goes to areal_fee_destination (Areal Finance).
Caller: Permissionless — anyone can mintAccounts:
| Parameter | Type | Description |
|---|---|---|
amount | u64 | USDC amount to deposit (6 decimals) |
user(signer) — depositorrwt_vault(mut) — updates supply, capital, NAVrwt_mint(mut) — RWT mint, authority = vault PDAuser_deposit(mut) — user’s USDC ATA, constraint:owner == user,mint == usdc_mint(hardcoded USDC mint)user_rwt(mut) — user’s RWT ATA, constraint:mint == vault.rwt_mintcapital_acc(mut) — vault’s USDC ATA, constraint:key == vault.capital_accumulator_atadao_fee_account(mut) — constraint:key == vault.areal_fee_destination
amount > 0mint_paused == false
- Calculate fee:
fee_total = amount * 100 / 10,000(1%) dao_fee = fee_total / 2(0.5% to Areal Finance)vault_fee = fee_total - dao_fee(0.5% stays in vault)net_deposit = amount - fee_totalrwt_out = net_deposit * 1,000,000 / nav_book_value- Transfer
net_deposit + vault_fee→ capital_acc - Transfer
dao_fee→ dao_fee_account - Mint
rwt_outRWT to user (vault PDA signs as mint authority) - Update:
total_invested_capital += net_deposit + vault_fee - Update:
total_rwt_supply += rwt_out - Recalculate NAV
- Emit
RwtMinted
Authority Operations
admin_mint_rwt
admin_mint_rwt
Mint RWT backed by OT positions already in the vault. No USDC deposit required — used when vault acquires OT through other means (e.g., governance decision to back RWT with existing OT).
Caller: Authority (Team Multisig)Accounts:
| Parameter | Type | Description |
|---|---|---|
rwt_amount | u64 | RWT tokens to mint |
backing_capital_usd | u64 | USD value of backing (6 decimals) |
authority(signer) — must matchvault.authorityrwt_vault(mut)rwt_mint(mut) — constraint:key == vault.rwt_mintrecipient_rwt(mut) — constraint:mint == vault.rwt_mint
rwt_amount > 0backing_capital_usd > 0(first admin_mint must establish initial NAV — prevents NAV = 0 with supply > 0)
RwtMinted.First mint: When
total_rwt_supply == 0, the NAV guard returns INITIAL_NAV (1.00` to match initial NAV.adjust_capital
adjust_capital
Decrease total_invested_capital (writedown, amortization, loss recognition). NAV decreases proportionally. Only decreases allowed — no manual appreciation.
Caller: Authority (Team Multisig)Accounts:
| Parameter | Type | Description |
|---|---|---|
writedown_amount | u64 | USDC value to subtract from capital (6 decimals) |
authority(signer) — must matchvault.authorityrwt_vault(mut)
writedown_amount > 0writedown_amount ≤ total_invested_capital - MIN_CAPITAL_FLOOR
total_invested_capital -= writedown_amount, recalculates NAV. Emits CapitalAdjusted.update_vault_manager
update_vault_manager
Change the manager wallet that can execute vault_swap.
Caller: Authority (Team Multisig)Accounts:
| Parameter | Type | Description |
|---|---|---|
new_manager | Pubkey | New manager wallet |
authority(signer) — must matchvault.authorityrwt_vault(mut)
vault.manager = new_manager. Emits VaultManagerUpdated.update_distribution_config
update_distribution_config
Change yield split ratios and/or destinations.
Caller: Authority (Team Multisig)Accounts:
| Parameter | Type | Description |
|---|---|---|
book_value_bps | u16 | NAV growth % |
liquidity_bps | u16 | Nexus % |
protocol_revenue_bps | u16 | ARL Treasury % |
liquidity_destination | Pubkey | Crank RWT ATA (crank routes to Nexus via nexus_deposit) |
protocol_revenue_destination | Pubkey | ARL Treasury RWT ATA |
authority(signer) — must matchvault.authorityrwt_vault— for authority verificationdist_config(mut) — overwritten with new values
book_value_bps + liquidity_bps + protocol_revenue_bps == 10,000
DistributionConfigUpdated.Authority Transfer
Two-step governance authority handoff. Same propose+accept pattern as OT contract, but authority is stored directly in RwtVault (not a separate PDA like OtGovernance). This is because RwtVault is a singleton — there’s only one vault, so a separate governance PDA adds no value.propose_authority_transfer
propose_authority_transfer
accept_authority_transfer
accept_authority_transfer
Manager Operations
vault_swap
vault_swap
Swap any token held by the vault through Native DEX. Vault PDA signs as the swap user. Manager decides direction, amount, and slippage tolerance.
Caller: Manager onlyAccounts:Validation:
| Parameter | Type | Description |
|---|---|---|
amount_in | u64 | Input token amount |
min_amount_out | u64 | Minimum output (slippage protection) |
a_to_b | bool | Swap direction in the DEX pool |
manager(signer) — must matchvault.managerrwt_vault— PDA, signs CPI swapvault_token_in(mut) — source ATA, constraint:owner == rwt_vault.key()vault_token_out(mut) — destination ATA, constraint:owner == rwt_vault.key()- DEX CPI accounts (all UncheckedAccount — validated by DEX program internally):
pool_state(mut) — DEX PoolState PDA for the swap pair["pool", token_a_mint, token_b_mint]dex_config— DEX global config["dex_config"]vault_in(mut) — pool’s token account for input side (authority = pool_state PDA)vault_out(mut) — pool’s token account for output side (authority = pool_state PDA)areal_fee_account(mut) — DEX’sdex_config.areal_fee_destination(RWT ATA, receives protocol fee)bin_array(mut, optional) — required only for Concentrated pools["bins", pool_state]
dex_program— constraint:key == DEX_PROGRAM_ID(hardcoded, prevents fake program)token_program
CPI signing: Vault PDA signs the DEX swap using seeds
[b"rwt_vault", &[vault.bump]]. DEX program sees rwt_vault as the swap user. Account order must match DEX swap instruction exactly — see Native DEX specification for canonical account layout.amount_in > 0min_amount_out > 0(slippage protection required)
native_dex::swap. Vault PDA signs via seeds. Emits VaultSwapExecuted.Use cases (each is a single vault_swap call):- USDC → OT (invest capital into real-world assets)
- OT → USDC (divest position)
- RWT → USDC (sell claimed yield)
- USDC → OT (buy OT with proceeds)
Yield Collection
claim_yield
claim_yield
Claim RWT rewards from a Yield Distribution merkle stream, then split according to distribution config. Vault PDA signs the YD claim CPI.
Caller: Permissionless (crank)Accounts:
| Parameter | Type | Description |
|---|---|---|
cumulative_amount | u64 | Cumulative claim amount (from merkle leaf) |
proof | Vec<[u8; 32]> | Merkle proof path |
crank(signer, mut) — pays for ClaimStatus init if first claimrwt_vault(mut) — signs YD claim CPI, updates capital/NAVdist_config— read yield split ratiosrwt_claim_ata(mut) — RWT ATA owned by vault (receives claimed tokens)liquidity_dest(mut) — constraint:key == dist_config.liquidity_destinationprotocol_revenue_dest(mut) — constraint:key == dist_config.protocol_revenue_destination- YD CPI accounts:
yd_distributor,yd_claim_status,yd_reward_vault(all UncheckedAccount) yield_distribution_program— constraint:key == YD_PROGRAM_ID(hardcoded, prevents fake program)token_program,system_program
- Snapshot
rwt_claim_ata.amountbefore - CPI →
yield_distribution::claim(cumulative_amount, proof)with vault PDA as claimant - Reload
rwt_claim_ata, calculateclaimed = new_balance - old_balance - If claimed == 0, return (nothing to distribute)
- Calculate splits (in RWT tokens):
book_value_share = claimed * book_value_bps / 10,000(stays in vault)liquidity_share = claimed * liquidity_bps / 10,000(→ liquidity_dest, then routed to Nexus vianexus_deposit)protocol_revenue_share = claimed - book_value_share - liquidity_share(→ ARL Treasury, calculated as remainder — not fromprotocol_revenue_bps— to prevent dust loss from integer division. The storedprotocol_revenue_bpsis used only for validation inupdate_distribution_configand for display.)
- Transfer
liquidity_shareRWT → liquidity_dest (vault PDA signs) - Transfer
protocol_revenue_shareRWT → protocol_revenue_dest (vault PDA signs) - Convert book_value_share to USD:
usd_value = (book_value_share as u128) * (nav_book_value as u128) / 1,000,000(cast to u128 before multiply to prevent overflow) total_invested_capital += usd_value- Recalculate NAV
- Emit
YieldDistributed
Emergency
pause_mint
pause_mint
Emergency pause — stops all user minting. Does NOT affect manager swaps, yield claims, or authority operations.Caller: Pause Authority only (Team Multisig)Accounts:
pause_authority(signer) — must matchvault.pause_authorityrwt_vault(mut)
vault.mint_paused = true. Emits MintPauseToggled(true).unpause_mint
unpause_mint
Resume minting after emergency pause.Caller: Pause Authority only (Team Multisig)Accounts:
pause_authority(signer) — must matchvault.pause_authorityrwt_vault(mut)
vault.mint_paused = false. Emits MintPauseToggled(false).State Accounts
RwtVault
Main vault state. Is mint authority for RWT SPL Mint. Signs CPI swaps and YD claims via PDA seeds.| Field | Type | Description |
|---|---|---|
total_invested_capital | u128 | Total capital in USDC equivalent (6 decimals) |
total_rwt_supply | u64 | Total RWT tokens in circulation |
nav_book_value | u64 | NAV per RWT: capital * 1,000,000 / supply |
capital_accumulator_ata | Pubkey | USDC ATA owned by vault (created at init) |
rwt_mint | Pubkey | RWT SPL token mint |
authority | Pubkey | Governance authority (Team Multisig) |
pending_authority | Option<Pubkey> | Pending authority transfer target |
manager | Pubkey | Manager wallet (can execute vault_swap) |
pause_authority | Pubkey | Emergency pause signer (Team Multisig, immutable) |
mint_paused | bool | Minting paused flag |
areal_fee_destination | Pubkey | Where 0.5% mint fee goes (static, immutable) |
bump | u8 | PDA bump seed |
["rwt_vault"]
RwtVault does not store individual OT position balances. OT ATAs are created dynamically when manager buys OT. Vault token holdings are read from on-chain ATA balances at query time.
RwtDistributionConfig
Configures how claimed RWT yield is split.| Field | Type | Description |
|---|---|---|
book_value_bps | u16 | % that stays in vault for NAV growth (default: 7,000 = 70%) |
liquidity_bps | u16 | % sent to Liquidity Nexus (default: 1,500 = 15%) |
protocol_revenue_bps | u16 | % sent to ARL Treasury (default: 1,500 = 15%) |
liquidity_destination | Pubkey | Crank RWT ATA (crank routes to Nexus via nexus_deposit for principal tracking) |
protocol_revenue_destination | Pubkey | ARL Treasury RWT ATA address |
bump | u8 | PDA bump seed |
["dist_config_rwt"]
BPS must always sum to 10,000.
update_distribution_config validates this.PDA Seeds
| Account | Seeds | Description |
|---|---|---|
| RwtVault | "rwt_vault" | Main vault state, RWT mint authority |
| RwtDistributionConfig | "dist_config_rwt" | Yield split configuration |
Both PDAs are global singletons (no per-project derivation). There is exactly one RWT vault per protocol deployment.
Constants
| Constant | Value | Description |
|---|---|---|
BPS_DENOMINATOR | 10,000 | 100% in basis points |
MINT_FEE_BPS | 100 | 1% total fee on user mints |
DEFAULT_BOOK_VALUE_BPS | 7,000 | 70% of yield → NAV growth |
DEFAULT_LIQUIDITY_BPS | 1,500 | 15% of yield → Liquidity Nexus |
DEFAULT_PROTOCOL_REVENUE_BPS | 1,500 | 15% of yield → ARL Treasury |
INITIAL_NAV | 1,000,000 | $1.00 in USDC lamports (6 decimals) |
MIN_CAPITAL_FLOOR | 1 | Minimum total_invested_capital (1 lamport, prevents NAV = 0) |
RWT_DECIMALS | 6 | RWT token decimals (matches USDC) |
YD_PROGRAM_ID | hardcoded | Yield Distribution program ID (validated in claim_yield) |
DEX_PROGRAM_ID | hardcoded | Native DEX program ID (validated in vault_swap) |
Events
| Event | Fields | When |
|---|---|---|
VaultInitialized | authority, rwt_mint, nav, timestamp | Vault created |
RwtMinted | user, deposit_amount, rwt_amount, fee_vault, fee_dao, nav_after, is_admin, timestamp | User or admin mints RWT (is_admin distinguishes the two) |
YieldDistributed | total_yield, book_value_share, liquidity_share, protocol_revenue_share, nav_after, timestamp | Yield claimed and split |
CapitalAdjusted | old_capital, new_capital, writedown_amount, old_nav, new_nav, timestamp | NAV writedown |
VaultSwapExecuted | token_in_mint, token_out_mint, amount_in, amount_out, timestamp | Manager executed swap |
VaultManagerUpdated | old_manager, new_manager, timestamp | Manager changed |
DistributionConfigUpdated | book_value_bps, liquidity_bps, protocol_revenue_bps, timestamp | Yield split changed |
AuthorityTransferProposed | current_authority, pending_authority, timestamp | Transfer proposed |
AuthorityTransferAccepted | old_authority, new_authority, timestamp | Transfer accepted |
MintPauseToggled | paused, timestamp | Pause state changed |
Error Codes
| Error | Description |
|---|---|
Unauthorized | Signer is not the required authority/manager/pause_authority |
MintPaused | Minting is paused (emergency) |
ZeroAmount | Amount must be > 0 |
ZeroSlippage | min_amount_out must be > 0 (slippage protection required) |
MathOverflow | Arithmetic overflow |
InsufficientCapital | Writedown would reduce capital below MIN_CAPITAL_FLOOR |
InvalidDistributionRatios | BPS don’t sum to 10,000 |
SelfTransfer | Cannot transfer authority to yourself |
NoPendingAuthority | No pending authority transfer to accept |
InvalidPendingAuthority | Signer ≠ pending_authority |
Architecture & Integration Guide
Cross-Program Integration
→ Yield Distribution (claims RWT yield)
→ Yield Distribution (claims RWT yield)
- OT revenue (USDC) flows to YD → YD converts USDC to RWT (swap + mint) → creates merkle streams
claim_yieldCPIs toyield_distribution::claimto receive RWT- Vault PDA is a leaf in YD merkle tree (holds OT tokens = eligible for yield)
- Claimed RWT split: 70% NAV / 15% Nexus / 15% ARL Treasury
- Crank must provide valid merkle proof from latest published root
→ Native DEX (manager swaps)
→ Native DEX (manager swaps)
vault_swapCPIs tonative_dex::swap- Vault PDA signs as swap user
- Manager controls direction and slippage
- Supports any pool: RWT/USDC, OT/RWT, OT/USDC
→ Ownership Token (holds OT positions)
→ Ownership Token (holds OT positions)
- Vault buys OT via DEX swaps
- OT balance in vault contributes to NAV
- More OT = more yield from YD = compound growth
→ Team Multisig (protocol authority)
→ Team Multisig (protocol authority)
Trust Assumptions
Deployment Checklist
Prerequisites: OT contract must be deployed first (provides OT Treasury address). Nexus managed separately by team.- Call
initialize_vaultwith:pause_authority= Team Multisig addressareal_fee_destination= Areal Finance USDC ATA (same address as OT contract’sareal_fee_destination)liquidity_destination= Crank wallet RWT ATA (crank routes to Nexus vianexus_deposit)protocol_revenue_destination= ARL Treasury RWT ATA (OT contract)
- Admin mint initial RWT via
admin_mint_rwtbacked by initial OT positions - Transfer authority to Team Multisig via
propose_authority_transfer+accept_authority_transfer
RWT Engine authority = Team Multisig (protocol operations). RWT is a protocol-level asset managed by the team directly, not through Futarchy proposals.
Token Flow Summary
| From | To | Mechanism | Who triggers |
|---|---|---|---|
| User USDC | Capital Accumulator ATA (99.5%) | mint_rwt (net_deposit + vault_fee) | User |
| User USDC | Areal Fee (0.5%) | mint_rwt (dao_fee) | User |
| Vault PDA | User RWT ATA | mint_rwt (mints RWT) | User |
| YD Stream | Vault RWT Claim ATA | claim_yield (CPI claim) | Crank |
| Vault RWT | Crank RWT ATA (15%) → Nexus via nexus_deposit | claim_yield + nexus_deposit | Crank |
| Vault RWT | ARL Treasury ATA (15%) | claim_yield | Crank |
| Vault any token | Vault any token | vault_swap (CPI DEX) | Manager |
| Authority | Vault state | admin_mint_rwt, adjust_capital | Team Multisig |