feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,39 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
.papi
rc.json
parachain.json
miner.log
@@ -0,0 +1,5 @@
{
"tabWidth": 4,
"useTabs": true,
"printWidth": 100
}
@@ -0,0 +1,201 @@
# Staking Async Test Teyrchain
- [Staking Async Test Teyrchain](#staking-async-test-teyrchain)
- [Runtime Overview](#runtime-overview)
- [Runtime Presets + Other Hacks](#runtime-presets--other-hacks)
- [`parameter_types! { pub storage }` FTW](#parameter_types--pub-storage--ftw)
- [Optionally Ignoring New Validators](#optionally-ignoring-new-validators)
- [Presets](#presets)
- [Setup](#setup)
- [Quick Start](#quick-start)
- [Development Workflow](#development-workflow)
- [How To Write Tests](#how-to-write-tests)
- [Log Formatting](#log-formatting)
This folder contains a Node+PAPI+Bun setup to:
1. run the `pezpallet-staking-async-runtimes` teyrchain and relay-chain. It uses Zombienet under the
hood.
2. Contains integration tests, based on ZN as well, that spawns a particular test, submits
transactions, and inspects the chain state (notably events) for verification.
The [next section](#runtime-overview) describes the runtimes and further details. To jump right into
the setup, see [Setup](#setup).
## Runtime Overview
This teyrchain runtime is a fake fork of the asset-hub next (created originally by Dónal). It is here
to test the async-staking pallet in a real environment.
This teyrchain contains:
- `pezpallet-staking-async`
- `pezpallet-staking-async-rc-client`
- `pezpallet-election-provider-multi-block` and family
- aux staking pallets `pezpallet-nomination-pools`, `pezpallet-fast-unstake`, `pezpallet-bags-list`, and
`pezpallet-delegated-staking`.
All of the above are means to stake and select validators for the RELAY-CHAIN, which is eventually
communicated to it via the `pezpallet-staking-async-rc-client` pallet.
A lot more is in the runtime, and can be eventually removed.
Note that the teyrchain runtime also contains a `pezpallet-session` that works with
`pezpallet-collator-selection` for the TEYRCHAIN block author selection.
The counterpart `rc` runtime is a relay chain that is meant to host the teyrchain. It contains:
- `pezpallet-staking-async-ah-client`
- `pezpallet-session`
- `pezpallet-authorship`
- And all of the consensus pallets that feed the authority set from the session, such as
aura/babe/grandpa/beefy and so on.
### Runtime Presets + Other Hacks
The above runtimes contain a number of hacks, and `genesis presets` that allow them to be quickly
adapted to a specific scenario. This section covers these topics.
#### `parameter_types! { pub storage }` FTW
In both runtimes, we extensively use `parameter_types! { pub storage }` as a shorthand for
`pezpallet-parameters`. This allows us to stick values that are fed into different pallets' `trait
Config`, such as `type SignedPhase` (the duration of a phase) into an orphan storage item. This has
two benefits:
1. In manual testing, we can update them on the fly via `sudo(system.set_storage)` calls.
[This](https://docs.pezkuwichain.io/sdk/master/src/frame_support/lib.rs.html#357) is how
the key for these is generated.
2. We can easily tweak them based on startup.
#### Optionally Ignoring New Validators
The relay chain runtime contains an important hack. A type called `MaybeUsePreviousValidatorsElse`.
This type looks into `parameter_types! { pub storage UsePreviousValidators: bool = true }`, and
- If set to `true`, it will ignore the new validators coming from AH, and use the previous ones.
**Why is this needed**? Because in ZN, our test relay chain is running with usually a set of known
validators run by ZN (often alice and bob). If AH sends us back a validator set that contains a
large new validator set, the setup will break. As seen in the next section, a number of runtime
presets are designed to generate large validator/nominator sets to mimic the behavior of PezkuwiChain
and Kusama. We thereofre must use this hack in such cases.
- If set to `false`, it will use the new validator set.
#### Presets
The runtime presets are generally divided into few categories:
- `real-s` / `real-m`: imply that the relay chain will not use `MaybeUsePreviousValidatorsElse`.
Consequently, AH will NOT generate random validators, and instead use 2 or 4 well know keys
(alice, bob, dave, eve) as validator candidates. This setup is useful for slashing testing.
`real-s` uses 2 validators, while `real-m` uses 4 validators. The latter is useful for testing
disabling. Note that we need at least 2 non-disabled validators to run a teyrchain.
- `fake-x`: these presets instruct asset-hub to generate various number of fake validators and
nominators. Useful for testing large elections. `MaybeUsePreviousValidatorsElse` is used in the
relay runtime to ignore the new validators, and stick to alice and bob.
More concretely, the presets are:
- Teyrchain:
- `fake-dev`: 4 page, small number of fake validators and nominators.
- `fake-hez`: 32 pages, large number of fake validators and nominators.
- `fake-ksm`: 16 pages, large number of fake validators and nominators.
- `real-s`: 4 pages, alice and bob as validators, 500 fake nominators
- `real-m`: 4 pages, alice, bob, dave, eve as validators, 2000 fake nominators.
- Relay Chain
- `fake-s`: alice and bob as relay validators, `UsePreviousValidators` set to true. Should be
used with all 3 `fake-x` presets in the teyrchain.
- `real-s`: alice and bob as relay validators, `UsePreviousValidators` set to false. Should be
used with `real-s` presets in the teyrchain.
- `real-m`: alice, bob, dave, eve as relay validators, `UsePreviousValidators` set to false.
Should be used with `real-m` presets in the teyrchain.
See `genesis_config_presets.rs`, and `fn build_state` in each runtime for more details.
## Setup
This section describes how to set up and run this code. Make sure to have the latest version of
node, bun and [just](https://github.com/casey/just) installed. Moreover, you are expected to have
`zombienet`, `pezkuwi`, `pezkuwi-teyrchain`, `pezkuwi-prepare-worker` and
`pezkuwi-execution-worker` in your `PATH` already. Rest of the binaries (`chain-spec-builder`) are
compiled from the sdk.
> verified compatible zombienet version: 1.3.126
### Quick Start
For first-time setup, run:
```bash
just setup
```
This single command will:
- Start the chains and generate fresh metadata
- Generate PAPI descriptors from the running chains
- Install all dependencies including the generated descriptors
- Clean up chain processes
After this, you can use regular `just install` (or `bun install`) commands without issues.
### Development Workflow
```bash
# First time setup. Compiles the binaries, spawns ZN, generates PAPI types against it.
just setup
# Regular development - install dependencies
just install # or equivalently: bun install
# Run tests
just test
# Running specific tests
bun test tests/example.test.ts
bun test tests/unsigned-dev.ts
# and so on..
```
Further useful commands:
```bash
# Generate fresh descriptors (when chains are running)
just generate-descriptors
# Run with a specific preset
just run <preset>
# See available presets
just presets
# Clean everything and start fresh
just reset
# See all available commands
just help
```
## How To Write Tests
See `tests/example.test.ts`.
## Log Formatting
The tests, for each block which contains an event in which we are interested in, will emit a log like this:
```
verbose: [Rely#91][⛓ 2,039ms / 777 kb] Processing event: ...
verbose: [Para#71][⛓ 38ms / 852 kb][✍️ hd=0.22, xt=4.07, st=6.82, sum=11.11, cmp=9.74, time=2ms] Processing event: ...
```
- `Rely` indicates the relay chain (truncated to be 4 chars), `Para` indicates the teyrchain.
- Both chains' logs contain onchain (⛓) weight information, obtained from `pezframe-system`.
- `Para` logs contain more information from the collator/author's logs (✍️). They are:
- `hd` header size,
- `xt` extrinsics siz
- `st` storage proof size
- `sum` total PoV,
- `cmp` compressed PoV
- and `time`, authoring time in the collator.
@@ -0,0 +1,576 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "papi-tests",
"dependencies": {
"@polkadot-labs/hdkd": "^0.0.16",
"commander": "^14.0.0",
"polkadot-api": "^1.13.1",
"strip-ansi": "^7.1.0",
"winston": "^3.17.0",
},
"devDependencies": {
"@polkadot-api/cli": "^0.14.0",
"@types/bun": "latest",
},
"optionalDependencies": {
"@polkadot-api/descriptors": "file:.papi/descriptors",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
"@colors/colors": ["@colors/colors@1.6.0", "", {}, "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="],
"@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="],
"@dabh/diagnostics": ["@dabh/diagnostics@2.0.3", "", { "dependencies": { "colorspace": "1.1.x", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.5", "", { "os": "android", "cpu": "arm64" }, "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.5", "", { "os": "android", "cpu": "x64" }, "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.5", "", { "os": "linux", "cpu": "arm" }, "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.5", "", { "os": "linux", "cpu": "none" }, "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.5", "", { "os": "linux", "cpu": "x64" }, "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.5", "", { "os": "none", "cpu": "arm64" }, "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.5", "", { "os": "none", "cpu": "x64" }, "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.5", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@noble/curves": ["@noble/curves@1.9.2", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g=="],
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@polkadot-api/cli": ["@polkadot-api/cli@0.14.0", "", { "dependencies": { "@commander-js/extra-typings": "^14.0.0", "@polkadot-api/codegen": "0.16.3", "@polkadot-api/ink-contracts": "0.3.4", "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/known-chains": "0.9.0", "@polkadot-api/metadata-compatibility": "0.3.0", "@polkadot-api/observable-client": "0.12.0", "@polkadot-api/polkadot-sdk-compat": "2.3.2", "@polkadot-api/sm-provider": "0.1.7", "@polkadot-api/smoldot": "0.3.10", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/bizinikiwi-client": "0.4.1", "@polkadot-api/utils": "0.2.0", "@polkadot-api/wasm-executor": "^0.2.1", "@polkadot-api/ws-provider": "0.4.0", "@types/node": "^22.15.30", "commander": "^14.0.0", "execa": "^9.6.0", "fs.promises.exists": "^1.1.4", "ora": "^8.2.0", "read-pkg": "^9.0.1", "rxjs": "^7.8.2", "tsc-prog": "^2.3.0", "tsup": "^8.5.0", "typescript": "^5.8.3", "write-package": "^7.1.0" }, "bin": { "papi": "dist/main.js", "polkadot-api": "dist/main.js" } }, "sha512-bDNWSbSVWNRYYjOQgCWp321Rut4/yDxjfn0wJcACcCiWboJNu4ErZ6370yLovkbTDrOUqMPO+Mnq7cnN4xPzGA=="],
"@polkadot-api/codegen": ["@polkadot-api/codegen@0.16.3", "", { "dependencies": { "@polkadot-api/ink-contracts": "0.3.4", "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/metadata-compatibility": "0.3.0", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-vZy4r/AlkYdTPIxgvifmC2YqwVQgBA2RRLV7apGF2WzmqTTIGjLITAw+6M78X4pKZEkPR35+gVE4Wgvk+TvliQ=="],
"@polkadot-api/descriptors": ["@polkadot-api/descriptors@file:.papi/descriptors", { "peerDependencies": { "polkadot-api": ">=1.11.2" } }],
"@polkadot-api/ink-contracts": ["@polkadot-api/ink-contracts@0.3.4", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-JnCIX3GCV47u9h9poUJUJMcZ+wr1/2wCbg6NftmpqeQfin5BsWYOnKSkrMY7SEWpXC4EfACK8ucub9oo2rv/jg=="],
"@polkadot-api/json-rpc-provider": ["@polkadot-api/json-rpc-provider@0.0.4", "", {}, "sha512-9cDijLIxzHOBuq6yHqpqjJ9jBmXrctjc1OFqU+tQrS96adQze3mTIH6DTgfb/0LMrqxzxffz1HQGrIlEH00WrA=="],
"@polkadot-api/json-rpc-provider-proxy": ["@polkadot-api/json-rpc-provider-proxy@0.2.4", "", {}, "sha512-nuGoY9QpBAiRU7xmXN3nugFvPcnSu3IxTLm1OWcNTGlZ1LW5bvdQHz3JLk56+Jlyb3GJ971hqdg2DJsMXkKCOg=="],
"@polkadot-api/known-chains": ["@polkadot-api/known-chains@0.9.0", "", {}, "sha512-m/YF29FJBX9rTEgBKo63JHEg13Kw5EHbLRM9QCNMmT/mkvKlCgj5pm/T38Y8w3NS+4M8TRCyoXrUDXKUL1lX7g=="],
"@polkadot-api/logs-provider": ["@polkadot-api/logs-provider@0.0.6", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4" } }, "sha512-4WgHlvy+xee1ADaaVf6+MlK/+jGMtsMgAzvbQOJZnP4PfQuagoTqaeayk8HYKxXGphogLlPbD06tANxcb+nvAg=="],
"@polkadot-api/merkleize-metadata": ["@polkadot-api/merkleize-metadata@1.1.18", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-WKP4urGv6YDO9rGeCLX+I/I0uJCXunytNzyPfBMOK9dwJD48UpX+oHf2aNnWvP4j9zQImY/oaBQQ9Sp89ABZOw=="],
"@polkadot-api/metadata-builders": ["@polkadot-api/metadata-builders@0.12.2", "", { "dependencies": { "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-Mmivj1YZve1fCkM90eIIo73B0jkiqMA3zwhFcqYaBfuAu4x5t0rt+Ucfx76t4qxJOhG8wXp/tmdbzWIrWG909A=="],
"@polkadot-api/metadata-compatibility": ["@polkadot-api/metadata-compatibility@0.3.0", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/bizinikiwi-bindings": "0.14.0" } }, "sha512-9GNHY7YM5Jb3/TAphwd6iy46Vl1n4m7IEVwLZVZ9ZLlJLJrW+gxVOz1htE+iIHVohDak4lIJiXioza3mNKAvaw=="],
"@polkadot-api/observable-client": ["@polkadot-api/observable-client@0.12.0", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" }, "peerDependencies": { "@polkadot-api/bizinikiwi-client": "0.4.1", "rxjs": ">=7.8.0" } }, "sha512-2edI5Ke3EOWT5F4+m13alJx3zTQGJd9CPvL9iHvwyBpgJ+5T+VNf69lHpTQ16ruWGnravPG0ixkz1FhFh8ZOKA=="],
"@polkadot-api/pjs-signer": ["@polkadot-api/pjs-signer@0.6.9", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/signers-common": "0.1.10", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-WXWiIbYnN2N9VgUlGT5M1qKJBHB2EvrxPo/eHm9gjwsTwWU/x0RvInAE4P1lcNztCtyqZL41FiwEyYZnNzh1CQ=="],
"@polkadot-api/polkadot-sdk-compat": ["@polkadot-api/polkadot-sdk-compat@2.3.2", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4" } }, "sha512-rLCveP3a6Xd0r218yRqVY34lJ8bXVmE12cArbU4JFp9p8e8Jbb6xdqOdu7bQtjlZUsahhcmfIHYQSXKziST7PA=="],
"@polkadot-api/polkadot-signer": ["@polkadot-api/polkadot-signer@0.1.6", "", {}, "sha512-X7ghAa4r7doETtjAPTb50IpfGtrBmy3BJM5WCfNKa1saK04VFY9w+vDn+hwEcM4p0PcDHt66Ts74hzvHq54d9A=="],
"@polkadot-api/signer": ["@polkadot-api/signer@0.2.2", "", { "dependencies": { "@noble/hashes": "^1.8.0", "@polkadot-api/merkleize-metadata": "1.1.18", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/signers-common": "0.1.10", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-COIOOCgnwHwcwVle4eBmHNHbrqjIL7Bff94GZQlOMaOULlY1ajSthVGOZQ2b04rda6r3mlhNE+sAoDBPXPpq7w=="],
"@polkadot-api/signers-common": ["@polkadot-api/signers-common@0.1.10", "", { "dependencies": { "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/utils": "0.2.0" } }, "sha512-Or2wTrbnc6FXHbjPDf8OU2mPoFYdkcr3tx35W4FYML4OTsSnDORX+h3Q6cGbWSHdWp0VeT6QcEj6ewfaTuLREA=="],
"@polkadot-api/sm-provider": ["@polkadot-api/sm-provider@0.1.7", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/json-rpc-provider-proxy": "0.2.4" }, "peerDependencies": { "@polkadot-api/smoldot": ">=0.3" } }, "sha512-BhNKVeIFZdawpPVadXszLl8IP4EDjcLHe/GchfRRFkvoNFuwS2nNv/npYIqCviXV+dd2R8VnEELxwScsf380Og=="],
"@polkadot-api/smoldot": ["@polkadot-api/smoldot@0.3.10", "", { "dependencies": { "@types/node": "^22.15.30", "smoldot": "2.0.36" } }, "sha512-oL0Qsq2p3h2mU1/+gNq4h2rC/S99WoDiqkpmxg/phzknjXcbYXouYLSvhGbECygE1vWPVPl3IWAOjW/gcKdYKw=="],
"@polkadot-api/bizinikiwi-bindings": ["@polkadot-api/bizinikiwi-bindings@0.14.0", "", { "dependencies": { "@noble/hashes": "^1.8.0", "@polkadot-api/utils": "0.2.0", "@scure/base": "^1.2.6", "scale-ts": "^1.6.1" } }, "sha512-ZmZnw4IbxCXIqI1thEtQ83WrXwsvkqnbo0S6GRzbmrfZFh2JyVxGckPrMMLPH3ScqGgNJJkEdGGU/jSFffqTCA=="],
"@polkadot-api/bizinikiwi-client": ["@polkadot-api/bizinikiwi-client@0.4.1", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/utils": "0.2.0" } }, "sha512-g88H0ksYNxNyfidgDNpE5QvTUGb8dC5NNx12bICbMCWu4NGokbH6jW6eucWNLI0VxWCAfzGjxzibCw5NDl/6+g=="],
"@polkadot-api/utils": ["@polkadot-api/utils@0.2.0", "", {}, "sha512-nY3i5fQJoAxU4n3bD7Fs208/KR2J95SGfVc58kDjbRYN5a84kWaGEqzjBNtP9oqht49POM8Bm9mbIrkvC1Bzuw=="],
"@polkadot-api/wasm-executor": ["@polkadot-api/wasm-executor@0.2.1", "", {}, "sha512-EN3qtu9Aurz1PoEjvrvL/Z9lSMrLkRU2K1fOjzWFpI5siBgQ2eN/tMLbX1VjaSk1VhvXmbXPaqBrkfdMCxLdsg=="],
"@polkadot-api/ws-provider": ["@polkadot-api/ws-provider@0.4.0", "", { "dependencies": { "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/json-rpc-provider-proxy": "0.2.4", "ws": "^8.18.1" } }, "sha512-ZurjUHHAlQ1Ux8HiZz7mtkg1qjq6LmqxcHljcZxne0U7foCZrXdWHsohwlV8kUQUir5kXuDsNvdZN/MFCUMaVw=="],
"@polkadot-labs/hdkd": ["@polkadot-labs/hdkd@0.0.16", "", { "dependencies": { "@polkadot-labs/hdkd-helpers": "0.0.16" } }, "sha512-72vOaxBUzzEXdR5nPbsgbA3ReJ0uuBpt6ZU9TNMYACKVIRH0ivjBtsH6HRo0YGEmNbWLCma40hJ2HfVd16xgTQ=="],
"@polkadot-labs/hdkd-helpers": ["@polkadot-labs/hdkd-helpers@0.0.16", "", { "dependencies": { "@noble/curves": "^1.9.1", "@noble/hashes": "^1.8.0", "@scure/base": "^1.2.5", "micro-sr25519": "^0.1.3", "scale-ts": "^1.6.1" } }, "sha512-Lc6ydojO2X93+LyxG0sMJStP340xlqaHweLbClZXqt33himfTSba+X6cX+8bzi8tCx/sFwgWVKQpsZ55UlFgMQ=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.44.0", "", { "os": "android", "cpu": "arm" }, "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.44.0", "", { "os": "android", "cpu": "arm64" }, "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.44.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.44.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.44.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.44.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.44.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.44.0", "", { "os": "linux", "cpu": "arm" }, "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.44.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q=="],
"@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg=="],
"@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.44.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.44.0", "", { "os": "linux", "cpu": "none" }, "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.44.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.44.0", "", { "os": "linux", "cpu": "x64" }, "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.44.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.44.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.44.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ=="],
"@rx-state/core": ["@rx-state/core@0.1.4", "", { "peerDependencies": { "rxjs": ">=7" } }, "sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ=="],
"@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="],
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
"@types/bun": ["@types/bun@1.2.16", "", { "dependencies": { "bun-types": "1.2.16" } }, "sha512-1aCZJ/6nSiViw339RsaNhkNoEloLaPzZhxMOYEa7OzRzO41IGg5n/7I43/ZIAW/c+Q6cT12Vf7fOZOoVIzb5BQ=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/node": ["@types/node@22.15.32", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA=="],
"@types/normalize-package-data": ["@types/normalize-package-data@2.4.4", "", {}, "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA=="],
"@types/triple-beam": ["@types/triple-beam@1.3.5", "", {}, "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"bun-types": ["bun-types@1.2.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A=="],
"bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="],
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
"chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
"color": ["color@3.2.1", "", { "dependencies": { "color-convert": "^1.9.3", "color-string": "^1.6.0" } }, "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA=="],
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"colorspace": ["colorspace@1.1.4", "", { "dependencies": { "color": "^3.1.3", "text-hex": "1.0.x" } }, "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w=="],
"commander": ["commander@14.0.0", "", {}, "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA=="],
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
"detect-indent": ["detect-indent@7.0.1", "", {}, "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
"execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="],
"fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="],
"fecha": ["fecha@4.2.3", "", {}, "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="],
"figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
"fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="],
"fn.name": ["fn.name@1.1.0", "", {}, "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fs.promises.exists": ["fs.promises.exists@1.1.4", "", {}, "sha512-lJzUGWbZn8vhGWBedA+RYjB/BeJ+3458ljUfmplqhIeb6ewzTFWNPCR1HCiYCkXV9zxcHz9zXkJzMsEgDLzh3Q=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
"get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"index-to-position": ["index-to-position@1.1.0", "", {}, "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"kuler": ["kuler@2.0.0", "", {}, "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="],
"lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
"load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
"lodash.sortby": ["lodash.sortby@4.7.0", "", {}, "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA=="],
"log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
"logform": ["logform@2.7.0", "", { "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" } }, "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ=="],
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"micro-sr25519": ["micro-sr25519@0.1.3", "", { "dependencies": { "@noble/curves": "~1.8.0", "@noble/hashes": "~1.7.1" } }, "sha512-Tw1I3Yjq9XySsU3hsgPVkQTG3NIje070VUWtT4tb9d1tVwQqpCIBH4SM5h4Mxp2Ua4PUyPsot2F40eyJ0QnzTg=="],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"mlly": ["mlly@1.7.4", "", { "dependencies": { "acorn": "^8.14.0", "pathe": "^2.0.1", "pkg-types": "^1.3.0", "ufo": "^1.5.4" } }, "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
"normalize-package-data": ["normalize-package-data@6.0.2", "", { "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" } }, "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g=="],
"npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"one-time": ["one-time@1.0.0", "", { "dependencies": { "fn.name": "1.x.x" } }, "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g=="],
"onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="],
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"polkadot-api": ["polkadot-api@1.14.0", "", { "dependencies": { "@polkadot-api/cli": "0.14.0", "@polkadot-api/ink-contracts": "0.3.4", "@polkadot-api/json-rpc-provider": "0.0.4", "@polkadot-api/known-chains": "0.9.0", "@polkadot-api/logs-provider": "0.0.6", "@polkadot-api/metadata-builders": "0.12.2", "@polkadot-api/metadata-compatibility": "0.3.0", "@polkadot-api/observable-client": "0.12.0", "@polkadot-api/pjs-signer": "0.6.9", "@polkadot-api/polkadot-sdk-compat": "2.3.2", "@polkadot-api/polkadot-signer": "0.1.6", "@polkadot-api/signer": "0.2.2", "@polkadot-api/sm-provider": "0.1.7", "@polkadot-api/smoldot": "0.3.10", "@polkadot-api/bizinikiwi-bindings": "0.14.0", "@polkadot-api/bizinikiwi-client": "0.4.1", "@polkadot-api/utils": "0.2.0", "@polkadot-api/ws-provider": "0.4.0", "@rx-state/core": "^0.1.4" }, "peerDependencies": { "rxjs": ">=7.8.0" }, "bin": { "papi": "bin/cli.mjs", "polkadot-api": "bin/cli.mjs" } }, "sha512-9mHj6V5/lwslwtHIEXi9xoqWOJSTy+GVtSGmJ1hhUlunhYMs6w8JiiIf4WZB8P/er2+S53QNCaegbYWCFdMBgA=="],
"postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
"pretty-ms": ["pretty-ms@9.2.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"read-pkg": ["read-pkg@9.0.1", "", { "dependencies": { "@types/normalize-package-data": "^2.4.3", "normalize-package-data": "^6.0.0", "parse-json": "^8.0.0", "type-fest": "^4.6.0", "unicorn-magic": "^0.1.0" } }, "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"rollup": ["rollup@4.44.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.44.0", "@rollup/rollup-android-arm64": "4.44.0", "@rollup/rollup-darwin-arm64": "4.44.0", "@rollup/rollup-darwin-x64": "4.44.0", "@rollup/rollup-freebsd-arm64": "4.44.0", "@rollup/rollup-freebsd-x64": "4.44.0", "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", "@rollup/rollup-linux-arm-musleabihf": "4.44.0", "@rollup/rollup-linux-arm64-gnu": "4.44.0", "@rollup/rollup-linux-arm64-musl": "4.44.0", "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", "@rollup/rollup-linux-riscv64-gnu": "4.44.0", "@rollup/rollup-linux-riscv64-musl": "4.44.0", "@rollup/rollup-linux-s390x-gnu": "4.44.0", "@rollup/rollup-linux-x64-gnu": "4.44.0", "@rollup/rollup-linux-x64-musl": "4.44.0", "@rollup/rollup-win32-arm64-msvc": "4.44.0", "@rollup/rollup-win32-ia32-msvc": "4.44.0", "@rollup/rollup-win32-x64-msvc": "4.44.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA=="],
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
"scale-ts": ["scale-ts@1.6.1", "", {}, "sha512-PBMc2AWc6wSEqJYBDPcyCLUj9/tMKnLX70jLOSndMtcUoLQucP/DM0vnQo1wJAYjTrQiq8iG9rD0q6wFzgjH7g=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
"smoldot": ["smoldot@2.0.36", "", { "dependencies": { "ws": "^8.8.1" } }, "sha512-0GtHgxOs1VGs+WzpUgTQ52Zg92/q4mnIPEl+smArI4pis6aduQ6ZiXRllbDafsIb18wWYsxaBLNjBkNOB8xBrw=="],
"sort-keys": ["sort-keys@5.1.0", "", { "dependencies": { "is-plain-obj": "^4.0.0" } }, "sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ=="],
"source-map": ["source-map@0.8.0-beta.0", "", { "dependencies": { "whatwg-url": "^7.0.0" } }, "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA=="],
"spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="],
"spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
"spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="],
"spdx-license-ids": ["spdx-license-ids@3.0.21", "", {}, "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg=="],
"stack-trace": ["stack-trace@0.0.10", "", {}, "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="],
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
"text-hex": ["text-hex@1.0.0", "", {}, "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="],
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
"tinyglobby": ["tinyglobby@0.2.14", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
"tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"triple-beam": ["triple-beam@1.4.1", "", {}, "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="],
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
"tsc-prog": ["tsc-prog@2.3.0", "", { "peerDependencies": { "typescript": ">=4" } }, "sha512-ycET2d75EgcX7y8EmG4KiZkLAwUzbY4xRhA6NU0uVbHkY4ZjrAAuzTMxXI85kOwATqPnBI5C/7y7rlpY0xdqHA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tsup": ["tsup@8.5.0", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.25.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "0.8.0-beta.0", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unicorn-magic": ["unicorn-magic@0.1.0", "", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="],
"webidl-conversions": ["webidl-conversions@4.0.2", "", {}, "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="],
"whatwg-url": ["whatwg-url@7.1.0", "", { "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", "webidl-conversions": "^4.0.2" } }, "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"winston": ["winston@3.17.0", "", { "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", "winston-transport": "^4.9.0" } }, "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw=="],
"winston-transport": ["winston-transport@4.9.0", "", { "dependencies": { "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" } }, "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A=="],
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="],
"write-json-file": ["write-json-file@6.0.0", "", { "dependencies": { "detect-indent": "^7.0.1", "is-plain-obj": "^4.1.0", "sort-keys": "^5.0.0", "write-file-atomic": "^5.0.1" } }, "sha512-MNHcU3f9WxnNyR6MxsYSj64Jz0+dwIpisWKWq9gqLj/GwmA9INg3BZ3vt70/HB3GEwrnDQWr4RPrywnhNzmUFA=="],
"write-package": ["write-package@7.1.0", "", { "dependencies": { "deepmerge-ts": "^7.1.0", "read-pkg": "^9.0.1", "sort-keys": "^5.0.0", "type-fest": "^4.23.0", "write-json-file": "^6.0.0" } }, "sha512-DqUx8GI3r9BFWwU2DPKddL1E7xWfbFED82mLVhGXKlFEPe8IkBftzO7WfNwHtk7oGDHDeuH/o8VMpzzfMwmLUA=="],
"ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="],
"yoctocolors": ["yoctocolors@2.1.1", "", {}, "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ=="],
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"bun-types/@types/node": ["@types/node@24.0.3", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg=="],
"color-string/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"execa/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
"get-stream/is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
"log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
"micro-sr25519/@noble/curves": ["@noble/curves@1.8.2", "", { "dependencies": { "@noble/hashes": "1.7.2" } }, "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g=="],
"micro-sr25519/@noble/hashes": ["@noble/hashes@1.7.2", "", {}, "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"bun-types/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi-cjs/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"wrap-ansi-cjs/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
}
}
@@ -0,0 +1,133 @@
# PAPI Tests Development Workflow
# Install dependencies (descriptors needs to be generated and installed the 1st time only via `setup`)
install:
echo "🔄 Install bun dependencies"
bun install
# Generate fresh metadata and descriptors, then install all dependencies
setup:
#!/usr/bin/env bash
set -euo pipefail
echo "🧹 Killing any existing zombienet or chain processes..."
just killall
just install || echo "🚧 Generate and installed missing descriptors..."
just build-and-generate-chains
# Start zombienet and wait for ws endpoints to be ready
zombienet --provider native -l text spawn zn-s.toml 2>&1 &
CHAINS_PID=$!
echo "⏳ Waiting for chains to be ready (ws endpoints on 9944, 9945 and 9946)..."
for port in 9944 9945 9946; do
for i in {1..24}; do
if nc -z localhost $port; then
echo "✅ Port $port is open."
break
fi
sleep 10
if [ $i -eq 24 ]; then
echo "❌ Timeout waiting for port $port"
kill $CHAINS_PID || true
pkill -f zombienet || true
exit 1
fi
done
done
just generate-descriptors
echo "🧹 Cleaning up chain processes..."
kill $CHAINS_PID || true
just killall
echo "✅ Setup complete! You can now run tests or development commands."
# Clean generated files and dependencies
clean:
#!/usr/bin/env bash
set -euo pipefail
rm -rf .papi node_modules bun.lockb
echo "🧹 Cleaned .papi, node_modules, and lockfile"
# Generate descriptors from running chains (assumes chains are already running)
generate-descriptors:
#!/usr/bin/env bash
set -euo pipefail
echo "📋 Generating PAPI descriptors from ws endpoints..."
npx papi add -w ws://localhost:9946 parachain
npx papi add -w ws://localhost:9945 rc
npx papi
bun install --only-missing
echo "📋 Descriptors generated and dependencies updated"
# Run tests
test:
bun test
# Build and generate chain specs (shared logic)
build-and-generate-chains:
#!/usr/bin/env bash
set -euo pipefail
echo "🔧 Building chain-spec-builder and runtimes..."
LOG="runtime::multiblock-election=info,runtime::staking=info"
RUST_LOG=${LOG} cargo build --release -p pallet-staking-async-rc-runtime -p pallet-staking-async-parachain-runtime -p staging-chain-spec-builder
echo "✅ Removing any old chain-spec file"
rm -f ./parachain.json
rm -f ./rc.json
echo "✅ Creating parachain chain specs"
RUST_LOG=${LOG} ../../../../../target/release/chain-spec-builder \
create \
-t development \
--runtime ../../../../../target/release/wbuild/pallet-staking-async-parachain-runtime/pallet_staking_async_parachain_runtime.compact.compressed.wasm \
--relay-chain rococo-local \
--para-id 1100 \
named-preset fake-dot
mv ./chain_spec.json ./parachain.json
echo "✅ Creating rc chain specs"
RUST_LOG=${LOG} ../../../../../target/release/chain-spec-builder \
create \
-t development \
--runtime ../../../../../target/release/wbuild/pallet-staking-async-rc-runtime/fast_runtime_binary.rs.wasm \
named-preset fake-s
mv ./chain_spec.json ./rc.json
# Run a specific runtime preset, or print presets if none is given
run preset='':
#!/usr/bin/env bash
if [ -z "{{preset}}" ]; then
echo "⚠️ Please specify a preset."
just presets
else
bun run src/index.ts run --para-preset {{preset}}
fi
# Show available presets
presets:
@echo "Available parachain presets:"
@echo " fake-dev - 4 pages, small number of fake validators and nominators"
@echo " fake-dot - 32 pages, large number of fake validators and nominators"
@echo " fake-ksm - 16 pages, large number of fake validators and nominators"
@echo " real-s - 4 pages, alice and bob as validators, 500 fake nominators"
@echo " real-m - 4 pages, alice, bob, dave, eve as validators, 2000 fake nominators"
# Full development setup (clean + setup)
reset: clean setup
# Show help
help:
just --list
# kill all relevant processes. This is useful in case you see weird errors, most likely it is
# because you have other old stale ones running.
killall:
#!/usr/bin/env bash
set -euo pipefail
pkill -f zombienet || true
pkill -f chain-spec-builder || true
pkill -f polkadot || true
pkill -f polkadot-parachain || true
@@ -0,0 +1,23 @@
{
"name": "papi-tests",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@pezkuwi-api/cli": "^0.14.0",
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"@pezkuwi-labs/hdkd": "^0.0.16",
"commander": "^14.0.0",
"pezkuwi-api": "^1.13.1",
"strip-ansi": "^7.1.0",
"winston": "^3.17.0"
},
"optionalDependencies": {
"@pezkuwi-api/descriptors": "file:.papi/descriptors"
}
}
@@ -0,0 +1,170 @@
import { spawn, spawnSync } from "child_process";
import { Presets } from "./index";
import { logger } from "./utils";
import { join } from "path";
import stripAnsi from "strip-ansi";
import { createWriteStream } from "fs";
export function rcPresetFor(paraPreset: Presets): string {
return paraPreset == Presets.FakeDev ||
paraPreset == Presets.FakeDot ||
paraPreset == Presets.FakeKsm
? "fake-s"
: paraPreset;
}
export function znConfigFor(paraPreset: Presets): string {
return paraPreset == Presets.RealM ? "../zn-m.toml" : "../zn-s.toml";
}
/// Returns the teyrchain log file.
export async function runPreset(paraPreset: Presets): Promise<void> {
prepPreset(paraPreset);
const znConfig = znConfigFor(paraPreset);
logger.info(`Launching ZN config for preset: ${paraPreset}, config: ${znConfig}`);
cmd("zombienet", ["--provider", "native", "-l", "text", "spawn", znConfig], "inherit");
}
export async function runPresetUntilLaunched(
paraPreset: Presets
): Promise<{ killZn: () => void; paraLog: string | null }> {
prepPreset(paraPreset);
const znConfig = znConfigFor(paraPreset);
logger.info(`Launching ZN config for preset: ${paraPreset}, config: ${znConfig}`);
const child = spawn("zombienet", ["--provider", "native", "-l", "text", "spawn", znConfig], {
stdio: "pipe",
cwd: __dirname,
});
return new Promise<{ killZn: () => void; paraLog: string | null }>((resolve, reject) => {
const logCmds: string[] = [];
child.stdout.on("data", (data) => {
const raw: string = stripAnsi(data.toString());
if (raw.includes("Log Cmd : ")) {
raw.split("\n")
.filter((line) => line.includes("Log Cmd : "))
.forEach((line) => {
logCmds.push(line.replace("Log Cmd : ", "").trim());
});
}
// our hacky way to know ZN is done.
if (raw.includes("Teyrchain ID : 1100")) {
for (const cmd of logCmds) {
logger.info(`${cmd}`);
}
logger.info(`Launched ZN: ${paraPreset}`);
// Extract log path from the last log command
const lastCmd = logCmds[logCmds.length - 1];
const paraLog = lastCmd ? lastCmd.match(/tail -f\s+(.+\.log)/)?.[1] || null : null;
resolve({
killZn: () => {
child.kill();
logger.verbose(`Killed zn process`);
},
paraLog,
});
}
});
child.on("error", (err) => {
reject(err);
});
});
}
export async function spawnMiner(): Promise<() => void> {
logger.info(`Spawning miner in background`);
const logFile = createWriteStream(join(__dirname, "miner.log"), { flags: "a" });
const child = spawn(
"polkadot-staking-miner",
[
"--uri",
"ws://127.0.0.1:9946",
"experimental-monitor-multi-block",
"--seed-or-path",
"//Bob",
],
{ stdio: "pipe", cwd: __dirname }
);
child.stdout?.pipe(logFile);
child.stderr?.pipe(logFile);
return new Promise<() => void>((resolve, reject) => {
child.on("error", (err) => {
logger.error(`Error in miner miner: ${err}`);
reject(err);
});
resolve(() => {
logger.verbose(`Killing miner process`);
logFile.end();
child.kill();
});
});
}
function prepPreset(paraPreset: Presets): void {
const rcPreset = rcPresetFor(paraPreset);
const targetDir = "../../../../../../target";
logger.info(`Running para-preset: ${paraPreset}, rc-preset: ${rcPreset}`);
cmd("cargo", [
"build",
"--release",
`-p`,
`pallet-staking-async-rc-runtime`,
`-p`,
`pallet-staking-async-teyrchain-runtime`,
`-p`,
`staging-chain-spec-builder`,
]);
cmd("rm", ["./teyrchain.json"]);
cmd("rm", ["./rc.json"]);
cmd(join(targetDir, "/release/chain-spec-builder"), [
"create",
"-t",
"development",
"--runtime",
join(
targetDir,
"/release/wbuild/pallet-staking-async-teyrchain-runtime/pallet_staking_async_teyrchain_runtime.compact.compressed.wasm"
),
"--relay-chain",
"pezkuwichain-local",
"--para-id",
"1100",
"named-preset",
paraPreset,
]);
cmd("mv", ["chain_spec.json", "teyrchain.json"]);
cmd(join(targetDir, "/release/chain-spec-builder"), [
"create",
"-t",
"development",
"--runtime",
join(
targetDir,
"/release/wbuild/pallet-staking-async-rc-runtime/fast_runtime_binary.rs.wasm"
),
"named-preset",
rcPreset,
]);
cmd("mv", ["chain_spec.json", "rc.json"]);
}
function cmd(cmd: string, args: string[], stdio: string = "ignore"): void {
logger.info(`Running command: ${cmd} ${args.join(" ")}`);
// @ts-ignore
const result = spawnSync(cmd, args, { stdio: stdio, cwd: __dirname });
if (result.error || result.status !== 0) {
logger.error(`Error running command: ${cmd} ${args.join(" ")}`);
logger.error(`Status: ${result.status}`);
}
}
@@ -0,0 +1,67 @@
import { rcPresetFor, runPreset } from "./cmd";
import { logger } from "./utils";
import { monitorVmpQueues } from "./vmp-monitor";
import { Command } from "commander";
export enum Presets {
FakeDev = "fake-dev",
FakeDot = "fake-dot",
FakeKsm = "fake-ksm",
RealS = "real-s",
RealM = "real-m",
}
if (require.main === module) {
const program = new Command();
program
.name("staking-async-papi-tests")
.description("Run staking-async PAPI tests")
.version("0.1.0");
program
.command("run")
.description("Run a given preset. This just sets up the ZN env and runs it")
.option(
"-p, --para-preset <preset>",
"run the given teyrchain preset. The right relay preset, and zn-toml file are auto-chosen.",
Presets.FakeDev
)
.action(async (options) => {
const { paraPreset } = options;
runPreset(paraPreset);
});
program
.command("monitor-vmp")
.description("Monitor VMP (Vertical Message Passing) - both DMP and UMP queues")
.option(
"--relay-port <port>",
"Relay chain WebSocket port",
"9944"
)
.option(
"--para-port <port>",
"Teyrchain WebSocket port (optional)",
"9946"
)
.option(
"-r, --refresh <seconds>",
"Refresh interval in seconds",
"3"
)
.option(
"--para-id <id>",
"Specific teyrchain ID to monitor (default: all)"
)
.action(async (options) => {
const { relayPort, paraPort, refresh, paraId } = options;
await monitorVmpQueues({
relayPort: parseInt(relayPort),
paraPort: paraPort ? parseInt(paraPort) : undefined,
refreshInterval: parseInt(refresh),
paraId: paraId ? parseInt(paraId) : undefined
});
});
program.parse(process.argv);
}
@@ -0,0 +1,452 @@
import { readFileSync } from "fs";
import { logger, safeJsonStringify, type ApiDeclarations } from "./utils";
import { exit } from "process";
import chalk from "chalk";
export enum Chain {
Relay = "Rely",
Teyrchain = "Para",
}
interface IEvent {
module: string;
event: string;
data: any | undefined;
}
interface IBlock {
chain: Chain;
number: number;
hash: string;
events: IEvent[];
weights: any;
authorship: IAuthorshipData | null;
}
/// The on-chain weight consumed in a block, exactly as stored by `frame-system`
interface IWeight {
normal: {
ref_time: bigint;
proof_size: bigint;
};
operational: {
ref_time: bigint;
proof_size: bigint;
};
mandatory: {
ref_time: bigint;
proof_size: bigint;
};
}
/// Information obtained from the collator about authorship of a block.
interface IAuthorshipData {
/// The header size in PoV in kb.
header: number;
/// The extrinsics size in PoV in kb.
extrinsics: number;
/// The storage proof size in PoV in kb.
proof: number;
/// The compressed PoV size (sum of all the above) in kb.
compressed: number;
/// The time it took to author the block in ms.
time: number;
}
/// Print an event.
function pe(e: IEvent): string {
return `${e.module} ${e.event} ${e.data ? safeJsonStringify(e.data) : "no data"}`;
}
interface IObservableEvent {
chain: Chain;
module: string;
event: string;
dataCheck: ((data: any) => boolean) | undefined;
byBlock: number | undefined;
}
export class Observe {
e: IObservableEvent;
onPass: () => void = () => {};
constructor(
chain: Chain,
module: string,
event: string,
dataCheck: ((data: any) => boolean) | undefined = undefined,
byBlock: number | undefined = undefined,
onPass: () => void = () => {}
) {
this.e = { chain, module, event, dataCheck, byBlock };
this.onPass = onPass;
}
toString(): string {
return `Observe(${this.e.chain}, ${this.e.module}, ${this.e.event}, ${
this.e.dataCheck ? "dataCheck" : "no dataCheck"
}, ${this.e.byBlock ? this.e.byBlock : "no byBlock"})`;
}
static on(chain: Chain, mod: string, event: string): ObserveBuilder {
return new ObserveBuilder(chain, mod, event);
}
}
export class ObserveBuilder {
private chain: Chain;
private module: string;
private event: string;
private dataCheck?: (data: any) => boolean;
private byBlockVal?: number;
private onPassCallback: () => void = () => {};
constructor(chain: Chain, module: string, event: string) {
this.chain = chain;
this.module = module;
this.event = event;
}
withDataCheck(check: (data: any) => boolean): ObserveBuilder {
this.dataCheck = check;
return this;
}
byBlock(blockNumber: number): ObserveBuilder {
this.byBlockVal = blockNumber;
return this;
}
onPass(callback: () => void): ObserveBuilder {
this.onPassCallback = callback;
return this;
}
build(): Observe {
if (!this.module || !this.event) {
throw new Error("Module and event are required");
}
return new Observe(
this.chain,
this.module,
this.event,
this.dataCheck,
this.byBlockVal,
this.onPassCallback
);
}
}
export enum EventOutcome {
TimedOut = "TimedOut",
Done = "Done",
}
export class TestCase {
eventSequence: Observe[];
onKill: () => void;
allowPerChainInterleavedEvents: boolean = false;
private resolveTestPromise: (outcome: EventOutcome) => void = () => {};
/// See `example.test.ts` for more info.
constructor(e: Observe[], interleave: boolean = false, onKill: () => void = () => {}) {
this.eventSequence = e;
this.onKill = onKill;
this.allowPerChainInterleavedEvents = interleave;
}
setTestPromiseResolvers(resolve: (outcome: EventOutcome) => void) {
this.resolveTestPromise = resolve;
}
match(ours: IObservableEvent, theirs: IEvent, theirsChain: Chain): boolean {
const trivialComp =
ours.chain === theirsChain &&
ours.module === theirs.module &&
ours.event === theirs.event;
if (trivialComp) {
// note: only run data check if it is defined and all other criteria match
const dataComp = ours.dataCheck === undefined ? true : ours.dataCheck!(theirs.data);
return trivialComp && dataComp;
} else {
return false;
}
}
notTimedOut(ours: IObservableEvent, block: number): boolean {
return ours.byBlock === undefined ? true : block <= ours.byBlock;
}
// with thousand separator!
wts(num: bigint): string {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
formatWeight(weight: IWeight): string {
const weightPerMs = BigInt(Math.pow(10, 9));
const WeightPerKb = BigInt(1024);
const refTime =
weight.normal.ref_time + weight.operational.ref_time + weight.mandatory.ref_time;
const proofSize =
weight.normal.proof_size + weight.operational.proof_size + weight.mandatory.proof_size;
return `${this.wts(refTime / weightPerMs)}ms / ${this.wts(proofSize / WeightPerKb)} kb`;
}
formatAuthorship(authorship: IAuthorshipData): string {
return `hd=${authorship.header.toFixed(2)}, xt=${authorship.extrinsics.toFixed(
2
)}, st=${authorship.proof.toFixed(2)}, sum=${(
authorship.header +
authorship.extrinsics +
authorship.proof
).toFixed(2)}, cmp=${authorship.compressed.toFixed(2)}, time=${authorship.time}ms`;
}
commonLog(blockData: IBlock): string {
const number = `#${blockData.number}`;
const chain = blockData.chain === Chain.Relay
? chalk.blue(blockData.chain) // Blue for Relay - works well in both modes
: chalk.green(blockData.chain); // Green for Teyrchain - works well in both modes
const weight = `${this.formatWeight(blockData.weights)}`;
const authorship = blockData.authorship
? `[✍️ ${this.formatAuthorship(blockData.authorship)}]`
: "";
return `[${chain}${number}][${weight}]${authorship}`;
}
// returns a [`primary`, `maybeSecondary`] event to check. `primary` should always be checked first, and if not secondary is checked.
nextEvent(chain: Chain): [Observe, Observe | undefined] {
const next = this.eventSequence[0]!;
if (this.allowPerChainInterleavedEvents && this.eventSequence.length > 1) {
// get the next event in our list that is of type `chain`.
const nextOfChain = this.eventSequence.slice(1).find((e) => e.e.chain === chain);
return [next, nextOfChain];
} else {
return [next, undefined];
}
}
removeEvent(e: Observe): void {
const index = this.eventSequence.findIndex((x) => x.e === e.e);
if (index !== -1) {
this.eventSequence.splice(index, 1);
} else {
logger.warn(`Event not found for removal: ${e.toString()}`);
exit(1);
}
}
onBlock(blockData: IBlock) {
// sort from small to big
logger.debug(`${this.commonLog(blockData)} events: ${blockData.events.length}`);
const firstTimeOut = this.eventSequence
.filter((e) => e.e.byBlock)
.sort((x, y) => x.e.byBlock! - y.e.byBlock!);
if (firstTimeOut.length > 0 && blockData.number > firstTimeOut[0]!.e.byBlock!) {
logger.error(
`Block ${blockData.number} is past the first timeout at block ${firstTimeOut[0]}, exiting.`
);
this.resolveTestPromise(EventOutcome.TimedOut);
}
for (const e of blockData.events) {
this.onEvent(e, blockData);
}
}
onEvent(e: IEvent, blockData: IBlock) {
if (!this.eventSequence.length) {
logger.warn(`No events to process for ${blockData.chain}, event: ${pe(e)}`);
return;
}
logger.verbose(`${this.commonLog(blockData)} Processing event: ${pe(e)}`);
const [primary, maybeSecondary] = this.nextEvent(blockData.chain);
if (this.match(primary.e, e, blockData.chain)) {
primary.onPass();
this.removeEvent(primary);
logger.info(`Primary event passed`);
if (this.eventSequence.length === 0) {
logger.info("All events processed.");
this.resolveTestPromise(EventOutcome.Done);
} else {
logger.verbose(
`Next expected event: ${this.eventSequence[0]!.toString()}, remaining events: ${
this.eventSequence.length
}`
);
}
} else if (maybeSecondary && this.match(maybeSecondary.e, e, blockData.chain)) {
maybeSecondary.onPass();
this.removeEvent(maybeSecondary);
logger.info(`Secondary event passed`);
// when we check secondary events, we must have at least 2 items in the list, so no
// need to check for the end of list.
} else {
logger.debug(`event not relevant`);
}
}
}
// Extract information about the authoring of `block` number from the given `logFile`. This will
// work in 3 steps:
// 1. After filtering for `[Teyrchain]`, and looking at the log file from end to start, it will find
// the line containing `Prepared block for proposing at ${block}`. From this, we extract the
// authoring time in ms
// 2. Them, we only keep the rest of the log file (optimization). We find the first line thereafter
// containing `PoV size header_kb=... extrinsics_kb=... storage_proof_kb=...` and extract the
// sizes of the header, extrinsics and storage proof.
// 3. Finally, we find the first line thereafter containing `Compressed PoV size: ...kb` and extract
// the compressed size.
//
// Note: `logFile` must always relate to a teyrchain.
function extractAuthorshipData(block: number, logFile: string): IAuthorshipData | null {
if (block == 0) {
return null;
}
const log = readFileSync(logFile)
.toString()
.split("\n")
.filter((l) => l.includes("[Teyrchain]"))
.reverse();
const target = `Prepared block for proposing at ${block}`;
const findTime = (log: string[]): { time: number; readStack: string[] } => {
const readStack: string[] = [];
for (let i = 0; i < log.length; i++) {
const line = log[i];
if (!line) {
continue;
}
readStack.push(line);
if (line?.includes(target)) {
const match = line.match("([0-9]+) ms");
if (match) {
return { time: Number(match.at(1)!), readStack };
}
}
}
throw `Could not find authorship line ${target}`;
};
const findProofs = (
readStack: string[]
): { header: number; extrinsics: number; proof: number } => {
for (let i = 0; i < readStack.length; i++) {
const line = readStack[i];
const match = line?.match(
"PoV size header_kb=([0-9]+.[0-9]+) extrinsics_kb=([0-9]+.[0-9]+) storage_proof_kb=([0-9]+.[0-9]+)"
);
if (match) {
return {
header: Number(match[1]!),
extrinsics: Number(match[2]!),
proof: Number(match[3])!,
};
}
}
throw "Could not find the expected PoV data in log file.";
};
const findCompressed = (readStack: string[]): number => {
for (let i = 0; i < readStack.length; i++) {
const line = readStack[i];
const match = line?.match("Compressed PoV size: ([0-9]+.[0-9]+)kb");
if (match) {
return Number(match[1]!);
}
}
throw "Could not find the expected compressed data in log file.";
};
const { time, readStack } = findTime(log);
// reverse the read stack again, as we want the first proof related prints after we `findTime`.
readStack.reverse();
const { header, extrinsics, proof } = findProofs(readStack);
const compressed = findCompressed(readStack);
return { time, header, extrinsics, proof, compressed };
}
export async function runTest(
test: TestCase,
apis: ApiDeclarations,
paraLog: string | null
): Promise<EventOutcome> {
const { rcClient, paraClient, rcApi, paraApi } = apis;
let completionPromise: Promise<EventOutcome> = new Promise((resolve, _) => {
// Pass the resolve/reject functions to the TestCase instance
test.setTestPromiseResolvers(resolve);
rcClient.finalizedBlock$.subscribe(async (block) => {
const events = await rcApi.query.System.Events.getValue({ at: block.hash });
const weights = await rcApi.query.System.BlockWeight.getValue({ at: block.hash });
const interested = events
.filter(
(e) =>
e.event.type === "Session" ||
e.event.type === "RootOffences" ||
e.event.type === "StakingAhClient"
)
.map((e) => ({
module: e.event.type,
event: e.event.value.type,
data: e.event.value.value,
}));
test.onBlock({
chain: Chain.Relay,
number: block.number,
hash: block.hash,
events: interested,
weights: weights,
authorship: null,
});
});
paraClient.finalizedBlock$.subscribe(async (block) => {
const events = await paraApi.query.System.Events.getValue({ at: block.hash });
const weights = await paraApi.query.System.BlockWeight.getValue({ at: block.hash });
const interested = events
.filter(
(e) =>
e.event.type == "Staking" ||
e.event.type == "MultiBlockElection" ||
e.event.type == "MultiBlockElectionSigned" ||
e.event.type == "MultiBlockElectionVerifier" ||
e.event.type == "StakingRcClient"
)
.map((e) => ({
module: e.event.type,
event: e.event.value.type,
data: e.event.value.value,
}));
test.onBlock({
chain: Chain.Teyrchain,
number: block.number,
hash: block.hash,
events: interested,
weights: weights,
authorship: paraLog ? extractAuthorshipData(block.number, paraLog!) : null,
});
});
});
// Handle graceful exit on SIGINT
process.on("SIGINT", () => {
console.log("Exiting on Ctrl+C...");
rcClient.destroy();
paraClient.destroy();
test.onKill();
process.exit(0);
});
// Wait for the completionPromise to resolve/reject
const finalOutcome = await completionPromise;
rcClient.destroy();
paraClient.destroy();
logger.info(`Test completed with outcome: ${finalOutcome}, calling onKill...`);
test.onKill();
return finalOutcome;
}
@@ -0,0 +1,143 @@
import { teyrchain, rc } from "@polkadot-api/descriptors";
import {
Binary,
createClient,
type PolkadotClient,
type PolkadotSigner,
type TypedApi,
} from "polkadot-api";
import { fromBufferToBase58 } from "@polkadot-api/bizinikiwi-bindings";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import { getWsProvider } from "polkadot-api/ws-provider/web";
import { createLogger, format, transports } from "winston";
import { sr25519CreateDerive } from "@polkadot-labs/hdkd";
import { DEV_PHRASE, entropyToMiniSecret, mnemonicToEntropy, type KeyPair } from "@polkadot-labs/hdkd-helpers";
import { getPolkadotSigner } from "polkadot-api/signer";
export const GlobalTimeout = 30 * 60 * 1000;
export const aliceStash = "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY";
export const logger = createLogger({
level: process.env.LOG_LEVEL || "verbose",
format: format.combine(format.timestamp(), format.cli()),
defaultMeta: { service: "staking-papi-tests" },
transports: [new transports.Console()],
});
const miniSecret = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
const derive = sr25519CreateDerive(miniSecret);
const aliceKeyPair = derive("//Alice");
export const alice = getPolkadotSigner(aliceKeyPair.publicKey, "Sr25519", aliceKeyPair.sign);
export function deriveFrom(s: string, d: string): KeyPair {
const miniSecret = entropyToMiniSecret(mnemonicToEntropy(s));
const derive = sr25519CreateDerive(miniSecret);
return derive(d);
}
export function derivePubkeyFrom(d: string): string {
const miniSecret = entropyToMiniSecret(mnemonicToEntropy(DEV_PHRASE));
const derive = sr25519CreateDerive(miniSecret);
const keyPair = derive(d);
// Convert to SS58 address using Bizinikiwi format (42)
return ss58(keyPair.publicKey);
}
export function ss58(key: Uint8Array): string {
return fromBufferToBase58(42)(key);
}
export type ApiDeclarations = {
rcClient: PolkadotClient;
paraClient: PolkadotClient;
rcApi: TypedApi<typeof rc>;
paraApi: TypedApi<typeof teyrchain>;
};
export async function nullifySigned(
paraApi: TypedApi<typeof teyrchain>,
signer: PolkadotSigner = alice
): Promise<boolean> {
// signed and signed validation phase to 0
const call = paraApi.tx.System.set_storage({
items: [
// SignedPhase key
[
Binary.fromBytes(
Uint8Array.from([
99, 88, 172, 210, 3, 94, 196, 187, 134, 63, 169, 129, 224, 193, 119, 185,
])
),
Binary.fromBytes(Uint8Array.from([0, 0, 0, 0])),
],
// SignedValidation key
[
Binary.fromBytes(
Uint8Array.from([
72, 56, 74, 129, 110, 79, 113, 169, 54, 203, 118, 220, 158, 48, 63, 42,
])
),
Binary.fromBytes(Uint8Array.from([0, 0, 0, 0])),
],
],
}).decodedCall;
const res = await paraApi.tx.Sudo.sudo({ call }).signAndSubmit(alice);
return res.ok;
}
export async function nullifyUnsigned(
paraApi: TypedApi<typeof teyrchain>,
signer: PolkadotSigner = alice
): Promise<boolean> {
// signed and signed validation phase to 0
const call = paraApi.tx.System.set_storage({
items: [
// UnsignedPhase key
[
Binary.fromBytes(
Uint8Array.from([
194, 9, 245, 216, 235, 146, 6, 129, 181, 108, 100, 184, 105, 78, 167, 140,
])
),
Binary.fromBytes(Uint8Array.from([0, 0, 0, 0])),
],
],
}).decodedCall;
const res = await paraApi.tx.Sudo.sudo({ call }).signAndSubmit(alice);
return res.ok;
}
export async function getApis(): Promise<ApiDeclarations> {
const rcClient = createClient(withPolkadotSdkCompat(getWsProvider("ws://localhost:9945")));
const rcApi = rcClient.getTypedApi(rc);
const paraClient = createClient(withPolkadotSdkCompat(getWsProvider("ws://localhost:9946")));
const paraApi = paraClient.getTypedApi(teyrchain);
logger.info(`Connected to ${(await rcApi.constants.System.Version()).spec_name}`);
logger.info(`Connected to ${(await paraApi.constants.System.Version()).spec_name}`);
return { rcApi, paraApi, rcClient, paraClient };
}
// Safely convert anything to a string so we can compare them.
export function safeJsonStringify(data: any): string {
const bigIntReplacer = (key: string, value: any): any => {
if (typeof value === "bigint") {
return value.toString();
}
return value;
};
try {
return JSON.stringify(data, bigIntReplacer);
} catch (error: any) {
// Handle potential errors during stringification (e.g., circular references)
console.error("Error during JSON stringification:", error.message);
throw new Error(
"Failed to stringify data due to unsupported types or circular references."
);
}
}
@@ -0,0 +1,502 @@
/*
* Vertical Message Passing (VMP) Monitor
*
* This tool monitors both Downward Message Passing (DMP) and Upward Message Passing (UMP) queues
* in the Polkadot relay chain and teyrchains.
*
* ## Message Flow Overview
*
* ### Downward Message Passing (DMP): Relay Chain → Teyrchain
*
* 1. **Message Creation**: Messages are created on the relay chain (e.g., XCM messages from governance)
* 2. **Queueing**: Messages are stored in `Dmp::DownwardMessageQueues` storage on the relay chain
* - Each teyrchain has its own queue indexed by ParaId
* - Messages include the actual message bytes and the block number when sent
* 3. **Delivery**: During teyrchain validation, these messages are included in the teyrchain's
* inherent data and delivered to the teyrchain
* 4. **Processing**: The teyrchain processes these messages in `teyrchain-system` pallet
* - Messages are passed to the configured `DmpQueue` handler
* - In modern implementations, this is typically the `message-queue` pallet
* 5. **Fee Management**: `Dmp::DeliveryFeeFactor` tracks fee multipliers per teyrchain
*
* ### Upward Message Passing (UMP): Teyrchain → Relay Chain
*
* 1. **Message Creation**: Messages are created on the teyrchain (e.g., XCM messages)
* 2. **Teyrchain Queueing**: Messages are first stored in `TeyrchainSystem::PendingUpwardMessages`
* - The teyrchain tracks bandwidth limits and adjusts fee factors based on queue size
* 3. **Commitment**: In `on_finalize`, pending messages are moved to `TeyrchainSystem::UpwardMessages`
* - These are included in the teyrchain block's proof of validity
* 4. **Relay Chain Reception**: When the relay chain validates the teyrchain block:
* - UMP messages are extracted from the proof
* - Messages are processed by `inclusion` pallet's `receive_upward_messages`
* 5. **Processing**: Messages are enqueued into the `message-queue` pallet with origin `Ump(ParaId)`
* - The message-queue pallet handles actual execution with weight limits
* - Messages can be temporarily or permanently overweight
*
* ## Key Components
*
* ### Relay Chain Pallets
* - `dmp`: Manages downward message queues and delivery fees
* - `inclusion`: Handles teyrchain block validation and UMP message reception
* - `message-queue`: Generic message queue processor for various origins (UMP, DMP, HRMP)
*
* ### Teyrchain Pallets
* - `teyrchain-system`: Manages UMP message sending and DMP message reception
* - `message-queue`: Processes received DMP messages (and other message types)
*
* ## Storage Layout
*
* ### Relay Chain
* - `Dmp::DownwardMessageQueues`: Map<ParaId, Vec<InboundDownwardMessage>>
* - `Dmp::DeliveryFeeFactor`: Map<ParaId, FixedU128>
* - Well-known keys for UMP queue sizes (relay_dispatch_queue_size)
*
* ### Teyrchain
* - `TeyrchainSystem::PendingUpwardMessages`: Vec<UpwardMessage>
* - `TeyrchainSystem::UpwardMessages`: Vec<UpwardMessage> (cleared each block)
* - `TeyrchainSystem::UpwardDeliveryFeeFactor`: FixedU128
*
* ## Message Queue Pallet
*
* The `message-queue` pallet is a generic, paginated message processor that:
* - Stores messages in "books" organized by origin (e.g., Ump(ParaId), Dmp)
* - Each book contains pages of messages to handle large message volumes efficiently
* - Processes messages with strict weight limits to ensure block production
* - Handles overweight messages that exceed processing limits
* - Emits events for processed, overweight, and failed messages
*
* ## Bandwidth and Fee Management
*
* - Both DMP and UMP implement dynamic fee mechanisms
* - Fees increase when queues grow large (deterring spam)
* - Fees decrease when queues are small (encouraging usage)
* - Bandwidth limits prevent any single teyrchain from monopolizing message passing
*
* ## VMP Message Limits and Risk Analysis
*
* There are 4 key categories of limits in the VMP system:
*
* ### 1. Single Message Size Limit
*
* **DMP (Downward):**
* - Enforced at: `polkadot/runtime/teyrchains/src/dmp.rs:189` in `can_queue_downward_message()`
* - Configuration: `max_downward_message_size`
* - Check: Rejects if `serialized_len > config.max_downward_message_size`
*
* **UMP (Upward):**
* - Teyrchain enforcement: `cumulus/pallets/teyrchain-system/src/lib.rs:1665` in `send_upward_message()`
* - Relay validation: `polkadot/runtime/teyrchains/src/inclusion/mod.rs:967` in `check_upward_messages()`
* - Configuration: `max_upward_message_size` (hard bound: 128KB defined as MAX_UPWARD_MESSAGE_SIZE_BOUND)
*
* ### 2. Queue Total Size (Bytes)
*
* **DMP:**
* - Max capacity: `MAX_POSSIBLE_ALLOCATION / max_downward_message_size`
* - Calculated in: `polkadot/runtime/teyrchains/src/dmp.rs:318-319` in `dmq_max_length()`
* - Enforced at: `polkadot/runtime/teyrchains/src/dmp.rs:194` in `can_queue_downward_message()`
*
* **UMP:**
* - Teyrchain check: `cumulus/pallets/teyrchain-system/src/lib.rs:369-373` (respects relay's remaining capacity)
* - Relay limit: `max_upward_queue_size` enforced at `polkadot/runtime/teyrchains/src/inclusion/mod.rs:977-980`
*
* ### 3. Queue Total Count (Messages)
*
* **DMP:**
* - No explicit total message count limit
* - Only implicitly limited by total queue size
*
* **UMP:**
* - Relay limit: `max_upward_queue_count` at `polkadot/runtime/teyrchains/src/inclusion/mod.rs:958-961`
* - Teyrchain respects relay's `remaining_count` from `relay_dispatch_queue_remaining_capacity`
*
* ### 4. Per-Block Append Limit
*
* **DMP:**
* - No explicit per-block limit for senders
* - Receivers process up to `processed_downward_messages` per block
*
* **UMP:**
* - Configuration: `max_upward_message_num_per_candidate`
* - Teyrchain limit: `cumulus/pallets/teyrchain-system/src/lib.rs:386` in `on_finalize()`
* - Relay validation: `polkadot/runtime/teyrchains/src/inclusion/mod.rs:949-952`
* - Max bound: 16,384 messages (MAX_UPWARD_MESSAGE_NUM in `polkadot/teyrchain/src/primitives.rs:436`)
*
* ### Receiver-side Risk: Weight Exhaustion
*
* Both DMP and UMP messages are processed through the message-queue pallet:
* - Weight check: bizinikiwi/frame/message-queue/src/lib.rs:1591 in `process_message_payload()`
* - Messages exceeding `overweight_limit` are marked as overweight
* - Configuration: `ServiceWeight` and `IdleMaxServiceWeight`
* - Overweight handling: Permanently overweight messages require manual execution via `execute_overweight()`
*
* **Key Insight**: DMP is less restrictive with only size-based limits, while UMP implements all four types of limits,
* providing more granular control over message flow.
*/
import { createClient, type PolkadotClient, type TypedApi } from "polkadot-api";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import { getWsProvider } from "polkadot-api/ws-provider/web";
import { rc, teyrchain } from "@polkadot-api/descriptors";
import { logger } from "./utils";
interface MonitorOptions {
relayPort: number;
paraPort?: number;
refreshInterval: number;
paraId?: number;
}
interface DmpQueueInfo {
paraId: number;
messageCount: number;
totalSize: number;
avgMessageSize: number;
feeFactor: string;
messages: Array<{
size: number;
sentAt: number;
}>;
}
interface UmpQueueInfo {
paraId: number;
relayQueueCount: number;
relayQueueSize: number;
pendingCount?: number;
pendingSize?: number;
}
interface MessageStats {
dmp: {
totalQueues: number;
totalMessages: number;
totalSize: number;
avgMessagesPerQueue: number;
avgSizePerMessage: number;
queues: DmpQueueInfo[];
};
ump: {
totalParas: number;
totalMessages: number;
totalSize: number;
queues: UmpQueueInfo[];
};
}
export async function monitorVmpQueues(options: MonitorOptions): Promise<void> {
const relayWsUrl = `ws://127.0.0.1:${options.relayPort}`;
const paraWsUrl = options.paraPort ? `ws://127.0.0.1:${options.paraPort}` : null;
logger.info(`🚀 Connecting to relay chain at ${relayWsUrl}`);
if (paraWsUrl) {
logger.info(`🚀 Connecting to teyrchain at ${paraWsUrl}`);
}
logger.info(`📊 Monitoring VMP queues${options.paraId ? ` for teyrchain ${options.paraId}` : ' for all teyrchains'}`);
logger.info(`⏱️ Refresh interval: ${options.refreshInterval}s`);
logger.info("");
try {
// Connect to relay chain
const relayWsProvider = getWsProvider(relayWsUrl);
const relayClient = createClient(withPolkadotSdkCompat(relayWsProvider));
const relayApi = relayClient.getTypedApi(rc);
// Test relay connection
const relayChainSpec = await relayClient.getChainSpecData();
logger.info(`✅ Connected to relay chain: ${relayChainSpec.name}`);
// Connect to teyrchain if port provided
let paraClient: PolkadotClient | null = null;
let paraApi: any | null = null;
if (paraWsUrl) {
const paraWsProvider = getWsProvider(paraWsUrl);
paraClient = createClient(withPolkadotSdkCompat(paraWsProvider));
// Use teyrchain descriptor for the teyrchain API
paraApi = paraClient.getTypedApi(teyrchain);
const paraChainSpec = await paraClient.getChainSpecData();
logger.info(`✅ Connected to teyrchain: ${paraChainSpec.name}`);
}
const version = await relayApi.constants.System.Version();
logger.info(`Relay chain: ${version.spec_name} v${version.spec_version}`);
logger.info("");
// Start monitoring loop
while (true) {
try {
await displayMessageStatus(relayApi, paraApi, options.paraId);
} catch (error) {
logger.error("Error fetching message data:", error);
}
await sleep(options.refreshInterval * 1000);
// Clear screen for next update
if (process.stdout.isTTY) {
process.stdout.write('\x1Bc');
}
}
} catch (error) {
logger.error("Failed to initialize monitoring:", error);
process.exit(1);
}
}
async function displayMessageStatus(relayApi: TypedApi<typeof rc>, paraApi: any | null, specificParaId?: number): Promise<void> {
const timestamp = new Date().toLocaleString();
console.log("╔═══════════════════════════════════════════════════════════════╗");
console.log("║ Vertical Message Passing Monitor ║");
console.log(`║ Last updated: ${timestamp.padEnd(45)}`);
console.log("╚═══════════════════════════════════════════════════════════════╝");
console.log();
try {
const stats = await fetchMessageStats(relayApi, paraApi, specificParaId);
// Display DMP Statistics
console.log("📥 DMP (Downward Message Passing) Statistics:");
console.log(` Active Queues: ${stats.dmp.totalQueues}`);
console.log(` Total Messages: ${stats.dmp.totalMessages}`);
console.log(` Total Size: ${formatBytes(stats.dmp.totalSize)}`);
if (stats.dmp.totalQueues > 0) {
console.log(` Avg Messages/Queue: ${stats.dmp.avgMessagesPerQueue.toFixed(1)}`);
}
if (stats.dmp.totalMessages > 0) {
console.log(` Avg Message Size: ${formatBytes(stats.dmp.avgSizePerMessage)}`);
}
console.log();
// Display UMP Statistics
console.log("📤 UMP (Upward Message Passing) Statistics:");
console.log(` Active Paras: ${stats.ump.totalParas}`);
console.log(` Total Messages: ${stats.ump.totalMessages}`);
console.log(` Total Size: ${formatBytes(stats.ump.totalSize)}`);
console.log();
// Display DMP queue details
if (stats.dmp.queues.length > 0) {
console.log("📋 DMP Queue Details:");
console.log("┌─────────────┬───────────┬─────────────┬─────────────┬─────────────┐");
console.log("│ Para ID │ Messages │ Total Size │ Avg Size │ Fee Factor │");
console.log("├─────────────┼───────────┼─────────────┼─────────────┼─────────────┤");
for (const queue of stats.dmp.queues.slice(0, 10)) {
console.log(
`${queue.paraId.toString().padEnd(11)}${queue.messageCount.toString().padEnd(9)}${formatBytes(queue.totalSize).padEnd(11)}${formatBytes(queue.avgMessageSize).padEnd(11)}${queue.feeFactor.padEnd(11)}`
);
}
console.log("└─────────────┴───────────┴─────────────┴─────────────┴─────────────┘");
if (stats.dmp.queues.length > 10) {
console.log(`... and ${stats.dmp.queues.length - 10} more DMP queues`);
}
console.log();
}
// Display UMP queue details
if (stats.ump.queues.length > 0) {
console.log("📋 UMP Queue Details:");
console.log("┌─────────────┬─────────────┬─────────────┬──────────────┬──────────────┐");
console.log("│ Para ID │ Relay Msgs │ Relay Size │ Pending Msgs │ Pending Size │");
console.log("├─────────────┼─────────────┼─────────────┼──────────────┼──────────────┤");
for (const queue of stats.ump.queues.slice(0, 10)) {
const pendingStr = queue.pendingCount !== undefined ? queue.pendingCount.toString() : "N/A";
const pendingSizeStr = queue.pendingSize !== undefined ? formatBytes(queue.pendingSize) : "N/A";
console.log(
`${queue.paraId.toString().padEnd(11)}${queue.relayQueueCount.toString().padEnd(11)}${formatBytes(queue.relayQueueSize).padEnd(11)}${pendingStr.padEnd(12)}${pendingSizeStr.padEnd(12)}`
);
}
console.log("└─────────────┴─────────────┴─────────────┴──────────────┴──────────────┘");
if (stats.ump.queues.length > 10) {
console.log(`... and ${stats.ump.queues.length - 10} more UMP queues`);
}
console.log();
}
// Show most active queues
const topDmpQueues = stats.dmp.queues
.filter(q => q.messages.length > 0)
.sort((a, b) => b.messageCount - a.messageCount)
.slice(0, 3);
if (topDmpQueues.length > 0) {
console.log("🔥 Most Active DMP Queues:");
for (const queue of topDmpQueues) {
console.log(` Para ${queue.paraId}: ${queue.messageCount} messages, latest at block ${Math.max(...queue.messages.map(m => m.sentAt))}`);
}
console.log();
}
// Warning thresholds
const totalMessages = stats.dmp.totalMessages + stats.ump.totalMessages;
const totalSize = stats.dmp.totalSize + stats.ump.totalSize;
if (totalMessages > 1000) {
console.log("⚠️ WARNING: High message count detected!");
}
if (totalSize > 10 * 1024 * 1024) { // 10MB
console.log("⚠️ WARNING: High memory usage detected!");
}
// Note about teyrchain connection
if (!paraApi && specificParaId) {
console.log("️ Note: Connect to teyrchain with --para-port to see pending UMP messages");
}
} catch (error) {
console.log("❌ Error fetching message statistics:");
console.log(` ${error}`);
}
console.log();
console.log("Press Ctrl+C to stop monitoring");
console.log();
}
async function fetchMessageStats(relayApi: TypedApi<typeof rc>, paraApi: any | null, specificParaId?: number): Promise<MessageStats> {
// Fetch DMP stats from relay chain
const [downwardMessageQueues, deliveryFeeFactors] = await Promise.all([
relayApi.query.Dmp.DownwardMessageQueues.getEntries(),
relayApi.query.Dmp.DeliveryFeeFactor.getEntries()
]);
const dmpQueues: DmpQueueInfo[] = [];
let dmpTotalMessages = 0;
let dmpTotalSize = 0;
// Process DMP queues
for (const { keyArgs: [paraId], value: messages } of downwardMessageQueues) {
if (specificParaId !== undefined && paraId !== specificParaId) {
continue;
}
const messageCount = messages.length;
if (messageCount === 0) continue;
const messageSizes = messages.map((msg) => {
return msg.msg.asBytes().length
});
const queueTotalSize = messageSizes.reduce((sum: number, size: number) => sum + size, 0);
const avgMessageSize = messageCount > 0 ? queueTotalSize / messageCount : 0;
const feeFactorEntry = deliveryFeeFactors.find(entry => entry.keyArgs[0] === paraId);
const feeFactorRaw = feeFactorEntry?.value || 1_000_000_000_000_000_000n;
const feeFactorValue = typeof feeFactorRaw === 'bigint' ?
Number(feeFactorRaw) / 1_000_000_000_000_000_000 :
typeof feeFactorRaw === 'number' ?
feeFactorRaw / 1_000_000_000_000_000_000 :
1.0;
const feeFactor = feeFactorValue.toFixed(6);
dmpQueues.push({
paraId,
messageCount,
totalSize: queueTotalSize,
avgMessageSize,
feeFactor,
messages: messages.map((msg: any, idx: number) => ({
size: messageSizes[idx]!,
sentAt: msg.sent_at || 0
}))
});
dmpTotalMessages += messageCount;
dmpTotalSize += queueTotalSize;
}
// Sort DMP queues by message count
dmpQueues.sort((a, b) => b.messageCount - a.messageCount);
// Fetch UMP stats
const umpQueues: UmpQueueInfo[] = [];
let umpTotalMessages = 0;
let umpTotalSize = 0;
// Only check UMP for specified paraId when monitoring a specific teyrchain
const paraIds = specificParaId ? [specificParaId] : [];
for (const paraId of paraIds) {
try {
// Try to get the relay dispatch queue size from well-known key
// This is stored by the inclusion pallet when processing UMP messages
const wellKnownKey = `0x` +
`3a6865617070616765735f73746f726167653a` + // :heappages_storage:
`0000` + // twox128("Teyrchains")
`0000` + // twox128("RelayDispatchQueueSize")
`0000` + // twox64(paraId) - simplified, would need proper encoding
paraId.toString(16).padStart(8, '0');
// For now, we'll check if the para has any activity in message queue
// This is a simplified approach - in production you'd query the actual storage
const umpQueueInfo: UmpQueueInfo = {
paraId,
relayQueueCount: 0,
relayQueueSize: 0
};
// If we have teyrchain connection and it matches our paraId, get pending messages
if (paraApi && paraId === specificParaId) {
try {
const pendingMessages = await paraApi.query.TeyrchainSystem.PendingUpwardMessages();
if (pendingMessages) {
umpQueueInfo.pendingCount = pendingMessages.length;
umpQueueInfo.pendingSize = pendingMessages.reduce((sum: number, msg: any) => {
return sum + (Array.isArray(msg) ? msg.length : 0);
}, 0);
}
} catch (error) {
// Teyrchain might not have this storage item
}
}
// Only add if there's any activity
if (umpQueueInfo.relayQueueCount > 0 || umpQueueInfo.pendingCount) {
umpQueues.push(umpQueueInfo);
umpTotalMessages += umpQueueInfo.relayQueueCount + (umpQueueInfo.pendingCount || 0);
umpTotalSize += umpQueueInfo.relayQueueSize + (umpQueueInfo.pendingSize || 0);
}
} catch (error) {
// Continue with next para if this one fails
}
}
return {
dmp: {
totalQueues: dmpQueues.length,
totalMessages: dmpTotalMessages,
totalSize: dmpTotalSize,
avgMessagesPerQueue: dmpQueues.length > 0 ? dmpTotalMessages / dmpQueues.length : 0,
avgSizePerMessage: dmpTotalMessages > 0 ? dmpTotalSize / dmpTotalMessages : 0,
queues: dmpQueues
},
ump: {
totalParas: umpQueues.length,
totalMessages: umpTotalMessages,
totalSize: umpTotalSize,
queues: umpQueues
}
};
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
@@ -0,0 +1,122 @@
import { Chain, Observe } from "../src/test-case";
import { logger, nullifySigned, nullifyUnsigned, type ApiDeclarations } from "../src/utils";
// An unsigned solution scenario:
//
// When no staking-miner is running (and for simplicity the signed phase is also set to zero). We
// expect an unsigned solution to successfullly proceed and submit a solution with `minerPages` out
// of the total `pages`.
export function commonUnsignedSteps(
expectedValidatorSetCount: number,
minerPages: number,
pages: number,
doNullifySigned: boolean,
apis: ApiDeclarations
): Observe[] {
return [
// first relay session change at block 11
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
// by block 10 we will plan a new era
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
.onPass(() => {
if (doNullifySigned) {
nullifySigned(apis.paraApi).then((ok) => {
logger.verbose("Nullified signed phase:", ok);
});
}
}),
// eventually we will verify all pages
...Array.from({ length: minerPages }, (_, __) => {
return Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Verified");
}),
// eventually it will be queued
Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Queued"),
// eventually multiblock election will transition to `Done`
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
(x: any) => x.to.type === "Done"
),
// eventually we will export all 4 pages to staking
// export events.
...Array.from({ length: pages }, (_, __) => {
return Observe.on(Chain.Teyrchain, "Staking", "PagedElectionProceeded");
}),
// eventually multiblock goes back to `Off`
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
(x: any) => x.to.type === "Off"
),
// eventually we will send it back to RC
Observe.on(Chain.Relay, "StakingAhClient", "ValidatorSetReceived").withDataCheck(
(x: any) => x.id === 1 && x.new_validator_set_count === expectedValidatorSetCount
),
Observe.on(Chain.Relay, "Session", "NewQueued"),
// eventually we will receive a session report back in AH with activation timestamp
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived").withDataCheck(
(x) => x.activation_timestamp !== undefined
),
// eventually we will have era paid (inflation)
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
].map((s) => s.build());
}
// A signed solution scenario.
//
// This test expect you to call `spawnMiner` in the final test code. A full solution of `pages` is
// expected to be submitted.
export function commonSignedSteps(
pages: number,
expectedValidatorSetCount: number,
apis: ApiDeclarations
): Observe[] {
return [
// first relay session change at block 11
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
// by block 10 we will plan a new era
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
.onPass(() => {
nullifyUnsigned(apis.paraApi).then((ok) => {
logger.verbose("Nullified unsigned phase:", ok);
});
}),
// Eventually a signed submission is registered...
Observe.on(Chain.Teyrchain, "MultiBlockElectionSigned", "Registered"),
// ... and exact number of pages are generated
...Array.from({ length: pages }, () =>
Observe.on(Chain.Teyrchain, "MultiBlockElectionSigned", "Stored")
),
// ... and exact number of pages are verified
...Array.from({ length: pages }, () =>
Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Verified")
),
// eventually it will be queued
Observe.on(Chain.Teyrchain, "MultiBlockElectionVerifier", "Queued"),
// eventually the signed submitter is rewarded.
// TODO: check rewarded account is Bob
Observe.on(Chain.Teyrchain, "MultiBlockElectionSigned", "Rewarded"),
// eventually multiblock election will transition to `Done`
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
(x: any) => x.to.type === "Done"
),
// eventually we will export all pages.
...Array.from({ length: pages }, () =>
Observe.on(Chain.Teyrchain, "Staking", "PagedElectionProceeded")
),
// eventually multiblock goes back to `Off`
Observe.on(Chain.Teyrchain, "MultiBlockElection", "PhaseTransitioned").withDataCheck(
(x: any) => x.to.type === "Off"
),
// eventually we will send it back to RC
Observe.on(Chain.Relay, "StakingAhClient", "ValidatorSetReceived").withDataCheck(
(x: any) => x.id === 1 && x.new_validator_set_count === expectedValidatorSetCount
),
Observe.on(Chain.Relay, "Session", "NewQueued"),
// eventually we will receive a session report back in AH with activation timestamp
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived").withDataCheck(
(x) => x.activation_timestamp !== undefined
),
// eventually we will have era paid (inflation)
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
].map((s) => s.build());
}
@@ -0,0 +1,67 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout, logger, nullifySigned } from "../src/utils";
/// This is the preset against which your test will run. See the README or `PResets` for more info.
const PRESET: Presets = Presets.FakeKsm;
test(
`example test with preset ${PRESET}`,
async () => {
/// We run the test with our defined preset.
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
/// Grab PAPI Apis to both relay and teyrchain instance of the ZN.
const apis = await getApis();
// Our test is defined here. We expect a sequence of events to be observed in RC or
// Teyrchain. The events that we can observe are defined in `test-case.ts`'s `runTest`. In
// short, they are all of the events related to staking.
const testCase = new TestCase(
[
Observe.on(Chain.Relay, "Session", "NewSession")
// An event can be expected to happen by a certain block
.byBlock(11)
// And it can execute a callback when it passes.
.onPass(() => {
logger.verbose("New session observed on relay chain");
})
// and we can check the data of the event.
.withDataCheck((x: any) => {
logger.verbose("shall we check the data? maybe", x);
return true
}),
// add more `Observe`s here
].map((s) => s.build()),
// Passing this to true will allow events to be _interleaved_. If set to `false`, the
// above sequence of events are expected to happen in a strict order. If `true`, the
// events of each `Chain` must happen in a strict order, but intra-chain events can come
// in any order. For example, assume we have the following 4 observes in our test case:
// 1. Observe.on(Chain.Relay, "Module", "Event1")
// 2. Observe.on(Chain.Relay, "Module", "Event2")
// 3. Observe.on(Chain.Para, "Module", "Event3")
// 4. Observe.on(Chain.Para, "Module", "Event4")
//
// Without interleaving, 1 -> 4 has to be observed as-is.
//
// With interleaving, at any point in time, the first unobserved event of each chain
// type is acceptable. For example, the following is valid:
//
// 3 -> 1 -> 4 -> 2
//
// In some sense, with `interleave = true`, we break apart the test case into two stacks
// that need to be popped in order, while in interleave = false, it is one stack.
true,
// Something to happen when the test is over. Always kill ZN, and any other processes
// you might spawn.
() => {
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,52 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout, logger, nullifyUnsigned } from "../src/utils";
import { commonSignedSteps } from "./common";
const PRESET: Presets = Presets.FakeDot;
test(
`pruning era with signed (full solution) on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const killMiner = await spawnMiner();
// This test has no real assertions. Change the `HistoryDepth` to 1 in the runtime, run it,
// and observe the logs and PoV sizes.
const steps = [
// first relay session change at block 11
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11)
.onPass(() => {
nullifyUnsigned(apis.paraApi).then((ok) => {
logger.verbose("Nullified signed phase:", ok);
});
}),
Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
.withDataCheck((x) => x.index == 0),
Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
.withDataCheck((x) => x.index == 1),
// Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
// .withDataCheck((x) => x.index == 2),
// Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
// .withDataCheck((x) => x.index == 3),
// Observe.on(Chain.Teyrchain, "Staking", "EraPruned")
// .withDataCheck((x) => x.index == 4),
].map((s) => s.build())
const testCase = new TestCase(
steps,
true,
() => {
killMiner();
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout * 10 }
);
@@ -0,0 +1,32 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
import { EventOutcome, runTest, TestCase } from "../src/test-case";
import {
getApis,
GlobalTimeout,
} from "../src/utils";
import { commonSignedSteps } from "./common";
const PRESET: Presets = Presets.FakeDev;
test(
`signed solution on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const killMiner = await spawnMiner();
const testCase = new TestCase(
commonSignedSteps(4, 10, apis),
true,
() => {
killMiner();
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,30 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
import { EventOutcome, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout} from "../src/utils";
import { commonSignedSteps } from "./common";
const PRESET: Presets = Presets.FakeDot;
test(
`signed solution on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const killMiner = await spawnMiner();
const testCase = new TestCase(
commonSignedSteps(32, 500, apis),
true,
() => {
killMiner();
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,30 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched, spawnMiner } from "../src/cmd";
import { EventOutcome, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout } from "../src/utils";
import { commonSignedSteps } from "./common";
const PRESET: Presets = Presets.FakeKsm;
test(
`signed solution on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const killMiner = await spawnMiner();
const testCase = new TestCase(
commonSignedSteps(16, 1000, apis),
true,
() => {
killMiner();
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,103 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash } from "../src/utils";
// Note the `RealM` preset as this test MUST run with enough validators such that we can disable one.
const PRESET: Presets = Presets.RealM;
test(
`slashing with disabling on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
let aliceExposedNominators = 0;
let pages = 0;
const steps = [
// first relay session change at block 11
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
// eventually AH will will be instructed to plan a new session.
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
.onPass(() => {
nullifySigned(apis.paraApi);
}),
// Eventually we will receive an activation timestamp in AH, meaning the first era was complete.
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived")
.withDataCheck((x) => x.activation_timestamp !== undefined)
.onPass(() => {
// upon completion, submit a slash to rc
logger.info("Submitting slash to RC");
const call = apis.rcApi.tx.RootOffences.create_offence({
offenders: [
// alice//Stash, 50%, which will cause any disabling. See `DisablingStrategy` in `./runtimes/teyrchain`.
[aliceStash, 500000000],
],
maybe_identifications: undefined,
maybe_session_index: undefined,
}).decodedCall;
apis.rcApi.tx.Sudo.sudo({ call })
.signAndSubmit(alice)
.then((res: any) => {
logger.verbose("Slash submission result:", res.ok);
});
}),
// we will receive the root offence event
Observe.on(Chain.Relay, "RootOffences", "OffenceCreated"),
// Session pallet has insta-disabled this validator.
Observe.on(Chain.Relay, "Session", "ValidatorDisabled").withDataCheck(
(x: any) => x.validator === aliceStash
),
// Rest of the events are the same as in the non-disabling test.
// eventually we will receive an offence in the teyrchain, first the rc-client
Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck(
(x: any) => x.offences_count === 1
),
// then staking
Observe.on(Chain.Teyrchain, "Staking", "OffenceReported")
.withDataCheck((x: any) => x.offence_era === 1 && x.fraction === 500000000)
.onPass(async () => {
// let's calculate how many pages of exposure alice has -- this will impact the number of next events.
const overview = await apis.paraApi.query.Staking.ErasStakersOverview.getValue(
1,
aliceStash
);
pages = overview?.page_count || 0;
aliceExposedNominators = overview?.nominator_count || 0;
// TODO: lazily create the `Slashed` and `SlashComputed` based on this
logger.verbose(
`Alice has ${aliceExposedNominators} exposed nominators (${pages}) whom we expect to slash later`
);
}),
// then staking will calculate the slashes, we only check 1 page
Observe.on(Chain.Teyrchain, "Staking", "SlashComputed").withDataCheck(
(x: any) => x.page === 0
),
// staking will eventually bump to active era 2, where slashes will be applied.
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated").withDataCheck(
(x: any) => x.active_era === 2
),
// staking will apply slashes, we only check one slash.
Observe.on(Chain.Teyrchain, "Staking", "Slashed"),
];
const testCase = new TestCase(
steps.map((s) => s.build()),
true,
() => {
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,96 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash } from "../src/utils";
const PRESET: Presets = Presets.RealS;
test(
`slashing without disabling on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
let aliceExposedNominators = 0;
let pages = 0;
const steps = [
// first relay session change at block 11
Observe.on(Chain.Relay, "Session", "NewSession").byBlock(11),
// eventually AH will will be instructed to plan a new session.
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated")
.withDataCheck((x: any) => x.active_era == 0 && x.planned_era == 1)
.onPass(() => {
nullifySigned(apis.paraApi);
}),
// Eventually we will receive an activation timestamp in AH, meaning the first era was complete.
Observe.on(Chain.Teyrchain, "StakingRcClient", "SessionReportReceived")
.withDataCheck((x) => x.activation_timestamp !== undefined)
.onPass(() => {
// upon completion, submit a slash to rc
logger.info("Submitting slash to RC");
const call = apis.rcApi.tx.RootOffences.create_offence({
offenders: [
// alice//Stash, 10%, which will NOT cause any disabling. See `DisablingStrategy` in `./runtimes/teyrchain`.
[aliceStash, 100000000],
],
maybe_identifications: undefined,
maybe_session_index: undefined,
}).decodedCall;
apis.rcApi.tx.Sudo.sudo({ call })
.signAndSubmit(alice)
.then((res: any) => {
logger.verbose("Slash submission result:", res.ok);
});
}),
// we will receive the root offence event
Observe.on(Chain.Relay, "RootOffences", "OffenceCreated"),
// eventually we will receive an offence in the teyrchain, first the rc-client
Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck(
(x: any) => x.offences_count === 1
),
// then staking
Observe.on(Chain.Teyrchain, "Staking", "OffenceReported")
.withDataCheck((x: any) => x.offence_era === 1 && x.fraction === 100000000)
.onPass(async () => {
// let's calculate how many pages of exposure alice has -- this will impact the number of next events.
const overview = await apis.paraApi.query.Staking.ErasStakersOverview.getValue(
1,
aliceStash
);
pages = overview?.page_count || 0;
aliceExposedNominators = overview?.nominator_count || 0;
// TODO: lazily create the `Slashed` and `SlashComputed` based on this
logger.verbose(
`Alice has ${aliceExposedNominators} exposed nominators (${pages}) whom we expect to slash later`
);
}),
// then staking will calculate the slasheh, we only check 1 page
Observe.on(Chain.Teyrchain, "Staking", "SlashComputed").withDataCheck(
(x: any) => x.page === 0
),
// staking will eventually bump to active era 2, where slashes will be applied.
Observe.on(Chain.Teyrchain, "Staking", "EraPaid"),
Observe.on(Chain.Teyrchain, "Staking", "SessionRotated").withDataCheck(
(x: any) => x.active_era === 2
),
// staking will apply slashes, we only check one slash.
Observe.on(Chain.Teyrchain, "Staking", "Slashed"),
];
const testCase = new TestCase(
steps.map((s) => s.build()),
true,
() => {
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,97 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { alice, getApis, GlobalTimeout, logger, nullifySigned, aliceStash, derivePubkeyFrom, ss58 } from "../src/utils";
const PRESET: Presets = Presets.RealS;
test(
`slashing spam test on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
// total number of offences to send.
const target = 1000;
// the size of each batch.
const batchSize = 100;
const numBatches = Math.ceil(target / batchSize);
let sent = 0;
let received = 0;
// onchain-page-size for offence queueing in RC is 50, so we expect 20 pages for 1000 offences.
const steps = [
// first relay session change at block 11, just a sanity check
Observe.on(Chain.Relay, "Session", "NewSession")
.byBlock(11)
.onPass(async () => {
logger.info(`Submitting ${target} offences in batches of ${batchSize}`);
// Calculate number of batches needed
let nonce = await apis.rcApi.apis.AccountNonceApi.account_nonce(ss58(alice.publicKey));
logger.info(`Alice nonce at start: ${nonce}`);
for (let batchIndex = 0; batchIndex < numBatches; batchIndex++) {
const start = batchIndex * batchSize;
const end = Math.min(start + batchSize, target);
const currentBatchSize = end - start;
logger.info(`Processing batch ${batchIndex + 1}/${numBatches}: offences ${start} to ${end - 1}`);
// Create batch of offence calls
const offenceCalls = Array.from({ length: currentBatchSize }, (_, i) => {
const offenceIndex = start + i;
logger.debug(`Preparing offence ${offenceIndex}: ${derivePubkeyFrom(`//${offenceIndex}`)}`);
return apis.rcApi.tx.RootOffences.report_offence({
offences: [[
[derivePubkeyFrom(`//${offenceIndex}`), { total: BigInt(0), own: BigInt(0), others: [] }],
0, // session index
BigInt(offenceIndex), // time slot, each being unique
100000000 // slash ppm
]]
}).decodedCall;
});
// Submit this batch as a single transaction
try {
const batchCall = apis.rcApi.tx.Utility.force_batch({ calls: offenceCalls }).decodedCall;
const result = apis.rcApi.tx.Sudo.sudo({ call: batchCall })
.signAndSubmit(alice, { at: "best", nonce: nonce });
logger.info(`Batch ${batchIndex + 1} submitted`);
nonce += 1;
sent += currentBatchSize;
} catch (error) {
logger.error(`Batch ${batchIndex + 1} failed:`, error);
// Continue with next batch even if this one fails
}
}
}),
// in the meantime, we expect to see on the AH side:
...Array.from({ length: 20 }, (_, __) =>
Observe.on(Chain.Teyrchain, "StakingRcClient", "OffenceReceived").withDataCheck((x) => {
received += x.offences_count;
return true
}),
),
];
const testCase = new TestCase(
steps.map((s) => s.build()),
true,
() => {
logger.info(`Test completed. Created ${sent} offences, processed ${received} in teyrchain`);
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
expect(sent).toEqual(received);
},
{ timeout: GlobalTimeout * 2 } // Double timeout for this complex test
);
@@ -0,0 +1,26 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { EventOutcome, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout} from "../src/utils";
import { commonUnsignedSteps } from "./common";
const PRESET: Presets = Presets.FakeDev;
test(
`unsigned solution on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const steps = commonUnsignedSteps(10, 4, 4, false, apis);
const testCase = new TestCase(steps, true, () => {
killZn();
});
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,33 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { EventOutcome, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout } from "../src/utils";
import { commonUnsignedSteps } from "./common";
const PRESET: Presets = Presets.FakeDot;
test(
`unsigned solution on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const expectedValidatorSetCount = await apis.paraApi.query.Staking.ValidatorCount.getValue();
const pages = 32;
const minerPages = 4;
const steps = commonUnsignedSteps(expectedValidatorSetCount, minerPages, pages, true, apis);
const testCase = new TestCase(
steps,
true,
() => {
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,33 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { EventOutcome, runTest, TestCase } from "../src/test-case";
import { getApis, GlobalTimeout } from "../src/utils";
import { commonUnsignedSteps } from "./common";
const PRESET: Presets = Presets.FakeKsm;
test(
`unsigned solution on ${PRESET}`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
const expectedValidatorSetCount = await apis.paraApi.query.Staking.ValidatorCount.getValue();
const pages = 16;
const minerPages = 4;
const steps = commonUnsignedSteps(expectedValidatorSetCount, minerPages, pages, true, apis);
const testCase = new TestCase(
steps,
true,
() => {
killZn();
}
);
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,243 @@
import { test, expect } from "bun:test";
import { Presets } from "../src";
import { runPresetUntilLaunched } from "../src/cmd";
import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case";
import { alice, aliceStash, deriveFrom, getApis, GlobalTimeout, logger, safeJsonStringify, ss58, type ApiDeclarations } from "../src/utils";
import { DEV_PHRASE } from "@polkadot-labs/hdkd-helpers";
import { FixedSizeBinary, type PolkadotSigner, type TxCall, type TxCallData, type TypedApi } from "polkadot-api";
import { teyrchain, rc } from "@polkadot-api/descriptors";
const PRESET: Presets = Presets.FakeDev;
async function sendUp(api: TypedApi<typeof teyrchain>, count: number) {
const calls: TxCallData[] = [];
const ed = await api.constants.Balances.ExistentialDeposit();
for (let i = 0; i < count; i++) {
const account = deriveFrom(DEV_PHRASE, `//up_${i}`);
const endowment = BigInt(1000) * ed;
const teleport = endowment / BigInt(10);
const forceSetBalance = api.tx.Balances.force_set_balance({
new_free: endowment,
who: { type: "Id", value: ss58(account.publicKey) }
});
const xcm = api.tx.PolkadotXcm.teleport_assets({
dest: {
type: "V5",
value: {
parents: 1,
interior: {
type: "Here",
value: undefined
}
}
},
beneficiary: {
type: "V5",
value: {
parents: 0,
interior: {
type: "X1",
value: {
type: "AccountId32",
value: { id: new FixedSizeBinary(account.publicKey) }
}
}
}
},
assets: {
type: "V5",
value: [
{
id: {
parents: 1,
interior: {
type: "Here",
value: undefined
}
},
fun: {
type: "Fungible",
"value": teleport
}
}
]
},
fee_asset_id: {
type: "V5",
value: {
parents: 1,
interior: {
type: "Here",
value: undefined
}
},
},
})
const dispatchAs = api.tx.Utility.dispatch_as({
as_origin: { type: "system", value: { type: "Signed", value: ss58(account.publicKey) } },
call: xcm.decodedCall
});
calls.push(forceSetBalance.decodedCall);
calls.push(dispatchAs.decodedCall);
}
const finalBatch = api.tx.Utility.batch_all({ calls });
const finalSudo = api.tx.Sudo.sudo({ call: finalBatch.decodedCall });
try {
const res = await finalSudo.signAndSubmit(alice, { at: "best" });
let success = 0;
let failure = 0;
res.events.forEach((e) => {
logger.debug(safeJsonStringify(e.value));
if (e.value.type === "DispatchedAs") {
// @ts-ignore
if (e.value.value.result.success) {
success += 1
} else {
failure += 1
}
}
});
logger.info(`Sent ${count} upward messages, intercepted ${success + failure} events, ${success} succeeded, ${failure} failed`);
} catch(e) {
logger.warn(`Error sending upward messages: ${e}`);
}
}
async function sendDown(api: TypedApi<typeof rc>, count: number) {
const calls: TxCallData[] = [];
const ed = await api.constants.Balances.ExistentialDeposit();
for (let i = 0; i < count; i++) {
const account = deriveFrom(DEV_PHRASE, `//down_${i}`)
const endowment = BigInt(1000) * ed;
const teleport = endowment / BigInt(10);
const forceSetBalance = api.tx.Balances.force_set_balance({
new_free: endowment,
who: { type: "Id", value: ss58(account.publicKey) }
})
const xcm = api.tx.XcmPallet.teleport_assets({
dest: {
type: "V5",
value: {
parents: 0,
interior: {
type: "X1",
value: { type: "Teyrchain", value: 1100 }
}
}
},
beneficiary: {
type: "V5",
value: {
parents: 0,
interior: {
type: "X1",
value: {
type: "AccountId32",
value: { id: new FixedSizeBinary(account.publicKey) }
}
}
}
},
assets: {
type: "V5",
value: [
{
id: {
parents: 0,
interior: {
type: "Here",
value: undefined
}
},
fun: {
type: "Fungible",
value: teleport
}
}
]
},
fee_asset_id: {
type: "V5",
value: {
parents: 0,
interior: {
type: "Here",
value: undefined
}
},
},
})
const dispatchAs = api.tx.Utility.dispatch_as({
as_origin: { type: "system", value: { type: "Signed", value: ss58(account.publicKey) } },
call: xcm.decodedCall
});
calls.push(forceSetBalance.decodedCall);
calls.push(dispatchAs.decodedCall);
}
const finalBatch = api.tx.Utility.batch_all({ calls });
const finalSudo = api.tx.Sudo.sudo({ call: finalBatch.decodedCall });
try {
const res = await finalSudo.signAndSubmit(alice, { at: "best" });
let success = 0;
let failure = 0;
res.events.forEach((e) => {
logger.verbose(safeJsonStringify(e.value));
if (e.value.type === "DispatchedAs") {
// @ts-ignore
if (e.value.value.result.success) {
success += 1
} else {
failure += 1
}
}
});
logger.info(`Sent ${count} downward messages, intercepted ${success + failure} events, ${success} succeeded, ${failure} failed`);
} catch(e) {
logger.warn(`Error sending downward messages: ${e}`);
}
}
test(
`${PRESET} preset with vmp queues being spammed af`,
async () => {
const { killZn, paraLog } = await runPresetUntilLaunched(PRESET);
const apis = await getApis();
// This test is meant to not run automatically, so most things are commented out.
// const downSub = apis.rcClient.blocks$.subscribe((block) => {
// if (block.number > 10) {
// logger.verbose(`spammer::down spamming at height ${block.number}`);
// sendDown(apis.rcApi, (block.number * 10) + 50);
// }
// });
// const upSub = apis.paraClient.blocks$.subscribe((block) => {
// if (block.number > 0) {
// logger.verbose(`spammer::up spamming at height ${block.number}`);
// sendUp(apis.paraApi, 40);
// }
// });
const steps: Observe[] = [
Observe.on(Chain.Relay, "Session", "NewSession")
.byBlock(11),
// Observe.on(Chain.Relay, "WontReach", "WontReach")
].map((s) => s.build());
const testCase = new TestCase(steps, true, () => {
killZn();
// downSub.unsubscribe();
// upSub.unsubscribe()
});
const outcome = await runTest(testCase, apis, paraLog);
expect(outcome).toEqual(EventOutcome.Done);
},
{ timeout: GlobalTimeout }
);
@@ -0,0 +1,28 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["esnext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}
@@ -0,0 +1,38 @@
[relaychain]
default_command = "pezkuwi"
chain_spec_path = "./rc.json"
[[relaychain.nodes]]
name = "alice"
validator = true
rpc_port = 9942
[[relaychain.nodes]]
name = "bob"
validator = true
rpc_port = 9943
args = [
"-lruntime::system=debug,runtime::session=trace,runtime::staking-async::ah-client=trace,runtime::ah-client=debug",
]
[[relaychain.nodes]]
name = "eve"
validator = true
rpc_port = 9944
[[relaychain.nodes]]
name = "dave"
validator = true
rpc_port = 9945
[[teyrchains]]
id = 1100
chain_spec_path = "./teyrchain.json"
[teyrchains.collator]
name = "charlie"
rpc_port = 9946
args = [
"-lruntime::system=debug,runtime::multiblock-election=trace,runtime::staking=debug,runtime::staking::rc-client=trace,runtime::rc-client=debug",
]
@@ -0,0 +1,30 @@
[relaychain]
default_command = "pezkuwi"
chain_spec_path = "./rc.json"
[[relaychain.nodes]]
name = "alice"
validator = true
rpc_port = 9944
args = [
"-lruntime::system=debug,runtime::session=trace,runtime::staking-async::ah-client=trace,runtime::ah-client=debug,xcm=trace",
]
[[relaychain.nodes]]
name = "bob"
validator = true
rpc_port = 9945
args = [
"-lruntime::system=debug,runtime::session=trace,runtime::staking-async::ah-client=trace,runtime::ah-client=debug",
]
[[teyrchains]]
id = 1100
chain_spec_path = "./teyrchain.json"
[teyrchains.collator]
name = "charlie"
rpc_port = 9946
args = [
"-lruntime::system=debug,runtime::multiblock-election=trace,runtime::staking=debug,runtime::staking::rc-client=trace,runtime::rc-client=debug,xcm=trace,teyrchain-system=debug,runtime=info",
]