@neynar/smart-contract-ts-generator
v0.7.1
Published
Generate TypeScript contract wrappers from contract addresses on any EVM chain
Readme
Contract TypeScript Generator
Automatically generate TypeScript wrappers for smart contracts from their addresses on various blockchain networks.
Features
- Fetches contract ABIs from Etherscan (and variants) or Sourcify
- Generates type-safe TypeScript code using abitype
- Creates React hooks using wagmi for contract interaction
- Optional class-based client for server-side or complex use cases
- Supports multiple chains (Base, Optimism, Ethereum, and testnets)
Installation
NPM
npm install -g @neynar/smart-contract-ts-generatorYarn
yarn global add @neynar/smart-contract-ts-generatorNPX (No Installation)
npx @neynar/smart-contract-ts-generator --address <CONTRACT_ADDRESS> --chain <CHAIN_NAME> --name <CONTRACT_NAME>Local Development (Without Publishing)
For testing locally without publishing to npm:
# Clone and set up
git clone https://github.com/neynarxyz/smart-contract-ts-generator.git
cd smart-contract-ts-generator
yarn install
yarn build
# Option 1: Link globally (recommended)
yarn link
# Now you can use: smart-contract-gen ...
# Option 2: Use yarn from within the repo
yarn generate --address 0x... --chain base --name USDC
# Option 3: Use npx from within the repo
npx . --address 0x... --chain base --name USDCUsage
Basic Usage
smart-contract-gen \
--address <CONTRACT_ADDRESS> \
--chain <CHAIN_NAME> \
--name <CONTRACT_NAME>Or with npx:
npx @neynar/smart-contract-ts-generator \
--address <CONTRACT_ADDRESS> \
--chain <CHAIN_NAME> \
--name <CONTRACT_NAME>Examples
Basic Usage - USDC on Base
smart-contract-gen \
--address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--chain base \
--name USDCOutput: ./base/usdc/
Custom Root Directory
smart-contract-gen \
--address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
--chain base \
--name USDC \
--output ./contractsOutput: ./contracts/base/usdc/
CLI Options
Required Options
-a, --address <address>- Contract address (0x-prefixed hex)-c, --chain <chain>- Chain name (see supported chains below)-n, --name <name>- Contract name for generated files (e.g., USDC, MyToken)
Optional Options
-o, --output <root>- Output root directory (default: current directory). Files are generated at<root>/<chain>/<name>-k, --api-key <key>- Etherscan API key (or setETHERSCAN_API_KEYenv var)--no-class- Skip generating class-based client (only generate hooks)-h, --help- Show help message
Supported Chains
| Chain | Chain ID | Explorer API | | ---------------- | -------- | ----------------------------------- | | base | 8453 | api.basescan.org | | base-sepolia | 84532 | api-sepolia.basescan.org | | optimism | 10 | api-optimistic.etherscan.io | | optimism-sepolia | 11155420 | api-sepolia-optimistic.etherscan.io | | ethereum | 1 | api.etherscan.io | | sepolia | 11155111 | api-sepolia.etherscan.io |
Using Etherscan API Key
For better rate limits and reliability, provide an Etherscan API key:
# Via environment variable
export ETHERSCAN_API_KEY=your_api_key
smart-contract-gen ...
# Or via CLI argument
smart-contract-gen \
--api-key your_api_key \
--address ... \
--chain ... \
--name ...Get a free API key from:
- Base: https://basescan.org/apis
- Optimism: https://optimistic.etherscan.io/apis
- Ethereum: https://etherscan.io/apis
Generated Files
The generator creates a directory with the following structure:
<output-directory>/
├── abi.ts # Raw ABI with const assertion
├── hooks.ts # React hooks for wagmi
├── client.ts # Class-based client (optional)
└── index.ts # Public exportsExample Generated Code
React Hooks (hooks.ts)
import { useReadContract, useWriteContract } from 'wagmi';
import type { Address } from 'viem';
import { CONTRACT_ABI } from './abi.ts';
// Read function hook
export function useUsdcBalanceOf(
contractAddress: Address,
account: Address,
options?: { enabled?: boolean; watch?: boolean }
) {
return useReadContract({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: 'balanceOf',
args: [account],
query: options,
});
}
// Write function hook
export function useUsdcTransfer(contractAddress: Address) {
const { writeContract, ...rest } = useWriteContract();
const write = ({ to, amount }: { to: Address; amount: bigint }) => {
return writeContract({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: 'transfer',
args: [to, amount],
});
};
return { ...rest, write, writeAsync: write };
}Class-Based Client (client.ts)
import type { PublicClient, WalletClient, Address, Hex } from 'viem';
import { CONTRACT_ABI } from './abi.ts';
export class UsdcContract {
private publicClient: PublicClient;
private walletClient?: WalletClient;
private address: Address;
constructor(address: Address, publicClient: PublicClient, walletClient?: WalletClient) {
this.address = address;
this.publicClient = publicClient;
this.walletClient = walletClient;
}
// Read method
async balanceOf(account: Address): Promise<bigint> {
return await this.publicClient.readContract({
address: this.address,
abi: CONTRACT_ABI,
functionName: 'balanceOf',
args: [account],
});
}
// Write method
async transfer(to: Address, amount: bigint): Promise<Hex> {
if (!this.walletClient) {
throw new Error('Wallet client required for write operations');
}
const { request } = await this.publicClient.simulateContract({
address: this.address,
abi: CONTRACT_ABI,
functionName: 'transfer',
args: [to, amount],
account: this.walletClient.account,
});
return await this.walletClient.writeContract(request);
}
}Using Generated Code
In React Components (Hooks)
import { useUsdcBalanceOf, useUsdcTransfer } from './contracts/usdc';
function MyComponent() {
const contractAddress = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
const userAddress = '0x...';
// Read balance
const { data: balance, isLoading } = useUsdcBalanceOf(contractAddress, userAddress);
// Write transfer
const { write: transfer, isPending } = useUsdcTransfer(contractAddress);
const handleTransfer = () => {
transfer({
to: '0xRecipient...',
amount: 1000000n, // 1 USDC (6 decimals)
});
};
return (
<div>
<p>Balance: {balance?.toString()}</p>
<button onClick={handleTransfer} disabled={isPending}>
Transfer
</button>
</div>
);
}In Server-Side Code (Class)
import { createPublicClient, createWalletClient, http } from 'viem';
import { base } from 'viem/chains';
import { UsdcContract } from './contracts/usdc';
const publicClient = createPublicClient({
chain: base,
transport: http(),
});
const walletClient = createWalletClient({
chain: base,
transport: http(),
account: yourAccount,
});
const usdc = new UsdcContract(
'0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
publicClient,
walletClient
);
// Read balance
const balance = await usdc.balanceOf('0x...');
// Transfer tokens
const txHash = await usdc.transfer('0xRecipient...', 1000000n);How It Works
- Fetch ABI: Retrieves the contract ABI from Etherscan API (primary) or Sourcify (fallback)
- Parse ABI: Validates and categorizes functions (read vs write), events, and constructor
- Generate Code: Creates TypeScript files with:
- ABI constant with type assertion
- React hooks for each function
- Optional class-based client
- Type-safe function arguments and return types
- Write Files: Outputs formatted TypeScript files to the specified directory
Type Safety
All generated code is fully type-safe using:
- abitype: Provides strict TypeScript types derived from the ABI
- viem: Ensures type-safe contract interactions
- wagmi: Type-safe React hooks for Ethereum
The generator automatically converts Solidity types to TypeScript:
uint256→bigintaddress→Address(from viem)bytes→Hex(from viem)bool→booleanstring→string- Arrays →
readonly T[]
Troubleshooting
Contract Not Found
Make sure the contract is verified on Etherscan or Sourcify. Unverified contracts cannot have their ABIs fetched.
Rate Limiting
If you encounter rate limiting errors:
- Get a free Etherscan API key (see links above)
- Use the
--api-keyoption orETHERSCAN_API_KEYenvironment variable - The tool automatically falls back to Sourcify if Etherscan fails
Invalid ABI
If the generated code has issues, check that:
- The contract address is correct
- The contract is on the specified chain
- The contract is verified on the block explorer
Contributing
To add support for new chains:
- Add the chain configuration to
CHAIN_CONFIGSin fetch-abi.ts - Include the chain ID, name, Etherscan API URL, and Sourcify support flag
License
Part of the Neynar monorepo.
