Skip to content

SignerBtc.signPsbt options (autoFinalized, inputsToSign) are not normalized consistently across wallets, causing divergent behavior #517

Description

@yixyxiu

Description

SignPsbtOptionsLike (packages/core/src/signer/btc/psbt.ts) declares:

  • autoFinalized?: boolean — documented as "Default is true."
  • inputsToSign?: InputToSignLike[]

However, the BTC wallet signer implementations handle this inconsistently, so a dApp calling signer.signPsbt(psbtHex) (or with an incomplete options) can get different results depending on which wallet is connected — even though the caller wrote identical code.

Where the inconsistency comes from

  • JoyID (packages/joy-id/src/btc/index.ts) correctly normalizes options via ccc.SignPsbtOptions.from(options) before use, so autoFinalized reliably defaults to true as documented.

  • UniSat (packages/uni-sat/src/signer.ts) and OKX (packages/okx/src/btc/index.ts) pass the raw options argument straight through to the injected wallet provider's signPsbt(psbtHex, options):

    async signPsbt(psbtHex: ccc.HexLike, options?: ccc.SignPsbtOptionsLike): Promise<ccc.Hex> {
      return ccc.hexFrom(
        await this.provider.signPsbt(ccc.hexFrom(psbtHex).slice(2), options),
      );
    }

    When options (or options.autoFinalized) is undefined, the actual default is whatever UniSat's/OKX's own extension implements — not guaranteed to match CCC's documented default of true.

    Worse, UniSat's and OKX's real APIs expect the field name toSignInputs, not inputsToSign (see UniSat docs, OKX docs). Since options is forwarded unmodified, a caller who sets options.inputsToSign on these two wallets has it silently ignored — the wallet instead falls back to signing all inputs matching the connected address.

  • Xverse (packages/xverse/src/signer.ts) never sends any autoFinalized/finalize-equivalent field in its RPC request at all:

    this.provider.request("signPsbt", {
      psbt: psbtBase64,
      signInputs,
      broadcast: false,
    })

    So options.autoFinalized is silently dropped for Xverse regardless of what the caller sets.

Impact

A dApp built against the ccc.SignerBtc abstraction that calls signPsbt(psbtHex) (relying on the documented default) or signPsbt(psbtHex, { autoFinalized: false, inputsToSign: [...] }) can get:

  • A fully-finalized PSBT from one wallet and a non-finalized one from another (or vice-versa), even though no option was changed.
  • Silently-ignored inputsToSign on UniSat/OKX, causing unintended inputs to be signed.

This breaks the abstraction's promise that identical SignerBtc calls behave consistently across wallets.

Suggested fix

  • In every SignerBtc implementation, normalize the incoming options via ccc.SignPsbtOptions.from(options) first (as JoyID already does), so the documented default (autoFinalized: true) is always honored regardless of wallet.
  • For UniSat/OKX, translate the normalized SignPsbtOptions into the wallet's actual expected shape ({ autoFinalized, toSignInputs }) instead of forwarding the CCC-level object as-is.
  • For Xverse, actually forward autoFinalized (mapped to whatever Xverse's signPsbt RPC method supports) instead of dropping it, or throw/warn if Xverse has no equivalent so callers aren't silently misled.
  • Consider adding a shared test (e.g. against mocked providers) asserting that all SignerBtc implementations apply the same default/behavior for a given SignPsbtOptionsLike.

Relevant files

  • packages/core/src/signer/btc/psbt.ts
  • packages/joy-id/src/btc/index.ts (signPsbt)
  • packages/uni-sat/src/signer.ts (signPsbt)
  • packages/okx/src/btc/index.ts (signPsbt)
  • packages/xverse/src/signer.ts (signPsbt, signAndBroadcastPsbt, prepareSignPsbtParams)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions