├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── apps ├── contracts │ ├── .env.example │ ├── .gitignore │ ├── .gitmodules │ ├── .solhint.json │ ├── .solhintignore │ ├── Makefile │ ├── README.md │ ├── diagrams │ │ └── request.excalidraw │ ├── foundry.toml │ ├── hardhat.config.js │ ├── networks.js │ ├── package.json │ ├── priceConfig.js │ ├── priceRequest.js │ ├── script │ │ ├── RealEstate.s.sol │ │ ├── getPrice.js │ │ └── helpers │ │ │ └── BaseScript.s.sol │ ├── src │ │ └── RealEstate.sol │ ├── tasks │ │ ├── billing │ │ │ ├── add.js │ │ │ ├── create.js │ │ │ ├── fund.js │ │ │ └── index.js │ │ ├── consumer │ │ │ ├── index.js │ │ │ ├── request.js │ │ │ └── response.js │ │ ├── index.js │ │ └── utils │ │ │ ├── index.js │ │ │ ├── logger.js │ │ │ ├── network.js │ │ │ ├── price.js │ │ │ ├── prompt.js │ │ │ └── spin.js │ └── yarn.lock ├── frontend │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── README.md │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── angle.svg │ │ ├── architecture.svg │ │ ├── arrow-go-to-up-blue.svg │ │ ├── arrow-go-to-up.svg │ │ ├── arrow-right.svg │ │ ├── chainlink.svg │ │ ├── charger.svg │ │ ├── chevron-down.svg │ │ ├── close.svg │ │ ├── code.svg │ │ ├── design.png │ │ ├── design.svg │ │ ├── dev-expert.svg │ │ ├── docs.svg │ │ ├── error.svg │ │ ├── external-muted.svg │ │ ├── external.svg │ │ ├── faucet.svg │ │ ├── github.svg │ │ ├── globe.svg │ │ ├── house.svg │ │ ├── how-it-works-meteo.png │ │ ├── how-it-works-x.jpg │ │ ├── loading.gif │ │ ├── menu.svg │ │ ├── onchain.svg │ │ ├── refresh.svg │ │ └── remix.png │ ├── src │ │ ├── app │ │ │ ├── api │ │ │ │ └── onchain-price │ │ │ │ │ └── route.ts │ │ │ ├── architecture │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── under-the-hood.tsx │ │ │ ├── design │ │ │ │ ├── layout.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── under-the-hood.tsx │ │ │ ├── favicon.ico │ │ │ ├── layout.tsx │ │ │ ├── opengraph-image.png │ │ │ ├── page.tsx │ │ │ └── tokenization │ │ │ │ ├── layout.tsx │ │ │ │ ├── offchain-response.tsx │ │ │ │ ├── onchain-data.tsx │ │ │ │ ├── onchain-response.tsx │ │ │ │ ├── opengraph-image.png │ │ │ │ ├── page.tsx │ │ │ │ └── under-the-hood.tsx │ │ ├── components │ │ │ ├── code-block.tsx │ │ │ ├── footer.tsx │ │ │ ├── loading-spinner.tsx │ │ │ ├── main-nav.tsx │ │ │ ├── mobile-nav.tsx │ │ │ ├── site-header.tsx │ │ │ ├── token-input.tsx │ │ │ ├── ui │ │ │ │ ├── button.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── command.tsx │ │ │ │ ├── dialog.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── popover.tsx │ │ │ │ └── scroll-area.tsx │ │ │ └── update-button.tsx │ │ ├── config │ │ │ ├── contract.ts │ │ │ └── site.tsx │ │ ├── hooks │ │ │ ├── useDebounce.ts │ │ │ └── useWait.ts │ │ ├── lib │ │ │ ├── fetch-house.ts │ │ │ ├── fonts.ts │ │ │ ├── request-onchain.ts │ │ │ └── utils.ts │ │ ├── styles │ │ │ └── globals.css │ │ └── types.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── yarn.lock └── server │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── abis │ ├── ERC20.json │ ├── ERC721.json │ └── RealEstate.json │ ├── api │ ├── houses │ │ └── index.js │ └── noop.js │ ├── constants.js │ ├── index.ts │ ├── middleware │ ├── cache.js │ ├── logger.js │ ├── powered.js │ └── rt.js │ ├── package.json │ ├── router.js │ ├── utils │ └── web3.js │ ├── vercel.json │ └── yarn.lock ├── docs └── FOUNDRY.md ├── package.json └── yarn.lock /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Compiler files 5 | cache/ 6 | out/ 7 | 8 | # Development Broadcast Logs 9 | !/broadcast 10 | broadcast 11 | /broadcast/*/31337/ 12 | /broadcast/**/dry-run/ 13 | 14 | # Docs 15 | # docs/ 16 | 17 | # Environment Variables 18 | .env 19 | .env.local 20 | .env.enc 21 | 22 | # HH Files 23 | build 24 | 25 | # Misc. 26 | .DS_STORE -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "apps/contracts/lib/chainlink"] 2 | path = apps/contracts/lib/chainlink 3 | url = https://github.com/lib/chainlink 4 | [submodule "apps/contracts/lib/chainlink-local"] 5 | path = apps/contracts/lib/chainlink-local 6 | url = https://github.com/lib/chainlink-local 7 | [submodule "apps/contracts/lib/forge-std"] 8 | path = apps/contracts/lib/forge-std 9 | url = https://github.com/lib/forge-std 10 | [submodule "apps/contracts/lib/openzeppelin-contracts"] 11 | path = apps/contracts/lib/openzeppelin-contracts 12 | url = https://github.com/lib/openzeppelin-contracts 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "svg.preview.background": "white" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SmartContract ChainLink, Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disclaimer 2 | This tutorial represents an educational example to use a Chainlink system, product, or service and is provided to demonstrate how to interact with Chainlink’s systems, products, and services to integrate them into your own. This template is provided “AS IS” and “AS AVAILABLE” without warranties of any kind, it has not been audited, and it may be missing key checks or error handling to make the usage of the system, product or service more clear. Do not use the code in this example in a production environment without completing your own audits and application of best practices. Neither Chainlink Labs, the Chainlink Foundation, nor Chainlink node operators are responsible for unintended outputs that are generated due to errors in code. 3 | 4 | # Real World Asset (RWA) Tokenization 5 | > Asset tokenization using Chainlink Functions on Avalanche. 6 | 7 | ---- 8 | 9 | # Getting Started 10 | 11 | ## Requirements 12 | 13 | - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) 14 | - You'll know you did it right if you can run `git --version` and you see a response like `git version x.x.x` 15 | - [foundry](https://getfoundry.sh/) 16 | - You'll know you did it right if you can run `forge --version` and you see a response like `forge 0.2.0 (816e00b 2023-03-16T00:05:26.396218Z)` 17 | - [node](https://nodejs.org/en/download/) 18 | - You'll know you did it right if you can run `node --version` and you see a response like `v16.13.0` 19 | - [yarn](https://classic.yarnpkg.com/en/docs/install) 20 | - You'll know you did it right if you can run `yarn --v` and you see a response like `1.22.22` 21 | - [deno](https://docs.deno.com/runtime/manual/getting_started/installation) 22 | - You'll know you did it right if you can run `deno --version` and you see a response like `deno 1.40.5 (release, x86_64-apple-darwin) v8 12.1.285.27 typescript 5.3.3` 23 | 24 | ## Installation (Scripts) 25 | The scripts below are contained in the root level of this repository and they enable you to cover a lot of ground without having to navigate to each directory independently. 26 | 27 | Please ensure you understand the logic progression of each installation step prior to executing these scripts, so you can explore how everything works together. 28 | 29 | I use whiteboards when digesting new topics. It helps me visualization the process, but do whatever works for you and try out new things because you are capable of SO much more than you may even imagine... 30 | 31 | ``` 32 | yarn prepare 33 | ``` 34 | Installs all app dependences, then prompts you to input a password, which will be used to encrypt your [`contracts`](/apps/contracts/README.md) environment variables. 35 | 36 | ``` 37 | yarn setup 38 | ``` 39 | Sets your environment variables for the contracts app. In our case, we only need `PRIVATE_KEY` to begin. 40 | 41 | ``` 42 | yarn build 43 | ``` 44 | Carries out the [Makefile](/apps/contracts/Makefile), compiles contracts, then builds the frontend. 45 | 46 | 47 | ``` 48 | yarn deploy 49 | ``` 50 | Deploys [`RealEstate.sol`](/apps/contracts/src/RealEstate.sol) to Fuji testnet. 51 | > **Important**: make sure to update the deployment script prior to deploying. If you do not already have a subscription, please make sure to run the `yarn sub:create` script before deployment. 52 | 53 | After deployment please take note of the contract address and update throughout all 3 apps using search and replace, as explained [here](/apps/contracts/README.md). 54 | 55 | If you made changes to the smart contract, be sure to also update the ABI everywhere it is found in the apps. You abi may be found here in your ignored [`out` directory](/apps/contracts/out/RealEstate.sol/RealEstate.json). 56 | 57 | When I open the JSON, I format it with a JSON extension in VS Code. Next, I copy the ABI, which is contained in brackets at the start of the JSON. 58 | 59 | ``` 60 | yarn assign 61 | ``` 62 | Depending on whether you are interesting in (or in need of) creating a new subscription (`subId`), then you will either run the create script to creates, otherwise you will assign your recently deployment RealEstate.sol contract to the `--subid` you specify. 63 | 64 | Be sure to update the `--subid ` task argument, otherwise `assign` will fail, subscriptions your `PRIVATE_KEY` does not own. 65 | 66 | ``` 67 | yarn serve 68 | ``` 69 | Starts the backend api server instance. 70 | 71 | ``` 72 | yarn show 73 | ``` 74 | Launches dev mode of the frontend for local hosting and debugging prior to deployment. 75 | 76 | ``` 77 | yarn request 78 | ``` 79 | 80 | Submits a priceUpdate request transaction on the RealEstate asset tied to the `--tokenid` passed into the task arg. Prior to submitting the request transaction, you will be provided with an estimated cost (in **LINK**) along with the option to proceed or cancel the transaction. 81 | 82 | ``` 83 | yarn response 84 | ``` 85 | Shows the response from updating the price associated with the `--tokenid` passed into the task arg. 86 | 87 | ## Directories (`Apps`) 88 | There are 3 distinct apps: server, contracts, frontend. 89 | 90 | - [Server](/apps/server/README.md) hosts the API, which is used for demonstration purposes for this tutorial. In a production environment, you will use an API that hosts real data as it pertains to the asset you desire to tokenize. This is only for demonstration purposes and not designed to be used in production. 91 | 92 | - [Contracts](/apps/contracts/README.md) stores the smart contract and the tasks for executing the key functionality for a tokenized Real Estate asset that is connected to Chainlink's Functions. 93 | 94 | - [Frontend](/apps/frontend/README.md) stores the code that hosts the UI we use for demonstration purposes. This is useful for comparing the on-chain data with the off-chain data. In a production environment, you will need to use a frontend that aligns with your use cases. This is simply for the purposes of demonstrating in an educational environment and not designed to help with the issuance or maintanence of a tokenized asset. 95 | 96 | 97 | --- 98 | 99 | # Useful Resources 100 | 101 | - [Chainlink Blog: RWA Explained](https://blog.chain.link/real-world-assets-rwas-explained/) 102 | 103 | - [Education Hub: Tokenized Real Estate](https://chain.link/education-hub/tokenized-real-estate) 104 | 105 | - [Bridge Interactive Data](https://bridgedataoutput.com/docs/explorer/reso-web-api#oShowProperty) 106 | 107 | - [How to: Tokenize an Asset](https://chain.link/education-hub/how-to-tokenize-an-asset) 108 | 109 | - [What is Tokenized Real Estate?](https://chain.link/education-hub/tokenized-real-estate) 110 | 111 | - [Cyfrin: What Are Tokenized Assets?](https://www.cyfrin.io/blog/what-are-tokenized-assets-crypto-rwas-explained) 112 | 113 | - [Tokenized Real World Assets](https://blog.chain.link/tokenized-real-world-assets) 114 | -------------------------------------------------------------------------------- /apps/contracts/.env.example: -------------------------------------------------------------------------------- 1 | AVALANCHE_RPC_URL="https://avalanche.drpc.org" 2 | AVALANCHE_FUJI_RPC_URL="https://api.avax-test.network/ext/bc/C/rpc" 3 | AVALANCHE_FUJI_CHAIN_ID=43113 4 | SNOWTRACE_API_KEY="verifyContract" 5 | 6 | PRIVATE_KEY="" -------------------------------------------------------------------------------- /apps/contracts/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Compiler files 5 | cache/ 6 | out/ 7 | 8 | # Development Broadcast Logs 9 | !/broadcast 10 | broadcast 11 | /broadcast/*/31337/ 12 | /broadcast/**/dry-run/ 13 | 14 | # Docs 15 | # docs/ 16 | 17 | # Environment Variables 18 | .env 19 | .env.local 20 | .env.enc 21 | 22 | # HH Files 23 | build 24 | 25 | # Misc. 26 | .DS_STORE -------------------------------------------------------------------------------- /apps/contracts/.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/rwa-tokenization/427bf7f772d7f7913455b3dddc90ba7cb011f786/apps/contracts/.gitmodules -------------------------------------------------------------------------------- /apps/contracts/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "no-console": "off", 5 | "compiler-version": ["warn", "^0.8.19"], 6 | "quotes": "off", 7 | "func-visibility": ["warn", { "ignoreConstructors": true }], 8 | "no-unused-vars": "off", 9 | "explicit-types": "off" 10 | } 11 | } -------------------------------------------------------------------------------- /apps/contracts/.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | -------------------------------------------------------------------------------- /apps/contracts/Makefile: -------------------------------------------------------------------------------- 1 | -include .env 2 | 3 | .PHONY: all test clean deploy fund help install snapshot format anvil scopefile deploy-bridges 4 | 5 | DEFAULT_ANVIL_KEY := 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 6 | 7 | all: remove install build 8 | 9 | # Clean Repo 10 | clean :; forge clean 11 | 12 | # Remove Modules 13 | remove :; rm -rf .gitmodules && rm -rf .git/modules/* && rm -rf lib && touch .gitmodules && git add . && git commit -m "modules" 14 | 15 | # Install Libs 16 | install :; forge install https://github.com/smartcontractkit/chainlink lib/chainlink --no-commit && forge install https://github.com/smartcontractkit/chainlink-local lib/chainlink-local --no-commit && forge install https://github.com/foundry-rs/forge-std lib/forge-std --no-commit && forge install https://github.com/OpenZeppelin/openzeppelin-contracts lib/openzeppelin-contracts --no-commit 17 | # cyfrin/foundry-devops --no-commit && forge install smartcontractkit/chainlink-brownie-contracts@0.8.0 --no-commit && forge install foundry-rs/forge-std --no-commit && forge install openzeppelin/openzeppelin-contracts --no-commit 18 | 19 | # Update Dependencies 20 | update:; forge update 21 | 22 | build:; forge build 23 | 24 | test :; forge test -------------------------------------------------------------------------------- /apps/contracts/README.md: -------------------------------------------------------------------------------- 1 | # Smart Contracts for RWA Tokenization 2 | 3 | ## Getting Started 4 | - [Install Foundry and Rust](/docs/INSTALL.md) 5 | - [Foundry Guide](/docs/FOUNDRY.md) 6 | 7 | ## Overview of Functions 8 | Chainlink functions enables you to leverage the power of a decentralized oracle network (DON) to execute external function calls (off-chain) to inform on-chain interactions. 9 | 10 | Chainlink is able to execute a user-defined function via a DON, which comes to consensus on the results and reports the median result to the requesting contract via a callback function. 11 | 12 | --- 13 | 14 | ## Functions Workflow 15 | 16 | ### 0. Install Dependencies & Build 17 | ``` 18 | yarn && yarn build && make 19 | ``` 20 | 21 | ### 1. Setup Environment Variables 22 | 23 | #### Create Password 24 | Chainlink Functions enables you to securely share secrets with the DON. Secrets are encrypted with a password. 25 | ``` 26 | npx env-enc set-pw 27 | ``` 28 | Once the ecrpytion key is created with your desired password, you can safely share your secrets with the DON, which requires multiple nodes to decrypt with consensus. 29 | 30 | #### Store Variables 31 | 32 | We may now safely store environment variables without worrying about them being exposed, since they will be encrypted with your desired password. 33 | 34 | These variables will be stored in a file called `.env.enc`. 35 | 36 | ``` 37 | npx env-enc set 38 | ``` 39 | After running the command, you'll be prompted to enter the following for each variable to be encrypted: 40 | 41 | - **Name**: used to identify the variable. 42 | 43 | - **Value**: your (*unencrypted*) environment variable (*secret*). 44 | 45 | For this demonstration, you will need to add the following to your encrypted environment variables: 46 | - `PRIVATE_KEY` 47 | 48 | **Note**: you may verify your environment variables are set by running `npx env-enc view`. 49 | 50 | ### 2. Simulate Functions 51 | Before deploying, it's useful to simulate the execution of your function to ensure the output of your function execution is as expected. 52 | 53 | You may simulate your function using the command below. 54 | 55 | ``` 56 | node script/getPrice.js 57 | ``` 58 | 59 | For full details on creating HTTP requestion via functions, read our [API Reference Documentation](https://docs.chain.link/chainlink-functions/api-reference/javascript-source). 60 | 61 | ### 3. Deploy Consumer 62 | 63 | ``` 64 | forge script script/RealEstate.s.sol:RealEstateScript --rpc-url --broadcast 65 | ``` 66 | 67 | **Note**: ensure you have updated the deployment script to align with your target blockchain configurations. 68 | 69 | ### 4. Create Subscription 70 | Fund a new Functions billing subscription via the [Chainlink Functions UI](https://functions.chain.link/) and add your deployed Consumer Contract as a an authorized consumer to your subscription OR do so programmatically, as follows:
71 | ``` 72 | npx hardhat func-sub-create --network --amount --contract 73 | ``` 74 | 75 | ### 5. Make Requests 76 | Functions enable you to make requests via the consumer contract. Before requesting, make sure you have successfully compiled your Consumer Contract (`RealEstate.sol`), otherwise the request will fail to process. 77 | 78 | You may do this programmatically with:
79 | ``` 80 | npx hardhat func-request --network --contract --subid --tokenid 81 | ``` 82 | 83 | You will see a confirmation request, so hit `Y` and press enter. 84 | 85 | Once the request is fulfilled the console will show the response (decoded into the relevant return type) from the execution of your custom JS script. 86 | 87 | ### 6. Make Queries 88 | You are also able to query the response that was stored in your Functions Consumer contract either through the [Functions UI](https://functions.chain.link/) or programmatically as follows:
89 | ``` 90 | npx hardhat func-response --network --contract --tokenid 91 | ``` 92 | -------------------------------------------------------------------------------- /apps/contracts/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | via_ir = true 7 | 8 | remappings = [ 9 | '@chainlink/contracts/=lib/chainlink/contracts/', 10 | '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', 11 | '@chainlink/local/=lib/chainlink-local/', 12 | ] 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 15 | -------------------------------------------------------------------------------- /apps/contracts/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomicfoundation/hardhat-foundry"); 2 | require("@nomicfoundation/hardhat-toolbox") 3 | require("hardhat-contract-sizer") 4 | require("./tasks") 5 | 6 | const { networks } = require("./networks") 7 | 8 | // Enable gas reporting (optional) 9 | const REPORT_GAS = process.env.REPORT_GAS?.toLowerCase() === "true" ? true : false 10 | 11 | const SOLC_SETTINGS = { 12 | optimizer: { 13 | enabled: true, 14 | runs: 1_000, 15 | }, 16 | } 17 | 18 | /** @type import('hardhat/config').HardhatUserConfig */ 19 | module.exports = { 20 | defaultNetwork: "avalancheFuji", 21 | solidity: { 22 | compilers: [ 23 | { 24 | version: "0.8.20", 25 | settings: SOLC_SETTINGS, 26 | }, 27 | { 28 | version: "0.8.19", 29 | settings: SOLC_SETTINGS, 30 | }, 31 | { 32 | version: "0.8.7", 33 | settings: SOLC_SETTINGS, 34 | }, 35 | { 36 | version: "0.7.0", 37 | settings: SOLC_SETTINGS, 38 | }, 39 | { 40 | version: "0.6.6", 41 | settings: SOLC_SETTINGS, 42 | }, 43 | { 44 | version: "0.4.24", 45 | settings: SOLC_SETTINGS, 46 | }, 47 | ], 48 | }, 49 | networks: { 50 | ...networks, 51 | }, 52 | etherscan: { 53 | apiKey: { 54 | avalancheFuji: networks.avalanche.verifyApiKey 55 | }, 56 | }, 57 | gasReporter: { 58 | enabled: REPORT_GAS, 59 | currency: "USD", 60 | outputFile: "gas-report.txt", 61 | noColors: true, 62 | }, 63 | contractSizer: { 64 | runOnCompile: false, 65 | only: [ 66 | "RealEstate" 67 | ], 68 | }, 69 | paths: { 70 | sources: "./src", 71 | tests: "./test", 72 | cache: "./build/cache", 73 | artifacts: "./build/artifacts", 74 | }, 75 | mocha: { 76 | timeout: 200000, // 200 seconds max for running tests 77 | }, 78 | } 79 | -------------------------------------------------------------------------------- /apps/contracts/networks.js: -------------------------------------------------------------------------------- 1 | // All supported networks and related contract addresses are defined here. 2 | // 3 | // LINK token addresses: https://docs.chain.link/resources/link-token-contracts/ 4 | // Price feeds addresses: https://docs.chain.link/data-feeds/price-feeds/addresses 5 | // Chain IDs: https://chainlist.org/?testnets=true 6 | 7 | // Loads environment variables from .env.enc file (if it exists) 8 | require("@chainlink/env-enc").config() 9 | 10 | const DEFAULT_VERIFICATION_BLOCK_CONFIRMATIONS = 2 11 | 12 | const npmCommand = process.env.npm_lifecycle_event 13 | const isTestEnvironment = npmCommand == "test" || npmCommand == "test:unit" 14 | 15 | // Set EVM private keys (required) 16 | const PRIVATE_KEY = process.env.PRIVATE_KEY 17 | 18 | // TODO @dev - set this to run the accept.js task. 19 | const SECOND_PRIVATE_KEY = process.env.SECOND_PRIVATE_KEY 20 | 21 | if (!isTestEnvironment && !PRIVATE_KEY) { 22 | throw Error("Set the PRIVATE_KEY environment variable with your EVM wallet private key") 23 | } 24 | 25 | const accounts = [] 26 | if (PRIVATE_KEY) { 27 | accounts.push(PRIVATE_KEY) 28 | } 29 | if (SECOND_PRIVATE_KEY) { 30 | accounts.push(SECOND_PRIVATE_KEY) 31 | } 32 | 33 | // console.log(`Using accounts: ${accounts}`) 34 | 35 | const networks = { 36 | avalanche: { 37 | url: "https://api.avax.network/ext/bc/C/rpc", 38 | gasPrice: undefined, 39 | nonce: undefined, 40 | accounts, 41 | verifyApiKey: "verifyContract", 42 | chainId: 43114, 43 | confirmations: DEFAULT_VERIFICATION_BLOCK_CONFIRMATIONS, 44 | nativeCurrencySymbol: "AVAX", 45 | linkToken: "0x5947BB275c521040051D82396192181b413227A3", 46 | linkPriceFeed: "0x1b8a25F73c9420dD507406C3A3816A276b62f56a", // LINK/AVAX 47 | functionsRouter: "0x9f82a6A0758517FD0AfA463820F586999AF314a0", 48 | donId: "fun-avalanche-mainnet-1", 49 | gatewayUrls: ["https://01.functions-gateway.chain.link/", "https://02.functions-gateway.chain.link/"], 50 | }, 51 | avalancheFuji: { 52 | // url: "https://api.avax-test.network/ext/bc/C/rpc", 53 | url: 'https://ava-testnet.public.blastapi.io/ext/bc/C/rpc', 54 | // url: 'https://rpc.ankr.com/avalanche_fuji', 55 | gasPrice: undefined, 56 | nonce: undefined, 57 | accounts, 58 | verifyApiKey: "verifyContract", 59 | chainId: 43113, 60 | confirmations: DEFAULT_VERIFICATION_BLOCK_CONFIRMATIONS, 61 | nativeCurrencySymbol: "AVAX", 62 | linkToken: "0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846", 63 | linkPriceFeed: "0x79c91fd4F8b3DaBEe17d286EB11cEE4D83521775", // LINK/AVAX 64 | functionsRouter: "0xA9d587a00A31A52Ed70D6026794a8FC5E2F5dCb0", 65 | donId: "fun-avalanche-fuji-1", 66 | gatewayUrls: [ 67 | "https://01.functions-gateway.testnet.chain.link/", 68 | "https://02.functions-gateway.testnet.chain.link/", 69 | ], 70 | }, 71 | // localFunctionsTestnet is updated dynamically by scripts/startLocal.js so it should not be modified here 72 | localFunctionsTestnet: { 73 | url: "http://localhost:8545/", 74 | accounts, 75 | confirmations: 1, 76 | nativeCurrencySymbol: "ETH", 77 | verifyApiKey: "UNSET", 78 | linkToken: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", 79 | functionsRouter: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", 80 | donId: "local-functions-testnet", 81 | }, 82 | } 83 | 84 | module.exports = { 85 | networks, 86 | } -------------------------------------------------------------------------------- /apps/contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rwa-tokenization-contracts", 3 | "version": "1.0.0", 4 | "description": "RWA tokenization using Chainlink Functions on Avalanche.", 5 | "main": "index.js", 6 | "repository": "https://github.com/SmartContractKit/rwa-tokenization.git", 7 | "author": "Buns Enchantress ", 8 | "license": "MIT", 9 | "scripts": { 10 | "signin": "npx env-enc set-pw", 11 | "setup": "npx env-enc set", 12 | "build": "npx hardhat compile", 13 | 14 | "price": "node script/getPrice.js", 15 | "flat": "forge flatten --output src/flats/RealEstate.sol src/RealEstate.sol", 16 | "deploy": "forge script script/RealEstate.s.sol:RealEstateScript --rpc-url https://api.avax-test.network/ext/bc/C/rpc --broadcast", 17 | 18 | "sub:create": "npx hardhat func-sub-create --network avalancheFuji --amount 2 --contract 0x73C6a46cCffdbDD3E02d263216821C2759EBFa7d", 19 | "assign": "npx hardhat func-sub-add --network avalancheFuji --contract 0x73C6a46cCffdbDD3E02d263216821C2759EBFa7d --subid 10790", 20 | 21 | "request": "npx hardhat func-request --network avalancheFuji --contract 0x73C6a46cCffdbDD3E02d263216821C2759EBFa7d --subid 10790 --tokenid 0", 22 | "response": "npx hardhat func-response --network avalancheFuji --contract 0x73C6a46cCffdbDD3E02d263216821C2759EBFa7d --tokenid 0" 23 | }, 24 | "dependencies": { 25 | "@chainlink/env-enc": "^1.0.5", 26 | "@chainlink/functions-toolkit": "^0.2.8" 27 | }, 28 | "devDependencies": { 29 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", 30 | "@nomicfoundation/hardhat-network-helpers": "^1.0.6", 31 | "@nomicfoundation/hardhat-toolbox": "^2.0.0", 32 | "@nomicfoundation/hardhat-ethers": "^3.0.5", 33 | "@nomicfoundation/hardhat-foundry": "^1.1.1", 34 | "@nomiclabs/hardhat-ethers": "^2.2.2", 35 | "@nomiclabs/hardhat-etherscan": "^3.1.0", 36 | "@typechain/ethers-v5": "^10.1.0", 37 | "@typechain/hardhat": "^9.1.0", 38 | "chai": "^4.3.6", 39 | "hardhat": "^2.22.2", 40 | "hardhat-contract-sizer": "^2.6.1", 41 | "hardhat-gas-reporter": "^2.1.0", 42 | "ora": "5.4.1", 43 | "solidity-coverage": "^0.8.12", 44 | "typechain": "^8.3.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /apps/contracts/priceConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const { Location, ReturnType, CodeLanguage } = require("@chainlink/functions-toolkit") 3 | 4 | // loads: environment variables from .env.enc file (if exists) 5 | require("@chainlink/env-enc").config() 6 | 7 | // configures request: via settings in the fields below 8 | const priceConfig = { 9 | 10 | // source code location 11 | codeLocation: Location.Inline, 12 | 13 | // code language (JavaScript only) 14 | codeLanguage: CodeLanguage.JavaScript, 15 | 16 | // source code to be executed 17 | source: fs.readFileSync("priceRequest.js").toString(), 18 | 19 | // (optional) accessed within the source code with `secrets.varName` (ie: secrets.apiKey), must be a string. 20 | secrets: { 21 | // apiKey: process.env.API_KEY 22 | }, 23 | 24 | // args (array[""]): source code accesses via `args[index]`. 25 | args: ["0"], 26 | 27 | // shows: expected type of the returned value. 28 | expectedReturnType: ReturnType.uint256, 29 | 30 | // Per-node secrets objects assigned to each DON member. When using per-node secrets, nodes can only use secrets which they have been assigned. 31 | perNodeSecrets: [], 32 | 33 | // ETH wallet key used to sign secrets so they cannot be accessed by a 3rd party 34 | walletPrivateKey: process.env["PRIVATE_KEY"], 35 | 36 | // Redundant URLs which point to encrypted off-chain secrets 37 | secretsURLs: [], 38 | } 39 | 40 | module.exports = priceConfig -------------------------------------------------------------------------------- /apps/contracts/priceRequest.js: -------------------------------------------------------------------------------- 1 | const tokenId = args[0]; 2 | 3 | const apiResponse = await Functions.makeHttpRequest({ 4 | url: `https://api.chateau.voyage/house/${tokenId}`, 5 | }); 6 | 7 | const listPrice = Number(apiResponse.data.listPrice); 8 | 9 | console.log(`List Price: ${listPrice}`); 10 | 11 | return listPrice; -------------------------------------------------------------------------------- /apps/contracts/script/RealEstate.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.7.0; 3 | 4 | import "forge-std/Script.sol"; 5 | import "../src/RealEstate.sol"; 6 | 7 | contract RealEstateScript is Script { 8 | // network-specific settings: verify target network configurations. 9 | bytes32 public immutable DON_ID = bytes32(0x66756e2d6176616c616e6368652d66756a692d31000000000000000000000000); 10 | address public immutable FUNCTIONS_ROUTER_ADDRESS = address(0xA9d587a00A31A52Ed70D6026794a8FC5E2F5dCb0); 11 | uint32 public immutable GAS_LIMIT = 300_000; 12 | uint64 public immutable SUBSCRIPTION_ID = 10790; // note: update for you own subId 13 | uint public immutable EPOCH = 120; // 2 minutes 14 | 15 | function run() external { 16 | uint deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 17 | vm.startBroadcast(deployerPrivateKey); 18 | 19 | // REFER TO DOCS FOR UP-TO-DATE ADDRESSES 20 | // https://docs.chain.link/chainlink-functions/supported-networks 21 | 22 | RealEstate realEstate = new RealEstate( 23 | FUNCTIONS_ROUTER_ADDRESS, 24 | DON_ID, 25 | SUBSCRIPTION_ID, 26 | GAS_LIMIT, 27 | EPOCH 28 | ); 29 | 30 | realEstate.issueHouse( 31 | 0x6D3cF2d1D7e113a2687dF9080CC490e176F53760, 32 | '759 Main Lane', 33 | '1200000', 34 | '6500' 35 | ); 36 | 37 | realEstate.issueHouse( 38 | 0x6D3cF2d1D7e113a2687dF9080CC490e176F53760, 39 | '5754 Princess Boulevard', 40 | '360000', 41 | '4500' 42 | ); 43 | 44 | realEstate.issueHouse( 45 | 0x6D3cF2d1D7e113a2687dF9080CC490e176F53760, 46 | '79560 Globe Row', 47 | '225000', 48 | '2250' 49 | ); 50 | 51 | realEstate.issueHouse( 52 | 0x6D3cF2d1D7e113a2687dF9080CC490e176F53760, 53 | '25 Copper Passage', 54 | '460000', 55 | '3260' 56 | ); 57 | 58 | vm.stopBroadcast(); 59 | } 60 | } -------------------------------------------------------------------------------- /apps/contracts/script/getPrice.js: -------------------------------------------------------------------------------- 1 | const process = require("process") 2 | const path = require("path") 3 | const { simulateScript, decodeResult } = require("@chainlink/functions-toolkit") 4 | require("@chainlink/env-enc").config(); 5 | 6 | const sendRequest = async () => { 7 | // @dev Update this to point to your desired request config file. 8 | const requestConfigPath = path.join(process.cwd(), "priceConfig.js") 9 | // console.log(`Using Functions request config file ${requestConfigPath}\n`) 10 | const requestConfig = require(requestConfigPath); 11 | 12 | // simulate: JavaScript execution locally. 13 | const { responseBytesHexstring, /*errorString,*/ capturedTerminalOutput } = await simulateScript(requestConfig) 14 | console.log(`${capturedTerminalOutput}\n`) 15 | if (responseBytesHexstring) { 16 | console.log( 17 | `Response returned by script during local simulation: 18 | ${decodeResult( 19 | responseBytesHexstring, 20 | requestConfig.expectedReturnType 21 | ).toString()}\n` 22 | ) 23 | } 24 | // if (errorString) { 25 | // console.log(`Error returned by simulated script:\n${errorString}\n`) 26 | // } 27 | }; 28 | 29 | sendRequest().catch((err) => { 30 | console.log("\nError running source simulator: ", err); 31 | }); -------------------------------------------------------------------------------- /apps/contracts/script/helpers/BaseScript.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import "forge-std/Script.sol"; 5 | import { VmSafe } from "forge-std/Vm.sol"; 6 | 7 | contract BaseScript is Script { 8 | /// @notice Manages the broadcast context for nested script calls. 9 | /// @dev This modifier is used to maintains the original msg.sender identity across nested script calls. 10 | modifier nestedScriptContext { 11 | VmSafe.CallerMode callerMode; 12 | (callerMode,,) = vm.readCallers(); 13 | 14 | bool isNestedCall = msg.sender != DEFAULT_SENDER; 15 | 16 | if (isNestedCall) { 17 | endCurrentContext(callerMode); 18 | vm.startBroadcast(msg.sender); 19 | _; 20 | vm.stopBroadcast(); 21 | } else { 22 | _; 23 | } 24 | } 25 | 26 | /// @dev Ends the current context based on the caller mode. 27 | function endCurrentContext(VmSafe.CallerMode callerMode) internal { 28 | if (uint(callerMode) >= uint(VmSafe.CallerMode.Prank)) { 29 | vm.stopPrank(); 30 | } else if (uint(callerMode) >= uint(VmSafe.CallerMode.Broadcast)) { 31 | vm.stopBroadcast(); 32 | } 33 | } 34 | 35 | function run() external pure {} 36 | } 37 | -------------------------------------------------------------------------------- /apps/contracts/src/RealEstate.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.19; 3 | import { FunctionsClient } from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol"; 4 | import { FunctionsRequest } from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol"; 5 | import { ConfirmedOwner } from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol"; 6 | 7 | import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; 8 | import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 9 | import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; 10 | 11 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 12 | import { ERC721URIStorage } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; 13 | import { ERC721Burnable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; 14 | 15 | /** 16 | * @title Chainlink Functions example consuming Real Estate API 17 | */ 18 | contract RealEstate is 19 | FunctionsClient, 20 | ConfirmedOwner, 21 | ERC721("Tokenized Real Estate", "tRE"), 22 | ERC721URIStorage, 23 | ERC721Burnable 24 | { 25 | using FunctionsRequest for FunctionsRequest.Request; 26 | using SafeERC20 for IERC20; 27 | 28 | struct APIResponse { 29 | uint index; 30 | string tokenId; 31 | string response; 32 | } 33 | 34 | struct House { 35 | string tokenId; 36 | address recipientAddress; 37 | string homeAddress; 38 | string listPrice; 39 | string squareFootage; 40 | uint createTime; 41 | uint lastUpdate; 42 | } 43 | 44 | House[] public houseInfo; 45 | 46 | // Chainlink Functions script source code. 47 | string private constant SOURCE_PRICE_INFO = 48 | "const id = args[0];" 49 | "const priceResponse = await Functions.makeHttpRequest({" 50 | "url: `https://api.chateau.voyage/house/${id}`," 51 | "});" 52 | "if (priceResponse.error) {" 53 | "throw Error('Housing Price Request Error');" 54 | "}" 55 | "const price = priceResponse.data.latestValue;" 56 | "return Functions.encodeString(price);"; 57 | 58 | bytes32 public donId; // DON ID for the Functions DON to which the requests are sent 59 | uint64 private subscriptionId; // Subscription ID for the Chainlink Functions 60 | uint32 private gasLimit; // Gas limit for the Chainlink Functions callbacks 61 | uint public epoch; // Time interval for price updates. 62 | uint private _totalHouses; 63 | 64 | // Mapping of request IDs to API response info 65 | mapping(bytes32 => APIResponse) public requests; 66 | mapping(string => bytes32) public latestRequestId; 67 | mapping(string tokenId => string price) public latestPrice; 68 | 69 | 70 | event LastPriceRequested(bytes32 indexed requestId, string tokenId); 71 | event LastPriceReceived(bytes32 indexed requestId, string response); 72 | 73 | event RequestFailed(bytes error); 74 | 75 | constructor( 76 | address router, 77 | bytes32 _donId, 78 | uint64 _subscriptionId, 79 | uint32 _gasLimit, 80 | uint _epoch 81 | ) FunctionsClient(router) ConfirmedOwner(msg.sender) { 82 | donId = _donId; 83 | subscriptionId = _subscriptionId; 84 | gasLimit = _gasLimit; 85 | epoch = _epoch; 86 | } 87 | 88 | /** 89 | * @notice Issues new tokenized real estate NFT asset. 90 | */ 91 | function issueHouse( 92 | address recipientAddress, 93 | string memory homeAddress, 94 | string memory listPrice, 95 | string memory squareFootage 96 | ) external onlyOwner { 97 | uint index = _totalHouses; 98 | string memory tokenId = string(abi.encode(index)); 99 | 100 | // increase: _totalHouses. 101 | _totalHouses++; 102 | 103 | // create: instance of a House. 104 | houseInfo.push(House({ 105 | tokenId: tokenId, 106 | recipientAddress: recipientAddress, 107 | homeAddress: homeAddress, 108 | listPrice: listPrice, 109 | squareFootage: squareFootage, 110 | createTime: block.timestamp, 111 | lastUpdate: block.timestamp 112 | })); 113 | 114 | setURI( 115 | index, 116 | homeAddress, 117 | listPrice, 118 | squareFootage 119 | ); 120 | 121 | _safeMint(recipientAddress, index); 122 | } 123 | 124 | /** 125 | * @notice Request `lastPrice` for a given `tokenId` 126 | * @param tokenId id of said token e.g. 0 127 | */ 128 | function requestPrice(string calldata tokenId, uint index) external { 129 | string[] memory args = new string[](1); 130 | args[0] = tokenId; 131 | 132 | // gets: houseInfo[tokenId] 133 | House storage house = houseInfo[index]; 134 | 135 | // ensures: price update is not too soon (i.e. not until a full epoch elapsed). 136 | require(block.timestamp - house.lastUpdate >= epoch, "RealEstate: Price update too soon"); 137 | 138 | bytes32 requestId = _sendRequest(SOURCE_PRICE_INFO, args); 139 | // maps: `tokenId` associated with a given `requestId`. 140 | requests[requestId].tokenId = tokenId; 141 | // maps: `index` associated with a given `requestId`. 142 | requests[requestId].index = index; 143 | 144 | latestRequestId[tokenId] = requestId; 145 | 146 | emit LastPriceRequested(requestId, tokenId); 147 | } 148 | 149 | /** 150 | * @notice Construct and store a URI containing the off-chain data. 151 | * @param tokenId the tokenId associated with the home. 152 | * @param homeAddress the address of the home. 153 | * @param listPrice year the home was built. 154 | * @param squareFootage size of the home (in ft^2) 155 | */ 156 | function setURI( 157 | uint tokenId, 158 | string memory homeAddress, 159 | string memory listPrice, 160 | string memory squareFootage 161 | ) internal { 162 | // [then] create URI: with property details. 163 | string memory uri = Base64.encode( 164 | bytes( 165 | string( 166 | abi.encodePacked( 167 | '{"name": "Tokenized Real Estate",' 168 | '"description": "Tokenized Real Estate",', 169 | '"image": "",' 170 | '"attributes": [', 171 | '{"trait_type": "homeAddress",', 172 | '"value": ', 173 | homeAddress, 174 | "}", 175 | ',{"trait_type": "listPrice",', 176 | '"value": ', 177 | listPrice, 178 | "}", 179 | ',{"trait_type": "squareFootage",', 180 | '"value": ', 181 | squareFootage, 182 | "}", 183 | "]}" 184 | ) 185 | ) 186 | ) 187 | ); 188 | // [then] create: finalTokenURI: with metadata. 189 | string memory finalTokenURI = string(abi.encodePacked("data:application/json;base64,", uri)); 190 | 191 | // [then] set: tokenURI for a given `tokenId`, containing metadata. 192 | _setTokenURI(tokenId, finalTokenURI); 193 | 194 | } 195 | 196 | /** 197 | * @notice Process the response from the executed Chainlink Functions script 198 | * @param requestId The request ID 199 | * @param response The response from the Chainlink Functions script 200 | */ 201 | function _processResponse( 202 | bytes32 requestId, 203 | bytes memory response 204 | ) private { 205 | requests[requestId].response = string(response); 206 | 207 | uint index = requests[requestId].index; 208 | string memory tokenId = requests[requestId].tokenId; 209 | 210 | // store: latest price for a given `tokenId`. 211 | latestPrice[tokenId] = string(response); 212 | 213 | // gets: houseInfo[tokenId] 214 | House storage house = houseInfo[index]; 215 | 216 | // updates: listPrice for a given `tokenId`. 217 | house.listPrice = string(response); 218 | // updates: lastUpdate for a given `tokenId`. 219 | house.lastUpdate = block.timestamp; 220 | 221 | emit LastPriceReceived(requestId, string(response)); 222 | } 223 | 224 | // CHAINLINK FUNCTIONS // 225 | 226 | /** 227 | * @notice Triggers an on-demand Functions request 228 | * @param args String arguments passed into the source code and accessible via the global variable `args` 229 | */ 230 | function _sendRequest( 231 | string memory source, 232 | string[] memory args 233 | ) internal returns (bytes32 requestId) { 234 | FunctionsRequest.Request memory req; 235 | req.initializeRequest( 236 | FunctionsRequest.Location.Inline, 237 | FunctionsRequest.CodeLanguage.JavaScript, 238 | source 239 | ); 240 | if (args.length > 0) { 241 | req.setArgs(args); 242 | } 243 | requestId = _sendRequest( 244 | req.encodeCBOR(), 245 | subscriptionId, 246 | gasLimit, 247 | donId 248 | ); 249 | } 250 | 251 | /** 252 | * @notice Fulfillment callback function 253 | * @param requestId The request ID, returned by sendRequest() 254 | * @param response Aggregated response from the user code 255 | * @param err Aggregated error from the user code or from the execution pipeline 256 | * Either response or error parameter will be set, but never both 257 | */ 258 | function fulfillRequest( 259 | bytes32 requestId, 260 | bytes memory response, 261 | bytes memory err 262 | ) internal override { 263 | if (err.length > 0) { 264 | emit RequestFailed(err); 265 | return; 266 | } 267 | _processResponse(requestId, response); 268 | } 269 | 270 | // ERC721 SETTINGS // 271 | 272 | // gets: tokenURI for a given `tokenId`. 273 | function tokenURI( 274 | uint tokenId 275 | ) public view override(ERC721, ERC721URIStorage) returns (string memory) { 276 | return super.tokenURI(tokenId); 277 | } 278 | 279 | // checks: interface is supported by this contract. 280 | function supportsInterface( 281 | bytes4 interfaceId 282 | ) public view override(ERC721, ERC721URIStorage) returns (bool) { 283 | return super.supportsInterface(interfaceId); 284 | } 285 | 286 | function totalHouses() public view returns (uint) { 287 | return _totalHouses; 288 | } 289 | 290 | // OWNER SETTING // 291 | 292 | // prevents excessive calls from UI. 293 | function setEpoch(uint _epoch) public onlyOwner { 294 | epoch = _epoch; 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /apps/contracts/tasks/billing/add.js: -------------------------------------------------------------------------------- 1 | const { SubscriptionManager } = require("@chainlink/functions-toolkit") 2 | const { networks } = require("../../networks") 3 | 4 | task("func-sub-add", "Adds a consumer contract to the Functions billing subscription") 5 | .addParam("subid", "Subscription ID") 6 | .addParam("contract", "Address of the Functions consumer contract to authorize for billing") 7 | .setAction(async (taskArgs) => { 8 | const consumerAddress = taskArgs.contract 9 | const subscriptionId = parseInt(taskArgs.subid) 10 | 11 | const signer = await ethers.getSigner() 12 | const linkTokenAddress = networks[network.name]["linkToken"] 13 | const functionsRouterAddress = networks[network.name]["functionsRouter"] 14 | const txOptions = { confirmations: networks[network.name].confirmations } 15 | 16 | const sm = new SubscriptionManager({ signer, linkTokenAddress, functionsRouterAddress }) 17 | await sm.initialize() 18 | 19 | console.log(`\nAdding ${consumerAddress} to subscription ${subscriptionId}...`) 20 | const addConsumerTx = await sm.addConsumer({ subscriptionId, consumerAddress, txOptions }) 21 | console.log(`Added consumer contract ${consumerAddress} in Tx: ${addConsumerTx.transactionHash}`) 22 | }) 23 | -------------------------------------------------------------------------------- /apps/contracts/tasks/billing/create.js: -------------------------------------------------------------------------------- 1 | const { SubscriptionManager } = require("@chainlink/functions-toolkit") 2 | const chalk = require("chalk") 3 | const { networks } = require("../../networks") 4 | const utils = require("../utils") 5 | 6 | task("func-sub-create", "Creates a new billing subscription for Functions consumer contracts") 7 | .addOptionalParam("amount", "Initial amount used to fund the subscription in LINK") 8 | .addOptionalParam( 9 | "contract", 10 | "Address of the consumer contract address authorized to use the new billing subscription" 11 | ) 12 | .setAction(async (taskArgs) => { 13 | const signer = await ethers.getSigner() 14 | console.log('network: %s', network) 15 | const functionsRouterAddress = networks[network.name]["functionsRouter"] 16 | console.log('functionsRouter: %s', networks[network.name]["functionsRouter"]) 17 | 18 | const linkTokenAddress = networks[network.name]["linkToken"] 19 | console.log('linkTokenAddress: %s', linkTokenAddress) 20 | 21 | const linkAmount = taskArgs.amount 22 | const confirmations = linkAmount > 0 ? networks[network.name].confirmations : 1 23 | const consumerAddress = taskArgs.contract 24 | console.log('consumerAddress: %s', consumerAddress) 25 | const txOptions = { confirmations } 26 | 27 | const sm = new SubscriptionManager({ signer, linkTokenAddress, functionsRouterAddress }) 28 | await sm.initialize() 29 | 30 | console.log("\nCreating Functions billing subscription...") 31 | const subscriptionId = await sm.createSubscription({ consumerAddress, txOptions }) 32 | console.log(`\nCreated Functions billing subscription: ${subscriptionId}`) 33 | 34 | // Fund subscription 35 | if (linkAmount) { 36 | await utils.prompt( 37 | `\nPlease confirm that you wish to fund Subscription ${subscriptionId} with ${chalk.blue( 38 | linkAmount + " LINK" 39 | )} from your wallet.` 40 | ) 41 | 42 | console.log(`\nFunding subscription ${subscriptionId} with ${linkAmount} LINK...`) 43 | const juelsAmount = ethers.utils.parseUnits(linkAmount, 18).toString() 44 | const fundTxReceipt = await sm.fundSubscription({ juelsAmount, subscriptionId, txOptions }) 45 | console.log( 46 | `\nSubscription ${subscriptionId} funded with ${linkAmount} LINK in Tx: ${fundTxReceipt.transactionHash}` 47 | ) 48 | 49 | const subInfo = await sm.getSubscriptionInfo(subscriptionId) 50 | // parse balances into LINK for readability 51 | subInfo.balance = ethers.utils.formatEther(subInfo.balance) + " LINK" 52 | subInfo.blockedBalance = ethers.utils.formatEther(subInfo.blockedBalance) + " LINK" 53 | 54 | console.log("\nSubscription Info: ", subInfo) 55 | } 56 | }) 57 | -------------------------------------------------------------------------------- /apps/contracts/tasks/billing/fund.js: -------------------------------------------------------------------------------- 1 | const { SubscriptionManager } = require("@chainlink/functions-toolkit") 2 | const chalk = require("chalk") 3 | const { networks } = require("../../networks") 4 | const utils = require("../utils") 5 | 6 | task("func-sub-fund", "Funds a billing subscription for Functions consumer contracts") 7 | .addParam("amount", "Amount to fund subscription in LINK") 8 | .addParam("subid", "Subscription ID to fund") 9 | .setAction(async (taskArgs) => { 10 | const signer = await ethers.getSigner() 11 | const linkTokenAddress = networks[network.name]["linkToken"] 12 | const functionsRouterAddress = networks[network.name]["functionsRouter"] 13 | const txOptions = { confirmations: networks[network.name].confirmations } 14 | 15 | const subscriptionId = parseInt(taskArgs.subid) 16 | const linkAmount = taskArgs.amount 17 | const juelsAmount = ethers.utils.parseUnits(linkAmount, 18).toString() 18 | 19 | const sm = new SubscriptionManager({ signer, linkTokenAddress, functionsRouterAddress }) 20 | await sm.initialize() 21 | 22 | await utils.prompt( 23 | `\nPlease confirm that you wish to fund Subscription ${subscriptionId} with ${chalk.blue( 24 | linkAmount + " LINK" 25 | )} from your wallet.` 26 | ) 27 | 28 | console.log(`\nFunding subscription ${subscriptionId} with ${linkAmount} LINK...`) 29 | 30 | const fundTxReceipt = await sm.fundSubscription({ juelsAmount, subscriptionId, txOptions }) 31 | console.log( 32 | `\nSubscription ${subscriptionId} funded with ${linkAmount} LINK in Tx: ${fundTxReceipt.transactionHash}` 33 | ) 34 | 35 | const subInfo = await sm.getSubscriptionInfo(subscriptionId) 36 | 37 | // parse balances into LINK for readability 38 | subInfo.balance = ethers.utils.formatEther(subInfo.balance) + " LINK" 39 | subInfo.blockedBalance = ethers.utils.formatEther(subInfo.blockedBalance) + " LINK" 40 | 41 | console.log("\nUpdated subscription Info: ", subInfo) 42 | }) 43 | -------------------------------------------------------------------------------- /apps/contracts/tasks/billing/index.js: -------------------------------------------------------------------------------- 1 | exports.create = require("./create") 2 | exports.fund = require("./fund") 3 | exports.add = require("./add") 4 | -------------------------------------------------------------------------------- /apps/contracts/tasks/consumer/index.js: -------------------------------------------------------------------------------- 1 | exports.readData = require("./response.js") 2 | exports.requestData = require("./request.js") 3 | -------------------------------------------------------------------------------- /apps/contracts/tasks/consumer/response.js: -------------------------------------------------------------------------------- 1 | const { decodeResult } = require("@chainlink/functions-toolkit") 2 | const path = require("path") 3 | const process = require("process") 4 | 5 | task( 6 | "func-response", 7 | "Reads the latest response (or error) returned to a FunctionsConsumer via tokenid" 8 | ) 9 | .addParam("contract", "Address of the consumer contract to read") 10 | .addParam("tokenid", "tokenid to read") 11 | .addOptionalParam( 12 | "configpath", 13 | "Path to Functions request config file", 14 | `${__dirname}/../../priceConfig.js`, 15 | types.string 16 | ) 17 | .setAction(async (taskArgs) => { 18 | console.log(`Reading data from Functions consumer contract ${taskArgs.contract} on network ${network.name}`) 19 | const consumerContractFactory = await ethers.getContractFactory("RealEstate") 20 | const consumerContract = await consumerContractFactory.attach(taskArgs.contract) 21 | const tokenid = parseInt(taskArgs.tokenid) 22 | const houseInfo = await consumerContract.houseInfo(tokenid) 23 | 24 | // get the result from the on-chain response and display it. 25 | let latestResponse = Number(houseInfo.listPrice) 26 | console.log("\nOn-Chain response (listPrice): ", latestResponse.toLocaleString( 27 | "en-US", 28 | { style: "currency", currency: "USD" } 29 | )) 30 | if (latestResponse.length > 0 && latestResponse !== "0x") { 31 | const requestConfig = require(path.isAbsolute(taskArgs.configpath) 32 | ? taskArgs.configpath 33 | : path.join(process.cwd(), taskArgs.configpath)) 34 | const decodedResult = decodeResult(latestResponse, requestConfig.expectedReturnType).toString() 35 | console.log(`\nOn-chain response represented as a hex string: ${latestResponse}\n${ 36 | `Decoded as: ${decodedResult}`}`) 37 | } else if (latestResponse == "0x") { 38 | console.log("Empty Response: ", latestResponse) 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /apps/contracts/tasks/index.js: -------------------------------------------------------------------------------- 1 | exports.Consumer = require("./consumer") 2 | exports.Billing = require("./billing") 3 | -------------------------------------------------------------------------------- /apps/contracts/tasks/utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require("./network.js"), 3 | ...require("./price.js"), 4 | ...require("./prompt.js"), 5 | ...require("./spin.js"), 6 | ...require("./logger.js"), 7 | } 8 | -------------------------------------------------------------------------------- /apps/contracts/tasks/utils/logger.js: -------------------------------------------------------------------------------- 1 | const { Console } = require("console") 2 | const { Transform } = require("stream") 3 | 4 | function table(input) { 5 | // @see https://stackoverflow.com/a/67859384 6 | const ts = new Transform({ 7 | transform(chunk, enc, cb) { 8 | cb(null, chunk) 9 | }, 10 | }) 11 | const logger = new Console({ stdout: ts }) 12 | logger.table(input) 13 | const table = (ts.read() || "").toString() 14 | let result = "" 15 | for (let row of table.split(/[\r\n]+/)) { 16 | let r = row.replace(/[^┬]*┬/, "┌") 17 | r = r.replace(/^├─*┼/, "├") 18 | r = r.replace(/│[^│]*/, "") 19 | r = r.replace(/^└─*┴/, "└") 20 | r = r.replace(/'/g, " ") 21 | result += `${r}\n` 22 | } 23 | console.log(result) 24 | } 25 | 26 | const logger = { table } 27 | 28 | module.exports = { 29 | logger, 30 | } 31 | -------------------------------------------------------------------------------- /apps/contracts/tasks/utils/network.js: -------------------------------------------------------------------------------- 1 | const BASE_URLS = { 2 | 1: "https://etherscan.io/", 3 | 137: "https://polygonscan.com/", 4 | 43114: "https://snowtrace.io/", 5 | 80001: "https://mumbai.polygonscan.com/", 6 | 11155111: "https://sepolia.etherscan.io/", 7 | 43113: "https://testnet.snowtrace.io/", 8 | 421614: "https://sepolia.arbiscan.io/", 9 | 42161: "https://arbiscan.io/", 10 | 84532: "https://sepolia.basescan.org/", 11 | 11155420: "https://sepolia-optimistic.etherscan.io/", 12 | } 13 | 14 | /** 15 | * Returns the Etherscan API domain for a given chainId. 16 | * 17 | * @param chainId Ethereum chain ID 18 | */ 19 | function getEtherscanURL(chainId) { 20 | const idNotFound = !Object.keys(BASE_URLS).includes(chainId.toString()) 21 | if (idNotFound) { 22 | throw new Error("Invalid chain Id") 23 | } 24 | return BASE_URLS[chainId] 25 | } 26 | 27 | module.exports = { 28 | getEtherscanURL, 29 | } 30 | -------------------------------------------------------------------------------- /apps/contracts/tasks/utils/price.js: -------------------------------------------------------------------------------- 1 | function numberWithCommas(x) { 2 | return x.toString().replace(/\B(? 11 | rl.question(query, (ans) => { 12 | rl.close() 13 | resolve(ans) 14 | }) 15 | ) 16 | } 17 | 18 | async function prompt(query) { 19 | if (!process.env.SKIP_PROMPTS) { 20 | if (query) console.log(`${query}\n`) 21 | const reply = await ask(`${chalk.green("Continue?")} Enter (y) Yes / (n) No\n`) 22 | if (reply.toLowerCase() !== "y" && reply.toLowerCase() !== "yes") { 23 | console.log("Aborted.") 24 | process.exit(1) 25 | } 26 | } 27 | } 28 | 29 | // async function promptId(query) { 30 | // if (!process.env.SKIP_PROMPTS) { 31 | // if (query) console.log(`${query}\n`) 32 | // const reply = await ask(`${chalk.green("Continue?")} Enter (y) Yes / (n) No\n`) 33 | // if (reply.toLowerCase() !== "y" && reply.toLowerCase() !== "yes") { 34 | // console.log("Aborted.") 35 | // process.exit(1) 36 | // } 37 | // } 38 | // } 39 | 40 | module.exports = { 41 | ask, 42 | prompt, 43 | // promptId, 44 | } 45 | -------------------------------------------------------------------------------- /apps/contracts/tasks/utils/spin.js: -------------------------------------------------------------------------------- 1 | const ora = require("ora") 2 | 3 | function spin(config = {}) { 4 | const spinner = ora({ spinner: "dots2", ...config }) 5 | spinner.start() 6 | return spinner 7 | } 8 | 9 | module.exports = { 10 | spin, 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/.env.example: -------------------------------------------------------------------------------- 1 | NETWORK_RPC_URL="" 2 | PRIVATE_KEY= 3 | 4 | # setup KV via Vercel: https://vercel.com/docs/storage/vercel-kv/quickstart 5 | KV_URL="" 6 | KV_REST_API_URL="" 7 | KV_REST_API_TOKEN="" 8 | KV_REST_API_READ_ONLY_TOKEN="" -------------------------------------------------------------------------------- /apps/frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "prettier", 5 | "plugin:tailwindcss/recommended" 6 | ], 7 | "plugins": ["tailwindcss"], 8 | "settings": { 9 | "tailwindcss": { 10 | "callees": ["cn"], 11 | "config": "./tailwind.config.js" 12 | } 13 | }, 14 | "rules": { 15 | "@next/next/no-html-link-for-pages": ["error", "./src/app"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | 11 | /coverage 12 | 13 | # next.js 14 | 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | 20 | /build 21 | 22 | # misc 23 | 24 | .DS_Store 25 | \*.pem 26 | 27 | # debug 28 | 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | .pnpm-debug.log* 33 | 34 | # local env files 35 | 36 | .env 37 | .env\*.local 38 | 39 | # vercel 40 | 41 | .vercel 42 | 43 | # typescript 44 | 45 | \*.tsbuildinfo 46 | next-env.d.ts 47 | -------------------------------------------------------------------------------- /apps/frontend/.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies = false -------------------------------------------------------------------------------- /apps/frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | -------------------------------------------------------------------------------- /apps/frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "endOfLine": "lf", 4 | "printWidth": 80, 5 | "semi": false, 6 | "singleQuote": true, 7 | "tabWidth": 2, 8 | "trailingComma": "all" 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Real Estate Demonstration UI 2 | 3 | This UI is used for demonstration purposes only. 4 | 5 | This is useful for comparing the on-chain data with the off-chain data. In a production environment, you will need to use a frontend that aligns with your use cases. 6 | 7 | This is simply for the purposes of demonstrating in an educational environment and not designed to help with the issuance or maintanence of a tokenized asset. -------------------------------------------------------------------------------- /apps/frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: false, 4 | images: { 5 | domains: ['pbs.twimg.com', 'abs.twimg.com', 'images.unsplash.com'], 6 | }, 7 | webpack: (config) => { 8 | config.resolve.fallback = { fs: false, net: false, tls: false } 9 | return config 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web3-starter", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "prettier:check": "prettier --check .", 11 | "prettier:write": "prettier --write ." 12 | }, 13 | "dependencies": { 14 | "@radix-ui/react-alert-dialog": "^1.0.4", 15 | "@radix-ui/react-collapsible": "^1.0.3", 16 | "@radix-ui/react-dialog": "^1.0.4", 17 | "@radix-ui/react-dropdown-menu": "^2.0.5", 18 | "@radix-ui/react-popover": "^1.0.7", 19 | "@radix-ui/react-scroll-area": "^1.0.4", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "@types/react-syntax-highlighter": "^15.5.9", 22 | "class-variance-authority": "^0.6.0", 23 | "clsx": "^1.2.1", 24 | "cmdk": "^0.2.0", 25 | "date-fns": "^2.30.0", 26 | "lucide-react": "^0.220.0", 27 | "next": "^14.2.25", 28 | "next-themes": "^0.3.0", 29 | "react": "^18.2.0", 30 | "react-dom": "^18.2.0", 31 | "react-gtm-module": "^2.0.11", 32 | "react-syntax-highlighter": "^15.5.0", 33 | "react-wrap-balancer": "^0.5.0", 34 | "tailwind-merge": "^1.12.0", 35 | "tailwindcss-animate": "^1.0.5" 36 | }, 37 | "devDependencies": { 38 | "@types/node": "^17.0.31", 39 | "@types/react": "^18.0.9", 40 | "@types/react-dom": "^18.0.3", 41 | "@types/react-gtm-module": "^2.0.3", 42 | "@upstash/ratelimit": "^0.4.4", 43 | "@vercel/kv": "^0.2.4", 44 | "autoprefixer": "^10.4.14", 45 | "bufferutil": "^4.0.8", 46 | "encoding": "^0.1.13", 47 | "eslint": "^8.15.0", 48 | "eslint-config-next": "^12.1.6", 49 | "eslint-config-prettier": "^8.8.0", 50 | "eslint-plugin-tailwindcss": "^3.12.0", 51 | "ethers": "^6.8.1", 52 | "lokijs": "^1.5.12", 53 | "pino-pretty": "^10.2.3", 54 | "postcss": "^8.4.23", 55 | "prettier": "2.8.8", 56 | "tailwindcss": "^3.3.1", 57 | "typescript": "^5.0.4", 58 | "utf-8-validate": "^5.0.10" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /apps/frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/public/angle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/frontend/public/architecture.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 19 | 27 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /apps/frontend/public/arrow-go-to-up-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /apps/frontend/public/arrow-go-to-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /apps/frontend/public/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /apps/frontend/public/chainlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /apps/frontend/public/charger.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 12 | 13 | 15 | 17 | 20 | 22 | 23 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /apps/frontend/public/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /apps/frontend/public/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /apps/frontend/public/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /apps/frontend/public/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/rwa-tokenization/427bf7f772d7f7913455b3dddc90ba7cb011f786/apps/frontend/public/design.png -------------------------------------------------------------------------------- /apps/frontend/public/design.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/frontend/public/dev-expert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/frontend/public/docs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by potrace 1.15, written by Peter Selinger 2001-2017 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/frontend/public/external-muted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/frontend/public/external.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/frontend/public/faucet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by potrace 1.15, written by Peter Selinger 2001-2017 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /apps/frontend/public/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/frontend/public/globe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | 12 | 15 | 18 | 19 | 21 | 23 | 25 | 26 | 27 | 29 | 30 | 33 | 36 | 37 | -------------------------------------------------------------------------------- /apps/frontend/public/house.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | Created by potrace 1.15, written by Peter Selinger 2001-2017 7 | 8 | 9 | 17 | 34 | 35 | -------------------------------------------------------------------------------- /apps/frontend/public/how-it-works-meteo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/rwa-tokenization/427bf7f772d7f7913455b3dddc90ba7cb011f786/apps/frontend/public/how-it-works-meteo.png -------------------------------------------------------------------------------- /apps/frontend/public/how-it-works-x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/rwa-tokenization/427bf7f772d7f7913455b3dddc90ba7cb011f786/apps/frontend/public/how-it-works-x.jpg -------------------------------------------------------------------------------- /apps/frontend/public/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/rwa-tokenization/427bf7f772d7f7913455b3dddc90ba7cb011f786/apps/frontend/public/loading.gif -------------------------------------------------------------------------------- /apps/frontend/public/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /apps/frontend/public/onchain.svg: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /apps/frontend/public/refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/frontend/public/remix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartcontractkit/rwa-tokenization/427bf7f772d7f7913455b3dddc90ba7cb011f786/apps/frontend/public/remix.png -------------------------------------------------------------------------------- /apps/frontend/src/app/api/onchain-price/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server' 2 | 3 | import { getHouseOnChain, getLatestRequestId, requestHouseOnChain } from '@/lib/request-onchain' 4 | // import { addToHouseHistory } from '@/lib/history' 5 | 6 | export async function POST(request: NextRequest) { 7 | const params = await request.json() 8 | if (!params || !params.tokenId) return NextResponse.error() 9 | 10 | const { tokenId } = params 11 | 12 | // submits: transaction to the blockchain. 13 | const { tx, requestId } = await requestHouseOnChain(tokenId) 14 | 15 | if (!tx) return NextResponse.error() 16 | 17 | return NextResponse.json({ tx, requestId }) 18 | } 19 | 20 | // export async function GET(request: NextRequest) { 21 | // const { searchParams } = new URL(request.url) 22 | // // const requestId = searchParams.get('requestId') || '' 23 | // const tokenId = searchParams.get('tokenId') || '' 24 | // console.log('GET: tokenId: %s', tokenId) 25 | // const requestId = await getLatestRequestId(tokenId) 26 | // console.log('GET: requestId: %s', requestId) 27 | 28 | // if (!requestId) return NextResponse.error() 29 | 30 | // const data = await getHouseOnChain(requestId.toString()) 31 | // console.log('GET: data: %s', data) 32 | // const index = data?.index 33 | // const response = data?.response 34 | // console.log('GET: index: %s; tokenId: %s; response: %s;', index, tokenId, response) 35 | 36 | // return NextResponse.json({ index, tokenId, response }) 37 | // } -------------------------------------------------------------------------------- /apps/frontend/src/app/architecture/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Footer } from '@/components/footer' 2 | 3 | export default function OpenMeteoLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode 7 | }) { 8 | return ( 9 | <> 10 | {children} 11 |