@rhinestone/1auth 0.1.0
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/dist/chunk-UXYKIMGZ.mjs +482 -0
- package/dist/chunk-UXYKIMGZ.mjs.map +1 -0
- package/dist/client-C1inywuT.d.mts +777 -0
- package/dist/client-C1inywuT.d.ts +777 -0
- package/dist/index.d.mts +267 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +2701 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2213 -0
- package/dist/index.mjs.map +1 -0
- package/dist/provider-Dgh51NRc.d.mts +24 -0
- package/dist/provider-q7M728Mn.d.ts +24 -0
- package/dist/react.d.mts +41 -0
- package/dist/react.d.ts +41 -0
- package/dist/react.js +228 -0
- package/dist/react.js.map +1 -0
- package/dist/react.mjs +193 -0
- package/dist/react.mjs.map +1 -0
- package/dist/server.d.mts +81 -0
- package/dist/server.d.ts +81 -0
- package/dist/server.js +142 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +116 -0
- package/dist/server.mjs.map +1 -0
- package/dist/wagmi.d.mts +15 -0
- package/dist/wagmi.d.ts +15 -0
- package/dist/wagmi.js +569 -0
- package/dist/wagmi.js.map +1 -0
- package/dist/wagmi.mjs +176 -0
- package/dist/wagmi.mjs.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2701 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BatchQueueProvider: () => BatchQueueProvider,
|
|
34
|
+
BatchQueueWidget: () => BatchQueueWidget,
|
|
35
|
+
PASSKEY_MESSAGE_PREFIX: () => PASSKEY_MESSAGE_PREFIX,
|
|
36
|
+
PasskeyProviderClient: () => PasskeyProviderClient,
|
|
37
|
+
createPasskeyAccount: () => createPasskeyAccount,
|
|
38
|
+
createPasskeyProvider: () => createPasskeyProvider,
|
|
39
|
+
createPasskeyWalletClient: () => createPasskeyWalletClient,
|
|
40
|
+
encodeWebAuthnSignature: () => encodeWebAuthnSignature,
|
|
41
|
+
getAllSupportedChainsAndTokens: () => getAllSupportedChainsAndTokens,
|
|
42
|
+
getChainById: () => getChainById,
|
|
43
|
+
getChainExplorerUrl: () => getChainExplorerUrl,
|
|
44
|
+
getChainName: () => getChainName2,
|
|
45
|
+
getChainRpcUrl: () => getChainRpcUrl,
|
|
46
|
+
getSupportedChainIds: () => getSupportedChainIds,
|
|
47
|
+
getSupportedChains: () => getSupportedChains,
|
|
48
|
+
getSupportedTokenSymbols: () => getSupportedTokenSymbols,
|
|
49
|
+
getSupportedTokens: () => getSupportedTokens,
|
|
50
|
+
getTokenAddress: () => import_sdk.getTokenAddress,
|
|
51
|
+
getTokenDecimals: () => import_sdk.getTokenDecimals,
|
|
52
|
+
getTokenSymbol: () => getTokenSymbol,
|
|
53
|
+
hashCalls: () => hashCalls,
|
|
54
|
+
hashMessage: () => hashMessage2,
|
|
55
|
+
isTestnet: () => isTestnet,
|
|
56
|
+
isTokenAddressSupported: () => isTokenAddressSupported,
|
|
57
|
+
resolveTokenAddress: () => resolveTokenAddress,
|
|
58
|
+
useBatchQueue: () => useBatchQueue,
|
|
59
|
+
verifyMessageHash: () => verifyMessageHash
|
|
60
|
+
});
|
|
61
|
+
module.exports = __toCommonJS(index_exports);
|
|
62
|
+
|
|
63
|
+
// src/client.ts
|
|
64
|
+
var import_viem2 = require("viem");
|
|
65
|
+
|
|
66
|
+
// src/registry.ts
|
|
67
|
+
var import_viem = require("viem");
|
|
68
|
+
var viemChains = __toESM(require("viem/chains"));
|
|
69
|
+
var import_sdk = require("@rhinestone/sdk");
|
|
70
|
+
var env = typeof process !== "undefined" ? process.env : {};
|
|
71
|
+
var ALL_VIEM_CHAINS = Object.values(viemChains).filter(
|
|
72
|
+
(value) => typeof value === "object" && value !== null && "id" in value && "name" in value
|
|
73
|
+
);
|
|
74
|
+
var VIEM_CHAIN_BY_ID = new Map(
|
|
75
|
+
ALL_VIEM_CHAINS.map((chain) => [chain.id, chain])
|
|
76
|
+
);
|
|
77
|
+
var SUPPORTED_CHAIN_IDS = new Set(
|
|
78
|
+
(0, import_sdk.getAllSupportedChainsAndTokens)().map((entry) => entry.chainId)
|
|
79
|
+
);
|
|
80
|
+
function parseBool(value) {
|
|
81
|
+
if (value === "true" || value === "1") return true;
|
|
82
|
+
if (value === "false" || value === "0") return false;
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
function resolveIncludeTestnets(explicit) {
|
|
86
|
+
if (explicit !== void 0) return explicit;
|
|
87
|
+
const envValue = parseBool(env.NEXT_PUBLIC_ORCHESTRATOR_USE_TESTNETS) ?? parseBool(env.ORCHESTRATOR_USE_TESTNETS);
|
|
88
|
+
return envValue ?? false;
|
|
89
|
+
}
|
|
90
|
+
function applyChainFilters(chainIds, options) {
|
|
91
|
+
const includeTestnets = resolveIncludeTestnets(options?.includeTestnets);
|
|
92
|
+
const allowlist = options?.chainIds;
|
|
93
|
+
let filtered = chainIds;
|
|
94
|
+
if (!includeTestnets) {
|
|
95
|
+
filtered = filtered.filter((chainId) => !isTestnet(chainId));
|
|
96
|
+
}
|
|
97
|
+
if (allowlist && allowlist.length > 0) {
|
|
98
|
+
const allowed = new Set(allowlist);
|
|
99
|
+
filtered = filtered.filter((chainId) => allowed.has(chainId));
|
|
100
|
+
}
|
|
101
|
+
return filtered;
|
|
102
|
+
}
|
|
103
|
+
function getSupportedChainIds(options) {
|
|
104
|
+
return applyChainFilters(Array.from(SUPPORTED_CHAIN_IDS), options);
|
|
105
|
+
}
|
|
106
|
+
function getSupportedChains(options) {
|
|
107
|
+
return getSupportedChainIds(options).map((chainId) => VIEM_CHAIN_BY_ID.get(chainId)).filter((chain) => Boolean(chain));
|
|
108
|
+
}
|
|
109
|
+
function getAllSupportedChainsAndTokens(options) {
|
|
110
|
+
const allowed = new Set(getSupportedChainIds(options));
|
|
111
|
+
return (0, import_sdk.getAllSupportedChainsAndTokens)().filter((entry) => allowed.has(entry.chainId)).map((entry) => ({
|
|
112
|
+
chainId: entry.chainId,
|
|
113
|
+
tokens: entry.tokens
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
function getChainById(chainId) {
|
|
117
|
+
if (!SUPPORTED_CHAIN_IDS.has(chainId)) {
|
|
118
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
119
|
+
}
|
|
120
|
+
const chain = VIEM_CHAIN_BY_ID.get(chainId);
|
|
121
|
+
if (!chain) {
|
|
122
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
123
|
+
}
|
|
124
|
+
return chain;
|
|
125
|
+
}
|
|
126
|
+
function getChainName(chainId) {
|
|
127
|
+
try {
|
|
128
|
+
return getChainById(chainId).name;
|
|
129
|
+
} catch {
|
|
130
|
+
return `Chain ${chainId}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function getChainExplorerUrl(chainId) {
|
|
134
|
+
try {
|
|
135
|
+
return getChainById(chainId).blockExplorers?.default?.url;
|
|
136
|
+
} catch {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function getChainRpcUrl(chainId) {
|
|
141
|
+
try {
|
|
142
|
+
const chain = getChainById(chainId);
|
|
143
|
+
return chain.rpcUrls?.default?.http?.[0] || chain.rpcUrls?.public?.http?.[0];
|
|
144
|
+
} catch {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function getSupportedTokens(chainId) {
|
|
149
|
+
return (0, import_sdk.getSupportedTokens)(chainId);
|
|
150
|
+
}
|
|
151
|
+
function getSupportedTokenSymbols(chainId) {
|
|
152
|
+
return getSupportedTokens(chainId).map((token) => token.symbol);
|
|
153
|
+
}
|
|
154
|
+
function resolveTokenAddress(token, chainId) {
|
|
155
|
+
if ((0, import_viem.isAddress)(token)) {
|
|
156
|
+
return token;
|
|
157
|
+
}
|
|
158
|
+
return (0, import_sdk.getTokenAddress)(token.toUpperCase(), chainId);
|
|
159
|
+
}
|
|
160
|
+
function isTestnet(chainId) {
|
|
161
|
+
try {
|
|
162
|
+
return getChainById(chainId).testnet ?? false;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function getTokenSymbol(tokenAddress, chainId) {
|
|
168
|
+
const token = getSupportedTokens(chainId).find(
|
|
169
|
+
(entry) => entry.address.toLowerCase() === tokenAddress.toLowerCase()
|
|
170
|
+
);
|
|
171
|
+
if (!token) {
|
|
172
|
+
throw new Error(`Unsupported token: ${tokenAddress} on chain ${chainId}`);
|
|
173
|
+
}
|
|
174
|
+
return token.symbol;
|
|
175
|
+
}
|
|
176
|
+
function isTokenAddressSupported(tokenAddress, chainId) {
|
|
177
|
+
try {
|
|
178
|
+
return getSupportedTokens(chainId).some(
|
|
179
|
+
(entry) => entry.address.toLowerCase() === tokenAddress.toLowerCase()
|
|
180
|
+
);
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/client.ts
|
|
187
|
+
var POPUP_WIDTH = 450;
|
|
188
|
+
var POPUP_HEIGHT = 600;
|
|
189
|
+
var DEFAULT_EMBED_WIDTH = "400px";
|
|
190
|
+
var DEFAULT_EMBED_HEIGHT = "500px";
|
|
191
|
+
var MODAL_WIDTH = 360;
|
|
192
|
+
var PasskeyProviderClient = class {
|
|
193
|
+
constructor(config) {
|
|
194
|
+
this.config = config;
|
|
195
|
+
this.theme = config.theme || {};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Update the theme configuration at runtime
|
|
199
|
+
*/
|
|
200
|
+
setTheme(theme) {
|
|
201
|
+
this.theme = theme;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Build theme URL parameters
|
|
205
|
+
*/
|
|
206
|
+
getThemeParams(overrideTheme) {
|
|
207
|
+
const theme = { ...this.theme, ...overrideTheme };
|
|
208
|
+
const params = new URLSearchParams();
|
|
209
|
+
if (theme.mode) {
|
|
210
|
+
params.set("theme", theme.mode);
|
|
211
|
+
}
|
|
212
|
+
if (theme.accent) {
|
|
213
|
+
params.set("accent", theme.accent);
|
|
214
|
+
}
|
|
215
|
+
return params.toString();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get the dialog URL (Vite app URL)
|
|
219
|
+
* Defaults to providerUrl if dialogUrl is not set
|
|
220
|
+
*/
|
|
221
|
+
getDialogUrl() {
|
|
222
|
+
return this.config.dialogUrl || this.config.providerUrl;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Get the origin for message validation
|
|
226
|
+
* Uses dialogUrl origin if set, otherwise providerUrl origin
|
|
227
|
+
*/
|
|
228
|
+
getDialogOrigin() {
|
|
229
|
+
const dialogUrl = this.getDialogUrl();
|
|
230
|
+
try {
|
|
231
|
+
return new URL(dialogUrl).origin;
|
|
232
|
+
} catch {
|
|
233
|
+
return dialogUrl;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Get the base provider URL
|
|
238
|
+
*/
|
|
239
|
+
getProviderUrl() {
|
|
240
|
+
return this.config.providerUrl;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get the configured client ID
|
|
244
|
+
*/
|
|
245
|
+
getClientId() {
|
|
246
|
+
return this.config.clientId;
|
|
247
|
+
}
|
|
248
|
+
async waitForTransactionHash(intentId, options = {}) {
|
|
249
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
250
|
+
const intervalMs = options.intervalMs ?? 2e3;
|
|
251
|
+
const deadline = Date.now() + timeoutMs;
|
|
252
|
+
while (Date.now() < deadline) {
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(
|
|
255
|
+
`${this.config.providerUrl}/api/intent/status/${intentId}`,
|
|
256
|
+
{
|
|
257
|
+
headers: {
|
|
258
|
+
"x-client-id": this.config.clientId
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
if (response.ok) {
|
|
263
|
+
const data = await response.json();
|
|
264
|
+
if (data.transactionHash) {
|
|
265
|
+
return data.transactionHash;
|
|
266
|
+
}
|
|
267
|
+
if (data.status === "failed" || data.status === "expired") {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
274
|
+
}
|
|
275
|
+
return void 0;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Show Porto-style "Get started" auth dialog (combines sign in + sign up)
|
|
279
|
+
* This is the recommended method for authentication - shows a modal overlay
|
|
280
|
+
* with both sign in and create account options.
|
|
281
|
+
*/
|
|
282
|
+
async authWithModal(options) {
|
|
283
|
+
const dialogUrl = this.getDialogUrl();
|
|
284
|
+
const params = new URLSearchParams({
|
|
285
|
+
clientId: this.config.clientId,
|
|
286
|
+
mode: "iframe"
|
|
287
|
+
});
|
|
288
|
+
if (options?.username) {
|
|
289
|
+
params.set("username", options.username);
|
|
290
|
+
}
|
|
291
|
+
if (options?.oauthEnabled === false) {
|
|
292
|
+
params.set("oauth", "0");
|
|
293
|
+
}
|
|
294
|
+
const themeParams = this.getThemeParams(options?.theme);
|
|
295
|
+
if (themeParams) {
|
|
296
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
297
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
298
|
+
}
|
|
299
|
+
const url = `${dialogUrl}/dialog/auth?${params.toString()}`;
|
|
300
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(url);
|
|
301
|
+
return this.waitForModalAuthResponse(dialog, iframe, cleanup);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Authenticate a user with an optional challenge to sign.
|
|
305
|
+
*
|
|
306
|
+
* This method combines authentication (sign in / sign up) with optional
|
|
307
|
+
* challenge signing, enabling off-chain login without on-chain transactions.
|
|
308
|
+
*
|
|
309
|
+
* When a challenge is provided:
|
|
310
|
+
* 1. User authenticates (sign in or sign up)
|
|
311
|
+
* 2. The challenge is hashed with a domain separator
|
|
312
|
+
* 3. User signs the hash with their passkey
|
|
313
|
+
* 4. Returns user info + signature for server-side verification
|
|
314
|
+
*
|
|
315
|
+
* The domain separator ("\x19Passkey Signed Message:\n") ensures the signature
|
|
316
|
+
* cannot be reused for transaction signing, preventing phishing attacks.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* // Authenticate with a login challenge
|
|
321
|
+
* const result = await client.authenticate({
|
|
322
|
+
* challenge: `Login to MyApp\nTimestamp: ${Date.now()}\nNonce: ${crypto.randomUUID()}`
|
|
323
|
+
* });
|
|
324
|
+
*
|
|
325
|
+
* if (result.success && result.signature) {
|
|
326
|
+
* // Verify signature server-side
|
|
327
|
+
* const isValid = await verifyOnServer(
|
|
328
|
+
* result.username,
|
|
329
|
+
* result.signature,
|
|
330
|
+
* result.signedHash
|
|
331
|
+
* );
|
|
332
|
+
* }
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
async authenticate(options) {
|
|
336
|
+
const dialogUrl = this.getDialogUrl();
|
|
337
|
+
const params = new URLSearchParams({
|
|
338
|
+
clientId: this.config.clientId,
|
|
339
|
+
mode: "iframe"
|
|
340
|
+
});
|
|
341
|
+
if (options?.challenge) {
|
|
342
|
+
params.set("challenge", options.challenge);
|
|
343
|
+
}
|
|
344
|
+
const themeParams = this.getThemeParams(options?.theme);
|
|
345
|
+
if (themeParams) {
|
|
346
|
+
const themeParsed = new URLSearchParams(themeParams);
|
|
347
|
+
themeParsed.forEach((value, key) => params.set(key, value));
|
|
348
|
+
}
|
|
349
|
+
const url = `${dialogUrl}/dialog/authenticate?${params.toString()}`;
|
|
350
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(url);
|
|
351
|
+
return this.waitForAuthenticateResponse(dialog, iframe, cleanup);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Show signing in a modal overlay (Porto-style iframe dialog)
|
|
355
|
+
*/
|
|
356
|
+
async signWithModal(options) {
|
|
357
|
+
const dialogUrl = this.getDialogUrl();
|
|
358
|
+
const themeParams = this.getThemeParams(options?.theme);
|
|
359
|
+
const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
|
|
360
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
|
|
361
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
362
|
+
await new Promise((resolve) => {
|
|
363
|
+
iframe.onload = () => {
|
|
364
|
+
iframe.contentWindow?.postMessage({
|
|
365
|
+
type: "PASSKEY_INIT",
|
|
366
|
+
mode: "iframe",
|
|
367
|
+
challenge: options.challenge,
|
|
368
|
+
username: options.username,
|
|
369
|
+
description: options.description,
|
|
370
|
+
transaction: options.transaction,
|
|
371
|
+
metadata: options.metadata
|
|
372
|
+
}, dialogOrigin);
|
|
373
|
+
resolve();
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
return this.waitForSigningResponse(dialog, iframe, cleanup);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Send an intent to the Rhinestone orchestrator
|
|
380
|
+
*
|
|
381
|
+
* This is the high-level method for cross-chain transactions:
|
|
382
|
+
* 1. Prepares the intent (gets quote from orchestrator)
|
|
383
|
+
* 2. Shows the signing modal with real fees
|
|
384
|
+
* 3. Submits the signed intent for execution
|
|
385
|
+
* 4. Returns the transaction hash
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* const result = await client.sendIntent({
|
|
390
|
+
* username: 'alice',
|
|
391
|
+
* targetChain: 8453, // Base
|
|
392
|
+
* calls: [
|
|
393
|
+
* {
|
|
394
|
+
* to: '0x...',
|
|
395
|
+
* data: '0x...',
|
|
396
|
+
* label: 'Swap ETH for USDC',
|
|
397
|
+
* sublabel: '0.1 ETH → ~250 USDC',
|
|
398
|
+
* },
|
|
399
|
+
* ],
|
|
400
|
+
* });
|
|
401
|
+
*
|
|
402
|
+
* if (result.success) {
|
|
403
|
+
* console.log('Transaction hash:', result.transactionHash);
|
|
404
|
+
* }
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
async sendIntent(options) {
|
|
408
|
+
const signedIntent = options.signedIntent;
|
|
409
|
+
const username = signedIntent?.username || options.username;
|
|
410
|
+
const targetChain = signedIntent?.targetChain || options.targetChain;
|
|
411
|
+
const calls = signedIntent?.calls || options.calls;
|
|
412
|
+
if (!username && !signedIntent?.accountAddress) {
|
|
413
|
+
return {
|
|
414
|
+
success: false,
|
|
415
|
+
intentId: "",
|
|
416
|
+
status: "failed",
|
|
417
|
+
error: {
|
|
418
|
+
code: "INVALID_OPTIONS",
|
|
419
|
+
message: "Either username, accountAddress, or signedIntent with user identifier is required"
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
if (!targetChain || !calls?.length) {
|
|
424
|
+
return {
|
|
425
|
+
success: false,
|
|
426
|
+
intentId: "",
|
|
427
|
+
status: "failed",
|
|
428
|
+
error: {
|
|
429
|
+
code: "INVALID_OPTIONS",
|
|
430
|
+
message: "targetChain and calls are required (either directly or via signedIntent)"
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
let prepareResponse;
|
|
435
|
+
try {
|
|
436
|
+
const requestBody = signedIntent || {
|
|
437
|
+
username: options.username,
|
|
438
|
+
targetChain: options.targetChain,
|
|
439
|
+
calls: options.calls,
|
|
440
|
+
tokenRequests: options.tokenRequests,
|
|
441
|
+
sourceAssets: options.sourceAssets,
|
|
442
|
+
clientId: this.config.clientId
|
|
443
|
+
};
|
|
444
|
+
const response = await fetch(`${this.config.providerUrl}/api/intent/prepare`, {
|
|
445
|
+
method: "POST",
|
|
446
|
+
headers: {
|
|
447
|
+
"Content-Type": "application/json"
|
|
448
|
+
},
|
|
449
|
+
body: JSON.stringify(requestBody)
|
|
450
|
+
});
|
|
451
|
+
if (!response.ok) {
|
|
452
|
+
const errorData = await response.json().catch(() => ({}));
|
|
453
|
+
const errorMessage = errorData.error || "Failed to prepare intent";
|
|
454
|
+
if (errorMessage.includes("User not found")) {
|
|
455
|
+
localStorage.removeItem("1auth-user");
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
success: false,
|
|
459
|
+
intentId: "",
|
|
460
|
+
status: "failed",
|
|
461
|
+
error: {
|
|
462
|
+
code: errorMessage.includes("User not found") ? "USER_NOT_FOUND" : "PREPARE_FAILED",
|
|
463
|
+
message: errorMessage
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
prepareResponse = await response.json();
|
|
468
|
+
} catch (error) {
|
|
469
|
+
return {
|
|
470
|
+
success: false,
|
|
471
|
+
intentId: "",
|
|
472
|
+
status: "failed",
|
|
473
|
+
error: {
|
|
474
|
+
code: "NETWORK_ERROR",
|
|
475
|
+
message: error instanceof Error ? error.message : "Network error"
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const dialogUrl = this.getDialogUrl();
|
|
480
|
+
const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe`;
|
|
481
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
|
|
482
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
483
|
+
await new Promise((resolve) => {
|
|
484
|
+
const handleReady = (event) => {
|
|
485
|
+
if (event.origin !== dialogOrigin) return;
|
|
486
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
487
|
+
window.removeEventListener("message", handleReady);
|
|
488
|
+
iframe.contentWindow?.postMessage({
|
|
489
|
+
type: "PASSKEY_INIT",
|
|
490
|
+
mode: "iframe",
|
|
491
|
+
calls,
|
|
492
|
+
chainId: targetChain,
|
|
493
|
+
transaction: prepareResponse.transaction,
|
|
494
|
+
challenge: prepareResponse.challenge,
|
|
495
|
+
username,
|
|
496
|
+
accountAddress: prepareResponse.accountAddress,
|
|
497
|
+
intentId: prepareResponse.intentId
|
|
498
|
+
}, dialogOrigin);
|
|
499
|
+
resolve();
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
window.addEventListener("message", handleReady);
|
|
503
|
+
});
|
|
504
|
+
const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
|
|
505
|
+
if (!signingResult.success) {
|
|
506
|
+
return {
|
|
507
|
+
success: false,
|
|
508
|
+
intentId: prepareResponse.intentId,
|
|
509
|
+
status: "failed",
|
|
510
|
+
error: signingResult.error
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
let executeResponse;
|
|
514
|
+
try {
|
|
515
|
+
const response = await fetch(`${this.config.providerUrl}/api/intent/execute`, {
|
|
516
|
+
method: "POST",
|
|
517
|
+
headers: {
|
|
518
|
+
"Content-Type": "application/json"
|
|
519
|
+
},
|
|
520
|
+
body: JSON.stringify({
|
|
521
|
+
intentId: prepareResponse.intentId,
|
|
522
|
+
signature: signingResult.signature,
|
|
523
|
+
passkey: signingResult.passkey
|
|
524
|
+
// Include passkey info for signature encoding
|
|
525
|
+
})
|
|
526
|
+
});
|
|
527
|
+
if (!response.ok) {
|
|
528
|
+
const errorData = await response.json().catch(() => ({}));
|
|
529
|
+
this.sendTransactionStatus(iframe, "failed");
|
|
530
|
+
await this.waitForDialogClose(dialog, cleanup);
|
|
531
|
+
return {
|
|
532
|
+
success: false,
|
|
533
|
+
intentId: prepareResponse.intentId,
|
|
534
|
+
status: "failed",
|
|
535
|
+
error: {
|
|
536
|
+
code: "EXECUTE_FAILED",
|
|
537
|
+
message: errorData.error || "Failed to execute intent"
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
executeResponse = await response.json();
|
|
542
|
+
} catch (error) {
|
|
543
|
+
this.sendTransactionStatus(iframe, "failed");
|
|
544
|
+
await this.waitForDialogClose(dialog, cleanup);
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
intentId: prepareResponse.intentId,
|
|
548
|
+
status: "failed",
|
|
549
|
+
error: {
|
|
550
|
+
code: "NETWORK_ERROR",
|
|
551
|
+
message: error instanceof Error ? error.message : "Network error"
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const closeOn = options.closeOn || "preconfirmed";
|
|
556
|
+
const acceptPreconfirmations = closeOn !== "completed";
|
|
557
|
+
let finalStatus = executeResponse.status;
|
|
558
|
+
let finalTxHash = executeResponse.transactionHash;
|
|
559
|
+
if (finalStatus === "pending") {
|
|
560
|
+
this.sendTransactionStatus(iframe, "processing");
|
|
561
|
+
try {
|
|
562
|
+
const waitResponse = await fetch(
|
|
563
|
+
`${this.config.providerUrl}/api/intent/wait/${prepareResponse.intentId}?preconfirm=${acceptPreconfirmations}`,
|
|
564
|
+
{
|
|
565
|
+
headers: {
|
|
566
|
+
"x-client-id": this.config.clientId
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
if (waitResponse.ok) {
|
|
571
|
+
const waitResult = await waitResponse.json();
|
|
572
|
+
finalStatus = waitResult.status === "preconfirmed" || waitResult.status === "completed" ? "completed" : waitResult.status;
|
|
573
|
+
finalTxHash = waitResult.transactionHash;
|
|
574
|
+
} else {
|
|
575
|
+
console.error("Wait endpoint failed:", await waitResponse.text());
|
|
576
|
+
}
|
|
577
|
+
} catch (waitError) {
|
|
578
|
+
console.error("Failed to wait for intent:", waitError);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const displayStatus = finalStatus === "completed" ? "confirmed" : finalStatus;
|
|
582
|
+
this.sendTransactionStatus(iframe, displayStatus, finalTxHash);
|
|
583
|
+
await this.waitForDialogClose(dialog, cleanup);
|
|
584
|
+
if (options.waitForHash && !finalTxHash) {
|
|
585
|
+
const hash = await this.waitForTransactionHash(prepareResponse.intentId, {
|
|
586
|
+
timeoutMs: options.hashTimeoutMs,
|
|
587
|
+
intervalMs: options.hashIntervalMs
|
|
588
|
+
});
|
|
589
|
+
if (hash) {
|
|
590
|
+
finalTxHash = hash;
|
|
591
|
+
finalStatus = "completed";
|
|
592
|
+
} else {
|
|
593
|
+
finalStatus = "failed";
|
|
594
|
+
return {
|
|
595
|
+
success: false,
|
|
596
|
+
intentId: prepareResponse.intentId,
|
|
597
|
+
status: finalStatus,
|
|
598
|
+
transactionHash: finalTxHash,
|
|
599
|
+
operationId: executeResponse.operationId,
|
|
600
|
+
error: {
|
|
601
|
+
code: "HASH_TIMEOUT",
|
|
602
|
+
message: "Timed out waiting for transaction hash"
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
success: finalStatus === "completed",
|
|
609
|
+
intentId: prepareResponse.intentId,
|
|
610
|
+
status: finalStatus,
|
|
611
|
+
transactionHash: finalTxHash,
|
|
612
|
+
operationId: executeResponse.operationId,
|
|
613
|
+
error: executeResponse.error
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Send transaction status to the dialog iframe
|
|
618
|
+
*/
|
|
619
|
+
sendTransactionStatus(iframe, status, transactionHash) {
|
|
620
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
621
|
+
iframe.contentWindow?.postMessage(
|
|
622
|
+
{
|
|
623
|
+
type: "TRANSACTION_STATUS",
|
|
624
|
+
status,
|
|
625
|
+
transactionHash
|
|
626
|
+
},
|
|
627
|
+
dialogOrigin
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Wait for the signing result without closing the modal
|
|
632
|
+
*/
|
|
633
|
+
waitForIntentSigningResponse(requestId, dialog, _iframe, cleanup) {
|
|
634
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
635
|
+
return new Promise((resolve) => {
|
|
636
|
+
const handleMessage = (event) => {
|
|
637
|
+
if (event.origin !== dialogOrigin) return;
|
|
638
|
+
const message = event.data;
|
|
639
|
+
const payload = message?.data;
|
|
640
|
+
if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
|
|
641
|
+
window.removeEventListener("message", handleMessage);
|
|
642
|
+
if (message.success && payload.signature) {
|
|
643
|
+
resolve({
|
|
644
|
+
success: true,
|
|
645
|
+
requestId,
|
|
646
|
+
signature: payload.signature,
|
|
647
|
+
passkey: payload.passkey
|
|
648
|
+
// Include passkey info for signature encoding
|
|
649
|
+
});
|
|
650
|
+
} else {
|
|
651
|
+
resolve({
|
|
652
|
+
success: false,
|
|
653
|
+
error: message.error || {
|
|
654
|
+
code: "SIGNING_FAILED",
|
|
655
|
+
message: "Signing failed"
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
} else if (message?.type === "PASSKEY_CLOSE") {
|
|
660
|
+
window.removeEventListener("message", handleMessage);
|
|
661
|
+
cleanup();
|
|
662
|
+
resolve({
|
|
663
|
+
success: false,
|
|
664
|
+
error: {
|
|
665
|
+
code: "USER_REJECTED",
|
|
666
|
+
message: "User closed the dialog"
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
window.addEventListener("message", handleMessage);
|
|
672
|
+
dialog.showModal();
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Wait for signing result (simplified - no requestId matching)
|
|
677
|
+
*/
|
|
678
|
+
waitForSigningResponse(dialog, _iframe, cleanup) {
|
|
679
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
680
|
+
console.log("[SDK] waitForSigningResponse, expecting origin:", dialogOrigin);
|
|
681
|
+
return new Promise((resolve) => {
|
|
682
|
+
const handleMessage = (event) => {
|
|
683
|
+
console.log("[SDK] Received message:", event.origin, event.data?.type);
|
|
684
|
+
if (event.origin !== dialogOrigin) {
|
|
685
|
+
console.log("[SDK] Origin mismatch, ignoring. Expected:", dialogOrigin, "Got:", event.origin);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
const message = event.data;
|
|
689
|
+
const payload = message?.data;
|
|
690
|
+
if (message?.type === "PASSKEY_SIGNING_RESULT") {
|
|
691
|
+
window.removeEventListener("message", handleMessage);
|
|
692
|
+
if (message.success && payload?.signature) {
|
|
693
|
+
resolve({
|
|
694
|
+
success: true,
|
|
695
|
+
signature: payload.signature,
|
|
696
|
+
passkey: payload.passkey,
|
|
697
|
+
signedHash: payload.signedHash
|
|
698
|
+
});
|
|
699
|
+
} else {
|
|
700
|
+
resolve({
|
|
701
|
+
success: false,
|
|
702
|
+
error: message.error || {
|
|
703
|
+
code: "SIGNING_FAILED",
|
|
704
|
+
message: "Signing failed"
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
} else if (message?.type === "PASSKEY_CLOSE") {
|
|
709
|
+
window.removeEventListener("message", handleMessage);
|
|
710
|
+
cleanup();
|
|
711
|
+
resolve({
|
|
712
|
+
success: false,
|
|
713
|
+
error: {
|
|
714
|
+
code: "USER_REJECTED",
|
|
715
|
+
message: "User closed the dialog"
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
window.addEventListener("message", handleMessage);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Wait for the dialog to be closed
|
|
725
|
+
*/
|
|
726
|
+
waitForDialogClose(dialog, cleanup) {
|
|
727
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
728
|
+
return new Promise((resolve) => {
|
|
729
|
+
const handleMessage = (event) => {
|
|
730
|
+
if (event.origin !== dialogOrigin) return;
|
|
731
|
+
if (event.data?.type === "PASSKEY_CLOSE") {
|
|
732
|
+
window.removeEventListener("message", handleMessage);
|
|
733
|
+
cleanup();
|
|
734
|
+
resolve();
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
const handleClose = () => {
|
|
738
|
+
window.removeEventListener("message", handleMessage);
|
|
739
|
+
dialog.removeEventListener("close", handleClose);
|
|
740
|
+
cleanup();
|
|
741
|
+
resolve();
|
|
742
|
+
};
|
|
743
|
+
window.addEventListener("message", handleMessage);
|
|
744
|
+
dialog.addEventListener("close", handleClose);
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Poll for intent status
|
|
749
|
+
*
|
|
750
|
+
* Use this to check on the status of a submitted intent
|
|
751
|
+
* that hasn't completed yet.
|
|
752
|
+
*/
|
|
753
|
+
async getIntentStatus(intentId) {
|
|
754
|
+
try {
|
|
755
|
+
const response = await fetch(
|
|
756
|
+
`${this.config.providerUrl}/api/intent/status/${intentId}`,
|
|
757
|
+
{
|
|
758
|
+
headers: {
|
|
759
|
+
"x-client-id": this.config.clientId
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
);
|
|
763
|
+
if (!response.ok) {
|
|
764
|
+
const errorData = await response.json().catch(() => ({}));
|
|
765
|
+
return {
|
|
766
|
+
success: false,
|
|
767
|
+
intentId,
|
|
768
|
+
status: "failed",
|
|
769
|
+
error: {
|
|
770
|
+
code: "STATUS_FAILED",
|
|
771
|
+
message: errorData.error || "Failed to get intent status"
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
const data = await response.json();
|
|
776
|
+
return {
|
|
777
|
+
success: data.status === "completed",
|
|
778
|
+
intentId,
|
|
779
|
+
status: data.status,
|
|
780
|
+
transactionHash: data.transactionHash,
|
|
781
|
+
operationId: data.operationId
|
|
782
|
+
};
|
|
783
|
+
} catch (error) {
|
|
784
|
+
return {
|
|
785
|
+
success: false,
|
|
786
|
+
intentId,
|
|
787
|
+
status: "failed",
|
|
788
|
+
error: {
|
|
789
|
+
code: "NETWORK_ERROR",
|
|
790
|
+
message: error instanceof Error ? error.message : "Network error"
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Send a swap intent through the Rhinestone orchestrator
|
|
797
|
+
*
|
|
798
|
+
* This is a high-level abstraction for token swaps (including cross-chain):
|
|
799
|
+
* 1. Resolves token symbols to addresses
|
|
800
|
+
* 2. Builds the swap intent with tokenRequests (output-first model)
|
|
801
|
+
* 3. The orchestrator's solver network finds the best route
|
|
802
|
+
* 4. Executes via the standard intent flow
|
|
803
|
+
*
|
|
804
|
+
* NOTE: The `amount` parameter specifies the OUTPUT amount (what the user wants to receive),
|
|
805
|
+
* not the input amount. The orchestrator will calculate the required input from sourceAssets.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```typescript
|
|
809
|
+
* // Buy 100 USDC using ETH on Base
|
|
810
|
+
* const result = await client.sendSwap({
|
|
811
|
+
* username: 'alice',
|
|
812
|
+
* targetChain: 8453,
|
|
813
|
+
* fromToken: 'ETH',
|
|
814
|
+
* toToken: 'USDC',
|
|
815
|
+
* amount: '100', // Receive 100 USDC
|
|
816
|
+
* });
|
|
817
|
+
*
|
|
818
|
+
* // Cross-chain: Buy 50 USDC on Base, paying with ETH from any chain
|
|
819
|
+
* const result = await client.sendSwap({
|
|
820
|
+
* username: 'alice',
|
|
821
|
+
* targetChain: 8453, // Base
|
|
822
|
+
* fromToken: 'ETH',
|
|
823
|
+
* toToken: 'USDC',
|
|
824
|
+
* amount: '50', // Receive 50 USDC
|
|
825
|
+
* });
|
|
826
|
+
* ```
|
|
827
|
+
*/
|
|
828
|
+
async sendSwap(options) {
|
|
829
|
+
try {
|
|
830
|
+
getChainById(options.targetChain);
|
|
831
|
+
} catch {
|
|
832
|
+
return {
|
|
833
|
+
success: false,
|
|
834
|
+
intentId: "",
|
|
835
|
+
status: "failed",
|
|
836
|
+
error: {
|
|
837
|
+
code: "INVALID_CHAIN",
|
|
838
|
+
message: `Unsupported chain: ${options.targetChain}`
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const resolveToken = (token, label) => {
|
|
843
|
+
try {
|
|
844
|
+
const address = resolveTokenAddress(token, options.targetChain);
|
|
845
|
+
if (!isTokenAddressSupported(address, options.targetChain)) {
|
|
846
|
+
return {
|
|
847
|
+
error: `Unsupported ${label}: ${token} on chain ${options.targetChain}`
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
return { address };
|
|
851
|
+
} catch (error) {
|
|
852
|
+
return {
|
|
853
|
+
error: error instanceof Error ? error.message : `Unsupported ${label}: ${token} on chain ${options.targetChain}`
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
const fromTokenResult = resolveToken(options.fromToken, "fromToken");
|
|
858
|
+
if (!fromTokenResult.address) {
|
|
859
|
+
return {
|
|
860
|
+
success: false,
|
|
861
|
+
intentId: "",
|
|
862
|
+
status: "failed",
|
|
863
|
+
error: {
|
|
864
|
+
code: "INVALID_TOKEN",
|
|
865
|
+
message: fromTokenResult.error || `Unknown fromToken: ${options.fromToken}`
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
const toTokenResult = resolveToken(options.toToken, "toToken");
|
|
870
|
+
if (!toTokenResult.address) {
|
|
871
|
+
return {
|
|
872
|
+
success: false,
|
|
873
|
+
intentId: "",
|
|
874
|
+
status: "failed",
|
|
875
|
+
error: {
|
|
876
|
+
code: "INVALID_TOKEN",
|
|
877
|
+
message: toTokenResult.error || `Unknown toToken: ${options.toToken}`
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
const fromTokenAddress = fromTokenResult.address;
|
|
882
|
+
const toTokenAddress = toTokenResult.address;
|
|
883
|
+
console.log("[SDK sendSwap] Token resolution:", {
|
|
884
|
+
fromToken: options.fromToken,
|
|
885
|
+
fromTokenAddress,
|
|
886
|
+
toToken: options.toToken,
|
|
887
|
+
toTokenAddress,
|
|
888
|
+
targetChain: options.targetChain
|
|
889
|
+
});
|
|
890
|
+
const formatTokenLabel = (token, fallback) => {
|
|
891
|
+
if (!token.startsWith("0x")) {
|
|
892
|
+
return token.toUpperCase();
|
|
893
|
+
}
|
|
894
|
+
try {
|
|
895
|
+
return getTokenSymbol(token, options.targetChain);
|
|
896
|
+
} catch {
|
|
897
|
+
return fallback;
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
const fromSymbol = formatTokenLabel(
|
|
901
|
+
options.fromToken,
|
|
902
|
+
`${options.fromToken.slice(0, 6)}...${options.fromToken.slice(-4)}`
|
|
903
|
+
);
|
|
904
|
+
const toSymbol = formatTokenLabel(
|
|
905
|
+
options.toToken,
|
|
906
|
+
`${options.toToken.slice(0, 6)}...${options.toToken.slice(-4)}`
|
|
907
|
+
);
|
|
908
|
+
const isFromNativeEth = fromTokenAddress === "0x0000000000000000000000000000000000000000";
|
|
909
|
+
const isToNativeEth = toTokenAddress === "0x0000000000000000000000000000000000000000";
|
|
910
|
+
const KNOWN_DECIMALS = {
|
|
911
|
+
ETH: 18,
|
|
912
|
+
WETH: 18,
|
|
913
|
+
USDC: 6,
|
|
914
|
+
USDT: 6,
|
|
915
|
+
USDT0: 6
|
|
916
|
+
};
|
|
917
|
+
const getDecimals = (symbol, chainId) => {
|
|
918
|
+
const upperSymbol = symbol.toUpperCase();
|
|
919
|
+
try {
|
|
920
|
+
const decimals = (0, import_sdk.getTokenDecimals)(upperSymbol, chainId);
|
|
921
|
+
console.log(`[SDK] getTokenDecimals(${upperSymbol}, ${chainId}) = ${decimals}`);
|
|
922
|
+
return decimals;
|
|
923
|
+
} catch (e) {
|
|
924
|
+
console.warn(`[SDK] getTokenDecimals failed for ${upperSymbol}, using fallback`, e);
|
|
925
|
+
return KNOWN_DECIMALS[upperSymbol] ?? 18;
|
|
926
|
+
}
|
|
927
|
+
};
|
|
928
|
+
const fromDecimals = getDecimals(options.fromToken, options.targetChain);
|
|
929
|
+
const toDecimals = getDecimals(options.toToken, options.targetChain);
|
|
930
|
+
const isBridge = options.fromToken.toUpperCase() === options.toToken.toUpperCase();
|
|
931
|
+
let tokenRequests;
|
|
932
|
+
if (!isToNativeEth) {
|
|
933
|
+
tokenRequests = [{
|
|
934
|
+
token: toTokenAddress,
|
|
935
|
+
amount: (0, import_viem2.parseUnits)(options.amount, toDecimals).toString()
|
|
936
|
+
}];
|
|
937
|
+
}
|
|
938
|
+
console.log("[SDK sendSwap] Building intent:", {
|
|
939
|
+
isBridge,
|
|
940
|
+
isFromNativeEth,
|
|
941
|
+
isToNativeEth,
|
|
942
|
+
fromDecimals,
|
|
943
|
+
toDecimals,
|
|
944
|
+
tokenRequests
|
|
945
|
+
});
|
|
946
|
+
const result = await this.sendIntent({
|
|
947
|
+
username: options.username,
|
|
948
|
+
targetChain: options.targetChain,
|
|
949
|
+
calls: [
|
|
950
|
+
{
|
|
951
|
+
// Minimal call - just signals to orchestrator we want the tokenRequests delivered
|
|
952
|
+
to: toTokenAddress,
|
|
953
|
+
value: "0"
|
|
954
|
+
}
|
|
955
|
+
],
|
|
956
|
+
// Request specific output tokens - this is what actually matters for swaps
|
|
957
|
+
tokenRequests,
|
|
958
|
+
// Constrain orchestrator to use only the fromToken as input
|
|
959
|
+
// This ensures the swap uses the correct source token
|
|
960
|
+
// Pass the symbol (not address) so orchestrator can resolve per-chain
|
|
961
|
+
sourceAssets: options.sourceAssets || [options.fromToken.toUpperCase()],
|
|
962
|
+
closeOn: options.closeOn || "preconfirmed",
|
|
963
|
+
waitForHash: options.waitForHash,
|
|
964
|
+
hashTimeoutMs: options.hashTimeoutMs,
|
|
965
|
+
hashIntervalMs: options.hashIntervalMs
|
|
966
|
+
});
|
|
967
|
+
return {
|
|
968
|
+
...result,
|
|
969
|
+
quote: result.success ? {
|
|
970
|
+
fromToken: fromTokenAddress,
|
|
971
|
+
toToken: toTokenAddress,
|
|
972
|
+
amountIn: options.amount,
|
|
973
|
+
amountOut: "",
|
|
974
|
+
// Filled by orchestrator quote
|
|
975
|
+
rate: ""
|
|
976
|
+
} : void 0
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Sign an arbitrary message with the user's passkey
|
|
981
|
+
*
|
|
982
|
+
* This is for off-chain message signing (e.g., authentication challenges,
|
|
983
|
+
* terms acceptance, login signatures), NOT for transaction signing.
|
|
984
|
+
* The message is displayed to the user and signed with their passkey.
|
|
985
|
+
*
|
|
986
|
+
* @example
|
|
987
|
+
* ```typescript
|
|
988
|
+
* // Sign a login challenge
|
|
989
|
+
* const result = await client.signMessage({
|
|
990
|
+
* username: 'alice',
|
|
991
|
+
* message: `Sign in to MyApp\nTimestamp: ${Date.now()}\nNonce: ${crypto.randomUUID()}`,
|
|
992
|
+
* description: 'Verify your identity to continue',
|
|
993
|
+
* });
|
|
994
|
+
*
|
|
995
|
+
* if (result.success) {
|
|
996
|
+
* // Send signature to your backend for verification
|
|
997
|
+
* await fetch('/api/verify', {
|
|
998
|
+
* method: 'POST',
|
|
999
|
+
* body: JSON.stringify({
|
|
1000
|
+
* signature: result.signature,
|
|
1001
|
+
* message: result.signedMessage,
|
|
1002
|
+
* }),
|
|
1003
|
+
* });
|
|
1004
|
+
* }
|
|
1005
|
+
* ```
|
|
1006
|
+
*/
|
|
1007
|
+
async signMessage(options) {
|
|
1008
|
+
const dialogUrl = this.getDialogUrl();
|
|
1009
|
+
const themeParams = this.getThemeParams(options?.theme);
|
|
1010
|
+
const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
|
|
1011
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
|
|
1012
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1013
|
+
await new Promise((resolve) => {
|
|
1014
|
+
const handleReady = (event) => {
|
|
1015
|
+
if (event.origin !== dialogOrigin) return;
|
|
1016
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
1017
|
+
window.removeEventListener("message", handleReady);
|
|
1018
|
+
iframe.contentWindow?.postMessage({
|
|
1019
|
+
type: "PASSKEY_INIT",
|
|
1020
|
+
mode: "iframe",
|
|
1021
|
+
message: options.message,
|
|
1022
|
+
challenge: options.challenge || options.message,
|
|
1023
|
+
username: options.username,
|
|
1024
|
+
description: options.description,
|
|
1025
|
+
metadata: options.metadata
|
|
1026
|
+
}, dialogOrigin);
|
|
1027
|
+
resolve();
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
window.addEventListener("message", handleReady);
|
|
1031
|
+
});
|
|
1032
|
+
const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
|
|
1033
|
+
cleanup();
|
|
1034
|
+
if (signingResult.success) {
|
|
1035
|
+
return {
|
|
1036
|
+
success: true,
|
|
1037
|
+
signature: signingResult.signature,
|
|
1038
|
+
signedMessage: options.message,
|
|
1039
|
+
signedHash: signingResult.signedHash,
|
|
1040
|
+
passkey: signingResult.passkey
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
success: false,
|
|
1045
|
+
error: signingResult.error
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Sign EIP-712 typed data with the user's passkey
|
|
1050
|
+
*
|
|
1051
|
+
* This method allows signing structured data following the EIP-712 standard.
|
|
1052
|
+
* The typed data is displayed to the user in a human-readable format before signing.
|
|
1053
|
+
*
|
|
1054
|
+
* @example
|
|
1055
|
+
* ```typescript
|
|
1056
|
+
* // Sign an ERC-2612 Permit
|
|
1057
|
+
* const result = await client.signTypedData({
|
|
1058
|
+
* username: 'alice',
|
|
1059
|
+
* domain: {
|
|
1060
|
+
* name: 'Dai Stablecoin',
|
|
1061
|
+
* version: '1',
|
|
1062
|
+
* chainId: 1,
|
|
1063
|
+
* verifyingContract: '0x6B175474E89094C44Da98b954EecdeCB5BE3830F',
|
|
1064
|
+
* },
|
|
1065
|
+
* types: {
|
|
1066
|
+
* Permit: [
|
|
1067
|
+
* { name: 'owner', type: 'address' },
|
|
1068
|
+
* { name: 'spender', type: 'address' },
|
|
1069
|
+
* { name: 'value', type: 'uint256' },
|
|
1070
|
+
* { name: 'nonce', type: 'uint256' },
|
|
1071
|
+
* { name: 'deadline', type: 'uint256' },
|
|
1072
|
+
* ],
|
|
1073
|
+
* },
|
|
1074
|
+
* primaryType: 'Permit',
|
|
1075
|
+
* message: {
|
|
1076
|
+
* owner: '0xabc...',
|
|
1077
|
+
* spender: '0xdef...',
|
|
1078
|
+
* value: 1000000000000000000n,
|
|
1079
|
+
* nonce: 0n,
|
|
1080
|
+
* deadline: 1735689600n,
|
|
1081
|
+
* },
|
|
1082
|
+
* });
|
|
1083
|
+
*
|
|
1084
|
+
* if (result.success) {
|
|
1085
|
+
* console.log('Signed hash:', result.signedHash);
|
|
1086
|
+
* }
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
async signTypedData(options) {
|
|
1090
|
+
const signedHash = (0, import_viem2.hashTypedData)({
|
|
1091
|
+
domain: options.domain,
|
|
1092
|
+
types: options.types,
|
|
1093
|
+
primaryType: options.primaryType,
|
|
1094
|
+
message: options.message
|
|
1095
|
+
});
|
|
1096
|
+
const dialogUrl = this.getDialogUrl();
|
|
1097
|
+
const themeParams = this.getThemeParams(options?.theme);
|
|
1098
|
+
const signingUrl = `${dialogUrl}/dialog/sign?mode=iframe${themeParams ? `&${themeParams}` : ""}`;
|
|
1099
|
+
const { dialog, iframe, cleanup } = this.createModalDialog(signingUrl);
|
|
1100
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1101
|
+
await new Promise((resolve) => {
|
|
1102
|
+
const handleReady = (event) => {
|
|
1103
|
+
if (event.origin !== dialogOrigin) return;
|
|
1104
|
+
if (event.data?.type === "PASSKEY_READY") {
|
|
1105
|
+
window.removeEventListener("message", handleReady);
|
|
1106
|
+
iframe.contentWindow?.postMessage({
|
|
1107
|
+
type: "PASSKEY_INIT",
|
|
1108
|
+
mode: "iframe",
|
|
1109
|
+
signingMode: "typedData",
|
|
1110
|
+
typedData: {
|
|
1111
|
+
domain: options.domain,
|
|
1112
|
+
types: options.types,
|
|
1113
|
+
primaryType: options.primaryType,
|
|
1114
|
+
message: options.message
|
|
1115
|
+
},
|
|
1116
|
+
challenge: signedHash,
|
|
1117
|
+
username: options.username,
|
|
1118
|
+
description: options.description
|
|
1119
|
+
}, dialogOrigin);
|
|
1120
|
+
resolve();
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
window.addEventListener("message", handleReady);
|
|
1124
|
+
});
|
|
1125
|
+
const signingResult = await this.waitForSigningResponse(dialog, iframe, cleanup);
|
|
1126
|
+
cleanup();
|
|
1127
|
+
if (signingResult.success) {
|
|
1128
|
+
return {
|
|
1129
|
+
success: true,
|
|
1130
|
+
signature: signingResult.signature,
|
|
1131
|
+
signedHash,
|
|
1132
|
+
passkey: signingResult.passkey
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
success: false,
|
|
1137
|
+
error: signingResult.error
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
async signWithPopup(options) {
|
|
1141
|
+
const request = await this.createSigningRequest(options, "popup");
|
|
1142
|
+
const dialogUrl = this.getDialogUrl();
|
|
1143
|
+
const signingUrl = `${dialogUrl}/dialog/sign/${request.requestId}?mode=popup`;
|
|
1144
|
+
const popup = this.openPopup(signingUrl);
|
|
1145
|
+
if (!popup) {
|
|
1146
|
+
return {
|
|
1147
|
+
success: false,
|
|
1148
|
+
error: {
|
|
1149
|
+
code: "POPUP_BLOCKED",
|
|
1150
|
+
message: "Popup was blocked by the browser. Please allow popups for this site."
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
return this.waitForPopupResponse(request.requestId, popup);
|
|
1155
|
+
}
|
|
1156
|
+
async signWithRedirect(options, redirectUrl) {
|
|
1157
|
+
const finalRedirectUrl = redirectUrl || this.config.redirectUrl;
|
|
1158
|
+
if (!finalRedirectUrl) {
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
"redirectUrl is required for redirect flow. Pass it to signWithRedirect() or set it in the constructor."
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
const request = await this.createSigningRequest(
|
|
1164
|
+
options,
|
|
1165
|
+
"redirect",
|
|
1166
|
+
finalRedirectUrl
|
|
1167
|
+
);
|
|
1168
|
+
const dialogUrl = this.getDialogUrl();
|
|
1169
|
+
const signingUrl = `${dialogUrl}/dialog/sign/${request.requestId}?mode=redirect&redirectUrl=${encodeURIComponent(finalRedirectUrl)}`;
|
|
1170
|
+
window.location.href = signingUrl;
|
|
1171
|
+
}
|
|
1172
|
+
async signWithEmbed(options, embedOptions) {
|
|
1173
|
+
const request = await this.createSigningRequest(options, "embed");
|
|
1174
|
+
const iframe = this.createEmbed(request.requestId, embedOptions);
|
|
1175
|
+
return this.waitForEmbedResponse(request.requestId, iframe, embedOptions);
|
|
1176
|
+
}
|
|
1177
|
+
createEmbed(requestId, options) {
|
|
1178
|
+
const dialogUrl = this.getDialogUrl();
|
|
1179
|
+
const iframe = document.createElement("iframe");
|
|
1180
|
+
iframe.src = `${dialogUrl}/dialog/sign/${requestId}?mode=iframe`;
|
|
1181
|
+
iframe.style.width = options.width || DEFAULT_EMBED_WIDTH;
|
|
1182
|
+
iframe.style.height = options.height || DEFAULT_EMBED_HEIGHT;
|
|
1183
|
+
iframe.style.border = "none";
|
|
1184
|
+
iframe.style.borderRadius = "12px";
|
|
1185
|
+
iframe.style.boxShadow = "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)";
|
|
1186
|
+
iframe.id = `passkey-embed-${requestId}`;
|
|
1187
|
+
iframe.allow = "publickey-credentials-get *; publickey-credentials-create *";
|
|
1188
|
+
iframe.onload = () => {
|
|
1189
|
+
options.onReady?.();
|
|
1190
|
+
};
|
|
1191
|
+
options.container.appendChild(iframe);
|
|
1192
|
+
return iframe;
|
|
1193
|
+
}
|
|
1194
|
+
waitForEmbedResponse(requestId, iframe, embedOptions) {
|
|
1195
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1196
|
+
return new Promise((resolve) => {
|
|
1197
|
+
const cleanup = () => {
|
|
1198
|
+
window.removeEventListener("message", handleMessage);
|
|
1199
|
+
iframe.remove();
|
|
1200
|
+
embedOptions.onClose?.();
|
|
1201
|
+
};
|
|
1202
|
+
const handleMessage = (event) => {
|
|
1203
|
+
if (event.origin !== dialogOrigin) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const message = event.data;
|
|
1207
|
+
const payload = message?.data;
|
|
1208
|
+
if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
|
|
1209
|
+
cleanup();
|
|
1210
|
+
if (message.success && payload.signature) {
|
|
1211
|
+
resolve({
|
|
1212
|
+
success: true,
|
|
1213
|
+
requestId,
|
|
1214
|
+
signature: payload.signature
|
|
1215
|
+
});
|
|
1216
|
+
} else {
|
|
1217
|
+
resolve({
|
|
1218
|
+
success: false,
|
|
1219
|
+
requestId,
|
|
1220
|
+
error: message.error
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
window.addEventListener("message", handleMessage);
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
removeEmbed(requestId) {
|
|
1229
|
+
const iframe = document.getElementById(`passkey-embed-${requestId}`);
|
|
1230
|
+
if (iframe) {
|
|
1231
|
+
iframe.remove();
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
async handleRedirectCallback() {
|
|
1235
|
+
const params = new URLSearchParams(window.location.search);
|
|
1236
|
+
const requestId = params.get("request_id");
|
|
1237
|
+
const status = params.get("status");
|
|
1238
|
+
const error = params.get("error");
|
|
1239
|
+
const errorMessage = params.get("error_message");
|
|
1240
|
+
if (error) {
|
|
1241
|
+
return {
|
|
1242
|
+
success: false,
|
|
1243
|
+
requestId: requestId || void 0,
|
|
1244
|
+
error: {
|
|
1245
|
+
code: error,
|
|
1246
|
+
message: errorMessage || "Unknown error"
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
if (!requestId) {
|
|
1251
|
+
return {
|
|
1252
|
+
success: false,
|
|
1253
|
+
error: {
|
|
1254
|
+
code: "INVALID_REQUEST",
|
|
1255
|
+
message: "No request_id found in callback URL"
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
if (status !== "completed") {
|
|
1260
|
+
return {
|
|
1261
|
+
success: false,
|
|
1262
|
+
requestId,
|
|
1263
|
+
error: {
|
|
1264
|
+
code: "UNKNOWN",
|
|
1265
|
+
message: `Unexpected status: ${status}`
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
return this.fetchSigningResult(requestId);
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Fetch passkeys for a user from the auth provider
|
|
1273
|
+
*/
|
|
1274
|
+
async getPasskeys(username) {
|
|
1275
|
+
const response = await fetch(
|
|
1276
|
+
`${this.config.providerUrl}/api/users/${encodeURIComponent(username)}/passkeys`,
|
|
1277
|
+
{
|
|
1278
|
+
headers: {
|
|
1279
|
+
"x-client-id": this.config.clientId
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
);
|
|
1283
|
+
if (!response.ok) {
|
|
1284
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1285
|
+
throw new Error(errorData.error || errorData.message || "Failed to fetch passkeys");
|
|
1286
|
+
}
|
|
1287
|
+
const data = await response.json();
|
|
1288
|
+
return data.passkeys;
|
|
1289
|
+
}
|
|
1290
|
+
async createSigningRequest(options, mode, redirectUrl) {
|
|
1291
|
+
const response = await fetch(
|
|
1292
|
+
`${this.config.providerUrl}/api/sign/request`,
|
|
1293
|
+
{
|
|
1294
|
+
method: "POST",
|
|
1295
|
+
headers: {
|
|
1296
|
+
"Content-Type": "application/json"
|
|
1297
|
+
},
|
|
1298
|
+
body: JSON.stringify({
|
|
1299
|
+
clientId: this.config.clientId,
|
|
1300
|
+
username: options.username,
|
|
1301
|
+
challenge: options.challenge,
|
|
1302
|
+
description: options.description,
|
|
1303
|
+
metadata: options.metadata,
|
|
1304
|
+
transaction: options.transaction,
|
|
1305
|
+
mode,
|
|
1306
|
+
redirectUrl
|
|
1307
|
+
})
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
if (!response.ok) {
|
|
1311
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1312
|
+
throw new Error(errorData.error || errorData.message || "Failed to create signing request");
|
|
1313
|
+
}
|
|
1314
|
+
return response.json();
|
|
1315
|
+
}
|
|
1316
|
+
openPopup(url) {
|
|
1317
|
+
const left = window.screenX + (window.outerWidth - POPUP_WIDTH) / 2;
|
|
1318
|
+
const top = window.screenY + 50;
|
|
1319
|
+
return window.open(
|
|
1320
|
+
url,
|
|
1321
|
+
"passkey-signing",
|
|
1322
|
+
`width=${POPUP_WIDTH},height=${POPUP_HEIGHT},left=${left},top=${top},popup=true`
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Create a modal dialog with an iframe inside (Porto-style overlay)
|
|
1327
|
+
*/
|
|
1328
|
+
createModalDialog(url) {
|
|
1329
|
+
const dialogUrl = this.getDialogUrl();
|
|
1330
|
+
const hostUrl = new URL(dialogUrl);
|
|
1331
|
+
const dialog = document.createElement("dialog");
|
|
1332
|
+
dialog.dataset.passkey = "";
|
|
1333
|
+
document.body.appendChild(dialog);
|
|
1334
|
+
const style = document.createElement("style");
|
|
1335
|
+
style.textContent = `
|
|
1336
|
+
dialog[data-passkey] {
|
|
1337
|
+
padding: 0;
|
|
1338
|
+
border: none;
|
|
1339
|
+
background: transparent;
|
|
1340
|
+
max-width: none;
|
|
1341
|
+
max-height: none;
|
|
1342
|
+
margin: 0;
|
|
1343
|
+
position: fixed;
|
|
1344
|
+
top: 50px;
|
|
1345
|
+
left: 50%;
|
|
1346
|
+
transform: translateX(-50%);
|
|
1347
|
+
outline: none;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
dialog[data-passkey]::backdrop {
|
|
1351
|
+
background-color: rgba(0, 0, 0, 0.4);
|
|
1352
|
+
backdrop-filter: blur(8px);
|
|
1353
|
+
-webkit-backdrop-filter: blur(8px);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
dialog[data-passkey] iframe {
|
|
1357
|
+
background-color: transparent;
|
|
1358
|
+
border-radius: 14px;
|
|
1359
|
+
border: none;
|
|
1360
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
1361
|
+
transition: width 0.2s ease-out, height 0.15s ease-out;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
@media (min-width: 769px) {
|
|
1365
|
+
dialog[data-passkey] iframe {
|
|
1366
|
+
animation: passkey_zoomIn 0.2s cubic-bezier(0.32, 0.72, 0, 1);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
@media (max-width: 768px) {
|
|
1371
|
+
dialog[data-passkey] {
|
|
1372
|
+
width: 100vw !important;
|
|
1373
|
+
height: auto !important;
|
|
1374
|
+
max-height: 90vh !important;
|
|
1375
|
+
max-height: 90dvh !important;
|
|
1376
|
+
top: auto !important;
|
|
1377
|
+
bottom: 0 !important;
|
|
1378
|
+
left: 0 !important;
|
|
1379
|
+
right: 0 !important;
|
|
1380
|
+
transform: none !important;
|
|
1381
|
+
margin: 0 !important;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
dialog[data-passkey] iframe {
|
|
1385
|
+
animation: passkey_slideFromBottom 0.3s cubic-bezier(0.32, 0.72, 0, 1);
|
|
1386
|
+
border-bottom-left-radius: 0 !important;
|
|
1387
|
+
border-bottom-right-radius: 0 !important;
|
|
1388
|
+
width: 100% !important;
|
|
1389
|
+
max-height: 90vh !important;
|
|
1390
|
+
max-height: 90dvh !important;
|
|
1391
|
+
box-shadow: 0 -4px 32px rgba(0, 0, 0, 0.15) !important;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
@keyframes passkey_zoomIn {
|
|
1396
|
+
from {
|
|
1397
|
+
opacity: 0;
|
|
1398
|
+
transform: scale(0.96) translateY(8px);
|
|
1399
|
+
}
|
|
1400
|
+
to {
|
|
1401
|
+
opacity: 1;
|
|
1402
|
+
transform: scale(1) translateY(0);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
@keyframes passkey_slideFromBottom {
|
|
1407
|
+
from { transform: translate3d(0, 100%, 0); }
|
|
1408
|
+
to { transform: translate3d(0, 0, 0); }
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
@keyframes passkey_shake {
|
|
1412
|
+
0%, 100% { transform: translateX(0); }
|
|
1413
|
+
20% { transform: translateX(-4px); }
|
|
1414
|
+
40% { transform: translateX(4px); }
|
|
1415
|
+
60% { transform: translateX(-4px); }
|
|
1416
|
+
80% { transform: translateX(4px); }
|
|
1417
|
+
}
|
|
1418
|
+
`;
|
|
1419
|
+
dialog.appendChild(style);
|
|
1420
|
+
const iframe = document.createElement("iframe");
|
|
1421
|
+
iframe.setAttribute(
|
|
1422
|
+
"allow",
|
|
1423
|
+
"publickey-credentials-get *; publickey-credentials-create *; clipboard-write"
|
|
1424
|
+
);
|
|
1425
|
+
iframe.setAttribute("aria-label", "Passkey Authentication");
|
|
1426
|
+
iframe.setAttribute("tabindex", "0");
|
|
1427
|
+
iframe.setAttribute(
|
|
1428
|
+
"sandbox",
|
|
1429
|
+
"allow-forms allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
|
|
1430
|
+
);
|
|
1431
|
+
iframe.setAttribute("src", url);
|
|
1432
|
+
iframe.setAttribute("title", "Passkey");
|
|
1433
|
+
iframe.style.border = "none";
|
|
1434
|
+
iframe.style.height = "400px";
|
|
1435
|
+
iframe.style.width = `${MODAL_WIDTH}px`;
|
|
1436
|
+
dialog.appendChild(iframe);
|
|
1437
|
+
const handleMessage = (event) => {
|
|
1438
|
+
if (event.origin !== hostUrl.origin) return;
|
|
1439
|
+
if (event.data?.type === "PASSKEY_RESIZE") {
|
|
1440
|
+
iframe.style.height = `${event.data.height}px`;
|
|
1441
|
+
if (event.data.width) {
|
|
1442
|
+
iframe.style.width = `${event.data.width}px`;
|
|
1443
|
+
}
|
|
1444
|
+
} else if (event.data?.type === "PASSKEY_DISCONNECT") {
|
|
1445
|
+
localStorage.removeItem("1auth-user");
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
window.addEventListener("message", handleMessage);
|
|
1449
|
+
const handleEscape = (event) => {
|
|
1450
|
+
if (event.key === "Escape") {
|
|
1451
|
+
cleanup();
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
document.addEventListener("keydown", handleEscape);
|
|
1455
|
+
dialog.addEventListener("click", (event) => {
|
|
1456
|
+
if (event.target === dialog) {
|
|
1457
|
+
cleanup();
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
dialog.showModal();
|
|
1461
|
+
const cleanup = () => {
|
|
1462
|
+
window.removeEventListener("message", handleMessage);
|
|
1463
|
+
document.removeEventListener("keydown", handleEscape);
|
|
1464
|
+
dialog.close();
|
|
1465
|
+
dialog.remove();
|
|
1466
|
+
};
|
|
1467
|
+
return { dialog, iframe, cleanup };
|
|
1468
|
+
}
|
|
1469
|
+
waitForModalAuthResponse(_dialog, _iframe, cleanup) {
|
|
1470
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1471
|
+
return new Promise((resolve) => {
|
|
1472
|
+
const handleMessage = (event) => {
|
|
1473
|
+
if (event.origin !== dialogOrigin) return;
|
|
1474
|
+
const data = event.data;
|
|
1475
|
+
if (data?.type === "PASSKEY_LOGIN_RESULT") {
|
|
1476
|
+
window.removeEventListener("message", handleMessage);
|
|
1477
|
+
cleanup();
|
|
1478
|
+
if (data.success) {
|
|
1479
|
+
resolve({
|
|
1480
|
+
success: true,
|
|
1481
|
+
username: data.data?.username,
|
|
1482
|
+
user: data.data?.user
|
|
1483
|
+
});
|
|
1484
|
+
} else {
|
|
1485
|
+
resolve({
|
|
1486
|
+
success: false,
|
|
1487
|
+
error: data.error
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
} else if (data?.type === "PASSKEY_REGISTER_RESULT") {
|
|
1491
|
+
window.removeEventListener("message", handleMessage);
|
|
1492
|
+
cleanup();
|
|
1493
|
+
if (data.success) {
|
|
1494
|
+
resolve({
|
|
1495
|
+
success: true,
|
|
1496
|
+
username: data.data?.username
|
|
1497
|
+
});
|
|
1498
|
+
} else {
|
|
1499
|
+
resolve({
|
|
1500
|
+
success: false,
|
|
1501
|
+
error: data.error
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
} else if (data?.type === "PASSKEY_CLOSE") {
|
|
1505
|
+
window.removeEventListener("message", handleMessage);
|
|
1506
|
+
cleanup();
|
|
1507
|
+
resolve({
|
|
1508
|
+
success: false,
|
|
1509
|
+
error: {
|
|
1510
|
+
code: "USER_CANCELLED",
|
|
1511
|
+
message: "Authentication was cancelled"
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
window.addEventListener("message", handleMessage);
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
waitForAuthenticateResponse(_dialog, _iframe, cleanup) {
|
|
1520
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1521
|
+
return new Promise((resolve) => {
|
|
1522
|
+
const handleMessage = (event) => {
|
|
1523
|
+
if (event.origin !== dialogOrigin) return;
|
|
1524
|
+
const data = event.data;
|
|
1525
|
+
if (data?.type === "PASSKEY_AUTHENTICATE_RESULT") {
|
|
1526
|
+
window.removeEventListener("message", handleMessage);
|
|
1527
|
+
cleanup();
|
|
1528
|
+
if (data.success) {
|
|
1529
|
+
resolve({
|
|
1530
|
+
success: true,
|
|
1531
|
+
username: data.data?.username,
|
|
1532
|
+
user: data.data?.user,
|
|
1533
|
+
accountAddress: data.data?.accountAddress,
|
|
1534
|
+
signature: data.data?.signature,
|
|
1535
|
+
signedHash: data.data?.signedHash
|
|
1536
|
+
});
|
|
1537
|
+
} else {
|
|
1538
|
+
resolve({
|
|
1539
|
+
success: false,
|
|
1540
|
+
error: data.error
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1543
|
+
} else if (data?.type === "PASSKEY_CLOSE") {
|
|
1544
|
+
window.removeEventListener("message", handleMessage);
|
|
1545
|
+
cleanup();
|
|
1546
|
+
resolve({
|
|
1547
|
+
success: false,
|
|
1548
|
+
error: {
|
|
1549
|
+
code: "USER_CANCELLED",
|
|
1550
|
+
message: "Authentication was cancelled"
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
window.addEventListener("message", handleMessage);
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
waitForModalSigningResponse(requestId, _dialog, _iframe, cleanup) {
|
|
1559
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1560
|
+
return new Promise((resolve) => {
|
|
1561
|
+
const handleMessage = (event) => {
|
|
1562
|
+
if (event.origin !== dialogOrigin) return;
|
|
1563
|
+
const message = event.data;
|
|
1564
|
+
const payload = message?.data;
|
|
1565
|
+
if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
|
|
1566
|
+
window.removeEventListener("message", handleMessage);
|
|
1567
|
+
cleanup();
|
|
1568
|
+
if (message.success && payload.signature) {
|
|
1569
|
+
resolve({
|
|
1570
|
+
success: true,
|
|
1571
|
+
requestId,
|
|
1572
|
+
signature: payload.signature
|
|
1573
|
+
});
|
|
1574
|
+
} else {
|
|
1575
|
+
resolve({
|
|
1576
|
+
success: false,
|
|
1577
|
+
requestId,
|
|
1578
|
+
error: message.error
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
} else if (message?.type === "PASSKEY_CLOSE") {
|
|
1582
|
+
window.removeEventListener("message", handleMessage);
|
|
1583
|
+
cleanup();
|
|
1584
|
+
resolve({
|
|
1585
|
+
success: false,
|
|
1586
|
+
requestId,
|
|
1587
|
+
error: {
|
|
1588
|
+
code: "USER_REJECTED",
|
|
1589
|
+
message: "Signing was cancelled"
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
window.addEventListener("message", handleMessage);
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
waitForPopupResponse(requestId, popup) {
|
|
1598
|
+
const dialogOrigin = this.getDialogOrigin();
|
|
1599
|
+
return new Promise((resolve) => {
|
|
1600
|
+
const checkClosed = setInterval(() => {
|
|
1601
|
+
if (popup.closed) {
|
|
1602
|
+
clearInterval(checkClosed);
|
|
1603
|
+
window.removeEventListener("message", handleMessage);
|
|
1604
|
+
resolve({
|
|
1605
|
+
success: false,
|
|
1606
|
+
requestId,
|
|
1607
|
+
error: {
|
|
1608
|
+
code: "USER_REJECTED",
|
|
1609
|
+
message: "Popup was closed without completing"
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
}, 500);
|
|
1614
|
+
const handleMessage = (event) => {
|
|
1615
|
+
if (event.origin !== dialogOrigin) {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
const message = event.data;
|
|
1619
|
+
const payload = message?.data;
|
|
1620
|
+
if (message?.type === "PASSKEY_SIGNING_RESULT" && payload?.requestId === requestId) {
|
|
1621
|
+
clearInterval(checkClosed);
|
|
1622
|
+
window.removeEventListener("message", handleMessage);
|
|
1623
|
+
popup.close();
|
|
1624
|
+
if (message.success && payload.signature) {
|
|
1625
|
+
resolve({
|
|
1626
|
+
success: true,
|
|
1627
|
+
requestId,
|
|
1628
|
+
signature: payload.signature
|
|
1629
|
+
});
|
|
1630
|
+
} else {
|
|
1631
|
+
resolve({
|
|
1632
|
+
success: false,
|
|
1633
|
+
requestId,
|
|
1634
|
+
error: message.error
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
window.addEventListener("message", handleMessage);
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
async fetchSigningResult(requestId) {
|
|
1643
|
+
const response = await fetch(
|
|
1644
|
+
`${this.config.providerUrl}/api/sign/request/${requestId}`,
|
|
1645
|
+
{
|
|
1646
|
+
headers: {
|
|
1647
|
+
"x-client-id": this.config.clientId
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
);
|
|
1651
|
+
if (!response.ok) {
|
|
1652
|
+
return {
|
|
1653
|
+
success: false,
|
|
1654
|
+
requestId,
|
|
1655
|
+
error: {
|
|
1656
|
+
code: "NETWORK_ERROR",
|
|
1657
|
+
message: "Failed to fetch signing result"
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
const data = await response.json();
|
|
1662
|
+
if (data.status === "COMPLETED" && data.signature) {
|
|
1663
|
+
return {
|
|
1664
|
+
success: true,
|
|
1665
|
+
requestId,
|
|
1666
|
+
signature: data.signature
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
const errorCode = data.error?.code || "UNKNOWN";
|
|
1670
|
+
return {
|
|
1671
|
+
success: false,
|
|
1672
|
+
requestId,
|
|
1673
|
+
error: {
|
|
1674
|
+
code: errorCode,
|
|
1675
|
+
message: data.error?.message || `Request status: ${data.status}`
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// src/provider.ts
|
|
1682
|
+
var import_viem4 = require("viem");
|
|
1683
|
+
|
|
1684
|
+
// src/walletClient/utils.ts
|
|
1685
|
+
var import_viem3 = require("viem");
|
|
1686
|
+
var P256_N = BigInt(
|
|
1687
|
+
"0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551"
|
|
1688
|
+
);
|
|
1689
|
+
var P256_N_DIV_2 = P256_N / 2n;
|
|
1690
|
+
var WEBAUTHN_AUTH_TYPE = {
|
|
1691
|
+
type: "tuple",
|
|
1692
|
+
components: [
|
|
1693
|
+
{ type: "bytes", name: "authenticatorData" },
|
|
1694
|
+
{ type: "string", name: "clientDataJSON" },
|
|
1695
|
+
{ type: "uint256", name: "challengeIndex" },
|
|
1696
|
+
{ type: "uint256", name: "typeIndex" },
|
|
1697
|
+
{ type: "uint256", name: "r" },
|
|
1698
|
+
{ type: "uint256", name: "s" }
|
|
1699
|
+
]
|
|
1700
|
+
};
|
|
1701
|
+
function encodeWebAuthnSignature(sig) {
|
|
1702
|
+
let s = BigInt(sig.s);
|
|
1703
|
+
if (s > P256_N_DIV_2) {
|
|
1704
|
+
s = P256_N - s;
|
|
1705
|
+
}
|
|
1706
|
+
return (0, import_viem3.encodeAbiParameters)([WEBAUTHN_AUTH_TYPE], [
|
|
1707
|
+
{
|
|
1708
|
+
authenticatorData: sig.authenticatorData,
|
|
1709
|
+
clientDataJSON: sig.clientDataJSON,
|
|
1710
|
+
challengeIndex: BigInt(sig.challengeIndex),
|
|
1711
|
+
typeIndex: BigInt(sig.typeIndex),
|
|
1712
|
+
r: BigInt(sig.r),
|
|
1713
|
+
s
|
|
1714
|
+
}
|
|
1715
|
+
]);
|
|
1716
|
+
}
|
|
1717
|
+
function hashCalls(calls) {
|
|
1718
|
+
const encoded = (0, import_viem3.encodeAbiParameters)(
|
|
1719
|
+
[
|
|
1720
|
+
{
|
|
1721
|
+
type: "tuple[]",
|
|
1722
|
+
components: [
|
|
1723
|
+
{ type: "address", name: "to" },
|
|
1724
|
+
{ type: "bytes", name: "data" },
|
|
1725
|
+
{ type: "uint256", name: "value" }
|
|
1726
|
+
]
|
|
1727
|
+
}
|
|
1728
|
+
],
|
|
1729
|
+
[
|
|
1730
|
+
calls.map((c) => ({
|
|
1731
|
+
to: c.to,
|
|
1732
|
+
data: c.data || "0x",
|
|
1733
|
+
value: c.value || 0n
|
|
1734
|
+
}))
|
|
1735
|
+
]
|
|
1736
|
+
);
|
|
1737
|
+
return (0, import_viem3.keccak256)(encoded);
|
|
1738
|
+
}
|
|
1739
|
+
function buildTransactionReview(calls) {
|
|
1740
|
+
return {
|
|
1741
|
+
actions: calls.map((call, i) => ({
|
|
1742
|
+
type: "custom",
|
|
1743
|
+
label: call.label || `Contract Call ${i + 1}`,
|
|
1744
|
+
sublabel: call.sublabel || `To: ${call.to.slice(0, 10)}...${call.to.slice(-8)}`,
|
|
1745
|
+
amount: call.value ? `${call.value} wei` : void 0
|
|
1746
|
+
}))
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// src/provider.ts
|
|
1751
|
+
var DEFAULT_STORAGE_KEY = "1auth-user";
|
|
1752
|
+
function createPasskeyProvider(options) {
|
|
1753
|
+
const { client } = options;
|
|
1754
|
+
let chainId = options.chainId;
|
|
1755
|
+
const storageKey = options.storageKey || DEFAULT_STORAGE_KEY;
|
|
1756
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1757
|
+
const emit = (event, ...args) => {
|
|
1758
|
+
const set = listeners.get(event);
|
|
1759
|
+
if (!set) return;
|
|
1760
|
+
for (const listener of set) listener(...args);
|
|
1761
|
+
};
|
|
1762
|
+
const getStoredUser = () => {
|
|
1763
|
+
if (typeof window === "undefined") return null;
|
|
1764
|
+
try {
|
|
1765
|
+
const raw = localStorage.getItem(storageKey);
|
|
1766
|
+
if (!raw) return null;
|
|
1767
|
+
const parsed = JSON.parse(raw);
|
|
1768
|
+
if (!parsed?.username || !parsed?.address) return null;
|
|
1769
|
+
return parsed;
|
|
1770
|
+
} catch {
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
const setStoredUser = (user) => {
|
|
1775
|
+
if (typeof window === "undefined") return;
|
|
1776
|
+
localStorage.setItem(storageKey, JSON.stringify(user));
|
|
1777
|
+
};
|
|
1778
|
+
const clearStoredUser = () => {
|
|
1779
|
+
if (typeof window === "undefined") return;
|
|
1780
|
+
localStorage.removeItem(storageKey);
|
|
1781
|
+
};
|
|
1782
|
+
const resolveAccountAddress = async (username) => {
|
|
1783
|
+
const response = await fetch(
|
|
1784
|
+
`${client.getProviderUrl()}/api/users/${encodeURIComponent(username)}/account`,
|
|
1785
|
+
{
|
|
1786
|
+
headers: {
|
|
1787
|
+
"x-client-id": client.getClientId()
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
);
|
|
1791
|
+
if (!response.ok) {
|
|
1792
|
+
const data2 = await response.json().catch(() => ({}));
|
|
1793
|
+
throw new Error(data2.error || "Failed to resolve account address");
|
|
1794
|
+
}
|
|
1795
|
+
const data = await response.json();
|
|
1796
|
+
return data.address;
|
|
1797
|
+
};
|
|
1798
|
+
const connect = async () => {
|
|
1799
|
+
const stored = getStoredUser();
|
|
1800
|
+
if (stored) {
|
|
1801
|
+
return [stored.address];
|
|
1802
|
+
}
|
|
1803
|
+
const result = await client.authWithModal();
|
|
1804
|
+
if (!result.success || !result.username) {
|
|
1805
|
+
throw new Error(result.error?.message || "Authentication failed");
|
|
1806
|
+
}
|
|
1807
|
+
const address = await resolveAccountAddress(result.username);
|
|
1808
|
+
setStoredUser({ username: result.username, address });
|
|
1809
|
+
emit("accountsChanged", [address]);
|
|
1810
|
+
emit("connect", { chainId: (0, import_viem4.numberToHex)(chainId) });
|
|
1811
|
+
return [address];
|
|
1812
|
+
};
|
|
1813
|
+
const disconnect = async () => {
|
|
1814
|
+
clearStoredUser();
|
|
1815
|
+
emit("accountsChanged", []);
|
|
1816
|
+
emit("disconnect");
|
|
1817
|
+
};
|
|
1818
|
+
const ensureUser = async () => {
|
|
1819
|
+
const stored = getStoredUser();
|
|
1820
|
+
if (stored) return stored;
|
|
1821
|
+
const [address] = await connect();
|
|
1822
|
+
const username = getStoredUser()?.username;
|
|
1823
|
+
if (!username || !address) {
|
|
1824
|
+
throw new Error("Failed to resolve user session");
|
|
1825
|
+
}
|
|
1826
|
+
return { username, address };
|
|
1827
|
+
};
|
|
1828
|
+
const parseChainId = (value) => {
|
|
1829
|
+
if (typeof value === "number") return value;
|
|
1830
|
+
if (typeof value === "string") {
|
|
1831
|
+
if (value.startsWith("0x")) return Number.parseInt(value, 16);
|
|
1832
|
+
const parsed = Number(value);
|
|
1833
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
1834
|
+
}
|
|
1835
|
+
return void 0;
|
|
1836
|
+
};
|
|
1837
|
+
const normalizeValue = (value) => {
|
|
1838
|
+
if (value === void 0 || value === null) return void 0;
|
|
1839
|
+
if (typeof value === "bigint") return value.toString();
|
|
1840
|
+
if (typeof value === "number") return Math.trunc(value).toString();
|
|
1841
|
+
if (typeof value === "string") {
|
|
1842
|
+
if (value.startsWith("0x")) {
|
|
1843
|
+
try {
|
|
1844
|
+
return BigInt(value).toString();
|
|
1845
|
+
} catch {
|
|
1846
|
+
return "0";
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return value;
|
|
1850
|
+
}
|
|
1851
|
+
return void 0;
|
|
1852
|
+
};
|
|
1853
|
+
const normalizeCalls = (calls) => {
|
|
1854
|
+
return calls.map((call) => {
|
|
1855
|
+
const c = call;
|
|
1856
|
+
return {
|
|
1857
|
+
to: c.to,
|
|
1858
|
+
data: c.data || "0x",
|
|
1859
|
+
value: normalizeValue(c.value) || "0",
|
|
1860
|
+
label: c.label,
|
|
1861
|
+
sublabel: c.sublabel
|
|
1862
|
+
};
|
|
1863
|
+
});
|
|
1864
|
+
};
|
|
1865
|
+
const decodeMessage = (value) => {
|
|
1866
|
+
if (!(0, import_viem4.isHex)(value)) return value;
|
|
1867
|
+
try {
|
|
1868
|
+
return (0, import_viem4.hexToString)(value);
|
|
1869
|
+
} catch {
|
|
1870
|
+
return value;
|
|
1871
|
+
}
|
|
1872
|
+
};
|
|
1873
|
+
const signMessage = async (message) => {
|
|
1874
|
+
const { username } = await ensureUser();
|
|
1875
|
+
const result = await client.signMessage({
|
|
1876
|
+
username,
|
|
1877
|
+
message
|
|
1878
|
+
});
|
|
1879
|
+
if (!result.success || !result.signature) {
|
|
1880
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
1881
|
+
}
|
|
1882
|
+
return encodeWebAuthnSignature(result.signature);
|
|
1883
|
+
};
|
|
1884
|
+
const signTypedData = async (typedData) => {
|
|
1885
|
+
const { username } = await ensureUser();
|
|
1886
|
+
const data = typeof typedData === "string" ? JSON.parse(typedData) : typedData;
|
|
1887
|
+
const result = await client.signTypedData({
|
|
1888
|
+
username,
|
|
1889
|
+
domain: data.domain,
|
|
1890
|
+
types: data.types,
|
|
1891
|
+
primaryType: data.primaryType,
|
|
1892
|
+
message: data.message
|
|
1893
|
+
});
|
|
1894
|
+
if (!result.success || !result.signature) {
|
|
1895
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
1896
|
+
}
|
|
1897
|
+
return encodeWebAuthnSignature(result.signature);
|
|
1898
|
+
};
|
|
1899
|
+
const sendIntent = async (payload) => {
|
|
1900
|
+
const closeOn = options.waitForHash ?? true ? "completed" : "preconfirmed";
|
|
1901
|
+
const result = await client.sendIntent({
|
|
1902
|
+
...payload,
|
|
1903
|
+
closeOn,
|
|
1904
|
+
waitForHash: options.waitForHash ?? true,
|
|
1905
|
+
hashTimeoutMs: options.hashTimeoutMs,
|
|
1906
|
+
hashIntervalMs: options.hashIntervalMs
|
|
1907
|
+
});
|
|
1908
|
+
if (!result.success || !result.transactionHash) {
|
|
1909
|
+
throw new Error(result.error?.message || "Transaction failed");
|
|
1910
|
+
}
|
|
1911
|
+
return result.transactionHash;
|
|
1912
|
+
};
|
|
1913
|
+
const request = async ({ method, params }) => {
|
|
1914
|
+
switch (method) {
|
|
1915
|
+
case "eth_chainId":
|
|
1916
|
+
return (0, import_viem4.numberToHex)(chainId);
|
|
1917
|
+
case "eth_accounts": {
|
|
1918
|
+
const stored = getStoredUser();
|
|
1919
|
+
return stored ? [stored.address] : [];
|
|
1920
|
+
}
|
|
1921
|
+
case "eth_requestAccounts":
|
|
1922
|
+
return connect();
|
|
1923
|
+
case "wallet_connect":
|
|
1924
|
+
return connect();
|
|
1925
|
+
case "wallet_disconnect":
|
|
1926
|
+
await disconnect();
|
|
1927
|
+
return true;
|
|
1928
|
+
case "wallet_switchEthereumChain": {
|
|
1929
|
+
const [param] = params || [];
|
|
1930
|
+
const next = parseChainId(param?.chainId ?? param);
|
|
1931
|
+
if (!next) {
|
|
1932
|
+
throw new Error("Invalid chainId");
|
|
1933
|
+
}
|
|
1934
|
+
chainId = next;
|
|
1935
|
+
emit("chainChanged", (0, import_viem4.numberToHex)(chainId));
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
case "personal_sign": {
|
|
1939
|
+
const paramList = Array.isArray(params) ? params : [];
|
|
1940
|
+
const first = paramList[0];
|
|
1941
|
+
const second = paramList[1];
|
|
1942
|
+
const message = typeof first === "string" && first.startsWith("0x") && second ? typeof second === "string" && !second.startsWith("0x") ? second : decodeMessage(first) : typeof first === "string" ? decodeMessage(first) : typeof second === "string" ? decodeMessage(second) : "";
|
|
1943
|
+
if (!message) throw new Error("Invalid personal_sign payload");
|
|
1944
|
+
return signMessage(message);
|
|
1945
|
+
}
|
|
1946
|
+
case "eth_sign": {
|
|
1947
|
+
const paramList = Array.isArray(params) ? params : [];
|
|
1948
|
+
const message = typeof paramList[1] === "string" ? paramList[1] : "";
|
|
1949
|
+
if (!message) throw new Error("Invalid eth_sign payload");
|
|
1950
|
+
return signMessage(decodeMessage(message));
|
|
1951
|
+
}
|
|
1952
|
+
case "eth_signTypedData":
|
|
1953
|
+
case "eth_signTypedData_v4": {
|
|
1954
|
+
const paramList = Array.isArray(params) ? params : [];
|
|
1955
|
+
const typedData = paramList[1] ?? paramList[0];
|
|
1956
|
+
return signTypedData(typedData);
|
|
1957
|
+
}
|
|
1958
|
+
case "eth_sendTransaction": {
|
|
1959
|
+
const paramList = Array.isArray(params) ? params : [];
|
|
1960
|
+
const tx = paramList[0] || {};
|
|
1961
|
+
const user = await ensureUser();
|
|
1962
|
+
const targetChain = parseChainId(tx.chainId) ?? chainId;
|
|
1963
|
+
const calls = normalizeCalls([tx]);
|
|
1964
|
+
return sendIntent({
|
|
1965
|
+
username: user.username,
|
|
1966
|
+
targetChain,
|
|
1967
|
+
calls
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
case "wallet_sendCalls": {
|
|
1971
|
+
const paramList = Array.isArray(params) ? params : [];
|
|
1972
|
+
const payload = paramList[0] || {};
|
|
1973
|
+
const user = await ensureUser();
|
|
1974
|
+
const targetChain = parseChainId(payload.chainId) ?? chainId;
|
|
1975
|
+
const calls = normalizeCalls(payload.calls || []);
|
|
1976
|
+
if (!calls.length) throw new Error("No calls provided");
|
|
1977
|
+
return sendIntent({
|
|
1978
|
+
username: user.username,
|
|
1979
|
+
targetChain,
|
|
1980
|
+
calls
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
case "wallet_getCapabilities": {
|
|
1984
|
+
const chainIds = getSupportedChainIds();
|
|
1985
|
+
const tokensByChain = Object.fromEntries(
|
|
1986
|
+
chainIds.map((id) => [id, getSupportedTokens(id)])
|
|
1987
|
+
);
|
|
1988
|
+
return {
|
|
1989
|
+
chains: chainIds,
|
|
1990
|
+
tokens: tokensByChain
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
default:
|
|
1994
|
+
throw new Error(`Unsupported method: ${method}`);
|
|
1995
|
+
}
|
|
1996
|
+
};
|
|
1997
|
+
return {
|
|
1998
|
+
request,
|
|
1999
|
+
on(event, listener) {
|
|
2000
|
+
const set = listeners.get(event) ?? /* @__PURE__ */ new Set();
|
|
2001
|
+
set.add(listener);
|
|
2002
|
+
listeners.set(event, set);
|
|
2003
|
+
},
|
|
2004
|
+
removeListener(event, listener) {
|
|
2005
|
+
const set = listeners.get(event);
|
|
2006
|
+
if (!set) return;
|
|
2007
|
+
set.delete(listener);
|
|
2008
|
+
if (set.size === 0) listeners.delete(event);
|
|
2009
|
+
},
|
|
2010
|
+
disconnect
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// src/account.ts
|
|
2015
|
+
var import_viem5 = require("viem");
|
|
2016
|
+
var import_accounts = require("viem/accounts");
|
|
2017
|
+
function createPasskeyAccount(client, params) {
|
|
2018
|
+
const { address, username } = params;
|
|
2019
|
+
const normalizeMessage = (message) => {
|
|
2020
|
+
if (typeof message === "string") return message;
|
|
2021
|
+
const raw = message.raw;
|
|
2022
|
+
if ((0, import_viem5.isHex)(raw)) {
|
|
2023
|
+
try {
|
|
2024
|
+
return (0, import_viem5.hexToString)(raw);
|
|
2025
|
+
} catch {
|
|
2026
|
+
return raw;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
return (0, import_viem5.bytesToString)(raw);
|
|
2030
|
+
};
|
|
2031
|
+
const account = (0, import_accounts.toAccount)({
|
|
2032
|
+
address,
|
|
2033
|
+
signMessage: async ({ message }) => {
|
|
2034
|
+
const result = await client.signMessage({
|
|
2035
|
+
username,
|
|
2036
|
+
message: normalizeMessage(message)
|
|
2037
|
+
});
|
|
2038
|
+
if (!result.success || !result.signature) {
|
|
2039
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
2040
|
+
}
|
|
2041
|
+
return encodeWebAuthnSignature(result.signature);
|
|
2042
|
+
},
|
|
2043
|
+
signTransaction: async () => {
|
|
2044
|
+
throw new Error("signTransaction not supported; use sendIntent");
|
|
2045
|
+
},
|
|
2046
|
+
signTypedData: async (typedData) => {
|
|
2047
|
+
if (!typedData.domain || !typedData.types || !typedData.primaryType) {
|
|
2048
|
+
throw new Error("Invalid typed data");
|
|
2049
|
+
}
|
|
2050
|
+
const domainInput = typedData.domain;
|
|
2051
|
+
if (!domainInput.name || !domainInput.version) {
|
|
2052
|
+
throw new Error("Typed data domain must include name and version");
|
|
2053
|
+
}
|
|
2054
|
+
const domain = {
|
|
2055
|
+
name: domainInput.name,
|
|
2056
|
+
version: domainInput.version,
|
|
2057
|
+
chainId: typeof domainInput.chainId === "bigint" ? Number(domainInput.chainId) : domainInput.chainId,
|
|
2058
|
+
verifyingContract: domainInput.verifyingContract,
|
|
2059
|
+
salt: domainInput.salt
|
|
2060
|
+
};
|
|
2061
|
+
const rawTypes = typedData.types;
|
|
2062
|
+
const normalizedTypes = Object.fromEntries(
|
|
2063
|
+
Object.entries(rawTypes).map(([key, fields]) => [
|
|
2064
|
+
key,
|
|
2065
|
+
fields.map((field) => ({ name: field.name, type: field.type }))
|
|
2066
|
+
])
|
|
2067
|
+
);
|
|
2068
|
+
const result = await client.signTypedData({
|
|
2069
|
+
username,
|
|
2070
|
+
domain,
|
|
2071
|
+
types: normalizedTypes,
|
|
2072
|
+
primaryType: typedData.primaryType,
|
|
2073
|
+
message: typedData.message
|
|
2074
|
+
});
|
|
2075
|
+
if (!result.success || !result.signature) {
|
|
2076
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
2077
|
+
}
|
|
2078
|
+
return encodeWebAuthnSignature(result.signature);
|
|
2079
|
+
}
|
|
2080
|
+
});
|
|
2081
|
+
return {
|
|
2082
|
+
...account,
|
|
2083
|
+
username
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// src/walletClient/index.ts
|
|
2088
|
+
var import_viem6 = require("viem");
|
|
2089
|
+
var import_accounts2 = require("viem/accounts");
|
|
2090
|
+
function createPasskeyWalletClient(config) {
|
|
2091
|
+
const provider = new PasskeyProviderClient({
|
|
2092
|
+
providerUrl: config.providerUrl,
|
|
2093
|
+
clientId: config.clientId,
|
|
2094
|
+
dialogUrl: config.dialogUrl
|
|
2095
|
+
});
|
|
2096
|
+
const signMessageImpl = async (message) => {
|
|
2097
|
+
const hash = (0, import_viem6.hashMessage)(message);
|
|
2098
|
+
const result = await provider.signWithModal({
|
|
2099
|
+
challenge: hash,
|
|
2100
|
+
username: config.username,
|
|
2101
|
+
description: "Sign message",
|
|
2102
|
+
transaction: {
|
|
2103
|
+
actions: [
|
|
2104
|
+
{
|
|
2105
|
+
type: "custom",
|
|
2106
|
+
label: "Sign Message",
|
|
2107
|
+
sublabel: typeof message === "string" ? message.slice(0, 50) + (message.length > 50 ? "..." : "") : "Raw message"
|
|
2108
|
+
}
|
|
2109
|
+
]
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
if (!result.success) {
|
|
2113
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
2114
|
+
}
|
|
2115
|
+
return encodeWebAuthnSignature(result.signature);
|
|
2116
|
+
};
|
|
2117
|
+
const signTransactionImpl = async (transaction) => {
|
|
2118
|
+
const calls = [
|
|
2119
|
+
{
|
|
2120
|
+
to: transaction.to,
|
|
2121
|
+
data: transaction.data,
|
|
2122
|
+
value: transaction.value
|
|
2123
|
+
}
|
|
2124
|
+
];
|
|
2125
|
+
const hash = hashCalls(calls);
|
|
2126
|
+
const result = await provider.signWithModal({
|
|
2127
|
+
challenge: hash,
|
|
2128
|
+
username: config.username,
|
|
2129
|
+
description: "Sign transaction",
|
|
2130
|
+
transaction: buildTransactionReview(calls)
|
|
2131
|
+
});
|
|
2132
|
+
if (!result.success) {
|
|
2133
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
2134
|
+
}
|
|
2135
|
+
return encodeWebAuthnSignature(result.signature);
|
|
2136
|
+
};
|
|
2137
|
+
const signTypedDataImpl = async (typedData) => {
|
|
2138
|
+
const hash = (0, import_viem6.hashTypedData)(typedData);
|
|
2139
|
+
const result = await provider.signWithModal({
|
|
2140
|
+
challenge: hash,
|
|
2141
|
+
username: config.username,
|
|
2142
|
+
description: "Sign typed data",
|
|
2143
|
+
transaction: {
|
|
2144
|
+
actions: [
|
|
2145
|
+
{
|
|
2146
|
+
type: "custom",
|
|
2147
|
+
label: "Sign Data",
|
|
2148
|
+
sublabel: typedData.primaryType || "Typed Data"
|
|
2149
|
+
}
|
|
2150
|
+
]
|
|
2151
|
+
}
|
|
2152
|
+
});
|
|
2153
|
+
if (!result.success) {
|
|
2154
|
+
throw new Error(result.error?.message || "Signing failed");
|
|
2155
|
+
}
|
|
2156
|
+
return encodeWebAuthnSignature(result.signature);
|
|
2157
|
+
};
|
|
2158
|
+
const account = (0, import_accounts2.toAccount)({
|
|
2159
|
+
address: config.accountAddress,
|
|
2160
|
+
signMessage: ({ message }) => signMessageImpl(message),
|
|
2161
|
+
signTransaction: signTransactionImpl,
|
|
2162
|
+
signTypedData: signTypedDataImpl
|
|
2163
|
+
});
|
|
2164
|
+
const client = (0, import_viem6.createWalletClient)({
|
|
2165
|
+
account,
|
|
2166
|
+
chain: config.chain,
|
|
2167
|
+
transport: config.transport
|
|
2168
|
+
});
|
|
2169
|
+
const extendedClient = Object.assign(client, {
|
|
2170
|
+
/**
|
|
2171
|
+
* Send a single transaction via intent flow
|
|
2172
|
+
*/
|
|
2173
|
+
async sendTransaction(transaction) {
|
|
2174
|
+
const calls = [
|
|
2175
|
+
{
|
|
2176
|
+
to: transaction.to,
|
|
2177
|
+
data: transaction.data,
|
|
2178
|
+
value: transaction.value
|
|
2179
|
+
}
|
|
2180
|
+
];
|
|
2181
|
+
const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
|
|
2182
|
+
const result = await provider.sendIntent({
|
|
2183
|
+
username: config.username,
|
|
2184
|
+
targetChain: config.chain.id,
|
|
2185
|
+
calls: calls.map((call) => ({
|
|
2186
|
+
to: call.to,
|
|
2187
|
+
data: call.data,
|
|
2188
|
+
value: call.value ? call.value.toString() : "0",
|
|
2189
|
+
label: call.label,
|
|
2190
|
+
sublabel: call.sublabel
|
|
2191
|
+
})),
|
|
2192
|
+
closeOn,
|
|
2193
|
+
waitForHash: config.waitForHash ?? true,
|
|
2194
|
+
hashTimeoutMs: config.hashTimeoutMs,
|
|
2195
|
+
hashIntervalMs: config.hashIntervalMs
|
|
2196
|
+
});
|
|
2197
|
+
if (!result.success || !result.transactionHash) {
|
|
2198
|
+
throw new Error(result.error?.message || "Transaction failed");
|
|
2199
|
+
}
|
|
2200
|
+
return result.transactionHash;
|
|
2201
|
+
},
|
|
2202
|
+
/**
|
|
2203
|
+
* Send multiple calls as a single batched transaction
|
|
2204
|
+
*/
|
|
2205
|
+
async sendCalls(params) {
|
|
2206
|
+
const { calls } = params;
|
|
2207
|
+
const closeOn = config.waitForHash ?? true ? "completed" : "preconfirmed";
|
|
2208
|
+
const result = await provider.sendIntent({
|
|
2209
|
+
username: config.username,
|
|
2210
|
+
targetChain: config.chain.id,
|
|
2211
|
+
calls: calls.map((call) => ({
|
|
2212
|
+
to: call.to,
|
|
2213
|
+
data: call.data,
|
|
2214
|
+
value: call.value ? call.value.toString() : "0",
|
|
2215
|
+
label: call.label,
|
|
2216
|
+
sublabel: call.sublabel
|
|
2217
|
+
})),
|
|
2218
|
+
closeOn,
|
|
2219
|
+
waitForHash: config.waitForHash ?? true,
|
|
2220
|
+
hashTimeoutMs: config.hashTimeoutMs,
|
|
2221
|
+
hashIntervalMs: config.hashIntervalMs
|
|
2222
|
+
});
|
|
2223
|
+
if (!result.success || !result.transactionHash) {
|
|
2224
|
+
throw new Error(result.error?.message || "Transaction failed");
|
|
2225
|
+
}
|
|
2226
|
+
return result.transactionHash;
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
return extendedClient;
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
// src/batch/BatchQueueContext.tsx
|
|
2233
|
+
var React = __toESM(require("react"));
|
|
2234
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
2235
|
+
function getChainName2(chainId) {
|
|
2236
|
+
return getChainName(chainId);
|
|
2237
|
+
}
|
|
2238
|
+
var BatchQueueContext = React.createContext(null);
|
|
2239
|
+
function useBatchQueue() {
|
|
2240
|
+
const context = React.useContext(BatchQueueContext);
|
|
2241
|
+
if (!context) {
|
|
2242
|
+
throw new Error("useBatchQueue must be used within a BatchQueueProvider");
|
|
2243
|
+
}
|
|
2244
|
+
return context;
|
|
2245
|
+
}
|
|
2246
|
+
function generateId() {
|
|
2247
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
2248
|
+
}
|
|
2249
|
+
function getStorageKey(username) {
|
|
2250
|
+
return username ? `1auth_batch_queue_${username}` : "1auth_batch_queue_anonymous";
|
|
2251
|
+
}
|
|
2252
|
+
function BatchQueueProvider({
|
|
2253
|
+
client,
|
|
2254
|
+
username,
|
|
2255
|
+
children
|
|
2256
|
+
}) {
|
|
2257
|
+
const [queue, setQueue] = React.useState([]);
|
|
2258
|
+
const [isExpanded, setExpanded] = React.useState(false);
|
|
2259
|
+
const [isSigning, setIsSigning] = React.useState(false);
|
|
2260
|
+
const [animationTrigger, setAnimationTrigger] = React.useState(0);
|
|
2261
|
+
const batchChainId = queue.length > 0 ? queue[0].targetChain : null;
|
|
2262
|
+
React.useEffect(() => {
|
|
2263
|
+
const storageKey = getStorageKey(username);
|
|
2264
|
+
try {
|
|
2265
|
+
const stored = localStorage.getItem(storageKey);
|
|
2266
|
+
if (stored) {
|
|
2267
|
+
const parsed = JSON.parse(stored);
|
|
2268
|
+
if (Array.isArray(parsed) && parsed.every(
|
|
2269
|
+
(item) => typeof item.id === "string" && typeof item.call === "object" && typeof item.targetChain === "number"
|
|
2270
|
+
)) {
|
|
2271
|
+
setQueue(parsed);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
} catch {
|
|
2275
|
+
}
|
|
2276
|
+
}, [username]);
|
|
2277
|
+
React.useEffect(() => {
|
|
2278
|
+
const storageKey = getStorageKey(username);
|
|
2279
|
+
try {
|
|
2280
|
+
if (queue.length > 0) {
|
|
2281
|
+
localStorage.setItem(storageKey, JSON.stringify(queue));
|
|
2282
|
+
} else {
|
|
2283
|
+
localStorage.removeItem(storageKey);
|
|
2284
|
+
}
|
|
2285
|
+
} catch {
|
|
2286
|
+
}
|
|
2287
|
+
}, [queue, username]);
|
|
2288
|
+
const addToBatch = React.useCallback((call, targetChain) => {
|
|
2289
|
+
if (batchChainId !== null && batchChainId !== targetChain) {
|
|
2290
|
+
return {
|
|
2291
|
+
success: false,
|
|
2292
|
+
error: `Batch is set to ${getChainName2(batchChainId)}. Sign current batch first or clear it.`
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
const batchedCall = {
|
|
2296
|
+
id: generateId(),
|
|
2297
|
+
call,
|
|
2298
|
+
targetChain,
|
|
2299
|
+
addedAt: Date.now()
|
|
2300
|
+
};
|
|
2301
|
+
setQueue((prev) => [...prev, batchedCall]);
|
|
2302
|
+
setAnimationTrigger((prev) => prev + 1);
|
|
2303
|
+
return { success: true };
|
|
2304
|
+
}, [batchChainId]);
|
|
2305
|
+
const removeFromBatch = React.useCallback((id) => {
|
|
2306
|
+
setQueue((prev) => prev.filter((item) => item.id !== id));
|
|
2307
|
+
}, []);
|
|
2308
|
+
const clearBatch = React.useCallback(() => {
|
|
2309
|
+
setQueue([]);
|
|
2310
|
+
setExpanded(false);
|
|
2311
|
+
}, []);
|
|
2312
|
+
const signAll = React.useCallback(async (username2) => {
|
|
2313
|
+
if (queue.length === 0) {
|
|
2314
|
+
return {
|
|
2315
|
+
success: false,
|
|
2316
|
+
intentId: "",
|
|
2317
|
+
status: "failed",
|
|
2318
|
+
error: {
|
|
2319
|
+
code: "EMPTY_BATCH",
|
|
2320
|
+
message: "No calls in batch to sign"
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
const targetChain = queue[0].targetChain;
|
|
2325
|
+
const calls = queue.map((item) => item.call);
|
|
2326
|
+
setIsSigning(true);
|
|
2327
|
+
try {
|
|
2328
|
+
const result = await client.sendIntent({
|
|
2329
|
+
username: username2,
|
|
2330
|
+
targetChain,
|
|
2331
|
+
calls
|
|
2332
|
+
});
|
|
2333
|
+
if (result.success) {
|
|
2334
|
+
clearBatch();
|
|
2335
|
+
}
|
|
2336
|
+
return result;
|
|
2337
|
+
} finally {
|
|
2338
|
+
setIsSigning(false);
|
|
2339
|
+
}
|
|
2340
|
+
}, [queue, client, clearBatch]);
|
|
2341
|
+
const value = {
|
|
2342
|
+
queue,
|
|
2343
|
+
batchChainId,
|
|
2344
|
+
addToBatch,
|
|
2345
|
+
removeFromBatch,
|
|
2346
|
+
clearBatch,
|
|
2347
|
+
signAll,
|
|
2348
|
+
isExpanded,
|
|
2349
|
+
setExpanded,
|
|
2350
|
+
isSigning,
|
|
2351
|
+
animationTrigger
|
|
2352
|
+
};
|
|
2353
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BatchQueueContext.Provider, { value, children });
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
// src/batch/BatchQueueWidget.tsx
|
|
2357
|
+
var React2 = __toESM(require("react"));
|
|
2358
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
2359
|
+
var ANIMATION_STYLES = `
|
|
2360
|
+
@keyframes batch-bounce-in {
|
|
2361
|
+
0% { transform: scale(0.8); opacity: 0; }
|
|
2362
|
+
50% { transform: scale(1.05); }
|
|
2363
|
+
100% { transform: scale(1); opacity: 1; }
|
|
2364
|
+
}
|
|
2365
|
+
@keyframes batch-item-bounce {
|
|
2366
|
+
0%, 100% { transform: translateX(0); }
|
|
2367
|
+
25% { transform: translateX(-2px); }
|
|
2368
|
+
75% { transform: translateX(2px); }
|
|
2369
|
+
}
|
|
2370
|
+
`;
|
|
2371
|
+
function injectStyles() {
|
|
2372
|
+
if (typeof document === "undefined") return;
|
|
2373
|
+
const styleId = "batch-queue-widget-styles";
|
|
2374
|
+
if (document.getElementById(styleId)) return;
|
|
2375
|
+
const style = document.createElement("style");
|
|
2376
|
+
style.id = styleId;
|
|
2377
|
+
style.textContent = ANIMATION_STYLES;
|
|
2378
|
+
document.head.appendChild(style);
|
|
2379
|
+
}
|
|
2380
|
+
var widgetStyles = {
|
|
2381
|
+
position: "fixed",
|
|
2382
|
+
bottom: "16px",
|
|
2383
|
+
right: "16px",
|
|
2384
|
+
zIndex: 50,
|
|
2385
|
+
backgroundColor: "#18181b",
|
|
2386
|
+
color: "#ffffff",
|
|
2387
|
+
borderRadius: "12px",
|
|
2388
|
+
boxShadow: "0 4px 12px rgba(0,0,0,0.3)",
|
|
2389
|
+
minWidth: "240px",
|
|
2390
|
+
maxWidth: "360px",
|
|
2391
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
2392
|
+
fontSize: "14px",
|
|
2393
|
+
overflow: "hidden"
|
|
2394
|
+
};
|
|
2395
|
+
var headerStyles = {
|
|
2396
|
+
display: "flex",
|
|
2397
|
+
alignItems: "center",
|
|
2398
|
+
justifyContent: "space-between",
|
|
2399
|
+
padding: "12px 16px",
|
|
2400
|
+
cursor: "pointer",
|
|
2401
|
+
userSelect: "none"
|
|
2402
|
+
};
|
|
2403
|
+
var headerLeftStyles = {
|
|
2404
|
+
display: "flex",
|
|
2405
|
+
alignItems: "center",
|
|
2406
|
+
gap: "8px"
|
|
2407
|
+
};
|
|
2408
|
+
var counterBadgeStyles = {
|
|
2409
|
+
backgroundColor: "#ffffff",
|
|
2410
|
+
color: "#18181b",
|
|
2411
|
+
borderRadius: "9999px",
|
|
2412
|
+
padding: "2px 8px",
|
|
2413
|
+
fontSize: "12px",
|
|
2414
|
+
fontWeight: 600
|
|
2415
|
+
};
|
|
2416
|
+
var chainBadgeStyles = {
|
|
2417
|
+
backgroundColor: "rgba(255,255,255,0.15)",
|
|
2418
|
+
color: "#a1a1aa",
|
|
2419
|
+
borderRadius: "4px",
|
|
2420
|
+
padding: "2px 6px",
|
|
2421
|
+
fontSize: "11px"
|
|
2422
|
+
};
|
|
2423
|
+
var signAllButtonStyles = {
|
|
2424
|
+
backgroundColor: "#ffffff",
|
|
2425
|
+
color: "#18181b",
|
|
2426
|
+
border: "none",
|
|
2427
|
+
borderRadius: "6px",
|
|
2428
|
+
padding: "6px 12px",
|
|
2429
|
+
fontSize: "13px",
|
|
2430
|
+
fontWeight: 500,
|
|
2431
|
+
cursor: "pointer",
|
|
2432
|
+
transition: "background-color 0.15s"
|
|
2433
|
+
};
|
|
2434
|
+
var signAllButtonHoverStyles = {
|
|
2435
|
+
backgroundColor: "#e4e4e7"
|
|
2436
|
+
};
|
|
2437
|
+
var signAllButtonDisabledStyles = {
|
|
2438
|
+
opacity: 0.5,
|
|
2439
|
+
cursor: "not-allowed"
|
|
2440
|
+
};
|
|
2441
|
+
var listContainerStyles = {
|
|
2442
|
+
maxHeight: "300px",
|
|
2443
|
+
overflowY: "auto",
|
|
2444
|
+
borderTop: "1px solid #27272a"
|
|
2445
|
+
};
|
|
2446
|
+
var callItemStyles = {
|
|
2447
|
+
display: "flex",
|
|
2448
|
+
alignItems: "flex-start",
|
|
2449
|
+
padding: "10px 16px",
|
|
2450
|
+
borderBottom: "1px solid #27272a",
|
|
2451
|
+
position: "relative"
|
|
2452
|
+
};
|
|
2453
|
+
var callItemLastStyles = {
|
|
2454
|
+
borderBottom: "none"
|
|
2455
|
+
};
|
|
2456
|
+
var callContentStyles = {
|
|
2457
|
+
flex: 1,
|
|
2458
|
+
minWidth: 0
|
|
2459
|
+
};
|
|
2460
|
+
var callLabelStyles = {
|
|
2461
|
+
fontWeight: 500,
|
|
2462
|
+
marginBottom: "2px"
|
|
2463
|
+
};
|
|
2464
|
+
var callSublabelStyles = {
|
|
2465
|
+
color: "#a1a1aa",
|
|
2466
|
+
fontSize: "12px",
|
|
2467
|
+
overflow: "hidden",
|
|
2468
|
+
textOverflow: "ellipsis",
|
|
2469
|
+
whiteSpace: "nowrap"
|
|
2470
|
+
};
|
|
2471
|
+
var removeButtonStyles = {
|
|
2472
|
+
position: "absolute",
|
|
2473
|
+
left: "8px",
|
|
2474
|
+
top: "50%",
|
|
2475
|
+
transform: "translateY(-50%)",
|
|
2476
|
+
backgroundColor: "#dc2626",
|
|
2477
|
+
color: "#ffffff",
|
|
2478
|
+
border: "none",
|
|
2479
|
+
borderRadius: "4px",
|
|
2480
|
+
width: "20px",
|
|
2481
|
+
height: "20px",
|
|
2482
|
+
display: "flex",
|
|
2483
|
+
alignItems: "center",
|
|
2484
|
+
justifyContent: "center",
|
|
2485
|
+
cursor: "pointer",
|
|
2486
|
+
fontSize: "14px",
|
|
2487
|
+
opacity: 0,
|
|
2488
|
+
transition: "opacity 0.15s"
|
|
2489
|
+
};
|
|
2490
|
+
var removeButtonVisibleStyles = {
|
|
2491
|
+
opacity: 1
|
|
2492
|
+
};
|
|
2493
|
+
var clearButtonStyles = {
|
|
2494
|
+
display: "block",
|
|
2495
|
+
width: "100%",
|
|
2496
|
+
padding: "10px 16px",
|
|
2497
|
+
backgroundColor: "transparent",
|
|
2498
|
+
color: "#a1a1aa",
|
|
2499
|
+
border: "none",
|
|
2500
|
+
borderTop: "1px solid #27272a",
|
|
2501
|
+
fontSize: "12px",
|
|
2502
|
+
cursor: "pointer",
|
|
2503
|
+
textAlign: "center",
|
|
2504
|
+
transition: "color 0.15s"
|
|
2505
|
+
};
|
|
2506
|
+
var clearButtonHoverStyles = {
|
|
2507
|
+
color: "#ffffff"
|
|
2508
|
+
};
|
|
2509
|
+
function ChevronIcon({ down }) {
|
|
2510
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2511
|
+
"svg",
|
|
2512
|
+
{
|
|
2513
|
+
width: "16",
|
|
2514
|
+
height: "16",
|
|
2515
|
+
viewBox: "0 0 24 24",
|
|
2516
|
+
fill: "none",
|
|
2517
|
+
stroke: "currentColor",
|
|
2518
|
+
strokeWidth: "2",
|
|
2519
|
+
strokeLinecap: "round",
|
|
2520
|
+
strokeLinejoin: "round",
|
|
2521
|
+
style: {
|
|
2522
|
+
transition: "transform 0.2s",
|
|
2523
|
+
transform: down ? "rotate(0deg)" : "rotate(-90deg)"
|
|
2524
|
+
},
|
|
2525
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m6 9 6 6 6-6" })
|
|
2526
|
+
}
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
function BatchQueueWidget({ onSignAll }) {
|
|
2530
|
+
const {
|
|
2531
|
+
queue,
|
|
2532
|
+
batchChainId,
|
|
2533
|
+
removeFromBatch,
|
|
2534
|
+
clearBatch,
|
|
2535
|
+
isExpanded,
|
|
2536
|
+
setExpanded,
|
|
2537
|
+
isSigning,
|
|
2538
|
+
animationTrigger
|
|
2539
|
+
} = useBatchQueue();
|
|
2540
|
+
const [hoveredItemId, setHoveredItemId] = React2.useState(null);
|
|
2541
|
+
const [isSignAllHovered, setIsSignAllHovered] = React2.useState(false);
|
|
2542
|
+
const [isClearHovered, setIsClearHovered] = React2.useState(false);
|
|
2543
|
+
const [shouldAnimate, setShouldAnimate] = React2.useState(false);
|
|
2544
|
+
React2.useEffect(() => {
|
|
2545
|
+
injectStyles();
|
|
2546
|
+
}, []);
|
|
2547
|
+
React2.useEffect(() => {
|
|
2548
|
+
if (animationTrigger > 0) {
|
|
2549
|
+
setShouldAnimate(true);
|
|
2550
|
+
const timer = setTimeout(() => setShouldAnimate(false), 300);
|
|
2551
|
+
return () => clearTimeout(timer);
|
|
2552
|
+
}
|
|
2553
|
+
}, [animationTrigger]);
|
|
2554
|
+
if (queue.length === 0) {
|
|
2555
|
+
return null;
|
|
2556
|
+
}
|
|
2557
|
+
const handleHeaderClick = () => {
|
|
2558
|
+
setExpanded(!isExpanded);
|
|
2559
|
+
};
|
|
2560
|
+
const handleSignAllClick = (e) => {
|
|
2561
|
+
e.stopPropagation();
|
|
2562
|
+
if (!isSigning) {
|
|
2563
|
+
onSignAll();
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
const animatedWidgetStyles = {
|
|
2567
|
+
...widgetStyles,
|
|
2568
|
+
animation: shouldAnimate ? "batch-bounce-in 0.3s ease-out" : void 0
|
|
2569
|
+
};
|
|
2570
|
+
const currentSignAllStyles = {
|
|
2571
|
+
...signAllButtonStyles,
|
|
2572
|
+
...isSignAllHovered && !isSigning ? signAllButtonHoverStyles : {},
|
|
2573
|
+
...isSigning ? signAllButtonDisabledStyles : {}
|
|
2574
|
+
};
|
|
2575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: animatedWidgetStyles, children: [
|
|
2576
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: headerStyles, onClick: handleHeaderClick, children: [
|
|
2577
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: headerLeftStyles, children: [
|
|
2578
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ChevronIcon, { down: isExpanded }),
|
|
2579
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: counterBadgeStyles, children: queue.length }),
|
|
2580
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
2581
|
+
"call",
|
|
2582
|
+
queue.length !== 1 ? "s" : "",
|
|
2583
|
+
" queued"
|
|
2584
|
+
] }),
|
|
2585
|
+
batchChainId && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: chainBadgeStyles, children: getChainName2(batchChainId) })
|
|
2586
|
+
] }),
|
|
2587
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2588
|
+
"button",
|
|
2589
|
+
{
|
|
2590
|
+
style: currentSignAllStyles,
|
|
2591
|
+
onClick: handleSignAllClick,
|
|
2592
|
+
onMouseEnter: () => setIsSignAllHovered(true),
|
|
2593
|
+
onMouseLeave: () => setIsSignAllHovered(false),
|
|
2594
|
+
disabled: isSigning,
|
|
2595
|
+
children: isSigning ? "Signing..." : "Sign All"
|
|
2596
|
+
}
|
|
2597
|
+
)
|
|
2598
|
+
] }),
|
|
2599
|
+
isExpanded && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
2600
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: listContainerStyles, children: queue.map((item, index) => {
|
|
2601
|
+
const isHovered = hoveredItemId === item.id;
|
|
2602
|
+
const isLast = index === queue.length - 1;
|
|
2603
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2604
|
+
"div",
|
|
2605
|
+
{
|
|
2606
|
+
style: {
|
|
2607
|
+
...callItemStyles,
|
|
2608
|
+
...isLast ? callItemLastStyles : {},
|
|
2609
|
+
paddingLeft: isHovered ? "36px" : "16px",
|
|
2610
|
+
transition: "padding-left 0.15s"
|
|
2611
|
+
},
|
|
2612
|
+
onMouseEnter: () => setHoveredItemId(item.id),
|
|
2613
|
+
onMouseLeave: () => setHoveredItemId(null),
|
|
2614
|
+
children: [
|
|
2615
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2616
|
+
"button",
|
|
2617
|
+
{
|
|
2618
|
+
style: {
|
|
2619
|
+
...removeButtonStyles,
|
|
2620
|
+
...isHovered ? removeButtonVisibleStyles : {}
|
|
2621
|
+
},
|
|
2622
|
+
onClick: () => removeFromBatch(item.id),
|
|
2623
|
+
title: "Remove from batch",
|
|
2624
|
+
children: "\xD7"
|
|
2625
|
+
}
|
|
2626
|
+
),
|
|
2627
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: callContentStyles, children: [
|
|
2628
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: callLabelStyles, children: item.call.label || "Contract Call" }),
|
|
2629
|
+
item.call.sublabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: callSublabelStyles, children: item.call.sublabel }),
|
|
2630
|
+
!item.call.sublabel && item.call.to && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: callSublabelStyles, children: [
|
|
2631
|
+
"To: ",
|
|
2632
|
+
item.call.to.slice(0, 6),
|
|
2633
|
+
"...",
|
|
2634
|
+
item.call.to.slice(-4)
|
|
2635
|
+
] })
|
|
2636
|
+
] })
|
|
2637
|
+
]
|
|
2638
|
+
},
|
|
2639
|
+
item.id
|
|
2640
|
+
);
|
|
2641
|
+
}) }),
|
|
2642
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2643
|
+
"button",
|
|
2644
|
+
{
|
|
2645
|
+
style: {
|
|
2646
|
+
...clearButtonStyles,
|
|
2647
|
+
...isClearHovered ? clearButtonHoverStyles : {}
|
|
2648
|
+
},
|
|
2649
|
+
onClick: clearBatch,
|
|
2650
|
+
onMouseEnter: () => setIsClearHovered(true),
|
|
2651
|
+
onMouseLeave: () => setIsClearHovered(false),
|
|
2652
|
+
children: "Clear batch"
|
|
2653
|
+
}
|
|
2654
|
+
)
|
|
2655
|
+
] })
|
|
2656
|
+
] });
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// src/verify.ts
|
|
2660
|
+
var import_viem7 = require("viem");
|
|
2661
|
+
var PASSKEY_MESSAGE_PREFIX = "Passkey Signed Message:\n";
|
|
2662
|
+
function hashMessage2(message) {
|
|
2663
|
+
const prefixed = PASSKEY_MESSAGE_PREFIX + message.length.toString() + message;
|
|
2664
|
+
return (0, import_viem7.keccak256)((0, import_viem7.toBytes)(prefixed));
|
|
2665
|
+
}
|
|
2666
|
+
function verifyMessageHash(message, signedHash) {
|
|
2667
|
+
if (!signedHash) return false;
|
|
2668
|
+
const expectedHash = hashMessage2(message);
|
|
2669
|
+
return expectedHash.toLowerCase() === signedHash.toLowerCase();
|
|
2670
|
+
}
|
|
2671
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2672
|
+
0 && (module.exports = {
|
|
2673
|
+
BatchQueueProvider,
|
|
2674
|
+
BatchQueueWidget,
|
|
2675
|
+
PASSKEY_MESSAGE_PREFIX,
|
|
2676
|
+
PasskeyProviderClient,
|
|
2677
|
+
createPasskeyAccount,
|
|
2678
|
+
createPasskeyProvider,
|
|
2679
|
+
createPasskeyWalletClient,
|
|
2680
|
+
encodeWebAuthnSignature,
|
|
2681
|
+
getAllSupportedChainsAndTokens,
|
|
2682
|
+
getChainById,
|
|
2683
|
+
getChainExplorerUrl,
|
|
2684
|
+
getChainName,
|
|
2685
|
+
getChainRpcUrl,
|
|
2686
|
+
getSupportedChainIds,
|
|
2687
|
+
getSupportedChains,
|
|
2688
|
+
getSupportedTokenSymbols,
|
|
2689
|
+
getSupportedTokens,
|
|
2690
|
+
getTokenAddress,
|
|
2691
|
+
getTokenDecimals,
|
|
2692
|
+
getTokenSymbol,
|
|
2693
|
+
hashCalls,
|
|
2694
|
+
hashMessage,
|
|
2695
|
+
isTestnet,
|
|
2696
|
+
isTokenAddressSupported,
|
|
2697
|
+
resolveTokenAddress,
|
|
2698
|
+
useBatchQueue,
|
|
2699
|
+
verifyMessageHash
|
|
2700
|
+
});
|
|
2701
|
+
//# sourceMappingURL=index.js.map
|