Skip to content

Commit 5c5cb61

Browse files
authored
Merge pull request #93 from sktbrd/feat/swap-editorial-strip
feat(swap): editorial full-width strip layout (Remix C)
2 parents 2f9705a + d96ae43 commit 5c5cb61

8 files changed

Lines changed: 596 additions & 275 deletions

File tree

src/app/api/0x/price/route.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { NextResponse, type NextRequest } from "next/server";
2-
import { DAO_ADDRESSES, SWAP_FEE_BPS } from "@/lib/config";
2+
import { getSwapFeeRecipient, SWAP_FEE_BPS } from "@/lib/config";
33

44
// Server-only API key — never leaked to the client bundle.
55
const ZEROX_API_KEY = process.env.ZEROX_API_KEY ?? "";
66

7-
// Fee recipient + rate live in src/lib/config.ts so they ship with the code
8-
// (the DAO treasury is canonical and overridable via NEXT_PUBLIC_TREASURY_ADDRESS
9-
// alongside every other DAO address).
10-
const FEE_RECIPIENT = DAO_ADDRESSES.treasury;
7+
// Fee recipient is chain-aware: Base → DAO treasury, others → multichain
8+
// custody address. See `getSwapFeeRecipient` in src/lib/config.ts.
119
const FEE_BPS = String(SWAP_FEE_BPS);
1210

1311
const ZEROX_HEADERS: HeadersInit = {
@@ -41,8 +39,9 @@ export async function GET(request: NextRequest) {
4139

4240
if (wantsFee) {
4341
const buyToken = params.get("buyToken") ?? "";
44-
if (buyToken) {
45-
params.set("swapFeeRecipient", FEE_RECIPIENT);
42+
const chainId = Number(params.get("chainId") ?? "0");
43+
if (buyToken && Number.isFinite(chainId) && chainId > 0) {
44+
params.set("swapFeeRecipient", getSwapFeeRecipient(chainId));
4645
params.set("swapFeeBps", FEE_BPS);
4746
params.set("swapFeeToken", buyToken);
4847
}

src/app/api/0x/quote/route.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { NextResponse, type NextRequest } from "next/server";
2-
import { DAO_ADDRESSES, SWAP_FEE_BPS } from "@/lib/config";
2+
import { getSwapFeeRecipient, SWAP_FEE_BPS } from "@/lib/config";
33

44
// Server-only API key — never leaked to the client bundle.
55
const ZEROX_API_KEY = process.env.ZEROX_API_KEY ?? "";
66

7-
// Fee recipient + rate live in src/lib/config.ts; the recipient is the DAO
8-
// treasury (overridable via NEXT_PUBLIC_TREASURY_ADDRESS).
9-
const FEE_RECIPIENT = DAO_ADDRESSES.treasury;
7+
// Fee recipient is chain-aware: Base → DAO treasury, others → multichain
8+
// custody address. See `getSwapFeeRecipient` in src/lib/config.ts.
109
const FEE_BPS = String(SWAP_FEE_BPS);
1110

1211
const ZEROX_HEADERS: HeadersInit = {
@@ -36,8 +35,9 @@ export async function GET(request: NextRequest) {
3635

3736
if (wantsFee) {
3837
const buyToken = params.get("buyToken") ?? "";
39-
if (buyToken) {
40-
params.set("swapFeeRecipient", FEE_RECIPIENT);
38+
const chainId = Number(params.get("chainId") ?? "0");
39+
if (buyToken && Number.isFinite(chainId) && chainId > 0) {
40+
params.set("swapFeeRecipient", getSwapFeeRecipient(chainId));
4141
params.set("swapFeeBps", FEE_BPS);
4242
params.set("swapFeeToken", buyToken);
4343
}

src/app/swap/ChainSelector.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"use client";
2+
3+
import { Check, ChevronDown } from "lucide-react";
4+
import {
5+
DropdownMenu,
6+
DropdownMenuContent,
7+
DropdownMenuItem,
8+
DropdownMenuTrigger,
9+
} from "@/components/ui/dropdown-menu";
10+
import { SWAP_CHAINS } from "./chains";
11+
import { useSwapChain } from "./SwapChainContext";
12+
13+
export function ChainSelector() {
14+
const { chain, setChainId } = useSwapChain();
15+
16+
return (
17+
<DropdownMenu>
18+
<DropdownMenuTrigger
19+
className="inline-flex items-center gap-1.5 rounded-md bg-blue-100 px-2 py-1 text-xs font-semibold tracking-wide text-blue-800 transition-colors hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 dark:bg-blue-900/30 dark:text-blue-200 dark:hover:bg-blue-900/50"
20+
aria-label="Select chain"
21+
>
22+
{chain.shortName}
23+
<ChevronDown className="h-3 w-3" />
24+
</DropdownMenuTrigger>
25+
<DropdownMenuContent align="start" className="min-w-[10rem]">
26+
{SWAP_CHAINS.map((c) => (
27+
<DropdownMenuItem key={c.id} onSelect={() => setChainId(c.id)} className="gap-2 text-sm">
28+
<Check
29+
className={c.id === chain.id ? "h-3.5 w-3.5 opacity-100" : "h-3.5 w-3.5 opacity-0"}
30+
/>
31+
{c.name}
32+
</DropdownMenuItem>
33+
))}
34+
</DropdownMenuContent>
35+
</DropdownMenu>
36+
);
37+
}

src/app/swap/SwapChainContext.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import { DEFAULT_SWAP_CHAIN, getSwapChain, type SwapChain } from "./chains";
5+
6+
interface SwapChainContextValue {
7+
chain: SwapChain;
8+
setChainId: (id: number) => void;
9+
}
10+
11+
const SwapChainContext = React.createContext<SwapChainContextValue | null>(null);
12+
13+
/**
14+
* Holds the chain currently selected in the swap UI. ChainSelector writes
15+
* to it; SwapWidget reads from it. The wallet's *active* chain is separate
16+
* (driven by thirdweb) — wrong-network UI compares the two.
17+
*/
18+
export function SwapChainProvider({ children }: { children: React.ReactNode }) {
19+
const [chainId, setChainId] = React.useState<number>(DEFAULT_SWAP_CHAIN.id);
20+
const chain = React.useMemo(() => getSwapChain(chainId), [chainId]);
21+
const value = React.useMemo<SwapChainContextValue>(() => ({ chain, setChainId }), [chain]);
22+
return <SwapChainContext.Provider value={value}>{children}</SwapChainContext.Provider>;
23+
}
24+
25+
export function useSwapChain(): SwapChainContextValue {
26+
const ctx = React.useContext(SwapChainContext);
27+
if (!ctx) throw new Error("useSwapChain must be used inside <SwapChainProvider>");
28+
return ctx;
29+
}

0 commit comments

Comments
 (0)