diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bb7483808..daba2715d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [4.0.14] - 2023.10.10 +### Added +- Nft express minting over created collection for EVM chains. + ## [4.0.13] - 2023.10.09 ### Added - Added RPC support for the BITCOIN_CASH network. Users can now make RPC calls to these network using the `Network.BITCOIN_CASH` network. diff --git a/package.json b/package.json index 2b18eb9793..cae6dbcae7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tatumio/tatum", - "version": "4.0.13", + "version": "4.0.14", "description": "Tatum JS SDK", "author": "Tatum", "repository": "https://github.com/tatumio/tatum-js", diff --git a/src/e2e/tatum.nft.spec.ts b/src/e2e/tatum.nft.spec.ts index 3aa0b7a4be..745099045f 100644 --- a/src/e2e/tatum.nft.spec.ts +++ b/src/e2e/tatum.nft.spec.ts @@ -1,4 +1,5 @@ -import { Ethereum, Network, TatumSDK, Tezos } from '../service' +import { Ethereum, Network, Polygon, TatumSDK, Tezos } from '../service' +import { Utils } from '../util' describe('Tatum NFT', () => { let client: Ethereum @@ -245,3 +246,70 @@ describe('Tatum NFT - Tezos', () => { expect(result.data).toStrictEqual({ txId: expect.any(String) }) }) }) + +describe.skip('Tatum NFT - Test mint flow', () => { + it('Test', async () => { + const toAddress = '0xb361d67c8a573a510cea04c2fa22bc311dd6dc01' + const ownerAddress = '0x89144c7c7b4d44e9f99a465f58f47ae62f018a4c' + + const client = await TatumSDK.init({ + network: Network.POLYGON_MUMBAI, + }) + + const result = await client.nft.createNftCollection({ + name: 'Test contract', + symbol: 'MUMBAI_TEST', + owner: ownerAddress, + }) + console.log('deploy result', JSON.stringify(result)) + + const scAddress = await Utils.retryWithTimeout(() => { + return client.rpc.getContractAddress(result.data.txId) + }, 20000) + + console.log('sc address', scAddress) + + const nft1 = await client.nft.mintNft({ + to: toAddress, + url: 'ipfs://bafkreiallhajtd2j57lkuufjtabe6be2m3qh672nmnmkhvttv6d44cezaa', + contractAddress: scAddress ?? '', + tokenId: '1', + }) + console.log('nft txid 1', JSON.stringify(nft1)) + await Utils.retryWithTimeout(() => { + return client.rpc.getTransactionByHash(nft1.data.txId) + }, 20000) + + const nft2 = await Utils.retryWithTimeout(async () => { + const res = await client.nft.mintNft({ + to: toAddress, + url: 'ipfs://bafkreiallhajtd2j57lkuufjtabe6be2m3qh672nmnmkhvttv6d44cezaa', + contractAddress: scAddress ?? '', + tokenId: '2', + }) + return res?.data?.txId + }, 20000) + + console.log('nft txid 2', nft2) + await Utils.retryWithTimeout(() => { + return client.rpc.getTransactionByHash(nft2) + }, 20000) + + const nft3 = await Utils.retryWithTimeout(async () => { + const res = await client.nft.mintNft({ + to: toAddress, + url: 'ipfs://bafkreiallhajtd2j57lkuufjtabe6be2m3qh672nmnmkhvttv6d44cezaa', + contractAddress: scAddress ?? '', + tokenId: '3', + }) + return res?.data?.txId + }, 20000) + + console.log('nft txid 3', nft3) + await Utils.retryWithTimeout(() => { + return client.rpc.getTransactionByHash(nft3) + }, 20000) + + expect(nft3).toStrictEqual(expect.any(String)) + }) +}) diff --git a/src/service/nft/nft.dto.ts b/src/service/nft/nft.dto.ts index 926dcef3b5..f568102d57 100644 --- a/src/service/nft/nft.dto.ts +++ b/src/service/nft/nft.dto.ts @@ -40,6 +40,29 @@ export interface CreateNftEvmCollection extends CreateNftCollectionBase { baseURI?: string } +export interface MintNft { + /** + * Address to send NFT to + */ + to: string + /** + * The URL pointing to the NFT metadata; for more information, see EIP-721 + */ + url: string + /** + * Smart contract address of the NFT collection + */ + contractAddress: string + /** + * Token Id of NFT to be minted + */ + tokenId: string + /** + * Address of the NFT collection minter, this is optional and defaults to the owner address + */ + minter?: string +} + export interface MetadataResponse { url: string metadata: object diff --git a/src/service/nft/nft.ts b/src/service/nft/nft.ts index 43cf874424..ce00faa0a1 100644 --- a/src/service/nft/nft.ts +++ b/src/service/nft/nft.ts @@ -14,6 +14,7 @@ import { GetCollection, GetNftMetadata, GetTokenOwner, + MintNft, NftAddressBalance, NftTokenDetail, NftTransaction, @@ -111,6 +112,26 @@ export class Nft { }), ) } + + /** + * Mint new NFT (using ERC-721 compatible smart contract). This operation mints nft using smart contract on blockchain. + * You don't need to specify the default minter of the collection, as the owner of the collection is the default minter. + * You don't have to have any funds on the address, as the nft is minted by Tatum. + * @param body Body of the request. + * @returns ResponseDto<{txId: string}> Transaction ID of the mint transaction. { + */ + async mintNft(body: MintNft): Promise> { + return ErrorUtils.tryFail(() => + this.connector.post<{ txId: string }>({ + path: `contract/erc721/mint`, + body: { + ...body, + chain: this.config.network, + }, + }), + ) + } + /** * Get balance of NFT for given address. * You can get balance of multiple addresses in one call. diff --git a/src/util/util.shared.ts b/src/util/util.shared.ts index f3484ca872..3afb04eea7 100644 --- a/src/util/util.shared.ts +++ b/src/util/util.shared.ts @@ -279,6 +279,23 @@ export const Utils = { } }, delay: (t: number) => new Promise((resolve) => setTimeout(resolve, t)), + retryWithTimeout: async (action: () => Promise, timeoutInMs = 10000, delayInMs = 500): Promise => { + const startTime = Date.now() + let lastError: unknown = null + while (timeoutInMs + startTime > Date.now()) { + try { + const result: T = await action() + if (result === null || result === undefined) { + throw new Error('Null result') + } + return result + } catch (e: unknown) { + lastError = e + await Utils.delay(delayInMs) + } + } + throw lastError ?? new Error('Retry timeout failed') + }, fetchWithTimeout: async ( url: string, containerId: string,