@ledgerhq/coin-stellar 6.26.0 → 6.26.1
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/.oxlintrc.json +7 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +194 -0
- package/knip.json +12 -0
- package/lib/api/index.d.ts +5 -5
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +12 -10
- package/lib/api/index.js.map +1 -1
- package/lib/config.d.ts +1 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +2 -0
- package/lib/config.js.map +1 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +3 -1
- package/lib/constants.js.map +1 -1
- package/lib/logic/broadcast.d.ts.map +1 -1
- package/lib/logic/broadcast.js +2 -0
- package/lib/logic/broadcast.js.map +1 -1
- package/lib/logic/combine.d.ts +1 -1
- package/lib/logic/combine.d.ts.map +1 -1
- package/lib/logic/combine.js +2 -0
- package/lib/logic/combine.js.map +1 -1
- package/lib/logic/common.d.ts +1 -1
- package/lib/logic/common.d.ts.map +1 -1
- package/lib/logic/common.js +4 -2
- package/lib/logic/common.js.map +1 -1
- package/lib/logic/craftTransaction.d.ts +1 -1
- package/lib/logic/craftTransaction.d.ts.map +1 -1
- package/lib/logic/craftTransaction.js +10 -8
- package/lib/logic/craftTransaction.js.map +1 -1
- package/lib/logic/estimateFees.d.ts.map +1 -1
- package/lib/logic/estimateFees.js +2 -0
- package/lib/logic/estimateFees.js.map +1 -1
- package/lib/logic/getBalance.d.ts +1 -1
- package/lib/logic/getBalance.d.ts.map +1 -1
- package/lib/logic/getBalance.js +4 -2
- package/lib/logic/getBalance.js.map +1 -1
- package/lib/logic/getBlock.d.ts +1 -1
- package/lib/logic/getBlock.d.ts.map +1 -1
- package/lib/logic/getBlock.js +39 -34
- package/lib/logic/getBlock.js.map +1 -1
- package/lib/logic/getBlockInfo.d.ts +1 -1
- package/lib/logic/getBlockInfo.d.ts.map +1 -1
- package/lib/logic/getBlockInfo.js +2 -0
- package/lib/logic/getBlockInfo.js.map +1 -1
- package/lib/logic/index.d.ts +12 -12
- package/lib/logic/index.d.ts.map +1 -1
- package/lib/logic/index.js +2 -0
- package/lib/logic/index.js.map +1 -1
- package/lib/logic/lastBlock.d.ts +1 -1
- package/lib/logic/lastBlock.d.ts.map +1 -1
- package/lib/logic/lastBlock.js +2 -0
- package/lib/logic/lastBlock.js.map +1 -1
- package/lib/logic/listOperations.d.ts +1 -1
- package/lib/logic/listOperations.d.ts.map +1 -1
- package/lib/logic/listOperations.js +6 -4
- package/lib/logic/listOperations.js.map +1 -1
- package/lib/logic/sdkWrapper.d.ts +1 -1
- package/lib/logic/sdkWrapper.d.ts.map +1 -1
- package/lib/logic/sdkWrapper.js +2 -0
- package/lib/logic/sdkWrapper.js.map +1 -1
- package/lib/logic/utils.d.ts.map +1 -1
- package/lib/logic/utils.js +4 -2
- package/lib/logic/utils.js.map +1 -1
- package/lib/logic/validateAddress.d.ts +1 -1
- package/lib/logic/validateAddress.d.ts.map +1 -1
- package/lib/logic/validateAddress.js +2 -0
- package/lib/logic/validateAddress.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 +21 -19
- package/lib/logic/validateIntent.js.map +1 -1
- package/lib/logic/validateMemo.d.ts +1 -1
- package/lib/logic/validateMemo.d.ts.map +1 -1
- package/lib/logic/validateMemo.js +7 -5
- package/lib/logic/validateMemo.js.map +1 -1
- package/lib/network/horizon.d.ts +6 -7
- package/lib/network/horizon.d.ts.map +1 -1
- package/lib/network/horizon.js +71 -68
- package/lib/network/horizon.js.map +1 -1
- package/lib/network/horizonLedgerErrors.d.ts.map +1 -1
- package/lib/network/horizonLedgerErrors.js +5 -3
- package/lib/network/horizonLedgerErrors.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 +2 -0
- package/lib/network/index.js.map +1 -1
- package/lib/network/serialization.d.ts +6 -4
- package/lib/network/serialization.d.ts.map +1 -1
- package/lib/network/serialization.js +95 -91
- package/lib/network/serialization.js.map +1 -1
- package/lib/polyfill.d.ts.map +1 -1
- package/lib/polyfill.js +6 -4
- package/lib/polyfill.js.map +1 -1
- package/lib/supportedFeatures.d.ts +1 -1
- package/lib/supportedFeatures.d.ts.map +1 -1
- package/lib/supportedFeatures.js +3 -1
- package/lib/supportedFeatures.js.map +1 -1
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/errors.js +14 -12
- package/lib/types/errors.js.map +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/model.d.ts +6 -6
- package/lib/types/model.d.ts.map +1 -1
- package/lib/types/model.js +7 -5
- package/lib/types/model.js.map +1 -1
- package/lib-es/api/index.d.ts +5 -5
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +20 -18
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/config.d.ts +1 -1
- package/lib-es/config.d.ts.map +1 -1
- package/lib-es/config.js +3 -1
- package/lib-es/config.js.map +1 -1
- package/lib-es/constants.d.ts.map +1 -1
- package/lib-es/constants.js +3 -1
- package/lib-es/constants.js.map +1 -1
- package/lib-es/logic/broadcast.d.ts.map +1 -1
- package/lib-es/logic/broadcast.js +3 -1
- package/lib-es/logic/broadcast.js.map +1 -1
- package/lib-es/logic/combine.d.ts +1 -1
- package/lib-es/logic/combine.d.ts.map +1 -1
- package/lib-es/logic/combine.js +3 -1
- package/lib-es/logic/combine.js.map +1 -1
- package/lib-es/logic/common.d.ts +1 -1
- package/lib-es/logic/common.d.ts.map +1 -1
- package/lib-es/logic/common.js +5 -3
- package/lib-es/logic/common.js.map +1 -1
- package/lib-es/logic/craftTransaction.d.ts +1 -1
- package/lib-es/logic/craftTransaction.d.ts.map +1 -1
- package/lib-es/logic/craftTransaction.js +15 -13
- package/lib-es/logic/craftTransaction.js.map +1 -1
- package/lib-es/logic/estimateFees.d.ts.map +1 -1
- package/lib-es/logic/estimateFees.js +3 -1
- package/lib-es/logic/estimateFees.js.map +1 -1
- package/lib-es/logic/getBalance.d.ts +1 -1
- package/lib-es/logic/getBalance.d.ts.map +1 -1
- package/lib-es/logic/getBalance.js +5 -3
- package/lib-es/logic/getBalance.js.map +1 -1
- package/lib-es/logic/getBlock.d.ts +1 -1
- package/lib-es/logic/getBlock.d.ts.map +1 -1
- package/lib-es/logic/getBlock.js +42 -37
- package/lib-es/logic/getBlock.js.map +1 -1
- package/lib-es/logic/getBlockInfo.d.ts +1 -1
- package/lib-es/logic/getBlockInfo.d.ts.map +1 -1
- package/lib-es/logic/getBlockInfo.js +3 -1
- package/lib-es/logic/getBlockInfo.js.map +1 -1
- package/lib-es/logic/index.d.ts +12 -12
- package/lib-es/logic/index.d.ts.map +1 -1
- package/lib-es/logic/index.js +14 -12
- package/lib-es/logic/index.js.map +1 -1
- package/lib-es/logic/lastBlock.d.ts +1 -1
- package/lib-es/logic/lastBlock.d.ts.map +1 -1
- package/lib-es/logic/lastBlock.js +3 -1
- package/lib-es/logic/lastBlock.js.map +1 -1
- package/lib-es/logic/listOperations.d.ts +1 -1
- package/lib-es/logic/listOperations.d.ts.map +1 -1
- package/lib-es/logic/listOperations.js +7 -5
- package/lib-es/logic/listOperations.js.map +1 -1
- package/lib-es/logic/sdkWrapper.d.ts +1 -1
- package/lib-es/logic/sdkWrapper.d.ts.map +1 -1
- package/lib-es/logic/sdkWrapper.js +4 -2
- package/lib-es/logic/sdkWrapper.js.map +1 -1
- package/lib-es/logic/utils.d.ts.map +1 -1
- package/lib-es/logic/utils.js +6 -4
- package/lib-es/logic/utils.js.map +1 -1
- package/lib-es/logic/validateAddress.d.ts +1 -1
- package/lib-es/logic/validateAddress.d.ts.map +1 -1
- package/lib-es/logic/validateAddress.js +3 -1
- package/lib-es/logic/validateAddress.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 +30 -28
- package/lib-es/logic/validateIntent.js.map +1 -1
- package/lib-es/logic/validateMemo.d.ts +1 -1
- package/lib-es/logic/validateMemo.d.ts.map +1 -1
- package/lib-es/logic/validateMemo.js +8 -6
- package/lib-es/logic/validateMemo.js.map +1 -1
- package/lib-es/network/horizon.d.ts +6 -7
- package/lib-es/network/horizon.d.ts.map +1 -1
- package/lib-es/network/horizon.js +80 -77
- package/lib-es/network/horizon.js.map +1 -1
- package/lib-es/network/horizonLedgerErrors.d.ts.map +1 -1
- package/lib-es/network/horizonLedgerErrors.js +7 -5
- package/lib-es/network/horizonLedgerErrors.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 -1
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/serialization.d.ts +6 -4
- package/lib-es/network/serialization.d.ts.map +1 -1
- package/lib-es/network/serialization.js +97 -95
- package/lib-es/network/serialization.js.map +1 -1
- package/lib-es/polyfill.d.ts.map +1 -1
- package/lib-es/polyfill.js +7 -5
- package/lib-es/polyfill.js.map +1 -1
- package/lib-es/supportedFeatures.d.ts +1 -1
- package/lib-es/supportedFeatures.d.ts.map +1 -1
- package/lib-es/supportedFeatures.js +3 -1
- package/lib-es/supportedFeatures.js.map +1 -1
- package/lib-es/types/errors.d.ts.map +1 -1
- package/lib-es/types/errors.js +15 -13
- package/lib-es/types/errors.js.map +1 -1
- package/lib-es/types/index.d.ts +2 -2
- package/lib-es/types/index.d.ts.map +1 -1
- package/lib-es/types/index.js +4 -2
- package/lib-es/types/index.js.map +1 -1
- package/lib-es/types/model.d.ts +6 -6
- package/lib-es/types/model.d.ts.map +1 -1
- package/lib-es/types/model.js +7 -5
- package/lib-es/types/model.js.map +1 -1
- package/package.json +13 -16
- package/src/api/index.integ.test.ts +165 -162
- package/src/api/index.test.ts +106 -103
- package/src/api/index.ts +49 -46
- package/src/api/index_error.test.ts +48 -25
- package/src/config.ts +15 -12
- package/src/constants.ts +4 -1
- package/src/globals.d.ts +7 -5
- package/src/logic/broadcast.integ.test.ts +22 -19
- package/src/logic/broadcast.ts +5 -2
- package/src/logic/combine.test.ts +18 -15
- package/src/logic/combine.ts +8 -5
- package/src/logic/common.ts +9 -6
- package/src/logic/craftTransaction.test.ts +76 -75
- package/src/logic/craftTransaction.ts +46 -43
- package/src/logic/estimateFees.ts +6 -3
- package/src/logic/getBalance.test.ts +31 -28
- package/src/logic/getBalance.ts +13 -10
- package/src/logic/getBlock.integ.test.ts +40 -39
- package/src/logic/getBlock.test.ts +462 -415
- package/src/logic/getBlock.ts +142 -127
- package/src/logic/getBlockInfo.test.ts +72 -69
- package/src/logic/getBlockInfo.ts +15 -12
- package/src/logic/index.ts +15 -12
- package/src/logic/lastBlock.ts +6 -3
- package/src/logic/listOperations.ts +14 -11
- package/src/logic/listOperations.unit.test.ts +212 -209
- package/src/logic/networkWrappers.test.ts +32 -29
- package/src/logic/sdkWrapper.ts +19 -16
- package/src/logic/utils.ts +13 -10
- package/src/logic/validateAddress.test.ts +21 -18
- package/src/logic/validateAddress.ts +7 -4
- package/src/logic/validateIntent.test.ts +62 -59
- package/src/logic/validateIntent.ts +87 -84
- package/src/logic/validateMemo.test.ts +50 -47
- package/src/logic/validateMemo.ts +16 -13
- package/src/network/broadcastTransaction.test.ts +60 -60
- package/src/network/fetchLedgerOperations.unit.test.ts +155 -0
- package/src/network/horizon.integ.test.ts +45 -42
- package/src/network/horizon.test.ts +17 -14
- package/src/network/horizon.ts +279 -281
- package/src/network/horizon.unit.test.ts +238 -237
- package/src/network/horizonLedgerErrors.test.ts +81 -78
- package/src/network/horizonLedgerErrors.ts +22 -19
- package/src/network/index.ts +4 -1
- package/src/network/registerHorizonInterceptors.test.ts +163 -160
- package/src/network/serialization.ts +232 -227
- package/src/network/serialization.unit.test.ts +801 -798
- package/src/polyfill.ts +18 -15
- package/src/supportedFeatures.ts +6 -3
- package/src/types/errors.ts +33 -32
- package/src/types/index.ts +5 -2
- package/src/types/model.ts +55 -52
- package/tsconfig.json +1 -1
- package/.unimportedrc.json +0 -17
- package/LICENSE.txt +0 -21
- package/src/network/fetchLedgerOperations.nock.unit.test.ts +0 -156
|
@@ -1,43 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { BalanceAsset, RawOperation } from '../types'
|
|
5
|
+
import type { Horizon } from '@stellar/stellar-sdk'
|
|
3
6
|
import {
|
|
4
7
|
BASE_RESERVE,
|
|
5
8
|
MIN_BALANCE,
|
|
6
9
|
getReservedBalance,
|
|
7
10
|
rawOperationsToOperations,
|
|
8
|
-
} from
|
|
11
|
+
} from './serialization'
|
|
9
12
|
|
|
10
|
-
const ADDR =
|
|
11
|
-
const OTHER_ADDR =
|
|
12
|
-
const THIRD_ADDR =
|
|
13
|
-
const ACCOUNT_ID =
|
|
14
|
-
const TX_HASH =
|
|
15
|
-
const BLOCK_HASH =
|
|
16
|
-
const BLOCK_TIME =
|
|
17
|
-
const CREATED_AT =
|
|
13
|
+
const ADDR = 'GBADDR'
|
|
14
|
+
const OTHER_ADDR = 'GBOTHER'
|
|
15
|
+
const THIRD_ADDR = 'GBTHIRD'
|
|
16
|
+
const ACCOUNT_ID = 'stellar:GBADDR'
|
|
17
|
+
const TX_HASH = 'txh'
|
|
18
|
+
const BLOCK_HASH = 'block-hash'
|
|
19
|
+
const BLOCK_TIME = '2025-06-01T12:00:00Z'
|
|
20
|
+
const CREATED_AT = '2025-06-01T12:00:00Z'
|
|
18
21
|
|
|
19
22
|
type TxOverrides = Partial<{
|
|
20
|
-
ledger_attr: number
|
|
21
|
-
fee_charged: string
|
|
22
|
-
fee_account: string
|
|
23
|
-
source_account_sequence: string
|
|
24
|
-
memo_type: string
|
|
25
|
-
memo: string | undefined
|
|
26
|
-
ledger_hash: string
|
|
27
|
-
ledger_closed_at: string
|
|
28
|
-
}
|
|
23
|
+
ledger_attr: number
|
|
24
|
+
fee_charged: string
|
|
25
|
+
fee_account: string
|
|
26
|
+
source_account_sequence: string
|
|
27
|
+
memo_type: string
|
|
28
|
+
memo: string | undefined
|
|
29
|
+
ledger_hash: string
|
|
30
|
+
ledger_closed_at: string
|
|
31
|
+
}>
|
|
29
32
|
|
|
30
33
|
function makeTx(overrides: TxOverrides = {}) {
|
|
31
34
|
const {
|
|
32
35
|
ledger_attr = 100,
|
|
33
|
-
fee_charged =
|
|
36
|
+
fee_charged = '100',
|
|
34
37
|
fee_account,
|
|
35
|
-
source_account_sequence =
|
|
36
|
-
memo_type =
|
|
38
|
+
source_account_sequence = '42',
|
|
39
|
+
memo_type = 'none',
|
|
37
40
|
memo,
|
|
38
41
|
ledger_hash = BLOCK_HASH,
|
|
39
42
|
ledger_closed_at = BLOCK_TIME,
|
|
40
|
-
} = overrides
|
|
43
|
+
} = overrides
|
|
41
44
|
|
|
42
45
|
return {
|
|
43
46
|
ledger_attr,
|
|
@@ -50,1147 +53,1147 @@ function makeTx(overrides: TxOverrides = {}) {
|
|
|
50
53
|
hash: ledger_hash,
|
|
51
54
|
closed_at: ledger_closed_at,
|
|
52
55
|
}),
|
|
53
|
-
}
|
|
56
|
+
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
// `RawOperation.type` is constrained to a narrow union of Horizon operation type
|
|
57
60
|
// literals which makes building fixtures with plain strings impractical, so we
|
|
58
61
|
// accept any string and cast at the construction site.
|
|
59
|
-
type RawOpInput = { type: string } & Record<string, unknown
|
|
62
|
+
type RawOpInput = { type: string } & Record<string, unknown>
|
|
60
63
|
|
|
61
64
|
function makeOp(rawOpOverrides: RawOpInput, txOverrides: TxOverrides = {}): RawOperation {
|
|
62
65
|
return {
|
|
63
|
-
id:
|
|
64
|
-
paging_token:
|
|
66
|
+
id: 'op-1',
|
|
67
|
+
paging_token: 'pt-1',
|
|
65
68
|
source_account: ADDR,
|
|
66
69
|
transaction_hash: TX_HASH,
|
|
67
70
|
transaction_successful: true,
|
|
68
71
|
created_at: CREATED_AT,
|
|
69
72
|
transaction: jest.fn().mockResolvedValue(makeTx(txOverrides)),
|
|
70
73
|
...rawOpOverrides,
|
|
71
|
-
} as unknown as RawOperation
|
|
74
|
+
} as unknown as RawOperation
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
describe(
|
|
75
|
-
it(
|
|
76
|
-
expect(BASE_RESERVE).toBe(0.5)
|
|
77
|
-
})
|
|
77
|
+
describe('constants', () => {
|
|
78
|
+
it('exports BASE_RESERVE equal to 0.5 (stellar protocol base reserve in XLM)', () => {
|
|
79
|
+
expect(BASE_RESERVE).toBe(0.5)
|
|
80
|
+
})
|
|
78
81
|
|
|
79
|
-
it(
|
|
80
|
-
expect(MIN_BALANCE).toBe(1)
|
|
81
|
-
})
|
|
82
|
-
})
|
|
82
|
+
it('exports MIN_BALANCE equal to 1 (minimum stellar account balance in XLM)', () => {
|
|
83
|
+
expect(MIN_BALANCE).toBe(1)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
83
86
|
|
|
84
|
-
describe(
|
|
87
|
+
describe('getReservedBalance', () => {
|
|
85
88
|
type AccountInput = Partial<{
|
|
86
|
-
num_sponsoring: string
|
|
87
|
-
num_sponsored: string
|
|
88
|
-
subentry_count: number
|
|
89
|
-
balances: Array<Partial<BalanceAsset
|
|
90
|
-
}
|
|
89
|
+
num_sponsoring: string
|
|
90
|
+
num_sponsored: string
|
|
91
|
+
subentry_count: number
|
|
92
|
+
balances: Array<Partial<BalanceAsset>>
|
|
93
|
+
}>
|
|
91
94
|
|
|
92
95
|
function makeAccount(overrides: AccountInput = {}): Horizon.ServerApi.AccountRecord {
|
|
93
96
|
return {
|
|
94
|
-
num_sponsoring:
|
|
95
|
-
num_sponsored:
|
|
97
|
+
num_sponsoring: '0',
|
|
98
|
+
num_sponsored: '0',
|
|
96
99
|
subentry_count: 0,
|
|
97
|
-
balances: [{ asset_type:
|
|
100
|
+
balances: [{ asset_type: 'native', selling_liabilities: '0' }],
|
|
98
101
|
...overrides,
|
|
99
|
-
} as unknown as Horizon.ServerApi.AccountRecord
|
|
102
|
+
} as unknown as Horizon.ServerApi.AccountRecord
|
|
100
103
|
}
|
|
101
104
|
|
|
102
|
-
it(
|
|
103
|
-
expect(getReservedBalance(makeAccount()).toNumber()).toBe(1)
|
|
104
|
-
})
|
|
105
|
+
it('should return 1 XLM (the minimum reserve) when account has no subentries, sponsors or liabilities', () => {
|
|
106
|
+
expect(getReservedBalance(makeAccount()).toNumber()).toBe(1)
|
|
107
|
+
})
|
|
105
108
|
|
|
106
|
-
it(
|
|
107
|
-
expect(getReservedBalance(makeAccount({ subentry_count: 4 })).toNumber()).toBe(3)
|
|
108
|
-
})
|
|
109
|
+
it('should add 0.5 XLM per subentry', () => {
|
|
110
|
+
expect(getReservedBalance(makeAccount({ subentry_count: 4 })).toNumber()).toBe(3)
|
|
111
|
+
})
|
|
109
112
|
|
|
110
|
-
it(
|
|
111
|
-
expect(getReservedBalance(makeAccount({ num_sponsoring:
|
|
112
|
-
})
|
|
113
|
+
it('should add 0.5 XLM per sponsoring entry', () => {
|
|
114
|
+
expect(getReservedBalance(makeAccount({ num_sponsoring: '2' })).toNumber()).toBe(2)
|
|
115
|
+
})
|
|
113
116
|
|
|
114
|
-
it(
|
|
117
|
+
it('should subtract 0.5 XLM per sponsored entry', () => {
|
|
115
118
|
expect(
|
|
116
|
-
getReservedBalance(makeAccount({ num_sponsoring:
|
|
117
|
-
).toBe(2)
|
|
118
|
-
})
|
|
119
|
+
getReservedBalance(makeAccount({ num_sponsoring: '4', num_sponsored: '2' })).toNumber()
|
|
120
|
+
).toBe(2)
|
|
121
|
+
})
|
|
119
122
|
|
|
120
|
-
it(
|
|
123
|
+
it('should add native selling_liabilities on top of the entry reserve', () => {
|
|
121
124
|
expect(
|
|
122
125
|
getReservedBalance(
|
|
123
|
-
makeAccount({ balances: [{ asset_type:
|
|
124
|
-
).toNumber()
|
|
125
|
-
).toBe(11.5)
|
|
126
|
-
})
|
|
126
|
+
makeAccount({ balances: [{ asset_type: 'native', selling_liabilities: '10.5' }] })
|
|
127
|
+
).toNumber()
|
|
128
|
+
).toBe(11.5)
|
|
129
|
+
})
|
|
127
130
|
|
|
128
|
-
it(
|
|
131
|
+
it('should ignore non-native selling_liabilities (only the native balance contributes)', () => {
|
|
129
132
|
expect(
|
|
130
133
|
getReservedBalance(
|
|
131
134
|
makeAccount({
|
|
132
135
|
balances: [
|
|
133
|
-
{ asset_type:
|
|
134
|
-
{ asset_type:
|
|
136
|
+
{ asset_type: 'credit_alphanum4', selling_liabilities: '100', asset_code: 'USDC' },
|
|
137
|
+
{ asset_type: 'native', selling_liabilities: '2' },
|
|
135
138
|
],
|
|
136
|
-
})
|
|
137
|
-
).toNumber()
|
|
138
|
-
).toBe(3)
|
|
139
|
-
})
|
|
139
|
+
})
|
|
140
|
+
).toNumber()
|
|
141
|
+
).toBe(3)
|
|
142
|
+
})
|
|
140
143
|
|
|
141
|
-
it(
|
|
142
|
-
expect(getReservedBalance(makeAccount({ balances: [] })).toNumber()).toBe(1)
|
|
143
|
-
})
|
|
144
|
+
it('should fall back to 0 selling_liabilities when no native balance entry exists', () => {
|
|
145
|
+
expect(getReservedBalance(makeAccount({ balances: [] })).toNumber()).toBe(1)
|
|
146
|
+
})
|
|
144
147
|
|
|
145
|
-
it(
|
|
148
|
+
it('should treat undefined selling_liabilities on the native balance as 0', () => {
|
|
146
149
|
expect(
|
|
147
|
-
getReservedBalance(makeAccount({ balances: [{ asset_type:
|
|
148
|
-
).toBe(1)
|
|
149
|
-
})
|
|
150
|
+
getReservedBalance(makeAccount({ balances: [{ asset_type: 'native' }] })).toNumber()
|
|
151
|
+
).toBe(1)
|
|
152
|
+
})
|
|
150
153
|
|
|
151
|
-
it(
|
|
154
|
+
it('should combine subentries, sponsoring/sponsored entries and selling_liabilities', () => {
|
|
152
155
|
// (2 + 3 + 2 - 1) * 0.5 + 4.5 = 6 * 0.5 + 4.5 = 7.5
|
|
153
156
|
expect(
|
|
154
157
|
getReservedBalance(
|
|
155
158
|
makeAccount({
|
|
156
159
|
subentry_count: 3,
|
|
157
|
-
num_sponsoring:
|
|
158
|
-
num_sponsored:
|
|
159
|
-
balances: [{ asset_type:
|
|
160
|
-
})
|
|
161
|
-
).toNumber()
|
|
162
|
-
).toBe(7.5)
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
describe(
|
|
167
|
-
describe(
|
|
168
|
-
it(
|
|
160
|
+
num_sponsoring: '2',
|
|
161
|
+
num_sponsored: '1',
|
|
162
|
+
balances: [{ asset_type: 'native', selling_liabilities: '4.5' }],
|
|
163
|
+
})
|
|
164
|
+
).toNumber()
|
|
165
|
+
).toBe(7.5)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
describe('rawOperationsToOperations', () => {
|
|
170
|
+
describe('address filtering', () => {
|
|
171
|
+
it('should exclude operations not involving the address', async () => {
|
|
169
172
|
const op = makeOp({
|
|
170
|
-
type:
|
|
173
|
+
type: 'payment',
|
|
171
174
|
from: OTHER_ADDR,
|
|
172
175
|
to: THIRD_ADDR,
|
|
173
|
-
amount:
|
|
176
|
+
amount: '10',
|
|
174
177
|
source_account: OTHER_ADDR,
|
|
175
|
-
})
|
|
176
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
177
|
-
expect(result).toHaveLength(0)
|
|
178
|
-
})
|
|
178
|
+
})
|
|
179
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
180
|
+
expect(result).toHaveLength(0)
|
|
181
|
+
})
|
|
179
182
|
|
|
180
|
-
it(
|
|
183
|
+
it('should include operations where `from` equals the address', async () => {
|
|
181
184
|
const op = makeOp({
|
|
182
|
-
type:
|
|
185
|
+
type: 'payment',
|
|
183
186
|
from: ADDR,
|
|
184
187
|
to: OTHER_ADDR,
|
|
185
|
-
amount:
|
|
186
|
-
})
|
|
187
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
188
|
-
expect(result).toHaveLength(1)
|
|
189
|
-
})
|
|
188
|
+
amount: '10',
|
|
189
|
+
})
|
|
190
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
191
|
+
expect(result).toHaveLength(1)
|
|
192
|
+
})
|
|
190
193
|
|
|
191
|
-
it(
|
|
194
|
+
it('should include operations where `to` equals the address', async () => {
|
|
192
195
|
const op = makeOp({
|
|
193
|
-
type:
|
|
196
|
+
type: 'payment',
|
|
194
197
|
from: OTHER_ADDR,
|
|
195
198
|
to: ADDR,
|
|
196
|
-
amount:
|
|
199
|
+
amount: '10',
|
|
197
200
|
source_account: OTHER_ADDR,
|
|
198
|
-
})
|
|
199
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
200
|
-
expect(result).toHaveLength(1)
|
|
201
|
-
})
|
|
201
|
+
})
|
|
202
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
203
|
+
expect(result).toHaveLength(1)
|
|
204
|
+
})
|
|
202
205
|
|
|
203
|
-
it(
|
|
206
|
+
it('should include operations where `funder` equals the address', async () => {
|
|
204
207
|
const op = makeOp({
|
|
205
|
-
type:
|
|
208
|
+
type: 'create_account',
|
|
206
209
|
funder: ADDR,
|
|
207
210
|
account: OTHER_ADDR,
|
|
208
|
-
starting_balance:
|
|
209
|
-
})
|
|
210
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
211
|
-
expect(result).toHaveLength(1)
|
|
212
|
-
})
|
|
211
|
+
starting_balance: '10',
|
|
212
|
+
})
|
|
213
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
214
|
+
expect(result).toHaveLength(1)
|
|
215
|
+
})
|
|
213
216
|
|
|
214
|
-
it(
|
|
217
|
+
it('should include operations where `account` equals the address', async () => {
|
|
215
218
|
const op = makeOp({
|
|
216
|
-
type:
|
|
219
|
+
type: 'create_account',
|
|
217
220
|
funder: OTHER_ADDR,
|
|
218
221
|
account: ADDR,
|
|
219
|
-
starting_balance:
|
|
222
|
+
starting_balance: '10',
|
|
220
223
|
source_account: OTHER_ADDR,
|
|
221
|
-
})
|
|
222
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
223
|
-
expect(result).toHaveLength(1)
|
|
224
|
-
})
|
|
224
|
+
})
|
|
225
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
226
|
+
expect(result).toHaveLength(1)
|
|
227
|
+
})
|
|
225
228
|
|
|
226
|
-
it(
|
|
229
|
+
it('should include operations where `trustor` equals the address', async () => {
|
|
227
230
|
const op = makeOp({
|
|
228
|
-
type:
|
|
231
|
+
type: 'change_trust',
|
|
229
232
|
trustor: ADDR,
|
|
230
|
-
limit:
|
|
231
|
-
asset_code:
|
|
232
|
-
asset_issuer:
|
|
233
|
+
limit: '1000',
|
|
234
|
+
asset_code: 'USDC',
|
|
235
|
+
asset_issuer: 'GISSUER',
|
|
233
236
|
source_account: OTHER_ADDR,
|
|
234
|
-
})
|
|
235
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
236
|
-
expect(result).toHaveLength(1)
|
|
237
|
-
})
|
|
237
|
+
})
|
|
238
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
239
|
+
expect(result).toHaveLength(1)
|
|
240
|
+
})
|
|
238
241
|
|
|
239
|
-
it(
|
|
242
|
+
it('should include operations where `source_account` equals the address', async () => {
|
|
240
243
|
const op = makeOp({
|
|
241
|
-
type:
|
|
244
|
+
type: 'change_trust',
|
|
242
245
|
trustor: OTHER_ADDR,
|
|
243
|
-
limit:
|
|
244
|
-
asset_code:
|
|
245
|
-
asset_issuer:
|
|
246
|
+
limit: '1000',
|
|
247
|
+
asset_code: 'USDC',
|
|
248
|
+
asset_issuer: 'GISSUER',
|
|
246
249
|
source_account: ADDR,
|
|
247
|
-
})
|
|
248
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
249
|
-
expect(result).toHaveLength(1)
|
|
250
|
-
})
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
describe(
|
|
254
|
-
it.each([
|
|
255
|
-
|
|
256
|
-
async type => {
|
|
250
|
+
})
|
|
251
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
252
|
+
expect(result).toHaveLength(1)
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('operation type filtering', () => {
|
|
257
|
+
it.each(['manage_offer', 'set_options', 'allow_trust', 'manage_data', 'bump_sequence'])(
|
|
258
|
+
'should not surface a typed Operation for unsupported %s operation',
|
|
259
|
+
async (type) => {
|
|
257
260
|
const op = makeOp({
|
|
258
261
|
type,
|
|
259
262
|
source_account: ADDR,
|
|
260
|
-
})
|
|
261
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
263
|
+
})
|
|
264
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
262
265
|
// No typed Operation (OUT/IN/OPT_IN/...) is emitted for unsupported types
|
|
263
|
-
expect(result.find(o => o.type !==
|
|
264
|
-
}
|
|
265
|
-
)
|
|
266
|
+
expect(result.find((o) => o.type !== 'FEES')).toBeUndefined()
|
|
267
|
+
}
|
|
268
|
+
)
|
|
266
269
|
|
|
267
270
|
it.each([
|
|
268
|
-
[
|
|
269
|
-
[
|
|
270
|
-
[
|
|
271
|
-
[
|
|
271
|
+
['create_account', { funder: ADDR, account: OTHER_ADDR, starting_balance: '1' }],
|
|
272
|
+
['payment', { from: ADDR, to: OTHER_ADDR, amount: '1' }],
|
|
273
|
+
['path_payment_strict_send', { from: ADDR, to: OTHER_ADDR, amount: '1' }],
|
|
274
|
+
['path_payment_strict_receive', { from: ADDR, to: OTHER_ADDR, amount: '1' }],
|
|
272
275
|
[
|
|
273
|
-
|
|
274
|
-
{ trustor: ADDR, limit:
|
|
276
|
+
'change_trust',
|
|
277
|
+
{ trustor: ADDR, limit: '1000', asset_code: 'USDC', asset_issuer: 'GISSUER' },
|
|
275
278
|
],
|
|
276
|
-
])(
|
|
277
|
-
const op = makeOp({ type, ...extra } as RawOpInput)
|
|
278
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
279
|
-
expect(result).toHaveLength(1)
|
|
280
|
-
})
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
describe(
|
|
279
|
+
])('should include supported %s operation', async (type, extra) => {
|
|
280
|
+
const op = makeOp({ type, ...extra } as RawOpInput)
|
|
281
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
282
|
+
expect(result).toHaveLength(1)
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
describe('fee-only operations', () => {
|
|
284
287
|
// Non-fee-bump Stellar transactions have `fee_account === source_account`,
|
|
285
288
|
// which is what most of these fixtures model.
|
|
286
|
-
it.each([
|
|
287
|
-
|
|
288
|
-
async type => {
|
|
289
|
+
it.each(['set_options', 'manage_offer', 'manage_data', 'bump_sequence', 'allow_trust'])(
|
|
290
|
+
'emits a single FEES Operation for unsupported %s when address paid the fee',
|
|
291
|
+
async (type) => {
|
|
289
292
|
const op = makeOp(
|
|
290
293
|
{ type, source_account: ADDR, transaction_hash: `tx-${type}` },
|
|
291
|
-
{ fee_account: ADDR, fee_charged:
|
|
292
|
-
)
|
|
293
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
294
|
-
expect(result).toHaveLength(1)
|
|
294
|
+
{ fee_account: ADDR, fee_charged: '150' }
|
|
295
|
+
)
|
|
296
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
297
|
+
expect(result).toHaveLength(1)
|
|
295
298
|
expect(result[0]).toMatchObject({
|
|
296
|
-
type:
|
|
299
|
+
type: 'FEES',
|
|
297
300
|
value: 0n,
|
|
298
|
-
asset: { type:
|
|
301
|
+
asset: { type: 'native' },
|
|
299
302
|
senders: [ADDR],
|
|
300
303
|
recipients: [],
|
|
301
304
|
tx: { hash: `tx-${type}`, fees: 150n, feesPayer: ADDR, failed: false },
|
|
302
|
-
})
|
|
303
|
-
expect(result[0].details).toMatchObject({ ledgerOpType:
|
|
304
|
-
expect(result[0].id).toBe(`${ACCOUNT_ID}-tx-${type}-FEES`)
|
|
305
|
-
}
|
|
306
|
-
)
|
|
305
|
+
})
|
|
306
|
+
expect(result[0].details).toMatchObject({ ledgerOpType: 'FEES' })
|
|
307
|
+
expect(result[0].id).toBe(`${ACCOUNT_ID}-tx-${type}-FEES`)
|
|
308
|
+
}
|
|
309
|
+
)
|
|
307
310
|
|
|
308
|
-
it(
|
|
311
|
+
it('emits a FEES Operation for create_claimable_balance when address is the sponsor', async () => {
|
|
309
312
|
const op = makeOp(
|
|
310
313
|
{
|
|
311
|
-
type:
|
|
314
|
+
type: 'create_claimable_balance',
|
|
312
315
|
source_account: ADDR,
|
|
313
|
-
asset:
|
|
314
|
-
amount:
|
|
316
|
+
asset: 'USDC:GISSUER',
|
|
317
|
+
amount: '100',
|
|
315
318
|
sponsor: ADDR,
|
|
316
|
-
transaction_hash:
|
|
319
|
+
transaction_hash: 'tx-ccb',
|
|
317
320
|
},
|
|
318
|
-
{ fee_account: ADDR, fee_charged:
|
|
319
|
-
)
|
|
320
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
321
|
-
expect(out.type).toBe(
|
|
322
|
-
expect(out.tx.fees).toBe(100n)
|
|
321
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
322
|
+
)
|
|
323
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
324
|
+
expect(out.type).toBe('FEES')
|
|
325
|
+
expect(out.tx.fees).toBe(100n)
|
|
323
326
|
// The token amount locked in the claimable balance is intentionally NOT
|
|
324
327
|
// exposed on the FEES op; only the XLM fee deduction is.
|
|
325
|
-
expect(out.value).toBe(0n)
|
|
326
|
-
expect(out.asset).toEqual({ type:
|
|
327
|
-
})
|
|
328
|
+
expect(out.value).toBe(0n)
|
|
329
|
+
expect(out.asset).toEqual({ type: 'native' })
|
|
330
|
+
})
|
|
328
331
|
|
|
329
|
-
it(
|
|
332
|
+
it('does NOT emit a FEES Operation when address is not the fee payer (fee-bump or unrelated)', async () => {
|
|
330
333
|
const op = makeOp(
|
|
331
334
|
{
|
|
332
|
-
type:
|
|
335
|
+
type: 'set_options',
|
|
333
336
|
source_account: ADDR,
|
|
334
|
-
transaction_hash:
|
|
337
|
+
transaction_hash: 'tx-fee-bumped',
|
|
335
338
|
},
|
|
336
339
|
// fee bumped by another account
|
|
337
|
-
{ fee_account: OTHER_ADDR, fee_charged:
|
|
338
|
-
)
|
|
339
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
340
|
-
expect(result).toHaveLength(0)
|
|
341
|
-
})
|
|
340
|
+
{ fee_account: OTHER_ADDR, fee_charged: '150' }
|
|
341
|
+
)
|
|
342
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
343
|
+
expect(result).toHaveLength(0)
|
|
344
|
+
})
|
|
342
345
|
|
|
343
|
-
it(
|
|
346
|
+
it('does NOT emit a FEES Operation when the tx already has a surfaced Operation (avoids double-counting)', async () => {
|
|
344
347
|
// Multi-op transaction: one supported (change_trust, OPT_IN) + one
|
|
345
348
|
// unsupported (set_options). Only the supported op is surfaced; the
|
|
346
349
|
// unsupported one does not produce an extra FEES row since the tx fee is
|
|
347
350
|
// already carried on the OPT_IN op's `tx.fees`.
|
|
348
351
|
const supportedOp = makeOp(
|
|
349
352
|
{
|
|
350
|
-
type:
|
|
353
|
+
type: 'change_trust',
|
|
351
354
|
trustor: ADDR,
|
|
352
|
-
limit:
|
|
353
|
-
asset_code:
|
|
354
|
-
asset_issuer:
|
|
355
|
+
limit: '1000',
|
|
356
|
+
asset_code: 'BTC',
|
|
357
|
+
asset_issuer: 'GISSUER',
|
|
355
358
|
source_account: ADDR,
|
|
356
|
-
transaction_hash:
|
|
357
|
-
id:
|
|
358
|
-
paging_token:
|
|
359
|
+
transaction_hash: 'tx-mixed',
|
|
360
|
+
id: 'op-1',
|
|
361
|
+
paging_token: 'pt-1',
|
|
359
362
|
},
|
|
360
|
-
{ fee_account: ADDR, fee_charged:
|
|
361
|
-
)
|
|
363
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
364
|
+
)
|
|
362
365
|
const unsupportedOp = makeOp(
|
|
363
366
|
{
|
|
364
|
-
type:
|
|
367
|
+
type: 'set_options',
|
|
365
368
|
source_account: ADDR,
|
|
366
|
-
transaction_hash:
|
|
367
|
-
id:
|
|
368
|
-
paging_token:
|
|
369
|
+
transaction_hash: 'tx-mixed',
|
|
370
|
+
id: 'op-2',
|
|
371
|
+
paging_token: 'pt-2',
|
|
369
372
|
},
|
|
370
|
-
{ fee_account: ADDR, fee_charged:
|
|
371
|
-
)
|
|
373
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
374
|
+
)
|
|
372
375
|
const result = await rawOperationsToOperations(
|
|
373
376
|
[supportedOp, unsupportedOp],
|
|
374
377
|
ADDR,
|
|
375
378
|
ACCOUNT_ID,
|
|
376
|
-
0
|
|
377
|
-
)
|
|
378
|
-
expect(result).toHaveLength(1)
|
|
379
|
-
expect(result[0].type).toBe(
|
|
380
|
-
expect(result[0].tx.fees).toBe(100n)
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
it(
|
|
379
|
+
0
|
|
380
|
+
)
|
|
381
|
+
expect(result).toHaveLength(1)
|
|
382
|
+
expect(result[0].type).toBe('OPT_IN')
|
|
383
|
+
expect(result[0].tx.fees).toBe(100n)
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('emits a single FEES Operation per tx even when multiple unsupported ops share the same tx hash', async () => {
|
|
384
387
|
const opA = makeOp(
|
|
385
388
|
{
|
|
386
|
-
type:
|
|
389
|
+
type: 'set_options',
|
|
387
390
|
source_account: ADDR,
|
|
388
|
-
transaction_hash:
|
|
389
|
-
id:
|
|
390
|
-
paging_token:
|
|
391
|
+
transaction_hash: 'tx-multi',
|
|
392
|
+
id: 'op-a',
|
|
393
|
+
paging_token: 'pt-a',
|
|
391
394
|
},
|
|
392
|
-
{ fee_account: ADDR, fee_charged:
|
|
393
|
-
)
|
|
395
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
396
|
+
)
|
|
394
397
|
const opB = makeOp(
|
|
395
398
|
{
|
|
396
|
-
type:
|
|
399
|
+
type: 'manage_data',
|
|
397
400
|
source_account: ADDR,
|
|
398
|
-
transaction_hash:
|
|
399
|
-
id:
|
|
400
|
-
paging_token:
|
|
401
|
+
transaction_hash: 'tx-multi',
|
|
402
|
+
id: 'op-b',
|
|
403
|
+
paging_token: 'pt-b',
|
|
401
404
|
},
|
|
402
|
-
{ fee_account: ADDR, fee_charged:
|
|
403
|
-
)
|
|
404
|
-
const result = await rawOperationsToOperations([opA, opB], ADDR, ACCOUNT_ID, 0)
|
|
405
|
-
expect(result).toHaveLength(1)
|
|
406
|
-
expect(result[0].type).toBe(
|
|
407
|
-
expect(result[0].tx.hash).toBe(
|
|
405
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
406
|
+
)
|
|
407
|
+
const result = await rawOperationsToOperations([opA, opB], ADDR, ACCOUNT_ID, 0)
|
|
408
|
+
expect(result).toHaveLength(1)
|
|
409
|
+
expect(result[0].type).toBe('FEES')
|
|
410
|
+
expect(result[0].tx.hash).toBe('tx-multi')
|
|
408
411
|
// The FEES op anchors at the first raw op's index/pagingToken so the
|
|
409
412
|
// descending Horizon page order is preserved.
|
|
410
|
-
expect(result[0].details).toMatchObject({ index:
|
|
411
|
-
})
|
|
413
|
+
expect(result[0].details).toMatchObject({ index: 'op-a', pagingToken: 'pt-a' })
|
|
414
|
+
})
|
|
412
415
|
|
|
413
|
-
it(
|
|
416
|
+
it('honors minHeight for fee-only operations', async () => {
|
|
414
417
|
const op = makeOp(
|
|
415
|
-
{ type:
|
|
416
|
-
{ fee_account: ADDR, fee_charged:
|
|
417
|
-
)
|
|
418
|
-
expect(await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 100)).toHaveLength(0)
|
|
419
|
-
})
|
|
418
|
+
{ type: 'set_options', source_account: ADDR },
|
|
419
|
+
{ fee_account: ADDR, fee_charged: '100', ledger_attr: 99 }
|
|
420
|
+
)
|
|
421
|
+
expect(await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 100)).toHaveLength(0)
|
|
422
|
+
})
|
|
420
423
|
|
|
421
|
-
it(
|
|
424
|
+
it('marks the FEES Operation as failed when the transaction failed', async () => {
|
|
422
425
|
const op = makeOp(
|
|
423
426
|
{
|
|
424
|
-
type:
|
|
427
|
+
type: 'set_options',
|
|
425
428
|
source_account: ADDR,
|
|
426
429
|
transaction_successful: false,
|
|
427
|
-
transaction_hash:
|
|
430
|
+
transaction_hash: 'tx-failed',
|
|
428
431
|
},
|
|
429
|
-
{ fee_account: ADDR, fee_charged:
|
|
430
|
-
)
|
|
431
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
432
|
-
expect(out.type).toBe(
|
|
433
|
-
expect(out.tx.failed).toBe(true)
|
|
434
|
-
expect(out.tx.fees).toBe(100n)
|
|
435
|
-
})
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
describe(
|
|
439
|
-
it(
|
|
432
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
433
|
+
)
|
|
434
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
435
|
+
expect(out.type).toBe('FEES')
|
|
436
|
+
expect(out.tx.failed).toBe(true)
|
|
437
|
+
expect(out.tx.fees).toBe(100n)
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
describe('minHeight filtering', () => {
|
|
442
|
+
it('should exclude operations from blocks strictly below minHeight', async () => {
|
|
440
443
|
const op = makeOp(
|
|
441
|
-
{ type:
|
|
442
|
-
{ ledger_attr: 99 }
|
|
443
|
-
)
|
|
444
|
-
expect(await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 100)).toHaveLength(0)
|
|
445
|
-
})
|
|
444
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '5' },
|
|
445
|
+
{ ledger_attr: 99 }
|
|
446
|
+
)
|
|
447
|
+
expect(await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 100)).toHaveLength(0)
|
|
448
|
+
})
|
|
446
449
|
|
|
447
|
-
it(
|
|
450
|
+
it('should include operations at minHeight', async () => {
|
|
448
451
|
const op = makeOp(
|
|
449
|
-
{ type:
|
|
450
|
-
{ ledger_attr: 100 }
|
|
451
|
-
)
|
|
452
|
-
expect(await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 100)).toHaveLength(1)
|
|
453
|
-
})
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
describe(
|
|
457
|
-
it(
|
|
452
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '5' },
|
|
453
|
+
{ ledger_attr: 100 }
|
|
454
|
+
)
|
|
455
|
+
expect(await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 100)).toHaveLength(1)
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
describe('operation type determination', () => {
|
|
460
|
+
it('should mark create_account as OUT when funder is the address', async () => {
|
|
458
461
|
const op = makeOp({
|
|
459
|
-
type:
|
|
462
|
+
type: 'create_account',
|
|
460
463
|
funder: ADDR,
|
|
461
464
|
account: OTHER_ADDR,
|
|
462
|
-
starting_balance:
|
|
463
|
-
})
|
|
464
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
465
|
-
expect(out.type).toBe(
|
|
466
|
-
})
|
|
465
|
+
starting_balance: '10',
|
|
466
|
+
})
|
|
467
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
468
|
+
expect(out.type).toBe('OUT')
|
|
469
|
+
})
|
|
467
470
|
|
|
468
|
-
it(
|
|
471
|
+
it('should mark create_account as IN when account is the address', async () => {
|
|
469
472
|
const op = makeOp({
|
|
470
|
-
type:
|
|
473
|
+
type: 'create_account',
|
|
471
474
|
funder: OTHER_ADDR,
|
|
472
475
|
account: ADDR,
|
|
473
|
-
starting_balance:
|
|
476
|
+
starting_balance: '10',
|
|
474
477
|
source_account: OTHER_ADDR,
|
|
475
|
-
})
|
|
476
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
477
|
-
expect(out.type).toBe(
|
|
478
|
-
})
|
|
479
|
-
|
|
480
|
-
it(
|
|
481
|
-
const op = makeOp({ type:
|
|
482
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
483
|
-
expect(out.type).toBe(
|
|
484
|
-
})
|
|
485
|
-
|
|
486
|
-
it(
|
|
478
|
+
})
|
|
479
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
480
|
+
expect(out.type).toBe('IN')
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it('should mark payment as OUT when from is the address and recipient differs', async () => {
|
|
484
|
+
const op = makeOp({ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '5' })
|
|
485
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
486
|
+
expect(out.type).toBe('OUT')
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('should mark payment as IN when the address is the recipient', async () => {
|
|
487
490
|
const op = makeOp({
|
|
488
|
-
type:
|
|
491
|
+
type: 'payment',
|
|
489
492
|
from: OTHER_ADDR,
|
|
490
493
|
to: ADDR,
|
|
491
|
-
amount:
|
|
494
|
+
amount: '5',
|
|
492
495
|
source_account: OTHER_ADDR,
|
|
493
|
-
})
|
|
494
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
495
|
-
expect(out.type).toBe(
|
|
496
|
-
})
|
|
497
|
-
|
|
498
|
-
it(
|
|
499
|
-
const op = makeOp({ type:
|
|
500
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
501
|
-
expect(out.type).toBe(
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
it(
|
|
496
|
+
})
|
|
497
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
498
|
+
expect(out.type).toBe('IN')
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('should mark self-payments (from=to=address) as IN', async () => {
|
|
502
|
+
const op = makeOp({ type: 'payment', from: ADDR, to: ADDR, amount: '5' })
|
|
503
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
504
|
+
expect(out.type).toBe('IN')
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
it('should mark path_payment_strict_send as OUT when address is not the recipient', async () => {
|
|
505
508
|
const op = makeOp({
|
|
506
|
-
type:
|
|
509
|
+
type: 'path_payment_strict_send',
|
|
507
510
|
from: ADDR,
|
|
508
511
|
to: OTHER_ADDR,
|
|
509
|
-
amount:
|
|
510
|
-
})
|
|
511
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
512
|
-
expect(out.type).toBe(
|
|
513
|
-
})
|
|
512
|
+
amount: '5',
|
|
513
|
+
})
|
|
514
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
515
|
+
expect(out.type).toBe('OUT')
|
|
516
|
+
})
|
|
514
517
|
|
|
515
|
-
it(
|
|
518
|
+
it('should mark path_payment_strict_send as IN when address is the recipient', async () => {
|
|
516
519
|
const op = makeOp({
|
|
517
|
-
type:
|
|
520
|
+
type: 'path_payment_strict_send',
|
|
518
521
|
from: OTHER_ADDR,
|
|
519
522
|
to: ADDR,
|
|
520
|
-
amount:
|
|
523
|
+
amount: '5',
|
|
521
524
|
source_account: OTHER_ADDR,
|
|
522
|
-
})
|
|
523
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
524
|
-
expect(out.type).toBe(
|
|
525
|
-
})
|
|
525
|
+
})
|
|
526
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
527
|
+
expect(out.type).toBe('IN')
|
|
528
|
+
})
|
|
526
529
|
|
|
527
|
-
it(
|
|
530
|
+
it('should mark path_payment_strict_receive as OUT when address is the source', async () => {
|
|
528
531
|
const op = makeOp({
|
|
529
|
-
type:
|
|
532
|
+
type: 'path_payment_strict_receive',
|
|
530
533
|
from: ADDR,
|
|
531
534
|
to: OTHER_ADDR,
|
|
532
|
-
amount:
|
|
533
|
-
})
|
|
534
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
535
|
-
expect(out.type).toBe(
|
|
536
|
-
})
|
|
535
|
+
amount: '5',
|
|
536
|
+
})
|
|
537
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
538
|
+
expect(out.type).toBe('OUT')
|
|
539
|
+
})
|
|
537
540
|
|
|
538
|
-
it(
|
|
541
|
+
it('should mark path_payment_strict_receive as IN when address is the recipient', async () => {
|
|
539
542
|
const op = makeOp({
|
|
540
|
-
type:
|
|
543
|
+
type: 'path_payment_strict_receive',
|
|
541
544
|
from: OTHER_ADDR,
|
|
542
545
|
to: ADDR,
|
|
543
|
-
amount:
|
|
546
|
+
amount: '5',
|
|
544
547
|
source_account: OTHER_ADDR,
|
|
545
|
-
})
|
|
546
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
547
|
-
expect(out.type).toBe(
|
|
548
|
-
})
|
|
548
|
+
})
|
|
549
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
550
|
+
expect(out.type).toBe('IN')
|
|
551
|
+
})
|
|
549
552
|
|
|
550
|
-
it(
|
|
553
|
+
it('should mark change_trust as OPT_IN when limit is non-zero', async () => {
|
|
551
554
|
const op = makeOp({
|
|
552
|
-
type:
|
|
555
|
+
type: 'change_trust',
|
|
553
556
|
trustor: ADDR,
|
|
554
|
-
limit:
|
|
555
|
-
asset_code:
|
|
556
|
-
asset_issuer:
|
|
557
|
-
})
|
|
558
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
559
|
-
expect(out.type).toBe(
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
it(
|
|
557
|
+
limit: '1000',
|
|
558
|
+
asset_code: 'USDC',
|
|
559
|
+
asset_issuer: 'GISSUER',
|
|
560
|
+
})
|
|
561
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
562
|
+
expect(out.type).toBe('OPT_IN')
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('should mark change_trust as OPT_OUT when limit is zero', async () => {
|
|
563
566
|
const op = makeOp({
|
|
564
|
-
type:
|
|
567
|
+
type: 'change_trust',
|
|
565
568
|
trustor: ADDR,
|
|
566
|
-
limit:
|
|
567
|
-
asset_code:
|
|
568
|
-
asset_issuer:
|
|
569
|
-
})
|
|
570
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
571
|
-
expect(out.type).toBe(
|
|
572
|
-
})
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
describe(
|
|
569
|
+
limit: '0',
|
|
570
|
+
asset_code: 'USDC',
|
|
571
|
+
asset_issuer: 'GISSUER',
|
|
572
|
+
})
|
|
573
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
574
|
+
expect(out.type).toBe('OPT_OUT')
|
|
575
|
+
})
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
describe('memo handling', () => {
|
|
576
579
|
it("should set NO_MEMO when memo_type is 'none'", async () => {
|
|
577
580
|
const op = makeOp(
|
|
578
|
-
{ type:
|
|
579
|
-
{ memo_type:
|
|
580
|
-
)
|
|
581
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
582
|
-
expect(out.details).toMatchObject({ memo: { type:
|
|
583
|
-
})
|
|
581
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
582
|
+
{ memo_type: 'none' }
|
|
583
|
+
)
|
|
584
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
585
|
+
expect(out.details).toMatchObject({ memo: { type: 'NO_MEMO' } })
|
|
586
|
+
})
|
|
584
587
|
|
|
585
588
|
it("should set MEMO_ID with raw memo when memo_type is 'id'", async () => {
|
|
586
589
|
const op = makeOp(
|
|
587
|
-
{ type:
|
|
588
|
-
{ memo_type:
|
|
589
|
-
)
|
|
590
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
591
|
-
expect(out.details).toMatchObject({ memo: { type:
|
|
592
|
-
})
|
|
590
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
591
|
+
{ memo_type: 'id', memo: '12345' }
|
|
592
|
+
)
|
|
593
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
594
|
+
expect(out.details).toMatchObject({ memo: { type: 'MEMO_ID', value: '12345' } })
|
|
595
|
+
})
|
|
593
596
|
|
|
594
597
|
it("should set MEMO_TEXT with raw memo when memo_type is 'text'", async () => {
|
|
595
598
|
const op = makeOp(
|
|
596
|
-
{ type:
|
|
597
|
-
{ memo_type:
|
|
598
|
-
)
|
|
599
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
600
|
-
expect(out.details).toMatchObject({ memo: { type:
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
it(
|
|
604
|
-
const memoBase64 = Buffer.from(
|
|
599
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
600
|
+
{ memo_type: 'text', memo: 'hello' }
|
|
601
|
+
)
|
|
602
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
603
|
+
expect(out.details).toMatchObject({ memo: { type: 'MEMO_TEXT', value: 'hello' } })
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
it('should convert MEMO_RETURN from base64 to hex', async () => {
|
|
607
|
+
const memoBase64 = Buffer.from('deadbeef', 'hex').toString('base64')
|
|
605
608
|
const op = makeOp(
|
|
606
|
-
{ type:
|
|
607
|
-
{ memo_type:
|
|
608
|
-
)
|
|
609
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
609
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
610
|
+
{ memo_type: 'return', memo: memoBase64 }
|
|
611
|
+
)
|
|
612
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
610
613
|
expect(out.details).toMatchObject({
|
|
611
|
-
memo: { type:
|
|
612
|
-
})
|
|
613
|
-
})
|
|
614
|
+
memo: { type: 'MEMO_RETURN', value: 'deadbeef' },
|
|
615
|
+
})
|
|
616
|
+
})
|
|
614
617
|
|
|
615
|
-
it(
|
|
616
|
-
const memoBase64 = Buffer.from(
|
|
618
|
+
it('should convert MEMO_HASH from base64 to hex', async () => {
|
|
619
|
+
const memoBase64 = Buffer.from('cafebabe', 'hex').toString('base64')
|
|
617
620
|
const op = makeOp(
|
|
618
|
-
{ type:
|
|
619
|
-
{ memo_type:
|
|
620
|
-
)
|
|
621
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
621
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
622
|
+
{ memo_type: 'hash', memo: memoBase64 }
|
|
623
|
+
)
|
|
624
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
622
625
|
expect(out.details).toMatchObject({
|
|
623
|
-
memo: { type:
|
|
624
|
-
})
|
|
625
|
-
})
|
|
626
|
+
memo: { type: 'MEMO_HASH', value: 'cafebabe' },
|
|
627
|
+
})
|
|
628
|
+
})
|
|
626
629
|
|
|
627
|
-
it(
|
|
630
|
+
it('should leave memo undefined when memo_type is set but memo value is missing', async () => {
|
|
628
631
|
const op = makeOp(
|
|
629
|
-
{ type:
|
|
630
|
-
{ memo_type:
|
|
631
|
-
)
|
|
632
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
633
|
-
expect((out.details as { memo?: unknown }).memo).toBeUndefined()
|
|
634
|
-
})
|
|
635
|
-
})
|
|
636
|
-
|
|
637
|
-
describe(
|
|
638
|
-
it(
|
|
639
|
-
const op = makeOp({ type:
|
|
640
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
641
|
-
expect(out.asset).toEqual({ type:
|
|
642
|
-
expect(out.value).toBe(100000000n)
|
|
632
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
633
|
+
{ memo_type: 'id', memo: undefined }
|
|
634
|
+
)
|
|
635
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
636
|
+
expect((out.details as { memo?: unknown }).memo).toBeUndefined()
|
|
637
|
+
})
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
describe('asset and value handling', () => {
|
|
641
|
+
it('should set native asset and value=amount*10^7 for native payments', async () => {
|
|
642
|
+
const op = makeOp({ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '10' })
|
|
643
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
644
|
+
expect(out.asset).toEqual({ type: 'native' })
|
|
645
|
+
expect(out.value).toBe(100000000n)
|
|
643
646
|
expect(out.details).toMatchObject({
|
|
644
647
|
assetCode: undefined,
|
|
645
648
|
assetIssuer: undefined,
|
|
646
649
|
assetAmount: undefined,
|
|
647
|
-
})
|
|
648
|
-
})
|
|
650
|
+
})
|
|
651
|
+
})
|
|
649
652
|
|
|
650
|
-
it(
|
|
653
|
+
it('should set token asset, NONE type and value=fee_charged when asset_code+asset_issuer are present', async () => {
|
|
651
654
|
const op = makeOp(
|
|
652
655
|
{
|
|
653
|
-
type:
|
|
656
|
+
type: 'payment',
|
|
654
657
|
from: ADDR,
|
|
655
658
|
to: OTHER_ADDR,
|
|
656
|
-
amount:
|
|
657
|
-
asset_code:
|
|
658
|
-
asset_issuer:
|
|
659
|
+
amount: '10',
|
|
660
|
+
asset_code: 'USDC',
|
|
661
|
+
asset_issuer: 'GISSUER',
|
|
659
662
|
},
|
|
660
|
-
{ fee_charged:
|
|
661
|
-
)
|
|
662
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
663
|
+
{ fee_charged: '200' }
|
|
664
|
+
)
|
|
665
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
663
666
|
expect(out.asset).toEqual({
|
|
664
|
-
type:
|
|
665
|
-
assetReference:
|
|
666
|
-
assetOwner:
|
|
667
|
-
})
|
|
668
|
-
expect(out.value).toBe(200n)
|
|
669
|
-
expect(out.type).toBe(
|
|
667
|
+
type: 'token',
|
|
668
|
+
assetReference: 'USDC',
|
|
669
|
+
assetOwner: 'GISSUER',
|
|
670
|
+
})
|
|
671
|
+
expect(out.value).toBe(200n)
|
|
672
|
+
expect(out.type).toBe('NONE')
|
|
670
673
|
expect(out.details).toMatchObject({
|
|
671
|
-
assetCode:
|
|
672
|
-
assetIssuer:
|
|
673
|
-
assetAmount:
|
|
674
|
-
})
|
|
675
|
-
})
|
|
674
|
+
assetCode: 'USDC',
|
|
675
|
+
assetIssuer: 'GISSUER',
|
|
676
|
+
assetAmount: '100000000',
|
|
677
|
+
})
|
|
678
|
+
})
|
|
676
679
|
|
|
677
|
-
it(
|
|
680
|
+
it('should fall back to native asset when asset_code is set but asset_issuer is missing', async () => {
|
|
678
681
|
const op = makeOp({
|
|
679
|
-
type:
|
|
682
|
+
type: 'payment',
|
|
680
683
|
from: ADDR,
|
|
681
684
|
to: OTHER_ADDR,
|
|
682
|
-
amount:
|
|
683
|
-
asset_code:
|
|
684
|
-
})
|
|
685
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
686
|
-
expect(out.asset).toEqual({ type:
|
|
687
|
-
})
|
|
688
|
-
|
|
689
|
-
it(
|
|
685
|
+
amount: '10',
|
|
686
|
+
asset_code: 'USDC',
|
|
687
|
+
})
|
|
688
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
689
|
+
expect(out.asset).toEqual({ type: 'native' })
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
it('should preserve OPT_IN/OPT_OUT type for token change_trust operations', async () => {
|
|
690
693
|
const op = makeOp({
|
|
691
|
-
type:
|
|
694
|
+
type: 'change_trust',
|
|
692
695
|
trustor: ADDR,
|
|
693
|
-
limit:
|
|
694
|
-
asset_code:
|
|
695
|
-
asset_issuer:
|
|
696
|
-
})
|
|
697
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
698
|
-
expect(out.type).toBe(
|
|
699
|
-
})
|
|
700
|
-
|
|
701
|
-
it(
|
|
696
|
+
limit: '1000',
|
|
697
|
+
asset_code: 'USDC',
|
|
698
|
+
asset_issuer: 'GISSUER',
|
|
699
|
+
})
|
|
700
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
701
|
+
expect(out.type).toBe('OPT_IN')
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
it('should parse create_account starting_balance through the stellar unit (10^7)', async () => {
|
|
702
705
|
const op = makeOp({
|
|
703
|
-
type:
|
|
706
|
+
type: 'create_account',
|
|
704
707
|
funder: ADDR,
|
|
705
708
|
account: OTHER_ADDR,
|
|
706
|
-
starting_balance:
|
|
707
|
-
})
|
|
708
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
709
|
-
expect(out.value).toBe(25000000n)
|
|
710
|
-
})
|
|
711
|
-
})
|
|
712
|
-
|
|
713
|
-
describe(
|
|
714
|
-
it(
|
|
709
|
+
starting_balance: '2.5',
|
|
710
|
+
})
|
|
711
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
712
|
+
expect(out.value).toBe(25000000n)
|
|
713
|
+
})
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
describe('path_payment_strict_send native source side', () => {
|
|
717
|
+
it('emits a native OUT row using source_amount when address is the source and source asset is native', async () => {
|
|
715
718
|
const op = makeOp(
|
|
716
719
|
{
|
|
717
|
-
type:
|
|
720
|
+
type: 'path_payment_strict_send',
|
|
718
721
|
from: ADDR,
|
|
719
722
|
to: OTHER_ADDR,
|
|
720
723
|
source_account: ADDR,
|
|
721
|
-
source_asset_type:
|
|
722
|
-
source_amount:
|
|
723
|
-
asset_type:
|
|
724
|
-
asset_code:
|
|
725
|
-
asset_issuer:
|
|
726
|
-
amount:
|
|
724
|
+
source_asset_type: 'native',
|
|
725
|
+
source_amount: '0.7920000',
|
|
726
|
+
asset_type: 'credit_alphanum12',
|
|
727
|
+
asset_code: 'SPDX',
|
|
728
|
+
asset_issuer: 'GISSUER',
|
|
729
|
+
amount: '1234.5678901',
|
|
727
730
|
},
|
|
728
|
-
{ fee_charged:
|
|
729
|
-
)
|
|
730
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
731
|
+
{ fee_charged: '100' }
|
|
732
|
+
)
|
|
733
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
731
734
|
// No FEES row: native OUT already carries the fee on `tx.fees`.
|
|
732
|
-
expect(result.map(o => `${o.type}:${o.asset.type}`)).toEqual([
|
|
735
|
+
expect(result.map((o) => `${o.type}:${o.asset.type}`)).toEqual(['OUT:native'])
|
|
733
736
|
expect(result[0]).toMatchObject({
|
|
734
|
-
type:
|
|
735
|
-
asset: { type:
|
|
737
|
+
type: 'OUT',
|
|
738
|
+
asset: { type: 'native' },
|
|
736
739
|
value: 7920000n,
|
|
737
740
|
senders: [ADDR],
|
|
738
741
|
recipients: [OTHER_ADDR],
|
|
739
742
|
tx: { fees: 100n },
|
|
740
|
-
})
|
|
741
|
-
})
|
|
743
|
+
})
|
|
744
|
+
})
|
|
742
745
|
|
|
743
|
-
it(
|
|
746
|
+
it('emits a native IN row using `amount` when address is the destination and dest asset is native', async () => {
|
|
744
747
|
const op = makeOp(
|
|
745
748
|
{
|
|
746
|
-
type:
|
|
749
|
+
type: 'path_payment_strict_send',
|
|
747
750
|
from: OTHER_ADDR,
|
|
748
751
|
to: ADDR,
|
|
749
752
|
source_account: OTHER_ADDR,
|
|
750
|
-
source_asset_type:
|
|
751
|
-
source_asset_code:
|
|
752
|
-
source_asset_issuer:
|
|
753
|
-
source_amount:
|
|
754
|
-
asset_type:
|
|
755
|
-
amount:
|
|
753
|
+
source_asset_type: 'credit_alphanum4',
|
|
754
|
+
source_asset_code: 'USDC',
|
|
755
|
+
source_asset_issuer: 'GISSUER',
|
|
756
|
+
source_amount: '10.0000000',
|
|
757
|
+
asset_type: 'native',
|
|
758
|
+
amount: '9.9000000',
|
|
756
759
|
},
|
|
757
|
-
{ fee_charged:
|
|
758
|
-
)
|
|
759
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
760
|
+
{ fee_charged: '100' }
|
|
761
|
+
)
|
|
762
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
760
763
|
expect(result).toEqual([
|
|
761
764
|
expect.objectContaining({
|
|
762
|
-
type:
|
|
763
|
-
asset: { type:
|
|
765
|
+
type: 'IN',
|
|
766
|
+
asset: { type: 'native' },
|
|
764
767
|
value: 99000000n,
|
|
765
768
|
senders: [OTHER_ADDR],
|
|
766
769
|
recipients: [ADDR],
|
|
767
770
|
}),
|
|
768
|
-
])
|
|
769
|
-
})
|
|
771
|
+
])
|
|
772
|
+
})
|
|
770
773
|
|
|
771
|
-
it(
|
|
774
|
+
it('emits a token NONE row when the source asset is a token and address is the source', async () => {
|
|
772
775
|
const op = makeOp(
|
|
773
776
|
{
|
|
774
|
-
type:
|
|
777
|
+
type: 'path_payment_strict_send',
|
|
775
778
|
from: ADDR,
|
|
776
779
|
to: OTHER_ADDR,
|
|
777
780
|
source_account: ADDR,
|
|
778
|
-
source_asset_type:
|
|
779
|
-
source_asset_code:
|
|
780
|
-
source_asset_issuer:
|
|
781
|
-
source_amount:
|
|
782
|
-
asset_type:
|
|
783
|
-
asset_code:
|
|
784
|
-
asset_issuer:
|
|
785
|
-
amount:
|
|
781
|
+
source_asset_type: 'credit_alphanum4',
|
|
782
|
+
source_asset_code: 'USDC',
|
|
783
|
+
source_asset_issuer: 'GISSUER',
|
|
784
|
+
source_amount: '5.0000000',
|
|
785
|
+
asset_type: 'credit_alphanum4',
|
|
786
|
+
asset_code: 'EURC',
|
|
787
|
+
asset_issuer: 'GISSUER2',
|
|
788
|
+
amount: '4.5000000',
|
|
786
789
|
},
|
|
787
|
-
{ fee_charged:
|
|
788
|
-
)
|
|
789
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
790
|
-
expect(out.type).toBe(
|
|
790
|
+
{ fee_charged: '300' }
|
|
791
|
+
)
|
|
792
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
793
|
+
expect(out.type).toBe('NONE')
|
|
791
794
|
expect(out.asset).toEqual({
|
|
792
|
-
type:
|
|
793
|
-
assetReference:
|
|
794
|
-
assetOwner:
|
|
795
|
-
})
|
|
795
|
+
type: 'token',
|
|
796
|
+
assetReference: 'USDC',
|
|
797
|
+
assetOwner: 'GISSUER',
|
|
798
|
+
})
|
|
796
799
|
// legacy convention: NONE token rows carry `value = fee_charged` and the
|
|
797
800
|
// real token amount on `details.assetAmount`.
|
|
798
|
-
expect(out.value).toBe(300n)
|
|
801
|
+
expect(out.value).toBe(300n)
|
|
799
802
|
expect(out.details).toMatchObject({
|
|
800
|
-
assetCode:
|
|
801
|
-
assetIssuer:
|
|
802
|
-
assetAmount:
|
|
803
|
-
ledgerOpType:
|
|
804
|
-
})
|
|
805
|
-
})
|
|
806
|
-
|
|
807
|
-
it(
|
|
803
|
+
assetCode: 'USDC',
|
|
804
|
+
assetIssuer: 'GISSUER',
|
|
805
|
+
assetAmount: '50000000',
|
|
806
|
+
ledgerOpType: 'NONE',
|
|
807
|
+
})
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
it('emits both a native OUT (source) and a token NONE (dest) row for self-swaps', async () => {
|
|
808
811
|
const op = makeOp(
|
|
809
812
|
{
|
|
810
|
-
type:
|
|
813
|
+
type: 'path_payment_strict_send',
|
|
811
814
|
from: ADDR,
|
|
812
815
|
to: ADDR,
|
|
813
816
|
source_account: ADDR,
|
|
814
|
-
source_asset_type:
|
|
815
|
-
source_amount:
|
|
816
|
-
asset_type:
|
|
817
|
-
asset_code:
|
|
818
|
-
asset_issuer:
|
|
819
|
-
amount:
|
|
820
|
-
id:
|
|
817
|
+
source_asset_type: 'native',
|
|
818
|
+
source_amount: '0.7920000',
|
|
819
|
+
asset_type: 'credit_alphanum12',
|
|
820
|
+
asset_code: 'SPDX',
|
|
821
|
+
asset_issuer: 'GISSUER',
|
|
822
|
+
amount: '1234.5678901',
|
|
823
|
+
id: 'op-self-swap',
|
|
821
824
|
},
|
|
822
|
-
{ fee_charged:
|
|
823
|
-
)
|
|
824
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
825
|
-
expect(result.map(o => `${o.type}:${o.asset.type}`)).toEqual([
|
|
826
|
-
expect(result.map(o => (o.details as { index: string }).index)).toEqual([
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
])
|
|
830
|
-
expect(result[0]).toMatchObject({ value: 7920000n, senders: [ADDR], recipients: [ADDR] })
|
|
825
|
+
{ fee_charged: '4580' }
|
|
826
|
+
)
|
|
827
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
828
|
+
expect(result.map((o) => `${o.type}:${o.asset.type}`)).toEqual(['OUT:native', 'NONE:token'])
|
|
829
|
+
expect(result.map((o) => (o.details as { index: string }).index)).toEqual([
|
|
830
|
+
'op-self-swap-OUT-NATIVE',
|
|
831
|
+
'op-self-swap-IN-TOKEN-SPDX',
|
|
832
|
+
])
|
|
833
|
+
expect(result[0]).toMatchObject({ value: 7920000n, senders: [ADDR], recipients: [ADDR] })
|
|
831
834
|
expect(result[1]).toMatchObject({
|
|
832
835
|
value: 4580n,
|
|
833
|
-
details: { assetAmount:
|
|
834
|
-
})
|
|
835
|
-
})
|
|
836
|
-
})
|
|
836
|
+
details: { assetAmount: '12345678901' },
|
|
837
|
+
})
|
|
838
|
+
})
|
|
839
|
+
})
|
|
837
840
|
|
|
838
|
-
describe(
|
|
839
|
-
it(
|
|
841
|
+
describe('liquidity_pool_deposit', () => {
|
|
842
|
+
it('emits a native OUT row using reserves_deposited when one of the reserves is native', async () => {
|
|
840
843
|
const op = makeOp(
|
|
841
844
|
{
|
|
842
|
-
type:
|
|
845
|
+
type: 'liquidity_pool_deposit',
|
|
843
846
|
source_account: ADDR,
|
|
844
|
-
liquidity_pool_id:
|
|
847
|
+
liquidity_pool_id: 'pool-id',
|
|
845
848
|
reserves_deposited: [
|
|
846
|
-
{ asset:
|
|
847
|
-
{ asset:
|
|
849
|
+
{ asset: 'native', amount: '10.0000000' },
|
|
850
|
+
{ asset: 'SPDX:GISSUER', amount: '57173830.2052035' },
|
|
848
851
|
],
|
|
849
|
-
shares_received:
|
|
850
|
-
id:
|
|
852
|
+
shares_received: '23909.3448196',
|
|
853
|
+
id: 'op-lp-deposit',
|
|
851
854
|
},
|
|
852
|
-
{ fee_charged:
|
|
853
|
-
)
|
|
854
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
855
|
-
expect(result.map(o => `${o.type}:${o.asset.type}`)).toEqual([
|
|
855
|
+
{ fee_charged: '100' }
|
|
856
|
+
)
|
|
857
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
858
|
+
expect(result.map((o) => `${o.type}:${o.asset.type}`)).toEqual(['OUT:native', 'NONE:token'])
|
|
856
859
|
expect(result[0]).toMatchObject({
|
|
857
|
-
type:
|
|
858
|
-
asset: { type:
|
|
860
|
+
type: 'OUT',
|
|
861
|
+
asset: { type: 'native' },
|
|
859
862
|
value: 100000000n,
|
|
860
863
|
tx: { fees: 100n },
|
|
861
|
-
})
|
|
864
|
+
})
|
|
862
865
|
// Token leg: legacy NONE convention with the deposited token amount on
|
|
863
866
|
// `details.assetAmount`.
|
|
864
867
|
expect(result[1]).toMatchObject({
|
|
865
|
-
type:
|
|
866
|
-
asset: { type:
|
|
868
|
+
type: 'NONE',
|
|
869
|
+
asset: { type: 'token', assetReference: 'SPDX', assetOwner: 'GISSUER' },
|
|
867
870
|
value: 100n,
|
|
868
|
-
details: { assetAmount:
|
|
869
|
-
})
|
|
870
|
-
expect(result.map(o => (o.details as { index: string }).index)).toEqual([
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
])
|
|
874
|
-
})
|
|
875
|
-
|
|
876
|
-
it(
|
|
871
|
+
details: { assetAmount: '571738302052035' },
|
|
872
|
+
})
|
|
873
|
+
expect(result.map((o) => (o.details as { index: string }).index)).toEqual([
|
|
874
|
+
'op-lp-deposit-NATIVE',
|
|
875
|
+
'op-lp-deposit-TOKEN-SPDX',
|
|
876
|
+
])
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
it('does NOT emit a duplicate FEES row when liquidity_pool_deposit is surfaced', async () => {
|
|
877
880
|
const op = makeOp(
|
|
878
881
|
{
|
|
879
|
-
type:
|
|
882
|
+
type: 'liquidity_pool_deposit',
|
|
880
883
|
source_account: ADDR,
|
|
881
|
-
liquidity_pool_id:
|
|
884
|
+
liquidity_pool_id: 'pool-id',
|
|
882
885
|
reserves_deposited: [
|
|
883
|
-
{ asset:
|
|
884
|
-
{ asset:
|
|
886
|
+
{ asset: 'native', amount: '10.0000000' },
|
|
887
|
+
{ asset: 'SPDX:GISSUER', amount: '57173830.2052035' },
|
|
885
888
|
],
|
|
886
889
|
},
|
|
887
|
-
{ fee_account: ADDR, fee_charged:
|
|
888
|
-
)
|
|
889
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
890
|
-
expect(result.map(o => o.type)).toEqual([
|
|
891
|
-
expect(result.find(o => o.type ===
|
|
892
|
-
})
|
|
893
|
-
})
|
|
894
|
-
|
|
895
|
-
describe(
|
|
896
|
-
it(
|
|
890
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
891
|
+
)
|
|
892
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
893
|
+
expect(result.map((o) => o.type)).toEqual(['OUT', 'NONE'])
|
|
894
|
+
expect(result.find((o) => o.type === 'FEES')).toBeUndefined()
|
|
895
|
+
})
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
describe('liquidity_pool_withdraw', () => {
|
|
899
|
+
it('emits a native IN row using reserves_received when one of the reserves is native', async () => {
|
|
897
900
|
const op = makeOp(
|
|
898
901
|
{
|
|
899
|
-
type:
|
|
902
|
+
type: 'liquidity_pool_withdraw',
|
|
900
903
|
source_account: ADDR,
|
|
901
|
-
liquidity_pool_id:
|
|
904
|
+
liquidity_pool_id: 'pool-id',
|
|
902
905
|
reserves_received: [
|
|
903
|
-
{ asset:
|
|
904
|
-
{ asset:
|
|
906
|
+
{ asset: 'native', amount: '26.6614878' },
|
|
907
|
+
{ asset: 'SPDX:GISSUER', amount: '152433937.6498273' },
|
|
905
908
|
],
|
|
906
|
-
shares:
|
|
907
|
-
id:
|
|
909
|
+
shares: '63745.8705215',
|
|
910
|
+
id: 'op-lp-withdraw',
|
|
908
911
|
},
|
|
909
|
-
{ fee_charged:
|
|
910
|
-
)
|
|
911
|
-
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
912
|
-
expect(result.map(o => `${o.type}:${o.asset.type}`)).toEqual([
|
|
912
|
+
{ fee_charged: '100' }
|
|
913
|
+
)
|
|
914
|
+
const result = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
915
|
+
expect(result.map((o) => `${o.type}:${o.asset.type}`)).toEqual(['IN:native', 'NONE:token'])
|
|
913
916
|
expect(result[0]).toMatchObject({
|
|
914
|
-
type:
|
|
915
|
-
asset: { type:
|
|
917
|
+
type: 'IN',
|
|
918
|
+
asset: { type: 'native' },
|
|
916
919
|
value: 266614878n,
|
|
917
920
|
senders: [ADDR],
|
|
918
|
-
})
|
|
919
|
-
})
|
|
920
|
-
})
|
|
921
|
+
})
|
|
922
|
+
})
|
|
923
|
+
})
|
|
921
924
|
|
|
922
|
-
describe(
|
|
923
|
-
it(
|
|
925
|
+
describe('failed transactions', () => {
|
|
926
|
+
it('should set value=0 for IN ops when transaction failed', async () => {
|
|
924
927
|
const op = makeOp(
|
|
925
928
|
{
|
|
926
|
-
type:
|
|
929
|
+
type: 'payment',
|
|
927
930
|
from: OTHER_ADDR,
|
|
928
931
|
to: ADDR,
|
|
929
|
-
amount:
|
|
932
|
+
amount: '10',
|
|
930
933
|
source_account: OTHER_ADDR,
|
|
931
934
|
transaction_successful: false,
|
|
932
935
|
},
|
|
933
|
-
{ fee_charged:
|
|
934
|
-
)
|
|
935
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
936
|
-
expect(out.value).toBe(0n)
|
|
937
|
-
expect(out.tx.failed).toBe(true)
|
|
938
|
-
})
|
|
939
|
-
|
|
940
|
-
it(
|
|
936
|
+
{ fee_charged: '150' }
|
|
937
|
+
)
|
|
938
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
939
|
+
expect(out.value).toBe(0n)
|
|
940
|
+
expect(out.tx.failed).toBe(true)
|
|
941
|
+
})
|
|
942
|
+
|
|
943
|
+
it('should set value=fee_charged for OUT ops when transaction failed', async () => {
|
|
941
944
|
const op = makeOp(
|
|
942
945
|
{
|
|
943
|
-
type:
|
|
946
|
+
type: 'payment',
|
|
944
947
|
from: ADDR,
|
|
945
948
|
to: OTHER_ADDR,
|
|
946
|
-
amount:
|
|
949
|
+
amount: '10',
|
|
947
950
|
transaction_successful: false,
|
|
948
951
|
},
|
|
949
|
-
{ fee_charged:
|
|
950
|
-
)
|
|
951
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
952
|
-
expect(out.value).toBe(150n)
|
|
953
|
-
expect(out.tx.failed).toBe(true)
|
|
954
|
-
})
|
|
955
|
-
})
|
|
956
|
-
|
|
957
|
-
describe(
|
|
958
|
-
it(
|
|
952
|
+
{ fee_charged: '150' }
|
|
953
|
+
)
|
|
954
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
955
|
+
expect(out.value).toBe(150n)
|
|
956
|
+
expect(out.tx.failed).toBe(true)
|
|
957
|
+
})
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
describe('recipients', () => {
|
|
961
|
+
it('should use to_muxed for payment when present', async () => {
|
|
959
962
|
const op = makeOp({
|
|
960
|
-
type:
|
|
963
|
+
type: 'payment',
|
|
961
964
|
from: ADDR,
|
|
962
965
|
to: OTHER_ADDR,
|
|
963
|
-
to_muxed:
|
|
964
|
-
amount:
|
|
965
|
-
})
|
|
966
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
967
|
-
expect(out.recipients).toEqual([
|
|
968
|
-
})
|
|
969
|
-
|
|
970
|
-
it(
|
|
971
|
-
const op = makeOp({ type:
|
|
972
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
973
|
-
expect(out.recipients).toEqual([OTHER_ADDR])
|
|
974
|
-
})
|
|
975
|
-
|
|
976
|
-
it(
|
|
966
|
+
to_muxed: 'MMUXED',
|
|
967
|
+
amount: '10',
|
|
968
|
+
})
|
|
969
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
970
|
+
expect(out.recipients).toEqual(['MMUXED'])
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
it('should fall back to `to` for payment when no to_muxed is set', async () => {
|
|
974
|
+
const op = makeOp({ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '10' })
|
|
975
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
976
|
+
expect(out.recipients).toEqual([OTHER_ADDR])
|
|
977
|
+
})
|
|
978
|
+
|
|
979
|
+
it('should use `account` for create_account recipients', async () => {
|
|
977
980
|
const op = makeOp({
|
|
978
|
-
type:
|
|
981
|
+
type: 'create_account',
|
|
979
982
|
funder: ADDR,
|
|
980
983
|
account: OTHER_ADDR,
|
|
981
|
-
starting_balance:
|
|
982
|
-
})
|
|
983
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
984
|
-
expect(out.recipients).toEqual([OTHER_ADDR])
|
|
985
|
-
})
|
|
984
|
+
starting_balance: '10',
|
|
985
|
+
})
|
|
986
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
987
|
+
expect(out.recipients).toEqual([OTHER_ADDR])
|
|
988
|
+
})
|
|
986
989
|
|
|
987
|
-
it(
|
|
990
|
+
it('should use `to` for path_payment_strict_send recipients', async () => {
|
|
988
991
|
const op = makeOp({
|
|
989
|
-
type:
|
|
992
|
+
type: 'path_payment_strict_send',
|
|
990
993
|
from: ADDR,
|
|
991
994
|
to: OTHER_ADDR,
|
|
992
|
-
amount:
|
|
993
|
-
})
|
|
994
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
995
|
-
expect(out.recipients).toEqual([OTHER_ADDR])
|
|
996
|
-
})
|
|
995
|
+
amount: '10',
|
|
996
|
+
})
|
|
997
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
998
|
+
expect(out.recipients).toEqual([OTHER_ADDR])
|
|
999
|
+
})
|
|
997
1000
|
|
|
998
|
-
it(
|
|
1001
|
+
it('should return empty recipients for change_trust', async () => {
|
|
999
1002
|
const op = makeOp({
|
|
1000
|
-
type:
|
|
1003
|
+
type: 'change_trust',
|
|
1001
1004
|
trustor: ADDR,
|
|
1002
|
-
limit:
|
|
1003
|
-
asset_code:
|
|
1004
|
-
asset_issuer:
|
|
1005
|
-
})
|
|
1006
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1007
|
-
expect(out.recipients).toEqual([])
|
|
1008
|
-
})
|
|
1009
|
-
|
|
1010
|
-
it(
|
|
1005
|
+
limit: '1000',
|
|
1006
|
+
asset_code: 'USDC',
|
|
1007
|
+
asset_issuer: 'GISSUER',
|
|
1008
|
+
})
|
|
1009
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1010
|
+
expect(out.recipients).toEqual([])
|
|
1011
|
+
})
|
|
1012
|
+
|
|
1013
|
+
it('should return empty recipients for path_payment_strict_receive', async () => {
|
|
1011
1014
|
const op = makeOp({
|
|
1012
|
-
type:
|
|
1015
|
+
type: 'path_payment_strict_receive',
|
|
1013
1016
|
from: OTHER_ADDR,
|
|
1014
1017
|
to: ADDR,
|
|
1015
|
-
amount:
|
|
1018
|
+
amount: '10',
|
|
1016
1019
|
source_account: OTHER_ADDR,
|
|
1017
|
-
})
|
|
1018
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1019
|
-
expect(out.recipients).toEqual([ADDR])
|
|
1020
|
-
})
|
|
1021
|
-
})
|
|
1022
|
-
|
|
1023
|
-
describe(
|
|
1024
|
-
it(
|
|
1020
|
+
})
|
|
1021
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1022
|
+
expect(out.recipients).toEqual([ADDR])
|
|
1023
|
+
})
|
|
1024
|
+
})
|
|
1025
|
+
|
|
1026
|
+
describe('sequence handling', () => {
|
|
1027
|
+
it('should expose source_account_sequence as a string when it is a number', async () => {
|
|
1025
1028
|
const op = makeOp(
|
|
1026
|
-
{ type:
|
|
1027
|
-
{ source_account_sequence:
|
|
1028
|
-
)
|
|
1029
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1030
|
-
expect(out.details).toMatchObject({ sequence:
|
|
1031
|
-
})
|
|
1032
|
-
|
|
1033
|
-
it(
|
|
1029
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
1030
|
+
{ source_account_sequence: '123456789' }
|
|
1031
|
+
)
|
|
1032
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1033
|
+
expect(out.details).toMatchObject({ sequence: '123456789' })
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
it('should expose sequence as undefined when source_account_sequence is not numeric', async () => {
|
|
1034
1037
|
const op = makeOp(
|
|
1035
|
-
{ type:
|
|
1036
|
-
{ source_account_sequence:
|
|
1037
|
-
)
|
|
1038
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1039
|
-
expect((out.details as { sequence?: string }).sequence).toBeUndefined()
|
|
1040
|
-
})
|
|
1041
|
-
})
|
|
1042
|
-
|
|
1043
|
-
describe(
|
|
1044
|
-
it(
|
|
1038
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
1039
|
+
{ source_account_sequence: 'not-a-number' }
|
|
1040
|
+
)
|
|
1041
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1042
|
+
expect((out.details as { sequence?: string }).sequence).toBeUndefined()
|
|
1043
|
+
})
|
|
1044
|
+
})
|
|
1045
|
+
|
|
1046
|
+
describe('transaction metadata', () => {
|
|
1047
|
+
it('should expose fee_account as feesPayer when present (fee bump transactions)', async () => {
|
|
1045
1048
|
const op = makeOp(
|
|
1046
|
-
{ type:
|
|
1047
|
-
{ fee_account:
|
|
1048
|
-
)
|
|
1049
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1050
|
-
expect(out.tx.feesPayer).toBe(
|
|
1051
|
-
expect(out.tx.fees).toBe(500n)
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1054
|
-
it(
|
|
1055
|
-
const op = makeOp({ type:
|
|
1056
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1057
|
-
expect(
|
|
1058
|
-
})
|
|
1059
|
-
|
|
1060
|
-
it(
|
|
1049
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
1050
|
+
{ fee_account: 'GSPONSOR', fee_charged: '500' }
|
|
1051
|
+
)
|
|
1052
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1053
|
+
expect(out.tx.feesPayer).toBe('GSPONSOR')
|
|
1054
|
+
expect(out.tx.fees).toBe(500n)
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
it('should omit feesPayer when fee_account is not set', async () => {
|
|
1058
|
+
const op = makeOp({ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' })
|
|
1059
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1060
|
+
expect('feesPayer' in out.tx).toBe(false)
|
|
1061
|
+
})
|
|
1062
|
+
|
|
1063
|
+
it('should build the operation id as `${accountId}-${transactionHash}-${type}`', async () => {
|
|
1061
1064
|
const op = makeOp({
|
|
1062
|
-
type:
|
|
1065
|
+
type: 'payment',
|
|
1063
1066
|
from: ADDR,
|
|
1064
1067
|
to: OTHER_ADDR,
|
|
1065
|
-
amount:
|
|
1066
|
-
transaction_hash:
|
|
1067
|
-
})
|
|
1068
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1069
|
-
expect(out.id).toBe(`${ACCOUNT_ID}-my-tx-hash-OUT`)
|
|
1070
|
-
})
|
|
1071
|
-
|
|
1072
|
-
it(
|
|
1068
|
+
amount: '1',
|
|
1069
|
+
transaction_hash: 'my-tx-hash',
|
|
1070
|
+
})
|
|
1071
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1072
|
+
expect(out.id).toBe(`${ACCOUNT_ID}-my-tx-hash-OUT`)
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
it('should populate block metadata from the ledger record', async () => {
|
|
1073
1076
|
const op = makeOp(
|
|
1074
|
-
{ type:
|
|
1077
|
+
{ type: 'payment', from: ADDR, to: OTHER_ADDR, amount: '1' },
|
|
1075
1078
|
{
|
|
1076
1079
|
ledger_attr: 4242,
|
|
1077
|
-
ledger_hash:
|
|
1078
|
-
ledger_closed_at:
|
|
1079
|
-
}
|
|
1080
|
-
)
|
|
1081
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1080
|
+
ledger_hash: 'ledger-hash',
|
|
1081
|
+
ledger_closed_at: '2025-07-04T08:30:00Z',
|
|
1082
|
+
}
|
|
1083
|
+
)
|
|
1084
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1082
1085
|
expect(out.tx.block).toEqual({
|
|
1083
|
-
hash:
|
|
1086
|
+
hash: 'ledger-hash',
|
|
1084
1087
|
height: 4242,
|
|
1085
|
-
time: new Date(
|
|
1086
|
-
})
|
|
1087
|
-
})
|
|
1088
|
+
time: new Date('2025-07-04T08:30:00Z'),
|
|
1089
|
+
})
|
|
1090
|
+
})
|
|
1088
1091
|
|
|
1089
1092
|
it("should set tx.date from the operation's created_at", async () => {
|
|
1090
1093
|
const op = makeOp({
|
|
1091
|
-
type:
|
|
1094
|
+
type: 'payment',
|
|
1092
1095
|
from: ADDR,
|
|
1093
1096
|
to: OTHER_ADDR,
|
|
1094
|
-
amount:
|
|
1095
|
-
created_at:
|
|
1096
|
-
})
|
|
1097
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1098
|
-
expect(out.tx.date).toEqual(new Date(
|
|
1099
|
-
})
|
|
1100
|
-
|
|
1101
|
-
it(
|
|
1097
|
+
amount: '1',
|
|
1098
|
+
created_at: '2024-02-29T01:02:03Z',
|
|
1099
|
+
})
|
|
1100
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1101
|
+
expect(out.tx.date).toEqual(new Date('2024-02-29T01:02:03Z'))
|
|
1102
|
+
})
|
|
1103
|
+
|
|
1104
|
+
it('should include senders from the operation source_account', async () => {
|
|
1102
1105
|
const op = makeOp({
|
|
1103
|
-
type:
|
|
1106
|
+
type: 'payment',
|
|
1104
1107
|
from: ADDR,
|
|
1105
1108
|
to: OTHER_ADDR,
|
|
1106
|
-
amount:
|
|
1107
|
-
source_account:
|
|
1108
|
-
})
|
|
1109
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1110
|
-
expect(out.senders).toEqual([
|
|
1111
|
-
})
|
|
1112
|
-
|
|
1113
|
-
it(
|
|
1109
|
+
amount: '1',
|
|
1110
|
+
source_account: 'GSOURCE',
|
|
1111
|
+
})
|
|
1112
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1113
|
+
expect(out.senders).toEqual(['GSOURCE'])
|
|
1114
|
+
})
|
|
1115
|
+
|
|
1116
|
+
it('should expose pagingToken and index in details', async () => {
|
|
1114
1117
|
const op = makeOp({
|
|
1115
|
-
type:
|
|
1118
|
+
type: 'payment',
|
|
1116
1119
|
from: ADDR,
|
|
1117
1120
|
to: OTHER_ADDR,
|
|
1118
|
-
amount:
|
|
1119
|
-
id:
|
|
1120
|
-
paging_token:
|
|
1121
|
-
})
|
|
1122
|
-
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1123
|
-
expect(out.details).toMatchObject({ pagingToken:
|
|
1124
|
-
})
|
|
1125
|
-
})
|
|
1126
|
-
|
|
1127
|
-
describe(
|
|
1128
|
-
it(
|
|
1121
|
+
amount: '1',
|
|
1122
|
+
id: 'op-42',
|
|
1123
|
+
paging_token: 'pt-42',
|
|
1124
|
+
})
|
|
1125
|
+
const [out] = await rawOperationsToOperations([op], ADDR, ACCOUNT_ID, 0)
|
|
1126
|
+
expect(out.details).toMatchObject({ pagingToken: 'pt-42', index: 'op-42' })
|
|
1127
|
+
})
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
describe('batch processing', () => {
|
|
1131
|
+
it('should preserve relative order of the operations that pass filters', async () => {
|
|
1129
1132
|
const opA = makeOp({
|
|
1130
|
-
type:
|
|
1133
|
+
type: 'payment',
|
|
1131
1134
|
from: ADDR,
|
|
1132
1135
|
to: OTHER_ADDR,
|
|
1133
|
-
amount:
|
|
1134
|
-
transaction_hash:
|
|
1135
|
-
})
|
|
1136
|
+
amount: '1',
|
|
1137
|
+
transaction_hash: 'tx-a',
|
|
1138
|
+
})
|
|
1136
1139
|
// Unrelated to the listed address: not in any filter field and not the
|
|
1137
1140
|
// fee payer, so it must be dropped entirely (no fee row).
|
|
1138
1141
|
const opIgnored = makeOp(
|
|
1139
1142
|
{
|
|
1140
|
-
type:
|
|
1143
|
+
type: 'manage_offer',
|
|
1141
1144
|
source_account: OTHER_ADDR,
|
|
1142
|
-
transaction_hash:
|
|
1145
|
+
transaction_hash: 'tx-ignored',
|
|
1143
1146
|
},
|
|
1144
|
-
{ fee_account: OTHER_ADDR }
|
|
1145
|
-
)
|
|
1147
|
+
{ fee_account: OTHER_ADDR }
|
|
1148
|
+
)
|
|
1146
1149
|
const opB = makeOp({
|
|
1147
|
-
type:
|
|
1150
|
+
type: 'payment',
|
|
1148
1151
|
from: OTHER_ADDR,
|
|
1149
1152
|
to: ADDR,
|
|
1150
|
-
amount:
|
|
1153
|
+
amount: '2',
|
|
1151
1154
|
source_account: OTHER_ADDR,
|
|
1152
|
-
transaction_hash:
|
|
1153
|
-
})
|
|
1155
|
+
transaction_hash: 'tx-b',
|
|
1156
|
+
})
|
|
1154
1157
|
|
|
1155
|
-
const result = await rawOperationsToOperations([opA, opIgnored, opB], ADDR, ACCOUNT_ID, 0)
|
|
1156
|
-
expect(result.map(o => o.tx.hash)).toEqual([
|
|
1157
|
-
})
|
|
1158
|
+
const result = await rawOperationsToOperations([opA, opIgnored, opB], ADDR, ACCOUNT_ID, 0)
|
|
1159
|
+
expect(result.map((o) => o.tx.hash)).toEqual(['tx-a', 'tx-b'])
|
|
1160
|
+
})
|
|
1158
1161
|
|
|
1159
|
-
it(
|
|
1162
|
+
it('should interleave fee-only ops at the position of their first raw op in the page', async () => {
|
|
1160
1163
|
const opA = makeOp({
|
|
1161
|
-
type:
|
|
1164
|
+
type: 'payment',
|
|
1162
1165
|
from: ADDR,
|
|
1163
1166
|
to: OTHER_ADDR,
|
|
1164
|
-
amount:
|
|
1165
|
-
transaction_hash:
|
|
1166
|
-
})
|
|
1167
|
+
amount: '1',
|
|
1168
|
+
transaction_hash: 'tx-a',
|
|
1169
|
+
})
|
|
1167
1170
|
const opFeeOnly = makeOp(
|
|
1168
1171
|
{
|
|
1169
|
-
type:
|
|
1172
|
+
type: 'set_options',
|
|
1170
1173
|
source_account: ADDR,
|
|
1171
|
-
transaction_hash:
|
|
1174
|
+
transaction_hash: 'tx-fees',
|
|
1172
1175
|
},
|
|
1173
|
-
{ fee_account: ADDR, fee_charged:
|
|
1174
|
-
)
|
|
1176
|
+
{ fee_account: ADDR, fee_charged: '100' }
|
|
1177
|
+
)
|
|
1175
1178
|
const opB = makeOp({
|
|
1176
|
-
type:
|
|
1179
|
+
type: 'payment',
|
|
1177
1180
|
from: OTHER_ADDR,
|
|
1178
1181
|
to: ADDR,
|
|
1179
|
-
amount:
|
|
1182
|
+
amount: '2',
|
|
1180
1183
|
source_account: OTHER_ADDR,
|
|
1181
|
-
transaction_hash:
|
|
1182
|
-
})
|
|
1183
|
-
|
|
1184
|
-
const result = await rawOperationsToOperations([opA, opFeeOnly, opB], ADDR, ACCOUNT_ID, 0)
|
|
1185
|
-
expect(result.map(o => `${o.tx.hash}:${o.type}`)).toEqual([
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
])
|
|
1190
|
-
})
|
|
1191
|
-
|
|
1192
|
-
it(
|
|
1193
|
-
expect(await rawOperationsToOperations([], ADDR, ACCOUNT_ID, 0)).toEqual([])
|
|
1194
|
-
})
|
|
1195
|
-
})
|
|
1196
|
-
})
|
|
1184
|
+
transaction_hash: 'tx-b',
|
|
1185
|
+
})
|
|
1186
|
+
|
|
1187
|
+
const result = await rawOperationsToOperations([opA, opFeeOnly, opB], ADDR, ACCOUNT_ID, 0)
|
|
1188
|
+
expect(result.map((o) => `${o.tx.hash}:${o.type}`)).toEqual([
|
|
1189
|
+
'tx-a:OUT',
|
|
1190
|
+
'tx-fees:FEES',
|
|
1191
|
+
'tx-b:IN',
|
|
1192
|
+
])
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
it('should return an empty array when given no operations', async () => {
|
|
1196
|
+
expect(await rawOperationsToOperations([], ADDR, ACCOUNT_ID, 0)).toEqual([])
|
|
1197
|
+
})
|
|
1198
|
+
})
|
|
1199
|
+
})
|