@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,232 @@
|
|
|
1
|
+
import { BigNumber } from "bignumber.js";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { TypeRegistry } from "@polkadot/types";
|
|
4
|
+
import { u8aConcat } from "@polkadot/util";
|
|
5
|
+
import { FeeNotLoaded } from "@ledgerhq/errors";
|
|
6
|
+
import Polkadot from "@ledgerhq/hw-app-polkadot";
|
|
7
|
+
import type { PolkadotAccount, Transaction } from "./types";
|
|
8
|
+
import type {
|
|
9
|
+
Account,
|
|
10
|
+
Operation,
|
|
11
|
+
OperationType,
|
|
12
|
+
SignOperationEvent,
|
|
13
|
+
SignOperationFnSignature,
|
|
14
|
+
} from "@ledgerhq/types-live";
|
|
15
|
+
import { DeviceCommunication } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
16
|
+
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
17
|
+
import { buildTransaction } from "./js-buildTransaction";
|
|
18
|
+
import { calculateAmount, getNonce, isFirstBond } from "./logic";
|
|
19
|
+
const MODE_TO_TYPE = {
|
|
20
|
+
send: "OUT",
|
|
21
|
+
bond: "BOND",
|
|
22
|
+
unbond: "UNBOND",
|
|
23
|
+
rebond: "BOND",
|
|
24
|
+
withdrawUnbonded: "WITHDRAW_UNBONDED",
|
|
25
|
+
nominate: "NOMINATE",
|
|
26
|
+
chill: "CHILL",
|
|
27
|
+
setController: "SET_CONTROLLER",
|
|
28
|
+
claimReward: "REWARD_PAYOUT",
|
|
29
|
+
default: "FEES",
|
|
30
|
+
};
|
|
31
|
+
const MODE_TO_PALLET_METHOD = {
|
|
32
|
+
send: "balances.transferKeepAlive",
|
|
33
|
+
sendMax: "balances.transfer",
|
|
34
|
+
bond: "staking.bond",
|
|
35
|
+
bondExtra: "staking.bondExtra",
|
|
36
|
+
unbond: "staking.unbond",
|
|
37
|
+
rebond: "staking.rebond",
|
|
38
|
+
withdrawUnbonded: "staking.withdrawUnbonded",
|
|
39
|
+
nominate: "staking.nominate",
|
|
40
|
+
chill: "staking.chill",
|
|
41
|
+
setController: "staking.setController",
|
|
42
|
+
claimReward: "staking.payoutStakers",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getExtra = (
|
|
46
|
+
type: string,
|
|
47
|
+
account: PolkadotAccount,
|
|
48
|
+
transaction: Transaction
|
|
49
|
+
) => {
|
|
50
|
+
const extra = {
|
|
51
|
+
palletMethod: MODE_TO_PALLET_METHOD[transaction.mode],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (transaction.mode == "send" && transaction.useAllAmount) {
|
|
55
|
+
extra.palletMethod = MODE_TO_PALLET_METHOD["sendMax"];
|
|
56
|
+
} else if (transaction.mode === "bond" && !isFirstBond(account)) {
|
|
57
|
+
extra.palletMethod = MODE_TO_PALLET_METHOD["bondExtra"];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
switch (type) {
|
|
61
|
+
case "OUT":
|
|
62
|
+
return { ...extra, transferAmount: new BigNumber(transaction.amount) };
|
|
63
|
+
|
|
64
|
+
case "BOND":
|
|
65
|
+
return { ...extra, bondedAmount: new BigNumber(transaction.amount) };
|
|
66
|
+
|
|
67
|
+
case "UNBOND":
|
|
68
|
+
return { ...extra, unbondedAmount: new BigNumber(transaction.amount) };
|
|
69
|
+
|
|
70
|
+
case "WITHDRAW_UNBONDED":
|
|
71
|
+
return {
|
|
72
|
+
...extra,
|
|
73
|
+
withdrawUnbondedAmount: new BigNumber(
|
|
74
|
+
account.polkadotResources?.unlockedBalance || 0
|
|
75
|
+
),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
case "NOMINATE":
|
|
79
|
+
return { ...extra, validators: transaction.validators };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return extra;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const buildOptimisticOperation = (
|
|
86
|
+
account: PolkadotAccount,
|
|
87
|
+
transaction: Transaction,
|
|
88
|
+
fee: BigNumber
|
|
89
|
+
): Operation => {
|
|
90
|
+
const type = (MODE_TO_TYPE[transaction.mode] ??
|
|
91
|
+
MODE_TO_TYPE.default) as OperationType;
|
|
92
|
+
const value =
|
|
93
|
+
type === "OUT"
|
|
94
|
+
? new BigNumber(transaction.amount).plus(fee)
|
|
95
|
+
: new BigNumber(fee);
|
|
96
|
+
const extra = getExtra(type, account, transaction);
|
|
97
|
+
const operation: Operation = {
|
|
98
|
+
id: encodeOperationId(account.id, "", type),
|
|
99
|
+
hash: "",
|
|
100
|
+
type,
|
|
101
|
+
value,
|
|
102
|
+
fee,
|
|
103
|
+
blockHash: null,
|
|
104
|
+
blockHeight: null,
|
|
105
|
+
senders: [account.freshAddress],
|
|
106
|
+
recipients: [transaction.recipient].filter(Boolean),
|
|
107
|
+
accountId: account.id,
|
|
108
|
+
transactionSequenceNumber: getNonce(account),
|
|
109
|
+
date: new Date(),
|
|
110
|
+
extra,
|
|
111
|
+
};
|
|
112
|
+
return operation;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Serialize a signed transaction in a format that can be submitted over the
|
|
117
|
+
* Node RPC Interface from the signing payload and signature produced by the
|
|
118
|
+
* remote signer.
|
|
119
|
+
*
|
|
120
|
+
* @param unsigned - The JSON representing the unsigned transaction.
|
|
121
|
+
* @param signature - Signature of the signing payload produced by the remote signer.
|
|
122
|
+
* @param registry - Registry used for constructing the payload.
|
|
123
|
+
*/
|
|
124
|
+
export const signExtrinsic = async (
|
|
125
|
+
unsigned: Record<string, any>,
|
|
126
|
+
signature: any,
|
|
127
|
+
registry: TypeRegistry
|
|
128
|
+
): Promise<string> => {
|
|
129
|
+
const extrinsic = registry.createType("Extrinsic", unsigned, {
|
|
130
|
+
version: unsigned.version,
|
|
131
|
+
});
|
|
132
|
+
extrinsic.addSignature(unsigned.address, signature, unsigned as any);
|
|
133
|
+
return extrinsic.toHex();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sign Extrinsic with a fake signature (for fees estimation).
|
|
138
|
+
*
|
|
139
|
+
* @param unsigned - The JSON representing the unsigned transaction.
|
|
140
|
+
* @param registry - Registry used for constructing the payload.
|
|
141
|
+
*/
|
|
142
|
+
export const fakeSignExtrinsic = async (
|
|
143
|
+
unsigned: Record<string, any>,
|
|
144
|
+
registry: TypeRegistry
|
|
145
|
+
): Promise<string> => {
|
|
146
|
+
const fakeSignature = u8aConcat(
|
|
147
|
+
new Uint8Array([1]),
|
|
148
|
+
new Uint8Array(64).fill(0x42)
|
|
149
|
+
);
|
|
150
|
+
return signExtrinsic(unsigned, fakeSignature, registry);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sign Transaction with Ledger hardware
|
|
155
|
+
*/
|
|
156
|
+
const buildSignOperation =
|
|
157
|
+
(withDevice: DeviceCommunication): SignOperationFnSignature<Transaction> =>
|
|
158
|
+
({
|
|
159
|
+
account,
|
|
160
|
+
deviceId,
|
|
161
|
+
transaction,
|
|
162
|
+
}: {
|
|
163
|
+
account: Account;
|
|
164
|
+
deviceId: any;
|
|
165
|
+
transaction: Transaction;
|
|
166
|
+
}): Observable<SignOperationEvent> =>
|
|
167
|
+
withDevice(deviceId)(
|
|
168
|
+
(transport) =>
|
|
169
|
+
new Observable((o) => {
|
|
170
|
+
async function main() {
|
|
171
|
+
o.next({
|
|
172
|
+
type: "device-signature-requested",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!transaction.fees) {
|
|
176
|
+
throw new FeeNotLoaded();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Ensure amount is filled when useAllAmount
|
|
180
|
+
const transactionToSign = {
|
|
181
|
+
...transaction,
|
|
182
|
+
amount: calculateAmount({
|
|
183
|
+
a: account as PolkadotAccount,
|
|
184
|
+
t: transaction,
|
|
185
|
+
}),
|
|
186
|
+
};
|
|
187
|
+
const { unsigned, registry } = await buildTransaction(
|
|
188
|
+
account as PolkadotAccount,
|
|
189
|
+
transactionToSign,
|
|
190
|
+
true
|
|
191
|
+
);
|
|
192
|
+
const payload = registry
|
|
193
|
+
.createType("ExtrinsicPayload", unsigned, {
|
|
194
|
+
version: unsigned.version,
|
|
195
|
+
})
|
|
196
|
+
.toU8a({
|
|
197
|
+
method: true,
|
|
198
|
+
});
|
|
199
|
+
// Sign by device
|
|
200
|
+
const polkadot = new Polkadot(transport);
|
|
201
|
+
// FIXME: the type of payload Uint8Array is not compatible with the signature of sign which accept a string
|
|
202
|
+
const r = await polkadot.sign(
|
|
203
|
+
account.freshAddressPath,
|
|
204
|
+
payload as any
|
|
205
|
+
);
|
|
206
|
+
const signed = await signExtrinsic(unsigned, r.signature, registry);
|
|
207
|
+
o.next({
|
|
208
|
+
type: "device-signature-granted",
|
|
209
|
+
});
|
|
210
|
+
const operation = buildOptimisticOperation(
|
|
211
|
+
account as PolkadotAccount,
|
|
212
|
+
transactionToSign,
|
|
213
|
+
transactionToSign.fees ?? new BigNumber(0)
|
|
214
|
+
);
|
|
215
|
+
o.next({
|
|
216
|
+
type: "signed",
|
|
217
|
+
signedOperation: {
|
|
218
|
+
operation,
|
|
219
|
+
signature: signed,
|
|
220
|
+
expirationDate: null,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
main().then(
|
|
226
|
+
() => o.complete(),
|
|
227
|
+
(e) => o.error(e)
|
|
228
|
+
);
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
export default buildSignOperation;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Account } from "@ledgerhq/types-live";
|
|
2
|
+
import { encodeAccountId } from "@ledgerhq/coin-framework/account/index";
|
|
3
|
+
import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
4
|
+
import { mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
5
|
+
import { getAccount, getOperations } from "./api";
|
|
6
|
+
import { loadPolkadotCrypto } from "./polkadot-crypto";
|
|
7
|
+
|
|
8
|
+
export const getAccountShape: GetAccountShape = async (info) => {
|
|
9
|
+
await loadPolkadotCrypto();
|
|
10
|
+
const { address, initialAccount, currency, derivationMode } = info;
|
|
11
|
+
const oldOperations = initialAccount?.operations || [];
|
|
12
|
+
const startAt = oldOperations.length
|
|
13
|
+
? (oldOperations[0].blockHeight || 0) + 1
|
|
14
|
+
: 0;
|
|
15
|
+
const {
|
|
16
|
+
blockHeight,
|
|
17
|
+
balance,
|
|
18
|
+
spendableBalance,
|
|
19
|
+
nonce,
|
|
20
|
+
lockedBalance,
|
|
21
|
+
controller,
|
|
22
|
+
stash,
|
|
23
|
+
unlockedBalance,
|
|
24
|
+
unlockingBalance,
|
|
25
|
+
unlockings,
|
|
26
|
+
nominations,
|
|
27
|
+
numSlashingSpans,
|
|
28
|
+
} = await getAccount(address);
|
|
29
|
+
const accountId = encodeAccountId({
|
|
30
|
+
type: "js",
|
|
31
|
+
version: "2",
|
|
32
|
+
currencyId: currency.id,
|
|
33
|
+
xpubOrAddress: address,
|
|
34
|
+
derivationMode,
|
|
35
|
+
});
|
|
36
|
+
const newOperations = await getOperations(accountId, address, startAt);
|
|
37
|
+
const operations = mergeOps(oldOperations, newOperations);
|
|
38
|
+
const shape = {
|
|
39
|
+
id: accountId,
|
|
40
|
+
balance,
|
|
41
|
+
spendableBalance,
|
|
42
|
+
operationsCount: operations.length,
|
|
43
|
+
blockHeight,
|
|
44
|
+
polkadotResources: {
|
|
45
|
+
nonce,
|
|
46
|
+
controller,
|
|
47
|
+
stash,
|
|
48
|
+
lockedBalance,
|
|
49
|
+
unlockedBalance,
|
|
50
|
+
unlockingBalance,
|
|
51
|
+
unlockings,
|
|
52
|
+
nominations,
|
|
53
|
+
numSlashingSpans,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
return { ...shape, operations } as Partial<Account>;
|
|
57
|
+
};
|
package/src/logic.ts
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { BigNumber } from "bignumber.js";
|
|
2
|
+
import type { Account, OperationType } from "@ledgerhq/types-live";
|
|
3
|
+
import type { PolkadotAccount, Transaction } from "./types";
|
|
4
|
+
import { getCurrentPolkadotPreloadData } from "./preload";
|
|
5
|
+
|
|
6
|
+
export const EXISTENTIAL_DEPOSIT = new BigNumber(10000000000);
|
|
7
|
+
export const EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN = new BigNumber(1000000000); // Polkadot recommended Existential Deposit error margin
|
|
8
|
+
export const MAX_NOMINATIONS = 16;
|
|
9
|
+
export const MAX_UNLOCKINGS = 32;
|
|
10
|
+
export const PRELOAD_MAX_AGE = 60 * 1000;
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-loss-of-precision
|
|
12
|
+
export const MAX_AMOUNT_INPUT = 0xffffffffffffffff;
|
|
13
|
+
export const FEES_SAFETY_BUFFER = new BigNumber(1000000000); // Arbitrary buffer for paying fees of next transactions
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns true if account is a stash.
|
|
17
|
+
* When the account is a stash, we have the information of which account is the controller
|
|
18
|
+
*
|
|
19
|
+
* @param {PolkadotAccount} a
|
|
20
|
+
*/
|
|
21
|
+
export const isStash = (a: PolkadotAccount): boolean => {
|
|
22
|
+
return !!a.polkadotResources?.controller;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if account is a controller.
|
|
27
|
+
* when account is a controller, we have the information of which stash it controls
|
|
28
|
+
*
|
|
29
|
+
* @param {PolkadotAccount} a
|
|
30
|
+
*/
|
|
31
|
+
export const isController = (a: PolkadotAccount): boolean => {
|
|
32
|
+
return !!a.polkadotResources?.stash;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if account is controlled by an external account (not self)
|
|
37
|
+
*
|
|
38
|
+
* @param {PolkadotAccount} a
|
|
39
|
+
*/
|
|
40
|
+
export const hasExternalController = (a: PolkadotAccount): boolean => {
|
|
41
|
+
return a.polkadotResources?.controller
|
|
42
|
+
? a.polkadotResources?.controller !== a.freshAddress
|
|
43
|
+
: false;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns true if account controls an external stash (not self)
|
|
48
|
+
*
|
|
49
|
+
* @param {PolkadotAccount} a
|
|
50
|
+
*/
|
|
51
|
+
export const hasExternalStash = (a: PolkadotAccount): boolean => {
|
|
52
|
+
return a.polkadotResources?.stash
|
|
53
|
+
? a.polkadotResources?.stash !== a.freshAddress
|
|
54
|
+
: false;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Must have the minimum balance to bond
|
|
59
|
+
*
|
|
60
|
+
* @param {PolkadotAccount} a
|
|
61
|
+
*/
|
|
62
|
+
export const canBond = (a: Account): boolean => {
|
|
63
|
+
const { balance } = a;
|
|
64
|
+
return EXISTENTIAL_DEPOSIT.lte(balance);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the minimum bond amount required to bond/rebond
|
|
69
|
+
*
|
|
70
|
+
* @param {PolkadotAccount} a
|
|
71
|
+
*/
|
|
72
|
+
export const getMinimumAmountToBond = (
|
|
73
|
+
a: PolkadotAccount,
|
|
74
|
+
minimumBondBalance: BigNumber | undefined
|
|
75
|
+
): BigNumber => {
|
|
76
|
+
const currentlyBondedBalance = calculateMaxUnbond(a);
|
|
77
|
+
|
|
78
|
+
if (minimumBondBalance && currentlyBondedBalance.lt(minimumBondBalance)) {
|
|
79
|
+
return minimumBondBalance.minus(currentlyBondedBalance);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return new BigNumber(0);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Return true if the account has at least the current minimum bonded balance required by the network
|
|
87
|
+
*
|
|
88
|
+
* @param {PolkadotAccount} a
|
|
89
|
+
*/
|
|
90
|
+
export const hasMinimumBondBalance = (a: PolkadotAccount): boolean => {
|
|
91
|
+
const { minimumBondBalance } = getCurrentPolkadotPreloadData();
|
|
92
|
+
return (
|
|
93
|
+
!a.polkadotResources ||
|
|
94
|
+
a.polkadotResources.lockedBalance.gte(new BigNumber(minimumBondBalance))
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Return true if some operation with type is pending and not yet synchronized
|
|
100
|
+
*
|
|
101
|
+
* @param {PolkadotAccount} a
|
|
102
|
+
*/
|
|
103
|
+
export const hasPendingOperationType = (
|
|
104
|
+
a: Account,
|
|
105
|
+
type: OperationType
|
|
106
|
+
): boolean => {
|
|
107
|
+
return a.pendingOperations?.some((op) => op.type === type) ?? false;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Retyrns true if has reached the maximum of unlocking slots
|
|
112
|
+
*
|
|
113
|
+
* @param {PolkadotAccount} a
|
|
114
|
+
*/
|
|
115
|
+
export const hasMaxUnlockings = (a: PolkadotAccount) => {
|
|
116
|
+
const { unlockings = [] } = a.polkadotResources || {};
|
|
117
|
+
return (unlockings?.length || 0) >= MAX_UNLOCKINGS;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Return true if account has enough Locked Balance to rebond
|
|
122
|
+
*
|
|
123
|
+
* @param {PolkadotAccount} a
|
|
124
|
+
*/
|
|
125
|
+
export const hasLockedBalance = (a: PolkadotAccount) => {
|
|
126
|
+
const {
|
|
127
|
+
lockedBalance = new BigNumber(0),
|
|
128
|
+
unlockingBalance = new BigNumber(0),
|
|
129
|
+
} = a.polkadotResources || {};
|
|
130
|
+
return lockedBalance.minus(unlockingBalance).gt(0);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Must have locked Balance
|
|
135
|
+
*
|
|
136
|
+
* @param {PolkadotAccount} a
|
|
137
|
+
*/
|
|
138
|
+
export const canUnbond = (a: PolkadotAccount): boolean => {
|
|
139
|
+
return hasLockedBalance(a) && !hasMaxUnlockings(a);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Returns true if an account can nominate
|
|
144
|
+
*
|
|
145
|
+
* @param {PolkadotAccount} a
|
|
146
|
+
*/
|
|
147
|
+
export const canNominate = (a: PolkadotAccount): boolean => isController(a);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns true if account must do a first bond - false for a bond extra
|
|
151
|
+
*
|
|
152
|
+
* @param {Account} a
|
|
153
|
+
*/
|
|
154
|
+
export const isFirstBond = (a: PolkadotAccount): boolean => !isStash(a);
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns nonce for an account
|
|
158
|
+
*
|
|
159
|
+
* @param {Account} a
|
|
160
|
+
*/
|
|
161
|
+
export const getNonce = (a: PolkadotAccount): number => {
|
|
162
|
+
const lastPendingOp = a.pendingOperations[0];
|
|
163
|
+
const nonce = Math.max(
|
|
164
|
+
a.polkadotResources?.nonce || 0,
|
|
165
|
+
lastPendingOp && typeof lastPendingOp.transactionSequenceNumber === "number"
|
|
166
|
+
? lastPendingOp.transactionSequenceNumber + 1
|
|
167
|
+
: 0
|
|
168
|
+
);
|
|
169
|
+
return nonce;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Calculate max bond which is the actual spendable minus a safety buffer,
|
|
174
|
+
* so the user still has funds to pay the fees for next transactions
|
|
175
|
+
*
|
|
176
|
+
* @param {*} a
|
|
177
|
+
* @param {*} t
|
|
178
|
+
*/
|
|
179
|
+
const calculateMaxBond = (a: PolkadotAccount, t: Transaction): BigNumber => {
|
|
180
|
+
const amount = a.spendableBalance
|
|
181
|
+
.minus(t.fees || 0)
|
|
182
|
+
.minus(FEES_SAFETY_BUFFER);
|
|
183
|
+
return amount.lt(0) ? new BigNumber(0) : amount;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Calculates max unbond amount which is the remaining active locked balance (not unlocking)
|
|
188
|
+
*
|
|
189
|
+
* @param {*} account
|
|
190
|
+
*/
|
|
191
|
+
const calculateMaxUnbond = (a: PolkadotAccount): BigNumber => {
|
|
192
|
+
return (
|
|
193
|
+
a.polkadotResources?.lockedBalance.minus(
|
|
194
|
+
a.polkadotResources.unlockingBalance
|
|
195
|
+
) ?? new BigNumber(0)
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Calculates max rebond amount which is the current unlocking balance (including unlocked)
|
|
201
|
+
*
|
|
202
|
+
* @param {*} account
|
|
203
|
+
*/
|
|
204
|
+
const calculateMaxRebond = (a: PolkadotAccount): BigNumber => {
|
|
205
|
+
return a.polkadotResources?.unlockingBalance ?? new BigNumber(0);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Calculate the real spendable
|
|
210
|
+
*
|
|
211
|
+
* @param {*} a
|
|
212
|
+
* @param {*} t
|
|
213
|
+
*/
|
|
214
|
+
const calculateMaxSend = (a: Account, t: Transaction): BigNumber => {
|
|
215
|
+
const amount = a.spendableBalance.minus(t.fees || 0);
|
|
216
|
+
return amount.lt(0) ? new BigNumber(0) : amount;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Calculates correct amount if useAllAmount
|
|
221
|
+
*
|
|
222
|
+
* @param {*} param
|
|
223
|
+
*/
|
|
224
|
+
export const calculateAmount = ({
|
|
225
|
+
a,
|
|
226
|
+
t,
|
|
227
|
+
}: {
|
|
228
|
+
a: PolkadotAccount;
|
|
229
|
+
t: Transaction;
|
|
230
|
+
}): BigNumber => {
|
|
231
|
+
let amount = t.amount;
|
|
232
|
+
|
|
233
|
+
if (t.useAllAmount) {
|
|
234
|
+
switch (t.mode) {
|
|
235
|
+
case "send":
|
|
236
|
+
amount = calculateMaxSend(a, t);
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case "bond":
|
|
240
|
+
amount = calculateMaxBond(a, t);
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case "unbond":
|
|
244
|
+
amount = calculateMaxUnbond(a);
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case "rebond":
|
|
248
|
+
amount = calculateMaxRebond(a);
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
default:
|
|
252
|
+
amount = a.spendableBalance.minus(t.fees || 0);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
} else if (t.amount.gt(MAX_AMOUNT_INPUT)) {
|
|
256
|
+
return new BigNumber(MAX_AMOUNT_INPUT);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return amount.lt(0) ? new BigNumber(0) : amount;
|
|
260
|
+
};
|
|
261
|
+
export const getMinimumBalance = (a: Account): BigNumber => {
|
|
262
|
+
const lockedBalance = a.balance.minus(a.spendableBalance);
|
|
263
|
+
return lockedBalance.lte(EXISTENTIAL_DEPOSIT)
|
|
264
|
+
? EXISTENTIAL_DEPOSIT.minus(lockedBalance)
|
|
265
|
+
: new BigNumber(0);
|
|
266
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import dot from "./platformAdapter";
|
|
2
|
+
import { PolkadotTransaction as PlatformTransaction } from "@ledgerhq/live-app-sdk";
|
|
3
|
+
import { FAMILIES } from "@ledgerhq/live-app-sdk";
|
|
4
|
+
import BigNumber from "bignumber.js";
|
|
5
|
+
import { Transaction } from "./types";
|
|
6
|
+
|
|
7
|
+
describe("getPlatformTransactionSignFlowInfos", () => {
|
|
8
|
+
describe("should properly get infos for DOT platform tx", () => {
|
|
9
|
+
it("with most basic tx", () => {
|
|
10
|
+
const dotPlatformTx: PlatformTransaction = {
|
|
11
|
+
family: FAMILIES.POLKADOT,
|
|
12
|
+
amount: new BigNumber(100000),
|
|
13
|
+
recipient: "0xABCDEF",
|
|
14
|
+
mode: "send",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const expectedLiveTx: Partial<Transaction> = {
|
|
18
|
+
family: dotPlatformTx.family,
|
|
19
|
+
amount: dotPlatformTx.amount,
|
|
20
|
+
recipient: dotPlatformTx.recipient,
|
|
21
|
+
mode: dotPlatformTx.mode,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const { canEditFees, hasFeesProvided, liveTx } =
|
|
25
|
+
dot.getPlatformTransactionSignFlowInfos(dotPlatformTx);
|
|
26
|
+
|
|
27
|
+
expect(canEditFees).toBe(false);
|
|
28
|
+
|
|
29
|
+
expect(hasFeesProvided).toBe(false);
|
|
30
|
+
|
|
31
|
+
expect(liveTx).toEqual(expectedLiveTx);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("with era provided", () => {
|
|
35
|
+
const dotPlatformTx: PlatformTransaction = {
|
|
36
|
+
family: FAMILIES.POLKADOT,
|
|
37
|
+
amount: new BigNumber(100000),
|
|
38
|
+
recipient: "0xABCDEF",
|
|
39
|
+
mode: "send",
|
|
40
|
+
era: 1,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const expectedLiveTx: Partial<Transaction> = {
|
|
44
|
+
family: dotPlatformTx.family,
|
|
45
|
+
amount: dotPlatformTx.amount,
|
|
46
|
+
recipient: dotPlatformTx.recipient,
|
|
47
|
+
mode: dotPlatformTx.mode,
|
|
48
|
+
era: `${dotPlatformTx.era}`,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const { canEditFees, hasFeesProvided, liveTx } =
|
|
52
|
+
dot.getPlatformTransactionSignFlowInfos(dotPlatformTx);
|
|
53
|
+
|
|
54
|
+
expect(canEditFees).toBe(false);
|
|
55
|
+
|
|
56
|
+
expect(hasFeesProvided).toBe(false);
|
|
57
|
+
|
|
58
|
+
expect(liveTx).toEqual(expectedLiveTx);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// TODO: to remove once live-app-sdk is depreciated and removed from LL
|
|
2
|
+
|
|
3
|
+
import { PolkadotTransaction as PlatformTransaction } from "@ledgerhq/live-app-sdk";
|
|
4
|
+
import { Transaction } from "./types";
|
|
5
|
+
|
|
6
|
+
const CAN_EDIT_FEES = false;
|
|
7
|
+
|
|
8
|
+
const convertToLiveTransaction = (
|
|
9
|
+
tx: PlatformTransaction
|
|
10
|
+
): Partial<Transaction> => ({
|
|
11
|
+
...tx,
|
|
12
|
+
era: tx.era ? `${tx.era}` : undefined,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const getPlatformTransactionSignFlowInfos = (
|
|
16
|
+
tx: PlatformTransaction
|
|
17
|
+
): {
|
|
18
|
+
canEditFees: boolean;
|
|
19
|
+
hasFeesProvided: boolean;
|
|
20
|
+
liveTx: Partial<Transaction>;
|
|
21
|
+
} => {
|
|
22
|
+
return {
|
|
23
|
+
canEditFees: CAN_EDIT_FEES,
|
|
24
|
+
liveTx: convertToLiveTransaction(tx),
|
|
25
|
+
hasFeesProvided: false,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default { getPlatformTransactionSignFlowInfos };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { cryptoWaitReady } from "@polkadot/util-crypto";
|
|
2
|
+
|
|
3
|
+
let loadingPromise: null | Promise<boolean>;
|
|
4
|
+
|
|
5
|
+
export function loadPolkadotCrypto(): Promise<boolean> {
|
|
6
|
+
if (loadingPromise) return loadingPromise;
|
|
7
|
+
const p = cryptoWaitReady();
|
|
8
|
+
loadingPromise = p;
|
|
9
|
+
return p;
|
|
10
|
+
}
|