diff --git a/packages/core/src/signer/btc/signerBtc.ts b/packages/core/src/signer/btc/signerBtc.ts index 64112a74..cb77de1b 100644 --- a/packages/core/src/signer/btc/signerBtc.ts +++ b/packages/core/src/signer/btc/signerBtc.ts @@ -123,4 +123,21 @@ 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 + * @returns A promise that resolves to the signed PSBT hex string + * @todo Add support for Taproot signing options (useTweakedSigner, etc.) + */ + abstract signPsbt(psbtHex: string): Promise; + + /** + * Broadcasts a signed PSBT to the Bitcoin network. + * + * @param psbtHex - The hex string of signed PSBT to broadcast + * @returns A promise that resolves to the transaction ID + */ + abstract pushPsbt(psbtHex: string): Promise; } diff --git a/packages/core/src/signer/btc/signerBtcPublicKeyReadonly.ts b/packages/core/src/signer/btc/signerBtcPublicKeyReadonly.ts index 50096db7..ba7e9322 100644 --- a/packages/core/src/signer/btc/signerBtcPublicKeyReadonly.ts +++ b/packages/core/src/signer/btc/signerBtcPublicKeyReadonly.ts @@ -70,4 +70,12 @@ export class SignerBtcPublicKeyReadonly extends SignerBtc { async getBtcPublicKey(): Promise { return this.publicKey; } + + async signPsbt(_: string): Promise { + throw new Error("Read-only signer does not support signPsbt"); + } + + async pushPsbt(_: string): Promise { + throw new Error("Read-only signer does not support pushPsbt"); + } } diff --git a/packages/joy-id/src/btc/index.ts b/packages/joy-id/src/btc/index.ts index b564119e..ba51bd6a 100644 --- a/packages/joy-id/src/btc/index.ts +++ b/packages/joy-id/src/btc/index.ts @@ -25,6 +25,16 @@ export class BitcoinSigner extends ccc.SignerBtc { throw new Error("Not connected"); } + // Additional validation to ensure connection has valid address + if ( + !this.connection.address || + typeof this.connection.address !== "string" + ) { + throw new Error( + "Invalid connection - missing or invalid Bitcoin address", + ); + } + return this.connection; } @@ -198,4 +208,65 @@ export class BitcoinSigner extends ccc.SignerBtc { ); return signature; } + + /** + * Signs a PSBT using JoyID wallet. + * + * @param psbtHex - The hex string of PSBT to sign + * @returns A promise that resolves to the signed PSBT hex string + */ + async signPsbt(psbtHex: string): Promise { + const { address } = await this.assertConnection(); + + const config = this.getConfig(); + const { tx: signedPsbtHex } = await createPopup( + buildJoyIDURL( + { + ...config, + tx: psbtHex, + signerAddress: address, + autoFinalized: true, + }, + "popup", + "/sign-psbt", + ), + { ...config, type: DappRequestType.SignPsbt }, + ); + + return signedPsbtHex; + } + + /** + * Signs and broadcasts a PSBT to the Bitcoin network using JoyID wallet. + * + * This method combines both signing and broadcasting in a single operation. + * + * @param psbtHex - The hex string of PSBT to sign and broadcast + * @returns A promise that resolves to the transaction ID + * + * @remarks + * Use this method directly for sign+broadcast operations to avoid double popups. + * While calling signPsbt() then pushPsbt() will still work, it triggers two popups and requires double signing. + */ + async pushPsbt(psbtHex: string): Promise { + const { address } = await this.assertConnection(); + + const config = this.getConfig(); + const { tx: txid } = await createPopup( + buildJoyIDURL( + { + ...config, + tx: psbtHex, + signerAddress: address, + autoFinalized: true, // sendPsbt always finalizes + isSend: true, + }, + "popup", + "/sign-psbt", + ), + { ...config, type: DappRequestType.SignPsbt }, // Use SignPsbt type for both operations + ); + + return txid; + } } diff --git a/packages/okx/src/advancedBarrel.ts b/packages/okx/src/advancedBarrel.ts index 4704b662..bdd6b3e0 100644 --- a/packages/okx/src/advancedBarrel.ts +++ b/packages/okx/src/advancedBarrel.ts @@ -2,8 +2,21 @@ import { Nip07A } from "@ckb-ccc/nip07/advanced"; import { UniSatA } from "@ckb-ccc/uni-sat/advanced"; export interface BitcoinProvider - extends Pick, - Partial> { + extends Pick< + UniSatA.Provider, + "on" | "removeListener" | "signMessage" | "signPsbt" | "pushPsbt" + >, + Partial< + Omit< + UniSatA.Provider, + | "on" + | "removeListener" + | "signMessage" + | "signPsbt" + | "pushPsbt" + | "pushTx" + > + > { connect?(): Promise<{ address: string; publicKey: string; diff --git a/packages/okx/src/btc/index.ts b/packages/okx/src/btc/index.ts index 3ef023d9..400d591d 100644 --- a/packages/okx/src/btc/index.ts +++ b/packages/okx/src/btc/index.ts @@ -176,4 +176,24 @@ export class BitcoinSigner extends ccc.SignerBtc { return this.provider.signMessage(challenge, "ecdsa"); } + + /** + * Signs a PSBT using OKX wallet. + * + * @param psbtHex - The hex string of PSBT to sign + * @returns A promise that resolves to the signed PSBT hex string + */ + async signPsbt(psbtHex: string): Promise { + return this.provider.signPsbt(psbtHex); + } + + /** + * Broadcasts a signed PSBT to the Bitcoin network. + * + * @param psbtHex - The hex string of signed PSBT to broadcast + * @returns A promise that resolves to the transaction ID + */ + async pushPsbt(psbtHex: string): Promise { + return this.provider.pushPsbt(psbtHex); + } } diff --git a/packages/uni-sat/src/advancedBarrel.ts b/packages/uni-sat/src/advancedBarrel.ts index e6ae56b5..612c6273 100644 --- a/packages/uni-sat/src/advancedBarrel.ts +++ b/packages/uni-sat/src/advancedBarrel.ts @@ -2,6 +2,23 @@ * Interface representing a provider for interacting with accounts and signing messages. */ export interface Provider { + /** + * Signs a PSBT using UniSat wallet. + * + * @param psbtHex - The hex string of PSBT to sign + * @returns A promise that resolves to the signed PSBT hex string + * @todo Add support for Taproot signing options (useTweakedSigner, etc.) + */ + signPsbt(psbtHex: string): Promise; + + /** + * Broadcasts a signed PSBT to the Bitcoin network. + * + * @param psbtHex - The hex string of signed PSBT to broadcast + * @returns A promise that resolves to the transaction ID + */ + pushPsbt(psbtHex: string): Promise; + /** * Requests user accounts. * @returns A promise that resolves to an array of account addresses. diff --git a/packages/uni-sat/src/signer.ts b/packages/uni-sat/src/signer.ts index 653bba8e..109db82e 100644 --- a/packages/uni-sat/src/signer.ts +++ b/packages/uni-sat/src/signer.ts @@ -150,4 +150,24 @@ export class Signer extends ccc.SignerBtc { return this.provider.signMessage(challenge, "ecdsa"); } + + /** + * Signs a PSBT using UniSat wallet. + * + * @param psbtHex - The hex string of PSBT to sign + * @returns A promise that resolves to the signed PSBT hex string + */ + async signPsbt(psbtHex: string): Promise { + return this.provider.signPsbt(psbtHex); + } + + /** + * Broadcasts a signed PSBT to the Bitcoin network. + * + * @param psbtHex - The hex string of signed PSBT to broadcast + * @returns A promise that resolves to the transaction ID + */ + async pushPsbt(psbtHex: string): Promise { + return this.provider.pushPsbt(psbtHex); + } } diff --git a/packages/utxo-global/src/btc/index.ts b/packages/utxo-global/src/btc/index.ts index 57e73594..c123b2a9 100644 --- a/packages/utxo-global/src/btc/index.ts +++ b/packages/utxo-global/src/btc/index.ts @@ -127,4 +127,26 @@ export class SignerBtc extends ccc.SignerBtc { this.accountCache ?? (await this.getBtcAccount()), ); } + + /** + * Signs a PSBT using UTXO Global wallet. + * + * @param psbtHex - The hex string of PSBT to sign + * @returns A promise that resolves to the signed PSBT hex string + * @todo Implement PSBT signing with UTXO Global + */ + async signPsbt(_: string): Promise { + throw new Error("UTXO Global PSBT signing not implemented yet"); + } + + /** + * Broadcasts a signed PSBT to the Bitcoin network. + * + * @param psbtHex - The hex string of signed PSBT to broadcast + * @returns A promise that resolves to the transaction ID + * @todo Implement PSBT broadcasting with UTXO Global + */ + async pushPsbt(_: string): Promise { + throw new Error("UTXO Global PSBT broadcasting not implemented yet"); + } } diff --git a/packages/xverse/src/signer.ts b/packages/xverse/src/signer.ts index bf6df9c0..e9fd211b 100644 --- a/packages/xverse/src/signer.ts +++ b/packages/xverse/src/signer.ts @@ -167,4 +167,12 @@ export class Signer extends ccc.SignerBtc { ) ).signature; } + + async signPsbt(_: string): Promise { + throw new Error("Not implemented"); + } + + async pushPsbt(_: string): Promise { + throw new Error("Not implemented"); + } }