@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
package/src/preload.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* eslint-disable no-prototype-builtins */
|
|
2
|
+
import { BigNumber } from "bignumber.js";
|
|
3
|
+
import { Observable, Subject } from "rxjs";
|
|
4
|
+
import { log } from "@ledgerhq/logs";
|
|
5
|
+
import { PRELOAD_MAX_AGE } from "./logic";
|
|
6
|
+
import { getRegistry, getMinimumBondBalance } from "./cache";
|
|
7
|
+
import type { PolkadotPreloadData, PolkadotValidator } from "./types";
|
|
8
|
+
import { getStakingProgress, getValidators } from "./validators";
|
|
9
|
+
import { loadPolkadotCrypto } from "./polkadot-crypto";
|
|
10
|
+
|
|
11
|
+
let currentPolkadotPreloadedData: PolkadotPreloadData = {
|
|
12
|
+
validators: [],
|
|
13
|
+
staking: undefined,
|
|
14
|
+
minimumBondBalance: "0",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function fromHydrateValidator(
|
|
18
|
+
validatorRaw: Record<string, any>
|
|
19
|
+
): PolkadotValidator {
|
|
20
|
+
return {
|
|
21
|
+
address: validatorRaw.address,
|
|
22
|
+
identity: validatorRaw.identity,
|
|
23
|
+
nominatorsCount: Number(validatorRaw.nominatorsCount),
|
|
24
|
+
rewardPoints:
|
|
25
|
+
validatorRaw.rewardPoints === null
|
|
26
|
+
? null
|
|
27
|
+
: new BigNumber(validatorRaw.rewardPoints),
|
|
28
|
+
commission: new BigNumber(validatorRaw.commission),
|
|
29
|
+
totalBonded: new BigNumber(validatorRaw.totalBonded),
|
|
30
|
+
selfBonded: new BigNumber(validatorRaw.selfBonded),
|
|
31
|
+
isElected: !!validatorRaw.isElected,
|
|
32
|
+
isOversubscribed: !!validatorRaw.isOversubscribed,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function fromHydratePreloadData(data: any): PolkadotPreloadData {
|
|
37
|
+
let validators = [];
|
|
38
|
+
let staking:
|
|
39
|
+
| {
|
|
40
|
+
electionClosed: boolean;
|
|
41
|
+
activeEra: number;
|
|
42
|
+
maxNominatorRewardedPerValidator: number;
|
|
43
|
+
bondingDuration: number;
|
|
44
|
+
}
|
|
45
|
+
| undefined = undefined;
|
|
46
|
+
let minimumBondBalance = "0";
|
|
47
|
+
|
|
48
|
+
if (typeof data === "object" && data) {
|
|
49
|
+
if (Array.isArray(data.validators)) {
|
|
50
|
+
validators = data.validators.map(fromHydrateValidator);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (data.staking !== null && typeof data.staking === "object") {
|
|
54
|
+
const {
|
|
55
|
+
electionClosed,
|
|
56
|
+
activeEra,
|
|
57
|
+
maxNominatorRewardedPerValidator,
|
|
58
|
+
bondingDuration,
|
|
59
|
+
} = data.staking;
|
|
60
|
+
staking = {
|
|
61
|
+
electionClosed: !!electionClosed,
|
|
62
|
+
activeEra: Number(activeEra),
|
|
63
|
+
maxNominatorRewardedPerValidator:
|
|
64
|
+
Number(maxNominatorRewardedPerValidator) || 128,
|
|
65
|
+
bondingDuration: Number(bondingDuration) || 28,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
data.minimumBondBalance !== null &&
|
|
71
|
+
typeof data.minimumBondBalance === "string"
|
|
72
|
+
) {
|
|
73
|
+
minimumBondBalance = data.minimumBondBalance || "0";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
validators,
|
|
79
|
+
staking,
|
|
80
|
+
minimumBondBalance,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const updates = new Subject<PolkadotPreloadData>();
|
|
85
|
+
export function getCurrentPolkadotPreloadData(): PolkadotPreloadData {
|
|
86
|
+
return currentPolkadotPreloadedData;
|
|
87
|
+
}
|
|
88
|
+
export function setPolkadotPreloadData(data: PolkadotPreloadData) {
|
|
89
|
+
if (data === currentPolkadotPreloadedData) return;
|
|
90
|
+
currentPolkadotPreloadedData = data;
|
|
91
|
+
updates.next(data);
|
|
92
|
+
}
|
|
93
|
+
export function getPolkadotPreloadDataUpdates(): Observable<PolkadotPreloadData> {
|
|
94
|
+
return updates.asObservable();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* load max cache time for the validators
|
|
99
|
+
*/
|
|
100
|
+
export const getPreloadStrategy = () => ({
|
|
101
|
+
preloadMaxAge: PRELOAD_MAX_AGE,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const shouldRefreshValidators = (previousState, currentState) => {
|
|
105
|
+
return !previousState || currentState.activeEra !== previousState.activeEra;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const preload = async (): Promise<PolkadotPreloadData> => {
|
|
109
|
+
await loadPolkadotCrypto();
|
|
110
|
+
await getRegistry(); // ensure registry is already in cache.
|
|
111
|
+
const minimumBondBalance = await getMinimumBondBalance();
|
|
112
|
+
const minimumBondBalanceStr = minimumBondBalance.toString();
|
|
113
|
+
|
|
114
|
+
const currentStakingProgress = await getStakingProgress();
|
|
115
|
+
const { validators: previousValidators, staking: previousStakingProgress } =
|
|
116
|
+
currentPolkadotPreloadedData;
|
|
117
|
+
let validators = previousValidators;
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
!validators ||
|
|
121
|
+
!validators.length ||
|
|
122
|
+
shouldRefreshValidators(previousStakingProgress, currentStakingProgress)
|
|
123
|
+
) {
|
|
124
|
+
log("polkadot/preload", "refreshing polkadot validators...");
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
validators = await getValidators("all");
|
|
128
|
+
} catch (error) {
|
|
129
|
+
log("polkadot/preload", "failed to fetch validators", {
|
|
130
|
+
error,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
validators,
|
|
137
|
+
staking: currentStakingProgress,
|
|
138
|
+
minimumBondBalance: minimumBondBalanceStr,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
export const hydrate = (data: unknown) => {
|
|
142
|
+
const hydrated = fromHydratePreloadData(data);
|
|
143
|
+
log(
|
|
144
|
+
"polkadot/preload",
|
|
145
|
+
"hydrate " + hydrated.validators.length + " polkadot validators"
|
|
146
|
+
);
|
|
147
|
+
setPolkadotPreloadData(hydrated);
|
|
148
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BigNumber } from "bignumber.js";
|
|
2
|
+
import type { PolkadotResourcesRaw, PolkadotResources } from "./types";
|
|
3
|
+
export function toPolkadotResourcesRaw(
|
|
4
|
+
r: PolkadotResources
|
|
5
|
+
): PolkadotResourcesRaw {
|
|
6
|
+
const { nonce, controller, stash } = r;
|
|
7
|
+
return {
|
|
8
|
+
controller,
|
|
9
|
+
stash,
|
|
10
|
+
nonce,
|
|
11
|
+
lockedBalance: r.lockedBalance.toString(),
|
|
12
|
+
unlockedBalance: r.unlockedBalance.toString(),
|
|
13
|
+
unlockingBalance: r.unlockingBalance.toString(),
|
|
14
|
+
unlockings: r.unlockings?.map((u) => ({
|
|
15
|
+
amount: u.amount.toString(),
|
|
16
|
+
completionDate: u.completionDate.toISOString(),
|
|
17
|
+
})),
|
|
18
|
+
nominations: r.nominations?.map((n) => ({
|
|
19
|
+
address: n.address,
|
|
20
|
+
value: n.value.toString(),
|
|
21
|
+
status: n.status,
|
|
22
|
+
})),
|
|
23
|
+
numSlashingSpans: r.numSlashingSpans,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function fromPolkadotResourcesRaw(
|
|
27
|
+
r: PolkadotResourcesRaw
|
|
28
|
+
): PolkadotResources {
|
|
29
|
+
const { nonce, controller, stash } = r;
|
|
30
|
+
return {
|
|
31
|
+
controller,
|
|
32
|
+
stash,
|
|
33
|
+
nonce,
|
|
34
|
+
lockedBalance: new BigNumber(r.lockedBalance),
|
|
35
|
+
unlockedBalance: new BigNumber(r.unlockedBalance),
|
|
36
|
+
unlockingBalance: new BigNumber(r.unlockingBalance),
|
|
37
|
+
unlockings: r.unlockings?.map((u) => ({
|
|
38
|
+
amount: new BigNumber(u.amount),
|
|
39
|
+
completionDate: new Date(u.completionDate),
|
|
40
|
+
})),
|
|
41
|
+
nominations: r.nominations?.map((n) => ({
|
|
42
|
+
address: n.address,
|
|
43
|
+
value: new BigNumber(n.value),
|
|
44
|
+
status: n.status,
|
|
45
|
+
})),
|
|
46
|
+
numSlashingSpans: Number(r.numSlashingSpans) || 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/specs.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import expect from "expect";
|
|
2
|
+
import invariant from "invariant";
|
|
3
|
+
import sampleSize from "lodash/sampleSize";
|
|
4
|
+
import { BigNumber } from "bignumber.js";
|
|
5
|
+
import { getCurrentPolkadotPreloadData } from "./preload";
|
|
6
|
+
import type { PolkadotAccount, PolkadotResources, Transaction } from "./types";
|
|
7
|
+
import {
|
|
8
|
+
getCryptoCurrencyById,
|
|
9
|
+
parseCurrencyUnit,
|
|
10
|
+
} from "@ledgerhq/coin-framework/currencies/index";
|
|
11
|
+
import {
|
|
12
|
+
botTest,
|
|
13
|
+
expectSiblingsHaveSpendablePartGreaterThan,
|
|
14
|
+
genericTestDestination,
|
|
15
|
+
pickSiblings,
|
|
16
|
+
} from "@ledgerhq/coin-framework/bot/specs";
|
|
17
|
+
import type { AppSpec } from "@ledgerhq/coin-framework/bot/types";
|
|
18
|
+
import { toOperationRaw } from "@ledgerhq/coin-framework/account/index";
|
|
19
|
+
import {
|
|
20
|
+
canBond,
|
|
21
|
+
canUnbond,
|
|
22
|
+
canNominate,
|
|
23
|
+
isFirstBond,
|
|
24
|
+
hasMinimumBondBalance,
|
|
25
|
+
getMinimumBalance,
|
|
26
|
+
} from "./logic";
|
|
27
|
+
import { DeviceModelId } from "@ledgerhq/devices";
|
|
28
|
+
import { acceptTransaction } from "./speculos-deviceActions";
|
|
29
|
+
|
|
30
|
+
const maxAccounts = 32;
|
|
31
|
+
const currency = getCryptoCurrencyById("polkadot");
|
|
32
|
+
// FIXME Should be replaced with EXISTENTIAL_DEPOSIT_RECOMMENDED_MARGIN in logic.ts
|
|
33
|
+
const POLKADOT_MIN_SAFE = parseCurrencyUnit(currency.units[0], "0.1");
|
|
34
|
+
// FIXME Should be replaced with EXISTENTIAL_DEPOSIT in logic.ts
|
|
35
|
+
const EXISTENTIAL_DEPOSIT = parseCurrencyUnit(currency.units[0], "1.0");
|
|
36
|
+
const MIN_LOCKED_BALANCE_REQ = parseCurrencyUnit(currency.units[0], "1.0");
|
|
37
|
+
|
|
38
|
+
const polkadot: AppSpec<Transaction> = {
|
|
39
|
+
name: "Polkadot",
|
|
40
|
+
currency: getCryptoCurrencyById("polkadot"),
|
|
41
|
+
appQuery: {
|
|
42
|
+
model: DeviceModelId.nanoS,
|
|
43
|
+
appName: "Polkadot",
|
|
44
|
+
},
|
|
45
|
+
testTimeout: 2 * 60 * 1000,
|
|
46
|
+
genericDeviceAction: acceptTransaction,
|
|
47
|
+
minViableAmount: POLKADOT_MIN_SAFE,
|
|
48
|
+
transactionCheck: ({ maxSpendable }) => {
|
|
49
|
+
invariant(maxSpendable.gt(POLKADOT_MIN_SAFE), "balance is too low");
|
|
50
|
+
},
|
|
51
|
+
test: ({ operation, optimisticOperation }) => {
|
|
52
|
+
const opExpected: Record<string, any> = toOperationRaw({
|
|
53
|
+
...optimisticOperation,
|
|
54
|
+
});
|
|
55
|
+
delete opExpected.value;
|
|
56
|
+
delete opExpected.fee;
|
|
57
|
+
delete opExpected.date;
|
|
58
|
+
delete opExpected.blockHash;
|
|
59
|
+
delete opExpected.blockHeight;
|
|
60
|
+
botTest("optimistic operation matches", () =>
|
|
61
|
+
expect(toOperationRaw(operation)).toMatchObject(opExpected)
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
mutations: [
|
|
65
|
+
{
|
|
66
|
+
name: "send 50%~",
|
|
67
|
+
maxRun: 4,
|
|
68
|
+
testDestination: genericTestDestination,
|
|
69
|
+
transaction: ({ account, siblings, bridge }) => {
|
|
70
|
+
invariant(
|
|
71
|
+
(account as PolkadotAccount).polkadotResources,
|
|
72
|
+
"polkadot resource"
|
|
73
|
+
);
|
|
74
|
+
const sibling = pickSiblings(siblings, maxAccounts);
|
|
75
|
+
let amount = account.spendableBalance
|
|
76
|
+
.minus(EXISTENTIAL_DEPOSIT)
|
|
77
|
+
.div(1.9 + 0.2 * Math.random())
|
|
78
|
+
.integerValue();
|
|
79
|
+
|
|
80
|
+
if (!sibling.used && amount.lt(EXISTENTIAL_DEPOSIT)) {
|
|
81
|
+
invariant(
|
|
82
|
+
account.spendableBalance.gt(
|
|
83
|
+
EXISTENTIAL_DEPOSIT.plus(POLKADOT_MIN_SAFE)
|
|
84
|
+
),
|
|
85
|
+
"send is too low to activate account"
|
|
86
|
+
);
|
|
87
|
+
amount = EXISTENTIAL_DEPOSIT;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const minimumBalanceExistential = getMinimumBalance(account);
|
|
91
|
+
const leftover = account.spendableBalance.minus(
|
|
92
|
+
amount.plus(POLKADOT_MIN_SAFE)
|
|
93
|
+
);
|
|
94
|
+
if (
|
|
95
|
+
minimumBalanceExistential.gt(0) &&
|
|
96
|
+
leftover.lt(minimumBalanceExistential) &&
|
|
97
|
+
leftover.gt(0)
|
|
98
|
+
) {
|
|
99
|
+
throw new Error("risk of PolkadotDoMaxSendInstead");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
transaction: bridge.createTransaction(account),
|
|
104
|
+
updates: [
|
|
105
|
+
{
|
|
106
|
+
recipient: sibling.freshAddress,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
amount,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "send max",
|
|
117
|
+
maxRun: 1,
|
|
118
|
+
testDestination: genericTestDestination,
|
|
119
|
+
transaction: ({ account, siblings, bridge }) => {
|
|
120
|
+
invariant(
|
|
121
|
+
(account as PolkadotAccount).polkadotResources,
|
|
122
|
+
"polkadot resources"
|
|
123
|
+
);
|
|
124
|
+
const sibling = pickSiblings(siblings, maxAccounts);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
transaction: bridge.createTransaction(account),
|
|
128
|
+
updates: [
|
|
129
|
+
{
|
|
130
|
+
recipient: sibling.freshAddress,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
useAllAmount: true,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "bond - bondExtra",
|
|
141
|
+
maxRun: 1,
|
|
142
|
+
transaction: ({ siblings, account, bridge }) => {
|
|
143
|
+
expectSiblingsHaveSpendablePartGreaterThan(siblings, 0.5);
|
|
144
|
+
|
|
145
|
+
invariant((account as PolkadotAccount).polkadotResources, "polkadot");
|
|
146
|
+
invariant(canBond(account), "can't bond");
|
|
147
|
+
invariant(
|
|
148
|
+
hasMinimumBondBalance(account as PolkadotAccount),
|
|
149
|
+
"not enough balance to bond"
|
|
150
|
+
);
|
|
151
|
+
const options: {
|
|
152
|
+
recipient?: string;
|
|
153
|
+
rewardDestination?: string;
|
|
154
|
+
amount?: BigNumber;
|
|
155
|
+
}[] = [];
|
|
156
|
+
|
|
157
|
+
if (isFirstBond(account as PolkadotAccount)) {
|
|
158
|
+
invariant(
|
|
159
|
+
account.balance.gt(EXISTENTIAL_DEPOSIT.plus(POLKADOT_MIN_SAFE)),
|
|
160
|
+
"cant cover fee + bonding amount"
|
|
161
|
+
);
|
|
162
|
+
options.push({
|
|
163
|
+
recipient: account.freshAddress,
|
|
164
|
+
rewardDestination: "Stash",
|
|
165
|
+
});
|
|
166
|
+
options.push({
|
|
167
|
+
amount: EXISTENTIAL_DEPOSIT,
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
invariant(
|
|
171
|
+
account.spendableBalance.gt(POLKADOT_MIN_SAFE),
|
|
172
|
+
"cant cover fee + bonding amount"
|
|
173
|
+
);
|
|
174
|
+
options.push({
|
|
175
|
+
amount: new BigNumber(100000),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
transaction: bridge.createTransaction(account),
|
|
181
|
+
updates: [
|
|
182
|
+
{
|
|
183
|
+
mode: "bond",
|
|
184
|
+
},
|
|
185
|
+
...options,
|
|
186
|
+
],
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "unbond",
|
|
192
|
+
maxRun: 2,
|
|
193
|
+
transaction: ({ account, bridge }) => {
|
|
194
|
+
const { polkadotResources } = account as PolkadotAccount;
|
|
195
|
+
invariant(polkadotResources, "polkadot");
|
|
196
|
+
invariant(canUnbond(account as PolkadotAccount), "can't unbond");
|
|
197
|
+
invariant(
|
|
198
|
+
account.spendableBalance.gt(POLKADOT_MIN_SAFE),
|
|
199
|
+
"can't cover fee"
|
|
200
|
+
);
|
|
201
|
+
const amount = (polkadotResources as PolkadotResources).lockedBalance
|
|
202
|
+
.minus((polkadotResources as PolkadotResources).unlockingBalance)
|
|
203
|
+
.times(0.2);
|
|
204
|
+
return {
|
|
205
|
+
transaction: bridge.createTransaction(account),
|
|
206
|
+
updates: [
|
|
207
|
+
{
|
|
208
|
+
mode: "unbond",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
amount,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "rebond",
|
|
219
|
+
maxRun: 1,
|
|
220
|
+
transaction: ({ account, bridge }) => {
|
|
221
|
+
const { polkadotResources } = account as PolkadotAccount;
|
|
222
|
+
invariant(polkadotResources, "polkadot");
|
|
223
|
+
invariant(
|
|
224
|
+
polkadotResources?.unlockingBalance.gt(MIN_LOCKED_BALANCE_REQ),
|
|
225
|
+
"can't rebond"
|
|
226
|
+
);
|
|
227
|
+
invariant(
|
|
228
|
+
account.spendableBalance.gt(POLKADOT_MIN_SAFE),
|
|
229
|
+
"can't cover fee"
|
|
230
|
+
);
|
|
231
|
+
const amount = BigNumber.maximum(
|
|
232
|
+
(polkadotResources as PolkadotResources).unlockingBalance.times(0.2),
|
|
233
|
+
MIN_LOCKED_BALANCE_REQ
|
|
234
|
+
);
|
|
235
|
+
return {
|
|
236
|
+
transaction: bridge.createTransaction(account),
|
|
237
|
+
updates: [
|
|
238
|
+
{
|
|
239
|
+
mode: "rebond",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
amount,
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: "nominate",
|
|
250
|
+
maxRun: 1,
|
|
251
|
+
transaction: ({ account, bridge }) => {
|
|
252
|
+
invariant((account as PolkadotAccount).polkadotResources, "polkadot");
|
|
253
|
+
invariant(canNominate(account as PolkadotAccount), "can't nominate");
|
|
254
|
+
invariant(
|
|
255
|
+
account.spendableBalance.gt(POLKADOT_MIN_SAFE),
|
|
256
|
+
"cant cover fee"
|
|
257
|
+
);
|
|
258
|
+
const data = getCurrentPolkadotPreloadData();
|
|
259
|
+
const validators = sampleSize(
|
|
260
|
+
data.validators.map((v) => v.address),
|
|
261
|
+
2
|
|
262
|
+
);
|
|
263
|
+
return {
|
|
264
|
+
transaction: bridge.createTransaction(account),
|
|
265
|
+
updates: [
|
|
266
|
+
{
|
|
267
|
+
mode: "nominate",
|
|
268
|
+
},
|
|
269
|
+
...validators.map((_, i) => ({
|
|
270
|
+
validators: validators.slice(0, i + 1),
|
|
271
|
+
})),
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "withdraw",
|
|
278
|
+
maxRun: 2,
|
|
279
|
+
transaction: ({ account, bridge }) => {
|
|
280
|
+
const { polkadotResources } = account as PolkadotAccount;
|
|
281
|
+
invariant(polkadotResources, "polkadot");
|
|
282
|
+
invariant(
|
|
283
|
+
polkadotResources?.unlockedBalance.gt(0),
|
|
284
|
+
"nothing to withdraw"
|
|
285
|
+
);
|
|
286
|
+
invariant(
|
|
287
|
+
account.spendableBalance.gt(POLKADOT_MIN_SAFE),
|
|
288
|
+
"can't cover fee"
|
|
289
|
+
);
|
|
290
|
+
return {
|
|
291
|
+
transaction: bridge.createTransaction(account),
|
|
292
|
+
updates: [
|
|
293
|
+
{
|
|
294
|
+
mode: "withdrawUnbonded",
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export default {
|
|
304
|
+
polkadot,
|
|
305
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { DeviceAction } from "@ledgerhq/coin-framework/bot/types";
|
|
2
|
+
import type { PolkadotAccount, Transaction } from "./types";
|
|
3
|
+
import {
|
|
4
|
+
deviceActionFlow,
|
|
5
|
+
formatDeviceAmount,
|
|
6
|
+
SpeculosButton,
|
|
7
|
+
} from "@ledgerhq/coin-framework/bot/specs";
|
|
8
|
+
|
|
9
|
+
export const acceptTransaction: DeviceAction<Transaction, any> =
|
|
10
|
+
deviceActionFlow({
|
|
11
|
+
steps: [
|
|
12
|
+
{
|
|
13
|
+
title: "Staking",
|
|
14
|
+
button: SpeculosButton.RIGHT,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
title: "Balances",
|
|
18
|
+
button: SpeculosButton.RIGHT,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: "Dest",
|
|
22
|
+
button: SpeculosButton.RIGHT,
|
|
23
|
+
expectedValue: ({ transaction }) => transaction.recipient,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: "Amount",
|
|
27
|
+
button: SpeculosButton.RIGHT,
|
|
28
|
+
expectedValue: ({ account, status }) => {
|
|
29
|
+
return formatDeviceAmount(account.currency, status.amount, {
|
|
30
|
+
forceFloating: true,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: "Chain",
|
|
36
|
+
button: SpeculosButton.RIGHT,
|
|
37
|
+
expectedValue: () => "Polkadot",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: "Nonce",
|
|
41
|
+
button: SpeculosButton.RIGHT,
|
|
42
|
+
expectedValue: ({ account }) =>
|
|
43
|
+
(
|
|
44
|
+
(account as PolkadotAccount).polkadotResources?.nonce || 0
|
|
45
|
+
).toString(),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
title: "Tip",
|
|
49
|
+
button: SpeculosButton.RIGHT,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "Controller",
|
|
53
|
+
button: SpeculosButton.RIGHT,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: "Payee",
|
|
57
|
+
button: SpeculosButton.RIGHT,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: "Max additional",
|
|
61
|
+
button: SpeculosButton.RIGHT,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "Targets",
|
|
65
|
+
button: SpeculosButton.RIGHT,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
title: "Era Phase",
|
|
69
|
+
button: SpeculosButton.RIGHT,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
title: "Era Period",
|
|
73
|
+
button: SpeculosButton.RIGHT,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
title: "Block",
|
|
77
|
+
button: SpeculosButton.RIGHT,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
title: "APPROVE",
|
|
81
|
+
button: SpeculosButton.BOTH,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Transaction, TransactionRaw } from "./types";
|
|
2
|
+
import { BigNumber } from "bignumber.js";
|
|
3
|
+
import {
|
|
4
|
+
formatTransactionStatusCommon as formatTransactionStatus,
|
|
5
|
+
fromTransactionCommonRaw,
|
|
6
|
+
fromTransactionStatusRawCommon as fromTransactionStatusRaw,
|
|
7
|
+
toTransactionCommonRaw,
|
|
8
|
+
toTransactionStatusRawCommon as toTransactionStatusRaw,
|
|
9
|
+
} from "@ledgerhq/coin-framework/transaction/common";
|
|
10
|
+
import type { Account } from "@ledgerhq/types-live";
|
|
11
|
+
import { getAccountUnit } from "@ledgerhq/coin-framework/account/index";
|
|
12
|
+
import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
|
|
13
|
+
export const formatTransaction = (
|
|
14
|
+
{ mode, amount, recipient, validators, useAllAmount }: Transaction,
|
|
15
|
+
account: Account
|
|
16
|
+
): string => `
|
|
17
|
+
${mode.toUpperCase()} ${
|
|
18
|
+
useAllAmount
|
|
19
|
+
? "MAX"
|
|
20
|
+
: amount.isZero()
|
|
21
|
+
? ""
|
|
22
|
+
: " " +
|
|
23
|
+
formatCurrencyUnit(getAccountUnit(account), amount, {
|
|
24
|
+
showCode: true,
|
|
25
|
+
disableRounding: true,
|
|
26
|
+
})
|
|
27
|
+
}${recipient ? `\nTO ${recipient}` : ""}${
|
|
28
|
+
!validators ? "" : validators.join("\n")
|
|
29
|
+
}`;
|
|
30
|
+
export const fromTransactionRaw = (tr: TransactionRaw): Transaction => {
|
|
31
|
+
const common = fromTransactionCommonRaw(tr);
|
|
32
|
+
return {
|
|
33
|
+
...common,
|
|
34
|
+
family: tr.family,
|
|
35
|
+
mode: tr.mode,
|
|
36
|
+
fees: tr.fees ? new BigNumber(tr.fees) : null,
|
|
37
|
+
validators: tr.validators,
|
|
38
|
+
era: tr.era,
|
|
39
|
+
rewardDestination: tr.rewardDestination,
|
|
40
|
+
numSlashingSpans: tr.numSlashingSpans,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export const toTransactionRaw = (t: Transaction): TransactionRaw => {
|
|
44
|
+
const common = toTransactionCommonRaw(t);
|
|
45
|
+
return {
|
|
46
|
+
...common,
|
|
47
|
+
family: t.family,
|
|
48
|
+
mode: t.mode,
|
|
49
|
+
fees: t.fees?.toString() || null,
|
|
50
|
+
validators: t.validators,
|
|
51
|
+
era: t.era,
|
|
52
|
+
rewardDestination: t.rewardDestination,
|
|
53
|
+
numSlashingSpans: t.numSlashingSpans,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
formatTransaction,
|
|
59
|
+
fromTransactionRaw,
|
|
60
|
+
toTransactionRaw,
|
|
61
|
+
fromTransactionStatusRaw,
|
|
62
|
+
toTransactionStatusRaw,
|
|
63
|
+
formatTransactionStatus,
|
|
64
|
+
};
|