@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 +49 -0
- package/README.md +168 -0
- package/dist/build.d.ts +41 -0
- package/dist/build.js +100 -0
- package/dist/caps.d.ts +42 -0
- package/dist/caps.js +44 -0
- package/dist/data/registry.json +1 -0
- package/dist/format.d.ts +30 -0
- package/dist/format.js +113 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +15 -0
- package/dist/plan.d.ts +32 -0
- package/dist/plan.js +79 -0
- package/dist/registry.d.ts +34 -0
- package/dist/registry.js +69 -0
- package/dist/types.d.ts +163 -0
- package/dist/types.js +8 -0
- package/package.json +49 -0
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** A guard argument: the NAME of the transaction-data key holding the keyset. */
|
|
2
|
+
export interface KeysetRef {
|
|
3
|
+
readKeyset: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Render a JS value as a Pact literal for a DECLARED type.
|
|
7
|
+
*
|
|
8
|
+
* Every rule here is a bug someone already shipped.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Pact's decimal lexer REJECTS a bare integer in a decimal slot: `5` is an integer literal and
|
|
12
|
+
* will not coerce, so an amount of 5 must be emitted as `5.0`. Number.toString() gives "5".
|
|
13
|
+
* This is the shipped defect the UI's builders call F-SEC-001.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatDecimal(v: unknown): string;
|
|
16
|
+
export declare function formatInteger(v: unknown): string;
|
|
17
|
+
/**
|
|
18
|
+
* Ouronet account ids are GLYPH strings and routinely contain characters that must survive
|
|
19
|
+
* verbatim. Escape only what Pact's string lexer requires -- a backslash and a double quote.
|
|
20
|
+
* Do NOT JSON.stringify: that would \\u-escape every non-ASCII glyph and change the account.
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatString(v: unknown): string;
|
|
23
|
+
export declare function formatBool(v: unknown): string;
|
|
24
|
+
/**
|
|
25
|
+
* Render `value` for a parameter declared as `type`.
|
|
26
|
+
*
|
|
27
|
+
* Pact LIST literals are SPACE-separated, not comma-separated: `[1 2 3]`. A comma-joined list
|
|
28
|
+
* is a different expression and will not parse as the caller intends.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatForType(value: unknown, type: string): string;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { RegistryError } from "./registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* Render a JS value as a Pact literal for a DECLARED type.
|
|
4
|
+
*
|
|
5
|
+
* Every rule here is a bug someone already shipped.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Pact's decimal lexer REJECTS a bare integer in a decimal slot: `5` is an integer literal and
|
|
9
|
+
* will not coerce, so an amount of 5 must be emitted as `5.0`. Number.toString() gives "5".
|
|
10
|
+
* This is the shipped defect the UI's builders call F-SEC-001.
|
|
11
|
+
*/
|
|
12
|
+
export function formatDecimal(v) {
|
|
13
|
+
if (typeof v === "string") {
|
|
14
|
+
if (!/^-?\d+(\.\d+)?$/.test(v))
|
|
15
|
+
throw new RegistryError(`Not a decimal literal: "${v}"`);
|
|
16
|
+
return v.includes(".") ? v : v + ".0";
|
|
17
|
+
}
|
|
18
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
19
|
+
throw new RegistryError(`Expected a decimal, got ${JSON.stringify(v)}`);
|
|
20
|
+
// toFixed(12) rather than toString(): 1e-7 stringifies to "1e-7", which Pact cannot read.
|
|
21
|
+
// Trailing zeros are trimmed back, but at least one digit is always kept after the point.
|
|
22
|
+
const s = v.toFixed(12).replace(/0+$/, "");
|
|
23
|
+
return s.endsWith(".") ? s + "0" : s;
|
|
24
|
+
}
|
|
25
|
+
export function formatInteger(v) {
|
|
26
|
+
if (typeof v === "string" && /^-?\d+$/.test(v))
|
|
27
|
+
return v;
|
|
28
|
+
if (typeof v !== "number" || !Number.isInteger(v))
|
|
29
|
+
throw new RegistryError(`Expected an integer, got ${JSON.stringify(v)}`);
|
|
30
|
+
return String(v);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Ouronet account ids are GLYPH strings and routinely contain characters that must survive
|
|
34
|
+
* verbatim. Escape only what Pact's string lexer requires -- a backslash and a double quote.
|
|
35
|
+
* Do NOT JSON.stringify: that would \\u-escape every non-ASCII glyph and change the account.
|
|
36
|
+
*/
|
|
37
|
+
export function formatString(v) {
|
|
38
|
+
if (typeof v !== "string")
|
|
39
|
+
throw new RegistryError(`Expected a string, got ${JSON.stringify(v)}`);
|
|
40
|
+
return `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
41
|
+
}
|
|
42
|
+
export function formatBool(v) {
|
|
43
|
+
if (typeof v !== "boolean")
|
|
44
|
+
throw new RegistryError(`Expected a bool, got ${JSON.stringify(v)}`);
|
|
45
|
+
return v ? "true" : "false";
|
|
46
|
+
}
|
|
47
|
+
/** Pact object literal: `{ "k": v, ... }`. Keys are always quoted strings. */
|
|
48
|
+
function formatObject(v, type) {
|
|
49
|
+
if (v === null || typeof v !== "object" || Array.isArray(v))
|
|
50
|
+
throw new RegistryError(`Expected an object for ${type}, got ${JSON.stringify(v)}`);
|
|
51
|
+
const body = Object.entries(v)
|
|
52
|
+
.map(([k, val]) => `${formatString(k)}: ${formatLoose(val)}`)
|
|
53
|
+
.join(", ");
|
|
54
|
+
return `{ ${body} }`;
|
|
55
|
+
}
|
|
56
|
+
/** Best-effort for values inside an untyped object, where no declared type is available. */
|
|
57
|
+
function formatLoose(v) {
|
|
58
|
+
if (typeof v === "string")
|
|
59
|
+
return formatString(v);
|
|
60
|
+
if (typeof v === "boolean")
|
|
61
|
+
return formatBool(v);
|
|
62
|
+
if (typeof v === "number")
|
|
63
|
+
return Number.isInteger(v) ? String(v) : formatDecimal(v);
|
|
64
|
+
if (Array.isArray(v))
|
|
65
|
+
return `[${v.map(formatLoose).join(" ")}]`;
|
|
66
|
+
if (v && typeof v === "object")
|
|
67
|
+
return formatObject(v, "object");
|
|
68
|
+
throw new RegistryError(`Cannot render ${JSON.stringify(v)} as a Pact literal`);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Render `value` for a parameter declared as `type`.
|
|
72
|
+
*
|
|
73
|
+
* Pact LIST literals are SPACE-separated, not comma-separated: `[1 2 3]`. A comma-joined list
|
|
74
|
+
* is a different expression and will not parse as the caller intends.
|
|
75
|
+
*/
|
|
76
|
+
export function formatForType(value, type) {
|
|
77
|
+
const t = type.trim();
|
|
78
|
+
if (t.startsWith("[") && t.endsWith("]")) {
|
|
79
|
+
if (!Array.isArray(value))
|
|
80
|
+
throw new RegistryError(`Expected a list for ${t}, got ${JSON.stringify(value)}`);
|
|
81
|
+
const inner = t.slice(1, -1);
|
|
82
|
+
return `[${value.map(v => formatForType(v, inner)).join(" ")}]`;
|
|
83
|
+
}
|
|
84
|
+
switch (t) {
|
|
85
|
+
case "string": return formatString(value);
|
|
86
|
+
case "decimal": return formatDecimal(value);
|
|
87
|
+
case "integer": return formatInteger(value);
|
|
88
|
+
case "bool": return formatBool(value);
|
|
89
|
+
case "time": return formatString(value);
|
|
90
|
+
}
|
|
91
|
+
if (t === "guard" || t.startsWith("keyset")) {
|
|
92
|
+
// A guard is NEVER a literal. The calling convention across Ouronet is a `read-keyset`
|
|
93
|
+
// reference in the code, with the keyset itself in the transaction's `data` under that
|
|
94
|
+
// name -- `(coin.C_UR|TransferAnew "a" "b" (read-keyset "ks") 1.0)`.
|
|
95
|
+
//
|
|
96
|
+
// So this takes the NAME of the data key, not the keyset. Passing `{keys, pred}` is
|
|
97
|
+
// refused rather than serialised: inlining it would type-error at best, and at worst
|
|
98
|
+
// produce something that parses and means something else.
|
|
99
|
+
const name = typeof value === "string" ? value
|
|
100
|
+
: value && typeof value === "object" && typeof value.readKeyset === "string"
|
|
101
|
+
? value.readKeyset
|
|
102
|
+
: null;
|
|
103
|
+
if (name === null) {
|
|
104
|
+
throw new RegistryError(`A \`${t}\` is passed as (read-keyset "<name>"), so this argument is the NAME of the ` +
|
|
105
|
+
`data key -- give "ks" or { readKeyset: "ks" }, and put the keyset itself in the ` +
|
|
106
|
+
`transaction's data under that name. Got ${JSON.stringify(value)}.`);
|
|
107
|
+
}
|
|
108
|
+
return `(read-keyset ${formatString(name)})`;
|
|
109
|
+
}
|
|
110
|
+
if (t === "object" || t.startsWith("object{"))
|
|
111
|
+
return formatObject(value, t);
|
|
112
|
+
throw new RegistryError(`No rendering rule for declared type "${t}".`);
|
|
113
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ouronet/talos-registry — the Ouronet callable surface, generated from the deployed contracts.
|
|
3
|
+
*
|
|
4
|
+
* A consumer supplies VALUES. It never types a function name, an argument order, or an arity.
|
|
5
|
+
*
|
|
6
|
+
* The registry is a BUNDLED SNAPSHOT, pinned at build time. That is deliberate: refreshing at
|
|
7
|
+
* runtime would add a trust surface (whatever a node returns) and a failure mode (offline means
|
|
8
|
+
* no registry) that a pinned artefact does not have. `surfaceHash` identifies exactly which
|
|
9
|
+
* surface a build was compiled against.
|
|
10
|
+
*/
|
|
11
|
+
export { registry, surfaceHash, namespace, RegistryError, getEntrypoint, tryGetEntrypoint, getPreview, entrypointKeys, modules, entrypointsOfModule, resolveByName } from "./registry.js";
|
|
12
|
+
export { buildCall, buildPreviewCall, buildGhostCall } from "./build.js";
|
|
13
|
+
export type { BuildOptions } from "./build.js";
|
|
14
|
+
export { planCall, explainCall } from "./plan.js";
|
|
15
|
+
export type { CallPlan } from "./plan.js";
|
|
16
|
+
export { parseCapability, parseCapabilities, capabilityRecipe } from "./caps.js";
|
|
17
|
+
export type { ParsedCapability, CapabilityRecipe } from "./caps.js";
|
|
18
|
+
export { formatForType, formatDecimal, formatInteger, formatString, formatBool } from "./format.js";
|
|
19
|
+
export type * from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ouronet/talos-registry — the Ouronet callable surface, generated from the deployed contracts.
|
|
3
|
+
*
|
|
4
|
+
* A consumer supplies VALUES. It never types a function name, an argument order, or an arity.
|
|
5
|
+
*
|
|
6
|
+
* The registry is a BUNDLED SNAPSHOT, pinned at build time. That is deliberate: refreshing at
|
|
7
|
+
* runtime would add a trust surface (whatever a node returns) and a failure mode (offline means
|
|
8
|
+
* no registry) that a pinned artefact does not have. `surfaceHash` identifies exactly which
|
|
9
|
+
* surface a build was compiled against.
|
|
10
|
+
*/
|
|
11
|
+
export { registry, surfaceHash, namespace, RegistryError, getEntrypoint, tryGetEntrypoint, getPreview, entrypointKeys, modules, entrypointsOfModule, resolveByName } from "./registry.js";
|
|
12
|
+
export { buildCall, buildPreviewCall, buildGhostCall } from "./build.js";
|
|
13
|
+
export { planCall, explainCall } from "./plan.js";
|
|
14
|
+
export { parseCapability, parseCapabilities, capabilityRecipe } from "./caps.js";
|
|
15
|
+
export { formatForType, formatDecimal, formatInteger, formatString, formatBool } from "./format.js";
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ExecutionMode, OwnershipClause } from "./types.js";
|
|
2
|
+
/** An ordered, human-readable plan for performing one operation correctly. */
|
|
3
|
+
export interface CallPlan {
|
|
4
|
+
key: string;
|
|
5
|
+
mode: ExecutionMode;
|
|
6
|
+
/** reads to run BEFORE building the transaction, in no particular order */
|
|
7
|
+
preflight: string[];
|
|
8
|
+
/** the reader that computes required capabilities, if any */
|
|
9
|
+
capabilityReader: string | null;
|
|
10
|
+
/** accounts whose key the caller must be able to sign for */
|
|
11
|
+
mustSignFor: OwnershipClause[];
|
|
12
|
+
/** true when the gas station pays; "step-0-only" for a defpact */
|
|
13
|
+
sponsored: boolean | "step-0-only";
|
|
14
|
+
/** how many transactions this operation takes, when the registry can say */
|
|
15
|
+
transactions: "one" | "many-parallel" | "many-sequential" | "many-continuation";
|
|
16
|
+
/** the single most important thing to get right */
|
|
17
|
+
warnings: string[];
|
|
18
|
+
/** a supported alternative, when this route is not the supported one */
|
|
19
|
+
preferInstead: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Everything a consumer must do to perform one operation, in order.
|
|
23
|
+
*
|
|
24
|
+
* Reading the registry field by field is possible but invites cherry-picking: the four
|
|
25
|
+
* launchpad buys need a capability read that has nothing to do with their inputs, the ten
|
|
26
|
+
* defpacts need a continuation whose gas the customer pays, and three pagers must not be
|
|
27
|
+
* fired concurrently. Those facts live in different keys, and a consumer who checks one and
|
|
28
|
+
* not the others gets a transaction that submits and does the wrong thing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function planCall(key: string): CallPlan;
|
|
31
|
+
/** Render a plan as text -- for a CLI, a code comment, or a docs page. */
|
|
32
|
+
export declare function explainCall(key: string): string;
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getEntrypoint } from "./registry.js";
|
|
2
|
+
/**
|
|
3
|
+
* Everything a consumer must do to perform one operation, in order.
|
|
4
|
+
*
|
|
5
|
+
* Reading the registry field by field is possible but invites cherry-picking: the four
|
|
6
|
+
* launchpad buys need a capability read that has nothing to do with their inputs, the ten
|
|
7
|
+
* defpacts need a continuation whose gas the customer pays, and three pagers must not be
|
|
8
|
+
* fired concurrently. Those facts live in different keys, and a consumer who checks one and
|
|
9
|
+
* not the others gets a transaction that submits and does the wrong thing.
|
|
10
|
+
*/
|
|
11
|
+
export function planCall(key) {
|
|
12
|
+
const ep = getEntrypoint(key);
|
|
13
|
+
const ex = ep.execution;
|
|
14
|
+
const warnings = [];
|
|
15
|
+
const transactions = ex.mode === "defpact" ? "many-continuation"
|
|
16
|
+
: ex.mode === "indirect-parallel" ? "many-parallel"
|
|
17
|
+
: ex.mode === "indirect-sequential" ? "many-sequential"
|
|
18
|
+
: "one";
|
|
19
|
+
if (ex.mode === "indirect-sequential")
|
|
20
|
+
warnings.push("STRICTLY ORDERED. Each call advances a stored cursor, so firing pages concurrently " +
|
|
21
|
+
"races it -- submit one, confirm, then the next, until the preflight reports done.");
|
|
22
|
+
if (ex.mode === "indirect-parallel")
|
|
23
|
+
warnings.push("Order-independent: the slices MAY be submitted together. Cut them from the preflight; " +
|
|
24
|
+
"do not invent the partition.");
|
|
25
|
+
if (ex.mode === "defpact") {
|
|
26
|
+
warnings.push(`MULTI-TRANSACTION by continuation: ${ex.steps ?? "?"} steps. Step 0 runs on submit; the ` +
|
|
27
|
+
"rest are `cont` payloads against the pact id the first result returns. Submitting once " +
|
|
28
|
+
"and reporting success leaves the operation half-finished.");
|
|
29
|
+
warnings.push("CONTINUATIONS ARE NOT GAS-SPONSORED. Chainweb injects `exec-code` only for `exec` " +
|
|
30
|
+
"payloads, and DALOS.GAS_PAYER reads it eagerly, so it cannot evaluate a `cont`. " +
|
|
31
|
+
"Step 0 is sponsored; the CUSTOMER account pays for every later step.");
|
|
32
|
+
}
|
|
33
|
+
if (ep.externalCaps)
|
|
34
|
+
warnings.push(`Requires capabilities beyond GAS_PAYER. Call ${ep.externalCaps.computedBy} and attach ` +
|
|
35
|
+
`what it returns -- do NOT recompute the amounts; they depend on live price and a ` +
|
|
36
|
+
`slippage pad, and a self-derived figure signs a capability the contract will not match.`);
|
|
37
|
+
for (const c of ep.ownership.requires ?? [])
|
|
38
|
+
if (c.when === "CONDITIONAL")
|
|
39
|
+
warnings.push(`Ownership of ${c.account} is CONDITIONAL: ${c.conditionalOn ?? ""}`.trim());
|
|
40
|
+
if (ep.ownership.unmapped?.length)
|
|
41
|
+
warnings.push(`Ownership enforces exist deeper in the call tree that the registry could not map to this ` +
|
|
42
|
+
`entrypoint's parameters (${ep.ownership.unmapped.join(", ")}). Treat the list as ` +
|
|
43
|
+
`INCOMPLETE, not as "nothing else is required".`);
|
|
44
|
+
if (!ep.preview)
|
|
45
|
+
warnings.push(`No cost preview exists. ${ep.previewMissing ?? ""}`.trim());
|
|
46
|
+
return {
|
|
47
|
+
key,
|
|
48
|
+
mode: ex.mode,
|
|
49
|
+
preflight: (ex.preflight ?? []).filter((p) => typeof p === "string"),
|
|
50
|
+
capabilityReader: ex.capabilityPreflight ?? null,
|
|
51
|
+
mustSignFor: ep.ownership.requires ?? [],
|
|
52
|
+
sponsored: ep.sponsorship.sponsored,
|
|
53
|
+
transactions,
|
|
54
|
+
warnings,
|
|
55
|
+
preferInstead: ex.preferInstead ?? null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Render a plan as text -- for a CLI, a code comment, or a docs page. */
|
|
59
|
+
export function explainCall(key) {
|
|
60
|
+
const p = planCall(key);
|
|
61
|
+
const L = [`${p.key} [${p.mode}, ${p.transactions} transaction(s)]`];
|
|
62
|
+
if (p.preferInstead)
|
|
63
|
+
L.push(` PREFER INSTEAD: ${p.preferInstead}`);
|
|
64
|
+
L.push(` gas: ${p.sponsored === true ? "sponsored by the Ouronet gas station"
|
|
65
|
+
: p.sponsored === "step-0-only" ? "step 0 sponsored; continuations paid by the customer"
|
|
66
|
+
: "NOT sponsored -- the caller pays"}`);
|
|
67
|
+
if (p.preflight.length)
|
|
68
|
+
L.push(` preflight: ${p.preflight.join(", ")}`);
|
|
69
|
+
if (p.capabilityReader)
|
|
70
|
+
L.push(` capabilities from: ${p.capabilityReader}`);
|
|
71
|
+
if (p.mustSignFor.length) {
|
|
72
|
+
L.push(" must sign for:");
|
|
73
|
+
for (const c of p.mustSignFor)
|
|
74
|
+
L.push(` - ${c.account} [${c.when}] -- ${c.meaning}`);
|
|
75
|
+
}
|
|
76
|
+
for (const w of p.warnings)
|
|
77
|
+
L.push(` ! ${w}`);
|
|
78
|
+
return L.join("\n");
|
|
79
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Entrypoint, Preview, RegistryDoc } from "./types.js";
|
|
2
|
+
/** The bundled snapshot. Pinned at build time; never fetched at runtime. */
|
|
3
|
+
export declare const registry: RegistryDoc;
|
|
4
|
+
/** Identifies exactly which surface this build was compiled against. */
|
|
5
|
+
export declare const surfaceHash: string;
|
|
6
|
+
export declare const namespace: string;
|
|
7
|
+
/** Thrown for every refusal in this package, so a consumer can catch one class. */
|
|
8
|
+
export declare class RegistryError extends Error {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Look up an entrypoint by its FULL `MODULE.function` key.
|
|
13
|
+
*
|
|
14
|
+
* Throws on an unknown key rather than returning undefined, and the message lists near
|
|
15
|
+
* matches -- because the failure this package exists to prevent is a name that no longer
|
|
16
|
+
* resolves, and on chain that surfaces as a RESOLUTION error which `try` cannot catch and
|
|
17
|
+
* consumers render as missing data.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getEntrypoint(key: string): Entrypoint;
|
|
20
|
+
export declare function tryGetEntrypoint(key: string): Entrypoint | undefined;
|
|
21
|
+
export declare function getPreview(key: string): Preview | undefined;
|
|
22
|
+
export declare function entrypointKeys(): string[];
|
|
23
|
+
export declare function modules(): string[];
|
|
24
|
+
export declare function entrypointsOfModule(module: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a BARE function name to its full key.
|
|
27
|
+
*
|
|
28
|
+
* Refuses when the name is ambiguous instead of picking one. Four SWP liquidity names resolve
|
|
29
|
+
* to two modules with IDENTICAL signatures and DIFFERENT execution modes -- one single
|
|
30
|
+
* transaction, one defpact -- so guessing is silent in both directions: address the defpact
|
|
31
|
+
* believing it direct and the operation sits half-finished; address the direct one believing
|
|
32
|
+
* it a defpact and there is no pact for the continuation to continue.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveByName(fn: string): string;
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import data from "./data/registry.json" with { type: "json" };
|
|
2
|
+
/** The bundled snapshot. Pinned at build time; never fetched at runtime. */
|
|
3
|
+
export const registry = data;
|
|
4
|
+
/** Identifies exactly which surface this build was compiled against. */
|
|
5
|
+
export const surfaceHash = registry.surfaceHash;
|
|
6
|
+
export const namespace = registry.namespace;
|
|
7
|
+
/** Thrown for every refusal in this package, so a consumer can catch one class. */
|
|
8
|
+
export class RegistryError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "RegistryError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Look up an entrypoint by its FULL `MODULE.function` key.
|
|
16
|
+
*
|
|
17
|
+
* Throws on an unknown key rather than returning undefined, and the message lists near
|
|
18
|
+
* matches -- because the failure this package exists to prevent is a name that no longer
|
|
19
|
+
* resolves, and on chain that surfaces as a RESOLUTION error which `try` cannot catch and
|
|
20
|
+
* consumers render as missing data.
|
|
21
|
+
*/
|
|
22
|
+
export function getEntrypoint(key) {
|
|
23
|
+
const hit = registry.entrypoints[key];
|
|
24
|
+
if (hit)
|
|
25
|
+
return hit;
|
|
26
|
+
const [, fn = key] = key.split(/\.(.*)/s);
|
|
27
|
+
const near = Object.keys(registry.entrypoints).filter(k => k.endsWith("." + fn));
|
|
28
|
+
throw new RegistryError(near.length
|
|
29
|
+
? `Unknown entrypoint "${key}". The same function exists on: ${near.join(", ")}. ` +
|
|
30
|
+
`Entrypoints are addressed MODULE.function.`
|
|
31
|
+
: `Unknown entrypoint "${key}". It is not in surface ${surfaceHash}; either the name is ` +
|
|
32
|
+
`stale or the module is not deployed.`);
|
|
33
|
+
}
|
|
34
|
+
export function tryGetEntrypoint(key) {
|
|
35
|
+
return registry.entrypoints[key];
|
|
36
|
+
}
|
|
37
|
+
export function getPreview(key) {
|
|
38
|
+
return registry.previews[key];
|
|
39
|
+
}
|
|
40
|
+
export function entrypointKeys() {
|
|
41
|
+
return Object.keys(registry.entrypoints);
|
|
42
|
+
}
|
|
43
|
+
export function modules() {
|
|
44
|
+
return [...new Set(entrypointKeys().map(k => k.split(".", 1)[0]))].sort();
|
|
45
|
+
}
|
|
46
|
+
export function entrypointsOfModule(module) {
|
|
47
|
+
return entrypointKeys().filter(k => k.startsWith(module + "."));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a BARE function name to its full key.
|
|
51
|
+
*
|
|
52
|
+
* Refuses when the name is ambiguous instead of picking one. Four SWP liquidity names resolve
|
|
53
|
+
* to two modules with IDENTICAL signatures and DIFFERENT execution modes -- one single
|
|
54
|
+
* transaction, one defpact -- so guessing is silent in both directions: address the defpact
|
|
55
|
+
* believing it direct and the operation sits half-finished; address the direct one believing
|
|
56
|
+
* it a defpact and there is no pact for the continuation to continue.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveByName(fn) {
|
|
59
|
+
const hits = entrypointKeys().filter(k => k.split(/\.(.*)/s)[1] === fn);
|
|
60
|
+
if (hits.length === 1)
|
|
61
|
+
return hits[0];
|
|
62
|
+
if (hits.length === 0)
|
|
63
|
+
throw new RegistryError(`No entrypoint named "${fn}".`);
|
|
64
|
+
const detail = hits
|
|
65
|
+
.map(k => `${k} (${registry.entrypoints[k].execution.mode})`)
|
|
66
|
+
.join(", ");
|
|
67
|
+
throw new RegistryError(`"${fn}" is ambiguous -- it resolves to ${hits.length} modules: ${detail}. ` +
|
|
68
|
+
`Address it as MODULE.function.`);
|
|
69
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of Deploy/OURONET-REGISTRY.json.
|
|
3
|
+
*
|
|
4
|
+
* Generated by REPL/tools/_registry.py in the Pact repo, which reads the CHAIN via
|
|
5
|
+
* `describe-module` and falls back to the sources only for modules not yet deployed. So the
|
|
6
|
+
* authority here is what is CALLABLE, not what someone intends to deploy.
|
|
7
|
+
*/
|
|
8
|
+
/** One parameter, in declaration order. Position is the calling convention. */
|
|
9
|
+
export interface Param {
|
|
10
|
+
name: string;
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
/** How a call executes -- see `ExecutionMode`. */
|
|
14
|
+
export type ExecutionMode =
|
|
15
|
+
/** the provided inputs suffice; nothing is read off-chain first */
|
|
16
|
+
"direct"
|
|
17
|
+
/** ONE transaction, but a preflight read supplies some argument */
|
|
18
|
+
| "indirect-single"
|
|
19
|
+
/** a preflight is cut into slices, one per transaction, ORDER-INDEPENDENT */
|
|
20
|
+
| "indirect-parallel"
|
|
21
|
+
/** a preflight reports progress; each call advances a stored cursor. STRICTLY ORDERED */
|
|
22
|
+
| "indirect-sequential"
|
|
23
|
+
/** multi-transaction by CONTINUATION: step 0 on submit, the rest are `cont` payloads */
|
|
24
|
+
| "defpact";
|
|
25
|
+
export interface FedParam {
|
|
26
|
+
param: string;
|
|
27
|
+
type: string;
|
|
28
|
+
/** the read(s) that produce this value. A client cannot construct it. */
|
|
29
|
+
producedBy: string[];
|
|
30
|
+
/** for a fed slice, what cuts the preflight's output into per-transaction slices */
|
|
31
|
+
slicedBy: string[] | null;
|
|
32
|
+
}
|
|
33
|
+
export interface Continuation {
|
|
34
|
+
payload: "cont";
|
|
35
|
+
pactId: string;
|
|
36
|
+
step: string;
|
|
37
|
+
rollback: string;
|
|
38
|
+
/** empty for every defpact in Ouronet: no step reads client input */
|
|
39
|
+
data: Record<string, never>;
|
|
40
|
+
dataNote: string;
|
|
41
|
+
/** the surprise: continuations are NOT gas-sponsored. Step 0 is. */
|
|
42
|
+
gas: string;
|
|
43
|
+
}
|
|
44
|
+
export interface Execution {
|
|
45
|
+
mode: ExecutionMode;
|
|
46
|
+
shape?: string;
|
|
47
|
+
note: string;
|
|
48
|
+
/** reads to run BEFORE building the transaction */
|
|
49
|
+
preflight?: string[] | null;
|
|
50
|
+
fedParams?: FedParam[] | null;
|
|
51
|
+
preflightUnresolved?: boolean;
|
|
52
|
+
/** defpact only */
|
|
53
|
+
pact?: string;
|
|
54
|
+
steps?: number;
|
|
55
|
+
rollbackSteps?: number;
|
|
56
|
+
inputs?: ExecutionMode;
|
|
57
|
+
continuation?: Continuation;
|
|
58
|
+
/** false when a supported single-transaction twin exists -- see preferInstead */
|
|
59
|
+
supported?: boolean;
|
|
60
|
+
preferInstead?: string;
|
|
61
|
+
supportedNote?: string;
|
|
62
|
+
/** the reader that computes this call's required capabilities */
|
|
63
|
+
capabilityPreflight?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface OwnershipClause {
|
|
66
|
+
/** the account, or "owner of `<entity>`" when a lookup is needed */
|
|
67
|
+
account: string;
|
|
68
|
+
via: "parameter" | "reader";
|
|
69
|
+
/** ALWAYS = unconditional. CONDITIONAL = binds on one branch only. */
|
|
70
|
+
when: "ALWAYS" | "CONDITIONAL";
|
|
71
|
+
meaning: string;
|
|
72
|
+
reader?: string;
|
|
73
|
+
subject?: string;
|
|
74
|
+
conditionalOn?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface Ownership {
|
|
77
|
+
resolved: boolean;
|
|
78
|
+
requires?: OwnershipClause[];
|
|
79
|
+
/** subjects found deeper in the call tree that could not be mapped to a parameter */
|
|
80
|
+
unmapped?: string[] | null;
|
|
81
|
+
unmappedNote?: string;
|
|
82
|
+
reason?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface Sponsorship {
|
|
85
|
+
/** true, false, or "step-0-only" for a defpact whose continuations the customer pays */
|
|
86
|
+
sponsored: boolean | "step-0-only";
|
|
87
|
+
sponsor?: string;
|
|
88
|
+
cap?: string;
|
|
89
|
+
capArgs?: Param[];
|
|
90
|
+
suppliedBy?: string | null;
|
|
91
|
+
conditional?: string | null;
|
|
92
|
+
stepsNote?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface ExternalCaps {
|
|
95
|
+
required: true;
|
|
96
|
+
/** the READER that computes the capability arguments -- it IS the formula */
|
|
97
|
+
computedBy: string;
|
|
98
|
+
readerParams: Param[];
|
|
99
|
+
/** reader parameter -> this entrypoint's parameter, or a note when client-supplied */
|
|
100
|
+
argsFrom: Record<string, string>;
|
|
101
|
+
returns: string;
|
|
102
|
+
format: string;
|
|
103
|
+
parse: string;
|
|
104
|
+
attachTo: string;
|
|
105
|
+
note: string;
|
|
106
|
+
}
|
|
107
|
+
export interface Ghost {
|
|
108
|
+
/** example arguments by parameter name. `null` means "obtain from the preflight". */
|
|
109
|
+
args: Record<string, unknown>;
|
|
110
|
+
source: string;
|
|
111
|
+
notes?: Record<string, string>;
|
|
112
|
+
unresolved?: string[];
|
|
113
|
+
unresolvedNote?: string;
|
|
114
|
+
warning: string;
|
|
115
|
+
}
|
|
116
|
+
export interface Entrypoint {
|
|
117
|
+
params: Param[];
|
|
118
|
+
/** declared return type, or null when the signature does not pin one */
|
|
119
|
+
returns: string | null;
|
|
120
|
+
returnsNote?: string;
|
|
121
|
+
source: "deployed" | "repo-only";
|
|
122
|
+
modulePath: string;
|
|
123
|
+
moduleHash?: string;
|
|
124
|
+
callable?: false;
|
|
125
|
+
note?: string;
|
|
126
|
+
ownership: Ownership;
|
|
127
|
+
sponsorship: Sponsorship;
|
|
128
|
+
execution: Execution;
|
|
129
|
+
externalCaps?: ExternalCaps;
|
|
130
|
+
ghost: Ghost;
|
|
131
|
+
/** the INFO_ reader that prices this call */
|
|
132
|
+
preview?: string;
|
|
133
|
+
previewVia?: string;
|
|
134
|
+
previewMissing?: string;
|
|
135
|
+
}
|
|
136
|
+
export interface Preview {
|
|
137
|
+
params: Param[];
|
|
138
|
+
returns: string | null;
|
|
139
|
+
source: "deployed" | "repo-only";
|
|
140
|
+
modulePath: string;
|
|
141
|
+
moduleHash?: string;
|
|
142
|
+
}
|
|
143
|
+
export interface Divergence {
|
|
144
|
+
module: string;
|
|
145
|
+
function: string;
|
|
146
|
+
deployed: Param[] | null;
|
|
147
|
+
repo: Param[] | null;
|
|
148
|
+
}
|
|
149
|
+
export interface RegistryDoc {
|
|
150
|
+
note: string;
|
|
151
|
+
namespace: string;
|
|
152
|
+
generatedFrom: "chain+repo" | "repo-only";
|
|
153
|
+
entrypoints: Record<string, Entrypoint>;
|
|
154
|
+
previews: Record<string, Preview>;
|
|
155
|
+
/** bare names that resolve to more than one module -- ALWAYS address MODULE.function */
|
|
156
|
+
nameCollisions: Record<string, {
|
|
157
|
+
module: string;
|
|
158
|
+
mode: ExecutionMode;
|
|
159
|
+
}[]>;
|
|
160
|
+
divergences: Divergence[];
|
|
161
|
+
notDeployed: string[];
|
|
162
|
+
surfaceHash: string;
|
|
163
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of Deploy/OURONET-REGISTRY.json.
|
|
3
|
+
*
|
|
4
|
+
* Generated by REPL/tools/_registry.py in the Pact repo, which reads the CHAIN via
|
|
5
|
+
* `describe-module` and falls back to the sources only for modules not yet deployed. So the
|
|
6
|
+
* authority here is what is CALLABLE, not what someone intends to deploy.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ouronet/talos-registry",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "The Ouronet callable surface, generated from the deployed contracts. Supply values; never type a function name.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./types": {
|
|
14
|
+
"types": "./dist/types.d.ts",
|
|
15
|
+
"import": "./dist/types.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"sync": "node scripts/sync-registry.mjs",
|
|
25
|
+
"build": "npm run sync && tsc -p tsconfig.build.json",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"clean": "rimraf dist",
|
|
30
|
+
"sync:check": "node scripts/sync-registry.mjs --check"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"ouronet",
|
|
34
|
+
"pact",
|
|
35
|
+
"kadena",
|
|
36
|
+
"stoachain",
|
|
37
|
+
"registry"
|
|
38
|
+
],
|
|
39
|
+
"license": "UNLICENSED",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "^5.6.0",
|
|
42
|
+
"vitest": "^2.1.0",
|
|
43
|
+
"rimraf": "^6.0.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"registry": "https://registry.npmjs.org",
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|