@seams/wallet-server 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "seams_wallet_server_artifact_manifest_v1",
3
3
  "package": {
4
4
  "name": "@seams/wallet-server",
5
- "version": "0.6.0"
5
+ "version": "0.6.1"
6
6
  },
7
7
  "workers": {
8
8
  "router": {
@@ -12,6 +12,14 @@ if (!existsSync(binary)) {
12
12
  }
13
13
  // Package managers can normalize executable bits on bundled native assets.
14
14
  if ((statSync(binary).mode & 0o111) === 0) chmodSync(binary, 0o755);
15
- const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' });
15
+ const result = spawnSync(binary, process.argv.slice(2), {
16
+ stdio: ['inherit', 'pipe', 'inherit'],
17
+ encoding: 'utf8',
18
+ });
16
19
  if (result.error) throw result.error;
20
+ if (result.status === 0 && process.stdout.isTTY) {
21
+ console.log('Local role identity ready.');
22
+ } else if (result.stdout) {
23
+ process.stdout.write(result.stdout);
24
+ }
17
25
  process.exitCode = result.status ?? 1;
@@ -22,6 +22,7 @@ const workersReadyPath = path.join(localRoot, '.runtime', 'role-workers.ready');
22
22
  // Separate local stacks reuse Worker names, so each needs its own discovery registry.
23
23
  const workerEnv = {
24
24
  ...process.env,
25
+ WRANGLER_LOG: process.env.WRANGLER_LOG || 'warn',
25
26
  WRANGLER_REGISTRY_PATH: path.join(localRoot, '.local', 'worker-registry'),
26
27
  };
27
28
  const children = [];
@@ -46,14 +47,18 @@ async function main() {
46
47
  startWorkers(runtime);
47
48
  await waitForWorkers(runtime);
48
49
  writeFileSync(workersReadyPath, 'ready\n');
49
- console.log(
50
- JSON.stringify({
51
- kind: 'wallet_role_workers_ready_v1',
52
- mpcRouterUrl: runtime.mpcRouterUrl,
53
- ceremonyPrivateJwkPath,
54
- workers: runtime.configs.map(describeWorker),
55
- }),
56
- );
50
+ if (process.stdout.isTTY) {
51
+ console.log('Local Wallet role Workers ready.');
52
+ } else {
53
+ console.log(
54
+ JSON.stringify({
55
+ kind: 'wallet_role_workers_ready_v1',
56
+ mpcRouterUrl: runtime.mpcRouterUrl,
57
+ ceremonyPrivateJwkPath,
58
+ workers: runtime.configs.map(describeWorker),
59
+ }),
60
+ );
61
+ }
57
62
  await waitUntilStopped();
58
63
  }
59
64
 
@@ -181,8 +186,9 @@ function applyPrivateD1Migrations(runtime) {
181
186
  config.configPath,
182
187
  ],
183
188
  repoRoot,
184
- { ...process.env, CI: 'true' },
189
+ { ...workerEnv, CI: 'true' },
185
190
  );
191
+ console.log(`Local database ready: ${config.role}`);
186
192
  }
187
193
  }
188
194
 
@@ -9,13 +9,26 @@ import path from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
10
 
11
11
  import { prepareLocalHostedWalletGatewayConfig } from './prepare-local-runtime-config.mjs';
12
+ import { printLocalServiceTable } from '../../../scripts/print-local-service-table.mjs';
12
13
 
13
14
  const repoRoot = process.cwd();
14
15
  const gatewayUrl = process.env.SEAMS_INTENDED_ROUTER_URL || 'http://127.0.0.1:4100';
15
- const routerUrl = `http://127.0.0.1:${4102 + Number(process.env.SEAMS_LOCAL_PORT_OFFSET || 0)}`;
16
+ const localPortOffset = Number(process.env.SEAMS_LOCAL_PORT_OFFSET || 0);
17
+ const routerUrl = `http://127.0.0.1:${4102 + localPortOffset}`;
18
+ const roleServices = [
19
+ { name: 'mpc-router', port: 4102 },
20
+ { name: 'deriver-a', port: 4103 },
21
+ { name: 'deriver-b', port: 4104 },
22
+ { name: 'signing-worker', port: 4105 },
23
+ { name: 'tenant-root-control-plane', port: 4106 },
24
+ ];
16
25
  const options = parseArguments(process.argv.slice(2));
17
26
  const localRoot = path.resolve(
18
- options.root || path.join(tmpdir(), `${path.basename(repoRoot)}-wallet-system`),
27
+ options.root ||
28
+ path.join(
29
+ tmpdir(),
30
+ `${path.basename(repoRoot)}-wallet-system-${randomBytes(8).toString('hex')}`,
31
+ ),
19
32
  );
20
33
  const gatewayStateRoot = path.join(localRoot, '.local', 'cloudflare-state', 'wallet-gateway');
21
34
  const ceremonyPrivateJwkPath = path.join(
@@ -36,6 +49,7 @@ const identity = Object.freeze({
36
49
  // Separate local stacks reuse Worker names, so each needs its own discovery registry.
37
50
  const workerEnv = {
38
51
  ...process.env,
52
+ WRANGLER_LOG: process.env.WRANGLER_LOG || 'warn',
39
53
  WRANGLER_REGISTRY_PATH: path.join(localRoot, '.local', 'worker-registry'),
40
54
  };
41
55
  const children = [];
@@ -51,10 +65,12 @@ async function main() {
51
65
  installSignalHandlers();
52
66
  mkdirSync(localRoot, { recursive: true });
53
67
  rmSync(workersReadyPath, { force: true });
68
+ console.log('Starting local Wallet role Workers...');
54
69
  startRoleWorkers();
55
70
  await waitForHttp(`${routerUrl}/.well-known/router-ab/keyset`, 120_000, true);
56
71
  await waitForFile(workersReadyPath, 120_000);
57
72
  await waitForFile(ceremonyPrivateJwkPath, 10_000);
73
+ console.log('Provisioning local tenant root...');
58
74
  const tenantRoot = bootstrapTenantRoot();
59
75
  const deployment = localDeployment(tenantRoot);
60
76
  const runtime = prepareLocalHostedWalletGatewayConfig({
@@ -67,23 +83,35 @@ async function main() {
67
83
  deployment,
68
84
  });
69
85
  applySignerMigrations(runtime);
86
+ console.log('Starting local Wallet Gateway...');
70
87
  startGateway(runtime);
71
88
  await waitForHttp(`${runtime.gatewayUrl}/readyz`, 120_000, true);
72
- console.log(
73
- JSON.stringify({
74
- kind: 'wallet_local_system_ready_v1',
75
- gatewayUrl: runtime.gatewayUrl,
76
- appOrigin: options.appOrigin,
77
- walletOrigin: options.walletOrigin,
78
- projectEnvironmentId: identity.environmentId,
79
- publishableKey: deployment.credential.publishableKey,
80
- signingWorkerId: 'local-signing-worker',
81
- root: localRoot,
82
- }),
83
- );
89
+ const ready = {
90
+ kind: 'wallet_local_system_ready_v1',
91
+ gatewayUrl: runtime.gatewayUrl,
92
+ appOrigin: options.appOrigin,
93
+ walletOrigin: options.walletOrigin,
94
+ projectEnvironmentId: identity.environmentId,
95
+ publishableKey: deployment.credential.publishableKey,
96
+ signingWorkerId: 'local-signing-worker',
97
+ root: localRoot,
98
+ };
99
+ if (process.stdout.isTTY) printServiceTable(ready);
100
+ else console.log(JSON.stringify(ready));
84
101
  await waitUntilStopped();
85
102
  }
86
103
 
104
+ function printServiceTable(ready) {
105
+ const services = [];
106
+ for (const role of roleServices) {
107
+ services.push({ name: role.name, url: `http://127.0.0.1:${role.port + localPortOffset}` });
108
+ }
109
+ services.push({ name: 'wallet-gateway', url: ready.gatewayUrl });
110
+ printLocalServiceTable('Local Wallet services ready', services);
111
+ console.log(`Site: ${ready.appOrigin} (start pnpm site in another terminal)`);
112
+ console.log(`Runtime state: ${ready.root}`);
113
+ }
114
+
87
115
  function parseArguments(args) {
88
116
  const parsed = {
89
117
  help: false,
@@ -254,8 +282,9 @@ function applySignerMigrations(runtime) {
254
282
  '--config',
255
283
  runtime.configPath,
256
284
  ],
257
- { ...process.env, CI: 'true' },
285
+ { ...workerEnv, CI: 'true' },
258
286
  );
287
+ console.log('Local database ready: wallet-gateway');
259
288
  }
260
289
 
261
290
  function startGateway(runtime) {
@@ -1233,6 +1233,7 @@ function createD1WalletRegistrationRouteService(assembly) {
1233
1233
  readActiveEmailOtpEnrollment: assembly.emailOtpRecoveryService.readActiveEmailOtpEnrollment.bind(assembly.emailOtpRecoveryService),
1234
1234
  setupWalletRegistration: assembly.walletRegistrations.setupWalletRegistration.bind(assembly.walletRegistrations),
1235
1235
  respondWalletRegistration: assembly.walletRegistrations.respondWalletRegistration.bind(assembly.walletRegistrations),
1236
+ authorizeWalletRegistrationNearAdmission: assembly.walletRegistrations.authorizeWalletRegistrationNearAdmission.bind(assembly.walletRegistrations),
1236
1237
  activateWalletRegistration: assembly.walletRegistrations.activateWalletRegistration.bind(assembly.walletRegistrations),
1237
1238
  completeWalletRegistrationNearProvisioning: assembly.walletRegistrations.completeWalletRegistrationNearProvisioning.bind(assembly.walletRegistrations),
1238
1239
  refreshEd25519YaoWalletSession: assembly.walletRegistrations.refreshEd25519YaoWalletSession.bind(assembly.walletRegistrations),
@@ -933,6 +933,16 @@ function mergeRouterServerTiming(target, header) {
933
933
  }
934
934
  }
935
935
  }
936
+ function recordRegistrationServerTiming(sink, name, startedAtMs) {
937
+ sink.push([name, Math.max(0, Date.now() - startedAtMs)]);
938
+ }
939
+ async function recordRegistrationPromiseTiming(args) {
940
+ try {
941
+ return await args.promise;
942
+ } finally {
943
+ recordRegistrationServerTiming(args.sink, args.name, args.startedAtMs);
944
+ }
945
+ }
936
946
  var CloudflareD1WalletRegistrationService = class {
937
947
  constructor(input) {
938
948
  this.authorizationService = input.authorizationService;
@@ -1783,8 +1793,99 @@ var CloudflareD1WalletRegistrationService = class {
1783
1793
  return walletRegistrationSetupError("internal", errorMessage(error) || "Failed to set up wallet registration");
1784
1794
  }
1785
1795
  }
1786
- async respondWalletRegistration(input, traceContext) {
1787
- const serverTiming = [];
1796
+ async authorizeWalletRegistrationNearAdmission(input) {
1797
+ try {
1798
+ const snapshot = await this.getRegistrationCeremonyIntentStore().getCeremonySnapshot(input.registrationCeremonyId);
1799
+ if (!snapshot) return {
1800
+ ok: false,
1801
+ code: "not_found",
1802
+ message: "registration ceremony not found"
1803
+ };
1804
+ const ceremony = snapshot.ceremony;
1805
+ if (ceremony.signerState.kind !== "signer_set_registration") return {
1806
+ ok: false,
1807
+ code: "invalid_state",
1808
+ message: "signer-set registration state is required"
1809
+ };
1810
+ const branches = registrationSignerBranchesFromPlan(ceremony.signerPlan);
1811
+ if (!findStoredWalletRegistrationEvmFamilyEcdsaBranch(ceremony.signerState) || !branches.nearEd25519) return {
1812
+ ok: false,
1813
+ code: "invalid_state",
1814
+ message: "mixed registration state is required"
1815
+ };
1816
+ const expectedOrigin = normalizeOptionalTrimmedString(ceremony.expectedOrigin) || "";
1817
+ const setupDigestB64u = await computeWalletRegistrationSetupDigestB64u({
1818
+ registrationCeremonyId: ceremony.registrationCeremonyId,
1819
+ intent: ceremony.intent,
1820
+ intentDigestB64u: ceremony.digestB64u,
1821
+ orgId: ceremony.orgId,
1822
+ signingRootId: normalizeOptionalTrimmedString(ceremony.signingRootId) || "",
1823
+ signingRootVersion: normalizeOptionalTrimmedString(ceremony.signingRootVersion) || "",
1824
+ expectedOrigin
1825
+ });
1826
+ const verifiedSetup = await verifySignedWalletRegistrationSetup(input.verifier, input.signedSetup, {
1827
+ registrationCeremonyId: ceremony.registrationCeremonyId,
1828
+ setupDigestB64u,
1829
+ nowMs: Date.now()
1830
+ });
1831
+ if (!verifiedSetup.ok) return {
1832
+ ok: false,
1833
+ code: verifiedSetup.code,
1834
+ message: verifiedSetup.message
1835
+ };
1836
+ const registrationBearerToken = normalizeOptionalTrimmedString(input.signedSetup);
1837
+ if (!registrationBearerToken) return {
1838
+ ok: false,
1839
+ code: "invalid_body",
1840
+ message: "signedSetup is required"
1841
+ };
1842
+ const verifiedAuthority = await this.walletAuthMethods.verifyRegistrationAuthorityForIntent({
1843
+ orgId: ceremony.orgId,
1844
+ authority: input.authority,
1845
+ expectedDigestB64u: ceremony.digestB64u,
1846
+ expectedOrigin,
1847
+ intent: ceremony.intent,
1848
+ verificationOperationId: ceremony.registrationCeremonyId,
1849
+ verificationReceiptExpiresAtMs: ceremony.expiresAtMs,
1850
+ ...input.userAgent ? { userAgent: input.userAgent } : {}
1851
+ });
1852
+ if (!verifiedAuthority.ok) return {
1853
+ ok: false,
1854
+ code: verifiedAuthority.code,
1855
+ message: verifiedAuthority.message
1856
+ };
1857
+ if (verifiedAuthority.authority.walletId !== ceremony.intent.walletId) return {
1858
+ ok: false,
1859
+ code: "scope_mismatch",
1860
+ message: "registration authority walletId does not match the ceremony"
1861
+ };
1862
+ const admitted = await this.authorizeRespondEd25519Branch({
1863
+ ceremony,
1864
+ branch: branches.nearEd25519,
1865
+ authority: verifiedAuthority.authority,
1866
+ registrationBearerToken
1867
+ });
1868
+ if (!admitted.ok) return admitted;
1869
+ if (!admitted.deferred) return {
1870
+ ok: false,
1871
+ code: "internal",
1872
+ message: "NEAR admission produced no work"
1873
+ };
1874
+ return {
1875
+ ok: true,
1876
+ registrationCeremonyId: ceremony.registrationCeremonyId,
1877
+ ed25519: admitted.deferred
1878
+ };
1879
+ } catch (error) {
1880
+ return {
1881
+ ok: false,
1882
+ code: "internal",
1883
+ message: errorMessage(error) || "Failed to authorize NEAR registration admission"
1884
+ };
1885
+ }
1886
+ }
1887
+ async respondWalletRegistration(input, traceContext, timingSink) {
1888
+ const serverTiming = timingSink ?? [];
1788
1889
  const totalStartedAtMs = Date.now();
1789
1890
  const mark = (name, startedAtMs) => {
1790
1891
  serverTiming.push([name, Math.max(0, Date.now() - startedAtMs)]);
@@ -1793,7 +1894,7 @@ var CloudflareD1WalletRegistrationService = class {
1793
1894
  const store = this.getRegistrationCeremonyIntentStore();
1794
1895
  const loadStartedAtMs = Date.now();
1795
1896
  const snapshot = await store.getCeremonySnapshot(input.registrationCeremonyId);
1796
- mark("respond_d1_load", loadStartedAtMs);
1897
+ mark("ecdsa_respond_d1_load", loadStartedAtMs);
1797
1898
  if (!snapshot) return {
1798
1899
  ok: false,
1799
1900
  code: "not_found",
@@ -1952,7 +2053,7 @@ var CloudflareD1WalletRegistrationService = class {
1952
2053
  authority,
1953
2054
  registrationBearerToken
1954
2055
  })]);
1955
- mark("respond_router", routerStartedAtMs);
2056
+ mark("ecdsa_respond_router", routerStartedAtMs);
1956
2057
  if (!strictResult.ok) {
1957
2058
  if (!strictResult.retryable) await store.cancelTerminalCeremony({
1958
2059
  registrationCeremonyId: ceremony.registrationCeremonyId,
@@ -2006,16 +2107,16 @@ var CloudflareD1WalletRegistrationService = class {
2006
2107
  const reconciled = await store.getCeremony(input.registrationCeremonyId);
2007
2108
  const reconciledBranch = reconciled?.signerState.kind === "signer_set_registration" ? findStoredWalletRegistrationEvmFamilyEcdsaBranch(reconciled.signerState) : null;
2008
2109
  if (!reconciled || reconciledBranch?.kind !== "evm_family_ecdsa_pending_activation" || !sameRouterAbEcdsaRegistrationRequest(reconciledBranch.registrationRequest, strictRegistration)) throw error;
2009
- mark("respond_d1_commit", commitStartedAtMs);
2010
- mark("respond_total", totalStartedAtMs);
2110
+ mark("ecdsa_respond_d1_commit", commitStartedAtMs);
2111
+ mark("ecdsa_respond_total", totalStartedAtMs);
2011
2112
  return walletRegistrationRespondResult({
2012
2113
  ceremony: reconciled,
2013
2114
  strictResult: reconciledBranch.publicResponse,
2014
2115
  ed25519: storedRespondEd25519DeferredWork(reconciled.signerState)
2015
2116
  });
2016
2117
  }
2017
- mark("respond_d1_commit", commitStartedAtMs);
2018
- mark("respond_total", totalStartedAtMs);
2118
+ mark("ecdsa_respond_d1_commit", commitStartedAtMs);
2119
+ mark("ecdsa_respond_total", totalStartedAtMs);
2019
2120
  return walletRegistrationRespondResult({
2020
2121
  ceremony: next,
2021
2122
  strictResult: strictResult.value.publicResponse,
@@ -2072,7 +2173,10 @@ var CloudflareD1WalletRegistrationService = class {
2072
2173
  });
2073
2174
  }
2074
2175
  async authorizeNearRegistrationContinuation(input) {
2075
- const installation = await this.readCommittedRegistrationInstallation(input.lifecycleId);
2176
+ return (await this.readNearRegistrationContinuationContext(input))?.verified ?? null;
2177
+ }
2178
+ async readNearRegistrationContinuationContext(input, installed) {
2179
+ const installation = installed ?? await this.readCommittedRegistrationInstallation(input.lifecycleId);
2076
2180
  if (!installation || !isRegistrationEcdsaReadyReceipt(installation.receipt)) return null;
2077
2181
  const nowMs = Date.now();
2078
2182
  const issued = await this.authorizationService.readLiveWalletSessionAuthorizationProjectionByCredential({
@@ -2086,9 +2190,13 @@ var CloudflareD1WalletRegistrationService = class {
2086
2190
  const originalSubjects = installation.receipt.committed.session.walletSession.capabilitySubjects;
2087
2191
  for (const subject of originalSubjects) if (!issued.session.capabilitySubjects.some(sameRegistrationContinuationSubject.bind(void 0, subject))) return null;
2088
2192
  return {
2089
- credential: input.credential,
2090
- admissionRequest: installation.projection.nearEd25519.admissionRequest,
2091
- expiresAtMs: Math.min(issued.session.expiresAtMs, nowMs + 6e4)
2193
+ verified: {
2194
+ credential: input.credential,
2195
+ admissionRequest: installation.projection.nearEd25519.admissionRequest,
2196
+ expiresAtMs: Math.min(issued.session.expiresAtMs, nowMs + 6e4)
2197
+ },
2198
+ installation,
2199
+ authorization: issued
2092
2200
  };
2093
2201
  }
2094
2202
  async readCommittedRegistrationInstallation(registrationCeremonyId) {
@@ -2125,10 +2233,10 @@ var CloudflareD1WalletRegistrationService = class {
2125
2233
  prepared: record.prepared
2126
2234
  };
2127
2235
  }
2128
- async prepareNearProvisioningOperation(registrationCeremonyId, allocated) {
2129
- const ceremony = await this.getRegistrationCeremonyIntentStore().getCeremony(registrationCeremonyId);
2236
+ async prepareNearProvisioningOperation(registrationCeremonyId, allocated, installed) {
2237
+ const ceremony = installed ? null : await this.getRegistrationCeremonyIntentStore().getCeremony(registrationCeremonyId);
2130
2238
  if (!ceremony) {
2131
- const committed = await this.readCommittedRegistrationInstallation(registrationCeremonyId);
2239
+ const committed = installed ?? await this.readCommittedRegistrationInstallation(registrationCeremonyId);
2132
2240
  if (!committed) throw new Error("Registration ceremony is unavailable for identity preparation");
2133
2241
  if (allocated && (allocated.walletAuthorityId !== committed.prepared.walletAuthorityId || allocated.deviceId !== committed.prepared.deviceId || allocated.walletAuthMethodId !== committed.prepared.walletAuthMethodId)) throw new Error("registration operation identity does not match its committed installation");
2134
2242
  return committed.prepared;
@@ -2146,9 +2254,16 @@ var CloudflareD1WalletRegistrationService = class {
2146
2254
  };
2147
2255
  }
2148
2256
  async commitDeferredEd25519Signer(args, prepared) {
2149
- const ownerProofContext = await this.readRegistrationOwnerProofContext(args.input.registrationCeremonyId);
2257
+ const committedInstallation = args.authoritySource.kind === "committed_mixed" ? args.authoritySource.installation : null;
2258
+ if (committedInstallation && !isRegistrationEcdsaReadyReceipt(committedInstallation.receipt)) throw new Error("Committed mixed registration receipt is not ECDSA-ready");
2259
+ const ownerProofContext = committedInstallation ? {
2260
+ expectedOrigin: committedInstallation.receipt.expectedOrigin,
2261
+ runtimePolicyScope: registrationPreparedContextRuntimePolicyScope(committedInstallation.projection.preparedContext),
2262
+ expiresAtMs: committedInstallation.receipt.expiresAtMs
2263
+ } : await this.readRegistrationOwnerProofContext(args.input.registrationCeremonyId);
2150
2264
  if (!ownerProofContext) throw new Error("Registration owner proof context is unavailable");
2151
- const ceremony = await this.getRegistrationCeremonyIntentStore().getCeremony(args.input.registrationCeremonyId);
2265
+ if (!ownerProofContext.runtimePolicyScope) throw new Error("Committed registration is missing its runtime policy scope");
2266
+ const ceremony = committedInstallation ? null : await this.getRegistrationCeremonyIntentStore().getCeremony(args.input.registrationCeremonyId);
2152
2267
  let recovery = null;
2153
2268
  let registrationAuthority;
2154
2269
  let sessionPredecessor;
@@ -2158,7 +2273,7 @@ var CloudflareD1WalletRegistrationService = class {
2158
2273
  registrationAuthority = authority;
2159
2274
  sessionPredecessor = { kind: "ed25519_only" };
2160
2275
  } else {
2161
- const committed = await this.readCommittedRegistrationInstallation(args.input.registrationCeremonyId);
2276
+ const committed = committedInstallation ?? await this.readCommittedRegistrationInstallation(args.input.registrationCeremonyId);
2162
2277
  if (!committed) throw new Error("Committed registration installation is unavailable");
2163
2278
  recovery = registrationFinalizeRecoveryFromCommittedInstallation({
2164
2279
  setupClaims: args.setupClaims,
@@ -2171,14 +2286,7 @@ var CloudflareD1WalletRegistrationService = class {
2171
2286
  if (!isRegistrationEcdsaReadyReceipt(activationReceipt)) throw new Error("Committed mixed registration receipt is not ECDSA-ready");
2172
2287
  if (activationReceipt.committed.session.tokens.kind !== "evm_family_ecdsa") throw new Error("Committed mixed registration is missing its ECDSA session projection");
2173
2288
  if (args.input.authorization.kind === "wallet_session") {
2174
- const authorization = await this.authorizeNearRegistrationContinuation({
2175
- lifecycleId: args.input.registrationCeremonyId,
2176
- credential: args.input.authorization.credential
2177
- }) ? await this.authorizationService.readLiveWalletSessionAuthorizationProjectionByCredential({
2178
- tenantId: this.authorizationTenantId,
2179
- token: args.input.authorization.credential,
2180
- nowMs: Date.now()
2181
- }) : null;
2289
+ const authorization = args.authoritySource.kind === "committed_mixed" ? args.authoritySource.authorization : null;
2182
2290
  if (!authorization) throw new Error("NEAR continuation Wallet Session is no longer active");
2183
2291
  sessionPredecessor = {
2184
2292
  kind: "resumed_mixed",
@@ -2190,7 +2298,7 @@ var CloudflareD1WalletRegistrationService = class {
2190
2298
  ecdsa: activationReceipt.committed.session.tokens.ecdsa
2191
2299
  };
2192
2300
  }
2193
- const effectivePrepared = await this.prepareNearProvisioningOperation(args.input.registrationCeremonyId, prepared);
2301
+ const effectivePrepared = await this.prepareNearProvisioningOperation(args.input.registrationCeremonyId, prepared, committedInstallation ?? void 0);
2194
2302
  const committed = await this.executeWalletRegistrationFinalize({
2195
2303
  kind: "near_ed25519",
2196
2304
  registrationCeremonyId: args.input.registrationCeremonyId,
@@ -2358,16 +2466,22 @@ var CloudflareD1WalletRegistrationService = class {
2358
2466
  * be destroyed because its signer ceremony needs another attempt.
2359
2467
  */
2360
2468
  async verifyNearProvisioningAuthority(input) {
2361
- if (input.authorization.kind === "registration_grant") return await verifyWalletRegistrationSetupClaims(input.verifier, input.signedSetup, {
2362
- registrationCeremonyId: input.registrationCeremonyId,
2363
- nowMs: Date.now()
2364
- });
2365
- const authorized = await this.authorizeNearRegistrationContinuation({
2469
+ if (input.authorization.kind === "registration_grant") {
2470
+ const verified = await verifyWalletRegistrationSetupClaims(input.verifier, input.signedSetup, {
2471
+ registrationCeremonyId: input.registrationCeremonyId,
2472
+ nowMs: Date.now()
2473
+ });
2474
+ return verified.ok ? {
2475
+ ...verified,
2476
+ source: { kind: "registration_grant" }
2477
+ } : verified;
2478
+ }
2479
+ const installation = await this.readCommittedRegistrationInstallation(input.registrationCeremonyId);
2480
+ const continuation = installation ? await this.readNearRegistrationContinuationContext({
2366
2481
  lifecycleId: input.registrationCeremonyId,
2367
2482
  credential: input.authorization.credential
2368
- });
2369
- const installation = await this.readCommittedRegistrationInstallation(input.registrationCeremonyId);
2370
- if (!authorized || !installation || typeof input.signedSetup !== "string") return {
2483
+ }, installation) : null;
2484
+ if (!continuation || typeof input.signedSetup !== "string") return {
2371
2485
  ok: false,
2372
2486
  code: "invalid_grant",
2373
2487
  message: "NEAR continuation authority is unavailable"
@@ -2381,24 +2495,34 @@ var CloudflareD1WalletRegistrationService = class {
2381
2495
  };
2382
2496
  registrationFinalizeRecoveryFromCommittedInstallation({
2383
2497
  setupClaims: claims,
2384
- projection: installation.projection
2498
+ projection: continuation.installation.projection
2385
2499
  });
2386
2500
  return {
2387
2501
  ok: true,
2388
2502
  claims: {
2389
2503
  ...claims,
2390
- expiresAtMs: authorized.expiresAtMs
2504
+ expiresAtMs: continuation.verified.expiresAtMs
2505
+ },
2506
+ source: {
2507
+ kind: "committed_mixed",
2508
+ installation: continuation.installation,
2509
+ authorization: continuation.authorization
2391
2510
  }
2392
2511
  };
2393
2512
  }
2394
- async completeWalletRegistrationNearProvisioning(input) {
2513
+ async completeWalletRegistrationNearProvisioning(input, timingSink) {
2514
+ const serverTiming = timingSink ?? [];
2515
+ const totalStartedAtMs = Date.now();
2395
2516
  try {
2517
+ const authorityStartedAtMs = Date.now();
2396
2518
  const verified = await this.verifyNearProvisioningAuthority(input);
2519
+ recordRegistrationServerTiming(serverTiming, "near_finalize_authority", authorityStartedAtMs);
2397
2520
  if (!verified.ok) return {
2398
2521
  ok: false,
2399
2522
  code: verified.code,
2400
2523
  message: verified.message
2401
2524
  };
2525
+ const fingerprintStartedAtMs = Date.now();
2402
2526
  const requestFingerprint = base64UrlEncode(await sha256BytesUtf8(alphabetizeStringify({
2403
2527
  version: "wallet_registration_near_provisioning_digest_v1",
2404
2528
  registrationCeremonyId: input.registrationCeremonyId,
@@ -2407,6 +2531,8 @@ var CloudflareD1WalletRegistrationService = class {
2407
2531
  ed25519: input.ed25519,
2408
2532
  emailOtpEnrollment: input.emailOtpEnrollment
2409
2533
  })));
2534
+ recordRegistrationServerTiming(serverTiming, "near_finalize_fingerprint", fingerprintStartedAtMs);
2535
+ const sideEffectStartedAtMs = Date.now();
2410
2536
  const run = await runRouterAbEd25519YaoRegistrationSideEffectV2(this.nearProvisioningSideEffects, {
2411
2537
  kind: "prepared_resumable",
2412
2538
  operation: "near_provisioning",
@@ -2414,11 +2540,12 @@ var CloudflareD1WalletRegistrationService = class {
2414
2540
  requestFingerprint,
2415
2541
  resumeAfterMs: D1_WALLET_REGISTRATION_OPERATION_RESUME_AFTER_MS,
2416
2542
  nowMs: Date.now,
2417
- prepare: async () => await this.prepareNearProvisioningOperation(input.registrationCeremonyId),
2543
+ prepare: async () => await this.prepareNearProvisioningOperation(input.registrationCeremonyId, void 0, verified.source.kind === "committed_mixed" ? verified.source.installation : void 0),
2418
2544
  derivePreparedArtifactFingerprint: async (prepared) => base64UrlEncode(await sha256BytesUtf8(alphabetizeStringify(prepared))),
2419
2545
  execute: this.commitDeferredEd25519Signer.bind(this, {
2420
2546
  input,
2421
- setupClaims: verified.claims
2547
+ setupClaims: verified.claims,
2548
+ authoritySource: verified.source
2422
2549
  }),
2423
2550
  projectReceipt: (execution) => projectWalletRegistrationSessionCommitReceiptV2({
2424
2551
  operation: "near_provisioning",
@@ -2428,6 +2555,7 @@ var CloudflareD1WalletRegistrationService = class {
2428
2555
  }),
2429
2556
  replay: this.replayWalletRegistrationNearProvisioningCommit.bind(this)
2430
2557
  });
2558
+ recordRegistrationServerTiming(serverTiming, "near_finalize_side_effect", sideEffectStartedAtMs);
2431
2559
  switch (run.kind) {
2432
2560
  case "executed":
2433
2561
  case "exact_replay": break;
@@ -2471,18 +2599,30 @@ var CloudflareD1WalletRegistrationService = class {
2471
2599
  message: "NEAR provisioning did not establish a Wallet Session",
2472
2600
  nearProvisioning: { status: "near_failed_retryable" }
2473
2601
  };
2474
- await cleanupFinalizedRegistrationCeremony({
2475
- store: this.getRegistrationCeremonyIntentStore(),
2476
- registrationCeremonyId: input.registrationCeremonyId
2602
+ const cleanup = recordRegistrationPromiseTiming({
2603
+ sink: serverTiming,
2604
+ name: "near_finalize_cleanup",
2605
+ startedAtMs: Date.now(),
2606
+ promise: cleanupFinalizedRegistrationCeremony({
2607
+ store: this.getRegistrationCeremonyIntentStore(),
2608
+ registrationCeremonyId: input.registrationCeremonyId
2609
+ })
2477
2610
  });
2478
2611
  if (input.authorization.kind === "wallet_session") {
2479
- const current = await this.authorizationService.readLiveWalletSessionAuthorizationProjectionByCredential({
2480
- tenantId: this.authorizationTenantId,
2481
- token: input.authorization.credential,
2482
- nowMs: Date.now()
2483
- });
2612
+ const projectionStartedAtMs = Date.now();
2613
+ const [current] = await Promise.all([recordRegistrationPromiseTiming({
2614
+ sink: serverTiming,
2615
+ name: "near_finalize_session_projection",
2616
+ startedAtMs: projectionStartedAtMs,
2617
+ promise: this.authorizationService.readLiveWalletSessionAuthorizationProjectionByCredential({
2618
+ tenantId: this.authorizationTenantId,
2619
+ token: input.authorization.credential,
2620
+ nowMs: Date.now()
2621
+ })
2622
+ }), cleanup]);
2484
2623
  const tokens = committed.registrationEstablishedSession.session.tokens;
2485
2624
  if (!current || tokens.kind !== "near_ed25519_and_evm_family_ecdsa") throw new Error("NEAR continuation Wallet Session is no longer active");
2625
+ recordRegistrationServerTiming(serverTiming, "near_finalize_total", totalStartedAtMs);
2486
2626
  return {
2487
2627
  ...committed,
2488
2628
  registrationEstablishedSession: {
@@ -2493,11 +2633,14 @@ var CloudflareD1WalletRegistrationService = class {
2493
2633
  nearProvisioning: { status: "near_ready" }
2494
2634
  };
2495
2635
  }
2636
+ await cleanup;
2637
+ recordRegistrationServerTiming(serverTiming, "near_finalize_total", totalStartedAtMs);
2496
2638
  return {
2497
2639
  ...committed,
2498
2640
  nearProvisioning: { status: "near_ready" }
2499
2641
  };
2500
2642
  } catch (error) {
2643
+ recordRegistrationServerTiming(serverTiming, "near_finalize_total", totalStartedAtMs);
2501
2644
  return {
2502
2645
  ok: false,
2503
2646
  code: "internal",
@@ -2791,7 +2934,7 @@ var CloudflareD1WalletRegistrationService = class {
2791
2934
  * before execution; an ambiguous outcome after the custody call stays
2792
2935
  * `uncertain` rather than being guessed either way.
2793
2936
  */
2794
- async activateWalletRegistration(input, traceContext) {
2937
+ async activateWalletRegistration(input, traceContext, serverTiming) {
2795
2938
  try {
2796
2939
  const verifiedSetup = await verifyWalletRegistrationSetupClaims(input.verifier, input.signedSetup, {
2797
2940
  registrationCeremonyId: input.registrationCeremonyId,
@@ -2830,7 +2973,8 @@ var CloudflareD1WalletRegistrationService = class {
2830
2973
  registrationCeremonyId: claims.registrationCeremonyId,
2831
2974
  idempotencyKey,
2832
2975
  input,
2833
- traceContext
2976
+ traceContext,
2977
+ serverTiming
2834
2978
  }),
2835
2979
  projectReceipt: (execution) => projectWalletRegistrationSessionCommitReceiptV2({
2836
2980
  operation: "registration_activate",
@@ -2930,6 +3074,7 @@ var CloudflareD1WalletRegistrationService = class {
2930
3074
  publicFacts: ecdsa.clientActivation
2931
3075
  }
2932
3076
  }, context.traceContext, context.idempotencyKey);
3077
+ if (activated.ok && activated.gatewayServerTiming) context.serverTiming?.push(...activated.gatewayServerTiming);
2933
3078
  if (!activated.ok) return unissuedRegistrationCommit(throwIfRouterAbEd25519YaoRetryableSideEffectFailureV1(activated));
2934
3079
  const keyHandle = activated.ecdsa.bootstrap?.keyHandle;
2935
3080
  if (!keyHandle) return unissuedRegistrationCommit({