@ledgerhq/coin-xrp 7.23.4 → 7.24.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/CHANGELOG.md +12 -0
- package/lib/api/getBlock.d.ts.map +1 -1
- package/lib/api/getBlock.js +17 -5
- package/lib/api/getBlock.js.map +1 -1
- package/lib/api/listOperations.d.ts.map +1 -1
- package/lib/api/listOperations.js +120 -44
- package/lib/api/listOperations.js.map +1 -1
- package/lib/api/validateIntent.d.ts.map +1 -1
- package/lib/api/validateIntent.js +12 -7
- package/lib/api/validateIntent.js.map +1 -1
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +2 -0
- package/lib/network/index.js.map +1 -1
- package/lib/types/model.d.ts +1 -0
- package/lib/types/model.d.ts.map +1 -1
- package/lib/utils/errors.d.ts +3 -0
- package/lib/utils/errors.d.ts.map +1 -1
- package/lib/utils/errors.js +2 -1
- package/lib/utils/errors.js.map +1 -1
- package/lib-es/api/getBlock.d.ts.map +1 -1
- package/lib-es/api/getBlock.js +17 -5
- package/lib-es/api/getBlock.js.map +1 -1
- package/lib-es/api/listOperations.d.ts.map +1 -1
- package/lib-es/api/listOperations.js +120 -44
- package/lib-es/api/listOperations.js.map +1 -1
- package/lib-es/api/validateIntent.d.ts.map +1 -1
- package/lib-es/api/validateIntent.js +14 -9
- package/lib-es/api/validateIntent.js.map +1 -1
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +2 -0
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/types/model.d.ts +1 -0
- package/lib-es/types/model.d.ts.map +1 -1
- package/lib-es/utils/errors.d.ts +3 -0
- package/lib-es/utils/errors.d.ts.map +1 -1
- package/lib-es/utils/errors.js +1 -0
- package/lib-es/utils/errors.js.map +1 -1
- package/package.json +1 -1
- package/src/api/getBlock.test.ts +102 -1
- package/src/api/getBlock.ts +23 -5
- package/src/api/index.integ.test.ts +6 -6
- package/src/api/index.test.ts +21 -5
- package/src/api/listOperations.test.ts +211 -3
- package/src/api/listOperations.ts +153 -49
- package/src/api/validateIntent.test.ts +105 -11
- package/src/api/validateIntent.ts +14 -9
- package/src/network/index.test.ts +32 -0
- package/src/network/index.ts +2 -0
- package/src/types/model.ts +1 -0
- package/src/utils/errors.ts +2 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { TransactionIntent } from '@ledgerhq/coin-module-framework/api/types'
|
|
5
5
|
import { XrpMapMemo } from '../types'
|
|
6
|
-
import { XrpInvalidMemoError } from '../utils/errors'
|
|
6
|
+
import { XrpDestinationTagRequired, XrpInvalidMemoError } from '../utils/errors'
|
|
7
7
|
import * as logicValidateMemo from '../utils/validateMemo'
|
|
8
8
|
import { validateIntent } from './validateIntent'
|
|
9
9
|
|
|
@@ -12,6 +12,17 @@ const mockGetBalance = jest.fn()
|
|
|
12
12
|
const mockGetServerInfos = jest.fn()
|
|
13
13
|
|
|
14
14
|
const RECIPIENT_NEW = 'rDKsbvy9uaNpPtvVFraJyNGfjvTw8xivgK'
|
|
15
|
+
const RECIPIENT_TAG_REQUIRED = 'rwnYLUsoBQX3ECa1A5bSKLdbPoHKnqf63J'
|
|
16
|
+
|
|
17
|
+
const mockGetAccountInfo = jest.fn((addr: string) =>
|
|
18
|
+
Promise.resolve({
|
|
19
|
+
isNewAccount: addr === RECIPIENT_NEW,
|
|
20
|
+
balance: '0',
|
|
21
|
+
ownerCount: 0,
|
|
22
|
+
sequence: 0,
|
|
23
|
+
requireDestinationTag: addr === RECIPIENT_TAG_REQUIRED,
|
|
24
|
+
})
|
|
25
|
+
)
|
|
15
26
|
|
|
16
27
|
jest.mock('./getBalance', () => ({
|
|
17
28
|
getBalance: () => mockGetBalance(),
|
|
@@ -19,16 +30,7 @@ jest.mock('./getBalance', () => ({
|
|
|
19
30
|
|
|
20
31
|
jest.mock('../network', () => ({
|
|
21
32
|
getServerInfos: () => mockGetServerInfos(),
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
jest.mock('../utils', () => ({
|
|
25
|
-
...jest.requireActual('../utils'),
|
|
26
|
-
recipientIsNew: jest.fn((addr: string) => {
|
|
27
|
-
if (addr === RECIPIENT_NEW) {
|
|
28
|
-
return Promise.resolve(true)
|
|
29
|
-
}
|
|
30
|
-
return Promise.resolve(false)
|
|
31
|
-
}),
|
|
33
|
+
getAccountInfo: (addr: string) => mockGetAccountInfo(addr),
|
|
32
34
|
}))
|
|
33
35
|
|
|
34
36
|
jest.mock('../utils/validateMemo')
|
|
@@ -418,4 +420,96 @@ describe('validateIntent', () => {
|
|
|
418
420
|
|
|
419
421
|
expect(spiedValidateMemo).toHaveBeenCalledWith(memos.get('destinationTag'))
|
|
420
422
|
})
|
|
423
|
+
|
|
424
|
+
it('errors when destinationTag is not a single string value', async () => {
|
|
425
|
+
mockGetServerInfos.mockResolvedValue({
|
|
426
|
+
info: {
|
|
427
|
+
validated_ledger: {
|
|
428
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
const memos = new Map<string, string | string[]>()
|
|
434
|
+
memos.set('destinationTag', ['12345', '67890'])
|
|
435
|
+
|
|
436
|
+
const status = await validateIntent(
|
|
437
|
+
{
|
|
438
|
+
intentType: 'transaction',
|
|
439
|
+
sender: SENDER,
|
|
440
|
+
asset: { unit: { code: 'XRP', magnitude: 6 } },
|
|
441
|
+
amount: 1_000_000n,
|
|
442
|
+
recipient: RECIPIENT,
|
|
443
|
+
memo: {
|
|
444
|
+
type: '',
|
|
445
|
+
memos,
|
|
446
|
+
},
|
|
447
|
+
} as TransactionIntent<XrpMapMemo>,
|
|
448
|
+
[{ value: 50_000_000n, asset: { type: 'native' }, locked: 0n }],
|
|
449
|
+
{ value: 10_000n } // fees
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
expect(status.errors.transaction).toBeInstanceOf(XrpInvalidMemoError)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('errors when recipient requires a destination tag and none is provided', async () => {
|
|
456
|
+
mockGetServerInfos.mockResolvedValue({
|
|
457
|
+
info: {
|
|
458
|
+
validated_ledger: {
|
|
459
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const status = await validateIntent(
|
|
465
|
+
{
|
|
466
|
+
intentType: 'transaction',
|
|
467
|
+
sender: SENDER,
|
|
468
|
+
asset: { unit: { code: 'XRP', magnitude: 6 } },
|
|
469
|
+
amount: 20_000_000n,
|
|
470
|
+
recipient: RECIPIENT_TAG_REQUIRED,
|
|
471
|
+
memo: {
|
|
472
|
+
type: '',
|
|
473
|
+
memos: new Map<string, string | string[]>(),
|
|
474
|
+
},
|
|
475
|
+
} as TransactionIntent<XrpMapMemo>,
|
|
476
|
+
[{ value: 50_000_000n, asset: { type: 'native' }, locked: 0n }],
|
|
477
|
+
{ value: 10_000n } // fees
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
expect(status.errors.transaction).toBeInstanceOf(XrpDestinationTagRequired)
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('does not error when recipient requires a destination tag and one is provided', async () => {
|
|
484
|
+
mockGetServerInfos.mockResolvedValue({
|
|
485
|
+
info: {
|
|
486
|
+
validated_ledger: {
|
|
487
|
+
reserve_base_xrp: reserveBase / 1_000_000n,
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
spiedValidateMemo.mockReturnValueOnce(true)
|
|
493
|
+
|
|
494
|
+
const memos = new Map<string, string | string[]>()
|
|
495
|
+
memos.set('destinationTag', '12345')
|
|
496
|
+
|
|
497
|
+
const status = await validateIntent(
|
|
498
|
+
{
|
|
499
|
+
intentType: 'transaction',
|
|
500
|
+
sender: SENDER,
|
|
501
|
+
asset: { unit: { code: 'XRP', magnitude: 6 } },
|
|
502
|
+
amount: 20_000_000n,
|
|
503
|
+
recipient: RECIPIENT_TAG_REQUIRED,
|
|
504
|
+
memo: {
|
|
505
|
+
type: '',
|
|
506
|
+
memos,
|
|
507
|
+
},
|
|
508
|
+
} as TransactionIntent<XrpMapMemo>,
|
|
509
|
+
[{ value: 50_000_000n, asset: { type: 'native' }, locked: 0n }],
|
|
510
|
+
{ value: 10_000n } // fees
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
expect(status.errors.transaction).not.toBeDefined()
|
|
514
|
+
})
|
|
421
515
|
})
|
|
@@ -19,11 +19,10 @@ import {
|
|
|
19
19
|
RecipientRequired,
|
|
20
20
|
} from '@ledgerhq/errors'
|
|
21
21
|
import { isValidClassicAddress } from 'ripple-address-codec'
|
|
22
|
-
import { getServerInfos } from '../network'
|
|
22
|
+
import { getAccountInfo, getServerInfos } from '../network'
|
|
23
23
|
import { XrpMapMemo } from '../types'
|
|
24
|
-
import { recipientIsNew } from '../utils'
|
|
25
24
|
import { parseAPIValue } from '../utils/common'
|
|
26
|
-
import { XrpInvalidMemoError } from '../utils/errors'
|
|
25
|
+
import { XrpDestinationTagRequired, XrpInvalidMemoError } from '../utils/errors'
|
|
27
26
|
import { validateMemo } from '../utils/validateMemo'
|
|
28
27
|
|
|
29
28
|
export const validateIntent = async (
|
|
@@ -41,6 +40,11 @@ export const validateIntent = async (
|
|
|
41
40
|
const totalSpent = transactionIntent.amount + estimatedFees
|
|
42
41
|
const amount = transactionIntent.amount
|
|
43
42
|
|
|
43
|
+
const recipientInfo =
|
|
44
|
+
transactionIntent.recipient && isValidClassicAddress(transactionIntent.recipient)
|
|
45
|
+
? await getAccountInfo(transactionIntent.recipient)
|
|
46
|
+
: undefined
|
|
47
|
+
|
|
44
48
|
if (amount > 0 && estimatedFees * 10n > amount) {
|
|
45
49
|
warnings.feeTooHigh = new FeeTooHigh()
|
|
46
50
|
}
|
|
@@ -63,8 +67,7 @@ export const validateIntent = async (
|
|
|
63
67
|
: 'Unknown unit',
|
|
64
68
|
})
|
|
65
69
|
} else if (
|
|
66
|
-
|
|
67
|
-
(await recipientIsNew(transactionIntent.recipient)) &&
|
|
70
|
+
recipientInfo?.isNewAccount &&
|
|
68
71
|
transactionIntent.amount < BigInt(reserveBaseXRP.toString())
|
|
69
72
|
) {
|
|
70
73
|
errors.amount = new NotEnoughBalanceBecauseDestinationNotCreated('', {
|
|
@@ -92,11 +95,13 @@ export const validateIntent = async (
|
|
|
92
95
|
errors.amount = new AmountRequired()
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
const destinationTag = transactionIntent.memo?.memos
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (destinationTag &&
|
|
98
|
+
const destinationTag = transactionIntent.memo?.memos?.get('destinationTag')
|
|
99
|
+
if (destinationTag !== undefined && typeof destinationTag !== 'string') {
|
|
100
|
+
errors.transaction = new XrpInvalidMemoError()
|
|
101
|
+
} else if (destinationTag && !validateMemo(destinationTag)) {
|
|
99
102
|
errors.transaction = new XrpInvalidMemoError()
|
|
103
|
+
} else if (!errors.recipient && !destinationTag && recipientInfo?.requireDestinationTag) {
|
|
104
|
+
errors.transaction = new XrpDestinationTagRequired()
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
return {
|
|
@@ -49,6 +49,7 @@ describe('getAccountInfo', () => {
|
|
|
49
49
|
balance: '0',
|
|
50
50
|
ownerCount: 0,
|
|
51
51
|
sequence: 0,
|
|
52
|
+
requireDestinationTag: false,
|
|
52
53
|
})
|
|
53
54
|
})
|
|
54
55
|
|
|
@@ -86,9 +87,40 @@ describe('getAccountInfo', () => {
|
|
|
86
87
|
balance: '999441667919804',
|
|
87
88
|
ownerCount: 0,
|
|
88
89
|
sequence: 153743,
|
|
90
|
+
requireDestinationTag: false,
|
|
89
91
|
})
|
|
90
92
|
})
|
|
91
93
|
|
|
94
|
+
it('returns requireDestinationTag true when the account_flags flag is set', async () => {
|
|
95
|
+
const address = 'rwnYLUsoBQX3ECa1A5bSKLdbPoHKnqf63J'
|
|
96
|
+
;(network as jest.Mock).mockResolvedValue({
|
|
97
|
+
data: {
|
|
98
|
+
result: {
|
|
99
|
+
account_data: {
|
|
100
|
+
Account: address,
|
|
101
|
+
Balance: '100000000',
|
|
102
|
+
Flags: 131072,
|
|
103
|
+
LedgerEntryType: 'AccountRoot',
|
|
104
|
+
OwnerCount: 0,
|
|
105
|
+
Sequence: 100,
|
|
106
|
+
index: 'BC0DAE09C0BFBC4A49AA94B849266588BFD6E1F554B184B5788AC55D6E07EB95',
|
|
107
|
+
},
|
|
108
|
+
account_flags: {
|
|
109
|
+
requireDestinationTag: true,
|
|
110
|
+
},
|
|
111
|
+
ledger_hash: '93E952B2770233B0ABFBFBBFBC3E2E2159DCABD07FEB5F4C49174027935D9FBB',
|
|
112
|
+
ledger_index: 1908009,
|
|
113
|
+
status: 'success',
|
|
114
|
+
validated: true,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const result = await getAccountInfo(address)
|
|
120
|
+
|
|
121
|
+
expect(result.requireDestinationTag).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
92
124
|
it('throws an error when backend returns any other error', async () => {
|
|
93
125
|
// Given
|
|
94
126
|
const invalidAddress = 'rNCgVpHinUDjXP2vHDFDMjm7ssBwpveHyaa'
|
package/src/network/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ export const getAccountInfo = async (
|
|
|
61
61
|
balance: '0',
|
|
62
62
|
ownerCount: 0,
|
|
63
63
|
sequence: 0,
|
|
64
|
+
requireDestinationTag: false,
|
|
64
65
|
}
|
|
65
66
|
} else {
|
|
66
67
|
return {
|
|
@@ -68,6 +69,7 @@ export const getAccountInfo = async (
|
|
|
68
69
|
balance: result.account_data.Balance,
|
|
69
70
|
ownerCount: result.account_data.OwnerCount,
|
|
70
71
|
sequence: result.account_data.Sequence,
|
|
72
|
+
requireDestinationTag: result.account_flags?.requireDestinationTag ?? false,
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
}
|
package/src/types/model.ts
CHANGED