diff --git a/.github/workflows/s3.yml b/.github/workflows/s3.yml new file mode 100644 index 00000000..0caaa199 --- /dev/null +++ b/.github/workflows/s3.yml @@ -0,0 +1,38 @@ +name: Deploy to S3 + +on: + push: + branches: [ "main", "develop" ] + workflow_dispatch: + +jobs: + + build: + environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'development' }} + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'npm' + - run: npm install + - name: create env file + run: | + touch .env + echo TYPESENSE_COLLECTION_NAME=${{ vars.TYPESENSE_COLLECTION_NAME }} >> .env + echo TYPESENSE_SERVER_HOST=${{ vars.TYPESENSE_SERVER_HOST }} >> .env + echo TYPESENSE_SEARCH_ONLY_APIKEY=${{ vars.TYPESENSE_SEARCH_ONLY_APIKEY }} >> .env + - run: npm run build + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-1 + + - name: Sync to S3 + run: aws s3 sync ./build s3://${{ secrets.AWS_S3_BUCKET }} diff --git a/.gitignore b/.gitignore index b083ed4f..1d5a2a7b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +yarn.lock* diff --git a/docs/index.md b/docs/index.md index bb9d267b..ac4a9de1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,8 @@ aelf is a high-performance, cloud-native, layer-1 blockchain with Mainnet nodes - [Lottery Game](/quick-start/developers/lottery-game-smart-contract/) contract - [DAO](/quick-start/developers/dao-dapp/) dApp - [NFT](/quick-start/developers/nft-dapp/) dApp + - [ToDo](/quick-start/developers/todo-dapp/) dApp + - For node operators - [Simulate](/quick-start/node-operators/simulating-a-bp-node/) a BP node - [Set up](/quick-start/node-operators/set-up-a-node-on-testnet/) a node on testnet diff --git a/docs/quick-start/developers/_deploy.md b/docs/quick-start/developers/_deploy.md index c5461585..7e37d123 100644 --- a/docs/quick-start/developers/_deploy.md +++ b/docs/quick-start/developers/_deploy.md @@ -103,11 +103,11 @@ aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH -e https:// ``` :::tip -โ„น๏ธ Note: You are to copy the smart contract address as we will be referencing it in the next quest! +โ„น๏ธ Note: You are to copy the smart contract address as we will be referencing it in the next steps! ::: :::info -๐ŸŽ‰ You have successfully deployed your Voting dApp smart contract on the aelf testnet! In the next quest, we will be building the frontend components that allow us to interact with our deployed smart contract! +๐ŸŽ‰ You have successfully deployed your dApp smart contract on the aelf testnet! In the next steps, we will be building the frontend components that allow us to interact with our deployed smart contract! ::: diff --git a/docs/quick-start/developers/_deploy_tic_tac_toe.md b/docs/quick-start/developers/_deploy_tic_tac_toe.md new file mode 100644 index 00000000..887713ad --- /dev/null +++ b/docs/quick-start/developers/_deploy_tic_tac_toe.md @@ -0,0 +1,113 @@ +#### Create A Wallet + +To send transactions on the aelf blockchain, you must have a wallet. + +- Run this command to create aelf wallet. + +```bash title="Terminal" +aelf-command create +``` + +![result](/img/create_wallet_output.png) + +- You will be prompted to save your account, please do **save** your account as shown below: + +```bash title="Terminal" +? Save account info into a file? (Y/n) Y +``` + +**Make sure to choose Y to save your account information.** + +:::tip +โ„น๏ธ Note: If you do not save your account information (by selecting n or N), do not export the wallet password. Only **proceed to the next** step if you have saved your account information. +::: + +- Next, enter and confirm your password. Then export your wallet password as shown below: + +```bash title="Terminal" +export WALLET_PASSWORD="YOUR_WALLET_PASSWORD" +``` + +#### Acquire Testnet Tokens (Faucet) for Development + +To deploy smart contracts or execute on-chain transactions on aelf, you'll require testnet ELF tokens. + +**Get ELF Tokens** + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +**1. Get Testnet ELF Tokens:** + +To receive testnet ELF tokens, run this command after replacing `$WALLET_ADDRESS` and `$WALLET_PASSWORD` with your wallet details: + +```bash title="Terminal" +export WALLET_ADDRESS="YOUR_WALLET_ADDRESS" +curl -X POST "https://faucet.aelf.dev/api/claim?walletAddress=$WALLET_ADDRESS" -H "accept: application/json" -d "" +``` + +**2. Check ELF Balance:** + +To check your ELF balance, use: + +```bash title="Terminal" +aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance +``` + +You will be prompted for the following: + +```sh title="Terminal" +Enter the required param : ELF +Enter the required param : **$WALLET_ADDRESS** +``` + +You should see the result displaying your wallet's ELF balance. + + + + +Go to https://faucet-ui.aelf.dev Enter your address and click `Get Tokens`. + +![result](/img/get-token-ui.png) + + + + +**Deploy Smart Contract:** + +The smart contract needs to be deployed on the chain before users can interact with it. + +Run the following command to deploy a contract. Remember to export the path of TicTacToe.dll.patched to CONTRACT_PATH. + +```bash title="Terminal" +export CONTRACT_PATH=$(find ~+ . -path "*patched*" | head -n 1) +``` + +```bash title="Terminal" +aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH -e https://tdvw-test-node.aelf.io/ +``` + +- Please wait for approximately 1 to 2 minutes. If the deployment is successful, it will provide you with the contract address. +![result](/img/deploy-result.png) + +- Copy the smart contract address from the `address` field +![result](/img/Contract_Address.png) + +- Export your smart contract address: + + ```bash title="Terminal" + export CONTRACT_ADDRESS="YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS" + ``` + +:::tip +โ„น๏ธ Note: You are to copy the smart contract address as we will be referencing it in the next steps! +::: + +:::info +๐ŸŽ‰ You have successfully deployed your Tic-Tac-Toe dApp smart contract on the aelf testnet! In the next steps, we will be building the frontend components that allow us to interact with our deployed smart contract! +::: + + diff --git a/docs/quick-start/developers/_deploy_todo.md b/docs/quick-start/developers/_deploy_todo.md new file mode 100644 index 00000000..badbeb5e --- /dev/null +++ b/docs/quick-start/developers/_deploy_todo.md @@ -0,0 +1,113 @@ +#### Create A Wallet + +To send transactions on the aelf blockchain, you must have a wallet. + +- Run this command to create aelf wallet. + +```bash title="Terminal" +aelf-command create +``` + +![result](/img/create_wallet_output.png) + +- You will be prompted to save your account, please do **save** your account as shown below: + +```bash title="Terminal" +? Save account info into a file? (Y/n) Y +``` + +**Make sure to choose Y to save your account information.** + +:::tip +โ„น๏ธ Note: If you do not save your account information (by selecting n or N), do not export the wallet password. Only **proceed to the next** step if you have saved your account information. +::: + +- Next, enter and confirm your password. Then export your wallet password as shown below: + +```bash title="Terminal" +export WALLET_PASSWORD="YOUR_WALLET_PASSWORD" +``` + +#### Acquire Testnet Tokens (Faucet) for Development + +To deploy smart contracts or execute on-chain transactions on aelf, you'll require testnet ELF tokens. + +**Get ELF Tokens** + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +**1. Get Testnet ELF Tokens:** + +To receive testnet ELF tokens, run this command after replacing `$WALLET_ADDRESS` and `$WALLET_PASSWORD` with your wallet details: + +```bash title="Terminal" +export WALLET_ADDRESS="YOUR_WALLET_ADDRESS" +curl -X POST "https://faucet.aelf.dev/api/claim?walletAddress=$WALLET_ADDRESS" -H "accept: application/json" -d "" +``` + +**2. Check ELF Balance:** + +To check your ELF balance, use: + +```bash title="Terminal" +aelf-command call ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx -a $WALLET_ADDRESS -p $WALLET_PASSWORD -e https://tdvw-test-node.aelf.io GetBalance +``` + +You will be prompted for the following: + +```sh title="Terminal" +Enter the required param : ELF +Enter the required param : **$WALLET_ADDRESS** +``` + +You should see the result displaying your wallet's ELF balance. + + + + +Go to https://faucet-ui.aelf.dev Enter your address and click `Get Tokens`. + +![result](/img/get-token-ui.png) + + + + +**Deploy Smart Contract:** + +The smart contract needs to be deployed on the chain before users can interact with it. + +Run the following command to deploy a contract. Remember to export the path of ToDoApp.dll.patched to CONTRACT_PATH. + +```bash title="Terminal" +export CONTRACT_PATH=$(find ~+ . -path "*patched*" | head -n 1) +``` + +```bash title="Terminal" +aelf-deploy -a $WALLET_ADDRESS -p $WALLET_PASSWORD -c $CONTRACT_PATH -e https://tdvw-test-node.aelf.io/ +``` + +- Please wait for approximately 1 to 2 minutes. If the deployment is successful, it will provide you with the contract address. +![result](/img/deploy-result.png) + +- Copy the smart contract address from the `address` field +![result](/img/Contract_Address.png) + +- Export your smart contract address: + + ```bash title="Terminal" + export CONTRACT_ADDRESS="YOUR_SMART_CONTRACT_ADDRESS e.g. 2LUmicHyH4RXrMjG4beDwuDsiWJESyLkgkwPdGTR8kahRzq5XS" + ``` + +:::tip +โ„น๏ธ Note: You are to copy the smart contract address as we will be referencing it in the next steps! +::: + +:::info +๐ŸŽ‰ You have successfully deployed your ToDo dApp smart contract on the aelf testnet! In the next steps, we will be building the frontend components that allow us to interact with our deployed smart contract! +::: + + diff --git a/docs/quick-start/developers/dao-dapp/_tree.json b/docs/quick-start/developers/dao-dapp/_tree.json new file mode 100644 index 00000000..6aa5fb3f --- /dev/null +++ b/docs/quick-start/developers/dao-dapp/_tree.json @@ -0,0 +1,63 @@ +{ + "type": "directory", + "uri": "2-dapp", + "expanded": true, + "children": [ + { + "type": "directory", + "uri": "app" + }, + { + "type": "directory", + "uri": "assets" + }, + { + "type": "directory", + "uri": "public" + }, + { + "type": "directory", + "uri": "src" + }, + { + "type": "file", + "uri": ".gitignore" + }, + { + "type": "file", + "uri": "components.json" + }, + { + "type": "file", + "uri": "index.html" + }, + { + "type": "file", + "uri": "package.json" + }, + { + "type": "file", + "uri": "postcss.config.js" + }, + { + "type": "file", + "uri": "README.md" + }, + { + "type": "file", + "uri": "tailwind.config.js" + }, + { + "type": "file", + "uri": "tsconfig.json" + }, + { + "type": "file", + "uri": "tsconfig.node.json" + }, + { + "type": "file", + "uri": "vite.config.ts" + } + ] +} diff --git a/docs/quick-start/developers/dao-dapp/index.md b/docs/quick-start/developers/dao-dapp/index.md index 1b8db240..ea3363bf 100644 --- a/docs/quick-start/developers/dao-dapp/index.md +++ b/docs/quick-start/developers/dao-dapp/index.md @@ -676,18 +676,82 @@ Let's start by cloning the frontend project repository from GitHub. - Run the following command in the `capstone_aelf` directory: ```bash title="Terminal" -git clone https://github.com/AElfProject/vote-contract-frontend.git +git clone https://github.com/AElfProject/aelf-samples.git ``` - Next, navigate to the frontend project directory with this command: ```bash title="Terminal" -cd vote-contract-frontend +cd aelf-samples/vote/2-dapp ``` -- Once you're in the `vote-contract-frontend` directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below. +- Once you're in the `2-dapp` directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below. - ![result](/img/vote-fe-directory.png) +export const tree = { + "type": "directory", + "uri": "2-dapp", + "expanded": true, + "children": [ + { + "type": "directory", + "uri": "app" + }, + { + "type": "directory", + "uri": "assets" + }, + { + "type": "directory", + "uri": "public" + }, + { + "type": "directory", + "uri": "src" + }, + { + "type": "file", + "uri": ".gitignore" + }, + { + "type": "file", + "uri": "components.json" + }, + { + "type": "file", + "uri": "index.html" + }, + { + "type": "file", + "uri": "package.json" + }, + { + "type": "file", + "uri": "postcss.config.js" + }, + { + "type": "file", + "uri": "README.md" + }, + { + "type": "file", + "uri": "tailwind.config.js" + }, + { + "type": "file", + "uri": "tsconfig.json" + }, + { + "type": "file", + "uri": "tsconfig.node.json" + }, + { + "type": "file", + "uri": "vite.config.ts" + } + ] +} + +
#### Install necessary libraries @@ -1012,7 +1076,7 @@ npm run dev :::info -โ„น๏ธ Note: Ensure that you are running this command under the **Developer_DAO** folder. +โ„น๏ธ Note: Ensure that you are running this command under the **vote/2-dapp** folder. ::: diff --git a/docs/quick-start/developers/nft-dapp/index.md b/docs/quick-start/developers/nft-dapp/index.md index 445b1175..d1fed28b 100644 --- a/docs/quick-start/developers/nft-dapp/index.md +++ b/docs/quick-start/developers/nft-dapp/index.md @@ -45,28 +45,87 @@ import TabItem from '@theme/TabItem'; ### Project Setup -Let's start by creating a separate folder called `nft_aelf`. Move to `nft_aelf` directory using following commands. +Let's start by cloning the frontend project repository from GitHub. -```bash title="Terminal" -mkdir nft_aelf -cd nft_aelf -``` -- Clone the NFT tutorial github project using the following command inside the `nft_aelf` directory: +- Run the following command your Terminal: ```bash title="Terminal" -git clone https://github.com/AElfProject/nft-tutorial.git +git clone https://github.com/AElfProject/aelf-samples.git ``` -- Next, navigate to the frontend project directory, `nft-tutorial` with the following command: +- Next, navigate to the frontend project directory with this command: ```bash title="Terminal" -cd nft-tutorial +cd aelf-samples/nft/2-dapp ``` +- Once you're in the `2-dapp` directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below. + +export const tree = { + "type": "directory", + "uri": "2-dapp", + "expanded": true, + "children": [ + { + "type": "directory", + "uri": "app" + }, + { + "type": "directory", + "uri": "assets" + }, + { + "type": "directory", + "uri": "public" + }, + { + "type": "directory", + "uri": "src" + }, + { + "type": "file", + "uri": ".gitignore" + }, + { + "type": "file", + "uri": "components.json" + }, + { + "type": "file", + "uri": "index.html" + }, + { + "type": "file", + "uri": "package.json" + }, + { + "type": "file", + "uri": "postcss.config.js" + }, + { + "type": "file", + "uri": "README.md" + }, + { + "type": "file", + "uri": "tailwind.config.js" + }, + { + "type": "file", + "uri": "tsconfig.json" + }, + { + "type": "file", + "uri": "tsconfig.node.json" + }, + { + "type": "file", + "uri": "vite.config.ts" + } + ] +} -- Once you're inside the `nft-tutorial` directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below. - - ![result](/img/nft-fe-directory.png) +
#### Install necessary packages and libraries @@ -1378,7 +1437,7 @@ npm run dev :::info -โ„น๏ธ Note: Ensure that you are running this command under the **NFT Tutorial** folder. +โ„น๏ธ Note: Ensure that you are running this command under the **nft/2-dapp** folder. ::: diff --git a/docs/quick-start/developers/tic-tac-toe-dapp/index.js b/docs/quick-start/developers/tic-tac-toe-dapp/index.js new file mode 100644 index 00000000..7fcea570 --- /dev/null +++ b/docs/quick-start/developers/tic-tac-toe-dapp/index.js @@ -0,0 +1,149 @@ +--- +sidebar_position: 6 +title: Tic-Tac-Toe dApp +description: Moderately difficult smart contract and dApp +--- + +**Description**: This contract is moderately difficult. It demonstrates the use of +state variables, user interactions, and smart contract integration to create a +basic ToDo App. + +**Purpose**: To introduce you to more advanced concepts such as state +management, event handling, and randomization in smart contracts. + +**Difficulty Level**: Moderate + + + +## Step 1 - Setting up your development environment + +import Setup from "../\_setup.md" + + + +## Step 2 - Develop Smart Contract + +### Start Your Smart Contract Project + +- Open your `Terminal`. + +- Enter the following command to generate a new project: + +```bash title="Terminal" +mkdir todo-app +cd todo-app +dotnet new aelf -n ToDoApp +``` + +### Adding Your Smart Contract Code + +Now that we have a template todo list project, we can customise the template to incorporate our own contract logic. +Lets start by implementing methods to provide basic functionality for updating and reading a message stored persistently in the contract state. +ToDoApp includes the below functionalities like: +1. Create a Task (Name, category, description, createAt, updatedAt) +2. Task Completed +3. Delete a task +4. List the tasks +5. Edit the task + +- Enter this command in your `Terminal`. + +```bash title="Terminal" +cd src +``` + +#### Defining Methods and Messages + +- Rename the file name from `Protobuf/contract/hello_world_contract.proto` to `todo_app.proto`: + +```bash title="Terminal" +mv Protobuf/contract/hello_world_contract.proto Protobuf/contract/todo_app.proto +``` + +- open the project with your IDE. + +The implementation of file `src/Protobuf/contract/todo_app.proto` is as follows: + +```csharp title="todo_app.proto" +syntax = "proto3"; + +import "aelf/core.proto"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ToDoApp"; + +service ToDoApp { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.ToDoApp.ToDoAppState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + + rpc createTask (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + + rpc IsCompleted (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + + rpc deleteTask (google.protobuf.Int64Value) returns (google.protobuf.Empty) { + } + + rpc editTask (aelf.Address) returns (google.protobuf.Empty) { + } + + rpc GetAllTasks (google.protobuf.Empty) returns (PlayAmountLimitMessage) { + option (aelf.is_view) = true; + } + + rpc GetOwner (google.protobuf.Empty) returns (google.protobuf.StringValue) { + option (aelf.is_view) = true; + } +} + +// An event that will be emitted from contract method call when Play is called. +message createTaskEvent { + option (aelf.is_event) = true; + int64 name = "Finish frontend"; + int64 description = "Finish loader feature of the frontend"; +} + +// An event that will be emitted from contract method call when Withdraw is called. +message deleteTaskEvent { + option (aelf.is_event) = true; + int64 name = "Finish frontend"; + int64 description = "Finish loader feature of the frontend"; +} + +// An event that will be emitted from contract method call when Deposit is called. +message EditTaskEvent { + option (aelf.is_event) = true; + int64 name = "Finish frontend"; + int64 description = "Finish loader feature of the frontend"; +} + +``` + +#### Define Contract States + +The implementation of file `src/ToDoApp.cs` is as follows: + +```csharp title="src/ToDoApp.cs" +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.ToDoApp +{ + // The state class is access the blockchain state + public partial class ToDoAppState : ContractState + { + // A state to check if contract is initialized + public BoolState Initialized { get; set; } + // A state to store the owner address + public SingletonState
Owner { get; set; } + } +} +``` \ No newline at end of file diff --git a/docs/quick-start/developers/tic-tac-toe-dapp/index.md b/docs/quick-start/developers/tic-tac-toe-dapp/index.md new file mode 100644 index 00000000..b18ed334 --- /dev/null +++ b/docs/quick-start/developers/tic-tac-toe-dapp/index.md @@ -0,0 +1,975 @@ +--- +sidebar_position: 6 +title: Tic-Tac-Toe dApp +description: Moderately difficult smart contract and dApp +--- + +**Description**: The Tic-Tac-Toe dApp is a decentralized gamify application that allows users to play the classic game of Tic-Tac-Toe on the aelf blockchain. It offers a simple and interactive interface where two players can compete against each other, with game data securely stored and verified on the aelf blockchain. + +**Purpose**: The purpose of the Tic-Tac-Toe gamify dApp is to demonstrate how traditional games can be implemented on the aelf blockchain, ensuring transparency and immutability of game outcomes. It serves as an educational tool for learning smart contract development and the basics of decentralized application functionality. + +**Difficulty Level**: Moderate + + + +## Step 1 - Setting up your development environment + +import Setup from "../\_setup.md" + + + +## Step 2 - Develop Smart Contract + +### Start Your Smart Contract Project + +- Open your `Terminal`. + +- Enter the following command to generate a new project: + +```bash title="Terminal" +mkdir tic-tac-toe-dapp +cd tic-tac-toe-dapp +dotnet new aelf -n TicTacToe +``` + +### Adding Your Smart Contract Code + +Now that we have a template tic-tac-toe project, we can customise the template to incorporate our own contract logic. Let's start by implementing methods to handle the basic functionality for starting the Tic-Tac-Toe game, allowing players to make moves, retrieving the game board data and current status, and checking if the game has been initialized. Tic-Tac-Toe dApp includes the below functionalities: +1. Start a new game +2. Make a move in the ongoing game +3. Get the board details +4. Get the current status of the board +5. Get the initial status of the board + +- Enter this command in your `Terminal`. + +```bash title="Terminal" +cd src +``` + +#### Defining Methods and Messages + +- Rename the proto file name `hello_world_contract.proto` inside folder `Protobuf/contract/` to `tic_tac_toe.proto`: + +```bash title="Terminal" +mv Protobuf/contract/hello_world_contract.proto Protobuf/contract/tic_tac_toe.proto +``` + +The `.proto` file defines the structure and serialization of data, ensuring consistent communication and data exchange between the contract and external systems. + +- Open the project with your IDE. + +The implementation of `tic_tac_toe.proto` file inside folder `src/Protobuf/contract/` is as follows: + +```csharp title="tic_tac_toe.proto" +syntax = "proto3"; + +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.TicTacToe"; + +service TicTacToe { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.TicTacToe.TicTacToeState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) {} + + rpc StartGame (google.protobuf.Empty) returns (google.protobuf.StringValue) {} + + rpc MakeMove (MoveInput) returns (google.protobuf.StringValue) {} + + rpc GetBoard (google.protobuf.Empty) returns (Board) { + option (aelf.is_view) = true; + } + + rpc GetGameStatus (google.protobuf.Empty) returns (GameStatus) { + option (aelf.is_view) = true; + } + rpc GetInitialStatus(google.protobuf.Empty) returns(google.protobuf.BoolValue){ + option (aelf.is_view) = true; + } +} + +// Input for making a move +message MoveInput { + int32 x = 1; + int32 y = 2; +} + +// A message to represent the game board +message Board { + repeated string rows = 1; +} + +// A message to represent the game status +message GameStatus { + string status = 1; + string winner = 2; +} +``` + +- `rpc` methods define the callable functions within the contract, allowing external systems to interact with the contract's logic. +- `message` represent the structured data exchanged between the contract and external systems. + +#### Define Contract States + +The implementation of the Tic-Tac-Toe dApp state inside file `src/TicTacToeState.cs` is as follows: + +```csharp title="src/TicTacToeState.cs" +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.TicTacToe +{ + // The state class is access the blockchain state + public partial class TicTacToeState : ContractState + { + // A state to check if contract is initialized + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public StringState Board { get; set; } // Board state as a concatenated string + public StringState CurrentPlayer { get; set; } // X or O + public StringState GameStatus { get; set; } // ongoing, finished, draw + public StringState Winner { get; set; } // X or O + } +} +``` + +- The `State.cs` file in an aelf blockchain smart contract holds the variables that store the contract's data, making sure this data is saved and accessible whenever the contract needs it. + +#### Implement Tic-Tac-Toe Smart Contract + +The implementation of the Tic-Tac-Toe dApp smart contract inside file `src/TicTacToe.cs` is as follows: + +```csharp title="src/TicTacToe.cs" +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; + +namespace AElf.Contracts.TicTacToe +{ + // Contract class must inherit the base class generated from the proto file + public class TicTacToe : TicTacToeContainer.TicTacToeBase + { + private const int BoardSize = 3; + + public override Empty Initialize(Empty input) + { + if (State.Initialized.Value) + { + return new Empty(); + } + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + ResetBoard(); + return new Empty(); + } + + public override StringValue StartGame(Empty input) + { + if (!State.Initialized.Value) + { + return new StringValue { Value = "Contract not initialized." }; + } + + ResetBoard(); + State.CurrentPlayer.Value = "X"; + State.GameStatus.Value = "ongoing"; + State.Winner.Value = ""; + return new StringValue { Value = "Game started. Player X's turn." }; + } + + public override StringValue MakeMove(MoveInput input) + { + if (State.GameStatus.Value != "ongoing") + { + return new StringValue { Value = "Game is not ongoing." }; + } + + var board = GetBoardArray(); + if (board[input.X, input.Y] != "") + { + return new StringValue { Value = "Invalid move. Cell is already occupied." }; + } + + board[input.X, input.Y] = State.CurrentPlayer.Value; + SaveBoard(board); + + if (CheckWinner()) + { + State.GameStatus.Value = "finished"; + State.Winner.Value = State.CurrentPlayer.Value; + return new StringValue { Value = $"Player {State.CurrentPlayer.Value} wins!" }; + } + + if (IsBoardFull(board)) + { + State.GameStatus.Value = "draw"; + return new StringValue { Value = "It's a draw!" }; + } + + State.CurrentPlayer.Value = State.CurrentPlayer.Value == "X" ? "O" : "X"; + return new StringValue { Value = $"Player {State.CurrentPlayer.Value}'s turn." }; + } + + public override Board GetBoard(Empty input) + { + var board = GetBoardArray(); + var boardMessage = new Board(); + + for (var i = 0; i < 3; i++) // Adjusted to 3 for a 3x3 Tic-Tac-Toe board + { + var row = new List(); + for (var j = 0; j < 3; j++) + { + row.Add(board[i, j]); + } + boardMessage.Rows.Add(string.Join(",", row)); + } + + return boardMessage; + } + + public override GameStatus GetGameStatus(Empty input) + { + return new GameStatus + { + Status = State.GameStatus.Value, + Winner = State.Winner.Value + }; + } + + public override BoolValue GetInitialStatus(Empty input){ + return new BoolValue { Value = State.Initialized.Value }; + } + + private void ResetBoard() + { + var emptyBoard = new string[BoardSize, BoardSize]; + for (var i = 0; i < BoardSize; i++) + { + for (var j = 0; j < BoardSize; j++) + { + emptyBoard[i, j] = ""; + } + } + SaveBoard(emptyBoard); + } + + private string[,] GetBoardArray() + { + var boardString = State.Board.Value; + var rows = boardString.Split(';'); + var board = new string[BoardSize, BoardSize]; + for (var i = 0; i < BoardSize; i++) + { + var cells = rows[i].Split(','); + for (var j = 0; j < BoardSize; j++) + { + board[i, j] = cells[j]; + } + } + return board; + } + + private void SaveBoard(string[,] board) + { + var rows = new string[BoardSize]; + for (var i = 0; i < BoardSize; i++) + { + rows[i] = string.Join(",", board[i, 0], board[i, 1], board[i, 2]); + } + State.Board.Value = string.Join(";", rows); + } + + private bool CheckWinner() + { + var board = GetBoardArray(); + var player = State.CurrentPlayer.Value; + + for (var i = 0; i < BoardSize; i++) + { + if (board[i, 0] == player && board[i, 1] == player && board[i, 2] == player) return true; + if (board[0, i] == player && board[1, i] == player && board[2, i] == player) return true; + } + + if (board[0, 0] == player && board[1, 1] == player && board[2, 2] == player) return true; + if (board[0, 2] == player && board[1, 1] == player && board[2, 0] == player) return true; + + return false; + } + + private bool IsBoardFull(string[,] board) + { + for (var i = 0; i < BoardSize; i++) + { + for (var j = 0; j < BoardSize; j++) + { + if (board[i, j] == "") return false; + } + } + return true; + } + } + +} +``` + +### Building Smart Contract + +- Build the smart contract code with the following command inside `src` folder: + +```bash title="Terminal" +dotnet build +``` + +You should see **TicTacToe.dll.patched** in the directory `1-smart-contract/src/bin/Debug/net.6.0` + +## Step 3 - Deploy Smart Contract + +import Deploy from "../\_deploy_tic_tac_toe.md" + + + +## Step 4 - Interact with Your Deployed Smart Contract through dApp + +### Project Setup + +Let's start by cloning the frontend project repository from github. + +```bash title="Terminal" +git clone https://github.com/AElfProject/aelf-samples.git +``` + +- Next, navigate to the frontend project directory with this command: + +```bash title="Terminal" +cd aelf-samples/tic-tac-toe/2-dapp +``` + +- Once you're inside the `2-dapp` directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below. + +export const tree = { + "type": "directory", + "uri": "2-dapp", + "expanded": true, + "children": [ + { + "type": "directory", + "uri": "app" + }, + { + "type": "directory", + "uri": "assets" + }, + { + "type": "directory", + "uri": "public" + }, + { + "type": "directory", + "uri": "src" + }, + { + "type": "file", + "uri": ".gitignore" + }, + { + "type": "file", + "uri": "components.json" + }, + { + "type": "file", + "uri": "index.html" + }, + { + "type": "file", + "uri": "package.json" + }, + { + "type": "file", + "uri": "postcss.config.js" + }, + { + "type": "file", + "uri": "README.md" + }, + { + "type": "file", + "uri": "tailwind.config.js" + }, + { + "type": "file", + "uri": "tsconfig.json" + }, + { + "type": "file", + "uri": "tsconfig.node.json" + }, + { + "type": "file", + "uri": "vite.config.ts" + } + ] +} + +
+ +#### Install necessary libraries + +- Run this command in the terminal to install all necessary packages and libraries: + +```bash title="Terminal" +npm install +``` + +We are now ready to build the frontend components of our Tic-Tac-Toe dApp. + +### Configure Portkey Provider & Write Connect Wallet Function + +Now, we'll set up our Portkey wallet provider to allow users to connect their Portkey wallets to the dApp and interact with the smart contract. We'll be interacting with the already deployed Tic-Tac-Toe smart contract for this tutorial. + +**Step 1. Locate the File:** + +- Go to the `src/hooks/useSmartContract.ts` file. + +**Step 2. Fetch the Smart Contract:** + +- Find the comment ` //Step A - Function to fetch a smart contract based on deployed wallet address` + +- Replace the existing **`fetchContract`** function with this updated code: + +```javascript title="useSmartContract.ts" +//Step A - Function to fetch a smart contract based on deployed wallet address +const fetchContract = async () => { + if (!provider) return null; + + try { + // 1. get the sidechain tDVW using provider.getChain + const chain = await provider?.getChain("tDVW"); + if (!chain) throw new Error("No chain"); + + //Address of TicTacToe Smart Contract + //Replace with Address of Deployed Smart Contract + const address = "your_deployed_tic_tac_toe_contract_address"; + + // 2. get the TicTacToe contract + const contract = chain?.getContract(address); + setSmartContract(contract); + } catch (error) { + console.log(error, "====error"); + } +} +``` + +:::tip +โ„น๏ธ Note: You are to replace the address placeholder with your deployed Tic Tac Toe smart contract address from "Deploy Smart Contract" step! + +example: Replace with Address of Deployed Smart Contract +const address = "your_deployed_tic_tac_toe_contract_address"; +::: + +**Explanation:** + +- **`fetchContract`** **Function**: This function fetches a smart contract based on the given chain symbol (e.g., "AELF" or "tDVW") and the contract address. + + - **Check Provider** : If no provider is available, the function returns null. + - **Fetch Chain** : The function fetches chain information using the provider. + - **Get Contract** : It retrieves the smart contract instance from the chain. + +`AELF` represents the mainnet chain and `tDVW` represents the testnet chain respectively on the aelf blockchain. + +**Step 3. Initialize and Fetch the Smart Contracts:** + +- Find the comment `// Step B - Effect hook to initialize and fetch the smart contract when the provider changes.` + +- Replace the existing **`useEffect`** hook with this updated code: + +```javascript title="useSmartContract.ts" + // Step B - Effect hook to initialize and fetch the smart contract when the provider changes + useEffect(() => { + fetchContract(); + }, [provider]); // Dependency array ensures this runs when the provider changes +``` + +**Explanation:** +- **`useEffect` Hook** : This hook initializes and fetches the smart contracts when the provider changes. + - **Check Provider** : If no provider is available, the function returns null. + - **Fetch Contracts** : It fetches and sets the smart contracts. + +By following these steps, we'll configure the Portkey provider to connect users' wallets to our app and interact with the Tic-Tac-Toe smart contract including Tic-Tac-Toe Game Play related functionalities. This setup will enable our frontend components to perform actions like `initializeContract`, `startGame`, `makeMove`, `getGameStatus` and `getLatestBoard` etc. + +### Configure Connect Wallet Function + +**Step 1: Locate the File** + +- Go to the `src/components/layout/header/index.tsx` file. + +**Step 2: Write the Connect Wallet Function** + +- The `header/index.tsx` file is the header of our Tic-Tac-Toe dApp. It allows users to connect their Portkey wallet with the Tic-Tac-Toe dApp. + +- Before users can interact with the smart contract, we need to write the `Connect Wallet` function. + +- Find the comment `// Step C - Connect Portkey Wallet`. + +- Replace the existing **`connect`** function with this code snippet: + +```javascript title="header/index.tsx" +const connect = async (walletProvider?: IPortkeyProvider) => { + // Step C - Connect Portkey Wallet + const accounts = await (walletProvider ? walletProvider : provider)?.request({ + method: MethodsBase.REQUEST_ACCOUNTS, + }); + const account = accounts?.AELF && accounts?.AELF[0]; + if (account) { + setCurrentWalletAddress(account.replace(/^ELF_/, "").replace(/_AELF$/, "")); + setIsConnected(true); + } + !walletProvider && toast.success("Successfully connected"); +}; +``` + +**Explanation:** + +- **`connect` Function** : This function connects the user's Portkey wallet with the dApp. + - **Fetch Accounts** : It fetches the wallet accounts using the provider. + - **Log Accounts** : Logs the accounts to the console for debugging. + - **Set Wallet Address** : Sets the current wallet address state variable with the fetched account. + +In this code, we fetch the Portkey wallet account using the provider and update the wallet address state variable. An alert notifies the user that their wallet is successfully connected. + +With the connect wallet function defined, we're ready to write the remaining functions in the next steps. + +### Check Contract Initialization + +**Step 1: Locate the File** + +- Go to the `src/pages/home/index.tsx` file. This file contains all the functionalities like show `initializeContract`, `startGame`, `makeMove`, `getGameStatus` and `getLatestBoard` etc. + +**Step 2: Prepare a Function to Check Whether the Contract is Initialized or not** + +- Scroll down to find the comment `// step 1 - Check If Contract is Initialized or not `. + +- Replace the existing **`checkIsContractInitialized`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 1 - Check If Contract is Initialized or not +const checkIsContractInitialized = async () => { + const result = await smartContract?.callViewMethod("GetInitialStatus", ""); // Call the GetInitialStatus method which is present on Smart Contract + setIsContractInitialized(result?.data?.value); // Expect value True if it's Initialized otherwise NULL if it's not +}; +``` + +### Initialize Contract + +- Scroll down to find the comment `// step 2 - Initialize the smart contract`. + +- Replace the existing **`checkIsContractInitialized`** function with this code snippet: + + + +```javascript title="home/index.tsx" +// step 2 - Initialize the smart contract +const initializeContract = async () => { + let initializeLoadingId; + try { + // Start Loading + initializeLoadingId = toast.loading("Initializing a Contract.."); + + await smartContract?.callSendMethod( + "Initialize", // Function Name + currentWalletAddress as string, // User Wallet Address + {} // No Arguments + ); + + // Update Loading Message with Success + toast.update(initializeLoadingId, { + render: "Contract Successfully Initialized", + type: "success", + isLoading: false, + }); + + return; + } catch (error: any) { + + // Update Loading Message with Error + toast.update(initializeLoadingId as Id, { + render: error.message, + type: "error", + isLoading: false, + }); + return; + } finally { + // Remove Loading Message + removeNotification(initializeLoadingId as Id); + } +}; +``` + +### Fetch Game Status + +- Write the function to fetch the game status. + +- Find the comment `// step 3 - Fetch Game Status function` on same file. + +- Replace the existing **`getGameStatus`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 3 - Fetch Game Status function +const getGameStatus = async (isFirstCheck?: boolean) => { + try { + const result = await smartContract?.callViewMethod("GetGameStatus", ""); + console.log("result", result); + if (result?.data.status === "finished") { + if (!isFirstCheck) { + toast.success(`${result.data.winner} is Winner ๐Ÿฅณ ๐Ÿ†`); + setWinner(result.data.winner); + } + } else { + setIsStarted(true); + getLatestBoard(); + } + } catch (error: any) { + toast.error(error.message); + } finally { + setLoading(false); + return; + } +}; +``` + +#### What This Function Does: + +1. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to fetch the game status using `getGameStatus` Function. + +Next, we'll write the **Fetch Board Data** function. + +### Fetch Board Data + +Write the function to fetch the board data. + +- Scroll down to find the comment `// step 4 - Fetch Latest Board Data from the Contract`. + +- Replace the existing **`getLatestBoard`** function with this code snippet: + +```javascript title="home/index.tsx" + // step 4 - Fetch Latest Board Data from the Contract + const getLatestBoard = async () => { + try { + const result = await smartContract?.callViewMethod("GetBoard", ""); + const arrayData = result?.data.rows + .map((rowData: any) => + rowData.split(",").map((item: string) => (item === "" ? null : item)) + ) + .flat(); + getNextTurn(arrayData); + setBoard(arrayData); + } catch (error: any) { + toast.error(error.message); + } finally { + return; + } + }; +``` + +#### What This Function Does: + +1. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to fetch the latest board data using `getLatestBoard` Function. + +3. **Convert Respnose Data into Array** : It converts object of the response data to an array to adjust every move into the UI board. + +Next, we'll write the **Start Game** function. + +### Start Game + +Write the Function to start the Tic-Tac-Toe game. + +- Scroll down to find the comment `// step 5 - Start Game function`. + +- Replace the existing **`startGame`** function with the following code snippet: + +```javascript title="home/index.tsx" +// step 5 - Start Game function +const startGame = async () => { + try { + if (!currentWalletAddress) { + toast.info("Please Login First"); + return; + } + if (!isContractInitialized) { + await initializeContract(); + } + await smartContract?.callSendMethod("StartGame", currentWalletAddress); + setTurnType("X"); + setIsStarted(true); + getLatestBoard(); + toast.success("Game Has been Started โœ…"); + } catch (error: any) { + toast.error(error.message); + } +}; +``` + +#### What This Function Does: + +1. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to start the game by using the `startGame` Function. + +Next, we'll write the **Make the Move** function. + +### Perform the Move + +Write a function to make the move. + +- Scroll down to find the comment `// step 6 - Make the Move Function`. + +- Replace the existing **`makeMove`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 6 - Make the Move Function +const makeMove = async (x: number, y: number) => { + try { + if (!currentWalletAddress) { + toast.info("Please Login First"); + return; + } + const moveIndex = getIndexFromPosition(x, y); + setMoveLoadingIndex(moveIndex as number); + const params = { + x: x, + y: y, + }; + await smartContract?.callSendMethod( + "MakeMove", + currentWalletAddress, + params + ); + // toast.success("Your Move Submitted"); + changeTurn(); + await getLatestBoard(); + await getGameStatus(); + } catch (error: any) { + toast.error(error.message); + } finally { + } +}; +``` + +#### What This Function Does: + +1. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to make the move by an input from a player using `makeMove` function by passing **X** and **Y** value of the Board. + +Now that we've written all the necessary frontend functions and components for play the Tic-Tac-Toe page, we're ready to run the Tic-Tac-Toe dApp application in the next step. + +### Run Application + +In this step, we will run the Tic-Tac-Toe dApp application. + +- To begin, run the following command on your terminal. + +```bash title="Terminal" +npm run dev +``` + +:::info + +**Note**: Ensure that you are running this command under the **tic-tac-toe/2-dapp** folder. + +::: + +- You should observe the following as shown below. + + ![run-app-success](/img/vote-npm-run-console.png) + +- Upon clicking on the **localhost URL**, you should be directed to the Tic-Tac-Toe dApp landing page as shown below. + +:::tip +If you are developing and testing this with github codespace, you can use `port forward` to test the web server that is running in codespace, here is the link on how to use `port forward` for codespace https://docs.github.com/en/codespaces/developing-in-a-codespace/forwarding-ports-in-your-codespace +::: + +- Usually codespaces automatically forward ports, you should see a pop-up message at the bottom right of your codespace browser window as shown in the diagram below: + + ![open-in-browser](/img/codespace-forwarded-port.png) + +- Click the link to open the Tic-Tac-Toe dApp in the browser. + + ![tic-tac-toe-home-page](/img/tic-tac-toe-home-page.png) + +#### Create Portkey Wallet + +:::info +Portkey is the first AA wallet from aelf's ecosystem, migrating users, developers and projects from Web2 to Web3 with DID solution. + +Users can swiftly log into Portkey via their Web2 social info with no private keys or mnemonics required. Underpinned by social recovery and decentralized guardian design, Portkey safeguards users' assets from centralized control and theft. Portkey has a unique payment delegation mechanism which enables interested parties to function as delegatees to pay for user activities on users' behalf. This means that users can create accounts for free and fees for other usages may also be covered in Portkey. + +Portkey also provides crypto on/off-ramp services, allowing users to exchange fiat with crypto freely. It supports the storage and management of various digital assets such as tokens, NFTs, etc. The compatibility with multi-chains and seamless connection to all kinds of DApps makes Portkey a great way to enter the world of Web3. + +With DID solution as its core, Portkey provides both Portkey Wallet and Portkey SDKs. + +For more information, you may visit the official documentation for Portkey at https://doc.portkey.finance/. +::: + + +- Download the Chrome extension for Portkey from https://chromewebstore.google.com/detail/portkey-wallet/iglbgmakmggfkoidiagnhknlndljlolb. + +:::info +The Portkey extension supports Chrome browser only (for now). Please ensure that you are using Chrome browser. +You may download Chrome from https://www.google.com/intl/en_sg/chrome/. +::: + +- Once you have downloaded the extension, you should see the following on your browser as shown below. + + ![welcome-to-portkey](/img/welcome-to-portkey.png) + +- Click on `Get Start` and you should see the following interface as shown below. + + ![portkey-login](/img/portkey-login.png) + + +**Sign up** + +- Switch to **aelf Testnet** network by selecting it: + + ![portkey-switch-to-testnet](/img/portkey-switch-to-testnet.png) + +:::danger +Please make sure you are using `aelf Testnet` in order to be able to receive your testnet tokens from the Faucet. +::: + +- Proceed to sign up with a Google Account or your preferred login method and complete the necessary accounts creation prompts and you should observe the following interface once you have signed up. + + ![success-login](/img/success-login.png) + +With that, you have successfully created your very first Portkey wallet within seconds. How easy was that? + +:::info +It is highly recommended to pin the Portkey wallet extension for easier access and navigation to your Portkey wallet! +::: + +- Next, click on โ€˜Open Portkeyโ€™ and you should now observe the following as shown below. + + ![portkey-wallet-preview](/img/portkey-wallet-preview.png) + +**Connect Portkey Wallet** + +- Click on **"Connect Wallet"** to connect your Portkey wallet. + + ![connect-wallet](/img/tic-tac-toe-connect-wallet.png) + +- The button will change to **"Your Wallet Address"** when the connection is successful. + + ![collect-wallet-success](/img/tic-tac-toe-wallet-connect-success.png) + +--- + +**Start The Game** + +- Click on **"Start The Game"** button to Start The Tic Tac Toe Game. + + ![start-game](/img/start-game-button.png) + +- You will receive a transaction request on your portkey wallet to **Sign** the transaction. + + ![start-game-sign-request](/img/start-game-sign-request.png) + +- Click on **Sign** the transaction. + +- After the transaction is successfully processed, your game will start โœ…. + + ![start-game-success](/img/start-game-success.png) + +As we have **Started the Game** successfully, let's start to perform the moves and enjoy the game. + +--- + +**Perform the Move** + + Let's start to make the moves and play the game. + +:::tip +Your first move will always be **"X"** as shown in the picture below. +::: + +- Click on the first square of the board, as shown in the picture below. + + ![make-move](/img/make-move.png) + +- You will receive a transaction request on your Portkey wallet to **Sign** for making the move. + + ![make-move-sign-request](/img/make-move-sign-request.png) + +- After the transaction is successfully processed, your first move will be submitted to blockchain โœ…. + +- Now it's turn of move by the opponent and the move sign will change from **"X"** to **"O"**. Your active turn is **"O"** now, as shown in the picture below. + +- Click on the middle square of the board for **"O"**, as shown in the picture below. + + ![second-move](/img/second-move.png) + +- You will receive a transaction request on your Portkey wallet to **Sign** for the second move. + + ![second-move-request](/img/second-move-request.png) + +- Click on **Sign** the transaction. + +- After the transaction is successfully processed, your second move will be submitted to the blockchain โœ…. + +- Now you can continue making other moves as demonstrated above. + +- After winning the game, you will see this winning screen with the corresponding move (**X** or **O**). + + ![winner-view](/img/winner-view.png) + +- Click on the **Start Again** button to continue playing the Tic-Tac-Toe game with aelf blockchain. Enjoy! + + ![start-again](/img/start-again.png) + +- You will be redirected again to the **Welcome screen** of the Game. + + ![welcome-screen](/img/welcome-screen.png) + +:::success +๐ŸŽ‰ Congratulations Learners! You have successfully built your Tic-Tac-Toe dApp with aelf Blockchain. +::: + + +## ๐ŸŽฏ Conclusion + +๐ŸŽ‰ Congratulations on successfully completing the **Tic Tac Toe dApp** tutorial! ๐ŸŽ‰ You've achieved significant milestones, from setting up your development environment to deploying and interacting with your Tic-Tac-Toe smart contract on the aelf blockchain. ๐ŸŒŸ + +**๐Ÿ“š What You've Learned** + +Throughout this tutorial, you've mastered: + + - **๐Ÿ› ๏ธ Setting Up Your Development Environment:** You equipped your workspace by installing and configuring all the necessary tools to get your smart contract project off the ground. + + - **๐Ÿ’ป Developing Your Smart Contract:** You built the core logic of your Tic Tac Toe game, writing and compiling the smart contract that manages game states, moves, and outcomes. + + - **๐Ÿš€ Deploying the Smart Contract:** You successfully deployed your Tic Tac Toe smart contract to the aelf blockchain, making it live and ready for gameplay. + + - **๐Ÿ”ง Interacting with Your Deployed Smart Contract through the dApp:** You connected your frontend to the blockchain, configured the Portkey provider, and implemented essential functions like wallet connectivity, game initialization, and move management. + +**๐Ÿ” Final Output** + +By now, you should have: + + - ๐Ÿ“œ A deployed Tic-Tac-Toe smart contract that governs the game's rules and manages players' moves on the blockchain. + + - ๐Ÿ’ป A fully functional Tic-Tac-Toe dApp, allowing users to connect their wallets, start a game, make moves, and determine the winner, all in a decentralized manner. + +**โžก๏ธ What's Next?** + +With the foundation laid, consider advancing your Tic-Tac-Toe dApp with more sophisticated features: + + - **๐Ÿ“ˆ Enhancing Game Logic:** Add more features like AI opponents, multiplayer functionality, or scoring systems to make the game more engaging. + + - **๐Ÿ”’ Improving Security:** Secure your game by applying best practices in smart contract security to protect users' data and gameplay integrity. + + - **๐ŸŒ Exploring Cross-Chain Capabilities:** Expand your dAppโ€™s reach by exploring aelfโ€™s cross-chain interoperability, enabling interactions with other blockchains. + +Blockchain technology and decentralized applications offer limitless possibilities. With your Tic-Tac-Toe dApp, you're now poised to continue innovating and exploring new horizons with aelf. ๐Ÿš€ + +Happy coding and expanding your **Tic-Tac-Toe dApp!** ๐Ÿ˜Š diff --git a/docs/quick-start/developers/todo-app/index.js b/docs/quick-start/developers/todo-dapp/index.js similarity index 100% rename from docs/quick-start/developers/todo-app/index.js rename to docs/quick-start/developers/todo-dapp/index.js diff --git a/docs/quick-start/developers/todo-dapp/index.md b/docs/quick-start/developers/todo-dapp/index.md new file mode 100644 index 00000000..44f2a607 --- /dev/null +++ b/docs/quick-start/developers/todo-dapp/index.md @@ -0,0 +1,1133 @@ +--- +sidebar_position: 5 +title: ToDo dApp +description: Moderately difficult smart contract and dApp +--- + +**Description**: This contract is moderately complex, demonstrating task management functionalities such as creating, updating, and deleting tasks. It also includes features for task prioritization and user interaction. + +**Purpose**: To introduce you to task management systems in smart contracts, focusing on state management, user interactions, and contract updates for efficient handling of to-do tasks. + +**Difficulty Level**: Moderate + + + + +## Step 1 - Setting up your development environment + +import Setup from "../\_setup.md" + + + +## Step 2 - Develop Smart Contract + +### Start Your Smart Contract Project + +- Open your `Terminal`. + +- Enter the following command to generate a new project: + +```bash title="Terminal" +mkdir todo-app +cd todo-app +dotnet new aelf -n ToDoApp +``` + +### Adding Your Smart Contract Code + +Now that we have a template todo list project, we can customise the template to incorporate our own contract logic. +Let's start by implementing methods to handle the basic functionality of creating, editing, listing, deleting, and marking tasks as complete within the contract state. ToDo dApp includes the below functionalities like: +1. Create a task (Name, category, description, createAt, updatedAt) +2. Mark task as completed +3. Delete a task +4. List all the tasks +5. Edit a task + +- Enter this command in your `Terminal`. + +```bash title="Terminal" +cd src +``` + +#### Defining Methods and Messages + +- Rename the proto file name `hello_world_contract.proto` inside folder `Protobuf/contract/` to `todo_app.proto`: + +```bash title="Terminal" +mv Protobuf/contract/hello_world_contract.proto Protobuf/contract/todo_app.proto +``` + +The `.proto` file defines the structure and serialization of data, ensuring consistent communication and data exchange between the contract and external systems. + +- Open the project with your IDE. + +The implementation of `todo_app.proto` file inside folder `src/Protobuf/contract/` is as follows: + +```csharp title="todo_app.proto" +syntax = "proto3"; +import "aelf/options.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "Protobuf/reference/acs12.proto"; +// The namespace of this class +option csharp_namespace = "AElf.Contracts.ToDo"; +service ToDo { + // The name of the state class the smart contract is going to use to access blockchain state + option (aelf.csharp_state) = "AElf.Contracts.ToDo.ToDoState"; + option (aelf.base) = "Protobuf/reference/acs12.proto"; + rpc Initialize (google.protobuf.Empty) returns (google.protobuf.Empty) { + } + rpc CreateTask (TaskInput) returns (google.protobuf.StringValue) { + } + rpc UpdateTask (TaskUpdateInput) returns (google.protobuf.Empty) { + } + rpc DeleteTask (google.protobuf.StringValue) returns (google.protobuf.Empty) { + } + rpc ListTasks (google.protobuf.StringValue) returns (TaskList) { + option (aelf.is_view) = true; + } + rpc GetTask (google.protobuf.StringValue) returns (Task) { + option (aelf.is_view) = true; + } + rpc GetInitialStatus (google.protobuf.Empty) returns (google.protobuf.BoolValue) { + option (aelf.is_view) = true; + } +} +// A message to represent a task +message Task { + string task_id = 1; + string name = 2; + string description = 3; + string category = 4; + string status = 5; + string owner = 6; + int64 created_at = 7; + int64 updated_at = 8; +} +// Input for creating a task +message TaskInput { + string name = 1; + string description = 2; + string category = 3; +} +// Input for updating a task +message TaskUpdateInput { + string task_id = 1; + string name = 2; + string description = 3; + string category = 4; + string status = 5; +} +// List of tasks +message TaskList { + repeated Task tasks = 1; +} +``` + +- `rpc` methods define the callable functions within the contract, allowing external systems to interact with the contract's logic. +- `message` represent the structured data exchanged between the contract and external systems. + +#### Define Contract States + +The implementation of the ToDo app state inside file `src/ToDoAppState.cs` is as follows: + +```csharp title="src/ToDoAppState.cs" +using AElf.Sdk.CSharp.State; +using AElf.Types; + +namespace AElf.Contracts.ToDo +{ + public class ToDoState : ContractState + { + public BoolState Initialized { get; set; } + public SingletonState
Owner { get; set; } + public MappedState Tasks { get; set; } // Mapping of task ID to Task + public MappedState TaskExistence { get; set; } // Mapping to track task existence + public StringState TaskIds { get; set; } // Concatenated string of task IDs + public Int32State TaskCounter { get; set; } // Counter for generating unique IDs + } +} +``` + +- The `State.cs` file in an aelf blockchain smart contract holds the variables that store the contract's data, making sure this data is saved and accessible whenever the contract needs it. + +#### Implement ToDo Smart Contract + +The implementation of the ToDo App smart contract inside file `src/ToDoApp.cs` is as follows: + +```csharp title="src/ToDoApp.cs" +using Google.Protobuf.WellKnownTypes; +using System.Collections.Generic; +namespace AElf.Contracts.ToDo +{ + public class ToDo : ToDoContainer.ToDoBase + { + public override Empty Initialize(Empty input) + { + if (State.Initialized.Value) + { + return new Empty(); + } + State.Initialized.Value = true; + State.Owner.Value = Context.Sender; + State.TaskIds.Value = ""; + State.TaskCounter.Value = 0; + return new Empty(); + } + public override StringValue CreateTask(TaskInput input) + { + if (!State.Initialized.Value) + { + return new StringValue { Value = "Contract not initialized." }; + } + var taskId = (State.TaskCounter.Value + 1).ToString(); + State.TaskCounter.Value++; + var timestamp = Context.CurrentBlockTime.Seconds; + // Create task dictionary entry directly in ToDo class + State.Tasks[taskId] = new Task + { + TaskId = taskId, + Name = input.Name, + Description = input.Description, + Category = input.Category, + Status = "pending", + CreatedAt = timestamp, + UpdatedAt = timestamp, + Owner = Context.Sender.ToString().Trim('"'), + }; + State.TaskExistence[taskId] = true; + // Append task ID to the list of IDs + var existingTaskIds = State.TaskIds.Value; + existingTaskIds += string.IsNullOrEmpty(existingTaskIds) ? taskId : $",{taskId}"; + State.TaskIds.Value = existingTaskIds; + return new StringValue { Value = taskId }; + } + public override Empty UpdateTask(TaskUpdateInput input) + { + var task = State.Tasks[input.TaskId]; + if (task == null) + { + return new Empty(); // Handle case if task doesn't exist + } + task.Name = input.Name ?? task.Name; + task.Description = input.Description ?? task.Description; + task.Category = input.Category ?? task.Category; + task.Status = input.Status ?? task.Status; + task.UpdatedAt = Context.CurrentBlockTime.Seconds; + State.Tasks[input.TaskId] = task; + return new Empty(); + } + public override Empty DeleteTask(StringValue input) + { + State.Tasks.Remove(input.Value); + State.TaskExistence.Remove(input.Value); + // Remove task ID from the list of IDs + var existingTaskIds = State.TaskIds.Value.Split(','); + var newTaskIds = new List(existingTaskIds.Length); + foreach (var taskId in existingTaskIds) + { + if (taskId != input.Value) + { + newTaskIds.Add(taskId); + } + } + State.TaskIds.Value = string.Join(",", newTaskIds); + return new Empty(); + } + public override TaskList ListTasks(StringValue input) + { + var owner = input.Value; // Get the owner value from the input + var taskList = new TaskList(); + var taskIds = State.TaskIds.Value.Split(','); + foreach (var taskId in taskIds) + { + var task = State.Tasks[taskId]; + if (task != null && task.Owner == owner) // Filter tasks by owner + { + taskList.Tasks.Add(task); + } + } + return taskList; + } + public override Task GetTask(StringValue input) + { + var task = State.Tasks[input.Value]; + if (task == null) + { + return new Task { TaskId = input.Value, Name = "Task not found." }; + } + return task; + } + public override BoolValue GetInitialStatus(Empty input) + { + return new BoolValue { Value = State.Initialized.Value }; + } + } +} +``` + +### Building Smart Contract + +- Build the smart contract code with the following command inside `src` folder: + +```bash title="Terminal" +dotnet build +``` + +You should see **ToDoApp.dll.patched** in the directory `ToDoApp/src/bin/Debug/net.6.0` + +## Step 3 - Deploy Smart Contract + +import Deploy from "../\_deploy_todo.md" + + + +## Step 4 - Interact with Your Deployed Smart Contract through dApp + +### Project Setup + +Let's start by cloning the frontend project repository from github. + +```bash title="Terminal" +git clone https://github.com/AElfProject/aelf-samples.git +``` + +- Next, navigate to the frontend project directory with this command: + +```bash title="Terminal" +cd aelf-samples/todo/2-dapp +``` + +- Once you're inside the `2-dapp` directory, open the project with your preferred IDE (e.g., VSCode). You should see the project structure as shown below. + +export const tree = { + "type": "directory", + "uri": "2-dapp", + "expanded": true, + "children": [ + { + "type": "directory", + "uri": "app" + }, + { + "type": "directory", + "uri": "assets" + }, + { + "type": "directory", + "uri": "public" + }, + { + "type": "directory", + "uri": "src" + }, + { + "type": "file", + "uri": ".gitignore" + }, + { + "type": "file", + "uri": "components.json" + }, + { + "type": "file", + "uri": "index.html" + }, + { + "type": "file", + "uri": "package.json" + }, + { + "type": "file", + "uri": "postcss.config.js" + }, + { + "type": "file", + "uri": "README.md" + }, + { + "type": "file", + "uri": "tailwind.config.js" + }, + { + "type": "file", + "uri": "tsconfig.json" + }, + { + "type": "file", + "uri": "tsconfig.node.json" + }, + { + "type": "file", + "uri": "vite.config.ts" + } + ] +} + +
+ +#### Install necessary libraries + +- Run this command in the terminal to install all necessary packages and libraries: + +```bash title="Terminal" +npm install +``` + +We are now ready to build the frontend components of our ToDo dApp. + +### Configure Portkey Provider & Write Connect Wallet Function + +Now, we'll set up our Portkey wallet provider to allow users to connect their Portkey wallets to the dApp and interact with the smart contract. We'll be interacting with the already deployed ToDo smart contract for this tutorial. + +**Step 1. Locate the File:** + +- Go to the `src/hooks/useTodoSmartContract.ts` file. + +**Step 2. Fetch the Smart Contract:** + +- Find the comment ` //Step A - Function to fetch a smart contract based on deployed wallet address` + +- Replace the existing **`fetchContract`** function with this updated code: + +```javascript title="useTodoSmartContract.ts" +//Step A - Function to fetch a smart contract based on deployed wallet address +const fetchContract = async () => { + if (!provider) return null; + + try { + // 1. get the sidechain tDVW using provider.getChain + const chain = await provider?.getChain("tDVW"); + if (!chain) throw new Error("No chain"); + + //Address of ToDo Smart Contract + //Replace with Address of Deployed Smart Contract + const address = "your_deployed_todo_contract_address"; + + // 2. get the ToDo contract + const todoContract = chain?.getContract(address); + setSmartContract(todoContract); + } catch (error) { + console.log(error, "====error"); + } +} +``` + +:::tip +โ„น๏ธ Note: You are to replace the address placeholder with your deployed ToDo smart contract address from "Deploy Smart Contract" step! + +example: +//Replace with Address of Deployed Smart Contract +const address = "your_deployed_todo__smart_contract_address"; +::: + +**Explanation:** + +- **`fetchContract`** **Function**: This function fetches a smart contract based on the given chain symbol (e.g., "AELF" or "tDVW") and the contract address. + + - **Check Provider** : If no provider is available, the function returns null. + - **Fetch Chain** : The function fetches chain information using the provider. + - **Get Contract** : It retrieves the smart contract instance from the chain. + +`AELF` represents the mainnet chain and `tDVW` represents the testnet chain respectively on aelf blockchain. + +**Step 3. Initialize and Fetch the Smart Contracts:** + +- Find the comment `// Step B - Effect hook to initialize and fetch the smart contract when the provider changes.` + +- Replace the existing **`useEffect`** hook with this updated code: + +```javascript title="useTodoSmartContract.ts" + // Step B - Effect hook to initialize and fetch the smart contract when the provider changes + useEffect(() => { + fetchContract(); + }, [provider]); // Dependency array ensures this runs when the provider changes +``` + +**Explanation:** +- **`useEffect` Hook** : This hook initializes and fetches the smart contracts when the provider changes. + - **Check Provider** : If no provider is available, the function returns null. + - **Fetch Contracts** : It fetches and sets the smart contracts. + +By following these steps, we'll configure the Portkey provider to connect users' wallets to our app and interact with the ToDo smart contract including task management related functionalities. This setup will enable our frontend components to perform actions like `Create Task`, `Edit Task`, and `Delete Task`. + +### Configure Connect Wallet Function + +**Step 1: Locate the File** + +- Go to the `src/components/layout/header/index.tsx` file. + +**Step 2: Write the Connect Wallet Function** + +- The `header/index.tsx` file is the header of our ToDo dApp. It allows users to connect their Portkey wallet with the ToDo dApp. + +- Before users can interact with the smart contract, we need to write the `Connect Wallet` function. + +- Find the comment `// Step C - Connect Portkey Wallet`. + +- Replace the existing connect function with this code snippet: + +```javascript title="header/index.tsx" +const connect = async (walletProvider?: IPortkeyProvider) => { + // Step C - Connect Portkey Wallet + const accounts = await (walletProvider ? walletProvider : provider)?.request({ + method: MethodsBase.REQUEST_ACCOUNTS, + }); + const account = accounts?.AELF && accounts?.AELF[0]; + if (account) { + setCurrentWalletAddress(account.replace(/^ELF_/, "").replace(/_AELF$/, "")); + setIsConnected(true); + } + !walletProvider && toast.success("Successfully connected"); +}; +``` + +**Explanation:** + +- **`connect` Function** : This function connects the user's Portkey wallet with the dApp. + - **Fetch Accounts** : It fetches the wallet accounts using the provider. + - **Log Accounts** : Logs the accounts to the console for debugging. + - **Set Wallet Address** : Sets the current wallet address state variable with the fetched account. + - **Update Connection Status** : Updates the state to indicate that the wallet is connected. + - **User Notification** : Displays an alert to notify the user that their wallet is successfully connected. + +In this code, we fetch the Portkey wallet account using the provider and update the wallet address state variable. An alert notifies the user that their wallet is successfully connected. + +With the connect wallet function defined, we're ready to write the remaining functions in the next steps. + +### Configure Create Task Form + +**Step 1: Locate the File** + +1. Go to the `src/pages/home/index.tsx` file. This file contains all the functionalities like show user's Task, CreateTask, UpdateTask, DeleteTask and Filter all Tasks, etc. + +**Step 2: Prepare Form to Create and Update Tasks** + +1. Find the comment `// Step D - Configure Todo Form`. + +2. Replace the form variable with this code snippet: + +```javascript title="home/index.tsx" +// Step D - Configure Todo Form +const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + description: "", + }, +}); +``` + +#### Here's what the function does: + +1. Initializes a new form variable with default values needed to create a task. + +2. Fields include: `name` and `description`. + +Now the form is ready for users to fill in the necessary details. + +### Check Contract Initialization + +- Scroll down to find the comment `// step 1 - Check if contract is initialized or not`. + +- Replace the existing **`checkIsContractInitialized`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 1 - Check if contract is initialized or not +const checkIsContractInitialized = async () => { + const result = await smartContract?.callViewMethod("GetInitialStatus", ""); // Call the GetInitialStatus method which is present on Smart Contract + setIsContractInitialized(result?.data?.value); // Expect value True if it's Initialized otherwise NULL if it's not +}; +``` + +### Initialize Contract + +- Scroll down to find the comment `// step 2 - Initialize the smart contract`. + +- Replace the existing **`checkIsContractInitialized`** function with this code snippet: + + + +```javascript title="home/index.tsx" +// step 2 - Initialize the smart contract +const initializeContract = async () => { + let initializeLoadingId; + try { + // Start Loading + initializeLoadingId = toast.loading("Initializing a Contract.."); + + await smartContract?.callSendMethod( + "Initialize", // Function Name + currentWalletAddress as string, // User Wallet Address + {} // No Arguments + ); + + // Update Loading Message with Success + toast.update(initializeLoadingId, { + render: "Contract Successfully Initialized", + type: "success", + isLoading: false, + }); + } catch (error: any) { + // Update Loading Message with Error + toast.update(initializeLoadingId as Id, { + render: error.message, + type: "error", + isLoading: false, + }); + } finally { + // Remove Loading Message + removeNotification(initializeLoadingId as Id); + } +}; +``` + +### Create a New Task + +- Write the function to `Create a New Task`** + +- The `home/index.tsx` file includes the code to create tasks. It allows users to create new tasks. + +- Find the comment `// step 3 - Create a New Task using Smart Contract`. + +- Replace the existing **`createNewTask`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 3 - Create a New Task using Smart Contract +const createNewTask = async (values: { + name: string; + description: string; +}) => { + let createLoadingId; + try { + // Start Loading + createLoadingId = toast.loading("Creating a New Task.."); + setFormLoading(true); + + // Prepare Arguments for Create a New Task + const sendData = { + name: values.name, + description: values.description, + category: selectedCategory?.value, + status: TASK_STATUS.pending, + }; + + // Call CreateTask Function of Smart Contract + await smartContract?.callSendMethod( + "CreateTask", + currentWalletAddress as string, + sendData + ); + + // Update Loading Message with Success + toast.update(createLoadingId, { + render: "New Task Successfully Created", + type: "success", + isLoading: false, + }); + + // Get New Data from Contract + getTodoData(); + } catch (error: any) { + // Update Loading Message with Error + toast.update(createLoadingId as Id, { + render: error.message, + type: "error", + isLoading: false, + }); + } finally { + // Close Form Modal + handleCloseModal(); + + // Remove Loading Message + removeNotification(createLoadingId as Id); + setFormLoading(false); + } +}; +``` + +#### What This Function Does: + +1. **Creates an Object with Task Details** : It prepares the data needed to create a new task. + +2. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to create the new task using the prepared data. + +Next, we'll write the **Update an Existing Task** function. + +### Update an Existing Task + +Write the function for update an existing task. + +- Scroll down to find the comment `// step 4 - Update an Existing Task`. + +- Replace the existing **`updateTask`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 4 - Update an Existing Task +const updateTask = async (values: { name: string; description: string }) => { + let updateLoadingId; + try { + // Start Loading + updateLoadingId = toast.loading("Updating a Task.."); + setFormLoading(true); + + // Prepare Arguments for Update the Task + const sendData = { + taskId: updateId, + name: values.name, + description: values.description, + category: selectedCategory?.value, + status: TASK_STATUS.pending, + }; + + // Call UpdateTask Function of Smart Contract + await smartContract?.callSendMethod( + "UpdateTask", + currentWalletAddress as string, + sendData + ); + + // Update Loading Message with Success + toast.update(updateLoadingId, { + render: "Task Successfully Updated", + type: "success", + isLoading: false, + }); + + // Get New Data from Contract + getTodoData(); + } catch (error: any) { + // Update Loading Message with Error + toast.update(updateLoadingId as Id, { + render: error.message, + type: "error", + isLoading: false, + }); + } finally { + // Close Form Modal + handleCloseModal(); + // Remove Loading Message + removeNotification(updateLoadingId as Id); + setFormLoading(false); + } +}; +``` + +#### What This Function Does: + +1. **Creates an Object with Updated Task Details** : It prepares the data needed for the updated task details + +2. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to update the existing task using the prepared data. + +Next, we'll write the **Update Task Status (completeTask)** function. + +### Update the Task Status + +Write the Function to update the task status (completeTask). + +- Scroll down to find the comment `// step 5- Update Status from Pending to Completed of the Task`. + +- Replace the existing **`completeTask`** function with the following code snippet: + +```javascript title="home/index.tsx" +// step 5- Update Status from Pending to Completed of the Task +const completeTask = async (data: ITodoObject) => { + let completeLoadingId; + try { + // Start Loading + completeLoadingId = toast.loading("Moving to Completed Task.."); + setUpdateId(data.taskId); // set Update Id for Loading on Button + + // Call UpdateTask Function of Smart Contract + await smartContract?.callSendMethod( + "UpdateTask", + currentWalletAddress as string, + { ...data, status: TASK_STATUS.completed } + ); + + // Update Loading Message with Success + toast.update(completeLoadingId, { + render: "Task Moved to Completed", + type: "success", + isLoading: false, + }); + + // Get New Data from Contract + await getTodoData(); + } catch (error: any) { + // Update Loading Message with Error + toast.update(completeLoadingId as Id, { + render: error.message, + type: "error", + isLoading: false, + }); + } finally { + setUpdateId(null); + // Remove Loading Message + removeNotification(completeLoadingId as Id); + } +}; +``` + +#### What This Function Does: + +1. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to update the task status by passind the `completed` status as an argument. + +Next, we'll write the **Delete the Task** function. + +### Delete the Task + +Write a function to delete an existing task. + +- Scroll down to find the comment `// step 6 - Delete the Task`. + +- Replace the existing **`deleteTask`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 6 - Delete the Task +const deleteTask = async (data: ITodoObject) => { + let deleteLoadingId; + try { + // Start Loading + deleteLoadingId = toast.loading("Removing a Task.."); + setDeletingId(data.taskId); // set Deleting Id for Loading on Button + + // Call UpdateTask Function of Smart Contract and update the status as "Removed" + await smartContract?.callSendMethod( + "UpdateTask", + currentWalletAddress as string, + { ...data, status: TASK_STATUS.removed } + ); + + // Update Loading Message with Success + toast.update(deleteLoadingId, { + render: "Task Successfully Removed", + type: "success", + isLoading: false, + }); + + // Get New Data from Contract + await getTodoData(); + } catch (error: any) { + // Update Loading Message with Error + toast.update(deleteLoadingId as Id, { + render: error.message, + type: "error", + isLoading: false, + }); + } finally { + setDeletingId(null); + // Remove Loading Message + removeNotification(deleteLoadingId as Id); + } +}; +``` + +#### What This Function Does: + +1. **Calls Smart Contract Method** : It interacts with the blockchain smart contract to delete the existing task by passing status as "removed". + +Next, we'll write the **Handle Submit Form** function. + +### Configure Submit Form + +- Scroll down to find the comment `// step 7 - Handle Submit Form`. + +- Replace the existing **`onSubmit`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 7 - Handle Submit Form +const onSubmit = async (values: { name: string; description: string }) => { + + // Check Whether Contract Initialized or not + if (isContractInitialized !== true) { + await initializeContract(); // initialize the contract if it's not initialized before + } + + // Check Whether Form is for Create or Update the Task + if (!!updateId) { + await updateTask(values); // Call updateTask for Update the task + } else { + await createNewTask(values); // Call createNewTask for Create a new task + } +}; +``` + +#### What This Function Does: + +1. **Check initialized contract**: It checks whether the smart contract is initialized or not by using `initializeContract` function. + +2. **Update Task**: Call the `updateTask` function if updatedId has any value. + +3. **Create Task**: Call the `createNewTask` function if updatedId does not have any value. + +Here, we have completed functions to **Create Task**, **Update Task** and **Delete Task** and now it's time to write a function to **Fetch Tasks** from the smart contract. + +### Fetch All Tasks + +- Scroll up to find the comment `// step 8 - Fetch All Tasks`. + +- Replace the existing **`getTodoData`** function with this code snippet: + +```javascript title="home/index.tsx" +// step 8 - Fetch All Tasks +const getTodoData = async () => { + try { + const result = await smartContract?.callViewMethod("ListTasks", { + value: currentWalletAddress, + }); + console.log("result", result?.data); + setTodoData(result?.data ? result?.data.tasks : []); + } catch (error) { + console.log("error======", error); + } finally { + setLoading(false); + } +}; +``` +#### Here's what the function does: + +1. **Fetches Task Data:** It calls `ListTasks` to get the list of all ToDo tasks from the ToDo smart contract. +2. **Set Tasks on State:** Get the result data from the smart contract and set an array of all tasks into `todoData` State. + +We have prepared necessary function to fetch all the tasks created from a connected user's wallet. + +Now that we've written all the necessary frontend functions and components, we're ready to run the ToDo dApp application in the next step. + +### Run Application + +In this step, we will run the ToDo dApp application. + +- To begin, run the following command on your terminal. + +```bash title="Terminal" +npm run dev +``` + +:::info + +**Note**: Ensure that you are running this command under the **todo/2-dapp** folder. + +::: + +- You should observe the following as shown below. + + ![run-app-success](/img/vote-npm-run-console.png) + +- Upon clicking on the **localhost URL**, you should be directed to the ToDo dApp landing page as shown below. + +:::tip +If you are developing and testing this with github codespace, you can use port forward to test the web server that is running in codespace, here is the link on how to use port forward for codespace https://docs.github.com/en/codespaces/developing-in-a-codespace/forwarding-ports-in-your-codespace +::: + +- Usually codespace will automatically forward port, you should see a pop-up message at the bottom right of your codespace browser window as shown in the diagram below: + + ![open-in-browser](/img/codespace-forwarded-port.png) + +- Click the link to open the ToDo dApp in the browser. + + ![todo-home-page](/img/todo-homepage.jpg) + +#### Create Portkey Wallet + +:::info +Portkey is the first AA wallet from aelf's ecosystem, migrating users, developers and projects from Web2 to Web3 with DID solution. + +Users can swiftly log into Portkey via their Web2 social info with no private keys or mnemonics required. Underpinned by social recovery and decentralized guardian design, Portkey safeguards users' assets from centralized control and theft. Portkey has a unique payment delegation mechanism which enables interested parties to function as delegatees to pay for user activities on users' behalf. This means that users can create accounts for free and fees for other usages may also be covered in Portkey. + +Portkey also provides crypto on/off-ramp services, allowing users to exchange fiat with crypto freely. It supports the storage and management of various digital assets such as tokens, NFTs, etc. The compatibility with multi-chains and seamless connection to all kinds of DApps makes Portkey a great way to enter the world of Web3. + +With DID solution as its core, Portkey provides both Portkey Wallet and Portkey SDKs. + +For more information, you may visit the official documentation for Portkey at https://doc.portkey.finance/. +::: + + +- Download the Chrome extension for Portkey from https://chromewebstore.google.com/detail/portkey-wallet/iglbgmakmggfkoidiagnhknlndljlolb. + +:::info +The Portkey extension supports Chrome browser only (for now). Please ensure that you are using Chrome browser. +You may download Chrome from https://www.google.com/intl/en_sg/chrome/. +::: + +- Once you have downloaded the extension, you should see the following on your browser as shown below. + + ![welcome-to-portkey](/img/welcome-to-portkey.png) + +- Click on `Get Start` and you should see the following interface as shown below. + + ![portkey-login](/img/portkey-login.png) + + +**Sign up** + +- Switch to **aelf Testnet** network by selecting it: + + ![portkey-switch-to-testnet](/img/portkey-switch-to-testnet.png) + +:::danger +Please make sure you are using `aelf Testnet` in order to be able to receive your testnet tokens from the Faucet. +::: + +- Proceed to sign up with a Google Account or your preferred login method and complete the necessary accounts creation prompts and you should observe the following interface once you have signed up. + + ![success-login](/img/success-login.png) + +With that, you have successfully created your very first Portkey wallet within seconds. How easy was that? + +:::info +It is highly recommended to pin the Portkey wallet extension for easier access and navigation to your Portkey wallet! +::: + +- Next, click on โ€˜Open Portkeyโ€™ and you should now observe the following as shown below. + + ![portkey-wallet-preview](/img/portkey-wallet-preview.png) + +**Connect Portkey Wallet** + +- Click on **"Connect Wallet"** to connect your Portkey wallet. + + ![connect-wallet](/img/todo-connect-wallet.jpg) + +- The button will change to **"Your Wallet Address"** when the connection is successful. + + ![collect-wallet-success](/img/todo-wallet-connect-success.jpg) + +--- + +**Create a New Task** + +- Click on **"Add New"** button to create a new task. + + ![create-task](/img/create-task.png) + +- You will see the pop-up modal with form to create a new task. Please fill all the necessary fields like `Name`, `Description` and `Category`. + + ![create-task-form](/img/create-task-form.png) + +- Click on **Create New Task** Button. + +- Now, You will receive a transaction request on your portkey wallet to **Sign** the transaction. + + ![create-task-sign-request](/img/create-task-sign-request.jpg) + +- Click on **Sign** the transaction. + +- After the transaction is successfully processed, your first task will be createdโœ…. + + ![create-task-success](/img/create-task-success.jpg) + +- Your task item looks like below with the following details - **`Name`** , **`Description`** , **`Last Updated Time`** , **`Create Date and Time`**, + + ![todo-item.jpg](/img/todo-item.jpg) + +- You will be able to perform these following action for a selected task - **`Edit`** , **`Complete`** , and **`Remove`** . + +As we have **Created a Task** successfully, let's update the task details. + +--- + +**Edit the Task** + +- Click on the **"Edit"** button to edit the task. + + ![update-task](/img/update-task.png) + +- You will see the pop-up modal with form to edit the task. Edit the necessary fields according to your need. + + ![edit-task-form](/img/edit-task-form.jpg) + +- Click on **Update Task** Button. + +- Now, You will recieve a transaction request on your portkey wallet to **Sign** the transaction. + + ![update-task-sign-request](/img/update-task-request.jpg) + +- Click on **Sign** the transaction. + +- After the transaction is successfully processed, your task details will be Updatedโœ…. + + ![update-task-success](/img/update-task-success.jpg) + +As we have **Edited a Task** successfully. Let's move that task to completed state. + +--- + +**Complete the Task** + +- Click on the **"Complete"** button to move the task to `Completed` status. + + ![complete-task-button](/img/complete-task-button.jpg) + +- Now, You will recieve a transaction request on your portkey wallet to **Sign** the transaction. + + ![complete-task-sign-request](/img/complete-task-request.jpg) + +- Click on **Sign** the transaction. + +- After the transaction is successfully processed, your task will be moved to the completed tabโœ…. + + ![complete-task-success](/img/complete-task-success.jpg) + +As we have performed **Complete Task** successfully. Let's remove the completed task. + +--- + +**Remove the Task** + +- Click on **"Remove"** button to remove the task. + + ![remove-task-button](/img/delete-task-button.jpg) + +- Now, You will recieve a transaction request on your portkey wallet to **Sign** the transaction. + + ![remove-task-sign-request](/img/delete-task-request.jpg) + +- Click on **Sign** the transaction. + +- After the transaction is successfully processed, your task will be moved to the removed tabโœ…. + + ![remove-task-success](/img/delete-task-success.jpg) + +:::success +๐ŸŽ‰ Congratulations Learners! You have successfully built your ToDo dApp. +::: + + +## ๐ŸŽฏ Conclusion + +๐ŸŽ‰ Congratulations on successfully completing the **ToDo dApp** tutorial! ๐ŸŽ‰ You've taken important steps in setting up your development environment, developing and deploying a smart contract on ToDo dApp, and building a fully functional ToDo decentralized application on the aelf blockchain. ๐ŸŒŸ + +**๐Ÿ“š What You've Learned** + +Throughout this tutorial, you've mastered: + + - **๐Ÿ› ๏ธ Setting Up Your Development Environment:** You prepared your workspace by installing and configuring all the necessary tools to kickstart your smart contract project. + + - **๐Ÿ’ป Developing Your Smart Contract:** You created the foundation of your ToDo dApp by writing and building the smart contract that manages tasks, from creation to deletion. + + - **๐Ÿš€ Deploying the Smart Contract:** You deployed your smart contract to the aelf blockchain, enabling its functionalities to be used in a live environment. + + - **๐Ÿ”ง Interacting with Your Deployed Smart Contract:** You connected your frontend to the blockchain, integrated Portkey for wallet connectivity, and implemented functions to manage tasks such as creating, updating, and deleting directly through the dApp. + +**๐Ÿ” Final Output** + +By now, you should have: + + - ๐Ÿ“œ A deployed smart contract that powers your ToDo dApp, managing tasks with functionalities for creation, updating, status management, and deletion. + + - ๐Ÿ’ป A fully operational ToDo dApp, allowing users to interact with the smart contract to efficiently manage their tasks. + +**โžก๏ธ What's Next?** + +With the basics under your belt, consider exploring more advanced topics: + + - **๐Ÿ“ˆ Enhancing Smart Contract Logic:** Introduce more complex features to your ToDo dApp, such as prioritization, deadlines, or collaboration tools. + + - **๐Ÿ”’ Improving Security:** Ensure your dApp and smart contract are secure by implementing best practices and security measures. + + - **๐ŸŒ Exploring Cross-Chain Features:** Expand your dAppโ€™s capabilities by exploring aelfโ€™s cross-chain interoperability, enabling interaction with other blockchains. + +The possibilities with blockchain technology and decentralized applications are endless. You're now well-equipped to take your ToDo dApp to the next level. Keep building, innovating, and exploring with aelf. ๐Ÿš€ + +Happy coding and expanding your **ToDo dApp! ๐Ÿ˜Š** diff --git a/docs/resources/aetherLink/index.md b/docs/resources/aetherLink/index.md index 9eed21c2..90cf2218 100644 --- a/docs/resources/aetherLink/index.md +++ b/docs/resources/aetherLink/index.md @@ -4,163 +4,231 @@ title: AetherLink description: Transfer tamper-proof data from off-chain to on-chain --- -# AetherLink +## 1. Background -## 1. Introduction +There is a growing need for decentralized applications (dApps) to access data feeds that are frequently updated, reliable, and secure. AetherLink Oracles is a leading oracle provider directly fulfilling this need. -A web3 oracle is essential for decentralized networks, enabling smart contracts to access real-world data like weather -updates or market prices. However, the security of oracles is a concern, as malicious actions can compromise contract -execution. Choosing reliable oracle services is crucial in web3 development. Moreover, oracles not only link blockchains -to real-world data but also enable the retrieval of information from other blockchain networks. +## 2. Target -![oracle](/img/oracle.png) +For developers of the AELF ecosystem, this article will provide a complete guide to accessing the AetherLink **VRF** and **Datafeeds** contracts. -## 2. aelf Oracle Services - AetherLink +## 3. Operational -Among the various products in aelf, some products may require information from other chains. For example, the [eBridge](https://ebridge.exchange/) -product needs to access lock-up information of NFTs on the Ethereum chain. This necessitates the use of oracle services -for data interchange. To expand the aelf ecosystem and enhance user services, it is essential to establish the -foundational services for oracle mechanisms. +### 3.1 Preparation -In this context, AetherLink, aelf's proprietary oracle service, has been developed. AetherLink primarily focuses on -providing Verified Random Function (VRF) capabilities and Data Feeds functionality. +#### 3.1.1 Import proto -## 3. What can AetherLink do? +First, you need to import oracle-related proto files into your contract csproj file. -### A. Random number generator +```xml + + + Protobuf\Proto\oracle_common_message.proto + + + + + Protobuf\Proto\oracle_contract.proto + + + + + Protobuf\Proto\request_interface.proto + + +``` -Generating random numbers directly on the blockchain is a complex and challenging problem. Due to the deterministic -nature of blockchain, the results of smart contract executions must be consistent across all nodes in the network. -Therefore, random number generation in smart contracts needs to be verifiable and replicable, ensuring that each node -obtains the same result when executing the same smart contract. +You can find the latest proto files through the following link: -However, traditional pseudo-random number generation methods, such as using block hashes, face challenges in this -environment. If block hashes are used, each node in the smart contract would generate the same random number using -identical inputs, making it impossible to achieve true randomness. +- https://github.com/AetherLinkProject/aetherLink-contracts/blob/master/protobuf/oracle_common_message.proto +- https://github.com/AetherLinkProject/aetherLink-contracts/blob/master/protobuf/oracle_contract.proto +- https://github.com/AetherLinkProject/aetherLink-contracts/blob/master/protobuf/request_interface.proto -AetherLink VRF introduces the concept of Verifiable Random Function (VRF), employing algorithms designed by -cryptographic experts to ensure that the generated random numbers on-chain are verifiable and unpredictable. AetherLink -VRF provides a secure, decentralized, and verifiable mechanism for generating random numbers in smart contracts, -delivering high-quality randomness without compromising security and usability. +#### 3.1.2 Protobuf file -The key advantages of using AetherLink VRF include: +Then you also need to introduce request_interface.proto in the proto file to inherit the oracle callback function to receive the oracle report -#### 1. Verifiability: +```protobuf +import "aelf/core.proto"; +import "aelf/options.proto"; +import "request_interface.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/timestamp.proto"; -Cryptographic proofs are used to demonstrate the authenticity of the random number generation process, allowing smart -contracts and users to verify the legitimacy of the generated random numbers. +service { + ... -#### 2. Unpredictability: + option (aelf.base) = "request_interface.proto"; -Utilizing VRF algorithms ensures that random numbers generated by AetherLink VRF are unpredictable, even under identical -input conditions. + ... +``` -#### 3. Decentralization: +#### 3.1.3 Set Oracle Address -AetherLink VRF operates across multiple nodes, enhancing the decentralization of random number generation and mitigating -risks associated with a single point of control. +Set the Oracle Contract Address in your contract state according to your operating environment. -```csharp title="Random number generator code segment" -{ - var random = State.ConsensusContract.GetRandomHash.Call(new Int64Value - { - Value = specificData.BlockNumber - }); +- MainNet **AELF** Address : `BGhrBNTPcLccaxPv6hHJrn4CHHzeMovTsrkhFse5o2nwfvQyG` +- MainNet **tDVV** Address : `BGhrBNTPcLccaxPv6hHJrn4CHHzeMovTsrkhFse5o2nwfvQyG` +- TestNet **AELF** Address : `21Fh7yog1B741yioZhNAFbs3byJ97jvBmbGAPPZKZpHHog5aEg` +- TestNet **tDVW** Address : `21Fh7yog1B741yioZhNAFbs3byJ97jvBmbGAPPZKZpHHog5aEg` - var alpha = HashHelper.ConcatAndCompute(random, specificData.PreSeed); +#### 3.1.4 Apply subscription ID - Context.ECVrfVerify(publicKey, alpha.ToByteArray(), report.Result.ToByteArray(), out var beta); - Assert(beta != null && beta.Length > 0, "Vrf verification fail."); +Before initiating a task to the oracle, you need to contact AetherLink to apply for a subscription number for your task. Later, you can manage the service fee based on this subscription id.Remember that you only have the right to use your own `subscription id`. - var randomHash = Hash.LoadFromByteArray(beta); +:::note - for (var i = 0; i < specificData.NumWords; i++) - { - response.Data.Add(HashHelper.ConcatAndCompute(randomHash, HashHelper.ComputeFrom(i))); - } +If you have prepared all the above work, we will explain the VRF and Datafeeds scenarios separately. You need to choose the appropriate contract as the entry point for your development scenario - return response; -} -``` +Currently, we provide two oracle capabilities: + +- VRF: VRF provides cryptographically secure randomness for your blockchain-based applications. +- Datafeeds: Provide data sources for Web2 asset prices for contracts. -The process involves obtaining a random hash from the consensus contract, verifying it through ECVRF with a public key -and an oracle-reported result, and then creating a set of random words based on the verified data. +::: -### B. DataFeeds +### 3.2 DataFeeds -The AetherLink Data Feeds Coordinator provides a quick and reliable connection for smart contracts to real-world data, -encompassing data types like asset prices, reserve balances, NFT floor prices, and L2 sequencer health. +First, you need to define how to initiate a Datafeeds oracle request in the contract. -Data feeds from the Coordinator include Price Feeds, Proof of Reserve Feeds, NFT Floor Price Feeds, Rate and Volatility -Feeds, and L2 Sequencer Uptime Feeds. For example, Price Feeds are crucial for real-time actions in applications like -decentralized finance (DeFi) platforms. These feeds aggregate data from multiple sources, ensuring reliability through -the Decentralized Data Model and Offchain Reporting. +```csharp +State.OracleContract.SendRequest.Send(new SendRequestInput +{ + SubscriptionId = SubscriptionId, + RequestTypeIndex = 1, + SpecificData = specificData, + TraceId = XXXXX // HASH +}); +``` + +- `OracleContract`: This is the target contract address, the oracle contract. +- `SendRequest`: This is the method name for sending the Datafeeds Request to the target contract. +- `SendRequestInput`: This is the input parameter of the method for sending transactions to the target contract + +| Param Name | Explanation | | +| ---------------- | ------------------------------------------------------------------ | --------------------------- | +| SubscriptionId | manage the service fee based on this subscription id | int32 | +| RequestTypeIndex | Task Type | int32, 1=Datafeeds \| 2=VRF | +| SpecificData | Detailed description of Datafeeds tasks | ByteString | +| TraceId | This ID can be used as a unique index to manage your oracle tasks. | Aelf.Hash | -Components of a data feed involve the Consumer (onchain or offchain applications using Data Feeds), Coordinator contract -(onchain coordinator pointing to the oracle), and Oracle contract (receiving periodic data updates from the oracle -network and storing aggregated data onchain). +#### 3.2.1 How to generate DataFeeds SpecificData -Different data feeds cater to specific use cases, such as Proof of Reserve Feeds indicating the status of reserves, NFT -Floor Price Feeds providing the lowest NFT prices, and Rate and Volatility Feeds offering interest rate curve data. L2 -sequencer uptime feeds track the last known status of the sequencer on Layer 2 networks. +Here we take the example of collecting ELF-USTD currency price pairs every 10 minutes. Note the red part: + +```json +{ + "Cron": "0 */10 * * * ?", + "DataFeedsJobSpec": { + "Type": "PriceFeeds", + "CurrencyPair": "ELF/USDT" + } +} +``` -## 4. AetherLink Contracts +After determining the task description, you need to convert it into a bystring type and then put it into the input parameter of the oracle request. -To achieve these functionalities, aelf has deployed 3 main contracts. Their names and their functionalities are: +```csharp +var jobSpec = "{\"Cron\": \"0 */10 * * * ?\",\"DataFeedsJobSpec\": {\"Type\": \"PriceFeeds\",\"CurrencyPair\": \"ELF/USDT\"}}"; -| Contract Type | Functions and Responsibilities | -| ------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| Consumer Contract | 1. Task initiation and receipt of results
2. Result inquiry | -| Coordinate Contract | 1. Task management
2. VRF Proof verification and random number generation
3. Threshold signature verification | -| Oracle Contract | 1. Node management
2. Task event publication | +var specificData = new AetherLink.Contracts.DataFeeds.Coordinator.SpecificData +{ + Data = ByteString.CopyFromUtf8(jobSpec), + DataVersion = 0 +}.ToByteString() -### A. Consumer Contract +var sendRequestInput = new SendRequestInput +{ + SubscriptionId = 1, + RequestTypeIndex = 1, + SpecificData = specificData, + TraceId = HhfWg...Y9kao31 // HASH +} +``` -A contract deployed by the user, for which the official interface proto file is provided by the platform for task -initiation and result retrieval. Users are required to reference and implement this interface. The main functionalities -include: +#### 3.2.2 How to handle oracle pricefeeds callbacks -#### a. Task initiation: +Here you need to override the HandleOracleFulfillment method -Initiated by the user, subsequently invoking the Oracle contract to carry out subsequent operations. +```csharp +public override Empty HandleOracleFulfillment(HandleOracleFulfillmentInput input) +{ + ... -#### b. Result retrieval: + var priceList = LongList.Parser.ParseFrom(input.Response); -Callback function executed by the Oracle contract, writing the results back to the user contract. Users are responsible -for implementing the logic for data storage. + var longList = new LongList { Data = { priceList.Data } }; -### B. Coordinator Contract + var sortedList = longList.Data.ToList().OrderBy(l => l).ToList(); -A contract provided by the platform. Based on the current products, PriceFeeds and VRF, two corresponding Coordinator -contracts need to be deployed. The main functionalities include: + var round = State.LatestRound.Value.Add(1); -#### a. Task management: + var newPriceRoundData = new PriceRoundData + { + Price = sortedList[sortedList.Count / 2], + RoundId = round, + UpdatedAt = Context.CurrentBlockTime + }; + State.LatestPriceRoundData.Value = newPriceRoundData; + State.PriceRoundData[round] = newPriceRoundData; + State.LatestRound.Value = round; +} +``` -Generates a unique 1D for tasks along with task details, storing them in the contract. +The above code will parse the oracle callback and record each result in the contract, providing the latest and historical currency price query capabilities. -#### b. Threshold signature verification (for non-algorithmic verification thresholds) / VRF Proof verification: +### 3.3 VRF -After nodes submit task results, if it's a PriceFeeds-type task, the Coordinator contract is responsible for verifying -the submitted signatures through threshold signature verification. If it's a VRF-type task, the Coordinator contract -needs to reconstruct the random hash from the submitted proof. +First, you need to define how to initiate a VRF oracle request in the contract. -### C. Oracle Contract +```csharp +State.OracleContract.SendRequest.Send(new SendRequestInput +{ + SubscriptionId = SubscriptionId, + RequestTypeIndex = 2, + SpecificData = specificData, + TraceId = XXXXX // HASH +}); +``` -The official contract provided decouples Oracle nodes and Consumer contracts from business logic. It has three main -functionalities: +- `OracleContract`: This is the target contract address, the oracle contract. +- `SendRequest`: This is the method name for sending the VRF Request to the target contract. +- `SendRequestInput`: This is the input parameter of the method for sending transactions to the target contract -#### a. Subscription Feature: +| Param Name | Explanation | | +| ---------------- | ------------------------------------------------------------------ | --------------------------- | +| SubscriptionId | manage the service fee based on this subscription id | int32 | +| RequestTypeIndex | Task Type | int32, 1=Datafeeds \| 2=VRF | +| SpecificData | Detailed description of Datafeeds tasks | ByteString | +| TraceId | This ID can be used as a unique index to manage your oracle tasks. | Aelf.Hash | -Provides subscription management functionality for user contracts, enabling task initiation through subscriptions. +#### 3.3.1 How to generate VRF SpecificData -#### b. Node Management Feature: +First, you need to specify an oracle node to perform your random number generation task, -Implements the registration and role assignment of Oracle nodes, with configurable parameters for threshold signature. +```csharp +var keyHashs = State.OracleContract.GetProvingKeyHashes.Call(new Empty()); +var keyHash = keyHashs[0] +``` -#### c. Event-Driven Feature: +Then bind the oracle node Keyhash in your VRF task and specify the number of random numbers to be generated. -Oracle nodes need to listen to events from this contract, triggering corresponding operations when events are emitted. +```csharp +var specificData = new AetherLink.Contracts.VRF.Coordinator.SpecificData +{ + KeyHash = keyHash, + NumWords = 3, + RequestConfirmations = 1 +}.ToByteString() +``` -The whole project structure: +#### 3.3.2 How to handle oracle pricefeeds callbacks -![structure](/img/AetherLinkStructure.png) \ No newline at end of file +```csharp +public override Empty HandleOracleFulfillment(HandleOracleFulfillmentInput input) +{ + var randomHashList = HashList.Parser.ParseFrom(input.Response); + State.RandomHashes[input.RequestId] = randomHashList; +} +``` diff --git a/package-lock.json b/package-lock.json index a1e1a0df..1cfcb503 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@docusaurus/core": "3.4.0", "@docusaurus/preset-classic": "3.4.0", "@mdx-js/react": "^3.0.0", + "@sinm/react-file-tree": "^1.1.1", "chaingpt-component": "^0.2.0-beta.3", "clsx": "^2.0.0", "docusaurus-theme-search-typesense": "^0.20.0-0", @@ -2992,6 +2993,16 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@sinm/react-file-tree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@sinm/react-file-tree/-/react-file-tree-1.1.1.tgz", + "integrity": "sha512-XlHLLNV18zWx7mYEH5gtlBb7jYFX7GaBA5jmhsG9YXhgwyyK33JgymrbU+IHDDBakAoZ+xISLKzu2ldzKbA/wg==", + "license": "MIT", + "dependencies": { + "@types/react-virtualized": "^9.21.15", + "react-virtualized": "^9.22.3" + } + }, "node_modules/@slorber/remark-comment": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", @@ -3524,6 +3535,16 @@ "@types/react-router": "*" } }, + "node_modules/@types/react-virtualized": { + "version": "9.21.30", + "resolved": "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.30.tgz", + "integrity": "sha512-4l2TFLQ8BCjNDQlvH85tU6gctuZoEdgYzENQyZHpgTHU7hoLzYgPSOALMAeA58LOWua8AzC6wBivPj1lfl6JgQ==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/react": "*" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -5655,6 +5676,16 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -12243,6 +12274,12 @@ "react": "^16.13.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, "node_modules/react-loadable": { "name": "@docusaurus/react-loadable", "version": "6.0.0", @@ -12318,6 +12355,33 @@ "react": ">=15" } }, + "node_modules/react-virtualized": { + "version": "9.22.5", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.5.tgz", + "integrity": "sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", + "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-virtualized/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", diff --git a/package.json b/package.json index b4abd612..9045a2dd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@docusaurus/core": "3.4.0", "@docusaurus/preset-classic": "3.4.0", "@mdx-js/react": "^3.0.0", + "@sinm/react-file-tree": "^1.1.1", "chaingpt-component": "^0.2.0-beta.3", "clsx": "^2.0.0", "docusaurus-theme-search-typesense": "^0.20.0-0", diff --git a/src/theme/FileTree/index.tsx b/src/theme/FileTree/index.tsx new file mode 100644 index 00000000..977a3637 --- /dev/null +++ b/src/theme/FileTree/index.tsx @@ -0,0 +1,13 @@ +import FileItemWithFileIcon from "@sinm/react-file-tree/lib/FileItemWithFileIcon"; +import "@sinm/react-file-tree/icons.css"; +import "@sinm/react-file-tree/styles.css"; +import { FileTree as FileTreeLib, TreeNode } from "@sinm/react-file-tree"; +const itemRenderer = (treeNode: TreeNode) => ( + +); + +const FileTree = ({ tree }: { tree?: TreeNode }) => { + return ; +}; + +export default FileTree; diff --git a/src/theme/MDXComponents.tsx b/src/theme/MDXComponents.tsx index 9c1da884..d1f27ec8 100644 --- a/src/theme/MDXComponents.tsx +++ b/src/theme/MDXComponents.tsx @@ -4,6 +4,7 @@ import MDXComponents from "@theme-original/MDXComponents"; import DocCardListLinks from "@site/src/theme/DocCardListLinks"; import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; +import FileTree from "./FileTree"; export default { // Re-use the default mapping @@ -13,4 +14,5 @@ export default { DocCardListLinks, Tabs, TabItem, + FileTree, }; diff --git a/static/img/complete-task-button.jpg b/static/img/complete-task-button.jpg new file mode 100644 index 00000000..ee03085b Binary files /dev/null and b/static/img/complete-task-button.jpg differ diff --git a/static/img/complete-task-request.jpg b/static/img/complete-task-request.jpg new file mode 100644 index 00000000..cbfda02f Binary files /dev/null and b/static/img/complete-task-request.jpg differ diff --git a/static/img/complete-task-success.jpg b/static/img/complete-task-success.jpg new file mode 100644 index 00000000..7e95efc0 Binary files /dev/null and b/static/img/complete-task-success.jpg differ diff --git a/static/img/create-task-form.png b/static/img/create-task-form.png new file mode 100644 index 00000000..fb3fa152 Binary files /dev/null and b/static/img/create-task-form.png differ diff --git a/static/img/create-task-sign-request.jpg b/static/img/create-task-sign-request.jpg new file mode 100644 index 00000000..bd3b49f1 Binary files /dev/null and b/static/img/create-task-sign-request.jpg differ diff --git a/static/img/create-task-success.jpg b/static/img/create-task-success.jpg new file mode 100644 index 00000000..5349d66b Binary files /dev/null and b/static/img/create-task-success.jpg differ diff --git a/static/img/create-task.png b/static/img/create-task.png new file mode 100644 index 00000000..08ee8359 Binary files /dev/null and b/static/img/create-task.png differ diff --git a/static/img/delete-task-button.jpg b/static/img/delete-task-button.jpg new file mode 100644 index 00000000..08804eb6 Binary files /dev/null and b/static/img/delete-task-button.jpg differ diff --git a/static/img/delete-task-request.jpg b/static/img/delete-task-request.jpg new file mode 100644 index 00000000..190983d7 Binary files /dev/null and b/static/img/delete-task-request.jpg differ diff --git a/static/img/delete-task-success.jpg b/static/img/delete-task-success.jpg new file mode 100644 index 00000000..1c5ae8c9 Binary files /dev/null and b/static/img/delete-task-success.jpg differ diff --git a/static/img/edit-task-form.jpg b/static/img/edit-task-form.jpg new file mode 100644 index 00000000..01635550 Binary files /dev/null and b/static/img/edit-task-form.jpg differ diff --git a/static/img/make-move-sign-request.png b/static/img/make-move-sign-request.png new file mode 100644 index 00000000..5e68c76e Binary files /dev/null and b/static/img/make-move-sign-request.png differ diff --git a/static/img/make-move.png b/static/img/make-move.png new file mode 100644 index 00000000..903db387 Binary files /dev/null and b/static/img/make-move.png differ diff --git a/static/img/nft-fe-directory.jpg b/static/img/nft-fe-directory.jpg new file mode 100644 index 00000000..e44cd802 Binary files /dev/null and b/static/img/nft-fe-directory.jpg differ diff --git a/static/img/second-move-request.png b/static/img/second-move-request.png new file mode 100644 index 00000000..b367d70b Binary files /dev/null and b/static/img/second-move-request.png differ diff --git a/static/img/second-move.png b/static/img/second-move.png new file mode 100644 index 00000000..fd35ecb2 Binary files /dev/null and b/static/img/second-move.png differ diff --git a/static/img/start-again.png b/static/img/start-again.png new file mode 100644 index 00000000..82267bcb Binary files /dev/null and b/static/img/start-again.png differ diff --git a/static/img/start-game-button.png b/static/img/start-game-button.png new file mode 100644 index 00000000..02ef2f60 Binary files /dev/null and b/static/img/start-game-button.png differ diff --git a/static/img/start-game-sign-request.png b/static/img/start-game-sign-request.png new file mode 100644 index 00000000..da7c4851 Binary files /dev/null and b/static/img/start-game-sign-request.png differ diff --git a/static/img/start-game-success.png b/static/img/start-game-success.png new file mode 100644 index 00000000..20874321 Binary files /dev/null and b/static/img/start-game-success.png differ diff --git a/static/img/tic-tac-toe-connect-wallet.png b/static/img/tic-tac-toe-connect-wallet.png new file mode 100644 index 00000000..e490c7da Binary files /dev/null and b/static/img/tic-tac-toe-connect-wallet.png differ diff --git a/static/img/tic-tac-toe-home-page.png b/static/img/tic-tac-toe-home-page.png new file mode 100644 index 00000000..5bf8fde5 Binary files /dev/null and b/static/img/tic-tac-toe-home-page.png differ diff --git a/static/img/tic-tac-toe-wallet-connect-success.png b/static/img/tic-tac-toe-wallet-connect-success.png new file mode 100644 index 00000000..12d6543b Binary files /dev/null and b/static/img/tic-tac-toe-wallet-connect-success.png differ diff --git a/static/img/todo-connect-wallet.jpg b/static/img/todo-connect-wallet.jpg new file mode 100644 index 00000000..034d35e4 Binary files /dev/null and b/static/img/todo-connect-wallet.jpg differ diff --git a/static/img/todo-homepage.jpg b/static/img/todo-homepage.jpg new file mode 100644 index 00000000..d97ccbb8 Binary files /dev/null and b/static/img/todo-homepage.jpg differ diff --git a/static/img/todo-item.jpg b/static/img/todo-item.jpg new file mode 100644 index 00000000..e3b5b95d Binary files /dev/null and b/static/img/todo-item.jpg differ diff --git a/static/img/todo-wallet-connect-success.jpg b/static/img/todo-wallet-connect-success.jpg new file mode 100644 index 00000000..12f8263a Binary files /dev/null and b/static/img/todo-wallet-connect-success.jpg differ diff --git a/static/img/update-task-request.jpg b/static/img/update-task-request.jpg new file mode 100644 index 00000000..10913db5 Binary files /dev/null and b/static/img/update-task-request.jpg differ diff --git a/static/img/update-task-success.jpg b/static/img/update-task-success.jpg new file mode 100644 index 00000000..e6e8bad7 Binary files /dev/null and b/static/img/update-task-success.jpg differ diff --git a/static/img/update-task.png b/static/img/update-task.png new file mode 100644 index 00000000..944e1267 Binary files /dev/null and b/static/img/update-task.png differ diff --git a/static/img/welcome-screen.png b/static/img/welcome-screen.png new file mode 100644 index 00000000..af93c8d4 Binary files /dev/null and b/static/img/welcome-screen.png differ diff --git a/static/img/winner-view.png b/static/img/winner-view.png new file mode 100644 index 00000000..d3328e71 Binary files /dev/null and b/static/img/winner-view.png differ diff --git a/yarn.lock b/yarn.lock index d8340d4b..e10696f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1196,7 +1196,7 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.22.6", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.22.6", "@babel/runtime@^7.23.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.24.7" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -1878,6 +1878,14 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz" integrity sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g== +"@sinm/react-file-tree@^1.1.1": + version "1.1.1" + resolved "https://registry.npmjs.org/@sinm/react-file-tree/-/react-file-tree-1.1.1.tgz" + integrity sha512-XlHLLNV18zWx7mYEH5gtlBb7jYFX7GaBA5jmhsG9YXhgwyyK33JgymrbU+IHDDBakAoZ+xISLKzu2ldzKbA/wg== + dependencies: + "@types/react-virtualized" "^9.21.15" + react-virtualized "^9.22.3" + "@slorber/remark-comment@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz" @@ -2241,6 +2249,14 @@ "@types/history" "^4.7.11" "@types/react" "*" +"@types/react-virtualized@^9.21.15": + version "9.21.30" + resolved "https://registry.npmjs.org/@types/react-virtualized/-/react-virtualized-9.21.30.tgz" + integrity sha512-4l2TFLQ8BCjNDQlvH85tU6gctuZoEdgYzENQyZHpgTHU7hoLzYgPSOALMAeA58LOWua8AzC6wBivPj1lfl6JgQ== + dependencies: + "@types/prop-types" "*" + "@types/react" "*" + "@types/react@*", "@types/react@>= 16.8.0 < 19.0.0", "@types/react@>=16": version "18.3.3" resolved "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz" @@ -3062,6 +3078,11 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" +clsx@^1.0.4: + version "1.2.1" + resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" @@ -3666,6 +3687,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.1.3: + version "5.2.1" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" @@ -7102,7 +7131,7 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@*, "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.3.1, "react-dom@>= 16.8.0 < 19.0.0": +react-dom@*, "react-dom@^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", "react-dom@^16.6.0 || ^17.0.0 || ^18.0.0", react-dom@^18.0.0, react-dom@^18.3.1, "react-dom@>= 16.8.0 < 19.0.0": version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -7141,6 +7170,11 @@ react-json-view-lite@^1.2.0: resolved "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.4.0.tgz" integrity sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-loadable-ssr-addon-v5-slorber@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz" @@ -7190,7 +7224,19 @@ react-router@^5.3.4, react-router@>=5, react-router@5.3.4: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react@*, "react@^16.13.1 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.3.1, "react@>= 16.8.0 < 19.0.0", react@>=15, react@>=16, react@>=16.0.0: +react-virtualized@^9.22.3: + version "9.22.5" + resolved "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.5.tgz" + integrity sha512-YqQMRzlVANBv1L/7r63OHa2b0ZsAaDp1UhVNEdUaXI8A5u6hTpA5NYtUueLH2rFuY/27mTGIBl7ZhqFKzw18YQ== + dependencies: + "@babel/runtime" "^7.7.2" + clsx "^1.0.4" + dom-helpers "^5.1.3" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.4" + +react@*, "react@^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0", "react@^16.13.1 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || ^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.3.1, "react@>= 16.8.0 < 19.0.0", react@>=15, react@>=16, react@>=16.0.0: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==