From 920f54f803605cc4a08c5cc164361be126b3d4d4 Mon Sep 17 00:00:00 2001 From: YanYuan Date: Wed, 2 Jul 2025 14:56:38 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20ContractClientBas?= =?UTF-8?q?e=20and=20Anchor=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new ContractClientBase abstract class for SVM contracts - Include Anchor as a new dependency in package.json - Update pnpm-lock.yaml with Anchor package details --- packages/svm/package.json | 3 +- .../svm/src/contracts/ContractClientBase.ts | 126 ++++++++++++++++++ pnpm-lock.yaml | 121 +++++++++++++++++ 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 packages/svm/src/contracts/ContractClientBase.ts diff --git a/packages/svm/package.json b/packages/svm/package.json index a19072c..565ba81 100644 --- a/packages/svm/package.json +++ b/packages/svm/package.json @@ -37,7 +37,8 @@ "@metaplex-foundation/mpl-token-metadata": "^3.4.0", "@metaplex-foundation/umi": "^1.2.0", "@metaplex-foundation/umi-bundle-defaults": "^1.2.0", - "@solana/spl-token-metadata": "^0.1.6" + "@solana/spl-token-metadata": "^0.1.6", + "@coral-xyz/anchor": "^0.30.1" }, "devDependencies": { "@solana/spl-token": "^0.4.12", diff --git a/packages/svm/src/contracts/ContractClientBase.ts b/packages/svm/src/contracts/ContractClientBase.ts new file mode 100644 index 0000000..1c638dc --- /dev/null +++ b/packages/svm/src/contracts/ContractClientBase.ts @@ -0,0 +1,126 @@ +import { Program, AnchorProvider, setProvider, Wallet } from '@coral-xyz/anchor'; +import type { Idl } from '@coral-xyz/anchor'; +import type { + AllAccountsMap, + AllInstructions, + IdlAccounts, + MakeMethodsNamespace, +} from '@coral-xyz/anchor/dist/cjs/program/namespace/types'; +import { clusterApiUrl, Connection, PublicKey } from '@solana/web3.js'; +import type { Cluster, Commitment, Signer } from '@solana/web3.js'; + +export interface ContractClientBaseOptions { + chainId?: string; + idl?: any; + walletClient: Wallet; + endpoint: string; +} + +export abstract class ContractClientBase { + protected program: Program; + protected connection: Connection; + protected wallet: Wallet; + protected chainId: string; + endpoint: string; + + constructor(contractInfo: ContractClientBaseOptions) { + const { idl, walletClient, chainId, endpoint } = contractInfo; + this.chainId = chainId || 'mainnet-beta'; + + this.connection = this.getConnection(); + this.wallet = walletClient; + this.endpoint = endpoint; + + const provider = new AnchorProvider(this.connection, this.wallet, { commitment: 'confirmed' }); + setProvider(provider); + + this.program = new Program(idl, provider); + } + + get programId() { + return this.program.programId; + } + + protected getConnection() { + const rpcUrl = this.endpoint ? this.endpoint : clusterApiUrl((this.chainId as Cluster) ?? 'mainnet-beta'); + + const conn = new Connection(rpcUrl); + return conn; + } + + protected async findProgramAddress( + seeds: Array, + programId?: PublicKey, + ): Promise<[PublicKey, number]> { + return await PublicKey.findProgramAddressSync(seeds, programId || new PublicKey(this.program.idl.address)); + } + + async fetchAccountData>( + accountName: A, + address: PublicKey | string, + commitment?: Commitment, + ): Promise[A]> { + try { + if (!this.program.account[accountName]) { + throw new Error(`Account ${String(accountName)} not found in program`); + } + + const account = await this.program.account[accountName].fetch(address, commitment); + return account as IdlAccounts[A]; + } catch (error) { + throw error; + } + } + + async invokeView>>( + methodName: M, + args: Parameters>[M]>, + ): Promise { + try { + if (!this.program.methods[methodName]) { + throw new Error(`Method ${methodName} not found in program`); + } + + const result = await this.program.methods[methodName](...args).view(); + + return result; + } catch (error) { + throw error; + } + } + + async invokeInstruction>>( + methodName: M, + accounts: any, + args: any, + options?: { signers?: Signer[] }, + ): Promise { + if (!this.program.methods[methodName]) { + throw new Error(`Method ${methodName} not found in program`); + } + let instruction = this.program.methods[methodName](...args).accounts(accounts); + + if (options?.signers?.length) { + instruction = instruction.signers(options.signers); + } + + const tx = await instruction.rpc(); + + await this.confirmTransaction(tx); + + return tx; + } + + async confirmTransaction(tx: string): Promise { + const latestBlockhash = await this.connection.getLatestBlockhash(); + await this.connection.confirmTransaction( + { + signature: tx, + blockhash: latestBlockhash.blockhash, + lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, + }, + 'finalized', + ); + return tx; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 225ade9..0256cb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ importers: '@bizjs/chainkit-utils': specifier: workspace:* version: link:../utils + '@coral-xyz/anchor': + specifier: ^0.30.1 + version: 0.30.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10) '@metaplex-foundation/mpl-token-metadata': specifier: ^3.4.0 version: 3.4.0(@metaplex-foundation/umi@1.2.0) @@ -230,6 +233,20 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@coral-xyz/anchor-errors@0.30.1': + resolution: {integrity: sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ==} + engines: {node: '>=10'} + + '@coral-xyz/anchor@0.30.1': + resolution: {integrity: sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ==} + engines: {node: '>=11'} + + '@coral-xyz/borsh@0.30.1': + resolution: {integrity: sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==} + engines: {node: '>=10'} + peerDependencies: + '@solana/web3.js': ^1.68.0 + '@emurgo/cardano-message-signing-nodejs@1.1.0': resolution: {integrity: sha512-PQRc8K8wZshEdmQenNUzVtiI8oJNF/1uAnBhidee5C4o1l2mDLOW+ur46HWHIFKQ6x8mSJTllcjMscHgzju0gQ==} @@ -1167,6 +1184,10 @@ packages: buffer-compare@1.1.1: resolution: {integrity: sha512-O6NvNiHZMd3mlIeMDjP6t/gPG75OqGPeiRZXoMQZJ6iy9GofCls4Ijs5YkPZZwoysizLiedhticmdyx/GyHghA==} + buffer-layout@1.2.2: + resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==} + engines: {node: '>=4.5'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -1207,6 +1228,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + cardinal@2.1.1: resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} hasBin: true @@ -1284,10 +1309,17 @@ packages: create-hash@1.2.0: resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crypto-hash@1.3.0: + resolution: {integrity: sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==} + engines: {node: '>=8'} + dataloader@2.2.3: resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} @@ -1339,6 +1371,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -1414,6 +1449,9 @@ packages: ethereum-cryptography@2.2.1: resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -1754,6 +1792,9 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -1827,6 +1868,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} @@ -2133,6 +2177,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2184,6 +2231,9 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + superstruct@0.15.5: + resolution: {integrity: sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==} + superstruct@2.0.2: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} @@ -2249,6 +2299,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + tough-cookie@4.1.4: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} @@ -2711,6 +2764,37 @@ snapshots: human-id: 4.1.1 prettier: 2.8.8 + '@coral-xyz/anchor-errors@0.30.1': {} + + '@coral-xyz/anchor@0.30.1(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)': + dependencies: + '@coral-xyz/anchor-errors': 0.30.1 + '@coral-xyz/borsh': 0.30.1(@solana/web3.js@1.98.2(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10)) + '@noble/hashes': 1.8.0 + '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + bs58: 4.0.1 + buffer-layout: 1.2.2 + camelcase: 6.3.0 + cross-fetch: 3.2.0 + crypto-hash: 1.3.0 + eventemitter3: 4.0.7 + pako: 2.1.0 + snake-case: 3.0.4 + superstruct: 0.15.5 + toml: 3.0.0 + transitivePeerDependencies: + - bufferutil + - encoding + - typescript + - utf-8-validate + + '@coral-xyz/borsh@0.30.1(@solana/web3.js@1.98.2(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10))': + dependencies: + '@solana/web3.js': 1.98.2(bufferutil@4.0.9)(typescript@5.7.3)(utf-8-validate@5.0.10) + bn.js: 5.2.1 + buffer-layout: 1.2.2 + '@emurgo/cardano-message-signing-nodejs@1.1.0': {} '@emurgo/cardano-serialization-lib-nodejs@13.2.1': {} @@ -3729,6 +3813,8 @@ snapshots: buffer-compare@1.1.1: {} + buffer-layout@1.2.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -3780,6 +3866,8 @@ snapshots: call-bind-apply-helpers: 1.0.1 get-intrinsic: 1.2.7 + camelcase@6.3.0: {} + cardinal@2.1.1: dependencies: ansicolors: 0.3.2 @@ -3854,12 +3942,20 @@ snapshots: ripemd160: 2.0.2 sha.js: 2.4.11 + cross-fetch@3.2.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + crypto-hash@1.3.0: {} + dataloader@2.2.3: {} debug@4.4.0: @@ -3898,6 +3994,11 @@ snapshots: dependencies: path-type: 4.0.0 + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.1 @@ -4007,6 +4108,8 @@ snapshots: '@scure/bip32': 1.4.0 '@scure/bip39': 1.3.0 + eventemitter3@4.0.7: {} + eventemitter3@5.0.1: {} expect-type@1.2.1: {} @@ -4370,6 +4473,10 @@ snapshots: loupe@3.1.3: {} + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} @@ -4427,6 +4534,11 @@ snapshots: nanoid@3.3.11: {} + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + node-addon-api@5.1.0: {} node-fetch@2.7.0: @@ -4730,6 +4842,11 @@ snapshots: slash@3.0.0: {} + snake-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + source-map-js@1.2.1: {} source-map@0.8.0-beta.0: @@ -4799,6 +4916,8 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 + superstruct@0.15.5: {} + superstruct@2.0.2: {} symbol.inspect@1.0.1: {} @@ -4850,6 +4969,8 @@ snapshots: dependencies: is-number: 7.0.0 + toml@3.0.0: {} + tough-cookie@4.1.4: dependencies: psl: 1.15.0 From 1ab3dae8ff92ae98e920692edc94b8a31423e8d7 Mon Sep 17 00:00:00 2001 From: YanYuan Date: Wed, 2 Jul 2025 16:03:09 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20ERC721=20contract?= =?UTF-8?q?=20client=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add new ERC721ContractClient class implementation - Include ERC721TokenMetadata type definition - Export ERC721ContractClient and related types from package entrypoint - Add test cases for ERC721 contract functionality --- .../contracts/erc721/ERC721ContractClient.ts | 90 +++++++++++++++++++ packages/evm/src/contracts/types.ts | 6 ++ packages/evm/src/index.ts | 3 +- packages/evm/tests/contants.test.ts | 27 +++++- 4 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 packages/evm/src/contracts/erc721/ERC721ContractClient.ts diff --git a/packages/evm/src/contracts/erc721/ERC721ContractClient.ts b/packages/evm/src/contracts/erc721/ERC721ContractClient.ts new file mode 100644 index 0000000..e44bb6b --- /dev/null +++ b/packages/evm/src/contracts/erc721/ERC721ContractClient.ts @@ -0,0 +1,90 @@ +import { erc721Abi, type Address, type Hash } from 'viem'; +import { ContractClientBase, type ContractClientBaseOptions } from '../ContractClientBase'; +import type { ERC721TokenMetadata } from '../types'; + +export type ERC721ContractClientOptions = Omit; + +export class ERC721ContractClient extends ContractClientBase { + constructor(options: ERC721ContractClientOptions) { + super({ ...options, abi: erc721Abi }); + } + + async name() { + const result = await this.readContract({ functionName: 'name' }); + return result as string; + } + + async symbol() { + const result = await this.readContract({ functionName: 'symbol' }); + return result as string; + } + + async totalSupply() { + const result = await this.readContract({ functionName: 'totalSupply' }); + return result as bigint; + } + + async balanceOf(owner: Address) { + const result = await this.readContract({ functionName: 'balanceOf', args: [owner] }); + return result as bigint; + } + + async ownerOf(tokenId: bigint) { + const result = await this.readContract({ functionName: 'ownerOf', args: [tokenId] }); + return result as Address; + } + + async approve(to: Address, tokenId: bigint) { + const result = await this.simulateAndWriteContract({ functionName: 'approve', args: [to, tokenId] }); + return result as Hash; + } + + async setApprovalForAll(operator: Address, approved: boolean) { + const result = await this.simulateAndWriteContract({ + functionName: 'setApprovalForAll', + args: [operator, approved], + }); + return result as Hash; + } + + async transferFrom(from: Address, to: Address, tokenId: bigint) { + const result = await this.simulateAndWriteContract({ + functionName: 'transferFrom', + args: [from, to, tokenId], + }); + return result as Hash; + } + + async safeTransferFrom(from: Address, to: Address, tokenId: bigint, data?: `0x${string}`) { + const args = data ? [from, to, tokenId, data] : [from, to, tokenId]; + const result = await this.simulateAndWriteContract({ + functionName: 'safeTransferFrom', + args, + }); + return result as Hash; + } + + async tokenURI(tokenId: bigint) { + const result = await this.readContract({ functionName: 'tokenURI', args: [tokenId] }); + return result as string; + } + + async tokenMetadata() { + const baseParams = { address: this.contractAddress, abi: this.abi }; + const multicallFunctions: { functionName: string; args?: unknown[] }[] = [ + { functionName: 'name' }, + { functionName: 'symbol' }, + { functionName: 'totalSupply' }, + ]; + const [name, symbol, totalSupply] = await this.multicall({ + contracts: multicallFunctions.map((item) => ({ + ...baseParams, + functionName: item.functionName, + args: item.args, + })), + allowFailure: false, + }); + + return { name, symbol, totalSupply } as ERC721TokenMetadata; + } +} diff --git a/packages/evm/src/contracts/types.ts b/packages/evm/src/contracts/types.ts index d3e9b6b..d65acdd 100644 --- a/packages/evm/src/contracts/types.ts +++ b/packages/evm/src/contracts/types.ts @@ -4,3 +4,9 @@ export type ERC20TokenMetadata = { totalSupply: bigint; decimals: number; }; + +export type ERC721TokenMetadata = { + name: string; + symbol: string; + totalSupply: bigint; +}; diff --git a/packages/evm/src/index.ts b/packages/evm/src/index.ts index 746fe78..3501a8b 100644 --- a/packages/evm/src/index.ts +++ b/packages/evm/src/index.ts @@ -3,4 +3,5 @@ export { verifyEIP712Message, verifyMessage } from './utils'; // contracts export { ContractClientBase, type ContractClientBaseOptions } from './contracts/ContractClientBase'; export { ERC20ContractClient, type ERC20ContractClientOptions } from './contracts/erc20/ERC20ContractClient'; -export { type ERC20TokenMetadata } from './contracts/types'; +export { ERC721ContractClient, type ERC721ContractClientOptions } from './contracts/erc721/ERC721ContractClient'; +export { type ERC20TokenMetadata, type ERC721TokenMetadata } from './contracts/types'; diff --git a/packages/evm/tests/contants.test.ts b/packages/evm/tests/contants.test.ts index 75f90f6..5f9211e 100644 --- a/packages/evm/tests/contants.test.ts +++ b/packages/evm/tests/contants.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'vitest'; -import { sepolia } from 'viem/chains'; -import { ERC20ContractClient } from '../src'; +import { mainnet, sepolia } from 'viem/chains'; +import { ERC20ContractClient, ERC721ContractClient } from '../src'; describe('contracts tests', () => { test('erc20 tests', async () => { @@ -26,4 +26,27 @@ describe('contracts tests', () => { expect(metadata).toStrictEqual({ name: 'USDC', symbol: 'USDC', totalSupply: 411951814911779444n, decimals: 6 }); }); + + test('erc721 tests', async () => { + //https://opensea.io/collection/seeing-signs + const erc721Contract = new ERC721ContractClient({ + chain: mainnet, + contractAddress: '0xbc37ee54f066e79c23389c55925f877f79f3cb84', + }); + + const metadata = await erc721Contract.tokenMetadata(); + + const [name, symbol, totalSupply] = await Promise.all([ + erc721Contract.name(), + erc721Contract.symbol(), + erc721Contract.totalSupply(), + ]); + + expect(metadata.name).toBe(name); + expect(metadata.symbol).toBe(symbol); + expect(metadata.totalSupply).toBe(totalSupply); + + const balance = await erc721Contract.balanceOf('0x303dD5e268855d6A04f3E44f33201180EDee7aFe'); + expect(balance).toBe(0n); + }); }); From 23d5444989f2c27f0b60c61df840c729d86dbe6d Mon Sep 17 00:00:00 2001 From: YanYuan Date: Wed, 23 Jul 2025 17:12:24 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A8=20chore:=20remove=20unused=20I?= =?UTF-8?q?DE=20configuration=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete .idea/.gitignore - Delete .idea/chainkit.iml - Delete .idea/codeStyles/Project.xml - Delete .idea/codeStyles/codeStyleConfig.xml - Delete .idea/modules.xml - Delete .idea/vcs.xml --- .idea/.gitignore | 8 ---- .idea/chainkit.iml | 12 ------ .idea/codeStyles/Project.xml | 59 ---------------------------- .idea/codeStyles/codeStyleConfig.xml | 5 --- .idea/modules.xml | 8 ---- .idea/vcs.xml | 6 --- 6 files changed, 98 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/chainkit.iml delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/chainkit.iml b/.idea/chainkit.iml deleted file mode 100644 index 24643cc..0000000 --- a/.idea/chainkit.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 4e86a5e..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 84cc9f8..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file