├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── _config.yml ├── codec ├── codec.js └── index.html ├── custom.css ├── event-count ├── event-count.js └── index.html ├── index.html ├── indices ├── index.html └── indices.js ├── nominations ├── index.html └── nominations.js ├── utilities.js └── versions ├── index.html ├── networks.js └── versions.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shawn Tabrizi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # substrate-js-utilities 2 | A set of useful JavaScript utilities for Substrate using Polkadot.js API 3 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /codec/codec.js: -------------------------------------------------------------------------------- 1 | const { TypeRegistry, createType } = polkadotTypes; 2 | 3 | let rawBytes = document.getElementById('rawBytes'); 4 | let customTypes = document.getElementById('customTypes'); 5 | let output = document.getElementById('output'); 6 | 7 | customTypes.addEventListener('input', parseCustomType); 8 | rawBytes.addEventListener('input', parseCustomType); 9 | 10 | /* CUSTOM TYPES EDITOR START */ 11 | 12 | // create the editor 13 | const options = { mode: 'code', onChange: parseCustomType }; 14 | const editor = new JSONEditor(customTypes, options); 15 | 16 | // set json 17 | const initialJson = [{ MyVec: 'Vec' }]; 18 | editor.set(initialJson); 19 | 20 | /* CUSTOM TYPES EDITOR END */ 21 | 22 | const registry = new TypeRegistry(); 23 | 24 | function parseCustomType() { 25 | try { 26 | let typesObject = editor.get(); 27 | 28 | let lastTypeKey; 29 | 30 | if (Array.isArray(typesObject)) { 31 | typesObject.map(type => { 32 | registry.register(type); 33 | }); 34 | 35 | let lastTypeObject = typesObject[typesObject.length - 1]; 36 | lastTypeKey = Object.keys(lastTypeObject)[0]; 37 | } else { 38 | registry.register(typesObject); 39 | lastTypeKey = Object.keys(typesObject)[0]; 40 | } 41 | 42 | output.innerText = JSON.stringify( 43 | createType(registry, lastTypeKey, rawBytes.value.trim()) 44 | ); 45 | } catch (e) { 46 | output.innerText = e; 47 | } 48 | } 49 | 50 | parseCustomType(); 51 | -------------------------------------------------------------------------------- /codec/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Substrate JS Utilities: Codec 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 |
26 |

← Substrate Utilities

27 |

Hopefully this page helps you do something.

28 |

Cheat Sheet

29 |
30 |
31 |
Enum
32 | { "MyEnum": { "_enum": ["A", "B"] } } 33 |
34 |
35 |
Tuple
36 | { "MyTuple": "(u64, u64)" } 37 |
38 |
39 |
Vector
40 | { "MyVec": "Vec<u8>" } 41 |
42 |
43 |
44 |
45 |
Advance Example
46 | [ { "MyEnum": { "_enum": ["A", "B", "C"] } }, { "MyPartA": "Vec<(MyEnum, u32)>" }, { "MyType": "(MyPartA, u128)" }, { "MyFinalType": "Vec<MyType>" } ] 47 |
48 |
49 |
50 |
51 |
52 |

SCALE Decoder

53 |
54 | 55 | 57 | 58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /custom.css: -------------------------------------------------------------------------------- 1 | /* Custom page CSS */ 2 | 3 | .container { 4 | width: auto; 5 | padding: 15px; 6 | } 7 | 8 | .footer { 9 | background-color: #f5f5f5; 10 | } 11 | 12 | pre { 13 | white-space: pre-wrap; /* Since CSS 2.1 */ 14 | white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ 15 | white-space: -pre-wrap; /* Opera 4-6 */ 16 | white-space: -o-pre-wrap; /* Opera 7 */ 17 | word-wrap: break-word; /* Internet Explorer 5.5+ */ 18 | } 19 | 20 | h4 { 21 | padding-top: 20px; 22 | } 23 | 24 | .form-control { 25 | overflow-wrap: break-word; 26 | height: auto; 27 | } 28 | -------------------------------------------------------------------------------- /event-count/event-count.js: -------------------------------------------------------------------------------- 1 | const { BN } = polkadotUtil; 2 | const { WsProvider, ApiPromise } = polkadotApi; 3 | 4 | // Global Variables 5 | var global = { 6 | all_events: [], 7 | blockHashes: [], 8 | endpoint: '', 9 | total_events: 0, 10 | }; 11 | 12 | 13 | // Given an address and a range of blocks, query the Substrate blockchain for the events across the range 14 | async function getEventsInRange(startBlock, endBlock) { 15 | // Tell the user the data is loading... 16 | document.getElementById('output').innerHTML = 17 | `Getting events for blocks ${startBlock} to ${endBlock}...`; 18 | 19 | let step = 1; 20 | 21 | try { 22 | var promises = []; 23 | 24 | // Get all block hashes 25 | for (let i = startBlock; i < endBlock; i = i + step) { 26 | if (!global.blockHashes.find(x => x.block == i)) { 27 | let blockHashPromise = substrate.rpc.chain.getBlockHash(i); 28 | promises.push(i, blockHashPromise); 29 | } 30 | } 31 | 32 | var results = await Promise.all(promises); 33 | 34 | for (let i = 0; i < results.length; i = i + 2) { 35 | global.blockHashes.push({ 36 | block: results[i], 37 | hash: results[i + 1] 38 | }); 39 | } 40 | 41 | var promises = []; 42 | 43 | // Loop over the blocks, using the step value 44 | for (let i = startBlock; i < endBlock; i = i + step) { 45 | // If we already have data about that block, skip it 46 | if (!global.all_events.find(x => x.block == i)) { 47 | // Get the block hash 48 | let blockHash = global.blockHashes.find(x => x.block == i).hash; 49 | // Create a promise to query the events for that block 50 | let eventPromise = substrate.query.system.events.at(blockHash); 51 | // Create a promise to get the timestamp for that block 52 | let timePromise = substrate.query.timestamp.now.at(blockHash); 53 | // Push data to a linear array of promises to run in parallel. 54 | promises.push(i, eventPromise, timePromise); 55 | } 56 | } 57 | 58 | // Call all promises in parallel for speed. 59 | var results = await Promise.all(promises); 60 | 61 | // Restructure the data into an array of objects 62 | var all_events = []; 63 | for (let i = 0; i < results.length; i = i + 3) { 64 | let block = results[i]; 65 | let events = results[i + 1]; 66 | 67 | let eventCount = events.length; 68 | 69 | all_events.push({ 70 | block: block, 71 | eventCount: eventCount, 72 | time: new Date(results[i + 2].toNumber()) 73 | }); 74 | } 75 | 76 | //Remove loading message 77 | document.getElementById('output').innerHTML = ''; 78 | 79 | return all_events; 80 | } catch (error) { 81 | document.getElementById('output').innerHTML = error; 82 | } 83 | } 84 | 85 | // Connect to Substrate endpoint 86 | async function connect() { 87 | let endpoint = document.getElementById('endpoint').value; 88 | if (!window.substrate || global.endpoint != endpoint) { 89 | const provider = new WsProvider(endpoint); 90 | document.getElementById('output').innerHTML = 'Connecting to Endpoint...'; 91 | window.substrate = await ApiPromise.create({ provider }); 92 | global.endpoint = endpoint; 93 | global.chainDecimals = substrate.registry.chainDecimals[0]; 94 | global.chainToken = substrate.registry.chainTokens[0]; 95 | document.getElementById('output').innerHTML = 'Connected'; 96 | } 97 | } 98 | 99 | // Create a table with the event information 100 | function createTable() { 101 | document.getElementById('output').innerHTML = "Creating Table..."; 102 | 103 | let keys = ["block", "eventCount", "time"]; 104 | 105 | let table = document.getElementById('events-table'); 106 | 107 | let count_container = document.createElement('p'); 108 | count_container.innerText = `Total Count: ${total_count()}`; 109 | 110 | // Clear table 111 | while (table.firstChild) { 112 | table.removeChild(table.firstChild); 113 | } 114 | 115 | let thead = document.createElement('thead'); 116 | let tbody = document.createElement('tbody'); 117 | 118 | let tr = document.createElement('tr'); 119 | for (key of keys) { 120 | let th = document.createElement('th'); 121 | th.innerText = key; 122 | tr.appendChild(th); 123 | } 124 | 125 | for (index of Object.keys(global.all_events)) { 126 | let tr2 = document.createElement('tr'); 127 | 128 | for (key of keys) { 129 | let td = document.createElement('td'); 130 | td.innerText = global.all_events[index][key]; 131 | tr2.appendChild(td); 132 | } 133 | tbody.appendChild(tr2); 134 | } 135 | 136 | thead.appendChild(tr); 137 | table.appendChild(count_container); 138 | table.appendChild(thead); 139 | table.appendChild(tbody); 140 | 141 | document.getElementById('output').innerHTML = "Done."; 142 | } 143 | 144 | function total_count() { 145 | let total_events = global.all_events.reduce((partialSum, a) => partialSum + a.eventCount, 0); 146 | return total_events 147 | } 148 | 149 | async function countEvents() { 150 | await connect(); 151 | 152 | // Find the intial range, from first block to current block 153 | var startBlock, endBlock; 154 | 155 | if (document.getElementById('endBlock').value) { 156 | endBlock = parseInt(document.getElementById('endBlock').value); 157 | } else { 158 | endBlock = parseInt(await substrate.derive.chain.bestNumber()); 159 | } 160 | 161 | if (document.getElementById('startBlock').value) { 162 | startBlock = parseInt(document.getElementById('startBlock').value); 163 | } else { 164 | // 10 blocks per minute, 1440 min per day, 7 days per week 165 | let HISTORICAL = 10 * 1440 * 7; 166 | startBlock = endBlock < HISTORICAL ? 0 : endBlock - HISTORICAL; 167 | } 168 | 169 | // query chain 100 blocks at a time 170 | let step = 100; 171 | for (let i = startBlock; i < endBlock; i = i + step) { 172 | newEvents = await getEventsInRange(i, i + step); 173 | global.all_events = global.all_events.concat(newEvents); 174 | createTable(); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /event-count/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Substrate Event Count 12 | 13 | 14 | 16 | 17 | 18 | 19 |
20 |
21 |

Substrate Event Count

22 |

Get a count of the historical events on a Substrate chain.

23 |
24 |
25 |
26 |
27 | Endpoint: 28 |
29 | 31 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Start Block: 43 |
44 | 45 |
46 |
47 |
48 |
49 |
50 | End Block: 51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |

Created by Shawn Tabrizi 62 | - Source on GitHub 63 | - Using Polkadot.js

64 |
65 | 66 | 68 | 69 | 72 | 75 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Substrate JS Utilities 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 |
24 |
25 |

Substrate Utilities

26 |

Hopefully this page helps you do something.

27 | 44 |
45 |
46 |
47 |

String to Hex

48 |
49 | 50 |
51 | 52 |
53 | 54 |
55 |

Balance to Hex (Little Endian)

56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 |

AccountId to Hex

64 |
65 | 66 |
67 | 68 |
69 | 70 |
71 |

Blake2 a String

72 |
73 | 74 | 76 |
77 | 78 |
79 |
Hash
80 |
81 |

Blake2 Concat a String

82 |
83 | 84 | 86 |
87 | 88 |
89 |
Hash
90 |
91 |

XXHash a String

92 |
93 | 94 | 96 |
97 | 98 |
99 |
Hash
100 |
101 |

XXHash Concat a String

102 |
103 | 104 | 106 |
107 | 108 |
109 |
Hash
110 |
111 |

Seed to Address

112 |
113 | 114 |
115 | 116 |
117 |
Address 118 |
119 |
120 |

Change Address Prefix 121 | (Prefix 123 | Lookup) 124 |

125 |
126 | 127 | 128 |
129 | 130 |
131 |
Address 132 |
133 |
134 |

"Module ID" to Address

135 |
136 | 137 |
138 | 139 |
140 |
Address 141 |
142 |
143 |

"Para ID" to Address

144 |
145 | 146 | 150 |
151 | 152 |
153 |
Address 154 |
155 |
156 |

Sub-Account Generator

157 |
158 | 159 | 160 |
161 | 162 |
163 |
Sub-Account 164 |
165 |
166 |

Ethereum to Substrate Address

167 |
168 | 169 |
170 | 171 |
172 | 173 |
174 |

u8 Array to Hex

175 |
176 | 178 |
179 | 180 |
181 | 182 |
183 |
184 |
185 |
186 |
187 |
188 | 189 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /indices/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Substrate JS Utilities: Indices 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 |
26 |

← Substrate Utilities

27 |

Hopefully this page helps you do something.

28 |

Query Indices on a Substrate Chain

29 |
30 |
31 | Endpoint: 32 |
33 | 35 | 39 |
40 |
41 |
42 |
43 |
44 | Start Index: 45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 | End Index: 53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 | 66 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /indices/indices.js: -------------------------------------------------------------------------------- 1 | const { WsProvider, ApiPromise } = polkadotApi; 2 | 3 | // Some global variables used by this code. 4 | let global = { 5 | indices: {}, 6 | limit: 1000, 7 | }; 8 | 9 | // Connect to Substrate endpoint 10 | async function connect() { 11 | let endpoint = document.getElementById('endpoint').value; 12 | if (!window.substrate || global.endpoint != endpoint) { 13 | const provider = new WsProvider(endpoint); 14 | document.getElementById('output').innerHTML = 'Connecting to Endpoint...'; 15 | window.substrate = await ApiPromise.create({ provider }); 16 | global.endpoint = endpoint; 17 | document.getElementById('output').innerHTML = 'Connected'; 18 | clearIndices(); 19 | } 20 | } 21 | 22 | // Batch query indices information from the chain 23 | async function findIndices(a, b) { 24 | document.getElementById('output').innerHTML = "Querying..."; 25 | let queries = []; 26 | for (let i = a; i <= b; i++) { 27 | // Don't look up values we already have 28 | if (!(i in global.indices)) { 29 | let query = substrate.query.indices.accounts(i); 30 | queries.push(i, query); 31 | } 32 | } 33 | 34 | let results = await Promise.all(queries); 35 | 36 | for (let i = 0; i < results.length; i += 2) { 37 | let index = results[i]; 38 | let account = results[i + 1]; 39 | let info = []; 40 | info.push(index); 41 | let ss58 = substrate.createType('AccountIndex', index).toString(); 42 | info.push(ss58); 43 | let indexInfo = account.isEmpty ? ["unclaimed", "", ""] : account.value; 44 | global.indices[index] = info.concat(indexInfo); 45 | } 46 | } 47 | 48 | // Create a table with the index information 49 | function createTable() { 50 | document.getElementById('output').innerHTML = "Creating Table..."; 51 | 52 | let keys = ["Index", "SS58", "Owner", "Deposit", "Permanent?"]; 53 | 54 | let table = document.getElementById('indices-table'); 55 | 56 | // Clear table 57 | while (table.firstChild) { 58 | table.removeChild(table.firstChild); 59 | } 60 | 61 | let thead = document.createElement('thead'); 62 | let tbody = document.createElement('tbody'); 63 | 64 | let tr = document.createElement('tr'); 65 | for (key of keys) { 66 | let th = document.createElement('th'); 67 | th.innerText = key; 68 | tr.appendChild(th); 69 | } 70 | 71 | for (index of Object.keys(global.indices)) { 72 | let tr2 = document.createElement('tr'); 73 | 74 | for (key in keys) { 75 | let td = document.createElement('td'); 76 | td.innerText = global.indices[index][key]; 77 | tr2.appendChild(td); 78 | } 79 | tbody.appendChild(tr2); 80 | } 81 | 82 | thead.appendChild(tr); 83 | table.appendChild(thead); 84 | table.appendChild(tbody); 85 | 86 | document.getElementById('output').innerHTML = "Done."; 87 | } 88 | 89 | // Clear the table and all stored indices 90 | function clearIndices() { 91 | global.indices = {}; 92 | let table = document.getElementById('indices-table'); 93 | // Clear table 94 | while (table.firstChild) { 95 | table.removeChild(table.firstChild); 96 | } 97 | } 98 | 99 | // Main function 100 | async function queryIndices() { 101 | try { 102 | await connect(); 103 | 104 | let start = parseInt(document.getElementById("start").value); 105 | let end = parseInt(document.getElementById("end").value); 106 | 107 | if (end - start > global.limit) { 108 | throw `Range is too large! Max range is ${global.limit} in one query.`; 109 | } 110 | 111 | await findIndices(start, end); 112 | createTable(); 113 | 114 | } catch (error) { 115 | document.getElementById('output').innerHTML = error; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /nominations/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Substrate JS Utilities: Nominations 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 |
26 |

← Substrate Utilities

27 |

Hopefully this page helps you do something.

28 |

Query Nominations on a Substrate Chain

29 |
30 |
31 | Address: 32 |
33 | 35 | 39 |
40 |
41 |
42 | Endpoint: 43 |
44 | 46 | 47 |
48 | Inflation (%): 49 |
50 | 51 |
52 |
53 |
54 | 55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 | 63 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /nominations/nominations.js: -------------------------------------------------------------------------------- 1 | const { WsProvider, ApiPromise } = polkadotApi; 2 | const { BN, BN_HUNDRED } = polkadotUtil; 3 | 4 | // Some global variables used by this code. 5 | let global = { 6 | endpoint: "", 7 | account: {}, 8 | validators: [], 9 | }; 10 | 11 | function output(text) { 12 | document.getElementById('output').innerHTML = text; 13 | } 14 | 15 | // Connect to Substrate endpoint 16 | async function connect() { 17 | let endpoint = document.getElementById('endpoint').value; 18 | if (!window.substrate || global.endpoint != endpoint) { 19 | const provider = new WsProvider(endpoint); 20 | output('Connecting to Endpoint...'); 21 | window.substrate = await ApiPromise.create({ provider }); 22 | global.endpoint = endpoint; 23 | output('Connected'); 24 | } 25 | } 26 | 27 | async function getNominations(address) { 28 | output("Querying..."); 29 | if (!substrate.derive.staking) { 30 | output("Could not find derive.staking API."); 31 | return; 32 | } 33 | 34 | global.account = await substrate.derive.staking.account(address); 35 | if (global.account.nominators.length == 0) { 36 | output(`No nominations found for ${address}.`); 37 | return; 38 | } 39 | global.validators = await substrate.derive.staking.accounts(global.account.nominators); 40 | // pull out commission and get identity 41 | for ([index, validator] of global.validators.entries()) { 42 | validator["identity"] = await substrate.derive.accounts.identity(validator.accountId); 43 | validator["commission"] = validator.validatorPrefs.commission.toHuman(); 44 | validator["display"] = validator.identity.displayParent ? validator.identity.displayParent + "/" : ""; 45 | validator["display"] += validator.identity.display; 46 | validator["#"] = index; 47 | validator["lastClaimed"] = validator.stakingLedger.claimedRewards.slice(-1)[0]; 48 | validator["selfStake"] = substrate.createType("Balance", validator.stakingLedger.active).toHuman(); 49 | } 50 | return global.validators; 51 | } 52 | 53 | // Create a table with the information 54 | function createTable(validators) { 55 | if (!validators || validator.length == 0) { 56 | output("No nominations found."); 57 | return; 58 | } 59 | output("Creating Table..."); 60 | 61 | let keys = ["#", "display", "accountId", "commission", "lastClaimed", "selfStake"]; 62 | let table = document.getElementById('validators-table'); 63 | 64 | // Clear table 65 | while (table.firstChild) { 66 | table.removeChild(table.firstChild); 67 | } 68 | 69 | let thead = document.createElement('thead'); 70 | let tbody = document.createElement('tbody'); 71 | 72 | let tr = document.createElement('tr'); 73 | for (key of keys) { 74 | let th = document.createElement('th'); 75 | th.innerText = key; 76 | tr.appendChild(th); 77 | } 78 | 79 | for (account of validators) { 80 | let tr2 = document.createElement('tr'); 81 | 82 | for (key of keys) { 83 | let td = document.createElement('td'); 84 | td.innerText = account[key]; 85 | tr2.appendChild(td); 86 | } 87 | tbody.appendChild(tr2); 88 | } 89 | 90 | thead.appendChild(tr); 91 | table.appendChild(thead); 92 | table.appendChild(tbody); 93 | 94 | output("Done."); 95 | } 96 | 97 | function estimateReward() { 98 | if (global.account && global.account.stakingLedger) { 99 | let active = global.account.stakingLedger.active.toBn(); 100 | let inflation = document.getElementById('inflation').value; 101 | let yearlyInflation = new BN(inflation, 10); 102 | let yearlyReward = active.mul(yearlyInflation).div(BN_HUNDRED); 103 | let daysInYear = new BN("365", 10); 104 | 105 | let dailyReward = yearlyReward.div(daysInYear); 106 | global.account["selfStake"] = substrate.createType("Balance", dailyReward).toHuman(); 107 | 108 | document.getElementById('estimate').innerText = `Active Stake: ${substrate.createType("Balance", active).toHuman()} 109 | Assuming Yearly Inflation: ${yearlyInflation}% 110 | Estimated Yearly Rewards: ${substrate.createType("Balance", yearlyReward).toHuman()} 111 | Estimated Daily Rewards: ${substrate.createType("Balance", dailyReward).toHuman()} 112 | `; 113 | 114 | document.getElementById('estimate').style.display = "block"; 115 | } 116 | } 117 | 118 | // Main function 119 | async function queryNominations() { 120 | try { 121 | await connect(); 122 | 123 | let address = document.getElementById("address").value; 124 | 125 | let validators = await getNominations(address); 126 | estimateReward(); 127 | createTable(validators); 128 | 129 | } catch (error) { 130 | console.error(error); 131 | output(error); 132 | } 133 | } 134 | 135 | document.getElementById('inflation').addEventListener("change", estimateReward); 136 | 137 | connect(); 138 | -------------------------------------------------------------------------------- /utilities.js: -------------------------------------------------------------------------------- 1 | const { stringToHex, isHex, bnToHex, hexToBn, hexToU8a, hexToString, u8aToHex, stringToU8a, bnToU8a } = polkadotUtil; 2 | const { blake2AsHex, keccak256AsU8a, xxhashAsHex, encodeAddress, decodeAddress, blake2AsU8a } = polkadotUtilCrypto; 3 | const { Keyring } = polkadotKeyring; 4 | /* String to Hex */ 5 | let s2h = { 6 | "string": document.getElementById("string-s2h"), 7 | "hex": document.getElementById("hex-s2h") 8 | }; 9 | 10 | s2h.string.addEventListener("input", string2hex); 11 | s2h.hex.addEventListener("input", hex2string); 12 | 13 | function string2hex() { 14 | try { 15 | s2h.hex.value = stringToHex(s2h.string.value); 16 | } catch (e) { 17 | s2h.hex.value = "Error"; 18 | console.error(e); 19 | } 20 | } 21 | 22 | function hex2string() { 23 | try { 24 | s2h.string.value = hexToString(s2h.hex.value); 25 | } catch (e) { 26 | s2h.string.value = "Error"; 27 | console.error(e); 28 | } 29 | } 30 | 31 | /* Balance to Hex (Little Endian) */ 32 | let b2h = { 33 | "balance": document.getElementById("balance-b2h"), 34 | "hex": document.getElementById("hex-b2h") 35 | }; 36 | 37 | b2h.balance.addEventListener("input", bn2hex); 38 | b2h.hex.addEventListener("input", hex2bn); 39 | 40 | function bn2hex() { 41 | try { 42 | b2h.hex.value = bnToHex(b2h.balance.value, { isLe: true }); 43 | } catch (e) { 44 | b2h.hex.value = "Error"; 45 | console.error(e); 46 | } 47 | } 48 | 49 | function hex2bn() { 50 | try { 51 | b2h.balance.value = hexToBn(b2h.hex.value, { isLe: true }); 52 | } catch (e) { 53 | b2h.balance.value = "Error"; 54 | console.error(e); 55 | } 56 | } 57 | 58 | /* AccountId to Hex */ 59 | let a2h = { 60 | "account": document.getElementById("account-a2h"), 61 | "hex": document.getElementById("hex-a2h") 62 | }; 63 | 64 | a2h.account.addEventListener("input", account2hex); 65 | a2h.hex.addEventListener("input", hex2account); 66 | 67 | function account2hex() { 68 | try { 69 | a2h.hex.value = u8aToHex(decodeAddress(a2h.account.value)); 70 | } catch (e) { 71 | a2h.hex.value = "Error"; 72 | console.error(e); 73 | } 74 | } 75 | 76 | function hex2account() { 77 | try { 78 | a2h.account.value = encodeAddress(a2h.hex.value); 79 | } catch (e) { 80 | a2h.account.value = "Error"; 81 | console.error(e); 82 | } 83 | } 84 | 85 | /* Blake-256 a String */ 86 | let blake2 = { 87 | "input": document.getElementById("input-blake2"), 88 | "bits": document.getElementById("bits-blake2"), 89 | "hash": document.getElementById("hash-blake2") 90 | }; 91 | 92 | blake2.input.addEventListener("input", blake2string); 93 | blake2.bits.addEventListener("input", blake2string); 94 | 95 | function blake2string() { 96 | try { 97 | blake2.hash.innerText = blake2AsHex(blake2.input.value, blake2.bits.value); 98 | } catch (e) { 99 | blake2.hash.innerText = "Error"; 100 | console.error(e); 101 | } 102 | } 103 | 104 | /* Blake-256 Concat a String */ 105 | let blake2concat = { 106 | "input": document.getElementById("input-blake2-concat"), 107 | "bits": document.getElementById("bits-blake2-concat"), 108 | "hash": document.getElementById("hash-blake2-concat") 109 | }; 110 | 111 | blake2concat.input.addEventListener("input", blake2ConcatString); 112 | blake2concat.bits.addEventListener("input", blake2ConcatString); 113 | 114 | function blake2ConcatString() { 115 | try { 116 | let hash = blake2AsHex(blake2concat.input.value, blake2concat.bits.value); 117 | if (isHex(blake2concat.input.value)) { 118 | hash += blake2concat.input.value.substr(2); 119 | } else { 120 | hash += stringToHex(blake2concat.input.value).substr(2); 121 | } 122 | blake2concat.hash.innerText = hash; 123 | } catch (e) { 124 | blake2concat.hash.innerText = "Error"; 125 | console.error(e); 126 | } 127 | } 128 | 129 | /* XXHash a String */ 130 | let xxhash = { 131 | "input": document.getElementById("input-xxhash"), 132 | "bits": document.getElementById("bits-xxhash"), 133 | "hash": document.getElementById("hash-xxhash") 134 | }; 135 | 136 | xxhash.input.addEventListener("input", xxhashString); 137 | xxhash.bits.addEventListener("input", xxhashString); 138 | 139 | function xxhashString() { 140 | try { 141 | xxhash.hash.innerText = xxhashAsHex(xxhash.input.value, xxhash.bits.value); 142 | } catch (e) { 143 | xxhash.hash.innerText = "Error"; 144 | console.error(e); 145 | } 146 | } 147 | 148 | /* XXHash Concat a String */ 149 | let xxhashconcat = { 150 | "input": document.getElementById("input-xxhash-concat"), 151 | "bits": document.getElementById("bits-xxhash-concat"), 152 | "hash": document.getElementById("hash-xxhash-concat") 153 | }; 154 | 155 | xxhashconcat.input.addEventListener("input", xxhashConcatString); 156 | xxhashconcat.bits.addEventListener("input", xxhashConcatString); 157 | 158 | function xxhashConcatString() { 159 | try { 160 | let hash = xxhashAsHex(xxhashconcat.input.value, xxhashconcat.bits.value); 161 | if (isHex(xxhashconcat.input.value)) { 162 | hash += xxhashconcat.input.value.substr(2); 163 | } else { 164 | hash += stringToHex(xxhashconcat.input.value).substr(2); 165 | } 166 | xxhashconcat.hash.innerText = hash; 167 | } catch (e) { 168 | xxhashconcat.hash.innerText = "Error"; 169 | console.error(e); 170 | } 171 | } 172 | 173 | /* Seed to Address */ 174 | let s2a = { 175 | "address": document.getElementById("address-s2a"), 176 | "seed": document.getElementById("seed-s2a") 177 | }; 178 | 179 | s2a.seed.addEventListener("input", seed2address); 180 | 181 | function seed2address() { 182 | try { 183 | let k = new Keyring({ type: "sr25519" }); 184 | let user = k.addFromUri(s2a.seed.value); 185 | s2a.address.innerText = user.address; 186 | } catch (e) { 187 | s2a.address.innerText = "Error"; 188 | console.error(e); 189 | } 190 | } 191 | 192 | /* Change Address Prefix */ 193 | let cap = { 194 | "address": document.getElementById("address-cap"), 195 | "prefix": document.getElementById("prefix-cap"), 196 | "result": document.getElementById("result-cap") 197 | }; 198 | 199 | cap.prefix.addEventListener("input", changeAddressPrefix); 200 | cap.address.addEventListener("input", changeAddressPrefix); 201 | 202 | function changeAddressPrefix() { 203 | try { 204 | let address = cap.address.value; 205 | let decoded = decodeAddress(address); 206 | let prefix = cap.prefix.value; 207 | if (prefix) { 208 | cap.result.innerText = encodeAddress(decoded, prefix); 209 | } else { 210 | cap.result.innerText = encodeAddress(decoded); 211 | } 212 | } catch (e) { 213 | cap.result.innerText = "Error"; 214 | console.error(e); 215 | } 216 | } 217 | 218 | /* Module ID to Address */ 219 | let modid = { 220 | "moduleId": document.getElementById("moduleId-modid"), 221 | "address": document.getElementById("address-modid") 222 | }; 223 | 224 | modid.moduleId.addEventListener("input", moduleId2Address); 225 | modid.address.addEventListener("input", moduleId2Address); 226 | 227 | function moduleId2Address() { 228 | try { 229 | let moduleId = modid.moduleId.value; 230 | if (moduleId.length != 8) { 231 | modid.address.innerText = "Module Id must be 8 characters (i.e. `py/trsry`)"; 232 | return 233 | } 234 | let address = stringToU8a(("modl" + moduleId).padEnd(32, '\0')); 235 | modid.address.innerText = encodeAddress(address); 236 | } catch (e) { 237 | modid.address.innerText = "Error"; 238 | console.error(e); 239 | } 240 | } 241 | 242 | /* Para ID to Address */ 243 | let paraid = { 244 | "paraId": document.getElementById("paraId-paraid"), 245 | "address": document.getElementById("address-paraid") 246 | }; 247 | let paraType = document.getElementById("type-paraid"); 248 | 249 | paraid.paraId.addEventListener("input", paraId2Address); 250 | paraid.address.addEventListener("input", paraId2Address); 251 | paraType.addEventListener("input", paraId2Address); 252 | 253 | function paraId2Address() { 254 | try { 255 | let paraId = paraid.paraId.value; 256 | if (!parseInt(paraId)) { 257 | paraid.address.innerText = "Para Id should be a number"; 258 | return 259 | } 260 | let type = paraType.value; 261 | let typeEncoded = stringToU8a(type); 262 | let paraIdEncoded = bnToU8a(parseInt(paraId), 16); 263 | let zeroPadding = new Uint8Array(32 - typeEncoded.length - paraIdEncoded.length).fill(0); 264 | let address = new Uint8Array([...typeEncoded, ...paraIdEncoded, ...zeroPadding]); 265 | paraid.address.innerText = encodeAddress(address); 266 | } catch (e) { 267 | paraid.address.innerText = "Error"; 268 | console.error(e); 269 | } 270 | } 271 | 272 | /* Sub Account Generator */ 273 | let subid = { 274 | "address": document.getElementById("address-subid"), 275 | "index": document.getElementById("index-subid"), 276 | "subid": document.getElementById("subid-subid") 277 | }; 278 | 279 | subid.address.addEventListener("input", subAccountId); 280 | subid.index.addEventListener("input", subAccountId); 281 | 282 | function subAccountId() { 283 | try { 284 | let address = subid.address.value; 285 | let index = subid.index.value; 286 | 287 | let seedBytes = stringToU8a("modlpy/utilisuba"); 288 | let whoBytes = decodeAddress(address); 289 | if (isNaN(parseInt(index))) { 290 | subid.subid.innerText = "Bad Index"; 291 | return; 292 | } 293 | let indexBytes = bnToU8a(parseInt(index), { bitLength: 16 }); 294 | let combinedBytes = new Uint8Array(seedBytes.length + whoBytes.length + indexBytes.length); 295 | combinedBytes.set(seedBytes); 296 | combinedBytes.set(whoBytes, seedBytes.length); 297 | combinedBytes.set(indexBytes, seedBytes.length + whoBytes.length); 298 | 299 | let entropy = blake2AsU8a(combinedBytes, 256); 300 | subid.subid.innerText = encodeAddress(entropy); 301 | } catch (e) { 302 | subid.subid.innerText = "Error"; 303 | console.error(e); 304 | } 305 | } 306 | 307 | /* Ethereum to Substrate Address */ 308 | let e2s = { 309 | "eth": document.getElementById("eth-e2s"), 310 | "sub": document.getElementById("sub-e2s"), 311 | }; 312 | 313 | e2s.eth.addEventListener("input", eth2Sub); 314 | e2s.sub.addEventListener("input", sub2Eth); 315 | 316 | function eth2Sub() { 317 | try { 318 | let ethAddress = e2s.eth.value; 319 | 320 | // Ensure the address is a valid Ethereum address (20 bytes) 321 | if (!ethAddress.startsWith('0x') || ethAddress.length !== 42) { 322 | e2s.sub.value = "Invalid Ethereum address"; 323 | return; 324 | } 325 | 326 | // Convert Ethereum address to bytes and append the `e` 327 | const ethBytes = hexToU8a(ethAddress); 328 | // Create Substrate address with all `0xee`. 329 | const substrateBytes = new Uint8Array(32).fill(0xee); 330 | // Copy the Ethereum bytes into the first 20 bytes 331 | substrateBytes.set(ethBytes, 0); 332 | 333 | // Convert to a Substrate address. 334 | const ss58Address = encodeAddress(substrateBytes, 42); 335 | 336 | e2s.sub.value = ss58Address; 337 | } catch (e) { 338 | e2s.sub.value = "Error"; 339 | console.error(e); 340 | } 341 | } 342 | 343 | // See https://github.com/paritytech/polkadot-sdk/blob/c4b8ec123afcef596fbc4ea3239ff9e392bcaf36/substrate/frame/revive/src/address.rs?plain=1#L101-L113 344 | function sub2Eth() { 345 | try { 346 | let substrateAddress = e2s.sub.value; 347 | 348 | // Decode the Substrate address into raw bytes. 349 | const substrateBytes = decodeAddress(substrateAddress); 350 | 351 | // if last 12 bytes are all `0xEE`, 352 | // we just strip the 0xEE suffix to get the original address 353 | if (substrateBytes.slice(20).every(b => b === 0xEE)) { 354 | e2s.eth.value = u8aToHex(substrateBytes.slice(0, 20)); 355 | return; 356 | } 357 | 358 | // this is an (ed|sr)25510 derived address 359 | // We Hash it with keccak_256 and take the last 20 bytes 360 | const ethBytes = keccak256AsU8a(substrateBytes).slice(-20); 361 | 362 | // Convert to Ethereum address. 363 | const ethAddress = u8aToHex(ethBytes); 364 | 365 | e2s.eth.value = ethAddress; 366 | } catch (e) { 367 | e2s.eth.value = "Error"; 368 | console.error(e); 369 | } 370 | } 371 | 372 | /* u8a to Hex */ 373 | let u82h = { 374 | "u8a": document.getElementById("u8a-u82h"), 375 | "hex": document.getElementById("hex-u82h") 376 | }; 377 | 378 | u82h.u8a.addEventListener("input", u8a2hex); 379 | u82h.hex.addEventListener("input", hex2u8a); 380 | 381 | function u8a2hex() { 382 | try { 383 | let array = u82h.u8a.value.replace(/ /g, "").split(",").filter(e => e); 384 | let u8array = new Uint8Array(array); 385 | u82h.hex.value = u8aToHex(u8array); 386 | } catch (e) { 387 | u82h.hex.value = "Error"; 388 | console.error(e); 389 | } 390 | } 391 | 392 | function hex2u8a() { 393 | try { 394 | let array = u82h.hex.value.match(/.{1,2}/g).map(byte => parseInt(byte, 16)); 395 | let u8a = new Uint8Array(array); 396 | u82h.u8a.value = u8a; 397 | } catch (e) { 398 | u82h.hex.value = "Error"; 399 | console.error(e); 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /versions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Substrate JS Utilities: Network Versions 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 |
26 |

← Substrate Utilities

27 |

Hopefully this page helps you do something.

28 |

Get Versions of Substrate Chains

29 | Start 30 | » 31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 | 39 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /versions/networks.js: -------------------------------------------------------------------------------- 1 | const networks = [ 2 | "wss://kusama-rpc.polkadot.io", 3 | "wss://rpc.polkadot.io", 4 | "wss://statemine-rpc.polkadot.io", 5 | "wss://statemint-rpc.polkadot.io", 6 | "wss://rococo-rpc.polkadot.io", 7 | "wss://westend-rpc.polkadot.io" 8 | ] 9 | -------------------------------------------------------------------------------- /versions/versions.js: -------------------------------------------------------------------------------- 1 | const { ApiPromise, WsProvider } = polkadotApi; 2 | 3 | // Connect to Substrate endpoint 4 | async function connect(endpoint) { 5 | const provider = new WsProvider(endpoint); 6 | console.log(`Connecting to ${endpoint}`); 7 | window.substrate = await ApiPromise.create({ provider }); 8 | console.log(`Connected to ${endpoint}`); 9 | } 10 | 11 | let keys = ["specName", "implName", "authoringVersion", "specVersion", "implVersion"]; 12 | 13 | let tbody = document.createElement('tbody'); 14 | 15 | // Create a table with the index information 16 | function createTable() { 17 | document.getElementById('output').innerHTML = "Creating Table..."; 18 | let table = document.getElementById('versions-table'); 19 | 20 | // Clear table 21 | while (table.firstChild) { 22 | table.removeChild(table.firstChild); 23 | } 24 | 25 | let thead = document.createElement('thead'); 26 | let tr = document.createElement('tr'); 27 | 28 | let th = document.createElement('th'); 29 | th.innerText = "network"; 30 | tr.appendChild(th); 31 | 32 | for (key of keys) { 33 | let th = document.createElement('th'); 34 | th.innerText = key; 35 | tr.appendChild(th); 36 | } 37 | 38 | thead.appendChild(tr); 39 | table.appendChild(thead); 40 | table.appendChild(tbody); 41 | } 42 | 43 | function addRow(network, version) { 44 | let tr2 = document.createElement('tr'); 45 | 46 | let td = document.createElement('td'); 47 | td.innerText = network; 48 | tr2.appendChild(td); 49 | 50 | for (key of keys) { 51 | let td = document.createElement('td'); 52 | td.innerText = version[key]; 53 | tr2.appendChild(td); 54 | } 55 | tbody.appendChild(tr2); 56 | } 57 | 58 | // Main function 59 | async function queryVersions() { 60 | createTable(); 61 | 62 | for (network of networks) { 63 | try { 64 | await connect(network); 65 | let version = await substrate.runtimeVersion.toJSON(); 66 | addRow(network, version); 67 | } catch (error) { 68 | console.error(error); 69 | } 70 | } 71 | } 72 | --------------------------------------------------------------------------------