@xyo-network/chain-sdk 1.7.8 → 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-sdk",
4
- "version": "1.7.8",
4
+ "version": "1.7.9",
5
5
  "description": "XYO Layer One SDK",
6
6
  "homepage": "https://xylabs.com",
7
7
  "bugs": {
@@ -24,21 +24,29 @@
24
24
  ".": {
25
25
  "browser": {
26
26
  "types": "./dist/browser/index-browser.d.ts",
27
+ "source": "./src/index-browser.ts",
27
28
  "default": "./dist/browser/index-browser.mjs"
28
29
  },
29
30
  "node": {
30
31
  "types": "./dist/node/index-node.d.ts",
32
+ "source": "./src/index-node.ts",
31
33
  "default": "./dist/node/index-node.mjs"
32
34
  },
33
35
  "neutral": {
34
36
  "types": "./dist/neutral/index-shared.d.ts",
37
+ "source": "./src/index-shared.ts",
35
38
  "default": "./dist/neutral/index-shared.mjs"
36
39
  }
37
40
  },
38
41
  "./package.json": "./package.json"
39
42
  },
40
43
  "module": "./dist/neutral/index-shared.mjs",
44
+ "source": "./src/index-shared.ts",
41
45
  "types": "./dist/neutral/index-shared.d.ts",
46
+ "files": [
47
+ "dist",
48
+ "src"
49
+ ],
42
50
  "workspaces": [
43
51
  "packages/*"
44
52
  ],
@@ -52,36 +60,36 @@
52
60
  "@xylabs/assert": "^4.13.21",
53
61
  "@xylabs/delay": "^4.13.21",
54
62
  "@xylabs/hex": "^4.13.21",
55
- "@xyo-network/account": "^4.1.5",
56
- "@xyo-network/account-model": "^4.1.5",
57
- "@xyo-network/chain-ethereum": "^1.7.8",
58
- "@xyo-network/chain-modules": "^1.7.8",
59
- "@xyo-network/chain-orchestration": "^1.7.8",
60
- "@xyo-network/chain-protocol": "^1.7.8",
61
- "@xyo-network/chain-services": "^1.7.8",
62
- "@xyo-network/chain-utils": "^1.7.8",
63
- "@xyo-network/chain-validation": "^1.7.8",
64
- "@xyo-network/payload-builder": "^4.1.5",
63
+ "@xyo-network/account": "^4.1.6",
64
+ "@xyo-network/account-model": "^4.1.6",
65
+ "@xyo-network/chain-ethereum": "^1.7.9",
66
+ "@xyo-network/chain-modules": "^1.7.9",
67
+ "@xyo-network/chain-orchestration": "^1.7.9",
68
+ "@xyo-network/chain-protocol": "^1.7.9",
69
+ "@xyo-network/chain-services": "^1.7.9",
70
+ "@xyo-network/chain-utils": "^1.7.9",
71
+ "@xyo-network/chain-validation": "^1.7.9",
72
+ "@xyo-network/payload-builder": "^4.1.6",
65
73
  "@xyo-network/typechain": "^3.5.4",
66
- "@xyo-network/wallet-model": "^4.1.5",
67
- "@xyo-network/xl1-protocol": "^1.7.5",
68
- "@xyo-network/xl1-protocol-sdk": "^1.7.8",
74
+ "@xyo-network/wallet-model": "^4.1.6",
75
+ "@xyo-network/xl1-protocol": "^1.7.10",
76
+ "@xyo-network/xl1-protocol-sdk": "^1.7.9",
69
77
  "ethers": "6.15.0"
70
78
  },
71
79
  "devDependencies": {
72
- "@types/node": "^24.0.14",
80
+ "@types/node": "^24.0.15",
73
81
  "@xylabs/decimal-precision": "^4.13.21",
74
82
  "@xylabs/delay": "^4.13.21",
75
83
  "@xylabs/ts-scripts-yarn3": "^7.0.0",
76
84
  "@xylabs/tsconfig": "^7.0.0",
77
- "@xyo-network/account": "^4.1.5",
78
- "@xyo-network/account-model": "^4.1.5",
79
- "@xyo-network/archivist-memory": "^4.1.5",
80
- "@xyo-network/archivist-model": "^4.1.5",
81
- "@xyo-network/boundwitness-builder": "^4.1.5",
82
- "@xyo-network/chain-analyze": "^1.7.8",
83
- "@xyo-network/payload-model": "^4.1.5",
84
- "@xyo-network/wallet": "^4.1.5",
85
+ "@xyo-network/account": "^4.1.6",
86
+ "@xyo-network/account-model": "^4.1.6",
87
+ "@xyo-network/archivist-memory": "^4.1.6",
88
+ "@xyo-network/archivist-model": "^4.1.6",
89
+ "@xyo-network/boundwitness-builder": "^4.1.6",
90
+ "@xyo-network/chain-analyze": "^1.7.9",
91
+ "@xyo-network/payload-model": "^4.1.6",
92
+ "@xyo-network/wallet": "^4.1.6",
85
93
  "eslint": "^9.31.0",
86
94
  "knip": "^5.62.0",
87
95
  "typescript": "^5.8.3",
@@ -0,0 +1,61 @@
1
+ import { Account } from '@xyo-network/account'
2
+ import type { AccountInstance } from '@xyo-network/account-model'
3
+ import { MemoryArchivist } from '@xyo-network/archivist-memory'
4
+ import { ChainHeadSelector } from '@xyo-network/chain-analyze'
5
+ import { buildRandomChain } from '@xyo-network/chain-protocol'
6
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
7
+ import type { HydratedBlock } from '@xyo-network/xl1-protocol'
8
+ import { flattenHydratedBlock } from '@xyo-network/xl1-protocol-sdk'
9
+ import {
10
+ beforeEach,
11
+ describe, expect, it,
12
+ } from 'vitest'
13
+
14
+ describe('ChainHeadSelector', () => {
15
+ let SampleBlockChain: HydratedBlock[]
16
+ let SampleGenesisOnlyBlockChain: HydratedBlock[]
17
+ let producer: AccountInstance
18
+ let archivist: MemoryArchivist
19
+ beforeEach (async () => {
20
+ producer = await Account.random()
21
+ archivist = await MemoryArchivist.create({ account: 'random' })
22
+ SampleBlockChain = (await buildRandomChain(producer, 10))
23
+ SampleGenesisOnlyBlockChain = await buildRandomChain(producer, 1)
24
+ })
25
+ it('no producers', async () => {
26
+ expect(SampleBlockChain.length).toEqual(10)
27
+ await archivist.insert(SampleBlockChain.flatMap(block => flattenHydratedBlock(block)))
28
+ const selector = new ChainHeadSelector({ chainArchivist: archivist })
29
+ const bestHead = await selector.findBestHead()
30
+ expect(bestHead).toBeDefined()
31
+ expect(bestHead?.previous).toBeDefined()
32
+ })
33
+ it('only one producer', async () => {
34
+ await archivist.insert(SampleBlockChain.flatMap(block => flattenHydratedBlock(block)))
35
+ const selector = new ChainHeadSelector({ chainArchivist: archivist, activeProducers: [producer.address] })
36
+ const bestHead = await selector.findBestHead()
37
+ expect(bestHead).toBeDefined()
38
+ expect(bestHead?.previous === null).toBeFalsy()
39
+ })
40
+ it('only genesis block', async () => {
41
+ await archivist.insert(SampleGenesisOnlyBlockChain.flatMap(block => flattenHydratedBlock(block)))
42
+ const selector = new ChainHeadSelector({ chainArchivist: archivist, activeProducers: [producer.address] })
43
+ const bestHead = await selector.findBestHead()
44
+ expect(bestHead).toBeDefined()
45
+ expect(bestHead?.previous).toBeNull()
46
+ })
47
+ it('with finalization', async () => {
48
+ await archivist.insert(SampleBlockChain.flatMap(block => flattenHydratedBlock(block)))
49
+ const selector = new ChainHeadSelector({ chainArchivist: archivist, activeProducers: [producer.address] })
50
+ const bestHead = await selector.findBestHead()
51
+ if (bestHead) {
52
+ await selector.finalizeBlock(await PayloadBuilder.hash(bestHead))
53
+ }
54
+ const bestHead2 = await selector.findBestHead()
55
+ expect(bestHead).toBeDefined()
56
+ expect(bestHead2).toBeDefined()
57
+ if (bestHead && bestHead2) {
58
+ expect(await PayloadBuilder.hash(bestHead)).toBe(await PayloadBuilder.hash(bestHead2))
59
+ }
60
+ })
61
+ })
@@ -0,0 +1,180 @@
1
+ import type { ChildProcess } from 'node:child_process'
2
+
3
+ import { assertEx } from '@xylabs/assert'
4
+ import { toFixedPoint } from '@xylabs/decimal-precision'
5
+ import { delay } from '@xylabs/delay'
6
+ import { Account } from '@xyo-network/account'
7
+ import {
8
+ createChainContract, createChainRewardsContract, getDefaultGasConfig,
9
+ } from '@xyo-network/chain-ethereum'
10
+ import type { EvmBlockRewardServiceParams } from '@xyo-network/chain-services'
11
+ import { EvmBlockRewardService, EvmChainService } from '@xyo-network/chain-services'
12
+ import { HDWallet } from '@xyo-network/wallet'
13
+ import type { WalletInstance } from '@xyo-network/wallet-model'
14
+ import { getDefaultConfig } from '@xyo-network/xl1-protocol-sdk'
15
+ import type { ContractRunner } from 'ethers/providers'
16
+ import { JsonRpcProvider } from 'ethers/providers'
17
+ import { Wallet } from 'ethers/wallet'
18
+ import {
19
+ afterAll,
20
+ beforeAll,
21
+ describe, expect, it,
22
+ } from 'vitest'
23
+
24
+ import { startGanache, stopGanache } from '../../test/index.ts'
25
+
26
+ const ganachePort = 8565
27
+ const testPhrase = 'edit hill neutral usage share kite knee abandon slim open lottery abandon'
28
+
29
+ // eslint-disable-next-line max-statements
30
+ describe('EvmBlockRewardService', () => {
31
+ const genesisReward = toFixedPoint(20_000_000_000n)
32
+ const initialStepReward = toFixedPoint(1000n)
33
+ const stepSize = 1_000_000n
34
+ const minRewardPerBlock = toFixedPoint(30n)
35
+ const config = getDefaultConfig()
36
+
37
+ let service: EvmBlockRewardService
38
+ let accountPerson: WalletInstance
39
+ let accountPerson2: WalletInstance
40
+ let walletPerson: WalletInstance
41
+ let ganacheProcess: ChildProcess
42
+ let ethWalletPerson: Wallet
43
+ let ethWalletPerson2: Wallet
44
+ let provider: JsonRpcProvider
45
+ let provider2: JsonRpcProvider
46
+
47
+ beforeAll(async () => {
48
+ ganacheProcess = await startGanache(ganachePort, testPhrase)
49
+ walletPerson = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/0'/0")
50
+ accountPerson = await walletPerson.derivePath("m/44'/60'/0'/0/0")
51
+ accountPerson2 = await walletPerson.derivePath("m/44'/60'/0'/0/1")
52
+ provider = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
53
+ ethWalletPerson = new Wallet(accountPerson.privateKey, provider)
54
+ provider2 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
55
+ ethWalletPerson2 = new Wallet(accountPerson2.privateKey, provider2)
56
+ const rewardsContract = await createChainRewardsContract(ethWalletPerson, genesisReward, getDefaultGasConfig())
57
+ const tokenAddress = '0x01'
58
+ const genesisBlockHash = '0x01'
59
+ const stakeContract = await createChainContract(ethWalletPerson2, tokenAddress, genesisBlockHash, rewardsContract, 1n, getDefaultGasConfig())
60
+ const id = stakeContract
61
+ const account = await Account.random()
62
+ const privateKey = assertEx(account.private?.hex, () => 'Error: Account does not have a private key')
63
+ const runner: ContractRunner = new Wallet(privateKey, provider)
64
+
65
+ const chainService = await EvmChainService.create({ id, runner })
66
+ const params: EvmBlockRewardServiceParams = {
67
+ account,
68
+ chainService,
69
+ config,
70
+ name: 'EvmBlockRewardService',
71
+ provider,
72
+ }
73
+ service = await EvmBlockRewardService.create(params)
74
+ }, 40_000)
75
+
76
+ afterAll(async () => {
77
+ await delay(1000)
78
+ stopGanache(ganacheProcess)
79
+ }, 20_000)
80
+
81
+ it('blocks 0 and 1', async () => {
82
+ expect(await service.getRewardForBlock(0n)).toBe(genesisReward)
83
+ expect(await service.getRewardForBlock(10n)).toBe(initialStepReward)
84
+ })
85
+
86
+ it('last block in step 1', async () => {
87
+ expect(await service.getRewardForBlock(stepSize - 1n)).toBe(initialStepReward)
88
+ })
89
+
90
+ it('first blocks in step 2', async () => {
91
+ expect(await service.getRewardForBlock(stepSize)).toBe(950_000_000_000_000_000_000n)
92
+ })
93
+
94
+ it('last blocks in step 2', async () => {
95
+ expect(await service.getRewardForBlock(stepSize * 2n - 1n)).toBe(950_000_000_000_000_000_000n)
96
+ })
97
+
98
+ it('first blocks in step 3', async () => {
99
+ expect(await service.getRewardForBlock(stepSize * 2n)).toBe(902_000_000_000_000_000_000n)
100
+ })
101
+
102
+ it('first blocks in step 5', async () => {
103
+ expect(await service.getRewardForBlock(stepSize * 4n)).toBe(813_000_000_000_000_000_000n)
104
+ expect(await service.getRewardForBlock(stepSize * 4n + 1n)).toBe(813_000_000_000_000_000_000n)
105
+ })
106
+
107
+ it('last blocks in step 5', async () => {
108
+ expect(await service.getRewardForBlock(stepSize * 5n - 1n)).toBe(813_000_000_000_000_000_000n)
109
+ })
110
+
111
+ it('first blocks in step 10', async () => {
112
+ expect(await service.getRewardForBlock(stepSize * 9n)).toBe(627_000_000_000_000_000_000n)
113
+ expect(await service.getRewardForBlock(stepSize * 9n + 1n)).toBe(627_000_000_000_000_000_000n)
114
+ })
115
+
116
+ it('last blocks in step 10', async () => {
117
+ expect(await service.getRewardForBlock(stepSize * 10n - 1n)).toBe(627_000_000_000_000_000_000n)
118
+ })
119
+
120
+ it('first blocks in step 20', async () => {
121
+ expect(await service.getRewardForBlock(stepSize * 19n)).toBe(372_000_000_000_000_000_000n)
122
+ expect(await service.getRewardForBlock(stepSize * 19n + 1n)).toBe(372_000_000_000_000_000_000n)
123
+ })
124
+
125
+ it('last blocks in step 20', async () => {
126
+ expect(await service.getRewardForBlock(stepSize * 20n - 1n)).toBe(372_000_000_000_000_000_000n)
127
+ })
128
+
129
+ it('first blocks in step 30', async () => {
130
+ expect(await service.getRewardForBlock(stepSize * 29n)).toBe(219_000_000_000_000_000_000n)
131
+ expect(await service.getRewardForBlock(stepSize * 29n + 1n)).toBe(219_000_000_000_000_000_000n)
132
+ })
133
+
134
+ it('last blocks in step 30', async () => {
135
+ expect(await service.getRewardForBlock(stepSize * 30n - 1n)).toBe(219_000_000_000_000_000_000n)
136
+ })
137
+
138
+ it('first blocks in step 40', async () => {
139
+ expect(await service.getRewardForBlock(stepSize * 39n)).toBe(128_000_000_000_000_000_000n)
140
+ expect(await service.getRewardForBlock(stepSize * 39n + 1n)).toBe(128_000_000_000_000_000_000n)
141
+ })
142
+
143
+ it('last blocks in step 40', async () => {
144
+ expect(await service.getRewardForBlock(stepSize * 40n - 1n)).toBe(128_000_000_000_000_000_000n)
145
+ })
146
+
147
+ it('first blocks in step 44', async () => {
148
+ expect(await service.getRewardForBlock(stepSize * 43n)).toBe(102_000_000_000_000_000_000n)
149
+ expect(await service.getRewardForBlock(stepSize * 43n + 1n)).toBe(102_000_000_000_000_000_000n)
150
+ })
151
+
152
+ it('last blocks in step 44', async () => {
153
+ expect(await service.getRewardForBlock(stepSize * 44n - 1n)).toBe(102_000_000_000_000_000_000n)
154
+ })
155
+
156
+ it('first blocks in step 64', async () => {
157
+ expect(await service.getRewardForBlock(stepSize * 63n)).toBe(31_000_000_000_000_000_000n)
158
+ expect(await service.getRewardForBlock(stepSize * 63n + 1n)).toBe(31_000_000_000_000_000_000n)
159
+ })
160
+
161
+ it('first blocks in step 68', async () => {
162
+ expect(await service.getRewardForBlock(stepSize * 64n)).toBe(minRewardPerBlock)
163
+ expect(await service.getRewardForBlock(stepSize * 64n + 1n)).toBe(minRewardPerBlock)
164
+ })
165
+
166
+ it('first blocks in step 80', async () => {
167
+ expect(await service.getRewardForBlock(stepSize * 80n)).toBe(minRewardPerBlock)
168
+ expect(await service.getRewardForBlock(stepSize * 80n + 1n)).toBe(minRewardPerBlock)
169
+ })
170
+
171
+ it('first blocks in step 200', async () => {
172
+ expect(await service.getRewardForBlock(stepSize * 200n)).toBe(minRewardPerBlock)
173
+ expect(await service.getRewardForBlock(stepSize * 200n + 1n)).toBe(minRewardPerBlock)
174
+ })
175
+
176
+ it('first blocks in step 2000', async () => {
177
+ expect(await service.getRewardForBlock(stepSize * 2000n)).toBe(minRewardPerBlock)
178
+ expect(await service.getRewardForBlock(stepSize * 2000n + 1n)).toBe(minRewardPerBlock)
179
+ })
180
+ })
@@ -0,0 +1,131 @@
1
+ import type { ChildProcess } from 'node:child_process'
2
+
3
+ import { assertEx } from '@xylabs/assert'
4
+ import { delay } from '@xylabs/delay'
5
+ import { asAddress } from '@xylabs/hex'
6
+ import type { AccountInstance } from '@xyo-network/account-model'
7
+ import { createChain, toEthAddress } from '@xyo-network/chain-ethereum'
8
+ import { EvmChainService } from '@xyo-network/chain-services'
9
+ import { XYO_ZERO_ADDRESS } from '@xyo-network/chain-utils'
10
+ import type { BurnableErc20 } from '@xyo-network/typechain'
11
+ import { BurnableErc20__factory as Erc20Factory, StakedXyoChain__factory as StakedXyoChainFactory } from '@xyo-network/typechain'
12
+ import { HDWallet } from '@xyo-network/wallet'
13
+ import type { WalletInstance } from '@xyo-network/wallet-model'
14
+ import type { ContractRunner } from 'ethers/providers'
15
+ import { JsonRpcProvider } from 'ethers/providers'
16
+ import { parseUnits } from 'ethers/utils'
17
+ import { Wallet } from 'ethers/wallet'
18
+ import {
19
+ afterAll, beforeAll, describe, expect, it,
20
+ } from 'vitest'
21
+
22
+ import { startGanache, stopGanache } from '../../test/index.ts'
23
+
24
+ const ganachePort = 8545
25
+ const testPhrase = 'edit hill neutral usage share kite knee abandon slim open lottery abandon'
26
+
27
+ const gasConfig = () => {
28
+ return {
29
+ gasLimit: 2_000_000, // Set the gas limit
30
+ gasPrice: parseUnits('100', 'gwei'),
31
+ }
32
+ }
33
+
34
+ describe('EvmContractChainStake', () => {
35
+ let account0: WalletInstance
36
+ let account1: WalletInstance
37
+ let walletPerson: WalletInstance
38
+ let initialProducerAccount: AccountInstance
39
+ let ganacheProcess: ChildProcess
40
+ let ethWalletPerson0: Wallet
41
+ let ethWalletPerson1: Wallet
42
+ let provider0: JsonRpcProvider
43
+ let provider1: JsonRpcProvider
44
+
45
+ beforeAll(async () => {
46
+ ganacheProcess = await startGanache(ganachePort, testPhrase)
47
+ walletPerson = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/0'/0")
48
+ initialProducerAccount = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/1'/0")
49
+ account0 = await walletPerson.derivePath("m/44'/60'/0'/0/0")
50
+ account1 = await walletPerson.derivePath("m/44'/60'/0'/0/1")
51
+ provider0 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
52
+ ethWalletPerson0 = new Wallet(account0.privateKey, provider0)
53
+ provider1 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
54
+ ethWalletPerson1 = new Wallet(account1.privateKey, provider1)
55
+ }, 20_000)
56
+
57
+ afterAll(async () => {
58
+ await delay(1000)
59
+ stopGanache(ganacheProcess)
60
+ }, 20_000)
61
+
62
+ describe.sequential('Create Fresh Chain', () => {
63
+ let erc20Contract: BurnableErc20
64
+ let erc20Address: string
65
+ let erc20ContractPerson0: BurnableErc20
66
+ let xyoChainContractAddress: string
67
+ let erc20ContractPerson1: BurnableErc20
68
+
69
+ it('Create Erc20', async () => {
70
+ const erc20 = new Erc20Factory(ethWalletPerson0)
71
+ erc20Contract = await (await erc20.deploy('Test Token', 'TST', parseUnits('1000000', 18), gasConfig())).waitForDeployment()
72
+ erc20Address = await erc20Contract.getAddress()
73
+ expect(erc20Address).toBeDefined()
74
+ erc20ContractPerson0 = Erc20Factory.connect(erc20Address, ethWalletPerson0)
75
+ const balance = await erc20ContractPerson0.balanceOf(ethWalletPerson0.address)
76
+ expect(balance).toBe(parseUnits('1000000', 18))
77
+ })
78
+
79
+ it('Transfer Tokens', async () => {
80
+ const transfer = await (await erc20ContractPerson0.transfer(ethWalletPerson1.address, parseUnits('1000', 18), gasConfig())).wait()
81
+ expect(transfer).toBeDefined()
82
+ erc20ContractPerson1 = Erc20Factory.connect(erc20Address, ethWalletPerson1)
83
+ const balance = await erc20ContractPerson1.balanceOf(ethWalletPerson1.address)
84
+ expect(balance).toBe(parseUnits('1000', 18))
85
+ })
86
+
87
+ it('Create Chain Contract', async () => {
88
+ // Create Chain Contract
89
+ const result = await (createChain(
90
+ ethWalletPerson0, // signer
91
+ toEthAddress(erc20Address), // stakingTokenAddress
92
+ initialProducerAccount, // initialProducer
93
+ null,
94
+ 0n,
95
+ XYO_ZERO_ADDRESS,
96
+ 50_000n,
97
+ gasConfig(),
98
+ ))
99
+
100
+ xyoChainContractAddress = toEthAddress(result[0])
101
+
102
+ const xyoChainPerson0 = StakedXyoChainFactory.connect(xyoChainContractAddress)
103
+ expect(xyoChainPerson0).toBeDefined()
104
+ const xyoChainPerson1 = StakedXyoChainFactory.connect(xyoChainContractAddress, ethWalletPerson1)
105
+ const xyoChainPerson1Address = await xyoChainPerson1.getAddress()
106
+ expect(xyoChainPerson1Address).toBeDefined()
107
+ }, 1_000_000)
108
+
109
+ it('Approve Stake Chain Address', async () => {
110
+ const approveResultTx = await (await erc20ContractPerson1.approve(xyoChainContractAddress, parseUnits('100', 18), gasConfig())).wait()
111
+ expect(approveResultTx).toBeDefined()
112
+ })
113
+
114
+ it('Stake Chain Contract', async () => {
115
+ const address = assertEx(asAddress(xyoChainContractAddress))
116
+ const xyoChainPerson1 = await EvmChainService.create({
117
+ id: address,
118
+ runner: ethWalletPerson1 as ContractRunner,
119
+ })
120
+ const stakeResult = await xyoChainPerson1.addStake(await ethWalletPerson1.getAddress(), parseUnits('50', 18))
121
+ expect(stakeResult).toBeTruthy()
122
+ })
123
+
124
+ it('Read Stake', async () => {
125
+ const address = assertEx(asAddress(xyoChainContractAddress))
126
+ const xyoChainPerson0 = await EvmChainService.create({ id: address, runner: ethWalletPerson0 as ContractRunner })
127
+ const stakeByAddressResult = await xyoChainPerson0.activeByAddressStaked(await ethWalletPerson1.getAddress())
128
+ expect(stakeByAddressResult).toBe(parseUnits('50', 18))
129
+ })
130
+ }, 10_000)
131
+ }, 10_000)
@@ -0,0 +1,77 @@
1
+ import { toFixedPoint } from '@xylabs/decimal-precision'
2
+ import { MemoryBlockRewardService } from '@xyo-network/chain-services'
3
+ import {
4
+ describe, expect, it,
5
+ } from 'vitest'
6
+
7
+ describe('XyoBlockRewardService', async () => {
8
+ const creatorReward = toFixedPoint(20_000_000_000n)
9
+ const initialStepReward = toFixedPoint(3000n)
10
+ const stepSize = 1_000_000n
11
+ const minRewardPerBlock = toFixedPoint(30n)
12
+ const service = await MemoryBlockRewardService.create({
13
+ initialStepReward, stepSize, creatorReward, minRewardPerBlock,
14
+ })
15
+
16
+ it('blocks 0 and 1', async () => {
17
+ expect(await service.getRewardForBlock(0n)).toBe(creatorReward)
18
+ expect(await service.getRewardForBlock(10n)).toBe(initialStepReward)
19
+ })
20
+
21
+ it('last block in step 1', async () => {
22
+ expect(await service.getRewardForBlock(stepSize - 1n)).toBe(initialStepReward)
23
+ })
24
+
25
+ it('first blocks in step 2', async () => {
26
+ expect(await service.getRewardForBlock(stepSize)).toBe(2_700_000_000_000_000_000_000n)
27
+ })
28
+
29
+ it('last blocks in step 2', async () => {
30
+ expect(await service.getRewardForBlock(stepSize * 2n - 1n)).toBe(2_700_000_000_000_000_000_000n)
31
+ })
32
+
33
+ it('first blocks in step 3', async () => {
34
+ expect(await service.getRewardForBlock(stepSize * 2n)).toBe(2_430_000_000_000_000_000_000n)
35
+ })
36
+
37
+ it('first blocks in step 5', async () => {
38
+ expect(await service.getRewardForBlock(stepSize * 4n)).toBe(1_968_300_000_000_000_000_000n)
39
+ expect(await service.getRewardForBlock(stepSize * 4n + 1n)).toBe(1_968_300_000_000_000_000_000n)
40
+ })
41
+
42
+ it('last blocks in step 5', async () => {
43
+ expect(await service.getRewardForBlock(stepSize * 5n - 1n)).toBe(1_968_300_000_000_000_000_000n)
44
+ })
45
+
46
+ it('first blocks in step 10', async () => {
47
+ expect(await service.getRewardForBlock(stepSize * 9n)).toBe(1_162_261_467_000_000_000_000n)
48
+ expect(await service.getRewardForBlock(stepSize * 9n + 1n)).toBe(1_162_261_467_000_000_000_000n)
49
+ })
50
+
51
+ it('last blocks in step 10', async () => {
52
+ expect(await service.getRewardForBlock(stepSize * 10n - 1n)).toBe(1_162_261_467_000_000_000_000n)
53
+ })
54
+
55
+ it('first blocks in step 40', async () => {
56
+ expect(await service.getRewardForBlock(stepSize * 39n)).toBe(49_269_609_804_781_974_438n)
57
+ expect(await service.getRewardForBlock(stepSize * 39n + 1n)).toBe(49_269_609_804_781_974_438n)
58
+ })
59
+
60
+ it('last blocks in step 40', async () => {
61
+ expect(await service.getRewardForBlock(stepSize * 40n - 1n)).toBe(49_269_609_804_781_974_438n)
62
+ })
63
+
64
+ it('first blocks in step 44', async () => {
65
+ expect(await service.getRewardForBlock(stepSize * 43n)).toBe(32_325_790_992_917_453_429n)
66
+ expect(await service.getRewardForBlock(stepSize * 43n + 1n)).toBe(32_325_790_992_917_453_429n)
67
+ })
68
+
69
+ it('last blocks in step 44', async () => {
70
+ expect(await service.getRewardForBlock(stepSize * 44n - 1n)).toBe(32_325_790_992_917_453_429n)
71
+ })
72
+
73
+ it('first blocks in step 54', async () => {
74
+ expect(await service.getRewardForBlock(stepSize * 53n)).toBe(minRewardPerBlock)
75
+ expect(await service.getRewardForBlock(stepSize * 53n + 1n)).toBe(minRewardPerBlock)
76
+ })
77
+ })
@@ -0,0 +1,98 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { toFixedPoint } from '@xylabs/decimal-precision'
3
+ import type { Address, Hash } from '@xylabs/hex'
4
+ import { toHex } from '@xylabs/hex'
5
+ import { Account } from '@xyo-network/account'
6
+ import type { AccountInstance } from '@xyo-network/account-model'
7
+ import { MemoryArchivist } from '@xyo-network/archivist-memory'
8
+ import type { ArchivistInstance } from '@xyo-network/archivist-model'
9
+ import { BoundWitnessBuilder } from '@xyo-network/boundwitness-builder'
10
+ import {
11
+ analyzeChain, BalanceAnalyzer, isChainSummaryBalances,
12
+ } from '@xyo-network/chain-analyze'
13
+ import {
14
+ buildGenesisBlock, buildNextBlock, buildRandomTransaction,
15
+ } from '@xyo-network/chain-protocol'
16
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
17
+ import type { Payload, WithStorageMeta } from '@xyo-network/payload-model'
18
+ import type { BlockBoundWitness, Transfer } from '@xyo-network/xl1-protocol'
19
+ import { TransferSchema } from '@xyo-network/xl1-protocol'
20
+ import { flattenHydratedBlock } from '@xyo-network/xl1-protocol-sdk'
21
+ import {
22
+ beforeAll,
23
+ describe, expect, it,
24
+ } from 'vitest'
25
+
26
+ const balancesFromHead = async (head: Hash, archivist: ArchivistInstance) => {
27
+ const { balances } = assertEx((await analyzeChain(
28
+ archivist,
29
+ [new BalanceAnalyzer()],
30
+ head,
31
+ )).find(isChainSummaryBalances), () => 'ChainSummaryBalances not found')
32
+ return balances
33
+ }
34
+
35
+ describe.sequential('Send XL1', () => {
36
+ let archivist: ArchivistInstance
37
+ let chainId: Address
38
+ let previousBlock: WithStorageMeta<BlockBoundWitness>
39
+ let txAccount1: AccountInstance
40
+ let txAccount2: AccountInstance
41
+ let blockAccount1: AccountInstance
42
+ let blockAccount2: AccountInstance
43
+ beforeAll(async () => {
44
+ // make a fake chainId using a random address
45
+ chainId = (await Account.random()).address
46
+ archivist = await MemoryArchivist.create({ account: 'random' })
47
+ })
48
+ it('Get Initial Balances', async () => {
49
+ blockAccount1 = await Account.random()
50
+ txAccount1 = await Account.random()
51
+ const firstHydratedTx = await buildRandomTransaction(chainId, undefined, txAccount1)
52
+ const firstBlock = await buildGenesisBlock(chainId, [firstHydratedTx], [], [blockAccount1], blockAccount1.address, toFixedPoint(3000n))
53
+ expect(firstBlock[0].block).toEqual(0)
54
+ await archivist.insert(flattenHydratedBlock(firstBlock))
55
+ previousBlock = firstBlock[0]
56
+ const balances = await balancesFromHead(previousBlock._hash, archivist)
57
+ expect(balances[blockAccount1.address]).toEqual({ positive: toHex(toFixedPoint(3000n)) })
58
+ }, 10_000)
59
+ it('Do a transfer', async () => {
60
+ blockAccount2 = await Account.random()
61
+ const receiveAccount = await Account.random()
62
+ txAccount2 = await Account.random()
63
+ const transferPayload: Transfer = {
64
+ from: txAccount2.address,
65
+ epoch: Date.now(),
66
+ schema: TransferSchema,
67
+ transfers: { [receiveAccount.address]: toHex(toFixedPoint(1n)) },
68
+ }
69
+ const [innerBw] = await new BoundWitnessBuilder().payload({ schema: 'network.xyo.test', nonce: 2 } as Payload).signer(await Account.random()).build()
70
+ const secondHydratedTx = await buildRandomTransaction(chainId, [transferPayload, innerBw], txAccount2)
71
+ const secondBlock = await buildNextBlock(previousBlock, [secondHydratedTx], [], [blockAccount2], blockAccount2.address, toFixedPoint(3000n))
72
+ expect(secondBlock[0].block).toEqual(1)
73
+ await archivist.insert([...flattenHydratedBlock(secondBlock)])
74
+ previousBlock = secondBlock[0]
75
+ const balanceChanges = await balancesFromHead(await PayloadBuilder.hash(secondBlock[0]), archivist)
76
+ expect(balanceChanges[blockAccount1.address]).toEqual({ positive: toHex(toFixedPoint(3000n)) })
77
+ expect(balanceChanges[blockAccount2.address]).toEqual({ positive: toHex(toFixedPoint(3000n)) })
78
+ expect(balanceChanges[receiveAccount.address]).toEqual({ positive: toHex(toFixedPoint(1n)) })
79
+ }, 10_000)
80
+ })
81
+
82
+ /*
83
+
84
+ # Send XL1
85
+
86
+ The signer of a boundwitness that contains a transfer payload will have their balance reduced by the amount of the transfer,
87
+ and the receiver will have their balance increased by the amount of the transfer.
88
+
89
+ The gas that is specified in the transfer payload will be deducted from the signer's balance and will be added to the block builder's balance.
90
+
91
+ The goal is for the block builder to only have to validate the transfers that are directly in the transactions that are being added to the block.
92
+ This means that the builder will only accept transfers from accounts that have matching vouchers, and will only accept transfers that are valid.
93
+
94
+ If an unknown account wants to send xl1 to another account, they must get a voucher from a bank that will allow them to send the xl1.
95
+
96
+ Do we need two payloads for this, or do we need one payload that has a list of transfers?
97
+
98
+ */
@@ -0,0 +1,44 @@
1
+ import { Account } from '@xyo-network/account'
2
+ import type { AccountInstance } from '@xyo-network/account-model'
3
+ import { MemoryArchivist } from '@xyo-network/archivist-memory'
4
+ import { buildRandomChain } from '@xyo-network/chain-protocol'
5
+ import { accountBalanceServiceFromArchivist } from '@xyo-network/chain-services'
6
+ import { RequiredBalanceBlockStateValidator } from '@xyo-network/chain-validation'
7
+ import { flattenHydratedBlock } from '@xyo-network/xl1-protocol-sdk'
8
+ import {
9
+ beforeEach,
10
+ describe, expect, it,
11
+ } from 'vitest'
12
+
13
+ describe('RequiredBalanceBlockStateValidator', () => {
14
+ let blockProducer: AccountInstance
15
+ let archivist: MemoryArchivist
16
+ const chainId = 'a82920051db4fcbb804463440dd45e03f72442fd'
17
+ beforeEach(async () => {
18
+ archivist = await MemoryArchivist.create({ account: 'random' })
19
+ blockProducer = await Account.random()
20
+ })
21
+ it('does not validate block level transfers', async () => {
22
+ expect(await Promise.resolve()).toBeUndefined()
23
+ const chain = await buildRandomChain(blockProducer, 2)
24
+ await archivist.insert(flattenHydratedBlock(chain[0]))
25
+ const hydratedBlock = chain[1]
26
+ const accountBalance = await accountBalanceServiceFromArchivist(archivist)
27
+
28
+ // Mock the account balance service to return a balance of 1 ETH for the block producer
29
+ const result = await RequiredBalanceBlockStateValidator(hydratedBlock, chainId, { accountBalance })
30
+ expect(result).toEqual([])
31
+ })
32
+
33
+ it('returns no errors when required balances are met', async () => {
34
+ expect(await Promise.resolve()).toBeUndefined()
35
+ })
36
+
37
+ it('returns an error when required balance is not met', async () => {
38
+ expect(await Promise.resolve()).toBeUndefined()
39
+ })
40
+
41
+ it('returns error when block has no previous hash (first block)', async () => {
42
+ expect(await Promise.resolve()).toBeUndefined()
43
+ })
44
+ })
package/xy.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import type { XyTsupConfig } from '@xylabs/ts-scripts-yarn3'
2
- const config: XyTsupConfig = {
3
- compile: {
4
- entryMode: 'custom',
5
- browser: { src: { entry: ['index-browser.ts'] } },
6
- node: { src: { entry: ['index-node.ts'] } },
7
- neutral: { src: { entry: ['index-shared.ts'] } },
8
- },
9
- }
10
-
11
- export default config