diff --git a/docs/guides/airnode/calling-an-airnode/index.md b/docs/guides/airnode/calling-an-airnode/index.md index c0c2da2d..8382b0bd 100644 --- a/docs/guides/airnode/calling-an-airnode/index.md +++ b/docs/guides/airnode/calling-an-airnode/index.md @@ -378,3 +378,47 @@ sponsor wallet for any new transactions. The funds from the `sponsorWallet` have been transferred to the owner. + +## 5. Using HTTP Gateways (optional) + +You can use +[HTTP Gateways](/reference/airnode/latest/understand/http-gateways.html) to make +requests to the Airnode off-chain. HTTP Gateways are usually used to test and +ensure if the Airnode is configured properly and working as expected without +having the need to make a request on-chain. + +[Read more about HTTP Gateways here.](/reference/airnode/latest/understand/http-gateways.html) + +Once you deploy an Airnode, the HTTP Gateway and the HTTP Signed data Gateway +URLs are displayed on the terminal at the end of the deployment process. You can +use these URLs to make HTTP requests to the Airnode. + +For this example, we are going to use the Coingecko's Airnode HTTP Gateway URL +to to make a request to the Airnode. You can use a tool like +[Postman](https://www.postman.com/) or CURL to make a POST request. + +To make a request for the HTTP Gateway, you need to make a POST request to the +HTTP Gateway URL followed by the endpoint ID along with the following parameters +defined in the request body. + +```bash +curl \ +-X POST \ +-H 'Content-Type: application/json' \ +-d '{"parameters": {"vs_currencies": "usd", "ids": "bitcoin"}}' \ +'https://v5smo0rs75.execute-api.us-east-1.amazonaws.com/v1/d8c1ffa0-6e08-a88c-c999-a31f2acc2c22/0x5dbf4e8c53ad4ffdec277ec4df847e6272597fd851f147c93208e0cff99df72d' +``` + +Similarly, for a HTTP Signed data Gateway request, you need to pass in the +encoded parameters defined as `encodedParameters` in the request body. You can +use the `@api3/airnode-abi` library to encode the parameters like previously +shown. + +```bash +curl \ +-X POST \ +-H 'Content-Type: application/json' \ +-d '{"encodedParameters": "0x315353535300000000000000000000000000000000000000000000000000000076735f63757272656e63696573000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120696473000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001605f7061746800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a05f7479706500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000000375736400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007626974636f696e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b626974636f696e2e7573640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006696e743235360000000000000000000000000000000000000000000000000000"}' \ +'https://p9a26cqzo9.execute-api.us-east-1.amazonaws.com/v1/1f5dddde-02c6-2712-b941-2ec24f0113dc/0x5dbf4e8c53ad4ffdec277ec4df847e6272597fd851f147c93208e0cff99df72d' + +``` diff --git a/docs/guides/airnode/setting-up-airnode/index.md b/docs/guides/airnode/setting-up-airnode/index.md new file mode 100644 index 00000000..0833ae24 --- /dev/null +++ b/docs/guides/airnode/setting-up-airnode/index.md @@ -0,0 +1,171 @@ +--- +title: Setting up an Airnode with your API +sidebarHeader: Guides +sidebarSubHeader: +pageHeader: Guides → Setting up an Airnode with your API +path: /guides/airnode/setting-up-airnode/ +outline: deep +tags: + - airnode + - setting up airnode + - configuring airnode +--- + + + + + + + +# {{$frontmatter.title}} + +## Introduction + +[Airnode](/reference/airnode/latest/concepts/airnode.md) is a first-party oracle +node deployed and operated by the API provider to provide any data from their +REST API on-chain. It is a lightweight, serverless, and cost-efficient oracle +node that can be deployed in minutes. + +This guide will walk you through the steps to set up an Airnode with your own +REST API. + +## ChainAPI + +[ChainAPI](https://chainapi.com/) is a platform that enables you to integrate +and deploy the open-source [Airnode](/reference/airnode/latest/understand/) with +its step-by-step integration and deployment tools. It helps streamline the +Airnode integration process for API providers. + +Using ChainAPI, API providers can configure and deploy an Airnode which allows +them to provide their API data to multiple chains. + +ChainAPI can connect almost any API, whether open or authenticated, to Airnode. +Airnode then queries your API operations to be consumed by EVM on-chain dApps, +by using the [Request-Response Protocol](/reference/airnode/latest/concepts/). + +To get started, go to [ChainAPI](https://chainapi.com/) and login using +[MetaMask](https://metamask.io/). + + + +Sign in with your MetaMask account. If you don't have a MetaMask account, you +can create one by following the instructions +[here](https://metamask.io/download.html). + +After registering, you will be redirected to the ChainAPI dashboard. + + + +## Creating a New Integration + +To create a new integration, click on the **Integrate** button on the top left +corner of the dashboard and select **Integrate API**. + + + +Enter your API name, select the category and description and click on +**Create**. + + + +Enter the base URL for your API and click on **Next**. You can also add in +support for security schemes like API keys or an HTTP bearer token by clicking +on **Add Security Scheme**. + + + +You will now be redirected to the next step where you can add your API +endpoints. For each endpoint you want to add, click on **Add Endpoint** and +enter the endpoint name, path, method, and description. + + + +You can now add all the parameters for your endpoint. Click on **Add parameter** +and enter the parameter name and type(query/header/path/cookie). You can also +define the parameter as a user-defined or a fixed value. You can add multiple +parameters for each endpoint by clicking on **Add parameter**. + + + +[Reserved parameters](/reference/ois/latest/reserved-parameters) define what +part of the response is to be picked and encoded before fulfillment. It can be +defined by the requester or can also be hardcoded in the Airnode configuration. +To hardcode the reserved parameters, click on **Reserved Parameters** to define +the parameter's `_name`, `_path` and `_times`. + +[Pre/Post Processing](/reference/ois/latest/processing) allows you to specify +snippets of Javascript code to be run before or after a request is made to your +Airnode. Click on **Advanced** and select **Add Pre-processing** or **Add +Post-processing** to add your snippets. + +- Pre-processing snippets are executed before making the request to the Airnode. +- Post-processing snippets are executed after receiving the response from the + Airnode. + +To read more about pre and post-processing, click +[here](/reference/ois/latest/processing). + +After adding all the required endpoints and parameters, click on **Finish**. + + + +## Setting up a Deployment + +To set up a new deployment for your Airnode, click on the **Deploy** button on +the top left corner of the dashboard and select **New Deployment**. + + + +Enter your deployment name and select the integration you would want to use with +the deployment. Click on **Next**. + + + +Select the cloud provider you would want to use for your deployment. You can +choose between AWS or GCP. Choose the region and the Airnode version and click +on **Next**. + + + +Select the chains you would want to use your Airnode on. You can choose any of +the supported chains (Mainnets/Testnets) with up to 10 custom providers for each +chain. Click on **Next** after selecting the chains and the number of providers. + + + +[Authorizer](/reference/airnode/latest/concepts/authorizers) contracts allow you +to specify which smart contracts can make requests to your Airnode’s endpoints. + +When an Airnode receives a request, it can use on-chain authorizer contracts to +verify if a response is warranted. This allows the Airnode to implement a wide +variety of policies and to authorize requester contract access to its underlying +API. + +- Public Authorizers will allow any smart contract to make requests to your + Airnode. +- Restricted Authorizers will only allow smart contract addresses that have been + granted access to make requests to your Airnode. + +To read more about authorizers, click +[here](/reference/airnode/latest/concepts/authorizers). + +For basic deployments, you can keep the authorizer as **Public**. Click on +**Next**. + + + +Review your configuration for the final time and click on **Next**. + + + +You will now be redirected to download the configuration files for your Airnode. +Click on **Download Files** to download the configuration file. + + + +You can now deploy your Airnode using the configuration file. You can either +refer to the steps on-screen or follow the deployment guide +[here](/guides/airnode/deploy-airnode/deploy-aws/). + +Once you are done deploying your Airnode, you can click on **Finish Deployment** +to view your deployment status. diff --git a/docs/guides/airnode/setting-up-airnode/src/chainapi-dashboard.png b/docs/guides/airnode/setting-up-airnode/src/chainapi-dashboard.png new file mode 100644 index 00000000..e3120f63 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/chainapi-dashboard.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/chainapi.png b/docs/guides/airnode/setting-up-airnode/src/chainapi.png new file mode 100644 index 00000000..ef557144 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/chainapi.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-2.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-2.png new file mode 100644 index 00000000..e4bacd35 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-2.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-3.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-3.png new file mode 100644 index 00000000..b00b418e Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-3.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-4.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-4.png new file mode 100644 index 00000000..ba0c453a Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-4.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-5.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-5.png new file mode 100644 index 00000000..014340ea Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-5.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-6.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-6.png new file mode 100644 index 00000000..46c34172 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-6.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-7.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-7.png new file mode 100644 index 00000000..17497170 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard-7.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard.png b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard.png new file mode 100644 index 00000000..102559fb Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/deployments-dashboard.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-2.png b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-2.png new file mode 100644 index 00000000..358b32b9 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-2.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-3.png b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-3.png new file mode 100644 index 00000000..487b6220 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-3.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-4.png b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-4.png new file mode 100644 index 00000000..525fff44 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-4.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-5.png b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-5.png new file mode 100644 index 00000000..643192a5 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-5.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-6.png b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-6.png new file mode 100644 index 00000000..a3d7e7d6 Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard-6.png differ diff --git a/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard.png b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard.png new file mode 100644 index 00000000..815cc26d Binary files /dev/null and b/docs/guides/airnode/setting-up-airnode/src/integrations-dashboard.png differ diff --git a/docs/guides/qrng/lottery-guide/index.md b/docs/guides/qrng/lottery-guide/index.md deleted file mode 100644 index 684d4287..00000000 --- a/docs/guides/qrng/lottery-guide/index.md +++ /dev/null @@ -1,362 +0,0 @@ ---- -title: Building a Lottery with QRNG -sidebarHeader: Guides -sidebarSubHeader: -pageHeader: Guides → QRNG -path: /guides/qrng/lottery-guide/index.html -outline: deep -tags: ---- - - - - - - - -# {{$frontmatter.title}} - -This is a simple tutorial that will walk you through building and deploying a -decentralized lottery smart contract in Solidity using Remix to demonstrate the -use of [API3's QRNG](/reference/qrng/) service. You will use the browser-based -[Remix IDE](https://remix.ethereum.org) and [MetaMask](https://metamask.io/). -Some basic knowledge of these two tools is assumed. - -Currently, QRNG has three [providers](/reference/qrng/providers.md), two of -which provide quantum random numbers. This guide will use the -[Testnet Random Numbers](/reference/qrng/providers#testnet-random-numbers), -available only on testnets, which returns a pseudorandom number. - -Anyone can choose a number 1–10,000 and buy a ticket to enter into a weekly -lottery. The ticket revenue is collected into a pot in the contract. After 7 -days, the contract will allow anyone to trigger the drawing. - -The contract will then call the API3 QRNG for a truly random number generated by -quantum mechanics. The pot will be split amongst all users that chose this -winning number. If there are no winners, the pot will be rolled over to the next -week. Once deployed, the lottery will continue to run and operate itself -automatically without any controlling parties. - -## 1. Coding the `Lottery` Contract - -::: warning Check your Network! - -Make sure you're on a Testnet before trying to deploy the contracts on-chain! - -::: - -> The complete contract code can be found -> [here](https://github.com/camronh/Lottery-Tutorial/blob/main/contracts/Lottery.sol) - -Head on to [Remix online IDE](https://remix.ethereum.org) using a browser that -you have added Metamask support to. Not all browsers support -[MetaMask](https://metamask.io/download/). - -It should load up the `Lottery` contract. - -[Open in Remix](https://remix.ethereum.org/#url=https://raw.githubusercontent.com/api3-ecosystem/remix-contracts/master/contracts/Lottery.sol&lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.22+commit.4fc1097e.js) - -![Add Contract](/guides/qrng/lottery-guide/src/lottery1.png) - -As a requester, our `Lottery.sol` contract will make requests to an Airnode, -specifically the API3 QRNG, using the -[Request-Response Protocol (RRP)](/reference/qrng/airnode-rrp-v0.md). It may be -helpful to take a little time to familiarize yourself if you haven't already. - -- You first need to define all the global variables - `pot`, `ticketPrice`, - `week`, `endTime`, `MAX_NUMBER`, `airnodeAddress`, `endpointId` and - `sponsorWallet`. - - ```Solidity - // Global Variables - uint256 public pot = 0; // total amount of ether in the pot - uint256 public ticketPrice = 0.0001 ether; // price of a single ticket - uint256 public week = 1; // current week counter - uint256 public endTime; // datetime that current week ends and lottery is closable - uint256 public constant MAX_NUMBER = 10000; // highest possible number - address public constant airnodeAddress = 0x9d3C147cA16DB954873A498e0af5852AB39139f2; - bytes32 public constant endpointId = 0x94555f83f1addda23fdaa7c74f27ce2b764ed5cc430c66f5ff1bcf39d583da36; - address payable public sponsorWallet; - ``` - -- Add the constructor function that will take the `_airnodeRrpAddress` - -- When deploying the contract, you need to pass in a datetime that the lottery - will end. After the lottery ends, the next week will begin and will end 7 days - after the original `endTime`. - - ```Solidity - /// @notice Initialize the contract with a set day and time of the week winners can be chosen - /// @param _endTime Unix time when the lottery becomes closable - constructor(uint256 _endTime, address _airnodeRrpAddress) - RrpRequesterV0(_airnodeRrpAddress) - { - if (_endTime <= block.timestamp) revert EndTimeReached(_endTime); - endTime = _endTime; // store the end time of the lottery - } - ``` - -The `Lottery` contract will have five main functions: `setSponsorWallet()`, -`enter()`, `getWinningNumber()`, `closeWeek()` and `getEntriesForNumber()`. - -- The `setSponsorWallet()` function will set the address of the - [Sponsor Wallet](/reference/airnode/latest/concepts/sponsor.md#sponsorwallet). - We will need to fund this wallet later. - - ```Solidity - function setSponsorWallet(address payable _sponsorWallet) - external - onlyOwner - { - sponsorWallet = _sponsorWallet; - } - ``` - -- The `enter()` function will take in `_number` as a parameter. It will be the - participant's chosen lottery number for which they're buying a ticket. It will - then add the user's address to list of entries for their number under the - current week and add the funds to the `pot`. - - ```solidity - function enter(uint256 _number) external payable { - require(_number <= MAX_NUMBER, "Number must be 1-MAX_NUMBER"); // guess has to be between 1 and MAX_NUMBER - if (block.timestamp >= endTime) revert EndTimeReached(endTime); // lottery has to be open - require(msg.value == ticketPrice, "Ticket price is 0.0001 ether"); // user needs to send 0.0001 ether with the transaction - tickets[week][_number].push(msg.sender); // add user's address to list of entries for their number under the current week - pot += ticketPrice; // account for the ticket sale in the pot - } - ``` - -Users can call this function with a number 1-10000 and a value of 0.001 ether to -buy a lottery ticket. The user's address is added to the addresses array in the -`tickets` mapping. - -- The `getWinningNumber()` function will be used to make the request for - randomness. It calls the `airnodeRrp.makeFullRequest()` function of the - `AirnodeRrpV0.sol` protocol contract which adds the request to its storage and - emits a `requestId`. - - ```Solidity - function getWinningNumber() external payable { - // require(block.timestamp > endTime, "Lottery has not ended"); // not available until end time has passed - require(msg.value >= 0.01 ether, "Please top up sponsor wallet"); // user needs to send 0.01 ether with the transaction - bytes32 requestId = airnodeRrp.makeFullRequest( - airnodeAddress, - endpointId, - address(this), // Use the contract address as the sponsor. This will allow us to skip the step of sponsoring the requester - sponsorWallet, - address(this), // Return the response to this contract - this.closeWeek.selector, // Call this function with the response - "" // No params - ); - pendingRequestIds[requestId] = true; // Store the request id in the pending request mapping - emit RequestedRandomNumber(requestId); // Emit an event that the request has been made - sponsorWallet.call{value: msg.value}(""); // Send funds to sponsor wallet - } - ``` - -- The off-chain QRNG Airnode gathers the request and performs a callback to the - contract with the random number. Here, the `closeWeek()` function fulfills the - request, gets the random number and assigns it as the winning number for that - week. - -- It then checks from an array of addresses who participated in the lottery, - divides the pot evenly among the winners and sends it to each winner. - - ```solidity - function closeWeek(bytes32 requestId, bytes calldata data) - external - onlyAirnodeRrp - { - require(pendingRequestIds[requestId], "No such request made"); - delete pendingRequestIds[requestId]; // remove request id from pending request ids - - uint256 _randomNumber = abi.decode(data, (uint256)) % MAX_NUMBER; // get the random number from the data - emit ReceivedRandomNumber(requestId, _randomNumber); // emit the random number as an event - - // require(block.timestamp > endTime, "Lottery is open"); // will prevent duplicate closings. If someone closed it first it will increment the end time and not allow - - winningNumber[week] = _randomNumber; - address[] memory winners = tickets[week][_randomNumber]; // get list of addresses that chose the random number this week - unchecked { - ++week; // increment week counter, will not overflow on human timelines - } - endTime += 7 days; // set end time for 7 days later - if (winners.length > 0) { - uint256 earnings = pot / winners.length; // divide pot evenly among winners - pot = 0; // reset pot - for (uint256 i = 0; i < winners.length; ) { - payable(winners[i]).call{value: earnings}(""); // send earnings to each winner - unchecked { - ++i; - } - } - } - } - ``` - -The `getEntriesForNumber()` is a read only function that returns the list of -addresses that chose the given number for the given week. - -```Solidity -function getEntriesForNumber(uint256 _number, uint256 _week) public view returns (address[] memory) { - return tickets[_week][_number]; -} -``` - -The -[`receive()`](https://docs.soliditylang.org/en/v0.8.14/contracts.html#receive-ether-function) -function will be called if funds are sent to the contract. In this case, we need -to add these funds to the pot. - -```Solidity -receive() external payable { - pot += msg.value; // add funds to the pot -} -``` - -## 2. Deploying the Contract - -:::warning 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](https://faucet.paradigm.xyz/) - -::: - -Now deploy the Lottery contract and call it through Remix. It will call the QRNG -Airnode to request a random number. - -### Compile and Deploy the Lottery Contract on Goerli Testnet - -- [Click here](https://remix.ethereum.org/#url=https://raw.githubusercontent.com/api3-ecosystem/remix-contracts/master/contracts/Lottery.sol&lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.22+commit.4fc1097e.js) - to open the Lottery Contract in Remix. - - ![Opening the Requester Contract in Remix](src/s1.png) - -- Click on the **COMPILE** tab on the left side of the dashboard and click on - **Compile Lottery.sol** - - - -- Head to Deploy and run Transactions and select **Injected Provider — - MetaMask** option under **Environment**. Connect your MetaMask. Make sure - you’re on the Goerli Testnet. - -- The `endTime` will be the ending time of the lottery. To have it end in the - next week, execute the following code snippet. Use its output for `_ENDTIME` - - ```js - console.log((nextWeek = Math.floor(Date.now() / 1000) + 9000)); - ``` - -- The `_airnodeRrpAddress` is the main `airnodeRrpAddress`. The RRP Contracts - have already been deployed on-chain. You can check for your specific chain - [here](/reference/airnode/latest/). Fill it in and Deploy the Contract. - - ![Deploying the Lottery](src/s3.png) - -## 3. Deriving the Sponsor Wallet - -The -[Sponsor Wallet](/reference/airnode/latest/concepts/sponsor.md#sponsorwallet) -needs to be derived from the requester's contract address (Lottery contract in -this case), the Airnode address, and the Airnode xpub. The wallet is used to pay -gas costs of the transactions. The sponsor wallet must be derived using the -command -[derive-sponsor-wallet-address](/reference/airnode/latest/developers/requesters-sponsors.md#how-to-derive-a-sponsor-wallet) -from the Admin CLI. Use the value of the sponsor wallet address that the command -outputs while making the request. **This wallet needs to be funded.** - -::: details Testnet Random Numbers QRNG Airnode Details - -``` -Testnet Random Numbers QRNG Airnode Address = 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D -Testnet Random Numbers QRNG Airnode XPUB = xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo -``` - -::: - -```sh -npx @api3/airnode-admin derive-sponsor-wallet-address \ - --airnode-xpub xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo \ - --airnode-address 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D \ - --sponsor-address - - Sponsor wallet address: 0x6394...5906757 - # Use the above address from your command execution as the value for sponsorWallet. -``` - -Click on the `setSponsorWallet` button and enter your Sponsor Wallet Address to -set it on-chain. - - - -::: warning 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. - -::: - -## 4. Making the Bet - -To make a bet on-chain, we've defined a minimum ticket price that the user will -have to pay to make a bet (0.0001 ETH). Under Deployed Contracts, select the -`enter` function and enter your number. You will also need to send in the ticket -price along with the bet. Head over to the top and enter the `ticketPrice` under -**VALUE** and click on transact. - -![Sending the bet](src/s7.png) - -![Sending the bet#2](src/s6.png) - -You can also check the Ticket Price by running the `ticketPrice` function. - -![checking ticket price](src/s5.png) - -Next, you need a way for people to call Airnode for a random number when the -lottery is closed. Call the `getWinningNumber` function in the contract to make -a random number request. This function will emit an event that will be used to -listen for our `requestID` that will be used to listen for a response. This -function will also fund the sponsor wallet. - -Enter the amount to fund the sponsor wallet (0.01 ETH) and call -`getWinningNumber` - -![getWinningBet](src/s7.png) - -![getWinningBet#2](src/s9.png) - -Head over to [Goerli Testnet Explorer](https://goerli.etherscan.io/) and check -your `sponsorWallet` for any new transactions. - - - -Here, You can see the latest `Fulfill` transaction. - -::: info 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. - -::: - -You can check the winning number by calling `winningNumber`. Pass in the week to -check which number won that week. - -![Making the Request](src/s10.png) - -## Conclusion - -This is how you can use Quantum Randomness in your smart contracts. To learn -more, go to the [QRNG reference](/reference/qrng/) section. If you have any -doubts/questions, visit the API3 -[Discord](https://discord.com/channels/758003776174030948/765618225144266793). - - diff --git a/docs/guides/qrng/lottery-guide/src/lottery1.png b/docs/guides/qrng/lottery-guide/src/lottery1.png deleted file mode 100644 index 917d91dd..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/lottery1.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s1.png b/docs/guides/qrng/lottery-guide/src/s1.png deleted file mode 100644 index b2c43e45..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s1.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s10.png b/docs/guides/qrng/lottery-guide/src/s10.png deleted file mode 100644 index eef2636c..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s10.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s11.png b/docs/guides/qrng/lottery-guide/src/s11.png deleted file mode 100644 index a2cc08d2..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s11.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s2.png b/docs/guides/qrng/lottery-guide/src/s2.png deleted file mode 100644 index 6f3bea64..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s2.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s3.png b/docs/guides/qrng/lottery-guide/src/s3.png deleted file mode 100644 index 56886084..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s3.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s4.png b/docs/guides/qrng/lottery-guide/src/s4.png deleted file mode 100644 index 943eda78..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s4.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s5.png b/docs/guides/qrng/lottery-guide/src/s5.png deleted file mode 100644 index 424a0c45..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s5.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s6.png b/docs/guides/qrng/lottery-guide/src/s6.png deleted file mode 100644 index 37229493..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s6.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s7.png b/docs/guides/qrng/lottery-guide/src/s7.png deleted file mode 100644 index 4af938c9..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s7.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s8.png b/docs/guides/qrng/lottery-guide/src/s8.png deleted file mode 100644 index f6d62518..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s8.png and /dev/null differ diff --git a/docs/guides/qrng/lottery-guide/src/s9.png b/docs/guides/qrng/lottery-guide/src/s9.png deleted file mode 100644 index 92206161..00000000 Binary files a/docs/guides/qrng/lottery-guide/src/s9.png and /dev/null differ diff --git a/docs/guides/qrng/quantumon/index.md b/docs/guides/qrng/quantumon/index.md deleted file mode 100644 index a7239621..00000000 --- a/docs/guides/qrng/quantumon/index.md +++ /dev/null @@ -1,441 +0,0 @@ ---- -title: Minting Dynamic NFTs with QRNG -sidebarHeader: Guides -sidebarSubHeader: -pageHeader: Guides → QRNG -path: /guides/qrng/quantumon/index.html -outline: deep -tags: - - qrng ---- - - - - - - - -# {{$frontmatter.title}} - -[Quantumon](https://quantumon.xyz/) - Quantum Monsters are a collection of AI -generated monsters that are minted to users with the help of -[API3's QRNG](/reference/qrng/) and [DALL-E AI](https://openai.com/dall-e-2/). -It utilities the power of QRNG to generate unique random NFTs. - -One of the most important aspects of NFTs is their perceived rarity making it -very important for the attributes that affect rarity to be randomly generated. -If a bad actor exploits the minting contract, they can keep the rare items for -themselves making it unfair for everyone else. With QRNG, you can do fair -distribution of NFTs using -[“out of order”](https://www.justinsilver.com/technology/cryptocurrency/nft-mint-random-token-id/) -minting. - -## 1. Defining the Imports - -To see how Quantumon works, you can visit the -[Quantumon](https://quantumon.xyz/) website. It uses an ERC-721 standard -contract and QRNG to mint random NFTs and distribute them. - -[Open in Remix](https://remix.ethereum.org/#url=https://raw.githubusercontent.com/api3-ecosystem/remix-contracts/master/contracts/Quantumon.sol&lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.14+commit.80d49f37.js). - -Start by specifying the license, solidity version, importing the necessary -contracts and then creating the actual contract itself. - -```solidity -//SPDX-License-Identifier: MIT -pragma solidity 0.8.14; -import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol"; -import "@openzeppelin/contracts@4.9.5/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts@4.9.5/access/Ownable.sol"; -contract Quantumon is ERC721, RrpRequesterV0, Ownable { - -} -``` - -- **Ownable** : This is - [Openzeppelin implementation of an ownership](https://docs.openzeppelin.com/contracts/2.x/access-control) - contract. Contracts that inherit the Ownable contract have access to modifiers - that make it possible for certain function to only be invoked by the owner. - The owner of the contract is the contract deployer. However the ownership can - be transferred by the owner. - -- **ERC721** : This is - [Openzeppelin implementation of a Non Fungible Token(NFT)](https://docs.openzeppelin.com/contracts/3.x/erc721) - standard. This is the contract we inherit to have the contract be able to mint - and transfer NFTs. - -- **RrpRequesterV0** : The main contract will inherit this contract to be - declared as a Requester that will be communicating with the - [Request Response Protocol(RRP)](/reference/airnode/latest/concepts/). Using - the RRP protocol you will request for a random number from the QRNG Airnode. - -## 2. Constructor and Public Variables - -```solidity -contract Quantumon is ERC721, RrpRequesterV0, Ownable { - using Strings for uint256; - - - uint256[9958] public ids; //Array to store the Quantomon Id - This is different from the tokenId - uint256 private index; // Track the next TokenId to be minted - string private _baseURIextended; // The Extended baseUrl for ERC721 - mapping(uint256 => string) private _tokenURIs; //Mapping a custom URI to a tokenId - - address public airnode; //The address of the QRNG airnode - bytes32 public endpointIdUint256; // The endpointId of the airnode to fetch a single random number - address public sponsorWallet; // The address of the sponsorWallet that will be making the fullfillment transaction - - - // Mapping that maps the requestId for a random number to the fullfillment status of that request - mapping(bytes32 => bool) public expectingRequestWithIdToBeFulfilled; - - //Mapping that maps the requestId to the address that made the request - mapping(bytes32 => address) requestToSender; - - constructor(address _airnodeRrp) - RrpRequesterV0(_airnodeRrp) - ERC721("QUANTUMON", "QUANTUMON") - { - - } -} -``` - -- `using Strings for uint256;` - - - This statement makes it possible for a `uint256` variable to call up - functions in the Strings library. - -- `ids`: This is an array used to help in “out of order” minting process. This - array does not keep track of the already minted NFTs. - -- `index`: This variable will be used to keep track of the number of NFTs minted - so far. - -- `_baseURIextended`: This variable holds the base URI which will be appended to - the tokenURI of each minted NFT. - -- `airnode`: The airnode variable holds the address of the QRNG Airnode which - will be used to request a random number from. - -- `endpointIdUint256`: When sending a request to the airnode you need to specify - the endpointId. You can think of the `endpointId` as the path in a url. For - example if the URL is `www.google.com/images` then `www.google.com` would be - the airnode and `/images` would be the `endpointId`. - -- `sponsorWallet`: The fullfilment transaction that returns us the random number - will be done via the `sponsorWallet` of QRNG Airnode. It is called - `sponsorWallet` because it is derived via the sponsor address(since we import - `RrpRequesterV0` this contract sponsor’s itself so the sponsor address is the - address of the contract after it is deployed) and the QRNG Airnode’s extended - public key. - -- `expectingRequestWithIdToBeFulfilled`: Whenever you make a request to the - airnode, a `requestId` is returned. This mapping maps the `requestId` to a - boolean, setting it true when a request is made and setting it false when the - request is fulfilled. - -- `requesterToSender`: This mapping maps the `requestId` to the address making - the request. - -The constructor of the contract will be invoked when we deploy the contract. The -constructor will accept a single argument called `_airnodeRrp` . This argument -is the address of the Airnode -[Request Response Protocol](/reference/airnode/latest/concepts/) of the chain -this contract will be deployed to. A list of all the AirnodeRrp addresses can be -found [here](/reference/airnode/latest/) for each chain. This address is passed -onto the constructor of `RrpRequesterV0` which sets the current deployed -contract as a sponsor for itself. You don’t have to worry about sponsorship for -now we will cover that in a bit. - -You also need to call the constructor of `ERC721` and give it the `tokenName` -and `tokenSymbol` as arguments. In this case it is `QUANTUMON` for both the name -and symbol. - -## 3. Setting the Request Parameters - -```solidity - function setRequestParameters( - address _airnode, - bytes32 _endpointIdUint256, - address _sponsorWallet - ) external onlyOwner { - airnode = _airnode; - endpointIdUint256 = _endpointIdUint256; - sponsorWallet = _sponsorWallet; - emit SetRequestParameters(airnode, endpointIdUint256, sponsorWallet); - } -} -``` - -The `setRequestParameters` function is used to set the QRNG Airnode parameters. -It sets the `airnode`, `endpointIdUint256` and `sponsorWallet`. This function -has the `onlyOwner` modifier which means that only the owner of this contract -can call this function. The `setRequestParameters` function should be called -immediately after deploying the contract. - -## 4. Metadata and TokenURI functions - -NFT platforms call the `tokenURI(uint256 tokenId)` function to get the url that -points to the metadata of the NFT. This metadata contains different attributes -of the NFT like its name, image and other custom attributes depending on which -platform you use. Opensea expects the metadata to be of the following format: - -```json -{ - "description": "Friendly OpenSea Creature", - "external_url": "https://openseacreatures.io/3", - "image": "https://storage.googleapis.com/opensea-prod.appspot.com/puffs/3.png", - "name": "Dave Starbelly", - "attributes": [ ... ], -} -``` - -Override the default ERC721 `tokenURI()` method to enable custom URI for every -token and also add a `baseURL` that gets prefixed to every `tokenURI`. - -```solidity - function setBaseURI(string memory baseURI) external onlyOwner { - _baseURIextended = baseURI; - emit SetBaseURI(_baseURIextended); - } - - function _setTokenURI(uint256 tokenId, string memory _tokenURI) - internal - virtual - { - require( - _exists(tokenId), - "ERC721Metadata: URI set of nonexistent token" - ); - _tokenURIs[tokenId] = _tokenURI; - } - - function _baseURI() internal view virtual override returns (string memory) { - return _baseURIextended; - } - - function tokenURI(uint256 tokenId) - public - view - virtual - override - returns (string memory) - { - require( - _exists(tokenId), - "ERC721Metadata: URI query for nonexistent token" - ); - - string memory _tokenURI = _tokenURIs[tokenId]; - string memory base = _baseURI(); - - // If there is no base URI, return the token URI. - if (bytes(base).length == 0) { - return _tokenURI; - } - // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). - if (bytes(_tokenURI).length > 0) { - return string(abi.encodePacked(base, _tokenURI)); - } - // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI. - return string(abi.encodePacked(base, tokenId.toString())); - } -``` - -`_baseURI`: This function is an internal function which means that this can only -be called by this contract or contracts that inherit this contract. `_baseURI` -is a method of the original ERC721 contract, we are overriding it using the -override keyword to just return the `_baseURIextended` string. - -`setBaseURI`: This function is an `onlyOwner` function that can set the -`_baseURIextended` string. - -`_setTokenURI`: This is an internal function that maps the URI to a `tokenId`. -If the `tokenId` hasn’t been minted it will revert. - -`tokenURI`: This is a `view` function (function that don’t change the state of -the blockchain) that is overridden to return the URI in the `_tokenURIs` -mapping. The code comments explain what it does in more detail. - -::: info Note: In solidity `string(abi.encodePacked(StringA,StringB))` returns -the concatenation of `stringA` and `stringB`. - -::: - -## 5. How the NFTs are structured - -We have all the necessary variables and functions to request a random number and -mint our NFT. In our case each NFT has a `_baseUrlExtended` that is set to - -`https://quantumon.s3.amazonaws.com/data/quantum_dex/nft/` - -If we add 0 to the end of that URL we get the metadata of the `0th` Quantumon. -If we add 1, we get the metadata of the `1st` Quantumon. In total there are -`9958` Quantumons which means there are `9958` metadata files. We want to be -able to randomly choose the number that gets appended to this url, however if a -Quantumon is already minted we don’t want to mint it again. To do this we use -this algorithm by Justin Silver that takes a random number and gives a -completely unique non minted QuantumonId. - -```solidity -function _pickRandomUniqueId(uint256 random) private returns (uint256 id) { - uint256 len = ids.length - index++; - require(len > 0, 'no ids left'); - uint256 randomIndex = random % len; - id = ids[randomIndex] != 0 ? ids[randomIndex] : randomIndex; - ids[randomIndex] = uint16(ids[len - 1] == 0 ? len - 1 : ids[len - 1]);ids[len - 1] = 0; - } -``` - -### `requestQuantumon` function - -```solidity - function requestQuantumon() public payable returns (bytes32) { - require( - msg.value >= 5 ether, - "Need to send atleast 5 ether to the sponsorWallet" - ); - bytes32 requestId = airnodeRrp.makeFullRequest( - airnode, - endpointIdUint256, - address(this), - sponsorWallet, - address(this), - this.generateQuantumon.selector, - "" - ); - expectingRequestWithIdToBeFulfilled[requestId] = true; - requestToSender[requestId] = msg.sender; - (bool success, ) = sponsorWallet.call{value: 0.01 ether}(""); - require(success, "Forward failed"); - emit RequestQuantumon(msg.sender, requestId); - return requestId; - } -``` - -To use the algorithm to mint the Quantumons you need to request the random -number first. The `requestQuantumon()` function creates the request to the -Airnode using the `airnodeRrp.makeFullRequest()` function. The -`airnodeRrp.makeFullRequest()` function expects the following arguments: - -- `airnode` -- `endpointId` -- `sponsor` -- `sponsorWallet` -- `fulfillmentAddress` -- `fulfillmentFunction` -- `parameters` - -To understand `sponsor` and `sponsorWallets`, we need to first understand the -first party oracle architecture. The diagram below will help you understand it a -little better. - - - -Essentially when you call `makeFullRequest()` an event is emitted and the API’s -airnode is listening to this event. Based on the event it makes the call to the -actual API itself and fetches the data we requested. In our case it fetches the -random number and makes a callback. This callback is again a blockchain -transaction. Blockchain transaction costs gas and the API’s airnode doesn’t want -to spend money on gas. Hence we “sponsor” our smart contract and send gas tokens -to the api cloud provider to make the callback transaction. - -But where do we send our funds? How will the API’s airnode know which sponsor -does the funds belong to in case of multiple such sponsors? - -There is a way to derive a wallet that is owned by the api cloud provider which -will be unique for each sponsor. - -using the command below you can derive a unique `sponsorWallet` for a given -sponsor, all you need is the airnode address and the airnode’s extended public -key. - -```bash -npx @api3/airnode-admin derive-sponsor-wallet-address \ - --airnode-xpub xpub6CUGRUo... \ - --airnode-address 0xe1e0dd... \ - --sponsor-address 0x9Ec6C4... -``` - - - -By inheriting the `RrpRequesterV0` contract, we declare the contract as its own -sponsor. This means that we can substitute the address of the deployed contract -as the sponsor address in the above command. - -`fulfillmentAddress` is the address the request will be fullfilled to, in our -case it will be the address of the contract itself. - -`fullfullmentFunction` is the function within the `fulfillmentAddress` that will -be called with the requested data. In our case it is the `generateQuantumon` -Function - -Now that we have all our parameters we can call the `makeFullRequest()` -function. The `makeFullRequest()` function returns a `requestId` , which we use -to set the `expectingRequestWithIdToBeFulfilled` mapping and also the -`requestToSender` mapping. - -The `requestQuantumon` function has a `require(msg.value >= 5 ether,"...")` -condition, which means the user must pay 5 ether to be able to call this -function. - -> _To have a streamlined UX we can have the user pay for the fulfillment -> transaction by sending some funds over to the sponsorWallet using -> sponsorWallet.call{value: 0.01 ether}("") . In most cases this amount should -> be enough to cover the fulfillment transaction, however it depends on the gas -> price and gas cost of the transaction, so vary it accordingly._ - -## 6. `generateQuantumons` function - -```solidity - function generateQuantumon(bytes32 requestId, bytes calldata data) - public - onlyAirnodeRrp - { - require( - expectingRequestWithIdToBeFulfilled[requestId], - "Request ID not known" - ); - expectingRequestWithIdToBeFulfilled[requestId] = false; - uint256 qrngUint256 = abi.decode(data, (uint256)); - uint256 id = _pickRandomUniqueId(qrngUint256); - uint256 tokenId = index - 1; - _safeMint(requestToSender[requestId], tokenId); - _setTokenURI(tokenId, id.toString()); - emit GenerateQuantumon(requestToSender[requestId], tokenId); - } -``` - -When the airnode makes the fullfillment transaction it will call -`generateQuantumon()` with `requestId` and data as arguments. - -:::warning Note: `generateQuantumon()` has the `onlyAirnodeRrpmodifier` which -means that only the `AirnodeRrp`contract can call this function. - -::: - -we decode the data, using `abi.decode(data, (uint256))` . We decode using -`uint256` because we know that `data` only contains a `uint256` random number. -Using this random number we call `_pickRandomUniqueId()` to get a non minted -Quantumon Id. We use index to keep track of the how many Quantumons have been -minted and it also serves as the `tokenId` of the next mint. - -We now call the `_mint()` function and supply it `destinationAddress`, `tokenId` -as arguments. The destination address is the address we set in the -`requestToSender` mapping for the specified `requestId`. - -We also call the `_setTokenURI()` which maps the Quantumon `id` to the -`tokenId`. (Remember: the tokenIds are minted sequentially, but we want the -Quantumon ids to be minted “out of order”). - -## Conclusion - -In this guide, you learned Quantumon utilities QRNG and minted Quantumon -Monsters. You also learned how to request data from a QRNG Airnode and how to -use the data to mint an NFT. - -See the source code for the -[Quantumon contract ](https://github.com/api3-ecosystem/remix-contracts/blob/master/contracts/Quantumon.sol). - - diff --git a/docs/guides/qrng/quantumon/src/fpoa.webp b/docs/guides/qrng/quantumon/src/fpoa.webp deleted file mode 100644 index 3cb8b377..00000000 Binary files a/docs/guides/qrng/quantumon/src/fpoa.webp and /dev/null differ diff --git a/docs/guides/qrng/quantumon/src/sponsor.webp b/docs/guides/qrng/quantumon/src/sponsor.webp deleted file mode 100644 index 4b184162..00000000 Binary files a/docs/guides/qrng/quantumon/src/sponsor.webp and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/index.md b/docs/guides/qrng/roulette-guide/index.md deleted file mode 100644 index 931170bd..00000000 --- a/docs/guides/qrng/roulette-guide/index.md +++ /dev/null @@ -1,897 +0,0 @@ ---- -title: Building an on-chain Roulette with QRNG -sidebarHeader: Guides -sidebarSubHeader: -pageHeader: Guides → QRNG -path: /guides/qrng/roulette-guide/index.html -outline: deep -tags: ---- - - - - - - - -# {{$frontmatter.title}} - -> ![Roulette](/guides/qrng/roulette-guide/src/SS2.png) - -This example project demonstrates how to code a Solidity roulette game that uses -API3's QRNG for true randomness. You will use Remix IDE to code and deploy the -contract. -[Click here to check out the project's Github repo with a proper working frontend](https://github.com/Ashar2shahid/qrng-roulette). - -[Click here to try out the Roulette](https://qrng-roulette.netlify.app/) - -Before starting, make sure you have a proper understanding of -[Airnode](/explore/airnode/what-is-airnode.md) and -[how it works.](/reference/airnode/latest/concepts/airnode.md) - -[Read more about QRNG and how it works.](/reference/qrng/) - -## Introduction - -In a game of roulette, players place their bets on a table with numbers and -betting options. The table corresponds to a spinning wheel with numbered -pockets, which is spun by the dealer. Once the ball comes to a stop in one of -the pockets, the dealer announces the winning number and pays out any winning -bets. - -Players can bet on a variety of options, including specific numbers or groups of -numbers, such as whether the ball will land on an odd or even number or on a red -or black pocket. - -The roulette that we're going to code will have the following betting options -for the users: - -- The user can select either the first, second or the third dozen of the numbers - on the board. -- The user can select either the first or the second half of the numbers on the - board. -- The user can select either the set of all even or odd numbers on the board -- The user can select all the red or black numbers on the board. -- The user can choose any one number he wishes on the board. - -If the number after the spin lands on one of the selected numbers, the user wins -the bet. - -## Coding the `Roulette` Contract - -::: danger Check your Network - -Make sure you're on a Testnet before trying to deploy the contracts on-chain! - -::: - -> The complete contract code can be found -> [here](https://github.com/Ashar2shahid/qrng-roulette/blob/main/contracts/contracts/Roulette.sol). - -Head on to [Remix online IDE](https://remix.ethereum.org) using a browser that -you have added Metamask support to. Not all browsers support -[MetaMask](https://metamask.io/). - -It should load up the Roulette contract. - -[Open in Remix](https://remix.ethereum.org/#url=https://raw.githubusercontent.com/api3-ecosystem/remix-contracts/master/contracts/Roulette.sol&lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.22+commit.4fc1097e.js) - -> ![Remix 1](/guides/qrng/roulette-guide/src/SS1.png) - -### Importing the `RrpRequesterV0` - -The Roulette contract is going to be the main Requester contract that makes -request to the QRNG Airnode using the -[Request-Response Protocol (RRP)](/reference/qrng/airnode-rrp-v0.md). - -```solidity -pragma solidity >=0.8.4; - -import "@api3/airnode-protocol/contracts/rrp/requesters/RrpRequesterV0.sol"; - -contract Roulette is RrpRequesterV0 { - uint256 public constant MIN_BET = 10000000000000; // .001 ETH - uint256 spinCount; - address airnode; - address immutable deployer; - address payable sponsorWallet; - bytes32 endpointId; - - // ~~~~~~~ ENUMS ~~~~~~~ - - enum BetType { - Color, - Number, - EvenOdd, - Third, - Half - } -``` - -You first start by importing the `RrpRequesterV0`, which is the -[Request-Response Protocol (RRP)](/reference/qrng/airnode-rrp-v0.md). You can -then start coding the `Roulette` contract by inheriting from `RrpRequesterV0`. - -You then define the following state variables: - -- `MIN_BET`: The minimum amount that is required to bet for a spin. -- `spinCount`: An unsigned integer to keep track of the number of times the - roulette wheel has been spun. -- `airnode`: The Airnode address of the QRNG Provider. -- `deployer`: An immutable address variable to store the address of the contract - deployer. -- `sponsorWallet`: A `payable` address variable to store the `sponsorWallet`, - that needs to be funded later to cover the gas costs for the QRNG request - fulfillment. -- `endpointId`: A `bytes32` variable to store the unique identifier for the QRNG - API endpoint. - -``` -Color -Number -EvenOdd -Third -Half -``` - -The contract also defines an enumeration type called `BetType` with five -possible values. - -These values represent different types of bets that players can make in the game -of Roulette. - -```solidity - mapping(address => bool) public userBetAColor; - mapping(address => bool) public userBetANumber; - mapping(address => bool) public userBetEvenOdd; - mapping(address => bool) public userBetThird; - mapping(address => bool) public userBetHalf; - mapping(address => bool) public userToColor; - mapping(address => bool) public userToEven; - - mapping(address => uint256) public userToCurrentBet; - mapping(address => uint256) public userToSpinCount; - mapping(address => uint256) public userToNumber; - mapping(address => uint256) public userToThird; - mapping(address => uint256) public userToHalf; - - mapping(bytes32 => bool) expectingRequestWithIdToBeFulfilled; - - mapping(bytes32 => uint256) public requestIdToSpinCount; - mapping(bytes32 => uint256) public requestIdToResult; - - mapping(uint256 => bool) blackNumber; - mapping(uint256 => bool) public blackSpin; - mapping(uint256 => bool) public spinIsComplete; - - mapping(uint256 => BetType) public spinToBetType; - mapping(uint256 => address) public spinToUser; - mapping(uint256 => uint256) public spinResult; - uint256 public finalNumber; - -``` - -The contract defines several mapping variables to store information about user -bets and the results of each spin in the game of Roulette. - -```solidity - error HouseBalanceTooLow(); - error NoBet(); - error ReturnFailed(); - error SpinNotComplete(); - error TransferToDeployerWalletFailed(); - error TransferToSponsorWalletFailed(); - - // ~~~~~~~ EVENTS ~~~~~~~ - - event RequestedUint256(bytes32 requestId); - event ReceivedUint256(bytes32 indexed requestId, uint256 response); - event SpinComplete(bytes32 indexed requestId, uint256 indexed spinNumber, uint256 qrngResult); - event WinningNumber(uint256 indexed spinNumber, uint256 winningNumber); -``` - -The contract also defines several error messages and events. - -```solidity - constructor(address _airnodeRrp) RrpRequesterV0(_airnodeRrp) { - deployer = msg.sender; - blackNumber[2] = true; - blackNumber[4] = true; - blackNumber[6] = true; - blackNumber[8] = true; - blackNumber[10] = true; - blackNumber[11] = true; - blackNumber[13] = true; - blackNumber[15] = true; - blackNumber[17] = true; - blackNumber[20] = true; - blackNumber[22] = true; - blackNumber[24] = true; - blackNumber[26] = true; - blackNumber[28] = true; - blackNumber[29] = true; - blackNumber[31] = true; - blackNumber[33] = true; - blackNumber[35] = true; - } -``` - -The constructor function will take the `_airnodeRrp` address during deployment -of the contract. You also need to set the `deployer` variable to the address of -the user who deployed the contract `(msg.sender)`. - -It also sets certain numbers as black by setting their corresponding values in -the `blackNumber` mapping to `true`. These numbers are 2, 4, 6, 8, 10, 11, 13, -15, 17, 20, 22, 24, 26, 28, 29, 31, 33, and 35. These are the numbers on a -roulette wheel that are colored black. - -### Setting the Request Parameters - -```solidity - function setRequestParameters(address _airnode, bytes32 _endpointId, address payable _sponsorWallet) external { - require(msg.sender == deployer, "msg.sender not deployer"); - airnode = _airnode; - endpointId = _endpointId; - sponsorWallet = _sponsorWallet; - } - - /// @notice sends msg.value to sponsorWallet to ensure Airnode continues responses - function topUpSponsorWallet() external payable { - require(msg.value != 0, "msg.value == 0"); - (bool sent, ) = sponsorWallet.call{ value: msg.value }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - } - - // to refill the "house" (address(this)) if bankrupt - receive() external payable {} -``` - -The `setRequestParameters` sets the QRNG `airnode` address, `endpointId`, and -`sponsorWallet` on-chain. This function can only be called by the deployer of -the contract. - -The `topUpSponsorWallet()` is used to top up the `sponsorWallet` address. You -will later derive it using the -[Airnode admin CLI](/reference/airnode/latest/packages/admin-cli.md). - -### Making a Request for a Random Number - -```solidity - function _spinRouletteWheel(uint256 _spinCount) internal { - require(!spinIsComplete[_spinCount], "spin already complete"); - require(_spinCount == userToSpinCount[msg.sender], "!= msg.sender spinCount"); - bytes32 requestId = airnodeRrp.makeFullRequest( - airnode, - endpointId, - address(this), - sponsorWallet, - address(this), - this.fulfillUint256.selector, - "" - ); - expectingRequestWithIdToBeFulfilled[requestId] = true; - requestIdToSpinCount[requestId] = _spinCount; - emit RequestedUint256(requestId); - } - - function fulfillUint256(bytes32 requestId, bytes calldata data) external onlyAirnodeRrp { - require(expectingRequestWithIdToBeFulfilled[requestId], "Unexpected Request ID"); - expectingRequestWithIdToBeFulfilled[requestId] = false; - uint256 _qrngUint256 = abi.decode(data, (uint256)); - requestIdToResult[requestId] = _qrngUint256; - _spinComplete(requestId, _qrngUint256); - finalNumber = (_qrngUint256 % 37); - emit ReceivedUint256(requestId, _qrngUint256); - } -``` - -The `_spinRouletteWheel()` is an internal function that makes a request for a -random number to use as the result of a roulette spin. It calls the -`airnodeRrp.makeFullRequest()` function of the `AirnodeRrpV0.sol` protocol -contract which adds the request to its storage and emits a `requestId`. It takes -a `_spinCount` parameter that represents the unique identifier for the spin. - -The function sets the `expectingRequestWithIdToBeFulfilled` mapping with the -`requestId` key to true. This is used to track whether the request has been -fulfilled. - -It sets the `requestIdToSpinCount` mapping with the `requestId` key to the -`_spinCount` parameter. This is used to map the request ID to the specific spin -count. - -It emits a `RequestedUint256` event with the `requestId` parameter to indicate -that a request has been made for a random number. - -The off-chain QRNG Airnode gathers the request and performs a callback to the -contract with the random number. The `fulfillUint256()` is a callback function -that is called by `AirnodeRrp` when a response is received. - -`_qrngUint256` stores the random number which is further passed to the -`_spinComplete()` with the `requestId`. - -Finally, the function emits a `ReceivedUint256` event with the received -`requestId` and the decoded `_qrngUint256`. - -```solidity - function _spinComplete(bytes32 _requestId, uint256 _qrngUint256) internal { - uint256 _spin = requestIdToSpinCount[_requestId]; - if (_qrngUint256 == 0) { - spinResult[_spin] = 37; - } else { - spinResult[_spin] = _qrngUint256; - } - spinIsComplete[_spin] = true; - if (spinToBetType[_spin] == BetType.Number) { - checkIfNumberWon(_spin); - } else if (spinToBetType[_spin] == BetType.Color) { - checkIfColorWon(_spin); - } else if (spinToBetType[_spin] == BetType.EvenOdd) { - checkIfEvenOddWon(_spin); - } else if (spinToBetType[_spin] == BetType.Half) { - checkIfHalfWon(_spin); - } else if (spinToBetType[_spin] == BetType.Third) { - checkIfThirdWon(_spin); - } - emit SpinComplete(_requestId, _spin, spinResult[_spin]); - } -``` - -The `_spinComplete()` is an internal function which is called by the -`fulfillUint256()` when the random number for a given spin has been receive. -This function takes in `_requestId` and `_qrngUint256`, which is the random -number that was generated by the QRNG service. - -The function first retrieves the spin number associated with the request ID, and -then checks if the QRNG request returned a valid random number or not. If it -didn't (i.e., returned 0), then the function assigns the value 37 to the spin -result, which is used to avoid a modulo problem in the calculation of payouts -for certain types of bets. - -If the QRNG request returned a valid random number, then the function assigns -that value to the spin result. The function then sets the `spinIsComplete` flag -for the spin to true, and checks what type of bet was made on the spin using the -`spinToBetType` mapping. Depending on the type of bet, the function calls a -specific helper function to check if the user won or lost the bet. - -Finally, the function emits a `SpinComplete` event with the request ID, spin -number, and spin result as parameters, to notify the user interface and other -contracts that the spin has been completed. - -### Betting on a Single Number - -```solidity - function betNumber(uint256 _numberBet) external payable returns (uint256) { - require(_numberBet < 37, "_numberBet is > 36"); - require(msg.value >= MIN_BET, "msg.value < MIN_BET"); - if (address(this).balance < msg.value * 35) revert HouseBalanceTooLow(); - userToCurrentBet[msg.sender] = msg.value; - unchecked { - ++spinCount; - } - userToSpinCount[msg.sender] = spinCount; - spinToUser[spinCount] = msg.sender; - userToNumber[msg.sender] = _numberBet; - userBetANumber[msg.sender] = true; - spinToBetType[spinCount] = BetType.Number; - _spinRouletteWheel(spinCount); - return (userToSpinCount[msg.sender]); - } -``` - -The `betNumber()` function allows a user to place a bet on a single-number (0 -to 36) in the roulette wheel. If the user's bet matches the number where the -ball lands, the payout will be 35 times the bet amount. - -```solidity - function checkIfNumberWon(uint256 _spin) internal returns (uint256) { - address _user = spinToUser[_spin]; - if (userToCurrentBet[_user] == 0) revert NoBet(); - if (!userBetANumber[_user]) revert NoBet(); - if (!spinIsComplete[_spin]) revert SpinNotComplete(); - if (spinResult[_spin] == 37) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] }(""); - if (!sent) revert ReturnFailed(); - } else {} - if (userToNumber[_user] == spinResult[_spin] % 37) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 35 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - (bool sent2, ) = deployer.call{ value: userToCurrentBet[_user] / 50 }(""); - if (!sent2) revert TransferToDeployerWalletFailed(); - } - userToCurrentBet[_user] = 0; - userBetANumber[_user] = false; - emit WinningNumber(_spin, spinResult[_spin] % 37); - return (spinResult[_spin] % 37); - } -``` - -`checkIfNumberWon()` is called internally to determine whether a user has won a -bet on a single number, after the roulette wheel has been spun and the random -result has been obtained. - -If the roulette spin resulted in a number that does not match the user's bet, -then the user loses the bet and the bet amount is split between the -`sponsorWallet` (10%), the deployer wallet (2%) and the house (88%). - -### Betting on a Dozen - -```solidity - function betOneThird(uint256 _oneThirdBet) external payable returns (uint256) { - require(_oneThirdBet == 1 || _oneThirdBet == 2 || _oneThirdBet == 3, "_oneThirdBet not 1 or 2 or 3"); - require(msg.value >= MIN_BET, "msg.value < MIN_BET"); - if (address(this).balance < msg.value * 3) revert HouseBalanceTooLow(); - userToCurrentBet[msg.sender] = msg.value; - unchecked { - ++spinCount; - } - spinToUser[spinCount] = msg.sender; - userToSpinCount[msg.sender] = spinCount; - userToThird[msg.sender] = _oneThirdBet; - userBetThird[msg.sender] = true; - spinToBetType[spinCount] = BetType.Third; - _spinRouletteWheel(spinCount); - return (userToSpinCount[msg.sender]); - } -``` - -`betOneThird()` is a payable function allows a user to place a bet on one of -three sections of the roulette table (1st, 2nd, or 3rd). The bet pays out 3:1 if -the section bet on is correct after the spin. - -```solidity - function checkIfThirdWon(uint256 _spin) internal returns (uint256) { - address _user = spinToUser[_spin]; - if (userToCurrentBet[_user] == 0) revert NoBet(); - if (!userBetThird[_user]) revert NoBet(); - if (!spinIsComplete[_spin]) revert SpinNotComplete(); - uint256 _result = spinResult[_spin] % 37; - uint256 _thirdResult; - if (_result > 0 && _result < 13) { - _thirdResult = 1; - } else if (_result > 12 && _result < 25) { - _thirdResult = 2; - } else if (_result > 24) { - _thirdResult = 3; - } - if (spinResult[_spin] == 37) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] }(""); - if (!sent) revert ReturnFailed(); - } else {} - if (userToThird[_user] == 1 && _thirdResult == 1) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 3 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else if (userToThird[_user] == 2 && _thirdResult == 2) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 3 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else if (userToThird[_user] == 3 && _thirdResult == 3) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 3 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - (bool sent2, ) = deployer.call{ value: userToCurrentBet[_user] / 50 }(""); - if (!sent2) revert TransferToDeployerWalletFailed(); - } - userToCurrentBet[_user] = 0; - userBetThird[_user] = false; - emit WinningNumber(_spin, spinResult[_spin] % 37); - return (spinResult[_spin] % 37); - } -``` - -`checkIfThirdWon()` is used to check if a user has won or lost their bet on a -third of the roulette table after a spin is complete. The function is called -internally and returns the winning number of the spin. - -The function first checks that the user has placed a bet on a third of the table -and that the spin is complete. It then calculates the winning third of the table -based on the spin result. If the user's bet matches the winning third of the -table, they receive their bet amount multiplied by 3. If they have not won, 10% -of their bet amount is sent to the `sponsorWallet` to ensure future fulfillment, -2% to the deployer, and the rest is kept by the house. - -Finally, the function resets the user's current bet and the bet type, emits an -event with the winning number of the spin, and returns the winning number of the -spin. - -```solidity - function betHalf(uint256 _halfBet) external payable returns (uint256) { - require(_halfBet == 1 || _halfBet == 2, "_halfBet not 1 or 2"); - require(msg.value >= MIN_BET, "msg.value < MIN_BET"); - if (address(this).balance < msg.value * 2) revert HouseBalanceTooLow(); - userToCurrentBet[msg.sender] = msg.value; - unchecked { - ++spinCount; - } - spinToUser[spinCount] = msg.sender; - userToSpinCount[msg.sender] = spinCount; - userToHalf[msg.sender] = _halfBet; - userBetHalf[msg.sender] = true; - spinToBetType[spinCount] = BetType.Half; - _spinRouletteWheel(spinCount); - return (userToSpinCount[msg.sender]); - } -``` - -### Betting on Half of the Table - -`betHalf()` allows users to place a bet on the first or second half of the -table, with a payout of 2:1 if correct after the spin. `_halfBet` takes in 1 or -2 as it's parameters that represents first and second half of the table. - -The function returns the `userToSpinCount[msg.sender]` value, which is the spin -count for the user that just placed a bet. - -```solidity - function checkIfHalfWon(uint256 _spin) internal returns (uint256) { - address _user = spinToUser[_spin]; - if (userToCurrentBet[_user] == 0) revert NoBet(); - if (!userBetHalf[_user]) revert NoBet(); - if (!spinIsComplete[_spin]) revert SpinNotComplete(); - uint256 _result = spinResult[_spin] % 37; - uint256 _halfResult; - if (_result > 0 && _result < 19) { - _halfResult = 1; - } else if (_result > 18) { - _halfResult = 2; - } - if (spinResult[_spin] == 37) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] }(""); - if (!sent) revert ReturnFailed(); - } else {} - if (userToHalf[_user] == 1 && _halfResult == 1) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 2 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else if (userToHalf[_user] == 2 && _halfResult == 2) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 2 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - (bool sent2, ) = deployer.call{ value: userToCurrentBet[_user] / 50 }(""); - if (!sent2) revert TransferToDeployerWalletFailed(); - } - userToCurrentBet[_user] = 0; - userBetHalf[_user] = false; - emit WinningNumber(_spin, spinResult[_spin] % 37); - return (spinResult[_spin] % 37); - } -``` - -`checkIfHalfWon()` checks whether a user has won a half bet after a spin is -complete. It first checks that the user has placed a half bet, that the spin is -complete, and that the user has placed a bet. It then calculates the result of -the spin, which is an integer between 0 and 36 inclusive. If the result is 37, -which represents 00, the bet is unsuccessful, and the user's bet is returned to -them. - -If the user's bet is successful, the function checks whether the user has bet on -the correct half of the table. If they have, their bet is multiplied by two and -returned to them. If they have not, 10% of their bet is sent to the sponsor -wallet to ensure future fulfillment, 2% is sent to the deployer, and the rest is -kept by the house. - -```solidity -function betEvenOdd(bool _isEven) external payable returns (uint256) { - require(msg.value >= MIN_BET, "msg.value < MIN_BET"); - if (address(this).balance < msg.value * 2) revert HouseBalanceTooLow(); - unchecked { - ++spinCount; - } - spinToUser[spinCount] = msg.sender; - userToCurrentBet[msg.sender] = msg.value; - userToSpinCount[msg.sender] = spinCount; - userBetEvenOdd[msg.sender] = true; - if (_isEven) { - userToEven[msg.sender] = true; - } else {} - spinToBetType[spinCount] = BetType.EvenOdd; - _spinRouletteWheel(spinCount); - return (userToSpinCount[msg.sender]); - } -``` - -### Betting on Even/Odd - -`betEvenOdd()` allows user to place an odd or even bet, which pays out 2:1 if -they are correct. It takes in a boolean `true` or `false` as a parameter for -even or odd bets. - -```solidity - function checkIfEvenOddWon(uint256 _spin) internal returns (uint256) { - address _user = spinToUser[_spin]; - if (userToCurrentBet[_user] == 0) revert NoBet(); - if (!userBetEvenOdd[_user]) revert NoBet(); - if (!spinIsComplete[_spin]) revert SpinNotComplete(); - uint256 _result = spinResult[_spin] % 37; - if (spinResult[_spin] == 37) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] }(""); - if (!sent) revert ReturnFailed(); - } else {} - if (_result == 0) { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - } else if (userToEven[_user] && (_result % 2 == 0)) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 2 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else if (!userToEven[_user] && _result % 2 != 0) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 2 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - (bool sent2, ) = deployer.call{ value: userToCurrentBet[_user] / 50 }(""); - if (!sent2) revert TransferToDeployerWalletFailed(); - } - userBetEvenOdd[_user] = false; - userToCurrentBet[_user] = 0; - emit WinningNumber(_spin, spinResult[_spin] % 37); - return (spinResult[_spin] % 37); - } -``` - -`checkIfEvenOddWon()` is an internal function used to check if a user's even/odd -bet has won after a spin has been completed. It first retrieves the address of -the user who placed the bet on this spin, and checks that the user had actually -placed a bet and that it was an even/odd bet. It then checks the result of the -spin, which is stored in the `spinResult` mapping under the given `_spin` key. -If the result is equal to 37, which represents the 0 value on the roulette -wheel, the user's bet is returned to them. - -If the result is not 37, the function determines if the user's bet was -successful based on whether they bet on even or odd. If the user bet on even and -the result is even, or if the user bet on odd and the result is odd, then the -user wins and is paid out twice their bet amount. If the user's bet is -unsuccessful, 10% of their bet is sent to the designated `sponsorWallet` to -ensure future payouts, 2% is sent to the deployer's wallet, and the remaining -amount is kept by the house. - -```solidity -function betColor(bool _isBlack) external payable returns (uint256) { - require(msg.value >= MIN_BET, "msg.value < MIN_BET"); - if (address(this).balance < msg.value * 2) revert HouseBalanceTooLow(); - unchecked { - ++spinCount; - } - spinToUser[spinCount] = msg.sender; - userToCurrentBet[msg.sender] = msg.value; - userToSpinCount[msg.sender] = spinCount; - userBetAColor[msg.sender] = true; - if (_isBlack) { - userToColor[msg.sender] = true; - } else {} - spinToBetType[spinCount] = BetType.Color; - _spinRouletteWheel(spinCount); - return (userToSpinCount[msg.sender]); - } - -``` - -### Betting on a Color - -`betColor()` allows user to place a black or red bet, which pays out 2:1 if they -are correct. It takes in a `_isBlack` variable as a parameter, i.e, `true` for -black, `false` for red. - -The function sets the `spinToBetType` mapping for this spin to `BetType.Color`, -and then calls `_spinRouletteWheel(spinCount)` - -```solidity -function checkIfColorWon(uint256 _spin) internal returns (uint256) { - address _user = spinToUser[_spin]; - if (userToCurrentBet[_user] == 0) revert NoBet(); - if (!userBetAColor[_user]) revert NoBet(); - if (!spinIsComplete[_spin]) revert SpinNotComplete(); - uint256 _result = spinResult[_spin] % 37; - if (spinResult[_spin] == 37) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] }(""); - if (!sent) revert ReturnFailed(); - } else if (_result == 0) { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - (bool sent2, ) = deployer.call{ value: userToCurrentBet[_user] / 50 }(""); - if (!sent2) revert TransferToDeployerWalletFailed(); - } else { - if (blackNumber[_result]) { - blackSpin[_spin] = true; - } else {} - if (userToColor[_user] && blackSpin[_spin]) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 2 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else if (!userToColor[_user] && !blackSpin[_spin] && _result != 0) { - (bool sent, ) = _user.call{ value: userToCurrentBet[_user] * 2 }(""); - if (!sent) revert HouseBalanceTooLow(); - } else { - (bool sent, ) = sponsorWallet.call{ value: userToCurrentBet[_user] / 10 }(""); - if (!sent) revert TransferToSponsorWalletFailed(); - (bool sent2, ) = deployer.call{ value: userToCurrentBet[_user] / 50 }(""); - if (!sent2) revert TransferToDeployerWalletFailed(); - } - } - userBetAColor[_user] = false; - userToCurrentBet[_user] = 0; - emit WinningNumber(_spin, spinResult[_spin] % 37); - return (spinResult[_spin] % 37); - } -} -``` - -`checkIfColorWon()` is an internal function that checks the result for a colour -bet when the spin is complete. It gets the user address from the mapping to -verify all the conditions. - -Then, it calculates the result of the spin by taking the modulo of the spin -result with 37. If the spin result is equal to 37, this indicates that the spin -failed to fulfill, and the user's bet amount will be returned to them. If the -spin result is 0, then the function will send 10% of the user's bet amount to -the sponsor wallet to ensure future fulfills, and 2% to the deployer, with the -remaining balance being kept by the house. - -If the spin result is not equal to 0 or 37, then the function checks if the -result is a black number or not. If it is a black number and the user bet on -black, or if it is a red number and the user bet on red, then the user wins and -will receive double their bet amount. - -> The complete contract code can be found -> [here](https://github.com/Ashar2shahid/qrng-roulette/blob/main/contracts/contracts/Roulette.sol). - -## Deploying the `Roulette` Contract - -::: warning Set up your Testnet Metamask Account! - -Make sure you've already configured your Metamask wallet and funded it with some -testnet MATIC before moving forward. You can request some from -[here➚](https://mumbaifaucet.com). - -::: - -You will now deploy the `Roulette` contract and interact with it through the -Remix IDE. - -### 1. Compiling the Contract - -[Click here➚]() to open the `Roulette` Contract in Remix if you haven't already. - -![](/guides/qrng/roulette-guide/src/SS1.png) - -Click on the **COMPILE** tab on the left side of the dashboard and click on -**Compile `roulette.sol`** - -![](/guides/qrng/roulette-guide/src/SS3.png) - -### 2. Deploying the Contract - -Head to **Deploy and run Transactions** and select **Injected Provider — -MetaMask** option under Environment. Connect your MetaMask. Make sure you’re on -the Polygon Mumbai testnet. - -The `_airnodeRrpAddress` is the main `airnodeRrpAddress`. The RRP contracts have -already been deployed on-chain. You can check for your specific chain -[here](/reference/airnode/latest/index.md#airnoderrpv0). Fill it in and click on -**transact** to deploy the contract. - -![](/guides/qrng/roulette-guide/src/SS4.png) - -### 3. Deriving the Sponsor Wallet - -The -[Sponsor Wallet](/reference/airnode/latest/concepts/sponsor.md#sponsorwallet) -needs to be derived from the requester's contract address (Lottery contract in -this case), the Airnode address, and the Airnode xpub. The wallet is used to pay -gas costs of the transactions. The sponsor wallet must be derived using the -command -[derive-sponsor-wallet-address](/reference/airnode/latest/packages/admin-cli.md#derive-sponsor-wallet-address) -from the Admin CLI. Use the value of the sponsor wallet address that the command -outputs while making the request. - -**This wallet needs to be funded.** - -::: details Testnet Random Numbers QRNG Airnode Details - -``` -Testnet Random Numbers QRNG Airnode Address = 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D -Testnet Random Numbers QRNG Airnode XPUB = xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo -``` - -::: - -```sh -npx @api3/airnode-admin derive-sponsor-wallet-address \ - --airnode-xpub xpub6CuDdF9zdWTRuGybJPuZUGnU4suZowMmgu15bjFZT2o6PUtk4Lo78KGJUGBobz3pPKRaN9sLxzj21CMe6StP3zUsd8tWEJPgZBesYBMY7Wo \ - --airnode-address 0x6238772544f029ecaBfDED4300f13A3c4FE84E1D \ - --sponsor-address - - Sponsor wallet address: 0x6394...5906757 - # Use the above address from your command execution as the value for sponsorWallet. -``` - -Click on the `setRequestParameters` button and enter the QRNG Airnode address, -`endpointID` and the `sponsorWallet` to set it on-chain. - -::: warning 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. - -::: - -## Using the `Roulette` Contract - -After deploying the contract, click the dropdown right under **Deployed -Contracts** and select `topUpSponsorWallet()`. This is a payable function that -will fund the `sponsorWallet`. This wallet will be responsible to fulfill the -randomness request. - -Fill in the amount in wei under **VALUE** on the tab above and click on -`topUpSponsorWallet()`. - -For Polygon Mumbai, `0.05` (`50000000000000000` wei) Matic should be sufficient -for now. - -After funding the `sponsorWallet`, we are also supposed to fund the main -contract(house) too. You can use Metamask to send some funds to the house. Copy -the deployed contract address and send funds to it. - -For this example, we can send 0.1 matic to the main contract(house). - -Copy the deployed contract address and send 0.1 matic to it through your -Metamask wallet. - -![](/guides/qrng/roulette-guide/src/SS5.png) - -Now you're ready to make a bet of your choice. - -- use `betOneThird()` to bet on either the first, second or the third dozen of - the numbers on the board. -- use `betHalf()` to bet on either the first or the second half of the numbers - on the board. -- use `betEvenOdd()` to bet on either the set of all even or odd numbers on the - board. -- use `betColor()` to bet on all the red or black numbers on the board. -- use `betNumber()` to bet on any one number you wish on the board. - -For this example, let's bet on all the even numbers of the table. As the -`MIN_BET` is set to be `10000000000000` wei, we'll use that value for making the -bet. - -Set the value to `10000000000000` wei and select `betEvenOdd()`. Pass in `true` -to select all the even numbers on the table and click on **transact**. - -![](/guides/qrng/roulette-guide/src/SS6.png) - -Now wait for the bet to be complete (For the QRNG Airnode to fulfill your -request). - -You can also check your `sponsorWallet` address on the block explorer to see if -the QRNG Airnode fulfilled the request. - -![](/guides/qrng/roulette-guide/src/SS7.png) - -You can now check the `finalNumber` getter function to see the final winning -number. - -![](/guides/qrng/roulette-guide/src/SS8.png) - -This means you lost the bet. If you head back over to the block explorer and -check the `Fulfill` transaction, you can see: - -- The contract sent `0.000001` MATIC to the `sponsorWallet`. -- The contract also sent `0.0000002` MATIC back to the deployer of the contract. -- The rest of the bet amount is kept by the house. - -![](/guides/qrng/roulette-guide/src/SS9.png) - -## Conclusion - -This is how you can use QRNG to build an on-chain Roulette. To learn more, go to -the [QRNG reference section](/reference/qrng/). If you have any -doubts/questions, visit the -[API3 Discord](https://discord.com/channels/758003776174030948/765618225144266793). - - diff --git a/docs/guides/qrng/roulette-guide/src/SS1.png b/docs/guides/qrng/roulette-guide/src/SS1.png deleted file mode 100644 index 063ed146..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS1.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS2.png b/docs/guides/qrng/roulette-guide/src/SS2.png deleted file mode 100644 index c785f1f2..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS2.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS3.png b/docs/guides/qrng/roulette-guide/src/SS3.png deleted file mode 100644 index 2efb8ac0..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS3.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS4.png b/docs/guides/qrng/roulette-guide/src/SS4.png deleted file mode 100644 index 1e9cf94a..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS4.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS5.png b/docs/guides/qrng/roulette-guide/src/SS5.png deleted file mode 100644 index 23c259b7..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS5.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS6.png b/docs/guides/qrng/roulette-guide/src/SS6.png deleted file mode 100644 index 2f6fc8e0..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS6.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS7.png b/docs/guides/qrng/roulette-guide/src/SS7.png deleted file mode 100644 index 8b4e5194..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS7.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS8.png b/docs/guides/qrng/roulette-guide/src/SS8.png deleted file mode 100644 index 5708e240..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS8.png and /dev/null differ diff --git a/docs/guides/qrng/roulette-guide/src/SS9.png b/docs/guides/qrng/roulette-guide/src/SS9.png deleted file mode 100644 index 06a5784e..00000000 Binary files a/docs/guides/qrng/roulette-guide/src/SS9.png and /dev/null differ diff --git a/docs/guides/sidebar.js b/docs/guides/sidebar.js index 92e8dbec..d4b254c9 100644 --- a/docs/guides/sidebar.js +++ b/docs/guides/sidebar.js @@ -27,7 +27,11 @@ module.exports = [ collapsed: false, items: [ { - text: 'Deploying an Airnode', + text: 'Setting up an Airnode with your API', + link: '/guides/airnode/setting-up-airnode/', + }, + { + text: 'Deploying an Airnode on a Cloud Provider', collapsed: false, items: [ { @@ -48,14 +52,14 @@ module.exports = [ }, ], }, - { - text: 'Post processing', - link: '/guides/airnode/post-processing/', - }, { text: 'Calling an Airnode', link: '/guides/airnode/calling-an-airnode/', }, + { + text: 'Post processing', + link: '/guides/airnode/post-processing/', + }, { text: 'Making an RRP Request', link: '/guides/airnode/rrp-request', @@ -86,18 +90,6 @@ module.exports = [ text: 'Using QRNG - Remix Example', link: '/guides/qrng/qrng-remix/', }, - { - text: 'Building a Lottery with QRNG', - link: '/guides/qrng/lottery-guide/', - }, - { - text: 'Minting Dynamic NFTs with QRNG', - link: '/guides/qrng/quantumon/', - }, - { - text: `Building a Roulette with QRNG`, - link: '/guides/qrng/roulette-guide/', - }, { text: 'QRNG YouTube Tutorials', link: '/guides/qrng/youtube-demos/', diff --git a/docs/reference/qrng/chains.md b/docs/reference/qrng/chains.md index 51a79d41..fa86e8ff 100644 --- a/docs/reference/qrng/chains.md +++ b/docs/reference/qrng/chains.md @@ -155,6 +155,7 @@ Quintessence is available on selected mainnets and testnets. | [{{getChainNameById(1313161554)}}](https://explorer.mainnet.aurora.dev) | 1313161554 | 0xE338f63170c42bA0d2a888f18F6185369779009c | 25 | | [{{getChainNameById(43114)}}](https://snowtrace.io/) | 43114 | 0xC02Ea0f403d5f3D45a4F1d0d817e7A2601346c9E | 25 | | [{{getChainNameById(8453)}}](https://basescan.org/) | 8453 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd | 10 | +| [{{getChainNameById(81457)}}](https://blastscan.io) | 81457 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd | 10 | | [{{getChainNameById(56)}}](https://bscscan.com) | 56 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd | 25 | | [{{getChainNameById(56288)}}](https://blockexplorer.bnb.boba.network) | 56288 | 0x20C9e9610d4e719a39F82893b3f42e2730F42778 | 25 | | [{{getChainNameById(288)}}](https://bobascan.com) | 288 | 0x1d4F592E0723e03bed2Ff6d78F3CEe6750f08B38 | 10 | @@ -172,6 +173,7 @@ Quintessence is available on selected mainnets and testnets. | [{{getChainNameById(1101)}}](https://zkevm.polygonscan.com/) | 1101 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd | 10 | | [{{getChainNameById(30)}}](https://explorer.rsk.co) | 30 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd | 3 | | [{{getChainNameById(416)}}](https://explorer.sx.technology) | 416 | 0xE338f63170c42bA0d2a888f18F6185369779009c | 20 | +| [{{getChainNameById(196)}}](https://www.okx.com/web3/explorer/xlayer) | 30 | 0xa0AD79D995DdeeB18a14eAef56A549A04e3Aa1Bd | 10 |