@ledgerhq/coin-stellar 0.2.0-nightly.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.
- package/.eslintrc.js +20 -0
- package/.turbo/turbo-build.log +4 -0
- package/.unimportedrc.json +35 -0
- package/CHANGELOG.md +13 -0
- package/LICENSE.txt +21 -0
- package/jest.config.js +8 -0
- package/lib/bridge/bridge.integration.test.d.ts +4 -0
- package/lib/bridge/bridge.integration.test.d.ts.map +1 -0
- package/lib/bridge/bridge.integration.test.js +317 -0
- package/lib/bridge/bridge.integration.test.js.map +1 -0
- package/lib/bridge/index.d.ts +10 -0
- package/lib/bridge/index.d.ts.map +1 -0
- package/lib/bridge/index.js +72 -0
- package/lib/bridge/index.js.map +1 -0
- package/lib/broadcast.d.ts +9 -0
- package/lib/broadcast.d.ts.map +1 -0
- package/lib/broadcast.js +26 -0
- package/lib/broadcast.js.map +1 -0
- package/lib/buildOptimisticOperation.d.ts +4 -0
- package/lib/buildOptimisticOperation.d.ts.map +1 -0
- package/lib/buildOptimisticOperation.js +72 -0
- package/lib/buildOptimisticOperation.js.map +1 -0
- package/lib/buildTransaction.d.ts +10 -0
- package/lib/buildTransaction.d.ts.map +1 -0
- package/lib/buildTransaction.js +97 -0
- package/lib/buildTransaction.js.map +1 -0
- package/lib/cli.d.ts +38 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +83 -0
- package/lib/cli.js.map +1 -0
- package/lib/config.d.ts +5 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +17 -0
- package/lib/config.js.map +1 -0
- package/lib/createTransaction.d.ts +10 -0
- package/lib/createTransaction.d.ts.map +1 -0
- package/lib/createTransaction.js +26 -0
- package/lib/createTransaction.js.map +1 -0
- package/lib/deviceTransactionConfig.d.ts +24 -0
- package/lib/deviceTransactionConfig.d.ts.map +1 -0
- package/lib/deviceTransactionConfig.js +41 -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/estimateMaxSpendable.d.ts +5 -0
- package/lib/estimateMaxSpendable.d.ts.map +1 -0
- package/lib/estimateMaxSpendable.js +32 -0
- package/lib/estimateMaxSpendable.js.map +1 -0
- package/lib/getTransactionStatus.d.ts +5 -0
- package/lib/getTransactionStatus.d.ts.map +1 -0
- package/lib/getTransactionStatus.js +170 -0
- package/lib/getTransactionStatus.js.map +1 -0
- package/lib/hw-getAddress.d.ts +6 -0
- package/lib/hw-getAddress.d.ts.map +1 -0
- package/lib/hw-getAddress.js +28 -0
- package/lib/hw-getAddress.js.map +1 -0
- package/lib/logic.d.ts +36 -0
- package/lib/logic.d.ts.map +1 -0
- package/lib/logic.js +289 -0
- package/lib/logic.js.map +1 -0
- package/lib/network/horizon.d.ts +71 -0
- package/lib/network/horizon.d.ts.map +1 -0
- package/lib/network/horizon.js +300 -0
- package/lib/network/horizon.js.map +1 -0
- package/lib/network/index.d.ts +2 -0
- package/lib/network/index.d.ts.map +1 -0
- package/lib/network/index.js +20 -0
- package/lib/network/index.js.map +1 -0
- package/lib/prepareTransaction.d.ts +5 -0
- package/lib/prepareTransaction.d.ts.map +1 -0
- package/lib/prepareTransaction.js +41 -0
- package/lib/prepareTransaction.js.map +1 -0
- package/lib/signOperation.d.ts +6 -0
- package/lib/signOperation.d.ts.map +1 -0
- package/lib/signOperation.js +56 -0
- package/lib/signOperation.js.map +1 -0
- package/lib/specs.d.ts +7 -0
- package/lib/specs.d.ts.map +1 -0
- package/lib/specs.js +234 -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 +99 -0
- package/lib/speculos-deviceActions.js.map +1 -0
- package/lib/synchronization.d.ts +4 -0
- package/lib/synchronization.d.ts.map +1 -0
- package/lib/synchronization.js +73 -0
- package/lib/synchronization.js.map +1 -0
- package/lib/tokens.d.ts +12 -0
- package/lib/tokens.d.ts.map +1 -0
- package/lib/tokens.js +58 -0
- package/lib/tokens.js.map +1 -0
- package/lib/transaction.d.ts +15 -0
- package/lib/transaction.d.ts.map +1 -0
- package/lib/transaction.js +63 -0
- package/lib/transaction.js.map +1 -0
- package/lib/types/index.d.ts +89 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +26 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/signer.d.ts +10 -0
- package/lib/types/signer.d.ts.map +1 -0
- package/lib/types/signer.js +3 -0
- package/lib/types/signer.js.map +1 -0
- package/lib-es/bridge/bridge.integration.test.d.ts +4 -0
- package/lib-es/bridge/bridge.integration.test.d.ts.map +1 -0
- package/lib-es/bridge/bridge.integration.test.js +311 -0
- package/lib-es/bridge/bridge.integration.test.js.map +1 -0
- package/lib-es/bridge/index.d.ts +10 -0
- package/lib-es/bridge/index.d.ts.map +1 -0
- package/lib-es/bridge/index.js +65 -0
- package/lib-es/bridge/index.js.map +1 -0
- package/lib-es/broadcast.d.ts +9 -0
- package/lib-es/broadcast.d.ts.map +1 -0
- package/lib-es/broadcast.js +22 -0
- package/lib-es/broadcast.js.map +1 -0
- package/lib-es/buildOptimisticOperation.d.ts +4 -0
- package/lib-es/buildOptimisticOperation.d.ts.map +1 -0
- package/lib-es/buildOptimisticOperation.js +65 -0
- package/lib-es/buildOptimisticOperation.js.map +1 -0
- package/lib-es/buildTransaction.d.ts +10 -0
- package/lib-es/buildTransaction.d.ts.map +1 -0
- package/lib-es/buildTransaction.js +90 -0
- package/lib-es/buildTransaction.js.map +1 -0
- package/lib-es/cli.d.ts +38 -0
- package/lib-es/cli.d.ts.map +1 -0
- package/lib-es/cli.js +77 -0
- package/lib-es/cli.js.map +1 -0
- package/lib-es/config.d.ts +5 -0
- package/lib-es/config.d.ts.map +1 -0
- package/lib-es/config.js +12 -0
- package/lib-es/config.js.map +1 -0
- package/lib-es/createTransaction.d.ts +10 -0
- package/lib-es/createTransaction.d.ts.map +1 -0
- package/lib-es/createTransaction.js +22 -0
- package/lib-es/createTransaction.js.map +1 -0
- package/lib-es/deviceTransactionConfig.d.ts +24 -0
- package/lib-es/deviceTransactionConfig.d.ts.map +1 -0
- package/lib-es/deviceTransactionConfig.js +39 -0
- package/lib-es/deviceTransactionConfig.js.map +1 -0
- package/lib-es/errors.d.ts +37 -0
- package/lib-es/errors.d.ts.map +1 -0
- package/lib-es/errors.js +14 -0
- package/lib-es/errors.js.map +1 -0
- package/lib-es/estimateMaxSpendable.d.ts +5 -0
- package/lib-es/estimateMaxSpendable.d.ts.map +1 -0
- package/lib-es/estimateMaxSpendable.js +25 -0
- package/lib-es/estimateMaxSpendable.js.map +1 -0
- package/lib-es/getTransactionStatus.d.ts +5 -0
- package/lib-es/getTransactionStatus.d.ts.map +1 -0
- package/lib-es/getTransactionStatus.js +166 -0
- package/lib-es/getTransactionStatus.js.map +1 -0
- package/lib-es/hw-getAddress.d.ts +6 -0
- package/lib-es/hw-getAddress.d.ts.map +1 -0
- package/lib-es/hw-getAddress.js +26 -0
- package/lib-es/hw-getAddress.js.map +1 -0
- package/lib-es/logic.d.ts +36 -0
- package/lib-es/logic.d.ts.map +1 -0
- package/lib-es/logic.js +274 -0
- package/lib-es/logic.js.map +1 -0
- package/lib-es/network/horizon.d.ts +71 -0
- package/lib-es/network/horizon.d.ts.map +1 -0
- package/lib-es/network/horizon.js +285 -0
- package/lib-es/network/horizon.js.map +1 -0
- package/lib-es/network/index.d.ts +2 -0
- package/lib-es/network/index.d.ts.map +1 -0
- package/lib-es/network/index.js +2 -0
- package/lib-es/network/index.js.map +1 -0
- package/lib-es/prepareTransaction.d.ts +5 -0
- package/lib-es/prepareTransaction.d.ts.map +1 -0
- package/lib-es/prepareTransaction.js +34 -0
- package/lib-es/prepareTransaction.js.map +1 -0
- package/lib-es/signOperation.d.ts +6 -0
- package/lib-es/signOperation.d.ts.map +1 -0
- package/lib-es/signOperation.js +52 -0
- package/lib-es/signOperation.js.map +1 -0
- package/lib-es/specs.d.ts +7 -0
- package/lib-es/specs.d.ts.map +1 -0
- package/lib-es/specs.js +229 -0
- package/lib-es/specs.js.map +1 -0
- package/lib-es/speculos-deviceActions.d.ts +4 -0
- package/lib-es/speculos-deviceActions.d.ts.map +1 -0
- package/lib-es/speculos-deviceActions.js +96 -0
- package/lib-es/speculos-deviceActions.js.map +1 -0
- package/lib-es/synchronization.d.ts +4 -0
- package/lib-es/synchronization.d.ts.map +1 -0
- package/lib-es/synchronization.js +69 -0
- package/lib-es/synchronization.js.map +1 -0
- package/lib-es/tokens.d.ts +12 -0
- package/lib-es/tokens.d.ts.map +1 -0
- package/lib-es/tokens.js +50 -0
- package/lib-es/tokens.js.map +1 -0
- package/lib-es/transaction.d.ts +15 -0
- package/lib-es/transaction.d.ts.map +1 -0
- package/lib-es/transaction.js +59 -0
- package/lib-es/transaction.js.map +1 -0
- package/lib-es/types/index.d.ts +89 -0
- package/lib-es/types/index.d.ts.map +1 -0
- package/lib-es/types/index.js +9 -0
- package/lib-es/types/index.js.map +1 -0
- package/lib-es/types/signer.d.ts +10 -0
- package/lib-es/types/signer.d.ts.map +1 -0
- package/lib-es/types/signer.js +2 -0
- package/lib-es/types/signer.js.map +1 -0
- package/package.json +80 -0
- package/src/bridge/bridge.integration.test.ts +373 -0
- package/src/bridge/index.ts +77 -0
- package/src/broadcast.ts +20 -0
- package/src/buildOptimisticOperation.ts +63 -0
- package/src/buildTransaction.ts +106 -0
- package/src/cli.ts +107 -0
- package/src/config.ts +18 -0
- package/src/createTransaction.ts +25 -0
- package/src/deviceTransactionConfig.ts +75 -0
- package/src/errors.ts +20 -0
- package/src/estimateMaxSpendable.ts +29 -0
- package/src/getTransactionStatus.ts +207 -0
- package/src/hw-getAddress.ts +24 -0
- package/src/logic.ts +371 -0
- package/src/network/horizon.ts +352 -0
- package/src/network/index.ts +17 -0
- package/src/prepareTransaction.ts +35 -0
- package/src/signOperation.ts +58 -0
- package/src/specs.ts +290 -0
- package/src/speculos-deviceActions.ts +117 -0
- package/src/synchronization.ts +80 -0
- package/src/tokens.ts +98 -0
- package/src/transaction.ts +99 -0
- package/src/types/index.ts +112 -0
- package/src/types/signer.ts +9 -0
- package/tsconfig.json +13 -0
package/src/logic.ts
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { BigNumber } from "bignumber.js";
|
|
2
|
+
import type { CacheRes } from "@ledgerhq/live-network/cache";
|
|
3
|
+
import { makeLRUCache } from "@ledgerhq/live-network/cache";
|
|
4
|
+
import type { Account, OperationType, TokenAccount } from "@ledgerhq/types-live";
|
|
5
|
+
import {
|
|
6
|
+
Horizon,
|
|
7
|
+
StrKey,
|
|
8
|
+
MuxedAccount,
|
|
9
|
+
// @ts-expect-error stellar-sdk ts definition missing?
|
|
10
|
+
AccountRecord,
|
|
11
|
+
} from "@stellar/stellar-sdk";
|
|
12
|
+
import { findSubAccountById } from "@ledgerhq/coin-framework/account/helpers";
|
|
13
|
+
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
14
|
+
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
15
|
+
import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit";
|
|
16
|
+
import {
|
|
17
|
+
BASE_RESERVE,
|
|
18
|
+
BASE_RESERVE_MIN_COUNT,
|
|
19
|
+
fetchBaseFee,
|
|
20
|
+
fetchSigners,
|
|
21
|
+
loadAccount,
|
|
22
|
+
} from "./network";
|
|
23
|
+
import type {
|
|
24
|
+
BalanceAsset,
|
|
25
|
+
RawOperation,
|
|
26
|
+
StellarOperation,
|
|
27
|
+
Transaction,
|
|
28
|
+
TransactionRaw,
|
|
29
|
+
} from "./types";
|
|
30
|
+
|
|
31
|
+
export const STELLAR_BURN_ADDRESS = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF";
|
|
32
|
+
|
|
33
|
+
const currency = getCryptoCurrencyById("stellar");
|
|
34
|
+
|
|
35
|
+
const getMinimumBalance = (account: Horizon.ServerApi.AccountRecord): BigNumber => {
|
|
36
|
+
return parseCurrencyUnit(currency.units[0], getReservedBalance(account).toString());
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function getAccountSpendableBalance(
|
|
40
|
+
balance: BigNumber,
|
|
41
|
+
account: Horizon.ServerApi.AccountRecord,
|
|
42
|
+
): Promise<BigNumber> {
|
|
43
|
+
const minimumBalance = getMinimumBalance(account);
|
|
44
|
+
const { recommendedFee } = await fetchBaseFee();
|
|
45
|
+
return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getAmountValue(
|
|
49
|
+
account: Account,
|
|
50
|
+
transaction: Transaction,
|
|
51
|
+
fees: BigNumber,
|
|
52
|
+
): BigNumber {
|
|
53
|
+
// Asset
|
|
54
|
+
if (transaction.subAccountId) {
|
|
55
|
+
const asset = findSubAccountById(account, transaction.subAccountId) as TokenAccount;
|
|
56
|
+
return transaction.useAllAmount ? new BigNumber(asset.spendableBalance) : transaction.amount;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Native
|
|
60
|
+
return transaction.useAllAmount && transaction.networkInfo
|
|
61
|
+
? BigNumber.max(account.spendableBalance.minus(fees), 0)
|
|
62
|
+
: transaction.amount;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getBalanceId(balance: BalanceAsset): string | null {
|
|
66
|
+
switch (balance.asset_type) {
|
|
67
|
+
case "native":
|
|
68
|
+
return "native";
|
|
69
|
+
case "liquidity_pool_shares":
|
|
70
|
+
return balance.liquidity_pool_id || null;
|
|
71
|
+
case "credit_alphanum4":
|
|
72
|
+
case "credit_alphanum12":
|
|
73
|
+
return `${balance.asset_code}:${balance.asset_issuer}`;
|
|
74
|
+
default:
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getReservedBalance(account: Horizon.ServerApi.AccountRecord): BigNumber {
|
|
80
|
+
const numOfSponsoringEntries = Number(account.num_sponsoring);
|
|
81
|
+
const numOfSponsoredEntries = Number(account.num_sponsored);
|
|
82
|
+
|
|
83
|
+
const nativeAsset = account.balances?.find(b => b.asset_type === "native") as BalanceAsset;
|
|
84
|
+
|
|
85
|
+
const amountInOffers = new BigNumber(nativeAsset?.selling_liabilities || 0);
|
|
86
|
+
const numOfEntries = new BigNumber(account.subentry_count || 0);
|
|
87
|
+
|
|
88
|
+
return new BigNumber(BASE_RESERVE_MIN_COUNT)
|
|
89
|
+
.plus(numOfEntries)
|
|
90
|
+
.plus(numOfSponsoringEntries)
|
|
91
|
+
.minus(numOfSponsoredEntries)
|
|
92
|
+
.times(BASE_RESERVE)
|
|
93
|
+
.plus(amountInOffers);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getOperationType(operation: RawOperation, addr: string): OperationType {
|
|
97
|
+
switch (operation.type) {
|
|
98
|
+
case "create_account":
|
|
99
|
+
return operation.funder === addr ? "OUT" : "IN";
|
|
100
|
+
|
|
101
|
+
case "payment":
|
|
102
|
+
if (operation.from === addr && operation.to !== addr) {
|
|
103
|
+
return "OUT";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return "IN";
|
|
107
|
+
|
|
108
|
+
case "path_payment_strict_send":
|
|
109
|
+
if (operation.to === addr) return "IN";
|
|
110
|
+
return "OUT";
|
|
111
|
+
|
|
112
|
+
case "path_payment_strict_receive":
|
|
113
|
+
return "IN";
|
|
114
|
+
|
|
115
|
+
case "change_trust":
|
|
116
|
+
if (new BigNumber(operation.limit).eq(0)) {
|
|
117
|
+
return "OPT_OUT";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return "OPT_IN";
|
|
121
|
+
|
|
122
|
+
default:
|
|
123
|
+
if (operation.source_account === addr) {
|
|
124
|
+
return "OUT";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return "IN";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function getAssetCodeIssuer(tr: Transaction | TransactionRaw): string[] {
|
|
132
|
+
if (tr.subAccountId) {
|
|
133
|
+
const assetString = tr.subAccountId.split("+")[1];
|
|
134
|
+
return assetString.split(":");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return [tr.assetCode || "", tr.assetIssuer || ""];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getRecipients(operation: RawOperation): string[] {
|
|
141
|
+
switch (operation.type) {
|
|
142
|
+
case "create_account":
|
|
143
|
+
return [operation.account];
|
|
144
|
+
|
|
145
|
+
case "payment":
|
|
146
|
+
return [operation.to_muxed || operation.to];
|
|
147
|
+
|
|
148
|
+
case "path_payment_strict_send":
|
|
149
|
+
return [operation.to];
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function formatOperation(
|
|
157
|
+
rawOperation: RawOperation,
|
|
158
|
+
accountId: string,
|
|
159
|
+
addr: string,
|
|
160
|
+
): Promise<StellarOperation> {
|
|
161
|
+
const transaction = await rawOperation.transaction();
|
|
162
|
+
const type = getOperationType(rawOperation, addr);
|
|
163
|
+
const value = getValue(rawOperation, transaction, type);
|
|
164
|
+
const recipients = getRecipients(rawOperation);
|
|
165
|
+
const memo = transaction.memo
|
|
166
|
+
? transaction.memo_type === "hash" || transaction.memo_type === "return"
|
|
167
|
+
? Buffer.from(transaction.memo, "base64").toString("hex")
|
|
168
|
+
: transaction.memo
|
|
169
|
+
: null;
|
|
170
|
+
|
|
171
|
+
const operation: StellarOperation = {
|
|
172
|
+
id: encodeOperationId(accountId, rawOperation.transaction_hash, type),
|
|
173
|
+
accountId,
|
|
174
|
+
fee: new BigNumber(transaction.fee_charged),
|
|
175
|
+
value: rawOperation?.asset_code ? new BigNumber(transaction.fee_charged) : value,
|
|
176
|
+
// Using type NONE to hide asset operations from the main account (show them
|
|
177
|
+
// only on sub-account)
|
|
178
|
+
type: rawOperation?.asset_code && !["OPT_IN", "OPT_OUT"].includes(type) ? "NONE" : type,
|
|
179
|
+
hash: rawOperation.transaction_hash,
|
|
180
|
+
blockHeight: transaction.ledger_attr,
|
|
181
|
+
date: new Date(rawOperation.created_at),
|
|
182
|
+
senders: [rawOperation.source_account],
|
|
183
|
+
recipients,
|
|
184
|
+
transactionSequenceNumber: Number(transaction.source_account_sequence),
|
|
185
|
+
hasFailed: !rawOperation.transaction_successful,
|
|
186
|
+
blockHash: null,
|
|
187
|
+
extra: {
|
|
188
|
+
ledgerOpType: type,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (rawOperation.paging_token) {
|
|
193
|
+
operation.extra.pagingToken = rawOperation.paging_token;
|
|
194
|
+
}
|
|
195
|
+
if (rawOperation.asset_code) {
|
|
196
|
+
operation.extra.assetCode = rawOperation.asset_code;
|
|
197
|
+
operation.extra.assetAmount = rawOperation.asset_code ? value.toString() : undefined;
|
|
198
|
+
}
|
|
199
|
+
if (rawOperation.asset_issuer) {
|
|
200
|
+
operation.extra.assetIssuer = rawOperation.asset_issuer;
|
|
201
|
+
}
|
|
202
|
+
if (memo) {
|
|
203
|
+
operation.extra.memo = memo;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return operation;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function getValue(
|
|
210
|
+
operation: RawOperation,
|
|
211
|
+
transaction: Horizon.ServerApi.TransactionRecord,
|
|
212
|
+
type: OperationType,
|
|
213
|
+
): BigNumber {
|
|
214
|
+
let value = new BigNumber(0);
|
|
215
|
+
|
|
216
|
+
if (!operation.transaction_successful) {
|
|
217
|
+
return type === "IN" ? value : new BigNumber(transaction.fee_charged || 0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
switch (operation.type) {
|
|
221
|
+
case "create_account":
|
|
222
|
+
value = parseCurrencyUnit(currency.units[0], operation.starting_balance);
|
|
223
|
+
|
|
224
|
+
if (type === "OUT") {
|
|
225
|
+
value = value.plus(transaction.fee_charged);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return value;
|
|
229
|
+
|
|
230
|
+
case "payment":
|
|
231
|
+
case "path_payment_strict_send":
|
|
232
|
+
case "path_payment_strict_receive":
|
|
233
|
+
return parseCurrencyUnit(currency.units[0], operation.amount);
|
|
234
|
+
|
|
235
|
+
default:
|
|
236
|
+
return type !== "IN" ? new BigNumber(transaction.fee_charged) : value;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function isMemoValid(memoType: string, memoValue: string): boolean {
|
|
241
|
+
switch (memoType) {
|
|
242
|
+
case "MEMO_TEXT":
|
|
243
|
+
if (memoValue.length > 28) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case "MEMO_ID":
|
|
250
|
+
if (new BigNumber(memoValue.toString()).isNaN()) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
case "MEMO_HASH":
|
|
257
|
+
case "MEMO_RETURN":
|
|
258
|
+
if (!memoValue.length || memoValue.length !== 64) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export async function isAccountMultiSign(account: Account): Promise<boolean> {
|
|
269
|
+
const signers = await fetchSigners(account);
|
|
270
|
+
return signers.length > 1;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Returns true if address is valid, false if it's invalid (can't parse or wrong checksum)
|
|
275
|
+
*
|
|
276
|
+
* @param {*} address
|
|
277
|
+
*/
|
|
278
|
+
export function isAddressValid(address: string): boolean {
|
|
279
|
+
if (!address) return false;
|
|
280
|
+
|
|
281
|
+
// FIXME Workaround for burn address, see https://ledgerhq.atlassian.net/browse/LIVE-4014
|
|
282
|
+
if (address === STELLAR_BURN_ADDRESS) return false;
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
return StrKey.isValidEd25519PublicKey(address) || StrKey.isValidMed25519PublicKey(address);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const getRecipientAccount: CacheRes<
|
|
292
|
+
Array<{
|
|
293
|
+
account: Account;
|
|
294
|
+
recipient: string;
|
|
295
|
+
}>,
|
|
296
|
+
{
|
|
297
|
+
id: string | null;
|
|
298
|
+
isMuxedAccount: boolean;
|
|
299
|
+
assetIds: string[];
|
|
300
|
+
} | null
|
|
301
|
+
> = makeLRUCache(
|
|
302
|
+
async ({ recipient }) => await recipientAccount(recipient),
|
|
303
|
+
extract => extract.recipient,
|
|
304
|
+
{
|
|
305
|
+
max: 300,
|
|
306
|
+
ttl: 5 * 60,
|
|
307
|
+
}, // 5 minutes
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
export async function recipientAccount(address?: string): Promise<{
|
|
311
|
+
id: string | null;
|
|
312
|
+
isMuxedAccount: boolean;
|
|
313
|
+
assetIds: string[];
|
|
314
|
+
} | null> {
|
|
315
|
+
if (!address) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let accountAddress = address;
|
|
320
|
+
|
|
321
|
+
const isMuxedAccount = StrKey.isValidMed25519PublicKey(address);
|
|
322
|
+
|
|
323
|
+
if (isMuxedAccount) {
|
|
324
|
+
const muxedAccount = MuxedAccount.fromAddress(address, "0");
|
|
325
|
+
accountAddress = muxedAccount.baseAccount().accountId();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const account: AccountRecord = await loadAccount(accountAddress);
|
|
329
|
+
|
|
330
|
+
if (!account) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
id: account.id,
|
|
336
|
+
isMuxedAccount,
|
|
337
|
+
assetIds: account.balances.reduce((allAssets: any[], balance: any) => {
|
|
338
|
+
return [...allAssets, getBalanceId(balance)];
|
|
339
|
+
}, []),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function rawOperationsToOperations(
|
|
344
|
+
operations: RawOperation[],
|
|
345
|
+
addr: string,
|
|
346
|
+
accountId: string,
|
|
347
|
+
): Promise<StellarOperation[]> {
|
|
348
|
+
const supportedOperationTypes = [
|
|
349
|
+
"create_account",
|
|
350
|
+
"payment",
|
|
351
|
+
"path_payment_strict_send",
|
|
352
|
+
"path_payment_strict_receive",
|
|
353
|
+
"change_trust",
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
return Promise.all(
|
|
357
|
+
operations
|
|
358
|
+
.filter(operation => {
|
|
359
|
+
return (
|
|
360
|
+
operation.from === addr ||
|
|
361
|
+
operation.to === addr ||
|
|
362
|
+
operation.funder === addr ||
|
|
363
|
+
operation.account === addr ||
|
|
364
|
+
operation.trustor === addr ||
|
|
365
|
+
operation.source_account === addr
|
|
366
|
+
);
|
|
367
|
+
})
|
|
368
|
+
.filter(operation => supportedOperationTypes.includes(operation.type))
|
|
369
|
+
.map(operation => formatOperation(operation, accountId, addr)),
|
|
370
|
+
);
|
|
371
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { LedgerAPI4xx, LedgerAPI5xx, NetworkDown } from "@ledgerhq/errors";
|
|
2
|
+
import type { Account, Operation } from "@ledgerhq/types-live";
|
|
3
|
+
import { BigNumber } from "bignumber.js";
|
|
4
|
+
import {
|
|
5
|
+
// @ts-expect-error stellar-sdk ts definition missing?
|
|
6
|
+
AccountRecord,
|
|
7
|
+
NetworkError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
Horizon,
|
|
10
|
+
BASE_FEE,
|
|
11
|
+
Asset,
|
|
12
|
+
Operation as StellarSdkOperation,
|
|
13
|
+
Account as StellarSdkAccount,
|
|
14
|
+
Transaction as StellarSdkTransaction,
|
|
15
|
+
TransactionBuilder,
|
|
16
|
+
Networks,
|
|
17
|
+
} from "@stellar/stellar-sdk";
|
|
18
|
+
import { log } from "@ledgerhq/logs";
|
|
19
|
+
import { getEnv } from "@ledgerhq/live-env";
|
|
20
|
+
import {
|
|
21
|
+
type BalanceAsset,
|
|
22
|
+
type NetworkInfo,
|
|
23
|
+
type RawOperation,
|
|
24
|
+
type Signer,
|
|
25
|
+
NetworkCongestionLevel,
|
|
26
|
+
} from "../types";
|
|
27
|
+
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
28
|
+
import {
|
|
29
|
+
getAccountSpendableBalance,
|
|
30
|
+
getReservedBalance,
|
|
31
|
+
rawOperationsToOperations,
|
|
32
|
+
} from "../logic";
|
|
33
|
+
import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies";
|
|
34
|
+
|
|
35
|
+
const LIMIT = getEnv("API_STELLAR_HORIZON_FETCH_LIMIT");
|
|
36
|
+
const FALLBACK_BASE_FEE = 100;
|
|
37
|
+
const TRESHOLD_LOW = 0.5;
|
|
38
|
+
const TRESHOLD_MEDIUM = 0.75;
|
|
39
|
+
const currency = getCryptoCurrencyById("stellar");
|
|
40
|
+
const server = new Horizon.Server(getEnv("API_STELLAR_HORIZON"));
|
|
41
|
+
|
|
42
|
+
// Constants
|
|
43
|
+
export const BASE_RESERVE = 0.5;
|
|
44
|
+
export const BASE_RESERVE_MIN_COUNT = 2;
|
|
45
|
+
export const MIN_BALANCE = 1;
|
|
46
|
+
|
|
47
|
+
// Due to the inconsistency between the axios version (1.6.5) used by `stellar-sdk`
|
|
48
|
+
// and the version (0.26.1) used by `@ledgerhq/live-network/network`, it is not possible to use the interceptors
|
|
49
|
+
// provided by `@ledgerhq/live-network/network`.
|
|
50
|
+
Horizon.AxiosClient.interceptors.request.use(config => {
|
|
51
|
+
if (!getEnv("ENABLE_NETWORK_LOGS")) {
|
|
52
|
+
return config;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { url, method, data } = config;
|
|
56
|
+
log("network", `${method} ${url}`, { data });
|
|
57
|
+
return config;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
Horizon.AxiosClient.interceptors.response.use(response => {
|
|
61
|
+
if (getEnv("ENABLE_NETWORK_LOGS")) {
|
|
62
|
+
const { url, method } = response.config;
|
|
63
|
+
log("network-success", `${response.status} ${method} ${url}`, { data: response.data });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// FIXME: workaround for the Stellar SDK not using the correct URL: the "next" URL
|
|
67
|
+
// included in server responses points to the node itself instead of our reverse proxy...
|
|
68
|
+
// (https://github.com/stellar/js-stellar-sdk/issues/637)
|
|
69
|
+
const next_href = response?.data?._links?.next?.href;
|
|
70
|
+
|
|
71
|
+
if (next_href) {
|
|
72
|
+
const next = new URL(next_href);
|
|
73
|
+
next.host = new URL(getEnv("API_STELLAR_HORIZON")).host;
|
|
74
|
+
response.data._links.next.href = next.toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return response;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function getFormattedAmount(amount: BigNumber) {
|
|
81
|
+
return amount.div(new BigNumber(10).pow(currency.units[0].magnitude)).toString(10);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function fetchBaseFee(): Promise<{
|
|
85
|
+
baseFee: number;
|
|
86
|
+
recommendedFee: number;
|
|
87
|
+
networkCongestionLevel: NetworkCongestionLevel;
|
|
88
|
+
}> {
|
|
89
|
+
// For tests
|
|
90
|
+
if (getEnv("API_STELLAR_HORIZON_STATIC_FEE")) {
|
|
91
|
+
return {
|
|
92
|
+
baseFee: 100,
|
|
93
|
+
recommendedFee: 100,
|
|
94
|
+
networkCongestionLevel: NetworkCongestionLevel.LOW,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const baseFee = new BigNumber(BASE_FEE).toNumber() || FALLBACK_BASE_FEE;
|
|
99
|
+
let recommendedFee = baseFee;
|
|
100
|
+
let networkCongestionLevel = NetworkCongestionLevel.MEDIUM;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const feeStats = await server.feeStats();
|
|
104
|
+
const ledgerCapacityUsage = feeStats.ledger_capacity_usage;
|
|
105
|
+
recommendedFee = new BigNumber(feeStats.fee_charged.mode).toNumber();
|
|
106
|
+
|
|
107
|
+
if (
|
|
108
|
+
new BigNumber(ledgerCapacityUsage).toNumber() > TRESHOLD_LOW &&
|
|
109
|
+
new BigNumber(ledgerCapacityUsage).toNumber() <= TRESHOLD_MEDIUM
|
|
110
|
+
) {
|
|
111
|
+
networkCongestionLevel = NetworkCongestionLevel.MEDIUM;
|
|
112
|
+
} else if (new BigNumber(ledgerCapacityUsage).toNumber() > TRESHOLD_MEDIUM) {
|
|
113
|
+
networkCongestionLevel = NetworkCongestionLevel.HIGH;
|
|
114
|
+
} else {
|
|
115
|
+
networkCongestionLevel = NetworkCongestionLevel.LOW;
|
|
116
|
+
}
|
|
117
|
+
} catch (e) {
|
|
118
|
+
// do nothing, will use defaults
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
baseFee,
|
|
123
|
+
recommendedFee,
|
|
124
|
+
networkCongestionLevel,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get all account-related data
|
|
130
|
+
*
|
|
131
|
+
* @async
|
|
132
|
+
* @param {string} addr
|
|
133
|
+
*/
|
|
134
|
+
export async function fetchAccount(addr: string): Promise<{
|
|
135
|
+
blockHeight: number;
|
|
136
|
+
balance: BigNumber;
|
|
137
|
+
spendableBalance: BigNumber;
|
|
138
|
+
assets: BalanceAsset[];
|
|
139
|
+
}> {
|
|
140
|
+
let account: Horizon.ServerApi.AccountRecord = {} as Horizon.ServerApi.AccountRecord;
|
|
141
|
+
let assets: BalanceAsset[] = [];
|
|
142
|
+
let balance = "0";
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
account = await server.accounts().accountId(addr).call();
|
|
146
|
+
balance =
|
|
147
|
+
account.balances?.find(balance => {
|
|
148
|
+
return balance.asset_type === "native";
|
|
149
|
+
})?.balance || "0";
|
|
150
|
+
// Getting all non-native (XLM) assets on the account
|
|
151
|
+
assets = account.balances?.filter(balance => {
|
|
152
|
+
return balance.asset_type !== "native";
|
|
153
|
+
}) as BalanceAsset[];
|
|
154
|
+
} catch (e) {
|
|
155
|
+
balance = "0";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const formattedBalance = parseCurrencyUnit(currency.units[0], balance);
|
|
159
|
+
|
|
160
|
+
const spendableBalance = await getAccountSpendableBalance(formattedBalance, account);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
blockHeight: account.sequence ? new BigNumber(account.sequence).toNumber() : 0,
|
|
164
|
+
balance: formattedBalance,
|
|
165
|
+
spendableBalance,
|
|
166
|
+
assets,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Fetch all operations for a single account from indexer
|
|
172
|
+
*
|
|
173
|
+
* @param {string} accountId
|
|
174
|
+
* @param {string} addr
|
|
175
|
+
* @param {string} order - "desc" or "asc" order of returned records
|
|
176
|
+
* @param {string} cursor - point to start fetching records
|
|
177
|
+
*
|
|
178
|
+
* @return {Operation[]}
|
|
179
|
+
*/
|
|
180
|
+
export async function fetchOperations({
|
|
181
|
+
accountId,
|
|
182
|
+
addr,
|
|
183
|
+
order,
|
|
184
|
+
cursor,
|
|
185
|
+
}: {
|
|
186
|
+
accountId: string;
|
|
187
|
+
addr: string;
|
|
188
|
+
order: "asc" | "desc";
|
|
189
|
+
cursor: string;
|
|
190
|
+
}): Promise<Operation[]> {
|
|
191
|
+
if (!addr) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let operations: Operation[] = [];
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
let rawOperations = await server
|
|
199
|
+
.operations()
|
|
200
|
+
.forAccount(addr)
|
|
201
|
+
.limit(LIMIT)
|
|
202
|
+
.order(order)
|
|
203
|
+
.cursor(cursor)
|
|
204
|
+
.includeFailed(true)
|
|
205
|
+
.join("transactions")
|
|
206
|
+
.call();
|
|
207
|
+
|
|
208
|
+
if (!rawOperations || !rawOperations.records.length) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
operations = operations.concat(
|
|
213
|
+
await rawOperationsToOperations(rawOperations.records as RawOperation[], addr, accountId),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
while (rawOperations.records.length > 0) {
|
|
217
|
+
rawOperations = await rawOperations.next();
|
|
218
|
+
operations = operations.concat(
|
|
219
|
+
await rawOperationsToOperations(rawOperations.records as RawOperation[], addr, accountId),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return operations;
|
|
224
|
+
} catch (e: unknown) {
|
|
225
|
+
// FIXME: terrible hacks, because Stellar SDK fails to cast network failures to typed errors in react-native...
|
|
226
|
+
// (https://github.com/stellar/js-stellar-sdk/issues/638)
|
|
227
|
+
const errorMsg = e ? String(e) : "";
|
|
228
|
+
|
|
229
|
+
if (e instanceof NotFoundError || errorMsg.match(/status code 404/)) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (errorMsg.match(/status code 4[0-9]{2}/)) {
|
|
234
|
+
throw new LedgerAPI4xx();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (errorMsg.match(/status code 5[0-9]{2}/)) {
|
|
238
|
+
throw new LedgerAPI5xx();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
e instanceof NetworkError ||
|
|
243
|
+
errorMsg.match(/ECONNRESET|ECONNREFUSED|ENOTFOUND|EPIPE|ETIMEDOUT/) ||
|
|
244
|
+
errorMsg.match(/undefined is not an object/)
|
|
245
|
+
) {
|
|
246
|
+
throw new NetworkDown();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
throw e;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function fetchAccountNetworkInfo(account: Account): Promise<NetworkInfo> {
|
|
254
|
+
try {
|
|
255
|
+
const extendedAccount = await server.accounts().accountId(account.freshAddress).call();
|
|
256
|
+
const baseReserve = getReservedBalance(extendedAccount);
|
|
257
|
+
const { recommendedFee, networkCongestionLevel, baseFee } = await fetchBaseFee();
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
family: "stellar",
|
|
261
|
+
fees: new BigNumber(recommendedFee.toString()),
|
|
262
|
+
baseFee: new BigNumber(baseFee.toString()),
|
|
263
|
+
baseReserve,
|
|
264
|
+
networkCongestionLevel,
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
family: "stellar",
|
|
269
|
+
fees: new BigNumber(0),
|
|
270
|
+
baseFee: new BigNumber(0),
|
|
271
|
+
baseReserve: new BigNumber(0),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export async function fetchSequence(account: Account): Promise<BigNumber> {
|
|
277
|
+
const extendedAccount = await loadAccount(account.freshAddress);
|
|
278
|
+
return extendedAccount ? new BigNumber(extendedAccount.sequence) : new BigNumber(0);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function fetchSigners(account: Account): Promise<Signer[]> {
|
|
282
|
+
try {
|
|
283
|
+
const extendedAccount = await server.accounts().accountId(account.freshAddress).call();
|
|
284
|
+
return extendedAccount.signers;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function broadcastTransaction(signedTransaction: string): Promise<string> {
|
|
291
|
+
const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC);
|
|
292
|
+
const res = await server.submitTransaction(transaction, {
|
|
293
|
+
skipMemoRequiredCheck: true,
|
|
294
|
+
});
|
|
295
|
+
return res.hash;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function buildPaymentOperation({
|
|
299
|
+
destination,
|
|
300
|
+
amount,
|
|
301
|
+
assetCode,
|
|
302
|
+
assetIssuer,
|
|
303
|
+
}: {
|
|
304
|
+
destination: string;
|
|
305
|
+
amount: BigNumber;
|
|
306
|
+
assetCode: string | undefined;
|
|
307
|
+
assetIssuer: string | undefined;
|
|
308
|
+
}) {
|
|
309
|
+
const formattedAmount = getFormattedAmount(amount);
|
|
310
|
+
// Non-native assets should always have asset code and asset issuer. If an
|
|
311
|
+
// asset doesn't have both, we assume it is native asset.
|
|
312
|
+
const asset = assetCode && assetIssuer ? new Asset(assetCode, assetIssuer) : Asset.native();
|
|
313
|
+
return StellarSdkOperation.payment({
|
|
314
|
+
destination: destination,
|
|
315
|
+
amount: formattedAmount,
|
|
316
|
+
asset,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function buildCreateAccountOperation(destination: string, amount: BigNumber) {
|
|
321
|
+
const formattedAmount = getFormattedAmount(amount);
|
|
322
|
+
return StellarSdkOperation.createAccount({
|
|
323
|
+
destination: destination,
|
|
324
|
+
startingBalance: formattedAmount,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function buildChangeTrustOperation(assetCode: string, assetIssuer: string) {
|
|
329
|
+
return StellarSdkOperation.changeTrust({
|
|
330
|
+
asset: new Asset(assetCode, assetIssuer),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function buildTransactionBuilder(source: StellarSdkAccount, fee: BigNumber) {
|
|
335
|
+
const formattedFee = fee.toString();
|
|
336
|
+
return new TransactionBuilder(source, {
|
|
337
|
+
fee: formattedFee,
|
|
338
|
+
networkPassphrase: Networks.PUBLIC,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export async function loadAccount(addr: string): Promise<AccountRecord | null> {
|
|
343
|
+
if (!addr || !addr.length) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
return await server.loadAccount(addr);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|