@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,429 @@
1
+ import {
2
+ emptyHistoryCache,
3
+ encodeAccountId,
4
+ inferSubOperations,
5
+ } from "@ledgerhq/coin-framework/account/index";
6
+ import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers";
7
+ import { mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
8
+ import {
9
+ findTokenById,
10
+ listTokensForCryptoCurrency,
11
+ } from "@ledgerhq/coin-framework/currencies/index";
12
+ import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
13
+ import { promiseAllBatched } from "@ledgerhq/live-promise";
14
+ import { BigNumber } from "bignumber.js";
15
+
16
+ import type {
17
+ AlgoAsset,
18
+ AlgoAssetTransferInfo,
19
+ AlgoPaymentInfo,
20
+ AlgoTransaction,
21
+ AlgorandAPI,
22
+ } from "./api";
23
+
24
+ import { AlgoTransactionType } from "./api";
25
+
26
+ import { CryptoCurrency, TokenCurrency } from "@ledgerhq/types-cryptoassets";
27
+ import type {
28
+ Account,
29
+ Operation,
30
+ OperationType,
31
+ SyncConfig,
32
+ TokenAccount,
33
+ } from "@ledgerhq/types-live";
34
+ import { computeAlgoMaxSpendable } from "./logic";
35
+ import { addPrefixToken, extractTokenId } from "./tokens";
36
+
37
+ const getASAOperationAmount = (
38
+ transaction: AlgoTransaction,
39
+ accountAddress: string
40
+ ): BigNumber => {
41
+ let assetAmount = new BigNumber(0);
42
+ if (transaction.type === AlgoTransactionType.ASSET_TRANSFER) {
43
+ const details = transaction.details as AlgoAssetTransferInfo;
44
+ const assetSender = details.assetSenderAddress
45
+ ? details.assetSenderAddress
46
+ : transaction.senderAddress;
47
+
48
+ // Account is either sender or recipient (if both the balance is unchanged)
49
+ if (
50
+ (assetSender === accountAddress) !==
51
+ (details.assetRecipientAddress == accountAddress)
52
+ ) {
53
+ assetAmount = assetAmount.plus(details.assetAmount);
54
+ }
55
+ // Account is either sender or close-to, but not both
56
+ if (
57
+ (assetSender === accountAddress) !==
58
+ (details.assetCloseToAddress &&
59
+ details.assetCloseToAddress === accountAddress)
60
+ ) {
61
+ if (details.assetCloseAmount) {
62
+ assetAmount = assetAmount.plus(details.assetCloseAmount);
63
+ }
64
+ }
65
+ }
66
+ return assetAmount;
67
+ };
68
+
69
+ const getOperationAmounts = (
70
+ transaction: AlgoTransaction,
71
+ accountAddress: string
72
+ ): { amount: BigNumber; rewards: BigNumber } => {
73
+ let amount = new BigNumber(0);
74
+ let rewards = new BigNumber(0);
75
+
76
+ if (transaction.senderAddress === accountAddress) {
77
+ const senderRewards = transaction.senderRewards;
78
+ amount = amount.minus(senderRewards).plus(transaction.fee);
79
+ rewards = rewards.plus(senderRewards);
80
+ }
81
+
82
+ if (transaction.type === AlgoTransactionType.PAYMENT) {
83
+ const details = transaction.details as AlgoPaymentInfo;
84
+ if (transaction.senderAddress == details.recipientAddress) {
85
+ return {
86
+ amount,
87
+ rewards,
88
+ };
89
+ }
90
+ if (transaction.senderAddress === accountAddress) {
91
+ amount = amount.plus(details.amount);
92
+ }
93
+ if (details.recipientAddress == accountAddress) {
94
+ const recipientRewards = transaction.recipientRewards;
95
+ amount = amount.plus(details.amount).plus(recipientRewards);
96
+ rewards = rewards.plus(recipientRewards);
97
+ }
98
+ if (
99
+ transaction.closeRewards &&
100
+ details.closeAmount &&
101
+ details.closeToAddress === accountAddress
102
+ ) {
103
+ const closeRewards = transaction.closeRewards;
104
+ amount = amount.plus(details.closeAmount).plus(closeRewards);
105
+ rewards = rewards.plus(closeRewards);
106
+ }
107
+ }
108
+
109
+ return {
110
+ amount,
111
+ rewards,
112
+ };
113
+ };
114
+
115
+ const getASAOperationType = (
116
+ transaction: AlgoTransaction,
117
+ accountAddress: string
118
+ ): OperationType => {
119
+ return transaction.senderAddress === accountAddress ? "OUT" : "IN";
120
+ };
121
+
122
+ const getOperationType = (
123
+ transaction: AlgoTransaction,
124
+ accountAddress: string
125
+ ): OperationType => {
126
+ if (transaction.type === AlgoTransactionType.ASSET_TRANSFER) {
127
+ const details = transaction.details as AlgoAssetTransferInfo;
128
+ if (
129
+ details.assetAmount.isZero() &&
130
+ transaction.senderAddress == details.assetRecipientAddress
131
+ ) {
132
+ return "OPT_IN";
133
+ } else if (
134
+ details.assetCloseToAddress &&
135
+ transaction.senderAddress == accountAddress
136
+ ) {
137
+ return "OPT_OUT";
138
+ } else {
139
+ return "FEES";
140
+ }
141
+ }
142
+
143
+ return transaction.senderAddress === accountAddress ? "OUT" : "IN";
144
+ };
145
+
146
+ const getOperationSenders = (transaction: AlgoTransaction): string[] => {
147
+ return [transaction.senderAddress];
148
+ };
149
+
150
+ const getOperationRecipients = (transaction: AlgoTransaction): string[] => {
151
+ const recipients: string[] = [];
152
+ if (transaction.type === AlgoTransactionType.PAYMENT) {
153
+ const details = transaction.details as AlgoPaymentInfo;
154
+ recipients.push(details.recipientAddress);
155
+ if (details.closeToAddress) {
156
+ recipients.push(details.closeToAddress);
157
+ }
158
+ } else if (transaction.type === AlgoTransactionType.ASSET_TRANSFER) {
159
+ const details = transaction.details as AlgoAssetTransferInfo;
160
+
161
+ recipients.push(details.assetRecipientAddress);
162
+ if (details.assetCloseToAddress) {
163
+ recipients.push(details.assetCloseToAddress);
164
+ }
165
+ }
166
+ return recipients;
167
+ };
168
+
169
+ const getOperationAssetId = (
170
+ transaction: AlgoTransaction
171
+ ): string | undefined => {
172
+ if (transaction.type === AlgoTransactionType.ASSET_TRANSFER) {
173
+ const details = transaction.details as AlgoAssetTransferInfo;
174
+ return details.assetId;
175
+ }
176
+ };
177
+
178
+ const mapTransactionToOperation = (
179
+ tx: AlgoTransaction,
180
+ accountId: string,
181
+ accountAddress: string,
182
+ subAccounts?: TokenAccount[]
183
+ ): Partial<Operation> => {
184
+ const hash = tx.id;
185
+ const blockHeight = tx.round;
186
+ const date = new Date(parseInt(tx.timestamp) * 1000);
187
+ const fee = tx.fee;
188
+ const memo = tx.note;
189
+ const senders: string[] = getOperationSenders(tx);
190
+ const recipients: string[] = getOperationRecipients(tx);
191
+ const { amount, rewards } = getOperationAmounts(tx, accountAddress);
192
+ const type = getOperationType(tx, accountAddress);
193
+ const assetId = getOperationAssetId(tx);
194
+
195
+ const subOperations = subAccounts
196
+ ? inferSubOperations(tx.id, subAccounts)
197
+ : undefined;
198
+
199
+ return {
200
+ id: encodeOperationId(accountId, hash, type),
201
+ hash,
202
+ date,
203
+ type,
204
+ value: amount,
205
+ fee,
206
+ senders,
207
+ recipients,
208
+ blockHeight,
209
+ accountId,
210
+ subOperations,
211
+ extra: {
212
+ rewards,
213
+ memo,
214
+ assetId,
215
+ },
216
+ };
217
+ };
218
+
219
+ const mapTransactionToASAOperation = (
220
+ tx: AlgoTransaction,
221
+ accountId: string,
222
+ accountAddress: string
223
+ ): Partial<Operation> => {
224
+ const hash = tx.id;
225
+ const blockHeight = tx.round;
226
+ const date = new Date(parseInt(tx.timestamp) * 1000);
227
+ const fee = tx.fee;
228
+ const senders: string[] = getOperationSenders(tx);
229
+ const recipients: string[] = getOperationRecipients(tx);
230
+ const type = getASAOperationType(tx, accountAddress);
231
+ const amount = getASAOperationAmount(tx, accountAddress);
232
+
233
+ return {
234
+ id: encodeOperationId(accountId, hash, type),
235
+ hash,
236
+ date,
237
+ type,
238
+ value: amount,
239
+ fee,
240
+ senders,
241
+ recipients,
242
+ blockHeight,
243
+ accountId,
244
+ extra: {},
245
+ };
246
+ };
247
+
248
+ export function makeGetAccountShape(algorandAPI: AlgorandAPI): GetAccountShape {
249
+ return async (info, syncConfig): Promise<Partial<Account>> => {
250
+ const { address, initialAccount, currency, derivationMode } = info;
251
+ const oldOperations = initialAccount?.operations || [];
252
+ const startAt = oldOperations.length
253
+ ? (oldOperations[0].blockHeight || 0) + 1
254
+ : 0;
255
+ const accountId = encodeAccountId({
256
+ type: "js",
257
+ version: "2",
258
+ currencyId: currency.id,
259
+ xpubOrAddress: address,
260
+ derivationMode,
261
+ });
262
+
263
+ const { round, balance, pendingRewards, assets } =
264
+ await algorandAPI.getAccount(address);
265
+
266
+ const nbAssets = assets.length;
267
+
268
+ // NOTE Actual spendable amount depends on the transaction
269
+ const spendableBalance = computeAlgoMaxSpendable({
270
+ accountBalance: balance,
271
+ nbAccountAssets: nbAssets,
272
+ mode: "send",
273
+ });
274
+
275
+ const newTransactions: AlgoTransaction[] =
276
+ await algorandAPI.getAccountTransactions(address, startAt);
277
+
278
+ const subAccounts = await buildSubAccounts({
279
+ currency,
280
+ accountId,
281
+ initialAccount,
282
+ initialAccountAddress: address,
283
+ assets,
284
+ newTransactions,
285
+ syncConfig,
286
+ });
287
+
288
+ const newOperations = newTransactions.map((tx) =>
289
+ mapTransactionToOperation(tx, accountId, address, subAccounts)
290
+ );
291
+
292
+ const operations = mergeOps(oldOperations, newOperations as Operation[]);
293
+
294
+ const shape = {
295
+ id: accountId,
296
+ xpub: address,
297
+ blockHeight: round,
298
+ balance,
299
+ spendableBalance,
300
+ operations,
301
+ operationsCount: operations.length,
302
+ subAccounts,
303
+ algorandResources: {
304
+ rewards: pendingRewards,
305
+ nbAssets,
306
+ },
307
+ };
308
+ return shape;
309
+ };
310
+ }
311
+
312
+ async function buildSubAccount({
313
+ parentAccountId,
314
+ parentAccountAddress,
315
+ token,
316
+ initialTokenAccount,
317
+ newTransactions,
318
+ balance,
319
+ }: {
320
+ parentAccountId: string;
321
+ parentAccountAddress: string;
322
+ token: TokenCurrency;
323
+ initialTokenAccount: TokenAccount;
324
+ newTransactions: AlgoTransaction[];
325
+ balance: BigNumber;
326
+ }) {
327
+ const extractedId = extractTokenId(token.id);
328
+ const tokenAccountId = parentAccountId + "+" + extractedId;
329
+
330
+ const oldOperations = initialTokenAccount?.operations || [];
331
+
332
+ const newOperations = newTransactions
333
+ .filter((tx) => tx.type === AlgoTransactionType.ASSET_TRANSFER)
334
+ .filter((tx) => {
335
+ const details = tx.details as AlgoAssetTransferInfo;
336
+ return Number(details.assetId) === Number(extractedId);
337
+ })
338
+ .filter((tx) => getOperationType(tx, parentAccountAddress) != "OPT_IN")
339
+ .map((tx) =>
340
+ mapTransactionToASAOperation(tx, tokenAccountId, parentAccountAddress)
341
+ );
342
+
343
+ const operations = mergeOps(oldOperations, newOperations as Operation[]);
344
+
345
+ const tokenAccount: TokenAccount = {
346
+ type: "TokenAccount",
347
+ id: tokenAccountId,
348
+ parentId: parentAccountId,
349
+ starred: false,
350
+ token,
351
+ operationsCount: operations.length,
352
+ operations,
353
+ pendingOperations: [],
354
+ balance,
355
+ spendableBalance: balance,
356
+ swapHistory: [],
357
+ creationDate:
358
+ operations.length > 0
359
+ ? operations[operations.length - 1].date
360
+ : new Date(),
361
+ balanceHistoryCache: emptyHistoryCache,
362
+ };
363
+ return tokenAccount;
364
+ }
365
+
366
+ async function buildSubAccounts({
367
+ currency,
368
+ accountId,
369
+ initialAccount,
370
+ initialAccountAddress,
371
+ assets,
372
+ newTransactions,
373
+ syncConfig,
374
+ }: {
375
+ currency: CryptoCurrency;
376
+ accountId: string;
377
+ initialAccount: Account | null | undefined;
378
+ initialAccountAddress: string;
379
+ assets: AlgoAsset[];
380
+ newTransactions: AlgoTransaction[];
381
+ syncConfig: SyncConfig;
382
+ }): Promise<TokenAccount[] | undefined> {
383
+ const { blacklistedTokenIds = [] } = syncConfig;
384
+ if (listTokensForCryptoCurrency(currency).length === 0) return undefined;
385
+ const tokenAccounts: TokenAccount[] = [];
386
+ const existingAccountByTicker: { [ticker: string]: TokenAccount } = {}; // used for fast lookup
387
+ const existingAccountTickers: string[] = []; // used to keep track of ordering
388
+
389
+ if (initialAccount && initialAccount.subAccounts) {
390
+ for (const existingSubAccount of initialAccount.subAccounts) {
391
+ if (existingSubAccount.type === "TokenAccount") {
392
+ const { ticker, id } = existingSubAccount.token;
393
+
394
+ if (!blacklistedTokenIds.includes(id)) {
395
+ existingAccountTickers.push(ticker);
396
+ existingAccountByTicker[ticker] = existingSubAccount;
397
+ }
398
+ }
399
+ }
400
+ }
401
+
402
+ // filter by token existence
403
+ await promiseAllBatched(3, assets, async (asset) => {
404
+ const token = findTokenById(addPrefixToken(asset.assetId));
405
+
406
+ if (token && !blacklistedTokenIds.includes(token.id)) {
407
+ const initialTokenAccount = existingAccountByTicker[token.ticker];
408
+ const tokenAccount = await buildSubAccount({
409
+ parentAccountId: accountId,
410
+ parentAccountAddress: initialAccountAddress,
411
+ initialTokenAccount,
412
+ token,
413
+ newTransactions,
414
+ balance: asset.balance,
415
+ });
416
+ if (tokenAccount) tokenAccounts.push(tokenAccount);
417
+ }
418
+ });
419
+ // Preserve order of tokenAccounts from the existing token accounts
420
+ tokenAccounts.sort((a, b) => {
421
+ const i = existingAccountTickers.indexOf(a.token.ticker);
422
+ const j = existingAccountTickers.indexOf(b.token.ticker);
423
+ if (i === j) return 0;
424
+ if (i < 0) return 1;
425
+ if (j < 0) return -1;
426
+ return i - j;
427
+ });
428
+ return tokenAccounts;
429
+ }
package/src/logic.ts ADDED
@@ -0,0 +1,46 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { AlgorandAPI } from "./api";
3
+ import type { AlgorandOperationMode } from "./types";
4
+
5
+ export const ALGORAND_MAX_MEMO_SIZE = 32;
6
+ export const ALGORAND_MIN_ACCOUNT_BALANCE = 100000;
7
+
8
+ export const recipientHasAsset =
9
+ (algorandAPI: AlgorandAPI) =>
10
+ async (recipientAddress: string, assetId: string): Promise<boolean> => {
11
+ const recipientAccount = await algorandAPI.getAccount(recipientAddress);
12
+ return recipientAccount.assets.map((a) => a.assetId).includes(assetId);
13
+ };
14
+
15
+ export const isAmountValid =
16
+ (algorandAPI: AlgorandAPI) =>
17
+ async (recipientAddress: string, amount: BigNumber): Promise<boolean> => {
18
+ const recipientAccount = await algorandAPI.getAccount(recipientAddress);
19
+ return recipientAccount.balance.isZero()
20
+ ? amount.gte(ALGORAND_MIN_ACCOUNT_BALANCE)
21
+ : true;
22
+ };
23
+
24
+ export const computeAlgoMaxSpendable = ({
25
+ accountBalance,
26
+ nbAccountAssets,
27
+ mode,
28
+ }: {
29
+ accountBalance: BigNumber;
30
+ nbAccountAssets: number;
31
+ mode: AlgorandOperationMode;
32
+ }): BigNumber => {
33
+ const minBalance = computeMinimumAlgoBalance(mode, nbAccountAssets);
34
+ const maxSpendable = accountBalance.minus(minBalance);
35
+ return maxSpendable.gte(0) ? maxSpendable : new BigNumber(0);
36
+ };
37
+
38
+ const computeMinimumAlgoBalance = (
39
+ mode: AlgorandOperationMode,
40
+ nbAccountAssets: number
41
+ ): BigNumber => {
42
+ const base = 100000; // 0.1 algo = 100000 malgo
43
+ const currentAssets = nbAccountAssets;
44
+ const newAsset = mode === "optIn" ? 1 : 0;
45
+ return new BigNumber(base * (1 + currentAssets + newAsset));
46
+ };
package/src/mock.ts ADDED
@@ -0,0 +1,131 @@
1
+ import { genAddress, genHex } from "@ledgerhq/coin-framework/mocks/helpers";
2
+ import { Account, Operation, OperationType } from "@ledgerhq/types-live";
3
+ import { BigNumber } from "bignumber.js";
4
+ import Prando from "prando";
5
+ import type { AlgorandAccount } from "./types";
6
+
7
+ function setAlgorandResources(account: Account): Account {
8
+ /** format algorandResources given the new delegations */
9
+ (account as AlgorandAccount).algorandResources = {
10
+ rewards: account.balance.multipliedBy(0.01),
11
+ nbAssets: account.subAccounts?.length ?? 0,
12
+ };
13
+ return account;
14
+ }
15
+
16
+ function genBaseOperation(
17
+ account: Account,
18
+ rng: Prando,
19
+ type: OperationType,
20
+ index: number
21
+ ): Operation {
22
+ const { operations: ops } = account;
23
+ const address = genAddress(account.currency, rng);
24
+ const lastOp = ops[index];
25
+ const date = new Date(
26
+ (lastOp ? lastOp.date.valueOf() : Date.now()) -
27
+ rng.nextInt(0, 100000000 * rng.next() * rng.next())
28
+ );
29
+ const hash = genHex(64, rng);
30
+
31
+ /** generate given operation */
32
+ return {
33
+ id: String(`mock_op_${ops.length}_${type}_${account.id}`),
34
+ hash,
35
+ type,
36
+ value: new BigNumber(0),
37
+ fee: new BigNumber(0),
38
+ senders: [address],
39
+ recipients: [address],
40
+ blockHash: genHex(64, rng),
41
+ blockHeight:
42
+ account.blockHeight -
43
+ // FIXME: always the same, valueOf for arithmetics operation on date in typescript
44
+ Math.floor((Date.now().valueOf() - date.valueOf()) / 900000),
45
+ accountId: account.id,
46
+ date,
47
+ extra: {
48
+ rewards: new BigNumber(0),
49
+ memo: "memo",
50
+ },
51
+ };
52
+ }
53
+
54
+ function addOptIn(account: Account, rng: Prando): Account {
55
+ /** select position on the operation stack where we will insert the new claim rewards */
56
+ const opIndex = rng.next(0, 10);
57
+ const opt = genBaseOperation(account, rng, "OPT_IN", opIndex);
58
+ opt.extra = { ...opt.extra, rewards: new BigNumber(0), assetId: "Thether" };
59
+ account.operations.splice(opIndex, 0, opt);
60
+ account.operationsCount++;
61
+ return account;
62
+ }
63
+
64
+ function addOptOut(account: Account, rng: Prando): Account {
65
+ /** select position on the operation stack where we will insert the new claim rewards */
66
+ const opIndex = rng.next(0, 10);
67
+ const opt = genBaseOperation(account, rng, "OPT_OUT", opIndex);
68
+ opt.extra = { ...opt.extra, rewards: new BigNumber(0), assetId: "Thether" };
69
+ account.operations.splice(opIndex, 0, opt);
70
+ account.operationsCount++;
71
+ return account;
72
+ }
73
+
74
+ /**
75
+ * add in specific algorand operations
76
+ * @memberof algorand/mock
77
+ * @param {Account} account
78
+ * @param {Prando} rng
79
+ */
80
+ function genAccountEnhanceOperations(account: Account, rng: Prando): Account {
81
+ addOptIn(account, rng);
82
+ addOptOut(account, rng);
83
+ setAlgorandResources(account);
84
+ return account;
85
+ }
86
+
87
+ /**
88
+ * Update spendable balance for the account based on delegation data
89
+ * @memberof algorand/mock
90
+ * @param {Account} account
91
+ */
92
+ function postSyncAccount(account: Account): Account {
93
+ const algorandResources = (account as AlgorandAccount).algorandResources || {
94
+ rewards: undefined,
95
+ };
96
+ const rewards = algorandResources.rewards || new BigNumber(0);
97
+ account.spendableBalance = account.balance.plus(rewards);
98
+ return account;
99
+ }
100
+
101
+ /**
102
+ * post account scan data logic
103
+ * clears account algorand resources if supposed to be empty
104
+ * @memberof algorand/mock
105
+ * @param {AlgorandAccount} account
106
+ */
107
+ function postScanAccount(
108
+ account: Account,
109
+ {
110
+ isEmpty,
111
+ }: {
112
+ isEmpty: boolean;
113
+ }
114
+ ): Account {
115
+ if (isEmpty) {
116
+ (account as AlgorandAccount).algorandResources = {
117
+ rewards: new BigNumber(0),
118
+ nbAssets: account.subAccounts?.length ?? 0,
119
+ };
120
+ account.operations = [];
121
+ account.subAccounts = [];
122
+ }
123
+
124
+ return account;
125
+ }
126
+
127
+ export default {
128
+ genAccountEnhanceOperations,
129
+ postSyncAccount,
130
+ postScanAccount,
131
+ };
@@ -0,0 +1,48 @@
1
+ import type { Account, AccountRaw } from "@ledgerhq/types-live";
2
+ import { BigNumber } from "bignumber.js";
3
+ import type {
4
+ AlgorandAccount,
5
+ AlgorandAccountRaw,
6
+ AlgorandResources,
7
+ AlgorandResourcesRaw,
8
+ } from "./types";
9
+
10
+ function toResourcesRaw(r: AlgorandResources): AlgorandResourcesRaw {
11
+ const { rewards, nbAssets } = r;
12
+ return {
13
+ rewards: rewards.toString(),
14
+ nbAssets,
15
+ };
16
+ }
17
+ function fromResourcesRaw(r: AlgorandResourcesRaw): AlgorandResources {
18
+ const { rewards, nbAssets } = r;
19
+ return {
20
+ rewards: new BigNumber(rewards),
21
+ nbAssets,
22
+ };
23
+ }
24
+
25
+ export function assignToAccountRaw(
26
+ account: Account,
27
+ accountRaw: AccountRaw
28
+ ): void {
29
+ const algorandAccount = account as AlgorandAccount;
30
+ const algorandAccountRaw = accountRaw as AlgorandAccountRaw;
31
+ if (algorandAccount.algorandResources) {
32
+ algorandAccountRaw.algorandResources = toResourcesRaw(
33
+ algorandAccount.algorandResources
34
+ );
35
+ }
36
+ }
37
+
38
+ export function assignFromAccountRaw(
39
+ accountRaw: AccountRaw,
40
+ account: Account
41
+ ): void {
42
+ const algorandResourcesRaw = (accountRaw as AlgorandAccountRaw)
43
+ .algorandResources;
44
+ const algorandAccount = account as AlgorandAccount;
45
+ if (algorandResourcesRaw) {
46
+ algorandAccount.algorandResources = fromResourcesRaw(algorandResourcesRaw);
47
+ }
48
+ }