From 7a2d64591715cf7aedb90a310be90038a93f6d90 Mon Sep 17 00:00:00 2001 From: KevinK <31565174+kevzzsk@users.noreply.github.com> Date: Wed, 31 Jul 2024 10:29:02 +0800 Subject: [PATCH] feat(SDK): support p2wsh format (#124) * feat(SDK): support p2wsh format * resolve comments --- packages/sdk/src/addresses/formats.ts | 6 ++++++ packages/sdk/src/fee/FeeEstimator.ts | 4 ++++ packages/sdk/src/utils/constants.ts | 1 + packages/sdk/src/utils/index.ts | 31 +++++++++++++++++++++++++-- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/addresses/formats.ts b/packages/sdk/src/addresses/formats.ts index db67dcc0..99079491 100644 --- a/packages/sdk/src/addresses/formats.ts +++ b/packages/sdk/src/addresses/formats.ts @@ -3,24 +3,28 @@ export const addressFormats = { p2pkh: /^[1][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2sh: /^[3][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2wpkh: /^(bc1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, + p2wsh: /^(bc1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, p2tr: /^(bc1p)[a-zA-HJ-NP-Z0-9]{14,74}$/ }, testnet: { p2pkh: /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2sh: /^[2][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2wpkh: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, + p2wsh: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, p2tr: /^(tb1p|bcrt1p)[a-zA-HJ-NP-Z0-9]{14,74}$/ }, signet: { p2pkh: /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2sh: /^[2][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2wpkh: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, + p2wsh: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, p2tr: /^(tb1p|bcrt1p)[a-zA-HJ-NP-Z0-9]{14,74}$/ }, regtest: { p2pkh: /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2sh: /^[2][a-km-zA-HJ-NP-Z1-9]{25,34}$/, p2wpkh: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, + p2wsh: /^(tb1[qp]|bcrt1[qp])[a-zA-HJ-NP-Z0-9]{14,74}$/, p2tr: /^(tb1p|bcrt1p)[a-zA-HJ-NP-Z0-9]{14,74}$/ } } as const @@ -28,6 +32,7 @@ export const addressFormats = { export const addressTypeToName = { p2pkh: "legacy", p2sh: "nested-segwit", + p2wsh: "native-segwit", p2wpkh: "segwit", p2tr: "taproot" } as const @@ -36,6 +41,7 @@ export const addressNameToType = { legacy: "p2pkh", segwit: "p2wpkh", "nested-segwit": "p2sh", + "native-segwit": "p2wsh", taproot: "p2tr" } as const diff --git a/packages/sdk/src/fee/FeeEstimator.ts b/packages/sdk/src/fee/FeeEstimator.ts index 5e0fb705..b1660b1d 100644 --- a/packages/sdk/src/fee/FeeEstimator.ts +++ b/packages/sdk/src/fee/FeeEstimator.ts @@ -132,6 +132,10 @@ export default class FeeEstimator { case "taproot": return { input: 42, output: 43, txHeader: 10.5, witness: 66 } // witness size is different for non-default sigHash + case "native-segwit": + // based of 1-of-1 multisig (more other config will change the witness) + return { input: 42, output: 43, txHeader: 10.5, witness: 112.5 } + case "segwit": return { input: 41, output: 31, txHeader: 10.5, witness: 105 } diff --git a/packages/sdk/src/utils/constants.ts b/packages/sdk/src/utils/constants.ts index 8504ee39..92796c26 100644 --- a/packages/sdk/src/utils/constants.ts +++ b/packages/sdk/src/utils/constants.ts @@ -3,6 +3,7 @@ import { AddressFormats } from ".." export const DERIVATION_PATHS_WITHOUT_INDEX: Record = { legacy: `m/44'/0'/0'/0/`, "nested-segwit": `m/49'/0'/0'/0/`, + "native-segwit": `m/84'/0'/0'/0/`, segwit: `m/84'/0'/0'/0/`, taproot: `m/86'/0'/0'/0/` } diff --git a/packages/sdk/src/utils/index.ts b/packages/sdk/src/utils/index.ts index 5215ae03..795fce3b 100644 --- a/packages/sdk/src/utils/index.ts +++ b/packages/sdk/src/utils/index.ts @@ -43,17 +43,29 @@ export function createTransaction( if (type === "p2sh") { return bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh({ pubkey: key, network: networkObj }), - network: networkObj + network: networkObj, + ...paymentOptions + }) + } + if (type === "p2wsh") { + return bitcoin.payments.p2wsh({ + redeem: bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({ pubkey: key, network: networkObj }), + network: networkObj + }), + network: networkObj, + ...paymentOptions }) } - return bitcoin.payments[type]({ pubkey: key, network: networkObj }) + return bitcoin.payments[type]({ pubkey: key, network: networkObj, ...paymentOptions }) } export function getDerivationPath(formatType: AddressFormats, account = 0, addressIndex = 0) { const pathFormat = { legacy: `m/44'/0'/${account}'/0/${addressIndex}`, "nested-segwit": `m/49'/0'/${account}'/0/${addressIndex}`, + "native-segwit": `m/84'/0'/${account}'/0/${addressIndex}`, segwit: `m/84'/0'/${account}'/0/${addressIndex}`, taproot: `m/86'/0'/${account}'/0/${addressIndex}` } @@ -179,6 +191,13 @@ export const isP2PKH = (script: Buffer, network: Network): IsBitcoinPaymentRespo payload: p2pkh } } +export const isP2WSH = (script: Buffer, network: Network): IsBitcoinPaymentResponse => { + const p2wsh = isPaymentFactory(bitcoin.payments.p2wsh, network)(script) + return { + type: "p2wsh", + payload: p2wsh + } +} export const isP2WPKH = (script: Buffer, network: Network): IsBitcoinPaymentResponse => { const p2wpkh = isPaymentFactory(bitcoin.payments.p2wpkh, network)(script) return { @@ -224,6 +243,14 @@ export function getScriptType(script: Buffer, network: Network): GetScriptTypeRe } } + const p2wsh = isP2WSH(script, network) + if (p2wsh.payload) { + return { + format: addressTypeToName["p2wsh"], + ...p2wpkh + } + } + const p2sh = isP2SHScript(script, network) if (p2sh.payload) { return {