diff --git a/modules/sdk-coin-iota/src/iota.ts b/modules/sdk-coin-iota/src/iota.ts index 782df2419b..bcc4d581b0 100644 --- a/modules/sdk-coin-iota/src/iota.ts +++ b/modules/sdk-coin-iota/src/iota.ts @@ -91,9 +91,9 @@ export class Iota extends BaseCoin { * @inheritDoc */ async explainTransaction(params: ExplainTransactionOptions): Promise { - const rawTx = params.txBase64; + const rawTx = params.txHex; if (!rawTx) { - throw new Error('missing required tx prebuild property txBase64'); + throw new Error('missing required tx prebuild property txHex'); } const transaction = await this.rebuildTransaction(rawTx); if (!transaction) { @@ -108,9 +108,9 @@ export class Iota extends BaseCoin { */ async verifyTransaction(params: VerifyTransactionOptions): Promise { const { txPrebuild: txPrebuild, txParams: txParams } = params; - const rawTx = txPrebuild.txBase64; + const rawTx = txPrebuild.txHex; if (!rawTx) { - throw new Error('missing required tx prebuild property txBase64'); + throw new Error('missing required tx prebuild property txHex'); } const transaction = await this.rebuildTransaction(rawTx); if (!transaction) { @@ -145,7 +145,7 @@ export class Iota extends BaseCoin { * @param params */ async parseTransaction(params: IotaParseTransactionOptions): Promise { - const transactionExplanation = await this.explainTransaction({ txBase64: params.txBase64 }); + const transactionExplanation = await this.explainTransaction({ txHex: params.txHex }); if (!transactionExplanation) { throw new Error('Invalid transaction'); @@ -262,10 +262,9 @@ export class Iota extends BaseCoin { const txBuilderFactory = this.getTxBuilderFactory(); try { const txBuilder = txBuilderFactory.from(txHex); - txBuilder.transaction.isSimulateTx = false; return (await txBuilder.build()) as Transaction; - } catch { - throw new Error('Failed to rebuild transaction'); + } catch (err) { + throw new Error(`Failed to rebuild transaction ${err.toString()}`); } } } diff --git a/modules/sdk-coin-iota/src/lib/iface.ts b/modules/sdk-coin-iota/src/lib/iface.ts index f3755aab50..28fa028369 100644 --- a/modules/sdk-coin-iota/src/lib/iface.ts +++ b/modules/sdk-coin-iota/src/lib/iface.ts @@ -43,9 +43,9 @@ export interface TransferTxData extends TxData { } export interface ExplainTransactionOptions { - txBase64: string; + txHex: string; } export interface IotaParseTransactionOptions extends BaseParseTransactionOptions { - txBase64: string; + txHex: string; } diff --git a/modules/sdk-coin-iota/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-iota/src/lib/transactionBuilderFactory.ts index 17a8c909d8..855a0d5689 100644 --- a/modules/sdk-coin-iota/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-iota/src/lib/transactionBuilderFactory.ts @@ -3,6 +3,7 @@ import { TransactionBuilder } from './transactionBuilder'; import { TransferBuilder } from './transferBuilder'; import { Transaction } from './transaction'; import { TRANSFER_TRANSACTION_COMMANDS } from './constants'; +import utils from './utils'; import { Transaction as IotaTransaction } from '@iota/iota-sdk/transactions'; import { BaseCoin as CoinConfig } from '@bitgo/statics'; @@ -22,11 +23,12 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { from(rawTx: string | Uint8Array): TransactionBuilder { let builder: TransactionBuilder; - const txType: TransactionType = this.identifyTxTypeFromRawTx(rawTx); + const rawTxBase64 = utils.getBase64String(rawTx); + const txType: TransactionType = this.identifyTxTypeFromRawTx(rawTxBase64); switch (txType) { case TransactionType.Send: builder = new TransferBuilder(this._coinConfig); - builder.fromImplementation(rawTx); + builder.fromImplementation(rawTxBase64); return builder; } throw new InvalidTransactionError('Unsupported transaction'); diff --git a/modules/sdk-coin-iota/src/lib/utils.ts b/modules/sdk-coin-iota/src/lib/utils.ts index e762d3652c..e89533c4d5 100644 --- a/modules/sdk-coin-iota/src/lib/utils.ts +++ b/modules/sdk-coin-iota/src/lib/utils.ts @@ -7,7 +7,7 @@ import { } from './constants'; import { Ed25519PublicKey } from '@iota/iota-sdk/keypairs/ed25519'; import { Transaction as IotaTransaction } from '@iota/iota-sdk/transactions'; -import { fromBase64 } from '@iota/bcs'; +import { toBase64, fromBase64 } from '@iota/iota-sdk/utils'; export class Utils implements BaseUtils { /** @inheritdoc */ @@ -49,6 +49,14 @@ export class Utils implements BaseUtils { return regex.test(value); } + getBase64String(value: string | Uint8Array): string { + if (value instanceof Uint8Array) { + return toBase64(value); + } else { + return toBase64(Buffer.from(value, 'hex')); + } + } + getAddressFromPublicKey(publicKey: string): string { const iotaPublicKey = new Ed25519PublicKey(Buffer.from(publicKey, 'hex')); return iotaPublicKey.toIotaAddress(); diff --git a/modules/sdk-coin-iota/test/unit/iota.ts b/modules/sdk-coin-iota/test/unit/iota.ts index a6d6c8e971..356c7af02f 100644 --- a/modules/sdk-coin-iota/test/unit/iota.ts +++ b/modules/sdk-coin-iota/test/unit/iota.ts @@ -136,16 +136,16 @@ describe('IOTA:', function () { }); describe('explainTransaction', () => { - it('should throw error for missing txBase64', async function () { + it('should throw error for missing txHex', async function () { await assert.rejects( - async () => await basecoin.explainTransaction({ txBase64: '' }), - /missing required tx prebuild property txBase64/ + async () => await basecoin.explainTransaction({ txHex: '' }), + /missing required tx prebuild property txHex/ ); }); it('should throw error for invalid transaction', async function () { await assert.rejects( - async () => await basecoin.explainTransaction({ txBase64: 'invalidTxBase64' }), + async () => await basecoin.explainTransaction({ txHex: 'invalidTxHex' }), /Failed to rebuild transaction/ ); }); @@ -171,14 +171,14 @@ describe('IOTA:', function () { }); describe('verifyTransaction', () => { - it('should throw error for missing txBase64', async function () { + it('should throw error for missing txHex', async function () { await assert.rejects( async () => await basecoin.verifyTransaction({ txPrebuild: {}, txParams: { recipients: testData.recipients }, }), - /missing required tx prebuild property txBase64/ + /missing required tx prebuild property txHex/ ); }); @@ -191,16 +191,14 @@ describe('IOTA:', function () { txBuilder.gasData(testData.gasData); const tx = (await txBuilder.build()) as TransferTransaction; - const txJson = tx.toJson(); - - // Rebuild from JSON to simulate what would happen in verification - const rebuiltTxBuilder = factory.getTransferBuilder(); - (rebuiltTxBuilder.transaction as TransferTransaction).parseFromJSON(txJson); - const rebuiltTx = (await rebuiltTxBuilder.build()) as TransferTransaction; - - // Verify the rebuilt transaction matches - should.equal(rebuiltTx.sender, testData.sender.address); - should.deepEqual(rebuiltTx.recipients, testData.recipients); + const txHex = Buffer.from(await tx.toBroadcastFormat(), 'base64').toString('hex'); + should.equal( + await basecoin.verifyTransaction({ + txPrebuild: { txHex: txHex }, + txParams: { recipients: testData.recipients }, + }), + true + ); }); it('should detect mismatched recipients', async function () { @@ -211,12 +209,15 @@ describe('IOTA:', function () { txBuilder.gasData(testData.gasData); const tx = (await txBuilder.build()) as TransferTransaction; - const txJson = tx.toJson(); - - const differentRecipients = [{ address: testData.addresses.validAddresses[0], amount: '9999' }]; - - // Recipients don't match - should.notDeepEqual(txJson.recipients, differentRecipients); + const txHex = Buffer.from(await tx.toBroadcastFormat(), 'base64').toString('hex'); + assert.rejects( + async () => + await basecoin.verifyTransaction({ + txPrebuild: { txHex: txHex }, + txParams: { recipients: testData.recipients }, + }), + /Tx recipients does not match with expected txParams recipients/ + ); }); it('should verify transaction without recipients parameter', async function () { @@ -227,17 +228,22 @@ describe('IOTA:', function () { txBuilder.gasData(testData.gasData); const tx = (await txBuilder.build()) as TransferTransaction; - - // Verification should still work even if no recipients are provided for comparison - should.exist(tx); - should.equal(tx.type, TransactionType.Send); + const txHex = Buffer.from(await tx.toBroadcastFormat(), 'base64').toString('hex'); + + should.equal( + await basecoin.verifyTransaction({ + txPrebuild: { txHex: txHex }, + txParams: {}, + }), + true + ); }); }); describe('parseTransaction', () => { it('should throw error for invalid transaction', async function () { await assert.rejects( - async () => await basecoin.parseTransaction({ txBase64: 'invalidTxBase64' }), + async () => await basecoin.parseTransaction({ txHex: 'invalidTxHex' }), /Failed to rebuild transaction/ ); }); diff --git a/modules/sdk-coin-iota/test/unit/transactionBuilder/transactionBuilderFactory.ts b/modules/sdk-coin-iota/test/unit/transactionBuilder/transactionBuilderFactory.ts index 3954c244ae..e7aa2acb70 100644 --- a/modules/sdk-coin-iota/test/unit/transactionBuilder/transactionBuilderFactory.ts +++ b/modules/sdk-coin-iota/test/unit/transactionBuilder/transactionBuilderFactory.ts @@ -42,8 +42,9 @@ describe('Iota Transaction Builder Factory', () => { const tx = (await txBuilder.build()) as TransferTransaction; const rawTx = await tx.toBroadcastFormat(); + const txHex = Buffer.from(rawTx, 'base64').toString('hex'); - const rebuiltBuilder = factory.from(rawTx); + const rebuiltBuilder = factory.from(txHex); should.exist(rebuiltBuilder); should(rebuiltBuilder instanceof TransferBuilder).be.true(); const rebuiltTx = (await rebuiltBuilder.build()) as TransferTransaction; diff --git a/modules/sdk-coin-iota/test/unit/utils.ts b/modules/sdk-coin-iota/test/unit/utils.ts index fcba3fc85e..b1382d7165 100644 --- a/modules/sdk-coin-iota/test/unit/utils.ts +++ b/modules/sdk-coin-iota/test/unit/utils.ts @@ -205,6 +205,47 @@ describe('Iota util library', function () { }); }); + describe('getBase64String', function () { + it('should convert Uint8Array to base64', function () { + const uint8Array = new Uint8Array([72, 101, 108, 108, 111]); // "Hello" + const result = utils.getBase64String(uint8Array); + should.exist(result); + should.equal(typeof result, 'string'); + should.equal(result.length > 0, true); + }); + + it('should convert hex string to base64', function () { + const hexString = '48656c6c6f'; // "Hello" in hex + const result = utils.getBase64String(hexString); + should.exist(result); + should.equal(typeof result, 'string'); + should.equal(result.length > 0, true); + }); + + it('should handle empty Uint8Array', function () { + const emptyArray = new Uint8Array([]); + const result = utils.getBase64String(emptyArray); + should.exist(result); + should.equal(typeof result, 'string'); + }); + + it('should handle Buffer conversion to base64', function () { + const buffer = Buffer.from('Hello World'); + const uint8Array = new Uint8Array(buffer); + const result = utils.getBase64String(uint8Array); + should.exist(result); + should.equal(typeof result, 'string'); + should.equal(result.length > 0, true); + }); + + it('should consistently convert same input to same output', function () { + const uint8Array = new Uint8Array([1, 2, 3, 4, 5]); + const result1 = utils.getBase64String(uint8Array); + const result2 = utils.getBase64String(new Uint8Array([1, 2, 3, 4, 5])); + should.equal(result1, result2); + }); + }); + describe('isValidRawTransaction', function () { it('should validate proper raw transactions', async function () { const factory = new TransactionBuilderFactory(coins.get('tiota'));