@ledgerhq/coin-xrp 7.24.0 → 7.25.2
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/CHANGELOG.md +24 -0
- package/lib/api/craftRawTransaction.d.ts.map +1 -1
- package/lib/api/craftRawTransaction.js +2 -1
- package/lib/api/craftRawTransaction.js.map +1 -1
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +5 -1
- package/lib/api/index.js.map +1 -1
- package/lib/api/listOperations.js +1 -1
- package/lib/api/listOperations.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 +4 -5
- package/lib/network/index.js.map +1 -1
- package/lib/network/types.d.ts +0 -1
- package/lib/network/types.d.ts.map +1 -1
- package/lib/network/types.js +0 -4
- package/lib/network/types.js.map +1 -1
- package/lib/utils/predicates.d.ts +5 -0
- package/lib/utils/predicates.d.ts.map +1 -0
- package/lib/utils/predicates.js +23 -0
- package/lib/utils/predicates.js.map +1 -0
- package/lib-es/api/craftRawTransaction.d.ts.map +1 -1
- package/lib-es/api/craftRawTransaction.js +2 -1
- package/lib-es/api/craftRawTransaction.js.map +1 -1
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +5 -1
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/api/listOperations.js +1 -1
- package/lib-es/api/listOperations.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 +3 -4
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/types.d.ts +0 -1
- package/lib-es/network/types.d.ts.map +1 -1
- package/lib-es/network/types.js +0 -3
- package/lib-es/network/types.js.map +1 -1
- package/lib-es/utils/predicates.d.ts +5 -0
- package/lib-es/utils/predicates.d.ts.map +1 -0
- package/lib-es/utils/predicates.js +19 -0
- package/lib-es/utils/predicates.js.map +1 -0
- package/package.json +9 -4
- package/src/api/combine.test.ts +18 -27
- package/src/api/craftRawTransaction.test.ts +5 -6
- package/src/api/craftRawTransaction.ts +4 -1
- package/src/api/getBalance.test.ts +2 -4
- package/src/api/getBlock.integ.test.ts +5 -7
- package/src/api/getBlockInfo.integ.test.ts +5 -7
- package/src/api/index.integ.test.ts +65 -42
- package/src/api/index.test.ts +56 -45
- package/src/api/index.ts +7 -2
- package/src/api/listOperations.test.ts +4 -2
- package/src/api/listOperations.ts +3 -5
- package/src/api/validateIntent.test.ts +56 -128
- package/src/network/index.test.ts +23 -28
- package/src/network/index.ts +13 -16
- package/src/network/types.ts +0 -3
- package/src/utils/predicates.ts +25 -0
- package/jest.config.js +0 -32
- package/jest.integ.config.js +0 -21
- package/knip.json +0 -11
- package/tsconfig.build.json +0 -22
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ErrorResponse } from '../network/types';
|
|
2
|
+
import type { SignerEntry } from '../types';
|
|
3
|
+
export declare function isSignerEntry(obj: unknown): obj is SignerEntry;
|
|
4
|
+
export declare function isErrorResponse(obj: object): obj is ErrorResponse;
|
|
5
|
+
//# sourceMappingURL=predicates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predicates.d.ts","sourceRoot":"","sources":["../../src/utils/predicates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,WAAW,CAc9D;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,aAAa,CAEjE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
export function isSignerEntry(obj) {
|
|
4
|
+
return (typeof obj === 'object' &&
|
|
5
|
+
obj !== null &&
|
|
6
|
+
'Signer' in obj &&
|
|
7
|
+
typeof obj.Signer === 'object' &&
|
|
8
|
+
obj.Signer !== null &&
|
|
9
|
+
'Account' in obj.Signer &&
|
|
10
|
+
typeof obj.Signer.Account === 'string' &&
|
|
11
|
+
'SigningPubKey' in obj.Signer &&
|
|
12
|
+
typeof obj.Signer.SigningPubKey === 'string' &&
|
|
13
|
+
'TxnSignature' in obj.Signer &&
|
|
14
|
+
typeof obj.Signer.TxnSignature === 'string');
|
|
15
|
+
}
|
|
16
|
+
export function isErrorResponse(obj) {
|
|
17
|
+
return 'status' in obj && obj.status === 'error';
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=predicates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predicates.js","sourceRoot":"","sources":["../../src/utils/predicates.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,sCAAsC;AAKtC,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACZ,QAAQ,IAAI,GAAG;QACf,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ;QAC9B,GAAG,CAAC,MAAM,KAAK,IAAI;QACnB,SAAS,IAAI,GAAG,CAAC,MAAM;QACvB,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,QAAQ;QACtC,eAAe,IAAI,GAAG,CAAC,MAAM;QAC7B,OAAO,GAAG,CAAC,MAAM,CAAC,aAAa,KAAK,QAAQ;QAC5C,cAAc,IAAI,GAAG,CAAC,MAAM;QAC5B,OAAO,GAAG,CAAC,MAAM,CAAC,YAAY,KAAK,QAAQ,CAC5C,CAAA;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,QAAQ,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAA;AAClD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ledgerhq/coin-xrp",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.25.2",
|
|
4
4
|
"description": "Ledger XRP Coin integration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Ledger",
|
|
@@ -20,6 +20,12 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib",
|
|
25
|
+
"lib-es",
|
|
26
|
+
"src",
|
|
27
|
+
"CHANGELOG.md"
|
|
28
|
+
],
|
|
23
29
|
"typesVersions": {
|
|
24
30
|
"*": {
|
|
25
31
|
"lib/*": [
|
|
@@ -72,10 +78,9 @@
|
|
|
72
78
|
"invariant": "^2.2.4",
|
|
73
79
|
"ripple-address-codec": "^5.0.0",
|
|
74
80
|
"ripple-binary-codec": "^1.3.0",
|
|
75
|
-
"@ledgerhq/coin-module-framework": "^
|
|
81
|
+
"@ledgerhq/coin-module-framework": "^4.0.0"
|
|
76
82
|
},
|
|
77
83
|
"devDependencies": {
|
|
78
|
-
"@faker-js/faker": "^8.4.1",
|
|
79
84
|
"@swc/core": "1.15.11",
|
|
80
85
|
"@swc/jest": "0.2.39",
|
|
81
86
|
"@types/invariant": "^2.2.37",
|
|
@@ -104,7 +109,7 @@
|
|
|
104
109
|
"test": "jest",
|
|
105
110
|
"test:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand",
|
|
106
111
|
"test-watch": "jest --watch",
|
|
107
|
-
"test-integ": "jest --config=jest.integ.config.js
|
|
112
|
+
"test-integ": "jest --config=jest.integ.config.js",
|
|
108
113
|
"test-integ:debug": "node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --config=jest.integ.config.js",
|
|
109
114
|
"typecheck": "tsc --noEmit --customConditions node",
|
|
110
115
|
"unimported": "knip --include=files,dependencies",
|
package/src/api/combine.test.ts
CHANGED
|
@@ -5,12 +5,6 @@ import { decode, encode } from 'ripple-binary-codec'
|
|
|
5
5
|
import { SignerEntry } from '../types'
|
|
6
6
|
import { combine } from './combine'
|
|
7
7
|
|
|
8
|
-
type DecodedTx = {
|
|
9
|
-
SigningPubKey?: string
|
|
10
|
-
TxnSignature?: string
|
|
11
|
-
Signers?: SignerEntry[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
function signerEntry(account: string, signingPubKey: string, txnSignature = ''): SignerEntry {
|
|
15
9
|
return {
|
|
16
10
|
Signer: {
|
|
@@ -45,12 +39,13 @@ describe('combine', () => {
|
|
|
45
39
|
],
|
|
46
40
|
})
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
expect(decode(combine(tx, 'A1B2'))).toMatchObject({
|
|
43
|
+
Signers: [
|
|
44
|
+
{ Signer: { TxnSignature: 'A1B2' } },
|
|
45
|
+
{ Signer: { TxnSignature: '' } },
|
|
46
|
+
{ Signer: { TxnSignature: 'DEADBEEF' } },
|
|
47
|
+
],
|
|
48
|
+
})
|
|
54
49
|
})
|
|
55
50
|
|
|
56
51
|
it('fills the matching multi-sign signer signature when public key is provided', () => {
|
|
@@ -63,11 +58,9 @@ describe('combine', () => {
|
|
|
63
58
|
Signers: [signerEntry(signer1, pubKey1, ''), signerEntry(signer2, pubKey2, '')],
|
|
64
59
|
})
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
expect(decoded.Signers?.[0]?.Signer.TxnSignature).toBe('')
|
|
70
|
-
expect(decoded.Signers?.[1]?.Signer.TxnSignature).toBe('B2C3')
|
|
61
|
+
expect(decode(combine(tx, 'B2C3', pubKey2))).toMatchObject({
|
|
62
|
+
Signers: [{ Signer: { TxnSignature: '' } }, { Signer: { TxnSignature: 'B2C3' } }],
|
|
63
|
+
})
|
|
71
64
|
})
|
|
72
65
|
|
|
73
66
|
it('sets TxnSignature and missing SigningPubKey for single-sign transactions', () => {
|
|
@@ -78,11 +71,10 @@ describe('combine', () => {
|
|
|
78
71
|
Destination: destination,
|
|
79
72
|
})
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
expect(decoded.SigningPubKey).toBe(pubKey1)
|
|
74
|
+
expect(decode(combine(tx, 'C3D4', pubKey1))).toMatchObject({
|
|
75
|
+
TxnSignature: 'C3D4',
|
|
76
|
+
SigningPubKey: pubKey1,
|
|
77
|
+
})
|
|
86
78
|
})
|
|
87
79
|
|
|
88
80
|
it('does not overwrite existing SigningPubKey for single-sign transactions', () => {
|
|
@@ -94,10 +86,9 @@ describe('combine', () => {
|
|
|
94
86
|
SigningPubKey: pubKey2,
|
|
95
87
|
})
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
expect(decoded.SigningPubKey).toBe(pubKey2)
|
|
89
|
+
expect(decode(combine(tx, 'D4E5', pubKey3))).toMatchObject({
|
|
90
|
+
TxnSignature: 'D4E5',
|
|
91
|
+
SigningPubKey: pubKey2,
|
|
92
|
+
})
|
|
102
93
|
})
|
|
103
94
|
})
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { encode, decode } from 'ripple-binary-codec'
|
|
5
5
|
import { SignerEntry } from '../types'
|
|
6
|
+
import { isSignerEntry } from '../utils/predicates'
|
|
6
7
|
import { craftRawTransaction } from './craftRawTransaction'
|
|
7
8
|
|
|
8
9
|
// --- Mocks ---
|
|
@@ -45,12 +46,10 @@ describe('craftRawTransaction', () => {
|
|
|
45
46
|
|
|
46
47
|
// Helper to safely extract Signers array without relying on type assertions elsewhere
|
|
47
48
|
function getSigners(obj: unknown): SignerEntry[] | undefined {
|
|
48
|
-
if (!obj || typeof obj !== 'object') return undefined
|
|
49
|
-
const
|
|
50
|
-
if (Array.isArray(
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
return undefined
|
|
49
|
+
if (!obj || typeof obj !== 'object' || !('Signers' in obj)) return undefined
|
|
50
|
+
const signers = obj.Signers
|
|
51
|
+
if (!Array.isArray(signers)) return undefined
|
|
52
|
+
return signers.filter(isSignerEntry)
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
it('fills missing Fee, Sequence, LastLedgerSequence & SigningPubKey for a standard payment', async () => {
|
|
@@ -7,6 +7,7 @@ import { JsonObject } from 'ripple-binary-codec/dist/types/serialized-type'
|
|
|
7
7
|
import { getLedgerIndex } from '../network'
|
|
8
8
|
import { SignerEntry } from '../types'
|
|
9
9
|
import { sortSignersByNumericAddress } from '../utils'
|
|
10
|
+
import { isSignerEntry } from '../utils/predicates'
|
|
10
11
|
import { estimateFees } from './estimateFees'
|
|
11
12
|
|
|
12
13
|
const LEDGER_OFFSET = 20
|
|
@@ -37,7 +38,9 @@ function craftRawTransactionMultiSign(
|
|
|
37
38
|
xrplTransaction.Signers.push(signer)
|
|
38
39
|
// The Signers array must be sorted based on the numeric value of the signer addresses
|
|
39
40
|
// We could probably insert it at the right place directly but like this we make sure the rest is also sorted.
|
|
40
|
-
xrplTransaction.Signers = sortSignersByNumericAddress(
|
|
41
|
+
xrplTransaction.Signers = sortSignersByNumericAddress(
|
|
42
|
+
xrplTransaction.Signers.filter(isSignerEntry)
|
|
43
|
+
)
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const serializedTransaction = encode(xrplTransaction)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { faker } from '@faker-js/faker'
|
|
5
4
|
import { getBalance } from './getBalance'
|
|
6
5
|
|
|
7
6
|
const address = 'ACCOUNT_ADDRESS'
|
|
@@ -28,9 +27,8 @@ describe('getBalance', () => {
|
|
|
28
27
|
},
|
|
29
28
|
})
|
|
30
29
|
// Given
|
|
31
|
-
const balance = faker.number.bigInt(100_000_000)
|
|
32
30
|
mockGetAccountInfo.mockResolvedValue({
|
|
33
|
-
balance:
|
|
31
|
+
balance: '100000000',
|
|
34
32
|
ownerCount: 0,
|
|
35
33
|
})
|
|
36
34
|
|
|
@@ -41,7 +39,7 @@ describe('getBalance', () => {
|
|
|
41
39
|
expect(mockGetAccountInfo).toHaveBeenCalledTimes(1)
|
|
42
40
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(1)
|
|
43
41
|
expect(mockGetAccountInfo.mock.lastCall[0]).toEqual(address)
|
|
44
|
-
expect(result).toEqual([{ value:
|
|
42
|
+
expect(result).toEqual([{ value: 100000000n, asset: { type: 'native' }, locked: 23000000n }])
|
|
45
43
|
})
|
|
46
44
|
|
|
47
45
|
it('returns from account with empty balance', async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import coinConfig, { XrpCoinConfig } from '../config'
|
|
4
|
+
import coinConfig, { type XrpCoinConfig } from '../config'
|
|
5
5
|
import { getBlock } from './getBlock'
|
|
6
6
|
|
|
7
7
|
let originalGetCoinConfig: () => XrpCoinConfig
|
|
@@ -9,12 +9,10 @@ let originalGetCoinConfig: () => XrpCoinConfig
|
|
|
9
9
|
describe('getBlock', () => {
|
|
10
10
|
beforeAll(() => {
|
|
11
11
|
originalGetCoinConfig = coinConfig.getCoinConfig
|
|
12
|
-
coinConfig.setCoinConfig(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}) as XrpCoinConfig
|
|
17
|
-
)
|
|
12
|
+
coinConfig.setCoinConfig(() => ({
|
|
13
|
+
node: 'https://s.altnet.rippletest.net:51234',
|
|
14
|
+
status: { type: 'active' },
|
|
15
|
+
}))
|
|
18
16
|
})
|
|
19
17
|
|
|
20
18
|
afterAll(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import coinConfig, { XrpCoinConfig } from '../config'
|
|
4
|
+
import coinConfig, { type XrpCoinConfig } from '../config'
|
|
5
5
|
import { getBlock } from './getBlock'
|
|
6
6
|
import { getBlockInfo } from './getBlockInfo'
|
|
7
7
|
|
|
@@ -10,12 +10,10 @@ let originalGetCoinConfig: () => XrpCoinConfig
|
|
|
10
10
|
describe('getBlockInfo', () => {
|
|
11
11
|
beforeAll(() => {
|
|
12
12
|
originalGetCoinConfig = coinConfig.getCoinConfig
|
|
13
|
-
coinConfig.setCoinConfig(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}) as XrpCoinConfig
|
|
18
|
-
)
|
|
13
|
+
coinConfig.setCoinConfig(() => ({
|
|
14
|
+
node: 'https://s.altnet.rippletest.net:51234',
|
|
15
|
+
status: { type: 'active' },
|
|
16
|
+
}))
|
|
19
17
|
})
|
|
20
18
|
|
|
21
19
|
afterAll(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { Operation } from '@ledgerhq/coin-module-framework/api/types'
|
|
4
|
+
import type { Operation } from '@ledgerhq/coin-module-framework/api/types'
|
|
5
5
|
import { decode, encodeForSigning } from 'ripple-binary-codec'
|
|
6
6
|
import { deriveKeypair, generateSeed, sign } from 'ripple-keypairs'
|
|
7
7
|
import { createApi } from '.'
|
|
@@ -313,14 +313,21 @@ describe('Xrp Api (mainnet)', () => {
|
|
|
313
313
|
type: 'IN',
|
|
314
314
|
fees: 0.00001,
|
|
315
315
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
316
|
+
expect(ops).toEqual(
|
|
317
|
+
expect.arrayContaining([
|
|
318
|
+
expect.objectContaining({
|
|
319
|
+
tx: expect.objectContaining({
|
|
320
|
+
hash: inTx.hash,
|
|
321
|
+
fees: BigInt(inTx.fees * 1e6),
|
|
322
|
+
feesPayer: inTx.sender,
|
|
323
|
+
}),
|
|
324
|
+
value: BigInt(inTx.amount * 1e6),
|
|
325
|
+
recipients: expect.arrayContaining([inTx.recipient]),
|
|
326
|
+
senders: expect.arrayContaining([inTx.sender]),
|
|
327
|
+
type: inTx.type,
|
|
328
|
+
}),
|
|
329
|
+
])
|
|
330
|
+
)
|
|
324
331
|
})
|
|
325
332
|
|
|
326
333
|
it('returns OUT operation', async () => {
|
|
@@ -333,14 +340,21 @@ describe('Xrp Api (mainnet)', () => {
|
|
|
333
340
|
type: 'OUT',
|
|
334
341
|
fees: 0.00001,
|
|
335
342
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
343
|
+
expect(ops).toEqual(
|
|
344
|
+
expect.arrayContaining([
|
|
345
|
+
expect.objectContaining({
|
|
346
|
+
tx: expect.objectContaining({
|
|
347
|
+
hash: outTx.hash,
|
|
348
|
+
fees: BigInt(outTx.fees * 1e6),
|
|
349
|
+
feesPayer: SENDER,
|
|
350
|
+
}),
|
|
351
|
+
value: BigInt(outTx.amount * 1e6),
|
|
352
|
+
recipients: expect.arrayContaining([outTx.recipient]),
|
|
353
|
+
senders: expect.arrayContaining([outTx.sender]),
|
|
354
|
+
type: outTx.type,
|
|
355
|
+
}),
|
|
356
|
+
])
|
|
357
|
+
)
|
|
344
358
|
})
|
|
345
359
|
|
|
346
360
|
it('returns OUT operation for OfferCreate that trades XRP via DEX', async () => {
|
|
@@ -356,14 +370,20 @@ describe('Xrp Api (mainnet)', () => {
|
|
|
356
370
|
type: 'OUT',
|
|
357
371
|
fees: 0.00001,
|
|
358
372
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
373
|
+
expect(ops).toEqual(
|
|
374
|
+
expect.arrayContaining([
|
|
375
|
+
expect.objectContaining({
|
|
376
|
+
tx: expect.objectContaining({
|
|
377
|
+
hash: outTx.hash,
|
|
378
|
+
fees: BigInt(outTx.fees * 1e6),
|
|
379
|
+
}),
|
|
380
|
+
value: BigInt(outTx.amount * 1e6),
|
|
381
|
+
recipients: [],
|
|
382
|
+
senders: expect.arrayContaining([outTx.sender]),
|
|
383
|
+
type: outTx.type,
|
|
384
|
+
}),
|
|
385
|
+
])
|
|
386
|
+
)
|
|
367
387
|
})
|
|
368
388
|
|
|
369
389
|
it('returns IN operation on account delete', async () => {
|
|
@@ -380,23 +400,26 @@ describe('Xrp Api (mainnet)', () => {
|
|
|
380
400
|
type: 'IN',
|
|
381
401
|
fees: 2.0,
|
|
382
402
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
403
|
+
expect(opsAccountDelete).toEqual(
|
|
404
|
+
expect.arrayContaining([
|
|
405
|
+
expect.objectContaining({
|
|
406
|
+
id: inTx.hash,
|
|
407
|
+
tx: expect.objectContaining({
|
|
408
|
+
hash: inTx.hash,
|
|
409
|
+
fees: BigInt(inTx.fees * 1e6),
|
|
410
|
+
feesPayer: inTx.sender,
|
|
411
|
+
failed: false,
|
|
412
|
+
}),
|
|
413
|
+
type: inTx.type,
|
|
414
|
+
value: BigInt(inTx.amount * 1e6),
|
|
415
|
+
senders: [inTx.sender],
|
|
416
|
+
recipients: [inTx.recipient],
|
|
417
|
+
details: expect.objectContaining({
|
|
418
|
+
xrpTxType: 'AccountDelete',
|
|
419
|
+
}),
|
|
420
|
+
}),
|
|
421
|
+
])
|
|
422
|
+
)
|
|
400
423
|
})
|
|
401
424
|
})
|
|
402
425
|
|
package/src/api/index.test.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { Operation
|
|
4
|
+
import type { Operation } from '@ledgerhq/coin-module-framework/api/types'
|
|
5
|
+
import { createSendTransactionIntentMockData } from '@ledgerhq/coin-module-framework/test-utils'
|
|
6
|
+
import BigNumber from 'bignumber.js'
|
|
5
7
|
import { GetTransactionsOptions } from '../network'
|
|
6
8
|
import { NetworkInfo, XrpMapMemo } from '../types'
|
|
7
9
|
import { RIPPLE_EPOCH } from '../utils'
|
|
@@ -280,7 +282,7 @@ describe('listOperations', () => {
|
|
|
280
282
|
}
|
|
281
283
|
expect(mockGetTransactions).toHaveBeenNthCalledWith(1, 'src', baseOptions)
|
|
282
284
|
expect(token).toEqual(expect.any(String))
|
|
283
|
-
await api.listOperations('src', { minHeight: 0, cursor: token
|
|
285
|
+
await api.listOperations('src', { minHeight: 0, cursor: token, order: 'asc' })
|
|
284
286
|
const optionsWithToken = {
|
|
285
287
|
...baseOptions,
|
|
286
288
|
marker: defaultMarker,
|
|
@@ -415,14 +417,15 @@ describe('Testing craftTransaction function', () => {
|
|
|
415
417
|
|
|
416
418
|
beforeAll(() => {
|
|
417
419
|
mockCraftTransaction.mockImplementation((_address, _transaction, _publicKey) => {
|
|
418
|
-
return Promise.resolve({ xrplTransaction: {}
|
|
420
|
+
return Promise.resolve({ xrplTransaction: {}, serializedTransaction: '' })
|
|
419
421
|
})
|
|
420
422
|
|
|
421
423
|
mockGetSequence.mockImplementation((_address) => Promise.resolve(0n))
|
|
422
424
|
|
|
423
425
|
mockEstimateFees.mockImplementation((_networkInfo) => {
|
|
426
|
+
const networkInfo: NetworkInfo = { family: 'xrp', serverFee: new BigNumber(0) }
|
|
424
427
|
return Promise.resolve({
|
|
425
|
-
networkInfo
|
|
428
|
+
networkInfo,
|
|
426
429
|
fees: DEFAULT_ESTIMATED_FEES,
|
|
427
430
|
})
|
|
428
431
|
})
|
|
@@ -435,10 +438,12 @@ describe('Testing craftTransaction function', () => {
|
|
|
435
438
|
it('should use custom user fees when user provides it for crafting a transaction', async () => {
|
|
436
439
|
const customFees = 99n
|
|
437
440
|
await api.craftTransaction(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
441
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
442
|
+
type: 'Payment',
|
|
443
|
+
sender: 'foo',
|
|
444
|
+
memo: { type: 'map', memos: new Map() },
|
|
445
|
+
}),
|
|
446
|
+
{ value: customFees }
|
|
442
447
|
)
|
|
443
448
|
|
|
444
449
|
expect(mockCraftTransaction).toHaveBeenCalledWith(
|
|
@@ -451,10 +456,13 @@ describe('Testing craftTransaction function', () => {
|
|
|
451
456
|
})
|
|
452
457
|
|
|
453
458
|
it('should use default fees when user does not provide them for crafting a transaction', async () => {
|
|
454
|
-
await api.craftTransaction(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
459
|
+
await api.craftTransaction(
|
|
460
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
461
|
+
type: 'Payment',
|
|
462
|
+
sender: 'foo',
|
|
463
|
+
memo: { type: 'map', memos: new Map() },
|
|
464
|
+
})
|
|
465
|
+
)
|
|
458
466
|
|
|
459
467
|
expect(mockCraftTransaction).toHaveBeenCalledWith(
|
|
460
468
|
expect.any(Object),
|
|
@@ -466,24 +474,26 @@ describe('Testing craftTransaction function', () => {
|
|
|
466
474
|
})
|
|
467
475
|
|
|
468
476
|
it('should pass signing pub key when user provides it for crafting a transaction', async () => {
|
|
469
|
-
await api.craftTransaction(
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
477
|
+
await api.craftTransaction(
|
|
478
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
479
|
+
type: 'Payment',
|
|
480
|
+
sender: 'foo',
|
|
481
|
+
senderPublicKey: 'bar',
|
|
482
|
+
memo: { type: 'map', memos: new Map() },
|
|
483
|
+
})
|
|
484
|
+
)
|
|
474
485
|
|
|
475
486
|
expect(mockCraftTransaction).toHaveBeenCalledWith(expect.any(Object), expect.any(Object), 'bar')
|
|
476
487
|
})
|
|
477
488
|
|
|
478
489
|
it('should pass memos when user provides it for crafting a transaction', async () => {
|
|
479
|
-
await api.craftTransaction(
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
type: 'map',
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
} as SendTransactionIntent<XrpMapMemo>)
|
|
490
|
+
await api.craftTransaction(
|
|
491
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
492
|
+
type: 'Payment',
|
|
493
|
+
sender: 'foo',
|
|
494
|
+
memo: { type: 'map', memos: new Map([['memos', ['testdata']]]) },
|
|
495
|
+
})
|
|
496
|
+
)
|
|
487
497
|
|
|
488
498
|
expect(mockCraftTransaction).toHaveBeenCalledWith(
|
|
489
499
|
expect.any(Object),
|
|
@@ -497,10 +507,13 @@ describe('Testing craftTransaction function', () => {
|
|
|
497
507
|
})
|
|
498
508
|
|
|
499
509
|
it('should not pass memos when user does not provide it for crafting a transaction', async () => {
|
|
500
|
-
await api.craftTransaction(
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
510
|
+
await api.craftTransaction(
|
|
511
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
512
|
+
type: 'Payment',
|
|
513
|
+
sender: 'foo',
|
|
514
|
+
memo: { type: 'map', memos: new Map() },
|
|
515
|
+
})
|
|
516
|
+
)
|
|
504
517
|
|
|
505
518
|
expect(mockCraftTransaction).toHaveBeenCalledWith(
|
|
506
519
|
expect.any(Object),
|
|
@@ -512,14 +525,13 @@ describe('Testing craftTransaction function', () => {
|
|
|
512
525
|
})
|
|
513
526
|
|
|
514
527
|
it('should not pass memos when user provides an empty memo list it for crafting a transaction', async () => {
|
|
515
|
-
await api.craftTransaction(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
type: 'map',
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
} as SendTransactionIntent<XrpMapMemo>)
|
|
528
|
+
await api.craftTransaction(
|
|
529
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
530
|
+
type: 'Payment',
|
|
531
|
+
sender: 'foo',
|
|
532
|
+
memo: { type: 'map', memos: new Map() },
|
|
533
|
+
})
|
|
534
|
+
)
|
|
523
535
|
|
|
524
536
|
expect(mockCraftTransaction).toHaveBeenCalledWith(
|
|
525
537
|
expect.any(Object),
|
|
@@ -531,14 +543,13 @@ describe('Testing craftTransaction function', () => {
|
|
|
531
543
|
})
|
|
532
544
|
|
|
533
545
|
it('should pass destination tag when user provides it for crafting a transaction', async () => {
|
|
534
|
-
await api.craftTransaction(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
type: 'map',
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
} as SendTransactionIntent<XrpMapMemo>)
|
|
546
|
+
await api.craftTransaction(
|
|
547
|
+
createSendTransactionIntentMockData<XrpMapMemo>({
|
|
548
|
+
type: 'Payment',
|
|
549
|
+
sender: 'foo',
|
|
550
|
+
memo: { type: 'map', memos: new Map([['destinationTag', '1337']]) },
|
|
551
|
+
})
|
|
552
|
+
)
|
|
542
553
|
|
|
543
554
|
expect(mockCraftTransaction).toHaveBeenCalledWith(
|
|
544
555
|
expect.any(Object),
|
package/src/api/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
TransactionIntent,
|
|
14
14
|
CraftedTransaction,
|
|
15
15
|
CoinModuleApi,
|
|
16
|
+
notSupported,
|
|
16
17
|
} from '@ledgerhq/coin-module-framework/api/index'
|
|
17
18
|
import { craftTransactionData } from '@ledgerhq/coin-module-framework/logic/craftTransactionData'
|
|
18
19
|
import { isSendTransactionIntent } from '@ledgerhq/coin-module-framework/utils'
|
|
@@ -48,6 +49,7 @@ export function createApi(config: XrpConfig): CoinModuleApi<XrpMapMemo> {
|
|
|
48
49
|
validateIntent,
|
|
49
50
|
getBlock,
|
|
50
51
|
getBlockInfo,
|
|
52
|
+
call: notSupported('call'),
|
|
51
53
|
getNextSequence: getSequence,
|
|
52
54
|
getStakes(_address: string, _cursor?: Cursor): Promise<Page<Stake>> {
|
|
53
55
|
throw new Error('getStakes is not supported')
|
|
@@ -73,13 +75,16 @@ async function craft(
|
|
|
73
75
|
const nextSequenceNumber = Number(await getSequence(transactionIntent.sender))
|
|
74
76
|
const estimatedFees = customFees?.value ?? (await estimateFees()).fees
|
|
75
77
|
|
|
76
|
-
const memosMap =
|
|
78
|
+
const memosMap =
|
|
79
|
+
transactionIntent.memo?.type === 'map'
|
|
80
|
+
? transactionIntent.memo.memos
|
|
81
|
+
: new Map<string, string | string[]>()
|
|
77
82
|
|
|
78
83
|
const destinationTagValue = memosMap.get('destinationTag')
|
|
79
84
|
const destinationTag =
|
|
80
85
|
typeof destinationTagValue === 'string' ? Number(destinationTagValue) : undefined
|
|
81
86
|
|
|
82
|
-
const memoStrings = memosMap.get('memos')
|
|
87
|
+
const memoStrings = memosMap.get('memos')
|
|
83
88
|
|
|
84
89
|
let memoEntries: MemoInput[] | undefined = undefined
|
|
85
90
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: © 2024 LEDGER SAS
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
import type { GetTransactionsOptions } from '../network'
|
|
5
|
+
import type { Marker } from '../network/types'
|
|
4
6
|
import { assert } from 'console'
|
|
5
7
|
import { Operation } from '@ledgerhq/coin-module-framework/api/types'
|
|
6
|
-
import { Marker } from '../network/types'
|
|
7
8
|
import { RIPPLE_EPOCH } from '../utils'
|
|
8
9
|
import { listOperations } from './listOperations'
|
|
9
10
|
|
|
@@ -17,7 +18,8 @@ const mockGetServerInfos = jest.fn().mockResolvedValue({
|
|
|
17
18
|
const mockNetworkGetTransactions = jest.fn()
|
|
18
19
|
jest.mock('../network', () => ({
|
|
19
20
|
getServerInfos: () => mockGetServerInfos(),
|
|
20
|
-
getTransactions: (
|
|
21
|
+
getTransactions: (address: string, options: GetTransactionsOptions | undefined) =>
|
|
22
|
+
mockNetworkGetTransactions(address, options),
|
|
21
23
|
}))
|
|
22
24
|
|
|
23
25
|
describe('listOperations', () => {
|