Skip to content

Commit

Permalink
Nodejs - Add Irc27Metadata and Irc30Metadata (#1261)
Browse files Browse the repository at this point in the history
* Add irc 27 metadata

* Add examples

* Add irc 30

* array literal

* doc

Co-authored-by: Thoralf-M <[email protected]>

* doc again

* format

* changelog

* Add asFeature helpers

* Update bindings/nodejs/lib/types/block/output/irc-27.ts

---------

Co-authored-by: Thoralf-M <[email protected]>
Co-authored-by: Thibault Martinez <[email protected]>
Co-authored-by: Abdulrahim Al Methiab <[email protected]>
  • Loading branch information
4 people authored Sep 25, 2023
1 parent 5b1867e commit ec617be
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 37 deletions.
1 change: 1 addition & 0 deletions bindings/nodejs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Account::{burn(), consolidateOutputs(), createAliasOutput(), meltNativeToken(), mintNativeToken(), createNativeToken(), mintNfts(), sendTransaction(), sendNativeTokens(), sendNft()}` methods;
- `Client::outputIds()` method;
- `GenericQueryParameter, UnlockableByAddress` types;
- `Irc27Metadata` and `Irc30Metadata` helpers;
- `Utils::outputHexBytes`;

## 1.0.11 - 2023-09-14
Expand Down
18 changes: 7 additions & 11 deletions bindings/nodejs/examples/client/15-build-nft-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SenderFeature,
Ed25519Address,
IssuerFeature,
Irc27Metadata,
} from '@iota/sdk';
require('dotenv').config({ path: '.env' });

Expand All @@ -35,14 +36,11 @@ async function run() {
'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy',
);

// IOTA NFT Standard - IRC27: https://github.com/iotaledger/tips/blob/main/tips/TIP-0027/tip-0027.md
const tip27ImmutableMetadata = {
standard: 'IRC27',
version: 'v1.0',
type: 'image/jpeg',
uri: 'https://mywebsite.com/my-nft-files-1.jpeg',
name: 'My NFT #0001',
};
const tip27ImmutableMetadata = new Irc27Metadata(
'image/jpeg',
'https://mywebsite.com/my-nft-files-1.jpeg',
'My NFT #0001',
);

const nftOutput = await client.buildNftOutput({
// NftId needs to be null the first time
Expand All @@ -52,9 +50,7 @@ async function run() {
],
immutableFeatures: [
new IssuerFeature(new Ed25519Address(hexAddress)),
new MetadataFeature(
utf8ToHex(JSON.stringify(tip27ImmutableMetadata)),
),
tip27ImmutableMetadata.asFeature(),
],
features: [
new SenderFeature(new Ed25519Address(hexAddress)),
Expand Down
10 changes: 8 additions & 2 deletions bindings/nodejs/examples/how_tos/native_tokens/create.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { CreateNativeTokenParams, utf8ToHex } from '@iota/sdk';
import { CreateNativeTokenParams, Irc30Metadata } from '@iota/sdk';

import { getUnlockedWallet } from '../../wallet/common';

Expand Down Expand Up @@ -51,11 +51,17 @@ async function run() {

console.log('Preparing transaction to create native token...');

const metadata = new Irc30Metadata(
'My Native Token',
'MNT',
10,
).withDescription('A native token to test the iota-sdk.');

// If we omit the AccountAddress field the first address of the account is used by default
const params: CreateNativeTokenParams = {
circulatingSupply: CIRCULATING_SUPPLY,
maximumSupply: MAXIMUM_SUPPLY,
foundryMetadata: utf8ToHex('Hello, World!'),
foundryMetadata: metadata.asHex(),
};

const prepared = await account.prepareCreateNativeToken(params);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { MintNftParams, NftId, utf8ToHex, Utils, Wallet } from '@iota/sdk';
import { MintNftParams, NftId, Utils, Wallet, Irc27Metadata } from '@iota/sdk';
require('dotenv').config({ path: '.env' });

// The NFT collection size
Expand Down Expand Up @@ -48,9 +48,7 @@ async function run() {
// Create the metadata with another index for each
for (let index = 0; index < NFT_COLLECTION_SIZE; index++) {
const params: MintNftParams = {
immutableMetadata: utf8ToHex(
getImmutableMetadata(index, issuerNftId),
),
immutableMetadata: getImmutableMetadata(index).asHex(),
// The NFT address from the NFT we minted in mint_issuer_nft example
issuer,
};
Expand Down Expand Up @@ -97,21 +95,18 @@ async function run() {
process.exit(0);
}

function getImmutableMetadata(index: number, issuerNftId: NftId) {
// Note: we use parse and stringify to remove all unnecessary whitespace
return JSON.stringify(
JSON.parse(`{
"standard":"IRC27",
"version":"v1.0",
"type":"video/mp4",
"uri":"ipfs://wrongcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5Ywrong",
"name":"Shimmer OG NFT ${index}",
"description":"The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation to celebrate the official launch of the Shimmer Network.",
"issuerName":"IOTA Foundation",
"collectionId":"${issuerNftId}",
"collectionName":"Shimmer OG"
}`),
);
function getImmutableMetadata(index: number) {
return new Irc27Metadata(
'video/mp4',
'https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT',
`Shimmer OG NFT ${index}`,
)
.withDescription(
'The Shimmer OG NFT was handed out 1337 times by the IOTA Foundation \
to celebrate the official launch of the Shimmer Network.',
)
.withIssuerName('IOTA Foundation')
.withCollectionName('Shimmer OG');
}

run();
11 changes: 8 additions & 3 deletions bindings/nodejs/examples/how_tos/nfts/mint_nft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
utf8ToHex,
Utils,
Wallet,
Irc27Metadata,
} from '@iota/sdk';
require('dotenv').config({ path: '.env' });

Expand All @@ -18,8 +19,6 @@ const NFT1_OWNER_ADDRESS =
'rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu';
// The metadata of the first minted NFT
const NFT1_METADATA = utf8ToHex('some NFT metadata');
// The immutable metadata of the first minted NFT
const NFT1_IMMUTABLE_METADATA = utf8ToHex('some NFT immutable metadata');
// The tag of the first minted NFT
const NFT1_TAG = utf8ToHex('some NFT tag');
// The base coin amount we sent with the second NFT
Expand Down Expand Up @@ -52,13 +51,19 @@ async function run() {
// We need to unlock stronghold.
await wallet.setStrongholdPassword(process.env.STRONGHOLD_PASSWORD);

const metadata = new Irc27Metadata(
'video/mp4',
'https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT',
'Shimmer OG NFT',
).withDescription('The original Shimmer NFT');

const params: MintNftParams = {
address: NFT1_OWNER_ADDRESS, // Remove or change to senderAddress to send to self
sender: senderAddress,
metadata: NFT1_METADATA,
tag: NFT1_TAG,
issuer: senderAddress,
immutableMetadata: NFT1_IMMUTABLE_METADATA,
immutableMetadata: metadata.asHex(),
};
let transaction = await account.mintNfts([params]);
console.log(`Transaction sent: ${transaction.transactionId}`);
Expand Down
2 changes: 2 additions & 0 deletions bindings/nodejs/lib/types/block/output/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * from './feature';
export * from './unlock-condition';
export * from './output';
export * from './token-scheme';
export * from './irc-27';
export * from './irc-30';
111 changes: 111 additions & 0 deletions bindings/nodejs/lib/types/block/output/irc-27.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { utf8ToHex } from '../../../utils';
import { MetadataFeature } from './feature';

/**
* The IRC27 NFT standard schema.
*/
class Irc27Metadata {
/** The IRC standard */
readonly standard: string = 'IRC27';
/** The current version. */
readonly version: string = 'v1.0';
/** The media type (MIME) of the asset.
*
* ## Examples
* - Image files: `image/jpeg`, `image/png`, `image/gif`, etc.
* - Video files: `video/x-msvideo` (avi), `video/mp4`, `video/mpeg`, etc.
* - Audio files: `audio/mpeg`, `audio/wav`, etc.
* - 3D Assets: `model/obj`, `model/u3d`, etc.
* - Documents: `application/pdf`, `text/plain`, etc.
*/
type: string;
/** URL pointing to the NFT file location. */
uri: string;
/** The human-readable name of the native token. */
name: string;
/** The human-readable collection name of the native token. */
collectionName?: string;
/** Royalty payment addresses mapped to the payout percentage. */
royalties: Map<string, number> = new Map();
/** The human-readable name of the native token creator. */
issuerName?: string;
/** The human-readable description of the token. */
description?: string;
/** Additional attributes which follow [OpenSea Metadata standards](https://docs.opensea.io/docs/metadata-standards). */
attributes: Attribute[] = [];

/**
* @param type The media type (MIME) of the asset.
* @param uri URL pointing to the NFT file location.
* @param name The human-readable name of the native token.
*/
constructor(type: string, uri: string, name: string) {
this.type = type;
this.uri = uri;
this.name = name;
}

withCollectionName(collectionName: string): Irc27Metadata {
this.collectionName = collectionName;
return this;
}

addRoyalty(address: string, percentage: number): Irc27Metadata {
this.royalties.set(address, percentage);
return this;
}

withRoyalties(royalties: Map<string, number>): Irc27Metadata {
this.royalties = royalties;
return this;
}

withIssuerName(issuerName: string): Irc27Metadata {
this.issuerName = issuerName;
return this;
}

withDescription(description: string): Irc27Metadata {
this.description = description;
return this;
}

addAttribute(attribute: Attribute): Irc27Metadata {
this.attributes.push(attribute);
return this;
}

withAttributes(attributes: Attribute[]): Irc27Metadata {
this.attributes = attributes;
return this;
}

asHex(): string {
return utf8ToHex(JSON.stringify(this));
}

asFeature(): MetadataFeature {
return new MetadataFeature(this.asHex());
}
}

class Attribute {
trait_type: string;
value: any;
display_type?: string;

constructor(trait_type: string, value: any) {
this.trait_type = trait_type;
this.value = value;
}

withDisplayType(display_type: string): Attribute {
this.display_type = display_type;
return this;
}
}

export { Irc27Metadata, Attribute };
68 changes: 68 additions & 0 deletions bindings/nodejs/lib/types/block/output/irc-30.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { utf8ToHex } from '../../../utils';
import { MetadataFeature } from './feature';

/**
* The IRC30 native token metadata standard schema.
*/
class Irc30Metadata {
/** The IRC standard */
readonly standard: string = 'IRC30';
/** The human-readable name of the native token. */
name: string;
/** The symbol/ticker of the token. */
symbol: string;
/** Number of decimals the token uses (divide the token amount by `10^decimals` to get its user representation). */
decimals: number;
/** The human-readable description of the token. */
description?: string;
/** URL pointing to more resources about the token. */
url?: string;
/** URL pointing to an image resource of the token logo. */
logoUrl?: string;
/** The svg logo of the token encoded as a byte string. */
logo?: string;

/**
* @param name The human-readable name of the native token.
* @param symbol The symbol/ticker of the token.
* @param decimals Number of decimals the token uses.
*/
constructor(name: string, symbol: string, decimals: number) {
this.name = name;
this.symbol = symbol;
this.decimals = decimals;
}

withDescription(description: string): Irc30Metadata {
this.description = description;
return this;
}

withUrl(url: string): Irc30Metadata {
this.url = url;
return this;
}

withLogoUrl(logoUrl: string): Irc30Metadata {
this.logoUrl = logoUrl;
return this;
}

withLogo(logo: string): Irc30Metadata {
this.logo = logo;
return this;
}

asHex(): string {
return utf8ToHex(JSON.stringify(this));
}

asFeature(): MetadataFeature {
return new MetadataFeature(this.asHex());
}
}

export { Irc30Metadata };
5 changes: 3 additions & 2 deletions sdk/src/types/block/output/feature/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ pub(crate) mod irc_30 {

use super::*;

/// The IRC30 NFT standard schema.
/// The IRC30 native token metadata standard schema.
#[derive(Clone, Debug, Serialize, Deserialize, Getters, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "standard", rename = "IRC30")]
Expand All @@ -322,7 +322,8 @@ pub(crate) mod irc_30 {
name: String,
/// The symbol/ticker of the token.
symbol: String,
/// Number of decimals the token uses (divide the token amount by 10^decimals to get its user representation).
/// Number of decimals the token uses (divide the token amount by `10^decimals` to get its user
/// representation).
decimals: u32,
/// The human-readable description of the token.
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down

0 comments on commit ec617be

Please sign in to comment.