From 9430bc0543a61bffe13828b0d32119af72f288a3 Mon Sep 17 00:00:00 2001 From: Matthew Foyle Date: Wed, 10 Jul 2024 11:21:09 +0200 Subject: [PATCH 1/4] fix: add error handling --- packages/nextjs/app/safe/page.tsx | 33 +++++++++++++++++-- .../utils/scaffold-eth/notification.tsx | 2 +- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/nextjs/app/safe/page.tsx b/packages/nextjs/app/safe/page.tsx index 9f904d4..1e6a8ee 100644 --- a/packages/nextjs/app/safe/page.tsx +++ b/packages/nextjs/app/safe/page.tsx @@ -49,6 +49,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}`), @@ -163,8 +186,14 @@ const SafePage = () => { setTransactionDetails([...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); 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"; /** From e6cc62b75fba157c55eb74d1817eaaa528d8e0dc Mon Sep 17 00:00:00 2001 From: Matthew Foyle Date: Wed, 10 Jul 2024 11:38:35 +0200 Subject: [PATCH 2/4] feat: funding button for Safe --- packages/nextjs/app/safe/page.tsx | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/nextjs/app/safe/page.tsx b/packages/nextjs/app/safe/page.tsx index 1e6a8ee..e8006b7 100644 --- a/packages/nextjs/app/safe/page.tsx +++ b/packages/nextjs/app/safe/page.tsx @@ -16,6 +16,7 @@ import { USDC_ADDRESS, } from "~~/lib/constants"; import { toMinsAgo } from "~~/lib/date-utils"; +import { sendTransaction } from "~~/lib/dynamic"; import { approveERC20, crossChainTransferERC20, @@ -26,7 +27,7 @@ import { notification } from "~~/utils/scaffold-eth"; const SafePage = () => { const { address, chain, isConnected } = useAccount(); - const { primaryWallet, isAuthenticated } = useDynamicContext(); + const { primaryWallet, isAuthenticated, networkConfigurations } = useDynamicContext(); const switchNetwork = useSwitchNetwork(); const [safeDeployed, setSafeDeployed] = useState(false); @@ -95,6 +96,20 @@ const SafePage = () => { fetchNetwork(); }, [primaryWallet]); + const handleFundSafeAccount = async () => { + if (!safeAddress || !primaryWallet || !networkConfigurations) return; + + const balance = (await Number(primaryWallet.connector.getBalance())) || 0; + + if (balance < 0.001) { + notification.error("Insufficient balance to fund Safe account with 0.001 Base Sepolia"); + return; + } + + const hash = await sendTransaction(safeAddress, "0.001", primaryWallet, networkConfigurations); + notification.success(`Funded Safe account with 0.001 Sepolia. Tx: ${hash}`); + }; + const handleDeploySafe = async () => { setLoading(true); setError(null); @@ -243,6 +258,13 @@ const SafePage = () => {
Safe Smart Wallet deployed!
+

Address: {safeAddress}

From 339588ec0f094792742ff1d8ee2512eb5a91b803 Mon Sep 17 00:00:00 2001 From: Matthew Foyle Date: Wed, 10 Jul 2024 15:11:08 +0200 Subject: [PATCH 3/4] fix: faucet link for now --- packages/nextjs/app/safe/page.tsx | 27 ++++----------------------- packages/nextjs/lib/dynamic.ts | 2 +- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/packages/nextjs/app/safe/page.tsx b/packages/nextjs/app/safe/page.tsx index e8006b7..8b73bcc 100644 --- a/packages/nextjs/app/safe/page.tsx +++ b/packages/nextjs/app/safe/page.tsx @@ -16,7 +16,6 @@ import { USDC_ADDRESS, } from "~~/lib/constants"; import { toMinsAgo } from "~~/lib/date-utils"; -import { sendTransaction } from "~~/lib/dynamic"; import { approveERC20, crossChainTransferERC20, @@ -27,7 +26,7 @@ import { notification } from "~~/utils/scaffold-eth"; const SafePage = () => { const { address, chain, isConnected } = useAccount(); - const { primaryWallet, isAuthenticated, networkConfigurations } = useDynamicContext(); + const { primaryWallet, isAuthenticated } = useDynamicContext(); const switchNetwork = useSwitchNetwork(); const [safeDeployed, setSafeDeployed] = useState(false); @@ -96,20 +95,6 @@ const SafePage = () => { fetchNetwork(); }, [primaryWallet]); - const handleFundSafeAccount = async () => { - if (!safeAddress || !primaryWallet || !networkConfigurations) return; - - const balance = (await Number(primaryWallet.connector.getBalance())) || 0; - - if (balance < 0.001) { - notification.error("Insufficient balance to fund Safe account with 0.001 Base Sepolia"); - return; - } - - const hash = await sendTransaction(safeAddress, "0.001", primaryWallet, networkConfigurations); - notification.success(`Funded Safe account with 0.001 Sepolia. Tx: ${hash}`); - }; - const handleDeploySafe = async () => { setLoading(true); setError(null); @@ -258,13 +243,9 @@ const SafePage = () => {
Safe Smart Wallet deployed!
- + + Fund it from Faucet +

Address: {safeAddress}

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>(); From 35711071ca32e1863a16a42e4302727107658366 Mon Sep 17 00:00:00 2001 From: Matthew Foyle Date: Wed, 10 Jul 2024 15:39:55 +0200 Subject: [PATCH 4/4] fix: refresh transactions --- packages/nextjs/app/safe/page.tsx | 45 ++++++++++++++++++++++++++++--- packages/nextjs/lib/blockscout.ts | 12 +++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/nextjs/app/safe/page.tsx b/packages/nextjs/app/safe/page.tsx index 8b73bcc..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); @@ -135,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]); @@ -150,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); }; @@ -180,10 +204,12 @@ 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) { const hasHexError = extractAndDecodeHexString((err as any).details); @@ -416,6 +442,19 @@ const SafePage = () => {
))} + {transferDetails.map(tx => ( +
+
+ +
+ {`${tx.hash.substring(0, 8)}...${tx.hash.substring(tx.hash.length - 8)}`} + +
+
+
{toMinsAgo(tx.timestamp)}
+
+
+ ))} )} 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; +};