Repository navigation
Import addresses as watch-only wallets - #964
Merged
Merged
Conversation
Rigidity
force-pushed
the
feat/watch-wallets
branch
2 times, most recently
from
October 29, 2026 03:37
7cdb96a to
ef99a1e
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
3 times, most recently
from
October 29, 2026 03:54
760a49d to
1fb0c0c
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
from
October 29, 2026 04:15
1fb0c0c to
bc7b745
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
2 times, most recently
from
October 29, 2026 04:21
df7d9ad to
c39e2af
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
from
October 29, 2026 04:22
c39e2af to
284c56e
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
3 times, most recently
from
October 29, 2026 04:33
9311ca3 to
0d03189
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
from
October 29, 2026 05:42
1a028cf to
03a6e76
Compare
Rigidity
force-pushed
the
feat/watch-wallets
branch
2 times, most recently
from
October 29, 2026 05:48
18cb578 to
48c7745
Compare
…s resume from the start
Rigidity
force-pushed
the
feat/watch-wallets
branch
from
October 29, 2026 05:49
48c7745 to
216f921
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #947. Part of #945. Stacked on #962.
Lets users import one or more XCH/TXCH addresses as a read-only watch wallet, to follow the balances, assets and history of addresses whose keys Sage doesn't hold (cold storage, another wallet). Watch wallets know only puzzle hashes, never puzzle reveals, so unlike cold wallets they can't even build unsigned transactions. This adds the
watchwallet kind to the plumbing from #961.Wallet config
wallets.tomldefines watch wallets withtype = "watch"and the watchedp2_puzzle_hashes, flat alongside the other fields. A watch entry withoutp2_puzzle_hashesfails to load with a clear error.WalletExistswhatever their prefix, since it's the same wallet on any network.FingerprintCollision.Database and sync
Externalp2 puzzle kind (kind = 4) stores a watched puzzle hash with no puzzle reveal. It counts as custody incustody_p2_puzzle_hashes,is_custody_p2_puzzle_hashand the transaction history's custody columns, so coins and hinted CATs, NFTs, DIDs and options whose inner p2 is a watched address register as owned. No migration is needed, since the kind has no table of its own. The sqlx query cache is regenerated.wallets.tomlbefore syncing, and sync subscribes to every watched puzzle hash like any other custody puzzle. Coin subscriptions skip external puzzles, since the puzzle subscriptions already cover them.0010_synced_p2_puzzles.sqladdsp2_puzzles.is_synced. Existing rows are marked synced, as they were assumed to be before, and new rows start unsynced. A puzzle hash is marked synced only once every page of its sync has been fetched. When a wallet opens, custody puzzle hashes that aren't synced yet (derivations, arbor and watched addresses) are synced from the start of the chain, and the rest resume from the last peak. That covers addresses added while a wallet was closed, a subscription that never went out before the app quit, an address removed and added again, and a sync interrupted partway through an address's history, which used to leave a permanent gap. BLS derivations added by increasing the derivation index get the same protection.ChildKinddetection works unchanged, since it keys off inner p2 puzzle hashes.kind = 3), which were subscribed twice before, since sync already subscribes to their puzzle hashes.is_custody_p2_puzzle_hash, so it was always false. A child at one of the wallet's own puzzle hashes that isn't in the database yet is now skipped, since syncing that puzzle hash delivers it. The root coin's update goes through the batch's open transaction, rather than a second connection that would wait on its write lock.insert_unhardened_derivations,p2_puzzle_hashesandincrease_derivation_indexreturnDerivationsNotSupported.Read-only enforcement
sage_wallet::Wallethas aWatchkind. Building any spend (selecting coins, completing spends, change) returnsWatchOnly("Watch-only wallets can't create or sign transactions"), as does a spend that reaches an external puzzle.master_secret_keyhelper, which returnsWatchOnlyfor watch wallets andNoSigningKeyfor cold ones. That coverssign_coin_spendsand offers. WalletConnect message signing checks it before looking up the address, so a watch wallet getsWatchOnlyinstead ofInvalidKey. Finalizing a clawback checks it too, since that skips the usual spend path when there's no fee.chip0002_getAssetCoinsreturnsWatchOnly, since it can't reveal the puzzles.wallet.signMessage,wallet.sendXchandwallet.signCoinSpends) are refused with awatch_onlyerror before the user is asked to approve them.API
import_watch_wallet { name, addresses, login, emoji }imports a watch wallet from addresses on any network and returns its fingerprint. Importing the same set of addresses twice returnsWalletExists.add_watch_addresses { fingerprint, addresses }andremove_watch_addresses { fingerprint, addresses }change what a watch wallet watches. For the open wallet, added addresses are subscribed right away and removed ones are deleted along with their synced coins, so balances update without a resync. Other wallets catch up when they're opened. A watch wallet keeps at least one address (NoWatchAddresses), and other wallets returnNotWatchWallet.WalletKindgains{ "watch": { addresses } }.get_sync_statusandget_wallet_addressreturn a watch wallet's first address as its receive address. A change address set over RPC doesn't replace it, since watch wallets don't make change.sage wallet statusin the CLI shows that a wallet is watch-only. The new endpoints are also available throughsage rpc.Frontend
WalletContexthasisWatchOnly. Watch wallets are read-only, andisTransactionDisabledstays on even when building unsigned transactions is allowed for cold wallets, so every spend action is disabled and "export unsigned" isn't offered..pocatalogs aren't touched, so the next release needspnpm extract.Not included
Detecting that an imported address is a vault's
P2Singletonaddress, and offering to import the vault, needs the vault puzzles and sync from #948 and #949. It's moved to #950.Testing
pnpm prettier:check,pnpm lint,tsc -b,pnpm test:unit(122 passed)cargo fmt --all -- --files-with-diff --check,cargo clippy --workspace --all-features --all-targetscargo testfor sage-config, sage-database, sage-wallet, sage, sage-rpc, sage-api, sage-apps and sage-cli. New tests cover:wallets.tomlshape and errors of watch entries.