@dopamint-fun/open-sdk 0.2.0-dev.1 → 0.2.0-dev.10

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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # `libs/dopa-open-client-ts`
1
+ # `libs/dopa-open/client-ts`
2
2
 
3
3
  Status: implemented
4
4
  Ownership: Optional official TypeScript SDK for DOPA-OPEN self-custody agents:
@@ -36,8 +36,8 @@ signatures, not whether this package is used. Allowed dependencies:
36
36
  ## Parity
37
37
 
38
38
  Preimages and signatures are pinned byte-identical to the Rust signer by
39
- `libs/dopa-open-client-rs/vectors/ts-signer-parity.json`, generated by
40
- `libs/dopa-open-client-rs/tests/ts_parity_vectors.rs` and replayed by
39
+ `libs/dopa-open/client-rs/vectors/ts-signer-parity.json`, generated by
40
+ `libs/dopa-open/client-rs/tests/ts_parity_vectors.rs` and replayed by
41
41
  `src/parity.test.ts` here. Regenerate with
42
42
  `WRITE_TS_PARITY_VECTORS=1 cargo test -p dopa-open-client-rs --test
43
43
  ts_parity_vectors`; a wire change updates both sides together.
package/dist/agentHttp.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * tour or table request without ever holding a bearer credential.
3
3
  *
4
4
  * Byte-identical to `dopa_open_api::agent_http`, pinned by
5
- * `libs/dopa-open-api/vectors/agent_http_capability_v1.json`. A bearer token
5
+ * `libs/dopa-open/api/vectors/agent_http_capability_v1.json`. A bearer token
6
6
  * is a secret presented per request; a signature is not, so each capability is
7
7
  * bound to the method, the request target, a digest of the exact body bytes,
8
8
  * and a single-use nonce inside a bounded window. One minted to enter the
@@ -13,7 +13,7 @@
13
13
  * server reconstructs them from the request it actually received, so there is
14
14
  * no restated copy to compare and therefore no comparison to forget.
15
15
  */
16
- import { ByteWriter, frameSigningBytes, fromHex, textBytes, toHex } from "./bytes.js";
16
+ import { ByteReader, ByteWriter, frameSigningBytes, fromHex, textBytes, toHex, } from "./bytes.js";
17
17
  import { blake2b256 } from "./crypto.js";
18
18
  import { signRaw } from "./keypair.js";
19
19
  const AGENT_HTTP_CAPABILITY_DOMAIN = textBytes("dopa_open::agent_http_capability::v1");
@@ -94,20 +94,17 @@ export function decodeAgentHttpHeader(value) {
94
94
  const bytes = fromHex(value);
95
95
  if (bytes.length !== HEADER_BYTES)
96
96
  throw new Error(`capability header must be ${HEADER_BYTES} bytes, got ${bytes.length}`);
97
- const readU64 = (offset) => {
98
- let out = 0n;
99
- for (let index = 0; index < 8; index++)
100
- out = (out << 8n) | BigInt(bytes[offset + index]);
101
- return out;
102
- };
103
- return {
104
- agentId: bytes.slice(0, 32),
105
- agentPublicKey: bytes.slice(32, 64),
106
- issuedAtMs: readU64(64),
107
- expiresAtMs: readU64(72),
108
- nonce: bytes.slice(80, 80 + AGENT_HTTP_CAPABILITY_NONCE_BYTES),
109
- signature: bytes.slice(80 + AGENT_HTTP_CAPABILITY_NONCE_BYTES),
97
+ const reader = new ByteReader(bytes);
98
+ const capability = {
99
+ agentId: reader.readFixed(32, "agent id"),
100
+ agentPublicKey: reader.readFixed(32, "agent public key"),
101
+ issuedAtMs: reader.readU64("issued_at_ms"),
102
+ expiresAtMs: reader.readU64("expires_at_ms"),
103
+ nonce: reader.readFixed(AGENT_HTTP_CAPABILITY_NONCE_BYTES, "nonce"),
104
+ signature: reader.readFixed(SIGNATURE_BYTES, "signature"),
110
105
  };
106
+ reader.finish();
107
+ return capability;
111
108
  }
112
109
  /** Mint and sign one capability, and return the header to send with it.
113
110
  *
package/dist/bytes.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * These helpers exist so every framing in this package spells a number the
6
6
  * same way; a preimage that drifts from the Rust client by one byte produces
7
7
  * signatures the authority silently rejects, which is why the parity vectors
8
- * in `libs/dopa-open-client-rs/vectors/` pin all of it.
8
+ * in `libs/dopa-open/client-rs/vectors/` pin all of it.
9
9
  */
10
10
  export function toHex(bytes) {
11
11
  let out = "";
package/dist/claim.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { signRaw } from "./keypair.js";
3
- import { concatBytes, equalBytes, frameSigningBytes, fromHex, textBytes, toHex0x, } from "./bytes.js";
3
+ import { ByteReader, ByteWriter, equalBytes, frameSigningBytes, fromHex, textBytes, toHex0x, } from "./bytes.js";
4
4
  /* The agent's half of a claim: an invitation its own key signs.
5
5
  *
6
6
  * A wallet claims an agent by signing for it in the arena's UI. That proves
@@ -22,16 +22,6 @@ export const AGENT_CLAIM_INVITE_MAX_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
22
22
  * nonce || signature`, every field fixed-width. */
23
23
  export const AGENT_CLAIM_INVITE_TOKEN_BYTES = 32 + 32 + 32 + 8 + 8 + 16 + 64;
24
24
  const ZERO_OWNER = new Uint8Array(32);
25
- function u64be(value) {
26
- const out = new Uint8Array(8);
27
- new DataView(out.buffer).setBigUint64(0, value, false);
28
- return out;
29
- }
30
- function fixed(bytes, length, what) {
31
- if (bytes.length !== length)
32
- throw new Error(`${what} must be ${length} bytes, got ${bytes.length}`);
33
- return bytes;
34
- }
35
25
  /** `AgentClaimInvite::canonical_payload` -- the framed fields. */
36
26
  export function claimInviteCanonicalPayload(invite) {
37
27
  if (invite.expiresAtMs <= invite.issuedAtMs)
@@ -41,7 +31,18 @@ export function claimInviteCanonicalPayload(invite) {
41
31
  throw new Error("an invitation may stay open for at most a week");
42
32
  if (invite.nonce.every((byte) => byte === 0))
43
33
  throw new Error("the invitation's nonce must not be all zero");
44
- return concatBytes(AGENT_CLAIM_INVITE_DOMAIN, Uint8Array.of(0, CANONICAL_WIRE_VERSION, INVITE_OPERATION), fixed(invite.agentId, 32, "agent id"), invite.owner ? fixed(invite.owner, 32, "owner") : ZERO_OWNER, fixed(invite.agentPublicKey, 32, "agent public key"), u64be(invite.issuedAtMs), u64be(invite.expiresAtMs), fixed(invite.nonce, AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce"));
34
+ return new ByteWriter()
35
+ .pushBytes(AGENT_CLAIM_INVITE_DOMAIN)
36
+ .pushByte(0)
37
+ .pushByte(CANONICAL_WIRE_VERSION)
38
+ .pushByte(INVITE_OPERATION)
39
+ .pushFixed(invite.agentId, 32, "agent id")
40
+ .pushFixed(invite.owner ?? ZERO_OWNER, 32, "owner")
41
+ .pushFixed(invite.agentPublicKey, 32, "agent public key")
42
+ .pushU64(invite.issuedAtMs)
43
+ .pushU64(invite.expiresAtMs)
44
+ .pushFixed(invite.nonce, AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce")
45
+ .bytes();
45
46
  }
46
47
  /** The bytes the agent's key signs, raw ed25519. */
47
48
  export function claimInviteSigningBytes(invite) {
@@ -66,7 +67,15 @@ export async function mintClaimInvite(agent, agentId, options = {}) {
66
67
  function claimInviteBytes(invite) {
67
68
  if (invite.signature.length !== 64)
68
69
  throw new Error("an invitation is encoded only once it is signed");
69
- return concatBytes(fixed(invite.agentId, 32, "agent id"), invite.owner ?? ZERO_OWNER, fixed(invite.agentPublicKey, 32, "agent public key"), u64be(invite.issuedAtMs), u64be(invite.expiresAtMs), fixed(invite.nonce, 16, "nonce"), invite.signature);
70
+ return new ByteWriter()
71
+ .pushFixed(invite.agentId, 32, "agent id")
72
+ .pushFixed(invite.owner ?? ZERO_OWNER, 32, "owner")
73
+ .pushFixed(invite.agentPublicKey, 32, "agent public key")
74
+ .pushU64(invite.issuedAtMs)
75
+ .pushU64(invite.expiresAtMs)
76
+ .pushFixed(invite.nonce, AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce")
77
+ .pushBytes(invite.signature)
78
+ .bytes();
70
79
  }
71
80
  /** The token the link carries and the claim body posts back: 0x-hex.
72
81
  *
@@ -115,20 +124,27 @@ export function decodeClaimInvite(token) {
115
124
  const bytes = claimInviteTokenBytes(token.trim());
116
125
  if (bytes.length !== AGENT_CLAIM_INVITE_TOKEN_BYTES)
117
126
  throw new Error(`an invitation token is ${AGENT_CLAIM_INVITE_TOKEN_BYTES} bytes, got ${bytes.length}`);
118
- const view = new DataView(bytes.buffer, bytes.byteOffset);
119
- const owner = bytes.slice(32, 64);
127
+ const reader = new ByteReader(bytes);
128
+ const agentId = reader.readFixed(32, "agent id");
129
+ const owner = reader.readFixed(32, "owner");
130
+ const agentPublicKey = reader.readFixed(32, "agent public key");
131
+ const issuedAtMs = reader.readU64("issued");
132
+ const expiresAtMs = reader.readU64("expires");
133
+ const nonce = reader.readFixed(AGENT_CLAIM_INVITE_NONCE_BYTES, "nonce");
134
+ const signature = reader.readFixed(64, "signature");
135
+ reader.finish();
120
136
  return {
121
- agentId: bytes.slice(0, 32),
137
+ agentId,
122
138
  owner: equalBytes(owner, ZERO_OWNER) ? null : owner,
123
- agentPublicKey: bytes.slice(64, 96),
124
- issuedAtMs: view.getBigUint64(96, false),
125
- expiresAtMs: view.getBigUint64(104, false),
126
- nonce: bytes.slice(112, 128),
127
- signature: bytes.slice(128),
139
+ agentPublicKey,
140
+ issuedAtMs,
141
+ expiresAtMs,
142
+ nonce,
143
+ signature,
128
144
  };
129
145
  }
130
146
  /** Where the owner goes to accept: the agent's claim page with the token. */
131
147
  export function claimInviteLink(arenaOrigin, agentIdHex, token) {
132
148
  const id = agentIdHex.startsWith("0x") ? agentIdHex : `0x${agentIdHex}`;
133
- return `${arenaOrigin.replace(/\/$/, "")}/arena/agents/${id}/claim?invite=${token}`;
149
+ return `${arenaOrigin.replace(/\/$/, "")}/open/agents/${id}/claim?invite=${token}`;
134
150
  }
package/dist/cli.js CHANGED
@@ -23,14 +23,20 @@ import { claimInviteLink, encodeClaimInvite, encodeClaimInviteCompact, mintClaim
23
23
  import { acceptAndAwaitAdmission } from "./offer.js";
24
24
  import { playTour, queueUntilSeated } from "./tour.js";
25
25
  import { joinRoomWhenComposed, MIN_ROOM_SEATS, openRoom, roomInvitePrompt, } from "./room.js";
26
- import { disputeHolding, JoinRefused, joinTransaction, leaveTransaction, listTournaments, matchmakingOverLine, planJoin, presentToTournament, readAgentEntry, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, } from "./openTournament.js";
27
- import { authorityOriginFromSessionBase, buildConsentRequest, digestForPrompt, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
26
+ import { disputeHolding, JoinRefused, joinTransaction, leaveTransaction, listTournaments, matchmakingOverLine, planJoin, presentToTournament, readAgentEntry, readAgentEntrySettled, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, } from "./openTournament.js";
27
+ import { buildConsentRequest, DEFAULT_OPEN_API_BASE_URL, digestForPrompt, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
28
28
  import { actionSigningBytes, joinSigningBytes, requireSessionVersion, resumeSigningBytes, SESSION_VERSION, } from "./sessionWire.js";
29
29
  import { describeNext, refusalMessageFromText } from "./refusal.js";
30
30
  function fail(message) {
31
31
  console.error(`dopa-open: ${message}`);
32
32
  process.exit(1);
33
33
  }
34
+ /** Resolves `--product-url`, falling back to `DOPA_OPEN_PRODUCT_URL` and then
35
+ * to the deployed default — the same precedence and env name the Rust CLI
36
+ * uses, so a script that sets one variable configures both binaries. */
37
+ function resolveProductUrl(explicit) {
38
+ return explicit ?? process.env.DOPA_OPEN_PRODUCT_URL ?? DEFAULT_OPEN_API_BASE_URL;
39
+ }
34
40
  const hex = (value, field) => {
35
41
  if (typeof value !== "string")
36
42
  fail(`${field} must be a hex string`);
@@ -116,9 +122,7 @@ async function commandRegister(args) {
116
122
  "dry-run": { type: "boolean", default: false },
117
123
  },
118
124
  });
119
- const productUrl = values["product-url"];
120
- if (!productUrl && !values["dry-run"])
121
- fail("--product-url is required (or pass --dry-run to print the request)");
125
+ const productUrl = resolveProductUrl(values["product-url"]);
122
126
  const agent = loadKeypair(values.key);
123
127
  const versions = (raw, flag) => {
124
128
  const list = raw.split(",").map((piece) => Number.parseInt(piece, 10));
@@ -162,7 +166,7 @@ async function commandRegister(args) {
162
166
  console.log(JSON.stringify(request, null, 2));
163
167
  return;
164
168
  }
165
- const response = await fetch(`${productUrl.replace(/\/$/, "")}/open/v1/agents`, {
169
+ const response = await fetch(`${productUrl.replace(/\/$/, "")}/v1/agents`, {
166
170
  method: "POST",
167
171
  headers: { "content-type": "application/json" },
168
172
  body: JSON.stringify(request),
@@ -225,9 +229,7 @@ async function commandName(args) {
225
229
  bio: { type: "string" },
226
230
  },
227
231
  });
228
- const productUrl = values["product-url"];
229
- if (!productUrl)
230
- fail("--product-url is required");
232
+ const productUrl = resolveProductUrl(values["product-url"]);
231
233
  if (!values["agent-id"])
232
234
  fail("--agent-id is required");
233
235
  if (!values.name && !values.handle && !values.bio)
@@ -258,7 +260,7 @@ async function commandName(args) {
258
260
  * against what they believed, and the refusal for getting it wrong is a bare
259
261
  * `stale_generation` that names neither. */
260
262
  async function readKeyState(productUrl, agentId) {
261
- const response = await fetch(`${productUrl}/open/v1/agents/${encodeURIComponent(agentId)}`);
263
+ const response = await fetch(`${productUrl}/v1/agents/${encodeURIComponent(agentId)}`);
262
264
  if (!response.ok)
263
265
  fail(`could not read agent ${agentId} (${response.status})`);
264
266
  const wire = (await response.json());
@@ -291,9 +293,7 @@ async function commandKey(args) {
291
293
  "dry-run": { type: "boolean", default: false },
292
294
  },
293
295
  });
294
- const productUrl = values["product-url"]?.replace(/\/$/, "");
295
- if (!productUrl)
296
- fail("--product-url is required");
296
+ const productUrl = resolveProductUrl(values["product-url"]).replace(/\/$/, "");
297
297
  const agentId = values["agent-id"];
298
298
  if (!agentId)
299
299
  fail("--agent-id is required");
@@ -356,7 +356,7 @@ async function commandKey(args) {
356
356
  return;
357
357
  }
358
358
  const path = retire ? "revocations" : "key-rotations";
359
- const response = await fetch(`${productUrl}/open/v1/agents/${encodeURIComponent(agentId)}/${path}`, {
359
+ const response = await fetch(`${productUrl}/v1/agents/${encodeURIComponent(agentId)}/${path}`, {
360
360
  method: "POST",
361
361
  headers: { "content-type": "application/json" },
362
362
  body: JSON.stringify(request),
@@ -486,9 +486,7 @@ async function commandPlay(args) {
486
486
  "disconnect-after-actions": { type: "string" },
487
487
  },
488
488
  });
489
- const productUrl = values["product-url"];
490
- if (!productUrl)
491
- fail("--product-url is required");
489
+ const productUrl = resolveProductUrl(values["product-url"]);
492
490
  if (!values["agent-id"])
493
491
  fail("--agent-id is required");
494
492
  /* A tour seat is played over the Product API, so it needs none of the
@@ -581,7 +579,7 @@ function runRecordPath(keyFile) {
581
579
  }
582
580
  function writeRunRecord(keyFile, record) {
583
581
  const path = runRecordPath(keyFile);
584
- const watchUrl = `${record.productUrl.replace(/\/$/, "")}/arena/matches/0x${record.executionId.replace(/^0x/i, "")}`;
582
+ const watchUrl = `${record.productUrl.replace(/\/$/, "")}/open/matches/0x${record.executionId.replace(/^0x/i, "")}`;
585
583
  writeFileSync(path, `${JSON.stringify({ ...record, watchUrl, at: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
586
584
  return path;
587
585
  }
@@ -642,7 +640,7 @@ async function signedGet(productUrl, target, agent, agentIdHex) {
642
640
  headers: { [AGENT_HTTP_CAPABILITY_HEADER]: header },
643
641
  });
644
642
  }
645
- /** `dopa-open me`: the arena's record of this agent, signed for.
643
+ /** `dopa-open me`: Open's record of this agent, signed for.
646
644
  *
647
645
  * Both agents that played on 2026-09-06 wrote this request by hand, against
648
646
  * the skill's own warning that a hand-written signature drifts by a byte. */
@@ -657,13 +655,13 @@ async function signedGet(productUrl, target, agent, agentIdHex) {
657
655
  async function resolveAgentId(productUrl, ownerAddressHex, given) {
658
656
  if (given)
659
657
  return given;
660
- const response = await fetch(`${productUrl.replace(/\/$/, "")}/open/v1/agents?owner=${ownerAddressHex}&limit=60`);
658
+ const response = await fetch(`${productUrl.replace(/\/$/, "")}/v1/agents?owner=${ownerAddressHex}&limit=60`);
661
659
  if (!response.ok)
662
660
  fail(`--agent-id was not given and the roster could not be read (${response.status})`);
663
661
  const body = (await response.json());
664
662
  const agents = body.agents ?? [];
665
663
  if (agents.length === 0)
666
- fail(`no agent on this arena is claimed by ${ownerAddressHex}. The roster lists an agent under the wallet that ` +
664
+ fail(`no agent on this Open deployment is claimed by ${ownerAddressHex}. The roster lists an agent under the wallet that ` +
667
665
  "claimed it, so one that registered itself is not here until it is claimed; pass --agent-id (register printed it)");
668
666
  if (agents.length > 1)
669
667
  fail(`this key owns ${agents.length} agents; pass --agent-id to say which:\n ${agents
@@ -680,16 +678,14 @@ async function commandMe(args) {
680
678
  "agent-id": { type: "string" },
681
679
  },
682
680
  });
683
- const productUrl = values["product-url"];
684
- if (!productUrl)
685
- fail("--product-url is required");
681
+ const productUrl = resolveProductUrl(values["product-url"]);
686
682
  const agent = loadKeypair(values.key);
687
683
  /* Without `--agent-id`, ask the roster which agent this key owns. */
688
684
  const agentId = await resolveAgentId(productUrl, agent.ownerAddressHex, values["agent-id"]);
689
- const response = await signedGet(productUrl, "/open/v1/agent/me", agent, agentId);
685
+ const response = await signedGet(productUrl, "/v1/agent/me", agent, agentId);
690
686
  const body = await response.text();
691
687
  if (!response.ok)
692
- fail(refusalMessageFromText(`agent/me at ${productUrl}/open/v1/agent/me`, response.status, body));
688
+ fail(refusalMessageFromText(`agent/me at ${productUrl}/v1/agent/me`, response.status, body));
693
689
  try {
694
690
  console.log(JSON.stringify(JSON.parse(body), null, 2));
695
691
  }
@@ -857,14 +853,19 @@ async function commandAct(args) {
857
853
  * this, so it is worth a caller of its own rather than a flag on the play
858
854
  * loop. Prints what the authority recorded. */
859
855
  async function consentToTerminal(options) {
860
- const offerResponse = await fetch(`${options.productUrl.replace(/\/$/, "")}/open/v1/playground/matches/${options.offerId}`);
856
+ const offerResponse = await fetch(`${options.productUrl.replace(/\/$/, "")}/v1/playground/matches/${options.offerId}`);
861
857
  const offerBody = await offerResponse.text();
862
858
  if (!offerResponse.ok)
863
859
  fail(`offer read failed (${offerResponse.status}): ${offerBody}`);
864
860
  const record = JSON.parse(offerBody);
865
861
  if (!record.admission)
866
862
  fail("offer is not admitted; there is no execution to settle");
867
- const origin = authorityOriginFromSessionBase(record.admission.session_base_url);
863
+ /* The settlement verbs live on the product origin `/v1/authority/…` is
864
+ the product's public route, which forwards to the shard that admitted
865
+ the execution. The session's own origin serves `/v1/exec/` only, so
866
+ deriving the consent root from `session_base_url` asks a listener that
867
+ does not serve this spelling. */
868
+ const origin = options.productUrl.replace(/\/$/, "");
868
869
  const path = settlementConsentPath(record.admission.execution_id);
869
870
  const promptResponse = await fetch(`${origin}${path}`);
870
871
  const promptText = await promptResponse.text();
@@ -1007,9 +1008,7 @@ async function commandConsent(args) {
1007
1008
  "agent-id": { type: "string" },
1008
1009
  },
1009
1010
  });
1010
- const productUrl = values["product-url"];
1011
- if (!productUrl)
1012
- fail("--product-url is required");
1011
+ const productUrl = resolveProductUrl(values["product-url"]);
1013
1012
  if (!values.offer)
1014
1013
  fail("--offer is required");
1015
1014
  if (!values.seat)
@@ -1045,9 +1044,7 @@ async function commandQueue(args) {
1045
1044
  play: { type: "boolean", default: false },
1046
1045
  },
1047
1046
  });
1048
- const productUrl = values["product-url"];
1049
- if (!productUrl)
1050
- fail("--product-url is required");
1047
+ const productUrl = resolveProductUrl(values["product-url"]);
1051
1048
  if (!values["agent-id"])
1052
1049
  fail("--agent-id is required");
1053
1050
  const tour = values.tour;
@@ -1091,7 +1088,7 @@ async function commandQueue(args) {
1091
1088
  Labelled `agent_page`, never `watch`. It was `watch` for one release, and
1092
1089
  an agent reading that line handed its operator a profile under the word
1093
1090
  WATCH; `watch` is the table, printed below once there is one. */
1094
- console.log(`agent_page ${client.productUrl.replace(/\/$/, "")}/arena/agents/0x${values["agent-id"].replace(/^0x/i, "")}`);
1091
+ console.log(`agent_page ${client.productUrl.replace(/\/$/, "")}/open/agents/0x${values["agent-id"].replace(/^0x/i, "")}`);
1095
1092
  let lastWaitingLine = "";
1096
1093
  const seated = await queueUntilSeated(client, tour, {
1097
1094
  minAgents,
@@ -1146,7 +1143,7 @@ async function commandQueue(args) {
1146
1143
  executionId: admitted.executionId,
1147
1144
  };
1148
1145
  const recordPath = writeRunRecord(values.key, runRecord);
1149
- console.log(`watch ${client.productUrl.replace(/\/$/, "")}/arena/matches/0x${admitted.executionId.replace(/^0x/i, "")}`);
1146
+ console.log(`watch ${client.productUrl.replace(/\/$/, "")}/open/matches/0x${admitted.executionId.replace(/^0x/i, "")}`);
1150
1147
  console.log(`run_record ${recordPath}`);
1151
1148
  console.log(`reconnect ${reconnectCommand(values.key, runRecord)}`);
1152
1149
  takeSeatLock(values.key, values["agent-id"]);
@@ -1238,9 +1235,7 @@ async function commandRoom(args) {
1238
1235
  "timeout-ms": { type: "string", default: String(30 * 60_000) },
1239
1236
  },
1240
1237
  });
1241
- const productUrl = values["product-url"];
1242
- if (!productUrl)
1243
- fail("--product-url is required");
1238
+ const productUrl = resolveProductUrl(values["product-url"]);
1244
1239
  if (!values["agent-id"])
1245
1240
  fail("--agent-id is required");
1246
1241
  const client = {
@@ -1279,7 +1274,7 @@ async function commandRoom(args) {
1279
1274
  that word is the match link on every door, and `room join` prints it
1280
1275
  once the room composes, so two different links under one label was a
1281
1276
  thing an agent had to be warned about. */
1282
- console.log(`room_page ${client.productUrl.replace(/\/$/, "")}/arena/tours/private-room/tables/${room.tableId}`);
1277
+ console.log(`room_page ${client.productUrl.replace(/\/$/, "")}/open/tours/private-room/tables/${room.tableId}`);
1283
1278
  return;
1284
1279
  }
1285
1280
  const tableId = values["table-id"];
@@ -1328,7 +1323,7 @@ async function commandRoom(args) {
1328
1323
  executionId: admitted.executionId,
1329
1324
  };
1330
1325
  const recordPath = writeRunRecord(values.key, runRecord);
1331
- console.log(`watch ${client.productUrl.replace(/\/$/, "")}/arena/matches/0x${admitted.executionId.replace(/^0x/i, "")}`);
1326
+ console.log(`watch ${client.productUrl.replace(/\/$/, "")}/open/matches/0x${admitted.executionId.replace(/^0x/i, "")}`);
1332
1327
  console.log(`run_record ${recordPath}`);
1333
1328
  console.log(`reconnect ${reconnectCommand(values.key, runRecord)}`);
1334
1329
  takeSeatLock(values.key, values["agent-id"]);
@@ -1395,9 +1390,7 @@ async function commandTournament(args) {
1395
1390
  "until-out": { type: "boolean", default: false },
1396
1391
  },
1397
1392
  });
1398
- const productUrl = values["product-url"];
1399
- if (!productUrl)
1400
- fail("--product-url is required");
1393
+ const productUrl = resolveProductUrl(values["product-url"]);
1401
1394
  const agent = loadKeypair(values.key);
1402
1395
  const tournamentId = await tournamentArg(productUrl, values.tournament);
1403
1396
  const overview = await readTournament(productUrl, tournamentId);
@@ -1408,7 +1401,7 @@ async function commandTournament(args) {
1408
1401
  this agent holds now is on the owner's side of the book. */
1409
1402
  const playTakenBackNext = () => describeNext({
1410
1403
  action: "read",
1411
- route: `/open/v1/tournaments/${tournamentId}/owners/${owner ?? "{owner}"}`,
1404
+ route: `/v1/tournaments/${tournamentId}/owners/${owner ?? "{owner}"}`,
1412
1405
  });
1413
1406
  const held = async () => owner ? await playsHeldBy(productUrl, tournamentId, owner, chip) : [];
1414
1407
  let plays = await held();
@@ -1471,8 +1464,16 @@ async function commandTournament(args) {
1471
1464
  return;
1472
1465
  }
1473
1466
  if (verb === "leave") {
1474
- if (entry?.state !== "queued")
1475
- fail(`this agent is ${entry?.state ?? "not in the book"}; only a queued agent can leave, and a seated one plays its table out`);
1467
+ /* An agent that queued a moment ago is on chain and not yet in the book,
1468
+ which is read a checkpoint behind it. Refusing on the first read told an
1469
+ agent that had just been printed `queued waiting` that it was not in the
1470
+ book at all, and the refusal's own `next` sent it to join again — which
1471
+ would queue an agent that is already queued. So an absent entry is
1472
+ waited out, briefly, before it is believed. */
1473
+ const settled = entry ??
1474
+ (await readAgentEntrySettled(productUrl, tournamentId, chip));
1475
+ if (settled?.state !== "queued")
1476
+ fail(`this agent is ${settled?.state ?? "not in the book"}; only a queued agent can leave, and a seated one plays its table out`);
1476
1477
  const executed = await sponsorAndExecute(client, leaveTransaction(overview, chip));
1477
1478
  console.log(`left ${executed.digest} ${executed.status}`);
1478
1479
  if (executed.status !== "success")
@@ -1634,7 +1635,7 @@ async function commandTournament(args) {
1634
1635
  console.log(`offer ${offerId}`);
1635
1636
  console.log(`seat ${seated.seat}`);
1636
1637
  if (seated.executionId)
1637
- console.log(`watch ${client.productUrl.replace(/\/$/, "")}/arena/matches/${seated.executionId}`);
1638
+ console.log(`watch ${client.productUrl.replace(/\/$/, "")}/open/matches/${seated.executionId}`);
1638
1639
  if (!values.play)
1639
1640
  return;
1640
1641
  const seat = seated.seat;
@@ -1676,7 +1677,7 @@ async function commandTournament(args) {
1676
1677
  }
1677
1678
  /** `dopa-open claim-invite`: the link a wallet needs to claim this agent.
1678
1679
  *
1679
- * A claim is two consents. The wallet signs on the arena's page; you, holding
1680
+ * A claim is two consents. The wallet signs on Open's claim page; you, holding
1680
1681
  * the agent's key, sign the invitation that lets it. Name the wallet with
1681
1682
  * `--owner` to make the link good for that wallet alone; leave it out and the
1682
1683
  * link is good for whoever opens it, for as long as `--hours` says. */
@@ -1692,9 +1693,7 @@ async function commandClaimInvite(args) {
1692
1693
  hours: { type: "string", default: "24" },
1693
1694
  },
1694
1695
  });
1695
- const productUrl = values["product-url"]?.replace(/\/$/, "");
1696
- if (!productUrl)
1697
- fail("--product-url is required");
1696
+ const productUrl = resolveProductUrl(values["product-url"]).replace(/\/$/, "");
1698
1697
  if (!values["agent-id"])
1699
1698
  fail("--agent-id is required");
1700
1699
  const hours = Number(values.hours);
@@ -1712,7 +1711,7 @@ async function commandClaimInvite(args) {
1712
1711
  expects the old form. Both decode to the same 192 bytes. */
1713
1712
  const token = encodeClaimInvite(invite);
1714
1713
  const linkToken = encodeClaimInviteCompact(invite);
1715
- /* The arena's pages and its API share an origin on a deployment; a local
1714
+ /* Open's pages and its API share an origin on a deployment; a local
1716
1715
  stack serves them apart, which is what --arena-url is for. */
1717
1716
  const arena = (values["arena-url"] ?? productUrl).replace(/\/$/, "");
1718
1717
  console.log(`invite ${token}`);
@@ -1724,7 +1723,7 @@ const USAGE = `usage: dopa-open <command>
1724
1723
 
1725
1724
  keygen generate a keypair into .dopa-keypair (Sui suiprivkey format)
1726
1725
  address print the owner address and public key of an existing key file
1727
- me print the arena's record of this agent, signed for (agent/me)
1726
+ me print Open's record of this agent, signed for (agent/me)
1728
1727
  claim-invite
1729
1728
  mint the claim link a wallet needs to claim this agent
1730
1729
  register self-allocate and register the agent with a product deployment
package/dist/identity.js CHANGED
@@ -7,11 +7,13 @@
7
7
  * and this key can no longer sign for it -- which is the point of a claim.
8
8
  *
9
9
  * The payload mirrors `AgentIdentityEdit::canonical_payload` in
10
- * `backend/dopa-open/product/src/domain/custodial/key_rotation.rs`: every
11
- * field is length-framed, so "ab"/"c" and "a"/"bc" cannot sign the same
12
- * bytes. The product parses before it verifies -- it trims each field and
13
- * lower-cases the handle -- so this signs the parsed form, or the signature
14
- * is over bytes the store never holds. */
10
+ * `backend/dopa-open/product/src/domain/custodial/key_rotation.rs`: the two
11
+ * ids are fixed 32 bytes and every named field is length-framed, so "ab"/"c"
12
+ * and "a"/"bc" cannot sign the same bytes, and neither can a shorter agent id
13
+ * borrow a byte from the owner behind it. The product parses before it
14
+ * verifies -- it trims each field and lower-cases the handle -- so this signs
15
+ * the parsed form, or the signature is over bytes the store never holds. */
16
+ import { ByteWriter, fromHex, textBytes } from "./bytes.js";
15
17
  import { signOwnerAuthenticator } from "./keypair.js";
16
18
  import { refusalMessageFromText } from "./refusal.js";
17
19
  const IDENTITY_DOMAIN = "dopa_open::agent_identity::v1";
@@ -25,40 +27,20 @@ export function parseIdentityFields(fields) {
25
27
  bio: fields.bio?.trim() || null,
26
28
  };
27
29
  }
28
- function u64be(value) {
29
- const out = new Uint8Array(8);
30
- new DataView(out.buffer).setBigUint64(0, BigInt(value));
31
- return out;
32
- }
33
- function fromHex(value) {
34
- const hex = value.replace(/^0x/i, "");
35
- const out = new Uint8Array(hex.length / 2);
36
- for (let index = 0; index < out.length; index++)
37
- out[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
38
- return out;
39
- }
40
30
  /** The exact bytes the owning address signs to name an agent. */
41
31
  export function canonicalIdentityPayload(edit) {
42
- const encoder = new TextEncoder();
43
- const domain = encoder.encode(IDENTITY_DOMAIN);
32
+ const domain = textBytes(IDENTITY_DOMAIN);
44
33
  const parsed = parseIdentityFields(edit.fields);
45
- const parts = [parsed.name ?? "", parsed.handle ?? "", parsed.bio ?? ""].map((field) => encoder.encode(field));
46
- const chunks = [
47
- u64be(domain.length),
48
- domain,
49
- fromHex(edit.agentId),
50
- fromHex(edit.owner),
51
- ...parts.flatMap((field) => [u64be(field.length), field]),
52
- u64be(edit.issuedAtMs),
53
- u64be(edit.expiresAtMs),
54
- ];
55
- const out = new Uint8Array(chunks.reduce((total, chunk) => total + chunk.length, 0));
56
- let offset = 0;
57
- for (const chunk of chunks) {
58
- out.set(chunk, offset);
59
- offset += chunk.length;
34
+ const writer = new ByteWriter()
35
+ .pushU64(domain.length)
36
+ .pushBytes(domain)
37
+ .pushFixed(fromHex(edit.agentId), 32, "agent id")
38
+ .pushFixed(fromHex(edit.owner), 32, "owner");
39
+ for (const field of [parsed.name ?? "", parsed.handle ?? "", parsed.bio ?? ""]) {
40
+ const bytes = textBytes(field);
41
+ writer.pushU64(bytes.length).pushBytes(bytes);
60
42
  }
61
- return out;
43
+ return writer.pushU64(edit.issuedAtMs).pushU64(edit.expiresAtMs).bytes();
62
44
  }
63
45
  /** Name an agent, as the address that owns it. Answers what the arena stored. */
64
46
  export async function nameAgent(args) {
@@ -73,8 +55,8 @@ export async function nameAgent(args) {
73
55
  issuedAtMs,
74
56
  expiresAtMs,
75
57
  }));
76
- const agentId = `0x${args.agentId.replace(/^0x/i, "")}`;
77
- const response = await fetchImpl(`${args.productUrl.replace(/\/$/, "")}/open/v1/agents/${agentId}/identity`, {
58
+ const agentId = `0x${args.agentId.replace(/^0x/, "")}`;
59
+ const response = await fetchImpl(`${args.productUrl.replace(/\/$/, "")}/v1/agents/${agentId}/identity`, {
78
60
  method: "PUT",
79
61
  headers: { "content-type": "application/json" },
80
62
  body: JSON.stringify({
package/dist/index.d.ts CHANGED
@@ -3,9 +3,9 @@ export { deriveAgentId, registerCanonicalPayload, registerPayloadDigest, type Re
3
3
  export { canonicalIdentityPayload, nameAgent, parseIdentityFields, type AgentIdentityFields, type NameAgentArgs, } from "./identity.js";
4
4
  export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, SESSION_VERSION, UnsupportedSessionVersionError, type ActionProposal, type ArtifactReference, type JoinRequest, type ResumeRequest, type SessionContext, } from "./sessionWire.js";
5
5
  export { encodeAckFrame, SESSION_ERROR_NAMES, MAX_PREDICTION_PACING_MS, admitAuthorityEvent, applyPredictionGateStatus, decodeAuthorityMessage, freshSessionBoundary, predictionGateMessage, predictionGatePreparationOf, predictionGateTargetReceipt, sessionErrorHint, sessionErrorName, PredictionGateConflictError, SessionBoundaryError, type AcceptedSessionBoundary, type AuthorityMessage, type PredictionGateMessage, type PredictionGateOpening, type PredictionGatePreparation, type PredictionGateRelease, type PredictionGateStatus, type PredictionWindowRef, type ReceiptRef, type ResumeCursor, type StateRef, type ViewSnapshot, } from "./sessionCodec.js";
6
- export { type OpenTableView, type PlayReport, type SeatDecision, type SeatDecisionResult, type SeatPosition, SessionClient, SessionRefusal, chooseSeatAction, playSeat, } from "./session.js";
6
+ export { type OpenTableView, type PlayReport, type SeatDecision, type SeatDecisionResult, type SeatPosition, SessionClient, SessionRefusal, chooseSeatAction, playSeat, waitForReadyOwner, } from "./session.js";
7
7
  export { cardFromByte, decodeLegalActions, decodeParticipantView, encodeAction, pickAction, type Card, type Rank, type Suit, type TexasAction, type TexasLegalActions, type TexasSeatView, } from "./texas.js";
8
- export { authorityOriginFromSessionBase, buildConsentRequest, digestForPrompt, recomputeSettlementDigest, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
8
+ export { buildConsentRequest, DEFAULT_OPEN_API_BASE_URL, digestForPrompt, recomputeSettlementDigest, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
9
9
  export { PACKAGE_NAME, RELEASE_CHANNELS, channelDistTag, installCommand, installSpec, resolveChannel, versionMatchesChannel, type ReleaseChannel, } from "./channel.js";
10
10
  export { fromHex, toHex } from "./bytes.js";
11
11
  export { AGENT_HTTP_CAPABILITY_HEADER, AGENT_HTTP_CAPABILITY_NONCE_BYTES, MAX_AGENT_HTTP_CAPABILITY_WINDOW_MS, agentHttpCanonicalPayload, agentHttpSigningBytes, decodeAgentHttpHeader, encodeAgentHttpHeader, mintAgentHttpCapability, type AgentHttpBinding, type AgentHttpCapability, } from "./agentHttp.js";
@@ -17,5 +17,5 @@ export { handEquity, requiredEquity, cardCode, type HandEquity, type HandEquityO
17
17
  export { cardIndex, handCategory, scoreHand, HAND_CATEGORIES, RANK_ORDER, SUIT_ORDER, type BestFive, type HandCategory, } from "./handRank.js";
18
18
  export { DEFAULT_SEAT_STATE_FILE, loadSeatState, saveSeatState, type SeatSessionState, } from "./seatState.js";
19
19
  export { newSeatState, openTurn, submitTurn, type SeatStateCheckpoint, type SeatTurnOutcome, type SeatTurnPosition, } from "./seatTurn.js";
20
- export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, type JoinPlan, type SponsoredExecution, type TournamentAgentEntry, type TournamentPass, type TournamentPlay, type TournamentOwnerView, type TournamentOwnerAgent, type TournamentChainObjects, type TournamentClient, type TournamentOverview, type TournamentQueueEntry, } from "./openTournament.js";
21
- export { ClientRefusal, describeNext, refusalLines, refusalMessage, refusalMessageFromText, type RefusalBody, type RefusalNext, } from "./refusal.js";
20
+ export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readAgentEntrySettled, BOOK_CATCHES_UP_MS, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, type JoinPlan, type SponsoredExecution, type TournamentAgentEntry, type TournamentPass, type TournamentPlay, type TournamentOwnerView, type TournamentOwnerAgent, type TournamentChainObjects, type TournamentClient, type TournamentOverview, type TournamentQueueEntry, } from "./openTournament.js";
21
+ export { ClientRefusal, describeNext, ownershipFencePoll, ownershipFenced, refusalLines, refusalMessage, refusalMessageFromText, type RefusalBody, type RefusalNext, } from "./refusal.js";
package/dist/index.js CHANGED
@@ -3,16 +3,16 @@
3
3
  * Generate and hold an ed25519 keypair (Sui format, `.dopa-keypair`), produce
4
4
  * the canonical signatures, and drive the Participant Session against
5
5
  * `session_base_url`. Byte parity with the Rust client is pinned by
6
- * `libs/dopa-open-client-rs/vectors/ts-signer-parity.json`.
6
+ * `libs/dopa-open/client-rs/vectors/ts-signer-parity.json`.
7
7
  */
8
8
  export { DEFAULT_KEY_FILE, generateKeypair, keypairFromSeed, loadKeypair, saveKeypair, signOwnerAuthenticator, signRaw, } from "./keypair.js";
9
9
  export { deriveAgentId, registerCanonicalPayload, registerPayloadDigest, } from "./registration.js";
10
10
  export { canonicalIdentityPayload, nameAgent, parseIdentityFields, } from "./identity.js";
11
11
  export { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, resumeSigningBytes, SESSION_VERSION, UnsupportedSessionVersionError, } from "./sessionWire.js";
12
12
  export { encodeAckFrame, SESSION_ERROR_NAMES, MAX_PREDICTION_PACING_MS, admitAuthorityEvent, applyPredictionGateStatus, decodeAuthorityMessage, freshSessionBoundary, predictionGateMessage, predictionGatePreparationOf, predictionGateTargetReceipt, sessionErrorHint, sessionErrorName, PredictionGateConflictError, SessionBoundaryError, } from "./sessionCodec.js";
13
- export { SessionClient, SessionRefusal, chooseSeatAction, playSeat, } from "./session.js";
13
+ export { SessionClient, SessionRefusal, chooseSeatAction, playSeat, waitForReadyOwner, } from "./session.js";
14
14
  export { cardFromByte, decodeLegalActions, decodeParticipantView, encodeAction, pickAction, } from "./texas.js";
15
- export { authorityOriginFromSessionBase, buildConsentRequest, digestForPrompt, recomputeSettlementDigest, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
15
+ export { buildConsentRequest, DEFAULT_OPEN_API_BASE_URL, digestForPrompt, recomputeSettlementDigest, settlementConsentPath, verifyConsentDisclosure, } from "./settlement.js";
16
16
  export { PACKAGE_NAME, RELEASE_CHANNELS, channelDistTag, installCommand, installSpec, resolveChannel, versionMatchesChannel, } from "./channel.js";
17
17
  export { fromHex, toHex } from "./bytes.js";
18
18
  export { AGENT_HTTP_CAPABILITY_HEADER, AGENT_HTTP_CAPABILITY_NONCE_BYTES, MAX_AGENT_HTTP_CAPABILITY_WINDOW_MS, agentHttpCanonicalPayload, agentHttpSigningBytes, decodeAgentHttpHeader, encodeAgentHttpHeader, mintAgentHttpCapability, } from "./agentHttp.js";
@@ -29,5 +29,5 @@ export { handEquity, requiredEquity, cardCode, } from "./equity.js";
29
29
  export { cardIndex, handCategory, scoreHand, HAND_CATEGORIES, RANK_ORDER, SUIT_ORDER, } from "./handRank.js";
30
30
  export { DEFAULT_SEAT_STATE_FILE, loadSeatState, saveSeatState, } from "./seatState.js";
31
31
  export { newSeatState, openTurn, submitTurn, } from "./seatTurn.js";
32
- export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, } from "./openTournament.js";
33
- export { ClientRefusal, describeNext, refusalLines, refusalMessage, refusalMessageFromText, } from "./refusal.js";
32
+ export { chipAddressOf, joinTransaction, leaveTransaction, listTournaments, planJoin, presentToTournament, readAgentEntry, readAgentEntrySettled, BOOK_CATCHES_UP_MS, readPass, readOwner, playsHeldBy, giveBackTransaction, readTournament, sponsorAndExecute, tournamentIdArg, tournamentPossessionSignature, JoinRefused, TournamentRefusal, TOURNAMENT_POSSESSION_SEAT, } from "./openTournament.js";
33
+ export { ClientRefusal, describeNext, ownershipFencePoll, ownershipFenced, refusalLines, refusalMessage, refusalMessageFromText, } from "./refusal.js";
package/dist/offer.js CHANGED
@@ -52,7 +52,7 @@ function record(raw) {
52
52
  };
53
53
  }
54
54
  export async function readOffer(productUrl, offerId) {
55
- const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/open/v1/playground/matches/${offerId}`);
55
+ const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/v1/playground/matches/${offerId}`);
56
56
  if (status !== 200)
57
57
  throw new Error(refusalMessage("offer read", status, json));
58
58
  return record(json);
@@ -81,7 +81,7 @@ export async function acceptOffer(productUrl, offerId, seat, agent) {
81
81
  signature: Array.from(signed.signature),
82
82
  seat_possession_signature: Array.from(signed.seatPossessionSignature),
83
83
  });
84
- const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/open/v1/playground/matches/${offerId}/acceptances`, { method: "POST", headers: { "content-type": "application/json" }, body });
84
+ const { status, json } = await readJson(`${productUrl.replace(/\/$/, "")}/v1/playground/matches/${offerId}/acceptances`, { method: "POST", headers: { "content-type": "application/json" }, body });
85
85
  if (status === 200 || status === 201)
86
86
  return;
87
87
  if (status === 409)
@@ -89,7 +89,7 @@ export async function acceptOffer(productUrl, offerId, seat, agent) {
89
89
  throw new Error(refusalMessage("acceptance", status, json));
90
90
  }
91
91
  async function admit(productUrl, offerId, agentId, agent) {
92
- const target = `/open/v1/playground/matches/${offerId}/admit`;
92
+ const target = `/v1/playground/matches/${offerId}/admit`;
93
93
  const { header } = await mintAgentHttpCapability(agent, agentId, {
94
94
  method: "POST",
95
95
  requestTarget: target,
@@ -143,6 +143,28 @@ export declare function readTournamentMatches(productUrl: string, tournamentId:
143
143
  export declare function disputeHolding(productUrl: string, tournamentId: string, chipAddress: string, fetchImpl?: typeof fetch): Promise<number | undefined>;
144
144
  /** The agent's entry in the chip book, or `null` before its first redeem. */
145
145
  export declare function readAgentEntry(productUrl: string, tournamentId: string, chipAddress: string, fetchImpl?: typeof fetch): Promise<TournamentAgentEntry | null>;
146
+ /** How long a book read waits for a join that has just landed.
147
+ *
148
+ * The book is the chain's, read a checkpoint behind it: an agent whose
149
+ * `queue_join` succeeded a moment ago is on chain and not yet in the book.
150
+ * Measured at about a second on a local stack; this is generous enough to
151
+ * cover a slower one and short enough that an agent that really is absent is
152
+ * told so rather than left waiting. */
153
+ export declare const BOOK_CATCHES_UP_MS = 8000;
154
+ /** The agent's entry, waited for while the book catches up with the chain.
155
+ *
156
+ * For a caller that has just been told its join landed. A plain
157
+ * `readAgentEntry` answering `null` in that window is not "this agent never
158
+ * queued" — it is "the book has not seen the queue join yet", and the two are
159
+ * worth telling apart before refusing somebody. Absent for the whole bound is
160
+ * the first answer, and this returns `null` for it. */
161
+ export declare function readAgentEntrySettled(productUrl: string, tournamentId: string, chipAddress: string, options?: {
162
+ boundMs?: number;
163
+ pollMs?: number;
164
+ fetchImpl?: typeof fetch;
165
+ sleep?: (ms: number) => Promise<void>;
166
+ now?: () => number;
167
+ }): Promise<TournamentAgentEntry | null>;
146
168
  /** An owner's pass, or `null` when the owner has claimed none. */
147
169
  export declare function readPass(productUrl: string, tournamentId: string, owner: string, fetchImpl?: typeof fetch): Promise<TournamentPass | null>;
148
170
  /** An owner's side: its pass and every agent it has claimed, each with the
@@ -26,7 +26,7 @@ function base(productUrl) {
26
26
  return productUrl.replace(/\/$/, "");
27
27
  }
28
28
  function tournamentPath(tournamentId, rest = "") {
29
- return `/open/v1/tournaments/${tournamentId}${rest}`;
29
+ return `/v1/tournaments/${tournamentId}${rest}`;
30
30
  }
31
31
  async function readJson(fetchImpl, url) {
32
32
  const response = await fetchImpl(url);
@@ -57,7 +57,7 @@ export async function readTournament(productUrl, tournamentId, fetchImpl = fetch
57
57
  }
58
58
  /** The open-entry tournaments the product runs. */
59
59
  export async function listTournaments(productUrl, fetchImpl = fetch) {
60
- const { status, json } = await readJson(fetchImpl, `${base(productUrl)}/open/v1/tournaments`);
60
+ const { status, json } = await readJson(fetchImpl, `${base(productUrl)}/v1/tournaments`);
61
61
  if (status === 404)
62
62
  return [];
63
63
  if (status !== 200)
@@ -100,6 +100,37 @@ export async function readAgentEntry(productUrl, tournamentId, chipAddress, fetc
100
100
  throw new TournamentRefusal(status, json, "chip book read");
101
101
  return json;
102
102
  }
103
+ /** How long a book read waits for a join that has just landed.
104
+ *
105
+ * The book is the chain's, read a checkpoint behind it: an agent whose
106
+ * `queue_join` succeeded a moment ago is on chain and not yet in the book.
107
+ * Measured at about a second on a local stack; this is generous enough to
108
+ * cover a slower one and short enough that an agent that really is absent is
109
+ * told so rather than left waiting. */
110
+ export const BOOK_CATCHES_UP_MS = 8_000;
111
+ /** The agent's entry, waited for while the book catches up with the chain.
112
+ *
113
+ * For a caller that has just been told its join landed. A plain
114
+ * `readAgentEntry` answering `null` in that window is not "this agent never
115
+ * queued" — it is "the book has not seen the queue join yet", and the two are
116
+ * worth telling apart before refusing somebody. Absent for the whole bound is
117
+ * the first answer, and this returns `null` for it. */
118
+ export async function readAgentEntrySettled(productUrl, tournamentId, chipAddress, options = {}) {
119
+ const boundMs = options.boundMs ?? BOOK_CATCHES_UP_MS;
120
+ const pollMs = options.pollMs ?? 1_000;
121
+ const now = options.now ?? Date.now;
122
+ const sleep = options.sleep ??
123
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
124
+ const deadline = now() + boundMs;
125
+ for (;;) {
126
+ const entry = await readAgentEntry(productUrl, tournamentId, chipAddress, options.fetchImpl ?? fetch);
127
+ if (entry)
128
+ return entry;
129
+ if (now() >= deadline)
130
+ return null;
131
+ await sleep(pollMs);
132
+ }
133
+ }
103
134
  /** An owner's pass, or `null` when the owner has claimed none. */
104
135
  export async function readPass(productUrl, tournamentId, owner, fetchImpl = fetch) {
105
136
  const { status, json } = await readJson(fetchImpl, `${base(productUrl)}${tournamentPath(tournamentId, `/passes/${owner}`)}`);
@@ -178,7 +209,7 @@ plays, owner) {
178
209
  if (entry && entry.state !== "idle")
179
210
  throw new JoinRefused(`this agent is already ${entry.state}${entry.state === "seated" ? " at a table" : ""}; an agent plays one table at a time`, {
180
211
  action: "wait",
181
- poll: `/open/v1/tournaments/${overview.tournamentId}/agents/${entry.chipAddress}`,
212
+ poll: `/v1/tournaments/${overview.tournamentId}/agents/${entry.chipAddress}`,
182
213
  });
183
214
  const booked = entry?.balance ?? 0;
184
215
  const wallet = entry?.walletBalance ?? booked;
package/dist/refusal.d.ts CHANGED
@@ -29,6 +29,11 @@ export interface RefusalBody {
29
29
  docs?: string;
30
30
  }
31
31
  /** `next …`, as one line an agent can act on. */
32
+ /** True when a refusal body says the writer is fenced and the caller should
33
+ * poll committed ownership rather than resend to the same origin. */
34
+ export declare function ownershipFenced(body: unknown): boolean;
35
+ /** The product route a fenced seat polls, when the refusal named one. */
36
+ export declare function ownershipFencePoll(body: unknown): string | undefined;
32
37
  export declare function describeNext(next: RefusalNext): string;
33
38
  /** The lines a refusal prints: its code, what it said, the move, the entry. */
34
39
  export declare function refusalLines(body: unknown): string[];
package/dist/refusal.js CHANGED
@@ -6,6 +6,27 @@
6
6
  * reading a refused command's output reads its move off the same lines rather
7
7
  * than off a table in a skill document. */
8
8
  /** `next …`, as one line an agent can act on. */
9
+ /** True when a refusal body says the writer is fenced and the caller should
10
+ * poll committed ownership rather than resend to the same origin. */
11
+ export function ownershipFenced(body) {
12
+ if (typeof body !== "object" || body === null)
13
+ return false;
14
+ const refusal = body;
15
+ if (refusal.code !== "ownership_fenced")
16
+ return false;
17
+ return (isNext(refusal.next) &&
18
+ refusal.next.action === "wait" &&
19
+ typeof refusal.next.poll === "string");
20
+ }
21
+ /** The product route a fenced seat polls, when the refusal named one. */
22
+ export function ownershipFencePoll(body) {
23
+ if (!ownershipFenced(body))
24
+ return undefined;
25
+ const next = body.next;
26
+ if (next && next.action === "wait")
27
+ return next.poll;
28
+ return undefined;
29
+ }
9
30
  export function describeNext(next) {
10
31
  switch (next.action) {
11
32
  case "retry":
package/dist/room.js CHANGED
@@ -57,7 +57,7 @@ export async function openRoom(client, seatCount) {
57
57
  seatCount < MIN_ROOM_SEATS ||
58
58
  seatCount > MAX_ROOM_SEATS)
59
59
  throw new Error(`a private room seats ${MIN_ROOM_SEATS} to ${MAX_ROOM_SEATS}, not ${seatCount}`);
60
- const { status, json } = await signedFetch(client, "POST", "/open/v1/tables/private-rooms", { seatCount });
60
+ const { status, json } = await signedFetch(client, "POST", "/v1/tables/private-rooms", { seatCount });
61
61
  if (status !== 201) {
62
62
  if (json?.code === "agent_not_claimed")
63
63
  throw notClaimed();
@@ -95,7 +95,7 @@ export class RoomJoinRefusal extends Error {
95
95
  }
96
96
  /** Join a room by the id its opener handed out. */
97
97
  export async function joinRoom(client, tableId) {
98
- const target = `/open/v1/tables/private-rooms/${tableId}/join`;
98
+ const target = `/v1/tables/private-rooms/${tableId}/join`;
99
99
  const { status, json } = await signedFetch(client, "POST", target);
100
100
  if (status !== 201) {
101
101
  if (json?.code === "agent_not_claimed")
@@ -100,6 +100,7 @@ interface SerialisedRelease {
100
100
  arrivalMs: string;
101
101
  lockedAtMs: string;
102
102
  budgetMs: string;
103
+ terminalDigest: string | null;
103
104
  }
104
105
  type SerialisedGate = {
105
106
  phase: "prepared";
package/dist/seatState.js CHANGED
@@ -175,6 +175,9 @@ export function encodeGate(gate) {
175
175
  arrivalMs: gate.release.arrivalMs.toString(),
176
176
  lockedAtMs: gate.release.lockedAtMs.toString(),
177
177
  budgetMs: gate.release.budgetMs.toString(),
178
+ terminalDigest: gate.release.terminalDigest
179
+ ? toHex0x(gate.release.terminalDigest)
180
+ : null,
178
181
  },
179
182
  };
180
183
  }
@@ -208,6 +211,9 @@ export function decodeGate(stored) {
208
211
  arrivalMs: BigInt(stored.release.arrivalMs),
209
212
  lockedAtMs: BigInt(stored.release.lockedAtMs),
210
213
  budgetMs: BigInt(stored.release.budgetMs),
214
+ terminalDigest: stored.release.terminalDigest
215
+ ? fromHex(stored.release.terminalDigest)
216
+ : null,
211
217
  }),
212
218
  };
213
219
  default:
package/dist/seatTurn.js CHANGED
@@ -478,7 +478,7 @@ async function sayAtTable(fetchImpl, session, agent, agentId, say) {
478
478
  if (!trimmed)
479
479
  return false;
480
480
  const { mintAgentHttpCapability, AGENT_HTTP_CAPABILITY_HEADER } = await import("./agentHttp.js");
481
- const target = `/open/v1/executions/${session.executionHex}/talk`;
481
+ const target = `/v1/executions/${session.executionHex}/talk`;
482
482
  const body = new TextEncoder().encode(JSON.stringify({ say: trimmed }));
483
483
  try {
484
484
  const { header } = await mintAgentHttpCapability(agent, agentId, {
package/dist/session.d.ts CHANGED
@@ -327,6 +327,18 @@ export interface RestartRetry {
327
327
  * answer, 408, 429 or a 5xx -- until `bound` has passed. Any other answer is
328
328
  * returned as it came, so a refusal stays the caller's to read. */
329
329
  export declare function fetchWhileRestarting(fetchImpl: typeof fetch, url: string, retry?: RestartRetry): Promise<Response>;
330
+ /** Poll product until the committed owner is ready, then return its origin. */
331
+ export declare function waitForReadyOwner(args: {
332
+ productUrl: string;
333
+ executionId: string;
334
+ fetchImpl?: typeof fetch;
335
+ timeoutMs?: number;
336
+ pauseMs?: number;
337
+ sleep?: (ms: number) => Promise<void>;
338
+ }): Promise<{
339
+ sessionOrigin: string;
340
+ version: number;
341
+ }>;
330
342
  export declare function openSeatSession(args: SeatSessionArgs): Promise<SeatSession>;
331
343
  export declare function playSeat(args: PlayArgs): Promise<PlayReport>;
332
344
  /** What the play loop does about a refusal, by the refusal's tag.
package/dist/session.js CHANGED
@@ -7,6 +7,7 @@ import { MAX_TRANSPORT_FRAME_BYTES, PredictionGateConflictError, SessionBoundary
7
7
  import { actionSigningBytes, encodeActionFrame, encodeJoinFrame, encodeResumeFrame, joinSigningBytes, requireSessionVersion, resumeSigningBytes, SESSION_VERSION, UnsupportedSessionVersionError, } from "./sessionWire.js";
8
8
  import { decodeLegalActions, decodeParticipantView, encodeAction, pickAction, } from "./texas.js";
9
9
  import { authorizeSeatChallenge } from "./seatAuth.js";
10
+ import { ownershipFenced } from "./refusal.js";
10
11
  const ACTION_IDENTITY_DOMAIN = textBytes("dopa_open::client::action_identity_v1");
11
12
  /** The lines a play door reports for one seat's sitting.
12
13
  *
@@ -528,13 +529,50 @@ export async function fetchWhileRestarting(fetchImpl, url, retry = {
528
529
  await sleep(retry.pauseMs);
529
530
  }
530
531
  }
532
+ /** Poll product until the committed owner is ready, then return its origin. */
533
+ export async function waitForReadyOwner(args) {
534
+ const fetchImpl = args.fetchImpl ?? fetch;
535
+ const sleep = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
536
+ const product = args.productUrl.replace(/\/$/, "");
537
+ const until = Date.now() + (args.timeoutMs ?? 30_000);
538
+ const pause = args.pauseMs ?? 200;
539
+ const id = args.executionId.replace(/^0x/i, "");
540
+ for (;;) {
541
+ const response = await fetchImpl(`${product}/v1/authority/executions/${id}/route`);
542
+ if (response.ok) {
543
+ const body = (await response.json());
544
+ if (body.ready && body.sessionOrigin)
545
+ return { sessionOrigin: body.sessionOrigin, version: body.version ?? 0 };
546
+ }
547
+ else {
548
+ await response.body?.cancel();
549
+ }
550
+ if (Date.now() + pause > until)
551
+ throw new Error("committed owner did not become ready");
552
+ await sleep(pause);
553
+ }
554
+ }
555
+ function httpFailure(error) {
556
+ if (!(error instanceof Error))
557
+ return null;
558
+ const match = /^[a-z-]+ failed \((\d+)\): (.*)$/is.exec(error.message);
559
+ if (!match)
560
+ return null;
561
+ const status = Number(match[1]);
562
+ try {
563
+ return { status, body: JSON.parse(match[2] ?? "") };
564
+ }
565
+ catch {
566
+ return { status, body: match[2] ?? "" };
567
+ }
568
+ }
531
569
  export async function openSeatSession(args) {
532
570
  const fetchImpl = args.fetchImpl ?? fetch;
533
571
  const product = args.productUrl.replace(/\/$/, "");
534
572
  /* Read again while the product is coming back: a seat reopens its session
535
573
  after every dropped stream, and the product answering 502 for the seconds
536
574
  it restarts ended the seat as surely as a refusal would have. */
537
- const offer = await fetchWhileRestarting(fetchImpl, `${product}/open/v1/playground/matches/${args.offerId}`, args.restartRetry);
575
+ const offer = await fetchWhileRestarting(fetchImpl, `${product}/v1/playground/matches/${args.offerId}`, args.restartRetry);
538
576
  if (!offer.ok)
539
577
  throw new Error(`offer read failed (${offer.status}): ${await offer.text()}`);
540
578
  const record = (await offer.json());
@@ -581,7 +619,7 @@ export async function openSeatSession(args) {
581
619
  }
582
620
  export async function playSeat(args) {
583
621
  const fetchImpl = args.fetchImpl ?? fetch;
584
- const { client, product, executionHex, coordinatorKey, timeAuthorityKey, executionId, } = await openSeatSession(args);
622
+ let { client, product, executionHex, coordinatorKey, timeAuthorityKey, executionId, } = await openSeatSession(args);
585
623
  const record = {
586
624
  admission: {
587
625
  execution_id: executionId,
@@ -923,6 +961,22 @@ export async function playSeat(args) {
923
961
  client joined this seat with this key; fighting it back would evict
924
962
  a reconnect that may be the operator's own, so this one steps aside
925
963
  and says so. Everything else is transient, and resume is right. */
964
+ const failed = httpFailure(error);
965
+ if (failed?.status === 409 && ownershipFenced(failed.body)) {
966
+ const ready = await waitForReadyOwner({
967
+ productUrl: product,
968
+ executionId: executionHex,
969
+ fetchImpl,
970
+ timeoutMs: 30_000,
971
+ pauseMs: 200,
972
+ });
973
+ client = new SessionClient(ready.sessionOrigin, args.agent, args.seat, args.agentId, client.executionId, client.executionManifestDigest, client.clientNonce, fetchImpl);
974
+ client.token = originToken;
975
+ await client.resume(lastContext, lastCursor);
976
+ originToken = client.token ?? originToken;
977
+ reconnects += 1;
978
+ continue;
979
+ }
926
980
  const answer = afterRefusal(error);
927
981
  if (answer === "step-aside")
928
982
  return {
@@ -1004,7 +1058,7 @@ export function normaliseCardCode(code) {
1004
1058
  export async function readDisclosedEntitlement(fetchImpl, product, executionId, seat, capability, options = {}) {
1005
1059
  const attempts = options.attempts ?? 15;
1006
1060
  const pauseMs = options.pauseMs ?? 1_000;
1007
- const target = `/open/v1/spectator/executions/${executionId}`;
1061
+ const target = `/v1/spectator/executions/${executionId}`;
1008
1062
  for (let attempt = 0; attempt < attempts; attempt += 1) {
1009
1063
  try {
1010
1064
  const header = capability ? await capability("GET", target) : null;
@@ -1032,7 +1086,7 @@ export async function readDisclosedEntitlement(fetchImpl, product, executionId,
1032
1086
  * gone -- which is precisely the moment this question is being asked. */
1033
1087
  export async function readSittingStatus(fetchImpl, product, executionHex) {
1034
1088
  try {
1035
- const response = await fetchImpl(`${product}/open/v1/history/matches/${executionHex}`);
1089
+ const response = await fetchImpl(`${product}/v1/history/matches/${executionHex}`);
1036
1090
  if (!response.ok)
1037
1091
  return { state: "unknown" };
1038
1092
  const wire = (await response.json());
@@ -1073,7 +1127,7 @@ export function agentReadCapability(agent, agentId) {
1073
1127
  export async function readPublicTable(fetchImpl, product, executionHex, names = new Map(), capability) {
1074
1128
  /* Bound to the target the server reconstructs from the request, which is the
1075
1129
  origin-form path and not the absolute URL the fetch is given. */
1076
- const target = `/open/v1/spectator/executions/${executionHex}`;
1130
+ const target = `/v1/spectator/executions/${executionHex}`;
1077
1131
  try {
1078
1132
  const header = capability ? await capability("GET", target) : null;
1079
1133
  const response = await fetchImpl(`${product}${target}`, header === null
@@ -1134,14 +1188,14 @@ export async function readPublicTable(fetchImpl, product, executionHex, names =
1134
1188
  return null;
1135
1189
  }
1136
1190
  }
1137
- /** What an agent is called, from `GET /open/v1/agents/{id}/custody`, read
1191
+ /** What an agent is called, from `GET /v1/agents/{id}/custody`, read
1138
1192
  * once and remembered in `names`. Null where the read did not answer. */
1139
1193
  async function readAgentNaming(fetchImpl, product, agentId, names) {
1140
1194
  const known = names.get(agentId);
1141
1195
  if (known !== undefined)
1142
1196
  return known;
1143
1197
  try {
1144
- const response = await fetchImpl(`${product}/open/v1/agents/${agentId}/custody`);
1198
+ const response = await fetchImpl(`${product}/v1/agents/${agentId}/custody`);
1145
1199
  if (!response.ok) {
1146
1200
  names.set(agentId, null);
1147
1201
  return null;
@@ -1164,7 +1218,7 @@ async function readAgentNaming(fetchImpl, product, agentId, names) {
1164
1218
  * table: an empty list where the read did not answer. */
1165
1219
  export async function readTableTalk(fetchImpl, product, executionHex, handIndex) {
1166
1220
  try {
1167
- const response = await fetchImpl(`${product}/open/v1/executions/${executionHex}/talk`);
1221
+ const response = await fetchImpl(`${product}/v1/executions/${executionHex}/talk`);
1168
1222
  if (!response.ok)
1169
1223
  return [];
1170
1224
  const wire = (await response.json());
@@ -1200,7 +1254,7 @@ function potChips(pot) {
1200
1254
  /** File one line at the table, signed as this seat's agent. True when the
1201
1255
  * product accepted it. */
1202
1256
  async function sayAtTable(fetchImpl, product, executionHex, args, say) {
1203
- const target = `/open/v1/executions/${executionHex}/talk`;
1257
+ const target = `/v1/executions/${executionHex}/talk`;
1204
1258
  const body = textBytes(JSON.stringify({ say }));
1205
1259
  try {
1206
1260
  const { header } = await mintAgentHttpCapability(args.agent, args.agentId, {
@@ -13,6 +13,7 @@ export declare const SEAT_AUTHORIZATION_RESPONSE_TAG = 16;
13
13
  export declare const PREDICTION_GATE_RELEASED_TAG = 17;
14
14
  export declare const PREDICTION_GATE_PREPARED_TAG = 18;
15
15
  export declare const PREDICTION_GATE_OPENED_TAG = 19;
16
+ export declare const PREDICTION_GATE_RELEASED_V4_TAG = 20;
16
17
  export declare const MAX_TRANSPORT_FRAME_BYTES: number;
17
18
  export interface WireEnvelope {
18
19
  message: string;
@@ -104,6 +105,9 @@ export interface PredictionGateRelease {
104
105
  lockCappedMs: bigint;
105
106
  budgetMs: bigint;
106
107
  participantDeadlineMs: bigint;
108
+ /** Digest of the protected LLM terminal decision, when bound. A release
109
+ * with none is the version-three notice; a bound one is version four. */
110
+ terminalDigest: Uint8Array | null;
107
111
  }
108
112
  /** The gate facts a participant retains for one revision. */
109
113
  export type PredictionGateStatus = {
@@ -23,6 +23,12 @@ export const SEAT_AUTHORIZATION_RESPONSE_TAG = 0x10;
23
23
  export const PREDICTION_GATE_RELEASED_TAG = 0x11;
24
24
  export const PREDICTION_GATE_PREPARED_TAG = 0x12;
25
25
  export const PREDICTION_GATE_OPENED_TAG = 0x13;
26
+ /* The released notice carries a version per terminal-digest binding: a
27
+ release with no protected-terminal digest is the byte-identical
28
+ version-three notice, and one bound to a digest is this notice with the
29
+ raw 32-byte digest after the overlay. `arena_session::wire` picks the
30
+ same tag from the same fact. */
31
+ export const PREDICTION_GATE_RELEASED_V4_TAG = 0x14;
26
32
  export const MAX_TRANSPORT_FRAME_BYTES = 1 << 20;
27
33
  export function encodeAckFrame(context, cursor) {
28
34
  const sequence = typeof cursor === "object" ? cursor.sequence : cursor;
@@ -276,7 +282,11 @@ export function bindPredictionGateOpening(fields) {
276
282
  throw new Error("prediction gate closes at or before it opened");
277
283
  return fields;
278
284
  }
279
- function readPredictionGateRelease(reader) {
285
+ /* `bound` is the notice version, not a field the sender may vary: the
286
+ version-three notice ends at the overlay and names no digest, and the
287
+ version-four notice ends with the raw digest and no option byte. Reading
288
+ an option here would read the cursor that follows. */
289
+ function readPredictionGateRelease(reader, bound) {
280
290
  const window = readPredictionWindow(reader);
281
291
  const actingSeat = reader.readU16("prediction gate acting seat");
282
292
  const state = readState(reader, "prediction gate state nonce", "prediction gate state commitment");
@@ -288,6 +298,9 @@ function readPredictionGateRelease(reader) {
288
298
  const arrivalMs = reader.readU64("prediction gate arrival milliseconds");
289
299
  const lockedAtMs = reader.readU64("prediction gate locked-at milliseconds");
290
300
  const budgetMs = reader.readU64("participant deadline budget milliseconds");
301
+ const terminalDigest = bound
302
+ ? reader.readFixed(32, "prediction gate terminal digest")
303
+ : null;
291
304
  return bindPredictionGateRelease({
292
305
  window,
293
306
  actingSeat,
@@ -298,6 +311,7 @@ function readPredictionGateRelease(reader) {
298
311
  arrivalMs,
299
312
  lockedAtMs,
300
313
  budgetMs,
314
+ terminalDigest,
301
315
  });
302
316
  }
303
317
  /** The structural refusals of `PredictionGateRelease::new`, with the bounded
@@ -566,8 +580,9 @@ export function decodeAuthorityMessage(bytes) {
566
580
  };
567
581
  break;
568
582
  }
569
- case PREDICTION_GATE_RELEASED_TAG: {
570
- const release = readPredictionGateRelease(reader);
583
+ case PREDICTION_GATE_RELEASED_TAG:
584
+ case PREDICTION_GATE_RELEASED_V4_TAG: {
585
+ const release = readPredictionGateRelease(reader, tag === PREDICTION_GATE_RELEASED_V4_TAG);
571
586
  const cursor = readCursor(reader);
572
587
  message = {
573
588
  type: "predictionGateReleased",
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Join, action, and resume signing bodies live here; acknowledgements are
5
5
  * unsigned and encoded next to the authority decoder. The layouts are pinned
6
- * by `libs/dopa-open-client-rs/vectors/ts-signer-parity.json`.
6
+ * by `libs/dopa-open/client-rs/vectors/ts-signer-parity.json`.
7
7
  */
8
8
  import { ByteWriter, frameSigningBytes, textBytes } from "./bytes.js";
9
9
  const JOIN_DOMAIN = textBytes("arena_session::join");
@@ -1,3 +1,8 @@
1
+ /** The Product API's deployed default origin. `dopa-open`'s `--product-url`
2
+ * (and `DOPA_OPEN_PRODUCT_URL`) default here, matching the Rust client and
3
+ * CLI's `DEFAULT_OPEN_API_BASE_URL`, so a caller only has to override it for
4
+ * a local stack or a non-default plane. */
5
+ export declare const DEFAULT_OPEN_API_BASE_URL = "https://open.dopamint.fun";
1
6
  export interface SeatEntitlement {
2
7
  seat: number;
3
8
  amount: bigint;
@@ -52,5 +57,4 @@ export declare function buildConsentRequest(args: ConsentDisclosure & {
52
57
  signature: Uint8Array;
53
58
  }): ConsentRequest;
54
59
  export declare function settlementConsentPath(executionIdHex: string): string;
55
- export declare function authorityOriginFromSessionBase(sessionBaseUrl: string): string;
56
60
  export declare function digestForPrompt(prompt: ConsentPrompt): Uint8Array;
@@ -2,6 +2,11 @@ import { ByteReader, ByteWriter, equalBytes, fromHex, textBytes, toHex0x, } from
2
2
  import { digestFramed } from "./crypto.js";
3
3
  const ANCHOR_DOMAIN_ID_V1 = textBytes("arena_tunnel::anchor_domain_id_v1");
4
4
  const SETTLEMENT_PURPOSE = textBytes("arena_tunnel::settlement");
5
+ /** The Product API's deployed default origin. `dopa-open`'s `--product-url`
6
+ * (and `DOPA_OPEN_PRODUCT_URL`) default here, matching the Rust client and
7
+ * CLI's `DEFAULT_OPEN_API_BASE_URL`, so a caller only has to override it for
8
+ * a local stack or a non-default plane. */
9
+ export const DEFAULT_OPEN_API_BASE_URL = "https://open.dopamint.fun";
5
10
  export function encodeTerminalState(state) {
6
11
  const writer = new ByteWriter()
7
12
  .pushU16(1)
@@ -103,13 +108,7 @@ export function buildConsentRequest(args) {
103
108
  }
104
109
  export function settlementConsentPath(executionIdHex) {
105
110
  const hex = executionIdHex.replace(/^0x/, "");
106
- return `/open/v1/authority/executions/${hex}/settlements`;
107
- }
108
- export function authorityOriginFromSessionBase(sessionBaseUrl) {
109
- const idx = sessionBaseUrl.indexOf("/v1/exec/");
110
- if (idx <= 0)
111
- throw new Error(`admission session_base_url is not an execution session URL: ${sessionBaseUrl}`);
112
- return sessionBaseUrl.slice(0, idx);
111
+ return `/v1/authority/executions/${hex}/settlements`;
113
112
  }
114
113
  export function digestForPrompt(prompt) {
115
114
  const terminal = decodeTerminalState(fromHex(prompt.encoded_terminal_state));
package/dist/tour.js CHANGED
@@ -63,8 +63,8 @@ function unavailable(error) {
63
63
  }
64
64
  export async function enterTour(client, tour, floor = {}) {
65
65
  const target = tour === "playground"
66
- ? "/open/v1/tours/playground/entries"
67
- : "/open/v1/tours/tournament/entries";
66
+ ? "/v1/tours/playground/entries"
67
+ : "/v1/tours/tournament/entries";
68
68
  if (floor.minAgents !== undefined && tour !== "playground")
69
69
  throw new Error("a floor of real agents only applies to the playground");
70
70
  /* No body at all when nothing is stated: the join every client sent before
@@ -124,13 +124,13 @@ export async function queueUntilSeated(client, tour, options = {}) {
124
124
  }
125
125
  }
126
126
  export async function readPosition(client, tableId) {
127
- const { status, json } = await signedFetch(client, "GET", `/open/v1/tables/${tableId}/decision`);
127
+ const { status, json } = await signedFetch(client, "GET", `/v1/tables/${tableId}/decision`);
128
128
  if (status !== 200)
129
129
  throw new Error(refusalMessage("decision read", status, json));
130
130
  return json;
131
131
  }
132
132
  export async function act(client, tableId, move) {
133
- const { status, json } = await signedFetch(client, "POST", `/open/v1/tables/${tableId}/actions`, move);
133
+ const { status, json } = await signedFetch(client, "POST", `/v1/tables/${tableId}/actions`, move);
134
134
  if (status !== 200)
135
135
  throw new Error(refusalMessage("action", status, json));
136
136
  return json;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@dopamint-fun/open-sdk",
3
- "version": "0.2.0-dev.1",
3
+ "version": "0.2.0-dev.10",
4
4
  "description": "DOPA-OPEN client SDK: generate and hold your own agent key, sign registrations, and play a Participant Session",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/CommandOSSLabs/dopamint-arena.git",
9
- "directory": "libs/dopa-open-client-ts"
9
+ "directory": "libs/dopa-open/client-ts"
10
10
  },
11
11
  "type": "module",
12
12
  "main": "dist/index.js",