Skip to content

Conversation

@Kukks
Copy link
Contributor

@Kukks Kukks commented Feb 1, 2026

Summary

This PR adds HD wallet support infrastructure with two major changes:

1. Contract Layer Field

  • Added ContractLayer type ("onchain" | "offchain" | "both") to Contract interface
  • Added layer index to IndexedDB schema for fast contract lookups at wallet startup
  • Updated ContractFilter and both repository implementations to support layer filtering
  • Removed BoardingContractHandler (boarding is just DefaultVtxo with longer timelock)
  • Removed scriptType from ContractHandler (replaced by Contract.layer)

2. Four-Category Balance Model

Replaced the confusing spendable/unspendable/recoverable model with clear spend-path categories:

Category Description Example
offchainSpendable Instant Ark transfers settled/preconfirmed VTXOs
batchSpendable Requires batch round swept VTXOs, confirmed boarding
onchainSpendable Requires unilateral exit expired batch, must withdraw onchain
locked Not spendable yet unconfirmed boarding, active timelocks

This gives wallet UIs actionable information:

  • "Send instantly" → offchainSpendable
  • "Available after next batch" → batchSpendable
  • "Requires onchain withdrawal" → onchainSpendable

Test plan

  • Build passes
  • Unit tests pass
  • Integration tests pass

Kukks added 29 commits January 30, 2026 14:52
Introduces SeedIdentity and ReadonlySeedIdentity classes that derive
keys using BIP86 (Taproot) paths. This enables users to transition
to HD wallets while maintaining current single-key behavior.

- SeedIdentity.fromMnemonic() with optional passphrase support
- SeedIdentity.fromSeed() for raw 64-byte seeds
- BIP86 derivation: m/86'/{0|1}'/0'/0/0 (mainnet/testnet)
- Descriptor format: tr([fingerprint/path']xpub.../0/*)
- Strict validation requiring /0/* template for HD compatibility
- JSON serialization with xpub validation on restore
- ReadonlySeedIdentity for watch-only functionality
Add rudimentary HD wallet methods to enable multi-address derivation:

SeedIdentity new methods:
- deriveSigningDescriptor(index): derive descriptor at specific index
- isOurs(descriptor): check if descriptor belongs to this identity
- signWithDescriptor(descriptor, requests): batched transaction signing
- signMessageWithDescriptor(descriptor, message): message signing at index

ReadonlySeedIdentity new methods:
- deriveSigningDescriptor(index): derive descriptor at specific index
- isOurs(descriptor): check if descriptor belongs to this identity
- xOnlyPublicKeyAtIndex(index): get x-only pubkey at index
- compressedPublicKeyAtIndex(index): get compressed pubkey at index

New types:
- SigningRequest: interface for batched signing requests

Backwards compatibility maintained - existing methods work at index 0.
- isDescriptor(): checks if string is tr(...) format
- normalizeToDescriptor(): wraps hex pubkey as tr(pubkey)
- extractPubKey(): extracts pubkey from simple descriptor
- parseHDDescriptor(): parses HD descriptor components

Enables backwards compatibility for legacy hex pubkeys.
Interface for signing with output descriptors, abstracting
HD vs static key implementations.
Wraps existing Identity implementations to provide DescriptorProvider
interface with static tr(pubkey) descriptors.
- Change DefaultContractParams to use string descriptors instead of Uint8Array
- Add normalizeToDescriptor() for backwards compatibility with hex pubkeys
- Update serializeParams/deserializeParams for descriptor format
- Update test helpers to use valid secp256k1 public keys
- All existing tests pass with the new format
- Add walletDescriptor field to PathContext (preferred over walletPubKey)
- Mark walletPubKey as @deprecated with migration note
- Update resolveRole to use walletDescriptor with fallback to walletPubKey
- Handle descriptor-to-pubkey extraction for role matching
Design document for HD wallet support covering:
- Interface hierarchy (IBaseWallet, ISingleKeyWallet, IHDWallet)
- Contract-based balance model with boarding as contract type
- Handler-based descriptor resolution for multi-path contracts
- PathSelection with descriptor for signing
- Add IBaseWallet, ISingleKeyWallet, IHDWallet interfaces
- Add HDWallet class with getAddresses(index) and contract-based getBalance()
- Add AddressInfo, ContractBalance, HDWalletBalance types
- Add WalletDescriptorInfo for multi-path descriptor resolution
- Add descriptor field to PathSelection for HD signing
- Implement getWalletDescriptors in DefaultContractHandler and VHTLCContractHandler
- Add deriveSigningDescriptor to DescriptorProvider interface
- Create BoardingContractHandler for unified balance model
- Refactor Wallet to implement ISingleKeyWallet (IWallet is now deprecated alias)
- Add ScriptType = "onchain" | "offchain" | "both" to contracts/types.ts
- Add scriptType property to ContractHandler interface
- DefaultContractHandler: scriptType = "offchain" (VTXOs via indexer)
- VHTLCContractHandler: scriptType = "offchain" (VTXOs via indexer)
- BoardingContractHandler: scriptType = "onchain" (UTXOs via esplora)

This enables ContractWatcher and balance aggregation to know which
provider to use for monitoring each contract type.
- Add ContractLayer type ("onchain" | "offchain" | "both") to Contract
- Add layer index to IndexedDB schema for fast contract lookups
- Update ContractFilter and repositories to support layer filtering
- Remove BoardingContractHandler (boarding uses DefaultVtxo with longer timelock)
- Remove scriptType from ContractHandler (moved to Contract.layer)

Balance model now categorizes coins by spend path:
- offchainSpendable: instant Ark transfers (settled/preconfirmed VTXOs)
- batchSpendable: requires batch round (swept, confirmed boarding)
- onchainSpendable: requires unilateral exit (expired batch)
- locked: not spendable yet (unconfirmed, timelocked)

This provides UIs with actionable balance information:
- "Send instantly" → offchainSpendable
- "Available after next batch" → batchSpendable
- "Requires onchain withdrawal" → onchainSpendable
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • next-version

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hd-wallet-interface

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Kukks added 6 commits February 1, 2026 22:57
Add required `layer` property to all Contract object creations:
- arkcontract.ts: both contractFromArkContract functions
- request.ts: CreateContract interface
- wallet.ts: default contract initialization
Extends ContractWatcher to support watching onchain UTXOs based on the
contract's layer field:

- layer: "offchain" -> watched via IndexerProvider subscription
- layer: "onchain" -> watched via OnchainProvider.watchAddresses
- layer: "both" -> watched via both providers

New event types:
- coin_received: emitted when new onchain coins arrive
- coin_confirmed: emitted when pending coins are confirmed

This enables unified contract watching where contracts can receive
both offchain VTXOs and onchain UTXOs through the same event system.
Tests were failing because mock contracts lacked the layer field,
which is now required for subscription filtering.
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.

2 participants