@ouronet/talos-registry 1.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/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ ## 1.1.0 — 2026-09-26
2
+
3
+ **Ghost values: 40 account-shaped parameters stopped rendering as `"example"`.**
4
+
5
+ Surface `67c0979111eb156b` → `7e59e59d4c5b7257`. No contract changed — the module hashes are
6
+ identical, verified by `_registrylive.py` (12 modules, 0 drifted). What moved is the ghost
7
+ dictionary, and the surface hash moves with it because the snapshot is the whole artefact.
8
+
9
+ **Why it mattered.** A consumer previewing `ATS|C_Coil` got
10
+ `No value found in table … for key: example` — true, and useless. The ghost dictionary keyed
11
+ account values on `patron` / `executor` / `executee` / `account` / `buyer` / `receiver`, and the
12
+ contracts' own vocabulary is much wider: `coiler`, `curler`, `brumator`, `constricter`, `culler`,
13
+ `fueler`, `kickstarter`, `awaker`, `hibernator`, `wrapper`, `unwrapper`, `vester`, `redeemer`,
14
+ `transmuter`, `donor`, `owner-konto`, `redemption-payer` … Every one fell through to the generic
15
+ string ghost.
16
+
17
+ **Not guessed.** The names were matched against `_executorplan.ACCT` — the curated account-vs-
18
+ entity vocabulary the patron/executor canon sweep already maintains in the Pact repo, for exactly
19
+ this distinction. Reusing it means there is one list, not two that drift.
20
+
21
+ **227 slots are still generic, and deliberately so.** They are entity ids — `fvt-id`, `pool-id`,
22
+ `score-id`, `collectable-id` — where a plausible-looking fake would produce a confident number for
23
+ an entity nobody owns. An obviously-fake `"example"` is the better failure.
24
+
25
+ ### `params` on every spec
26
+
27
+ Consumers merging real values into a preview need the **preview's** parameter names, and
28
+ **410 of 423 previews take a different parameter list from their entrypoint** —
29
+ `INFO_DPTF|Transfer` is `(patron id sender receiver transfer-amount)` against `C_Transfer`'s
30
+ `(patron executor executee id transfer-amount method)`. Merging on the execution signature would
31
+ put an account in an `id` slot on 97% of calls, silently, with a cost beside it.
32
+
33
+ # @ouronet/talos-registry
34
+
35
+ ## 1.0.0
36
+
37
+ First release. 423 entrypoints, 428 previews, surface `67c0979111eb156b`, generated against
38
+ mainnet by `REPL/tools/_registry.py --probe`.
39
+
40
+ * `buildCall` / `buildPreviewCall` / `buildGhostCall` — arguments by NAME, rendered in the
41
+ contract's declared order and checked against the deployed signature.
42
+ * `planCall` / `explainCall` — preflight reads, capability reader, signers, transaction count
43
+ and the warnings that live in different keys of the record.
44
+ * `capabilityRecipe` / `parseCapabilities` — the four launchpad buys.
45
+ * `formatForType` — Pact literal rules: decimals never bare, lists space-separated, glyph
46
+ strings verbatim, guards as `(read-keyset "name")`.
47
+ * Full types for the record.
48
+
49
+ 450 tests, including one per entrypoint asserting its ghost builds.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @ouronet/talos-registry
2
+
3
+ The Ouronet callable surface, generated from the **deployed** contracts.
4
+
5
+ A consumer supplies **values**. It never types a function name, an argument order, or an arity.
6
+
7
+ ```ts
8
+ import { buildCall, planCall, explainCall } from "@ouronet/talos-registry";
9
+
10
+ buildCall("TS01-C1.DPTF|C_Transfer", {
11
+ patron: "Σ.…", executor: "Σ.…", executee: "Σ.…",
12
+ id: "OURO-8Nh-JO8JO4F5", "transfer-amount": 1, method: false,
13
+ });
14
+ // (ouronet-ns.TS01-C1.DPTF|C_Transfer "Σ.…" "Σ.…" "Σ.…" "OURO-8Nh-JO8JO4F5" 1.0 false)
15
+ ```
16
+
17
+ Note `1` became `1.0`. Pact's decimal lexer rejects a bare integer in a decimal slot, and that
18
+ is the least interesting thing this package stops you getting wrong.
19
+
20
+ **This build:** 423 entrypoints, 428 previews, surface `7e59e59d4c5b7257`, generated against mainnet.
21
+ Every figure in this file is asserted by `tests/readme.test.ts` against the bundled snapshot, so
22
+ a stale number fails the suite rather than misleading a reader.
23
+
24
+ ---
25
+
26
+ ## Why it exists
27
+
28
+ Every consumer bug that led to this was a hand-written Pact template that drifted from the
29
+ contract, and **none of them failed loudly**:
30
+
31
+ - **A missing module member is a RESOLUTION error.** `try` cannot catch it, so consumers render
32
+ it as a default. Thirty-four stale names made Awake and Slumber report "no hibernated nonces"
33
+ against three live ones — it looked like missing data, not a broken call.
34
+ - **A short call is not rejected.** Pact **partially applies** and yields a closure. A launchpad
35
+ read passing four arguments to a five-parameter reader returned *"Evaluation did not reduce to
36
+ a value"*; the capabilities came back null and the purchase silently could not be funded.
37
+ - **An argument can move without the count changing.** `SWP|C_ChangeOwnership` kept four
38
+ parameters and moved the pool id to last. No arity check catches that.
39
+
40
+ `buildCall` takes arguments **by name** and renders them in the contract's declared order, so
41
+ both classes are impossible.
42
+
43
+ ---
44
+
45
+ ## What an entry knows
46
+
47
+ Nine things, for all 423 entrypoints:
48
+
49
+ | | |
50
+ |---|---|
51
+ | **shape** | parameters in order, with declared types, and the return type when one is declared |
52
+ | **preview** | the `INFO_` reader that prices the call — it lives on a *different module* |
53
+ | **ownership** | whose key the caller must sign for, marked `ALWAYS` or `CONDITIONAL` |
54
+ | **provenance** | deployed vs repo-only, module hash, and any deployed/repo divergence |
55
+ | **capabilities** | the reader that computes required `coin.TRANSFER` caps, and how to map its arguments |
56
+ | **sponsorship** | whether the gas station pays — and for a `defpact`, that it pays for step 0 only |
57
+ | **formula** | same field: the capability reader *is* the formula, because the amounts cannot be derived client-side |
58
+ | **ghost** | a worked example for every parameter, shapes read from mainnet |
59
+ | **execution** | how it runs — below |
60
+
61
+ ### Execution modes
62
+
63
+ | mode | n | meaning |
64
+ |---|---|---|
65
+ | `direct` | 396 | the inputs suffice |
66
+ | `defpact` | 10 | multi-transaction by **continuation** |
67
+ | `indirect-parallel` | 9 | a preflight cut into slices — order-independent, fire together |
68
+ | `indirect-single` | 5 | one transaction, but a preflight supplies an argument |
69
+ | `indirect-sequential` | 3 | a preflight reports progress; each call advances a **stored cursor** |
70
+
71
+ The last two both wear the `p` suffix in Pact and **only one is parallel**. Firing a cursor
72
+ pager's pages concurrently races its own counter. `planCall()` says which you have.
73
+
74
+ ---
75
+
76
+ ## Three things that surprise people
77
+
78
+ **Continuations are not gas-sponsored.** Chainweb injects `exec-code` only for `exec` payloads.
79
+ `DALOS.GAS_PAYER` binds it *eagerly*, so on a `cont` it raises before any check runs. Step 0 is
80
+ sponsored; the **customer account** pays every later step. All ten `defpact` entrypoints have a
81
+ single-transaction twin with a byte-identical parameter list — `preferInstead` names it, and
82
+ `buildCall` refuses the defpact route unless you pass `{ allowUnsupported: true }`.
83
+
84
+ **An entity id is not an account.** `swpair` is a pool id; nobody holds its key. The account is
85
+ whatever `UR_OwnerKonto` returns for it. `ownership.requires[].via` says `"parameter"` or
86
+ `"reader"`, and never conflates them.
87
+
88
+ **Four names resolve to two modules.** `SWP|C_AddFrozenLiquidity` and three siblings exist on
89
+ both `TS01-C3` (direct) and `TS01-CP` (defpact), with identical signatures. `resolveByName()`
90
+ **refuses** rather than guessing — picking wrong is silent in both directions.
91
+
92
+ ---
93
+
94
+ ## API
95
+
96
+ ```ts
97
+ // lookup
98
+ getEntrypoint(key) // throws, and names near matches, on a stale key
99
+ resolveByName(fn) // refuses an ambiguous bare name
100
+ entrypointKeys() / modules() / entrypointsOfModule(m)
101
+ registry / surfaceHash / namespace
102
+
103
+ // build — checked against the deployed signature
104
+ buildCall(key, argsByName, opts?)
105
+ buildPreviewCall(key, argsByName, opts?)
106
+ buildGhostCall(key) // the worked example. Illustrative, never submittable.
107
+
108
+ // plan
109
+ planCall(key) -> CallPlan // preflight, capabilities, signers, tx count, warnings
110
+ explainCall(key) -> string // the same, as text
111
+
112
+ // capabilities
113
+ capabilityRecipe(key) // null when GAS_PAYER alone suffices
114
+ parseCapabilities(strings) // <(coin.TRANSFER "a" "b" 1.0)> -> signer args
115
+
116
+ // rendering
117
+ formatForType(value, declaredType)
118
+ ```
119
+
120
+ ### A launchpad buy, end to end
121
+
122
+ ```ts
123
+ const KEY = "TS02-CPAD.SPARK|C_BuySparks";
124
+ const recipe = capabilityRecipe(KEY)!; // DEMIPAD-SPARK.URC_Acquire
125
+
126
+ const raw = await pactRead(buildCall(recipe.reader, {
127
+ buyer, amount: sparks, "iz-native": true, slippage: 1.0,
128
+ }));
129
+ const caps = parseCapabilities(raw); // attach to the PAYER signer
130
+
131
+ const code = buildCall(KEY, {
132
+ patron, buyer, "sparks-amount": sparks, "iz-native": true, "max-cost": maxCost,
133
+ });
134
+ ```
135
+
136
+ Call the reader. **Do not recompute the amounts** — they depend on live price, the
137
+ native/wrapped split and a slippage pad, and a self-derived figure signs a capability the
138
+ contract will not match.
139
+
140
+ ---
141
+
142
+ ## The snapshot is pinned, deliberately
143
+
144
+ The registry is **bundled at build time**, not fetched at runtime. Refreshing live would add a
145
+ trust surface (whatever a node returns) and a failure mode (offline means no registry) that a
146
+ pinned artefact does not have. `surfaceHash` identifies exactly which surface a build was
147
+ compiled against.
148
+
149
+ `npm run sync` copies it from the Pact repo and **refuses** a snapshot that was not generated
150
+ against the chain, or that carries deployed/repo divergences — a truncated or repo-only copy
151
+ would be worse than none, because consumers would validate against it and get confident wrong
152
+ answers.
153
+
154
+ Regenerate upstream with `python3 REPL/tools/_registry.py --probe`, then `npm run build` here.
155
+
156
+ ## Ghost values
157
+
158
+ Every parameter has a worked example. They are keyed by **parameter name**, then by type, then
159
+ derived from the contract's own `defschema` — 423 entrypoints share 2,182 slots but only 276
160
+ distinct names, and four of those cover half of them. Authoring per entrypoint would mean
161
+ writing the same account into 416 `patron` slots by hand.
162
+
163
+ Every id was read from **mainnet**, so the *shape* is real: you can see that a swpair is a
164
+ four-segment pipe-joined string and an account is a glyph string, not a `k:` address.
165
+
166
+ **They are not submittable.** The accounts are not yours to sign for, and an amount of `1.0` is
167
+ a placeholder. A preflight-fed parameter is `null` with a note naming the read — the registry
168
+ will not invent a value it has just told you cannot be constructed.
@@ -0,0 +1,41 @@
1
+ export interface BuildOptions {
2
+ /**
3
+ * Build even when `execution.supported` is false. The ten `defpact` entrypoints each have a
4
+ * single-transaction twin with an identical parameter list, and the owner's 2026-09-15 ruling
5
+ * is that the defpact paths are historical. Pass this only when exercising one deliberately.
6
+ */
7
+ allowUnsupported?: boolean;
8
+ /** Override the namespace. Defaults to the registry's own (`ouronet-ns`). */
9
+ namespace?: string;
10
+ }
11
+ /**
12
+ * Build the Pact code for a call, CHECKED against the deployed signature.
13
+ *
14
+ * This is the whole point of the package. Every consumer bug this registry was built after was
15
+ * a hand-written template that drifted from the contract:
16
+ *
17
+ * * five builders passed three arguments to entrypoints that take four, after the
18
+ * patron/executor sweep added `executor` -- the UI stopped compiling and nobody noticed
19
+ * because the repo's typecheck ran against a config that checked zero files;
20
+ * * a launchpad read passed four arguments to a five-parameter reader. Pact does not raise
21
+ * "wrong arity" for that: it PARTIALLY APPLIES and yields a closure, so the caller saw
22
+ * "Evaluation did not reduce to a value" and the buy silently could not be funded.
23
+ *
24
+ * Arguments are supplied BY NAME and rendered in the contract's declared order, so an argument
25
+ * cannot be transposed either -- `C_ChangeOwnership` moved its pool id to last while keeping
26
+ * the same arity, which no count check would catch.
27
+ */
28
+ export declare function buildCall(key: string, args: Record<string, unknown>, opts?: BuildOptions): string;
29
+ /**
30
+ * Build the cost-preview (`INFO_`) call for an entrypoint.
31
+ *
32
+ * The preview lives on a DIFFERENT module by design, and assuming otherwise is how a consumer
33
+ * ended up calling INFO-ZERO for previews that had moved to INFO-ONE. The pairing is recorded,
34
+ * not guessed.
35
+ */
36
+ export declare function buildPreviewCall(key: string, args: Record<string, unknown>, opts?: BuildOptions): string;
37
+ /** Build the example call from the registry's ghost values. Illustrative, never submittable. */
38
+ export declare function buildGhostCall(key: string): {
39
+ code: string;
40
+ warning: string;
41
+ };
package/dist/build.js ADDED
@@ -0,0 +1,100 @@
1
+ import { formatForType } from "./format.js";
2
+ import { getEntrypoint, namespace, RegistryError, registry } from "./registry.js";
3
+ /**
4
+ * Build the Pact code for a call, CHECKED against the deployed signature.
5
+ *
6
+ * This is the whole point of the package. Every consumer bug this registry was built after was
7
+ * a hand-written template that drifted from the contract:
8
+ *
9
+ * * five builders passed three arguments to entrypoints that take four, after the
10
+ * patron/executor sweep added `executor` -- the UI stopped compiling and nobody noticed
11
+ * because the repo's typecheck ran against a config that checked zero files;
12
+ * * a launchpad read passed four arguments to a five-parameter reader. Pact does not raise
13
+ * "wrong arity" for that: it PARTIALLY APPLIES and yields a closure, so the caller saw
14
+ * "Evaluation did not reduce to a value" and the buy silently could not be funded.
15
+ *
16
+ * Arguments are supplied BY NAME and rendered in the contract's declared order, so an argument
17
+ * cannot be transposed either -- `C_ChangeOwnership` moved its pool id to last while keeping
18
+ * the same arity, which no count check would catch.
19
+ */
20
+ export function buildCall(key, args, opts = {}) {
21
+ const ep = getEntrypoint(key);
22
+ if (ep.source === "repo-only") {
23
+ throw new RegistryError(`"${key}" is not callable: no live code is deployed for it. ${ep.note ?? ""}`.trim());
24
+ }
25
+ if (ep.execution.supported === false && !opts.allowUnsupported) {
26
+ throw new RegistryError(`"${key}" is not the supported route. ${ep.execution.supportedNote ?? ""} ` +
27
+ `Pass { allowUnsupported: true } to build it anyway.`);
28
+ }
29
+ const declared = ep.params.map(p => p.name);
30
+ const given = Object.keys(args);
31
+ const missing = declared.filter(n => !(n in args));
32
+ const extra = given.filter(n => !declared.includes(n));
33
+ if (missing.length || extra.length) {
34
+ const lines = [`Arguments do not match ${key}.`];
35
+ if (missing.length)
36
+ lines.push(` missing: ${missing.join(", ")}`);
37
+ if (extra.length)
38
+ lines.push(` unexpected: ${extra.join(", ")}`);
39
+ lines.push(` declared: (${ep.params.map(p => `${p.name}:${p.type}`).join(" ")})`);
40
+ // Say it plainly: a short call does not error on chain, it partially applies.
41
+ if (missing.length)
42
+ lines.push(` A short call is NOT rejected by Pact -- it partially applies and yields a closure, ` +
43
+ `so the transaction "succeeds" having done nothing.`);
44
+ throw new RegistryError(lines.join("\n"));
45
+ }
46
+ // A preflight-fed parameter must carry the READ's output. The registry marks these because a
47
+ // client cannot construct them; passing null here would build a call that cannot work.
48
+ for (const fed of ep.execution.fedParams ?? []) {
49
+ if (args[fed.param] === null || args[fed.param] === undefined) {
50
+ throw new RegistryError(`"${fed.param}" is the output of a preflight read and must be supplied.\n` +
51
+ ` run: ${fed.producedBy.join(" or ")}\n` +
52
+ ` ${ep.execution.note}`);
53
+ }
54
+ }
55
+ const ns = opts.namespace ?? namespace;
56
+ const rendered = ep.params.map(p => formatForType(args[p.name], p.type));
57
+ return `(${ns}.${key} ${rendered.join(" ")})`;
58
+ }
59
+ /**
60
+ * Build the cost-preview (`INFO_`) call for an entrypoint.
61
+ *
62
+ * The preview lives on a DIFFERENT module by design, and assuming otherwise is how a consumer
63
+ * ended up calling INFO-ZERO for previews that had moved to INFO-ONE. The pairing is recorded,
64
+ * not guessed.
65
+ */
66
+ export function buildPreviewCall(key, args, opts = {}) {
67
+ const ep = getEntrypoint(key);
68
+ if (!ep.preview) {
69
+ throw new RegistryError(`"${key}" has no cost preview. ${ep.previewMissing ?? ""}`.trim());
70
+ }
71
+ const pv = registry.previews[ep.preview];
72
+ const declared = pv.params.map(p => p.name);
73
+ const missing = declared.filter(n => !(n in args));
74
+ if (missing.length) {
75
+ throw new RegistryError(`Preview ${ep.preview} is missing: ${missing.join(", ")}\n` +
76
+ ` declared: (${pv.params.map(p => `${p.name}:${p.type}`).join(" ")})\n` +
77
+ ` note: a preview's parameters are NOT always the entrypoint's -- it may omit a ` +
78
+ `ceiling the user chooses, or name the same slot differently.`);
79
+ }
80
+ const ns = opts.namespace ?? namespace;
81
+ const rendered = pv.params.map(p => formatForType(args[p.name], p.type));
82
+ return `(${ns}.${ep.preview} ${rendered.join(" ")})`;
83
+ }
84
+ /** Build the example call from the registry's ghost values. Illustrative, never submittable. */
85
+ export function buildGhostCall(key) {
86
+ const ep = getEntrypoint(key);
87
+ const fed = new Set((ep.execution.fedParams ?? []).map(f => f.param));
88
+ const args = {};
89
+ for (const p of ep.params) {
90
+ args[p.name] = fed.has(p.name)
91
+ ? `<from ${(ep.execution.preflight ?? ["preflight"]).join(" / ")}>`
92
+ : ep.ghost.args[p.name];
93
+ }
94
+ return {
95
+ code: `(${namespace}.${key} ${ep.params
96
+ .map(p => (fed.has(p.name) ? String(args[p.name]) : formatForType(args[p.name], p.type)))
97
+ .join(" ")})`,
98
+ warning: ep.ghost.warning,
99
+ };
100
+ }
package/dist/caps.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /** One capability, parsed out of what a capability reader returned. */
2
+ export interface ParsedCapability {
3
+ /** fully-qualified, e.g. `coin.TRANSFER` */
4
+ name: string;
5
+ /** positional arguments, already typed for a Kadena signer */
6
+ args: (string | {
7
+ decimal: string;
8
+ })[];
9
+ }
10
+ /**
11
+ * Parse the strings a capability reader returns.
12
+ *
13
+ * `URC_Acquire` returns `[string]`, each wrapped in angle brackets:
14
+ *
15
+ * <(coin.TRANSFER "from" "to" 9.595000000000)>
16
+ *
17
+ * The brackets are the contract's own quoting; they are not part of the capability. Parsing
18
+ * this by hand is where a consumer drops a leg and signs for less than the transaction moves.
19
+ */
20
+ export declare function parseCapability(raw: string): ParsedCapability;
21
+ export declare function parseCapabilities(raws: readonly string[]): ParsedCapability[];
22
+ /** What a caller must do to obtain this entrypoint's extra capabilities. */
23
+ export interface CapabilityRecipe {
24
+ reader: string;
25
+ /** reader parameter -> where its value comes from */
26
+ argsFrom: Record<string, string>;
27
+ readerParams: {
28
+ name: string;
29
+ type: string;
30
+ }[];
31
+ attachTo: string;
32
+ note: string;
33
+ }
34
+ /**
35
+ * The recipe for an entrypoint's required capabilities, or null when GAS_PAYER is enough.
36
+ *
37
+ * Only four entrypoints need this -- the launchpad buys -- and the reader that computes them
38
+ * lives on the SALE module, not on the Talos module the entrypoint belongs to. Looking in the
39
+ * entrypoint's own module finds nothing, which reads as "no capabilities required" rather than
40
+ * as a failed lookup.
41
+ */
42
+ export declare function capabilityRecipe(key: string): CapabilityRecipe | null;
package/dist/caps.js ADDED
@@ -0,0 +1,44 @@
1
+ import { getEntrypoint, RegistryError } from "./registry.js";
2
+ /**
3
+ * Parse the strings a capability reader returns.
4
+ *
5
+ * `URC_Acquire` returns `[string]`, each wrapped in angle brackets:
6
+ *
7
+ * <(coin.TRANSFER "from" "to" 9.595000000000)>
8
+ *
9
+ * The brackets are the contract's own quoting; they are not part of the capability. Parsing
10
+ * this by hand is where a consumer drops a leg and signs for less than the transaction moves.
11
+ */
12
+ export function parseCapability(raw) {
13
+ const cleaned = raw.replace(/^\s*<\s*/, "").replace(/\s*>\s*$/, "").trim();
14
+ const m = cleaned.match(/^\(([^\s()]+)\s+"([^"]*)"\s+"([^"]*)"\s+([\d.]+)\)$/);
15
+ if (!m) {
16
+ throw new RegistryError(`Unrecognised capability string: ${raw}\n` +
17
+ ` expected: <(NAME "from" "to" amount)>`);
18
+ }
19
+ const [, name, from, to, amount] = m;
20
+ return { name, args: [from, to, { decimal: amount }] };
21
+ }
22
+ export function parseCapabilities(raws) {
23
+ return raws.map(parseCapability);
24
+ }
25
+ /**
26
+ * The recipe for an entrypoint's required capabilities, or null when GAS_PAYER is enough.
27
+ *
28
+ * Only four entrypoints need this -- the launchpad buys -- and the reader that computes them
29
+ * lives on the SALE module, not on the Talos module the entrypoint belongs to. Looking in the
30
+ * entrypoint's own module finds nothing, which reads as "no capabilities required" rather than
31
+ * as a failed lookup.
32
+ */
33
+ export function capabilityRecipe(key) {
34
+ const ec = getEntrypoint(key).externalCaps;
35
+ if (!ec)
36
+ return null;
37
+ return {
38
+ reader: ec.computedBy,
39
+ argsFrom: ec.argsFrom,
40
+ readerParams: ec.readerParams,
41
+ attachTo: ec.attachTo,
42
+ note: ec.note,
43
+ };
44
+ }