@le-space/ui 0.1.55 → 0.1.57

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.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Floating Aleph sponsor relay UI components for Svelte and React.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,9 +36,9 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@noble/hashes": "^1.8.0",
40
- "@le-space/browser": "0.1.52",
41
- "@le-space/core": "0.1.52"
39
+ "@le-space/browser": "0.1.57",
40
+ "@le-space/core": "0.1.57",
41
+ "viem": "^2.38.5"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": ">=18",
package/react/index.d.ts CHANGED
@@ -174,12 +174,12 @@ interface RootfsResolution {
174
174
  gatewayError?: string | null;
175
175
  }
176
176
 
177
- type DeploymentProgressStage = 'idle' | 'validating' | 'loading-manifest' | 'verifying-rootfs' | 'loading-pricing' | 'loading-balance' | 'selecting-crn' | 'building-message' | 'signing-message' | 'broadcasting' | 'waiting-for-aleph' | 'deployment-confirmed' | 'deployment-rejected' | 'building-delete-message' | 'signing-delete-message' | 'broadcasting-delete' | 'delete-completed' | 'refreshing-instances' | 'completed' | 'error';
177
+ type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
178
178
  interface DeploymentProgressEvent {
179
179
  stage: DeploymentProgressStage;
180
180
  label: string;
181
181
  progress: number;
182
- status: 'info' | 'success' | 'warning' | 'error';
182
+ status: "info" | "success" | "warning" | "error";
183
183
  itemHash?: string | null;
184
184
  detail?: string | null;
185
185
  error?: string | null;
package/react/index.js CHANGED
@@ -48,7 +48,8 @@ function formatNumber(value, digits = 0) {
48
48
  }
49
49
  function formatDateTime(value) {
50
50
  if (!value) return "-";
51
- const date = typeof value === "number" ? new Date(value) : new Date(value);
51
+ const normalizedValue = typeof value === "number" && value > 0 && value < 1e10 ? value * 1e3 : value;
52
+ const date = typeof normalizedValue === "number" ? new Date(normalizedValue) : new Date(normalizedValue);
52
53
  if (Number.isNaN(date.getTime())) return String(value);
53
54
  return date.toLocaleString();
54
55
  }
@@ -183,7 +184,7 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
183
184
  const payload = await response.json();
184
185
  return (payload.messages ?? []).map((message) => ({
185
186
  ...message,
186
- status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : message.status
187
+ status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : "pending"
187
188
  }));
188
189
  }
189
190
  async function fetch2n6WebAccessUrl(itemHash, twoN6ApiHost = DEFAULT_2N6_API_HOST) {
@@ -709,21 +710,16 @@ function resolveManifestSource(args) {
709
710
  }
710
711
 
711
712
  // src/shared/wallet-controller.ts
712
- import { keccak_256 } from "@noble/hashes/sha3";
713
+ import { getAddress } from "viem";
713
714
  function getEthereumProvider() {
714
715
  return globalThis.window?.ethereum ?? null;
715
716
  }
716
717
  function toChecksumAddress(address) {
717
- if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
718
+ try {
719
+ return getAddress(address);
720
+ } catch {
718
721
  throw new Error("Invalid EVM address.");
719
722
  }
720
- const normalized = address.slice(2).toLowerCase();
721
- const hash = Array.from(keccak_256(new TextEncoder().encode(normalized))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
722
- let result = "0x";
723
- for (let index = 0; index < normalized.length; index += 1) {
724
- result += Number.parseInt(hash[index], 16) >= 8 ? normalized[index].toUpperCase() : normalized[index];
725
- }
726
- return result;
727
723
  }
728
724
  async function connectWallet(provider = getEthereumProvider()) {
729
725
  if (!provider) {
@@ -1228,6 +1224,88 @@ async function deployInstance(args) {
1228
1224
  };
1229
1225
  }
1230
1226
 
1227
+ // ../core/src/guest.ts
1228
+ async function notifyCrnAllocation2(args) {
1229
+ const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1230
+ if (!normalizedCrnUrl) {
1231
+ return {
1232
+ status: "skipped",
1233
+ reason: "No CRN URL available for allocation notification."
1234
+ };
1235
+ }
1236
+ try {
1237
+ const response = await args.fetch(
1238
+ `${normalizedCrnUrl}/control/allocation/notify`,
1239
+ {
1240
+ method: "POST",
1241
+ headers: {
1242
+ "content-type": "application/json"
1243
+ },
1244
+ body: JSON.stringify({ instance: args.itemHash })
1245
+ }
1246
+ );
1247
+ const payload = await response.json().catch(() => null);
1248
+ const responseText = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
1249
+ if (!response.ok) {
1250
+ return {
1251
+ status: "unconfirmed",
1252
+ reason: `CRN allocation notify returned ${response.status}${responseText ? ` ${responseText}` : ""}.`,
1253
+ payload
1254
+ };
1255
+ }
1256
+ return {
1257
+ status: "confirmed",
1258
+ payload
1259
+ };
1260
+ } catch (error) {
1261
+ return {
1262
+ status: "unconfirmed",
1263
+ reason: error instanceof Error ? error.message : String(error)
1264
+ };
1265
+ }
1266
+ }
1267
+ function isRetryableAllocationNotifyReason(reason) {
1268
+ const normalized = String(reason ?? "").toLowerCase();
1269
+ return normalized.includes("503") && (normalized.includes("node hash not yet discovered") || normalized.includes("cannot accept targeted allocations"));
1270
+ }
1271
+ async function notifyCrnAllocationWithRetry(args) {
1272
+ const attempts = Math.max(1, Number(args.attempts ?? 6));
1273
+ const delayMs = Math.max(0, Number(args.delayMs ?? 2e3));
1274
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1275
+ let lastResult = null;
1276
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1277
+ args.onProgress?.({
1278
+ stage: "notifying-crn",
1279
+ label: "Notifying selected CRN",
1280
+ progress: 84,
1281
+ status: "info",
1282
+ itemHash: args.itemHash,
1283
+ detail: `${attempt + 1}/${attempts} ${String(args.crnUrl ?? "").trim() || "selected CRN"}`,
1284
+ error: null,
1285
+ timestamp: Date.now()
1286
+ });
1287
+ const result = await notifyCrnAllocation2({
1288
+ crnUrl: args.crnUrl,
1289
+ itemHash: args.itemHash,
1290
+ fetch: args.fetch
1291
+ });
1292
+ if (result.status === "confirmed" || result.status === "skipped") {
1293
+ return result;
1294
+ }
1295
+ lastResult = result;
1296
+ if (!isRetryableAllocationNotifyReason(result.reason)) {
1297
+ return result;
1298
+ }
1299
+ if (attempt < attempts - 1) {
1300
+ await sleep(delayMs);
1301
+ }
1302
+ }
1303
+ return lastResult ?? {
1304
+ status: "unconfirmed",
1305
+ reason: "CRN allocation notify was not confirmed."
1306
+ };
1307
+ }
1308
+
1231
1309
  // src/shared/events.ts
1232
1310
  function createDeploymentProgressEmitter() {
1233
1311
  const listeners = /* @__PURE__ */ new Set();
@@ -1301,14 +1379,19 @@ function defaultState(props = {}) {
1301
1379
  };
1302
1380
  }
1303
1381
  async function sha256Hex(payload) {
1304
- const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
1382
+ const digest = await globalThis.crypto.subtle.digest(
1383
+ "SHA-256",
1384
+ new TextEncoder().encode(payload)
1385
+ );
1305
1386
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1306
1387
  }
1307
1388
  function mappedPorts(execution) {
1308
- return Object.entries(execution?.networking?.mapped_ports ?? {}).map(([port, mapping]) => ({
1309
- label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1310
- hostPort: mapping.host ?? null
1311
- }));
1389
+ return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
1390
+ ([port, mapping]) => ({
1391
+ label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1392
+ hostPort: mapping.host ?? null
1393
+ })
1394
+ );
1312
1395
  }
1313
1396
  function rootfsHealth(args) {
1314
1397
  if (!args.manifestState.valid || !args.manifestState.manifest) {
@@ -1366,7 +1449,9 @@ async function resolveManifest(args) {
1366
1449
  }
1367
1450
  async function inspectInstanceRuntime(args) {
1368
1451
  const details = {
1369
- messageStatus: String(args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")).toLowerCase(),
1452
+ messageStatus: String(
1453
+ args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")
1454
+ ).toLowerCase(),
1370
1455
  allocationSource: null,
1371
1456
  crnUrl: null,
1372
1457
  hostIpv4: null,
@@ -1397,11 +1482,15 @@ async function inspectInstanceRuntime(args) {
1397
1482
  details.allocationSource = allocation?.source ?? null;
1398
1483
  details.crnUrl = allocation?.crnUrl ?? null;
1399
1484
  details.ipv6 = allocation?.vmIpv6 ?? null;
1400
- details.webUrl = await args.client.fetch2n6WebAccessUrl(args.instance.item_hash);
1485
+ details.webUrl = await args.client.fetch2n6WebAccessUrl(
1486
+ args.instance.item_hash
1487
+ );
1401
1488
  if (!allocation?.crnUrl) {
1402
1489
  return details;
1403
1490
  }
1404
- const executionLookup = await args.client.fetchCrnExecutionMap(allocation.crnUrl);
1491
+ const executionLookup = await args.client.fetchCrnExecutionMap(
1492
+ allocation.crnUrl
1493
+ );
1405
1494
  const executionPayload = executionLookup.payload?.[args.instance.item_hash];
1406
1495
  if (!executionPayload) {
1407
1496
  return details;
@@ -1429,7 +1518,10 @@ function compatibleCrnsForTier(crns, state) {
1429
1518
  if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
1430
1519
  return [];
1431
1520
  }
1432
- const spec = tierSpec(state.pricingSummary.pricing, state.pricingSummary.tier);
1521
+ const spec = tierSpec(
1522
+ state.pricingSummary.pricing,
1523
+ state.pricingSummary.tier
1524
+ );
1433
1525
  return filterDeployableCrns(crns, { spec });
1434
1526
  }
1435
1527
  async function pingPeer(libp2p) {
@@ -1719,8 +1811,14 @@ var SponsorRelayController = class {
1719
1811
  status: "info",
1720
1812
  detail: manifest.rootfsItemHash
1721
1813
  });
1722
- rootfsVerified = await verifyRootfsExists(manifest.rootfsItemHash, this.client.apiHost);
1723
- rootfsResolution = await resolveRootfsReference(manifest.rootfsItemHash, this.client.apiHost);
1814
+ rootfsVerified = await verifyRootfsExists(
1815
+ manifest.rootfsItemHash,
1816
+ this.client.apiHost
1817
+ );
1818
+ rootfsResolution = await resolveRootfsReference(
1819
+ manifest.rootfsItemHash,
1820
+ this.client.apiHost
1821
+ );
1724
1822
  }
1725
1823
  this.patch({
1726
1824
  manifestState,
@@ -1775,7 +1873,9 @@ var SponsorRelayController = class {
1775
1873
  return;
1776
1874
  }
1777
1875
  if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
1778
- this.patch({ errorText: "Manifest, rootfs, and pricing must be ready before deploying." });
1876
+ this.patch({
1877
+ errorText: "Manifest, rootfs, and pricing must be ready before deploying."
1878
+ });
1779
1879
  return;
1780
1880
  }
1781
1881
  this.patch({
@@ -1791,7 +1891,10 @@ var SponsorRelayController = class {
1791
1891
  status: "info",
1792
1892
  detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
1793
1893
  });
1794
- const spec = tierSpec(this.state.pricingSummary.pricing, this.state.pricingSummary.tier);
1894
+ const spec = tierSpec(
1895
+ this.state.pricingSummary.pricing,
1896
+ this.state.pricingSummary.tier
1897
+ );
1795
1898
  this.emitProgress({
1796
1899
  stage: "selecting-crn",
1797
1900
  label: "Selecting CRN",
@@ -1804,7 +1907,10 @@ var SponsorRelayController = class {
1804
1907
  name: this.state.instanceName.trim(),
1805
1908
  sshPublicKey: this.state.sshPublicKey.trim(),
1806
1909
  rootfsItemHash: this.state.manifest.rootfsItemHash,
1807
- rootfsSizeMiB: Math.max(this.state.manifest.rootfsSizeMiB, spec.diskMiB),
1910
+ rootfsSizeMiB: Math.max(
1911
+ this.state.manifest.rootfsSizeMiB,
1912
+ spec.diskMiB
1913
+ ),
1808
1914
  vcpus: spec.vcpus,
1809
1915
  memoryMiB: spec.memoryMiB,
1810
1916
  rootfsVersion: this.state.manifest.version,
@@ -1827,6 +1933,16 @@ var SponsorRelayController = class {
1827
1933
  statusText: `Deployment submitted: ${result.itemHash}`,
1828
1934
  lastDeploymentHash: result.itemHash
1829
1935
  });
1936
+ if (result.status === "processed" && this.state.selectedCrn?.address) {
1937
+ await notifyCrnAllocationWithRetry({
1938
+ crnUrl: this.state.selectedCrn.address,
1939
+ itemHash: result.itemHash,
1940
+ fetch: (url, init) => fetch(url, init),
1941
+ onProgress: (event) => {
1942
+ this.emitProgress(event);
1943
+ }
1944
+ });
1945
+ }
1830
1946
  this.emitProgress({
1831
1947
  stage: "refreshing-instances",
1832
1948
  label: "Refreshing instances",
package/shared/index.d.ts CHANGED
@@ -172,12 +172,12 @@ interface RootfsResolution {
172
172
  gatewayError?: string | null;
173
173
  }
174
174
 
175
- type DeploymentProgressStage = 'idle' | 'validating' | 'loading-manifest' | 'verifying-rootfs' | 'loading-pricing' | 'loading-balance' | 'selecting-crn' | 'building-message' | 'signing-message' | 'broadcasting' | 'waiting-for-aleph' | 'deployment-confirmed' | 'deployment-rejected' | 'building-delete-message' | 'signing-delete-message' | 'broadcasting-delete' | 'delete-completed' | 'refreshing-instances' | 'completed' | 'error';
175
+ type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
176
176
  interface DeploymentProgressEvent {
177
177
  stage: DeploymentProgressStage;
178
178
  label: string;
179
179
  progress: number;
180
- status: 'info' | 'success' | 'warning' | 'error';
180
+ status: "info" | "success" | "warning" | "error";
181
181
  itemHash?: string | null;
182
182
  detail?: string | null;
183
183
  error?: string | null;
package/shared/index.js CHANGED
@@ -45,7 +45,8 @@ function formatNumber(value, digits = 0) {
45
45
  }
46
46
  function formatDateTime(value) {
47
47
  if (!value) return "-";
48
- const date = typeof value === "number" ? new Date(value) : new Date(value);
48
+ const normalizedValue = typeof value === "number" && value > 0 && value < 1e10 ? value * 1e3 : value;
49
+ const date = typeof normalizedValue === "number" ? new Date(normalizedValue) : new Date(normalizedValue);
49
50
  if (Number.isNaN(date.getTime())) return String(value);
50
51
  return date.toLocaleString();
51
52
  }
@@ -180,7 +181,7 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
180
181
  const payload = await response.json();
181
182
  return (payload.messages ?? []).map((message) => ({
182
183
  ...message,
183
- status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : message.status
184
+ status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : "pending"
184
185
  }));
185
186
  }
186
187
  async function fetch2n6WebAccessUrl(itemHash, twoN6ApiHost = DEFAULT_2N6_API_HOST) {
@@ -706,21 +707,16 @@ function resolveManifestSource(args) {
706
707
  }
707
708
 
708
709
  // src/shared/wallet-controller.ts
709
- import { keccak_256 } from "@noble/hashes/sha3";
710
+ import { getAddress } from "viem";
710
711
  function getEthereumProvider() {
711
712
  return globalThis.window?.ethereum ?? null;
712
713
  }
713
714
  function toChecksumAddress(address) {
714
- if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
715
+ try {
716
+ return getAddress(address);
717
+ } catch {
715
718
  throw new Error("Invalid EVM address.");
716
719
  }
717
- const normalized = address.slice(2).toLowerCase();
718
- const hash = Array.from(keccak_256(new TextEncoder().encode(normalized))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
719
- let result = "0x";
720
- for (let index = 0; index < normalized.length; index += 1) {
721
- result += Number.parseInt(hash[index], 16) >= 8 ? normalized[index].toUpperCase() : normalized[index];
722
- }
723
- return result;
724
720
  }
725
721
  async function connectWallet(provider = getEthereumProvider()) {
726
722
  if (!provider) {
@@ -1225,6 +1221,88 @@ async function deployInstance(args) {
1225
1221
  };
1226
1222
  }
1227
1223
 
1224
+ // ../core/src/guest.ts
1225
+ async function notifyCrnAllocation2(args) {
1226
+ const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1227
+ if (!normalizedCrnUrl) {
1228
+ return {
1229
+ status: "skipped",
1230
+ reason: "No CRN URL available for allocation notification."
1231
+ };
1232
+ }
1233
+ try {
1234
+ const response = await args.fetch(
1235
+ `${normalizedCrnUrl}/control/allocation/notify`,
1236
+ {
1237
+ method: "POST",
1238
+ headers: {
1239
+ "content-type": "application/json"
1240
+ },
1241
+ body: JSON.stringify({ instance: args.itemHash })
1242
+ }
1243
+ );
1244
+ const payload = await response.json().catch(() => null);
1245
+ const responseText = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
1246
+ if (!response.ok) {
1247
+ return {
1248
+ status: "unconfirmed",
1249
+ reason: `CRN allocation notify returned ${response.status}${responseText ? ` ${responseText}` : ""}.`,
1250
+ payload
1251
+ };
1252
+ }
1253
+ return {
1254
+ status: "confirmed",
1255
+ payload
1256
+ };
1257
+ } catch (error) {
1258
+ return {
1259
+ status: "unconfirmed",
1260
+ reason: error instanceof Error ? error.message : String(error)
1261
+ };
1262
+ }
1263
+ }
1264
+ function isRetryableAllocationNotifyReason(reason) {
1265
+ const normalized = String(reason ?? "").toLowerCase();
1266
+ return normalized.includes("503") && (normalized.includes("node hash not yet discovered") || normalized.includes("cannot accept targeted allocations"));
1267
+ }
1268
+ async function notifyCrnAllocationWithRetry(args) {
1269
+ const attempts = Math.max(1, Number(args.attempts ?? 6));
1270
+ const delayMs = Math.max(0, Number(args.delayMs ?? 2e3));
1271
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1272
+ let lastResult = null;
1273
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1274
+ args.onProgress?.({
1275
+ stage: "notifying-crn",
1276
+ label: "Notifying selected CRN",
1277
+ progress: 84,
1278
+ status: "info",
1279
+ itemHash: args.itemHash,
1280
+ detail: `${attempt + 1}/${attempts} ${String(args.crnUrl ?? "").trim() || "selected CRN"}`,
1281
+ error: null,
1282
+ timestamp: Date.now()
1283
+ });
1284
+ const result = await notifyCrnAllocation2({
1285
+ crnUrl: args.crnUrl,
1286
+ itemHash: args.itemHash,
1287
+ fetch: args.fetch
1288
+ });
1289
+ if (result.status === "confirmed" || result.status === "skipped") {
1290
+ return result;
1291
+ }
1292
+ lastResult = result;
1293
+ if (!isRetryableAllocationNotifyReason(result.reason)) {
1294
+ return result;
1295
+ }
1296
+ if (attempt < attempts - 1) {
1297
+ await sleep(delayMs);
1298
+ }
1299
+ }
1300
+ return lastResult ?? {
1301
+ status: "unconfirmed",
1302
+ reason: "CRN allocation notify was not confirmed."
1303
+ };
1304
+ }
1305
+
1228
1306
  // src/shared/events.ts
1229
1307
  function createDeploymentProgressEmitter() {
1230
1308
  const listeners = /* @__PURE__ */ new Set();
@@ -1298,14 +1376,19 @@ function defaultState(props = {}) {
1298
1376
  };
1299
1377
  }
1300
1378
  async function sha256Hex(payload) {
1301
- const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload));
1379
+ const digest = await globalThis.crypto.subtle.digest(
1380
+ "SHA-256",
1381
+ new TextEncoder().encode(payload)
1382
+ );
1302
1383
  return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
1303
1384
  }
1304
1385
  function mappedPorts(execution) {
1305
- return Object.entries(execution?.networking?.mapped_ports ?? {}).map(([port, mapping]) => ({
1306
- label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1307
- hostPort: mapping.host ?? null
1308
- }));
1386
+ return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
1387
+ ([port, mapping]) => ({
1388
+ label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
1389
+ hostPort: mapping.host ?? null
1390
+ })
1391
+ );
1309
1392
  }
1310
1393
  function rootfsHealth(args) {
1311
1394
  if (!args.manifestState.valid || !args.manifestState.manifest) {
@@ -1363,7 +1446,9 @@ async function resolveManifest(args) {
1363
1446
  }
1364
1447
  async function inspectInstanceRuntime(args) {
1365
1448
  const details = {
1366
- messageStatus: String(args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")).toLowerCase(),
1449
+ messageStatus: String(
1450
+ args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")
1451
+ ).toLowerCase(),
1367
1452
  allocationSource: null,
1368
1453
  crnUrl: null,
1369
1454
  hostIpv4: null,
@@ -1394,11 +1479,15 @@ async function inspectInstanceRuntime(args) {
1394
1479
  details.allocationSource = allocation?.source ?? null;
1395
1480
  details.crnUrl = allocation?.crnUrl ?? null;
1396
1481
  details.ipv6 = allocation?.vmIpv6 ?? null;
1397
- details.webUrl = await args.client.fetch2n6WebAccessUrl(args.instance.item_hash);
1482
+ details.webUrl = await args.client.fetch2n6WebAccessUrl(
1483
+ args.instance.item_hash
1484
+ );
1398
1485
  if (!allocation?.crnUrl) {
1399
1486
  return details;
1400
1487
  }
1401
- const executionLookup = await args.client.fetchCrnExecutionMap(allocation.crnUrl);
1488
+ const executionLookup = await args.client.fetchCrnExecutionMap(
1489
+ allocation.crnUrl
1490
+ );
1402
1491
  const executionPayload = executionLookup.payload?.[args.instance.item_hash];
1403
1492
  if (!executionPayload) {
1404
1493
  return details;
@@ -1426,7 +1515,10 @@ function compatibleCrnsForTier(crns, state) {
1426
1515
  if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
1427
1516
  return [];
1428
1517
  }
1429
- const spec = tierSpec(state.pricingSummary.pricing, state.pricingSummary.tier);
1518
+ const spec = tierSpec(
1519
+ state.pricingSummary.pricing,
1520
+ state.pricingSummary.tier
1521
+ );
1430
1522
  return filterDeployableCrns(crns, { spec });
1431
1523
  }
1432
1524
  async function pingPeer(libp2p) {
@@ -1716,8 +1808,14 @@ var SponsorRelayController = class {
1716
1808
  status: "info",
1717
1809
  detail: manifest.rootfsItemHash
1718
1810
  });
1719
- rootfsVerified = await verifyRootfsExists(manifest.rootfsItemHash, this.client.apiHost);
1720
- rootfsResolution = await resolveRootfsReference(manifest.rootfsItemHash, this.client.apiHost);
1811
+ rootfsVerified = await verifyRootfsExists(
1812
+ manifest.rootfsItemHash,
1813
+ this.client.apiHost
1814
+ );
1815
+ rootfsResolution = await resolveRootfsReference(
1816
+ manifest.rootfsItemHash,
1817
+ this.client.apiHost
1818
+ );
1721
1819
  }
1722
1820
  this.patch({
1723
1821
  manifestState,
@@ -1772,7 +1870,9 @@ var SponsorRelayController = class {
1772
1870
  return;
1773
1871
  }
1774
1872
  if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
1775
- this.patch({ errorText: "Manifest, rootfs, and pricing must be ready before deploying." });
1873
+ this.patch({
1874
+ errorText: "Manifest, rootfs, and pricing must be ready before deploying."
1875
+ });
1776
1876
  return;
1777
1877
  }
1778
1878
  this.patch({
@@ -1788,7 +1888,10 @@ var SponsorRelayController = class {
1788
1888
  status: "info",
1789
1889
  detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
1790
1890
  });
1791
- const spec = tierSpec(this.state.pricingSummary.pricing, this.state.pricingSummary.tier);
1891
+ const spec = tierSpec(
1892
+ this.state.pricingSummary.pricing,
1893
+ this.state.pricingSummary.tier
1894
+ );
1792
1895
  this.emitProgress({
1793
1896
  stage: "selecting-crn",
1794
1897
  label: "Selecting CRN",
@@ -1801,7 +1904,10 @@ var SponsorRelayController = class {
1801
1904
  name: this.state.instanceName.trim(),
1802
1905
  sshPublicKey: this.state.sshPublicKey.trim(),
1803
1906
  rootfsItemHash: this.state.manifest.rootfsItemHash,
1804
- rootfsSizeMiB: Math.max(this.state.manifest.rootfsSizeMiB, spec.diskMiB),
1907
+ rootfsSizeMiB: Math.max(
1908
+ this.state.manifest.rootfsSizeMiB,
1909
+ spec.diskMiB
1910
+ ),
1805
1911
  vcpus: spec.vcpus,
1806
1912
  memoryMiB: spec.memoryMiB,
1807
1913
  rootfsVersion: this.state.manifest.version,
@@ -1824,6 +1930,16 @@ var SponsorRelayController = class {
1824
1930
  statusText: `Deployment submitted: ${result.itemHash}`,
1825
1931
  lastDeploymentHash: result.itemHash
1826
1932
  });
1933
+ if (result.status === "processed" && this.state.selectedCrn?.address) {
1934
+ await notifyCrnAllocationWithRetry({
1935
+ crnUrl: this.state.selectedCrn.address,
1936
+ itemHash: result.itemHash,
1937
+ fetch: (url, init) => fetch(url, init),
1938
+ onProgress: (event) => {
1939
+ this.emitProgress(event);
1940
+ }
1941
+ });
1942
+ }
1827
1943
  this.emitProgress({
1828
1944
  stage: "refreshing-instances",
1829
1945
  label: "Refreshing instances",