├── .gitattributes ├── .gitignore ├── .nvmrc ├── .prettierrc.json ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── approve.png ├── balance.png ├── candid_verify.png ├── confirmation.png ├── connect_wallet.png ├── contract_abi.png ├── deposit_input.png ├── deposit_metamask.png ├── deposit_principal.png ├── deposit_result.png ├── final.mp4 ├── final.png ├── get_items.png ├── json_to_serde.png ├── logs.png ├── mainnet_deposit.png ├── mainnet_terminal.png ├── receipt.png ├── starting.png ├── terminal.png ├── transfer.png ├── verified_onchain.png └── withdraw.png ├── backend └── payment │ ├── Cargo.toml │ ├── payment.did │ └── src │ └── lib.rs ├── dfx.json ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── predeploy.sh ├── public └── icp-logo.svg ├── src ├── components │ ├── Confirmation.tsx │ ├── Item.tsx │ ├── Shop.tsx │ ├── VerifyTransaction.tsx │ └── Wallet.tsx ├── declarations │ ├── evm_rpc │ │ ├── evm_rpc.did │ │ ├── evm_rpc.did.d.ts │ │ ├── evm_rpc.did.js │ │ ├── index.d.ts │ │ └── index.js │ ├── frontend │ │ ├── frontend.did │ │ ├── frontend.did.d.ts │ │ ├── frontend.did.js │ │ ├── index.d.ts │ │ └── index.js │ └── payment │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── payment.did │ │ ├── payment.did.d.ts │ │ └── payment.did.js ├── pages │ ├── _app.tsx │ ├── history.tsx │ └── index.tsx ├── service │ ├── abi.json │ ├── config.ts │ └── payment.ts └── styles │ ├── History.module.css │ ├── Home.module.css │ ├── Item.module.css │ ├── Shop.module.css │ └── global.css ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.local 3 | # Various IDEs and Editors 4 | .vscode/ 5 | .idea/ 6 | **/*~ 7 | 8 | # Mac OSX temporary files 9 | .DS_Store 10 | **/.DS_Store 11 | 12 | # dfx temporary files 13 | .dfx/ 14 | canister_ids.json 15 | 16 | # frontend code 17 | node_modules/ 18 | out/ 19 | 20 | # Next.js 21 | .next/ 22 | 23 | # Rust build artifacts 24 | target/ 25 | Cargo.lock 26 | 27 | # Yarn 28 | .yarn/* 29 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18.16 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "trailingComma": "none", 4 | "semi": false, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["backend/payment"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Henry Chan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a Cross-Chain ETH Payment and E-Commerce Platform on the Internet Computer: A Step-by-Step Tutorial 2 | 3 | ## Introduction 4 | 5 | This comprehensive tutorial guides you through the process of building a decentralized e-commerce platform on the Internet Computer that can accept Ethereum (ETH) payments, handle withdrawals, and manage a digital storefront. Starting from a basic template, we'll incrementally add features to create a robust, cross-chain solution. 6 | 7 | ## Why I Made This Tutorial? 8 | 9 | This tutorial is a hands-on guide designed for developers new to the Internet Computer blockchain. I've created a set of libraries to make development easier and more enjoyable, and this tutorial walks you through the process of building a decentralized ETH payment system using these libraries. 10 | 11 | We'll explore key features like HTTP outcalls and stable memory, providing a solid foundation for anyone looking to bring their dream project to life on the Internet Computer. Although this tutorial uses my specific set of methods and libraries, there are many other tools and techniques you could use. 12 | 13 | For example, you could integrate the [`ic-eth-rpc`](https://github.com/internet-computer-protocol/ic-eth-rpc) package for Ethereum RPC calls or accept ckETH as a payment method. The main goal here is to demonstrate the Internet Computer's flexibility and ease of use, especially when it comes to confirming transactions on-chain. 14 | 15 | ## Objective 16 | 17 | The goal of this tutorial is to create a fully functional decentralized e-commerce platform that: 18 | 19 | - Accepts ETH as payment for digital items 20 | - Integrates with Ethereum smart contracts for payment processing. 21 | - Verifies transactions on-chain for added security 22 | - Allows for the withdrawal of ETH to an Ethereum address 23 | - Utilizes stable memory to keep track of transactions and items. 24 | - Manages a digital storefront with items for sale 25 | - Implements access control to secure sensitive operations 26 | 27 | ## Final Product(YouTube Video) 28 | 29 | [![Watch the video](https://img.youtube.com/vi/lPNOqKwPRlE/maxresdefault.jpg)](https://youtu.be/lPNOqKwPRlE) 30 | 31 | ## Prerequisites 32 | 33 | - [DFINITY Canister SDK](https://sdk.dfinity.org/docs/quickstart/local-quickstart.html) 34 | - [Node.js](https://nodejs.org/en/download/) 35 | - [Rust](https://www.rust-lang.org/tools/install) 36 | - Basic understanding of Ethereum and smart contracts 37 | 38 | ## Step 1: Clone the Starter Repository and Run the Project Locally 39 | 40 | We'll begin by cloning the [`ic-rust-nextjs`](https://github.com/b3hr4d/ic-rust-nextjs) repository, which serves as our starter template. 41 | 42 | ### What's Inside the Starter Repository? 43 | 44 | - [`README.md`](https://github.com/b3hr4d/ic-rust-nextjs/blob/main/README.md): Provides an overview and setup instructions. 45 | - [`Cargo.toml`](https://github.com/b3hr4d/ic-rust-nextjs/blob/main/Cargo.toml): The manifest file for the Rust workspace. 46 | - [`dfx.json`](https://github.com/b3hr4d/ic-rust-nextjs/blob/main/dfx.json): Configuration file for the DFINITY Canister SDK. 47 | - [`backend/hello/src/lib.rs`](https://github.com/b3hr4d/ic-rust-nextjs/blob/main/backend/hello/src/lib.rs): The Rust code for the backend logic. 48 | - [`src/pages/index.tsx`](https://github.com/b3hr4d/ic-rust-nextjs/blob/main/src/pages/index.tsx): The main page of the Next.js app. 49 | - [`src/service/hello.ts`](https://github.com/b3hr4d/ic-rust-nextjs/blob/main/src/service/hello.ts): Service file to interact with the Rust backend. 50 | 51 | ### Cloning the Repository 52 | 53 | To clone the repository, open your terminal and run: 54 | 55 | ```bash 56 | git clone https://github.com/b3hr4d/ic-rust-nextjs.git 57 | ``` 58 | 59 | ### Running the Project Locally 60 | 61 | After cloning the repository, the next step is to run the project locally to ensure everything is set up correctly. Follow the commands below based on your package manager (Yarn or npm). 62 | 63 | ### Installing Dependencies 64 | 65 | First, let's install all the required dependencies: 66 | 67 | ```bash 68 | yarn install:all 69 | # or 70 | npm run install:all 71 | ``` 72 | 73 | ### Running Local Internet Computer 74 | 75 | To start the local Internet Computer environment, run: 76 | 77 | ```bash 78 | yarn dfx:start 79 | # or 80 | npm run dfx:start 81 | ``` 82 | 83 | ### Deploying to the Local Internet Computer 84 | 85 | Deploy your the backend canister to the local Internet Computer by running: 86 | 87 | ```bash 88 | yarn deploy payment 89 | # or 90 | npm run deploy payment 91 | ``` 92 | 93 | ### Running the Next.js App 94 | 95 | Finally, to run the Next.js(frontend) app, execute: 96 | 97 | ```bash 98 | yarn dev 99 | # or 100 | npm run dev 101 | ``` 102 | 103 | Open your browser and navigate to [http://localhost:3000](http://localhost:3000) to see your app running. 104 | 105 | ![Alt text](assets/starting.png) 106 | 107 | ## Step 1.1: Rename the Canister ID 108 | 109 | In this step, we'll rename the canister ID to make it easier to reference in the code. 110 | 111 | ### Renaming the Canister ID 112 | 113 | To rename the default project name and canister name from "hello" to "payment", follow these steps: 114 | 115 | 1. Open the `Cargo.toml` file in the "backend/hello" directory. 116 | 117 | 2. Find the line that says `name = "hello"` and change it to `name = "payment"`. 118 | 119 | 3. Save the file. 120 | 121 | 4. Next, open the `dfx.json` file in the root directory of your project. 122 | 123 | 5. Find the line that says `"hello": {` and change it to `"payment": {`. 124 | 125 | 6. Inside the `"payment"` object change "package" from `"hello"` to `"payment"` and candid from `"backend/hello/hello.did"` to `"backend/payment/payment.did"`. 126 | 127 | 7. Save the file. 128 | 129 | 8. Rename the directory `backend/hello` to `backend/payment`. 130 | 131 | 9. Open the `Cargo.toml` file in the root directory again. 132 | 133 | 10. Find the line that says `members = ["backend/hello"]` and change it to `members = ["backend/payment"]`. 134 | 135 | 11. Save the file. 136 | 137 | 12. Open the `payment` directory and locate the `hello.did` file. 138 | 139 | 13. Ensure that the `.did` file is named `payment.did`. 140 | 141 | 14. Save any changes if necessary. 142 | 143 | With these changes, your project and canister will now be named "payment" instead of "hello". 144 | 145 | ## Step 2: Modify the Backend for ETH Deposits 146 | 147 | In this step, we'll modify the backend to include a function that generates a deposit principal from a canister ID. This is essential for converting SepoliaETH into ckSepoliaETH, as per the ckEth documentation. 148 | 149 | ### Installing the `b3_utils` Rust Crate 150 | 151 | First, we need to install the [b3_utils](https://docs.rs/b3_utils/latest/b3_utils/) Rust crate. Open your `Cargo.toml` file and add the following line under `[dependencies]`: 152 | 153 | ```toml 154 | b3_utils = "0.11.0" 155 | ``` 156 | 157 | or run this command: 158 | 159 | ```bash 160 | cargo add b3_utils 161 | ``` 162 | 163 | ### Modifying the `greet` Function 164 | 165 | Replace the existing `greet` function with the new `deposit_principal` function: 166 | 167 | ```rust 168 | use b3_utils::{vec_to_hex_string_with_0x, Subaccount}; 169 | use candid::Principal; 170 | 171 | #[ic_cdk::query] 172 | fn deposit_principal(principal: String) -> String { 173 | let principal = Principal::from_text(principal).unwrap(); 174 | let subaccount = Subaccount::from_principal(principal); 175 | 176 | let bytes32 = subaccount.to_bytes32().unwrap(); 177 | 178 | vec_to_hex_string_with_0x(bytes32) 179 | } 180 | ``` 181 | 182 | #### Why This Change? 183 | 184 | 1. **Function Annotation**: We use `#[ic_cdk::query]` to indicate that this is a query method, meaning it's read-only and doesn't modify the state. 185 | 186 | 2. **Principal Conversion**: We convert the passed string into a `Principal` object, which is essential for generating a subaccount. 187 | 188 | 3. **Subaccount Generation**: We generate a `Subaccount` from the `Principal`, which is a necessary step for depositing ETH. 189 | 190 | 4. **Bytes32 Conversion**: We convert the subaccount into a bytes32 array, which is the required format for the smart contract on the Sepolia Ethereum testnet. 191 | 192 | 5. **Hex String**: Finally, we convert the bytes32 array into a hex string with a "0x" prefix, which can be used as an argument for the smart contract. 193 | 194 | ### Deploy the Modified Backend Canister 195 | 196 | After making the changes to the backend, open another terminal and deploy the canister to your local Internet Computer environment using the following command: 197 | 198 | ```bash 199 | yarn deploy payment 200 | ``` 201 | 202 | Note: confirm the consent with `yes` to the change on the terminal. 203 | 204 | This will deploy only the `payment` canister, which now includes the `deposit_principal` function. 205 | 206 | ### Update the Frontend Code 207 | 208 | Navigate to the frontend code where the `useQueryCall` hook is used. This is typically found in a component file. Change the method from `"greet"` to `"deposit_principal"`: 209 | 210 | ```javascript 211 | const { call, data, error, loading } = useQueryCall({ 212 | functionName: "deposit_principal" 213 | }) 214 | ``` 215 | 216 | #### Testing the Changes 217 | 218 | 1. **Pass the Canister ID**: Update the frontend to include an input field where you can enter the canister ID. 219 | 220 | 2. **Check the Output**: The output should be a hexadecimal string that represents the deposit principal, which can be used for depositing ETH. 221 | 222 | ![Alt text](assets/deposit_principal.png) 223 | 224 | ## Step 3: Integrate MetaMask and Call the Minter Helper Contract 225 | 226 | In this step, we'll integrate MetaMask using the [wagmi](https://wagmi.sh) library and set up the frontend to call the minter helper contract's deposit function. 227 | 228 | ### Prerequisites 229 | 230 | - Make sure you have the [MetaMask extension](https://metamask.io/download.html) installed in your browser. 231 | 232 | ### Installing `wagmi` and `viem` 233 | 234 | First, install the `wagmi` and `viem` packages: 235 | 236 | ```bash 237 | yarn add wagmi viem 238 | ``` 239 | 240 | ### Configure `wagmi` 241 | 242 | Create a new file `config.ts` inside the `src/service` directory and add the following code: 243 | 244 | ```javascript 245 | import { createPublicClient, http } from "viem" 246 | import { createConfig, sepolia } from "wagmi" 247 | 248 | export const config = createConfig({ 249 | chains: [sepolia], 250 | connectors: [injected()], 251 | client({ chain }) { 252 | return createClient({ chain, transport: http() }) 253 | } 254 | }) 255 | ``` 256 | 257 | ### Create the `Wallet` Component 258 | 259 | Create a new file named `Wallet.tsx` inside the `src/components` folder and add the following code: 260 | 261 | ```javascript 262 | import { useAccount, useConnect, useDisconnect } from "wagmi" 263 | import { MetaMaskConnector } from "wagmi/connectors/metaMask" 264 | 265 | interface WalletProps {} 266 | 267 | const Wallet: React.FC = ({}) => { 268 | const { address } = useAccount() 269 | 270 | const { connect } = useConnect({ 271 | connector: new MetaMaskConnector() 272 | }) 273 | 274 | const { disconnect } = useDisconnect() 275 | 276 | if (address) 277 | return ( 278 |
279 | Connected to: {address} 280 |
281 | 282 |
283 | ) 284 | return 285 | } 286 | 287 | export default Wallet 288 | ``` 289 | 290 | ### Update `index.tsx` 291 | 292 | Finally, update your `src/pages/index.tsx` file and replace `` with the following code`: 293 | 294 | ```javascript 295 | // ...existing imports 296 | import Wallet from "../components/Wallet" 297 | import { config } from "service/config" 298 | import { WagmiConfig } from "wagmi" 299 | 300 | function HomePage() { 301 | return ( 302 | {/* ...existing components */} 303 | {/* */} 304 | 305 | 306 | 307 | {/* ...existing components */} 308 | ) 309 | } 310 | ``` 311 | 312 | #### Testing the Changes 313 | 314 | You should see a "Connect Wallet" button on your browser, similar to the screenshot below. 315 | 316 | ![Alt text](assets/connect_wallet.png) 317 | Clicking on the button should open a MetaMask popup asking for permission to connect. After connecting, you should see your wallet address on the screen. 318 | 319 | ## Step 4: Prepare Minter Helper Contract and Enable Deposits 320 | 321 | In this step, we'll prepare the minter helper contract for calls and enable ETH deposits through the frontend. 322 | 323 | ### Fetch Contract ABI 324 | 325 | 1. **Navigate to Etherscan**: Open the [contract page on Sepolia Etherscan](https://sepolia.etherscan.io/address/0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34#code). 326 | 327 | 2. **Copy Contract ABI**: Copy the Contract ABI from the "Contract" tab. 328 | ![Alt text](assets/contract_abi.png) 329 | 330 | 3. **Create `abi.json`**: Inside the `src/service` directory, create a new file named `abi.json` and paste the copied ABI. 331 | 332 | ### Create the `Deposit` Component 333 | 334 | Create a new file named `Deposit.tsx` inside the `src/components` directory and add the following code: 335 | 336 | ```javascript 337 | import { canisterId } from "declarations/payment" 338 | import { useEffect, useState } from "react" 339 | import helperAbi from "service/abi.json" 340 | import { useQueryCall } from "service/payment" 341 | import { parseEther } from "viem" 342 | import { useContractWrite } from "wagmi" 343 | 344 | interface DepositProps {} 345 | 346 | const Deposit: React.FC = ({}) => { 347 | const [amount, setAmount] = useState(0) 348 | 349 | const { data: canisterDepositAddress } = useQueryCall({ 350 | functionName: "deposit_principal" 351 | }) 352 | 353 | useEffect(() => { 354 | call(canisterId) 355 | }, []) 356 | 357 | const { data, isLoading, write } = useContractWrite({ 358 | address: "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", 359 | abi: helperAbi, 360 | functionName: "deposit", 361 | value: parseEther(amount.toString()), 362 | args: [canisterDepositAddress] 363 | }) 364 | 365 | const changeHandler = (e: React.ChangeEvent) => { 366 | let amount = e.target.valueAsNumber 367 | if (Number.isNaN(amount) || amount < 0) amount = 0 368 | 369 | setAmount(amount) 370 | } 371 | 372 | if (isLoading) { 373 | return
Loading...
374 | } else if (data?.hash) { 375 | return
Transaction Hash: {data.hash}
376 | } else { 377 | return ( 378 |
379 | 380 | 381 |
382 | ) 383 | } 384 | } 385 | 386 | export default Deposit 387 | ``` 388 | 389 | #### Understanding `useContractWrite` 390 | 391 | The `useContractWrite` hook is used to interact with Ethereum smart contracts. Here's what each parameter does: 392 | 393 | - **`address`**: The Ethereum address of the contract you want to interact with. 394 | - **`abi`**: The ABI (Application Binary Interface) of the contract, which is a JSON representation of the contract's methods and structures. 395 | - **`functionName`**: The name of the function in the contract that you want to call. 396 | - **`value`**: The amount of ETH to send along with the function call, converted to its smallest unit (wei) using `parseEther`. 397 | - **`args`**: An array of arguments that the function takes. In this case, it's the deposit address generated by the canister. 398 | 399 | ### Update the `Wallet` Component 400 | 401 | Add the `` component to the `Wallet.tsx` file, right above the "Disconnect" button: 402 | 403 | ```javascript 404 | // ...existing code 405 | return ( 406 |
407 | Connected to: {address} 408 |
409 | 410 |
411 | 412 |
413 | ) 414 | ``` 415 | 416 | #### Testing the Changes 417 | 418 | You should have small amount of Sepolia ETH in your wallet. you can get some using this [faucet](https://sepoliafaucet.com/). 419 | 420 | 1. **Call the Deposit Function**: Please make sure you are on the Sepolia network on the metamask then Use the new deposit input and button to initiate a deposit. 421 | ![Alt text](assets/deposit_input.png) 422 | 2. **Confirm with MetaMask**: A MetaMask popup should appear asking for confirmation to proceed with the transaction. 423 | ![Alt text](assets/deposit_metamask.png) 424 | 3. **Check the Output**: After confirming, you should see a transaction hash. 425 | ![Alt text](assets/deposit_result.png) 426 | 427 | ## Step 5: Wait for Transaction Confirmation 428 | 429 | In this step, we'll implement a mechanism to wait for transaction confirmations before verifying the payment inside the canister. 430 | 431 | ### Create the `Confirmation` Component 432 | 433 | Create a new file named `Confirmation.tsx` inside the `src/components` directory and add the following code: 434 | 435 | ```javascript 436 | import { Hash } from "viem" 437 | import { useWaitForTransaction } from "wagmi" 438 | 439 | interface ConfirmationProps { 440 | hash: Hash; 441 | } 442 | 443 | const Confirmation: React.FC = ({ hash }) => { 444 | const { data, isError, error, isLoading } = useWaitForTransaction({ 445 | hash, 446 | confirmations: 6 // 6 confirmations for be sure that the transaction is confirmed 447 | }) 448 | 449 | if (isError && error) { 450 | return
Transaction error {error.toString()}
451 | } else if (isLoading) { 452 | return
Waiting for confirmation…
453 | } else if (data) { 454 | return
Transaction Status: {data.status}
455 | } else { 456 | return null 457 | } 458 | } 459 | 460 | export default Confirmation 461 | ``` 462 | 463 | #### Understanding `useWaitForTransaction` 464 | 465 | The `useWaitForTransaction` hook is used to wait for a specified number of confirmations for a given Ethereum transaction hash. Here's what each parameter does: 466 | 467 | - **`hash`**: The transaction hash for which you are waiting for confirmations. 468 | 469 | - **`confirmations`**: The number of confirmations to wait for before considering the transaction as confirmed. The default is 1, but in this example, we set it to 6 for added security. 470 | 471 | ### Update the `Deposit` Component 472 | 473 | Replace the line that shows the transaction hash with the `Confirmation` component: 474 | 475 | Change this: 476 | 477 | ```javascript 478 | return
Transaction Hash: {data.hash}
479 | ``` 480 | 481 | To this: 482 | 483 | ```javascript 484 | return 485 | ``` 486 | 487 | #### Testing the Changes 488 | 489 | 1. **Send Another Transaction**: Initiate another deposit transaction. 490 | 491 | 2. **Check the Output**: You should see the confirmation process in action. Once the specified number of confirmations is reached, the transaction status will be displayed. 492 | ![Alt text](assets/confirmation.png) 493 | 494 | ## Step 6: Fetching Transaction On-Chain 495 | 496 | In this step, we'll verify the Ethereum transaction on-chain by calling the Ethereum JSON-RPC API from within the canister. 497 | 498 | ### Add Dependencies 499 | 500 | Add the following dependencies to your `Cargo.toml`: 501 | 502 | ```toml 503 | serde = { version = "1.0", features = ["derive"] } 504 | ``` 505 | 506 | ### Create the `eth_get_transaction_receipt` Function 507 | 508 | #### Understanding the Function 509 | 510 | The function `eth_get_transaction_receipt` performs the following tasks: 511 | 512 | - **Call to EVM RPC Canister**: It initiates a call to the EVM RPC canister, utilizing the `eth_get_transaction_receipt` method to retrieve the transaction receipt for a given transaction hash. The function prepares the necessary parameters, including a list of Ethereum Sepolia network services (e.g., PublicNode, BlockPi, Ankr) to ensure reliable data retrieval. 513 | 514 | - **Handle the RPC Response**: The function processes the response from the EVM RPC canister. If the response is consistent across the selected network services, it returns the transaction `receipt` wrapped in an `Ok` result. If the results are inconsistent or if an error occurs during the RPC call, the function returns an error message wrapped in an Err result. 515 | 516 | - **Error Handling:**: It captures and returns any errors that occur during the process, such as network issues, inconsistencies in the RPC responses, or communication failures, providing detailed error messages for troubleshooting. 517 | 518 | ### Add Dependency 519 | 520 | Add the following dependency to your `Cargo.toml`: 521 | 522 | ```toml 523 | evm-rpc-canister-types = "1.0.0" 524 | ``` 525 | 526 | ### Modify dfx.json file 527 | 528 | Add the follwing code snippet to your `dfx.json` file: 529 | 530 | ```json 531 | "evm_rpc": { 532 | "type": "custom", 533 | "candid": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/latest/download/evm_rpc.did", 534 | "wasm": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/latest/download/evm_rpc.wasm.gz", 535 | "remote": { 536 | "id": { 537 | "ic": "7hfb6-caaaa-aaaar-qadga-cai" 538 | } 539 | }, 540 | "specified_id": "7hfb6-caaaa-aaaar-qadga-cai", 541 | "init_arg": "(record { nodesInSubnet = 28 })" 542 | } 543 | ``` 544 | 545 | ## Initiate the EVM RPC Canister with your canister ID 546 | 547 | Use the following types to import the structs from the `evm_rpc_canister_types` crate: 548 | 549 | ```rust 550 | // Import the structs from the crate 551 | use evm_rpc_canister_types::{ 552 | EthSepoliaService, GetTransactionReceiptResult, MultiGetTransactionReceiptResult, RpcServices, 553 | EVM_RPC, 554 | }; 555 | ``` 556 | 557 | ## Implement the code logic 558 | 559 | Here's the code snippet for the function: 560 | 561 | ```rust 562 | // Implementing the eth_get_transaction function 563 | async fn eth_get_transaction_receipt(hash: String) -> Result { 564 | // Make the call to the EVM_RPC canister 565 | let result: Result<(MultiGetTransactionReceiptResult,), String> = EVM_RPC 566 | .eth_get_transaction_receipt( 567 | RpcServices::EthSepolia(Some(vec![ 568 | EthSepoliaService::PublicNode, 569 | EthSepoliaService::BlockPi, 570 | EthSepoliaService::Ankr, 571 | ])), 572 | None, 573 | hash, 574 | 10_000_000_000, 575 | ) 576 | .await 577 | .map_err(|e| format!("Failed to call eth_getTransactionReceipt: {:?}", e)); 578 | 579 | match result { 580 | Ok((MultiGetTransactionReceiptResult::Consistent(receipt),)) => Ok(receipt), 581 | Ok((MultiGetTransactionReceiptResult::Inconsistent(error),)) => Err(format!( 582 | "EVM_RPC returned inconsistent results: {:?}", 583 | error 584 | )), 585 | Err(e) => Err(format!("Error calling EVM_RPC: {}", e)), 586 | } 587 | } 588 | ``` 589 | 590 | Note: Please always keep `ic_cdk::export_candid!();` at the very end of the `lib.rs` file. 591 | 592 | ### Test the Function Using Candid UI 593 | 594 | For testing the function, we'll use the Candid UI, which is a web-based interface for interacting with canisters. It's automatically generated when you deploy a canister using the DFINITY Canister SDK. 595 | 596 | Add this function to your `lib.rs` file: 597 | 598 | ```rust 599 | // Testing get receipt function 600 | #[ic_cdk::update] 601 | async fn get_receipt(hash: String) -> GetTransactionReceiptResult { 602 | eth_get_transaction_receipt(hash).await.unwrap() 603 | } 604 | ``` 605 | 606 | 1. **Deploy the Canister**: Deploy the updated canister using the command `yarn deploy evm_rpc && yarn deploy payment`. 607 | 608 | 2. **Navigate to Candid UI**: After successful deployment, navigate to the Candid UI using the link provided in the terminal. 609 | Somthing like this `http://127.0.0.1:4943/?canisterId=bd3sg-teaaa-aaaaa-qaaba-cai&id=bkyz2-fmaaa-aaaaa-qaaaq-cai` 610 | 611 | 3. **Test the Function**: Inside the Candid UI, you should see the `get_receipt` function. Test it by passing a transaction hash and observing the response. 612 | ![Alt text](assets/receipt.png) 613 | 614 | ## Step 7: On-Chain Verification of Transactions 615 | 616 | In this step, we'll create a function to verify Ethereum transactions on-chain. This function will use the logs emitted by the smart contract to verify the transaction details. 617 | 618 | #### Event Topics 619 | 620 | The logs' topics are based on the `ReceivedEth` event, which has the following signature: 621 | ![Alt text](assets/logs.png) 622 | 623 | ```solidity 624 | ReceivedEth (index_topic_1 address from, uint256 value, index_topic_2 bytes32 principal) 625 | ``` 626 | 627 | - `log.topics[0]`: Event signature hash 628 | - `log.topics[1]`: `from` address (index_topic_1) 629 | - `log.topics[2]`: `principal` (index_topic_2) 630 | 631 | #### Event Data 632 | 633 | - `log.data`: `value` (uint256) 634 | 635 | ### Create the `verify_transaction` Function 636 | 637 | Here's the code snippet for the function: 638 | 639 | ```rust 640 | const MINTER_ADDRESS: &str = "0xb44b5e756a894775fc32eddf3314bb1b1944dc34"; 641 | 642 | use candid::Nat; 643 | use b3_utils::hex_string_with_0x_to_nat; 644 | 645 | #[derive(CandidType, Deserialize)] 646 | pub struct VerifiedTransactionDetails { 647 | pub amount: Nat, 648 | pub from: String, 649 | } 650 | 651 | #[ic_cdk::update] 652 | async fn verify_transaction(hash: String) -> VerifiedTransactionDetails { 653 | // Get the transaction receipt 654 | let receipt_result = match eth_get_transaction_receipt(hash).await { 655 | Ok(receipt) => receipt, 656 | Err(e) => panic!("Failed to get receipt: {}", e), 657 | }; 658 | 659 | // Ensure the transaction was successful 660 | let receipt = match receipt_result { 661 | GetTransactionReceiptResult::Ok(Some(receipt)) => receipt, 662 | GetTransactionReceiptResult::Ok(None) => panic!("Receipt is None"), 663 | GetTransactionReceiptResult::Err(e) => { 664 | panic!("Error on Get transaction receipt result: {:?}", e) 665 | } 666 | }; 667 | 668 | // Check if the status indicates success (Nat 1) 669 | let success_status = Nat::from(1u8); 670 | if receipt.status != success_status { 671 | panic!("Transaction failed"); 672 | } 673 | 674 | // Verify the 'to' address matches the minter address 675 | if receipt.to != MINTER_ADDRESS { 676 | panic!("Minter address does not match"); 677 | } 678 | 679 | let deposit_principal = canister_deposit_principal(); 680 | 681 | // Verify the principal in the logs matches the deposit principal 682 | let log_principal = receipt 683 | .logs 684 | .iter() 685 | .find(|log| log.topics.get(2).map(|topic| topic.as_str()) == Some(&deposit_principal)) 686 | .unwrap_or_else(|| panic!("Principal not found in logs")); 687 | 688 | // Extract relevant transaction details 689 | let amount = hex_string_with_0x_to_nat(&log_principal.data) 690 | .unwrap_or_else(|e| panic!("Failed to parse amount: {}", e)); 691 | let from_address = receipt.from.clone(); 692 | 693 | VerifiedTransactionDetails { 694 | amount, 695 | from: from_address, 696 | } 697 | } 698 | ``` 699 | 700 | #### Understanding the Function 701 | 702 | The function `verify_transaction` performs the following tasks: 703 | 704 | - **Check Transaction Status**: It checks if the transaction was successful by comparing the `status` field to "1". 705 | 706 | - **Verify Address**: It verifies that the `to` address in the transaction and the `address` in the logs match the minter address. 707 | 708 | - **Verify Principal**: It verifies that the principal in the logs matches the canister's deposit principal. The principal is found in `log.topics[2]`. 709 | 710 | - **Return Transaction Details**: It returns the amount and the sender's address. 711 | 712 | ### Create a Function to Return Canister Deposit Principal 713 | 714 | For a more robust and secure way, create a new function that returns the canister's deposit principal: 715 | 716 | ```rust 717 | #[ic_cdk::query] 718 | fn canister_deposit_principal() -> String { 719 | let subaccount = Subaccount::from(ic_cdk::id()); 720 | 721 | let bytes32 = subaccount.to_bytes32().unwrap(); 722 | 723 | vec_to_hex_string_with_0x(bytes32) 724 | } 725 | ``` 726 | 727 | #### Testing the Functions 728 | 729 | 1. **Deploy the Canister**: Deploy the updated canister using `yarn deploy payment`. 730 | 731 | 2. **Navigate to Candid UI**: After successful deployment, navigate to the Candid UI using the link provided in the terminal. 732 | 733 | 3. **Test the Functions**: Inside the Candid UI, you should see the `verify_transaction` and `canister_deposit_principal` functions. Test them by passing a transaction hash and observing the response. 734 | ![Alt text](assets/candid_verify.png) 735 | 736 | ## Step 8: Frontend Update for On-Chain Verification 737 | 738 | In this step, we'll update the frontend to call the `verify_transaction` function after the transaction has been confirmed on-chain. 739 | 740 | ### Create the `VerifyTransaction` Component 741 | 742 | #### Understanding the Component 743 | 744 | The `VerifyTransaction` component performs the following tasks: 745 | 746 | - **Call `verify_transaction`**: It calls the `verify_transaction` function from the canister when the `hash` prop is provided. 747 | 748 | - **Display Status**: It displays the transaction details, including the amount and the sender's address, once the transaction is confirmed on-chain. 749 | 750 | Here's the code snippet for the component: 751 | 752 | ```javascript 753 | import { useEffect } from "react" 754 | import { useQueryCall } from "service/payment" 755 | import { formatEther } from "viem" 756 | 757 | interface VerifyTransactionProps { 758 | hash: string; 759 | } 760 | 761 | const VerifyTransaction: React.FC = ({ hash }) => { 762 | const { loading, error, data, call } = useQueryCall({ 763 | functionName: "verify_transaction" 764 | }) 765 | 766 | useEffect(() => { 767 | call([hash]) 768 | }, [hash]) 769 | 770 | if (loading) { 771 | return
Processing…
772 | } else if (error) { 773 | return
{error.toString()}
774 | } else if (data) { 775 | return ( 776 |
777 | Transaction({hash}) with {formatEther(data[0])}ETH from{" "} 778 | {data[1]} is confirmed on-chain. 779 |
780 | ) 781 | } else { 782 | return null 783 | } 784 | } 785 | 786 | export default VerifyTransaction 787 | ``` 788 | 789 | ### Update the `Confirmation` Component 790 | 791 | Replace the line that shows the transaction status with the `VerifyTransaction` component: 792 | 793 | Change this: 794 | 795 | ```javascript 796 | return
Transaction Status: {data.status}
797 | ``` 798 | 799 | To this: 800 | 801 | ```javascript 802 | return 803 | ``` 804 | 805 | #### Testing the Changes 806 | 807 | 1. **Initiate a Transaction**: Initiate a deposit transaction and confirm it. 808 | 809 | 2. **Check the Output**: You should see the transaction details displayed once the transaction is confirmed and procceed on-chain. 810 | ![Alt text](assets/verified_onchain.png) 811 | 812 | ## Step 9: Deploying to the Internet Computer Mainnet 813 | 814 | In this step, we'll deploy our project to the Internet Computer mainnet. This involves a few key steps: 815 | 816 | ### Topping Up Your Wallet with Cycles 817 | 818 | Before deploying to the mainnet, you'll need to ensure that your wallet has enough cycles. 819 | 820 | 1. **Quickstart**: Run `dfx quickstart` in your terminal and follow the process to top up your wallet. 821 | 822 | 2. **Faucet Cycles**: Alternatively, you can get some free cycles from the DFINITY [cycles faucet](https://forum.dfinity.org/t/cycles-faucet-is-now-live). 823 | 824 | ### Deploying the Canister 825 | 826 | Run the following command to deploy your canister to the mainnet: 827 | 828 | ```bash 829 | yarn deploy --network=ic 830 | ``` 831 | 832 | Alternatively, you can choose to deploy only the backend to the mainnet and run the frontend locally. To deploy just the backend, use: 833 | 834 | ```bash 835 | yarn deploy payment --network=ic 836 | ``` 837 | 838 | To run the frontend locally, execute: 839 | 840 | ```bash 841 | yarn dev 842 | ``` 843 | 844 | Upon successful deployment of the backend, you should see output similar to this in your terminal: 845 | 846 | ![alt text](./assets/terminal.png) 847 | 848 | ### Testing on Mainnet 849 | 850 | 1. **Open the Frontend**: If you've deployed the frontend to the mainnet, navigate to the frontend URL provided in the terminal. If you're running the frontend locally, you can access it via `http://localhost:3000` or the URL provided in your local development server. 851 | 852 | 2. **Initiate a Transaction**: Initiate a deposit transaction and confirm it. 853 | ![Alt text](assets/mainnet_deposit.png) 854 | 855 | ## Step 10: Integrating with ICRC Standard 856 | 857 | In this step, we'll integrate our canister with the ckETH ICRC standard to show the balance and enable ETH withdrawal. 858 | 859 | ### Adding Ledger Feature to `b3_utils` 860 | 861 | First, add the "ledger" feature to the `b3_utils` crate in your `Cargo.toml`: 862 | 863 | ```toml 864 | b3_utils = { version = "0.11.0", features = ["ledger"] } 865 | ``` 866 | 867 | ### Setting Up Ledger and Minter Constants 868 | 869 | Add the following lines at the top of your Rust code to specify the ledger and minter canister IDs: 870 | 871 | ```rust 872 | const LEDGER: &str = "apia6-jaaaa-aaaar-qabma-cai"; 873 | const MINTER: &str = "jzenf-aiaaa-aaaar-qaa7q-cai"; 874 | ``` 875 | 876 | ### Creating the Balance Function 877 | 878 | #### Understanding the Function 879 | 880 | The `balance` function uses the `ICRC1` trait from `b3_utils` to fetch the balance of the canister in ckETH. 881 | 882 | Here's the code snippet for the function: 883 | 884 | ```rust 885 | use b3_utils::ledger::{ICRCAccount, ICRC1}; 886 | use candid::Principal; 887 | 888 | #[ic_cdk::update] 889 | async fn balance() -> Nat { 890 | let account = ICRCAccount::new(ic_cdk::id(), None); 891 | 892 | ICRC1::from(LEDGER).balance_of(account).await.unwrap() 893 | } 894 | ``` 895 | 896 | #### Testing the ckETH Balance Function 897 | 898 | 1. **Deploy to Mainnet**: Run `yarn deploy payment --network=ic` to upgrade canister. 899 | 900 | 2. **Open Candid UI**: Navigate to the Candid UI and test the `balance` function. Note that the minting process might take some time. 901 | ![Alt text](assets/balance.png) 902 | 903 | ### Creating the Transfer Function 904 | 905 | #### Understanding the Function 906 | 907 | The `transfer` function allows the canister to transfer a specified amount of ckETH to another account. 908 | The function uses the `ICRC1` trait from `b3_utils` to transfer the specified amount of ckETH to the recipient. 909 | 910 | Here's the code snippet for the function: 911 | 912 | ```rust 913 | use b3_utils::ledger::{ICRC1TransferArgs, ICRC1TransferResult}; 914 | use std::str::FromStr; 915 | 916 | #[ic_cdk::update] 917 | async fn transfer(to: String, amount: Nat) -> ICRC1TransferResult { 918 | let to = ICRCAccount::from_str(&to).unwrap(); 919 | 920 | let transfer_args = ICRC1TransferArgs { 921 | to, 922 | amount, 923 | from_subaccount: None, 924 | fee: None, 925 | memo: None, 926 | created_at_time: None, 927 | }; 928 | 929 | ICRC1::from(LEDGER).transfer(transfer_args).await.unwrap() 930 | } 931 | ``` 932 | 933 | #### Testing the Transfer Function 934 | 935 | 1. **Deploy to Mainnet**: Run `yarn deploy payment --network=ic` to upgrade canister. 936 | 937 | 2. **Open Candid UI**: Navigate to the Candid UI and test the `transfer` function by passing the recipient's [ICRCAccount](https://forum.dfinity.org/t/icrc-1-account-human-readable-format/14682/56) comptible format string and the amount of ckETH to transfer. 938 | ![Alt text](assets/transfer.png) 939 | 940 | ### Approving the Minter to Spend ckETH 941 | 942 | #### Understanding the Function 943 | 944 | The `approve` function uses the `ICRC2` trait from `b3_utils` to approve the minter to spend your ckETH. This is a one-time action if you approve a large amount. 945 | 946 | Here's the code snippet for the function: 947 | 948 | ```rust 949 | use b3_utils::ledger::{ICRC2ApproveArgs, ICRC2ApproveResult, ICRC2}; 950 | 951 | #[ic_cdk::update] 952 | async fn approve(amount: Nat) -> ICRC2ApproveResult { 953 | let minter = Principal::from_text(&MINTER).unwrap(); 954 | 955 | let spender = ICRCAccount::new(minter, None); 956 | 957 | let args = ICRC2ApproveArgs { 958 | amount, 959 | spender, 960 | created_at_time: None, 961 | expected_allowance: None, 962 | expires_at: None, 963 | fee: None, 964 | memo: None, 965 | from_subaccount: None, 966 | }; 967 | 968 | ICRC2::from(LEDGER).approve(args).await.unwrap() 969 | } 970 | ``` 971 | 972 | #### Testing the Approve Function 973 | 974 | 1. **Deploy to Mainnet**: Again upgrade the canister using `yarn deploy payment --network=ic`. 975 | 976 | 2. **Open Candid UI**: Navigate to the Candid UI and test the `approve` function. 977 | ![Alt text](assets/approve.png) 978 | 979 | ## Step 11: Creating the Withdraw Function 980 | 981 | In this step, we'll create a `withdraw` function that allows users to withdraw ETH from the canister. 982 | 983 | ### Defining Types from the Minter Canister 984 | 985 | First, define some types that will be used for the withdrawal operation. These types are derived from the minter canister. 986 | 987 | ```rust 988 | use candid::{CandidType, Deserialize}; 989 | 990 | #[derive(CandidType, Deserialize)] 991 | pub struct WithdrawalArg { 992 | pub amount: Nat, 993 | pub recipient: String, 994 | } 995 | 996 | #[derive(CandidType, Deserialize, Clone, Debug)] 997 | pub struct RetrieveEthRequest { 998 | pub block_index: Nat, 999 | } 1000 | 1001 | #[derive(CandidType, Deserialize, Debug)] 1002 | pub enum WithdrawalError { 1003 | AmountTooLow { min_withdrawal_amount: Nat }, 1004 | InsufficientFunds { balance: Nat }, 1005 | InsufficientAllowance { allowance: Nat }, 1006 | TemporarilyUnavailable(String), 1007 | } 1008 | 1009 | type WithdrawalResult = Result; 1010 | ``` 1011 | 1012 | ### Creating the Withdraw Function 1013 | 1014 | #### Understanding the Function 1015 | 1016 | The `withdraw` function uses the `InterCall` trait from `b3_utils` to make an internal canister call to the minter canister. The function takes an `amount` and a `recipient` as arguments and initiates the withdrawal process. 1017 | 1018 | Here's the code snippet for the function: 1019 | 1020 | ```rust 1021 | use b3_utils::InterCall; 1022 | 1023 | #[ic_cdk::update] 1024 | async fn withdraw(amount: Nat, recipient: String) -> WithdrawalResult { 1025 | let withraw = WithdrawalArg { amount, recipient }; 1026 | 1027 | InterCall::from(MINTER) 1028 | .call("withdraw_eth", withraw) 1029 | .await 1030 | .unwrap() 1031 | } 1032 | ``` 1033 | 1034 | #### Testing the Withdraw Function 1035 | 1036 | 1. **Deploy to Mainnet**: Run `yarn deploy payment --network=ic`. 1037 | 1038 | 2. **Open Candid UI**: Navigate to the Candid UI and test the `withdraw` function. Make sure to enter the amount in wei. 1039 | ![Alt text](assets/withdraw.png) 1040 | 1041 | ## Step 12: Adding Security and Functionalities 1042 | 1043 | In this step, we'll add some security measures and functionalities to our canister. 1044 | 1045 | ### Adding Security Measures 1046 | 1047 | #### Guards 1048 | 1049 | We'll add guards to the `withdraw` and `approve` functions to ensure that only the controller can call them. Add the following line at the top of your Rust code: 1050 | 1051 | ```rust 1052 | use b3_utils::caller_is_controller; 1053 | ``` 1054 | 1055 | Then, add the `guard` attribute to the `withdraw` and `approve` functions: 1056 | 1057 | ```rust 1058 | #[ic_cdk::update(guard = "caller_is_controller")] 1059 | ``` 1060 | 1061 | #### Transaction List 1062 | 1063 | To prevent a transaction from being processed more than once, we'll use stable memory. Add the "stable_memory" feature to `b3_utils` in your `Cargo.toml`: 1064 | 1065 | ```toml 1066 | b3_utils = { version = "0.11.0", features = ["ledger", "stable_memory"] } 1067 | ``` 1068 | 1069 | Then, add the following code to initialize stable memory: 1070 | 1071 | ```rust 1072 | use b3_utils::memory::init_stable_mem_refcell; 1073 | use b3_utils::memory::types::DefaultStableBTreeMap; 1074 | use std::cell::RefCell; 1075 | 1076 | thread_local! { 1077 | static TRANSACTIONS: RefCell> = init_stable_mem_refcell("trasnactions", 1).unwrap(); 1078 | static ITEMS: RefCell> = init_stable_mem_refcell("items", 2).unwrap(); 1079 | } 1080 | ``` 1081 | 1082 | ### Adding Functionalities 1083 | 1084 | #### Item Management 1085 | 1086 | We'll add functionalities to set items and their prices, and to get the list of items. Here are the functions: 1087 | 1088 | ```rust 1089 | #[ic_cdk::query] 1090 | fn get_transaction_list() -> Vec<(String, String)> { 1091 | TRANSACTIONS.with(|t| { 1092 | t.borrow() 1093 | .iter() 1094 | .map(|(k, v)| (k.clone(), v.clone())) 1095 | .collect() 1096 | }) 1097 | } 1098 | 1099 | #[ic_cdk::update(guard = "caller_is_controller")] 1100 | fn set_item(item: String, price: u128) { 1101 | ITEMS.with(|p| p.borrow_mut().insert(item, price)); 1102 | } 1103 | 1104 | #[ic_cdk::query] 1105 | fn get_items() -> Vec<(String, u128)> { 1106 | ITEMS.with(|p| { 1107 | p.borrow() 1108 | .iter() 1109 | .map(|(k, v)| (k.clone(), v.clone())) 1110 | .collect() 1111 | }) 1112 | } 1113 | ``` 1114 | 1115 | #### Buying Items 1116 | 1117 | We'll add a function to buy items. This function will check the transaction list to ensure that the transaction has not been processed before. and check the amount to ensure that it's not too low. 1118 | 1119 | Here's the function: 1120 | 1121 | ```rust 1122 | #[ic_cdk::update] 1123 | async fn buy_item(item: String, hash: String) -> u64 { 1124 | if TRANSACTIONS.with(|t| t.borrow().contains_key(&hash)) { 1125 | panic!("Transaction already processed"); 1126 | } 1127 | 1128 | let price = ITEMS.with(|p| { 1129 | p.borrow() 1130 | .get(&item) 1131 | .unwrap_or_else(|| panic!("Item not found")) 1132 | .clone() 1133 | }); 1134 | 1135 | let verified_details = match verify_transaction(hash.clone()).await { 1136 | Ok(details) => details, 1137 | Err(e) => panic!("Transaction verification failed: {}", e), 1138 | }; 1139 | 1140 | let VerifiedTransactionDetails { amount, from } = verified_details; 1141 | 1142 | if amount.parse::().unwrap_or(0) < price { 1143 | panic!("Amount too low"); 1144 | } 1145 | 1146 | TRANSACTIONS.with(|t| { 1147 | let mut t = t.borrow_mut(); 1148 | t.insert(hash, from); 1149 | 1150 | t.len() as u64 1151 | }) 1152 | } 1153 | ``` 1154 | 1155 | ### Testing 1156 | 1157 | 1. **Deploy to Mainnet**: Run `yarn deploy payment --network=ic`. 1158 | 1159 | 2. **Testing Guards**: Use the terminal to execute functions with guards. For example: 1160 | 1161 | ```bash 1162 | dfx canister call payment withdraw '(10000000000000000, "0xB51f94aEEebE55A3760E8169A22e536eBD3a6DCB")' --network ic 1163 | ``` 1164 | 1165 | To add a new controller, run: 1166 | 1167 | ```bash 1168 | dfx canister update-settings payment --add-controller 'YOUR_PRINCIPAL' --network=ic 1169 | ``` 1170 | 1171 | 3. **Adding Items**: Add items using the terminal: 1172 | 1173 | ```bash 1174 | dfx canister call payment set_item '("Pizza", 1000000000000)' --network ic 1175 | ``` 1176 | 1177 | Check the items inside the Candid UI using `get_items`. 1178 | ![Alt text](assets/get_items.png) 1179 | 1180 | ## Step 13: Frontend Integration for Shop and Item Purchase 1181 | 1182 | In this step, we'll integrate the frontend to display a shop and handle item purchases. 1183 | 1184 | ### Shop Component 1185 | 1186 | Create a new file `Shop.tsx` inside the `src/components` directory and add the following code: 1187 | 1188 | ```jsx 1189 | import { useEffect } from "react" 1190 | import { useQueryCall } from "service/payment" 1191 | import Item from "./Item" 1192 | 1193 | interface ShopProps {} 1194 | 1195 | const Shop: React.FC = ({}) => { 1196 | const { 1197 | data: items, 1198 | loading, 1199 | call 1200 | } = useQueryCall({ 1201 | functionName: "get_items" 1202 | }) 1203 | 1204 | return ( 1205 |
1213 | {loading ? ( 1214 |
Loading...
1215 | ) : ( 1216 | items?.map(([name, price]) => { 1217 | return 1218 | }) 1219 | )} 1220 |
1221 | ) 1222 | } 1223 | 1224 | export default Shop 1225 | ``` 1226 | 1227 | This component fetches the list of items from the backend and displays them in a grid layout. 1228 | 1229 | ### Item Component 1230 | 1231 | Create a new file `Item.tsx` inside the `src/components` directory and add the following code: 1232 | 1233 | ```jsx 1234 | import { useEffect } from "react" 1235 | import helperAbi from "service/abi.json" 1236 | import { useQueryCall } from "service/payment" 1237 | import { formatEther } from "viem" 1238 | import { useContractWrite } from "wagmi" 1239 | import Confirmation from "./Confirmation" 1240 | 1241 | interface ItemProps { 1242 | name: string 1243 | price: bigint 1244 | } 1245 | 1246 | const Item: React.FC = ({ name, price }) => { 1247 | const { data: canisterDepositAddress, call } = useQueryCall( 1248 | functionName: "canister_deposit_principal" 1249 | ) 1250 | 1251 | const { data, isLoading, write } = useContractWrite({ 1252 | address: "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", 1253 | abi: helperAbi, 1254 | functionName: "deposit", 1255 | value: price, 1256 | args: [canisterDepositAddress] 1257 | }) 1258 | 1259 | if (isLoading) { 1260 | return
Buying {name}…
1261 | } else if (data?.hash) { 1262 | return 1263 | } else { 1264 | return ( 1265 |
1266 |

{name}

1267 |
{formatEther(price).toString()} ETH
1268 | 1269 |
1270 | ) 1271 | } 1272 | } 1273 | 1274 | export default Item 1275 | ``` 1276 | 1277 | This component handles the purchase of individual items. It uses the `canister_deposit_principal` and `deposit` methods to handle the transaction. 1278 | 1279 | ### Confirmation Component 1280 | 1281 | Edit the existing `Confirmation.tsx` file to add the `item` prop: 1282 | 1283 | ```jsx 1284 | import { Hash } from "viem" 1285 | import { useWaitForTransaction } from "wagmi" 1286 | import VerifyTransaction from "./VerifyTransaction" 1287 | 1288 | interface ConfirmationProps { 1289 | item: string 1290 | hash: Hash 1291 | } 1292 | 1293 | const Confirmation: React.FC = ({ item, hash }) => { 1294 | const { data, isError, error, isLoading } = useWaitForTransaction({ 1295 | hash, 1296 | confirmations: 6 1297 | }) 1298 | 1299 | if (isError && error) { 1300 | return
Transaction error {error.toString()}
1301 | } else if (isLoading) { 1302 | return
Waiting for confirmation on Ethereum…
1303 | } else if (data) { 1304 | return 1305 | } else { 1306 | return null 1307 | } 1308 | } 1309 | 1310 | export default Confirmation 1311 | ``` 1312 | 1313 | This component waits for the Ethereum transaction to be confirmed and then triggers the on-chain verification on the Internet Computer. 1314 | 1315 | ### Verify Transaction Component 1316 | 1317 | Edit the existing `VerifyTransaction.tsx` file to add the `item` prop and work with the new `buy_item` method: 1318 | 1319 | ```jsx 1320 | import { useEffect } from "react" 1321 | import { useQueryCall } from "service/payment" 1322 | 1323 | interface VerifyTransactionProps { 1324 | item: string 1325 | hash: string 1326 | } 1327 | 1328 | const VerifyTransaction: React.FC = ({ 1329 | item, 1330 | hash 1331 | }) => { 1332 | const { loading, error, data, call } = useUpdateCall({ 1333 | functionName: "buy_item" 1334 | }) 1335 | 1336 | useEffect(() => { 1337 | if (hash) { 1338 | call([item, hash]) 1339 | } 1340 | }, [hash]) 1341 | 1342 | if (loading) { 1343 | return
Processing Purchase on ICP...
1344 | } else if (error) { 1345 | return
{error.toString()}
1346 | } else if (data) { 1347 | return ( 1348 |
1349 |

{item} bought!

1350 |
Purchase ID: {data.toString()}
1351 |
1352 | ) 1353 | } else { 1354 | return null 1355 | } 1356 | } 1357 | 1358 | export default VerifyTransaction 1359 | ``` 1360 | 1361 | This component calls the `buy_item` method on the backend to finalize the purchase and display a purchase ID. 1362 | 1363 | ### Update Wallet Component 1364 | 1365 | In your `Wallet.tsx`, replace `` with ``. 1366 | 1367 | ### Testing 1368 | 1369 | 1. **Local Testing**: Run `yarn dev` to test the application locally. 1370 | 1371 | 2. **Deploy to Mainnet**: Run `yarn deploy --network=ic` to deploy the application to the Internet Computer mainnet. 1372 | 1373 | 3. **Live Example**: The live example can be accessed at [https://uu4vt-kqaaa-aaaap-abmia-cai.icp0.io/](https://uu4vt-kqaaa-aaaap-abmia-cai.icp0.io/). 1374 | -------------------------------------------------------------------------------- /assets/approve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/approve.png -------------------------------------------------------------------------------- /assets/balance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/balance.png -------------------------------------------------------------------------------- /assets/candid_verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/candid_verify.png -------------------------------------------------------------------------------- /assets/confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/confirmation.png -------------------------------------------------------------------------------- /assets/connect_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/connect_wallet.png -------------------------------------------------------------------------------- /assets/contract_abi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/contract_abi.png -------------------------------------------------------------------------------- /assets/deposit_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/deposit_input.png -------------------------------------------------------------------------------- /assets/deposit_metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/deposit_metamask.png -------------------------------------------------------------------------------- /assets/deposit_principal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/deposit_principal.png -------------------------------------------------------------------------------- /assets/deposit_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/deposit_result.png -------------------------------------------------------------------------------- /assets/final.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/final.mp4 -------------------------------------------------------------------------------- /assets/final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/final.png -------------------------------------------------------------------------------- /assets/get_items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/get_items.png -------------------------------------------------------------------------------- /assets/json_to_serde.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/json_to_serde.png -------------------------------------------------------------------------------- /assets/logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/logs.png -------------------------------------------------------------------------------- /assets/mainnet_deposit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/mainnet_deposit.png -------------------------------------------------------------------------------- /assets/mainnet_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/mainnet_terminal.png -------------------------------------------------------------------------------- /assets/receipt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/receipt.png -------------------------------------------------------------------------------- /assets/starting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/starting.png -------------------------------------------------------------------------------- /assets/terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/terminal.png -------------------------------------------------------------------------------- /assets/transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/transfer.png -------------------------------------------------------------------------------- /assets/verified_onchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/verified_onchain.png -------------------------------------------------------------------------------- /assets/withdraw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/b3hr4d/eth_payment_tutorial/e411c39d462b826911db21a5437602d2fe8c7a94/assets/withdraw.png -------------------------------------------------------------------------------- /backend/payment/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "payment" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | candid = "0.10" 13 | ic-cdk = "0.15" 14 | serde = { version = "1.0", features = ["derive"] } 15 | b3_utils = { version = "0.11", features = ["ledger", "stable_memory"] } 16 | evm-rpc-canister-types = "1.0" 17 | -------------------------------------------------------------------------------- /backend/payment/payment.did: -------------------------------------------------------------------------------- 1 | type GetTransactionReceiptResult = variant { 2 | Ok : opt TransactionReceipt; 3 | Err : RpcError; 4 | }; 5 | type HttpOutcallError = variant { 6 | IcError : record { code : RejectionCode; message : text }; 7 | InvalidHttpJsonRpcResponse : record { 8 | status : nat16; 9 | body : text; 10 | parsingError : opt text; 11 | }; 12 | }; 13 | type ICRC1TransferError = variant { 14 | GenericError : record { message : text; error_code : nat }; 15 | TemporarilyUnavailable; 16 | BadBurn : record { min_burn_amount : nat }; 17 | Duplicate : record { duplicate_of : nat }; 18 | BadFee : record { expected_fee : nat }; 19 | CreatedInFuture : record { ledger_time : nat64 }; 20 | TooOld; 21 | InsufficientFunds : record { balance : nat }; 22 | }; 23 | type ICRC2ApproveError = variant { 24 | GenericError : record { message : text; error_code : nat }; 25 | TemporarilyUnavailable; 26 | Duplicate : record { duplicate_of : nat }; 27 | BadFee : record { expected_fee : nat }; 28 | AllowanceChanged : record { current_allowance : nat }; 29 | CreatedInFuture : record { ledger_time : nat64 }; 30 | TooOld; 31 | Expired : record { ledger_time : nat64 }; 32 | InsufficientFunds : record { balance : nat }; 33 | }; 34 | type JsonRpcError = record { code : int64; message : text }; 35 | type LogEntry = record { 36 | transactionHash : opt text; 37 | blockNumber : opt nat; 38 | data : text; 39 | blockHash : opt text; 40 | transactionIndex : opt nat; 41 | topics : vec text; 42 | address : text; 43 | logIndex : opt nat; 44 | removed : bool; 45 | }; 46 | type ProviderError = variant { 47 | TooFewCycles : record { expected : nat; received : nat }; 48 | MissingRequiredProvider; 49 | ProviderNotFound; 50 | NoPermission; 51 | }; 52 | type RejectionCode = variant { 53 | NoError; 54 | CanisterError; 55 | SysTransient; 56 | DestinationInvalid; 57 | Unknown; 58 | SysFatal; 59 | CanisterReject; 60 | }; 61 | type Result = variant { Ok : nat; Err : ICRC2ApproveError }; 62 | type Result_1 = variant { Ok : nat; Err : ICRC1TransferError }; 63 | type Result_2 = variant { Ok : RetrieveEthRequest; Err : WithdrawalError }; 64 | type RetrieveEthRequest = record { block_index : nat }; 65 | type RpcError = variant { 66 | JsonRpcError : JsonRpcError; 67 | ProviderError : ProviderError; 68 | ValidationError : ValidationError; 69 | HttpOutcallError : HttpOutcallError; 70 | }; 71 | type TransactionReceipt = record { 72 | to : text; 73 | status : nat; 74 | transactionHash : text; 75 | blockNumber : nat; 76 | from : text; 77 | logs : vec LogEntry; 78 | blockHash : text; 79 | "type" : text; 80 | transactionIndex : nat; 81 | effectiveGasPrice : nat; 82 | logsBloom : text; 83 | contractAddress : opt text; 84 | gasUsed : nat; 85 | }; 86 | type ValidationError = variant { 87 | CredentialPathNotAllowed; 88 | HostNotAllowed : text; 89 | CredentialHeaderNotAllowed; 90 | UrlParseError : text; 91 | Custom : text; 92 | InvalidHex : text; 93 | }; 94 | type VerifiedTransactionDetails = record { from : text; amount : nat }; 95 | type WithdrawalError = variant { 96 | TemporarilyUnavailable : text; 97 | InsufficientAllowance : record { allowance : nat }; 98 | AmountTooLow : record { min_withdrawal_amount : nat }; 99 | InsufficientFunds : record { balance : nat }; 100 | }; 101 | service : { 102 | approve : (nat) -> (Result); 103 | balance : () -> (nat); 104 | buy_item : (text, text) -> (nat64); 105 | canister_deposit_principal : () -> (text) query; 106 | get_items : () -> (vec record { text; nat }) query; 107 | get_receipt : (text) -> (GetTransactionReceiptResult); 108 | get_transaction_list : () -> (vec record { text; text }) query; 109 | set_item : (text, nat) -> (); 110 | transfer : (text, nat) -> (Result_1); 111 | verify_transaction : (text) -> (VerifiedTransactionDetails); 112 | withdraw : (nat, text) -> (Result_2); 113 | } 114 | -------------------------------------------------------------------------------- /backend/payment/src/lib.rs: -------------------------------------------------------------------------------- 1 | use b3_utils::api::{CallCycles, InterCall}; 2 | use b3_utils::{caller_is_controller, hex_string_with_0x_to_nat}; 3 | use b3_utils::{vec_to_hex_string_with_0x, Subaccount}; 4 | use candid::Nat; 5 | 6 | const MINTER_ADDRESS: &str = "0xb44b5e756a894775fc32eddf3314bb1b1944dc34"; 7 | const LEDGER: &str = "apia6-jaaaa-aaaar-qabma-cai"; 8 | const MINTER: &str = "jzenf-aiaaa-aaaar-qaa7q-cai"; 9 | 10 | use b3_utils::memory::init_stable_mem_refcell; 11 | use b3_utils::memory::types::DefaultStableBTreeMap; 12 | use std::cell::RefCell; 13 | 14 | use evm_rpc_canister_types::{ 15 | EthSepoliaService, GetTransactionReceiptResult, MultiGetTransactionReceiptResult, RpcServices, 16 | EVM_RPC, 17 | }; 18 | 19 | thread_local! { 20 | static TRANSACTIONS: RefCell> = init_stable_mem_refcell("trasnactions", 1).unwrap(); 21 | static ITEMS: RefCell> = init_stable_mem_refcell("items", 2).unwrap(); 22 | } 23 | 24 | #[ic_cdk::query] 25 | fn get_transaction_list() -> Vec<(String, String)> { 26 | TRANSACTIONS.with(|t| { 27 | t.borrow() 28 | .iter() 29 | .map(|(k, v)| (k.clone(), v.clone())) 30 | .collect() 31 | }) 32 | } 33 | 34 | #[ic_cdk::update(guard = "caller_is_controller")] 35 | fn set_item(item: String, price: u128) { 36 | ITEMS.with(|p| p.borrow_mut().insert(item, price)); 37 | } 38 | 39 | #[ic_cdk::query] 40 | fn get_items() -> Vec<(String, u128)> { 41 | ITEMS.with(|p| { 42 | p.borrow() 43 | .iter() 44 | .map(|(k, v)| (k.clone(), v.clone())) 45 | .collect() 46 | }) 47 | } 48 | 49 | #[derive(CandidType, Deserialize)] 50 | pub struct VerifiedTransactionDetails { 51 | pub amount: Nat, 52 | pub from: String, 53 | } 54 | 55 | #[ic_cdk::update] 56 | async fn buy_item(item: String, hash: String) -> u64 { 57 | if TRANSACTIONS.with(|t| t.borrow().contains_key(&hash)) { 58 | panic!("Transaction already processed"); 59 | } 60 | 61 | let price = ITEMS.with(|p| { 62 | p.borrow() 63 | .get(&item) 64 | .unwrap_or_else(|| panic!("Item not found")) 65 | .clone() 66 | }); 67 | 68 | let verified_details = verify_transaction(hash.clone()).await; 69 | 70 | let VerifiedTransactionDetails { amount, from } = verified_details; 71 | 72 | if amount < price { 73 | panic!("Amount too low"); 74 | } 75 | 76 | TRANSACTIONS.with(|t| { 77 | let mut t = t.borrow_mut(); 78 | t.insert(hash, from); 79 | 80 | t.len() as u64 81 | }) 82 | } 83 | 84 | // Testing get receipt function 85 | #[ic_cdk::update] 86 | async fn get_receipt(hash: String) -> GetTransactionReceiptResult { 87 | eth_get_transaction_receipt(hash).await.unwrap() 88 | } 89 | 90 | async fn eth_get_transaction_receipt(hash: String) -> Result { 91 | // Make the call to the EVM_RPC canister 92 | let result: Result<(MultiGetTransactionReceiptResult,), String> = EVM_RPC 93 | .eth_get_transaction_receipt( 94 | RpcServices::EthSepolia(Some(vec![ 95 | EthSepoliaService::PublicNode, 96 | EthSepoliaService::BlockPi, 97 | EthSepoliaService::Ankr, 98 | ])), 99 | None, 100 | hash, 101 | 10_000_000_000, 102 | ) 103 | .await 104 | .map_err(|e| format!("Failed to call eth_getTransactionReceipt: {:?}", e)); 105 | 106 | match result { 107 | Ok((MultiGetTransactionReceiptResult::Consistent(receipt),)) => Ok(receipt), 108 | Ok((MultiGetTransactionReceiptResult::Inconsistent(error),)) => Err(format!( 109 | "EVM_RPC returned inconsistent results: {:?}", 110 | error 111 | )), 112 | Err(e) => Err(format!("Error calling EVM_RPC: {}", e)), 113 | } 114 | } 115 | 116 | // Function for verifying the transaction on-chain 117 | #[ic_cdk::update] 118 | async fn verify_transaction(hash: String) -> VerifiedTransactionDetails { 119 | // Get the transaction receipt 120 | let receipt_result = match eth_get_transaction_receipt(hash).await { 121 | Ok(receipt) => receipt, 122 | Err(e) => panic!("Failed to get receipt: {}", e), 123 | }; 124 | 125 | // Ensure the transaction was successful 126 | let receipt = match receipt_result { 127 | GetTransactionReceiptResult::Ok(Some(receipt)) => receipt, 128 | GetTransactionReceiptResult::Ok(None) => panic!("Receipt is None"), 129 | GetTransactionReceiptResult::Err(e) => { 130 | panic!("Error on Get transaction receipt result: {:?}", e) 131 | } 132 | }; 133 | 134 | // Check if the status indicates success (Nat 1) 135 | let success_status = Nat::from(1u8); 136 | if receipt.status != success_status { 137 | panic!("Transaction failed"); 138 | } 139 | 140 | // Verify the 'to' address matches the minter address 141 | if receipt.to != MINTER_ADDRESS { 142 | panic!("Minter address does not match"); 143 | } 144 | 145 | let deposit_principal = canister_deposit_principal(); 146 | 147 | // Verify the principal in the logs matches the deposit principal 148 | let log_principal = receipt 149 | .logs 150 | .iter() 151 | .find(|log| log.topics.get(2).map(|topic| topic.as_str()) == Some(&deposit_principal)) 152 | .unwrap_or_else(|| panic!("Principal not found in logs")); 153 | 154 | // Extract relevant transaction details 155 | let amount = hex_string_with_0x_to_nat(&log_principal.data) 156 | .unwrap_or_else(|e| panic!("Failed to parse amount: {}", e)); 157 | let from_address = receipt.from.clone(); 158 | 159 | VerifiedTransactionDetails { 160 | amount, 161 | from: from_address, 162 | } 163 | } 164 | 165 | #[ic_cdk::query] 166 | fn canister_deposit_principal() -> String { 167 | let subaccount = Subaccount::from(ic_cdk::id()); 168 | 169 | let bytes32 = subaccount.to_bytes32().unwrap(); 170 | 171 | vec_to_hex_string_with_0x(bytes32) 172 | } 173 | // Balance ----------------------------- 174 | use b3_utils::ledger::{ICRCAccount, ICRC1}; 175 | use candid::Principal; 176 | 177 | #[ic_cdk::update] 178 | async fn balance() -> Nat { 179 | let account = ICRCAccount::new(ic_cdk::id(), None); 180 | 181 | ICRC1::from(LEDGER).balance_of(account).await.unwrap() 182 | } 183 | 184 | // Transfer ----------------------------- 185 | use b3_utils::ledger::{ICRC1TransferArgs, ICRC1TransferResult}; 186 | use std::str::FromStr; 187 | 188 | #[ic_cdk::update(guard = "caller_is_controller")] 189 | async fn transfer(to: String, amount: Nat) -> ICRC1TransferResult { 190 | let to = ICRCAccount::from_str(&to).unwrap(); 191 | 192 | let transfer_args = ICRC1TransferArgs { 193 | to, 194 | amount, 195 | from_subaccount: None, 196 | fee: None, 197 | memo: None, 198 | created_at_time: None, 199 | }; 200 | 201 | ICRC1::from(LEDGER).transfer(transfer_args).await.unwrap() 202 | } 203 | 204 | // Approve ----------------------------- 205 | use b3_utils::ledger::{ICRC2ApproveArgs, ICRC2ApproveResult, ICRC2}; 206 | 207 | #[ic_cdk::update(guard = "caller_is_controller")] 208 | async fn approve(amount: Nat) -> ICRC2ApproveResult { 209 | let minter = Principal::from_text(&MINTER).unwrap(); 210 | 211 | let spender = ICRCAccount::from(minter); 212 | 213 | let args = ICRC2ApproveArgs { 214 | amount, 215 | spender, 216 | created_at_time: None, 217 | expected_allowance: None, 218 | expires_at: None, 219 | fee: None, 220 | memo: None, 221 | from_subaccount: None, 222 | }; 223 | 224 | ICRC2::from(LEDGER).approve(args).await.unwrap() 225 | } 226 | // Withdrawal ----------------------------- 227 | use candid::{CandidType, Deserialize}; 228 | 229 | #[derive(CandidType, Deserialize)] 230 | pub struct WithdrawalArg { 231 | pub amount: Nat, 232 | pub recipient: String, 233 | } 234 | 235 | #[derive(CandidType, Deserialize, Clone, Debug)] 236 | pub struct RetrieveEthRequest { 237 | pub block_index: Nat, 238 | } 239 | 240 | #[derive(CandidType, Deserialize, Debug)] 241 | pub enum WithdrawalError { 242 | AmountTooLow { min_withdrawal_amount: Nat }, 243 | InsufficientFunds { balance: Nat }, 244 | InsufficientAllowance { allowance: Nat }, 245 | TemporarilyUnavailable(String), 246 | } 247 | 248 | type WithdrawalResult = Result; 249 | 250 | #[ic_cdk::update(guard = "caller_is_controller")] 251 | async fn withdraw(amount: Nat, recipient: String) -> WithdrawalResult { 252 | let withraw = WithdrawalArg { amount, recipient }; 253 | 254 | InterCall::from(MINTER) 255 | .call("withdraw_eth", withraw, CallCycles::NoPay) 256 | .await 257 | .unwrap() 258 | } 259 | 260 | // Export ----------------------------- 261 | ic_cdk::export_candid!(); 262 | -------------------------------------------------------------------------------- /dfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "canisters": { 3 | "evm_rpc": { 4 | "type": "custom", 5 | "candid": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/latest/download/evm_rpc.did", 6 | "wasm": "https://github.com/internet-computer-protocol/evm-rpc-canister/releases/latest/download/evm_rpc.wasm.gz", 7 | "remote": { 8 | "id": { 9 | "ic": "7hfb6-caaaa-aaaar-qadga-cai" 10 | } 11 | }, 12 | "specified_id": "7hfb6-caaaa-aaaar-qadga-cai", 13 | "init_arg": "(record { nodesInSubnet = 28 })" 14 | }, 15 | "payment": { 16 | "type": "rust", 17 | "candid": "backend/payment/payment.did", 18 | "package": "payment", 19 | "declarations": { 20 | "node_compatibility": true 21 | } 22 | }, 23 | "frontend": { 24 | "dependencies": ["payment"], 25 | "frontend": { 26 | "entrypoint": "out/index.html" 27 | }, 28 | "source": ["out"], 29 | "type": "assets" 30 | } 31 | }, 32 | "output_env_file": ".env.local", 33 | "version": 1 34 | } 35 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | // Adjust the path to load env from ../.env file 2 | const envList = require("dotenv").config({ path: "./.env.local" }).parsed 3 | 4 | // Adjust the path to get version from package.json 5 | const { version } = require("./package.json") 6 | 7 | envList.NEXT_PUBLIC_IC_HOST = 8 | envList.DFX_NETWORK === "ic" ? "https://icp-api.io" : "http://localhost:4943" 9 | 10 | envList.NEXT_PUBLIC_VERSION = version 11 | 12 | /** @type {import('next').NextConfig} */ 13 | module.exports = { 14 | env: envList, 15 | output: "export", 16 | images: { 17 | unoptimized: true 18 | }, 19 | staticPageGenerationTimeout: 10000 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ic-rust-nextjs", 3 | "version": "2.0.0", 4 | "author": "b3hr4d ", 5 | "description": "Internet Computer Rust + NextJS Template", 6 | "keywords": [ 7 | "nextjs", 8 | "rust", 9 | "internet computer", 10 | "icp", 11 | "starter", 12 | "dfinity" 13 | ], 14 | "scripts": { 15 | "install:all": "(yarn -v && yarn || npm install) && npm run ic-wasm:install && npm run candid:install ", 16 | "candid:install": "cargo install candid-extractor", 17 | "ic-wasm:install": "cargo install ic-wasm", 18 | "build": "next build", 19 | "start": "next start", 20 | "export": "next build", 21 | "dev": "next dev", 22 | "dfx:start": "dfx start --background --clean", 23 | "dfx:stop": "dfx stop", 24 | "dfx:build": "sh ./predeploy.sh && dfx build", 25 | "predeploy": "sh ./predeploy.sh", 26 | "deploy": "dfx deploy", 27 | "generate": "dfx generate" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "22.0.3", 31 | "@types/react": "18.3.3", 32 | "dotenv": "16.4.5", 33 | "typescript": "5.5.4" 34 | }, 35 | "dependencies": { 36 | "@ic-reactor/react": "1.8.2", 37 | "@tanstack/query-core": "5.51.17", 38 | "@tanstack/react-query": "5.51.18", 39 | "@web3modal/wagmi": "5.0.11", 40 | "next": "14.2.5", 41 | "react": "18.3.1", 42 | "react-dom": "18.3.1", 43 | "viem": "^2.7.16", 44 | "wagmi": "2.12.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /predeploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eu 3 | 4 | backend_dir="./backend" 5 | target_dir="./target/wasm32-unknown-unknown/release" 6 | 7 | yellow='\033[1;33m' 8 | green='\033[0;32m' 9 | no_color='\033[0m' 10 | 11 | for app_root in "$backend_dir"/*; do 12 | package=$(basename "$app_root") 13 | did_file="$app_root/$package.did" 14 | 15 | echo "${green}Building $package in $app_root${no_color}" 16 | cargo build --manifest-path="$app_root/Cargo.toml" \ 17 | --target wasm32-unknown-unknown \ 18 | --release \ 19 | --package "$package" 20 | echo "Size of $package.wasm: $(ls -lh "$target_dir/$package.wasm" | awk '{print $5}')" 21 | 22 | if command -v candid-extractor >/dev/null 2>&1; then 23 | echo "${green}Generating Candid file for $package${no_color}" 24 | candid-extractor "$target_dir/$package.wasm" 2>/dev/null > "$did_file" 25 | echo "Size of $package.did: $(ls -lh "$did_file" | awk '{print $5}')" 26 | else 27 | echo "${yellow}candid-extractor not found. Skipping generating $package.did.${no_color}" 28 | fi 29 | 30 | # Check if ic-wasm is installed before attempting to shrink the wasm file 31 | if command -v ic-wasm >/dev/null 2>&1; then 32 | # you can install ic-wasm via `cargo install ic-wasm` for smaller wasm files 33 | echo "${green}Shrinking $package.wasm${no_color}" 34 | ic-wasm "$target_dir/$package.wasm" -o "$target_dir/$package.wasm" shrink 35 | echo "Size of shrunk $package.wasm: $(ls -lh "$target_dir/$package.wasm" | awk '{print $5}')" 36 | else 37 | echo "${yellow}ic-wasm not found. Skipping shrinking $package.${no_color}" 38 | fi 39 | 40 | dfx generate "$package" 41 | 42 | done 43 | -------------------------------------------------------------------------------- /public/icp-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/Confirmation.tsx: -------------------------------------------------------------------------------- 1 | import { Hash } from "viem" 2 | import VerifyTransaction from "./VerifyTransaction" 3 | import { useWaitForTransactionReceipt } from "wagmi" 4 | 5 | interface ConfirmationProps { 6 | item: string 7 | hash: Hash 8 | } 9 | 10 | const Confirmation: React.FC = ({ item, hash }) => { 11 | const { data, isError, error, isLoading } = useWaitForTransactionReceipt({ 12 | hash, 13 | confirmations: 2 14 | }) 15 | 16 | if (isError && error) { 17 | return
Transaction error {error.toString()}
18 | } else if (isLoading) { 19 | return
Waiting for confirmation on Ethereum…
20 | } else if (data) { 21 | return 22 | } else { 23 | return null 24 | } 25 | } 26 | 27 | export default Confirmation 28 | -------------------------------------------------------------------------------- /src/components/Item.tsx: -------------------------------------------------------------------------------- 1 | import helperAbi from "service/abi.json" 2 | import { formatEther } from "viem" 3 | import Confirmation from "./Confirmation" 4 | 5 | import styles from "styles/Item.module.css" 6 | import { useWriteContract } from "wagmi" 7 | import { useQueryCall } from "service/payment" 8 | 9 | interface ItemProps { 10 | name: string 11 | price: bigint 12 | } 13 | 14 | const Item: React.FC = ({ name, price }) => { 15 | const { data: canisterDepositAddress } = useQueryCall({ 16 | functionName: "canister_deposit_principal" 17 | }) 18 | 19 | const { data, isPending, writeContract } = useWriteContract() 20 | 21 | const buy = () => { 22 | writeContract({ 23 | address: "0xb44B5e756A894775FC32EDdf3314Bb1B1944dC34", 24 | abi: helperAbi, 25 | functionName: "deposit", 26 | value: price, 27 | args: [canisterDepositAddress] 28 | }) 29 | } 30 | 31 | return ( 32 |
33 | {isPending ? ( 34 |
Buying {name}…
35 | ) : data ? ( 36 | 37 | ) : ( 38 | <> 39 |

{name}

40 |
{formatEther(price).toString()} ETH
41 | 42 | 43 | )} 44 |
45 | ) 46 | } 47 | 48 | export default Item 49 | -------------------------------------------------------------------------------- /src/components/Shop.tsx: -------------------------------------------------------------------------------- 1 | import Item from "./Item" 2 | 3 | import styles from "styles/Shop.module.css" 4 | import { useQueryCall } from "service/payment" 5 | 6 | interface ShopProps {} 7 | 8 | const Shop: React.FC = ({}) => { 9 | const { data, call, loading } = useQueryCall({ functionName: "get_items" }) 10 | 11 | return ( 12 |
13 |
14 |

Items for Sale

15 | 18 |
19 |
20 | {loading ? ( 21 |
Loading...
22 | ) : data && data.length > 0 ? ( 23 | data.map(([name, price]) => { 24 | return 25 | }) 26 | ) : ( 27 |
No items available
28 | )} 29 |
30 |
31 | ) 32 | } 33 | 34 | export default Shop 35 | -------------------------------------------------------------------------------- /src/components/VerifyTransaction.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { useUpdateCall } from "service/payment" 3 | 4 | interface VerifyTransactionProps { 5 | item: string 6 | hash: string 7 | } 8 | 9 | const VerifyTransaction: React.FC = ({ 10 | item, 11 | hash 12 | }) => { 13 | const { loading, error, data, call } = useUpdateCall({ 14 | functionName: "buy_item" 15 | }) 16 | 17 | useEffect(() => { 18 | if (hash) { 19 | call([item, hash]) 20 | } 21 | }, [hash]) 22 | 23 | if (loading) { 24 | return
Processing Purchase on ICP...
25 | } else if (error) { 26 | return
{error.toString()}
27 | } else if (data) { 28 | return ( 29 |
30 |

{item} bought!

31 |
Purchase ID: {data.toString()}
32 |
33 | ) 34 | } else { 35 | return null 36 | } 37 | } 38 | 39 | export default VerifyTransaction 40 | -------------------------------------------------------------------------------- /src/components/Wallet.tsx: -------------------------------------------------------------------------------- 1 | import { useAccount, useConnect, useDisconnect } from "wagmi" 2 | import Shop from "./Shop" 3 | import { injected, walletConnect } from "wagmi/connectors" 4 | import { projectId } from "service/config" 5 | 6 | interface WalletProps {} 7 | 8 | const Wallet: React.FC = ({}) => { 9 | const { address } = useAccount() 10 | 11 | const { disconnect } = useDisconnect() 12 | 13 | const { connect } = useConnect() 14 | 15 | if (address) 16 | return ( 17 |
18 | Connected to: {address} 19 | 20 | 21 |
22 | ) 23 | return ( 24 |
25 | 34 | 51 |
52 | ) 53 | } 54 | 55 | export default Wallet 56 | -------------------------------------------------------------------------------- /src/declarations/evm_rpc/evm_rpc.did: -------------------------------------------------------------------------------- 1 | type Auth = variant { FreeRpc; PriorityRpc; RegisterProvider; Manage }; 2 | type Block = record { 3 | miner : text; 4 | totalDifficulty : nat; 5 | receiptsRoot : text; 6 | stateRoot : text; 7 | hash : text; 8 | difficulty : nat; 9 | size : nat; 10 | uncles : vec text; 11 | baseFeePerGas : nat; 12 | extraData : text; 13 | transactionsRoot : opt text; 14 | sha3Uncles : text; 15 | nonce : nat; 16 | number : nat; 17 | timestamp : nat; 18 | transactions : vec text; 19 | gasLimit : nat; 20 | logsBloom : text; 21 | parentHash : text; 22 | gasUsed : nat; 23 | mixHash : text; 24 | }; 25 | type BlockTag = variant { 26 | Earliest; 27 | Safe; 28 | Finalized; 29 | Latest; 30 | Number : nat; 31 | Pending; 32 | }; 33 | type EthMainnetService = variant { 34 | Alchemy; 35 | Ankr; 36 | BlockPi; 37 | Cloudflare; 38 | PublicNode; 39 | }; 40 | type EthSepoliaService = variant { 41 | Alchemy; 42 | Ankr; 43 | BlockPi; 44 | PublicNode; 45 | }; 46 | type L2MainnetService = variant { 47 | Alchemy; 48 | Ankr; 49 | BlockPi; 50 | PublicNode; 51 | }; 52 | type FeeHistory = record { 53 | reward : vec vec nat; 54 | gasUsedRatio : vec float64; 55 | oldestBlock : nat; 56 | baseFeePerGas : vec nat; 57 | }; 58 | type FeeHistoryArgs = record { 59 | blockCount : nat; 60 | newestBlock : BlockTag; 61 | rewardPercentiles : opt vec nat8; 62 | }; 63 | type GetLogsArgs = record { 64 | fromBlock : opt BlockTag; 65 | toBlock : opt BlockTag; 66 | addresses : vec text; 67 | topics : opt vec Topic; 68 | }; 69 | type GetTransactionCountArgs = record { address : text; block : BlockTag }; 70 | type HttpHeader = record { value : text; name : text }; 71 | type HttpOutcallError = variant { 72 | IcError : record { code : RejectionCode; message : text }; 73 | InvalidHttpJsonRpcResponse : record { 74 | status : nat16; 75 | body : text; 76 | parsingError : opt text; 77 | }; 78 | }; 79 | type InitArgs = record { 80 | nodesInSubnet : nat32; 81 | }; 82 | type JsonRpcError = record { code : int64; message : text }; 83 | type LogEntry = record { 84 | transactionHash : opt text; 85 | blockNumber : opt nat; 86 | data : text; 87 | blockHash : opt text; 88 | transactionIndex : opt nat; 89 | topics : vec text; 90 | address : text; 91 | logIndex : opt nat; 92 | removed : bool; 93 | }; 94 | type ManageProviderArgs = record { 95 | providerId : nat64; 96 | "service" : opt RpcService; 97 | primary : opt bool; 98 | }; 99 | type Metrics = record { 100 | requests : vec record { record { text; text }; nat64 }; 101 | responses : vec record { record { text; text; text }; nat64 }; 102 | inconsistentResponses : vec record { record { text; text }; nat64 }; 103 | cyclesCharged : vec record { record { text; text }; nat }; 104 | cyclesWithdrawn : nat; 105 | errNoPermission : nat64; 106 | errHttpOutcall : vec record { record { text; text }; nat64 }; 107 | errHostNotAllowed : vec record { text; nat64 }; 108 | }; 109 | type MultiFeeHistoryResult = variant { 110 | Consistent : FeeHistoryResult; 111 | Inconsistent : vec record { RpcService; FeeHistoryResult }; 112 | }; 113 | type MultiGetBlockByNumberResult = variant { 114 | Consistent : GetBlockByNumberResult; 115 | Inconsistent : vec record { RpcService; GetBlockByNumberResult }; 116 | }; 117 | type MultiGetLogsResult = variant { 118 | Consistent : GetLogsResult; 119 | Inconsistent : vec record { RpcService; GetLogsResult }; 120 | }; 121 | type MultiGetTransactionCountResult = variant { 122 | Consistent : GetTransactionCountResult; 123 | Inconsistent : vec record { RpcService; GetTransactionCountResult }; 124 | }; 125 | type MultiGetTransactionReceiptResult = variant { 126 | Consistent : GetTransactionReceiptResult; 127 | Inconsistent : vec record { RpcService; GetTransactionReceiptResult }; 128 | }; 129 | type MultiSendRawTransactionResult = variant { 130 | Consistent : SendRawTransactionResult; 131 | Inconsistent : vec record { RpcService; SendRawTransactionResult }; 132 | }; 133 | type ProviderError = variant { 134 | TooFewCycles : record { expected : nat; received : nat }; 135 | MissingRequiredProvider; 136 | ProviderNotFound; 137 | NoPermission; 138 | }; 139 | type ProviderId = nat64; 140 | type ProviderView = record { 141 | cyclesPerCall : nat64; 142 | owner : principal; 143 | hostname : text; 144 | primary : bool; 145 | chainId : nat64; 146 | cyclesPerMessageByte : nat64; 147 | providerId : nat64; 148 | }; 149 | type RegisterProviderArgs = record { 150 | cyclesPerCall : nat64; 151 | credentialPath : text; 152 | hostname : text; 153 | credentialHeaders : opt vec HttpHeader; 154 | chainId : nat64; 155 | cyclesPerMessageByte : nat64; 156 | }; 157 | type RejectionCode = variant { 158 | NoError; 159 | CanisterError; 160 | SysTransient; 161 | DestinationInvalid; 162 | Unknown; 163 | SysFatal; 164 | CanisterReject; 165 | }; 166 | type FeeHistoryResult = variant { Ok : opt FeeHistory; Err : RpcError }; 167 | type GetBlockByNumberResult = variant { Ok : Block; Err : RpcError }; 168 | type GetLogsResult = variant { Ok : vec LogEntry; Err : RpcError }; 169 | type GetTransactionCountResult = variant { Ok : nat; Err : RpcError }; 170 | type GetTransactionReceiptResult = variant { 171 | Ok : opt TransactionReceipt; 172 | Err : RpcError; 173 | }; 174 | type SendRawTransactionResult = variant { 175 | Ok : SendRawTransactionStatus; 176 | Err : RpcError; 177 | }; 178 | type RequestResult = variant { Ok : text; Err : RpcError }; 179 | type RequestCostResult = variant { Ok : nat; Err : RpcError }; 180 | type RpcConfig = record { responseSizeEstimate : opt nat64 }; 181 | type RpcError = variant { 182 | JsonRpcError : JsonRpcError; 183 | ProviderError : ProviderError; 184 | ValidationError : ValidationError; 185 | HttpOutcallError : HttpOutcallError; 186 | }; 187 | type RpcApi = record { url : text; headers : opt vec HttpHeader }; 188 | type RpcService = variant { 189 | Chain : nat64; 190 | Provider : nat64; 191 | Custom : RpcApi; 192 | EthSepolia : EthSepoliaService; 193 | EthMainnet : EthMainnetService; 194 | ArbitrumOne : L2MainnetService; 195 | BaseMainnet : L2MainnetService; 196 | OptimismMainnet : L2MainnetService; 197 | }; 198 | type RpcServices = variant { 199 | Custom : record { 200 | chainId : nat64; 201 | services : vec RpcApi; 202 | }; 203 | EthSepolia : opt vec EthSepoliaService; 204 | EthMainnet : opt vec EthMainnetService; 205 | ArbitrumOne : opt vec L2MainnetService; 206 | BaseMainnet : opt vec L2MainnetService; 207 | OptimismMainnet : opt vec L2MainnetService; 208 | }; 209 | type SendRawTransactionStatus = variant { 210 | Ok : opt text; 211 | NonceTooLow; 212 | NonceTooHigh; 213 | InsufficientFunds; 214 | }; 215 | // Each topic is a `vec text` of topic data composed with the "or" operator. 216 | // See https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getlogs 217 | type Topic = vec text; 218 | type TransactionReceipt = record { 219 | to : text; 220 | status : nat; 221 | transactionHash : text; 222 | blockNumber : nat; 223 | from : text; 224 | logs : vec LogEntry; 225 | blockHash : text; 226 | "type" : text; 227 | transactionIndex : nat; 228 | effectiveGasPrice : nat; 229 | logsBloom : text; 230 | contractAddress : opt text; 231 | gasUsed : nat; 232 | }; 233 | type UpdateProviderArgs = record { 234 | cyclesPerCall : opt nat64; 235 | credentialPath : opt text; 236 | hostname : opt text; 237 | credentialHeaders : opt vec HttpHeader; 238 | primary : opt bool; 239 | cyclesPerMessageByte : opt nat64; 240 | providerId : nat64; 241 | }; 242 | type ValidationError = variant { 243 | Custom : text; 244 | HostNotAllowed : text; 245 | UrlParseError : text; 246 | InvalidHex : text; 247 | CredentialPathNotAllowed; 248 | CredentialHeaderNotAllowed; 249 | }; 250 | service : (InitArgs) -> { 251 | authorize : (principal, Auth) -> (success : bool); 252 | deauthorize : (principal, Auth) -> (success : bool); 253 | eth_feeHistory : (RpcServices, opt RpcConfig, FeeHistoryArgs) -> (MultiFeeHistoryResult); 254 | eth_getBlockByNumber : (RpcServices, opt RpcConfig, BlockTag) -> (MultiGetBlockByNumberResult); 255 | eth_getLogs : (RpcServices, opt RpcConfig, GetLogsArgs) -> (MultiGetLogsResult); 256 | eth_getTransactionCount : (RpcServices, opt RpcConfig, GetTransactionCountArgs) -> ( 257 | MultiGetTransactionCountResult 258 | ); 259 | eth_getTransactionReceipt : (RpcServices, opt RpcConfig, hash : text) -> (MultiGetTransactionReceiptResult); 260 | eth_sendRawTransaction : (RpcServices, opt RpcConfig, rawSignedTransactionHex : text) -> (MultiSendRawTransactionResult); 261 | getAccumulatedCycleCount : (ProviderId) -> (cycles : nat) query; 262 | getAuthorized : (Auth) -> (vec principal) query; 263 | getMetrics : () -> (Metrics) query; 264 | getNodesInSubnet : () -> (numberOfNodes : nat32) query; 265 | getOpenRpcAccess : () -> (active : bool) query; 266 | getProviders : () -> (vec ProviderView) query; 267 | getServiceProviderMap : () -> (vec record { RpcService; nat64 }) query; 268 | manageProvider : (ManageProviderArgs) -> (); 269 | registerProvider : (RegisterProviderArgs) -> (nat64); 270 | request : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestResult); 271 | requestCost : (RpcService, json : text, maxResponseBytes : nat64) -> (RequestCostResult) query; 272 | setOpenRpcAccess : (active : bool) -> (); 273 | unregisterProvider : (ProviderId) -> (bool); 274 | updateProvider : (UpdateProviderArgs) -> (); 275 | withdrawAccumulatedCycles : (ProviderId, recipient : principal) -> (); 276 | }; 277 | -------------------------------------------------------------------------------- /src/declarations/evm_rpc/evm_rpc.did.d.ts: -------------------------------------------------------------------------------- 1 | import type { Principal } from '@dfinity/principal'; 2 | import type { ActorMethod } from '@dfinity/agent'; 3 | import type { IDL } from '@dfinity/candid'; 4 | 5 | export type Auth = { 'RegisterProvider' : null } | 6 | { 'FreeRpc' : null } | 7 | { 'PriorityRpc' : null } | 8 | { 'Manage' : null }; 9 | export interface Block { 10 | 'miner' : string, 11 | 'totalDifficulty' : bigint, 12 | 'receiptsRoot' : string, 13 | 'stateRoot' : string, 14 | 'hash' : string, 15 | 'difficulty' : bigint, 16 | 'size' : bigint, 17 | 'uncles' : Array, 18 | 'baseFeePerGas' : bigint, 19 | 'extraData' : string, 20 | 'transactionsRoot' : [] | [string], 21 | 'sha3Uncles' : string, 22 | 'nonce' : bigint, 23 | 'number' : bigint, 24 | 'timestamp' : bigint, 25 | 'transactions' : Array, 26 | 'gasLimit' : bigint, 27 | 'logsBloom' : string, 28 | 'parentHash' : string, 29 | 'gasUsed' : bigint, 30 | 'mixHash' : string, 31 | } 32 | export type BlockTag = { 'Earliest' : null } | 33 | { 'Safe' : null } | 34 | { 'Finalized' : null } | 35 | { 'Latest' : null } | 36 | { 'Number' : bigint } | 37 | { 'Pending' : null }; 38 | export type EthMainnetService = { 'Alchemy' : null } | 39 | { 'BlockPi' : null } | 40 | { 'Cloudflare' : null } | 41 | { 'PublicNode' : null } | 42 | { 'Ankr' : null }; 43 | export type EthSepoliaService = { 'Alchemy' : null } | 44 | { 'BlockPi' : null } | 45 | { 'PublicNode' : null } | 46 | { 'Ankr' : null }; 47 | export interface FeeHistory { 48 | 'reward' : Array>, 49 | 'gasUsedRatio' : Array, 50 | 'oldestBlock' : bigint, 51 | 'baseFeePerGas' : Array, 52 | } 53 | export interface FeeHistoryArgs { 54 | 'blockCount' : bigint, 55 | 'newestBlock' : BlockTag, 56 | 'rewardPercentiles' : [] | [Uint8Array | number[]], 57 | } 58 | export type FeeHistoryResult = { 'Ok' : [] | [FeeHistory] } | 59 | { 'Err' : RpcError }; 60 | export type GetBlockByNumberResult = { 'Ok' : Block } | 61 | { 'Err' : RpcError }; 62 | export interface GetLogsArgs { 63 | 'fromBlock' : [] | [BlockTag], 64 | 'toBlock' : [] | [BlockTag], 65 | 'addresses' : Array, 66 | 'topics' : [] | [Array], 67 | } 68 | export type GetLogsResult = { 'Ok' : Array } | 69 | { 'Err' : RpcError }; 70 | export interface GetTransactionCountArgs { 71 | 'address' : string, 72 | 'block' : BlockTag, 73 | } 74 | export type GetTransactionCountResult = { 'Ok' : bigint } | 75 | { 'Err' : RpcError }; 76 | export type GetTransactionReceiptResult = { 'Ok' : [] | [TransactionReceipt] } | 77 | { 'Err' : RpcError }; 78 | export interface HttpHeader { 'value' : string, 'name' : string } 79 | export type HttpOutcallError = { 80 | 'IcError' : { 'code' : RejectionCode, 'message' : string } 81 | } | 82 | { 83 | 'InvalidHttpJsonRpcResponse' : { 84 | 'status' : number, 85 | 'body' : string, 86 | 'parsingError' : [] | [string], 87 | } 88 | }; 89 | export interface InitArgs { 'nodesInSubnet' : number } 90 | export interface JsonRpcError { 'code' : bigint, 'message' : string } 91 | export type L2MainnetService = { 'Alchemy' : null } | 92 | { 'BlockPi' : null } | 93 | { 'PublicNode' : null } | 94 | { 'Ankr' : null }; 95 | export interface LogEntry { 96 | 'transactionHash' : [] | [string], 97 | 'blockNumber' : [] | [bigint], 98 | 'data' : string, 99 | 'blockHash' : [] | [string], 100 | 'transactionIndex' : [] | [bigint], 101 | 'topics' : Array, 102 | 'address' : string, 103 | 'logIndex' : [] | [bigint], 104 | 'removed' : boolean, 105 | } 106 | export interface ManageProviderArgs { 107 | 'service' : [] | [RpcService], 108 | 'primary' : [] | [boolean], 109 | 'providerId' : bigint, 110 | } 111 | export interface Metrics { 112 | 'cyclesWithdrawn' : bigint, 113 | 'responses' : Array<[[string, string, string], bigint]>, 114 | 'errNoPermission' : bigint, 115 | 'inconsistentResponses' : Array<[[string, string], bigint]>, 116 | 'cyclesCharged' : Array<[[string, string], bigint]>, 117 | 'requests' : Array<[[string, string], bigint]>, 118 | 'errHttpOutcall' : Array<[[string, string], bigint]>, 119 | 'errHostNotAllowed' : Array<[string, bigint]>, 120 | } 121 | export type MultiFeeHistoryResult = { 'Consistent' : FeeHistoryResult } | 122 | { 'Inconsistent' : Array<[RpcService, FeeHistoryResult]> }; 123 | export type MultiGetBlockByNumberResult = { 124 | 'Consistent' : GetBlockByNumberResult 125 | } | 126 | { 'Inconsistent' : Array<[RpcService, GetBlockByNumberResult]> }; 127 | export type MultiGetLogsResult = { 'Consistent' : GetLogsResult } | 128 | { 'Inconsistent' : Array<[RpcService, GetLogsResult]> }; 129 | export type MultiGetTransactionCountResult = { 130 | 'Consistent' : GetTransactionCountResult 131 | } | 132 | { 'Inconsistent' : Array<[RpcService, GetTransactionCountResult]> }; 133 | export type MultiGetTransactionReceiptResult = { 134 | 'Consistent' : GetTransactionReceiptResult 135 | } | 136 | { 'Inconsistent' : Array<[RpcService, GetTransactionReceiptResult]> }; 137 | export type MultiSendRawTransactionResult = { 138 | 'Consistent' : SendRawTransactionResult 139 | } | 140 | { 'Inconsistent' : Array<[RpcService, SendRawTransactionResult]> }; 141 | export type ProviderError = { 142 | 'TooFewCycles' : { 'expected' : bigint, 'received' : bigint } 143 | } | 144 | { 'MissingRequiredProvider' : null } | 145 | { 'ProviderNotFound' : null } | 146 | { 'NoPermission' : null }; 147 | export type ProviderId = bigint; 148 | export interface ProviderView { 149 | 'cyclesPerCall' : bigint, 150 | 'owner' : Principal, 151 | 'hostname' : string, 152 | 'primary' : boolean, 153 | 'chainId' : bigint, 154 | 'cyclesPerMessageByte' : bigint, 155 | 'providerId' : bigint, 156 | } 157 | export interface RegisterProviderArgs { 158 | 'cyclesPerCall' : bigint, 159 | 'credentialPath' : string, 160 | 'hostname' : string, 161 | 'credentialHeaders' : [] | [Array], 162 | 'chainId' : bigint, 163 | 'cyclesPerMessageByte' : bigint, 164 | } 165 | export type RejectionCode = { 'NoError' : null } | 166 | { 'CanisterError' : null } | 167 | { 'SysTransient' : null } | 168 | { 'DestinationInvalid' : null } | 169 | { 'Unknown' : null } | 170 | { 'SysFatal' : null } | 171 | { 'CanisterReject' : null }; 172 | export type RequestCostResult = { 'Ok' : bigint } | 173 | { 'Err' : RpcError }; 174 | export type RequestResult = { 'Ok' : string } | 175 | { 'Err' : RpcError }; 176 | export interface RpcApi { 'url' : string, 'headers' : [] | [Array] } 177 | export interface RpcConfig { 'responseSizeEstimate' : [] | [bigint] } 178 | export type RpcError = { 'JsonRpcError' : JsonRpcError } | 179 | { 'ProviderError' : ProviderError } | 180 | { 'ValidationError' : ValidationError } | 181 | { 'HttpOutcallError' : HttpOutcallError }; 182 | export type RpcService = { 'EthSepolia' : EthSepoliaService } | 183 | { 'BaseMainnet' : L2MainnetService } | 184 | { 'Custom' : RpcApi } | 185 | { 'OptimismMainnet' : L2MainnetService } | 186 | { 'ArbitrumOne' : L2MainnetService } | 187 | { 'EthMainnet' : EthMainnetService } | 188 | { 'Chain' : bigint } | 189 | { 'Provider' : bigint }; 190 | export type RpcServices = { 'EthSepolia' : [] | [Array] } | 191 | { 'BaseMainnet' : [] | [Array] } | 192 | { 'Custom' : { 'chainId' : bigint, 'services' : Array } } | 193 | { 'OptimismMainnet' : [] | [Array] } | 194 | { 'ArbitrumOne' : [] | [Array] } | 195 | { 'EthMainnet' : [] | [Array] }; 196 | export type SendRawTransactionResult = { 'Ok' : SendRawTransactionStatus } | 197 | { 'Err' : RpcError }; 198 | export type SendRawTransactionStatus = { 'Ok' : [] | [string] } | 199 | { 'NonceTooLow' : null } | 200 | { 'NonceTooHigh' : null } | 201 | { 'InsufficientFunds' : null }; 202 | export type Topic = Array; 203 | export interface TransactionReceipt { 204 | 'to' : string, 205 | 'status' : bigint, 206 | 'transactionHash' : string, 207 | 'blockNumber' : bigint, 208 | 'from' : string, 209 | 'logs' : Array, 210 | 'blockHash' : string, 211 | 'type' : string, 212 | 'transactionIndex' : bigint, 213 | 'effectiveGasPrice' : bigint, 214 | 'logsBloom' : string, 215 | 'contractAddress' : [] | [string], 216 | 'gasUsed' : bigint, 217 | } 218 | export interface UpdateProviderArgs { 219 | 'cyclesPerCall' : [] | [bigint], 220 | 'credentialPath' : [] | [string], 221 | 'hostname' : [] | [string], 222 | 'credentialHeaders' : [] | [Array], 223 | 'primary' : [] | [boolean], 224 | 'cyclesPerMessageByte' : [] | [bigint], 225 | 'providerId' : bigint, 226 | } 227 | export type ValidationError = { 'CredentialPathNotAllowed' : null } | 228 | { 'HostNotAllowed' : string } | 229 | { 'CredentialHeaderNotAllowed' : null } | 230 | { 'UrlParseError' : string } | 231 | { 'Custom' : string } | 232 | { 'InvalidHex' : string }; 233 | export interface _SERVICE { 234 | 'authorize' : ActorMethod<[Principal, Auth], boolean>, 235 | 'deauthorize' : ActorMethod<[Principal, Auth], boolean>, 236 | 'eth_feeHistory' : ActorMethod< 237 | [RpcServices, [] | [RpcConfig], FeeHistoryArgs], 238 | MultiFeeHistoryResult 239 | >, 240 | 'eth_getBlockByNumber' : ActorMethod< 241 | [RpcServices, [] | [RpcConfig], BlockTag], 242 | MultiGetBlockByNumberResult 243 | >, 244 | 'eth_getLogs' : ActorMethod< 245 | [RpcServices, [] | [RpcConfig], GetLogsArgs], 246 | MultiGetLogsResult 247 | >, 248 | 'eth_getTransactionCount' : ActorMethod< 249 | [RpcServices, [] | [RpcConfig], GetTransactionCountArgs], 250 | MultiGetTransactionCountResult 251 | >, 252 | 'eth_getTransactionReceipt' : ActorMethod< 253 | [RpcServices, [] | [RpcConfig], string], 254 | MultiGetTransactionReceiptResult 255 | >, 256 | 'eth_sendRawTransaction' : ActorMethod< 257 | [RpcServices, [] | [RpcConfig], string], 258 | MultiSendRawTransactionResult 259 | >, 260 | 'getAccumulatedCycleCount' : ActorMethod<[ProviderId], bigint>, 261 | 'getAuthorized' : ActorMethod<[Auth], Array>, 262 | 'getMetrics' : ActorMethod<[], Metrics>, 263 | 'getNodesInSubnet' : ActorMethod<[], number>, 264 | 'getOpenRpcAccess' : ActorMethod<[], boolean>, 265 | 'getProviders' : ActorMethod<[], Array>, 266 | 'getServiceProviderMap' : ActorMethod<[], Array<[RpcService, bigint]>>, 267 | 'manageProvider' : ActorMethod<[ManageProviderArgs], undefined>, 268 | 'registerProvider' : ActorMethod<[RegisterProviderArgs], bigint>, 269 | 'request' : ActorMethod<[RpcService, string, bigint], RequestResult>, 270 | 'requestCost' : ActorMethod<[RpcService, string, bigint], RequestCostResult>, 271 | 'setOpenRpcAccess' : ActorMethod<[boolean], undefined>, 272 | 'unregisterProvider' : ActorMethod<[ProviderId], boolean>, 273 | 'updateProvider' : ActorMethod<[UpdateProviderArgs], undefined>, 274 | 'withdrawAccumulatedCycles' : ActorMethod<[ProviderId, Principal], undefined>, 275 | } 276 | export declare const idlFactory: IDL.InterfaceFactory; 277 | export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; 278 | -------------------------------------------------------------------------------- /src/declarations/evm_rpc/evm_rpc.did.js: -------------------------------------------------------------------------------- 1 | export const idlFactory = ({ IDL }) => { 2 | const InitArgs = IDL.Record({ 'nodesInSubnet' : IDL.Nat32 }); 3 | const Auth = IDL.Variant({ 4 | 'RegisterProvider' : IDL.Null, 5 | 'FreeRpc' : IDL.Null, 6 | 'PriorityRpc' : IDL.Null, 7 | 'Manage' : IDL.Null, 8 | }); 9 | const EthSepoliaService = IDL.Variant({ 10 | 'Alchemy' : IDL.Null, 11 | 'BlockPi' : IDL.Null, 12 | 'PublicNode' : IDL.Null, 13 | 'Ankr' : IDL.Null, 14 | }); 15 | const L2MainnetService = IDL.Variant({ 16 | 'Alchemy' : IDL.Null, 17 | 'BlockPi' : IDL.Null, 18 | 'PublicNode' : IDL.Null, 19 | 'Ankr' : IDL.Null, 20 | }); 21 | const HttpHeader = IDL.Record({ 'value' : IDL.Text, 'name' : IDL.Text }); 22 | const RpcApi = IDL.Record({ 23 | 'url' : IDL.Text, 24 | 'headers' : IDL.Opt(IDL.Vec(HttpHeader)), 25 | }); 26 | const EthMainnetService = IDL.Variant({ 27 | 'Alchemy' : IDL.Null, 28 | 'BlockPi' : IDL.Null, 29 | 'Cloudflare' : IDL.Null, 30 | 'PublicNode' : IDL.Null, 31 | 'Ankr' : IDL.Null, 32 | }); 33 | const RpcServices = IDL.Variant({ 34 | 'EthSepolia' : IDL.Opt(IDL.Vec(EthSepoliaService)), 35 | 'BaseMainnet' : IDL.Opt(IDL.Vec(L2MainnetService)), 36 | 'Custom' : IDL.Record({ 37 | 'chainId' : IDL.Nat64, 38 | 'services' : IDL.Vec(RpcApi), 39 | }), 40 | 'OptimismMainnet' : IDL.Opt(IDL.Vec(L2MainnetService)), 41 | 'ArbitrumOne' : IDL.Opt(IDL.Vec(L2MainnetService)), 42 | 'EthMainnet' : IDL.Opt(IDL.Vec(EthMainnetService)), 43 | }); 44 | const RpcConfig = IDL.Record({ 'responseSizeEstimate' : IDL.Opt(IDL.Nat64) }); 45 | const BlockTag = IDL.Variant({ 46 | 'Earliest' : IDL.Null, 47 | 'Safe' : IDL.Null, 48 | 'Finalized' : IDL.Null, 49 | 'Latest' : IDL.Null, 50 | 'Number' : IDL.Nat, 51 | 'Pending' : IDL.Null, 52 | }); 53 | const FeeHistoryArgs = IDL.Record({ 54 | 'blockCount' : IDL.Nat, 55 | 'newestBlock' : BlockTag, 56 | 'rewardPercentiles' : IDL.Opt(IDL.Vec(IDL.Nat8)), 57 | }); 58 | const FeeHistory = IDL.Record({ 59 | 'reward' : IDL.Vec(IDL.Vec(IDL.Nat)), 60 | 'gasUsedRatio' : IDL.Vec(IDL.Float64), 61 | 'oldestBlock' : IDL.Nat, 62 | 'baseFeePerGas' : IDL.Vec(IDL.Nat), 63 | }); 64 | const JsonRpcError = IDL.Record({ 'code' : IDL.Int64, 'message' : IDL.Text }); 65 | const ProviderError = IDL.Variant({ 66 | 'TooFewCycles' : IDL.Record({ 'expected' : IDL.Nat, 'received' : IDL.Nat }), 67 | 'MissingRequiredProvider' : IDL.Null, 68 | 'ProviderNotFound' : IDL.Null, 69 | 'NoPermission' : IDL.Null, 70 | }); 71 | const ValidationError = IDL.Variant({ 72 | 'CredentialPathNotAllowed' : IDL.Null, 73 | 'HostNotAllowed' : IDL.Text, 74 | 'CredentialHeaderNotAllowed' : IDL.Null, 75 | 'UrlParseError' : IDL.Text, 76 | 'Custom' : IDL.Text, 77 | 'InvalidHex' : IDL.Text, 78 | }); 79 | const RejectionCode = IDL.Variant({ 80 | 'NoError' : IDL.Null, 81 | 'CanisterError' : IDL.Null, 82 | 'SysTransient' : IDL.Null, 83 | 'DestinationInvalid' : IDL.Null, 84 | 'Unknown' : IDL.Null, 85 | 'SysFatal' : IDL.Null, 86 | 'CanisterReject' : IDL.Null, 87 | }); 88 | const HttpOutcallError = IDL.Variant({ 89 | 'IcError' : IDL.Record({ 'code' : RejectionCode, 'message' : IDL.Text }), 90 | 'InvalidHttpJsonRpcResponse' : IDL.Record({ 91 | 'status' : IDL.Nat16, 92 | 'body' : IDL.Text, 93 | 'parsingError' : IDL.Opt(IDL.Text), 94 | }), 95 | }); 96 | const RpcError = IDL.Variant({ 97 | 'JsonRpcError' : JsonRpcError, 98 | 'ProviderError' : ProviderError, 99 | 'ValidationError' : ValidationError, 100 | 'HttpOutcallError' : HttpOutcallError, 101 | }); 102 | const FeeHistoryResult = IDL.Variant({ 103 | 'Ok' : IDL.Opt(FeeHistory), 104 | 'Err' : RpcError, 105 | }); 106 | const RpcService = IDL.Variant({ 107 | 'EthSepolia' : EthSepoliaService, 108 | 'BaseMainnet' : L2MainnetService, 109 | 'Custom' : RpcApi, 110 | 'OptimismMainnet' : L2MainnetService, 111 | 'ArbitrumOne' : L2MainnetService, 112 | 'EthMainnet' : EthMainnetService, 113 | 'Chain' : IDL.Nat64, 114 | 'Provider' : IDL.Nat64, 115 | }); 116 | const MultiFeeHistoryResult = IDL.Variant({ 117 | 'Consistent' : FeeHistoryResult, 118 | 'Inconsistent' : IDL.Vec(IDL.Tuple(RpcService, FeeHistoryResult)), 119 | }); 120 | const Block = IDL.Record({ 121 | 'miner' : IDL.Text, 122 | 'totalDifficulty' : IDL.Nat, 123 | 'receiptsRoot' : IDL.Text, 124 | 'stateRoot' : IDL.Text, 125 | 'hash' : IDL.Text, 126 | 'difficulty' : IDL.Nat, 127 | 'size' : IDL.Nat, 128 | 'uncles' : IDL.Vec(IDL.Text), 129 | 'baseFeePerGas' : IDL.Nat, 130 | 'extraData' : IDL.Text, 131 | 'transactionsRoot' : IDL.Opt(IDL.Text), 132 | 'sha3Uncles' : IDL.Text, 133 | 'nonce' : IDL.Nat, 134 | 'number' : IDL.Nat, 135 | 'timestamp' : IDL.Nat, 136 | 'transactions' : IDL.Vec(IDL.Text), 137 | 'gasLimit' : IDL.Nat, 138 | 'logsBloom' : IDL.Text, 139 | 'parentHash' : IDL.Text, 140 | 'gasUsed' : IDL.Nat, 141 | 'mixHash' : IDL.Text, 142 | }); 143 | const GetBlockByNumberResult = IDL.Variant({ 144 | 'Ok' : Block, 145 | 'Err' : RpcError, 146 | }); 147 | const MultiGetBlockByNumberResult = IDL.Variant({ 148 | 'Consistent' : GetBlockByNumberResult, 149 | 'Inconsistent' : IDL.Vec(IDL.Tuple(RpcService, GetBlockByNumberResult)), 150 | }); 151 | const Topic = IDL.Vec(IDL.Text); 152 | const GetLogsArgs = IDL.Record({ 153 | 'fromBlock' : IDL.Opt(BlockTag), 154 | 'toBlock' : IDL.Opt(BlockTag), 155 | 'addresses' : IDL.Vec(IDL.Text), 156 | 'topics' : IDL.Opt(IDL.Vec(Topic)), 157 | }); 158 | const LogEntry = IDL.Record({ 159 | 'transactionHash' : IDL.Opt(IDL.Text), 160 | 'blockNumber' : IDL.Opt(IDL.Nat), 161 | 'data' : IDL.Text, 162 | 'blockHash' : IDL.Opt(IDL.Text), 163 | 'transactionIndex' : IDL.Opt(IDL.Nat), 164 | 'topics' : IDL.Vec(IDL.Text), 165 | 'address' : IDL.Text, 166 | 'logIndex' : IDL.Opt(IDL.Nat), 167 | 'removed' : IDL.Bool, 168 | }); 169 | const GetLogsResult = IDL.Variant({ 170 | 'Ok' : IDL.Vec(LogEntry), 171 | 'Err' : RpcError, 172 | }); 173 | const MultiGetLogsResult = IDL.Variant({ 174 | 'Consistent' : GetLogsResult, 175 | 'Inconsistent' : IDL.Vec(IDL.Tuple(RpcService, GetLogsResult)), 176 | }); 177 | const GetTransactionCountArgs = IDL.Record({ 178 | 'address' : IDL.Text, 179 | 'block' : BlockTag, 180 | }); 181 | const GetTransactionCountResult = IDL.Variant({ 182 | 'Ok' : IDL.Nat, 183 | 'Err' : RpcError, 184 | }); 185 | const MultiGetTransactionCountResult = IDL.Variant({ 186 | 'Consistent' : GetTransactionCountResult, 187 | 'Inconsistent' : IDL.Vec(IDL.Tuple(RpcService, GetTransactionCountResult)), 188 | }); 189 | const TransactionReceipt = IDL.Record({ 190 | 'to' : IDL.Text, 191 | 'status' : IDL.Nat, 192 | 'transactionHash' : IDL.Text, 193 | 'blockNumber' : IDL.Nat, 194 | 'from' : IDL.Text, 195 | 'logs' : IDL.Vec(LogEntry), 196 | 'blockHash' : IDL.Text, 197 | 'type' : IDL.Text, 198 | 'transactionIndex' : IDL.Nat, 199 | 'effectiveGasPrice' : IDL.Nat, 200 | 'logsBloom' : IDL.Text, 201 | 'contractAddress' : IDL.Opt(IDL.Text), 202 | 'gasUsed' : IDL.Nat, 203 | }); 204 | const GetTransactionReceiptResult = IDL.Variant({ 205 | 'Ok' : IDL.Opt(TransactionReceipt), 206 | 'Err' : RpcError, 207 | }); 208 | const MultiGetTransactionReceiptResult = IDL.Variant({ 209 | 'Consistent' : GetTransactionReceiptResult, 210 | 'Inconsistent' : IDL.Vec( 211 | IDL.Tuple(RpcService, GetTransactionReceiptResult) 212 | ), 213 | }); 214 | const SendRawTransactionStatus = IDL.Variant({ 215 | 'Ok' : IDL.Opt(IDL.Text), 216 | 'NonceTooLow' : IDL.Null, 217 | 'NonceTooHigh' : IDL.Null, 218 | 'InsufficientFunds' : IDL.Null, 219 | }); 220 | const SendRawTransactionResult = IDL.Variant({ 221 | 'Ok' : SendRawTransactionStatus, 222 | 'Err' : RpcError, 223 | }); 224 | const MultiSendRawTransactionResult = IDL.Variant({ 225 | 'Consistent' : SendRawTransactionResult, 226 | 'Inconsistent' : IDL.Vec(IDL.Tuple(RpcService, SendRawTransactionResult)), 227 | }); 228 | const ProviderId = IDL.Nat64; 229 | const Metrics = IDL.Record({ 230 | 'cyclesWithdrawn' : IDL.Nat, 231 | 'responses' : IDL.Vec( 232 | IDL.Tuple(IDL.Tuple(IDL.Text, IDL.Text, IDL.Text), IDL.Nat64) 233 | ), 234 | 'errNoPermission' : IDL.Nat64, 235 | 'inconsistentResponses' : IDL.Vec( 236 | IDL.Tuple(IDL.Tuple(IDL.Text, IDL.Text), IDL.Nat64) 237 | ), 238 | 'cyclesCharged' : IDL.Vec( 239 | IDL.Tuple(IDL.Tuple(IDL.Text, IDL.Text), IDL.Nat) 240 | ), 241 | 'requests' : IDL.Vec(IDL.Tuple(IDL.Tuple(IDL.Text, IDL.Text), IDL.Nat64)), 242 | 'errHttpOutcall' : IDL.Vec( 243 | IDL.Tuple(IDL.Tuple(IDL.Text, IDL.Text), IDL.Nat64) 244 | ), 245 | 'errHostNotAllowed' : IDL.Vec(IDL.Tuple(IDL.Text, IDL.Nat64)), 246 | }); 247 | const ProviderView = IDL.Record({ 248 | 'cyclesPerCall' : IDL.Nat64, 249 | 'owner' : IDL.Principal, 250 | 'hostname' : IDL.Text, 251 | 'primary' : IDL.Bool, 252 | 'chainId' : IDL.Nat64, 253 | 'cyclesPerMessageByte' : IDL.Nat64, 254 | 'providerId' : IDL.Nat64, 255 | }); 256 | const ManageProviderArgs = IDL.Record({ 257 | 'service' : IDL.Opt(RpcService), 258 | 'primary' : IDL.Opt(IDL.Bool), 259 | 'providerId' : IDL.Nat64, 260 | }); 261 | const RegisterProviderArgs = IDL.Record({ 262 | 'cyclesPerCall' : IDL.Nat64, 263 | 'credentialPath' : IDL.Text, 264 | 'hostname' : IDL.Text, 265 | 'credentialHeaders' : IDL.Opt(IDL.Vec(HttpHeader)), 266 | 'chainId' : IDL.Nat64, 267 | 'cyclesPerMessageByte' : IDL.Nat64, 268 | }); 269 | const RequestResult = IDL.Variant({ 'Ok' : IDL.Text, 'Err' : RpcError }); 270 | const RequestCostResult = IDL.Variant({ 'Ok' : IDL.Nat, 'Err' : RpcError }); 271 | const UpdateProviderArgs = IDL.Record({ 272 | 'cyclesPerCall' : IDL.Opt(IDL.Nat64), 273 | 'credentialPath' : IDL.Opt(IDL.Text), 274 | 'hostname' : IDL.Opt(IDL.Text), 275 | 'credentialHeaders' : IDL.Opt(IDL.Vec(HttpHeader)), 276 | 'primary' : IDL.Opt(IDL.Bool), 277 | 'cyclesPerMessageByte' : IDL.Opt(IDL.Nat64), 278 | 'providerId' : IDL.Nat64, 279 | }); 280 | return IDL.Service({ 281 | 'authorize' : IDL.Func([IDL.Principal, Auth], [IDL.Bool], []), 282 | 'deauthorize' : IDL.Func([IDL.Principal, Auth], [IDL.Bool], []), 283 | 'eth_feeHistory' : IDL.Func( 284 | [RpcServices, IDL.Opt(RpcConfig), FeeHistoryArgs], 285 | [MultiFeeHistoryResult], 286 | [], 287 | ), 288 | 'eth_getBlockByNumber' : IDL.Func( 289 | [RpcServices, IDL.Opt(RpcConfig), BlockTag], 290 | [MultiGetBlockByNumberResult], 291 | [], 292 | ), 293 | 'eth_getLogs' : IDL.Func( 294 | [RpcServices, IDL.Opt(RpcConfig), GetLogsArgs], 295 | [MultiGetLogsResult], 296 | [], 297 | ), 298 | 'eth_getTransactionCount' : IDL.Func( 299 | [RpcServices, IDL.Opt(RpcConfig), GetTransactionCountArgs], 300 | [MultiGetTransactionCountResult], 301 | [], 302 | ), 303 | 'eth_getTransactionReceipt' : IDL.Func( 304 | [RpcServices, IDL.Opt(RpcConfig), IDL.Text], 305 | [MultiGetTransactionReceiptResult], 306 | [], 307 | ), 308 | 'eth_sendRawTransaction' : IDL.Func( 309 | [RpcServices, IDL.Opt(RpcConfig), IDL.Text], 310 | [MultiSendRawTransactionResult], 311 | [], 312 | ), 313 | 'getAccumulatedCycleCount' : IDL.Func([ProviderId], [IDL.Nat], ['query']), 314 | 'getAuthorized' : IDL.Func([Auth], [IDL.Vec(IDL.Principal)], ['query']), 315 | 'getMetrics' : IDL.Func([], [Metrics], ['query']), 316 | 'getNodesInSubnet' : IDL.Func([], [IDL.Nat32], ['query']), 317 | 'getOpenRpcAccess' : IDL.Func([], [IDL.Bool], ['query']), 318 | 'getProviders' : IDL.Func([], [IDL.Vec(ProviderView)], ['query']), 319 | 'getServiceProviderMap' : IDL.Func( 320 | [], 321 | [IDL.Vec(IDL.Tuple(RpcService, IDL.Nat64))], 322 | ['query'], 323 | ), 324 | 'manageProvider' : IDL.Func([ManageProviderArgs], [], []), 325 | 'registerProvider' : IDL.Func([RegisterProviderArgs], [IDL.Nat64], []), 326 | 'request' : IDL.Func( 327 | [RpcService, IDL.Text, IDL.Nat64], 328 | [RequestResult], 329 | [], 330 | ), 331 | 'requestCost' : IDL.Func( 332 | [RpcService, IDL.Text, IDL.Nat64], 333 | [RequestCostResult], 334 | ['query'], 335 | ), 336 | 'setOpenRpcAccess' : IDL.Func([IDL.Bool], [], []), 337 | 'unregisterProvider' : IDL.Func([ProviderId], [IDL.Bool], []), 338 | 'updateProvider' : IDL.Func([UpdateProviderArgs], [], []), 339 | 'withdrawAccumulatedCycles' : IDL.Func([ProviderId, IDL.Principal], [], []), 340 | }); 341 | }; 342 | export const init = ({ IDL }) => { 343 | const InitArgs = IDL.Record({ 'nodesInSubnet' : IDL.Nat32 }); 344 | return [InitArgs]; 345 | }; 346 | -------------------------------------------------------------------------------- /src/declarations/evm_rpc/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ActorSubclass, 3 | HttpAgentOptions, 4 | ActorConfig, 5 | Agent, 6 | } from "@dfinity/agent"; 7 | import type { Principal } from "@dfinity/principal"; 8 | import type { IDL } from "@dfinity/candid"; 9 | 10 | import { _SERVICE } from './evm_rpc.did'; 11 | 12 | export declare const idlFactory: IDL.InterfaceFactory; 13 | export declare const canisterId: string; 14 | 15 | export declare interface CreateActorOptions { 16 | /** 17 | * @see {@link Agent} 18 | */ 19 | agent?: Agent; 20 | /** 21 | * @see {@link HttpAgentOptions} 22 | */ 23 | agentOptions?: HttpAgentOptions; 24 | /** 25 | * @see {@link ActorConfig} 26 | */ 27 | actorOptions?: ActorConfig; 28 | } 29 | 30 | /** 31 | * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister. 32 | * @constructs {@link ActorSubClass} 33 | * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to 34 | * @param {CreateActorOptions} options - see {@link CreateActorOptions} 35 | * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions 36 | * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent 37 | * @see {@link HttpAgentOptions} 38 | * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor 39 | * @see {@link ActorConfig} 40 | */ 41 | export declare const createActor: ( 42 | canisterId: string | Principal, 43 | options?: CreateActorOptions 44 | ) => ActorSubclass<_SERVICE>; 45 | 46 | /** 47 | * Intialized Actor using default settings, ready to talk to a canister using its candid interface 48 | * @constructs {@link ActorSubClass} 49 | */ 50 | export declare const evm_rpc: ActorSubclass<_SERVICE>; 51 | -------------------------------------------------------------------------------- /src/declarations/evm_rpc/index.js: -------------------------------------------------------------------------------- 1 | import { Actor, HttpAgent } from "@dfinity/agent"; 2 | 3 | // Imports and re-exports candid interface 4 | import { idlFactory } from "./evm_rpc.did.js"; 5 | export { idlFactory } from "./evm_rpc.did.js"; 6 | 7 | /* CANISTER_ID is replaced by webpack based on node environment 8 | * Note: canister environment variable will be standardized as 9 | * process.env.CANISTER_ID_ 10 | * beginning in dfx 0.15.0 11 | */ 12 | export const canisterId = 13 | process.env.CANISTER_ID_EVM_RPC; 14 | 15 | export const createActor = (canisterId, options = {}) => { 16 | const agent = options.agent || new HttpAgent({ ...options.agentOptions }); 17 | 18 | if (options.agent && options.agentOptions) { 19 | console.warn( 20 | "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent." 21 | ); 22 | } 23 | 24 | // Fetch root key for certificate validation during development 25 | if (process.env.DFX_NETWORK !== "ic") { 26 | agent.fetchRootKey().catch((err) => { 27 | console.warn( 28 | "Unable to fetch root key. Check to ensure that your local replica is running" 29 | ); 30 | console.error(err); 31 | }); 32 | } 33 | 34 | // Creates an actor with using the candid interface and the HttpAgent 35 | return Actor.createActor(idlFactory, { 36 | agent, 37 | canisterId, 38 | ...options.actorOptions, 39 | }); 40 | }; 41 | 42 | export const evm_rpc = canisterId ? createActor(canisterId) : undefined; 43 | -------------------------------------------------------------------------------- /src/declarations/frontend/frontend.did: -------------------------------------------------------------------------------- 1 | type BatchId = nat; 2 | type ChunkId = nat; 3 | type Key = text; 4 | type Time = int; 5 | 6 | type CreateAssetArguments = record { 7 | key: Key; 8 | content_type: text; 9 | max_age: opt nat64; 10 | headers: opt vec HeaderField; 11 | enable_aliasing: opt bool; 12 | allow_raw_access: opt bool; 13 | }; 14 | 15 | // Add or change content for an asset, by content encoding 16 | type SetAssetContentArguments = record { 17 | key: Key; 18 | content_encoding: text; 19 | chunk_ids: vec ChunkId; 20 | sha256: opt blob; 21 | }; 22 | 23 | // Remove content for an asset, by content encoding 24 | type UnsetAssetContentArguments = record { 25 | key: Key; 26 | content_encoding: text; 27 | }; 28 | 29 | // Delete an asset 30 | type DeleteAssetArguments = record { 31 | key: Key; 32 | }; 33 | 34 | // Reset everything 35 | type ClearArguments = record {}; 36 | 37 | type BatchOperationKind = variant { 38 | CreateAsset: CreateAssetArguments; 39 | SetAssetContent: SetAssetContentArguments; 40 | 41 | SetAssetProperties: SetAssetPropertiesArguments; 42 | 43 | UnsetAssetContent: UnsetAssetContentArguments; 44 | DeleteAsset: DeleteAssetArguments; 45 | 46 | Clear: ClearArguments; 47 | }; 48 | 49 | type CommitBatchArguments = record { 50 | batch_id: BatchId; 51 | operations: vec BatchOperationKind 52 | }; 53 | 54 | type CommitProposedBatchArguments = record { 55 | batch_id: BatchId; 56 | evidence: blob; 57 | }; 58 | 59 | type ComputeEvidenceArguments = record { 60 | batch_id: BatchId; 61 | max_iterations: opt nat16 62 | }; 63 | 64 | type DeleteBatchArguments = record { 65 | batch_id: BatchId; 66 | }; 67 | 68 | type HeaderField = record { text; text; }; 69 | 70 | type HttpRequest = record { 71 | method: text; 72 | url: text; 73 | headers: vec HeaderField; 74 | body: blob; 75 | certificate_version: opt nat16; 76 | }; 77 | 78 | type HttpResponse = record { 79 | status_code: nat16; 80 | headers: vec HeaderField; 81 | body: blob; 82 | streaming_strategy: opt StreamingStrategy; 83 | }; 84 | 85 | type StreamingCallbackHttpResponse = record { 86 | body: blob; 87 | token: opt StreamingCallbackToken; 88 | }; 89 | 90 | type StreamingCallbackToken = record { 91 | key: Key; 92 | content_encoding: text; 93 | index: nat; 94 | sha256: opt blob; 95 | }; 96 | 97 | type StreamingStrategy = variant { 98 | Callback: record { 99 | callback: func (StreamingCallbackToken) -> (opt StreamingCallbackHttpResponse) query; 100 | token: StreamingCallbackToken; 101 | }; 102 | }; 103 | 104 | type SetAssetPropertiesArguments = record { 105 | key: Key; 106 | max_age: opt opt nat64; 107 | headers: opt opt vec HeaderField; 108 | allow_raw_access: opt opt bool; 109 | is_aliased: opt opt bool; 110 | }; 111 | 112 | type ConfigurationResponse = record { 113 | max_batches: opt nat64; 114 | max_chunks: opt nat64; 115 | max_bytes: opt nat64; 116 | }; 117 | 118 | type ConfigureArguments = record { 119 | max_batches: opt opt nat64; 120 | max_chunks: opt opt nat64; 121 | max_bytes: opt opt nat64; 122 | }; 123 | 124 | type Permission = variant { 125 | Commit; 126 | ManagePermissions; 127 | Prepare; 128 | }; 129 | 130 | type GrantPermission = record { 131 | to_principal: principal; 132 | permission: Permission; 133 | }; 134 | type RevokePermission = record { 135 | of_principal: principal; 136 | permission: Permission; 137 | }; 138 | type ListPermitted = record { permission: Permission }; 139 | 140 | type ValidationResult = variant { Ok : text; Err : text }; 141 | 142 | type AssetCanisterArgs = variant { 143 | Init: InitArgs; 144 | Upgrade: UpgradeArgs; 145 | }; 146 | 147 | type InitArgs = record {}; 148 | 149 | type UpgradeArgs = record { 150 | set_permissions: opt SetPermissions; 151 | }; 152 | 153 | /// Sets the list of principals granted each permission. 154 | type SetPermissions = record { 155 | prepare: vec principal; 156 | commit: vec principal; 157 | manage_permissions: vec principal; 158 | }; 159 | 160 | service: (asset_canister_args: opt AssetCanisterArgs) -> { 161 | api_version: () -> (nat16) query; 162 | 163 | get: (record { 164 | key: Key; 165 | accept_encodings: vec text; 166 | }) -> (record { 167 | content: blob; // may be the entirety of the content, or just chunk index 0 168 | content_type: text; 169 | content_encoding: text; 170 | sha256: opt blob; // sha256 of entire asset encoding, calculated by dfx and passed in SetAssetContentArguments 171 | total_length: nat; // all chunks except last have size == content.size() 172 | }) query; 173 | 174 | // if get() returned chunks > 1, call this to retrieve them. 175 | // chunks may or may not be split up at the same boundaries as presented to create_chunk(). 176 | get_chunk: (record { 177 | key: Key; 178 | content_encoding: text; 179 | index: nat; 180 | sha256: opt blob; // sha256 of entire asset encoding, calculated by dfx and passed in SetAssetContentArguments 181 | }) -> (record { content: blob }) query; 182 | 183 | list : (record {}) -> (vec record { 184 | key: Key; 185 | content_type: text; 186 | encodings: vec record { 187 | content_encoding: text; 188 | sha256: opt blob; // sha256 of entire asset encoding, calculated by dfx and passed in SetAssetContentArguments 189 | length: nat; // Size of this encoding's blob. Calculated when uploading assets. 190 | modified: Time; 191 | }; 192 | }) query; 193 | 194 | certified_tree : (record {}) -> (record { 195 | certificate: blob; 196 | tree: blob; 197 | }) query; 198 | 199 | create_batch : (record {}) -> (record { batch_id: BatchId }); 200 | 201 | create_chunk: (record { batch_id: BatchId; content: blob }) -> (record { chunk_id: ChunkId }); 202 | 203 | // Perform all operations successfully, or reject 204 | commit_batch: (CommitBatchArguments) -> (); 205 | 206 | // Save the batch operations for later commit 207 | propose_commit_batch: (CommitBatchArguments) -> (); 208 | 209 | // Given a batch already proposed, perform all operations successfully, or reject 210 | commit_proposed_batch: (CommitProposedBatchArguments) -> (); 211 | 212 | // Compute a hash over the CommitBatchArguments. Call until it returns Some(evidence). 213 | compute_evidence: (ComputeEvidenceArguments) -> (opt blob); 214 | 215 | // Delete a batch that has been created, or proposed for commit, but not yet committed 216 | delete_batch: (DeleteBatchArguments) -> (); 217 | 218 | create_asset: (CreateAssetArguments) -> (); 219 | set_asset_content: (SetAssetContentArguments) -> (); 220 | unset_asset_content: (UnsetAssetContentArguments) -> (); 221 | 222 | delete_asset: (DeleteAssetArguments) -> (); 223 | 224 | clear: (ClearArguments) -> (); 225 | 226 | // Single call to create an asset with content for a single content encoding that 227 | // fits within the message ingress limit. 228 | store: (record { 229 | key: Key; 230 | content_type: text; 231 | content_encoding: text; 232 | content: blob; 233 | sha256: opt blob 234 | }) -> (); 235 | 236 | http_request: (request: HttpRequest) -> (HttpResponse) query; 237 | http_request_streaming_callback: (token: StreamingCallbackToken) -> (opt StreamingCallbackHttpResponse) query; 238 | 239 | authorize: (principal) -> (); 240 | deauthorize: (principal) -> (); 241 | list_authorized: () -> (vec principal); 242 | grant_permission: (GrantPermission) -> (); 243 | revoke_permission: (RevokePermission) -> (); 244 | list_permitted: (ListPermitted) -> (vec principal); 245 | take_ownership: () -> (); 246 | 247 | get_asset_properties : (key: Key) -> (record { 248 | max_age: opt nat64; 249 | headers: opt vec HeaderField; 250 | allow_raw_access: opt bool; 251 | is_aliased: opt bool; } ) query; 252 | set_asset_properties: (SetAssetPropertiesArguments) -> (); 253 | 254 | get_configuration: () -> (ConfigurationResponse); 255 | configure: (ConfigureArguments) -> (); 256 | 257 | validate_grant_permission: (GrantPermission) -> (ValidationResult); 258 | validate_revoke_permission: (RevokePermission) -> (ValidationResult); 259 | validate_take_ownership: () -> (ValidationResult); 260 | validate_commit_proposed_batch: (CommitProposedBatchArguments) -> (ValidationResult); 261 | validate_configure: (ConfigureArguments) -> (ValidationResult); 262 | } 263 | -------------------------------------------------------------------------------- /src/declarations/frontend/frontend.did.d.ts: -------------------------------------------------------------------------------- 1 | import type { Principal } from '@dfinity/principal'; 2 | import type { ActorMethod } from '@dfinity/agent'; 3 | import type { IDL } from '@dfinity/candid'; 4 | 5 | export type AssetCanisterArgs = { 'Upgrade' : UpgradeArgs } | 6 | { 'Init' : InitArgs }; 7 | export type BatchId = bigint; 8 | export type BatchOperationKind = { 9 | 'SetAssetProperties' : SetAssetPropertiesArguments 10 | } | 11 | { 'CreateAsset' : CreateAssetArguments } | 12 | { 'UnsetAssetContent' : UnsetAssetContentArguments } | 13 | { 'DeleteAsset' : DeleteAssetArguments } | 14 | { 'SetAssetContent' : SetAssetContentArguments } | 15 | { 'Clear' : ClearArguments }; 16 | export type ChunkId = bigint; 17 | export type ClearArguments = {}; 18 | export interface CommitBatchArguments { 19 | 'batch_id' : BatchId, 20 | 'operations' : Array, 21 | } 22 | export interface CommitProposedBatchArguments { 23 | 'batch_id' : BatchId, 24 | 'evidence' : Uint8Array | number[], 25 | } 26 | export interface ComputeEvidenceArguments { 27 | 'batch_id' : BatchId, 28 | 'max_iterations' : [] | [number], 29 | } 30 | export interface ConfigurationResponse { 31 | 'max_batches' : [] | [bigint], 32 | 'max_bytes' : [] | [bigint], 33 | 'max_chunks' : [] | [bigint], 34 | } 35 | export interface ConfigureArguments { 36 | 'max_batches' : [] | [[] | [bigint]], 37 | 'max_bytes' : [] | [[] | [bigint]], 38 | 'max_chunks' : [] | [[] | [bigint]], 39 | } 40 | export interface CreateAssetArguments { 41 | 'key' : Key, 42 | 'content_type' : string, 43 | 'headers' : [] | [Array], 44 | 'allow_raw_access' : [] | [boolean], 45 | 'max_age' : [] | [bigint], 46 | 'enable_aliasing' : [] | [boolean], 47 | } 48 | export interface DeleteAssetArguments { 'key' : Key } 49 | export interface DeleteBatchArguments { 'batch_id' : BatchId } 50 | export interface GrantPermission { 51 | 'permission' : Permission, 52 | 'to_principal' : Principal, 53 | } 54 | export type HeaderField = [string, string]; 55 | export interface HttpRequest { 56 | 'url' : string, 57 | 'method' : string, 58 | 'body' : Uint8Array | number[], 59 | 'headers' : Array, 60 | 'certificate_version' : [] | [number], 61 | } 62 | export interface HttpResponse { 63 | 'body' : Uint8Array | number[], 64 | 'headers' : Array, 65 | 'streaming_strategy' : [] | [StreamingStrategy], 66 | 'status_code' : number, 67 | } 68 | export type InitArgs = {}; 69 | export type Key = string; 70 | export interface ListPermitted { 'permission' : Permission } 71 | export type Permission = { 'Prepare' : null } | 72 | { 'ManagePermissions' : null } | 73 | { 'Commit' : null }; 74 | export interface RevokePermission { 75 | 'permission' : Permission, 76 | 'of_principal' : Principal, 77 | } 78 | export interface SetAssetContentArguments { 79 | 'key' : Key, 80 | 'sha256' : [] | [Uint8Array | number[]], 81 | 'chunk_ids' : Array, 82 | 'content_encoding' : string, 83 | } 84 | export interface SetAssetPropertiesArguments { 85 | 'key' : Key, 86 | 'headers' : [] | [[] | [Array]], 87 | 'is_aliased' : [] | [[] | [boolean]], 88 | 'allow_raw_access' : [] | [[] | [boolean]], 89 | 'max_age' : [] | [[] | [bigint]], 90 | } 91 | export interface SetPermissions { 92 | 'prepare' : Array, 93 | 'commit' : Array, 94 | 'manage_permissions' : Array, 95 | } 96 | export interface StreamingCallbackHttpResponse { 97 | 'token' : [] | [StreamingCallbackToken], 98 | 'body' : Uint8Array | number[], 99 | } 100 | export interface StreamingCallbackToken { 101 | 'key' : Key, 102 | 'sha256' : [] | [Uint8Array | number[]], 103 | 'index' : bigint, 104 | 'content_encoding' : string, 105 | } 106 | export type StreamingStrategy = { 107 | 'Callback' : { 108 | 'token' : StreamingCallbackToken, 109 | 'callback' : [Principal, string], 110 | } 111 | }; 112 | export type Time = bigint; 113 | export interface UnsetAssetContentArguments { 114 | 'key' : Key, 115 | 'content_encoding' : string, 116 | } 117 | export interface UpgradeArgs { 'set_permissions' : [] | [SetPermissions] } 118 | export type ValidationResult = { 'Ok' : string } | 119 | { 'Err' : string }; 120 | export interface _SERVICE { 121 | 'api_version' : ActorMethod<[], number>, 122 | 'authorize' : ActorMethod<[Principal], undefined>, 123 | 'certified_tree' : ActorMethod< 124 | [{}], 125 | { 'certificate' : Uint8Array | number[], 'tree' : Uint8Array | number[] } 126 | >, 127 | 'clear' : ActorMethod<[ClearArguments], undefined>, 128 | 'commit_batch' : ActorMethod<[CommitBatchArguments], undefined>, 129 | 'commit_proposed_batch' : ActorMethod< 130 | [CommitProposedBatchArguments], 131 | undefined 132 | >, 133 | 'compute_evidence' : ActorMethod< 134 | [ComputeEvidenceArguments], 135 | [] | [Uint8Array | number[]] 136 | >, 137 | 'configure' : ActorMethod<[ConfigureArguments], undefined>, 138 | 'create_asset' : ActorMethod<[CreateAssetArguments], undefined>, 139 | 'create_batch' : ActorMethod<[{}], { 'batch_id' : BatchId }>, 140 | 'create_chunk' : ActorMethod< 141 | [{ 'content' : Uint8Array | number[], 'batch_id' : BatchId }], 142 | { 'chunk_id' : ChunkId } 143 | >, 144 | 'deauthorize' : ActorMethod<[Principal], undefined>, 145 | 'delete_asset' : ActorMethod<[DeleteAssetArguments], undefined>, 146 | 'delete_batch' : ActorMethod<[DeleteBatchArguments], undefined>, 147 | 'get' : ActorMethod< 148 | [{ 'key' : Key, 'accept_encodings' : Array }], 149 | { 150 | 'content' : Uint8Array | number[], 151 | 'sha256' : [] | [Uint8Array | number[]], 152 | 'content_type' : string, 153 | 'content_encoding' : string, 154 | 'total_length' : bigint, 155 | } 156 | >, 157 | 'get_asset_properties' : ActorMethod< 158 | [Key], 159 | { 160 | 'headers' : [] | [Array], 161 | 'is_aliased' : [] | [boolean], 162 | 'allow_raw_access' : [] | [boolean], 163 | 'max_age' : [] | [bigint], 164 | } 165 | >, 166 | 'get_chunk' : ActorMethod< 167 | [ 168 | { 169 | 'key' : Key, 170 | 'sha256' : [] | [Uint8Array | number[]], 171 | 'index' : bigint, 172 | 'content_encoding' : string, 173 | }, 174 | ], 175 | { 'content' : Uint8Array | number[] } 176 | >, 177 | 'get_configuration' : ActorMethod<[], ConfigurationResponse>, 178 | 'grant_permission' : ActorMethod<[GrantPermission], undefined>, 179 | 'http_request' : ActorMethod<[HttpRequest], HttpResponse>, 180 | 'http_request_streaming_callback' : ActorMethod< 181 | [StreamingCallbackToken], 182 | [] | [StreamingCallbackHttpResponse] 183 | >, 184 | 'list' : ActorMethod< 185 | [{}], 186 | Array< 187 | { 188 | 'key' : Key, 189 | 'encodings' : Array< 190 | { 191 | 'modified' : Time, 192 | 'sha256' : [] | [Uint8Array | number[]], 193 | 'length' : bigint, 194 | 'content_encoding' : string, 195 | } 196 | >, 197 | 'content_type' : string, 198 | } 199 | > 200 | >, 201 | 'list_authorized' : ActorMethod<[], Array>, 202 | 'list_permitted' : ActorMethod<[ListPermitted], Array>, 203 | 'propose_commit_batch' : ActorMethod<[CommitBatchArguments], undefined>, 204 | 'revoke_permission' : ActorMethod<[RevokePermission], undefined>, 205 | 'set_asset_content' : ActorMethod<[SetAssetContentArguments], undefined>, 206 | 'set_asset_properties' : ActorMethod< 207 | [SetAssetPropertiesArguments], 208 | undefined 209 | >, 210 | 'store' : ActorMethod< 211 | [ 212 | { 213 | 'key' : Key, 214 | 'content' : Uint8Array | number[], 215 | 'sha256' : [] | [Uint8Array | number[]], 216 | 'content_type' : string, 217 | 'content_encoding' : string, 218 | }, 219 | ], 220 | undefined 221 | >, 222 | 'take_ownership' : ActorMethod<[], undefined>, 223 | 'unset_asset_content' : ActorMethod<[UnsetAssetContentArguments], undefined>, 224 | 'validate_commit_proposed_batch' : ActorMethod< 225 | [CommitProposedBatchArguments], 226 | ValidationResult 227 | >, 228 | 'validate_configure' : ActorMethod<[ConfigureArguments], ValidationResult>, 229 | 'validate_grant_permission' : ActorMethod< 230 | [GrantPermission], 231 | ValidationResult 232 | >, 233 | 'validate_revoke_permission' : ActorMethod< 234 | [RevokePermission], 235 | ValidationResult 236 | >, 237 | 'validate_take_ownership' : ActorMethod<[], ValidationResult>, 238 | } 239 | export declare const idlFactory: IDL.InterfaceFactory; 240 | export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; 241 | -------------------------------------------------------------------------------- /src/declarations/frontend/frontend.did.js: -------------------------------------------------------------------------------- 1 | export const idlFactory = ({ IDL }) => { 2 | const SetPermissions = IDL.Record({ 3 | 'prepare' : IDL.Vec(IDL.Principal), 4 | 'commit' : IDL.Vec(IDL.Principal), 5 | 'manage_permissions' : IDL.Vec(IDL.Principal), 6 | }); 7 | const UpgradeArgs = IDL.Record({ 8 | 'set_permissions' : IDL.Opt(SetPermissions), 9 | }); 10 | const InitArgs = IDL.Record({}); 11 | const AssetCanisterArgs = IDL.Variant({ 12 | 'Upgrade' : UpgradeArgs, 13 | 'Init' : InitArgs, 14 | }); 15 | const ClearArguments = IDL.Record({}); 16 | const BatchId = IDL.Nat; 17 | const Key = IDL.Text; 18 | const HeaderField = IDL.Tuple(IDL.Text, IDL.Text); 19 | const SetAssetPropertiesArguments = IDL.Record({ 20 | 'key' : Key, 21 | 'headers' : IDL.Opt(IDL.Opt(IDL.Vec(HeaderField))), 22 | 'is_aliased' : IDL.Opt(IDL.Opt(IDL.Bool)), 23 | 'allow_raw_access' : IDL.Opt(IDL.Opt(IDL.Bool)), 24 | 'max_age' : IDL.Opt(IDL.Opt(IDL.Nat64)), 25 | }); 26 | const CreateAssetArguments = IDL.Record({ 27 | 'key' : Key, 28 | 'content_type' : IDL.Text, 29 | 'headers' : IDL.Opt(IDL.Vec(HeaderField)), 30 | 'allow_raw_access' : IDL.Opt(IDL.Bool), 31 | 'max_age' : IDL.Opt(IDL.Nat64), 32 | 'enable_aliasing' : IDL.Opt(IDL.Bool), 33 | }); 34 | const UnsetAssetContentArguments = IDL.Record({ 35 | 'key' : Key, 36 | 'content_encoding' : IDL.Text, 37 | }); 38 | const DeleteAssetArguments = IDL.Record({ 'key' : Key }); 39 | const ChunkId = IDL.Nat; 40 | const SetAssetContentArguments = IDL.Record({ 41 | 'key' : Key, 42 | 'sha256' : IDL.Opt(IDL.Vec(IDL.Nat8)), 43 | 'chunk_ids' : IDL.Vec(ChunkId), 44 | 'content_encoding' : IDL.Text, 45 | }); 46 | const BatchOperationKind = IDL.Variant({ 47 | 'SetAssetProperties' : SetAssetPropertiesArguments, 48 | 'CreateAsset' : CreateAssetArguments, 49 | 'UnsetAssetContent' : UnsetAssetContentArguments, 50 | 'DeleteAsset' : DeleteAssetArguments, 51 | 'SetAssetContent' : SetAssetContentArguments, 52 | 'Clear' : ClearArguments, 53 | }); 54 | const CommitBatchArguments = IDL.Record({ 55 | 'batch_id' : BatchId, 56 | 'operations' : IDL.Vec(BatchOperationKind), 57 | }); 58 | const CommitProposedBatchArguments = IDL.Record({ 59 | 'batch_id' : BatchId, 60 | 'evidence' : IDL.Vec(IDL.Nat8), 61 | }); 62 | const ComputeEvidenceArguments = IDL.Record({ 63 | 'batch_id' : BatchId, 64 | 'max_iterations' : IDL.Opt(IDL.Nat16), 65 | }); 66 | const ConfigureArguments = IDL.Record({ 67 | 'max_batches' : IDL.Opt(IDL.Opt(IDL.Nat64)), 68 | 'max_bytes' : IDL.Opt(IDL.Opt(IDL.Nat64)), 69 | 'max_chunks' : IDL.Opt(IDL.Opt(IDL.Nat64)), 70 | }); 71 | const DeleteBatchArguments = IDL.Record({ 'batch_id' : BatchId }); 72 | const ConfigurationResponse = IDL.Record({ 73 | 'max_batches' : IDL.Opt(IDL.Nat64), 74 | 'max_bytes' : IDL.Opt(IDL.Nat64), 75 | 'max_chunks' : IDL.Opt(IDL.Nat64), 76 | }); 77 | const Permission = IDL.Variant({ 78 | 'Prepare' : IDL.Null, 79 | 'ManagePermissions' : IDL.Null, 80 | 'Commit' : IDL.Null, 81 | }); 82 | const GrantPermission = IDL.Record({ 83 | 'permission' : Permission, 84 | 'to_principal' : IDL.Principal, 85 | }); 86 | const HttpRequest = IDL.Record({ 87 | 'url' : IDL.Text, 88 | 'method' : IDL.Text, 89 | 'body' : IDL.Vec(IDL.Nat8), 90 | 'headers' : IDL.Vec(HeaderField), 91 | 'certificate_version' : IDL.Opt(IDL.Nat16), 92 | }); 93 | const StreamingCallbackToken = IDL.Record({ 94 | 'key' : Key, 95 | 'sha256' : IDL.Opt(IDL.Vec(IDL.Nat8)), 96 | 'index' : IDL.Nat, 97 | 'content_encoding' : IDL.Text, 98 | }); 99 | const StreamingCallbackHttpResponse = IDL.Record({ 100 | 'token' : IDL.Opt(StreamingCallbackToken), 101 | 'body' : IDL.Vec(IDL.Nat8), 102 | }); 103 | const StreamingStrategy = IDL.Variant({ 104 | 'Callback' : IDL.Record({ 105 | 'token' : StreamingCallbackToken, 106 | 'callback' : IDL.Func( 107 | [StreamingCallbackToken], 108 | [IDL.Opt(StreamingCallbackHttpResponse)], 109 | ['query'], 110 | ), 111 | }), 112 | }); 113 | const HttpResponse = IDL.Record({ 114 | 'body' : IDL.Vec(IDL.Nat8), 115 | 'headers' : IDL.Vec(HeaderField), 116 | 'streaming_strategy' : IDL.Opt(StreamingStrategy), 117 | 'status_code' : IDL.Nat16, 118 | }); 119 | const Time = IDL.Int; 120 | const ListPermitted = IDL.Record({ 'permission' : Permission }); 121 | const RevokePermission = IDL.Record({ 122 | 'permission' : Permission, 123 | 'of_principal' : IDL.Principal, 124 | }); 125 | const ValidationResult = IDL.Variant({ 'Ok' : IDL.Text, 'Err' : IDL.Text }); 126 | return IDL.Service({ 127 | 'api_version' : IDL.Func([], [IDL.Nat16], ['query']), 128 | 'authorize' : IDL.Func([IDL.Principal], [], []), 129 | 'certified_tree' : IDL.Func( 130 | [IDL.Record({})], 131 | [ 132 | IDL.Record({ 133 | 'certificate' : IDL.Vec(IDL.Nat8), 134 | 'tree' : IDL.Vec(IDL.Nat8), 135 | }), 136 | ], 137 | ['query'], 138 | ), 139 | 'clear' : IDL.Func([ClearArguments], [], []), 140 | 'commit_batch' : IDL.Func([CommitBatchArguments], [], []), 141 | 'commit_proposed_batch' : IDL.Func([CommitProposedBatchArguments], [], []), 142 | 'compute_evidence' : IDL.Func( 143 | [ComputeEvidenceArguments], 144 | [IDL.Opt(IDL.Vec(IDL.Nat8))], 145 | [], 146 | ), 147 | 'configure' : IDL.Func([ConfigureArguments], [], []), 148 | 'create_asset' : IDL.Func([CreateAssetArguments], [], []), 149 | 'create_batch' : IDL.Func( 150 | [IDL.Record({})], 151 | [IDL.Record({ 'batch_id' : BatchId })], 152 | [], 153 | ), 154 | 'create_chunk' : IDL.Func( 155 | [IDL.Record({ 'content' : IDL.Vec(IDL.Nat8), 'batch_id' : BatchId })], 156 | [IDL.Record({ 'chunk_id' : ChunkId })], 157 | [], 158 | ), 159 | 'deauthorize' : IDL.Func([IDL.Principal], [], []), 160 | 'delete_asset' : IDL.Func([DeleteAssetArguments], [], []), 161 | 'delete_batch' : IDL.Func([DeleteBatchArguments], [], []), 162 | 'get' : IDL.Func( 163 | [IDL.Record({ 'key' : Key, 'accept_encodings' : IDL.Vec(IDL.Text) })], 164 | [ 165 | IDL.Record({ 166 | 'content' : IDL.Vec(IDL.Nat8), 167 | 'sha256' : IDL.Opt(IDL.Vec(IDL.Nat8)), 168 | 'content_type' : IDL.Text, 169 | 'content_encoding' : IDL.Text, 170 | 'total_length' : IDL.Nat, 171 | }), 172 | ], 173 | ['query'], 174 | ), 175 | 'get_asset_properties' : IDL.Func( 176 | [Key], 177 | [ 178 | IDL.Record({ 179 | 'headers' : IDL.Opt(IDL.Vec(HeaderField)), 180 | 'is_aliased' : IDL.Opt(IDL.Bool), 181 | 'allow_raw_access' : IDL.Opt(IDL.Bool), 182 | 'max_age' : IDL.Opt(IDL.Nat64), 183 | }), 184 | ], 185 | ['query'], 186 | ), 187 | 'get_chunk' : IDL.Func( 188 | [ 189 | IDL.Record({ 190 | 'key' : Key, 191 | 'sha256' : IDL.Opt(IDL.Vec(IDL.Nat8)), 192 | 'index' : IDL.Nat, 193 | 'content_encoding' : IDL.Text, 194 | }), 195 | ], 196 | [IDL.Record({ 'content' : IDL.Vec(IDL.Nat8) })], 197 | ['query'], 198 | ), 199 | 'get_configuration' : IDL.Func([], [ConfigurationResponse], []), 200 | 'grant_permission' : IDL.Func([GrantPermission], [], []), 201 | 'http_request' : IDL.Func([HttpRequest], [HttpResponse], ['query']), 202 | 'http_request_streaming_callback' : IDL.Func( 203 | [StreamingCallbackToken], 204 | [IDL.Opt(StreamingCallbackHttpResponse)], 205 | ['query'], 206 | ), 207 | 'list' : IDL.Func( 208 | [IDL.Record({})], 209 | [ 210 | IDL.Vec( 211 | IDL.Record({ 212 | 'key' : Key, 213 | 'encodings' : IDL.Vec( 214 | IDL.Record({ 215 | 'modified' : Time, 216 | 'sha256' : IDL.Opt(IDL.Vec(IDL.Nat8)), 217 | 'length' : IDL.Nat, 218 | 'content_encoding' : IDL.Text, 219 | }) 220 | ), 221 | 'content_type' : IDL.Text, 222 | }) 223 | ), 224 | ], 225 | ['query'], 226 | ), 227 | 'list_authorized' : IDL.Func([], [IDL.Vec(IDL.Principal)], []), 228 | 'list_permitted' : IDL.Func([ListPermitted], [IDL.Vec(IDL.Principal)], []), 229 | 'propose_commit_batch' : IDL.Func([CommitBatchArguments], [], []), 230 | 'revoke_permission' : IDL.Func([RevokePermission], [], []), 231 | 'set_asset_content' : IDL.Func([SetAssetContentArguments], [], []), 232 | 'set_asset_properties' : IDL.Func([SetAssetPropertiesArguments], [], []), 233 | 'store' : IDL.Func( 234 | [ 235 | IDL.Record({ 236 | 'key' : Key, 237 | 'content' : IDL.Vec(IDL.Nat8), 238 | 'sha256' : IDL.Opt(IDL.Vec(IDL.Nat8)), 239 | 'content_type' : IDL.Text, 240 | 'content_encoding' : IDL.Text, 241 | }), 242 | ], 243 | [], 244 | [], 245 | ), 246 | 'take_ownership' : IDL.Func([], [], []), 247 | 'unset_asset_content' : IDL.Func([UnsetAssetContentArguments], [], []), 248 | 'validate_commit_proposed_batch' : IDL.Func( 249 | [CommitProposedBatchArguments], 250 | [ValidationResult], 251 | [], 252 | ), 253 | 'validate_configure' : IDL.Func( 254 | [ConfigureArguments], 255 | [ValidationResult], 256 | [], 257 | ), 258 | 'validate_grant_permission' : IDL.Func( 259 | [GrantPermission], 260 | [ValidationResult], 261 | [], 262 | ), 263 | 'validate_revoke_permission' : IDL.Func( 264 | [RevokePermission], 265 | [ValidationResult], 266 | [], 267 | ), 268 | 'validate_take_ownership' : IDL.Func([], [ValidationResult], []), 269 | }); 270 | }; 271 | export const init = ({ IDL }) => { 272 | const SetPermissions = IDL.Record({ 273 | 'prepare' : IDL.Vec(IDL.Principal), 274 | 'commit' : IDL.Vec(IDL.Principal), 275 | 'manage_permissions' : IDL.Vec(IDL.Principal), 276 | }); 277 | const UpgradeArgs = IDL.Record({ 278 | 'set_permissions' : IDL.Opt(SetPermissions), 279 | }); 280 | const InitArgs = IDL.Record({}); 281 | const AssetCanisterArgs = IDL.Variant({ 282 | 'Upgrade' : UpgradeArgs, 283 | 'Init' : InitArgs, 284 | }); 285 | return [IDL.Opt(AssetCanisterArgs)]; 286 | }; 287 | -------------------------------------------------------------------------------- /src/declarations/frontend/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ActorSubclass, 3 | HttpAgentOptions, 4 | ActorConfig, 5 | Agent, 6 | } from "@dfinity/agent"; 7 | import type { Principal } from "@dfinity/principal"; 8 | import type { IDL } from "@dfinity/candid"; 9 | 10 | import { _SERVICE } from './frontend.did'; 11 | 12 | export declare const idlFactory: IDL.InterfaceFactory; 13 | export declare const canisterId: string; 14 | 15 | export declare interface CreateActorOptions { 16 | /** 17 | * @see {@link Agent} 18 | */ 19 | agent?: Agent; 20 | /** 21 | * @see {@link HttpAgentOptions} 22 | */ 23 | agentOptions?: HttpAgentOptions; 24 | /** 25 | * @see {@link ActorConfig} 26 | */ 27 | actorOptions?: ActorConfig; 28 | } 29 | 30 | /** 31 | * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister. 32 | * @constructs {@link ActorSubClass} 33 | * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to 34 | * @param {CreateActorOptions} options - see {@link CreateActorOptions} 35 | * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions 36 | * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent 37 | * @see {@link HttpAgentOptions} 38 | * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor 39 | * @see {@link ActorConfig} 40 | */ 41 | export declare const createActor: ( 42 | canisterId: string | Principal, 43 | options?: CreateActorOptions 44 | ) => ActorSubclass<_SERVICE>; 45 | 46 | /** 47 | * Intialized Actor using default settings, ready to talk to a canister using its candid interface 48 | * @constructs {@link ActorSubClass} 49 | */ 50 | export declare const frontend: ActorSubclass<_SERVICE>; 51 | -------------------------------------------------------------------------------- /src/declarations/frontend/index.js: -------------------------------------------------------------------------------- 1 | import { Actor, HttpAgent } from "@dfinity/agent"; 2 | 3 | // Imports and re-exports candid interface 4 | import { idlFactory } from "./frontend.did.js"; 5 | export { idlFactory } from "./frontend.did.js"; 6 | 7 | /* CANISTER_ID is replaced by webpack based on node environment 8 | * Note: canister environment variable will be standardized as 9 | * process.env.CANISTER_ID_ 10 | * beginning in dfx 0.15.0 11 | */ 12 | export const canisterId = 13 | process.env.CANISTER_ID_FRONTEND; 14 | 15 | export const createActor = (canisterId, options = {}) => { 16 | const agent = options.agent || new HttpAgent({ ...options.agentOptions }); 17 | 18 | if (options.agent && options.agentOptions) { 19 | console.warn( 20 | "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent." 21 | ); 22 | } 23 | 24 | // Fetch root key for certificate validation during development 25 | if (process.env.DFX_NETWORK !== "ic") { 26 | agent.fetchRootKey().catch((err) => { 27 | console.warn( 28 | "Unable to fetch root key. Check to ensure that your local replica is running" 29 | ); 30 | console.error(err); 31 | }); 32 | } 33 | 34 | // Creates an actor with using the candid interface and the HttpAgent 35 | return Actor.createActor(idlFactory, { 36 | agent, 37 | canisterId, 38 | ...options.actorOptions, 39 | }); 40 | }; 41 | 42 | export const frontend = canisterId ? createActor(canisterId) : undefined; 43 | -------------------------------------------------------------------------------- /src/declarations/payment/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ActorSubclass, 3 | HttpAgentOptions, 4 | ActorConfig, 5 | Agent, 6 | } from "@dfinity/agent"; 7 | import type { Principal } from "@dfinity/principal"; 8 | import type { IDL } from "@dfinity/candid"; 9 | 10 | import { _SERVICE } from './payment.did'; 11 | 12 | export declare const idlFactory: IDL.InterfaceFactory; 13 | export declare const canisterId: string; 14 | 15 | export declare interface CreateActorOptions { 16 | /** 17 | * @see {@link Agent} 18 | */ 19 | agent?: Agent; 20 | /** 21 | * @see {@link HttpAgentOptions} 22 | */ 23 | agentOptions?: HttpAgentOptions; 24 | /** 25 | * @see {@link ActorConfig} 26 | */ 27 | actorOptions?: ActorConfig; 28 | } 29 | 30 | /** 31 | * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister. 32 | * @constructs {@link ActorSubClass} 33 | * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to 34 | * @param {CreateActorOptions} options - see {@link CreateActorOptions} 35 | * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions 36 | * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent 37 | * @see {@link HttpAgentOptions} 38 | * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor 39 | * @see {@link ActorConfig} 40 | */ 41 | export declare const createActor: ( 42 | canisterId: string | Principal, 43 | options?: CreateActorOptions 44 | ) => ActorSubclass<_SERVICE>; 45 | 46 | /** 47 | * Intialized Actor using default settings, ready to talk to a canister using its candid interface 48 | * @constructs {@link ActorSubClass} 49 | */ 50 | export declare const payment: ActorSubclass<_SERVICE>; 51 | -------------------------------------------------------------------------------- /src/declarations/payment/index.js: -------------------------------------------------------------------------------- 1 | import { Actor, HttpAgent } from "@dfinity/agent"; 2 | 3 | // Imports and re-exports candid interface 4 | import { idlFactory } from "./payment.did.js"; 5 | export { idlFactory } from "./payment.did.js"; 6 | 7 | /* CANISTER_ID is replaced by webpack based on node environment 8 | * Note: canister environment variable will be standardized as 9 | * process.env.CANISTER_ID_ 10 | * beginning in dfx 0.15.0 11 | */ 12 | export const canisterId = 13 | process.env.CANISTER_ID_PAYMENT; 14 | 15 | export const createActor = (canisterId, options = {}) => { 16 | const agent = options.agent || new HttpAgent({ ...options.agentOptions }); 17 | 18 | if (options.agent && options.agentOptions) { 19 | console.warn( 20 | "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent." 21 | ); 22 | } 23 | 24 | // Fetch root key for certificate validation during development 25 | if (process.env.DFX_NETWORK !== "ic") { 26 | agent.fetchRootKey().catch((err) => { 27 | console.warn( 28 | "Unable to fetch root key. Check to ensure that your local replica is running" 29 | ); 30 | console.error(err); 31 | }); 32 | } 33 | 34 | // Creates an actor with using the candid interface and the HttpAgent 35 | return Actor.createActor(idlFactory, { 36 | agent, 37 | canisterId, 38 | ...options.actorOptions, 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /src/declarations/payment/payment.did: -------------------------------------------------------------------------------- 1 | type GetTransactionReceiptResult = variant { 2 | Ok : opt TransactionReceipt; 3 | Err : RpcError; 4 | }; 5 | type HttpOutcallError = variant { 6 | IcError : record { code : RejectionCode; message : text }; 7 | InvalidHttpJsonRpcResponse : record { 8 | status : nat16; 9 | body : text; 10 | parsingError : opt text; 11 | }; 12 | }; 13 | type ICRC1TransferError = variant { 14 | GenericError : record { message : text; error_code : nat }; 15 | TemporarilyUnavailable; 16 | BadBurn : record { min_burn_amount : nat }; 17 | Duplicate : record { duplicate_of : nat }; 18 | BadFee : record { expected_fee : nat }; 19 | CreatedInFuture : record { ledger_time : nat64 }; 20 | TooOld; 21 | InsufficientFunds : record { balance : nat }; 22 | }; 23 | type ICRC2ApproveError = variant { 24 | GenericError : record { message : text; error_code : nat }; 25 | TemporarilyUnavailable; 26 | Duplicate : record { duplicate_of : nat }; 27 | BadFee : record { expected_fee : nat }; 28 | AllowanceChanged : record { current_allowance : nat }; 29 | CreatedInFuture : record { ledger_time : nat64 }; 30 | TooOld; 31 | Expired : record { ledger_time : nat64 }; 32 | InsufficientFunds : record { balance : nat }; 33 | }; 34 | type JsonRpcError = record { code : int64; message : text }; 35 | type LogEntry = record { 36 | transactionHash : opt text; 37 | blockNumber : opt nat; 38 | data : text; 39 | blockHash : opt text; 40 | transactionIndex : opt nat; 41 | topics : vec text; 42 | address : text; 43 | logIndex : opt nat; 44 | removed : bool; 45 | }; 46 | type ProviderError = variant { 47 | TooFewCycles : record { expected : nat; received : nat }; 48 | MissingRequiredProvider; 49 | ProviderNotFound; 50 | NoPermission; 51 | }; 52 | type RejectionCode = variant { 53 | NoError; 54 | CanisterError; 55 | SysTransient; 56 | DestinationInvalid; 57 | Unknown; 58 | SysFatal; 59 | CanisterReject; 60 | }; 61 | type Result = variant { Ok : nat; Err : ICRC2ApproveError }; 62 | type Result_1 = variant { Ok : nat; Err : ICRC1TransferError }; 63 | type Result_2 = variant { Ok : RetrieveEthRequest; Err : WithdrawalError }; 64 | type RetrieveEthRequest = record { block_index : nat }; 65 | type RpcError = variant { 66 | JsonRpcError : JsonRpcError; 67 | ProviderError : ProviderError; 68 | ValidationError : ValidationError; 69 | HttpOutcallError : HttpOutcallError; 70 | }; 71 | type TransactionReceipt = record { 72 | to : text; 73 | status : nat; 74 | transactionHash : text; 75 | blockNumber : nat; 76 | from : text; 77 | logs : vec LogEntry; 78 | blockHash : text; 79 | "type" : text; 80 | transactionIndex : nat; 81 | effectiveGasPrice : nat; 82 | logsBloom : text; 83 | contractAddress : opt text; 84 | gasUsed : nat; 85 | }; 86 | type ValidationError = variant { 87 | CredentialPathNotAllowed; 88 | HostNotAllowed : text; 89 | CredentialHeaderNotAllowed; 90 | UrlParseError : text; 91 | Custom : text; 92 | InvalidHex : text; 93 | }; 94 | type VerifiedTransactionDetails = record { from : text; amount : nat }; 95 | type WithdrawalError = variant { 96 | TemporarilyUnavailable : text; 97 | InsufficientAllowance : record { allowance : nat }; 98 | AmountTooLow : record { min_withdrawal_amount : nat }; 99 | InsufficientFunds : record { balance : nat }; 100 | }; 101 | service : { 102 | approve : (nat) -> (Result); 103 | balance : () -> (nat); 104 | buy_item : (text, text) -> (nat64); 105 | canister_deposit_principal : () -> (text) query; 106 | get_items : () -> (vec record { text; nat }) query; 107 | get_receipt : (text) -> (GetTransactionReceiptResult); 108 | get_transaction_list : () -> (vec record { text; text }) query; 109 | set_item : (text, nat) -> (); 110 | transfer : (text, nat) -> (Result_1); 111 | verify_transaction : (text) -> (VerifiedTransactionDetails); 112 | withdraw : (nat, text) -> (Result_2); 113 | } 114 | -------------------------------------------------------------------------------- /src/declarations/payment/payment.did.d.ts: -------------------------------------------------------------------------------- 1 | import type { Principal } from '@dfinity/principal'; 2 | import type { ActorMethod } from '@dfinity/agent'; 3 | import type { IDL } from '@dfinity/candid'; 4 | 5 | export type GetTransactionReceiptResult = { 'Ok' : [] | [TransactionReceipt] } | 6 | { 'Err' : RpcError }; 7 | export type HttpOutcallError = { 8 | 'IcError' : { 'code' : RejectionCode, 'message' : string } 9 | } | 10 | { 11 | 'InvalidHttpJsonRpcResponse' : { 12 | 'status' : number, 13 | 'body' : string, 14 | 'parsingError' : [] | [string], 15 | } 16 | }; 17 | export type ICRC1TransferError = { 18 | 'GenericError' : { 'message' : string, 'error_code' : bigint } 19 | } | 20 | { 'TemporarilyUnavailable' : null } | 21 | { 'BadBurn' : { 'min_burn_amount' : bigint } } | 22 | { 'Duplicate' : { 'duplicate_of' : bigint } } | 23 | { 'BadFee' : { 'expected_fee' : bigint } } | 24 | { 'CreatedInFuture' : { 'ledger_time' : bigint } } | 25 | { 'TooOld' : null } | 26 | { 'InsufficientFunds' : { 'balance' : bigint } }; 27 | export type ICRC2ApproveError = { 28 | 'GenericError' : { 'message' : string, 'error_code' : bigint } 29 | } | 30 | { 'TemporarilyUnavailable' : null } | 31 | { 'Duplicate' : { 'duplicate_of' : bigint } } | 32 | { 'BadFee' : { 'expected_fee' : bigint } } | 33 | { 'AllowanceChanged' : { 'current_allowance' : bigint } } | 34 | { 'CreatedInFuture' : { 'ledger_time' : bigint } } | 35 | { 'TooOld' : null } | 36 | { 'Expired' : { 'ledger_time' : bigint } } | 37 | { 'InsufficientFunds' : { 'balance' : bigint } }; 38 | export interface JsonRpcError { 'code' : bigint, 'message' : string } 39 | export interface LogEntry { 40 | 'transactionHash' : [] | [string], 41 | 'blockNumber' : [] | [bigint], 42 | 'data' : string, 43 | 'blockHash' : [] | [string], 44 | 'transactionIndex' : [] | [bigint], 45 | 'topics' : Array, 46 | 'address' : string, 47 | 'logIndex' : [] | [bigint], 48 | 'removed' : boolean, 49 | } 50 | export type ProviderError = { 51 | 'TooFewCycles' : { 'expected' : bigint, 'received' : bigint } 52 | } | 53 | { 'MissingRequiredProvider' : null } | 54 | { 'ProviderNotFound' : null } | 55 | { 'NoPermission' : null }; 56 | export type RejectionCode = { 'NoError' : null } | 57 | { 'CanisterError' : null } | 58 | { 'SysTransient' : null } | 59 | { 'DestinationInvalid' : null } | 60 | { 'Unknown' : null } | 61 | { 'SysFatal' : null } | 62 | { 'CanisterReject' : null }; 63 | export type Result = { 'Ok' : bigint } | 64 | { 'Err' : ICRC2ApproveError }; 65 | export type Result_1 = { 'Ok' : bigint } | 66 | { 'Err' : ICRC1TransferError }; 67 | export type Result_2 = { 'Ok' : RetrieveEthRequest } | 68 | { 'Err' : WithdrawalError }; 69 | export interface RetrieveEthRequest { 'block_index' : bigint } 70 | export type RpcError = { 'JsonRpcError' : JsonRpcError } | 71 | { 'ProviderError' : ProviderError } | 72 | { 'ValidationError' : ValidationError } | 73 | { 'HttpOutcallError' : HttpOutcallError }; 74 | export interface TransactionReceipt { 75 | 'to' : string, 76 | 'status' : bigint, 77 | 'transactionHash' : string, 78 | 'blockNumber' : bigint, 79 | 'from' : string, 80 | 'logs' : Array, 81 | 'blockHash' : string, 82 | 'type' : string, 83 | 'transactionIndex' : bigint, 84 | 'effectiveGasPrice' : bigint, 85 | 'logsBloom' : string, 86 | 'contractAddress' : [] | [string], 87 | 'gasUsed' : bigint, 88 | } 89 | export type ValidationError = { 'CredentialPathNotAllowed' : null } | 90 | { 'HostNotAllowed' : string } | 91 | { 'CredentialHeaderNotAllowed' : null } | 92 | { 'UrlParseError' : string } | 93 | { 'Custom' : string } | 94 | { 'InvalidHex' : string }; 95 | export interface VerifiedTransactionDetails { 96 | 'from' : string, 97 | 'amount' : bigint, 98 | } 99 | export type WithdrawalError = { 'TemporarilyUnavailable' : string } | 100 | { 'InsufficientAllowance' : { 'allowance' : bigint } } | 101 | { 'AmountTooLow' : { 'min_withdrawal_amount' : bigint } } | 102 | { 'InsufficientFunds' : { 'balance' : bigint } }; 103 | export interface _SERVICE { 104 | 'approve' : ActorMethod<[bigint], Result>, 105 | 'balance' : ActorMethod<[], bigint>, 106 | 'buy_item' : ActorMethod<[string, string], bigint>, 107 | 'canister_deposit_principal' : ActorMethod<[], string>, 108 | 'get_items' : ActorMethod<[], Array<[string, bigint]>>, 109 | 'get_receipt' : ActorMethod<[string], GetTransactionReceiptResult>, 110 | 'get_transaction_list' : ActorMethod<[], Array<[string, string]>>, 111 | 'set_item' : ActorMethod<[string, bigint], undefined>, 112 | 'transfer' : ActorMethod<[string, bigint], Result_1>, 113 | 'verify_transaction' : ActorMethod<[string], VerifiedTransactionDetails>, 114 | 'withdraw' : ActorMethod<[bigint, string], Result_2>, 115 | } 116 | export declare const idlFactory: IDL.InterfaceFactory; 117 | export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; 118 | -------------------------------------------------------------------------------- /src/declarations/payment/payment.did.js: -------------------------------------------------------------------------------- 1 | export const idlFactory = ({ IDL }) => { 2 | const ICRC2ApproveError = IDL.Variant({ 3 | 'GenericError' : IDL.Record({ 4 | 'message' : IDL.Text, 5 | 'error_code' : IDL.Nat, 6 | }), 7 | 'TemporarilyUnavailable' : IDL.Null, 8 | 'Duplicate' : IDL.Record({ 'duplicate_of' : IDL.Nat }), 9 | 'BadFee' : IDL.Record({ 'expected_fee' : IDL.Nat }), 10 | 'AllowanceChanged' : IDL.Record({ 'current_allowance' : IDL.Nat }), 11 | 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }), 12 | 'TooOld' : IDL.Null, 13 | 'Expired' : IDL.Record({ 'ledger_time' : IDL.Nat64 }), 14 | 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }), 15 | }); 16 | const Result = IDL.Variant({ 'Ok' : IDL.Nat, 'Err' : ICRC2ApproveError }); 17 | const LogEntry = IDL.Record({ 18 | 'transactionHash' : IDL.Opt(IDL.Text), 19 | 'blockNumber' : IDL.Opt(IDL.Nat), 20 | 'data' : IDL.Text, 21 | 'blockHash' : IDL.Opt(IDL.Text), 22 | 'transactionIndex' : IDL.Opt(IDL.Nat), 23 | 'topics' : IDL.Vec(IDL.Text), 24 | 'address' : IDL.Text, 25 | 'logIndex' : IDL.Opt(IDL.Nat), 26 | 'removed' : IDL.Bool, 27 | }); 28 | const TransactionReceipt = IDL.Record({ 29 | 'to' : IDL.Text, 30 | 'status' : IDL.Nat, 31 | 'transactionHash' : IDL.Text, 32 | 'blockNumber' : IDL.Nat, 33 | 'from' : IDL.Text, 34 | 'logs' : IDL.Vec(LogEntry), 35 | 'blockHash' : IDL.Text, 36 | 'type' : IDL.Text, 37 | 'transactionIndex' : IDL.Nat, 38 | 'effectiveGasPrice' : IDL.Nat, 39 | 'logsBloom' : IDL.Text, 40 | 'contractAddress' : IDL.Opt(IDL.Text), 41 | 'gasUsed' : IDL.Nat, 42 | }); 43 | const JsonRpcError = IDL.Record({ 'code' : IDL.Int64, 'message' : IDL.Text }); 44 | const ProviderError = IDL.Variant({ 45 | 'TooFewCycles' : IDL.Record({ 'expected' : IDL.Nat, 'received' : IDL.Nat }), 46 | 'MissingRequiredProvider' : IDL.Null, 47 | 'ProviderNotFound' : IDL.Null, 48 | 'NoPermission' : IDL.Null, 49 | }); 50 | const ValidationError = IDL.Variant({ 51 | 'CredentialPathNotAllowed' : IDL.Null, 52 | 'HostNotAllowed' : IDL.Text, 53 | 'CredentialHeaderNotAllowed' : IDL.Null, 54 | 'UrlParseError' : IDL.Text, 55 | 'Custom' : IDL.Text, 56 | 'InvalidHex' : IDL.Text, 57 | }); 58 | const RejectionCode = IDL.Variant({ 59 | 'NoError' : IDL.Null, 60 | 'CanisterError' : IDL.Null, 61 | 'SysTransient' : IDL.Null, 62 | 'DestinationInvalid' : IDL.Null, 63 | 'Unknown' : IDL.Null, 64 | 'SysFatal' : IDL.Null, 65 | 'CanisterReject' : IDL.Null, 66 | }); 67 | const HttpOutcallError = IDL.Variant({ 68 | 'IcError' : IDL.Record({ 'code' : RejectionCode, 'message' : IDL.Text }), 69 | 'InvalidHttpJsonRpcResponse' : IDL.Record({ 70 | 'status' : IDL.Nat16, 71 | 'body' : IDL.Text, 72 | 'parsingError' : IDL.Opt(IDL.Text), 73 | }), 74 | }); 75 | const RpcError = IDL.Variant({ 76 | 'JsonRpcError' : JsonRpcError, 77 | 'ProviderError' : ProviderError, 78 | 'ValidationError' : ValidationError, 79 | 'HttpOutcallError' : HttpOutcallError, 80 | }); 81 | const GetTransactionReceiptResult = IDL.Variant({ 82 | 'Ok' : IDL.Opt(TransactionReceipt), 83 | 'Err' : RpcError, 84 | }); 85 | const ICRC1TransferError = IDL.Variant({ 86 | 'GenericError' : IDL.Record({ 87 | 'message' : IDL.Text, 88 | 'error_code' : IDL.Nat, 89 | }), 90 | 'TemporarilyUnavailable' : IDL.Null, 91 | 'BadBurn' : IDL.Record({ 'min_burn_amount' : IDL.Nat }), 92 | 'Duplicate' : IDL.Record({ 'duplicate_of' : IDL.Nat }), 93 | 'BadFee' : IDL.Record({ 'expected_fee' : IDL.Nat }), 94 | 'CreatedInFuture' : IDL.Record({ 'ledger_time' : IDL.Nat64 }), 95 | 'TooOld' : IDL.Null, 96 | 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }), 97 | }); 98 | const Result_1 = IDL.Variant({ 'Ok' : IDL.Nat, 'Err' : ICRC1TransferError }); 99 | const VerifiedTransactionDetails = IDL.Record({ 100 | 'from' : IDL.Text, 101 | 'amount' : IDL.Nat, 102 | }); 103 | const RetrieveEthRequest = IDL.Record({ 'block_index' : IDL.Nat }); 104 | const WithdrawalError = IDL.Variant({ 105 | 'TemporarilyUnavailable' : IDL.Text, 106 | 'InsufficientAllowance' : IDL.Record({ 'allowance' : IDL.Nat }), 107 | 'AmountTooLow' : IDL.Record({ 'min_withdrawal_amount' : IDL.Nat }), 108 | 'InsufficientFunds' : IDL.Record({ 'balance' : IDL.Nat }), 109 | }); 110 | const Result_2 = IDL.Variant({ 111 | 'Ok' : RetrieveEthRequest, 112 | 'Err' : WithdrawalError, 113 | }); 114 | return IDL.Service({ 115 | 'approve' : IDL.Func([IDL.Nat], [Result], []), 116 | 'balance' : IDL.Func([], [IDL.Nat], []), 117 | 'buy_item' : IDL.Func([IDL.Text, IDL.Text], [IDL.Nat64], []), 118 | 'canister_deposit_principal' : IDL.Func([], [IDL.Text], ['query']), 119 | 'get_items' : IDL.Func( 120 | [], 121 | [IDL.Vec(IDL.Tuple(IDL.Text, IDL.Nat))], 122 | ['query'], 123 | ), 124 | 'get_receipt' : IDL.Func([IDL.Text], [GetTransactionReceiptResult], []), 125 | 'get_transaction_list' : IDL.Func( 126 | [], 127 | [IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text))], 128 | ['query'], 129 | ), 130 | 'set_item' : IDL.Func([IDL.Text, IDL.Nat], [], []), 131 | 'transfer' : IDL.Func([IDL.Text, IDL.Nat], [Result_1], []), 132 | 'verify_transaction' : IDL.Func( 133 | [IDL.Text], 134 | [VerifiedTransactionDetails], 135 | [], 136 | ), 137 | 'withdraw' : IDL.Func([IDL.Nat, IDL.Text], [Result_2], []), 138 | }); 139 | }; 140 | export const init = ({ IDL }) => { return []; }; 141 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from "next/app" 2 | import React from "react" 3 | import "styles/global.css" 4 | 5 | const App: React.FC = ({ Component, pageProps }) => { 6 | return ( 7 |
8 | 11 |
12 | 13 |
14 | ) 15 | } 16 | 17 | export default App 18 | -------------------------------------------------------------------------------- /src/pages/history.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head" 2 | import { useQueryCall } from "service/payment" 3 | import styles from "styles/History.module.css" 4 | 5 | import Image from "next/image" 6 | import { formatEther } from "viem" 7 | 8 | interface purchaseProps {} 9 | 10 | const purchase: React.FC = ({}) => { 11 | const { loading, error, data } = useQueryCall({ 12 | functionName: "get_transaction_list" 13 | }) 14 | 15 | const { data: balance, loading: loadingBalance } = useQueryCall({ 16 | functionName: "balance" 17 | }) 18 | 19 | return ( 20 |
21 | 22 | Purchase History 23 | 24 |
25 |

Purchase History

26 | {loadingBalance &&
Fetching balance...
} 27 | {balance ? ( 28 |
29 | Balance: {formatEther(balance)} ckETH 30 |
31 | ) : null} 32 | {loading &&
Processing Purchase on ICP...
} 33 | {error ?
{error.toString()}
: null} 34 | {data ? ( 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | {data.map(([hash, buyer]) => { 44 | return ( 45 | 46 | 51 | 52 | 53 | ) 54 | })} 55 | 56 |
HashBuyer
47 | 48 | {hash} 49 | 50 | {buyer}
57 | ) : null} 58 |
59 | 74 |
75 | ) 76 | } 77 | 78 | export default purchase 79 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head" 2 | 3 | import styles from "styles/Home.module.css" 4 | 5 | import Wallet from "components/Wallet" 6 | import Image from "next/image" 7 | import { config } from "service/config" 8 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query" 9 | import { WagmiProvider } from "wagmi" 10 | 11 | const queryClient = new QueryClient() 12 | 13 | function HomePage() { 14 | return ( 15 |
16 | 17 | ETH payment 18 | 19 |
20 |

21 | Direct Ethereum Payment on the Internet Computer 22 |

23 | 24 | 25 | 26 | 27 | 28 |
29 | 44 |
45 | ) 46 | } 47 | 48 | export default HomePage 49 | -------------------------------------------------------------------------------- /src/service/abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_cketh_minter_main_address", 7 | "type": "address" 8 | } 9 | ], 10 | "stateMutability": "nonpayable", 11 | "type": "constructor" 12 | }, 13 | { 14 | "anonymous": false, 15 | "inputs": [ 16 | { 17 | "indexed": true, 18 | "internalType": "address", 19 | "name": "from", 20 | "type": "address" 21 | }, 22 | { 23 | "indexed": false, 24 | "internalType": "uint256", 25 | "name": "value", 26 | "type": "uint256" 27 | }, 28 | { 29 | "indexed": true, 30 | "internalType": "bytes32", 31 | "name": "principal", 32 | "type": "bytes32" 33 | } 34 | ], 35 | "name": "ReceivedEth", 36 | "type": "event" 37 | }, 38 | { 39 | "anonymous": false, 40 | "inputs": [ 41 | { 42 | "indexed": true, 43 | "internalType": "address", 44 | "name": "to", 45 | "type": "address" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "uint256", 50 | "name": "value", 51 | "type": "uint256" 52 | } 53 | ], 54 | "name": "SentEth", 55 | "type": "event" 56 | }, 57 | { 58 | "inputs": [ 59 | { 60 | "internalType": "bytes32", 61 | "name": "_principal", 62 | "type": "bytes32" 63 | } 64 | ], 65 | "name": "deposit", 66 | "outputs": [], 67 | "stateMutability": "payable", 68 | "type": "function" 69 | }, 70 | { 71 | "inputs": [], 72 | "name": "getMinterAddress", 73 | "outputs": [ 74 | { 75 | "internalType": "address", 76 | "name": "", 77 | "type": "address" 78 | } 79 | ], 80 | "stateMutability": "view", 81 | "type": "function" 82 | } 83 | ] -------------------------------------------------------------------------------- /src/service/config.ts: -------------------------------------------------------------------------------- 1 | import { createClient, http } from "viem" 2 | import { sepolia } from "viem/chains" 3 | import { createConfig } from "wagmi" 4 | import { injected, walletConnect } from "wagmi/connectors" 5 | 6 | export const projectId = process.env.NEXT_PUBLIC_WC_PROJECT_ID! 7 | 8 | export const config = createConfig({ 9 | chains: [sepolia], 10 | connectors: [ 11 | walletConnect({ 12 | projectId, 13 | metadata: { 14 | name: "ICPPayment", 15 | description: "Internet Computer Payment", 16 | url: "https://github.com/B3Pay", 17 | icons: ["https://avatars.githubusercontent.com/u/121541974"] 18 | } 19 | }), 20 | injected() 21 | ], 22 | client({ chain }) { 23 | return createClient({ chain, transport: http() }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /src/service/payment.ts: -------------------------------------------------------------------------------- 1 | import { createReactor } from "@ic-reactor/react" 2 | import { canisterId, idlFactory, payment } from "declarations/payment" 3 | 4 | export const { useActorState, useUpdateCall, useQueryCall } = createReactor< 5 | typeof payment 6 | >({ 7 | canisterId, 8 | idlFactory, 9 | host: process.env.NEXT_PUBLIC_IC_HOST 10 | }) 11 | -------------------------------------------------------------------------------- /src/styles/History.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 0.5rem; 3 | } 4 | 5 | .main { 6 | padding: 1rem 0; 7 | flex: 1; 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: center; 11 | align-items: center; 12 | gap: 1rem; 13 | } 14 | 15 | .title { 16 | margin: 0; 17 | line-height: 1.15; 18 | font-size: 2rem; 19 | text-align: center; 20 | } 21 | 22 | .logo { 23 | width: 140px; 24 | } 25 | 26 | .footer { 27 | width: 100%; 28 | height: 50px; 29 | border-top: 1px solid #eaeaea; 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | } 34 | 35 | .table { 36 | width: 100%; 37 | border-collapse: collapse; 38 | } 39 | 40 | .tableCell, 41 | .tableHeader { 42 | border: 1px solid #ccc; 43 | text-align: left; 44 | padding: 8px; 45 | overflow: hidden; 46 | text-overflow: ellipsis; 47 | white-space: nowrap; 48 | max-width: 150px; 49 | } -------------------------------------------------------------------------------- /src/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | padding: 0 0.5rem; 3 | } 4 | 5 | .main { 6 | padding: 1rem 0; 7 | flex: 1; 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: center; 11 | align-items: center; 12 | gap: 1rem; 13 | } 14 | 15 | .title { 16 | margin: 0; 17 | line-height: 1.15; 18 | font-size: 2rem; 19 | text-align: center; 20 | } 21 | 22 | .logo { 23 | width: 140px; 24 | } 25 | 26 | .footer { 27 | width: 100%; 28 | height: 50px; 29 | border-top: 1px solid #eaeaea; 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | } -------------------------------------------------------------------------------- /src/styles/Item.module.css: -------------------------------------------------------------------------------- 1 | .item { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | width: 100%; 7 | margin: 0 auto; 8 | padding: 0 1rem; 9 | text-align: center; 10 | border: 1px solid #eaeaea; 11 | padding: 2rem 2rem; 12 | } 13 | 14 | 15 | .item h3 { 16 | margin: 0 0 1rem 0; 17 | font-size: 1.5rem; 18 | } 19 | 20 | .item p { 21 | margin: 0; 22 | font-size: 1.25rem; 23 | line-height: 1.5; 24 | } 25 | 26 | .item button { 27 | font-size: 1rem; 28 | margin-left: 6px; 29 | margin-right: 6px; 30 | background-color: #b1d5ff; 31 | } -------------------------------------------------------------------------------- /src/styles/Shop.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | margin: 1rem; 7 | } 8 | 9 | .items { 10 | width: 100%; 11 | display: grid; 12 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 13 | grid-gap: 1rem; 14 | margin: 1rem; 15 | } 16 | 17 | .refresh { 18 | width: 50px; 19 | } 20 | 21 | .nav { 22 | display: flex; 23 | justify-content: space-between; 24 | align-items: center; 25 | width: 100%; 26 | } -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | line-height: 1.6; 8 | font-size: 18px; 9 | background-color: #f0f0f0; 10 | } 11 | 12 | * { 13 | box-sizing: border-box; 14 | } 15 | 16 | a { 17 | color: #0070f3; 18 | text-decoration: none; 19 | } 20 | 21 | a:hover { 22 | text-decoration: underline; 23 | } 24 | 25 | img { 26 | max-width: 100%; 27 | display: block; 28 | } 29 | 30 | button { 31 | width: 150px; 32 | height: 40px; 33 | font-size: 1rem; 34 | margin-left: 6px; 35 | margin-right: 6px; 36 | background-color: #b1d5ff; 37 | } 38 | 39 | section { 40 | padding: 6px; 41 | width: 600px 42 | } 43 | 44 | input { 45 | padding: 4px; 46 | margin: 0px 12px; 47 | width: 200px; 48 | height: 40px; 49 | font-size: 1rem; 50 | } 51 | 52 | label { 53 | font-size: 1.1rem; 54 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "src/**/*"], 27 | "exclude": ["node_modules", "out"] 28 | } 29 | --------------------------------------------------------------------------------