diff --git a/lib/contracts/constants.ts b/lib/contracts/constants.ts index 2dd9ae52..f27c06b2 100644 --- a/lib/contracts/constants.ts +++ b/lib/contracts/constants.ts @@ -37,7 +37,7 @@ export const STORY_FACTORY = (process.env.NEXT_PUBLIC_CONTRACT_ADDRESS ?? * Testnet: disabled (V1 contract incompatible with V2 ABI) */ export const ZAP_PLOTLINK = (IS_TESTNET ? "0x0000000000000000000000000000000000000000" - : "0x7bC192848003ab1Ba286C66AFD0dd8a1729c6b02") as `0x${string}`; + : "0x04f557F8D2806B34FC832a534c08DF514D4dfEeF") as `0x${string}`; /** $PLOT protocol token * Testnet: PL_TEST ERC-20 on Base Sepolia diff --git a/lib/zap.ts b/lib/zap.ts index a8ed6d22..77b6092c 100644 --- a/lib/zap.ts +++ b/lib/zap.ts @@ -120,7 +120,7 @@ export function buildZapMintTx( functionName: "mint" as const, args: [fromToken, storylineToken, amount, quote.fromTokenAmount] as const, value: isEth ? quote.fromTokenAmount : BigInt(0), - gas: BigInt(3_000_000), + gas: BigInt(5_000_000), }; } else { // Apply 3% slippage to minStorylineAmount @@ -135,7 +135,7 @@ export function buildZapMintTx( functionName: "mintReverse" as const, args: [fromToken, storylineToken, amount, slippageProtected] as const, value: isEth ? amount : BigInt(0), - gas: BigInt(3_000_000), + gas: BigInt(5_000_000), }; } } diff --git a/src/components/TradingWidget.tsx b/src/components/TradingWidget.tsx index bcb396d4..7ab6008d 100644 --- a/src/components/TradingWidget.tsx +++ b/src/components/TradingWidget.tsx @@ -37,6 +37,8 @@ function getTokenAddress(payToken: PayToken): Address { return token?.address ?? ETH_ADDRESS as Address; } +const ETH_GAS_BUFFER = BigInt("1000000000000000"); // 0.001 ETH reserved for gas + export function TradingWidget({ tokenAddress }: { tokenAddress: Address }) { const { address, isConnected } = useAccount(); const [tab, setTab] = useState("buy"); @@ -149,6 +151,44 @@ export function TradingWidget({ tokenAddress }: { tokenAddress: Address }) { if (isEthMode) refetchEthBalance(); }, [refetchTradeData, refetchEthBalance, isEthMode]); + // MAX button handler for buy tab + const handleBuyMax = useCallback(async () => { + if (!address || !isConnected) return; + + try { + let maxBalance: bigint; + if (isEthMode) { + const ethBal = ethBalanceData?.value ?? BigInt(0); + maxBalance = ethBal > ETH_GAS_BUFFER ? ethBal - ETH_GAS_BUFFER : BigInt(0); + } else if (isErc20ZapMode && erc20BalanceToken) { + maxBalance = await publicClient.readContract({ + address: erc20BalanceToken, + abi: erc20Abi, + functionName: "balanceOf", + args: [address], + }); + } else { + // PLOT mode + maxBalance = await publicClient.readContract({ + address: PLOT_TOKEN, + abi: erc20Abi, + functionName: "balanceOf", + args: [address], + }); + } + + if (maxBalance <= BigInt(0)) return; + + const fromToken = getTokenAddress(payToken); + const quote = await getZapQuote(fromToken, tokenAddress, maxBalance, "exact-input"); + if (quote.tokensOut && quote.tokensOut > BigInt(0)) { + setAmount(formatUnits(quote.tokensOut, 18)); + } + } catch { + // Silently fail — user can enter amount manually + } + }, [address, isConnected, isEthMode, isErc20ZapMode, erc20BalanceToken, payToken, tokenAddress, ethBalanceData]); + const executeTrade = useCallback(async () => { if (!address || parsedAmount === BigInt(0)) return; @@ -189,7 +229,7 @@ export function TradingWidget({ tokenAddress }: { tokenAddress: Address }) { setTxState("pending"); await publicClient.waitForTransactionReceipt({ hash }); } else if (tab === "buy" && isPlotMode && estimate) { - // PLOT mode: approve PLOT_TOKEN → MCV2_Bond.mint + // PLOT mode: approve PLOT_TOKEN -> MCV2_Bond.mint const maxCost = applySlippage(estimate, true); const allowance = await publicClient.readContract({ @@ -223,7 +263,7 @@ export function TradingWidget({ tokenAddress }: { tokenAddress: Address }) { setTxState("pending"); await publicClient.waitForTransactionReceipt({ hash }); } else if (tab === "sell" && estimate) { - // Sell: approve storyline token → burn → receive PLOT_TOKEN + // Sell: approve storyline token -> burn -> receive PLOT_TOKEN const minRefund = applySlippage(estimate, false); const allowance = await publicClient.readContract({ @@ -302,7 +342,12 @@ export function TradingWidget({ tokenAddress }: { tokenAddress: Address }) { return (
-

Trade

+

+ Trade to Support + + Every trade generates a 5% creator royalty — buying and selling these story tokens directly supports the writer to keep continuing this story. + +

{/* Tabs */}
@@ -325,34 +370,44 @@ export function TradingWidget({ tokenAddress }: { tokenAddress: Address }) { ))}
- {/* Pay token selector (buy tab only) */} + {/* Pay token selector (buy tab only) + balance */} {tab === "buy" && isZapAvailable && ( -
- Pay with - {(["ETH", "USDC", "HUNT", "PLOT"] as const).map((t) => ( - - ))} +
+
+ Pay with + {(["ETH", "USDC", "HUNT", "PLOT"] as const).map((t) => ( + + ))} +
+ {balance !== undefined && ( +

+ Balance: {formatUnits(balance, balanceDecimals)} {balanceLabel} +

+ )} + {insufficientBalance && ( +

Insufficient balance

+ )}
)} {/* Amount input */}
{tab === "sell" && balance !== undefined && ( + )}
- {balance !== undefined && ( + {/* Balance for sell tab and non-zap buy (PLOT direct) */} + {(tab === "sell" || !isZapAvailable) && balance !== undefined && (

Balance: {formatUnits(balance, balanceDecimals)} {balanceLabel}

)} - {insufficientBalance && ( + {(tab === "sell" || !isZapAvailable) && insufficientBalance && (

Insufficient balance

)}