目的是发起ERC20合约转账,
以下是手动调用合约形式:
// tokenTransfer3:可以运行,手动写代码的方式async tokenTransfer3(safeAddress: string,tokenAddress: string,to: string,amount: string,privateKeys: string[], // Safe 所有者的私钥列表): Promise<TransactionResult> {if (!this.safeSdk) {await this.initSafe(safeAddress);}if (!this.safeSdk) {throw new Error('Safe SDK 初始化失败');}const provider = new ethers.JsonRpcProvider(safeConfig.RPC_URL);const deadline = Math.floor(Date.now() / 1000) + 3600; // 1小时后过期// 创建领取代币模块合约实例const tokenWithdrawModule = new ethers.Contract(safeConfig.TOKEN_WITHDRAW_MODULE_ADDRESS,TokenWithdrawModuleABI,provider);const { deployer } = await getOwner();// 获取当前nonce - 注意这里需要传入三个参数const currentNonce = await tokenWithdrawModule.nonces(deployer.address);// 准备EIP-712签名数据const domain = {name: 'TokenWithdrawModuleV2', // 注意这里合约名称改了version: '1',chainId: safeConfig.CHAIN_ID,verifyingContract: safeConfig.TOKEN_WITHDRAW_MODULE_ADDRESS};const types = {TokenWithdrawModuleV2: [ // 注意这里类型名称也要改{ name: 'tokenAddress', type: 'address' }, // 注意这里签名的字段顺序要和合约一致{ name: 'safeAddress', type: 'address' },{ name: 'amount', type: 'uint256' },{ name: '_beneficiary', type: 'address' },{ name: 'nonce', type: 'uint256' },{ name: 'deadline', type: 'uint256' }]};const message = {tokenAddress, // 注意这里字段顺序要和类型定义一致safeAddress,amount,_beneficiary: deployer.address, // 注意这里计算签名使用sendernonce: Number(currentNonce),deadline};this.logger.warn(`准备签名数据: amount=${amount}, to=${to}, deadline=${deadline}`);// 收集所有签名const signatures: string[] = [];// deployer签名const deployerSignature = await deployer.signTypedData(domain, types, message);signatures.push(deployerSignature);this.logger.warn(`Deployer已签名: ${deployer.address.slice(0, 10)}...`);// // 其他签名者签名// for (const privateKey of privateKeys) {// const signer = new ethers.Wallet(privateKey, provider);// const signature = await signer.signTypedData(domain, types, message);// signatures.push(signature);// this.logger.warn(`签名者已签名: ${signer.address.slice(0, 10)}...`);// }// 合并所有签名const combinedSignature = ethers.concat(signatures.map(sig => ethers.getBytes(sig)));// 调用模块合约的tokenTransfer方法const moduleWithSigner = tokenWithdrawModule.connect(deployer);const tx = await (moduleWithSigner as any).tokenTransfer(tokenAddress,safeAddress,amount,to,deadline,combinedSignature,{ gasLimit: 500000 });this.logger.warn(`交易已发送: ${tx.hash}`);const receipt = await tx.wait();this.logger.warn(`交易已确认: ${receipt.hash}`);return receipt;}
采用sdk调用形式:
async tokenTransfer(safeAddress: string,tokenAddress: string,to: string,amount: string,privateKeys: string[],): Promise<TransactionResult> {// 如果尚未初始化,则先初始化 Safe 实例if (!this.safeSdk) {await this.initSafe(safeAddress);}if (!this.safeSdk) {throw new Error('Safe SDK 初始化失败');}const provider = new ethers.JsonRpcProvider(safeConfig.RPC_URL);const deadline = Math.floor(Date.now() / 1000) + 3600; // 1小时后过期// 编码合约调用数据const abiF = new ethers.Interface(TokenWithdrawModuleABI);const encodeFunctionData = abiF.encodeFunctionData('tokenTransfer', [tokenAddress, // _tokenAddresssafeAddress, // _safeAddressamount, // _amountto, // _beneficiarydeadline, // _deadline'0x' // _signatures - 空签名,SDK会处理签名]);// 构造Safe交易数据const safeTxData = {to: safeConfig.TOKEN_WITHDRAW_MODULE_ADDRESS,value: '0',data: encodeFunctionData,operation: 0,};// 使用Safe SDK组装交易const safeTransaction = await this.safeSdk.createTransaction({transactions: [safeTxData],options: {safeTxGas: '500000',}});this.logger.log('交易数据组装成功');// 使用deployer签名const { deployer } = await getOwner();await this.safeSdk.signTransaction(safeTransaction, deployer.address);this.logger.log(`Deployer已签名: ${deployer.address}`);// 收集其他签名者的签名for (const privateKey of privateKeys) {const signer = new ethers.Wallet(privateKey, provider);await this.safeSdk.signTransaction(safeTransaction, signer.address);this.logger.log(`签名者已签名: ${signer.address}`);}// 执行交易const feeData = await provider.getFeeData();const gasPrice = feeData.gasPrice ? (feeData.gasPrice * 12n / 10n).toString() : ethers.parseUnits('50', 'gwei').toString(); // 使用当前gas价格的1.2倍或默认50 Gweiconst tx = await this.safeSdk.executeTransaction(safeTransaction, {gasLimit: 500000,gasPrice});this.logger.log(`交易已发送,交易哈希 ${tx.hash},使用的gas价格: ${ethers.formatUnits(gasPrice, 'gwei')} Gwei`);// 等待交易被确认const receipt = await provider.waitForTransaction(tx.hash);if (!receipt) {throw new Error('交易等待超时');}this.logger.log(`交易已确认,区块号: ${receipt.blockNumber}`);if (receipt.status === 0) {throw new Error('交易执行失败');}return tx;}
sdk调用形式会出现一直pending的情况,不知道是不是测试链的问题