├── .gitpod.Dockerfile ├── frontend ├── assets │ ├── favicon.ico │ ├── logo-white.svg │ ├── logo-black.svg │ └── global.css ├── start.sh ├── package.json ├── index.js ├── index.html └── near-wallet.js ├── contract ├── build.sh ├── deploy.sh ├── tsconfig.json ├── package.json ├── src │ └── contract.ts └── README.md ├── .gitpod.yml ├── integration-tests ├── ava.config.cjs ├── package.json └── src │ └── main.ava.ts ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── package.json ├── LICENSE └── README.md /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | 3 | RUN sudo install-packages xdg-utils -------------------------------------------------------------------------------- /frontend/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailisp/hello-near-js/master/frontend/assets/favicon.ico -------------------------------------------------------------------------------- /contract/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo ">> Building contract" 4 | 5 | near-sdk-js build src/contract.ts build/contract.wasm -------------------------------------------------------------------------------- /contract/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # build the contract 4 | npm run build 5 | 6 | # deploy the contract 7 | near dev-deploy --wasmFile build/contract.wasm -------------------------------------------------------------------------------- /contract/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "target": "es5", 5 | "noEmit": true 6 | }, 7 | "exclude": [ 8 | "node_modules" 9 | ], 10 | } -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | tasks: 5 | - init: echo "welcome" 6 | command: npm i && npm run deploy && npm run start 7 | 8 | ports: 9 | - port: 1234 10 | onOpen: open-browser -------------------------------------------------------------------------------- /integration-tests/ava.config.cjs: -------------------------------------------------------------------------------- 1 | require("util").inspect.defaultOptions.depth = 5; // Increase AVA's printing depth 2 | 3 | module.exports = { 4 | timeout: "300000", 5 | files: ["src/*.ava.ts"], 6 | failWithoutAssertions: false, 7 | extensions: ["ts"], 8 | require: ["ts-node/register"], 9 | }; 10 | -------------------------------------------------------------------------------- /integration-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "integration-tests", 3 | "version": "1.0.0", 4 | "license": "(MIT AND Apache-2.0)", 5 | "scripts": { 6 | "test": "ava" 7 | }, 8 | "devDependencies": { 9 | "@types/bn.js": "^5.1.0", 10 | "@types/node": "^18.6.2", 11 | "ava": "^4.2.0", 12 | "near-workspaces": "^3.2.1", 13 | "ts-node": "^10.8.0", 14 | "typescript": "^4.7.2" 15 | }, 16 | "dependencies": {} 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: push 3 | jobs: 4 | workflows: 5 | strategy: 6 | matrix: 7 | platform: [ubuntu-latest, macos-latest] 8 | runs-on: ${{ matrix.platform }} 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: "16" 14 | - name: Install modules 15 | run: yarn 16 | - name: Run tests 17 | run: yarn test -------------------------------------------------------------------------------- /contract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contract", 3 | "version": "1.0.0", 4 | "license": "(MIT AND Apache-2.0)", 5 | "type": "module", 6 | "scripts": { 7 | "build": "./build.sh", 8 | "deploy": "./deploy.sh", 9 | "test": "echo no unit testing" 10 | }, 11 | "dependencies": { 12 | "near-cli": "^3.4.0", 13 | "near-sdk-js": "0.7.0" 14 | }, 15 | "devDependencies": { 16 | "typescript": "^4.8.4", 17 | "ts-morph": "^16.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contract/src/contract.ts: -------------------------------------------------------------------------------- 1 | import { NearBindgen, near, call, view } from 'near-sdk-js'; 2 | 3 | @NearBindgen({}) 4 | class HelloNear { 5 | greeting: string = "Hello"; 6 | 7 | @view({}) // This method is read-only and can be called for free 8 | get_greeting(): string { 9 | return this.greeting; 10 | } 11 | 12 | @call({}) // This method changes the state, for which it cost gas 13 | set_greeting({ greeting }: { greeting: string }): void { 14 | // Record a log permanently to the blockchain! 15 | near.log(`Saving greeting ${greeting}`); 16 | this.greeting = greeting; 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | # Developer note: near.gitignore will be renamed to .gitignore upon project creation 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # build 9 | **/out 10 | **/dist 11 | **/node_modules 12 | 13 | # keys 14 | **/neardev 15 | 16 | # testing 17 | /coverage 18 | 19 | # production 20 | **/build 21 | 22 | # misc 23 | .DS_Store 24 | .env.local 25 | .env.development.local 26 | .env.test.local 27 | .env.production.local 28 | /.cache 29 | 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | -------------------------------------------------------------------------------- /frontend/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GREEN='\033[1;32m' 4 | NC='\033[0m' # No Color 5 | 6 | CONTRACT_DIRECTORY=../contract 7 | DEV_ACCOUNT_FILE="${CONTRACT_DIRECTORY}/neardev/dev-account.env" 8 | 9 | start () { 10 | echo The app is starting! 11 | env-cmd -f $DEV_ACCOUNT_FILE parcel index.html --open 12 | } 13 | 14 | alert () { 15 | echo "======================================================" 16 | echo "It looks like you forgot to deploy your contract" 17 | echo ">> Run ${GREEN}'npm run deploy'${NC} from the 'root' directory" 18 | echo "======================================================" 19 | } 20 | 21 | if [ -f "$DEV_ACCOUNT_FILE" ]; then 22 | start 23 | else 24 | alert 25 | fi 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-near-js", 3 | "version": "1.0.0", 4 | "license": "(MIT AND Apache-2.0)", 5 | "scripts": { 6 | "start": "cd frontend && npm run start", 7 | "deploy": "cd contract && npm run deploy", 8 | "build": "npm run build:contract && npm run build:web", 9 | "build:web": "cd frontend && npm run build", 10 | "build:contract": "cd contract && npm run build", 11 | "test": "npm run build:contract && npm run test:unit && npm run test:integration", 12 | "test:unit": "cd contract && npm test", 13 | "test:integration": "cd integration-tests && npm test -- -- \"./contract/build/contract.wasm\"", 14 | "postinstall": "cd frontend && npm install && cd .. && cd integration-tests && npm install && cd .. && cd contract && npm install" 15 | }, 16 | "devDependencies": { 17 | "near-cli": "^3.3.0" 18 | }, 19 | "dependencies": {} 20 | } -------------------------------------------------------------------------------- /frontend/assets/logo-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/assets/logo-black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-near-app", 3 | "version": "1.0.0", 4 | "license": "(MIT AND Apache-2.0)", 5 | "scripts": { 6 | "start": "./start.sh", 7 | "build": "parcel build index.html --public-url ./" 8 | }, 9 | "devDependencies": { 10 | "env-cmd": "^10.1.0", 11 | "events": "^3.3.0", 12 | "nodemon": "^2.0.16", 13 | "parcel": "^2.7.0", 14 | "process": "^0.11.10" 15 | }, 16 | "dependencies": { 17 | "@near-wallet-selector/core": "^7.0.0", 18 | "@near-wallet-selector/ledger": "^7.0.0", 19 | "@near-wallet-selector/math-wallet": "^7.0.0", 20 | "@near-wallet-selector/meteor-wallet": "^7.0.0", 21 | "@near-wallet-selector/modal-ui": "^7.0.0", 22 | "@near-wallet-selector/my-near-wallet": "^7.0.0", 23 | "@near-wallet-selector/near-wallet": "^7.0.0", 24 | "@near-wallet-selector/nightly": "^7.0.0", 25 | "@near-wallet-selector/nightly-connect": "^7.0.0", 26 | "@near-wallet-selector/sender": "^7.0.0", 27 | "@near-wallet-selector/wallet-connect": "^7.0.0", 28 | "near-api-js": "^0.44.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Examples for building on the NEAR blockchain 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. -------------------------------------------------------------------------------- /integration-tests/src/main.ava.ts: -------------------------------------------------------------------------------- 1 | import { Worker, NearAccount } from 'near-workspaces'; 2 | import anyTest, { TestFn } from 'ava'; 3 | 4 | const test = anyTest as TestFn<{ 5 | worker: Worker; 6 | accounts: Record; 7 | }>; 8 | 9 | test.beforeEach(async (t) => { 10 | // Init the worker and start a Sandbox server 11 | const worker = await Worker.init(); 12 | 13 | // Deploy contract 14 | const root = worker.rootAccount; 15 | const contract = await root.createSubAccount('test-account'); 16 | 17 | // Get wasm file path from package.json test script in folder above 18 | await contract.deploy(process.argv[2]); 19 | 20 | // Save state for test runs, it is unique for each test 21 | t.context.worker = worker; 22 | t.context.accounts = { root, contract }; 23 | }); 24 | 25 | test.afterEach(async (t) => { 26 | // Stop Sandbox server 27 | await t.context.worker.tearDown().catch((error) => { 28 | console.log('Failed to stop the Sandbox:', error); 29 | }); 30 | }); 31 | 32 | test('returns the default greeting', async (t) => { 33 | const { contract } = t.context.accounts; 34 | const greeting: string = await contract.view('get_greeting', {}); 35 | t.is(greeting, 'Hello'); 36 | }); 37 | 38 | test('changes the greeting', async (t) => { 39 | const { root, contract } = t.context.accounts; 40 | await root.call(contract, 'set_greeting', { greeting: 'Howdy' }); 41 | const greeting: string = await contract.view('get_greeting', {}); 42 | t.is(greeting, 'Howdy'); 43 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello Near 👋 2 | [![](https://img.shields.io/badge/⋈%20Examples-Basics-green)](https://docs.near.org/tutorials/welcome) 3 | [![](https://img.shields.io/badge/Gitpod-Ready-orange)](https://gitpod.io/#/https://github.com/near-examples/hello-near-js) 4 | [![](https://img.shields.io/badge/Contract-js-yellow)](https://docs.near.org/develop/contracts/anatomy) 5 | [![](https://img.shields.io/badge/Frontend-JS-yellow)](https://docs.near.org/develop/integrate/frontend) 6 | [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fnear-examples%2Fhello-near-js%2Fbadge&style=flat&label=Tests)](https://actions-badge.atrox.dev/near-examples/hello-near-js/goto) 7 | 8 | 9 | Hello NEAR! is a friendly decentralized App that stores a greeting message. It is one of the simplest smart contracts you can create in NEAR, and the perfect gateway to introduce yourself in the world of smart contracts. 10 | 11 | ![](https://docs.near.org/assets/images/hello-near-banner-af016d03e81a65653c9230b95a05fe4a.png) 12 | 13 | 14 | # What This Example Shows 15 | 16 | 1. How to store and retrieve information in the NEAR network. 17 | 2. How to integrate a smart contract in a web frontend. 18 | 19 |
20 | 21 | # Quickstart 22 | 23 | Clone this repository locally or [**open it in gitpod**](https://gitpod.io/#/https://github.com/near-examples/hello-near-js). Then follow these steps: 24 | 25 | ### 1. Install Dependencies 26 | ```bash 27 | npm install 28 | ``` 29 | 30 | ### 2. Test the Contract 31 | Deploy your contract in a sandbox and simulate interactions from users. 32 | 33 | ```bash 34 | npm test 35 | ``` 36 | 37 | ### 3. Deploy the Contract 38 | Build the contract and deploy it in a testnet account 39 | ```bash 40 | npm run deploy 41 | ``` 42 | 43 | ### 4. Start the Frontend 44 | Start the web application to interact with your smart contract 45 | ```bash 46 | npm start 47 | ``` 48 | 49 | --- 50 | 51 | # Learn More 52 | 1. Learn more about the contract through its [README](./contract/README.md). 53 | 2. Check [**our documentation**](https://docs.near.org/develop/welcome). -------------------------------------------------------------------------------- /contract/README.md: -------------------------------------------------------------------------------- 1 | # Hello NEAR Contract 2 | 3 | The smart contract exposes two methods to enable storing and retrieving a greeting in the NEAR network. 4 | 5 | ```ts 6 | @NearBindgen({}) 7 | class HelloNear { 8 | greeting: string = "Hello"; 9 | 10 | @view // This method is read-only and can be called for free 11 | get_greeting(): string { 12 | return this.greeting; 13 | } 14 | 15 | @call // This method changes the state, for which it cost gas 16 | set_greeting({ greeting }: { greeting: string }): void { 17 | // Record a log permanently to the blockchain! 18 | near.log(`Saving greeting ${greeting}`); 19 | this.greeting = greeting; 20 | } 21 | } 22 | ``` 23 | 24 |
25 | 26 | # Quickstart 27 | 28 | 1. Make sure you have installed [node.js](https://nodejs.org/en/download/package-manager/) >= 16. 29 | 2. Install the [`NEAR CLI`](https://github.com/near/near-cli#setup) 30 | 31 |
32 | 33 | ## 1. Build and Deploy the Contract 34 | You can automatically compile and deploy the contract in the NEAR testnet by running: 35 | 36 | ```bash 37 | npm run deploy 38 | ``` 39 | 40 | Once finished, check the `neardev/dev-account` file to find the address in which the contract was deployed: 41 | 42 | ```bash 43 | cat ./neardev/dev-account 44 | # e.g. dev-1659899566943-21539992274727 45 | ``` 46 | 47 |
48 | 49 | ## 2. Retrieve the Greeting 50 | 51 | `get_greeting` is a read-only method (aka `view` method). 52 | 53 | `View` methods can be called for **free** by anyone, even people **without a NEAR account**! 54 | 55 | ```bash 56 | # Use near-cli to get the greeting 57 | near view get_greeting 58 | ``` 59 | 60 |
61 | 62 | ## 3. Store a New Greeting 63 | `set_greeting` changes the contract's state, for which it is a `call` method. 64 | 65 | `Call` methods can only be invoked using a NEAR account, since the account needs to pay GAS for the transaction. 66 | 67 | ```bash 68 | # Use near-cli to set a new greeting 69 | near call set_greeting '{"greeting":"howdy"}' --accountId 70 | ``` 71 | 72 | **Tip:** If you would like to call `set_greeting` using your own account, first login into NEAR using: 73 | 74 | ```bash 75 | # Use near-cli to login your NEAR account 76 | near login 77 | ``` 78 | 79 | and then use the logged account to sign the transaction: `--accountId `. -------------------------------------------------------------------------------- /frontend/index.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | import { Wallet } from './near-wallet'; 3 | 4 | const CONTRACT_ADDRESS = process.env.CONTRACT_NAME; 5 | 6 | // When creating the wallet you can optionally ask to create an access key 7 | // Having the key enables to call non-payable methods without interrupting the user to sign 8 | const wallet = new Wallet({ createAccessKeyFor: CONTRACT_ADDRESS }) 9 | 10 | // Setup on page load 11 | window.onload = async () => { 12 | let isSignedIn = await wallet.startUp(); 13 | 14 | if (isSignedIn) { 15 | signedInFlow(); 16 | } else { 17 | signedOutFlow(); 18 | } 19 | 20 | fetchGreeting(); 21 | }; 22 | 23 | // Button clicks 24 | document.querySelector('form').onsubmit = doUserAction; 25 | document.querySelector('#sign-in-button').onclick = () => { wallet.signIn(); }; 26 | document.querySelector('#sign-out-button').onclick = () => { wallet.signOut(); }; 27 | 28 | // Take the new greeting and send it to the contract 29 | async function doUserAction(event) { 30 | event.preventDefault(); 31 | const { greeting } = event.target.elements; 32 | 33 | document.querySelector('#signed-in-flow main') 34 | .classList.add('please-wait'); 35 | 36 | await wallet.callMethod({ method: 'set_greeting', args: { greeting: greeting.value }, contractId: CONTRACT_ADDRESS }); 37 | 38 | // ===== Fetch the data from the blockchain ===== 39 | await fetchGreeting(); 40 | document.querySelector('#signed-in-flow main') 41 | .classList.remove('please-wait'); 42 | } 43 | 44 | // Get greeting from the contract on chain 45 | async function fetchGreeting() { 46 | const currentGreeting = await wallet.viewMethod({ method: 'get_greeting', contractId: CONTRACT_ADDRESS }); 47 | 48 | document.querySelectorAll('[data-behavior=greeting]').forEach(el => { 49 | el.innerText = currentGreeting; 50 | el.value = currentGreeting; 51 | }); 52 | } 53 | 54 | // Display the signed-out-flow container 55 | function signedOutFlow() { 56 | document.querySelector('#signed-in-flow').style.display = 'none'; 57 | document.querySelector('#signed-out-flow').style.display = 'block'; 58 | } 59 | 60 | // Displaying the signed in flow container and fill in account-specific data 61 | function signedInFlow() { 62 | document.querySelector('#signed-out-flow').style.display = 'none'; 63 | document.querySelector('#signed-in-flow').style.display = 'block'; 64 | document.querySelectorAll('[data-behavior=account-id]').forEach(el => { 65 | el.innerText = wallet.accountId; 66 | }); 67 | } -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Welcome to NEAR 10 | 11 | 12 | 13 |
14 |

15 | The contract says: 16 |

17 |

18 | Welcome to NEAR! 19 |

20 |

21 | Your contract is storing a greeting message in the NEAR blockchain. To 22 | change it you need to sign in using the NEAR Wallet. It is very simple, 23 | just use the button below. 24 |

25 |

26 | Do not worry, this app runs in the test network ("testnet"). It works 27 | just like the main network ("mainnet"), but using NEAR Tokens that are 28 | only for testing! 29 |

30 |

31 | 32 |

33 | 34 |
35 | 36 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /frontend/near-wallet.js: -------------------------------------------------------------------------------- 1 | /* A helper file that simplifies using the wallet selector */ 2 | 3 | // near api js 4 | import { providers } from 'near-api-js'; 5 | 6 | // wallet selector UI 7 | import '@near-wallet-selector/modal-ui/styles.css'; 8 | import { setupModal } from '@near-wallet-selector/modal-ui'; 9 | import LedgerIconUrl from '@near-wallet-selector/ledger/assets/ledger-icon.png'; 10 | import MyNearIconUrl from '@near-wallet-selector/my-near-wallet/assets/my-near-wallet-icon.png'; 11 | 12 | // wallet selector options 13 | import { setupWalletSelector } from '@near-wallet-selector/core'; 14 | import { setupLedger } from '@near-wallet-selector/ledger'; 15 | import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet'; 16 | 17 | const THIRTY_TGAS = '30000000000000'; 18 | const NO_DEPOSIT = '0'; 19 | 20 | // Wallet that simplifies using the wallet selector 21 | export class Wallet { 22 | walletSelector; 23 | wallet; 24 | network; 25 | createAccessKeyFor; 26 | 27 | constructor({ createAccessKeyFor = undefined, network = 'testnet' }) { 28 | // Login to a wallet passing a contractId will create a local 29 | // key, so the user skips signing non-payable transactions. 30 | // Omitting the accountId will result in the user being 31 | // asked to sign all transactions. 32 | this.createAccessKeyFor = createAccessKeyFor 33 | this.network = network 34 | } 35 | 36 | // To be called when the website loads 37 | async startUp() { 38 | this.walletSelector = await setupWalletSelector({ 39 | network: this.network, 40 | modules: [setupMyNearWallet({ iconUrl: MyNearIconUrl }), 41 | setupLedger({ iconUrl: LedgerIconUrl })], 42 | }); 43 | 44 | const isSignedIn = this.walletSelector.isSignedIn(); 45 | 46 | if (isSignedIn) { 47 | this.wallet = await this.walletSelector.wallet(); 48 | this.accountId = this.walletSelector.store.getState().accounts[0].accountId; 49 | } 50 | 51 | return isSignedIn; 52 | } 53 | 54 | // Sign-in method 55 | signIn() { 56 | const description = 'Please select a wallet to sign in.'; 57 | const modal = setupModal(this.walletSelector, { contractId: this.createAccessKeyFor, description }); 58 | modal.show(); 59 | } 60 | 61 | // Sign-out method 62 | signOut() { 63 | this.wallet.signOut(); 64 | this.wallet = this.accountId = this.createAccessKeyFor = null; 65 | window.location.replace(window.location.origin + window.location.pathname); 66 | } 67 | 68 | // Make a read-only call to retrieve information from the network 69 | async viewMethod({ contractId, method, args = {} }) { 70 | const { network } = this.walletSelector.options; 71 | const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); 72 | 73 | let res = await provider.query({ 74 | request_type: 'call_function', 75 | account_id: contractId, 76 | method_name: method, 77 | args_base64: Buffer.from(JSON.stringify(args)).toString('base64'), 78 | finality: 'optimistic', 79 | }); 80 | return JSON.parse(Buffer.from(res.result).toString()); 81 | } 82 | 83 | // Call a method that changes the contract's state 84 | async callMethod({ contractId, method, args = {}, gas = THIRTY_TGAS, deposit = NO_DEPOSIT }) { 85 | // Sign a transaction with the "FunctionCall" action 86 | const outcome = await this.wallet.signAndSendTransaction({ 87 | signerId: this.accountId, 88 | receiverId: contractId, 89 | actions: [ 90 | { 91 | type: 'FunctionCall', 92 | params: { 93 | methodName: method, 94 | args, 95 | gas, 96 | deposit, 97 | }, 98 | }, 99 | ], 100 | }); 101 | 102 | return providers.getTransactionLastResult(outcome) 103 | } 104 | 105 | // Get transaction result from the network 106 | async getTransactionResult(txhash) { 107 | const { network } = this.walletSelector.options; 108 | const provider = new providers.JsonRpcProvider({ url: network.nodeUrl }); 109 | 110 | // Retrieve transaction result from the network 111 | const transaction = await provider.txStatus(txhash, 'unnused'); 112 | return providers.getTransactionLastResult(transaction); 113 | } 114 | } -------------------------------------------------------------------------------- /frontend/assets/global.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | --bg: #efefef; 7 | --fg: #1e1e1e; 8 | --gray: #555; 9 | --light-gray: #ccc; 10 | --shadow: #e6e6e6; 11 | --success: rgb(90, 206, 132); 12 | --primary: #FF585D; 13 | --secondary: #0072CE; 14 | 15 | background-color: var(--bg); 16 | color: var(--fg); 17 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif; 18 | font-size: calc(0.9em + 0.5vw); 19 | line-height: 1.3; 20 | } 21 | 22 | body { 23 | margin: 0; 24 | padding: 1em; 25 | } 26 | 27 | main { 28 | margin: 0 auto; 29 | max-width: 26em; 30 | } 31 | 32 | main.please-wait {} 33 | 34 | h1 { 35 | background-image: url(./logo-black.svg); 36 | background-position: center 1em; 37 | background-repeat: no-repeat; 38 | background-size: auto 1.5em; 39 | margin-top: 0; 40 | padding: 3.5em 0 0; 41 | text-align: center; 42 | font-size: 1.5em; 43 | } 44 | .greeting { 45 | color: var(--secondary); 46 | text-decoration: underline; 47 | } 48 | h2 { 49 | text-align: center; 50 | } 51 | 52 | .please-wait .change { 53 | pointer-events: none; 54 | } 55 | 56 | ul.information { 57 | margin: 2em 0 2em 0; 58 | padding: 0; 59 | text-align: left; 60 | font-size: 0.8em; 61 | 62 | } 63 | .information li:first-child { 64 | border-top: 1px solid var(--light-gray); 65 | } 66 | .information li { 67 | padding: 0.5em 0; 68 | border-bottom: 1px solid var(--light-gray); 69 | list-style: none; 70 | } 71 | 72 | .change { 73 | display: flex; 74 | flex-direction: column; 75 | align-content: stretch; 76 | justify-content: space-evenly; 77 | align-items: stretch; 78 | font-size: 1em; 79 | border: 2px solid var(--light-gray); 80 | padding: 0.5em; 81 | } 82 | .change > div { 83 | display: flex; 84 | align-content: stretch; 85 | justify-content: space-evenly; 86 | align-items: stretch; 87 | } 88 | .change input { 89 | flex: 1; 90 | border-bottom-right-radius: 0; 91 | border-top-right-radius: 0; 92 | } 93 | .change label { 94 | display: block; 95 | text-align: left; 96 | margin-right: 10px; 97 | padding-bottom: 0.5em; 98 | } 99 | .change button { 100 | border-bottom-left-radius: 0; 101 | border-top-left-radius: 0; 102 | } 103 | 104 | a, 105 | .link { 106 | color: var(--primary); 107 | text-decoration: none; 108 | } 109 | a:hover, 110 | a:focus, 111 | .link:hover, 112 | .link:focus { 113 | text-decoration: underline; 114 | } 115 | a:active, 116 | .link:active { 117 | color: var(--secondary); 118 | } 119 | 120 | button, input { 121 | font: inherit; 122 | outline: none; 123 | } 124 | 125 | main.please-wait .change button { 126 | position: relative; 127 | pointer-events: none; 128 | background-color: white; 129 | } 130 | main.please-wait .change button span { 131 | visibility: hidden; 132 | } 133 | 134 | button { 135 | background-color: var(--secondary); 136 | border-radius: 5px; 137 | border: none; 138 | color: #efefef; 139 | cursor: pointer; 140 | padding: 0.3em 0.75em; 141 | transition: transform 30ms; 142 | } 143 | button:hover, button:focus { 144 | box-shadow: 0 0 10em rgba(255, 255, 255, 0.2) inset; 145 | } 146 | 147 | input { 148 | background-color: var(--light-gray); 149 | border: none; 150 | border-radius: 5px 0 0 5px; 151 | caret-color: var(--primary); 152 | color: inherit; 153 | padding: 0.25em 0.5em; 154 | } 155 | input::selection { 156 | background-color: var(--secondary); 157 | color: #efefef; 158 | } 159 | input:focus { 160 | box-shadow: 0 0 10em rgba(0, 0, 0, 0.02) inset; 161 | } 162 | 163 | code { 164 | color: var(--gray); 165 | } 166 | 167 | li { 168 | padding-bottom: 1em; 169 | } 170 | 171 | @media (prefers-color-scheme: dark) { 172 | html { 173 | --bg: #1e1e1e; 174 | --fg: #efefef; 175 | --gray: #aaa; 176 | --shadow: #2a2a2a; 177 | --light-gray: #444; 178 | } 179 | h1 { 180 | background-image: url(./logo-white.svg); 181 | } 182 | input:focus { 183 | box-shadow: 0 0 10em rgba(255, 255, 255, 0.02) inset; 184 | } 185 | } 186 | 187 | main.please-wait .loader, 188 | main.please-wait .loader:after{ 189 | display: inline-block; 190 | } 191 | .loader, 192 | .loader:after { 193 | display: none; 194 | border-radius: 50%; 195 | width: 20px; 196 | height: 20px; 197 | } 198 | .loader { 199 | font-size: 10px; 200 | position: absolute; 201 | top: calc(50% - 10px); 202 | left: calc(50% - 10px); 203 | text-indent: -9999em; 204 | border-top: 3px solid var(--secondary); 205 | border-right: 3px solid var(--secondary); 206 | border-bottom: 3px solid var(--secondary); 207 | border-left: 3px solid #ffffff; 208 | -webkit-transform: translateZ(0); 209 | -ms-transform: translateZ(0); 210 | transform: translateZ(0); 211 | -webkit-animation: load8 1.1s infinite linear; 212 | animation: load8 1.1s infinite linear; 213 | } 214 | @-webkit-keyframes load8 { 215 | 0% { 216 | -webkit-transform: rotate(0deg); 217 | transform: rotate(0deg); 218 | } 219 | 100% { 220 | -webkit-transform: rotate(360deg); 221 | transform: rotate(360deg); 222 | } 223 | } 224 | @keyframes load8 { 225 | 0% { 226 | -webkit-transform: rotate(0deg); 227 | transform: rotate(0deg); 228 | } 229 | 100% { 230 | -webkit-transform: rotate(360deg); 231 | transform: rotate(360deg); 232 | } 233 | } 234 | --------------------------------------------------------------------------------