@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.
- package/README.md +171 -12
- package/dist/chunk-IHBVEU33.mjs +20 -0
- package/dist/chunk-IHBVEU33.mjs.map +1 -0
- package/dist/chunk-IIACVHR3.mjs +28 -0
- package/dist/chunk-IIACVHR3.mjs.map +1 -0
- package/dist/chunk-N6KE5CII.mjs +72 -0
- package/dist/chunk-N6KE5CII.mjs.map +1 -0
- package/dist/{chunk-SXISYG2P.mjs → chunk-VZYHCFEH.mjs} +195 -140
- package/dist/chunk-VZYHCFEH.mjs.map +1 -0
- package/dist/{client-BrMrhetG.d.mts → client-BNluVe4_.d.ts} +305 -849
- package/dist/{client-BrMrhetG.d.ts → client-CLCdahyj.d.mts} +305 -849
- package/dist/headless.d.mts +90 -0
- package/dist/headless.d.ts +90 -0
- package/dist/headless.js +327 -0
- package/dist/headless.js.map +1 -0
- package/dist/headless.mjs +280 -0
- package/dist/headless.mjs.map +1 -0
- package/dist/index.d.mts +96 -144
- package/dist/index.d.ts +96 -144
- package/dist/index.js +2984 -718
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2740 -627
- package/dist/index.mjs.map +1 -1
- package/dist/{provider-CDl9wYEc.d.mts → provider-BgD9PeDX.d.ts} +6 -5
- package/dist/{provider-Dgv533YQ.d.ts → provider-hxHGb6SX.d.mts} +6 -5
- package/dist/react.d.mts +42 -2
- package/dist/react.d.ts +42 -2
- package/dist/react.js +92 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +66 -1
- package/dist/react.mjs.map +1 -1
- package/dist/server.d.mts +118 -0
- package/dist/server.d.ts +118 -0
- package/dist/server.js +356 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +282 -0
- package/dist/server.mjs.map +1 -0
- package/dist/types-1BMD1PSH.d.mts +1425 -0
- package/dist/types-1BMD1PSH.d.ts +1425 -0
- package/dist/verify-CZe-m_Vf.d.ts +150 -0
- package/dist/verify-D3FaLeAi.d.mts +150 -0
- package/dist/wagmi.d.mts +5 -2
- package/dist/wagmi.d.ts +5 -2
- package/dist/wagmi.js +138 -43
- package/dist/wagmi.js.map +1 -1
- package/dist/wagmi.mjs +2 -1
- package/dist/wagmi.mjs.map +1 -1
- package/package.json +15 -2
- 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` |
|
|
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
|
|
87
|
+
### Server-Side JWT Sponsorship
|
|
88
88
|
|
|
89
89
|
```typescript
|
|
90
|
-
import {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
- `
|
|
122
|
-
- `
|
|
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":[]}
|