@tempoxyz/id 0.0.1 → 0.0.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.
Files changed (120) hide show
  1. package/dist/app/server/.vite/manifest.json +65 -65
  2. package/dist/app/server/assets/Grant-D_tun0mF.js.map +1 -1
  3. package/dist/app/server/assets/authorize-DXYo-K2n-B-6vqhvc.js +56 -0
  4. package/dist/app/server/assets/authorize-DXYo-K2n-B-6vqhvc.js.map +1 -0
  5. package/dist/app/server/assets/core-FSS9-r35.js +56 -0
  6. package/dist/app/server/assets/core-FSS9-r35.js.map +1 -0
  7. package/dist/app/server/assets/default-u_dwuiYb-Bby14ucv.js +2 -0
  8. package/dist/app/server/assets/{default-u_dwuiYb-DOrMMcg9.js.map → default-u_dwuiYb-Bby14ucv.js.map} +1 -1
  9. package/dist/app/server/assets/{dist-CoH0hD1s.js → dist-B9KiBJYw.js} +2 -2
  10. package/dist/app/server/assets/{dist-CoH0hD1s.js.map → dist-B9KiBJYw.js.map} +1 -1
  11. package/dist/app/server/assets/{dist-DcICI3fd.js → dist-CU1NXSiz.js} +2 -2
  12. package/dist/app/server/assets/{dist-DcICI3fd.js.map → dist-CU1NXSiz.js.map} +1 -1
  13. package/dist/app/server/assets/dist-CetH4JSY.js +1 -0
  14. package/dist/app/server/assets/{effect-QlVUlMFu-mXPkuQTb.js → effect-QlVUlMFu-Cm-eTtaO.js} +2 -2
  15. package/dist/app/server/assets/{effect-QlVUlMFu-mXPkuQTb.js.map → effect-QlVUlMFu-Cm-eTtaO.js.map} +1 -1
  16. package/dist/app/server/assets/factory-g3i_Ffi5.js +5 -0
  17. package/dist/app/server/assets/{factory-dvoOlKer.js.map → factory-g3i_Ffi5.js.map} +1 -1
  18. package/dist/app/server/assets/get-id-field-BpOAoqqI.js +6 -0
  19. package/dist/app/server/assets/get-id-field-BpOAoqqI.js.map +1 -0
  20. package/dist/app/server/assets/{index-CLddUTqr-CC33CMvS.js → index-CLddUTqr-CmgkbWi9.js} +2 -2
  21. package/dist/app/server/assets/{index-CLddUTqr-CC33CMvS.js.map → index-CLddUTqr-CmgkbWi9.js.map} +1 -1
  22. package/dist/app/server/assets/{index-DZEfthgZ-Ec2MMQ_e.js → index-DZEfthgZ-BnPmps9O.js} +2 -2
  23. package/dist/app/server/assets/{index-DZEfthgZ-Ec2MMQ_e.js.map → index-DZEfthgZ-BnPmps9O.js.map} +1 -1
  24. package/dist/app/server/assets/kysely-adapter-B8VoPArC.js +1 -0
  25. package/dist/app/server/assets/{schema-check-oLqvHR8y.js → schema-check-Bgwl1v0_.js} +2 -2
  26. package/dist/app/server/assets/{schema-check-oLqvHR8y.js.map → schema-check-Bgwl1v0_.js.map} +1 -1
  27. package/dist/app/server/assets/{sury-CWZTCd75-CDaOS2oM.js → sury-CWZTCd75-Bd_PzqCV.js} +2 -2
  28. package/dist/app/server/assets/{sury-CWZTCd75-CDaOS2oM.js.map → sury-CWZTCd75-Bd_PzqCV.js.map} +1 -1
  29. package/dist/app/server/assets/{valibot--1zFm7rT-CIKoH3MS.js → valibot--1zFm7rT-Dd2aEhWG.js} +2 -2
  30. package/dist/app/server/assets/{valibot--1zFm7rT-CIKoH3MS.js.map → valibot--1zFm7rT-Dd2aEhWG.js.map} +1 -1
  31. package/dist/app/server/assets/{valibot-D_HTw1Gn-BdPyLkXt.js → valibot-D_HTw1Gn-C1l3va6F.js} +2 -2
  32. package/dist/app/server/assets/{valibot-D_HTw1Gn-BdPyLkXt.js.map → valibot-D_HTw1Gn-C1l3va6F.js.map} +1 -1
  33. package/dist/app/server/assets/{zod-Bwrt9trS-BezI5Gi1.js → zod-Bwrt9trS-Dry2w0in.js} +2 -2
  34. package/dist/app/server/assets/{zod-Bwrt9trS-BezI5Gi1.js.map → zod-Bwrt9trS-Dry2w0in.js.map} +1 -1
  35. package/dist/app/server/assets/{zod-DSgpEGAE-DZ7M_Q33.js → zod-DSgpEGAE-BZMP86UA.js} +2 -2
  36. package/dist/app/server/assets/{zod-DSgpEGAE-DZ7M_Q33.js.map → zod-DSgpEGAE-BZMP86UA.js.map} +1 -1
  37. package/dist/app/server/index.js +35 -35
  38. package/dist/app/server/index.js.map +1 -1
  39. package/dist/client/Auth.d.ts +1 -1
  40. package/dist/client/Auth.d.ts.map +1 -1
  41. package/dist/client/Auth.js.map +1 -1
  42. package/dist/client/Decorator.d.ts +3 -1
  43. package/dist/client/Decorator.d.ts.map +1 -1
  44. package/dist/client/Decorator.js +1 -0
  45. package/dist/client/Decorator.js.map +1 -1
  46. package/dist/client/actions/passkey.d.ts +63 -2
  47. package/dist/client/actions/passkey.d.ts.map +1 -1
  48. package/dist/client/actions/passkey.js +121 -3
  49. package/dist/client/actions/passkey.js.map +1 -1
  50. package/dist/internal/Apps.d.ts +14 -0
  51. package/dist/internal/Apps.d.ts.map +1 -1
  52. package/dist/internal/Apps.js +14 -2
  53. package/dist/internal/Apps.js.map +1 -1
  54. package/dist/internal/Auth.d.ts +7 -0
  55. package/dist/internal/Auth.d.ts.map +1 -1
  56. package/dist/internal/Auth.js +2 -1
  57. package/dist/internal/Auth.js.map +1 -1
  58. package/dist/internal/Authorize.d.ts.map +1 -1
  59. package/dist/internal/Authorize.js +13 -13
  60. package/dist/internal/Authorize.js.map +1 -1
  61. package/dist/internal/ErrorResponse.d.ts +5 -0
  62. package/dist/internal/ErrorResponse.d.ts.map +1 -1
  63. package/dist/internal/ErrorResponse.js +8 -0
  64. package/dist/internal/ErrorResponse.js.map +1 -1
  65. package/dist/internal/KeyAuthorization.d.ts.map +1 -1
  66. package/dist/internal/KeyAuthorization.js +28 -28
  67. package/dist/internal/KeyAuthorization.js.map +1 -1
  68. package/dist/internal/Owner.d.ts +16 -0
  69. package/dist/internal/Owner.d.ts.map +1 -1
  70. package/dist/internal/Owner.js.map +1 -1
  71. package/dist/internal/Passkey.d.ts +66 -4
  72. package/dist/internal/Passkey.d.ts.map +1 -1
  73. package/dist/internal/Passkey.js +66 -30
  74. package/dist/internal/Passkey.js.map +1 -1
  75. package/dist/internal/Privy.d.ts +27 -0
  76. package/dist/internal/Privy.d.ts.map +1 -1
  77. package/dist/internal/Privy.js +82 -0
  78. package/dist/internal/Privy.js.map +1 -1
  79. package/dist/internal/TempoAccount.d.ts +57 -0
  80. package/dist/internal/TempoAccount.d.ts.map +1 -1
  81. package/dist/internal/TempoAccount.js +118 -5
  82. package/dist/internal/TempoAccount.js.map +1 -1
  83. package/dist/internal/owners/Privy.d.ts.map +1 -1
  84. package/dist/internal/owners/Privy.js +11 -2
  85. package/dist/internal/owners/Privy.js.map +1 -1
  86. package/package.json +1 -1
  87. package/src/api/account.ts +140 -103
  88. package/src/api/authenticate.ts +48 -51
  89. package/src/api/authorize.ts +35 -36
  90. package/src/api/device.ts +20 -23
  91. package/src/api/health.ts +1 -2
  92. package/src/api/lib/AccountStep.ts +75 -0
  93. package/src/api/lib/Auth.ts +6 -0
  94. package/src/api/lib/Cors.ts +3 -0
  95. package/src/api/lib/KeySigner.ts +1 -1
  96. package/src/api/oauth.ts +23 -22
  97. package/src/client/Auth.ts +1 -0
  98. package/src/client/Decorator.ts +6 -1
  99. package/src/client/actions/passkey.ts +197 -3
  100. package/src/internal/Apps.ts +26 -2
  101. package/src/internal/Auth.ts +4 -0
  102. package/src/internal/Authorize.ts +13 -15
  103. package/src/internal/ErrorResponse.ts +10 -0
  104. package/src/internal/KeyAuthorization.ts +28 -31
  105. package/src/internal/OpenApi.ts +35 -0
  106. package/src/internal/Owner.ts +16 -0
  107. package/src/internal/Passkey.ts +99 -34
  108. package/src/internal/Privy.ts +128 -0
  109. package/src/internal/TempoAccount.ts +221 -7
  110. package/src/internal/owners/Privy.ts +19 -6
  111. package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js +0 -56
  112. package/dist/app/server/assets/authorize-DXYo-K2n-rmQQ42x1.js.map +0 -1
  113. package/dist/app/server/assets/core-BBrZXfrB.js +0 -56
  114. package/dist/app/server/assets/core-BBrZXfrB.js.map +0 -1
  115. package/dist/app/server/assets/default-u_dwuiYb-DOrMMcg9.js +0 -2
  116. package/dist/app/server/assets/dist-CR6FW-yS.js +0 -1
  117. package/dist/app/server/assets/factory-dvoOlKer.js +0 -5
  118. package/dist/app/server/assets/get-id-field-OwLpfjj2.js +0 -6
  119. package/dist/app/server/assets/get-id-field-OwLpfjj2.js.map +0 -1
  120. package/dist/app/server/assets/kysely-adapter-B8tQwTcu.js +0 -1
@@ -52,7 +52,7 @@ namespace schema {
52
52
  export const query = z.object({
53
53
  chain: Chain.schema.optional().meta({
54
54
  description:
55
- 'The chain whose balance each account carries: `mainnet`, `testnet`, or another chain that the deployment lists. Without it, the accounts carry no balance.',
55
+ "The chain to read each account's balance on, such as `mainnet` or `testnet`. Without it, the response carries no balances.",
56
56
  examples: ['testnet'],
57
57
  }),
58
58
  client_id: z
@@ -62,7 +62,7 @@ namespace schema {
62
62
  .optional()
63
63
  .meta({
64
64
  description:
65
- "The app's client ID, whose realms select the accounts. A browser page from an origin that none of the app's redirect URIs has receives the `default` realm, as does a request without one.",
65
+ "The app whose realms select the accounts. Without it, or from a page outside the app's redirect URI origins, the `default` realm.",
66
66
  examples: ['client_01JZ8Q6W3N1R5B4V7C2X9K0M3T'],
67
67
  }),
68
68
  })
@@ -75,20 +75,19 @@ namespace schema {
75
75
  }),
76
76
  id: z.string().meta({
77
77
  description:
78
- "The user's ID, which is the `sub` of every token Tempo ID issues for the user.",
78
+ "The user's ID, which is the `sub` claim of the user's tokens.",
79
79
  examples: ['user_01JZ8Q6W3N1R5B4V7C2X9K0M3T'],
80
80
  }),
81
81
  name: z
82
82
  .string()
83
83
  .nullable()
84
84
  .meta({
85
- description:
86
- "The user's name, or `null` for a user who signed up without one.",
85
+ description: "The user's name, or `null` if the user has none.",
87
86
  examples: ['Ada Lovelace'],
88
87
  }),
89
88
  passkeys: z.boolean().meta({
90
89
  description:
91
- "Whether the realms the request reaches hold passkeys that sign in at Tempo ID's origin. Without `client_id`, the `default` realm's, where `POST /v1/account/passkey/options` registers a passkey.",
90
+ 'Whether the realms of the request accept passkeys that sign in on Tempo ID.',
92
91
  examples: [true],
93
92
  }),
94
93
  tempo: z
@@ -96,14 +95,14 @@ namespace schema {
96
95
  TempoAccount.schema.tempoAccount.extend({
97
96
  balance: Balance.schema.balance.nullable().optional().meta({
98
97
  description:
99
- "The USD value of the account's verified holdings on the chain that `chain` names, or absent without `chain`. `null` when the chain has no Tempo API URL, or the read fails or takes longer than 3 seconds.",
98
+ "The USD value of the account's verified tokens on `chain`. Absent without `chain`, and `null` if the balance cannot be read.",
100
99
  }),
101
100
  }),
102
101
  )
103
102
  .readonly()
104
103
  .meta({
105
104
  description:
106
- "The user's Tempo Accounts in the realms the request reaches: the account that the user selected for the app first, then oldest first.",
105
+ "The user's Tempo Accounts: the account that the user selected for the app first, then oldest first.",
107
106
  }),
108
107
  })
109
108
  .meta({ id: 'Account' })
@@ -125,30 +124,29 @@ namespace schema {
125
124
  publicKey: z
126
125
  .object({
127
126
  attestation: z.literal('none').meta({
128
- description:
129
- 'Tempo ID requests no attestation statement from the authenticator.',
127
+ description: 'Requests no attestation from the authenticator.',
130
128
  }),
131
129
  authenticatorSelection: z
132
130
  .object({
133
131
  requireResidentKey: z.literal(true).meta({
134
132
  description:
135
- 'The passkey must be discoverable, so it signs in without an email.',
133
+ 'Requires a discoverable passkey, which signs in without an email.',
136
134
  }),
137
135
  residentKey: z.literal('required').meta({
138
136
  description:
139
- 'The passkey must be discoverable, so it signs in without an email.',
137
+ 'Requires a discoverable passkey, which signs in without an email.',
140
138
  }),
141
139
  userVerification: z.literal('required').meta({
142
140
  description:
143
- 'Every passkey registration requires user verification.',
141
+ 'Requires user verification, such as a biometric or PIN.',
144
142
  }),
145
143
  })
146
144
  .meta({
147
- description: 'The authenticator that the passkey requires.',
145
+ description: 'The requirements for the authenticator.',
148
146
  }),
149
147
  challenge: z.templateLiteral(['0x', z.string()]).meta({
150
148
  description:
151
- 'The single-use challenge, as hex. It expires after 5 minutes, and only the same user spends it.',
149
+ 'The single-use challenge, as hex, which expires after 5 minutes.',
152
150
  examples: [
153
151
  '0xf6310589abb21116acc6e123967ed0a125b5041c43f8e157237093e07eaa354c',
154
152
  ],
@@ -166,13 +164,13 @@ namespace schema {
166
164
  }),
167
165
  })
168
166
  .meta({
169
- description: 'A passkey that the user holds already.',
167
+ description: 'A passkey that the user already has.',
170
168
  }),
171
169
  )
172
170
  .optional()
173
171
  .meta({
174
172
  description:
175
- "The user's passkeys of the relying party, which an authenticator that holds one of them refuses to replace. Absent for a user with none.",
173
+ "The user's existing passkeys, which the authenticator does not register again. Absent if the user has none.",
176
174
  }),
177
175
  pubKeyCredParams: z
178
176
  .array(
@@ -187,12 +185,11 @@ namespace schema {
187
185
  })
188
186
  .meta({
189
187
  description:
190
- 'The only key type that a Tempo Account accepts.',
188
+ 'A P-256 key, the only key type that a Tempo Account accepts.',
191
189
  }),
192
190
  )
193
191
  .meta({
194
- description:
195
- 'The key types the passkey may use: P-256 alone, which a Tempo Account verifies.',
192
+ description: 'The key types that the passkey can use.',
196
193
  }),
197
194
  rp: z
198
195
  .object({
@@ -201,21 +198,21 @@ namespace schema {
201
198
  examples: ['tempo.xyz'],
202
199
  }),
203
200
  name: z.string().meta({
204
- description: "The `default` realm's display name.",
201
+ description: 'The display name of the `default` realm.',
205
202
  examples: ['Tempo'],
206
203
  }),
207
204
  })
208
- .meta({ description: 'The relying party.' }),
205
+ .meta({ description: 'The relying party of the passkey.' }),
209
206
  user: z
210
207
  .object({
211
208
  displayName: z.string().meta({
212
209
  description:
213
- "The user's name, or the email address for a user without one.",
210
+ "The user's name, or the email address if the user has none.",
214
211
  examples: ['Ada Lovelace'],
215
212
  }),
216
213
  id: z.string().meta({
217
214
  description:
218
- 'A random user handle for this passkey, base64url, so a new passkey never replaces another in the authenticator.',
215
+ 'A random user handle, base64url. A new handle for each passkey stops the authenticator from replacing another passkey.',
219
216
  examples: ['3q2-7wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'],
220
217
  }),
221
218
  name: z.email().meta({
@@ -225,16 +222,16 @@ namespace schema {
225
222
  })
226
223
  .meta({
227
224
  description:
228
- 'The user, as the authenticator labels the passkey.',
225
+ 'The user that the authenticator shows with the passkey.',
229
226
  }),
230
227
  })
231
228
  .meta({
232
229
  description:
233
- 'The `PublicKeyCredentialCreationOptions`, serialized with the challenge as hex.',
230
+ 'The `PublicKeyCredentialCreationOptions`, with the challenge as hex.',
234
231
  }),
235
232
  })
236
233
  .meta({
237
- description: 'The options for `navigator.credentials.create()`.',
234
+ description: 'The argument for `navigator.credentials.create()`.',
238
235
  }),
239
236
  })
240
237
  }
@@ -268,11 +265,11 @@ namespace schema {
268
265
  )
269
266
  .meta({
270
267
  description:
271
- 'The new passkey, the only one the browser offers.',
268
+ 'The new passkey, which is the only passkey that the browser offers.',
272
269
  }),
273
270
  challenge: z.templateLiteral(['0x', z.string()]).meta({
274
271
  description:
275
- 'The single-use proof challenge, as hex. It expires after 5 minutes, and only the same user spends it.',
272
+ 'The single-use challenge, as hex, which expires after 5 minutes.',
276
273
  examples: [
277
274
  '0xf6310589abb21116acc6e123967ed0a125b5041c43f8e157237093e07eaa354c',
278
275
  ],
@@ -283,25 +280,25 @@ namespace schema {
283
280
  }),
284
281
  userVerification: z.literal('required').meta({
285
282
  description:
286
- 'Every passkey signature requires user verification.',
283
+ 'Requires user verification, such as a biometric or PIN.',
287
284
  }),
288
285
  })
289
286
  .meta({
290
287
  description:
291
- 'The `PublicKeyCredentialRequestOptions`, serialized with the challenge as hex.',
288
+ 'The `PublicKeyCredentialRequestOptions`, with the challenge as hex.',
292
289
  }),
293
290
  })
294
291
  .meta({
295
- description: 'The options for `navigator.credentials.get()`.',
292
+ description: 'The argument for `navigator.credentials.get()`.',
296
293
  }),
297
294
  type: z.literal('passkey').meta({
298
295
  description:
299
- 'Sign the options with the new passkey, then send the assertion to `POST /v1/account/passkey/verify`.',
296
+ 'Sign the challenge with the new passkey, then send the assertion to `POST /v1/account/passkey/verify`.',
300
297
  }),
301
298
  })
302
299
  .meta({
303
300
  description:
304
- "The next step: the new passkey's signature, which proves that the authenticator holds its private key.",
301
+ 'The next step: a signature of the new passkey, which proves that the authenticator holds its private key.',
305
302
  }),
306
303
  })
307
304
  }
@@ -310,21 +307,20 @@ namespace schema {
310
307
  export const json = z.object({
311
308
  account: address.optional().meta({
312
309
  description:
313
- "A configurable Tempo Account that the user's cloud wallet owns in the `default` realm, which the passkey joins as a second owner on `chain`. Absent, the passkey owns an account of its own.",
310
+ 'A configurable Tempo Account of the user, which the passkey joins as a second owner. Without it, the passkey owns a new account.',
314
311
  examples: ['0x7a3f9c2b1e5d8a4f6c0b3e7d9a2f5c8b1e4d7a0f'],
315
312
  }),
316
313
  assertion: Passkey.schema.assertion,
317
314
  chain: Chain.schema.optional().meta({
318
315
  description:
319
- "The chain whose accounts are configurable, where `account` is, and whose address of the passkey's own account the response returns. Without it, the first such chain for `account`, and the passkey's own address otherwise.",
316
+ 'The chain with configurable accounts where the passkey joins `account`. Defaults to the first such chain.',
320
317
  examples: ['local'],
321
318
  }),
322
319
  })
323
320
 
324
321
  export const response = z.object({
325
322
  address: z.string().meta({
326
- description:
327
- "The address of the Tempo Account that the passkey owns: `account`, or the passkey's own account on `chain`.",
323
+ description: 'The address of the Tempo Account that the passkey owns.',
328
324
  examples: ['0x7a3f9c2b1e5d8a4f6c0b3e7d9a2f5c8b1e4d7a0f'],
329
325
  }),
330
326
  id: Passkey.schema.credentialId.meta({
@@ -340,12 +336,12 @@ namespace schema {
340
336
  export const response = z.object({
341
337
  passkeys: z.boolean().meta({
342
338
  description:
343
- "Whether a passkey sign-in for the app runs on the page that sends the request, at a relying party of the app's realms. `false` without `client_id`, or for Tempo ID's own page.",
339
+ "Whether passkey sign-in for the app works on the page that sends the request. `false` without `client_id`, or on Tempo ID's own page.",
344
340
  examples: [true],
345
341
  }),
346
342
  realms: z.array(TempoAccount.schema.realm).readonly().meta({
347
343
  description:
348
- "The realms the app reaches, never empty. The first holds a user's first account.",
344
+ "The app's realms, in order. A user's first account is in the first realm.",
349
345
  }),
350
346
  })
351
347
  }
@@ -362,20 +358,20 @@ namespace schema {
362
358
  }),
363
359
  configurable: z.boolean().meta({
364
360
  description:
365
- 'Whether Tempo Accounts can be upgraded to configurable accounts (TIP-1107) on the chain, as the deployment sets.',
361
+ 'Whether Tempo Accounts on the chain are configurable (TIP-1107).',
366
362
  examples: [false],
367
363
  }),
368
364
  name: Chain.schema.meta({
369
- description: 'The name that the `chain` parameter carries.',
365
+ description: 'The value of the `chain` parameter.',
370
366
  examples: ['testnet'],
371
367
  }),
372
368
  })
373
- .meta({ description: 'A chain that a page can select.' })
369
+ .meta({ description: 'A chain.' })
374
370
 
375
371
  export const response = z.object({
376
372
  chains: z.array(entry).readonly().meta({
377
373
  description:
378
- "The chains in order: `mainnet`, `testnet`, then the deployment's listed chains.",
374
+ 'The chains: `mainnet`, `testnet`, then the chains that the deployment adds.',
379
375
  }),
380
376
  })
381
377
  }
@@ -392,12 +388,12 @@ namespace schema {
392
388
  examples: [true],
393
389
  }),
394
390
  })
395
- .meta({ description: 'A chain that a page can select.' }),
391
+ .meta({ description: 'A chain.' }),
396
392
  )
397
393
  .readonly()
398
394
  .meta({
399
395
  description:
400
- "The chains in order: `mainnet`, `testnet`, then the deployment's listed chains.",
396
+ 'The chains: `mainnet`, `testnet`, then the chains that the deployment adds.',
401
397
  }),
402
398
  })
403
399
  }
@@ -422,7 +418,7 @@ namespace schema {
422
418
  examples: [1337],
423
419
  }),
424
420
  name: Chain.schema.meta({
425
- description: 'The name that the `chain` parameter carries.',
421
+ description: 'The value of the `chain` parameter.',
426
422
  examples: ['local'],
427
423
  }),
428
424
  node: z.enum(['down', 'up']).meta({
@@ -446,14 +442,13 @@ namespace schema {
446
442
  address: z
447
443
  .templateLiteral(['0x', z.string().regex(/^[0-9a-fA-F]{40}$/)])
448
444
  .meta({
449
- description:
450
- "The Tempo Account that receives the tokens, one of the user's accounts in the `default` realm.",
445
+ description: "The user's Tempo Account that receives the tokens.",
451
446
  examples: ['0x7a3f9c2b1e5d8a4f6c0b3e7d9a2f5c8b1e4d7a0f'],
452
447
  pattern: '^0x[0-9a-fA-F]{40}$',
453
448
  }),
454
449
  chain: Chain.schema.meta({
455
450
  description:
456
- 'The test chain whose faucet drips the tokens: `testnet`, or a name that `GET /i/chains` lists. Tempo mainnet has no faucet.',
451
+ 'The test chain whose faucet sends the tokens. Tempo mainnet has no faucet.',
457
452
  examples: ['testnet'],
458
453
  }),
459
454
  })
@@ -470,8 +465,7 @@ namespace schema {
470
465
  )
471
466
  .readonly()
472
467
  .meta({
473
- description:
474
- 'The hashes of the transfers, one for each token that the faucet drips, each one landed.',
468
+ description: 'The hashes of the transfers, one for each token.',
475
469
  examples: [
476
470
  [
477
471
  '0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060',
@@ -486,12 +480,12 @@ namespace schema {
486
480
  .object({
487
481
  account: address.optional().meta({
488
482
  description:
489
- "One of the user's Tempo Accounts in the app's realms on `chain`, which a linked wallet.tempo passkey owns and the upgrade makes configurable. Absent with `credentialId`.",
483
+ 'The Tempo Account to upgrade, which a wallet.tempo passkey of the user owns. Absent with `credentialId`.',
490
484
  examples: ['0x7a3f9c2b1e5d8a4f6c0b3e7d9a2f5c8b1e4d7a0f'],
491
485
  }),
492
486
  chain: Chain.schema.optional().meta({
493
487
  description:
494
- 'The chain whose accounts are configurable, where the upgrade runs. Defaults to the first such chain that the deployment names.',
488
+ 'The chain with configurable accounts where the upgrade runs. Defaults to the first such chain.',
495
489
  examples: ['local'],
496
490
  }),
497
491
  client_id: z
@@ -501,12 +495,12 @@ namespace schema {
501
495
  .optional()
502
496
  .meta({
503
497
  description:
504
- 'The app whose realms hold the account. Without it, the `default` realm.',
498
+ 'The app whose realms hold the account. Defaults to the `default` realm.',
505
499
  examples: ['client_01JZ8Q6W3N1R5B4V7C2X9K0M3T'],
506
500
  }),
507
501
  credentialId: Passkey.schema.credentialId.optional().meta({
508
502
  description:
509
- "A wallet.tempo passkey of the user's email that has not linked to the user yet, as `GET /i/account/passkeys` lists it. Its assertion over the upgrade links it. Absent with `account`. Only Tempo ID's own page sends it.",
503
+ "A wallet.tempo passkey of the user's email that is not linked yet, which the upgrade links. Absent with `account`.",
510
504
  examples: ['KEbWNCc7NgaYnUyrNeFGX9_3Y-8'],
511
505
  }),
512
506
  key: z
@@ -520,7 +514,7 @@ namespace schema {
520
514
  })
521
515
  .meta({
522
516
  description:
523
- "The access key's raw signature over `step.key.digest`, as a serialized signature envelope.",
517
+ "The access key's signature of `step.key.digest`, as a serialized signature envelope.",
524
518
  examples: [
525
519
  '0x997c61aa10e2330c076d6ba7abca1e71703a8f039291db103a62e085c9b2cf0d3f7e31ec9028229acc83093fbb722fa700fd6e4ea15d584df1bf384eb0051e0d1c',
526
520
  ],
@@ -529,11 +523,11 @@ namespace schema {
529
523
  .optional()
530
524
  .meta({
531
525
  description:
532
- "The access key's signature of its registration, on the repeated request of a step with `key`.",
526
+ "The access key's signature of its registration, for a step with `key`.",
533
527
  }),
534
528
  key_authorization: KeyAuthorization.schema.rpc.optional().meta({
535
529
  description:
536
- "A key that the passkey signed as the account's own key, such as with `skip_upgrade`, on the first request. The chain registers it before the upgrade, so it keeps working after. Its chain is the upgrade's when `chain` is absent.",
530
+ 'A key that the passkey signed before the upgrade, such as with `skip_upgrade`. Tempo ID registers it first, so it keeps working after the upgrade.',
537
531
  }),
538
532
  passkey: z
539
533
  .object({
@@ -542,7 +536,7 @@ namespace schema {
542
536
  .optional()
543
537
  .meta({
544
538
  description:
545
- "The passkey step's result: the passkey's assertion over the step's challenge. Absent on the first request.",
539
+ "The passkey's assertion over the step's challenge. Absent on the first request.",
546
540
  }),
547
541
  })
548
542
  .refine(
@@ -585,11 +579,11 @@ namespace schema {
585
579
  )
586
580
  .meta({
587
581
  description:
588
- "The account's passkey, the only one the browser offers.",
582
+ "The account's passkey, which is the only passkey that the browser offers.",
589
583
  }),
590
584
  challenge: z.templateLiteral(['0x', z.string()]).meta({
591
585
  description:
592
- 'The digest of the transaction that installs the configuration, as hex. It expires after 5 minutes, and only the same user spends it.',
586
+ 'The digest of the upgrade transaction, as hex, which expires after 5 minutes.',
593
587
  examples: [
594
588
  '0xf6310589abb21116acc6e123967ed0a125b5041c43f8e157237093e07eaa354c',
595
589
  ],
@@ -600,22 +594,23 @@ namespace schema {
600
594
  }),
601
595
  userVerification: z.literal('required').meta({
602
596
  description:
603
- 'Every passkey signature requires user verification.',
597
+ 'Requires user verification, such as a biometric or PIN.',
604
598
  }),
605
599
  })
606
600
  .meta({
607
601
  description:
608
- 'The `PublicKeyCredentialRequestOptions`, serialized with the challenge as hex.',
602
+ 'The `PublicKeyCredentialRequestOptions`, with the challenge as hex.',
609
603
  }),
610
604
  })
611
605
  .meta({
612
- description: 'The options for `navigator.credentials.get()`.',
606
+ description:
607
+ 'The argument for `navigator.credentials.get()`.',
613
608
  }),
614
609
  key: z
615
610
  .object({
616
611
  digest: z.templateLiteral(['0x', z.string()]).meta({
617
612
  description:
618
- 'The digest of the transaction that registers the key, which the access key signs raw. Tempo ID sponsors the transaction.',
613
+ "The digest of the key's registration transaction, for the access key to sign. Tempo ID pays its fee.",
619
614
  examples: [
620
615
  '0x3c8a5e1f0b7d2c946a1e8f3b5d7c9a2e4f6b8d0c1e3a5f7b9d2c4e6a8f0b1d3c',
621
616
  ],
@@ -624,16 +619,16 @@ namespace schema {
624
619
  .optional()
625
620
  .meta({
626
621
  description:
627
- "The key's registration, for a request with `key_authorization` whose key the chain does not hold yet. Repeat the request with `key.signature` too.",
622
+ "The key's registration, if `key_authorization` names a key that the chain does not hold yet. Repeat the request with `key.signature`.",
628
623
  }),
629
624
  type: z.literal('passkey').meta({
630
625
  description:
631
- 'Sign the options with the passkey, then repeat the request with `passkey.assertion`.',
626
+ 'Sign the challenge with the passkey, then repeat the request with `passkey.assertion`.',
632
627
  }),
633
628
  })
634
629
  .meta({
635
630
  description:
636
- "The next step: the passkey's signature of the transaction, since only the passkey signs for the account.",
631
+ "The next step: the passkey's signature of the upgrade transaction.",
637
632
  }),
638
633
  })
639
634
  .meta({ description: 'The passkey step.' }),
@@ -647,8 +642,7 @@ namespace schema {
647
642
  z
648
643
  .object({
649
644
  address: address.meta({
650
- description:
651
- "The address of the passkey's Tempo Account, which its public key derives.",
645
+ description: "The address of the passkey's Tempo Account.",
652
646
  examples: ['0x7a3f9c2b1e5d8a4f6c0b3e7d9a2f5c8b1e4d7a0f'],
653
647
  }),
654
648
  credentialId: Passkey.schema.credentialId.meta({
@@ -661,7 +655,7 @@ namespace schema {
661
655
  .readonly()
662
656
  .meta({
663
657
  description:
664
- "The passkeys that wallet.tempo stores for the user's email and that link to no user yet, oldest first. Empty without the wallet service.",
658
+ "The wallet.tempo passkeys of the user's email that are not linked yet, oldest first.",
665
659
  }),
666
660
  })
667
661
  }
@@ -670,7 +664,7 @@ namespace schema {
670
664
  export const query = z.object({
671
665
  chain: Chain.schema.meta({
672
666
  description:
673
- 'The chain to list upgrades on, such as the chain that the sign-in page shows. A chain whose accounts are not configurable lists none.',
667
+ 'The chain to list upgrades on. A chain without configurable accounts lists none.',
674
668
  examples: ['t14'],
675
669
  }),
676
670
  })
@@ -716,8 +710,7 @@ namespace schema {
716
710
  )
717
711
  .readonly()
718
712
  .meta({
719
- description:
720
- 'The upgrades, linked accounts first, in the order of the chains. Empty without a chain whose accounts are configurable.',
713
+ description: 'The upgrades, linked accounts first.',
721
714
  }),
722
715
  })
723
716
  }
@@ -735,7 +728,7 @@ export const account = new Hono<Env>()
735
728
  '/v1/account',
736
729
  OpenApi.describeRoute({
737
730
  description:
738
- 'Reads the signed-in user and their Tempo Accounts in the realms of the app that `client_id` names, or of the `default` realm. The account that the user selected for the app comes first, then the oldest.',
731
+ 'Returns the signed-in user and their Tempo Accounts in the realms of the `client_id` app, or of the `default` realm.',
739
732
  responses: OpenApi.responses({
740
733
  errors: [
741
734
  ...OpenApi.validate.errors,
@@ -858,7 +851,7 @@ export const account = new Hono<Env>()
858
851
  '/v1/account',
859
852
  OpenApi.describeRoute({
860
853
  description:
861
- 'Returns the signed-in user and their Tempo Accounts, as `GET /v1/account` does. For a user with no account or passkey, it also creates a Privy-owned account in the first realm, when that realm has a Privy app. The JSON body needs a CORS preflight, which Tempo ID grants only to the origins of first-party apps.',
854
+ 'Returns the signed-in user and their Tempo Accounts, as `GET /v1/account` does, and first creates a Tempo Account for a user who has none. Only the pages of first-party apps can send it.',
862
855
  responses: OpenApi.responses({
863
856
  errors: [
864
857
  ...BodyLimit.errors,
@@ -988,7 +981,7 @@ export const account = new Hono<Env>()
988
981
  '/v1/account/passkey/options',
989
982
  OpenApi.describeRoute({
990
983
  description:
991
- "Issues a single-use registration challenge for the signed-in user, and returns creation options for a passkey of the `default` realm. Requires a sign-in within the last 10 minutes and fewer than 10 passkeys. Only Tempo ID's own page sends the request.",
984
+ "Passkey registration, step 1 of 3: returns the options for `navigator.credentials.create()`. Requires a sign-in within the last 10 minutes, on Tempo ID's own page or the page of a first-party app with the `tempo-id:sign` scope.",
992
985
  responses: OpenApi.responses({
993
986
  errors: [
994
987
  ...BodyLimit.errors,
@@ -999,6 +992,7 @@ export const account = new Hono<Env>()
999
992
  Passkey.LimitError,
1000
993
  Passkey.NotPermittedError,
1001
994
  Passkey.ReauthenticationRequiredError,
995
+ Passkey.RegisterNotPermittedError,
1002
996
  ],
1003
997
  success: {
1004
998
  description: 'The creation options.',
@@ -1010,9 +1004,18 @@ export const account = new Hono<Env>()
1010
1004
  }),
1011
1005
  OpenApi.validate('json', schema.passkeyOptions.json),
1012
1006
  async (c) => {
1013
- // The ceremony runs at Tempo ID's origin, so only its own page registers a passkey.
1014
- if (Cors.appOrigin(c))
1007
+ // The ceremony runs at Tempo ID's origin, or at a first-party app's page, which the challenge
1008
+ // stores. `Cors.firstParty` refuses any other page's write before the route runs.
1009
+ const page = Cors.appOrigin(c)
1010
+ if (page && !c.var.firstParty?.length)
1015
1011
  return ErrorResponse.json(c, new ErrorResponse.CrossSiteRequestError())
1012
+ // A registered passkey signs in as the user, so an app's page registers one only for an app
1013
+ // that the deployment trusts with its own challenges.
1014
+ if (page && !c.var.firstParty?.some((app) => app.sign))
1015
+ return ErrorResponse.json(
1016
+ c,
1017
+ new Passkey.RegisterNotPermittedError({ origin: page }),
1018
+ )
1016
1019
 
1017
1020
  const session = await Timing.measure(c, Session.get(c), 'Session.get')
1018
1021
  if (!session)
@@ -1062,14 +1065,26 @@ export const account = new Hono<Env>()
1062
1065
  }),
1063
1066
  'Realm.withDefaults',
1064
1067
  )
1065
- const relyingParty = Passkey.relyingParty(realms, { origin: env.URL })
1068
+ // The relying party must cover the page beside Tempo ID, so the page shares a site with Tempo
1069
+ // ID, and the browser sends the session cookie on the page's requests.
1070
+ const relyingParty = Passkey.relyingParty(realms, {
1071
+ origin: env.URL,
1072
+ page,
1073
+ })
1066
1074
  const realm = realms.find((entry) => entry.id === relyingParty?.realmId)
1067
1075
  if (!relyingParty || !realm)
1068
- return ErrorResponse.json(c, new Passkey.NotPermittedError())
1076
+ return ErrorResponse.json(
1077
+ c,
1078
+ new Passkey.NotPermittedError({ origin: page }),
1079
+ )
1069
1080
 
1070
1081
  const challenge = await Timing.measure(
1071
1082
  c,
1072
- Passkey.requestRegistration(db, { rpId: relyingParty.rpId, userId }),
1083
+ Passkey.requestRegistration(db, {
1084
+ origin: page,
1085
+ rpId: relyingParty.rpId,
1086
+ userId,
1087
+ }),
1073
1088
  'Passkey.requestRegistration',
1074
1089
  )
1075
1090
  const excluded = held.flatMap((key) =>
@@ -1102,7 +1117,7 @@ export const account = new Hono<Env>()
1102
1117
  '/v1/account/passkey',
1103
1118
  OpenApi.describeRoute({
1104
1119
  description:
1105
- "Verifies a new passkey's credential over a challenge from `POST /v1/account/passkey/options`, and returns a proof challenge for the passkey to sign. The attestation carries no signature, so only `POST /v1/account/passkey/verify` stores the passkey. Requires a sign-in within the last 10 minutes. Only Tempo ID's own page sends the request.",
1120
+ 'Passkey registration, step 2 of 3: verifies the credential from `navigator.credentials.create()`, and returns a challenge for the new passkey to sign. The signature proves that the authenticator holds the private key.',
1106
1121
  responses: OpenApi.responses({
1107
1122
  errors: [
1108
1123
  ...BodyLimit.errors,
@@ -1113,6 +1128,7 @@ export const account = new Hono<Env>()
1113
1128
  ErrorResponse.CrossSiteRequestError,
1114
1129
  Passkey.LimitError,
1115
1130
  Passkey.ReauthenticationRequiredError,
1131
+ Passkey.RegisterNotPermittedError,
1116
1132
  Passkey.WebAuthnRejectedError,
1117
1133
  ],
1118
1134
  success: {
@@ -1125,8 +1141,16 @@ export const account = new Hono<Env>()
1125
1141
  }),
1126
1142
  OpenApi.validate('json', schema.passkey.json),
1127
1143
  async (c) => {
1128
- if (Cors.appOrigin(c))
1144
+ const page = Cors.appOrigin(c)
1145
+ if (page && !c.var.firstParty?.length)
1129
1146
  return ErrorResponse.json(c, new ErrorResponse.CrossSiteRequestError())
1147
+ // A registered passkey signs in as the user, so an app's page registers one only for an app
1148
+ // that the deployment trusts with its own challenges.
1149
+ if (page && !c.var.firstParty?.some((app) => app.sign))
1150
+ return ErrorResponse.json(
1151
+ c,
1152
+ new Passkey.RegisterNotPermittedError({ origin: page }),
1153
+ )
1130
1154
 
1131
1155
  const session = await Timing.measure(c, Session.get(c), 'Session.get')
1132
1156
  if (!session)
@@ -1157,13 +1181,15 @@ export const account = new Hono<Env>()
1157
1181
  }),
1158
1182
  'Passkey.takeRegistration',
1159
1183
  )
1160
- const verified = challenge
1161
- ? Passkey.verifyRegistration(credential, {
1162
- challenge: challenge.challenge,
1163
- origin: new URL(env.URL).origin,
1164
- rpId: challenge.rpId,
1165
- })
1166
- : undefined
1184
+ // A challenge serves only the page that requested it, and the browser must report that page.
1185
+ const verified =
1186
+ challenge && challenge.origin === page
1187
+ ? Passkey.verifyRegistration(credential, {
1188
+ challenge: challenge.challenge,
1189
+ origin: challenge.origin ?? new URL(env.URL).origin,
1190
+ rpId: challenge.rpId,
1191
+ })
1192
+ : undefined
1167
1193
  if (!challenge || !verified)
1168
1194
  return ErrorResponse.json(c, new Passkey.WebAuthnRejectedError())
1169
1195
 
@@ -1209,6 +1235,7 @@ export const account = new Hono<Env>()
1209
1235
  c,
1210
1236
  Passkey.requestProof(db, {
1211
1237
  credential: verified,
1238
+ origin: challenge.origin,
1212
1239
  rpId: challenge.rpId,
1213
1240
  userId,
1214
1241
  }),
@@ -1230,7 +1257,7 @@ export const account = new Hono<Env>()
1230
1257
  '/v1/account/passkey/verify',
1231
1258
  OpenApi.describeRoute({
1232
1259
  description:
1233
- "Registers a passkey for the signed-in user with its assertion over the proof challenge. The passkey owns its own Tempo Account in the `default` realm, or joins `account` as a second owner. Requires a sign-in within the last 10 minutes. Only Tempo ID's own page sends the request.",
1260
+ "Passkey registration, step 3 of 3: verifies the new passkey's assertion from `navigator.credentials.get()`, and stores the passkey. The passkey owns a new Tempo Account, or joins `account` as a second owner.",
1234
1261
  responses: OpenApi.responses({
1235
1262
  errors: [
1236
1263
  ...BodyLimit.errors,
@@ -1244,6 +1271,7 @@ export const account = new Hono<Env>()
1244
1271
  ErrorResponse.CrossSiteRequestError,
1245
1272
  Passkey.LimitError,
1246
1273
  Passkey.ReauthenticationRequiredError,
1274
+ Passkey.RegisterNotPermittedError,
1247
1275
  Passkey.WebAuthnRejectedError,
1248
1276
  ],
1249
1277
  success: {
@@ -1256,8 +1284,16 @@ export const account = new Hono<Env>()
1256
1284
  }),
1257
1285
  OpenApi.validate('json', schema.passkeyVerify.json),
1258
1286
  async (c) => {
1259
- if (Cors.appOrigin(c))
1287
+ const page = Cors.appOrigin(c)
1288
+ if (page && !c.var.firstParty?.length)
1260
1289
  return ErrorResponse.json(c, new ErrorResponse.CrossSiteRequestError())
1290
+ // A registered passkey signs in as the user, so an app's page registers one only for an app
1291
+ // that the deployment trusts with its own challenges.
1292
+ if (page && !c.var.firstParty?.some((app) => app.sign))
1293
+ return ErrorResponse.json(
1294
+ c,
1295
+ new Passkey.RegisterNotPermittedError({ origin: page }),
1296
+ )
1261
1297
 
1262
1298
  const session = await Timing.measure(c, Session.get(c), 'Session.get')
1263
1299
  if (!session)
@@ -1291,10 +1327,11 @@ export const account = new Hono<Env>()
1291
1327
  // Only the attested public key verifies the proof, so the user holds its private key.
1292
1328
  if (
1293
1329
  !proof ||
1330
+ proof.origin !== page ||
1294
1331
  assertion.id !== proof.id ||
1295
1332
  !Passkey.verify(assertion, {
1296
1333
  challenge: proof.challenge,
1297
- origin: new URL(env.URL).origin,
1334
+ origin: proof.origin ?? new URL(env.URL).origin,
1298
1335
  publicKey: proof.publicKey,
1299
1336
  rpId: proof.rpId,
1300
1337
  })
@@ -1399,7 +1436,7 @@ export const account = new Hono<Env>()
1399
1436
  '/v1/realms',
1400
1437
  OpenApi.describeRoute({
1401
1438
  description:
1402
- 'Lists the realms of the app that `client_id` names, in order, and whether its passkey sign-in runs on the page that sends the request. An app without realms reaches the `default` realm alone.',
1439
+ 'Lists the realms of the `client_id` app, and whether passkey sign-in works on the page that sends the request.',
1403
1440
  responses: OpenApi.responses({
1404
1441
  errors: [...OpenApi.validate.errors],
1405
1442
  success: {
@@ -1459,7 +1496,7 @@ export const account = new Hono<Env>()
1459
1496
  '/v1/chains',
1460
1497
  OpenApi.describeRoute({
1461
1498
  description:
1462
- 'Lists the chains that the `chain` parameter selects, each with its chain ID: `mainnet`, `testnet`, then the chains that the deployment lists.',
1499
+ 'Lists the chains that the `chain` parameter accepts, with their chain IDs.',
1463
1500
  responses: OpenApi.responses({
1464
1501
  success: {
1465
1502
  description: 'The chains.',
@@ -1474,7 +1511,7 @@ export const account = new Hono<Env>()
1474
1511
  '/i/chains',
1475
1512
  OpenApi.describeRoute({
1476
1513
  description:
1477
- "Lists the chains that the `chain` parameter of `GET /v1/account` and `POST /v1/account` selects, as `GET /v1/chains` does, each with whether its faucet funds accounts. Internal: only Tempo ID's own pages send the request, so the public document leaves it out.",
1514
+ "Lists the chains, as `GET /v1/chains` does, with whether each has a faucet. Internal to Tempo ID's own pages.",
1478
1515
  // Internal routes stay out of `/openapi.json` and `/docs`.
1479
1516
  hide: true,
1480
1517
  responses: OpenApi.responses({
@@ -1504,7 +1541,7 @@ export const account = new Hono<Env>()
1504
1541
  '/i/chains/status',
1505
1542
  OpenApi.describeRoute({
1506
1543
  description:
1507
- "Returns whether the node and the Tempo API of each chain that the deployment lists respond, apart from `mainnet` and `testnet`. A probe that fails or takes longer than a second reads as `down`, and a status can be up to 2 seconds old. Internal: only Tempo ID's own page sends the request, so the public document leaves it out.",
1544
+ "Returns whether the node and the Tempo API of each added chain respond. Internal to Tempo ID's own pages.",
1508
1545
  // Internal routes stay out of `/openapi.json` and `/docs`.
1509
1546
  hide: true,
1510
1547
  responses: OpenApi.responses({
@@ -1555,7 +1592,7 @@ export const account = new Hono<Env>()
1555
1592
  '/i/account/fund',
1556
1593
  OpenApi.describeRoute({
1557
1594
  description:
1558
- "Funds one of the signed-in user's Tempo Accounts in the `default` realm from the faucet of the test chain that `chain` names, and waits until each transfer lands. Tempo mainnet has no faucet. Internal: only Tempo ID's own page sends the request, so the public document leaves it out.",
1595
+ "Funds one of the user's Tempo Accounts from the faucet of a test chain, and waits for the transfers. Internal to Tempo ID's own pages.",
1559
1596
  // Internal routes stay out of `/openapi.json` and `/docs`.
1560
1597
  hide: true,
1561
1598
  responses: OpenApi.responses({
@@ -1670,7 +1707,7 @@ export const account = new Hono<Env>()
1670
1707
  '/v1/account/upgrade',
1671
1708
  OpenApi.describeRoute({
1672
1709
  description:
1673
- "Upgrades a wallet.tempo passkey's Tempo Account of the signed-in user in place to a configurable account (TIP-1113), on a chain whose accounts are configurable. The account keeps its address, and the passkey stays its only owner. The first request returns the passkey step, and the repeat with `passkey.assertion` sends the upgrade. With `key_authorization`, Tempo ID first registers that key, sponsored, so it keeps working. Requires a sign-in within the last 10 minutes.",
1710
+ "Upgrades a wallet.tempo passkey's Tempo Account to a configurable account (TIP-1113) at the same address. The first request returns a passkey step, and the repeat with `passkey.assertion` sends the upgrade.",
1674
1711
  responses: OpenApi.responses({
1675
1712
  errors: [
1676
1713
  ...BodyLimit.errors,
@@ -1884,7 +1921,7 @@ export const account = new Hono<Env>()
1884
1921
  '/i/account/passkeys',
1885
1922
  OpenApi.describeRoute({
1886
1923
  description:
1887
- "Lists the wallet.tempo passkeys of the signed-in user's email that link to no user yet, which `POST /v1/account/upgrade` takes as `credentialId`. The sign-in proved the email, so the list reveals nothing to another person. Counts against the user's `ACCOUNT_LIMIT`. Refuses a cross-site navigation with `cross_site_request`. Internal: only Tempo ID's own page sends the request, so the public document leaves it out.",
1924
+ "Lists the wallet.tempo passkeys of the user's email that are not linked yet, for `POST /v1/account/upgrade`. Internal to Tempo ID's own pages.",
1888
1925
  // Internal routes stay out of `/openapi.json` and `/docs`.
1889
1926
  hide: true,
1890
1927
  responses: OpenApi.responses({
@@ -1942,7 +1979,7 @@ export const account = new Hono<Env>()
1942
1979
  '/i/account/upgrades',
1943
1980
  OpenApi.describeRoute({
1944
1981
  description:
1945
- "Lists the upgrades that wait for the signed-in user on `chain`, which the sign-in page offers, when `CONFIGURABLE_ACCOUNTS` names it. Each linked wallet.tempo passkey's account without a configuration on the chain comes first. For a user who holds no account, each wallet.tempo passkey of the email that links to no user follows. `POST /v1/account/upgrade` takes either. Reads stored rows and wallet.tempo, never a chain. Counts against the user's `ACCOUNT_LIMIT`. Refuses a cross-site navigation with `cross_site_request`. Internal: only Tempo ID's own page sends the request, so the public document leaves it out.",
1982
+ "Lists the user's pending upgrades on `chain`, for `POST /v1/account/upgrade`. Internal to Tempo ID's own pages.",
1946
1983
  // Internal routes stay out of `/openapi.json` and `/docs`.
1947
1984
  hide: true,
1948
1985
  responses: OpenApi.responses({