@le-space/ui 0.3.22 → 0.3.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/ui",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "Floating Aleph sponsor relay UI components for Svelte and React.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@le-space/browser": "0.3.22",
40
- "@le-space/core": "0.3.22",
39
+ "@le-space/browser": "0.3.24",
40
+ "@le-space/core": "0.3.24",
41
41
  "viem": "^2.38.5"
42
42
  },
43
43
  "peerDependencies": {
package/react/index.js CHANGED
@@ -39,7 +39,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
39
39
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
40
40
 
41
41
  // src/shared/package-version.ts
42
- var UI_PACKAGE_VERSION = "0.3.22";
42
+ var UI_PACKAGE_VERSION = "0.3.24";
43
43
 
44
44
  // src/shared/format.ts
45
45
  function shortHash(value, head = 8, tail = 6) {
@@ -2152,6 +2152,31 @@ var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2152
2152
  var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2153
2153
  var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2154
2154
  var DEFAULT_BOOTSTRAP_PAGINATION = 50;
2155
+ var RELAY_BOOTSTRAP_SIGNATURE_SCHEME = "personal_sign";
2156
+ function serializeOwnerAuthorizationPayload(payload) {
2157
+ return JSON.stringify({
2158
+ ownerAddress: payload.ownerAddress,
2159
+ publisherAddress: payload.publisherAddress,
2160
+ peerId: payload.peerId,
2161
+ registrationId: payload.registrationId,
2162
+ profile: payload.profile,
2163
+ version: payload.version,
2164
+ instanceItemHash: payload.instanceItemHash,
2165
+ issuedAt: payload.issuedAt,
2166
+ expiresAt: payload.expiresAt
2167
+ });
2168
+ }
2169
+ function serializeRelayProofPayload(payload) {
2170
+ return JSON.stringify({
2171
+ peerId: payload.peerId,
2172
+ multiaddrs: dedupeMultiaddrs(payload.multiaddrs),
2173
+ browserMultiaddrs: payload.browserMultiaddrs ? dedupeMultiaddrs(payload.browserMultiaddrs) : void 0,
2174
+ registrationId: payload.registrationId,
2175
+ profile: payload.profile,
2176
+ version: payload.version,
2177
+ updatedAt: payload.updatedAt
2178
+ });
2179
+ }
2155
2180
  function asTrimmedString(value) {
2156
2181
  return typeof value === "string" && value.trim() ? value.trim() : null;
2157
2182
  }
@@ -2163,9 +2188,48 @@ function asNumber4(value) {
2163
2188
  }
2164
2189
  return null;
2165
2190
  }
2191
+ function normalizeHost(host) {
2192
+ return host.trim().toLowerCase();
2193
+ }
2166
2194
  function normalizeAddress(address) {
2167
2195
  return address.trim().toLowerCase();
2168
2196
  }
2197
+ function normalizeSignature(signature) {
2198
+ const trimmed = signature.trim();
2199
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
2200
+ }
2201
+ function splitMultiaddr(addr) {
2202
+ return addr.split("/").filter(Boolean);
2203
+ }
2204
+ function isPrivateIpv4(ip) {
2205
+ const parts = ip.split(".").map((part) => Number(part));
2206
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
2207
+ return false;
2208
+ }
2209
+ if (parts[0] === 10) return true;
2210
+ if (parts[0] === 127) return true;
2211
+ if (parts[0] === 0) return true;
2212
+ if (parts[0] === 169 && parts[1] === 254) return true;
2213
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
2214
+ if (parts[0] === 192 && parts[1] === 168) return true;
2215
+ if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) return true;
2216
+ return false;
2217
+ }
2218
+ function isPrivateIpv6(ip) {
2219
+ const normalized = ip.trim().toLowerCase();
2220
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
2221
+ }
2222
+ function isLocalHostname(host) {
2223
+ const normalized = normalizeHost(host);
2224
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local");
2225
+ }
2226
+ function hasPeerId(addr) {
2227
+ return splitMultiaddr(addr).includes("p2p");
2228
+ }
2229
+ function isBrowserDialableMultiaddr(addr) {
2230
+ const normalized = addr.toLowerCase();
2231
+ return normalized.includes("/ws") || normalized.includes("/wss") || normalized.includes("/webtransport") || normalized.includes("/webrtc-direct");
2232
+ }
2169
2233
  function dedupeMultiaddrs(addrs) {
2170
2234
  const seen = /* @__PURE__ */ new Set();
2171
2235
  const result = [];
@@ -2177,6 +2241,33 @@ function dedupeMultiaddrs(addrs) {
2177
2241
  }
2178
2242
  return result;
2179
2243
  }
2244
+ function isPublicMultiaddr(addr) {
2245
+ const parts = splitMultiaddr(addr);
2246
+ for (let index = 0; index < parts.length; index += 1) {
2247
+ const protocol = parts[index];
2248
+ const value = parts[index + 1];
2249
+ if (protocol === "ip4" && value) {
2250
+ return !isPrivateIpv4(value);
2251
+ }
2252
+ if (protocol === "ip6" && value) {
2253
+ return !isPrivateIpv6(value);
2254
+ }
2255
+ if ((protocol === "dns4" || protocol === "dns6" || protocol === "dnsaddr") && value) {
2256
+ return !isLocalHostname(value);
2257
+ }
2258
+ }
2259
+ return false;
2260
+ }
2261
+ function filterPublicMultiaddrs(addrs, options = {}) {
2262
+ return dedupeMultiaddrs(addrs).filter((addr) => {
2263
+ if (!isPublicMultiaddr(addr)) return false;
2264
+ if (options.requirePeerId !== false && !hasPeerId(addr)) return false;
2265
+ if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr)) {
2266
+ return false;
2267
+ }
2268
+ return true;
2269
+ });
2270
+ }
2180
2271
  function normalizeRelayBootstrapContent(value) {
2181
2272
  if (!value || typeof value !== "object") return null;
2182
2273
  const content = value;
@@ -2271,6 +2362,104 @@ function normalizeRelayBootstrapPostRecord(value) {
2271
2362
  content: normalizeRelayBootstrapContent(entry.content)
2272
2363
  };
2273
2364
  }
2365
+ function buildRelayBootstrapPostContent(args) {
2366
+ const now = args.now ?? Date.now() / 1e3;
2367
+ const updatedAt = args.now ?? Date.now();
2368
+ return {
2369
+ type: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2370
+ address: args.sender,
2371
+ ...args.ref ? { ref: args.ref } : {},
2372
+ content: {
2373
+ peerId: args.peerId,
2374
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2375
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2376
+ browserDialableOnly: true
2377
+ }) : void 0,
2378
+ registrationId: args.registrationId,
2379
+ profile: args.profile,
2380
+ version: args.version,
2381
+ ownerAddress: args.ownerAddress,
2382
+ publisherAddress: args.publisherAddress,
2383
+ authorization: args.authorization,
2384
+ relayProof: args.relayProof,
2385
+ updatedAt: Math.round(updatedAt)
2386
+ },
2387
+ time: now
2388
+ };
2389
+ }
2390
+ async function createRelayBootstrapPost(args) {
2391
+ const nowMillis = args.now ?? Date.now();
2392
+ const nowSeconds = nowMillis / 1e3;
2393
+ const itemContent = buildRelayBootstrapPostContent({
2394
+ sender: args.sender,
2395
+ peerId: args.peerId,
2396
+ multiaddrs: args.multiaddrs,
2397
+ browserMultiaddrs: args.browserMultiaddrs,
2398
+ registrationId: args.registrationId,
2399
+ ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2400
+ postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2401
+ profile: args.profile,
2402
+ version: args.version,
2403
+ ownerAddress: args.ownerAddress,
2404
+ publisherAddress: args.publisherAddress,
2405
+ authorization: args.authorization,
2406
+ relayProof: args.relayProof,
2407
+ now: nowMillis
2408
+ });
2409
+ itemContent.time = nowSeconds;
2410
+ const serialized = JSON.stringify(itemContent);
2411
+ const itemHash = await args.hasher(serialized);
2412
+ return {
2413
+ channel: args.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL,
2414
+ sender: args.sender,
2415
+ chain: "ETH",
2416
+ type: "POST",
2417
+ time: nowSeconds,
2418
+ item_type: "inline",
2419
+ item_content: serialized,
2420
+ item_hash: itemHash
2421
+ };
2422
+ }
2423
+ async function signRelayBootstrapAuthorization(args) {
2424
+ const payload = {
2425
+ ownerAddress: args.ownerAddress,
2426
+ publisherAddress: args.publisherAddress,
2427
+ peerId: args.peerId,
2428
+ registrationId: args.registrationId,
2429
+ profile: args.profile,
2430
+ version: args.version,
2431
+ instanceItemHash: args.instanceItemHash,
2432
+ issuedAt: args.issuedAt ?? Date.now(),
2433
+ expiresAt: args.expiresAt
2434
+ };
2435
+ const serialized = serializeOwnerAuthorizationPayload(payload);
2436
+ const signature = await args.signer(args.ownerAddress, serialized);
2437
+ return {
2438
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2439
+ payload,
2440
+ signature: normalizeSignature(signature)
2441
+ };
2442
+ }
2443
+ async function signRelayBootstrapProof(args) {
2444
+ const payload = {
2445
+ peerId: args.peerId,
2446
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2447
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2448
+ browserDialableOnly: true
2449
+ }) : void 0,
2450
+ registrationId: args.registrationId,
2451
+ profile: args.profile,
2452
+ version: args.version,
2453
+ updatedAt: args.updatedAt ?? Date.now()
2454
+ };
2455
+ const serialized = serializeRelayProofPayload(payload);
2456
+ const signature = await args.signer(args.publisherAddress, serialized);
2457
+ return {
2458
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2459
+ payload,
2460
+ signature: normalizeSignature(signature)
2461
+ };
2462
+ }
2274
2463
  async function fetchAlephBootstrapPosts(options = {}) {
2275
2464
  const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
2276
2465
  if (typeof fetchImpl !== "function") {
@@ -2389,6 +2578,109 @@ async function waitForRelayBootstrapRegistration(args) {
2389
2578
  }
2390
2579
  return null;
2391
2580
  }
2581
+ async function publishRelayBootstrapRegistration(args) {
2582
+ const publisherAddress = args.publisherAddress ?? args.sender;
2583
+ const ownerAddress = args.ownerAddress;
2584
+ let ownerAuthorization = args.ownerAuthorization;
2585
+ let relayProof = args.relayProof;
2586
+ if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
2587
+ ownerAuthorization = await signRelayBootstrapAuthorization({
2588
+ ownerAddress,
2589
+ publisherAddress,
2590
+ peerId: args.peerId,
2591
+ registrationId: args.registrationId,
2592
+ profile: args.profile,
2593
+ version: args.version,
2594
+ instanceItemHash: args.instanceItemHash,
2595
+ issuedAt: args.authorizationIssuedAt ?? args.now ?? Date.now(),
2596
+ expiresAt: args.authorizationExpiresAt,
2597
+ signer: args.ownerSigner
2598
+ });
2599
+ }
2600
+ if (!relayProof && args.publisherSigner) {
2601
+ relayProof = await signRelayBootstrapProof({
2602
+ publisherAddress,
2603
+ peerId: args.peerId,
2604
+ multiaddrs: args.multiaddrs,
2605
+ browserMultiaddrs: args.browserMultiaddrs,
2606
+ registrationId: args.registrationId,
2607
+ profile: args.profile,
2608
+ version: args.version,
2609
+ updatedAt: args.now ?? Date.now(),
2610
+ signer: args.publisherSigner
2611
+ });
2612
+ }
2613
+ const unsigned = await createRelayBootstrapPost({
2614
+ sender: args.sender,
2615
+ peerId: args.peerId,
2616
+ multiaddrs: args.multiaddrs,
2617
+ browserMultiaddrs: args.browserMultiaddrs,
2618
+ registrationId: args.registrationId,
2619
+ channel: args.channel,
2620
+ ref: args.ref,
2621
+ postType: args.postType,
2622
+ profile: args.profile,
2623
+ version: args.version,
2624
+ ownerAddress,
2625
+ publisherAddress,
2626
+ authorization: ownerAuthorization,
2627
+ relayProof,
2628
+ now: args.now,
2629
+ hasher: args.hasher
2630
+ });
2631
+ const content = JSON.parse(unsigned.item_content);
2632
+ if ((content.content?.multiaddrs?.length ?? 0) === 0) {
2633
+ return {
2634
+ status: "skipped",
2635
+ reason: "No public relay multiaddrs remained after filtering.",
2636
+ publishedMultiaddrs: [],
2637
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2638
+ };
2639
+ }
2640
+ const message = await signAlephMessage(unsigned, args.signer);
2641
+ const { response, httpStatus } = await broadcastAlephMessage2(message, {
2642
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2643
+ sync: args.sync ?? true,
2644
+ fetch: args.fetch
2645
+ });
2646
+ let forgottenHashes = [];
2647
+ let forgetResult;
2648
+ if (args.forgetPrevious && args.registrationId) {
2649
+ const previousPosts = await fetchAlephBootstrapPosts({
2650
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2651
+ channel: args.channel,
2652
+ ref: args.ref,
2653
+ postType: args.postType,
2654
+ fetch: args.fetch
2655
+ });
2656
+ forgottenHashes = previousPosts.filter(
2657
+ (post) => post.address?.toLowerCase() === args.sender.toLowerCase() && post.content?.registrationId === args.registrationId && (post.itemHash ?? post.hash) != null && (post.itemHash ?? post.hash) !== unsigned.item_hash
2658
+ ).map((post) => post.itemHash ?? post.hash ?? "").filter(Boolean);
2659
+ if (forgottenHashes.length > 0) {
2660
+ forgetResult = await forgetAlephMessages({
2661
+ sender: args.sender,
2662
+ hashes: forgottenHashes,
2663
+ reason: args.forgetPreviousReason ?? `Replace older relay bootstrap records for ${args.registrationId}`,
2664
+ signer: args.signer,
2665
+ hasher: args.hasher,
2666
+ fetch: args.fetch,
2667
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2668
+ sync: args.sync ?? true
2669
+ });
2670
+ }
2671
+ }
2672
+ return {
2673
+ status: "published",
2674
+ itemHash: unsigned.item_hash,
2675
+ message,
2676
+ response,
2677
+ httpStatus,
2678
+ publishedMultiaddrs: content.content?.multiaddrs ?? [],
2679
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? [],
2680
+ forgottenHashes,
2681
+ forgetResult
2682
+ };
2683
+ }
2392
2684
 
2393
2685
  // ../core/src/bootstrap-config.ts
2394
2686
  var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
@@ -3216,9 +3508,61 @@ var SponsorRelayController = class {
3216
3508
  visibleRegistration
3217
3509
  });
3218
3510
  if (!visibleRegistration) {
3219
- throw new Error(
3220
- "Relay bootstrap registration did not become visible on Aleph."
3221
- );
3511
+ this.trace("deploy:bootstrap-registration-fallback-start", {
3512
+ itemHash: args.itemHash,
3513
+ registrationId,
3514
+ peerId,
3515
+ probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
3516
+ browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
3517
+ });
3518
+ this.emitProgress({
3519
+ stage: "publishing-bootstrap",
3520
+ label: "Publishing bootstrap fallback",
3521
+ progress: 98,
3522
+ status: "warning",
3523
+ itemHash: args.itemHash,
3524
+ detail: "Guest bootstrap registration was delayed. Publishing the relay bootstrap record from the browser."
3525
+ });
3526
+ const fallbackPublication = await publishRelayBootstrapRegistration({
3527
+ sender: this.state.wallet.address,
3528
+ signer: personalSign,
3529
+ hasher: sha256Hex,
3530
+ fetch: asJsonFetch,
3531
+ apiHost: this.client.apiHost,
3532
+ peerId,
3533
+ multiaddrs: relayMetadata.probeMultiaddrs,
3534
+ browserMultiaddrs: relayMetadata.browserBootstrapMultiaddrs,
3535
+ registrationId,
3536
+ profile,
3537
+ sync: true,
3538
+ forgetPrevious: true
3539
+ });
3540
+ this.trace("deploy:bootstrap-registration-fallback-published", {
3541
+ itemHash: args.itemHash,
3542
+ registrationId,
3543
+ peerId,
3544
+ fallbackPublication
3545
+ });
3546
+ const fallbackVisibleRegistration = await waitForRelayBootstrapRegistration({
3547
+ sender: this.state.wallet.address,
3548
+ registrationId,
3549
+ peerId,
3550
+ fetch: asJsonFetch,
3551
+ apiHost: this.client.apiHost,
3552
+ attempts: 24,
3553
+ delayMs: 2500
3554
+ });
3555
+ this.trace("deploy:bootstrap-registration-fallback-visible", {
3556
+ itemHash: args.itemHash,
3557
+ registrationId,
3558
+ peerId,
3559
+ fallbackVisibleRegistration
3560
+ });
3561
+ if (!fallbackVisibleRegistration) {
3562
+ throw new Error(
3563
+ "Relay bootstrap registration did not become visible on Aleph."
3564
+ );
3565
+ }
3222
3566
  }
3223
3567
  if (profile === "uc-go-peer" && args.deploymentToken) {
3224
3568
  await deleteVmBootstrapConfig({
package/shared/index.d.ts CHANGED
@@ -304,7 +304,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
304
304
  declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
305
305
  declare const DEPLOYMENT_PENDING_WARNING_MS: number;
306
306
 
307
- declare const UI_PACKAGE_VERSION = "0.3.22";
307
+ declare const UI_PACKAGE_VERSION = "0.3.24";
308
308
 
309
309
  interface RootfsRequiredPortForward {
310
310
  port: number;
package/shared/index.js CHANGED
@@ -38,7 +38,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
38
38
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
39
39
 
40
40
  // src/shared/package-version.ts
41
- var UI_PACKAGE_VERSION = "0.3.22";
41
+ var UI_PACKAGE_VERSION = "0.3.24";
42
42
 
43
43
  // src/shared/format.ts
44
44
  function shortHash(value, head = 8, tail = 6) {
@@ -2151,6 +2151,31 @@ var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2151
2151
  var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2152
2152
  var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2153
2153
  var DEFAULT_BOOTSTRAP_PAGINATION = 50;
2154
+ var RELAY_BOOTSTRAP_SIGNATURE_SCHEME = "personal_sign";
2155
+ function serializeOwnerAuthorizationPayload(payload) {
2156
+ return JSON.stringify({
2157
+ ownerAddress: payload.ownerAddress,
2158
+ publisherAddress: payload.publisherAddress,
2159
+ peerId: payload.peerId,
2160
+ registrationId: payload.registrationId,
2161
+ profile: payload.profile,
2162
+ version: payload.version,
2163
+ instanceItemHash: payload.instanceItemHash,
2164
+ issuedAt: payload.issuedAt,
2165
+ expiresAt: payload.expiresAt
2166
+ });
2167
+ }
2168
+ function serializeRelayProofPayload(payload) {
2169
+ return JSON.stringify({
2170
+ peerId: payload.peerId,
2171
+ multiaddrs: dedupeMultiaddrs(payload.multiaddrs),
2172
+ browserMultiaddrs: payload.browserMultiaddrs ? dedupeMultiaddrs(payload.browserMultiaddrs) : void 0,
2173
+ registrationId: payload.registrationId,
2174
+ profile: payload.profile,
2175
+ version: payload.version,
2176
+ updatedAt: payload.updatedAt
2177
+ });
2178
+ }
2154
2179
  function asTrimmedString(value) {
2155
2180
  return typeof value === "string" && value.trim() ? value.trim() : null;
2156
2181
  }
@@ -2162,9 +2187,48 @@ function asNumber4(value) {
2162
2187
  }
2163
2188
  return null;
2164
2189
  }
2190
+ function normalizeHost(host) {
2191
+ return host.trim().toLowerCase();
2192
+ }
2165
2193
  function normalizeAddress(address) {
2166
2194
  return address.trim().toLowerCase();
2167
2195
  }
2196
+ function normalizeSignature(signature) {
2197
+ const trimmed = signature.trim();
2198
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
2199
+ }
2200
+ function splitMultiaddr(addr) {
2201
+ return addr.split("/").filter(Boolean);
2202
+ }
2203
+ function isPrivateIpv4(ip) {
2204
+ const parts = ip.split(".").map((part) => Number(part));
2205
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
2206
+ return false;
2207
+ }
2208
+ if (parts[0] === 10) return true;
2209
+ if (parts[0] === 127) return true;
2210
+ if (parts[0] === 0) return true;
2211
+ if (parts[0] === 169 && parts[1] === 254) return true;
2212
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
2213
+ if (parts[0] === 192 && parts[1] === 168) return true;
2214
+ if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) return true;
2215
+ return false;
2216
+ }
2217
+ function isPrivateIpv6(ip) {
2218
+ const normalized = ip.trim().toLowerCase();
2219
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
2220
+ }
2221
+ function isLocalHostname(host) {
2222
+ const normalized = normalizeHost(host);
2223
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local");
2224
+ }
2225
+ function hasPeerId(addr) {
2226
+ return splitMultiaddr(addr).includes("p2p");
2227
+ }
2228
+ function isBrowserDialableMultiaddr(addr) {
2229
+ const normalized = addr.toLowerCase();
2230
+ return normalized.includes("/ws") || normalized.includes("/wss") || normalized.includes("/webtransport") || normalized.includes("/webrtc-direct");
2231
+ }
2168
2232
  function dedupeMultiaddrs(addrs) {
2169
2233
  const seen = /* @__PURE__ */ new Set();
2170
2234
  const result = [];
@@ -2176,6 +2240,33 @@ function dedupeMultiaddrs(addrs) {
2176
2240
  }
2177
2241
  return result;
2178
2242
  }
2243
+ function isPublicMultiaddr(addr) {
2244
+ const parts = splitMultiaddr(addr);
2245
+ for (let index = 0; index < parts.length; index += 1) {
2246
+ const protocol = parts[index];
2247
+ const value = parts[index + 1];
2248
+ if (protocol === "ip4" && value) {
2249
+ return !isPrivateIpv4(value);
2250
+ }
2251
+ if (protocol === "ip6" && value) {
2252
+ return !isPrivateIpv6(value);
2253
+ }
2254
+ if ((protocol === "dns4" || protocol === "dns6" || protocol === "dnsaddr") && value) {
2255
+ return !isLocalHostname(value);
2256
+ }
2257
+ }
2258
+ return false;
2259
+ }
2260
+ function filterPublicMultiaddrs(addrs, options = {}) {
2261
+ return dedupeMultiaddrs(addrs).filter((addr) => {
2262
+ if (!isPublicMultiaddr(addr)) return false;
2263
+ if (options.requirePeerId !== false && !hasPeerId(addr)) return false;
2264
+ if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr)) {
2265
+ return false;
2266
+ }
2267
+ return true;
2268
+ });
2269
+ }
2179
2270
  function normalizeRelayBootstrapContent(value) {
2180
2271
  if (!value || typeof value !== "object") return null;
2181
2272
  const content = value;
@@ -2270,6 +2361,104 @@ function normalizeRelayBootstrapPostRecord(value) {
2270
2361
  content: normalizeRelayBootstrapContent(entry.content)
2271
2362
  };
2272
2363
  }
2364
+ function buildRelayBootstrapPostContent(args) {
2365
+ const now = args.now ?? Date.now() / 1e3;
2366
+ const updatedAt = args.now ?? Date.now();
2367
+ return {
2368
+ type: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2369
+ address: args.sender,
2370
+ ...args.ref ? { ref: args.ref } : {},
2371
+ content: {
2372
+ peerId: args.peerId,
2373
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2374
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2375
+ browserDialableOnly: true
2376
+ }) : void 0,
2377
+ registrationId: args.registrationId,
2378
+ profile: args.profile,
2379
+ version: args.version,
2380
+ ownerAddress: args.ownerAddress,
2381
+ publisherAddress: args.publisherAddress,
2382
+ authorization: args.authorization,
2383
+ relayProof: args.relayProof,
2384
+ updatedAt: Math.round(updatedAt)
2385
+ },
2386
+ time: now
2387
+ };
2388
+ }
2389
+ async function createRelayBootstrapPost(args) {
2390
+ const nowMillis = args.now ?? Date.now();
2391
+ const nowSeconds = nowMillis / 1e3;
2392
+ const itemContent = buildRelayBootstrapPostContent({
2393
+ sender: args.sender,
2394
+ peerId: args.peerId,
2395
+ multiaddrs: args.multiaddrs,
2396
+ browserMultiaddrs: args.browserMultiaddrs,
2397
+ registrationId: args.registrationId,
2398
+ ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2399
+ postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2400
+ profile: args.profile,
2401
+ version: args.version,
2402
+ ownerAddress: args.ownerAddress,
2403
+ publisherAddress: args.publisherAddress,
2404
+ authorization: args.authorization,
2405
+ relayProof: args.relayProof,
2406
+ now: nowMillis
2407
+ });
2408
+ itemContent.time = nowSeconds;
2409
+ const serialized = JSON.stringify(itemContent);
2410
+ const itemHash = await args.hasher(serialized);
2411
+ return {
2412
+ channel: args.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL,
2413
+ sender: args.sender,
2414
+ chain: "ETH",
2415
+ type: "POST",
2416
+ time: nowSeconds,
2417
+ item_type: "inline",
2418
+ item_content: serialized,
2419
+ item_hash: itemHash
2420
+ };
2421
+ }
2422
+ async function signRelayBootstrapAuthorization(args) {
2423
+ const payload = {
2424
+ ownerAddress: args.ownerAddress,
2425
+ publisherAddress: args.publisherAddress,
2426
+ peerId: args.peerId,
2427
+ registrationId: args.registrationId,
2428
+ profile: args.profile,
2429
+ version: args.version,
2430
+ instanceItemHash: args.instanceItemHash,
2431
+ issuedAt: args.issuedAt ?? Date.now(),
2432
+ expiresAt: args.expiresAt
2433
+ };
2434
+ const serialized = serializeOwnerAuthorizationPayload(payload);
2435
+ const signature = await args.signer(args.ownerAddress, serialized);
2436
+ return {
2437
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2438
+ payload,
2439
+ signature: normalizeSignature(signature)
2440
+ };
2441
+ }
2442
+ async function signRelayBootstrapProof(args) {
2443
+ const payload = {
2444
+ peerId: args.peerId,
2445
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2446
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2447
+ browserDialableOnly: true
2448
+ }) : void 0,
2449
+ registrationId: args.registrationId,
2450
+ profile: args.profile,
2451
+ version: args.version,
2452
+ updatedAt: args.updatedAt ?? Date.now()
2453
+ };
2454
+ const serialized = serializeRelayProofPayload(payload);
2455
+ const signature = await args.signer(args.publisherAddress, serialized);
2456
+ return {
2457
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2458
+ payload,
2459
+ signature: normalizeSignature(signature)
2460
+ };
2461
+ }
2273
2462
  async function fetchAlephBootstrapPosts(options = {}) {
2274
2463
  const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
2275
2464
  if (typeof fetchImpl !== "function") {
@@ -2388,6 +2577,109 @@ async function waitForRelayBootstrapRegistration(args) {
2388
2577
  }
2389
2578
  return null;
2390
2579
  }
2580
+ async function publishRelayBootstrapRegistration(args) {
2581
+ const publisherAddress = args.publisherAddress ?? args.sender;
2582
+ const ownerAddress = args.ownerAddress;
2583
+ let ownerAuthorization = args.ownerAuthorization;
2584
+ let relayProof = args.relayProof;
2585
+ if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
2586
+ ownerAuthorization = await signRelayBootstrapAuthorization({
2587
+ ownerAddress,
2588
+ publisherAddress,
2589
+ peerId: args.peerId,
2590
+ registrationId: args.registrationId,
2591
+ profile: args.profile,
2592
+ version: args.version,
2593
+ instanceItemHash: args.instanceItemHash,
2594
+ issuedAt: args.authorizationIssuedAt ?? args.now ?? Date.now(),
2595
+ expiresAt: args.authorizationExpiresAt,
2596
+ signer: args.ownerSigner
2597
+ });
2598
+ }
2599
+ if (!relayProof && args.publisherSigner) {
2600
+ relayProof = await signRelayBootstrapProof({
2601
+ publisherAddress,
2602
+ peerId: args.peerId,
2603
+ multiaddrs: args.multiaddrs,
2604
+ browserMultiaddrs: args.browserMultiaddrs,
2605
+ registrationId: args.registrationId,
2606
+ profile: args.profile,
2607
+ version: args.version,
2608
+ updatedAt: args.now ?? Date.now(),
2609
+ signer: args.publisherSigner
2610
+ });
2611
+ }
2612
+ const unsigned = await createRelayBootstrapPost({
2613
+ sender: args.sender,
2614
+ peerId: args.peerId,
2615
+ multiaddrs: args.multiaddrs,
2616
+ browserMultiaddrs: args.browserMultiaddrs,
2617
+ registrationId: args.registrationId,
2618
+ channel: args.channel,
2619
+ ref: args.ref,
2620
+ postType: args.postType,
2621
+ profile: args.profile,
2622
+ version: args.version,
2623
+ ownerAddress,
2624
+ publisherAddress,
2625
+ authorization: ownerAuthorization,
2626
+ relayProof,
2627
+ now: args.now,
2628
+ hasher: args.hasher
2629
+ });
2630
+ const content = JSON.parse(unsigned.item_content);
2631
+ if ((content.content?.multiaddrs?.length ?? 0) === 0) {
2632
+ return {
2633
+ status: "skipped",
2634
+ reason: "No public relay multiaddrs remained after filtering.",
2635
+ publishedMultiaddrs: [],
2636
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2637
+ };
2638
+ }
2639
+ const message = await signAlephMessage(unsigned, args.signer);
2640
+ const { response, httpStatus } = await broadcastAlephMessage2(message, {
2641
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2642
+ sync: args.sync ?? true,
2643
+ fetch: args.fetch
2644
+ });
2645
+ let forgottenHashes = [];
2646
+ let forgetResult;
2647
+ if (args.forgetPrevious && args.registrationId) {
2648
+ const previousPosts = await fetchAlephBootstrapPosts({
2649
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2650
+ channel: args.channel,
2651
+ ref: args.ref,
2652
+ postType: args.postType,
2653
+ fetch: args.fetch
2654
+ });
2655
+ forgottenHashes = previousPosts.filter(
2656
+ (post) => post.address?.toLowerCase() === args.sender.toLowerCase() && post.content?.registrationId === args.registrationId && (post.itemHash ?? post.hash) != null && (post.itemHash ?? post.hash) !== unsigned.item_hash
2657
+ ).map((post) => post.itemHash ?? post.hash ?? "").filter(Boolean);
2658
+ if (forgottenHashes.length > 0) {
2659
+ forgetResult = await forgetAlephMessages({
2660
+ sender: args.sender,
2661
+ hashes: forgottenHashes,
2662
+ reason: args.forgetPreviousReason ?? `Replace older relay bootstrap records for ${args.registrationId}`,
2663
+ signer: args.signer,
2664
+ hasher: args.hasher,
2665
+ fetch: args.fetch,
2666
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2667
+ sync: args.sync ?? true
2668
+ });
2669
+ }
2670
+ }
2671
+ return {
2672
+ status: "published",
2673
+ itemHash: unsigned.item_hash,
2674
+ message,
2675
+ response,
2676
+ httpStatus,
2677
+ publishedMultiaddrs: content.content?.multiaddrs ?? [],
2678
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? [],
2679
+ forgottenHashes,
2680
+ forgetResult
2681
+ };
2682
+ }
2391
2683
 
2392
2684
  // ../core/src/bootstrap-config.ts
2393
2685
  var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
@@ -3215,9 +3507,61 @@ var SponsorRelayController = class {
3215
3507
  visibleRegistration
3216
3508
  });
3217
3509
  if (!visibleRegistration) {
3218
- throw new Error(
3219
- "Relay bootstrap registration did not become visible on Aleph."
3220
- );
3510
+ this.trace("deploy:bootstrap-registration-fallback-start", {
3511
+ itemHash: args.itemHash,
3512
+ registrationId,
3513
+ peerId,
3514
+ probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
3515
+ browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
3516
+ });
3517
+ this.emitProgress({
3518
+ stage: "publishing-bootstrap",
3519
+ label: "Publishing bootstrap fallback",
3520
+ progress: 98,
3521
+ status: "warning",
3522
+ itemHash: args.itemHash,
3523
+ detail: "Guest bootstrap registration was delayed. Publishing the relay bootstrap record from the browser."
3524
+ });
3525
+ const fallbackPublication = await publishRelayBootstrapRegistration({
3526
+ sender: this.state.wallet.address,
3527
+ signer: personalSign,
3528
+ hasher: sha256Hex,
3529
+ fetch: asJsonFetch,
3530
+ apiHost: this.client.apiHost,
3531
+ peerId,
3532
+ multiaddrs: relayMetadata.probeMultiaddrs,
3533
+ browserMultiaddrs: relayMetadata.browserBootstrapMultiaddrs,
3534
+ registrationId,
3535
+ profile,
3536
+ sync: true,
3537
+ forgetPrevious: true
3538
+ });
3539
+ this.trace("deploy:bootstrap-registration-fallback-published", {
3540
+ itemHash: args.itemHash,
3541
+ registrationId,
3542
+ peerId,
3543
+ fallbackPublication
3544
+ });
3545
+ const fallbackVisibleRegistration = await waitForRelayBootstrapRegistration({
3546
+ sender: this.state.wallet.address,
3547
+ registrationId,
3548
+ peerId,
3549
+ fetch: asJsonFetch,
3550
+ apiHost: this.client.apiHost,
3551
+ attempts: 24,
3552
+ delayMs: 2500
3553
+ });
3554
+ this.trace("deploy:bootstrap-registration-fallback-visible", {
3555
+ itemHash: args.itemHash,
3556
+ registrationId,
3557
+ peerId,
3558
+ fallbackVisibleRegistration
3559
+ });
3560
+ if (!fallbackVisibleRegistration) {
3561
+ throw new Error(
3562
+ "Relay bootstrap registration did not become visible on Aleph."
3563
+ );
3564
+ }
3221
3565
  }
3222
3566
  if (profile === "uc-go-peer" && args.deploymentToken) {
3223
3567
  await deleteVmBootstrapConfig({