{"_id":"@0xtan0/chain-utils-erc20","_rev":"2-aa15b07d97159269e79493ce39d36ef4","name":"@0xtan0/chain-utils-erc20","dist-tags":{"latest":"0.1.2"},"versions":{"0.0.1":{"name":"@0xtan0/chain-utils-erc20","version":"0.0.1","author":{"name":"Wonderland"},"license":"MIT","_id":"@0xtan0/chain-utils-erc20@0.0.1","maintainers":[{"name":"0xtan0","email":"tano@wonderland.xyz"}],"dist":{"shasum":"5f74afc43d92b344fa71e748300f7cd0a30b2654","tarball":"https://registry.npmjs.org/@0xtan0/chain-utils-erc20/-/chain-utils-erc20-0.0.1.tgz","fileCount":94,"integrity":"sha512-f1CcvfO9MCMRL2uJhvbKUsEykTESKj4hDIYimfZ+4b1h71lLshr+CjuN6dLMBFkgiTHdoe0ecPON/crWe1yPQw==","signatures":[{"sig":"MEQCIEE6651Sib5yUFH0NGy5PjpXuxaS0CVQjKKObPrJxVS2AiBPrS+rlXzbWhZJpAkpGHPdIVaiVQpKepxqIyyxVL2bBw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":210345},"main":"./dist/src/index.js","type":"module","types":"./dist/src/index.d.ts","exports":{".":{"types":"./dist/src/index.d.ts","import":"./dist/src/index.js","default":"./dist/src/index.js"}},"gitHead":"bdb7dc8fb7be021b8f629b1247639bce0bc0ec56","private":false,"scripts":{"lint":"eslint \"{src,test}/**/*.{js,ts,json}\"","test":"vitest run --config vitest.config.ts --passWithNoTests","build":"tsc -p tsconfig.build.json","clean":"rm -rf dist/","format":"prettier --check \"{src,test}/**/*.{js,ts,json}\"","lint:fix":"pnpm lint --fix","test:cov":"vitest run --config vitest.config.ts --coverage","format:fix":"prettier --write \"{src,test}/**/*.{js,ts,json}\"","check-types":"tsc --noEmit -p ./tsconfig.json"},"_npmUser":{"name":"0xtan0","email":"tano@wonderland.xyz"},"_npmVersion":"10.8.2","description":"Type-safe ERC-20 utilities for [viem](https://viem.sh/). Define a token once, query balances across every chain, and execute transfers with full type safety.","directories":{"src":"src"},"_nodeVersion":"20.18.1","dependencies":{"viem":"2.45.1","@0xtan0/chain-utils-core":"workspace:*"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/chain-utils-erc20_0.0.1_1771776115354_0.5162644217950414","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@0xtan0/chain-utils-erc20","version":"0.1.2","private":false,"description":"Type-safe ERC-20 utilities for [viem](https://viem.sh/). Define a token once, query balances across every chain, and execute transfers with full type safety.","repository":{"type":"git","url":"git+https://github.com/0xtan0/chain-utils.git","directory":"packages/erc20"},"license":"MIT","author":{"name":"Wonderland"},"type":"module","exports":{".":{"types":"./dist/src/index.d.ts","import":"./dist/src/index.js","default":"./dist/src/index.js"}},"main":"./dist/src/index.js","types":"./dist/src/index.d.ts","directories":{"src":"src"},"dependencies":{"viem":"2.45.1","@0xtan0/chain-utils-core":"0.1.1"},"scripts":{"build":"tsc -p tsconfig.build.json","check-types":"tsc --noEmit -p ./tsconfig.json","clean":"rm -rf dist/","format":"prettier --check \"{src,test}/**/*.{js,ts,json}\"","format:fix":"prettier --write \"{src,test}/**/*.{js,ts,json}\"","lint":"eslint \"{src,test}/**/*.{js,ts,json}\"","lint:fix":"pnpm lint --fix","test":"vitest run --config vitest.config.ts --passWithNoTests","test:cov":"vitest run --config vitest.config.ts --coverage"},"_id":"@0xtan0/chain-utils-erc20@0.1.2","bugs":{"url":"https://github.com/0xtan0/chain-utils/issues"},"homepage":"https://github.com/0xtan0/chain-utils#readme","_integrity":"sha512-wozU/idLd/bYD03X9z3f6Tr+iYTS0IOLt0w3iKsYvjRF3gJJ41uJfBAfp9o32Yc3OPhrprUXxMRc6+V2q04gPw==","_resolved":"/tmp/a0cbe2308d8495d82cf57993652ba739/0xtan0-chain-utils-erc20-0.1.2.tgz","_from":"file:0xtan0-chain-utils-erc20-0.1.2.tgz","_nodeVersion":"22.22.0","_npmVersion":"11.10.1","dist":{"integrity":"sha512-wozU/idLd/bYD03X9z3f6Tr+iYTS0IOLt0w3iKsYvjRF3gJJ41uJfBAfp9o32Yc3OPhrprUXxMRc6+V2q04gPw==","shasum":"1909044bf473ce87e549fcdc6c373e144f0fdaa7","tarball":"https://registry.npmjs.org/@0xtan0/chain-utils-erc20/-/chain-utils-erc20-0.1.2.tgz","fileCount":91,"unpackedSize":212699,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@0xtan0%2fchain-utils-erc20@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC2lBTU9YOQIq1CaEYlCg9dZc6JbrjWafbvHho+vHsFVgIgQQHR9f8K6sDPJoxMcgUgB4Tpx9CBKgoOJnwfa6LRrns="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:e11ada4f-f23f-46ed-8286-a4ea08843531"}},"maintainers":[{"name":"0xtan0","email":"tano@wonderland.xyz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/chain-utils-erc20_0.1.2_1771784860092_0.20050656564488611"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-22T16:01:55.240Z","modified":"2026-02-22T18:27:40.551Z","0.0.1":"2026-02-22T16:01:55.524Z","0.1.2":"2026-02-22T18:27:40.254Z"},"author":{"name":"Wonderland"},"license":"MIT","description":"Type-safe ERC-20 utilities for [viem](https://viem.sh/). Define a token once, query balances across every chain, and execute transfers with full type safety.","maintainers":[{"name":"0xtan0","email":"tano@wonderland.xyz"}],"readme":"# @0xtan0/chain-utils-erc20\n\nType-safe ERC-20 utilities for [viem](https://viem.sh/). Define a token once, query balances across every chain, and execute transfers with full type safety.\n\nBuilt on top of `@0xtan0/chain-utils-core` for production app code and agent-driven automation flows.\n\n## Install\n\n```bash\npnpm add @0xtan0/chain-utils-erc20 viem\n```\n\n## Highlights\n\n-   `defineToken` stores symbol, metadata, and per-chain addresses in one reusable definition.\n-   Batch reads (`getBalances`, `getAllowances`, `getTokenMetadataBatch`) are multicall-first by default via core.\n-   `createERC20MultichainClient` runs cross-chain reads in parallel with typed chain-keyed results.\n-   `forToken` binds token + RPC clients to reduce repeated arguments in every call.\n-   Chain, token, and query shapes are validated by TypeScript before runtime.\n\n## TypeScript Safety\n\n```ts\nimport { createERC20MultichainClient, defineToken } from \"@0xtan0/chain-utils-erc20\";\nimport { createPublicClient, http } from \"viem\";\nimport { arbitrum, mainnet, optimism } from \"viem/chains\";\n\nconst client = createERC20MultichainClient([\n    createPublicClient({ chain: mainnet, transport: http() }),\n    createPublicClient({ chain: optimism, transport: http() }),\n] as const);\n\nconst WETH_TYPED = defineToken(\"WETH\", { name: \"Wrapped Ether\", decimals: 18 })\n    .onChain(mainnet, \"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\")\n    .onChain(optimism, \"0x4200000000000000000000000000000000000006\")\n    .build();\n\nconst holder = \"0x000000000000000000000000000000000000dEaD\";\n\nawait client.getTokenBalance(WETH_TYPED, holder, [mainnet.id]); // ok\n\n// @ts-expect-error Token definition has no Arbitrum address\nawait client.getTokenBalance(WETH_TYPED, holder, [arbitrum.id]);\n\nawait client.getBalances([\n    {\n        chainId: mainnet.id,\n        token: WETH_TYPED.address(mainnet.id),\n        holder,\n    },\n]); // ok\n\n// @ts-expect-error chainId must be one of the configured client chains (1 | 10)\nawait client.getBalances([\n    {\n        chainId: arbitrum.id,\n        token: WETH_TYPED.address(mainnet.id),\n        holder,\n    },\n]);\n```\n\n### Example\n\nDirect `viem` usage typically duplicates token addresses and read logic per chain:\n\n```ts\nconst [\n    mainnetAliceBalance,\n    mainnetBobBalance,\n    opAliceBalance,\n    opBobBalance,\n    arbAliceBalance,\n    arbBobBalance,\n] = await Promise.all([\n    mainnetClient.readContract({\n        abi: erc20Abi,\n        address: USDC_MAINNET,\n        functionName: \"balanceOf\",\n        args: [alice],\n    }),\n    mainnetClient.readContract({\n        abi: erc20Abi,\n        address: USDC_MAINNET,\n        functionName: \"balanceOf\",\n        args: [bob],\n    }),\n    optimismClient.readContract({\n        abi: erc20Abi,\n        address: USDC_OPTIMISM,\n        functionName: \"balanceOf\",\n        args: [alice],\n    }),\n    optimismClient.readContract({\n        abi: erc20Abi,\n        address: USDC_OPTIMISM,\n        functionName: \"balanceOf\",\n        args: [bob],\n    }),\n    arbitrumClient.readContract({\n        abi: erc20Abi,\n        address: USDC_ARBITRUM,\n        functionName: \"balanceOf\",\n        args: [alice],\n    }),\n    arbitrumClient.readContract({\n        abi: erc20Abi,\n        address: USDC_ARBITRUM,\n        functionName: \"balanceOf\",\n        args: [bob],\n    }),\n]);\n```\n\nWith `erc20`, one token definition and one call handle the same flow:\n\n```ts\nimport { createERC20MultichainClient, USDC } from \"@0xtan0/chain-utils-erc20\";\n\nconst multichain = createERC20MultichainClient([mainnetRpc, opRpc, arbRpc]);\nconst usdc = multichain.forToken(USDC);\n\nconst balances = await usdc.getBalances([alice, bob]);\n```\n\n## Usage\n\n### Use a token definition\n\nA `TokenDefinition` is pure data — no RPC, no side effects. For common tokens, import a prebuilt definition:\n\n```ts\nimport { USDC, USDT } from \"@0xtan0/chain-utils-erc20\";\n```\n\nOr define your own token mapping:\n\n```ts\nimport { defineToken } from \"@0xtan0/chain-utils-erc20\";\nimport { arbitrum, mainnet, optimism } from \"viem/chains\";\n\nconst WETH = defineToken(\"WETH\", { name: \"Wrapped Ether\", decimals: 18 })\n    .onChain(mainnet, \"0xC02a...6Cc2\")\n    .onChain(optimism, \"0x4200...0006\")\n    .onChain(arbitrum, \"0x82aF...5C02\")\n    .build();\n```\n\n### Single-chain reads\n\n```ts\nimport { createERC20Client, USDC } from \"@0xtan0/chain-utils-erc20\";\nimport { createPublicClient, http } from \"viem\";\nimport { mainnet } from \"viem/chains\";\n\nconst rpc = createPublicClient({ chain: mainnet, transport: http() });\nconst client = createERC20Client({ client: rpc });\n\nconst tokenAddress = USDC.address(mainnet.id);\nconst meta = await client.getTokenMetadata(tokenAddress);\nconst supply = await client.getTotalSupply(tokenAddress);\nconst balance = await client.getBalance(tokenAddress, account);\nconst allow = await client.getAllowance(tokenAddress, owner, spender);\n```\n\n### Multichain reads\n\nOne client, multiple chains. All RPC calls fire in parallel.\n\n```ts\nimport { createERC20MultichainClient, USDC } from \"@0xtan0/chain-utils-erc20\";\n\nconst multichain = createERC20MultichainClient([mainnetRpc, opRpc, arbRpc]);\n\nconst balances = await multichain.getTokenBalance(USDC, account);\n\nfor (const [chainId, bal] of balances.resultsByChain) {\n    console.log(`Chain ${chainId}: ${bal.balance}`);\n}\n```\n\nPer-chain metadata:\n\n```ts\nfor (const chainId of multichain.chainIds) {\n    const client = multichain.getClient(chainId);\n    const meta = await client.getTokenMetadata(USDC.address(chainId));\n    console.log(`${meta.symbol} on chain ${chainId}`);\n}\n```\n\n### Bound tokens\n\nAttach RPC connections to a token definition for zero-config reads.\n\n```ts\nconst usdc = multichain.forToken(USDC);\n\nusdc.symbol; // \"USDC\"\nusdc.chainIds; // [1, 10, 42161]\n\n// One holder, all chains\nconst balances = await usdc.getBalance(account);\n\n// Multiple holders, all chains\nconst all = await usdc.getBalances([alice, bob]);\n```\n\n### Transfers\n\nOne call handles simulate, estimate gas, sign, broadcast, and wait.\n\n```ts\nimport { createERC20WriteClient, USDC } from \"@0xtan0/chain-utils-erc20\";\nimport { createWalletClient, http } from \"viem\";\n\nconst wallet = createWalletClient({ chain: mainnet, transport: http(), account });\nconst writer = createERC20WriteClient({ client: rpc, walletClient: wallet });\nconst usdcAddress = USDC.address(mainnet.id);\n\nconst receipt = await writer.transfer(usdcAddress, to, amount, {\n    waitForReceipt: true,\n});\n```\n\n### Approve + TransferFrom\n\n```ts\nawait aliceWriter.approve(usdcAddress, spender, amount, { waitForReceipt: true });\n\nconst { allowance } = await client.getAllowance(usdcAddress, alice, spender);\n\nawait spenderWriter.transferFrom(usdcAddress, alice, spender, amount, {\n    waitForReceipt: true,\n});\n```\n\n### Granular transaction control\n\nFull prepare / sign / send / wait pipeline for maximum control.\n\n```ts\nconst prepared = await writer.prepareTransferFrom(usdcAddress, from, to, amount);\nconst signed = await writer.signTransaction(prepared);\nconst hash = await writer.sendTransaction(signed);\nconst receipt = await writer.waitForReceipt(hash);\n```\n\n## API Summary\n\n| Export                        | Description                                                 |\n| ----------------------------- | ----------------------------------------------------------- |\n| `defineToken`                 | Builder for chain-agnostic token definitions                |\n| `USDC`, `USDT`                | Pre-built token definitions for common tokens               |\n| `createERC20Client`           | Single-chain read client (balance, allowance, metadata)     |\n| `createERC20WriteClient`      | Single-chain write client (transfer, approve, transferFrom) |\n| `createERC20MultichainClient` | Multichain client with parallel cross-chain queries         |\n| `ERC20BoundToken`             | Token + RPC connections for zero-config reads               |\n| `ERC20ErrorDecoder`           | Decodes ERC-20 revert errors into typed exceptions          |\n\n## Errors\n\nThe package throws typed errors for common ERC-20 failures:\n\n-   `InsufficientBalance` / `InsufficientAllowance`\n-   `InvalidSender` / `InvalidReceiver`\n-   `InvalidApprover` / `InvalidSpender`\n-   `InvalidAddress` / `NotERC20Contract`\n\n## License\n\nMIT\n","readmeFilename":"README.md","homepage":"https://github.com/0xtan0/chain-utils#readme","repository":{"type":"git","url":"git+https://github.com/0xtan0/chain-utils.git","directory":"packages/erc20"},"bugs":{"url":"https://github.com/0xtan0/chain-utils/issues"}}