diff --git a/apps/playground/src/pages/api/donate-mint-mesh.ts b/apps/playground/src/pages/api/donate-mint-mesh.ts index 2c3409340..76f2d4800 100644 --- a/apps/playground/src/pages/api/donate-mint-mesh.ts +++ b/apps/playground/src/pages/api/donate-mint-mesh.ts @@ -94,7 +94,7 @@ export default async function handler( .selectUtxosFrom(utxos) .mint("1", policyId, stringToHex(assetName)) .mintingScript(forgingScript) - .metadataValue("721", fullAssetMetadata) + .metadataValue(721, fullAssetMetadata) .txOut(donateAddress, [ { unit: "lovelace", quantity: costLovelace.toString() }, ]) diff --git a/apps/playground/src/pages/apis/transaction/basics/cip20.tsx b/apps/playground/src/pages/apis/transaction/basics/cip20.tsx index 2fc46c5e2..11ec8f601 100644 --- a/apps/playground/src/pages/apis/transaction/basics/cip20.tsx +++ b/apps/playground/src/pages/apis/transaction/basics/cip20.tsx @@ -34,7 +34,7 @@ function Left() { The specification for the individual strings follow the general design specification for JSON metadata, which is already implemented and in operation on the cardano blockchain. The used metadatum label is{" "} - 674:, this number was choosen because it is the T9 encoding + 674:, this number was chosen because it is the T9 encoding of the string msg. The message content has the key msg: and consists of an array of individual message-strings. The number of theses diff --git a/apps/playground/src/pages/apis/txbuilder/basics/cip20.tsx b/apps/playground/src/pages/apis/txbuilder/basics/cip20.tsx index 3d20ced5a..8d0903519 100644 --- a/apps/playground/src/pages/apis/txbuilder/basics/cip20.tsx +++ b/apps/playground/src/pages/apis/txbuilder/basics/cip20.tsx @@ -22,7 +22,7 @@ export default function TxbuilderCip20() { function Left() { let code = `txBuilder\n`; - code += ` .metadataValue(tag, metadata)\n`; + code += ` .metadataValue(label, metadata)\n`; return ( <> @@ -36,7 +36,7 @@ function Left() { The specification for the individual strings follow the general design specification for JSON metadata, which is already implemented and in operation on the cardano blockchain. The used metadatum label is{" "} - 674: this number was choosen because it is the T9 encoding + 674: this number was chosen because it is the T9 encoding of the string msg. The message content has the key msg: and consists of an array of individual message-strings. The number of theses @@ -60,14 +60,14 @@ function Right() { const changeAddress = await wallet.getChangeAddress(); const txBuilder = getTxBuilder(); - const tag = "674"; + const label = 674; const metadata = { msg: message.split("\n"), }; const unsignedTx = await txBuilder .changeAddress(changeAddress) - .metadataValue(tag.toString(), metadata) + .metadataValue(label, metadata) .selectUtxosFrom(utxos) .complete(); @@ -82,7 +82,7 @@ function Right() { codeSnippet += `const changeAddress = await wallet.getChangeAddress();\n`; codeSnippet += `const txBuilder = getTxBuilder();\n`; codeSnippet += `\n`; - codeSnippet += `const tag = "674";\n`; + codeSnippet += `const label = 674;\n`; codeSnippet += `const metadata = {\n`; codeSnippet += ` msg: [\n`; for (let line of message.split("\n")) { @@ -92,7 +92,7 @@ function Right() { codeSnippet += `});\n\n`; codeSnippet += `const unsignedTx = await txBuilder\n`; codeSnippet += ` .changeAddress(changeAddress)\n`; - codeSnippet += ` .metadataValue(tag, metadata)\n`; + codeSnippet += ` .metadataValue(label, metadata)\n`; codeSnippet += ` .selectUtxosFrom(utxos)\n`; codeSnippet += ` .complete();\n`; codeSnippet += `\n`; diff --git a/apps/playground/src/pages/apis/txbuilder/basics/multisig.tsx b/apps/playground/src/pages/apis/txbuilder/basics/multisig.tsx index f8297619a..9e209ef01 100644 --- a/apps/playground/src/pages/apis/txbuilder/basics/multisig.tsx +++ b/apps/playground/src/pages/apis/txbuilder/basics/multisig.tsx @@ -29,7 +29,7 @@ function Left() { codeTx += `const unsignedTx = await txBuilder\n`; codeTx += ` .mint("1", policyId, stringToHex("MeshToken"))\n`; codeTx += ` .mintingScript(forgingScript)\n`; - codeTx += ` .metadataValue("721", { [policyId]: { [assetName]: demoAssetMetadata } })\n`; + codeTx += ` .metadataValue(721, { [policyId]: { [assetName]: demoAssetMetadata } })\n`; codeTx += ` .changeAddress(address)\n`; codeTx += ` .selectUtxosFrom(utxos)\n`; codeTx += ` .complete();\n`; @@ -88,7 +88,7 @@ function Right() { const unsignedTx = await txBuilder .mint("1", policyId, stringToHex("MeshToken")) .mintingScript(forgingScript) - .metadataValue("721", { [policyId]: { [assetName]: demoAssetMetadata } }) + .metadataValue(721, { [policyId]: { [assetName]: demoAssetMetadata } }) .changeAddress(address) .selectUtxosFrom(utxos) .complete(); @@ -124,7 +124,7 @@ function Right() { codeSnippet += `const unsignedTx = await txBuilder\n`; codeSnippet += ` .mint("1", policyId, stringToHex("MeshToken"))\n`; codeSnippet += ` .mintingScript(forgingScript)\n`; - codeSnippet += ` .metadataValue("721", { [policyId]: { [assetName]: demoAssetMetadata } })\n`; + codeSnippet += ` .metadataValue(721, { [policyId]: { [assetName]: demoAssetMetadata } })\n`; codeSnippet += ` .changeAddress(address)\n`; codeSnippet += ` .selectUtxosFrom(utxos)\n`; codeSnippet += ` .complete();\n`; diff --git a/apps/playground/src/pages/apis/txbuilder/basics/set-metadata.tsx b/apps/playground/src/pages/apis/txbuilder/basics/set-metadata.tsx index ae9f7bda8..9dda03c70 100644 --- a/apps/playground/src/pages/apis/txbuilder/basics/set-metadata.tsx +++ b/apps/playground/src/pages/apis/txbuilder/basics/set-metadata.tsx @@ -22,7 +22,7 @@ export default function TxbuilderSetMetadata() { function Left() { let code = `txBuilder\n`; - code += ` .metadataValue(tag, metadata)\n`; + code += ` .metadataValue(label, metadata)\n`; return ( <> @@ -47,12 +47,12 @@ function Right() { const changeAddress = await wallet.getChangeAddress(); const txBuilder = getTxBuilder(); - const tag = "0"; + const label = 0; const metadata = "This is a message from the Mesh SDK"; const unsignedTx = await txBuilder .changeAddress(changeAddress) - .metadataValue(tag.toString(), metadata) + .metadataValue(label, metadata) .selectUtxosFrom(utxos) .complete(); @@ -66,11 +66,11 @@ function Right() { codeSnippet += `const address = await wallet.getChangeAddress();\n`; codeSnippet += `const txBuilder = getTxBuilder();\n`; codeSnippet += `\n`; - codeSnippet += `const tag = "0";\n`; + codeSnippet += `const label = 0;\n`; codeSnippet += `const metadata = "This is a message from the Mesh SDK";\n\n`; codeSnippet += `const unsignedTx = await txBuilder\n`; codeSnippet += ` .changeAddress(address)\n`; - codeSnippet += ` .metadataValue(tag, metadata)\n`; + codeSnippet += ` .metadataValue(label, metadata)\n`; codeSnippet += ` .selectUtxosFrom(utxos)\n`; codeSnippet += ` .complete();\n`; codeSnippet += `\n`; diff --git a/apps/playground/src/pages/apis/txbuilder/minting/minting-native-script.tsx b/apps/playground/src/pages/apis/txbuilder/minting/minting-native-script.tsx index f8526b1a6..a369af6c7 100644 --- a/apps/playground/src/pages/apis/txbuilder/minting/minting-native-script.tsx +++ b/apps/playground/src/pages/apis/txbuilder/minting/minting-native-script.tsx @@ -84,7 +84,7 @@ function Right() { const unsignedTx = await txBuilder .mint("1", policyId, tokenNameHex) .mintingScript(forgingScript) - .metadataValue("721", metadata) + .metadataValue(721, metadata) .changeAddress(changeAddress) .invalidHereafter(99999999) .selectUtxosFrom(utxos) @@ -125,7 +125,7 @@ const txBuilder = getTxBuilder(); const unsignedTx = await txBuilder .mint("1", policyId, tokenNameHex) .mintingScript(forgingScript) - .metadataValue("721", metadata) + .metadataValue(721, metadata) .changeAddress(changeAddress) .invalidHereafter(99999999) .selectUtxosFrom(utxos) diff --git a/apps/playground/src/pages/apis/txbuilder/minting/minting-one-signature.tsx b/apps/playground/src/pages/apis/txbuilder/minting/minting-one-signature.tsx index 956184cd8..4c2bbfe24 100644 --- a/apps/playground/src/pages/apis/txbuilder/minting/minting-one-signature.tsx +++ b/apps/playground/src/pages/apis/txbuilder/minting/minting-one-signature.tsx @@ -91,7 +91,7 @@ function Right() { const unsignedTx = await txBuilder .mint("1", policyId, tokenNameHex) .mintingScript(forgingScript) - .metadataValue("721", metadata) + .metadataValue(721, metadata) .changeAddress(changeAddress) .selectUtxosFrom(utxos) .complete(); @@ -124,7 +124,7 @@ function Right() { codeSnippet += `const unsignedTx = await txBuilder\n`; codeSnippet += ` .mint("1", policyId, tokenNameHex)\n`; codeSnippet += ` .mintingScript(forgingScript)\n`; - codeSnippet += ` .metadataValue("721", metadata)\n`; + codeSnippet += ` .metadataValue(721, metadata)\n`; codeSnippet += ` .changeAddress(changeAddress)\n`; codeSnippet += ` .selectUtxosFrom(utxos)\n`; codeSnippet += ` .complete();\n`; diff --git a/apps/playground/src/pages/apis/txbuilder/minting/minting-plutus-script.tsx b/apps/playground/src/pages/apis/txbuilder/minting/minting-plutus-script.tsx index b2acc3287..7f18f0441 100644 --- a/apps/playground/src/pages/apis/txbuilder/minting/minting-plutus-script.tsx +++ b/apps/playground/src/pages/apis/txbuilder/minting/minting-plutus-script.tsx @@ -42,7 +42,7 @@ function Left() { codeSnippet3 += ` .mint("1", policyId, tokenNameHex)\n`; codeSnippet3 += ` .mintingScript(demoPlutusMintingScript)\n`; codeSnippet3 += ` .mintRedeemerValue(mConStr0([userInput]))\n`; - codeSnippet3 += ` .metadataValue("721", metadata)\n`; + codeSnippet3 += ` .metadataValue(721, metadata)\n`; codeSnippet3 += ` .changeAddress(changeAddress)\n`; codeSnippet3 += ` .selectUtxosFrom(utxos)\n`; codeSnippet3 += ` .txInCollateral(\n`; @@ -125,7 +125,7 @@ function Right() { .mint("1", policyId, tokenNameHex) .mintingScript(demoPlutusMintingScript) .mintRedeemerValue(mConStr0([userInput])) - .metadataValue("721", metadata) + .metadataValue(721, metadata) .changeAddress(changeAddress) .selectUtxosFrom(utxos) .txInCollateral( @@ -159,7 +159,7 @@ function Right() { code += ` .mint("1", policyId, tokenNameHex)\n`; code += ` .mintingScript(demoPlutusMintingScript)\n`; code += ` .mintRedeemerValue(mConStr0(['${userInput}']))\n`; - code += ` .metadataValue("721", metadata)\n`; + code += ` .metadataValue(721, metadata)\n`; code += ` .changeAddress(changeAddress)\n`; code += ` .selectUtxosFrom(utxos)\n`; code += ` .txInCollateral(\n`; diff --git a/apps/playground/src/pages/apis/txbuilder/minting/minting-royalty-token.tsx b/apps/playground/src/pages/apis/txbuilder/minting/minting-royalty-token.tsx index c8d591a4c..d768e46d1 100644 --- a/apps/playground/src/pages/apis/txbuilder/minting/minting-royalty-token.tsx +++ b/apps/playground/src/pages/apis/txbuilder/minting/minting-royalty-token.tsx @@ -81,7 +81,7 @@ function Right() { const unsignedTx = await txBuilder .mint("1", policyId, "") .mintingScript(forgingScript) - .metadataValue("777", assetMetadata) + .metadataValue(777, assetMetadata) .changeAddress(address) .selectUtxosFrom(utxos) .complete(); @@ -114,7 +114,7 @@ function Right() { code += `const unsignedTx = await txBuilder\n`; code += ` .mint("1", policyId, "")\n`; code += ` .mintingScript(forgingScript)\n`; - code += ` .metadataValue("777", assetMetadata)\n`; + code += ` .metadataValue(777, assetMetadata)\n`; code += ` .changeAddress(address)\n`; code += ` .selectUtxosFrom(utxos)\n`; code += ` .complete();\n`; diff --git a/apps/playground/src/pages/apis/txbuilder/minting/multiple-assets.tsx b/apps/playground/src/pages/apis/txbuilder/minting/multiple-assets.tsx index 6a872b5a1..75835d2fd 100644 --- a/apps/playground/src/pages/apis/txbuilder/minting/multiple-assets.tsx +++ b/apps/playground/src/pages/apis/txbuilder/minting/multiple-assets.tsx @@ -35,7 +35,7 @@ function Left() { let codeSnippet2 = ``; codeSnippet2 += `txBuilder\n`; - codeSnippet2 += ` .metadataValue("721", metadata)\n`; + codeSnippet2 += ` .metadataValue(721, metadata)\n`; codeSnippet2 += ` .changeAddress(changeAddress)\n`; codeSnippet2 += ` .selectUtxosFrom(utxos);\n`; codeSnippet2 += `\n`; @@ -88,7 +88,7 @@ function Right() { } txBuilder - .metadataValue("721", metadata) + .metadataValue(721, metadata) .changeAddress(changeAddress) .selectUtxosFrom(utxos); @@ -125,7 +125,7 @@ function Right() { codeSnippet += `}\n`; codeSnippet += `\n`; codeSnippet += `txBuilder\n`; - codeSnippet += ` .metadataValue("721", metadata)\n`; + codeSnippet += ` .metadataValue(721, metadata)\n`; codeSnippet += ` .changeAddress(changeAddress)\n`; codeSnippet += ` .selectUtxosFrom(utxos);\n`; codeSnippet += `\n`; diff --git a/packages/mesh-common/src/types/transaction-builder/index.ts b/packages/mesh-common/src/types/transaction-builder/index.ts index f3a8e37a6..495e43fb5 100644 --- a/packages/mesh-common/src/types/transaction-builder/index.ts +++ b/packages/mesh-common/src/types/transaction-builder/index.ts @@ -25,7 +25,7 @@ export type MeshTxBuilderBody = { referenceInputs: RefTxIn[]; mints: MintItem[]; changeAddress: string; - metadata: Metadata[]; + metadata: TxMetadata; validityRange: ValidityRange; certificates: Certificate[]; withdrawals: Withdrawal[]; @@ -50,7 +50,7 @@ export const emptyTxBuilderBody = (): MeshTxBuilderBody => ({ referenceInputs: [], mints: [], changeAddress: "", - metadata: [], + metadata: new Map(), validityRange: {}, certificates: [], withdrawals: [], @@ -73,6 +73,13 @@ export type ValidityRange = { // Mint Types +// Transaction Metadata + +export type MetadatumMap = Map; +export type Metadatum = bigint | number | string | Uint8Array | MetadatumMap | Metadatum[]; +export type TxMetadata = Map; + +// to be used for serialization export type Metadata = { tag: string; metadata: string; diff --git a/packages/mesh-contract/src/content-ownership/offchain/offchain.ts b/packages/mesh-contract/src/content-ownership/offchain/offchain.ts index ec14ac921..e821502c4 100644 --- a/packages/mesh-contract/src/content-ownership/offchain/offchain.ts +++ b/packages/mesh-contract/src/content-ownership/offchain/offchain.ts @@ -525,7 +525,7 @@ export class MeshContentOwnershipContract extends MeshTxInitiator { const txHex = await this.mesh .mint("1", policyId, tokenNameHex) .mintingScript(forgingScript) - .metadataValue("721", metadata) + .metadataValue(721, metadata, 2) .changeAddress(walletAddress) .selectUtxosFrom(utxos) .complete(); diff --git a/packages/mesh-contract/src/plutus-nft/offchain.ts b/packages/mesh-contract/src/plutus-nft/offchain.ts index e12bb7b02..6aa6c007e 100644 --- a/packages/mesh-contract/src/plutus-nft/offchain.ts +++ b/packages/mesh-contract/src/plutus-nft/offchain.ts @@ -200,7 +200,7 @@ export class MeshPlutusNFTContract extends MeshTxInitiator { if (assetMetadata) { const metadata = { [policyId]: { [tokenName]: { ...assetMetadata } } }; - tx.metadataValue("721", metadata); + tx.metadataValue(721, metadata, 2); } tx.mintRedeemerValue(mConStr0([])) diff --git a/packages/mesh-core-csl/src/core/adaptor/index.ts b/packages/mesh-core-csl/src/core/adaptor/index.ts index 1169f94a3..9fec4a2f3 100644 --- a/packages/mesh-core-csl/src/core/adaptor/index.ts +++ b/packages/mesh-core-csl/src/core/adaptor/index.ts @@ -4,6 +4,7 @@ import { certificateToObj } from "./certificate"; import { mintItemToObj } from "./mint"; import { networkToObj } from "./network"; import { outputToObj } from "./output"; +import { txMetadataToObj } from "./metadata"; import { collateralTxInToObj, txInToObj } from "./txIn"; import { voteToObj } from "./vote"; import { withdrawalToObj } from "./withdrawal"; @@ -33,7 +34,7 @@ export const meshTxBuilderBodyToObj = ({ referenceInputs: referenceInputs, mints: mints.map((mint) => mintItemToObj(mint)), changeAddress, - metadata: metadata, + metadata: txMetadataToObj(metadata), validityRange: validityRangeToObj(validityRange), certificates: certificates.map(certificateToObj), signingKey: signingKey, diff --git a/packages/mesh-core-csl/src/core/adaptor/metadata.ts b/packages/mesh-core-csl/src/core/adaptor/metadata.ts new file mode 100644 index 000000000..fac16864f --- /dev/null +++ b/packages/mesh-core-csl/src/core/adaptor/metadata.ts @@ -0,0 +1,35 @@ +import type { Metadatum, TxMetadata } from "@meshsdk/common"; + +export const txMetadataToObj = (metadata: TxMetadata): object => { + const result: Record = {}; + metadata.forEach((value: Metadatum, key: bigint) => { + result[key.toString()] = metadatumToObj(value); + }); + return result; +}; + +const metadatumToObj = (metadatum: Metadatum): any => { + if (typeof metadatum === "number" || typeof metadatum === "string") { + return metadatum; + } else if (typeof metadatum === "bigint") { + return metadatum.toString(); + } else if (metadatum instanceof Uint8Array) { + return uint8ArrayToHex(metadatum); + } else if (metadatum instanceof Map) { + const result: Record = {}; + metadatum.forEach((value, key) => { + result[metadatumToObj(key)] = metadatumToObj(value); + }); + return result; + } else if (Array.isArray(metadatum)) { + return metadatum.map(metadatumToObj); + } else { + throw new Error("metadatumToObj: Unsupported Metadatum type"); + } +}; + +const uint8ArrayToHex = (bytes: Uint8Array): string => { + return Array.from(bytes) + .map(byte => byte.toString(16).padStart(2, "0")) + .join(""); +}; diff --git a/packages/mesh-core-csl/test/core/builder.test.ts b/packages/mesh-core-csl/test/core/builder.test.ts index bc690b184..64cb0c499 100644 --- a/packages/mesh-core-csl/test/core/builder.test.ts +++ b/packages/mesh-core-csl/test/core/builder.test.ts @@ -85,7 +85,7 @@ describe("Builder", () => { mints: [], changeAddress: "addr_test1qq0yavv5uve45rwvfaw96qynrqt8ckpmkwcg08vlwxxdncxk82f5wz75mzaesmqzl79xqsmedwgucwtuav5str6untqqmykcpn", - metadata: [], + metadata: new Map(), validityRange: {}, certificates: [], withdrawals: [], diff --git a/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts b/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts index 8c02608e8..23a51c10d 100644 --- a/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts +++ b/packages/mesh-transaction/src/mesh-tx-builder/tx-builder-core.ts @@ -35,7 +35,7 @@ import { Withdrawal, } from "@meshsdk/common"; -import { MetadataMergeLevel, mergeAllMetadataByTag } from "../utils"; +import { MetadataMergeLevel, metadataObjToMap, setAndMergeTxMetadata } from "../utils"; export class MeshTxBuilderCore { txEvaluationMultiplier = 1.1; @@ -1414,18 +1414,16 @@ export class MeshTxBuilderCore { /** * Add metadata to the transaction - * @param tag The tag of the metadata + * @param label The label of the metadata, preferably number * @param metadata The metadata in any format - * @param mergeExistingMetadataByTag Whether to merge the new metadata - * under a tag with the existing metadata with the same tag + * @param mergeExistingMetadataByLabel Whether to merge the new metadata + * with any existing metadata under the same label, and upto what level * @returns The MeshTxBuilder instance */ - metadataValue = (tag: string, metadata: any, mergeExistingMetadataByTag: MetadataMergeLevel = false) => { - const metadataString = JSONBig.stringify(metadata); - this.meshTxBuilderBody.metadata.push({ tag, metadata: metadataString }); - if (mergeExistingMetadataByTag) { - this.meshTxBuilderBody.metadata = mergeAllMetadataByTag(this.meshTxBuilderBody.metadata, tag, mergeExistingMetadataByTag); - } + metadataValue = (label: number | bigint | string, metadata: any, mergeExistingMetadataByLabel: MetadataMergeLevel = false) => { + label = BigInt(label); + metadata = metadataObjToMap(metadata); + setAndMergeTxMetadata(this.meshTxBuilderBody.metadata, label, metadata, mergeExistingMetadataByLabel); return this; }; diff --git a/packages/mesh-transaction/src/transaction/index.ts b/packages/mesh-transaction/src/transaction/index.ts index fb3eecfef..7838301e6 100644 --- a/packages/mesh-transaction/src/transaction/index.ts +++ b/packages/mesh-transaction/src/transaction/index.ts @@ -586,15 +586,15 @@ export class Transaction { /** * Add a JSON metadata entry to the transaction. * - * @param {number} key The key to use for the metadata entry. - * @param {unknown} value The value to use for the metadata entry. - * @param {MetadataMergeLevel} mergeExistingMetadataByTag Whether to merge the new metadata - * under a tag (key) with the existing metadata with the same tag + * @param {number} label The label to use for the metadata entry. + * @param {unknown} metadata The value to use for the metadata entry. + * @param {MetadataMergeLevel} mergeExistingMetadataByLabel Whether to merge the new metadata + * with any existing metadata under the same label, and upto what level * @returns {Transaction} The Transaction object. * @see {@link https://meshjs.dev/apis/transaction#setMetadata} */ - setMetadata(key: number, value: unknown, mergeExistingMetadataByTag: MetadataMergeLevel = false): Transaction { - this.txBuilder.metadataValue(key.toString(), value as object, mergeExistingMetadataByTag); + setMetadata(label: number, metadata: unknown, mergeExistingMetadataByLabel: MetadataMergeLevel = false): Transaction { + this.txBuilder.metadataValue(label, metadata as object, mergeExistingMetadataByLabel); return this; } diff --git a/packages/mesh-transaction/src/transaction/transaction-v2.ts b/packages/mesh-transaction/src/transaction/transaction-v2.ts index 0fc7151db..184f3a819 100644 --- a/packages/mesh-transaction/src/transaction/transaction-v2.ts +++ b/packages/mesh-transaction/src/transaction/transaction-v2.ts @@ -12,6 +12,8 @@ import { UTxO, } from "@meshsdk/common"; +import type { MetadataMergeLevel } from "../utils/metadata"; + export interface TransactionV2 { sendAssets( receiver: string, @@ -47,7 +49,11 @@ export interface TransactionV2 { setRequiredSigners(addresses: string[]): this; setTimeToExpire(slot: string): this; setTimeToStart(slot: string): this; - setMetadata(key: number, value: unknown): this; + setMetadata( + label: number, + metadata: unknown, + mergeExistingMetadataByLabel: MetadataMergeLevel + ): this; withdrawRewards(rewardAddress: string, lovelace: string): this; delegateStake(rewardAddress: string, poolId: string): this; deregisterStake(rewardAddress: string): this; diff --git a/packages/mesh-transaction/src/utils/metadata.ts b/packages/mesh-transaction/src/utils/metadata.ts index e345ce0b2..e2d1f150b 100644 --- a/packages/mesh-transaction/src/utils/metadata.ts +++ b/packages/mesh-transaction/src/utils/metadata.ts @@ -1,34 +1,64 @@ import JSONBig from "json-bigint"; -import { Metadata } from "@meshsdk/common"; - -type ValueType = "array" | "object" | "primitive" | "nullish"; +import type { TxMetadata, Metadatum, MetadatumMap } from "@meshsdk/common"; export type MetadataMergeLevel = boolean | number; -const getMergeDepth = (mergeOption: MetadataMergeLevel): number => { - return typeof mergeOption === "number" - ? mergeOption - : mergeOption === true - ? 1 - : 0; +export const metadataObjToMap = (metadata: any): Metadatum => { + if (typeof metadata === "bigint") { + return metadata; + } else if (typeof metadata === "string") { + return metadata; + } else if (typeof metadata === "number") { + return metadata; + } else if (metadata instanceof Uint8Array) { + return metadata; + } else if (Array.isArray(metadata)) { + // Recursively process each element in the array + return metadata.map(metadataObjToMap); + } else if (metadata && typeof metadata === "object") { + // Convert object to MetadatumMap + const map: MetadatumMap = new Map(); + Object.entries(metadata).forEach(([key, value]) => { + map.set(metadataObjToMap(key), metadataObjToMap(value)); + }); + return map; + } else { + throw new Error("Metadata map conversion: Unsupported metadata type"); + } } -const getType = (value: unknown): ValueType => { - if (Array.isArray(value)) - return "array"; - if (value !== null && typeof value === "object") - return "object"; - if (value === null || value === undefined) - return "nullish"; - return "primitive"; +/** + * Insert new metadata under a label into the transaction's metadata section + * and optionally merge with the existing metadata under the same label recursively + * upto a specified depth. + * + * @param txMetadata metadata map of meshTxBuilderBody, updated implicitly + * @param label label that the new metadata corresponds to + * @param metadata the new metadata + * @param mergeOption the effective depth till which the merge should happen, + * beyond this depth, the newer element would replace the older one. + * If false or 0, the new metadata overwrites any existing metadata + * under the same label + * @returns updated metadata map for meshTxBuilderBody + */ +export const setAndMergeTxMetadata = (txMetadata: TxMetadata, label: bigint, metadata: Metadatum, mergeOption: MetadataMergeLevel): TxMetadata => { + const mergeDepth = getMergeDepth(mergeOption); + + if (txMetadata.has(label)) { + txMetadata.set(label, mergeContents(txMetadata.get(label) as Metadatum, metadata, mergeDepth)); + } else { + txMetadata.set(label, metadata); + } + + return txMetadata; } /** - * Recursively merge two items. Returns the 2nd item if the maximum allowed + * Recursively merge two metadata. Returns the 2nd item if the maximum allowed * merge depth has passed. * - * Merging objects ({ key: value }): - * Two objects are merged by recursively including the (key, value) pairs from both the objects. + * Merging maps ({ key: value }): + * Two maps are merged by recursively including the (key, value) pairs from both the maps. * When further merge isn't allowed (by currentDepth), the 2nd item is preferred, * replacing the 1st item. * @@ -46,88 +76,71 @@ const getType = (value: unknown): ValueType => { * @param currentDepth the current merge depth; decreases in a recursive call * @returns merged item or a preferred item, chosen according to currentDepth */ -const mergeContents = (a: any, b: any, currentDepth: number): any => { - const type_a = getType(a); - const type_b = getType(b); - - if (currentDepth <= 0 || type_a === "nullish" || type_b === "nullish") { - // Tend to return the 2nd item, which is supposed to be the updated one - // If both are nullish, 2nd item is returned - return b ?? a ?? b; +const mergeContents = (a: Metadatum, b: Metadatum, currentDepth: number): Metadatum => { + // Handle no merge + if (currentDepth <= 0) { + return b; } - - if (type_a === "primitive" && type_b === "primitive") { - if (a === b) { - return b; - } else { - throw new Error(`Tx metadata merge error: cannot merge ${a} with ${b}`); - } + // Handle merging of maps + if (a instanceof Map && b instanceof Map) { + b.forEach((value: Metadatum, key: Metadatum) => { + if (a.has(key)) { + a.set(key, mergeContents(a.get(key) as Metadatum, value, currentDepth - 1)); + } else { + a.set(key, value); + } + }); + return a; } - - else if (type_a === "array" && type_b === "array") { + // Handle merging of arrays + else if (Array.isArray(a) && Array.isArray(b)) { return [...a, ...b]; } - - else if (type_a === "object" && type_b === "object") { - for (const key in b) { - Object.assign(a, { [key]: mergeContents(a[key], b[key], currentDepth - 1) }); + // Handle merging of primitive types + if ( + (typeof a === "number" || typeof a === "bigint" || typeof a === "string" || a instanceof Uint8Array) && + (typeof b === "number" || typeof b === "bigint" || typeof b === "string" || b instanceof Uint8Array) + ) { + if (typeof a === typeof b) { + if (a === b) { + // Equal primitive types (string, number or bigint) + return b; + } + if (a instanceof Uint8Array && b instanceof Uint8Array && areUint8ArraysEqual(a, b)) { + // Equal Uint8Array values + return b; + } } - return a; + // If values are not equal or types are mismatched + throw new Error(`Tx metadata merge error: cannot merge ${JSONBig.stringify(a)} with ${JSONBig.stringify(b)}`); } - throw new Error(`Tx metadata merge error: cannot merge ${type_a} type with ${type_b} type`); + // Unsupported or mismatched types + throw new Error(`Tx metadata merge error: cannot merge ${getMetadatumType(a)} type with ${getMetadatumType(b)} type`); } -const mergeChosenMetadata = (metadataArr: string[], tag: string, mergeDepth: number): Metadata[] => { - if (!metadataArr.length) - return []; - - // Assuming all the elements are JSON stringified, parse them first - for (let i = 0; i < metadataArr.length; i++) { - try { - metadataArr[i] = JSONBig.parse(metadataArr[i]!); - } catch (e) { - throw new Error(`Tx metadata merge error: cannot parse metadata value: ${e}`); - } - } +const getMergeDepth = (mergeOption: MetadataMergeLevel): number => { + return typeof mergeOption === "number" + ? mergeOption + : mergeOption === true + ? 1 + : 0; +} - let mergedSoFar = metadataArr[0]; +const getMetadatumType = (a: Metadatum): string => { + if (a instanceof Map) return "map"; + if (Array.isArray(a)) return "array"; + return "primitive"; +} - for (let i = 1; i < metadataArr.length; i++) { - mergedSoFar = mergeContents(mergedSoFar, metadataArr[i], mergeDepth); +const areUint8ArraysEqual = (a: Uint8Array, b: Uint8Array): boolean => { + if (a.length !== b.length) { + return false; } - - return [{ tag, metadata: JSONBig.stringify(mergedSoFar) }]; -}; - -/** - * Merges multiple metadata entries in an array of metadata that belong to the same tag. - * Can merge objects upto a defined depth. - * - * @param metadataList metadata array of meshTxBuilderBody - * @param tag tag value to group all the metadata belonging to that same tag - * @param mergeOption the effective depth till which the merge will happen, - * beyond this depth, the newer element would replace the older one. - * If false or 0, the latest metadata entry with the specified tag is preserved - * and the earlier ones are discarded - * @returns metadata array for meshTxBuilderBody; with all the metadata entries not belonging - * to the specified tag are preserved - */ -export const mergeAllMetadataByTag = (metadataList: Metadata[], tag: string, mergeOption: MetadataMergeLevel): Metadata[] => { - const mergeDepth = getMergeDepth(mergeOption); - - const chosenElementsMetadata = []; - const restElements = []; - - for (const metadata of metadataList) { - if (metadata.tag == tag) { // Number can also match here - chosenElementsMetadata.push(metadata.metadata); - } else { - restElements.push(metadata); + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; } } - - const mergedItem = mergeChosenMetadata(chosenElementsMetadata, tag, mergeDepth); - - return [...restElements, ...mergedItem]; + return true; } diff --git a/packages/mesh-transaction/test/transaction/txMetadata.test.ts b/packages/mesh-transaction/test/transaction/txMetadata.test.ts index a0cbd8765..67d10799b 100644 --- a/packages/mesh-transaction/test/transaction/txMetadata.test.ts +++ b/packages/mesh-transaction/test/transaction/txMetadata.test.ts @@ -1,205 +1,199 @@ -import JSONBig from "json-bigint"; -import { Metadata } from "@meshsdk/common"; -import { mergeAllMetadataByTag } from "@meshsdk/transaction"; +import { metadataObjToMap, setAndMergeTxMetadata } from "@meshsdk/transaction"; describe("Transaction Metadata Merge", () => { - - const createMetadataArray = (arr: { tag: string, metadata: any }[]): Metadata[] => { - for (const elem of arr) { - elem.metadata = JSONBig.stringify(elem.metadata); - } - return arr; - }; - it("should merge two identical number metadata entries", () => { - const input = createMetadataArray([ - { tag: "0", metadata: 42 }, - { tag: "0", metadata: 42 }, + const txMetadata = new Map([ + [0n, 42] ]); - const expectedOutput = createMetadataArray([ - { tag: "0", metadata: 42 }, + const label = 0n; + const metadata = 42; + const expectedOutput = new Map([ + [0n, 42] ]); - const output = mergeAllMetadataByTag(input, "0", true); - expect(output).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, true); + expect(txMetadata).toEqual(expectedOutput); }); it("should merge two identical string metadata entries", () => { - const input = createMetadataArray([ - { tag: "1", metadata: "42" }, - { tag: "1", metadata: "42" }, + const txMetadata = new Map([ + [1n, "Hey!"] ]); - const expectedOutput = createMetadataArray([ - { tag: "1", metadata: "42" }, + const label = 1n; + const metadata = "Hey!"; + const expectedOutput = new Map([ + [1n, "Hey!"] ]); - const output = mergeAllMetadataByTag(input, "1", true); - expect(output).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, true); + expect(txMetadata).toEqual(expectedOutput); }); it("should not merge two different numbers", () => { - const input = createMetadataArray([ - { tag: "1", metadata: 42 }, - { tag: "1", metadata: 43 }, + const txMetadata = new Map([ + [1n, 42] ]); - expect(() => mergeAllMetadataByTag(input, "1", true)).toThrow("cannot merge 42 with 43"); + const label = 1n; + const metadata = 43; + expect(() => setAndMergeTxMetadata(txMetadata, label, metadata, true)).toThrow("cannot merge 42 with 43"); }); it("should not merge two different strings", () => { - const input = createMetadataArray([ - { tag: "0", metadata: "Alice" }, - { tag: "0", metadata: "Bob" }, + const txMetadata = new Map([ + [0n, "Alice"] ]); - expect(() => mergeAllMetadataByTag(input, "0", true)).toThrow("cannot merge Alice with Bob"); + const label = 0n; + const metadata = "Bob"; + expect(() => setAndMergeTxMetadata(txMetadata, label, metadata, true)).toThrow("cannot merge Alice with Bob"); }); it("should not merge two same values of different types", () => { - const input = createMetadataArray([ - { tag: "0", metadata: 42 }, - { tag: "0", metadata: "42" }, + const txMetadata = new Map([ + [0n, 42] ]); - expect(() => mergeAllMetadataByTag(input, "0", true)).toThrow("cannot merge 42 with 42"); + const label = 0n; + const metadata = "42"; + expect(() => setAndMergeTxMetadata(txMetadata, label, metadata, true)).toThrow("cannot merge 42 with 42"); }); it("should replace with the latest item if there is no merge", () => { - const input = createMetadataArray([ - { tag: "1", metadata: 42 }, - { tag: "1", metadata: 43 }, + const txMetadata = new Map([ + [1n, 42] ]); - const expectedOutput = createMetadataArray([ - { tag: "1", metadata: 43 }, + const label = 1n; + const metadata = 43; + const expectedOutput = new Map([ + [1n, 43] ]); - expect(mergeAllMetadataByTag(input, "1", false)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, false); + expect(txMetadata).toEqual(expectedOutput); }); it("should not merge two different values of the same object key", () => { - const input = createMetadataArray([ - { tag: "721", metadata: { version: 1 } }, - { tag: "721", metadata: { version: 2 } }, + const txMetadata = new Map([ + [721n, metadataObjToMap({ version: 1 })] ]); - expect(() => mergeAllMetadataByTag(input, "721", 2)).toThrow("cannot merge 1 with 2"); + const label = 721n; + const metadata = metadataObjToMap({ version: 2 }); + expect(() => setAndMergeTxMetadata(txMetadata, label, metadata, 2)).toThrow("cannot merge 1 with 2"); }); it("should replace with the latest value of the same object key if values are not merged", () => { - const input = createMetadataArray([ - { tag: "721", metadata: { version: 1 } }, - { tag: "721", metadata: { version: 2 } }, + const txMetadata = new Map([ + [721n, metadataObjToMap({ version: 1 })] ]); - const expectedOutput = createMetadataArray([ - { tag: "721", metadata: { version: 2 } }, + const label = 721n; + const metadata = metadataObjToMap({ version: 2 }); + const expectedOutput = new Map([ + [721n, metadataObjToMap({ version: 2 })] ]); - expect(mergeAllMetadataByTag(input, "721", 1)).toEqual(expectedOutput); - expect(mergeAllMetadataByTag(input, "721", true)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, 1); + expect(txMetadata).toEqual(expectedOutput); }); it("should not merge different types", () => { - expect(() => mergeAllMetadataByTag( - createMetadataArray([ - { tag: "0", metadata: 0 }, - { tag: "0", metadata: [] } - ]), - "0", + expect(() => setAndMergeTxMetadata( + new Map([[0n, 0]]), + 0n, // label + [], // metadata true )).toThrow("cannot merge primitive type with array type"); - expect(() => mergeAllMetadataByTag( - createMetadataArray([ - { tag: "0", metadata: {} }, - { tag: "0", metadata: "" } - ]), - "0", + expect(() => setAndMergeTxMetadata( + new Map([[0n, metadataObjToMap({})]]), + 0n, // label + "", // metadata true - )).toThrow("cannot merge object type with primitive type"); - expect(() => mergeAllMetadataByTag( - createMetadataArray([ - { tag: "0", metadata: {} }, - { tag: "0", metadata: [] } - ]), - "0", + )).toThrow("cannot merge map type with primitive type"); + expect(() => setAndMergeTxMetadata( + new Map([[0n, metadataObjToMap({})]]), + 0n, // label + [], // metadata true - )).toThrow("cannot merge object type with array type"); + )).toThrow("cannot merge map type with array type"); }); - it("should preserve object key and value if merged with a nullish value", () => { - expect(mergeAllMetadataByTag( - createMetadataArray([ - { tag: "0", metadata: { value: 1 } }, - { tag: "0", metadata: { value: null } } - ]), - "0", - 2 - )).toEqual(createMetadataArray([ - { tag: "0", metadata: { value: 1 } } - ])); - expect(mergeAllMetadataByTag( - createMetadataArray([ - { tag: "0", metadata: { value: 1 } }, - { tag: "0", metadata: { value: undefined } } - ]), - "0", - true - )).toEqual(createMetadataArray([ - { tag: "0", metadata: { value: 1 } } - ])); + it("map conversion should not allow nullish values", () => { + expect(() => metadataObjToMap({ "value": null })).toThrow("Unsupported metadata type"); + }); + it("should not allow merging with nullish values", () => { + const txMetadata = new Map([ + [1n, metadataObjToMap({ "value": "some" })] + ]); + const label = 1n; + const metadata = new Map([["value", null]]); + expect(() => setAndMergeTxMetadata(txMetadata, label, metadata, 1)).toThrow(); }); it("should replace 674 standard msg array for default merge depth", () => { - const input = createMetadataArray([ - { tag: "674", metadata: { msg: ["A", "B", "C"] } }, - { tag: "674", metadata: { msg: ["D", "E", "F"] } }, - { tag: "674", metadata: { msg2: ["X", "Y", "Z"] } }, + const txMetadata = new Map([ + [ + 674n, + metadataObjToMap({ + msg: ["A", "B", "C"], + msg2: ["X", "Y", "Z"] + }) + ] ]); - - const expectedOutput = createMetadataArray([ - { - tag: "674", metadata: { + const label = 674n; + const metadata = metadataObjToMap({ + msg: ["D", "E", "F"] + }); + const expectedOutput = new Map([ + [ + 674n, + metadataObjToMap({ msg: ["D", "E", "F"], msg2: ["X", "Y", "Z"] - } - } + }) + ] ]); - - expect(mergeAllMetadataByTag(input, "674", 1)).toEqual(expectedOutput); - expect(mergeAllMetadataByTag(input, "674", true)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, true); + expect(txMetadata).toEqual(expectedOutput); }); it("should concatenate 674 standard msg arrays for merge depth 2", () => { - const input = createMetadataArray([ - { tag: "674", metadata: { msg: ["A", "B", "C"] } }, - { tag: "674", metadata: { msg: ["D", "E", "F"] } }, - { tag: "674", metadata: { msg2: ["X", "Y", "Z"] } }, + const txMetadata = new Map([ + [ + 674n, + metadataObjToMap({ + msg: ["A", "B", "C"], + msg2: ["X", "Y", "Z"] + }) + ] ]); - - const expectedOutput = createMetadataArray([ - { - tag: "674", metadata: { + const label = 674n; + const metadata = metadataObjToMap({ + msg: ["D", "E", "F"] + }); + const expectedOutput = new Map([ + [ + 674n, + metadataObjToMap({ msg: ["A", "B", "C", "D", "E", "F"], msg2: ["X", "Y", "Z"] - } - } + }) + ] ]); - - expect(mergeAllMetadataByTag(input, "674", 2)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, 2); + expect(txMetadata).toEqual(expectedOutput); }); it("should merge multiple CIP-25 NFTs metadata under the same policy id", () => { - const input: Metadata[] = createMetadataArray([ - { - tag: "721", - metadata: { + const txMetadata = new Map([ + [ + 721n, + metadataObjToMap({ "policyId1": { "My NFT 1": { "name": "My NFT 1" } } - } - }, - { - tag: "721", - metadata: { - "policyId1": { - "My NFT 2": { - "name": "My NFT 2", - "description": "My second NFT" - } - } + }) + ] + ]); + const label = 721n; + const metadata = metadataObjToMap({ + "policyId1": { + "My NFT 2": { + "name": "My NFT 2", + "description": "My second NFT" } } - ]); - - const expectedOutput: Metadata[] = createMetadataArray([ - { - tag: "721", - metadata: { + }); + const expectedOutput = new Map([ + [ + 721n, + metadataObjToMap({ "policyId1": { "My NFT 1": { "name": "My NFT 1" @@ -209,18 +203,18 @@ describe("Transaction Metadata Merge", () => { "description": "My second NFT" } } - } - } + }) + ] ]); - - expect(mergeAllMetadataByTag(input, "721", 2)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, 2); + expect(txMetadata).toEqual(expectedOutput); }); it("should merge multiple CIP-25 NFTs metadata under different policy ids", () => { - const input = createMetadataArray([ - { - tag: "721", - metadata: { + const txMetadata = new Map([ + [ + 721n, + metadataObjToMap({ "policyId1": { "My NFT 1": { "name": "My NFT 1", @@ -229,40 +223,60 @@ describe("Transaction Metadata Merge", () => { ] } } + }) + ] + ]); + const label = 721n; + const metadata1 = metadataObjToMap({ + "policyId2": { + "My NFT 1": { + "name": "My NFT 1 Policy 2", + "files": [ + { name: "NFT 1 P 2", src: "abc", mediaType: "image/png" } + ] } - }, - { - tag: "721", - metadata: { - "policyId2": { + } + }); + const expectedOutput1 = new Map([ + [ + 721n, + metadataObjToMap({ + "policyId1": { "My NFT 1": { - "name": "My NFT 1 Policy 2", + "name": "My NFT 1", "files": [ - { name: "NFT 1 P 2", src: "abc", mediaType: "image/png" } + { name: "NFT 1 Image", src: "xyz", mediaType: "image/jpeg" } ] } - } - } - }, - { - tag: "721", - metadata: { - "policyId1": { - "My NFT 2": { - "name": "My NFT 2", + }, + "policyId2": { + "My NFT 1": { + "name": "My NFT 1 Policy 2", "files": [ - { name: "NFT 2 Image", src: "pqr", mediaType: "image/jpeg" } + { name: "NFT 1 P 2", src: "abc", mediaType: "image/png" } ] } } + }) + ] + ]); + setAndMergeTxMetadata(txMetadata, label, metadata1, 2); + expect(txMetadata).toEqual(expectedOutput1); + // Merge more NFT metadata + const metadata2 = metadataObjToMap({ + "policyId1": { + "My NFT 2": { + "name": "My NFT 2", + "files": [ + { name: "NFT 2 Image", src: "pqr", mediaType: "image/jpeg" } + ] } } - ]); - - const expectedOutput = createMetadataArray([ - { - tag: "721", - metadata: { + }); + const expectedOutput2 = new Map([ + [ + 721n, + metadataObjToMap({ "policyId1": { "My NFT 1": { "name": "My NFT 1", @@ -285,133 +299,90 @@ describe("Transaction Metadata Merge", () => { ] } } - } - } + }) + ] ]); - - expect(mergeAllMetadataByTag(input, "721", 2)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata2, 2); + expect(txMetadata).toEqual(expectedOutput2); }); it("should replace with the latest CIP-25 NFT metadata of the same policy and asset id", () => { - const input = createMetadataArray([ - { - tag: "721", - metadata: { - "policyId1": { - "My NFT 1": { name: "NFT 1 Name", files: [{ name: "NFT Image" }] } // old metadata here - } - } - }, - { - tag: "721", - metadata: { + const txMetadata = new Map([ + [ + 721n, + metadataObjToMap({ "policyId1": { + "My NFT 1": { name: "NFT 1 Name", files: [{ name: "NFT Image" }] }, // old metadata here "My NFT 2": { name: "NFT 2 Name" } } - } - }, - { - tag: "721", - metadata: { - "policyId1": { - "My NFT 1": { name: "Latest NFT 1", image: "xyz", description: "Latest NFT here" } - } - } - } + }) + ] ]); - - const expectedOutput = createMetadataArray([ - { - tag: "721", - metadata: { + const label = 721n; + const metadata = metadataObjToMap({ + "policyId1": { + "My NFT 1": { name: "Latest NFT 1", image: "xyz", description: "Latest NFT here" } + } + }); + const expectedOutput = new Map([ + [ + 721n, + metadataObjToMap({ "policyId1": { "My NFT 1": { name: "Latest NFT 1", image: "xyz", description: "Latest NFT here" }, "My NFT 2": { name: "NFT 2 Name" } } - } - } + }) + ] ]); - - expect(mergeAllMetadataByTag(input, "721", 2)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, 2); + expect(txMetadata).toEqual(expectedOutput); }); it("should attach version to CIP-25 metadata", () => { - const input = createMetadataArray([ - { - tag: "721", - metadata: { - "policyId1": { "My NFT 1": { name: "My NFT 1" } } - } - }, - { - tag: "721", - metadata: { - "policyId1": { "My NFT 2": { name: "My NFT 2" } } - } - }, - { - tag: "721", - metadata: { - version: "1.0" - } - }, - { - tag: "721", - metadata: { + const txMetadata = new Map([ + [ + 721n, + metadataObjToMap({ + "policyId1": { "My NFT 1": { name: "My NFT 1" } }, "policyId2": { "My NFT 1": { name: "My NFT 1 Policy 2" } } - } - } + }) + ] ]); - - const expectedOutput = createMetadataArray([ - { - tag: "721", - metadata: { - "policyId1": { - "My NFT 1": { name: "My NFT 1" }, - "My NFT 2": { name: "My NFT 2" } - }, - "version": "1.0", // version inserted in an ordered manner - "policyId2": { - "My NFT 1": { name: "My NFT 1 Policy 2" } - } - } - } + const label = 721n; + const metadata = metadataObjToMap({ + version: 1 + }); + const expectedOutput = new Map([ + [ + 721n, + metadataObjToMap({ + "policyId1": { "My NFT 1": { name: "My NFT 1" } }, + "policyId2": { "My NFT 1": { name: "My NFT 1 Policy 2" } }, + "version": 1 + }) + ] ]); - - expect(mergeAllMetadataByTag(input, "721", 2)).toEqual(expectedOutput); + setAndMergeTxMetadata(txMetadata, label, metadata, 2); + expect(txMetadata).toEqual(expectedOutput); }); it("should preserve metadata entries with other tags in the original order", () => { - const input = createMetadataArray([ - { tag: "0", metadata: "line 1" }, - { tag: "0", metadata: "line 2" }, - { tag: "674", metadata: { msg: ["line 3"] } }, - { tag: "721", metadata: { policyId: { NFT: { name: "NFT" } } } }, - { tag: "674", metadata: { msg: ["line 5"] } }, - { tag: "721", metadata: { policyId: { NFT2: { name: "NFT 2" } } } }, - { tag: "1", metadata: "line 7" }, + const txMetadata = new Map([ + [0n, metadataObjToMap("line 1")], + [674n, metadataObjToMap({ msg: "line 2" })], + [721n, metadataObjToMap({ policyId1: { NFT1: { name: "line 3" } } })], + [1n, metadataObjToMap("line 4")] ]); - - const expectedOutput1 = createMetadataArray([ - { tag: "0", metadata: "line 1" }, - { tag: "0", metadata: "line 2" }, - { tag: "674", metadata: { msg: ["line 3"] } }, - { tag: "674", metadata: { msg: ["line 5"] } }, - { tag: "1", metadata: "line 7" }, - { tag: "721", metadata: { policyId: { NFT: { name: "NFT" }, NFT2: { name: "NFT 2" } } } }, + const label = 721n; + const metadata = metadataObjToMap({ policyId1: { NFT2: { name: "line 5" } } }); + const expectedOutput = new Map([ + [0n, metadataObjToMap("line 1")], + [674n, metadataObjToMap({ msg: "line 2" })], + [721n, metadataObjToMap({ policyId1: { NFT1: { name: "line 3" }, NFT2: { name: "line 5" } } })], + [1n, metadataObjToMap("line 4")] ]); - - expect(mergeAllMetadataByTag(input, "721", 2)).toEqual(expectedOutput1); - - const expectedOutput2 = createMetadataArray([ - { tag: "0", metadata: "line 1" }, - { tag: "0", metadata: "line 2" }, - { tag: "1", metadata: "line 7" }, - { tag: "721", metadata: { policyId: { NFT: { name: "NFT" }, NFT2: { name: "NFT 2" } } } }, - { tag: "674", metadata: { msg: ["line 5"] } }, - ]); - - expect(mergeAllMetadataByTag(expectedOutput1, "674", true)).toEqual(expectedOutput2); + setAndMergeTxMetadata(txMetadata, label, metadata, 2); + expect(txMetadata).toEqual(expectedOutput); }); });