@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.
Files changed (181) hide show
  1. package/.eslintrc.js +57 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/CHANGELOG.md +12 -0
  4. package/jest.config.js +10 -0
  5. package/lib/account.d.ts +15 -0
  6. package/lib/account.d.ts.map +1 -0
  7. package/lib/account.js +133 -0
  8. package/lib/account.js.map +1 -0
  9. package/lib/address.d.ts +7 -0
  10. package/lib/address.d.ts.map +1 -0
  11. package/lib/address.js +28 -0
  12. package/lib/address.js.map +1 -0
  13. package/lib/api/bisontrails.d.ts +11 -0
  14. package/lib/api/bisontrails.d.ts.map +1 -0
  15. package/lib/api/bisontrails.js +351 -0
  16. package/lib/api/bisontrails.js.map +1 -0
  17. package/lib/api/common.d.ts +12 -0
  18. package/lib/api/common.d.ts.map +1 -0
  19. package/lib/api/common.js +41 -0
  20. package/lib/api/common.js.map +1 -0
  21. package/lib/api/index.d.ts +3 -0
  22. package/lib/api/index.d.ts.map +1 -0
  23. package/lib/api/index.js +53 -0
  24. package/lib/api/index.js.map +1 -0
  25. package/lib/api/sidecar.d.ts +193 -0
  26. package/lib/api/sidecar.d.ts.map +1 -0
  27. package/lib/api/sidecar.js +844 -0
  28. package/lib/api/sidecar.js.map +1 -0
  29. package/lib/api/sidecar.types.d.ts +163 -0
  30. package/lib/api/sidecar.types.d.ts.map +1 -0
  31. package/lib/api/sidecar.types.js +3 -0
  32. package/lib/api/sidecar.types.js.map +1 -0
  33. package/lib/api/subscan.d.ts +6 -0
  34. package/lib/api/subscan.d.ts.map +1 -0
  35. package/lib/api/subscan.js +375 -0
  36. package/lib/api/subscan.js.map +1 -0
  37. package/lib/api/websocket.d.ts +92 -0
  38. package/lib/api/websocket.d.ts.map +1 -0
  39. package/lib/api/websocket.js +759 -0
  40. package/lib/api/websocket.js.map +1 -0
  41. package/lib/bridge/js.d.ts +6 -0
  42. package/lib/bridge/js.d.ts.map +1 -0
  43. package/lib/bridge/js.js +120 -0
  44. package/lib/bridge/js.js.map +1 -0
  45. package/lib/bridge.integration.test.d.ts +4 -0
  46. package/lib/bridge.integration.test.d.ts.map +1 -0
  47. package/lib/bridge.integration.test.js +795 -0
  48. package/lib/bridge.integration.test.js.map +1 -0
  49. package/lib/cache.d.ts +65 -0
  50. package/lib/cache.d.ts.map +1 -0
  51. package/lib/cache.js +182 -0
  52. package/lib/cache.js.map +1 -0
  53. package/lib/cli-transaction.d.ts +44 -0
  54. package/lib/cli-transaction.d.ts.map +1 -0
  55. package/lib/cli-transaction.js +148 -0
  56. package/lib/cli-transaction.js.map +1 -0
  57. package/lib/deviceTransactionConfig.d.ts +16 -0
  58. package/lib/deviceTransactionConfig.d.ts.map +1 -0
  59. package/lib/deviceTransactionConfig.js +171 -0
  60. package/lib/deviceTransactionConfig.js.map +1 -0
  61. package/lib/errors.d.ts +37 -0
  62. package/lib/errors.d.ts.map +1 -0
  63. package/lib/errors.js +17 -0
  64. package/lib/errors.js.map +1 -0
  65. package/lib/hw-getAddress.d.ts +4 -0
  66. package/lib/hw-getAddress.d.ts.map +1 -0
  67. package/lib/hw-getAddress.js +64 -0
  68. package/lib/hw-getAddress.js.map +1 -0
  69. package/lib/js-buildTransaction.d.ts +25 -0
  70. package/lib/js-buildTransaction.d.ts.map +1 -0
  71. package/lib/js-buildTransaction.js +265 -0
  72. package/lib/js-buildTransaction.js.map +1 -0
  73. package/lib/js-createTransaction.d.ts +9 -0
  74. package/lib/js-createTransaction.d.ts.map +1 -0
  75. package/lib/js-createTransaction.js +22 -0
  76. package/lib/js-createTransaction.js.map +1 -0
  77. package/lib/js-estimateMaxSpendable.d.ts +15 -0
  78. package/lib/js-estimateMaxSpendable.d.ts.map +1 -0
  79. package/lib/js-estimateMaxSpendable.js +89 -0
  80. package/lib/js-estimateMaxSpendable.js.map +1 -0
  81. package/lib/js-getFeesForTransaction.d.ts +14 -0
  82. package/lib/js-getFeesForTransaction.d.ts.map +1 -0
  83. package/lib/js-getFeesForTransaction.js +94 -0
  84. package/lib/js-getFeesForTransaction.js.map +1 -0
  85. package/lib/js-getTransactionStatus.d.ts +18 -0
  86. package/lib/js-getTransactionStatus.d.ts.map +1 -0
  87. package/lib/js-getTransactionStatus.js +316 -0
  88. package/lib/js-getTransactionStatus.js.map +1 -0
  89. package/lib/js-prepareTransaction.d.ts +9 -0
  90. package/lib/js-prepareTransaction.d.ts.map +1 -0
  91. package/lib/js-prepareTransaction.js +83 -0
  92. package/lib/js-prepareTransaction.js.map +1 -0
  93. package/lib/js-signOperation.d.ts +27 -0
  94. package/lib/js-signOperation.d.ts.map +1 -0
  95. package/lib/js-signOperation.js +235 -0
  96. package/lib/js-signOperation.js.map +1 -0
  97. package/lib/js-synchronisation.d.ts +3 -0
  98. package/lib/js-synchronisation.d.ts.map +1 -0
  99. package/lib/js-synchronisation.js +104 -0
  100. package/lib/js-synchronisation.js.map +1 -0
  101. package/lib/logic.d.ts +107 -0
  102. package/lib/logic.d.ts.map +1 -0
  103. package/lib/logic.js +242 -0
  104. package/lib/logic.js.map +1 -0
  105. package/lib/platformAdapter.d.ts +11 -0
  106. package/lib/platformAdapter.d.ts.map +1 -0
  107. package/lib/platformAdapter.js +25 -0
  108. package/lib/platformAdapter.js.map +1 -0
  109. package/lib/platformAdapter.test.d.ts +2 -0
  110. package/lib/platformAdapter.test.d.ts.map +1 -0
  111. package/lib/platformAdapter.test.js +51 -0
  112. package/lib/platformAdapter.test.js.map +1 -0
  113. package/lib/polkadot-crypto.d.ts +2 -0
  114. package/lib/polkadot-crypto.d.ts.map +1 -0
  115. package/lib/polkadot-crypto.js +14 -0
  116. package/lib/polkadot-crypto.js.map +1 -0
  117. package/lib/preload.d.ts +14 -0
  118. package/lib/preload.d.ts.map +1 -0
  119. package/lib/preload.js +173 -0
  120. package/lib/preload.js.map +1 -0
  121. package/lib/serialization.d.ts +4 -0
  122. package/lib/serialization.d.ts.map +1 -0
  123. package/lib/serialization.js +51 -0
  124. package/lib/serialization.js.map +1 -0
  125. package/lib/specs.d.ts +7 -0
  126. package/lib/specs.d.ts.map +1 -0
  127. package/lib/specs.js +274 -0
  128. package/lib/specs.js.map +1 -0
  129. package/lib/speculos-deviceActions.d.ts +4 -0
  130. package/lib/speculos-deviceActions.d.ts.map +1 -0
  131. package/lib/speculos-deviceActions.js +85 -0
  132. package/lib/speculos-deviceActions.js.map +1 -0
  133. package/lib/transaction.d.ts +15 -0
  134. package/lib/transaction.d.ts.map +1 -0
  135. package/lib/transaction.js +51 -0
  136. package/lib/transaction.js.map +1 -0
  137. package/lib/types.d.ts +96 -0
  138. package/lib/types.d.ts.map +1 -0
  139. package/lib/types.js +3 -0
  140. package/lib/types.js.map +1 -0
  141. package/lib/validators.d.ts +3 -0
  142. package/lib/validators.d.ts.map +1 -0
  143. package/lib/validators.js +7 -0
  144. package/lib/validators.js.map +1 -0
  145. package/package.json +95 -0
  146. package/src/account.ts +170 -0
  147. package/src/address.ts +28 -0
  148. package/src/api/bisontrails.ts +343 -0
  149. package/src/api/common.ts +52 -0
  150. package/src/api/index.ts +38 -0
  151. package/src/api/sidecar.ts +735 -0
  152. package/src/api/sidecar.types.ts +162 -0
  153. package/src/api/subscan.ts +362 -0
  154. package/src/api/websocket.ts +535 -0
  155. package/src/bridge/js.ts +81 -0
  156. package/src/bridge.integration.test.ts +764 -0
  157. package/src/cache.ts +202 -0
  158. package/src/cli-transaction.ts +184 -0
  159. package/src/deviceTransactionConfig.ts +209 -0
  160. package/src/errors.ts +39 -0
  161. package/src/hw-getAddress.ts +14 -0
  162. package/src/js-buildTransaction.ts +206 -0
  163. package/src/js-createTransaction.ts +22 -0
  164. package/src/js-estimateMaxSpendable.ts +37 -0
  165. package/src/js-getFeesForTransaction.ts +41 -0
  166. package/src/js-getTransactionStatus.ts +350 -0
  167. package/src/js-prepareTransaction.ts +27 -0
  168. package/src/js-signOperation.ts +232 -0
  169. package/src/js-synchronisation.ts +57 -0
  170. package/src/logic.ts +266 -0
  171. package/src/platformAdapter.test.ts +61 -0
  172. package/src/platformAdapter.ts +29 -0
  173. package/src/polkadot-crypto.ts +10 -0
  174. package/src/preload.ts +148 -0
  175. package/src/serialization.ts +48 -0
  176. package/src/specs.ts +305 -0
  177. package/src/speculos-deviceActions.ts +84 -0
  178. package/src/transaction.ts +64 -0
  179. package/src/types.ts +118 -0
  180. package/src/validators.ts +2 -0
  181. 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
+ };