当前位置: 首页 > ds >正文

基于智能合约实现非托管支付

背景

       随着区块链技术的普及,加密货币从单一的投资标的逐步延伸至支付场景,成为跨境贸易、数字服务、NFT 交易等领域的重要结算工具。然而,加密支付的核心矛盾始终聚焦于 “资金控制权” 与 “使用便捷性” 的平衡 —— 这一矛盾直接催生了两大主流模式:托管式加密支付与非托管式加密支付。二者基于不同的信任逻辑与技术路径,分别服务于不同用户群体的需求,共同构成了加密支付生态的核心骨架。

托管式的实现方式

实现流程

流程说明

  1. 用托管服务维护一个地址池
  2. 每生成一个订单,分配一个唯一地址,订单完成后回收地址
  3. 结算时归集地址池中的余额,再打给商家

优点

  1. 技术门槛稍低,地址可依赖托管平台管理
  2. 无合约风险,资金安全问题交给托管平台

缺点

  1. 结算过程存在归集操作,归集又依赖手续支付,流程较长,无法实时结算
  2. 地址占用,地址回收,支付过期,订单支付完成等状态处理存在复杂的业务逻辑,易出bug
  3. 需要托管商户资金,可能存在合规上的风险

非托管的实现方式

实现流程

流程说明

  1. 部署合约工厂
  2. 商户发起支付请求
  3. 调用合约工厂预测支付地址,实际是合约地址
  4. 用户扫码付款,监控链上入金
  5. 向支付地址上部署转账合约
  6. 调用转账合约发起归集

优点

  1. 可灵活的创建地址,不依赖托管平台
  2. 可直接链上结算,结算速度取决于链上速度
  3. 支持非稳定币的支付也较容易集成

缺点

  1. 存在私钥管理上的问题
  2. 部分业务依赖合约代码,对技术要求较高
  3. 无法实现无GAS费模式支付

核心代码

工厂合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;import "./Vault.sol";contract Create2Factory {event Deployed(address vault,bytes32 salt,address merchant,address payout);function deployVault(bytes32 salt,address merchant,address payout) external returns (address vaultAddr) {bytes memory bytecode = abi.encodePacked(type(Vault).creationCode,abi.encode(merchant, payout));assembly {vaultAddr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)if iszero(vaultAddr) {revert(0, 0)}}emit Deployed(vaultAddr, salt, merchant, payout);}function predict(address merchant,address payout,bytes32 salt) external view returns (address) {bytes memory bytecode = abi.encodePacked(type(Vault).creationCode,abi.encode(merchant, payout));bytes32 codeHash = keccak256(bytecode);returnaddress(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff),address(this),salt,codeHash)))));}
}

转账合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;interface IERC20 {function balanceOf(address) external view returns (uint256);function transfer(address, uint256) external returns (bool);
}contract Vault {address public immutable merchant;address public immutable payout;address public immutable factory;constructor(address _merchant, address _payout) payable {merchant = _merchant;payout = _payout;factory = msg.sender;}receive() external payable {}modifier onlyMerchant() {require(msg.sender == merchant, "not merchant");_;}function sweepETH(uint256 amount) external onlyMerchant {uint256 bal = address(this).balance;require(amount <= bal, "insufficient");(bool ok, ) = payable(payout).call{value: amount}("");require(ok, "eth transfer failed");}function sweepERC20(address token, uint256 amount) external onlyMerchant {require(IERC20(token).transfer(payout, amount),"erc20 transfer failed");}function sweepAll(address[] calldata tokens) external onlyMerchant {uint256 bal = address(this).balance;if (bal > 0) {(bool ok, ) = payable(payout).call{value: bal}("");require(ok, "eth transfer failed");}for (uint256 i = 0; i < tokens.length; i++) {uint256 tb = IERC20(tokens[i]).balanceOf(address(this));if (tb > 0)require(IERC20(tokens[i]).transfer(payout, tb),"erc20 transfer failed");}}
}

转账方法

package com.jinchi.service;import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.http.HttpService;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.utils.Numeric;
import org.web3j.tx.gas.DefaultGasProvider;
import org.web3j.tx.RawTransactionManager;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Type;import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;public class SweepERC20Example {public static void main(String[] args) throws Exception {// 1. 连接 RPC 节点Web3j web3 = Web3j.build(new HttpService("https://sepolia.infura.io/v3/fe34061738ec4907b9e6f75069fe6150"));// 2. 服务钱包String privateKey = "f6539a36c4d2a6a8ac821a9a47047ef68a97f70eab32c4b651919c9325012f88"; // ⚠️ 切勿明文保存在生产环境Credentials credentials = Credentials.create(privateKey);// 3. Vault 合约地址String vaultAddress = "0x399cdd9092f57244E0d5378fFBc42125595Edb0D";// 4. 需要 sweep 的 ERC20 代币地址(例如 USDT)String tokenAddress = "0x419Fe9f14Ff3aA22e46ff1d03a73EdF3b70A62ED";// 5. 要转账的数量:0.1 个代币BigDecimal tokenAmount = new BigDecimal("0.1"); // 用 BigDecimal 避免浮点数精度问题// 6. 转换为最小单位:0.1 × 10^6 = 100000BigInteger amountInWei = tokenAmount.multiply(new BigDecimal("10").pow(6)) // 乘以 10^6,精度为6.toBigInteger();// 7. 构造合约函数调用数据Function function = new Function("sweepERC20",Arrays.asList(new Address(tokenAddress), new Uint256(amountInWei)), // 参数Arrays.asList() // 无返回值);String encodedFunction = FunctionEncoder.encode(function);// 8. 获取链上的 nonce(交易计数)BigInteger nonce = web3.ethGetTransactionCount(credentials.getAddress(),org.web3j.protocol.core.DefaultBlockParameterName.LATEST).send().getTransactionCount();// 9. 预估 Gas(也可以设置固定 GasLimit)BigInteger gasPrice = web3.ethGasPrice().send().getGasPrice();BigInteger gasLimit = DefaultGasProvider.GAS_LIMIT; // 默认 21000,可根据实际情况调整// 10. 构造交易RawTransaction rawTransaction = RawTransaction.createTransaction(nonce,gasPrice,gasLimit,vaultAddress,BigInteger.ZERO, // sweepERC20 不需要转 ETHencodedFunction);// 11. 签名交易byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);String hexValue = Numeric.toHexString(signedMessage);// 12. 发送交易String txHash = web3.ethSendRawTransaction(hexValue).send().getTransactionHash();System.out.println("SweepERC20 tx sent: " + txHash);}
}

核心方法

keccak256

基于 Keccak-256 算法实现,主要用于将任意输入数据转换为一个固定长度(256 位,即 32 字节)的哈希值,在通过 CREATE2 opcode 部署合约时,keccak256 是计算预部署地址的核心。

address = keccak256(abi.encodePacked(bytes1(0xff),       // 固定前缀deployerAddress,    // 部署者(工厂合约)地址salt,               // 盐值(32字节,自定义随机数)keccak256(bytecode) // 被部署合约的字节码哈希)
)
关键特点
  • 确定性:相同输入永远产生相同哈希值(如 keccak256("hello") 结果固定);
  • 不可逆性:无法从哈希值反推原始输入;
  • 雪崩效应:输入的微小变化(如多一个空格)会导致哈希值完全不同;
  • 高效性:在 EVM 中执行成本低(gas 消耗较少)。
主要用途
  • 生成唯一标识(哈希值)
  • 构建映射(Mapping)的键
  • Create2 地址预测
  • 伪随机数生成(有限场景)

create2

CREATE2 是以太坊虚拟机(EVM)提供的一个 opcode,用于确定性部署智能合约,即可以在合约部署前预先计算出它的地址。与传统的 CREATE opcode 相比,CREATE2 最大的优势是部署地址不依赖部署顺序(nonce),仅由特定参数决定,这使得它在需要提前知道合约地址的场景中非常有用。

// 工厂合约中用 CREATE2 部署子合约
function deployWithCreate2(bytes32 salt, address param1) external returns (address newContract) {// 1. 准备被部署合约的字节码(包含构造函数参数)bytes memory bytecode = abi.encodePacked(type(MyContract).creationCode, // 合约的创建代码abi.encode(param1)             // 构造函数参数);// 2. 内联汇编调用 CREATE2assembly {// 参数:// 0: 部署时发送的 ETH 数量(这里为 0)// add(bytecode, 0x20): 字节码的起始位置(跳过长度前缀)// mload(bytecode): 字节码的长度// salt: 盐值newContract := create2(0, add(bytecode, 0x20), mload(bytecode), salt)// 检查部署是否成功if iszero(newContract) {revert(0, 0) // 部署失败则回滚}}
}
关键特点
  • 地址可预测:部署地址由 4 个参数唯一确定,提前计算后可在部署前就知道最终地址;
  • 与 nonce 无关:传统 CREATE 的地址依赖部署者的 nonce(交易次数),而 CREATE2 不依赖,避免因 nonce 变化导致地址改变;
  • 支持 “预部署” 场景:可以先告知用户地址,让用户向该地址转账,之后再部署合约处理资金。
主要用途
  • 支付通道:预先生成收款地址,用户转账后再部署合约处理资金(如你的 Vault 模式)。
  • 合约升级:通过固定地址的代理合约,结合 CREATE2 部署新实现合约,确保地址不变。
  • 链下交易:提前生成合约地址,在链下协商后再上链部署,避免地址变化导致的问题。
  • 批量部署:通过不同 salt 批量生成唯一地址,用于需要大量独立合约的场景(如 NFT 钱包)

实验步骤

部署工厂合约

  • 部署地址:0xcc35b9E418b91F83bbFB1c614959E111ca87B008
  • 合约地址:0x399cdd9092f57244E0d5378fFBc42125595Edb0D

计算收款地址(合约地址)

  • 调用地址:0xcc35b9E418b91F83bbFB1c614959E111ca87B008
  • 商户地址:0x8643Ae26DD2Eac2240808824B47368faD8b75F05
  • 结算地址:0xb18131733b533Ed553C52AA8E8E5d6875d7D4f9D
  • 收款地址(合约地址):0x399cdd9092f57244E0d5378fFBc42125595Edb0D

用户支付

区块链转账ERC20

部署转账合约

  • 调用地址:0xcc35b9E418b91F83bbFB1c614959E111ca87B008
  • 商户地址:0x8643Ae26DD2Eac2240808824B47368faD8b75F05
  • 结算地址:0xb18131733b533Ed553C52AA8E8E5d6875d7D4f9D
  • 合约地址:0x399cdd9092f57244E0d5378fFBc42125595Edb0D

发起转账

转账由后端Java代码发起,调用转账合约的sweepERC20方法,需要用到Merchant地址的私钥进行签名,由ERC20代币由转账合约转移至结算地址,GAS费由Merchant地址支付,实际生产中可灵活处理。

链上支付未来

基于稳定币链或者智能合约钱包实现真正的无GAS支付,将和传统支付一样丝滑

稳定币公链的发展

Stable

最近Tether和Bitfinex联合开发Layer1公链Stable,以USDT作为原生gas,支持免费的点对点USDT转账,应用于支付领域,目前内测阶段,Stable新特性:

  • 原生USDT作为Gas费
  • EVM兼容
  • FX引擎

Arc

Circle旗下科技公司最近推出Arc公链,专为稳定币金融打造,目前内测阶段,Arc新特性:

  • USDC作为原生Gas
  • 内置外汇引擎
  • 兼容EVM

ERC-4337基础设施的发展

使用 账户抽象 (ERC-4337) 的智能合约钱包创建了一种通过智能合约管理的钱包,而不是像 EOA 钱包(外部拥有地址)那样由单个私钥管理的钱包。

智能合约钱包的可编程性允许开发范围广泛的新用例。通过降低复杂性而不影响安全性或匿名性,智能合约钱包将帮助促进下一波区块链用户的入场。

智能合约钱包交易的典型流程是:

  1. 用户希望执行一个 UserOperation
  2. UserOperations 被发送到一个“替代内存池”
  3. 一个具有 EOA 钱包的 打包器 将所有 UserOperations 进行打包并发送到 EntryPoint 合约
  4. EntryPoint 合约验证并执行所有 UserOperations
  5. 打包 UserOperations 的 EOA 钱包将由用户的钱包或 Paymaster 偿还其代表用户支出的 ETH
http://www.xdnf.cn/news/20307.html

相关文章:

  • Qt添加图标资源
  • conda配置pytorch虚拟环境
  • git cherry-pick 用法
  • dpdk example
  • 自动化流水线
  • Ubuntu 22 redis集群搭建
  • 电脑越用越卡?C盘红到预警?这款清理神器帮你一键 “减负”!
  • 跨域请求问题浅解
  • 深入浅出 QComboBox:Qt 中的下拉列表组件
  • uniapp开发前端静态视频界面+如何将本地视频转换成网络地址
  • 2024年9月GESPC++三级真题解析(含视频)
  • 核心高并发复杂接口重构方案
  • 9.5 IO-线程day5
  • SQL Sever2022安装教程
  • LKT4202UGM重新定义物联网设备安全标准
  • python 自动化在web领域应用
  • Karmada v1.15 版本发布
  • 如何选择文件夹然后用vscode直接打开
  • 23种设计模式——装饰器模式(Decorator Pattern)详解
  • Meta AI眼镜Hypernova量产临近,微美全息构筑护城河引领人机交互变革浪潮
  • Ubuntu 22.0安装中文输入法
  • 分布式事务的Java实践
  • 面试官问:你如何提高工作效率?
  • 专项智能练习(计算机动画基础)
  • java log相关:Log4J、Log4J2、LogBack,SLF4J
  • 安防芯片ISP白平衡统计数据如何提升场景适应性?
  • 微信小程序如何进行分包处理?
  • 源雀SCRM开源:企微文件防泄密
  • 2025职教技能大赛汽车制造与维修赛道速递-产教融合实战亮剑​
  • 【详细指导】多文档界面(MDI)的应用程序-图像处理