You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何限制仅能通过自研dApp进行NFT mint,禁止直接调用智能合约铸造

ERC721 铸造权限限制(仅允许自有dApp铸造)实现方案

核心逻辑是「后端授权签名 + 合约校验签名」:你需要在后端维护一个仅自己持有私钥的签名地址,用户在dApp发起铸造时先向后端请求合法签名,调用合约mint方法时必须携带该签名,合约校验签名为授权地址签发才允许铸造。用户直接调用合约时无法获得合法签名,会被直接拦截。

1. Node.js 后端签名服务实现

首先安装ethers依赖:
npm install ethers
将签名私钥存在.env环境变量中,禁止硬编码泄露,核心签名逻辑示例:

const { ethers } = require("ethers");
require('dotenv').config();

// 签名私钥,仅后端可访问
const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY;
const signer = new ethers.Wallet(SIGNER_PRIVATE_KEY);

// 对外提供的签名接口逻辑
async function generateMintSignature(userAddress, tokenId, deadline) {
    // 构造消息哈希,参数顺序、类型必须和合约完全一致
    const messageHash = ethers.utils.solidityKeccak256(
        ["address", "uint256", "uint256"],
        [userAddress, tokenId, deadline]
    );
    // 生成符合EIP-191标准的签名,避免恶意签名风险
    const signature = await signer.signMessage(ethers.utils.arrayify(messageHash));
    return signature;
}

2. React 前端交互实现

前端仅负责传递参数、获取签名、调用合约,不要接触签名私钥:

import { ethers } from "ethers";

// 初始化钱包provider
const provider = new ethers.providers.Web3Provider(window.ethereum);
const userSigner = provider.getSigner();
const NFT_CONTRACT_ADDRESS = "你的合约地址";
const NFT_CONTRACT_ABI = []; // 你的合约ABI

const handleMint = async () => {
    // 获取当前用户钱包地址
    const userAddress = await userSigner.getAddress();
    // 生成铸造参数,tokenId可根据自己业务逻辑生成
    const tokenId = 100;
    // 签名有效期10分钟,防止签名被长期复用
    const deadline = Math.floor(Date.now() / 1000) + 10 * 60;

    // 请求后端获取签名
    const signRes = await fetch("/你的后端签名接口地址", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ userAddress, tokenId, deadline })
    });
    const { signature } = await signRes.json();

    // 携带签名调用合约mint方法
    const nftContract = new ethers.Contract(NFT_CONTRACT_ADDRESS, NFT_CONTRACT_ABI, userSigner);
    const mintTx = await nftContract.mint(userAddress, tokenId, deadline, signature);
    await mintTx.wait();
    alert("NFT铸造成功");
}

3. Solidity 合约签名校验实现

使用OpenZeppelin的ECDSA工具库做签名校验,合约示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract CustomNFT is ERC721 {
    using ECDSA for bytes32;
    // 授权签名地址,部署合约时传入后端签名私钥对应的公钥地址
    address public immutable authorizedSigner;
    // 可选:记录已使用的签名,防止重放攻击
    mapping(bytes32 => bool) public usedSignature;

    constructor(address _authorizedSigner) ERC721("MyNFT", "MNFT") {
        authorizedSigner = _authorizedSigner;
    }

    function mint(
        address to,
        uint256 tokenId,
        uint256 deadline,
        bytes calldata signature
    ) external {
        // 校验签名有效期
        require(block.timestamp <= deadline, "签名已过期");
        // 构造和后端完全一致的消息哈希
        bytes32 msgHash = keccak256(abi.encodePacked(to, tokenId, deadline));
        // 匹配EIP-191签名前缀规则
        bytes32 ethSignedHash = msgHash.toEthSignedMessageHash();
        // 可选:校验签名未被使用过
        require(!usedSignature[ethSignedHash], "签名已被使用");
        // 恢复签名者地址,校验是否为授权地址
        require(ethSignedHash.recover(signature) == authorizedSigner, "签名无效");
        
        // 校验通过,标记签名已使用
        usedSignature[ethSignedHash] = true;
        // 执行铸造
        _safeMint(to, tokenId);
    }
}

注意事项

  • 后端签名私钥必须严格保密,建议存放在云加密服务或者环境变量中,绝对不能提交到代码仓库、也不能暴露在前端代码中
  • 后端、前端、合约三者的签名参数顺序、类型必须完全一致,否则会出现签名校验失败的问题
  • 强烈建议同时加deadline有效期和签名使用记录两个规则,避免签名被截获后复用
  • 后端签名接口可以叠加自己的业务校验逻辑,比如校验用户是否在白名单、是否已经铸造过、是否符合铸造条件,符合要求才返回签名,从源头控制铸造权限

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 21:27:04