跳至主要内容

写入合约

使用 writeContract() 向已部署的智能合约发送状态更改交易。与 readContract 不同,此方法会提交链上交易,消耗 Gas,并返回一个可轮询确认的交易哈希。

方法签名

client.writeContract(params: ContractWriteParams): Promise<ApiResponse<ContractWriteResult>>

ContractWriteParams

import type { ContractWriteParams } from '@one_deploy/sdk';

interface ContractWriteParams {
contractAddress: string; // 目标合约地址
chainId: number; // 交易所在的链
functionName: string; // Solidity 函数名
args?: unknown[]; // 位置参数
abi?: Record<string, unknown>[]; // ABI 数组(如果合约已被索引则可选)
value?: string; // 原生代币金额(以 wei 为单位,如 ETH、MATIC)
gasLimit?: string; // 可选的 Gas 限制覆盖
}

ContractWriteResult

interface ContractWriteResult {
transactionHash: string; // 已提交的交易哈希
chainId: number;
status: 'pending' | 'submitted';
}

收到交易哈希后,使用 getTransactionStatus() 跟踪确认状态。

示例

ERC-20: approve

授权某个 spender 代你转移代币的额度:

import { useOneEngine } from '@one_deploy/sdk';

function ApproveButton({
token,
spender,
amount,
}: {
token: string;
spender: string;
amount: string;
}) {
const { client } = useOneEngine();

async function handleApprove() {
const res = await client.writeContract({
contractAddress: token,
chainId: 137,
functionName: 'approve',
args: [spender, amount], // amount 为最小单位(wei / raw)
});

if (!res.success) {
console.error('Approve failed:', res.error?.message);
return;
}

console.log('Tx submitted:', res.data.transactionHash);
// 现在轮询确认
await waitForConfirmation(client, res.data.transactionHash, 137);
}

return <button onClick={handleApprove}>Approve</button>;
}

ERC-20: transfer

向另一个地址转移代币:

const res = await client.writeContract({
contractAddress: '0xA0b8...3E7a', // Ethereum 上的 USDC
chainId: 1,
functionName: 'transfer',
args: [
'0xRecipient...Address', // to
'1000000', // amount(1 USDC = 1e6,6 位小数代币)
],
});

if (res.success) {
console.log('Transfer submitted:', res.data.transactionHash);
}

ERC-721: safeTransferFrom

安全地转移 NFT(接收方必须实现 onERC721Received):

const res = await client.writeContract({
contractAddress: '0xBC4C...a1F9',
chainId: 1,
functionName: 'safeTransferFrom',
args: [
'0xFromAddress...', // from
'0xToAddress...', // to
42, // tokenId
],
});

if (res.success) {
console.log('NFT transfer submitted:', res.data.transactionHash);
}

带 ETH 金额的自定义合约

调用 payable 函数并附带原生 ETH:

const customAbi = [
{
inputs: [{ name: 'referrer', type: 'address' }],
name: 'mintWithReferral',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
];

const res = await client.writeContract({
contractAddress: '0x5566...7788',
chainId: 1,
functionName: 'mintWithReferral',
args: ['0xReferrerAddress...'],
abi: customAbi,
value: '50000000000000000', // 0.05 ETH(以 wei 为单位)
});

if (res.success) {
console.log('Mint tx:', res.data.transactionHash);
}
提示

使用内置的 parseEther 工具函数或任何 BigNumber 库将人类可读的数值转换为 wei。value 字段始终期望以链的最小单位为计量的字符串

交易确认流程

writeContract 返回交易哈希后,轮询 getTransactionStatus 等待链上确认:

async function waitForConfirmation(
client: InstanceType<typeof import('@one_deploy/sdk').OneEngineClient>,
txHash: string,
chainId: number,
maxAttempts = 30,
intervalMs = 2000,
): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
const status = await client.getTransactionStatus(txHash, chainId);

if (status.data.status === 'confirmed') {
console.log('Transaction confirmed in block', status.data.blockNumber);
return;
}

if (status.data.status === 'failed') {
throw new Error(`Transaction failed: ${status.data.reason}`);
}

// status.data.status === 'pending' -- 继续轮询
await new Promise((r) => setTimeout(r, intervalMs));
}

throw new Error('Transaction confirmation timed out');
}

getTransactionStatus 响应

interface TransactionStatus {
transactionHash: string;
chainId: number;
status: 'pending' | 'confirmed' | 'failed';
blockNumber?: number;
gasUsed?: string;
reason?: string; // 当 status 为 'failed' 时存在
}

完整 React 示例

在单个组件中组合写入和确认:

import { useOneEngine } from '@one_deploy/sdk';
import { useState } from 'react';

function TransferToken() {
const { client } = useOneEngine();
const [status, setStatus] = useState<string>('idle');

async function handleTransfer() {
setStatus('submitting');

const res = await client.writeContract({
contractAddress: '0xA0b8...3E7a',
chainId: 137,
functionName: 'transfer',
args: ['0xRecipient...', '5000000'], // 5 USDC
});

if (!res.success) {
setStatus(`error: ${res.error?.message}`);
return;
}

setStatus('pending');
const txHash = res.data.transactionHash;

// 轮询确认
const interval = setInterval(async () => {
const txRes = await client.getTransactionStatus(txHash, 137);
if (txRes.data.status === 'confirmed') {
clearInterval(interval);
setStatus('confirmed');
} else if (txRes.data.status === 'failed') {
clearInterval(interval);
setStatus(`failed: ${txRes.data.reason}`);
}
}, 2000);
}

return (
<div>
<button onClick={handleTransfer} disabled={status === 'submitting' || status === 'pending'}>
Transfer 5 USDC
</button>
<p>Status: {status}</p>
</div>
);
}

错误处理

const res = await client.writeContract({
contractAddress: '0xToken...',
chainId: 1,
functionName: 'transfer',
args: ['0xTo...', '1000000000000000000'],
});

if (!res.success) {
switch (res.error?.code) {
case 'INSUFFICIENT_FUNDS':
console.error('Not enough balance to cover the transfer + gas.');
break;
case 'EXECUTION_REVERTED':
console.error('Contract reverted:', res.error.message);
break;
case 'GAS_ESTIMATION_FAILED':
console.error('Could not estimate gas. The transaction would likely revert.');
break;
case 'INVALID_ARGS':
console.error('Arguments do not match the function signature.');
break;
default:
console.error('Unexpected error:', res.error?.message);
}
}

下一步