wao 0.40.2 → 0.41.0

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.
Files changed (74) hide show
  1. package/cjs/accounts-web.js +25 -0
  2. package/cjs/accounts.js +38 -0
  3. package/cjs/adaptor-base.js +504 -287
  4. package/cjs/adaptor-cf.js +42 -0
  5. package/cjs/ao-loader.js +1 -1
  6. package/cjs/ao.js +18 -6
  7. package/cjs/aoconnect-base.js +1010 -546
  8. package/cjs/aoconnect-cf.js +24 -0
  9. package/cjs/ar-remote.js +277 -0
  10. package/cjs/armem-base.js +822 -211
  11. package/cjs/armem-cf.js +128 -0
  12. package/cjs/armem.js +11 -5
  13. package/cjs/bar.js +511 -173
  14. package/cjs/car.js +37 -0
  15. package/cjs/cf-env.js +54 -0
  16. package/cjs/cf.js +76 -0
  17. package/cjs/cli.js +12 -6
  18. package/cjs/create.js +1 -1
  19. package/cjs/devs.js +53 -0
  20. package/cjs/dodb.js +116 -0
  21. package/cjs/hb.js +136 -53
  22. package/cjs/hyperbeam.js +85 -44
  23. package/cjs/keygen.js +94 -0
  24. package/cjs/run.js +4 -1
  25. package/cjs/server.js +40 -9
  26. package/cjs/storage-multi.js +525 -0
  27. package/cjs/tgql-d1.js +664 -0
  28. package/cjs/tgql.js +293 -172
  29. package/cjs/workspace/.claude/agents/tester.md +2 -2
  30. package/cjs/workspace/.claude/skills/build/SKILL.md +2 -2
  31. package/cjs/workspace/.claude/skills/test/SKILL.md +3 -2
  32. package/cjs/workspace/CLAUDE.md +2 -2
  33. package/cjs/workspace/README.md +1 -1
  34. package/cjs/workspace/docs/debug.md +9 -4
  35. package/cjs/workspace/docs/hyperbeam-devices.md +50 -1
  36. package/cjs/workspace/package.json +3 -3
  37. package/esm/accounts-web.js +14 -0
  38. package/esm/accounts.js +27 -0
  39. package/esm/adaptor-base.js +129 -31
  40. package/esm/adaptor-cf.js +11 -0
  41. package/esm/ao-loader.js +1 -1
  42. package/esm/ao.js +21 -2
  43. package/esm/aoconnect-base.js +248 -7
  44. package/esm/aoconnect-cf.js +9 -0
  45. package/esm/ar-remote.js +87 -0
  46. package/esm/armem-base.js +304 -53
  47. package/esm/armem-cf.js +67 -0
  48. package/esm/armem.js +7 -2
  49. package/esm/bar.js +126 -16
  50. package/esm/car.js +10 -0
  51. package/esm/cf-env.js +29 -0
  52. package/esm/cf.js +11 -0
  53. package/esm/cli.js +6 -2
  54. package/esm/create.js +1 -1
  55. package/esm/devs.js +15 -0
  56. package/esm/dodb.js +26 -0
  57. package/esm/hb.js +93 -16
  58. package/esm/hyperbeam.js +68 -30
  59. package/esm/keygen.js +47 -0
  60. package/esm/run.js +4 -1
  61. package/esm/server.js +29 -9
  62. package/esm/storage-multi.js +183 -0
  63. package/esm/tgql-d1.js +407 -0
  64. package/esm/tgql.js +29 -10
  65. package/esm/workspace/.claude/agents/tester.md +2 -2
  66. package/esm/workspace/.claude/skills/build/SKILL.md +2 -2
  67. package/esm/workspace/.claude/skills/test/SKILL.md +3 -2
  68. package/esm/workspace/CLAUDE.md +2 -2
  69. package/esm/workspace/README.md +1 -1
  70. package/esm/workspace/docs/debug.md +9 -4
  71. package/esm/workspace/docs/hyperbeam-devices.md +50 -1
  72. package/esm/workspace/package.json +3 -3
  73. package/package.json +10 -3
  74. package/postinstall.cjs +84 -0
@@ -29,7 +29,7 @@ yarn test # all tests
29
29
  yarn test test/aos.test.js # specific file
30
30
  ```
31
31
 
32
- The test command runs: `node --experimental-wasm-memory64 --test --test-concurrency=1`
32
+ The test command runs: `node --test --test-concurrency=1` (Node 24+ enables wasm-memory64 by default and rejects the experimental flag; Node 22 users add `--experimental-wasm-memory64` via NODE_OPTIONS).
33
33
 
34
34
  ## Systematic Debugging
35
35
 
@@ -59,7 +59,7 @@ Report test results with specifics: which tests pass, which fail, and the error
59
59
  ## Common Issues
60
60
 
61
61
  - **Port already in use**: Kill stale `beam.smp` processes
62
- - **WASM memory error**: Ensure `--experimental-wasm-memory64` flag is present
62
+ - **WASM memory error**: On Node 22 set `NODE_OPTIONS=--experimental-wasm-memory64`. On Node 24+ the flag is default-on (and rejected if passed explicitly — "bad option: --experimental-wasm-memory64")
63
63
  - **Process not found**: Check that `src_data` path is correct
64
64
  - **HyperBEAM timeout**: Ensure `hbeam.kill()` is called in `after()`
65
65
  - **Send().receive() hangs**: Does NOT work on genesis-wasm — use fire-and-forget `Send()` + separate Handlers.add calls
@@ -210,5 +210,5 @@ Never force a task to `done` status with failing tests. The TaskCompleted hook w
210
210
  - Verify task statuses — a task stuck as `in_progress` blocks progression
211
211
 
212
212
  ### Permission denied on yarn test
213
- - Check `node --version` is 20+
214
- - Verify `--experimental-wasm-memory64` flag is present in package.json
213
+ - Check `node --version` is 22+ (24+ recommended)
214
+ - On Node 22, set `NODE_OPTIONS=--experimental-wasm-memory64`; on Node 24+ the flag is default-on and is rejected if passed explicitly
@@ -51,5 +51,6 @@ yarn test
51
51
  - Check `package.json` has `"wao"` in dependencies
52
52
 
53
53
  ### WASM memory error
54
- - Ensure `--experimental-wasm-memory64` flag is in the test command
55
- - Node.js 20+ required
54
+ - Node 24+ enables wasm-memory64 by default — DO NOT pass `--experimental-wasm-memory64` (it's rejected with "bad option")
55
+ - Node 22: set `NODE_OPTIONS=--experimental-wasm-memory64` before running
56
+ - Node.js 22+ required
@@ -95,7 +95,7 @@ yarn deploy --mainnet # remote HyperBEAM (push-1)
95
95
  yarn deploy --mainnet --lua # remote HyperBEAM (Lua mode)
96
96
  ```
97
97
 
98
- Runs: `node --experimental-wasm-memory64 --test --test-concurrency=1`
98
+ Runs: `node --test --test-concurrency=1` (Node 24+; on Node 22 prefix with `--experimental-wasm-memory64`)
99
99
 
100
100
  ## Deployment Targets
101
101
 
@@ -109,7 +109,7 @@ Runs: `node --experimental-wasm-memory64 --test --test-concurrency=1`
109
109
  - **Remote nodes**: Use `push-1` through `push-10` for full compute. `push.forward.computer` is push-only (no compute).
110
110
  - **Lua mode**: Faster but no `receive()` — use `msg.reply()` pattern instead.
111
111
  - **Wallet**: Run `yarn keygen` to generate `.wallet.json`.
112
- - **HyperBEAM fork**: `git clone -b wao-beta3 https://github.com/weavedb/HyperBEAM.git`
112
+ - **HyperBEAM fork**: `git clone -b wao-final https://github.com/weavedb/HyperBEAM.git`
113
113
 
114
114
  ### Frontend Commands
115
115
 
@@ -52,7 +52,7 @@ yarn deploy --local-hb --lua src/counter.lua
52
52
 
53
53
  **Remote nodes**: Use `push-1` through `push-10` for full compute. `push.forward.computer` is push-only (no compute).
54
54
 
55
- **HyperBEAM fork**: `git clone -b wao-beta3 https://github.com/weavedb/HyperBEAM.git && cd HyperBEAM && rebar3 compile`
55
+ **HyperBEAM fork**: `git clone -b wao-final https://github.com/weavedb/HyperBEAM.git && cd HyperBEAM && rebar3 compile`
56
56
 
57
57
  ---
58
58
 
@@ -144,13 +144,17 @@ await hb.schedule({
144
144
 
145
145
  **Problem:** `WebAssembly.Memory` errors or WASM module fails to load.
146
146
 
147
- **Fix:** Ensure the `--experimental-wasm-memory64` flag is present:
147
+ **Fix:** Node 24+ enables wasm-memory64 by default (and rejects the experimental flag at startup with "bad option"). On Node 22 or older, prefix with `--experimental-wasm-memory64`:
148
148
 
149
149
  ```bash
150
+ # Node 24+: no flag needed
151
+ node --test test/aos.test.js
152
+
153
+ # Node 22:
150
154
  node --experimental-wasm-memory64 --test test/aos.test.js
151
155
  ```
152
156
 
153
- The `yarn test` script should already include this flag.
157
+ The `yarn test` script ships without the flag (Node 24+ default); set `NODE_OPTIONS=--experimental-wasm-memory64` if you're on older Node.
154
158
 
155
159
  ### Genesis-WASM Server
156
160
 
@@ -202,7 +206,8 @@ The `yarn test` script should already include this flag.
202
206
  | `badarg in list_to_atom` | Atom not pre-registered | Add to preRegisterAtoms |
203
207
  | `multiple_matches` | Duplicate message content | Add nonce to messages |
204
208
  | `timeout` | Process or server hung | Kill stale processes, increase timeout |
205
- | `WASM memory error` | Missing memory64 flag | Add `--experimental-wasm-memory64` |
209
+ | `WASM memory error` | Missing memory64 (Node 22) | Set `NODE_OPTIONS=--experimental-wasm-memory64` (Node 22) — Node 24+ has it default-on |
210
+ | `bad option: --experimental-wasm-memory64` | Passing the flag on Node 24+ | Remove the flag — Node 24+ rejects it |
206
211
  | `Process not found` | Bad pid or deploy failed | Check deploy result, verify pid |
207
212
  | `Insufficient balance` | Payment balance too low | Top up via simple-pay or p4 |
208
213
  | `Not signed` | Missing wallet/JWK | Call `.init(jwk)` before operations |
@@ -215,7 +220,7 @@ When tests fail, check in order:
215
220
 
216
221
  1. **Ports clear?** `lsof -ti :10000-10010 | xargs -r kill -9`
217
222
  2. **beam.smp killed?** `pkill -f beam.smp`
218
- 3. **WASM flag?** `--experimental-wasm-memory64` in test command
223
+ 3. **WASM flag?** Node 24+ default-on; on Node 22 add `--experimental-wasm-memory64` (and on Node 24+ remove it — it's rejected)
219
224
  4. **HyperBEAM dir?** `ls ./HyperBEAM` exists
220
225
  5. **Wallet exists?** `.wallet.json` present (auto-generated)
221
226
  6. **hbeam.kill() in after()?** Always clean up
@@ -348,7 +348,7 @@ const result = await hb.computeLegacy({ pid, slot })
348
348
  **Limitations:**
349
349
  - External CU is single-pass — **`Send().receive()` does NOT work**
350
350
  - Auto-starts CU server at port 6363
351
- - Requires `--experimental-wasm-memory64` Node.js flag
351
+ - Requires wasm-memory64 (Node 24+ default; Node 22 needs `--experimental-wasm-memory64`)
352
352
 
353
353
  Functions: `init/3`, `compute/3`, `snapshot/3`, `import/3`
354
354
 
@@ -540,6 +540,55 @@ Query Arweave via GraphQL from within HyperBEAM.
540
540
 
541
541
  ---
542
542
 
543
+ ## v0.9-FINAL Devices (new since beta3)
544
+
545
+ ### location@1.0 — Scheduler-Location Registry
546
+
547
+ Replaces the old `/~scheduler@1.0/location` endpoint. Stores per-address scheduler-location records (with DNS/IP resolution and a TTL) in a node-local cache, with fallback to the Arweave gateway.
548
+
549
+ - `POST /~location@1.0/node` — operator generates and registers a record (signed)
550
+ - `POST /~location@1.0/known` — cache a peer's record if newer than the local one
551
+ - Read via `dev_whois` / `dev_location_cache`
552
+
553
+ ### metering@1.0 — Dynamic P4 Pricing
554
+
555
+ A P4 pricing device that records resource usage per request/response lifecycle.
556
+
557
+ - `estimate/3` opens a process-local session
558
+ - `consume/3` increments usage during the session
559
+ - `price/3` closes the session and returns the integer charge
560
+ - Operator sets `metering-rates` in node message (resource → AO units per resource unit)
561
+
562
+ ### blacklist@1.0 — Content Moderation by Blacklist
563
+
564
+ A request hook for filtering. Operator configures `blacklist-providers` (list) in node message; each provider returns a moderation set.
565
+
566
+ ### bundler@1.0 — ANS-104 Bundler Integration
567
+
568
+ Outbound bundler for posting ANS-104 transactions to upstream Arweave bundlers (e.g. `up.arweave.net`).
569
+
570
+ ### gzip@1.0 — gzip Codec
571
+
572
+ Compress/decompress message bodies via gzip during processing.
573
+
574
+ ### rate-limit@1.0 — Per-Address Rate Limiting
575
+
576
+ Operator-configurable per-address rate limits applied as a request hook.
577
+
578
+ ### tx@1.0 — Arweave TX Helpers
579
+
580
+ Helpers for fetching and validating Arweave transactions inline.
581
+
582
+ ### b32-name@1.0 — Base-32 Name Encoding
583
+
584
+ Helpers for base-32 name encoding/decoding used by other devices.
585
+
586
+ ### arweave@2.9 — Arweave Block + GraphQL Integration
587
+
588
+ Renamed in v0.9-FINAL (was `arweave@2.9-pre`). Provides Arweave block cache, offset helpers, and common Arweave protocol primitives.
589
+
590
+ ---
591
+
543
592
  ## Multi-Instance Setup
544
593
 
545
594
  Running multiple HyperBEAM nodes requires unique ports and Erlang node names:
@@ -3,9 +3,9 @@
3
3
  "version": "0.0.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
- "test": "node --experimental-wasm-memory64 --test --test-concurrency=1",
7
- "test-only": "node --experimental-wasm-memory64 --test-only --test-concurrency=1",
8
- "test-all": "node --experimental-wasm-memory64 --test --test-concurrency=1 test/**/*.test.js",
6
+ "test": "node --test --test-concurrency=1",
7
+ "test-only": "node --test-only --test-concurrency=1",
8
+ "test-all": "node --test --test-concurrency=1 test/**/*.test.js",
9
9
  "start": "trap 'kill $(jobs -p)' EXIT; node dashboard/server.js & cd dashboard && npx vite",
10
10
  "start:api": "node dashboard/server.js",
11
11
  "keygen": "node scripts/keygen.js",
@@ -1,6 +1,7 @@
1
1
  import * as WarpArBundles from "warp-arbundles"
2
2
  const pkg = WarpArBundles.default ?? WarpArBundles
3
3
  const { createData, ArweaveSigner } = pkg
4
+ import { toAddr } from "./utils.js"
4
5
 
5
6
  function createDataItemSigner(wallet) {
6
7
  const signer = async ({ data, tags, target, anchor }) => {
@@ -107,6 +108,19 @@ let mu = {
107
108
  addr: "eNaLJLsMiWCSWvQKNbk_YT-9ydeWl9lrWwXxLVp9kcg",
108
109
  }
109
110
 
111
+ if (globalThis.__WAO_WALLETS__) {
112
+ const w = globalThis.__WAO_WALLETS__
113
+ for (let i = 0; i < 3; i++) {
114
+ if (w[`acc${i}`]) {
115
+ const jwk = w[`acc${i}`]
116
+ acc[i] = { jwk, addr: toAddr(jwk.n) }
117
+ }
118
+ }
119
+ if (w.su) su = { jwk: w.su, addr: toAddr(w.su.n) }
120
+ if (w.cu) cu = { jwk: w.cu, addr: toAddr(w.cu.n) }
121
+ if (w.mu) mu = { jwk: w.mu, addr: toAddr(w.mu.n) }
122
+ }
123
+
110
124
  for (const v of acc) v.signer = createDataItemSigner(v.jwk)
111
125
  mu.signer = createDataItemSigner(mu.jwk)
112
126
  su.signer = createDataItemSigner(su.jwk)
package/esm/accounts.js CHANGED
@@ -1,5 +1,9 @@
1
1
  import { createDataItemSigner } from "@permaweb/aoconnect-69"
2
2
  import { ArweaveSigner } from "arbundles"
3
+ import { readFileSync } from "fs"
4
+ import { fileURLToPath } from "url"
5
+ import { dirname as pathDirname, resolve } from "path"
6
+ import { toAddr } from "./utils.js"
3
7
  const acc = [
4
8
  {
5
9
  jwk: {
@@ -90,6 +94,29 @@ let mu = {
90
94
  addr: "eNaLJLsMiWCSWvQKNbk_YT-9ydeWl9lrWwXxLVp9kcg",
91
95
  }
92
96
 
97
+ try {
98
+ const __fn = fileURLToPath(import.meta.url)
99
+ const __dir = pathDirname(__fn)
100
+ const walletsDir = resolve(__dir, "../devnet/.wallets")
101
+ const tryLoadJWK = name => {
102
+ try {
103
+ return JSON.parse(readFileSync(resolve(walletsDir, `${name}.json`), "utf-8"))
104
+ } catch {
105
+ return null
106
+ }
107
+ }
108
+ for (let i = 0; i < 3; i++) {
109
+ const jwk = tryLoadJWK(`acc${i}`)
110
+ if (jwk) acc[i] = { jwk, addr: toAddr(jwk.n) }
111
+ }
112
+ const suJwk = tryLoadJWK("su")
113
+ if (suJwk) su = { jwk: suJwk, addr: toAddr(suJwk.n) }
114
+ const cuJwk = tryLoadJWK("cu")
115
+ if (cuJwk) cu = { jwk: cuJwk, addr: toAddr(cuJwk.n) }
116
+ const muJwk = tryLoadJWK("mu")
117
+ if (muJwk) mu = { jwk: muJwk, addr: toAddr(muJwk.n) }
118
+ } catch {}
119
+
93
120
  for (const v of acc) v.signer = createDataItemSigner(v.jwk)
94
121
  mu.signer = createDataItemSigner(mu.jwk)
95
122
  su.signer = createDataItemSigner(su.jwk)
@@ -14,13 +14,37 @@ class Adaptor {
14
14
  aoconnect,
15
15
  log = false,
16
16
  db,
17
+ storage,
17
18
  connect,
18
19
  GQL,
19
20
  cu,
20
21
  su,
21
22
  mu,
23
+ d1,
24
+ r2,
25
+ kv,
26
+ network = "ao.DN.1",
22
27
  }) {
28
+ this.network = network
23
29
  this.data = {}
30
+ this._dataTimestamps = {}
31
+ // Clean up abandoned chunk uploads every 60 seconds.
32
+ // unref() so this interval doesn't keep the Node event loop alive after
33
+ // the host server closes — without it, test processes that use Server
34
+ // (e.g. test/hyperbeam/relay.test.js) hang indefinitely waiting on this
35
+ // timer even after every other handle is cleaned up.
36
+ this._chunkCleanupInterval = setInterval(() => {
37
+ const now = Date.now()
38
+ for (const key of Object.keys(this._dataTimestamps)) {
39
+ if (now - this._dataTimestamps[key] > 5 * 60 * 1000) { // 5 min timeout
40
+ delete this.data[key]
41
+ delete this._dataTimestamps[key]
42
+ }
43
+ }
44
+ }, 60 * 1000)
45
+ if (typeof this._chunkCleanupInterval?.unref === "function") {
46
+ this._chunkCleanupInterval.unref()
47
+ }
24
48
  let hb = null
25
49
  if (hb_url) hb = new HB({ url: hb_url })
26
50
  const {
@@ -34,13 +58,15 @@ class Adaptor {
34
58
  monitor,
35
59
  unmonitor,
36
60
  recover,
37
- } = connect(aoconnect, { log, cache: db, hb })
61
+ evaluate,
62
+ } = connect(aoconnect, { log, cache: db, storage, hb, d1, r2, kv })
38
63
  this.su_signer = su
39
64
  this.cu_signer = cu
40
65
  this.mu_signer = mu
41
66
  this.recover = recover
42
67
  this.monitor = monitor
43
68
  this.unmonitor = unmonitor
69
+ this.evaluate = evaluate
44
70
  this.spawn = spawn
45
71
  this._ar = _ar
46
72
  this.message = message
@@ -48,10 +74,10 @@ class Adaptor {
48
74
  this.result = result
49
75
  this.results = results
50
76
  this.mem = mem
51
- this.gql = new GQL({ mem })
77
+ this.gql = new GQL({ mem, d1: d1 || null })
52
78
  }
53
79
  async get(req, res) {
54
- res(await this[req.device](req))
80
+ return res(await this[req.device](req))
55
81
  }
56
82
  async bd(req) {
57
83
  return await this[`bd_${req.method.toLowerCase()}`](req)
@@ -189,6 +215,8 @@ class Adaptor {
189
215
  return await this.cu_post_result(req)
190
216
  case "/dry-run": // not in the AO spec
191
217
  return await this.cu_post_dryrun(req)
218
+ case "/evaluate":
219
+ return await this.cu_post_evaluate(req)
192
220
  default:
193
221
  return await this.bad()
194
222
  }
@@ -271,7 +299,8 @@ class Adaptor {
271
299
 
272
300
  async cu_get_state({ query, params, body, headers, method }) {
273
301
  const pid = params.pid
274
- const memory = this.mem.env[pid]?.memory ?? null
302
+ const p = await this.mem.get("env", pid)
303
+ const memory = p?.memory ?? null
275
304
  if (!memory) {
276
305
  return {
277
306
  status: 404,
@@ -293,7 +322,8 @@ class Adaptor {
293
322
  async cu_get_results({ query, params, body, headers, method }) {
294
323
  const pid = params.pid
295
324
  const { from = null, to = null, sort = "ASC", limit = 25 } = query
296
- let results = this.mem.env[pid]?.results ?? []
325
+ const p = await this.mem.get("env", pid)
326
+ let results = p?.results ?? []
297
327
  if (sort === "DESC") results = reverse(results)
298
328
  let _res = []
299
329
  let i = 1
@@ -301,7 +331,8 @@ class Adaptor {
301
331
  let started = isNil(from)
302
332
  for (let v of results) {
303
333
  if (started) {
304
- _res.push({ cursor: v, node: this.mem.msgs[v]?.res })
334
+ const msg = await this.mem.get("msgs", v)
335
+ _res.push({ cursor: v, node: msg?.res })
305
336
  count++
306
337
  if (!isNil(to) && v === to) break
307
338
  if (limit <= count) break
@@ -316,20 +347,23 @@ class Adaptor {
316
347
  let message = params.mid
317
348
  const process = query["process-id"]
318
349
  // check if recovery is ongoing and
319
- if (isNil(this.mem.env[process])) {
350
+ let p = await this.mem.get("env", process)
351
+ if (isNil(p)) {
320
352
  const { success } = await this.recover(process)
321
353
  if (!success) {
322
354
  console.log("process not found:", query["process-id"])
323
355
  return { status: 404, error: "not Found" }
324
356
  }
357
+ p = await this.mem.get("env", process)
325
358
  }
326
359
  const slot = message
327
360
  if (!/^[0-9a-zA-Z_-]{43,44}$/.test(message)) {
328
- message = this.mem.env[process]?.results?.[slot]
361
+ message = p?.results?.[slot]
329
362
  }
330
363
  if (isNil(message)) {
331
364
  await this.recover(process)
332
- message = this.mem.env[process]?.results?.[slot]
365
+ p = await this.mem.get("env", process)
366
+ message = p?.results?.[slot]
333
367
  if (isNil(message)) return { status: 404, error: "not Found" }
334
368
  }
335
369
  const res2 = await this.result({ message, process })
@@ -338,34 +372,57 @@ class Adaptor {
338
372
  async cu_post_result(...args) {
339
373
  return await this.cu_get_result(...args)
340
374
  }
375
+ async cu_post_evaluate({ query, params, body, headers, method }) {
376
+ const { message, process, data, tags: msgTags, from, is_spawn } = body
377
+ if (!process) return { status: 400, json: { error: "process required" } }
378
+ try {
379
+ const res = await this.evaluate({
380
+ message,
381
+ process,
382
+ data: data || "",
383
+ tags: msgTags,
384
+ from,
385
+ is_spawn,
386
+ module: body.module || (msgTags && tags(msgTags)?.Module),
387
+ scheduler: body.scheduler || (msgTags && tags(msgTags)?.Scheduler),
388
+ })
389
+ return { json: res || {} }
390
+ } catch (e) {
391
+ console.log("cu_post_evaluate error:", e)
392
+ return { status: 500, json: { error: e.message } }
393
+ }
394
+ }
341
395
  async su_get_root({ query, params, body, headers, method }) {
342
396
  return {
343
397
  json: {
344
398
  Unit: "Scheduler",
345
399
  Timestamp: Date.now(),
346
400
  Address: this.su_signer.addr,
347
- Processes: keys(this.mem.env),
401
+ Processes: await this.mem.getFieldKeys("env"),
348
402
  },
349
403
  }
350
404
  }
351
405
 
352
406
  async su_get_timestamp({ query, params, body, headers, method }) {
353
- return { json: { timestamp: Date.now(), block_height: this.mem.height } }
407
+ return { json: { timestamp: Date.now(), block_height: await this.mem.get("height") } }
354
408
  }
355
409
  async su_get_pid({ query, params, body, headers, method }) {
356
410
  const pid = params.pid
411
+ const p = await this.mem.get("env", pid)
357
412
  const edges = map(async v => {
358
413
  const tx = await this.mem.getTx(v)
359
414
  const _tags = tags(v.tags)
360
415
  const mid = _tags.Message
361
416
  const mtx = await this.mem.getTx(mid)
417
+ const mtxOwner = await this.mem.get("addrmap", mtx.owner)
418
+ const txOwner = await this.mem.get("addrmap", tx.owner)
362
419
  return {
363
420
  cursor: v,
364
421
  node: {
365
422
  message: {
366
423
  id: mtx.id,
367
424
  tags: mtx.tags,
368
- owner: this.mem.addrmap[mtx.owner],
425
+ owner: mtxOwner,
369
426
  anchor: mtx.anchor ?? null,
370
427
  target: pid,
371
428
  signature: mtx.signature,
@@ -374,14 +431,14 @@ class Adaptor {
374
431
  assignment: {
375
432
  id: tx.id,
376
433
  tags: tx.tags,
377
- owner: this.mem.addrmap[tx.owner],
434
+ owner: txOwner,
378
435
  anchor: tx.anchor ?? null,
379
436
  target: null,
380
437
  signature: tx.signature,
381
438
  },
382
439
  },
383
440
  }
384
- })(reverse(this.mem.env[pid]?.results ?? [])) // need mod
441
+ })(reverse(p?.results ?? [])) // need mod
385
442
  return { json: { page_info: { has_next_page: false }, edges } }
386
443
  }
387
444
 
@@ -398,23 +455,48 @@ class Adaptor {
398
455
  try {
399
456
  valid = await DataItem.verify(body)
400
457
  } catch (e) {
401
- console.log(e)
458
+ // verification may fail for cross-env items (node→browser); proceed anyway
402
459
  }
403
460
  let type = null
404
461
  let item = null
405
462
  if (valid || true) item = new DataItem(body)
406
463
  await item.setSignature(item.rawSignature)
407
464
  const _tags = tags(item.tags)
465
+ // Allow both ao.TN.1 (legacy testnet) and ao.DN.1 (WAO Devnet) variants
466
+ // through the local emulator. The variant is informational here; the
467
+ // local emulator doesn't actually need to enforce a strict network.
468
+ const _allowedNetworks = new Set([this.network, "ao.TN.1", "ao.DN.1"])
469
+ if (_tags.Variant && !_allowedNetworks.has(_tags.Variant)) {
470
+ return { status: 400, json: { error: `Variant mismatch: expected ${this.network}, got ${_tags.Variant}` } }
471
+ }
408
472
  let err = null
409
473
  if (_tags.Type === "Process") {
410
- const res = await this.spawn({
411
- item,
412
- module: _tags.Module,
413
- scheduler: _tags.Scheduler,
414
- })
415
- if (!res) err = "bad requrest"
474
+ try {
475
+ const res = await this.spawn({
476
+ item,
477
+ module: _tags.Module,
478
+ scheduler: _tags.Scheduler,
479
+ _cu_url: _tags["CU-URL"] || undefined,
480
+ })
481
+ if (!res) err = "bad requrest"
482
+ } catch (e) {
483
+ console.error("MU spawn error:", e.message, e.stack)
484
+ return { status: 500, json: { error: e.message, stack: (e.stack || "").split("\n").slice(0, 8) } }
485
+ }
416
486
  } else if (_tags.Type === "Message") {
417
- await this.message({ item, process: item.target })
487
+ try {
488
+ await this.message({ item, process: item.target })
489
+ } catch (e) {
490
+ console.error("MU message error:", e.message, e.stack)
491
+ err = e.message
492
+ }
493
+ } else if (_tags.Type === "Scheduler-Location" || _tags.Type === "Scheduler-Transfer") {
494
+ try {
495
+ await this._ar.postItems(item, this.su_signer.jwk)
496
+ } catch (e) {
497
+ console.error(`MU ${_tags.Type} error:`, e.message, e.stack)
498
+ err = e.message
499
+ }
418
500
  } else err = true
419
501
 
420
502
  if (err) return { status: 400, error: err }
@@ -435,8 +517,8 @@ class Adaptor {
435
517
  json: {
436
518
  version: 1,
437
519
  timestamp: Date.now(),
438
- height: this.mem.height,
439
- network: "wao.LN.1",
520
+ height: await this.mem.get("height"),
521
+ network: this.network,
440
522
  current: this.mem.getAnchor(),
441
523
  },
442
524
  }
@@ -459,7 +541,20 @@ class Adaptor {
459
541
  async ar_get_id({ query, params, body, headers, method }) {
460
542
  const _data = await this._ar.data(params.id)
461
543
  if (!_data) return { status: 404, send: null }
462
- else return { send: Buffer.from(_data, "base64") }
544
+ const tx = await this.mem.getTx(params.id)
545
+ let contentType = tx?._data?.type || ""
546
+ if (!contentType && tx?.tags) {
547
+ const ctTag = tx.tags.find(t => t.name === "Content-Type")
548
+ if (ctTag) contentType = ctTag.value
549
+ }
550
+ if (!contentType) contentType = "application/octet-stream"
551
+ const send = Buffer.isBuffer(_data) || _data instanceof Uint8Array
552
+ ? _data
553
+ : Buffer.from(String(_data), "base64")
554
+ return {
555
+ send,
556
+ headers: { "Content-Type": contentType },
557
+ }
463
558
  }
464
559
  async ar_get_price({ query, params, body, headers, method }) {
465
560
  return { send: "0" }
@@ -468,16 +563,16 @@ class Adaptor {
468
563
  try {
469
564
  const { query, variables } = body
470
565
  const { tar, args } = toGraphObj({ query, variables })
566
+ const first = args.first ?? 10
471
567
  let res2 = null
472
- if (tar === "transactions") {
473
- res2 = await this.gql.txs({ ...args })
474
- } else if (tar === "blocks") {
475
- res2 = await this.gql.blocks({ ...args })
476
- }
568
+ const fn = tar === "blocks" ? "blocks" : "txs"
569
+ res2 = await this.gql[fn]({ ...args, first: first + 1 })
570
+ const hasNextPage = res2.length > first
571
+ if (hasNextPage) res2 = res2.slice(0, first)
477
572
  const edges = map(v => ({ node: v, cursor: v.cursor }), res2)
478
573
  return {
479
574
  json: {
480
- data: { [tar]: { pageInfo: { hasNextPage: true }, edges } },
575
+ data: { [tar]: { pageInfo: { hasNextPage }, edges } },
481
576
  },
482
577
  }
483
578
  } catch (e) {
@@ -489,6 +584,7 @@ class Adaptor {
489
584
  // id = "tx" | "chunk"
490
585
  if (body.chunk) {
491
586
  if (this.data[body.data_root]) {
587
+ this._dataTimestamps[body.data_root] = Date.now()
492
588
  this.data[body.data_root].data += body.chunk
493
589
  const buf = Buffer.from(body.chunk, "base64")
494
590
  if (!this.data[body.data_root].chunks) {
@@ -508,12 +604,14 @@ class Adaptor {
508
604
  this.data[body.data_root].chunks.toString("base64")
509
605
  await this._ar.postTx(this.data[body.data_root])
510
606
  delete this.data[body.data_root]
607
+ delete this._dataTimestamps[body.data_root]
511
608
  }
512
609
  }
513
610
  return { json: { id: body.id } }
514
611
  } else {
515
612
  if (body.data_root && body.data === "") {
516
613
  this.data[body.data_root] = body
614
+ this._dataTimestamps[body.data_root] = Date.now()
517
615
  } else {
518
616
  await this._ar.postTx(body)
519
617
  }
@@ -0,0 +1,11 @@
1
+ import { connect } from "./aoconnect-cf.js"
2
+ import { cu, su, mu } from "./accounts-web.js"
3
+ import GQL from "./tgql.js"
4
+ import Base from "./adaptor-base.js"
5
+
6
+ class Adaptor extends Base {
7
+ constructor(obj) {
8
+ super({ ...obj, GQL, cu, su, mu, connect })
9
+ }
10
+ }
11
+ export default Adaptor
package/esm/ao-loader.js CHANGED
@@ -24000,7 +24000,7 @@ var require_wasm64_emscripten = __commonJS({
24000
24000
  _emscripten_get_now = () => deterministicNow()
24001
24001
  var growMemory = size => {
24002
24002
  var b = wasmMemory.buffer
24003
- var pages = (size - b.byteLength + 65535) / 65536
24003
+ var pages = BigInt(Math.ceil((size - b.byteLength) / 65536))
24004
24004
  try {
24005
24005
  wasmMemory.grow(pages)
24006
24006
  updateMemoryViews()
package/esm/ao.js CHANGED
@@ -99,7 +99,15 @@ class AO {
99
99
  if (_hb) {
100
100
  this.format = _hb === "ans104" ? _hb : "httpsig"
101
101
  const _hbOpt = { format: this.format }
102
- if (typeof _hb === "string" && _hb !== "ans104") _hbOpt.url = _hb
102
+ // `hb` can be "ans104" / "httpsig" (format selector with default URL),
103
+ // or an actual URL string. Only treat it as a URL when it looks like one.
104
+ if (
105
+ typeof _hb === "string" &&
106
+ _hb !== "ans104" &&
107
+ _hb !== "httpsig" &&
108
+ /^(https?:\/\/|\/\/|[a-zA-Z0-9_.-]+:\d+)/.test(_hb)
109
+ )
110
+ _hbOpt.url = _hb
103
111
  this.hb = new HB(_hbOpt)
104
112
  this.mode = mode ?? "legacy"
105
113
  }
@@ -679,8 +687,19 @@ class AO {
679
687
  mid = (await this.hb.scheduleLegacy(schedArgs)).slot
680
688
  try {
681
689
  res = await this.hb.computeLegacy({ pid, slot: mid })
690
+ // v0.9-FINAL's push device can enter long-running recursive loops
691
+ // for cross-process Send().receive() coroutines. Race the push
692
+ // call against a 20s timeout so msg() can return rather than
693
+ // wait forever; the now/results probe below still has a chance
694
+ // to pick up a partial reply.
695
+ const _pushTimeoutMs = 20000
682
696
  try {
683
- await this.hb.get({ path: `/${pid}/push`, slot: mid })
697
+ await Promise.race([
698
+ this.hb.get({ path: `/${pid}/push`, slot: mid }),
699
+ new Promise((_r, rej) =>
700
+ setTimeout(() => rej(new Error("push timeout")), _pushTimeoutMs)
701
+ ),
702
+ ])
684
703
  } catch (_e2) {}
685
704
  // Push updates now/ to its recursive resolution result, which may
686
705
  // include errors from other processes (cross-process trust errors).