(03)Technology

Vaults

An OstraVault holds one token for an agent and spends it only with a rotating one-time key. Anyone may submit a spend; only the signature decides what it does.

Chain Robinhood Chain · 4663Code 64c3213

3.1What a vault is#

A vault is a small contract that holds a single ERC-20 token, fixed when it is created OstraVault.sol:263-270. It stores the root of its current one-time key and a nonce, the number of spends so far, which is also the index of the current key OstraVault.sol:182-185. It has no owner address and no admin function, and it moves nothing but its own token: a swap's output goes from Uniswap straight to the recipient the key signed OstraVault.sol:54-113.

Every spend follows the same steps OstraVault.sol:285-351:

  1. The parameters must have the exact length of their action, or the spend reverts OstraVault.sol:353-381.
  2. The vault rebuilds the signed message from the chain id, its own address, its nonce, the next key root, the action and the parameters, and recovers the key root from the WOTS signature.
  3. That root must be the current key root (or, during a recovery, the recovered one).
  4. The key rotates and the nonce advances before any external call.
  5. The action runs.

Because the signature binds everything that moves value, whoever submits it, the relayer or anyone else, can only send it as it is. A spend that did not land is sent again with the same bytes, never signed again: there is no expiry OstraVault.sol:110-113.

3.2The ten actions#

#ActionWhat it does
0TransferSends an amount of the vault’s token to an address.
1OpenChannelOpens a payment channel from the vault (how an agent pays the relayer).
2SetCrankKeySets or removes the key that may settle the vault’s payment channels. That key can only send escrow back to the vault or apply vouchers the payer signed.
3SwapAn exact-input Uniswap v4 swap of the vault’s token, with the vault itself as the swapper.
4ConfigurePoolCalls configure on a hook as this vault: how a creator vault changes its pool’s rules.
5ClaimDividendClaims the dividends a launched token owes this vault, to a recipient it signs.
6SetGuardianAdds or removes a recovery guardian.
7SetRecoveryPolicySets how many guardians must agree and how long a recovery then waits.
8SupportRecoveryAs a guardian, supports moving another vault to a new key root.
9DevBuyPays the dev buy of exactly one launch, which only OstraDevBuy can send.

The action numbers are constants OstraVault.sol:131-140, and each one is documented with its parameters in the contract headerOstraVault.sol:54-113. A DevBuy signature names the hash of every launch and dev buy parameter, so even once public it can only ever make the launch its owner signed Launchpad.fork.t.sol:2104-2142.

3.3Swaps from a vault#

A vault swaps by unlocking the Uniswap v4 PoolManager itself, so the swapper a pool's hook sees is the vault. The signed parameters fix the pool, the direction, the amount in, the minimum out, the recipient, an optional referrer and the exact gas the swap gets OstraVault.sol:451-484.

  • The gas is signed. The PoolManager call gets exactly swapGas, between 100,000 and 10,000,000, whatever gas the sender attached; if not enough is left, the whole spend reverts and the key is not used Vault.fork.t.sol:706-735.
  • A failed swap still uses the key. A swap that fails inside the PoolManager, below its minimum, refused by a hook's circuit breaker or by a token, moves nothing, emits SwapFailed and rotates the key. A market that moved can never hold a signed key hostage.
  • The output can go to another vault. A buy of a launched token sends it straight to the agent's vault for that token Launchpad.fork.t.sol:604-622.

3.4The vaults of one agent#

One master seed gives an agent several vaults, each with its own keychain keys.ts:38-48:

VaultTokenUsed for
Main vaultUSDGThe agent’s funds: buys, withdrawals, shielding, paying the relayer.
Token vaultsA launched tokenWhat the agent bought; sells go back to the main vault.
Creator vaultUSDGOwns the pools the agent launches, receives creator payouts, pays dev buys and airdrops.
Private vaultsUSDGOne new vault per shielded leaf the agent spends, so unshielded funds are not gathered in one place.

Keychain ids are public; the only secret is the master seed, so the seed alone recovers every vault, deposit and leaf of the agentkeys.ts:1-9.

3.5Creating a vault#

OstraVaultFactory.createVault(token, initialKeyRoot, initialCrank) deploys a beacon proxy with CREATE2. The address depends only on the token, the first key root and the crank, so it is known before the vault exists, and calling the factory again for an existing vault returns it instead of failing. A front-runner can only create the very vault its owner asked for OstraVaultFactory.sol:22-35.

3.6Recovery#

A one-time key scheme lives or dies with its seed. A vault can name guardians, any accounts, ideally other Ostra vaults, which keeps recovery post-quantum. The owner sets a threshold and a delay between 36 hours and 14 days OstraVault.sol:161-164.

  1. The owner's new device derives a new key root for the vault's current nonce and gives it to the guardians.
  2. Guardians support that root. The support that reaches the threshold starts the delay. One recovery per round: the first proposal to reach the threshold is the one that waits OstraVault.sol:553-573.
  3. After the delay anyone may finish it, or the new seed's first spend finishes it itself OstraVault.sol:587-598.

The owner always wins. Any spend signed by the current key starts a new round: the waiting recovery and every support gathered so far stop counting OstraVault.sol:115-118. Guardians cannot take a vault from an owner who still holds its key and spends within the delay.

Two more layers sit off chain: an encrypted backup of the seed, which the owner exports in a terminal with a passphrase the agent never sees (scrypt and AES-256-GCM) backup-cli.ts:1-15, and the relayer's signature escrow, which lets a restored machine finish what the old one signed.

3.7Upgrades: 72 hours of notice#

Every vault is a beacon proxy, so one change of the vault beacon's implementation changes the code of every vault at once. Whoever can do that could, in principle, take every vault's funds without a single WOTS signature. Ostra treats that power as the most important key of the system, and it is not an elliptic-curve key.

The vault beacon 0xE9F5…18a0 and the pool beacon are owned by OstraUpgradeOwner 0x5553…6E5e, a contract that only acts on a WOTS signature from its own rotating key, and only 72 hours after the call was published on chain OstraUpgradeOwner.sol:6-28. During those 72 hours the full calldata of the upgrade is public in its CallScheduled event, and every vault owner can withdraw before a change they do not accept. The security page describes it in full.

3.8Limits#

  • A vault holds one ERC-20 token and no native ETH. A swap that pays out ETH must name a recipient that accepts ETH, such as an ordinary wallet.
  • The MCP server reports a soft limit of 250 USDG per vault; the contract does not enforce it config.ts:8-13.
  • A signed spend that never lands blocks the vault until it is sent, by design: nothing else may be signed with that key vault.ts:17-19.
  • The contract does not refuse a zero next key root; the signer does. A vault rotated to zero could only be moved by recovery.
  • A guardian that is an ordinary wallet is an elliptic-curve key. Use vaults as guardians to keep recovery post-quantum.