diff --git a/modules/sdk-coin-stx/src/lib/sbtcWithdrawBuilder.ts b/modules/sdk-coin-stx/src/lib/sbtcWithdrawBuilder.ts index c29156afb3..acba2336ba 100644 --- a/modules/sdk-coin-stx/src/lib/sbtcWithdrawBuilder.ts +++ b/modules/sdk-coin-stx/src/lib/sbtcWithdrawBuilder.ts @@ -25,7 +25,6 @@ import { decodeBtcAddress, isValidBtcAddress } from './btcAddressUtils'; const SBTC_TOKEN_CONTRACT_NAME = 'sbtc-token'; const SBTC_TOKEN_ASSET_NAME = 'sbtc-token'; -const HASHBYTES_BUFFER_LENGTH = 32; export class SbtcWithdrawBuilder extends AbstractContractBuilder { private _withdrawParams: SbtcWithdrawParams | undefined; @@ -130,20 +129,11 @@ export class SbtcWithdrawBuilder extends AbstractContractBuilder { private withdrawParamsToFunctionArgs(params: SbtcWithdrawParams) { const decoded = decodeBtcAddress(params.btcAddress); - - // Pad 20-byte hashes to 32 bytes with trailing zeros per sBTC contract spec (buff 32) - let hashBytes = decoded.hashBytes; - if (hashBytes.length < HASHBYTES_BUFFER_LENGTH) { - const padded = Buffer.alloc(HASHBYTES_BUFFER_LENGTH, 0); - hashBytes.copy(padded); - hashBytes = padded; - } - return [ uintCV(params.amount), tupleCV({ version: bufferCV(Buffer.from([decoded.version])), - hashbytes: bufferCV(hashBytes), + hashbytes: bufferCV(decoded.hashBytes), }), uintCV(params.maxFee), ]; diff --git a/modules/sdk-coin-stx/test/unit/transactionBuilder/sbtcWithdrawBuilder.ts b/modules/sdk-coin-stx/test/unit/transactionBuilder/sbtcWithdrawBuilder.ts index 4510a23e5b..ca75c4adab 100644 --- a/modules/sdk-coin-stx/test/unit/transactionBuilder/sbtcWithdrawBuilder.ts +++ b/modules/sdk-coin-stx/test/unit/transactionBuilder/sbtcWithdrawBuilder.ts @@ -1,5 +1,6 @@ import should from 'should'; -import { pubKeyfromPrivKey, publicKeyToString } from '@stacks/transactions'; +import { ClarityType, pubKeyfromPrivKey, publicKeyToString } from '@stacks/transactions'; +import { ContractCallPayload } from '@stacks/transactions/dist/payload'; import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; import { BitGoAPI } from '@bitgo/sdk-api'; @@ -52,6 +53,18 @@ describe('Stacks: sBTC Withdraw Builder', function () { should.exist(txJson.payload); txJson.payload.should.have.property('contractName', 'sbtc-withdrawal'); txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request'); + + // Verify hashbytes is 20 bytes for P2PKH + const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload; + const recipientTuple = payload.functionArgs[1]; + should.equal(recipientTuple.type, ClarityType.Tuple); + if (recipientTuple.type === ClarityType.Tuple) { + const hashbytes = recipientTuple.data['hashbytes']; + should.equal(hashbytes.type, ClarityType.Buffer); + if (hashbytes.type === ClarityType.Buffer) { + hashbytes.buffer.length.should.equal(20); + } + } }); it('a withdrawal with P2SH address', async () => { @@ -68,6 +81,16 @@ describe('Stacks: sBTC Withdraw Builder', function () { const tx = await builder.build(); const txJson = tx.toJson(); txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request'); + + // Verify hashbytes is 20 bytes for P2SH + const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload; + const recipientTuple = payload.functionArgs[1]; + if (recipientTuple.type === ClarityType.Tuple) { + const hashbytes = recipientTuple.data['hashbytes']; + if (hashbytes.type === ClarityType.Buffer) { + hashbytes.buffer.length.should.equal(20); + } + } }); it('a withdrawal with P2WPKH (bech32) address', async () => { @@ -84,6 +107,16 @@ describe('Stacks: sBTC Withdraw Builder', function () { const tx = await builder.build(); const txJson = tx.toJson(); txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request'); + + // Verify hashbytes is 20 bytes for P2WPKH + const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload; + const recipientTuple = payload.functionArgs[1]; + if (recipientTuple.type === ClarityType.Tuple) { + const hashbytes = recipientTuple.data['hashbytes']; + if (hashbytes.type === ClarityType.Buffer) { + hashbytes.buffer.length.should.equal(20); + } + } }); it('a withdrawal with P2WSH (bech32) address', async () => { @@ -100,6 +133,16 @@ describe('Stacks: sBTC Withdraw Builder', function () { const tx = await builder.build(); const txJson = tx.toJson(); txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request'); + + // Verify hashbytes is 32 bytes for P2WSH + const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload; + const recipientTuple = payload.functionArgs[1]; + if (recipientTuple.type === ClarityType.Tuple) { + const hashbytes = recipientTuple.data['hashbytes']; + if (hashbytes.type === ClarityType.Buffer) { + hashbytes.buffer.length.should.equal(32); + } + } }); it('a withdrawal with P2TR (bech32m) address', async () => { @@ -116,6 +159,16 @@ describe('Stacks: sBTC Withdraw Builder', function () { const tx = await builder.build(); const txJson = tx.toJson(); txJson.payload.should.have.property('functionName', 'initiate-withdrawal-request'); + + // Verify hashbytes is 32 bytes for P2TR + const payload = (tx as StxLib.Transaction).stxTransaction.payload as ContractCallPayload; + const recipientTuple = payload.functionArgs[1]; + if (recipientTuple.type === ClarityType.Tuple) { + const hashbytes = recipientTuple.data['hashbytes']; + if (hashbytes.type === ClarityType.Buffer) { + hashbytes.buffer.length.should.equal(32); + } + } }); });