@ledgerhq/coin-algorand 0.2.0-next.0

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.
@@ -0,0 +1,312 @@
1
+ import {
2
+ InvalidAddressBecauseDestinationIsAlsoSource,
3
+ NotEnoughBalance,
4
+ NotEnoughBalanceBecauseDestinationNotCreated,
5
+ } from "@ledgerhq/errors";
6
+ import type { CurrenciesData, DatasetTest } from "@ledgerhq/types-live";
7
+ import { BigNumber } from "bignumber.js";
8
+ import { AlgorandASANotOptInInRecipient } from "./errors";
9
+ import type { AlgorandTransaction, Transaction } from "./types";
10
+
11
+ const algorand: CurrenciesData<Transaction> = {
12
+ FIXME_ignoreAccountFields: [
13
+ "algorandResources.rewards", // We cant keep track of this since it's always moving
14
+ "balance", // Rewards are included, same as above
15
+ "spendableBalance", // Same since the rewards are included here too
16
+ ],
17
+ scanAccounts: [
18
+ {
19
+ name: "algorand seed 1",
20
+ apdus: `
21
+ => 800300000480000000
22
+ <= c8b672d16c497bb097a48f09a9cccf0c4c7d6391acb7a4e7cd3f236fadbef9c49000
23
+ => 800300000480000000
24
+ <= c8b672d16c497bb097a48f09a9cccf0c4c7d6391acb7a4e7cd3f236fadbef9c49000
25
+ => 800300000480000001
26
+ <= 21b3068ca2b9a3b0b1fc68d9ecbd61663f6957c68a9c767aa14a8abb437180e69000
27
+ => 800300000480000002
28
+ <= cc2b54ea5cbda5de6957086a8435c43a06e26559b2bfaebec56b748ec5a2a0519000
29
+ => 800300000480000003
30
+ <= 6104eb314f51f4db5733976bd8c066297019ebaa6adcf39b4aa318d553c571cc9000
31
+ => 800300000480000004
32
+ <= 6549e16ee1e242aba9d116dab058b6e33e4c6d801d3cbfe860195fc6e42940319000
33
+ `,
34
+ },
35
+ ],
36
+ accounts: [
37
+ {
38
+ FIXME_tests: ["balance is sum of ops"],
39
+ // Rewards issues
40
+ raw: {
41
+ id: "js:2:algorand:c8b672d16c497bb097a48f09a9cccf0c4c7d6391acb7a4e7cd3f236fadbef9c4:",
42
+ seedIdentifier:
43
+ "c8b672d16c497bb097a48f09a9cccf0c4c7d6391acb7a4e7cd3f236fadbef9c4",
44
+ name: "Algorand 1",
45
+ xpub: "c8b672d16c497bb097a48f09a9cccf0c4c7d6391acb7a4e7cd3f236fadbef9c4",
46
+ derivationMode: "",
47
+ index: 0,
48
+ freshAddress:
49
+ "ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q",
50
+ freshAddressPath: "44'/283'/0'/0/0",
51
+ freshAddresses: [
52
+ {
53
+ address:
54
+ "ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q",
55
+ derivationPath: "44'/283'/0'/0/0",
56
+ },
57
+ ],
58
+ unitMagnitude: 6,
59
+ blockHeight: 8518049,
60
+ operations: [],
61
+ pendingOperations: [],
62
+ currencyId: "algorand",
63
+ lastSyncDate: "",
64
+ balance: "1167089",
65
+ spendableBalance: "567089",
66
+ subAccounts: [],
67
+ },
68
+ transactions: [
69
+ {
70
+ name: "Same as Recipient",
71
+ transaction: (t) => ({
72
+ ...t,
73
+ amount: new BigNumber(100),
74
+ recipient:
75
+ "ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q",
76
+ }),
77
+ expectedStatus: {
78
+ errors: {
79
+ recipient: new InvalidAddressBecauseDestinationIsAlsoSource(),
80
+ },
81
+ warnings: {},
82
+ },
83
+ },
84
+ {
85
+ name: "Account creation minimum amount too low",
86
+ transaction: (t) => ({
87
+ ...t,
88
+ amount: new BigNumber("100"),
89
+ recipient:
90
+ "MVE6C3XB4JBKXKORC3NLAWFW4M7EY3MADU6L72DADFP4NZBJIAYXGSLN3Y",
91
+ }),
92
+ expectedStatus: {
93
+ errors: {
94
+ amount: new NotEnoughBalanceBecauseDestinationNotCreated(),
95
+ },
96
+ warnings: {},
97
+ },
98
+ },
99
+ {
100
+ name: "send",
101
+ transaction: (t) => ({
102
+ ...t,
103
+ amount: new BigNumber("1000"),
104
+ recipient:
105
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
106
+ }),
107
+ expectedStatus: {
108
+ errors: {},
109
+ warnings: {},
110
+ },
111
+ },
112
+ {
113
+ name: "send amount more than fees + base reserve",
114
+ transaction: (t, account) => ({
115
+ ...t,
116
+ amount: account.balance,
117
+ recipient:
118
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
119
+ }),
120
+ expectedStatus: {
121
+ errors: {
122
+ amount: new NotEnoughBalance(),
123
+ },
124
+ warnings: {},
125
+ },
126
+ },
127
+ {
128
+ name: "send more than base reserve",
129
+ transaction: (t, account) => ({
130
+ ...t,
131
+ amount: account.balance.minus("100"),
132
+ recipient:
133
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
134
+ }),
135
+ expectedStatus: {
136
+ errors: {
137
+ amount: new NotEnoughBalance(),
138
+ },
139
+ warnings: {},
140
+ },
141
+ },
142
+ {
143
+ name: "optIn",
144
+ transaction: (t) => ({
145
+ ...t,
146
+ mode: "optIn",
147
+ assetId: "algorand/asa/31231",
148
+ amount: new BigNumber("1000"),
149
+ recipient:
150
+ "ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q",
151
+ }),
152
+ expectedStatus: {
153
+ errors: {},
154
+ warnings: {},
155
+ amount: new BigNumber("0"),
156
+ },
157
+ },
158
+ {
159
+ name: "Can't send ASA to an address that didn't Opt-in",
160
+ transaction: (t) => ({
161
+ ...t,
162
+ subAccountId:
163
+ "js:2:algorand:ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q:+312769",
164
+ amount: new BigNumber("1000"),
165
+ recipient:
166
+ "ZQVVJ2S4XWS542KXBBVIINOEHIDOEZKZWK725PWFNN2I5RNCUBI53RT2EY",
167
+ }),
168
+ expectedStatus: {
169
+ errors: {
170
+ recipient: new AlgorandASANotOptInInRecipient(),
171
+ },
172
+ warnings: {},
173
+ },
174
+ },
175
+ {
176
+ name: "send Token",
177
+ transaction: (t) => ({
178
+ ...t,
179
+ subAccountId:
180
+ "js:2:algorand:ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q:+312769",
181
+ amount: new BigNumber("1000"),
182
+ recipient:
183
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
184
+ }),
185
+ expectedStatus: {
186
+ errors: {},
187
+ warnings: {},
188
+ amount: new BigNumber("1000"),
189
+ },
190
+ },
191
+ {
192
+ name: "send Token - more than available",
193
+ transaction: (t) => ({
194
+ ...t,
195
+ subAccountId:
196
+ "js:2:algorand:ZC3HFULMJF53BF5ER4E2TTGPBRGH2Y4RVS32JZ6NH4RW7LN67HCE6UBS3Q:+312769",
197
+ amount: new BigNumber("100000000000"),
198
+ recipient:
199
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
200
+ }),
201
+ expectedStatus: {
202
+ errors: {
203
+ amount: new NotEnoughBalance(),
204
+ },
205
+ warnings: {},
206
+ },
207
+ },
208
+ {
209
+ name: "send max",
210
+ transaction: (t) => ({
211
+ ...t,
212
+ recipient:
213
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
214
+ useAllAmount: true,
215
+ }),
216
+ expectedStatus: (account, _, status) => {
217
+ return {
218
+ amount: account.spendableBalance.minus(status.estimatedFees),
219
+ warnings: {},
220
+ errors: {},
221
+ };
222
+ },
223
+ },
224
+ ],
225
+ },
226
+ {
227
+ raw: {
228
+ id: "js:2:algorand:MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI:",
229
+ seedIdentifier:
230
+ "c8b672d16c497bb097a48f09a9cccf0c4c7d6391acb7a4e7cd3f236fadbef9c4",
231
+ xpub: "6104eb314f51f4db5733976bd8c066297019ebaa6adcf39b4aa318d553c571cc",
232
+ name: "Algorand 4",
233
+ derivationMode: "",
234
+ index: 3,
235
+ freshAddress:
236
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
237
+ freshAddressPath: "44'/283'/3'/0/0",
238
+ freshAddresses: [
239
+ {
240
+ address:
241
+ "MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI",
242
+ derivationPath: "44'/283'/3'/0/0",
243
+ },
244
+ ],
245
+ unitMagnitude: 6,
246
+ blockHeight: 8518189,
247
+ balance: "0",
248
+ spendableBalance: "0",
249
+ currencyId: "algorand",
250
+ lastSyncDate: "",
251
+ operations: [],
252
+ pendingOperations: [],
253
+ },
254
+ transactions: [
255
+ {
256
+ name: "Can't send funds if balance too low",
257
+ transaction: (t) => ({
258
+ ...t,
259
+ amount: new BigNumber("1000"),
260
+ recipient:
261
+ "YWZPDCL5XQPCPGBXKB7KAG7YF2QGCGEX37YTSM55CPEPHKNE2ZSKRAXNQ4",
262
+ }),
263
+ expectedStatus: {
264
+ errors: {},
265
+ warnings: {},
266
+ },
267
+ },
268
+ {
269
+ name: "Can't send ASA if Algo balance too low",
270
+ transaction: (t) => ({
271
+ ...t,
272
+ subAccountId:
273
+ "js:2:algorand:MECOWMKPKH2NWVZTS5V5RQDGFFYBT25KNLOPHG2KUMMNKU6FOHGJT24WBI:+312769",
274
+ amount: new BigNumber("1000000"),
275
+ recipient:
276
+ "YWZPDCL5XQPCPGBXKB7KAG7YF2QGCGEX37YTSM55CPEPHKNE2ZSKRAXNQ4",
277
+ }),
278
+ expectedStatus: {
279
+ errors: {
280
+ amount: new NotEnoughBalance(),
281
+ },
282
+ warnings: {},
283
+ },
284
+ },
285
+ ],
286
+ },
287
+ ],
288
+ };
289
+
290
+ export const dataset: DatasetTest<AlgorandTransaction> = {
291
+ implementations: ["js"],
292
+ currencies: {
293
+ algorand,
294
+ },
295
+ };
296
+
297
+ describe("Algorand bridge", () => {
298
+ test.todo(
299
+ "This is an empty test to make jest command pass. Remove it once there is a real test."
300
+ );
301
+ });
302
+
303
+ /**
304
+ * NOTE: if tests are added to this file,
305
+ * like done in libs/coin-polkadot/src/bridge.integration.test.ts for example,
306
+ * this file fill need to be imported in ledger-live-common
307
+ * libs/ledger-live-common/src/families/algorand/bridge.integration.test.ts
308
+ * like done for polkadot.
309
+ * cf.
310
+ * - libs/coin-polkadot/src/bridge.integration.test.ts
311
+ * - libs/ledger-live-common/src/families/polkadot/bridge.integration.test.ts
312
+ */
@@ -0,0 +1,98 @@
1
+ import { encode as msgpackEncode } from "algo-msgpack-with-bigint";
2
+ import type {
3
+ EncodedSignedTransaction as AlgoSignedTransactionPayload,
4
+ Transaction as AlgoTransaction,
5
+ EncodedTransaction as AlgoTransactionPayload,
6
+ } from "algosdk";
7
+ import {
8
+ makeAssetTransferTxnWithSuggestedParams,
9
+ makePaymentTxnWithSuggestedParams,
10
+ } from "algosdk";
11
+
12
+ import type { Account } from "@ledgerhq/types-live";
13
+ import { AlgorandAPI } from "./api";
14
+ import { extractTokenId } from "./tokens";
15
+ import type { Transaction } from "./types";
16
+
17
+ export const buildTransactionPayload =
18
+ (algorandAPI: AlgorandAPI) =>
19
+ async (
20
+ account: Account,
21
+ transaction: Transaction
22
+ ): Promise<AlgoTransactionPayload> => {
23
+ const { amount, recipient, mode, memo, assetId, subAccountId } =
24
+ transaction;
25
+ const subAccount = subAccountId
26
+ ? account.subAccounts &&
27
+ account.subAccounts.find((t) => t.id === subAccountId)
28
+ : null;
29
+
30
+ const note = memo ? new TextEncoder().encode(memo) : undefined;
31
+
32
+ const params = await algorandAPI.getTransactionParams();
33
+
34
+ let algoTxn: AlgoTransaction;
35
+ if (subAccount || (assetId && mode === "optIn")) {
36
+ const targetAssetId =
37
+ subAccount && subAccount.type === "TokenAccount"
38
+ ? extractTokenId(subAccount.token.id)
39
+ : assetId
40
+ ? extractTokenId(assetId)
41
+ : "";
42
+
43
+ if (!targetAssetId) {
44
+ throw new Error("Token Asset Id not found");
45
+ }
46
+
47
+ algoTxn = makeAssetTransferTxnWithSuggestedParams(
48
+ account.freshAddress,
49
+ recipient,
50
+ undefined,
51
+ undefined,
52
+ amount.toNumber(),
53
+ note,
54
+ Number(targetAssetId),
55
+ params,
56
+ undefined
57
+ );
58
+ } else {
59
+ algoTxn = makePaymentTxnWithSuggestedParams(
60
+ account.freshAddress,
61
+ recipient,
62
+ amount.toNumber(),
63
+ undefined,
64
+ note,
65
+ params
66
+ );
67
+ }
68
+
69
+ // Bit of safety: set tx validity to the next 1000 blocks
70
+ algoTxn.firstRound = params.lastRound;
71
+ algoTxn.lastRound = params.lastRound + 1000;
72
+
73
+ // Flaw in the SDK: payload isn't sorted, but it needs to be for msgPack encoding
74
+ const sorted = Object.fromEntries(
75
+ Object.entries(algoTxn.get_obj_for_encoding()).sort()
76
+ ) as AlgoTransactionPayload;
77
+
78
+ return sorted;
79
+ };
80
+
81
+ export const encodeToSign = (payload: AlgoTransactionPayload): string => {
82
+ const msgPackEncoded = msgpackEncode(payload);
83
+
84
+ return Buffer.from(msgPackEncoded).toString("hex");
85
+ };
86
+
87
+ export const encodeToBroadcast = (
88
+ payload: AlgoTransactionPayload,
89
+ signature: Buffer
90
+ ): Buffer => {
91
+ const signedPayload: AlgoSignedTransactionPayload = {
92
+ sig: signature,
93
+ txn: payload,
94
+ };
95
+ const msgPackEncoded = msgpackEncode(signedPayload);
96
+
97
+ return Buffer.from(msgPackEncoded);
98
+ };
@@ -0,0 +1,124 @@
1
+ import { getAccountCurrency } from "@ledgerhq/coin-framework/account/index";
2
+ import type {
3
+ Account,
4
+ AccountLike,
5
+ AccountLikeArray,
6
+ } from "@ledgerhq/types-live";
7
+ import invariant from "invariant";
8
+ import flatMap from "lodash/flatMap";
9
+ import { extractTokenId } from "./tokens";
10
+ import type { AlgorandAccount, Transaction } from "./types";
11
+
12
+ const options = [
13
+ {
14
+ name: "mode",
15
+ type: String,
16
+ desc: "mode of transaction: send, optIn, claimReward",
17
+ },
18
+ {
19
+ name: "fees",
20
+ type: String,
21
+ desc: "how much fees",
22
+ },
23
+ {
24
+ name: "gasLimit",
25
+ type: String,
26
+ desc: "how much gasLimit. default is estimated with the recipient",
27
+ },
28
+ {
29
+ name: "memo",
30
+ type: String,
31
+ desc: "set a memo",
32
+ },
33
+ {
34
+ name: "token",
35
+ alias: "t",
36
+ type: String,
37
+ desc: "use an token account children of the account",
38
+ multiple: true,
39
+ },
40
+ ];
41
+
42
+ function inferAccounts(
43
+ account: Account,
44
+ opts: Record<string, any>
45
+ ): AccountLikeArray {
46
+ invariant(account.currency.family === "algorand", "algorand family");
47
+
48
+ if (!opts.token || opts.mode === "optIn") {
49
+ const accounts: Account[] = [account];
50
+ return accounts;
51
+ }
52
+
53
+ return opts.token.map((token?: string) => {
54
+ const subAccounts = account.subAccounts || [];
55
+
56
+ if (token) {
57
+ const subAccount = subAccounts.find((t) => {
58
+ const currency = getAccountCurrency(t);
59
+ return (
60
+ token.toLowerCase() === currency.ticker.toLowerCase() ||
61
+ token.toLowerCase() === extractTokenId(currency.id)
62
+ );
63
+ });
64
+
65
+ if (!subAccount) {
66
+ throw new Error(
67
+ "token account '" +
68
+ token +
69
+ "' not found. Available: " +
70
+ subAccounts.map((t) => getAccountCurrency(t).ticker).join(", ")
71
+ );
72
+ }
73
+
74
+ return subAccount;
75
+ }
76
+ });
77
+ }
78
+
79
+ function inferTransactions(
80
+ transactions: Array<{
81
+ account: AccountLike;
82
+ transaction: Transaction;
83
+ }>,
84
+ opts: Record<string, any>,
85
+ { inferAmount }: any
86
+ ): Transaction[] {
87
+ return flatMap(transactions, ({ transaction, account }) => {
88
+ invariant(transaction.family === "algorand", "algorand family");
89
+
90
+ if (account.type === "Account") {
91
+ invariant(
92
+ (account as AlgorandAccount).algorandResources,
93
+ "unactivated account"
94
+ );
95
+ }
96
+
97
+ if (account.type === "TokenAccount") {
98
+ const isDelisted = account.token.delisted === true;
99
+ invariant(!isDelisted, "token is delisted");
100
+ }
101
+
102
+ return {
103
+ ...transaction,
104
+ family: "algorand",
105
+ fees: opts.fees ? inferAmount(account, opts.fees) : null,
106
+ memo: opts.memo,
107
+ mode: opts.mode || "send",
108
+ subAccountId: account.type === "TokenAccount" ? account.id : null,
109
+ assetId: opts.token ? "algorand/asa/" + opts.token : null,
110
+ };
111
+ });
112
+ }
113
+
114
+ /**
115
+ * FIXME: unsued network and cache params are passed to makeCliTools because of how the
116
+ * libs/ledger-live-common/scripts/sync-families-dispatch.mjs script works.
117
+ */
118
+ export default function makeCliTools(_network: unknown, _cache: unknown) {
119
+ return {
120
+ options,
121
+ inferAccounts,
122
+ inferTransactions,
123
+ };
124
+ }
@@ -0,0 +1,146 @@
1
+ import { getAccountUnit } from "@ledgerhq/coin-framework/account/index";
2
+ import {
3
+ findTokenById,
4
+ formatCurrencyUnit,
5
+ } from "@ledgerhq/coin-framework/currencies/index";
6
+ import type { CommonDeviceTransactionField as DeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common";
7
+ import { TokenCurrency } from "@ledgerhq/types-cryptoassets";
8
+ import { AccountLike } from "@ledgerhq/types-live";
9
+ import { extractTokenId } from "./tokens";
10
+ import type {
11
+ AlgorandTransaction,
12
+ Transaction,
13
+ TransactionStatus,
14
+ } from "./types";
15
+
16
+ export type ExtraDeviceTransactionField = {
17
+ type: "polkadot.validators";
18
+ label: string;
19
+ };
20
+
21
+ export const displayTokenValue = (token: TokenCurrency) =>
22
+ `${token.name} (#${extractTokenId(token.id)})`;
23
+
24
+ const getSendFields = (
25
+ transaction: Transaction,
26
+ status: TransactionStatus,
27
+ account: AccountLike,
28
+ addRecipient: boolean
29
+ ) => {
30
+ const { estimatedFees, amount } = status;
31
+ const fields: {
32
+ type: string;
33
+ label: string;
34
+ value?: string;
35
+ address?: string;
36
+ }[] = [];
37
+ fields.push({
38
+ type: "text",
39
+ label: "Type",
40
+ value: account.type === "TokenAccount" ? "Asset xfer" : "Payment",
41
+ });
42
+
43
+ if (estimatedFees && !estimatedFees.isZero()) {
44
+ fields.push({
45
+ type: "fees",
46
+ label: "Fee",
47
+ });
48
+ }
49
+
50
+ if (addRecipient) {
51
+ fields.push({
52
+ type: "address",
53
+ label: "Recipient",
54
+ address: transaction.recipient,
55
+ });
56
+ }
57
+
58
+ if (account.type === "TokenAccount") {
59
+ fields.push({
60
+ type: "text",
61
+ label: "Asset ID",
62
+ value: displayTokenValue(account.token),
63
+ });
64
+ }
65
+
66
+ if (amount) {
67
+ fields.push({
68
+ type: "text",
69
+ label: account.type === "TokenAccount" ? "Asset amt" : "Amount",
70
+ value: formatCurrencyUnit(getAccountUnit(account), amount, {
71
+ showCode: true,
72
+ disableRounding: true,
73
+ }),
74
+ });
75
+ }
76
+
77
+ return fields;
78
+ };
79
+
80
+ function getDeviceTransactionConfig({
81
+ account,
82
+ transaction,
83
+ status,
84
+ }: {
85
+ account: AccountLike;
86
+ transaction: AlgorandTransaction;
87
+ status: TransactionStatus;
88
+ }): Array<DeviceTransactionField> {
89
+ const { mode, assetId } = transaction;
90
+ const { estimatedFees } = status;
91
+ let fields: {
92
+ type: string;
93
+ label: string;
94
+ value?: string;
95
+ address?: string;
96
+ }[] = [];
97
+
98
+ switch (mode) {
99
+ case "send":
100
+ fields = getSendFields(transaction, status, account, false);
101
+ break;
102
+
103
+ case "claimReward":
104
+ fields = getSendFields(transaction, status, account, true);
105
+ break;
106
+
107
+ case "optIn":
108
+ fields.push({
109
+ type: "text",
110
+ label: "Type",
111
+ value: "Asset xfer",
112
+ });
113
+
114
+ if (estimatedFees && !estimatedFees.isZero()) {
115
+ fields.push({
116
+ type: "fees",
117
+ label: "Fee",
118
+ });
119
+ }
120
+
121
+ if (assetId) {
122
+ const token = findTokenById(assetId);
123
+ fields.push({
124
+ type: "text",
125
+ label: "Asset ID",
126
+ value: token
127
+ ? displayTokenValue(token)
128
+ : `#${extractTokenId(assetId)}`,
129
+ });
130
+ }
131
+
132
+ fields.push({
133
+ type: "text",
134
+ label: "Asset amt",
135
+ value: "0",
136
+ });
137
+ break;
138
+
139
+ default:
140
+ break;
141
+ }
142
+
143
+ return fields as Array<DeviceTransactionField>;
144
+ }
145
+
146
+ export default getDeviceTransactionConfig;
package/src/errors.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { createCustomErrorClass } from "@ledgerhq/errors";
2
+
3
+ export const AlgorandASANotOptInInRecipient = createCustomErrorClass(
4
+ "AlgorandASANotOptInInRecipient"
5
+ );
@@ -0,0 +1,14 @@
1
+ import type { Resolver } from "@ledgerhq/coin-framework/bridge/getAddressWrapper";
2
+ import Algorand from "@ledgerhq/hw-app-algorand";
3
+
4
+ const resolver: Resolver = async (transport, { path, verify }) => {
5
+ const algorand = new Algorand(transport);
6
+ const r = await algorand.getAddress(path, verify || false);
7
+ return {
8
+ address: r.address,
9
+ publicKey: r.publicKey,
10
+ path,
11
+ };
12
+ };
13
+
14
+ export default resolver;
@@ -0,0 +1,10 @@
1
+ import type { Account } from "@ledgerhq/types-live";
2
+ import { BigNumber } from "bignumber.js";
3
+ import type { AlgorandAccount } from "./types";
4
+
5
+ export function initAccount(r: Account): void {
6
+ (r as AlgorandAccount).algorandResources = {
7
+ rewards: new BigNumber(0),
8
+ nbAssets: r.subAccounts?.length ?? 0,
9
+ };
10
+ }