├── .flake8 ├── .gitignore ├── README.md ├── signing-providers ├── .prettierignore ├── .eslintignore ├── .prettierrc ├── src │ ├── index.js │ ├── helpers.js │ ├── config.devnet.js │ ├── config.js │ ├── auth.js │ ├── iframe-communication.js │ ├── extension.js │ ├── webview.js │ ├── cross-window.js │ ├── metamask.js │ ├── wallet-connect-v2.js │ ├── iframe-wallet.js │ ├── hw.js │ └── web-wallet.js ├── .eslintrc.js ├── dummy-certificate.pem ├── package.json ├── README.md ├── iframe-wallet.html ├── dummy-certificate-key.pem ├── webpack.config.js ├── webview-child.html └── index.html ├── wallet ├── README.md ├── package.json ├── index.js └── basic.js ├── contracts ├── adder.wasm ├── counter.wasm ├── counter.abi.json ├── adder.abi.json ├── example.abi.json └── multisig-full.abi.json ├── codec ├── package.json ├── README.md ├── decodeCustomType.js └── decodeEvent.js ├── testwallets ├── alice.pem └── alice.json └── .github └── workflows └── package-lock-checks.yml /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/** 2 | **/out/** 3 | codec/package-lock.json 4 | .idea 5 | settings.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mx-sdk-js-examples 2 | 3 | Examples of using sdk-js and its satellite packages. 4 | -------------------------------------------------------------------------------- /signing-providers/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | build/ 4 | out/ 5 | .yalc/ 6 | *.d.ts -------------------------------------------------------------------------------- /wallet/README.md: -------------------------------------------------------------------------------- 1 | # Examples: using wallet components 2 | 3 | ```bash 4 | node ./index.js 5 | ``` 6 | -------------------------------------------------------------------------------- /contracts/adder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-js-examples/HEAD/contracts/adder.wasm -------------------------------------------------------------------------------- /contracts/counter.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-sdk-js-examples/HEAD/contracts/counter.wasm -------------------------------------------------------------------------------- /signing-providers/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | build/ 4 | out/ 5 | .yalc/ 6 | *.d.ts 7 | .prettierignore -------------------------------------------------------------------------------- /signing-providers/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": false, 3 | "trailingComma": "all", 4 | "tabWidth": 4, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /codec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@multiversx/sdk-core": "15.0.0", 5 | "minimist": "1.2.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /wallet/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "0.0.0", 4 | "description": "Examples", 5 | "scripts": {}, 6 | "author": "MultiversX", 7 | "dependencies": { 8 | "@multiversx/sdk-core": "15.0.0", 9 | "axios": "^1.11.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /signing-providers/src/index.js: -------------------------------------------------------------------------------- 1 | export * from "./extension"; 2 | export * from "./hw"; 3 | export * from "./metamask"; 4 | export * from "./wallet-connect-v2"; 5 | export * from "./web-wallet"; 6 | export * from "./webview"; 7 | export * from "./cross-window"; 8 | export * from "./iframe-communication"; 9 | export * from "./iframe-wallet"; 10 | -------------------------------------------------------------------------------- /testwallets/alice.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- 2 | NDEzZjQyNTc1ZjdmMjZmYWQzMzE3YTc3ODc3MTIxMmZkYjgwMjQ1ODUwOTgxZTQ4 3 | YjU4YTRmMjVlMzQ0ZThmOTAxMzk0NzJlZmY2ODg2NzcxYTk4MmYzMDgzZGE1ZDQy 4 | MWYyNGMyOTE4MWU2Mzg4ODIyOGRjODFjYTYwZDY5ZTE= 5 | -----END PRIVATE KEY for erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th----- -------------------------------------------------------------------------------- /wallet/index.js: -------------------------------------------------------------------------------- 1 | const { exampleDeriveAccountsFromMnemonic, exampleSignAndBroadcastTransaction, exampleSignMessage, exampleVerifyTransactionSignature, exampleVerifyMessage } = require("./basic"); 2 | 3 | (async () => { 4 | exampleDeriveAccountsFromMnemonic(); 5 | await exampleSignAndBroadcastTransaction(); 6 | await exampleSignMessage(); 7 | await exampleVerifyMessage(); 8 | await exampleVerifyTransactionSignature(); 9 | })(); 10 | -------------------------------------------------------------------------------- /codec/README.md: -------------------------------------------------------------------------------- 1 | # Codec utilities (examples) 2 | 3 | Decode a transaction event: 4 | 5 | ``` 6 | node ./decodeEvent.js --abi=../contracts/example.abi.json --event=deposit --api=https://testnet-api.multiversx.com --tx=532087e5021c9ab8be8a4db5ad843cfe0610761f6334d9693b3765992fd05f67 7 | ``` 8 | 9 | Decode a value of a custom type (a structure): 10 | 11 | ``` 12 | node ./decodeCustomType.js --abi=../contracts/example.abi.json --type=DepositEvent --data=00000000000003db000000 13 | ``` 14 | -------------------------------------------------------------------------------- /signing-providers/src/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Displays a message and an optional outcome using console.log and alert. 3 | * 4 | * @param {string} message - The message to be displayed. 5 | * @param {Object} [outcome] - The optional outcome object to be displayed. 6 | * @returns {void} 7 | */ 8 | export const displayOutcome = (message, outcome) => { 9 | if (!outcome) { 10 | console.log(message); 11 | alert(message); 12 | return; 13 | } 14 | 15 | console.log(message, outcome); 16 | alert(`${message}\n${JSON.stringify(outcome, null, 4)}`); 17 | }; 18 | -------------------------------------------------------------------------------- /signing-providers/src/config.devnet.js: -------------------------------------------------------------------------------- 1 | import { WALLET_PROVIDER_DEVNET } from "@multiversx/sdk-web-wallet-provider"; 2 | 3 | export const CHAIN_ID = "D"; 4 | export const NETWORK_NAME = "Devnet"; 5 | export const WALLET_PROVIDER_URL = WALLET_PROVIDER_DEVNET; 6 | export const API_URL = "https://devnet-api.multiversx.com"; 7 | export const PROXY_URL = "https://devnet-gateway.multiversx.com"; 8 | export const WALLET_URL = "https://devnet-wallet.multiversx.com"; 9 | 10 | // Generate your own WalletConnect 2 ProjectId here: https://cloud.walletconnect.com/app 11 | export const WALLET_CONNECT_PROJECT_ID = "9b1a9564f91cb659ffe21b73d5c4e2d8"; 12 | export const WALLET_CONNECT_RELAY_URL = "wss://relay.walletconnect.com"; 13 | export const METAMASK_SNAP_WALLET_ADDRESS = "https://devnet-snap-wallet.multiversx.com"; 14 | -------------------------------------------------------------------------------- /signing-providers/src/config.js: -------------------------------------------------------------------------------- 1 | import { WALLET_PROVIDER_TESTNET } from "@multiversx/sdk-web-wallet-provider"; 2 | 3 | export const CHAIN_ID = "T"; 4 | export const NETWORK_NAME = "Testnet"; 5 | export const WALLET_PROVIDER_URL = WALLET_PROVIDER_TESTNET; 6 | export const API_URL = "https://testnet-api.multiversx.com"; 7 | export const PROXY_URL = "https://testnet-gateway.multiversx.com"; 8 | export const WALLET_URL = "https://testnet-wallet.multiversx.com"; 9 | 10 | // Generate your own WalletConnect 2 ProjectId here: https://cloud.walletconnect.com/app 11 | export const WALLET_CONNECT_PROJECT_ID = "9b1a9564f91cb659ffe21b73d5c4e2d8"; 12 | export const WALLET_CONNECT_RELAY_URL = "wss://relay.walletconnect.com"; 13 | export const METAMASK_SNAP_WALLET_ADDRESS = "https://testnet-snap-wallet.multiversx.com"; 14 | -------------------------------------------------------------------------------- /contracts/counter.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "constructor": { 4 | "inputs": [], 5 | "outputs": [] 6 | }, 7 | "endpoints": [ 8 | { 9 | "name": "increment", 10 | "inputs": [], 11 | "outputs": [ 12 | { 13 | "type": "i64" 14 | } 15 | ] 16 | }, 17 | { 18 | "name": "decrement", 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "type": "i64" 23 | } 24 | ] 25 | }, 26 | { 27 | "name": "get", 28 | "inputs": [], 29 | "outputs": [ 30 | { 31 | "type": "i64" 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /testwallets/alice.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 4, 3 | "id": "0dc10c02-b59b-4bac-9710-6b2cfa4284ba", 4 | "address": "0139472eff6886771a982f3083da5d421f24c29181e63888228dc81ca60d69e1", 5 | "bech32": "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", 6 | "crypto": { 7 | "ciphertext": "4c41ef6fdfd52c39b1585a875eb3c86d30a315642d0e35bb8205b6372c1882f135441099b11ff76345a6f3a930b5665aaf9f7325a32c8ccd60081c797aa2d538", 8 | "cipherparams": { 9 | "iv": "033182afaa1ebaafcde9ccc68a5eac31" 10 | }, 11 | "cipher": "aes-128-ctr", 12 | "kdf": "scrypt", 13 | "kdfparams": { 14 | "dklen": 32, 15 | "salt": "4903bd0e7880baa04fc4f886518ac5c672cdc745a6bd13dcec2b6c12e9bffe8d", 16 | "n": 4096, 17 | "r": 8, 18 | "p": 1 19 | }, 20 | "mac": "5b4a6f14ab74ba7ca23db6847e28447f0e6a7724ba9664cf425df707a84f5a8b" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/package-lock-checks.yml: -------------------------------------------------------------------------------- 1 | name: Check package-lock.json 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | compare-package-lock: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Use Node.js LTS 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: '16.14.2' 17 | 18 | - name: Check package-lock.json for "signing-providers" 19 | run: | 20 | cd signing-providers 21 | cp package-lock.json package-lock-copy.json 22 | rm -f package-lock.json 23 | npm install 24 | cmp package-lock.json package-lock-copy.json 25 | 26 | - name: Check package-lock.json for "wallet" 27 | run: | 28 | cd wallet 29 | cp package-lock.json package-lock-copy.json 30 | rm -f package-lock.json 31 | npm install 32 | cmp package-lock.json package-lock-copy.json 33 | 34 | # TODO: replicate the step above for other folders, as necessary 35 | -------------------------------------------------------------------------------- /signing-providers/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | sourceType: "module", 4 | ecmaVersion: 2020, 5 | }, 6 | extends: ["eslint:recommended", "plugin:prettier/recommended"], 7 | plugins: ["prettier"], 8 | root: true, 9 | env: { 10 | node: true, 11 | browser: true, 12 | }, 13 | ignorePatterns: [ 14 | ".eslintrc.js", 15 | "node_modules", 16 | "out", 17 | "out-tests", 18 | "out-browser", 19 | "out-browser-tests", 20 | "cookbook", 21 | ], 22 | rules: { 23 | "no-empty-function": "off", 24 | "no-use-before-define": "off", 25 | quotes: "off", 26 | "no-empty-pattern": "off", 27 | "no-var": "warn", 28 | "no-unused-vars": [ 29 | "warn", 30 | { 31 | argsIgnorePattern: "^_", 32 | varsIgnorePattern: "^_", 33 | }, 34 | ], 35 | "prefer-const": "off", 36 | "prettier/prettier": "error", 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /contracts/adder.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "docs": [ 3 | "One of the simplest smart contracts possible,", 4 | "it holds a single variable in storage, which anyone can increment." 5 | ], 6 | "name": "Adder", 7 | "constructor": { 8 | "inputs": [ 9 | { 10 | "name": "initial_value", 11 | "type": "BigUint" 12 | } 13 | ], 14 | "outputs": [] 15 | }, 16 | "endpoints": [ 17 | { 18 | "name": "getSum", 19 | "mutability": "readonly", 20 | "inputs": [], 21 | "outputs": [ 22 | { 23 | "type": "BigUint" 24 | } 25 | ] 26 | }, 27 | { 28 | "docs": [ 29 | "Add desired amount to the storage variable." 30 | ], 31 | "name": "add", 32 | "mutability": "mutable", 33 | "inputs": [ 34 | { 35 | "name": "value", 36 | "type": "BigUint" 37 | } 38 | ], 39 | "outputs": [] 40 | } 41 | ], 42 | "events": [], 43 | "hasCallback": false, 44 | "types": {} 45 | } 46 | -------------------------------------------------------------------------------- /signing-providers/src/auth.js: -------------------------------------------------------------------------------- 1 | import { NativeAuthClient } from "@multiversx/sdk-native-auth-client"; 2 | import { API_URL, NETWORK_NAME } from "./config"; 3 | 4 | export async function createNativeAuthInitialPart() { 5 | const client = new NativeAuthClient({ 6 | apiUrl: API_URL, 7 | expirySeconds: 7200, 8 | }); 9 | 10 | const initialPart = await client.initialize(); 11 | console.log("createNativeAuthInitialPart()", initialPart); 12 | 13 | return initialPart; 14 | } 15 | 16 | export function packNativeAuthToken(address, initialPart, signature) { 17 | console.log("packNativeAuthToken()"); 18 | console.log("address", address); 19 | console.log("initialPart", initialPart); 20 | console.log("signature", signature); 21 | 22 | const client = new NativeAuthClient(); 23 | const accessToken = client.getToken(address, initialPart, signature); 24 | return accessToken; 25 | } 26 | 27 | export function verifyNativeAuthToken(nativeAuthToken) { 28 | const message = ` 29 | Native auth token: 30 | ${nativeAuthToken} 31 | 32 | Normally, you would now send this token to your server, which would then validate it. 33 | 34 | Go and check it on: 35 | https://utils.multiversx.com/auth (switch to ${NETWORK_NAME})`; 36 | 37 | console.log(message); 38 | alert(message); 39 | } 40 | -------------------------------------------------------------------------------- /signing-providers/dummy-certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDxTCCAq2gAwIBAgIURipd1gg/wb/awjtz1q09AeYhAkcwDQYJKoZIhvcNAQEL 3 | BQAwcjELMAkGA1UEBhMCUk8xDTALBgNVBAgMBFRlc3QxDTALBgNVBAcMBFRlc3Qx 4 | DTALBgNVBAoMBFRlc3QxDTALBgNVBAsMBFRlc3QxEjAQBgNVBAMMCTEyNy4wLjAu 5 | MTETMBEGCSqGSIb3DQEJARYEVGVzdDAeFw0yMjAzMjgxNDU5NTdaFw0zMjAzMjUx 6 | NDU5NTdaMHIxCzAJBgNVBAYTAlJPMQ0wCwYDVQQIDARUZXN0MQ0wCwYDVQQHDARU 7 | ZXN0MQ0wCwYDVQQKDARUZXN0MQ0wCwYDVQQLDARUZXN0MRIwEAYDVQQDDAkxMjcu 8 | MC4wLjExEzARBgkqhkiG9w0BCQEWBFRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB 9 | DwAwggEKAoIBAQDH2ieSlD9B+FoJdAD1M3SJMNIxfeM2wyw3KWewaxJxd20qL/FX 10 | iBM2v5gRQZnxUdfjXALgz5DCa5zF/eKnDHGepPA2hc7fNLofXvB1qpO9M/Y1Cjvv 11 | 5kABIw1BtNMVaMRmzzkQW1aCMn9mF3qIm8+db7iMN6pNlvvXA1Q44UgZFu1G7lhn 12 | /HSQedb90ScoDOZ2HZnLIdI2sVoby9f6+va48Noqe2XdveA0GGE4Poch1c1l0ljf 13 | frd2mmnQRTg/bXgX5KQ+zEJg9TMTugz+WNrP49fW9s0Sr3/3q8gKG0D5xJrTUh0b 14 | lYBecj9r37wUUMK67IIsHZuAw6kf2GoLf2KVAgMBAAGjUzBRMB0GA1UdDgQWBBQv 15 | xzKASX+752Cpd/Ja7gjZ9lTIcTAfBgNVHSMEGDAWgBQvxzKASX+752Cpd/Ja7gjZ 16 | 9lTIcTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBzcWWvnWtb 17 | v7VlDqxoJIsveaGfKl8vlrvYR/FBskxDEQRjp6ptoDiDDbbTc6fJ9x4WdX003Cvv 18 | mkG1+SnjrpKTm8Sr5DlYPZW61yG/77Is7mLiQUN65NUhud2soTn5ZFst6aZSY0dA 19 | QtubNXoshwKSru7xVwzOBqiwf0kdBNnDxD4p2s88f99B26l6Ze4rw1xrRMgBaA2I 20 | WU0/jqOCkJc6khL0MMjvRGiilTAt4ra+IUf2hAwf6TORbH27CBWACPwL74rNb3Yh 21 | M2SSUK0wxego+3Jvh8JAiViVetPfkWI01Nu64VVB+CJOydTFotPGCSJkc/BF4yJq 22 | ZlzZ1OQJvjZX 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /signing-providers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "0.0.1", 4 | "description": "Examples", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "lint": "eslint . --ext .js", 9 | "lint:fix": "eslint . --ext .js --fix" 10 | }, 11 | "author": "MultiversX", 12 | "dependencies": { 13 | "@multiversx/sdk-core": "15.0.0", 14 | "@multiversx/sdk-dapp-utils": "3.0.1", 15 | "@multiversx/sdk-extension-provider": "5.1.1", 16 | "@multiversx/sdk-hw-provider": "8.1.1", 17 | "@multiversx/sdk-native-auth-client": "2.0.1", 18 | "@multiversx/sdk-wallet-connect-provider": "6.1.2", 19 | "@multiversx/sdk-web-wallet-cross-window-provider": "3.2.1", 20 | "@multiversx/sdk-web-wallet-iframe-provider": "3.1.1", 21 | "@multiversx/sdk-web-wallet-provider": "5.1.1", 22 | "@multiversx/sdk-webview-provider": "3.2.1", 23 | "protobufjs": "^7.4.0", 24 | "punycode": "2.3.0", 25 | "qrcode": "1.5.1", 26 | "qs": "6.10.3" 27 | }, 28 | "devDependencies": { 29 | "crypto-browserify": "3.12.0", 30 | "eslint": "^8.56.0", 31 | "eslint-config-prettier": "^10.1.5", 32 | "eslint-plugin-prettier": "^5.5.0", 33 | "path-browserify": "1.0.1", 34 | "prettier": "^3.5.3", 35 | "stream-browserify": "3.0.0", 36 | "webpack": "5.94.0", 37 | "webpack-cli": "4.8.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /signing-providers/README.md: -------------------------------------------------------------------------------- 1 | # Examples: using the signing providers 2 | 3 | This example (a simple, frontend-only web application) depicts the usage of different signing providers for dApps, such as: 4 | 5 | - [(Web) Wallet provider](https://github.com/multiversx/mx-sdk-js-web-wallet-provider) 6 | - [(Web) Wallet Cross-Window provider](@multiversx/sdk-web-wallet-cross-window-provider) 7 | - [xAlias](https://github.com/multiversx/mx-sdk-js-web-wallet-provider) - from the perspective of a dApp, this one follows the interface of the Web Wallet provider (above) 8 | - [DeFi Wallet provider](https://github.com/multiversx/mx-sdk-js-extension-provider) 9 | - [Wallet Connect (xPortal) provider](https://github.com/multiversx/mx-sdk-js-wallet-connect-provider) 10 | - [Hardware Wallet (Ledger) provider](https://github.com/multiversx/mx-sdk-js-hw-provider) 11 | 12 | ## Prerequisites 13 | 14 | Make sure you have the package `http-server` installed globally. 15 | 16 | ```bash 17 | npm install --global http-server 18 | ``` 19 | 20 | Note that some providers (such as `hw-provider`) have to be used in pages served via HTTPS in order to work properly (a dummy certificate is included here). 21 | 22 | Furthermore, make sure you install the browser extension `MultiversX DeFi Wallet` in advance. 23 | 24 | ## Running the examples 25 | 26 | When you are ready, build the examples: 27 | 28 | ```bash 29 | npm run build 30 | ``` 31 | 32 | Start the server (with a HTTPS binding): 33 | 34 | ```bash 35 | http-server -c-1 -S -C ./dummy-certificate.pem -K ./dummy-certificate-key.pem --port=8080 36 | ``` 37 | 38 | Afterwards, navigate to [https://localhost:8080/index.html](https://localhost:8080/index.html). 39 | -------------------------------------------------------------------------------- /signing-providers/iframe-wallet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iframe Wallet 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |

Iframe Wallet

17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 | 27 | 30 | 33 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /signing-providers/dummy-certificate-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDH2ieSlD9B+FoJ 3 | dAD1M3SJMNIxfeM2wyw3KWewaxJxd20qL/FXiBM2v5gRQZnxUdfjXALgz5DCa5zF 4 | /eKnDHGepPA2hc7fNLofXvB1qpO9M/Y1Cjvv5kABIw1BtNMVaMRmzzkQW1aCMn9m 5 | F3qIm8+db7iMN6pNlvvXA1Q44UgZFu1G7lhn/HSQedb90ScoDOZ2HZnLIdI2sVob 6 | y9f6+va48Noqe2XdveA0GGE4Poch1c1l0ljffrd2mmnQRTg/bXgX5KQ+zEJg9TMT 7 | ugz+WNrP49fW9s0Sr3/3q8gKG0D5xJrTUh0blYBecj9r37wUUMK67IIsHZuAw6kf 8 | 2GoLf2KVAgMBAAECggEAJwYdJg0WkQ4qnp/tM/P5NHS5Bnr7bA0OTDMkkRlHP6q/ 9 | QTadXKcwgUdGLVBu++UsT7P+x+Ef9ibHNQ4PPOk8Ims4kJzuOT11fnyuXXuSX6aO 10 | 0+qMq5p9MvuiMgtaEFslxqF+FgiPytqLb+bzwUsTbj2Lfq277myl/mUjA/xRdLxh 11 | /7yly7YBAoffsUsRu65ek7k/itV7heVCqpgbioJ4KjGpLruh3G8EEID7FfFlafRY 12 | phU9f+xWG9QtZTEBsWDgJhljeFZ64wOHpG3Wokpq17sYWoobykkRN19kgu6ag8ok 13 | 3LC2mkw8Hf56ZdZt2B+RDOQCez1x5O3N/GbO8obKAQKBgQDkiOYmtFPAbvnZHCdN 14 | JMYHFSMw6/hB+QZWho0oc+qAdzX4xE9rvAXOb7/oilyZf1DJdwILCm34NVH4gaVP 15 | BhGD24usTEKMq2w/JhLsLPYYlKyvtNKBCFpg0S7xoyfPT6xfUYfsnvN+50V7R5cG 16 | WyHeJytGKnkkc5UVRzuORWfagQKBgQDf3s6HrL3LTjuXWqgXNk6HefKP/888MZHL 17 | /iyJ6Av8xmeSyvWITcB2Hr7xka+weTb3Co/6MH140LDc0zAMEISsTKP+p4qV5Ewv 18 | e60wOAx+7r0gYPy9vTef7oW4zwh1we1UUO1FklI3/mUIEkNlHQnSGouTCrS/BVib 19 | 7IWBi8N2FQKBgQCXbmciymacomyIAnHAWlelpcn1xsZv4LTkbK/oWDbQ/S0UM/B0 20 | cNhgHAhL7DLDu2sqs+L0seqAh8RTKIUDQgAlITsB5l5Km+RUS8RKHtjLHOj4XJcH 21 | zSMl+DZlAzmD00Viu8GXdxPdyR1vPNbD7WsZq1avXcF79+KXgOXjtfXFAQKBgQCa 22 | kVploKYWNfi9ArHl/O/xaAhK1iN+evcgMmkL+nQ0XcRrPMiUCKLmq0nIvn7gSIDj 23 | xp4r7sji3qwOe53D5q/DytK742+zGEJl0m18SmaOSUW5kl1On9NFEEmxlPhRXckA 24 | GzQHggRUdsfI79sqeAcs0nkl2BF9hjJszbxL+nTFFQKBgQDVl/toy30trOx9vyB+ 25 | yA76hyF654Drjjnbhbi1UDkKoVo95H1q8Arnu/VzgEhW/GcfIUAkUAuRlxNK08Et 26 | LjINeDA2blVI9emwGSLRSeuMkpOLTexs5PqaB64A36IPn2YZWRsQ760z0PqlZwPf 27 | G7Or7/WAMMKnmsuXYA2ZOc6w7A== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /signing-providers/webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | "use strict"; 3 | 4 | //@ts-check 5 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 6 | 7 | const path = require("path"); 8 | 9 | const webpack = require("webpack"); 10 | 11 | /** @type WebpackConfig */ 12 | const config = { 13 | mode: "none", 14 | entry: { 15 | app: "./src/index.js", 16 | }, 17 | output: { 18 | filename: "[name].js", 19 | path: path.join(__dirname, "./out"), 20 | libraryTarget: "umd", 21 | }, 22 | resolve: { 23 | mainFields: ["browser", "module", "main"], 24 | extensions: [".ts", ".js"], // support ts-files and js-files 25 | alias: { 26 | // provides alternate implementation for node module and source files 27 | }, 28 | fallback: { 29 | // Webpack 5 no longer polyfills Node.js core modules automatically. 30 | // see https://webpack.js.org/configuration/resolve/#resolvefallback 31 | // for the list of Node.js core module polyfills. 32 | assert: require.resolve("assert"), 33 | buffer: require.resolve("buffer"), 34 | stream: require.resolve("stream-browserify"), 35 | crypto: require.resolve("crypto-browserify"), 36 | path: require.resolve("path-browserify"), 37 | punycode: require.resolve("punycode"), 38 | fs: false, 39 | }, 40 | }, 41 | module: { 42 | rules: [], 43 | }, 44 | plugins: [ 45 | new webpack.ProvidePlugin({ 46 | Buffer: ["buffer", "Buffer"], 47 | }), 48 | ], 49 | externals: {}, 50 | performance: { 51 | hints: false, 52 | }, 53 | devtool: "eval-source-map", // create a source map that points to the original source file 54 | }; 55 | 56 | module.exports = [config]; 57 | -------------------------------------------------------------------------------- /codec/decodeCustomType.js: -------------------------------------------------------------------------------- 1 | import { AbiRegistry, BinaryCodec } from "@multiversx/sdk-core"; 2 | import * as fs from "fs"; 3 | import minimist from "minimist"; 4 | import { homedir } from "os"; 5 | 6 | async function main() { 7 | const argv = minimist(process.argv.slice(2)); 8 | const abiPath = asUserPath(argv.abi); 9 | const type = argv.type; 10 | const encodedData = argv.data; 11 | 12 | if (!abiPath) { 13 | throw new Error("Missing parameter 'abi'! E.g. --abi=~/example.abi.json"); 14 | } 15 | if (!fs.existsSync(abiPath)) { 16 | throw new Error(`File not found: ${abiPath}`); 17 | } 18 | if (!type) { 19 | throw new Error("Missing parameter 'type'! E.g. --type=DepositEvent"); 20 | } 21 | if (!encodedData) { 22 | throw new Error("Missing parameter 'data'! E.g. --data=00000000000003db000000"); 23 | } 24 | 25 | const abiContent = fs.readFileSync(abiPath, { encoding: "utf8" }); 26 | const abiObj = JSON.parse(abiContent); 27 | const abiRegistry = AbiRegistry.create(abiObj); 28 | const data = Buffer.from(encodedData, "hex"); 29 | 30 | const customType = abiRegistry.customTypes.find((e) => e.getName() == type); 31 | if (!customType) { 32 | throw new Error(`Custom type not found: ${type}`); 33 | } 34 | 35 | const codec = new BinaryCodec(); 36 | const decoded = codec.decodeTopLevel(data, customType); 37 | const decodedValue = decoded.valueOf(); 38 | 39 | console.log(JSON.stringify(decodedValue, null, 4)); 40 | } 41 | 42 | function asUserPath(userPath) { 43 | return (userPath || "").toString().replace("~", homedir); 44 | } 45 | 46 | async function doMain() { 47 | try { 48 | await main(); 49 | } catch (error) { 50 | console.error("Error:", error.message); 51 | process.exit(1); 52 | } 53 | } 54 | 55 | await doMain(); 56 | -------------------------------------------------------------------------------- /signing-providers/webview-child.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Examples 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |

Webview

16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 | 32 | 35 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /codec/decodeEvent.js: -------------------------------------------------------------------------------- 1 | import { AbiRegistry, ApiNetworkProvider, ResultsParser } from "@multiversx/sdk-core"; 2 | import * as fs from "fs"; 3 | import minimist from "minimist"; 4 | import { homedir } from "os"; 5 | 6 | async function main() { 7 | const argv = minimist(process.argv.slice(2)); 8 | const abiPath = asUserPath(argv.abi); 9 | const eventIdentifier = argv.event; 10 | const apiUrl = argv.api; 11 | const transactionHash = argv.tx; 12 | 13 | if (!abiPath) { 14 | throw new Error("Missing parameter 'abi'! E.g. --abi=~/example.abi.json"); 15 | } 16 | if (!fs.existsSync(abiPath)) { 17 | throw new Error(`File not found: ${abiPath}`); 18 | } 19 | if (!eventIdentifier) { 20 | throw new Error("Missing parameter 'event'! E.g. --event=deposit"); 21 | } 22 | if (!apiUrl) { 23 | throw new Error("Missing parameter 'api'! E.g. --api=https://testnet-api.multiversx.com"); 24 | } 25 | if (!transactionHash) { 26 | throw new Error("Missing parameter 'tx'! E.g. --tx=532087e5021c9ab8be8a4db5ad843cfe0610761f6334d9693b3765992fd05f67"); 27 | } 28 | 29 | const abiContent = fs.readFileSync(abiPath, { encoding: "utf8" }); 30 | const abiObj = JSON.parse(abiContent); 31 | const abiRegistry = AbiRegistry.create(abiObj); 32 | 33 | const eventDefinition = abiRegistry.getEvent(eventIdentifier); 34 | const resultsParser = new ResultsParser(); 35 | 36 | const provider = new ApiNetworkProvider(apiUrl, { clientName: "multiversx-sdk-js-examples" }); 37 | const transaction = await provider.getTransaction(transactionHash); 38 | const event = findTransactionEvent(transaction, eventIdentifier); 39 | if (!event) { 40 | throw new Error(`Event not found: ${eventIdentifier}`); 41 | } 42 | 43 | const outcome = resultsParser.parseEvent(event, eventDefinition); 44 | console.log(JSON.stringify(outcome, null, 4)); 45 | } 46 | 47 | function asUserPath(userPath) { 48 | return (userPath || "").toString().replace("~", homedir); 49 | } 50 | 51 | function findTransactionEvent(transaction, eventIdentifier) { 52 | const eventInTransaction = transaction.logs.findFirstOrNoneEvent(eventIdentifier); 53 | if (eventInTransaction) { 54 | return eventInTransaction; 55 | } 56 | 57 | for (const contractResult of transaction.contractResults.items) { 58 | const eventInContractResult = contractResult.logs.findFirstOrNoneEvent(eventIdentifier); 59 | if (eventInContractResult) { 60 | return eventInContractResult; 61 | } 62 | } 63 | 64 | return null; 65 | } 66 | 67 | async function doMain() { 68 | try { 69 | await main(); 70 | } catch (error) { 71 | console.error("Error:", error.message); 72 | process.exit(1); 73 | } 74 | } 75 | 76 | await doMain(); 77 | -------------------------------------------------------------------------------- /signing-providers/src/iframe-communication.js: -------------------------------------------------------------------------------- 1 | import { IframeProvider } from "@multiversx/sdk-web-wallet-iframe-provider/out"; 2 | import { IframeLoginTypes } from "@multiversx/sdk-web-wallet-iframe-provider/out/constants"; 3 | import { CHAIN_ID } from "./config"; 4 | import { displayOutcome } from "./helpers"; 5 | import { Address, Message, Transaction } from "@multiversx/sdk-core"; 6 | 7 | // IMPORTANT: The iframe wallet must be served over HTTPS on different domain 8 | // example: http-server -c-1 -S -C ./dummy-certificate.pem -K ./dummy-certificate-key.pem --port=3000 9 | const IFRAME_WALLET_URL = `https://192.168.50.183:3000/iframe-wallet.html`; 10 | 11 | export class IframeCommunication { 12 | constructor() { 13 | this.provider = IframeProvider.getInstance(); 14 | this.address = ""; 15 | } 16 | 17 | async init() { 18 | // TODO: change to iframe login type 19 | this.provider.setLoginType(IframeLoginTypes.metamask); 20 | this.provider.setWalletUrl(IFRAME_WALLET_URL); 21 | const isInitialized = await this.provider.init(); 22 | return isInitialized; 23 | } 24 | 25 | async login() { 26 | await this.init(); 27 | const account = await this.provider.login(); 28 | this.address = account.address; 29 | alert(`Address: ${account.address}`); 30 | } 31 | 32 | async logout() { 33 | await this.provider.logout(); 34 | this.address = ""; 35 | } 36 | 37 | async signTransactions() { 38 | const sender = this.address; 39 | const firstTransaction = new Transaction({ 40 | nonce: 42, 41 | value: "1", 42 | sender: new Address(sender), 43 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 44 | gasPrice: 1000000000, 45 | gasLimit: 50000, 46 | data: Buffer.from(""), 47 | chainID: CHAIN_ID, 48 | version: 1, 49 | }); 50 | 51 | const secondTransaction = new Transaction({ 52 | nonce: 43, 53 | value: "100000000", 54 | sender: new Address(sender), 55 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 56 | gasPrice: 1000000000, 57 | gasLimit: 50000, 58 | data: Buffer.from("hello world"), 59 | chainID: CHAIN_ID, 60 | version: 1, 61 | }); 62 | 63 | await this.provider.signTransactions([firstTransaction, secondTransaction]); 64 | console.log("First transaction, upon signing:", firstTransaction); 65 | console.log("Second transaction, upon signing:", secondTransaction); 66 | 67 | alert(JSON.stringify([firstTransaction.toSendable(), secondTransaction.toSendable()], null, 4)); 68 | } 69 | 70 | async signMessage() { 71 | const message = new Message({ 72 | address: new Address(this.address), 73 | data: Buffer.from("hello"), 74 | }); 75 | const signedMessage = await this.provider.signMessage(message); 76 | displayOutcome("Message signed. Signature: ", signedMessage); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /signing-providers/src/extension.js: -------------------------------------------------------------------------------- 1 | import { Address, Message, Transaction, TransactionPayload } from "@multiversx/sdk-core"; 2 | import { ExtensionProvider } from "@multiversx/sdk-extension-provider"; 3 | import { createNativeAuthInitialPart, packNativeAuthToken, verifyNativeAuthToken } from "./auth"; 4 | import { CHAIN_ID } from "./config"; 5 | import { displayOutcome } from "./helpers"; 6 | 7 | export class Extension { 8 | constructor() { 9 | this.provider = ExtensionProvider.getInstance(); 10 | } 11 | 12 | async login() { 13 | await this.provider.init(); 14 | const account = await this.provider.login(); 15 | 16 | alert(`Address: ${account.address}`); 17 | } 18 | 19 | async loginWithToken() { 20 | await this.provider.init(); 21 | 22 | const nativeAuthInitialPart = await createNativeAuthInitialPart(); 23 | const account = await this.provider.login({ token: nativeAuthInitialPart }); 24 | 25 | const address = account.address; 26 | const signature = account.signature; 27 | const nativeAuthToken = packNativeAuthToken(address, nativeAuthInitialPart, signature); 28 | 29 | verifyNativeAuthToken(nativeAuthToken); 30 | } 31 | 32 | async logout() { 33 | await this.provider.init(); 34 | await this.provider.logout(); 35 | } 36 | 37 | async signTransaction() { 38 | await this.provider.init(); 39 | 40 | const sender = await this.provider.getAddress(); 41 | const transaction = new Transaction({ 42 | nonce: 42, 43 | value: "1", 44 | sender: new Address(sender), 45 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 46 | gasPrice: 1000000000, 47 | gasLimit: 50000, 48 | data: Buffer.from("hello"), 49 | chainID: CHAIN_ID, 50 | version: 1, 51 | }); 52 | 53 | await this.provider.signTransaction(transaction); 54 | 55 | alert(JSON.stringify(transaction.toSendable(), null, 4)); 56 | } 57 | 58 | async signTransactions() { 59 | await this.provider.init(); 60 | 61 | const sender = await this.provider.getAddress(); 62 | const firstTransaction = new Transaction({ 63 | nonce: 42, 64 | value: "1", 65 | sender: new Address(sender), 66 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 67 | gasPrice: 1000000000, 68 | gasLimit: 50000, 69 | data: Buffer.from("hello"), 70 | chainID: CHAIN_ID, 71 | version: 1, 72 | }); 73 | 74 | const secondTransaction = new Transaction({ 75 | nonce: 43, 76 | value: "100000000", 77 | sender: new Address(sender), 78 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 79 | gasPrice: 1000000000, 80 | gasLimit: 50000, 81 | data: Buffer.from("hello world"), 82 | chainID: CHAIN_ID, 83 | version: 1, 84 | }); 85 | 86 | await this.provider.signTransactions([firstTransaction, secondTransaction]); 87 | console.log("First transaction, upon signing:", firstTransaction); 88 | console.log("Second transaction, upon signing:", secondTransaction); 89 | 90 | alert(JSON.stringify([firstTransaction.toSendable(), secondTransaction.toSendable()], null, 4)); 91 | } 92 | 93 | async signMessage() { 94 | await this.provider.init(); 95 | 96 | const address = await this.provider.getAddress(); 97 | 98 | const message = new Message({ 99 | address: new Address(address), 100 | data: Buffer.from("hello"), 101 | }); 102 | 103 | const signedMessage = await this.provider.signMessage(message); 104 | 105 | displayOutcome("Message signed. Signature: ", Buffer.from(signedMessage?.signature).toString("hex")); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /signing-providers/src/webview.js: -------------------------------------------------------------------------------- 1 | import { Address, Message, Transaction } from "@multiversx/sdk-core"; 2 | import { CHAIN_ID } from "./config"; 3 | import { WebviewProvider } from "@multiversx/sdk-webview-provider/out/WebviewProvider"; 4 | 5 | import { displayOutcome } from "./helpers"; 6 | 7 | export const addressOfAlice = new Address("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); 8 | 9 | export class Webview { 10 | constructor() { 11 | this._provider = WebviewProvider.getInstance({ 12 | resetStateCallback: () => console.log("Reset state callback called"), 13 | }); 14 | } 15 | 16 | async init() { 17 | return await this._provider.init(); 18 | } 19 | 20 | async login() { 21 | const response = await this._provider.login(); 22 | 23 | console.log("Login response:" + JSON.stringify(response)); 24 | alert("Login response:" + JSON.stringify(response)); 25 | 26 | return response; 27 | } 28 | 29 | async logout() { 30 | const response = await this._provider.logout(); 31 | 32 | console.log("Logout response:" + JSON.stringify(response)); 33 | alert("Logout response:" + JSON.stringify(response)); 34 | 35 | return response; 36 | } 37 | 38 | async relogin() { 39 | const accessToken = await this._provider.relogin(); 40 | 41 | alert("accessToken = " + JSON.stringify(accessToken)); 42 | console.log("accessToken = " + JSON.stringify(accessToken)); 43 | 44 | if (!accessToken) { 45 | console.error("Unable to re-login. Missing accessToken."); 46 | alert("Unable to re-login. Missing accessToken."); 47 | return null; 48 | } 49 | 50 | return accessToken; 51 | } 52 | 53 | async signTransaction() { 54 | const transaction = new Transaction({ 55 | nonce: 42, 56 | value: "1", 57 | sender: addressOfAlice, 58 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 59 | gasPrice: 1000000000, 60 | gasLimit: 50000, 61 | data: Buffer.from("world"), 62 | chainID: CHAIN_ID, 63 | version: 1, 64 | }); 65 | 66 | const response = await this._provider.signTransaction(transaction); 67 | 68 | console.log("Sign transaction response:" + JSON.stringify(response)); 69 | alert("Sign transaction response:" + JSON.stringify(response)); 70 | 71 | return response; 72 | } 73 | 74 | async signTransactions() { 75 | const transaction = new Transaction({ 76 | nonce: 42, 77 | value: "1", 78 | sender: addressOfAlice, 79 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 80 | gasPrice: 1000000000, 81 | gasLimit: 50000, 82 | data: Buffer.from("world"), 83 | chainID: CHAIN_ID, 84 | version: 1, 85 | }); 86 | 87 | const response = await this._provider.signTransactions([transaction]); 88 | 89 | if (!response) { 90 | this._provider.cancelAction(); 91 | return null; 92 | } 93 | 94 | console.log("Sign transactions response:" + JSON.stringify(response)); 95 | alert("Sign transactions response:" + JSON.stringify(response)); 96 | 97 | return response; 98 | } 99 | 100 | async signMessage() { 101 | const message = new Message({ 102 | address: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 103 | data: Buffer.from("hello"), 104 | }); 105 | 106 | const signedMessage = await this._provider.signMessage(message); 107 | 108 | displayOutcome("Message signed. Signature: ", Buffer.from(signedMessage?.signature).toString("hex")); 109 | } 110 | 111 | async cancelAction() { 112 | return await this._provider.cancelAction(); 113 | } 114 | 115 | async isInitialized() { 116 | return this._provider.isInitialized(); 117 | } 118 | 119 | async isConnected() { 120 | return this._provider.isConnected(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /signing-providers/src/cross-window.js: -------------------------------------------------------------------------------- 1 | import { Address, Message, Transaction } from "@multiversx/sdk-core"; 2 | import { CHAIN_ID, WALLET_URL } from "./config"; 3 | import { CrossWindowProvider } from "@multiversx/sdk-web-wallet-cross-window-provider"; 4 | 5 | import { createNativeAuthInitialPart, packNativeAuthToken, verifyNativeAuthToken } from "./auth"; 6 | import { displayOutcome } from "./helpers"; 7 | 8 | const callbackUrl = window.location.href; 9 | 10 | export class CrossWindowWallet { 11 | constructor() { 12 | this._provider = CrossWindowProvider.getInstance(); 13 | this._address = ""; 14 | } 15 | 16 | async init() { 17 | await CrossWindowProvider.getInstance().init(); 18 | this._provider = CrossWindowProvider.getInstance().setWalletUrl(WALLET_URL); 19 | } 20 | 21 | async login() { 22 | await this.init(); 23 | 24 | const { address } = await this._provider.login({ callbackUrl }); 25 | this._address = address; 26 | console.log("Login response:" + JSON.stringify(address)); 27 | 28 | return address; 29 | } 30 | 31 | async loginWithToken() { 32 | await this.init(); 33 | 34 | const nativeAuthInitialPart = await createNativeAuthInitialPart(); 35 | await this._provider.login({ token: nativeAuthInitialPart, callbackUrl }); 36 | 37 | const address = this._provider.account.address; 38 | const signature = this._provider.account.signature; 39 | const nativeAuthToken = packNativeAuthToken(address, nativeAuthInitialPart, signature); 40 | this._address = address; 41 | 42 | verifyNativeAuthToken(nativeAuthToken); 43 | } 44 | 45 | async logout() { 46 | const response = await this._provider.logout(); 47 | 48 | displayOutcome("Logout response:", response); 49 | 50 | return response; 51 | } 52 | 53 | async signTransaction() { 54 | const transaction = new Transaction({ 55 | nonce: 42, 56 | value: "1", 57 | sender: new Address(this._address), 58 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 59 | gasPrice: 1000000000, 60 | gasLimit: 50000, 61 | data: Uint8Array.from(Buffer.from("hello world")), 62 | chainID: CHAIN_ID, 63 | version: 1, 64 | }); 65 | 66 | await this._provider.signTransaction(transaction, { 67 | callbackUrl: encodeURIComponent(callbackUrl), 68 | }); 69 | 70 | alert(JSON.stringify(transaction.toSendable(), null, 4)); 71 | } 72 | 73 | async signTransactions() { 74 | const sender = this._address; 75 | const firstTransaction = new Transaction({ 76 | nonce: 42, 77 | value: "1", 78 | sender: new Address(sender), 79 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 80 | gasPrice: 1000000000, 81 | gasLimit: 50000, 82 | data: Uint8Array.from(Buffer.from("hello once")), 83 | chainID: CHAIN_ID, 84 | version: 1, 85 | }); 86 | 87 | const secondTransaction = new Transaction({ 88 | nonce: 43, 89 | value: "100000000", 90 | sender: new Address(sender), 91 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 92 | gasPrice: 1000000000, 93 | gasLimit: 50000, 94 | data: Uint8Array.from(Buffer.from("hello twice")), 95 | chainID: CHAIN_ID, 96 | version: 1, 97 | }); 98 | 99 | const response = await this._provider.signTransactions([firstTransaction, secondTransaction], { 100 | callbackUrl: encodeURIComponent(callbackUrl), 101 | }); 102 | 103 | const plainResponse = response.map((r) => r.toPlainObject()); 104 | console.log("First transaction, upon signing:", firstTransaction); 105 | console.log("Second transaction, upon signing:", secondTransaction); 106 | console.log("Response:", plainResponse); 107 | 108 | alert(JSON.stringify(plainResponse, null, 4)); 109 | } 110 | 111 | async signMessage() { 112 | await this._provider.init(); 113 | const address = this._address; 114 | 115 | const message = new Message({ 116 | address: new Address(address), 117 | data: Buffer.from("hello"), 118 | }); 119 | 120 | const signedMessage = await this._provider.signMessage(message); 121 | 122 | displayOutcome("Message signed. Signature: ", Buffer.from(signedMessage?.signature).toString("hex")); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /signing-providers/src/metamask.js: -------------------------------------------------------------------------------- 1 | import { Address, Message, Transaction, TransactionPayload } from "@multiversx/sdk-core"; 2 | import { IframeProvider } from "@multiversx/sdk-web-wallet-iframe-provider/out"; 3 | import { IframeLoginTypes } from "@multiversx/sdk-web-wallet-iframe-provider/out/constants"; 4 | 5 | import { createNativeAuthInitialPart, packNativeAuthToken, verifyNativeAuthToken } from "./auth"; 6 | import { CHAIN_ID, METAMASK_SNAP_WALLET_ADDRESS } from "./config.devnet"; 7 | import { displayOutcome } from "./helpers"; 8 | 9 | const callbackUrl = window.location.href; 10 | 11 | export class Metamask { 12 | constructor() { 13 | this._provider = IframeProvider.getInstance(); 14 | this._address = ""; 15 | } 16 | 17 | async init() { 18 | this._provider.setLoginType(IframeLoginTypes.metamask); 19 | console.log("Metamask snap wallet address:" + METAMASK_SNAP_WALLET_ADDRESS); 20 | 21 | this._provider.setWalletUrl(METAMASK_SNAP_WALLET_ADDRESS); 22 | await this._provider.init(); 23 | } 24 | 25 | async login() { 26 | await this.init(); 27 | 28 | await this._provider.login({}); 29 | 30 | const { address } = this._provider.account; 31 | this._address = address; 32 | console.log("Login response:" + JSON.stringify(address)); 33 | 34 | return address; 35 | } 36 | 37 | async loginWithToken() { 38 | await this.init(); 39 | 40 | const nativeAuthInitialPart = await createNativeAuthInitialPart(); 41 | await this._provider.login({ token: nativeAuthInitialPart }); 42 | 43 | const address = this._provider.account.address; 44 | const signature = this._provider.account.signature; 45 | const nativeAuthToken = packNativeAuthToken(address, nativeAuthInitialPart, signature); 46 | this._address = address; 47 | 48 | verifyNativeAuthToken(nativeAuthToken); 49 | } 50 | 51 | async logout() { 52 | const response = await this._provider.logout(); 53 | 54 | console.log("Logout response:" + JSON.stringify(response)); 55 | 56 | return response; 57 | } 58 | 59 | async signTransaction() { 60 | const transaction = new Transaction({ 61 | nonce: 42, 62 | value: "1", 63 | sender: new Address(this._address), 64 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 65 | gasPrice: 1000000000, 66 | gasLimit: 50000, 67 | data: Buffer.from("hello"), 68 | chainID: CHAIN_ID, 69 | version: 1, 70 | }); 71 | 72 | await this._provider.signTransaction(transaction, { 73 | callbackUrl: encodeURIComponent(callbackUrl), 74 | }); 75 | 76 | alert(JSON.stringify(transaction.toSendable(), null, 4)); 77 | } 78 | 79 | async signTransactions() { 80 | const sender = this._address; 81 | const firstTransaction = new Transaction({ 82 | nonce: 42, 83 | value: "1", 84 | sender: new Address(sender), 85 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 86 | gasPrice: 1000000000, 87 | gasLimit: 150000, 88 | data: Buffer.from("hello once"), 89 | chainID: CHAIN_ID, 90 | version: 1, 91 | }); 92 | 93 | const secondTransaction = new Transaction({ 94 | nonce: 43, 95 | value: "100000000", 96 | sender: new Address(sender), 97 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 98 | gasPrice: 1000000000, 99 | gasLimit: 150000, 100 | data: Buffer.from("hello twice"), 101 | chainID: CHAIN_ID, 102 | version: 1, 103 | }); 104 | 105 | const response = await this._provider.signTransactions([firstTransaction, secondTransaction], { 106 | callbackUrl: encodeURIComponent(callbackUrl), 107 | }); 108 | console.log("First transaction, upon signing:", firstTransaction); 109 | console.log("Second transaction, upon signing:", secondTransaction); 110 | 111 | const plainResponse = response.map((r) => r.toPlainObject()); 112 | console.log("Response:", plainResponse); 113 | 114 | alert(JSON.stringify(plainResponse, null, 4)); 115 | } 116 | 117 | async signMessage() { 118 | await this._provider.init(); 119 | 120 | const message = new Message({ 121 | address: new Address(this._address), 122 | data: Buffer.from("hello"), 123 | }); 124 | 125 | const signedMessage = await this._provider.signMessage(message); 126 | 127 | displayOutcome("Message signed. Signature: ", Buffer.from(signedMessage?.signature).toString("hex")); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /contracts/example.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoints": [ 3 | { 4 | "name": "deposit", 5 | "inputs": [ 6 | { 7 | "name": "to", 8 | "type": "Address" 9 | }, 10 | { 11 | "name": "opt_transfer_data", 12 | "type": "optional", 13 | "multi_arg": true 14 | } 15 | ], 16 | "outputs": [] 17 | } 18 | ], 19 | "events": [ 20 | { 21 | "identifier": "deposit", 22 | "inputs": [ 23 | { 24 | "name": "dest_address", 25 | "type": "Address", 26 | "indexed": true 27 | }, 28 | { 29 | "name": "tokens", 30 | "type": "List", 31 | "indexed": true 32 | }, 33 | { 34 | "name": "event_data", 35 | "type": "DepositEvent" 36 | } 37 | ] 38 | } 39 | ], 40 | "types": { 41 | "DepositEvent": { 42 | "type": "struct", 43 | "fields": [ 44 | { 45 | "name": "tx_nonce", 46 | "type": "u64" 47 | }, 48 | { 49 | "name": "opt_function", 50 | "type": "Option" 51 | }, 52 | { 53 | "name": "opt_arguments", 54 | "type": "Option>" 55 | }, 56 | { 57 | "name": "opt_gas_limit", 58 | "type": "Option" 59 | } 60 | ] 61 | }, 62 | "EsdtTokenPayment": { 63 | "type": "struct", 64 | "fields": [ 65 | { 66 | "name": "token_identifier", 67 | "type": "TokenIdentifier" 68 | }, 69 | { 70 | "name": "token_nonce", 71 | "type": "u64" 72 | }, 73 | { 74 | "name": "amount", 75 | "type": "BigUint" 76 | } 77 | ] 78 | }, 79 | "TransferData": { 80 | "type": "struct", 81 | "fields": [ 82 | { 83 | "name": "gas_limit", 84 | "type": "u64" 85 | }, 86 | { 87 | "name": "function", 88 | "type": "bytes" 89 | }, 90 | { 91 | "name": "args", 92 | "type": "List" 93 | } 94 | ] 95 | }, 96 | "Reward": { 97 | "type": "struct", 98 | "fields": [ 99 | { 100 | "name": "reward_type", 101 | "type": "RewardType" 102 | }, 103 | { 104 | "name": "reward_token_id", 105 | "type": "EgldOrEsdtTokenIdentifier" 106 | }, 107 | { 108 | "name": "value", 109 | "type": "BigUint" 110 | }, 111 | { 112 | "name": "description", 113 | "type": "bytes" 114 | }, 115 | { 116 | "name": "percentage_chance", 117 | "type": "u64" 118 | }, 119 | { 120 | "name": "epochs_cooldown", 121 | "type": "u64" 122 | } 123 | ] 124 | }, 125 | "RewardType": { 126 | "type": "enum", 127 | "variants": [ 128 | { 129 | "name": "None", 130 | "discriminant": 0 131 | }, 132 | { 133 | "name": "ExperiencePoints", 134 | "discriminant": 1 135 | }, 136 | { 137 | "name": "MysteryBox", 138 | "discriminant": 2 139 | }, 140 | { 141 | "name": "SFT", 142 | "discriminant": 3 143 | }, 144 | { 145 | "name": "PercentValue", 146 | "discriminant": 4 147 | }, 148 | { 149 | "name": "FixedValue", 150 | "discriminant": 5 151 | }, 152 | { 153 | "name": "CustomReward", 154 | "discriminant": 6 155 | } 156 | ] 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /signing-providers/src/wallet-connect-v2.js: -------------------------------------------------------------------------------- 1 | import { Address, Message, Transaction } from "@multiversx/sdk-core"; 2 | import { WalletConnectV2Provider } from "@multiversx/sdk-wallet-connect-provider"; 3 | import QRCode from "qrcode"; 4 | import { createNativeAuthInitialPart, packNativeAuthToken, verifyNativeAuthToken } from "./auth"; 5 | import { CHAIN_ID, WALLET_CONNECT_PROJECT_ID, WALLET_CONNECT_RELAY_URL } from "./config"; 6 | import { displayOutcome } from "./helpers"; 7 | 8 | export class WalletConnectV2 { 9 | constructor() { 10 | this.provider = new WalletConnectV2Provider( 11 | this.prepareCallbacks(), 12 | CHAIN_ID, 13 | WALLET_CONNECT_RELAY_URL, 14 | WALLET_CONNECT_PROJECT_ID, 15 | ); 16 | } 17 | 18 | prepareCallbacks() { 19 | const self = this; 20 | 21 | return { 22 | onClientLogin: async function () { 23 | closeModal(); 24 | const address = self.provider.getAddress(); 25 | alert(`onClientLogin(), address: ${address}`); 26 | }, 27 | onClientLogout: function () { 28 | alert("onClientLogout()"); 29 | }, 30 | onClientEvent: function (event) { 31 | alert("onClientEvent()", event); 32 | }, 33 | }; 34 | } 35 | 36 | async login() { 37 | await this.provider.init(); 38 | const { uri, approval } = await this.provider.connect(); 39 | 40 | await openModal(uri); 41 | 42 | try { 43 | await this.provider.login({ approval }); 44 | } catch (err) { 45 | console.log(err); 46 | alert("Connection Proposal Refused"); 47 | } 48 | } 49 | 50 | async loginWithToken() { 51 | await this.provider.init(); 52 | const nativeAuthInitialPart = await createNativeAuthInitialPart(); 53 | const { uri, approval } = await this.provider.connect(); 54 | 55 | await openModal(uri); 56 | 57 | try { 58 | const account = await this.provider.login({ 59 | approval, 60 | token: nativeAuthInitialPart, 61 | }); 62 | 63 | const address = account.address; 64 | const signature = account.signature; 65 | const nativeAuthToken = packNativeAuthToken(address, nativeAuthInitialPart, signature); 66 | 67 | verifyNativeAuthToken(nativeAuthToken); 68 | } catch (err) { 69 | console.log(err); 70 | alert("Rejected by user"); 71 | } 72 | } 73 | 74 | async logout() { 75 | await this.provider.init(); 76 | await this.provider.logout(); 77 | } 78 | 79 | async signTransaction() { 80 | await this.provider.init(); 81 | 82 | const sender = this.provider.getAddress(); 83 | const transaction = new Transaction({ 84 | nonce: 42, 85 | value: "1", 86 | sender: new Address(sender), 87 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 88 | gasPrice: 1000000000, 89 | gasLimit: 50000, 90 | data: Buffer.from("hello"), 91 | chainID: CHAIN_ID, 92 | version: 1, 93 | }); 94 | 95 | await this.provider.signTransaction(transaction); 96 | 97 | alert(JSON.stringify(transaction.toSendable(), null, 4)); 98 | } 99 | 100 | async signTransactions() { 101 | await this.provider.init(); 102 | 103 | const sender = this.provider.getAddress(); 104 | const firstTransaction = new Transaction({ 105 | nonce: 43, 106 | value: "1", 107 | sender: new Address(sender), 108 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 109 | gasPrice: 1000000000, 110 | gasLimit: 50000, 111 | data: Buffer.from("hello"), 112 | chainID: CHAIN_ID, 113 | version: 1, 114 | }); 115 | 116 | const secondTransaction = new Transaction({ 117 | nonce: 44, 118 | value: "100000000", 119 | sender: new Address(sender), 120 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 121 | gasPrice: 1000000000, 122 | gasLimit: 50000, 123 | data: Buffer.from("hello world"), 124 | chainID: CHAIN_ID, 125 | version: 1, 126 | }); 127 | 128 | const transactions = [firstTransaction, secondTransaction]; 129 | await this.provider.signTransactions(transactions); 130 | 131 | alert(JSON.stringify([firstTransaction.toSendable(), secondTransaction.toSendable()], null, 4)); 132 | } 133 | 134 | async signMessage() { 135 | await this.provider.init(); 136 | const address = this.provider.getAddress(); 137 | 138 | const message = new Message({ 139 | address: new Address(address), 140 | data: Buffer.from("hello"), 141 | }); 142 | 143 | const signedMessage = await this.provider.signMessage(message); 144 | 145 | displayOutcome("Message signed. Signature: ", Buffer.from(signedMessage?.signature).toString("hex")); 146 | } 147 | } 148 | 149 | async function openModal(connectorUri) { 150 | const svg = await QRCode.toString(connectorUri, { type: "svg" }); 151 | 152 | window.$("#MyWalletConnectV2QRContainer").html(svg); 153 | window.$("#MyWalletConnectV2Modal").modal("show"); 154 | } 155 | 156 | function closeModal() { 157 | window.$("#MyWalletConnectV2Modal").modal("hide"); 158 | } 159 | -------------------------------------------------------------------------------- /signing-providers/src/iframe-wallet.js: -------------------------------------------------------------------------------- 1 | import { 2 | WindowProviderRequestEnums, 3 | WindowProviderResponseEnums, 4 | SignMessageStatusEnum, 5 | } from "@multiversx/sdk-web-wallet-cross-window-provider/out/enums"; 6 | import { Address, Message, Transaction } from "@multiversx/sdk-core"; 7 | import { ExtensionProvider } from "@multiversx/sdk-extension-provider"; 8 | 9 | function getEventOrigin(event) { 10 | return event.origin || event.originalEvent.origin; 11 | } 12 | 13 | export class IframeWallet { 14 | constructor() { 15 | this._handshakeEstablished = false; 16 | this._isIframe = window.self !== window.top; 17 | this.provider = ExtensionProvider.getInstance(); 18 | window.addEventListener("message", this.messageListener.bind(this)); 19 | window.addEventListener("beforeunload", this.closeHandshake.bind(this)); 20 | this.replyToDapp({ 21 | type: WindowProviderResponseEnums.handshakeResponse, 22 | data: "", 23 | }); 24 | } 25 | 26 | async login() { 27 | await this.provider.init(); 28 | this.replyToDapp({ 29 | type: WindowProviderResponseEnums.handshakeResponse, 30 | data: "", 31 | }); 32 | const account = await this.provider.login(); 33 | 34 | this.replyToDapp({ 35 | type: WindowProviderResponseEnums.loginResponse, 36 | data: { 37 | address: account.address, 38 | signature: account.signature, 39 | }, 40 | }); 41 | } 42 | 43 | async logout() { 44 | await this.provider.init(); 45 | await this.provider.logout(); 46 | this.replyToDapp({ 47 | type: WindowProviderResponseEnums.disconnectResponse, 48 | data: {}, 49 | }); 50 | } 51 | 52 | async signMessage(payload) { 53 | const address = await this.provider.getAddress(); 54 | 55 | const message = new Message({ 56 | address: new Address(address), 57 | data: Buffer.from(payload.message), 58 | }); 59 | 60 | const signedMessage = await this.provider.signMessage(message); 61 | 62 | this.replyToDapp({ 63 | type: WindowProviderResponseEnums.signMessageResponse, 64 | data: { 65 | signature: Buffer.from(signedMessage?.signature).toString("hex"), 66 | status: SignMessageStatusEnum.signed, 67 | }, 68 | }); 69 | } 70 | 71 | closeHandshake() { 72 | this._handshakeEstablished = false; 73 | this.replyWithCancelled(); 74 | this.replyToDapp({ 75 | type: WindowProviderResponseEnums.handshakeResponse, 76 | data: "", 77 | }); 78 | } 79 | 80 | replyWithCancelled() { 81 | this.replyToDapp({ 82 | type: WindowProviderResponseEnums.cancelResponse, 83 | data: { address: "" }, 84 | }); 85 | } 86 | 87 | /** 88 | * @param {MessageEvent} event 89 | */ 90 | async messageListener(event) { 91 | const callbackUrl = getEventOrigin(event); 92 | const isFromSelf = callbackUrl === window.location.origin; 93 | 94 | if (isFromSelf) { 95 | return; 96 | } 97 | 98 | const { type, payload } = event.data; 99 | 100 | const isHandshakeEstablished = 101 | type === WindowProviderRequestEnums.finalizeHandshakeRequest || 102 | // handshake must be established for all other requests 103 | this._handshakeEstablished; 104 | 105 | if (!isHandshakeEstablished && !this._isIframe) { 106 | if (window.opener) { 107 | console.error("Handshake could not be established."); 108 | } 109 | 110 | return; 111 | } 112 | 113 | switch (type) { 114 | case WindowProviderRequestEnums.loginRequest: { 115 | await this.login(); 116 | break; 117 | } 118 | 119 | case WindowProviderRequestEnums.signMessageRequest: { 120 | await this.signMessage(payload); 121 | break; 122 | } 123 | 124 | case WindowProviderRequestEnums.signTransactionsRequest: { 125 | const transactions = payload.map((plainTransactionObject) => 126 | Transaction.newFromPlainObject(plainTransactionObject), 127 | ); 128 | 129 | const signedTransactions = await this.provider.signTransactions(transactions); 130 | 131 | this.replyToDapp({ 132 | type: WindowProviderResponseEnums.signTransactionsResponse, 133 | data: signedTransactions.map((transaction) => transaction.toPlainObject()), 134 | }); 135 | break; 136 | } 137 | 138 | case WindowProviderResponseEnums.cancelResponse: 139 | case WindowProviderRequestEnums.cancelAction: { 140 | this.replyWithCancelled(); 141 | break; 142 | } 143 | 144 | case WindowProviderRequestEnums.finalizeHandshakeRequest: { 145 | this._handshakeEstablished = true; 146 | this.replyToDapp({ 147 | type: WindowProviderResponseEnums.finalizeHandshakeResponse, 148 | data: { handshakeSession: "" }, 149 | }); 150 | break; 151 | } 152 | 153 | case WindowProviderRequestEnums.logoutRequest: { 154 | await this.logout(); 155 | break; 156 | } 157 | 158 | default: 159 | break; 160 | } 161 | } 162 | 163 | /** 164 | * @param {Object} props 165 | * @param {WindowProviderResponseEnums} props.type 166 | * @param {Object} props.data 167 | */ 168 | async replyToDapp(props) { 169 | const target = window.opener ?? window.parent; 170 | 171 | target.postMessage( 172 | { 173 | type: props.type, 174 | payload: { 175 | data: props.data, 176 | }, 177 | }, 178 | "*", 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /wallet/basic.js: -------------------------------------------------------------------------------- 1 | const { Account, Address, Message, Transaction, MessageComputer, Mnemonic, UserSigner, UserVerifier, UserSecretKey, TransactionComputer } = require("@multiversx/sdk-core"); 2 | const axios = require("axios"); 3 | 4 | // https://github.com/multiversx/mx-sdk-testwallets/blob/main/users/mnemonic.txt 5 | const DummyMnemonic = "moral volcano peasant pass circle pen over picture flat shop clap goat never lyrics gather prepare woman film husband gravity behind test tiger improve"; 6 | const APIUrl = "https://devnet-api.multiversx.com"; 7 | 8 | module.exports.exampleDeriveAccountsFromMnemonic = function () { 9 | const mnemonic = Mnemonic.fromString(DummyMnemonic); 10 | 11 | // https://github.com/multiversx/mx-sdk-js-wallet/blob/main/src/users.spec.ts 12 | const addressIndexOfAlice = 0; 13 | const userSecretKeyOfAlice = mnemonic.deriveKey(addressIndexOfAlice); 14 | const userPublicKeyOfAlice = userSecretKeyOfAlice.generatePublicKey(); 15 | const addressOfAlice = userPublicKeyOfAlice.toAddress(); 16 | const addressOfAliceAsBech32 = addressOfAlice.toBech32(); 17 | 18 | const addressIndexOfBob = 1; 19 | const userSecretKeyOfBob = mnemonic.deriveKey(addressIndexOfBob); 20 | const userPublicKeyOfBob = userSecretKeyOfBob.generatePublicKey(); 21 | const addressOfBob = userPublicKeyOfBob.toAddress(); 22 | const addressOfBobAsBech32 = addressOfBob.toBech32(); 23 | 24 | console.log("Alice", addressOfAliceAsBech32); 25 | console.log("Bob", addressOfBobAsBech32); 26 | }; 27 | 28 | module.exports.exampleSignAndBroadcastTransaction = async function () { 29 | const mnemonic = Mnemonic.fromString(DummyMnemonic); 30 | 31 | const userSecretKey = mnemonic.deriveKey(0); 32 | const userPublicKey = userSecretKey.generatePublicKey(); 33 | const address = userPublicKey.toAddress(); 34 | const signer = new UserSigner(userSecretKey); 35 | 36 | // https://docs.multiversx.com/integrators/creating-transactions/#nonce-management 37 | const nonce = await recallAccountNonce(address); 38 | 39 | // https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-cookbook/#preparing-a-simple-transaction 40 | const data = "for the lunch"; 41 | const transaction = new Transaction({ 42 | nonce: nonce, 43 | // 0.123456789000000000 EGLD 44 | value: 123456789000000000n, 45 | sender: address, 46 | receiver: new Address("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx"), 47 | data: Buffer.from(data), 48 | gasPrice: 1000000000, 49 | gasLimit: 500000n, 50 | chainID: "D" 51 | }); 52 | 53 | 54 | const transactionComputer = new TransactionComputer(); 55 | const serializedTransaction = transactionComputer.computeBytesForSigning(transaction); 56 | const signature = await signer.sign(serializedTransaction); 57 | transaction.signature = signature; 58 | 59 | console.log("Transaction signature", transaction.signature.toString()); 60 | console.log("Transaction hash", transaction.txHash); 61 | 62 | console.log("Data to broadcast:"); 63 | console.log(transaction); 64 | 65 | await broadcastTransaction(transaction); 66 | }; 67 | 68 | async function recallAccountNonce(address) { 69 | const url = `${APIUrl}/accounts/${address.toString()}`; 70 | const response = await axios.get(url); 71 | return response.data.nonce; 72 | } 73 | 74 | async function broadcastTransaction(transaction) { 75 | const url = `${APIUrl}/transactions`; 76 | const data = transaction.toSendable(); 77 | 78 | const response = await axios.post(url, data, { 79 | headers: { 80 | "Content-Type": "application/json", 81 | }, 82 | }); 83 | 84 | console.log(response.data); 85 | } 86 | 87 | module.exports.exampleSignMessage = async function () { 88 | const mnemonic = Mnemonic.fromString(DummyMnemonic); 89 | const userSecretKey = mnemonic.deriveKey(0); 90 | const userPublicKey = userSecretKey.generatePublicKey(); 91 | const address = userPublicKey.toAddress().toBech32(); 92 | const signer = new UserSigner(userSecretKey); 93 | 94 | const dataExample = `${address}hello{}`; 95 | const message = new Message({ 96 | data: Buffer.from(dataExample), 97 | address: address 98 | }); 99 | 100 | const messageComputer = new MessageComputer(); 101 | const serializedMessage = messageComputer.computeBytesForSigning(message); 102 | const signature = await signer.sign(serializedMessage); 103 | message.signature = signature; 104 | 105 | console.log("Message signature", message.signature); 106 | 107 | // In order to validate a message signature, follow: 108 | // https://docs.multiversx.com/sdk-and-tools/sdk-js/sdk-js-signing-providers/#verifying-the-signature-of-a-login-token 109 | }; 110 | 111 | module.exports.exampleVerifyMessage = async function () { 112 | let signer = new Account( 113 | UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"), 114 | ); 115 | let verifier = new UserVerifier( 116 | UserSecretKey.fromString( 117 | "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf", 118 | ).generatePublicKey(), 119 | ); 120 | const messageComputer = new MessageComputer(); 121 | const dataExample = `hello`; 122 | const message = new Message({ 123 | data: Buffer.from(dataExample), 124 | address: signer.address, 125 | }); 126 | message.signature = await signer.signMessage(message); 127 | 128 | const serializedMessage = messageComputer.computeBytesForSigning(message); 129 | const signature = message.signature; 130 | 131 | console.log("verify() with good signature:", await verifier.verify(serializedMessage, signature)); 132 | 133 | message.data = Buffer.from("bye"); 134 | const serializedMessageAltered = messageComputer.computeBytesForSigning(message); 135 | console.log("verify() with bad signature (message altered):", await verifier.verify(serializedMessageAltered, signature)); 136 | }; 137 | 138 | module.exports.exampleVerifyTransactionSignature = async function () { 139 | let signer = new Account( 140 | UserSecretKey.fromString("1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf"), 141 | ); 142 | let verifier = new UserVerifier( 143 | UserSecretKey.fromString( 144 | "1a927e2af5306a9bb2ea777f73e06ecc0ac9aaa72fb4ea3fecf659451394cccf", 145 | ).generatePublicKey(), 146 | ); 147 | const transactionComputer = new TransactionComputer(); 148 | const transaction = new Transaction({ 149 | nonce: 8n, 150 | value: 10000000000000000000n, 151 | sender: Address.newFromBech32("erd1l453hd0gt5gzdp7czpuall8ggt2dcv5zwmfdf3sd3lguxseux2fsmsgldz"), 152 | receiver: Address.newFromBech32("erd1cux02zersde0l7hhklzhywcxk4u9n4py5tdxyx7vrvhnza2r4gmq4vw35r"), 153 | gasPrice: 1000000000n, 154 | gasLimit: 50000n, 155 | chainID: "1", 156 | }); 157 | 158 | const serialized = transactionComputer.computeBytesForSigning(transaction); 159 | const signature = await signer.sign(serialized); 160 | console.log("verify() with good signature:", await verifier.verify(serialized, signature)); 161 | 162 | transaction.nonce = 7n; 163 | const serializedAlteredTransaction = transactionComputer.computeBytesForSigning(transaction); 164 | console.log("verify() with bad signature (message altered):", await verifier.verify(serializedAlteredTransaction, signature)); 165 | }; 166 | -------------------------------------------------------------------------------- /signing-providers/src/hw.js: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | ApiNetworkProvider, 4 | Message, 5 | ProxyNetworkProvider, 6 | Transaction, 7 | TransactionOptions, 8 | } from "@multiversx/sdk-core"; 9 | import { HWProvider } from "@multiversx/sdk-hw-provider"; 10 | import { CrossWindowProvider } from "@multiversx/sdk-web-wallet-cross-window-provider"; 11 | import { WalletProvider } from "@multiversx/sdk-web-wallet-provider"; 12 | import { createNativeAuthInitialPart, packNativeAuthToken, verifyNativeAuthToken } from "./auth"; 13 | import { API_URL, WALLET_PROVIDER_URL, CHAIN_ID, PROXY_URL } from "./config"; 14 | import { displayOutcome } from "./helpers"; 15 | 16 | export class HW { 17 | constructor() { 18 | this.hwProvider = new HWProvider(); 19 | this.walletProvider = new WalletProvider(WALLET_PROVIDER_URL); 20 | this.apiNetworkProvider = new ApiNetworkProvider(API_URL, { 21 | clientName: "multiversx-sdk-js-examples", 22 | }); 23 | 24 | this.proxyNetworkProvider = new ProxyNetworkProvider(PROXY_URL, { 25 | clientName: "multiversx-sdk-js-examples", 26 | }); 27 | } 28 | 29 | async login() { 30 | await this.hwProvider.init(); 31 | 32 | const addressIndex = parseInt(document.getElementById("addressIndexForLogin").value); 33 | console.log("AddressIndex", addressIndex); 34 | 35 | await this.hwProvider.login({ addressIndex: addressIndex }); 36 | 37 | const address = await this.hwProvider.getAddress(); 38 | 39 | displayOutcome("Logged in. Address:", address); 40 | } 41 | 42 | async loginWithToken() { 43 | await this.hwProvider.init(); 44 | 45 | const addressIndex = parseInt(document.getElementById("addressIndexForLogin").value); 46 | console.log("AddressIndex", addressIndex); 47 | 48 | const nativeAuthInitialPart = await createNativeAuthInitialPart(); 49 | 50 | const { address, signature } = await this.hwProvider.tokenLogin({ 51 | addressIndex: addressIndex, 52 | token: Buffer.from(nativeAuthInitialPart), 53 | }); 54 | 55 | const nativeAuthToken = packNativeAuthToken(address, nativeAuthInitialPart, signature.toString("hex")); 56 | verifyNativeAuthToken(nativeAuthToken); 57 | } 58 | 59 | async displayAddresses() { 60 | await this.hwProvider.init(); 61 | 62 | const addresses = await this.hwProvider.getAccounts(); 63 | alert(addresses.join(",\n")); 64 | } 65 | 66 | async setAddressIndex() { 67 | await this.hwProvider.init(); 68 | 69 | const addressIndex = parseInt(document.getElementById("addressIndexForSetAddress").value); 70 | console.log("Set addressIndex", addressIndex); 71 | 72 | await this.hwProvider.setAddressIndex(addressIndex); 73 | 74 | displayOutcome(`Address has been set: ${await this.hwProvider.getAddress()}.`); 75 | } 76 | 77 | async signTransaction() { 78 | await this.hwProvider.init(); 79 | 80 | const senderBech32 = await this.hwProvider.getAddress(); 81 | const sender = new Address(senderBech32); 82 | const guardian = await this.getGuardian(sender); 83 | 84 | const transactionOptions = guardian ? TransactionOptions.withOptions({ guarded: true }) : undefined; 85 | 86 | const transaction = new Transaction({ 87 | nonce: 42, 88 | value: "1", 89 | gasLimit: 70000, 90 | sender: sender, 91 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 92 | data: Buffer.from("hello"), 93 | chainID: CHAIN_ID, 94 | guardian: guardian, 95 | options: transactionOptions, 96 | }); 97 | 98 | const signedTransaction = await this.hwProvider.signTransaction(transaction); 99 | 100 | if (guardian) { 101 | const guardedTransactions = await this.guardTransactions([signedTransaction]); 102 | displayOutcome( 103 | "Transaction signed & guarded.", 104 | JSON.stringify(guardedTransactions.map((tx) => tx.toSendable())), 105 | ); 106 | } else { 107 | displayOutcome("Transaction signed.", signedTransaction.toSendable()); 108 | } 109 | } 110 | 111 | async signTransactions() { 112 | await this.hwProvider.init(); 113 | 114 | const senderBech32 = await this.hwProvider.getAddress(); 115 | const sender = new Address(senderBech32); 116 | const guardian = await this.getGuardian(sender); 117 | const transactionOptions = guardian ? TransactionOptions.withOptions({ guarded: true }) : undefined; 118 | 119 | const firstTransaction = new Transaction({ 120 | nonce: 42, 121 | value: "1", 122 | sender: sender, 123 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 124 | gasPrice: 1000000000, 125 | gasLimit: 50000, 126 | data: Buffer.from("hello"), 127 | chainID: CHAIN_ID, 128 | guardian: guardian, 129 | options: transactionOptions, 130 | }); 131 | 132 | const secondTransaction = new Transaction({ 133 | nonce: 43, 134 | value: "100000000", 135 | sender: sender, 136 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 137 | gasPrice: 1000000000, 138 | gasLimit: 50000, 139 | data: Buffer.from("hello world"), 140 | chainID: CHAIN_ID, 141 | guardian: guardian, 142 | options: transactionOptions, 143 | }); 144 | 145 | const transactions = [firstTransaction, secondTransaction]; 146 | const signedTransactions = await this.hwProvider.signTransactions(transactions); 147 | 148 | if (guardian) { 149 | const guardedTransactions = await this.guardTransactions(signedTransactions); 150 | displayOutcome( 151 | "Transactions signed & guarded.", 152 | JSON.stringify(guardedTransactions.map((tx) => tx.toSendable())), 153 | ); 154 | } else { 155 | displayOutcome( 156 | "Transactions signed.", 157 | signedTransactions.map((transaction) => transaction.toSendable()), 158 | ); 159 | } 160 | } 161 | 162 | async getGuardian(sender) { 163 | const guardianData = await this.proxyNetworkProvider.getGuardianData(sender); 164 | return guardianData.getCurrentGuardianAddress(); 165 | } 166 | 167 | async showSignedTransactionsWhenGuarded() { 168 | const plainSignedTransactions = this.walletProvider.getTransactionsFromWalletUrl(); 169 | const signedTransactions = []; 170 | 171 | // Now let's convert them back to sdk-js' Transaction objects. 172 | // Note that the Web Wallet provider returns the data field as a plain string. 173 | // However, sdk-js' Transaction.newFromPlainObject expects it to be base64-encoded. 174 | // Therefore, we need to apply a workaround (an additional conversion). 175 | for (const plainTransaction of plainSignedTransactions) { 176 | const plainTransactionClone = structuredClone(plainTransaction); 177 | plainTransactionClone.data = Buffer.from(plainTransactionClone.data).toString("base64"); 178 | const transaction = Transaction.newFromPlainObject(plainTransactionClone); 179 | signedTransactions.push(transaction); 180 | } 181 | 182 | displayOutcome( 183 | "Transactions signed.", 184 | signedTransactions.map((transaction) => transaction.toSendable()), 185 | ); 186 | } 187 | 188 | async signMessage() { 189 | await this.hwProvider.init(); 190 | const address = await this.hwProvider.getAddress(); 191 | 192 | const message = new Message({ 193 | address: new Address(address), 194 | data: Buffer.from("hello"), 195 | }); 196 | 197 | const signedMessage = await this.hwProvider.signMessage(message); 198 | 199 | displayOutcome("Message signed. Signature: ", Buffer.from(signedMessage?.signature).toString("hex")); 200 | } 201 | 202 | async guardTransactions(transactions) { 203 | // instantiate wallet cross-window provider 204 | await CrossWindowProvider.getInstance().init(); 205 | const crossWindowProvider = CrossWindowProvider.getInstance(); 206 | crossWindowProvider.setWalletUrl(WALLET_PROVIDER_URL); 207 | 208 | // set sender 209 | const senderBech32 = await this.hwProvider.getAddress(); 210 | const sender = new Address(senderBech32); 211 | crossWindowProvider.setAddress(sender); 212 | 213 | // the user signs transactions on ledger so we need to perform an extra 214 | // user action so the popup is opened 215 | crossWindowProvider.setShouldShowConsentPopup(true); 216 | 217 | const guardedTransactions = await crossWindowProvider.guardTransactions(transactions); 218 | 219 | return guardedTransactions; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /signing-providers/src/web-wallet.js: -------------------------------------------------------------------------------- 1 | import { Address, AddressComputer, ApiNetworkProvider, Message, Transaction } from "@multiversx/sdk-core"; 2 | import { WalletProvider } from "@multiversx/sdk-web-wallet-provider"; 3 | import qs from "qs"; 4 | import { createNativeAuthInitialPart, packNativeAuthToken, verifyNativeAuthToken } from "./auth"; 5 | import { API_URL, CHAIN_ID, WALLET_PROVIDER_URL } from "./config"; 6 | import { displayOutcome } from "./helpers"; 7 | 8 | export class WebWallet { 9 | constructor() { 10 | this.provider = new WalletProvider(WALLET_PROVIDER_URL); 11 | this.apiNetworkProvider = new ApiNetworkProvider(API_URL, { 12 | clientName: "multiversx-sdk-js-examples", 13 | }); 14 | this._address = ""; 15 | } 16 | 17 | async login() { 18 | const callbackUrl = getCurrentLocation(); 19 | await this.provider.login({ callbackUrl: callbackUrl }); 20 | } 21 | 22 | async loginWithToken() { 23 | const nativeAuthInitialPart = await createNativeAuthInitialPart(); 24 | // This is just an example of how to store the "nativeAuthInitialPart" in-between page changes & redirects (in "localStorage"). 25 | // In real-life, use the approach that best suits your application. 26 | localStorage.setItem("web-wallet-example:nativeAuthInitialPart", nativeAuthInitialPart); 27 | const callbackUrl = getCurrentLocation(); 28 | await this.provider.login({ 29 | callbackUrl: callbackUrl, 30 | token: nativeAuthInitialPart, 31 | }); 32 | } 33 | 34 | async logout() { 35 | const callbackUrl = getCurrentLocation(); 36 | await this.provider.logout({ 37 | callbackUrl: callbackUrl, 38 | redirectDelayMilliseconds: 10, 39 | }); 40 | } 41 | 42 | async showAddress() { 43 | const address = getUrlParams().address; 44 | this._address = address; 45 | displayOutcome(address ? "Address: " : "Error: ", address ? address : "Try to login first."); 46 | } 47 | 48 | async showTokenSignature() { 49 | const signature = getUrlParams().signature; 50 | displayOutcome( 51 | signature ? "Signature: " : "Error: ", 52 | signature ? signature : "Try to login (with token) first.", 53 | ); 54 | } 55 | 56 | async validateTokenSignature() { 57 | const address = getUrlParams().address; 58 | const nativeAuthInitialPart = await localStorage.getItem("web-wallet-example:nativeAuthInitialPart"); 59 | const signature = getUrlParams().signature; 60 | const nativeAuthToken = packNativeAuthToken(address, nativeAuthInitialPart, signature); 61 | 62 | verifyNativeAuthToken(nativeAuthToken); 63 | } 64 | 65 | async signTransaction() { 66 | const sender = getUrlParams().address; 67 | if (!sender) { 68 | displayOutcome("Try to login first."); 69 | return; 70 | } 71 | 72 | const senderNonce = await this.recallNonce(sender); 73 | 74 | const transaction = new Transaction({ 75 | nonce: senderNonce, 76 | value: "1000000000000000000", 77 | sender: new Address(sender), 78 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 79 | gasPrice: 1000000000, 80 | gasLimit: 50000, 81 | data: Buffer.from("hello world"), 82 | chainID: CHAIN_ID, 83 | }); 84 | 85 | await this.provider.signTransaction(transaction); 86 | } 87 | 88 | async signTransactions() { 89 | const sender = getUrlParams().address; 90 | if (!sender) { 91 | displayOutcome("Try to login first."); 92 | return; 93 | } 94 | 95 | const senderNonce = await this.recallNonce(sender); 96 | 97 | const firstTransaction = new Transaction({ 98 | nonce: senderNonce, 99 | value: "1000000000000000000", 100 | gasLimit: 70000, 101 | sender: new Address(sender), 102 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 103 | data: Buffer.from("hello"), 104 | chainID: CHAIN_ID, 105 | }); 106 | 107 | const secondTransaction = new Transaction({ 108 | nonce: senderNonce + 1n, 109 | value: "3000000000000000000", 110 | gasLimit: 70000, 111 | sender: new Address(sender), 112 | receiver: new Address("erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa"), 113 | data: Buffer.from("world"), 114 | chainID: CHAIN_ID, 115 | }); 116 | 117 | await this.provider.signTransactions([firstTransaction, secondTransaction]); 118 | } 119 | 120 | async signRelayedTransaction() { 121 | const sender = getUrlParams().address; 122 | if (!sender) { 123 | displayOutcome("Try to login first."); 124 | return; 125 | } 126 | 127 | const senderShard = new AddressComputer().getShardOfAddress(Address.newFromBech32(sender)); 128 | const relayer = { 129 | // https://github.com/multiversx/mx-sdk-testwallets/blob/main/users/mike.pem 130 | 0: "erd1uv40ahysflse896x4ktnh6ecx43u7cmy9wnxnvcyp7deg299a4sq6vaywa", 131 | // https://github.com/multiversx/mx-sdk-testwallets/blob/main/users/grace.pem 132 | 1: "erd1r69gk66fmedhhcg24g2c5kn2f2a5k4kvpr6jfw67dn2lyydd8cfswy6ede", 133 | // https://github.com/multiversx/mx-sdk-testwallets/blob/main/users/carol.pem 134 | 2: "erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8", 135 | }[senderShard]; 136 | 137 | console.log("Relayer shard:", senderShard); 138 | console.log("Relayer:", relayer); 139 | 140 | const senderNonce = await this.recallNonce(sender); 141 | const data = Buffer.from("hello"); 142 | 143 | const transaction = new Transaction({ 144 | nonce: senderNonce, 145 | value: "10000000000000000", 146 | sender: Address.newFromBech32(sender), 147 | receiver: Address.newFromBech32("erd1testnlersh4z0wsv8kjx39me4rmnvjkwu8dsaea7ukdvvc9z396qykv7z7"), 148 | relayer: Address.newFromBech32(relayer), 149 | gasPrice: 1000000000, 150 | gasLimit: 100000 + 1500 * data.length, 151 | data: data, 152 | chainID: CHAIN_ID, 153 | }); 154 | 155 | await this.provider.signTransaction(transaction); 156 | } 157 | 158 | async showSignedTransactions() { 159 | const plainSignedTransactions = this.provider.getTransactionsFromWalletUrl(); 160 | alert(JSON.stringify(plainSignedTransactions, null, 4)); 161 | 162 | // Now let's convert them back to sdk-js' Transaction objects. 163 | // Note that the Web Wallet provider returns the data field as a plain string. 164 | // However, sdk-js' Transaction.fromPlainObject expects it to be base64-encoded. 165 | // Therefore, we need to apply a workaround (an additional conversion). 166 | for (const plainTransaction of plainSignedTransactions) { 167 | const plainTransactionClone = structuredClone(plainTransaction); 168 | plainTransactionClone.data = Buffer.from(plainTransactionClone.data).toString("base64"); 169 | const transaction = Transaction.newFromPlainObject(plainTransactionClone); 170 | 171 | console.log(transaction.toSendable()); 172 | } 173 | } 174 | 175 | async sendSignedTransactions() { 176 | const plainSignedTransactions = this.provider.getTransactionsFromWalletUrl(); 177 | 178 | for (const plainTransaction of plainSignedTransactions) { 179 | const plainTransactionClone = structuredClone(plainTransaction); 180 | plainTransactionClone.data = Buffer.from(plainTransactionClone.data).toString("base64"); 181 | const transaction = Transaction.newFromPlainObject(plainTransactionClone); 182 | 183 | await this.apiNetworkProvider.sendTransaction(transaction); 184 | } 185 | } 186 | 187 | async signMessage() { 188 | if (!this._address) { 189 | return displayOutcome("Unable to sign.", "Login & press Show address first."); 190 | } 191 | 192 | const message = new Message({ 193 | address: new Address(this._address), 194 | data: Buffer.from("hello"), 195 | }); 196 | 197 | const callbackUrl = getCurrentLocation(); 198 | await this.provider.signMessage(message, { callbackUrl }); 199 | } 200 | 201 | async showMessageSignature() { 202 | const signature = this.provider.getMessageSignatureFromWalletUrl(); 203 | return displayOutcome("Signature:", signature); 204 | } 205 | 206 | async recallNonce(address) { 207 | const accountOnNetwork = await this.apiNetworkProvider.getAccount(Address.newFromBech32(address)); 208 | const nonce = BigInt(accountOnNetwork.nonce); 209 | 210 | console.log(`recallNonce(), address = ${address}, nonce = ${nonce}`); 211 | 212 | return nonce; 213 | } 214 | } 215 | 216 | function getUrlParams() { 217 | const queryString = window.location.search.slice(1); 218 | const params = qs.parse(queryString); 219 | 220 | console.log("URL params", params); 221 | 222 | return params; 223 | } 224 | 225 | function getCurrentLocation() { 226 | return window.location.href.split("?")[0]; 227 | } 228 | -------------------------------------------------------------------------------- /signing-providers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Examples 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Examples of using sdk-js' signing providers

12 | 13 |
14 |
15 |
16 |

[🌐] Web Wallet URL

17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 |
31 |
32 |
Extract data from URL (query string)
33 | 34 | 35 | 38 | 41 | 44 | 47 |
48 |
49 |
Broadcast
50 | 51 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |

[🗂️] Web Wallet Cross-Window

62 |
63 |
64 |
65 |
66 | 67 | 70 | 73 | 76 | 77 | 78 |
79 |
80 |
81 | 82 |
83 |
84 |
85 |

[🦊] Metamask

86 |

87 | NOTE: use 88 |

localhost:8080
91 | to connect 92 |

93 |
94 |
95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 |
105 |
106 | 107 |
108 |
109 |
110 |

[🧩] Extension (MultiversX DeFi Wallet)

111 |
112 |
113 |
114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 |
122 |
123 |
124 | 125 |
126 |
127 |
128 |

[📱] Wallet Connect V2 ( xPortal )

129 |
130 |
131 |
132 |
133 | 134 | 137 | 140 | 143 | 144 | 145 |
146 |
147 | 165 |
166 | 167 |
168 |
169 |
170 |

[𝍍] Hardware Wallet

171 |
172 |
173 |
174 |
175 |
176 | 177 |
178 |
179 |
180 | 188 |
189 |
190 | 191 | 192 |
193 |
194 |
195 |
196 | 204 |
205 |
206 | 207 |
208 |
209 | 210 |
211 | 212 | 213 | 214 |
215 |
216 | 219 |
220 |
221 |
222 |
223 |
224 | 225 |
226 |
227 |
228 |

[🖼️] Iframe Communication

229 |
230 |
231 |
232 |
233 | 234 | 237 | 238 | 239 |
240 |
241 |
242 | 243 | 244 | 245 | 250 | 255 | 260 | 261 | 262 | 263 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /contracts/multisig-full.abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildInfo": { 3 | "rustc": { 4 | "version": "1.71.0-nightly", 5 | "commitHash": "a2b1646c597329d0a25efa3889b66650f65de1de", 6 | "commitDate": "2023-05-25", 7 | "channel": "Nightly", 8 | "short": "rustc 1.71.0-nightly (a2b1646c5 2023-05-25)" 9 | }, 10 | "contractCrate": { 11 | "name": "multisig", 12 | "version": "1.0.0", 13 | "gitVersion": "v0.45.2.1-reproducible-169-g37d970c" 14 | }, 15 | "framework": { 16 | "name": "multiversx-sc", 17 | "version": "0.47.2" 18 | } 19 | }, 20 | "docs": [ 21 | "Multi-signature smart contract implementation.", 22 | "Acts like a wallet that needs multiple signers for any action performed.", 23 | "See the readme file for more detailed documentation." 24 | ], 25 | "name": "Multisig", 26 | "constructor": { 27 | "inputs": [ 28 | { 29 | "name": "quorum", 30 | "type": "u32" 31 | }, 32 | { 33 | "name": "board", 34 | "type": "variadic
", 35 | "multi_arg": true 36 | } 37 | ], 38 | "outputs": [] 39 | }, 40 | "endpoints": [ 41 | { 42 | "name": "upgrade", 43 | "mutability": "mutable", 44 | "inputs": [], 45 | "outputs": [] 46 | }, 47 | { 48 | "docs": [ 49 | "Allows the contract to receive funds even if it is marked as unpayable in the protocol." 50 | ], 51 | "name": "deposit", 52 | "mutability": "mutable", 53 | "payableInTokens": [ 54 | "*" 55 | ], 56 | "inputs": [], 57 | "outputs": [] 58 | }, 59 | { 60 | "docs": [ 61 | "Clears storage pertaining to an action that is no longer supposed to be executed.", 62 | "Any signatures that the action received must first be removed, via `unsign`.", 63 | "Otherwise this endpoint would be prone to abuse." 64 | ], 65 | "name": "discardAction", 66 | "mutability": "mutable", 67 | "inputs": [ 68 | { 69 | "name": "action_id", 70 | "type": "u32" 71 | } 72 | ], 73 | "outputs": [] 74 | }, 75 | { 76 | "docs": [ 77 | "Discard all the actions with the given IDs" 78 | ], 79 | "name": "discardBatch", 80 | "mutability": "mutable", 81 | "inputs": [ 82 | { 83 | "name": "action_ids", 84 | "type": "variadic", 85 | "multi_arg": true 86 | } 87 | ], 88 | "outputs": [] 89 | }, 90 | { 91 | "docs": [ 92 | "Minimum number of signatures needed to perform any action." 93 | ], 94 | "name": "getQuorum", 95 | "mutability": "readonly", 96 | "inputs": [], 97 | "outputs": [ 98 | { 99 | "type": "u32" 100 | } 101 | ] 102 | }, 103 | { 104 | "docs": [ 105 | "Denormalized board member count.", 106 | "It is kept in sync with the user list by the contract." 107 | ], 108 | "name": "getNumBoardMembers", 109 | "mutability": "readonly", 110 | "inputs": [], 111 | "outputs": [ 112 | { 113 | "type": "u32" 114 | } 115 | ] 116 | }, 117 | { 118 | "name": "getNumGroups", 119 | "mutability": "readonly", 120 | "inputs": [], 121 | "outputs": [ 122 | { 123 | "type": "u32" 124 | } 125 | ] 126 | }, 127 | { 128 | "docs": [ 129 | "Denormalized proposer count.", 130 | "It is kept in sync with the user list by the contract." 131 | ], 132 | "name": "getNumProposers", 133 | "mutability": "readonly", 134 | "inputs": [], 135 | "outputs": [ 136 | { 137 | "type": "u32" 138 | } 139 | ] 140 | }, 141 | { 142 | "name": "getActionGroup", 143 | "mutability": "readonly", 144 | "inputs": [ 145 | { 146 | "name": "group_id", 147 | "type": "u32" 148 | } 149 | ], 150 | "outputs": [ 151 | { 152 | "type": "variadic", 153 | "multi_result": true 154 | } 155 | ] 156 | }, 157 | { 158 | "name": "getLastGroupActionId", 159 | "mutability": "readonly", 160 | "inputs": [], 161 | "outputs": [ 162 | { 163 | "type": "u32" 164 | } 165 | ] 166 | }, 167 | { 168 | "docs": [ 169 | "The index of the last proposed action.", 170 | "0 means that no action was ever proposed yet." 171 | ], 172 | "name": "getActionLastIndex", 173 | "mutability": "readonly", 174 | "inputs": [], 175 | "outputs": [ 176 | { 177 | "type": "u32" 178 | } 179 | ] 180 | }, 181 | { 182 | "docs": [ 183 | "Initiates board member addition process.", 184 | "Can also be used to promote a proposer to board member." 185 | ], 186 | "name": "proposeAddBoardMember", 187 | "mutability": "mutable", 188 | "inputs": [ 189 | { 190 | "name": "board_member_address", 191 | "type": "Address" 192 | } 193 | ], 194 | "outputs": [ 195 | { 196 | "type": "u32" 197 | } 198 | ] 199 | }, 200 | { 201 | "docs": [ 202 | "Initiates proposer addition process..", 203 | "Can also be used to demote a board member to proposer." 204 | ], 205 | "name": "proposeAddProposer", 206 | "mutability": "mutable", 207 | "inputs": [ 208 | { 209 | "name": "proposer_address", 210 | "type": "Address" 211 | } 212 | ], 213 | "outputs": [ 214 | { 215 | "type": "u32" 216 | } 217 | ] 218 | }, 219 | { 220 | "docs": [ 221 | "Removes user regardless of whether it is a board member or proposer." 222 | ], 223 | "name": "proposeRemoveUser", 224 | "mutability": "mutable", 225 | "inputs": [ 226 | { 227 | "name": "user_address", 228 | "type": "Address" 229 | } 230 | ], 231 | "outputs": [ 232 | { 233 | "type": "u32" 234 | } 235 | ] 236 | }, 237 | { 238 | "name": "proposeChangeQuorum", 239 | "mutability": "mutable", 240 | "inputs": [ 241 | { 242 | "name": "new_quorum", 243 | "type": "u32" 244 | } 245 | ], 246 | "outputs": [ 247 | { 248 | "type": "u32" 249 | } 250 | ] 251 | }, 252 | { 253 | "docs": [ 254 | "Propose a transaction in which the contract will perform a transfer-execute call.", 255 | "Can send EGLD without calling anything.", 256 | "Can call smart contract endpoints directly.", 257 | "Doesn't really work with builtin functions." 258 | ], 259 | "name": "proposeTransferExecute", 260 | "mutability": "mutable", 261 | "inputs": [ 262 | { 263 | "name": "to", 264 | "type": "Address" 265 | }, 266 | { 267 | "name": "egld_amount", 268 | "type": "BigUint" 269 | }, 270 | { 271 | "name": "opt_gas_limit", 272 | "type": "Option" 273 | }, 274 | { 275 | "name": "function_call", 276 | "type": "variadic", 277 | "multi_arg": true 278 | } 279 | ], 280 | "outputs": [ 281 | { 282 | "type": "u32" 283 | } 284 | ] 285 | }, 286 | { 287 | "name": "proposeTransferExecuteEsdt", 288 | "mutability": "mutable", 289 | "inputs": [ 290 | { 291 | "name": "to", 292 | "type": "Address" 293 | }, 294 | { 295 | "name": "tokens", 296 | "type": "List" 297 | }, 298 | { 299 | "name": "opt_gas_limit", 300 | "type": "Option" 301 | }, 302 | { 303 | "name": "function_call", 304 | "type": "variadic", 305 | "multi_arg": true 306 | } 307 | ], 308 | "outputs": [ 309 | { 310 | "type": "u32" 311 | } 312 | ] 313 | }, 314 | { 315 | "docs": [ 316 | "Propose a transaction in which the contract will perform an async call call.", 317 | "Can call smart contract endpoints directly.", 318 | "Can use ESDTTransfer/ESDTNFTTransfer/MultiESDTTransfer to send tokens, while also optionally calling endpoints.", 319 | "Works well with builtin functions.", 320 | "Cannot simply send EGLD directly without calling anything." 321 | ], 322 | "name": "proposeAsyncCall", 323 | "mutability": "mutable", 324 | "inputs": [ 325 | { 326 | "name": "to", 327 | "type": "Address" 328 | }, 329 | { 330 | "name": "egld_amount", 331 | "type": "BigUint" 332 | }, 333 | { 334 | "name": "opt_gas_limit", 335 | "type": "Option" 336 | }, 337 | { 338 | "name": "function_call", 339 | "type": "variadic", 340 | "multi_arg": true 341 | } 342 | ], 343 | "outputs": [ 344 | { 345 | "type": "u32" 346 | } 347 | ] 348 | }, 349 | { 350 | "name": "proposeSCDeployFromSource", 351 | "mutability": "mutable", 352 | "inputs": [ 353 | { 354 | "name": "amount", 355 | "type": "BigUint" 356 | }, 357 | { 358 | "name": "source", 359 | "type": "Address" 360 | }, 361 | { 362 | "name": "code_metadata", 363 | "type": "CodeMetadata" 364 | }, 365 | { 366 | "name": "arguments", 367 | "type": "variadic", 368 | "multi_arg": true 369 | } 370 | ], 371 | "outputs": [ 372 | { 373 | "type": "u32" 374 | } 375 | ] 376 | }, 377 | { 378 | "name": "proposeSCUpgradeFromSource", 379 | "mutability": "mutable", 380 | "inputs": [ 381 | { 382 | "name": "sc_address", 383 | "type": "Address" 384 | }, 385 | { 386 | "name": "amount", 387 | "type": "BigUint" 388 | }, 389 | { 390 | "name": "source", 391 | "type": "Address" 392 | }, 393 | { 394 | "name": "code_metadata", 395 | "type": "CodeMetadata" 396 | }, 397 | { 398 | "name": "arguments", 399 | "type": "variadic", 400 | "multi_arg": true 401 | } 402 | ], 403 | "outputs": [ 404 | { 405 | "type": "u32" 406 | } 407 | ] 408 | }, 409 | { 410 | "name": "proposeBatch", 411 | "mutability": "mutable", 412 | "inputs": [ 413 | { 414 | "name": "actions", 415 | "type": "variadic", 416 | "multi_arg": true 417 | } 418 | ], 419 | "outputs": [ 420 | { 421 | "type": "u32" 422 | } 423 | ] 424 | }, 425 | { 426 | "docs": [ 427 | "Used by board members to sign actions." 428 | ], 429 | "name": "sign", 430 | "mutability": "mutable", 431 | "inputs": [ 432 | { 433 | "name": "action_id", 434 | "type": "u32" 435 | } 436 | ], 437 | "outputs": [] 438 | }, 439 | { 440 | "docs": [ 441 | "Sign all the actions in the given batch" 442 | ], 443 | "name": "signBatch", 444 | "mutability": "mutable", 445 | "inputs": [ 446 | { 447 | "name": "group_id", 448 | "type": "u32" 449 | } 450 | ], 451 | "outputs": [] 452 | }, 453 | { 454 | "name": "signAndPerform", 455 | "mutability": "mutable", 456 | "inputs": [ 457 | { 458 | "name": "action_id", 459 | "type": "u32" 460 | } 461 | ], 462 | "outputs": [ 463 | { 464 | "type": "optional
", 465 | "multi_result": true 466 | } 467 | ] 468 | }, 469 | { 470 | "name": "signBatchAndPerform", 471 | "mutability": "mutable", 472 | "inputs": [ 473 | { 474 | "name": "group_id", 475 | "type": "u32" 476 | } 477 | ], 478 | "outputs": [] 479 | }, 480 | { 481 | "docs": [ 482 | "Board members can withdraw their signatures if they no longer desire for the action to be executed.", 483 | "Actions that are left with no valid signatures can be then deleted to free up storage." 484 | ], 485 | "name": "unsign", 486 | "mutability": "mutable", 487 | "inputs": [ 488 | { 489 | "name": "action_id", 490 | "type": "u32" 491 | } 492 | ], 493 | "outputs": [] 494 | }, 495 | { 496 | "docs": [ 497 | "Unsign all actions with the given IDs" 498 | ], 499 | "name": "unsignBatch", 500 | "mutability": "mutable", 501 | "inputs": [ 502 | { 503 | "name": "group_id", 504 | "type": "u32" 505 | } 506 | ], 507 | "outputs": [] 508 | }, 509 | { 510 | "docs": [ 511 | "Returns `true` (`1`) if the user has signed the action.", 512 | "Does not check whether or not the user is still a board member and the signature valid." 513 | ], 514 | "name": "signed", 515 | "mutability": "readonly", 516 | "inputs": [ 517 | { 518 | "name": "user", 519 | "type": "Address" 520 | }, 521 | { 522 | "name": "action_id", 523 | "type": "u32" 524 | } 525 | ], 526 | "outputs": [ 527 | { 528 | "type": "bool" 529 | } 530 | ] 531 | }, 532 | { 533 | "name": "unsignForOutdatedBoardMembers", 534 | "mutability": "mutable", 535 | "inputs": [ 536 | { 537 | "name": "action_id", 538 | "type": "u32" 539 | }, 540 | { 541 | "name": "outdated_board_members", 542 | "type": "variadic", 543 | "multi_arg": true 544 | } 545 | ], 546 | "outputs": [] 547 | }, 548 | { 549 | "docs": [ 550 | "Returns `true` (`1`) if `getActionValidSignerCount >= getQuorum`." 551 | ], 552 | "name": "quorumReached", 553 | "mutability": "readonly", 554 | "inputs": [ 555 | { 556 | "name": "action_id", 557 | "type": "u32" 558 | } 559 | ], 560 | "outputs": [ 561 | { 562 | "type": "bool" 563 | } 564 | ] 565 | }, 566 | { 567 | "docs": [ 568 | "Proposers and board members use this to launch signed actions." 569 | ], 570 | "name": "performAction", 571 | "mutability": "mutable", 572 | "inputs": [ 573 | { 574 | "name": "action_id", 575 | "type": "u32" 576 | } 577 | ], 578 | "outputs": [ 579 | { 580 | "type": "optional
", 581 | "multi_result": true 582 | } 583 | ] 584 | }, 585 | { 586 | "docs": [ 587 | "Perform all the actions in the given batch" 588 | ], 589 | "name": "performBatch", 590 | "mutability": "mutable", 591 | "inputs": [ 592 | { 593 | "name": "group_id", 594 | "type": "u32" 595 | } 596 | ], 597 | "outputs": [] 598 | }, 599 | { 600 | "name": "dnsRegister", 601 | "onlyOwner": true, 602 | "mutability": "mutable", 603 | "payableInTokens": [ 604 | "EGLD" 605 | ], 606 | "inputs": [ 607 | { 608 | "name": "dns_address", 609 | "type": "Address" 610 | }, 611 | { 612 | "name": "name", 613 | "type": "bytes" 614 | } 615 | ], 616 | "outputs": [] 617 | }, 618 | { 619 | "docs": [ 620 | "Iterates through all actions and retrieves those that are still pending.", 621 | "Serialized full action data:", 622 | "- the action id", 623 | "- the serialized action data", 624 | "- (number of signers followed by) list of signer addresses." 625 | ], 626 | "name": "getPendingActionFullInfo", 627 | "mutability": "readonly", 628 | "inputs": [ 629 | { 630 | "name": "opt_range", 631 | "type": "optional>", 632 | "multi_arg": true 633 | } 634 | ], 635 | "outputs": [ 636 | { 637 | "type": "variadic", 638 | "multi_result": true 639 | } 640 | ], 641 | "labels": [ 642 | "multisig-external-view" 643 | ], 644 | "allow_multiple_var_args": true 645 | }, 646 | { 647 | "docs": [ 648 | "Indicates user rights.", 649 | "`0` = no rights,", 650 | "`1` = can propose, but not sign,", 651 | "`2` = can propose and sign." 652 | ], 653 | "name": "userRole", 654 | "mutability": "readonly", 655 | "inputs": [ 656 | { 657 | "name": "user", 658 | "type": "Address" 659 | } 660 | ], 661 | "outputs": [ 662 | { 663 | "type": "UserRole" 664 | } 665 | ], 666 | "labels": [ 667 | "multisig-external-view" 668 | ] 669 | }, 670 | { 671 | "docs": [ 672 | "Lists all users that can sign actions." 673 | ], 674 | "name": "getAllBoardMembers", 675 | "mutability": "readonly", 676 | "inputs": [], 677 | "outputs": [ 678 | { 679 | "type": "variadic
", 680 | "multi_result": true 681 | } 682 | ], 683 | "labels": [ 684 | "multisig-external-view" 685 | ] 686 | }, 687 | { 688 | "docs": [ 689 | "Lists all proposers that are not board members." 690 | ], 691 | "name": "getAllProposers", 692 | "mutability": "readonly", 693 | "inputs": [], 694 | "outputs": [ 695 | { 696 | "type": "variadic
", 697 | "multi_result": true 698 | } 699 | ], 700 | "labels": [ 701 | "multisig-external-view" 702 | ] 703 | }, 704 | { 705 | "docs": [ 706 | "Serialized action data of an action with index." 707 | ], 708 | "name": "getActionData", 709 | "mutability": "readonly", 710 | "inputs": [ 711 | { 712 | "name": "action_id", 713 | "type": "u32" 714 | } 715 | ], 716 | "outputs": [ 717 | { 718 | "type": "Action" 719 | } 720 | ], 721 | "labels": [ 722 | "multisig-external-view" 723 | ] 724 | }, 725 | { 726 | "docs": [ 727 | "Gets addresses of all users who signed an action.", 728 | "Does not check if those users are still board members or not,", 729 | "so the result may contain invalid signers." 730 | ], 731 | "name": "getActionSigners", 732 | "mutability": "readonly", 733 | "inputs": [ 734 | { 735 | "name": "action_id", 736 | "type": "u32" 737 | } 738 | ], 739 | "outputs": [ 740 | { 741 | "type": "List
" 742 | } 743 | ], 744 | "labels": [ 745 | "multisig-external-view" 746 | ] 747 | }, 748 | { 749 | "docs": [ 750 | "Gets addresses of all users who signed an action and are still board members.", 751 | "All these signatures are currently valid." 752 | ], 753 | "name": "getActionSignerCount", 754 | "mutability": "readonly", 755 | "inputs": [ 756 | { 757 | "name": "action_id", 758 | "type": "u32" 759 | } 760 | ], 761 | "outputs": [ 762 | { 763 | "type": "u32" 764 | } 765 | ], 766 | "labels": [ 767 | "multisig-external-view" 768 | ] 769 | }, 770 | { 771 | "docs": [ 772 | "It is possible for board members to lose their role.", 773 | "They are not automatically removed from all actions when doing so,", 774 | "therefore the contract needs to re-check every time when actions are performed.", 775 | "This function is used to validate the signers before performing an action.", 776 | "It also makes it easy to check before performing an action." 777 | ], 778 | "name": "getActionValidSignerCount", 779 | "mutability": "readonly", 780 | "inputs": [ 781 | { 782 | "name": "action_id", 783 | "type": "u32" 784 | } 785 | ], 786 | "outputs": [ 787 | { 788 | "type": "u32" 789 | } 790 | ], 791 | "labels": [ 792 | "multisig-external-view" 793 | ] 794 | } 795 | ], 796 | "events": [ 797 | { 798 | "identifier": "asyncCallSuccess", 799 | "inputs": [ 800 | { 801 | "name": "results", 802 | "type": "variadic", 803 | "indexed": true 804 | } 805 | ] 806 | }, 807 | { 808 | "identifier": "asyncCallError", 809 | "inputs": [ 810 | { 811 | "name": "err_code", 812 | "type": "u32", 813 | "indexed": true 814 | }, 815 | { 816 | "name": "err_message", 817 | "type": "bytes", 818 | "indexed": true 819 | } 820 | ] 821 | }, 822 | { 823 | "identifier": "startPerformAction", 824 | "inputs": [ 825 | { 826 | "name": "data", 827 | "type": "ActionFullInfo" 828 | } 829 | ] 830 | }, 831 | { 832 | "identifier": "performChangeUser", 833 | "inputs": [ 834 | { 835 | "name": "action_id", 836 | "type": "u32", 837 | "indexed": true 838 | }, 839 | { 840 | "name": "changed_user", 841 | "type": "Address", 842 | "indexed": true 843 | }, 844 | { 845 | "name": "old_role", 846 | "type": "UserRole", 847 | "indexed": true 848 | }, 849 | { 850 | "name": "new_role", 851 | "type": "UserRole", 852 | "indexed": true 853 | } 854 | ] 855 | }, 856 | { 857 | "identifier": "performChangeQuorum", 858 | "inputs": [ 859 | { 860 | "name": "action_id", 861 | "type": "u32", 862 | "indexed": true 863 | }, 864 | { 865 | "name": "new_quorum", 866 | "type": "u32", 867 | "indexed": true 868 | } 869 | ] 870 | }, 871 | { 872 | "identifier": "performAsyncCall", 873 | "inputs": [ 874 | { 875 | "name": "action_id", 876 | "type": "u32", 877 | "indexed": true 878 | }, 879 | { 880 | "name": "to", 881 | "type": "Address", 882 | "indexed": true 883 | }, 884 | { 885 | "name": "egld_value", 886 | "type": "BigUint", 887 | "indexed": true 888 | }, 889 | { 890 | "name": "gas", 891 | "type": "u64", 892 | "indexed": true 893 | }, 894 | { 895 | "name": "endpoint", 896 | "type": "bytes", 897 | "indexed": true 898 | }, 899 | { 900 | "name": "arguments", 901 | "type": "variadic", 902 | "indexed": true 903 | } 904 | ] 905 | }, 906 | { 907 | "identifier": "performTransferExecuteEgld", 908 | "inputs": [ 909 | { 910 | "name": "action_id", 911 | "type": "u32", 912 | "indexed": true 913 | }, 914 | { 915 | "name": "to", 916 | "type": "Address", 917 | "indexed": true 918 | }, 919 | { 920 | "name": "egld_value", 921 | "type": "BigUint", 922 | "indexed": true 923 | }, 924 | { 925 | "name": "gas", 926 | "type": "u64", 927 | "indexed": true 928 | }, 929 | { 930 | "name": "endpoint", 931 | "type": "bytes", 932 | "indexed": true 933 | }, 934 | { 935 | "name": "arguments", 936 | "type": "variadic", 937 | "indexed": true 938 | } 939 | ] 940 | }, 941 | { 942 | "identifier": "performTransferExecuteEsdt", 943 | "inputs": [ 944 | { 945 | "name": "action_id", 946 | "type": "u32", 947 | "indexed": true 948 | }, 949 | { 950 | "name": "to", 951 | "type": "Address", 952 | "indexed": true 953 | }, 954 | { 955 | "name": "tokens", 956 | "type": "List", 957 | "indexed": true 958 | }, 959 | { 960 | "name": "gas", 961 | "type": "u64", 962 | "indexed": true 963 | }, 964 | { 965 | "name": "endpoint", 966 | "type": "bytes", 967 | "indexed": true 968 | }, 969 | { 970 | "name": "arguments", 971 | "type": "variadic", 972 | "indexed": true 973 | } 974 | ] 975 | }, 976 | { 977 | "identifier": "performDeployFromSource", 978 | "inputs": [ 979 | { 980 | "name": "action_id", 981 | "type": "u32", 982 | "indexed": true 983 | }, 984 | { 985 | "name": "egld_value", 986 | "type": "BigUint", 987 | "indexed": true 988 | }, 989 | { 990 | "name": "source_address", 991 | "type": "Address", 992 | "indexed": true 993 | }, 994 | { 995 | "name": "code_metadata", 996 | "type": "CodeMetadata", 997 | "indexed": true 998 | }, 999 | { 1000 | "name": "gas", 1001 | "type": "u64", 1002 | "indexed": true 1003 | }, 1004 | { 1005 | "name": "arguments", 1006 | "type": "variadic", 1007 | "indexed": true 1008 | } 1009 | ] 1010 | }, 1011 | { 1012 | "identifier": "performUpgradeFromSource", 1013 | "inputs": [ 1014 | { 1015 | "name": "action_id", 1016 | "type": "u32", 1017 | "indexed": true 1018 | }, 1019 | { 1020 | "name": "target_address", 1021 | "type": "Address", 1022 | "indexed": true 1023 | }, 1024 | { 1025 | "name": "egld_value", 1026 | "type": "BigUint", 1027 | "indexed": true 1028 | }, 1029 | { 1030 | "name": "source_address", 1031 | "type": "Address", 1032 | "indexed": true 1033 | }, 1034 | { 1035 | "name": "code_metadata", 1036 | "type": "CodeMetadata", 1037 | "indexed": true 1038 | }, 1039 | { 1040 | "name": "gas", 1041 | "type": "u64", 1042 | "indexed": true 1043 | }, 1044 | { 1045 | "name": "arguments", 1046 | "type": "variadic", 1047 | "indexed": true 1048 | } 1049 | ] 1050 | } 1051 | ], 1052 | "esdtAttributes": [], 1053 | "hasCallback": true, 1054 | "types": { 1055 | "Action": { 1056 | "type": "enum", 1057 | "variants": [ 1058 | { 1059 | "name": "Nothing", 1060 | "discriminant": 0 1061 | }, 1062 | { 1063 | "name": "AddBoardMember", 1064 | "discriminant": 1, 1065 | "fields": [ 1066 | { 1067 | "name": "0", 1068 | "type": "Address" 1069 | } 1070 | ] 1071 | }, 1072 | { 1073 | "name": "AddProposer", 1074 | "discriminant": 2, 1075 | "fields": [ 1076 | { 1077 | "name": "0", 1078 | "type": "Address" 1079 | } 1080 | ] 1081 | }, 1082 | { 1083 | "name": "RemoveUser", 1084 | "discriminant": 3, 1085 | "fields": [ 1086 | { 1087 | "name": "0", 1088 | "type": "Address" 1089 | } 1090 | ] 1091 | }, 1092 | { 1093 | "name": "ChangeQuorum", 1094 | "discriminant": 4, 1095 | "fields": [ 1096 | { 1097 | "name": "0", 1098 | "type": "u32" 1099 | } 1100 | ] 1101 | }, 1102 | { 1103 | "name": "SendTransferExecuteEgld", 1104 | "discriminant": 5, 1105 | "fields": [ 1106 | { 1107 | "name": "0", 1108 | "type": "CallActionData" 1109 | } 1110 | ] 1111 | }, 1112 | { 1113 | "name": "SendTransferExecuteEsdt", 1114 | "discriminant": 6, 1115 | "fields": [ 1116 | { 1117 | "name": "0", 1118 | "type": "EsdtTransferExecuteData" 1119 | } 1120 | ] 1121 | }, 1122 | { 1123 | "name": "SendAsyncCall", 1124 | "discriminant": 7, 1125 | "fields": [ 1126 | { 1127 | "name": "0", 1128 | "type": "CallActionData" 1129 | } 1130 | ] 1131 | }, 1132 | { 1133 | "name": "SCDeployFromSource", 1134 | "discriminant": 8, 1135 | "fields": [ 1136 | { 1137 | "name": "amount", 1138 | "type": "BigUint" 1139 | }, 1140 | { 1141 | "name": "source", 1142 | "type": "Address" 1143 | }, 1144 | { 1145 | "name": "code_metadata", 1146 | "type": "CodeMetadata" 1147 | }, 1148 | { 1149 | "name": "arguments", 1150 | "type": "List" 1151 | } 1152 | ] 1153 | }, 1154 | { 1155 | "name": "SCUpgradeFromSource", 1156 | "discriminant": 9, 1157 | "fields": [ 1158 | { 1159 | "name": "sc_address", 1160 | "type": "Address" 1161 | }, 1162 | { 1163 | "name": "amount", 1164 | "type": "BigUint" 1165 | }, 1166 | { 1167 | "name": "source", 1168 | "type": "Address" 1169 | }, 1170 | { 1171 | "name": "code_metadata", 1172 | "type": "CodeMetadata" 1173 | }, 1174 | { 1175 | "name": "arguments", 1176 | "type": "List" 1177 | } 1178 | ] 1179 | } 1180 | ] 1181 | }, 1182 | "ActionFullInfo": { 1183 | "type": "struct", 1184 | "docs": [ 1185 | "Not used internally, just to retrieve results via endpoint." 1186 | ], 1187 | "fields": [ 1188 | { 1189 | "name": "action_id", 1190 | "type": "u32" 1191 | }, 1192 | { 1193 | "name": "group_id", 1194 | "type": "u32" 1195 | }, 1196 | { 1197 | "name": "action_data", 1198 | "type": "Action" 1199 | }, 1200 | { 1201 | "name": "signers", 1202 | "type": "List
" 1203 | } 1204 | ] 1205 | }, 1206 | "ActionStatus": { 1207 | "type": "enum", 1208 | "variants": [ 1209 | { 1210 | "name": "Available", 1211 | "discriminant": 0 1212 | }, 1213 | { 1214 | "name": "Aborted", 1215 | "discriminant": 1 1216 | } 1217 | ] 1218 | }, 1219 | "CallActionData": { 1220 | "type": "struct", 1221 | "fields": [ 1222 | { 1223 | "name": "to", 1224 | "type": "Address" 1225 | }, 1226 | { 1227 | "name": "egld_amount", 1228 | "type": "BigUint" 1229 | }, 1230 | { 1231 | "name": "opt_gas_limit", 1232 | "type": "Option" 1233 | }, 1234 | { 1235 | "name": "endpoint_name", 1236 | "type": "bytes" 1237 | }, 1238 | { 1239 | "name": "arguments", 1240 | "type": "List" 1241 | } 1242 | ] 1243 | }, 1244 | "EsdtTokenPayment": { 1245 | "type": "struct", 1246 | "fields": [ 1247 | { 1248 | "name": "token_identifier", 1249 | "type": "TokenIdentifier" 1250 | }, 1251 | { 1252 | "name": "token_nonce", 1253 | "type": "u64" 1254 | }, 1255 | { 1256 | "name": "amount", 1257 | "type": "BigUint" 1258 | } 1259 | ] 1260 | }, 1261 | "EsdtTransferExecuteData": { 1262 | "type": "struct", 1263 | "fields": [ 1264 | { 1265 | "name": "to", 1266 | "type": "Address" 1267 | }, 1268 | { 1269 | "name": "tokens", 1270 | "type": "List" 1271 | }, 1272 | { 1273 | "name": "opt_gas_limit", 1274 | "type": "Option" 1275 | }, 1276 | { 1277 | "name": "endpoint_name", 1278 | "type": "bytes" 1279 | }, 1280 | { 1281 | "name": "arguments", 1282 | "type": "List" 1283 | } 1284 | ] 1285 | }, 1286 | "UserRole": { 1287 | "type": "enum", 1288 | "variants": [ 1289 | { 1290 | "name": "None", 1291 | "discriminant": 0 1292 | }, 1293 | { 1294 | "name": "Proposer", 1295 | "discriminant": 1 1296 | }, 1297 | { 1298 | "name": "BoardMember", 1299 | "discriminant": 2 1300 | } 1301 | ] 1302 | } 1303 | } 1304 | } 1305 | --------------------------------------------------------------------------------