@le-space/ui 0.6.1 → 0.6.2

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
@@ -28,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
28
28
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
29
29
 
30
30
  // src/shared/package-version.ts
31
- var UI_PACKAGE_VERSION = "0.6.1";
31
+ var UI_PACKAGE_VERSION = "0.6.2";
32
32
 
33
33
  // src/shared/format.ts
34
34
  function shortHash(value, head = 8, tail = 6) {
@@ -2274,6 +2274,45 @@ async function configureOrbitdbRelaySetup2(args) {
2274
2274
  clearTimeout(timeout);
2275
2275
  }
2276
2276
  }
2277
+ async function configureUcanStore(args) {
2278
+ const controller = new AbortController();
2279
+ const timeout = setTimeout(
2280
+ () => controller.abort(),
2281
+ Number(args.timeoutMs ?? 18e4)
2282
+ );
2283
+ const payload = {
2284
+ public_ipv4: args.hostIpv4,
2285
+ public_ipv6: args.publicIpv6 ?? void 0,
2286
+ proxy_url: args.proxyUrl ?? void 0,
2287
+ webauthn_origin: args.webauthnOrigin ?? void 0,
2288
+ webauthn_origin_fallbacks: args.webauthnOriginFallbacks ?? void 0,
2289
+ admin_did: args.adminDid ?? void 0,
2290
+ bootstrap_package: args.bootstrapPackage ?? void 0,
2291
+ no_start: args.noStart === true ? true : void 0
2292
+ };
2293
+ try {
2294
+ const response = await args.fetch(
2295
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2296
+ {
2297
+ method: "POST",
2298
+ headers: {
2299
+ "content-type": "application/json"
2300
+ },
2301
+ body: JSON.stringify(payload),
2302
+ signal: controller.signal
2303
+ }
2304
+ );
2305
+ const responsePayload = await response.json().catch(() => null);
2306
+ if (!response.ok) {
2307
+ throw new Error(
2308
+ `Service setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2309
+ );
2310
+ }
2311
+ return responsePayload;
2312
+ } finally {
2313
+ clearTimeout(timeout);
2314
+ }
2315
+ }
2277
2316
  async function fetchUcGoPeerMetadata(args) {
2278
2317
  const attempts = Math.max(1, Number(args.attempts ?? 60));
2279
2318
  const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
@@ -2341,7 +2380,7 @@ async function fetchUcGoPeerMetadata(args) {
2341
2380
  }
2342
2381
  }
2343
2382
  throw new Error(
2344
- `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2383
+ `Guest metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2345
2384
  );
2346
2385
  }
2347
2386
 
@@ -3273,7 +3312,130 @@ function createDeploymentProgressEmitter() {
3273
3312
  };
3274
3313
  }
3275
3314
 
3315
+ // ../shared-types/src/ucan-store-bootstrap.ts
3316
+ function isRecord(value) {
3317
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3318
+ }
3319
+ function normalizeNonEmptyString(value) {
3320
+ if (typeof value !== "string") return null;
3321
+ const normalized = value.trim();
3322
+ return normalized ? normalized : null;
3323
+ }
3324
+ function normalizeDid(value) {
3325
+ const did = normalizeNonEmptyString(value);
3326
+ return did && did.startsWith("did:") ? did : null;
3327
+ }
3328
+ function normalizeOrigin(value) {
3329
+ const raw = normalizeNonEmptyString(value);
3330
+ if (!raw) return null;
3331
+ try {
3332
+ const parsed = new URL(raw);
3333
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.pathname && parsed.pathname !== "/" || parsed.search || parsed.hash) {
3334
+ return null;
3335
+ }
3336
+ return parsed.origin;
3337
+ } catch {
3338
+ return null;
3339
+ }
3340
+ }
3341
+ function normalizeExpiration(value) {
3342
+ if (value == null) return void 0;
3343
+ if (value === null) return null;
3344
+ if (!Number.isInteger(value) || Number(value) < 0) return void 0;
3345
+ return Number(value);
3346
+ }
3347
+ function validateUcanStoreBootstrapPackage(value) {
3348
+ if (!isRecord(value)) {
3349
+ return {
3350
+ valid: false,
3351
+ errors: ["Bootstrap package must be a JSON object."],
3352
+ bootstrapPackage: null
3353
+ };
3354
+ }
3355
+ const errors = [];
3356
+ const operatorAddress = normalizeNonEmptyString(value.operatorAddress);
3357
+ if (!operatorAddress || !/^0x[a-fA-F0-9]{40}$/.test(operatorAddress)) {
3358
+ errors.push("operatorAddress must be a 0x-prefixed 20-byte Ethereum address.");
3359
+ }
3360
+ const adminDid = normalizeDid(value.adminDid);
3361
+ if (!adminDid) {
3362
+ errors.push("adminDid must be a non-empty DID string.");
3363
+ }
3364
+ const serviceDidRaw = value.serviceDid;
3365
+ const serviceDid = serviceDidRaw == null || serviceDidRaw === "" ? null : normalizeDid(serviceDidRaw);
3366
+ if (serviceDidRaw != null && serviceDidRaw !== "" && !serviceDid) {
3367
+ errors.push("serviceDid must be empty or a non-empty DID string.");
3368
+ }
3369
+ const spaceDid = normalizeDid(value.spaceDid);
3370
+ if (!spaceDid) {
3371
+ errors.push("spaceDid must be a non-empty DID string.");
3372
+ }
3373
+ const rootDelegationProof = normalizeNonEmptyString(value.rootDelegationProof);
3374
+ if (!rootDelegationProof) {
3375
+ errors.push("rootDelegationProof must be a non-empty proof string.");
3376
+ }
3377
+ const allowedCapabilities = Array.isArray(value.allowedCapabilities) ? value.allowedCapabilities.map((entry) => normalizeNonEmptyString(entry)).filter((entry) => Boolean(entry)) : [];
3378
+ if (allowedCapabilities.length === 0) {
3379
+ errors.push("allowedCapabilities must contain at least one capability string.");
3380
+ }
3381
+ const defaultUserDelegationExpiration = normalizeExpiration(
3382
+ value.defaultUserDelegationExpiration
3383
+ );
3384
+ if (value.defaultUserDelegationExpiration != null && defaultUserDelegationExpiration === void 0) {
3385
+ errors.push(
3386
+ "defaultUserDelegationExpiration must be null or a non-negative integer number of seconds."
3387
+ );
3388
+ }
3389
+ const maxUserDelegationExpiration = normalizeExpiration(
3390
+ value.maxUserDelegationExpiration
3391
+ );
3392
+ if (value.maxUserDelegationExpiration != null && maxUserDelegationExpiration === void 0) {
3393
+ errors.push(
3394
+ "maxUserDelegationExpiration must be null or a non-negative integer number of seconds."
3395
+ );
3396
+ }
3397
+ if (typeof defaultUserDelegationExpiration === "number" && typeof maxUserDelegationExpiration === "number" && defaultUserDelegationExpiration > maxUserDelegationExpiration) {
3398
+ errors.push(
3399
+ "defaultUserDelegationExpiration cannot exceed maxUserDelegationExpiration."
3400
+ );
3401
+ }
3402
+ const pwaOrigin = normalizeOrigin(value.pwaOrigin);
3403
+ if (!pwaOrigin) {
3404
+ errors.push("pwaOrigin must be an http(s) origin without path, query, or hash.");
3405
+ }
3406
+ const serviceOrigin = normalizeOrigin(value.serviceOrigin);
3407
+ if (!serviceOrigin) {
3408
+ errors.push("serviceOrigin must be an http(s) origin without path, query, or hash.");
3409
+ }
3410
+ if (errors.length > 0) {
3411
+ return {
3412
+ valid: false,
3413
+ errors,
3414
+ bootstrapPackage: null
3415
+ };
3416
+ }
3417
+ return {
3418
+ valid: true,
3419
+ errors: [],
3420
+ bootstrapPackage: {
3421
+ operatorAddress,
3422
+ adminDid,
3423
+ serviceDid,
3424
+ spaceDid,
3425
+ rootDelegationProof,
3426
+ allowedCapabilities,
3427
+ defaultUserDelegationExpiration,
3428
+ maxUserDelegationExpiration,
3429
+ pwaOrigin,
3430
+ serviceOrigin
3431
+ }
3432
+ };
3433
+ }
3434
+
3276
3435
  // src/shared/controller.ts
3436
+ function deploymentNoun(profile) {
3437
+ return profile === "ucan-store" ? "service" : "relay";
3438
+ }
3277
3439
  function asJsonFetch(input, init) {
3278
3440
  return fetch(input, init).then(async (response) => ({
3279
3441
  ok: response.ok,
@@ -3281,11 +3443,122 @@ function asJsonFetch(input, init) {
3281
3443
  json: async () => await response.json()
3282
3444
  }));
3283
3445
  }
3284
- function isConfirmedRelaySetupResult(value) {
3446
+ function isConfirmedGuestSetupResult(value) {
3285
3447
  return Boolean(
3286
3448
  value && typeof value === "object" && "status" in value && value.status === "configured"
3287
3449
  );
3288
3450
  }
3451
+ function defaultUcanStoreBootstrapInput(props) {
3452
+ return {
3453
+ adminDid: props.ucanStoreBootstrap?.adminDid ?? "",
3454
+ serviceDid: props.ucanStoreBootstrap?.serviceDid ?? "",
3455
+ spaceDid: props.ucanStoreBootstrap?.spaceDid ?? "",
3456
+ rootDelegationProof: props.ucanStoreBootstrap?.rootDelegationProof ?? "",
3457
+ allowedCapabilities: props.ucanStoreBootstrap?.allowedCapabilities ?? "",
3458
+ defaultUserDelegationExpiration: props.ucanStoreBootstrap?.defaultUserDelegationExpiration ?? "",
3459
+ maxUserDelegationExpiration: props.ucanStoreBootstrap?.maxUserDelegationExpiration ?? "",
3460
+ pwaOrigin: props.ucanStoreBootstrap?.pwaOrigin ?? "",
3461
+ serviceOrigin: props.ucanStoreBootstrap?.serviceOrigin ?? ""
3462
+ };
3463
+ }
3464
+ function currentPageOrigin() {
3465
+ try {
3466
+ const origin = globalThis.location?.origin?.trim();
3467
+ return origin ? origin : null;
3468
+ } catch {
3469
+ return null;
3470
+ }
3471
+ }
3472
+ function parseOptionalExpiration(value) {
3473
+ const normalized = value.trim();
3474
+ if (!normalized) return void 0;
3475
+ const parsed = Number.parseInt(normalized, 10);
3476
+ if (!Number.isInteger(parsed) || parsed < 0) {
3477
+ return null;
3478
+ }
3479
+ return parsed;
3480
+ }
3481
+ function parseAllowedCapabilities(value) {
3482
+ return value.split(/[\n,]/).map((entry) => entry.trim()).filter(Boolean);
3483
+ }
3484
+ function draftUcanStoreBootstrapErrors(args) {
3485
+ const errors = [];
3486
+ const { input } = args;
3487
+ if (!args.operatorAddress) {
3488
+ errors.push("Connect MetaMask before configuring the ucan-store bootstrap package.");
3489
+ }
3490
+ if (!input.adminDid.trim().startsWith("did:")) {
3491
+ errors.push("Admin DID must be a non-empty DID string.");
3492
+ }
3493
+ if (input.serviceDid.trim() && !input.serviceDid.trim().startsWith("did:")) {
3494
+ errors.push("Service DID must be empty or a non-empty DID string.");
3495
+ }
3496
+ if (!input.spaceDid.trim().startsWith("did:")) {
3497
+ errors.push("Space DID must be a non-empty DID string.");
3498
+ }
3499
+ if (!input.rootDelegationProof.trim()) {
3500
+ errors.push("Root delegation proof is required.");
3501
+ }
3502
+ if (parseAllowedCapabilities(input.allowedCapabilities).length === 0) {
3503
+ errors.push("Add at least one allowed capability.");
3504
+ }
3505
+ const defaultExpiration = parseOptionalExpiration(
3506
+ input.defaultUserDelegationExpiration
3507
+ );
3508
+ if (defaultExpiration === null) {
3509
+ errors.push(
3510
+ "Default user delegation expiration must be a non-negative integer number of seconds."
3511
+ );
3512
+ }
3513
+ const maxExpiration = parseOptionalExpiration(
3514
+ input.maxUserDelegationExpiration
3515
+ );
3516
+ if (maxExpiration === null) {
3517
+ errors.push(
3518
+ "Max user delegation expiration must be a non-negative integer number of seconds."
3519
+ );
3520
+ }
3521
+ if (typeof defaultExpiration === "number" && typeof maxExpiration === "number" && defaultExpiration > maxExpiration) {
3522
+ errors.push(
3523
+ "Default user delegation expiration cannot exceed the max user delegation expiration."
3524
+ );
3525
+ }
3526
+ const pwaOrigin = input.pwaOrigin.trim() || currentPageOrigin() || "";
3527
+ if (!pwaOrigin) {
3528
+ errors.push("PWA origin is required for the ucan-store bootstrap package.");
3529
+ }
3530
+ return errors;
3531
+ }
3532
+ function buildUcanStoreBootstrapPackage(args) {
3533
+ const candidate = {
3534
+ operatorAddress: args.operatorAddress,
3535
+ adminDid: args.input.adminDid.trim(),
3536
+ serviceDid: args.input.serviceDid.trim() || null,
3537
+ spaceDid: args.input.spaceDid.trim(),
3538
+ rootDelegationProof: args.input.rootDelegationProof.trim(),
3539
+ allowedCapabilities: parseAllowedCapabilities(args.input.allowedCapabilities),
3540
+ defaultUserDelegationExpiration: parseOptionalExpiration(
3541
+ args.input.defaultUserDelegationExpiration
3542
+ ),
3543
+ maxUserDelegationExpiration: parseOptionalExpiration(
3544
+ args.input.maxUserDelegationExpiration
3545
+ ),
3546
+ pwaOrigin: args.input.pwaOrigin.trim() || args.pwaOriginFallback?.trim() || currentPageOrigin() || "",
3547
+ serviceOrigin: args.input.serviceOrigin.trim() || args.serviceOriginFallback?.trim() || ""
3548
+ };
3549
+ if (candidate.defaultUserDelegationExpiration === void 0) {
3550
+ delete candidate.defaultUserDelegationExpiration;
3551
+ }
3552
+ if (candidate.maxUserDelegationExpiration === void 0) {
3553
+ delete candidate.maxUserDelegationExpiration;
3554
+ }
3555
+ if (candidate.serviceDid === null) {
3556
+ delete candidate.serviceDid;
3557
+ }
3558
+ return validateUcanStoreBootstrapPackage(
3559
+ candidate
3560
+ );
3561
+ }
3289
3562
  function defaultState(props = {}) {
3290
3563
  return {
3291
3564
  ready: false,
@@ -3300,6 +3573,7 @@ function defaultState(props = {}) {
3300
3573
  manifestJson: props.manifestJson ?? "",
3301
3574
  sshPublicKey: props.sshPublicKey ?? "",
3302
3575
  instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
3576
+ ucanStoreBootstrap: defaultUcanStoreBootstrapInput(props),
3303
3577
  tierId: DEFAULT_TIER_ID,
3304
3578
  showAdvanced: Boolean(props.manifestJson?.trim() || props.sshPublicKey?.trim()),
3305
3579
  showInstances: props.showInstances ?? true,
@@ -3434,13 +3708,33 @@ async function resolveManifest(args) {
3434
3708
  if (pasted) return pasted;
3435
3709
  return loadRootfsManifest(args.manifestUrl);
3436
3710
  }
3437
- function relayProfileForManifest(manifest) {
3711
+ function deploymentProfileForManifest(manifest) {
3438
3712
  if (manifest?.profile === "uc-go-peer") return "uc-go-peer";
3439
3713
  if (manifest?.profile === "orbitdb-relay") {
3440
3714
  return manifest.profile;
3441
3715
  }
3716
+ if (manifest?.profile === "ucan-store") return "ucan-store";
3442
3717
  return null;
3443
3718
  }
3719
+ function bootstrapRelayProfileForManifest(manifest) {
3720
+ const profile = deploymentProfileForManifest(manifest);
3721
+ if (profile === "uc-go-peer" || profile === "orbitdb-relay") {
3722
+ return profile;
3723
+ }
3724
+ return null;
3725
+ }
3726
+ function supportsBootstrapRegistrationsForManifest(manifest) {
3727
+ return bootstrapRelayProfileForManifest(manifest) != null;
3728
+ }
3729
+ function deploymentReadyLabel(profile) {
3730
+ return profile === "ucan-store" ? "Service ready" : "Relay ready";
3731
+ }
3732
+ function deploymentReadyDetail(profile, fallback) {
3733
+ if (profile === "ucan-store") {
3734
+ return "The upload service runtime is available and the deployment finished successfully.";
3735
+ }
3736
+ return fallback;
3737
+ }
3444
3738
  function registrationIdInstanceItemHash(registrationId) {
3445
3739
  const normalized = String(registrationId ?? "").trim();
3446
3740
  if (!normalized) return null;
@@ -3689,19 +3983,148 @@ var SponsorRelayController = class {
3689
3983
  this.progressEmitter.emit(nextEvent);
3690
3984
  }
3691
3985
  async configureRelayBootstrapRegistration(args) {
3692
- const profile = relayProfileForManifest(this.state.manifest);
3693
- if (!profile) return;
3694
- if (!this.state.wallet.address) {
3986
+ const deploymentProfile = deploymentProfileForManifest(this.state.manifest);
3987
+ const profile = bootstrapRelayProfileForManifest(this.state.manifest);
3988
+ if (!profile && deploymentProfile !== "ucan-store") return;
3989
+ if (profile && !this.state.wallet.address) {
3695
3990
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3696
3991
  }
3697
- if (!args.runtime.hostIpv4) {
3698
- throw new Error("Relay runtime did not expose a host IPv4 address.");
3992
+ const runtimeHostIpv4 = args.runtime.hostIpv4;
3993
+ if (!runtimeHostIpv4) {
3994
+ throw new Error(
3995
+ `${deploymentProfile === "ucan-store" ? "Service" : "Relay"} runtime did not expose a host IPv4 address.`
3996
+ );
3699
3997
  }
3700
3998
  let runtime = args.runtime;
3701
3999
  let mappedPorts2 = runtime.mappedPorts ?? {};
3702
4000
  const setupPort = mappedPorts2["80"]?.host ?? null;
3703
4001
  if (!setupPort) {
3704
- throw new Error("Relay runtime did not expose the temporary setup endpoint.");
4002
+ throw new Error(
4003
+ `${deploymentProfile === "ucan-store" ? "Service" : "Relay"} runtime did not expose the temporary setup endpoint.`
4004
+ );
4005
+ }
4006
+ if (deploymentProfile === "ucan-store") {
4007
+ const serviceHostIpv4 = runtimeHostIpv4;
4008
+ const serviceProxyUrl = runtime.proxyUrl;
4009
+ if (!serviceProxyUrl) {
4010
+ throw new Error("Service runtime did not expose a proxy URL.");
4011
+ }
4012
+ const walletAddress2 = this.state.wallet.address;
4013
+ if (!walletAddress2) {
4014
+ throw new Error("A connected wallet is required to configure the ucan-store bootstrap package.");
4015
+ }
4016
+ const bootstrapValidation = buildUcanStoreBootstrapPackage({
4017
+ input: this.state.ucanStoreBootstrap,
4018
+ operatorAddress: walletAddress2,
4019
+ serviceOriginFallback: serviceProxyUrl,
4020
+ pwaOriginFallback: currentPageOrigin()
4021
+ });
4022
+ if (!bootstrapValidation.valid || !bootstrapValidation.bootstrapPackage) {
4023
+ throw new Error(
4024
+ bootstrapValidation.errors[0] ?? "The ucan-store bootstrap package is invalid."
4025
+ );
4026
+ }
4027
+ this.emitProgress({
4028
+ stage: "deployment-confirmed",
4029
+ label: "Configuring service",
4030
+ progress: 93,
4031
+ status: "info",
4032
+ itemHash: args.itemHash,
4033
+ detail: "Waiting for the guest setup endpoint before applying the upload service configuration."
4034
+ });
4035
+ const setupHealth = await waitForSetupEndpoint({
4036
+ hostIpv4: serviceHostIpv4,
4037
+ setupPort,
4038
+ fetch: (url, init) => fetch(url, init),
4039
+ attempts: 15,
4040
+ delayMs: 4e3,
4041
+ httpTimeoutMs: 1e4,
4042
+ onAttempt: (result, attempt, attempts) => {
4043
+ this.emitProgress({
4044
+ stage: "deployment-confirmed",
4045
+ label: "Waiting for guest setup",
4046
+ progress: 93,
4047
+ status: result.ok ? "success" : "info",
4048
+ itemHash: args.itemHash,
4049
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${serviceHostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${serviceHostIpv4}:${setupPort}/health.`
4050
+ });
4051
+ }
4052
+ });
4053
+ if (!setupHealth.ok) {
4054
+ throw new Error(
4055
+ `Service setup endpoint did not become reachable at http://${serviceHostIpv4}:${setupPort}/health.`
4056
+ );
4057
+ }
4058
+ this.emitProgress({
4059
+ stage: "deployment-confirmed",
4060
+ label: "Applying service config",
4061
+ progress: 94,
4062
+ status: "info",
4063
+ itemHash: args.itemHash,
4064
+ detail: "Publishing the public HTTPS origin into the guest upload service configuration."
4065
+ });
4066
+ const configureResult = await configureUcanStore({
4067
+ hostIpv4: serviceHostIpv4,
4068
+ publicIpv6: runtime.ipv6,
4069
+ setupPort,
4070
+ proxyUrl: serviceProxyUrl,
4071
+ webauthnOrigin: serviceProxyUrl,
4072
+ adminDid: bootstrapValidation.bootstrapPackage.adminDid,
4073
+ bootstrapPackage: bootstrapValidation.bootstrapPackage,
4074
+ fetch: (url, init) => fetch(url, init),
4075
+ timeoutMs: 18e4
4076
+ });
4077
+ if (!isConfirmedGuestSetupResult(configureResult)) {
4078
+ throw new Error(
4079
+ `Service guest configuration could not be confirmed at http://${serviceHostIpv4}:${setupPort}/configure.`
4080
+ );
4081
+ }
4082
+ this.emitProgress({
4083
+ stage: "deployment-confirmed",
4084
+ label: "Waiting for service metadata",
4085
+ progress: 95,
4086
+ status: "info",
4087
+ itemHash: args.itemHash,
4088
+ detail: "Waiting for the upload service metadata endpoint to report the public service DID and PWA environment."
4089
+ });
4090
+ const serviceMetadata = await fetchUcGoPeerMetadata({
4091
+ hostIpv4: serviceHostIpv4,
4092
+ setupPort,
4093
+ fetch: (url, init) => fetch(url, init),
4094
+ attempts: 80,
4095
+ delayMs: 3e3,
4096
+ timeoutMs: 24e4,
4097
+ isReady: ({ payload, ok }) => {
4098
+ if (!ok || !payload || typeof payload !== "object") return false;
4099
+ const metadata2 = payload.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
4100
+ const pwaEnv = metadata2?.pwa_env && typeof metadata2.pwa_env === "object" ? metadata2.pwa_env : null;
4101
+ return Boolean(
4102
+ typeof metadata2?.upload_service_did === "string" && metadata2.upload_service_did.length > 0 && typeof pwaEnv?.VITE_UPLOAD_SERVICE_URL === "string" && pwaEnv.VITE_UPLOAD_SERVICE_URL.length > 0
4103
+ );
4104
+ },
4105
+ onAttempt: ({ ready, attempt, attempts, requestUrl }) => {
4106
+ this.emitProgress({
4107
+ stage: "deployment-confirmed",
4108
+ label: ready ? "Service metadata ready" : "Waiting for service metadata",
4109
+ progress: 95,
4110
+ status: ready ? "success" : "info",
4111
+ itemHash: args.itemHash,
4112
+ detail: ready ? `Service metadata ${attempt}/${attempts}: public upload service wiring is ready.` : `Service metadata ${attempt}/${attempts}: waiting for ${requestUrl}.`
4113
+ });
4114
+ }
4115
+ });
4116
+ const metadata = serviceMetadata && typeof serviceMetadata === "object" ? serviceMetadata.metadata ?? null : null;
4117
+ if (!metadata || typeof metadata !== "object" || typeof metadata.upload_service_did !== "string") {
4118
+ throw new Error(
4119
+ "Service metadata did not include the public upload service DID."
4120
+ );
4121
+ }
4122
+ return;
4123
+ }
4124
+ if (!profile) return;
4125
+ const walletAddress = this.state.wallet.address;
4126
+ if (!walletAddress) {
4127
+ throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3705
4128
  }
3706
4129
  let bootstrapSignalRelayMetadata = null;
3707
4130
  if (profile === "orbitdb-relay") {
@@ -3714,7 +4137,7 @@ var SponsorRelayController = class {
3714
4137
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3715
4138
  });
3716
4139
  const setupHealth = await waitForSetupEndpoint({
3717
- hostIpv4: args.runtime.hostIpv4,
4140
+ hostIpv4: runtimeHostIpv4,
3718
4141
  setupPort,
3719
4142
  fetch: (url, init) => fetch(url, init),
3720
4143
  attempts: 15,
@@ -3727,13 +4150,13 @@ var SponsorRelayController = class {
3727
4150
  progress: 93,
3728
4151
  status: result.ok ? "success" : "info",
3729
4152
  itemHash: args.itemHash,
3730
- detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${args.runtime.hostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${args.runtime.hostIpv4}:${setupPort}/health.`
4153
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${runtimeHostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${runtimeHostIpv4}:${setupPort}/health.`
3731
4154
  });
3732
4155
  }
3733
4156
  });
3734
4157
  if (!setupHealth.ok) {
3735
4158
  throw new Error(
3736
- `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
4159
+ `Relay setup endpoint did not become reachable at http://${runtimeHostIpv4}:${setupPort}/health.`
3737
4160
  );
3738
4161
  }
3739
4162
  this.emitProgress({
@@ -3750,7 +4173,7 @@ var SponsorRelayController = class {
3750
4173
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3751
4174
  }
3752
4175
  const configureResult = await configureOrbitdbRelaySetup2({
3753
- hostIpv4: args.runtime.hostIpv4,
4176
+ hostIpv4: runtimeHostIpv4,
3754
4177
  publicIpv6: args.runtime.ipv6,
3755
4178
  setupPort,
3756
4179
  tcpPort,
@@ -3763,7 +4186,7 @@ var SponsorRelayController = class {
3763
4186
  fetch: (url, init) => fetch(url, init),
3764
4187
  timeoutMs: 18e4
3765
4188
  });
3766
- if (!isConfirmedRelaySetupResult(configureResult)) {
4189
+ if (!isConfirmedGuestSetupResult(configureResult)) {
3767
4190
  throw new Error(
3768
4191
  `Relay guest configuration could not be confirmed at http://${args.runtime.hostIpv4}:${setupPort}/configure.`
3769
4192
  );
@@ -3821,7 +4244,7 @@ var SponsorRelayController = class {
3821
4244
  "Relay runtime lost its host IPv4 address while waiting for HTTPS activation."
3822
4245
  );
3823
4246
  }
3824
- const runtimeHostIpv4 = runtime.hostIpv4;
4247
+ const runtimeHostIpv42 = runtime.hostIpv4;
3825
4248
  const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
3826
4249
  this.emitProgress({
3827
4250
  stage: "deployment-confirmed",
@@ -3834,13 +4257,13 @@ var SponsorRelayController = class {
3834
4257
  const record = {
3835
4258
  deploymentToken: args.deploymentToken,
3836
4259
  profile,
3837
- ownerAddress: this.state.wallet.address,
4260
+ ownerAddress: walletAddress,
3838
4261
  instanceItemHash: args.itemHash,
3839
4262
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3840
4263
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3841
4264
  status: "pending",
3842
4265
  runtime: {
3843
- publicIpv4: runtimeHostIpv4,
4266
+ publicIpv4: runtimeHostIpv42,
3844
4267
  publicIpv6: runtime.ipv6 ?? null,
3845
4268
  proxyUrl: activeProxyUrl,
3846
4269
  mappedPorts: mappedPorts2
@@ -3854,7 +4277,7 @@ var SponsorRelayController = class {
3854
4277
  }
3855
4278
  };
3856
4279
  const bootstrapConfigPublication = await publishVmBootstrapConfig({
3857
- sender: this.state.wallet.address,
4280
+ sender: walletAddress,
3858
4281
  record,
3859
4282
  signer: personalSign,
3860
4283
  hasher: sha256Hex,
@@ -3881,7 +4304,7 @@ var SponsorRelayController = class {
3881
4304
  });
3882
4305
  const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
3883
4306
  deploymentToken: args.deploymentToken,
3884
- ownerAddress: this.state.wallet.address,
4307
+ ownerAddress: walletAddress,
3885
4308
  instanceItemHash: args.itemHash,
3886
4309
  fetch: asJsonFetch,
3887
4310
  apiHost: this.client.apiHost,
@@ -4053,7 +4476,7 @@ var SponsorRelayController = class {
4053
4476
  detail: "Guest bootstrap registration was delayed. Publishing the relay bootstrap record from the browser."
4054
4477
  });
4055
4478
  const fallbackPublication = await publishRelayBootstrapRegistration({
4056
- sender: this.state.wallet.address,
4479
+ sender: walletAddress,
4057
4480
  signer: personalSign,
4058
4481
  hasher: sha256Hex,
4059
4482
  fetch: asJsonFetch,
@@ -4073,7 +4496,7 @@ var SponsorRelayController = class {
4073
4496
  fallbackPublication
4074
4497
  });
4075
4498
  const fallbackVisibleRegistration = await waitForRelayBootstrapRegistration({
4076
- sender: this.state.wallet.address,
4499
+ sender: walletAddress,
4077
4500
  registrationId,
4078
4501
  peerId,
4079
4502
  fetch: asJsonFetch,
@@ -4106,7 +4529,7 @@ var SponsorRelayController = class {
4106
4529
  if (profile === "uc-go-peer" && args.deploymentToken) {
4107
4530
  let cleanupAggregateItemHash = null;
4108
4531
  await deleteVmBootstrapConfig({
4109
- sender: this.state.wallet.address,
4532
+ sender: walletAddress,
4110
4533
  deploymentToken: args.deploymentToken,
4111
4534
  signer: personalSign,
4112
4535
  hasher: sha256Hex,
@@ -4272,17 +4695,35 @@ var SponsorRelayController = class {
4272
4695
  });
4273
4696
  return;
4274
4697
  }
4698
+ const currentProfile = deploymentProfileForManifest(this.state.manifest);
4275
4699
  const hasConfirmedBootstrapRegistration = this.state.bootstrapRegistrations.some(
4276
4700
  (entry) => entry.instanceItemHash === itemHash && entry.confirmed
4277
4701
  );
4702
+ if (currentProfile === "ucan-store") {
4703
+ this.emitProgress({
4704
+ stage: "completed",
4705
+ label: deploymentReadyLabel(currentProfile),
4706
+ progress: 100,
4707
+ status: "success",
4708
+ itemHash,
4709
+ detail: deploymentReadyDetail(
4710
+ currentProfile,
4711
+ "Runtime networking is available and the deployment finished successfully."
4712
+ )
4713
+ });
4714
+ return;
4715
+ }
4278
4716
  if (hasConfirmedBootstrapRegistration) {
4279
4717
  this.emitProgress({
4280
4718
  stage: "completed",
4281
- label: "Relay ready",
4719
+ label: deploymentReadyLabel(currentProfile),
4282
4720
  progress: 100,
4283
4721
  status: "success",
4284
4722
  itemHash,
4285
- detail: "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4723
+ detail: deploymentReadyDetail(
4724
+ currentProfile,
4725
+ "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4726
+ )
4286
4727
  });
4287
4728
  return;
4288
4729
  }
@@ -4292,7 +4733,7 @@ var SponsorRelayController = class {
4292
4733
  progress: 92,
4293
4734
  status: "info",
4294
4735
  itemHash,
4295
- detail: "Deployment processed and runtime networking is available. Finishing relay setup and bootstrap verification."
4736
+ detail: `Deployment processed and runtime networking is available. Finishing ${deploymentNoun(currentProfile)} setup and verification.`
4296
4737
  });
4297
4738
  }
4298
4739
  canSkipRuntimeRefresh(instance) {
@@ -4405,6 +4846,15 @@ var SponsorRelayController = class {
4405
4846
  setInstanceName(instanceName) {
4406
4847
  this.patch({ instanceName });
4407
4848
  }
4849
+ setUcanStoreBootstrapField(field, value) {
4850
+ this.patch({
4851
+ ucanStoreBootstrap: {
4852
+ ...this.state.ucanStoreBootstrap,
4853
+ [field]: value
4854
+ },
4855
+ showAdvanced: this.state.showAdvanced || Boolean(value.trim())
4856
+ });
4857
+ }
4408
4858
  setTierId(tierId) {
4409
4859
  this.patch({ tierId });
4410
4860
  this.recomputePricingSummary();
@@ -4490,6 +4940,7 @@ var SponsorRelayController = class {
4490
4940
  }
4491
4941
  }
4492
4942
  async refresh() {
4943
+ const currentProfile = deploymentProfileForManifest(this.state.manifest);
4493
4944
  this.trace("refresh:start", {
4494
4945
  wallet: this.state.wallet.address,
4495
4946
  manifestUrl: this.state.manifestUrl,
@@ -4498,7 +4949,7 @@ var SponsorRelayController = class {
4498
4949
  this.patch({
4499
4950
  busy: { refreshing: true },
4500
4951
  errorText: null,
4501
- statusText: "Refreshing relay sponsor data"
4952
+ statusText: currentProfile === "ucan-store" ? "Refreshing service deployment data" : "Refreshing relay deployment data"
4502
4953
  });
4503
4954
  try {
4504
4955
  const [pricingSummary, crns] = await Promise.all([
@@ -4510,6 +4961,8 @@ var SponsorRelayController = class {
4510
4961
  manifestJson: this.state.manifestJson
4511
4962
  }).catch((error) => manifestLoadErrorState(error));
4512
4963
  const manifest = manifestState.manifest;
4964
+ const resolvedProfile = deploymentProfileForManifest(manifest);
4965
+ const bootstrapEnabled = supportsBootstrapRegistrationsForManifest(manifest);
4513
4966
  let balance = this.state.balance;
4514
4967
  let instances = [];
4515
4968
  let bootstrapRegistrations = [];
@@ -4518,10 +4971,10 @@ var SponsorRelayController = class {
4518
4971
  const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
4519
4972
  this.client.fetchBalance(this.state.wallet.address),
4520
4973
  this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
4521
- fetchAlephBootstrapPosts({
4974
+ bootstrapEnabled ? fetchAlephBootstrapPosts({
4522
4975
  apiHost: this.client.apiHost,
4523
4976
  fetch
4524
- }).catch(() => [])
4977
+ }).catch(() => []) : Promise.resolve([])
4525
4978
  ]);
4526
4979
  balance = nextBalance;
4527
4980
  instances = await Promise.all(
@@ -4651,7 +5104,7 @@ var SponsorRelayController = class {
4651
5104
  bootstrapRegistrations,
4652
5105
  orphanBootstrapRegistrations,
4653
5106
  busy: { refreshing: false },
4654
- statusText: "Relay sponsor data ready"
5107
+ statusText: resolvedProfile === "ucan-store" ? "Service deployment data ready" : "Relay deployment data ready"
4655
5108
  });
4656
5109
  this.trace("refresh:success", {
4657
5110
  manifestValid: manifestState.valid,
@@ -4688,6 +5141,16 @@ var SponsorRelayController = class {
4688
5141
  });
4689
5142
  return;
4690
5143
  }
5144
+ if (deploymentProfileForManifest(this.state.manifest) === "ucan-store") {
5145
+ const draftErrors = draftUcanStoreBootstrapErrors({
5146
+ input: this.state.ucanStoreBootstrap,
5147
+ operatorAddress: this.state.wallet.address
5148
+ });
5149
+ if (draftErrors.length > 0) {
5150
+ this.patch({ errorText: draftErrors[0] });
5151
+ return;
5152
+ }
5153
+ }
4691
5154
  this.patch({
4692
5155
  busy: { deploying: true },
4693
5156
  errorText: null,
@@ -4737,7 +5200,7 @@ var SponsorRelayController = class {
4737
5200
  detail: `Trying ${crnDisplayName}`
4738
5201
  });
4739
5202
  try {
4740
- const profile = relayProfileForManifest(this.state.manifest);
5203
+ const profile = deploymentProfileForManifest(this.state.manifest);
4741
5204
  attemptProfile = profile;
4742
5205
  const sshPublicKey = profile === "uc-go-peer" ? appendDeploymentTokenToSshPublicKey(
4743
5206
  this.state.sshPublicKey.trim(),
@@ -4956,11 +5419,14 @@ var SponsorRelayController = class {
4956
5419
  this.lastCompletedDeploymentHash = result.itemHash;
4957
5420
  this.emitProgress({
4958
5421
  stage: "completed",
4959
- label: "Relay ready",
5422
+ label: deploymentReadyLabel(attemptProfile),
4960
5423
  progress: 100,
4961
5424
  status: "success",
4962
5425
  itemHash: result.itemHash,
4963
- detail: "Relay runtime, setup, metadata, and bootstrap registration are confirmed."
5426
+ detail: deploymentReadyDetail(
5427
+ attemptProfile,
5428
+ "Relay runtime, setup, metadata, and bootstrap registration are confirmed."
5429
+ )
4964
5430
  });
4965
5431
  return;
4966
5432
  } catch (error) {
@@ -5034,11 +5500,14 @@ var SponsorRelayController = class {
5034
5500
  this.lastCompletedDeploymentHash = attemptItemHash;
5035
5501
  this.emitProgress({
5036
5502
  stage: "completed",
5037
- label: "Relay ready",
5503
+ label: deploymentReadyLabel(attemptProfile),
5038
5504
  progress: 100,
5039
5505
  status: "success",
5040
5506
  itemHash: attemptItemHash,
5041
- detail: "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
5507
+ detail: deploymentReadyDetail(
5508
+ attemptProfile,
5509
+ "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
5510
+ )
5042
5511
  });
5043
5512
  return;
5044
5513
  }
@@ -5065,11 +5534,14 @@ var SponsorRelayController = class {
5065
5534
  this.lastCompletedDeploymentHash = attemptItemHash;
5066
5535
  this.emitProgress({
5067
5536
  stage: "completed",
5068
- label: "Relay ready",
5537
+ label: deploymentReadyLabel(attemptProfile),
5069
5538
  progress: 100,
5070
5539
  status: "success",
5071
5540
  itemHash: attemptItemHash,
5072
- detail: "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
5541
+ detail: deploymentReadyDetail(
5542
+ attemptProfile,
5543
+ "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
5544
+ )
5073
5545
  });
5074
5546
  return;
5075
5547
  }
@@ -5384,7 +5856,8 @@ function useSponsorRelayController(props) {
5384
5856
  props.schedulerApiHost,
5385
5857
  props.showInstances,
5386
5858
  props.sshPublicKey,
5387
- props.twoN6ApiHost
5859
+ props.twoN6ApiHost,
5860
+ props.ucanStoreBootstrap
5388
5861
  ]
5389
5862
  );
5390
5863
  useEffect(() => {
@@ -5518,6 +5991,27 @@ var POLLING_STAGES = /* @__PURE__ */ new Set([
5518
5991
  "publishing-bootstrap",
5519
5992
  "refreshing-instances"
5520
5993
  ]);
5994
+ function stateDeploymentProfile(state) {
5995
+ return state.manifest?.profile === "ucan-store" ? "ucan-store" : "relay";
5996
+ }
5997
+ function supportsBootstrapUi(state) {
5998
+ return stateDeploymentProfile(state) !== "ucan-store";
5999
+ }
6000
+ function deploymentPanelTitle(state) {
6001
+ return stateDeploymentProfile(state) === "ucan-store" ? "Sponsor Service" : "Sponsor Relay";
6002
+ }
6003
+ function deploymentLauncherLabel(state, compactInlineLabel, launcherMode) {
6004
+ if (launcherMode === "inline" && compactInlineLabel) {
6005
+ return stateDeploymentProfile(state) === "ucan-store" ? "Service" : "Relay";
6006
+ }
6007
+ return deploymentPanelTitle(state);
6008
+ }
6009
+ function deploymentInstanceFallbackLabel(state) {
6010
+ return stateDeploymentProfile(state) === "ucan-store" ? "service" : "relay";
6011
+ }
6012
+ function deploymentButtonLabel(state) {
6013
+ return stateDeploymentProfile(state) === "ucan-store" ? "Deploy Service" : "Deploy Relay";
6014
+ }
5521
6015
  function isDeploymentProgressVisible(state) {
5522
6016
  const stage = state.deploymentProgress.stage;
5523
6017
  if (stage === "idle") {
@@ -5533,17 +6027,19 @@ function isDeploymentProgressVisible(state) {
5533
6027
  }
5534
6028
  function pollingIndicator(state) {
5535
6029
  if (state.busy.refreshing) {
6030
+ const isService2 = stateDeploymentProfile(state) === "ucan-store";
5536
6031
  return {
5537
- label: "Checking relay state",
5538
- detail: state.statusText || "Refreshing relay sponsor data from Aleph and the selected CRN."
6032
+ label: isService2 ? "Checking deployment state" : "Checking relay state",
6033
+ detail: state.statusText || (isService2 ? "Refreshing service deployment data from Aleph and the selected CRN." : "Refreshing relay deployment data from Aleph and the selected CRN.")
5539
6034
  };
5540
6035
  }
5541
6036
  if (!POLLING_STAGES.has(state.deploymentProgress.stage)) {
5542
6037
  return null;
5543
6038
  }
6039
+ const isService = stateDeploymentProfile(state) === "ucan-store";
5544
6040
  return {
5545
- label: state.deploymentProgress.label || "Polling relay state",
5546
- detail: state.deploymentProgress.detail || state.statusText || "Waiting for the next confirmed relay state from Aleph."
6041
+ label: state.deploymentProgress.label || (isService ? "Polling deployment state" : "Polling relay state"),
6042
+ detail: state.deploymentProgress.detail || state.statusText || (isService ? "Waiting for the next confirmed deployment state from Aleph." : "Waiting for the next confirmed relay state from Aleph.")
5547
6043
  };
5548
6044
  }
5549
6045
  function launcherIndicator(state) {
@@ -5592,7 +6088,7 @@ function launcherIndicator(state) {
5592
6088
  if (state.busy.refreshing) {
5593
6089
  return {
5594
6090
  label: "SYNC",
5595
- detail: "Refreshing relay state",
6091
+ detail: stateDeploymentProfile(state) === "ucan-store" ? "Refreshing deployment state" : "Refreshing relay state",
5596
6092
  tone: "info"
5597
6093
  };
5598
6094
  }
@@ -5609,6 +6105,7 @@ function SponsorRelayFab(props) {
5609
6105
  const versionLabel = resolvedVersion.startsWith("v") ? resolvedVersion : `v${resolvedVersion}`;
5610
6106
  const launcherMode = props.launcherMode ?? "floating";
5611
6107
  const indicator = launcherIndicator(state);
6108
+ const bootstrapUiEnabled = supportsBootstrapUi(state);
5612
6109
  const confirmedRegistrationByInstanceHash = new Map(
5613
6110
  (state.bootstrapRegistrations ?? []).filter((entry) => entry.confirmed && entry.instanceItemHash).map((entry) => [entry.instanceItemHash, entry])
5614
6111
  );
@@ -5710,7 +6207,8 @@ function SponsorRelayFab(props) {
5710
6207
  bottom: "11.5rem",
5711
6208
  maxHeight: "calc(100vh - 12.5rem)"
5712
6209
  };
5713
- const launcherLabel = launcherMode === "inline" && compactInlineLabel ? "Relay" : "Sponsor Relay";
6210
+ const launcherLabel = deploymentLauncherLabel(state, compactInlineLabel, launcherMode);
6211
+ const deployCallToAction = state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : deploymentButtonLabel(state) : "Connect MetaMask";
5714
6212
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5715
6213
  /* @__PURE__ */ jsx("style", { children: `@keyframes leSpaceRelayPulse { 0%, 100% { transform: scale(1); opacity: 0.74; } 50% { transform: scale(1.22); opacity: 1; } }` }),
5716
6214
  /* @__PURE__ */ jsxs(
@@ -5723,7 +6221,7 @@ function SponsorRelayFab(props) {
5723
6221
  onMouseLeave: () => setHovered(false),
5724
6222
  onFocus: () => setHovered(true),
5725
6223
  onBlur: () => setHovered(false),
5726
- title: indicator.detail ?? "Sponsor Relay",
6224
+ title: indicator.detail ?? deploymentPanelTitle(state),
5727
6225
  style: {
5728
6226
  position: launcherMode === "floating" ? "fixed" : "relative",
5729
6227
  right: launcherMode === "floating" ? "1.4rem" : void 0,
@@ -5880,7 +6378,7 @@ function SponsorRelayFab(props) {
5880
6378
  margin: "0.2rem 0 0",
5881
6379
  fontFamily: '"Epilogue", sans-serif'
5882
6380
  },
5883
- children: "Sponsor Relay"
6381
+ children: deploymentPanelTitle(state)
5884
6382
  }
5885
6383
  )
5886
6384
  ] }),
@@ -6217,7 +6715,156 @@ function SponsorRelayFab(props) {
6217
6715
  )
6218
6716
  ]
6219
6717
  }
6220
- )
6718
+ ),
6719
+ stateDeploymentProfile(state) === "ucan-store" ? /* @__PURE__ */ jsxs("details", { open: true, children: [
6720
+ /* @__PURE__ */ jsx("summary", { children: "UCAN Store Bootstrap" }),
6721
+ /* @__PURE__ */ jsxs(
6722
+ "div",
6723
+ {
6724
+ style: {
6725
+ display: "grid",
6726
+ gap: "0.75rem",
6727
+ marginTop: "0.65rem"
6728
+ },
6729
+ children: [
6730
+ /* @__PURE__ */ jsx(
6731
+ "input",
6732
+ {
6733
+ style: fieldStyle,
6734
+ value: state.ucanStoreBootstrap.adminDid,
6735
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6736
+ "adminDid",
6737
+ event.currentTarget.value
6738
+ ),
6739
+ placeholder: "Admin DID"
6740
+ }
6741
+ ),
6742
+ /* @__PURE__ */ jsx(
6743
+ "input",
6744
+ {
6745
+ style: fieldStyle,
6746
+ value: state.ucanStoreBootstrap.serviceDid,
6747
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6748
+ "serviceDid",
6749
+ event.currentTarget.value
6750
+ ),
6751
+ placeholder: "Service DID override (optional)"
6752
+ }
6753
+ ),
6754
+ /* @__PURE__ */ jsx(
6755
+ "input",
6756
+ {
6757
+ style: fieldStyle,
6758
+ value: state.ucanStoreBootstrap.spaceDid,
6759
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6760
+ "spaceDid",
6761
+ event.currentTarget.value
6762
+ ),
6763
+ placeholder: "Space DID"
6764
+ }
6765
+ ),
6766
+ /* @__PURE__ */ jsx(
6767
+ "textarea",
6768
+ {
6769
+ style: fieldStyle,
6770
+ rows: 5,
6771
+ value: state.ucanStoreBootstrap.rootDelegationProof,
6772
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6773
+ "rootDelegationProof",
6774
+ event.currentTarget.value
6775
+ ),
6776
+ placeholder: "Root delegation proof"
6777
+ }
6778
+ ),
6779
+ /* @__PURE__ */ jsx(
6780
+ "textarea",
6781
+ {
6782
+ style: fieldStyle,
6783
+ rows: 4,
6784
+ value: state.ucanStoreBootstrap.allowedCapabilities,
6785
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6786
+ "allowedCapabilities",
6787
+ event.currentTarget.value
6788
+ ),
6789
+ placeholder: "Allowed capabilities, one per line or comma-separated\nspace/blob/add\nspace/blob/list"
6790
+ }
6791
+ ),
6792
+ /* @__PURE__ */ jsxs(
6793
+ "div",
6794
+ {
6795
+ style: {
6796
+ display: "grid",
6797
+ gap: "0.75rem",
6798
+ gridTemplateColumns: "repeat(auto-fit, minmax(10rem, 1fr))"
6799
+ },
6800
+ children: [
6801
+ /* @__PURE__ */ jsx(
6802
+ "input",
6803
+ {
6804
+ style: fieldStyle,
6805
+ value: state.ucanStoreBootstrap.defaultUserDelegationExpiration,
6806
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6807
+ "defaultUserDelegationExpiration",
6808
+ event.currentTarget.value
6809
+ ),
6810
+ placeholder: "Default expiration (seconds)"
6811
+ }
6812
+ ),
6813
+ /* @__PURE__ */ jsx(
6814
+ "input",
6815
+ {
6816
+ style: fieldStyle,
6817
+ value: state.ucanStoreBootstrap.maxUserDelegationExpiration,
6818
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6819
+ "maxUserDelegationExpiration",
6820
+ event.currentTarget.value
6821
+ ),
6822
+ placeholder: "Max expiration (seconds)"
6823
+ }
6824
+ )
6825
+ ]
6826
+ }
6827
+ ),
6828
+ /* @__PURE__ */ jsx(
6829
+ "input",
6830
+ {
6831
+ style: fieldStyle,
6832
+ value: state.ucanStoreBootstrap.pwaOrigin,
6833
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6834
+ "pwaOrigin",
6835
+ event.currentTarget.value
6836
+ ),
6837
+ placeholder: "PWA origin override (optional, defaults to current page origin)"
6838
+ }
6839
+ ),
6840
+ /* @__PURE__ */ jsx(
6841
+ "input",
6842
+ {
6843
+ style: fieldStyle,
6844
+ value: state.ucanStoreBootstrap.serviceOrigin,
6845
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6846
+ "serviceOrigin",
6847
+ event.currentTarget.value
6848
+ ),
6849
+ placeholder: "Service origin override (optional, defaults to runtime proxy URL)"
6850
+ }
6851
+ ),
6852
+ /* @__PURE__ */ jsx(
6853
+ "div",
6854
+ {
6855
+ style: {
6856
+ color: "#9fb2ca",
6857
+ fontSize: "0.74rem",
6858
+ lineHeight: 1.35,
6859
+ marginTop: "-0.15rem"
6860
+ },
6861
+ children: "Operator address comes from the connected MetaMask account. If you leave service origin empty, `relay-button` will use the runtime proxy URL during guest configuration."
6862
+ }
6863
+ )
6864
+ ]
6865
+ }
6866
+ )
6867
+ ] }) : null
6221
6868
  ]
6222
6869
  }
6223
6870
  )
@@ -6253,7 +6900,7 @@ function SponsorRelayFab(props) {
6253
6900
  onClick: () => void (state.wallet.connected ? controller.deploy() : controller.connectWallet()),
6254
6901
  disabled: state.wallet.connected ? state.busy.deploying || state.rootfsHealth.tone !== "ok" : state.busy.connectingWallet,
6255
6902
  style: { ...primaryButtonStyle, width: "100%", marginTop: "1rem" },
6256
- children: state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : "Deploy Relay" : "Connect MetaMask"
6903
+ children: deployCallToAction
6257
6904
  }
6258
6905
  ),
6259
6906
  state.lastDeploymentHash ? /* @__PURE__ */ jsxs("p", { children: [
@@ -6280,8 +6927,8 @@ function SponsorRelayFab(props) {
6280
6927
  flexWrap: "wrap"
6281
6928
  },
6282
6929
  children: [
6283
- (entry.instance.content?.metadata?.name ?? "relay") + " \xB7 " + shortHash(entry.instance.item_hash),
6284
- confirmedRegistration ? /* @__PURE__ */ jsxs(
6930
+ (entry.instance.content?.metadata?.name ?? deploymentInstanceFallbackLabel(state)) + " \xB7 " + shortHash(entry.instance.item_hash),
6931
+ bootstrapUiEnabled && confirmedRegistration ? /* @__PURE__ */ jsxs(
6285
6932
  "span",
6286
6933
  {
6287
6934
  style: {
@@ -6329,7 +6976,7 @@ function SponsorRelayFab(props) {
6329
6976
  "Status: ",
6330
6977
  entry.details.messageStatus
6331
6978
  ] }),
6332
- confirmedRegistration ? /* @__PURE__ */ jsxs("div", { children: [
6979
+ bootstrapUiEnabled && confirmedRegistration ? /* @__PURE__ */ jsxs("div", { children: [
6333
6980
  "Bootstrap registration:",
6334
6981
  " ",
6335
6982
  shortHash(
@@ -6381,7 +7028,7 @@ function SponsorRelayFab(props) {
6381
7028
  )
6382
7029
  ] }, entry.instance.item_hash);
6383
7030
  }),
6384
- orphanRegistrations.length > 0 ? /* @__PURE__ */ jsxs(
7031
+ bootstrapUiEnabled && orphanRegistrations.length > 0 ? /* @__PURE__ */ jsxs(
6385
7032
  "div",
6386
7033
  {
6387
7034
  style: {