@xyo-network/chain-services 1.7.7 → 1.7.9

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json.schemastore.org/package.json",
3
3
  "name": "@xyo-network/chain-services",
4
- "version": "1.7.7",
4
+ "version": "1.7.9",
5
5
  "description": "XYO Layer One SDK Services",
6
6
  "homepage": "https://xylabs.com",
7
7
  "bugs": {
@@ -23,62 +23,64 @@
23
23
  "exports": {
24
24
  ".": {
25
25
  "types": "./dist/neutral/index.d.ts",
26
+ "source": "./src/index.ts",
26
27
  "default": "./dist/neutral/index.mjs"
27
28
  },
28
29
  "./package.json": "./package.json"
29
30
  },
30
31
  "module": "./dist/neutral/index.mjs",
32
+ "source": "./src/index.ts",
31
33
  "types": "./dist/neutral/index.d.ts",
32
- "scripts": {
33
- "deploy": "echo Deploy not allowed!",
34
- "deploy3": "echo Deploy3 not allowed!"
35
- },
34
+ "files": [
35
+ "dist",
36
+ "src"
37
+ ],
36
38
  "dependencies": {
37
39
  "@opentelemetry/api": "^1.9.0",
38
- "@xylabs/array": "^4.13.19",
39
- "@xylabs/assert": "^4.13.19",
40
- "@xylabs/creatable": "^4.13.19",
41
- "@xylabs/decimal-precision": "^4.13.19",
42
- "@xylabs/events": "^4.13.19",
43
- "@xylabs/exists": "^4.13.19",
44
- "@xylabs/forget": "^4.13.19",
45
- "@xylabs/hex": "^4.13.19",
46
- "@xylabs/promise": "^4.13.19",
47
- "@xylabs/telemetry": "^4.13.19",
48
- "@xylabs/typeof": "^4.13.19",
49
- "@xyo-network/account-model": "^4.1.4",
50
- "@xyo-network/archivist-memory": "^4.1.4",
51
- "@xyo-network/archivist-model": "^4.1.4",
52
- "@xyo-network/boundwitness-model": "^4.1.4",
53
- "@xyo-network/boundwitness-validator": "^4.1.4",
54
- "@xyo-network/boundwitness-wrapper": "^4.1.4",
55
- "@xyo-network/chain-analyze": "^1.7.7",
56
- "@xyo-network/chain-ethereum": "^1.7.7",
57
- "@xyo-network/chain-modules": "^1.7.7",
58
- "@xyo-network/chain-protocol": "^1.7.7",
59
- "@xyo-network/chain-utils": "^1.7.7",
60
- "@xyo-network/chain-validation": "^1.7.7",
61
- "@xyo-network/chain-wrappers": "^1.7.7",
62
- "@xyo-network/payload-builder": "^4.1.4",
63
- "@xyo-network/payload-model": "^4.1.4",
40
+ "@xylabs/array": "^4.13.21",
41
+ "@xylabs/assert": "^4.13.21",
42
+ "@xylabs/creatable": "^4.13.21",
43
+ "@xylabs/decimal-precision": "^4.13.21",
44
+ "@xylabs/events": "^4.13.21",
45
+ "@xylabs/exists": "^4.13.21",
46
+ "@xylabs/forget": "^4.13.21",
47
+ "@xylabs/hex": "^4.13.21",
48
+ "@xylabs/promise": "^4.13.21",
49
+ "@xylabs/telemetry": "^4.13.21",
50
+ "@xylabs/typeof": "^4.13.21",
51
+ "@xyo-network/account-model": "^4.1.6",
52
+ "@xyo-network/archivist-memory": "^4.1.6",
53
+ "@xyo-network/archivist-model": "^4.1.6",
54
+ "@xyo-network/boundwitness-model": "^4.1.6",
55
+ "@xyo-network/boundwitness-validator": "^4.1.6",
56
+ "@xyo-network/boundwitness-wrapper": "^4.1.6",
57
+ "@xyo-network/chain-analyze": "^1.7.9",
58
+ "@xyo-network/chain-ethereum": "^1.7.9",
59
+ "@xyo-network/chain-modules": "^1.7.9",
60
+ "@xyo-network/chain-protocol": "^1.7.9",
61
+ "@xyo-network/chain-utils": "^1.7.9",
62
+ "@xyo-network/chain-validation": "^1.7.9",
63
+ "@xyo-network/chain-wrappers": "^1.7.9",
64
+ "@xyo-network/payload-builder": "^4.1.6",
65
+ "@xyo-network/payload-model": "^4.1.6",
64
66
  "@xyo-network/typechain": "^3.5.4",
65
- "@xyo-network/xl1-protocol": "^1.7.5",
66
- "@xyo-network/xl1-protocol-sdk": "^1.7.7",
67
+ "@xyo-network/xl1-protocol": "^1.7.10",
68
+ "@xyo-network/xl1-protocol-sdk": "^1.7.9",
67
69
  "async-mutex": "^0.5.0",
68
70
  "ethers": "6.15.0",
69
71
  "lru-cache": "^11.1.0"
70
72
  },
71
73
  "devDependencies": {
72
- "@types/node": "^24.0.14",
73
- "@xylabs/delay": "^4.13.19",
74
+ "@types/node": "^24.0.15",
75
+ "@xylabs/delay": "^4.13.21",
74
76
  "@xylabs/ts-scripts-yarn3": "^7.0.0",
75
77
  "@xylabs/tsconfig": "^7.0.0",
76
- "@xylabs/vitest-extended": "^4.13.19",
77
- "@xyo-network/account": "^4.1.4",
78
- "@xyo-network/account-model": "^4.1.4",
79
- "@xyo-network/chain-validation": "^1.7.7",
80
- "@xyo-network/wallet": "^4.1.4",
81
- "knip": "^5.61.3",
78
+ "@xylabs/vitest-extended": "^4.13.21",
79
+ "@xyo-network/account": "^4.1.6",
80
+ "@xyo-network/account-model": "^4.1.6",
81
+ "@xyo-network/chain-validation": "^1.7.9",
82
+ "@xyo-network/wallet": "^4.1.6",
83
+ "knip": "^5.62.0",
82
84
  "typescript": "^5.8.3",
83
85
  "vitest": "^3.2.4",
84
86
  "vitest-mock-extended": "^3.1.0",
@@ -215,7 +215,7 @@ export class BaseBlockProducerService extends BaseService<BaseBlockProducerServi
215
215
 
216
216
  // Build the block
217
217
  const block = await buildNextBlock(head, fundedNextBlockTransactions, blockPayloads, [this.account])
218
- this.logger?.warn(`buildBlock: ${block[0].block} with ${block[1].length} payloads`)
218
+ this.logger?.info(`buildBlock: ${block[0].block} with ${block[1].length} payloads`)
219
219
  const errors = await this.validateHydratedBlockState(block, this.chainId, { accountBalance: this.balanceService })
220
220
  if (errors.length > 0) {
221
221
  this.logger?.warn(`Validation of produced block failed: ${errors.at(0)?.message}`)
@@ -0,0 +1,320 @@
1
+ import '@xylabs/vitest-extended'
2
+
3
+ import { filterAs } from '@xylabs/array'
4
+ import { assertEx } from '@xylabs/assert'
5
+ import { delay } from '@xylabs/delay'
6
+ import type { Address } from '@xylabs/hex'
7
+ import { asAddress, ZERO_HASH } from '@xylabs/hex'
8
+ import { Account } from '@xyo-network/account'
9
+ import type { AccountInstance } from '@xyo-network/account-model'
10
+ import { MemoryArchivist } from '@xyo-network/archivist-memory'
11
+ import type { ArchivistInstance } from '@xyo-network/archivist-model'
12
+ import { buildRandomChain, buildRandomTransaction } from '@xyo-network/chain-protocol'
13
+ import { validateHydratedBlockState } from '@xyo-network/chain-validation'
14
+ import {
15
+ type PayloadBundle, PayloadBundleSchema, type WithStorageMeta,
16
+ } from '@xyo-network/payload-model'
17
+ import { HDWallet } from '@xyo-network/wallet'
18
+ import type {
19
+ BlockBoundWitness, BlockRewardService, ElectionService,
20
+ HydratedBlock,
21
+ HydratedBlockStateValidationFunctionV2,
22
+ StakeIntentService,
23
+ } from '@xyo-network/xl1-protocol'
24
+ import {
25
+ asBlockBoundWitness,
26
+ asChainStakeIntent,
27
+ asTransactionBoundWitness,
28
+ HydratedBlockStateValidationError,
29
+ } from '@xyo-network/xl1-protocol'
30
+ import type { Config } from '@xyo-network/xl1-protocol-sdk'
31
+ import {
32
+ flattenHydratedBlock, flattenHydratedTransaction, getDefaultConfig,
33
+ } from '@xyo-network/xl1-protocol-sdk'
34
+ import {
35
+ beforeAll, beforeEach, describe, expect, it,
36
+ } from 'vitest'
37
+ import { mock } from 'vitest-mock-extended'
38
+
39
+ import { accountBalanceServiceFromArchivist } from '../../AccountBalance/index.ts'
40
+ import { MemoryBlockRewardService } from '../../BlockReward/index.ts'
41
+ import type { BasePendingTransactionsServiceParams } from '../../PendingTransactions/index.ts'
42
+ import { BasePendingTransactionsService } from '../../PendingTransactions/index.ts'
43
+ import type { BaseBlockProducerServiceParams } from '../BaseBlockProducerService.ts'
44
+ import { BaseBlockProducerService } from '../BaseBlockProducerService.ts'
45
+
46
+ describe('XyoBlockProducer', () => {
47
+ const leaderCount = 3
48
+ let account: AccountInstance
49
+ let blockProducer: BaseBlockProducerService
50
+ let chainArchivist: ArchivistInstance
51
+ let electionService: ReturnType<typeof mock<ElectionService>>
52
+ let pendingBundledTransactionsArchivist: ArchivistInstance
53
+ let pendingTransactionsService: BasePendingTransactionsService
54
+ let rejectedTransactionsArchivist: ArchivistInstance
55
+ let rewardAddress: Address = '1111111111111111111111111111111111111111'
56
+ let rewardService: BlockRewardService
57
+ let stakeIntentService: ReturnType<typeof mock<StakeIntentService>>
58
+ let transactionAccount: AccountInstance
59
+ const validPendingTransactions = Math.ceil(BaseBlockProducerService.DefaultBlockSize)
60
+ let config: Config
61
+
62
+ let currentBlock: WithStorageMeta<BlockBoundWitness>
63
+
64
+ const chainId = assertEx(asAddress('Be17531fec6fEc55f3EAc3f1c187c87e4C47F81E'))
65
+
66
+ beforeAll(async () => {
67
+ account = await Account.random()
68
+ transactionAccount = await HDWallet.fromPhrase('room maximum palace fragile man pyramid school indoor base business want bronze assume marble report')
69
+ })
70
+
71
+ beforeEach(async () => {
72
+ config = getDefaultConfig()
73
+ config.producer.disableIntentRedeclaration = false
74
+ chainArchivist = await MemoryArchivist.create({ account: 'random' })
75
+ rejectedTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
76
+ electionService = mock<ElectionService>()
77
+ pendingBundledTransactionsArchivist = await MemoryArchivist.create({ account: 'random' })
78
+ const BasePendingTransactionsServiceParams: BasePendingTransactionsServiceParams = {
79
+ name: 'TestBasePendingTransactionsServiceParams',
80
+ chainArchivist: chainArchivist,
81
+ chainId,
82
+ config,
83
+ pendingBundledTransactionsArchivist,
84
+ rejectedTransactionsArchivist,
85
+ }
86
+ pendingTransactionsService = await BasePendingTransactionsService.create(BasePendingTransactionsServiceParams)
87
+ await addPendingTransactions(validPendingTransactions)
88
+
89
+ stakeIntentService = mock<StakeIntentService>()
90
+ stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([])
91
+ rewardService = await MemoryBlockRewardService.create()
92
+ const balanceService = await accountBalanceServiceFromArchivist(chainArchivist)
93
+ const params: BaseBlockProducerServiceParams = {
94
+ name: 'TestXyoBlockProducerParams',
95
+ account,
96
+ balanceService,
97
+ chainArchivist,
98
+ chainId,
99
+ config,
100
+ electionService,
101
+ pendingTransactionsService,
102
+ pendingBundledTransactionsArchivist,
103
+ rejectedTransactionsArchivist,
104
+ rewardAddress,
105
+ rewardService,
106
+ stakeIntentService,
107
+ validateHydratedBlockState,
108
+ }
109
+ blockProducer = await BaseBlockProducerService.create(params)
110
+ })
111
+
112
+ const addPendingTransactions = async (count: number = validPendingTransactions) => {
113
+ for (let i = 0; i < count; i++) {
114
+ const transaction = await buildRandomTransaction(chainId, [], transactionAccount)
115
+ const bundledPayload: PayloadBundle = {
116
+ schema: PayloadBundleSchema, payloads: flattenHydratedTransaction(transaction), root: transaction[0]._hash,
117
+ }
118
+ await pendingBundledTransactionsArchivist.insert([bundledPayload])
119
+ }
120
+ }
121
+
122
+ describe('next', () => {
123
+ describe('block production', () => {
124
+ describe('when not current block leader', () => {
125
+ beforeEach(async () => {
126
+ const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
127
+ currentBlock = hydratedBlock[0]
128
+ await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
129
+ const leaders = await Promise.all(Array.from({ length: leaderCount }, async () => (await Account.random()).address))
130
+ electionService.getCreatorCommitteeForNextBlock.mockResolvedValue(leaders)
131
+ stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([])
132
+ })
133
+ it('should return undefined', async () => {
134
+ // Arrange
135
+
136
+ // Act
137
+ const result = await blockProducer.next(currentBlock)
138
+
139
+ // Assert
140
+ expect(result).toBeUndefined()
141
+ })
142
+ })
143
+ describe('when current block leader', () => {
144
+ beforeEach(async () => {
145
+ const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
146
+ currentBlock = hydratedBlock[0]
147
+ await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
148
+ await delay(1000)
149
+ electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
150
+ })
151
+ it('should return a valid block', async () => {
152
+ // Arrange
153
+
154
+ // Act
155
+ const result = await blockProducer.next(currentBlock)
156
+
157
+ // Assert
158
+ expect(result).toBeDefined()
159
+ expect(result).toBeArrayOfSize(2)
160
+ const [block, transactionsAndData] = result ?? []
161
+ expect(block).toBeDefined()
162
+ expect(asBlockBoundWitness(block)).toBeDefined()
163
+ expect(transactionsAndData).toBeDefined()
164
+ expect(transactionsAndData).toBeArray()
165
+ const transactions = filterAs(assertEx(transactionsAndData), asTransactionBoundWitness)
166
+ expect(transactions).toBeArrayOfSize(validPendingTransactions)
167
+ })
168
+ it('should remove invalid transactions', async () => {
169
+ // Arrange
170
+ let rejectBlock = true
171
+ const validateHydratedBlockState: HydratedBlockStateValidationFunctionV2 = async (
172
+ hydratedBlock: HydratedBlock,
173
+ chainId: Address,
174
+ ) => {
175
+ return rejectBlock ? [await Promise.resolve(new HydratedBlockStateValidationError(ZERO_HASH, chainId, hydratedBlock, 'Invalid block'))] : []
176
+ }
177
+ const balanceService = await accountBalanceServiceFromArchivist(chainArchivist)
178
+ const params: BaseBlockProducerServiceParams = {
179
+ name: 'TestXyoBlockProducerParams',
180
+ account,
181
+ balanceService,
182
+ chainArchivist,
183
+ chainId,
184
+ config,
185
+ electionService,
186
+ pendingBundledTransactionsArchivist,
187
+ pendingTransactionsService,
188
+ rejectedTransactionsArchivist,
189
+ rewardAddress,
190
+ rewardService,
191
+ stakeIntentService,
192
+ validateHydratedBlockState,
193
+ }
194
+ blockProducer = await BaseBlockProducerService.create(params)
195
+
196
+ // Act
197
+ // Force producer to reject first block
198
+ rejectBlock = true
199
+ const invalidTransactionCount = Math.floor(validPendingTransactions / 2)
200
+ await addPendingTransactions(invalidTransactionCount)
201
+
202
+ // Ensure bad block is not produced
203
+ const firstResult = await blockProducer.next(currentBlock)
204
+ expect(firstResult).toBeUndefined()
205
+ // Do not reject second block
206
+ rejectBlock = false
207
+ // Add more pending transactions
208
+ const newTransactionCount = Math.floor(validPendingTransactions / 2)
209
+ await addPendingTransactions(newTransactionCount)
210
+ // Ensure producer recovers from bad block
211
+ const result = await blockProducer.next(currentBlock)
212
+
213
+ // Assert
214
+ expect(result).toBeDefined()
215
+ expect(result).toBeArrayOfSize(2)
216
+ const [block, transactionsAndData] = result ?? []
217
+ expect(block).toBeDefined()
218
+ expect(asBlockBoundWitness(block)).toBeDefined()
219
+ expect(transactionsAndData).toBeDefined()
220
+ expect(transactionsAndData).toBeArray()
221
+ const transactions = filterAs(assertEx(transactionsAndData), asTransactionBoundWitness)
222
+ expect(transactions).toBeArrayOfSize(newTransactionCount)
223
+ })
224
+ })
225
+ })
226
+ describe('producer re-declare intent', () => {
227
+ describe('when within re-declare intent window', () => {
228
+ beforeEach(async () => {
229
+ const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
230
+ currentBlock = hydratedBlock[0]
231
+ await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
232
+ electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
233
+ stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([[0, 10]])
234
+ })
235
+ it('should re-declare intent if configured for re-declaration', async () => {
236
+ // Arrange
237
+ config.producer.disableIntentRedeclaration = false
238
+
239
+ // Act
240
+ const result = await blockProducer.next(currentBlock)
241
+
242
+ // Assert
243
+ expect(result).toBeDefined()
244
+ expect(result).toBeArrayOfSize(2)
245
+ const [block, payloads] = result ?? []
246
+ expect(block).toBeDefined()
247
+ expect(asBlockBoundWitness(block)).toBeDefined()
248
+ expect(payloads).toBeArray()
249
+ const allData = flattenHydratedBlock(assertEx(result))
250
+ const declaration = filterAs(allData, asChainStakeIntent).at(0)
251
+ expect(declaration).toBeDefined()
252
+ expect(declaration?.from).toEqual(blockProducer.address)
253
+ })
254
+ it('should not re-declare intent if not configured for re-declaration', async () => {
255
+ // Arrange
256
+ config.producer.disableIntentRedeclaration = true
257
+
258
+ // Act
259
+ const result = await blockProducer.next(currentBlock)
260
+
261
+ // Assert
262
+ expect(result).toBeDefined()
263
+ expect(result).toBeArrayOfSize(2)
264
+ const [block, payloads] = result ?? []
265
+ expect(block).toBeDefined()
266
+ expect(asBlockBoundWitness(block)).toBeDefined()
267
+ expect(payloads).toBeArray()
268
+ const allData = flattenHydratedBlock(assertEx(result))
269
+ const declaration = filterAs(allData, asChainStakeIntent).at(0)
270
+ expect(declaration).toBeUndefined()
271
+ })
272
+ })
273
+ describe('when not within re-declare intent window', () => {
274
+ beforeEach(async () => {
275
+ const [hydratedBlock] = await buildRandomChain(account, 1, undefined, chainId, transactionAccount)
276
+ currentBlock = hydratedBlock[0]
277
+ await chainArchivist.insert(flattenHydratedBlock(hydratedBlock))
278
+ electionService.getCreatorCommitteeForNextBlock.mockResolvedValue([account.address])
279
+ stakeIntentService.getDeclaredCandidateRanges.mockResolvedValue([[0, 1_000_000_000]])
280
+ })
281
+ it('should not re-declare intent if configured for re-declaration', async () => {
282
+ // Arrange
283
+ config.producer.disableIntentRedeclaration = false
284
+
285
+ // Act
286
+ const result = await blockProducer.next(currentBlock)
287
+
288
+ // Assert
289
+ expect(result).toBeDefined()
290
+ expect(result).toBeArrayOfSize(2)
291
+ const [block, payloads] = result ?? []
292
+ expect(block).toBeDefined()
293
+ expect(asBlockBoundWitness(block)).toBeDefined()
294
+ expect(payloads).toBeArray()
295
+ const allData = flattenHydratedBlock(assertEx(result))
296
+ const declaration = filterAs(allData, asChainStakeIntent).at(0)
297
+ expect(declaration).toBeUndefined()
298
+ })
299
+ it('should not re-declare intent if not configured for re-declaration', async () => {
300
+ // Arrange
301
+ config.producer.disableIntentRedeclaration = true
302
+
303
+ // Act
304
+ const result = await blockProducer.next(currentBlock)
305
+
306
+ // Assert
307
+ expect(result).toBeDefined()
308
+ expect(result).toBeArrayOfSize(2)
309
+ const [block, payloads] = result ?? []
310
+ expect(block).toBeDefined()
311
+ expect(asBlockBoundWitness(block)).toBeDefined()
312
+ expect(payloads).toBeArray()
313
+ const allData = flattenHydratedBlock(assertEx(result))
314
+ const declaration = filterAs(allData, asChainStakeIntent).at(0)
315
+ expect(declaration).toBeUndefined()
316
+ })
317
+ })
318
+ })
319
+ })
320
+ })
@@ -0,0 +1,72 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import {
3
+ asAddress, hexToBigInt, toHex,
4
+ } from '@xylabs/hex'
5
+ import { isDefined } from '@xylabs/typeof'
6
+ import { XYO_ZERO_ADDRESS } from '@xyo-network/chain-utils'
7
+ import type { WithStorageMeta } from '@xyo-network/payload-model'
8
+ import {
9
+ defaultTransactionFees, type HydratedTransaction, type Transfer,
10
+ } from '@xyo-network/xl1-protocol'
11
+ import {
12
+ describe, expect, it,
13
+ } from 'vitest'
14
+
15
+ import { generateTransactionFeeTransfers } from '../generateTransactionFeeTransfers.ts'
16
+
17
+ describe('generateTransactionTransfers', () => {
18
+ const testCases: HydratedTransaction[][] = [
19
+ [
20
+ [
21
+ {
22
+ schema: 'network.xyo.boundwitness',
23
+ addresses: ['39d22bed37f77bee0c056f648bde6efa489981c3'],
24
+ payload_hashes: ['65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8'],
25
+ payload_schemas: ['network.xyo.transfer'],
26
+ previous_hashes: [null],
27
+ $signatures: [
28
+ '0605878a92045bb752cc12066b585589b513cee652bf4bc36c19289495ccd3d3751a1202b5bfb334325c4c6a59a4a7dad7468739a8b4ffe07b6091317b16dc09',
29
+ ],
30
+ nbf: 2835,
31
+ exp: 3835,
32
+ fees: {
33
+ base: toHex(defaultTransactionFees.base),
34
+ gasLimit: toHex(defaultTransactionFees.gasLimit),
35
+ gasPrice: toHex(defaultTransactionFees.gasPrice),
36
+ priority: '00',
37
+ },
38
+ chain: 'a82920051db4fcbb804463440dd45e03f72442fd',
39
+ script: ['elevate|65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8'],
40
+ from: '39d22bed37f77bee0c056f648bde6efa489981c3',
41
+ _dataHash: '47be77303edd6073f82e9fff119107f1e173a08e7285e8e5f6e0c979d43e5b7d',
42
+ _hash: '1419062ddb266ff58515c4ffbc435ec482b6129d9e0c3c707fb7610c67215281',
43
+ _sequence: '00000196f46014770000000167215281',
44
+ },
45
+ [
46
+ {
47
+ schema: 'network.xyo.transfer',
48
+ epoch: 1_747_856_659_420,
49
+ from: '39d22bed37f77bee0c056f648bde6efa489981c3',
50
+ transfers: { db5df9c99e661500c95f2c933023ba693e5c1ae9: '056bc75e2d63100000' },
51
+ _dataHash: '65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8',
52
+ _hash: '65c64a5b2a2bf5aebc71255b64b36cc570dcd0d0539238757469d3772aef69c8',
53
+ _sequence: '00000196f4601477000000002aef69c8',
54
+ } as WithStorageMeta<Transfer>,
55
+ ],
56
+ ],
57
+ ],
58
+ ]
59
+ it.each(testCases)('should generate correct Transfer payloads from hydrated transactions', async (...transactions) => {
60
+ const producerAddress = assertEx(asAddress('3e86dac7546202156c4620d3cf36fb0ac30404a3'))
61
+
62
+ // Patch the transactionRequiredGas function to return our test gas amount
63
+
64
+ const result = await generateTransactionFeeTransfers(producerAddress, transactions)
65
+
66
+ const burnValueHex = result.at(0)?.transfers[XYO_ZERO_ADDRESS]
67
+ expect(burnValueHex).toBeDefined()
68
+ const burnValue = isDefined(burnValueHex) ? hexToBigInt(burnValueHex) : 0n
69
+ expect(burnValue).toBeGreaterThan(0n)
70
+ expect(burnValue).toEqual(1_000_000_000_000n)
71
+ })
72
+ })