You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现智能合约自动购买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);
    }
}
关键注意事项
  1. 网络参数适配:上面的代码用的是Sepolia测试网参数,部署到主网或其他测试网时,需要替换VRF_COORDINATORLINKKEY_HASHuniswapRouterethLinkPriceFeed的地址。
  2. 滑点控制:通过Chainlink Price Feed动态计算minLinkAmount,既能避免交易因滑点过大失败,又能防止被三明治攻击,你可以根据需求调整滑点比例(比如改成90%允许更大滑点)。
  3. 剩余资金处理:代码中默认将兑换LINK后剩余的ETH退还给用户,你也可以选择留在合约中作为运营资金,只需删除对应的退款逻辑即可。
  4. 合约安全:务必添加权限控制(如onlyOwner),避免恶意调用withdrawFunds等敏感函数;同时要注意处理重入风险,不过Solidity 0.8+默认有溢出检查,transfer函数也能一定程度避免重入。

内容的提问来源于stack exchange,提问作者Alexandru Chiriac

火山引擎 最新活动