@agentlayer.tech/wallet 0.1.90 → 0.1.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-evm-wallet",
3
- "version": "0.1.90",
3
+ "version": "0.1.92",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate EVM wallet service built on Tether WDK.",
@@ -8,7 +8,7 @@
8
8
  "bootstrap": "sh ./bootstrap.sh",
9
9
  "start:local": "sh ./run-local.sh",
10
10
  "start": "node src/server.js",
11
- "check": "node --check src/server.js && node --check src/wdk_evm_wallet.js && node --check src/config.js && node --check src/json.js && node --check src/local_vault.js && node --check src/network_state.js",
11
+ "check": "node --check src/server.js && node --check src/shutdown.js && node --check src/wdk_evm_wallet.js && node --check src/config.js && node --check src/json.js && node --check src/local_vault.js && node --check src/network_state.js",
12
12
  "test:swap-runtime": "node --test --test-concurrency=1 tests/smoke_swap_runtime.mjs",
13
13
  "test:aave-runtime": "node --test --test-concurrency=1 tests/smoke_aave_runtime.mjs",
14
14
  "test:morpho-runtime": "node --test --test-concurrency=1 tests/smoke_morpho_runtime.mjs",
@@ -16,6 +16,7 @@
16
16
  "test:uniswap-runtime": "node --test --test-concurrency=1 tests/smoke_uniswap_runtime.mjs",
17
17
  "test:unit": "node --test tests/unit_uniswap_helpers.mjs",
18
18
  "test:identity": "node --test tests/unit_instance_identity.mjs",
19
+ "test:shutdown": "node --test tests/shutdown.test.mjs",
19
20
  "test:network-config": "node --test tests/unit_network_config.mjs",
20
21
  "test:network-state": "node --test tests/unit_network_state.mjs",
21
22
  "test:wallet-network": "node --test tests/unit_wdk_wallet_network.mjs"
@@ -7,6 +7,7 @@ import { loadConfig } from "./config.js";
7
7
  import { readJsonBody, sendJson } from "./json.js";
8
8
  import { LocalEvmVault } from "./local_vault.js";
9
9
  import { EvmNetworkState } from "./network_state.js";
10
+ import { createShutdownCoordinator, withTrackedRequest } from "./shutdown.js";
10
11
  import { WdkEvmWalletService } from "./wdk_evm_wallet.js";
11
12
 
12
13
  const config = loadConfig();
@@ -683,12 +684,32 @@ async function handleRequest(request, response) {
683
684
  }
684
685
 
685
686
  const server = createServer((request, response) => {
686
- handleRequest(request, response).catch((error) => {
687
- const shaped = toErrorResponse(error, new URL(request.url || "/", "http://localhost").pathname, 500);
688
- sendJson(response, shaped.statusCode, shaped.payload);
687
+ void withTrackedRequest(shutdown, async () => {
688
+ try {
689
+ await handleRequest(request, response);
690
+ } catch (error) {
691
+ const shaped = toErrorResponse(
692
+ error,
693
+ new URL(request.url || "/", "http://localhost").pathname,
694
+ 500,
695
+ );
696
+ sendJson(response, shaped.statusCode, shaped.payload);
697
+ }
689
698
  });
690
699
  });
691
700
 
701
+ // 8s stays strictly inside the 10s SIGTERM->SIGKILL window the Python client
702
+ // allows (agent_wallet/evm_user_wallets.py), so the orderly path always wins.
703
+ const shutdown = createShutdownCoordinator({
704
+ closeServer: () => server.close(),
705
+ exit: (code) => process.exit(code),
706
+ graceMs: 8000,
707
+ log: (message) => console.log(message),
708
+ });
709
+
710
+ process.on("SIGTERM", () => shutdown.begin("SIGTERM"));
711
+ process.on("SIGINT", () => shutdown.begin("SIGINT"));
712
+
692
713
  server.listen(config.port, config.host, () => {
693
714
  console.log(
694
715
  `wdk-evm-wallet listening on ${config.host}:${config.port} (${config.network})`
@@ -0,0 +1,63 @@
1
+ // Coordinates an orderly shutdown: stop accepting connections, let in-flight
2
+ // requests finish, then exit. Vault writes are not atomic (see local_vault.js),
3
+ // so an abrupt exit mid-write can truncate a wallet file. Every dependency is
4
+ // injectable so the drain loop can be tested without real timers.
5
+ export function createShutdownCoordinator({
6
+ closeServer,
7
+ exit,
8
+ now = () => Date.now(),
9
+ schedule = (fn, ms) => setTimeout(fn, ms),
10
+ graceMs = 8000,
11
+ pollMs = 100,
12
+ log = () => {},
13
+ }) {
14
+ let inFlight = 0;
15
+ let shuttingDown = false;
16
+
17
+ function begin(signalName) {
18
+ if (shuttingDown) return;
19
+ shuttingDown = true;
20
+ log(
21
+ `wdk-evm-wallet received ${signalName}, draining ${inFlight} in-flight request(s)`,
22
+ );
23
+ closeServer();
24
+
25
+ const deadline = now() + graceMs;
26
+ const tick = () => {
27
+ if (inFlight <= 0 || now() >= deadline) {
28
+ exit(0);
29
+ return;
30
+ }
31
+ schedule(tick, pollMs);
32
+ };
33
+ tick();
34
+ }
35
+
36
+ return {
37
+ begin,
38
+ trackStart() {
39
+ inFlight += 1;
40
+ },
41
+ trackEnd() {
42
+ if (inFlight > 0) inFlight -= 1;
43
+ },
44
+ isShuttingDown() {
45
+ return shuttingDown;
46
+ },
47
+ get inFlight() {
48
+ return inFlight;
49
+ },
50
+ };
51
+ }
52
+
53
+ // Track the lifetime of the handler itself, not the HTTP connection. A client
54
+ // can disconnect while a vault write or transaction is still running, and the
55
+ // response "close" event must not make shutdown treat that work as finished.
56
+ export async function withTrackedRequest(coordinator, handler) {
57
+ coordinator.trackStart();
58
+ try {
59
+ return await handler();
60
+ } finally {
61
+ coordinator.trackEnd();
62
+ }
63
+ }