@xyo-network/chain-sdk 1.0.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/LICENSE +165 -0
- package/README.md +57 -0
- package/babel.config.json +6 -0
- package/dist/browser/index-browser.mjs +170 -0
- package/dist/browser/index-browser.mjs.map +1 -0
- package/dist/neutral/index.mjs +311 -0
- package/dist/neutral/index.mjs.map +1 -0
- package/dist/node/index-node.mjs +330 -0
- package/dist/node/index-node.mjs.map +1 -0
- package/dist/types/index-browser.d.ts +2 -0
- package/dist/types/index-browser.d.ts.map +1 -0
- package/dist/types/index-node.d.ts +2 -0
- package/dist/types/index-node.d.ts.map +1 -0
- package/dist/types/index-shared.d.ts +9 -0
- package/dist/types/index-shared.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/orchestration/archivist/index.d.ts +2 -0
- package/dist/types/orchestration/archivist/index.d.ts.map +1 -0
- package/dist/types/orchestration/archivist/initArchivistSync.d.ts +16 -0
- package/dist/types/orchestration/archivist/initArchivistSync.d.ts.map +1 -0
- package/dist/types/orchestration/bridge.d.ts +3 -0
- package/dist/types/orchestration/bridge.d.ts.map +1 -0
- package/dist/types/orchestration/index.d.ts +4 -0
- package/dist/types/orchestration/index.d.ts.map +1 -0
- package/dist/types/orchestration/initBridgedModule.d.ts +13 -0
- package/dist/types/orchestration/initBridgedModule.d.ts.map +1 -0
- package/dist/types/test/evm/index.d.ts +3 -0
- package/dist/types/test/evm/index.d.ts.map +1 -0
- package/dist/types/test/evm/runGanache.d.ts +4 -0
- package/dist/types/test/evm/runGanache.d.ts.map +1 -0
- package/dist/types/test/evm/setup.d.ts +2 -0
- package/dist/types/test/evm/setup.d.ts.map +1 -0
- package/dist/types/test/evm/stakingContractUtils.d.ts +9 -0
- package/dist/types/test/evm/stakingContractUtils.d.ts.map +1 -0
- package/dist/types/test/globalSetup.d.ts +2 -0
- package/dist/types/test/globalSetup.d.ts.map +1 -0
- package/dist/types/test/index.d.ts +2 -0
- package/dist/types/test/index.d.ts.map +1 -0
- package/package.json +120 -0
- package/src/Layers.md +64 -0
- package/src/index-browser.ts +1 -0
- package/src/index-node.ts +1 -0
- package/src/index-shared.ts +8 -0
- package/src/index.ts +2 -0
- package/src/orchestration/archivist/index.ts +1 -0
- package/src/orchestration/archivist/initArchivistSync.ts +61 -0
- package/src/orchestration/bridge.ts +25 -0
- package/src/orchestration/index.ts +3 -0
- package/src/orchestration/initBridgedModule.ts +35 -0
- package/src/test/evm/index.ts +2 -0
- package/src/test/evm/runGanache.ts +41 -0
- package/src/test/evm/setup.ts +75 -0
- package/src/test/evm/stakingContractUtils.ts +109 -0
- package/src/test/globalSetup.ts +14 -0
- package/src/test/index.ts +1 -0
- package/xy.config.ts +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json.schemastore.org/package.json",
|
|
3
|
+
"name": "@xyo-network/chain-sdk",
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"description": "XYO Layer One SDK",
|
|
6
|
+
"homepage": "https://xylabs.com",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "git+https://github.com/xylabs/xyo-chain/issues",
|
|
9
|
+
"email": "support@xylabs.com"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/xylabs/xyo-chain.git"
|
|
14
|
+
},
|
|
15
|
+
"license": "LGPL-3.0-only",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "XY Labs Development Team",
|
|
18
|
+
"email": "support@xylabs.com",
|
|
19
|
+
"url": "https://xylabs.com"
|
|
20
|
+
},
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"type": "module",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"browser": {
|
|
26
|
+
"types": "./dist/types/index.d.ts",
|
|
27
|
+
"default": "./dist/browser/index-browser.mjs"
|
|
28
|
+
},
|
|
29
|
+
"node": {
|
|
30
|
+
"types": "./dist/types/index.d.ts",
|
|
31
|
+
"default": "./dist/node/index-node.mjs"
|
|
32
|
+
},
|
|
33
|
+
"neutral": {
|
|
34
|
+
"types": "./dist/types/index.d.ts",
|
|
35
|
+
"default": "./dist/neutral/index.mjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"module": "./dist/neutral/index.mjs",
|
|
41
|
+
"types": "./dist/types/index.d.ts",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "xy build -j 8",
|
|
44
|
+
"build-tests": "tsc --noEmit --lib dom,esnext",
|
|
45
|
+
"deploy": "echo Deploy not allowed!",
|
|
46
|
+
"deploy3": "echo Deploy3 not allowed!"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@opentelemetry/api": "^1.9.0",
|
|
50
|
+
"@xylabs/assert": "^4.6.0",
|
|
51
|
+
"@xylabs/decimal-precision": "^4.6.0",
|
|
52
|
+
"@xylabs/delay": "^4.6.0",
|
|
53
|
+
"@xylabs/hex": "^4.6.0",
|
|
54
|
+
"@xyo-network/account": "^3.9.37",
|
|
55
|
+
"@xyo-network/account-model": "^3.9.37",
|
|
56
|
+
"@xyo-network/archivist-memory": "^3.9.37",
|
|
57
|
+
"@xyo-network/archivist-model": "^3.9.37",
|
|
58
|
+
"@xyo-network/boundwitness-builder": "^3.9.37",
|
|
59
|
+
"@xyo-network/bridge-http": "^3.9.37",
|
|
60
|
+
"@xyo-network/bridge-model": "^3.9.37",
|
|
61
|
+
"@xyo-network/chain-ethereum": "^1.0.1",
|
|
62
|
+
"@xyo-network/chain-model": "^1.0.1",
|
|
63
|
+
"@xyo-network/chain-modules": "^1.0.1",
|
|
64
|
+
"@xyo-network/chain-protocol": "^1.0.1",
|
|
65
|
+
"@xyo-network/chain-services": "^1.0.1",
|
|
66
|
+
"@xyo-network/chain-utils": "^1.0.1",
|
|
67
|
+
"@xyo-network/chain-validation": "^1.0.1",
|
|
68
|
+
"@xyo-network/diviner-model": "^3.9.37",
|
|
69
|
+
"@xyo-network/module-model": "^3.9.37",
|
|
70
|
+
"@xyo-network/node-memory": "^3.9.37",
|
|
71
|
+
"@xyo-network/payload-builder": "^3.9.37",
|
|
72
|
+
"@xyo-network/payload-model": "^3.9.37",
|
|
73
|
+
"@xyo-network/sentinel-memory": "^3.9.37",
|
|
74
|
+
"@xyo-network/sentinel-model": "^3.9.37",
|
|
75
|
+
"@xyo-network/typechain": "^3.4.15",
|
|
76
|
+
"async-mutex": "^0.5.0",
|
|
77
|
+
"ethers": "^6.13.5"
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@types/node": "^22.13.10",
|
|
81
|
+
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
|
82
|
+
"@typescript-eslint/parser": "^8.26.1",
|
|
83
|
+
"@xylabs/config": "^6.0.9",
|
|
84
|
+
"@xylabs/delay": "^4.6.0",
|
|
85
|
+
"@xylabs/eslint-config-flat": "^6.0.9",
|
|
86
|
+
"@xylabs/ts-scripts-yarn3": "^6.0.9",
|
|
87
|
+
"@xylabs/tsconfig": "^6.0.9",
|
|
88
|
+
"@xylabs/tsconfig-react": "^6.0.9",
|
|
89
|
+
"@xylabs/vitest-extended": "^4.6.0",
|
|
90
|
+
"@xyo-network/account": "^3.9.37",
|
|
91
|
+
"@xyo-network/id-payload-plugin": "^3.9.37",
|
|
92
|
+
"@xyo-network/node-model": "^3.9.37",
|
|
93
|
+
"@xyo-network/open-zeppelin-typechain": "^3.4.15",
|
|
94
|
+
"eslint": "^9.22.0",
|
|
95
|
+
"eslint-import-resolver-typescript": "^3.8.7",
|
|
96
|
+
"ganache": "^7.9.2",
|
|
97
|
+
"knip": "^5.46.0",
|
|
98
|
+
"reflect-metadata": "^0.2.2",
|
|
99
|
+
"ts-node": "^10.9.2",
|
|
100
|
+
"tslib": "^2.8.1",
|
|
101
|
+
"typedoc": "^0.27.9",
|
|
102
|
+
"typescript": "^5.8.2",
|
|
103
|
+
"vite": "^6.2.2",
|
|
104
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
105
|
+
"vitest": "^3.0.8",
|
|
106
|
+
"vitest-mock-extended": "^3.0.1"
|
|
107
|
+
},
|
|
108
|
+
"packageManager": "yarn@4.6.0",
|
|
109
|
+
"engines": {
|
|
110
|
+
"node": ">=22"
|
|
111
|
+
},
|
|
112
|
+
"engineStrict": true,
|
|
113
|
+
"volta": {
|
|
114
|
+
"node": "22.3.0",
|
|
115
|
+
"yarn": "4.6.0"
|
|
116
|
+
},
|
|
117
|
+
"publishConfig": {
|
|
118
|
+
"access": "restricted"
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/Layers.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Blockchain State Layers
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
- [Blockchain State Layers](#blockchain-state-layers)
|
|
5
|
+
- [Table of Contents](#table-of-contents)
|
|
6
|
+
- [Layers](#layers)
|
|
7
|
+
- [Protocol Services](#protocol-services)
|
|
8
|
+
- [Chain Builder Services](#chain-builder-services)
|
|
9
|
+
- [Chain Iterator Services](#chain-iterator-services)
|
|
10
|
+
- [Validator Services](#validator-services)
|
|
11
|
+
- [Producer Services](#producer-services)
|
|
12
|
+
- [Dapp/Gateway Services Common](#dappgateway-services-common)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
## Layers
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### Protocol Services
|
|
19
|
+
**Description:** Used to build individual, protocol-valid blocks
|
|
20
|
+
**State:** Stateless
|
|
21
|
+
- Block Services
|
|
22
|
+
- Transaction Services
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### Chain Builder Services
|
|
27
|
+
**Description:** Used to build protocol-valid chain for a specific staking contract
|
|
28
|
+
**State:** Staking contract
|
|
29
|
+
- Contract Services
|
|
30
|
+
- Chain Services
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### Chain Iterator Services
|
|
35
|
+
**Description:** Services that enable read access of a chain
|
|
36
|
+
**State:** XYO Chain Head, Archivist(s)
|
|
37
|
+
- Chain State & History
|
|
38
|
+
- Account Balance Services
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### Validator Services
|
|
43
|
+
**Description:** Services that enable historical & next-block validation
|
|
44
|
+
**State:** XYO Chain Creation
|
|
45
|
+
- Leader Election
|
|
46
|
+
- Block Reward Calculation
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
### Producer Services
|
|
51
|
+
**Description:** Services that enable next-block production
|
|
52
|
+
**State:** Pending transactions for inclusion in the blockchain
|
|
53
|
+
- Mempool/Pending Transaction Service
|
|
54
|
+
- Voucher Service
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
### Dapp/Gateway Services Common
|
|
59
|
+
**Description:** Services that enable Dapps to Interact with Chain
|
|
60
|
+
**State:** Application state
|
|
61
|
+
- `web2 RPC`
|
|
62
|
+
- `addTransaction`
|
|
63
|
+
- `readBalance`
|
|
64
|
+
- `validatePending`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index-shared.ts'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './index.ts'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './orchestration/index.ts'
|
|
2
|
+
export * from '@xyo-network/chain-ethereum'
|
|
3
|
+
export * from '@xyo-network/chain-model'
|
|
4
|
+
export * from '@xyo-network/chain-modules'
|
|
5
|
+
export * from '@xyo-network/chain-protocol'
|
|
6
|
+
export * from '@xyo-network/chain-services'
|
|
7
|
+
export * from '@xyo-network/chain-utils'
|
|
8
|
+
export * from '@xyo-network/chain-validation'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './initArchivistSync.ts'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TracerProvider } from '@opentelemetry/api'
|
|
2
|
+
import { ArchivistSyncDiviner } from '@xyo-network/chain-modules'
|
|
3
|
+
import { spanAsync } from '@xyo-network/chain-utils'
|
|
4
|
+
import { ArchivistInstance } from '@xyo-network/archivist-model'
|
|
5
|
+
import { DivinerConfigSchema } from '@xyo-network/diviner-model'
|
|
6
|
+
import { ModuleIdentifier } from '@xyo-network/module-model'
|
|
7
|
+
import { MemoryNode } from '@xyo-network/node-memory'
|
|
8
|
+
import { MemorySentinel } from '@xyo-network/sentinel-memory'
|
|
9
|
+
import { SentinelConfigSchema } from '@xyo-network/sentinel-model'
|
|
10
|
+
|
|
11
|
+
export const initArchivistSync = async (
|
|
12
|
+
name: ModuleIdentifier,
|
|
13
|
+
inArchivist: ArchivistInstance,
|
|
14
|
+
outArchivist: ArchivistInstance,
|
|
15
|
+
frequency = 1000,
|
|
16
|
+
traceProvider?: TracerProvider,
|
|
17
|
+
) => {
|
|
18
|
+
const tracer = traceProvider?.getTracer(`archivist-sync-diviner [${name}]`)
|
|
19
|
+
return await spanAsync(`initArchivistSync [${name}]`, async () => {
|
|
20
|
+
const node = await MemoryNode.create({ account: 'random' })
|
|
21
|
+
const finalizedArchivistSyncDiviner = await ArchivistSyncDiviner.create({
|
|
22
|
+
account: 'random',
|
|
23
|
+
inArchivist,
|
|
24
|
+
outArchivist,
|
|
25
|
+
tracer,
|
|
26
|
+
config: { schema: DivinerConfigSchema, name },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
await node.register(finalizedArchivistSyncDiviner)
|
|
30
|
+
await node.attach(finalizedArchivistSyncDiviner.address)
|
|
31
|
+
|
|
32
|
+
const sentinel = await MemorySentinel.create({
|
|
33
|
+
account: 'random',
|
|
34
|
+
config: {
|
|
35
|
+
name,
|
|
36
|
+
schema: SentinelConfigSchema,
|
|
37
|
+
tasks: [
|
|
38
|
+
{
|
|
39
|
+
required: true,
|
|
40
|
+
mod: finalizedArchivistSyncDiviner.address,
|
|
41
|
+
endPoint: 'divine',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
automations: [
|
|
45
|
+
{
|
|
46
|
+
frequency,
|
|
47
|
+
frequencyUnits: 'millis',
|
|
48
|
+
schema: 'network.xyo.automation.interval',
|
|
49
|
+
start: Date.now(),
|
|
50
|
+
type: 'interval',
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
await node.register(sentinel)
|
|
57
|
+
await node.attach(sentinel.address)
|
|
58
|
+
|
|
59
|
+
return node
|
|
60
|
+
}, tracer)
|
|
61
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HttpBridge, HttpBridgeConfigSchema } from '@xyo-network/bridge-http'
|
|
2
|
+
import { AttachableBridgeInstance } from '@xyo-network/bridge-model'
|
|
3
|
+
import { Mutex } from 'async-mutex'
|
|
4
|
+
|
|
5
|
+
const initMutex = new Mutex()
|
|
6
|
+
const bridgeSingletonMap = new Map<string, AttachableBridgeInstance>()
|
|
7
|
+
|
|
8
|
+
export const initBridge = async (nodeUrl: string): Promise<AttachableBridgeInstance> => {
|
|
9
|
+
return await initMutex.runExclusive(async () => {
|
|
10
|
+
const existing = bridgeSingletonMap.get(nodeUrl)
|
|
11
|
+
if (existing) return existing
|
|
12
|
+
const bridge = await HttpBridge.create({
|
|
13
|
+
account: 'random',
|
|
14
|
+
config: {
|
|
15
|
+
name: 'HttpBridge',
|
|
16
|
+
client: { url: nodeUrl, discoverRoots: 'start' },
|
|
17
|
+
schema: HttpBridgeConfigSchema,
|
|
18
|
+
security: { allowAnonymous: true },
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
await bridge.start()
|
|
22
|
+
bridgeSingletonMap.set(nodeUrl, bridge)
|
|
23
|
+
return bridge
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import { Initializable } from '@xyo-network/chain-model'
|
|
3
|
+
import { Address } from '@xylabs/hex'
|
|
4
|
+
import { asAttachableArchivistInstance, AttachableArchivistInstance } from '@xyo-network/archivist-model'
|
|
5
|
+
import { BridgeInstance } from '@xyo-network/bridge-model'
|
|
6
|
+
import {
|
|
7
|
+
asAttachableModuleInstance, AttachableModuleInstance, ModuleIdentifier,
|
|
8
|
+
} from '@xyo-network/module-model'
|
|
9
|
+
import { Mutex } from 'async-mutex'
|
|
10
|
+
|
|
11
|
+
const initMutex = new Mutex()
|
|
12
|
+
const bridgedModuleDictionary: Record<Address, Record<ModuleIdentifier, AttachableModuleInstance>> = {}
|
|
13
|
+
|
|
14
|
+
export const initBridgedModule: Initializable<
|
|
15
|
+
{ bridge: BridgeInstance; moduleName: ModuleIdentifier }, AttachableModuleInstance
|
|
16
|
+
> = async ({ bridge, moduleName }): Promise<AttachableModuleInstance> => {
|
|
17
|
+
return await initMutex.runExclusive(async () => {
|
|
18
|
+
const existing = bridgedModuleDictionary?.[bridge.address]?.[moduleName]
|
|
19
|
+
if (existing) return existing
|
|
20
|
+
const mod = assertEx(await bridge.resolve(moduleName), () => `Error: Could not resolve ${moduleName}`)
|
|
21
|
+
const moduleInstance = assertEx(asAttachableModuleInstance(mod), () => `Error: Could not convert ${moduleName} to attachable module instance`)
|
|
22
|
+
bridgedModuleDictionary[bridge.address] = bridgedModuleDictionary[bridge.address] || {}
|
|
23
|
+
bridgedModuleDictionary[bridge.address][moduleName] = moduleInstance
|
|
24
|
+
return moduleInstance
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const initBridgedArchivistModule: Initializable<
|
|
29
|
+
{ bridge: BridgeInstance; moduleName: ModuleIdentifier }, AttachableArchivistInstance
|
|
30
|
+
> = async ({ bridge, moduleName }): Promise<AttachableArchivistInstance> => {
|
|
31
|
+
return assertEx(
|
|
32
|
+
asAttachableArchivistInstance(await initBridgedModule({ bridge, moduleName })),
|
|
33
|
+
() => `Error: Could not convert ${moduleName} to attachable archivist instance`,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ChildProcess, spawn } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { delay } from '@xylabs/delay'
|
|
4
|
+
import { JsonRpcProvider } from 'ethers/providers'
|
|
5
|
+
|
|
6
|
+
async function isGanacheRunning(url: string) {
|
|
7
|
+
try {
|
|
8
|
+
const provider = new JsonRpcProvider(url)
|
|
9
|
+
await provider.getBlockNumber() // Try to fetch the latest block number
|
|
10
|
+
return true // If this succeeds, the server is running
|
|
11
|
+
} catch {
|
|
12
|
+
return false // If this fails, the server is not running
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function startGanache(port: number, mnemonic: string) {
|
|
17
|
+
console.log('Starting Ganache CLI...')
|
|
18
|
+
|
|
19
|
+
// Start Ganache CLI
|
|
20
|
+
const ganacheProcess = spawn(
|
|
21
|
+
'yarn',
|
|
22
|
+
['ganache', '--port', `${port}`, '--chain.chainId', '1337', '--mnemonic', mnemonic],
|
|
23
|
+
{ stdio: 'inherit' },
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// Wait for Ganache to be ready
|
|
27
|
+
while (!await isGanacheRunning(`http://127.0.0.1:${port}`)) {
|
|
28
|
+
await delay(500)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return ganacheProcess
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function stopGanache(process: ChildProcess) {
|
|
35
|
+
console.log('Stopping Ganache CLI...')
|
|
36
|
+
|
|
37
|
+
if (process) {
|
|
38
|
+
process.kill('SIGTERM')
|
|
39
|
+
console.log('Ganache CLI stopped.')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import { createChain } from '@xyo-network/chain-ethereum'
|
|
3
|
+
import { toEthAddress } from '@xyo-network/chain-model'
|
|
4
|
+
import { EvmChainService } from '@xyo-network/chain-services'
|
|
5
|
+
import { XYO_ZERO_ADDRESS } from '@xyo-network/chain-utils'
|
|
6
|
+
import { asAddress } from '@xylabs/hex'
|
|
7
|
+
import { HDWallet } from '@xyo-network/account'
|
|
8
|
+
import { BurnableErc20__factory as Erc20Factory, StakedXyoChain__factory as StakedXyoChainFactory } from '@xyo-network/typechain'
|
|
9
|
+
import { JsonRpcProvider } from 'ethers/providers'
|
|
10
|
+
import { parseUnits } from 'ethers/utils'
|
|
11
|
+
import { Wallet } from 'ethers/wallet'
|
|
12
|
+
|
|
13
|
+
import { startGanache } from './runGanache.ts'
|
|
14
|
+
|
|
15
|
+
const ganachePort = 8545
|
|
16
|
+
const testPhrase = 'edit hill neutral usage share kite knee abandon slim open lottery abandon'
|
|
17
|
+
|
|
18
|
+
const gasConfig = () => {
|
|
19
|
+
return {
|
|
20
|
+
gasLimit: 2_000_000, // Set the gas limit
|
|
21
|
+
gasPrice: parseUnits('100', 'gwei'),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create Erc20
|
|
26
|
+
await startGanache(ganachePort, testPhrase)
|
|
27
|
+
const walletPerson = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/0'/0")
|
|
28
|
+
const initialProducer = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/1'/0")
|
|
29
|
+
const account0 = await walletPerson.derivePath("m/44'/60'/0'/0/0")
|
|
30
|
+
const account1 = await walletPerson.derivePath("m/44'/60'/0'/0/1")
|
|
31
|
+
const provider0 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
32
|
+
const ethWalletPerson0 = new Wallet(account0.privateKey, provider0)
|
|
33
|
+
const provider1 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
34
|
+
const ethWalletPerson1 = new Wallet(account1.privateKey, provider1)
|
|
35
|
+
|
|
36
|
+
const erc20 = new Erc20Factory(ethWalletPerson0)
|
|
37
|
+
const erc20Contract = await (await erc20.deploy('Test Token', 'TST', parseUnits('1000000', 18), gasConfig())).waitForDeployment()
|
|
38
|
+
const erc20Address = await erc20Contract.getAddress()
|
|
39
|
+
console.log('ERC20 Contract Address:', erc20Address)
|
|
40
|
+
const erc20ContractPerson0 = Erc20Factory.connect(erc20Address, ethWalletPerson0)
|
|
41
|
+
|
|
42
|
+
// Transfer Tokens
|
|
43
|
+
await (await erc20ContractPerson0.transfer(ethWalletPerson1.address, parseUnits('1000', 18), gasConfig())).wait()
|
|
44
|
+
const erc20ContractPerson1 = Erc20Factory.connect(erc20Address, ethWalletPerson1)
|
|
45
|
+
const balance = await erc20ContractPerson1.balanceOf(ethWalletPerson1.address)
|
|
46
|
+
console.log(erc20Address, 'ERC20 Balance:', balance.toString())
|
|
47
|
+
|
|
48
|
+
// Create Chain Contract
|
|
49
|
+
const [xyoChainContractAddress] = await (createChain(
|
|
50
|
+
ethWalletPerson0, // signer
|
|
51
|
+
toEthAddress(erc20Address), // stakingTokenAddress
|
|
52
|
+
initialProducer, // initialProducer
|
|
53
|
+
null,
|
|
54
|
+
0n,
|
|
55
|
+
XYO_ZERO_ADDRESS,
|
|
56
|
+
50_000n,
|
|
57
|
+
gasConfig(),
|
|
58
|
+
))
|
|
59
|
+
|
|
60
|
+
const xyoChainPerson0 = StakedXyoChainFactory.connect(xyoChainContractAddress)
|
|
61
|
+
|
|
62
|
+
console.log('XYO Chain Contract Address:', xyoChainContractAddress)
|
|
63
|
+
// const xyoChainPerson1 = StakedXyoChainFactory.connect(xyoChainContractAddress, ethWalletPerson1)
|
|
64
|
+
|
|
65
|
+
// Approve Stake Chain Address
|
|
66
|
+
await (await erc20ContractPerson1.approve(xyoChainContractAddress, parseUnits('100', 18), gasConfig())).wait()
|
|
67
|
+
|
|
68
|
+
// Stake Chain Contract
|
|
69
|
+
const address = assertEx(asAddress(xyoChainContractAddress))
|
|
70
|
+
const xyoChainPerson1 = await EvmChainService.create({ id: address, runner: ethWalletPerson1 })
|
|
71
|
+
await xyoChainPerson1.addStake(await ethWalletPerson1.getAddress(), parseUnits('50', 18))
|
|
72
|
+
|
|
73
|
+
// Read Stake
|
|
74
|
+
const stakeByAddressResult = await xyoChainPerson0.activeByAddressStaked(await ethWalletPerson1.getAddress())
|
|
75
|
+
console.log('Stake By Address:', stakeByAddressResult.toString())
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import { createChain } from '@xyo-network/chain-ethereum'
|
|
3
|
+
import { ChainStakeIntent, ChainStakeIntentSchema, HydratedBlock } from '@xyo-network/chain-model'
|
|
4
|
+
import {
|
|
5
|
+
buildBlock, buildTransaction
|
|
6
|
+
} from '@xyo-network/chain-protocol'
|
|
7
|
+
import { XYO_ZERO_ADDRESS } from '@xyo-network/chain-utils'
|
|
8
|
+
import { Address, asAddress } from '@xylabs/hex'
|
|
9
|
+
import {
|
|
10
|
+
Account, AccountInstance, WalletInstance,
|
|
11
|
+
} from '@xyo-network/account'
|
|
12
|
+
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
13
|
+
import { BurnableErc20__factory as Erc20Factory } from '@xyo-network/typechain'
|
|
14
|
+
import { getAddress } from 'ethers/address'
|
|
15
|
+
import { JsonRpcProvider } from 'ethers/providers'
|
|
16
|
+
import { parseUnits } from 'ethers/utils'
|
|
17
|
+
import { Wallet } from 'ethers/wallet'
|
|
18
|
+
|
|
19
|
+
const gasConfig = () => {
|
|
20
|
+
return {
|
|
21
|
+
gasLimit: 2_000_000, // Set the gas limit
|
|
22
|
+
gasPrice: parseUnits('100', 'gwei'),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const createTestErc20 = async (ganachePort: number, testPhrase: string, contractCreator: WalletInstance): Promise<Address> => {
|
|
27
|
+
const provider = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
28
|
+
const ethWallet = new Wallet(contractCreator.privateKey, provider)
|
|
29
|
+
const erc20 = new Erc20Factory(ethWallet)
|
|
30
|
+
const totalSupply = parseUnits('1000000', 18)
|
|
31
|
+
const erc20Contract = await (await erc20.deploy('Test Token', 'TST', totalSupply, gasConfig())).waitForDeployment()
|
|
32
|
+
const erc20Address = await erc20Contract.getAddress()
|
|
33
|
+
const erc20ContractPerson0 = Erc20Factory.connect(erc20Address, ethWallet)
|
|
34
|
+
const balance = await erc20ContractPerson0.balanceOf(ethWallet.address)
|
|
35
|
+
assertEx(balance === totalSupply, () => 'Balance does not match total supply')
|
|
36
|
+
return assertEx(asAddress(erc20Address), () => 'Invalid ERC20 contract address')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const transferTestTokens = async (ganachePort: number, erc20Address: Address, account0: WalletInstance, account1: WalletInstance) => {
|
|
40
|
+
const provider0 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
41
|
+
const ethWalletPerson0 = new Wallet(account0.privateKey, provider0)
|
|
42
|
+
const provider1 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
43
|
+
const ethWalletPerson1 = new Wallet(account1.privateKey, provider1)
|
|
44
|
+
const erc20ContractPerson0 = Erc20Factory.connect(getAddress(erc20Address), ethWalletPerson0)
|
|
45
|
+
const transferAmount = parseUnits('1000', 18)
|
|
46
|
+
await (await erc20ContractPerson0.transfer(ethWalletPerson1.address, transferAmount, gasConfig())).wait()
|
|
47
|
+
const erc20ContractPerson1 = Erc20Factory.connect(getAddress(erc20Address), ethWalletPerson1)
|
|
48
|
+
const balance = await erc20ContractPerson1.balanceOf(ethWalletPerson1.address)
|
|
49
|
+
assertEx(balance === transferAmount, () => 'Balance does not match transfer amount')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const createTestChainContract = async (
|
|
53
|
+
ganachePort: number,
|
|
54
|
+
erc20Address: Address,
|
|
55
|
+
account0: WalletInstance,
|
|
56
|
+
initialProducer: AccountInstance,
|
|
57
|
+
): Promise<Address> => {
|
|
58
|
+
const provider0 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
59
|
+
const ethWalletPerson0 = new Wallet(account0.privateKey, provider0)
|
|
60
|
+
const [xyoChainContractAddress] = await createChain(
|
|
61
|
+
ethWalletPerson0,
|
|
62
|
+
erc20Address,
|
|
63
|
+
initialProducer,
|
|
64
|
+
XYO_ZERO_ADDRESS,
|
|
65
|
+
0n,
|
|
66
|
+
XYO_ZERO_ADDRESS,
|
|
67
|
+
50_000n,
|
|
68
|
+
gasConfig(),
|
|
69
|
+
)
|
|
70
|
+
return assertEx(asAddress(xyoChainContractAddress), () => 'Invalid staking contract address')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const approveTestStakeChainAddress = async (ganachePort: number, erc20Address: Address, xyoChainContractAddress: Address, account1: WalletInstance) => {
|
|
74
|
+
const provider1 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
75
|
+
const ethWalletPerson1 = new Wallet(account1.privateKey, provider1)
|
|
76
|
+
const erc20ContractPerson1 = Erc20Factory.connect(getAddress(erc20Address), ethWalletPerson1)
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
78
|
+
const _approveResultTx = await (await erc20ContractPerson1.approve(getAddress(xyoChainContractAddress), parseUnits('100', 18), gasConfig())).wait()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const createTestGenesisBlock = async (
|
|
82
|
+
blockProducerAccounts: WalletInstance[],
|
|
83
|
+
): Promise<HydratedBlock> => {
|
|
84
|
+
const randomChainId = (await Account.random()).address
|
|
85
|
+
// Create staked intents for all the block producers declaring their intent to produce blocks
|
|
86
|
+
const signedStakedBlockProducerIntents = await Promise.all(blockProducerAccounts.map(async (blockProducerAccount) => {
|
|
87
|
+
const stakeIntent = new PayloadBuilder<ChainStakeIntent>({ schema: ChainStakeIntentSchema }).fields({
|
|
88
|
+
from: blockProducerAccount.address,
|
|
89
|
+
exp: Number.MAX_SAFE_INTEGER,
|
|
90
|
+
nbf: 0,
|
|
91
|
+
intent: 'producer',
|
|
92
|
+
}).build()
|
|
93
|
+
|
|
94
|
+
const signedStakedBlockProducerIntent = await buildTransaction(
|
|
95
|
+
randomChainId,
|
|
96
|
+
[stakeIntent],
|
|
97
|
+
0n,
|
|
98
|
+
blockProducerAccount,
|
|
99
|
+
0,
|
|
100
|
+
1000,
|
|
101
|
+
[],
|
|
102
|
+
)
|
|
103
|
+
return signedStakedBlockProducerIntent
|
|
104
|
+
}))
|
|
105
|
+
const intents = signedStakedBlockProducerIntents.map(intent => intent[0])
|
|
106
|
+
const payloads = signedStakedBlockProducerIntents.flatMap(intent => intent[1])
|
|
107
|
+
const [firstBlock, signedTransactionBws] = await buildBlock(randomChainId, signedStakedBlockProducerIntents, [], blockProducerAccounts)
|
|
108
|
+
return [firstBlock, [...intents, ...signedTransactionBws, ...payloads]] as const
|
|
109
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const globalTeardown = async () => {
|
|
2
|
+
console.log('Running global teardown after all tests...')
|
|
3
|
+
// TODO: Stop Ganache here?
|
|
4
|
+
await new Promise(resolve => setTimeout(resolve, 1))
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default async function globalSetup() {
|
|
8
|
+
console.log('Running global setup before all tests...')
|
|
9
|
+
// TODO: Start Ganache here?
|
|
10
|
+
await new Promise(resolve => setTimeout(resolve, 1))
|
|
11
|
+
|
|
12
|
+
// Return a teardown function that Vitest will call after all tests complete
|
|
13
|
+
return globalTeardown
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './evm/index.ts'
|
package/xy.config.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { XyTsupConfig } from '@xylabs/ts-scripts-yarn3'
|
|
2
|
+
const config: XyTsupConfig = {
|
|
3
|
+
compile: {
|
|
4
|
+
browser: { src: { entry: ['src/index-browser.ts'] } },
|
|
5
|
+
node: { src: { entry: ['src/index-node.ts'] } },
|
|
6
|
+
neutral: { src: true },
|
|
7
|
+
},
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default config
|