@toon-protocol/rig 3.5.3 → 4.0.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.
Files changed (34) hide show
  1. package/README.md +112 -152
  2. package/dist/chunk-CDSFVZDV.js +290 -0
  3. package/dist/chunk-CDSFVZDV.js.map +1 -0
  4. package/dist/{chunk-B5ISARMU.js → chunk-GVXBUL3C.js} +15 -2
  5. package/dist/chunk-GVXBUL3C.js.map +1 -0
  6. package/dist/chunk-LTFMPOHU.js +194 -0
  7. package/dist/chunk-LTFMPOHU.js.map +1 -0
  8. package/dist/{chunk-LCYCKWAT.js → chunk-YZRR2UO6.js} +86 -5
  9. package/dist/chunk-YZRR2UO6.js.map +1 -0
  10. package/dist/{chunk-HJDPM37T.js → chunk-ZZBA5VVL.js} +11 -7
  11. package/dist/{chunk-HJDPM37T.js.map → chunk-ZZBA5VVL.js.map} +1 -1
  12. package/dist/cli/rig.js +298 -529
  13. package/dist/cli/rig.js.map +1 -1
  14. package/dist/index.d.ts +3 -109
  15. package/dist/index.js +9 -108
  16. package/dist/index.js.map +1 -1
  17. package/dist/{publisher-BFcnZeJI.d.ts → publisher-CcL96ugq.d.ts} +58 -1
  18. package/dist/standalone/index.d.ts +241 -378
  19. package/dist/standalone/index.js +19 -11
  20. package/dist/standalone-mode-LEAC4A2L.js +390 -0
  21. package/dist/standalone-mode-LEAC4A2L.js.map +1 -0
  22. package/package.json +5 -4
  23. package/dist/chunk-4CAOEXGQ.js +0 -631
  24. package/dist/chunk-4CAOEXGQ.js.map +0 -1
  25. package/dist/chunk-B5ISARMU.js.map +0 -1
  26. package/dist/chunk-FFQXFV4W.js +0 -400
  27. package/dist/chunk-FFQXFV4W.js.map +0 -1
  28. package/dist/chunk-LCYCKWAT.js.map +0 -1
  29. package/dist/chunk-TTJCBGKW.js +0 -447
  30. package/dist/chunk-TTJCBGKW.js.map +0 -1
  31. package/dist/network-bootstrap-CTYIZMP6.js +0 -40
  32. package/dist/network-bootstrap-CTYIZMP6.js.map +0 -1
  33. package/dist/standalone-mode-LBWGU7NV.js +0 -724
  34. package/dist/standalone-mode-LBWGU7NV.js.map +0 -1
@@ -1,631 +0,0 @@
1
- import {
2
- DEFAULT_CONTENT_TYPE,
3
- NonceLock,
4
- checkDaemonIdentity,
5
- contentTypeForPath,
6
- recordKey
7
- } from "./chunk-LCYCKWAT.js";
8
- import {
9
- MAX_OBJECT_SIZE
10
- } from "./chunk-B5ISARMU.js";
11
-
12
- // src/standalone/standalone-publisher.ts
13
- import { ToonClient, extractArweaveTxId } from "@toon-protocol/client";
14
- var StandalonePublishError = class extends Error {
15
- constructor(message) {
16
- super(message);
17
- this.name = "StandalonePublishError";
18
- }
19
- };
20
- function deriveRouteDestinations(anchor) {
21
- const segs = anchor.split(".");
22
- if (segs.at(-1) === "store" && segs.at(-2) === "relay") {
23
- const base = segs.slice(0, -2).join(".");
24
- return { publish: `${base}.relay`, store: `${base}.store` };
25
- }
26
- return { publish: anchor, store: anchor };
27
- }
28
- function channelInternals(client) {
29
- const c = client;
30
- const negotiations = c.peerNegotiations instanceof Map ? c.peerNegotiations : void 0;
31
- const cm = c.channelManager && typeof c.channelManager.trackChannel === "function" && c.channelManager.peerChannels instanceof Map ? c.channelManager : void 0;
32
- const onChain = c.onChainChannelClient && c.onChainChannelClient.channelContext instanceof Map ? c.onChainChannelClient : void 0;
33
- return {
34
- ...negotiations ? { peerNegotiations: negotiations } : {},
35
- ...cm ? { channelManager: cm } : {},
36
- ...onChain ? { onChainChannelClient: onChain } : {}
37
- };
38
- }
39
- var StandalonePublisher = class {
40
- client;
41
- ownsClient;
42
- publishDestination;
43
- storeDestination;
44
- channelDestination;
45
- eventFee;
46
- routePrices;
47
- daemonPort;
48
- skipDaemonCheck;
49
- lockDir;
50
- fetchImpl;
51
- channelMap;
52
- /** ILP anchor the channel is keyed by in the map (peer/apex destination). */
53
- channelAnchor;
54
- negotiationFallbacks;
55
- warn;
56
- lock;
57
- channelId;
58
- readyPromise;
59
- /** Guard + client start only (no channel) — see {@link startClientOnly}. */
60
- clientReadyPromise;
61
- /** True when the last start() RESUMED the recorded channel (#263). */
62
- lastOpenResumed = false;
63
- constructor(options) {
64
- if (options.client && options.clientConfig) {
65
- throw new Error(
66
- "StandalonePublisher: provide either `clientConfig` or `client`, not both"
67
- );
68
- }
69
- if (options.client) {
70
- this.client = options.client;
71
- this.ownsClient = false;
72
- } else if (options.clientConfig) {
73
- this.client = new ToonClient(options.clientConfig);
74
- this.ownsClient = true;
75
- } else {
76
- throw new Error(
77
- "StandalonePublisher: one of `clientConfig` (mnemonic-based ToonClient config) or `client` is required"
78
- );
79
- }
80
- const anchor = options.channelDestination ?? options.clientConfig?.destinationAddress;
81
- const routes = anchor ? deriveRouteDestinations(anchor) : void 0;
82
- this.publishDestination = options.publishDestination ?? routes?.publish;
83
- this.storeDestination = options.storeDestination ?? routes?.store;
84
- this.channelDestination = options.channelDestination;
85
- this.eventFee = options.eventFee ?? 1n;
86
- this.routePrices = options.routePrices;
87
- this.daemonPort = options.daemonPort;
88
- this.skipDaemonCheck = options.skipDaemonCheck ?? false;
89
- this.lockDir = options.lockDir;
90
- this.fetchImpl = options.fetchImpl;
91
- this.channelMap = options.channelMap;
92
- this.channelAnchor = anchor;
93
- this.negotiationFallbacks = options.negotiationFallbacks;
94
- this.warn = options.warn ?? ((line) => process.stderr.write(`${line}
95
- `));
96
- }
97
- /** Hex Nostr pubkey of the embedded identity (available before start). */
98
- getPublicKey() {
99
- return this.client.getPublicKey();
100
- }
101
- /**
102
- * Run the nonce guard, start the embedded client, and open (or resume) the
103
- * payment channel. Called lazily by the first paid operation; safe to call
104
- * eagerly to fail fast. Idempotent.
105
- */
106
- start() {
107
- this.readyPromise ??= (async () => {
108
- await this.startClientOnly();
109
- try {
110
- this.channelId = await this.openOrResumeChannel();
111
- } catch (err) {
112
- this.lock?.release();
113
- this.lock = void 0;
114
- this.clientReadyPromise = void 0;
115
- throw err;
116
- }
117
- })().catch((err) => {
118
- this.readyPromise = void 0;
119
- throw err;
120
- });
121
- return this.readyPromise;
122
- }
123
- /**
124
- * Run the nonce guard and start the embedded client WITHOUT touching the
125
- * payment channel (#263): `channel close`/`settle` operate on a RECORDED
126
- * channel and must never open a fresh one as a side effect of starting.
127
- * Idempotent; `start()` layers the channel open/resume on top of this.
128
- */
129
- startClientOnly() {
130
- this.clientReadyPromise ??= this.doStartClient().catch((err) => {
131
- this.clientReadyPromise = void 0;
132
- throw err;
133
- });
134
- return this.clientReadyPromise;
135
- }
136
- async doStartClient() {
137
- const pubkey = this.client.getPublicKey();
138
- if (!this.skipDaemonCheck) {
139
- await checkDaemonIdentity(pubkey, {
140
- ...this.daemonPort !== void 0 ? { port: this.daemonPort } : {},
141
- ...this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}
142
- });
143
- }
144
- this.lock = await NonceLock.acquire(pubkey, {
145
- ...this.lockDir !== void 0 ? { dir: this.lockDir } : {}
146
- });
147
- try {
148
- if (this.client.isStarted?.() !== true) {
149
- await this.client.start();
150
- }
151
- this.applyNegotiationFallbacks();
152
- } catch (err) {
153
- this.lock.release();
154
- this.lock = void 0;
155
- throw err;
156
- }
157
- }
158
- /**
159
- * Back-fill negotiated peer metadata the announce did not carry
160
- * (#264/#260 root cause 3): after the client's bootstrap negotiation, any
161
- * peer negotiation missing `tokenNetwork`/`tokenAddress` gets the derived
162
- * per-chain fallback for its negotiated chain, BEFORE the lazy channel
163
- * open reads them. Announced values are never overridden.
164
- */
165
- applyNegotiationFallbacks() {
166
- const fallbacks = this.negotiationFallbacks;
167
- if (!fallbacks) return;
168
- const negotiations = channelInternals(this.client).peerNegotiations;
169
- if (!negotiations) {
170
- this.warn(
171
- "rig: settlement fallbacks configured but the client does not expose negotiation internals \u2014 on-chain channel opening may fail if the peer announced no TokenNetwork"
172
- );
173
- return;
174
- }
175
- for (const negotiation of negotiations.values()) {
176
- if (!negotiation.tokenNetwork) {
177
- const tokenNetwork = fallbacks.tokenNetworks?.[negotiation.chain];
178
- if (tokenNetwork) negotiation.tokenNetwork = tokenNetwork;
179
- }
180
- if (!negotiation.tokenAddress) {
181
- const tokenAddress = fallbacks.preferredTokens?.[negotiation.chain];
182
- if (tokenAddress) negotiation.tokenAddress = tokenAddress;
183
- }
184
- }
185
- }
186
- /**
187
- * Open the payment channel — or, when the channel map has a record for
188
- * (identity, anchor), RESUME the recorded on-chain channel (#262).
189
- *
190
- * Resume seeds the client's ChannelManager (`trackChannel` rehydrates the
191
- * cumulative-claim watermark from channels.json; `peerChannels` makes the
192
- * subsequent `openChannel` return the same id instead of opening on-chain).
193
- * A fresh open is RECORDED so the next invocation resumes it. A corrupt map
194
- * file throws BEFORE anything is opened — never a silent duplicate open.
195
- */
196
- async openOrResumeChannel() {
197
- const map = this.channelMap;
198
- if (!map) {
199
- return this.client.openChannel(this.channelDestination);
200
- }
201
- if (!this.channelAnchor) {
202
- this.warn(
203
- "rig: no channel anchor destination configured \u2014 the peer\u2192channel mapping cannot be persisted, so this run may open a fresh channel"
204
- );
205
- return this.client.openChannel(this.channelDestination);
206
- }
207
- const anchor = this.channelAnchor;
208
- const identity = this.client.getPublicKey();
209
- const candidates = map.listFor(identity, anchor);
210
- const internals = channelInternals(this.client);
211
- const resumed = await this.resumeRecordedChannel(
212
- map,
213
- candidates,
214
- internals
215
- );
216
- const channelId = await this.client.openChannel(this.channelDestination);
217
- if (resumed && channelId === resumed.channelId) {
218
- this.lastOpenResumed = true;
219
- map.touch(recordKey(resumed));
220
- } else {
221
- this.lastOpenResumed = false;
222
- this.recordOpenedChannel(map, internals, identity, anchor, channelId);
223
- }
224
- return channelId;
225
- }
226
- /**
227
- * Try to resume one recorded channel: the first candidate whose peer is
228
- * still negotiated on the SAME chain + tokenNetwork and whose watermark
229
- * does not show it closed/settled. Returns the resumed record, if any.
230
- */
231
- async resumeRecordedChannel(map, candidates, internals) {
232
- if (candidates.length === 0) return void 0;
233
- const cm = internals.channelManager;
234
- if (!cm?.trackChannel || !cm.peerChannels) {
235
- this.warn(
236
- "rig: a recorded channel exists but the client does not expose channel internals to resume it \u2014 a fresh channel may be opened"
237
- );
238
- return void 0;
239
- }
240
- for (const record of candidates) {
241
- const negotiation = internals.peerNegotiations?.get(record.peerId);
242
- if (!negotiation || negotiation.chain !== record.chain || (negotiation.tokenNetwork ?? "") !== record.tokenNetwork) {
243
- continue;
244
- }
245
- const watermark = map.readWatermark(record.channelId);
246
- if (watermark?.closedAt !== void 0 || watermark?.settledAt !== void 0) {
247
- continue;
248
- }
249
- if (!watermark) {
250
- this.warn(
251
- `rig: resuming channel ${record.channelId} with no local claim watermark (${map.watermarkPath}) \u2014 if this channel was claimed against before, the peer will reject the stale claims; remove its entry from ${map.mapPath} to open a fresh channel instead`
252
- );
253
- }
254
- cm.trackChannel(record.channelId, record.context);
255
- cm.peerChannels.set(record.peerId, record.channelId);
256
- if (record.context.chainType === "evm" && record.depositTotal === void 0 && this.client.rehydrateChannelDeposit) {
257
- try {
258
- const deposit = await this.client.rehydrateChannelDeposit(
259
- record.channelId,
260
- {
261
- chain: record.chain,
262
- tokenNetworkAddress: record.context.tokenNetworkAddress
263
- }
264
- );
265
- if (deposit !== void 0) {
266
- map.touch(recordKey(record), {
267
- depositTotal: deposit.toString()
268
- });
269
- }
270
- } catch (err) {
271
- this.warn(
272
- `rig: deposit re-read for resumed channel ${record.channelId} failed: ${err instanceof Error ? err.message : String(err)}`
273
- );
274
- }
275
- }
276
- return record;
277
- }
278
- return void 0;
279
- }
280
- /** Record a freshly opened channel (+ seed its claim watermark at 0/0). */
281
- recordOpenedChannel(map, internals, identity, destination, channelId) {
282
- let peerId;
283
- for (const [peer, channel] of internals.channelManager?.peerChannels ?? []) {
284
- if (channel === channelId) {
285
- peerId = peer;
286
- break;
287
- }
288
- }
289
- const negotiation = peerId !== void 0 ? internals.peerNegotiations?.get(peerId) : void 0;
290
- if (peerId === void 0 || !negotiation) {
291
- this.warn(
292
- `rig: opened channel ${channelId} but could not record the peer\u2192channel mapping (client does not expose negotiation internals) \u2014 the NEXT invocation may open another channel`
293
- );
294
- return;
295
- }
296
- let depositTotal;
297
- try {
298
- depositTotal = this.client.getChannelDepositTotal?.(channelId);
299
- } catch {
300
- }
301
- map.record({
302
- channelId,
303
- peerId,
304
- identity,
305
- destination,
306
- chain: negotiation.chain,
307
- tokenNetwork: negotiation.tokenNetwork ?? "",
308
- // Context mirrors the daemon's persistApexChannel shape — exactly what
309
- // trackChannel needs on resume.
310
- context: {
311
- chainType: negotiation.chainType,
312
- chainId: typeof negotiation.chainId === "number" ? negotiation.chainId : 0,
313
- tokenNetworkAddress: negotiation.tokenNetwork ?? "",
314
- ...negotiation.tokenAddress ? { tokenAddress: negotiation.tokenAddress } : {},
315
- recipient: negotiation.settlementAddress
316
- },
317
- ...depositTotal !== void 0 && depositTotal > 0n ? { depositTotal: depositTotal.toString() } : {}
318
- });
319
- map.seedWatermark(channelId);
320
- }
321
- /** Release the identity lock and stop the embedded client (if we own it). */
322
- async stop() {
323
- this.lock?.release();
324
- this.lock = void 0;
325
- this.readyPromise = void 0;
326
- this.clientReadyPromise = void 0;
327
- this.channelId = void 0;
328
- if (this.ownsClient && this.client.isStarted?.() !== false) {
329
- await this.client.stop();
330
- }
331
- }
332
- // ── Money lifecycle (#263) ──────────────────────────────────────────────────
333
- /**
334
- * Explicit `rig channel open`: the SAME resume-or-open path the lazy paid
335
- * writes use (guard → start → resume the recorded channel or open + record
336
- * a fresh one), surfaced with a receipt — plus an optional extra collateral
337
- * deposit on top of the open/resume.
338
- */
339
- async openChannelExplicit(opts) {
340
- await this.start();
341
- const channelId = this.requireChannel();
342
- const identity = this.client.getPublicKey();
343
- const destination = this.channelAnchor ?? "";
344
- const record = this.channelMap && this.channelAnchor ? this.channelMap.listFor(identity, this.channelAnchor).find((r) => r.channelId === channelId) : void 0;
345
- const outcome = {
346
- channelId,
347
- resumed: this.lastOpenResumed,
348
- destination,
349
- ...record ? { chain: record.chain, peerId: record.peerId } : {},
350
- ...record?.depositTotal !== void 0 ? { depositTotal: record.depositTotal } : {}
351
- };
352
- if (opts?.deposit !== void 0 && opts.deposit > 0n) {
353
- if (!this.client.depositToChannel) {
354
- throw new StandalonePublishError(
355
- "this client build does not support channel deposits (depositToChannel is unavailable)"
356
- );
357
- }
358
- const deposited = await this.client.depositToChannel(
359
- channelId,
360
- opts.deposit
361
- );
362
- outcome.depositAdded = opts.deposit.toString();
363
- outcome.depositTotal = deposited.depositTotal;
364
- if (deposited.txHash) outcome.depositTxHash = deposited.txHash;
365
- if (record) {
366
- this.channelMap?.touch(recordKey(record), {
367
- depositTotal: deposited.depositTotal
368
- });
369
- }
370
- }
371
- return outcome;
372
- }
373
- /**
374
- * Adopt a RECORDED channel into the running client so on-chain close/
375
- * settle/deposit can act on it: `trackChannel` rehydrates the claim
376
- * watermark + withdraw timers from channels.json, `peerChannels` binds the
377
- * peer, and the on-chain client's context cache is re-seeded (it only
378
- * learns context at open time — a resumed channel would otherwise throw
379
- * "No on-chain context").
380
- */
381
- adoptRecordedChannel(record) {
382
- const internals = channelInternals(this.client);
383
- const cm = internals.channelManager;
384
- if (cm?.trackChannel && cm.peerChannels) {
385
- cm.trackChannel(record.channelId, record.context);
386
- cm.peerChannels.set(record.peerId, record.channelId);
387
- }
388
- const contextCache = internals.onChainChannelClient?.channelContext;
389
- if (contextCache && !contextCache.has(record.channelId)) {
390
- contextCache.set(record.channelId, {
391
- chain: record.chain,
392
- tokenNetworkAddress: record.context.tokenNetworkAddress,
393
- ...record.context.tokenAddress ? { tokenAddress: record.context.tokenAddress } : {}
394
- });
395
- }
396
- }
397
- /**
398
- * Close a recorded channel: starts the on-chain settlement challenge
399
- * window. The client persists `closedAt`/`settleableAt` into the claim
400
- * watermark store, which is exactly where `rig channel list`/`balance`
401
- * derive the closing/settleable/settled status from. Guard + client start,
402
- * but NEVER a channel open ({@link startClientOnly}).
403
- */
404
- async closeRecordedChannel(record) {
405
- await this.startClientOnly();
406
- if (!this.client.closeChannel) {
407
- throw new StandalonePublishError(
408
- "this client build does not support closing channels (closeChannel is unavailable)"
409
- );
410
- }
411
- this.adoptRecordedChannel(record);
412
- const result = await this.client.closeChannel(record.channelId);
413
- this.channelMap?.touch(recordKey(record));
414
- return result;
415
- }
416
- /**
417
- * Settle a recorded channel after its challenge window — releases the
418
- * remaining collateral. The client enforces the `now >= settleableAt` time
419
- * guard BEFORE spending gas (a too-early call throws its retryable
420
- * SettleTooEarlyError) and persists `settledAt` into the watermark store.
421
- */
422
- async settleRecordedChannel(record) {
423
- await this.startClientOnly();
424
- if (!this.client.settleChannel) {
425
- throw new StandalonePublishError(
426
- "this client build does not support settling channels (settleChannel is unavailable)"
427
- );
428
- }
429
- this.adoptRecordedChannel(record);
430
- const result = await this.client.settleChannel(record.channelId);
431
- this.channelMap?.touch(recordKey(record));
432
- return result;
433
- }
434
- /**
435
- * The full multi-chain wallet view (#299) for the embedded identity — native
436
- * coin + configured tokens (USDC) per chain — a FREE read on the UNSTARTED
437
- * client (no nonce guard, no uplink, no channel). The client derives the
438
- * Solana/Mina addresses from the mnemonic on demand, so ALL configured chains
439
- * appear even before a start. Best-effort per chain (an unreachable RPC yields
440
- * an `unreadable` chain, not a failure). `fallback` supplies Solana/Mina RPC
441
- * defaults (e.g. the network preset) for chains the config has no channel for.
442
- */
443
- async readWalletChainBalances(fallback) {
444
- if (!this.client.getWalletBalances) return [];
445
- return await this.client.getWalletBalances(fallback);
446
- }
447
- // ── Publisher ─────────────────────────────────────────────────────────────
448
- /**
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.
453
- */
454
- getFeeRates() {
455
- return Promise.resolve({
456
- uploadFee: this.uploadFee(),
457
- eventFee: this.effectiveEventFee()
458
- });
459
- }
460
- /**
461
- * The flat per-event claim: `eventFee`, floored at the publish route's
462
- * announced price (the connector rejects a claim below it — F06).
463
- */
464
- effectiveEventFee() {
465
- const min = this.routePrices?.publish ?? 0n;
466
- return this.eventFee > min ? this.eventFee : min;
467
- }
468
- /**
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.
473
- */
474
- uploadFee() {
475
- return this.routePrices?.store ?? 0n;
476
- }
477
- /**
478
- * Upload one git object as a kind:5094 store write (Git-SHA/Git-Type/Repo
479
- * tagged — the proven seed-pipeline shape), signing one balance-proof claim
480
- * for the store route's flat price
481
- * ({@link StandalonePublisherOptions.routePrices}) — ADR 0020 prices a
482
- * packet per handler, not per byte.
483
- *
484
- * #368: a blob's `Content-Type` is derived from its path extension (else
485
- * octet-stream) and sent in the `output` tag, which the store forwards onto
486
- * the Arweave upload's Content-Type — so a gateway serves `index.html` as
487
- * `text/html`, not `application/octet-stream`. Non-blob objects (trees,
488
- * commits, tags) are never served as files, so they stay octet-stream.
489
- */
490
- async uploadGitObject(upload) {
491
- if (upload.body.length > MAX_OBJECT_SIZE) {
492
- throw new StandalonePublishError(
493
- `git object ${upload.sha} exceeds the ${MAX_OBJECT_SIZE}-byte limit: ${upload.body.length} bytes`
494
- );
495
- }
496
- await this.start();
497
- const channelId = this.requireChannel();
498
- const contentType = upload.type === "blob" ? contentTypeForPath(upload.path) : DEFAULT_CONTENT_TYPE;
499
- const fee = this.uploadFee();
500
- const event = this.client.signEvent({
501
- kind: 5094,
502
- content: "",
503
- created_at: nowSeconds(),
504
- tags: [
505
- ["i", upload.body.toString("base64"), "blob"],
506
- ["bid", fee.toString(), "usdc"],
507
- ["output", contentType],
508
- ["Git-SHA", upload.sha],
509
- ["Git-Type", upload.type],
510
- ["Repo", upload.repoId]
511
- ]
512
- });
513
- const claim = await this.client.signBalanceProof(channelId, fee);
514
- const result = await this.client.publishEvent(event, {
515
- ...this.storeDestination ? { destination: this.storeDestination } : {},
516
- claim,
517
- ilpAmount: fee,
518
- // The store backend serves POST /store (not the relay's /write).
519
- proxyPath: "/store"
520
- });
521
- if (!result.success) {
522
- throw new StandalonePublishError(
523
- `git-object upload rejected (${upload.sha}): ${result.error ?? "store rejected the write"}`
524
- );
525
- }
526
- if (!result.response) {
527
- throw new StandalonePublishError(
528
- `git-object upload FULFILL carried no sealed response (${upload.sha}); expected the Arweave tx ID`
529
- );
530
- }
531
- return { txId: extractArweaveTxId(result.response), feePaid: fee };
532
- }
533
- /**
534
- * Upload one raw blob (no git envelope) with an explicit `Content-Type`
535
- * (#368) — a kind:5094 store write carrying just the `i`/`bid`/`output`
536
- * tags (and an optional `Repo` provenance tag), NOT the Git-SHA/Git-Type
537
- * tags. Without those the store keeps the bytes verbatim instead of
538
- * re-deriving a git envelope, which is exactly what the ar.io path manifest
539
- * needs. One balance-proof claim for the store route's flat price.
540
- */
541
- async uploadBlob(upload) {
542
- if (upload.body.length > MAX_OBJECT_SIZE) {
543
- throw new StandalonePublishError(
544
- `blob exceeds the ${MAX_OBJECT_SIZE}-byte limit: ${upload.body.length} bytes`
545
- );
546
- }
547
- await this.start();
548
- const channelId = this.requireChannel();
549
- const fee = this.uploadFee();
550
- const event = this.client.signEvent({
551
- kind: 5094,
552
- content: "",
553
- created_at: nowSeconds(),
554
- tags: [
555
- ["i", upload.body.toString("base64"), "blob"],
556
- ["bid", fee.toString(), "usdc"],
557
- ["output", upload.contentType],
558
- ...upload.repoId ? [["Repo", upload.repoId]] : []
559
- ]
560
- });
561
- const claim = await this.client.signBalanceProof(channelId, fee);
562
- const result = await this.client.publishEvent(event, {
563
- ...this.storeDestination ? { destination: this.storeDestination } : {},
564
- claim,
565
- ilpAmount: fee,
566
- proxyPath: "/store"
567
- });
568
- if (!result.success) {
569
- throw new StandalonePublishError(
570
- `blob upload rejected: ${result.error ?? "store rejected the write"}`
571
- );
572
- }
573
- if (!result.response) {
574
- throw new StandalonePublishError(
575
- "blob upload FULFILL carried no sealed response; expected the Arweave tx ID"
576
- );
577
- }
578
- return { txId: extractArweaveTxId(result.response), feePaid: fee };
579
- }
580
- /**
581
- * Sign the event with the embedded identity and pay-to-publish it through
582
- * the relay write route, one claim for the flat per-event fee.
583
- *
584
- * `relayUrls` is the interface's plural forward-compat surface (parked
585
- * #84): the standalone impl routes over ILP to its single configured
586
- * publish destination, so more than one relay is refused rather than
587
- * silently half-published.
588
- */
589
- async publishEvent(event, relayUrls) {
590
- if (relayUrls.length > 1) {
591
- throw new StandalonePublishError(
592
- `multi-relay publish is not supported yet (got ${relayUrls.length} relays) \u2014 the standalone publisher routes to a single relay destination (#84 parked)`
593
- );
594
- }
595
- await this.start();
596
- const channelId = this.requireChannel();
597
- const signed = this.client.signEvent(event);
598
- const fee = this.effectiveEventFee();
599
- const claim = await this.client.signBalanceProof(channelId, fee);
600
- const result = await this.client.publishEvent(signed, {
601
- ...this.publishDestination ? { destination: this.publishDestination } : {},
602
- claim,
603
- ilpAmount: fee
604
- });
605
- if (!result.success) {
606
- throw new StandalonePublishError(
607
- `publish rejected (kind ${event.kind}): ${result.error ?? "relay rejected the event"}`
608
- );
609
- }
610
- return { eventId: result.eventId ?? signed.id, feePaid: fee };
611
- }
612
- requireChannel() {
613
- if (!this.channelId) {
614
- throw new StandalonePublishError(
615
- "no payment channel open \u2014 start() did not complete"
616
- );
617
- }
618
- return this.channelId;
619
- }
620
- };
621
- function nowSeconds() {
622
- return Math.floor(Date.now() / 1e3);
623
- }
624
-
625
- export {
626
- extractArweaveTxId,
627
- StandalonePublishError,
628
- deriveRouteDestinations,
629
- StandalonePublisher
630
- };
631
- //# sourceMappingURL=chunk-4CAOEXGQ.js.map