Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
50c1a19
temp
MananTank Sep 12, 2025
c917607
screen cleanup
MananTank Sep 12, 2025
751bd0a
add success and error screens
MananTank Sep 12, 2025
ce099a3
resolve todo
MananTank Sep 12, 2025
ad7daa8
UI improvements
MananTank Sep 15, 2025
0b70ce8
Fix theme in select-sell ui
MananTank Sep 15, 2025
dbba64d
add JSdoc, export component
MananTank Sep 15, 2025
2fe8436
add swap widget in public token page
MananTank Sep 15, 2025
814acd6
padding changes
MananTank Sep 15, 2025
a630bc9
remove log
MananTank Sep 15, 2025
8513839
fix knip lint
MananTank Sep 15, 2025
7681650
show balance, show not enough balance error
MananTank Sep 15, 2025
a5011af
Fix notEnoughBalance
MananTank Sep 15, 2025
3d7fd1e
use Sell/prepare when selling
MananTank Sep 15, 2025
ebf6eef
Save token selection and amounts when going back
MananTank Sep 15, 2025
6fb8a4e
Merge branch 'main' into mnn/swap-ui
MananTank Sep 15, 2025
ae43ed5
Remove extra quote loading step
MananTank Sep 15, 2025
abf8eea
fix lint
MananTank Sep 15, 2025
2f2f8de
UI tweaks
MananTank Sep 15, 2025
8ff84b5
use quote when wallet is not connected
MananTank Sep 15, 2025
9d17bdc
combine owned tokens and all tokens
MananTank Sep 16, 2025
ed5ce8a
update
MananTank Sep 16, 2025
b2d6429
Fix buy widget showing payment method selection screen
MananTank Sep 16, 2025
2a78c45
ui improvements
MananTank Sep 16, 2025
654cc82
show quote error message
MananTank Sep 16, 2025
0349636
Add JSDoc, fix lint
MananTank Sep 16, 2025
1d98c3e
improve fiat onramp ui
MananTank Sep 16, 2025
8f75d50
ui updates in buy widget
MananTank Sep 16, 2025
9cf8688
better switch ux, swap label
MananTank Sep 16, 2025
b3fe76a
move token selection to modal
MananTank Sep 16, 2025
33fa31a
address comments
MananTank Sep 17, 2025
0d2d39d
pass quote to callbacks, add events
MananTank Sep 17, 2025
1405e11
add embed in chain, bridge page
MananTank Sep 17, 2025
f0c4fc8
cleanup
MananTank Sep 17, 2025
43b06e1
add changeset
MananTank Sep 17, 2025
d87c23b
fix build
MananTank Sep 17, 2025
b1f23a2
update changeset version
MananTank Sep 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/lucky-turtles-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"thirdweb": minor
---

Add `SwapWidget` component for swapping tokens using thirdweb Bridge

```tsx
<SwapWidget client={thirdwebClient} />
```
68 changes: 68 additions & 0 deletions apps/dashboard/src/@/analytics/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,54 @@ export function reportAssetBuySuccessful(properties: {
});
}

type TokenSwapParams = {
buyTokenChainId: number;
buyTokenAddress: string;
sellTokenChainId: number;
sellTokenAddress: string;
pageType: "asset" | "bridge" | "chain";
};

/**
* ### Why do we need to report this event?
* - To track number of successful token swaps from the token page
* - To track which tokens are being swapped the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportTokenSwapSuccessful(properties: TokenSwapParams) {
posthog.capture("token swap successful", properties);
}

/**
* ### Why do we need to report this event?
* - To track number of failed token swaps from the token page
* - To track which tokens are being swapped the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportTokenSwapFailed(
properties: TokenSwapParams & {
errorMessage: string;
},
) {
posthog.capture("token swap failed", properties);
}

/**
* ### Why do we need to report this event?
* - To track number of cancelled token swaps from the token page
* - To track which tokens are being swapped the most
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportTokenSwapCancelled(properties: TokenSwapParams) {
posthog.capture("token swap cancelled", properties);
}

/**
* ### Why do we need to report this event?
* - To track number of failed asset purchases from the token page
Expand All @@ -272,6 +320,26 @@ export function reportAssetBuyFailed(properties: {
});
}

/**
* ### Why do we need to report this event?
* - To track number of cancelled asset purchases from the token page
* - To track the errors that users encounter when trying to purchase an asset
*
* ### Who is responsible for this event?
* @MananTank
*/
export function reportAssetBuyCancelled(properties: {
chainId: number;
contractType: AssetContractType;
assetType: "nft" | "coin";
}) {
posthog.capture("asset buy cancelled", {
assetType: properties.assetType,
chainId: properties.chainId,
contractType: properties.contractType,
});
}

// Assets Landing Page ----------------------------

/**
Expand Down
156 changes: 156 additions & 0 deletions apps/dashboard/src/@/components/blocks/BuyAndSwapEmbed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
"use client";

import { useTheme } from "next-themes";
import { useState } from "react";
import type { Chain, ThirdwebClient } from "thirdweb";
import { BuyWidget, SwapWidget } from "thirdweb/react";
import {
reportAssetBuyCancelled,
reportAssetBuyFailed,
reportAssetBuySuccessful,
reportTokenSwapCancelled,
reportTokenSwapFailed,
reportTokenSwapSuccessful,
} from "@/analytics/report";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { parseError } from "@/utils/errorParser";
import { getSDKTheme } from "@/utils/sdk-component-theme";

export function BuyAndSwapEmbed(props: {
client: ThirdwebClient;
chain: Chain;
tokenAddress: string | undefined;
buyAmount: string | undefined;
pageType: "asset" | "bridge" | "chain";
}) {
const { theme } = useTheme();
const [tab, setTab] = useState<"buy" | "swap">("swap");
const themeObj = getSDKTheme(theme === "light" ? "light" : "dark");
return (
<div className="bg-card rounded-2xl border overflow-hidden flex flex-col">
<div className="flex gap-2.5 p-4 border-b border-dashed">
<TabButton
label="Swap"
onClick={() => setTab("swap")}
isActive={tab === "swap"}
/>
<TabButton
label="Buy"
onClick={() => setTab("buy")}
isActive={tab === "buy"}
/>
</div>

{tab === "buy" && (
<BuyWidget
amount={props.buyAmount || "1"}
chain={props.chain}
className="!rounded-2xl !w-full !border-none"
title=""
client={props.client}
connectOptions={{
autoConnect: false,
}}
onError={(e) => {
const errorMessage = parseError(e);
if (props.pageType === "asset") {
reportAssetBuyFailed({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
error: errorMessage,
});
}
}}
onCancel={() => {
if (props.pageType === "asset") {
reportAssetBuyCancelled({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
});
}
}}
onSuccess={() => {
if (props.pageType === "asset") {
reportAssetBuySuccessful({
assetType: "coin",
chainId: props.chain.id,
contractType: "DropERC20",
});
}
}}
theme={themeObj}
tokenAddress={props.tokenAddress as `0x${string}`}
paymentMethods={["card"]}
/>
)}

{tab === "swap" && (
<SwapWidget
client={props.client}
theme={themeObj}
className="!rounded-2xl !border-none !w-full"
prefill={{
sellToken: {
chainId: props.chain.id,
tokenAddress: props.tokenAddress,
},
buyToken: {
chainId: props.chain.id,
},
}}
onError={(error, quote) => {
const errorMessage = parseError(error);
reportTokenSwapFailed({
errorMessage: errorMessage,
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
}}
onSuccess={(quote) => {
reportTokenSwapSuccessful({
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
}}
onCancel={(quote) => {
reportTokenSwapCancelled({
buyTokenChainId: quote.intent.destinationChainId,
buyTokenAddress: quote.intent.destinationTokenAddress,
sellTokenChainId: quote.intent.originChainId,
sellTokenAddress: quote.intent.originTokenAddress,
pageType: props.pageType,
});
}}
/>
)}
</div>
);
}

function TabButton(props: {
label: string;
onClick: () => void;
isActive: boolean;
}) {
return (
<Button
onClick={props.onClick}
className={cn(
"rounded-full text-muted-foreground px-5 text-base bg-accent",
props.isActive && "text-foreground border-foreground",
)}
variant="outline"
>
{props.label}
</Button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { GridPattern } from "@/components/ui/background-patterns";

export function GridPatternEmbedContainer(props: {
children: React.ReactNode;
}) {
return (
<div className=" sm:flex sm:justify-center w-full sm:border sm:border-dashed sm:bg-accent/20 sm:py-12 rounded-lg overflow-hidden relative">
<GridPattern
width={30}
height={30}
x={-1}
y={-1}
strokeDasharray={"4 2"}
className="text-border dark:text-border/70 hidden lg:block"
style={{
maskImage:
"linear-gradient(to bottom right,white,transparent,transparent)",
}}
/>
<div className="sm:w-[420px] z-10">{props.children}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
"use client";
import { useTheme } from "next-themes";
import { defineChain, type ThirdwebClient } from "thirdweb";
import type { ThirdwebClient } from "thirdweb";
import type { ChainMetadata } from "thirdweb/chains";
import { BuyWidget } from "thirdweb/react";
import { getSDKTheme } from "@/utils/sdk-component-theme";
import { BuyAndSwapEmbed } from "@/components/blocks/BuyAndSwapEmbed";
import { GridPatternEmbedContainer } from "@/components/blocks/grid-pattern-embed-container";
import { defineDashboardChain } from "@/lib/defineDashboardChain";

export function BuyFundsSection(props: {
chain: ChainMetadata;
client: ThirdwebClient;
}) {
const { theme } = useTheme();
return (
<section className="flex flex-col gap-4 items-center justify-center">
<BuyWidget
amount="0"
// eslint-disable-next-line no-restricted-syntax
chain={defineChain(props.chain.chainId)}
<GridPatternEmbedContainer>
<BuyAndSwapEmbed
client={props.client}
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
// eslint-disable-next-line no-restricted-syntax
chain={defineDashboardChain(props.chain.chainId, props.chain)}
buyAmount={undefined}
tokenAddress={undefined}
pageType="chain"
/>
</section>
</GridPatternEmbedContainer>
);
}

This file was deleted.

Loading
Loading