@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.
Files changed (233) hide show
  1. package/.eslintrc.js +20 -0
  2. package/.turbo/turbo-build.log +4 -0
  3. package/.unimportedrc.json +35 -0
  4. package/CHANGELOG.md +13 -0
  5. package/LICENSE.txt +21 -0
  6. package/jest.config.js +8 -0
  7. package/lib/bridge/bridge.integration.test.d.ts +4 -0
  8. package/lib/bridge/bridge.integration.test.d.ts.map +1 -0
  9. package/lib/bridge/bridge.integration.test.js +317 -0
  10. package/lib/bridge/bridge.integration.test.js.map +1 -0
  11. package/lib/bridge/index.d.ts +10 -0
  12. package/lib/bridge/index.d.ts.map +1 -0
  13. package/lib/bridge/index.js +72 -0
  14. package/lib/bridge/index.js.map +1 -0
  15. package/lib/broadcast.d.ts +9 -0
  16. package/lib/broadcast.d.ts.map +1 -0
  17. package/lib/broadcast.js +26 -0
  18. package/lib/broadcast.js.map +1 -0
  19. package/lib/buildOptimisticOperation.d.ts +4 -0
  20. package/lib/buildOptimisticOperation.d.ts.map +1 -0
  21. package/lib/buildOptimisticOperation.js +72 -0
  22. package/lib/buildOptimisticOperation.js.map +1 -0
  23. package/lib/buildTransaction.d.ts +10 -0
  24. package/lib/buildTransaction.d.ts.map +1 -0
  25. package/lib/buildTransaction.js +97 -0
  26. package/lib/buildTransaction.js.map +1 -0
  27. package/lib/cli.d.ts +38 -0
  28. package/lib/cli.d.ts.map +1 -0
  29. package/lib/cli.js +83 -0
  30. package/lib/cli.js.map +1 -0
  31. package/lib/config.d.ts +5 -0
  32. package/lib/config.d.ts.map +1 -0
  33. package/lib/config.js +17 -0
  34. package/lib/config.js.map +1 -0
  35. package/lib/createTransaction.d.ts +10 -0
  36. package/lib/createTransaction.d.ts.map +1 -0
  37. package/lib/createTransaction.js +26 -0
  38. package/lib/createTransaction.js.map +1 -0
  39. package/lib/deviceTransactionConfig.d.ts +24 -0
  40. package/lib/deviceTransactionConfig.d.ts.map +1 -0
  41. package/lib/deviceTransactionConfig.js +41 -0
  42. package/lib/deviceTransactionConfig.js.map +1 -0
  43. package/lib/errors.d.ts +37 -0
  44. package/lib/errors.d.ts.map +1 -0
  45. package/lib/errors.js +17 -0
  46. package/lib/errors.js.map +1 -0
  47. package/lib/estimateMaxSpendable.d.ts +5 -0
  48. package/lib/estimateMaxSpendable.d.ts.map +1 -0
  49. package/lib/estimateMaxSpendable.js +32 -0
  50. package/lib/estimateMaxSpendable.js.map +1 -0
  51. package/lib/getTransactionStatus.d.ts +5 -0
  52. package/lib/getTransactionStatus.d.ts.map +1 -0
  53. package/lib/getTransactionStatus.js +170 -0
  54. package/lib/getTransactionStatus.js.map +1 -0
  55. package/lib/hw-getAddress.d.ts +6 -0
  56. package/lib/hw-getAddress.d.ts.map +1 -0
  57. package/lib/hw-getAddress.js +28 -0
  58. package/lib/hw-getAddress.js.map +1 -0
  59. package/lib/logic.d.ts +36 -0
  60. package/lib/logic.d.ts.map +1 -0
  61. package/lib/logic.js +289 -0
  62. package/lib/logic.js.map +1 -0
  63. package/lib/network/horizon.d.ts +71 -0
  64. package/lib/network/horizon.d.ts.map +1 -0
  65. package/lib/network/horizon.js +300 -0
  66. package/lib/network/horizon.js.map +1 -0
  67. package/lib/network/index.d.ts +2 -0
  68. package/lib/network/index.d.ts.map +1 -0
  69. package/lib/network/index.js +20 -0
  70. package/lib/network/index.js.map +1 -0
  71. package/lib/prepareTransaction.d.ts +5 -0
  72. package/lib/prepareTransaction.d.ts.map +1 -0
  73. package/lib/prepareTransaction.js +41 -0
  74. package/lib/prepareTransaction.js.map +1 -0
  75. package/lib/signOperation.d.ts +6 -0
  76. package/lib/signOperation.d.ts.map +1 -0
  77. package/lib/signOperation.js +56 -0
  78. package/lib/signOperation.js.map +1 -0
  79. package/lib/specs.d.ts +7 -0
  80. package/lib/specs.d.ts.map +1 -0
  81. package/lib/specs.js +234 -0
  82. package/lib/specs.js.map +1 -0
  83. package/lib/speculos-deviceActions.d.ts +4 -0
  84. package/lib/speculos-deviceActions.d.ts.map +1 -0
  85. package/lib/speculos-deviceActions.js +99 -0
  86. package/lib/speculos-deviceActions.js.map +1 -0
  87. package/lib/synchronization.d.ts +4 -0
  88. package/lib/synchronization.d.ts.map +1 -0
  89. package/lib/synchronization.js +73 -0
  90. package/lib/synchronization.js.map +1 -0
  91. package/lib/tokens.d.ts +12 -0
  92. package/lib/tokens.d.ts.map +1 -0
  93. package/lib/tokens.js +58 -0
  94. package/lib/tokens.js.map +1 -0
  95. package/lib/transaction.d.ts +15 -0
  96. package/lib/transaction.d.ts.map +1 -0
  97. package/lib/transaction.js +63 -0
  98. package/lib/transaction.js.map +1 -0
  99. package/lib/types/index.d.ts +89 -0
  100. package/lib/types/index.d.ts.map +1 -0
  101. package/lib/types/index.js +26 -0
  102. package/lib/types/index.js.map +1 -0
  103. package/lib/types/signer.d.ts +10 -0
  104. package/lib/types/signer.d.ts.map +1 -0
  105. package/lib/types/signer.js +3 -0
  106. package/lib/types/signer.js.map +1 -0
  107. package/lib-es/bridge/bridge.integration.test.d.ts +4 -0
  108. package/lib-es/bridge/bridge.integration.test.d.ts.map +1 -0
  109. package/lib-es/bridge/bridge.integration.test.js +311 -0
  110. package/lib-es/bridge/bridge.integration.test.js.map +1 -0
  111. package/lib-es/bridge/index.d.ts +10 -0
  112. package/lib-es/bridge/index.d.ts.map +1 -0
  113. package/lib-es/bridge/index.js +65 -0
  114. package/lib-es/bridge/index.js.map +1 -0
  115. package/lib-es/broadcast.d.ts +9 -0
  116. package/lib-es/broadcast.d.ts.map +1 -0
  117. package/lib-es/broadcast.js +22 -0
  118. package/lib-es/broadcast.js.map +1 -0
  119. package/lib-es/buildOptimisticOperation.d.ts +4 -0
  120. package/lib-es/buildOptimisticOperation.d.ts.map +1 -0
  121. package/lib-es/buildOptimisticOperation.js +65 -0
  122. package/lib-es/buildOptimisticOperation.js.map +1 -0
  123. package/lib-es/buildTransaction.d.ts +10 -0
  124. package/lib-es/buildTransaction.d.ts.map +1 -0
  125. package/lib-es/buildTransaction.js +90 -0
  126. package/lib-es/buildTransaction.js.map +1 -0
  127. package/lib-es/cli.d.ts +38 -0
  128. package/lib-es/cli.d.ts.map +1 -0
  129. package/lib-es/cli.js +77 -0
  130. package/lib-es/cli.js.map +1 -0
  131. package/lib-es/config.d.ts +5 -0
  132. package/lib-es/config.d.ts.map +1 -0
  133. package/lib-es/config.js +12 -0
  134. package/lib-es/config.js.map +1 -0
  135. package/lib-es/createTransaction.d.ts +10 -0
  136. package/lib-es/createTransaction.d.ts.map +1 -0
  137. package/lib-es/createTransaction.js +22 -0
  138. package/lib-es/createTransaction.js.map +1 -0
  139. package/lib-es/deviceTransactionConfig.d.ts +24 -0
  140. package/lib-es/deviceTransactionConfig.d.ts.map +1 -0
  141. package/lib-es/deviceTransactionConfig.js +39 -0
  142. package/lib-es/deviceTransactionConfig.js.map +1 -0
  143. package/lib-es/errors.d.ts +37 -0
  144. package/lib-es/errors.d.ts.map +1 -0
  145. package/lib-es/errors.js +14 -0
  146. package/lib-es/errors.js.map +1 -0
  147. package/lib-es/estimateMaxSpendable.d.ts +5 -0
  148. package/lib-es/estimateMaxSpendable.d.ts.map +1 -0
  149. package/lib-es/estimateMaxSpendable.js +25 -0
  150. package/lib-es/estimateMaxSpendable.js.map +1 -0
  151. package/lib-es/getTransactionStatus.d.ts +5 -0
  152. package/lib-es/getTransactionStatus.d.ts.map +1 -0
  153. package/lib-es/getTransactionStatus.js +166 -0
  154. package/lib-es/getTransactionStatus.js.map +1 -0
  155. package/lib-es/hw-getAddress.d.ts +6 -0
  156. package/lib-es/hw-getAddress.d.ts.map +1 -0
  157. package/lib-es/hw-getAddress.js +26 -0
  158. package/lib-es/hw-getAddress.js.map +1 -0
  159. package/lib-es/logic.d.ts +36 -0
  160. package/lib-es/logic.d.ts.map +1 -0
  161. package/lib-es/logic.js +274 -0
  162. package/lib-es/logic.js.map +1 -0
  163. package/lib-es/network/horizon.d.ts +71 -0
  164. package/lib-es/network/horizon.d.ts.map +1 -0
  165. package/lib-es/network/horizon.js +285 -0
  166. package/lib-es/network/horizon.js.map +1 -0
  167. package/lib-es/network/index.d.ts +2 -0
  168. package/lib-es/network/index.d.ts.map +1 -0
  169. package/lib-es/network/index.js +2 -0
  170. package/lib-es/network/index.js.map +1 -0
  171. package/lib-es/prepareTransaction.d.ts +5 -0
  172. package/lib-es/prepareTransaction.d.ts.map +1 -0
  173. package/lib-es/prepareTransaction.js +34 -0
  174. package/lib-es/prepareTransaction.js.map +1 -0
  175. package/lib-es/signOperation.d.ts +6 -0
  176. package/lib-es/signOperation.d.ts.map +1 -0
  177. package/lib-es/signOperation.js +52 -0
  178. package/lib-es/signOperation.js.map +1 -0
  179. package/lib-es/specs.d.ts +7 -0
  180. package/lib-es/specs.d.ts.map +1 -0
  181. package/lib-es/specs.js +229 -0
  182. package/lib-es/specs.js.map +1 -0
  183. package/lib-es/speculos-deviceActions.d.ts +4 -0
  184. package/lib-es/speculos-deviceActions.d.ts.map +1 -0
  185. package/lib-es/speculos-deviceActions.js +96 -0
  186. package/lib-es/speculos-deviceActions.js.map +1 -0
  187. package/lib-es/synchronization.d.ts +4 -0
  188. package/lib-es/synchronization.d.ts.map +1 -0
  189. package/lib-es/synchronization.js +69 -0
  190. package/lib-es/synchronization.js.map +1 -0
  191. package/lib-es/tokens.d.ts +12 -0
  192. package/lib-es/tokens.d.ts.map +1 -0
  193. package/lib-es/tokens.js +50 -0
  194. package/lib-es/tokens.js.map +1 -0
  195. package/lib-es/transaction.d.ts +15 -0
  196. package/lib-es/transaction.d.ts.map +1 -0
  197. package/lib-es/transaction.js +59 -0
  198. package/lib-es/transaction.js.map +1 -0
  199. package/lib-es/types/index.d.ts +89 -0
  200. package/lib-es/types/index.d.ts.map +1 -0
  201. package/lib-es/types/index.js +9 -0
  202. package/lib-es/types/index.js.map +1 -0
  203. package/lib-es/types/signer.d.ts +10 -0
  204. package/lib-es/types/signer.d.ts.map +1 -0
  205. package/lib-es/types/signer.js +2 -0
  206. package/lib-es/types/signer.js.map +1 -0
  207. package/package.json +80 -0
  208. package/src/bridge/bridge.integration.test.ts +373 -0
  209. package/src/bridge/index.ts +77 -0
  210. package/src/broadcast.ts +20 -0
  211. package/src/buildOptimisticOperation.ts +63 -0
  212. package/src/buildTransaction.ts +106 -0
  213. package/src/cli.ts +107 -0
  214. package/src/config.ts +18 -0
  215. package/src/createTransaction.ts +25 -0
  216. package/src/deviceTransactionConfig.ts +75 -0
  217. package/src/errors.ts +20 -0
  218. package/src/estimateMaxSpendable.ts +29 -0
  219. package/src/getTransactionStatus.ts +207 -0
  220. package/src/hw-getAddress.ts +24 -0
  221. package/src/logic.ts +371 -0
  222. package/src/network/horizon.ts +352 -0
  223. package/src/network/index.ts +17 -0
  224. package/src/prepareTransaction.ts +35 -0
  225. package/src/signOperation.ts +58 -0
  226. package/src/specs.ts +290 -0
  227. package/src/speculos-deviceActions.ts +117 -0
  228. package/src/synchronization.ts +80 -0
  229. package/src/tokens.ts +98 -0
  230. package/src/transaction.ts +99 -0
  231. package/src/types/index.ts +112 -0
  232. package/src/types/signer.ts +9 -0
  233. package/tsconfig.json +13 -0
@@ -0,0 +1,63 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { Account } from "@ledgerhq/types-live";
3
+ import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
4
+ import { StellarOperation, Transaction } from "./types";
5
+ import { getAmountValue } from "./logic";
6
+ import { fetchSequence } from "./network";
7
+
8
+ export async function buildOptimisticOperation(
9
+ account: Account,
10
+ transaction: Transaction,
11
+ ): Promise<StellarOperation> {
12
+ const transactionSequenceNumber = await fetchSequence(account);
13
+ const fees = transaction.fees ?? new BigNumber(0);
14
+ const type = transaction.mode === "changeTrust" ? "OPT_IN" : "OUT";
15
+
16
+ const operation: StellarOperation = {
17
+ id: encodeOperationId(account.id, "", type),
18
+ hash: "",
19
+ type,
20
+ value: transaction.subAccountId ? fees : getAmountValue(account, transaction, fees),
21
+ fee: fees,
22
+ blockHash: null,
23
+ blockHeight: null,
24
+ senders: [account.freshAddress],
25
+ recipients: [transaction.recipient],
26
+ accountId: account.id,
27
+ date: new Date(),
28
+ transactionSequenceNumber: transactionSequenceNumber?.plus(1).toNumber(),
29
+ extra: {
30
+ ledgerOpType: type,
31
+ },
32
+ };
33
+
34
+ const { subAccountId } = transaction;
35
+ const { subAccounts } = account;
36
+
37
+ const tokenAccount = !subAccountId
38
+ ? null
39
+ : subAccounts && subAccounts.find(ta => ta.id === subAccountId);
40
+
41
+ if (tokenAccount && subAccountId) {
42
+ operation.subOperations = [
43
+ {
44
+ id: `${subAccountId}--OUT`,
45
+ hash: "",
46
+ type: "OUT",
47
+ value: transaction.useAllAmount ? tokenAccount.balance : transaction.amount,
48
+ fee: new BigNumber(0),
49
+ blockHash: null,
50
+ blockHeight: null,
51
+ senders: [account.freshAddress],
52
+ recipients: [transaction.recipient],
53
+ accountId: subAccountId,
54
+ date: new Date(),
55
+ extra: {
56
+ ledgerOpType: type,
57
+ },
58
+ },
59
+ ];
60
+ }
61
+
62
+ return operation;
63
+ }
@@ -0,0 +1,106 @@
1
+ import invariant from "invariant";
2
+ import { Memo, Operation as StellarSdkOperation, xdr } from "@stellar/stellar-sdk";
3
+ import { AmountRequired, FeeNotLoaded, NetworkDown } from "@ledgerhq/errors";
4
+ import type { Account } from "@ledgerhq/types-live";
5
+ import type { Transaction } from "./types";
6
+ import {
7
+ buildPaymentOperation,
8
+ buildCreateAccountOperation,
9
+ buildTransactionBuilder,
10
+ buildChangeTrustOperation,
11
+ loadAccount,
12
+ } from "./network";
13
+ import { getRecipientAccount, getAmountValue } from "./logic";
14
+ import { StellarAssetRequired, StellarMuxedAccountNotExist } from "./errors";
15
+
16
+ /**
17
+ * @param {Account} account
18
+ * @param {Transaction} transaction
19
+ */
20
+ export async function buildTransaction(account: Account, transaction: Transaction) {
21
+ const { recipient, networkInfo, fees, memoType, memoValue, mode, assetCode, assetIssuer } =
22
+ transaction;
23
+
24
+ if (!fees) {
25
+ throw new FeeNotLoaded();
26
+ }
27
+
28
+ const source = await loadAccount(account.freshAddress);
29
+
30
+ if (!source) {
31
+ throw new NetworkDown();
32
+ }
33
+
34
+ invariant(networkInfo && networkInfo.family === "stellar", "stellar family");
35
+
36
+ const transactionBuilder = buildTransactionBuilder(source, fees);
37
+ let operation: xdr.Operation<StellarSdkOperation.ChangeTrust> | null = null;
38
+
39
+ if (mode === "changeTrust") {
40
+ if (!assetCode || !assetIssuer) {
41
+ throw new StellarAssetRequired("");
42
+ }
43
+
44
+ operation = buildChangeTrustOperation(assetCode, assetIssuer);
45
+ } else {
46
+ // Payment
47
+ const amount = getAmountValue(account, transaction, fees);
48
+
49
+ if (!amount) {
50
+ throw new AmountRequired();
51
+ }
52
+
53
+ const recipientAccount = await getRecipientAccount({
54
+ account,
55
+ recipient: transaction.recipient,
56
+ });
57
+
58
+ if (recipientAccount?.id) {
59
+ operation = buildPaymentOperation({
60
+ destination: recipient,
61
+ amount,
62
+ assetCode,
63
+ assetIssuer,
64
+ });
65
+ } else {
66
+ if (recipientAccount?.isMuxedAccount) {
67
+ throw new StellarMuxedAccountNotExist("");
68
+ }
69
+
70
+ operation = buildCreateAccountOperation(recipient, amount);
71
+ }
72
+ }
73
+
74
+ transactionBuilder.addOperation(operation);
75
+
76
+ let memo: Memo | null = null;
77
+
78
+ if (memoType && memoValue) {
79
+ switch (memoType) {
80
+ case "MEMO_TEXT":
81
+ memo = Memo.text(memoValue);
82
+ break;
83
+
84
+ case "MEMO_ID":
85
+ memo = Memo.id(memoValue);
86
+ break;
87
+
88
+ case "MEMO_HASH":
89
+ memo = Memo.hash(memoValue);
90
+ break;
91
+
92
+ case "MEMO_RETURN":
93
+ memo = Memo.return(memoValue);
94
+ break;
95
+ }
96
+ }
97
+
98
+ if (memo) {
99
+ transactionBuilder.addMemo(memo);
100
+ }
101
+
102
+ const built = transactionBuilder.setTimeout(0).build();
103
+ return built;
104
+ }
105
+
106
+ export default buildTransaction;
package/src/cli.ts ADDED
@@ -0,0 +1,107 @@
1
+ import invariant from "invariant";
2
+ import type { AccountLike, Account, AccountLikeArray } from "@ledgerhq/types-live";
3
+ import { getAccountCurrency } from "@ledgerhq/coin-framework/account/helpers";
4
+ import type { Transaction } from "./types";
5
+ import { getAssetIdFromTokenId } from "./tokens";
6
+
7
+ const options = [
8
+ {
9
+ name: "fee",
10
+ type: String,
11
+ desc: "how much fee",
12
+ },
13
+ {
14
+ name: "memoType",
15
+ type: String,
16
+ desc: "stellar memo type",
17
+ },
18
+ {
19
+ name: "memoValue",
20
+ type: String,
21
+ desc: "stellar memo value",
22
+ },
23
+ {
24
+ name: "mode",
25
+ type: String,
26
+ desc: "change operation type",
27
+ },
28
+ {
29
+ name: "assetIssuer",
30
+ type: String,
31
+ desc: "Asset issuer",
32
+ },
33
+ {
34
+ name: "assetCode",
35
+ type: String,
36
+ desc: "Same as token",
37
+ },
38
+ ] as const;
39
+
40
+ function inferTransactions(
41
+ transactions: Array<{
42
+ account: AccountLike;
43
+ transaction: Transaction;
44
+ }>,
45
+ opts: Record<string, any>,
46
+ ): Transaction[] {
47
+ return transactions.map(({ transaction, account }) => {
48
+ invariant(transaction.family === "stellar", "stellar family");
49
+
50
+ return {
51
+ ...transaction,
52
+ subAccountId: account.type === "TokenAccount" ? account.id : null,
53
+ memoType: opts.memoType,
54
+ memoValue: opts.memoValue,
55
+ mode: opts.mode ?? "send",
56
+ assetCode: opts.token,
57
+ assetIssuer: opts.assetIssuer,
58
+ };
59
+ });
60
+ }
61
+
62
+ function inferAccounts(account: Account, opts: Record<string, any>): AccountLikeArray {
63
+ invariant(account.currency.family === "stellar", "stellar family");
64
+
65
+ if (opts.subAccountId) {
66
+ const assetSubAccount = account.subAccounts?.find(a => a.id === opts.subAccountId);
67
+
68
+ if (!assetSubAccount) {
69
+ throw new Error(`${opts.subAccountId} asset not found`);
70
+ }
71
+
72
+ return [assetSubAccount];
73
+ }
74
+
75
+ if (opts.assetCode) {
76
+ const subAccounts = account.subAccounts || [];
77
+
78
+ const subAccount = subAccounts.find(sa => {
79
+ const currency = getAccountCurrency(sa);
80
+ return (
81
+ opts.assetCode.toLowerCase() === currency.ticker.toLowerCase() ||
82
+ opts.assetCode.toLowerCase() === getAssetIdFromTokenId(currency.id)
83
+ );
84
+ });
85
+
86
+ if (!subAccount) {
87
+ throw new Error(
88
+ "token account '" +
89
+ opts.assetCode +
90
+ "' not found. Available: " +
91
+ subAccounts.map(t => getAccountCurrency(t).ticker).join(", "),
92
+ );
93
+ }
94
+
95
+ return [subAccount];
96
+ }
97
+
98
+ return [account];
99
+ }
100
+
101
+ export default function makeCliTools() {
102
+ return {
103
+ options,
104
+ inferTransactions,
105
+ inferAccounts,
106
+ };
107
+ }
package/src/config.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { CurrencyConfig, CoinConfig } from "@ledgerhq/coin-framework/config";
2
+ import { MissingCoinConfig } from "@ledgerhq/coin-framework/errors";
3
+
4
+ export type StellarCoinConfig = CurrencyConfig;
5
+
6
+ let coinConfig: CoinConfig<StellarCoinConfig> | undefined;
7
+
8
+ export function setCoinConfig(config: CoinConfig<StellarCoinConfig>): void {
9
+ coinConfig = config;
10
+ }
11
+
12
+ export function getCoinConfig(): StellarCoinConfig {
13
+ if (!coinConfig) {
14
+ throw new MissingCoinConfig();
15
+ }
16
+
17
+ return coinConfig();
18
+ }
@@ -0,0 +1,25 @@
1
+ import { BigNumber } from "bignumber.js";
2
+ import { AccountBridge } from "@ledgerhq/types-live";
3
+ import type { Transaction } from "./types";
4
+
5
+ /**
6
+ * Create an empty transaction
7
+ *
8
+ * @returns {Transaction}
9
+ */
10
+ export const createTransaction: AccountBridge<Transaction>["createTransaction"] = () => ({
11
+ family: "stellar",
12
+ amount: new BigNumber(0),
13
+ baseReserve: null,
14
+ networkInfo: null,
15
+ fees: null,
16
+ recipient: "",
17
+ memoValue: null,
18
+ memoType: null,
19
+ useAllAmount: false,
20
+ mode: "send",
21
+ assetCode: "",
22
+ assetIssuer: "",
23
+ });
24
+
25
+ export default createTransaction;
@@ -0,0 +1,75 @@
1
+ import type { AccountLike, Account } from "@ledgerhq/types-live";
2
+ import type { CommonDeviceTransactionField as DeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common";
3
+ import type { Transaction, TransactionStatus } from "./types";
4
+
5
+ export type ExtraDeviceTransactionField =
6
+ | {
7
+ type: "stellar.memo";
8
+ label: string;
9
+ }
10
+ | {
11
+ type: "stellar.network";
12
+ label: string;
13
+ }
14
+ | {
15
+ type: "stellar.assetCode";
16
+ label: string;
17
+ }
18
+ | {
19
+ type: "stellar.assetIssuer";
20
+ label: string;
21
+ };
22
+
23
+ function getDeviceTransactionConfig({
24
+ status: { amount, estimatedFees },
25
+ transaction,
26
+ }: {
27
+ account: AccountLike;
28
+ parentAccount: Account | null | undefined;
29
+ transaction: Transaction;
30
+ status: TransactionStatus;
31
+ }): Array<DeviceTransactionField | ExtraDeviceTransactionField> {
32
+ const { assetCode, assetIssuer } = transaction;
33
+
34
+ const fields: Array<DeviceTransactionField | ExtraDeviceTransactionField> = [
35
+ {
36
+ type: "stellar.network",
37
+ label: "Network",
38
+ },
39
+ ];
40
+
41
+ if (!amount.isZero()) {
42
+ fields.push({
43
+ type: "amount",
44
+ label: "Amount",
45
+ });
46
+ }
47
+
48
+ if (assetCode && assetIssuer) {
49
+ fields.push({
50
+ type: "stellar.assetCode",
51
+ label: "Asset",
52
+ });
53
+ fields.push({
54
+ type: "stellar.assetIssuer",
55
+ label: "Asset issuer",
56
+ });
57
+ }
58
+
59
+ fields.push({
60
+ type: "stellar.memo",
61
+ label: "Memo",
62
+ });
63
+
64
+ //NB device displays [none] for an empty memo
65
+ if (estimatedFees && !estimatedFees.isZero()) {
66
+ fields.push({
67
+ type: "fees",
68
+ label: "Fees",
69
+ });
70
+ }
71
+
72
+ return fields;
73
+ }
74
+
75
+ export default getDeviceTransactionConfig;
package/src/errors.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { createCustomErrorClass } from "@ledgerhq/errors";
2
+
3
+ export const StellarBurnAddressError = createCustomErrorClass("StellarBurnAddressError");
4
+ export const StellarAssetRequired = createCustomErrorClass("StellarAssetRequired");
5
+ export const StellarMuxedAccountNotExist = createCustomErrorClass("StellarMuxedAccountNotExist");
6
+ export const StellarMemoRecommended = createCustomErrorClass("StellarMemoRecommended");
7
+ export const StellarWrongMemoFormat = createCustomErrorClass("StellarWrongMemoFormat");
8
+ export const StellarAssetNotAccepted = createCustomErrorClass("StellarAssetNotAccepted");
9
+ export const StellarAssetNotFound = createCustomErrorClass("StellarAssetNotFound");
10
+ export const StellarNotEnoughNativeBalance = createCustomErrorClass(
11
+ "StellarNotEnoughNativeBalance",
12
+ );
13
+ export const StellarFeeSmallerThanRecommended = createCustomErrorClass(
14
+ "StellarFeeSmallerThanRecommended",
15
+ );
16
+ export const StellarFeeSmallerThanBase = createCustomErrorClass("StellarFeeSmallerThanBase");
17
+ export const StellarNotEnoughNativeBalanceToAddTrustline = createCustomErrorClass(
18
+ "StellarNotEnoughNativeBalanceToAddTrustline",
19
+ );
20
+ export const StellarSourceHasMultiSign = createCustomErrorClass("StellarSourceHasMultiSign");
@@ -0,0 +1,29 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { AccountBridge } from "@ledgerhq/types-live";
3
+ import { getMainAccount } from "@ledgerhq/coin-framework/account";
4
+ import { getTransactionStatus } from "./getTransactionStatus";
5
+ import { prepareTransaction } from "./prepareTransaction";
6
+ import { createTransaction } from "./createTransaction";
7
+ import type { Transaction } from "./types";
8
+
9
+ const notCreatedStellarMockAddress = "GAW46JE3SHIAYLNNNQCAZFQ437WB5ZH7LDRDWR5LVDWHCTHCKYB6RCCH";
10
+
11
+ export const estimateMaxSpendable: AccountBridge<Transaction>["estimateMaxSpendable"] = async ({
12
+ account,
13
+ parentAccount,
14
+ transaction,
15
+ }) => {
16
+ const mainAccount = getMainAccount(account, parentAccount);
17
+ const preparedTransaction = await prepareTransaction(mainAccount, {
18
+ ...createTransaction(account),
19
+ ...transaction,
20
+ recipient: transaction?.recipient || notCreatedStellarMockAddress,
21
+ // not used address
22
+ useAllAmount: true,
23
+ });
24
+
25
+ const status = await getTransactionStatus(mainAccount, preparedTransaction);
26
+ return status.amount.gte(0) ? status.amount : new BigNumber(0);
27
+ };
28
+
29
+ export default estimateMaxSpendable;
@@ -0,0 +1,207 @@
1
+ import {
2
+ AccountAwaitingSendPendingOperations,
3
+ AmountRequired,
4
+ NotEnoughBalance,
5
+ FeeNotLoaded,
6
+ InvalidAddressBecauseDestinationIsAlsoSource,
7
+ NotEnoughSpendableBalance,
8
+ NotEnoughBalanceBecauseDestinationNotCreated,
9
+ RecipientRequired,
10
+ InvalidAddress,
11
+ } from "@ledgerhq/errors";
12
+ import { BigNumber } from "bignumber.js";
13
+ import type { AccountBridge } from "@ledgerhq/types-live";
14
+ import { findSubAccountById } from "@ledgerhq/coin-framework/account/index";
15
+ import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
16
+ import { isAddressValid, isAccountMultiSign, isMemoValid, getRecipientAccount } from "./logic";
17
+ import { BASE_RESERVE, MIN_BALANCE } from "./network";
18
+ import type { Transaction } from "./types";
19
+ import {
20
+ StellarWrongMemoFormat,
21
+ StellarAssetRequired,
22
+ StellarAssetNotAccepted,
23
+ StellarAssetNotFound,
24
+ StellarNotEnoughNativeBalance,
25
+ StellarFeeSmallerThanRecommended,
26
+ StellarFeeSmallerThanBase,
27
+ StellarNotEnoughNativeBalanceToAddTrustline,
28
+ StellarMuxedAccountNotExist,
29
+ StellarSourceHasMultiSign,
30
+ } from "./errors";
31
+
32
+ export const getTransactionStatus: AccountBridge<Transaction>["getTransactionStatus"] = async (
33
+ account,
34
+ transaction,
35
+ ) => {
36
+ const errors: Record<string, Error> = {};
37
+ const warnings: Record<string, Error> = {};
38
+ const useAllAmount = !!transaction.useAllAmount;
39
+
40
+ const destinationNotExistMessage = new NotEnoughBalanceBecauseDestinationNotCreated("", {
41
+ minimalAmount: `${MIN_BALANCE} XLM`,
42
+ });
43
+
44
+ if (account.pendingOperations.length > 0) {
45
+ throw new AccountAwaitingSendPendingOperations();
46
+ }
47
+
48
+ if (!transaction.fees || !transaction.baseReserve) {
49
+ errors.fees = new FeeNotLoaded();
50
+ }
51
+
52
+ const estimatedFees = !transaction.fees ? new BigNumber(0) : transaction.fees;
53
+ const baseReserve = !transaction.baseReserve ? new BigNumber(0) : transaction.baseReserve;
54
+ const isAssetPayment =
55
+ transaction.subAccountId && transaction.assetCode && transaction.assetIssuer;
56
+ const nativeBalance = account.balance;
57
+ const nativeAmountAvailable = account.spendableBalance.minus(estimatedFees);
58
+
59
+ let amount = new BigNumber(0);
60
+ let maxAmount = new BigNumber(0);
61
+ let totalSpent = new BigNumber(0);
62
+
63
+ // Enough native balance to cover transaction (with required reserve + fees)
64
+ if (!errors.amount && nativeAmountAvailable.lt(0)) {
65
+ errors.amount = new StellarNotEnoughNativeBalance();
66
+ }
67
+
68
+ // Entered fee is smaller than base fee
69
+ if (estimatedFees.lt(transaction.networkInfo?.baseFee || 0)) {
70
+ errors.transaction = new StellarFeeSmallerThanBase();
71
+ // Entered fee is smaller than recommended
72
+ } else if (estimatedFees.lt(transaction.networkInfo?.fees || 0)) {
73
+ warnings.transaction = new StellarFeeSmallerThanRecommended();
74
+ }
75
+
76
+ // Operation specific checks
77
+ if (transaction.mode === "changeTrust") {
78
+ // Check asset provided
79
+ if (!transaction.assetCode || !transaction.assetIssuer) {
80
+ // This is unlikely
81
+ errors.transaction = new StellarAssetRequired("");
82
+ }
83
+
84
+ // Has enough native balance to add new trustline
85
+ if (nativeAmountAvailable.minus(BASE_RESERVE).lt(0)) {
86
+ errors.amount = new StellarNotEnoughNativeBalanceToAddTrustline();
87
+ }
88
+ } else {
89
+ // Payment
90
+ // Check recipient address
91
+ if (!transaction.recipient) {
92
+ errors.recipient = new RecipientRequired("");
93
+ } else if (!isAddressValid(transaction.recipient)) {
94
+ errors.recipient = new InvalidAddress("", {
95
+ currencyName: account.currency.name,
96
+ });
97
+ } else if (account.freshAddress === transaction.recipient) {
98
+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
99
+ }
100
+
101
+ const recipientAccount = await getRecipientAccount({
102
+ account: account,
103
+ recipient: transaction.recipient,
104
+ });
105
+
106
+ // Check recipient account
107
+ if (!recipientAccount?.id && !errors.recipient && !warnings.recipient) {
108
+ if (recipientAccount?.isMuxedAccount) {
109
+ errors.recipient = new StellarMuxedAccountNotExist();
110
+ } else {
111
+ if (isAssetPayment) {
112
+ errors.recipient = destinationNotExistMessage;
113
+ } else {
114
+ warnings.recipient = destinationNotExistMessage;
115
+ }
116
+ }
117
+ }
118
+
119
+ // Asset payment
120
+ if (isAssetPayment) {
121
+ const asset = findSubAccountById(account, transaction.subAccountId || "");
122
+
123
+ if (asset === null) {
124
+ // This is unlikely
125
+ throw new StellarAssetNotFound();
126
+ }
127
+
128
+ // Check recipient account accepts asset
129
+ if (
130
+ recipientAccount?.id &&
131
+ !errors.recipient &&
132
+ !warnings.recipient &&
133
+ !recipientAccount.assetIds.includes(`${transaction.assetCode}:${transaction.assetIssuer}`)
134
+ ) {
135
+ errors.recipient = new StellarAssetNotAccepted("", {
136
+ assetCode: transaction.assetCode,
137
+ });
138
+ }
139
+
140
+ const assetBalance = asset?.balance || new BigNumber(0);
141
+
142
+ maxAmount = asset?.spendableBalance || assetBalance;
143
+ amount = useAllAmount ? maxAmount : transaction.amount;
144
+ totalSpent = amount;
145
+
146
+ if (!errors.amount && amount.gt(assetBalance)) {
147
+ errors.amount = new NotEnoughBalance();
148
+ }
149
+ } else {
150
+ // Native payment
151
+ maxAmount = nativeAmountAvailable;
152
+ amount = useAllAmount ? maxAmount : transaction.amount || 0;
153
+
154
+ if (amount.gt(maxAmount)) {
155
+ errors.amount = new NotEnoughBalance();
156
+ }
157
+
158
+ totalSpent = useAllAmount ? nativeAmountAvailable : transaction.amount.plus(estimatedFees);
159
+
160
+ // Need to send at least 1 XLM to create an account
161
+ if (!errors.recipient && !recipientAccount?.id && !errors.amount && amount.lt(10000000)) {
162
+ errors.amount = destinationNotExistMessage;
163
+ }
164
+
165
+ if (totalSpent.gt(nativeBalance.minus(baseReserve))) {
166
+ errors.amount = new NotEnoughSpendableBalance(undefined, {
167
+ minimumAmount: formatCurrencyUnit(account.currency.units[0], baseReserve, {
168
+ disableRounding: true,
169
+ showCode: true,
170
+ }),
171
+ });
172
+ }
173
+
174
+ if (!errors.recipient && !errors.amount && (amount.lt(0) || totalSpent.gt(nativeBalance))) {
175
+ errors.amount = new NotEnoughBalance();
176
+ totalSpent = new BigNumber(0);
177
+ amount = new BigNumber(0);
178
+ }
179
+ }
180
+
181
+ if (!errors.amount && amount.eq(0)) {
182
+ errors.amount = new AmountRequired();
183
+ }
184
+ }
185
+
186
+ if (await isAccountMultiSign(account)) {
187
+ errors.recipient = new StellarSourceHasMultiSign();
188
+ }
189
+
190
+ if (
191
+ transaction.memoType &&
192
+ transaction.memoValue &&
193
+ !isMemoValid(transaction.memoType, transaction.memoValue)
194
+ ) {
195
+ errors.transaction = new StellarWrongMemoFormat();
196
+ }
197
+
198
+ return {
199
+ errors,
200
+ warnings,
201
+ estimatedFees,
202
+ amount,
203
+ totalSpent,
204
+ };
205
+ };
206
+
207
+ export default getTransactionStatus;
@@ -0,0 +1,24 @@
1
+ import { GetAddressFn } from "@ledgerhq/coin-framework/bridge/getAddressWrapper";
2
+ import { SignerContext } from "@ledgerhq/coin-framework/signer";
3
+ import { GetAddressOptions } from "@ledgerhq/coin-framework/derivation";
4
+ import { StrKey } from "@stellar/stellar-sdk";
5
+ import { StellarSigner } from "./types/signer";
6
+
7
+ function resolver(signerContext: SignerContext<StellarSigner>): GetAddressFn {
8
+ return async (deviceId: string, { path, verify }: GetAddressOptions) => {
9
+ const rawPublicKey = await signerContext(deviceId, async signer => {
10
+ const { rawPublicKey } = await signer.getPublicKey(path, verify);
11
+ return rawPublicKey;
12
+ });
13
+
14
+ const publicKey = StrKey.encodeEd25519PublicKey(rawPublicKey);
15
+
16
+ return {
17
+ path,
18
+ address: publicKey,
19
+ publicKey: rawPublicKey.toString("hex"),
20
+ };
21
+ };
22
+ }
23
+
24
+ export default resolver;