@ledgerhq/coin-stellar 6.2.0 → 6.3.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 +4 -0
- package/CHANGELOG.md +14 -0
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +3 -5
- package/lib/api/index.js.map +1 -1
- package/lib/logic/estimateFees.d.ts +1 -1
- package/lib/logic/estimateFees.d.ts.map +1 -1
- package/lib/logic/estimateFees.js +3 -3
- package/lib/logic/estimateFees.js.map +1 -1
- package/lib/logic/getBalance.test.js +111 -15
- package/lib/logic/getBalance.test.js.map +1 -1
- package/lib/logic/listOperations.unit.test.js +41 -12
- package/lib/logic/listOperations.unit.test.js.map +1 -1
- package/lib/logic/validateIntent.d.ts +2 -2
- package/lib/logic/validateIntent.d.ts.map +1 -1
- package/lib/logic/validateIntent.js +6 -5
- package/lib/logic/validateIntent.js.map +1 -1
- package/lib/network/horizon.d.ts +2 -4
- package/lib/network/horizon.d.ts.map +1 -1
- package/lib/network/horizon.js +24 -15
- package/lib/network/horizon.js.map +1 -1
- package/lib/network/index.d.ts +1 -1
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +1 -4
- package/lib/network/index.js.map +1 -1
- package/lib/network/serialization.d.ts +2 -1
- package/lib/network/serialization.d.ts.map +1 -1
- package/lib/network/serialization.js +7 -13
- package/lib/network/serialization.js.map +1 -1
- package/lib/polyfill.d.ts.map +1 -1
- package/lib/polyfill.js +4 -1
- package/lib/polyfill.js.map +1 -1
- package/lib/types/bridge.d.ts +2 -0
- package/lib/types/bridge.d.ts.map +1 -1
- package/lib/types/bridge.js.map +1 -1
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +3 -5
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/logic/estimateFees.d.ts +1 -1
- package/lib-es/logic/estimateFees.d.ts.map +1 -1
- package/lib-es/logic/estimateFees.js +4 -4
- package/lib-es/logic/estimateFees.js.map +1 -1
- package/lib-es/logic/getBalance.test.js +111 -12
- package/lib-es/logic/getBalance.test.js.map +1 -1
- package/lib-es/logic/listOperations.unit.test.js +41 -12
- package/lib-es/logic/listOperations.unit.test.js.map +1 -1
- package/lib-es/logic/validateIntent.d.ts +2 -2
- package/lib-es/logic/validateIntent.d.ts.map +1 -1
- package/lib-es/logic/validateIntent.js +5 -4
- package/lib-es/logic/validateIntent.js.map +1 -1
- package/lib-es/network/horizon.d.ts +2 -4
- package/lib-es/network/horizon.d.ts.map +1 -1
- package/lib-es/network/horizon.js +22 -14
- package/lib-es/network/horizon.js.map +1 -1
- package/lib-es/network/index.d.ts +1 -1
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +1 -1
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/serialization.d.ts +2 -1
- package/lib-es/network/serialization.d.ts.map +1 -1
- package/lib-es/network/serialization.js +4 -9
- package/lib-es/network/serialization.js.map +1 -1
- package/lib-es/polyfill.d.ts.map +1 -1
- package/lib-es/polyfill.js +4 -1
- package/lib-es/polyfill.js.map +1 -1
- package/lib-es/types/bridge.d.ts +2 -0
- package/lib-es/types/bridge.d.ts.map +1 -1
- package/lib-es/types/bridge.js.map +1 -1
- package/package.json +10 -10
- package/src/api/index.ts +3 -6
- package/src/logic/estimateFees.ts +4 -4
- package/src/logic/getBalance.test.ts +128 -17
- package/src/logic/listOperations.unit.test.ts +45 -12
- package/src/logic/validateIntent.ts +10 -9
- package/src/network/horizon.ts +27 -19
- package/src/network/index.ts +0 -3
- package/src/network/serialization.ts +4 -13
- package/src/polyfill.ts +4 -1
- package/src/types/bridge.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledgerhq/coin-stellar",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0-nightly.0",
|
|
4
4
|
"description": "Ledger Stellar Coin integration",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -102,20 +102,20 @@
|
|
|
102
102
|
"./package.json": "./package.json"
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
|
-
"@stellar/stellar-sdk": "^
|
|
105
|
+
"@stellar/stellar-sdk": "^14.0.0",
|
|
106
106
|
"bignumber.js": "^9.1.2",
|
|
107
107
|
"expect": "^27.4.6",
|
|
108
108
|
"invariant": "^2.2.2",
|
|
109
109
|
"rxjs": "^7.8.1",
|
|
110
110
|
"@exodus/patch-broken-hermes-typed-arrays": "1.0.0-alpha.1",
|
|
111
|
-
"@ledgerhq/coin-framework": "^6.2.0",
|
|
112
|
-
"@ledgerhq/cryptoassets": "^13.
|
|
113
|
-
"@ledgerhq/devices": "8.5.0",
|
|
114
|
-
"@ledgerhq/errors": "^6.
|
|
115
|
-
"@ledgerhq/live-env": "^2.
|
|
116
|
-
"@ledgerhq/live-network": "^2.0.
|
|
111
|
+
"@ledgerhq/coin-framework": "^6.2.1-nightly.0",
|
|
112
|
+
"@ledgerhq/cryptoassets": "^13.27.0-nightly.0",
|
|
113
|
+
"@ledgerhq/devices": "8.5.1-nightly.0",
|
|
114
|
+
"@ledgerhq/errors": "^6.25.0-nightly.0",
|
|
115
|
+
"@ledgerhq/live-env": "^2.15.0-nightly.1",
|
|
116
|
+
"@ledgerhq/live-network": "^2.0.16-nightly.1",
|
|
117
117
|
"@ledgerhq/logs": "^6.13.0",
|
|
118
|
-
"@ledgerhq/types-cryptoassets": "^7.
|
|
118
|
+
"@ledgerhq/types-cryptoassets": "^7.26.0-nightly.1",
|
|
119
119
|
"@ledgerhq/types-live": "^6.82.0"
|
|
120
120
|
},
|
|
121
121
|
"devDependencies": {
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"scripts": {
|
|
133
133
|
"clean": "rimraf lib lib-es",
|
|
134
134
|
"build": "tsc && tsc -m esnext --moduleResolution bundler --outDir lib-es",
|
|
135
|
-
"coverage": "jest --coverage
|
|
135
|
+
"coverage": "jest --coverage",
|
|
136
136
|
"prewatch": "pnpm build",
|
|
137
137
|
"watch": "tsc --watch",
|
|
138
138
|
"watch:es": "tsc --watch -m esnext --moduleResolution bundler --outDir lib-es",
|
package/src/api/index.ts
CHANGED
|
@@ -77,8 +77,7 @@ async function craft(
|
|
|
77
77
|
transactionIntent: TransactionIntent<StellarMemo>,
|
|
78
78
|
customFees?: FeeEstimation,
|
|
79
79
|
): Promise<string> {
|
|
80
|
-
const fees =
|
|
81
|
-
customFees?.value || transactionIntent.fees || (await estimateFees(transactionIntent.sender));
|
|
80
|
+
const fees = customFees?.value || (await estimateFees());
|
|
82
81
|
|
|
83
82
|
// NOTE: check how many memos, throw if more than one?
|
|
84
83
|
// if (transactionIntent.memos && transactionIntent.memos.length > 1) {
|
|
@@ -116,10 +115,8 @@ function compose(tx: string, signature: string, pubkey?: string): string {
|
|
|
116
115
|
return combine(envelopeFromAnyXDR(tx, "base64"), signature, pubkey);
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
async function estimate(
|
|
120
|
-
const value =
|
|
121
|
-
? transactionIntent?.fees
|
|
122
|
-
: await estimateFees(transactionIntent.sender);
|
|
118
|
+
async function estimate(_transactionIntent: TransactionIntent): Promise<FeeEstimation> {
|
|
119
|
+
const value = await estimateFees();
|
|
123
120
|
return { value };
|
|
124
121
|
}
|
|
125
122
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchBaseFee } from "../network";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Estimate the fees for one transaction
|
|
5
5
|
* @see {@link https://developers.stellar.org/docs/learn/fundamentals/fees-resource-limits-metering#inclusion-fee}
|
|
6
6
|
*/
|
|
7
|
-
export async function estimateFees(
|
|
8
|
-
const
|
|
9
|
-
return BigInt(
|
|
7
|
+
export async function estimateFees(): Promise<bigint> {
|
|
8
|
+
const baseFee = await fetchBaseFee();
|
|
9
|
+
return BigInt(baseFee.recommendedFee);
|
|
10
10
|
}
|
|
@@ -1,26 +1,137 @@
|
|
|
1
|
-
import coinConfig, { StellarCoinConfig } from "../config";
|
|
2
1
|
import { getBalance } from "./getBalance";
|
|
3
|
-
import {
|
|
2
|
+
import { fetchAccount } from "../network";
|
|
3
|
+
import { findTokenById } from "@ledgerhq/cryptoassets/tokens";
|
|
4
|
+
import { getAssetIdFromAsset } from "./utils";
|
|
5
|
+
import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit";
|
|
6
|
+
|
|
7
|
+
jest.mock("../network", () => ({
|
|
8
|
+
fetchAccount: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock("@ledgerhq/cryptoassets/tokens", () => ({
|
|
12
|
+
findTokenById: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock("./utils", () => ({
|
|
16
|
+
getAssetIdFromAsset: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
jest.mock("@ledgerhq/coin-framework/currencies/parseCurrencyUnit", () => ({
|
|
20
|
+
parseCurrencyUnit: jest.fn(),
|
|
21
|
+
}));
|
|
4
22
|
|
|
5
23
|
describe("getBalance", () => {
|
|
6
|
-
|
|
7
|
-
()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
it("returns native balance when no assets are present", async () => {
|
|
25
|
+
(fetchAccount as jest.Mock).mockResolvedValue({
|
|
26
|
+
balance: 100000000,
|
|
27
|
+
spendableBalance: 99999990,
|
|
28
|
+
assets: [],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = await getBalance("test-address");
|
|
32
|
+
|
|
33
|
+
expect(result).toEqual([
|
|
34
|
+
{
|
|
35
|
+
value: BigInt(100000000),
|
|
36
|
+
asset: { type: "native" },
|
|
37
|
+
locked: BigInt(10),
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns native and asset balances when assets are present", async () => {
|
|
43
|
+
(fetchAccount as jest.Mock).mockResolvedValue({
|
|
44
|
+
balance: 100000000,
|
|
45
|
+
spendableBalance: 99999990,
|
|
46
|
+
assets: [
|
|
47
|
+
{
|
|
48
|
+
asset_type: "credit_alphanum4",
|
|
49
|
+
asset_code: "USDC",
|
|
50
|
+
asset_issuer: "issuer-address",
|
|
51
|
+
balance: "50.1234567",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
(getAssetIdFromAsset as jest.Mock).mockReturnValue("USDC-issuer-address");
|
|
57
|
+
(findTokenById as jest.Mock).mockReturnValue({
|
|
58
|
+
units: [{ code: "USDC", magnitude: 7 }],
|
|
59
|
+
});
|
|
60
|
+
(parseCurrencyUnit as jest.Mock).mockReturnValue({
|
|
61
|
+
toString: () => "50.1234567",
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const result = await getBalance("test-address");
|
|
65
|
+
|
|
66
|
+
expect(result).toEqual([
|
|
19
67
|
{
|
|
20
|
-
value: BigInt(
|
|
68
|
+
value: BigInt(100000000),
|
|
21
69
|
asset: { type: "native" },
|
|
22
|
-
locked: BigInt(
|
|
70
|
+
locked: BigInt(10),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
value: BigInt(50),
|
|
74
|
+
asset: {
|
|
75
|
+
type: "credit_alphanum4",
|
|
76
|
+
assetReference: "USDC",
|
|
77
|
+
assetOwner: "issuer-address",
|
|
78
|
+
},
|
|
23
79
|
},
|
|
24
80
|
]);
|
|
81
|
+
|
|
82
|
+
expect(fetchAccount).toHaveBeenCalledWith("test-address");
|
|
83
|
+
expect(getAssetIdFromAsset).toHaveBeenCalledWith({
|
|
84
|
+
asset_type: "credit_alphanum4",
|
|
85
|
+
asset_code: "USDC",
|
|
86
|
+
asset_issuer: "issuer-address",
|
|
87
|
+
balance: "50.1234567",
|
|
88
|
+
});
|
|
89
|
+
expect(findTokenById).toHaveBeenCalledWith("stellar/asset/USDC-issuer-address");
|
|
90
|
+
expect(parseCurrencyUnit).toHaveBeenCalledWith({ code: "USDC", magnitude: 7 }, "50.1234567");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("handles assets without matching tokens gracefully", async () => {
|
|
94
|
+
(fetchAccount as jest.Mock).mockResolvedValue({
|
|
95
|
+
balance: 100000000,
|
|
96
|
+
spendableBalance: 99999990,
|
|
97
|
+
assets: [
|
|
98
|
+
{
|
|
99
|
+
asset_type: "credit_alphanum4",
|
|
100
|
+
asset_code: "USDT",
|
|
101
|
+
asset_issuer: "issuer-address",
|
|
102
|
+
balance: "25.0000000",
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
(getAssetIdFromAsset as jest.Mock).mockReturnValue("USDT-issuer-address");
|
|
108
|
+
(findTokenById as jest.Mock).mockReturnValue(null);
|
|
109
|
+
|
|
110
|
+
const result = await getBalance("test-address");
|
|
111
|
+
|
|
112
|
+
expect(result).toEqual([
|
|
113
|
+
{
|
|
114
|
+
value: BigInt(100000000),
|
|
115
|
+
asset: { type: "native" },
|
|
116
|
+
locked: BigInt(10),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
value: BigInt(0),
|
|
120
|
+
asset: {
|
|
121
|
+
type: "credit_alphanum4",
|
|
122
|
+
assetReference: "USDT",
|
|
123
|
+
assetOwner: "issuer-address",
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
expect(fetchAccount).toHaveBeenCalledWith("test-address");
|
|
129
|
+
expect(getAssetIdFromAsset).toHaveBeenCalledWith({
|
|
130
|
+
asset_type: "credit_alphanum4",
|
|
131
|
+
asset_code: "USDT",
|
|
132
|
+
asset_issuer: "issuer-address",
|
|
133
|
+
balance: "25.0000000",
|
|
134
|
+
});
|
|
135
|
+
expect(findTokenById).toHaveBeenCalledWith("stellar/asset/USDT-issuer-address");
|
|
25
136
|
});
|
|
26
137
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { listOperations } from "./listOperations";
|
|
2
2
|
import coinConfig from "../config";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { Horizon } from "@stellar/stellar-sdk";
|
|
4
|
+
|
|
5
|
+
jest.mock("@stellar/stellar-sdk");
|
|
5
6
|
|
|
6
7
|
describe("listOperations", () => {
|
|
7
8
|
coinConfig.setCoinConfig(() => ({
|
|
@@ -9,8 +10,34 @@ describe("listOperations", () => {
|
|
|
9
10
|
explorer: { url: "https://stellar.coin.ledger.com" },
|
|
10
11
|
}));
|
|
11
12
|
|
|
13
|
+
const mockCall = jest.fn();
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
(Horizon.Server as jest.Mock).mockImplementation(() => ({
|
|
17
|
+
operations: () => ({
|
|
18
|
+
forAccount: () => ({
|
|
19
|
+
limit: () => ({
|
|
20
|
+
order: () => ({
|
|
21
|
+
cursor: () => ({
|
|
22
|
+
includeFailed: () => ({
|
|
23
|
+
join: () => ({
|
|
24
|
+
call: mockCall,
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
}),
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
12
39
|
it("lists operations associated with an address", async () => {
|
|
13
|
-
|
|
40
|
+
mockCall.mockResolvedValueOnce({
|
|
14
41
|
records: [
|
|
15
42
|
{
|
|
16
43
|
id: "operation_id1",
|
|
@@ -20,7 +47,7 @@ describe("listOperations", () => {
|
|
|
20
47
|
from: "address",
|
|
21
48
|
to: "receiver1",
|
|
22
49
|
amount: "46.0600000",
|
|
23
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
50
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
24
51
|
transaction_successful: true,
|
|
25
52
|
created_at: "2025-01-01",
|
|
26
53
|
transaction: () => ({
|
|
@@ -40,7 +67,7 @@ describe("listOperations", () => {
|
|
|
40
67
|
from: "address",
|
|
41
68
|
to: "receiver2",
|
|
42
69
|
amount: "666.0000000",
|
|
43
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
70
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
44
71
|
transaction_successful: false,
|
|
45
72
|
created_at: "2025-01-01",
|
|
46
73
|
transaction: () => ({
|
|
@@ -60,7 +87,7 @@ describe("listOperations", () => {
|
|
|
60
87
|
from: "sender",
|
|
61
88
|
to: "address",
|
|
62
89
|
amount: "50.5000000",
|
|
63
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
90
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
64
91
|
transaction_successful: true,
|
|
65
92
|
created_at: "2025-01-01",
|
|
66
93
|
transaction: () => ({
|
|
@@ -85,6 +112,7 @@ describe("listOperations", () => {
|
|
|
85
112
|
ledgerOpType: "OUT",
|
|
86
113
|
sequence: undefined,
|
|
87
114
|
memo: undefined,
|
|
115
|
+
status: "success",
|
|
88
116
|
},
|
|
89
117
|
senders: ["address"],
|
|
90
118
|
recipients: ["receiver1"],
|
|
@@ -105,6 +133,7 @@ describe("listOperations", () => {
|
|
|
105
133
|
ledgerOpType: "OUT",
|
|
106
134
|
sequence: undefined,
|
|
107
135
|
memo: undefined,
|
|
136
|
+
status: "failed",
|
|
108
137
|
},
|
|
109
138
|
senders: ["address"],
|
|
110
139
|
recipients: ["receiver2"],
|
|
@@ -125,6 +154,7 @@ describe("listOperations", () => {
|
|
|
125
154
|
ledgerOpType: "IN",
|
|
126
155
|
sequence: undefined,
|
|
127
156
|
memo: undefined,
|
|
157
|
+
status: "success",
|
|
128
158
|
},
|
|
129
159
|
senders: ["sender"],
|
|
130
160
|
recipients: ["address"],
|
|
@@ -143,7 +173,7 @@ describe("listOperations", () => {
|
|
|
143
173
|
});
|
|
144
174
|
|
|
145
175
|
it("stops at minHeight", async () => {
|
|
146
|
-
|
|
176
|
+
mockCall.mockResolvedValueOnce({
|
|
147
177
|
records: [
|
|
148
178
|
{
|
|
149
179
|
id: "operation_id1",
|
|
@@ -153,7 +183,7 @@ describe("listOperations", () => {
|
|
|
153
183
|
from: "address",
|
|
154
184
|
to: "receiver1",
|
|
155
185
|
amount: "46.0600000",
|
|
156
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
186
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
157
187
|
transaction_successful: true,
|
|
158
188
|
created_at: "2025-01-01",
|
|
159
189
|
transaction: () => ({
|
|
@@ -173,7 +203,7 @@ describe("listOperations", () => {
|
|
|
173
203
|
from: "address",
|
|
174
204
|
to: "receiver2",
|
|
175
205
|
amount: "666.0000000",
|
|
176
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
206
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
177
207
|
transaction_successful: false,
|
|
178
208
|
created_at: "2025-01-01",
|
|
179
209
|
transaction: () => ({
|
|
@@ -193,7 +223,7 @@ describe("listOperations", () => {
|
|
|
193
223
|
from: "sender",
|
|
194
224
|
to: "address",
|
|
195
225
|
amount: "50.5000000",
|
|
196
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
226
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
197
227
|
transaction_successful: true,
|
|
198
228
|
created_at: "2025-01-01",
|
|
199
229
|
transaction: () => ({
|
|
@@ -218,6 +248,7 @@ describe("listOperations", () => {
|
|
|
218
248
|
ledgerOpType: "OUT",
|
|
219
249
|
sequence: undefined,
|
|
220
250
|
memo: undefined,
|
|
251
|
+
status: "success",
|
|
221
252
|
},
|
|
222
253
|
senders: ["address"],
|
|
223
254
|
recipients: ["receiver1"],
|
|
@@ -238,6 +269,7 @@ describe("listOperations", () => {
|
|
|
238
269
|
ledgerOpType: "OUT",
|
|
239
270
|
sequence: undefined,
|
|
240
271
|
memo: undefined,
|
|
272
|
+
status: "failed",
|
|
241
273
|
},
|
|
242
274
|
senders: ["address"],
|
|
243
275
|
recipients: ["receiver2"],
|
|
@@ -256,7 +288,7 @@ describe("listOperations", () => {
|
|
|
256
288
|
});
|
|
257
289
|
|
|
258
290
|
it("should return memo if set", async () => {
|
|
259
|
-
|
|
291
|
+
mockCall.mockResolvedValueOnce({
|
|
260
292
|
records: [
|
|
261
293
|
{
|
|
262
294
|
id: "operation_id1",
|
|
@@ -266,7 +298,7 @@ describe("listOperations", () => {
|
|
|
266
298
|
from: "address",
|
|
267
299
|
to: "receiver1",
|
|
268
300
|
amount: "46.0600000",
|
|
269
|
-
type: HorizonApi.OperationResponseType.payment,
|
|
301
|
+
type: Horizon.HorizonApi.OperationResponseType.payment,
|
|
270
302
|
transaction_successful: true,
|
|
271
303
|
created_at: "2025-01-01",
|
|
272
304
|
transaction: () => ({
|
|
@@ -306,6 +338,7 @@ describe("listOperations", () => {
|
|
|
306
338
|
type: "MEMO_TEXT",
|
|
307
339
|
value: "momo",
|
|
308
340
|
},
|
|
341
|
+
status: "success",
|
|
309
342
|
},
|
|
310
343
|
},
|
|
311
344
|
],
|
|
@@ -8,14 +8,13 @@ import {
|
|
|
8
8
|
InvalidAddress,
|
|
9
9
|
} from "@ledgerhq/errors";
|
|
10
10
|
import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
|
|
11
|
-
import { TransactionValidation, TransactionIntent } from "@ledgerhq/coin-framework/api/types";
|
|
12
|
-
import { isAddressValid, isAccountMultiSign, isMemoValid } from "./utils";
|
|
13
11
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "
|
|
12
|
+
TransactionValidation,
|
|
13
|
+
TransactionIntent,
|
|
14
|
+
FeeEstimation,
|
|
15
|
+
} from "@ledgerhq/coin-framework/api/types";
|
|
16
|
+
import { isAddressValid, isAccountMultiSign, isMemoValid } from "./utils";
|
|
17
|
+
import { fetchAccountNetworkInfo, getRecipientAccount } from "../network";
|
|
19
18
|
import {
|
|
20
19
|
StellarWrongMemoFormat,
|
|
21
20
|
StellarAssetRequired,
|
|
@@ -32,9 +31,11 @@ import {
|
|
|
32
31
|
import BigNumber from "bignumber.js";
|
|
33
32
|
import { getBalance } from "./getBalance";
|
|
34
33
|
import { fetchAccount } from "../network/horizon";
|
|
34
|
+
import { BASE_RESERVE, MIN_BALANCE } from "../network/serialization";
|
|
35
35
|
|
|
36
36
|
export const validateIntent = async (
|
|
37
37
|
transactionIntent: TransactionIntent<StellarMemo>,
|
|
38
|
+
customFees?: FeeEstimation,
|
|
38
39
|
): Promise<TransactionValidation> => {
|
|
39
40
|
const errors: Record<string, Error> = {};
|
|
40
41
|
const warnings: Record<string, Error> = {};
|
|
@@ -47,7 +48,7 @@ export const validateIntent = async (
|
|
|
47
48
|
const { spendableBalance, balance } = await fetchAccount(transactionIntent.sender);
|
|
48
49
|
const networkInfo = await fetchAccountNetworkInfo(transactionIntent.sender);
|
|
49
50
|
|
|
50
|
-
const estimatedFees =
|
|
51
|
+
const estimatedFees = customFees?.value ?? 0n;
|
|
51
52
|
const baseReserve = networkInfo.baseReserve
|
|
52
53
|
? BigInt(Math.round(networkInfo.baseReserve.toNumber() * 10)) / 10n
|
|
53
54
|
: 0n;
|
|
@@ -203,7 +204,7 @@ export const validateIntent = async (
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
if (
|
|
207
|
+
if (amount === 0n) {
|
|
207
208
|
errors.amount = new AmountRequired();
|
|
208
209
|
}
|
|
209
210
|
}
|
package/src/network/horizon.ts
CHANGED
|
@@ -26,11 +26,7 @@ import {
|
|
|
26
26
|
NetworkCongestionLevel,
|
|
27
27
|
StellarOperation,
|
|
28
28
|
} from "../types";
|
|
29
|
-
import {
|
|
30
|
-
getAccountSpendableBalance,
|
|
31
|
-
getReservedBalance,
|
|
32
|
-
rawOperationsToOperations,
|
|
33
|
-
} from "./serialization";
|
|
29
|
+
import { getReservedBalance, rawOperationsToOperations } from "./serialization";
|
|
34
30
|
import { patchHermesTypedArraysIfNeeded, unpatchHermesTypedArrays } from "../polyfill";
|
|
35
31
|
|
|
36
32
|
const FALLBACK_BASE_FEE = 100;
|
|
@@ -52,11 +48,6 @@ function getServer(): Horizon.Server {
|
|
|
52
48
|
return server;
|
|
53
49
|
}
|
|
54
50
|
|
|
55
|
-
// Constants
|
|
56
|
-
export const BASE_RESERVE = 0.5;
|
|
57
|
-
export const BASE_RESERVE_MIN_COUNT = 2;
|
|
58
|
-
export const MIN_BALANCE = 1;
|
|
59
|
-
|
|
60
51
|
// Due to the inconsistency between the axios version (1.6.5) used by `stellar-sdk`
|
|
61
52
|
// and the version (0.26.1) used by `@ledgerhq/live-network/network`, it is not possible to use the interceptors
|
|
62
53
|
// provided by `@ledgerhq/live-network/network`.
|
|
@@ -78,6 +69,19 @@ function useConfigHost(url: string): string {
|
|
|
78
69
|
return u.toString();
|
|
79
70
|
}
|
|
80
71
|
|
|
72
|
+
const getMinimumBalance = (account: Horizon.ServerApi.AccountRecord): BigNumber => {
|
|
73
|
+
return parseCurrencyUnit(currency.units[0], getReservedBalance(account).toString());
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export async function getAccountSpendableBalance(
|
|
77
|
+
balance: BigNumber,
|
|
78
|
+
account: Horizon.ServerApi.AccountRecord,
|
|
79
|
+
): Promise<BigNumber> {
|
|
80
|
+
const minimumBalance = getMinimumBalance(account);
|
|
81
|
+
const { recommendedFee } = await fetchBaseFee();
|
|
82
|
+
return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
81
85
|
Horizon.AxiosClient.interceptors.response.use(response => {
|
|
82
86
|
if (coinConfig.getCoinConfig().enableNetworkLogs) {
|
|
83
87
|
const { url, method } = response.config;
|
|
@@ -323,7 +327,7 @@ export async function fetchOperations({
|
|
|
323
327
|
|
|
324
328
|
// in this context, if we have filtered out operations it means those operations were < minHeight, so we are done
|
|
325
329
|
const nextCursor =
|
|
326
|
-
filteredOps.length
|
|
330
|
+
filteredOps.length === rawOps.length ? rawOps[rawOps.length - 1].paging_token : "";
|
|
327
331
|
|
|
328
332
|
return [filteredOps, nextCursor];
|
|
329
333
|
} catch (e: unknown) {
|
|
@@ -396,14 +400,18 @@ export async function fetchSigners(account: string): Promise<Signer[]> {
|
|
|
396
400
|
}
|
|
397
401
|
|
|
398
402
|
export async function broadcastTransaction(signedTransaction: string): Promise<string> {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
try {
|
|
404
|
+
patchHermesTypedArraysIfNeeded();
|
|
405
|
+
const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC);
|
|
406
|
+
|
|
407
|
+
const res = await getServer().submitTransaction(transaction, {
|
|
408
|
+
skipMemoRequiredCheck: true,
|
|
409
|
+
});
|
|
410
|
+
return res.hash;
|
|
411
|
+
} finally {
|
|
412
|
+
// Restore
|
|
413
|
+
unpatchHermesTypedArrays();
|
|
414
|
+
}
|
|
407
415
|
}
|
|
408
416
|
|
|
409
417
|
export async function loadAccount(addr: string): Promise<AccountRecord | null> {
|
package/src/network/index.ts
CHANGED
|
@@ -7,24 +7,15 @@ import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
|
7
7
|
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
8
8
|
import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit";
|
|
9
9
|
import { Horizon } from "@stellar/stellar-sdk";
|
|
10
|
-
import { BASE_RESERVE, BASE_RESERVE_MIN_COUNT, fetchBaseFee } from "./horizon";
|
|
11
10
|
import type { BalanceAsset, RawOperation, StellarMemo, StellarOperation } from "../types";
|
|
12
11
|
import BigNumber from "bignumber.js";
|
|
13
12
|
|
|
14
13
|
const currency = getCryptoCurrencyById("stellar");
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const minimumBalance = getMinimumBalance(account);
|
|
21
|
-
const { recommendedFee } = await fetchBaseFee();
|
|
22
|
-
return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const getMinimumBalance = (account: Horizon.ServerApi.AccountRecord): BigNumber => {
|
|
26
|
-
return parseCurrencyUnit(currency.units[0], getReservedBalance(account).toString());
|
|
27
|
-
};
|
|
15
|
+
// Constants
|
|
16
|
+
const BASE_RESERVE_MIN_COUNT = 2;
|
|
17
|
+
export const BASE_RESERVE = 0.5;
|
|
18
|
+
export const MIN_BALANCE = 1;
|
|
28
19
|
|
|
29
20
|
export function getReservedBalance(account: Horizon.ServerApi.AccountRecord): BigNumber {
|
|
30
21
|
const numOfSponsoringEntries = Number(account.num_sponsoring);
|
package/src/polyfill.ts
CHANGED
|
@@ -22,7 +22,10 @@ export function patchHermesTypedArraysIfNeeded() {
|
|
|
22
22
|
if (global.HermesInternal && !hermesTypedArrayPatched) {
|
|
23
23
|
try {
|
|
24
24
|
log("coin:stellar", "🔵 Patching TypedArray because of Hermes bug");
|
|
25
|
-
require
|
|
25
|
+
// Only require the module when we're actually running on Hermes
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
27
|
+
const fixHermesTypedArrayBug = require("@exodus/patch-broken-hermes-typed-arrays");
|
|
28
|
+
fixHermesTypedArrayBug();
|
|
26
29
|
hermesTypedArrayPatched = true;
|
|
27
30
|
} catch (e) {
|
|
28
31
|
log("coin:stellar", "Failed to patch typed arrays");
|
package/src/types/bridge.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
TransactionStatusCommon,
|
|
10
10
|
TransactionStatusCommonRaw,
|
|
11
11
|
} from "@ledgerhq/types-live";
|
|
12
|
+
import { FeeEstimation } from "@ledgerhq/coin-framework/api/types";
|
|
12
13
|
|
|
13
14
|
export type NetworkInfo = {
|
|
14
15
|
family: "stellar";
|
|
@@ -52,6 +53,7 @@ export type StellarTransactionMode = "send" | "changeTrust";
|
|
|
52
53
|
export type Transaction = TransactionCommon & {
|
|
53
54
|
family: "stellar";
|
|
54
55
|
networkInfo?: NetworkInfo | null | undefined;
|
|
56
|
+
customFees?: FeeEstimation;
|
|
55
57
|
fees?: BigNumber | null;
|
|
56
58
|
baseReserve?: BigNumber | null;
|
|
57
59
|
memoType?: string | null;
|