@cinchor/sdk 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,799 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BURN_SENTINEL: () => BURN_SENTINEL,
24
+ CAPABILITY_STATUS_LABELS: () => CAPABILITY_STATUS_LABELS,
25
+ CapabilityRegistry: () => CapabilityRegistry,
26
+ CapabilityStatus: () => CapabilityStatus,
27
+ CinchorClient: () => CinchorClient,
28
+ DEFAULT_GAS_LIMIT: () => DEFAULT_GAS_LIMIT,
29
+ DEFAULT_GAS_PRICE: () => DEFAULT_GAS_PRICE,
30
+ ENFORCEMENT_LABELS: () => ENFORCEMENT_LABELS,
31
+ EnforcementCode: () => EnforcementCode,
32
+ IGNIS_LOCAL: () => IGNIS_LOCAL,
33
+ Verdict: () => Verdict,
34
+ addressArg: () => addressArg,
35
+ canonicalJson: () => canonicalJson,
36
+ counterpartyKey: () => counterpartyKey,
37
+ decodeAddress: () => decodeAddress,
38
+ deriveAttestationId: () => deriveAttestationId,
39
+ deriveCapabilityId: () => deriveCapabilityId,
40
+ encodeAddress: () => encodeAddress,
41
+ exportPrefixFor: () => exportPrefixFor,
42
+ hashDecisionContext: () => hashDecisionContext,
43
+ nowSecs: () => nowSecs,
44
+ parseAddressReturn: () => parseAddressReturn,
45
+ verifyDecisionContext: () => verifyDecisionContext
46
+ });
47
+ module.exports = __toCommonJS(index_exports);
48
+
49
+ // src/capability-registry.ts
50
+ var import_sdk2 = require("@omne/sdk");
51
+
52
+ // src/address.ts
53
+ var import_base = require("@scure/base");
54
+ var import_sdk = require("@omne/sdk");
55
+ var ADDRESS_HRP = "om";
56
+ var WITNESS_VERSION = 2;
57
+ function decodeAddress(address) {
58
+ const { prefix, words } = import_base.bech32m.decode(address);
59
+ if (prefix !== ADDRESS_HRP) {
60
+ throw new Error(`Invalid address HRP: expected "${ADDRESS_HRP}", got "${prefix}"`);
61
+ }
62
+ if (words[0] !== WITNESS_VERSION) {
63
+ throw new Error(`Invalid witness version: expected ${WITNESS_VERSION}, got ${words[0]}`);
64
+ }
65
+ return new Uint8Array(import_base.bech32m.fromWords(Array.from(words.slice(1))));
66
+ }
67
+ function encodeAddress(payload) {
68
+ if (payload.length !== 32) {
69
+ throw new Error(`Address payload must be 32 bytes, got ${payload.length}`);
70
+ }
71
+ const dataWords = import_base.bech32m.toWords(payload);
72
+ const words = new Uint8Array(dataWords.length + 1);
73
+ words[0] = WITNESS_VERSION;
74
+ words.set(dataWords, 1);
75
+ return import_base.bech32m.encode(ADDRESS_HRP, words);
76
+ }
77
+ function addressArg(address) {
78
+ return { type: import_sdk.ArgType.Address, data: decodeAddress(address) };
79
+ }
80
+ async function sha256(bytes) {
81
+ const ab = new ArrayBuffer(bytes.byteLength);
82
+ new Uint8Array(ab).set(bytes);
83
+ return new Uint8Array(await crypto.subtle.digest("SHA-256", ab));
84
+ }
85
+ function u64be(value) {
86
+ const b = new Uint8Array(8);
87
+ new DataView(b.buffer).setBigUint64(0, BigInt(value), false);
88
+ return b;
89
+ }
90
+ function concat(...parts) {
91
+ const total = parts.reduce((n, p) => n + p.length, 0);
92
+ const out = new Uint8Array(total);
93
+ let off = 0;
94
+ for (const p of parts) {
95
+ out.set(p, off);
96
+ off += p.length;
97
+ }
98
+ return out;
99
+ }
100
+ async function deriveCapabilityId(principal, agent, nonce, createdAt) {
101
+ const preimage = concat(
102
+ decodeAddress(principal),
103
+ decodeAddress(agent),
104
+ u64be(nonce),
105
+ u64be(createdAt)
106
+ );
107
+ return encodeAddress(await sha256(preimage));
108
+ }
109
+ async function counterpartyKey(capabilityId, counterparty) {
110
+ const preimage = concat(decodeAddress(capabilityId), decodeAddress(counterparty));
111
+ return encodeAddress(await sha256(preimage));
112
+ }
113
+
114
+ // src/config.ts
115
+ function exportPrefixFor(contract) {
116
+ return contract.exportPrefix ?? `axiom_contract::${contract.name}::`;
117
+ }
118
+ var DEFAULT_GAS_LIMIT = 2e5;
119
+ var DEFAULT_GAS_PRICE = "5000";
120
+ var BURN_SENTINEL = "om1zmm0dahk7mm0dahk7mm0dahk7mm0dahk7mm0dahk7mm0dahk7mm0qdtuxap";
121
+ var IGNIS_LOCAL = {
122
+ name: "ignis",
123
+ chainId: 3,
124
+ rpcUrl: "http://127.0.0.1:26657"
125
+ };
126
+
127
+ // src/types.ts
128
+ var CapabilityStatus = /* @__PURE__ */ ((CapabilityStatus3) => {
129
+ CapabilityStatus3[CapabilityStatus3["NotFound"] = 0] = "NotFound";
130
+ CapabilityStatus3[CapabilityStatus3["Active"] = 1] = "Active";
131
+ CapabilityStatus3[CapabilityStatus3["Revoked"] = 2] = "Revoked";
132
+ return CapabilityStatus3;
133
+ })(CapabilityStatus || {});
134
+ var CAPABILITY_STATUS_LABELS = [
135
+ "not_found",
136
+ "active",
137
+ "revoked"
138
+ ];
139
+ var EnforcementCode = /* @__PURE__ */ ((EnforcementCode2) => {
140
+ EnforcementCode2[EnforcementCode2["NotFound"] = 0] = "NotFound";
141
+ EnforcementCode2[EnforcementCode2["Allowed"] = 1] = "Allowed";
142
+ EnforcementCode2[EnforcementCode2["Revoked"] = 2] = "Revoked";
143
+ EnforcementCode2[EnforcementCode2["Expired"] = 3] = "Expired";
144
+ EnforcementCode2[EnforcementCode2["OverBudget"] = 4] = "OverBudget";
145
+ EnforcementCode2[EnforcementCode2["OutOfAllowlist"] = 5] = "OutOfAllowlist";
146
+ return EnforcementCode2;
147
+ })(EnforcementCode || {});
148
+ var ENFORCEMENT_LABELS = {
149
+ [0 /* NotFound */]: "not_found",
150
+ [1 /* Allowed */]: "allowed",
151
+ [2 /* Revoked */]: "revoked",
152
+ [3 /* Expired */]: "expired",
153
+ [4 /* OverBudget */]: "over_budget",
154
+ [5 /* OutOfAllowlist */]: "out_of_allowlist"
155
+ };
156
+ var Verdict = /* @__PURE__ */ ((Verdict2) => {
157
+ Verdict2[Verdict2["InPolicy"] = 1] = "InPolicy";
158
+ Verdict2[Verdict2["OutOfPolicy"] = 2] = "OutOfPolicy";
159
+ return Verdict2;
160
+ })(Verdict || {});
161
+
162
+ // src/capability-registry.ts
163
+ function toBigInt(v) {
164
+ return BigInt(v ?? 0);
165
+ }
166
+ function toNumber(v) {
167
+ return Number(v ?? 0);
168
+ }
169
+ var CapabilityRegistry = class _CapabilityRegistry {
170
+ contract;
171
+ rpcUrl;
172
+ chainId;
173
+ contractAddress;
174
+ exportPrefix;
175
+ defaultGasLimit;
176
+ defaultGasPrice;
177
+ /** Per-signer next-nonce cache (see {@link sendSigned} for node nonce semantics). */
178
+ nonces = /* @__PURE__ */ new Map();
179
+ constructor(contract, config) {
180
+ this.contract = contract;
181
+ this.rpcUrl = config.network.rpcUrl;
182
+ this.chainId = config.network.chainId;
183
+ this.contractAddress = config.contract.address;
184
+ this.exportPrefix = exportPrefixFor(config.contract);
185
+ this.defaultGasLimit = config.defaultGasLimit ?? DEFAULT_GAS_LIMIT;
186
+ this.defaultGasPrice = config.defaultGasPrice ?? DEFAULT_GAS_PRICE;
187
+ }
188
+ static async connect(config) {
189
+ const client = new import_sdk2.OmneClient(config.network.rpcUrl);
190
+ await client.connect();
191
+ const contract = new import_sdk2.OmneContract(client, config.contract.address);
192
+ return new _CapabilityRegistry(contract, config);
193
+ }
194
+ fn(name) {
195
+ return this.exportPrefix + name;
196
+ }
197
+ /** A single, non-retrying JSON-RPC POST. */
198
+ async rpc(method, params) {
199
+ const resp = await fetch(this.rpcUrl, {
200
+ method: "POST",
201
+ headers: { "Content-Type": "application/json" },
202
+ body: JSON.stringify({ jsonrpc: "2.0", method, params, id: 1 })
203
+ });
204
+ if (!resp.ok) {
205
+ throw new Error(`${method} HTTP ${resp.status}: ${await resp.text()}`);
206
+ }
207
+ const json = await resp.json();
208
+ if (json.error) throw new Error(`${method} rejected: ${json.error.message ?? "unknown"}`);
209
+ return json.result;
210
+ }
211
+ // ── Write path ──────────────────────────────────────────────────
212
+ /**
213
+ * Build, SIGN, and submit a state-changing contract call. The Omne node
214
+ * mandates a valid ML-DSA-44 signature on every transaction; we encode the
215
+ * call data, build the transaction, sign it with the role's signer (chainId
216
+ * 3 = Ignis), and submit via a single raw JSON-RPC POST. The submitter
217
+ * ("from") must equal the signer's om1z address — the node re-derives the PQC
218
+ * address from the public key and checks it matches.
219
+ */
220
+ async sendSigned(signer, method, args, gasLimit = this.defaultGasLimit) {
221
+ const data = (0, import_sdk2.encodeContractCall)(this.fn(method), args);
222
+ let nonce = this.nonces.get(signer.address);
223
+ if (nonce === void 0) nonce = await this.fetchNonce(signer.address);
224
+ for (let attempt = 0; attempt < 2; attempt++) {
225
+ const wire = this.buildSignedWire(signer, data, nonce, gasLimit);
226
+ const res = await this.submitOnce(wire);
227
+ if (res.status === "ok" || res.status === "duplicate") {
228
+ this.nonces.set(signer.address, nonce + 1);
229
+ return this.pollReceipt(res.txHash);
230
+ }
231
+ const fresh = await this.fetchNonce(signer.address);
232
+ if (attempt === 0 && fresh > nonce) {
233
+ nonce = fresh;
234
+ this.nonces.set(signer.address, fresh);
235
+ continue;
236
+ }
237
+ throw new Error(
238
+ `transaction rejected nonce ${nonce} as too low; node reports ${fresh} (cannot recover)`
239
+ );
240
+ }
241
+ throw new Error("sendSigned: exhausted submit attempts");
242
+ }
243
+ /** Fetch the signer's next nonce from the node (0 on this devnet). */
244
+ async fetchNonce(address) {
245
+ try {
246
+ const n = await this.rpc("omne_getNonce", [address]);
247
+ return Number(n ?? 0);
248
+ } catch {
249
+ const acct = await this.rpc("omne_getAccount", [address]);
250
+ return Number(acct?.nonce ?? 0);
251
+ }
252
+ }
253
+ /**
254
+ * Build a signed wire payload for a contract call at a given nonce, validating
255
+ * the ML-DSA-44 signature/pubkey/chainId lengths locally before submit so a
256
+ * malformed signed object fails fast here, not at the node.
257
+ */
258
+ buildSignedWire(signer, data, nonce, gasLimit) {
259
+ const tx = {
260
+ from: signer.address,
261
+ to: this.contractAddress,
262
+ value: "0",
263
+ gasLimit,
264
+ gasPrice: this.defaultGasPrice,
265
+ nonce,
266
+ data,
267
+ chainId: this.chainId
268
+ };
269
+ const signed = signer.signTransaction(tx, { chainId: this.chainId });
270
+ if (typeof signed.signature !== "string" || !/^[0-9a-f]{4840}$/i.test(signed.signature)) {
271
+ throw new Error(
272
+ `signTransaction produced an invalid ML-DSA-44 signature (expected 4840 hex chars, got ${signed.signature?.length ?? 0})`
273
+ );
274
+ }
275
+ if (typeof signed.publicKey !== "string" || !/^[0-9a-f]{2624}$/i.test(signed.publicKey)) {
276
+ throw new Error(
277
+ `signTransaction produced an invalid ML-DSA-44 public key (expected 2624 hex chars, got ${signed.publicKey?.length ?? 0})`
278
+ );
279
+ }
280
+ if (!Number.isInteger(signed.chainId) || signed.chainId < 0) {
281
+ throw new Error(`signed tx carries an invalid chainId: ${signed.chainId}`);
282
+ }
283
+ return {
284
+ from: signed.from,
285
+ to: signed.to,
286
+ value: signed.value,
287
+ gasLimit: signed.gasLimit,
288
+ gasPrice: signed.gasPrice,
289
+ nonce: signed.nonce,
290
+ chainId: signed.chainId,
291
+ data: signed.data ?? "",
292
+ signature: { signature: signed.signature, publicKey: signed.publicKey }
293
+ };
294
+ }
295
+ /** Submit a signed wire payload with one non-retrying POST. */
296
+ async submitOnce(wire) {
297
+ const resp = await fetch(this.rpcUrl, {
298
+ method: "POST",
299
+ headers: { "Content-Type": "application/json" },
300
+ body: JSON.stringify({ jsonrpc: "2.0", method: "omne_sendTransaction", params: [wire], id: 1 })
301
+ });
302
+ if (!resp.ok) {
303
+ throw new Error(`omne_sendTransaction HTTP ${resp.status}: ${await resp.text()}`);
304
+ }
305
+ const json = await resp.json();
306
+ if (json.error) {
307
+ const msg = String(json.error.message ?? json.error);
308
+ if (/already\s*known|already\s*exists|duplicate/i.test(msg)) {
309
+ return { status: "duplicate", txHash: null };
310
+ }
311
+ if (/nonce\s*too\s*low|invalid\s*nonce/i.test(msg)) {
312
+ return { status: "stale_nonce", txHash: null };
313
+ }
314
+ throw new Error(`omne_sendTransaction rejected: ${msg}`);
315
+ }
316
+ const r = json.result;
317
+ return { status: "ok", txHash: typeof r === "string" ? r : r?.transactionHash ?? null };
318
+ }
319
+ /**
320
+ * Poll for a transaction receipt, tolerating the node's "receipt not found"
321
+ * RPC error while the tx is still in the mempool. 60s default: a 4-node BFT
322
+ * mesh confirms contract calls in ~20-30s.
323
+ */
324
+ async pollReceipt(txHash, timeoutMs = 6e4, intervalMs = 1e3) {
325
+ if (!txHash) return { transactionHash: null, status: "submitted", blockNumber: null };
326
+ const start = Date.now();
327
+ let lastTransportError = null;
328
+ while (Date.now() - start < timeoutMs) {
329
+ try {
330
+ const r = await this.rpc("omne_getTransactionReceipt", [txHash]);
331
+ if (r) return r;
332
+ } catch (e) {
333
+ const msg = e instanceof Error ? e.message : String(e);
334
+ if (/receipt.*not\s*found|not\s*found|-32000|-32602/i.test(msg)) {
335
+ } else {
336
+ lastTransportError = msg;
337
+ }
338
+ }
339
+ await new Promise((res) => setTimeout(res, intervalMs));
340
+ }
341
+ return {
342
+ transactionHash: txHash,
343
+ status: "pending",
344
+ blockNumber: null,
345
+ note: lastTransportError ? `receipt not available within ${timeoutMs}ms; last transport error: ${lastTransportError}` : `receipt not available within ${timeoutMs}ms`
346
+ };
347
+ }
348
+ // ── Reads ───────────────────────────────────────────────────────
349
+ async queryNumber(method, id) {
350
+ const r = await this.contract.query(this.fn(method), [addressArg(id)]);
351
+ return toNumber(r.returnValue);
352
+ }
353
+ async queryBigInt(method, id) {
354
+ const r = await this.contract.query(this.fn(method), [addressArg(id)]);
355
+ return toBigInt(r.returnValue);
356
+ }
357
+ async queryAddress(method, id) {
358
+ const r = await this.contract.query(this.fn(method), [addressArg(id)]);
359
+ return parseAddressReturn(r.returnValue);
360
+ }
361
+ getStatus(capabilityId) {
362
+ return this.queryNumber("get_status", capabilityId);
363
+ }
364
+ getMaxSpend(capabilityId) {
365
+ return this.queryBigInt("get_max_spend", capabilityId);
366
+ }
367
+ getValidUntil(capabilityId) {
368
+ return this.queryBigInt("get_valid_until", capabilityId);
369
+ }
370
+ getTotalSpent(capabilityId) {
371
+ return this.queryBigInt("get_total_spent", capabilityId);
372
+ }
373
+ getActionCount(capabilityId) {
374
+ return this.queryBigInt("get_action_count", capabilityId);
375
+ }
376
+ getCreatedAt(capabilityId) {
377
+ return this.queryBigInt("get_created_at", capabilityId);
378
+ }
379
+ getRevokedAt(capabilityId) {
380
+ return this.queryBigInt("get_revoked_at", capabilityId);
381
+ }
382
+ getPolicyVersion(capabilityId) {
383
+ return this.queryBigInt("get_policy_version", capabilityId);
384
+ }
385
+ getAttestationCount(capabilityId) {
386
+ return this.queryBigInt("get_attestation_count", capabilityId);
387
+ }
388
+ async getAllowlistEnabled(capabilityId) {
389
+ return await this.queryNumber("get_allowlist_enabled", capabilityId) === 1;
390
+ }
391
+ async isCounterpartyAllowed(capabilityId, counterparty) {
392
+ const key = await counterpartyKey(capabilityId, counterparty);
393
+ const r = await this.contract.query(this.fn("is_counterparty_allowed"), [addressArg(key)]);
394
+ return toNumber(r.returnValue) === 1;
395
+ }
396
+ /** Read a decision attestation's on-chain record (the tamper-evidence anchor). */
397
+ async getAttestation(attestationId) {
398
+ const [exists, ch, pv, v, t] = await Promise.all([
399
+ this.contract.query(this.fn("get_attestation_exists"), [addressArg(attestationId)]),
400
+ this.contract.query(this.fn("get_attestation_context_hash"), [addressArg(attestationId)]),
401
+ this.contract.query(this.fn("get_attestation_policy_version"), [addressArg(attestationId)]),
402
+ this.contract.query(this.fn("get_attestation_verdict"), [addressArg(attestationId)]),
403
+ this.contract.query(this.fn("get_attestation_time"), [addressArg(attestationId)])
404
+ ]);
405
+ return {
406
+ exists: toNumber(exists.returnValue) === 1,
407
+ contextHash: parseAddressReturn(ch.returnValue),
408
+ policyVersion: toBigInt(pv.returnValue),
409
+ verdict: toNumber(v.returnValue),
410
+ time: toBigInt(t.returnValue)
411
+ };
412
+ }
413
+ /** Fetch the complete state of a capability in one call. */
414
+ async getCapabilityState(capabilityId) {
415
+ const [
416
+ status,
417
+ principal,
418
+ agent,
419
+ maxSpend,
420
+ validUntil,
421
+ totalSpent,
422
+ actionCount,
423
+ createdAt,
424
+ revokedAt
425
+ ] = await Promise.all([
426
+ this.getStatus(capabilityId),
427
+ this.queryAddress("get_principal", capabilityId).catch(() => ""),
428
+ this.queryAddress("get_agent", capabilityId).catch(() => ""),
429
+ this.getMaxSpend(capabilityId),
430
+ this.getValidUntil(capabilityId),
431
+ this.getTotalSpent(capabilityId),
432
+ this.getActionCount(capabilityId),
433
+ this.getCreatedAt(capabilityId),
434
+ this.getRevokedAt(capabilityId)
435
+ ]);
436
+ return {
437
+ capabilityId,
438
+ status,
439
+ statusLabel: CAPABILITY_STATUS_LABELS[status] ?? "not_found",
440
+ principal,
441
+ agent,
442
+ maxSpend,
443
+ validUntil,
444
+ totalSpent,
445
+ actionCount,
446
+ createdAt,
447
+ revokedAt
448
+ };
449
+ }
450
+ // ── Writes (signed; require a funded signer) ─────────────────────
451
+ /** Principal mints a scoped capability to an agent. Signed by the principal. */
452
+ mintPermission(opts) {
453
+ return this.sendSigned(
454
+ opts.signer,
455
+ "mint_permission",
456
+ [
457
+ addressArg(opts.capabilityId),
458
+ addressArg(opts.principal),
459
+ addressArg(opts.agent),
460
+ import_sdk2.AbiEncode.i64(opts.maxSpend),
461
+ import_sdk2.AbiEncode.i64(opts.validUntil),
462
+ import_sdk2.AbiEncode.i64(opts.allowlistEnabled ? 1n : 0n),
463
+ import_sdk2.AbiEncode.i64(opts.currentTime)
464
+ ],
465
+ opts.gasLimit
466
+ );
467
+ }
468
+ /** Principal authorizes a counterparty for a capability's allowlist. */
469
+ async addAllowedCounterparty(opts) {
470
+ const key = await counterpartyKey(opts.capabilityId, opts.counterparty);
471
+ return this.sendSigned(
472
+ opts.signer,
473
+ "add_allowed_counterparty",
474
+ [addressArg(opts.capabilityId), addressArg(key), import_sdk2.AbiEncode.i64(opts.currentTime)],
475
+ opts.gasLimit
476
+ );
477
+ }
478
+ /** Principal updates a capability's policy (limits) and bumps its on-chain version. */
479
+ updatePolicy(opts) {
480
+ return this.sendSigned(
481
+ opts.signer,
482
+ "update_policy",
483
+ [
484
+ addressArg(opts.capabilityId),
485
+ import_sdk2.AbiEncode.i64(opts.newMaxSpend),
486
+ import_sdk2.AbiEncode.i64(opts.newValidUntil),
487
+ import_sdk2.AbiEncode.i64(opts.currentTime)
488
+ ],
489
+ opts.gasLimit
490
+ );
491
+ }
492
+ /**
493
+ * Agent records a capability-bound action — the substrate Policy Enforcement
494
+ * Point. Signed by the agent. `counterparty` is ignored when the capability's
495
+ * allowlist is disabled; when enabled, the substrate refuses (code 5) unless
496
+ * the counterparty has been allowlisted.
497
+ */
498
+ async recordAction(opts) {
499
+ const cp = opts.counterparty ?? BURN_SENTINEL;
500
+ const key = await counterpartyKey(opts.capabilityId, cp);
501
+ return this.sendSigned(
502
+ opts.signer,
503
+ "record_action",
504
+ [
505
+ addressArg(opts.capabilityId),
506
+ import_sdk2.AbiEncode.i64(opts.amountSpent),
507
+ addressArg(key),
508
+ import_sdk2.AbiEncode.i64(opts.currentTime)
509
+ ],
510
+ opts.gasLimit
511
+ );
512
+ }
513
+ /** Principal revokes a capability. Terminal. Signed by the principal. */
514
+ revokePermission(opts) {
515
+ return this.sendSigned(
516
+ opts.signer,
517
+ "revoke_permission",
518
+ [addressArg(opts.capabilityId), import_sdk2.AbiEncode.i64(opts.currentTime)],
519
+ opts.gasLimit
520
+ );
521
+ }
522
+ /**
523
+ * Agent records a decision attestation (provable-after). Commits the
524
+ * decision's context_hash + verdict, bound to the capability's current policy
525
+ * version. Signed by the agent.
526
+ */
527
+ recordAttestation(opts) {
528
+ return this.sendSigned(
529
+ opts.signer,
530
+ "record_attestation",
531
+ [
532
+ addressArg(opts.attestationId),
533
+ addressArg(opts.capabilityId),
534
+ addressArg(opts.contextHash),
535
+ import_sdk2.AbiEncode.i64(BigInt(opts.verdict)),
536
+ import_sdk2.AbiEncode.i64(opts.currentTime)
537
+ ],
538
+ opts.gasLimit
539
+ );
540
+ }
541
+ };
542
+ function parseAddressReturn(returnValue) {
543
+ if (typeof returnValue === "string" && /^(0x)?[0-9a-fA-F]+$/.test(returnValue)) {
544
+ let hex = returnValue.replace(/^0x/, "");
545
+ if (hex.length > 64) return returnValue;
546
+ hex = hex.padStart(64, "0");
547
+ const bytes = new Uint8Array(32);
548
+ for (let i = 0; i < 32; i++) {
549
+ bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
550
+ }
551
+ try {
552
+ return encodeAddress(bytes);
553
+ } catch {
554
+ return returnValue;
555
+ }
556
+ }
557
+ if (returnValue === "0" || returnValue === null) return "";
558
+ return String(returnValue);
559
+ }
560
+
561
+ // src/attestation.ts
562
+ function canonicalJson(value) {
563
+ const norm = (v) => {
564
+ if (Array.isArray(v)) return v.map(norm);
565
+ if (v && typeof v === "object") {
566
+ return Object.keys(v).sort().reduce((acc, k) => {
567
+ acc[k] = norm(v[k]);
568
+ return acc;
569
+ }, {});
570
+ }
571
+ return v;
572
+ };
573
+ return JSON.stringify(norm(value));
574
+ }
575
+ async function hashDecisionContext(context) {
576
+ const bytes = new TextEncoder().encode(canonicalJson(context));
577
+ return encodeAddress(await sha256(bytes));
578
+ }
579
+ async function deriveAttestationId(capabilityId, contextHash, seq) {
580
+ const preimage = concat(
581
+ decodeAddress(capabilityId),
582
+ decodeAddress(contextHash),
583
+ u64be(seq)
584
+ );
585
+ return encodeAddress(await sha256(preimage));
586
+ }
587
+ async function verifyDecisionContext(context, onChainContextHash) {
588
+ const recomputed = await hashDecisionContext(context);
589
+ return { ok: recomputed === onChainContextHash, recomputed };
590
+ }
591
+
592
+ // src/client.ts
593
+ function nowSecs() {
594
+ return BigInt(Math.floor(Date.now() / 1e3));
595
+ }
596
+ async function settle(read, done, timeoutMs = 12e3, intervalMs = 750) {
597
+ const start = Date.now();
598
+ let last = await read();
599
+ while (!done(last) && Date.now() - start < timeoutMs) {
600
+ await new Promise((r) => setTimeout(r, intervalMs));
601
+ last = await read();
602
+ }
603
+ return last;
604
+ }
605
+ var CinchorClient = class _CinchorClient {
606
+ registry;
607
+ constructor(registry) {
608
+ this.registry = registry;
609
+ }
610
+ /** Connect to a network + deployed accountability contract. */
611
+ static async connect(config) {
612
+ return new _CinchorClient(await CapabilityRegistry.connect(config));
613
+ }
614
+ // ── Capability lifecycle ─────────────────────────────────────────
615
+ /**
616
+ * Mint a cryptographically-scoped capability to an agent: capability, spend
617
+ * ceiling, validity window, revocable at will. Returns the derived capability
618
+ * id and the commit receipt.
619
+ */
620
+ async mintCapability(opts) {
621
+ const currentTime = opts.currentTime ?? nowSecs();
622
+ const validUntil = opts.validUntil ?? (opts.ttlSeconds !== void 0 ? currentTime + BigInt(opts.ttlSeconds) : (() => {
623
+ throw new Error("mintCapability requires either validUntil or ttlSeconds");
624
+ })());
625
+ const nonce = opts.nonce ?? Math.floor(Math.random() * 2 ** 48);
626
+ const capabilityId = await deriveCapabilityId(
627
+ opts.principal.address,
628
+ opts.agent,
629
+ nonce,
630
+ Number(currentTime)
631
+ );
632
+ const receipt = await this.registry.mintPermission({
633
+ signer: opts.principal,
634
+ capabilityId,
635
+ principal: opts.principal.address,
636
+ agent: opts.agent,
637
+ maxSpend: opts.maxSpend,
638
+ validUntil,
639
+ allowlistEnabled: opts.allowlist,
640
+ currentTime,
641
+ gasLimit: opts.gasLimit
642
+ });
643
+ await settle(
644
+ () => this.getCapability(capabilityId),
645
+ (c) => c.status === 1 /* Active */
646
+ );
647
+ return { capabilityId, receipt };
648
+ }
649
+ /** Revoke a capability. Terminal. Signed by the principal. */
650
+ async revoke(opts) {
651
+ const receipt = await this.registry.revokePermission({
652
+ signer: opts.principal,
653
+ capabilityId: opts.capability,
654
+ currentTime: opts.currentTime ?? nowSecs(),
655
+ gasLimit: opts.gasLimit
656
+ });
657
+ await settle(
658
+ () => this.getCapability(opts.capability),
659
+ (c) => c.status === 2 /* Revoked */
660
+ );
661
+ return receipt;
662
+ }
663
+ /** Update a capability's policy (limits) and bump its on-chain version. */
664
+ updatePolicy(opts) {
665
+ return this.registry.updatePolicy({
666
+ signer: opts.principal,
667
+ capabilityId: opts.capability,
668
+ newMaxSpend: opts.maxSpend,
669
+ newValidUntil: opts.validUntil,
670
+ currentTime: opts.currentTime ?? nowSecs(),
671
+ gasLimit: opts.gasLimit
672
+ });
673
+ }
674
+ /** Authorize a counterparty for a capability's allowlist. */
675
+ allowCounterparty(opts) {
676
+ return this.registry.addAllowedCounterparty({
677
+ signer: opts.principal,
678
+ capabilityId: opts.capability,
679
+ counterparty: opts.counterparty,
680
+ currentTime: opts.currentTime ?? nowSecs(),
681
+ gasLimit: opts.gasLimit
682
+ });
683
+ }
684
+ // ── The two verbs ────────────────────────────────────────────────
685
+ /**
686
+ * Authorize-or-refuse a consequential action. The substrate enforces the
687
+ * capability's invariants atomically: an out-of-scope action commits no state
688
+ * change. The verdict is read back from committed state (a record_action
689
+ * receipt does not carry the contract's return code).
690
+ *
691
+ * Verdict classification assumes serial, single-signer use of the capability
692
+ * (one in-flight action at a time) — the documented integration pattern.
693
+ */
694
+ async enforce(opts) {
695
+ const currentTime = opts.currentTime ?? nowSecs();
696
+ const before = await this.registry.getCapabilityState(opts.capability);
697
+ const receipt = await this.registry.recordAction({
698
+ signer: opts.agent,
699
+ capabilityId: opts.capability,
700
+ amountSpent: opts.amount,
701
+ counterparty: opts.counterparty,
702
+ currentTime,
703
+ gasLimit: opts.gasLimit
704
+ });
705
+ const after = await settle(
706
+ () => this.registry.getCapabilityState(opts.capability),
707
+ (a) => a.actionCount > before.actionCount,
708
+ 8e3
709
+ );
710
+ const code = await this.classify(before, after, opts.amount, currentTime, opts);
711
+ return { allowed: code === 1 /* Allowed */, code, reason: ENFORCEMENT_LABELS[code], receipt };
712
+ }
713
+ async classify(before, after, amount, t, opts) {
714
+ if (after.actionCount > before.actionCount && after.totalSpent === before.totalSpent + amount) {
715
+ return 1 /* Allowed */;
716
+ }
717
+ if (before.status === 0 /* NotFound */) return 0 /* NotFound */;
718
+ if (before.status === 2 /* Revoked */) return 2 /* Revoked */;
719
+ if (t > before.validUntil) return 3 /* Expired */;
720
+ if (before.totalSpent + amount > before.maxSpend) return 4 /* OverBudget */;
721
+ if (await this.registry.getAllowlistEnabled(opts.capability)) {
722
+ const cp = opts.counterparty ?? BURN_SENTINEL;
723
+ if (!await this.registry.isCounterpartyAllowed(opts.capability, cp)) {
724
+ return 5 /* OutOfAllowlist */;
725
+ }
726
+ }
727
+ return 0 /* NotFound */;
728
+ }
729
+ /**
730
+ * Commit a tamper-evident attestation of a decision. The full context is
731
+ * hashed canonically; the hash + verdict are committed on-chain, bound to the
732
+ * capability's current policy version. An auditor later re-hashes the
733
+ * off-chain artifact and confirms it matches (see {@link verifyAttestation}).
734
+ */
735
+ async attest(opts) {
736
+ const currentTime = opts.currentTime ?? nowSecs();
737
+ const verdict = opts.verdict ?? 1 /* InPolicy */;
738
+ const seq = opts.seq ?? 0;
739
+ const contextHash = await hashDecisionContext(opts.context);
740
+ const attestationId = await deriveAttestationId(opts.capability, contextHash, seq);
741
+ const receipt = await this.registry.recordAttestation({
742
+ signer: opts.agent,
743
+ attestationId,
744
+ capabilityId: opts.capability,
745
+ contextHash,
746
+ verdict,
747
+ currentTime,
748
+ gasLimit: opts.gasLimit
749
+ });
750
+ await settle(() => this.registry.getAttestation(attestationId), (a) => a.exists);
751
+ return { attestationId, contextHash, verdict, receipt };
752
+ }
753
+ // ── Audit (reads; no signer required) ────────────────────────────
754
+ /** Read the full on-chain state of a capability. */
755
+ getCapability(capabilityId) {
756
+ return this.registry.getCapabilityState(capabilityId);
757
+ }
758
+ /** Read a decision attestation's on-chain record. */
759
+ getAttestation(attestationId) {
760
+ return this.registry.getAttestation(attestationId);
761
+ }
762
+ /**
763
+ * Tamper check: re-hash a decision context and confirm it matches the
764
+ * attestation's on-chain commitment. Returns whether it matches, the
765
+ * recomputed hash, and the on-chain record.
766
+ */
767
+ async verifyAttestation(context, attestationId) {
768
+ const onChain = await this.registry.getAttestation(attestationId);
769
+ const { ok, recomputed } = await verifyDecisionContext(context, onChain.contextHash);
770
+ return { ok: ok && onChain.exists, recomputed, onChain };
771
+ }
772
+ };
773
+ // Annotate the CommonJS export names for ESM import in node:
774
+ 0 && (module.exports = {
775
+ BURN_SENTINEL,
776
+ CAPABILITY_STATUS_LABELS,
777
+ CapabilityRegistry,
778
+ CapabilityStatus,
779
+ CinchorClient,
780
+ DEFAULT_GAS_LIMIT,
781
+ DEFAULT_GAS_PRICE,
782
+ ENFORCEMENT_LABELS,
783
+ EnforcementCode,
784
+ IGNIS_LOCAL,
785
+ Verdict,
786
+ addressArg,
787
+ canonicalJson,
788
+ counterpartyKey,
789
+ decodeAddress,
790
+ deriveAttestationId,
791
+ deriveCapabilityId,
792
+ encodeAddress,
793
+ exportPrefixFor,
794
+ hashDecisionContext,
795
+ nowSecs,
796
+ parseAddressReturn,
797
+ verifyDecisionContext
798
+ });
799
+ //# sourceMappingURL=index.cjs.map