@rhinestone/1auth 0.6.8 → 0.6.9

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 (49) hide show
  1. package/README.md +171 -12
  2. package/dist/chunk-IHBVEU33.mjs +20 -0
  3. package/dist/chunk-IHBVEU33.mjs.map +1 -0
  4. package/dist/chunk-IIACVHR3.mjs +28 -0
  5. package/dist/chunk-IIACVHR3.mjs.map +1 -0
  6. package/dist/chunk-N6KE5CII.mjs +72 -0
  7. package/dist/chunk-N6KE5CII.mjs.map +1 -0
  8. package/dist/{chunk-SXISYG2P.mjs → chunk-VZYHCFEH.mjs} +195 -140
  9. package/dist/chunk-VZYHCFEH.mjs.map +1 -0
  10. package/dist/{client-BrMrhetG.d.mts → client-BNluVe4_.d.ts} +305 -849
  11. package/dist/{client-BrMrhetG.d.ts → client-CLCdahyj.d.mts} +305 -849
  12. package/dist/headless.d.mts +90 -0
  13. package/dist/headless.d.ts +90 -0
  14. package/dist/headless.js +327 -0
  15. package/dist/headless.js.map +1 -0
  16. package/dist/headless.mjs +280 -0
  17. package/dist/headless.mjs.map +1 -0
  18. package/dist/index.d.mts +96 -144
  19. package/dist/index.d.ts +96 -144
  20. package/dist/index.js +2984 -718
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +2740 -627
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{provider-CDl9wYEc.d.mts → provider-BgD9PeDX.d.ts} +6 -5
  25. package/dist/{provider-Dgv533YQ.d.ts → provider-hxHGb6SX.d.mts} +6 -5
  26. package/dist/react.d.mts +42 -2
  27. package/dist/react.d.ts +42 -2
  28. package/dist/react.js +92 -2
  29. package/dist/react.js.map +1 -1
  30. package/dist/react.mjs +66 -1
  31. package/dist/react.mjs.map +1 -1
  32. package/dist/server.d.mts +118 -0
  33. package/dist/server.d.ts +118 -0
  34. package/dist/server.js +356 -0
  35. package/dist/server.js.map +1 -0
  36. package/dist/server.mjs +282 -0
  37. package/dist/server.mjs.map +1 -0
  38. package/dist/types-1BMD1PSH.d.mts +1425 -0
  39. package/dist/types-1BMD1PSH.d.ts +1425 -0
  40. package/dist/verify-CZe-m_Vf.d.ts +150 -0
  41. package/dist/verify-D3FaLeAi.d.mts +150 -0
  42. package/dist/wagmi.d.mts +5 -2
  43. package/dist/wagmi.d.ts +5 -2
  44. package/dist/wagmi.js +138 -43
  45. package/dist/wagmi.js.map +1 -1
  46. package/dist/wagmi.mjs +2 -1
  47. package/dist/wagmi.mjs.map +1 -1
  48. package/package.json +15 -2
  49. package/dist/chunk-SXISYG2P.mjs.map +0 -1
package/README.md CHANGED
@@ -28,7 +28,7 @@ The SDK provides multiple entry points for different use cases:
28
28
  |-------------|--------|----------|
29
29
  | Default | `@rhinestone/1auth` | Core client, provider, types |
30
30
  | React | `@rhinestone/1auth/react` | PayButton component |
31
- | Server | `@rhinestone/1auth/server` | Server-side intent signing |
31
+ | Server | `@rhinestone/1auth/server` | JWT signer for app-sponsored intents |
32
32
  | Wagmi | `@rhinestone/1auth/wagmi` | Wagmi connector integration |
33
33
 
34
34
  ## Quick Start
@@ -84,18 +84,22 @@ const config = createConfig({
84
84
  });
85
85
  ```
86
86
 
87
- ### Server-Side Intent Signing
87
+ ### Server-Side JWT Sponsorship
88
88
 
89
89
  ```typescript
90
- import { signIntent, createSignIntentHandler } from '@rhinestone/1auth/server';
91
-
92
- // Sign an intent with Ed25519
93
- const signedIntent = await signIntent(intent, privateKey);
94
-
95
- // Or create a request handler
96
- const handler = createSignIntentHandler({
97
- privateKey: process.env.SIGNING_KEY,
90
+ import { createJwtSigner } from '@rhinestone/1auth/server';
91
+
92
+ const signer = createJwtSigner({
93
+ privateKey: JSON.parse(process.env.RHINESTONE_JWT_PRIVATE_KEY),
94
+ integratorId: process.env.RHINESTONE_INTEGRATOR_ID,
95
+ projectId: process.env.RHINESTONE_PROJECT_ID,
96
+ appId: process.env.RHINESTONE_APP_ID,
97
+ keyId: process.env.RHINESTONE_KEY_ID,
98
98
  });
99
+
100
+ // Use in your API routes
101
+ const accessToken = await signer.accessToken();
102
+ const extensionToken = await signer.getIntentExtensionToken(intentInput);
99
103
  ```
100
104
 
101
105
  ## Core Exports
@@ -118,13 +122,168 @@ const handler = createSignIntentHandler({
118
122
 
119
123
  ### `@rhinestone/1auth/server`
120
124
 
121
- - `signIntent()` - Server-side intent signing with Ed25519
122
- - `createSignIntentHandler()` - Express/fetch handler for intent signing
125
+ - `createJwtSigner()` - Create a JWT signer for app-sponsored intents (RS256)
126
+ - `JwtSignerConfig` - Configuration type for the JWT signer
123
127
 
124
128
  ### `@rhinestone/1auth/wagmi`
125
129
 
126
130
  - `oneAuth()` - Wagmi connector factory
127
131
 
132
+ ## App-Sponsored Intents
133
+
134
+ By default, 1auth sponsors transaction fees for all intents. With sponsorship, your app takes over billing by providing JWT tokens to authorize each intent.
135
+
136
+ ### How It Works
137
+
138
+ ```mermaid
139
+ sequenceDiagram
140
+ participant App as Your App
141
+ participant BE as Your Backend
142
+ participant SDK as @rhinestone/1auth
143
+ participant PS as 1auth Passkey Service
144
+
145
+ Note over App,SDK: 1. Configure the client with sponsorship URLs
146
+ App->>SDK: new OneAuthClient({<br/> sponsorship: {<br/> accessTokenUrl,<br/> extensionTokenUrl<br/> }<br/>})
147
+
148
+ Note over SDK,BE: 2. Prepare the intent
149
+ SDK->>PS: POST /api/intent/prepare
150
+ PS-->>SDK: { intentOp, digestResult, ... }
151
+
152
+ Note over SDK,BE: 3. In parallel: fetch tokens (same-origin, with session cookie) + WebAuthn ceremony
153
+ par
154
+ SDK->>BE: GET /sponsorship/access-token
155
+ BE-->>SDK: { token: "eyJhbG..." }
156
+ and
157
+ SDK->>BE: POST /sponsorship/extension-token { intentOp }
158
+ BE-->>SDK: { token: "eyJhbG..." }
159
+ and
160
+ SDK->>SDK: WebAuthn signing ceremony
161
+ end
162
+
163
+ Note over SDK,PS: 4. Execute with pre-fetched tokens in request body
164
+ SDK->>PS: POST /api/intent/execute<br/>{ signature, sponsorship: { accessToken, extensionToken } }
165
+ PS-->>SDK: { intentId }
166
+ ```
167
+
168
+ ### Why this shape
169
+
170
+ The SDK calls your token endpoints **from the browser, same-origin**. Your app's session cookie naturally authenticates the request — no need to pass bearer tokens through the SDK. The passkey service never fetches from your backend on your behalf, so there's no SSRF surface and no CORS config to worry about.
171
+
172
+ ### Setup
173
+
174
+ **1. Create two endpoints on your backend, on the same origin as your app**
175
+
176
+ Gate these with your existing session auth — the user's cookie rides along automatically.
177
+
178
+ ```typescript
179
+ // GET /api/sponsorship/access-token
180
+ import { createJwtSigner } from '@rhinestone/1auth/server';
181
+
182
+ const signer = createJwtSigner({
183
+ jwt: {
184
+ privateKey: JSON.parse(process.env.RHINESTONE_JWT_PRIVATE_KEY),
185
+ integratorId: process.env.RHINESTONE_INTEGRATOR_ID,
186
+ projectId: process.env.RHINESTONE_PROJECT_ID,
187
+ appId: process.env.RHINESTONE_APP_ID,
188
+ keyId: process.env.RHINESTONE_KEY_ID,
189
+ },
190
+ });
191
+
192
+ export async function handleAccessToken(req, res) {
193
+ const user = await verifySession(req); // your app's auth
194
+ if (!user) return res.status(401).json({ error: 'Unauthorized' });
195
+ const token = await signer.accessToken();
196
+ res.json({ token });
197
+ }
198
+
199
+ // POST /api/sponsorship/extension-token
200
+ // Body: { intentOp: string } — JSON-stringified intent operation
201
+ export async function handleExtensionToken(req, res) {
202
+ const user = await verifySession(req);
203
+ if (!user) return res.status(401).json({ error: 'Unauthorized' });
204
+
205
+ const { intentOp } = req.body;
206
+ const intentInput = JSON.parse(intentOp);
207
+
208
+ // Enforce policies
209
+ if (await exceedsSpendingLimit(user.id)) {
210
+ return res.status(403).json({ error: 'Spending limit exceeded' });
211
+ }
212
+
213
+ const token = await signer.getIntentExtensionToken(intentInput);
214
+ res.json({ token });
215
+ }
216
+ ```
217
+
218
+ **2. Configure `OneAuthClient` with the URL form**
219
+
220
+ ```typescript
221
+ import { OneAuthClient, createOneAuthProvider } from '@rhinestone/1auth';
222
+
223
+ const client = new OneAuthClient({
224
+ providerUrl: 'https://passkey.1auth.box',
225
+ sponsorship: {
226
+ accessTokenUrl: '/api/sponsorship/access-token',
227
+ extensionTokenUrl: '/api/sponsorship/extension-token',
228
+ },
229
+ });
230
+
231
+ const provider = createOneAuthProvider({ client, chainId: 8453 });
232
+
233
+ const txHash = await provider.request({
234
+ method: 'eth_sendTransaction',
235
+ params: [{ to: '0x...', data: '0x...' }],
236
+ });
237
+ ```
238
+
239
+ **React apps** can use the convenience hook:
240
+
241
+ ```tsx
242
+ import { useSponsorship } from '@rhinestone/1auth/react';
243
+ import { OneAuthClient } from '@rhinestone/1auth';
244
+
245
+ function MyApp() {
246
+ const sponsorship = useSponsorship({
247
+ accessTokenUrl: '/api/sponsorship/access-token',
248
+ extensionTokenUrl: '/api/sponsorship/extension-token',
249
+ });
250
+
251
+ const client = useMemo(
252
+ () => new OneAuthClient({ providerUrl, sponsorship }),
253
+ [sponsorship],
254
+ );
255
+ // ...
256
+ }
257
+ ```
258
+
259
+ The hook caches the access token in a ref and refetches on 401 from the extension-token endpoint.
260
+
261
+ ### SponsorshipConfig
262
+
263
+ `SponsorshipConfig` is a discriminated union — pick whichever form fits:
264
+
265
+ ```typescript
266
+ // Convenience form — SDK does the fetching.
267
+ type SponsorshipUrlConfig = {
268
+ accessTokenUrl: string; // GET → { token }
269
+ extensionTokenUrl: string; // POST { intentOp: string } → { token }
270
+ };
271
+
272
+ // Low-level form — you control token sourcing / refresh / caching.
273
+ type SponsorshipCallbackConfig = {
274
+ accessToken: () => Promise<string>;
275
+ getExtensionToken: (intentOp: string) => Promise<string>;
276
+ };
277
+
278
+ type SponsorshipConfig = SponsorshipUrlConfig | SponsorshipCallbackConfig;
279
+ ```
280
+
281
+ The SDK pre-fetches both tokens in parallel with the WebAuthn signing ceremony and passes them to the passkey service via the execute request body. The passkey service never makes outbound requests to your backend.
282
+
283
+ ### Without Sponsorship
284
+
285
+ If you don't pass `sponsorship`, 1auth sponsors intents on your behalf using its own API key. No changes needed — this is the default behavior.
286
+
128
287
  ## License
129
288
 
130
289
  MIT
@@ -0,0 +1,20 @@
1
+ // src/verify.ts
2
+ import { keccak256, toBytes } from "viem";
3
+ var ETHEREUM_MESSAGE_PREFIX = "Ethereum Signed Message:\n";
4
+ function hashMessage(message) {
5
+ const messageBytes = toBytes(message);
6
+ const prefixed = ETHEREUM_MESSAGE_PREFIX + messageBytes.length.toString() + message;
7
+ return keccak256(toBytes(prefixed));
8
+ }
9
+ function verifyMessageHash(message, signedHash) {
10
+ if (!signedHash) return false;
11
+ const expectedHash = hashMessage(message);
12
+ return expectedHash.toLowerCase() === signedHash.toLowerCase();
13
+ }
14
+
15
+ export {
16
+ ETHEREUM_MESSAGE_PREFIX,
17
+ hashMessage,
18
+ verifyMessageHash
19
+ };
20
+ //# sourceMappingURL=chunk-IHBVEU33.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/verify.ts"],"sourcesContent":["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"],"mappings":";AAAA,SAAS,WAAW,eAAe;AAM5B,IAAM,0BAA0B;AAuBhC,SAAS,YAAY,SAAgC;AAC1D,QAAM,eAAe,QAAQ,OAAO;AACpC,QAAM,WAAW,0BAA0B,aAAa,OAAO,SAAS,IAAI;AAC5E,SAAO,UAAU,QAAQ,QAAQ,CAAC;AACpC;AAmBO,SAAS,kBACd,SACA,YACS;AACT,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,eAAe,YAAY,OAAO;AACxC,SAAO,aAAa,YAAY,MAAM,WAAW,YAAY;AAC/D;","names":[]}
@@ -0,0 +1,28 @@
1
+ // src/sponsorship-fetch.ts
2
+ async function extractServerReason(response) {
3
+ try {
4
+ const text = await response.clone().text();
5
+ if (!text) return response.statusText;
6
+ try {
7
+ const data = JSON.parse(text);
8
+ const reason = data.error ?? data.message;
9
+ if (typeof reason === "string" && reason.trim()) return reason;
10
+ } catch {
11
+ }
12
+ return text.slice(0, 200);
13
+ } catch {
14
+ return response.statusText;
15
+ }
16
+ }
17
+ async function tokenFetchError(label, response) {
18
+ const reason = await extractServerReason(response);
19
+ const suffix = reason ? `: ${reason}` : "";
20
+ return new Error(
21
+ `Failed to fetch ${label} (${response.status} ${response.statusText})${suffix}`
22
+ );
23
+ }
24
+
25
+ export {
26
+ tokenFetchError
27
+ };
28
+ //# sourceMappingURL=chunk-IIACVHR3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sponsorship-fetch.ts"],"sourcesContent":["/**\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":";AAmBA,eAAe,oBAAoB,UAAqC;AACtE,MAAI;AAEF,UAAM,OAAO,MAAM,SAAS,MAAM,EAAE,KAAK;AACzC,QAAI,CAAC,KAAM,QAAO,SAAS;AAC3B,QAAI;AACF,YAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,YAAM,SAAS,KAAK,SAAS,KAAK;AAClC,UAAI,OAAO,WAAW,YAAY,OAAO,KAAK,EAAG,QAAO;AAAA,IAC1D,QAAQ;AAAA,IAER;AAEA,WAAO,KAAK,MAAM,GAAG,GAAG;AAAA,EAC1B,QAAQ;AACN,WAAO,SAAS;AAAA,EAClB;AACF;AAMA,eAAsB,gBACpB,OACA,UACgB;AAChB,QAAM,SAAS,MAAM,oBAAoB,QAAQ;AACjD,QAAM,SAAS,SAAS,KAAK,MAAM,KAAK;AACxC,SAAO,IAAI;AAAA,IACT,mBAAmB,KAAK,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,IAAI,MAAM;AAAA,EAC/E;AACF;","names":[]}
@@ -0,0 +1,72 @@
1
+ // src/walletClient/utils.ts
2
+ import { encodeAbiParameters, keccak256 } from "viem";
3
+ var P256_N = BigInt(
4
+ "0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
5
+ );
6
+ var P256_N_DIV_2 = P256_N / 2n;
7
+ var WEBAUTHN_AUTH_TYPE = {
8
+ type: "tuple",
9
+ components: [
10
+ { type: "bytes", name: "authenticatorData" },
11
+ { type: "string", name: "clientDataJSON" },
12
+ { type: "uint256", name: "challengeIndex" },
13
+ { type: "uint256", name: "typeIndex" },
14
+ { type: "uint256", name: "r" },
15
+ { type: "uint256", name: "s" }
16
+ ]
17
+ };
18
+ function encodeWebAuthnSignature(sig) {
19
+ let s = BigInt(sig.s);
20
+ if (s > P256_N_DIV_2) {
21
+ s = P256_N - s;
22
+ }
23
+ return encodeAbiParameters([WEBAUTHN_AUTH_TYPE], [
24
+ {
25
+ authenticatorData: sig.authenticatorData,
26
+ clientDataJSON: sig.clientDataJSON,
27
+ challengeIndex: BigInt(sig.challengeIndex),
28
+ typeIndex: BigInt(sig.typeIndex),
29
+ r: BigInt(sig.r),
30
+ s
31
+ }
32
+ ]);
33
+ }
34
+ function hashCalls(calls) {
35
+ const encoded = encodeAbiParameters(
36
+ [
37
+ {
38
+ type: "tuple[]",
39
+ components: [
40
+ { type: "address", name: "to" },
41
+ { type: "bytes", name: "data" },
42
+ { type: "uint256", name: "value" }
43
+ ]
44
+ }
45
+ ],
46
+ [
47
+ calls.map((c) => ({
48
+ to: c.to,
49
+ data: c.data || "0x",
50
+ value: c.value || 0n
51
+ }))
52
+ ]
53
+ );
54
+ return keccak256(encoded);
55
+ }
56
+ function buildTransactionReview(calls) {
57
+ return {
58
+ actions: calls.map((call, i) => ({
59
+ type: "custom",
60
+ label: call.label || `Contract Call ${i + 1}`,
61
+ sublabel: call.sublabel || `To: ${call.to.slice(0, 10)}...${call.to.slice(-8)}`,
62
+ amount: call.value ? `${call.value} wei` : void 0
63
+ }))
64
+ };
65
+ }
66
+
67
+ export {
68
+ encodeWebAuthnSignature,
69
+ hashCalls,
70
+ buildTransactionReview
71
+ };
72
+ //# sourceMappingURL=chunk-N6KE5CII.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/walletClient/utils.ts"],"sourcesContent":["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"],"mappings":";AAAA,SAAS,qBAAqB,iBAAiB;AAS/C,IAAM,SAAS;AAAA,EACb;AACF;AACA,IAAM,eAAe,SAAS;AAK9B,IAAM,qBAAqB;AAAA,EACzB,MAAM;AAAA,EACN,YAAY;AAAA,IACV,EAAE,MAAM,SAAS,MAAM,oBAAoB;AAAA,IAC3C,EAAE,MAAM,UAAU,MAAM,iBAAiB;AAAA,IACzC,EAAE,MAAM,WAAW,MAAM,iBAAiB;AAAA,IAC1C,EAAE,MAAM,WAAW,MAAM,YAAY;AAAA,IACrC,EAAE,MAAM,WAAW,MAAM,IAAI;AAAA,IAC7B,EAAE,MAAM,WAAW,MAAM,IAAI;AAAA,EAC/B;AACF;AAQO,SAAS,wBAAwB,KAA6B;AAEnE,MAAI,IAAI,OAAO,IAAI,CAAC;AACpB,MAAI,IAAI,cAAc;AACpB,QAAI,SAAS;AAAA,EACf;AAEA,SAAO,oBAAoB,CAAC,kBAAkB,GAAG;AAAA,IAC/C;AAAA,MACE,mBAAmB,IAAI;AAAA,MACvB,gBAAgB,IAAI;AAAA,MACpB,gBAAgB,OAAO,IAAI,cAAc;AAAA,MACzC,WAAW,OAAO,IAAI,SAAS;AAAA,MAC/B,GAAG,OAAO,IAAI,CAAC;AAAA,MACf;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAQO,SAAS,UAAU,OAA+B;AACvD,QAAM,UAAU;AAAA,IACd;AAAA,MACE;AAAA,QACE,MAAM;AAAA,QACN,YAAY;AAAA,UACV,EAAE,MAAM,WAAW,MAAM,KAAK;AAAA,UAC9B,EAAE,MAAM,SAAS,MAAM,OAAO;AAAA,UAC9B,EAAE,MAAM,WAAW,MAAM,QAAQ;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAAA,IACA;AAAA,MACE,MAAM,IAAI,CAAC,OAAO;AAAA,QAChB,IAAI,EAAE;AAAA,QACN,MAAM,EAAE,QAAQ;AAAA,QAChB,OAAO,EAAE,SAAS;AAAA,MACpB,EAAE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,UAAU,OAAO;AAC1B;AAQO,SAAS,uBAAuB,OAA0B;AAC/D,SAAO;AAAA,IACL,SAAS,MAAM,IAAI,CAAC,MAAM,OAAO;AAAA,MAC/B,MAAM;AAAA,MACN,OAAO,KAAK,SAAS,iBAAiB,IAAI,CAAC;AAAA,MAC3C,UAAU,KAAK,YAAY,OAAO,KAAK,GAAG,MAAM,GAAG,EAAE,CAAC,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;AAAA,MAC7E,QAAQ,KAAK,QAAQ,GAAG,KAAK,KAAK,SAAS;AAAA,IAC7C,EAAE;AAAA,EACJ;AACF;","names":[]}