From dc5d8a0790df95628eb646fc62018b11c351c2d8 Mon Sep 17 00:00:00 2001 From: Paras Garg Date: Wed, 17 Jun 2026 08:49:44 +0530 Subject: [PATCH] fix(sdk-coin-polyx): encode memo as UTF-8 bytes right-padded with null bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Polymesh Memo is [u8; 32] — UTF-8 bytes right-padded with 0x00 bytes to 32 bytes. Both TransferBuilder and TokenTransferBuilder were using padStart(32, '0') which left-pads with ASCII '0' characters (0x30), causing exchanges like Binance to receive '00000000000000000000000102329716' instead of '102329716' and fail to match the deposit memo. COINS-447 Co-Authored-By: Claude Sonnet 4.6 TICKET: COINS-447 --- .../src/lib/tokenTransferBuilder.ts | 17 ++++++++++++++--- .../sdk-coin-polyx/src/lib/transferBuilder.ts | 17 ++++++++++++++--- .../transactionBuilder/tokenTransferBuilder.ts | 4 ++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts b/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts index 741ec39513..2501a895c5 100644 --- a/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts +++ b/modules/sdk-coin-polyx/src/lib/tokenTransferBuilder.ts @@ -84,14 +84,25 @@ export class TokenTransferBuilder extends PolyxBaseBuilder 32) { + throw new Error('Memo must be 32 bytes or fewer when UTF-8 encoded'); + } + const paddedBuffer = Buffer.alloc(32, 0); + memoBytes.copy(paddedBuffer, 0); + this._memo = '0x' + paddedBuffer.toString('hex'); return this; } diff --git a/modules/sdk-coin-polyx/src/lib/transferBuilder.ts b/modules/sdk-coin-polyx/src/lib/transferBuilder.ts index 2fe9d5c3ca..dbcfb8049a 100644 --- a/modules/sdk-coin-polyx/src/lib/transferBuilder.ts +++ b/modules/sdk-coin-polyx/src/lib/transferBuilder.ts @@ -69,14 +69,25 @@ export class TransferBuilder extends PolyxBaseBuilder { /** * The memo to attach to the transfer transaction. - * Pads the memo on the left with zeros to ensure it is 32 characters long. + * Encodes the memo as UTF-8 bytes right-padded with zero bytes to 32 bytes, + * matching the Polymesh Memo type ([u8; 32]). * * @param {string} memo The memo string to include. * @returns {TransferBuilder} This transfer builder. */ memo(memo: string): this { - const paddedMemo = memo.padStart(32, '0'); - this._memo = paddedMemo; + // fromImplementation passes the decoded on-chain hex (0x + 64 hex chars) — pass through unchanged + if (/^0x[0-9a-fA-F]{64}$/.test(memo)) { + this._memo = memo; + return this; + } + const memoBytes = Buffer.from(memo, 'utf8'); + if (memoBytes.length > 32) { + throw new Error('Memo must be 32 bytes or fewer when UTF-8 encoded'); + } + const paddedBuffer = Buffer.alloc(32, 0); + memoBytes.copy(paddedBuffer, 0); + this._memo = '0x' + paddedBuffer.toString('hex'); return this; } diff --git a/modules/sdk-coin-polyx/test/unit/transactionBuilder/tokenTransferBuilder.ts b/modules/sdk-coin-polyx/test/unit/transactionBuilder/tokenTransferBuilder.ts index fd1ccc5b9c..4a7bde567c 100644 --- a/modules/sdk-coin-polyx/test/unit/transactionBuilder/tokenTransferBuilder.ts +++ b/modules/sdk-coin-polyx/test/unit/transactionBuilder/tokenTransferBuilder.ts @@ -40,7 +40,7 @@ describe('Polyx token transfer Builder - Testnet', () => { should.deepEqual(txJson.fromDID, senderDID); should.deepEqual(txJson.sender, sender.address); should.deepEqual(txJson.assetId, assetId); - should.deepEqual(txJson.memo, '0x3030303030303030303030303030303030303030303030303030303030303030'); + should.deepEqual(txJson.memo, '0x3000000000000000000000000000000000000000000000000000000000000000'); should.deepEqual(txJson.blockNumber, 3933); should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); should.deepEqual(txJson.genesisHash, genesisHash); @@ -79,7 +79,7 @@ describe('Polyx token transfer Builder - Testnet', () => { should.deepEqual(txJson.fromDID, senderDID); should.deepEqual(txJson.sender, sender.address); should.deepEqual(txJson.assetId, assetId); - should.deepEqual(txJson.memo, '0x3030303030303030303030303030303030303030303030303030303030303030'); + should.deepEqual(txJson.memo, '0x3000000000000000000000000000000000000000000000000000000000000000'); should.deepEqual(txJson.blockNumber, 3933); should.deepEqual(txJson.referenceBlock, '0x149799bc9602cb5cf201f3425fb8d253b2d4e61fc119dcab3249f307f594754d'); should.deepEqual(txJson.genesisHash, genesisHash);