Skip to content

Commit

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

* imports

* add standard

* defaults

* Add irc 30

* cleanup

* camelCase

* more cleanup

* changelog

* lints

* doc

* type annotations

* from_dict

* default dict

* default factory list too

* Add as_feature

* and to irc 30

* unused import
  • Loading branch information
DaughterOfMars authored Sep 20, 2023
1 parent b9dd984 commit 610dc50
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 43 deletions.
1 change: 1 addition & 0 deletions bindings/python/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

- `ConflictReason` display implementation with an explanation of the conflict;
- `Account::{burn(), consolidate_outputs(), create_alias_output(), create_native_token(), melt_native_token(), mint_native_token(), mint_nfts(), send_transaction(), send_native_tokens(), send_nft()}` methods;
- `Irc27Metadata` and `Irc30Metadata` helpers;
- `Client::output_ids()` method;
- `QueryParameter::unlockable_by_address` field;

Expand Down
11 changes: 9 additions & 2 deletions bindings/python/examples/client/10_mint_nft.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address,
MetadataFeature, MnemonicSecretManager, Utils,
utf8_to_hex)
utf8_to_hex, Irc27Metadata)

load_dotenv()

Expand All @@ -18,6 +18,13 @@

secret_manager = MnemonicSecretManager(os.environ['MNEMONIC'])

metadata = Irc27Metadata(
"video/mp4",
"https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT",
"Shimmer OG NFT",
description="The original Shimmer NFT"
)

nft_output = client.build_nft_output(
unlock_conditions=[
AddressUnlockCondition(
Expand All @@ -28,7 +35,7 @@
nft_id='0x0000000000000000000000000000000000000000000000000000000000000000',
amount=1000000,
immutable_features=[
MetadataFeature(utf8_to_hex('Hello, World!'))
metadata.as_feature()
],
features=[
MetadataFeature(utf8_to_hex('Hello, World!'))
Expand Down
32 changes: 18 additions & 14 deletions bindings/python/examples/client/build_nft.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@

from dotenv import load_dotenv

from iota_sdk import (AddressUnlockCondition, Client, Ed25519Address,
IssuerFeature, MetadataFeature, SenderFeature,
TagFeature, Utils, utf8_to_hex)
from iota_sdk import (
AddressUnlockCondition,
Client,
Ed25519Address,
IssuerFeature,
MetadataFeature,
SenderFeature,
TagFeature,
Utils,
utf8_to_hex,
Irc27Metadata,
)

load_dotenv()

Expand All @@ -17,15 +26,11 @@
hexAddress = Utils.bech32_to_hex(
'rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy')

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

# Build NFT output
nft_output = client.build_nft_output(
Expand All @@ -36,8 +41,7 @@
nft_id='0x0000000000000000000000000000000000000000000000000000000000000000',
immutable_features=[
IssuerFeature(Ed25519Address(hexAddress)),
MetadataFeature(utf8_to_hex(json.dumps(
tip_27_immutable_metadata, separators=(',', ':'))))
tip_27_immutable_metadata.as_feature()
],
features=[
SenderFeature(Ed25519Address(hexAddress)),
Expand Down
8 changes: 6 additions & 2 deletions bindings/python/examples/how_tos/native_tokens/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from dotenv import load_dotenv

from iota_sdk import CreateNativeTokenParams, Wallet, utf8_to_hex
from iota_sdk import CreateNativeTokenParams, Wallet, Irc30Metadata

load_dotenv()

Expand Down Expand Up @@ -38,10 +38,14 @@

print('Preparing transaction to create native token...')

metadata = Irc30Metadata(
"My Native Token", "MNT", 10, description="A native token to test the iota-sdk."
)

params = CreateNativeTokenParams(
100,
100,
utf8_to_hex('Hello, World!'),
metadata.as_hex(),
)

prepared_transaction = account.prepare_create_native_token(params, None)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import json
import os
import sys

from dotenv import load_dotenv

from iota_sdk import MintNftParams, Utils, Wallet, utf8_to_hex
from iota_sdk import MintNftParams, Utils, Wallet, Irc27Metadata

load_dotenv()

Expand Down Expand Up @@ -36,41 +35,43 @@
issuer = Utils.nft_id_to_bech32(issuer_nft_id, bech32_hrp)


def get_immutable_metadata(index: int, collection_id: str) -> str:
def get_immutable_metadata(index: int) -> str:
"""Returns the immutable metadata for the NFT with the given index"""
data = {
"standard": "IRC27",
"version": "v1.0",
"type": "video/mp4",
"uri": "ipfs://wrongcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5Ywrong",
"name": "Shimmer OG NFT #" + str(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": collection_id,
"collectionName": "Shimmer OG"
}
return json.dumps(data, separators=(',', ':'))
Irc27Metadata(
"video/mp4",
"https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT",
"Shimmer OG NFT #" + str(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",
collectionName="Shimmer OG",
).as_hex()


# Create the metadata with another index for each
nft_mint_params = list(map(lambda index: MintNftParams(
immutableMetadata=utf8_to_hex(
get_immutable_metadata(index, issuer_nft_id)),
issuer=issuer
), range(NFT_COLLECTION_SIZE)))
nft_mint_params = list(
map(
lambda index: MintNftParams(
immutableMetadata=get_immutable_metadata(index), issuer=issuer
),
range(NFT_COLLECTION_SIZE),
)
)

while nft_mint_params:
chunk, nft_mint_params = nft_mint_params[:NUM_NFTS_MINTED_PER_TRANSACTION], nft_mint_params[NUM_NFTS_MINTED_PER_TRANSACTION:]
chunk, nft_mint_params = (
nft_mint_params[:NUM_NFTS_MINTED_PER_TRANSACTION],
nft_mint_params[NUM_NFTS_MINTED_PER_TRANSACTION:],
)
print(
f'Minting {len(chunk)} NFTs... ({NFT_COLLECTION_SIZE-len(nft_mint_params)}/{NFT_COLLECTION_SIZE})')
f'Minting {len(chunk)} NFTs... ({NFT_COLLECTION_SIZE-len(nft_mint_params)}/{NFT_COLLECTION_SIZE})'
)
transaction = account.mint_nfts(chunk)

# Wait for transaction to get included
block_id = account.retry_transaction_until_included(
transaction.transactionId)

print(
f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}')
print(f'Block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}')

# Sync so the new outputs are available again for new transactions
account.sync()
2 changes: 2 additions & 0 deletions bindings/python/iota_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from .types.common import *
from .types.event import *
from .types.feature import *
from .types.irc_27 import *
from .types.irc_30 import *
from .types.filter_options import *
from .types.input import *
from .types.native_token import *
Expand Down
66 changes: 66 additions & 0 deletions bindings/python/iota_sdk/types/irc_27.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

import json
from iota_sdk import utf8_to_hex, MetadataFeature
from dataclasses import dataclass, field
from typing import Optional, List, Any
from dacite import from_dict


@dataclass
class Attribute:
"""An attribute which follows [OpenSea Metadata standards](https://docs.opensea.io/docs/metadata-standards).
Attributes:
trait_type: The trait type.
value: The value of the specified Attribute.
display_type: The optional type used to display the Attribute.
"""

trait_type: str
value: Any
display_type: Optional[str] = None


@dataclass
class Irc27Metadata:
"""The IRC27 NFT standard schema.
Attributes:
standard: The metadata standard (IRC27).
version: The metadata spec version (v1.0).
type: 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.
uri: URL pointing to the NFT file location.
name: The human-readable name of the native token.
collectionName: The human-readable collection name of the native token.
royalties: Royalty payment addresses mapped to the payout percentage.
issuerName: The human-readable name of the native token creator.
description: The human-readable description of the token.
attributes: Additional attributes which follow [OpenSea Metadata standards](https://docs.opensea.io/docs/metadata-standards).
"""

standard: str = field(default="IRC27", init=False)
version: str = field(default="v1.0", init=False)
type: str
uri: str
name: str
collectionName: Optional[str] = None
royalties: dict[str, float] = field(default_factory=dict)
issuerName: Optional[str] = None
description: Optional[str] = None
attributes: List[Attribute] = field(default_factory=list)

@staticmethod
def from_dict(metadata_dict: dict):
return from_dict(Irc27Metadata, metadata_dict)

def as_hex(self):
utf8_to_hex(json.dumps(self.as_dict(), separators=(",", ":")))

def as_feature(self):
MetadataFeature(self.as_hex())
43 changes: 43 additions & 0 deletions bindings/python/iota_sdk/types/irc_30.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

import json
from iota_sdk.types.common import HexStr
from iota_sdk import utf8_to_hex, MetadataFeature
from dataclasses import dataclass, field
from typing import Optional
from dacite import from_dict


@dataclass
class Irc30Metadata:
"""The IRC30 native token metadata standard schema.
Attributes:
standard: The metadata standard (IRC30).
name: The human-readable name of the native token.
symbol: The symbol/ticker of the token.
decimals: Number of decimals the token uses (divide the token amount by 10^decimals to get its user representation).
description: The human-readable description of the token.
url: URL pointing to more resources about the token.
logoUrl: URL pointing to an image resource of the token logo.
logo: The svg logo of the token encoded as a byte string.
"""

standard: str = field(default="IRC30", init=False)
name: str
symbol: str
decimals: int
description: Optional[str] = None
url: Optional[str] = None
logoUrl: Optional[str] = None
logo: Optional[HexStr] = None

@staticmethod
def from_dict(metadata_dict: dict):
return from_dict(Irc30Metadata, metadata_dict)

def as_hex(self):
utf8_to_hex(json.dumps(self.as_dict(), separators=(",", ":")))

def as_feature(self):
MetadataFeature(self.as_hex())
48 changes: 47 additions & 1 deletion bindings/python/tests/test_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

from iota_sdk import Block, Client, MnemonicSecretManager, Utils, SecretManager, OutputId, hex_to_utf8, utf8_to_hex, Bip44, CoinType
from iota_sdk import Block, Client, MnemonicSecretManager, Utils, SecretManager, OutputId, hex_to_utf8, utf8_to_hex, Bip44, CoinType, Irc27Metadata, Irc30Metadata
import json
import unittest

Expand Down Expand Up @@ -102,3 +102,49 @@ def test_block():
"0xd76cdb7acf228ecdad590a42b91acc077c1518c1a271411229e33e050fc19b44", "0xecef38d3af7e63da78a5e70128efe371f2191088b31879f7b0e81da657fa21c6"], "payload": {"type": 5, "tag": "0x68656c6c6f", "data": "0x68656c6c6f"}, "nonce": "6917529027641139843"}
block = Block.from_dict(block_dict)
assert block.id() == "0x7ce5ad074d4162e57f83cfa01cd2303ef5356567027ce0bcee0c9f57bc11656e"


def test_irc_27():
metadata = Irc27Metadata(
"video/mp4",
"https://ipfs.io/ipfs/QmPoYcVm9fx47YXNTkhpMEYSxCD3Bqh7PJYr7eo5YjLgiT",
"Shimmer OG NFT",
description="The original Shimmer NFT"
)
metadata_dict = {
"standard": "IRC27",
"version": metadata.version,
"type": metadata.type,
"uri": metadata.uri,
"name": metadata.name,
"collectionName": metadata.collectionName,
"royalties": metadata.royalties,
"issuerName": metadata.issuerName,
"description": metadata.description,
"attributes": metadata.attributes
}
metadata_deser = Irc27Metadata.from_dict(metadata_dict)
assert metadata == metadata_deser


def test_irc_30():
metadata = Irc30Metadata(
"FooCoin",
"FOO",
3,
description="FooCoin is the utility and governance token of FooLand, \
a revolutionary protocol in the play-to-earn crypto gaming field.",
url="https://foocoin.io/",
logoUrl="https://ipfs.io/ipfs/QmR36VFfo1hH2RAwVs4zVJ5btkopGip5cW7ydY4jUQBrkR"
)
metadata_dict = {
"standard": "IRC30",
"name": metadata.name,
"description": metadata.description,
"decimals": metadata.decimals,
"symbol": metadata.symbol,
"url": metadata.url,
"logoUrl": metadata.logoUrl
}
metadata_deser = Irc30Metadata.from_dict(metadata_dict)
assert metadata == metadata_deser

0 comments on commit 610dc50

Please sign in to comment.