@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
|
@@ -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;
|