Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 77 additions & 0 deletions .github/scripts/testnet-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/sh
# Run dedicated testnet QA in a clean Linux container without changing host HOME.
set -eu

script_dir=$(CDPATH='' cd "$(dirname "$0")" && pwd)
workspace=$(git -C "$script_dir/../.." rev-parse --show-toplevel)
artifact_output="$workspace/packages/agent-cli/artifacts"
stage=''

cleanup() {
original_status=$?
cleanup_failed=0
trap - EXIT HUP INT TERM
set +e
# Remove previous run evidence even when setup/npm failed before a new report.
if ! mkdir -p "$artifact_output"; then
cleanup_failed=1
echo 'testnet-run.sh: could not prepare artifact output' >&2
fi
if ! rm -f "$artifact_output/lifecycle-report.json" \
"$artifact_output/events.jsonl" "$artifact_output/events-follow.jsonl" \
"$artifact_output/audit.jsonl" "$artifact_output"/runner*.jsonl; then
cleanup_failed=1
echo 'testnet-run.sh: could not purge previous test artifacts' >&2
fi
if [ -n "$stage" ]; then
source_artifacts="$stage/packages/agent-cli/artifacts"
for artifact in "$source_artifacts/lifecycle-report.json" \
"$source_artifacts/events.jsonl" "$source_artifacts/events-follow.jsonl" \
"$source_artifacts/audit.jsonl" "$source_artifacts"/runner*.jsonl; do
if [ -f "$artifact" ] && [ ! -L "$artifact" ]; then
if ! cp "$artifact" "$artifact_output/"; then
cleanup_failed=1
echo 'testnet-run.sh: could not copy a test artifact' >&2
fi
fi
done
if ! rm -rf "$stage"; then
cleanup_failed=1
echo 'testnet-run.sh: could not remove test staging' >&2
fi
fi
if [ "$original_status" -eq 0 ] && [ ! -f "$artifact_output/lifecycle-report.json" ]; then
cleanup_failed=1
echo 'testnet-run.sh: successful test run produced no report artifact' >&2
fi
if [ "$original_status" -eq 0 ] && [ "$cleanup_failed" -ne 0 ]; then
original_status=1
fi
exit "$original_status"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

stage=$(sh "$script_dir/colima-stage.sh")
# shellcheck disable=SC1091
. "$script_dir/ensure-colima.sh"

git -C "$workspace" archive --format=tar HEAD > "$stage/source.tar"
tar -xf "$stage/source.tar" -C "$stage"
rm "$stage/source.tar"

# Secret values stay out of argv/env files. Docker copies only these named
# variables into its fresh environment; the image keeps its own default HOME.
docker run --rm --init --platform linux/arm64 \
-e CI=true \
-e GITHUB_SHA -e GITHUB_RUN_ID \
-e BLOXWAP_E2E -e BLOXWAP_TEST_ACCOUNT_ONLY \
-e BLOXWAP_TEST_MASTER_KEY -e BLOXWAP_PRIVY_APP_ID -e BLOXWAP_PRIVY_APP_SECRET \
-v "$stage:/work" -w /work node:24-bookworm \
sh -eu -c '
npm ci
npm run build
npm run test:e2e
'
25 changes: 2 additions & 23 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,11 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run build
- name: Verify the live testnet lifecycle (Relay mocked)
env:
HOME: ${{ runner.temp }}/e2e-home
run: |
mkdir -p "$HOME"
npm run test:e2e
run: .github/scripts/testnet-run.sh
- name: Record testnet coverage and outcome
if: always()
run: |
node --input-type=module <<'NODE'
import { readFileSync, appendFileSync, existsSync } from 'node:fs';
const path = 'packages/agent-cli/artifacts/lifecycle-report.json';
if (!existsSync(path)) {
appendFileSync(process.env.GITHUB_STEP_SUMMARY, 'No lifecycle report was produced. Live coverage is unproven.\n');
process.exit(1);
}
const report = JSON.parse(readFileSync(path));
const stages = report.stages.filter(stage => stage.status !== 'running');
appendFileSync(process.env.GITHUB_STEP_SUMMARY,
`Testnet lifecycle: **${report.status}**. Cleanup: ${report.cleanup}.\n\n` +
'Hyperliquid testnet and dedicated Privy app are live; Relay is a zero-value mock using pre-funded test collateral. This script is not an AI-agent session.\n\n' +
stages.map(stage => `- ${stage.name}: ${stage.status}\n`).join(''));
NODE
run: node packages/agent-cli/scripts/testnet-summary.mjs packages/agent-cli/artifacts/lifecycle-report.json
- uses: actions/upload-artifact@v4
if: always()
with:
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/content/docs/skill.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ and failure handling. These checks do **not** prove a live agent or
exchange run. The nightly testnet lifecycle uses only a company-owned disposable
CI master account and separate test Privy app, real Hyperliquid testnet and Privy,
and an explicitly mocked Relay route backed by already funded test collateral.
It never transfers real funds. Missing CI credentials fail the job; a skipped or
The same harness supports a caller-controlled external wallet approval adapter
on a clean QA runner; its private key stays in that wallet. It never transfers
real funds. Missing CI credentials fail the job; a skipped or
mocked test must never be reported as live validation. Record the workflow run,
commit, lifecycle report, event and audit artifacts before claiming a testnet pass.
See `docs/agent-workflow.md` and `docs/testnet.md` in the repository for setup.
4 changes: 3 additions & 1 deletion apps/docs/public/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ and failure handling. These checks do **not** prove a live agent or
exchange run. The nightly testnet lifecycle uses only a company-owned disposable
CI master account and separate test Privy app, real Hyperliquid testnet and Privy,
and an explicitly mocked Relay route backed by already funded test collateral.
It never transfers real funds. Missing CI credentials fail the job; a skipped or
The same harness supports a caller-controlled external wallet approval adapter
on a clean QA runner; its private key stays in that wallet. It never transfers
real funds. Missing CI credentials fail the job; a skipped or
mocked test must never be reported as live validation. Record the workflow run,
commit, lifecycle report, event and audit artifacts before claiming a testnet pass.
See `docs/agent-workflow.md` and `docs/testnet.md` in the repository for setup.
2 changes: 1 addition & 1 deletion docs/ACCEPTANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Node 22.12 minimum.
| #29–#32 | Locked sequence journal across independent writers/restarts, replay/types/follow, exact fill fee units; log filtering/redaction; runner heartbeat/staleness and deposit polling; mocked-clock expiry blocking and once-per-threshold warnings. |
| #33–#36 | New approval verified before signer handoff/old revocation, resumable rotation and immediate local revocation tests; fsynced cross-process hash-chain with intent/decision/signature digest/response/fill linkage; tamper/redaction tests and linked full-lifecycle records. CLI/RPC pre-schema hex/mnemonic detector and threat model. |
| #37–#38 | Skill source/frontmatter/error-catalog tests and every documented command input parsed through actual CLI schemas offline; user-owned Privy, ordered external approvals, bounded polling, exchange reconciliation and supported-test-route guidance; install/custody README, generated command reference and static Pages site. The skill deliverable is complete; the unproven coding-agent testnet session is tracked explicitly in #39. |
| #39 | Offline full CLI/SDK/RPC account lifecycle test and testnet-driver assertions; separate nightly script signs only for an acknowledged disposable CI test account, uses mocked Relay and already funded test collateral, reconciles exchange outcomes and exports event/audit/report artifacts. Explicit live execution fails when prerequisites are missing. Nightly live evidence remains pending. |
| #39 | Offline CLI/SDK/RPC lifecycle plus fifteen-stage testnet harness with external wallet approval adapter or separate CI-only disposable signer; exact prices, correlated events, post-rotation trading, partial-rotation cleanup and zero orders/agents/positions verification. Schema 2 reports/CI summaries validate stage and service evidence; Relay remains mocked. Missing configuration fails explicitly. Nightly live evidence and the independent coding-agent skill session remain pending. |
| #40–#42 | Script-free packed global installation and installed bunx smoke, executable bin, license and matching skill versions; Node 22.12/24 install CI; Linux/macOS ARM/x64 native binary smoke; changesets/tagged-main/version/provenance/asset pipeline with candidate-only manual runs. Publishing and public registry verification await npm configuration. |
| #49 | Token guard rejects declarations anywhere in formatted/minified CSS. Unit regression and a mutation of global.css prove docs:check rejects the issue's exact same-line copy. |

Expand Down
13 changes: 11 additions & 2 deletions docs/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| #8 | done | Service error adapters, redacted upstream detail, fixture-driven classification and retry defaults. |
| #9 | done | Bounded jittered retries and per-signer persisted monotonic nonces guarded by exclusive locks. Stale locks fail closed. |
| #10 | done | Privy server SDK create/link, P-256 owner, private authorization storage and typed-data signer; sandbox test env-gated. |
| #11 | partial | User-owned Privy app, v2 domain/network and no-export policy, authorized wallet attachment and init revalidation; local typed-data and withdrawal rejection tests pass. Direct live Privy rejection still needs a dedicated test app. L1 action hashes prevent Privy per-action/notional inspection; exchange agent permissions enforce the withdrawal boundary. |
| #11 | partial | Each user brings their own external master wallet and Privy app. v2 domain/network and no-export policy, authorized wallet attachment and init revalidation; local typed-data and withdrawal rejection tests pass. Direct live Privy rejection still needs a dedicated test app. L1 action hashes prevent Privy per-action/notional inspection; exchange agent permissions enforce the withdrawal boundary. |
| #12 | done | Idempotent managed-wallet onboarding, persisted external master EIP-712 approvals/deeplink, expiry bound, agent/builder verification and master-key rejection. |
| #13 | done | Default Bloxwap builder address with f=10 (0.01%); config/env overrides, init approval, approved-maximum checks and fund/order address/rate reporting. |
| #14 | done | Typed validated Relay quote/status adapter, perps USDC destination, multiple origin routes and fee parsing; mocked fixtures. |
Expand Down Expand Up @@ -41,7 +41,7 @@
| #36 | done | Pre-schema recursive CLI/RPC secret detector, BIP39-like seed rejection, private authorization refs and env/log/error scrubbing; threat model docs. |
| #37 | done | Skill content, packaged/embedded installation, schema-validated examples and public /SKILL.md hosting are complete. Frontmatter, gotchas, error handling, discovery links and source parity are verified. The unverified live coding-agent testnet criterion is explicitly transferred to #39. |
| #38 | done | Install/testnet README, custody architecture, schema-generated command reference and static docs site with gated Pages workflow. |
| #39 | partial | Full mocked CLI/SDK/RPC lifecycle and fail-closed testnet harness, with builder approval, exchange outcome reconciliation, runner/events, rotation/revoke and audit assertions. Nightly live execution/journals and the independent coding-agent skill session transferred from #37 need dedicated QA setup and evidence; neither is claimed complete. |
| #39 | partial | Fifteen-stage testnet harness supports external wallet signing and separate CI-only disposable keys, exact exchange reconciliation, correlated WebSocket/follow/replay events, post-rotation trading, audit verification, partial-rotation cleanup and zero orders/agents/positions checks. Schema 2 reports and CI summaries require evidence. Offline regressions pass; nightly live journals and the independent coding-agent skill session transferred from #37 still require dedicated QA configuration and evidence. |
| #40 | partial | Script-free tarball global install and actual installed bunx help/schema smoke pass locally; executable/skill/license checks and Node 22.12/24 macOS/Linux CI. Public registry installation awaits publication. |
| #41 | partial | Validated Changesets patch workflow with optional version PR, native binary smoke/checksums, tagged-main version gates, exact tested tarball publication with provenance and registry verification. Manual candidates cannot publish. Actual release needs npm authentication and the publishing gate; organization policy blocks automatic version PRs, so the default workflow uploads a reviewable patch. |
| #42 | done | Final packaged flow audited and corrected (network/config isolation, exact loss/exposure/receipt checks, trigger preservation, readiness and stream errors). Skill shipped/embedded in npm and compiled CLI; Claude/Codex installation with CODEX_HOME, version synchronization, idempotency and overwrite protection. |
Expand Down Expand Up @@ -75,3 +75,12 @@ account trading were used for this review.
The validated version-patch workflow prepared 0.2.0, consuming the pending
changesets and synchronizing package/lock/skill versions and generated docs.
This is release preparation; public npm installation/provenance is still pending.

## Testnet harness — 2026-10-18

#39 now supports caller-controlled external wallet approvals alongside the
separate disposable CI signer, with fifteen evidence-checked stages, correlated
events and interrupted-rotation cleanup. Runner shutdown is confirmed before
journal export. Build, lint, typecheck, 229 offline CLI tests, docs checks and the
static docs build pass. The `testnet` environment still needs QA configuration;
no credentialed lifecycle or independent coding-agent session is claimed.
10 changes: 7 additions & 3 deletions docs/ci-runners.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ docker pull --platform linux/amd64 debian:bookworm-slim
docker pull --platform linux/arm64 debian:bookworm-slim
docker pull --platform linux/amd64 node:22.12.0-bookworm
docker pull --platform linux/amd64 node:24-bookworm
docker pull --platform linux/arm64 node:24-bookworm
```

`debian:bookworm-slim` runs the compiled Linux CLI (glibc). The Node bookworm
Expand All @@ -76,9 +77,10 @@ it.

Host-side scratch space is `$RUNNER_TEMP` (GitHub Actions empties it at the
start and end of each job). Docker bind mounts use `$HOME/.bloxwap-ci/` because
Colima does not see `/opt/github-runners`. The testnet job sets
`HOME=$RUNNER_TEMP/e2e-home` so a persistent Mac mini cannot reuse `~/.bloxwap`
state.
Colima does not see `/opt/github-runners`. The testnet job stages tracked source
there and builds/runs in a fresh `linux/arm64` Node 24 container with its default
home. The host's wallet state is not mounted. Only explicit report/journal
artifacts are copied back, including after a failed run, before staging is removed.

## Network

Expand All @@ -100,6 +102,8 @@ The runner needs outbound access to:
- **Install smoke** still covers macOS and Linux × Node 22.12 and 24. Linux
cases pack on the Mac, then install inside a `linux/amd64` Node container.
- **Docs smoke** uses Playwright Chromium already installed for macOS.
- **Testnet lifecycle** runs in a fresh `linux/arm64` Node 24 container so repeated
jobs cannot reuse wallet state on the persistent runner.

CI helper scripts live in `.github/scripts/` and are POSIX `/bin/sh` (BSD
userland). They must stay free of GNU-only flags, `apt`, and `sudo`.
2 changes: 1 addition & 1 deletion docs/custody.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ shared Bloxwap Privy account, credential proxy, or hosted Bloxwap signer.
Do not send Privy credentials or wallet keys to Bloxwap, an AI conversation,
repository, issue, or support message.

The master wallet stays external and approves a separate Hyperliquid API agent.
Each user brings their own master wallet. It stays external and approves a separate Hyperliquid API agent.
Privy manages the agent wallet key in the user's app; the CLI generates its
P-256 owner authorization key locally. Only the public authorization key is
registered with Privy. The private authorization key remains in a private local
Expand Down
17 changes: 16 additions & 1 deletion docs/issues/39.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Labels: Security & Observability, size:L

## Description
Scripted e2e on HL testnet: init (with test master key in CI only), fund (mocked Relay), run, order/cancel/modify, events, rotate, revoke.
Scripted e2e on HL testnet: init with external wallet approvals (or a disposable CI-only test signer), fund (mocked Relay), run, order/cancel/modify, events, rotate, revoke.

## Acceptance criteria
- [ ] Runs in CI nightly with secrets
Expand All @@ -16,6 +16,21 @@ The live coding-agent session remains unverified. It is separate from the
deterministic CI harness and offline fixture tests. #37 covers the completed
skill deliverable; this issue owns both forms of live testnet evidence.

## Implementation
The harness supports a caller-controlled external signer over JSON stdin/stdout;
the user's master key never enters the CLI or harness. The dedicated CI signer
is a separate test-only mode. See [setup and protocol](../testnet.md).

Fifteen stages reconcile both approvals, settlement, runner readiness, exact
exchange prices, correlated open/canceled events and replay, post-rotation
trading, final revocation and audit integrity. Cleanup tracks both run-owned
agents before approvals and verifies no remaining orders, agents or positions.
Schema 2 reports and CI summaries reject incomplete evidence and preserve the
separate, unverified coding-agent session requirement.

Live acceptance is pending a configured QA wallet and Privy app; offline tests
do not check these acceptance boxes.

## Dependencies
- #27
- #29
Expand Down
Loading
Loading