@swype-org/react-sdk 0.1.2 → 0.1.4

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,302 @@ 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
+ });
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
+ });
269
409
  }
270
- const client = viem.createPublicClient({
271
- chain: viemChain,
272
- transport: viem.http()
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
+ async function waitForWalletClient(wagmiConfig2, walletClientParams = {}, maxAttempts = 15, intervalMs = 200) {
531
+ for (let i = 0; i < maxAttempts; i++) {
532
+ try {
533
+ return await getWalletClient(wagmiConfig2, walletClientParams);
534
+ } catch {
535
+ if (i === maxAttempts - 1) {
536
+ throw new Error("Wallet not ready. Please try again.");
537
+ }
538
+ await new Promise((r) => setTimeout(r, intervalMs));
539
+ }
540
+ }
541
+ throw new Error("Wallet not ready. Please try again.");
542
+ }
543
+ function parseSignTypedDataPayload(typedData) {
544
+ const domain = typedData.domain;
545
+ const types = typedData.types;
546
+ const primaryType = typedData.primaryType;
547
+ const message = typedData.message;
548
+ if (!domain || typeof domain !== "object" || Array.isArray(domain)) {
549
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid domain object.");
550
+ }
551
+ if (!types || typeof types !== "object" || Array.isArray(types)) {
552
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid types object.");
553
+ }
554
+ if (typeof primaryType !== "string") {
555
+ throw new Error("SIGN_PERMIT2 typedData is missing primaryType.");
556
+ }
557
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
558
+ throw new Error("SIGN_PERMIT2 typedData is missing a valid message object.");
559
+ }
560
+ return {
561
+ domain,
562
+ types,
563
+ primaryType,
564
+ message
565
+ };
276
566
  }
277
567
  function useTransferPolling(intervalMs = 3e3) {
278
568
  const { apiBaseUrl } = useSwypeConfig();
@@ -324,14 +614,13 @@ function useTransferPolling(intervalMs = 3e3) {
324
614
  }
325
615
  function useAuthorizationExecutor() {
326
616
  const { apiBaseUrl } = useSwypeConfig();
327
- const { address, chainId: currentChainId, isConnected } = wagmi.useAccount();
617
+ const wagmiConfig2 = wagmi.useConfig();
328
618
  const { connectAsync, connectors } = wagmi.useConnect();
329
619
  const { switchChainAsync } = wagmi.useSwitchChain();
330
- const { signTypedDataAsync } = wagmi.useSignTypedData();
331
- const { data: walletClient } = wagmi.useWalletClient();
332
620
  const [executing, setExecuting] = react.useState(false);
333
621
  const [results, setResults] = react.useState([]);
334
622
  const [error, setError] = react.useState(null);
623
+ const [currentAction, setCurrentAction] = react.useState(null);
335
624
  const executingRef = react.useRef(false);
336
625
  const [pendingSelectSource, setPendingSelectSource] = react.useState(null);
337
626
  const selectSourceResolverRef = react.useRef(null);
@@ -342,31 +631,28 @@ function useAuthorizationExecutor() {
342
631
  setPendingSelectSource(null);
343
632
  }
344
633
  }, []);
345
- const [pendingAllowanceSelection, setPendingAllowanceSelection] = react.useState(null);
346
- const allowanceSelectionResolverRef = react.useRef(null);
347
634
  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
635
  const executeOpenProvider = react.useCallback(
356
636
  async (action) => {
357
637
  try {
358
- if (isConnected && address) {
359
- const hexChainId2 = currentChainId ? `0x${currentChainId.toString(16)}` : void 0;
638
+ const account = getAccount(wagmiConfig2);
639
+ if (account.isConnected && account.address) {
640
+ const hexChainId2 = account.chainId ? `0x${account.chainId.toString(16)}` : void 0;
360
641
  return {
361
642
  actionId: action.id,
362
643
  type: action.type,
363
644
  status: "success",
364
- message: `Connected. Account: ${address}, Chain: ${hexChainId2}`,
365
- data: { accounts: [address], chainId: hexChainId2 }
645
+ message: `Connected. Account: ${account.address}, Chain: ${hexChainId2}`,
646
+ data: { accounts: [account.address], chainId: hexChainId2 }
366
647
  };
367
648
  }
368
649
  const targetId = action.metadata?.wagmiConnectorId;
369
- const connector = targetId ? connectors.find((c) => c.id === targetId) ?? connectors[0] : connectors[0];
650
+ const metaMaskConnector = connectors.find((c) => {
651
+ const id = c.id.toLowerCase();
652
+ const name = c.name.toLowerCase();
653
+ return id.includes("metamask") || name.includes("metamask");
654
+ });
655
+ const connector = targetId ? connectors.find((c) => c.id === targetId) ?? metaMaskConnector ?? connectors[0] : metaMaskConnector ?? connectors[0];
370
656
  if (!connector) {
371
657
  return {
372
658
  actionId: action.id,
@@ -393,7 +679,7 @@ function useAuthorizationExecutor() {
393
679
  };
394
680
  }
395
681
  },
396
- [isConnected, address, currentChainId, connectors, connectAsync]
682
+ [wagmiConfig2, connectors, connectAsync]
397
683
  );
398
684
  const executeSelectSource = react.useCallback(
399
685
  async (action) => {
@@ -441,6 +727,7 @@ function useAuthorizationExecutor() {
441
727
  const executeSwitchChain = react.useCallback(
442
728
  async (action) => {
443
729
  try {
730
+ const account = getAccount(wagmiConfig2);
444
731
  const targetChainIdHex = action.metadata?.targetChainId;
445
732
  if (!targetChainIdHex) {
446
733
  return {
@@ -452,7 +739,7 @@ function useAuthorizationExecutor() {
452
739
  }
453
740
  const targetChainIdNum = parseInt(targetChainIdHex, 16);
454
741
  const hexChainId = `0x${targetChainIdNum.toString(16)}`;
455
- if (currentChainId === targetChainIdNum) {
742
+ if (account.chainId === targetChainIdNum) {
456
743
  return {
457
744
  actionId: action.id,
458
745
  type: action.type,
@@ -478,252 +765,253 @@ function useAuthorizationExecutor() {
478
765
  };
479
766
  }
480
767
  },
481
- [currentChainId, switchChainAsync]
768
+ [wagmiConfig2, switchChainAsync]
482
769
  );
483
- const executeApprovePermit2 = react.useCallback(
770
+ const executeRegisterPasskey = react.useCallback(
484
771
  async (action) => {
485
772
  try {
486
- const tokenAddress = action.metadata?.tokenAddress;
487
- const permit2Address = action.metadata?.permit2Address;
488
- const metadataChainId = action.metadata?.chainId;
489
- if (!tokenAddress || !permit2Address) {
773
+ const account = getAccount(wagmiConfig2);
774
+ const challenge = new Uint8Array(32);
775
+ crypto.getRandomValues(challenge);
776
+ const credential = await navigator.credentials.create({
777
+ publicKey: {
778
+ challenge,
779
+ rp: {
780
+ name: "Swype",
781
+ id: window.location.hostname
782
+ },
783
+ user: {
784
+ id: new TextEncoder().encode(account.address ?? "user"),
785
+ name: account.address ?? "Swype User",
786
+ displayName: "Swype User"
787
+ },
788
+ pubKeyCredParams: [
789
+ { alg: -7, type: "public-key" },
790
+ // ES256 (P-256)
791
+ { alg: -257, type: "public-key" }
792
+ // RS256
793
+ ],
794
+ authenticatorSelection: {
795
+ authenticatorAttachment: "platform",
796
+ residentKey: "preferred",
797
+ userVerification: "required"
798
+ },
799
+ timeout: 6e4
800
+ }
801
+ });
802
+ if (!credential) {
490
803
  return {
491
804
  actionId: action.id,
492
805
  type: action.type,
493
806
  status: "error",
494
- message: "Missing tokenAddress or permit2Address in action metadata."
807
+ message: "Passkey creation was cancelled."
495
808
  };
496
809
  }
497
- if (!address) {
810
+ const response = credential.response;
811
+ const publicKeyBytes = response.getPublicKey?.();
812
+ const credentialId = btoa(
813
+ String.fromCharCode(...new Uint8Array(credential.rawId))
814
+ );
815
+ const publicKey = publicKeyBytes ? btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes))) : "";
816
+ return {
817
+ actionId: action.id,
818
+ type: action.type,
819
+ status: "success",
820
+ message: "Passkey created successfully.",
821
+ data: {
822
+ credentialId,
823
+ publicKey
824
+ }
825
+ };
826
+ } catch (err) {
827
+ return {
828
+ actionId: action.id,
829
+ type: action.type,
830
+ status: "error",
831
+ message: err instanceof Error ? err.message : "Failed to create passkey"
832
+ };
833
+ }
834
+ },
835
+ [wagmiConfig2]
836
+ );
837
+ const executeCreateSmartAccount = react.useCallback(
838
+ async (action) => {
839
+ return {
840
+ actionId: action.id,
841
+ type: action.type,
842
+ status: "success",
843
+ message: "Smart account creation acknowledged. Server is deploying.",
844
+ data: {}
845
+ };
846
+ },
847
+ []
848
+ );
849
+ const executeApprovePermit2 = react.useCallback(
850
+ async (action) => {
851
+ try {
852
+ const walletClient = await waitForWalletClient(wagmiConfig2);
853
+ const account = getAccount(wagmiConfig2);
854
+ const sender = account.address ?? walletClient.account?.address;
855
+ const expectedWalletAddress = action.metadata?.walletAddress;
856
+ if (!sender) {
857
+ throw new Error("Wallet account not available. Please connect your wallet.");
858
+ }
859
+ if (expectedWalletAddress && sender.toLowerCase() !== expectedWalletAddress.toLowerCase()) {
498
860
  return {
499
861
  actionId: action.id,
500
862
  type: action.type,
501
863
  status: "error",
502
- message: "Wallet not connected."
864
+ message: `Connected wallet ${sender} does not match the required source wallet ${expectedWalletAddress}. Please switch accounts in your wallet and retry.`
503
865
  };
504
866
  }
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 {
547
- }
548
- }
549
- if (!walletClient) {
867
+ const to = action.metadata?.to;
868
+ const data = action.metadata?.data;
869
+ const tokenSymbol = action.metadata?.tokenSymbol;
870
+ if (!to || !data) {
550
871
  return {
551
872
  actionId: action.id,
552
873
  type: action.type,
553
874
  status: "error",
554
- message: "Wallet client not available."
875
+ message: "APPROVE_PERMIT2 metadata is missing transaction parameters (to, data)."
555
876
  };
556
877
  }
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]
878
+ const txHash = await walletClient.request({
879
+ method: "eth_sendTransaction",
880
+ params: [
881
+ {
882
+ from: sender,
883
+ to,
884
+ data,
885
+ value: "0x0"
886
+ }
887
+ ]
563
888
  });
564
- if (chainClient) {
565
- await chainClient.waitForTransactionReceipt({ hash: txHash });
566
- }
889
+ console.info(
890
+ `[swype-sdk][approve-permit2] ERC-20 approve tx sent. token=${tokenSymbol ?? to}, txHash=${txHash}`
891
+ );
567
892
  return {
568
893
  actionId: action.id,
569
894
  type: action.type,
570
895
  status: "success",
571
- message: `Permit2 approved. Tx: ${txHash}`,
572
- data: { txHash, approved: true }
896
+ message: `Approved Permit2 to spend ${tokenSymbol ?? "tokens"}.`,
897
+ data: { txHash }
573
898
  };
574
899
  } catch (err) {
900
+ const message = err instanceof Error ? err.message : "Failed to approve Permit2";
901
+ const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
575
902
  return {
576
903
  actionId: action.id,
577
904
  type: action.type,
578
905
  status: "error",
579
- message: err instanceof Error ? err.message : "Failed to approve Permit2"
906
+ message: isRejected ? "You rejected the approval transaction. Please approve the Permit2 spending allowance in your wallet to continue." : message
580
907
  };
581
908
  }
582
909
  },
583
- [address, currentChainId, walletClient]
910
+ [wagmiConfig2]
584
911
  );
585
912
  const executeSignPermit2 = react.useCallback(
586
913
  async (action) => {
587
914
  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) {
915
+ const expectedWalletAddress = action.metadata?.walletAddress;
916
+ const walletClient = await waitForWalletClient(
917
+ wagmiConfig2,
918
+ expectedWalletAddress ? { account: expectedWalletAddress } : {}
919
+ );
920
+ const account = getAccount(wagmiConfig2);
921
+ const connectedAddress = account.address ?? walletClient.account?.address;
922
+ const sender = expectedWalletAddress ?? connectedAddress;
923
+ if (!sender) {
924
+ throw new Error("Wallet account not available. Please connect your wallet.");
925
+ }
926
+ if (expectedWalletAddress && connectedAddress && connectedAddress.toLowerCase() !== expectedWalletAddress.toLowerCase()) {
611
927
  return {
612
928
  actionId: action.id,
613
929
  type: action.type,
614
930
  status: "error",
615
- message: "Missing spenderAddress or tokenAddress in action metadata."
931
+ message: `Connected wallet ${sender} does not match the required source wallet ${expectedWalletAddress}. Please switch accounts in your wallet and retry.`
616
932
  };
617
933
  }
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(
934
+ let typedData = action.metadata?.typedData;
935
+ const tokenSymbol = action.metadata?.tokenSymbol;
936
+ if (!typedData && sessionIdRef.current) {
937
+ const POLL_INTERVAL_MS = 1e3;
938
+ const MAX_POLLS = 15;
939
+ for (let i = 0; i < MAX_POLLS; i++) {
940
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
941
+ const session = await fetchAuthorizationSession(
653
942
  apiBaseUrl,
654
- sessionIdRef.current,
655
- { defaultAllowance: allowanceSelection.topUpAmount }
943
+ sessionIdRef.current
656
944
  );
657
- } catch {
945
+ const updatedAction = session.actions.find((a) => a.id === action.id);
946
+ typedData = updatedAction?.metadata?.typedData;
947
+ if (typedData) break;
658
948
  }
659
949
  }
660
- const tokenDecimals = Number(action.metadata?.tokenDecimals ?? 6);
661
- const topUpSmallestUnit = BigInt(
662
- Math.round(allowanceSelection.topUpAmount * 10 ** tokenDecimals)
950
+ if (!typedData) {
951
+ return {
952
+ actionId: action.id,
953
+ type: action.type,
954
+ status: "error",
955
+ message: "SIGN_PERMIT2 metadata is missing typedData. The server may still be preparing the signing payload."
956
+ };
957
+ }
958
+ const parsedTypedData = parseSignTypedDataPayload(typedData);
959
+ console.info(
960
+ `[swype-sdk][sign-permit2] Signing typed data. expectedOwner=${expectedWalletAddress ?? "N/A"}, senderParam=${sender}, connectedAddress=${connectedAddress ?? "N/A"}, primaryType=${parsedTypedData.primaryType}, domainChainId=${String(parsedTypedData.domain.chainId ?? "N/A")}, verifyingContract=${String(parsedTypedData.domain.verifyingContract ?? "N/A")}`
663
961
  );
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
- }
962
+ const signature = await walletClient.signTypedData({
963
+ account: sender,
964
+ domain: parsedTypedData.domain,
965
+ types: parsedTypedData.types,
966
+ primaryType: parsedTypedData.primaryType,
967
+ message: parsedTypedData.message
699
968
  });
969
+ const recoverInput = {
970
+ domain: parsedTypedData.domain,
971
+ types: parsedTypedData.types,
972
+ primaryType: parsedTypedData.primaryType,
973
+ message: parsedTypedData.message,
974
+ signature
975
+ };
976
+ const recoveredSigner = await viem.recoverTypedDataAddress(recoverInput);
977
+ const expectedSigner = (expectedWalletAddress ?? sender).toLowerCase();
978
+ console.info(
979
+ `[swype-sdk][sign-permit2] Signature recovered. recoveredSigner=${recoveredSigner}, expectedSigner=${expectedSigner}`
980
+ );
981
+ if (recoveredSigner.toLowerCase() !== expectedSigner) {
982
+ return {
983
+ actionId: action.id,
984
+ type: action.type,
985
+ status: "error",
986
+ message: `Wallet signed with ${recoveredSigner}, but source wallet is ${expectedWalletAddress ?? sender}. Please switch to the source wallet in MetaMask and retry.`
987
+ };
988
+ }
989
+ console.info(
990
+ `[swype-sdk][sign-permit2] Permit2 EIP-712 signature obtained. token=${tokenSymbol ?? "unknown"}`
991
+ );
700
992
  return {
701
993
  actionId: action.id,
702
994
  type: action.type,
703
995
  status: "success",
704
- message: "Permit2 allowance signature obtained.",
705
- data: {
706
- signature,
707
- signer: address,
708
- nonce: nonce.toString(),
709
- sigDeadline: sigDeadline.toString(),
710
- expiration: expiration.toString(),
711
- amount: permitAmount.toString()
712
- }
996
+ message: `Permit2 allowance signed for ${tokenSymbol ?? "tokens"}.`,
997
+ data: { signature }
713
998
  };
714
999
  } catch (err) {
1000
+ const message = err instanceof Error ? err.message : "Failed to sign Permit2 allowance";
1001
+ const isRejected = message.includes("rejected") || message.includes("denied") || message.includes("user rejected");
715
1002
  return {
716
1003
  actionId: action.id,
717
1004
  type: action.type,
718
1005
  status: "error",
719
- message: err instanceof Error ? err.message : "Failed to sign Permit2"
1006
+ message: isRejected ? "You rejected the Permit2 signature request. Please approve the signature in your wallet to allow fund transfers." : message
720
1007
  };
721
1008
  }
722
1009
  },
723
- [address, currentChainId, signTypedDataAsync, apiBaseUrl]
1010
+ [wagmiConfig2, apiBaseUrl]
724
1011
  );
725
1012
  const executeAction = react.useCallback(
726
1013
  async (action) => {
1014
+ setCurrentAction(action);
727
1015
  switch (action.type) {
728
1016
  case "OPEN_PROVIDER":
729
1017
  return executeOpenProvider(action);
@@ -731,7 +1019,11 @@ function useAuthorizationExecutor() {
731
1019
  return executeSelectSource(action);
732
1020
  case "SWITCH_CHAIN":
733
1021
  return executeSwitchChain(action);
734
- case "APPROVE_PERMIT_2":
1022
+ case "REGISTER_PASSKEY":
1023
+ return executeRegisterPasskey(action);
1024
+ case "CREATE_SMART_ACCOUNT":
1025
+ return executeCreateSmartAccount(action);
1026
+ case "APPROVE_PERMIT2":
735
1027
  return executeApprovePermit2(action);
736
1028
  case "SIGN_PERMIT2":
737
1029
  return executeSignPermit2(action);
@@ -744,16 +1036,15 @@ function useAuthorizationExecutor() {
744
1036
  };
745
1037
  }
746
1038
  },
747
- [executeOpenProvider, executeSelectSource, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
1039
+ [executeOpenProvider, executeSelectSource, executeSwitchChain, executeRegisterPasskey, executeCreateSmartAccount, executeApprovePermit2, executeSignPermit2]
748
1040
  );
749
1041
  const executeSession = react.useCallback(
750
1042
  async (transfer) => {
751
1043
  if (executingRef.current) return;
752
1044
  executingRef.current = true;
753
1045
  if (!transfer.authorizationSessions || transfer.authorizationSessions.length === 0) {
754
- setError("No authorization sessions available.");
755
1046
  executingRef.current = false;
756
- return;
1047
+ throw new Error("No authorization sessions available.");
757
1048
  }
758
1049
  const sessionId = transfer.authorizationSessions[0].id;
759
1050
  sessionIdRef.current = sessionId;
@@ -775,8 +1066,7 @@ function useAuthorizationExecutor() {
775
1066
  actionPollRetries++;
776
1067
  }
777
1068
  if (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED") {
778
- setError("Authorization actions were not created in time. Please try again.");
779
- return;
1069
+ throw new Error("Authorization actions were not created in time. Please try again.");
780
1070
  }
781
1071
  while (pendingActions.length > 0) {
782
1072
  const action = pendingActions[0];
@@ -785,8 +1075,7 @@ function useAuthorizationExecutor() {
785
1075
  if (result.status === "error") {
786
1076
  allResults.push(result);
787
1077
  setResults([...allResults]);
788
- setError(result.message);
789
- break;
1078
+ throw new Error(result.message);
790
1079
  }
791
1080
  completedActionIds.add(action.id);
792
1081
  const updatedSession = await reportActionCompletion(
@@ -798,7 +1087,6 @@ function useAuthorizationExecutor() {
798
1087
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
799
1088
  if (action.type === "OPEN_PROVIDER" && pendingActions.length > 0) {
800
1089
  const chainResults = [result];
801
- let chainBroken = false;
802
1090
  while (pendingActions.length > 0) {
803
1091
  const nextAction = pendingActions[0];
804
1092
  const nextResult = await executeAction(nextAction);
@@ -806,9 +1094,7 @@ function useAuthorizationExecutor() {
806
1094
  chainResults.push(nextResult);
807
1095
  allResults.push(...chainResults);
808
1096
  setResults([...allResults]);
809
- setError(nextResult.message);
810
- chainBroken = true;
811
- break;
1097
+ throw new Error(nextResult.message);
812
1098
  }
813
1099
  completedActionIds.add(nextAction.id);
814
1100
  const nextSession = await reportActionCompletion(
@@ -820,7 +1106,6 @@ function useAuthorizationExecutor() {
820
1106
  chainResults.push(nextResult);
821
1107
  pendingActions = currentSession.actions.filter((a) => a.status === "PENDING" && !completedActionIds.has(a.id)).sort((a, b) => a.orderIndex - b.orderIndex);
822
1108
  }
823
- if (chainBroken) break;
824
1109
  allResults.push(...chainResults);
825
1110
  setResults([...allResults]);
826
1111
  continue;
@@ -829,8 +1114,11 @@ function useAuthorizationExecutor() {
829
1114
  setResults([...allResults]);
830
1115
  }
831
1116
  } catch (err) {
832
- setError(err instanceof Error ? err.message : "Authorization failed");
1117
+ const msg = err instanceof Error ? err.message : "Authorization failed";
1118
+ setError(msg);
1119
+ throw err;
833
1120
  } finally {
1121
+ setCurrentAction(null);
834
1122
  setExecuting(false);
835
1123
  executingRef.current = false;
836
1124
  }
@@ -841,13 +1129,99 @@ function useAuthorizationExecutor() {
841
1129
  executing,
842
1130
  results,
843
1131
  error,
1132
+ currentAction,
844
1133
  pendingSelectSource,
845
1134
  resolveSelectSource,
846
- pendingAllowanceSelection,
847
- resolveAllowanceSelection,
848
1135
  executeSession
849
1136
  };
850
1137
  }
1138
+ function useTransferSigning(pollIntervalMs = 2e3) {
1139
+ const { apiBaseUrl } = useSwypeConfig();
1140
+ const { getAccessToken } = reactAuth.usePrivy();
1141
+ const [signing, setSigning] = react.useState(false);
1142
+ const [signPayload, setSignPayload] = react.useState(null);
1143
+ const [error, setError] = react.useState(null);
1144
+ const signTransfer2 = react.useCallback(
1145
+ async (transferId) => {
1146
+ setSigning(true);
1147
+ setError(null);
1148
+ setSignPayload(null);
1149
+ try {
1150
+ const token = await getAccessToken();
1151
+ if (!token) {
1152
+ throw new Error("Could not get access token");
1153
+ }
1154
+ const MAX_POLLS = 60;
1155
+ let payload = null;
1156
+ for (let i = 0; i < MAX_POLLS; i++) {
1157
+ const transfer = await fetchTransfer(apiBaseUrl, token, transferId);
1158
+ if (transfer.signPayload) {
1159
+ payload = transfer.signPayload;
1160
+ setSignPayload(payload);
1161
+ break;
1162
+ }
1163
+ if (transfer.status !== "AUTHORIZED" && transfer.status !== "CREATED") {
1164
+ throw new Error(`Unexpected transfer status: ${transfer.status}`);
1165
+ }
1166
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1167
+ }
1168
+ if (!payload) {
1169
+ throw new Error("Timed out waiting for sign payload. Please try again.");
1170
+ }
1171
+ const userOpHashHex = payload.userOpHash;
1172
+ const hashBytes = new Uint8Array(
1173
+ (userOpHashHex.startsWith("0x") ? userOpHashHex.slice(2) : userOpHashHex).match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
1174
+ );
1175
+ const assertion = await navigator.credentials.get({
1176
+ publicKey: {
1177
+ challenge: hashBytes,
1178
+ rpId: window.location.hostname,
1179
+ userVerification: "required",
1180
+ timeout: 6e4
1181
+ }
1182
+ });
1183
+ if (!assertion) {
1184
+ throw new Error("Passkey authentication was cancelled.");
1185
+ }
1186
+ const response = assertion.response;
1187
+ const signature = btoa(
1188
+ String.fromCharCode(...new Uint8Array(response.signature))
1189
+ );
1190
+ const authenticatorData = btoa(
1191
+ String.fromCharCode(
1192
+ ...new Uint8Array(response.authenticatorData)
1193
+ )
1194
+ );
1195
+ const clientDataJSON = btoa(
1196
+ String.fromCharCode(
1197
+ ...new Uint8Array(response.clientDataJSON)
1198
+ )
1199
+ );
1200
+ const signedUserOp = {
1201
+ ...payload.userOp,
1202
+ signature,
1203
+ authenticatorData,
1204
+ clientDataJSON
1205
+ };
1206
+ const updatedTransfer = await signTransfer(
1207
+ apiBaseUrl,
1208
+ token,
1209
+ transferId,
1210
+ signedUserOp
1211
+ );
1212
+ return updatedTransfer;
1213
+ } catch (err) {
1214
+ const msg = err instanceof Error ? err.message : "Failed to sign transfer";
1215
+ setError(msg);
1216
+ throw err;
1217
+ } finally {
1218
+ setSigning(false);
1219
+ }
1220
+ },
1221
+ [apiBaseUrl, getAccessToken, pollIntervalMs]
1222
+ );
1223
+ return { signing, signPayload, error, signTransfer: signTransfer2 };
1224
+ }
851
1225
  function Spinner({ size = 40, label }) {
852
1226
  const { tokens } = useSwypeConfig();
853
1227
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1579,204 +1953,6 @@ function AdvancedSettings({
1579
1953
  )
1580
1954
  ] });
1581
1955
  }
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
1956
  function isMobile() {
1781
1957
  if (typeof navigator === "undefined") return false;
1782
1958
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
@@ -1787,11 +1963,11 @@ function computeSmartDefaults(accts, transferAmount) {
1787
1963
  if (accts.length === 0) return null;
1788
1964
  for (const acct of accts) {
1789
1965
  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
1966
+ if (wallet.status === "ACTIVE") {
1967
+ const bestSource = wallet.sources.find(
1968
+ (s) => s.balance.available.amount >= transferAmount
1793
1969
  );
1794
- if (matchingSource && matchingSource.balance.available.amount >= transferAmount) {
1970
+ if (bestSource) {
1795
1971
  return { accountId: acct.id, walletId: wallet.id };
1796
1972
  }
1797
1973
  }
@@ -1848,6 +2024,7 @@ function SwypePayment({
1848
2024
  const pollingTransferIdRef = react.useRef(null);
1849
2025
  const authExecutor = useAuthorizationExecutor();
1850
2026
  const polling = useTransferPolling();
2027
+ const transferSigning = useTransferSigning();
1851
2028
  const sourceType = connectingNewAccount ? "providerId" : selectedWalletId ? "walletId" : selectedAccountId ? "accountId" : "providerId";
1852
2029
  const sourceId = connectingNewAccount ? selectedProviderId ?? "" : selectedWalletId ? selectedWalletId : selectedAccountId ? selectedAccountId : selectedProviderId ?? "";
1853
2030
  react.useEffect(() => {
@@ -2027,9 +2204,11 @@ function SwypePayment({
2027
2204
  await authExecutor.executeSession(t);
2028
2205
  }
2029
2206
  }
2207
+ const signedTransfer = await transferSigning.signTransfer(t.id);
2208
+ setTransfer(signedTransfer);
2030
2209
  polling.startPolling(t.id);
2031
2210
  } catch (err) {
2032
- const msg = err instanceof Error ? err.message : "Transfer creation failed";
2211
+ const msg = err instanceof Error ? err.message : "Transfer failed";
2033
2212
  setError(msg);
2034
2213
  onError?.(msg);
2035
2214
  setStep("ready");
@@ -2044,6 +2223,7 @@ function SwypePayment({
2044
2223
  apiBaseUrl,
2045
2224
  getAccessToken,
2046
2225
  authExecutor,
2226
+ transferSigning,
2047
2227
  polling,
2048
2228
  onError
2049
2229
  ]);
@@ -2512,17 +2692,73 @@ function SwypePayment({
2512
2692
  ] });
2513
2693
  }
2514
2694
  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
- ) });
2695
+ if (transferSigning.signing && transferSigning.signPayload) {
2696
+ const payload = transferSigning.signPayload;
2697
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
2698
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", style: { margin: "0 auto 16px" }, children: [
2699
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "48", height: "48", rx: "12", fill: tokens.accent + "20" }),
2700
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M24 14v8M20 18h8M24 26v2M24 32v2", stroke: tokens.accent, strokeWidth: "2", strokeLinecap: "round" })
2701
+ ] }),
2702
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { ...headingStyle, marginBottom: "8px" }, children: "Authorize Transfer" }),
2703
+ /* @__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." }),
2704
+ /* @__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: [
2705
+ payload.amount && payload.tokenSymbol && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2706
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Amount" }),
2707
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: tokens.text }, children: [
2708
+ payload.amount,
2709
+ " ",
2710
+ payload.tokenSymbol
2711
+ ] })
2712
+ ] }),
2713
+ payload.bridgeRelayAddress && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2714
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Bridge relay" }),
2715
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontFamily: '"SF Mono", "Fira Code", monospace', fontSize: "0.75rem" }, children: [
2716
+ payload.bridgeRelayAddress.slice(0, 6),
2717
+ "...",
2718
+ payload.bridgeRelayAddress.slice(-4)
2719
+ ] })
2720
+ ] }),
2721
+ payload.estimatedFeeUsd && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
2722
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Est. fee" }),
2723
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 600 }, children: [
2724
+ "$",
2725
+ payload.estimatedFeeUsd
2726
+ ] })
2727
+ ] })
2728
+ ] }),
2729
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner, { label: "Waiting for passkey..." })
2730
+ ] }) });
2523
2731
  }
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...";
2732
+ const currentActionType = authExecutor.currentAction?.type;
2733
+ const getRegistrationMessage = () => {
2734
+ switch (currentActionType) {
2735
+ case "REGISTER_PASSKEY":
2736
+ return {
2737
+ label: "Creating your passkey...",
2738
+ description: "Set up a passkey for secure, one-touch payments."
2739
+ };
2740
+ case "CREATE_SMART_ACCOUNT":
2741
+ return {
2742
+ label: "Creating your smart account...",
2743
+ description: "Setting up your smart account for gasless payments."
2744
+ };
2745
+ case "APPROVE_PERMIT2":
2746
+ return {
2747
+ label: "Approving token access...",
2748
+ description: "Approve the prompt in your wallet to allow secure token transfers."
2749
+ };
2750
+ case "SIGN_PERMIT2":
2751
+ return {
2752
+ label: "Signing transfer permission...",
2753
+ description: "Sign the permit to allow your smart account to transfer tokens on your behalf."
2754
+ };
2755
+ default:
2756
+ return { label: "", description: "" };
2757
+ }
2758
+ };
2759
+ const regMsg = getRegistrationMessage();
2760
+ 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...";
2761
+ 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
2762
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
2527
2763
  /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 48 }),
2528
2764
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -2566,11 +2802,11 @@ function SwypePayment({
2566
2802
  },
2567
2803
  r.actionId
2568
2804
  )) }),
2569
- (error || authExecutor.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
2805
+ (error || authExecutor.error || transferSigning.error || polling.error) && /* @__PURE__ */ jsxRuntime.jsx(
2570
2806
  "div",
2571
2807
  {
2572
2808
  style: { ...errorStyle, marginTop: "16px", textAlign: "left" },
2573
- children: error || authExecutor.error || polling.error
2809
+ children: error || authExecutor.error || transferSigning.error || polling.error
2574
2810
  }
2575
2811
  )
2576
2812
  ] }) });