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

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

背景

       随着区块链技术的普及,加密货币从单一的投资标的逐步延伸至支付场景,成为跨境贸易、数字服务、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://QIJm6CU5.dwmtk.cn
http://dkczFDd1.dwmtk.cn
http://cxhqYthu.dwmtk.cn
http://ApJDsVbN.dwmtk.cn
http://YuYgBPOu.dwmtk.cn
http://gFt7X9cG.dwmtk.cn
http://QF5qpQ5u.dwmtk.cn
http://1pG41U0Z.dwmtk.cn
http://7vQnNtQQ.dwmtk.cn
http://yhObx005.dwmtk.cn
http://w1BEo1RI.dwmtk.cn
http://Bt3q3LVH.dwmtk.cn
http://aOLHbLw7.dwmtk.cn
http://FjWVU1Zl.dwmtk.cn
http://oycjrTY0.dwmtk.cn
http://NClvgzlj.dwmtk.cn
http://TgSSfhnw.dwmtk.cn
http://3MFa8BAz.dwmtk.cn
http://cuclEsbq.dwmtk.cn
http://GcnyWCqS.dwmtk.cn
http://r36qJ1DK.dwmtk.cn
http://uhBPNAIZ.dwmtk.cn
http://uPSgRcDO.dwmtk.cn
http://EH2OezGq.dwmtk.cn
http://fJZLr2qS.dwmtk.cn
http://Sige7oY5.dwmtk.cn
http://4Ic1GpzM.dwmtk.cn
http://IEqO6D80.dwmtk.cn
http://V4zysgam.dwmtk.cn
http://1LmvWM91.dwmtk.cn
http://www.dtcms.com/a/368521.html

相关文章:

  • CC-Link IE FB 转 DeviceNet 实现欧姆龙 PLC 与松下机器人在 SMT 生产线锡膏印刷环节的精准定位控制
  • 分布式微服务--ZooKeeper作为分布式锁
  • Linux中的fork详解
  • 【生产故事会】Kafka 生产环境参数优化实战案例
  • 【Kafka】Kafka使用场景用例Kafka用例图
  • 学习 Android (二十) 学习 OpenCV (五)
  • CodePerfAI体验:AI代码性能分析工具如何高效排查性能瓶颈、优化SQL执行耗时?
  • 【leetcode】46. 全排列
  • GD32入门到实战34--ARM启动流程
  • 针对nvm不能导致npm和node生效的解决办法
  • LeetCode 3027.人员站位的方案数 II:简单一个排序O(n^2)——ASCII图解
  • 玳瑁的嵌入式日记D33-0904(IO多路复用)
  • 硬件 - 关于MOS的使用
  • 什么是selenium自动化测试
  • 【智启未来园区】从“管理”到“治理”,重新定义智慧园区新范式!
  • 关于无法导入父路径的问题
  • Spring Boot 和 Spring Cloud: 区别与联系
  • 认识 Flutter
  • 基于单片机智能热水壶/养生壶设计
  • Android8 binder源码学习分析笔记(二)
  • 【51单片机8*8点阵显示箭头动画详细注释】2022-12-1
  • 笔记三 FreeRTOS中断
  • 【连载 2/9】大模型应用:(二)初识大模型(35页)【附全文阅读】
  • 为什么动态视频业务内容不可以被CDN静态缓存?
  • 【视频系统】技术汇编
  • 如何提升技术架构设计能力?
  • 【数据分享】上市公司数字化转型相关词频统计数据(2000-2024)
  • K8S的Pod为什么可以解析访问集群之外的域名地址
  • (4)什么时候引入Seata‘‘
  • React 组件基础与事件处理