@ledgerhq/coin-polkadot 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +57 -0
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +12 -0
- package/jest.config.js +10 -0
- package/lib/account.d.ts +15 -0
- package/lib/account.d.ts.map +1 -0
- package/lib/account.js +133 -0
- package/lib/account.js.map +1 -0
- package/lib/address.d.ts +7 -0
- package/lib/address.d.ts.map +1 -0
- package/lib/address.js +28 -0
- package/lib/address.js.map +1 -0
- package/lib/api/bisontrails.d.ts +11 -0
- package/lib/api/bisontrails.d.ts.map +1 -0
- package/lib/api/bisontrails.js +351 -0
- package/lib/api/bisontrails.js.map +1 -0
- package/lib/api/common.d.ts +12 -0
- package/lib/api/common.d.ts.map +1 -0
- package/lib/api/common.js +41 -0
- package/lib/api/common.js.map +1 -0
- package/lib/api/index.d.ts +3 -0
- package/lib/api/index.d.ts.map +1 -0
- package/lib/api/index.js +53 -0
- package/lib/api/index.js.map +1 -0
- package/lib/api/sidecar.d.ts +193 -0
- package/lib/api/sidecar.d.ts.map +1 -0
- package/lib/api/sidecar.js +844 -0
- package/lib/api/sidecar.js.map +1 -0
- package/lib/api/sidecar.types.d.ts +163 -0
- package/lib/api/sidecar.types.d.ts.map +1 -0
- package/lib/api/sidecar.types.js +3 -0
- package/lib/api/sidecar.types.js.map +1 -0
- package/lib/api/subscan.d.ts +6 -0
- package/lib/api/subscan.d.ts.map +1 -0
- package/lib/api/subscan.js +375 -0
- package/lib/api/subscan.js.map +1 -0
- package/lib/api/websocket.d.ts +92 -0
- package/lib/api/websocket.d.ts.map +1 -0
- package/lib/api/websocket.js +759 -0
- package/lib/api/websocket.js.map +1 -0
- package/lib/bridge/js.d.ts +6 -0
- package/lib/bridge/js.d.ts.map +1 -0
- package/lib/bridge/js.js +120 -0
- package/lib/bridge/js.js.map +1 -0
- package/lib/bridge.integration.test.d.ts +4 -0
- package/lib/bridge.integration.test.d.ts.map +1 -0
- package/lib/bridge.integration.test.js +795 -0
- package/lib/bridge.integration.test.js.map +1 -0
- package/lib/cache.d.ts +65 -0
- package/lib/cache.d.ts.map +1 -0
- package/lib/cache.js +182 -0
- package/lib/cache.js.map +1 -0
- package/lib/cli-transaction.d.ts +44 -0
- package/lib/cli-transaction.d.ts.map +1 -0
- package/lib/cli-transaction.js +148 -0
- package/lib/cli-transaction.js.map +1 -0
- package/lib/deviceTransactionConfig.d.ts +16 -0
- package/lib/deviceTransactionConfig.d.ts.map +1 -0
- package/lib/deviceTransactionConfig.js +171 -0
- package/lib/deviceTransactionConfig.js.map +1 -0
- package/lib/errors.d.ts +37 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +17 -0
- package/lib/errors.js.map +1 -0
- package/lib/hw-getAddress.d.ts +4 -0
- package/lib/hw-getAddress.d.ts.map +1 -0
- package/lib/hw-getAddress.js +64 -0
- package/lib/hw-getAddress.js.map +1 -0
- package/lib/js-buildTransaction.d.ts +25 -0
- package/lib/js-buildTransaction.d.ts.map +1 -0
- package/lib/js-buildTransaction.js +265 -0
- package/lib/js-buildTransaction.js.map +1 -0
- package/lib/js-createTransaction.d.ts +9 -0
- package/lib/js-createTransaction.d.ts.map +1 -0
- package/lib/js-createTransaction.js +22 -0
- package/lib/js-createTransaction.js.map +1 -0
- package/lib/js-estimateMaxSpendable.d.ts +15 -0
- package/lib/js-estimateMaxSpendable.d.ts.map +1 -0
- package/lib/js-estimateMaxSpendable.js +89 -0
- package/lib/js-estimateMaxSpendable.js.map +1 -0
- package/lib/js-getFeesForTransaction.d.ts +14 -0
- package/lib/js-getFeesForTransaction.d.ts.map +1 -0
- package/lib/js-getFeesForTransaction.js +94 -0
- package/lib/js-getFeesForTransaction.js.map +1 -0
- package/lib/js-getTransactionStatus.d.ts +18 -0
- package/lib/js-getTransactionStatus.d.ts.map +1 -0
- package/lib/js-getTransactionStatus.js +316 -0
- package/lib/js-getTransactionStatus.js.map +1 -0
- package/lib/js-prepareTransaction.d.ts +9 -0
- package/lib/js-prepareTransaction.d.ts.map +1 -0
- package/lib/js-prepareTransaction.js +83 -0
- package/lib/js-prepareTransaction.js.map +1 -0
- package/lib/js-signOperation.d.ts +27 -0
- package/lib/js-signOperation.d.ts.map +1 -0
- package/lib/js-signOperation.js +235 -0
- package/lib/js-signOperation.js.map +1 -0
- package/lib/js-synchronisation.d.ts +3 -0
- package/lib/js-synchronisation.d.ts.map +1 -0
- package/lib/js-synchronisation.js +104 -0
- package/lib/js-synchronisation.js.map +1 -0
- package/lib/logic.d.ts +107 -0
- package/lib/logic.d.ts.map +1 -0
- package/lib/logic.js +242 -0
- package/lib/logic.js.map +1 -0
- package/lib/platformAdapter.d.ts +11 -0
- package/lib/platformAdapter.d.ts.map +1 -0
- package/lib/platformAdapter.js +25 -0
- package/lib/platformAdapter.js.map +1 -0
- package/lib/platformAdapter.test.d.ts +2 -0
- package/lib/platformAdapter.test.d.ts.map +1 -0
- package/lib/platformAdapter.test.js +51 -0
- package/lib/platformAdapter.test.js.map +1 -0
- package/lib/polkadot-crypto.d.ts +2 -0
- package/lib/polkadot-crypto.d.ts.map +1 -0
- package/lib/polkadot-crypto.js +14 -0
- package/lib/polkadot-crypto.js.map +1 -0
- package/lib/preload.d.ts +14 -0
- package/lib/preload.d.ts.map +1 -0
- package/lib/preload.js +173 -0
- package/lib/preload.js.map +1 -0
- package/lib/serialization.d.ts +4 -0
- package/lib/serialization.d.ts.map +1 -0
- package/lib/serialization.js +51 -0
- package/lib/serialization.js.map +1 -0
- package/lib/specs.d.ts +7 -0
- package/lib/specs.d.ts.map +1 -0
- package/lib/specs.js +274 -0
- package/lib/specs.js.map +1 -0
- package/lib/speculos-deviceActions.d.ts +4 -0
- package/lib/speculos-deviceActions.d.ts.map +1 -0
- package/lib/speculos-deviceActions.js +85 -0
- package/lib/speculos-deviceActions.js.map +1 -0
- package/lib/transaction.d.ts +15 -0
- package/lib/transaction.d.ts.map +1 -0
- package/lib/transaction.js +51 -0
- package/lib/transaction.js.map +1 -0
- package/lib/types.d.ts +96 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +3 -0
- package/lib/types.js.map +1 -0
- package/lib/validators.d.ts +3 -0
- package/lib/validators.d.ts.map +1 -0
- package/lib/validators.js +7 -0
- package/lib/validators.js.map +1 -0
- package/package.json +95 -0
- package/src/account.ts +170 -0
- package/src/address.ts +28 -0
- package/src/api/bisontrails.ts +343 -0
- package/src/api/common.ts +52 -0
- package/src/api/index.ts +38 -0
- package/src/api/sidecar.ts +735 -0
- package/src/api/sidecar.types.ts +162 -0
- package/src/api/subscan.ts +362 -0
- package/src/api/websocket.ts +535 -0
- package/src/bridge/js.ts +81 -0
- package/src/bridge.integration.test.ts +764 -0
- package/src/cache.ts +202 -0
- package/src/cli-transaction.ts +184 -0
- package/src/deviceTransactionConfig.ts +209 -0
- package/src/errors.ts +39 -0
- package/src/hw-getAddress.ts +14 -0
- package/src/js-buildTransaction.ts +206 -0
- package/src/js-createTransaction.ts +22 -0
- package/src/js-estimateMaxSpendable.ts +37 -0
- package/src/js-getFeesForTransaction.ts +41 -0
- package/src/js-getTransactionStatus.ts +350 -0
- package/src/js-prepareTransaction.ts +27 -0
- package/src/js-signOperation.ts +232 -0
- package/src/js-synchronisation.ts +57 -0
- package/src/logic.ts +266 -0
- package/src/platformAdapter.test.ts +61 -0
- package/src/platformAdapter.ts +29 -0
- package/src/polkadot-crypto.ts +10 -0
- package/src/preload.ts +148 -0
- package/src/serialization.ts +48 -0
- package/src/specs.ts +305 -0
- package/src/speculos-deviceActions.ts +84 -0
- package/src/transaction.ts +64 -0
- package/src/types.ts +118 -0
- package/src/validators.ts +2 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
/*
|
|
2
|
+
THIS FILE IS UNUSED AND PROVIDED AS EXAMPLE FOR USING POLKADOT'S WEBSOCKET API DIRECTLY
|
|
3
|
+
|
|
4
|
+
POLKADOT.JS API VERSION 2.5.1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
|
|
9
|
+
/* istanbul ignore file */
|
|
10
|
+
import uniq from "lodash/uniq";
|
|
11
|
+
import compact from "lodash/compact";
|
|
12
|
+
import { BigNumber } from "bignumber.js";
|
|
13
|
+
import { getEnv } from "@ledgerhq/coin-framework/env";
|
|
14
|
+
//@ts-expect-error
|
|
15
|
+
import { WsProvider, ApiPromise } from "@polkadot/api";
|
|
16
|
+
import { u8aToString } from "@polkadot/util";
|
|
17
|
+
import { AccountId, Registration } from "@polkadot/types/interfaces";
|
|
18
|
+
import { Data, Option } from "@polkadot/types";
|
|
19
|
+
import type { ITuple } from "@polkadot/types/types";
|
|
20
|
+
import type { PolkadotValidator, PolkadotStakingProgress } from "../types";
|
|
21
|
+
type AsyncApiFunction = (api: typeof ApiPromise) => Promise<any>;
|
|
22
|
+
const VALIDATOR_COMISSION_RATIO = 1000000000;
|
|
23
|
+
|
|
24
|
+
const getWsUrl = () => getEnv("API_POLKADOT_NODE");
|
|
25
|
+
|
|
26
|
+
const WEBSOCKET_DEBOUNCE_DELAY = 30000;
|
|
27
|
+
let api;
|
|
28
|
+
let pendingQueries: Array<Promise<any>> = [];
|
|
29
|
+
let apiDisconnectTimeout;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Connects to Substrate Node, executes calls then disconnects
|
|
33
|
+
*
|
|
34
|
+
* @param {*} execute - the calls to execute on api
|
|
35
|
+
*/
|
|
36
|
+
async function withApi(execute: AsyncApiFunction): Promise<any> {
|
|
37
|
+
// If client is instanciated already, ensure it is connected & ready
|
|
38
|
+
if (api) {
|
|
39
|
+
try {
|
|
40
|
+
await api.isReadyOrError;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
// definitely not connected...
|
|
43
|
+
api = null;
|
|
44
|
+
pendingQueries = [];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!api) {
|
|
49
|
+
const wsProvider = new WsProvider(
|
|
50
|
+
getWsUrl(),
|
|
51
|
+
/* autoConnectMs = */
|
|
52
|
+
false
|
|
53
|
+
);
|
|
54
|
+
wsProvider.connect();
|
|
55
|
+
api = await new ApiPromise({
|
|
56
|
+
provider: wsProvider,
|
|
57
|
+
}).isReadyOrError;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
cancelDebouncedDisconnect();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const query = execute(api);
|
|
64
|
+
pendingQueries.push(query.catch((err) => err));
|
|
65
|
+
const res = await query;
|
|
66
|
+
return res;
|
|
67
|
+
} finally {
|
|
68
|
+
debouncedDisconnect();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Disconnects Websocket API client after all pending queries are flushed.
|
|
74
|
+
*/
|
|
75
|
+
export const disconnect = async () => {
|
|
76
|
+
cancelDebouncedDisconnect();
|
|
77
|
+
|
|
78
|
+
if (api) {
|
|
79
|
+
const disconnecting = api;
|
|
80
|
+
const pending = pendingQueries;
|
|
81
|
+
api = undefined;
|
|
82
|
+
pendingQueries = [];
|
|
83
|
+
await Promise.all(pending);
|
|
84
|
+
await disconnecting.disconnect();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const cancelDebouncedDisconnect = () => {
|
|
89
|
+
if (apiDisconnectTimeout) {
|
|
90
|
+
clearTimeout(apiDisconnectTimeout);
|
|
91
|
+
apiDisconnectTimeout = null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Disconnects Websocket client after a delay.
|
|
97
|
+
*/
|
|
98
|
+
const debouncedDisconnect = () => {
|
|
99
|
+
cancelDebouncedDisconnect();
|
|
100
|
+
apiDisconnectTimeout = setTimeout(disconnect, WEBSOCKET_DEBOUNCE_DELAY);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns true if ElectionStatus is Close. If ElectionStatus is Open, some features must be disabled.
|
|
105
|
+
*/
|
|
106
|
+
export const isElectionClosed = async (): Promise<Boolean> =>
|
|
107
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
108
|
+
const status = await api.query.staking.eraElectionStatus();
|
|
109
|
+
const res = status.isClose;
|
|
110
|
+
return !!res;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns true if the address is a new account with no balance
|
|
115
|
+
*
|
|
116
|
+
* @param {*} addr
|
|
117
|
+
*/
|
|
118
|
+
export const isNewAccount = async (address: string): Promise<Boolean> =>
|
|
119
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
120
|
+
const {
|
|
121
|
+
nonce,
|
|
122
|
+
data: { free },
|
|
123
|
+
} = await api.query.system.account(address);
|
|
124
|
+
return (
|
|
125
|
+
new BigNumber(0).isEqualTo(nonce) && new BigNumber(0).isEqualTo(free)
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Returns true if the address is a new account with no balance
|
|
131
|
+
*
|
|
132
|
+
* @param {*} addr
|
|
133
|
+
*/
|
|
134
|
+
export const isControllerAddress = async (address: string): Promise<Boolean> =>
|
|
135
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
136
|
+
const ledgetOpt = await api.query.staking.ledger(address);
|
|
137
|
+
return ledgetOpt.isSome;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all validators addresses to check for validity.
|
|
142
|
+
*/
|
|
143
|
+
const getValidatorsStashesAddresses = async (): Promise<string[]> =>
|
|
144
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
145
|
+
const list = await api.derive.staking.stashes();
|
|
146
|
+
return list.map((v) => v.toString());
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns all addresses that are not validators
|
|
151
|
+
*/
|
|
152
|
+
export const verifyValidatorAddresses = async (
|
|
153
|
+
validators: string[]
|
|
154
|
+
): Promise<string[]> => {
|
|
155
|
+
const allValidators = await getValidatorsStashesAddresses();
|
|
156
|
+
return validators.filter((v) => !allValidators.includes(v));
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get all account-related data
|
|
161
|
+
*
|
|
162
|
+
* @param {*} addr
|
|
163
|
+
*/
|
|
164
|
+
export const getAccount = async (addr: string) =>
|
|
165
|
+
withApi(async () => {
|
|
166
|
+
const balances = await getBalances(addr);
|
|
167
|
+
const stakingInfo = await getStakingInfo(addr);
|
|
168
|
+
const nominations = await getNominations(addr);
|
|
169
|
+
return { ...balances, ...stakingInfo, nominations };
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Returns all the balances for an account
|
|
174
|
+
*
|
|
175
|
+
* @param {*} addr - the account address
|
|
176
|
+
*/
|
|
177
|
+
export const getBalances = async (addr: string) =>
|
|
178
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
179
|
+
const [finalizedHash, balances] = await Promise.all([
|
|
180
|
+
api.rpc.chain.getFinalizedHead(),
|
|
181
|
+
api.derive.balances.all(addr),
|
|
182
|
+
]);
|
|
183
|
+
const { number } = await api.rpc.chain.getHeader(finalizedHash);
|
|
184
|
+
return {
|
|
185
|
+
blockHeight: number.toNumber(),
|
|
186
|
+
balance: new BigNumber(balances.freeBalance),
|
|
187
|
+
spendableBalance: new BigNumber(balances.availableBalance),
|
|
188
|
+
nonce: balances.accountNonce.toNumber(),
|
|
189
|
+
lockedBalance: new BigNumber(balances.lockedBalance),
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Returns all staking-related data for an account
|
|
195
|
+
*
|
|
196
|
+
* @param {*} addr
|
|
197
|
+
*/
|
|
198
|
+
export const getStakingInfo = async (addr: string) =>
|
|
199
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
200
|
+
const [controlledLedgerOpt, bonded] = await Promise.all([
|
|
201
|
+
api.query.staking.ledger(addr),
|
|
202
|
+
api.query.staking.bonded(addr),
|
|
203
|
+
]);
|
|
204
|
+
// NOTE: controlledLedgerOpt is not the current stash ledger...
|
|
205
|
+
const stash = controlledLedgerOpt.isSome
|
|
206
|
+
? controlledLedgerOpt.unwrap().stash.toString()
|
|
207
|
+
: null;
|
|
208
|
+
const controller = bonded.isSome ? bonded.unwrap().toString() : null;
|
|
209
|
+
|
|
210
|
+
// If account is not a stash, no need to fetch corresponding ledger
|
|
211
|
+
if (!controller) {
|
|
212
|
+
return {
|
|
213
|
+
controller: null,
|
|
214
|
+
stash: stash || null,
|
|
215
|
+
unlockedBalance: new BigNumber(0),
|
|
216
|
+
unlockingBalance: new BigNumber(0),
|
|
217
|
+
unlockings: [],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const [activeOpt, ledgerOpt] = await Promise.all([
|
|
222
|
+
api.query.staking.activeEra(),
|
|
223
|
+
api.query.staking.ledger(controller),
|
|
224
|
+
]);
|
|
225
|
+
const now = new Date();
|
|
226
|
+
const { index, start } = activeOpt.unwrapOrDefault();
|
|
227
|
+
const activeEraIndex = index.toNumber();
|
|
228
|
+
const activeEraStart = start.unwrap().toNumber();
|
|
229
|
+
const blockTime = api.consts.babe.expectedBlockTime; // 6000 ms
|
|
230
|
+
|
|
231
|
+
const epochDuration = api.consts.babe.epochDuration; // 2400 blocks
|
|
232
|
+
|
|
233
|
+
const eraLength = api.consts.staking.sessionsPerEra // 6 sessions
|
|
234
|
+
.mul(epochDuration)
|
|
235
|
+
.mul(blockTime)
|
|
236
|
+
.toNumber();
|
|
237
|
+
const ledger = ledgerOpt.isSome ? ledgerOpt.unwrap() : null;
|
|
238
|
+
const unlockings = ledger
|
|
239
|
+
? ledger.unlocking.map((lock) => ({
|
|
240
|
+
amount: new BigNumber(lock.value),
|
|
241
|
+
completionDate: new Date(
|
|
242
|
+
activeEraStart + (lock.era - activeEraIndex) * eraLength
|
|
243
|
+
), // This is an estimation of the date of completion, since it depends on block validation speed
|
|
244
|
+
}))
|
|
245
|
+
: [];
|
|
246
|
+
const unlocked = unlockings.filter((lock) => lock.completionDate <= now);
|
|
247
|
+
const unlockingBalance = unlockings.reduce(
|
|
248
|
+
(sum, lock) => sum.plus(lock.amount),
|
|
249
|
+
new BigNumber(0)
|
|
250
|
+
);
|
|
251
|
+
const unlockedBalance = unlocked.reduce(
|
|
252
|
+
(sum, lock) => sum.plus(lock.amount),
|
|
253
|
+
new BigNumber(0)
|
|
254
|
+
);
|
|
255
|
+
return {
|
|
256
|
+
controller,
|
|
257
|
+
stash,
|
|
258
|
+
unlockedBalance,
|
|
259
|
+
unlockingBalance,
|
|
260
|
+
unlockings,
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Returns nominations for an account including validator address, status and associated stake.
|
|
266
|
+
*
|
|
267
|
+
* @param {*} addr
|
|
268
|
+
*/
|
|
269
|
+
export const getNominations = async (addr: string) =>
|
|
270
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
271
|
+
const [{ activeEra }, nominationsOpt] = await Promise.all([
|
|
272
|
+
api.derive.session.indexes(),
|
|
273
|
+
api.query.staking.nominators(addr),
|
|
274
|
+
]);
|
|
275
|
+
if (nominationsOpt.isNone) return [];
|
|
276
|
+
const targets = nominationsOpt.unwrap().targets;
|
|
277
|
+
const [exposures, stashes] = await Promise.all([
|
|
278
|
+
api.query.staking.erasStakers.multi(
|
|
279
|
+
targets.map((target) => [activeEra, target])
|
|
280
|
+
),
|
|
281
|
+
api.derive.staking.stashes(),
|
|
282
|
+
]);
|
|
283
|
+
const allStashes = stashes.map((stash) => stash.toString());
|
|
284
|
+
return targets.map((target, index) => {
|
|
285
|
+
const exposure = exposures[index];
|
|
286
|
+
const individualExposure = exposure.others.find(
|
|
287
|
+
(o) => o.who.toString() === addr
|
|
288
|
+
);
|
|
289
|
+
const value = individualExposure
|
|
290
|
+
? new BigNumber(individualExposure.value)
|
|
291
|
+
: new BigNumber(0);
|
|
292
|
+
const status = exposure.others.length
|
|
293
|
+
? individualExposure
|
|
294
|
+
? "active"
|
|
295
|
+
: "inactive"
|
|
296
|
+
: allStashes.includes(target.toString())
|
|
297
|
+
? "waiting"
|
|
298
|
+
: null;
|
|
299
|
+
return {
|
|
300
|
+
address: target.toString(),
|
|
301
|
+
value,
|
|
302
|
+
status,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Returns all the params from the chain to build an extrinsic (a transaction on Substrate)
|
|
309
|
+
*/
|
|
310
|
+
export const getTransactionParams = async () =>
|
|
311
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
312
|
+
const chainName = await api.rpc.system.chain();
|
|
313
|
+
const blockHash = await api.rpc.chain.getFinalizedHead();
|
|
314
|
+
const genesisHash = await api.rpc.chain.getBlockHash(0);
|
|
315
|
+
const { number } = await api.rpc.chain.getHeader(blockHash);
|
|
316
|
+
const { specName, specVersion, transactionVersion } =
|
|
317
|
+
await api.rpc.state.getRuntimeVersion(blockHash);
|
|
318
|
+
return {
|
|
319
|
+
blockHash,
|
|
320
|
+
blockNumber: number,
|
|
321
|
+
genesisHash,
|
|
322
|
+
chainName: chainName.toString(),
|
|
323
|
+
specName: specName.toString(),
|
|
324
|
+
specVersion,
|
|
325
|
+
transactionVersion,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Broadcast the transaction to the substrate node
|
|
331
|
+
*
|
|
332
|
+
* @param {string} extrinsic - the encoded extrinsic to send
|
|
333
|
+
*/
|
|
334
|
+
export const submitExtrinsic = async (extrinsic: string) =>
|
|
335
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
336
|
+
const tx = api.tx(extrinsic);
|
|
337
|
+
const hash = await api.rpc.author.submitExtrinsic(tx);
|
|
338
|
+
return hash;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Retrieve the transaction fees and weights
|
|
343
|
+
* Note: fees on Substrate are not set by the signer, but directly by the blockchain runtime.
|
|
344
|
+
*
|
|
345
|
+
* @param {string} extrinsic - the encoded extrinsic to send with a fake signing
|
|
346
|
+
*/
|
|
347
|
+
export const paymentInfo = async (extrinsic: string) =>
|
|
348
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
349
|
+
const info = await api.rpc.payment.queryInfo(extrinsic);
|
|
350
|
+
return info;
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Fetch all reward points for validators for current era
|
|
355
|
+
*
|
|
356
|
+
* @returns Map<String, BigNumber>
|
|
357
|
+
*/
|
|
358
|
+
export const fetchRewardPoints = async () =>
|
|
359
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
360
|
+
const activeOpt = await api.query.staking.activeEra();
|
|
361
|
+
const { index: activeEra } = activeOpt.unwrapOrDefault();
|
|
362
|
+
const { individual } = await api.query.staking.erasRewardPoints(activeEra);
|
|
363
|
+
// recast BTreeMap<AccountId,RewardPoint> to Map<String, RewardPoint> because strict equality does not work
|
|
364
|
+
const rewards = new Map<String, BigNumber>(
|
|
365
|
+
[...individual.entries()].map(([k, v]) => [
|
|
366
|
+
k.toString(),
|
|
367
|
+
new BigNumber(v.toString()),
|
|
368
|
+
])
|
|
369
|
+
);
|
|
370
|
+
return rewards;
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @source https://github.com/polkadot-js/api/blob/master/packages/api-derive/src/accounts/info.ts
|
|
375
|
+
*/
|
|
376
|
+
function dataAsString(data: Data): string {
|
|
377
|
+
return data.isRaw
|
|
378
|
+
? u8aToString(data.asRaw.toU8a(true)).trim()
|
|
379
|
+
: data.isNone
|
|
380
|
+
? ""
|
|
381
|
+
: data.toHex();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Fetch identity name of multiple addresses.
|
|
386
|
+
* Get parent identity if any, and concatenate parent name with child name.
|
|
387
|
+
*
|
|
388
|
+
* @param {string[]} addresses
|
|
389
|
+
*/
|
|
390
|
+
export const fetchIdentities = async (addresses: string[]) =>
|
|
391
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
392
|
+
const superOfOpts = await api.query.identity.superOf.multi<
|
|
393
|
+
Option<ITuple<[AccountId, Data]>>
|
|
394
|
+
>(addresses);
|
|
395
|
+
const withParent = superOfOpts.map((superOfOpt) =>
|
|
396
|
+
superOfOpt?.isSome ? superOfOpt?.unwrap() : undefined
|
|
397
|
+
);
|
|
398
|
+
const parentAddresses = uniq(
|
|
399
|
+
compact(withParent.map((superOf) => superOf && superOf[0].toString()))
|
|
400
|
+
);
|
|
401
|
+
const [identities, parentIdentities] = await Promise.all([
|
|
402
|
+
api.query.identity.identityOf.multi<Option<Registration>>(addresses),
|
|
403
|
+
api.query.identity.identityOf.multi<Option<Registration>>(
|
|
404
|
+
parentAddresses
|
|
405
|
+
),
|
|
406
|
+
]);
|
|
407
|
+
const map = new Map<string, string>(
|
|
408
|
+
addresses.map((addr, index) => {
|
|
409
|
+
const indexOfParent = withParent[index]
|
|
410
|
+
? parentAddresses.indexOf(withParent[index][0].toString())
|
|
411
|
+
: -1;
|
|
412
|
+
const identityOpt =
|
|
413
|
+
indexOfParent > -1
|
|
414
|
+
? parentIdentities[indexOfParent]
|
|
415
|
+
: identities[index];
|
|
416
|
+
|
|
417
|
+
if (identityOpt.isNone) {
|
|
418
|
+
return [addr, ""];
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const {
|
|
422
|
+
info: { display },
|
|
423
|
+
} = identityOpt.unwrap();
|
|
424
|
+
const name = withParent[index]
|
|
425
|
+
? `${dataAsString(display)} / ${dataAsString(withParent[index][1])}`
|
|
426
|
+
: dataAsString(display);
|
|
427
|
+
return [addr, name];
|
|
428
|
+
})
|
|
429
|
+
);
|
|
430
|
+
return map;
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Transforms each validator into an internal Validator type.
|
|
435
|
+
* @param {*} rewards - map of addres sand corresponding reward
|
|
436
|
+
* @param {*} identities - map of address and corresponding identity
|
|
437
|
+
* @param {*} elected - list of elected validator addresses
|
|
438
|
+
* @param {*} maxNominators - constant for oversubscribed validators
|
|
439
|
+
* @param {*} validator - the validator details to transform.
|
|
440
|
+
*/
|
|
441
|
+
const mapValidator = (
|
|
442
|
+
rewards,
|
|
443
|
+
identities,
|
|
444
|
+
elected,
|
|
445
|
+
maxNominators,
|
|
446
|
+
validator
|
|
447
|
+
): PolkadotValidator => {
|
|
448
|
+
const address = validator.accountId.toString();
|
|
449
|
+
return {
|
|
450
|
+
address: address,
|
|
451
|
+
identity: identities.get(address) || "",
|
|
452
|
+
nominatorsCount: validator.exposure.others.length,
|
|
453
|
+
rewardPoints: rewards.get(address) || null,
|
|
454
|
+
commission: new BigNumber(validator.validatorPrefs.commission).dividedBy(
|
|
455
|
+
VALIDATOR_COMISSION_RATIO
|
|
456
|
+
),
|
|
457
|
+
totalBonded: new BigNumber(validator.exposure.total),
|
|
458
|
+
selfBonded: new BigNumber(validator.exposure.own),
|
|
459
|
+
isElected: elected.includes(address),
|
|
460
|
+
isOversubscribed: validator.exposure.others.length >= maxNominators,
|
|
461
|
+
};
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* List all validators for the current era, and their exposure, and identity.
|
|
466
|
+
*/
|
|
467
|
+
export const getValidators = async (
|
|
468
|
+
stashes: string | string[] = "elected"
|
|
469
|
+
): Promise<PolkadotValidator> =>
|
|
470
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
471
|
+
const [allStashes, elected] = await Promise.all([
|
|
472
|
+
api.derive.staking.stashes(),
|
|
473
|
+
api.query.session.validators(),
|
|
474
|
+
]);
|
|
475
|
+
let stashIds;
|
|
476
|
+
const allIds = allStashes.map((s) => s.toString());
|
|
477
|
+
const electedIds = elected.map((s) => s.toString());
|
|
478
|
+
|
|
479
|
+
if (Array.isArray(stashes)) {
|
|
480
|
+
stashIds = allIds.filter((s) => stashes.includes(s));
|
|
481
|
+
} else if (stashes === "elected") {
|
|
482
|
+
stashIds = electedIds;
|
|
483
|
+
} else {
|
|
484
|
+
const waitingIds = allIds.filter((v) => !electedIds.includes(v));
|
|
485
|
+
|
|
486
|
+
if (stashes === "waiting") {
|
|
487
|
+
stashIds = waitingIds;
|
|
488
|
+
} else {
|
|
489
|
+
// Keep elected in first positions
|
|
490
|
+
stashIds = [...electedIds, ...waitingIds];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const [validators, rewards, identities] = await Promise.all([
|
|
495
|
+
api.derive.staking.accounts(stashIds),
|
|
496
|
+
fetchRewardPoints(),
|
|
497
|
+
fetchIdentities(stashIds),
|
|
498
|
+
]);
|
|
499
|
+
return validators.map(
|
|
500
|
+
mapValidator.bind(
|
|
501
|
+
null,
|
|
502
|
+
rewards,
|
|
503
|
+
identities,
|
|
504
|
+
electedIds,
|
|
505
|
+
api.consts.staking.maxNominatorRewardedPerValidator
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Get Active Era progress
|
|
512
|
+
*/
|
|
513
|
+
export const getStakingProgress = async (): Promise<PolkadotStakingProgress> =>
|
|
514
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
515
|
+
const [activeEraOpt, status] = await Promise.all([
|
|
516
|
+
api.query.staking.activeEra(),
|
|
517
|
+
api.query.staking.eraElectionStatus(),
|
|
518
|
+
]);
|
|
519
|
+
const { index: activeEra } = activeEraOpt.unwrapOrDefault();
|
|
520
|
+
return {
|
|
521
|
+
activeEra: activeEra.toNumber(),
|
|
522
|
+
electionClosed: !!status.isClose,
|
|
523
|
+
};
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Return TypeRegistry and decorated extrinsics from client
|
|
528
|
+
*/
|
|
529
|
+
export const getRegistry = async () =>
|
|
530
|
+
withApi(async (api: typeof ApiPromise) => {
|
|
531
|
+
return {
|
|
532
|
+
registry: api.registry,
|
|
533
|
+
extrinsics: api.tx,
|
|
534
|
+
};
|
|
535
|
+
});
|
package/src/bridge/js.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AccountBridge,
|
|
3
|
+
CurrencyBridge,
|
|
4
|
+
Operation,
|
|
5
|
+
SignedOperation,
|
|
6
|
+
} from "@ledgerhq/types-live";
|
|
7
|
+
import type { Transaction } from "../types";
|
|
8
|
+
import {
|
|
9
|
+
DeviceCommunication,
|
|
10
|
+
makeAccountBridgeReceive,
|
|
11
|
+
makeScanAccounts,
|
|
12
|
+
makeSync,
|
|
13
|
+
} from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
14
|
+
import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation";
|
|
15
|
+
import { submitExtrinsic } from "../api";
|
|
16
|
+
import { getPreloadStrategy, preload, hydrate } from "../preload";
|
|
17
|
+
import { getAccountShape } from "../js-synchronisation";
|
|
18
|
+
import createTransaction from "../js-createTransaction";
|
|
19
|
+
import prepareTransaction from "../js-prepareTransaction";
|
|
20
|
+
import getTransactionStatus from "../js-getTransactionStatus";
|
|
21
|
+
import estimateMaxSpendable from "../js-estimateMaxSpendable";
|
|
22
|
+
import buildSignOperation from "../js-signOperation";
|
|
23
|
+
import getAddress from "../hw-getAddress";
|
|
24
|
+
import { loadPolkadotCrypto } from "../polkadot-crypto";
|
|
25
|
+
import getAddressWrapper from "@ledgerhq/coin-framework/bridge/getAddressWrapper";
|
|
26
|
+
|
|
27
|
+
const updateTransaction = (t, patch) => ({ ...t, ...patch });
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Broadcast the signed transaction
|
|
31
|
+
* @param {signature: string, operation: string} signedOperation
|
|
32
|
+
*/
|
|
33
|
+
const broadcast = async ({
|
|
34
|
+
signedOperation: { signature, operation },
|
|
35
|
+
}: {
|
|
36
|
+
signedOperation: SignedOperation;
|
|
37
|
+
}): Promise<Operation> => {
|
|
38
|
+
await loadPolkadotCrypto();
|
|
39
|
+
const hash = await submitExtrinsic(signature);
|
|
40
|
+
return patchOperationWithHash(operation, hash);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function buildCurrencyBridge(
|
|
44
|
+
deviceCommunication: DeviceCommunication
|
|
45
|
+
): CurrencyBridge {
|
|
46
|
+
const scanAccounts = makeScanAccounts({
|
|
47
|
+
getAccountShape,
|
|
48
|
+
deviceCommunication,
|
|
49
|
+
getAddressFn: getAddress,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
getPreloadStrategy,
|
|
54
|
+
preload,
|
|
55
|
+
hydrate,
|
|
56
|
+
scanAccounts,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildAccountBridge(
|
|
61
|
+
deviceCommunication: DeviceCommunication
|
|
62
|
+
): AccountBridge<Transaction> {
|
|
63
|
+
const receive = makeAccountBridgeReceive(
|
|
64
|
+
getAddressWrapper(getAddress),
|
|
65
|
+
deviceCommunication
|
|
66
|
+
);
|
|
67
|
+
const signOperation = buildSignOperation(deviceCommunication);
|
|
68
|
+
const sync = makeSync({ getAccountShape });
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
estimateMaxSpendable,
|
|
72
|
+
createTransaction,
|
|
73
|
+
updateTransaction,
|
|
74
|
+
getTransactionStatus,
|
|
75
|
+
prepareTransaction,
|
|
76
|
+
sync,
|
|
77
|
+
receive,
|
|
78
|
+
signOperation,
|
|
79
|
+
broadcast,
|
|
80
|
+
};
|
|
81
|
+
}
|