diff --git a/examples/blockchain-data/.gitignore b/examples/blockchain-data/.gitignore new file mode 100644 index 0000000000..3635472a96 --- /dev/null +++ b/examples/blockchain-data/.gitignore @@ -0,0 +1,3 @@ +# yarn v2 +.yarn +.pnp.* diff --git a/examples/blockchain-data/README.md b/examples/blockchain-data/README.md index b5f8f3ea0b..0142344b0b 100644 --- a/examples/blockchain-data/README.md +++ b/examples/blockchain-data/README.md @@ -1 +1,23 @@ -# Coming Soon™ \ No newline at end of file +# Blockchain Data API Examples + +These examples demonstrate ways of implementing the methods exposed by the blockchainData package of `@imtbl/sdk`, in order to interact with Immutable's [Blockchain Data APIs](https://docs.immutable.com/products/zkEVM/blockchain-data). + +Immutable's Blockchain Data APIs index on-chain state changes and off-chain metadata to allow developers to efficiently retrieve information from Immutable's rollups. + +## Getting Started + +Pre-requisites: + +- Node.js 20.0.0 or higher + +Install dependencies: + +```bash +yarn install +``` + +Test the examples: + +```bash +yarn test +``` diff --git a/examples/blockchain-data/api-examples-with-node/exported-types.ts b/examples/blockchain-data/api-examples-with-node/exported-types.ts new file mode 100644 index 0000000000..8476b6646b --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/exported-types.ts @@ -0,0 +1,19 @@ +import { blockchainData, config } from '@imtbl/sdk'; + +const configuration: blockchainData.BlockchainDataModuleConfiguration = { + baseConfig: new config.ImmutableConfiguration({ + environment: config.Environment.PRODUCTION, + }), +}; + +const client = new blockchainData.BlockchainData(configuration); + +export async function getChains( + request: blockchainData.Types.ListChainsRequestParams, +): Promise { + const chains: blockchainData.Types.ListChainsResult = await client.listChains( + request, + ); + + return chains.result[0]; // type inference, autocomplete works here for `Chain` object +} diff --git a/examples/blockchain-data/api-examples-with-node/get-collection.ts b/examples/blockchain-data/api-examples-with-node/get-collection.ts new file mode 100644 index 0000000000..c61deb4fb0 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/get-collection.ts @@ -0,0 +1,12 @@ +import { blockchainData } from '@imtbl/sdk'; + +import { client } from '../lib'; + +export async function getCollection( + contractAddress: string, +): Promise { + return await client.getCollection({ + chainName: 'imtbl-zkevm-testnet', + contractAddress, + }); +}; diff --git a/examples/blockchain-data/api-examples-with-node/get-metadata.ts b/examples/blockchain-data/api-examples-with-node/get-metadata.ts new file mode 100644 index 0000000000..c24c8664aa --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/get-metadata.ts @@ -0,0 +1,14 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function getMetadata( + chainName: string, + contractAddress: string, + metadataId: string +): Promise { + return await client.getMetadata({ + chainName, + contractAddress, + metadataId, + }); +} diff --git a/examples/blockchain-data/api-examples-with-node/get-nft.ts b/examples/blockchain-data/api-examples-with-node/get-nft.ts new file mode 100644 index 0000000000..fabf924796 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/get-nft.ts @@ -0,0 +1,10 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function getNFT(chainName: string, contractAddress: string, tokenId: string): Promise { + return await client.getNFT({ + chainName: chainName, + contractAddress: contractAddress, + tokenId: tokenId, + }); +} diff --git a/examples/blockchain-data/api-examples-with-node/index.ts b/examples/blockchain-data/api-examples-with-node/index.ts new file mode 100644 index 0000000000..ee9f13e523 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/index.ts @@ -0,0 +1,27 @@ +import { verifySuccessfulMints } from "./verify-successful-mints"; +import { getChains } from "./exported-types"; +import { getCollection } from "./get-collection"; +import { getMetadata } from "./get-metadata"; +import { getNFT } from "./get-nft"; +import { listMetadata } from "./list-metadata"; +import { listCollections } from "./list-collections"; +import { listCollectionsByNFTOwner } from "./list-collections-by-owner"; +import { listActivities } from "./list-activities"; +import { listNFTsByAccountAddress } from "./list-nfts-by-account-address"; +import { refreshNFTMetadata } from "./refresh-nft-metadata"; +import { refreshStackedMetadata } from "./refresh-stacked-metadata"; + +export { + verifySuccessfulMints, + getChains, + getCollection, + getMetadata, + getNFT, + listMetadata, + listCollections, + listCollectionsByNFTOwner, + listActivities, + listNFTsByAccountAddress, + refreshNFTMetadata, + refreshStackedMetadata, +}; diff --git a/examples/blockchain-data/api-examples-with-node/list-activities.ts b/examples/blockchain-data/api-examples-with-node/list-activities.ts new file mode 100644 index 0000000000..05fcf99e0c --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/list-activities.ts @@ -0,0 +1,14 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function listActivities( + chainName: string, + contractAddress: string, + pageSize: number +): Promise { + return await client.listActivities({ + chainName, + contractAddress, + pageSize, + }); +} diff --git a/examples/blockchain-data/api-examples-with-node/list-collections-by-owner.ts b/examples/blockchain-data/api-examples-with-node/list-collections-by-owner.ts new file mode 100644 index 0000000000..45840514f7 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/list-collections-by-owner.ts @@ -0,0 +1,12 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function listCollectionsByNFTOwner( + chainName: string, + accountAddress: string +): Promise { + return await client.listCollectionsByNFTOwner({ + chainName, + accountAddress, + }); +} diff --git a/examples/blockchain-data/api-examples-with-node/list-collections.ts b/examples/blockchain-data/api-examples-with-node/list-collections.ts new file mode 100644 index 0000000000..c34aff6847 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/list-collections.ts @@ -0,0 +1,10 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function listCollections( + chainName: string +): Promise { + return await client.listCollections({ + chainName, + }); +} diff --git a/examples/blockchain-data/api-examples-with-node/list-metadata.ts b/examples/blockchain-data/api-examples-with-node/list-metadata.ts new file mode 100644 index 0000000000..04db2b4f09 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/list-metadata.ts @@ -0,0 +1,12 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function listMetadata( + chainName: string, + contractAddress: string +): Promise { + return await client.listNFTMetadataByContractAddress({ + chainName, + contractAddress, + }); +} diff --git a/examples/blockchain-data/api-examples-with-node/list-nfts-by-account-address.ts b/examples/blockchain-data/api-examples-with-node/list-nfts-by-account-address.ts new file mode 100644 index 0000000000..8c1989065a --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/list-nfts-by-account-address.ts @@ -0,0 +1,14 @@ +import { blockchainData } from '@imtbl/sdk'; +import { client } from '../lib'; + +export async function listNFTsByAccountAddress( + chainName: string, + contractAddress: string, + accountAddress: string, +): Promise { + return await client.listNFTsByAccountAddress({ + chainName, + contractAddress, + accountAddress, + }); +}; diff --git a/examples/blockchain-data/api-examples-with-node/refresh-nft-metadata.ts b/examples/blockchain-data/api-examples-with-node/refresh-nft-metadata.ts new file mode 100644 index 0000000000..607d2877b6 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/refresh-nft-metadata.ts @@ -0,0 +1,34 @@ +import { blockchainData } from "@imtbl/sdk"; +import { client } from "../lib"; + +export async function refreshNFTMetadata( + chainName: string, + contractAddress: string, + newName: string +): Promise { + const nftMetadata: blockchainData.Types.RefreshMetadataByTokenID[] = [ + { + name: newName, + animation_url: null, + image: null, + external_url: null, + youtube_url: null, + description: null, + attributes: [ + { + trait_type: 'Power', + value: 'Happy', + }, + ], + token_id: '1', + }, + ]; + + return await client.refreshNFTMetadata({ + chainName, + contractAddress, + refreshNFTMetadataByTokenIDRequest: { + nft_metadata: nftMetadata, + }, + }); +}; diff --git a/examples/blockchain-data/api-examples-with-node/refresh-stacked-metadata.ts b/examples/blockchain-data/api-examples-with-node/refresh-stacked-metadata.ts new file mode 100644 index 0000000000..17e869491d --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/refresh-stacked-metadata.ts @@ -0,0 +1,27 @@ +import { blockchainData } from '@imtbl/sdk'; +import { client } from '../lib'; + +export async function refreshStackedMetadata( + chainName: string, + contractAddress: string, + newName: string +): Promise { + return await client.refreshStackedMetadata({ + chainName, + contractAddress, + refreshMetadataByIDRequest: { + metadata: [ + { + name: newName, + animation_url: null, + image: null, + external_url: null, + youtube_url: null, + description: null, + attributes: [], + metadata_id: '1', + }, + ], + }, + }); +}; diff --git a/examples/blockchain-data/api-examples-with-node/setup-with-api-key.ts b/examples/blockchain-data/api-examples-with-node/setup-with-api-key.ts new file mode 100644 index 0000000000..af64f72065 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/setup-with-api-key.ts @@ -0,0 +1,12 @@ +import { config, blockchainData } from '@imtbl/sdk'; + +const API_KEY = 'YOUR_API_KEY'; +const PUBLISHABLE_KEY = 'YOUR_PUBLISHABLE_KEY'; + +const client = new blockchainData.BlockchainData({ + baseConfig: { + environment: config.Environment.PRODUCTION, + apiKey: API_KEY, + publishableKey: PUBLISHABLE_KEY, + }, +}); diff --git a/examples/blockchain-data/api-examples-with-node/setup.ts b/examples/blockchain-data/api-examples-with-node/setup.ts new file mode 100644 index 0000000000..3c8021a539 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/setup.ts @@ -0,0 +1,10 @@ +import { config, blockchainData } from '@imtbl/sdk'; + +const PUBLISHABLE_KEY = 'YOUR_PUBLISHABLE_KEY'; // Replace with your Publishable Key from the Immutable Hub + +const client = new blockchainData.BlockchainData({ + baseConfig: { + environment: config.Environment.PRODUCTION, + publishableKey: PUBLISHABLE_KEY, + }, +}); diff --git a/examples/blockchain-data/api-examples-with-node/verify-successful-mints.ts b/examples/blockchain-data/api-examples-with-node/verify-successful-mints.ts new file mode 100644 index 0000000000..af3de053b6 --- /dev/null +++ b/examples/blockchain-data/api-examples-with-node/verify-successful-mints.ts @@ -0,0 +1,12 @@ +import { blockchainData } from '@imtbl/sdk'; +import { client } from '../lib'; + +export async function verifySuccessfulMints( + contractAddress: string, +): Promise { + return await client.listActivities({ + chainName: 'imtbl-zkevm-testnet', + contractAddress, + activityType: blockchainData.Types.ActivityType.Mint, + }); +} diff --git a/examples/blockchain-data/jest.config.js b/examples/blockchain-data/jest.config.js new file mode 100644 index 0000000000..71183e4768 --- /dev/null +++ b/examples/blockchain-data/jest.config.js @@ -0,0 +1,8 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ +module.exports = { + testEnvironment: 'node', + transform: { + '^.+.tsx?$': ['ts-jest', {}], + }, + setupFiles: ['dotenv/config'], +}; diff --git a/examples/blockchain-data/lib/client.ts b/examples/blockchain-data/lib/client.ts new file mode 100644 index 0000000000..6b75dd799a --- /dev/null +++ b/examples/blockchain-data/lib/client.ts @@ -0,0 +1,11 @@ +import { config as immutableConfig, blockchainData } from '@imtbl/sdk'; + +export const config: blockchainData.BlockchainDataModuleConfiguration = { + baseConfig: { + environment: immutableConfig.Environment.SANDBOX, + apiKey: process.env.API_KEY, + publishableKey: process.env.PUBLISHABLE_KEY, + }, +}; + +export const client = new blockchainData.BlockchainData(config); diff --git a/examples/blockchain-data/lib/index.ts b/examples/blockchain-data/lib/index.ts new file mode 100644 index 0000000000..727b0d60d3 --- /dev/null +++ b/examples/blockchain-data/lib/index.ts @@ -0,0 +1,3 @@ +import { client } from './client'; + +export { client }; diff --git a/examples/blockchain-data/package.json b/examples/blockchain-data/package.json new file mode 100644 index 0000000000..2389debb2a --- /dev/null +++ b/examples/blockchain-data/package.json @@ -0,0 +1,21 @@ +{ + "name": "blockchain-data-api-examples", + "version": "1.0.0", + "description": "Code examples for interacting with the Blockchain Data API package of @imtbl/sdk", + "main": "index.js", + "scripts": { + "test": "jest" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@imtbl/sdk": "latest" + }, + "devDependencies": { + "@jest/globals": "^29.7.0", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "^5" + } +} diff --git a/examples/blockchain-data/test/blockchain-data.test.ts b/examples/blockchain-data/test/blockchain-data.test.ts new file mode 100644 index 0000000000..75975ad50f --- /dev/null +++ b/examples/blockchain-data/test/blockchain-data.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, test } from '@jest/globals'; + +import { + verifySuccessfulMints, + getChains, + getCollection, + getMetadata, + getNFT, + listMetadata, + listCollections, + listCollectionsByNFTOwner, + listActivities, + listNFTsByAccountAddress, +} from '../api-examples-with-node'; + +const CHAIN_NAME = 'imtbl-zkevm-testnet'; +const CONTRACT_ADDRESS = '0x21F0D60cfE554B6d5b7f9E799BDeBD97C5d64274'; +const NFT_OWNER = '0x9C1634bebC88653D2Aebf4c14a3031f62092b1D9'; + +describe('Activities', () => { + describe('listActivities', () => { + test('listing activities from a contract address returns activities', async () => { + const result = await listActivities(CHAIN_NAME, CONTRACT_ADDRESS, 10); + expect(result.result.length).toBeGreaterThan(0); + }); + }); + + describe('verifySuccessfulMints', () => { + test('listing activities from a contract address returns mint activities', async () => { + const result = await verifySuccessfulMints(CONTRACT_ADDRESS); + expect(result.result.length).toBeGreaterThan(0); + }); + }); +}); + +describe('Collections', () => { + describe('listCollections', () => { + test('returns a list of collections', async () => { + const result = await listCollections(CHAIN_NAME); + expect(result.result.length).toBeGreaterThan(0); + }); + }); + + describe('listCollectionsByNFTOwner', () => { + test('returns a list of collections', async () => { + const result = await listCollectionsByNFTOwner(CHAIN_NAME, NFT_OWNER); + expect(result.result.length).toBeGreaterThan(0); + }); + }); + + describe('getCollection', () => { + test('returns a collection', async () => { + const result = await getCollection(CONTRACT_ADDRESS); + expect(result.result).not.toBe(null); + }); + }); +}); + +describe('Metadata', () => { + describe('getMetadata', () => { + test('returns metadata', async () => { + const result = await getMetadata( + CHAIN_NAME, + CONTRACT_ADDRESS, + '018dc943-03b1-549d-6ddf-17935bae0c0e', + ); + expect(result.result).not.toBe(null); + }); + }); + describe('listMetadata', () => { + test('lists metadata', async () => { + const result = await listMetadata(CHAIN_NAME, CONTRACT_ADDRESS); + expect(result.result.length).toBeGreaterThan(0); + }); + }); +}); + +describe('NFTs', () => { + describe('listNFTsByAccountAddress', () => { + test('returns a list of NFTs', async () => { + const result = await listNFTsByAccountAddress( + CHAIN_NAME, + '0xd9cfd0a6d1496a4da6e8ad570344e1482ce3c257', + NFT_OWNER, + ); + expect(result.result.length).toBeGreaterThan(0); + }); + }); + + test('returns nft', async () => { + const result = await getNFT(CHAIN_NAME, CONTRACT_ADDRESS, '199144'); + expect(result.result).not.toBe(null); + }); +}); + +describe('Setup', () => { + describe('getChains', () => { + test('returns a chain', async () => { + const result = await getChains({}); + expect(result).not.toBe(null); + }); + }); +}); diff --git a/examples/blockchain-data/tsconfig.json b/examples/blockchain-data/tsconfig.json new file mode 100644 index 0000000000..7194b96a4e --- /dev/null +++ b/examples/blockchain-data/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2016", + "jsx": "preserve", + "module": "commonjs", + "noEmit": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/package.json b/package.json index 711983294e..f00a8770eb 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,8 @@ "tests/**", "examples/passport/**", "examples/orderbook/**", - "examples/checkout/**" + "examples/checkout/**", + "examples/blockchain-data/**" ], "nohoist": [ "examples/**",