如何实现智能合约自动购买LINK(或其他ERC20)代币?Chainlink VRF场景下自动LINK充值方案咨询
这个需求太贴合实际了——谁都不想让用户额外去折腾LINK代币的购买和转账,咱们直接把「ETH转LINK」的逻辑嵌进合约里,让用户只用付ETH就能完成抽牌+VRF请求的全流程。下面给你一个可运行的实现方案,包含核心思路、代码示例和关键注意事项:
核心逻辑拆解
- 用户向Coordinator合约支付ETH作为抽牌费用
- 合约自动将ETH通过去中心化交易所(比如Uniswap V2)兑换成足够支付VRF手续费的LINK
- 用兑换得到的LINK直接支付Chainlink VRF的请求手续费
- 触发VRF随机数请求,后续用返回的随机数完成抽牌逻辑
具体实现步骤&代码示例
我们选用Chainlink VRF V1(单次支付模式,更适合自动用ETH兑换LINK支付手续费的场景)结合Uniswap V2来实现,下面是完整的Solidity合约代码(以Sepolia测试网为例,参数可按需调整):
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol"; import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; contract CardDrawCoordinator is VRFConsumerBase { // -------------------------- 网络配置参数(Sepolia测试网) -------------------------- // Chainlink VRF Coordinator地址 address public constant VRF_COORDINATOR = 0x8C7382F9D8f56b33781fE506E897a4F1e2d17255; // LINK代币地址 address public constant LINK = 0x779877A7B0D9E8603169DdbD7836e478b4624789; // VRF KeyHash(用于指定随机数生成器) bytes32 public constant KEY_HASH = 0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c; // VRF请求手续费(0.0001 LINK) uint256 public constant VRF_FEE = 100000000000000; // Uniswap V2 Router地址 IUniswapV2Router02 public immutable uniswapRouter; // ETH/LINK价格Feed地址(用于动态计算滑点) AggregatorV3Interface public immutable ethLinkPriceFeed; // -------------------------- 抽牌状态管理 -------------------------- mapping(uint256 => address) public requestToPlayer; event CardDrawn(uint256 requestId, address player, uint256 cardId); // 合约初始化 constructor(address _uniswapRouter, address _ethLinkFeed) VRFConsumerBase(VRF_COORDINATOR, LINK) { uniswapRouter = IUniswapV2Router02(_uniswapRouter); ethLinkPriceFeed = AggregatorV3Interface(_ethLinkFeed); // 授权VRF Coordinator可以转移合约中的LINK(用于支付手续费) IERC20(LINK).approve(VRF_COORDINATOR, type(uint256).max); } // 用户调用此函数支付ETH抽牌 function drawCard() external payable { require(msg.value > 0, "请支付ETH用于抽牌"); // 1. 将ETH兑换成足够支付VRF手续费的LINK uint256 linkReceived = swapEthForLink(msg.value); require(linkReceived >= VRF_FEE, "兑换的LINK不足以支付VRF手续费"); // 2. 请求Chainlink VRF随机数 uint256 requestId = requestRandomness(KEY_HASH, VRF_FEE); requestToPlayer[requestId] = msg.sender; // 3. 将剩余ETH退还给用户(可选,根据需求调整) uint256 remainingEth = address(this).balance; if (remainingEth > 0) { payable(msg.sender).transfer(remainingEth); } } // ETH兑换LINK的核心函数(带滑点保护) function swapEthForLink(uint256 ethAmount) internal returns (uint256 linkReceived) { // 构建兑换路径:ETH → WETH → LINK(Uniswap V2要求ETH先转WETH) address[] memory path = new address[](2); path[0] = uniswapRouter.WETH(); path[1] = LINK; // 动态计算最小接收LINK数量(允许5%滑点,避免三明治攻击) uint256 minLinkAmount = calculateMinLinkAmount(ethAmount); // 调用Uniswap Router完成兑换 uint256[] memory amounts = uniswapRouter.swapExactETHForTokens{value: ethAmount}( minLinkAmount, path, address(this), block.timestamp + 300 // 交易截止时间(5分钟) ); return amounts[1]; } // 利用Chainlink Price Feed计算最小接收LINK数量 function calculateMinLinkAmount(uint256 ethAmount) internal view returns (uint256) { (, int256 price, , , ) = ethLinkPriceFeed.latestRoundData(); // price单位是8位小数,表示1 ETH可兑换的LINK数量 uint256 linkAmount = (ethAmount * uint256(price)) / 1e18; // 允许5%滑点 return linkAmount * 95 / 100; } // VRF随机数回调函数,处理抽牌逻辑 function fulfillRandomness(uint256 requestId, uint256 randomness) internal override { address player = requestToPlayer[requestId]; require(player != address(0), "无效的请求ID"); // 示例:用随机数从52张牌中抽选 uint256 cardId = randomness % 52; emit CardDrawn(requestId, player, cardId); // 这里可以添加将抽到的牌转移给玩家、更新玩家卡组状态等逻辑 } // 紧急提取合约中多余的ETH/LINK(仅合约所有者可调用) function withdrawFunds() external onlyOwner { payable(owner()).transfer(address(this).balance); uint256 linkBalance = IERC20(LINK).balanceOf(address(this)); IERC20(LINK).transfer(owner(), linkBalance); } }
关键注意事项
- 网络参数适配:上面的代码用的是Sepolia测试网参数,部署到主网或其他测试网时,需要替换
VRF_COORDINATOR、LINK、KEY_HASH、uniswapRouter和ethLinkPriceFeed的地址。 - 滑点控制:通过Chainlink Price Feed动态计算
minLinkAmount,既能避免交易因滑点过大失败,又能防止被三明治攻击,你可以根据需求调整滑点比例(比如改成90%允许更大滑点)。 - 剩余资金处理:代码中默认将兑换LINK后剩余的ETH退还给用户,你也可以选择留在合约中作为运营资金,只需删除对应的退款逻辑即可。
- 合约安全:务必添加权限控制(如
onlyOwner),避免恶意调用withdrawFunds等敏感函数;同时要注意处理重入风险,不过Solidity 0.8+默认有溢出检查,transfer函数也能一定程度避免重入。
内容的提问来源于stack exchange,提问作者Alexandru Chiriac




