Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
b8dd52b
feat: scaffold strict TypeScript CLI and CI (#1)
joeblau Oct 13, 2026
f9ac4c4
feat: define private runner interface ADR (#2)
joeblau Oct 13, 2026
a1d9104
feat: normalize errors and document stable codes (#3)
joeblau Oct 13, 2026
b31617f
feat: add validated JSON command framework (#4)
joeblau Oct 13, 2026
86b1176
feat: add private versioned configuration store (#5)
joeblau Oct 13, 2026
8aa3d88
feat: add redacted rotating structured logs (#6)
joeblau Oct 13, 2026
69f0285
feat: wrap Hyperliquid clients and market metadata (#7)
joeblau Oct 13, 2026
5646216
feat: map upstream service errors (#8)
joeblau Oct 13, 2026
8a1f57a
feat: add bounded retries and persistent nonce locking (#9)
joeblau Oct 13, 2026
71312e3
feat: integrate Privy managed agent signing (#10)
joeblau Oct 13, 2026
5f53c05
feat: restrict agent custody policy and document boundary (#11)
joeblau Oct 13, 2026
99cfc71
feat: implement resumable master-wallet onboarding (#12)
joeblau Oct 13, 2026
6914bb6
feat: validate builder fee approvals (#13)
joeblau Oct 13, 2026
c09530a
feat: add Relay HyperCore deposit quote client (#14)
joeblau Oct 13, 2026
779dbb4
feat: add exact USDC decimal conversion (#15)
joeblau Oct 13, 2026
b4b670c
feat: render terminal and file QR codes (#16)
joeblau Oct 13, 2026
2404047
feat: implement idempotent funding command (#17)
joeblau Oct 13, 2026
65ee8bb
feat: support resumable deposit signature steps (#18)
joeblau Oct 13, 2026
bfce6e5
feat: track deposit settlement lifecycle (#19)
joeblau Oct 13, 2026
e55065c
feat: add account status balance positions and orders (#20)
joeblau Oct 13, 2026
bd697f2
feat: enforce local pre-trade risk limits (#21)
joeblau Oct 13, 2026
eb74b57
feat: implement risk-checked order placement (#22)
joeblau Oct 13, 2026
3b06ce1
feat: add single and batch order cancellation (#23)
joeblau Oct 13, 2026
98b8c28
feat: add risk-checked order modification (#24)
joeblau Oct 13, 2026
d58af36
feat: add validated leverage and margin mode updates (#25)
joeblau Oct 13, 2026
d861988
feat: add persistent runner lifecycle (#26)
joeblau Oct 13, 2026
f584a9a
feat: route CLI commands through private runner RPC (#27)
joeblau Oct 13, 2026
52b143c
feat: maintain reconnecting Hyperliquid subscriptions (#28)
joeblau Oct 13, 2026
4a9488f
feat: add durable JSON-line event stream (#29)
joeblau Oct 13, 2026
24f068d
feat: add filtered runner log tailing (#30)
joeblau Oct 13, 2026
003ff62
feat: expose runner heartbeat and connection health (#31)
joeblau Oct 13, 2026
364dfa5
feat: detect agent expiry and emit warnings (#32)
joeblau Oct 13, 2026
b16971e
feat: rotate managed agents with verified signer handoff (#33)
joeblau Oct 13, 2026
e1a48c0
feat: add agent revocation with local trading block (#34)
joeblau Oct 13, 2026
9b95695
feat: add linked tamper-evident trading audit (#35)
joeblau Oct 13, 2026
ec9905e
feat: reject master secrets and document security model (#36)
joeblau Oct 13, 2026
bd8183a
feat: document agent-native trading skill (#37)
joeblau Oct 13, 2026
ed8d434
feat: generate command reference and documentation site (#38)
joeblau Oct 13, 2026
e13ffc3
feat: add mocked and gated testnet lifecycle suites (#39)
joeblau Oct 13, 2026
1970104
feat: package executable CLI for global npm and Bun installs (#40)
joeblau Oct 13, 2026
a04a6c7
feat: add gated provenance release pipeline (#41)
joeblau Oct 13, 2026
1885f8b
feat: distribute and install version-matched trading skill (#42)
joeblau Oct 13, 2026
89c3efe
feat: default Bloxwap builder code and fee (#13)
joeblau Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"$schema":"https://unpkg.com/@changesets/[email protected]/schema.json","changelog":"@changesets/cli/changelog","commit":false,"fixed":[],"linked":[],"access":"public","baseBranch":"main","updateInternalDependencies":"patch","ignore":[]}
6 changes: 6 additions & 0 deletions .changeset/first-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@bloxwap/agent-cli": minor
---

Add managed Hyperliquid agents, Relay funding, JSON commands, a persistent runner,
risk checks, durable events/audit journals, and the packaged trading skill.
19 changes: 19 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm run typecheck
- run: npm test
23 changes: 23 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Documentation
on:
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
jobs:
docs:
runs-on: ubuntu-latest
environment: github-pages
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run docs:generate
- uses: actions/upload-pages-artifact@v3
with:
path: site
- uses: actions/deploy-pages@v4
31 changes: 31 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Testnet lifecycle
on:
schedule:
- cron: '23 4 * * *'
workflow_dispatch:
permissions:
contents: read
jobs:
testnet:
runs-on: ubuntu-latest
timeout-minutes: 15
environment: testnet
env:
BLOXWAP_E2E: '1'
BLOXWAP_TEST_MASTER_KEY: ${{ secrets.BLOXWAP_TEST_MASTER_KEY }}
BLOXWAP_PRIVY_APP_ID: ${{ secrets.BLOXWAP_PRIVY_APP_ID }}
BLOXWAP_PRIVY_APP_SECRET: ${{ secrets.BLOXWAP_PRIVY_APP_SECRET }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: always()
with:
name: testnet-journals
path: artifacts/
23 changes: 23 additions & 0 deletions .github/workflows/install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Install smoke
on:
pull_request:
push:
branches: [main]
jobs:
install:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- uses: oven-sh/setup-bun@v2
- run: npm ci
- run: npm run build
- run: npm run test:package
env:
BLOXWAP_SMOKE_BUN: '1'
42 changes: 42 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
permissions:
contents: write
id-token: write
jobs:
release:
if: vars.BLOXWAP_PUBLISH == '1'
runs-on: ubuntu-latest
environment: npm-release
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org
- uses: oven-sh/setup-bun@v2
- run: npm ci
- name: Verify tag and package version
if: startsWith(github.ref, 'refs/tags/')
run: node -e 'const p=require("./package.json");if(process.env.GITHUB_REF_NAME!==`v${p.version}`)throw Error("Tag/version mismatch")'
- run: npm run build
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run test:package
- run: npm run build:binaries
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
generate_release_notes: true
files: |
binaries/*
SKILL.md
CHANGELOG.md
21 changes: 21 additions & 0 deletions .github/workflows/version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Version packages
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- uses: changesets/action@v1
with:
version: npm run version:packages
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
dist/
coverage/
.env
*.tgz
site/
artifacts/
.bloxwap/
binaries/
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
dist/
site/
binaries/
src/core/skill-content.ts
docs/issues/
docs/commands.md
test/__snapshots__/
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"singleQuote":true,"trailingComma":"all"}
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# @bloxwap/agent-cli

## 0.1.0

Initial implementation of managed-agent trading, funding and persistent runner.
Use Changesets for subsequent versioning and release notes.
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# agent-cli

An agent-native Hyperliquid trading CLI with JSON input/output, a persistent
signing runner, Privy managed agents, Relay funding, and a packaged agent skill.

Install: `npm i -g @bloxwap/agent-cli` or `bunx @bloxwap/agent-cli --help`.
The requested Hyperliquid SDK declares Node **22.12+**; use that supported runtime
or current Bun. No postinstall script. Publishing is release-gated.

## Testnet quickstart

```sh
npm ci
npm run build
node dist/cli.js --help
node dist/cli.js config set privyAppId YOUR_APP_ID
node dist/cli.js config set privyAppSecretRef env:BLOXWAP_PRIVY_APP_SECRET
# Set BLOXWAP_PRIVY_APP_SECRET securely outside the CLI/conversation.
node dist/cli.js init --network testnet --master-address 0xYOUR_MASTER_ADDRESS
# Sign returned EIP-712 approval externally, then submit its 65-byte signature:
node dist/cli.js init --network testnet --signature 0xYOUR_SIGNATURE
node dist/cli.js fund --network testnet --amount 10 --origin-chain 42161
# Show QR/address and exact origin token/fees to user; complete any deposit steps.
node dist/cli.js status --network testnet
# Continue only when active deposits report settled.
node dist/cli.js run --network testnet --detach
node dist/cli.js market --coin BTC
node dist/cli.js order --json '{"coin":"BTC","side":"buy","size":"0.001","price":"20000","tif":"Gtc"}'
node dist/cli.js events --follow
```

Testnet onboarding/trades require Privy sandbox credentials, an externally signed
testnet master approval and testnet collateral. Relay HyperCore testnet routes may
be unavailable; the e2e suite mocks Relay. The placeholders above must be replaced
with addresses/signatures from your test environment. Never paste a private key.

## Architecture and custody

```mermaid
flowchart LR
Agent[Agent / CLI] -->|private JSON-RPC| Runner[Persistent runner]
Runner -->|P-256 authorization| Privy[Privy managed agent]
Privy -->|EIP-712 signature| HL[Hyperliquid exchange]
Agent --> Relay[Relay funding quote]
Master[External master wallet] -->|one-time approvals| HL
Runner --> Journal[Events / audit / logs]
```

Master custody stays external. Privy owns the separate agent key; the CLI holds
only an authorization key. Hyperliquid agent permissions deny withdrawal.
Privy policies restrict signing domains; local checks enforce configured risk
limits. Read [custody limits](docs/custody.md) before using a funded account.

State: `~/.bloxwap`; config: `~/.bloxwap/config.json`; IPC:
`~/.bloxwap/run.sock`; env prefix: `BLOXWAP_`. All local state is private to the OS
user. The default Bloxwap builder address is
`0x71b09A08257078A4d642F7Dd7315e656c837329a`, with `f=10` (0.01%).
Override via `config set builder.address` / `config set builder.fee` or
`BLOXWAP_BUILDER_ADDRESS` / `BLOXWAP_BUILDER_FEE`; init requests builder approval.

## Development and docs

`npm run build`, `npm run lint`, `npm run typecheck`, `npm test` must pass before
committing. Unit tests mock services. Credential-dependent tests are gated: `npm run test:sandbox` and `npm run test:e2e` skip without the required environment. The master-key testnet harness is CI-only; unit tests never access the network.

`npm run docs:generate` creates [command schemas](docs/commands.md) and a static
site in `site/`. [Docs index](docs/index.md), [security](docs/security.md),
[error catalog](docs/errors.md), and [SKILL.md](SKILL.md) explain operation.
121 changes: 121 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
name: agent-cli
description: Operate the agent-cli Hyperliquid trading CLI, including managed-agent onboarding, funding, orders, event monitoring, rotation, and normalized error recovery.
metadata:
version: "0.1.0"
---

Use `agent-cli` for the user's authorized Hyperliquid account operations. Obtain
command schemas with `agent-cli schema COMMAND`; arguments can be flags or
`--json '{...}'`, or a JSON object on stdin. Ordinary commands print exactly one
JSON object: `{ok:true,data}` or `{ok:false,code,message,retryable,details}`.
Logs/QR appear on stderr. `events --follow` and `logs` explicitly stream JSON lines.
Use `--network testnet` during testing; never silently change to mainnet.

## Onboarding sequence

1. Configure Privy app ID and secret reference, e.g. `config set privyAppId APP`
and `config set privyAppSecretRef env:BLOXWAP_PRIVY_APP_SECRET`. The user sets
the environment secret outside the conversation. The default Bloxwap builder is
`0x71b09A08257078A4d642F7Dd7315e656c837329a`, with `f=10` (1 bp = 0.01%).
Override with `config set builder.address` / `config set builder.fee` or
`BLOXWAP_BUILDER_ADDRESS` / `BLOXWAP_BUILDER_FEE`.
2. Run `init --master-address 0x... --network testnet`. Show the returned approval
payload/deeplink/QR. The user signs in their master wallet; submit only the
resulting signature with `init --signature 0x...`. Submit builder approval
separately with `init --builder-signature 0x...` for the configured 0.01% default. Continue only after `approved:true`.
3. Run `fund --amount 10 --origin-chain 42161`. Show `qrAscii`/deposit address,
origin chain/token, minimum, fees, expected received amount and expiry. On
testnet, do not treat the default Arbitrum mainnet chain/token as a test route:
use an explicitly supported test chain/token and `originDecimals`, or the
credential-gated CI harness with mocked Relay and pre-funded test collateral.
If no test route is known, stop funding and report the missing route. The user
transfers the quoted origin token. If `signatureSteps` require signatures,
show the exact payload and submit each via `deposit` in returned order.
4. Poll `status` until each active deposit is `settled`, for at most 12 polls
or 5 minutes, whichever comes first. Start with 1-second delays, double to a
15-second cap, and stop with an explicit pending report if the budget expires.
Settlement requires Relay success and Hyperliquid balance receipt. Stop on
failed/refunded deposits and explain their status. Do not place orders while
any deposit is awaiting funds or pending.
5. Run `run --detach`, then monitor `events --follow --since SEQ`. Persist the
last handled sequence; replay after reconnect. An unfilled or rejected order
remains incomplete: do not claim the requested trade succeeded. A fill updates the trading
plan, an error follows the catalog below, and expiry warnings prompt rotation
using external master-wallet approval.

## Commands and example envelopes

Examples show representative `data`, not live balances or service promises.
Full inputs/outputs are discoverable with `schema`; amounts/prices are strings.

| Input | Example output |
| --- | --- |
| `--help` | `{"ok":true,"data":{"commands":[{"name":"status","description":"Read account status"}],"globalFlags":["--json","--config","--network","--pretty"]}}` |
| `schema order` | `{"ok":true,"data":{"input":{"type":"object"},"output":{"oneOf":[]}}}` (actual schemas include properties) |
| `config get network` | `{"ok":true,"data":"testnet"}` |
| `config set limits.maxLeverage 5` | `{"ok":true,"data":{"version":1,"limits":{"maxLeverage":5}}}` |
| `config validate` | `{"ok":true,"data":{"valid":true,"version":1}}` |
| `init --master-address 0x...` | `{"ok":true,"data":{"approved":false,"requiresSignature":true,"approvals":{"agent":{"typedData":{}}},"qrAscii":"..."}}` |
| `fund --amount 10 --no-qr` | `{"ok":true,"data":{"depositId":"...","depositAddress":"0x...","expectedReceived":"100000000","builderFeeRate":"0.01%","builderAddress":"0x71b09A08257078A4d642F7Dd7315e656c837329a","state":"awaiting_funds","qrAscii":null}}` |
| `deposit --deposit-id ID` | `{"ok":true,"data":{"depositId":"ID","steps":[{"id":"step:0","requiresSignature":true,"payload":{}}]}}` |
| `deposit --deposit-id ID --step-id step:0 --signature 0x...` | `{"ok":true,"data":{"depositId":"ID","steps":[]}}` |
| `status` | `{"ok":true,"data":{"balance":{"accountValue":"100"},"agent":{"approved":true,"daysRemaining":"179"},"deposits":[],"policy":{"id":"...","version":"1"},"runner":{"pid":"123"}}}` |
| `balance` | `{"ok":true,"data":{"accountValue":"100","accountValueUnits":"10000000000","withdrawable":"96","marginUsed":"4"}}` |
| `market --coin BTC` | `{"ok":true,"data":{"coin":"BTC","asset":"0","szDecimals":"5","maxLeverage":"40","mid":"20000"}}` |
| `positions` | `{"ok":true,"data":[{"coin":"BTC","size":"0.001","entryPrice":"20000","leverage":"5","marginMode":"cross"}]}` |
| `orders` | `{"ok":true,"data":[{"coin":"BTC","oid":"123","sz":"0.001","limitPx":"20000","side":"B"}]}` |
| `order --json '{"coin":"BTC","side":"buy","size":"0.001","price":"20000","tif":"Gtc","cloid":"0x11111111111111111111111111111111"}'` | `{"ok":true,"data":{"result":{"status":"ok","response":{"data":{"statuses":[{"resting":{"oid":123}}]}}},"builderFeeRate":"0.01%","builderAddress":"0x71b09A08257078A4d642F7Dd7315e656c837329a","cloid":"0x..."}}` |
| `order --json '{"coin":"BTC","side":"sell","size":"0.001","type":"market","slippage":0.01,"reduceOnly":true}'` | `{"ok":true,"data":{"result":{"status":"ok","response":{"data":{"statuses":[{"filled":{"oid":123}}]}}}}}` |
| `cancel --oid 123` or `cancel --all` | `{"ok":true,"data":{"cancelled":["123"],"result":{"status":"ok"}}}` |
| `modify --json '{"modifications":[{"oid":123,"price":"20100","size":"0.001"}]}'` | `{"ok":true,"data":{"status":"ok"}}` |
| `leverage --coin BTC --leverage 5 --mode isolated` | `{"ok":true,"data":{"status":"ok"}}` |
| `run --detach` | `{"ok":true,"data":{"pid":123,"startedAt":1700000000000,"network":"testnet"}}` |
| `events --since 12 --types '["fill"]'` | `{"ok":true,"data":[{"seq":13,"timestamp":"2026-01-01T00:00:00.000Z","type":"fill","data":{"tid":1}}]}` |
| `events --follow` | `{"seq":13,"timestamp":"2026-01-01T00:00:00.000Z","type":"fill","data":{"tid":1}}` (one line per event) |
| `logs --follow --level warn` | `{"level":"warn","requestId":"...","message":"Risk rejected","time":"..."}` (JSON lines) |
| `rotate` then `rotate --signature 0x...` | `{"ok":true,"data":{"oldAgent":"0x...","newAgent":"0x...","swapped":true,"oldRevoked":false,"revokeApproval":{}}}` |
| `rotate --revoke-signature 0x...` | `{"ok":true,"data":{"swapped":true,"oldRevoked":true}}` |
| `revoke --cancel-orders` | `{"ok":true,"data":{"locallyRevoked":true,"exchangeRevoked":false,"requiresSignature":true,"approval":{}}}` |
| `revoke --signature 0x...` | `{"ok":true,"data":{"locallyRevoked":true,"exchangeRevoked":true}}` |
| `audit --intent ID` | `{"ok":true,"data":[{"intentId":"ID","stage":"decision","data":{"allowed":true},"previousHash":"...","hash":"..."}]}` |
| `skill install --target codex` | `{"ok":true,"data":{"target":"codex","path":".../agent-cli/SKILL.md","version":"0.1.0"}}` |

Before an order, query `market --coin BTC` for `mid`, `szDecimals`, asset and
market leverage ceiling. Compute size/notional with decimal strings/bigints,
using the user-approved limit or the bounded IOC execution price. A valid schema
alone does not establish that an order is tradable or within the budget.

Use `--intent-id ID --intent "reason"` on trading actions to connect the user's
reasoning to the audit. Keep cloid stable when retrying a known order intent.
For TP/SL use `type:"tp"|"sl"` and `triggerPrice` with size/price/reduceOnly;
market orders use an explicit slippage-bounded IOC, not an unbounded market order.

## Error recovery

| Code | Action |
| --- | --- |
| RATE_LIMIT, NETWORK_ERROR | At most 3 read retries with exponential backoff; for uncertain mutations, reconcile order state/cloid before retrying. Stop and report unresolved results |
| NONCE_COLLISION | Retry using a newly issued nonce; inspect a stale lock if persistent |
| DEPOSIT_PENDING | Poll status until settled; stop on terminal funding failure |
| RUNNER_NOT_RUNNING | Start runner or use configured direct fallback |
| INSUFFICIENT_MARGIN | Reduce exposure or request authorized funding |
| POLICY_REJECTED | Stop and revise intent within limits; do not bypass policy |
| AGENT_EXPIRED | Rotate with master-wallet approval |
| AGENT_NOT_APPROVED | Complete init/approval or explain revocation |
| INVALID_SIZE, INVALID_PRICE | Correct input using market precision |
| UNKNOWN_MARKET | Refresh metadata; use an allowed market |
| ORDER_NOT_FOUND | Refresh orders before changing intent |
| CONFIG_INVALID | Correct configuration/input; never insert secrets |
| UPSTREAM_ERROR, INTERNAL | Stop, inspect redacted details/logs and report failure |

## Gotchas

HyperCore perps USDC uses **8 decimals**; EVM USDC uses **6**. Preserve strings and
bigints. Some deposits require **two distinct signatures**; resume stored steps
rather than restarting the route. Agent expiry is at most **180 days**. Never
ask for, paste, log or import a master private key or seed. The managed agent is
trade-only; local limits include markets, size/notional, leverage, daily loss and
open orders. Privy sees an L1 action hash, so it cannot independently inspect
notional/action contents; Hyperliquid agent permissions and local checks enforce
those boundaries. State is always `~/.bloxwap`, env prefix `BLOXWAP_`.
Loading
Loading