Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fbb29dc25 | ||
|
|
71a1e58eaf | ||
|
|
68b48f4faa | ||
|
|
1c660d4712 | ||
|
|
30b5c30ba0 | ||
|
|
86461f9a07 | ||
|
|
2ebba6139a | ||
|
|
f3bacc16cb | ||
|
|
968913d55b | ||
|
|
bd70db4a5d | ||
|
|
d9a138a90a | ||
|
|
777aed1e5f | ||
|
|
fbed4be959 | ||
|
|
5a26217a43 | ||
|
|
938c1e2f60 | ||
|
|
af9a73d930 | ||
|
|
34c5d24043 | ||
|
|
e612f16a85 | ||
|
|
4a30b9c89a | ||
|
|
ef9cf3409b | ||
|
|
fd20a6cabb | ||
|
|
9095041ea7 | ||
|
|
2dbb9e2de4 | ||
|
|
00b8adf3a2 | ||
|
|
ae3dfa36c3 | ||
|
|
eb603f8908 | ||
|
|
e11b0da20e | ||
|
|
4c51fe6a8f | ||
|
|
64034c0ddb | ||
|
|
90c1e576ff | ||
|
|
27d5a81c49 | ||
|
|
dbadc23e04 | ||
|
|
21d5d0b6af | ||
|
|
b2239c91c9 |
@@ -2,7 +2,7 @@ name: Auto Author Assign
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, ready_for_review, draft]
|
||||
types: [opened, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
@@ -10,6 +10,7 @@ module.exports = [
|
||||
"/wallets",
|
||||
"/account-abstraction",
|
||||
"/payments",
|
||||
"/payments/x402",
|
||||
"/auth",
|
||||
"/in-app-wallets",
|
||||
"/transactions",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "server-only";
|
||||
|
||||
import { headers } from "next/headers";
|
||||
import { cookies, headers } from "next/headers";
|
||||
import Stripe from "stripe";
|
||||
import type { Team } from "@/api/team/get-team";
|
||||
import {
|
||||
@@ -88,6 +88,9 @@ export async function fetchClientSecret(team: Team) {
|
||||
throw new Error("No customer ID found");
|
||||
}
|
||||
|
||||
// try to get the gclid cookie
|
||||
const gclid = (await cookies()).get("gclid")?.value;
|
||||
|
||||
// Create Checkout Sessions from body params.
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
ui_mode: "embedded",
|
||||
@@ -117,6 +120,8 @@ export async function fetchClientSecret(team: Team) {
|
||||
missing_payment_method: "cancel",
|
||||
},
|
||||
},
|
||||
// if gclid exists, set it as a metadata field so we can attribute the conversion later
|
||||
metadata: gclid ? { gclid } : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export function reportContractDeployed(properties: {
|
||||
publisher: string | undefined;
|
||||
contractName: string | undefined;
|
||||
deploymentType?: "asset";
|
||||
is_testnet: boolean | undefined;
|
||||
}) {
|
||||
posthog.capture("contract deployed", properties);
|
||||
}
|
||||
@@ -40,6 +41,7 @@ export function reportContractDeployed(properties: {
|
||||
export function reportContractDeployFailed(properties: {
|
||||
errorMessage: string;
|
||||
chainId: number;
|
||||
is_testnet: boolean | undefined;
|
||||
publisher: string | undefined;
|
||||
contractName: string | undefined;
|
||||
}) {
|
||||
@@ -171,9 +173,7 @@ export function reportOnboardingCompleted() {
|
||||
*
|
||||
*/
|
||||
export function reportFaucetUsed(properties: { chainId: number }) {
|
||||
posthog.capture("faucet used", {
|
||||
chainId: properties.chainId,
|
||||
});
|
||||
posthog.capture("faucet used", properties);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
@@ -197,12 +197,7 @@ export function reportChainConfigurationAdded(properties: {
|
||||
decimals: number;
|
||||
};
|
||||
}) {
|
||||
posthog.capture("chain configuration added", {
|
||||
chainId: properties.chainId,
|
||||
chainName: properties.chainName,
|
||||
nativeCurrency: properties.nativeCurrency,
|
||||
rpcURLs: properties.rpcURLs,
|
||||
});
|
||||
posthog.capture("chain configuration added", properties);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
@@ -227,12 +222,9 @@ export function reportAssetBuySuccessful(properties: {
|
||||
chainId: number;
|
||||
contractType: AssetContractType | undefined;
|
||||
assetType: "nft" | "coin";
|
||||
is_testnet: boolean | undefined;
|
||||
}) {
|
||||
posthog.capture("asset buy successful", {
|
||||
assetType: properties.assetType,
|
||||
chainId: properties.chainId,
|
||||
contractType: properties.contractType,
|
||||
});
|
||||
posthog.capture("asset buy successful", properties);
|
||||
}
|
||||
|
||||
type TokenSwapParams = {
|
||||
@@ -349,16 +341,12 @@ export function reportTokenSwapCancelled(properties: TokenSwapParams) {
|
||||
*/
|
||||
export function reportAssetBuyFailed(properties: {
|
||||
chainId: number;
|
||||
is_testnet: boolean | undefined;
|
||||
contractType: AssetContractType | undefined;
|
||||
assetType: "nft" | "coin";
|
||||
error: string;
|
||||
}) {
|
||||
posthog.capture("asset buy failed", {
|
||||
assetType: properties.assetType,
|
||||
chainId: properties.chainId,
|
||||
contractType: properties.contractType,
|
||||
error: properties.error,
|
||||
});
|
||||
posthog.capture("asset buy failed", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -371,14 +359,11 @@ export function reportAssetBuyFailed(properties: {
|
||||
*/
|
||||
export function reportAssetBuyCancelled(properties: {
|
||||
chainId: number;
|
||||
is_testnet: boolean | undefined;
|
||||
contractType: AssetContractType | undefined;
|
||||
assetType: "nft" | "coin";
|
||||
}) {
|
||||
posthog.capture("asset buy cancelled", {
|
||||
assetType: properties.assetType,
|
||||
chainId: properties.chainId,
|
||||
contractType: properties.contractType,
|
||||
});
|
||||
posthog.capture("asset buy cancelled", properties);
|
||||
}
|
||||
|
||||
// Assets Landing Page ----------------------------
|
||||
@@ -423,10 +408,7 @@ export function reportAssetCreationStepConfigured(
|
||||
step: "coin-info" | "token-distribution" | "launch-coin";
|
||||
},
|
||||
) {
|
||||
posthog.capture("asset creation step configured", {
|
||||
assetType: properties.assetType,
|
||||
step: properties.step,
|
||||
});
|
||||
posthog.capture("asset creation step configured", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,11 +422,10 @@ export function reportAssetCreationStepConfigured(
|
||||
export function reportAssetCreationSuccessful(properties: {
|
||||
assetType: "nft" | "coin";
|
||||
contractType: AssetContractType;
|
||||
chainId: number;
|
||||
is_testnet: boolean | undefined;
|
||||
}) {
|
||||
posthog.capture("asset creation successful", {
|
||||
assetType: properties.assetType,
|
||||
contractType: properties.contractType,
|
||||
});
|
||||
posthog.capture("asset creation successful", properties);
|
||||
}
|
||||
|
||||
type CoinCreationStep =
|
||||
@@ -466,7 +447,12 @@ type CoinCreationStep =
|
||||
* @MananTank
|
||||
*/
|
||||
export function reportAssetCreationFailed(
|
||||
properties: { contractType: AssetContractType; error: string } & (
|
||||
properties: {
|
||||
contractType: AssetContractType;
|
||||
error: string;
|
||||
is_testnet: boolean | undefined;
|
||||
chainId: number;
|
||||
} & (
|
||||
| {
|
||||
assetType: "nft";
|
||||
step:
|
||||
@@ -481,12 +467,7 @@ export function reportAssetCreationFailed(
|
||||
}
|
||||
),
|
||||
) {
|
||||
posthog.capture("asset creation failed", {
|
||||
assetType: properties.assetType,
|
||||
contractType: properties.contractType,
|
||||
error: properties.error,
|
||||
step: properties.step,
|
||||
});
|
||||
posthog.capture("asset creation failed", properties);
|
||||
}
|
||||
|
||||
type UpsellParams = {
|
||||
@@ -582,6 +563,7 @@ export function reportPaymentLinkBuyFailed(properties: {
|
||||
export function reportAssetPageview(properties: {
|
||||
assetType: "nft" | "coin";
|
||||
chainId: number;
|
||||
is_testnet: boolean | undefined;
|
||||
}) {
|
||||
posthog.capture("asset pageview", properties);
|
||||
}
|
||||
@@ -593,7 +575,10 @@ export function reportAssetPageview(properties: {
|
||||
* ### Who is responsible for this event?
|
||||
* @MananTank
|
||||
*/
|
||||
export function reportChainPageview(properties: { chainId: number }) {
|
||||
export function reportChainPageview(properties: {
|
||||
chainId: number;
|
||||
is_testnet: boolean | undefined;
|
||||
}) {
|
||||
posthog.capture("chain pageview", properties);
|
||||
}
|
||||
|
||||
@@ -686,10 +671,7 @@ export function reportProductFeedback(properties: {
|
||||
feedback: string;
|
||||
source: "desktop" | "mobile";
|
||||
}) {
|
||||
posthog.capture("product feedback submitted", {
|
||||
feedback: properties.feedback,
|
||||
source: properties.source,
|
||||
});
|
||||
posthog.capture("product feedback submitted", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,7 @@ export function BuyAndSwapEmbed(props: {
|
||||
tokenAddress: string | undefined;
|
||||
buyAmount: string | undefined;
|
||||
pageType: PageType;
|
||||
isTestnet: boolean | undefined;
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
const [tab, setTab] = useState<"buy" | "swap">("swap");
|
||||
@@ -116,6 +117,7 @@ export function BuyAndSwapEmbed(props: {
|
||||
chainId: props.chain.id,
|
||||
error: errorMessage,
|
||||
contractType: undefined,
|
||||
is_testnet: props.isTestnet,
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -141,6 +143,7 @@ export function BuyAndSwapEmbed(props: {
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: undefined,
|
||||
is_testnet: props.isTestnet,
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -166,6 +169,7 @@ export function BuyAndSwapEmbed(props: {
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: undefined,
|
||||
is_testnet: props.isTestnet,
|
||||
});
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function AdvancedSearchInput(props: {
|
||||
value={searchType}
|
||||
onValueChange={(value) => setSearchType(value as SearchType)}
|
||||
>
|
||||
<SelectTrigger className="w-[140px] bg-card">
|
||||
<SelectTrigger className="w-[140px] bg-background rounded-full">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -61,7 +61,7 @@ export function AdvancedSearchInput(props: {
|
||||
<div className="flex flex-1">
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
className="bg-card pl-9 border-r-0 rounded-r-none"
|
||||
className="bg-background pl-9 border-r-0 rounded-r-none rounded-l-full"
|
||||
placeholder={`Search by ${searchType}...`}
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
@@ -86,7 +86,7 @@ export function AdvancedSearchInput(props: {
|
||||
onClick={handleSearch}
|
||||
variant="outline"
|
||||
disabled={!query.trim() || props.isLoading}
|
||||
className="rounded-l-none gap-2 bg-card disabled:opacity-100"
|
||||
className="rounded-l-none rounded-r-full gap-2 bg-background disabled:opacity-100"
|
||||
>
|
||||
{props.isLoading && <Spinner className="size-4" />}
|
||||
Search
|
||||
|
||||
+22
-9
@@ -2,7 +2,7 @@
|
||||
|
||||
import { createColumnHelper } from "@tanstack/react-table";
|
||||
import { format } from "date-fns";
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
|
||||
import { ArrowLeftIcon, ArrowRightIcon, UserIcon } from "lucide-react";
|
||||
import Papa from "papaparse";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
@@ -264,11 +264,24 @@ export function InAppWalletUsersPageContent(
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* Top section */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col md:flex-row lg:items-center justify-end gap-3">
|
||||
<div className="w-full max-w-lg">
|
||||
<div className="overflow-hidden rounded-xl border border-border bg-card">
|
||||
<div className="flex flex-col lg:flex-row lg:justify-between p-4 lg:px-6 py-5 lg:items-center gap-5">
|
||||
<div>
|
||||
<div className="flex mb-3">
|
||||
<div className="p-2 rounded-full bg-background border border-border">
|
||||
<UserIcon className="size-5 text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="font-semibold text-2xl tracking-tight">
|
||||
User Wallets
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
View and manage your project's users
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start lg:items-end gap-2.5 border-t lg:border-t-0 pt-5 lg:pt-0 border-dashed">
|
||||
<div className="w-full lg:w-auto lg:min-w-[320px]">
|
||||
<AdvancedSearchInput
|
||||
onSearch={handleSearch}
|
||||
onClear={handleClearSearch}
|
||||
@@ -277,7 +290,7 @@ export function InAppWalletUsersPageContent(
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="gap-2 bg-card"
|
||||
className="gap-2 bg-background rounded-full"
|
||||
disabled={wallets.length === 0 || isPending}
|
||||
onClick={downloadCSV}
|
||||
variant="outline"
|
||||
@@ -298,11 +311,11 @@ export function InAppWalletUsersPageContent(
|
||||
data={wallets}
|
||||
isFetched={walletsQuery.isFetched}
|
||||
isPending={walletsQuery.isPending}
|
||||
tableContainerClassName="rounded-b-none"
|
||||
tableContainerClassName="rounded-none border-x-0 border-b-0"
|
||||
title="in-app wallets"
|
||||
/>
|
||||
|
||||
<div className="flex justify-center gap-3 rounded-b-lg border border-t-0 bg-card p-6">
|
||||
<div className="flex justify-center gap-3 border-t bg-card p-6">
|
||||
<Button
|
||||
className="gap-2 bg-background"
|
||||
disabled={activePage === 1 || walletsQuery.isPending}
|
||||
|
||||
@@ -379,12 +379,27 @@ export function useEngineTransactions(params: {
|
||||
page?: number;
|
||||
status?: EngineStatus;
|
||||
};
|
||||
id?: string;
|
||||
}) {
|
||||
const { instanceUrl, autoUpdate, authToken } = params;
|
||||
const { instanceUrl, autoUpdate, authToken, id } = params;
|
||||
|
||||
return useQuery({
|
||||
placeholderData: keepPreviousData,
|
||||
queryFn: async () => {
|
||||
if (id) {
|
||||
const res = await fetch(`${instanceUrl}transaction/status/${id}`, {
|
||||
headers: getEngineRequestHeaders(authToken),
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
const transaction = (json.result as Transaction) || {};
|
||||
return {
|
||||
transactions: [transaction],
|
||||
totalCount: 1,
|
||||
};
|
||||
}
|
||||
|
||||
const url = new URL(`${instanceUrl}transaction/get-all`);
|
||||
if (params.queryParams) {
|
||||
for (const key in params.queryParams) {
|
||||
@@ -402,7 +417,6 @@ export function useEngineTransactions(params: {
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
return (json.result as TransactionResponse) || {};
|
||||
},
|
||||
queryKey: engineKeys.transactions(instanceUrl, params),
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ export function BuyFundsSection(props: { chain: ChainMetadata }) {
|
||||
return (
|
||||
<GridPatternEmbedContainer>
|
||||
<BuyAndSwapEmbed
|
||||
isTestnet={props.chain.testnet}
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
chain={defineDashboardChain(props.chain.chainId, props.chain)}
|
||||
buyAmount={undefined}
|
||||
|
||||
+2
-1
@@ -2,10 +2,11 @@
|
||||
import { reportChainPageview } from "@/analytics/report";
|
||||
import { useEffectOnce } from "@/hooks/useEffectOnce";
|
||||
|
||||
export function ChainPageView(props: { chainId: number }) {
|
||||
export function ChainPageView(props: { chainId: number; is_testnet: boolean }) {
|
||||
useEffectOnce(() => {
|
||||
reportChainPageview({
|
||||
chainId: props.chainId,
|
||||
is_testnet: props.is_testnet,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export default async function ChainPageLayout(props: {
|
||||
|
||||
return (
|
||||
<div className="flex grow flex-col">
|
||||
<ChainPageView chainId={chain.chainId} />
|
||||
<ChainPageView chainId={chain.chainId} is_testnet={chain.testnet} />
|
||||
<div className="border-border border-b bg-card">
|
||||
<TeamHeader />
|
||||
</div>
|
||||
|
||||
+2
@@ -5,11 +5,13 @@ import { useEffectOnce } from "@/hooks/useEffectOnce";
|
||||
export function AssetPageView(props: {
|
||||
assetType: "nft" | "coin";
|
||||
chainId: number;
|
||||
is_testnet: boolean;
|
||||
}) {
|
||||
useEffectOnce(() => {
|
||||
reportAssetPageview({
|
||||
assetType: props.assetType,
|
||||
chainId: props.chainId,
|
||||
is_testnet: props.is_testnet,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+3
@@ -122,6 +122,7 @@ export function TokenDropClaim(props: {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC20",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
toast.error("Failed to approve spending", {
|
||||
@@ -160,6 +161,7 @@ export function TokenDropClaim(props: {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC20",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
toast.error("Failed to buy tokens", {
|
||||
@@ -172,6 +174,7 @@ export function TokenDropClaim(props: {
|
||||
assetType: "coin",
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC20",
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
setStepsUI({
|
||||
|
||||
+6
-1
@@ -76,7 +76,11 @@ export async function ERC20PublicPage(props: {
|
||||
|
||||
return (
|
||||
<div className="flex grow flex-col">
|
||||
<AssetPageView assetType="coin" chainId={props.chainMetadata.chainId} />
|
||||
<AssetPageView
|
||||
assetType="coin"
|
||||
chainId={props.chainMetadata.chainId}
|
||||
is_testnet={props.chainMetadata.testnet}
|
||||
/>
|
||||
<PageHeader containerClassName="max-w-7xl" />
|
||||
|
||||
<div className="border-b border-dashed">
|
||||
@@ -199,6 +203,7 @@ function BuyEmbed(props: {
|
||||
tokenAddress={props.clientContract.address}
|
||||
buyAmount={undefined}
|
||||
pageType="asset"
|
||||
isTestnet={props.chainMetadata.testnet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+5
-1
@@ -18,7 +18,11 @@ export function NFTPublicPageLayout(props: {
|
||||
}) {
|
||||
return (
|
||||
<div className="flex grow flex-col">
|
||||
<AssetPageView assetType="nft" chainId={props.chainMetadata.chainId} />
|
||||
<AssetPageView
|
||||
assetType="nft"
|
||||
chainId={props.chainMetadata.chainId}
|
||||
is_testnet={props.chainMetadata.testnet}
|
||||
/>
|
||||
<PageHeader containerClassName="max-w-8xl" />
|
||||
<div className="border-b">
|
||||
<div className="container max-w-8xl">
|
||||
|
||||
+4
@@ -120,6 +120,7 @@ export function BuyEditionDrop(props: BuyEditionDropProps) {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC1155",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
console.error(errorMessage);
|
||||
@@ -147,6 +148,7 @@ export function BuyEditionDrop(props: BuyEditionDropProps) {
|
||||
assetType: "nft",
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC1155",
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
props.onSuccess?.();
|
||||
@@ -163,6 +165,7 @@ export function BuyEditionDrop(props: BuyEditionDropProps) {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC1155",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -180,6 +183,7 @@ export function BuyEditionDrop(props: BuyEditionDropProps) {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC1155",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+4
@@ -77,6 +77,7 @@ export function BuyNFTDrop(props: BuyNFTDropProps) {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC721",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -103,6 +104,7 @@ export function BuyNFTDrop(props: BuyNFTDropProps) {
|
||||
assetType: "nft",
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC721",
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
props.onSuccess?.();
|
||||
@@ -115,6 +117,7 @@ export function BuyNFTDrop(props: BuyNFTDropProps) {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC721",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -131,6 +134,7 @@ export function BuyNFTDrop(props: BuyNFTDropProps) {
|
||||
chainId: props.contract.chain.id,
|
||||
contractType: "DropERC721",
|
||||
error: errorMessage,
|
||||
is_testnet: props.chainMetadata.testnet,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+17
-5
@@ -190,25 +190,37 @@ export async function generateContractLayoutMetadata(params: {
|
||||
.replace("Testnet", "")
|
||||
.trim();
|
||||
|
||||
const title = `${contractDisplayName} | ${cleanedChainName} Smart Contract`;
|
||||
let title = `${contractDisplayName} | ${cleanedChainName} Smart Contract`;
|
||||
let description = "";
|
||||
|
||||
if (isERC721 || isERC1155) {
|
||||
description = `View tokens, source code, transactions, balances, and analytics for the ${contractDisplayName} smart contract on ${cleanedChainName}.`;
|
||||
} else if (isERC20) {
|
||||
description = `View ERC20 tokens, transactions, balances, source code, and analytics for the ${contractDisplayName} smart contract on ${cleanedChainName}`;
|
||||
title = `${contractMetadata.name} (${contractMetadata.symbol}) on ${cleanedChainName} | Buy, Swap, Bridge & Price`;
|
||||
description = `Buy, swap & bridge ${contractMetadata.name} (${contractMetadata.symbol}) on ${cleanedChainName} with thirdweb Bridge. View contract address, holders, analytics, transactions and live price.`;
|
||||
} else {
|
||||
description = `View tokens, transactions, balances, source code, and analytics for the ${contractDisplayName} smart contract on ${cleanedChainName}`;
|
||||
description = `View tokens, transactions, balances, source code, and analytics for the ${contractMetadata.name} smart contract on ${cleanedChainName}`;
|
||||
}
|
||||
|
||||
return {
|
||||
description: description,
|
||||
title: title,
|
||||
openGraph: {
|
||||
description: description,
|
||||
title: title,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
const fallbackTitle = `${shortenIfAddress(params.contractAddress)} | ${params.chainIdOrSlug}`;
|
||||
const fallbackDescription = `View tokens, transactions, balances, source code, and analytics for the smart contract on Chain ID ${params.chainIdOrSlug}`;
|
||||
|
||||
return {
|
||||
description: `View tokens, transactions, balances, source code, and analytics for the smart contract on Chain ID ${params.chainIdOrSlug}`,
|
||||
title: `${shortenIfAddress(params.contractAddress)} | ${params.chainIdOrSlug}`,
|
||||
description: fallbackDescription,
|
||||
title: fallbackTitle,
|
||||
openGraph: {
|
||||
description: fallbackDescription,
|
||||
title: fallbackTitle,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -50,6 +50,7 @@ import {
|
||||
} from "@/constants/addresses";
|
||||
import { LAST_USED_PROJECT_ID, LAST_USED_TEAM_ID } from "@/constants/cookie";
|
||||
import { ZERO_FEE_CHAINS } from "@/constants/fee-config";
|
||||
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
|
||||
import {
|
||||
useCustomFactoryAbi,
|
||||
useFunctionParamsFromABI,
|
||||
@@ -206,6 +207,8 @@ export const CustomContractForm: React.FC<CustomContractFormProps> = ({
|
||||
|
||||
const activeAccount = useActiveAccount();
|
||||
const walletChain = useActiveWalletChain();
|
||||
const walletChainMetadata = useV5DashboardChain(walletChain?.id);
|
||||
|
||||
const { onError } = useTxNotifications(
|
||||
"Successfully deployed contract",
|
||||
"Failed to deploy contract",
|
||||
@@ -656,6 +659,7 @@ export const CustomContractForm: React.FC<CustomContractFormProps> = ({
|
||||
chainId: walletChain.id,
|
||||
contractName: metadata.name,
|
||||
publisher: rewriteTwPublisher(metadata.publisher),
|
||||
is_testnet: walletChainMetadata?.testnet,
|
||||
});
|
||||
|
||||
deployStatusModal.nextStep();
|
||||
@@ -694,6 +698,7 @@ export const CustomContractForm: React.FC<CustomContractFormProps> = ({
|
||||
contractName: metadata.name,
|
||||
errorMessage: parsedError,
|
||||
publisher: rewriteTwPublisher(metadata.publisher),
|
||||
is_testnet: walletChain.testnet,
|
||||
});
|
||||
|
||||
deployStatusModal.close();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BringToFrontIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import { unstable_cache } from "next/cache";
|
||||
import Link from "next/link";
|
||||
import { Bridge } from "thirdweb";
|
||||
import { serverThirdwebClient } from "@/constants/thirdweb-client.server";
|
||||
import { PageHeader } from "./components/header";
|
||||
@@ -35,13 +34,10 @@ export default async function Page() {
|
||||
<h1 className="text-4xl font-bold tracking-tighter mb-1">
|
||||
Discover and swap any tokens on any chain, instantly
|
||||
</h1>
|
||||
<Link
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
href="https://thirdweb.com/monetize/bridge"
|
||||
target="_blank"
|
||||
>
|
||||
Powered by thirdweb bridge
|
||||
</Link>
|
||||
|
||||
<p className="text-muted-foreground">
|
||||
85+ chains, 3200+ tokens and 9+ million routes supported
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<TokenPage chains={chains} />
|
||||
|
||||
+29
-22
@@ -25,6 +25,34 @@ import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { ThirdwebMiniLogo } from "../../../../../../../components/ThirdwebMiniLogo";
|
||||
import { searchParams } from "../search-params";
|
||||
|
||||
function getStatusBadge(invoice: Stripe.Invoice) {
|
||||
switch (invoice.status) {
|
||||
case "paid":
|
||||
return <Badge variant="success">Paid</Badge>;
|
||||
case "open":
|
||||
// we treat "uncollectible" as unpaid
|
||||
case "uncollectible": {
|
||||
// if the invoice has a due date, use that, otherwise use the effective at date (which is the date the invoice was created)
|
||||
const effectiveDueDate = invoice.due_date || invoice.effective_at;
|
||||
|
||||
// if the invoice due date is in the past, we want to display it as past due
|
||||
if (effectiveDueDate && effectiveDueDate * 1000 < Date.now()) {
|
||||
return <Badge variant="destructive">Past Due</Badge>;
|
||||
}
|
||||
return <Badge variant="outline">Open</Badge>;
|
||||
}
|
||||
case "void":
|
||||
return <Badge variant="secondary">Void</Badge>;
|
||||
|
||||
default:
|
||||
return <Badge variant="outline">Unknown</Badge>;
|
||||
}
|
||||
}
|
||||
|
||||
function isInvoicePayable(invoice: Stripe.Invoice) {
|
||||
return invoice.status === "open" || invoice.status === "uncollectible";
|
||||
}
|
||||
|
||||
export function BillingHistory(props: {
|
||||
teamSlug: string;
|
||||
invoices: Stripe.Invoice[];
|
||||
@@ -57,27 +85,6 @@ export function BillingHistory(props: {
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusBadge = (invoice: Stripe.Invoice) => {
|
||||
switch (invoice.status) {
|
||||
case "paid":
|
||||
return <Badge variant="success">Paid</Badge>;
|
||||
case "open":
|
||||
// we treate "uncollectible" as unpaid
|
||||
case "uncollectible": {
|
||||
// if the invoice due date is in the past, we want to display it as past due
|
||||
if (invoice.due_date && invoice.due_date * 1000 < Date.now()) {
|
||||
return <Badge variant="destructive">Past Due</Badge>;
|
||||
}
|
||||
return <Badge variant="outline">Open</Badge>;
|
||||
}
|
||||
case "void":
|
||||
return <Badge variant="secondary">Void</Badge>;
|
||||
|
||||
default:
|
||||
return <Badge variant="outline">Unknown</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
if (props.invoices.length === 0) {
|
||||
if (props.status === "open") {
|
||||
return (
|
||||
@@ -126,7 +133,7 @@ export function BillingHistory(props: {
|
||||
<TableCell>{getStatusBadge(invoice)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end space-x-2">
|
||||
{invoice.status === "open" && (
|
||||
{isInvoicePayable(invoice) && (
|
||||
<>
|
||||
{/* always show the crypto payment button */}
|
||||
<ToolTipLabel
|
||||
|
||||
+1
-1
@@ -552,7 +552,7 @@ export function AccountAbstractionSettingsPage(
|
||||
you build your own rules.{" "}
|
||||
<UnderlineLink
|
||||
className="text-primary-500"
|
||||
href="https://portal.thirdweb.com/wallets/smart-wallet/sponsorship-rules#setting-up-a-server-verifier"
|
||||
href="https://portal.thirdweb.com/transactions/policies#server-verifier"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
||||
+3
-3
@@ -28,11 +28,11 @@ export function QuickStartSection(props: {
|
||||
icon={ArrowRightLeftIcon}
|
||||
setupTime={5}
|
||||
id="swap_tokens"
|
||||
features={["Swap any token", "Cross-chain swaps"]}
|
||||
features={["Swap any token", "Cross-chain swap"]}
|
||||
description="Swap tokens cross-chain with dedicated swapping endpoints."
|
||||
link={{
|
||||
href: `https://portal.thirdweb.com/payments/swap`,
|
||||
label: "Setup Swaps",
|
||||
href: `https://portal.thirdweb.com/bridge/swap`,
|
||||
label: "Setup Swap",
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
+8
-58
@@ -69,70 +69,20 @@ export default async function Page(props: {
|
||||
},
|
||||
},
|
||||
links: [
|
||||
// TODO - add docs when bridge docs are added in portal
|
||||
// {
|
||||
// type: "docs",
|
||||
// href: "https://portal.thirdweb.com/payments",
|
||||
// },
|
||||
{
|
||||
type: "docs",
|
||||
href: "https://portal.thirdweb.com/bridge",
|
||||
},
|
||||
{
|
||||
type: "playground",
|
||||
href: "https://playground.thirdweb.com/bridge/swap-widget",
|
||||
},
|
||||
{
|
||||
type: "api",
|
||||
href: "https://api.thirdweb.com/reference#tag/bridge",
|
||||
},
|
||||
],
|
||||
}}
|
||||
footer={{
|
||||
center: {
|
||||
links: [
|
||||
{
|
||||
href: "https://playground.thirdweb.com/payments/ui-components",
|
||||
label: "UI Component",
|
||||
},
|
||||
{
|
||||
href: "https://playground.thirdweb.com/connect/payments/fund-wallet",
|
||||
label: "Buy Crypto",
|
||||
},
|
||||
{
|
||||
href: "https://playground.thirdweb.com/connect/payments/commerce",
|
||||
label: "Checkout",
|
||||
},
|
||||
{
|
||||
href: "https://playground.thirdweb.com/connect/payments/transactions",
|
||||
label: "Transactions",
|
||||
},
|
||||
],
|
||||
title: "Demos",
|
||||
},
|
||||
left: {
|
||||
links: [
|
||||
{
|
||||
href: "https://portal.thirdweb.com/payments",
|
||||
label: "Overview",
|
||||
},
|
||||
{
|
||||
href: "https://portal.thirdweb.com/typescript/v5/convertCryptoToFiat",
|
||||
label: "TypeScript",
|
||||
},
|
||||
{
|
||||
href: "https://portal.thirdweb.com/react/v5/pay/fund-wallets",
|
||||
label: "React",
|
||||
},
|
||||
{
|
||||
href: "https://portal.thirdweb.com/dotnet/universal-bridge/quickstart",
|
||||
label: ".NET",
|
||||
},
|
||||
],
|
||||
title: "Documentation",
|
||||
},
|
||||
right: {
|
||||
links: [
|
||||
{
|
||||
href: "https://www.youtube.com/watch?v=aBu175-VsNY",
|
||||
label: "Implement cross-chain payments in any app",
|
||||
},
|
||||
],
|
||||
title: "Tutorials",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-12">
|
||||
<ResponsiveSearchParamsProvider value={searchParams}>
|
||||
|
||||
+13
@@ -1,3 +1,4 @@
|
||||
import { Input } from "@workspace/ui/components/input";
|
||||
import { format, formatDistanceToNowStrict } from "date-fns";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
@@ -110,6 +111,7 @@ export function TransactionsTable(props: {
|
||||
const [autoUpdate, setAutoUpdate] = useState(true);
|
||||
const [page, setPage] = useState(1);
|
||||
const [status, setStatus] = useState<EngineStatus | undefined>(undefined);
|
||||
const [filterId, setFilterId] = useState<string | undefined>(undefined);
|
||||
const autoUpdateId = useId();
|
||||
const pageSize = 10;
|
||||
const transactionsQuery = useEngineTransactions({
|
||||
@@ -121,6 +123,7 @@ export function TransactionsTable(props: {
|
||||
page: page,
|
||||
status,
|
||||
},
|
||||
id: filterId,
|
||||
});
|
||||
|
||||
const transactions = transactionsQuery.data?.transactions ?? [];
|
||||
@@ -160,6 +163,16 @@ export function TransactionsTable(props: {
|
||||
onCheckedChange={(v) => setAutoUpdate(!!v)}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className="max-w-[250px]"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.trim();
|
||||
setFilterId(value || undefined);
|
||||
setPage(1);
|
||||
}}
|
||||
placeholder="Filter by Queue ID"
|
||||
value={filterId || ""}
|
||||
/>
|
||||
<StatusSelector
|
||||
setStatus={(v) => {
|
||||
setStatus(v);
|
||||
|
||||
+4
@@ -74,6 +74,10 @@ export type CreateNFTCollectionFunctions = {
|
||||
count: number;
|
||||
};
|
||||
gasless: boolean;
|
||||
onNotEnoughFunds: (data: {
|
||||
requiredAmount: string;
|
||||
balance: string;
|
||||
}) => void;
|
||||
}) => Promise<void>;
|
||||
lazyMintNFTs: (params: {
|
||||
values: CreateNFTCollectionAllValues;
|
||||
|
||||
+98
-3
@@ -1,6 +1,14 @@
|
||||
"use client";
|
||||
import { useRef } from "react";
|
||||
import { encode, getContract, type ThirdwebClient } from "thirdweb";
|
||||
import {
|
||||
encode,
|
||||
estimateGasCost,
|
||||
getContract,
|
||||
getGasPrice,
|
||||
type PreparedTransaction,
|
||||
type ThirdwebClient,
|
||||
toEther,
|
||||
} from "thirdweb";
|
||||
import { deployERC721Contract, deployERC1155Contract } from "thirdweb/deploys";
|
||||
import { multicall } from "thirdweb/extensions/common";
|
||||
import {
|
||||
@@ -14,7 +22,8 @@ import {
|
||||
} from "thirdweb/extensions/erc1155";
|
||||
import { grantRole } from "thirdweb/extensions/permissions";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { maxUint256 } from "thirdweb/utils";
|
||||
import { maxUint256, resolvePromisedValue } from "thirdweb/utils";
|
||||
import { getWalletBalance } from "thirdweb/wallets";
|
||||
import { create7702MinimalAccount } from "thirdweb/wallets/smart";
|
||||
import { revalidatePathAction } from "@/actions/revalidate";
|
||||
import { reportContractDeployed } from "@/analytics/report";
|
||||
@@ -39,6 +48,10 @@ export function CreateNFTPage(props: {
|
||||
const contractAddressRef = useRef<string | undefined>(undefined);
|
||||
const getChain = useGetV5DashboardChain();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const sendAndConfirmTxNoPayModal = useSendAndConfirmTx({
|
||||
payModal: false,
|
||||
});
|
||||
|
||||
function getAccount(params: { gasless: boolean }) {
|
||||
if (!activeAccount) {
|
||||
throw new Error("Wallet is not connected");
|
||||
@@ -130,6 +143,7 @@ export function CreateNFTPage(props: {
|
||||
contractName: contractType,
|
||||
deploymentType: "asset",
|
||||
publisher: "deployer.thirdweb.eth",
|
||||
is_testnet: chain.testnet,
|
||||
});
|
||||
|
||||
contractAddressRef.current = contractAddress;
|
||||
@@ -218,6 +232,10 @@ export function CreateNFTPage(props: {
|
||||
count: number;
|
||||
};
|
||||
gasless: boolean;
|
||||
onNotEnoughFunds: (data: {
|
||||
requiredAmount: string;
|
||||
balance: string;
|
||||
}) => void;
|
||||
}) {
|
||||
const { values, batch } = params;
|
||||
const contract = getDeployedContract({
|
||||
@@ -277,7 +295,46 @@ export function CreateNFTPage(props: {
|
||||
data: encodedTransactions,
|
||||
});
|
||||
|
||||
await sendAndConfirmTx.mutateAsync(tx);
|
||||
// if there are more than one batches, on the first batch, we check if the user has enough funds to cover the cost of all the batches ->
|
||||
// calculate the cost of one batch, multiply by the number of batches to get the total cost
|
||||
// if the user does not have enough funds, call the onNotEnoughFunds callback
|
||||
|
||||
const totalBatches = Math.ceil(values.nfts.length / batch.count);
|
||||
|
||||
if (batch.startIndex === 0 && totalBatches > 1 && !params.gasless) {
|
||||
if (!activeAccount) {
|
||||
throw new Error("Wallet is not connected");
|
||||
}
|
||||
|
||||
const costPerBatch = await getTotalTransactionCost({
|
||||
tx: tx,
|
||||
from: activeAccount.address,
|
||||
});
|
||||
|
||||
const totalCost = costPerBatch * BigInt(totalBatches);
|
||||
|
||||
const walletBalance = await getWalletBalance({
|
||||
address: activeAccount.address,
|
||||
chain: contract.chain,
|
||||
client: contract.client,
|
||||
});
|
||||
|
||||
if (walletBalance.value < totalCost) {
|
||||
params.onNotEnoughFunds({
|
||||
balance: toEther(walletBalance.value),
|
||||
requiredAmount: toEther(totalCost),
|
||||
});
|
||||
throw new Error(
|
||||
`Not enough funds: Required ${toEther(totalCost)}, Balance ${toEther(walletBalance.value)}`,
|
||||
);
|
||||
}
|
||||
|
||||
await sendAndConfirmTxNoPayModal.mutateAsync(tx);
|
||||
} else if (totalBatches > 1) {
|
||||
await sendAndConfirmTxNoPayModal.mutateAsync(tx);
|
||||
} else {
|
||||
await sendAndConfirmTx.mutateAsync(tx);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSetAdmins(params: {
|
||||
@@ -390,3 +447,41 @@ function transformSocialUrls(
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
}
|
||||
|
||||
async function getTransactionGasCost(tx: PreparedTransaction, from?: string) {
|
||||
try {
|
||||
const gasCost = await estimateGasCost({
|
||||
from,
|
||||
transaction: tx,
|
||||
});
|
||||
|
||||
const bufferCost = gasCost.wei / 10n;
|
||||
|
||||
// Note: get tx.value AFTER estimateGasCost
|
||||
// add 10% extra gas cost to the estimate to ensure user buys enough to cover the tx cost
|
||||
return gasCost.wei + bufferCost;
|
||||
} catch {
|
||||
if (from) {
|
||||
// try again without passing from
|
||||
return await getTransactionGasCost(tx);
|
||||
}
|
||||
// fallback if both fail, use the tx value + 1M * gas price
|
||||
const gasPrice = await getGasPrice({
|
||||
chain: tx.chain,
|
||||
client: tx.client,
|
||||
});
|
||||
|
||||
return 1_000_000n * gasPrice;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTotalTransactionCost(params: {
|
||||
tx: PreparedTransaction;
|
||||
from?: string;
|
||||
}) {
|
||||
const [txValue, txGasCost] = await Promise.all([
|
||||
resolvePromisedValue(params.tx.value),
|
||||
getTransactionGasCost(params.tx, params.from),
|
||||
]);
|
||||
return (txValue || 0n) + txGasCost;
|
||||
}
|
||||
|
||||
+180
-32
@@ -1,14 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
ArrowUpFromLineIcon,
|
||||
ImageOffIcon,
|
||||
RefreshCcwIcon,
|
||||
ShoppingBagIcon,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import {
|
||||
BuyWidget,
|
||||
TokenProvider,
|
||||
TokenSymbol,
|
||||
useActiveAccount,
|
||||
@@ -37,6 +42,7 @@ import {
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
import { ChainOverview } from "../../_common/chain-overview";
|
||||
import { StepCard } from "../../_common/step-card";
|
||||
import { StorageErrorPlanUpsell } from "../../_common/storage-error-upsell";
|
||||
@@ -71,6 +77,10 @@ export function LaunchNFT(props: {
|
||||
const walletRequiresApproval = activeWallet?.id !== "inApp";
|
||||
const account = useActiveAccount();
|
||||
const contractAddressRef = useRef<string | null>(null);
|
||||
const { theme } = useTheme();
|
||||
const chainMeta = useV5DashboardChain(
|
||||
Number(formValues.collectionInfo.chain),
|
||||
);
|
||||
|
||||
function updateStatus(
|
||||
index: number,
|
||||
@@ -161,6 +171,18 @@ export function LaunchNFT(props: {
|
||||
const [isGasless, setIsGasless] = useState(canEnableGasless);
|
||||
const showGaslessSection = false; // activeWallet?.id === "inApp";
|
||||
|
||||
const [notEnoughFunds, setNotEnoughFunds] = useState<
|
||||
| false
|
||||
| {
|
||||
values: {
|
||||
requiredAmount: string;
|
||||
balance: string;
|
||||
};
|
||||
isBuySuccess: boolean;
|
||||
showBuyWidget: boolean;
|
||||
}
|
||||
>(false);
|
||||
|
||||
const contractLink = contractAddressRef.current
|
||||
? `/team/${props.teamSlug}/${props.projectSlug}/contract/${formValues.collectionInfo.chain}/${contractAddressRef.current}`
|
||||
: null;
|
||||
@@ -204,6 +226,16 @@ export function LaunchNFT(props: {
|
||||
},
|
||||
gasless: isGasless,
|
||||
values: formValues,
|
||||
onNotEnoughFunds: (data) => {
|
||||
setNotEnoughFunds({
|
||||
values: {
|
||||
balance: data.balance,
|
||||
requiredAmount: data.requiredAmount,
|
||||
},
|
||||
isBuySuccess: false,
|
||||
showBuyWidget: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
batchesProcessedRef.current += 1;
|
||||
@@ -216,6 +248,16 @@ export function LaunchNFT(props: {
|
||||
},
|
||||
gasless: isGasless,
|
||||
values: formValues,
|
||||
onNotEnoughFunds: (data) => {
|
||||
setNotEnoughFunds({
|
||||
values: {
|
||||
balance: data.balance,
|
||||
requiredAmount: data.requiredAmount,
|
||||
},
|
||||
isBuySuccess: false,
|
||||
showBuyWidget: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -274,6 +316,8 @@ export function LaunchNFT(props: {
|
||||
contractType: ercType === "erc721" ? "DropERC721" : "DropERC1155",
|
||||
error: errorMessage,
|
||||
step: currentStep.id,
|
||||
is_testnet: chainMeta.testnet,
|
||||
chainId: Number(formValues.collectionInfo.chain),
|
||||
});
|
||||
|
||||
throw error;
|
||||
@@ -283,6 +327,8 @@ export function LaunchNFT(props: {
|
||||
reportAssetCreationSuccessful({
|
||||
assetType: "nft",
|
||||
contractType: ercType === "erc721" ? "DropERC721" : "DropERC1155",
|
||||
chainId: Number(formValues.collectionInfo.chain),
|
||||
is_testnet: chainMeta?.testnet ?? false,
|
||||
});
|
||||
|
||||
props.onLaunchSuccess();
|
||||
@@ -357,41 +403,143 @@ export function LaunchNFT(props: {
|
||||
className="gap-0 overflow-hidden p-0 md:max-w-[480px]"
|
||||
dialogCloseClassName="hidden"
|
||||
>
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
<DialogHeader className="space-y-0.5">
|
||||
<DialogTitle className="font-semibold text-xl tracking-tight">
|
||||
Status
|
||||
</DialogTitle>
|
||||
{walletRequiresApproval && (
|
||||
<DialogDescription>
|
||||
Each step will prompt a signature request in your wallet
|
||||
</DialogDescription>
|
||||
)}
|
||||
</DialogHeader>
|
||||
{/** biome-ignore lint/complexity/useOptionalChain: can't change it to optional chain */}
|
||||
{notEnoughFunds && notEnoughFunds.showBuyWidget ? (
|
||||
<div className="bg-card">
|
||||
<div className="border-b py-2 px-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setNotEnoughFunds({
|
||||
...notEnoughFunds,
|
||||
showBuyWidget: false,
|
||||
})
|
||||
}
|
||||
className="gap-2 px-2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ArrowLeftIcon className="size-4" />
|
||||
Back
|
||||
</Button>
|
||||
</div>
|
||||
<BuyWidget
|
||||
client={props.client}
|
||||
chain={chain}
|
||||
amount={String(
|
||||
Number(notEnoughFunds.values.requiredAmount) -
|
||||
Number(notEnoughFunds.values.balance),
|
||||
)}
|
||||
buttonLabel={`Buy ${chainMeta?.nativeCurrency?.symbol || "ETH"}`}
|
||||
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
|
||||
className="!border-none !w-full !rounded-none"
|
||||
onSuccess={() => {
|
||||
setNotEnoughFunds({
|
||||
...notEnoughFunds,
|
||||
isBuySuccess: true,
|
||||
showBuyWidget: false,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-6 p-6 mb-2">
|
||||
<DialogHeader className="space-y-0.5">
|
||||
<DialogTitle className="font-semibold text-xl tracking-tight">
|
||||
Status
|
||||
</DialogTitle>
|
||||
{walletRequiresApproval && (
|
||||
<DialogDescription>
|
||||
Each step will prompt a signature request in your wallet
|
||||
</DialogDescription>
|
||||
)}
|
||||
</DialogHeader>
|
||||
|
||||
<MultiStepStatus
|
||||
onRetry={handleRetry}
|
||||
renderError={(step, errorMessage) => {
|
||||
if (
|
||||
props.teamPlan === "free" &&
|
||||
errorMessage.toLowerCase().includes("storage limit")
|
||||
) {
|
||||
return (
|
||||
<StorageErrorPlanUpsell
|
||||
onRetry={() => handleRetry(step)}
|
||||
teamSlug={props.teamSlug}
|
||||
trackingCampaign="create-nft"
|
||||
/>
|
||||
);
|
||||
}
|
||||
<MultiStepStatus
|
||||
onRetry={handleRetry}
|
||||
renderError={(step, errorMessage) => {
|
||||
if (
|
||||
props.teamPlan === "free" &&
|
||||
errorMessage.toLowerCase().includes("storage limit")
|
||||
) {
|
||||
return (
|
||||
<StorageErrorPlanUpsell
|
||||
onRetry={() => handleRetry(step)}
|
||||
teamSlug={props.teamSlug}
|
||||
trackingCampaign="create-nft"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
steps={steps}
|
||||
/>
|
||||
</div>
|
||||
if (notEnoughFunds) {
|
||||
return (
|
||||
<div>
|
||||
{notEnoughFunds.isBuySuccess ? (
|
||||
<p className="text-success-text text-sm mt-1.5 mb-2">
|
||||
Funds bought successfully
|
||||
</p>
|
||||
) : (
|
||||
<div className="mt-2 mb-2 space-y-1">
|
||||
<p className="text-destructive-text text-sm">
|
||||
Not enough funds
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Required Funds:{" "}
|
||||
{notEnoughFunds.values.requiredAmount}{" "}
|
||||
{chainMeta?.nativeCurrency?.symbol || "ETH"}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Balance: {notEnoughFunds.values.balance}{" "}
|
||||
{chainMeta?.nativeCurrency?.symbol || "ETH"}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-2 flex justify-end gap-4 border-border border-t bg-card p-6">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleRetry(step);
|
||||
setNotEnoughFunds(false);
|
||||
}}
|
||||
size="sm"
|
||||
variant={
|
||||
notEnoughFunds.isBuySuccess
|
||||
? "default"
|
||||
: "outline"
|
||||
}
|
||||
className="gap-2"
|
||||
>
|
||||
<RefreshCcwIcon className="size-3.5" />
|
||||
Retry
|
||||
</Button>
|
||||
|
||||
{!notEnoughFunds.isBuySuccess && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setNotEnoughFunds({
|
||||
...notEnoughFunds,
|
||||
showBuyWidget: true,
|
||||
});
|
||||
}}
|
||||
size="sm"
|
||||
className="gap-2"
|
||||
>
|
||||
<ShoppingBagIcon className="size-3.5" />
|
||||
Buy Funds
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}}
|
||||
steps={steps}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-4 border-border border-t bg-card p-6">
|
||||
{!isComplete ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
|
||||
+6
-1
@@ -178,12 +178,14 @@ export function CreateTokenAssetPage(props: {
|
||||
teamId: props.teamId,
|
||||
});
|
||||
|
||||
const chainMetadata = getChain(Number(params.values.chain));
|
||||
reportContractDeployed({
|
||||
address: contractAddress,
|
||||
chainId: Number(params.values.chain),
|
||||
contractName: "DropERC20",
|
||||
contractName: "ERC20Asset",
|
||||
deploymentType: "asset",
|
||||
publisher: account.address,
|
||||
is_testnet: chainMetadata.testnet,
|
||||
});
|
||||
|
||||
contractAddressRef.current = contractAddress;
|
||||
@@ -299,12 +301,15 @@ export function CreateTokenAssetPage(props: {
|
||||
teamId: props.teamId,
|
||||
});
|
||||
|
||||
const chainMetadata = getChain(Number(values.chain));
|
||||
|
||||
reportContractDeployed({
|
||||
address: contractAddress,
|
||||
chainId: Number(values.chain),
|
||||
contractName: "DropERC20",
|
||||
deploymentType: "asset",
|
||||
publisher: "deployer.thirdweb.eth",
|
||||
is_testnet: chainMetadata.testnet,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
+4
@@ -230,6 +230,8 @@ export function LaunchTokenStatus(props: {
|
||||
: "ERC20Asset",
|
||||
error: errorMessage,
|
||||
step: currentStep.id,
|
||||
is_testnet: chainMetadata?.testnet,
|
||||
chainId: Number(formValues.chain),
|
||||
});
|
||||
|
||||
updateStatus(i, {
|
||||
@@ -247,6 +249,8 @@ export function LaunchTokenStatus(props: {
|
||||
formValues.saleMode === "drop-erc20:token-drop"
|
||||
? "DropERC20"
|
||||
: "ERC20Asset",
|
||||
chainId: Number(formValues.chain),
|
||||
is_testnet: chainMetadata?.testnet,
|
||||
});
|
||||
|
||||
if (contractAddressRef.current) {
|
||||
|
||||
+48
-18
@@ -13,6 +13,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
|
||||
import { CopyTextButton } from "@/components/ui/CopyTextButton";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
@@ -49,6 +50,8 @@ export function TransactionsTableUI(props: {
|
||||
getData: (params: {
|
||||
page: number;
|
||||
status: TransactionStatus | undefined;
|
||||
id: string | undefined;
|
||||
from: string | undefined;
|
||||
}) => Promise<TransactionsResponse>;
|
||||
project: Project;
|
||||
teamSlug: string;
|
||||
@@ -60,13 +63,15 @@ export function TransactionsTableUI(props: {
|
||||
const [status, setStatus] = useState<TransactionStatus | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [id, setId] = useState<string | undefined>(undefined);
|
||||
const [from, setFrom] = useState<string | undefined>(undefined);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const pageSize = 10;
|
||||
const transactionsQuery = useQuery({
|
||||
placeholderData: keepPreviousData,
|
||||
queryFn: () => props.getData({ page, status }),
|
||||
queryKey: ["transactions", props.project.id, page, status],
|
||||
queryFn: () => props.getData({ page, status, id, from }),
|
||||
queryKey: ["transactions", props.project.id, page, status, id, from],
|
||||
refetchInterval: autoUpdate ? 4_000 : false,
|
||||
});
|
||||
|
||||
@@ -84,18 +89,18 @@ export function TransactionsTableUI(props: {
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-lg border border-border bg-card">
|
||||
<div className="flex flex-col gap-4 rounded-lg rounded-b-none px-6 py-6 lg:flex-row lg:justify-between">
|
||||
<div>
|
||||
<h2 className="font-semibold text-2xl tracking-tight">
|
||||
Transaction History
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Transactions sent from server wallets
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 rounded-lg rounded-b-none px-6 py-6">
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h2 className="font-semibold text-2xl tracking-tight">
|
||||
Transaction History
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Transactions sent from server wallets
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-5 border-border border-t pt-4 lg:border-none lg:pt-0">
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2 border-border border-t pt-4 lg:border-none lg:pt-0">
|
||||
<Label htmlFor={autoUpdateId}>Auto Update</Label>
|
||||
<Switch
|
||||
checked={autoUpdate}
|
||||
@@ -103,14 +108,39 @@ export function TransactionsTableUI(props: {
|
||||
onCheckedChange={(v) => setAutoUpdate(!!v)}
|
||||
/>
|
||||
</div>
|
||||
<StatusSelector
|
||||
setStatus={(v) => {
|
||||
setStatus(v);
|
||||
// reset page
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-end gap-3">
|
||||
<Input
|
||||
className="max-w-[250px]"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.trim();
|
||||
setId(value || undefined);
|
||||
setPage(1);
|
||||
}}
|
||||
status={status}
|
||||
placeholder="Filter by Queue ID"
|
||||
value={id || ""}
|
||||
/>
|
||||
<Input
|
||||
className="max-w-[250px]"
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.trim();
|
||||
setFrom(value || undefined);
|
||||
setPage(1);
|
||||
}}
|
||||
placeholder="Filter by wallet address"
|
||||
value={from || ""}
|
||||
/>
|
||||
<div>
|
||||
<StatusSelector
|
||||
setStatus={(v) => {
|
||||
setStatus(v);
|
||||
// reset page
|
||||
setPage(1);
|
||||
}}
|
||||
status={status}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+9
-1
@@ -16,11 +16,13 @@ export function TransactionsTable(props: {
|
||||
return (
|
||||
<TransactionsTableUI
|
||||
client={props.client}
|
||||
getData={async ({ page, status }) => {
|
||||
getData={async ({ page, status, id, from }) => {
|
||||
return await getTransactions({
|
||||
page,
|
||||
project: props.project,
|
||||
status,
|
||||
id,
|
||||
from,
|
||||
});
|
||||
}}
|
||||
project={props.project}
|
||||
@@ -34,10 +36,14 @@ async function getTransactions({
|
||||
project,
|
||||
page,
|
||||
status,
|
||||
id,
|
||||
from,
|
||||
}: {
|
||||
project: Project;
|
||||
page: number;
|
||||
status: TransactionStatus | undefined;
|
||||
id: string | undefined;
|
||||
from: string | undefined;
|
||||
}) {
|
||||
const transactions = await engineCloudProxy<{ result: TransactionsResponse }>(
|
||||
{
|
||||
@@ -52,6 +58,8 @@ async function getTransactions({
|
||||
limit: "20",
|
||||
page: page.toString(),
|
||||
status: status ?? undefined,
|
||||
id: id ?? undefined,
|
||||
from: from ?? undefined,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
+16
-24
@@ -96,15 +96,22 @@ export function ServerWalletsTableUI({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start lg:items-end gap-5 border-t lg:border-t-0 pt-5 lg:pt-0 border-dashed">
|
||||
<SingleNetworkSelector
|
||||
chainId={selectedChainId}
|
||||
onChange={setSelectedChainId}
|
||||
client={client}
|
||||
disableChainId
|
||||
className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50"
|
||||
placeholder="Select network"
|
||||
popoverContentClassName="!w-[320px] rounded-xl overflow-hidden"
|
||||
/>
|
||||
<div className="flex flex-row gap-2.5">
|
||||
<CreateServerWallet
|
||||
managementAccessToken={managementAccessToken}
|
||||
project={project}
|
||||
teamSlug={teamSlug}
|
||||
/>
|
||||
<SingleNetworkSelector
|
||||
chainId={selectedChainId}
|
||||
onChange={setSelectedChainId}
|
||||
client={client}
|
||||
disableChainId
|
||||
className="w-fit min-w-[180px] rounded-full bg-background hover:bg-accent/50"
|
||||
placeholder="Select network"
|
||||
popoverContentClassName="!w-[320px] rounded-xl overflow-hidden"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5">
|
||||
<Label
|
||||
@@ -187,25 +194,10 @@ export function ServerWalletsTableUI({
|
||||
<XIcon className="size-5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-muted-foreground">No server wallets found</p>
|
||||
<CreateServerWallet
|
||||
managementAccessToken={managementAccessToken}
|
||||
project={project}
|
||||
teamSlug={teamSlug}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</TableContainer>
|
||||
|
||||
{wallets.length > 0 && (
|
||||
<div className="flex justify-end items-center p-4 py-5 lg:px-6 border-t">
|
||||
<CreateServerWallet
|
||||
managementAccessToken={managementAccessToken}
|
||||
project={project}
|
||||
teamSlug={teamSlug}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{totalPages > 1 && (
|
||||
<div className="flex flex-col items-center border-t p-6">
|
||||
<div className="mb-4 text-muted-foreground text-sm">
|
||||
|
||||
+69
-7
@@ -1,3 +1,4 @@
|
||||
import { createVaultClient, listEoas } from "@thirdweb-dev/vault-sdk";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
|
||||
import { getAuthToken } from "@/api/auth-token";
|
||||
@@ -6,10 +7,13 @@ import type { DurationId } from "@/components/analytics/date-range-selector";
|
||||
import { ResponsiveTimeFilters } from "@/components/analytics/responsive-time-filters";
|
||||
import { ProjectPage } from "@/components/blocks/project-page/project-page";
|
||||
import { InAppWalletUsersPageContent } from "@/components/in-app-wallet-users-content/in-app-wallet-users-content";
|
||||
import { NEXT_PUBLIC_THIRDWEB_VAULT_URL } from "@/constants/public-envs";
|
||||
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
|
||||
import { WalletProductIcon } from "@/icons/WalletProductIcon";
|
||||
import { getFiltersFromSearchParams } from "@/lib/time";
|
||||
import { loginRedirect } from "@/utils/redirects";
|
||||
import type { Wallet } from "../transactions/server-wallets/wallet-table/types";
|
||||
import { ServerWalletsTable } from "../transactions/server-wallets/wallet-table/wallet-table";
|
||||
import { InAppWalletAnalytics } from "./analytics/chart";
|
||||
import { InAppWalletsSummary } from "./analytics/chart/Summary";
|
||||
|
||||
@@ -20,6 +24,7 @@ export default async function Page(props: {
|
||||
to?: string;
|
||||
type?: string;
|
||||
interval?: string;
|
||||
page?: string;
|
||||
}>;
|
||||
}) {
|
||||
const [searchParams, params] = await Promise.all([
|
||||
@@ -40,12 +45,47 @@ export default async function Page(props: {
|
||||
to: searchParams.to,
|
||||
});
|
||||
|
||||
const project = await getProject(params.team_slug, params.project_slug);
|
||||
const [vaultClient, project] = await Promise.all([
|
||||
createVaultClient({
|
||||
baseUrl: NEXT_PUBLIC_THIRDWEB_VAULT_URL,
|
||||
}).catch(() => undefined),
|
||||
getProject(params.team_slug, params.project_slug),
|
||||
]);
|
||||
|
||||
if (!project) {
|
||||
redirect(`/team/${params.team_slug}`);
|
||||
}
|
||||
|
||||
const projectEngineCloudService = project.services.find(
|
||||
(service) => service.name === "engineCloud",
|
||||
);
|
||||
|
||||
const managementAccessToken =
|
||||
projectEngineCloudService?.managementAccessToken;
|
||||
|
||||
// Fetch server wallets with pagination (5 per page)
|
||||
const pageSize = 5;
|
||||
const currentPage = Number.parseInt(searchParams.page ?? "1");
|
||||
|
||||
const eoas =
|
||||
vaultClient && managementAccessToken
|
||||
? await listEoas({
|
||||
client: vaultClient,
|
||||
request: {
|
||||
auth: {
|
||||
accessToken: managementAccessToken,
|
||||
},
|
||||
options: {
|
||||
page: currentPage - 1,
|
||||
// @ts-expect-error - TODO: fix this
|
||||
page_size: pageSize,
|
||||
},
|
||||
},
|
||||
})
|
||||
: { data: { items: [], totalRecords: 0 }, error: null, success: true };
|
||||
|
||||
const serverWallets = eoas.data?.items as Wallet[] | undefined;
|
||||
|
||||
const client = getClientThirdwebClient({
|
||||
jwt: authToken,
|
||||
teamId: project.teamId,
|
||||
@@ -103,12 +143,34 @@ export default async function Page(props: {
|
||||
authToken={authToken}
|
||||
/>
|
||||
|
||||
<InAppWalletUsersPageContent
|
||||
authToken={authToken}
|
||||
client={client}
|
||||
projectClientId={project.publishableKey}
|
||||
teamId={project.teamId}
|
||||
/>
|
||||
{/* Server Wallets Section */}
|
||||
<div className="flex flex-col gap-4">
|
||||
{eoas.error ? null : (
|
||||
<ServerWalletsTable
|
||||
client={client}
|
||||
currentPage={currentPage}
|
||||
managementAccessToken={managementAccessToken ?? undefined}
|
||||
project={project}
|
||||
teamSlug={params.team_slug}
|
||||
totalPages={Math.ceil(eoas.data.totalRecords / pageSize)}
|
||||
totalRecords={eoas.data.totalRecords}
|
||||
wallets={serverWallets ?? []}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* User Wallets Section */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<h2 className="font-semibold text-2xl tracking-tight">
|
||||
User wallets
|
||||
</h2>
|
||||
<InAppWalletUsersPageContent
|
||||
authToken={authToken}
|
||||
client={client}
|
||||
projectClientId={project.publishableKey}
|
||||
teamId={project.teamId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ProjectPage>
|
||||
</ResponsiveSearchParamsProvider>
|
||||
|
||||
+2
@@ -30,11 +30,13 @@ function unclaimedFeesStub(token0Amount: bigint, token1Amount: bigint) {
|
||||
address: "0x1234567890123456789012345678901234567890",
|
||||
amount: token0Amount,
|
||||
symbol: "FOO",
|
||||
decimals: 18,
|
||||
},
|
||||
token1: {
|
||||
address: "0x0987654321098765432109876543210987654321",
|
||||
amount: token1Amount,
|
||||
symbol: "BAR",
|
||||
decimals: 18,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+7
-1
@@ -29,11 +29,13 @@ export function ClaimRewardsPage(props: {
|
||||
address: string;
|
||||
amount: bigint;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
};
|
||||
token1: {
|
||||
address: string;
|
||||
amount: bigint;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
};
|
||||
};
|
||||
chainSlug: string;
|
||||
@@ -98,11 +100,13 @@ export function ClaimRewardsPageUI(props: {
|
||||
address: string;
|
||||
amount: bigint;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
};
|
||||
token1: {
|
||||
address: string;
|
||||
amount: bigint;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
};
|
||||
};
|
||||
recipient: string;
|
||||
@@ -275,6 +279,7 @@ function TokenReward(props: {
|
||||
address: string;
|
||||
amount: bigint;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
};
|
||||
client: ThirdwebClient;
|
||||
chain: Chain;
|
||||
@@ -303,7 +308,8 @@ function TokenReward(props: {
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<p className="font-bold text-sm">
|
||||
{toTokens(props.token.amount, 18)} {props.token.symbol}
|
||||
{toTokens(props.token.amount, props.token.decimals)}{" "}
|
||||
{props.token.symbol}
|
||||
</p>
|
||||
<Link
|
||||
target="_blank"
|
||||
|
||||
+2
@@ -31,6 +31,7 @@ export default async function Page(props: {
|
||||
const chain = info.clientContract.chain;
|
||||
const assetContractServer = info.serverContract;
|
||||
const serverClient = assetContractServer.client;
|
||||
const chainMetadata = info.chainMetadata;
|
||||
|
||||
const { address: entrypointContractAddress } =
|
||||
await getDeployedEntrypointERC20({
|
||||
@@ -56,6 +57,7 @@ export default async function Page(props: {
|
||||
|
||||
// Note: must use server contract/client here
|
||||
const unclaimedFees = await getUnclaimedFees({
|
||||
chainMetadata,
|
||||
positionManager: getContract({
|
||||
address: reward.positionManager,
|
||||
chain,
|
||||
|
||||
+47
-17
@@ -1,11 +1,14 @@
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { getContract, readContract } from "thirdweb";
|
||||
import { getAddress, getContract, readContract, ZERO_ADDRESS } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { symbol } from "thirdweb/extensions/common";
|
||||
import { decimals } from "thirdweb/extensions/erc20";
|
||||
|
||||
const maxUint128 = 2n ** 128n - 1n;
|
||||
|
||||
export async function getUnclaimedFees(params: {
|
||||
positionManager: ThirdwebContract;
|
||||
chainMetadata: ChainMetadata;
|
||||
reward: {
|
||||
tokenId: bigint;
|
||||
recipient: string;
|
||||
@@ -56,33 +59,60 @@ export async function getUnclaimedFees(params: {
|
||||
const token0Address = positionsResult[2];
|
||||
const token1Address = positionsResult[3];
|
||||
|
||||
const [token0Symbol, token1Symbol] = await Promise.all([
|
||||
symbol({
|
||||
contract: getContract({
|
||||
address: token0Address,
|
||||
chain,
|
||||
client,
|
||||
}),
|
||||
}),
|
||||
symbol({
|
||||
contract: getContract({
|
||||
address: token1Address,
|
||||
chain,
|
||||
client,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
const token0Contract = getContract({
|
||||
address: token0Address,
|
||||
chain,
|
||||
client,
|
||||
});
|
||||
|
||||
const token1Contract = getContract({
|
||||
address: token1Address,
|
||||
chain,
|
||||
client,
|
||||
});
|
||||
|
||||
const isToken0Native = getAddress(token0Address) === getAddress(ZERO_ADDRESS);
|
||||
const isToken1Native = getAddress(token1Address) === getAddress(ZERO_ADDRESS);
|
||||
|
||||
const nativeSymbol = params.chainMetadata.nativeCurrency.symbol;
|
||||
const nativeDecimals = params.chainMetadata.nativeCurrency.decimals;
|
||||
|
||||
const [token0Symbol, token1Symbol, token0Decimals, token1Decimals] =
|
||||
await Promise.all([
|
||||
isToken0Native
|
||||
? nativeSymbol
|
||||
: symbol({
|
||||
contract: token0Contract,
|
||||
}),
|
||||
isToken1Native
|
||||
? nativeSymbol
|
||||
: symbol({
|
||||
contract: token1Contract,
|
||||
}),
|
||||
isToken0Native
|
||||
? nativeDecimals
|
||||
: decimals({
|
||||
contract: token0Contract,
|
||||
}),
|
||||
isToken1Native
|
||||
? nativeDecimals
|
||||
: decimals({
|
||||
contract: token1Contract,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
token0: {
|
||||
address: token0Address,
|
||||
amount: collectResult[0],
|
||||
symbol: token0Symbol,
|
||||
decimals: token0Decimals,
|
||||
},
|
||||
token1: {
|
||||
address: token1Address,
|
||||
amount: collectResult[1],
|
||||
symbol: token1Symbol,
|
||||
decimals: token1Decimals,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export function UniversalBridgeEmbed({
|
||||
buyAmount={amount}
|
||||
tokenAddress={token?.address}
|
||||
pageType="bridge"
|
||||
isTestnet={chain.testnet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { bridgeAppThirdwebClient } from "./constants";
|
||||
|
||||
const title = "thirdweb Bridge: Buy, Bridge & Swap Crypto on 85+ Chains";
|
||||
const description =
|
||||
"Bridge, swap, and on-ramp across 9M+ token routes on 85+ chains (Ethereum, Base, Optimism, Arbitrum, BNB & more). Best-price routing and near-instant fast finality";
|
||||
"Bridge and swap 3,200+ tokens across 85+ chains (Ethereum, Base, Optimism, Arbitrum, BNB & more). Best-price routing with near-instant finality";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description,
|
||||
@@ -105,8 +105,7 @@ export default async function BridgePage({
|
||||
href="https://portal.thirdweb.com/bridge"
|
||||
linkType="integrate-bridge"
|
||||
>
|
||||
Integrate Bridge in your apps in minutes, and start generating
|
||||
revenue
|
||||
Accept Payments in Any Token Right Inside Your App
|
||||
</PillLink>
|
||||
|
||||
<PillLink
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ShieldCheckIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { Bridge, defineChain, toTokens } from "thirdweb";
|
||||
import { getChainMetadata } from "thirdweb/chains";
|
||||
import { shortenAddress } from "thirdweb/utils";
|
||||
@@ -80,131 +81,137 @@ export default async function PayPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex z-10 flex-col lg:flex-row h-full w-full">
|
||||
<header className="min-w-full lg:min-w-[500px] border-b lg:border-r lg:h-full bg-card flex flex-col gap-4 items-start p-4 lg:p-8">
|
||||
<div>
|
||||
<div className="flex flex-row items-center justify-start gap-4">
|
||||
{projectMetadata.image && (
|
||||
<img
|
||||
src={
|
||||
resolveSchemeWithErrorHandler({
|
||||
uri: projectMetadata.image,
|
||||
client: payAppThirdwebClient,
|
||||
}) || ""
|
||||
}
|
||||
alt={projectMetadata.name}
|
||||
width={25}
|
||||
height={25}
|
||||
className="rounded-full overflow-hidden"
|
||||
/>
|
||||
<ThemeProvider
|
||||
forcedTheme={theme === "light" ? "light" : "dark"}
|
||||
attribute="class"
|
||||
disableTransitionOnChange
|
||||
enableSystem={false}
|
||||
>
|
||||
<div className="flex z-10 flex-col lg:flex-row h-full w-full">
|
||||
<header className="min-w-full lg:min-w-[500px] border-b lg:border-r lg:h-full bg-card flex flex-col gap-4 items-start p-4 lg:p-8">
|
||||
<div>
|
||||
<div className="flex flex-row items-center justify-start gap-4">
|
||||
{projectMetadata.image && (
|
||||
<img
|
||||
src={
|
||||
resolveSchemeWithErrorHandler({
|
||||
uri: projectMetadata.image,
|
||||
client: payAppThirdwebClient,
|
||||
}) || ""
|
||||
}
|
||||
alt={projectMetadata.name}
|
||||
width={25}
|
||||
height={25}
|
||||
className="rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
<h2 className="text-xl font-bold">{projectMetadata.name}</h2>
|
||||
</div>
|
||||
{projectMetadata.description && (
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{projectMetadata.description}
|
||||
</p>
|
||||
)}
|
||||
<h2 className="text-xl font-bold">{projectMetadata.name}</h2>
|
||||
</div>
|
||||
{projectMetadata.description && (
|
||||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{projectMetadata.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="hidden lg:block my-4 w-full">
|
||||
{paymentLink.amount && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Details</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{token.iconUri && (
|
||||
<img
|
||||
src={resolveSchemeWithErrorHandler({
|
||||
uri: token.iconUri,
|
||||
client: getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
})}
|
||||
alt={token.name}
|
||||
width={25}
|
||||
height={25}
|
||||
className="size-5 rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
{toTokens(BigInt(paymentLink.amount), token.decimals)}{" "}
|
||||
{token.symbol}
|
||||
</div>
|
||||
{token.prices.USD && (
|
||||
<span>
|
||||
$
|
||||
{(
|
||||
Number(token.prices.USD) *
|
||||
Number(
|
||||
toTokens(BigInt(paymentLink.amount), token.decimals),
|
||||
)
|
||||
).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{chain && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Network</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{chain.icon?.url && (
|
||||
<img
|
||||
src={resolveSchemeWithErrorHandler({
|
||||
uri: chain.icon.url,
|
||||
client: getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
})}
|
||||
alt={chain.name}
|
||||
width={chain.icon.width}
|
||||
height={chain.icon.height}
|
||||
className="size-5 rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
{chain.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{recipientEnsOrAddress.ensName ||
|
||||
(recipientEnsOrAddress.address && (
|
||||
<div className="hidden lg:block my-4 w-full">
|
||||
{paymentLink.amount && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Seller</span>
|
||||
<span className="text-muted-foreground text-xs">Details</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
{recipientEnsOrAddress.ensName ??
|
||||
shortenAddress(recipientEnsOrAddress.address)}
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{token.iconUri && (
|
||||
<img
|
||||
src={resolveSchemeWithErrorHandler({
|
||||
uri: token.iconUri,
|
||||
client: getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
})}
|
||||
alt={token.name}
|
||||
width={25}
|
||||
height={25}
|
||||
className="size-5 rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
{toTokens(BigInt(paymentLink.amount), token.decimals)}{" "}
|
||||
{token.symbol}
|
||||
</div>
|
||||
{token.prices.USD && (
|
||||
<span>
|
||||
$
|
||||
{(
|
||||
Number(token.prices.USD) *
|
||||
Number(
|
||||
toTokens(BigInt(paymentLink.amount), token.decimals),
|
||||
)
|
||||
).toFixed(2)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{chain && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Network</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
{chain.icon?.url && (
|
||||
<img
|
||||
src={resolveSchemeWithErrorHandler({
|
||||
uri: chain.icon.url,
|
||||
client: getConfiguredThirdwebClient({
|
||||
secretKey: DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
teamId: undefined,
|
||||
}),
|
||||
})}
|
||||
alt={chain.name}
|
||||
width={chain.icon.width}
|
||||
height={chain.icon.height}
|
||||
className="size-5 rounded-full overflow-hidden"
|
||||
/>
|
||||
)}
|
||||
{chain.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{recipientEnsOrAddress.ensName ||
|
||||
(recipientEnsOrAddress.address && (
|
||||
<div className="flex flex-col gap-1 w-full my-4">
|
||||
<span className="text-muted-foreground text-xs">Seller</span>
|
||||
<div className="font-medium flex-row flex justify-between items-center w-full">
|
||||
{recipientEnsOrAddress.ensName ??
|
||||
shortenAddress(recipientEnsOrAddress.address)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-auto hidden lg:block">
|
||||
<Badge className="flex items-center gap-1.5 bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800">
|
||||
<ShieldCheckIcon className="size-3" />
|
||||
Secured by thirdweb
|
||||
</Badge>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex justify-center p-12 w-full items-center">
|
||||
<PayPageWidget
|
||||
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
|
||||
chainId={Number(paymentLink.destinationToken.chainId)}
|
||||
clientId={undefined} // Payment links don't need to use the same client ID to be executed
|
||||
image={paymentLink.imageUrl}
|
||||
name={paymentLink.title}
|
||||
paymentLinkId={id}
|
||||
purchaseData={paymentLink.purchaseData}
|
||||
recipientAddress={paymentLink.receiver}
|
||||
redirectUri={redirectUri}
|
||||
theme={theme}
|
||||
token={token}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
<div className="mt-auto hidden lg:block">
|
||||
<Badge className="flex items-center gap-1.5 bg-purple-100 text-purple-800 border-purple-200 dark:bg-purple-950 dark:text-purple-300 dark:border-purple-800">
|
||||
<ShieldCheckIcon className="size-3" />
|
||||
Secured by thirdweb
|
||||
</Badge>
|
||||
</div>
|
||||
</header>
|
||||
<main className="flex justify-center p-12 w-full items-center">
|
||||
<PayPageWidget
|
||||
amount={paymentLink.amount ? BigInt(paymentLink.amount) : undefined}
|
||||
chainId={Number(paymentLink.destinationToken.chainId)}
|
||||
clientId={undefined} // Payment links don't need to use the same client ID to be executed
|
||||
image={paymentLink.imageUrl}
|
||||
name={paymentLink.title}
|
||||
paymentLinkId={id}
|
||||
purchaseData={paymentLink.purchaseData}
|
||||
recipientAddress={paymentLink.receiver}
|
||||
redirectUri={redirectUri}
|
||||
token={token}
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
import { payAppThirdwebClient } from "app/pay/constants";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect } from "react";
|
||||
import { createThirdwebClient, NATIVE_TOKEN_ADDRESS, toTokens } from "thirdweb";
|
||||
import { AutoConnect, CheckoutWidget } from "thirdweb/react";
|
||||
import { checksumAddress } from "thirdweb/utils";
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
reportPaymentLinkBuySuccessful,
|
||||
} from "@/analytics/report";
|
||||
import { useV5DashboardChain } from "@/hooks/chains/v5-adapter";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
|
||||
export function PayPageWidget({
|
||||
chainId,
|
||||
@@ -21,7 +21,6 @@ export function PayPageWidget({
|
||||
name,
|
||||
image,
|
||||
redirectUri,
|
||||
theme,
|
||||
purchaseData,
|
||||
clientId,
|
||||
}: {
|
||||
@@ -34,17 +33,10 @@ export function PayPageWidget({
|
||||
image?: string;
|
||||
redirectUri?: string;
|
||||
clientId: string | undefined;
|
||||
theme?: "light" | "dark";
|
||||
purchaseData: Record<string, unknown> | undefined;
|
||||
}) {
|
||||
const { theme: browserTheme, setTheme } = useTheme();
|
||||
const { theme } = useTheme();
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
if (theme) {
|
||||
setTheme(theme);
|
||||
}
|
||||
}, [theme, setTheme]);
|
||||
const chain = useV5DashboardChain(chainId);
|
||||
|
||||
return (
|
||||
@@ -55,6 +47,7 @@ export function PayPageWidget({
|
||||
}
|
||||
/>
|
||||
<CheckoutWidget
|
||||
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
|
||||
connectOptions={{
|
||||
wallets: [
|
||||
createWallet("io.metamask"),
|
||||
@@ -87,7 +80,6 @@ export function PayPageWidget({
|
||||
paymentLinkId={paymentLinkId}
|
||||
purchaseData={purchaseData}
|
||||
seller={checksumAddress(recipientAddress)}
|
||||
theme={theme ?? (browserTheme === "light" ? "light" : "dark")}
|
||||
tokenAddress={
|
||||
token.address === NATIVE_TOKEN_ADDRESS
|
||||
? undefined
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "@workspace/ui/global.css";
|
||||
import { Inter } from "next/font/google";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PayProviders } from "./components/client/Providers.client";
|
||||
|
||||
@@ -24,16 +23,9 @@ export default async function PayLayout({
|
||||
)}
|
||||
>
|
||||
<PayProviders>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="dark"
|
||||
disableTransitionOnChange
|
||||
enableSystem={false}
|
||||
>
|
||||
<div className="relative mx-auto flex h-full w-full items-center justify-center overflow-x-hidden overflow-y-scroll">
|
||||
{children}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
<div className="relative mx-auto flex h-full w-full items-center justify-center overflow-x-hidden overflow-y-scroll">
|
||||
{children}
|
||||
</div>
|
||||
</PayProviders>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Metadata } from "next";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { createThirdwebClient, defineChain, getContract } from "thirdweb";
|
||||
import { getCurrencyMetadata } from "thirdweb/extensions/erc20";
|
||||
import { checksumAddress } from "thirdweb/utils";
|
||||
@@ -33,7 +34,16 @@ export default async function PayPage({
|
||||
!params.tokenAddress &&
|
||||
!params.amount
|
||||
) {
|
||||
return <PaymentLinkForm />;
|
||||
return (
|
||||
<ThemeProvider
|
||||
forcedTheme={params.theme === "light" ? "light" : "dark"}
|
||||
attribute="class"
|
||||
disableTransitionOnChange
|
||||
enableSystem={false}
|
||||
>
|
||||
<PaymentLinkForm />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Validate query parameters
|
||||
@@ -84,17 +94,23 @@ export default async function PayPage({
|
||||
};
|
||||
|
||||
return (
|
||||
<PayPageWidget
|
||||
amount={BigInt(params.amount)}
|
||||
chainId={Number(params.chainId)}
|
||||
clientId={client.clientId}
|
||||
image={params.image}
|
||||
name={params.name}
|
||||
purchaseData={undefined}
|
||||
recipientAddress={params.recipientAddress}
|
||||
redirectUri={params.redirectUri}
|
||||
theme={params.theme}
|
||||
token={token}
|
||||
/>
|
||||
<ThemeProvider
|
||||
forcedTheme={params.theme === "light" ? "light" : "dark"}
|
||||
attribute="class"
|
||||
disableTransitionOnChange
|
||||
enableSystem={false}
|
||||
>
|
||||
<PayPageWidget
|
||||
amount={BigInt(params.amount)}
|
||||
chainId={Number(params.chainId)}
|
||||
clientId={client.clientId}
|
||||
image={params.image}
|
||||
name={params.name}
|
||||
purchaseData={undefined}
|
||||
recipientAddress={params.recipientAddress}
|
||||
redirectUri={params.redirectUri}
|
||||
token={token}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,20 @@ import { defineDashboardChain } from "@/lib/defineDashboardChain";
|
||||
import { isLoginRequired } from "@/utils/auth";
|
||||
import { LAST_VISITED_TEAM_PAGE_PATH } from "./app/(app)/team/components/last-visited-page/consts";
|
||||
|
||||
type CookiesToSet = Record<
|
||||
string,
|
||||
| [string]
|
||||
| [
|
||||
string,
|
||||
{
|
||||
httpOnly: boolean;
|
||||
sameSite?: "lax" | "strict" | "none";
|
||||
secure: boolean;
|
||||
maxAge: number;
|
||||
},
|
||||
]
|
||||
>;
|
||||
|
||||
// ignore assets, api - only intercept page routes
|
||||
export const config = {
|
||||
matcher: [
|
||||
@@ -24,12 +38,12 @@ export const config = {
|
||||
};
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
const { pathname, searchParams } = request.nextUrl;
|
||||
|
||||
// nebula subdomain handling
|
||||
const paths = pathname.slice(1).split("/");
|
||||
|
||||
let cookiesToSet: Record<string, string> | undefined;
|
||||
let cookiesToSet: CookiesToSet = {};
|
||||
|
||||
const activeAccount = request.cookies.get(COOKIE_ACTIVE_ACCOUNT)?.value;
|
||||
const authCookie = activeAccount
|
||||
@@ -56,12 +70,27 @@ export async function middleware(request: NextRequest) {
|
||||
cookiesToSet = {};
|
||||
}
|
||||
|
||||
cookiesToSet[key] = value;
|
||||
cookiesToSet[key] = [value];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle gclid (if it exists in search params)
|
||||
const gclid = searchParams.get("gclid");
|
||||
if (gclid) {
|
||||
cookiesToSet.gclid = [
|
||||
gclid,
|
||||
{
|
||||
// TODO: define conversion window, for now 7d should do fine
|
||||
maxAge: 7 * 24 * 60 * 60,
|
||||
httpOnly: false,
|
||||
sameSite: "lax",
|
||||
secure: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// logged in paths
|
||||
if (isLoginRequired(pathname)) {
|
||||
// check if the user is logged in (has a valid auth cookie)
|
||||
@@ -146,7 +175,7 @@ export async function middleware(request: NextRequest) {
|
||||
if (cookiesToSet) {
|
||||
const defaultResponse = NextResponse.next();
|
||||
for (const entry of Object.entries(cookiesToSet)) {
|
||||
defaultResponse.cookies.set(entry[0], entry[1]);
|
||||
defaultResponse.cookies.set(entry[0], entry[1][0], entry[1][1]);
|
||||
}
|
||||
|
||||
return defaultResponse;
|
||||
@@ -162,7 +191,7 @@ function isPossibleAddressOrENSName(address: string) {
|
||||
function rewrite(
|
||||
request: NextRequest,
|
||||
relativePath: string,
|
||||
cookiesToSet: Record<string, string> | undefined,
|
||||
cookiesToSet: CookiesToSet,
|
||||
) {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = relativePath;
|
||||
@@ -170,7 +199,7 @@ function rewrite(
|
||||
|
||||
if (cookiesToSet) {
|
||||
for (const entry of Object.entries(cookiesToSet)) {
|
||||
res.cookies.set(entry[0], entry[1]);
|
||||
res.cookies.set(entry[0], entry[1][0], entry[1][1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +213,7 @@ function redirect(
|
||||
| {
|
||||
searchParams?: string;
|
||||
permanent?: boolean;
|
||||
cookiesToSet?: Record<string, string> | undefined;
|
||||
cookiesToSet?: CookiesToSet;
|
||||
}
|
||||
| undefined,
|
||||
) {
|
||||
@@ -196,7 +225,7 @@ function redirect(
|
||||
|
||||
if (options?.cookiesToSet) {
|
||||
for (const entry of Object.entries(options.cookiesToSet)) {
|
||||
res.cookies.set(entry[0], entry[1]);
|
||||
res.cookies.set(entry[0], entry[1][0], entry[1][1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,15 @@ const baseNextConfig: NextConfig = {
|
||||
},
|
||||
productionBrowserSourceMaps: false,
|
||||
reactStrictMode: true,
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/:path*",
|
||||
destination: "https://thirdweb.com/ai",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -76,7 +76,7 @@ export function ChatContainer() {
|
||||
return (
|
||||
<Reasoning
|
||||
key={`${message.id}-reasoning-${i}`}
|
||||
className="w-full"
|
||||
className="w-full max-w-md"
|
||||
isStreaming={status === "streaming"}
|
||||
>
|
||||
<ReasoningTrigger />
|
||||
|
||||
@@ -1,10 +1,90 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { createThirdwebClient, defineChain } from "thirdweb";
|
||||
import { toUnits } from "thirdweb/utils";
|
||||
import { facilitator, settlePayment } from "thirdweb/x402";
|
||||
import { token } from "../../payments/x402/components/constants";
|
||||
|
||||
// Allow streaming responses up to 5 minutes
|
||||
export const maxDuration = 300;
|
||||
|
||||
export async function GET(_req: Request) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: "Payment successful. You have accessed the protected route.",
|
||||
export async function GET(request: NextRequest) {
|
||||
const client = createThirdwebClient({
|
||||
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
|
||||
});
|
||||
|
||||
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
|
||||
// const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string;
|
||||
const ENGINE_VAULT_ACCESS_TOKEN = process.env
|
||||
.ENGINE_VAULT_ACCESS_TOKEN as string;
|
||||
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
|
||||
|
||||
const twFacilitator = facilitator({
|
||||
baseUrl: `${API_URL}/v1/payments/x402`,
|
||||
client,
|
||||
serverWalletAddress: BACKEND_WALLET_ADDRESS,
|
||||
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
const paymentData = request.headers.get("X-PAYMENT");
|
||||
const queryParams = request.nextUrl.searchParams;
|
||||
|
||||
const chainId = queryParams.get("chainId");
|
||||
|
||||
if (!chainId) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing required parameters" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const amount = queryParams.get("amount") || "0.01";
|
||||
const tokenAddress = queryParams.get("tokenAddress") || token.address;
|
||||
const decimals = queryParams.get("decimals") || token.decimals.toString();
|
||||
const waitUntil =
|
||||
(queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") ||
|
||||
"simulated";
|
||||
|
||||
const result = await settlePayment({
|
||||
resourceUrl: "https://playground-web.thirdweb.com/api/paywall",
|
||||
method: "GET",
|
||||
paymentData,
|
||||
network: defineChain(Number(chainId)),
|
||||
price: {
|
||||
amount: toUnits(amount, parseInt(decimals)).toString(),
|
||||
asset: {
|
||||
address: tokenAddress as `0x${string}`,
|
||||
decimals: decimals ? parseInt(decimals) : token.decimals,
|
||||
},
|
||||
},
|
||||
routeConfig: {
|
||||
description: "Access to paid content",
|
||||
},
|
||||
waitUntil,
|
||||
facilitator: twFacilitator,
|
||||
});
|
||||
|
||||
if (result.status === 200) {
|
||||
// payment successful, execute the request
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: "Payment successful. You have accessed the protected route.",
|
||||
payment: {
|
||||
amount,
|
||||
tokenAddress,
|
||||
},
|
||||
receipt: result.paymentReceipt,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: result.responseHeaders,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// otherwise, request payment
|
||||
return NextResponse.json(result.responseBody, {
|
||||
status: result.status,
|
||||
headers: result.responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
|
||||
sellerAddress: "0x0000000000000000000000000000000000000000",
|
||||
title: "",
|
||||
transactionData: "",
|
||||
receiverAddress: undefined,
|
||||
currency: "USD",
|
||||
showThirdwebBranding: true,
|
||||
},
|
||||
|
||||
@@ -118,8 +118,12 @@ tokenId: 2n,
|
||||
tokenAddress: options.payOptions.buyTokenAddress
|
||||
? quotes(options.payOptions.buyTokenAddress)
|
||||
: undefined,
|
||||
seller: options.payOptions.sellerAddress
|
||||
? quotes(options.payOptions.sellerAddress)
|
||||
seller:
|
||||
widget === "checkout"
|
||||
? quotes(options.payOptions.sellerAddress)
|
||||
: undefined,
|
||||
receiverAddress: options.payOptions.receiverAddress
|
||||
? quotes(options.payOptions.receiverAddress)
|
||||
: undefined,
|
||||
buttonLabel: options.payOptions.buttonLabel
|
||||
? quotes(options.payOptions.buttonLabel)
|
||||
|
||||
@@ -140,6 +140,7 @@ export function LeftSection(props: {
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label>Token</Label>
|
||||
<TokenSelector
|
||||
disableAddress
|
||||
chainId={selectedChain}
|
||||
client={THIRDWEB_CLIENT}
|
||||
enabled={true}
|
||||
@@ -154,10 +155,10 @@ export function LeftSection(props: {
|
||||
)}
|
||||
|
||||
{/* Mode-specific form fields */}
|
||||
<div className="my-2">
|
||||
<div className="">
|
||||
{/* Buy Mode - Amount and Payment Methods */}
|
||||
{props.widget === "buy" && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={buyTokenAmountId}>Amount</Label>
|
||||
<Input
|
||||
@@ -177,6 +178,30 @@ export function LeftSection(props: {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{props.widget === "buy" && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="receiver-address">Receiver Address</Label>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Receive the tokens in a different wallet address
|
||||
</p>
|
||||
<Input
|
||||
className="bg-card"
|
||||
id="receiver-address"
|
||||
onChange={(e) => {
|
||||
setOptions((v) => ({
|
||||
...v,
|
||||
payOptions: {
|
||||
...v.payOptions,
|
||||
receiverAddress: e.target.value as Address,
|
||||
},
|
||||
}));
|
||||
}}
|
||||
placeholder="0x..."
|
||||
value={payOptions.receiverAddress || ""}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Payment Methods */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<Label>Payment Methods</Label>
|
||||
@@ -206,6 +231,7 @@ export function LeftSection(props: {
|
||||
/>
|
||||
<Label htmlFor={cryptoPaymentId}>Crypto</Label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
checked={payOptions.paymentMethods.includes("card")}
|
||||
@@ -257,7 +283,6 @@ export function LeftSection(props: {
|
||||
value={payOptions.sellerAddress || ""}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={paymentAmountId}>Price</Label>
|
||||
<Input
|
||||
|
||||
@@ -67,6 +67,12 @@ export function RightSection(props: {
|
||||
tokenAddress={props.options.payOptions.buyTokenAddress}
|
||||
currency={props.options.payOptions.currency}
|
||||
showThirdwebBranding={props.options.payOptions.showThirdwebBranding}
|
||||
receiverAddress={props.options.payOptions.receiverAddress}
|
||||
key={JSON.stringify({
|
||||
amount: props.options.payOptions.buyTokenAmount,
|
||||
chain: props.options.payOptions.buyTokenChain,
|
||||
tokenAddress: props.options.payOptions.buyTokenAddress,
|
||||
})}
|
||||
{...(props.options.payOptions.buttonLabel && {
|
||||
buttonLabel: props.options.payOptions.buttonLabel,
|
||||
})}
|
||||
@@ -89,7 +95,6 @@ export function RightSection(props: {
|
||||
}
|
||||
name={props.options.payOptions.title || "Your Product Name"}
|
||||
paymentMethods={props.options.payOptions.paymentMethods}
|
||||
presetOptions={[1, 2, 3]}
|
||||
seller={props.options.payOptions.sellerAddress}
|
||||
theme={themeObj}
|
||||
tokenAddress={props.options.payOptions.buyTokenAddress}
|
||||
|
||||
@@ -48,6 +48,7 @@ export type BridgeComponentsPlaygroundOptions = {
|
||||
|
||||
// direct_payment mode options
|
||||
sellerAddress: Address;
|
||||
receiverAddress: Address | undefined;
|
||||
|
||||
// transaction mode options
|
||||
transactionData?: string; // Simplified for demo; could be more complex in real implementation
|
||||
|
||||
@@ -17,6 +17,7 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
|
||||
paymentMethods: ["crypto", "card"],
|
||||
sellerAddress: "0x0000000000000000000000000000000000000000",
|
||||
title: "",
|
||||
receiverAddress: undefined,
|
||||
transactionData: "",
|
||||
currency: "USD",
|
||||
showThirdwebBranding: true,
|
||||
|
||||
@@ -20,6 +20,7 @@ const defaultOptions: BridgeComponentsPlaygroundOptions = {
|
||||
transactionData: "",
|
||||
currency: "USD",
|
||||
showThirdwebBranding: true,
|
||||
receiverAddress: undefined,
|
||||
},
|
||||
theme: {
|
||||
darkColorOverrides: {},
|
||||
|
||||
@@ -44,7 +44,6 @@ export function X402LeftSection(props: {
|
||||
const chainId = useId();
|
||||
const tokenId = useId();
|
||||
const amountId = useId();
|
||||
const payToId = useId();
|
||||
const waitUntilId = useId();
|
||||
|
||||
const handleChainChange = (chainId: number) => {
|
||||
@@ -82,13 +81,6 @@ export function X402LeftSection(props: {
|
||||
}));
|
||||
};
|
||||
|
||||
const handlePayToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setOptions((v) => ({
|
||||
...v,
|
||||
payTo: e.target.value as `0x${string}`,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleWaitUntilChange = (
|
||||
value: "simulated" | "submitted" | "confirmed",
|
||||
) => {
|
||||
@@ -148,22 +140,6 @@ export function X402LeftSection(props: {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Pay To input */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={payToId}>Pay To Address</Label>
|
||||
<Input
|
||||
id={payToId}
|
||||
type="text"
|
||||
placeholder="0x..."
|
||||
value={options.payTo}
|
||||
onChange={handlePayToChange}
|
||||
className="bg-card"
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
The wallet address that will receive the payment
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Wait Until selection */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor={waitUntilId}>Wait Until</Label>
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { createThirdwebClient, defineChain } from "thirdweb";
|
||||
import { toUnits } from "thirdweb/utils";
|
||||
import { facilitator, settlePayment } from "thirdweb/x402";
|
||||
import { token } from "./app/payments/x402/components/constants";
|
||||
|
||||
const client = createThirdwebClient({
|
||||
secretKey: process.env.THIRDWEB_SECRET_KEY as string,
|
||||
});
|
||||
|
||||
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
|
||||
// const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_SMART_WALLET as string;
|
||||
const ENGINE_VAULT_ACCESS_TOKEN = process.env
|
||||
.ENGINE_VAULT_ACCESS_TOKEN as string;
|
||||
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
|
||||
function createFacilitator(
|
||||
waitUntil: "simulated" | "submitted" | "confirmed" = "simulated",
|
||||
) {
|
||||
return facilitator({
|
||||
baseUrl: `${API_URL}/v1/payments/x402`,
|
||||
client,
|
||||
serverWalletAddress: BACKEND_WALLET_ADDRESS,
|
||||
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
|
||||
waitUtil: waitUntil,
|
||||
});
|
||||
}
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const pathname = request.nextUrl.pathname;
|
||||
const method = request.method.toUpperCase();
|
||||
const resourceUrl = `${request.nextUrl.protocol}//${request.nextUrl.host}${pathname}`;
|
||||
const paymentData = request.headers.get("X-PAYMENT");
|
||||
const queryParams = request.nextUrl.searchParams;
|
||||
|
||||
const chainId = queryParams.get("chainId");
|
||||
const payTo = queryParams.get("payTo");
|
||||
|
||||
if (!chainId || !payTo) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing required parameters" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const amount = queryParams.get("amount") || "0.01";
|
||||
const tokenAddress = queryParams.get("tokenAddress") || token.address;
|
||||
const decimals = queryParams.get("decimals") || token.decimals.toString();
|
||||
const waitUntil =
|
||||
(queryParams.get("waitUntil") as "simulated" | "submitted" | "confirmed") ||
|
||||
"simulated";
|
||||
|
||||
const result = await settlePayment({
|
||||
resourceUrl,
|
||||
method,
|
||||
paymentData,
|
||||
payTo: payTo as `0x${string}`,
|
||||
network: defineChain(Number(chainId)),
|
||||
price: {
|
||||
amount: toUnits(amount, parseInt(decimals)).toString(),
|
||||
asset: {
|
||||
address: tokenAddress as `0x${string}`,
|
||||
decimals: decimals ? parseInt(decimals) : token.decimals,
|
||||
},
|
||||
},
|
||||
routeConfig: {
|
||||
description: "Access to paid content",
|
||||
},
|
||||
facilitator: createFacilitator(waitUntil),
|
||||
});
|
||||
|
||||
if (result.status === 200) {
|
||||
// payment successful, execute the request
|
||||
const response = NextResponse.next();
|
||||
for (const [key, value] of Object.entries(result.responseHeaders)) {
|
||||
response.headers.set(key, value);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// otherwise, request payment
|
||||
return NextResponse.json(result.responseBody, {
|
||||
status: result.status,
|
||||
headers: result.responseHeaders,
|
||||
});
|
||||
}
|
||||
|
||||
// Configure which paths the middleware should run on
|
||||
export const config = {
|
||||
matcher: ["/api/paywall"],
|
||||
};
|
||||
@@ -17,6 +17,76 @@ https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>
|
||||
|
||||
Make sure to keep your secret key safe and never share it with anyone.
|
||||
|
||||
By default, all tools are available. You can filter the tools available by passing a comma-separated list of tools as a query parameter.
|
||||
|
||||
```http
|
||||
https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>&tools=fetchWithPayment,getWalletBalance
|
||||
```
|
||||
|
||||
You can find all available tools at the bottom of this page.
|
||||
|
||||
## Usage with agents
|
||||
|
||||
Use your favorite agent framework to plug in the MCP server as a collection of tools for your agent. Refer to your agent framework's documentation for more information.
|
||||
|
||||
#### Example usage with langchain:
|
||||
|
||||
<Tabs defaultValue="typescript">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
|
||||
<TabsTrigger value="python">Python</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="typescript">
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
||||
|
||||
const mcpServers = {
|
||||
thirdweb: {
|
||||
url: `https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>`
|
||||
}
|
||||
};
|
||||
|
||||
// Connect to MCP servers and get tools
|
||||
const client = new MultiServerMCPClient(mcpServers);
|
||||
const tools = await client.getTools();
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: tools,
|
||||
});
|
||||
await agent.invoke({messages: [{ role: "user", content: "create a server wallet called 'my-wallet'" }]})
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="python">
|
||||
|
||||
```python
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
client = MultiServerMCPClient(
|
||||
{
|
||||
"thirdweb": {
|
||||
"url": "https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>",
|
||||
}
|
||||
}
|
||||
)
|
||||
tools = await client.get_tools()
|
||||
agent = create_react_agent("openai:gpt-4.1", tools)
|
||||
response = await agent.invoke({"messages": "create a server wallet called 'my-wallet'"})
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Once installed, you can use the entire thirdweb API with natural language.
|
||||
|
||||
## Usage with LLM clients
|
||||
|
||||
You can also use the MCP server on your own LLM client, like cursor, claude code and more. Refer to your LLM client's documentation for more information.
|
||||
@@ -112,31 +182,6 @@ claude mcp add --transport http "thirdweb-api" "https://api.thirdweb.com/mcp?sec
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Usage with agents
|
||||
|
||||
Use your favorite agent framework to plug in the MCP server as a collection of tools for your agent. Refer to your agent framework's documentation for more information.
|
||||
|
||||
#### Example usage with langchain:
|
||||
|
||||
```python
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
client = MultiServerMCPClient(
|
||||
{
|
||||
"thirdweb-api": {
|
||||
"transport": "streamable_http",
|
||||
"url": "https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>",
|
||||
}
|
||||
}
|
||||
)
|
||||
tools = await client.get_tools()
|
||||
agent = create_react_agent("openai:gpt-4.1", tools)
|
||||
response = await agent.ainvoke({"messages": "create a server wallet called 'my-wallet'"})
|
||||
```
|
||||
|
||||
Once installed, you can use the entire thirdweb API with natural language.
|
||||
|
||||
## Example prompts
|
||||
|
||||
#### Managing server wallets
|
||||
@@ -164,3 +209,46 @@ List my contracts
|
||||
```
|
||||
approve 100 USDC from treasury wallet to executor wallet
|
||||
```
|
||||
|
||||
## Available tools
|
||||
|
||||
Currently, the following tools are available:
|
||||
|
||||
- `initiateAuthentication`
|
||||
- `completeAuthentication`
|
||||
- `linkAuthentication`
|
||||
- `unlinkAuthentication`
|
||||
- `getMyWallet`
|
||||
- `listUserWallets`
|
||||
- `createUserWallet`
|
||||
- `listServerWallets`
|
||||
- `createServerWallet`
|
||||
- `getWalletBalance`
|
||||
- `getWalletTransactions`
|
||||
- `getWalletTokens`
|
||||
- `getWalletNFTs`
|
||||
- `signMessage`
|
||||
- `signTypedData`
|
||||
- `sendTokens`
|
||||
- `listContracts`
|
||||
- `deployContract`
|
||||
- `readContract`
|
||||
- `writeContract`
|
||||
- `getContractTransactions`
|
||||
- `getContractEvents`
|
||||
- `getContractMetadata`
|
||||
- `getContractSignatures`
|
||||
- `getTransactionById`
|
||||
- `listTransactions`
|
||||
- `sendTransactions`
|
||||
- `createPayment`
|
||||
- `paymentsPurchase`
|
||||
- `getPaymentHistory`
|
||||
- `fetchWithPayment`
|
||||
- `createToken`
|
||||
- `listTokens`
|
||||
- `getTokenOwners`
|
||||
- `getBridgeChains`
|
||||
- `convertFiatToCrypto`
|
||||
- `bridgeSwap`
|
||||
- `chat`
|
||||
@@ -53,10 +53,6 @@ The BridgeWidget Script makes it easy to embed cross-chain swaps and fiat onramp
|
||||
BridgeWidget.render(container, {
|
||||
clientId: "your-thirdweb-client-id",
|
||||
theme: "dark",
|
||||
buy: {
|
||||
chainId: 8453,
|
||||
amount: "0.1",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -126,9 +122,9 @@ type Options = {
|
||||
};
|
||||
};
|
||||
};
|
||||
buy: {
|
||||
amount: string;
|
||||
chainId: number;
|
||||
buy?: {
|
||||
amount?: string;
|
||||
chainId?: number;
|
||||
tokenAddress?: string;
|
||||
buttonLabel?: string;
|
||||
onCancel?: (quote: BuyOrOnrampPrepareResult | undefined) => void;
|
||||
@@ -310,8 +306,8 @@ The options to configure the "Buy" tab UI.
|
||||
|
||||
```ts
|
||||
type Buy = {
|
||||
amount: string; // Required
|
||||
chainId: number; // Required
|
||||
amount?: string;
|
||||
chainId?: number;
|
||||
tokenAddress?: string;
|
||||
buttonLabel?: string;
|
||||
onCancel?: (quote: BuyOrOnrampPrepareResult | undefined) => void;
|
||||
@@ -327,21 +323,21 @@ type Buy = {
|
||||
```
|
||||
|
||||
|
||||
<Details summary="amount" tags={["Required"]}>
|
||||
<Details summary="amount">
|
||||
|
||||
Set the default token amount shown in the "Buy" tab.
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summary="chainId" tags={["Required"]}>
|
||||
<Details summary="chainId">
|
||||
|
||||
Specify which chain should be selected in the "Buy" tab.
|
||||
Specify the default chain to be selected in the "Buy" tab.
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summary="tokenAddress">
|
||||
|
||||
Specify which token should be selected in the "Buy" tab. If not specified, the native token of the selected chain will be selected.
|
||||
Specify the default token to be selected in the "Buy" tab. If not specified, the native token of the selected chain will be selected.
|
||||
|
||||
</Details>
|
||||
|
||||
|
||||
@@ -80,9 +80,13 @@ export const sidebar: SideBar = {
|
||||
href: `${paymentsSlug}/x402/server`,
|
||||
name: "Server Side",
|
||||
},
|
||||
{
|
||||
href: `${paymentsSlug}/x402/agents`,
|
||||
name: "Agents",
|
||||
},
|
||||
{
|
||||
href: `${paymentsSlug}/x402/facilitator`,
|
||||
name: "Facilitator API",
|
||||
name: "Facilitator",
|
||||
},
|
||||
],
|
||||
name: "x402",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { OpenApiEndpoint, createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "x402 Agents",
|
||||
icon: "payments",
|
||||
},
|
||||
title: "x402 Agents",
|
||||
description: "Easily create AI agents that can pay for any x402-compatible API calls.",
|
||||
});
|
||||
|
||||
# x402 Agents
|
||||
|
||||
Easily create AI agents that can pay for any x402-compatible API calls.
|
||||
|
||||
## Using the MCP Server
|
||||
|
||||
The easiest way to equip your agents with the ability to pay for API calls is to use the [remote MCP server](/ai/mcp) and provide the tools to your agent.
|
||||
|
||||
The MCP server comes with all the tools by default, but you can filter the tools available by passing a comma-separated list of tools as a query parameter.
|
||||
|
||||
In this example, we create a ReAct agent using LangChain and filter the MCP tools to `fetchWithPayment` and `getWalletBalance` as the only 2 tools we give our agent. You can view the full list of available tools [here](/ai/mcp#available-tools).
|
||||
|
||||
<Tabs defaultValue="typescript">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
|
||||
<TabsTrigger value="python">Python</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="typescript">
|
||||
|
||||
```typescript
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4.1",
|
||||
});
|
||||
|
||||
const mcpServers = {
|
||||
thirdweb: {
|
||||
url: `https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>&tools=fetchWithPayment,getWalletBalance`
|
||||
}
|
||||
};
|
||||
|
||||
// Connect to MCP servers and get tools
|
||||
const client = new MultiServerMCPClient(mcpServers);
|
||||
const tools = await client.getTools();
|
||||
|
||||
const agent = createReactAgent({
|
||||
llm: model,
|
||||
tools: tools,
|
||||
prompt: "Use the fetchWithPayment tool to fetch any endpoint. Always pay in {{tokenAddress}}. Your wallet address is {{walletAddress}}."
|
||||
});
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="python">
|
||||
|
||||
```python
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
client = MultiServerMCPClient(
|
||||
{
|
||||
"thirdweb": {
|
||||
"url": "https://api.thirdweb.com/mcp?secretKey=<your-project-secret-key>&tools=fetchWithPayment,getWalletBalance",
|
||||
}
|
||||
}
|
||||
)
|
||||
tools = await client.get_tools()
|
||||
agent = create_react_agent("openai:gpt-4.1", tools, prompt="Use the fetchWithPayment tool to fetch any endpoint. Always pay in {{tokenAddress}}. Your wallet address is {{walletAddress}}.")
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
## Using the API directly
|
||||
|
||||
You can also create your own MCP tool or wrap all external calls with the `/v1/x402/fetchWithPayment` endpoint to automatically handle payment flows when APIs return a `402 Payment Required` response.
|
||||
|
||||
Pass the target `url`, `method` and optional `from` wallet address to the endpoint that will complete the payment. The address should be one of your [user wallets](/wallets/users) or [server wallets](/wallets/server).
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.thirdweb.com/v1/payments/x402/fetch?url=https://api.example.com/premium&method=GET&from=0x1234... \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "x-secret-key: <your-project-secret-key>" \
|
||||
-d '{ ... }' # optional request body passed through to the url called.
|
||||
```
|
||||
|
||||
<OpenApiEndpoint path="/v1/payments/x402/fetch" method="POST" />
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, OpenApiEndpoint } from "@doc";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, OpenApiEndpoint, createMetadata } from "@doc";
|
||||
import { TypeScriptIcon, EngineIcon } from "@/icons";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "x402 Client",
|
||||
icon: "payments",
|
||||
},
|
||||
title: "x402 Client",
|
||||
description: "Make requests to any x402-compatible backend and automatically handle payment flows when APIs return a `402 Payment Required` response.",
|
||||
});
|
||||
|
||||
# Client Side
|
||||
|
||||
Make requests to any x402-compatible backend by automatically handling payment flows when APIs return a `402 Payment Required` response.
|
||||
Make requests to any x402-compatible backend and automatically handle payment flows when APIs return a `402 Payment Required` response.
|
||||
|
||||
The client library wraps the native `fetch` API and handles:
|
||||
1. Initial request to the API
|
||||
@@ -68,46 +77,18 @@ The client library wraps the native `fetch` API and handles:
|
||||
<TabsContent value="http">
|
||||
## Prepare Payment
|
||||
|
||||
Create a signed payment header from the authenticated user to include in your API request:
|
||||
Fetch any x402-compatible API and automatically handle payment flows when APIs return a `402 Payment Required` response using the authenticated wallet.
|
||||
|
||||
### Making the Paid Request
|
||||
|
||||
After preparing the payment, include it in your API request:
|
||||
Only requires passing a 'url' and 'from' query parameters with the authenticated wallet address (server or user wallet) to complete the payment.
|
||||
|
||||
```bash
|
||||
# First, make the initial request to get payment requirements
|
||||
curl -X GET https://api.example.com/paid-endpoint
|
||||
|
||||
# Response will be 402 Payment Required with payment details
|
||||
# {
|
||||
# "x402Version": 1,
|
||||
# "accepts": [...payment requirements...],
|
||||
# "error": "Payment required"
|
||||
# }
|
||||
|
||||
# Select one of the payment methods, and sign the payment authorization using the API
|
||||
curl -X POST https://api.thirdweb.com/v1/payments/x402/prepare \
|
||||
curl -X POST https://api.thirdweb.com/v1/payments/x402/fetch?url=https://api.example.com/premium&from=0x1234... \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer <user-access-token>" \
|
||||
-d '{
|
||||
"from": "0x1234...",
|
||||
"paymentRequirements": { ... }
|
||||
}'
|
||||
|
||||
# Response will contain the signed payment header
|
||||
# {
|
||||
# "paymentPayload": { ... },
|
||||
# "paymentHeader": "..." // base64 encoded payment header
|
||||
# }
|
||||
|
||||
# Finally, make the request with the payment header
|
||||
curl -X GET https://api.example.com/paid-endpoint \
|
||||
-H "X-PAYMENT: <base64-encoded-payment>"
|
||||
-H "Authorization: Bearer <user-wallet-token>" \
|
||||
-d '{ ... }' # request body passed through to the url called.
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
<OpenApiEndpoint path="/v1/payments/x402/prepare" method="POST" />
|
||||
<OpenApiEndpoint path="/v1/payments/x402/fetch" method="POST" />
|
||||
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, OpenApiEndpoint, Callout } from "@doc";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, OpenApiEndpoint, Callout, createMetadata } from "@doc";
|
||||
import { TypeScriptIcon, EngineIcon } from "@/icons";
|
||||
|
||||
# Facilitator API
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "x402 Facilitator",
|
||||
icon: "payments",
|
||||
},
|
||||
title: "x402 Facilitator",
|
||||
description: "Verify and submit x402 payments using your own server wallet.",
|
||||
});
|
||||
|
||||
# Facilitator
|
||||
|
||||
The facilitator is a service that handles verifying and submitting x402 payments. It uses your own [server wallet](/wallets/server) and leverages EIP-7702 to submit transactions gaslessly.
|
||||
|
||||
@@ -16,6 +25,13 @@ The thirdweb facilitator is compatible with any x402 backend and middleware libr
|
||||
|
||||
You can view all transactions processed by your facilitator in your project dashboard.
|
||||
|
||||
## Chain and token support
|
||||
|
||||
Our facilitator supports payments on any EVM chain, as long as the payment token supports either:
|
||||
|
||||
- ERC-2612 permit (most ERC20 tokens)
|
||||
- ERC-3009 sign with authorization (USDC on all chains)
|
||||
|
||||
<Tabs defaultValue="typescript">
|
||||
<TabsList>
|
||||
<TabsTrigger value="typescript" className="flex items-center [&>p]:mb-0">
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { ArticleIconCard } from "@doc";
|
||||
import { ArticleIconCard, createMetadata } from "@doc";
|
||||
import { ReactIcon, TypeScriptIcon, EngineIcon } from "@/icons";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "x402 Payments",
|
||||
icon: "payments",
|
||||
},
|
||||
title: "x402 Payments",
|
||||
description: "Instant payments for your APIs, websites, and autonomous agents.",
|
||||
});
|
||||
|
||||
# x402 Payments
|
||||
|
||||
x402 is an open-source protocol that turns the dormant HTTP 402 Payment Required status code into a fully-featured, on-chain payment layer for APIs, websites, and autonomous agents.
|
||||
@@ -33,6 +42,8 @@ const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet);
|
||||
const response = await fetchWithPay('https://api.example.com/paid-endpoint');
|
||||
```
|
||||
|
||||
You can also use the thirdweb API to fetch any x402 compatible endpoint and pay for it with the authenticated wallet. See the [client side docs](/payments/x402/client) for more details.
|
||||
|
||||
## Server Side
|
||||
|
||||
To make your API calls payable, use the `settlePayment` function in a middleware or endpoint:
|
||||
@@ -77,6 +88,8 @@ export async function GET(request: Request) {
|
||||
}
|
||||
```
|
||||
|
||||
You can also create middlewares to handle payment for multiple endpoints, see the [server side docs](/payments/x402/server) for more details. The [facilitator](/payments/x402/facilitator) handles settling the payment onchain using your own server wallet.
|
||||
|
||||
## Going Further
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6">
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, DocImage } from "@doc";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent, DocImage, createMetadata } from "@doc";
|
||||
import { Steps, Step } from "@doc";
|
||||
import PaymentFlow from "./x402-protocol-flow.png";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "x402 Server",
|
||||
icon: "payments",
|
||||
},
|
||||
title: "x402 Server",
|
||||
description: "Accept x402 payments in your APIs from any x402-compatible client.",
|
||||
});
|
||||
|
||||
# Server Side
|
||||
|
||||
Accept x402 payments in your APIs using any x402-compatible client. Your server can verify and settle payments using thirdweb's facilitator service or any custom facilitator.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Gas Sponsorship Policies
|
||||
|
||||
Sponsorship policies are a way to control the execution of sponsored transactions on your account.
|
||||
|
||||
You can use them to:
|
||||
|
||||
- Set global spend limits.
|
||||
- Restrict sponsorship to a particular chain.
|
||||
- Restrict sponsorship to a particular contract.
|
||||
- Allow/Disallow specific wallets
|
||||
- Setup your own server verifier.
|
||||
|
||||
These policies can be set from your project dashboard, under project > account abstraction > settings.
|
||||
|
||||
## Server Verifier
|
||||
|
||||
The most flexible and powerful way to control the execution of sponsored transactions is to setup your own server verifier.
|
||||
|
||||
In the project dashboard, under project > account abstraction > settings, setup your backend URL and any additional headers you need to pass to the verifier.
|
||||
|
||||
On every user transaction, your backend will be called with the transaction data and the user's wallet address, your endpoint should return a boolean indicating whether the transaction should be sponsored.
|
||||
|
||||
### Backend endpoint specification:
|
||||
|
||||
Your backend will be called with the following request:
|
||||
|
||||
```http
|
||||
POST https://your-backend-url/verify-transaction
|
||||
Content-Type: application/json
|
||||
Referer: "https://api.thirdweb.com",
|
||||
# additional custom headers
|
||||
|
||||
# request body
|
||||
{
|
||||
"clientId": string;
|
||||
"chainId": number;
|
||||
"userOp": {
|
||||
sender: string;
|
||||
targets: string[];
|
||||
gasLimit: string;
|
||||
gasPrice: string;
|
||||
data?: {
|
||||
targets: string[];
|
||||
callDatas: string[];
|
||||
values: string[];
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Your backend should process the request, apply your own policy logic based on the request data and return the following JSON response:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"isAllowed": boolean;
|
||||
"reason"?: string;
|
||||
}
|
||||
```
|
||||
|
||||
The `isAllowed` field indicates whether the transaction should be sponsored or not. `reason` is an optional field that can be used to provide a reason for the decision.
|
||||
@@ -42,6 +42,10 @@ export const sidebar: SideBar = {
|
||||
href: `${transactionsSlug}/session-keys`,
|
||||
name: "Session Keys",
|
||||
},
|
||||
{
|
||||
href: `${transactionsSlug}/policies`,
|
||||
name: "Sponsorship Policies",
|
||||
},
|
||||
],
|
||||
},
|
||||
{ separator: true },
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
VITE_THIRDWEB_CLIENT_ID=
|
||||
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
@@ -0,0 +1 @@
|
||||
legacy-peer-deps = true
|
||||
@@ -0,0 +1,33 @@
|
||||
# wagmi-inapp
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`968913d`](https://github.com/thirdweb-dev/js/commit/968913d55b761c1e23062fdf6008904fa8c17050)]:
|
||||
- thirdweb@5.108.9
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.159
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af9a73d`](https://github.com/thirdweb-dev/js/commit/af9a73d9302078a506b7d3cb8d55b611f15b6293)]:
|
||||
- thirdweb@5.108.8
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.158
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e612f16`](https://github.com/thirdweb-dev/js/commit/e612f16a85ffda024d7f74b41abb48b2999b9107)]:
|
||||
- thirdweb@5.108.7
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.157
|
||||
|
||||
## 0.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`eb603f8`](https://github.com/thirdweb-dev/js/commit/eb603f89086f25202b157eb748f70a252dd7c6e7), [`eb603f8`](https://github.com/thirdweb-dev/js/commit/eb603f89086f25202b157eb748f70a252dd7c6e7)]:
|
||||
- @thirdweb-dev/wagmi-adapter@0.2.156
|
||||
- thirdweb@5.108.6
|
||||
@@ -0,0 +1,37 @@
|
||||
# Thirdweb Wagmi Adapter
|
||||
|
||||
This is a demo of the [thirdweb Wagmi Adapter](https://github.com/thirdweb-dev/js/tree/main/packages/wagmi-adapter).
|
||||
|
||||
It demonstrates how to connect [in-app smart wallets](https://portal.thirdweb.com/connect/wallet/sign-in-methods/configure) to a Wagmi app.
|
||||
|
||||
Users connect with google, and obtain a ERC-4337 smart account that can be used to interact with the app without paying for gas.
|
||||
|
||||
```ts
|
||||
// src/wagmi.ts
|
||||
export const config = createConfig({
|
||||
chains: [chain],
|
||||
connectors: [
|
||||
inAppWalletConnector({
|
||||
client,
|
||||
smartAccount: {
|
||||
chain: thirdwebChain(chain),
|
||||
sponsorGas: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
transports: {
|
||||
[chain.id]: http(),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Copy the `.env.example` file to `.env` and set your Thirdweb client ID. You can get your client ID from the [thirdweb dashboard](https://thirdweb.com).
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.6/schema.json",
|
||||
"extends": "//",
|
||||
"overrides": [
|
||||
{
|
||||
"assist": {
|
||||
"actions": {
|
||||
"source": {
|
||||
"useSortedKeys": "off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"includes": ["package.json"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Create Wagmi</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "wagmi-inapp",
|
||||
"private": true,
|
||||
"version": "0.0.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"fix": "biome check ./src --fix",
|
||||
"format": "biome format ./src --write",
|
||||
"lint": "biome check ./src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "5.81.5",
|
||||
"@thirdweb-dev/wagmi-adapter": "workspace:*",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"thirdweb": "workspace:*",
|
||||
"viem": "2.37.9",
|
||||
"wagmi": "2.17.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.0.6",
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@vitejs/plugin-react": "5.0.4",
|
||||
"@wagmi/cli": "latest",
|
||||
"buffer": "^6.0.3",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.1.7"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { ConnectionOptions } from "@thirdweb-dev/wagmi-adapter";
|
||||
import { ConnectButton } from "thirdweb/react";
|
||||
import {
|
||||
useAccount,
|
||||
useConnect,
|
||||
useDisconnect,
|
||||
useSendTransaction,
|
||||
} from "wagmi";
|
||||
import { chain, client } from "./wagmi.js";
|
||||
|
||||
function App() {
|
||||
const account = useAccount();
|
||||
const { connectors, connect, status, error } = useConnect();
|
||||
const { disconnect } = useDisconnect();
|
||||
const {
|
||||
sendTransaction,
|
||||
isPending,
|
||||
isSuccess,
|
||||
isError,
|
||||
error: sendTxError,
|
||||
data: sendTxData,
|
||||
} = useSendTransaction();
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<h2>Account</h2>
|
||||
|
||||
<div>
|
||||
status: {account.status}
|
||||
<br />
|
||||
addresses: {JSON.stringify(account.addresses)}
|
||||
<br />
|
||||
chainId: {account.chainId}
|
||||
</div>
|
||||
|
||||
{account.status === "connected" && (
|
||||
<button type="button" onClick={() => disconnect()}>
|
||||
Disconnect
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Connect</h2>
|
||||
<ConnectButton
|
||||
client={client}
|
||||
onConnect={(wallet) => {
|
||||
// auto connect to wagmi on tw connect
|
||||
const twConnector = connectors.find(
|
||||
(c) => c.id === "in-app-wallet",
|
||||
);
|
||||
if (twConnector) {
|
||||
const options = {
|
||||
wallet,
|
||||
} satisfies ConnectionOptions;
|
||||
connect({
|
||||
connector: twConnector,
|
||||
chainId: chain.id,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{connectors.map((connector) => (
|
||||
<button
|
||||
key={connector.uid}
|
||||
onClick={() => {
|
||||
if (connector.id === "in-app-wallet") {
|
||||
const connectOptions = {
|
||||
strategy: "google",
|
||||
} satisfies ConnectionOptions;
|
||||
connect({
|
||||
connector,
|
||||
chainId: chain.id,
|
||||
...connectOptions,
|
||||
});
|
||||
} else {
|
||||
connect({ connector, chainId: chain.id });
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{connector.name}
|
||||
</button>
|
||||
))}
|
||||
<div>{status}</div>
|
||||
<div>{error?.message}</div>
|
||||
</div>
|
||||
{account && (
|
||||
<div>
|
||||
<h2>Transact</h2>
|
||||
<button
|
||||
onClick={() => sendTransaction({ to: account.address, value: 0n })}
|
||||
type="button"
|
||||
>
|
||||
Send Tx
|
||||
</button>
|
||||
<div>{isPending ? "Sending..." : ""}</div>
|
||||
<div>
|
||||
{isSuccess
|
||||
? `Success: ${sendTxData}`
|
||||
: isError
|
||||
? sendTxError?.message
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,21 @@
|
||||
:root {
|
||||
background-color: #181818;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
color-scheme: light dark;
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-synthesis: none;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
background-color: #f8f8f8;
|
||||
color: #181818;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Buffer } from "buffer";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { WagmiProvider } from "wagmi";
|
||||
|
||||
import App from "./App.js";
|
||||
import { config } from "./wagmi.js";
|
||||
|
||||
import "./index.css";
|
||||
import { ThirdwebProvider } from "thirdweb/react";
|
||||
|
||||
globalThis.Buffer = Buffer;
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: always exists
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<ThirdwebProvider>
|
||||
<WagmiProvider config={config}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<App />
|
||||
</QueryClientProvider>
|
||||
</WagmiProvider>
|
||||
</ThirdwebProvider>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,41 @@
|
||||
import { inAppWalletConnector } from "@thirdweb-dev/wagmi-adapter";
|
||||
import { createThirdwebClient, defineChain as thirdwebChain } from "thirdweb";
|
||||
import { createConfig, http } from "wagmi";
|
||||
import { baseSepolia } from "wagmi/chains";
|
||||
|
||||
const clientId = import.meta.env.VITE_THIRDWEB_CLIENT_ID;
|
||||
|
||||
if (!clientId) {
|
||||
throw new Error(
|
||||
"VITE_THIRDWEB_CLIENT_ID is not set. Get your client ID from https://thirdweb.com",
|
||||
);
|
||||
}
|
||||
|
||||
export const client = createThirdwebClient({
|
||||
clientId,
|
||||
});
|
||||
|
||||
export const chain = baseSepolia;
|
||||
export const thirdwebChainForWallet = thirdwebChain(baseSepolia.id);
|
||||
|
||||
export const config = createConfig({
|
||||
chains: [chain],
|
||||
connectors: [
|
||||
inAppWalletConnector({
|
||||
client,
|
||||
smartAccount: {
|
||||
chain: thirdwebChain(chain.id),
|
||||
sponsorGas: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
transports: {
|
||||
[chain.id]: http(),
|
||||
},
|
||||
});
|
||||
|
||||
declare module "wagmi" {
|
||||
interface Register {
|
||||
config: typeof config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
// This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config.
|
||||
"include": [],
|
||||
"compilerOptions": {
|
||||
// Incremental builds
|
||||
// NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes.
|
||||
"incremental": false,
|
||||
|
||||
// Type checking
|
||||
"strict": true,
|
||||
"useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022.
|
||||
"noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode.
|
||||
"noImplicitReturns": true, // Not enabled by default in `strict` mode.
|
||||
"useUnknownInCatchVariables": true, // TODO: This would normally be enabled in `strict` mode but would require some adjustments to the codebase.
|
||||
"noImplicitOverride": true, // Not enabled by default in `strict` mode.
|
||||
"noUnusedLocals": true, // Not enabled by default in `strict` mode.
|
||||
"noUnusedParameters": true, // Not enabled by default in `strict` mode.
|
||||
"exactOptionalPropertyTypes": false, // Not enabled by default in `strict` mode.
|
||||
"noUncheckedIndexedAccess": true, // Not enabled by default in `strict` mode.
|
||||
|
||||
// JavaScript support
|
||||
"allowJs": false,
|
||||
"checkJs": false,
|
||||
|
||||
// Interop constraints
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers.
|
||||
|
||||
// Language and environment
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "NodeNext",
|
||||
"target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping.
|
||||
"lib": [
|
||||
"ES2022", // By using ES2022 we get access to the `.cause` property on `Error` instances.
|
||||
"DOM" // We are adding `DOM` here to get the `fetch`, etc. types. This should be removed once these types are available via DefinitelyTyped.
|
||||
],
|
||||
|
||||
// Skip type checking for node modules
|
||||
"skipLibCheck": true,
|
||||
|
||||
// jsx for "/react" portion
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
// This configuration is used for local development and type checking.
|
||||
"extends": "./tsconfig.base.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"],
|
||||
// "references": [{ "path": "./scripts/tsconfig.json" }],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
/** @type {import('vite').UserConfig} */
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 3000,
|
||||
},
|
||||
});
|
||||
@@ -65,6 +65,7 @@
|
||||
"playground": "turbo run dev --filter=./apps/playground-web --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/nebula --filter=./packages/ai-sdk-provider",
|
||||
"playground:build": "turbo run build --filter=./apps/playground-web",
|
||||
"portal": "turbo run dev --filter=./apps/portal --filter=./packages/thirdweb --filter=./packages/insight --filter=./packages/engine --filter=./packages/nebula",
|
||||
"wagmi-demo": "turbo run dev --filter=./apps/wagmi-demo --filter=./packages/thirdweb --filter=./packages/wagmi-adapter",
|
||||
"portal:build": "turbo run build --filter=./apps/portal",
|
||||
"prefix": "pnpm dlx sherif@latest -i remark-gfm -i eslint --fix",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @thirdweb-dev/ai-sdk-provider
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8211](https://github.com/thirdweb-dev/js/pull/8211) [`71a1e58`](https://github.com/thirdweb-dev/js/commit/71a1e58eafa6d51f0a3eb9c7a3fcc685465c72b5) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Default in-memory session store across thirdweb AI instances, option to pass your own
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -56,5 +56,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.1.3"
|
||||
"version": "0.1.4"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
export { createThirdwebAI, type ThirdwebProvider } from "../provider.js";
|
||||
export {
|
||||
createThirdwebAI,
|
||||
type SessionStore,
|
||||
type ThirdwebProvider,
|
||||
} from "../provider.js";
|
||||
export type {
|
||||
MonitorTransactionInput,
|
||||
MonitorTransactionOutput,
|
||||
|
||||
@@ -544,7 +544,7 @@ export class ThirdwebProvider implements ProviderV2 {
|
||||
|
||||
constructor(config: ThirdwebConfig = {}) {
|
||||
this.config = config;
|
||||
this.session = new SessionStore();
|
||||
this.session = config.sessionStore || memorySessionStore;
|
||||
}
|
||||
|
||||
chat(id?: string, settings: ThirdwebSettings = {}) {
|
||||
@@ -581,22 +581,29 @@ export class ThirdwebProvider implements ProviderV2 {
|
||||
}
|
||||
}
|
||||
|
||||
class SessionStore {
|
||||
export interface SessionStore {
|
||||
getSessionId(chatId: string): string | null;
|
||||
setSessionId(chatId: string, sessionId: string): void;
|
||||
clearSessionId(chatId: string): void;
|
||||
}
|
||||
|
||||
class InMemorySessionStore implements SessionStore {
|
||||
private sessionId: Map<string, string> = new Map();
|
||||
|
||||
getSessionId(chatId: string) {
|
||||
getSessionId(chatId: string): string | null {
|
||||
return this.sessionId.get(chatId) || null;
|
||||
}
|
||||
|
||||
setSessionId(chatId: string, sessionId: string) {
|
||||
setSessionId(chatId: string, sessionId: string): void {
|
||||
this.sessionId.set(chatId, sessionId);
|
||||
}
|
||||
|
||||
clearSessionId(chatId: string) {
|
||||
clearSessionId(chatId: string): void {
|
||||
this.sessionId.delete(chatId);
|
||||
}
|
||||
}
|
||||
|
||||
// singleton session store used as default if no session store is provided
|
||||
const memorySessionStore = new InMemorySessionStore();
|
||||
|
||||
// Factory function for easier usage
|
||||
export function createThirdwebAI(config: ThirdwebConfig = {}) {
|
||||
return new ThirdwebProvider(config);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { UIDataTypes, UIMessage } from "ai";
|
||||
import type { SessionStore } from "./provider.js";
|
||||
import type {
|
||||
MonitorTransactionInput,
|
||||
MonitorTransactionOutput,
|
||||
@@ -26,6 +27,11 @@ export interface ThirdwebConfig {
|
||||
* Optional: The user wallet auth token (JWT) for executing transactions
|
||||
*/
|
||||
walletAuthToken?: string;
|
||||
/**
|
||||
* Optional: The session store to use for storing and retrieving thirdweb AI session IDs from ai SDK chat ids.
|
||||
* If not provided, a default in-memory session store will be used.
|
||||
*/
|
||||
sessionStore?: SessionStore;
|
||||
}
|
||||
|
||||
export interface ThirdwebSettings {
|
||||
|
||||
@@ -1,5 +1,33 @@
|
||||
# @thirdweb-dev/nebula
|
||||
|
||||
## 0.2.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`968913d`](https://github.com/thirdweb-dev/js/commit/968913d55b761c1e23062fdf6008904fa8c17050)]:
|
||||
- thirdweb@5.108.9
|
||||
|
||||
## 0.2.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af9a73d`](https://github.com/thirdweb-dev/js/commit/af9a73d9302078a506b7d3cb8d55b611f15b6293)]:
|
||||
- thirdweb@5.108.8
|
||||
|
||||
## 0.2.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e612f16`](https://github.com/thirdweb-dev/js/commit/e612f16a85ffda024d7f74b41abb48b2999b9107)]:
|
||||
- thirdweb@5.108.7
|
||||
|
||||
## 0.2.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`eb603f8`](https://github.com/thirdweb-dev/js/commit/eb603f89086f25202b157eb748f70a252dd7c6e7)]:
|
||||
- thirdweb@5.108.6
|
||||
|
||||
## 0.2.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.58"
|
||||
"version": "0.2.62"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.108.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8192](https://github.com/thirdweb-dev/js/pull/8192) [`968913d`](https://github.com/thirdweb-dev/js/commit/968913d55b761c1e23062fdf6008904fa8c17050) Thanks [@MananTank](https://github.com/MananTank)! - Fix TransactionWidget not updating when `currency` prop is changed after initial render
|
||||
|
||||
## 5.108.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8184](https://github.com/thirdweb-dev/js/pull/8184) [`af9a73d`](https://github.com/thirdweb-dev/js/commit/af9a73d9302078a506b7d3cb8d55b611f15b6293) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Fix waitUntil facilitator param not being respected
|
||||
|
||||
## 5.108.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8181](https://github.com/thirdweb-dev/js/pull/8181) [`e612f16`](https://github.com/thirdweb-dev/js/commit/e612f16a85ffda024d7f74b41abb48b2999b9107) Thanks [@MananTank](https://github.com/MananTank)! - BuyWidget UI improvements and new features:
|
||||
- `chain`, and `amount` props are now optional
|
||||
- User can always change the token and chain selection in the widget
|
||||
- Both fiat and token amounts are editable
|
||||
- connected wallet can be disconnected from the widget
|
||||
- current balance displayed in the widget
|
||||
|
||||
## 5.108.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8167](https://github.com/thirdweb-dev/js/pull/8167) [`eb603f8`](https://github.com/thirdweb-dev/js/commit/eb603f89086f25202b157eb748f70a252dd7c6e7) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Update dependencies
|
||||
|
||||
## 5.108.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"qrcode": "1.5.3",
|
||||
"toml": "3.0.0",
|
||||
"uqr": "0.1.2",
|
||||
"viem": "2.33.2",
|
||||
"viem": "2.37.9",
|
||||
"x402": "0.6.1",
|
||||
"zod": "3.25.75"
|
||||
},
|
||||
@@ -67,7 +67,7 @@
|
||||
"@types/react": "19.1.8",
|
||||
"@types/react-dom": "19.1.6",
|
||||
"@viem/anvil": "0.0.10",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"@vitejs/plugin-react": "5.0.4",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vitest/ui": "3.2.4",
|
||||
"dotenv-mono": "^1.3.14",
|
||||
@@ -94,7 +94,7 @@
|
||||
"typedoc": "0.27.9",
|
||||
"typedoc-better-json": "0.9.4",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "7.0.1",
|
||||
"vite": "7.1.7",
|
||||
"vitest": "3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -430,5 +430,5 @@
|
||||
}
|
||||
},
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "5.108.5"
|
||||
"version": "5.108.9"
|
||||
}
|
||||
|
||||
@@ -432,7 +432,8 @@ type WalletToViemOptions = {
|
||||
function walletToViem(options: WalletToViemOptions): WalletClient {
|
||||
const { wallet, chain, client } = options;
|
||||
|
||||
if (!wallet.getAccount()) {
|
||||
const account = wallet.getAccount();
|
||||
if (!account) {
|
||||
throw new Error("Wallet is not connected.");
|
||||
}
|
||||
|
||||
@@ -456,7 +457,7 @@ function walletToViem(options: WalletToViemOptions): WalletClient {
|
||||
wallet,
|
||||
});
|
||||
return createWalletClient({
|
||||
account: wallet.getAccount()?.address,
|
||||
account: getAddress(account.address),
|
||||
chain: viemChain,
|
||||
key: "thirdweb-wallet",
|
||||
transport: custom({
|
||||
|
||||
@@ -276,7 +276,7 @@ async function getLogsFromInsight(options: {
|
||||
|
||||
function toLog(r: ContractEvent[]) {
|
||||
const cleanedEventData = r.map((tx) => ({
|
||||
address: tx.address,
|
||||
address: tx.address as Hex,
|
||||
blockHash: tx.block_hash as Hex,
|
||||
blockNumber: numberToHex(Number(tx.block_number)),
|
||||
blockTimestamp: tx.block_timestamp,
|
||||
|
||||
@@ -72,6 +72,7 @@ export { useActiveAccount } from "../react/core/hooks/wallets/useActiveAccount.j
|
||||
export { useActiveWallet } from "../react/core/hooks/wallets/useActiveWallet.js";
|
||||
export { useActiveWalletChain } from "../react/core/hooks/wallets/useActiveWalletChain.js";
|
||||
export { useActiveWalletConnectionStatus } from "../react/core/hooks/wallets/useActiveWalletConnectionStatus.js";
|
||||
export { useAddConnectedWallet } from "../react/core/hooks/wallets/useAddConnectedWallet.js";
|
||||
export { useAdminWallet } from "../react/core/hooks/wallets/useAdminWallet.js";
|
||||
export { useAuthToken } from "../react/core/hooks/wallets/useAuthToken.js";
|
||||
// eip5792
|
||||
|
||||
@@ -82,6 +82,7 @@ export { useActiveAccount } from "../react/core/hooks/wallets/useActiveAccount.j
|
||||
export { useActiveWallet } from "../react/core/hooks/wallets/useActiveWallet.js";
|
||||
export { useActiveWalletChain } from "../react/core/hooks/wallets/useActiveWalletChain.js";
|
||||
export { useActiveWalletConnectionStatus } from "../react/core/hooks/wallets/useActiveWalletConnectionStatus.js";
|
||||
export { useAddConnectedWallet } from "../react/core/hooks/wallets/useAddConnectedWallet.js";
|
||||
export { useAdminWallet } from "../react/core/hooks/wallets/useAdminWallet.js";
|
||||
export { useAuthToken } from "../react/core/hooks/wallets/useAuthToken.js";
|
||||
// eip5792
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as Bridge from "../../../../bridge/index.js";
|
||||
import type { Chain } from "../../../../chains/types.js";
|
||||
import type { BuyWithCryptoStatus } from "../../../../pay/buyWithCrypto/getStatus.js";
|
||||
import type { BuyWithFiatStatus } from "../../../../pay/buyWithFiat/getStatus.js";
|
||||
import type { SupportedFiatCurrency } from "../../../../pay/convert/type.js";
|
||||
import type { PurchaseData } from "../../../../pay/types.js";
|
||||
import type { FiatProvider } from "../../../../pay/utils/commonTypes.js";
|
||||
import type { GaslessOptions } from "../../../../transaction/actions/gasless/types.js";
|
||||
@@ -94,6 +95,11 @@ export type SendTransactionPayModalConfig =
|
||||
* The user's ISO 3166 alpha-2 country code. This is used to determine onramp provider support.
|
||||
*/
|
||||
country?: string;
|
||||
/**
|
||||
* The currency to use for showing the fiat values
|
||||
* @default "USD"
|
||||
*/
|
||||
currency?: SupportedFiatCurrency;
|
||||
}
|
||||
| false;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getThirdwebBaseUrl } from "../../../utils/domains.js";
|
||||
import { getClientFetch } from "../../../utils/fetch.js";
|
||||
import { toTokens, toUnits } from "../../../utils/units.js";
|
||||
import type { Wallet } from "../../../wallets/interfaces/wallet.js";
|
||||
import type { PaymentMethod } from "../machines/paymentMachine.js";
|
||||
import type { PaymentMethod } from "../../web/ui/Bridge/types.js";
|
||||
import type { SupportedTokens } from "../utils/defaultTokens.js";
|
||||
import { useActiveWallet } from "./wallets/useActiveWallet.js";
|
||||
|
||||
@@ -33,7 +33,6 @@ export function usePaymentMethods(options: {
|
||||
destinationAmount: string;
|
||||
client: ThirdwebClient;
|
||||
payerWallet?: Wallet;
|
||||
includeDestinationToken?: boolean;
|
||||
supportedTokens?: SupportedTokens;
|
||||
}) {
|
||||
const {
|
||||
@@ -41,7 +40,6 @@ export function usePaymentMethods(options: {
|
||||
destinationAmount,
|
||||
client,
|
||||
payerWallet,
|
||||
includeDestinationToken,
|
||||
supportedTokens,
|
||||
} = options;
|
||||
const localWallet = useActiveWallet(); // TODO (bridge): get all connected wallets
|
||||
@@ -135,7 +133,6 @@ export function usePaymentMethods(options: {
|
||||
destinationToken.address,
|
||||
destinationAmount,
|
||||
payerWallet?.getAccount()?.address,
|
||||
includeDestinationToken,
|
||||
supportedTokens,
|
||||
], // 5 minutes
|
||||
refetchOnWindowFocus: false,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { getCompilerMetadata } from "../../../contract/actions/get-compiler-meta
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import { decimals } from "../../../extensions/erc20/read/decimals.js";
|
||||
import { getToken } from "../../../pay/convert/get-token.js";
|
||||
import type { SupportedFiatCurrency } from "../../../pay/convert/type.js";
|
||||
import { encode } from "../../../transaction/actions/encode.js";
|
||||
import type { PreparedTransaction } from "../../../transaction/prepare-transaction.js";
|
||||
import { getTransactionGasCost } from "../../../transaction/utils.js";
|
||||
@@ -18,10 +17,7 @@ import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-va
|
||||
import { toTokens } from "../../../utils/units.js";
|
||||
import type { Wallet } from "../../../wallets/interfaces/wallet.js";
|
||||
import { hasSponsoredTransactionsEnabled } from "../../../wallets/smart/is-smart-wallet.js";
|
||||
import {
|
||||
formatCurrencyAmount,
|
||||
formatTokenAmount,
|
||||
} from "../../web/ui/ConnectWallet/screens/formatTokenBalance.js";
|
||||
import { formatTokenAmount } from "../../web/ui/ConnectWallet/screens/formatTokenBalance.js";
|
||||
import { useChainMetadata } from "./others/useChainQuery.js";
|
||||
|
||||
interface TransactionDetails {
|
||||
@@ -31,7 +27,6 @@ interface TransactionDetails {
|
||||
selector: string;
|
||||
description?: string;
|
||||
};
|
||||
usdValueDisplay: string | null;
|
||||
txCostDisplay: string;
|
||||
gasCostDisplay: string | null;
|
||||
tokenInfo: TokenWithPrices | null;
|
||||
@@ -45,7 +40,6 @@ interface UseTransactionDetailsOptions {
|
||||
transaction: PreparedTransaction;
|
||||
client: ThirdwebClient;
|
||||
wallet: Wallet | undefined;
|
||||
currency: SupportedFiatCurrency | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +48,6 @@ interface UseTransactionDetailsOptions {
|
||||
*/
|
||||
export function useTransactionDetails({
|
||||
transaction,
|
||||
currency,
|
||||
client,
|
||||
wallet,
|
||||
}: UseTransactionDetailsOptions) {
|
||||
@@ -158,9 +151,6 @@ export function useTransactionDetails({
|
||||
: (value || 0n) + (gasCostWei || 0n);
|
||||
const totalCost = toTokens(totalCostWei, decimal);
|
||||
|
||||
const price = tokenInfo?.prices[currency || "USD"] || 0;
|
||||
const usdValue = price ? Number(totalCost) * price : null;
|
||||
|
||||
return {
|
||||
contractMetadata,
|
||||
costWei,
|
||||
@@ -173,9 +163,6 @@ export function useTransactionDetails({
|
||||
totalCost,
|
||||
totalCostWei,
|
||||
txCostDisplay: `${formatTokenAmount(costWei, decimal)} ${tokenSymbol}`,
|
||||
usdValueDisplay: usdValue
|
||||
? formatCurrencyAmount(currency || "USD", usdValue)
|
||||
: null,
|
||||
};
|
||||
},
|
||||
queryKey: [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user