@arkade-os/sdk Documentation - v0.5.0-rc.11
    Preparing search index...

    Class Wallet

    Main wallet implementation for Bitcoin transactions with Arkade protocol support. The wallet does not store any data locally and relies on Arkade and onchain providers to fetch onchain and virtual outputs.

    // Create a wallet with providers
    const wallet = await Wallet.create({
    identity: MnemonicIdentity.fromMnemonic('abandon abandon...'),
    arkProvider: new RestArkProvider(),
    onchainProvider: new EsploraProvider()
    });

    // Use custom providers and/or URLs (e.g., for Expo/React Native)
    const wallet = await Wallet.create({
    identity: MnemonicIdentity.fromMnemonic('abandon abandon...'),
    arkProvider: new ExpoArkProvider('https://arkade.computer'),
    indexerProvider: new ExpoIndexerProvider('https://arkade.computer'),
    onchainProvider: new EsploraProvider('https://mempool.space/api')
    });

    // Get addresses
    const arkAddress = await wallet.getAddress();
    const boardingAddress = await wallet.getBoardingAddress();

    // Send bitcoin
    const txid = await wallet.send({
    address: 'ark1q...',
    amount: 50000,
    });

    Hierarchy (View Summary)

    Implements

    Index

    Properties

    Accessors

    Methods

    Properties

    activity: ActivityRegistry = ...

    Activity resolvers consumed by getActivityHistory.

    arkProvider: ArkProvider

    Re-widened to public: a full wallet's provider is part of its API (ExpoWallet and the delegate manager read it), while ReadonlyWallet keeps it protected so a readonly view cannot hand out submitTx.

    contractRepository: ContractRepository
    delegateProvider?: DelegateProvider
    dustAmount: bigint
    exitDataCapture?: {
        minExitWorthSats?: number;
        mode?: ExitCaptureMode;
        sources?: ExitDataSource[];
    }

    Opt-in exit-data capture settings; see StorageConfig.exitDataCapture.

    forfeitOutputScript: Bytes
    forfeitPubkey: Bytes
    identity: Identity

    Signing identity associated with the wallet.

    A real signer, not a ReadonlyIdentity that structurally fits: contract corridors need all four members — sign, signMessage, signerSession and xOnlyPublicKey — and signerSession is the one a watch-only identity lacks. isSigningIdentity is the check; a wallet that fails it is refused as WalletCannotSignError before anything is funded, rather than at the push that discovers there is no signer.

    indexerProvider: IndexerProvider
    intentRepository?: IntentRepository

    Opt-in intent-lifecycle repository. Assigned by the create() factories from config.storage.intentRepository; undefined ⇒ all intent-persistence code paths are no-ops (default behaviour unchanged).

    network: Network
    onchainProvider: OnchainProvider
    settlementConfig: false | SettlementConfig
    virtualTxRepository?: VirtualTxRepository

    Experimental / inert. Opt-in virtual-tx / exit-branch repository, exposed so callers can pass it to Unroll.Session.create as a best-effort raw-tx cache. Assigned by create() from config.storage.virtualTxRepository. Normal sync never writes it (ContractManager isn't given it); undefined ⇒ no-op.

    walletContractTimelocks: RelativeTimelock[]
    walletRepository: WalletRepository
    MIN_FEE_RATE: number = 1

    Accessors

    • get arkServerPublicKey(): Bytes

      The wallet's current active server signer (x-only, 32 bytes). Read-only from the outside; mutated only via Wallet.setArkServerPublicKeyForRotation during mid-session server-signer rotation (plan §4). Single-valued for wallets that never span a rotation.

      Returns Bytes

    • get defaultContractScript(): string

      Get the pkScript hex for the wallet's primary offchain address. For the full wallet-owned script set registered in ContractManager, use getWalletScripts().

      Returns string

    Methods

    • Parameters

      • descriptor: string

      Returns Promise<void>

      HDAllocationCapable.advanceSigningDescriptorWatermark

    • Build an offchain transaction from the given inputs and outputs, sign it, submit to the Arkade provider, and finalize.

      Signs whatever it is handed, ungated — see settle for the trade.

      Parameters

      Returns Promise<{ arkTxid: string; signedCheckpointTxs: string[] }>

      The Arkade transaction id and server-signed checkpoint PSBTs (for bookkeeping)

    • Claim an ArkadeCash bearer instrument: sweep what can be swept, report the rest.

      Every spendable VTXO at the arkadeCash address is swept to this wallet in its own offchain transaction, signed with the key carried by the arkadeCash string. Nothing is ever persisted: no contract is imported, and the arkadeCash key is not stored. Anything that cannot be swept — server-swept, subdust, already claimed, or asset-bearing — is returned in unclaimed with a per-VTXO reason and otherwise ignored.

      The arkadeCash string is the recovery token: a claim interrupted between submit and finalize is completed by simply re-running claimCash, which drains any pending arkadeCash transaction on the server before sweeping.

      Parameters

      • cashStr: string

        The encoded arkadeCash string (e.g., "arkadecash1...")

      Returns Promise<ArkadeCashClaimResult>

      The swept total and the report of what was left behind. The shape is open: further buckets may be added alongside unclaimed.

    • Create an ArkadeCash bearer instrument.

      Generates a fresh keypair, sends the specified amount to a DefaultVtxo controlled by the new key, and returns the encoded arkadeCash string. The receiver can claim the funds using claimCash() without ever sharing their address.

      ArkadeCash is a short-lived instrument: it carries a private key, so it is meant to be handed over and claimed promptly, not held. A note left unclaimed past its batch expiry is swept by the server and claimCash can only report it, not move it.

      Parameters

      • amount: number

        Amount in satoshis to send. Must be a whole number of sats at or above the dust threshold — a below-dust amount would mint an OP_RETURN output that is unspendable as cash.

      Returns Promise<string>

      The encoded arkadeCash string (e.g., "arkadecash1...")

    • Finalizes pending transactions by retrieving them from the server and finalizing each one. Skips the server check entirely when no send was interrupted (no pending tx flag set).

      Parameters

      • Optionalvtxos: ExtendedVirtualCoin[]

        Optional list of virtual outputs to use instead of retrieving them from the server

      Returns Promise<{ finalized: string[]; pending: string[] }>

      Array of transaction IDs that were finalized

    • Server info for the Arkade server this wallet is connected to, resolved exactly as construction resolves it: live wins, a retryable failure falls back to the snapshot persisted at boot, a terminal one propagates.

      Live rather than the pinned boot snapshot because the fields callers come here for — signerPubkey, checkpointTapscript, fees — are the ones a mid-session rotation moves, and a covenant built against a superseded signer is unspendable.

      One rotation caveat: reading does NOT re-pin the wallet — arkServerPublicKey, dustAmount and the tapscripts move only through handleServerInfoChanged/rotateServerSigner — so inside a rotation window this can report epoch N+1 while the wallet still spends on N. The window closes on its own: a read that observes a moved digest makes the provider emit onServerInfoChanged, which is what drives that rotation. A caller about to bind the answer into a covenant passes { requireLive: true } and fails closed instead of receiving the boot snapshot.

      Parameters

      Returns Promise<ArkadeInfo>

      The Arkade server's info

      ArkadeInfo

    • Chain reads against this wallet's server for scripts it does not own.

      Binds the wallet's own indexerProvider — which may be an Expo or injected one that a caller's hand-built RestIndexerProvider would silently bypass. getVtxos goes through getNormalizedVtxos, which is what makes every VTXO leaving the seam carry its canonical facts.

      A bound object rather than the provider itself, so an IReadonlyWallet holder gets getVtxos/getVirtualTxs and nothing else at runtime. (indexerProvider is still public on the concrete classes, unlike arkProvider, so this narrows the interface rather than the field.)

      Returns Promise<ArkadeReader>

    • The on-chain (P2TR) addresses of every boarding tapscript this wallet uses — the current address plus any historical rotated boarding addresses. The aggregating boarding readers (history, notifications) fan out over this set so deposits at previous boarding addresses are still surfaced (plan §6-IV); getBoardingAddress stays single-valued.

      Returns Promise<string[]>

    • Fetch and cache onchain inputs (UTXOs) received at the wallet's boarding addresses — the current address plus any historical rotated boarding addresses that still hold unspent UTXOs (plan §6-III.1). Each UTXO is annotated with the tapscript of the address it actually sits on, so the spending path forfeits / exits it with the correct per-index leaves.

      Current-signer only: a flatten of getBoardingUtxosForSigners over the wallet's current signer, so the two paths cannot drift. Old-signer boarding recovery goes through the deprecated-signer migration API instead (it would otherwise pull EXPIRED-signer inputs into a plain settle() that the server must reject).

      Returns Promise<ExtendedCoin[]>

    • Fetch and cache onchain inputs (UTXOs) received at the boarding addresses of the given signer set, grouped per boarding address so the caller keeps the address↔signer association that ExtendedCoin cannot carry (it retains only the encoded leaves/tapTree the spend needs, not the DefaultVtxo.Script and its serverPubKey/CSV delay).

      Per group it does exactly what getBoardingUtxos does per tapscript: getCoins → extendCoinWithTapscript → saveUtxos. Offline-first: it does not call getInfo(); the caller supplies the allowed signer set, so the only network calls are the per-address getCoins.

      Parameters

      • allowedSigners: Set<string>

        x-only-hex server keys whose boarding addresses to fetch (passed through to getBoardingTapscripts).

      Returns Promise<BoardingUtxoGroup[]>

    • Get the ContractManager for managing contracts including the wallet's default address.

      The ContractManager handles:

      • The wallet's default receiving address (as a "default" contract)
      • External contracts (Boltz swaps, HTLCs, etc.)
      • Multi-contract watching with resilient connections

      Returns Promise<ContractManager>

      const manager = await wallet.getContractManager();

      // Create a contract for a Boltz swap
      const contract = await manager.createContract({
      label: "Boltz Swap",
      type: "vhtlc",
      params: { ... },
      script: swapScript,
      address: swapAddress,
      });

      // Start watching for events (includes wallet's default address)
      const stop = await manager.onContractEvent((event) => {
      console.log(`${event.type} on ${event.contractScript}`);
      });
    • Allocate a fresh address of each requested type, all derived from one newly allocated HD index.

      This is the explicit allocator the receive path otherwise lacks: getAddress and getBoardingAddress are stable reads of the wallet's display addresses, and the display receive address only advances when a payment arrives (WalletReceiveRotator rotates on vtxo_received). Issuing a second address before the first is paid — one invoice for Alice, another for Bob — has to go through here.

      Each call:

      • allocates one index from the shared HD stream, however many types were asked for, so a default + boarding pair are siblings rather than two burnt indices;
      • builds each requested script at that index, preserving every other option of the wallet's current script for that flavour (including a delegate wallet's delegate shape);
      • persists each as an active contract carrying its signingDescriptor, so the ContractWatcher monitors it, the balance counts it, and signerForDescriptor can recover the key;
      • slides the look-ahead band, since the watermark moved past indices an external issuer may still be handing out.

      The minted rows are deliberately left untagged: the boot lookups adopt the newest WALLET_RECEIVE_SOURCE-tagged row as the display address, and a side address issued to one counterparty must not become the address the wallet advertises to everyone else. getAddress and getBoardingAddress are unchanged by this call.

      A wallet with no HD stream (walletMode: 'static' / 'auto') has one address per flavour for its lifetime. Without forceNew it returns those — the real persisted rows, no index burned; with forceNew it throws WalletCannotAllocateAddressError rather than hand back an address that is not in fact fresh.

      Parameters

      Returns Promise<NewAddress[]>

      const [invoice] = await wallet.getNewAddresses({ forceNew: true });
      // hand `invoice.address` to Alice, keep the descriptor with the invoice
      const signer = await wallet.signerForDescriptor(invoice.signingDescriptor);
    • Allocate and return a fresh on-chain boarding address, rotating the wallet's current boarding tapscript to a new HD index.

      This is the explicit boarding allocator — the analogue of dotnet's GetNextContract(NextContractPurpose.Boarding). Unlike getBoardingAddress (a stable read of the current display address that never burns an index), each call here:

      • allocates the next index from the shared HD stream (so boarding and L2 receive interleave on one monotonic index);
      • builds the boarding tapscript at that index with the boarding-exit CSV;
      • persists an active boarding contract tagged WALLET_RECEIVE_SOURCE (with its signingDescriptor) so the ContractWatcher monitors it, boot can restore it as the current boarding address, and descriptor-aware signing can recover the per-index key;
      • swaps the wallet's current boardingTapscript.

      Gated by walletMode: a static / auto wallet has no descriptor provider and keeps a single index-0 boarding address for its lifetime, so this returns the existing getBoardingAddress unchanged (no rotation, no index burned).

      Behaviour change. A wallet configured with a custom DescriptorProvider (walletMode: <provider>) now rotates here. It previously did not: allocation went through the contract manager, which only wires an allocate hook for HDDescriptorProvider, so such a wallet read as "declined to allocate" and silently kept its index-0 boarding address forever. Those wallets now burn an index per call — including via maybeRotateBoardingAfterBoard, which fires on every settle that consumes a boarding UTXO. Funds at retired addresses stay reachable: each rotation persists its boarding contract before swapping, and getBoardingUtxos fans out over the full historical set.

      Returns Promise<string>

      Use getNewAddresses — it mints any combination of address types at one shared index, and reports the contract row behind each. Note the difference in display behaviour: this method swaps the advertised boarding address, where getNewAddresses mints side addresses and leaves getBoardingAddress alone. To keep this method's behaviour, keep calling this method.

    • Returns Promise<string | undefined>

      HDAllocationCapable.getNextSigningDescriptor

      Every Wallet answers — this is where the wallet's key-provisioning policy lives, so consumers never have to branch on wallet shape:

      • HD: allocate a fresh index through the contract manager (which owns cross-context serialization and the look-ahead band).
      • custom provider: whatever the provider allocates.
      • static / auto: the identity key as a bare tr(pubkey) descriptor — the same answer every call, because that IS the static policy.
    • Composed provider-connection freshness: the LATEST server-info resolution (boot, or any later getArkadeInfo read) combined with the contract-manager's indexer-sync health, if the manager has been initialized. Reads no live provider state — it never forces a ContractManager to construct — so it is safe for readonly callers that only use address/balance APIs.

      • Latest resolution fell back to the cached snapshot → degraded on arkade (cache).
      • Otherwise, if the contract manager has degraded to repository data → degraded on indexer (repository).
      • Otherwise online.

      This only describes sync freshness; wallet balances/VTXOs are always read from the repository regardless of this state.

      Returns ProviderConnectionState

    • Parameters

      • Optionalopts: { lookAhead?: number }

      Returns Promise<string[]>

      HDWalletCapable.getUsedSigningDescriptors

      Union of the watermark band and the descriptors persisted on contracts: the band alone would miss rows a restore scan wrote, and the contracts alone would miss indices allocated for something the wallet never persisted (a swap, an externally issued invoice).

    • Debug-log any explicit input generic selection would have excluded, for each of the three reasons it excludes on. Explicit-input APIs stay ungated on purpose (naming an outpoint is the intent the gate protects, and it is how an escrowed deposit is recovered once generic selection stops covering it), so this only makes the crossing visible — notably settle({ inputs: await wallet.getVtxos() }), which launders the raw read into a spend. Never throws into a spend path.

      Public so the worker handler and plugins can report their own explicit-input crossings through the same three checks.

      Parameters

      • source: string
      • inputs: readonly { script?: string; txid: string; vout: number }[]

      Returns Promise<void>

    • Subscribe to onchain and offchain notifications for newly received funds.

      The onchain watcher tracks the full boarding-address set (current + historical rotated). When boarding rotates after subscribing — e.g. rotate-on-board allocates a fresh address via getNewBoardingAddress — the watcher automatically re-subscribes to widen its set, so a deposit to the new address fires a notification within the same session (no watcher re-init required). The re-subscribe is driven by onBoardingRotation; static / auto / readonly wallets never rotate boarding, so it never fires for them.

      Parameters

      • eventCallback: (coins: IncomingFunds) => void

        Callback invoked when matching funds are detected

      Returns Promise<() => void>

      A function that stops the subscriptions

    • Outpoints of VTXOs whose deprecated signer is past its cutoff (EXPIRED) and which have not yet been swept — unspendable until they recover. Offline: classifies the repo's contracts against the cached signer set (active + _deprecatedSigners, cutoffs included). Empty fast-path when no signer is deprecated. Consumed by getBalance (the pendingRecovery bucket) and by getSpendableVtxos so neither counts nor spends them.

      Takes a fresh contractSnapshot, which syncs against the indexer. Callers that already hold a snapshot — or that must not sync at all, like the worker's balance read — pass it to pendingRecoveryOutpointsIn instead.

      Returns Promise<Set<string>>

    • Refresh the cached deprecated-signer set from a fresh server-info snapshot. Called by the create() factories at construction, by the server-info-change handler mid-session, and by the deprecated-signer migration pass (VtxoManager.migrateDeprecatedSignerVtxos). This set feeds pendingRecoveryOutpoints, which drops EXPIRED (past-cutoff) deprecated-signer VTXOs from the wallet's own coin selection. Lenient: a malformed deprecated entry is skipped, never fatal to wallet creation.

      Parameters

      • info: { deprecatedSigners?: readonly { cutoffDate?: bigint; pubkey?: string }[] }

      Returns void

    • Explicitly recover this wallet's contracts and balance on a fresh repo. HD wallets run a gap-limit scan across the index range; static / non-HD wallets restore based on the single default pubkey. Never throws because of identity/mode (a static identity is a valid, narrower restore); throws on operational failure (so a truncated restore is loud, not silent — the gap window may have closed early). Idempotent and safe to call concurrently (calls coalesce into one scan).

      Ordering is deliberate (spec §3.B / §4): scan → advance the HD watermark → inline VTXO pull → only THEN surface aggregated handler errors, so safely-discovered funds are always recovered even when one discovery handler failed.

      Parameters

      • Optionalopts: { gapLimit?: number }
        • OptionalgapLimit?: number

          Consecutive-unused-index window. Default 20. A non-positive / non-integer value is a programmer error and throws synchronously (distinct from operational failure).

      Returns Promise<void>

      Concurrent calls coalesce: if a restore is already in flight, subsequent callers receive the same promise and their gapLimit is ignored — the first caller's value governs the running scan.

    • Internal

      Mid-session server-signer rotation (plan §4). When arkd rotates its active signer mid-session — the case the long-lived service worker and Expo background processes that own automatic migration must handle — a wallet constructed before the rotation keeps deriving old-signer receive addresses. Building a migration output to such an address would produce a VTXO the server must reject, so the wallet must first re-derive its own receive state under the new active signer.

      Follows the WalletReceiveRotator.rotate write-path pattern with the server key swapped instead of the user key: build the new offchain and boarding tapscripts locally (preserving every other option), register the matching default/delegate and boarding contract rows through ContractManager.createContract, and only then commit the new tapscripts and server key to the wallet's visible state. The signing metadata of the current receive/boarding rows is carried onto the new rows so a rotated (descriptor-backed) receive pubkey can still sign.

      The old-signer contract rows are intentionally left active and watched — they are exactly the deprecated-signer contracts the migration pass drains. Idempotent: a no-op when the wallet already tracks xonly.

      Serialized against HD receive rotation so the two paths (both of which rebuild and swap offchainTapscript) cannot interleave.

      Invoked by the VtxoManager migration pass; not part of the stable public API.

      Parameters

      • newServerPubKey: Bytes
      • checkpointTapscript: string

      Returns Promise<void>

    • Send BTC and/or assets to one or more recipients, passed either as variadic Recipients or as a single SendParams object — the latter also carries the virtual outputs to spend.

      Parameters

      Returns Promise<string>

      Promise resolving to the Arkade transaction ID

      SendParams

      const txid = await wallet.send({
      address: 'ark1q...',
      amount: 1000, // (optional, default to dust) btc amount to send to the output
      assets: [{ assetId: 'abc123...', amount: 50n }] // (optional) list of assets to send
      });

      // choosing the inputs as well as the outputs
      const txid = await wallet.send({
      recipients: [{ address: 'ark1q...', amount: 1000 }],
      selectedVtxos: mine, // spent as given, nothing added
      });
    • Internal

      Migration primitive (deprecated-signer plan, step 1). Spend an explicit set of the wallet's own deprecated-signer VTXOs into a single full-value output on the wallet's active signer, through the Ark send path (not settle) so arkd builds checkpoints against the active server epoch. Consumed in-process by VtxoManager's migration pass; not part of the public IWallet API and never accepts boarding ExtendedCoin inputs.

      The caller (migrateCore) must have already moved the wallet onto the active signer (ensureReceiveOnActiveSigner) and sized the batch (caps + dust floor); this method validates the inputs, preserves all input assets on the self output, and persists the new active-signer VTXO even though there is no separate change output. It records no TxSent history — the funds never leave the wallet.

      Parameters

      Returns Promise<string>

    • Internal

      Sole write path for arkServerPublicKey after construction. Called by Wallet.rotateServerSigner once the rotated offchain and boarding contract rows have been persisted. External code must treat arkServerPublicKey as read-only.

      Parameters

      • serverPubKey: Bytes

      Returns void

    • Internal

      Sole write path for serverUnrollScript after construction. Called by Wallet._doRotateServerSigner with the checkpoint script sourced from the fresh ArkadeInfo that triggered the rotation, so the send path builds checkpoints against the new server epoch. External code must treat serverUnrollScript as read-only.

      Parameters

      Returns void

    • Settle boarding inputs and/or virtual outputs into a finalized mainnet transaction.

      params.inputs is ungated: whatever the caller names is settled, including VTXOs generic selection would skip. That is what makes an escrowed or otherwise gated deposit recoverable by hand — but it also means settle({ inputs: await wallet.getVtxos() }) silently bypasses the gate. Pass getSpendableVtxos instead when the intent is "settle whatever is spendable".

      Parameters

      • Optionalparams: SettleParams

        Optional settlement inputs and outputs. When omitted, the wallet settles all eligible funds.

      • OptionaleventCallback: (event: SettlementEvent) => void

        Optional callback invoked for settlement stream events.

      Returns Promise<string>

      The finalized Arkade transaction id

    • Internal

      Await an onServerInfoChanged handler still applying a rotation.

      The provider emits synchronously but handleServerInfoChanged runs off the emit, so between the two a caller can observe a half-applied rotation: rotateServerSigner persists the active signer's contract rows before committing the tapscripts, so arkServerPublicKey still reads the old key while the new key's rows are already in the repository. A path that refreshes server info and then reads signer-derived state must drain the chain first, or it both reads that torn state and races the handler for the rotation itself.

      Resolves once the chain is idle; a handler that threw already logged, and its rejection is swallowed here.

      Invoked by dispose and the VtxoManager migration pass; not part of the stable public API.

      Returns Promise<void>

    • Parameters

      • descriptor: string

      Returns Promise<Identity>

      HDWalletCapable.signerForDescriptor

      Fail-loud contract: the returned identity is always the descriptor's own key. A descriptor this wallet cannot sign for throws ForeignDescriptorError instead of silently substituting the baseline identity — that identity would sign happily with the wrong key, and the failure would only surface as a rejected transaction or a dead script, far from the call that caused it.

    • Convert this wallet to a readonly wallet.

      Returns Promise<ReadonlyWallet>

      A readonly wallet with the same configuration but readonly identity

      const wallet = await Wallet.create({ identity: MnemonicIdentity.fromMnemonic('abandon abandon...'), ... });
      const readonlyWallet = await wallet.toReadonly();

      // Can query balance and addresses
      const balance = await readonlyWallet.getBalance();
      const address = await readonlyWallet.getAddress();

      // But cannot send transactions (type error)
      // readonlyWallet.send(...); // TypeScript error