帖子
分享您的知识。
尝试铸造需要 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这里最可能的问题是,用于验证智能合约内部签名的消息与 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.
你知道答案吗?
请登录并分享。
Solidity is an object-oriented, high-level language for implementing smart contracts. It is a curly-bracket language designed to target the Ethereum Virtual Machine (EVM).