diff --git a/bindings/nodejs/examples/client/04-get-output.ts b/bindings/nodejs/examples/client/04-get-output.ts index 1b439c3f11..5141243ba9 100644 --- a/bindings/nodejs/examples/client/04-get-output.ts +++ b/bindings/nodejs/examples/client/04-get-output.ts @@ -1,7 +1,7 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Client, initLogger } from '@iota/sdk'; +import { Client, initLogger, OutputId } from '@iota/sdk'; require('dotenv').config({ path: '.env' }); // Run with command: @@ -22,7 +22,9 @@ async function run() { }); try { const output = await client.getOutput( - '0x022aefa73dff09b35b21ab5493412b0d354ad07a970a12b71e8087c6f3a7b8660000', + new OutputId( + '0x022aefa73dff09b35b21ab5493412b0d354ad07a970a12b71e8087c6f3a7b866000000000000', + ), ); console.log('Output: ', output); } catch (error) { diff --git a/bindings/nodejs/examples/client/05-get-address-balance.ts b/bindings/nodejs/examples/client/05-get-address-balance.ts index e8563c2c3f..7dc1a5fb62 100644 --- a/bindings/nodejs/examples/client/05-get-address-balance.ts +++ b/bindings/nodejs/examples/client/05-get-address-balance.ts @@ -1,7 +1,13 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Client, CommonOutput, SecretManager, initLogger } from '@iota/sdk'; +import { + Client, + CommonOutput, + SecretManager, + initLogger, + OutputId, +} from '@iota/sdk'; require('dotenv').config({ path: '.env' }); // Run with command: @@ -45,7 +51,9 @@ async function run() { }); // Get outputs by their IDs - const addressOutputs = await client.getOutputs(outputIdsResponse.items); + const addressOutputs = await client.getOutputs( + outputIdsResponse.items.map((id) => new OutputId(id)), + ); // Calculate the total amount and native tokens let totalAmount = BigInt(0); diff --git a/bindings/nodejs/examples/how_tos/client/get-outputs.ts b/bindings/nodejs/examples/how_tos/client/get-outputs.ts index 6382a7f5c6..48bccc9b0e 100644 --- a/bindings/nodejs/examples/how_tos/client/get-outputs.ts +++ b/bindings/nodejs/examples/how_tos/client/get-outputs.ts @@ -1,7 +1,7 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Client, initLogger } from '@iota/sdk'; +import { Client, initLogger, OutputId } from '@iota/sdk'; require('dotenv').config({ path: '.env' }); // Run with command: @@ -33,7 +33,9 @@ async function run() { console.log('First output of query:'); console.log('ID: ', outputIdsResponse.items[0]); - const outputs = await client.getOutputs(outputIdsResponse.items); + const outputs = await client.getOutputs( + outputIdsResponse.items.map((id) => new OutputId(id)), + ); console.log(outputs[0]); } catch (error) { console.error('Error: ', error); diff --git a/bindings/nodejs/lib/types/block/id.ts b/bindings/nodejs/lib/types/block/id.ts index a2fbf268c1..557b41c1fe 100644 --- a/bindings/nodejs/lib/types/block/id.ts +++ b/bindings/nodejs/lib/types/block/id.ts @@ -2,6 +2,28 @@ // SPDX-License-Identifier: Apache-2.0 import { HexEncodedString } from '../utils'; +import { SlotIndex } from './slot'; + +/** + * Base class for IDs with a hex encoded slot index at the end. + */ +export class IdWithSlotIndex extends String { + slotIndex(): SlotIndex { + const numberString = super.slice(-8); + const chunks = []; + for ( + let i = 0, charsLength = numberString.length; + i < charsLength; + i += 2 + ) { + chunks.push(numberString.substring(i, i + 2)); + } + const separated = chunks.map((n) => parseInt(n, 16)); + const buf = Uint8Array.from(separated).buffer; + const view = new DataView(buf); + return view.getUint32(0, true); + } +} /** * An Account ID represented as hex-encoded string. @@ -21,13 +43,18 @@ export type NftId = HexEncodedString; /** * A Block ID represented as hex-encoded string. */ -export type BlockId = HexEncodedString; +export class BlockId extends IdWithSlotIndex {} /** * A Token ID represented as hex-encoded string. */ export type TokenId = HexEncodedString; +/** + * A Transaction ID represented as hex-encoded string. + */ +export class TransactionId extends IdWithSlotIndex {} + /** * A Foundry ID represented as hex-encoded string. */ diff --git a/bindings/nodejs/lib/types/block/input/input.ts b/bindings/nodejs/lib/types/block/input/input.ts index 39f0ba5062..b5f2dff5b7 100644 --- a/bindings/nodejs/lib/types/block/input/input.ts +++ b/bindings/nodejs/lib/types/block/input/input.ts @@ -1,7 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { HexEncodedString } from '../../utils'; +import { Transform, Type } from 'class-transformer'; +import { TransactionId } from '../id'; import { OutputId } from '../output'; /** @@ -33,7 +34,9 @@ class UTXOInput extends Input { /** * The transaction ID. */ - readonly transactionId: HexEncodedString; + @Type(() => TransactionId) + @Transform(({ value }) => new TransactionId(value), { toClassOnly: true }) + readonly transactionId: TransactionId; /** * The output index. */ @@ -43,10 +46,7 @@ class UTXOInput extends Input { * @param transactionId The ID of the transaction it is an input of. * @param transactionOutputIndex The index of the input within the transaction. */ - constructor( - transactionId: HexEncodedString, - transactionOutputIndex: number, - ) { + constructor(transactionId: TransactionId, transactionOutputIndex: number) { super(InputType.UTXO); this.transactionId = transactionId; this.transactionOutputIndex = transactionOutputIndex; diff --git a/bindings/nodejs/lib/types/block/output/output.ts b/bindings/nodejs/lib/types/block/output/output.ts index c72e96dd8c..35a651a592 100644 --- a/bindings/nodejs/lib/types/block/output/output.ts +++ b/bindings/nodejs/lib/types/block/output/output.ts @@ -9,13 +9,32 @@ import { Feature, FeatureDiscriminator, NativeTokenFeature } from './feature'; // Temp solution for not double parsing JSON import { plainToInstance, Type } from 'class-transformer'; -import { HexEncodedString, NumericString, u64 } from '../../utils'; +import { NumericString, u64 } from '../../utils'; import { TokenScheme, TokenSchemeDiscriminator } from './token-scheme'; -import { AccountId, NftId, AnchorId, DelegationId } from '../id'; +import { AccountId, NftId, AnchorId, DelegationId, TransactionId } from '../id'; import { EpochIndex } from '../../block/slot'; import { NativeToken } from '../../models/native-token'; -export type OutputId = HexEncodedString; +export class OutputId extends String { + transactionId(): TransactionId { + return new TransactionId(this.slice(74)); + } + outputIndex(): number { + const numberString = this.slice(-4); + const chunks = []; + for ( + let i = 0, charsLength = numberString.length; + i < charsLength; + i += 2 + ) { + chunks.push(numberString.substring(i, i + 2)); + } + const separated = chunks.map((n) => parseInt(n, 16)); + const buf = Uint8Array.from(separated).buffer; + const view = new DataView(buf); + return view.getUint16(0, true); + } +} /** * All of the output types. diff --git a/bindings/nodejs/lib/types/block/slot/commitment.ts b/bindings/nodejs/lib/types/block/slot/commitment.ts index 232703f319..5982beccb4 100644 --- a/bindings/nodejs/lib/types/block/slot/commitment.ts +++ b/bindings/nodejs/lib/types/block/slot/commitment.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { u64 } from '../..'; +import { IdWithSlotIndex } from '../id'; /** * Timeline is divided into slots, and each slot has a corresponding slot index. @@ -21,7 +22,7 @@ type EpochIndex = number; /** * Identifier of a slot commitment */ -type SlotCommitmentId = string; +class SlotCommitmentId extends IdWithSlotIndex {} /** * A BLAKE2b-256 hash of concatenating multiple sparse merkle tree roots of a slot. diff --git a/bindings/nodejs/lib/types/client/bridge/client.ts b/bindings/nodejs/lib/types/client/bridge/client.ts index aa3ea3ef26..5bd79f7de2 100644 --- a/bindings/nodejs/lib/types/client/bridge/client.ts +++ b/bindings/nodejs/lib/types/client/bridge/client.ts @@ -17,6 +17,7 @@ import type { OutputId, Payload, SlotIndex, + SlotCommitmentId, } from '../../block'; import type { PreparedTransactionData } from '../prepared-transaction-data'; import type { @@ -223,21 +224,21 @@ export interface __GetTransactionMetadataMethod__ { export interface __GetCommitmentMethod__ { name: 'getCommitment'; data: { - commitmentId: HexEncodedString; + commitmentId: SlotCommitmentId; }; } export interface __GetUtxoChangesMethod__ { name: 'getUtxoChanges'; data: { - commitmentId: HexEncodedString; + commitmentId: SlotCommitmentId; }; } export interface __GetUtxoChangesFullMethod__ { name: 'getUtxoChangesFull'; data: { - commitmentId: HexEncodedString; + commitmentId: SlotCommitmentId; }; } diff --git a/bindings/nodejs/lib/types/client/prepared-transaction-data.ts b/bindings/nodejs/lib/types/client/prepared-transaction-data.ts index 9af6cb57eb..141662507e 100644 --- a/bindings/nodejs/lib/types/client/prepared-transaction-data.ts +++ b/bindings/nodejs/lib/types/client/prepared-transaction-data.ts @@ -3,11 +3,11 @@ import { Type } from 'class-transformer'; import { Address, AddressDiscriminator } from '../block/address'; -import { Output, OutputDiscriminator, OutputId } from '../block/output/output'; +import { Output, OutputDiscriminator } from '../block/output/output'; import { Transaction } from '../block/payload/signed_transaction'; import { IOutputMetadataResponse } from '../models/api'; import { Bip44 } from '../secret_manager'; -import { NumericString } from '../utils'; +import { HexEncodedString, NumericString } from '../utils'; /** * Helper struct for offline signing. @@ -28,7 +28,7 @@ export class PreparedTransactionData { /** * Mana rewards by input. */ - manaRewards?: { [outputId: OutputId]: NumericString }; + manaRewards?: { [outputId: HexEncodedString]: NumericString }; } /** diff --git a/bindings/nodejs/lib/types/models/api/block-id-response.ts b/bindings/nodejs/lib/types/models/api/block-id-response.ts index fff073e335..47f17f48e1 100644 --- a/bindings/nodejs/lib/types/models/api/block-id-response.ts +++ b/bindings/nodejs/lib/types/models/api/block-id-response.ts @@ -1,7 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { HexEncodedString } from '../../utils/hex-encoding'; +import { BlockId } from '../../block'; + /** * Block id response. */ @@ -9,5 +10,5 @@ export interface IBlockIdResponse { /** * The block id. */ - blockId: HexEncodedString; + blockId: BlockId; } diff --git a/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts b/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts index 2bf8b46c3c..f00c5db191 100644 --- a/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts +++ b/bindings/nodejs/lib/types/models/api/plugins/indexer/output-response.ts @@ -1,8 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +import { OutputId } from '../../../../block/output/output'; import { NumericString } from '../../../../utils'; -import type { HexEncodedString } from '../../../../utils/hex-encoding'; /** * Details of an outputs response from the indexer plugin. @@ -23,5 +23,5 @@ export interface IOutputsResponse { /** * The output IDs (transaction hash + output index) of the outputs on this address. */ - items: HexEncodedString[]; + items: OutputId[]; } diff --git a/bindings/nodejs/lib/types/models/api/tips-response.ts b/bindings/nodejs/lib/types/models/api/tips-response.ts index 5316610d27..b1c07c7304 100644 --- a/bindings/nodejs/lib/types/models/api/tips-response.ts +++ b/bindings/nodejs/lib/types/models/api/tips-response.ts @@ -1,7 +1,8 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { HexEncodedString } from '../../utils/hex-encoding'; +import { BlockId } from '../../block/id'; + /** * Response from the tips endpoint. */ @@ -9,5 +10,5 @@ export interface ITipsResponse { /** * The block ids of the tip. */ - tips: HexEncodedString[]; + tips: BlockId[]; } diff --git a/bindings/nodejs/lib/types/models/block-metadata.ts b/bindings/nodejs/lib/types/models/block-metadata.ts index ba7cee6f57..0f6029c87e 100644 --- a/bindings/nodejs/lib/types/models/block-metadata.ts +++ b/bindings/nodejs/lib/types/models/block-metadata.ts @@ -1,11 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { HexEncodedString } from '../utils/hex-encoding'; import { BlockState, TransactionState } from './state'; import { BlockFailureReason } from './block-failure-reason'; -import { Block } from '../block'; -import { TransactionId } from '../wallet'; +import { Block, BlockId, TransactionId } from '../block'; import { TransactionFailureReason } from './transaction-failure-reason'; /** @@ -15,7 +13,7 @@ export interface IBlockMetadata { /** * The block id. */ - blockId: HexEncodedString; + blockId: BlockId; /** * The block state. */ diff --git a/bindings/nodejs/lib/types/utils/bridge/utils.ts b/bindings/nodejs/lib/types/utils/bridge/utils.ts index 00617c7043..8360aab79c 100644 --- a/bindings/nodejs/lib/types/utils/bridge/utils.ts +++ b/bindings/nodejs/lib/types/utils/bridge/utils.ts @@ -224,7 +224,7 @@ export interface __VerifyTransactionSemantic__ { inputs: InputSigningData[]; protocolParameters: ProtocolParameters; unlocks?: Unlock[]; - manaRewards?: { [outputId: OutputId]: NumericString }; + manaRewards?: { [outputId: HexEncodedString]: NumericString }; }; } diff --git a/bindings/nodejs/lib/types/wallet/event.ts b/bindings/nodejs/lib/types/wallet/event.ts index 990aed38ca..10b8884e79 100644 --- a/bindings/nodejs/lib/types/wallet/event.ts +++ b/bindings/nodejs/lib/types/wallet/event.ts @@ -4,15 +4,10 @@ import type { OutputData } from './output'; import { InclusionState } from './transaction'; import { InputSigningData, Remainder } from '../client'; -import { Transaction, SignedTransactionPayload } from '../block'; +import { Transaction, SignedTransactionPayload, TransactionId } from '../block'; import { OutputResponse } from '../models'; import { HexEncodedString } from '../utils'; -/** - * A Transaction ID represented as hex-encoded string. - */ -export type TransactionId = string; - /** * All of the wallet event types. */ diff --git a/bindings/nodejs/lib/types/wallet/participation.ts b/bindings/nodejs/lib/types/wallet/participation.ts index 9c0a84e887..9b5c0c2f03 100644 --- a/bindings/nodejs/lib/types/wallet/participation.ts +++ b/bindings/nodejs/lib/types/wallet/participation.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { INode } from '../client'; -import type { OutputId } from '../block/output'; +import { HexEncodedString } from '../utils'; /** * An object containing an account's entire participation overview. @@ -16,7 +16,7 @@ export interface ParticipationOverview { */ export interface Participations { [eventId: ParticipationEventId]: { - [outputId: OutputId]: TrackedParticipationOverview; + [outputId: HexEncodedString]: TrackedParticipationOverview; }; } diff --git a/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts b/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts index b2043bae99..def0268ba5 100644 --- a/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts +++ b/bindings/nodejs/lib/types/wallet/signed-transaction-data.ts @@ -4,8 +4,7 @@ import { Type } from 'class-transformer'; import { SignedTransactionPayload } from '../block/payload/signed_transaction'; import { InputSigningData } from '../client'; -import { OutputId } from '../block/output'; -import { NumericString } from '../utils'; +import { HexEncodedString, NumericString } from '../utils'; /** The signed transaction with inputs data */ export class SignedTransactionData { @@ -16,5 +15,5 @@ export class SignedTransactionData { @Type(() => InputSigningData) inputsData!: InputSigningData; /** Mana rewards by input */ - manaRewards?: { [outputId: OutputId]: NumericString }; + manaRewards?: { [outputId: HexEncodedString]: NumericString }; } diff --git a/bindings/nodejs/lib/types/wallet/transaction-options.ts b/bindings/nodejs/lib/types/wallet/transaction-options.ts index 10249d9a17..3f88d2b72d 100644 --- a/bindings/nodejs/lib/types/wallet/transaction-options.ts +++ b/bindings/nodejs/lib/types/wallet/transaction-options.ts @@ -1,7 +1,7 @@ // Copyright 2024 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { AccountAddress, AccountId, Bech32Address } from '../block'; +import { AccountAddress, AccountId, Bech32Address, OutputId } from '../block'; import { TaggedDataPayload } from '../block/payload/tagged'; import { Burn } from '../client'; import { u256, HexEncodedString, NumericString, u64 } from '../utils'; @@ -18,9 +18,9 @@ export interface TransactionOptions { * If custom inputs are provided, only those are used. * If also other additional inputs should be used, `mandatoryInputs` should be used instead. */ - customInputs?: string[]; + customInputs?: OutputId[]; /** Inputs that must be used for the transaction. */ - mandatoryInputs?: string[]; + mandatoryInputs?: OutputId[]; /** Specifies what needs to be burned during input selection. */ burn?: Burn; /** Optional note, that is only stored locally. */ diff --git a/bindings/nodejs/lib/types/wallet/transaction.ts b/bindings/nodejs/lib/types/wallet/transaction.ts index 9f4d65e0ae..290bf7d344 100644 --- a/bindings/nodejs/lib/types/wallet/transaction.ts +++ b/bindings/nodejs/lib/types/wallet/transaction.ts @@ -1,7 +1,8 @@ // Copyright 2021-2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import { Type } from 'class-transformer'; +import { Transform, Type } from 'class-transformer'; +import { BlockId, TransactionId } from '../block'; import { SignedTransactionPayload } from '../block/payload/signed_transaction'; import { OutputResponse } from '../models/api'; @@ -23,13 +24,17 @@ export class TransactionWithMetadata { @Type(() => SignedTransactionPayload) payload!: SignedTransactionPayload; /** The block id in which the transaction payload was included */ - blockId?: string; + @Type(() => BlockId) + @Transform(({ value }) => new BlockId(value), { toClassOnly: true }) + blockId?: BlockId; /** The inclusion state of the transaction */ inclusionState!: InclusionState; /** The creation time */ timestamp!: string; /** The transaction id */ - transactionId!: string; + @Type(() => TransactionId) + @Transform(({ value }) => new TransactionId(value), { toClassOnly: true }) + transactionId!: TransactionId; /** The network id in which the transaction was sent */ networkId!: string; /** If the transaction was created by the wallet or someone else */ diff --git a/bindings/nodejs/lib/types/wallet/wallet.ts b/bindings/nodejs/lib/types/wallet/wallet.ts index af3953ee98..31de56761b 100644 --- a/bindings/nodejs/lib/types/wallet/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/wallet.ts @@ -8,10 +8,10 @@ import { NftId, TokenId, } from '../block/id'; -import { DecayedMana, u256, u64 } from '../utils'; +import { DecayedMana, HexEncodedString, u256, u64 } from '../utils'; import { IClientOptions } from '../client'; import { Bip44, SecretManagerType } from '../secret_manager/secret-manager'; -import { Bech32Address, OutputId } from '../block'; +import { Bech32Address } from '../block'; /** Options for the Wallet builder. */ export interface WalletOptions { @@ -51,7 +51,7 @@ export interface Balance { * Outputs with multiple unlock conditions and if they can currently be spent or not. If there is a * TimelockUnlockCondition or ExpirationUnlockCondition this can change at any time */ - potentiallyLockedOutputs: { [outputId: OutputId]: boolean }; + potentiallyLockedOutputs: { [outputId: HexEncodedString]: boolean }; } /** The balance of the base coin */ diff --git a/bindings/nodejs/lib/utils/utils.ts b/bindings/nodejs/lib/utils/utils.ts index e75d8997ec..0609d7072f 100644 --- a/bindings/nodejs/lib/utils/utils.ts +++ b/bindings/nodejs/lib/utils/utils.ts @@ -466,7 +466,7 @@ export class Utils { inputs: InputSigningData[], protocolParameters: ProtocolParameters, unlocks?: Unlock[], - manaRewards?: { [outputId: OutputId]: NumericString }, + manaRewards?: { [outputId: HexEncodedString]: NumericString }, ): string { const conflictReason = callUtilsMethod({ name: 'verifyTransactionSemantic', diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index 6611def1fe..0bf0f27452 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -1522,7 +1522,7 @@ export class Wallet { * included (referenced by a milestone). Returns the included block id. */ async reissueTransactionUntilIncluded( - transactionId: string, + transactionId: TransactionId, interval?: number, maxAttempts?: number, ): Promise { diff --git a/bindings/nodejs/tests/types/ids.spec.ts b/bindings/nodejs/tests/types/ids.spec.ts new file mode 100644 index 0000000000..bf1bbc1d74 --- /dev/null +++ b/bindings/nodejs/tests/types/ids.spec.ts @@ -0,0 +1,21 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { expect, describe, it } from '@jest/globals'; +import { BlockId, OutputId, TransactionId } from '../../'; + +describe('ID tests', () => { + + it('get slot index', async () => { + const blockId = new BlockId("0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64900000000") + expect(blockId.slotIndex()).toEqual(0); + const transactionId = new TransactionId("0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c64901000000") + expect(transactionId.slotIndex()).toEqual(1); + }); + + it('get output index', async () => { + const outputId = new OutputId("0x52fdfc072182654f163f5f0f9a621d729566c74d10037c4d7bbb0407d1e2c649000000002a00") + expect(outputId.transactionId().slotIndex()).toEqual(0); + expect(outputId.outputIndex()).toEqual(42); + }); +}); diff --git a/bindings/wasm/tests/utilityMethods.spec.ts b/bindings/wasm/tests/utilityMethods.spec.ts index 5c56aa27ce..5b02f47f2d 100644 --- a/bindings/wasm/tests/utilityMethods.spec.ts +++ b/bindings/wasm/tests/utilityMethods.spec.ts @@ -1,4 +1,4 @@ -import { Utils } from '../node/lib'; +import { OutputId, Utils } from '../node/lib'; describe('Utils methods', () => { it('generates and validates mnemonic', async () => { @@ -50,7 +50,7 @@ describe('Utils methods', () => { it('hash output id', async () => { const outputId = - '0x0000000000000000000000000000000000000000000000000000000000000000000000000000'; + new OutputId('0x0000000000000000000000000000000000000000000000000000000000000000000000000000'); const accountId = Utils.computeAccountId(outputId);