Using QRNG - Remix Example
Introduction
QRNG (Quantum Random Number Generator) is a free to use public utility provided by the API3 DAO that provides quantum randomness on-chain. It is powered by Airnode, the first-party oracle that is directly operated by the QRNG API providers. This way, Quantum RNG can be provided on-chain in a trustless manner without the need for a third-party oracle. The QRNG service is currently available on all major EVM compatible chains. Check the list of supported chains here.
Click here to read more about what QRNG is how it works.
This guide will walk you through coding and deploying a smart contract that requests a quantum random number on-chain using API3's QRNG Service.
You will use the browser-based Remix IDE and MetaMask. Some basic knowledge of these two tools is assumed.
Currently, QRNG has three providers, two of which provide quantum random numbers. This guide will use the Testnet Random Numbers, available only on testnets, which returns a pseudorandom number.
1. Coding the Contract
Check your Network!
Make sure you're on a Testnet before trying to deploy the contracts on-chain!
Given below is an example of a basic contract that makes a request to the QRNG Airnode. To follow along, you can open the following contract in Remix and try deploying it yourself. This contract will be requesting the random number directly from the QRNG Provider.
//SPDX-License-Identifier: MIT
pragma solidity 0.8.9;
import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol";
import "@openzeppelin/contracts@4.9.5/access/Ownable.sol";
/// @title Example contract that uses Airnode RRP to access QRNG services
contract QrngExample is RrpRequesterV0, Ownable {
event RequestedUint256(bytes32 indexed requestId);
event ReceivedUint256(bytes32 indexed requestId, uint256 response);
event RequestedUint256Array(bytes32 indexed requestId, uint256 size);
event ReceivedUint256Array(bytes32 indexed requestId, uint256[] response);
event WithdrawalRequested(address indexed airnode, address indexed sponsorWallet);
address public airnode; // The address of the QRNG Airnode
bytes32 public endpointIdUint256; // The endpoint ID for requesting a single random number
bytes32 public endpointIdUint256Array; // The endpoint ID for requesting an array of random numbers
address public sponsorWallet; // The wallet that will cover the gas costs of the request
uint256 public _qrngUint256; // The random number returned by the QRNG Airnode
uint256[] public _qrngUint256Array; // The array of random numbers returned by the QRNG Airnode
mapping(bytes32 => bool) public expectingRequestWithIdToBeFulfilled;
constructor(address _airnodeRrp) RrpRequesterV0(_airnodeRrp) {}
/// @notice Sets the parameters for making requests
function setRequestParameters(
address _airnode,
bytes32 _endpointIdUint256,
bytes32 _endpointIdUint256Array,
address _sponsorWallet
) external {
airnode = _airnode;
endpointIdUint256 = _endpointIdUint256;
endpointIdUint256Array = _endpointIdUint256Array;
sponsorWallet = _sponsorWallet;
}
/// @notice To receive funds from the sponsor wallet and send them to the owner.
receive() external payable {
payable(owner()).transfer(msg.value);
emit WithdrawalRequested(airnode, sponsorWallet);
}
/// @notice Requests a `uint256`
/// @dev This request will be fulfilled by the contract's sponsor wallet,
/// which means spamming it may drain the sponsor wallet.
function makeRequestUint256() external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256.selector,
""
);
expectingRequestWithIdToBeFulfilled[requestId] = true;
emit RequestedUint256(requestId);
}
/// @notice Called by the Airnode through the AirnodeRrp contract to
/// fulfill the request
function fulfillUint256(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256 qrngUint256 = abi.decode(data, (uint256));
_qrngUint256 = qrngUint256;
// Do what you want with `qrngUint256` here...
emit ReceivedUint256(requestId, qrngUint256);
}
/// @notice Requests a `uint256[]`
/// @param size Size of the requested array
function makeRequestUint256Array(uint256 size) external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256Array,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256Array.selector,
// Using Airnode ABI to encode the parameters
abi.encode(bytes32("1u"), bytes32("size"), size)
);
expectingRequestWithIdToBeFulfilled[requestId] = true;
emit RequestedUint256Array(requestId, size);
}
/// @notice Called by the Airnode through the AirnodeRrp contract to
/// fulfill the request
function fulfillUint256Array(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256[] memory qrngUint256Array = abi.decode(data, (uint256[]));
// Do what you want with `qrngUint256Array` here...
_qrngUint256Array = qrngUint256Array;
emit ReceivedUint256Array(requestId, qrngUint256Array);
}
/// @notice Getter functions to check the returned value.
function getRandomNumber() public view returns (uint256) {
return _qrngUint256;
}
function getRandomNumberArray() public view returns(uint256[] memory) {
return _qrngUint256Array;
}
/// @notice To withdraw funds from the sponsor wallet to the contract.
function withdraw() external onlyOwner {
airnodeRrp.requestWithdrawal(
airnode,
sponsorWallet
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
The contract will have eight main functions:
- The
setRequestParameters()
takes inairnode
,endpointIdUint256
,_endpointIdUint256Array
,sponsorWallet
and sets these parameters on-chain.
function setRequestParameters(
address _airnode,
bytes32 _endpointIdUint256,
bytes32 _endpointIdUint256Array,
address _sponsorWallet
) external {
airnode = _airnode;
endpointIdUint256 = _endpointIdUint256;
endpointIdUint256Array = _endpointIdUint256Array;
sponsorWallet = _sponsorWallet;
}
2
3
4
5
6
7
8
9
10
11
- The
makeRequestUint256()
function calls theairnodeRrp.makeFullRequest()
function of theAirnodeRrpV0.sol
protocol contract which adds the request to its storage and emits arequestId
.
function makeRequestUint256() external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256.selector,
""
);
expectingRequestWithIdToBeFulfilled[requestId] = true;
emit RequestedUint256(requestId);
}
2
3
4
5
6
7
8
9
10
11
12
13
- The targeted off-chain QRNG Airnode gathers the request and performs a callback to the
RemixQrngExample
with the random number.
function fulfillUint256(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256 qrngUint256 = abi.decode(data, (uint256));
// Do what you want with `qrngUint256` here...
_qrngUint256 = qrngUint256;
emit ReceivedUint256(requestId, qrngUint256);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- Similarly,
makeRequestUint256Array()
takes in_endpointIdUint256Array
and can be used to request an array of random numbers.
function makeRequestUint256Array(uint256 size) external {
bytes32 requestId = airnodeRrp.makeFullRequest(
airnode,
endpointIdUint256Array,
address(this),
sponsorWallet,
address(this),
this.fulfillUint256Array.selector,
// Using Airnode ABI to encode the parameters
abi.encode(bytes32("1u"), bytes32("size"), size)
);
expectingRequestWithIdToBeFulfilled[requestId] = true;
emit RequestedUint256Array(requestId, size);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- The
fulfillUint256Array()
will be the callback if an array of random numbers is requested.
function fulfillUint256Array(bytes32 requestId, bytes calldata data)
external
onlyAirnodeRrp
{
require(
expectingRequestWithIdToBeFulfilled[requestId],
"Request ID not known"
);
expectingRequestWithIdToBeFulfilled[requestId] = false;
uint256[] memory qrngUint256Array = abi.decode(data, (uint256[]));
// Do what you want with `qrngUint256Array` here...
_qrngUint256Array = qrngUint256Array;
emit ReceivedUint256Array(requestId, qrngUint256Array);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
getRandomNumber()
and andgetRandomNumberArray()
are getter functions that returns the random number and random number array respectively.
function getRandomNumber() public view returns (uint256) {
return _qrngUint256;
}
function getRandomNumberArray() public view returns (uint256[] memory) {
return _qrngUint256Array;
}
2
3
4
5
6
7
withdraw()
is used to request a withdrawal from the QRNG Airnode. The Airnode picks up the request, and sends the funds from thesponsorWallet
to the contract usingreceive()
that inturn sends the funds back to the owner. It calls therequestWithdrawal
function of theairnodeRrp
contract.
receive() external payable {
payable(owner()).transfer(msg.value);
emit WithdrawalRequested(airnode, sponsorWallet);
}
function withdraw() external onlyOwner {
airnodeRrp.requestWithdrawal(
airnode,
sponsorWallet
);
}
2
3
4
5
6
7
8
9
10
11
2. Compiling the Contract
We now need to compile and deploy the contract. Be sure the RemixQrngExample.sol
contract is selected in the FILE EXPLORER tab. Switch to the SOLIDITY COMPILER tab. Select the 0.8.9
version of Solidity from the COMPILER pick list. Select the Compile RemixQrngExample.sol button to compile the contract.
3. Deploying the Contract
Set up your Testnet Metamask Account!
Make sure you've already configured your Metamask wallet and funded it with some testnet ETH before moving forward. You can request some from here➚
This guide will use the Testnet Random Numbers which has the same usage as the production quantum random number generator providers but returns a pseudorandom number.
Switch to the DEPLOY & RUN TRANSACTIONS tab. Use MetaMask and switch to the desired account and testnet for your deployment.
Select the ENVIRONMENT pick list and switch to Injected Web3. Check that the testnet and account you selected in MetaMask are displayed in Remix as shown below.
Be sure
QrngRequester - contracts/QrngRequester.sol
is selected in the CONTRACT pick list.Add the Airnode
_airnodeRrp
address parameter value for the constructor into the field next to the Deploy button. See the list of addresses for the testnet you are using.Click on Deploy and approve the transaction with MetaMask.
4. Setting the Parameters
Testnet Random Numbers Provider
This guide uses Testnet Random Numbers as the QRNG Airnode provider. Testnet Random Numbers emulates QRNG on testnets. If you wish to use QRNG in production, you can use the mainnet QRNG providers instead. Also make sure to check if your particular chain is supported by the QRNG providers. Check which chains are supported here.
Before making a request, parameters must be set. They determine which Airnode endpoint will be called and define the wallet used to pay the gas costs for the response.
Under Deployed Contracts, expand and expose the functions and variables of the contract. Note the address of the contract that is displayed with its name. This is the requester's contract address which will be needed later.
Next, select and expand the setRequestParameters
function to see all the parameters that are needed.
_airnode
: The airnode address of the desired QRNG service provider. Use Testnet Random Numbers (0x6238772544f029ecaBfDED4300f13A3c4FE84E1D
→ ❏ )._endpointIdUint256
: The Testnet Random Numbers Airnode endpoint ID (0x94555f83f1addda23fdaa7c74f27ce2b764ed5cc430c66f5ff1bcf39d583da36
→ ❏ ) which will return a single random number._endpointIdUint256Array
: The Testnet Random Numbers Airnode endpoint ID (0x9877ec98695c139310480b4323b9d474d48ec4595560348a2341218670f7fbc2
→ ❏ ) which will return an array of random numbers._sponsorWallet
: A wallet derived from the Airnode address and the Airnode xpub used by Testnet Random Numbers, and the smart contract address forRemixQrngExample.sol
. The wallet is used to pay gas costs to acquire a random number. A sponsor wallet must be derived using the command derive-sponsor-wallet-address from the Admin CLI. Use the value of the sponsor wallet address that the command outputs. This wallet needs to be funded.shnpx @api3/airnode-admin derive-sponsor-wallet-address \ --airnode-address 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D \ --airnode-xpub xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo \ --sponsor-address <use-the-address-of: RemixQrngExample.sol> # --airnode-address: Airnode address (Testnet Random Numbers provider) # --airnode-xpub: Airnode xpub (Testnet Random Numbers provider) # --sponsor-address: Use the smart contract address for # RemixQrngExample.sol as displayed in the Remix IDE. # The command outputs. Sponsor wallet address: 0x6394...5906757 # Use this address as the value for _sponsorWallet.
1
2
3
4
5
6
7
8
9
10
11
12
13Be sure to fund the public address of the sponsor wallet that the command outputs with enough testnet currency. The funds are used to pay gas costs for the Airnode's response. You can use the table below for the amount of fund as reference.
Funding table reference
Testnet Amount Unit Chain Id Ethereum-Goerli 0.1 ETH 5 Ethereum-Sepolia 0.05 SEP 11155111 RSK testnet 0.001 tRBTC 31 BNB Chain testnet 0.005 tBNB 97 Optimism testnet 0.05 ETH 420 Moonbase Alpha testnet 0.1 DEV 1287 Fantom testnet 0.5 FTM 4002 Avalanche Fuji testnet 0.3 AVAX 43113 Polygon Mumbai testnet 0.05 MATIC 80001 Milkomeda C1 testnet 0.5 mTAda 200101 Arbitrum testnet 0.01 AGOR 421613
Select the Transact button in Remix to send the parameters to the smart contract. Approve the transaction with MetaMask. After the transaction completes you can see each parameter's value by clicking the buttons with a parameter name. These parameters will be used each time the smart contract requests a random number.
Designated Sponsor Wallets
Sponsors should not fund a sponsorWallet
with more than they can trust the Airnode with, as the Airnode controls the private key to the sponsorWallet
. The deployer of such Airnode undertakes no custody obligations, and the risk of loss or misuse of any excess funds sent to the sponsorWallet
remains with the sponsor.
5. Make a Request
Be sure you have funded the sponsor wallet created in the last step. Its funds will be used to pay gas costs when Airnode returns a random number to the callback function fulfillUint256()
.
Each request made will use the parameters set in the last step. You can change the parameters at any time and subsequent requests will use the newer parameter set.
To request a single random number:
To make a request for a single random number, select the makeRequestUint256()
button in Remix. Approve the transaction with MetaMask.
To request an array of random numbers:
To make a request for an array of random numbers, select the makeRequestUint256Array()
button in Remix, add the required array size and click on transact. Approve the transaction with MetaMask.
As soon as the transaction completes in MetaMask, select the getRandomNumber/getRandomNumberArray button in Remix. This will return the value of _qrngUint256
/_qrngUint256Array
which equals 0. This is because the random number/ random number array has yet to be returned to the callback function. Copy and paste the requestId
into the field for expectingRequestWithIdToBeFulfilled
and select the button. You will see the value is true, meaning the callback has not been made.
You might need to wait for a minute or two
The Airnode calls the fulfill() function in AirnodeRrpV0.sol
that will in turn call back the requester contract at fulfillAddress
using function fulfillFunctionId
to deliver data.
Head over to the block explorer and check your sponsorWallet
for any new transactions. Here, you can see the latest Fulfill
transaction.
6. View the Response
The request is gathered by the off-chain Airnode which in turn calls the API provider. Once the API provider returns data, Airnode will callback to the RemixQrngExample.sol
contract function fulfillUint256
/fulfillUint256Array
with the random number/array of random numbers.
select the getRandomNumber/getRandomNumberArray button in Remix again. If the callback has been successfully completed the randomNumber will be present. The value of expectingRequestWithIdToBeFulfilled will be false.
7. Withdrawing Funds from the sponsorWallet
(optional)
You can withdraw funds from the sponsor wallet to address of the owner by calling the withdraw()
function.
The QRNG Airnode listens for withdrawal requests and fulfills them automatically. Therefore, the Requester contract makes a request for withdrawal to the Airnode. The Airnode then fulfills the request, calls the receive()
function in the Requester contract, that sends the funds back to the owner.
receive() external payable {
payable(owner()).transfer(msg.value);
}
2
3
The sponsorWallet
does not get deleted, and can be used in the future simply by funding it again.
Simply click on the withdraw button in Remix and approve the transaction.
Click here to read more about how sponsors, requesters and withdrawals work
Now wait for the QRNG Airnode to fulfill the withdrawal request. You can check the sponsor wallet for any new transactions.
The funds from the sponsorWallet
have been transferred to the owner.