@mcsherrylabs/evolver-mcp 0.0.0-stage → 0.8.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/amount.d.ts +20 -0
- package/dist/amount.d.ts.map +1 -0
- package/dist/amount.js +44 -0
- package/dist/amount.js.map +1 -0
- package/dist/bootstrap.d.ts +264 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +901 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/channel.d.ts +78 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +288 -0
- package/dist/channel.js.map +1 -0
- package/dist/claim.d.ts +48 -0
- package/dist/claim.d.ts.map +1 -0
- package/dist/claim.js +81 -0
- package/dist/claim.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +34 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +39 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/content.d.ts +51 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +60 -0
- package/dist/content.js.map +1 -0
- package/dist/group.d.ts +60 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +204 -0
- package/dist/group.js.map +1 -0
- package/dist/identity.d.ts +87 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +230 -0
- package/dist/identity.js.map +1 -0
- package/dist/inbox.d.ts +30 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +95 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +206 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +39 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +83 -0
- package/dist/mcp.js.map +1 -0
- package/dist/read.d.ts +175 -0
- package/dist/read.d.ts.map +1 -0
- package/dist/read.js +219 -0
- package/dist/read.js.map +1 -0
- package/dist/result.d.ts +110 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +97 -0
- package/dist/result.js.map +1 -0
- package/dist/signer.d.ts +37 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +76 -0
- package/dist/signer.js.map +1 -0
- package/dist/tools.d.ts +44 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +642 -0
- package/dist/tools.js.map +1 -0
- package/dist/write.d.ts +173 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +322 -0
- package/dist/write.js.map +1 -0
- package/package.json +55 -4
- package/README.md +0 -3
package/dist/cli.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Package `bin` entry (`evolver-mcp`). Thin dispatcher over the two faces of the program:
|
|
2
|
+
//
|
|
3
|
+
// - `--bootstrap` → the Phase 7 first-run bootstrap (mint/PoP/claim/register).
|
|
4
|
+
// - anything else → the ordinary stdio MCP server (`main()` in index.ts).
|
|
5
|
+
//
|
|
6
|
+
// The reason this file exists (instead of naming dist/index.js as the bin) is package mode:
|
|
7
|
+
// when the bootstrap runs out of a published install, the registration it derives must resolve
|
|
8
|
+
// the PACKAGE (`npx -y @mcsherrylabs/evolver-mcp`), not a checkout realpath that a fresh box
|
|
9
|
+
// never has. This wrapper injects `EVOLVER_MCP_ENTRY_COMMAND` before delegating so the bootstrap
|
|
10
|
+
// emits a package-resolving registration, while the unchanged default (env unset) keeps the
|
|
11
|
+
// checkout-entry form Phase 7 locked in.
|
|
12
|
+
import { main } from "./index.js";
|
|
13
|
+
import { hasBootstrapFlag, runBootstrap } from "./bootstrap.js";
|
|
14
|
+
const PACKAGE_NAME = "@mcsherrylabs/evolver-mcp";
|
|
15
|
+
const BOOTSTRAP_ENTRY_COMMAND = () => `npx -y ${PACKAGE_NAME}`;
|
|
16
|
+
await (async () => {
|
|
17
|
+
if (hasBootstrapFlag(process.argv)) {
|
|
18
|
+
// Package mode: only if the operator has not already chosen an entry command, default the
|
|
19
|
+
// registration to `npx -y @mcsherrylabs/evolver-mcp` so the printed `claude mcp add` resolves
|
|
20
|
+
// the published package on the second launch.
|
|
21
|
+
const env = { ...process.env, EVOLVER_BOOTSTRAP_CWD: process.cwd() };
|
|
22
|
+
if (env.EVOLVER_MCP_ENTRY_COMMAND === undefined) {
|
|
23
|
+
env.EVOLVER_MCP_ENTRY_COMMAND = BOOTSTRAP_ENTRY_COMMAND();
|
|
24
|
+
}
|
|
25
|
+
const r = await runBootstrap(process.argv, env);
|
|
26
|
+
if (r.toStderr)
|
|
27
|
+
console.error(r.output);
|
|
28
|
+
else
|
|
29
|
+
process.stdout.write(r.output);
|
|
30
|
+
process.exit(r.exitCode);
|
|
31
|
+
}
|
|
32
|
+
await main();
|
|
33
|
+
})();
|
|
34
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,EAAE;AACF,gGAAgG;AAChG,2FAA2F;AAC3F,EAAE;AACF,4FAA4F;AAC5F,+FAA+F;AAC/F,6FAA6F;AAC7F,iGAAiG;AACjG,4FAA4F;AAC5F,yCAAyC;AAEzC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhE,MAAM,YAAY,GAAG,2BAA2B,CAAC;AACjD,MAAM,uBAAuB,GAAG,GAAG,EAAE,CAAC,UAAU,YAAY,EAAE,CAAC;AAE/D,MAAM,CAAC,KAAK,IAAI,EAAE;IAChB,IAAI,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,0FAA0F;QAC1F,8FAA8F;QAC9F,8CAA8C;QAC9C,MAAM,GAAG,GAAsB,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,qBAAqB,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;QACxF,IAAI,GAAG,CAAC,yBAAyB,KAAK,SAAS,EAAE,CAAC;YAChD,GAAG,CAAC,yBAAyB,GAAG,uBAAuB,EAAE,CAAC;QAC5D,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;;YACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC,EAAE,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** How to trust the endpoint. A node's own API presents a self-signed identity certificate; a
|
|
2
|
+
* reverse proxy in front of one presents an ordinary trusted certificate. Both are real
|
|
3
|
+
* deployments here, so this cannot be left unstated. */
|
|
4
|
+
export interface TlsOptions {
|
|
5
|
+
readonly caFile?: string;
|
|
6
|
+
/** Accept a certificate that does not chain to a trusted root — for a node's own listener. */
|
|
7
|
+
readonly insecure?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface EvolverConfig {
|
|
10
|
+
readonly url: string;
|
|
11
|
+
readonly identity: string;
|
|
12
|
+
readonly tls?: TlsOptions;
|
|
13
|
+
/** The event processor's read server endpoint (the inbox lives there, not on the node). The
|
|
14
|
+
* processor is a SEPARATE process from the node this config's `url` points at, and it is
|
|
15
|
+
* read-only and no-auth (FR-034): reaching it is the access control, and the MCP only ever
|
|
16
|
+
* GETs `/inbox` and `/inbox/<ledgerId>`. It is OPTIONAL — an unset processor answers `inbox`
|
|
17
|
+
* calls as `misconfigured`, never a guess. It widens NOTHING on the signing boundary: the
|
|
18
|
+
* processor can never sign and never sees key material (T-6-01). */
|
|
19
|
+
readonly processorUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
export type ConfigResult = {
|
|
22
|
+
readonly ok: true;
|
|
23
|
+
readonly config: EvolverConfig;
|
|
24
|
+
} | {
|
|
25
|
+
readonly ok: false;
|
|
26
|
+
readonly error: string;
|
|
27
|
+
};
|
|
28
|
+
/** The default chain is a node on this host. Addressing a remote one is an explicit choice. */
|
|
29
|
+
export declare const DEFAULT_URL = "http://127.0.0.1:8440";
|
|
30
|
+
export declare const isLocal: (url: string) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the raw config file contents to a validated `EvolverConfig`.
|
|
33
|
+
*
|
|
34
|
+
* Pure: no filesystem, no environment. `loadConfig` does the reading and calls this.
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveConfig(raw: unknown): ConfigResult;
|
|
37
|
+
/** Read the single config file and apply the rule. The only part that touches a filesystem. */
|
|
38
|
+
export declare function loadConfig(path: string): ConfigResult;
|
|
39
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA;;yDAEyD;AACzD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B;;;;;yEAKqE;IACrE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,+FAA+F;AAC/F,eAAO,MAAM,WAAW,0BAA0B,CAAC;AAEnD,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,OAKrC,CAAC;AASF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAsCxD;AAOD,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAErD"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Configuration: the single explicit config file, and the rule that governs it.
|
|
2
|
+
//
|
|
3
|
+
// One file only — a spawned-per-session stdio server has no project directory to anchor a
|
|
4
|
+
// two-file project/home precedence to (D-06). The file holds the node endpoint, the identity
|
|
5
|
+
// name, and TLS trust — and NOTHING that could be key material (D-07). A config that names a key
|
|
6
|
+
// is refused; reading a repository is never allowed to change which key signs.
|
|
7
|
+
//
|
|
8
|
+
// The rule is a PURE function (`resolveConfig`) with the file reading kept separate, so it can be
|
|
9
|
+
// exercised without a filesystem. A rule only checked by hand on a real box is a rule that drifts.
|
|
10
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
|
+
/** The default chain is a node on this host. Addressing a remote one is an explicit choice. */
|
|
12
|
+
export const DEFAULT_URL = "http://127.0.0.1:8440";
|
|
13
|
+
export const isLocal = (url) => {
|
|
14
|
+
try {
|
|
15
|
+
const h = new URL(url).hostname;
|
|
16
|
+
return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "[::1]";
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const asRecord = (v) => typeof v === "object" && v !== null ? v : undefined;
|
|
23
|
+
/** Keys that must never appear in the config file — a key path (or any other key-material
|
|
24
|
+
* carrier) has no place in a committable file. Rejected outright, not ignored. */
|
|
25
|
+
const FORBIDDEN_KEYS = ["keyLocation", "keyPath", "contentUrl"];
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the raw config file contents to a validated `EvolverConfig`.
|
|
28
|
+
*
|
|
29
|
+
* Pure: no filesystem, no environment. `loadConfig` does the reading and calls this.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveConfig(raw) {
|
|
32
|
+
const r = asRecord(raw);
|
|
33
|
+
if (r === undefined)
|
|
34
|
+
return { ok: false, error: "configuration file must contain a JSON object" };
|
|
35
|
+
for (const key of FORBIDDEN_KEYS) {
|
|
36
|
+
if (r[key] !== undefined)
|
|
37
|
+
return { ok: false, error: "config must not carry key material" };
|
|
38
|
+
}
|
|
39
|
+
const identity = r["identity"];
|
|
40
|
+
if (typeof identity !== "string" || identity.length === 0)
|
|
41
|
+
return { ok: false, error: "configuration has no `identity` (a non-empty string is required)" };
|
|
42
|
+
const url = typeof r["url"] === "string" && r["url"].length > 0 ? r["url"] : DEFAULT_URL;
|
|
43
|
+
const tls = asRecord(r["tls"]);
|
|
44
|
+
// The processor URL is a distinct, OPTIONAL endpoint name (NOT a signing/content path), so it
|
|
45
|
+
// is never added to FORBIDDEN_KEYS. It is honoured only when present as a non-empty string;
|
|
46
|
+
// anything else is ignored (the inbox is optional and answers `misconfigured` per-call when
|
|
47
|
+
// unset, rather than failing config resolution).
|
|
48
|
+
const rawProcessorUrl = r["processorUrl"];
|
|
49
|
+
const trimmedProcessorUrl = typeof rawProcessorUrl === "string" ? rawProcessorUrl.trim() : "";
|
|
50
|
+
// Normalize so a trailing slash (or slashes) never yields a double-slash `/inbox` path that 404s
|
|
51
|
+
// as a misleading `unreachable` — mirroring the reference meerkatUrl's `replace(/\/$/, "")`.
|
|
52
|
+
const processorUrl = trimmedProcessorUrl.length > 0 ? trimmedProcessorUrl.replace(/\/+$/, "") : undefined;
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
config: {
|
|
56
|
+
url,
|
|
57
|
+
identity,
|
|
58
|
+
...(tls ? { tls } : {}),
|
|
59
|
+
...(processorUrl ? { processorUrl } : {}),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const readJson = (file) => {
|
|
64
|
+
if (!existsSync(file))
|
|
65
|
+
return undefined;
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(readFileSync(file, "utf8"));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/** Read the single config file and apply the rule. The only part that touches a filesystem. */
|
|
74
|
+
export function loadConfig(path) {
|
|
75
|
+
return resolveConfig(readJson(path));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ToolResult } from "./result.js";
|
|
2
|
+
/** Only what the content verbs need from a client. `NodeClient` satisfies it structurally, which
|
|
3
|
+
* is what lets the rules be exercised against a fake — a rule only checked against a live node
|
|
4
|
+
* is a rule that drifts. */
|
|
5
|
+
export interface ContentClient {
|
|
6
|
+
postContent(bytes: Uint8Array, opts?: {
|
|
7
|
+
authorizedPrincipals?: string[];
|
|
8
|
+
}): Promise<{
|
|
9
|
+
key: string;
|
|
10
|
+
size: number;
|
|
11
|
+
}>;
|
|
12
|
+
getContent(key: string): Promise<Uint8Array | null>;
|
|
13
|
+
}
|
|
14
|
+
/** What the content verbs need from the world: the SEPARATE content client (built against
|
|
15
|
+
* `contentBaseUrl`, never the chain client) plus that content endpoint name for refusals.
|
|
16
|
+
* Injected so the whole flow is exercisable against a structural fake, exactly like `ChainReads`
|
|
17
|
+
* (read.ts) and `ClaimFn` (claim.ts). */
|
|
18
|
+
export interface ContentDeps {
|
|
19
|
+
readonly client: ContentClient;
|
|
20
|
+
readonly contentUrl: string;
|
|
21
|
+
}
|
|
22
|
+
/** What a successful publish answers: the node's key and size, plus a locally-computed sha256
|
|
23
|
+
* over the exact published bytes. No `txId` — content has nothing to submit later. */
|
|
24
|
+
export interface Published {
|
|
25
|
+
readonly key: string;
|
|
26
|
+
readonly size: number;
|
|
27
|
+
readonly sha256: string;
|
|
28
|
+
}
|
|
29
|
+
/** What a successful fetch answers: the key, size, locally-recomputed sha256, and the bytes
|
|
30
|
+
* decoded to UTF-8 `text`. The raw `bytes` field is DROPPED — never surfaced to the model
|
|
31
|
+
* (Pitfall 5). `null` is the DEFINITIVE "no reachable holder" negative (Pitfall 3). */
|
|
32
|
+
export interface Fetched {
|
|
33
|
+
readonly key: string;
|
|
34
|
+
readonly size: number;
|
|
35
|
+
readonly sha256: string;
|
|
36
|
+
readonly text: string;
|
|
37
|
+
}
|
|
38
|
+
/** SHA-256 of the given bytes, hex-encoded. Computed locally via Web Crypto — never trusted from
|
|
39
|
+
* the node (T-5-04). */
|
|
40
|
+
export declare function sha256Hex(bytes: Uint8Array): Promise<string>;
|
|
41
|
+
/** Publish UTF-8 text to the content cache (READ-11). OPEN-only publish for v1: no
|
|
42
|
+
* `authorizedPrincipals` is exposed, closing the blank-grant-list footgun (Pitfall 4). Empty or
|
|
43
|
+
* blank text is refused locally as `partial` BEFORE any call. Returns `{ key, size, sha256 }`
|
|
44
|
+
* with the sha256 computed locally over the exact bytes published. */
|
|
45
|
+
export declare function publishBytes(deps: ContentDeps, text: string): Promise<ToolResult<Published>>;
|
|
46
|
+
/** Fetch content from the cache by key and decode it to text (READ-10). Empty/blank key refused
|
|
47
|
+
* locally as `partial` BEFORE any call. A `null` from the SDK is the DEFINITIVE negative — no
|
|
48
|
+
* reachable holder — surfaced as `ok { value: null }`, NOT `not_found` (Pitfall 3); a throw is
|
|
49
|
+
* `unreachable`. Raw bytes are decoded to `text` and dropped (Pitfall 5). */
|
|
50
|
+
export declare function fetchBytes(deps: ContentDeps, key: string): Promise<ToolResult<Fetched | null>>;
|
|
51
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;6BAE6B;AAC7B,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CACrD;AAED;;;0CAG0C;AAC1C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;uFACuF;AACvF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;wFAEwF;AACxF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;yBACyB;AACzB,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AAED;;;uEAGuE;AACvE,wBAAsB,YAAY,CAChC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAchC;AAED;;;8EAG8E;AAC9E,wBAAsB,UAAU,CAC9B,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAYrC"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The content cache is a SECOND non-signing verb family, alongside `claimIdentity` (SIGN-08): it
|
|
2
|
+
// is a DIRECT node call through `NodeClient.postContent`/`getContent`, because the MCP holds no
|
|
3
|
+
// key for the content it publishes or fetches — content is a loopback byte store on its OWN
|
|
4
|
+
// listener (a different port from the chain API), and reaching that listener is the entire
|
|
5
|
+
// authorization. Nothing is recorded in the session stores, no `txId` is returned, and nothing
|
|
6
|
+
// reaches `evolver_submit`.
|
|
7
|
+
//
|
|
8
|
+
// The digest is NEVER trusted from the node: `sha256Hex` is computed locally over the exact
|
|
9
|
+
// bytes, so a reader verifies against what the PUBLISHER asserted, not against what the node
|
|
10
|
+
// claims (a node-trusted digest would verify the node against itself — T-5-04).
|
|
11
|
+
//
|
|
12
|
+
// A GET that returns `null` is a DEFINITIVE negative ("no reachable holder had it" — the node
|
|
13
|
+
// walked the overlay on a miss), NOT a throw: a throw is `unreachable` (transport failure), and
|
|
14
|
+
// the two are never collapsed (Pitfall 3).
|
|
15
|
+
import { unreachableFrom } from "./result.js";
|
|
16
|
+
/** SHA-256 of the given bytes, hex-encoded. Computed locally via Web Crypto — never trusted from
|
|
17
|
+
* the node (T-5-04). */
|
|
18
|
+
export async function sha256Hex(bytes) {
|
|
19
|
+
const d = await crypto.subtle.digest("SHA-256", new Uint8Array(bytes));
|
|
20
|
+
return Array.from(new Uint8Array(d))
|
|
21
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
22
|
+
.join("");
|
|
23
|
+
}
|
|
24
|
+
/** Publish UTF-8 text to the content cache (READ-11). OPEN-only publish for v1: no
|
|
25
|
+
* `authorizedPrincipals` is exposed, closing the blank-grant-list footgun (Pitfall 4). Empty or
|
|
26
|
+
* blank text is refused locally as `partial` BEFORE any call. Returns `{ key, size, sha256 }`
|
|
27
|
+
* with the sha256 computed locally over the exact bytes published. */
|
|
28
|
+
export async function publishBytes(deps, text) {
|
|
29
|
+
const textStr = String(text ?? "");
|
|
30
|
+
if (textStr.trim().length === 0)
|
|
31
|
+
return { kind: "partial", what: "text", cause: "publishing empty content is refused — write something first" };
|
|
32
|
+
const bytes = new TextEncoder().encode(textStr);
|
|
33
|
+
try {
|
|
34
|
+
const sha256 = await sha256Hex(bytes);
|
|
35
|
+
const r = await deps.client.postContent(bytes);
|
|
36
|
+
return { kind: "ok", value: { key: r.key, size: r.size, sha256 } };
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
return unreachableFrom(e, deps.contentUrl);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Fetch content from the cache by key and decode it to text (READ-10). Empty/blank key refused
|
|
43
|
+
* locally as `partial` BEFORE any call. A `null` from the SDK is the DEFINITIVE negative — no
|
|
44
|
+
* reachable holder — surfaced as `ok { value: null }`, NOT `not_found` (Pitfall 3); a throw is
|
|
45
|
+
* `unreachable`. Raw bytes are decoded to `text` and dropped (Pitfall 5). */
|
|
46
|
+
export async function fetchBytes(deps, key) {
|
|
47
|
+
const k = String(key ?? "").trim();
|
|
48
|
+
if (k.length === 0)
|
|
49
|
+
return { kind: "partial", what: "key", cause: "which content? a non-empty key is required" };
|
|
50
|
+
try {
|
|
51
|
+
const bytes = await deps.client.getContent(k);
|
|
52
|
+
if (bytes === null)
|
|
53
|
+
return { kind: "ok", value: null };
|
|
54
|
+
return { kind: "ok", value: { key: k, size: bytes.length, sha256: await sha256Hex(bytes), text: new TextDecoder().decode(bytes) } };
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
return unreachableFrom(e, deps.contentUrl);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
|
package/dist/group.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Signer } from "@mcsherrylabs/evolver-client";
|
|
2
|
+
import type { ChainReads } from "./read.js";
|
|
3
|
+
import type { SignedGroup } from "./write.js";
|
|
4
|
+
import type { ToolResult } from "./result.js";
|
|
5
|
+
/** What the group verbs need from the world. NO `submitTx` — deliberately: the signing verbs submit
|
|
6
|
+
* nothing, so the capability is not present to reach for (zero-submit is structural). */
|
|
7
|
+
export interface GroupOpsDeps {
|
|
8
|
+
readonly identity: string;
|
|
9
|
+
readonly signer: Signer;
|
|
10
|
+
readonly ttlOffset?: number;
|
|
11
|
+
readonly reads: ChainReads;
|
|
12
|
+
}
|
|
13
|
+
/** A signed groups-ledger operation, as returned by a signing verb. */
|
|
14
|
+
export type GroupSignResult = ToolResult<SignedGroup>;
|
|
15
|
+
/**
|
|
16
|
+
* Create a new group (SIGN-09), signed for `deps.identity`. The name is validated STRUCTURALLY before
|
|
17
|
+
* anything is signed — the chain FINES an invalid name rather than refusing it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function signCreateGroup(deps: GroupOpsDeps, params: {
|
|
20
|
+
name: string;
|
|
21
|
+
}): Promise<GroupSignResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Delete a group (SIGN-10). The name is validated structurally before anything is signed. Ownership
|
|
24
|
+
* is NOT checked locally — a delete by a non-owner is chain-enforced and surfaces as an
|
|
25
|
+
* `invalid_group_body` fine via `classifySubmit`, not as a local refusal.
|
|
26
|
+
*/
|
|
27
|
+
export declare function signDeleteGroup(deps: GroupOpsDeps, params: {
|
|
28
|
+
name: string;
|
|
29
|
+
}): Promise<GroupSignResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Add a member to a group (SIGN-11). Refusals fire FIRST, in order: invalid group name, empty member
|
|
32
|
+
* id, self-add, invalid node id — then the chain read, then compose.
|
|
33
|
+
*/
|
|
34
|
+
export declare function signAddMember(deps: GroupOpsDeps, params: {
|
|
35
|
+
groupName: string;
|
|
36
|
+
memberNodeId: string;
|
|
37
|
+
}): Promise<GroupSignResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Remove a member from a group (SIGN-12). Same refusal order as add-member (invalid name, empty id,
|
|
40
|
+
* self-add, invalid id) BEFORE the chain read.
|
|
41
|
+
*/
|
|
42
|
+
export declare function signRemoveMember(deps: GroupOpsDeps, params: {
|
|
43
|
+
groupName: string;
|
|
44
|
+
memberNodeId: string;
|
|
45
|
+
}): Promise<GroupSignResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Multi-recipient credit transfer (SIGN-02): move credit from the signing identity to 1..20
|
|
48
|
+
* recipients. This is the plain `transferBody` on `LEDGER.identity` — NOT a group-membership encoder
|
|
49
|
+
* and NOT an ownership transfer (group ownership transfer is deferred to v2; it always fines
|
|
50
|
+
* "missing co-signer auth"). "One verb = one tx"; the multi-entry array is the ONE thing that
|
|
51
|
+
* distinguishes it from the singleton `evolver_sign_transfer`. Order is significant — it decides
|
|
52
|
+
* which transfer survives a short-balance apply.
|
|
53
|
+
*/
|
|
54
|
+
export declare function signGroupTransfer(deps: GroupOpsDeps, params: {
|
|
55
|
+
entries: Array<{
|
|
56
|
+
recipient: string;
|
|
57
|
+
amount: string;
|
|
58
|
+
}>;
|
|
59
|
+
}): Promise<GroupSignResult>;
|
|
60
|
+
//# sourceMappingURL=group.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;0FAC0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;AAetD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GACvB,OAAO,CAAC,eAAe,CAAC,CAqB1B;AAkCD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GACvB,OAAO,CAAC,eAAe,CAAC,CAgB1B;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAClD,OAAO,CAAC,eAAe,CAAC,CAkB1B;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAChE,OAAO,CAAC,eAAe,CAAC,CA2C1B"}
|
package/dist/group.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Groups-ledger signing verbs (SIGN-09..12) plus the multi-recipient group credit transfer
|
|
2
|
+
// (SIGN-02). Each is a compose→sign→record→return-txId operation with ZERO submit, mirroring the
|
|
3
|
+
// identity verbs in `src/identity.ts` and resolving in the SAME `submitSigned`/`inspectSigned` path.
|
|
4
|
+
//
|
|
5
|
+
// The ledger is pinned to `LEDGER.groups` for the four membership verbs (never a caller-chosen id),
|
|
6
|
+
// and to `LEDGER.identity` for the credit transfer (credit lives on identities; a group holds no
|
|
7
|
+
// balance). Nothing here can reach a node before `evolver_submit`. The costly local refusals —
|
|
8
|
+
// invalid group name, empty/invalid member id, self-add, a bad transfer entry — fire BEFORE anything
|
|
9
|
+
// is signed, because the chain's answer for a malformed body arrives as a FINE, too late to refuse
|
|
10
|
+
// (Pitfalls 4/5).
|
|
11
|
+
//
|
|
12
|
+
// ⚠️ KEY MATERIAL NEVER REACHES A RECORD OR A REFUSAL. Records carry the group name and member node
|
|
13
|
+
// id and decimal amount strings only; refusals name the FIELD/PATH, never the bytes.
|
|
14
|
+
import { LEDGER, createGroupBody, deleteGroupBody, addMemberBody, removeMemberBody, transferBody } from "@mcsherrylabs/evolver-client";
|
|
15
|
+
import { isValidGroupName, isValidNodeId } from "@mcsherrylabs/evolver-client";
|
|
16
|
+
import { parseAmount, formatAmount } from "./amount.js";
|
|
17
|
+
import { DEFAULT_TTL_OFFSET, composeAndSign } from "./write.js";
|
|
18
|
+
/** Read the chain tip to bound a transaction's life; an unreachable chain is `unreachable`, never a
|
|
19
|
+
* submit. */
|
|
20
|
+
async function tipOrUnreachable(deps) {
|
|
21
|
+
try {
|
|
22
|
+
return await deps.reads.currentRound();
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
return { kind: "unreachable", chain: "", cause: e instanceof Error ? e.message : String(e) };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** The maximum number of recipients an on-chain credit transfer carries (the node's own 1..20 rule). */
|
|
29
|
+
const MAX_TRANSFER_ENTRIES = 20;
|
|
30
|
+
/**
|
|
31
|
+
* Create a new group (SIGN-09), signed for `deps.identity`. The name is validated STRUCTURALLY before
|
|
32
|
+
* anything is signed — the chain FINES an invalid name rather than refusing it.
|
|
33
|
+
*/
|
|
34
|
+
export async function signCreateGroup(deps, params) {
|
|
35
|
+
const name = String(params.name ?? "").trim();
|
|
36
|
+
if (!isValidGroupName(name))
|
|
37
|
+
return {
|
|
38
|
+
kind: "partial",
|
|
39
|
+
what: "name",
|
|
40
|
+
cause: `"${name}" is not a valid group name — the chain fines an invalid name rather than refusing it, so it is refused here`,
|
|
41
|
+
};
|
|
42
|
+
const tip = await tipOrUnreachable(deps);
|
|
43
|
+
if (typeof tip !== "number")
|
|
44
|
+
return tip;
|
|
45
|
+
const expiresAtRound = tip + (deps.ttlOffset ?? DEFAULT_TTL_OFFSET);
|
|
46
|
+
return composeAndSign(deps, {
|
|
47
|
+
ledgerId: LEDGER.groups, // hard-pinned, never caller-chosen
|
|
48
|
+
body: createGroupBody(name),
|
|
49
|
+
ttlRound: expiresAtRound,
|
|
50
|
+
signedAtRound: tip,
|
|
51
|
+
verb: "create_group",
|
|
52
|
+
fields: { groupName: name },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/** The refusal shared by the three member/file verbs: validate the group name BEFORE anything is
|
|
56
|
+
* read or signed (the chain fines, it does not refuse, an invalid name). */
|
|
57
|
+
function validGroupName(name) {
|
|
58
|
+
if (!isValidGroupName(name))
|
|
59
|
+
return {
|
|
60
|
+
kind: "partial",
|
|
61
|
+
what: "name",
|
|
62
|
+
cause: `"${name}" is not a valid group name — the chain fines an invalid name rather than refusing it, so it is refused here`,
|
|
63
|
+
};
|
|
64
|
+
return name;
|
|
65
|
+
}
|
|
66
|
+
/** The refusal shared by add/remove-member: the member id must be non-empty, not the sender, and a
|
|
67
|
+
* syntactically valid node id — all refused BEFORE the chain read (the chain fines these). */
|
|
68
|
+
function validMemberId(memberNodeId, identity) {
|
|
69
|
+
if (memberNodeId.length === 0)
|
|
70
|
+
return { kind: "partial", what: "memberNodeId", cause: "no member id given" };
|
|
71
|
+
if (memberNodeId === identity)
|
|
72
|
+
return {
|
|
73
|
+
kind: "partial",
|
|
74
|
+
what: "memberNodeId",
|
|
75
|
+
cause: `adding or removing "${identity}" (this agent) is a semantic no-op worth refusing cheaply — a group's owner is not its own member`,
|
|
76
|
+
};
|
|
77
|
+
if (!isValidNodeId(memberNodeId))
|
|
78
|
+
return {
|
|
79
|
+
kind: "partial",
|
|
80
|
+
what: "memberNodeId",
|
|
81
|
+
cause: `"${memberNodeId}" is not a valid node id — the chain fines an invalid id rather than refusing it, so it is refused here`,
|
|
82
|
+
};
|
|
83
|
+
return memberNodeId;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Delete a group (SIGN-10). The name is validated structurally before anything is signed. Ownership
|
|
87
|
+
* is NOT checked locally — a delete by a non-owner is chain-enforced and surfaces as an
|
|
88
|
+
* `invalid_group_body` fine via `classifySubmit`, not as a local refusal.
|
|
89
|
+
*/
|
|
90
|
+
export async function signDeleteGroup(deps, params) {
|
|
91
|
+
const groupName = validGroupName(String(params.name ?? "").trim());
|
|
92
|
+
if (typeof groupName !== "string")
|
|
93
|
+
return groupName;
|
|
94
|
+
const tip = await tipOrUnreachable(deps);
|
|
95
|
+
if (typeof tip !== "number")
|
|
96
|
+
return tip;
|
|
97
|
+
const expiresAtRound = tip + (deps.ttlOffset ?? DEFAULT_TTL_OFFSET);
|
|
98
|
+
return composeAndSign(deps, {
|
|
99
|
+
ledgerId: LEDGER.groups,
|
|
100
|
+
body: deleteGroupBody(groupName),
|
|
101
|
+
ttlRound: expiresAtRound,
|
|
102
|
+
signedAtRound: tip,
|
|
103
|
+
verb: "delete_group",
|
|
104
|
+
fields: { groupName },
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Add a member to a group (SIGN-11). Refusals fire FIRST, in order: invalid group name, empty member
|
|
109
|
+
* id, self-add, invalid node id — then the chain read, then compose.
|
|
110
|
+
*/
|
|
111
|
+
export async function signAddMember(deps, params) {
|
|
112
|
+
const groupName = validGroupName(String(params.groupName ?? "").trim());
|
|
113
|
+
if (typeof groupName !== "string")
|
|
114
|
+
return groupName;
|
|
115
|
+
const member = validMemberId(String(params.memberNodeId ?? "").trim(), deps.identity);
|
|
116
|
+
if (typeof member !== "string")
|
|
117
|
+
return member;
|
|
118
|
+
const tip = await tipOrUnreachable(deps);
|
|
119
|
+
if (typeof tip !== "number")
|
|
120
|
+
return tip;
|
|
121
|
+
const expiresAtRound = tip + (deps.ttlOffset ?? DEFAULT_TTL_OFFSET);
|
|
122
|
+
return composeAndSign(deps, {
|
|
123
|
+
ledgerId: LEDGER.groups,
|
|
124
|
+
body: addMemberBody(groupName, member),
|
|
125
|
+
ttlRound: expiresAtRound,
|
|
126
|
+
signedAtRound: tip,
|
|
127
|
+
verb: "add_member",
|
|
128
|
+
fields: { groupName, memberNodeId: member },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Remove a member from a group (SIGN-12). Same refusal order as add-member (invalid name, empty id,
|
|
133
|
+
* self-add, invalid id) BEFORE the chain read.
|
|
134
|
+
*/
|
|
135
|
+
export async function signRemoveMember(deps, params) {
|
|
136
|
+
const groupName = validGroupName(String(params.groupName ?? "").trim());
|
|
137
|
+
if (typeof groupName !== "string")
|
|
138
|
+
return groupName;
|
|
139
|
+
const member = validMemberId(String(params.memberNodeId ?? "").trim(), deps.identity);
|
|
140
|
+
if (typeof member !== "string")
|
|
141
|
+
return member;
|
|
142
|
+
const tip = await tipOrUnreachable(deps);
|
|
143
|
+
if (typeof tip !== "number")
|
|
144
|
+
return tip;
|
|
145
|
+
const expiresAtRound = tip + (deps.ttlOffset ?? DEFAULT_TTL_OFFSET);
|
|
146
|
+
return composeAndSign(deps, {
|
|
147
|
+
ledgerId: LEDGER.groups,
|
|
148
|
+
body: removeMemberBody(groupName, member),
|
|
149
|
+
ttlRound: expiresAtRound,
|
|
150
|
+
signedAtRound: tip,
|
|
151
|
+
verb: "remove_member",
|
|
152
|
+
fields: { groupName, memberNodeId: member },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Multi-recipient credit transfer (SIGN-02): move credit from the signing identity to 1..20
|
|
157
|
+
* recipients. This is the plain `transferBody` on `LEDGER.identity` — NOT a group-membership encoder
|
|
158
|
+
* and NOT an ownership transfer (group ownership transfer is deferred to v2; it always fines
|
|
159
|
+
* "missing co-signer auth"). "One verb = one tx"; the multi-entry array is the ONE thing that
|
|
160
|
+
* distinguishes it from the singleton `evolver_sign_transfer`. Order is significant — it decides
|
|
161
|
+
* which transfer survives a short-balance apply.
|
|
162
|
+
*/
|
|
163
|
+
export async function signGroupTransfer(deps, params) {
|
|
164
|
+
const entries = Array.isArray(params.entries) ? params.entries : [];
|
|
165
|
+
if (entries.length === 0)
|
|
166
|
+
return { kind: "partial", what: "entries", cause: "at least one transfer entry is required" };
|
|
167
|
+
if (entries.length > MAX_TRANSFER_ENTRIES)
|
|
168
|
+
return {
|
|
169
|
+
kind: "partial",
|
|
170
|
+
what: "entries",
|
|
171
|
+
cause: `at most ${MAX_TRANSFER_ENTRIES} entries are allowed (the node's own limit) — got ${entries.length}`,
|
|
172
|
+
};
|
|
173
|
+
// Validate every entry BEFORE anything is signed: a bad amount or a missing recipient is refused
|
|
174
|
+
// here (naming the entry index, never the bytes) rather than fined on-chain.
|
|
175
|
+
const parsed = [];
|
|
176
|
+
for (let i = 0; i < entries.length; i++) {
|
|
177
|
+
const e = entries[i];
|
|
178
|
+
const recipient = String(e.recipient ?? "").trim();
|
|
179
|
+
if (recipient.length === 0)
|
|
180
|
+
return { kind: "partial", what: `entries.${i}.recipient`, cause: "no recipient given" };
|
|
181
|
+
const amount = parseAmount(String(e.amount ?? ""));
|
|
182
|
+
if (!amount.ok)
|
|
183
|
+
return { kind: "partial", what: `entries.${i}.amount`, cause: amount.error };
|
|
184
|
+
parsed.push({ recipient, amount: amount.value });
|
|
185
|
+
}
|
|
186
|
+
const tip = await tipOrUnreachable(deps);
|
|
187
|
+
if (typeof tip !== "number")
|
|
188
|
+
return tip;
|
|
189
|
+
const expiresAtRound = tip + (deps.ttlOffset ?? DEFAULT_TTL_OFFSET);
|
|
190
|
+
const fields = {};
|
|
191
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
192
|
+
fields[`recipient.${i}`] = parsed[i].recipient;
|
|
193
|
+
fields[`amount.${i}`] = formatAmount(parsed[i].amount);
|
|
194
|
+
}
|
|
195
|
+
return composeAndSign(deps, {
|
|
196
|
+
ledgerId: LEDGER.identity, // NOT groups — credit lives on identities (ownership transfer is deferred to v2)
|
|
197
|
+
body: transferBody(parsed.map((e) => ({ recipient: e.recipient, amount: e.amount }))),
|
|
198
|
+
ttlRound: expiresAtRound,
|
|
199
|
+
signedAtRound: tip,
|
|
200
|
+
verb: "group_transfer",
|
|
201
|
+
fields,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=group.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group.js","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"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"}
|