@ledgerhq/coin-xrp 6.1.3 → 6.2.0-nightly.3
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/.turbo/turbo-build.log +1 -1
- package/.unimportedrc.json +3 -1
- package/CHANGELOG.md +20 -23
- package/jest.config.js +2 -5
- package/lib/api/index.d.ts +2 -11
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.integ.test.js +24 -7
- package/lib/api/index.integ.test.js.map +1 -1
- package/lib/api/index.js +15 -5
- package/lib/api/index.js.map +1 -1
- package/lib/api/index.test.js +18 -9
- package/lib/api/index.test.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -3
- package/lib/index.js.map +1 -1
- package/lib/logic/combine.d.ts.map +1 -1
- package/lib/logic/combine.js +8 -3
- package/lib/logic/combine.js.map +1 -1
- package/lib/logic/getBalance.d.ts.map +1 -1
- package/lib/logic/getBalance.js +13 -1
- package/lib/logic/getBalance.js.map +1 -1
- package/lib/logic/getBalance.test.js +14 -1
- package/lib/logic/getBalance.test.js.map +1 -1
- package/lib/logic/getTransactionStatus.d.ts +3 -0
- package/lib/logic/getTransactionStatus.d.ts.map +1 -0
- package/lib/{bridge → logic}/getTransactionStatus.js +15 -19
- package/lib/logic/getTransactionStatus.js.map +1 -0
- package/lib/logic/getTransactionStatus.test.d.ts +2 -0
- package/lib/logic/getTransactionStatus.test.d.ts.map +1 -0
- package/lib/logic/getTransactionStatus.test.js +184 -0
- package/lib/logic/getTransactionStatus.test.js.map +1 -0
- package/lib/logic/index.d.ts +2 -1
- package/lib/logic/index.d.ts.map +1 -1
- package/lib/logic/index.js +3 -2
- package/lib/logic/index.js.map +1 -1
- package/lib/logic/utils.d.ts +0 -1
- package/lib/logic/utils.d.ts.map +1 -1
- package/lib/logic/utils.js +14 -10
- package/lib/logic/utils.js.map +1 -1
- package/lib/test/bridgeDatasetTest.d.ts.map +1 -1
- package/lib/test/bridgeDatasetTest.js +7 -7
- package/lib/test/bridgeDatasetTest.js.map +1 -1
- package/lib/{bridge/transaction.d.ts → transaction.d.ts} +1 -1
- package/lib/transaction.d.ts.map +1 -0
- package/lib/transaction.js.map +1 -0
- package/lib/types/model.d.ts +7 -0
- package/lib/types/model.d.ts.map +1 -1
- package/lib-es/api/index.d.ts +2 -11
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.integ.test.js +24 -7
- package/lib-es/api/index.integ.test.js.map +1 -1
- package/lib-es/api/index.js +16 -6
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/api/index.test.js +18 -9
- package/lib-es/api/index.test.js.map +1 -1
- package/lib-es/index.d.ts +0 -1
- package/lib-es/index.d.ts.map +1 -1
- package/lib-es/index.js +0 -1
- package/lib-es/index.js.map +1 -1
- package/lib-es/logic/combine.d.ts.map +1 -1
- package/lib-es/logic/combine.js +8 -3
- package/lib-es/logic/combine.js.map +1 -1
- package/lib-es/logic/getBalance.d.ts.map +1 -1
- package/lib-es/logic/getBalance.js +14 -2
- package/lib-es/logic/getBalance.js.map +1 -1
- package/lib-es/logic/getBalance.test.js +14 -1
- package/lib-es/logic/getBalance.test.js.map +1 -1
- package/lib-es/logic/getTransactionStatus.d.ts +3 -0
- package/lib-es/logic/getTransactionStatus.d.ts.map +1 -0
- package/lib-es/{bridge → logic}/getTransactionStatus.js +13 -14
- package/lib-es/logic/getTransactionStatus.js.map +1 -0
- package/lib-es/logic/getTransactionStatus.test.d.ts +2 -0
- package/lib-es/logic/getTransactionStatus.test.d.ts.map +1 -0
- package/lib-es/logic/getTransactionStatus.test.js +159 -0
- package/lib-es/logic/getTransactionStatus.test.js.map +1 -0
- package/lib-es/logic/index.d.ts +2 -1
- package/lib-es/logic/index.d.ts.map +1 -1
- package/lib-es/logic/index.js +2 -1
- package/lib-es/logic/index.js.map +1 -1
- package/lib-es/logic/utils.d.ts +0 -1
- package/lib-es/logic/utils.d.ts.map +1 -1
- package/lib-es/logic/utils.js +13 -8
- package/lib-es/logic/utils.js.map +1 -1
- package/lib-es/test/bridgeDatasetTest.d.ts.map +1 -1
- package/lib-es/test/bridgeDatasetTest.js +7 -7
- package/lib-es/test/bridgeDatasetTest.js.map +1 -1
- package/lib-es/{bridge/transaction.d.ts → transaction.d.ts} +1 -1
- package/lib-es/transaction.d.ts.map +1 -0
- package/lib-es/transaction.js.map +1 -0
- package/lib-es/types/model.d.ts +7 -0
- package/lib-es/types/model.d.ts.map +1 -1
- package/package.json +12 -13
- package/src/api/index.integ.test.ts +24 -8
- package/src/api/index.test.ts +23 -22
- package/src/api/index.ts +28 -19
- package/src/index.ts +0 -1
- package/src/logic/combine.ts +10 -3
- package/src/logic/getBalance.test.ts +14 -1
- package/src/logic/getBalance.ts +18 -2
- package/src/logic/getTransactionStatus.test.ts +215 -0
- package/src/{bridge → logic}/getTransactionStatus.ts +18 -21
- package/src/logic/index.ts +2 -6
- package/src/logic/utils.ts +24 -8
- package/src/test/bridgeDatasetTest.ts +8 -7
- package/src/{bridge/transaction.ts → transaction.ts} +1 -1
- package/src/types/model.ts +11 -0
- package/lib/bridge/broadcast.d.ts +0 -4
- package/lib/bridge/broadcast.d.ts.map +0 -1
- package/lib/bridge/broadcast.js +0 -11
- package/lib/bridge/broadcast.js.map +0 -1
- package/lib/bridge/createTransaction.d.ts +0 -4
- package/lib/bridge/createTransaction.d.ts.map +0 -1
- package/lib/bridge/createTransaction.js +0 -18
- package/lib/bridge/createTransaction.js.map +0 -1
- package/lib/bridge/estimateMaxSpendable.d.ts +0 -4
- package/lib/bridge/estimateMaxSpendable.d.ts.map +0 -1
- package/lib/bridge/estimateMaxSpendable.js +0 -26
- package/lib/bridge/estimateMaxSpendable.js.map +0 -1
- package/lib/bridge/getTransactionStatus.d.ts +0 -4
- package/lib/bridge/getTransactionStatus.d.ts.map +0 -1
- package/lib/bridge/getTransactionStatus.js.map +0 -1
- package/lib/bridge/index.d.ts +0 -11
- package/lib/bridge/index.d.ts.map +0 -1
- package/lib/bridge/index.js +0 -47
- package/lib/bridge/index.js.map +0 -1
- package/lib/bridge/prepareTransaction.d.ts +0 -4
- package/lib/bridge/prepareTransaction.d.ts.map +0 -1
- package/lib/bridge/prepareTransaction.js +0 -14
- package/lib/bridge/prepareTransaction.js.map +0 -1
- package/lib/bridge/signOperation.d.ts +0 -5
- package/lib/bridge/signOperation.d.ts.map +0 -1
- package/lib/bridge/signOperation.js +0 -76
- package/lib/bridge/signOperation.js.map +0 -1
- package/lib/bridge/synchronization.d.ts +0 -3
- package/lib/bridge/synchronization.d.ts.map +0 -1
- package/lib/bridge/synchronization.js +0 -85
- package/lib/bridge/synchronization.js.map +0 -1
- package/lib/bridge/synchronization.test.d.ts +0 -2
- package/lib/bridge/synchronization.test.d.ts.map +0 -1
- package/lib/bridge/synchronization.test.js +0 -140
- package/lib/bridge/synchronization.test.js.map +0 -1
- package/lib/bridge/transaction.d.ts.map +0 -1
- package/lib/bridge/transaction.js.map +0 -1
- package/lib-es/bridge/broadcast.d.ts +0 -4
- package/lib-es/bridge/broadcast.d.ts.map +0 -1
- package/lib-es/bridge/broadcast.js +0 -7
- package/lib-es/bridge/broadcast.js.map +0 -1
- package/lib-es/bridge/createTransaction.d.ts +0 -4
- package/lib-es/bridge/createTransaction.d.ts.map +0 -1
- package/lib-es/bridge/createTransaction.js +0 -11
- package/lib-es/bridge/createTransaction.js.map +0 -1
- package/lib-es/bridge/estimateMaxSpendable.d.ts +0 -4
- package/lib-es/bridge/estimateMaxSpendable.d.ts.map +0 -1
- package/lib-es/bridge/estimateMaxSpendable.js +0 -19
- package/lib-es/bridge/estimateMaxSpendable.js.map +0 -1
- package/lib-es/bridge/getTransactionStatus.d.ts +0 -4
- package/lib-es/bridge/getTransactionStatus.d.ts.map +0 -1
- package/lib-es/bridge/getTransactionStatus.js.map +0 -1
- package/lib-es/bridge/index.d.ts +0 -11
- package/lib-es/bridge/index.d.ts.map +0 -1
- package/lib-es/bridge/index.js +0 -41
- package/lib-es/bridge/index.js.map +0 -1
- package/lib-es/bridge/prepareTransaction.d.ts +0 -4
- package/lib-es/bridge/prepareTransaction.d.ts.map +0 -1
- package/lib-es/bridge/prepareTransaction.js +0 -10
- package/lib-es/bridge/prepareTransaction.js.map +0 -1
- package/lib-es/bridge/signOperation.d.ts +0 -5
- package/lib-es/bridge/signOperation.d.ts.map +0 -1
- package/lib-es/bridge/signOperation.js +0 -72
- package/lib-es/bridge/signOperation.js.map +0 -1
- package/lib-es/bridge/synchronization.d.ts +0 -3
- package/lib-es/bridge/synchronization.d.ts.map +0 -1
- package/lib-es/bridge/synchronization.js +0 -78
- package/lib-es/bridge/synchronization.js.map +0 -1
- package/lib-es/bridge/synchronization.test.d.ts +0 -2
- package/lib-es/bridge/synchronization.test.d.ts.map +0 -1
- package/lib-es/bridge/synchronization.test.js +0 -135
- package/lib-es/bridge/synchronization.test.js.map +0 -1
- package/lib-es/bridge/transaction.d.ts.map +0 -1
- package/lib-es/bridge/transaction.js.map +0 -1
- package/src/bridge/broadcast.ts +0 -11
- package/src/bridge/createTransaction.ts +0 -13
- package/src/bridge/estimateMaxSpendable.ts +0 -25
- package/src/bridge/index.ts +0 -59
- package/src/bridge/prepareTransaction.ts +0 -18
- package/src/bridge/signOperation.ts +0 -100
- package/src/bridge/synchronization.test.ts +0 -153
- package/src/bridge/synchronization.ts +0 -108
- /package/lib/{bridge/transaction.js → transaction.js} +0 -0
- /package/lib-es/{bridge/transaction.js → transaction.js} +0 -0
package/src/api/index.ts
CHANGED
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
getNextValidSequence,
|
|
17
17
|
lastBlock,
|
|
18
18
|
listOperations,
|
|
19
|
-
|
|
19
|
+
getTransactionStatus,
|
|
20
20
|
} from "../logic";
|
|
21
|
-
import { ListOperationsOptions, XrpAsset } from "../types";
|
|
21
|
+
import { ListOperationsOptions, XrpAsset, XrpMapMemo } from "../types";
|
|
22
22
|
|
|
23
|
-
export function createApi(config: XrpConfig): Api<XrpAsset,
|
|
23
|
+
export function createApi(config: XrpConfig): Api<XrpAsset, XrpMapMemo> {
|
|
24
24
|
coinConfig.setCoinConfig(() => ({ ...config, status: { type: "active" } }));
|
|
25
25
|
|
|
26
26
|
return {
|
|
@@ -31,36 +31,45 @@ export function createApi(config: XrpConfig): Api<XrpAsset, TransactionIntentExt
|
|
|
31
31
|
getBalance,
|
|
32
32
|
lastBlock,
|
|
33
33
|
listOperations: operations,
|
|
34
|
+
validateIntent: getTransactionStatus,
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
export type TransactionIntentExtra = {
|
|
38
|
-
destinationTag?: number | null | undefined;
|
|
39
|
-
memos?: MemoInput[];
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export type XrpSender = {
|
|
43
|
-
address: string;
|
|
44
|
-
publicKey?: string;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
38
|
async function craft(
|
|
48
|
-
transactionIntent: TransactionIntent<XrpAsset,
|
|
39
|
+
transactionIntent: TransactionIntent<XrpAsset, XrpMapMemo>,
|
|
49
40
|
customFees?: bigint,
|
|
50
41
|
): Promise<string> {
|
|
51
|
-
const nextSequenceNumber = await getNextValidSequence(transactionIntent.sender
|
|
42
|
+
const nextSequenceNumber = await getNextValidSequence(transactionIntent.sender);
|
|
52
43
|
const estimatedFees = customFees !== undefined ? customFees : (await estimateFees()).fee;
|
|
44
|
+
|
|
45
|
+
const memosMap =
|
|
46
|
+
transactionIntent.memo?.type === "map" ? transactionIntent.memo.memos : new Map();
|
|
47
|
+
|
|
48
|
+
const destinationTagValue = memosMap.get("destinationTag");
|
|
49
|
+
const destinationTag =
|
|
50
|
+
typeof destinationTagValue === "string" ? Number(destinationTagValue) : undefined;
|
|
51
|
+
|
|
52
|
+
const memoStrings = memosMap.get("memos") as string[] | undefined;
|
|
53
|
+
|
|
54
|
+
let memoEntries: { type: string; data: string }[] = [];
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(memoStrings) && memoStrings.length > 0) {
|
|
57
|
+
memoEntries = memoStrings.map(value => ({ type: "memo", data: value }));
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
const tx = await craftTransaction(
|
|
54
|
-
{ address: transactionIntent.sender
|
|
61
|
+
{ address: transactionIntent.sender, nextSequenceNumber },
|
|
55
62
|
{
|
|
56
63
|
recipient: transactionIntent.recipient,
|
|
57
64
|
amount: transactionIntent.amount,
|
|
58
65
|
fee: estimatedFees,
|
|
59
|
-
destinationTag
|
|
60
|
-
|
|
66
|
+
destinationTag,
|
|
67
|
+
// NOTE: double check before/after here
|
|
68
|
+
memos: memoEntries,
|
|
61
69
|
},
|
|
62
|
-
transactionIntent.
|
|
70
|
+
transactionIntent.senderPublicKey,
|
|
63
71
|
);
|
|
72
|
+
|
|
64
73
|
return tx.serializedTransaction;
|
|
65
74
|
}
|
|
66
75
|
|
package/src/index.ts
CHANGED
package/src/logic/combine.ts
CHANGED
|
@@ -8,11 +8,18 @@ type XRPTransaction = JsonObject & {
|
|
|
8
8
|
|
|
9
9
|
export function combine(transaction: string, signature: string, publicKey?: string): string {
|
|
10
10
|
const xrplTransaction: JsonObject = decode(transaction);
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
let transactionWithSignature: XRPTransaction = { ...xrplTransaction } as any;
|
|
12
13
|
|
|
13
14
|
if (publicKey) {
|
|
14
|
-
transactionWithSignature = {
|
|
15
|
+
transactionWithSignature = {
|
|
16
|
+
...transactionWithSignature,
|
|
17
|
+
SigningPubKey: publicKey,
|
|
18
|
+
};
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
transactionWithSignature = { ...transactionWithSignature, TxnSignature: signature };
|
|
22
|
+
|
|
23
|
+
const encoded = encode(transactionWithSignature).toUpperCase();
|
|
24
|
+
return encoded;
|
|
18
25
|
}
|
|
@@ -2,21 +2,33 @@ import { faker } from "@faker-js/faker";
|
|
|
2
2
|
import { getBalance } from "./getBalance";
|
|
3
3
|
|
|
4
4
|
const mockGetAccountInfo = jest.fn();
|
|
5
|
+
const mockGetServerInfos = jest.fn();
|
|
5
6
|
jest.mock("../network", () => ({
|
|
6
7
|
getAccountInfo: (address: string) => mockGetAccountInfo(address),
|
|
8
|
+
getServerInfos: () => mockGetServerInfos(),
|
|
7
9
|
}));
|
|
8
10
|
|
|
9
11
|
describe("getBalance", () => {
|
|
10
12
|
afterEach(() => {
|
|
11
13
|
mockGetAccountInfo.mockClear();
|
|
14
|
+
mockGetServerInfos.mockClear();
|
|
12
15
|
});
|
|
13
16
|
|
|
14
17
|
it("returns the balance from Explorer", async () => {
|
|
18
|
+
mockGetServerInfos.mockResolvedValue({
|
|
19
|
+
info: {
|
|
20
|
+
validated_ledger: {
|
|
21
|
+
reserve_base_xrp: 23,
|
|
22
|
+
reserve_inc_xrp: 5,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
15
26
|
// Given
|
|
16
27
|
const balance = faker.number.bigInt(100_000_000);
|
|
17
28
|
const address = "ACCOUNT_ADDRESS";
|
|
18
29
|
mockGetAccountInfo.mockResolvedValue({
|
|
19
30
|
balance,
|
|
31
|
+
ownerCount: 0,
|
|
20
32
|
});
|
|
21
33
|
|
|
22
34
|
// When
|
|
@@ -24,7 +36,8 @@ describe("getBalance", () => {
|
|
|
24
36
|
|
|
25
37
|
// Then
|
|
26
38
|
expect(mockGetAccountInfo).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(mockGetServerInfos).toHaveBeenCalledTimes(1);
|
|
27
40
|
expect(mockGetAccountInfo.mock.lastCall[0]).toEqual(address);
|
|
28
|
-
expect(result).toEqual([{ value: balance, asset: { type: "native" } }]);
|
|
41
|
+
expect(result).toEqual([{ value: balance, asset: { type: "native" }, locked: 23000000n }]);
|
|
29
42
|
});
|
|
30
43
|
});
|
package/src/logic/getBalance.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { Balance } from "@ledgerhq/coin-framework/api/types";
|
|
2
|
-
import { getAccountInfo } from "../network";
|
|
2
|
+
import { getAccountInfo, getServerInfos } from "../network";
|
|
3
3
|
import { XrpAsset } from "../types";
|
|
4
|
+
import { parseAPIValue } from "./common";
|
|
4
5
|
|
|
5
6
|
export async function getBalance(address: string): Promise<Balance<XrpAsset>[]> {
|
|
6
7
|
const accountInfo = await getAccountInfo(address);
|
|
7
|
-
|
|
8
|
+
const serverInfo = await getServerInfos();
|
|
9
|
+
|
|
10
|
+
const reserveMinXRP = parseAPIValue(serverInfo.info.validated_ledger.reserve_base_xrp.toString());
|
|
11
|
+
const reservePerTrustline = parseAPIValue(
|
|
12
|
+
serverInfo.info.validated_ledger.reserve_inc_xrp.toString(),
|
|
13
|
+
);
|
|
14
|
+
const trustlines = accountInfo.ownerCount;
|
|
15
|
+
|
|
16
|
+
const locked = reserveMinXRP.plus(reservePerTrustline.times(trustlines));
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
value: BigInt(accountInfo.balance),
|
|
20
|
+
asset: { type: "native" },
|
|
21
|
+
locked: BigInt(locked.toString()),
|
|
22
|
+
},
|
|
23
|
+
];
|
|
8
24
|
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { getTransactionStatus } from "./getTransactionStatus";
|
|
2
|
+
import * as logic from "./index";
|
|
3
|
+
|
|
4
|
+
const mockGetServerInfos = jest.fn();
|
|
5
|
+
const mockCachedRecipientIsNew = jest.fn();
|
|
6
|
+
|
|
7
|
+
jest.mock("../network", () => ({
|
|
8
|
+
getServerInfos: () => mockGetServerInfos(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.spyOn(logic, "cachedRecipientIsNew").mockImplementation(addr => {
|
|
12
|
+
if (addr === RECIPIENT_NEW) {
|
|
13
|
+
return Promise.resolve(true);
|
|
14
|
+
}
|
|
15
|
+
return Promise.resolve(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const reserveBase = 10_000_000n; // 10 XRP (drops)
|
|
19
|
+
|
|
20
|
+
const SENDER = "rPSCfmnX3t9jQJG5RNcZtSaP5UhExZDue4";
|
|
21
|
+
const RECIPIENT = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe";
|
|
22
|
+
const RECIPIENT_NEW = "rDKsbvy9uaNpPtvVFraJyNGfjvTw8xivgK";
|
|
23
|
+
|
|
24
|
+
const account = {
|
|
25
|
+
address: SENDER,
|
|
26
|
+
balance: 50_000_000n,
|
|
27
|
+
currencyUnit: {
|
|
28
|
+
code: "XRP",
|
|
29
|
+
magnitude: 6,
|
|
30
|
+
name: "XRP",
|
|
31
|
+
symbol: "XRP",
|
|
32
|
+
},
|
|
33
|
+
currencyName: "XRP",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe("getTransactionStatus", () => {
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
mockGetServerInfos.mockReset();
|
|
39
|
+
mockCachedRecipientIsNew.mockReset();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns no errors on valid transaction", async () => {
|
|
43
|
+
mockGetServerInfos.mockResolvedValue({
|
|
44
|
+
info: {
|
|
45
|
+
validated_ledger: {
|
|
46
|
+
reserve_base_xrp: reserveBase / 1_000_000n, // XRP value, not drops
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
mockCachedRecipientIsNew.mockResolvedValue(false);
|
|
52
|
+
|
|
53
|
+
const result = await getTransactionStatus(
|
|
54
|
+
account as any,
|
|
55
|
+
{
|
|
56
|
+
amount: 20_000_000n,
|
|
57
|
+
fee: 10_000n,
|
|
58
|
+
recipient: RECIPIENT,
|
|
59
|
+
} as any,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
expect(result.errors).toEqual({});
|
|
63
|
+
expect(result.warnings).toEqual({});
|
|
64
|
+
expect(result.totalSpent).toBe(20_010_000n);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("throws FeeTooHigh warning when fee is >10% of amount", async () => {
|
|
68
|
+
mockGetServerInfos.mockResolvedValue({
|
|
69
|
+
info: {
|
|
70
|
+
validated_ledger: {
|
|
71
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = await getTransactionStatus(
|
|
77
|
+
account as any,
|
|
78
|
+
{
|
|
79
|
+
amount: 1_000_000n,
|
|
80
|
+
fee: 200_000n, // 20%
|
|
81
|
+
recipient: RECIPIENT,
|
|
82
|
+
} as any,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(result.warnings.feeTooHigh).toBeInstanceOf(Error);
|
|
86
|
+
expect(result.errors).toEqual({});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("errors when fee is missing", async () => {
|
|
90
|
+
mockGetServerInfos.mockResolvedValue({
|
|
91
|
+
info: {
|
|
92
|
+
validated_ledger: {
|
|
93
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await getTransactionStatus(
|
|
99
|
+
account as any,
|
|
100
|
+
{
|
|
101
|
+
amount: 10_000_000n,
|
|
102
|
+
recipient: RECIPIENT,
|
|
103
|
+
} as any,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(result.errors.fee?.name).toBe("FeeNotLoaded");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("errors if recipient is same as sender", async () => {
|
|
110
|
+
mockGetServerInfos.mockResolvedValue({
|
|
111
|
+
info: {
|
|
112
|
+
validated_ledger: {
|
|
113
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await getTransactionStatus(
|
|
119
|
+
account as any,
|
|
120
|
+
{
|
|
121
|
+
amount: 10_000_000n,
|
|
122
|
+
fee: 10_000n,
|
|
123
|
+
recipient: SENDER,
|
|
124
|
+
} as any,
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
expect(result.errors.recipient?.name).toBe("InvalidAddressBecauseDestinationIsAlsoSource");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("errors if recipient is new and amount is too low", async () => {
|
|
131
|
+
mockGetServerInfos.mockResolvedValue({
|
|
132
|
+
info: {
|
|
133
|
+
validated_ledger: {
|
|
134
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
mockCachedRecipientIsNew.mockResolvedValue(true);
|
|
140
|
+
|
|
141
|
+
const result = await getTransactionStatus(
|
|
142
|
+
account as any,
|
|
143
|
+
{
|
|
144
|
+
amount: 5_000_000n,
|
|
145
|
+
fee: 10_000n,
|
|
146
|
+
recipient: RECIPIENT_NEW,
|
|
147
|
+
} as any,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
expect(result.errors.amount?.name).toBe("NotEnoughBalanceBecauseDestinationNotCreated");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("errors if amount is zero", async () => {
|
|
154
|
+
mockGetServerInfos.mockResolvedValue({
|
|
155
|
+
info: {
|
|
156
|
+
validated_ledger: {
|
|
157
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = await getTransactionStatus(
|
|
163
|
+
account as any,
|
|
164
|
+
{
|
|
165
|
+
amount: 0n,
|
|
166
|
+
fee: 10_000n,
|
|
167
|
+
recipient: RECIPIENT,
|
|
168
|
+
} as any,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
expect(result.errors.amount?.name).toBe("AmountRequired");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("errors if recipient is invalid", async () => {
|
|
175
|
+
mockGetServerInfos.mockResolvedValue({
|
|
176
|
+
info: {
|
|
177
|
+
validated_ledger: {
|
|
178
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const result = await getTransactionStatus(
|
|
184
|
+
account as any,
|
|
185
|
+
{
|
|
186
|
+
amount: 1_000_000n,
|
|
187
|
+
fee: 10_000n,
|
|
188
|
+
recipient: "not-an-address",
|
|
189
|
+
} as any,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(result.errors.recipient?.name).toBe("InvalidAddress");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("errors if recipient is missing", async () => {
|
|
196
|
+
mockGetServerInfos.mockResolvedValue({
|
|
197
|
+
info: {
|
|
198
|
+
validated_ledger: {
|
|
199
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const result = await getTransactionStatus(
|
|
205
|
+
account as any,
|
|
206
|
+
{
|
|
207
|
+
amount: 1_000_000n,
|
|
208
|
+
fee: 10_000n,
|
|
209
|
+
recipient: "",
|
|
210
|
+
} as any,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
expect(result.errors.recipient?.name).toBe("RecipientRequired");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -9,40 +9,37 @@ import {
|
|
|
9
9
|
NotEnoughSpendableBalance,
|
|
10
10
|
RecipientRequired,
|
|
11
11
|
} from "@ledgerhq/errors";
|
|
12
|
-
import BigNumber from "bignumber.js";
|
|
13
12
|
import { isValidClassicAddress } from "ripple-address-codec";
|
|
14
|
-
import { Account, AccountBridge } from "@ledgerhq/types-live";
|
|
15
13
|
import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
|
|
16
14
|
import { getServerInfos } from "../network";
|
|
17
|
-
import { cachedRecipientIsNew, parseAPIValue } from "
|
|
18
|
-
import { Transaction,
|
|
15
|
+
import { cachedRecipientIsNew, parseAPIValue } from ".";
|
|
16
|
+
import { Transaction, TransactionValidation, Account } from "@ledgerhq/coin-framework/api/types";
|
|
19
17
|
|
|
20
|
-
export const getTransactionStatus
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
>["getTransactionStatus"] = async (account, transaction) => {
|
|
18
|
+
export const getTransactionStatus = async (
|
|
19
|
+
account: Account,
|
|
20
|
+
transaction: Transaction,
|
|
21
|
+
): Promise<TransactionValidation> => {
|
|
25
22
|
const errors: Record<string, Error> = {};
|
|
26
23
|
const warnings: Record<string, Error> = {};
|
|
27
24
|
const serverInfos = await getServerInfos();
|
|
28
25
|
const reserveBaseXRP = parseAPIValue(
|
|
29
26
|
serverInfos.info.validated_ledger.reserve_base_xrp.toString(),
|
|
30
27
|
);
|
|
31
|
-
const estimatedFees =
|
|
32
|
-
const totalSpent =
|
|
33
|
-
const amount =
|
|
28
|
+
const estimatedFees = transaction.fee || 0n;
|
|
29
|
+
const totalSpent = transaction.amount + estimatedFees;
|
|
30
|
+
const amount = transaction.amount;
|
|
34
31
|
|
|
35
|
-
if (amount
|
|
32
|
+
if (amount > 0 && estimatedFees * 10n > amount) {
|
|
36
33
|
warnings.feeTooHigh = new FeeTooHigh();
|
|
37
34
|
}
|
|
38
35
|
|
|
39
36
|
if (!transaction.fee) {
|
|
40
37
|
errors.fee = new FeeNotLoaded();
|
|
41
|
-
} else if (transaction.fee
|
|
38
|
+
} else if (transaction.fee == 0n) {
|
|
42
39
|
errors.fee = new FeeRequired();
|
|
43
|
-
} else if (totalSpent
|
|
40
|
+
} else if (totalSpent > account.balance - BigInt(reserveBaseXRP.toString())) {
|
|
44
41
|
errors.amount = new NotEnoughSpendableBalance("", {
|
|
45
|
-
minimumAmount: formatCurrencyUnit(account.
|
|
42
|
+
minimumAmount: formatCurrencyUnit(account.currencyUnit, reserveBaseXRP, {
|
|
46
43
|
disableRounding: true,
|
|
47
44
|
useGrouping: false,
|
|
48
45
|
showCode: true,
|
|
@@ -51,10 +48,10 @@ export const getTransactionStatus: AccountBridge<
|
|
|
51
48
|
} else if (
|
|
52
49
|
transaction.recipient &&
|
|
53
50
|
(await cachedRecipientIsNew(transaction.recipient)) &&
|
|
54
|
-
transaction.amount.
|
|
51
|
+
transaction.amount < BigInt(reserveBaseXRP.toString())
|
|
55
52
|
) {
|
|
56
53
|
errors.amount = new NotEnoughBalanceBecauseDestinationNotCreated("", {
|
|
57
|
-
minimalAmount: formatCurrencyUnit(account.
|
|
54
|
+
minimalAmount: formatCurrencyUnit(account.currencyUnit, reserveBaseXRP, {
|
|
58
55
|
disableRounding: true,
|
|
59
56
|
useGrouping: false,
|
|
60
57
|
showCode: true,
|
|
@@ -64,15 +61,15 @@ export const getTransactionStatus: AccountBridge<
|
|
|
64
61
|
|
|
65
62
|
if (!transaction.recipient) {
|
|
66
63
|
errors.recipient = new RecipientRequired("");
|
|
67
|
-
} else if (account.
|
|
64
|
+
} else if (account.address === transaction.recipient) {
|
|
68
65
|
errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
|
|
69
66
|
} else if (!isValidClassicAddress(transaction.recipient)) {
|
|
70
67
|
errors.recipient = new InvalidAddress("", {
|
|
71
|
-
currencyName: account.
|
|
68
|
+
currencyName: account.currencyName,
|
|
72
69
|
});
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
if (!errors.amount && amount
|
|
72
|
+
if (!errors.amount && amount == 0n) {
|
|
76
73
|
errors.amount = new AmountRequired();
|
|
77
74
|
}
|
|
78
75
|
|
package/src/logic/index.ts
CHANGED
|
@@ -6,11 +6,7 @@ export { estimateFees } from "./estimateFees";
|
|
|
6
6
|
export { getBalance } from "./getBalance";
|
|
7
7
|
export { lastBlock } from "./lastBlock";
|
|
8
8
|
export { listOperations } from "./listOperations";
|
|
9
|
-
export {
|
|
10
|
-
|
|
11
|
-
cachedRecipientIsNew,
|
|
12
|
-
getNextValidSequence,
|
|
13
|
-
removeCachedRecipientIsNew,
|
|
14
|
-
} from "./utils";
|
|
9
|
+
export { getTransactionStatus } from "./getTransactionStatus";
|
|
10
|
+
export { RIPPLE_EPOCH, cachedRecipientIsNew, getNextValidSequence } from "./utils";
|
|
15
11
|
|
|
16
12
|
export { parseAPIValue } from "./common";
|
package/src/logic/utils.ts
CHANGED
|
@@ -22,6 +22,15 @@ function isRecipientValid(recipient: string): boolean {
|
|
|
22
22
|
return isValidClassicAddress(recipient);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// --- 10-Seconds Cache Implementation ---
|
|
26
|
+
type CacheEntry = {
|
|
27
|
+
value: boolean;
|
|
28
|
+
expiresAt: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const recipientCache = new Map<string, CacheEntry>();
|
|
32
|
+
const TTL = 10 * 1000; // 10 seconds
|
|
33
|
+
|
|
25
34
|
const recipientIsNew = async (recipient: string): Promise<boolean> => {
|
|
26
35
|
if (!isRecipientValid(recipient)) return false;
|
|
27
36
|
|
|
@@ -29,12 +38,19 @@ const recipientIsNew = async (recipient: string): Promise<boolean> => {
|
|
|
29
38
|
return info.isNewAccount;
|
|
30
39
|
};
|
|
31
40
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
export const cachedRecipientIsNew = async (recipient: string): Promise<boolean> => {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const cached = recipientCache.get(recipient);
|
|
44
|
+
|
|
45
|
+
if (cached && now < cached.expiresAt) {
|
|
46
|
+
return cached.value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isNew = await recipientIsNew(recipient);
|
|
50
|
+
recipientCache.set(recipient, {
|
|
51
|
+
value: isNew,
|
|
52
|
+
expiresAt: now + TTL,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return isNew;
|
|
40
56
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import BigNumber from "bignumber.js";
|
|
2
2
|
import { DatasetTest } from "@ledgerhq/types-live";
|
|
3
3
|
import { InvalidAddressBecauseDestinationIsAlsoSource } from "@ledgerhq/errors";
|
|
4
|
-
import { fromTransactionRaw } from "../
|
|
4
|
+
import { fromTransactionRaw } from "../transaction";
|
|
5
5
|
import { Transaction } from "../types";
|
|
6
6
|
|
|
7
7
|
export const newAddress1 = "rZvBc5e2YR1A9otS3r9DyGh3NDP8XLLp4";
|
|
@@ -97,18 +97,19 @@ export const dataset: DatasetTest<Transaction> = {
|
|
|
97
97
|
recipient: "rageXHB6Q4VbvvWdTzKANwjeCT4HXFCKX7",
|
|
98
98
|
amount: "10000000",
|
|
99
99
|
tag: null,
|
|
100
|
-
fee: "
|
|
100
|
+
fee: "10", // NOTE: fee is not customizable, this field is ignored
|
|
101
101
|
feeCustomUnit: null,
|
|
102
102
|
networkInfo: null,
|
|
103
103
|
}),
|
|
104
104
|
expectedStatus: {
|
|
105
105
|
amount: new BigNumber("10000000"),
|
|
106
|
-
estimatedFees: new BigNumber("
|
|
106
|
+
estimatedFees: new BigNumber("10"), // NOTE: hardcoded fee
|
|
107
|
+
|
|
107
108
|
errors: {
|
|
108
109
|
recipient: new InvalidAddressBecauseDestinationIsAlsoSource(),
|
|
109
110
|
},
|
|
110
111
|
warnings: {},
|
|
111
|
-
totalSpent: new BigNumber("
|
|
112
|
+
totalSpent: new BigNumber("10000010"), // NOTE: amount + hardcoded fee
|
|
112
113
|
},
|
|
113
114
|
},
|
|
114
115
|
{
|
|
@@ -118,16 +119,16 @@ export const dataset: DatasetTest<Transaction> = {
|
|
|
118
119
|
recipient: "rB6pwovsyrFWhPYUsjj9V3CHck985QjiXi",
|
|
119
120
|
amount: "10000000",
|
|
120
121
|
tag: 12345,
|
|
121
|
-
fee: "
|
|
122
|
+
fee: "10", // NOTE: fee is not customizable, this field is ignored
|
|
122
123
|
feeCustomUnit: null,
|
|
123
124
|
networkInfo: null,
|
|
124
125
|
}),
|
|
125
126
|
expectedStatus: {
|
|
126
127
|
amount: new BigNumber("10000000"),
|
|
127
|
-
estimatedFees: new BigNumber("
|
|
128
|
+
estimatedFees: new BigNumber("10"), // NOTE: hardcoded fee
|
|
128
129
|
errors: {},
|
|
129
130
|
warnings: {},
|
|
130
|
-
totalSpent: new BigNumber("
|
|
131
|
+
totalSpent: new BigNumber("10000010"), // NOTE: amount + hardcoded fee
|
|
131
132
|
},
|
|
132
133
|
},
|
|
133
134
|
],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BigNumber } from "bignumber.js";
|
|
2
|
-
import type { Transaction, TransactionRaw } from "
|
|
2
|
+
import type { Transaction, TransactionRaw } from "./types";
|
|
3
3
|
import { formatTransactionStatus } from "@ledgerhq/coin-framework/formatters";
|
|
4
4
|
import {
|
|
5
5
|
fromTransactionCommonRaw,
|
package/src/types/model.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { TypedMapMemo } from "@ledgerhq/coin-framework/api/types";
|
|
2
|
+
|
|
1
3
|
export type AccountInfo = {
|
|
2
4
|
isNewAccount: boolean;
|
|
3
5
|
balance: string;
|
|
@@ -11,7 +13,16 @@ export type XrpMemo = {
|
|
|
11
13
|
type?: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
export type XrpMemoKind = "destinationTag" | "memo";
|
|
17
|
+
|
|
18
|
+
export type XrpMemoValueMap = {
|
|
19
|
+
destinationTag: string;
|
|
20
|
+
memos: string[];
|
|
21
|
+
};
|
|
22
|
+
export type XrpMapMemo = TypedMapMemo<XrpMemoValueMap>;
|
|
23
|
+
|
|
14
24
|
type Order = "asc" | "desc";
|
|
25
|
+
|
|
15
26
|
export type ListOperationsOptions = {
|
|
16
27
|
// pagination:
|
|
17
28
|
limit?: number;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../src/bridge/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,WAAW,CAK7D,CAAC"}
|
package/lib/bridge/broadcast.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.broadcast = void 0;
|
|
4
|
-
const operation_1 = require("@ledgerhq/coin-framework/operation");
|
|
5
|
-
const logic_1 = require("../logic");
|
|
6
|
-
const broadcast = async ({ signedOperation: { signature, operation }, }) => {
|
|
7
|
-
const hash = await (0, logic_1.broadcast)(signature);
|
|
8
|
-
return (0, operation_1.patchOperationWithHash)(operation, hash);
|
|
9
|
-
};
|
|
10
|
-
exports.broadcast = broadcast;
|
|
11
|
-
//# sourceMappingURL=broadcast.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../../src/bridge/broadcast.ts"],"names":[],"mappings":";;;AACA,kEAA4E;AAC5E,oCAAuD;AAGhD,MAAM,SAAS,GAA4C,KAAK,EAAE,EACvE,eAAe,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAC1C,EAAE,EAAE;IACH,MAAM,IAAI,GAAG,MAAM,IAAA,iBAAc,EAAC,SAAS,CAAC,CAAC;IAC7C,OAAO,IAAA,kCAAsB,EAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC,CAAC;AALW,QAAA,SAAS,aAKpB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"createTransaction.d.ts","sourceRoot":"","sources":["../../src/bridge/createTransaction.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,mBAAmB,CAQ5E,CAAC"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createTransaction = void 0;
|
|
7
|
-
const bignumber_js_1 = __importDefault(require("bignumber.js"));
|
|
8
|
-
const createTransaction = () => ({
|
|
9
|
-
family: "xrp",
|
|
10
|
-
amount: new bignumber_js_1.default(0),
|
|
11
|
-
recipient: "",
|
|
12
|
-
fee: null,
|
|
13
|
-
tag: undefined,
|
|
14
|
-
networkInfo: null,
|
|
15
|
-
feeCustomUnit: null,
|
|
16
|
-
});
|
|
17
|
-
exports.createTransaction = createTransaction;
|
|
18
|
-
//# sourceMappingURL=createTransaction.js.map
|