"use client";
import { useRef, useState } from "react";
import { type Address } from "viem";
import {
useWallet,
useSpiceExecution,
useSupplyAssets,
useSupplyQuote,
buildSupplyChainBatches,
buildPayChainBatches,
estimateFeePreview,
executeSupply,
type Asset,
type BatchBuilderContext,
type BuildActionCallsContext,
type Call,
type ExecutorContext,
type SupplyStep,
type SupplyQuoteResult,
} from "@spicenet-io/spiceflow-ui";
const destinationToken = {
chainId: 8453,
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" as Address, // USDC on Base
symbol: "USDC",
decimals: 6,
};
// One function per market: the destination chain calls to run once funds arrive.
// ctx.userAddress is the wallet the action runs as.
const buildActionCalls = (amount: bigint, ctx: BuildActionCallsContext): Call[] => [
{ to: destinationToken.address, value: 0n, data: encodeApprove(MARKET, amount) },
{ to: MARKET, value: 0n, data: encodeSupply(destinationToken.address, amount, ctx.userAddress) },
];
export function SupplyPanel() {
const { address, isReady, isAuthenticated, isConnected, provider } = useWallet();
const { execute, estimate } = useSpiceExecution();
// Every asset the user can fund this market with, across all chains
const { displayAssets, embeddedWalletAddress, externalAddress, isNon7702 } =
useSupplyAssets({ address, destinationToken, enabled: true });
const [selected, setSelected] = useState<Asset | null>(null);
const [amount, setAmount] = useState("");
const [step, setStep] = useState<SupplyStep>("idle");
const [error, setError] = useState<string | null>(null);
const [statusMsg, setStatusMsg] = useState("");
const [txHash, setTxHash] = useState<string | null>(null);
const [isExecuting, setIsExecuting] = useState(false);
const [, setShowStatusPanel] = useState(false);
const [, setPaymentResult] = useState<any>(null);
const submittedRef = useRef(false);
const abortRef = useRef<AbortController | null>(null);
const selectedAsset = selected ? { asset: selected, amount } : null;
// Routing quote: does the chosen source asset need a swap into the destination token?
const quote: SupplyQuoteResult = useSupplyQuote({
direction: "input",
selectedAsset,
destinationToken,
chainId: destinationToken.chainId,
recipient: embeddedWalletAddress ?? address,
paymentAmount: amount,
});
// The token the execution is funded in on the destination chain
const executionTokenAddress: Address =
quote.needsSwap && quote.resolvedTokenIn
? quote.resolvedTokenIn
: selected && selected.chainId === destinationToken.chainId
? selected.address
: (quote.resolvedTokenIn ?? destinationToken.address);
const isSourceDestToken =
!!selected &&
selected.address.toLowerCase() === destinationToken.address.toLowerCase() &&
selected.chainId === destinationToken.chainId;
const supply = async () => {
if (!selectedAsset) return;
const batchCtx: BatchBuilderContext = {
currentPaymentAmount: amount,
destinationToken,
embeddedWalletAddress,
providerEmbeddedAddress: embeddedWalletAddress,
externalWalletAddress: externalAddress,
address,
isNon7702,
isPayMode: false,
resolvedChainId: destinationToken.chainId,
buildActionCalls,
feeExecutionMode: "backend-transfer",
buildSpicenetBatch: undefined,
selectedAsset,
needsSwap: quote.needsSwap,
resolvedSwap: quote.resolvedSwap,
exactOutputSwap: quote.exactOutputSwap,
quoteAmountOut: quote.quoteAmountOut,
resolvedTokenIn: quote.resolvedTokenIn,
requestTimestampMs: Date.now(),
};
const buildSupply = (opts?: Parameters<typeof buildSupplyChainBatches>[1]) =>
buildSupplyChainBatches(batchCtx, opts);
const buildPay = (opts?: Parameters<typeof buildPayChainBatches>[1]) =>
buildPayChainBatches(batchCtx, opts);
const parsedAmount = parseFloat(amount) || 0;
// Fee quote. In production, run this when the amount settles and show it
// before enabling the submit button.
const feeQuote = await estimateFeePreview({
selectedAsset,
isPayMode: false,
parsedAmount,
needsSwap: quote.needsSwap,
exactOutputSwap: quote.exactOutputSwap,
isNon7702,
isSourceDestToken,
currentPaymentAmount: amount,
destinationToken,
feeExecutionMode: "backend-transfer",
resolvedTokenIn: quote.resolvedTokenIn,
embeddedWalletAddress,
providerEmbeddedAddress: embeddedWalletAddress,
resolvedChainId: destinationToken.chainId,
isConnected,
address,
ready: isReady,
authenticated: isAuthenticated,
isDirect: quote.isDirect,
resolvedSwap: quote.resolvedSwap,
isQuoting: quote.isQuoting,
executionTokenAddress,
buildPayChainBatches: buildPay,
buildSupplyChainBatches: buildSupply,
estimate,
});
const ctx: ExecutorContext = {
selectedAsset,
parsedAmount,
currentPaymentAmount: amount,
isInsufficientBalance: parsedAmount > (selected?.balanceFormatted ?? 0),
isPayMode: false,
isNon7702,
isSourceDestToken,
needsSwap: quote.needsSwap,
exactOutputSwap: quote.exactOutputSwap,
selectedIsEquivalent: quote.selectedIsEquivalent,
resolvedTokenIn: quote.resolvedTokenIn,
embeddedWalletAddress,
providerEmbeddedAddress: embeddedWalletAddress,
externalWallet: null,
externalWalletAddress: externalAddress,
address,
isConnected,
ready: isReady,
authenticated: isAuthenticated,
provider,
resolvedChainId: destinationToken.chainId,
executionTokenAddress,
feeQuote,
execution: {
estimate,
execute,
buildPayChainBatches: buildPay,
buildSupplyChainBatches: buildSupply,
onProgress: (p) => setStatusMsg(p.message),
},
ui: {
setError,
setStep,
setStatusMsg,
setTxHash,
setIsExecuting,
setShowStatusPanel,
setPaymentResult,
},
refs: {
spiceDepositSubmittedRef: submittedRef,
abortControllerRef: abortRef,
},
destinationToken,
buildActionCalls,
feeExecutionMode: "backend-transfer",
buildSpicenetBatch: undefined,
onPayExecute: undefined,
onSuccess: (hash) => console.log("supplied", hash),
};
await executeSupply(ctx);
};
return (
<div>
{displayAssets.map((a) => (
<button key={`${a.chainId}-${a.address}`} onClick={() => setSelected(a)}>
{a.symbol} on chain {a.chainId}: {a.balanceFormatted}
</button>
))}
<input value={amount} onChange={(e) => setAmount(e.target.value)} />
<button onClick={supply} disabled={isExecuting || !selected}>
{step === "success" ? "Done" : "Supply"}
</button>
{statusMsg && <p>{statusMsg}</p>}
{error && <p>{error}</p>}
{txHash && <p>{txHash}</p>}
</div>
);
}