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
9 changes: 8 additions & 1 deletion .github/workflows/dx-e2e-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ on:
- 'lib/**'
- '.github/workflows/dx-e2e-beta.yml'
- '.github/actions/dx-e2e-run/**'
pull_request:
paths:
- 'run.sh'
- 'journeys/**'
- 'lib/**'
- '.github/workflows/dx-e2e-beta.yml'
- '.github/actions/dx-e2e-run/**'
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -47,7 +54,7 @@ jobs:
max-parallel: 3
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
journey: [02-lang-tour, 03-lang-edge, 04-devnet-roundtrip, 05-invoke]
journey: [02-lang-tour, 03-lang-edge, 04-devnet-roundtrip, 05-invoke, 07-codegen-consume, 08-interface-consume]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/dx-e2e-run
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/dx-e2e-live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: DX E2E (live)

# The secrets-gated live-network journey (10-live-network): resolve against a
# real preprod TRP endpoint. Never on push — the offline gate must stay
# secret-free. Until the TRP_*/TEST_PARTY_* secrets are configured on this
# repo, the journey skips green and the scheduled run is a no-op (see the
# journey's README).

on:
workflow_dispatch:
schedule:
- cron: '0 8 * * 1' # weekly live-drift check

permissions:
contents: read

jobs:
live:
name: live (ubuntu-latest · 10-live-network)
runs-on: ubuntu-latest
env:
TRP_ENDPOINT_PREPROD: ${{ secrets.TRP_ENDPOINT_PREPROD }}
TRP_API_KEY_PREPROD: ${{ secrets.TRP_API_KEY_PREPROD }}
TEST_PARTY_A_ADDRESS: ${{ secrets.TEST_PARTY_A_ADDRESS }}
TEST_PARTY_B_ADDRESS: ${{ secrets.TEST_PARTY_B_ADDRESS }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/dx-e2e-run
with:
channel: stable
journey: 10-live-network
github-token: ${{ secrets.GITHUB_TOKEN }}
9 changes: 8 additions & 1 deletion .github/workflows/dx-e2e-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ on:
- 'lib/**'
- '.github/workflows/dx-e2e-stable.yml'
- '.github/actions/dx-e2e-run/**'
pull_request:
paths:
- 'run.sh'
- 'journeys/**'
- 'lib/**'
- '.github/workflows/dx-e2e-stable.yml'
- '.github/actions/dx-e2e-run/**'
workflow_dispatch:
schedule:
- cron: '0 7 * * *' # nightly drift check
Expand Down Expand Up @@ -50,7 +57,7 @@ jobs:
max-parallel: 3
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest]
journey: [01-basic-init, 02-lang-tour, 03-lang-edge, 05-invoke, 06-hydra]
journey: [01-basic-init, 02-lang-tour, 03-lang-edge, 04-devnet-roundtrip, 05-invoke, 06-hydra, 07-codegen-consume, 08-interface-consume, 09-protocol-fixture]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/dx-e2e-run
Expand Down
16 changes: 7 additions & 9 deletions journeys/04-devnet-roundtrip/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ local Dolos devnet, restores deterministic cshell wallets, submits the scaffolde
asserts the resulting balances — exercising trix + tx3c + dolos + cshell + the resolver together.

- **Scope:** runtime (needs a working devnet). No secrets, no live network beyond the one-time install.
- **Channels:** runs everywhere (no `tx3c` floor), but the CI workflow currently schedules it on the
**beta** job only.
- **Channels:** runs everywhere (no `tx3c` floor); scheduled on both the **stable** and **beta** jobs.

## Currently failing on released channels — intentionally
## History: parked on beta until trix 0.26.2

The balance-assertion phase hits a known, tracked trix bug (the expect path queried the wrong cshell
store, passed the `@bob` placeholder, and parsed a mismatched utxo shape). Fixed on trix `main`
(tx3-lang/trix#123) but not yet in a released channel, so this journey **fails** on released binaries.
The assertion is kept **strict** (not a tolerated `xfail`) so the broken round-trip is a real,
visible failure. It goes green automatically once the fix ships to a channel; at that point add this
journey to the `stable` job too.
The balance-assertion phase used to hit a known trix bug (the expect path queried the wrong cshell
store, passed the `@bob` placeholder, and parsed a mismatched utxo shape — tx3-lang/trix#123), so
this journey was scheduled on the beta job only and kept **strict** (not `xfail`) so the broken
round-trip stayed a visible red. The fix ships in trix 0.26.2 on both channels; the journey is on
the stable job since then.
33 changes: 33 additions & 0 deletions journeys/07-codegen-consume/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 07-codegen-consume

The `trix codegen` → generated SDK path, end to end with real binaries: generate the
`ts-client` bindings (real `tx3c codegen` + the published `codegen-v1beta0` templates from
`tx3-lang/web-sdk`), compile them inside a host package, then resolve a transfer *through the
generated client* against a local devnet TRP.

Since tx3-lang/trix#129 hermeticized trix's local suite, its contract tests stub `tx3c` and
assert only argv + the per-protocol `.tx3/codegen/<plugin>/<name>/` layout. This journey is the
only CI anywhere that runs real `tx3c codegen` — template rendering, binding content, that the
output actually compiles, and that the compiled client can drive the TRP lifecycle.

- **Scope:** runtime (phase 4 needs a working devnet). Phases 1–3 are offline apart from two
anonymous downloads: the codegen templates (GitHub) and npm packages (registry). No secrets.
- **Toolchain gate:** `#@ min-tx3c: 0.23.0` (matches the verified toolchain; `05-invoke` has the
same floor). Additionally requires `node`/`npm` on PATH — the journey *skips* cleanly when
they're missing (all GitHub-hosted runners ship Node).
- **Fixtures:** `package.json` + `tsconfig.json` + `resolve.ts` — the host application shell the
generated module is dropped into. The templates deliberately emit a bare `protocol.ts` (the
standalone-package files are gated behind `options.standalone`), so a host package is the
representative consumption mode.

## Phases

1. `trix init` + `trix codegen --plugin ts-client` — asserts the seeded `[[codegen]]` entry, the
per-protocol output layout (no flat file), and the generated content (protocol identity,
embedded TIR envelope, typed `Client` wrapper).
2. `npm install` + `tsc` — the generated client type-checks and compiles against the released
`tx3-sdk` it imports.
3. `trix devnet --background` + `node resolve.js` — the compiled client binds parties
(deterministic devnet identities), builds the scaffolded `transfer`, and resolves it over TRP
to an unsigned tx (asserted on `cbor` + `hash`). Resolve-only: `trix invoke` semantics minus
signing, same as `05-invoke`.
77 changes: 77 additions & 0 deletions journeys/07-codegen-consume/journey.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
#
# Journey 07 — codegen consume. See README.md for what this covers.
# Run via e2e/run.sh, which provides $TRIX and an isolated working directory.
#
#@ min-tx3c: 0.23.0

source "${E2E_LIB:?E2E_LIB not set — run this journey via e2e/run.sh}"

JOURNEY_HOME="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

journey_begin "07-codegen-consume" "codegen a ts client → compile it → resolve a transfer through it (devnet)"

# The journey compiles and runs the generated TypeScript client, so a Node
# toolchain is infrastructure here — gate on it like 06 gates on docker.
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
skip "node/npm not found — skipping codegen-consume journey"
journey_end
exit 0
fi

# 1. Scaffold under a stable directory name: the project name is inferred from
# it, and it doubles as the per-protocol output subdir we assert on below.
mkdir demo && cd demo
run_cmd "trix init -y — scaffold a new project" "${TRIX}" init -y

# 2. Generate the ts client: real `tx3c codegen` against the published
# codegen-v1beta0 templates (downloaded from tx3-lang/web-sdk).
run_cmd "trix codegen --plugin ts-client — generate bindings" "${TRIX}" codegen --plugin ts-client
assert_output_contains "Added [[codegen]]" "plugin entry seeded into trix.toml"
assert_output_contains "Bindgen successful for 'demo'"
assert_exists ".tx3/codegen/ts-client/demo/protocol.ts" "per-protocol layout: .tx3/codegen/ts-client/demo/"
[[ -e ".tx3/codegen/ts-client/protocol.ts" ]] && die "bindings leaked outside the per-protocol subdir"
ok "no flat protocol.ts outside the per-protocol subdir"

run_cmd "read the generated client" cat .tx3/codegen/ts-client/demo/protocol.ts
assert_output_contains 'PROTOCOL_NAME = "demo"' "protocol identity embedded"
assert_output_contains "TRANSFER_TIR" "transfer TIR envelope embedded"
assert_output_contains "class Client" "typed client wrapper emitted"

# 3. Compile it in a host package. The templates emit a bare module — the
# consuming application provides package.json/tsconfig, exactly what the
# fixture models. npm install talks to the public npm registry (no secrets).
mkdir app
cp .tx3/codegen/ts-client/demo/protocol.ts app/
cp "${JOURNEY_HOME}/package.json" "${JOURNEY_HOME}/tsconfig.json" "${JOURNEY_HOME}/resolve.ts" app/
cd app
run_cmd "npm install — fetch tx3-sdk + typescript" npm install --no-fund --no-audit --loglevel=error
run_cmd "tsc — compile the generated client + host consumer" npx tsc
assert_exists "dist/protocol.js" "generated client compiled"
assert_exists "dist/resolve.js" "host consumer compiled"
cd ..

# 4. Round-trip: resolve a transfer *through the generated client* against a
# local devnet. 05-invoke covers the `trix invoke` path; this is the
# generated-SDK path over the same TRP. Resolve-only — no signing, headless.
ALICE="$("${TRIX}" identities alice address-testnet 2>/dev/null | grep '^addr' | head -n1)"
BOB="$("${TRIX}" identities bob address-testnet 2>/dev/null | grep '^addr' | head -n1)"
[[ -n "${ALICE}" && -n "${BOB}" ]] || die "could not resolve alice/bob testnet addresses"

run_cmd "trix devnet --background — start a local devnet" "${TRIX}" devnet --background
assert_output_contains "devnet started in background"
DEVNET_PIDS="$(pgrep -f 'dolos.*daemon' | tr '\n' ' ')"
# shellcheck disable=SC2064
trap "[[ -n \"${DEVNET_PIDS}\" ]] && kill -9 ${DEVNET_PIDS} 2>/dev/null" EXIT
for _ in $(seq 1 30); do
(exec 3<>/dev/tcp/127.0.0.1/8164) 2>/dev/null && { exec 3>&- 3<&-; break; }
sleep 1
done
sleep 3

run_cmd "node resolve.js — resolve a transfer through the generated client" \
node app/dist/resolve.js "http://127.0.0.1:8164" "${ALICE}" "${BOB}"
assert_output_contains '"cbor"' "generated client resolved to an unsigned transaction"
assert_output_contains '"hash"' "resolved tx carries a hash"

journey_end
12 changes: 12 additions & 0 deletions journeys/07-codegen-consume/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "codegen-consume-host",
"private": true,
"type": "module",
"dependencies": {
"tx3-sdk": "^0.13.0"
},
"devDependencies": {
"@types/node": "^22.14.1",
"typescript": "^5.8.3"
}
}
21 changes: 21 additions & 0 deletions journeys/07-codegen-consume/resolve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Host-app consumer of the generated bindings: bind the parties, build the
// scaffolded `transfer`, and resolve it through a live TRP endpoint. Compiling
// this file type-checks the generated client's surface; running it exercises
// the full generated-SDK resolve path.
import { Party } from "tx3-sdk";
import { Client, PROTOCOL_NAME, type TransferParams } from "./protocol.js";

const [endpoint, sender, receiver] = process.argv.slice(2);
if (!endpoint || !sender || !receiver) {
console.error("usage: resolve.js <trp-endpoint> <sender-addr> <receiver-addr>");
process.exit(2);
}

const params: TransferParams = { quantity: 2_000_000 };

const client = new Client({ endpoint }, "local")
.withSender(Party.address(sender))
.withReceiver(Party.address(receiver));

const resolved = await client.transfer(params).resolve();
console.log(JSON.stringify({ protocol: PROTOCOL_NAME, hash: resolved.hash, cbor: resolved.txHex }));
14 changes: 14 additions & 0 deletions journeys/07-codegen-consume/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "node16",
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["*.ts"],
"exclude": ["node_modules"]
}
30 changes: 30 additions & 0 deletions journeys/08-interface-consume/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 08-interface-consume

The consumer half of the interface story with real binaries, no registry: build a publisher
protocol's TII with the real toolchain, prime a consumer project's
`.tx3/tii/<scope>/<name>/<version>/` cache by hand (byte-for-byte what `trix use` writes:
`main.tii`, informative `main.tx3`, `metadata.json` with a matching digest), declare it under
`[interfaces]`, then drive `trix inspect tir` and `trix codegen` through it.

Since tx3-lang/trix#129 hermeticized trix's local suite, the interface plumbing there (digest
gate, alias → cached-TII routing) runs against a fake `tx3c` and a canned TII fixture. What only
this journey validates is the *real* half: `tx3c decode` of a genuinely published-shaped TII, and
real codegen from a cached interface artifact — the interface is consumed, never recompiled.

- **Scope:** compile/lower (no devnet). Offline except the codegen templates download (GitHub,
anonymous). No secrets, no registry infrastructure.
- **Toolchain gate:** `#@ min-tx3c: 0.23.0` (matches the verified toolchain).
- **Fixtures:** `trix.toml` + `main.tx3` — the publisher protocol (`acme/widget:0.1.0`, one
`widget_transfer` tx with distinctive party names to assert on).

## What's asserted

1. `trix build` puts the publisher TII at `<scope>/<name>/<version>` in the TII tree.
2. `inspect tir --tx widget::widget_transfer` (alias) and
`inspect tir --tx acme/widget:0.1.0::widget_transfer` (full ref) both decode the cached TII —
asserted on the publisher's party/parameter names appearing in the TIR.
3. `trix codegen --plugin ts-client` generates bindings for the interface from its cached TII into
`.tx3/codegen/ts-client/widget/`, embedding the interface's identity and TIR envelope.

The **full cut** (publish to a real registry, `trix use` pull path, auth) is a separate, heavier
journey tracked in the umbrella plans.
86 changes: 86 additions & 0 deletions journeys/08-interface-consume/journey.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
#
# Journey 08 — interface consume. See README.md for what this covers.
# Run via e2e/run.sh, which provides $TRIX and an isolated working directory.
#
#@ min-tx3c: 0.23.0

source "${E2E_LIB:?E2E_LIB not set — run this journey via e2e/run.sh}"

JOURNEY_HOME="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"

journey_begin "08-interface-consume" "build a real TII → prime the interface cache → inspect + codegen through it"

sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | cut -d' ' -f1
else
shasum -a 256 "$1" | cut -d' ' -f1
fi
}

# 1. Publisher side: compile the interface's TII with the real toolchain — the
# same normative artifact `trix publish` would push to the registry.
mkdir widget && cd widget
cp "${JOURNEY_HOME}/trix.toml" trix.toml
cp "${JOURNEY_HOME}/main.tx3" main.tx3
run_cmd "trix build — compile the publisher protocol" "${TRIX}" build
TII_SRC=".tx3/tii/acme/widget/0.1.0/main.tii"
assert_exists "${TII_SRC}" "publisher TII at <scope>/<name>/<version>"
DIGEST="sha256:$(sha256_of "${TII_SRC}")"
cd ..

# 2. Consumer side: a separate project that declares acme/widget:0.1.0 and
# consumes it from a hand-primed cache — byte-for-byte the layout `trix use`
# writes (main.tii + informative main.tx3 + metadata.json whose digest matches
# the trix.toml pin). No registry involved: a cached interface works offline.
mkdir consumer && cd consumer
run_cmd "trix init -y — scaffold the consumer project" "${TRIX}" init -y
CACHE=".tx3/tii/acme/widget/0.1.0"
mkdir -p "${CACHE}"
cp "../widget/${TII_SRC}" "${CACHE}/main.tii"
cp ../widget/main.tx3 "${CACHE}/main.tx3"
cat > "${CACHE}/metadata.json" <<EOF
{
"scope": "acme",
"name": "widget",
"version": "0.1.0",
"digest": "${DIGEST}",
"published_date": 0,
"fetched_at": 0,
"has_readme": false
}
EOF
cat >> trix.toml <<EOF

[interfaces.widget]
ref = "acme/widget:0.1.0"
digest = "${DIGEST}"
EOF
ok "interface cache primed and declared (${DIGEST})"

# 3. Real decode: an interface tx is served from its cached published TII via
# `tx3c decode` — never recompiled from source. Both reference forms must
# address the same cache.
run_cmd "trix inspect tir --tx widget::widget_transfer — decode by alias" \
"${TRIX}" inspect tir --tx widget::widget_transfer
assert_output_contains '"owner"' "publisher's Owner party in the decoded TIR"
assert_output_contains '"buyer"' "publisher's Buyer party in the decoded TIR"
assert_output_contains '"quantity"' "tx parameter in the decoded TIR"

run_cmd "trix inspect tir --tx acme/widget:0.1.0::widget_transfer — decode by full ref" \
"${TRIX}" inspect tir --tx acme/widget:0.1.0::widget_transfer
assert_output_contains '"buyer"' "full registry ref routes to the same cached TII"

# 4. Codegen for the interface: bindings are generated from the cached TII
# (real `tx3c codegen`), alongside the consumer's own protocol.
run_cmd "trix codegen --plugin ts-client — generate for project + interface" \
"${TRIX}" codegen --plugin ts-client
assert_output_contains "Bindgen successful for 'widget'"
assert_exists ".tx3/codegen/ts-client/widget/protocol.ts" "interface bindings in their own subdir"

run_cmd "read the interface client" cat .tx3/codegen/ts-client/widget/protocol.ts
assert_output_contains 'PROTOCOL_NAME = "widget"' "interface identity embedded from the cached TII"
assert_output_contains "WIDGET_TRANSFER_TIR" "interface tx TIR envelope embedded"

journey_end
Loading
Loading