@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.js
CHANGED
|
@@ -4,7 +4,7 @@ import { getTransferSolInstruction } from '@solana-program/system';
|
|
|
4
4
|
import { findAssociatedTokenPda, TOKEN_PROGRAM_ADDRESS, getCreateAssociatedTokenIdempotentInstruction, ASSOCIATED_TOKEN_PROGRAM_ADDRESS, getTransferCheckedInstruction } from '@solana-program/token';
|
|
5
5
|
import Decimal3 from 'decimal.js-light';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import {
|
|
7
|
+
import { finalizeEvent, verifyEvent, getPublicKey, nip19, generateSecretKey, SimplePool } from 'nostr-tools';
|
|
8
8
|
import * as nip44 from 'nostr-tools/nip44';
|
|
9
9
|
|
|
10
10
|
// src/constants.ts
|
|
@@ -72,7 +72,12 @@ var DEFAULTS = {
|
|
|
72
72
|
// Default ceiling for a single iroh file transfer (seed/fetch). A tunable
|
|
73
73
|
// default, not a protocol constant - the transfer is resumable and its own
|
|
74
74
|
// budget, decoupled from the result-wait window.
|
|
75
|
-
IROH_FETCH_TIMEOUT_MS: 3e5
|
|
75
|
+
IROH_FETCH_TIMEOUT_MS: 3e5,
|
|
76
|
+
// Ceiling for a single Blossom blob upload (PUT /upload). Large blobs (up to
|
|
77
|
+
// LIMITS.MAX_FILE_SIZE) need far more than the 30s used for small media images.
|
|
78
|
+
BLOSSOM_UPLOAD_TIMEOUT_MS: 3e5,
|
|
79
|
+
// Ceiling for a single encrypted Blossom blob download (GET). Same budget as upload.
|
|
80
|
+
BLOSSOM_FETCH_TIMEOUT_MS: 3e5
|
|
76
81
|
};
|
|
77
82
|
var LIMITS = {
|
|
78
83
|
MAX_INPUT_LENGTH: 1e5,
|
|
@@ -97,6 +102,12 @@ var LIMITS = {
|
|
|
97
102
|
// providers may lower it per deployment.
|
|
98
103
|
MAX_FILE_SIZE: 1073741824,
|
|
99
104
|
// 1 GiB
|
|
105
|
+
// Cap for the ENCRYPTED Blossom path (web/SDK). The encrypt-then-upload flow is
|
|
106
|
+
// whole-buffer in WebCrypto + BlossomService (~3x file-size peak RAM), so this is
|
|
107
|
+
// deliberately far below MAX_FILE_SIZE to stay safe in a browser tab; larger files
|
|
108
|
+
// use iroh. The relay enforces a ~128 MiB server-side backstop.
|
|
109
|
+
MAX_BLOSSOM_ENCRYPTED_BYTES: 104857600,
|
|
110
|
+
// 100 MiB
|
|
100
111
|
MAX_TIMEOUT_SECS: 600,
|
|
101
112
|
// Upper bound for execution budgets (`max_execution_secs` / `execution_timeout_secs`).
|
|
102
113
|
// Distinct from MAX_TIMEOUT_SECS (the result-wait cap): execution budgets may be
|
|
@@ -1175,6 +1186,180 @@ async function createPaymentRequestWithOnchainConfig(rpc, programId, recipient,
|
|
|
1175
1186
|
options
|
|
1176
1187
|
);
|
|
1177
1188
|
}
|
|
1189
|
+
var KIND_BLOSSOM_AUTH = 24242;
|
|
1190
|
+
var DEFAULT_BLOSSOM_URL = "https://files.elisym.network";
|
|
1191
|
+
var AUTH_TTL_SECS = 600;
|
|
1192
|
+
var BlossomService = class {
|
|
1193
|
+
constructor(serverUrl = DEFAULT_BLOSSOM_URL, fallback) {
|
|
1194
|
+
this.serverUrl = serverUrl;
|
|
1195
|
+
this.fallback = fallback;
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Upload a file to the Blossom server, returning its descriptor. On any failure, falls
|
|
1199
|
+
* back to the configured uploader (if any) and returns a normalized descriptor with
|
|
1200
|
+
* `provider: 'fallback'`. Works with browser File objects and Node.js/Bun Blobs.
|
|
1201
|
+
*/
|
|
1202
|
+
async upload(identity, file) {
|
|
1203
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
1204
|
+
if (bytes.byteLength > LIMITS.MAX_FILE_SIZE) {
|
|
1205
|
+
throw new Error(
|
|
1206
|
+
`File too large: ${bytes.byteLength} bytes exceeds limit of ${LIMITS.MAX_FILE_SIZE}.`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
|
|
1210
|
+
const hashHex = [...new Uint8Array(hashBuffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1211
|
+
try {
|
|
1212
|
+
return await this.uploadToBlossom(identity, bytes, hashHex, file.type);
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
if (!this.fallback) {
|
|
1215
|
+
throw err;
|
|
1216
|
+
}
|
|
1217
|
+
const url = await this.fallback(identity, file);
|
|
1218
|
+
return {
|
|
1219
|
+
url,
|
|
1220
|
+
sha256: hashHex,
|
|
1221
|
+
size: file.size,
|
|
1222
|
+
type: file.type || "application/octet-stream",
|
|
1223
|
+
provider: "fallback"
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
/** Delete a blob by sha256 (BUD-02). Blossom only - there is no fallback for deletes. */
|
|
1228
|
+
async delete(identity, sha256) {
|
|
1229
|
+
if (!/^[0-9a-f]{64}$/.test(sha256)) {
|
|
1230
|
+
throw new Error("sha256 must be 64 lowercase hex chars.");
|
|
1231
|
+
}
|
|
1232
|
+
const authHeader = this.authHeader(identity, "delete", sha256);
|
|
1233
|
+
const controller = new AbortController();
|
|
1234
|
+
const timer = setTimeout(() => controller.abort(), DEFAULTS.BLOSSOM_UPLOAD_TIMEOUT_MS);
|
|
1235
|
+
try {
|
|
1236
|
+
const res = await fetch(`${this.serverUrl}/${sha256}`, {
|
|
1237
|
+
method: "DELETE",
|
|
1238
|
+
headers: { Authorization: authHeader },
|
|
1239
|
+
signal: controller.signal
|
|
1240
|
+
});
|
|
1241
|
+
if (!res.ok) {
|
|
1242
|
+
throw new Error(`Delete failed: ${res.status} ${res.statusText}`);
|
|
1243
|
+
}
|
|
1244
|
+
} finally {
|
|
1245
|
+
clearTimeout(timer);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
|
|
1250
|
+
* the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
|
|
1251
|
+
*/
|
|
1252
|
+
async download(url, opts = {}) {
|
|
1253
|
+
const maxBytes = opts.maxBytes ?? LIMITS.MAX_FILE_SIZE;
|
|
1254
|
+
const controller = new AbortController();
|
|
1255
|
+
const timer = setTimeout(
|
|
1256
|
+
() => controller.abort(),
|
|
1257
|
+
opts.timeoutMs ?? DEFAULTS.BLOSSOM_FETCH_TIMEOUT_MS
|
|
1258
|
+
);
|
|
1259
|
+
try {
|
|
1260
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
1261
|
+
if (!res.ok) {
|
|
1262
|
+
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
1263
|
+
}
|
|
1264
|
+
const declared = Number(res.headers.get("content-length"));
|
|
1265
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
1266
|
+
throw new Error(`Blob too large: ${declared} bytes exceeds limit of ${maxBytes}.`);
|
|
1267
|
+
}
|
|
1268
|
+
if (!res.body) {
|
|
1269
|
+
throw new Error("Download response has no body.");
|
|
1270
|
+
}
|
|
1271
|
+
const reader = res.body.getReader();
|
|
1272
|
+
const chunks = [];
|
|
1273
|
+
let total = 0;
|
|
1274
|
+
let chunk = await reader.read();
|
|
1275
|
+
while (!chunk.done) {
|
|
1276
|
+
total += chunk.value.byteLength;
|
|
1277
|
+
if (total > maxBytes) {
|
|
1278
|
+
await reader.cancel();
|
|
1279
|
+
throw new Error(`Blob exceeds limit of ${maxBytes} bytes.`);
|
|
1280
|
+
}
|
|
1281
|
+
chunks.push(chunk.value);
|
|
1282
|
+
chunk = await reader.read();
|
|
1283
|
+
}
|
|
1284
|
+
const bytes = new Uint8Array(total);
|
|
1285
|
+
let offset = 0;
|
|
1286
|
+
for (const c of chunks) {
|
|
1287
|
+
bytes.set(c, offset);
|
|
1288
|
+
offset += c.byteLength;
|
|
1289
|
+
}
|
|
1290
|
+
if (opts.expectedSha256 !== void 0) {
|
|
1291
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
1292
|
+
const hashHex = [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1293
|
+
if (hashHex !== opts.expectedSha256) {
|
|
1294
|
+
throw new Error(
|
|
1295
|
+
`Download integrity check failed: got ${hashHex}, expected ${opts.expectedSha256}.`
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
return bytes;
|
|
1300
|
+
} finally {
|
|
1301
|
+
clearTimeout(timer);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
async uploadToBlossom(identity, bytes, hashHex, mime) {
|
|
1305
|
+
const contentType = mime || "application/octet-stream";
|
|
1306
|
+
const authHeader = this.authHeader(identity, "upload", hashHex);
|
|
1307
|
+
const controller = new AbortController();
|
|
1308
|
+
const timer = setTimeout(() => controller.abort(), DEFAULTS.BLOSSOM_UPLOAD_TIMEOUT_MS);
|
|
1309
|
+
try {
|
|
1310
|
+
const res = await fetch(`${this.serverUrl}/upload`, {
|
|
1311
|
+
method: "PUT",
|
|
1312
|
+
headers: { Authorization: authHeader, "Content-Type": contentType },
|
|
1313
|
+
body: bytes,
|
|
1314
|
+
signal: controller.signal
|
|
1315
|
+
});
|
|
1316
|
+
if (!res.ok) {
|
|
1317
|
+
throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
|
|
1318
|
+
}
|
|
1319
|
+
let data;
|
|
1320
|
+
try {
|
|
1321
|
+
data = await res.json();
|
|
1322
|
+
} catch {
|
|
1323
|
+
throw new Error("Invalid response from Blossom server.");
|
|
1324
|
+
}
|
|
1325
|
+
if (!data.url || !data.sha256) {
|
|
1326
|
+
throw new Error("No descriptor returned from Blossom server.");
|
|
1327
|
+
}
|
|
1328
|
+
if (data.sha256 !== hashHex) {
|
|
1329
|
+
throw new Error(
|
|
1330
|
+
`Blossom upload integrity check failed: server returned ${data.sha256}, expected ${hashHex}.`
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
return {
|
|
1334
|
+
url: data.url,
|
|
1335
|
+
sha256: data.sha256,
|
|
1336
|
+
size: data.size ?? bytes.byteLength,
|
|
1337
|
+
type: data.type ?? contentType,
|
|
1338
|
+
uploaded: data.uploaded,
|
|
1339
|
+
provider: "blossom"
|
|
1340
|
+
};
|
|
1341
|
+
} finally {
|
|
1342
|
+
clearTimeout(timer);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
authHeader(identity, verb, sha256) {
|
|
1346
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1347
|
+
const authEvent = finalizeEvent(
|
|
1348
|
+
{
|
|
1349
|
+
kind: KIND_BLOSSOM_AUTH,
|
|
1350
|
+
created_at: now,
|
|
1351
|
+
tags: [
|
|
1352
|
+
["t", verb],
|
|
1353
|
+
["x", sha256],
|
|
1354
|
+
["expiration", String(now + AUTH_TTL_SECS)]
|
|
1355
|
+
],
|
|
1356
|
+
content: `${verb} blob via elisym SDK`
|
|
1357
|
+
},
|
|
1358
|
+
identity.secretKey
|
|
1359
|
+
);
|
|
1360
|
+
return "Nostr " + btoa(JSON.stringify(authEvent));
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1178
1363
|
var RANKING_ACTIVITY_WINDOW_SECS = 30 * 24 * 60 * 60;
|
|
1179
1364
|
var RANKING_BUCKET_SIZE_SECS = 60;
|
|
1180
1365
|
var COLD_START_BUCKET = -Infinity;
|
|
@@ -1831,6 +2016,26 @@ var FileTransportSchema = z.discriminatedUnion("kind", [
|
|
|
1831
2016
|
kind: z.literal("iroh"),
|
|
1832
2017
|
/** Opaque iroh `BlobTicket` string. Parsed into a real ticket only at fetch time. */
|
|
1833
2018
|
ticket: z.string().min(1).max(MAX_TICKET_LENGTH)
|
|
2019
|
+
}),
|
|
2020
|
+
z.object({
|
|
2021
|
+
kind: z.literal("blossom"),
|
|
2022
|
+
/** Public HTTP(S) URL of the CIPHERTEXT blob on a Blossom relay. */
|
|
2023
|
+
url: z.string().url().max(2048),
|
|
2024
|
+
/** sha256 (lowercase hex) of the ciphertext - what the relay stores and addresses. */
|
|
2025
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
2026
|
+
/**
|
|
2027
|
+
* Hybrid-encryption parameters. The file bytes are AES-256-GCM encrypted with a random
|
|
2028
|
+
* content key; that key is NIP-44-wrapped to the recipient. `name`/`mime`/`size` on the
|
|
2029
|
+
* attachment describe the PLAINTEXT and live only inside the (encrypted) envelope - never
|
|
2030
|
+
* sent to the relay (the relay only ever sees opaque ciphertext).
|
|
2031
|
+
*/
|
|
2032
|
+
enc: z.object({
|
|
2033
|
+
alg: z.literal("AES-256-GCM"),
|
|
2034
|
+
/** base64 12-byte GCM IV (non-secret). */
|
|
2035
|
+
iv: z.string().min(1).max(64),
|
|
2036
|
+
/** NIP-44-wrapped content key. */
|
|
2037
|
+
key: z.string().min(1).max(2048)
|
|
2038
|
+
})
|
|
1834
2039
|
})
|
|
1835
2040
|
]);
|
|
1836
2041
|
var FileAttachmentSchema = z.object({
|
|
@@ -1839,8 +2044,18 @@ var FileAttachmentSchema = z.object({
|
|
|
1839
2044
|
/** Declared size in bytes (display/hint only; enforcement is on actual streamed bytes). */
|
|
1840
2045
|
size: z.number().int().nonnegative(),
|
|
1841
2046
|
mime: z.string().min(1).max(255),
|
|
1842
|
-
/**
|
|
1843
|
-
|
|
2047
|
+
/**
|
|
2048
|
+
* Ordered by sender preference; at least one KNOWN transport. Parsed leniently: unknown
|
|
2049
|
+
* transport `kind`s are dropped (not rejected) so adding a new transport never makes an older
|
|
2050
|
+
* decoder throw away the whole envelope - it just ignores the kinds it doesn't know and uses
|
|
2051
|
+
* the ones it does. At least one known transport must survive, else the attachment is invalid.
|
|
2052
|
+
*/
|
|
2053
|
+
transports: z.array(z.unknown()).transform(
|
|
2054
|
+
(arr) => arr.flatMap((t) => {
|
|
2055
|
+
const parsed = FileTransportSchema.safeParse(t);
|
|
2056
|
+
return parsed.success ? [parsed.data] : [];
|
|
2057
|
+
})
|
|
2058
|
+
).refine((arr) => arr.length >= 1, { message: "attachment has no known transport" }),
|
|
1844
2059
|
/** Optional provider hint (unix seconds) for when seeding may stop. */
|
|
1845
2060
|
seedingExpiresAt: z.number().int().nonnegative().optional()
|
|
1846
2061
|
});
|
|
@@ -1849,6 +2064,37 @@ var JobPayloadEnvelopeSchema = z.object({
|
|
|
1849
2064
|
text: z.string().optional(),
|
|
1850
2065
|
attachment: FileAttachmentSchema.optional()
|
|
1851
2066
|
});
|
|
2067
|
+
var ACCEPT_TRANSPORTS_TAG = "accept";
|
|
2068
|
+
var KNOWN_TRANSPORT_KINDS = ["iroh", "blossom"];
|
|
2069
|
+
function isKnownTransportKind(value) {
|
|
2070
|
+
return KNOWN_TRANSPORT_KINDS.includes(value);
|
|
2071
|
+
}
|
|
2072
|
+
function buildAcceptTransportsTag(kinds) {
|
|
2073
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2074
|
+
const out = [ACCEPT_TRANSPORTS_TAG];
|
|
2075
|
+
for (const kind of kinds) {
|
|
2076
|
+
if (isKnownTransportKind(kind) && !seen.has(kind)) {
|
|
2077
|
+
seen.add(kind);
|
|
2078
|
+
out.push(kind);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
return out;
|
|
2082
|
+
}
|
|
2083
|
+
function readAcceptedTransports(tags) {
|
|
2084
|
+
const tag = tags.find((t) => t[0] === ACCEPT_TRANSPORTS_TAG);
|
|
2085
|
+
if (tag === void 0) {
|
|
2086
|
+
return void 0;
|
|
2087
|
+
}
|
|
2088
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2089
|
+
const out = [];
|
|
2090
|
+
for (const value of tag.slice(1)) {
|
|
2091
|
+
if (isKnownTransportKind(value) && !seen.has(value)) {
|
|
2092
|
+
seen.add(value);
|
|
2093
|
+
out.push(value);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
return out.length > 0 ? out : void 0;
|
|
2097
|
+
}
|
|
1852
2098
|
function encodeJobPayload(payload) {
|
|
1853
2099
|
const envelope = { v: ENVELOPE_VERSION };
|
|
1854
2100
|
if (payload.text !== void 0) {
|
|
@@ -1951,6 +2197,12 @@ var MarketplaceService = class {
|
|
|
1951
2197
|
tags.push(["p", options.providerPubkey]);
|
|
1952
2198
|
tags.push(["encrypted", "nip44"]);
|
|
1953
2199
|
}
|
|
2200
|
+
if (options.acceptTransports && options.acceptTransports.length > 0) {
|
|
2201
|
+
const acceptTag = buildAcceptTransportsTag(options.acceptTransports);
|
|
2202
|
+
if (acceptTag.length > 1) {
|
|
2203
|
+
tags.push(acceptTag);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
1954
2206
|
const kind = jobRequestKind(options.kindOffset ?? DEFAULT_KIND_OFFSET);
|
|
1955
2207
|
const event = finalizeEvent(
|
|
1956
2208
|
{
|
|
@@ -3338,6 +3590,7 @@ var ElisymClient = class {
|
|
|
3338
3590
|
marketplace;
|
|
3339
3591
|
ping;
|
|
3340
3592
|
media;
|
|
3593
|
+
blossom;
|
|
3341
3594
|
policies;
|
|
3342
3595
|
payment;
|
|
3343
3596
|
constructor(config = {}) {
|
|
@@ -3346,6 +3599,10 @@ var ElisymClient = class {
|
|
|
3346
3599
|
this.marketplace = new MarketplaceService(this.pool);
|
|
3347
3600
|
this.ping = new PingService(this.pool);
|
|
3348
3601
|
this.media = new MediaService(config.uploadUrl);
|
|
3602
|
+
this.blossom = new BlossomService(
|
|
3603
|
+
config.blossomUrl,
|
|
3604
|
+
(identity, file) => this.media.upload(identity, file)
|
|
3605
|
+
);
|
|
3349
3606
|
this.policies = new PoliciesService(this.pool);
|
|
3350
3607
|
this.payment = config.payment ?? new SolanaPaymentStrategy();
|
|
3351
3608
|
}
|
|
@@ -3354,6 +3611,147 @@ var ElisymClient = class {
|
|
|
3354
3611
|
}
|
|
3355
3612
|
};
|
|
3356
3613
|
|
|
3614
|
+
// src/primitives/file-crypto.ts
|
|
3615
|
+
var KEY_BYTES = 32;
|
|
3616
|
+
var IV_BYTES = 12;
|
|
3617
|
+
function bytesToBase64(bytes) {
|
|
3618
|
+
let bin = "";
|
|
3619
|
+
for (const b of bytes) {
|
|
3620
|
+
bin += String.fromCharCode(b);
|
|
3621
|
+
}
|
|
3622
|
+
return btoa(bin);
|
|
3623
|
+
}
|
|
3624
|
+
function base64ToBytes(b64) {
|
|
3625
|
+
const bin = atob(b64);
|
|
3626
|
+
const out = new Uint8Array(bin.length);
|
|
3627
|
+
for (let i = 0; i < bin.length; i += 1) {
|
|
3628
|
+
out[i] = bin.charCodeAt(i);
|
|
3629
|
+
}
|
|
3630
|
+
return out;
|
|
3631
|
+
}
|
|
3632
|
+
async function encryptBytesForRecipient(bytes, senderSk, recipientPubkey) {
|
|
3633
|
+
const rawKey = crypto.getRandomValues(new Uint8Array(KEY_BYTES));
|
|
3634
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
3635
|
+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["encrypt"]);
|
|
3636
|
+
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv, tagLength: 128 }, key, bytes);
|
|
3637
|
+
const wrappedKey = nip44Encrypt(bytesToBase64(rawKey), senderSk, recipientPubkey);
|
|
3638
|
+
return { ciphertext: new Uint8Array(ct), wrappedKey, iv: bytesToBase64(iv) };
|
|
3639
|
+
}
|
|
3640
|
+
async function decryptBytesFromSender(ciphertext, wrappedKey, iv, receiverSk, senderPubkey) {
|
|
3641
|
+
const rawKey = base64ToBytes(nip44Decrypt(wrappedKey, receiverSk, senderPubkey));
|
|
3642
|
+
const key = await crypto.subtle.importKey("raw", rawKey, "AES-GCM", false, ["decrypt"]);
|
|
3643
|
+
const pt = await crypto.subtle.decrypt(
|
|
3644
|
+
{ name: "AES-GCM", iv: base64ToBytes(iv), tagLength: 128 },
|
|
3645
|
+
key,
|
|
3646
|
+
ciphertext
|
|
3647
|
+
);
|
|
3648
|
+
return new Uint8Array(pt);
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
// src/transport/blossom-transport.ts
|
|
3652
|
+
var AES_GCM_TAG_BYTES = 16;
|
|
3653
|
+
function createBlossomTransport(opts) {
|
|
3654
|
+
const { blossom, identity } = opts;
|
|
3655
|
+
return {
|
|
3656
|
+
async seedBytes({ bytes, recipientPubkey }) {
|
|
3657
|
+
if (bytes.byteLength > LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES) {
|
|
3658
|
+
throw new Error(
|
|
3659
|
+
`File too large for encrypted Blossom: ${bytes.byteLength} bytes exceeds ${LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES}.`
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
const { ciphertext, wrappedKey, iv } = await encryptBytesForRecipient(
|
|
3663
|
+
bytes,
|
|
3664
|
+
identity.secretKey,
|
|
3665
|
+
recipientPubkey
|
|
3666
|
+
);
|
|
3667
|
+
const blob = new Blob([ciphertext], { type: "application/octet-stream" });
|
|
3668
|
+
const descriptor = await blossom.upload(identity, blob);
|
|
3669
|
+
if (descriptor.provider !== "blossom") {
|
|
3670
|
+
throw new Error("Blossom upload fell back to a non-content-addressed provider.");
|
|
3671
|
+
}
|
|
3672
|
+
return {
|
|
3673
|
+
kind: "blossom",
|
|
3674
|
+
url: descriptor.url,
|
|
3675
|
+
sha256: descriptor.sha256,
|
|
3676
|
+
enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
|
|
3677
|
+
};
|
|
3678
|
+
},
|
|
3679
|
+
async fetchToBytes({ transport, senderPubkey, maxBytes }) {
|
|
3680
|
+
const plaintextCap = maxBytes ?? LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES;
|
|
3681
|
+
const ciphertext = await blossom.download(transport.url, {
|
|
3682
|
+
maxBytes: plaintextCap + AES_GCM_TAG_BYTES,
|
|
3683
|
+
expectedSha256: transport.sha256
|
|
3684
|
+
});
|
|
3685
|
+
return decryptBytesFromSender(
|
|
3686
|
+
ciphertext,
|
|
3687
|
+
transport.enc.key,
|
|
3688
|
+
transport.enc.iv,
|
|
3689
|
+
identity.secretKey,
|
|
3690
|
+
senderPubkey
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
// src/transport/file-jobs.ts
|
|
3697
|
+
var EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3698
|
+
".exe",
|
|
3699
|
+
".dll",
|
|
3700
|
+
".bat",
|
|
3701
|
+
".cmd",
|
|
3702
|
+
".com",
|
|
3703
|
+
".msi",
|
|
3704
|
+
".sh",
|
|
3705
|
+
".app",
|
|
3706
|
+
".scr",
|
|
3707
|
+
".ps1"
|
|
3708
|
+
]);
|
|
3709
|
+
var EXECUTABLE_MIMES = /* @__PURE__ */ new Set([
|
|
3710
|
+
"application/x-msdownload",
|
|
3711
|
+
"application/x-msdos-program",
|
|
3712
|
+
"application/x-sh",
|
|
3713
|
+
"application/x-executable",
|
|
3714
|
+
"application/vnd.microsoft.portable-executable",
|
|
3715
|
+
"application/x-mach-binary"
|
|
3716
|
+
]);
|
|
3717
|
+
function looksExecutable(name, type) {
|
|
3718
|
+
const dot = name.lastIndexOf(".");
|
|
3719
|
+
const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
|
|
3720
|
+
return EXECUTABLE_EXTENSIONS.has(ext) || EXECUTABLE_MIMES.has(type);
|
|
3721
|
+
}
|
|
3722
|
+
async function buildEncryptedFileInput(args) {
|
|
3723
|
+
const { file, providerPubkey, identity, blossom } = args;
|
|
3724
|
+
const name = file.name ?? "upload";
|
|
3725
|
+
if (looksExecutable(name, file.type)) {
|
|
3726
|
+
throw new Error("Refusing to upload an executable file.");
|
|
3727
|
+
}
|
|
3728
|
+
const transport = createBlossomTransport({ blossom, identity });
|
|
3729
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
3730
|
+
const member = await transport.seedBytes({ bytes, recipientPubkey: providerPubkey });
|
|
3731
|
+
return {
|
|
3732
|
+
name,
|
|
3733
|
+
size: bytes.byteLength,
|
|
3734
|
+
mime: file.type || "application/octet-stream",
|
|
3735
|
+
transports: [member]
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
async function fetchEncryptedFileOutput(args) {
|
|
3739
|
+
const { attachment, providerPubkey, identity, blossom, maxBytes } = args;
|
|
3740
|
+
const member = attachment.transports.find(
|
|
3741
|
+
(t) => t.kind === "blossom"
|
|
3742
|
+
);
|
|
3743
|
+
if (member === void 0) {
|
|
3744
|
+
throw new Error("Attachment has no blossom transport.");
|
|
3745
|
+
}
|
|
3746
|
+
const transport = createBlossomTransport({ blossom, identity });
|
|
3747
|
+
const bytes = await transport.fetchToBytes({
|
|
3748
|
+
transport: member,
|
|
3749
|
+
senderPubkey: providerPubkey,
|
|
3750
|
+
maxBytes
|
|
3751
|
+
});
|
|
3752
|
+
return { bytes, name: attachment.name, mime: attachment.mime };
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3357
3755
|
// src/services/jobErrors.ts
|
|
3358
3756
|
var AGENT_UNAVAILABLE_MARKERS = [
|
|
3359
3757
|
"agent temporarily unavailable",
|
|
@@ -3925,6 +4323,6 @@ function makeCensor() {
|
|
|
3925
4323
|
};
|
|
3926
4324
|
}
|
|
3927
4325
|
|
|
3928
|
-
export { BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, JobWaitTimeoutError, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_LONG_FORM_ARTICLE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, PaymentRequestSchema, PingService, PoliciesService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, aggregateNetworkStats, assertExpiry, assertLamports, assetByKey, assetKey, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, decodeJobPayload, encodeJobPayload, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, formatAssetAmount, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
|
|
4326
|
+
export { ACCEPT_TRANSPORTS_TAG, BlossomService, BoundedSet, DEFAULTS, DEFAULT_KIND_OFFSET, DEFAULT_REDACT_PATHS, DiscoveryService, ELISYM_PROTOCOL_TAG, ENVELOPE_VERSION, ElisymClient, ElisymIdentity, GlobalConfigSchema, INPUT_REDACT_PATHS, JobWaitTimeoutError, KIND_APP_HANDLER, KIND_JOB_FEEDBACK, KIND_JOB_REQUEST, KIND_JOB_REQUEST_BASE, KIND_JOB_RESULT, KIND_JOB_RESULT_BASE, KIND_LONG_FORM_ARTICLE, KIND_PING, KIND_PONG, KNOWN_ASSETS, LAMPORTS_PER_SOL, LIMITS, MarketplaceService, MediaService, NATIVE_SOL, NostrPool, POLICY_D_TAG_PREFIX, POLICY_TYPE_REGEX, POLICY_T_TAG, PROTOCOL_PROGRAM_ID_DEVNET, PaymentRequestSchema, PingService, PoliciesService, RELAYS, SECRET_REDACT_PATHS, SessionSpendLimitEntrySchema, SolanaPaymentStrategy, USDC_SOLANA_DEVNET, aggregateNetworkStats, assertExpiry, assertLamports, assetByKey, assetKey, buildAcceptTransportsTag, buildEncryptedFileInput, buildPaymentInstructions, calculateProtocolFee, classifyJobError, clearPriorityFeeCache, clearProtocolConfigCache, clearQuickVerifyCache, compareAgentsByRank, computeRankKey, createBlossomTransport, createPaymentRequestWithOnchainConfig, createSlidingWindowLimiter, decodeJobPayload, decryptBytesFromSender, encodeJobPayload, encryptBytesForRecipient, estimateNetworkBaseline, estimatePriorityFeeMicroLamports, estimateSolFeeLamports, fetchEncryptedFileOutput, formatAssetAmount, formatFeeBreakdown, formatNetworkBaseline, formatSol, getNetworkStats, getProtocolConfig, getProtocolProgramId, jobRequestKind, jobResultKind, makeCensor, nip44Decrypt, nip44Encrypt, parseAssetAmount, parsePaymentRequest, pickPercentileFee, readAcceptedTransports, resolveAssetFromPaymentRequest, resolveKnownAsset, timeAgo, toDTag, truncateKey, utf8ByteLength, validateAgentName, validateExpiry, verifyJobPaymentQuick };
|
|
3929
4327
|
//# sourceMappingURL=index.js.map
|
|
3930
4328
|
//# sourceMappingURL=index.js.map
|