I'm testing out a Chainlink VRF implementation on Arbitrum Sepolia and after calling requestRandomWords
, I don't seem to ever get back a response in the form of a call to fulfillRandomWords
.
I'm able to see that the request goes through from a RandomWordsRequested
event that gets logged - here's one example of a transaction with this log (which also contains other pertinent info like the subscription id, the requesting contract address, etc.)...
As shown in the link above, the address of my consumer contract on Arbitrum Sepolia is 0x8C34303C98cf94b2A332293E0888DFaF9C8ddAec
. This contract both calls requestRandomWords
on the coordinator and implements fulfillRandomWords
as follows...
contract SomeContract is VRFConsumerBaseV2Plus {
function requestRandomWords(uint32 _numWords) internal {
IVRFCoordinatorV2Plus coordinator = IVRFCoordinatorV2Plus(0x5CE8D5A2BC84beb22a398CCA51996F7930313D61);
uint256 requestId = coordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: 0x1770bdc7eec7771f7ba4ffd640f34260d7f095b79c92d34a5b2551d6f6cfd2be,
subId: 115678517865845314128708521410416585367502008983375723740453418397630079470798,
requestConfirmations: 1,
callbackGasLimit: 2500000,
numWords: _numWords,
extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({ nativePayment: false }))
})
);
}
function fulfillRandomWords(
uint256 _requestId,
uint256[] calldata _randomWords
) internal override {
// Do some stuff
}
}
I have confirmed that the consumer is registered under the appropriate subscription id.
The fulfillRandomWords
function is executed successfully in response to your requestIds, and the corresponding RandomWordsFulfilled
events are emitted, as you can see in these transactions:
For request id 70478683367982197649124627064126082699697643183791205784294629728598288438554
: 0x1c06f3489bd9835f2aa39d8d03447b7e1b6a53adc0368f314ff5ce6734f4b578
For request id 88364870351260394125084081620819479699342045779156455279879578975759625912927
: 0x2e00db5126602b7b503f5e8dcf6e0ea65f5ee9ce5eb59eb4df93335ad036df74
As Andrej mentioned in the comments, you can filter the logs of the RandomWordsFulfilled
event by topic1
(i.e., your request id).
Since the block explorer only allows filtering by topic0
(i.e., the keccak256
hash of the event signature), I’ve created this script (using ethers.js
) to filter based on both topic0
and topic1
combined:
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://arbitrum-sepolia-rpc.publicnode.com");
// VRFCoordinatorV2_5 contract address
const contractAddress = "0x5CE8D5A2BC84beb22a398CCA51996F7930313D61";
// ABI of the RandomWordsFulfilled event (only needed to decode logs later)
const abi = [
"event RandomWordsFulfilled(uint256 indexed requestId, uint256 outputSeed, uint256 indexed subId, uint96 payment, bool nativePayment, bool success, bool onlyPremium)"
];
// Compute topic0 (Keccak-256 of event signature)
const eventSignature = "RandomWordsFulfilled(uint256,uint256,uint256,uint96,bool,bool,bool)";
const topic0 = ethers.id(eventSignature); // Hashes event signature
// Convert uint256 to 32-byte hex
const requestId = 88364870351260394125084081620819479699342045779156455279879578975759625912927n;
const topic1 = ethers.zeroPadValue(ethers.toBeHex(requestId), 32); // Convert to 32-byte format
const fromBlock = 128387786; // Block number of the transaction corresponding to your placeBet function call which is calling the requestRandomWords function (https://sepolia.arbiscan.io/tx/0x122f49c3c11fb6626afdf017ccc94ede946f656a1c913a50525070daec67a776)
const toBlock = fromBlock + 50;
// Define filter
const filter = {
address: contractAddress,
fromBlock: fromBlock,
toBlock: toBlock,
topics: [topic0, topic1] // Filtering by topic0 (event type) and topic1 (requestId)
};
(async () => {
try {
// Fetch logs
const logs = await provider.getLogs(filter);
// Decode logs
const iface = new ethers.Interface(abi);
logs.forEach(log => {
const parsedLog = iface.parseLog(log);
console.log("RandomWordsFulfilled Event:", parsedLog.args);
console.log("Transaction Hash:", log.transactionHash);
});
} catch (error) {
console.error("Error fetching logs:", error);
}
})();
You can create a file named vrf_FilterRandomWordsFulfilledEventByRequestId.js
(or any other name you prefer) and paste this script into it. Then, execute the script using the following command:
node vrf_FilterRandomWordsFulfilledEventByRequestId.js
OUTPUT:
NOTE: To receive the random words and reflect them in your contract, you need to store them in a state variable/array or emit them via your contract's event (or maybe do both) in your overridden fulfillRandomWords
function, like this:
event RequestFulfilled(uint256 requestId, uint256[] randomWords);
uint256[] public s_randomWords;
function fulfillRandomWords(
uint256 _requestId,
uint256[] calldata _randomWords
) internal override {
s_randomWords = _randomWords;
emit RequestFulfilled(_requestId, _randomWords);
}
As you can see, the five random words/numbers are shown in this RequestFulfilled
event log, which I emitted in the overridden fulfillRandomWords
function of my VRFTest contract.