@swype-org/react-sdk 0.1.1 → 0.1.3

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/index.cjs CHANGED
@@ -6,6 +6,8 @@ var wagmi = require('wagmi');
6
6
  var chains = require('wagmi/chains');
7
7
  var reactQuery = require('@tanstack/react-query');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
+ var viem = require('viem');
10
+ var utils = require('viem/utils');
9
11
 
10
12
  var __defProp = Object.defineProperty;
11
13
  var __export = (target, all) => {
@@ -84,13 +86,19 @@ function SwypeProvider({
84
86
  if (!queryClientRef.current) {
85
87
  queryClientRef.current = new reactQuery.QueryClient();
86
88
  }
89
+ const [depositAmount, setDepositAmountRaw] = react.useState(null);
90
+ const setDepositAmount = react.useCallback((amount) => {
91
+ setDepositAmountRaw(amount);
92
+ }, []);
87
93
  const value = react.useMemo(
88
94
  () => ({
89
95
  apiBaseUrl,
90
96
  theme,
91
- tokens: getTheme(theme)
97
+ tokens: getTheme(theme),
98
+ depositAmount,
99
+ setDepositAmount
92
100
  }),
93
- [apiBaseUrl, theme]
101
+ [apiBaseUrl, theme, depositAmount, setDepositAmount]
94
102
  );
95
103
  return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClientRef.current, children: /* @__PURE__ */ jsxRuntime.jsx(wagmi.WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ jsxRuntime.jsx(
96
104
  reactAuth.PrivyProvider,
@@ -112,6 +120,20 @@ function useSwypeConfig() {
112
120
  }
113
121
  return ctx;
114
122
  }
123
+ function useSwypeDepositAmount() {
124
+ const ctx = react.useContext(SwypeContext);
125
+ if (!ctx) {
126
+ throw new Error(
127
+ "useSwypeDepositAmount must be used within a <SwypeProvider>"
128
+ );
129
+ }
130
+ return {
131
+ /** Current deposit amount, or null if not set */
132
+ amount: ctx.depositAmount,
133
+ /** Set the deposit amount (pass null to clear) */
134
+ setAmount: ctx.setDepositAmount
135
+ };
136
+ }
115
137
 
116
138
  // src/api.ts
117
139
  var api_exports = {};
@@ -123,7 +145,9 @@ __export(api_exports, {
123
145
  fetchProviders: () => fetchProviders,
124
146
  fetchTransfer: () => fetchTransfer,
125
147
  reportActionCompletion: () => reportActionCompletion,
126
- updateUserConfig: () => updateUserConfig
148
+ signTransfer: () => signTransfer,
149
+ updateUserConfig: () => updateUserConfig,
150
+ updateUserConfigBySession: () => updateUserConfigBySession
127
151
  });
128
152
  async function throwApiError(res) {
129
153
  const body = await res.json().catch(() => null);
@@ -190,6 +214,18 @@ async function fetchTransfer(apiBaseUrl, token, transferId) {
190
214
  if (!res.ok) await throwApiError(res);
191
215
  return await res.json();
192
216
  }
217
+ async function signTransfer(apiBaseUrl, token, transferId, signedUserOp) {
218
+ const res = await fetch(`${apiBaseUrl}/v1/transfers/${transferId}`, {
219
+ method: "PATCH",
220
+ headers: {
221
+ "Content-Type": "application/json",
222
+ Authorization: `Bearer ${token}`
223
+ },
224
+ body: JSON.stringify({ signedUserOp })
225
+ });
226
+ if (!res.ok) await throwApiError(res);
227
+ return await res.json();
228
+ }
193
229
  async function fetchAuthorizationSession(apiBaseUrl, sessionId) {
194
230
  const res = await fetch(
195
231
  `${apiBaseUrl}/v1/authorization-sessions/${sessionId}`
@@ -208,6 +244,17 @@ async function updateUserConfig(apiBaseUrl, token, config) {
208
244
  });
209
245
  if (!res.ok) await throwApiError(res);
210
246
  }
247
+ async function updateUserConfigBySession(apiBaseUrl, sessionId, config) {
248
+ const res = await fetch(
249
+ `${apiBaseUrl}/v1/authorization-sessions/${sessionId}/user-config`,
250
+ {
251
+ method: "PATCH",
252
+ headers: { "Content-Type": "application/json" },
253
+ body: JSON.stringify({ config })
254
+ }
255
+ );
256
+ if (!res.ok) await throwApiError(res);
257
+ }
211
258
  async function reportActionCompletion(apiBaseUrl, actionId, result) {
212
259
  const res = await fetch(
213
260
  `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
@@ -220,6 +267,443 @@ async function reportActionCompletion(apiBaseUrl, actionId, result) {
220
267
  if (!res.ok) await throwApiError(res);
221
268
  return await res.json();
222
269
  }
270
+
271
+ // node_modules/@wagmi/core/dist/esm/version.js
272
+ var version = "2.22.1";
273
+
274
+ // node_modules/@wagmi/core/dist/esm/utils/getVersion.js
275
+ var getVersion = () => `@wagmi/core@${version}`;
276
+
277
+ // node_modules/@wagmi/core/dist/esm/errors/base.js
278
+ var __classPrivateFieldGet = function(receiver, state, kind, f) {
279
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
280
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
281
+ };
282
+ var _BaseError_instances;
283
+ var _BaseError_walk;
284
+ var BaseError = class _BaseError extends Error {
285
+ get docsBaseUrl() {
286
+ return "https://wagmi.sh/core";
287
+ }
288
+ get version() {
289
+ return getVersion();
290
+ }
291
+ constructor(shortMessage, options = {}) {
292
+ super();
293
+ _BaseError_instances.add(this);
294
+ Object.defineProperty(this, "details", {
295
+ enumerable: true,
296
+ configurable: true,
297
+ writable: true,
298
+ value: void 0
299
+ });
300
+ Object.defineProperty(this, "docsPath", {
301
+ enumerable: true,
302
+ configurable: true,
303
+ writable: true,
304
+ value: void 0
305
+ });
306
+ Object.defineProperty(this, "metaMessages", {
307
+ enumerable: true,
308
+ configurable: true,
309
+ writable: true,
310
+ value: void 0
311
+ });
312
+ Object.defineProperty(this, "shortMessage", {
313
+ enumerable: true,
314
+ configurable: true,
315
+ writable: true,
316
+ value: void 0
317
+ });
318
+ Object.defineProperty(this, "name", {
319
+ enumerable: true,
320
+ configurable: true,
321
+ writable: true,
322
+ value: "WagmiCoreError"
323
+ });
324
+ const details = options.cause instanceof _BaseError ? options.cause.details : options.cause?.message ? options.cause.message : options.details;
325
+ const docsPath = options.cause instanceof _BaseError ? options.cause.docsPath || options.docsPath : options.docsPath;
326
+ this.message = [
327
+ shortMessage || "An error occurred.",
328
+ "",
329
+ ...options.metaMessages ? [...options.metaMessages, ""] : [],
330
+ ...docsPath ? [
331
+ `Docs: ${this.docsBaseUrl}${docsPath}.html${options.docsSlug ? `#${options.docsSlug}` : ""}`
332
+ ] : [],
333
+ ...details ? [`Details: ${details}`] : [],
334
+ `Version: ${this.version}`
335
+ ].join("\n");
336
+ if (options.cause)
337
+ this.cause = options.cause;
338
+ this.details = details;
339
+ this.docsPath = docsPath;
340
+ this.metaMessages = options.metaMessages;
341
+ this.shortMessage = shortMessage;
342
+ }
343
+ walk(fn) {
344
+ return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk).call(this, this, fn);
345
+ }
346
+ };
347
+ _BaseError_instances = /* @__PURE__ */ new WeakSet(), _BaseError_walk = function _BaseError_walk2(err, fn) {
348
+ if (fn?.(err))
349
+ return err;
350
+ if (err.cause)
351
+ return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk2).call(this, err.cause, fn);
352
+ return err;
353
+ };
354
+
355
+ // node_modules/@wagmi/core/dist/esm/errors/config.js
356
+ var ConnectorNotConnectedError = class extends BaseError {
357
+ constructor() {
358
+ super("Connector not connected.");
359
+ Object.defineProperty(this, "name", {
360
+ enumerable: true,
361
+ configurable: true,
362
+ writable: true,
363
+ value: "ConnectorNotConnectedError"
364
+ });
365
+ }
366
+ };
367
+ var ConnectorAccountNotFoundError = class extends BaseError {
368
+ constructor({ address, connector }) {
369
+ super(`Account "${address}" not found for connector "${connector.name}".`);
370
+ Object.defineProperty(this, "name", {
371
+ enumerable: true,
372
+ configurable: true,
373
+ writable: true,
374
+ value: "ConnectorAccountNotFoundError"
375
+ });
376
+ }
377
+ };
378
+ var ConnectorChainMismatchError = class extends BaseError {
379
+ constructor({ connectionChainId, connectorChainId }) {
380
+ super(`The current chain of the connector (id: ${connectorChainId}) does not match the connection's chain (id: ${connectionChainId}).`, {
381
+ metaMessages: [
382
+ `Current Chain ID: ${connectorChainId}`,
383
+ `Expected Chain ID: ${connectionChainId}`
384
+ ]
385
+ });
386
+ Object.defineProperty(this, "name", {
387
+ enumerable: true,
388
+ configurable: true,
389
+ writable: true,
390
+ value: "ConnectorChainMismatchError"
391
+ });
392
+ }
393
+ };
394
+ var ConnectorUnavailableReconnectingError = class extends BaseError {
395
+ constructor({ connector }) {
396
+ super(`Connector "${connector.name}" unavailable while reconnecting.`, {
397
+ details: [
398
+ "During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uid`.",
399
+ "All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored.",
400
+ "This error commonly occurs for connectors that asynchronously inject after reconnection has already started."
401
+ ].join(" ")
402
+ });
403
+ Object.defineProperty(this, "name", {
404
+ enumerable: true,
405
+ configurable: true,
406
+ writable: true,
407
+ value: "ConnectorUnavailableReconnectingError"
408
+ });
409
+ }
410
+ };
411
+ async function getConnectorClient(config, parameters = {}) {
412
+ const { assertChainId = true } = parameters;
413
+ let connection;
414
+ if (parameters.connector) {
415
+ const { connector: connector2 } = parameters;
416
+ if (config.state.status === "reconnecting" && !connector2.getAccounts && !connector2.getChainId)
417
+ throw new ConnectorUnavailableReconnectingError({ connector: connector2 });
418
+ const [accounts, chainId2] = await Promise.all([
419
+ connector2.getAccounts().catch((e) => {
420
+ if (parameters.account === null)
421
+ return [];
422
+ throw e;
423
+ }),
424
+ connector2.getChainId()
425
+ ]);
426
+ connection = {
427
+ accounts,
428
+ chainId: chainId2,
429
+ connector: connector2
430
+ };
431
+ } else
432
+ connection = config.state.connections.get(config.state.current);
433
+ if (!connection)
434
+ throw new ConnectorNotConnectedError();
435
+ const chainId = parameters.chainId ?? connection.chainId;
436
+ const connectorChainId = await connection.connector.getChainId();
437
+ if (assertChainId && connectorChainId !== chainId)
438
+ throw new ConnectorChainMismatchError({
439
+ connectionChainId: chainId,
440
+ connectorChainId
441
+ });
442
+ const connector = connection.connector;
443
+ if (connector.getClient)
444
+ return connector.getClient({ chainId });
445
+ const account = utils.parseAccount(parameters.account ?? connection.accounts[0]);
446
+ if (account)
447
+ account.address = utils.getAddress(account.address);
448
+ if (parameters.account && !connection.accounts.some((x) => x.toLowerCase() === account.address.toLowerCase()))
449
+ throw new ConnectorAccountNotFoundError({
450
+ address: account.address,
451
+ connector
452
+ });
453
+ const chain = config.chains.find((chain2) => chain2.id === chainId);
454
+ const provider = await connection.connector.getProvider({ chainId });
455
+ return viem.createClient({
456
+ account,
457
+ chain,
458
+ name: "Connector Client",
459
+ transport: (opts) => viem.custom(provider)({ ...opts, retryCount: 0 })
460
+ });
461
+ }
462
+
463
+ // node_modules/@wagmi/core/dist/esm/actions/getAccount.js
464
+ function getAccount(config) {
465
+ const uid = config.state.current;
466
+ const connection = config.state.connections.get(uid);
467
+ const addresses = connection?.accounts;
468
+ const address = addresses?.[0];
469
+ const chain = config.chains.find((chain2) => chain2.id === connection?.chainId);
470
+ const status = config.state.status;
471
+ switch (status) {
472
+ case "connected":
473
+ return {
474
+ address,
475
+ addresses,
476
+ chain,
477
+ chainId: connection?.chainId,
478
+ connector: connection?.connector,
479
+ isConnected: true,
480
+ isConnecting: false,
481
+ isDisconnected: false,
482
+ isReconnecting: false,
483
+ status
484
+ };
485
+ case "reconnecting":
486
+ return {
487
+ address,
488
+ addresses,
489
+ chain,
490
+ chainId: connection?.chainId,
491
+ connector: connection?.connector,
492
+ isConnected: !!address,
493
+ isConnecting: false,
494
+ isDisconnected: false,
495
+ isReconnecting: true,
496
+ status
497
+ };
498
+ case "connecting":
499
+ return {
500
+ address,
501
+ addresses,
502
+ chain,
503
+ chainId: connection?.chainId,
504
+ connector: connection?.connector,
505
+ isConnected: false,
506
+ isConnecting: true,
507
+ isDisconnected: false,
508
+ isReconnecting: false,
509
+ status
510
+ };
511
+ case "disconnected":
512
+ return {
513
+ address: void 0,
514
+ addresses: void 0,
515
+ chain: void 0,
516
+ chainId: void 0,
517
+ connector: void 0,
518
+ isConnected: false,
519
+ isConnecting: false,
520
+ isDisconnected: true,
521
+ isReconnecting: false,
522
+ status
523
+ };
524
+ }
525
+ }
526
+ async function getWalletClient(config, parameters = {}) {
527
+ const client = await getConnectorClient(config, parameters);
528
+ return client.extend(viem.walletActions);
529
+ }
530
+
531
+ // src/authorization/upgrade.ts
532
+ function isNonEmptyString(value) {
533
+ return typeof value === "string" && value.trim().length > 0;
534
+ }
535
+ function isValidChainId(value) {
536
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
537
+ }
538
+ function isRpcClient(value) {
539
+ return typeof value === "object" && value !== null && "request" in value && typeof value.request === "function";
540
+ }
541
+ function sleep(ms) {
542
+ return new Promise((resolve) => setTimeout(resolve, ms));
543
+ }
544
+ async function pollCallsStatus(client, bundleId, label, timeoutMs = 12e4, intervalMs = 3e3) {
545
+ const deadline = Date.now() + timeoutMs;
546
+ while (Date.now() < deadline) {
547
+ const raw = await client.request({
548
+ method: "wallet_getCallsStatus",
549
+ params: [bundleId]
550
+ });
551
+ const result = raw;
552
+ const status = result.status;
553
+ console.info(`[swype-sdk][upgrade] ${label} status:`, status);
554
+ if (status === 200 || status === "CONFIRMED" || status === "confirmed") {
555
+ const txHash = result.receipts?.[0]?.transactionHash ?? "";
556
+ if (!txHash) {
557
+ console.warn(`[swype-sdk][upgrade] ${label}: confirmed but no receipt txHash.`);
558
+ }
559
+ return txHash;
560
+ }
561
+ if (typeof status === "number" && status >= 400) {
562
+ throw new Error(
563
+ `${label} failed with status ${status}. Receipts: ${JSON.stringify(result.receipts ?? [])}`
564
+ );
565
+ }
566
+ if (typeof status === "string" && (status.toLowerCase().includes("fail") || status.toLowerCase().includes("error"))) {
567
+ throw new Error(
568
+ `${label} failed: ${status}. Receipts: ${JSON.stringify(result.receipts ?? [])}`
569
+ );
570
+ }
571
+ await sleep(intervalMs);
572
+ }
573
+ throw new Error(`${label} timed out after ${timeoutMs / 1e3}s.`);
574
+ }
575
+ async function sendCalls(client, callsParams, label) {
576
+ try {
577
+ const result = await client.request({
578
+ method: "wallet_sendCalls",
579
+ params: [callsParams]
580
+ });
581
+ const bundleId = typeof result === "string" ? result : result?.id;
582
+ if (!bundleId) {
583
+ throw new Error(`${label}: wallet_sendCalls returned an unexpected result: ${JSON.stringify(result)}`);
584
+ }
585
+ return bundleId;
586
+ } catch (err) {
587
+ const msg = err.message ?? "";
588
+ if (msg.includes("Unauthorized") || msg.includes("-32006")) {
589
+ throw new Error(
590
+ "MetaMask smart accounts may not be enabled or may not be supported on this chain. Please ensure you are using a supported network and that smart accounts are enabled in MetaMask Settings \u2192 Experimental."
591
+ );
592
+ }
593
+ throw new Error(`${label} failed: ${msg}`);
594
+ }
595
+ }
596
+ async function submitUpgradeTransaction(params) {
597
+ const { walletClient, sender, metadata } = params;
598
+ const addKeyCalldata = metadata?.addKeyCalldata;
599
+ if (!isNonEmptyString(addKeyCalldata)) {
600
+ throw new Error("Incomplete upgrade metadata: missing addKeyCalldata.");
601
+ }
602
+ const resolvedSmartAccountAddress = metadata?.smartAccountAddress ?? sender;
603
+ if (!isNonEmptyString(resolvedSmartAccountAddress)) {
604
+ throw new Error("No connected account address found for smart account upgrade.");
605
+ }
606
+ const chainId = metadata?.upgradePayload?.authorization?.chainId;
607
+ if (!isValidChainId(chainId)) {
608
+ throw new Error("Invalid or missing chainId in upgrade metadata.");
609
+ }
610
+ if (!isRpcClient(walletClient)) {
611
+ throw new Error(
612
+ "Connected wallet client does not support request(). EIP-7702 upgrade requires a wallet client with raw RPC access."
613
+ );
614
+ }
615
+ const hexChainId = `0x${chainId.toString(16)}`;
616
+ console.info(
617
+ "[swype-sdk][upgrade] Step 1/2: Triggering EIP-7702 upgrade via wallet_sendCalls.",
618
+ { from: resolvedSmartAccountAddress, chainId: hexChainId }
619
+ );
620
+ const upgradeBundleId = await sendCalls(
621
+ walletClient,
622
+ {
623
+ version: "2.0.0",
624
+ from: resolvedSmartAccountAddress,
625
+ chainId: hexChainId,
626
+ atomicRequired: false,
627
+ calls: [{ to: resolvedSmartAccountAddress, value: "0x0" }]
628
+ },
629
+ "EIP-7702 upgrade"
630
+ );
631
+ console.info("[swype-sdk][upgrade] Upgrade bundle submitted. Polling for confirmation\u2026", {
632
+ bundleId: upgradeBundleId
633
+ });
634
+ const upgradeTxHash = await pollCallsStatus(
635
+ walletClient,
636
+ upgradeBundleId,
637
+ "EIP-7702 upgrade"
638
+ );
639
+ console.info("[swype-sdk][upgrade] Step 1/2 complete: account upgraded.", {
640
+ txHash: upgradeTxHash
641
+ });
642
+ console.info(
643
+ "[swype-sdk][upgrade] Step 2/2: Registering passkey via wallet_sendCalls (addKey)."
644
+ );
645
+ const addKeyBundleId = await sendCalls(
646
+ walletClient,
647
+ {
648
+ version: "2.0.0",
649
+ from: resolvedSmartAccountAddress,
650
+ chainId: hexChainId,
651
+ atomicRequired: false,
652
+ calls: [
653
+ {
654
+ to: resolvedSmartAccountAddress,
655
+ value: "0x0",
656
+ data: addKeyCalldata
657
+ }
658
+ ]
659
+ },
660
+ "Passkey registration (addKey)"
661
+ );
662
+ console.info("[swype-sdk][upgrade] addKey bundle submitted. Polling for confirmation\u2026", {
663
+ bundleId: addKeyBundleId
664
+ });
665
+ const addKeyTxHash = await pollCallsStatus(
666
+ walletClient,
667
+ addKeyBundleId,
668
+ "Passkey registration"
669
+ );
670
+ console.info("[swype-sdk][upgrade] Step 2/2 complete: passkey registered.", {
671
+ txHash: addKeyTxHash
672
+ });
673
+ return {
674
+ txHash: addKeyTxHash || upgradeTxHash,
675
+ smartAccountAddress: resolvedSmartAccountAddress
676
+ };
677
+ }
678
+
679
+ // src/hooks.ts
680
+ async function waitForWalletClient(wagmiConfig2, maxAttempts = 15, intervalMs = 200) {
681
+ for (let i = 0; i < maxAttempts; i++) {
682
+ try {
683
+ return await getWalletClient(wagmiConfig2);
684
+ } catch {
685
+ if (i === maxAttempts - 1) {
686
+ throw new Error("Wallet not ready. Please try again.");
687
+ }
688
+ await new Promise((r) => setTimeout(r, intervalMs));
689
+ }
690
+ }
691
+ throw new Error("Wallet not ready. Please try again.");
692
+ }
693
+ function extractErrorMessage(err) {
694
+ if (err instanceof Error) return err.message;
695
+ if (typeof err === "string") return err;
696
+ if (err && typeof err === "object") {
697
+ const maybeMessage = err.message;
698
+ if (typeof maybeMessage === "string") return maybeMessage;
699
+ }
700
+ return "Failed to upgrade account";
701
+ }
702
+ function isMetaMaskConnector(account) {
703
+ const connectorName = account.connector?.name?.toLowerCase() ?? "";
704
+ const connectorId = account.connector?.id?.toLowerCase() ?? "";
705
+ return connectorName.includes("metamask") || connectorId.includes("metamask");
706
+ }
223
707
  function useTransferPolling(intervalMs = 3e3) {
224
708
  const { apiBaseUrl } = useSwypeConfig();
225
709
  const { getAccessToken } = reactAuth.usePrivy();
@@ -270,31 +754,45 @@ function useTransferPolling(intervalMs = 3e3) {
270
754
  }
271
755
  function useAuthorizationExecutor() {
272
756
  const { apiBaseUrl } = useSwypeConfig();
273
- const { address, chainId: currentChainId, isConnected } = wagmi.useAccount();
757
+ const wagmiConfig2 = wagmi.useConfig();
274
758
  const { connectAsync, connectors } = wagmi.useConnect();
275
759
  const { switchChainAsync } = wagmi.useSwitchChain();
276
- const { signTypedDataAsync } = wagmi.useSignTypedData();
277
- const publicClient = wagmi.usePublicClient();
278
- const { data: walletClient } = wagmi.useWalletClient();
279
760
  const [executing, setExecuting] = react.useState(false);
280
761
  const [results, setResults] = react.useState([]);
281
762
  const [error, setError] = react.useState(null);
763
+ const [currentAction, setCurrentAction] = react.useState(null);
282
764
  const executingRef = react.useRef(false);
765
+ const [pendingSelectSource, setPendingSelectSource] = react.useState(null);
766
+ const selectSourceResolverRef = react.useRef(null);
767
+ const resolveSelectSource = react.useCallback((selection) => {
768
+ if (selectSourceResolverRef.current) {
769
+ selectSourceResolverRef.current(selection);
770
+ selectSourceResolverRef.current = null;
771
+ setPendingSelectSource(null);
772
+ }
773
+ }, []);
774
+ const sessionIdRef = react.useRef(null);
283
775
  const executeOpenProvider = react.useCallback(
284
776
  async (action) => {
285
777
  try {
286
- if (isConnected && address) {
287
- const hexChainId2 = currentChainId ? `0x${currentChainId.toString(16)}` : void 0;
778
+ const account = getAccount(wagmiConfig2);
779
+ if (account.isConnected && account.address) {
780
+ const hexChainId2 = account.chainId ? `0x${account.chainId.toString(16)}` : void 0;
288
781
  return {
289
782
  actionId: action.id,
290
783
  type: action.type,
291
784
  status: "success",
292
- message: `Connected. Account: ${address}, Chain: ${hexChainId2}`,
293
- data: { accounts: [address], chainId: hexChainId2 }
785
+ message: `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
786
+ data: { accounts: [account.address], chainId: hexChainId2 }
294
787
  };
295
788
  }
296
789
  const targetId = action.metadata?.wagmiConnectorId;
297
- const connector = targetId ? connectors.find((c) => c.id === targetId) ?? connectors[0] : connectors[0];
790
+ const metaMaskConnector = connectors.find((c) => {
791
+ const id = c.id.toLowerCase();
792
+ const name = c.name.toLowerCase();
793
+ return id.includes("metamask") || name.includes("metamask");
794
+ });
795
+ const connector = targetId ? connectors.find((c) => c.id === targetId) ?? metaMaskConnector ?? connectors[0] : metaMaskConnector ?? connectors[0];
298
796
  if (!connector) {
299
797
  return {
300
798
  actionId: action.id,
@@ -321,243 +819,231 @@ function useAuthorizationExecutor() {
321
819
  };
322
820
  }
323
821
  },
324
- [isConnected, address, currentChainId, connectors, connectAsync]
822
+ [wagmiConfig2, connectors, connectAsync]
325
823
  );
326
- const executeSwitchChain = react.useCallback(
824
+ const executeSelectSource = react.useCallback(
327
825
  async (action) => {
328
826
  try {
329
- const targetChainIdHex = action.metadata?.targetChainId;
330
- if (!targetChainIdHex) {
827
+ const options = action.metadata?.options;
828
+ const recommended = action.metadata?.recommended;
829
+ if (!options || options.length <= 1) {
830
+ const selection2 = recommended ?? { chainName: "Base", tokenSymbol: "USDC" };
331
831
  return {
332
832
  actionId: action.id,
333
833
  type: action.type,
334
- status: "error",
335
- message: "No targetChainId in action metadata."
834
+ status: "success",
835
+ message: `Auto-selected ${selection2.tokenSymbol} on ${selection2.chainName}.`,
836
+ data: {
837
+ selectedChainName: selection2.chainName,
838
+ selectedTokenSymbol: selection2.tokenSymbol
839
+ }
336
840
  };
337
841
  }
338
- const targetChainIdNum = parseInt(targetChainIdHex, 16);
339
- await switchChainAsync({ chainId: targetChainIdNum });
340
- const hexChainId = `0x${targetChainIdNum.toString(16)}`;
842
+ const selection = await new Promise((resolve) => {
843
+ selectSourceResolverRef.current = resolve;
844
+ setPendingSelectSource(action);
845
+ });
341
846
  return {
342
847
  actionId: action.id,
343
848
  type: action.type,
344
849
  status: "success",
345
- message: `Switched to chain ${hexChainId}.`,
346
- data: { chainId: hexChainId, switched: true }
850
+ message: `Selected ${selection.tokenSymbol} on ${selection.chainName}.`,
851
+ data: {
852
+ selectedChainName: selection.chainName,
853
+ selectedTokenSymbol: selection.tokenSymbol
854
+ }
347
855
  };
348
856
  } catch (err) {
349
857
  return {
350
858
  actionId: action.id,
351
859
  type: action.type,
352
860
  status: "error",
353
- message: err instanceof Error ? err.message : "Failed to switch chain"
861
+ message: err instanceof Error ? err.message : "Failed to select source"
354
862
  };
355
863
  }
356
864
  },
357
- [switchChainAsync]
865
+ []
358
866
  );
359
- const executeApprovePermit2 = react.useCallback(
867
+ const executeSwitchChain = react.useCallback(
360
868
  async (action) => {
361
869
  try {
362
- const tokenAddress = action.metadata?.tokenAddress;
363
- const permit2Address = action.metadata?.permit2Address;
364
- if (!tokenAddress || !permit2Address) {
870
+ const account = getAccount(wagmiConfig2);
871
+ const targetChainIdHex = action.metadata?.targetChainId;
872
+ if (!targetChainIdHex) {
365
873
  return {
366
874
  actionId: action.id,
367
875
  type: action.type,
368
876
  status: "error",
369
- message: "Missing tokenAddress or permit2Address in action metadata."
877
+ message: "No targetChainId in action metadata."
370
878
  };
371
879
  }
372
- if (!address) {
880
+ const targetChainIdNum = parseInt(targetChainIdHex, 16);
881
+ const hexChainId = `0x${targetChainIdNum.toString(16)}`;
882
+ if (account.chainId === targetChainIdNum) {
373
883
  return {
374
884
  actionId: action.id,
375
885
  type: action.type,
376
- status: "error",
377
- message: "Wallet not connected."
886
+ status: "success",
887
+ message: `Already on chain ${hexChainId}. Skipped.`,
888
+ data: { chainId: hexChainId, switched: false }
378
889
  };
379
890
  }
380
- const ERC20_ABI = [
381
- {
382
- name: "allowance",
383
- type: "function",
384
- stateMutability: "view",
385
- inputs: [
386
- { name: "owner", type: "address" },
387
- { name: "spender", type: "address" }
388
- ],
389
- outputs: [{ name: "", type: "uint256" }]
390
- },
391
- {
392
- name: "approve",
393
- type: "function",
394
- stateMutability: "nonpayable",
395
- inputs: [
396
- { name: "spender", type: "address" },
397
- { name: "amount", type: "uint256" }
891
+ await switchChainAsync({ chainId: targetChainIdNum });
892
+ return {
893
+ actionId: action.id,
894
+ type: action.type,
895
+ status: "success",
896
+ message: `Switched to chain ${hexChainId}.`,
897
+ data: { chainId: hexChainId, switched: true }
898
+ };
899
+ } catch (err) {
900
+ return {
901
+ actionId: action.id,
902
+ type: action.type,
903
+ status: "error",
904
+ message: err instanceof Error ? err.message : "Failed to switch chain"
905
+ };
906
+ }
907
+ },
908
+ [wagmiConfig2, switchChainAsync]
909
+ );
910
+ const executeRegisterPasskey = react.useCallback(
911
+ async (action) => {
912
+ try {
913
+ const account = getAccount(wagmiConfig2);
914
+ const challenge = new Uint8Array(32);
915
+ crypto.getRandomValues(challenge);
916
+ const credential = await navigator.credentials.create({
917
+ publicKey: {
918
+ challenge,
919
+ rp: {
920
+ name: "Swype",
921
+ id: window.location.hostname
922
+ },
923
+ user: {
924
+ id: new TextEncoder().encode(account.address ?? "user"),
925
+ name: account.address ?? "Swype User",
926
+ displayName: "Swype User"
927
+ },
928
+ pubKeyCredParams: [
929
+ { alg: -7, type: "public-key" },
930
+ // ES256 (P-256)
931
+ { alg: -257, type: "public-key" }
932
+ // RS256
398
933
  ],
399
- outputs: [{ name: "", type: "bool" }]
400
- }
401
- ];
402
- if (publicClient) {
403
- const currentAllowance = await publicClient.readContract({
404
- address: tokenAddress,
405
- abi: ERC20_ABI,
406
- functionName: "allowance",
407
- args: [address, permit2Address]
408
- });
409
- if (currentAllowance > 0n) {
410
- return {
411
- actionId: action.id,
412
- type: action.type,
413
- status: "success",
414
- message: `Permit2 already approved (allowance: ${currentAllowance.toString()}). Skipped.`,
415
- data: { skipped: true, existingAllowance: currentAllowance.toString() }
416
- };
934
+ authenticatorSelection: {
935
+ authenticatorAttachment: "platform",
936
+ residentKey: "preferred",
937
+ userVerification: "required"
938
+ },
939
+ timeout: 6e4
417
940
  }
418
- }
419
- if (!walletClient) {
941
+ });
942
+ if (!credential) {
420
943
  return {
421
944
  actionId: action.id,
422
945
  type: action.type,
423
946
  status: "error",
424
- message: "Wallet client not available."
947
+ message: "Passkey creation was cancelled."
425
948
  };
426
949
  }
427
- const MAX_UINT256 = 2n ** 256n - 1n;
428
- const txHash = await walletClient.writeContract({
429
- address: tokenAddress,
430
- abi: ERC20_ABI,
431
- functionName: "approve",
432
- args: [permit2Address, MAX_UINT256]
433
- });
434
- if (publicClient) {
435
- await publicClient.waitForTransactionReceipt({ hash: txHash });
436
- }
950
+ const response = credential.response;
951
+ const publicKeyBytes = response.getPublicKey?.();
952
+ const credentialId = btoa(
953
+ String.fromCharCode(...new Uint8Array(credential.rawId))
954
+ );
955
+ const publicKey = publicKeyBytes ? btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes))) : "";
437
956
  return {
438
957
  actionId: action.id,
439
958
  type: action.type,
440
959
  status: "success",
441
- message: `Permit2 approved. Tx: ${txHash}`,
442
- data: { txHash, approved: true }
960
+ message: "Passkey created successfully.",
961
+ data: {
962
+ credentialId,
963
+ publicKey
964
+ }
443
965
  };
444
966
  } catch (err) {
445
967
  return {
446
968
  actionId: action.id,
447
969
  type: action.type,
448
970
  status: "error",
449
- message: err instanceof Error ? err.message : "Failed to approve Permit2"
971
+ message: err instanceof Error ? err.message : "Failed to create passkey"
450
972
  };
451
973
  }
452
974
  },
453
- [address, publicClient, walletClient]
975
+ [wagmiConfig2]
454
976
  );
455
- const executeSignPermit2 = react.useCallback(
977
+ const executeUpgradeSmartAccount = react.useCallback(
456
978
  async (action) => {
457
979
  try {
458
- const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
459
- const PERMIT2_ALLOWANCE_ABI = [
460
- {
461
- name: "allowance",
462
- type: "function",
463
- stateMutability: "view",
464
- inputs: [
465
- { name: "owner", type: "address" },
466
- { name: "token", type: "address" },
467
- { name: "spender", type: "address" }
468
- ],
469
- outputs: [
470
- { name: "amount", type: "uint160" },
471
- { name: "expiration", type: "uint48" },
472
- { name: "nonce", type: "uint48" }
473
- ]
980
+ const account = getAccount(wagmiConfig2);
981
+ const walletClient = await waitForWalletClient(wagmiConfig2);
982
+ const sender = account.address ?? walletClient.account?.address;
983
+ if (!sender) {
984
+ throw new Error("Wallet account not available. Please connect your wallet.");
985
+ }
986
+ if (!isMetaMaskConnector(account)) {
987
+ throw new Error(
988
+ "EIP-7702 smart account upgrade requires a MetaMask-compatible wallet. Please reconnect using MetaMask and try again."
989
+ );
990
+ }
991
+ const metadata = action.metadata;
992
+ const { txHash, smartAccountAddress } = await submitUpgradeTransaction({
993
+ walletClient,
994
+ sender,
995
+ metadata
996
+ });
997
+ return {
998
+ actionId: action.id,
999
+ type: action.type,
1000
+ status: "success",
1001
+ message: "Account upgrade transaction submitted.",
1002
+ data: {
1003
+ txHash,
1004
+ smartAccountAddress
474
1005
  }
475
- ];
476
- const spenderAddress = action.metadata?.spenderAddress;
477
- const tokenAddress = action.metadata?.tokenAddress;
478
- const metadataChainId = action.metadata?.chainId;
479
- const metadataAmount = action.metadata?.amount;
480
- if (!spenderAddress || !tokenAddress) {
1006
+ };
1007
+ } catch (err) {
1008
+ console.error("Failed to upgrade account", err);
1009
+ const message = extractErrorMessage(err);
1010
+ const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
1011
+ return {
1012
+ actionId: action.id,
1013
+ type: action.type,
1014
+ status: "error",
1015
+ message: isRejected ? "You rejected the upgrade transaction. Please approve the transaction prompt in your wallet to continue." : message
1016
+ };
1017
+ }
1018
+ },
1019
+ [wagmiConfig2]
1020
+ );
1021
+ const executeGrantPermissions = react.useCallback(
1022
+ async (action) => {
1023
+ try {
1024
+ const walletClient = await waitForWalletClient(wagmiConfig2);
1025
+ const signingPayload = action.metadata?.signingPayload;
1026
+ const tokens = action.metadata?.tokens;
1027
+ if (!signingPayload) {
481
1028
  return {
482
1029
  actionId: action.id,
483
1030
  type: action.type,
484
1031
  status: "error",
485
- message: "Missing spenderAddress or tokenAddress in action metadata."
1032
+ message: "No signing payload in action metadata."
486
1033
  };
487
1034
  }
488
- if (publicClient && address && metadataAmount) {
489
- try {
490
- const [existingAmount, existingExpiration] = await publicClient.readContract({
491
- address: PERMIT2_ADDRESS,
492
- abi: PERMIT2_ALLOWANCE_ABI,
493
- functionName: "allowance",
494
- args: [address, tokenAddress, spenderAddress]
495
- });
496
- const requiredSmallestUnit = BigInt(
497
- Math.ceil(metadataAmount * 1e6)
498
- );
499
- const now = Math.floor(Date.now() / 1e3);
500
- if (existingAmount >= requiredSmallestUnit && existingExpiration > now) {
501
- return {
502
- actionId: action.id,
503
- type: action.type,
504
- status: "success",
505
- message: `Permit2 allowance already sufficient (${existingAmount.toString()}). Skipped.`,
506
- data: { skipped: true, existingAllowance: existingAmount.toString() }
507
- };
508
- }
509
- } catch {
510
- }
511
- }
512
- const metadataAllowance = action.metadata?.allowance;
513
- const permitAmount = metadataAllowance ? BigInt(Math.ceil(metadataAllowance * 1e6)) : metadataAmount ? BigInt(Math.ceil(metadataAmount * 1e6)) : BigInt(5e6);
514
- const permit2ChainId = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
515
- const nonce = Number(action.metadata?.nonce ?? 0);
516
- const sigDeadline = BigInt(Math.floor(Date.now() / 1e3) + 3600);
517
- const expiration = Math.floor(Date.now() / 1e3) + 30 * 24 * 60 * 60;
518
- const signature = await signTypedDataAsync({
519
- domain: {
520
- name: "Permit2",
521
- chainId: permit2ChainId,
522
- verifyingContract: PERMIT2_ADDRESS
523
- },
524
- types: {
525
- PermitSingle: [
526
- { name: "details", type: "PermitDetails" },
527
- { name: "spender", type: "address" },
528
- { name: "sigDeadline", type: "uint256" }
529
- ],
530
- PermitDetails: [
531
- { name: "token", type: "address" },
532
- { name: "amount", type: "uint160" },
533
- { name: "expiration", type: "uint48" },
534
- { name: "nonce", type: "uint48" }
535
- ]
536
- },
537
- primaryType: "PermitSingle",
538
- message: {
539
- details: {
540
- token: tokenAddress,
541
- amount: permitAmount,
542
- expiration,
543
- nonce
544
- },
545
- spender: spenderAddress,
546
- sigDeadline
547
- }
1035
+ const signature = await walletClient.signMessage({
1036
+ message: JSON.stringify(signingPayload)
548
1037
  });
1038
+ const tokenSummary = tokens?.map((t) => `${t.symbol} on ${t.chainName}`).join(", ") ?? "all tokens";
549
1039
  return {
550
1040
  actionId: action.id,
551
1041
  type: action.type,
552
1042
  status: "success",
553
- message: "Permit2 allowance signature obtained.",
1043
+ message: `Permissions granted for ${tokenSummary}.`,
554
1044
  data: {
555
- signature,
556
- signer: address,
557
- nonce: nonce.toString(),
558
- sigDeadline: sigDeadline.toString(),
559
- expiration: expiration.toString(),
560
- amount: permitAmount.toString()
1045
+ signedDelegation: signature,
1046
+ tokens
561
1047
  }
562
1048
  };
563
1049
  } catch (err) {
@@ -565,23 +1051,28 @@ function useAuthorizationExecutor() {
565
1051
  actionId: action.id,
566
1052
  type: action.type,
567
1053
  status: "error",
568
- message: err instanceof Error ? err.message : "Failed to sign Permit2"
1054
+ message: err instanceof Error ? err.message : "Failed to grant permissions"
569
1055
  };
570
1056
  }
571
1057
  },
572
- [address, currentChainId, publicClient, signTypedDataAsync]
1058
+ [wagmiConfig2]
573
1059
  );
574
1060
  const executeAction = react.useCallback(
575
1061
  async (action) => {
1062
+ setCurrentAction(action);
576
1063
  switch (action.type) {
577
1064
  case "OPEN_PROVIDER":
578
1065
  return executeOpenProvider(action);
1066
+ case "SELECT_SOURCE":
1067
+ return executeSelectSource(action);
579
1068
  case "SWITCH_CHAIN":
580
1069
  return executeSwitchChain(action);
581
- case "APPROVE_PERMIT_2":
582
- return executeApprovePermit2(action);
583
- case "SIGN_PERMIT2":
584
- return executeSignPermit2(action);
1070
+ case "REGISTER_PASSKEY":
1071
+ return executeRegisterPasskey(action);
1072
+ case "UPGRADE_SMART_ACCOUNT":
1073
+ return executeUpgradeSmartAccount(action);
1074
+ case "GRANT_PERMISSIONS":
1075
+ return executeGrantPermissions(action);
585
1076
  default:
586
1077
  return {
587
1078
  actionId: action.id,
@@ -591,18 +1082,18 @@ function useAuthorizationExecutor() {
591
1082
  };
592
1083
  }
593
1084
  },
594
- [executeOpenProvider, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
1085
+ [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeUpgradeSmartAccount, executeGrantPermissions]
595
1086
  );
596
1087
  const executeSession = react.useCallback(
597
1088
  async (transfer) => {
598
1089
  if (executingRef.current) return;
599
1090
  executingRef.current = true;
600
1091
  if (!transfer.authorizationSessions || transfer.authorizationSessions.length === 0) {
601
- setError("No authorization sessions available.");
602
1092
  executingRef.current = false;
603
- return;
1093
+ throw new Error("No authorization sessions available.");
604
1094
  }
605
1095
  const sessionId = transfer.authorizationSessions[0].id;
1096
+ sessionIdRef.current = sessionId;
606
1097
  setExecuting(true);
607
1098
  setError(null);
608
1099
  setResults([]);
@@ -611,6 +1102,18 @@ function useAuthorizationExecutor() {
611
1102
  const allResults = [];
612
1103
  const completedActionIds = /* @__PURE__ */ new Set();
613
1104
  let pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
1105
+ const ACTION_POLL_INTERVAL_MS = 500;
1106
+ const ACTION_POLL_MAX_RETRIES = 20;
1107
+ let actionPollRetries = 0;
1108
+ while (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED" && actionPollRetries < ACTION_POLL_MAX_RETRIES) {
1109
+ await new Promise((r) => setTimeout(r, ACTION_POLL_INTERVAL_MS));
1110
+ currentSession = await fetchAuthorizationSession(apiBaseUrl, sessionId);
1111
+ pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
1112
+ actionPollRetries++;
1113
+ }
1114
+ if (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED") {
1115
+ throw new Error("Authorization actions were not created in time. Please try again.");
1116
+ }
614
1117
  while (pendingActions.length > 0) {
615
1118
  const action = pendingActions[0];
616
1119
  if (completedActionIds.has(action.id)) break;
@@ -618,8 +1121,7 @@ function useAuthorizationExecutor() {
618
1121
  if (result.status === "error") {
619
1122
  allResults.push(result);
620
1123
  setResults([...allResults]);
621
- setError(result.message);
622
- break;
1124
+ throw new Error(result.message);
623
1125
  }
624
1126
  completedActionIds.add(action.id);
625
1127
  const updatedSession = await reportActionCompletion(
@@ -631,7 +1133,6 @@ function useAuthorizationExecutor() {
631
1133
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
632
1134
  if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
633
1135
  const chainResults = [result];
634
- let chainBroken = false;
635
1136
  while (pendingActions.length > 0) {
636
1137
  const nextAction = pendingActions[0];
637
1138
  const nextResult = await executeAction(nextAction);
@@ -639,9 +1140,7 @@ function useAuthorizationExecutor() {
639
1140
  chainResults.push(nextResult);
640
1141
  allResults.push(...chainResults);
641
1142
  setResults([...allResults]);
642
- setError(nextResult.message);
643
- chainBroken = true;
644
- break;
1143
+ throw new Error(nextResult.message);
645
1144
  }
646
1145
  completedActionIds.add(nextAction.id);
647
1146
  const nextSession = await reportActionCompletion(
@@ -653,7 +1152,6 @@ function useAuthorizationExecutor() {
653
1152
  chainResults.push(nextResult);
654
1153
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
655
1154
  }
656
- if (chainBroken) break;
657
1155
  allResults.push(...chainResults);
658
1156
  setResults([...allResults]);
659
1157
  continue;
@@ -662,15 +1160,113 @@ function useAuthorizationExecutor() {
662
1160
  setResults([...allResults]);
663
1161
  }
664
1162
  } catch (err) {
665
- setError(err instanceof Error ? err.message : "Authorization failed");
1163
+ const msg = err instanceof Error ? err.message : "Authorization failed";
1164
+ setError(msg);
1165
+ throw err;
666
1166
  } finally {
1167
+ setCurrentAction(null);
667
1168
  setExecuting(false);
668
1169
  executingRef.current = false;
669
1170
  }
670
1171
  },
671
1172
  [apiBaseUrl, executeAction]
672
1173
  );
673
- return { executing, results, error, executeSession };
1174
+ return {
1175
+ executing,
1176
+ results,
1177
+ error,
1178
+ currentAction,
1179
+ pendingSelectSource,
1180
+ resolveSelectSource,
1181
+ executeSession
1182
+ };
1183
+ }
1184
+ function useTransferSigning(pollIntervalMs = 2e3) {
1185
+ const { apiBaseUrl } = useSwypeConfig();
1186
+ const { getAccessToken } = reactAuth.usePrivy();
1187
+ const [signing, setSigning] = react.useState(false);
1188
+ const [signPayload, setSignPayload] = react.useState(null);
1189
+ const [error, setError] = react.useState(null);
1190
+ const signTransfer2 = react.useCallback(
1191
+ async (transferId) => {
1192
+ setSigning(true);
1193
+ setError(null);
1194
+ setSignPayload(null);
1195
+ try {
1196
+ const token = await getAccessToken();
1197
+ if (!token) {
1198
+ throw new Error("Could not get access token");
1199
+ }
1200
+ const MAX_POLLS = 60;
1201
+ let payload = null;
1202
+ for (let i = 0; i < MAX_POLLS; i++) {
1203
+ const transfer = await fetchTransfer(apiBaseUrl, token, transferId);
1204
+ if (transfer.signPayload) {
1205
+ payload = transfer.signPayload;
1206
+ setSignPayload(payload);
1207
+ break;
1208
+ }
1209
+ if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
1210
+ throw new Error(`Unexpected transfer status: ${transfer.status}`);
1211
+ }
1212
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1213
+ }
1214
+ if (!payload) {
1215
+ throw new Error("Timed out waiting for sign payload. Please try again.");
1216
+ }
1217
+ const userOpHashHex = payload.userOpHash;
1218
+ const hashBytes = new Uint8Array(
1219
+ (userOpHashHex.startsWith("0x") ? userOpHashHex.slice(2) : userOpHashHex).match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
1220
+ );
1221
+ const assertion = await navigator.credentials.get({
1222
+ publicKey: {
1223
+ challenge: hashBytes,
1224
+ rpId: window.location.hostname,
1225
+ userVerification: "required",
1226
+ timeout: 6e4
1227
+ }
1228
+ });
1229
+ if (!assertion) {
1230
+ throw new Error("Passkey authentication was cancelled.");
1231
+ }
1232
+ const response = assertion.response;
1233
+ const signature = btoa(
1234
+ String.fromCharCode(...new Uint8Array(response.signature))
1235
+ );
1236
+ const authenticatorData = btoa(
1237
+ String.fromCharCode(
1238
+ ...new Uint8Array(response.authenticatorData)
1239
+ )
1240
+ );
1241
+ const clientDataJSON = btoa(
1242
+ String.fromCharCode(
1243
+ ...new Uint8Array(response.clientDataJSON)
1244
+ )
1245
+ );
1246
+ const signedUserOp = {
1247
+ ...payload.userOp,
1248
+ signature,
1249
+ authenticatorData,
1250
+ clientDataJSON
1251
+ };
1252
+ const updatedTransfer = await signTransfer(
1253
+ apiBaseUrl,
1254
+ token,
1255
+ transferId,
1256
+ signedUserOp
1257
+ );
1258
+ return updatedTransfer;
1259
+ } catch (err) {
1260
+ const msg = err instanceof Error ? err.message : "Failed to sign transfer";
1261
+ setError(msg);
1262
+ throw err;
1263
+ } finally {
1264
+ setSigning(false);
1265
+ }
1266
+ },
1267
+ [apiBaseUrl, getAccessToken, pollIntervalMs]
1268
+ );
1269
+ return { signing, signPayload, error, signTransfer: signTransfer2 };
674
1270
  }
675
1271
  function Spinner({ size = 40, label }) {
676
1272
  const { tokens } = useSwypeConfig();
@@ -788,163 +1384,232 @@ function ProviderCard({ provider, selected, onClick }) {
788
1384
  }
789
1385
  );
790
1386
  }
791
- function AccountWalletSelector({
1387
+ function AccountDropdown({
792
1388
  accounts,
793
- selection,
794
- onSelect
1389
+ selectedAccountId,
1390
+ onSelect,
1391
+ selectedWalletId,
1392
+ onWalletSelect
795
1393
  }) {
796
1394
  const { tokens } = useSwypeConfig();
797
- const [expandedAccountId, setExpandedAccountId] = react.useState(null);
798
- const activeAccounts = accounts.filter(
799
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
1395
+ const [open, setOpen] = react.useState(false);
1396
+ const containerRef = react.useRef(null);
1397
+ const selected = accounts.find((a) => a.id === selectedAccountId);
1398
+ const selectedWallet = selected?.wallets.find(
1399
+ (w) => w.id === selectedWalletId
800
1400
  );
801
- if (activeAccounts.length === 0) return null;
802
- const handleAccountClick = (accountId) => {
803
- if (expandedAccountId === accountId) {
804
- setExpandedAccountId(null);
805
- } else {
806
- setExpandedAccountId(accountId);
807
- }
808
- };
809
- const handleWalletClick = (account, wallet) => {
810
- const isAlreadySelected = selection?.accountId === account.id && selection?.walletId === wallet.id;
811
- if (isAlreadySelected) {
812
- onSelect(null);
813
- } else {
814
- onSelect({
815
- accountId: account.id,
816
- walletId: wallet.id,
817
- accountName: account.name,
818
- walletName: wallet.name,
819
- chainName: wallet.chain.name
820
- });
821
- }
822
- };
823
- const formatBalance = (wallet) => {
824
- const parts = [];
825
- for (const src of wallet.sources) {
826
- if (src.token.status === "ACTIVE") {
827
- parts.push(
828
- `${src.balance.available.amount.toFixed(2)} ${src.balance.available.currency} (${src.token.symbol})`
829
- );
1401
+ react.useEffect(() => {
1402
+ if (!open) return;
1403
+ const handleClick = (e) => {
1404
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
1405
+ setOpen(false);
830
1406
  }
831
- }
832
- if (parts.length === 0 && wallet.balance) {
833
- return `${wallet.balance.available.amount.toFixed(2)} ${wallet.balance.available.currency}`;
834
- }
835
- return parts.join(", ") || "No balance";
836
- };
837
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: "100%" }, children: [
838
- /* @__PURE__ */ jsxRuntime.jsx(
839
- "label",
1407
+ };
1408
+ document.addEventListener("mousedown", handleClick);
1409
+ return () => document.removeEventListener("mousedown", handleClick);
1410
+ }, [open]);
1411
+ if (accounts.length === 0) return null;
1412
+ const hasMultipleChoices = accounts.length > 1 || accounts.length === 1 && onWalletSelect && accounts[0].wallets.filter((w) => w.balance.available.amount > 0).length > 1;
1413
+ if (!hasMultipleChoices) {
1414
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1415
+ "div",
840
1416
  {
841
1417
  style: {
842
- display: "block",
843
- fontSize: "0.8rem",
844
- color: tokens.textMuted,
845
- marginBottom: "8px",
846
- fontWeight: 500,
847
- textTransform: "uppercase",
848
- letterSpacing: "0.05em"
1418
+ display: "flex",
1419
+ alignItems: "center",
1420
+ gap: "6px",
1421
+ fontSize: "0.85rem",
1422
+ color: tokens.textSecondary
849
1423
  },
850
- children: "Or use a connected account"
1424
+ children: [
1425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: accounts[0].name }),
1426
+ (selectedWallet ?? accounts[0].wallets[0]) && /* @__PURE__ */ jsxRuntime.jsx(
1427
+ "span",
1428
+ {
1429
+ style: {
1430
+ fontSize: "0.75rem",
1431
+ color: tokens.textMuted,
1432
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1433
+ },
1434
+ children: selectedWallet ? `${selectedWallet.chain.name} \xB7 ${selectedWallet.name}` : accounts[0].wallets[0]?.name
1435
+ }
1436
+ )
1437
+ ]
851
1438
  }
852
- ),
853
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: activeAccounts.map((account) => {
854
- const isExpanded = expandedAccountId === account.id;
855
- const activeWallets = account.wallets.filter((w) => w.status === "ACTIVE");
856
- const hasSelection = selection?.accountId === account.id;
857
- return /* @__PURE__ */ jsxRuntime.jsxs(
858
- "div",
859
- {
860
- style: {
861
- border: `1px solid ${hasSelection ? tokens.accent : tokens.border}`,
862
- borderRadius: tokens.radius,
863
- overflow: "hidden",
864
- transition: "border-color 0.15s ease"
865
- },
866
- children: [
1439
+ );
1440
+ }
1441
+ const getAccountBalance = (account) => {
1442
+ let total = 0;
1443
+ for (const w of account.wallets) {
1444
+ total += w.balance.available.amount;
1445
+ }
1446
+ return total > 0 ? `$${total.toFixed(2)}` : "";
1447
+ };
1448
+ const hasActiveWallet = (account) => account.wallets.some((w) => w.status === "ACTIVE");
1449
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, style: { position: "relative" }, children: [
1450
+ /* @__PURE__ */ jsxRuntime.jsxs(
1451
+ "button",
1452
+ {
1453
+ onClick: () => setOpen(!open),
1454
+ style: {
1455
+ display: "flex",
1456
+ alignItems: "center",
1457
+ gap: "6px",
1458
+ background: "transparent",
1459
+ border: "none",
1460
+ cursor: "pointer",
1461
+ padding: "4px 0",
1462
+ color: tokens.textSecondary,
1463
+ fontFamily: "inherit",
1464
+ fontSize: "0.85rem",
1465
+ outline: "none"
1466
+ },
1467
+ children: [
1468
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500, color: tokens.text }, children: selected?.name ?? "Select account" }),
1469
+ (selectedWallet ?? selected?.wallets?.[0]) && /* @__PURE__ */ jsxRuntime.jsx(
1470
+ "span",
1471
+ {
1472
+ style: {
1473
+ fontSize: "0.75rem",
1474
+ color: tokens.textMuted,
1475
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1476
+ },
1477
+ children: selectedWallet ? `${selectedWallet.chain.name} \xB7 ${selectedWallet.name}` : selected.wallets[0].name
1478
+ }
1479
+ ),
1480
+ /* @__PURE__ */ jsxRuntime.jsx(
1481
+ "svg",
1482
+ {
1483
+ width: "12",
1484
+ height: "12",
1485
+ viewBox: "0 0 24 24",
1486
+ fill: "none",
1487
+ style: {
1488
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
1489
+ transition: "transform 0.15s ease",
1490
+ flexShrink: 0
1491
+ },
1492
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1493
+ "path",
1494
+ {
1495
+ d: "M7 10l5 5 5-5",
1496
+ stroke: tokens.textMuted,
1497
+ strokeWidth: "2",
1498
+ strokeLinecap: "round",
1499
+ strokeLinejoin: "round"
1500
+ }
1501
+ )
1502
+ }
1503
+ )
1504
+ ]
1505
+ }
1506
+ ),
1507
+ open && /* @__PURE__ */ jsxRuntime.jsx(
1508
+ "div",
1509
+ {
1510
+ style: {
1511
+ position: "absolute",
1512
+ top: "100%",
1513
+ left: 0,
1514
+ right: 0,
1515
+ marginTop: "4px",
1516
+ background: tokens.bgCard,
1517
+ border: `1px solid ${tokens.border}`,
1518
+ borderRadius: tokens.radius,
1519
+ boxShadow: tokens.shadowLg,
1520
+ zIndex: 50,
1521
+ overflow: "hidden",
1522
+ minWidth: "220px"
1523
+ },
1524
+ children: accounts.map((account) => {
1525
+ const isAcctSelected = account.id === selectedAccountId;
1526
+ const balance = getAccountBalance(account);
1527
+ const active = hasActiveWallet(account);
1528
+ const walletsWithBalance = account.wallets.filter(
1529
+ (w) => w.balance.available.amount > 0
1530
+ );
1531
+ const showWallets = onWalletSelect && walletsWithBalance.length > 0;
1532
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
867
1533
  /* @__PURE__ */ jsxRuntime.jsxs(
868
1534
  "button",
869
1535
  {
870
- onClick: () => handleAccountClick(account.id),
1536
+ onClick: () => {
1537
+ onSelect(account.id);
1538
+ if (!showWallets) setOpen(false);
1539
+ },
871
1540
  style: {
872
- width: "100%",
873
1541
  display: "flex",
874
1542
  alignItems: "center",
875
1543
  justifyContent: "space-between",
876
- padding: "12px 14px",
877
- background: hasSelection ? tokens.accent + "10" : tokens.bgInput,
1544
+ width: "100%",
1545
+ padding: "10px 14px",
1546
+ background: isAcctSelected && !selectedWalletId ? tokens.accent + "12" : "transparent",
878
1547
  border: "none",
1548
+ borderBottom: showWallets ? "none" : `1px solid ${tokens.border}`,
879
1549
  cursor: "pointer",
880
1550
  color: tokens.text,
881
1551
  fontFamily: "inherit",
882
- fontSize: "0.9rem",
883
- fontWeight: 500,
1552
+ fontSize: "0.85rem",
884
1553
  textAlign: "left",
885
1554
  outline: "none",
886
- transition: "background 0.15s ease"
1555
+ transition: "background 0.1s ease"
887
1556
  },
888
1557
  children: [
889
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [
890
- /* @__PURE__ */ jsxRuntime.jsx(
1558
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
1559
+ /* @__PURE__ */ jsxRuntime.jsxs(
891
1560
  "div",
892
1561
  {
893
1562
  style: {
894
- width: 28,
895
- height: 28,
896
- borderRadius: "6px",
897
- background: tokens.accent + "25",
898
1563
  display: "flex",
899
1564
  alignItems: "center",
900
- justifyContent: "center",
901
- fontSize: "0.75rem",
902
- fontWeight: 700,
903
- color: tokens.accent,
904
- flexShrink: 0
1565
+ gap: "6px"
905
1566
  },
906
- children: account.name.charAt(0).toUpperCase()
1567
+ children: [
1568
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: account.name }),
1569
+ active && /* @__PURE__ */ jsxRuntime.jsx(
1570
+ "span",
1571
+ {
1572
+ style: {
1573
+ fontSize: "0.6rem",
1574
+ fontWeight: 600,
1575
+ color: tokens.success,
1576
+ background: tokens.successBg,
1577
+ padding: "1px 5px",
1578
+ borderRadius: "3px",
1579
+ textTransform: "uppercase",
1580
+ letterSpacing: "0.03em"
1581
+ },
1582
+ children: "Active"
1583
+ }
1584
+ )
1585
+ ]
907
1586
  }
908
1587
  ),
909
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
910
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: account.name }),
911
- /* @__PURE__ */ jsxRuntime.jsxs(
912
- "div",
913
- {
914
- style: {
915
- fontSize: "0.75rem",
916
- color: tokens.textMuted,
917
- marginTop: "2px"
918
- },
919
- children: [
920
- activeWallets.length,
921
- " wallet",
922
- activeWallets.length !== 1 ? "s" : ""
923
- ]
924
- }
925
- )
926
- ] })
1588
+ balance && /* @__PURE__ */ jsxRuntime.jsx(
1589
+ "div",
1590
+ {
1591
+ style: {
1592
+ fontSize: "0.75rem",
1593
+ color: tokens.textMuted,
1594
+ marginTop: "2px"
1595
+ },
1596
+ children: balance
1597
+ }
1598
+ )
927
1599
  ] }),
928
- /* @__PURE__ */ jsxRuntime.jsx(
1600
+ isAcctSelected && !selectedWalletId && /* @__PURE__ */ jsxRuntime.jsx(
929
1601
  "svg",
930
1602
  {
931
1603
  width: "14",
932
1604
  height: "14",
933
1605
  viewBox: "0 0 24 24",
934
1606
  fill: "none",
935
- style: {
936
- transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
937
- transition: "transform 0.2s ease",
938
- flexShrink: 0
939
- },
1607
+ style: { flexShrink: 0, marginLeft: "8px" },
940
1608
  children: /* @__PURE__ */ jsxRuntime.jsx(
941
1609
  "path",
942
1610
  {
943
- d: "M7 10l5 5 5-5",
944
- stroke: tokens.textMuted,
945
- strokeWidth: "2",
946
- strokeLinecap: "round",
947
- strokeLinejoin: "round"
1611
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1612
+ fill: tokens.accent
948
1613
  }
949
1614
  )
950
1615
  }
@@ -952,128 +1617,383 @@ function AccountWalletSelector({
952
1617
  ]
953
1618
  }
954
1619
  ),
955
- isExpanded && /* @__PURE__ */ jsxRuntime.jsx(
956
- "div",
957
- {
958
- style: {
959
- borderTop: `1px solid ${tokens.border}`,
960
- background: tokens.bgCard
961
- },
962
- children: activeWallets.map((wallet) => {
963
- const isSelected = selection?.walletId === wallet.id;
964
- return /* @__PURE__ */ jsxRuntime.jsxs(
965
- "button",
966
- {
967
- onClick: () => handleWalletClick(account, wallet),
968
- style: {
969
- width: "100%",
970
- display: "flex",
971
- alignItems: "center",
972
- justifyContent: "space-between",
973
- padding: "10px 14px 10px 24px",
974
- background: isSelected ? tokens.accent + "12" : "transparent",
975
- border: "none",
976
- borderBottom: `1px solid ${tokens.border}`,
977
- cursor: "pointer",
978
- color: tokens.text,
979
- fontFamily: "inherit",
980
- fontSize: "0.85rem",
981
- textAlign: "left",
982
- outline: "none",
983
- transition: "background 0.1s ease"
984
- },
985
- children: [
986
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
987
- /* @__PURE__ */ jsxRuntime.jsxs(
988
- "div",
1620
+ showWallets && walletsWithBalance.map((wallet, wIdx) => {
1621
+ const isWalletSelected = isAcctSelected && wallet.id === selectedWalletId;
1622
+ const walletBal = wallet.balance.available.amount > 0 ? `$${wallet.balance.available.amount.toFixed(2)}` : "";
1623
+ const isLastWallet = wIdx === walletsWithBalance.length - 1;
1624
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1625
+ "button",
1626
+ {
1627
+ onClick: () => {
1628
+ onWalletSelect(account.id, wallet.id);
1629
+ setOpen(false);
1630
+ },
1631
+ style: {
1632
+ display: "flex",
1633
+ alignItems: "center",
1634
+ justifyContent: "space-between",
1635
+ width: "100%",
1636
+ padding: "8px 14px 8px 28px",
1637
+ background: isWalletSelected ? tokens.accent + "12" : "transparent",
1638
+ border: "none",
1639
+ borderBottom: isLastWallet ? `1px solid ${tokens.border}` : "none",
1640
+ cursor: "pointer",
1641
+ color: tokens.text,
1642
+ fontFamily: "inherit",
1643
+ fontSize: "0.8rem",
1644
+ textAlign: "left",
1645
+ outline: "none",
1646
+ transition: "background 0.1s ease"
1647
+ },
1648
+ children: [
1649
+ /* @__PURE__ */ jsxRuntime.jsxs(
1650
+ "div",
1651
+ {
1652
+ style: {
1653
+ display: "flex",
1654
+ alignItems: "center",
1655
+ gap: "6px",
1656
+ minWidth: 0,
1657
+ flex: 1
1658
+ },
1659
+ children: [
1660
+ /* @__PURE__ */ jsxRuntime.jsx(
1661
+ "span",
989
1662
  {
990
1663
  style: {
991
- display: "flex",
992
- alignItems: "center",
993
- gap: "6px"
1664
+ fontWeight: 500,
1665
+ fontSize: "0.8rem"
994
1666
  },
995
- children: [
996
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: wallet.name }),
997
- /* @__PURE__ */ jsxRuntime.jsx(
998
- "span",
999
- {
1000
- style: {
1001
- fontSize: "0.7rem",
1002
- color: tokens.textMuted,
1003
- background: tokens.bgHover,
1004
- padding: "1px 6px",
1005
- borderRadius: "4px"
1006
- },
1007
- children: wallet.chain.name
1008
- }
1009
- )
1010
- ]
1667
+ children: wallet.chain.name
1011
1668
  }
1012
1669
  ),
1013
1670
  /* @__PURE__ */ jsxRuntime.jsx(
1014
- "div",
1671
+ "span",
1672
+ {
1673
+ style: {
1674
+ fontSize: "0.7rem",
1675
+ color: tokens.textMuted,
1676
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1677
+ },
1678
+ children: wallet.name
1679
+ }
1680
+ ),
1681
+ walletBal && /* @__PURE__ */ jsxRuntime.jsx(
1682
+ "span",
1015
1683
  {
1016
1684
  style: {
1017
- fontSize: "0.75rem",
1018
- color: tokens.textSecondary,
1019
- marginTop: "3px"
1685
+ fontSize: "0.7rem",
1686
+ color: tokens.textMuted,
1687
+ marginLeft: "auto"
1020
1688
  },
1021
- children: formatBalance(wallet)
1689
+ children: walletBal
1022
1690
  }
1023
1691
  )
1024
- ] }),
1025
- isSelected && /* @__PURE__ */ jsxRuntime.jsx(
1026
- "svg",
1692
+ ]
1693
+ }
1694
+ ),
1695
+ isWalletSelected && /* @__PURE__ */ jsxRuntime.jsx(
1696
+ "svg",
1697
+ {
1698
+ width: "12",
1699
+ height: "12",
1700
+ viewBox: "0 0 24 24",
1701
+ fill: "none",
1702
+ style: {
1703
+ flexShrink: 0,
1704
+ marginLeft: "8px"
1705
+ },
1706
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1707
+ "path",
1027
1708
  {
1028
- width: "16",
1029
- height: "16",
1030
- viewBox: "0 0 24 24",
1031
- fill: "none",
1032
- style: { flexShrink: 0, marginLeft: "8px" },
1033
- children: /* @__PURE__ */ jsxRuntime.jsx(
1034
- "path",
1035
- {
1036
- d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1037
- fill: tokens.accent
1038
- }
1039
- )
1709
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1710
+ fill: tokens.accent
1040
1711
  }
1041
1712
  )
1042
- ]
1043
- },
1044
- wallet.id
1045
- );
1046
- })
1047
- }
1048
- )
1049
- ]
1050
- },
1051
- account.id
1052
- );
1053
- }) }),
1054
- selection && /* @__PURE__ */ jsxRuntime.jsxs(
1055
- "div",
1713
+ }
1714
+ )
1715
+ ]
1716
+ },
1717
+ wallet.id
1718
+ );
1719
+ })
1720
+ ] }, account.id);
1721
+ })
1722
+ }
1723
+ )
1724
+ ] });
1725
+ }
1726
+ var ASSETS = ["USDC", "USDT"];
1727
+ function AdvancedSettings({
1728
+ settings,
1729
+ onChange,
1730
+ chains,
1731
+ providers,
1732
+ onConnectNewAccount,
1733
+ connectingNewAccount
1734
+ }) {
1735
+ const { tokens } = useSwypeConfig();
1736
+ const [open, setOpen] = react.useState(false);
1737
+ const [showProviders, setShowProviders] = react.useState(false);
1738
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "12px" }, children: [
1739
+ /* @__PURE__ */ jsxRuntime.jsxs(
1740
+ "button",
1056
1741
  {
1742
+ onClick: () => setOpen(!open),
1057
1743
  style: {
1058
- marginTop: "8px",
1059
- fontSize: "0.8rem",
1060
- color: tokens.accent,
1061
1744
  display: "flex",
1062
1745
  alignItems: "center",
1063
- gap: "6px"
1746
+ gap: "6px",
1747
+ background: "transparent",
1748
+ border: "none",
1749
+ cursor: "pointer",
1750
+ padding: "4px 0",
1751
+ color: tokens.textMuted,
1752
+ fontFamily: "inherit",
1753
+ fontSize: "0.8rem",
1754
+ fontWeight: 500,
1755
+ outline: "none",
1756
+ letterSpacing: "0.02em"
1064
1757
  },
1065
1758
  children: [
1066
- /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
1067
- "path",
1759
+ /* @__PURE__ */ jsxRuntime.jsx(
1760
+ "svg",
1068
1761
  {
1069
- d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1070
- fill: tokens.accent
1762
+ width: "10",
1763
+ height: "10",
1764
+ viewBox: "0 0 24 24",
1765
+ fill: "none",
1766
+ style: {
1767
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
1768
+ transition: "transform 0.15s ease"
1769
+ },
1770
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1771
+ "path",
1772
+ {
1773
+ d: "M7 10l5 5 5-5",
1774
+ stroke: tokens.textMuted,
1775
+ strokeWidth: "2.5",
1776
+ strokeLinecap: "round",
1777
+ strokeLinejoin: "round"
1778
+ }
1779
+ )
1780
+ }
1781
+ ),
1782
+ "Advanced options"
1783
+ ]
1784
+ }
1785
+ ),
1786
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
1787
+ "div",
1788
+ {
1789
+ style: {
1790
+ marginTop: "10px",
1791
+ padding: "14px",
1792
+ background: tokens.bgInput,
1793
+ borderRadius: tokens.radius,
1794
+ border: `1px solid ${tokens.border}`
1795
+ },
1796
+ children: [
1797
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "14px" }, children: [
1798
+ /* @__PURE__ */ jsxRuntime.jsx(
1799
+ "label",
1800
+ {
1801
+ style: {
1802
+ display: "block",
1803
+ fontSize: "0.7rem",
1804
+ fontWeight: 600,
1805
+ color: tokens.textMuted,
1806
+ textTransform: "uppercase",
1807
+ letterSpacing: "0.05em",
1808
+ marginBottom: "6px"
1809
+ },
1810
+ children: "Asset"
1811
+ }
1812
+ ),
1813
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: "6px" }, children: ASSETS.map((asset) => {
1814
+ const isSelected = settings.asset === asset;
1815
+ return /* @__PURE__ */ jsxRuntime.jsx(
1816
+ "button",
1817
+ {
1818
+ onClick: () => onChange({
1819
+ ...settings,
1820
+ asset: isSelected ? null : asset
1821
+ }),
1822
+ style: {
1823
+ padding: "6px 14px",
1824
+ fontSize: "0.8rem",
1825
+ fontWeight: 600,
1826
+ fontFamily: "inherit",
1827
+ borderRadius: "6px",
1828
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1829
+ background: isSelected ? tokens.accent + "18" : "transparent",
1830
+ color: isSelected ? tokens.accent : tokens.text,
1831
+ cursor: "pointer",
1832
+ outline: "none",
1833
+ transition: "all 0.12s ease"
1834
+ },
1835
+ children: asset
1836
+ },
1837
+ asset
1838
+ );
1839
+ }) })
1840
+ ] }),
1841
+ chains.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "14px" }, children: [
1842
+ /* @__PURE__ */ jsxRuntime.jsx(
1843
+ "label",
1844
+ {
1845
+ style: {
1846
+ display: "block",
1847
+ fontSize: "0.7rem",
1848
+ fontWeight: 600,
1849
+ color: tokens.textMuted,
1850
+ textTransform: "uppercase",
1851
+ letterSpacing: "0.05em",
1852
+ marginBottom: "6px"
1853
+ },
1854
+ children: "Chain"
1855
+ }
1856
+ ),
1857
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: chains.map((chain) => {
1858
+ const isSelected = settings.chain === chain.name;
1859
+ return /* @__PURE__ */ jsxRuntime.jsx(
1860
+ "button",
1861
+ {
1862
+ onClick: () => onChange({
1863
+ ...settings,
1864
+ chain: isSelected ? null : chain.name
1865
+ }),
1866
+ style: {
1867
+ padding: "6px 14px",
1868
+ fontSize: "0.8rem",
1869
+ fontWeight: 600,
1870
+ fontFamily: "inherit",
1871
+ borderRadius: "6px",
1872
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1873
+ background: isSelected ? tokens.accent + "18" : "transparent",
1874
+ color: isSelected ? tokens.accent : tokens.text,
1875
+ cursor: "pointer",
1876
+ outline: "none",
1877
+ transition: "all 0.12s ease"
1878
+ },
1879
+ children: chain.name
1880
+ },
1881
+ chain.id
1882
+ );
1883
+ }) })
1884
+ ] }),
1885
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: !showProviders ? /* @__PURE__ */ jsxRuntime.jsxs(
1886
+ "button",
1887
+ {
1888
+ onClick: () => setShowProviders(true),
1889
+ disabled: connectingNewAccount,
1890
+ style: {
1891
+ display: "flex",
1892
+ alignItems: "center",
1893
+ gap: "6px",
1894
+ background: "transparent",
1895
+ border: `1px dashed ${tokens.border}`,
1896
+ borderRadius: tokens.radius,
1897
+ padding: "10px 14px",
1898
+ width: "100%",
1899
+ cursor: connectingNewAccount ? "not-allowed" : "pointer",
1900
+ color: tokens.textSecondary,
1901
+ fontFamily: "inherit",
1902
+ fontSize: "0.825rem",
1903
+ fontWeight: 500,
1904
+ outline: "none",
1905
+ opacity: connectingNewAccount ? 0.5 : 1,
1906
+ transition: "opacity 0.1s ease"
1907
+ },
1908
+ children: [
1909
+ /* @__PURE__ */ jsxRuntime.jsx(
1910
+ "svg",
1911
+ {
1912
+ width: "14",
1913
+ height: "14",
1914
+ viewBox: "0 0 24 24",
1915
+ fill: "none",
1916
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1917
+ "path",
1918
+ {
1919
+ d: "M12 5v14M5 12h14",
1920
+ stroke: tokens.textMuted,
1921
+ strokeWidth: "2",
1922
+ strokeLinecap: "round"
1923
+ }
1924
+ )
1925
+ }
1926
+ ),
1927
+ "Connect new account"
1928
+ ]
1071
1929
  }
1072
- ) }),
1073
- "Using ",
1074
- selection.walletName,
1075
- " on ",
1076
- selection.chainName
1930
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1931
+ /* @__PURE__ */ jsxRuntime.jsxs(
1932
+ "div",
1933
+ {
1934
+ style: {
1935
+ display: "flex",
1936
+ alignItems: "center",
1937
+ justifyContent: "space-between",
1938
+ marginBottom: "8px"
1939
+ },
1940
+ children: [
1941
+ /* @__PURE__ */ jsxRuntime.jsx(
1942
+ "label",
1943
+ {
1944
+ style: {
1945
+ fontSize: "0.7rem",
1946
+ fontWeight: 600,
1947
+ color: tokens.textMuted,
1948
+ textTransform: "uppercase",
1949
+ letterSpacing: "0.05em"
1950
+ },
1951
+ children: "Select provider"
1952
+ }
1953
+ ),
1954
+ /* @__PURE__ */ jsxRuntime.jsx(
1955
+ "button",
1956
+ {
1957
+ onClick: () => setShowProviders(false),
1958
+ style: {
1959
+ background: "transparent",
1960
+ border: "none",
1961
+ cursor: "pointer",
1962
+ color: tokens.textMuted,
1963
+ fontSize: "0.75rem",
1964
+ fontFamily: "inherit",
1965
+ outline: "none",
1966
+ padding: "2px 4px"
1967
+ },
1968
+ children: "Cancel"
1969
+ }
1970
+ )
1971
+ ]
1972
+ }
1973
+ ),
1974
+ /* @__PURE__ */ jsxRuntime.jsx(
1975
+ "div",
1976
+ {
1977
+ style: {
1978
+ display: "flex",
1979
+ flexDirection: "column",
1980
+ gap: "6px"
1981
+ },
1982
+ children: providers.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
1983
+ ProviderCard,
1984
+ {
1985
+ provider: p,
1986
+ selected: false,
1987
+ onClick: () => {
1988
+ onConnectNewAccount(p.id);
1989
+ setShowProviders(false);
1990
+ }
1991
+ },
1992
+ p.id
1993
+ ))
1994
+ }
1995
+ )
1996
+ ] }) })
1077
1997
  ]
1078
1998
  }
1079
1999
  )
@@ -1085,69 +2005,135 @@ function isMobile() {
1085
2005
  navigator.userAgent
1086
2006
  );
1087
2007
  }
2008
+ function computeSmartDefaults(accts, transferAmount) {
2009
+ if (accts.length === 0) return null;
2010
+ for (const acct of accts) {
2011
+ for (const wallet of acct.wallets) {
2012
+ if (wallet.status === "ACTIVE") {
2013
+ const bestSource = wallet.sources.find(
2014
+ (s) => s.balance.available.amount >= transferAmount
2015
+ );
2016
+ if (bestSource) {
2017
+ return { accountId: acct.id, walletId: wallet.id };
2018
+ }
2019
+ }
2020
+ }
2021
+ }
2022
+ let bestAccount = null;
2023
+ let bestWallet = null;
2024
+ let bestBalance = -1;
2025
+ for (const acct of accts) {
2026
+ for (const wallet of acct.wallets) {
2027
+ const walletBal = wallet.balance.available.amount;
2028
+ if (walletBal > bestBalance) {
2029
+ bestBalance = walletBal;
2030
+ bestAccount = acct;
2031
+ bestWallet = wallet;
2032
+ }
2033
+ }
2034
+ }
2035
+ if (bestAccount) {
2036
+ return {
2037
+ accountId: bestAccount.id,
2038
+ walletId: bestWallet?.id ?? null
2039
+ };
2040
+ }
2041
+ return { accountId: accts[0].id, walletId: null };
2042
+ }
1088
2043
  function SwypePayment({
1089
2044
  destination,
1090
2045
  onComplete,
1091
2046
  onError
1092
2047
  }) {
1093
- const { apiBaseUrl, tokens } = useSwypeConfig();
2048
+ const { apiBaseUrl, tokens, depositAmount } = useSwypeConfig();
1094
2049
  const { ready, authenticated, login, getAccessToken } = reactAuth.usePrivy();
1095
2050
  const [step, setStep] = react.useState("login");
1096
2051
  const [error, setError] = react.useState(null);
1097
2052
  const [providers, setProviders] = react.useState([]);
1098
2053
  const [accounts, setAccounts] = react.useState([]);
2054
+ const [chains, setChains] = react.useState([]);
1099
2055
  const [loadingData, setLoadingData] = react.useState(false);
1100
- const [selectedProviderId, setSelectedProviderId] = react.useState(
1101
- null
2056
+ const [selectedAccountId, setSelectedAccountId] = react.useState(null);
2057
+ const [selectedWalletId, setSelectedWalletId] = react.useState(null);
2058
+ const [selectedProviderId, setSelectedProviderId] = react.useState(null);
2059
+ const [connectingNewAccount, setConnectingNewAccount] = react.useState(false);
2060
+ const [amount, setAmount] = react.useState(
2061
+ depositAmount != null ? depositAmount.toString() : ""
1102
2062
  );
1103
- const [walletSelection, setWalletSelection] = react.useState(null);
1104
- const [amount, setAmount] = react.useState("0.10");
1105
- const [topUpBalance, setTopUpBalance] = react.useState("");
2063
+ const [advancedSettings, setAdvancedSettings] = react.useState({
2064
+ asset: null,
2065
+ chain: null
2066
+ });
1106
2067
  const [transfer, setTransfer] = react.useState(null);
1107
2068
  const [creatingTransfer, setCreatingTransfer] = react.useState(false);
1108
2069
  const [mobileFlow, setMobileFlow] = react.useState(false);
1109
2070
  const pollingTransferIdRef = react.useRef(null);
1110
2071
  const authExecutor = useAuthorizationExecutor();
1111
2072
  const polling = useTransferPolling();
1112
- const sourceType = walletSelection ? "walletId" : "providerId";
1113
- const sourceId = walletSelection?.walletId ?? selectedProviderId ?? "";
2073
+ const transferSigning = useTransferSigning();
2074
+ const sourceType = connectingNewAccount ? "providerId" : selectedWalletId ? "walletId" : selectedAccountId ? "accountId" : "providerId";
2075
+ const sourceId = connectingNewAccount ? selectedProviderId ?? "" : selectedWalletId ? selectedWalletId : selectedAccountId ? selectedAccountId : selectedProviderId ?? "";
2076
+ react.useEffect(() => {
2077
+ if (depositAmount != null) {
2078
+ setAmount(depositAmount.toString());
2079
+ }
2080
+ }, [depositAmount]);
1114
2081
  react.useEffect(() => {
1115
2082
  if (ready && authenticated && step === "login") {
1116
- setStep("select-source");
2083
+ if (depositAmount != null && depositAmount > 0) {
2084
+ setStep("ready");
2085
+ } else {
2086
+ setStep("enter-amount");
2087
+ }
1117
2088
  }
1118
- }, [ready, authenticated, step]);
2089
+ }, [ready, authenticated, step, depositAmount]);
2090
+ const loadingDataRef = react.useRef(false);
1119
2091
  react.useEffect(() => {
1120
- if (step !== "select-source") return;
1121
- if (providers.length > 0) return;
2092
+ if (!authenticated) return;
2093
+ if (accounts.length > 0 || loadingDataRef.current) return;
1122
2094
  let cancelled = false;
2095
+ loadingDataRef.current = true;
1123
2096
  const load = async () => {
1124
2097
  setLoadingData(true);
1125
2098
  setError(null);
1126
2099
  try {
1127
2100
  const token = await getAccessToken();
1128
2101
  if (!token) throw new Error("Not authenticated");
1129
- const [prov, accts] = await Promise.all([
2102
+ const [prov, accts, chn] = await Promise.all([
1130
2103
  fetchProviders(apiBaseUrl, token),
1131
- fetchAccounts(apiBaseUrl, token)
2104
+ fetchAccounts(apiBaseUrl, token),
2105
+ fetchChains(apiBaseUrl, token)
1132
2106
  ]);
1133
2107
  if (cancelled) return;
1134
2108
  setProviders(prov);
1135
2109
  setAccounts(accts);
1136
- if (prov.length > 0) setSelectedProviderId(prov[0].id);
2110
+ setChains(chn);
2111
+ const parsedAmt = depositAmount != null ? depositAmount : 0;
2112
+ const defaults = computeSmartDefaults(accts, parsedAmt);
2113
+ if (defaults) {
2114
+ setSelectedAccountId(defaults.accountId);
2115
+ setSelectedWalletId(defaults.walletId);
2116
+ } else if (prov.length > 0) {
2117
+ setSelectedProviderId(prov[0].id);
2118
+ }
1137
2119
  } catch (err) {
1138
2120
  if (!cancelled) {
1139
2121
  const msg = err instanceof Error ? err.message : "Failed to load data";
1140
2122
  setError(msg);
1141
2123
  }
1142
2124
  } finally {
1143
- if (!cancelled) setLoadingData(false);
2125
+ if (!cancelled) {
2126
+ setLoadingData(false);
2127
+ loadingDataRef.current = false;
2128
+ }
1144
2129
  }
1145
2130
  };
1146
2131
  load();
1147
2132
  return () => {
1148
2133
  cancelled = true;
2134
+ loadingDataRef.current = false;
1149
2135
  };
1150
- }, [step, providers.length, apiBaseUrl, getAccessToken]);
2136
+ }, [authenticated, accounts.length, apiBaseUrl, getAccessToken]);
1151
2137
  react.useEffect(() => {
1152
2138
  if (!polling.transfer) return;
1153
2139
  if (polling.transfer.status === "COMPLETED") {
@@ -1174,6 +2160,61 @@ function SwypePayment({
1174
2160
  document.removeEventListener("visibilitychange", handleVisibility);
1175
2161
  };
1176
2162
  }, [mobileFlow, polling]);
2163
+ react.useEffect(() => {
2164
+ if (!authExecutor.pendingSelectSource) return;
2165
+ const action = authExecutor.pendingSelectSource;
2166
+ const hasAdvancedOverride = advancedSettings.asset !== null || advancedSettings.chain !== null;
2167
+ if (hasAdvancedOverride) {
2168
+ const options = action.metadata?.options ?? [];
2169
+ const recommended = action.metadata?.recommended;
2170
+ const match = options.find(
2171
+ (opt) => (advancedSettings.chain === null || opt.chainName === advancedSettings.chain) && (advancedSettings.asset === null || opt.tokenSymbol === advancedSettings.asset)
2172
+ );
2173
+ if (match) {
2174
+ authExecutor.resolveSelectSource({
2175
+ chainName: match.chainName,
2176
+ tokenSymbol: match.tokenSymbol
2177
+ });
2178
+ } else if (recommended) {
2179
+ authExecutor.resolveSelectSource({
2180
+ chainName: recommended.chainName,
2181
+ tokenSymbol: recommended.tokenSymbol
2182
+ });
2183
+ }
2184
+ } else {
2185
+ const options = action.metadata?.options ?? [];
2186
+ const recommended = action.metadata?.recommended;
2187
+ const selWallet = selectedWalletId ? accounts.find((a) => a.id === selectedAccountId)?.wallets.find((w) => w.id === selectedWalletId) : null;
2188
+ if (selWallet) {
2189
+ const walletMatch = options.find(
2190
+ (opt) => opt.chainName === selWallet.chain.name
2191
+ );
2192
+ if (walletMatch) {
2193
+ authExecutor.resolveSelectSource({
2194
+ chainName: walletMatch.chainName,
2195
+ tokenSymbol: walletMatch.tokenSymbol
2196
+ });
2197
+ return;
2198
+ }
2199
+ }
2200
+ if (recommended) {
2201
+ authExecutor.resolveSelectSource({
2202
+ chainName: recommended.chainName,
2203
+ tokenSymbol: recommended.tokenSymbol
2204
+ });
2205
+ } else if (options.length > 0) {
2206
+ authExecutor.resolveSelectSource({
2207
+ chainName: options[0].chainName,
2208
+ tokenSymbol: options[0].tokenSymbol
2209
+ });
2210
+ } else {
2211
+ authExecutor.resolveSelectSource({
2212
+ chainName: "Base",
2213
+ tokenSymbol: "USDC"
2214
+ });
2215
+ }
2216
+ }
2217
+ }, [authExecutor, authExecutor.pendingSelectSource, advancedSettings, selectedWalletId, selectedAccountId, accounts]);
1177
2218
  const handlePay = react.useCallback(async () => {
1178
2219
  const parsedAmount = parseFloat(amount);
1179
2220
  if (isNaN(parsedAmount) || parsedAmount <= 0) {
@@ -1181,7 +2222,7 @@ function SwypePayment({
1181
2222
  return;
1182
2223
  }
1183
2224
  if (!sourceId) {
1184
- setError("Select a source.");
2225
+ setError("No account or provider selected.");
1185
2226
  return;
1186
2227
  }
1187
2228
  setStep("processing");
@@ -1191,12 +2232,6 @@ function SwypePayment({
1191
2232
  try {
1192
2233
  const token = await getAccessToken();
1193
2234
  if (!token) throw new Error("Not authenticated");
1194
- const parsedTopUp = parseFloat(topUpBalance);
1195
- if (!isNaN(parsedTopUp) && parsedTopUp > 0) {
1196
- await updateUserConfig(apiBaseUrl, token, {
1197
- defaultAllowance: parsedTopUp
1198
- });
1199
- }
1200
2235
  const t = await createTransfer(apiBaseUrl, token, {
1201
2236
  sourceType,
1202
2237
  sourceId,
@@ -1215,12 +2250,14 @@ function SwypePayment({
1215
2250
  await authExecutor.executeSession(t);
1216
2251
  }
1217
2252
  }
2253
+ const signedTransfer = await transferSigning.signTransfer(t.id);
2254
+ setTransfer(signedTransfer);
1218
2255
  polling.startPolling(t.id);
1219
2256
  } catch (err) {
1220
- const msg = err instanceof Error ? err.message : "Transfer creation failed";
2257
+ const msg = err instanceof Error ? err.message : "Transfer failed";
1221
2258
  setError(msg);
1222
2259
  onError?.(msg);
1223
- setStep("enter-amount");
2260
+ setStep("ready");
1224
2261
  } finally {
1225
2262
  setCreatingTransfer(false);
1226
2263
  }
@@ -1232,20 +2269,26 @@ function SwypePayment({
1232
2269
  apiBaseUrl,
1233
2270
  getAccessToken,
1234
2271
  authExecutor,
2272
+ transferSigning,
1235
2273
  polling,
1236
- onError,
1237
- topUpBalance
2274
+ onError
1238
2275
  ]);
1239
2276
  const handleNewPayment = () => {
1240
- setStep("select-source");
2277
+ setStep("ready");
1241
2278
  setTransfer(null);
1242
2279
  setError(null);
1243
- setAmount("0.10");
1244
- setTopUpBalance("");
2280
+ setAmount(depositAmount != null ? depositAmount.toString() : "");
1245
2281
  setMobileFlow(false);
1246
2282
  pollingTransferIdRef.current = null;
1247
- setWalletSelection(null);
1248
- if (providers.length > 0) setSelectedProviderId(providers[0].id);
2283
+ setConnectingNewAccount(false);
2284
+ setSelectedWalletId(null);
2285
+ setAdvancedSettings({ asset: null, chain: null });
2286
+ if (accounts.length > 0) setSelectedAccountId(accounts[0].id);
2287
+ };
2288
+ const handleConnectNewAccount = (providerId) => {
2289
+ setSelectedProviderId(providerId);
2290
+ setSelectedAccountId(null);
2291
+ setConnectingNewAccount(true);
1249
2292
  };
1250
2293
  const cardStyle = {
1251
2294
  background: tokens.bgCard,
@@ -1283,15 +2326,10 @@ function SwypePayment({
1283
2326
  opacity: 0.5,
1284
2327
  cursor: "not-allowed"
1285
2328
  };
1286
- const btnSecondary = {
2329
+ ({
1287
2330
  ...btnPrimary,
1288
- background: "transparent",
1289
2331
  color: tokens.textSecondary,
1290
- border: `1px solid ${tokens.border}`,
1291
- width: "auto",
1292
- flex: "0 0 auto",
1293
- padding: "14px 20px"
1294
- };
2332
+ border: `1px solid ${tokens.border}`});
1295
2333
  const errorStyle = {
1296
2334
  background: tokens.errorBg,
1297
2335
  border: `1px solid ${tokens.error}`,
@@ -1382,62 +2420,20 @@ function SwypePayment({
1382
2420
  /* @__PURE__ */ jsxRuntime.jsx("button", { style: btnPrimary, onClick: login, children: "Connect to Swype" })
1383
2421
  ] }) });
1384
2422
  }
1385
- if (step === "select-source") {
1386
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle, children: [
1387
- stepBadge("Select payment source"),
1388
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle, children: "Choose a provider" }),
1389
- error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle, children: error }),
1390
- loadingData ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "24px 0", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { label: "Loading providers..." }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1391
- accounts.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1392
- AccountWalletSelector,
1393
- {
1394
- accounts,
1395
- selection: walletSelection,
1396
- onSelect: (sel) => {
1397
- setWalletSelection(sel);
1398
- if (sel) setSelectedProviderId(null);
1399
- }
1400
- }
1401
- ) }),
1402
- !walletSelection && /* @__PURE__ */ jsxRuntime.jsx(
1403
- "div",
1404
- {
1405
- style: {
1406
- display: "flex",
1407
- flexDirection: "column",
1408
- gap: "8px",
1409
- marginBottom: "20px"
1410
- },
1411
- children: providers.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
1412
- ProviderCard,
1413
- {
1414
- provider: p,
1415
- selected: selectedProviderId === p.id,
1416
- onClick: () => {
1417
- setSelectedProviderId(p.id);
1418
- setWalletSelection(null);
1419
- }
1420
- },
1421
- p.id
1422
- ))
1423
- }
1424
- ),
1425
- /* @__PURE__ */ jsxRuntime.jsx(
1426
- "button",
1427
- {
1428
- style: sourceId ? btnPrimary : btnDisabled,
1429
- disabled: !sourceId,
1430
- onClick: () => {
1431
- setError(null);
1432
- setStep("enter-amount");
1433
- },
1434
- children: "Continue"
1435
- }
1436
- )
1437
- ] })
1438
- ] });
1439
- }
1440
2423
  if (step === "enter-amount") {
2424
+ const parsedAmount = parseFloat(amount);
2425
+ const canContinue = !isNaN(parsedAmount) && parsedAmount > 0;
2426
+ let maxSourceBalance = null;
2427
+ for (const acct of accounts) {
2428
+ for (const wallet of acct.wallets) {
2429
+ for (const source of wallet.sources) {
2430
+ const bal = source.balance.available.amount;
2431
+ if (maxSourceBalance === null || bal > maxSourceBalance) {
2432
+ maxSourceBalance = bal;
2433
+ }
2434
+ }
2435
+ }
2436
+ }
1441
2437
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle, children: [
1442
2438
  stepBadge("Enter amount"),
1443
2439
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: headingStyle, children: "How much?" }),
@@ -1453,7 +2449,7 @@ function SwypePayment({
1453
2449
  border: `1px solid ${tokens.border}`,
1454
2450
  borderRadius: tokens.radius,
1455
2451
  padding: "4px 14px 4px 4px",
1456
- marginBottom: "20px"
2452
+ marginBottom: "8px"
1457
2453
  },
1458
2454
  children: [
1459
2455
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1477,6 +2473,7 @@ function SwypePayment({
1477
2473
  step: "0.01",
1478
2474
  value: amount,
1479
2475
  onChange: (e) => setAmount(e.target.value),
2476
+ placeholder: "0.00",
1480
2477
  style: {
1481
2478
  flex: 1,
1482
2479
  background: "transparent",
@@ -1508,156 +2505,301 @@ function SwypePayment({
1508
2505
  ]
1509
2506
  }
1510
2507
  ),
1511
- /* @__PURE__ */ jsxRuntime.jsxs(
2508
+ /* @__PURE__ */ jsxRuntime.jsx(
1512
2509
  "div",
1513
2510
  {
1514
2511
  style: {
1515
- display: "flex",
1516
- alignItems: "center",
1517
- gap: "8px",
1518
- background: tokens.bgInput,
1519
- border: `1px solid ${tokens.border}`,
1520
- borderRadius: tokens.radius,
1521
- padding: "4px 14px 4px 4px",
1522
- marginBottom: "20px"
2512
+ fontSize: "0.8rem",
2513
+ color: tokens.textMuted,
2514
+ marginBottom: "20px",
2515
+ paddingLeft: "2px"
1523
2516
  },
1524
- children: [
1525
- /* @__PURE__ */ jsxRuntime.jsx(
1526
- "span",
1527
- {
1528
- style: {
1529
- fontSize: "1rem",
1530
- fontWeight: 600,
1531
- color: tokens.textMuted,
1532
- paddingLeft: "10px",
1533
- userSelect: "none"
1534
- },
1535
- children: "$"
1536
- }
1537
- ),
1538
- /* @__PURE__ */ jsxRuntime.jsx(
1539
- "input",
1540
- {
1541
- type: "number",
1542
- min: "0",
1543
- step: "1",
1544
- placeholder: "Top up balance (optional)",
1545
- value: topUpBalance,
1546
- onChange: (e) => setTopUpBalance(e.target.value),
1547
- style: {
1548
- flex: 1,
1549
- background: "transparent",
1550
- border: "none",
1551
- outline: "none",
1552
- color: tokens.text,
1553
- fontSize: "1rem",
1554
- fontWeight: 600,
1555
- fontFamily: "inherit",
1556
- padding: "10px 0"
1557
- }
1558
- }
1559
- ),
1560
- /* @__PURE__ */ jsxRuntime.jsx(
1561
- "span",
1562
- {
1563
- style: {
1564
- fontSize: "0.75rem",
1565
- fontWeight: 600,
1566
- color: tokens.textMuted,
1567
- background: tokens.bgHover,
1568
- padding: "4px 10px",
1569
- borderRadius: "6px",
1570
- whiteSpace: "nowrap"
1571
- },
1572
- children: "USD"
1573
- }
1574
- )
1575
- ]
2517
+ children: loadingData ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading balance..." }) : maxSourceBalance !== null && maxSourceBalance > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2518
+ "Available: ",
2519
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: tokens.textSecondary }, children: [
2520
+ "$",
2521
+ maxSourceBalance.toFixed(2)
2522
+ ] })
2523
+ ] }) : null
1576
2524
  }
1577
2525
  ),
1578
2526
  /* @__PURE__ */ jsxRuntime.jsx(
1579
- "p",
2527
+ "button",
1580
2528
  {
1581
- style: {
1582
- fontSize: "0.75rem",
1583
- color: tokens.textMuted,
1584
- margin: "-12px 0 16px 0",
1585
- lineHeight: 1.4
2529
+ style: canContinue ? btnPrimary : btnDisabled,
2530
+ disabled: !canContinue,
2531
+ onClick: () => {
2532
+ setError(null);
2533
+ setStep("ready");
1586
2534
  },
1587
- children: "Set a higher allowance to skip wallet prompts on future payments under this amount."
2535
+ children: "Continue"
1588
2536
  }
1589
- ),
1590
- /* @__PURE__ */ jsxRuntime.jsxs(
1591
- "div",
1592
- {
1593
- style: {
1594
- fontSize: "0.825rem",
1595
- color: tokens.textSecondary,
1596
- marginBottom: "20px",
1597
- padding: "12px 14px",
1598
- background: tokens.bgInput,
1599
- borderRadius: tokens.radius,
1600
- lineHeight: 1.7
1601
- },
1602
- children: [
1603
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1604
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "To" }),
2537
+ )
2538
+ ] });
2539
+ }
2540
+ if (step === "ready") {
2541
+ const parsedAmount = parseFloat(amount);
2542
+ const canPay = !isNaN(parsedAmount) && parsedAmount > 0 && !!sourceId && !loadingData;
2543
+ const noAccounts = !loadingData && accounts.length === 0;
2544
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: cardStyle, children: [
2545
+ stepBadge("Review & pay"),
2546
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: errorStyle, children: error }),
2547
+ loadingData ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "24px 0", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { label: "Loading..." }) }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2548
+ /* @__PURE__ */ jsxRuntime.jsxs(
2549
+ "div",
2550
+ {
2551
+ style: {
2552
+ textAlign: "center",
2553
+ marginBottom: "20px"
2554
+ },
2555
+ children: [
1605
2556
  /* @__PURE__ */ jsxRuntime.jsxs(
1606
- "span",
2557
+ "div",
1607
2558
  {
1608
2559
  style: {
1609
- fontFamily: '"SF Mono", "Fira Code", monospace',
1610
- fontSize: "0.8rem"
2560
+ fontSize: "2rem",
2561
+ fontWeight: 700,
2562
+ color: tokens.text,
2563
+ lineHeight: 1.2
1611
2564
  },
1612
2565
  children: [
1613
- destination.address.slice(0, 6),
1614
- "...",
1615
- destination.address.slice(-4)
2566
+ "$",
2567
+ parsedAmount > 0 ? parsedAmount.toFixed(2) : "0.00"
1616
2568
  ]
1617
2569
  }
2570
+ ),
2571
+ /* @__PURE__ */ jsxRuntime.jsx(
2572
+ "button",
2573
+ {
2574
+ onClick: () => setStep("enter-amount"),
2575
+ style: {
2576
+ background: "transparent",
2577
+ border: "none",
2578
+ cursor: "pointer",
2579
+ color: tokens.textMuted,
2580
+ fontSize: "0.75rem",
2581
+ fontFamily: "inherit",
2582
+ outline: "none",
2583
+ padding: "4px 8px",
2584
+ marginTop: "4px"
2585
+ },
2586
+ children: "Change amount"
2587
+ }
1618
2588
  )
1619
- ] }),
1620
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1621
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Token" }),
1622
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: destination.token.symbol })
1623
- ] }),
1624
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1625
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Source" }),
1626
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: walletSelection ? `${walletSelection.walletName} (${walletSelection.chainName})` : providers.find((p) => p.id === selectedProviderId)?.name ?? "Provider" })
1627
- ] })
1628
- ]
1629
- }
1630
- ),
1631
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "10px" }, children: [
1632
- /* @__PURE__ */ jsxRuntime.jsx(
1633
- "button",
2589
+ ]
2590
+ }
2591
+ ),
2592
+ /* @__PURE__ */ jsxRuntime.jsxs(
2593
+ "div",
1634
2594
  {
1635
- style: btnSecondary,
1636
- onClick: () => {
1637
- setError(null);
1638
- setStep("select-source");
2595
+ style: {
2596
+ fontSize: "0.825rem",
2597
+ color: tokens.textSecondary,
2598
+ marginBottom: "16px",
2599
+ padding: "12px 14px",
2600
+ background: tokens.bgInput,
2601
+ borderRadius: tokens.radius,
2602
+ lineHeight: 1.7
1639
2603
  },
1640
- children: "Back"
2604
+ children: [
2605
+ /* @__PURE__ */ jsxRuntime.jsxs(
2606
+ "div",
2607
+ {
2608
+ style: { display: "flex", justifyContent: "space-between" },
2609
+ children: [
2610
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "To" }),
2611
+ /* @__PURE__ */ jsxRuntime.jsxs(
2612
+ "span",
2613
+ {
2614
+ style: {
2615
+ fontFamily: '"SF Mono", "Fira Code", monospace',
2616
+ fontSize: "0.8rem"
2617
+ },
2618
+ children: [
2619
+ destination.address.slice(0, 6),
2620
+ "...",
2621
+ destination.address.slice(-4)
2622
+ ]
2623
+ }
2624
+ )
2625
+ ]
2626
+ }
2627
+ ),
2628
+ /* @__PURE__ */ jsxRuntime.jsxs(
2629
+ "div",
2630
+ {
2631
+ style: { display: "flex", justifyContent: "space-between" },
2632
+ children: [
2633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Token" }),
2634
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 600 }, children: destination.token.symbol })
2635
+ ]
2636
+ }
2637
+ ),
2638
+ /* @__PURE__ */ jsxRuntime.jsxs(
2639
+ "div",
2640
+ {
2641
+ style: {
2642
+ display: "flex",
2643
+ justifyContent: "space-between",
2644
+ alignItems: "center"
2645
+ },
2646
+ children: [
2647
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "From" }),
2648
+ noAccounts ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500, color: tokens.textMuted }, children: "New account" }) : /* @__PURE__ */ jsxRuntime.jsx(
2649
+ AccountDropdown,
2650
+ {
2651
+ accounts,
2652
+ selectedAccountId,
2653
+ selectedWalletId,
2654
+ onSelect: (id) => {
2655
+ setSelectedAccountId(id);
2656
+ setSelectedWalletId(null);
2657
+ setConnectingNewAccount(false);
2658
+ setSelectedProviderId(null);
2659
+ },
2660
+ onWalletSelect: (accountId, walletId) => {
2661
+ setSelectedAccountId(accountId);
2662
+ setSelectedWalletId(walletId);
2663
+ setConnectingNewAccount(false);
2664
+ setSelectedProviderId(null);
2665
+ }
2666
+ }
2667
+ )
2668
+ ]
2669
+ }
2670
+ )
2671
+ ]
1641
2672
  }
1642
2673
  ),
2674
+ noAccounts && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: "16px" }, children: [
2675
+ /* @__PURE__ */ jsxRuntime.jsx(
2676
+ "label",
2677
+ {
2678
+ style: {
2679
+ display: "block",
2680
+ fontSize: "0.8rem",
2681
+ color: tokens.textMuted,
2682
+ marginBottom: "8px",
2683
+ fontWeight: 500,
2684
+ textTransform: "uppercase",
2685
+ letterSpacing: "0.05em"
2686
+ },
2687
+ children: "Connect a wallet"
2688
+ }
2689
+ ),
2690
+ /* @__PURE__ */ jsxRuntime.jsx(
2691
+ "div",
2692
+ {
2693
+ style: {
2694
+ display: "flex",
2695
+ flexDirection: "column",
2696
+ gap: "8px"
2697
+ },
2698
+ children: providers.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
2699
+ ProviderCard,
2700
+ {
2701
+ provider: p,
2702
+ selected: selectedProviderId === p.id,
2703
+ onClick: () => {
2704
+ setSelectedProviderId(p.id);
2705
+ setSelectedAccountId(null);
2706
+ setConnectingNewAccount(false);
2707
+ }
2708
+ },
2709
+ p.id
2710
+ ))
2711
+ }
2712
+ )
2713
+ ] }),
1643
2714
  /* @__PURE__ */ jsxRuntime.jsxs(
1644
2715
  "button",
1645
2716
  {
1646
- style: parseFloat(amount) > 0 ? btnPrimary : btnDisabled,
1647
- disabled: !(parseFloat(amount) > 0),
2717
+ style: canPay ? btnPrimary : btnDisabled,
2718
+ disabled: !canPay,
1648
2719
  onClick: handlePay,
1649
2720
  children: [
1650
2721
  "Pay $",
1651
- parseFloat(amount || "0").toFixed(2)
2722
+ parsedAmount > 0 ? parsedAmount.toFixed(2) : "0.00"
1652
2723
  ]
1653
2724
  }
2725
+ ),
2726
+ !noAccounts && /* @__PURE__ */ jsxRuntime.jsx(
2727
+ AdvancedSettings,
2728
+ {
2729
+ settings: advancedSettings,
2730
+ onChange: setAdvancedSettings,
2731
+ chains,
2732
+ providers,
2733
+ onConnectNewAccount: handleConnectNewAccount,
2734
+ connectingNewAccount
2735
+ }
1654
2736
  )
1655
2737
  ] })
1656
2738
  ] });
1657
2739
  }
1658
2740
  if (step === "processing") {
1659
- const statusLabel = creatingTransfer ? "Creating transfer..." : mobileFlow ? "Waiting for authorization..." : authExecutor.executing ? "Authorizing..." : polling.isPolling ? "Processing payment..." : "Please wait...";
1660
- const statusDescription = creatingTransfer ? "Setting up your transfer..." : mobileFlow ? "Complete the authorization in your wallet app, then return here." : authExecutor.executing ? "Complete the wallet prompts to authorize this payment." : polling.isPolling ? "Your payment is being processed. This usually takes a few moments." : "Hang tight...";
2741
+ if (transferSigning.signing && transferSigning.signPayload) {
2742
+ const payload = transferSigning.signPayload;
2743
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
2744
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", style: { margin: "0 auto 16px" }, children: [
2745
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "48", height: "48", rx: "12", fill: tokens.accent + "20" }),
2746
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M24 14v8M20 18h8M24 26v2M24 32v2", stroke: tokens.accent, strokeWidth: "2", strokeLinecap: "round" })
2747
+ ] }),
2748
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { ...headingStyle, marginBottom: "8px" }, children: "Authorize Transfer" }),
2749
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.85rem", color: tokens.textSecondary, margin: "0 0 16px 0", lineHeight: 1.5 }, children: "Use your passkey to confirm this payment." }),
2750
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "0.825rem", color: tokens.textSecondary, padding: "12px 14px", background: tokens.bgInput, borderRadius: tokens.radius, textAlign: "left", lineHeight: 1.7, marginBottom: "16px" }, children: [
2751
+ payload.amount && payload.tokenSymbol && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2752
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Amount" }),
2753
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: tokens.text }, children: [
2754
+ payload.amount,
2755
+ " ",
2756
+ payload.tokenSymbol
2757
+ ] })
2758
+ ] }),
2759
+ payload.bridgeRelayAddress && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2760
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Bridge relay" }),
2761
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontFamily: '"SF Mono", "Fira Code", monospace', fontSize: "0.75rem" }, children: [
2762
+ payload.bridgeRelayAddress.slice(0, 6),
2763
+ "...",
2764
+ payload.bridgeRelayAddress.slice(-4)
2765
+ ] })
2766
+ ] }),
2767
+ payload.estimatedFeeUsd && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2768
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Est. fee" }),
2769
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600 }, children: [
2770
+ "$",
2771
+ payload.estimatedFeeUsd
2772
+ ] })
2773
+ ] })
2774
+ ] }),
2775
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { label: "Waiting for passkey..." })
2776
+ ] }) });
2777
+ }
2778
+ const currentActionType = authExecutor.currentAction?.type;
2779
+ const getRegistrationMessage = () => {
2780
+ switch (currentActionType) {
2781
+ case "REGISTER_PASSKEY":
2782
+ return {
2783
+ label: "Creating your passkey...",
2784
+ description: "Set up a passkey for secure, one-touch payments."
2785
+ };
2786
+ case "UPGRADE_SMART_ACCOUNT":
2787
+ return {
2788
+ label: "Upgrading your account...",
2789
+ description: "Approve the prompts in MetaMask to upgrade your account to a smart account and register your passkey. You may see two prompts."
2790
+ };
2791
+ case "GRANT_PERMISSIONS":
2792
+ return {
2793
+ label: "Setting up permissions...",
2794
+ description: "Signing delegation permissions. Transfer routing and gas sponsorship are handled by Swype backend."
2795
+ };
2796
+ default:
2797
+ return { label: "", description: "" };
2798
+ }
2799
+ };
2800
+ const regMsg = getRegistrationMessage();
2801
+ const statusLabel = creatingTransfer ? "Creating transfer..." : mobileFlow ? "Waiting for authorization..." : authExecutor.executing && regMsg.label ? regMsg.label : authExecutor.executing ? "Authorizing..." : transferSigning.signing ? "Preparing transfer..." : polling.isPolling ? "Processing payment..." : "Please wait...";
2802
+ const statusDescription = creatingTransfer ? "Setting up your transfer..." : mobileFlow ? "Complete the authorization in your wallet app, then return here." : authExecutor.executing && regMsg.description ? regMsg.description : authExecutor.executing ? "Complete the wallet prompts to authorize this payment." : transferSigning.signing ? "Waiting for backend to prepare your transfer payload..." : polling.isPolling ? "Your payment is being processed. This usually takes a few moments." : "Hang tight...";
1661
2803
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
1662
2804
  /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 48 }),
1663
2805
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1701,11 +2843,11 @@ function SwypePayment({
1701
2843
  },
1702
2844
  r.actionId
1703
2845
  )) }),
1704
- (error || authExecutor.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
2846
+ (error || authExecutor.error || transferSigning.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
1705
2847
  "div",
1706
2848
  {
1707
2849
  style: { ...errorStyle, marginTop: "16px", textAlign: "left" },
1708
- children: error || authExecutor.error || polling.error
2850
+ children: error || authExecutor.error || transferSigning.error || polling.error
1709
2851
  }
1710
2852
  )
1711
2853
  ] }) });
@@ -1840,6 +2982,7 @@ exports.lightTheme = lightTheme;
1840
2982
  exports.swypeApi = api_exports;
1841
2983
  exports.useAuthorizationExecutor = useAuthorizationExecutor;
1842
2984
  exports.useSwypeConfig = useSwypeConfig;
2985
+ exports.useSwypeDepositAmount = useSwypeDepositAmount;
1843
2986
  exports.useTransferPolling = useTransferPolling;
1844
2987
  //# sourceMappingURL=index.cjs.map
1845
2988
  //# sourceMappingURL=index.cjs.map