@seams/wallet-server 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/artifact-manifest.json +28 -23
  2. package/cloudflare-router-ab/build/deriver-a/build-stamp.json +1 -1
  3. package/cloudflare-router-ab/build/deriver-a/index_bg.wasm +0 -0
  4. package/cloudflare-router-ab/build/deriver-b/build-stamp.json +1 -1
  5. package/cloudflare-router-ab/build/deriver-b/index_bg.wasm +0 -0
  6. package/cloudflare-router-ab/build/local-tools/darwin-arm64/router_ab_local_init +0 -0
  7. package/cloudflare-router-ab/build/local-tools/darwin-x64/router_ab_local_init +0 -0
  8. package/cloudflare-router-ab/build/local-tools/linux-x64/router_ab_local_init +0 -0
  9. package/cloudflare-router-ab/build/router/build-stamp.json +1 -1
  10. package/cloudflare-router-ab/build/router/index_bg.wasm +0 -0
  11. package/cloudflare-router-ab/build/signing-worker/build-stamp.json +1 -1
  12. package/cloudflare-router-ab/build/signing-worker/index_bg.wasm +0 -0
  13. package/cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json +1 -1
  14. package/cloudflare-router-ab/build/tenant-root-control-plane/index_bg.wasm +0 -0
  15. package/dist/esm/router/cloudflare/d1/auth/d1RouterApiAuthService.js +1 -1
  16. package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationCommitStore.js +46 -2
  17. package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationRead.js +7 -4
  18. package/dist/esm/router/cloudflare/d1/registration/d1WalletRegistrationService.js +25 -9
  19. package/dist/esm/router/cloudflare.js +1 -1
  20. package/dist/esm/router/domains/walletRegistration/walletRegistrationRoutes.js +1 -1
  21. package/dist/esm/router/express.js +1 -1
  22. package/dist/esm/wasm/shamir3pass_runtime/pkg/shamir3pass_runtime_bg.wasm +0 -0
  23. package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1WalletRegistrationCommitStore.d.ts +34 -2
  24. package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1WalletRegistrationRead.d.ts +4 -1
  25. package/dist/types/wallet-server/src/router/cloudflare/d1/registration/d1WalletRegistrationService.d.ts +1 -1
  26. package/dist/types/wallet-server/src/router/framework/authServicePort.d.ts +1 -1
  27. package/migrations/d1-signer/0078_registration_passkey_claims.sql +20 -0
  28. package/package.json +1 -1
@@ -2,17 +2,17 @@
2
2
  "schemaVersion": "seams_wallet_server_artifact_manifest_v1",
3
3
  "package": {
4
4
  "name": "@seams/wallet-server",
5
- "version": "0.9.1"
5
+ "version": "0.9.3"
6
6
  },
7
7
  "workers": {
8
8
  "router": {
9
9
  "entry": "cloudflare-router-ab/build/router/worker/shim.mjs",
10
- "fingerprint": "fa090144e437ce96ebd3e9f2e9976ad9419f31c644b7549f032c921d34f5551f",
10
+ "fingerprint": "9aa6adbab32e5358ec10422887961436e7fef549c9c740331e05f51aa6f095e7",
11
11
  "files": [
12
12
  {
13
13
  "path": "cloudflare-router-ab/build/router/build-stamp.json",
14
14
  "bytes": 119,
15
- "sha256": "4c874df0cfa4c298109b9e0d2743878362d9cc32f3dfc5f9a84d5eb619ca4e95"
15
+ "sha256": "e0dc70f76677f14be6bfe19283b8d1ac143e19f87f747f1b7b547301d6d1d732"
16
16
  },
17
17
  {
18
18
  "path": "cloudflare-router-ab/build/router/index.js",
@@ -21,8 +21,8 @@
21
21
  },
22
22
  {
23
23
  "path": "cloudflare-router-ab/build/router/index_bg.wasm",
24
- "bytes": 9585441,
25
- "sha256": "b93fafd76ff89a2a1308572ac0d12e68a158fe096aa62c60e3bd9a1e2aecaea3"
24
+ "bytes": 8093903,
25
+ "sha256": "07aec63f57a09dbcb3c6fa25dee6184ef45fa6e9168816b0d80a3b9259a4cd4d"
26
26
  },
27
27
  {
28
28
  "path": "cloudflare-router-ab/build/router/package.json",
@@ -38,12 +38,12 @@
38
38
  },
39
39
  "deriver-a": {
40
40
  "entry": "cloudflare-router-ab/build/deriver-a/worker/shim.mjs",
41
- "fingerprint": "f6d804b3eab07dee2e8eb85a6300dd4288fb606b52298728b2163bc36e0dfc08",
41
+ "fingerprint": "9d5e7b103536e7ebb8171d92441c5e9e3d0ffc56e7fa2c14ae366b4c830a6021",
42
42
  "files": [
43
43
  {
44
44
  "path": "cloudflare-router-ab/build/deriver-a/build-stamp.json",
45
45
  "bytes": 122,
46
- "sha256": "9360fbc8ecd55cc50e63a4e994abf7606ea810e707336982177fcdbaa9598f72"
46
+ "sha256": "f4584b8a1735dceb2c83fe247d053a368d36f195baea47e0e84a0db749c0489a"
47
47
  },
48
48
  {
49
49
  "path": "cloudflare-router-ab/build/deriver-a/index.js",
@@ -52,8 +52,8 @@
52
52
  },
53
53
  {
54
54
  "path": "cloudflare-router-ab/build/deriver-a/index_bg.wasm",
55
- "bytes": 15515779,
56
- "sha256": "c1cb040e2140a132ecee2fe9822bc24a669f67d325ab34968eae5daccd2d5bce"
55
+ "bytes": 13967465,
56
+ "sha256": "ab240fd5d74b80184b32d46427b490b72df08c2d90a42a47124a7f1d132f1bbd"
57
57
  },
58
58
  {
59
59
  "path": "cloudflare-router-ab/build/deriver-a/package.json",
@@ -69,12 +69,12 @@
69
69
  },
70
70
  "deriver-b": {
71
71
  "entry": "cloudflare-router-ab/build/deriver-b/worker/shim.mjs",
72
- "fingerprint": "cf673f8bbba1ca5a716971851b14ccbdfb664957c9b679141eab6b5f40afb6ee",
72
+ "fingerprint": "7d00d2bd13540dbf54852a229719bb49f63e13f63156eae27ed6d02bf155bfd5",
73
73
  "files": [
74
74
  {
75
75
  "path": "cloudflare-router-ab/build/deriver-b/build-stamp.json",
76
76
  "bytes": 122,
77
- "sha256": "2cf0dcc3eefd36c5a0280b11dca688f8ff47c912dfe68eafd17b6a489b4a1b34"
77
+ "sha256": "42b32d715606dc48c314c621a98bf9427aa497d4a2b5ea42d90fd5773ee10f7d"
78
78
  },
79
79
  {
80
80
  "path": "cloudflare-router-ab/build/deriver-b/index.js",
@@ -83,8 +83,8 @@
83
83
  },
84
84
  {
85
85
  "path": "cloudflare-router-ab/build/deriver-b/index_bg.wasm",
86
- "bytes": 15613711,
87
- "sha256": "84f3acdae49765c3c49a0b26a8eea05662b1281b3be09abc0579d7caeb1f9715"
86
+ "bytes": 14020253,
87
+ "sha256": "5ed975153c24dd4504d9bab7eb418fb4478edc25a26e424001b166cde68522a6"
88
88
  },
89
89
  {
90
90
  "path": "cloudflare-router-ab/build/deriver-b/package.json",
@@ -100,12 +100,12 @@
100
100
  },
101
101
  "signing-worker": {
102
102
  "entry": "cloudflare-router-ab/build/signing-worker/worker/shim.mjs",
103
- "fingerprint": "09511c8571ec458f4523f7cc2b9c2cb71faaccc189edd759d9af1f6d772fc506",
103
+ "fingerprint": "70f940a849724cee4ccbd8aab11645858179e95578badd8dab090a657a816e90",
104
104
  "files": [
105
105
  {
106
106
  "path": "cloudflare-router-ab/build/signing-worker/build-stamp.json",
107
107
  "bytes": 127,
108
- "sha256": "dedc96cabc8588370f1e252ba0297f70b6aa3a4a41062a91dd6702686459ae06"
108
+ "sha256": "01ea18855d322077772ed0ecd6d4b0117627648fdbbfb06c0d946f704dcc3d07"
109
109
  },
110
110
  {
111
111
  "path": "cloudflare-router-ab/build/signing-worker/index.js",
@@ -114,8 +114,8 @@
114
114
  },
115
115
  {
116
116
  "path": "cloudflare-router-ab/build/signing-worker/index_bg.wasm",
117
- "bytes": 11035697,
118
- "sha256": "12d038715f4acf588212242409cd9aef787e375142950d49a81aee5a9384f98d"
117
+ "bytes": 8988257,
118
+ "sha256": "2410f507efeb9b7501018dcfee4de2ffc36a664fe8ab59b3c3e487368ba05f56"
119
119
  },
120
120
  {
121
121
  "path": "cloudflare-router-ab/build/signing-worker/package.json",
@@ -131,12 +131,12 @@
131
131
  },
132
132
  "tenant-root-control-plane": {
133
133
  "entry": "cloudflare-router-ab/build/tenant-root-control-plane/worker/shim.mjs",
134
- "fingerprint": "d72a232927c25de160c55b8435aa2b0fd6d2bd94852df3848ed200210f469a6e",
134
+ "fingerprint": "6ea385bf5cdad01cd1363343344884ee9c96dc3497896a398c49f3757b6c501a",
135
135
  "files": [
136
136
  {
137
137
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/build-stamp.json",
138
138
  "bytes": 139,
139
- "sha256": "fc2b97971d6a78159210253f9ee42c446057179edaf11c22df9a8a97c90bdc28"
139
+ "sha256": "f55647bb6cda0be99e58c921780c808e6b40ef608e08fef94163ab3b66c57938"
140
140
  },
141
141
  {
142
142
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/index.js",
@@ -145,8 +145,8 @@
145
145
  },
146
146
  {
147
147
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/index_bg.wasm",
148
- "bytes": 6275434,
149
- "sha256": "4b8adb07f6d2a93b9f01a598e37ee71ce078d1d645561508f7aa8951271122da"
148
+ "bytes": 5220037,
149
+ "sha256": "58da690b6240f7f081a47a300d05c109e23428793c429e47b368f168ca93f917"
150
150
  },
151
151
  {
152
152
  "path": "cloudflare-router-ab/build/tenant-root-control-plane/package.json",
@@ -164,8 +164,8 @@
164
164
  "migrations": {
165
165
  "signer": {
166
166
  "directory": "migrations/d1-signer",
167
- "head": "0077_wallet_object_addresses.sql",
168
- "fingerprint": "ad5b9c7b4a2a23fdde929400763b5f3baba6514a2497f0e65177429bee454ac2",
167
+ "head": "0078_registration_passkey_claims.sql",
168
+ "fingerprint": "d1305343eafa23d821d280e0ca7955ea4cbb685850ead2a2e4b611e7cab5fb33",
169
169
  "files": [
170
170
  {
171
171
  "path": "migrations/d1-signer/0001_signer_d1_initial.sql",
@@ -551,6 +551,11 @@
551
551
  "path": "migrations/d1-signer/0077_wallet_object_addresses.sql",
552
552
  "bytes": 1243,
553
553
  "sha256": "693c3606116f2ff49c1eb77a56631c97448cc296855753c7cf54c1e9a103e9f6"
554
+ },
555
+ {
556
+ "path": "migrations/d1-signer/0078_registration_passkey_claims.sql",
557
+ "bytes": 880,
558
+ "sha256": "c0bab803761cc25b502217a1f9b3fe7d5082711d4632ab1ffc117ba47581f530"
554
559
  }
555
560
  ]
556
561
  },
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1791780253066}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-a-entrypoint","started_at_ms":1791827115529}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1791780483686}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-deriver-b-entrypoint","started_at_ms":1791827364267}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1791780687305}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-router-entrypoint","started_at_ms":1791827596969}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1791779948156}
1
+ {"profile":"release","wallet_objects":true,"features":"strict-worker-signing-worker-entrypoint","started_at_ms":1791826784348}
@@ -1 +1 @@
1
- {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1791780899928}
1
+ {"profile":"release","wallet_objects":false,"features":"strict-worker-tenant-root-control-plane-entrypoint","started_at_ms":1791827823651}
@@ -846,7 +846,7 @@ function createCloudflareD1RouterApiAuthAssembly(input) {
846
846
  tenantRootCustodyLineage: options.tenantRootCustodyLineage,
847
847
  getWalletStore,
848
848
  activateSideEffects: walletRegistrationActivateSideEffectStore(options),
849
- registrationRead: new D1WalletRegistrationRead(options.database, options.registrationAuthority ?? null, walletRegistrationNearProvisioningSideEffectStore(options), getRegistrationCeremonyIntentStore, yaoLifecycleDecisions),
849
+ registrationRead: new D1WalletRegistrationRead(options.database, options.registrationAuthority ?? null, walletRegistrationNearProvisioningSideEffectStore(options), getRegistrationCeremonyIntentStore, yaoLifecycleDecisions, walletRegistrationCommitStore),
850
850
  walletRegistrationCommitStore,
851
851
  walletCustodyCommitStore,
852
852
  walletAuthMethods,
@@ -201,20 +201,64 @@ function emailOtpCommitStatements(input) {
201
201
  case "email_otp_wallet_registration_commit_v1": return input.emailOtp.statements;
202
202
  }
203
203
  }
204
+ function heldClaimCovers(held, authority) {
205
+ return held !== null && held.walletId === authority.walletId && held.rpId === authority.rpId && held.credentialIdB64u === authority.credentialIdB64u;
206
+ }
204
207
  var CloudflareD1WalletRegistrationCommitStore = class {
205
208
  constructor(input) {
206
209
  this.credentialClaims = input.credentialClaims;
207
210
  this.database = input.database;
208
211
  this.scope = normalizeScope(input);
209
212
  }
210
- async reserveAuthority(authority) {
211
- if (authority.kind === "passkey") await reservePasskeyCredential(this.credentialClaims, {
213
+ async reserveAuthority(authority, held = null) {
214
+ if (authority.kind === "passkey" && !heldClaimCovers(held, authority)) await reservePasskeyCredential(this.credentialClaims, {
212
215
  walletId: authority.walletId,
213
216
  rpId: authority.rpId,
214
217
  credentialIdB64u: authority.credentialIdB64u
215
218
  });
216
219
  return this.commit.bind(this, authority);
217
220
  }
221
+ async claimPasskeyAhead(input) {
222
+ const { authority } = input;
223
+ if (authority.kind !== "passkey" || !this.credentialClaims) return;
224
+ if (!await this.credentialClaims.claim({
225
+ walletId: authority.walletId,
226
+ rpId: authority.rpId,
227
+ credentialIdB64u: authority.credentialIdB64u
228
+ })) return;
229
+ await this.database.prepare(`INSERT OR IGNORE INTO registration_passkey_claims (
230
+ namespace, org_id, project_id, env_id, registration_ceremony_id,
231
+ wallet_id, rp_id, credential_id_b64u, claimed_at_ms
232
+ ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)`).bind(this.scope.namespace, this.scope.orgId, this.scope.projectId, this.scope.envId, input.registrationCeremonyId, authority.walletId, authority.rpId, authority.credentialIdB64u, input.nowMs).run();
233
+ }
234
+ prepareHeldPasskeyClaimRead(registrationCeremonyId) {
235
+ return this.database.prepare(`SELECT wallet_id, rp_id, credential_id_b64u
236
+ FROM registration_passkey_claims
237
+ WHERE namespace = ?1
238
+ AND org_id = ?2
239
+ AND project_id = ?3
240
+ AND env_id = ?4
241
+ AND registration_ceremony_id = ?5`).bind(this.scope.namespace, this.scope.orgId, this.scope.projectId, this.scope.envId, registrationCeremonyId);
242
+ }
243
+ parseHeldPasskeyClaimRead(result) {
244
+ if (!result.success) throw new Error("Registration passkey claim read failed");
245
+ const row = result.results?.[0];
246
+ if (!row) return null;
247
+ if (typeof row.wallet_id !== "string" || typeof row.rp_id !== "string" || typeof row.credential_id_b64u !== "string") throw new Error("Stored registration passkey claim is invalid");
248
+ return {
249
+ walletId: row.wallet_id,
250
+ rpId: row.rp_id,
251
+ credentialIdB64u: row.credential_id_b64u
252
+ };
253
+ }
254
+ async deleteHeldPasskeyClaim(registrationCeremonyId) {
255
+ await this.database.prepare(`DELETE FROM registration_passkey_claims
256
+ WHERE namespace = ?1
257
+ AND org_id = ?2
258
+ AND project_id = ?3
259
+ AND env_id = ?4
260
+ AND registration_ceremony_id = ?5`).bind(this.scope.namespace, this.scope.orgId, this.scope.projectId, this.scope.envId, registrationCeremonyId).run();
261
+ }
218
262
  async commit(authority, input, executeBatch) {
219
263
  assertCommitWalletIdentity(input, authority);
220
264
  const ed25519Signer = input.walletSigners.find(isEd25519Signer);
@@ -1,12 +1,13 @@
1
1
  //#region src/router/cloudflare/d1/registration/d1WalletRegistrationRead.ts
2
2
  /** Read admission and immutable finalization state from one D1 snapshot. */
3
3
  var D1WalletRegistrationRead = class {
4
- constructor(database, authority, operations, getCeremonies, decisions) {
4
+ constructor(database, authority, operations, getCeremonies, decisions, passkeyClaims) {
5
5
  this.database = database;
6
6
  this.authority = authority;
7
7
  this.operations = operations;
8
8
  this.getCeremonies = getCeremonies;
9
9
  this.decisions = decisions;
10
+ this.passkeyClaims = passkeyClaims;
10
11
  }
11
12
  async readRespond(home) {
12
13
  if (!this.authority) return {
@@ -37,10 +38,11 @@ var D1WalletRegistrationRead = class {
37
38
  this.authority.prepareHomeRead(home),
38
39
  this.operations.prepareRead(operationKey),
39
40
  ceremonies.prepareCeremonyRead(home.ceremonyId),
40
- this.decisions.prepareRead(home.ceremonyId)
41
+ this.decisions.prepareRead(home.ceremonyId),
42
+ this.passkeyClaims.prepareHeldPasskeyClaimRead(home.ceremonyId)
41
43
  ]);
42
- const [homeRead, operationRead, ceremonyRead, decisionRead] = results;
43
- if (results.length !== 4 || !homeRead || !operationRead || !ceremonyRead || !decisionRead) throw new Error("Registration finalization D1 batch returned incomplete results");
44
+ const [homeRead, operationRead, ceremonyRead, decisionRead, passkeyClaimRead] = results;
45
+ if (results.length !== 5 || !homeRead || !operationRead || !ceremonyRead || !decisionRead || !passkeyClaimRead) throw new Error("Registration finalization D1 batch returned incomplete results");
44
46
  const admission = await this.authority.admitHomeRead(home, homeRead);
45
47
  if (!admission.ok) return admission;
46
48
  if (!ceremonyRead.success) throw new Error("Registration ceremony D1 read failed");
@@ -48,6 +50,7 @@ var D1WalletRegistrationRead = class {
48
50
  ok: true,
49
51
  operation: this.operations.parseReadResult(operationRead),
50
52
  decision: this.decisions.parseRead(home.ceremonyId, decisionRead),
53
+ heldPasskeyClaim: this.passkeyClaims.parseHeldPasskeyClaimRead(passkeyClaimRead),
51
54
  readCeremony: ceremonies.parseCeremonyRead.bind(ceremonies, home.ceremonyId, ceremonyRead)
52
55
  };
53
56
  }
@@ -529,9 +529,7 @@ function assertNeverRegistrationReceiptCommit(value) {
529
529
  throw new Error(`Unsupported registration receipt commit: ${String(value)}`);
530
530
  }
531
531
  async function cleanupFinalizedRegistrationCeremony(input) {
532
- try {
533
- await input.store.deleteCeremony(input.registrationCeremonyId);
534
- } catch {}
532
+ await Promise.allSettled([input.store.deleteCeremony(input.registrationCeremonyId), input.passkeyClaims?.deleteHeldPasskeyClaim(input.registrationCeremonyId)]);
535
533
  }
536
534
  function d1RegistrationRouteDiagnostics(recorder) {
537
535
  return {
@@ -1690,7 +1688,7 @@ var CloudflareD1WalletRegistrationService = class {
1690
1688
  };
1691
1689
  }
1692
1690
  }
1693
- async respondWalletRegistration(input, traceContext, timingSink) {
1691
+ async respondWalletRegistration(input, traceContext, timingSink, waitUntil) {
1694
1692
  const serverTiming = timingSink ?? [];
1695
1693
  const totalStartedAtMs = Date.now();
1696
1694
  const mark = (name, startedAtMs) => {
@@ -1769,6 +1767,7 @@ var CloudflareD1WalletRegistrationService = class {
1769
1767
  ownershipGeneration: homeAdmission.ownershipGeneration,
1770
1768
  startedDispatchRoot,
1771
1769
  expectedOrigin,
1770
+ waitUntil,
1772
1771
  ...input.userAgent ? { userAgent: input.userAgent } : {}
1773
1772
  });
1774
1773
  if (!ecdsaBranch) return {
@@ -2167,7 +2166,7 @@ var CloudflareD1WalletRegistrationService = class {
2167
2166
  ed25519: args.input.ed25519,
2168
2167
  emailOtpEnrollment: args.input.emailOtpEnrollment,
2169
2168
  walletCustodyCommit: args.input.walletCustodyCommit
2170
- }, effectivePrepared, source, args.serverTiming, initialSessionPlan, args.lifecycleDecision);
2169
+ }, effectivePrepared, source, args.serverTiming, initialSessionPlan, args.lifecycleDecision, args.heldPasskeyClaim);
2171
2170
  if (!execution.ok) return unissuedRegistrationCommit(throwIfRouterAbEd25519YaoRetryableSideEffectFailureV1(execution));
2172
2171
  const finalized = execution.finalized;
2173
2172
  if (finalized.kind !== "near_ed25519") {
@@ -2414,7 +2413,8 @@ var CloudflareD1WalletRegistrationService = class {
2414
2413
  setupClaims: verified.claims,
2415
2414
  authoritySource: verified.source,
2416
2415
  preparedCeremony: () => preparedCeremony,
2417
- lifecycleDecision: initial.decision
2416
+ lifecycleDecision: initial.decision,
2417
+ heldPasskeyClaim: initial.heldPasskeyClaim
2418
2418
  }),
2419
2419
  projectReceipt: (execution) => projectWalletRegistrationSessionCommitReceiptV2({
2420
2420
  operation: "near_provisioning",
@@ -2475,7 +2475,8 @@ var CloudflareD1WalletRegistrationService = class {
2475
2475
  startedAtMs: Date.now(),
2476
2476
  promise: cleanupFinalizedRegistrationCeremony({
2477
2477
  store: this.getRegistrationCeremonyIntentStore(),
2478
- registrationCeremonyId: input.registrationCeremonyId
2478
+ registrationCeremonyId: input.registrationCeremonyId,
2479
+ passkeyClaims: this.walletRegistrationCommitStore
2479
2480
  })
2480
2481
  });
2481
2482
  if (waitUntil) waitUntil(cleanup);
@@ -2650,6 +2651,16 @@ var CloudflareD1WalletRegistrationService = class {
2650
2651
  expected: input.snapshot,
2651
2652
  next: authorizedCeremony(admitted.admissionRequest)
2652
2653
  });
2654
+ if (!stored && input.waitUntil && verifiedAuthority.kind === "passkey") input.waitUntil(this.walletRegistrationCommitStore.claimPasskeyAhead({
2655
+ registrationCeremonyId: ceremony.registrationCeremonyId,
2656
+ authority: verifiedAuthority,
2657
+ nowMs: Date.now()
2658
+ }).catch((error) => {
2659
+ console.warn("[wallet-registration] early passkey claim failed", {
2660
+ registrationCeremonyId: ceremony.registrationCeremonyId,
2661
+ reason: errorMessage(error)
2662
+ });
2663
+ }));
2653
2664
  return {
2654
2665
  ok: true,
2655
2666
  registrationCeremonyId: ceremony.registrationCeremonyId,
@@ -3215,7 +3226,7 @@ var CloudflareD1WalletRegistrationService = class {
3215
3226
  };
3216
3227
  }
3217
3228
  }
3218
- async executeWalletRegistrationFinalize(request, prepared, source, serverTiming, initialSessionPlan, lifecycleDecision) {
3229
+ async executeWalletRegistrationFinalize(request, prepared, source, serverTiming, initialSessionPlan, lifecycleDecision, heldPasskeyClaim = null) {
3219
3230
  const finalizeTiming = createD1RegistrationRouteTimingRecorder("wallets_register_finalize");
3220
3231
  const totalTiming = startD1RegistrationRouteTiming("registerFinalizeTotalMs");
3221
3232
  let startedCustody = null;
@@ -3450,7 +3461,12 @@ var CloudflareD1WalletRegistrationService = class {
3450
3461
  },
3451
3462
  consumerBinding: requestFingerprint
3452
3463
  })
3453
- }), this.walletRegistrationCommitStore.reserveAuthority(ceremonyAuthority)]);
3464
+ }), recordRegistrationPromiseTiming({
3465
+ sink: serverTiming,
3466
+ name: "near_finalize_authority_reserve",
3467
+ startedAtMs: Date.now(),
3468
+ promise: this.walletRegistrationCommitStore.reserveAuthority(ceremonyAuthority, heldPasskeyClaim)
3469
+ })]);
3454
3470
  if (activationConsumption.status === "rejected") throw activationConsumption.reason;
3455
3471
  if (authorityReservation.status === "rejected") throw authorityReservation.reason;
3456
3472
  const consumed = activationConsumption.value;
@@ -13961,7 +13961,7 @@ async function handleRouterApiWalletRegistrationRespond(input) {
13961
13961
  ...parsed.value,
13962
13962
  verifier: session,
13963
13963
  ...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
13964
- }, traceContext.value ?? void 0, serverTiming);
13964
+ }, traceContext.value ?? void 0, serverTiming, input.waitUntil);
13965
13965
  return routeJson(result.ok ? 200 : 400, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
13966
13966
  }
13967
13967
  async function handleRouterApiWalletRegistrationNearAdmission(input) {
@@ -1641,7 +1641,7 @@ async function handleRouterApiWalletRegistrationRespond(input) {
1641
1641
  ...parsed.value,
1642
1642
  verifier: session,
1643
1643
  ...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
1644
- }, traceContext.value ?? void 0, serverTiming);
1644
+ }, traceContext.value ?? void 0, serverTiming, input.waitUntil);
1645
1645
  return routeJson(result.ok ? 200 : 400, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
1646
1646
  }
1647
1647
  async function handleRouterApiWalletRegistrationNearAdmission(input) {
@@ -9314,7 +9314,7 @@ async function handleRouterApiWalletRegistrationRespond(input) {
9314
9314
  ...parsed.value,
9315
9315
  verifier: session,
9316
9316
  ...registrationUserAgentFromHeaders(input.headers) ? { userAgent: registrationUserAgentFromHeaders(input.headers) } : {}
9317
- }, traceContext.value ?? void 0, serverTiming);
9317
+ }, traceContext.value ?? void 0, serverTiming, input.waitUntil);
9318
9318
  return routeJson(result.ok ? 200 : 400, result, { headers: ecdsaGatewayServerTimingHeaders(serverTiming) });
9319
9319
  }
9320
9320
  async function handleRouterApiWalletRegistrationNearAdmission(input) {
@@ -31,8 +31,32 @@ type D1WalletRegistrationCommitInput = (D1WalletRegistrationCommitBase & D1Walle
31
31
  readonly kind: 'email_otp_wallet_registration_commit_v1';
32
32
  readonly emailOtp: D1EmailOtpRegistrationCommitPlan;
33
33
  });
34
+ /** A passkey claim that shared authority granted for one ceremony before its finalization. */
35
+ export type HeldRegistrationPasskeyClaim = {
36
+ readonly walletId: string;
37
+ readonly rpId: string;
38
+ readonly credentialIdB64u: string;
39
+ };
34
40
  export interface D1WalletRegistrationCommitStore {
35
- reserveAuthority(authority: RegistrationAuthority): Promise<ReservedWalletRegistrationCommit>;
41
+ /**
42
+ * Claims the authority's passkey through shared authority, unless `held`
43
+ * records that the same claim was already granted for this ceremony.
44
+ */
45
+ reserveAuthority(authority: RegistrationAuthority, held?: HeldRegistrationPasskeyClaim | null): Promise<ReservedWalletRegistrationCommit>;
46
+ /**
47
+ * Claims a verified passkey before finalization and records a granted
48
+ * claim. A refusal records nothing, so finalization claims again and
49
+ * reports it.
50
+ */
51
+ claimPasskeyAhead(input: {
52
+ readonly registrationCeremonyId: string;
53
+ readonly authority: RegistrationAuthority;
54
+ readonly nowMs: number;
55
+ }): Promise<void>;
56
+ prepareHeldPasskeyClaimRead(registrationCeremonyId: string): D1PreparedStatementLike;
57
+ parseHeldPasskeyClaimRead(result: D1ResultLike<Record<string, unknown>>): HeldRegistrationPasskeyClaim | null;
58
+ /** Removes the record of a finalized ceremony. Shared authority keeps the claim. */
59
+ deleteHeldPasskeyClaim(registrationCeremonyId: string): Promise<void>;
36
60
  }
37
61
  export declare class CloudflareD1WalletRegistrationCommitStore implements D1WalletRegistrationCommitStore {
38
62
  private readonly database;
@@ -46,7 +70,15 @@ export declare class CloudflareD1WalletRegistrationCommitStore implements D1Wall
46
70
  readonly projectId: string;
47
71
  readonly envId: string;
48
72
  });
49
- reserveAuthority(authority: RegistrationAuthority): Promise<ReservedWalletRegistrationCommit>;
73
+ reserveAuthority(authority: RegistrationAuthority, held?: HeldRegistrationPasskeyClaim | null): Promise<ReservedWalletRegistrationCommit>;
74
+ claimPasskeyAhead(input: {
75
+ readonly registrationCeremonyId: string;
76
+ readonly authority: RegistrationAuthority;
77
+ readonly nowMs: number;
78
+ }): Promise<void>;
79
+ prepareHeldPasskeyClaimRead(registrationCeremonyId: string): D1PreparedStatementLike;
80
+ parseHeldPasskeyClaimRead(result: D1ResultLike<Record<string, unknown>>): HeldRegistrationPasskeyClaim | null;
81
+ deleteHeldPasskeyClaim(registrationCeremonyId: string): Promise<void>;
50
82
  private commit;
51
83
  }
52
84
  export {};
@@ -3,6 +3,7 @@ import type { WalletRegistrationHomeAuthority } from '../../../domains/walletReg
3
3
  import type { CloudflareD1RegistrationCeremonyIntentStore } from './d1RegistrationCeremonyStore';
4
4
  import type { D1WalletExecutionAuthority } from './d1WalletExecutionAuthority';
5
5
  import type { D1WalletOperationStore } from './d1WalletOperationStore';
6
+ import type { D1WalletRegistrationCommitStore } from './d1WalletRegistrationCommitStore';
6
7
  import type { D1WalletRegistrationNearProvisioningSideEffectRecord } from './d1WalletRegistrationService';
7
8
  import type { CloudflareD1Ed25519YaoLifecycleDecisionStoreV1 } from '../ed25519Yao/d1Ed25519YaoLifecycleDecisionStore';
8
9
  /** Read admission and immutable finalization state from one D1 snapshot. */
@@ -12,7 +13,8 @@ export declare class D1WalletRegistrationRead {
12
13
  readonly operations: D1WalletOperationStore<D1WalletRegistrationNearProvisioningSideEffectRecord>;
13
14
  private readonly getCeremonies;
14
15
  private readonly decisions;
15
- constructor(database: D1DatabaseLike, authority: D1WalletExecutionAuthority | null, operations: D1WalletOperationStore<D1WalletRegistrationNearProvisioningSideEffectRecord>, getCeremonies: () => CloudflareD1RegistrationCeremonyIntentStore, decisions: CloudflareD1Ed25519YaoLifecycleDecisionStoreV1);
16
+ private readonly passkeyClaims;
17
+ constructor(database: D1DatabaseLike, authority: D1WalletExecutionAuthority | null, operations: D1WalletOperationStore<D1WalletRegistrationNearProvisioningSideEffectRecord>, getCeremonies: () => CloudflareD1RegistrationCeremonyIntentStore, decisions: CloudflareD1Ed25519YaoLifecycleDecisionStoreV1, passkeyClaims: Pick<D1WalletRegistrationCommitStore, 'prepareHeldPasskeyClaimRead' | 'parseHeldPasskeyClaimRead'>);
16
18
  readRespond(home: Parameters<WalletRegistrationHomeAuthority['admitHome']>[0]): Promise<{
17
19
  readonly ok: false;
18
20
  readonly code: string;
@@ -37,6 +39,7 @@ export declare class D1WalletRegistrationRead {
37
39
  ok: true;
38
40
  operation: import("../../../cloudflare-adaptor").VersionedJsonRecordReadResult<D1WalletRegistrationNearProvisioningSideEffectRecord>;
39
41
  decision: import("../../../domains/ed25519Yao/capabilityLifecycle/routerAbEd25519YaoLifecycleDecision").RouterAbEd25519YaoLifecycleDecisionV1 | null;
42
+ heldPasskeyClaim: import("./d1WalletRegistrationCommitStore").HeldRegistrationPasskeyClaim | null;
40
43
  readCeremony: () => Promise<import("../../../../core/RegistrationCeremonyStore").StoredWalletRegistrationCeremony | null>;
41
44
  }>;
42
45
  }
@@ -260,7 +260,7 @@ export declare class CloudflareD1WalletRegistrationService {
260
260
  provisionEd25519YaoWalletSession(request: RouterAbEd25519YaoVerifiedWalletUnlockRequestV1): Promise<RouterAbEd25519YaoVerifiedWalletUnlockResponseV1>;
261
261
  setupWalletRegistration(input: WalletRegistrationSetupInput): Promise<WalletRegistrationSetupResponseV2>;
262
262
  authorizeWalletRegistrationNearAdmission(input: WalletRegistrationNearAdmissionInput): Promise<WalletRegistrationNearAdmissionResponseV2>;
263
- respondWalletRegistration(input: WalletRegistrationRespondInput, traceContext?: RouterAbTraceContextV1, timingSink?: Array<readonly [string, number]>): Promise<WalletRegistrationRespondResponseV2>;
263
+ respondWalletRegistration(input: WalletRegistrationRespondInput, traceContext?: RouterAbTraceContextV1, timingSink?: Array<readonly [string, number]>, waitUntil?: (promise: Promise<unknown>) => void): Promise<WalletRegistrationRespondResponseV2>;
264
264
  /** For callers that must read the context before finalize tombstones it. */
265
265
  private registrationOwnerProofWithContext;
266
266
  authorizeNearRegistrationContinuation(input: {
@@ -1031,7 +1031,7 @@ export interface RouterApiWalletRegistrationService {
1031
1031
  listWalletEcdsaKeyFactsInventory(input: RouterApiMethodTypes['listWalletEcdsaKeyFactsInventory']['input']): Promise<RouterApiMethodTypes['listWalletEcdsaKeyFactsInventory']['result']>;
1032
1032
  readActiveEmailOtpEnrollment(input: RouterApiMethodTypes['readActiveEmailOtpEnrollment']['input']): Promise<RouterApiMethodTypes['readActiveEmailOtpEnrollment']['result']>;
1033
1033
  setupWalletRegistration(input: WalletRegistrationSetupInput): Promise<RouteResponse>;
1034
- respondWalletRegistration(input: WalletRegistrationRespondInput, traceContext?: RouterAbTraceContextV1, serverTiming?: Array<readonly [string, number]>): Promise<WalletRegistrationRespondResponseV2>;
1034
+ respondWalletRegistration(input: WalletRegistrationRespondInput, traceContext?: RouterAbTraceContextV1, serverTiming?: Array<readonly [string, number]>, waitUntil?: (promise: Promise<unknown>) => void): Promise<WalletRegistrationRespondResponseV2>;
1035
1035
  authorizeWalletRegistrationNearAdmission(input: WalletRegistrationNearAdmissionInput): Promise<WalletRegistrationNearAdmissionResponseV2>;
1036
1036
  activateWalletRegistration(input: WalletRegistrationActivateInput, traceContext?: RouterAbTraceContextV1, serverTiming?: Array<readonly [string, number]>): Promise<WalletRegistrationActivateResponseV2>;
1037
1037
  completeWalletRegistrationNearProvisioning(input: WalletRegistrationNearProvisioningInput, serverTiming?: Array<readonly [string, number]>, waitUntil?: (promise: Promise<unknown>) => void): Promise<WalletRegistrationNearProvisioningResponseV2>;
@@ -0,0 +1,20 @@
1
+ -- A passkey ownership claim that shared authority granted for one registration
2
+ -- ceremony before its finalization. Finalization reads the row and skips its
3
+ -- own claim request. A missing row means only that finalization claims again.
4
+ CREATE TABLE registration_passkey_claims (
5
+ namespace TEXT NOT NULL,
6
+ org_id TEXT NOT NULL,
7
+ project_id TEXT NOT NULL,
8
+ env_id TEXT NOT NULL,
9
+ registration_ceremony_id TEXT NOT NULL,
10
+ wallet_id TEXT NOT NULL,
11
+ rp_id TEXT NOT NULL,
12
+ credential_id_b64u TEXT NOT NULL,
13
+ claimed_at_ms INTEGER NOT NULL,
14
+ PRIMARY KEY (namespace, org_id, project_id, env_id, registration_ceremony_id)
15
+ );
16
+
17
+ -- Shared authority never reassigns a claim, so a recorded claim never changes.
18
+ CREATE TRIGGER registration_passkey_claims_immutable
19
+ BEFORE UPDATE ON registration_passkey_claims
20
+ BEGIN SELECT RAISE(ABORT, 'registration_passkey_claims_immutable'); END;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/wallet-server",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "type": "module",
5
5
  "description": "Seams server TypeScript SDK for NEAR and EVM-family wallets",
6
6
  "main": "./dist/esm/index.js",