0xtrails 0.8.0 → 0.8.2
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/{ccip-uMWNlvmJ.js → ccip-ru_Yzdas.js} +5 -5
- package/dist/chains.d.ts.map +1 -1
- package/dist/{index-BiPwqVkZ.js → index-Si7cO9V7.js} +10080 -9842
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -117
- package/dist/intents.d.ts.map +1 -1
- package/dist/mutations.d.ts.map +1 -1
- package/dist/{refund.d.ts → recover.d.ts} +27 -11
- package/dist/recover.d.ts.map +1 -0
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/index.js +3 -3
- package/package.json +2 -2
- package/src/chains.ts +25 -12
- package/src/index.ts +5 -5
- package/src/intents.ts +1 -5
- package/src/mutations.ts +0 -2
- package/src/{refund.ts → recover.ts} +564 -58
- package/src/widget/components/AccountIntentTransactionHistory.tsx +15 -54
- package/dist/refund.d.ts.map +0 -1
|
@@ -16,11 +16,18 @@ import type { WalletClient, Chain } from "viem"
|
|
|
16
16
|
import { splitSignature } from "./gasless.js"
|
|
17
17
|
import { useMemo, useCallback } from "react"
|
|
18
18
|
import { useGetIntent } from "./widget/hooks/useGetIntent.js"
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
useTokenBalances,
|
|
21
|
+
getTokenBalanceUsd,
|
|
22
|
+
type Price,
|
|
23
|
+
} from "./tokenBalances.js"
|
|
20
24
|
import { getERC20TransferData } from "./encoders.js"
|
|
21
25
|
import { zeroAddress } from "viem"
|
|
26
|
+
import { useTokenPrices } from "./prices.js"
|
|
22
27
|
import type { Payload as WalletPayload } from "@0xsequence/wallet-primitives"
|
|
23
28
|
import { attemptSwitchChain } from "./chainSwitch.js"
|
|
29
|
+
import { decodeGuestModuleEvents } from "./decoders.js"
|
|
30
|
+
import type { TransactionReceipt } from "viem"
|
|
24
31
|
|
|
25
32
|
// Constants for execute function
|
|
26
33
|
type ChainId = number
|
|
@@ -561,13 +568,13 @@ export function determineRefundCall(
|
|
|
561
568
|
}
|
|
562
569
|
}
|
|
563
570
|
|
|
564
|
-
export interface
|
|
571
|
+
export interface UseIntentRecoverParams {
|
|
565
572
|
intentId: string | undefined
|
|
566
573
|
walletClient: WalletClient | undefined
|
|
567
574
|
refundToAddress?: `0x${string}`
|
|
568
575
|
}
|
|
569
576
|
|
|
570
|
-
export interface
|
|
577
|
+
export interface UseIntentRecoverReturn {
|
|
571
578
|
intent: any | null
|
|
572
579
|
isLoadingIntent: boolean
|
|
573
580
|
isLoadingBalances: boolean
|
|
@@ -580,7 +587,7 @@ export interface UseTrailsRefundReturn {
|
|
|
580
587
|
payload: Payload.Calls
|
|
581
588
|
refundCall: Payload.Call
|
|
582
589
|
}>
|
|
583
|
-
|
|
590
|
+
getRecoverTx: (params: {
|
|
584
591
|
signedHash: string
|
|
585
592
|
payload: Payload.Calls
|
|
586
593
|
}) => Promise<{
|
|
@@ -589,27 +596,43 @@ export interface UseTrailsRefundReturn {
|
|
|
589
596
|
chainId: number
|
|
590
597
|
chain: Chain
|
|
591
598
|
}>
|
|
599
|
+
recover: () => Promise<{
|
|
600
|
+
txHash: `0x${string}`
|
|
601
|
+
receipt: TransactionReceipt
|
|
602
|
+
chainId: number
|
|
603
|
+
}>
|
|
604
|
+
getRecoverStatus: (params: {
|
|
605
|
+
txHash: `0x${string}`
|
|
606
|
+
chainId: number
|
|
607
|
+
}) => Promise<{
|
|
608
|
+
status: "success" | "fail" | "pending"
|
|
609
|
+
}>
|
|
592
610
|
}
|
|
593
611
|
|
|
594
612
|
/**
|
|
595
|
-
* Hook for building and signing
|
|
613
|
+
* Hook for building and signing recover transactions
|
|
596
614
|
* @example
|
|
597
615
|
* ```tsx
|
|
598
|
-
* const { signPayload,
|
|
616
|
+
* const { signPayload, getRecoverTx, recover } = useIntentRecover({
|
|
599
617
|
* intentId: "0x...",
|
|
600
618
|
* walletClient: walletClient,
|
|
601
619
|
* })
|
|
602
620
|
*
|
|
603
|
-
*
|
|
604
|
-
* const
|
|
605
|
-
* await
|
|
621
|
+
* // Option 1: Manual flow
|
|
622
|
+
* const signedHash = await signPayload()
|
|
623
|
+
* const recoverTx = await getRecoverTx({ signedHash })
|
|
624
|
+
* await walletClient.sendTransaction(recoverTx)
|
|
625
|
+
*
|
|
626
|
+
* // Option 2: Automatic flow (recommended)
|
|
627
|
+
* const { txHash, receipt, chainId } = await recover()
|
|
628
|
+
* const status = await getRecoverStatus({ txHash, chainId })
|
|
606
629
|
* ```
|
|
607
630
|
*/
|
|
608
|
-
export function
|
|
631
|
+
export function useIntentRecover({
|
|
609
632
|
intentId,
|
|
610
633
|
walletClient,
|
|
611
634
|
refundToAddress,
|
|
612
|
-
}:
|
|
635
|
+
}: UseIntentRecoverParams): UseIntentRecoverReturn {
|
|
613
636
|
const {
|
|
614
637
|
intent: intentData,
|
|
615
638
|
isLoading: isLoadingIntent,
|
|
@@ -620,44 +643,333 @@ export function useTrailsRefund({
|
|
|
620
643
|
enabled: !!intentId,
|
|
621
644
|
})
|
|
622
645
|
|
|
623
|
-
// Get token balances for
|
|
624
|
-
const
|
|
646
|
+
// Get token balances for both origin and destination intent addresses
|
|
647
|
+
const originIntentAddressForBalances = useMemo<Address.Address | null>(() => {
|
|
625
648
|
if (!intentData?.originIntentAddress) return null
|
|
626
649
|
return Address.from(intentData.originIntentAddress)
|
|
627
650
|
}, [intentData?.originIntentAddress])
|
|
628
651
|
|
|
652
|
+
const destinationIntentAddressForBalances =
|
|
653
|
+
useMemo<Address.Address | null>(() => {
|
|
654
|
+
if (!intentData?.destinationIntentAddress) return null
|
|
655
|
+
return Address.from(intentData.destinationIntentAddress)
|
|
656
|
+
}, [intentData?.destinationIntentAddress])
|
|
657
|
+
|
|
629
658
|
const {
|
|
630
|
-
tokenBalancesData,
|
|
631
|
-
isLoadingBalances,
|
|
632
|
-
balanceError:
|
|
633
|
-
} = useTokenBalances(
|
|
659
|
+
tokenBalancesData: originTokenBalancesData,
|
|
660
|
+
isLoadingBalances: isLoadingOriginBalances,
|
|
661
|
+
balanceError: originBalanceError,
|
|
662
|
+
} = useTokenBalances(originIntentAddressForBalances)
|
|
634
663
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
664
|
+
const {
|
|
665
|
+
tokenBalancesData: destinationTokenBalancesData,
|
|
666
|
+
isLoadingBalances: isLoadingDestinationBalances,
|
|
667
|
+
balanceError: destinationBalanceError,
|
|
668
|
+
} = useTokenBalances(destinationIntentAddressForBalances)
|
|
669
|
+
|
|
670
|
+
// Get token prices for origin balances
|
|
671
|
+
const originTokensForPricing = useMemo(() => {
|
|
672
|
+
if (!originTokenBalancesData) return []
|
|
673
|
+
const tokens: Array<{
|
|
674
|
+
tokenSymbol: string
|
|
675
|
+
tokenAddress: string
|
|
676
|
+
chainId: number
|
|
677
|
+
}> = []
|
|
678
|
+
if (originTokenBalancesData.nativeBalances) {
|
|
679
|
+
for (const nativeBalance of originTokenBalancesData.nativeBalances) {
|
|
680
|
+
if (nativeBalance.chainId) {
|
|
681
|
+
tokens.push({
|
|
682
|
+
tokenSymbol: (nativeBalance as any).symbol || "ETH",
|
|
683
|
+
tokenAddress: zeroAddress,
|
|
684
|
+
chainId: nativeBalance.chainId,
|
|
685
|
+
})
|
|
645
686
|
}
|
|
646
687
|
}
|
|
647
688
|
}
|
|
689
|
+
if (originTokenBalancesData.balances) {
|
|
690
|
+
for (const tokenBalance of originTokenBalancesData.balances) {
|
|
691
|
+
if (tokenBalance.contractInfo?.chainId) {
|
|
692
|
+
tokens.push({
|
|
693
|
+
tokenSymbol: tokenBalance.contractInfo?.symbol || "",
|
|
694
|
+
tokenAddress: tokenBalance.contractAddress || zeroAddress,
|
|
695
|
+
chainId: tokenBalance.contractInfo.chainId,
|
|
696
|
+
})
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return tokens
|
|
701
|
+
}, [originTokenBalancesData])
|
|
702
|
+
|
|
703
|
+
const { tokenPrices: originTokenPrices } = useTokenPrices(
|
|
704
|
+
originTokensForPricing as any,
|
|
705
|
+
)
|
|
648
706
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
707
|
+
// Get token prices for destination balances
|
|
708
|
+
const destinationTokensForPricing = useMemo(() => {
|
|
709
|
+
if (!destinationTokenBalancesData) return []
|
|
710
|
+
const tokens: Array<{
|
|
711
|
+
tokenSymbol: string
|
|
712
|
+
tokenAddress: string
|
|
713
|
+
chainId: number
|
|
714
|
+
}> = []
|
|
715
|
+
if (destinationTokenBalancesData.nativeBalances) {
|
|
716
|
+
for (const nativeBalance of destinationTokenBalancesData.nativeBalances) {
|
|
717
|
+
if (nativeBalance.chainId) {
|
|
718
|
+
tokens.push({
|
|
719
|
+
tokenSymbol: (nativeBalance as any).symbol || "ETH",
|
|
720
|
+
tokenAddress: zeroAddress,
|
|
721
|
+
chainId: nativeBalance.chainId,
|
|
722
|
+
})
|
|
655
723
|
}
|
|
656
724
|
}
|
|
657
725
|
}
|
|
726
|
+
if (destinationTokenBalancesData.balances) {
|
|
727
|
+
for (const tokenBalance of destinationTokenBalancesData.balances) {
|
|
728
|
+
if (tokenBalance.contractInfo?.chainId) {
|
|
729
|
+
tokens.push({
|
|
730
|
+
tokenSymbol: tokenBalance.contractInfo?.symbol || "",
|
|
731
|
+
tokenAddress: tokenBalance.contractAddress || zeroAddress,
|
|
732
|
+
chainId: tokenBalance.contractInfo.chainId,
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return tokens
|
|
738
|
+
}, [destinationTokenBalancesData])
|
|
739
|
+
|
|
740
|
+
const { tokenPrices: destinationTokenPrices } = useTokenPrices(
|
|
741
|
+
destinationTokensForPricing as any,
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
// Calculate total USD balance for an address
|
|
745
|
+
const calculateTotalUsdBalance = useCallback(
|
|
746
|
+
(
|
|
747
|
+
balancesData:
|
|
748
|
+
| {
|
|
749
|
+
nativeBalances?: Array<{
|
|
750
|
+
balance?: string
|
|
751
|
+
chainId?: number
|
|
752
|
+
symbol?: string
|
|
753
|
+
}>
|
|
754
|
+
balances?: Array<{
|
|
755
|
+
balance?: string
|
|
756
|
+
contractAddress?: string
|
|
757
|
+
contractInfo?: {
|
|
758
|
+
decimals?: number
|
|
759
|
+
chainId?: number
|
|
760
|
+
symbol?: string
|
|
761
|
+
}
|
|
762
|
+
}>
|
|
763
|
+
}
|
|
764
|
+
| undefined,
|
|
765
|
+
tokenPrices:
|
|
766
|
+
| Array<{
|
|
767
|
+
token: { chainId: number; tokenAddress: string }
|
|
768
|
+
priceUsd?: number
|
|
769
|
+
}>
|
|
770
|
+
| undefined,
|
|
771
|
+
): number => {
|
|
772
|
+
if (!balancesData || !tokenPrices) return 0
|
|
773
|
+
|
|
774
|
+
let totalUsd = 0
|
|
775
|
+
|
|
776
|
+
// Sum native token USD values
|
|
777
|
+
if (balancesData.nativeBalances) {
|
|
778
|
+
for (const nativeBalance of balancesData.nativeBalances) {
|
|
779
|
+
const priceData = tokenPrices.find(
|
|
780
|
+
(p) =>
|
|
781
|
+
p.token.tokenAddress === zeroAddress &&
|
|
782
|
+
p.token.chainId === nativeBalance.chainId &&
|
|
783
|
+
p.priceUsd !== undefined,
|
|
784
|
+
)
|
|
785
|
+
if (priceData?.priceUsd && nativeBalance.balance) {
|
|
786
|
+
const price: Price = {
|
|
787
|
+
value: priceData.priceUsd,
|
|
788
|
+
currency: "USD",
|
|
789
|
+
}
|
|
790
|
+
const usdValue = getTokenBalanceUsd(
|
|
791
|
+
{
|
|
792
|
+
balance: nativeBalance.balance,
|
|
793
|
+
chainId: nativeBalance.chainId,
|
|
794
|
+
} as any,
|
|
795
|
+
price,
|
|
796
|
+
)
|
|
797
|
+
totalUsd += usdValue
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Sum ERC20 token USD values
|
|
803
|
+
if (balancesData.balances) {
|
|
804
|
+
for (const tokenBalance of balancesData.balances) {
|
|
805
|
+
const priceData = tokenPrices.find(
|
|
806
|
+
(p) =>
|
|
807
|
+
p.token.tokenAddress?.toLowerCase() ===
|
|
808
|
+
(tokenBalance.contractAddress || zeroAddress).toLowerCase() &&
|
|
809
|
+
p.token.chainId === tokenBalance.contractInfo?.chainId &&
|
|
810
|
+
p.priceUsd !== undefined,
|
|
811
|
+
)
|
|
812
|
+
if (priceData?.priceUsd && tokenBalance.balance) {
|
|
813
|
+
const price: Price = {
|
|
814
|
+
value: priceData.priceUsd,
|
|
815
|
+
currency: "USD",
|
|
816
|
+
}
|
|
817
|
+
const usdValue = getTokenBalanceUsd(
|
|
818
|
+
{
|
|
819
|
+
balance: tokenBalance.balance,
|
|
820
|
+
contractAddress: tokenBalance.contractAddress,
|
|
821
|
+
contractInfo: tokenBalance.contractInfo,
|
|
822
|
+
} as any,
|
|
823
|
+
price,
|
|
824
|
+
)
|
|
825
|
+
totalUsd += usdValue
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
return totalUsd
|
|
831
|
+
},
|
|
832
|
+
[],
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
// Determine which intent address has the bigger USD balance
|
|
836
|
+
const selectedIntentAddress = useMemo<{
|
|
837
|
+
address: Address.Address
|
|
838
|
+
chainId: number
|
|
839
|
+
intentAddress: Address.Address
|
|
840
|
+
tokenBalancesData: typeof originTokenBalancesData
|
|
841
|
+
} | null>(() => {
|
|
842
|
+
if (!intentData) return null
|
|
843
|
+
|
|
844
|
+
const originTotalUsd = calculateTotalUsdBalance(
|
|
845
|
+
originTokenBalancesData,
|
|
846
|
+
originTokenPrices,
|
|
847
|
+
)
|
|
848
|
+
const destinationTotalUsd = calculateTotalUsdBalance(
|
|
849
|
+
destinationTokenBalancesData,
|
|
850
|
+
destinationTokenPrices,
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
logger.console.log("[recover] Comparing USD balances:", {
|
|
854
|
+
originAddress: intentData.originIntentAddress,
|
|
855
|
+
originTotalUsd: `$${originTotalUsd.toFixed(2)}`,
|
|
856
|
+
destinationAddress: intentData.destinationIntentAddress,
|
|
857
|
+
destinationTotalUsd: `$${destinationTotalUsd.toFixed(2)}`,
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
// If destination has a bigger USD balance, use it
|
|
861
|
+
if (
|
|
862
|
+
destinationIntentAddressForBalances &&
|
|
863
|
+
destinationTotalUsd > originTotalUsd
|
|
864
|
+
) {
|
|
865
|
+
const chainId = intentData.destinationCalls?.chainId
|
|
866
|
+
if (!chainId) {
|
|
867
|
+
logger.console.warn(
|
|
868
|
+
"[recover] Destination has bigger USD balance but no chainId, using origin",
|
|
869
|
+
)
|
|
870
|
+
// Fallback to origin if no destination chainId
|
|
871
|
+
if (!intentData.originIntentAddress || !originIntentAddressForBalances)
|
|
872
|
+
return null
|
|
873
|
+
return {
|
|
874
|
+
address: originIntentAddressForBalances,
|
|
875
|
+
chainId: Number(intentData.originCalls?.chainId || 0),
|
|
876
|
+
intentAddress: Address.from(intentData.originIntentAddress),
|
|
877
|
+
tokenBalancesData: originTokenBalancesData,
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (!intentData.destinationIntentAddress) {
|
|
881
|
+
logger.console.warn(
|
|
882
|
+
"[recover] Destination has bigger USD balance but no destinationIntentAddress, using origin",
|
|
883
|
+
)
|
|
884
|
+
// Fallback to origin if no destinationIntentAddress
|
|
885
|
+
if (!intentData.originIntentAddress || !originIntentAddressForBalances)
|
|
886
|
+
return null
|
|
887
|
+
return {
|
|
888
|
+
address: originIntentAddressForBalances,
|
|
889
|
+
chainId: Number(intentData.originCalls?.chainId || 0),
|
|
890
|
+
intentAddress: Address.from(intentData.originIntentAddress),
|
|
891
|
+
tokenBalancesData: originTokenBalancesData,
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
address: destinationIntentAddressForBalances,
|
|
896
|
+
chainId: Number(chainId),
|
|
897
|
+
intentAddress: Address.from(intentData.destinationIntentAddress),
|
|
898
|
+
tokenBalancesData: destinationTokenBalancesData,
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Otherwise use origin (or if destination doesn't exist)
|
|
903
|
+
if (!intentData.originIntentAddress || !originIntentAddressForBalances)
|
|
904
|
+
return null
|
|
905
|
+
return {
|
|
906
|
+
address: originIntentAddressForBalances,
|
|
907
|
+
chainId: Number(intentData.originCalls?.chainId || 0),
|
|
908
|
+
intentAddress: Address.from(intentData.originIntentAddress),
|
|
909
|
+
tokenBalancesData: originTokenBalancesData,
|
|
910
|
+
}
|
|
911
|
+
}, [
|
|
912
|
+
intentData,
|
|
913
|
+
originTokenBalancesData,
|
|
914
|
+
destinationTokenBalancesData,
|
|
915
|
+
originIntentAddressForBalances,
|
|
916
|
+
destinationIntentAddressForBalances,
|
|
917
|
+
originTokenPrices,
|
|
918
|
+
destinationTokenPrices,
|
|
919
|
+
calculateTotalUsdBalance,
|
|
920
|
+
])
|
|
921
|
+
|
|
922
|
+
const isLoadingBalances =
|
|
923
|
+
isLoadingOriginBalances || isLoadingDestinationBalances
|
|
924
|
+
const balancesError = originBalanceError || destinationBalanceError
|
|
925
|
+
|
|
926
|
+
// Check if ANY intent address (origin or destination) has any balance (native or ERC20)
|
|
927
|
+
// This ensures we show the recover button if there are balances in either address
|
|
928
|
+
const hasIntentBalance = useMemo<boolean>(() => {
|
|
929
|
+
// Helper function to check if balances data has any balance
|
|
930
|
+
const checkBalances = (
|
|
931
|
+
balancesData:
|
|
932
|
+
| {
|
|
933
|
+
nativeBalances?: Array<{ balance?: string; chainId?: number }>
|
|
934
|
+
balances?: Array<{
|
|
935
|
+
balance?: string
|
|
936
|
+
contractAddress?: string
|
|
937
|
+
contractInfo?: { decimals?: number; chainId?: number }
|
|
938
|
+
}>
|
|
939
|
+
}
|
|
940
|
+
| undefined,
|
|
941
|
+
): boolean => {
|
|
942
|
+
if (!balancesData) return false
|
|
943
|
+
|
|
944
|
+
// Check native token balances
|
|
945
|
+
if (balancesData.nativeBalances) {
|
|
946
|
+
for (const nativeBalance of balancesData.nativeBalances) {
|
|
947
|
+
const balance = BigInt(nativeBalance.balance || "0")
|
|
948
|
+
if (balance > 0n) {
|
|
949
|
+
return true
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// Check ERC20 token balances
|
|
955
|
+
if (balancesData.balances) {
|
|
956
|
+
for (const tokenBalance of balancesData.balances) {
|
|
957
|
+
const balance = BigInt(tokenBalance.balance || "0")
|
|
958
|
+
if (balance > 0n) {
|
|
959
|
+
return true
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
return false
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// Check both origin and destination balances
|
|
968
|
+
const originHasBalance = checkBalances(originTokenBalancesData)
|
|
969
|
+
const destinationHasBalance = checkBalances(destinationTokenBalancesData)
|
|
658
970
|
|
|
659
|
-
return
|
|
660
|
-
}, [
|
|
971
|
+
return originHasBalance || destinationHasBalance
|
|
972
|
+
}, [originTokenBalancesData, destinationTokenBalancesData])
|
|
661
973
|
|
|
662
974
|
// Get refund address (use walletClient account or provided address)
|
|
663
975
|
// Don't throw during render - validate when methods are called
|
|
@@ -724,18 +1036,32 @@ export function useTrailsRefund({
|
|
|
724
1036
|
})
|
|
725
1037
|
}
|
|
726
1038
|
|
|
727
|
-
// Determine
|
|
1039
|
+
// Determine which intent address to recover from (biggest balance)
|
|
1040
|
+
if (!selectedIntentAddress) {
|
|
1041
|
+
throw new Error("No intent address selected for recovery")
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
logger.console.log("[recover] Selected intent address for recovery:", {
|
|
1045
|
+
address: selectedIntentAddress.address,
|
|
1046
|
+
chainId: selectedIntentAddress.chainId,
|
|
1047
|
+
intentAddress: selectedIntentAddress.intentAddress,
|
|
1048
|
+
})
|
|
1049
|
+
|
|
1050
|
+
// Determine refund call from token balances of selected address
|
|
728
1051
|
const refundCall = determineRefundCall(
|
|
729
|
-
tokenBalancesData,
|
|
1052
|
+
selectedIntentAddress.tokenBalancesData,
|
|
730
1053
|
effectiveRefundAddress,
|
|
731
1054
|
)
|
|
732
1055
|
|
|
733
|
-
//
|
|
734
|
-
const chainId =
|
|
1056
|
+
// Use chain ID from selected intent address
|
|
1057
|
+
const chainId = selectedIntentAddress.chainId
|
|
735
1058
|
if (!chainId) {
|
|
736
|
-
throw new Error("Chain ID not found
|
|
1059
|
+
throw new Error("Chain ID not found for selected intent address")
|
|
737
1060
|
}
|
|
738
|
-
logger.console.log(
|
|
1061
|
+
logger.console.log(
|
|
1062
|
+
"[recover] Using chain ID from selected intent:",
|
|
1063
|
+
chainId,
|
|
1064
|
+
)
|
|
739
1065
|
|
|
740
1066
|
// Switch to the correct chain before signing
|
|
741
1067
|
if (walletClient) {
|
|
@@ -756,11 +1082,11 @@ export function useTrailsRefund({
|
|
|
756
1082
|
}
|
|
757
1083
|
}
|
|
758
1084
|
|
|
759
|
-
//
|
|
760
|
-
if (!
|
|
761
|
-
throw new Error("
|
|
1085
|
+
// Use the selected intent address (biggest balance)
|
|
1086
|
+
if (!selectedIntentAddress) {
|
|
1087
|
+
throw new Error("No intent address selected for recovery")
|
|
762
1088
|
}
|
|
763
|
-
const intentAddress =
|
|
1089
|
+
const intentAddress = selectedIntentAddress.intentAddress
|
|
764
1090
|
|
|
765
1091
|
// Create payload with random space and calculate hash for signing
|
|
766
1092
|
const payload = createRefundPayload(
|
|
@@ -783,12 +1109,12 @@ export function useTrailsRefund({
|
|
|
783
1109
|
payload,
|
|
784
1110
|
refundCall,
|
|
785
1111
|
}
|
|
786
|
-
}, [intentData, walletClient,
|
|
1112
|
+
}, [intentData, walletClient, selectedIntentAddress, effectiveRefundAddress])
|
|
787
1113
|
|
|
788
1114
|
// Export as signPayload for the hook return
|
|
789
1115
|
const signPayload = signPayloadCallback
|
|
790
1116
|
|
|
791
|
-
const
|
|
1117
|
+
const getRecoverTx = useCallback(
|
|
792
1118
|
async (params: {
|
|
793
1119
|
signedHash: string
|
|
794
1120
|
payload: Payload.Calls
|
|
@@ -847,24 +1173,29 @@ export function useTrailsRefund({
|
|
|
847
1173
|
})
|
|
848
1174
|
}
|
|
849
1175
|
|
|
1176
|
+
// Use the selected intent address (biggest balance)
|
|
1177
|
+
if (!selectedIntentAddress) {
|
|
1178
|
+
throw new Error("No intent address selected for recovery")
|
|
1179
|
+
}
|
|
1180
|
+
|
|
850
1181
|
// Use the refund call from the payload (passed from signPayload)
|
|
851
1182
|
const refundCall = params.payload.calls[0]
|
|
852
1183
|
if (!refundCall) {
|
|
853
1184
|
throw new Error("Refund call not found in payload")
|
|
854
1185
|
}
|
|
855
1186
|
|
|
856
|
-
//
|
|
857
|
-
const chainId =
|
|
1187
|
+
// Use chain ID from selected intent address
|
|
1188
|
+
const chainId = selectedIntentAddress.chainId
|
|
858
1189
|
if (!chainId) {
|
|
859
|
-
throw new Error("Chain ID not found
|
|
1190
|
+
throw new Error("Chain ID not found for selected intent address")
|
|
860
1191
|
}
|
|
861
|
-
logger.console.log(
|
|
1192
|
+
logger.console.log(
|
|
1193
|
+
"[recover] Using chain ID from selected intent:",
|
|
1194
|
+
chainId,
|
|
1195
|
+
)
|
|
862
1196
|
|
|
863
|
-
//
|
|
864
|
-
|
|
865
|
-
throw new Error("Intent data missing originIntentAddress")
|
|
866
|
-
}
|
|
867
|
-
const intentAddress = Address.from(intentData.originIntentAddress)
|
|
1197
|
+
// Use the selected intent address
|
|
1198
|
+
const intentAddress = selectedIntentAddress.intentAddress
|
|
868
1199
|
|
|
869
1200
|
// Build transaction with signature using the payload passed from signPayload
|
|
870
1201
|
const mainSigner = walletClient.account.address as Address.Address
|
|
@@ -897,7 +1228,180 @@ export function useTrailsRefund({
|
|
|
897
1228
|
chain: chainInfo as Chain,
|
|
898
1229
|
}
|
|
899
1230
|
},
|
|
900
|
-
[intentData, walletClient, effectiveRefundAddress],
|
|
1231
|
+
[intentData, walletClient, selectedIntentAddress, effectiveRefundAddress],
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
const recover = useCallback(async (): Promise<{
|
|
1235
|
+
txHash: `0x${string}`
|
|
1236
|
+
receipt: TransactionReceipt
|
|
1237
|
+
chainId: number
|
|
1238
|
+
}> => {
|
|
1239
|
+
if (!intentData || !walletClient || !walletClient.account) {
|
|
1240
|
+
throw new Error("Intent data and wallet client with account required")
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Validate refund address
|
|
1244
|
+
if (!effectiveRefundAddress) {
|
|
1245
|
+
throw new Error(
|
|
1246
|
+
"Refund address is required. Provide refundToAddress or connect a wallet with a valid address",
|
|
1247
|
+
)
|
|
1248
|
+
}
|
|
1249
|
+
if (Address.isEqual(effectiveRefundAddress, zeroAddress)) {
|
|
1250
|
+
throw new Error("Refund address cannot be zero address")
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
logger.console.log("[recover] Starting recover process")
|
|
1254
|
+
|
|
1255
|
+
// Step 1: Sign the payload
|
|
1256
|
+
const { signature, payload } = await signPayload()
|
|
1257
|
+
logger.console.log("[recover] Payload signed:", signature)
|
|
1258
|
+
|
|
1259
|
+
// Step 2: Get the recover transaction
|
|
1260
|
+
const recoverTx = await getRecoverTx({
|
|
1261
|
+
signedHash: signature,
|
|
1262
|
+
payload,
|
|
1263
|
+
})
|
|
1264
|
+
logger.console.log("[recover] Recover transaction built:", recoverTx)
|
|
1265
|
+
|
|
1266
|
+
// Step 3: Switch to the correct chain if needed
|
|
1267
|
+
await attemptSwitchChain({
|
|
1268
|
+
walletClient,
|
|
1269
|
+
desiredChainId: recoverTx.chainId,
|
|
1270
|
+
})
|
|
1271
|
+
logger.console.log("[recover] Switched to chain:", recoverTx.chainId)
|
|
1272
|
+
|
|
1273
|
+
// Step 4: Send the transaction
|
|
1274
|
+
if (!walletClient.account) {
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
"Wallet client account is required for sending transaction",
|
|
1277
|
+
)
|
|
1278
|
+
}
|
|
1279
|
+
const txHash = await walletClient.sendTransaction({
|
|
1280
|
+
account: walletClient.account,
|
|
1281
|
+
to: recoverTx.to,
|
|
1282
|
+
data: recoverTx.data,
|
|
1283
|
+
chain: recoverTx.chain,
|
|
1284
|
+
})
|
|
1285
|
+
logger.console.log("[recover] Transaction sent:", txHash)
|
|
1286
|
+
|
|
1287
|
+
// Step 5: Wait for receipt
|
|
1288
|
+
const publicClient = getChainRpcClient(recoverTx.chainId)
|
|
1289
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
1290
|
+
hash: txHash,
|
|
1291
|
+
})
|
|
1292
|
+
logger.console.log("[recover] Transaction receipt received:", receipt)
|
|
1293
|
+
|
|
1294
|
+
return {
|
|
1295
|
+
txHash,
|
|
1296
|
+
receipt,
|
|
1297
|
+
chainId: recoverTx.chainId,
|
|
1298
|
+
}
|
|
1299
|
+
}, [
|
|
1300
|
+
intentData,
|
|
1301
|
+
walletClient,
|
|
1302
|
+
effectiveRefundAddress,
|
|
1303
|
+
signPayload,
|
|
1304
|
+
getRecoverTx,
|
|
1305
|
+
])
|
|
1306
|
+
|
|
1307
|
+
const getRecoverStatus = useCallback(
|
|
1308
|
+
async (params: {
|
|
1309
|
+
txHash: `0x${string}`
|
|
1310
|
+
chainId: number
|
|
1311
|
+
}): Promise<{
|
|
1312
|
+
status: "success" | "fail" | "pending"
|
|
1313
|
+
}> => {
|
|
1314
|
+
const { txHash, chainId } = params
|
|
1315
|
+
|
|
1316
|
+
logger.console.log("[recover] Getting recover status:", {
|
|
1317
|
+
txHash,
|
|
1318
|
+
chainId,
|
|
1319
|
+
})
|
|
1320
|
+
|
|
1321
|
+
const publicClient = getChainRpcClient(chainId)
|
|
1322
|
+
|
|
1323
|
+
// First, check if the transaction exists (might be pending)
|
|
1324
|
+
try {
|
|
1325
|
+
const transaction = await publicClient.getTransaction({
|
|
1326
|
+
hash: txHash,
|
|
1327
|
+
})
|
|
1328
|
+
|
|
1329
|
+
// If transaction exists but we can't get receipt, it's pending
|
|
1330
|
+
if (transaction) {
|
|
1331
|
+
try {
|
|
1332
|
+
const receipt = await publicClient.getTransactionReceipt({
|
|
1333
|
+
hash: txHash,
|
|
1334
|
+
})
|
|
1335
|
+
|
|
1336
|
+
// Transaction has been mined, check status
|
|
1337
|
+
// Check if transaction itself failed
|
|
1338
|
+
if (receipt.status === "reverted") {
|
|
1339
|
+
logger.console.log("[recover] Transaction reverted")
|
|
1340
|
+
return { status: "fail" }
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// Decode events to check for CallFailed events
|
|
1344
|
+
const guestModuleEvents = decodeGuestModuleEvents(receipt)
|
|
1345
|
+
const hasCallFailed = guestModuleEvents.some(
|
|
1346
|
+
(event) => event.type === "CallFailed",
|
|
1347
|
+
)
|
|
1348
|
+
|
|
1349
|
+
if (hasCallFailed) {
|
|
1350
|
+
logger.console.log(
|
|
1351
|
+
"[recover] Found CallFailed events in transaction",
|
|
1352
|
+
)
|
|
1353
|
+
return { status: "fail" }
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
logger.console.log(
|
|
1357
|
+
"[recover] Transaction succeeded with no failures",
|
|
1358
|
+
)
|
|
1359
|
+
return { status: "success" }
|
|
1360
|
+
} catch {
|
|
1361
|
+
// Receipt not available yet, transaction is pending
|
|
1362
|
+
logger.console.log(
|
|
1363
|
+
"[recover] Transaction receipt not available, pending",
|
|
1364
|
+
)
|
|
1365
|
+
return { status: "pending" }
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
// Transaction might not exist yet or there's an error
|
|
1370
|
+
logger.console.warn("[recover] Error getting transaction:", error)
|
|
1371
|
+
// If we can't find the transaction, it might be pending or invalid
|
|
1372
|
+
// Try to get receipt anyway as a fallback
|
|
1373
|
+
try {
|
|
1374
|
+
const receipt = await publicClient.getTransactionReceipt({
|
|
1375
|
+
hash: txHash,
|
|
1376
|
+
})
|
|
1377
|
+
|
|
1378
|
+
if (receipt.status === "reverted") {
|
|
1379
|
+
return { status: "fail" }
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
const guestModuleEvents = decodeGuestModuleEvents(receipt)
|
|
1383
|
+
const hasCallFailed = guestModuleEvents.some(
|
|
1384
|
+
(event) => event.type === "CallFailed",
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
if (hasCallFailed) {
|
|
1388
|
+
return { status: "fail" }
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
return { status: "success" }
|
|
1392
|
+
} catch {
|
|
1393
|
+
// If we can't get receipt either, assume pending
|
|
1394
|
+
logger.console.log(
|
|
1395
|
+
"[recover] Cannot determine status, assuming pending",
|
|
1396
|
+
)
|
|
1397
|
+
return { status: "pending" }
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// Fallback: if we can't determine, assume pending
|
|
1402
|
+
return { status: "pending" }
|
|
1403
|
+
},
|
|
1404
|
+
[],
|
|
901
1405
|
)
|
|
902
1406
|
|
|
903
1407
|
return {
|
|
@@ -909,6 +1413,8 @@ export function useTrailsRefund({
|
|
|
909
1413
|
hasIntentBalance,
|
|
910
1414
|
refetchIntent,
|
|
911
1415
|
signPayload,
|
|
912
|
-
|
|
1416
|
+
getRecoverTx,
|
|
1417
|
+
recover,
|
|
1418
|
+
getRecoverStatus,
|
|
913
1419
|
}
|
|
914
1420
|
}
|