repos — cfx-solidity
repos/cfx-solidity
Solidity pipeline for Conflux development — standard ABIs, contract bindings, and compiler tooling. Independent carve-out from cfx-core, designed for framework-stable consumption.
Overview
This monorepo contains two primary packages:
@cfxdevkit/abis: Standard EVM ABI shapes (ERC-20/721/1155/4626, Multicall3) and Swappi V2 interfaces.@cfxdevkit/contracts: Typed helpers for reading, writing, deploying, and bridging contracts on Conflux eSpace and Core Space.
Both packages are published separately but developed in tandem. The abis package has zero dependencies on the cfxdevkit framework, making it safe to depend on from any layer (including @cfxdevkit/cdk). The contracts layer builds atop abis and @cfxdevkit/cdk to provide a convenient surface for contract interactions.
Package: @cfxdevkit/abis
Standard EVM ABI shapes re-exported from viem under framework-stable aliases. Includes core token standards, common extensions, and Conflux-specific Swappi V2 bindings.
Key Features
- Canonical Sources: ABIs are imported directly from
viem(which tracks official EIPs) to ensure alignment with upstream standards. - Framework-Stable Aliases: Both uppercase constants (e.g.,
ERC20_ABI) and camelCase aliases (e.g.,erc20Abi) are exported for consistency. - Extended Interfaces: Includes OpenZeppelin 5.x extensions commonly used in DevKit templates (e.g.,
ERC20_EXTENDED_ABIwithmint,burn,pausable, and role management). - Swappi V2 Support: Full Uniswap V2-compatible ABIs for Swappi on Conflux eSpace, including factory, pair, and router interfaces, plus deployed addresses for mainnet (1030) and testnet (71).
- Zero cfxdevkit Dependencies: Safe to use from
@cfxdevkit/cdkor any other layer without pulling in contract execution machinery.
File Structure
src/
├─ erc20.ts # ERC-20, ERC-2612 (permit), and extended variants
├─ erc721.ts # ERC-721, enumerable, royalty (ERC-2981), and extended variants
├─ erc1155.ts # Full ERC-1155 interface
├─ erc4626.ts # ERC-4626 tokenized vault
├─ erc165.ts # Minimal ERC-165 interface detection
├─ multicall3.ts # Multicall3 ABI and canonical address
└─ swappi/ # Swappi V2 (Uniswap V2 fork) for Conflux eSpace
├─ factory.ts # IUniswapV2Factory
├─ pair.ts # IUniswapV2Pair
└─ router.ts # IUniswapV2Router02Usage
import { ERC20_ABI, erc20Abi } from '@cfxdevkit/abis';
// Both point to the same viem-sourced ABI
console.log(ERC20_ABI === erc20Abi); // true
import { SWAPPI_FACTORY_ABI, SWAPPI_FACTORY_ADDRESS } from '@cfxdevkit/abis';
// Use with viem to create a contract instance
import { createPublicClient, http } from 'viem';
const client = createPublicClient({
chain: mainnet, // or your Conflux eSpace chain
transport: http(),
});
const factory = client.getContract({
address: SWAPPI_FACTORY_ADDRESS[1030], // mainnet
abi: SWAPPI_FACTORY_ABI,
});Testing
Run pnpm test in the packages/abis directory to validate:
- ABI shapes are arrays
- CamelCase aliases point to uppercase constants
- Required functions are present in each ABI
- Multicall3 address matches the canonical deployment
Package: @cfxdevkit/contracts
Standard contract bindings and a thin read/write/deploy surface for @cfxdevkit/cdk. Provides typed helpers for interacting with ERC-20/721/1155 contracts, multicall3, bridging, and deployment on both Conflux eSpace and Core Space.
Key Features
- Chain-Agnostic Helpers: Functions automatically select the correct RPC method (
eth_call/eth_sendRawTransactionfor eSpace,cfx_call/cfx_sendRawTransactionfor Core) based onclient.family. - Typed Surface: Leverages TypeScript and viem for ABI-safe encoding/decoding.
- Modular Design: Split into focused sub-packages:
abis: Back-compat re-exports of@cfxdevkit/abisread: Typed contract reads (readContract)write: Typed contract writes (sendWrite,prepareWrite)deploy: Contract deployment (deployContract)erc20: Nested ERC-20 specific helpersbridge: Cross-space transfers between Conflux Core and eSpaceerrors: Stable error codes for programmatic handling
- No Framework Lock-in: While built for
@cfxdevkit/cdk, only depends on its client/signer interfaces (not implementation).
File Structure
src/
├─ abis/ # Back-compat re-exports of @cfxdevkit/abis
├─ read/ # readContract (eSpace/Core Space aware)
├─ write/ # sendWrite, prepareWrite, receipt handling
├─ deploy/ # deployContract (Core/eSpace specific logic)
├─ erc20/ # Typed ERC-20 helpers (balanceOf, transfer, etc.)
├─ bridge/ # Core <-> eSpace transfers via CrossSpaceCall
├─ errors/ # ContractsError with stable codes
└─ index.ts # Barrel export (discouraged for tree-shaking)Key Functions
readContract
Typed contract read that works on both chains:
import { readContract } from '@cfxdevkit/contracts/read';
import { ERC20_ABI } from '@cfxdevkit/abis';
const balance = await readContract({
client, // from @cfxdevkit/cdk
address: '0x...', // eSpace or 'cfx:...' for Core
abi: ERC20_ABI,
functionName: 'balanceOf',
args: ['0x...'],
});sendWrite
Typed contract write with automatic gas/fee estimation:
import { sendWrite } from '@cfxdevkit/contracts/write';
import { ERC20_ABI } from '@cfxdevkit/abis';
const { hash } = await sendWrite({
client,
signer, // from @cfxdevkit/cdk
address: '0x...',
abi: ERC20_ABI,
functionName: 'transfer',
args: ['0x...', 1n * 10n ** 18n],
waitForReceipt: true, // optional
});deployContract
Deploys a contract bytecode with ABI, handling Core/eSpace differences:
import { deployContract } from '@cfxdevkit/contracts/deploy';
import { ERC20_ABI } from '@cfxdevkit/abis';
const { hash, address } = await deployContract({
client,
signer,
abi: ERC20_ABI,
bytecode: '0x...', // compiled contract bytecode
args: [initialSupply], // constructor args
waitForReceipt: true,
});erc20
Nested helpers for common ERC-20 operations:
import { erc20 } from '@cfxdevkit/contracts/erc20';
const name = await erc20.name({ client, address: tokenAddress });
const balance = await erc20.balanceOf(
{ client, address: tokenAddress },
userAddress
);
const { hash } = await erc20.transfer(
{ client, address: tokenAddress, signer },
recipient,
amount
);bridge
Cross-space transfers between Conflux Core and eSpace:
import { transferToEspace, getMappedBalance } from '@cfxdevkit/contracts/bridge';
// Core to eSpace
await transferToEspace({
client, // Core Space client
signer, // dual-address signer
to: eSpaceAddress, // 0x... address
value: amount,
});
// Query mapped balance on Core
const balance = await getMappedBalance({
client,
coreHexAddress: coreAddress, // 0x... Core address
});Error Handling
All errors are ContractsError instances with a stable code field:
import { ContractsError } from '@cfxdevkit/contracts/errors';
try {
await sendWrite(...);
} catch (err) {
if (err instanceof ContractsError) {
switch (err.code) {
case 'contracts/reverted':
// handle revert
break;
case 'contracts/receipt-timeout':
// handle timeout
break;
// ...
}
}
}Testing
Run pnpm test in packages/contracts to validate:
- Chain-specific RPC calls (eSpace vs Core)
- Proper address validation (0x hex for eSpace, base32 for Core)
- Encoding/decoding correctness
- Error wrapping and propagation
- Deployment flows (mocked signers/clients)
Usage Example: Full ERC-20 Flow
import { createPublicClient, http } from 'viem';
import { privateKeyToAccount } from '@cfxdevkit/cdk';
import { erc20 } from '@cfxdevkit/contracts/erc20';
// Setup clients (example for Conflux eSpace testnet)
const espaceClient = createPublicClient({
chain: {
id: 71,
name: 'Conflux eSpace Testnet',
nativeToken: { symbol: 'CFX', decimals: 18 },
rpc: { http: ['http://espace-testnet.rpc'] },
},
transport: http(),
});
const coreClient = /* ... similar for Core Space ... */;
// Setup signer (dual-address for Cross Space calls)
const account = privateKeyToAccount('0x...'); // must have coreAddress set for bridge
// Read token metadata
const [name, symbol, decimals] = await Promise.all([
erc20.name({ client: espaceClient, address: tokenAddress }),
erc20.symbol({ client: espaceClient, address: tokenAddress }),
erc20.decimals({ client: espaceClient, address: tokenAddress }),
]);
// Check balance
const balance = await erc20.balanceOf(
{ client: espaceClient, address: tokenAddress },
account.address
);
// Transfer tokens
const { hash } = await erc20.transfer(
{ client: espaceClient, address: tokenAddress, signer: account },
recipient,
amount
);Development
Prerequisites
- Node.js >=22
- pnpm >=10
Commands
# Install dependencies
pnpm install
# Build all packages
pnpm run -r build
# Run tests
pnpm run -r test
# Typecheck
pnpm run -r typecheck
# Lint
pnpm run -r lintDependency Validation
The module includes scripts to verify workspace consistency:
scripts/check-deps.mjs: Ensures allworkspace:*dependencies are declared within the reposcripts/validate-workspace.mjs: Confirmspnpm-workspace.template.yamlmatches actual package structure
Architecture Notes
Chain Abstraction
The contracts layer abstracts over Conflux’s two execution environments:
- eSpace: Ethereum-compatible VM (uses
eth_*RPC) - Core Space: Native Conflux VM (uses
cfx_*RPC)
Helpers like readContract and sendWrite inspect client.family to dispatch the correct RPC methods, while address validation ensures eSpace uses 0x hex and Core Space uses base32 (cfx:.../cfxtest:...).
Why Separate ABIs Package?
The @cfxdevkit/abis package exists to:
- Provide a stable, zero-dependency source of ABI shapes
- Allow
@cfxdevkit/cdkand other low-level layers to depend on ABIs without pulling in contract execution logic - Enable independent versioning of ABI definitions from contract helpers
Swappi V2 Specifics
Swappi V2 is a Uniswap V2 fork deployed on Conflux eSpace. The ABIs follow the canonical interfaces, but addresses are chain-specific:
- Mainnet (1030): Factory, Router, and WCFX addresses as defined in
swappi.ts - Testnet (71): Separate set of testnet addresses Always verify addresses against official Swappi documentation before production use.
See Also
@cfxdevkit/cdk: Core client/signer abstractions used by this module- viem : Underlying Ethereum tooling used for ABI encoding/decoding
- Conflux Documentation : Chain-specific RPC details