@elisym/sdk 0.24.1 → 0.25.1

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/index.cjs CHANGED
@@ -97,7 +97,12 @@ var DEFAULTS = {
97
97
  // Default ceiling for a single iroh file transfer (seed/fetch). A tunable
98
98
  // default, not a protocol constant - the transfer is resumable and its own
99
99
  // budget, decoupled from the result-wait window.
100
- IROH_FETCH_TIMEOUT_MS: 3e5
100
+ IROH_FETCH_TIMEOUT_MS: 3e5,
101
+ // Ceiling for a single Blossom blob upload (PUT /upload). Large blobs (up to
102
+ // LIMITS.MAX_FILE_SIZE) need far more than the 30s used for small media images.
103
+ BLOSSOM_UPLOAD_TIMEOUT_MS: 3e5,
104
+ // Ceiling for a single encrypted Blossom blob download (GET). Same budget as upload.
105
+ BLOSSOM_FETCH_TIMEOUT_MS: 3e5
101
106
  };
102
107
  var LIMITS = {
103
108
  MAX_INPUT_LENGTH: 1e5,
@@ -122,6 +127,12 @@ var LIMITS = {
122
127
  // providers may lower it per deployment.
123
128
  MAX_FILE_SIZE: 1073741824,
124
129
  // 1 GiB
130
+ // Cap for the ENCRYPTED Blossom path (web/SDK). The encrypt-then-upload flow is
131
+ // whole-buffer in WebCrypto + BlossomService (~3x file-size peak RAM), so this is
132
+ // deliberately far below MAX_FILE_SIZE to stay safe in a browser tab; larger files
133
+ // use iroh. The relay enforces a ~128 MiB server-side backstop.
134
+ MAX_BLOSSOM_ENCRYPTED_BYTES: 104857600,
135
+ // 100 MiB
125
136
  MAX_TIMEOUT_SECS: 600,
126
137
  // Upper bound for execution budgets (`max_execution_secs` / `execution_timeout_secs`).
127
138
  // Distinct from MAX_TIMEOUT_SECS (the result-wait cap): execution budgets may be
@@ -1200,6 +1211,180 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
1200
1211
  options
1201
1212
  );
1202
1213
  }
1214
+ var KIND_BLOSSOM_AUTH = 24242;
1215
+ var DEFAULT_BLOSSOM_URL = "https://files.elisym.network";
1216
+ var AUTH_TTL_SECS = 600;
1217
+ var BlossomService = class {
1218
+ constructor(serverUrl = DEFAULT_BLOSSOM_URL, fallback) {
1219
+ this.serverUrl = serverUrl;
1220
+ this.fallback = fallback;
1221
+ }
1222
+ /**
1223
+ * Upload a file to the Blossom server, returning its descriptor. On any failure, falls
1224
+ * back to the configured uploader (if any) and returns a normalized descriptor with
1225
+ * `provider: 'fallback'`. Works with browser File objects and Node.js/Bun Blobs.
1226
+ */
1227
+ async upload(identity, file) {
1228
+ const bytes = new Uint8Array(await file.arrayBuffer());
1229
+ if (bytes.byteLength > LIMITS.MAX_FILE_SIZE) {
1230
+ throw new Error(
1231
+ `File too large: ${bytes.byteLength} bytes exceeds limit of ${LIMITS.MAX_FILE_SIZE}.`
1232
+ );
1233
+ }
1234
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
1235
+ const hashHex = [...new Uint8Array(hashBuffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
1236
+ try {
1237
+ return await this.uploadToBlossom(identity, bytes, hashHex, file.type);
1238
+ } catch (err) {
1239
+ if (!this.fallback) {
1240
+ throw err;
1241
+ }
1242
+ const url = await this.fallback(identity, file);
1243
+ return {
1244
+ url,
1245
+ sha256: hashHex,
1246
+ size: file.size,
1247
+ type: file.type || "application/octet-stream",
1248
+ provider: "fallback"
1249
+ };
1250
+ }
1251
+ }
1252
+ /** Delete a blob by sha256 (BUD-02). Blossom only - there is no fallback for deletes. */
1253
+ async delete(identity, sha256) {
1254
+ if (!/^[0-9a-f]{64}$/.test(sha256)) {
1255
+ throw new Error("sha256 must be 64 lowercase hex chars.");
1256
+ }
1257
+ const authHeader = this.authHeader(identity, "delete", sha256);
1258
+ const controller = new AbortController();
1259
+ const timer = setTimeout(() => controller.abort(), DEFAULTS.BLOSSOM_UPLOAD_TIMEOUT_MS);
1260
+ try {
1261
+ const res = await fetch(`${this.serverUrl}/${sha256}`, {
1262
+ method: "DELETE",
1263
+ headers: { Authorization: authHeader },
1264
+ signal: controller.signal
1265
+ });
1266
+ if (!res.ok) {
1267
+ throw new Error(`Delete failed: ${res.status} ${res.statusText}`);
1268
+ }
1269
+ } finally {
1270
+ clearTimeout(timer);
1271
+ }
1272
+ }
1273
+ /**
1274
+ * Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
1275
+ * the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
1276
+ */
1277
+ async download(url, opts = {}) {
1278
+ const maxBytes = opts.maxBytes ?? LIMITS.MAX_FILE_SIZE;
1279
+ const controller = new AbortController();
1280
+ const timer = setTimeout(
1281
+ () => controller.abort(),
1282
+ opts.timeoutMs ?? DEFAULTS.BLOSSOM_FETCH_TIMEOUT_MS
1283
+ );
1284
+ try {
1285
+ const res = await fetch(url, { signal: controller.signal });
1286
+ if (!res.ok) {
1287
+ throw new Error(`Download failed: ${res.status} ${res.statusText}`);
1288
+ }
1289
+ const declared = Number(res.headers.get("content-length"));
1290
+ if (Number.isFinite(declared) && declared > maxBytes) {
1291
+ throw new Error(`Blob too large: ${declared} bytes exceeds limit of ${maxBytes}.`);
1292
+ }
1293
+ if (!res.body) {
1294
+ throw new Error("Download response has no body.");
1295
+ }
1296
+ const reader = res.body.getReader();
1297
+ const chunks = [];
1298
+ let total = 0;
1299
+ let chunk = await reader.read();
1300
+ while (!chunk.done) {
1301
+ total += chunk.value.byteLength;
1302
+ if (total > maxBytes) {
1303
+ await reader.cancel();
1304
+ throw new Error(`Blob exceeds limit of ${maxBytes} bytes.`);
1305
+ }
1306
+ chunks.push(chunk.value);
1307
+ chunk = await reader.read();
1308
+ }
1309
+ const bytes = new Uint8Array(total);
1310
+ let offset = 0;
1311
+ for (const c of chunks) {
1312
+ bytes.set(c, offset);
1313
+ offset += c.byteLength;
1314
+ }
1315
+ if (opts.expectedSha256 !== void 0) {
1316
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
1317
+ const hashHex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
1318
+ if (hashHex !== opts.expectedSha256) {
1319
+ throw new Error(
1320
+ `Download integrity check failed: got ${hashHex}, expected ${opts.expectedSha256}.`
1321
+ );
1322
+ }
1323
+ }
1324
+ return bytes;
1325
+ } finally {
1326
+ clearTimeout(timer);
1327
+ }
1328
+ }
1329
+ async uploadToBlossom(identity, bytes, hashHex, mime) {
1330
+ const contentType = mime || "application/octet-stream";
1331
+ const authHeader = this.authHeader(identity, "upload", hashHex);
1332
+ const controller = new AbortController();
1333
+ const timer = setTimeout(() => controller.abort(), DEFAULTS.BLOSSOM_UPLOAD_TIMEOUT_MS);
1334
+ try {
1335
+ const res = await fetch(`${this.serverUrl}/upload`, {
1336
+ method: "PUT",
1337
+ headers: { Authorization: authHeader, "Content-Type": contentType },
1338
+ body: bytes,
1339
+ signal: controller.signal
1340
+ });
1341
+ if (!res.ok) {
1342
+ throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
1343
+ }
1344
+ let data;
1345
+ try {
1346
+ data = await res.json();
1347
+ } catch {
1348
+ throw new Error("Invalid response from Blossom server.");
1349
+ }
1350
+ if (!data.url || !data.sha256) {
1351
+ throw new Error("No descriptor returned from Blossom server.");
1352
+ }
1353
+ if (data.sha256 !== hashHex) {
1354
+ throw new Error(
1355
+ `Blossom upload integrity check failed: server returned ${data.sha256}, expected ${hashHex}.`
1356
+ );
1357
+ }
1358
+ return {
1359
+ url: data.url,
1360
+ sha256: data.sha256,
1361
+ size: data.size ?? bytes.byteLength,
1362
+ type: data.type ?? contentType,
1363
+ uploaded: data.uploaded,
1364
+ provider: "blossom"
1365
+ };
1366
+ } finally {
1367
+ clearTimeout(timer);
1368
+ }
1369
+ }
1370
+ authHeader(identity, verb, sha256) {
1371
+ const now = Math.floor(Date.now() / 1e3);
1372
+ const authEvent = nostrTools.finalizeEvent(
1373
+ {
1374
+ kind: KIND_BLOSSOM_AUTH,
1375
+ created_at: now,
1376
+ tags: [
1377
+ ["t", verb],
1378
+ ["x", sha256],
1379
+ ["expiration", String(now + AUTH_TTL_SECS)]
1380
+ ],
1381
+ content: `${verb} blob via elisym SDK`
1382
+ },
1383
+ identity.secretKey
1384
+ );
1385
+ return "Nostr " + btoa(JSON.stringify(authEvent));
1386
+ }
1387
+ };
1203
1388
  var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
1204
1389
  var RANKING_BUCKET_SIZE_SECS = 60;
1205
1390
  var COLD_START_BUCKET = -Infinity;
@@ -1856,6 +2041,26 @@ var FileTransportSchema = zod.z.discriminatedUnion("kind", [
1856
2041
  kind: zod.z.literal("iroh"),
1857
2042
  /** Opaque iroh `BlobTicket` string. Parsed into a real ticket only at fetch time. */
1858
2043
  ticket: zod.z.string().min(1).max(MAX_TICKET_LENGTH)
2044
+ }),
2045
+ zod.z.object({
2046
+ kind: zod.z.literal("blossom"),
2047
+ /** Public HTTP(S) URL of the CIPHERTEXT blob on a Blossom relay. */
2048
+ url: zod.z.string().url().max(2048),
2049
+ /** sha256 (lowercase hex) of the ciphertext - what the relay stores and addresses. */
2050
+ sha256: zod.z.string().regex(/^[0-9a-f]{64}$/),
2051
+ /**
2052
+ * Hybrid-encryption parameters. The file bytes are AES-256-GCM encrypted with a random
2053
+ * content key; that key is NIP-44-wrapped to the recipient. `name`/`mime`/`size` on the
2054
+ * attachment describe the PLAINTEXT and live only inside the (encrypted) envelope - never
2055
+ * sent to the relay (the relay only ever sees opaque ciphertext).
2056
+ */
2057
+ enc: zod.z.object({
2058
+ alg: zod.z.literal("AES-256-GCM"),
2059
+ /** base64 12-byte GCM IV (non-secret). */
2060
+ iv: zod.z.string().min(1).max(64),
2061
+ /** NIP-44-wrapped content key. */
2062
+ key: zod.z.string().min(1).max(2048)
2063
+ })
1859
2064
  })
1860
2065
  ]);
1861
2066
  var FileAttachmentSchema = zod.z.object({
@@ -1864,8 +2069,18 @@ var FileAttachmentSchema = zod.z.object({
1864
2069
  /** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
1865
2070
  size: zod.z.number().int().nonnegative(),
1866
2071
  mime: zod.z.string().min(1).max(255),
1867
- /** Ordered by sender preference; at least one. */
1868
- transports: zod.z.array(FileTransportSchema).min(1),
2072
+ /**
2073
+ * Ordered by sender preference; at least one KNOWN transport. Parsed leniently: unknown
2074
+ * transport `kind`s are dropped (not rejected) so adding a new transport never makes an older
2075
+ * decoder throw away the whole envelope - it just ignores the kinds it doesn't know and uses
2076
+ * the ones it does. At least one known transport must survive, else the attachment is invalid.
2077
+ */
2078
+ transports: zod.z.array(zod.z.unknown()).transform(
2079
+ (arr) => arr.flatMap((t) => {
2080
+ const parsed = FileTransportSchema.safeParse(t);
2081
+ return parsed.success ? [parsed.data] : [];
2082
+ })
2083
+ ).refine((arr) => arr.length >= 1, { message: "attachment has no known transport" }),
1869
2084
  /** Optional provider hint (unix seconds) for when seeding may stop. */
1870
2085
  seedingExpiresAt: zod.z.number().int().nonnegative().optional()
1871
2086
  });
@@ -1874,6 +2089,37 @@ var JobPayloadEnvelopeSchema = zod.z.object({
1874
2089
  text: zod.z.string().optional(),
1875
2090
  attachment: FileAttachmentSchema.optional()
1876
2091
  });
2092
+ var ACCEPT_TRANSPORTS_TAG = "accept";
2093
+ var KNOWN_TRANSPORT_KINDS = ["iroh", "blossom"];
2094
+ function isKnownTransportKind(value) {
2095
+ return KNOWN_TRANSPORT_KINDS.includes(value);
2096
+ }
2097
+ function buildAcceptTransportsTag(kinds) {
2098
+ const seen = /* @__PURE__ */ new Set();
2099
+ const out = [ACCEPT_TRANSPORTS_TAG];
2100
+ for (const kind of kinds) {
2101
+ if (isKnownTransportKind(kind) && !seen.has(kind)) {
2102
+ seen.add(kind);
2103
+ out.push(kind);
2104
+ }
2105
+ }
2106
+ return out;
2107
+ }
2108
+ function readAcceptedTransports(tags) {
2109
+ const tag = tags.find((t) => t[0] === ACCEPT_TRANSPORTS_TAG);
2110
+ if (tag === void 0) {
2111
+ return void 0;
2112
+ }
2113
+ const seen = /* @__PURE__ */ new Set();
2114
+ const out = [];
2115
+ for (const value of tag.slice(1)) {
2116
+ if (isKnownTransportKind(value) && !seen.has(value)) {
2117
+ seen.add(value);
2118
+ out.push(value);
2119
+ }
2120
+ }
2121
+ return out.length > 0 ? out : void 0;
2122
+ }
1877
2123
  function encodeJobPayload(payload) {
1878
2124
  const envelope = { v: ENVELOPE_VERSION };
1879
2125
  if (payload.text !== void 0) {
@@ -1976,6 +2222,12 @@ var MarketplaceService = class {
1976
2222
  tags.push(["p", options.providerPubkey]);
1977
2223
  tags.push(["encrypted", "nip44"]);
1978
2224
  }
2225
+ if (options.acceptTransports && options.acceptTransports.length > 0) {
2226
+ const acceptTag = buildAcceptTransportsTag(options.acceptTransports);
2227
+ if (acceptTag.length > 1) {
2228
+ tags.push(acceptTag);
2229
+ }
2230
+ }
1979
2231
  const kind = jobRequestKind(options.kindOffset ?? DEFAULT_KIND_OFFSET);
1980
2232
  const event = nostrTools.finalizeEvent(
1981
2233
  {
@@ -3363,6 +3615,7 @@ var ElisymClient = class {
3363
3615
  marketplace;
3364
3616
  ping;
3365
3617
  media;
3618
+ blossom;
3366
3619
  policies;
3367
3620
  payment;
3368
3621
  constructor(config = {}) {
@@ -3371,6 +3624,10 @@ var ElisymClient = class {
3371
3624
  this.marketplace = new MarketplaceService(this.pool);
3372
3625
  this.ping = new PingService(this.pool);
3373
3626
  this.media = new MediaService(config.uploadUrl);
3627
+ this.blossom = new BlossomService(
3628
+ config.blossomUrl,
3629
+ (identity, file) => this.media.upload(identity, file)
3630
+ );
3374
3631
  this.policies = new PoliciesService(this.pool);
3375
3632
  this.payment = config.payment ?? new SolanaPaymentStrategy();
3376
3633
  }
@@ -3379,6 +3636,147 @@ var ElisymClient = class {
3379
3636
  }
3380
3637
  };
3381
3638
 
3639
+ // src/primitives/file-crypto.ts
3640
+ var KEY_BYTES = 32;
3641
+ var IV_BYTES = 12;
3642
+ function bytesToBase64(bytes) {
3643
+ let bin = "";
3644
+ for (const b of bytes) {
3645
+ bin += String.fromCharCode(b);
3646
+ }
3647
+ return btoa(bin);
3648
+ }
3649
+ function base64ToBytes(b64) {
3650
+ const bin = atob(b64);
3651
+ const out = new Uint8Array(bin.length);
3652
+ for (let i = 0; i < bin.length; i += 1) {
3653
+ out[i] = bin.charCodeAt(i);
3654
+ }
3655
+ return out;
3656
+ }
3657
+ async function encryptBytesForRecipient(bytes, senderSk, recipientPubkey) {
3658
+ const rawKey = crypto.getRandomValues(new Uint8Array(KEY_BYTES));
3659
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
3660
+ const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["encrypt"]);
3661
+ const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv, tagLength: 128 }, key, bytes);
3662
+ const wrappedKey = nip44Encrypt(bytesToBase64(rawKey), senderSk, recipientPubkey);
3663
+ return { ciphertext: new Uint8Array(ct), wrappedKey, iv: bytesToBase64(iv) };
3664
+ }
3665
+ async function decryptBytesFromSender(ciphertext, wrappedKey, iv, receiverSk, senderPubkey) {
3666
+ const rawKey = base64ToBytes(nip44Decrypt(wrappedKey, receiverSk, senderPubkey));
3667
+ const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["decrypt"]);
3668
+ const pt = await crypto.subtle.decrypt(
3669
+ { name: "AES-GCM", iv: base64ToBytes(iv), tagLength: 128 },
3670
+ key,
3671
+ ciphertext
3672
+ );
3673
+ return new Uint8Array(pt);
3674
+ }
3675
+
3676
+ // src/transport/blossom-transport.ts
3677
+ var AES_GCM_TAG_BYTES = 16;
3678
+ function createBlossomTransport(opts) {
3679
+ const { blossom, identity } = opts;
3680
+ return {
3681
+ async seedBytes({ bytes, recipientPubkey }) {
3682
+ if (bytes.byteLength > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
3683
+ throw new Error(
3684
+ `File too large for encrypted Blossom: ${bytes.byteLength} bytes exceeds ${LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES}.`
3685
+ );
3686
+ }
3687
+ const { ciphertext, wrappedKey, iv } = await encryptBytesForRecipient(
3688
+ bytes,
3689
+ identity.secretKey,
3690
+ recipientPubkey
3691
+ );
3692
+ const blob = new Blob([ciphertext], { type: "application/octet-stream" });
3693
+ const descriptor = await blossom.upload(identity, blob);
3694
+ if (descriptor.provider !== "blossom") {
3695
+ throw new Error("Blossom upload fell back to a non-content-addressed provider.");
3696
+ }
3697
+ return {
3698
+ kind: "blossom",
3699
+ url: descriptor.url,
3700
+ sha256: descriptor.sha256,
3701
+ enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
3702
+ };
3703
+ },
3704
+ async fetchToBytes({ transport, senderPubkey, maxBytes }) {
3705
+ const plaintextCap = maxBytes ?? LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES;
3706
+ const ciphertext = await blossom.download(transport.url, {
3707
+ maxBytes: plaintextCap + AES_GCM_TAG_BYTES,
3708
+ expectedSha256: transport.sha256
3709
+ });
3710
+ return decryptBytesFromSender(
3711
+ ciphertext,
3712
+ transport.enc.key,
3713
+ transport.enc.iv,
3714
+ identity.secretKey,
3715
+ senderPubkey
3716
+ );
3717
+ }
3718
+ };
3719
+ }
3720
+
3721
+ // src/transport/file-jobs.ts
3722
+ var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
3723
+ ".exe",
3724
+ ".dll",
3725
+ ".bat",
3726
+ ".cmd",
3727
+ ".com",
3728
+ ".msi",
3729
+ ".sh",
3730
+ ".app",
3731
+ ".scr",
3732
+ ".ps1"
3733
+ ]);
3734
+ var EXECUTABLE_MIMES = /* @__PURE__ */ new Set([
3735
+ "application/x-msdownload",
3736
+ "application/x-msdos-program",
3737
+ "application/x-sh",
3738
+ "application/x-executable",
3739
+ "application/vnd.microsoft.portable-executable",
3740
+ "application/x-mach-binary"
3741
+ ]);
3742
+ function looksExecutable(name, type) {
3743
+ const dot = name.lastIndexOf(".");
3744
+ const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
3745
+ return EXECUTABLE_EXTENSIONS.has(ext) || EXECUTABLE_MIMES.has(type);
3746
+ }
3747
+ async function buildEncryptedFileInput(args) {
3748
+ const { file, providerPubkey, identity, blossom } = args;
3749
+ const name = file.name ?? "upload";
3750
+ if (looksExecutable(name, file.type)) {
3751
+ throw new Error("Refusing to upload an executable file.");
3752
+ }
3753
+ const transport = createBlossomTransport({ blossom, identity });
3754
+ const bytes = new Uint8Array(await file.arrayBuffer());
3755
+ const member = await transport.seedBytes({ bytes, recipientPubkey: providerPubkey });
3756
+ return {
3757
+ name,
3758
+ size: bytes.byteLength,
3759
+ mime: file.type || "application/octet-stream",
3760
+ transports: [member]
3761
+ };
3762
+ }
3763
+ async function fetchEncryptedFileOutput(args) {
3764
+ const { attachment, providerPubkey, identity, blossom, maxBytes } = args;
3765
+ const member = attachment.transports.find(
3766
+ (t) => t.kind === "blossom"
3767
+ );
3768
+ if (member === void 0) {
3769
+ throw new Error("Attachment has no blossom transport.");
3770
+ }
3771
+ const transport = createBlossomTransport({ blossom, identity });
3772
+ const bytes = await transport.fetchToBytes({
3773
+ transport: member,
3774
+ senderPubkey: providerPubkey,
3775
+ maxBytes
3776
+ });
3777
+ return { bytes, name: attachment.name, mime: attachment.mime };
3778
+ }
3779
+
3382
3780
  // src/services/jobErrors.ts
3383
3781
  var AGENT_UNAVAILABLE_MARKERS = [
3384
3782
  "agent temporarily unavailable",
@@ -3950,6 +4348,8 @@ function makeCensor() {
3950
4348
  };
3951
4349
  }
3952
4350
 
4351
+ exports.ACCEPT_TRANSPORTS_TAG = ACCEPT_TRANSPORTS_TAG;
4352
+ exports.BlossomService = BlossomService;
3953
4353
  exports.BoundedSet = BoundedSet;
3954
4354
  exports.DEFAULTS = DEFAULTS;
3955
4355
  exports.DEFAULT_KIND_OFFSET = DEFAULT_KIND_OFFSET;
@@ -3995,6 +4395,8 @@ exports.assertExpiry = assertExpiry;
3995
4395
  exports.assertLamports = assertLamports;
3996
4396
  exports.assetByKey = assetByKey;
3997
4397
  exports.assetKey = assetKey;
4398
+ exports.buildAcceptTransportsTag = buildAcceptTransportsTag;
4399
+ exports.buildEncryptedFileInput = buildEncryptedFileInput;
3998
4400
  exports.buildPaymentInstructions = buildPaymentInstructions;
3999
4401
  exports.calculateProtocolFee = calculateProtocolFee;
4000
4402
  exports.classifyJobError = classifyJobError;
@@ -4003,13 +4405,17 @@ exports.clearProtocolConfigCache = clearProtocolConfigCache;
4003
4405
  exports.clearQuickVerifyCache = clearQuickVerifyCache;
4004
4406
  exports.compareAgentsByRank = compareAgentsByRank;
4005
4407
  exports.computeRankKey = computeRankKey;
4408
+ exports.createBlossomTransport = createBlossomTransport;
4006
4409
  exports.createPaymentRequestWithOnchainConfig = createPaymentRequestWithOnchainConfig;
4007
4410
  exports.createSlidingWindowLimiter = createSlidingWindowLimiter;
4008
4411
  exports.decodeJobPayload = decodeJobPayload;
4412
+ exports.decryptBytesFromSender = decryptBytesFromSender;
4009
4413
  exports.encodeJobPayload = encodeJobPayload;
4414
+ exports.encryptBytesForRecipient = encryptBytesForRecipient;
4010
4415
  exports.estimateNetworkBaseline = estimateNetworkBaseline;
4011
4416
  exports.estimatePriorityFeeMicroLamports = estimatePriorityFeeMicroLamports;
4012
4417
  exports.estimateSolFeeLamports = estimateSolFeeLamports;
4418
+ exports.fetchEncryptedFileOutput = fetchEncryptedFileOutput;
4013
4419
  exports.formatAssetAmount = formatAssetAmount;
4014
4420
  exports.formatFeeBreakdown = formatFeeBreakdown;
4015
4421
  exports.formatNetworkBaseline = formatNetworkBaseline;
@@ -4025,6 +4431,7 @@ exports.nip44Encrypt = nip44Encrypt;
4025
4431
  exports.parseAssetAmount = parseAssetAmount;
4026
4432
  exports.parsePaymentRequest = parsePaymentRequest;
4027
4433
  exports.pickPercentileFee = pickPercentileFee;
4434
+ exports.readAcceptedTransports = readAcceptedTransports;
4028
4435
  exports.resolveAssetFromPaymentRequest = resolveAssetFromPaymentRequest;
4029
4436
  exports.resolveKnownAsset = resolveKnownAsset;
4030
4437
  exports.timeAgo = timeAgo;