-
Notifications
You must be signed in to change notification settings - Fork 32
feat(btc): add PSBT signing and broadcasting support #334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3aa4425
d0af061
5d4ad4e
52d54a3
d95d2e1
13f666c
95d570b
ea7e626
f458ad0
e864ad7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| export * from "./psbt.js"; | ||
| export * from "./signerBtc.js"; | ||
| export * from "./signerBtcPublicKeyReadonly.js"; | ||
| export * from "./verify.js"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /** | ||
| * Options for signing a PSBT (Partially Signed Bitcoin Transaction) | ||
| */ | ||
| export type SignPsbtOptions = { | ||
| /** | ||
| * Whether to finalize the PSBT after signing. | ||
| * Default is true. | ||
| */ | ||
| autoFinalized?: boolean; | ||
| /** | ||
| * Array of inputs to sign | ||
| */ | ||
| toSignInputs?: ToSignInput[]; | ||
| }; | ||
|
|
||
| /** | ||
| * Specification for an input to sign in a PSBT. | ||
| * Must specify at least one of: address or pubkey. | ||
| */ | ||
| export type ToSignInput = { | ||
| /** | ||
| * Which input to sign (index in the PSBT inputs array) | ||
| */ | ||
| index: number; | ||
| /** | ||
| * (Optional) Sighash types to use for signing. | ||
| */ | ||
| sighashTypes?: number[]; | ||
| /** | ||
| * (Optional) When signing and unlocking Taproot addresses, the tweakSigner is used by default | ||
| * for signature generation. Setting this to true allows for signing with the original private key. | ||
| * Default value is false. | ||
| */ | ||
| disableTweakSigner?: boolean; | ||
| } & ( | ||
| | { | ||
| /** | ||
| * The address whose corresponding private key to use for signing. | ||
| */ | ||
| address: string; | ||
| /** | ||
| * The public key whose corresponding private key to use for signing. | ||
| */ | ||
| publicKey?: string; | ||
| } | ||
| | { | ||
| /** | ||
| * The address whose corresponding private key to use for signing. | ||
| */ | ||
| address?: string; | ||
| /** | ||
| * The public key whose corresponding private key to use for signing. | ||
| */ | ||
| publicKey: string; | ||
| } | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import { KnownScript } from "../../client/index.js"; | |
| import { HexLike, hexFrom } from "../../hex/index.js"; | ||
| import { numToBytes } from "../../num/index.js"; | ||
| import { Signer, SignerSignType, SignerType } from "../signer/index.js"; | ||
| import { SignPsbtOptions } from "./psbt.js"; | ||
| import { btcEcdsaPublicKeyHash } from "./verify.js"; | ||
|
|
||
| /** | ||
|
|
@@ -22,6 +23,32 @@ export abstract class SignerBtc extends Signer { | |
| return SignerSignType.BtcEcdsa; | ||
| } | ||
|
|
||
| /** | ||
| * Whether the wallet supports a single call to sign + broadcast (combined flow). | ||
| * Default false; override in implementations like Xverse/JoyID. | ||
| */ | ||
| get supportsSingleCallSignAndBroadcast(): boolean { | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Sign and broadcast a PSBT in one call when supported, otherwise falls back | ||
| * to sign then push. Prefer this over manual sign+push to avoid double popups. | ||
| */ | ||
| async signAndPushPsbt( | ||
| psbtHex: string, | ||
| options?: SignPsbtOptions, | ||
| ): Promise<string> { | ||
| if (this.supportsSingleCallSignAndBroadcast) { | ||
| // Wallet handles sign+broadcast internally (e.g., Xverse/JoyID) | ||
| return this.pushPsbt(psbtHex, options); | ||
| } | ||
|
|
||
| // Split-mode wallets: sign first, then broadcast | ||
| const signedPsbt = await this.signPsbt(psbtHex, options); | ||
| return this.pushPsbt(signedPsbt, options); | ||
| } | ||
|
Comment on lines
+38
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To improve type safety and consistency across the codebase, I recommend changing the return types of The For example, abstract signPsbt(
psbtHex: string,
options?: SignPsbtOptions,
): Promise<Hex>;This would require updating the implementing classes. For instance, in the Xverse signer, you could then remove
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bitcoin tx IDs and PSBTs are bare hex strings with no |
||
|
|
||
| /** | ||
| * Gets the Bitcoin account associated with the signer. | ||
| * | ||
|
|
@@ -123,4 +150,32 @@ export abstract class SignerBtc extends Signer { | |
| tx.setWitnessArgsAt(info.position, witness); | ||
| return tx; | ||
| } | ||
|
|
||
| /** | ||
| * Signs a Partially Signed Bitcoin Transaction (PSBT). | ||
| * | ||
| * @param psbtHex - The hex string of PSBT to sign | ||
| * @param options - Options for signing the PSBT | ||
| * @returns A promise that resolves to the signed PSBT hex string | ||
| */ | ||
| abstract signPsbt( | ||
| psbtHex: string, | ||
| options?: SignPsbtOptions, | ||
| ): Promise<string>; | ||
|
|
||
| /** | ||
| * Pushes a PSBT to the Bitcoin network. | ||
| * | ||
| * For wallets that support a single call for signing and broadcasting (where `supportsSingleCallSignAndBroadcast` is true), | ||
| * this method takes an **unsigned** PSBT, signs it, and broadcasts it. | ||
| * For other wallets, this method takes a **signed** PSBT and only broadcasts it. | ||
| * | ||
| * @param psbtHex - The hex string of the PSBT to push. Can be signed or unsigned depending on the wallet's capabilities. | ||
| * @param options - Options for signing the PSBT. Only used by wallets that perform signing in this step. | ||
| * @returns A promise that resolves to the transaction ID. | ||
| */ | ||
| abstract pushPsbt( | ||
| psbtHex: string, | ||
| options?: SignPsbtOptions, | ||
| ): Promise<string>; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
ToSignInputtype has duplicated properties and comments foraddressandpublicKey. This can be refactored to improve readability and maintainability by defining the properties once and then expressing the constraint that at least one of them must be present.