Solidity.

帖子

分享您的知识。

Christian O'Connor.
May 22, 2023
专家问答

尝试铸造需要 2 个签名的 NFT 并使用 API3 时,我遇到 “验证失败” 错误

我有这份稳固合同:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";

contract RandomSurfaceReachT1 is ERC721URIStorage, Ownable, RrpRequesterV0 {
    event RequestedRandom(bytes32 indexed requestId);
    event MintedRandomNFT(bytes32 indexed requestId, uint256 response);
    event MintCostChanged(uint256 newCost);
    event Withdrawn(address indexed to, uint256 amount);

    address public airnode;
    bytes32 public endpointIdUint256;
    address public sponsorWallet;
    address public authorizedAccount;
    uint256 public tokenCounter;
    uint256 public mintCost = 0.01 ether;
    uint256 public constant MAX_MINTS_PER_ADDRESS = 3;

    enum Classifier {FIRST, SECOND, THIRD}
    mapping(uint256 => Classifier) public tokenIdToClassifier;
    mapping(bytes32 => bool) public awaitingFulfillment;
    mapping(bytes32 => address) public requestToMinter;
    mapping(address => uint256) public minterToMintCount;

    struct RandomNft {
        uint256 nonce;
        address from;
    }

    string public firstUri;
    string public secondUri;
    string public thirdUri;

    string private constant ERR_INVALID_SIGNER = "INVALID_SIGNER";
    string private constant ERR_REQUEST_ID_UNKNOWN = "Request ID not known";
    string private constant ERR_MINT_COST_NOT_MET = "Minting cost not met";
    string private constant ERR_MINT_LIMIT_REACHED = "Mint limit reached";
    string private constant ERR_VERIFICATION_FAILED = "Verification failed";

    constructor(address _airnodeRrp) RrpRequesterV0(_airnodeRrp) ERC721("PRIVATE MINT RANDOM NFT", "PMRNFT") {}

    function setParameters(
        address _airnode,
        bytes32 _endpointIdUint256,
        address _sponsorWallet,
        address _authorizedAccount
    ) external onlyOwner() {
        airnode = _airnode;
        endpointIdUint256 = _endpointIdUint256;
        sponsorWallet = _sponsorWallet;
        authorizedAccount = _authorizedAccount;
    }

    function setURIs(
        string calldata _firstUri,
        string calldata _secondUri,
        string calldata _thirdUri
    ) external onlyOwner() {
        firstUri = _firstUri;
        secondUri = _secondUri;
        thirdUri = _thirdUri;
    }

    function setMintCost(uint256 _newCost) public onlyOwner() {
        mintCost = _newCost;
        emit MintCostChanged(_newCost);
    }

    function requestRandomNFT(
        RandomNft memory nft,
        bytes32 sigR,
        bytes32 sigS,
        uint8 sigV
    ) external payable {
        require(msg.value == mintCost, ERR_MINT_COST_NOT_MET);
        require(minterToMintCount[msg.sender] < MAX_MINTS_PER_ADDRESS, ERR_MINT_LIMIT_REACHED);
        require(verify(authorizedAccount, nft, sigR, sigS, sigV), ERR_VERIFICATION_FAILED);
        bytes32 requestId = airnodeRrp.makeFullRequest(
            airnode,
            endpointIdUint256,
            address(this),
            sponsorWallet,
            address(this),
            this.fulfill.selector,
            ""
        );
        awaitingFulfillment[requestId] = true;
        requestToMinter[requestId] = msg.sender;
        emit RequestedRandom(requestId);
    }

    function fulfill(bytes32 requestId, bytes calldata data)
        external
        onlyAirnodeRrp
    {
        require(awaitingFulfillment[requestId], ERR_REQUEST_ID_UNKNOWN);

        uint256 newId = tokenCounter++;
        uint256 randomUint256 = abi.decode(data, (uint256));
        Classifier classifier = Classifier(randomUint256 % 3);
        
        tokenIdToClassifier[newId] = classifier;
        _safeMint(requestToMinter[requestId], newId);
        minterToMintCount[requestToMinter[requestId]]++;
        awaitingFulfillment[requestId] = false;

        if (classifier == Classifier.FIRST) {
            _setTokenURI(newId, firstUri);
        } else if (classifier == Classifier.SECOND) {
            _setTokenURI(newId, secondUri);
        } else if (classifier == Classifier.THIRD) {
            _setTokenURI(newId, thirdUri);
        }

        emit MintedRandomNFT(requestId, randomUint256);
    }

    function verify(
        address signer,
        RandomNft memory nft,
        bytes32 sigR,
        bytes32 sigS,
        uint8 sigV
    ) internal pure returns (bool) {
        require(signer != address(0), ERR_INVALID_SIGNER);
        return
            signer ==
            ecrecover(
                keccak256(abi.encode(nft.nonce, nft.from)),
                sigV,
                sigR,
                sigS
            );
    }

    function withdraw() external onlyOwner() {
        uint balance = address(this).balance;
        payable(owner()).transfer(balance);
        emit Withdrawn(owner(), balance);
    }
}

Notice that it uses API3 to select a random Classifier for the minted NFT from 3 Classification choices. Also an external signature is required. This is so I can have a next.js app with a private key on the backend that enables the website to be the sole source of NFT mints. I deployed the contract then the derived my sponsor wallet with the following command (P.S. my deployed contract instance is 0x8Ca82f3b509F18e79a4880415Df0cCB9807FA39a):

npx @api3/airnode-admin derive-sponsor-wallet-address --airnode-xpub xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo --airnode-address 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D --sponsor-address 0x8Ca82f3b509F18e79a4880415Df0cCB9807FA39a

This output 0x320ce404b4e9a0ab44e890a91162109bf5b8fe80 as the sponsor wallet.

I ran the following function with hardhat:

const Token = await ethers.getContractFactory("RandomSurfaceReachT1");
const token = await Token.attach("0x8Ca82f3b509F18e79a4880415Df0cCB9807FA39a");

// API3 Nodary address: 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D
// API3 Nodary RPC Connect String: 0xfb6d017bb87991b7495f563db3c8cf59ff87b09781947bb1e417006ad7f55a78
// address for private key that I'm going to use for signing 0xBcc0785B2Fe7e8E2875E8Ee110EBE9A3d948f6D2

await token.setParameters("0x6238772544f029ecaBfDED4300f13A3c4FE84E1D", "0xfb6d017bb87991b7495f563db3c8cf59ff87b09781947bb1e417006ad7f55a78", "0x320ce404b4e9a0ab44e890a91162109bf5b8fe80", "0xBcc0785B2Fe7e8E2875E8Ee110EBE9A3d948f6D2");
await token.setURIs("ipfs://bafybeif7eum33srxx2eeh73vzpzaeidcz4olkak644kcduto3vtnon7bya/firstBlue.json", "ipfs://bafybeif7eum33srxx2eeh73vzpzaeidcz4olkak644kcduto3vtnon7bya/secondGreen.json", "ipfs://bafybeif7eum33srxx2eeh73vzpzaeidcz4olkak644kcduto3vtnon7bya/thirdRed.json");

This is a repo to my next.js app https://github.com/ChristianOConnor/nft-sig-verify-mint-page-pub. My .env.local file looks like this:

NODE_ENV=development
NEXT_PUBLIC_CONTRACTS_ADDRESS='0x8Ca82f3b509F18e79a4880415Df0cCB9807FA39a'
PRIVATE_KEY_FOR_SIGNING='PRIVATE KEY FOR 0xBcc0785B2Fe7e8E2875E8Ee110EBE9A3d948f6D2'
I replaced PRIVATE_KEY_FOR_SIGNING with a placeholder.

When I connect my wallet and click "Mint Now!" I get this in my web browser console:

Browser:
ContractFunctionExecutionError: The contract function "requestRandomNFT" reverted with the following reason:
Verification failed

Contract Call:
  address:   0x8Ca82f3b509F18e79a4880415Df0cCB9807FA39a
  function:  requestRandomNFT((uint256 nonce, address from), bytes32 sigR, bytes32 sigS, uint8 sigV)
  args:                      ({"nonce":7121712914477423,"from":"0x41Ae1A06481FFf8DaE7dBBB90508A0fe50632449"}, 0x04d42a0436d2d5122cbebaca45e89ffef1c221e2c283a7f5216e3129d430271d, 0x3fe532453c5e08a757cc6a3ac4787714f53a9b537a58c118ad0763afe00e73a4, 28)
  sender:    0x41Ae1A06481FFf8DaE7dBBB90508A0fe50632449

Docs: https://viem.sh/docs/contract/simulateContract.html
Version: viem@0.3.31
    at Module.getContractError (getContractError.js:24:12)
    at Module.simulateContract (simulateContract.js:40:15)
    at async Module.prepareWriteContract (chunk-NRSD7F2O.js:2075:31)

And this is the output of (prepareError || error)?.message:

Error: The contract function "requestRandomNFT" reverted with the following reason: Verification failed Contract Call: address: 0x8Ca82f3b509F18e79a4880415Df0cCB9807FA39a function: requestRandomNFT((uint256 nonce, address from), bytes32 sigR, bytes32 sigS, uint8 sigV) args: ({"nonce":7121712914477423,"from":"0x41Ae1A06481FFf8DaE7dBBB90508A0fe50632449"}, 0x04d42a0436d2d5122cbebaca45e89ffef1c221e2c283a7f5216e3129d430271d, 0x3fe532453c5e08a757cc6a3ac4787714f53a9b537a58c118ad0763afe00e73a4, 28) sender: 0x41Ae1A06481FFf8DaE7dBBB90508A0fe50632449 Docs: https://viem.sh/docs/contract/simulateContract.html Version: viem@0.3.31

我现在如何获得铸币厂!成功铸造 NFT 的按钮?

  • Smart Contract
  • Solidity
1
1
分享
评论
.

答案

1
Sergey Ilin.
May 23 2023, 18:25

这里最可能的问题是,用于验证智能合约内部签名的消息与 API 签名的消息不同.

最简单的调试方法可能是你传递的简单视图操作nonce and from arguments that calculates and returns a value of keccak256(abi.encode(nonce, from)). 这个哈希值必须与这里由 API 签名的消息相匹配-https://github.com/ChristianOConnor/nft-sig-verify-mint-page-pub/blob/cec00589cb3929118087873a99d80383875d0367/src/app/api/authorizor/route.ts#LL10C1-L11C1

API 调用的事实 JSON.stringify(message) makes me believe that they will not match. Contract serializes nonce and form using RLP encoding. ethers js has a function for that ethers.utils.RLP.encode-https://docs.ethers.org/v5/api/utils/encoding/#utils-rlpEncode.

0
最佳答案
评论
.

你知道答案吗?

请登录并分享。

我们使用 cookie 确保您在我们的网站上获得最佳体验。
更多信息