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
3 changes: 3 additions & 0 deletions .github/workflows/ts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
node:
- "22"
- "24"
- "26"

defaults:
run:
Expand All @@ -45,6 +46,8 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
# The runner image can lag behind the security releases that `engines` requires.
check-latest: true
cache: npm
cache-dependency-path: ts/package-lock.json

Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/ts-standalone-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ permissions:
contents: read

env:
NODE_VERSION: "20"
NODE_VERSION: "22"

jobs:
quality:
Expand All @@ -29,6 +29,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
# The runner image can lag behind the security releases that `engines` requires.
check-latest: true
cache: npm
cache-dependency-path: ts/package-lock.json

Expand Down Expand Up @@ -94,6 +96,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
# The runner image can lag behind the security releases that `engines` requires.
check-latest: true
cache: npm
cache-dependency-path: ts/package-lock.json

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Everything below (except the TypeScript Implementation section) refers to the Ja

## TypeScript Implementation

The `ts/` package is a self-contained, agent-first CLI (Node.js 20+, ESM, TypeScript). Every command
The `ts/` package is a self-contained, agent-first CLI (Node.js 22.23.4+ / 24.22.1+ / 26.12.1+, ESM, TypeScript). Every command
has a stable JSON envelope, deterministic exit codes, and discoverable schemas; interactive prompts
are used only for secret input (create / import / backup / delete). **All `ts/` commands run from the
`ts/` directory.**
Expand Down
2 changes: 1 addition & 1 deletion ts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The TRON ids used before CAIP-2 (`tron:mainnet`, `tron:nile`, `tron:shasta`) rem

## Install

**Prerequisites**: [Node.js](https://nodejs.org) **20 or later** (`node --version` to check). Ledger signing additionally needs a supported Ledger device with the TRON or Ethereum app installed — see the [Ledger guide](docs/guide/ledger.md).
**Prerequisites**: [Node.js](https://nodejs.org) **22.23.4+, 24.22.1+ or 26.12.1+** — the October 2026 security releases (`node --version` to check). Ledger signing additionally needs a supported Ledger device with the TRON or Ethereum app installed — see the [Ledger guide](docs/guide/ledger.md).

```bash
npm install -g @tron-walletcli/wallet-cli
Expand Down
2 changes: 1 addition & 1 deletion ts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"access": "public"
},
"engines": {
"node": ">=20"
"node": ">=22.23.4 <23 || >=24.22.1 <25 || >=26.12.1"
},
"scripts": {
"build": "tsup",
Expand Down
18 changes: 18 additions & 0 deletions ts/src/adapters/outbound/x402/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ it("rejects malformed request URLs and keeps serving health requests", async ()
});
});

it("closes the connection after a 402 challenge so the paid retry cannot reuse a stale socket", async () => {
await withServer({}, async (port) => {
const response = await new Promise<{ status?: number; connection?: string }>(
(resolve, reject) => {
const req = request({ hostname: "127.0.0.1", port, path: "/pay" }, (res) => {
res.resume();
res.on("end", () =>
resolve({ status: res.statusCode, connection: res.headers.connection }),
);
});
req.on("error", reject);
req.end();
},
);
expect(response).toEqual({ status: 402, connection: "close" });
});
});

it.each([
[{ success: true, transaction: "", network: "tron:0xcd8690dc" }, 502],
[{ success: true, transaction: "a".repeat(64), network: "eip155:1" }, 502],
Expand Down
4 changes: 4 additions & 0 deletions ts/src/adapters/outbound/x402/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ export class X402HttpServer implements X402ServerPort {
if (pathname !== "/pay") return json(response, 404, { error: "not found" });
const signature = request.headers["payment-signature"];
if (!signature || Array.isArray(signature)) {
// The paid retry follows key decryption and signing, which can block the client's event
// loop past keepAliveTimeout (roundtrip runs both ends in one process). A reused socket
// would then be reset under the retry, so make the client open a fresh one.
response.setHeader("connection", "close");
response.setHeader("payment-required", encodePaymentRequiredHeader(challenge as never));
return json(response, 402, challenge);
}
Expand Down
2 changes: 1 addition & 1 deletion ts/test/bai-recharge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,5 @@ it.each([
rmSync(home, { recursive: true, force: true });
}
},
60000,
120000,
);
8 changes: 4 additions & 4 deletions ts/test/beta-server-roundtrip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ import {appendFileSync} from 'node:fs';const realFetch=globalThis.fetch;globalTh
env: { ...process.env, WALLET_CLI_HOME: home },
input: "test-password\n",
encoding: "utf8",
timeout: 20000,
timeout: 40000,
},
);
expect(r.status, r.stderr + r.stdout).toBe(0);
Expand All @@ -159,7 +159,7 @@ import {appendFileSync} from 'node:fs';const realFetch=globalThis.fetch;globalTh
rmSync(home, { recursive: true, force: true });
}
},
30000,
60000,
);

it
Expand Down Expand Up @@ -262,7 +262,7 @@ globalThis.fetch = async(input,init) => {
env: { ...process.env, WALLET_CLI_HOME: home },
input: "test-password\n",
encoding: "utf8",
timeout: 20000,
timeout: 40000,
},
);
if (missing) {
Expand Down Expand Up @@ -296,7 +296,7 @@ globalThis.fetch = async(input,init) => {
rmSync(home, { recursive: true, force: true });
}
},
30000,
60000,
);

it.skipIf(!entry).each(["0", "0.000", "0.5", "9007199254740992"])(
Expand Down
4 changes: 3 additions & 1 deletion ts/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export default defineConfig({
sequence: { groupOrder: 1 },
environment: "node",
include: ["test/**/*.test.ts"],
testTimeout: 30_000,
// Cases that spawn the CLI several times with real scrypt have measured ~30s on a loaded
// 4-vCPU CI runner, so 30s left no headroom.
testTimeout: 60_000,
hookTimeout: 30_000,
// Build once, then every case spawns `node dist/index.js` instead of cold-transpiling
// the whole CLI through tsx. `verify:package` sets WALLET_CLI_TEST_ENTRY to the
Expand Down