@le-space/ui 0.3.0 → 0.3.3

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/react/index.js CHANGED
@@ -32,6 +32,7 @@ var IDLE_DEPLOYMENT_PROGRESS = {
32
32
  };
33
33
  var REFRESH_INTERVAL_MS = 3e4;
34
34
  var RELAY_PING_INTERVAL_MS = 2e4;
35
+ var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
35
36
  var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
36
37
  var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
37
38
  var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
@@ -57,6 +58,12 @@ function formatDateTime(value) {
57
58
  if (Number.isNaN(date.getTime())) return String(value);
58
59
  return date.toLocaleString();
59
60
  }
61
+ function formatTierSpecLabel(vcpus, memoryMiB, diskMiB) {
62
+ if (vcpus == null || !Number.isFinite(vcpus) || memoryMiB == null || !Number.isFinite(memoryMiB) || diskMiB == null || !Number.isFinite(diskMiB)) {
63
+ return "-";
64
+ }
65
+ return `(${formatNumber(vcpus, 0)} vCPU \xB7 ${formatNumber(memoryMiB / 1024, 1)} GiB RAM \xB7 ${formatNumber(diskMiB / 1024, 0)} GiB disk)`;
66
+ }
60
67
  function buildSshCommand(hostIpv4, mappedPorts2) {
61
68
  if (!hostIpv4) return null;
62
69
  const sshPort = mappedPorts2.find((entry) => entry.label.startsWith("22/"))?.hostPort ?? 22;
@@ -402,6 +409,18 @@ function extractReferenceHashes(details) {
402
409
  if (!Array.isArray(errors)) return [];
403
410
  return errors.filter((value) => typeof value === "string");
404
411
  }
412
+ function extractInsufficientBalanceMessage(details) {
413
+ if (!details || typeof details !== "object" || !("errors" in details)) return null;
414
+ const errors = details.errors;
415
+ const firstError = Array.isArray(errors) && errors[0] && typeof errors[0] === "object" ? errors[0] : null;
416
+ const accountBalance = typeof firstError?.account_balance === "number" || typeof firstError?.account_balance === "string" ? Number(firstError.account_balance) : Number.NaN;
417
+ const requiredBalance = typeof firstError?.required_balance === "number" || typeof firstError?.required_balance === "string" ? Number(firstError.required_balance) : Number.NaN;
418
+ if (!Number.isFinite(accountBalance) || !Number.isFinite(requiredBalance)) {
419
+ return null;
420
+ }
421
+ const shortfall = requiredBalance - accountBalance;
422
+ return shortfall > 0 ? `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short` : `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required`;
423
+ }
405
424
  function describeRejectedDeployment(payload, references, rootfsRef) {
406
425
  const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
407
426
  const pendingReferences = references.filter((reference) => reference.status === "pending");
@@ -416,6 +435,10 @@ function describeRejectedDeployment(payload, references, rootfsRef) {
416
435
  if (missingReferences.length > 0) {
417
436
  return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
418
437
  }
438
+ const insufficientBalanceMessage = extractInsufficientBalanceMessage(payload.details);
439
+ if (insufficientBalanceMessage) {
440
+ return `Aleph rejected this deployment due to ${insufficientBalanceMessage}.`;
441
+ }
419
442
  const referencedHashes = extractReferenceHashes(payload.details);
420
443
  if (referencedHashes.length > 0) {
421
444
  return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
@@ -1028,6 +1051,20 @@ function extractReferenceHashes2(details) {
1028
1051
  if (!Array.isArray(errors)) return [];
1029
1052
  return errors.filter((value) => typeof value === "string");
1030
1053
  }
1054
+ function extractInsufficientBalanceMessage2(details) {
1055
+ if (!details || typeof details !== "object" || !("errors" in details)) {
1056
+ return null;
1057
+ }
1058
+ const errors = details.errors;
1059
+ const firstError = Array.isArray(errors) && errors[0] && typeof errors[0] === "object" ? errors[0] : null;
1060
+ const accountBalance = typeof firstError?.account_balance === "number" || typeof firstError?.account_balance === "string" ? Number(firstError.account_balance) : Number.NaN;
1061
+ const requiredBalance = typeof firstError?.required_balance === "number" || typeof firstError?.required_balance === "string" ? Number(firstError.required_balance) : Number.NaN;
1062
+ if (!Number.isFinite(accountBalance) || !Number.isFinite(requiredBalance)) {
1063
+ return null;
1064
+ }
1065
+ const shortfall = requiredBalance - accountBalance;
1066
+ return shortfall > 0 ? `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short` : `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required`;
1067
+ }
1031
1068
  function describeRejectedDeployment2(payload, references, rootfsRef) {
1032
1069
  const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
1033
1070
  const pendingReferences = references.filter((reference) => reference.status === "pending");
@@ -1042,6 +1079,10 @@ function describeRejectedDeployment2(payload, references, rootfsRef) {
1042
1079
  if (missingReferences.length > 0) {
1043
1080
  return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
1044
1081
  }
1082
+ const insufficientBalanceMessage = extractInsufficientBalanceMessage2(payload.details);
1083
+ if (insufficientBalanceMessage) {
1084
+ return `Aleph rejected this deployment due to ${insufficientBalanceMessage}.`;
1085
+ }
1045
1086
  const referencedHashes = extractReferenceHashes2(payload.details);
1046
1087
  if (referencedHashes.length > 0) {
1047
1088
  return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
@@ -1454,6 +1495,14 @@ function buildPaymentQuote(tier, pricing, balance) {
1454
1495
  label: "credits"
1455
1496
  };
1456
1497
  }
1498
+ function describeRejectedInstanceBroadcast(response) {
1499
+ const balanceMessage = extractInsufficientBalanceMessage2(response.details);
1500
+ if (balanceMessage) {
1501
+ return `Aleph rejected this deployment due to ${balanceMessage}.`;
1502
+ }
1503
+ const errorCode = typeof response.error_code === "number" ? response.error_code : null;
1504
+ return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
1505
+ }
1457
1506
  function createInstanceContent(args) {
1458
1507
  const sshKey = normalizeSshPublicKey(args.sshPublicKey);
1459
1508
  if (!sshKey) {
@@ -1570,9 +1619,9 @@ async function deployInstance(args) {
1570
1619
  label: status === "processed" ? "Deployment accepted by Aleph" : status === "rejected" ? "Deployment rejected by Aleph" : "Deployment submitted to Aleph",
1571
1620
  progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
1572
1621
  status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
1573
- detail: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1622
+ detail: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
1574
1623
  itemHash: message.item_hash,
1575
- error: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1624
+ error: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
1576
1625
  timestamp: Date.now()
1577
1626
  });
1578
1627
  return {
@@ -1581,7 +1630,7 @@ async function deployInstance(args) {
1581
1630
  status,
1582
1631
  message,
1583
1632
  response,
1584
- rejectionReason: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null
1633
+ rejectionReason: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null
1585
1634
  };
1586
1635
  }
1587
1636
 
@@ -1971,7 +2020,13 @@ async function configureUcGoPeer(args) {
1971
2020
  ws_port: args.wsPort ?? void 0,
1972
2021
  udp_port: args.udpPort ?? void 0,
1973
2022
  quic_port: args.quicPort ?? void 0,
1974
- webrtc_port: args.webrtcPort ?? void 0
2023
+ webrtc_port: args.webrtcPort ?? void 0,
2024
+ bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2025
+ bootstrap_publisher_libp2p_identity_b64: args.bootstrapPublisherLibp2pIdentityBase64 ?? void 0,
2026
+ bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2027
+ bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2028
+ bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2029
+ no_start: args.noStart === true ? true : void 0
1975
2030
  };
1976
2031
  try {
1977
2032
  const response = await args.fetch(
@@ -2011,7 +2066,13 @@ async function configureOrbitdbRelaySetup2(args) {
2011
2066
  metrics_port: args.metricsPort ?? void 0,
2012
2067
  metrics_https_port: args.metricsHttpsPort ?? void 0,
2013
2068
  webrtc_port: args.webrtcPort ?? void 0,
2014
- quic_port: args.quicPort ?? void 0
2069
+ quic_port: args.quicPort ?? void 0,
2070
+ bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2071
+ bootstrap_publisher_libp2p_identity_hex: args.bootstrapPublisherLibp2pIdentityHex ?? void 0,
2072
+ bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2073
+ bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2074
+ bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2075
+ no_start: args.noStart === true ? true : void 0
2015
2076
  };
2016
2077
  try {
2017
2078
  const response = await args.fetch(
@@ -2085,14 +2146,56 @@ async function fetchUcGoPeerMetadata(args) {
2085
2146
  }
2086
2147
 
2087
2148
  // ../aleph-bootstrap/src/index.ts
2149
+ import { recoverMessageAddress } from "viem";
2088
2150
  var DEFAULT_ALEPH_API_HOST3 = "https://api2.aleph.im";
2089
2151
  var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
2090
2152
  var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2091
2153
  var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2092
2154
  var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2155
+ var DEFAULT_BOOTSTRAP_PAGINATION = 50;
2156
+ var RELAY_BOOTSTRAP_SIGNATURE_SCHEME = "personal_sign";
2157
+ function serializeOwnerAuthorizationPayload(payload) {
2158
+ return JSON.stringify({
2159
+ ownerAddress: payload.ownerAddress,
2160
+ publisherAddress: payload.publisherAddress,
2161
+ peerId: payload.peerId,
2162
+ registrationId: payload.registrationId,
2163
+ profile: payload.profile,
2164
+ version: payload.version,
2165
+ instanceItemHash: payload.instanceItemHash,
2166
+ issuedAt: payload.issuedAt,
2167
+ expiresAt: payload.expiresAt
2168
+ });
2169
+ }
2170
+ function serializeRelayProofPayload(payload) {
2171
+ return JSON.stringify({
2172
+ peerId: payload.peerId,
2173
+ multiaddrs: dedupeMultiaddrs(payload.multiaddrs),
2174
+ browserMultiaddrs: payload.browserMultiaddrs ? dedupeMultiaddrs(payload.browserMultiaddrs) : void 0,
2175
+ registrationId: payload.registrationId,
2176
+ profile: payload.profile,
2177
+ version: payload.version,
2178
+ updatedAt: payload.updatedAt
2179
+ });
2180
+ }
2181
+ function asTrimmedString(value) {
2182
+ return typeof value === "string" && value.trim() ? value.trim() : null;
2183
+ }
2184
+ function asNumber4(value) {
2185
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2186
+ if (typeof value === "string" && value.trim()) {
2187
+ const parsed = Number(value);
2188
+ return Number.isFinite(parsed) ? parsed : null;
2189
+ }
2190
+ return null;
2191
+ }
2093
2192
  function normalizeHost(host) {
2094
2193
  return host.trim().toLowerCase();
2095
2194
  }
2195
+ function normalizeSignature(signature) {
2196
+ const trimmed = signature.trim();
2197
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
2198
+ }
2096
2199
  function splitMultiaddr(addr) {
2097
2200
  return addr.split("/").filter(Boolean);
2098
2201
  }
@@ -2163,6 +2266,100 @@ function filterPublicMultiaddrs(addrs, options = {}) {
2163
2266
  return true;
2164
2267
  });
2165
2268
  }
2269
+ function normalizeRelayBootstrapContent(value) {
2270
+ if (!value || typeof value !== "object") return null;
2271
+ const content = value;
2272
+ const peerId = asTrimmedString(content.peerId);
2273
+ const updatedAt = asNumber4(content.updatedAt);
2274
+ if (!peerId || updatedAt == null) return null;
2275
+ const multiaddrs = Array.isArray(content.multiaddrs) ? content.multiaddrs.filter((entry) => typeof entry === "string") : [];
2276
+ const browserMultiaddrs = Array.isArray(content.browserMultiaddrs) ? content.browserMultiaddrs.filter((entry) => typeof entry === "string") : void 0;
2277
+ return {
2278
+ peerId,
2279
+ multiaddrs: dedupeMultiaddrs(multiaddrs),
2280
+ browserMultiaddrs: browserMultiaddrs ? dedupeMultiaddrs(browserMultiaddrs) : void 0,
2281
+ registrationId: asTrimmedString(content.registrationId) ?? void 0,
2282
+ profile: asTrimmedString(content.profile) ?? void 0,
2283
+ version: asTrimmedString(content.version) ?? void 0,
2284
+ ownerAddress: asTrimmedString(content.ownerAddress) ?? void 0,
2285
+ publisherAddress: asTrimmedString(content.publisherAddress) ?? void 0,
2286
+ authorization: normalizeRelayBootstrapAuthorizationRecord(content.authorization) ?? void 0,
2287
+ relayProof: normalizeRelayBootstrapProofRecord(content.relayProof) ?? void 0,
2288
+ updatedAt
2289
+ };
2290
+ }
2291
+ function normalizeRelayBootstrapAuthorizationPayload(value) {
2292
+ if (!value || typeof value !== "object") return null;
2293
+ const payload = value;
2294
+ const ownerAddress = asTrimmedString(payload.ownerAddress);
2295
+ const publisherAddress = asTrimmedString(payload.publisherAddress);
2296
+ const peerId = asTrimmedString(payload.peerId);
2297
+ const issuedAt = asNumber4(payload.issuedAt);
2298
+ const expiresAt = asNumber4(payload.expiresAt) ?? void 0;
2299
+ if (!ownerAddress || !publisherAddress || !peerId || issuedAt == null) {
2300
+ return null;
2301
+ }
2302
+ return {
2303
+ ownerAddress,
2304
+ publisherAddress,
2305
+ peerId,
2306
+ registrationId: asTrimmedString(payload.registrationId) ?? void 0,
2307
+ profile: asTrimmedString(payload.profile) ?? void 0,
2308
+ version: asTrimmedString(payload.version) ?? void 0,
2309
+ instanceItemHash: asTrimmedString(payload.instanceItemHash) ?? void 0,
2310
+ issuedAt,
2311
+ expiresAt
2312
+ };
2313
+ }
2314
+ function normalizeRelayBootstrapAuthorizationRecord(value) {
2315
+ if (!value || typeof value !== "object") return null;
2316
+ const record = value;
2317
+ const scheme = asTrimmedString(record.scheme);
2318
+ const signature = asTrimmedString(record.signature);
2319
+ const payload = normalizeRelayBootstrapAuthorizationPayload(record.payload);
2320
+ if (!scheme || !signature || !payload) return null;
2321
+ return { scheme, signature, payload };
2322
+ }
2323
+ function normalizeRelayBootstrapProofPayload(value) {
2324
+ if (!value || typeof value !== "object") return null;
2325
+ const payload = value;
2326
+ const peerId = asTrimmedString(payload.peerId);
2327
+ const updatedAt = asNumber4(payload.updatedAt);
2328
+ if (!peerId || updatedAt == null) return null;
2329
+ const multiaddrs = Array.isArray(payload.multiaddrs) ? payload.multiaddrs.filter((entry) => typeof entry === "string") : [];
2330
+ const browserMultiaddrs = Array.isArray(payload.browserMultiaddrs) ? payload.browserMultiaddrs.filter((entry) => typeof entry === "string") : void 0;
2331
+ return {
2332
+ peerId,
2333
+ multiaddrs: dedupeMultiaddrs(multiaddrs),
2334
+ browserMultiaddrs: browserMultiaddrs ? dedupeMultiaddrs(browserMultiaddrs) : void 0,
2335
+ registrationId: asTrimmedString(payload.registrationId) ?? void 0,
2336
+ profile: asTrimmedString(payload.profile) ?? void 0,
2337
+ version: asTrimmedString(payload.version) ?? void 0,
2338
+ updatedAt
2339
+ };
2340
+ }
2341
+ function normalizeRelayBootstrapProofRecord(value) {
2342
+ if (!value || typeof value !== "object") return null;
2343
+ const record = value;
2344
+ const scheme = asTrimmedString(record.scheme);
2345
+ const signature = asTrimmedString(record.signature);
2346
+ const payload = normalizeRelayBootstrapProofPayload(record.payload);
2347
+ if (!scheme || !signature || !payload) return null;
2348
+ return { scheme, signature, payload };
2349
+ }
2350
+ function normalizeRelayBootstrapPostRecord(value) {
2351
+ if (!value || typeof value !== "object") return null;
2352
+ const entry = value;
2353
+ return {
2354
+ hash: asTrimmedString(entry.hash),
2355
+ itemHash: asTrimmedString(entry.item_hash),
2356
+ address: asTrimmedString(entry.address),
2357
+ ref: asTrimmedString(entry.ref),
2358
+ type: asTrimmedString(entry.type),
2359
+ time: asNumber4(entry.time),
2360
+ content: normalizeRelayBootstrapContent(entry.content)
2361
+ };
2362
+ }
2166
2363
  function buildRelayBootstrapPostContent(args) {
2167
2364
  const now = args.now ?? Date.now() / 1e3;
2168
2365
  const updatedAt = args.now ?? Date.now();
@@ -2176,8 +2373,13 @@ function buildRelayBootstrapPostContent(args) {
2176
2373
  browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2177
2374
  browserDialableOnly: true
2178
2375
  }) : void 0,
2376
+ registrationId: args.registrationId,
2179
2377
  profile: args.profile,
2180
2378
  version: args.version,
2379
+ ownerAddress: args.ownerAddress,
2380
+ publisherAddress: args.publisherAddress,
2381
+ authorization: args.authorization,
2382
+ relayProof: args.relayProof,
2181
2383
  updatedAt: Math.round(updatedAt)
2182
2384
  },
2183
2385
  time: now
@@ -2191,10 +2393,15 @@ async function createRelayBootstrapPost(args) {
2191
2393
  peerId: args.peerId,
2192
2394
  multiaddrs: args.multiaddrs,
2193
2395
  browserMultiaddrs: args.browserMultiaddrs,
2396
+ registrationId: args.registrationId,
2194
2397
  ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2195
2398
  postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2196
2399
  profile: args.profile,
2197
2400
  version: args.version,
2401
+ ownerAddress: args.ownerAddress,
2402
+ publisherAddress: args.publisherAddress,
2403
+ authorization: args.authorization,
2404
+ relayProof: args.relayProof,
2198
2405
  now: nowMillis
2199
2406
  });
2200
2407
  itemContent.time = nowSeconds;
@@ -2211,19 +2418,130 @@ async function createRelayBootstrapPost(args) {
2211
2418
  item_hash: itemHash
2212
2419
  };
2213
2420
  }
2421
+ async function signRelayBootstrapAuthorization(args) {
2422
+ const payload = {
2423
+ ownerAddress: args.ownerAddress,
2424
+ publisherAddress: args.publisherAddress,
2425
+ peerId: args.peerId,
2426
+ registrationId: args.registrationId,
2427
+ profile: args.profile,
2428
+ version: args.version,
2429
+ instanceItemHash: args.instanceItemHash,
2430
+ issuedAt: args.issuedAt ?? Date.now(),
2431
+ expiresAt: args.expiresAt
2432
+ };
2433
+ const serialized = serializeOwnerAuthorizationPayload(payload);
2434
+ const signature = await args.signer(args.ownerAddress, serialized);
2435
+ return {
2436
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2437
+ payload,
2438
+ signature: normalizeSignature(signature)
2439
+ };
2440
+ }
2441
+ async function signRelayBootstrapProof(args) {
2442
+ const payload = {
2443
+ peerId: args.peerId,
2444
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2445
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2446
+ browserDialableOnly: true
2447
+ }) : void 0,
2448
+ registrationId: args.registrationId,
2449
+ profile: args.profile,
2450
+ version: args.version,
2451
+ updatedAt: args.updatedAt ?? Date.now()
2452
+ };
2453
+ const serialized = serializeRelayProofPayload(payload);
2454
+ const signature = await args.signer(args.publisherAddress, serialized);
2455
+ return {
2456
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2457
+ payload,
2458
+ signature: normalizeSignature(signature)
2459
+ };
2460
+ }
2461
+ async function fetchAlephBootstrapPosts(options = {}) {
2462
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
2463
+ if (typeof fetchImpl !== "function") {
2464
+ throw new Error(
2465
+ "A fetch implementation is required to query Aleph bootstrap posts."
2466
+ );
2467
+ }
2468
+ const url = new URL(
2469
+ "/api/v0/posts.json",
2470
+ options.apiHost ?? DEFAULT_ALEPH_API_HOST3
2471
+ );
2472
+ url.searchParams.set(
2473
+ "channels",
2474
+ options.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL
2475
+ );
2476
+ url.searchParams.set("refs", options.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF);
2477
+ url.searchParams.set(
2478
+ "types",
2479
+ options.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE
2480
+ );
2481
+ url.searchParams.set(
2482
+ "pagination",
2483
+ String(options.pagination ?? DEFAULT_BOOTSTRAP_PAGINATION)
2484
+ );
2485
+ url.searchParams.set("page", String(options.page ?? 1));
2486
+ const response = await fetchImpl(url.toString(), {
2487
+ method: "GET",
2488
+ headers: { accept: "application/json" }
2489
+ });
2490
+ if (!response.ok) {
2491
+ throw new Error(`Aleph bootstrap post lookup failed: ${response.status}`);
2492
+ }
2493
+ const payload = await response.json();
2494
+ return (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null);
2495
+ }
2214
2496
 
2215
2497
  // ../core/src/bootstrap-registration.ts
2216
2498
  async function publishRelayBootstrapRegistration(args) {
2499
+ const publisherAddress = args.publisherAddress ?? args.sender;
2500
+ const ownerAddress = args.ownerAddress;
2501
+ let ownerAuthorization = args.ownerAuthorization;
2502
+ let relayProof = args.relayProof;
2503
+ if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
2504
+ ownerAuthorization = await signRelayBootstrapAuthorization({
2505
+ ownerAddress,
2506
+ publisherAddress,
2507
+ peerId: args.peerId,
2508
+ registrationId: args.registrationId,
2509
+ profile: args.profile,
2510
+ version: args.version,
2511
+ instanceItemHash: args.instanceItemHash,
2512
+ issuedAt: args.authorizationIssuedAt ?? args.now ?? Date.now(),
2513
+ expiresAt: args.authorizationExpiresAt,
2514
+ signer: args.ownerSigner
2515
+ });
2516
+ }
2517
+ if (!relayProof && args.publisherSigner) {
2518
+ relayProof = await signRelayBootstrapProof({
2519
+ publisherAddress,
2520
+ peerId: args.peerId,
2521
+ multiaddrs: args.multiaddrs,
2522
+ browserMultiaddrs: args.browserMultiaddrs,
2523
+ registrationId: args.registrationId,
2524
+ profile: args.profile,
2525
+ version: args.version,
2526
+ updatedAt: args.now ?? Date.now(),
2527
+ signer: args.publisherSigner
2528
+ });
2529
+ }
2217
2530
  const unsigned = await createRelayBootstrapPost({
2218
2531
  sender: args.sender,
2219
2532
  peerId: args.peerId,
2220
2533
  multiaddrs: args.multiaddrs,
2221
2534
  browserMultiaddrs: args.browserMultiaddrs,
2535
+ registrationId: args.registrationId,
2222
2536
  channel: args.channel,
2223
2537
  ref: args.ref,
2224
2538
  postType: args.postType,
2225
2539
  profile: args.profile,
2226
2540
  version: args.version,
2541
+ ownerAddress,
2542
+ publisherAddress,
2543
+ authorization: ownerAuthorization,
2544
+ relayProof,
2227
2545
  now: args.now,
2228
2546
  hasher: args.hasher
2229
2547
  });
@@ -2242,6 +2560,32 @@ async function publishRelayBootstrapRegistration(args) {
2242
2560
  sync: args.sync ?? true,
2243
2561
  fetch: args.fetch
2244
2562
  });
2563
+ let forgottenHashes = [];
2564
+ let forgetResult;
2565
+ if (args.forgetPrevious && args.registrationId) {
2566
+ const previousPosts = await fetchAlephBootstrapPosts({
2567
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2568
+ channel: args.channel,
2569
+ ref: args.ref,
2570
+ postType: args.postType,
2571
+ fetch: args.fetch
2572
+ });
2573
+ forgottenHashes = previousPosts.filter(
2574
+ (post) => post.address?.toLowerCase() === args.sender.toLowerCase() && post.content?.registrationId === args.registrationId && (post.itemHash ?? post.hash) != null && (post.itemHash ?? post.hash) !== unsigned.item_hash
2575
+ ).map((post) => post.itemHash ?? post.hash ?? "").filter(Boolean);
2576
+ if (forgottenHashes.length > 0) {
2577
+ forgetResult = await forgetAlephMessages({
2578
+ sender: args.sender,
2579
+ hashes: forgottenHashes,
2580
+ reason: args.forgetPreviousReason ?? `Replace older relay bootstrap records for ${args.registrationId}`,
2581
+ signer: args.signer,
2582
+ hasher: args.hasher,
2583
+ fetch: args.fetch,
2584
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2585
+ sync: args.sync ?? true
2586
+ });
2587
+ }
2588
+ }
2245
2589
  return {
2246
2590
  status: "published",
2247
2591
  itemHash: unsigned.item_hash,
@@ -2249,7 +2593,9 @@ async function publishRelayBootstrapRegistration(args) {
2249
2593
  response,
2250
2594
  httpStatus,
2251
2595
  publishedMultiaddrs: content.content?.multiaddrs ?? [],
2252
- publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2596
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? [],
2597
+ forgottenHashes,
2598
+ forgetResult
2253
2599
  };
2254
2600
  }
2255
2601
 
@@ -2288,8 +2634,9 @@ function defaultState(props = {}) {
2288
2634
  sshPublicKey: props.sshPublicKey ?? "",
2289
2635
  instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
2290
2636
  tierId: DEFAULT_TIER_ID,
2637
+ showAdvanced: Boolean(props.manifestJson?.trim() || props.sshPublicKey?.trim()),
2291
2638
  showInstances: props.showInstances ?? true,
2292
- showPasteManifest: false,
2639
+ showPasteManifest: Boolean(props.manifestJson?.trim()),
2293
2640
  busy: {
2294
2641
  connectingWallet: false,
2295
2642
  refreshing: false,
@@ -2325,6 +2672,14 @@ function defaultState(props = {}) {
2325
2672
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
2326
2673
  };
2327
2674
  }
2675
+ function manifestLoadErrorState(error) {
2676
+ const message = error instanceof Error ? error.message : String(error);
2677
+ return {
2678
+ manifest: null,
2679
+ valid: false,
2680
+ errors: [message]
2681
+ };
2682
+ }
2328
2683
  function isDebugEnabled(props) {
2329
2684
  if (props.debug) {
2330
2685
  return true;
@@ -2607,6 +2962,7 @@ var SponsorRelayController = class {
2607
2962
  client;
2608
2963
  refreshTimer = null;
2609
2964
  pingTimer = null;
2965
+ manifestRefreshTimer = null;
2610
2966
  stopWalletWatch = null;
2611
2967
  props;
2612
2968
  progressEmitter = createDeploymentProgressEmitter();
@@ -2979,6 +3335,7 @@ var SponsorRelayController = class {
2979
3335
  destroy() {
2980
3336
  if (this.refreshTimer) clearInterval(this.refreshTimer);
2981
3337
  if (this.pingTimer) clearInterval(this.pingTimer);
3338
+ if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
2982
3339
  this.stopWalletWatch?.();
2983
3340
  }
2984
3341
  setOpen(open) {
@@ -2989,15 +3346,27 @@ var SponsorRelayController = class {
2989
3346
  }
2990
3347
  setManifestUrl(manifestUrl) {
2991
3348
  this.patch({ manifestUrl });
3349
+ this.queueManifestRefresh();
2992
3350
  }
2993
3351
  setManifestJson(manifestJson) {
2994
- this.patch({ manifestJson });
3352
+ this.patch({
3353
+ manifestJson,
3354
+ showAdvanced: this.state.showAdvanced || Boolean(manifestJson.trim()),
3355
+ showPasteManifest: this.state.showPasteManifest || Boolean(manifestJson.trim())
3356
+ });
3357
+ this.queueManifestRefresh();
3358
+ }
3359
+ setShowAdvanced(showAdvanced) {
3360
+ this.patch({ showAdvanced });
2995
3361
  }
2996
3362
  setShowPasteManifest(showPasteManifest) {
2997
3363
  this.patch({ showPasteManifest });
2998
3364
  }
2999
3365
  setSshPublicKey(sshPublicKey) {
3000
- this.patch({ sshPublicKey });
3366
+ this.patch({
3367
+ sshPublicKey,
3368
+ showAdvanced: this.state.showAdvanced || Boolean(sshPublicKey.trim())
3369
+ });
3001
3370
  }
3002
3371
  setInstanceName(instanceName) {
3003
3372
  this.patch({ instanceName });
@@ -3033,6 +3402,18 @@ var SponsorRelayController = class {
3033
3402
  selectedCrn
3034
3403
  });
3035
3404
  }
3405
+ queueManifestRefresh() {
3406
+ if (!this.state.ready) {
3407
+ return;
3408
+ }
3409
+ if (this.manifestRefreshTimer) {
3410
+ clearTimeout(this.manifestRefreshTimer);
3411
+ }
3412
+ this.manifestRefreshTimer = setTimeout(() => {
3413
+ this.manifestRefreshTimer = null;
3414
+ void this.refresh();
3415
+ }, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS);
3416
+ }
3036
3417
  async connectWallet() {
3037
3418
  this.patch({
3038
3419
  busy: { connectingWallet: true },
@@ -3086,15 +3467,15 @@ var SponsorRelayController = class {
3086
3467
  statusText: "Refreshing relay sponsor data"
3087
3468
  });
3088
3469
  try {
3089
- const manifestState = await resolveManifest({
3090
- manifestUrl: this.state.manifestUrl,
3091
- manifestJson: this.state.manifestJson
3092
- });
3093
- const manifest = manifestState.manifest;
3094
3470
  const [pricingSummary, crns] = await Promise.all([
3095
3471
  fetchInstancePricing(this.client.apiHost),
3096
3472
  this.client.fetchCrns()
3097
3473
  ]);
3474
+ const manifestState = await resolveManifest({
3475
+ manifestUrl: this.state.manifestUrl,
3476
+ manifestJson: this.state.manifestJson
3477
+ }).catch((error) => manifestLoadErrorState(error));
3478
+ const manifest = manifestState.manifest;
3098
3479
  let balance = this.state.balance;
3099
3480
  let instances = [];
3100
3481
  if (this.state.wallet.address) {
@@ -3584,11 +3965,11 @@ var basePanelStyle = {
3584
3965
  zIndex: 9999,
3585
3966
  width: "min(28rem, calc(100vw - 2rem))",
3586
3967
  overflow: "auto",
3587
- border: "1px solid rgba(255,255,255,0.12)",
3968
+ border: "1px solid var(--le-space-sponsor-relay-panel-border, rgba(199, 210, 254, 0.22))",
3588
3969
  borderRadius: "1.4rem",
3589
- background: "rgba(12,20,31,0.92)",
3970
+ background: "var(--le-space-sponsor-relay-panel-bg, rgba(49, 46, 129, 0.94))",
3590
3971
  color: "#f8fafc",
3591
- boxShadow: "0 28px 80px rgba(3,8,20,0.45)",
3972
+ boxShadow: "var(--le-space-sponsor-relay-panel-shadow, 0 28px 80px rgba(49, 46, 129, 0.34))",
3592
3973
  padding: "1rem",
3593
3974
  fontFamily: '"DM Sans", "Inter", sans-serif'
3594
3975
  };
@@ -3603,6 +3984,11 @@ var fieldStyle = {
3603
3984
  fontSize: "0.98rem",
3604
3985
  lineHeight: 1.45
3605
3986
  };
3987
+ var themedLauncherBackground = "linear-gradient(135deg, var(--le-space-sponsor-relay-launcher-start, #4f46e5) 0%, var(--le-space-sponsor-relay-launcher-end, #6366f1) 100%)";
3988
+ var themedLauncherBorder = "var(--le-space-sponsor-relay-launcher-border, rgba(199, 210, 254, 0.42))";
3989
+ var themedLauncherBadgeBackground = "var(--le-space-sponsor-relay-launcher-badge-bg, rgba(49, 46, 129, 0.92))";
3990
+ var themedLauncherBadgeBorder = "var(--le-space-sponsor-relay-launcher-badge-border, rgba(191, 219, 254, 0.24))";
3991
+ var themedLauncherDot = "var(--le-space-sponsor-relay-launcher-dot, #f59e0b)";
3606
3992
  function progressToneColor(status) {
3607
3993
  switch (status) {
3608
3994
  case "success":
@@ -3783,11 +4169,11 @@ function SponsorRelayFab(props) {
3783
4169
  const progressActive = state.deploymentProgress.stage !== "idle" && state.deploymentProgress.stage !== "completed" && state.deploymentProgress.stage !== "error";
3784
4170
  const deploymentProgressVisible = isDeploymentProgressVisible(state);
3785
4171
  const pulseScale = progressActive ? 1.03 : hovered ? 1.015 : 1;
3786
- const pulseShadow = progressActive ? `0 0 0 4px ${progressToneColor(indicator.tone)}22, 0 12px 28px rgba(15, 23, 42, 0.22)` : successFlash ? `0 0 0 5px rgba(55, 214, 122, 0.22), 0 14px 32px rgba(55, 214, 122, 0.22)` : launcherMode === "inline" ? hovered ? "0 14px 30px rgba(79, 70, 229, 0.28)" : "0 10px 24px rgba(99, 102, 241, 0.22)" : void 0;
3787
- const inlineButtonBackground = indicator.tone === "error" ? "linear-gradient(135deg, #b91c1c 0%, #dc2626 100%)" : indicator.tone === "warning" ? "linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%)" : indicator.tone === "success" ? "linear-gradient(135deg, #4f46e5 0%, #6366f1 100%)" : "linear-gradient(135deg, #4f46e5 0%, #6366f1 100%)";
3788
- const inlineButtonBorder = indicator.tone === "error" ? "1px solid rgba(254, 202, 202, 0.45)" : indicator.tone === "warning" ? "1px solid rgba(196, 181, 253, 0.42)" : "1px solid rgba(199, 210, 254, 0.42)";
3789
- const inlineBadgeBackground = indicator.tone === "error" ? "rgba(127, 29, 29, 0.88)" : indicator.tone === "warning" ? "rgba(88, 28, 135, 0.88)" : indicator.tone === "success" ? "rgba(49, 46, 129, 0.92)" : "rgba(30, 41, 59, 0.92)";
3790
- const inlineBadgeBorder = indicator.tone === "error" ? "1px solid rgba(254, 202, 202, 0.34)" : indicator.tone === "warning" ? "1px solid rgba(233, 213, 255, 0.28)" : indicator.tone === "success" ? "1px solid rgba(191, 219, 254, 0.24)" : "1px solid rgba(191, 219, 254, 0.2)";
4172
+ const pulseShadow = progressActive ? `0 0 0 4px ${progressToneColor(indicator.tone)}22, 0 12px 28px rgba(15, 23, 42, 0.22)` : successFlash ? `0 0 0 5px rgba(55, 214, 122, 0.22), 0 14px 32px rgba(55, 214, 122, 0.22)` : launcherMode === "inline" ? hovered ? "var(--le-space-sponsor-relay-launcher-hover-shadow, 0 14px 30px rgba(79, 70, 229, 0.28))" : "var(--le-space-sponsor-relay-launcher-shadow, 0 10px 24px rgba(99, 102, 241, 0.22))" : void 0;
4173
+ const inlineButtonBackground = themedLauncherBackground;
4174
+ const inlineButtonBorder = `1px solid ${themedLauncherBorder}`;
4175
+ const inlineBadgeBackground = themedLauncherBadgeBackground;
4176
+ const inlineBadgeBorder = `1px solid ${themedLauncherBadgeBorder}`;
3791
4177
  const panelStyle = launcherMode === "inline" ? inlinePanelStyle ?? {
3792
4178
  ...basePanelStyle,
3793
4179
  top: "4.75rem",
@@ -3819,8 +4205,8 @@ function SponsorRelayFab(props) {
3819
4205
  bottom: launcherMode === "floating" ? "5.8rem" : void 0,
3820
4206
  zIndex: launcherMode === "floating" ? 1e4 : "auto",
3821
4207
  borderRadius: "999px",
3822
- border: launcherMode === "floating" ? "2px solid rgba(255,255,255,0.9)" : inlineButtonBorder,
3823
- background: launcherMode === "floating" ? "linear-gradient(135deg, #e91315 0%, #ffc83f 100%)" : inlineButtonBackground,
4208
+ border: launcherMode === "floating" ? `2px solid ${themedLauncherBorder}` : inlineButtonBorder,
4209
+ background: launcherMode === "floating" ? themedLauncherBackground : inlineButtonBackground,
3824
4210
  color: "white",
3825
4211
  minHeight: launcherMode === "floating" ? void 0 : "2.25rem",
3826
4212
  padding: launcherMode === "floating" ? "0.9rem 1.2rem" : "0.42rem 0.56rem 0.42rem 0.76rem",
@@ -3856,8 +4242,8 @@ function SponsorRelayFab(props) {
3856
4242
  width: launcherMode === "floating" ? "0.5rem" : "0.42rem",
3857
4243
  height: launcherMode === "floating" ? "0.5rem" : "0.42rem",
3858
4244
  borderRadius: "999px",
3859
- background: launcherMode === "floating" ? "rgba(255,255,255,0.96)" : "#f59e0b",
3860
- boxShadow: launcherMode === "floating" ? "0 0 0 3px rgba(255,255,255,0.22)" : "0 0 0 3px rgba(245, 158, 11, 0.18)"
4245
+ background: launcherMode === "floating" ? "rgba(255,255,255,0.96)" : themedLauncherDot,
4246
+ boxShadow: launcherMode === "floating" ? "0 0 0 3px rgba(255,255,255,0.22)" : "0 0 0 3px var(--le-space-sponsor-relay-launcher-dot-ring, rgba(245, 158, 11, 0.18))"
3861
4247
  }
3862
4248
  }
3863
4249
  ),
@@ -3916,7 +4302,7 @@ function SponsorRelayFab(props) {
3916
4302
  position: "fixed",
3917
4303
  inset: 0,
3918
4304
  zIndex: 9998,
3919
- background: "radial-gradient(circle at 88% 82%, rgba(233,19,21,0.18), transparent 34%)"
4305
+ background: "radial-gradient(circle at 88% 82%, var(--le-space-sponsor-relay-backdrop-accent, rgba(79, 70, 229, 0.2)), transparent 34%)"
3920
4306
  }
3921
4307
  }
3922
4308
  ),
@@ -4137,15 +4523,6 @@ function SponsorRelayFab(props) {
4137
4523
  {
4138
4524
  style: { display: "grid", gap: "0.75rem", marginTop: "0.9rem" },
4139
4525
  children: [
4140
- /* @__PURE__ */ jsx(
4141
- "input",
4142
- {
4143
- style: fieldStyle,
4144
- value: state.manifestUrl,
4145
- onChange: (event) => controller.setManifestUrl(event.currentTarget.value),
4146
- placeholder: "Manifest URL"
4147
- }
4148
- ),
4149
4526
  /* @__PURE__ */ jsx(
4150
4527
  "input",
4151
4528
  {
@@ -4161,31 +4538,95 @@ function SponsorRelayFab(props) {
4161
4538
  style: fieldStyle,
4162
4539
  value: state.pricingSummary.tier?.id ?? state.tierId,
4163
4540
  onChange: (event) => controller.setTierId(event.currentTarget.value),
4164
- children: (state.pricingSummary.pricing?.tiers ?? []).map((tier) => /* @__PURE__ */ jsx("option", { value: tier.id, children: tier.id }, tier.id))
4541
+ children: (state.pricingSummary.pricing?.tiers?.length ? state.pricingSummary.pricing.tiers : [{ id: state.tierId, compute_units: 1 }]).map((tier) => {
4542
+ const unit = state.pricingSummary.pricing?.compute_unit;
4543
+ const vcpus = unit ? unit.vcpus * tier.compute_units : null;
4544
+ const memoryMiB = unit ? unit.memory_mib * tier.compute_units : null;
4545
+ const diskMiB = unit ? unit.disk_mib * tier.compute_units : null;
4546
+ return /* @__PURE__ */ jsx("option", { value: tier.id, children: `${tier.id} ${formatTierSpecLabel(vcpus, memoryMiB, diskMiB)}` }, tier.id);
4547
+ })
4165
4548
  }
4166
4549
  ),
4167
4550
  /* @__PURE__ */ jsx(
4168
- "textarea",
4551
+ "div",
4169
4552
  {
4170
- style: fieldStyle,
4171
- rows: 3,
4172
- value: state.sshPublicKey,
4173
- onChange: (event) => controller.setSshPublicKey(event.currentTarget.value),
4174
- placeholder: "SSH public key"
4553
+ style: {
4554
+ color: "#9fb2ca",
4555
+ fontSize: "0.74rem",
4556
+ lineHeight: 1.35,
4557
+ marginTop: "-0.15rem"
4558
+ },
4559
+ children: formatTierSpecLabel(
4560
+ state.pricingSummary.vcpus,
4561
+ state.pricingSummary.memoryMiB,
4562
+ state.pricingSummary.diskMiB
4563
+ )
4175
4564
  }
4176
4565
  ),
4177
- /* @__PURE__ */ jsxs("details", { children: [
4178
- /* @__PURE__ */ jsx("summary", { children: "Paste Manifest" }),
4179
- /* @__PURE__ */ jsx(
4180
- "textarea",
4181
- {
4182
- style: { ...fieldStyle, marginTop: "0.65rem" },
4183
- rows: 7,
4184
- value: state.manifestJson,
4185
- onChange: (event) => controller.setManifestJson(event.currentTarget.value)
4186
- }
4187
- )
4188
- ] })
4566
+ /* @__PURE__ */ jsxs(
4567
+ "details",
4568
+ {
4569
+ open: state.showAdvanced,
4570
+ onToggle: (event) => controller.setShowAdvanced(
4571
+ event.currentTarget.open
4572
+ ),
4573
+ children: [
4574
+ /* @__PURE__ */ jsx("summary", { children: "Advanced" }),
4575
+ /* @__PURE__ */ jsxs(
4576
+ "div",
4577
+ {
4578
+ style: {
4579
+ display: "grid",
4580
+ gap: "0.75rem",
4581
+ marginTop: "0.65rem"
4582
+ },
4583
+ children: [
4584
+ /* @__PURE__ */ jsx(
4585
+ "input",
4586
+ {
4587
+ style: fieldStyle,
4588
+ value: state.manifestUrl,
4589
+ onChange: (event) => controller.setManifestUrl(event.currentTarget.value),
4590
+ placeholder: "Manifest URL"
4591
+ }
4592
+ ),
4593
+ /* @__PURE__ */ jsx(
4594
+ "textarea",
4595
+ {
4596
+ style: fieldStyle,
4597
+ rows: 3,
4598
+ value: state.sshPublicKey,
4599
+ onChange: (event) => controller.setSshPublicKey(event.currentTarget.value),
4600
+ placeholder: "SSH public key"
4601
+ }
4602
+ ),
4603
+ /* @__PURE__ */ jsxs(
4604
+ "details",
4605
+ {
4606
+ open: state.showPasteManifest,
4607
+ onToggle: (event) => controller.setShowPasteManifest(
4608
+ event.currentTarget.open
4609
+ ),
4610
+ children: [
4611
+ /* @__PURE__ */ jsx("summary", { children: "Paste Manifest" }),
4612
+ /* @__PURE__ */ jsx(
4613
+ "textarea",
4614
+ {
4615
+ style: { ...fieldStyle, marginTop: "0.65rem" },
4616
+ rows: 7,
4617
+ value: state.manifestJson,
4618
+ onChange: (event) => controller.setManifestJson(event.currentTarget.value)
4619
+ }
4620
+ )
4621
+ ]
4622
+ }
4623
+ )
4624
+ ]
4625
+ }
4626
+ )
4627
+ ]
4628
+ }
4629
+ )
4189
4630
  ]
4190
4631
  }
4191
4632
  ),