@toon-protocol/rig 2.14.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3HRFDH7H.js → chunk-2KJJ7LSO.js} +3 -3
- package/dist/chunk-2KJJ7LSO.js.map +1 -0
- package/dist/{chunk-VKEUIYQO.js → chunk-4CAOEXGQ.js} +34 -82
- package/dist/chunk-4CAOEXGQ.js.map +1 -0
- package/dist/{chunk-35ALWUDJ.js → chunk-B5ISARMU.js} +2 -10
- package/dist/chunk-B5ISARMU.js.map +1 -0
- package/dist/{chunk-EEUHZNWS.js → chunk-HJDPM37T.js} +28 -12
- package/dist/chunk-HJDPM37T.js.map +1 -0
- package/dist/{chunk-ACTH6ISJ.js → chunk-TTJCBGKW.js} +2 -2
- package/dist/cli/rig.js +47 -33
- package/dist/cli/rig.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -5
- package/dist/{network-bootstrap-3X2GXNJB.js → network-bootstrap-CTYIZMP6.js} +3 -3
- package/dist/{publisher-ftQeZSib.d.ts → publisher-BFcnZeJI.d.ts} +19 -20
- package/dist/standalone/index.d.ts +31 -36
- package/dist/standalone/index.js +2 -2
- package/dist/{standalone-mode-OT63GEPM.js → standalone-mode-AA2UWRGQ.js} +5 -5
- package/package.json +9 -8
- package/dist/chunk-35ALWUDJ.js.map +0 -1
- package/dist/chunk-3HRFDH7H.js.map +0 -1
- package/dist/chunk-EEUHZNWS.js.map +0 -1
- package/dist/chunk-VKEUIYQO.js.map +0 -1
- /package/dist/{chunk-ACTH6ISJ.js.map → chunk-TTJCBGKW.js.map} +0 -0
- /package/dist/{network-bootstrap-3X2GXNJB.js.map → network-bootstrap-CTYIZMP6.js.map} +0 -0
- /package/dist/{standalone-mode-OT63GEPM.js.map → standalone-mode-AA2UWRGQ.js.map} +0 -0
|
@@ -138,7 +138,7 @@ function buildStatus(targetEventId, statusKind, targetPubkey) {
|
|
|
138
138
|
// src/remote-state.ts
|
|
139
139
|
import { decode as decodeToon } from "@toon-format/toon";
|
|
140
140
|
|
|
141
|
-
//
|
|
141
|
+
// ../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/dist/gateways.js
|
|
142
142
|
var ARWEAVE_GATEWAYS = [
|
|
143
143
|
"https://ar-io.dev",
|
|
144
144
|
"https://arweave.net",
|
|
@@ -146,7 +146,7 @@ var ARWEAVE_GATEWAYS = [
|
|
|
146
146
|
];
|
|
147
147
|
var ARWEAVE_FETCH_TIMEOUT_MS = 15e3;
|
|
148
148
|
|
|
149
|
-
//
|
|
149
|
+
// ../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/dist/git-sha.js
|
|
150
150
|
var ARWEAVE_GRAPHQL_URL = "https://arweave.net/graphql";
|
|
151
151
|
var SHA_CACHE_MAX_SIZE = 1e4;
|
|
152
152
|
var shaToTxIdCache = /* @__PURE__ */ new Map();
|
|
@@ -497,4 +497,4 @@ export {
|
|
|
497
497
|
queryRelay,
|
|
498
498
|
fetchRemoteState
|
|
499
499
|
};
|
|
500
|
-
//# sourceMappingURL=chunk-
|
|
500
|
+
//# sourceMappingURL=chunk-2KJJ7LSO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/nip34-events.ts","../src/remote-state.ts","../../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/src/gateways.ts","../../../node_modules/.pnpm/@toon-protocol+arweave@0.2.0/node_modules/@toon-protocol/arweave/src/git-sha.ts"],"sourcesContent":["/**\n * Pure NIP-34 event builders for the Git-to-TOON write path.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/event-builders.ts` (#223).\n * All builders return UnsignedEvent — the caller signs with their keypair\n * via finalizeEvent() and publishes through a Publisher (#226). Tag\n * structures follow the NIP-34 spec and `@toon-protocol/core/nip34`.\n */\n\nimport {\n ISSUE_KIND,\n PATCH_KIND,\n REPOSITORY_ANNOUNCEMENT_KIND,\n} from '@toon-protocol/core/nip34';\nimport type {\n STATUS_APPLIED_KIND,\n STATUS_CLOSED_KIND,\n STATUS_DRAFT_KIND,\n STATUS_OPEN_KIND,\n} from '@toon-protocol/core/nip34';\n\n// Kinds not (yet) exported by @toon-protocol/core/nip34:\n/** Repository State (refs) — replaceable, pairs with kind:30617 via `d` tag. */\nexport const REPOSITORY_STATE_KIND = 30618;\n/** Comment on an issue or patch (NIP-22 style threading within NIP-34). */\nexport const COMMENT_KIND = 1622;\n\n// ---------------------------------------------------------------------------\n// UnsignedEvent type (subset of nostr-tools — no id, sig, or pubkey)\n// ---------------------------------------------------------------------------\n\nexport interface UnsignedEvent {\n kind: number;\n content: string;\n tags: string[][];\n created_at: number;\n}\n\n// ---------------------------------------------------------------------------\n// kind:30617 — Repository Announcement (+ maintainer authority, #287)\n// ---------------------------------------------------------------------------\n\n/**\n * NIP-34 tag naming the repo's declared maintainers: one multi-valued tag\n * `[\"maintainers\", \"<hex-pubkey>\", \"<hex-pubkey>\", …]` on the kind:30617\n * announcement (mirrors the spec's multi-valued `relays` tag). The repo\n * OWNER — the announcement event's own pubkey — is ALWAYS an implicit\n * maintainer and need not be listed. Consumers derive an issue/PR's status\n * ONLY from kind:1630-1633 events signed by owner ∪ maintainers (#287): the\n * relay is permissionless, so this is the CONSUMER-side authority filter.\n */\nexport const MAINTAINERS_TAG = 'maintainers';\n\nconst HEX64 = /^[0-9a-f]{64}$/;\n\n/**\n * Collect the declared maintainer pubkeys (lowercased hex) from a kind:30617\n * event's tags. Tolerant of repeated `maintainers` tags and non-hex noise —\n * only 64-char hex values survive. Does NOT include the owner (implicit).\n */\nexport function parseMaintainers(tags: string[][]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n if (tag[0] !== MAINTAINERS_TAG) continue;\n for (const value of tag.slice(1)) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n out.push(hex);\n }\n }\n }\n return out;\n}\n\n/**\n * The set of pubkeys whose kind:1630-1633 status events are authoritative for\n * a repo: the owner (always) ∪ the declared maintainers (from the 30617's\n * `maintainers` tag). All values are lowercased hex.\n */\nexport function authorizedStatusAuthors(\n ownerPubkey: string,\n repoAnnouncementTags: string[][]\n): Set<string> {\n return new Set([\n ownerPubkey.toLowerCase(),\n ...parseMaintainers(repoAnnouncementTags),\n ]);\n}\n\n/**\n * Build a kind:30617 repository announcement event.\n *\n * @param repoId - Repository identifier (d tag)\n * @param name - Human-readable repository name\n * @param description - Repository description\n * @param maintainers - Optional declared maintainer pubkeys (hex). Emitted as\n * a single `[\"maintainers\", …]` tag when non-empty; duplicate and non-64-hex\n * values are dropped. The owner (the signer) is an implicit maintainer and\n * need not be listed — if passed it is emitted, which is harmless since the\n * owner is authorized regardless. See {@link MAINTAINERS_TAG}.\n */\nexport function buildRepoAnnouncement(\n repoId: string,\n name: string,\n description: string,\n maintainers: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['d', repoId],\n ['name', name],\n ['description', description],\n ];\n const declared: string[] = [];\n const seen = new Set<string>();\n for (const value of maintainers) {\n const hex = value.toLowerCase();\n if (HEX64.test(hex) && !seen.has(hex)) {\n seen.add(hex);\n declared.push(hex);\n }\n }\n if (declared.length > 0) {\n tags.push([MAINTAINERS_TAG, ...declared]);\n }\n return {\n kind: REPOSITORY_ANNOUNCEMENT_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:30618 — Repository Refs/State\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:30618 repository refs/state event.\n *\n * @param repoId - Repository identifier (d tag, matches kind:30617)\n * @param refs - Map of ref paths to commit SHAs (e.g., { 'refs/heads/main': 'abc123' })\n * @param arweaveMap - Map of git SHAs to Arweave transaction IDs\n */\nexport function buildRepoRefs(\n repoId: string,\n refs: Record<string, string>,\n arweaveMap: Record<string, string> = {}\n): UnsignedEvent {\n const tags: string[][] = [['d', repoId]];\n\n // Add ref tags\n for (const [refPath, commitSha] of Object.entries(refs)) {\n tags.push(['r', refPath, commitSha]);\n }\n\n // Default HEAD to first ref (typically refs/heads/main)\n const firstRef = Object.keys(refs)[0];\n if (firstRef) {\n tags.push(['HEAD', `ref: ${firstRef}`]);\n }\n\n // Add arweave SHA-to-txId mapping tags\n for (const [sha, txId] of Object.entries(arweaveMap)) {\n tags.push(['arweave', sha, txId]);\n }\n\n return {\n kind: REPOSITORY_STATE_KIND,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1621 — Issue\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1621 issue event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Issue title (subject tag)\n * @param body - Issue body (Markdown content)\n * @param labels - Optional labels (t tags)\n */\nexport function buildIssue(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n body: string,\n labels: string[] = []\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ...labels.map((label) => ['t', label]),\n ];\n\n return {\n kind: ISSUE_KIND,\n content: body,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1622 — Comment (on issue or PR)\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1622 comment event.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param issueOrPrEventId - Event ID of the issue or PR being commented on\n * @param authorPubkey - Pubkey of the issue/PR author (NIP-34 `p` tag for threading), NOT the comment author\n * @param body - Comment body (Markdown content)\n * @param marker - Event reference marker: 'root' or 'reply' (default: 'reply')\n */\nexport function buildComment(\n repoOwnerPubkey: string,\n repoId: string,\n issueOrPrEventId: string,\n authorPubkey: string,\n body: string,\n marker: 'root' | 'reply' = 'reply'\n): UnsignedEvent {\n return {\n kind: COMMENT_KIND,\n content: body,\n tags: [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['e', issueOrPrEventId, '', marker],\n ['p', authorPubkey],\n ],\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1617 — Patch / PR\n// ---------------------------------------------------------------------------\n\n/**\n * Build a kind:1617 patch event.\n *\n * The PR body/description travels in a dedicated `description` tag, NEVER in\n * `content` (#280): `content` is real `git format-patch` output that readers\n * pipe straight into `git am`, and git's patch-format detection hard-fails on\n * any leading prose (verified: \"Patch format detection failed.\"). The tag\n * route keeps `git am` consumption intact while `rig pr show` and the\n * rig-web/views `parsePR` renderers surface the description.\n *\n * @param repoOwnerPubkey - Pubkey of the repository owner\n * @param repoId - Repository identifier\n * @param title - Patch/PR title (subject tag)\n * @param commits - Array of { sha, parentSha } for commit and parent-commit tags\n * @param branchTag - Branch name for the t tag\n * @param content - Real `git format-patch` text (NIP-34 patch body); defaults\n * to '' for callers that only reference commits by tag\n * @param description - PR body/cover text (`description` tag) — kept out of\n * `content` so `git am` still applies it\n */\nexport function buildPatch(\n repoOwnerPubkey: string,\n repoId: string,\n title: string,\n commits: { sha: string; parentSha: string }[],\n branchTag?: string,\n content = '',\n description?: string\n): UnsignedEvent {\n const tags: string[][] = [\n ['a', `${REPOSITORY_ANNOUNCEMENT_KIND}:${repoOwnerPubkey}:${repoId}`],\n ['p', repoOwnerPubkey],\n ['subject', title],\n ];\n\n if (description !== undefined && description !== '') {\n tags.push(['description', description]);\n }\n\n for (const commit of commits) {\n tags.push(['commit', commit.sha]);\n tags.push(['parent-commit', commit.parentSha]);\n }\n\n if (branchTag) {\n tags.push(['t', branchTag]);\n }\n\n return {\n kind: PATCH_KIND,\n content,\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n\n// ---------------------------------------------------------------------------\n// kind:1630-1633 — Status\n// ---------------------------------------------------------------------------\n\n/** Status kinds: 1630 open, 1631 applied/merged, 1632 closed, 1633 draft. */\nexport type StatusKind =\n | typeof STATUS_OPEN_KIND\n | typeof STATUS_APPLIED_KIND\n | typeof STATUS_CLOSED_KIND\n | typeof STATUS_DRAFT_KIND;\n\n/**\n * Build a status event (kind 1630-1633).\n *\n * @param targetEventId - Event ID of the patch, PR, or issue being updated\n * @param statusKind - One of 1630 (open), 1631 (applied), 1632 (closed), 1633 (draft)\n * @param targetPubkey - Optional pubkey of the target event author (p tag per NIP-34 StatusEvent)\n */\nexport function buildStatus(\n targetEventId: string,\n statusKind: StatusKind,\n targetPubkey?: string\n): UnsignedEvent {\n const tags: string[][] = [['e', targetEventId]];\n if (targetPubkey) {\n tags.push(['p', targetPubkey]);\n }\n return {\n kind: statusKind,\n content: '',\n tags,\n created_at: Math.floor(Date.now() / 1000),\n };\n}\n","/**\n * Remote repository state reader — the \"what does the remote have?\" half of\n * `rig push` (epic #222, ticket #225).\n *\n * Queries relay(s) over NIP-01 WebSocket for the repository's NIP-34 state:\n * - kind:30618 (repository state): `r` tags → ref map, `HEAD` symref,\n * `arweave` tags → git SHA → Arweave txId hints.\n * - kind:30617 (repository announcement): presence = the repo exists on\n * TOON (first-push detection) + name/description/relays metadata.\n *\n * Both kinds are NIP-33 parameterized-replaceable: per relay only the latest\n * event per (kind, author, d) survives, but we still pick the newest across\n * relays (highest created_at, ties broken by lowest id per NIP-01).\n *\n * SHAs missing from the `arweave` tag map can be resolved via the shared\n * Arweave GraphQL Git-SHA resolver (@toon-protocol/arweave — the same module\n * the rig SPA uses) through {@link RemoteState.resolveMissing}.\n *\n * Relay payload encodings (mirrors rig's `web/relay-client.ts` decode logic):\n * EVENT payloads arrive as an inline JSON object (standard NIP-01), as a\n * double-JSON-encoded string (devnet relay quirk: a JSON string containing\n * the event JSON), or as a TOON-encoded string. All three are tolerated.\n */\n\nimport { decode as decodeToon } from '@toon-format/toon';\nimport {\n resolveGitSha,\n seedShaCache,\n shaCacheKey,\n} from '@toon-protocol/arweave';\nimport { REPOSITORY_ANNOUNCEMENT_KIND } from '@toon-protocol/core/nip34';\n\nimport { REPOSITORY_STATE_KIND, parseMaintainers } from './nip34-events.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A signed Nostr event as seen on a relay (read side). */\nexport interface NostrEvent {\n id: string;\n pubkey: string;\n created_at: number;\n kind: number;\n tags: string[][];\n content: string;\n sig: string;\n}\n\n/** NIP-01 subscription filter (only the fields rig's readers send). */\nexport interface NostrFilter {\n ids?: string[];\n kinds?: number[];\n authors?: string[];\n '#d'?: string[];\n /** Repo address tag filter, e.g. `30617:<owner>:<repoId>` (#278 tracker). */\n '#a'?: string[];\n /** Event-reference tag filter (#278 tracker: statuses + comments). */\n '#e'?: string[];\n limit?: number;\n}\n\n/**\n * Minimal structural WebSocket type — satisfied by the WHATWG WebSocket\n * global (Node >= 22 / undici, browsers) and by the `ws` package.\n */\nexport interface WebSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: string, listener: (event: never) => void): void;\n}\n\n/** Factory for WebSocket connections (injectable for tests / `ws` fallback). */\nexport type WebSocketFactory = (url: string) => WebSocketLike;\n\nexport interface FetchRemoteStateOptions {\n /**\n * Relay WebSocket URLs to query. Plural from day one (forward-compat with\n * multi-relay routing); size 1 is typical today. Results are merged and the\n * newest replaceable event across all relays wins.\n */\n relayUrls: string[];\n /** Repository owner's pubkey (hex) — the author of 30617/30618. */\n ownerPubkey: string;\n /** Repository identifier (NIP-34 `d` tag). */\n repoId: string;\n /** Per-relay timeout in milliseconds (default 10000). On timeout the relay contributes whatever it sent so far. */\n timeoutMs?: number;\n /**\n * Git-SHA → Arweave txId resolver used by {@link RemoteState.resolveMissing}.\n * Defaults to the shared GraphQL resolver from @toon-protocol/arweave;\n * injectable for tests.\n */\n resolveSha?: (sha: string, repo: string) => Promise<string | null>;\n /** WebSocket constructor override (defaults to the global WebSocket). */\n webSocketFactory?: WebSocketFactory;\n}\n\n/** Remote repository state assembled from relay events. */\nexport interface RemoteState {\n /** True when a kind:30617 announcement exists (false ⇒ first push). */\n announced: boolean;\n /** Ref map from the latest kind:30618: refname → commit SHA. */\n refs: Map<string, string>;\n /** HEAD symref target (e.g. `refs/heads/main`), or null if unset. */\n headSymref: string | null;\n /** Git SHA → Arweave txId hints from the latest kind:30618 `arweave` tags. */\n shaToTxId: Map<string, string>;\n /** The latest kind:30618 event, or null if the repo has no state yet. */\n refsEvent: NostrEvent | null;\n /** The latest kind:30617 event, or null if the repo is unannounced. */\n announceEvent: NostrEvent | null;\n /** Repository name from the announcement `name` tag. */\n name: string | null;\n /** Announcement `description` tag (falls back to event content). */\n description: string | null;\n /** Relay URLs advertised in the announcement `relays` tag(s). */\n relays: string[];\n /**\n * Declared maintainer pubkeys (hex) from the announcement `maintainers`\n * tag (#287). Does NOT include the owner (an implicit maintainer). Empty\n * when unannounced or owner-only.\n */\n maintainers: string[];\n /**\n * Resolve SHAs to Arweave txIds: served from the `arweave` tag map when\n * present, otherwise via the GraphQL Git-SHA resolver. SHAs that resolve\n * nowhere are omitted from the returned map.\n */\n resolveMissing(shas: string[]): Promise<Map<string, string>>;\n}\n\n// ---------------------------------------------------------------------------\n// Relay query (NIP-01 REQ → EVENT* → EOSE)\n// ---------------------------------------------------------------------------\n\n/** Validate that a relay URL uses a WebSocket scheme (mirrors rig's url-utils). */\nfunction isValidRelayUrl(url: string): boolean {\n return /^wss?:\\/\\//i.test(url);\n}\n\n/** WHATWG WebSocket OPEN ready state. */\nconst WS_OPEN = 1;\n\nfunction defaultWebSocketFactory(url: string): WebSocketLike {\n const ctor = (\n globalThis as { WebSocket?: new (url: string) => WebSocketLike }\n ).WebSocket;\n if (!ctor) {\n throw new Error(\n 'No global WebSocket constructor (Node >= 22 required) — pass webSocketFactory'\n );\n }\n return new ctor(url);\n}\n\n/**\n * Decode a relay EVENT payload, tolerating every encoding seen in the wild:\n * inline object (standard NIP-01), double-JSON-encoded string (devnet relay\n * serves the event as a JSON string containing the event JSON), or a\n * TOON-encoded string (rig's `decodeToonMessage` path).\n */\nfunction decodeEventPayload(payload: unknown): NostrEvent | null {\n if (payload !== null && typeof payload === 'object') {\n return payload as NostrEvent;\n }\n if (typeof payload !== 'string') {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(payload);\n if (parsed !== null && typeof parsed === 'object') {\n return parsed as NostrEvent;\n }\n } catch {\n // Not JSON — fall through to TOON\n }\n try {\n return decodeToon(payload) as unknown as NostrEvent;\n } catch {\n return null;\n }\n}\n\n/**\n * Query one relay: send a REQ, collect EVENTs until EOSE, then CLOSE.\n * Mirrors rig's `queryRelay` (partial results on timeout / early close).\n * Exported for reuse by the network-bootstrap discovery (kind:10032).\n */\nexport function queryRelay(\n relayUrl: string,\n filter: NostrFilter,\n timeoutMs: number,\n webSocketFactory: WebSocketFactory\n): Promise<NostrEvent[]> {\n return new Promise((resolve, reject) => {\n const events: NostrEvent[] = [];\n const subId = `git-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n let ws: WebSocketLike;\n // eslint-disable-next-line prefer-const -- assigned after `settle` is defined\n let timeoutHandle: ReturnType<typeof setTimeout>;\n let settled = false;\n\n const settle = (outcome: 'resolve' | 'reject', error?: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timeoutHandle);\n try {\n if (ws.readyState === WS_OPEN) {\n ws.send(JSON.stringify(['CLOSE', subId]));\n }\n ws.close();\n } catch {\n // Ignore close errors\n }\n if (outcome === 'reject') {\n reject(error);\n } else {\n resolve(events);\n }\n };\n\n if (!isValidRelayUrl(relayUrl)) {\n reject(\n new Error(\n `Invalid relay URL protocol (must be ws:// or wss://): ${relayUrl}`\n )\n );\n return;\n }\n\n try {\n ws = webSocketFactory(relayUrl);\n } catch (err) {\n reject(new Error(`Failed to connect to relay ${relayUrl}: ${String(err)}`));\n return;\n }\n\n timeoutHandle = setTimeout(() => {\n // Resolve with whatever we collected so far (partial results)\n settle('resolve');\n }, timeoutMs);\n\n ws.addEventListener('open', () => {\n ws.send(JSON.stringify(['REQ', subId, filter]));\n });\n\n ws.addEventListener('message', (msgEvent: { data?: unknown }) => {\n try {\n const msg = JSON.parse(String(msgEvent.data)) as unknown[];\n if (!Array.isArray(msg) || msg.length < 2) return;\n\n const msgType = msg[0];\n if (msgType === 'EVENT' && msg[1] === subId && msg[2] !== undefined) {\n const event = decodeEventPayload(msg[2]);\n if (event) events.push(event);\n } else if (msgType === 'EOSE' && msg[1] === subId) {\n settle('resolve');\n }\n } catch {\n // Ignore parse errors for individual messages\n }\n });\n\n ws.addEventListener('error', (event: { message?: unknown }) => {\n const detail =\n typeof event === 'object' && event !== null && 'message' in event\n ? String(event.message)\n : 'unknown';\n settle(\n 'reject',\n new Error(`WebSocket error connecting to ${relayUrl}: ${detail}`)\n );\n });\n\n ws.addEventListener('close', () => {\n // If we haven't settled yet, resolve with what we have\n settle('resolve');\n });\n });\n}\n\n// ---------------------------------------------------------------------------\n// NIP-33 replaceable selection + tag parsing\n// ---------------------------------------------------------------------------\n\n/**\n * Pick the winning replaceable event: highest created_at, ties broken by\n * lowest id (NIP-01 replaceable-event convention).\n */\nfunction latestReplaceable(events: NostrEvent[]): NostrEvent | null {\n let winner: NostrEvent | null = null;\n for (const event of events) {\n if (\n winner === null ||\n event.created_at > winner.created_at ||\n (event.created_at === winner.created_at && event.id < winner.id)\n ) {\n winner = event;\n }\n }\n return winner;\n}\n\n/** Get the first value for a tag name. */\nfunction getTagValue(tags: string[][], name: string): string | undefined {\n const tag = tags.find((t) => t[0] === name);\n return tag?.[1];\n}\n\n/** Maximum number of refs parsed from a single kind:30618 event (mirrors views). */\nconst MAX_REFS_PER_EVENT = 1000;\n\n/** Symref prefix used in `HEAD` tags: `[\"HEAD\", \"ref: refs/heads/main\"]`. */\nconst SYMREF_PREFIX = 'ref: ';\n\ninterface ParsedRefs {\n refs: Map<string, string>;\n headSymref: string | null;\n shaToTxId: Map<string, string>;\n}\n\n/** Parse a kind:30618 event's `r` / `HEAD` / `arweave` tags. */\nfunction parseRefsEvent(event: NostrEvent): ParsedRefs {\n const refs = new Map<string, string>();\n const shaToTxId = new Map<string, string>();\n let headSymref: string | null = null;\n\n for (const tag of event.tags) {\n const [tagName, v1, v2] = tag;\n if (tagName === 'r' && v1 && v2) {\n if (v1 === 'HEAD' && v2.startsWith(SYMREF_PREFIX)) {\n // Alternate symref spelling: [\"r\", \"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v2.slice(SYMREF_PREFIX.length);\n continue;\n }\n if (refs.size >= MAX_REFS_PER_EVENT) continue;\n refs.set(v1, v2);\n } else if (tagName === 'HEAD' && v1?.startsWith(SYMREF_PREFIX)) {\n // NIP-34 symref tag: [\"HEAD\", \"ref: refs/heads/main\"]\n headSymref = v1.slice(SYMREF_PREFIX.length);\n } else if (tagName === 'arweave' && v1 && v2) {\n shaToTxId.set(v1, v2);\n }\n }\n\n return { refs, headSymref, shaToTxId };\n}\n\n// ---------------------------------------------------------------------------\n// fetchRemoteState\n// ---------------------------------------------------------------------------\n\n/**\n * Fetch the remote repository state from the relay(s).\n *\n * Sends one REQ per relay for kind:30617 + kind:30618 with\n * `authors=[ownerPubkey]`, `#d=[repoId]`, collects until EOSE (or timeout),\n * and reduces to the latest replaceable event per kind.\n *\n * Resolves as long as at least one relay answers; throws only when every\n * relay fails. Events from other authors / repos are ignored (defense\n * against misbehaving relays that over-return).\n */\nexport async function fetchRemoteState(\n options: FetchRemoteStateOptions\n): Promise<RemoteState> {\n const {\n relayUrls,\n ownerPubkey,\n repoId,\n timeoutMs = 10000,\n resolveSha = resolveGitSha,\n webSocketFactory = defaultWebSocketFactory,\n } = options;\n\n if (relayUrls.length === 0) {\n throw new Error('fetchRemoteState: relayUrls must not be empty');\n }\n if (!ownerPubkey) {\n throw new Error('fetchRemoteState: ownerPubkey is required');\n }\n if (!repoId) {\n throw new Error('fetchRemoteState: repoId is required');\n }\n\n const filter: NostrFilter = {\n kinds: [REPOSITORY_ANNOUNCEMENT_KIND, REPOSITORY_STATE_KIND],\n authors: [ownerPubkey],\n '#d': [repoId],\n };\n\n const results = await Promise.allSettled(\n relayUrls.map((url) => queryRelay(url, filter, timeoutMs, webSocketFactory))\n );\n\n const failures: string[] = [];\n const byId = new Map<string, NostrEvent>();\n for (const result of results) {\n if (result.status === 'rejected') {\n failures.push(String((result.reason as Error)?.message ?? result.reason));\n continue;\n }\n for (const event of result.value) {\n // Only trust events from the repo owner for this repo — relays are\n // untrusted and may over-return.\n if (event.pubkey !== ownerPubkey) continue;\n if (getTagValue(event.tags, 'd') !== repoId) continue;\n if (typeof event.id === 'string' && !byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n }\n\n if (failures.length === relayUrls.length) {\n throw new Error(\n `fetchRemoteState: all ${relayUrls.length} relay(s) failed: ${failures.join('; ')}`\n );\n }\n\n const events = [...byId.values()];\n const refsEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_STATE_KIND)\n );\n const announceEvent = latestReplaceable(\n events.filter((e) => e.kind === REPOSITORY_ANNOUNCEMENT_KIND)\n );\n\n const { refs, headSymref, shaToTxId } = refsEvent\n ? parseRefsEvent(refsEvent)\n : {\n refs: new Map<string, string>(),\n headSymref: null,\n shaToTxId: new Map<string, string>(),\n };\n\n // Seed the shared resolver cache so later resolveGitSha calls (here or in\n // any other consumer of @toon-protocol/arweave) skip GraphQL for known SHAs.\n if (shaToTxId.size > 0) {\n seedShaCache(\n [...shaToTxId].map(\n ([sha, txId]) => [shaCacheKey(sha, repoId), txId] as [string, string]\n )\n );\n }\n\n // Announcement metadata (mirrors views' parseRepoAnnouncement defaults).\n const name = announceEvent\n ? (getTagValue(announceEvent.tags, 'name') ?? null)\n : null;\n const description = announceEvent\n ? (getTagValue(announceEvent.tags, 'description') ?? announceEvent.content)\n : null;\n // NIP-34 announcement relays tag is multi-valued: [\"relays\", url1, url2, …]\n const relays: string[] = [];\n if (announceEvent) {\n for (const tag of announceEvent.tags) {\n if (tag[0] === 'relays') {\n relays.push(...tag.slice(1).filter((url) => url.length > 0));\n }\n }\n }\n // Declared maintainers (#287): the `maintainers` tag on the 30617. Owner is\n // an implicit maintainer and is NOT listed here.\n const maintainers = announceEvent\n ? parseMaintainers(announceEvent.tags)\n : [];\n\n const resolveMissing = async (\n shas: string[]\n ): Promise<Map<string, string>> => {\n const resolved = new Map<string, string>();\n const missing: string[] = [];\n for (const sha of new Set(shas)) {\n const known = shaToTxId.get(sha);\n if (known !== undefined) {\n resolved.set(sha, known);\n } else {\n missing.push(sha);\n }\n }\n const lookups = await Promise.all(\n missing.map(\n async (sha) => [sha, await resolveSha(sha, repoId)] as const\n )\n );\n for (const [sha, txId] of lookups) {\n if (txId) resolved.set(sha, txId);\n }\n return resolved;\n };\n\n return {\n announced: announceEvent !== null,\n refs,\n headSymref,\n shaToTxId,\n refsEvent,\n announceEvent,\n name,\n description,\n relays,\n maintainers,\n resolveMissing,\n };\n}\n",null,null],"mappings":"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","names":["REPOSITORY_ANNOUNCEMENT_KIND","REPOSITORY_ANNOUNCEMENT_KIND"]}
|
|
@@ -6,12 +6,11 @@ import {
|
|
|
6
6
|
recordKey
|
|
7
7
|
} from "./chunk-LCYCKWAT.js";
|
|
8
8
|
import {
|
|
9
|
-
MAX_OBJECT_SIZE
|
|
10
|
-
|
|
11
|
-
} from "./chunk-35ALWUDJ.js";
|
|
9
|
+
MAX_OBJECT_SIZE
|
|
10
|
+
} from "./chunk-B5ISARMU.js";
|
|
12
11
|
|
|
13
12
|
// src/standalone/standalone-publisher.ts
|
|
14
|
-
import { ToonClient,
|
|
13
|
+
import { ToonClient, extractArweaveTxId } from "@toon-protocol/client";
|
|
15
14
|
var StandalonePublishError = class extends Error {
|
|
16
15
|
constructor(message) {
|
|
17
16
|
super(message);
|
|
@@ -26,48 +25,6 @@ function deriveRouteDestinations(anchor) {
|
|
|
26
25
|
}
|
|
27
26
|
return { publish: anchor, store: anchor };
|
|
28
27
|
}
|
|
29
|
-
var ARWEAVE_TX_ID_REGEX = /^[A-Za-z0-9_-]{43}$/;
|
|
30
|
-
function extractArweaveTxId(base64Data) {
|
|
31
|
-
const http = parseFulfillHttp(base64Data);
|
|
32
|
-
if (!http.isHttp) {
|
|
33
|
-
const legacy = Buffer.from(base64Data, "base64").toString("utf8");
|
|
34
|
-
if (!ARWEAVE_TX_ID_REGEX.test(legacy)) {
|
|
35
|
-
throw new StandalonePublishError(
|
|
36
|
-
`FULFILL data is not a valid Arweave tx ID: "${legacy}"`
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
return legacy;
|
|
40
|
-
}
|
|
41
|
-
if (http.status < 200 || http.status >= 300) {
|
|
42
|
-
throw new StandalonePublishError(
|
|
43
|
-
`git-object upload failed: store returned HTTP ${http.status}` + (http.body ? ` - ${http.body}` : "")
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
let parsed;
|
|
47
|
-
try {
|
|
48
|
-
parsed = JSON.parse(http.body);
|
|
49
|
-
} catch {
|
|
50
|
-
throw new StandalonePublishError(
|
|
51
|
-
`git-object upload response body was not valid JSON: "${http.body}"`
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
if (parsed.accept === false) {
|
|
55
|
-
const reason = typeof parsed.error === "string" ? `: ${parsed.error}` : "";
|
|
56
|
-
throw new StandalonePublishError(
|
|
57
|
-
`git-object upload rejected by store (accept:false)${reason}`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
if (typeof parsed.txId === "string" && ARWEAVE_TX_ID_REGEX.test(parsed.txId)) {
|
|
61
|
-
return parsed.txId;
|
|
62
|
-
}
|
|
63
|
-
if (typeof parsed.data === "string" && parsed.data.length > 0) {
|
|
64
|
-
const decoded = Buffer.from(parsed.data, "base64").toString("utf8");
|
|
65
|
-
if (ARWEAVE_TX_ID_REGEX.test(decoded)) return decoded;
|
|
66
|
-
}
|
|
67
|
-
throw new StandalonePublishError(
|
|
68
|
-
`git-object upload response did not contain a valid Arweave tx ID: "${http.body}"`
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
28
|
function channelInternals(client) {
|
|
72
29
|
const c = client;
|
|
73
30
|
const negotiations = c.peerNegotiations instanceof Map ? c.peerNegotiations : void 0;
|
|
@@ -86,7 +43,6 @@ var StandalonePublisher = class {
|
|
|
86
43
|
storeDestination;
|
|
87
44
|
channelDestination;
|
|
88
45
|
eventFee;
|
|
89
|
-
uploadFeePerByte;
|
|
90
46
|
routePrices;
|
|
91
47
|
daemonPort;
|
|
92
48
|
skipDaemonCheck;
|
|
@@ -127,7 +83,6 @@ var StandalonePublisher = class {
|
|
|
127
83
|
this.storeDestination = options.storeDestination ?? routes?.store;
|
|
128
84
|
this.channelDestination = options.channelDestination;
|
|
129
85
|
this.eventFee = options.eventFee ?? 1n;
|
|
130
|
-
this.uploadFeePerByte = options.uploadFeePerByte ?? 10n;
|
|
131
86
|
this.routePrices = options.routePrices;
|
|
132
87
|
this.daemonPort = options.daemonPort;
|
|
133
88
|
this.skipDaemonCheck = options.skipDaemonCheck ?? false;
|
|
@@ -253,7 +208,11 @@ var StandalonePublisher = class {
|
|
|
253
208
|
const identity = this.client.getPublicKey();
|
|
254
209
|
const candidates = map.listFor(identity, anchor);
|
|
255
210
|
const internals = channelInternals(this.client);
|
|
256
|
-
const resumed = await this.resumeRecordedChannel(
|
|
211
|
+
const resumed = await this.resumeRecordedChannel(
|
|
212
|
+
map,
|
|
213
|
+
candidates,
|
|
214
|
+
internals
|
|
215
|
+
);
|
|
257
216
|
const channelId = await this.client.openChannel(this.channelDestination);
|
|
258
217
|
if (resumed && channelId === resumed.channelId) {
|
|
259
218
|
this.lastOpenResumed = true;
|
|
@@ -487,20 +446,15 @@ var StandalonePublisher = class {
|
|
|
487
446
|
}
|
|
488
447
|
// ── Publisher ─────────────────────────────────────────────────────────────
|
|
489
448
|
/**
|
|
490
|
-
* Fee rates for `planPush` estimation
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* `eventFee` is returned pre-floored at the publish route price, and the
|
|
495
|
-
* store route price rides as `minUploadFee` (the per-upload floor —
|
|
496
|
-
* per-byte pricing can't fold a flat floor into the rate).
|
|
449
|
+
* Fee rates for `planPush` estimation, both flat per packet (ADR 0020),
|
|
450
|
+
* with the announced route prices folded in so the ESTIMATE always equals
|
|
451
|
+
* the CLAIMS: `eventFee` is returned pre-floored at the publish route price,
|
|
452
|
+
* and `uploadFee` IS the store route price.
|
|
497
453
|
*/
|
|
498
454
|
getFeeRates() {
|
|
499
|
-
const minUploadFee = this.routePrices?.store;
|
|
500
455
|
return Promise.resolve({
|
|
501
|
-
|
|
502
|
-
eventFee: this.effectiveEventFee()
|
|
503
|
-
...minUploadFee !== void 0 ? { minUploadFee } : {}
|
|
456
|
+
uploadFee: this.uploadFee(),
|
|
457
|
+
eventFee: this.effectiveEventFee()
|
|
504
458
|
});
|
|
505
459
|
}
|
|
506
460
|
/**
|
|
@@ -512,21 +466,20 @@ var StandalonePublisher = class {
|
|
|
512
466
|
return this.eventFee > min ? this.eventFee : min;
|
|
513
467
|
}
|
|
514
468
|
/**
|
|
515
|
-
* The per-upload claim:
|
|
516
|
-
*
|
|
469
|
+
* The per-upload claim: the store route's flat price, whatever the object's
|
|
470
|
+
* size. Byte-proportional pricing has no successor (ADR 0020) — the route
|
|
471
|
+
* table is the price list — and the connector charges this figure whether
|
|
472
|
+
* the body is 100 bytes or 100 KB.
|
|
517
473
|
*/
|
|
518
|
-
uploadFee(
|
|
519
|
-
return
|
|
520
|
-
bytes,
|
|
521
|
-
this.uploadFeePerByte,
|
|
522
|
-
this.routePrices?.store
|
|
523
|
-
);
|
|
474
|
+
uploadFee() {
|
|
475
|
+
return this.routePrices?.store ?? 0n;
|
|
524
476
|
}
|
|
525
477
|
/**
|
|
526
478
|
* Upload one git object as a kind:5094 store write (Git-SHA/Git-Type/Repo
|
|
527
479
|
* tagged — the proven seed-pipeline shape), signing one balance-proof claim
|
|
528
|
-
* for
|
|
529
|
-
*
|
|
480
|
+
* for the store route's flat price
|
|
481
|
+
* ({@link StandalonePublisherOptions.routePrices}) — ADR 0020 prices a
|
|
482
|
+
* packet per handler, not per byte.
|
|
530
483
|
*
|
|
531
484
|
* #368: a blob's `Content-Type` is derived from its path extension (else
|
|
532
485
|
* octet-stream) and sent in the `output` tag, which the store forwards onto
|
|
@@ -543,7 +496,7 @@ var StandalonePublisher = class {
|
|
|
543
496
|
await this.start();
|
|
544
497
|
const channelId = this.requireChannel();
|
|
545
498
|
const contentType = upload.type === "blob" ? contentTypeForPath(upload.path) : DEFAULT_CONTENT_TYPE;
|
|
546
|
-
const fee = this.uploadFee(
|
|
499
|
+
const fee = this.uploadFee();
|
|
547
500
|
const event = this.client.signEvent({
|
|
548
501
|
kind: 5094,
|
|
549
502
|
content: "",
|
|
@@ -570,12 +523,12 @@ var StandalonePublisher = class {
|
|
|
570
523
|
`git-object upload rejected (${upload.sha}): ${result.error ?? "store rejected the write"}`
|
|
571
524
|
);
|
|
572
525
|
}
|
|
573
|
-
if (!result.
|
|
526
|
+
if (!result.response) {
|
|
574
527
|
throw new StandalonePublishError(
|
|
575
|
-
`git-object upload FULFILL carried no
|
|
528
|
+
`git-object upload FULFILL carried no sealed response (${upload.sha}); expected the Arweave tx ID`
|
|
576
529
|
);
|
|
577
530
|
}
|
|
578
|
-
return { txId: extractArweaveTxId(result.
|
|
531
|
+
return { txId: extractArweaveTxId(result.response), feePaid: fee };
|
|
579
532
|
}
|
|
580
533
|
/**
|
|
581
534
|
* Upload one raw blob (no git envelope) with an explicit `Content-Type`
|
|
@@ -583,8 +536,7 @@ var StandalonePublisher = class {
|
|
|
583
536
|
* tags (and an optional `Repo` provenance tag), NOT the Git-SHA/Git-Type
|
|
584
537
|
* tags. Without those the store keeps the bytes verbatim instead of
|
|
585
538
|
* re-deriving a git envelope, which is exactly what the ar.io path manifest
|
|
586
|
-
* needs. One balance-proof claim for
|
|
587
|
-
* floored at the store route's announced price.
|
|
539
|
+
* needs. One balance-proof claim for the store route's flat price.
|
|
588
540
|
*/
|
|
589
541
|
async uploadBlob(upload) {
|
|
590
542
|
if (upload.body.length > MAX_OBJECT_SIZE) {
|
|
@@ -594,7 +546,7 @@ var StandalonePublisher = class {
|
|
|
594
546
|
}
|
|
595
547
|
await this.start();
|
|
596
548
|
const channelId = this.requireChannel();
|
|
597
|
-
const fee = this.uploadFee(
|
|
549
|
+
const fee = this.uploadFee();
|
|
598
550
|
const event = this.client.signEvent({
|
|
599
551
|
kind: 5094,
|
|
600
552
|
content: "",
|
|
@@ -618,12 +570,12 @@ var StandalonePublisher = class {
|
|
|
618
570
|
`blob upload rejected: ${result.error ?? "store rejected the write"}`
|
|
619
571
|
);
|
|
620
572
|
}
|
|
621
|
-
if (!result.
|
|
573
|
+
if (!result.response) {
|
|
622
574
|
throw new StandalonePublishError(
|
|
623
|
-
"blob upload FULFILL carried no
|
|
575
|
+
"blob upload FULFILL carried no sealed response; expected the Arweave tx ID"
|
|
624
576
|
);
|
|
625
577
|
}
|
|
626
|
-
return { txId: extractArweaveTxId(result.
|
|
578
|
+
return { txId: extractArweaveTxId(result.response), feePaid: fee };
|
|
627
579
|
}
|
|
628
580
|
/**
|
|
629
581
|
* Sign the event with the embedded identity and pay-to-publish it through
|
|
@@ -671,9 +623,9 @@ function nowSeconds() {
|
|
|
671
623
|
}
|
|
672
624
|
|
|
673
625
|
export {
|
|
626
|
+
extractArweaveTxId,
|
|
674
627
|
StandalonePublishError,
|
|
675
628
|
deriveRouteDestinations,
|
|
676
|
-
extractArweaveTxId,
|
|
677
629
|
StandalonePublisher
|
|
678
630
|
};
|
|
679
|
-
//# sourceMappingURL=chunk-
|
|
631
|
+
//# sourceMappingURL=chunk-4CAOEXGQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/standalone/standalone-publisher.ts"],"sourcesContent":["/**\n * StandalonePublisher — impl 2 of the {@link Publisher} seam (#228): an\n * EMBEDDED ToonClient constructed from the caller's config (mnemonic +\n * account index, the exact `packages/client/src/config.ts` shape) instead of\n * routing through a running toon-clientd. Made for CI jobs, servers, and\n * one-shot CLI runs where no daemon exists.\n *\n * Paid-write mechanics mirror the production daemon path\n * (`client-mcp/src/daemon/client-runner.ts`) and the proven seed pipeline\n * (`rig/tests/e2e/seed/lib/{publish,git-builder}.ts`):\n *\n * - one signed balance-proof CLAIM per write (`signBalanceProof(channelId,\n * fee)` — the ChannelManager accumulates the cumulative watermark),\n * - publishes route to the relay write destination with a flat per-event\n * fee (the daemon's `feePerEvent` convention),\n * - git objects upload as kind:5094 store writes tagged\n * Git-SHA/Git-Type/Repo, priced at bytes × per-byte rate (the seed\n * pipeline's bid), routed to the store destination via `proxyPath:\n * '/store'`, with the Arweave txId decoded from the FULFILL data.\n *\n * Because the embedded client signs claims on the SAME channels a daemon on\n * the same identity would, every paid operation is preceded by the nonce\n * guard (./nonce-guard.ts): refuse if a toon-clientd holds this identity,\n * and hold an exclusive per-pubkey lockfile against other standalone\n * processes for the lifetime of this publisher.\n *\n * Channel REUSE (#262): with a `channelMap` configured, start() resumes the\n * channel recorded for (identity, channel anchor) — `trackChannel` rehydrates\n * the cumulative-claim watermark from the client's channels.json — and\n * records any fresh lazy open, so sequential CLI invocations share ONE\n * on-chain channel instead of stranding a deposit per run (./channel-map.ts).\n */\n\nimport type {\n PublishEventResult,\n SignedBalanceProof,\n ToonClientConfig,\n} from '@toon-protocol/client';\nimport { ToonClient, extractArweaveTxId } from '@toon-protocol/client';\nimport { MAX_OBJECT_SIZE } from '../objects.js';\nimport { contentTypeForPath, DEFAULT_CONTENT_TYPE } from '../mime.js';\nimport type { UnsignedEvent } from '../nip34-events.js';\nimport {\n type BlobUpload,\n type FeeRates,\n type GitObjectUpload,\n type PublishReceipt,\n type Publisher,\n type UploadReceipt,\n} from '../publisher.js';\nimport {\n recordKey,\n type ChannelMapRecord,\n type ChannelMapStore,\n type PersistedChannelContext,\n} from './channel-map.js';\nimport type {\n ChannelCloseOutcome,\n ChannelOpenOutcome,\n ChannelSettleOutcome,\n WalletChainBalanceInfo,\n} from './money.js';\nimport { checkDaemonIdentity, NonceLock } from './nonce-guard.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A fully-signed Nostr event (structural subset of nostr-tools' NostrEvent). */\nexport interface SignedNostrEvent extends UnsignedEvent {\n id: string;\n pubkey: string;\n sig: string;\n}\n\n/**\n * The slice of `ToonClient` the publisher drives. Kept structural so tests\n * inject a mock and alternative client builds stay compatible.\n */\nexport interface ToonClientLike {\n start(): Promise<unknown>;\n stop(): Promise<void>;\n isStarted?(): boolean;\n getPublicKey(): string;\n signEvent(template: UnsignedEvent): SignedNostrEvent;\n openChannel(destination?: string): Promise<string>;\n signBalanceProof(\n channelId: string,\n amount: bigint\n ): Promise<SignedBalanceProof>;\n publishEvent(\n event: SignedNostrEvent,\n options?: {\n destination?: string;\n claim?: SignedBalanceProof;\n ilpAmount?: bigint;\n proxyPath?: string;\n }\n ): Promise<PublishEventResult>;\n /** On-chain deposit total for a tracked channel (channel-map bookkeeping). */\n getChannelDepositTotal?(channelId: string): bigint;\n /** Re-read a RESUMED channel's on-chain deposit (persisted state omits it). */\n rehydrateChannelDeposit?(\n channelId: string,\n opts: { chain: string; tokenNetworkAddress: string }\n ): Promise<bigint | undefined>;\n // ── money lifecycle (#263) — optional, matches ToonClient's surface ──────\n /** Deposit extra collateral (base-unit delta) into an open channel. */\n depositToChannel?(\n channelId: string,\n amount: string | bigint\n ): Promise<{ channelId: string; txHash?: string; depositTotal: string }>;\n /** Close a channel — starts the settlement challenge window (on-chain). */\n closeChannel?(channelId: string): Promise<{\n channelId: string;\n txHash?: string;\n /** Unix SECONDS, string-encoded. */\n closedAt: string;\n settleableAt: string;\n }>;\n /** Settle a closed channel after its window — releases funds (on-chain). */\n settleChannel?(\n channelId: string\n ): Promise<{ channelId: string; txHash?: string }>;\n /**\n * Free FULL multi-chain wallet view (#299) — native coin + configured tokens\n * per chain — works on an UNSTARTED client (Solana/Mina addresses are derived\n * from the mnemonic on demand). `fallback` supplies Solana/Mina channel params\n * to read WHEN the client config has none, without touching settlement config.\n */\n getWalletBalances?(\n fallback?: WalletViewFallback\n ): Promise<WalletChainBalanceInfo[]>;\n}\n\n/**\n * Wallet-view-only Solana/Mina channel defaults (#299): RPC/GraphQL + token used\n * to READ the balance of a chain the identity has no configured channel for, so\n * `rig balance` shows all three chains (0 for a not-yet-on-chain account). These\n * NEVER enter the client's settlement config — they cannot influence chain\n * negotiation — so a preset default here is safe even for a chain the wallet\n * cannot yet settle on.\n */\nexport interface WalletViewFallback {\n solanaChannel?: ToonClientConfig['solanaChannel'];\n minaChannel?: ToonClientConfig['minaChannel'];\n}\n\nexport interface StandalonePublisherOptions {\n /**\n * ToonClient config (mnemonic + mnemonicAccountIndex + proxy/BTP uplink +\n * settlement fields — see `packages/client/src/config.ts`). Exactly one of\n * `clientConfig` | `client` is required.\n */\n clientConfig?: ToonClientConfig;\n /** Pre-built client (tests / advanced callers). */\n client?: ToonClientLike;\n /**\n * ILP route for event publishes (relay `/write`). Default: derived from the\n * config's `destinationAddress` anchor (`<base>.relay.store` → `<base>.relay`,\n * matching the daemon's route derivation), else the anchor itself.\n */\n publishDestination?: string;\n /**\n * ILP route for git-object uploads (store `/store` → Arweave). Default:\n * derived like `publishDestination` (`<base>.relay.store` → `<base>.store`).\n */\n storeDestination?: string;\n /**\n * ILP destination the payment channel anchors to. Default: the client\n * config's `destinationAddress`.\n */\n channelDestination?: string;\n /** Flat fee per published event (daemon `feePerEvent` convention). Default 1n. */\n eventFee?: bigint;\n /**\n * FLAT per-packet route prices for the publish/store destinations, as the\n * payment peer announces them (kind:10032 `capabilities` — e.g.\n * `{capability:'os.publish', address:'g.proxy.relay', price:'1000'}`) or as\n * the terminating connector reports them from `GET /ilp/routes/price`.\n *\n * The connector gates every paid packet at the destination route's price: a\n * balance-proof claim advancing the channel by less is rejected (F06). Since\n * ADR 0020 (toon-client#452) a price is flat per handler, so `store` is the\n * WHOLE upload fee rather than a floor under a per-byte computation — an\n * upload of any size costs it. `publish` still floors {@link eventFee},\n * which remains a caller-chosen flat figure.\n *\n * A destination without a known price prices its packets at 0 here and lets\n * the connector be the one to refuse — this class never invents a rate.\n */\n routePrices?: { publish?: bigint; store?: bigint };\n /** Daemon control API port probed by the nonce guard. */\n daemonPort?: number;\n /**\n * Skip Guard 1 (the same-identity daemon refusal, {@link checkDaemonIdentity})\n * — the force-standalone override ({@link standaloneForced} / RIG_STANDALONE\n * / `rig --standalone`). Guard 2, the exclusive per-identity {@link NonceLock},\n * ALWAYS runs regardless, so two standalone processes still can't race the\n * channel watermark. Default false (daemon-aware).\n */\n skipDaemonCheck?: boolean;\n /** Directory for the per-identity advisory lockfile. */\n lockDir?: string;\n /** Fetch impl for the daemon probe (tests). */\n fetchImpl?: typeof fetch;\n /**\n * Peer→channel map store (#262): start() resumes the channel recorded for\n * (identity, channel anchor) instead of opening a fresh one, and records\n * any fresh lazy open for the next invocation. Absent (embedded callers\n * managing their own channel lifecycle): the historical behaviour — open\n * lazily every run, record nothing.\n */\n channelMap?: ChannelMapStore;\n /**\n * Per-chain settlement parameters to BACK-FILL into the client's peer\n * negotiations after start (#264/#260): peers whose kind:10032 announce\n * carries no `tokenNetworks`/`preferredTokens` negotiate with those fields\n * empty, and the on-chain channel open then fails (\"tokenNetwork address\n * is required\"). Values here never override what a peer DID announce —\n * they only fill gaps, keyed by the negotiated chain id.\n */\n negotiationFallbacks?: {\n tokenNetworks?: Record<string, string>;\n preferredTokens?: Record<string, string>;\n };\n /** Sink for non-fatal channel-persistence warnings (default: stderr). */\n warn?: (line: string) => void;\n}\n\n/** A relay/store rejected a paid write (fee NOT spent iff the claim failed too). */\nexport class StandalonePublishError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StandalonePublishError';\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route derivation (duplicated from client-mcp daemon/config.ts — that\n// package depends on this one for #227, so importing it back would be\n// circular; keep in sync)\n// ---------------------------------------------------------------------------\n\n/**\n * Derive publish/store routes from the channel anchor. Behind the devnet\n * proxy the anchor is `<base>.relay.store` (e.g. `g.proxy.relay.store`):\n * publishes terminate at `<base>.relay`, uploads at `<base>.store`. Anchors\n * not matching the convention pass through unchanged.\n */\nexport function deriveRouteDestinations(anchor: string): {\n publish: string;\n store: string;\n} {\n const segs = anchor.split('.');\n if (segs.at(-1) === 'store' && segs.at(-2) === 'relay') {\n const base = segs.slice(0, -2).join('.');\n return { publish: `${base}.relay`, store: `${base}.store` };\n }\n return { publish: anchor, store: anchor };\n}\n\n// ---------------------------------------------------------------------------\n// FULFILL → Arweave txId\n// ---------------------------------------------------------------------------\n\n/**\n * Decode the Arweave txId from a store-write answer.\n *\n * Re-exported from `@toon-protocol/client` rather than reimplemented: this\n * file used to carry its own copy because the client's extractor was thought\n * not to be exported. It is (and always was), and now that the answer is a\n * sealed response envelope rather than HTTP text there is exactly one shape\n * to read — a second reader of it could only drift.\n */\nexport { extractArweaveTxId };\n\n// ---------------------------------------------------------------------------\n// Channel-resume introspection (#262)\n//\n// The peer negotiation table and the ChannelManager's peer→channel map are\n// PRIVATE on ToonClient, so resuming a channel reaches into them through a\n// structural runtime cast — the exact pattern the daemon uses\n// (client-mcp/src/daemon/client-runner.ts, openOrResumeApexChannel /\n// routeChildPeersThroughApexChannel). Keep the shapes in sync with\n// @toon-protocol/client's ToonClient/ChannelManager.\n// ---------------------------------------------------------------------------\n\n/** The slice of a `PeerNegotiation` the channel map records. */\ninterface NegotiationLike {\n chain: string;\n chainType: string;\n chainId: number | string;\n settlementAddress: string;\n tokenAddress?: string;\n tokenNetwork?: string;\n}\n\ninterface ChannelInternals {\n peerNegotiations?: Map<string, NegotiationLike>;\n channelManager?: {\n trackChannel?: (\n channelId: string,\n context: PersistedChannelContext\n ) => void;\n peerChannels?: Map<string, string>;\n };\n /**\n * The client's on-chain channel client caches per-channel chain context at\n * OPEN time only; close/settle/deposit on a channel resumed from the map\n * would throw \"No on-chain context\" without re-seeding it (#263). Same\n * structural-cast contract as the rest of this block — keep in sync with\n * `@toon-protocol/client`'s OnChainChannelClient.\n */\n onChainChannelClient?: {\n channelContext?: Map<\n string,\n { chain: string; tokenNetworkAddress: string; tokenAddress?: string }\n >;\n };\n}\n\n/** Best-effort access to the client's private negotiation/channel state. */\nfunction channelInternals(client: ToonClientLike): ChannelInternals {\n const c = client as unknown as ChannelInternals;\n const negotiations =\n c.peerNegotiations instanceof Map ? c.peerNegotiations : undefined;\n const cm =\n c.channelManager &&\n typeof c.channelManager.trackChannel === 'function' &&\n c.channelManager.peerChannels instanceof Map\n ? c.channelManager\n : undefined;\n const onChain =\n c.onChainChannelClient &&\n c.onChainChannelClient.channelContext instanceof Map\n ? c.onChainChannelClient\n : undefined;\n return {\n ...(negotiations ? { peerNegotiations: negotiations } : {}),\n ...(cm ? { channelManager: cm } : {}),\n ...(onChain ? { onChainChannelClient: onChain } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// StandalonePublisher\n// ---------------------------------------------------------------------------\n\nexport class StandalonePublisher implements Publisher {\n private readonly client: ToonClientLike;\n private readonly ownsClient: boolean;\n private readonly publishDestination: string | undefined;\n private readonly storeDestination: string | undefined;\n private readonly channelDestination: string | undefined;\n private readonly eventFee: bigint;\n private readonly routePrices:\n | StandalonePublisherOptions['routePrices']\n | undefined;\n private readonly daemonPort: number | undefined;\n private readonly skipDaemonCheck: boolean;\n private readonly lockDir: string | undefined;\n private readonly fetchImpl: typeof fetch | undefined;\n private readonly channelMap: ChannelMapStore | undefined;\n /** ILP anchor the channel is keyed by in the map (peer/apex destination). */\n private readonly channelAnchor: string | undefined;\n private readonly negotiationFallbacks:\n | StandalonePublisherOptions['negotiationFallbacks']\n | undefined;\n private readonly warn: (line: string) => void;\n\n private lock: NonceLock | undefined;\n private channelId: string | undefined;\n private readyPromise: Promise<void> | undefined;\n /** Guard + client start only (no channel) — see {@link startClientOnly}. */\n private clientReadyPromise: Promise<void> | undefined;\n /** True when the last start() RESUMED the recorded channel (#263). */\n private lastOpenResumed = false;\n\n constructor(options: StandalonePublisherOptions) {\n if (options.client && options.clientConfig) {\n throw new Error(\n 'StandalonePublisher: provide either `clientConfig` or `client`, not both'\n );\n }\n if (options.client) {\n this.client = options.client;\n this.ownsClient = false;\n } else if (options.clientConfig) {\n this.client = new ToonClient(options.clientConfig);\n this.ownsClient = true;\n } else {\n throw new Error(\n 'StandalonePublisher: one of `clientConfig` (mnemonic-based ToonClient config) or `client` is required'\n );\n }\n\n // Routes: explicit option → derived from the channel anchor (same\n // `<base>.relay.store` convention the daemon resolves).\n const anchor =\n options.channelDestination ?? options.clientConfig?.destinationAddress;\n const routes = anchor ? deriveRouteDestinations(anchor) : undefined;\n this.publishDestination = options.publishDestination ?? routes?.publish;\n this.storeDestination = options.storeDestination ?? routes?.store;\n this.channelDestination = options.channelDestination;\n\n this.eventFee = options.eventFee ?? 1n;\n this.routePrices = options.routePrices;\n this.daemonPort = options.daemonPort;\n this.skipDaemonCheck = options.skipDaemonCheck ?? false;\n this.lockDir = options.lockDir;\n this.fetchImpl = options.fetchImpl;\n this.channelMap = options.channelMap;\n this.channelAnchor = anchor;\n this.negotiationFallbacks = options.negotiationFallbacks;\n this.warn = options.warn ?? ((line) => process.stderr.write(`${line}\\n`));\n }\n\n /** Hex Nostr pubkey of the embedded identity (available before start). */\n getPublicKey(): string {\n return this.client.getPublicKey();\n }\n\n /**\n * Run the nonce guard, start the embedded client, and open (or resume) the\n * payment channel. Called lazily by the first paid operation; safe to call\n * eagerly to fail fast. Idempotent.\n */\n start(): Promise<void> {\n this.readyPromise ??= (async () => {\n await this.startClientOnly();\n try {\n this.channelId = await this.openOrResumeChannel();\n } catch (err) {\n // Release the identity lock on a failed channel open (pre-#263\n // behaviour): nothing holds claims yet, so another process may go.\n this.lock?.release();\n this.lock = undefined;\n this.clientReadyPromise = undefined;\n throw err;\n }\n })().catch((err: unknown) => {\n // Let a later call retry (e.g. after the conflicting daemon stops).\n this.readyPromise = undefined;\n throw err;\n });\n return this.readyPromise;\n }\n\n /**\n * Run the nonce guard and start the embedded client WITHOUT touching the\n * payment channel (#263): `channel close`/`settle` operate on a RECORDED\n * channel and must never open a fresh one as a side effect of starting.\n * Idempotent; `start()` layers the channel open/resume on top of this.\n */\n startClientOnly(): Promise<void> {\n this.clientReadyPromise ??= this.doStartClient().catch((err: unknown) => {\n this.clientReadyPromise = undefined;\n throw err;\n });\n return this.clientReadyPromise;\n }\n\n private async doStartClient(): Promise<void> {\n const pubkey = this.client.getPublicKey();\n\n // Guard 1: refuse while a toon-clientd holds this identity — UNLESS the\n // force-standalone override is set (RIG_STANDALONE / `rig --standalone`),\n // in which case the caller has explicitly chosen to sign for itself and\n // ignore the daemon. Guard 2 (the NonceLock below) still runs either way.\n if (!this.skipDaemonCheck) {\n await checkDaemonIdentity(pubkey, {\n ...(this.daemonPort !== undefined ? { port: this.daemonPort } : {}),\n ...(this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}),\n });\n }\n\n // Guard 2: exclusive advisory lock against other standalone processes.\n this.lock = await NonceLock.acquire(pubkey, {\n ...(this.lockDir !== undefined ? { dir: this.lockDir } : {}),\n });\n\n try {\n if (this.client.isStarted?.() !== true) {\n await this.client.start();\n }\n // #264: back-fill negotiation gaps as soon as the client's bootstrap\n // negotiation exists — before any channel open (start()) or recorded-\n // channel operation reads them.\n this.applyNegotiationFallbacks();\n } catch (err) {\n this.lock.release();\n this.lock = undefined;\n throw err;\n }\n }\n\n /**\n * Back-fill negotiated peer metadata the announce did not carry\n * (#264/#260 root cause 3): after the client's bootstrap negotiation, any\n * peer negotiation missing `tokenNetwork`/`tokenAddress` gets the derived\n * per-chain fallback for its negotiated chain, BEFORE the lazy channel\n * open reads them. Announced values are never overridden.\n */\n private applyNegotiationFallbacks(): void {\n const fallbacks = this.negotiationFallbacks;\n if (!fallbacks) return;\n const negotiations = channelInternals(this.client).peerNegotiations;\n if (!negotiations) {\n this.warn(\n 'rig: settlement fallbacks configured but the client does not ' +\n 'expose negotiation internals — on-chain channel opening may fail ' +\n 'if the peer announced no TokenNetwork'\n );\n return;\n }\n for (const negotiation of negotiations.values()) {\n if (!negotiation.tokenNetwork) {\n const tokenNetwork = fallbacks.tokenNetworks?.[negotiation.chain];\n if (tokenNetwork) negotiation.tokenNetwork = tokenNetwork;\n }\n if (!negotiation.tokenAddress) {\n const tokenAddress = fallbacks.preferredTokens?.[negotiation.chain];\n if (tokenAddress) negotiation.tokenAddress = tokenAddress;\n }\n }\n }\n\n /**\n * Open the payment channel — or, when the channel map has a record for\n * (identity, anchor), RESUME the recorded on-chain channel (#262).\n *\n * Resume seeds the client's ChannelManager (`trackChannel` rehydrates the\n * cumulative-claim watermark from channels.json; `peerChannels` makes the\n * subsequent `openChannel` return the same id instead of opening on-chain).\n * A fresh open is RECORDED so the next invocation resumes it. A corrupt map\n * file throws BEFORE anything is opened — never a silent duplicate open.\n */\n private async openOrResumeChannel(): Promise<string> {\n const map = this.channelMap;\n if (!map) {\n // No persistence configured: historical lazy open, nothing recorded.\n return this.client.openChannel(this.channelDestination);\n }\n if (!this.channelAnchor) {\n this.warn(\n 'rig: no channel anchor destination configured — the peer→channel ' +\n 'mapping cannot be persisted, so this run may open a fresh channel'\n );\n return this.client.openChannel(this.channelDestination);\n }\n\n const anchor = this.channelAnchor;\n const identity = this.client.getPublicKey();\n // Corruption check happens HERE, before any on-chain open (throws).\n const candidates = map.listFor(identity, anchor);\n const internals = channelInternals(this.client);\n const resumed = await this.resumeRecordedChannel(\n map,\n candidates,\n internals\n );\n\n // Idempotent — returns the (resumed or existing) channel for the peer if\n // one is tracked, else opens lazily on-chain.\n const channelId = await this.client.openChannel(this.channelDestination);\n\n if (resumed && channelId === resumed.channelId) {\n this.lastOpenResumed = true;\n map.touch(recordKey(resumed));\n } else {\n this.lastOpenResumed = false;\n this.recordOpenedChannel(map, internals, identity, anchor, channelId);\n }\n return channelId;\n }\n\n /**\n * Try to resume one recorded channel: the first candidate whose peer is\n * still negotiated on the SAME chain + tokenNetwork and whose watermark\n * does not show it closed/settled. Returns the resumed record, if any.\n */\n private async resumeRecordedChannel(\n map: ChannelMapStore,\n candidates: ChannelMapRecord[],\n internals: ChannelInternals\n ): Promise<ChannelMapRecord | undefined> {\n if (candidates.length === 0) return undefined;\n const cm = internals.channelManager;\n if (!cm?.trackChannel || !cm.peerChannels) {\n this.warn(\n 'rig: a recorded channel exists but the client does not expose ' +\n 'channel internals to resume it — a fresh channel may be opened'\n );\n return undefined;\n }\n\n for (const record of candidates) {\n // The peer must still be negotiated on the recorded chain/tokenNetwork;\n // a rotated peer identity or re-negotiated settlement gets a fresh\n // channel (recorded under its own key) instead of stale claims.\n const negotiation = internals.peerNegotiations?.get(record.peerId);\n if (\n !negotiation ||\n negotiation.chain !== record.chain ||\n (negotiation.tokenNetwork ?? '') !== record.tokenNetwork\n ) {\n continue;\n }\n\n // Never resume a channel the withdraw flow already closed/settled.\n const watermark = map.readWatermark(record.channelId);\n if (\n watermark?.closedAt !== undefined ||\n watermark?.settledAt !== undefined\n ) {\n continue;\n }\n if (!watermark) {\n // Fresh channels are seeded at record time, so a missing entry means\n // the watermark store was lost. Resuming from nonce 0 fails SAFE: a\n // regressed cumulative claim is rejected by the peer (no double\n // spend), but warn so the failure is diagnosable.\n this.warn(\n `rig: resuming channel ${record.channelId} with no local claim ` +\n `watermark (${map.watermarkPath}) — if this channel was claimed ` +\n 'against before, the peer will reject the stale claims; remove ' +\n `its entry from ${map.mapPath} to open a fresh channel instead`\n );\n }\n\n // trackChannel rehydrates nonce/cumulative from the watermark store;\n // seeding peerChannels makes ensureChannel/openChannel reuse the id.\n cm.trackChannel(record.channelId, record.context);\n cm.peerChannels.set(record.peerId, record.channelId);\n\n // Persisted channel state omits the on-chain deposit — re-read it so\n // fee/balance accounting is right (EVM only; mirrors the daemon).\n // #279 latency trim: skipped when the record already carries a\n // depositTotal — deposits only change through rig's own flows (open\n // --deposit / a later resume), which update the record; this is\n // display/accounting state, not the claim watermark, so a stale value\n // can never double-spend (the peer enforces the cumulative claim).\n if (\n record.context.chainType === 'evm' &&\n record.depositTotal === undefined &&\n this.client.rehydrateChannelDeposit\n ) {\n try {\n const deposit = await this.client.rehydrateChannelDeposit(\n record.channelId,\n {\n chain: record.chain,\n tokenNetworkAddress: record.context.tokenNetworkAddress,\n }\n );\n if (deposit !== undefined) {\n map.touch(recordKey(record), {\n depositTotal: deposit.toString(),\n });\n }\n } catch (err) {\n this.warn(\n `rig: deposit re-read for resumed channel ${record.channelId} ` +\n `failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n return record;\n }\n return undefined;\n }\n\n /** Record a freshly opened channel (+ seed its claim watermark at 0/0). */\n private recordOpenedChannel(\n map: ChannelMapStore,\n internals: ChannelInternals,\n identity: string,\n destination: string,\n channelId: string\n ): void {\n let peerId: string | undefined;\n for (const [peer, channel] of internals.channelManager?.peerChannels ??\n []) {\n if (channel === channelId) {\n peerId = peer;\n break;\n }\n }\n const negotiation =\n peerId !== undefined\n ? internals.peerNegotiations?.get(peerId)\n : undefined;\n if (peerId === undefined || !negotiation) {\n this.warn(\n `rig: opened channel ${channelId} but could not record the ` +\n 'peer→channel mapping (client does not expose negotiation ' +\n 'internals) — the NEXT invocation may open another channel'\n );\n return;\n }\n\n let depositTotal: bigint | undefined;\n try {\n depositTotal = this.client.getChannelDepositTotal?.(channelId);\n } catch {\n // deposit unknown — recorded without it; a later resume re-reads it\n }\n\n map.record({\n channelId,\n peerId,\n identity,\n destination,\n chain: negotiation.chain,\n tokenNetwork: negotiation.tokenNetwork ?? '',\n // Context mirrors the daemon's persistApexChannel shape — exactly what\n // trackChannel needs on resume.\n context: {\n chainType: negotiation.chainType,\n chainId:\n typeof negotiation.chainId === 'number' ? negotiation.chainId : 0,\n tokenNetworkAddress: negotiation.tokenNetwork ?? '',\n ...(negotiation.tokenAddress\n ? { tokenAddress: negotiation.tokenAddress }\n : {}),\n recipient: negotiation.settlementAddress,\n },\n ...(depositTotal !== undefined && depositTotal > 0n\n ? { depositTotal: depositTotal.toString() }\n : {}),\n });\n // Seed nonce 0 / cumulative 0 so a later resume can tell \"never claimed\n // against\" apart from \"watermark lost\" (which only fails claim-rejected).\n map.seedWatermark(channelId);\n }\n\n /** Release the identity lock and stop the embedded client (if we own it). */\n async stop(): Promise<void> {\n this.lock?.release();\n this.lock = undefined;\n this.readyPromise = undefined;\n this.clientReadyPromise = undefined;\n this.channelId = undefined;\n // Never stop a client that was never started (`ToonClient.stop()` throws\n // INVALID_STATE) — e.g. after a free `rig balance` read (#263).\n if (this.ownsClient && this.client.isStarted?.() !== false) {\n await this.client.stop();\n }\n }\n\n // ── Money lifecycle (#263) ──────────────────────────────────────────────────\n\n /**\n * Explicit `rig channel open`: the SAME resume-or-open path the lazy paid\n * writes use (guard → start → resume the recorded channel or open + record\n * a fresh one), surfaced with a receipt — plus an optional extra collateral\n * deposit on top of the open/resume.\n */\n async openChannelExplicit(opts?: {\n /** Extra collateral to deposit AFTER the open/resume (base units). */\n deposit?: bigint;\n }): Promise<ChannelOpenOutcome> {\n await this.start();\n const channelId = this.requireChannel();\n const identity = this.client.getPublicKey();\n const destination = this.channelAnchor ?? '';\n const record =\n this.channelMap && this.channelAnchor\n ? this.channelMap\n .listFor(identity, this.channelAnchor)\n .find((r) => r.channelId === channelId)\n : undefined;\n\n const outcome: ChannelOpenOutcome = {\n channelId,\n resumed: this.lastOpenResumed,\n destination,\n ...(record ? { chain: record.chain, peerId: record.peerId } : {}),\n ...(record?.depositTotal !== undefined\n ? { depositTotal: record.depositTotal }\n : {}),\n };\n\n if (opts?.deposit !== undefined && opts.deposit > 0n) {\n if (!this.client.depositToChannel) {\n throw new StandalonePublishError(\n 'this client build does not support channel deposits ' +\n '(depositToChannel is unavailable)'\n );\n }\n const deposited = await this.client.depositToChannel(\n channelId,\n opts.deposit\n );\n outcome.depositAdded = opts.deposit.toString();\n outcome.depositTotal = deposited.depositTotal;\n if (deposited.txHash) outcome.depositTxHash = deposited.txHash;\n if (record) {\n this.channelMap?.touch(recordKey(record), {\n depositTotal: deposited.depositTotal,\n });\n }\n }\n return outcome;\n }\n\n /**\n * Adopt a RECORDED channel into the running client so on-chain close/\n * settle/deposit can act on it: `trackChannel` rehydrates the claim\n * watermark + withdraw timers from channels.json, `peerChannels` binds the\n * peer, and the on-chain client's context cache is re-seeded (it only\n * learns context at open time — a resumed channel would otherwise throw\n * \"No on-chain context\").\n */\n private adoptRecordedChannel(record: ChannelMapRecord): void {\n const internals = channelInternals(this.client);\n const cm = internals.channelManager;\n if (cm?.trackChannel && cm.peerChannels) {\n cm.trackChannel(record.channelId, record.context);\n cm.peerChannels.set(record.peerId, record.channelId);\n }\n const contextCache = internals.onChainChannelClient?.channelContext;\n if (contextCache && !contextCache.has(record.channelId)) {\n contextCache.set(record.channelId, {\n chain: record.chain,\n tokenNetworkAddress: record.context.tokenNetworkAddress,\n ...(record.context.tokenAddress\n ? { tokenAddress: record.context.tokenAddress }\n : {}),\n });\n }\n }\n\n /**\n * Close a recorded channel: starts the on-chain settlement challenge\n * window. The client persists `closedAt`/`settleableAt` into the claim\n * watermark store, which is exactly where `rig channel list`/`balance`\n * derive the closing/settleable/settled status from. Guard + client start,\n * but NEVER a channel open ({@link startClientOnly}).\n */\n async closeRecordedChannel(\n record: ChannelMapRecord\n ): Promise<ChannelCloseOutcome> {\n await this.startClientOnly();\n if (!this.client.closeChannel) {\n throw new StandalonePublishError(\n 'this client build does not support closing channels ' +\n '(closeChannel is unavailable)'\n );\n }\n this.adoptRecordedChannel(record);\n const result = await this.client.closeChannel(record.channelId);\n this.channelMap?.touch(recordKey(record));\n return result;\n }\n\n /**\n * Settle a recorded channel after its challenge window — releases the\n * remaining collateral. The client enforces the `now >= settleableAt` time\n * guard BEFORE spending gas (a too-early call throws its retryable\n * SettleTooEarlyError) and persists `settledAt` into the watermark store.\n */\n async settleRecordedChannel(\n record: ChannelMapRecord\n ): Promise<ChannelSettleOutcome> {\n await this.startClientOnly();\n if (!this.client.settleChannel) {\n throw new StandalonePublishError(\n 'this client build does not support settling channels ' +\n '(settleChannel is unavailable)'\n );\n }\n this.adoptRecordedChannel(record);\n const result = await this.client.settleChannel(record.channelId);\n this.channelMap?.touch(recordKey(record));\n return result;\n }\n\n /**\n * The full multi-chain wallet view (#299) for the embedded identity — native\n * coin + configured tokens (USDC) per chain — a FREE read on the UNSTARTED\n * client (no nonce guard, no uplink, no channel). The client derives the\n * Solana/Mina addresses from the mnemonic on demand, so ALL configured chains\n * appear even before a start. Best-effort per chain (an unreachable RPC yields\n * an `unreadable` chain, not a failure). `fallback` supplies Solana/Mina RPC\n * defaults (e.g. the network preset) for chains the config has no channel for.\n */\n async readWalletChainBalances(\n fallback?: WalletViewFallback\n ): Promise<WalletChainBalanceInfo[]> {\n if (!this.client.getWalletBalances) return [];\n return await this.client.getWalletBalances(fallback);\n }\n\n // ── Publisher ─────────────────────────────────────────────────────────────\n\n /**\n * Fee rates for `planPush` estimation, both flat per packet (ADR 0020),\n * with the announced route prices folded in so the ESTIMATE always equals\n * the CLAIMS: `eventFee` is returned pre-floored at the publish route price,\n * and `uploadFee` IS the store route price.\n */\n getFeeRates(): Promise<FeeRates> {\n return Promise.resolve({\n uploadFee: this.uploadFee(),\n eventFee: this.effectiveEventFee(),\n });\n }\n\n /**\n * The flat per-event claim: `eventFee`, floored at the publish route's\n * announced price (the connector rejects a claim below it — F06).\n */\n private effectiveEventFee(): bigint {\n const min = this.routePrices?.publish ?? 0n;\n return this.eventFee > min ? this.eventFee : min;\n }\n\n /**\n * The per-upload claim: the store route's flat price, whatever the object's\n * size. Byte-proportional pricing has no successor (ADR 0020) — the route\n * table is the price list — and the connector charges this figure whether\n * the body is 100 bytes or 100 KB.\n */\n private uploadFee(): bigint {\n return this.routePrices?.store ?? 0n;\n }\n\n /**\n * Upload one git object as a kind:5094 store write (Git-SHA/Git-Type/Repo\n * tagged — the proven seed-pipeline shape), signing one balance-proof claim\n * for the store route's flat price\n * ({@link StandalonePublisherOptions.routePrices}) — ADR 0020 prices a\n * packet per handler, not per byte.\n *\n * #368: a blob's `Content-Type` is derived from its path extension (else\n * octet-stream) and sent in the `output` tag, which the store forwards onto\n * the Arweave upload's Content-Type — so a gateway serves `index.html` as\n * `text/html`, not `application/octet-stream`. Non-blob objects (trees,\n * commits, tags) are never served as files, so they stay octet-stream.\n */\n async uploadGitObject(upload: GitObjectUpload): Promise<UploadReceipt> {\n if (upload.body.length > MAX_OBJECT_SIZE) {\n throw new StandalonePublishError(\n `git object ${upload.sha} exceeds the ${MAX_OBJECT_SIZE}-byte limit: ${upload.body.length} bytes`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const contentType =\n upload.type === 'blob'\n ? contentTypeForPath(upload.path)\n : DEFAULT_CONTENT_TYPE;\n\n const fee = this.uploadFee();\n const event = this.client.signEvent({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\n tags: [\n ['i', upload.body.toString('base64'), 'blob'],\n ['bid', fee.toString(), 'usdc'],\n ['output', contentType],\n ['Git-SHA', upload.sha],\n ['Git-Type', upload.type],\n ['Repo', upload.repoId],\n ],\n });\n\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(event, {\n ...(this.storeDestination ? { destination: this.storeDestination } : {}),\n claim,\n ilpAmount: fee,\n // The store backend serves POST /store (not the relay's /write).\n proxyPath: '/store',\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `git-object upload rejected (${upload.sha}): ${result.error ?? 'store rejected the write'}`\n );\n }\n if (!result.response) {\n throw new StandalonePublishError(\n `git-object upload FULFILL carried no sealed response (${upload.sha}); expected the Arweave tx ID`\n );\n }\n return { txId: extractArweaveTxId(result.response), feePaid: fee };\n }\n\n /**\n * Upload one raw blob (no git envelope) with an explicit `Content-Type`\n * (#368) — a kind:5094 store write carrying just the `i`/`bid`/`output`\n * tags (and an optional `Repo` provenance tag), NOT the Git-SHA/Git-Type\n * tags. Without those the store keeps the bytes verbatim instead of\n * re-deriving a git envelope, which is exactly what the ar.io path manifest\n * needs. One balance-proof claim for the store route's flat price.\n */\n async uploadBlob(upload: BlobUpload): Promise<UploadReceipt> {\n if (upload.body.length > MAX_OBJECT_SIZE) {\n throw new StandalonePublishError(\n `blob exceeds the ${MAX_OBJECT_SIZE}-byte limit: ${upload.body.length} bytes`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const fee = this.uploadFee();\n const event = this.client.signEvent({\n kind: 5094,\n content: '',\n created_at: nowSeconds(),\n tags: [\n ['i', upload.body.toString('base64'), 'blob'],\n ['bid', fee.toString(), 'usdc'],\n ['output', upload.contentType],\n ...(upload.repoId ? [['Repo', upload.repoId]] : []),\n ],\n });\n\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(event, {\n ...(this.storeDestination ? { destination: this.storeDestination } : {}),\n claim,\n ilpAmount: fee,\n proxyPath: '/store',\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `blob upload rejected: ${result.error ?? 'store rejected the write'}`\n );\n }\n if (!result.response) {\n throw new StandalonePublishError(\n 'blob upload FULFILL carried no sealed response; expected the Arweave tx ID'\n );\n }\n return { txId: extractArweaveTxId(result.response), feePaid: fee };\n }\n\n /**\n * Sign the event with the embedded identity and pay-to-publish it through\n * the relay write route, one claim for the flat per-event fee.\n *\n * `relayUrls` is the interface's plural forward-compat surface (parked\n * #84): the standalone impl routes over ILP to its single configured\n * publish destination, so more than one relay is refused rather than\n * silently half-published.\n */\n async publishEvent(\n event: UnsignedEvent,\n relayUrls: string[]\n ): Promise<PublishReceipt> {\n if (relayUrls.length > 1) {\n throw new StandalonePublishError(\n `multi-relay publish is not supported yet (got ${relayUrls.length} relays) — the standalone publisher routes to a single relay destination (#84 parked)`\n );\n }\n await this.start();\n const channelId = this.requireChannel();\n\n const signed = this.client.signEvent(event);\n const fee = this.effectiveEventFee();\n const claim = await this.client.signBalanceProof(channelId, fee);\n const result = await this.client.publishEvent(signed, {\n ...(this.publishDestination\n ? { destination: this.publishDestination }\n : {}),\n claim,\n ilpAmount: fee,\n });\n if (!result.success) {\n throw new StandalonePublishError(\n `publish rejected (kind ${event.kind}): ${result.error ?? 'relay rejected the event'}`\n );\n }\n return { eventId: result.eventId ?? signed.id, feePaid: fee };\n }\n\n private requireChannel(): string {\n if (!this.channelId) {\n throw new StandalonePublishError(\n 'no payment channel open — start() did not complete'\n );\n }\n return this.channelId;\n }\n}\n\nfunction nowSeconds(): number {\n return Math.floor(Date.now() / 1000);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -46,13 +46,6 @@ function createGitTag(opts) {
|
|
|
46
46
|
return hashGitObject("tag", Buffer.from(lines.join("\n"), "utf-8"));
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// src/publisher.ts
|
|
50
|
-
function flooredUploadFee(bytes, ratePerByte, minFee) {
|
|
51
|
-
const fee = BigInt(bytes) * ratePerByte;
|
|
52
|
-
const min = minFee ?? 0n;
|
|
53
|
-
return fee > min ? fee : min;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
49
|
export {
|
|
57
50
|
MAX_OBJECT_SIZE,
|
|
58
51
|
EMPTY_BLOB_SHA,
|
|
@@ -60,7 +53,6 @@ export {
|
|
|
60
53
|
createGitBlob,
|
|
61
54
|
createGitTree,
|
|
62
55
|
createGitCommit,
|
|
63
|
-
createGitTag
|
|
64
|
-
flooredUploadFee
|
|
56
|
+
createGitTag
|
|
65
57
|
};
|
|
66
|
-
//# sourceMappingURL=chunk-
|
|
58
|
+
//# sourceMappingURL=chunk-B5ISARMU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/objects.ts"],"sourcesContent":["/**\n * Pure git object construction and SHA-1 envelope hashing.\n *\n * Promoted from `packages/rig-web/tests/e2e/seed/lib/git-builder.ts` (#223) —\n * the proven seed pipeline builders, now the core of the Git-to-TOON write\n * path. Everything here is pure: no network, no signing, no payments.\n * Upload/publish lives with the Publisher (#226).\n *\n * Git object format: `<type> <size>\\0<content>`. The SHA-1 is computed over\n * the full envelope (header + NUL + content); the `body` (content only) is\n * what gets uploaded to Arweave.\n */\n\nimport { createHash } from 'node:crypto';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** All git object types TOON can carry. */\nexport type GitObjectType = 'blob' | 'tree' | 'commit' | 'tag';\n\nexport interface GitObject {\n /** SHA-1 hex digest computed over full git envelope */\n sha: string;\n /** Full git object (header + null + content) */\n buffer: Buffer;\n /** Body only (content after the null byte) — this is what gets uploaded */\n body: Buffer;\n}\n\n/**\n * Maximum uploadable git object body size: 95KB safety margin under the\n * 100KB free tier (R10-005). Larger objects are a hard error in v1.\n */\nexport const MAX_OBJECT_SIZE = 95 * 1024;\n\n/**\n * Git's universal empty-blob SHA-1: the object for a zero-byte file.\n *\n * It is the ONLY zero-byte object git can produce (trees, commits, and tags\n * are never empty) and it always has this exact constant value. The store\n * rejects a zero-byte kind:5094 blob upload as malformed (F00), so `rig` never\n * uploads this object — it is skipped on push and synthesized locally on\n * clone/fetch (both keyed off an ACTUAL zero-length body, never a heuristic).\n */\nexport const EMPTY_BLOB_SHA = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391';\n\n// ---------------------------------------------------------------------------\n// Envelope hashing\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a raw object body in the git envelope (`<type> <size>\\0`) and compute\n * its SHA-1. This is exactly what `git hash-object -t <type>` does.\n */\nexport function hashGitObject(type: GitObjectType, body: Buffer): GitObject {\n const header = Buffer.from(`${type} ${body.length}\\0`);\n const fullObject = Buffer.concat([header, body]);\n const sha = createHash('sha1').update(fullObject).digest('hex');\n return { sha, buffer: fullObject, body };\n}\n\n// ---------------------------------------------------------------------------\n// Git object construction\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a git blob object and compute its SHA-1.\n *\n * Format: blob <size>\\0<content>\n * SHA is over the full envelope; body is content only (for upload).\n */\nexport function createGitBlob(content: string): GitObject {\n return hashGitObject('blob', Buffer.from(content, 'utf-8'));\n}\n\n/**\n * Construct a git tree object from sorted entries.\n *\n * Format: tree <size>\\0<entries>\n * Each entry: <mode> <name>\\0<20-byte-raw-sha1>\n * Entries MUST be sorted by name (byte-wise).\n */\nexport function createGitTree(\n entries: { mode: string; name: string; sha: string }[]\n): GitObject {\n // Git sorts tree entries by raw byte order (NOT locale-aware)\n const sorted = [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0\n );\n\n const entryBuffers: Buffer[] = [];\n for (const entry of sorted) {\n const modeAndName = Buffer.from(`${entry.mode} ${entry.name}\\0`);\n // Raw 20-byte SHA-1 (NOT hex)\n const rawSha = Buffer.from(entry.sha, 'hex');\n entryBuffers.push(Buffer.concat([modeAndName, rawSha]));\n }\n\n return hashGitObject('tree', Buffer.concat(entryBuffers));\n}\n\n/**\n * Construct a git commit object.\n *\n * Format: commit <size>\\0tree <tree-sha>\\n[parent ...]\\nauthor ...\\ncommitter ...\\n\\n<message>\n * Tree/parent SHAs are hex-encoded (40 chars) in commits, unlike tree entries.\n */\nexport function createGitCommit(opts: {\n treeSha: string;\n parentSha?: string;\n authorName: string;\n authorPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `tree ${opts.treeSha}`,\n ...(opts.parentSha ? [`parent ${opts.parentSha}`] : []),\n `author ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n `committer ${opts.authorName} <${opts.authorPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('commit', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n\n/**\n * Construct an annotated git tag object.\n *\n * Format: tag <size>\\0object <sha>\\ntype <type>\\ntag <name>\\ntagger ...\\n\\n<message>\n * The tagged object is usually a commit, but git allows tagging any object\n * type (including another tag).\n */\nexport function createGitTag(opts: {\n /** SHA-1 of the object being tagged (hex, 40 chars) */\n objectSha: string;\n /** Type of the tagged object (usually 'commit') */\n objectType: GitObjectType;\n /** Tag name, e.g. 'v1.0.0' */\n tagName: string;\n taggerName: string;\n taggerPubkey: string;\n message: string;\n timestamp: number;\n}): GitObject {\n const lines = [\n `object ${opts.objectSha}`,\n `type ${opts.objectType}`,\n `tag ${opts.tagName}`,\n `tagger ${opts.taggerName} <${opts.taggerPubkey}@nostr> ${opts.timestamp} +0000`,\n '',\n opts.message,\n ];\n return hashGitObject('tag', Buffer.from(lines.join('\\n'), 'utf-8'));\n}\n"],"mappings":";AAaA,SAAS,kBAAkB;AAsBpB,IAAM,kBAAkB,KAAK;AAW7B,IAAM,iBAAiB;AAUvB,SAAS,cAAc,MAAqB,MAAyB;AAC1E,QAAM,SAAS,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,MAAM,IAAI;AACrD,QAAM,aAAa,OAAO,OAAO,CAAC,QAAQ,IAAI,CAAC;AAC/C,QAAM,MAAM,WAAW,MAAM,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK;AAC9D,SAAO,EAAE,KAAK,QAAQ,YAAY,KAAK;AACzC;AAYO,SAAS,cAAc,SAA4B;AACxD,SAAO,cAAc,QAAQ,OAAO,KAAK,SAAS,OAAO,CAAC;AAC5D;AASO,SAAS,cACd,SACW;AAEX,QAAM,SAAS,CAAC,GAAG,OAAO,EAAE;AAAA,IAAK,CAAC,GAAG,MACnC,EAAE,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI;AAAA,EAC/C;AAEA,QAAM,eAAyB,CAAC;AAChC,aAAW,SAAS,QAAQ;AAC1B,UAAM,cAAc,OAAO,KAAK,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,IAAI;AAE/D,UAAM,SAAS,OAAO,KAAK,MAAM,KAAK,KAAK;AAC3C,iBAAa,KAAK,OAAO,OAAO,CAAC,aAAa,MAAM,CAAC,CAAC;AAAA,EACxD;AAEA,SAAO,cAAc,QAAQ,OAAO,OAAO,YAAY,CAAC;AAC1D;AAQO,SAAS,gBAAgB,MAOlB;AACZ,QAAM,QAAQ;AAAA,IACZ,QAAQ,KAAK,OAAO;AAAA,IACpB,GAAI,KAAK,YAAY,CAAC,UAAU,KAAK,SAAS,EAAE,IAAI,CAAC;AAAA,IACrD,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IACxE,aAAa,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IAC3E;AAAA,IACA,KAAK;AAAA,EACP;AACA,SAAO,cAAc,UAAU,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACvE;AASO,SAAS,aAAa,MAWf;AACZ,QAAM,QAAQ;AAAA,IACZ,UAAU,KAAK,SAAS;AAAA,IACxB,QAAQ,KAAK,UAAU;AAAA,IACvB,OAAO,KAAK,OAAO;AAAA,IACnB,UAAU,KAAK,UAAU,KAAK,KAAK,YAAY,WAAW,KAAK,SAAS;AAAA,IACxE;AAAA,IACA,KAAK;AAAA,EACP;AACA,SAAO,cAAc,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC;AACpE;","names":[]}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EMPTY_BLOB_SHA,
|
|
3
3
|
MAX_OBJECT_SIZE,
|
|
4
|
-
flooredUploadFee,
|
|
5
4
|
hashGitObject
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-B5ISARMU.js";
|
|
7
6
|
import {
|
|
8
7
|
ARWEAVE_FETCH_TIMEOUT_MS,
|
|
9
8
|
ARWEAVE_GATEWAYS,
|
|
10
9
|
buildRepoAnnouncement,
|
|
11
10
|
buildRepoRefs,
|
|
12
11
|
isValidArweaveTxId
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-2KJJ7LSO.js";
|
|
14
13
|
|
|
15
14
|
// src/repo-reader.ts
|
|
16
15
|
import { execFile, spawn } from "child_process";
|
|
@@ -997,11 +996,21 @@ async function planPush(options) {
|
|
|
997
996
|
for (const ref of selected) {
|
|
998
997
|
const remoteSha = remoteState.refs.get(ref.refname) ?? null;
|
|
999
998
|
if (remoteSha === null) {
|
|
1000
|
-
refUpdates.push({
|
|
999
|
+
refUpdates.push({
|
|
1000
|
+
refname: ref.refname,
|
|
1001
|
+
localSha: ref.sha,
|
|
1002
|
+
remoteSha,
|
|
1003
|
+
kind: "new"
|
|
1004
|
+
});
|
|
1001
1005
|
continue;
|
|
1002
1006
|
}
|
|
1003
1007
|
if (remoteSha === ref.sha) {
|
|
1004
|
-
refUpdates.push({
|
|
1008
|
+
refUpdates.push({
|
|
1009
|
+
refname: ref.refname,
|
|
1010
|
+
localSha: ref.sha,
|
|
1011
|
+
remoteSha,
|
|
1012
|
+
kind: "up-to-date"
|
|
1013
|
+
});
|
|
1005
1014
|
continue;
|
|
1006
1015
|
}
|
|
1007
1016
|
let fastForward = false;
|
|
@@ -1012,9 +1021,19 @@ async function planPush(options) {
|
|
|
1012
1021
|
fastForward = false;
|
|
1013
1022
|
}
|
|
1014
1023
|
if (fastForward) {
|
|
1015
|
-
refUpdates.push({
|
|
1024
|
+
refUpdates.push({
|
|
1025
|
+
refname: ref.refname,
|
|
1026
|
+
localSha: ref.sha,
|
|
1027
|
+
remoteSha,
|
|
1028
|
+
kind: "fast-forward"
|
|
1029
|
+
});
|
|
1016
1030
|
} else if (force) {
|
|
1017
|
-
refUpdates.push({
|
|
1031
|
+
refUpdates.push({
|
|
1032
|
+
refname: ref.refname,
|
|
1033
|
+
localSha: ref.sha,
|
|
1034
|
+
remoteSha,
|
|
1035
|
+
kind: "forced"
|
|
1036
|
+
});
|
|
1018
1037
|
} else {
|
|
1019
1038
|
rejected.push({ refname: ref.refname, localSha: ref.sha, remoteSha });
|
|
1020
1039
|
}
|
|
@@ -1076,10 +1095,7 @@ async function planPush(options) {
|
|
|
1076
1095
|
}
|
|
1077
1096
|
const announceNeeded = !remoteState.announced;
|
|
1078
1097
|
const totalObjectBytes = objects.reduce((sum, o) => sum + o.size, 0);
|
|
1079
|
-
const uploadFee = objects.
|
|
1080
|
-
(sum, o) => sum + flooredUploadFee(o.size, feeRates.uploadFeePerByte, feeRates.minUploadFee),
|
|
1081
|
-
0n
|
|
1082
|
-
);
|
|
1098
|
+
const uploadFee = BigInt(objects.length) * feeRates.uploadFee;
|
|
1083
1099
|
const eventCount = 1 + (announceNeeded ? 1 : 0);
|
|
1084
1100
|
const eventFees = BigInt(eventCount) * feeRates.eventFee;
|
|
1085
1101
|
return {
|
|
@@ -1227,4 +1243,4 @@ export {
|
|
|
1227
1243
|
planPush,
|
|
1228
1244
|
executePush
|
|
1229
1245
|
};
|
|
1230
|
-
//# sourceMappingURL=chunk-
|
|
1246
|
+
//# sourceMappingURL=chunk-HJDPM37T.js.map
|