From a605397b3fcb6d55d1ad9dedbddfc153c8c5a7f2 Mon Sep 17 00:00:00 2001 From: Isaac Onyemaechi Ugwu Date: Tue, 23 Dec 2025 16:37:59 +0100 Subject: [PATCH 1/6] fix: correct rocket animation rotation and improve easing for smoother transitions (#331) --- app/components/Footer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 9d604f3b..5cb159f3 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -91,19 +91,20 @@ export const Footer = () => { }, fulfilled: { y: [0, -5, 5, 0], - rotate: [-20, -15, -25, -20], + rotate: [20, 15, 25, 20], scale: 1.15, filter: "drop-shadow(0 4px 0 #FFD700)", transition: { repeat: Infinity, duration: 0.1, - ease: "linear", + ease: "easeInOut", filter: { duration: 0.5 }, }, }, settled: { x: "-175vw", - y: "-45vw", + y: "-95vw", + rotate: [20, 15, 25, 20], filter: "drop-shadow(0 8px 32px #FFD700)", transition: { x: { duration: 1.2, ease: "easeIn" }, From 8ad2eb118fb192c0024e6a614c2f19e35dde80da Mon Sep 17 00:00:00 2001 From: Isaac Onyemaechi Date: Wed, 7 Jan 2026 14:20:14 +0100 Subject: [PATCH 2/6] fix: update footer rocket illustration and adjust navbar layout for better alignment --- app/components/Footer.tsx | 9 ++++----- app/components/Navbar.tsx | 42 ++++++--------------------------------- 2 files changed, 10 insertions(+), 41 deletions(-) diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index 5cb159f3..a0eeea31 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -91,7 +91,7 @@ export const Footer = () => { }, fulfilled: { y: [0, -5, 5, 0], - rotate: [20, 15, 25, 20], + rotate: [-20, -15, -25, -20], scale: 1.15, filter: "drop-shadow(0 4px 0 #FFD700)", transition: { @@ -103,8 +103,7 @@ export const Footer = () => { }, settled: { x: "-175vw", - y: "-95vw", - rotate: [20, 15, 25, 20], + y: "-45vw", filter: "drop-shadow(0 8px 32px #FFD700)", transition: { x: { duration: 1.2, ease: "easeIn" }, @@ -204,8 +203,8 @@ export const Footer = () => { { >
{ > {IS_MAIN_PRODUCTION_DOMAIN ? ( <> - {/* */} - - Noblocks Logo - {/* */} - Noblocks Logo + + ) : ( <> - {/* */} - - Noblocks Logo - {/* */} - Noblocks Logo + + )} @@ -186,7 +156,7 @@ export const Navbar = () => { className={classNames( "size-5 cursor-pointer text-icon-outline-secondary transition-transform duration-200 dark:text-white/50 max-sm:hidden", isDropdownOpen ? "rotate-0" : "-rotate-90", - IS_MAIN_PRODUCTION_DOMAIN ? "mt-[10px]" : "mt-[10px]", // this adjusts the arrow position for beta logo + IS_MAIN_PRODUCTION_DOMAIN ? "" : "!-mt-[15px]", // this adjusts the arrow position for beta logo )} onClick={(e) => { e.preventDefault(); From 2f4b004f2d8a77d38b2fe890f0618273f5b62f82 Mon Sep 17 00:00:00 2001 From: Isaac Onyemaechi Date: Thu, 8 Jan 2026 12:55:50 +0100 Subject: [PATCH 3/6] fix: update footer component min-height for improved layout consistency --- app/components/Footer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index a0eeea31..4cc2b9ff 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -118,7 +118,7 @@ export const Footer = () => { return ( Date: Fri, 9 Jan 2026 10:46:09 +0100 Subject: [PATCH 4/6] feat: add native ETH support and improve token transfer logic --- app/context/TokensContext.tsx | 16 +++++ app/hooks/useSmartWalletTransfer.ts | 58 ++++++++++++++++--- app/mocks.ts | 13 ++--- app/types.ts | 1 + app/utils.ts | 90 ++++++++++++++++++++--------- public/logos/eth-logo.svg | 13 +++++ 6 files changed, 148 insertions(+), 43 deletions(-) create mode 100644 public/logos/eth-logo.svg diff --git a/app/context/TokensContext.tsx b/app/context/TokensContext.tsx index fad0885c..af0141d1 100644 --- a/app/context/TokensContext.tsx +++ b/app/context/TokensContext.tsx @@ -68,6 +68,22 @@ export function TokensProvider({ children }: { children: ReactNode }) { if (!hasUSDT) { newTokens["Base"].push(usdtBase); } + + // Ensure native ETH is present in Base + const nativeETH = { + name: "Ethereum", + symbol: "ETH", + decimals: 18, + address: "", // Native token has no contract address + imageUrl: "/logos/eth-logo.svg", + isNative: true, + }; + const hasNativeETH = newTokens["Base"].some( + (token) => token.symbol === "ETH" && token.isNative, + ); + if (!hasNativeETH) { + newTokens["Base"].push(nativeETH); + } } // Merge fallback tokens for any networks missing from API response diff --git a/app/hooks/useSmartWalletTransfer.ts b/app/hooks/useSmartWalletTransfer.ts index fd36d6ec..227f68fb 100644 --- a/app/hooks/useSmartWalletTransfer.ts +++ b/app/hooks/useSmartWalletTransfer.ts @@ -5,7 +5,7 @@ import { getExplorerLink } from "../utils"; import { saveTransaction } from "../api/aggregator"; import { trackEvent } from "./analytics/useMixpanel"; import type { Token, Network } from "../types"; -import type { User } from "@privy-io/react-auth"; +import { useSendTransaction, type User } from "@privy-io/react-auth"; interface SmartWalletClient { sendTransaction: (args: { @@ -104,6 +104,45 @@ export function useSmartWalletTransfer({ const tokenData = availableTokens.find( (t) => t.symbol.toUpperCase() === searchToken, ); + + // Native token transfer logic (ETH, BNB, etc.) + if (tokenData?.isNative && tokenData?.address === "") { + const value = BigInt(Math.floor(amount * 1e18)); + const hash = await client?.sendTransaction({ + to: recipientAddress as `0x${string}`, + value, + data: "0x" as `0x${string}`, + }); + if (!hash) throw new Error("No transaction hash returned"); + const txhash = hash as unknown as string; + setTxHash(txhash); + setTransferAmount(amount.toString()); + setTransferToken(token); + setIsSuccess(true); + setIsLoading(false); + toast.success( + `${amount.toString()} ${token} successfully transferred`, + ); + trackEvent("Transfer completed", { + Amount: amount, + "Send token": token, + "Recipient address": recipientAddress, + Network: selectedNetwork.chain.name, + "Transaction hash": hash, + "Transfer date": new Date().toISOString(), + }); + await saveTransferTransaction({ + txHash: txhash, + recipientAddress, + amount, + token, + }); + if (resetForm) resetForm(); + if (refreshBalance) refreshBalance(); + return; + } + + // ERC-20 token transfer logic const tokenAddress = tokenData?.address as `0x${string}` | undefined; const tokenDecimals = tokenData?.decimals; if (!tokenAddress || tokenDecimals === undefined) { @@ -143,7 +182,7 @@ export function useSmartWalletTransfer({ setIsSuccess(true); setIsLoading(false); toast.success(`${amount.toString()} ${token} successfully transferred`); - + // Track successful transfer trackEvent("Transfer completed", { Amount: amount, @@ -153,7 +192,7 @@ export function useSmartWalletTransfer({ "Transaction hash": hash, "Transfer date": new Date().toISOString(), }); - + // Save to transaction history await saveTransferTransaction({ txHash: hash, @@ -164,10 +203,11 @@ export function useSmartWalletTransfer({ if (resetForm) resetForm(); if (refreshBalance) refreshBalance(); } catch (e: unknown) { - const errorMessage = (e as { shortMessage?: string; message?: string }).shortMessage || + const errorMessage = + (e as { shortMessage?: string; message?: string }).shortMessage || (e as { message?: string }).message || "Transfer failed"; - + setError(errorMessage); setIsLoading(false); setIsSuccess(false); @@ -180,9 +220,11 @@ export function useSmartWalletTransfer({ Network: selectedNetwork.chain.name, "Reason for failure": errorMessage, "Transfer date": new Date().toISOString(), - "Error type": errorMessage.includes("429") ? "RPC Rate Limited" : - errorMessage.includes("HTTP") ? "RPC Connection Error" : - "Transaction Error", + "Error type": errorMessage.includes("429") + ? "RPC Rate Limited" + : errorMessage.includes("HTTP") + ? "RPC Connection Error" + : "Transaction Error", }); } }, diff --git a/app/mocks.ts b/app/mocks.ts index d80a3aa4..8cd7ac0f 100644 --- a/app/mocks.ts +++ b/app/mocks.ts @@ -1,6 +1,5 @@ import { arbitrum, base, bsc, polygon, lisk, celo, mainnet } from "viem/chains"; - export const acceptedCurrencies = [ { name: "NGN", @@ -18,7 +17,7 @@ export const acceptedCurrencies = [ name: "TZS", label: "Tanzanian Shilling (TZS)", }, - { + { name: "MWK", label: "Malawian Kwacha (MWK)", disabled: true, @@ -60,7 +59,7 @@ export const networks = [ }, { chain: mainnet, - imageUrl: "/logos/ethereum-logo.svg", + imageUrl: "/logos/eth-logo.svg", }, { chain: lisk, @@ -73,10 +72,10 @@ export const networks = [ chain: polygon, imageUrl: "/logos/polygon-logo.svg", }, -// { -// chain: hedera, -// imageUrl: "/logos/hedera-logo.svg", -// }, + // { + // chain: hedera, + // imageUrl: "/logos/hedera-logo.svg", + // }, // { // chain: scroll, // imageUrl: "/logos/scroll-logo.svg", diff --git a/app/types.ts b/app/types.ts index 98bf0761..cbe08264 100644 --- a/app/types.ts +++ b/app/types.ts @@ -212,6 +212,7 @@ export type Token = { decimals: number; address: string; imageUrl?: string; + isNative?: boolean; }; export type APIToken = { diff --git a/app/utils.ts b/app/utils.ts index c106aa7f..b5f87e81 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -255,6 +255,14 @@ export const FALLBACK_TOKENS: { [key: string]: Token[] } = { address: "0x46c85152bfe9f96829aa94755d9f915f9b10ef5f", imageUrl: "/logos/cngn-logo.svg", }, + { + name: "Ethereum", + symbol: "ETH", + decimals: 18, + address: "", // Native token has no contract address + imageUrl: "/logos/eth-logo.svg", + isNative: true, + }, ], "Arbitrum One": [ { @@ -344,28 +352,28 @@ export const FALLBACK_TOKENS: { [key: string]: Token[] } = { }, ], Ethereum: [ - { - name: "USD Coin", - symbol: "USDC", - decimals: 6, - address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - imageUrl: "/logos/usdc-logo.svg", - }, - { - name: "Tether USD", - symbol: "USDT", - decimals: 6, - address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", - imageUrl: "/logos/usdt-logo.svg", - }, - { + { + name: "USD Coin", + symbol: "USDC", + decimals: 6, + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + imageUrl: "/logos/usdc-logo.svg", + }, + { + name: "Tether USD", + symbol: "USDT", + decimals: 6, + address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + imageUrl: "/logos/usdt-logo.svg", + }, + { name: "cNGN", symbol: "cNGN", decimals: 6, address: "0x17CDB2a01e7a34CbB3DD4b83260B05d0274C8dab", imageUrl: "/logos/cngn-logo.svg", }, -], + ], }; /** @@ -428,6 +436,22 @@ export async function getNetworkTokens(network = ""): Promise { if (!hasUSDT) { tokens["Base"].push(usdtBase); } + + // Ensure native ETH is present in Base + const nativeETH = { + name: "Ethereum", + symbol: "ETH", + decimals: 18, + address: "", // Native token has no contract address + imageUrl: "/logos/eth-logo.svg", + isNative: true, + }; + const hasNativeETH = tokens["Base"].some( + (token) => token.symbol === "ETH" && token.isNative, + ); + if (!hasNativeETH) { + tokens["Base"].push(nativeETH); + } } // Merge fallback tokens for any networks missing from API response Object.keys(FALLBACK_TOKENS).forEach((networkName) => { @@ -471,16 +495,25 @@ export async function fetchWalletBalance( // Fetch balances in parallel const balancePromises = supportedTokens.map(async (token: Token) => { try { - const balanceInWei = await client.readContract({ - address: token.address as `0x${string}`, - abi: erc20Abi, - functionName: "balanceOf", - args: [address as `0x${string}`], - }); - const balance = Number(balanceInWei) / Math.pow(10, token.decimals); - // Ensure balance is a valid number - balances[token.symbol] = isNaN(balance) ? 0 : balance; - return balances[token.symbol]; + if (token.isNative && token.address === "") { + // Native token balance (ETH, BNB, etc.) + const balanceInWei = await client.getBalance({ address }); + const balance = Number(balanceInWei) / Math.pow(10, token.decimals); + balances[token.symbol] = isNaN(balance) ? 0 : balance; + return balances[token.symbol]; + } else { + // ERC-20 token balance + const balanceInWei = await client.readContract({ + address: token.address as `0x${string}`, + abi: erc20Abi, + functionName: "balanceOf", + args: [address as `0x${string}`], + }); + const balance = Number(balanceInWei) / Math.pow(10, token.decimals); + // Ensure balance is a valid number + balances[token.symbol] = isNaN(balance) ? 0 : balance; + return balances[token.symbol]; + } } catch (error) { console.error(`Error fetching balance for ${token.symbol}:`, error); balances[token.symbol] = 0; @@ -609,7 +642,7 @@ export function getGatewayContractAddress(network = ""): string | undefined { Optimism: "0xd293fcd3dbc025603911853d893a4724cf9f70a0", Celo: "0xf418217e3f81092ef44b81c5c8336e6a6fdb0e4b", Lisk: "0xff0E00E0110C1FBb5315D276243497b66D3a4d8a", - Ethereum: "0x8d2c0d398832b814e3814802ff2dc8b8ef4381e5" + Ethereum: "0x8d2c0d398832b814e3814802ff2dc8b8ef4381e5", }[network]; } @@ -1205,7 +1238,8 @@ export function calculateSenderFee( const defaultFeePercent = 0.1; // 0.1% default fee for local transfers const maxFeeCapInHumanReadable = 10000; // 10k CNGN cap in human-readable units const decimalsMultiplier = BigInt(10 ** tokenDecimals); - const maxFeeCapInBaseUnits = BigInt(maxFeeCapInHumanReadable) * decimalsMultiplier; // 10k CNGN in base units + const maxFeeCapInBaseUnits = + BigInt(maxFeeCapInHumanReadable) * decimalsMultiplier; // 10k CNGN in base units // Calculate fee in human-readable format const calculatedFee = isLocalTransfer diff --git a/public/logos/eth-logo.svg b/public/logos/eth-logo.svg new file mode 100644 index 00000000..30d2142c --- /dev/null +++ b/public/logos/eth-logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + From 4a7a47784c63fd05abdd5587bec541c0408d7da0 Mon Sep 17 00:00:00 2001 From: Isaac Onyemaechi Date: Fri, 9 Jan 2026 11:35:11 +0100 Subject: [PATCH 5/6] fix: update node engine version to 22.x and specify pnpm version in package.json --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 67144177..05d41864 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,9 @@ "tailwindcss": "^3.4.17", "typescript": "^5.8.3" }, - "engines": { - "node": ">=18.0.0" + "engines": { + "node": "22.x", + "pnpm": "10.14.0" }, "packageManager": "pnpm@10.14.0", "pnpm": { From d4ba4de53e369829b6af3d43f27696defb1b212f Mon Sep 17 00:00:00 2001 From: Chibuotu Amadi Date: Fri, 16 Jan 2026 21:47:38 +0100 Subject: [PATCH 6/6] refactor: update native ETH handling to target specific networks in token logic --- app/context/TokensContext.tsx | 30 +++++++++++++++--------------- app/utils.ts | 30 +++++++++++++++--------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/app/context/TokensContext.tsx b/app/context/TokensContext.tsx index af0141d1..3c02ad1b 100644 --- a/app/context/TokensContext.tsx +++ b/app/context/TokensContext.tsx @@ -69,21 +69,21 @@ export function TokensProvider({ children }: { children: ReactNode }) { newTokens["Base"].push(usdtBase); } - // Ensure native ETH is present in Base - const nativeETH = { - name: "Ethereum", - symbol: "ETH", - decimals: 18, - address: "", // Native token has no contract address - imageUrl: "/logos/eth-logo.svg", - isNative: true, - }; - const hasNativeETH = newTokens["Base"].some( - (token) => token.symbol === "ETH" && token.isNative, - ); - if (!hasNativeETH) { - newTokens["Base"].push(nativeETH); - } + // Ensure native ETH is present in the target network + // const nativeETH = { + // name: "Ethereum", + // symbol: "ETH", + // decimals: 18, + // address: "", // Native token has no contract address + // imageUrl: "/logos/eth-logo.svg", + // isNative: true, + // }; + // const hasNativeETH = newTokens["Ethereum"].some( + // (token) => token.symbol === "ETH" && token.isNative, + // ); + // if (!hasNativeETH) { + // newTokens["Ethereum"].push(nativeETH); + // } } // Merge fallback tokens for any networks missing from API response diff --git a/app/utils.ts b/app/utils.ts index b5f87e81..a2942205 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -437,21 +437,21 @@ export async function getNetworkTokens(network = ""): Promise { tokens["Base"].push(usdtBase); } - // Ensure native ETH is present in Base - const nativeETH = { - name: "Ethereum", - symbol: "ETH", - decimals: 18, - address: "", // Native token has no contract address - imageUrl: "/logos/eth-logo.svg", - isNative: true, - }; - const hasNativeETH = tokens["Base"].some( - (token) => token.symbol === "ETH" && token.isNative, - ); - if (!hasNativeETH) { - tokens["Base"].push(nativeETH); - } + // Ensure native ETH is present in the target network + // const nativeETH = { + // name: "Ethereum", + // symbol: "ETH", + // decimals: 18, + // address: "", // Native token has no contract address + // imageUrl: "/logos/eth-logo.svg", + // isNative: true, + // }; + // const hasNativeETH = tokens["Ethereum"].some( + // (token) => token.symbol === "ETH" && token.isNative, + // ); + // if (!hasNativeETH) { + // tokens["Ethereum"].push(nativeETH); + // } } // Merge fallback tokens for any networks missing from API response Object.keys(FALLBACK_TOKENS).forEach((networkName) => {