diff --git a/packages/nextjs/app/safe/page.tsx b/packages/nextjs/app/safe/page.tsx index 9f904d4..d38f894 100644 --- a/packages/nextjs/app/safe/page.tsx +++ b/packages/nextjs/app/safe/page.tsx @@ -9,7 +9,12 @@ import { useAccount, useBalance, useReadContract } from "wagmi"; import { CheckCircleIcon } from "@heroicons/react/20/solid"; import { ClipboardIcon } from "@heroicons/react/24/outline"; import { ERC20_ABI } from "~~/lib/ABI"; -import { TransactionDetails, getTransactionOnBaseSepoliaByHash } from "~~/lib/blockscout"; +import { + TransactionDetails, + getTokenTransfersOnBaseSepolia, + getTransactionOnBaseSepoliaByHash, + getTransactionsOnBaseSepolia, +} from "~~/lib/blockscout"; import { BASE_SEPOLIA_BLOCKSCOUT_TX_BASE_URL, CROSSCHAIN_TRANSFER_CONTRACT_BASE_SEPOLIA, @@ -33,7 +38,8 @@ const SafePage = () => { const [safeAddress, setSafeAddress] = useState(""); const [transactions, setTransactions] = useState([]); - const [transactionDetails, setTransactionDetails] = useState([]); // TransactionDetails[ + const [transactionDetails, setTransactionDetails] = useState([]); + const [transferDetails, setTransferDetails] = useState([]); const [refreshingTransactions, setRefreshingTransactions] = useState(false); const [loading, setLoading] = useState(false); const [transferAmount, setTransferAmount] = useState(0); @@ -49,6 +55,29 @@ const SafePage = () => { chainId: chain?.id, }); + function extractAndDecodeHexString(input: string) { + // Regular expression to match a hexadecimal string + const hexPattern = /0x[0-9A-Fa-f]+/; + + // Match the input string against the pattern + const match = input.match(hexPattern); + + // Return the decoded hex string or null if no match is found + if (match) { + const hexString = match[0]; + // Remove the '0x' prefix + const cleanedHexString = hexString.slice(2); + // Decode the hex string + let decodedString = ""; + for (let i = 0; i < cleanedHexString.length; i += 2) { + decodedString += String.fromCharCode(parseInt(cleanedHexString.substr(i, 2), 16)); + } + return decodedString; + } else { + return null; + } + } + const { data: safeUSDCBalance, refetch: refetchSafeUSDCBalance } = useReadContract({ abi: ERC20_ABI, address: chain ? USDC_ADDRESS[chain?.id] : ("" as `0x${string}`), @@ -112,6 +141,9 @@ const SafePage = () => { BigInt(transferAmount * 10 ** 6), recipientAddress, ); + + notification.success("Crosschain transfer initiated successfully: " + txHash); + console.log("txHash", txHash); setTransactions([...transactions, txHash]); const transactionDetail = await getTransactionOnBaseSepoliaByHash(txHash); setTransactionDetails([...transactionDetails, transactionDetail]); @@ -127,11 +159,26 @@ const SafePage = () => { setRefreshingTransactions(true); setTransactionDetails([]); const txDetails = []; + + setTransferDetails([]); + const transferDetails = []; + + if (!primaryWallet) return; + const transactions = await getTransactionsOnBaseSepolia(primaryWallet.address); + const transfers = await getTokenTransfersOnBaseSepolia(primaryWallet.address); + for (const txHash of transactions) { const transactionDetail = await getTransactionOnBaseSepoliaByHash(txHash); txDetails.push(transactionDetail); setTransactionDetails(txDetails); } + + for (const transfer of transfers) { + const transactionDetail = await getTransactionOnBaseSepoliaByHash(transfer.tx_hash); + transferDetails.push(transactionDetail); + setTransferDetails(transferDetails); + } + setRefreshingTransactions(false); }; @@ -157,14 +204,22 @@ const SafePage = () => { BigInt(crossChainTransferAmount * 10 ** 6), crossChainRecipientAddress, ); + + notification.success("Crosschain transfer initiated successfully: " + txHash); console.log("txHash", txHash); setTransactions([...transactions, txHash]); const transactionDetail = await getTransactionOnBaseSepoliaByHash(txHash); - setTransactionDetails([...transactionDetails, transactionDetail]); + setTransferDetails([...transactionDetails, transactionDetail]); } catch (err) { if (err instanceof Error) { - notification.error(err.message); - console.error(err.message); + const hasHexError = extractAndDecodeHexString((err as any).details); + if (hasHexError !== null) { + notification.error(hasHexError); + console.error(hasHexError); + } else { + notification.error((error as any).details); + console.error((error as any).details); + } } else { setError("Failed to transfer tokens."); console.error(err); @@ -214,6 +269,9 @@ const SafePage = () => {
Safe Smart Wallet deployed!
+ + Fund it from Faucet +

Address: {safeAddress}

@@ -384,6 +442,19 @@ const SafePage = () => {
))} + {transferDetails.map(tx => ( + + ))} )} diff --git a/packages/nextjs/lib/blockscout.ts b/packages/nextjs/lib/blockscout.ts index d2cf0a2..4a95e68 100644 --- a/packages/nextjs/lib/blockscout.ts +++ b/packages/nextjs/lib/blockscout.ts @@ -59,3 +59,15 @@ export const getTransactionOnBaseSepoliaByHash = async (txHash: string): Promise const response = await fetch(`https://base-sepolia.blockscout.com/api/v2/transactions/${txHash}`); return response.json(); }; + +export const getTokenTransfersOnBaseSepolia = async (address: string) => { + const response = await fetch(`https://base-sepolia.blockscout.com/api/v2/addresses/${address}/token-transfers?type=`); + const json = await response.json(); + return json.items; +}; + +export const getTransactionsOnBaseSepolia = async (address: string) => { + const response = await fetch(`https://base-sepolia.blockscout.com/api/v2/addresses/${address}/transactions`); + const json = await response.json(); + return json.items; +}; diff --git a/packages/nextjs/lib/dynamic.ts b/packages/nextjs/lib/dynamic.ts index e1b8bda..2d1ce67 100644 --- a/packages/nextjs/lib/dynamic.ts +++ b/packages/nextjs/lib/dynamic.ts @@ -15,7 +15,7 @@ export const sendTransaction = async ( amount: string, wallet: Wallet, networkConfigurations: NetworkConfigurationMap, -): Promise => { +): Promise => { try { const walletClient = wallet.connector.getWalletClient>(); diff --git a/packages/nextjs/utils/scaffold-eth/notification.tsx b/packages/nextjs/utils/scaffold-eth/notification.tsx index cf57849..177dde9 100644 --- a/packages/nextjs/utils/scaffold-eth/notification.tsx +++ b/packages/nextjs/utils/scaffold-eth/notification.tsx @@ -30,7 +30,7 @@ const ENUM_STATUSES = { warning: , }; -const DEFAULT_DURATION = 3000; +const DEFAULT_DURATION = 6000; const DEFAULT_POSITION: ToastPosition = "top-center"; /**