@swype-org/react-sdk 0.1.2 → 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
@@ -7,7 +7,7 @@ var chains = require('wagmi/chains');
7
7
  var reactQuery = require('@tanstack/react-query');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
9
  var viem = require('viem');
10
- var chains$1 = require('viem/chains');
10
+ var utils = require('viem/utils');
11
11
 
12
12
  var __defProp = Object.defineProperty;
13
13
  var __export = (target, all) => {
@@ -145,6 +145,7 @@ __export(api_exports, {
145
145
  fetchProviders: () => fetchProviders,
146
146
  fetchTransfer: () => fetchTransfer,
147
147
  reportActionCompletion: () => reportActionCompletion,
148
+ signTransfer: () => signTransfer,
148
149
  updateUserConfig: () => updateUserConfig,
149
150
  updateUserConfigBySession: () => updateUserConfigBySession
150
151
  });
@@ -213,6 +214,18 @@ async function fetchTransfer(apiBaseUrl, token, transferId) {
213
214
  if (!res.ok) await throwApiError(res);
214
215
  return await res.json();
215
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
+ }
216
229
  async function fetchAuthorizationSession(apiBaseUrl, sessionId) {
217
230
  const res = await fetch(
218
231
  `${apiBaseUrl}/v1/authorization-sessions/${sessionId}`
@@ -254,25 +267,442 @@ async function reportActionCompletion(apiBaseUrl, actionId, result) {
254
267
  if (!res.ok) await throwApiError(res);
255
268
  return await res.json();
256
269
  }
257
- var VIEM_CHAINS = {
258
- 1: chains$1.mainnet,
259
- 8453: chains$1.base,
260
- 42161: chains$1.arbitrum
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
+ }
261
346
  };
262
- var chainClientCache = /* @__PURE__ */ new Map();
263
- function getPublicClientForChain(numericChainId) {
264
- const existing = chainClientCache.get(numericChainId);
265
- if (existing) return existing;
266
- const viemChain = VIEM_CHAINS[numericChainId];
267
- if (!viemChain) {
268
- throw new Error(`Unsupported chain ID: ${numericChainId}`);
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
+ });
269
365
  }
270
- const client = viem.createPublicClient({
271
- chain: viemChain,
272
- transport: viem.http()
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 })
273
460
  });
274
- chainClientCache.set(numericChainId, client);
275
- return client;
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");
276
706
  }
277
707
  function useTransferPolling(intervalMs = 3e3) {
278
708
  const { apiBaseUrl } = useSwypeConfig();
@@ -324,14 +754,13 @@ function useTransferPolling(intervalMs = 3e3) {
324
754
  }
325
755
  function useAuthorizationExecutor() {
326
756
  const { apiBaseUrl } = useSwypeConfig();
327
- const { address, chainId: currentChainId, isConnected } = wagmi.useAccount();
757
+ const wagmiConfig2 = wagmi.useConfig();
328
758
  const { connectAsync, connectors } = wagmi.useConnect();
329
759
  const { switchChainAsync } = wagmi.useSwitchChain();
330
- const { signTypedDataAsync } = wagmi.useSignTypedData();
331
- const { data: walletClient } = wagmi.useWalletClient();
332
760
  const [executing, setExecuting] = react.useState(false);
333
761
  const [results, setResults] = react.useState([]);
334
762
  const [error, setError] = react.useState(null);
763
+ const [currentAction, setCurrentAction] = react.useState(null);
335
764
  const executingRef = react.useRef(false);
336
765
  const [pendingSelectSource, setPendingSelectSource] = react.useState(null);
337
766
  const selectSourceResolverRef = react.useRef(null);
@@ -342,31 +771,28 @@ function useAuthorizationExecutor() {
342
771
  setPendingSelectSource(null);
343
772
  }
344
773
  }, []);
345
- const [pendingAllowanceSelection, setPendingAllowanceSelection] = react.useState(null);
346
- const allowanceSelectionResolverRef = react.useRef(null);
347
774
  const sessionIdRef = react.useRef(null);
348
- const resolveAllowanceSelection = react.useCallback((selection) => {
349
- if (allowanceSelectionResolverRef.current) {
350
- allowanceSelectionResolverRef.current(selection);
351
- allowanceSelectionResolverRef.current = null;
352
- setPendingAllowanceSelection(null);
353
- }
354
- }, []);
355
775
  const executeOpenProvider = react.useCallback(
356
776
  async (action) => {
357
777
  try {
358
- if (isConnected && address) {
359
- 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;
360
781
  return {
361
782
  actionId: action.id,
362
783
  type: action.type,
363
784
  status: "success",
364
- message: `Connected. Account: ${address}, Chain: ${hexChainId2}`,
365
- data: { accounts: [address], chainId: hexChainId2 }
785
+ message: `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
786
+ data: { accounts: [account.address], chainId: hexChainId2 }
366
787
  };
367
788
  }
368
789
  const targetId = action.metadata?.wagmiConnectorId;
369
- 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];
370
796
  if (!connector) {
371
797
  return {
372
798
  actionId: action.id,
@@ -393,7 +819,7 @@ function useAuthorizationExecutor() {
393
819
  };
394
820
  }
395
821
  },
396
- [isConnected, address, currentChainId, connectors, connectAsync]
822
+ [wagmiConfig2, connectors, connectAsync]
397
823
  );
398
824
  const executeSelectSource = react.useCallback(
399
825
  async (action) => {
@@ -441,6 +867,7 @@ function useAuthorizationExecutor() {
441
867
  const executeSwitchChain = react.useCallback(
442
868
  async (action) => {
443
869
  try {
870
+ const account = getAccount(wagmiConfig2);
444
871
  const targetChainIdHex = action.metadata?.targetChainId;
445
872
  if (!targetChainIdHex) {
446
873
  return {
@@ -452,7 +879,7 @@ function useAuthorizationExecutor() {
452
879
  }
453
880
  const targetChainIdNum = parseInt(targetChainIdHex, 16);
454
881
  const hexChainId = `0x${targetChainIdNum.toString(16)}`;
455
- if (currentChainId === targetChainIdNum) {
882
+ if (account.chainId === targetChainIdNum) {
456
883
  return {
457
884
  actionId: action.id,
458
885
  type: action.type,
@@ -478,237 +905,145 @@ function useAuthorizationExecutor() {
478
905
  };
479
906
  }
480
907
  },
481
- [currentChainId, switchChainAsync]
908
+ [wagmiConfig2, switchChainAsync]
482
909
  );
483
- const executeApprovePermit2 = react.useCallback(
910
+ const executeRegisterPasskey = react.useCallback(
484
911
  async (action) => {
485
912
  try {
486
- const tokenAddress = action.metadata?.tokenAddress;
487
- const permit2Address = action.metadata?.permit2Address;
488
- const metadataChainId = action.metadata?.chainId;
489
- if (!tokenAddress || !permit2Address) {
490
- return {
491
- actionId: action.id,
492
- type: action.type,
493
- status: "error",
494
- message: "Missing tokenAddress or permit2Address in action metadata."
495
- };
496
- }
497
- if (!address) {
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
933
+ ],
934
+ authenticatorSelection: {
935
+ authenticatorAttachment: "platform",
936
+ residentKey: "preferred",
937
+ userVerification: "required"
938
+ },
939
+ timeout: 6e4
940
+ }
941
+ });
942
+ if (!credential) {
498
943
  return {
499
944
  actionId: action.id,
500
945
  type: action.type,
501
946
  status: "error",
502
- message: "Wallet not connected."
947
+ message: "Passkey creation was cancelled."
503
948
  };
504
949
  }
505
- const ERC20_ABI = [
506
- {
507
- name: "allowance",
508
- type: "function",
509
- stateMutability: "view",
510
- inputs: [
511
- { name: "owner", type: "address" },
512
- { name: "spender", type: "address" }
513
- ],
514
- outputs: [{ name: "", type: "uint256" }]
515
- },
516
- {
517
- name: "approve",
518
- type: "function",
519
- stateMutability: "nonpayable",
520
- inputs: [
521
- { name: "spender", type: "address" },
522
- { name: "amount", type: "uint256" }
523
- ],
524
- outputs: [{ name: "", type: "bool" }]
525
- }
526
- ];
527
- const targetChainIdNum = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
528
- const chainClient = targetChainIdNum ? getPublicClientForChain(targetChainIdNum) : void 0;
529
- if (chainClient) {
530
- try {
531
- const currentAllowance = await chainClient.readContract({
532
- address: tokenAddress,
533
- abi: ERC20_ABI,
534
- functionName: "allowance",
535
- args: [address, permit2Address]
536
- });
537
- if (currentAllowance > 0n) {
538
- return {
539
- actionId: action.id,
540
- type: action.type,
541
- status: "success",
542
- message: `Permit2 already approved (allowance: ${currentAllowance.toString()}). Skipped.`,
543
- data: { skipped: true, existingAllowance: currentAllowance.toString() }
544
- };
545
- }
546
- } catch {
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))) : "";
956
+ return {
957
+ actionId: action.id,
958
+ type: action.type,
959
+ status: "success",
960
+ message: "Passkey created successfully.",
961
+ data: {
962
+ credentialId,
963
+ publicKey
547
964
  }
965
+ };
966
+ } catch (err) {
967
+ return {
968
+ actionId: action.id,
969
+ type: action.type,
970
+ status: "error",
971
+ message: err instanceof Error ? err.message : "Failed to create passkey"
972
+ };
973
+ }
974
+ },
975
+ [wagmiConfig2]
976
+ );
977
+ const executeUpgradeSmartAccount = react.useCallback(
978
+ async (action) => {
979
+ try {
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.");
548
985
  }
549
- if (!walletClient) {
550
- return {
551
- actionId: action.id,
552
- type: action.type,
553
- status: "error",
554
- message: "Wallet client not available."
555
- };
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
+ );
556
990
  }
557
- const MAX_UINT256 = 2n ** 256n - 1n;
558
- const txHash = await walletClient.writeContract({
559
- address: tokenAddress,
560
- abi: ERC20_ABI,
561
- functionName: "approve",
562
- args: [permit2Address, MAX_UINT256]
991
+ const metadata = action.metadata;
992
+ const { txHash, smartAccountAddress } = await submitUpgradeTransaction({
993
+ walletClient,
994
+ sender,
995
+ metadata
563
996
  });
564
- if (chainClient) {
565
- await chainClient.waitForTransactionReceipt({ hash: txHash });
566
- }
567
997
  return {
568
998
  actionId: action.id,
569
999
  type: action.type,
570
1000
  status: "success",
571
- message: `Permit2 approved. Tx: ${txHash}`,
572
- data: { txHash, approved: true }
1001
+ message: "Account upgrade transaction submitted.",
1002
+ data: {
1003
+ txHash,
1004
+ smartAccountAddress
1005
+ }
573
1006
  };
574
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");
575
1011
  return {
576
1012
  actionId: action.id,
577
1013
  type: action.type,
578
1014
  status: "error",
579
- message: err instanceof Error ? err.message : "Failed to approve Permit2"
1015
+ message: isRejected ? "You rejected the upgrade transaction. Please approve the transaction prompt in your wallet to continue." : message
580
1016
  };
581
1017
  }
582
1018
  },
583
- [address, currentChainId, walletClient]
1019
+ [wagmiConfig2]
584
1020
  );
585
- const executeSignPermit2 = react.useCallback(
1021
+ const executeGrantPermissions = react.useCallback(
586
1022
  async (action) => {
587
1023
  try {
588
- const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
589
- const PERMIT2_ALLOWANCE_ABI = [
590
- {
591
- name: "allowance",
592
- type: "function",
593
- stateMutability: "view",
594
- inputs: [
595
- { name: "owner", type: "address" },
596
- { name: "token", type: "address" },
597
- { name: "spender", type: "address" }
598
- ],
599
- outputs: [
600
- { name: "amount", type: "uint160" },
601
- { name: "expiration", type: "uint48" },
602
- { name: "nonce", type: "uint48" }
603
- ]
604
- }
605
- ];
606
- const spenderAddress = action.metadata?.spenderAddress;
607
- const tokenAddress = action.metadata?.tokenAddress;
608
- const metadataChainId = action.metadata?.chainId;
609
- const metadataAmount = action.metadata?.amount;
610
- if (!spenderAddress || !tokenAddress) {
1024
+ const walletClient = await waitForWalletClient(wagmiConfig2);
1025
+ const signingPayload = action.metadata?.signingPayload;
1026
+ const tokens = action.metadata?.tokens;
1027
+ if (!signingPayload) {
611
1028
  return {
612
1029
  actionId: action.id,
613
1030
  type: action.type,
614
1031
  status: "error",
615
- message: "Missing spenderAddress or tokenAddress in action metadata."
1032
+ message: "No signing payload in action metadata."
616
1033
  };
617
1034
  }
618
- const permit2ChainId = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
619
- const chainClient = permit2ChainId ? getPublicClientForChain(permit2ChainId) : void 0;
620
- let onChainNonce;
621
- if (chainClient && address) {
622
- try {
623
- const [existingAmount, existingExpiration, chainNonce] = await chainClient.readContract({
624
- address: PERMIT2_ADDRESS,
625
- abi: PERMIT2_ALLOWANCE_ABI,
626
- functionName: "allowance",
627
- args: [address, tokenAddress, spenderAddress]
628
- });
629
- onChainNonce = chainNonce;
630
- if (metadataAmount) {
631
- const requiredSmallestUnit = BigInt(metadataAmount);
632
- const now = Math.floor(Date.now() / 1e3);
633
- if (existingAmount >= requiredSmallestUnit && existingExpiration > now) {
634
- return {
635
- actionId: action.id,
636
- type: action.type,
637
- status: "success",
638
- message: `Permit2 allowance already sufficient (${existingAmount.toString()}). Skipped.`,
639
- data: { skipped: true, existingAllowance: existingAmount.toString() }
640
- };
641
- }
642
- }
643
- } catch {
644
- }
645
- }
646
- const allowanceSelection = await new Promise((resolve) => {
647
- allowanceSelectionResolverRef.current = resolve;
648
- setPendingAllowanceSelection(action);
649
- });
650
- if (allowanceSelection.topUpAmount > 0 && sessionIdRef.current) {
651
- try {
652
- await updateUserConfigBySession(
653
- apiBaseUrl,
654
- sessionIdRef.current,
655
- { defaultAllowance: allowanceSelection.topUpAmount }
656
- );
657
- } catch {
658
- }
659
- }
660
- const tokenDecimals = Number(action.metadata?.tokenDecimals ?? 6);
661
- const topUpSmallestUnit = BigInt(
662
- Math.round(allowanceSelection.topUpAmount * 10 ** tokenDecimals)
663
- );
664
- const baseAmount = metadataAmount ? BigInt(metadataAmount) : BigInt(0);
665
- const permitAmount = baseAmount + topUpSmallestUnit;
666
- const nonce = onChainNonce ?? Number(action.metadata?.nonce ?? 0);
667
- const sigDeadline = BigInt(Math.floor(Date.now() / 1e3) + 3600);
668
- const expiration = Math.floor(Date.now() / 1e3) + 30 * 24 * 60 * 60;
669
- const signature = await signTypedDataAsync({
670
- domain: {
671
- name: "Permit2",
672
- chainId: permit2ChainId,
673
- verifyingContract: PERMIT2_ADDRESS
674
- },
675
- types: {
676
- PermitSingle: [
677
- { name: "details", type: "PermitDetails" },
678
- { name: "spender", type: "address" },
679
- { name: "sigDeadline", type: "uint256" }
680
- ],
681
- PermitDetails: [
682
- { name: "token", type: "address" },
683
- { name: "amount", type: "uint160" },
684
- { name: "expiration", type: "uint48" },
685
- { name: "nonce", type: "uint48" }
686
- ]
687
- },
688
- primaryType: "PermitSingle",
689
- message: {
690
- details: {
691
- token: tokenAddress,
692
- amount: permitAmount,
693
- expiration,
694
- nonce
695
- },
696
- spender: spenderAddress,
697
- sigDeadline
698
- }
1035
+ const signature = await walletClient.signMessage({
1036
+ message: JSON.stringify(signingPayload)
699
1037
  });
1038
+ const tokenSummary = tokens?.map((t) => `${t.symbol} on ${t.chainName}`).join(", ") ?? "all tokens";
700
1039
  return {
701
1040
  actionId: action.id,
702
1041
  type: action.type,
703
1042
  status: "success",
704
- message: "Permit2 allowance signature obtained.",
1043
+ message: `Permissions granted for ${tokenSummary}.`,
705
1044
  data: {
706
- signature,
707
- signer: address,
708
- nonce: nonce.toString(),
709
- sigDeadline: sigDeadline.toString(),
710
- expiration: expiration.toString(),
711
- amount: permitAmount.toString()
1045
+ signedDelegation: signature,
1046
+ tokens
712
1047
  }
713
1048
  };
714
1049
  } catch (err) {
@@ -716,14 +1051,15 @@ function useAuthorizationExecutor() {
716
1051
  actionId: action.id,
717
1052
  type: action.type,
718
1053
  status: "error",
719
- message: err instanceof Error ? err.message : "Failed to sign Permit2"
1054
+ message: err instanceof Error ? err.message : "Failed to grant permissions"
720
1055
  };
721
1056
  }
722
1057
  },
723
- [address, currentChainId, signTypedDataAsync, apiBaseUrl]
1058
+ [wagmiConfig2]
724
1059
  );
725
1060
  const executeAction = react.useCallback(
726
1061
  async (action) => {
1062
+ setCurrentAction(action);
727
1063
  switch (action.type) {
728
1064
  case "OPEN_PROVIDER":
729
1065
  return executeOpenProvider(action);
@@ -731,10 +1067,12 @@ function useAuthorizationExecutor() {
731
1067
  return executeSelectSource(action);
732
1068
  case "SWITCH_CHAIN":
733
1069
  return executeSwitchChain(action);
734
- case "APPROVE_PERMIT_2":
735
- return executeApprovePermit2(action);
736
- case "SIGN_PERMIT2":
737
- 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);
738
1076
  default:
739
1077
  return {
740
1078
  actionId: action.id,
@@ -744,16 +1082,15 @@ function useAuthorizationExecutor() {
744
1082
  };
745
1083
  }
746
1084
  },
747
- [executeOpenProvider, executeSelectSource, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
1085
+ [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeUpgradeSmartAccount, executeGrantPermissions]
748
1086
  );
749
1087
  const executeSession = react.useCallback(
750
1088
  async (transfer) => {
751
1089
  if (executingRef.current) return;
752
1090
  executingRef.current = true;
753
1091
  if (!transfer.authorizationSessions || transfer.authorizationSessions.length === 0) {
754
- setError("No authorization sessions available.");
755
1092
  executingRef.current = false;
756
- return;
1093
+ throw new Error("No authorization sessions available.");
757
1094
  }
758
1095
  const sessionId = transfer.authorizationSessions[0].id;
759
1096
  sessionIdRef.current = sessionId;
@@ -775,8 +1112,7 @@ function useAuthorizationExecutor() {
775
1112
  actionPollRetries++;
776
1113
  }
777
1114
  if (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED") {
778
- setError("Authorization actions were not created in time. Please try again.");
779
- return;
1115
+ throw new Error("Authorization actions were not created in time. Please try again.");
780
1116
  }
781
1117
  while (pendingActions.length > 0) {
782
1118
  const action = pendingActions[0];
@@ -785,8 +1121,7 @@ function useAuthorizationExecutor() {
785
1121
  if (result.status === "error") {
786
1122
  allResults.push(result);
787
1123
  setResults([...allResults]);
788
- setError(result.message);
789
- break;
1124
+ throw new Error(result.message);
790
1125
  }
791
1126
  completedActionIds.add(action.id);
792
1127
  const updatedSession = await reportActionCompletion(
@@ -798,7 +1133,6 @@ function useAuthorizationExecutor() {
798
1133
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
799
1134
  if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
800
1135
  const chainResults = [result];
801
- let chainBroken = false;
802
1136
  while (pendingActions.length > 0) {
803
1137
  const nextAction = pendingActions[0];
804
1138
  const nextResult = await executeAction(nextAction);
@@ -806,9 +1140,7 @@ function useAuthorizationExecutor() {
806
1140
  chainResults.push(nextResult);
807
1141
  allResults.push(...chainResults);
808
1142
  setResults([...allResults]);
809
- setError(nextResult.message);
810
- chainBroken = true;
811
- break;
1143
+ throw new Error(nextResult.message);
812
1144
  }
813
1145
  completedActionIds.add(nextAction.id);
814
1146
  const nextSession = await reportActionCompletion(
@@ -820,7 +1152,6 @@ function useAuthorizationExecutor() {
820
1152
  chainResults.push(nextResult);
821
1153
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
822
1154
  }
823
- if (chainBroken) break;
824
1155
  allResults.push(...chainResults);
825
1156
  setResults([...allResults]);
826
1157
  continue;
@@ -829,8 +1160,11 @@ function useAuthorizationExecutor() {
829
1160
  setResults([...allResults]);
830
1161
  }
831
1162
  } catch (err) {
832
- setError(err instanceof Error ? err.message : "Authorization failed");
1163
+ const msg = err instanceof Error ? err.message : "Authorization failed";
1164
+ setError(msg);
1165
+ throw err;
833
1166
  } finally {
1167
+ setCurrentAction(null);
834
1168
  setExecuting(false);
835
1169
  executingRef.current = false;
836
1170
  }
@@ -841,13 +1175,99 @@ function useAuthorizationExecutor() {
841
1175
  executing,
842
1176
  results,
843
1177
  error,
1178
+ currentAction,
844
1179
  pendingSelectSource,
845
1180
  resolveSelectSource,
846
- pendingAllowanceSelection,
847
- resolveAllowanceSelection,
848
1181
  executeSession
849
1182
  };
850
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 };
1270
+ }
851
1271
  function Spinner({ size = 40, label }) {
852
1272
  const { tokens } = useSwypeConfig();
853
1273
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1579,204 +1999,6 @@ function AdvancedSettings({
1579
1999
  )
1580
2000
  ] });
1581
2001
  }
1582
- var TOP_UP_OPTIONS = [0, 25, 50, 100, 500];
1583
- function AllowanceSelector({ action, onSelect }) {
1584
- const { tokens } = useSwypeConfig();
1585
- const metadataAmount = action.metadata?.amount;
1586
- const tokenDecimals = Number(action.metadata?.tokenDecimals ?? 6);
1587
- const currency = action.metadata?.currency ?? "USD";
1588
- const transferAmountRaw = metadataAmount ? Number(BigInt(metadataAmount)) / 10 ** tokenDecimals : 0;
1589
- const [selectedTopUp, setSelectedTopUp] = react.useState(0);
1590
- const totalAllowance = transferAmountRaw + selectedTopUp;
1591
- const formatAmount = (amount) => `$${amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
1592
- return /* @__PURE__ */ jsxRuntime.jsxs(
1593
- "div",
1594
- {
1595
- style: {
1596
- padding: "4px 0",
1597
- textAlign: "left"
1598
- },
1599
- children: [
1600
- /* @__PURE__ */ jsxRuntime.jsx(
1601
- "h3",
1602
- {
1603
- style: {
1604
- fontSize: "1rem",
1605
- fontWeight: 600,
1606
- color: tokens.text,
1607
- margin: "0 0 6px 0",
1608
- textAlign: "center"
1609
- },
1610
- children: "Set spending limit"
1611
- }
1612
- ),
1613
- /* @__PURE__ */ jsxRuntime.jsx(
1614
- "p",
1615
- {
1616
- style: {
1617
- fontSize: "0.8rem",
1618
- color: tokens.textMuted,
1619
- margin: "0 0 16px 0",
1620
- lineHeight: 1.5,
1621
- textAlign: "center"
1622
- },
1623
- children: "Pre-authorize a higher amount so future payments go through instantly without wallet prompts."
1624
- }
1625
- ),
1626
- /* @__PURE__ */ jsxRuntime.jsxs(
1627
- "div",
1628
- {
1629
- style: {
1630
- display: "flex",
1631
- justifyContent: "space-between",
1632
- alignItems: "center",
1633
- padding: "10px 14px",
1634
- background: tokens.bgInput,
1635
- borderRadius: tokens.radius,
1636
- marginBottom: "12px",
1637
- fontSize: "0.825rem"
1638
- },
1639
- children: [
1640
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: tokens.textSecondary }, children: "This payment" }),
1641
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: tokens.text }, children: [
1642
- formatAmount(transferAmountRaw),
1643
- " ",
1644
- currency
1645
- ] })
1646
- ]
1647
- }
1648
- ),
1649
- /* @__PURE__ */ jsxRuntime.jsx(
1650
- "div",
1651
- {
1652
- style: {
1653
- display: "flex",
1654
- flexDirection: "column",
1655
- gap: "6px",
1656
- marginBottom: "16px"
1657
- },
1658
- children: TOP_UP_OPTIONS.map((topUp) => {
1659
- const isSelected = selectedTopUp === topUp;
1660
- const total = transferAmountRaw + topUp;
1661
- const label = topUp === 0 ? "Just this payment" : `+${formatAmount(topUp)} for future payments`;
1662
- return /* @__PURE__ */ jsxRuntime.jsxs(
1663
- "button",
1664
- {
1665
- onClick: () => setSelectedTopUp(topUp),
1666
- style: {
1667
- display: "flex",
1668
- alignItems: "center",
1669
- justifyContent: "space-between",
1670
- width: "100%",
1671
- padding: "12px 14px",
1672
- background: isSelected ? tokens.accent + "14" : "transparent",
1673
- border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1674
- borderRadius: tokens.radius,
1675
- cursor: "pointer",
1676
- color: tokens.text,
1677
- fontFamily: "inherit",
1678
- fontSize: "0.825rem",
1679
- textAlign: "left",
1680
- outline: "none",
1681
- transition: "all 0.12s ease"
1682
- },
1683
- children: [
1684
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1685
- /* @__PURE__ */ jsxRuntime.jsx(
1686
- "div",
1687
- {
1688
- style: {
1689
- width: 16,
1690
- height: 16,
1691
- borderRadius: "50%",
1692
- border: `2px solid ${isSelected ? tokens.accent : tokens.border}`,
1693
- display: "flex",
1694
- alignItems: "center",
1695
- justifyContent: "center",
1696
- flexShrink: 0
1697
- },
1698
- children: isSelected && /* @__PURE__ */ jsxRuntime.jsx(
1699
- "div",
1700
- {
1701
- style: {
1702
- width: 8,
1703
- height: 8,
1704
- borderRadius: "50%",
1705
- background: tokens.accent
1706
- }
1707
- }
1708
- )
1709
- }
1710
- ),
1711
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: isSelected ? 600 : 400 }, children: label })
1712
- ] }),
1713
- /* @__PURE__ */ jsxRuntime.jsx(
1714
- "span",
1715
- {
1716
- style: {
1717
- fontWeight: 600,
1718
- color: isSelected ? tokens.accent : tokens.textMuted,
1719
- fontSize: "0.8rem",
1720
- flexShrink: 0,
1721
- marginLeft: "8px"
1722
- },
1723
- children: formatAmount(total)
1724
- }
1725
- )
1726
- ]
1727
- },
1728
- topUp
1729
- );
1730
- })
1731
- }
1732
- ),
1733
- /* @__PURE__ */ jsxRuntime.jsxs(
1734
- "div",
1735
- {
1736
- style: {
1737
- display: "flex",
1738
- justifyContent: "space-between",
1739
- alignItems: "center",
1740
- padding: "10px 14px",
1741
- background: tokens.bgInput,
1742
- borderRadius: tokens.radius,
1743
- marginBottom: "14px",
1744
- fontSize: "0.825rem"
1745
- },
1746
- children: [
1747
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: tokens.textSecondary }, children: "Total authorization" }),
1748
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 700, color: tokens.text, fontSize: "0.9rem" }, children: [
1749
- formatAmount(totalAllowance),
1750
- " ",
1751
- currency
1752
- ] })
1753
- ]
1754
- }
1755
- ),
1756
- /* @__PURE__ */ jsxRuntime.jsx(
1757
- "button",
1758
- {
1759
- onClick: () => onSelect({ topUpAmount: selectedTopUp }),
1760
- style: {
1761
- width: "100%",
1762
- padding: "14px",
1763
- background: tokens.accent,
1764
- color: tokens.accentText,
1765
- border: "none",
1766
- borderRadius: tokens.radius,
1767
- fontSize: "1rem",
1768
- fontWeight: 600,
1769
- cursor: "pointer",
1770
- transition: "background 0.15s ease",
1771
- fontFamily: "inherit"
1772
- },
1773
- children: "Authorize & Sign"
1774
- }
1775
- )
1776
- ]
1777
- }
1778
- );
1779
- }
1780
2002
  function isMobile() {
1781
2003
  if (typeof navigator === "undefined") return false;
1782
2004
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
@@ -1787,11 +2009,11 @@ function computeSmartDefaults(accts, transferAmount) {
1787
2009
  if (accts.length === 0) return null;
1788
2010
  for (const acct of accts) {
1789
2011
  for (const wallet of acct.wallets) {
1790
- if (wallet.status === "ACTIVE" && wallet.lastAuthorizedToken) {
1791
- const matchingSource = wallet.sources.find(
1792
- (s) => s.token.symbol === wallet.lastAuthorizedToken.symbol
2012
+ if (wallet.status === "ACTIVE") {
2013
+ const bestSource = wallet.sources.find(
2014
+ (s) => s.balance.available.amount >= transferAmount
1793
2015
  );
1794
- if (matchingSource && matchingSource.balance.available.amount >= transferAmount) {
2016
+ if (bestSource) {
1795
2017
  return { accountId: acct.id, walletId: wallet.id };
1796
2018
  }
1797
2019
  }
@@ -1848,6 +2070,7 @@ function SwypePayment({
1848
2070
  const pollingTransferIdRef = react.useRef(null);
1849
2071
  const authExecutor = useAuthorizationExecutor();
1850
2072
  const polling = useTransferPolling();
2073
+ const transferSigning = useTransferSigning();
1851
2074
  const sourceType = connectingNewAccount ? "providerId" : selectedWalletId ? "walletId" : selectedAccountId ? "accountId" : "providerId";
1852
2075
  const sourceId = connectingNewAccount ? selectedProviderId ?? "" : selectedWalletId ? selectedWalletId : selectedAccountId ? selectedAccountId : selectedProviderId ?? "";
1853
2076
  react.useEffect(() => {
@@ -2027,9 +2250,11 @@ function SwypePayment({
2027
2250
  await authExecutor.executeSession(t);
2028
2251
  }
2029
2252
  }
2253
+ const signedTransfer = await transferSigning.signTransfer(t.id);
2254
+ setTransfer(signedTransfer);
2030
2255
  polling.startPolling(t.id);
2031
2256
  } catch (err) {
2032
- const msg = err instanceof Error ? err.message : "Transfer creation failed";
2257
+ const msg = err instanceof Error ? err.message : "Transfer failed";
2033
2258
  setError(msg);
2034
2259
  onError?.(msg);
2035
2260
  setStep("ready");
@@ -2044,6 +2269,7 @@ function SwypePayment({
2044
2269
  apiBaseUrl,
2045
2270
  getAccessToken,
2046
2271
  authExecutor,
2272
+ transferSigning,
2047
2273
  polling,
2048
2274
  onError
2049
2275
  ]);
@@ -2512,17 +2738,68 @@ function SwypePayment({
2512
2738
  ] });
2513
2739
  }
2514
2740
  if (step === "processing") {
2515
- if (authExecutor.pendingAllowanceSelection) {
2516
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
2517
- AllowanceSelector,
2518
- {
2519
- action: authExecutor.pendingAllowanceSelection,
2520
- onSelect: authExecutor.resolveAllowanceSelection
2521
- }
2522
- ) });
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
+ ] }) });
2523
2777
  }
2524
- const statusLabel = creatingTransfer ? "Creating transfer..." : mobileFlow ? "Waiting for authorization..." : authExecutor.executing ? "Authorizing..." : polling.isPolling ? "Processing payment..." : "Please wait...";
2525
- 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...";
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...";
2526
2803
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
2527
2804
  /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 48 }),
2528
2805
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2566,11 +2843,11 @@ function SwypePayment({
2566
2843
  },
2567
2844
  r.actionId
2568
2845
  )) }),
2569
- (error || authExecutor.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
2846
+ (error || authExecutor.error || transferSigning.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
2570
2847
  "div",
2571
2848
  {
2572
2849
  style: { ...errorStyle, marginTop: "16px", textAlign: "left" },
2573
- children: error || authExecutor.error || polling.error
2850
+ children: error || authExecutor.error || transferSigning.error || polling.error
2574
2851
  }
2575
2852
  )
2576
2853
  ] }) });