@rhinestone/1auth 0.7.3 → 0.7.5

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 (42) hide show
  1. package/dist/{chunk-IHBVEU33.mjs → chunk-6AHEN3FA.mjs} +1 -1
  2. package/dist/chunk-6AHEN3FA.mjs.map +1 -0
  3. package/dist/{chunk-UXBBB7AV.mjs → chunk-6MUQKHAT.mjs} +7 -31
  4. package/dist/chunk-6MUQKHAT.mjs.map +1 -0
  5. package/dist/{client-CuASptX3.d.ts → client-BqKAd2RZ.d.mts} +17 -17
  6. package/dist/{client-D4HVy1KF.d.mts → client-C9mcj5jN.d.ts} +17 -17
  7. package/dist/headless.d.mts +2 -2
  8. package/dist/headless.d.ts +2 -2
  9. package/dist/headless.js +4 -6
  10. package/dist/headless.js.map +1 -1
  11. package/dist/headless.mjs +4 -6
  12. package/dist/headless.mjs.map +1 -1
  13. package/dist/index.d.mts +12 -21
  14. package/dist/index.d.ts +12 -21
  15. package/dist/index.js +44 -99
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +40 -71
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/{provider-6JoziYDq.d.ts → provider-CCyUBseo.d.ts} +3 -3
  20. package/dist/{provider-CAQT7Gqx.d.mts → provider-CRhZVLg8.d.mts} +3 -3
  21. package/dist/react.d.mts +9 -4
  22. package/dist/react.d.ts +9 -4
  23. package/dist/react.js +4 -7
  24. package/dist/react.js.map +1 -1
  25. package/dist/react.mjs +4 -7
  26. package/dist/react.mjs.map +1 -1
  27. package/dist/server.d.mts +2 -2
  28. package/dist/server.d.ts +2 -2
  29. package/dist/server.js.map +1 -1
  30. package/dist/server.mjs +1 -1
  31. package/dist/{types-C0jKNT_t.d.mts → types-CbhPRY5s.d.mts} +23 -50
  32. package/dist/{types-C0jKNT_t.d.ts → types-CbhPRY5s.d.ts} +23 -50
  33. package/dist/{verify-CnOwPq78.d.ts → verify-DPJR_foR.d.ts} +4 -6
  34. package/dist/{verify-aWdi5O2z.d.mts → verify-DVYDJgOS.d.mts} +4 -6
  35. package/dist/wagmi.d.mts +3 -3
  36. package/dist/wagmi.d.ts +3 -3
  37. package/dist/wagmi.js +6 -30
  38. package/dist/wagmi.js.map +1 -1
  39. package/dist/wagmi.mjs +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunk-IHBVEU33.mjs.map +0 -1
  42. package/dist/chunk-UXBBB7AV.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { O as OneAuthClient } from './client-CuASptX3.js';
2
- import { a0 as CloseOnStatus } from './types-C0jKNT_t.js';
1
+ import { O as OneAuthClient } from './client-C9mcj5jN.js';
2
+ import { a0 as CloseOnStatus } from './types-CbhPRY5s.js';
3
3
 
4
4
  /**
5
5
  * EIP-1193 JSON-RPC provider factory for the 1auth passkey authentication system.
@@ -46,7 +46,7 @@ type OneAuthProviderOptions = {
46
46
  *
47
47
  * The returned provider object can be used anywhere a standard Ethereum provider is
48
48
  * accepted (e.g. passed to `createWalletClient` in viem, used as `window.ethereum`, or
49
- * supplied to ethers.js `BrowserProvider`). Connection state (address + username) is
49
+ * supplied to ethers.js `BrowserProvider`). Connection state (the account address) is
50
50
  * persisted in `localStorage` under the configured `storageKey`.
51
51
  *
52
52
  * @param options - Provider configuration
@@ -1,5 +1,5 @@
1
- import { O as OneAuthClient } from './client-D4HVy1KF.mjs';
2
- import { a0 as CloseOnStatus } from './types-C0jKNT_t.mjs';
1
+ import { O as OneAuthClient } from './client-BqKAd2RZ.mjs';
2
+ import { a0 as CloseOnStatus } from './types-CbhPRY5s.mjs';
3
3
 
4
4
  /**
5
5
  * EIP-1193 JSON-RPC provider factory for the 1auth passkey authentication system.
@@ -46,7 +46,7 @@ type OneAuthProviderOptions = {
46
46
  *
47
47
  * The returned provider object can be used anywhere a standard Ethereum provider is
48
48
  * accepted (e.g. passed to `createWalletClient` in viem, used as `window.ethereum`, or
49
- * supplied to ethers.js `BrowserProvider`). Connection state (address + username) is
49
+ * supplied to ethers.js `BrowserProvider`). Connection state (the account address) is
50
50
  * persisted in `localStorage` under the configured `storageKey`.
51
51
  *
52
52
  * @param options - Provider configuration
package/dist/react.d.mts CHANGED
@@ -1,15 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { O as OneAuthClient } from './client-D4HVy1KF.mjs';
4
- import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-C0jKNT_t.mjs';
3
+ import { O as OneAuthClient } from './client-BqKAd2RZ.mjs';
4
+ import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-CbhPRY5s.mjs';
5
5
  import 'viem';
6
6
  import '@rhinestone/sdk';
7
7
 
8
8
  interface PayButtonProps {
9
9
  /** The OneAuthClient instance */
10
10
  client: OneAuthClient;
11
- /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */
12
- intent: Omit<SendIntentOptions, "username" | "closeOn">;
11
+ /**
12
+ * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted
13
+ * because PayButton resolves it internally (cached session or auth modal) and
14
+ * overwrites it on the sendIntent call — callers pass only calls + target
15
+ * chain, keeping the drop-in payment API intact.
16
+ */
17
+ intent: Omit<SendIntentOptions, "closeOn" | "accountAddress">;
13
18
  /** Called when payment succeeds */
14
19
  onSuccess?: (result: SendIntentResult) => void;
15
20
  /** Called when payment fails */
package/dist/react.d.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { O as OneAuthClient } from './client-CuASptX3.js';
4
- import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-C0jKNT_t.js';
3
+ import { O as OneAuthClient } from './client-C9mcj5jN.js';
4
+ import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-CbhPRY5s.js';
5
5
  import 'viem';
6
6
  import '@rhinestone/sdk';
7
7
 
8
8
  interface PayButtonProps {
9
9
  /** The OneAuthClient instance */
10
10
  client: OneAuthClient;
11
- /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */
12
- intent: Omit<SendIntentOptions, "username" | "closeOn">;
11
+ /**
12
+ * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted
13
+ * because PayButton resolves it internally (cached session or auth modal) and
14
+ * overwrites it on the sendIntent call — callers pass only calls + target
15
+ * chain, keeping the drop-in payment API intact.
16
+ */
17
+ intent: Omit<SendIntentOptions, "closeOn" | "accountAddress">;
13
18
  /** Called when payment succeeds */
14
19
  onSuccess?: (result: SendIntentResult) => void;
15
20
  /** Called when payment fails */
package/dist/react.js CHANGED
@@ -160,27 +160,23 @@ function PayButton({
160
160
  }
161
161
  };
162
162
  const executePayment = async (forceReauth = false) => {
163
- let username = null;
164
163
  let accountAddress = null;
165
164
  if (!forceReauth) {
166
165
  const savedUser = localStorage.getItem("1auth-user");
167
166
  if (savedUser) {
168
167
  try {
169
168
  const parsed = JSON.parse(savedUser);
170
- username = parsed.username || null;
171
169
  accountAddress = parsed.address || null;
172
170
  } catch {
173
171
  localStorage.removeItem("1auth-user");
174
172
  }
175
173
  }
176
174
  }
177
- if (!username && !accountAddress) {
175
+ if (!accountAddress) {
178
176
  const authResult = await client.authWithModal();
179
177
  if (authResult.success && authResult.user) {
180
- username = authResult.user.username || null;
181
178
  accountAddress = authResult.user.address || null;
182
179
  const stored = {};
183
- if (username) stored.username = username;
184
180
  if (accountAddress) stored.address = accountAddress;
185
181
  if (authResult.signerType) stored.signerType = authResult.signerType;
186
182
  localStorage.setItem("1auth-user", JSON.stringify(stored));
@@ -192,8 +188,9 @@ function PayButton({
192
188
  try {
193
189
  result = await client.sendIntent({
194
190
  ...intent,
195
- username: username || void 0,
196
- accountAddress: accountAddress || void 0,
191
+ // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard
192
+ // above either resolved an address or bailed out before reaching here.
193
+ accountAddress,
197
194
  closeOn
198
195
  });
199
196
  } catch (err) {
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx","../src/sponsorship-fetch.ts"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */\n intent: Omit<SendIntentOptions, \"username\" | \"closeOn\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ username, address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached username/address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing user from localStorage\n let username: string | null = null;\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n username = parsed.username || null;\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no user (or forced reauth), authenticate first\n if (!username && !accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n username = authResult.user.username || null;\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (username) stored.username = username;\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n username: username || undefined,\n accountAddress: accountAddress || undefined,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n","/**\n * Shared helpers for fetching sponsorship tokens (access + extension) from an\n * integrator's backend endpoints.\n *\n * The integrator's token routes return their failure reason in the JSON body\n * (`{ error: \"createSponsorshipSigner: missing env var(s): ...\" }`). Surfacing\n * only the HTTP status — \"Failed to fetch access token (500 ...)\" — hides that\n * reason and turns every misconfiguration into the same opaque dialog error.\n * These helpers read the body and fold the server's message into the thrown\n * Error so the actual cause (missing env var, bad JWK, expired session) is\n * visible at the call site and in the dialog.\n */\n\n/**\n * Pull a human-readable reason out of a failed token response. Tries the JSON\n * `error`/`message` fields first, then falls back to a short text snippet, then\n * to the HTTP status text. Never throws — diagnostics must not mask the original\n * failure with a parse error.\n */\nasync function extractServerReason(response: Response): Promise<string> {\n try {\n // Clone so a JSON parse failure can still fall back to reading text.\n const text = await response.clone().text();\n if (!text) return response.statusText;\n try {\n const data = JSON.parse(text) as { error?: unknown; message?: unknown };\n const reason = data.error ?? data.message;\n if (typeof reason === \"string\" && reason.trim()) return reason;\n } catch {\n // Body wasn't JSON — fall through to the raw text snippet.\n }\n // Cap the snippet so an HTML error page doesn't flood the message.\n return text.slice(0, 200);\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Build an Error for a failed token fetch that includes the server's reason.\n * `label` names the token kind, e.g. \"access token\" or \"extension token\".\n */\nexport async function tokenFetchError(\n label: string,\n response: Response,\n): Promise<Error> {\n const reason = await extractServerReason(response);\n const suffix = reason ? `: ${reason}` : \"\";\n return new Error(\n `Failed to fetch ${label} (${response.status} ${response.statusText})${suffix}`,\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.tsx","../src/sponsorship-fetch.ts"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /**\n * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted\n * because PayButton resolves it internally (cached session or auth modal) and\n * overwrites it on the sendIntent call — callers pass only calls + target\n * chain, keeping the drop-in payment API intact.\n */\n intent: Omit<SendIntentOptions, \"closeOn\" | \"accountAddress\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached account address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing account address from localStorage\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no account address (or forced reauth), authenticate first\n if (!accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard\n // above either resolved an address or bailed out before reaching here.\n accountAddress,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n","/**\n * Shared helpers for fetching sponsorship tokens (access + extension) from an\n * integrator's backend endpoints.\n *\n * The integrator's token routes return their failure reason in the JSON body\n * (`{ error: \"createSponsorshipSigner: missing env var(s): ...\" }`). Surfacing\n * only the HTTP status — \"Failed to fetch access token (500 ...)\" — hides that\n * reason and turns every misconfiguration into the same opaque dialog error.\n * These helpers read the body and fold the server's message into the thrown\n * Error so the actual cause (missing env var, bad JWK, expired session) is\n * visible at the call site and in the dialog.\n */\n\n/**\n * Pull a human-readable reason out of a failed token response. Tries the JSON\n * `error`/`message` fields first, then falls back to a short text snippet, then\n * to the HTTP status text. Never throws — diagnostics must not mask the original\n * failure with a parse error.\n */\nasync function extractServerReason(response: Response): Promise<string> {\n try {\n // Clone so a JSON parse failure can still fall back to reading text.\n const text = await response.clone().text();\n if (!text) return response.statusText;\n try {\n const data = JSON.parse(text) as { error?: unknown; message?: unknown };\n const reason = data.error ?? data.message;\n if (typeof reason === \"string\" && reason.trim()) return reason;\n } catch {\n // Body wasn't JSON — fall through to the raw text snippet.\n }\n // Cap the snippet so an HTML error page doesn't flood the message.\n return text.slice(0, 200);\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Build an Error for a failed token fetch that includes the server's reason.\n * `label` names the token kind, e.g. \"access token\" or \"extension token\".\n */\nexport async function tokenFetchError(\n label: string,\n response: Response,\n): Promise<Error> {\n const reason = await extractServerReason(response);\n const suffix = reason ? `: ${reason}` : \"\";\n return new Error(\n `Failed to fetch ${label} (${response.status} ${response.statusText})${suffix}`,\n );\n}\n"],"mappings":"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","names":[]}
package/dist/react.mjs CHANGED
@@ -101,27 +101,23 @@ function PayButton({
101
101
  }
102
102
  };
103
103
  const executePayment = async (forceReauth = false) => {
104
- let username = null;
105
104
  let accountAddress = null;
106
105
  if (!forceReauth) {
107
106
  const savedUser = localStorage.getItem("1auth-user");
108
107
  if (savedUser) {
109
108
  try {
110
109
  const parsed = JSON.parse(savedUser);
111
- username = parsed.username || null;
112
110
  accountAddress = parsed.address || null;
113
111
  } catch {
114
112
  localStorage.removeItem("1auth-user");
115
113
  }
116
114
  }
117
115
  }
118
- if (!username && !accountAddress) {
116
+ if (!accountAddress) {
119
117
  const authResult = await client.authWithModal();
120
118
  if (authResult.success && authResult.user) {
121
- username = authResult.user.username || null;
122
119
  accountAddress = authResult.user.address || null;
123
120
  const stored = {};
124
- if (username) stored.username = username;
125
121
  if (accountAddress) stored.address = accountAddress;
126
122
  if (authResult.signerType) stored.signerType = authResult.signerType;
127
123
  localStorage.setItem("1auth-user", JSON.stringify(stored));
@@ -133,8 +129,9 @@ function PayButton({
133
129
  try {
134
130
  result = await client.sendIntent({
135
131
  ...intent,
136
- username: username || void 0,
137
- accountAddress: accountAddress || void 0,
132
+ // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard
133
+ // above either resolved an address or bailed out before reaching here.
134
+ accountAddress,
138
135
  closeOn
139
136
  });
140
137
  } catch (err) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */\n intent: Omit<SendIntentOptions, \"username\" | \"closeOn\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ username, address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached username/address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing user from localStorage\n let username: string | null = null;\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n username = parsed.username || null;\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no user (or forced reauth), authenticate first\n if (!username && !accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n username = authResult.user.username || null;\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (username) stored.username = username;\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n username: username || undefined,\n accountAddress: accountAddress || undefined,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.tsx"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /**\n * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted\n * because PayButton resolves it internally (cached session or auth modal) and\n * overwrites it on the sendIntent call — callers pass only calls + target\n * chain, keeping the drop-in payment API intact.\n */\n intent: Omit<SendIntentOptions, \"closeOn\" | \"accountAddress\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached account address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing account address from localStorage\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no account address (or forced reauth), authenticate first\n if (!accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard\n // above either resolved an address or bailed out before reaching here.\n accountAddress,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n"],"mappings":"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","names":[]}
package/dist/server.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Address, Hex } from 'viem';
2
- export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-aWdi5O2z.mjs';
3
- export { W as WebAuthnSignature } from './types-C0jKNT_t.mjs';
2
+ export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-DVYDJgOS.mjs';
3
+ export { W as WebAuthnSignature } from './types-CbhPRY5s.mjs';
4
4
  import '@rhinestone/sdk';
5
5
 
6
6
  /**
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Address, Hex } from 'viem';
2
- export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-CnOwPq78.js';
3
- export { W as WebAuthnSignature } from './types-C0jKNT_t.js';
2
+ export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-DPJR_foR.js';
3
+ export { W as WebAuthnSignature } from './types-CbhPRY5s.js';
4
4
  import '@rhinestone/sdk';
5
5
 
6
6
  /**
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/verify.ts","../src/walletClient/utils.ts","../src/registry.ts"],"sourcesContent":["/**\n * Server-side utilities for JWT sponsorship.\n *\n * The recommended entry point is `createSponsorshipSigner`, a\n * framework-agnostic factory that reads credentials from environment\n * variables and exposes the two operations each sponsorship endpoint\n * needs: `accessToken()` and `extensionToken(intentOp)`.\n *\n * For custom claims or non-standard flows, `createJwtSigner` is exposed as\n * an escape hatch. The implementation lives here instead of re-exporting\n * `@rhinestone/sdk/jwt-server` directly so downstream app builds do not need\n * to resolve the SDK's optional Express adapter surface.\n *\n * @example Next.js App Router\n * ```ts\n * // app/api/sponsorship/access-token/route.ts\n * import { createSponsorshipSigner } from \"@rhinestone/1auth/server\";\n *\n * const signer = createSponsorshipSigner();\n *\n * export async function GET() {\n * // Guard with your own session/auth before minting.\n * return Response.json({ token: await signer.accessToken() });\n * }\n * ```\n */\n\nimport { SignJWT, importJWK } from \"jose\";\nimport { isAddress, type Address, type Hex } from \"viem\";\n\nexport { hashMessage, verifyMessageHash } from \"./verify\";\nexport { encodeWebAuthnSignature } from \"./walletClient/utils\";\nexport type { WebAuthnSignature } from \"./types\";\n// Re-exported so sponsorship endpoints can build a `shouldSponsor` chain\n// policy without importing the browser-oriented main entry. `isTestnet` fails\n// closed (unknown/unrecognised chain id → false), which is the safe default\n// for a sponsorship gate: deny anything we can't positively classify.\nexport { isTestnet } from \"./registry\";\n\nexport interface JwtCredentials {\n privateKey: JsonWebKey;\n integratorId: string;\n projectId: string;\n appId: string;\n keyId: string;\n audience?: string;\n}\n\nexport interface SponsorshipFilter {\n chain?: (chain: { id: number }) => boolean | Promise<boolean>;\n account?: (address: Address) => boolean | Promise<boolean>;\n calls?: (\n calls: {\n to: Address;\n value: bigint;\n data: Hex;\n }[],\n ) => boolean | Promise<boolean>;\n}\n\nexport interface JwtSignerConfig {\n jwt: JwtCredentials;\n shouldSponsor?: SponsorshipFilter;\n}\n\nexport class SponsorshipDeniedError extends Error {\n constructor() {\n super(\"Sponsorship denied\");\n this.name = \"SponsorshipDeniedError\";\n }\n}\n\n/**\n * Sponsorship signer used by the two endpoints the SDK client calls:\n *\n * - `accessToken()` — mints the app's identity token; return as\n * `{ token }` from `GET /sponsorship/access-token`.\n * - `extensionToken(intentOp)` — mints a per-intent sponsorship grant;\n * return as `{ token }` from `POST /sponsorship/extension-token`.\n * Accepts either the JSON-stringified `intentOp` the SDK sends or a\n * pre-parsed object.\n */\nexport interface SponsorshipSigner {\n accessToken(): Promise<string>;\n extensionToken(intentOp: string | object): Promise<string>;\n}\n\nexport interface CreateSponsorshipSignerOptions {\n /** Override all credentials explicitly (skips env lookup). */\n credentials?: JwtCredentials;\n /** Override the env source (defaults to `process.env`). */\n env?: NodeJS.ProcessEnv;\n /** Optional per-intent sponsorship filter (see `@rhinestone/sdk/jwt-server`). */\n shouldSponsor?: SponsorshipFilter;\n}\n\nexport interface VerifyOneAuthAccountOptions {\n /** 1auth provider origin, for example `https://passkey.1auth.app`. */\n providerUrl: string;\n /** Smart account address returned by a successful 1auth auth result. */\n address: Address;\n /** Optional app client id forwarded to provider APIs that scope by app. */\n clientId?: string;\n /** Injectable fetch implementation for tests and non-standard runtimes. */\n fetch?: typeof fetch;\n}\n\nexport interface VerifiedOneAuthAccount {\n address: Address;\n deployed?: boolean;\n chainId?: number;\n}\n\n/** Error raised when the provider cannot bind an address to a 1auth account. */\nexport class OneAuthAccountVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OneAuthAccountVerificationError\";\n }\n}\n\nconst ENV_KEYS = {\n privateKey: \"RHINESTONE_JWT_PRIVATE_KEY\",\n integratorId: \"RHINESTONE_INTEGRATOR_ID\",\n projectId: \"RHINESTONE_PROJECT_ID\",\n appId: \"RHINESTONE_APP_ID\",\n keyId: \"RHINESTONE_KEY_ID\",\n} as const;\n\n/** Build the documented account-lookup URL for a 1auth provider. */\nfunction accountLookupUrl(providerUrl: string, address: Address): string {\n return new URL(`/api/account/${address.toLowerCase()}`, providerUrl).toString();\n}\n\n/** Safely parse a provider account lookup response. */\nasync function parseAccountLookupResponse(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\n/**\n * Verify that an address belongs to a 1auth account known by the configured\n * provider. This is intended for server-side app sessions that already have a\n * fresh challenge signature and need to reject arbitrary ERC-1271 contracts.\n */\nexport async function verifyOneAuthAccount(\n options: VerifyOneAuthAccountOptions,\n): Promise<VerifiedOneAuthAccount> {\n if (!isAddress(options.address)) {\n throw new OneAuthAccountVerificationError(\n \"A valid 1auth account address is required\",\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new OneAuthAccountVerificationError(\n \"No fetch implementation is available to verify the 1auth account\",\n );\n }\n\n const url = accountLookupUrl(options.providerUrl, options.address);\n const headers: HeadersInit = { accept: \"application/json\" };\n if (options.clientId) headers[\"x-client-id\"] = options.clientId;\n\n let response: Response;\n try {\n response = await fetchImpl(url, {\n headers,\n cache: \"no-store\",\n });\n } catch (error) {\n throw new OneAuthAccountVerificationError(\n `Unable to reach the configured 1auth provider: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n const body = await parseAccountLookupResponse(response);\n if (!response.ok) {\n const message =\n body && typeof body === \"object\" && \"error\" in body\n ? String((body as { error: unknown }).error)\n : response.statusText || \"account lookup failed\";\n throw new OneAuthAccountVerificationError(\n `Configured 1auth provider did not recognize this account (${response.status}: ${message})`,\n );\n }\n\n const account = body as Partial<VerifiedOneAuthAccount> | null;\n if (!account?.address || !isAddress(account.address)) {\n throw new OneAuthAccountVerificationError(\n \"Configured 1auth provider returned a malformed account record\",\n );\n }\n if (account.address.toLowerCase() !== options.address.toLowerCase()) {\n throw new OneAuthAccountVerificationError(\n \"Configured 1auth provider returned a different account address\",\n );\n }\n\n return {\n address: account.address,\n deployed:\n typeof account.deployed === \"boolean\" ? account.deployed : undefined,\n chainId: typeof account.chainId === \"number\" ? account.chainId : undefined,\n };\n}\n\n/**\n * RFC 8785 JSON Canonicalization Scheme (JCS).\n *\n * The digest embedded in extension tokens must be byte-for-byte stable across\n * environments, so we canonicalize JSON before hashing instead of trusting\n * ordinary `JSON.stringify` object key order.\n */\nfunction jcsCanonicalise(value: unknown): string {\n return serialize(value);\n}\n\nfunction serialize(value: unknown): string {\n if (value === null || value === undefined) {\n return \"null\";\n }\n\n switch (typeof value) {\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"number\":\n if (!Number.isFinite(value)) {\n throw new Error(`JCS: non-finite number: ${value}`);\n }\n return Object.is(value, -0) ? \"0\" : String(value);\n case \"string\":\n return JSON.stringify(value);\n case \"bigint\":\n if (\n value > BigInt(Number.MAX_SAFE_INTEGER) ||\n value < BigInt(-Number.MAX_SAFE_INTEGER)\n ) {\n throw new Error(\n `JCS: BigInt ${value} exceeds safe integer range — convert to string before calling`,\n );\n }\n return String(value);\n default:\n break;\n }\n\n if (Array.isArray(value)) {\n return `[${value.map((item) => serialize(item)).join(\",\")}]`;\n }\n\n const obj = value as Record<string, unknown>;\n const members: string[] = [];\n for (const key of Object.keys(obj).sort()) {\n const item = obj[key];\n if (item === undefined) continue;\n members.push(`${JSON.stringify(key)}:${serialize(item)}`);\n }\n return `{${members.join(\",\")}}`;\n}\n\n/**\n * Compute the canonical digest embedded in per-intent sponsorship grants.\n */\nasync function computeIntentInputDigest(intentInput: unknown): Promise<string> {\n const canonical = jcsCanonicalise(intentInput);\n const encoded = new TextEncoder().encode(canonical);\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encoded);\n return Array.from(new Uint8Array(hashBuffer))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction parseIntentInput(intentInput: unknown) {\n if (typeof intentInput !== \"object\" || intentInput === null) {\n throw new Error(\"intentInput must be a non-null object\");\n }\n\n const input = intentInput as {\n destinationChainId?: number;\n account?: { address?: string };\n destinationExecutions?: { to: Address; value: string; data: Hex }[];\n };\n\n if (typeof input.destinationChainId !== \"number\") {\n throw new Error(\"intentInput.destinationChainId must be a number\");\n }\n\n if (typeof input.account !== \"object\" || input.account === null) {\n throw new Error(\"intentInput.account must be a non-null object\");\n }\n\n if (typeof input.account.address !== \"string\") {\n throw new Error(\"intentInput.account.address must be a string\");\n }\n\n if (!Array.isArray(input.destinationExecutions)) {\n throw new Error(\"intentInput.destinationExecutions must be an array\");\n }\n\n return {\n chain: { id: input.destinationChainId },\n account: input.account.address as Address,\n calls: input.destinationExecutions.map((execution) => ({\n to: execution.to,\n value: BigInt(execution.value),\n data: execution.data,\n })),\n };\n}\n\nasync function shouldSponsor(\n intentInput: unknown,\n filters: SponsorshipFilter,\n): Promise<boolean> {\n const parsed = parseIntentInput(intentInput);\n if (filters.chain && !(await filters.chain(parsed.chain))) {\n return false;\n }\n if (filters.account && !(await filters.account(parsed.account))) {\n return false;\n }\n if (filters.calls && !(await filters.calls(parsed.calls))) {\n return false;\n }\n return true;\n}\n\nfunction pickAlg(jwk: JsonWebKey): \"ES256\" | \"ES384\" | \"ES512\" | \"RS256\" {\n if (jwk.kty === \"EC\") {\n if (jwk.crv === \"P-256\") return \"ES256\";\n if (jwk.crv === \"P-384\") return \"ES384\";\n if (jwk.crv === \"P-521\") return \"ES512\";\n throw new Error(`Unsupported EC curve: ${jwk.crv}`);\n }\n if (jwk.kty === \"RSA\") return \"RS256\";\n throw new Error(`Unsupported JWK kty: ${jwk.kty}`);\n}\n\n/**\n * Create a low-level JWT signer compatible with Rhinestone sponsorship flows.\n *\n * This mirrors the upstream SDK behavior while keeping our public server entry\n * point self-contained for app bundlers.\n */\nexport function createJwtSigner(config: JwtSignerConfig) {\n const {\n jwt: {\n privateKey,\n integratorId,\n projectId,\n appId,\n keyId,\n audience = \"rhinestone-api\",\n },\n shouldSponsor: filters,\n } = config;\n\n const alg = pickAlg(privateKey);\n let cachedKey: Awaited<ReturnType<typeof importJWK>> | null = null;\n\n async function getKey() {\n if (!cachedKey) {\n cachedKey = await importJWK(privateKey, alg);\n }\n return cachedKey;\n }\n\n return {\n accessToken: async () => {\n const key = await getKey();\n return new SignJWT({ typ: \"access\", app_id: appId })\n .setProtectedHeader({ alg, kid: keyId })\n .setIssuer(integratorId)\n .setSubject(projectId)\n .setAudience(audience)\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(key);\n },\n getIntentExtensionToken: async (intentInput: unknown) => {\n if (filters && !(await shouldSponsor(intentInput, filters))) {\n throw new SponsorshipDeniedError();\n }\n\n const key = await getKey();\n const digest = await computeIntentInputDigest(intentInput);\n return new SignJWT({\n typ: \"intent_extension\",\n app_id: appId,\n jti: crypto.randomUUID(),\n policy: {\n sponsorship: {\n scope: \"intent\",\n intent_input: { digest },\n },\n },\n })\n .setProtectedHeader({ alg, kid: keyId })\n .setIssuer(integratorId)\n .setSubject(projectId)\n .setAudience(audience)\n .setIssuedAt()\n .setExpirationTime(\"5m\")\n .sign(key);\n },\n };\n}\n\nfunction readCredentialsFromEnv(env: NodeJS.ProcessEnv): JwtCredentials {\n const missing: string[] = [];\n const read = (key: string) => {\n const value = env[key];\n if (!value) missing.push(key);\n return value ?? \"\";\n };\n\n const privateKeyRaw = read(ENV_KEYS.privateKey);\n const integratorId = read(ENV_KEYS.integratorId);\n const projectId = read(ENV_KEYS.projectId);\n const appId = read(ENV_KEYS.appId);\n const keyId = read(ENV_KEYS.keyId);\n\n if (missing.length > 0) {\n throw new Error(\n `createSponsorshipSigner: missing env var(s): ${missing.join(\", \")}`,\n );\n }\n\n let privateKey: JsonWebKey;\n try {\n privateKey = JSON.parse(privateKeyRaw) as JsonWebKey;\n } catch {\n throw new Error(\n `createSponsorshipSigner: ${ENV_KEYS.privateKey} must be a JSON-encoded JWK`,\n );\n }\n\n return { privateKey, integratorId, projectId, appId, keyId };\n}\n\nexport function createSponsorshipSigner(\n options: CreateSponsorshipSignerOptions = {},\n): SponsorshipSigner {\n const credentials =\n options.credentials ?? readCredentialsFromEnv(options.env ?? process.env);\n\n const inner = createJwtSigner({\n jwt: credentials,\n shouldSponsor: options.shouldSponsor,\n });\n\n return {\n accessToken: () => inner.accessToken(),\n extensionToken: (intentOp) => {\n const intentInput =\n typeof intentOp === \"string\" ? JSON.parse(intentOp) : intentOp;\n return inner.getIntentExtensionToken(intentInput);\n },\n };\n}\n","import { keccak256, toBytes } from \"viem\";\n\n/**\n * The EIP-191 prefix used for personal message signing.\n * This is the standard Ethereum message prefix for `personal_sign`.\n */\nexport const ETHEREUM_MESSAGE_PREFIX = \"\\x19Ethereum Signed Message:\\n\";\n\n/**\n * Hash a message with the EIP-191 Ethereum prefix.\n *\n * This is the same hashing function used by the passkey sign dialog.\n * Use this to verify that the `signedHash` returned from `signMessage()`\n * matches your original message.\n *\n * Format: keccak256(\"\\x19Ethereum Signed Message:\\n\" + len + message)\n *\n * @example\n * ```typescript\n * const message = \"Sign in to MyApp\\nTimestamp: 1234567890\";\n * const result = await client.signMessage({ username: 'alice', message });\n *\n * // Verify the hash matches\n * const expectedHash = hashMessage(message);\n * if (result.signedHash === expectedHash) {\n * console.log('Hash matches - signature is for this message');\n * }\n * ```\n */\nexport function hashMessage(message: string): `0x${string}` {\n const messageBytes = toBytes(message);\n const prefixed = ETHEREUM_MESSAGE_PREFIX + messageBytes.length.toString() + message;\n return keccak256(toBytes(prefixed));\n}\n\n/**\n * Verify that a signedHash matches the expected message.\n *\n * This is a convenience wrapper around `hashMessage()` that returns\n * a boolean. For full cryptographic verification of the P256 signature,\n * use on-chain verification via the WebAuthn.sol contract.\n *\n * @example\n * ```typescript\n * const result = await client.signMessage({ username: 'alice', message });\n *\n * if (result.success && verifyMessageHash(message, result.signedHash)) {\n * // The signature is for this exact message\n * // For full verification, verify the P256 signature on-chain or server-side\n * }\n * ```\n */\nexport function verifyMessageHash(\n message: string,\n signedHash: string | undefined\n): boolean {\n if (!signedHash) return false;\n const expectedHash = hashMessage(message);\n return expectedHash.toLowerCase() === signedHash.toLowerCase();\n}\n","import { encodeAbiParameters, keccak256 } from 'viem';\nimport type { Hex } from 'viem';\nimport type { WebAuthnSignature } from '../types';\nimport type { TransactionCall } from './types';\n\n/**\n * P-256 curve order (n)\n * Used for signature malleability normalization\n */\nconst P256_N = BigInt(\n '0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551'\n);\nconst P256_N_DIV_2 = P256_N / 2n;\n\n/**\n * WebAuthnAuth struct type for ABI encoding\n */\nconst WEBAUTHN_AUTH_TYPE = {\n type: 'tuple',\n components: [\n { type: 'bytes', name: 'authenticatorData' },\n { type: 'string', name: 'clientDataJSON' },\n { type: 'uint256', name: 'challengeIndex' },\n { type: 'uint256', name: 'typeIndex' },\n { type: 'uint256', name: 'r' },\n { type: 'uint256', name: 's' },\n ],\n} as const;\n\n/**\n * Encode a WebAuthn signature for ERC-1271 verification on-chain\n *\n * @param sig - The WebAuthn signature from the passkey\n * @returns ABI-encoded signature bytes\n */\nexport function encodeWebAuthnSignature(sig: WebAuthnSignature): Hex {\n // Normalize s to prevent signature malleability\n let s = BigInt(sig.s);\n if (s > P256_N_DIV_2) {\n s = P256_N - s;\n }\n\n return encodeAbiParameters([WEBAUTHN_AUTH_TYPE], [\n {\n authenticatorData: sig.authenticatorData as Hex,\n clientDataJSON: sig.clientDataJSON,\n challengeIndex: BigInt(sig.challengeIndex),\n typeIndex: BigInt(sig.typeIndex),\n r: BigInt(sig.r),\n s,\n },\n ]);\n}\n\n/**\n * Hash an array of transaction calls for signing\n *\n * @param calls - Array of transaction calls\n * @returns keccak256 hash of the encoded calls\n */\nexport function hashCalls(calls: TransactionCall[]): Hex {\n const encoded = encodeAbiParameters(\n [\n {\n type: 'tuple[]',\n components: [\n { type: 'address', name: 'to' },\n { type: 'bytes', name: 'data' },\n { type: 'uint256', name: 'value' },\n ],\n },\n ],\n [\n calls.map((c) => ({\n to: c.to,\n data: c.data || '0x',\n value: c.value || 0n,\n })),\n ]\n );\n return keccak256(encoded);\n}\n\n/**\n * Build transaction review display data from calls\n *\n * @param calls - Array of transaction calls\n * @returns TransactionDetails for the signing modal\n */\nexport function buildTransactionReview(calls: TransactionCall[]) {\n return {\n actions: calls.map((call, i) => ({\n type: 'custom' as const,\n label: call.label || `Contract Call ${i + 1}`,\n sublabel: call.sublabel || `To: ${call.to.slice(0, 10)}...${call.to.slice(-8)}`,\n amount: call.value ? `${call.value} wei` : undefined,\n })),\n };\n}\n","/**\n * @file Chain and token registry for the 1auth SDK.\n *\n * Wraps `@rhinestone/shared-configs` chain/token data and combines it with\n * viem's chain definitions to expose a filtered, testnet-aware registry.\n * Consumers can look up supported chains and tokens, resolve token addresses\n * by symbol, and retrieve chain metadata such as explorer URLs and default RPC\n * endpoints.\n *\n * Testnet inclusion is controlled by the `includeTestnets` option on each\n * function or, as a project-wide default, by the environment variables\n * `NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS` or `ORCHESTRATOR_USE_TESTNETS`.\n */\n\nimport type { Address, Chain } from \"viem\";\nimport { isAddress } from \"viem\";\nimport * as viemChains from \"viem/chains\";\nimport { chainRegistry } from \"@rhinestone/shared-configs\";\n\nexport type TokenConfig = {\n symbol: string;\n address: Address;\n decimals: number;\n};\n\nexport type ChainFilterOptions = {\n includeTestnets?: boolean;\n chainIds?: number[];\n};\n\ntype ChainRegistryEntry = (typeof chainRegistry)[keyof typeof chainRegistry];\n\nconst env: Record<string, string | undefined> =\n typeof process !== \"undefined\" ? process.env : {};\n\n// Build a chain-ID-to-Chain map from viem, resolving collisions by preferring\n// mainnet definitions over testnets. viem exports duplicate numeric IDs (e.g.\n// zoraTestnet and hyperEvm both use 999); a naive Map would silently keep\n// whichever entry happens to appear last in iteration order, which may be the\n// testnet. The loop below only overwrites an existing entry when the incoming\n// chain is NOT a testnet and the existing one IS, ensuring the production chain\n// always wins while still falling back to a testnet entry when no mainnet\n// definition exists for a given ID.\nconst VIEM_CHAIN_BY_ID = new Map<number, Chain>();\nfor (const value of Object.values(viemChains)) {\n if (typeof value !== \"object\" || value === null || !(\"id\" in value) || !(\"name\" in value)) continue;\n const chain = value as Chain;\n const existing = VIEM_CHAIN_BY_ID.get(chain.id);\n if (!existing || (existing.testnet && !chain.testnet)) {\n VIEM_CHAIN_BY_ID.set(chain.id, chain);\n }\n}\n\nfunction isEvmRegistryEntry(\n entry: ChainRegistryEntry | undefined,\n): entry is ChainRegistryEntry & { vmType: \"evm\" } {\n return entry?.vmType === \"evm\";\n}\n\nfunction getEvmRegistryEntry(\n chainId: number,\n): (ChainRegistryEntry & { vmType: \"evm\" }) | undefined {\n const entry = chainRegistry[String(chainId) as keyof typeof chainRegistry];\n return isEvmRegistryEntry(entry) ? entry : undefined;\n}\n\n// The shared-configs registry includes non-EVM chains (Solana, Tron). Filter\n// to EVM entries before exposing supported chains — downstream consumers\n// (viem clients, wallet_getCapabilities, RPC proxy) only handle eip155 chains.\nconst REGISTRY_CHAIN_IDS = Object.entries(chainRegistry)\n .filter(([, entry]) => isEvmRegistryEntry(entry))\n .map(([id]) => Number(id));\nconst SUPPORTED_CHAIN_IDS = new Set(REGISTRY_CHAIN_IDS);\n\nfunction tokensForChain(chainId: number): TokenConfig[] {\n const entry = getEvmRegistryEntry(chainId);\n if (!entry) return [];\n return entry.tokens.map((token) => ({\n symbol: token.symbol,\n address: token.address as Address,\n decimals: token.decimals,\n }));\n}\n\n/** Treat any 20-byte hex string as an address; checksum casing is optional for user inputs. */\nfunction isAddressInput(value: string): value is Address {\n return isAddress(value, { strict: false });\n}\n\n/**\n * Parse a string environment variable into a boolean, returning `undefined`\n * when the value is absent or not a recognised truthy/falsy literal.\n *\n * Accepted truthy values: `\"true\"`, `\"1\"`.\n * Accepted falsy values: `\"false\"`, `\"0\"`.\n */\nfunction parseBool(value?: string): boolean | undefined {\n if (value === \"true\" || value === \"1\") return true;\n if (value === \"false\" || value === \"0\") return false;\n return undefined;\n}\n\n/**\n * Determine whether testnet chains should be included in registry results.\n *\n * Precedence (highest to lowest):\n * 1. `explicit` argument — a caller-supplied boolean always wins.\n * 2. `NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS` env var — checked first so\n * Next.js browser bundles (which only expose `NEXT_PUBLIC_*` variables)\n * can configure testnet mode without a server-side env var.\n * 3. `ORCHESTRATOR_USE_TESTNETS` env var — server-side / Node.js fallback.\n * 4. Defaults to `false` (testnets excluded) when none of the above is set.\n */\nfunction resolveIncludeTestnets(explicit?: boolean): boolean {\n if (explicit !== undefined) return explicit;\n const envValue =\n parseBool(env.NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS) ??\n parseBool(env.ORCHESTRATOR_USE_TESTNETS);\n return envValue ?? false;\n}\n\nfunction applyChainFilters(chainIds: number[], options?: ChainFilterOptions): number[] {\n const includeTestnets = resolveIncludeTestnets(options?.includeTestnets);\n const allowlist = options?.chainIds;\n let filtered = chainIds;\n\n if (!includeTestnets) {\n filtered = filtered.filter((chainId) => !isTestnet(chainId));\n }\n\n if (allowlist) {\n const allowed = new Set(allowlist);\n filtered = filtered.filter((chainId) => allowed.has(chainId));\n }\n\n return filtered;\n}\n\nexport function getSupportedChainIds(options?: ChainFilterOptions): number[] {\n return applyChainFilters(REGISTRY_CHAIN_IDS, options);\n}\n\nexport function getSupportedChains(options?: ChainFilterOptions): Chain[] {\n return getSupportedChainIds(options)\n .map((chainId) => VIEM_CHAIN_BY_ID.get(chainId))\n .filter((chain): chain is Chain => Boolean(chain));\n}\n\nexport function getAllSupportedChainsAndTokens(options?: ChainFilterOptions): Array<{\n chainId: number;\n tokens: TokenConfig[];\n}> {\n return getSupportedChainIds(options).map((chainId) => ({\n chainId,\n tokens: tokensForChain(chainId),\n }));\n}\n\nexport function getChainById(chainId: number): Chain {\n if (!SUPPORTED_CHAIN_IDS.has(chainId)) {\n throw new Error(`Unsupported chain ID: ${chainId}`);\n }\n const chain = VIEM_CHAIN_BY_ID.get(chainId);\n if (!chain) {\n throw new Error(`Unsupported chain ID: ${chainId}`);\n }\n return chain;\n}\n\nexport function getChainName(chainId: number): string {\n try {\n return getChainById(chainId).name;\n } catch {\n return `Chain ${chainId}`;\n }\n}\n\nexport function getChainExplorerUrl(chainId: number): string | undefined {\n try {\n return getChainById(chainId).blockExplorers?.default?.url;\n } catch {\n return undefined;\n }\n}\n\nexport function getChainRpcUrl(chainId: number): string | undefined {\n try {\n const chain = getChainById(chainId);\n return chain.rpcUrls?.default?.http?.[0] || chain.rpcUrls?.public?.http?.[0];\n } catch {\n return undefined;\n }\n}\n\nexport function getSupportedTokens(chainId: number): TokenConfig[] {\n return tokensForChain(chainId);\n}\n\nexport function getSupportedTokenSymbols(chainId: number): string[] {\n return getSupportedTokens(chainId).map((token) => token.symbol);\n}\n\nexport function getTokenAddress(symbolOrAddress: string, chainId: number): Address {\n if (isAddressInput(symbolOrAddress)) {\n return symbolOrAddress;\n }\n const match = getSupportedTokens(chainId).find(\n (t) => t.symbol.toUpperCase() === symbolOrAddress.toUpperCase()\n );\n if (!match) {\n throw new Error(`Unsupported token \"${symbolOrAddress}\" on chain ${chainId}`);\n }\n return match.address;\n}\n\nexport function getTokenDecimals(symbolOrAddress: string, chainId: number): number {\n const match = getSupportedTokens(chainId).find(\n (t) =>\n isAddressInput(symbolOrAddress)\n ? t.address.toLowerCase() === symbolOrAddress.toLowerCase()\n : t.symbol.toUpperCase() === symbolOrAddress.toUpperCase()\n );\n if (!match && isAddressInput(symbolOrAddress) && SUPPORTED_CHAIN_IDS.has(chainId)) {\n return 18;\n }\n if (!match) {\n throw new Error(`Unsupported token \"${symbolOrAddress}\" on chain ${chainId}`);\n }\n return match.decimals;\n}\n\nexport function resolveTokenAddress(token: string, chainId: number): Address {\n if (isAddressInput(token)) {\n return token;\n }\n return getTokenAddress(token, chainId);\n}\n\nexport function isTestnet(chainId: number): boolean {\n try {\n return getChainById(chainId).testnet ?? false;\n } catch {\n return false;\n }\n}\n\nexport function getTokenSymbol(tokenAddress: Address, chainId: number): string {\n const token = getSupportedTokens(chainId).find(\n (entry) => entry.address.toLowerCase() === tokenAddress.toLowerCase()\n );\n if (token) {\n return token.symbol;\n }\n if (!SUPPORTED_CHAIN_IDS.has(chainId)) {\n throw new Error(`Unsupported chain ID: ${chainId}`);\n }\n return \"Token\";\n}\n\nexport function isTokenAddressSupported(tokenAddress: Address, chainId: number): boolean {\n return isAddressInput(tokenAddress) && SUPPORTED_CHAIN_IDS.has(chainId);\n}\n"],"mappings":"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","names":["import_viem","import_viem","import_viem","env"]}
1
+ {"version":3,"sources":["../src/server.ts","../src/verify.ts","../src/walletClient/utils.ts","../src/registry.ts"],"sourcesContent":["/**\n * Server-side utilities for JWT sponsorship.\n *\n * The recommended entry point is `createSponsorshipSigner`, a\n * framework-agnostic factory that reads credentials from environment\n * variables and exposes the two operations each sponsorship endpoint\n * needs: `accessToken()` and `extensionToken(intentOp)`.\n *\n * For custom claims or non-standard flows, `createJwtSigner` is exposed as\n * an escape hatch. The implementation lives here instead of re-exporting\n * `@rhinestone/sdk/jwt-server` directly so downstream app builds do not need\n * to resolve the SDK's optional Express adapter surface.\n *\n * @example Next.js App Router\n * ```ts\n * // app/api/sponsorship/access-token/route.ts\n * import { createSponsorshipSigner } from \"@rhinestone/1auth/server\";\n *\n * const signer = createSponsorshipSigner();\n *\n * export async function GET() {\n * // Guard with your own session/auth before minting.\n * return Response.json({ token: await signer.accessToken() });\n * }\n * ```\n */\n\nimport { SignJWT, importJWK } from \"jose\";\nimport { isAddress, type Address, type Hex } from \"viem\";\n\nexport { hashMessage, verifyMessageHash } from \"./verify\";\nexport { encodeWebAuthnSignature } from \"./walletClient/utils\";\nexport type { WebAuthnSignature } from \"./types\";\n// Re-exported so sponsorship endpoints can build a `shouldSponsor` chain\n// policy without importing the browser-oriented main entry. `isTestnet` fails\n// closed (unknown/unrecognised chain id → false), which is the safe default\n// for a sponsorship gate: deny anything we can't positively classify.\nexport { isTestnet } from \"./registry\";\n\nexport interface JwtCredentials {\n privateKey: JsonWebKey;\n integratorId: string;\n projectId: string;\n appId: string;\n keyId: string;\n audience?: string;\n}\n\nexport interface SponsorshipFilter {\n chain?: (chain: { id: number }) => boolean | Promise<boolean>;\n account?: (address: Address) => boolean | Promise<boolean>;\n calls?: (\n calls: {\n to: Address;\n value: bigint;\n data: Hex;\n }[],\n ) => boolean | Promise<boolean>;\n}\n\nexport interface JwtSignerConfig {\n jwt: JwtCredentials;\n shouldSponsor?: SponsorshipFilter;\n}\n\nexport class SponsorshipDeniedError extends Error {\n constructor() {\n super(\"Sponsorship denied\");\n this.name = \"SponsorshipDeniedError\";\n }\n}\n\n/**\n * Sponsorship signer used by the two endpoints the SDK client calls:\n *\n * - `accessToken()` — mints the app's identity token; return as\n * `{ token }` from `GET /sponsorship/access-token`.\n * - `extensionToken(intentOp)` — mints a per-intent sponsorship grant;\n * return as `{ token }` from `POST /sponsorship/extension-token`.\n * Accepts either the JSON-stringified `intentOp` the SDK sends or a\n * pre-parsed object.\n */\nexport interface SponsorshipSigner {\n accessToken(): Promise<string>;\n extensionToken(intentOp: string | object): Promise<string>;\n}\n\nexport interface CreateSponsorshipSignerOptions {\n /** Override all credentials explicitly (skips env lookup). */\n credentials?: JwtCredentials;\n /** Override the env source (defaults to `process.env`). */\n env?: NodeJS.ProcessEnv;\n /** Optional per-intent sponsorship filter (see `@rhinestone/sdk/jwt-server`). */\n shouldSponsor?: SponsorshipFilter;\n}\n\nexport interface VerifyOneAuthAccountOptions {\n /** 1auth provider origin, for example `https://passkey.1auth.app`. */\n providerUrl: string;\n /** Smart account address returned by a successful 1auth auth result. */\n address: Address;\n /** Optional app client id forwarded to provider APIs that scope by app. */\n clientId?: string;\n /** Injectable fetch implementation for tests and non-standard runtimes. */\n fetch?: typeof fetch;\n}\n\nexport interface VerifiedOneAuthAccount {\n address: Address;\n deployed?: boolean;\n chainId?: number;\n}\n\n/** Error raised when the provider cannot bind an address to a 1auth account. */\nexport class OneAuthAccountVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"OneAuthAccountVerificationError\";\n }\n}\n\nconst ENV_KEYS = {\n privateKey: \"RHINESTONE_JWT_PRIVATE_KEY\",\n integratorId: \"RHINESTONE_INTEGRATOR_ID\",\n projectId: \"RHINESTONE_PROJECT_ID\",\n appId: \"RHINESTONE_APP_ID\",\n keyId: \"RHINESTONE_KEY_ID\",\n} as const;\n\n/** Build the documented account-lookup URL for a 1auth provider. */\nfunction accountLookupUrl(providerUrl: string, address: Address): string {\n return new URL(`/api/account/${address.toLowerCase()}`, providerUrl).toString();\n}\n\n/** Safely parse a provider account lookup response. */\nasync function parseAccountLookupResponse(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\n/**\n * Verify that an address belongs to a 1auth account known by the configured\n * provider. This is intended for server-side app sessions that already have a\n * fresh challenge signature and need to reject arbitrary ERC-1271 contracts.\n */\nexport async function verifyOneAuthAccount(\n options: VerifyOneAuthAccountOptions,\n): Promise<VerifiedOneAuthAccount> {\n if (!isAddress(options.address)) {\n throw new OneAuthAccountVerificationError(\n \"A valid 1auth account address is required\",\n );\n }\n\n const fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new OneAuthAccountVerificationError(\n \"No fetch implementation is available to verify the 1auth account\",\n );\n }\n\n const url = accountLookupUrl(options.providerUrl, options.address);\n const headers: HeadersInit = { accept: \"application/json\" };\n if (options.clientId) headers[\"x-client-id\"] = options.clientId;\n\n let response: Response;\n try {\n response = await fetchImpl(url, {\n headers,\n cache: \"no-store\",\n });\n } catch (error) {\n throw new OneAuthAccountVerificationError(\n `Unable to reach the configured 1auth provider: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n\n const body = await parseAccountLookupResponse(response);\n if (!response.ok) {\n const message =\n body && typeof body === \"object\" && \"error\" in body\n ? String((body as { error: unknown }).error)\n : response.statusText || \"account lookup failed\";\n throw new OneAuthAccountVerificationError(\n `Configured 1auth provider did not recognize this account (${response.status}: ${message})`,\n );\n }\n\n const account = body as Partial<VerifiedOneAuthAccount> | null;\n if (!account?.address || !isAddress(account.address)) {\n throw new OneAuthAccountVerificationError(\n \"Configured 1auth provider returned a malformed account record\",\n );\n }\n if (account.address.toLowerCase() !== options.address.toLowerCase()) {\n throw new OneAuthAccountVerificationError(\n \"Configured 1auth provider returned a different account address\",\n );\n }\n\n return {\n address: account.address,\n deployed:\n typeof account.deployed === \"boolean\" ? account.deployed : undefined,\n chainId: typeof account.chainId === \"number\" ? account.chainId : undefined,\n };\n}\n\n/**\n * RFC 8785 JSON Canonicalization Scheme (JCS).\n *\n * The digest embedded in extension tokens must be byte-for-byte stable across\n * environments, so we canonicalize JSON before hashing instead of trusting\n * ordinary `JSON.stringify` object key order.\n */\nfunction jcsCanonicalise(value: unknown): string {\n return serialize(value);\n}\n\nfunction serialize(value: unknown): string {\n if (value === null || value === undefined) {\n return \"null\";\n }\n\n switch (typeof value) {\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"number\":\n if (!Number.isFinite(value)) {\n throw new Error(`JCS: non-finite number: ${value}`);\n }\n return Object.is(value, -0) ? \"0\" : String(value);\n case \"string\":\n return JSON.stringify(value);\n case \"bigint\":\n if (\n value > BigInt(Number.MAX_SAFE_INTEGER) ||\n value < BigInt(-Number.MAX_SAFE_INTEGER)\n ) {\n throw new Error(\n `JCS: BigInt ${value} exceeds safe integer range — convert to string before calling`,\n );\n }\n return String(value);\n default:\n break;\n }\n\n if (Array.isArray(value)) {\n return `[${value.map((item) => serialize(item)).join(\",\")}]`;\n }\n\n const obj = value as Record<string, unknown>;\n const members: string[] = [];\n for (const key of Object.keys(obj).sort()) {\n const item = obj[key];\n if (item === undefined) continue;\n members.push(`${JSON.stringify(key)}:${serialize(item)}`);\n }\n return `{${members.join(\",\")}}`;\n}\n\n/**\n * Compute the canonical digest embedded in per-intent sponsorship grants.\n */\nasync function computeIntentInputDigest(intentInput: unknown): Promise<string> {\n const canonical = jcsCanonicalise(intentInput);\n const encoded = new TextEncoder().encode(canonical);\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", encoded);\n return Array.from(new Uint8Array(hashBuffer))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction parseIntentInput(intentInput: unknown) {\n if (typeof intentInput !== \"object\" || intentInput === null) {\n throw new Error(\"intentInput must be a non-null object\");\n }\n\n const input = intentInput as {\n destinationChainId?: number;\n account?: { address?: string };\n destinationExecutions?: { to: Address; value: string; data: Hex }[];\n };\n\n if (typeof input.destinationChainId !== \"number\") {\n throw new Error(\"intentInput.destinationChainId must be a number\");\n }\n\n if (typeof input.account !== \"object\" || input.account === null) {\n throw new Error(\"intentInput.account must be a non-null object\");\n }\n\n if (typeof input.account.address !== \"string\") {\n throw new Error(\"intentInput.account.address must be a string\");\n }\n\n if (!Array.isArray(input.destinationExecutions)) {\n throw new Error(\"intentInput.destinationExecutions must be an array\");\n }\n\n return {\n chain: { id: input.destinationChainId },\n account: input.account.address as Address,\n calls: input.destinationExecutions.map((execution) => ({\n to: execution.to,\n value: BigInt(execution.value),\n data: execution.data,\n })),\n };\n}\n\nasync function shouldSponsor(\n intentInput: unknown,\n filters: SponsorshipFilter,\n): Promise<boolean> {\n const parsed = parseIntentInput(intentInput);\n if (filters.chain && !(await filters.chain(parsed.chain))) {\n return false;\n }\n if (filters.account && !(await filters.account(parsed.account))) {\n return false;\n }\n if (filters.calls && !(await filters.calls(parsed.calls))) {\n return false;\n }\n return true;\n}\n\nfunction pickAlg(jwk: JsonWebKey): \"ES256\" | \"ES384\" | \"ES512\" | \"RS256\" {\n if (jwk.kty === \"EC\") {\n if (jwk.crv === \"P-256\") return \"ES256\";\n if (jwk.crv === \"P-384\") return \"ES384\";\n if (jwk.crv === \"P-521\") return \"ES512\";\n throw new Error(`Unsupported EC curve: ${jwk.crv}`);\n }\n if (jwk.kty === \"RSA\") return \"RS256\";\n throw new Error(`Unsupported JWK kty: ${jwk.kty}`);\n}\n\n/**\n * Create a low-level JWT signer compatible with Rhinestone sponsorship flows.\n *\n * This mirrors the upstream SDK behavior while keeping our public server entry\n * point self-contained for app bundlers.\n */\nexport function createJwtSigner(config: JwtSignerConfig) {\n const {\n jwt: {\n privateKey,\n integratorId,\n projectId,\n appId,\n keyId,\n audience = \"rhinestone-api\",\n },\n shouldSponsor: filters,\n } = config;\n\n const alg = pickAlg(privateKey);\n let cachedKey: Awaited<ReturnType<typeof importJWK>> | null = null;\n\n async function getKey() {\n if (!cachedKey) {\n cachedKey = await importJWK(privateKey, alg);\n }\n return cachedKey;\n }\n\n return {\n accessToken: async () => {\n const key = await getKey();\n return new SignJWT({ typ: \"access\", app_id: appId })\n .setProtectedHeader({ alg, kid: keyId })\n .setIssuer(integratorId)\n .setSubject(projectId)\n .setAudience(audience)\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(key);\n },\n getIntentExtensionToken: async (intentInput: unknown) => {\n if (filters && !(await shouldSponsor(intentInput, filters))) {\n throw new SponsorshipDeniedError();\n }\n\n const key = await getKey();\n const digest = await computeIntentInputDigest(intentInput);\n return new SignJWT({\n typ: \"intent_extension\",\n app_id: appId,\n jti: crypto.randomUUID(),\n policy: {\n sponsorship: {\n scope: \"intent\",\n intent_input: { digest },\n },\n },\n })\n .setProtectedHeader({ alg, kid: keyId })\n .setIssuer(integratorId)\n .setSubject(projectId)\n .setAudience(audience)\n .setIssuedAt()\n .setExpirationTime(\"5m\")\n .sign(key);\n },\n };\n}\n\nfunction readCredentialsFromEnv(env: NodeJS.ProcessEnv): JwtCredentials {\n const missing: string[] = [];\n const read = (key: string) => {\n const value = env[key];\n if (!value) missing.push(key);\n return value ?? \"\";\n };\n\n const privateKeyRaw = read(ENV_KEYS.privateKey);\n const integratorId = read(ENV_KEYS.integratorId);\n const projectId = read(ENV_KEYS.projectId);\n const appId = read(ENV_KEYS.appId);\n const keyId = read(ENV_KEYS.keyId);\n\n if (missing.length > 0) {\n throw new Error(\n `createSponsorshipSigner: missing env var(s): ${missing.join(\", \")}`,\n );\n }\n\n let privateKey: JsonWebKey;\n try {\n privateKey = JSON.parse(privateKeyRaw) as JsonWebKey;\n } catch {\n throw new Error(\n `createSponsorshipSigner: ${ENV_KEYS.privateKey} must be a JSON-encoded JWK`,\n );\n }\n\n return { privateKey, integratorId, projectId, appId, keyId };\n}\n\nexport function createSponsorshipSigner(\n options: CreateSponsorshipSignerOptions = {},\n): SponsorshipSigner {\n const credentials =\n options.credentials ?? readCredentialsFromEnv(options.env ?? process.env);\n\n const inner = createJwtSigner({\n jwt: credentials,\n shouldSponsor: options.shouldSponsor,\n });\n\n return {\n accessToken: () => inner.accessToken(),\n extensionToken: (intentOp) => {\n const intentInput =\n typeof intentOp === \"string\" ? JSON.parse(intentOp) : intentOp;\n return inner.getIntentExtensionToken(intentInput);\n },\n };\n}\n","import { keccak256, toBytes } from \"viem\";\n\n/**\n * The EIP-191 prefix used for personal message signing.\n * This is the standard Ethereum message prefix for `personal_sign`.\n */\nexport const ETHEREUM_MESSAGE_PREFIX = \"\\x19Ethereum Signed Message:\\n\";\n\n/**\n * Hash a message with the EIP-191 Ethereum prefix.\n *\n * This is the same hashing function used by the passkey sign dialog.\n * Use this to verify that the `signedHash` returned from `signMessage()`\n * matches your original message.\n *\n * Format: keccak256(\"\\x19Ethereum Signed Message:\\n\" + len + message)\n *\n * @example\n * ```typescript\n * const message = \"Sign in to MyApp\\nTimestamp: 1234567890\";\n * const result = await client.signMessage({ accountAddress: '0x...', message });\n *\n * // Verify the hash matches\n * const expectedHash = hashMessage(message);\n * if (result.signedHash === expectedHash) {\n * console.log('Hash matches - signature is for this message');\n * }\n * ```\n */\nexport function hashMessage(message: string): `0x${string}` {\n const messageBytes = toBytes(message);\n const prefixed = ETHEREUM_MESSAGE_PREFIX + messageBytes.length.toString() + message;\n return keccak256(toBytes(prefixed));\n}\n\n/**\n * Verify that a signedHash matches the expected message.\n *\n * This is a convenience wrapper around `hashMessage()` that returns\n * a boolean. For full cryptographic verification of the P256 signature,\n * use on-chain verification via the WebAuthn.sol contract.\n *\n * @example\n * ```typescript\n * const result = await client.signMessage({ accountAddress: '0x...', message });\n *\n * if (result.success && verifyMessageHash(message, result.signedHash)) {\n * // The signature is for this exact message\n * // For full verification, verify the P256 signature on-chain or server-side\n * }\n * ```\n */\nexport function verifyMessageHash(\n message: string,\n signedHash: string | undefined\n): boolean {\n if (!signedHash) return false;\n const expectedHash = hashMessage(message);\n return expectedHash.toLowerCase() === signedHash.toLowerCase();\n}\n","import { encodeAbiParameters, keccak256 } from 'viem';\nimport type { Hex } from 'viem';\nimport type { WebAuthnSignature } from '../types';\nimport type { TransactionCall } from './types';\n\n/**\n * P-256 curve order (n)\n * Used for signature malleability normalization\n */\nconst P256_N = BigInt(\n '0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551'\n);\nconst P256_N_DIV_2 = P256_N / 2n;\n\n/**\n * WebAuthnAuth struct type for ABI encoding\n */\nconst WEBAUTHN_AUTH_TYPE = {\n type: 'tuple',\n components: [\n { type: 'bytes', name: 'authenticatorData' },\n { type: 'string', name: 'clientDataJSON' },\n { type: 'uint256', name: 'challengeIndex' },\n { type: 'uint256', name: 'typeIndex' },\n { type: 'uint256', name: 'r' },\n { type: 'uint256', name: 's' },\n ],\n} as const;\n\n/**\n * Encode a WebAuthn signature for ERC-1271 verification on-chain\n *\n * @param sig - The WebAuthn signature from the passkey\n * @returns ABI-encoded signature bytes\n */\nexport function encodeWebAuthnSignature(sig: WebAuthnSignature): Hex {\n // Normalize s to prevent signature malleability\n let s = BigInt(sig.s);\n if (s > P256_N_DIV_2) {\n s = P256_N - s;\n }\n\n return encodeAbiParameters([WEBAUTHN_AUTH_TYPE], [\n {\n authenticatorData: sig.authenticatorData as Hex,\n clientDataJSON: sig.clientDataJSON,\n challengeIndex: BigInt(sig.challengeIndex),\n typeIndex: BigInt(sig.typeIndex),\n r: BigInt(sig.r),\n s,\n },\n ]);\n}\n\n/**\n * Hash an array of transaction calls for signing\n *\n * @param calls - Array of transaction calls\n * @returns keccak256 hash of the encoded calls\n */\nexport function hashCalls(calls: TransactionCall[]): Hex {\n const encoded = encodeAbiParameters(\n [\n {\n type: 'tuple[]',\n components: [\n { type: 'address', name: 'to' },\n { type: 'bytes', name: 'data' },\n { type: 'uint256', name: 'value' },\n ],\n },\n ],\n [\n calls.map((c) => ({\n to: c.to,\n data: c.data || '0x',\n value: c.value || 0n,\n })),\n ]\n );\n return keccak256(encoded);\n}\n\n/**\n * Build transaction review display data from calls\n *\n * @param calls - Array of transaction calls\n * @returns TransactionDetails for the signing modal\n */\nexport function buildTransactionReview(calls: TransactionCall[]) {\n return {\n actions: calls.map((call, i) => ({\n type: 'custom' as const,\n label: call.label || `Contract Call ${i + 1}`,\n sublabel: call.sublabel || `To: ${call.to.slice(0, 10)}...${call.to.slice(-8)}`,\n amount: call.value ? `${call.value} wei` : undefined,\n })),\n };\n}\n","/**\n * @file Chain and token registry for the 1auth SDK.\n *\n * Wraps `@rhinestone/shared-configs` chain/token data and combines it with\n * viem's chain definitions to expose a filtered, testnet-aware registry.\n * Consumers can look up supported chains and tokens, resolve token addresses\n * by symbol, and retrieve chain metadata such as explorer URLs and default RPC\n * endpoints.\n *\n * Testnet inclusion is controlled by the `includeTestnets` option on each\n * function or, as a project-wide default, by the environment variables\n * `NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS` or `ORCHESTRATOR_USE_TESTNETS`.\n */\n\nimport type { Address, Chain } from \"viem\";\nimport { isAddress } from \"viem\";\nimport * as viemChains from \"viem/chains\";\nimport { chainRegistry } from \"@rhinestone/shared-configs\";\n\nexport type TokenConfig = {\n symbol: string;\n address: Address;\n decimals: number;\n};\n\nexport type ChainFilterOptions = {\n includeTestnets?: boolean;\n chainIds?: number[];\n};\n\ntype ChainRegistryEntry = (typeof chainRegistry)[keyof typeof chainRegistry];\n\nconst env: Record<string, string | undefined> =\n typeof process !== \"undefined\" ? process.env : {};\n\n// Build a chain-ID-to-Chain map from viem, resolving collisions by preferring\n// mainnet definitions over testnets. viem exports duplicate numeric IDs (e.g.\n// zoraTestnet and hyperEvm both use 999); a naive Map would silently keep\n// whichever entry happens to appear last in iteration order, which may be the\n// testnet. The loop below only overwrites an existing entry when the incoming\n// chain is NOT a testnet and the existing one IS, ensuring the production chain\n// always wins while still falling back to a testnet entry when no mainnet\n// definition exists for a given ID.\nconst VIEM_CHAIN_BY_ID = new Map<number, Chain>();\nfor (const value of Object.values(viemChains)) {\n if (typeof value !== \"object\" || value === null || !(\"id\" in value) || !(\"name\" in value)) continue;\n const chain = value as Chain;\n const existing = VIEM_CHAIN_BY_ID.get(chain.id);\n if (!existing || (existing.testnet && !chain.testnet)) {\n VIEM_CHAIN_BY_ID.set(chain.id, chain);\n }\n}\n\nfunction isEvmRegistryEntry(\n entry: ChainRegistryEntry | undefined,\n): entry is ChainRegistryEntry & { vmType: \"evm\" } {\n return entry?.vmType === \"evm\";\n}\n\nfunction getEvmRegistryEntry(\n chainId: number,\n): (ChainRegistryEntry & { vmType: \"evm\" }) | undefined {\n const entry = chainRegistry[String(chainId) as keyof typeof chainRegistry];\n return isEvmRegistryEntry(entry) ? entry : undefined;\n}\n\n// The shared-configs registry includes non-EVM chains (Solana, Tron). Filter\n// to EVM entries before exposing supported chains — downstream consumers\n// (viem clients, wallet_getCapabilities, RPC proxy) only handle eip155 chains.\nconst REGISTRY_CHAIN_IDS = Object.entries(chainRegistry)\n .filter(([, entry]) => isEvmRegistryEntry(entry))\n .map(([id]) => Number(id));\nconst SUPPORTED_CHAIN_IDS = new Set(REGISTRY_CHAIN_IDS);\n\nfunction tokensForChain(chainId: number): TokenConfig[] {\n const entry = getEvmRegistryEntry(chainId);\n if (!entry) return [];\n return entry.tokens.map((token) => ({\n symbol: token.symbol,\n address: token.address as Address,\n decimals: token.decimals,\n }));\n}\n\n/** Treat any 20-byte hex string as an address; checksum casing is optional for user inputs. */\nfunction isAddressInput(value: string): value is Address {\n return isAddress(value, { strict: false });\n}\n\n/**\n * Parse a string environment variable into a boolean, returning `undefined`\n * when the value is absent or not a recognised truthy/falsy literal.\n *\n * Accepted truthy values: `\"true\"`, `\"1\"`.\n * Accepted falsy values: `\"false\"`, `\"0\"`.\n */\nfunction parseBool(value?: string): boolean | undefined {\n if (value === \"true\" || value === \"1\") return true;\n if (value === \"false\" || value === \"0\") return false;\n return undefined;\n}\n\n/**\n * Determine whether testnet chains should be included in registry results.\n *\n * Precedence (highest to lowest):\n * 1. `explicit` argument — a caller-supplied boolean always wins.\n * 2. `NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS` env var — checked first so\n * Next.js browser bundles (which only expose `NEXT_PUBLIC_*` variables)\n * can configure testnet mode without a server-side env var.\n * 3. `ORCHESTRATOR_USE_TESTNETS` env var — server-side / Node.js fallback.\n * 4. Defaults to `false` (testnets excluded) when none of the above is set.\n */\nfunction resolveIncludeTestnets(explicit?: boolean): boolean {\n if (explicit !== undefined) return explicit;\n const envValue =\n parseBool(env.NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS) ??\n parseBool(env.ORCHESTRATOR_USE_TESTNETS);\n return envValue ?? false;\n}\n\nfunction applyChainFilters(chainIds: number[], options?: ChainFilterOptions): number[] {\n const includeTestnets = resolveIncludeTestnets(options?.includeTestnets);\n const allowlist = options?.chainIds;\n let filtered = chainIds;\n\n if (!includeTestnets) {\n filtered = filtered.filter((chainId) => !isTestnet(chainId));\n }\n\n if (allowlist) {\n const allowed = new Set(allowlist);\n filtered = filtered.filter((chainId) => allowed.has(chainId));\n }\n\n return filtered;\n}\n\nexport function getSupportedChainIds(options?: ChainFilterOptions): number[] {\n return applyChainFilters(REGISTRY_CHAIN_IDS, options);\n}\n\nexport function getSupportedChains(options?: ChainFilterOptions): Chain[] {\n return getSupportedChainIds(options)\n .map((chainId) => VIEM_CHAIN_BY_ID.get(chainId))\n .filter((chain): chain is Chain => Boolean(chain));\n}\n\nexport function getAllSupportedChainsAndTokens(options?: ChainFilterOptions): Array<{\n chainId: number;\n tokens: TokenConfig[];\n}> {\n return getSupportedChainIds(options).map((chainId) => ({\n chainId,\n tokens: tokensForChain(chainId),\n }));\n}\n\nexport function getChainById(chainId: number): Chain {\n if (!SUPPORTED_CHAIN_IDS.has(chainId)) {\n throw new Error(`Unsupported chain ID: ${chainId}`);\n }\n const chain = VIEM_CHAIN_BY_ID.get(chainId);\n if (!chain) {\n throw new Error(`Unsupported chain ID: ${chainId}`);\n }\n return chain;\n}\n\nexport function getChainName(chainId: number): string {\n try {\n return getChainById(chainId).name;\n } catch {\n return `Chain ${chainId}`;\n }\n}\n\nexport function getChainExplorerUrl(chainId: number): string | undefined {\n try {\n return getChainById(chainId).blockExplorers?.default?.url;\n } catch {\n return undefined;\n }\n}\n\nexport function getChainRpcUrl(chainId: number): string | undefined {\n try {\n const chain = getChainById(chainId);\n return chain.rpcUrls?.default?.http?.[0] || chain.rpcUrls?.public?.http?.[0];\n } catch {\n return undefined;\n }\n}\n\nexport function getSupportedTokens(chainId: number): TokenConfig[] {\n return tokensForChain(chainId);\n}\n\nexport function getSupportedTokenSymbols(chainId: number): string[] {\n return getSupportedTokens(chainId).map((token) => token.symbol);\n}\n\nexport function getTokenAddress(symbolOrAddress: string, chainId: number): Address {\n if (isAddressInput(symbolOrAddress)) {\n return symbolOrAddress;\n }\n const match = getSupportedTokens(chainId).find(\n (t) => t.symbol.toUpperCase() === symbolOrAddress.toUpperCase()\n );\n if (!match) {\n throw new Error(`Unsupported token \"${symbolOrAddress}\" on chain ${chainId}`);\n }\n return match.address;\n}\n\nexport function getTokenDecimals(symbolOrAddress: string, chainId: number): number {\n const match = getSupportedTokens(chainId).find(\n (t) =>\n isAddressInput(symbolOrAddress)\n ? t.address.toLowerCase() === symbolOrAddress.toLowerCase()\n : t.symbol.toUpperCase() === symbolOrAddress.toUpperCase()\n );\n if (!match && isAddressInput(symbolOrAddress) && SUPPORTED_CHAIN_IDS.has(chainId)) {\n return 18;\n }\n if (!match) {\n throw new Error(`Unsupported token \"${symbolOrAddress}\" on chain ${chainId}`);\n }\n return match.decimals;\n}\n\nexport function resolveTokenAddress(token: string, chainId: number): Address {\n if (isAddressInput(token)) {\n return token;\n }\n return getTokenAddress(token, chainId);\n}\n\nexport function isTestnet(chainId: number): boolean {\n try {\n return getChainById(chainId).testnet ?? false;\n } catch {\n return false;\n }\n}\n\nexport function getTokenSymbol(tokenAddress: Address, chainId: number): string {\n const token = getSupportedTokens(chainId).find(\n (entry) => entry.address.toLowerCase() === tokenAddress.toLowerCase()\n );\n if (token) {\n return token.symbol;\n }\n if (!SUPPORTED_CHAIN_IDS.has(chainId)) {\n throw new Error(`Unsupported chain ID: ${chainId}`);\n }\n return \"Token\";\n}\n\nexport function isTokenAddressSupported(tokenAddress: Address, chainId: number): boolean {\n return isAddressInput(tokenAddress) && SUPPORTED_CHAIN_IDS.has(chainId);\n}\n"],"mappings":"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","names":["import_viem","import_viem","import_viem","env"]}
package/dist/server.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  hashMessage,
3
3
  verifyMessageHash
4
- } from "./chunk-IHBVEU33.mjs";
4
+ } from "./chunk-6AHEN3FA.mjs";
5
5
  import {
6
6
  encodeWebAuthnSignature
7
7
  } from "./chunk-N6KE5CII.mjs";