Skip to content

Sync vault singletons, their recovery state, and coins at their addresses - #968

Open
Rigidity wants to merge 4 commits into
rigidity/vault-p2-singleton-puzzlesfrom
rigidity/vault-sync
Open

Rigidity wants to merge 4 commits into
rigidity/vault-p2-singleton-puzzlesfrom
rigidity/vault-sync

Conversation

@Rigidity

@Rigidity Rigidity commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #949. Part of #945. Stacked on #967.

Follows tracked vaults on chain: their singleton lineage, custody and recovery state, and the coins and assets at their P2 singleton addresses. Nothing tracks a vault yet outside of tests; importing one comes in #969.

Following a vault

  • A vault coin isn't hinted to anything Sage owns, so a vault is followed from its launcher. Launchers of tracked vaults that haven't been synced are subscribed by coin ID from the start of the chain. A synced launcher is stored with the vault's asset, so the puzzle queue finds the eve coin by syncing its children, and each later coin the same way.
  • ChildKind::Vault parses a vault coin from its parent. The SDK has no Vault::parse, so it runs the parent singleton's inner puzzle, which must be a top-level MIPS tree, and takes the odd CREATE_COIN as the next coin and custody hash. An eve coin's custody hash comes from the memo in its launcher's solution, and is only trusted if it rebuilds the coin's puzzle hash. Without a memo, it stays unknown until the eve coin is spent and reveals its inner puzzle.
  • Every vault coin is stored with its custody hash, plus a lineage proof once it has a vault parent. Vault coins have no p2 puzzle, so they're never part of the XCH balance. Unspent vault coins are subscribed by coin ID, so after a restart sync resumes from the stored lineage instead of the launcher.
  • Coins are only treated as a vault's if its launcher ID is tracked.

Configs and state

Addresses

  • Each tracked vault keeps 100 unused P2 singleton addresses past the highest one that has received a coin, like derivations. Those addresses count as custody, so XCH sent to them, and CATs, NFTs, DIDs and options hinted to them, are found by the existing sync. The receive address is nonce 0.
  • Coins locked to fixed conditions or a vault (P2ConditionsOrSingleton), such as Cloud Wallet's offer pre-split coins, are recognized from their (vault_address, (conditions, ...)) memos, for XCH and CATs. They're only kept if the address in the memos is one of our vault addresses, and the coin's p2 puzzle hash can be rebuilt from that vault and the conditions. Both puzzle versions are tried, with the address's nonce and nonce 0, as Cloud Wallet uses. Anyone can write these memos, so a CAT's lock is checked before it can add or change anything about its asset.

Transaction history

Transactions are grouped by block, so a vault spend and the coins it spends from its addresses already show up as one transaction.

Tests

Peer simulator tests in sage_wallet::vault_sync spend real vaults with BLS keys:

  • mint, custody spend, recovery start, cancel, recovery start again, finish after the timelock, rekey and melt, for both puzzle versions. They check the custody hash, config and state at every step, and the state change event.
  • a vault minted without memos, whose balances are still shown, and whose eve custody hash is filled in once it's spent.
  • payments to nonce 0 and nonce 7, including a CAT and an NFT, and the address gap growing once the last address is paid.
  • an offer pre-split coin, which is kept and shown as the vault's address in history, and one with the same memo format that isn't locked to the vault, which is ignored.
  • the same for CATs: a pre-split CAT locked to the vault is in its balance, and an unrelated one with the same memos leaves no asset behind.
  • a vault spent while the wallet is closed, which is picked up from its stored lineage after a restart.
  • a vault without recovery, spent through its custody at the top level, then rekeyed to add recovery and to remove it again, with its config decoded from the memos each time.

A database test covers deriving the state from the lineage, including a reorg and a melt.

Not included

  • NFT offer pre-split coins aren't recognized. Cloud Wallet tracks them on its server rather than with memos.
  • Vault coins appear in the transaction history with the vault asset. Showing custody changes differently can come with Recover, cancel, and rekey vaults #954.

Review-loop changes

  • A coin locked to fixed conditions or a vault only counts as ours, including for mempool transactions, once its puzzle hash is rebuilt from the vault address in its memos.
  • Transaction confirmations show such a coin at its own puzzle hash, not the vault address its memos claim, since anyone can write them.

@Rigidity

Copy link
Copy Markdown
Collaborator Author

Screenshots

Taken in pnpm sandbox (alice, 1280 wide) with the vaults it now mints (#969). After import, the vault's coins sync: the 1 TXCH sent to its first address shows as its balance, the vault singleton appears in the history, and a vault whose recovery was started on chain syncs to Recovery pending.

Balance from the vault's address History

The full end-to-end run of every import method is in the comment on #969.

@Rigidity
Rigidity force-pushed the rigidity/vault-sync branch from cb739cf to 3956937 Compare November 15, 2026 01:10
- Only count a coin locked to fixed conditions or a vault as ours once its puzzle hash is rebuilt
  from the vault address in its memos, so a stranger's transaction can't be taken for ours, and
  kept pending, because its memos name one of our vault addresses.
- Share the test harness's sync setup with restart_sync, which left out the mempool source.
- Test pre-split coins with the Legacy puzzle version, and with memos naming a later address.
- Test that a launcher memo for another config doesn't set the eve coin's custody or config.
Its memos name a vault address, but anyone can write them and the lock isn't checked when a
transaction is summarized, so a dApp's spend could show a payment to its own puzzle hash as one
to the user's vault. Also test the CAT branch of the relevance check.
@Rigidity
Rigidity force-pushed the rigidity/vault-sync branch from 3956937 to 50d4e94 Compare November 15, 2026 06:00

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sync vault singletons, recovery state, and P2 singleton coins

1 participant