@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/agent-store.cjs +5 -2
- package/dist/agent-store.cjs.map +1 -1
- package/dist/agent-store.js +5 -2
- package/dist/agent-store.js.map +1 -1
- package/dist/index.cjs +410 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +285 -35
- package/dist/index.d.ts +285 -35
- package/dist/index.js +403 -5
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +1 -2
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +1 -2
- package/dist/node.js.map +1 -1
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.js.map +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
1868
|
-
|
|
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;
|