Compare commits
92
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00dcc346bf | ||
|
|
e0738d68d2 | ||
|
|
e160deb8b7 | ||
|
|
a6f846fc1e | ||
|
|
0e3f3460ad | ||
|
|
552f7025c1 | ||
|
|
15f083912c | ||
|
|
04862de9e4 | ||
|
|
ea46572947 | ||
|
|
f276dcfd76 | ||
|
|
6495b94910 | ||
|
|
210f631cba | ||
|
|
557f0e29e8 | ||
|
|
f9bbf8101c | ||
|
|
59d226512c | ||
|
|
778bf2cc4b | ||
|
|
97e5080fdf | ||
|
|
9f9fadf242 | ||
|
|
224c9d034f | ||
|
|
8b8dfd9bf9 | ||
|
|
70e52188a1 | ||
|
|
c82b606cd2 | ||
|
|
1c6b12449b | ||
|
|
ea8061048a | ||
|
|
3ad9f7deba | ||
|
|
0b454050b1 | ||
|
|
dc1f7ab2ee | ||
|
|
bd17c08891 | ||
|
|
79e3694e37 | ||
|
|
ef15ba7c43 | ||
|
|
a724bc39a2 | ||
|
|
2eaff98d8a | ||
|
|
1759112d72 | ||
|
|
85d6074e92 | ||
|
|
2f6f3a0947 | ||
|
|
4284b9d77b | ||
|
|
b149019318 | ||
|
|
e2ebb602dc | ||
|
|
2d76cf9040 | ||
|
|
e7d0899e76 | ||
|
|
88001cfe49 | ||
|
|
a9c388a613 | ||
|
|
4ff1cf7979 | ||
|
|
e78a984b4e | ||
|
|
e37bd8e994 | ||
|
|
a94cf80dbd | ||
|
|
f789e7663a | ||
|
|
a51f4a0031 | ||
|
|
d06a15ecbb | ||
|
|
61fddefcb1 | ||
|
|
14fe9ee2b9 | ||
|
|
4ca407dbf0 | ||
|
|
f714e5d810 | ||
|
|
a328250be1 | ||
|
|
928704ad5c | ||
|
|
acbbf2039f | ||
|
|
4375e7331f | ||
|
|
22d736cb24 | ||
|
|
bb89561a1b | ||
|
|
c92081b056 | ||
|
|
dc57b850d3 | ||
|
|
6429a238c1 | ||
|
|
769019dd0b | ||
|
|
488a3af154 | ||
|
|
8a29e36934 | ||
|
|
ea98087d41 | ||
|
|
030349bb92 | ||
|
|
17d40afda3 | ||
|
|
bc6309ddc7 | ||
|
|
f47eda6842 | ||
|
|
2328985699 | ||
|
|
18b7f9104f | ||
|
|
82be4fa53a | ||
|
|
dbb2614306 | ||
|
|
b8f496603c | ||
|
|
4c782ad64d | ||
|
|
af6a306955 | ||
|
|
4b319c353d | ||
|
|
aadaf209b5 | ||
|
|
df6f8e2646 | ||
|
|
5b871ac281 | ||
|
|
7eed8e61db | ||
|
|
6ef2f3b37a | ||
|
|
596aaa5495 | ||
|
|
89eeac1f8a | ||
|
|
c331d189bb | ||
|
|
f867799301 | ||
|
|
597431ec1d | ||
|
|
d5ff9f2f5d | ||
|
|
76694f0cec | ||
|
|
65a8c39ce0 | ||
|
|
ac3a95ebbb |
@@ -172,6 +172,8 @@ See our [open source page](https://thirdweb.com/open-source) for more informatio
|
||||
- [Documentation](https://portal.thirdweb.com/)
|
||||
- [Templates](https://thirdweb.com/templates)
|
||||
- [YouTube](https://www.youtube.com/c/thirdweb)
|
||||
- [X/Twitter](https://x.com/thirdweb)
|
||||
- [Telegram](https://t.me/officialthirdweb)
|
||||
|
||||
## Support
|
||||
|
||||
|
||||
@@ -95,6 +95,30 @@ module.exports = {
|
||||
'Import "posthog-js" directly only within the analytics helpers ("src/@/analytics/*"). Use the exported helpers from "@/analytics/track" elsewhere.',
|
||||
name: "posthog-js",
|
||||
},
|
||||
{
|
||||
importNames: ["useSendTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead',
|
||||
name: "thirdweb/react",
|
||||
},
|
||||
{
|
||||
importNames: ["useSendAndConfirmTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead',
|
||||
name: "thirdweb/react",
|
||||
},
|
||||
{
|
||||
importNames: ["sendTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead if used in react component',
|
||||
name: "thirdweb",
|
||||
},
|
||||
{
|
||||
importNames: ["sendAndConfirmTransaction"],
|
||||
message:
|
||||
'Use `import { useSendAndConfirmTx } from "@/hooks/useSendTx";` instead if used in react component',
|
||||
name: "thirdweb",
|
||||
},
|
||||
],
|
||||
patterns: [
|
||||
{
|
||||
|
||||
@@ -64,4 +64,5 @@ module.exports = [
|
||||
"/enterprise",
|
||||
"/token",
|
||||
"/vault",
|
||||
"/monetize/bridge",
|
||||
];
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
],
|
||||
"ignoreBinaries": ["only-allow"],
|
||||
"ignoreDependencies": [
|
||||
"@thirdweb-dev/api",
|
||||
"@thirdweb-dev/service-utils",
|
||||
"@thirdweb-dev/vault-sdk",
|
||||
"thirdweb",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@shazow/whatsabi": "0.22.2",
|
||||
"@tanstack/react-query": "5.81.5",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@thirdweb-dev/api": "workspace:*",
|
||||
"@thirdweb-dev/service-utils": "workspace:*",
|
||||
"@thirdweb-dev/vault-sdk": "workspace:*",
|
||||
"@vercel/functions": "2.2.2",
|
||||
@@ -34,6 +35,7 @@
|
||||
"compare-versions": "^6.1.0",
|
||||
"date-fns": "4.1.0",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"fetch-event-stream": "0.1.5",
|
||||
"fuse.js": "7.1.0",
|
||||
"input-otp": "^1.4.1",
|
||||
"ioredis": "^5.6.1",
|
||||
|
||||
@@ -14,6 +14,7 @@ type ProxyActionParams = {
|
||||
body?: string;
|
||||
headers?: Record<string, string>;
|
||||
parseAsText?: boolean;
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
type ProxyActionResult<T> =
|
||||
|
||||
@@ -250,6 +250,54 @@ export function reportAssetBuySuccessful(properties: {
|
||||
});
|
||||
}
|
||||
|
||||
type TokenSwapParams = {
|
||||
buyTokenChainId: number;
|
||||
buyTokenAddress: string;
|
||||
sellTokenChainId: number;
|
||||
sellTokenAddress: string;
|
||||
pageType: "asset" | "bridge" | "chain";
|
||||
};
|
||||
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - To track number of successful token swaps from the token page
|
||||
* - To track which tokens are being swapped the most
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @MananTank
|
||||
*/
|
||||
export function reportTokenSwapSuccessful(properties: TokenSwapParams) {
|
||||
posthog.capture("token swap successful", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - To track number of failed token swaps from the token page
|
||||
* - To track which tokens are being swapped the most
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @MananTank
|
||||
*/
|
||||
export function reportTokenSwapFailed(
|
||||
properties: TokenSwapParams & {
|
||||
errorMessage: string;
|
||||
},
|
||||
) {
|
||||
posthog.capture("token swap failed", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - To track number of cancelled token swaps from the token page
|
||||
* - To track which tokens are being swapped the most
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @MananTank
|
||||
*/
|
||||
export function reportTokenSwapCancelled(properties: TokenSwapParams) {
|
||||
posthog.capture("token swap cancelled", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - To track number of failed asset purchases from the token page
|
||||
@@ -272,6 +320,26 @@ export function reportAssetBuyFailed(properties: {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - To track number of cancelled asset purchases from the token page
|
||||
* - To track the errors that users encounter when trying to purchase an asset
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @MananTank
|
||||
*/
|
||||
export function reportAssetBuyCancelled(properties: {
|
||||
chainId: number;
|
||||
contractType: AssetContractType;
|
||||
assetType: "nft" | "coin";
|
||||
}) {
|
||||
posthog.capture("asset buy cancelled", {
|
||||
assetType: properties.assetType,
|
||||
chainId: properties.chainId,
|
||||
contractType: properties.contractType,
|
||||
});
|
||||
}
|
||||
|
||||
// Assets Landing Page ----------------------------
|
||||
|
||||
/**
|
||||
@@ -547,3 +615,27 @@ export function reportChainInfraRpcOmissionAgreed(properties: {
|
||||
}) {
|
||||
posthog.capture("chain infra checkout rpc omission agreed", properties);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// FEEDBACK
|
||||
// ----------------------------
|
||||
|
||||
/**
|
||||
* ### Why do we need to report this event?
|
||||
* - To track user feedback and sentiment about the product
|
||||
* - To identify common issues or feature requests
|
||||
* - To measure user satisfaction and engagement
|
||||
* - To prioritize product improvements based on user input
|
||||
*
|
||||
* ### Who is responsible for this event?
|
||||
* @gisellechacon
|
||||
*/
|
||||
export function reportProductFeedback(properties: {
|
||||
feedback: string;
|
||||
source: "desktop" | "mobile";
|
||||
}) {
|
||||
posthog.capture("product feedback submitted", {
|
||||
feedback: properties.feedback,
|
||||
source: properties.source,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -297,38 +297,53 @@ export type PaymentsResponse = {
|
||||
totalCount: number;
|
||||
};
|
||||
};
|
||||
|
||||
type BridgePaymentType = "buy" | "sell" | "transfer";
|
||||
type OnrampPaymentType = "onramp";
|
||||
|
||||
export type Payment = {
|
||||
// common
|
||||
id: string;
|
||||
blockNumber?: bigint;
|
||||
transactionId: string;
|
||||
createdAt: string;
|
||||
clientId: string;
|
||||
sender: string;
|
||||
receiver: string;
|
||||
developerFeeRecipient: string;
|
||||
developerFeeBps: number;
|
||||
transactions: Array<{
|
||||
chainId: number;
|
||||
transactionHash: string;
|
||||
}>;
|
||||
status: "PENDING" | "COMPLETED" | "FAILED" | "NOT_FOUND";
|
||||
type: "buy" | "sell" | "transfer";
|
||||
originAmount: bigint;
|
||||
destinationAmount: bigint;
|
||||
purchaseData: unknown;
|
||||
originToken: {
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
chainId: number;
|
||||
};
|
||||
|
||||
destinationAmount: string;
|
||||
destinationToken: {
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
chainId: number;
|
||||
};
|
||||
createdAt: string;
|
||||
};
|
||||
purchaseData: unknown;
|
||||
} & (
|
||||
| {
|
||||
type: BridgePaymentType;
|
||||
transactionId: string;
|
||||
blockNumber?: string;
|
||||
sender: string;
|
||||
developerFeeRecipient: string;
|
||||
developerFeeBps: number;
|
||||
originAmount: string;
|
||||
originToken: {
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
chainId: number;
|
||||
};
|
||||
}
|
||||
| {
|
||||
onrampId: string;
|
||||
type: OnrampPaymentType;
|
||||
}
|
||||
);
|
||||
|
||||
export type BridgePayment = Extract<Payment, { type: BridgePaymentType }>;
|
||||
|
||||
export async function getPayments(props: {
|
||||
clientId: string;
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { useState } from "react";
|
||||
import type { Chain, ThirdwebClient } from "thirdweb";
|
||||
import { BuyWidget, SwapWidget } from "thirdweb/react";
|
||||
import {
|
||||
reportAssetBuyCancelled,
|
||||
reportAssetBuyFailed,
|
||||
reportAssetBuySuccessful,
|
||||
reportTokenSwapCancelled,
|
||||
reportTokenSwapFailed,
|
||||
reportTokenSwapSuccessful,
|
||||
} from "@/analytics/report";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
|
||||
export function BuyAndSwapEmbed(props: {
|
||||
client: ThirdwebClient;
|
||||
chain: Chain;
|
||||
tokenAddress: string | undefined;
|
||||
buyAmount: string | undefined;
|
||||
pageType: "asset" | "bridge" | "chain";
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
const [tab, setTab] = useState<"buy" | "swap">("swap");
|
||||
const themeObj = getSDKTheme(theme === "light" ? "light" : "dark");
|
||||
return (
|
||||
<div className="bg-card rounded-2xl border overflow-hidden flex flex-col">
|
||||
<div className="flex gap-2.5 p-4 border-b border-dashed">
|
||||
<TabButton
|
||||
label="Swap"
|
||||
onClick={() => setTab("swap")}
|
||||
isActive={tab === "swap"}
|
||||
/>
|
||||
<TabButton
|
||||
label="Buy"
|
||||
onClick={() => setTab("buy")}
|
||||
isActive={tab === "buy"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{tab === "buy" && (
|
||||
<BuyWidget
|
||||
amount={props.buyAmount || "1"}
|
||||
chain={props.chain}
|
||||
className="!rounded-2xl !w-full !border-none"
|
||||
title=""
|
||||
client={props.client}
|
||||
connectOptions={{
|
||||
autoConnect: false,
|
||||
}}
|
||||
onError={(e) => {
|
||||
const errorMessage = parseError(e);
|
||||
if (props.pageType === "asset") {
|
||||
reportAssetBuyFailed({
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: "DropERC20",
|
||||
error: errorMessage,
|
||||
});
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
if (props.pageType === "asset") {
|
||||
reportAssetBuyCancelled({
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: "DropERC20",
|
||||
});
|
||||
}
|
||||
}}
|
||||
onSuccess={() => {
|
||||
if (props.pageType === "asset") {
|
||||
reportAssetBuySuccessful({
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: "DropERC20",
|
||||
});
|
||||
}
|
||||
}}
|
||||
theme={themeObj}
|
||||
tokenAddress={props.tokenAddress as `0x${string}`}
|
||||
paymentMethods={["card"]}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tab === "swap" && (
|
||||
<SwapWidget
|
||||
client={props.client}
|
||||
theme={themeObj}
|
||||
className="!rounded-2xl !border-none !w-full"
|
||||
prefill={{
|
||||
sellToken: {
|
||||
chainId: props.chain.id,
|
||||
tokenAddress: props.tokenAddress,
|
||||
},
|
||||
// only set `buyToken` as "Native token" if `sellToken` is not a "native token" already
|
||||
buyToken: props.tokenAddress
|
||||
? {
|
||||
chainId: props.chain.id,
|
||||
}
|
||||
: undefined,
|
||||
}}
|
||||
onError={(error, quote) => {
|
||||
const errorMessage = parseError(error);
|
||||
reportTokenSwapFailed({
|
||||
errorMessage: errorMessage,
|
||||
buyTokenChainId: quote.intent.destinationChainId,
|
||||
buyTokenAddress: quote.intent.destinationTokenAddress,
|
||||
sellTokenChainId: quote.intent.originChainId,
|
||||
sellTokenAddress: quote.intent.originTokenAddress,
|
||||
pageType: props.pageType,
|
||||
});
|
||||
}}
|
||||
onSuccess={(quote) => {
|
||||
reportTokenSwapSuccessful({
|
||||
buyTokenChainId: quote.intent.destinationChainId,
|
||||
buyTokenAddress: quote.intent.destinationTokenAddress,
|
||||
sellTokenChainId: quote.intent.originChainId,
|
||||
sellTokenAddress: quote.intent.originTokenAddress,
|
||||
pageType: props.pageType,
|
||||
});
|
||||
}}
|
||||
onCancel={(quote) => {
|
||||
reportTokenSwapCancelled({
|
||||
buyTokenChainId: quote.intent.destinationChainId,
|
||||
buyTokenAddress: quote.intent.destinationTokenAddress,
|
||||
sellTokenChainId: quote.intent.originChainId,
|
||||
sellTokenAddress: quote.intent.originTokenAddress,
|
||||
pageType: props.pageType,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TabButton(props: {
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
isActive: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
onClick={props.onClick}
|
||||
className={cn(
|
||||
"rounded-full text-muted-foreground px-5 text-base bg-accent",
|
||||
props.isActive && "text-foreground border-foreground",
|
||||
)}
|
||||
variant="outline"
|
||||
>
|
||||
{props.label}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { type Dispatch, type SetStateAction, useMemo } from "react";
|
||||
import { CodeClient } from "@/components/ui/code/code.client";
|
||||
@@ -6,11 +7,14 @@ import { TabButtons } from "@/components/ui/tabs";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type CodeEnvironment =
|
||||
| "api"
|
||||
| "javascript"
|
||||
| "typescript"
|
||||
| "react"
|
||||
| "react-native"
|
||||
| "unity";
|
||||
| "dotnet"
|
||||
| "unity"
|
||||
| "curl";
|
||||
|
||||
type SupportedEnvironment = {
|
||||
environment: CodeEnvironment;
|
||||
@@ -20,6 +24,10 @@ type SupportedEnvironment = {
|
||||
type CodeSnippet = Partial<Record<CodeEnvironment, string>>;
|
||||
|
||||
const Environments: SupportedEnvironment[] = [
|
||||
{
|
||||
environment: "api",
|
||||
title: "API",
|
||||
},
|
||||
{
|
||||
environment: "javascript",
|
||||
title: "JavaScript",
|
||||
@@ -36,10 +44,18 @@ const Environments: SupportedEnvironment[] = [
|
||||
environment: "react-native",
|
||||
title: "React Native",
|
||||
},
|
||||
{
|
||||
environment: "dotnet",
|
||||
title: ".NET",
|
||||
},
|
||||
{
|
||||
environment: "unity",
|
||||
title: "Unity",
|
||||
},
|
||||
{
|
||||
environment: "curl",
|
||||
title: "cURL",
|
||||
},
|
||||
];
|
||||
|
||||
interface CodeSegmentProps {
|
||||
@@ -122,9 +138,14 @@ export const CodeSegment: React.FC<CodeSegmentProps> = ({
|
||||
: activeEnvironment === "react" ||
|
||||
activeEnvironment === "react-native"
|
||||
? "tsx"
|
||||
: activeEnvironment === "unity"
|
||||
: activeEnvironment === "unity" ||
|
||||
activeEnvironment === "dotnet"
|
||||
? "cpp"
|
||||
: activeEnvironment
|
||||
: activeEnvironment === "api"
|
||||
? "javascript"
|
||||
: activeEnvironment === "curl"
|
||||
? "bash"
|
||||
: activeEnvironment
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { ChevronDownIcon, ChevronRightIcon } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useMemo } from "react";
|
||||
import { AppFooter } from "@/components/footers/app-footer";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
@@ -91,7 +90,6 @@ export function FullWidthSidebarLayout(props: {
|
||||
<main className="flex min-w-0 grow flex-col max-sm:w-full">
|
||||
{children}
|
||||
</main>
|
||||
<AppFooter />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { GridPattern } from "@/components/ui/background-patterns";
|
||||
|
||||
export function GridPatternEmbedContainer(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className=" sm:flex sm:justify-center w-full sm:border sm:border-dashed sm:bg-accent/20 sm:py-12 rounded-lg overflow-hidden relative">
|
||||
<GridPattern
|
||||
width={30}
|
||||
height={30}
|
||||
x={-1}
|
||||
y={-1}
|
||||
strokeDasharray={"4 2"}
|
||||
className="text-border dark:text-border/70 hidden lg:block"
|
||||
style={{
|
||||
maskImage:
|
||||
"linear-gradient(to bottom right,white,transparent,transparent)",
|
||||
}}
|
||||
/>
|
||||
<div className="sm:w-[420px] z-10">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ type Action =
|
||||
component: React.ReactNode;
|
||||
};
|
||||
|
||||
function Action(props: { action: Action; variant?: "default" | "secondary" }) {
|
||||
function Action(props: { action: Action; variant?: "default" | "outline" }) {
|
||||
const action = props.action;
|
||||
|
||||
return "component" in action ? (
|
||||
@@ -31,10 +31,7 @@ function Action(props: { action: Action; variant?: "default" | "secondary" }) {
|
||||
) : (
|
||||
<Button
|
||||
asChild
|
||||
className={cn(
|
||||
"rounded-full",
|
||||
props.variant === "secondary" && "border border-border",
|
||||
)}
|
||||
className={cn("rounded-full")}
|
||||
size="sm"
|
||||
variant={props.variant}
|
||||
>
|
||||
@@ -60,7 +57,7 @@ export type ProjectPageHeaderProps = {
|
||||
icon: React.FC<{ className?: string }>;
|
||||
isProjectIcon?: boolean;
|
||||
actions: {
|
||||
primary: Action;
|
||||
primary?: Action;
|
||||
secondary?: Action;
|
||||
} | null;
|
||||
|
||||
@@ -114,7 +111,7 @@ export function ProjectPageHeader(props: ProjectPageHeaderProps) {
|
||||
{props.actions && (
|
||||
<div className="hidden lg:flex items-center gap-3">
|
||||
{props.actions.secondary && (
|
||||
<Action action={props.actions.secondary} variant="secondary" />
|
||||
<Action action={props.actions.secondary} variant="outline" />
|
||||
)}
|
||||
|
||||
{props.actions.primary && (
|
||||
@@ -132,7 +129,7 @@ export function ProjectPageHeader(props: ProjectPageHeaderProps) {
|
||||
{props.title}
|
||||
</h2>
|
||||
{/* description */}
|
||||
<p className="text-sm text-muted-foreground line-clamp-3 md:line-clamp-2">
|
||||
<p className="text-sm text-muted-foreground text-pretty">
|
||||
{props.description}
|
||||
</p>
|
||||
</div>
|
||||
@@ -145,7 +142,7 @@ export function ProjectPageHeader(props: ProjectPageHeaderProps) {
|
||||
)}
|
||||
|
||||
{props.actions?.secondary && (
|
||||
<Action action={props.actions.secondary} variant="secondary" />
|
||||
<Action action={props.actions.secondary} variant="outline" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -87,7 +87,7 @@ export function ChatBar(props: {
|
||||
<PopoverContent className="w-72">
|
||||
<div>
|
||||
<p className="mb-3 text-muted-foreground text-sm">
|
||||
Get access to image uploads by signing in to Nebula
|
||||
Get access to image uploads by signing in to thirdweb
|
||||
</p>
|
||||
<Button
|
||||
className="w-full"
|
||||
|
||||
@@ -28,7 +28,10 @@ export function CustomChatButton(props: {
|
||||
const closeModal = useCallback(() => setIsOpen(false), []);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
if (layoutSegments[0] === "~" && layoutSegments[1] === "support") {
|
||||
if (
|
||||
(layoutSegments[0] === "~" && layoutSegments[1] === "support") ||
|
||||
layoutSegments.includes("ai")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,23 @@ export default function Component() {
|
||||
events: [preparedEvent]
|
||||
});
|
||||
}`,
|
||||
unity: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = await ThirdwebManager.Instance.GetContract(
|
||||
address: "{{contract_address}}",
|
||||
chainId: {{chainId}},
|
||||
);
|
||||
|
||||
// Listen to contract events
|
||||
var events = await contract.Events("{{function}}");`,
|
||||
dotnet: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = await ThirdwebContract.Create(client, "{{contract_address}}", {{chainId}});
|
||||
|
||||
// Listen to contract events
|
||||
var events = await contract.Events("{{function}}");`,
|
||||
},
|
||||
install: {
|
||||
javascript: "npm i thirdweb",
|
||||
@@ -73,6 +90,9 @@ export default function Component() {
|
||||
unity: `// Download the .unitypackage from the latest release:
|
||||
// https://github.com/thirdweb-dev/unity-sdk/releases
|
||||
// and drag it into your project`,
|
||||
dotnet: `// Install the thirdweb SDK via NuGet:
|
||||
// dotnet add package Thirdweb
|
||||
// Or search for "Thirdweb" in your NuGet package manager`,
|
||||
},
|
||||
read: {
|
||||
javascript: `import { readContract } from "thirdweb";
|
||||
@@ -100,6 +120,33 @@ export default function Component() {
|
||||
params: [{{args}}]
|
||||
});
|
||||
}`,
|
||||
unity: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = await ThirdwebManager.Instance.GetContract(
|
||||
address: "{{contract_address}}",
|
||||
chainId: {{chainId}}
|
||||
);
|
||||
|
||||
// Read from the contract
|
||||
var result = await contract.Read<T>(
|
||||
"{{function}}",
|
||||
{{args}}
|
||||
);`,
|
||||
dotnet: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = await ThirdwebContract.Create(
|
||||
client,
|
||||
"{{contract_address}}",
|
||||
{{chainId}}
|
||||
);
|
||||
|
||||
// Read from the contract
|
||||
var result = await contract.Read<T>(
|
||||
"{{function}}",
|
||||
{{args}}
|
||||
);`,
|
||||
},
|
||||
setup: {
|
||||
javascript: `import { createThirdwebClient, getContract } from "thirdweb";
|
||||
@@ -165,11 +212,19 @@ function App() {
|
||||
`,
|
||||
unity: `using Thirdweb;
|
||||
|
||||
// Reference the SDK
|
||||
var sdk = ThirdwebManager.Instance.SDK;
|
||||
// Get your contract
|
||||
var contract = await ThirdwebManager.Instance.GetContract(
|
||||
address: "{{contract_address}}",
|
||||
chainId: {{chainId}}
|
||||
);`,
|
||||
dotnet: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = sdk.GetContract("{{contract_address}}");`,
|
||||
var contract = await ThirdwebContract.Create(
|
||||
client,
|
||||
"{{contract_address}}",
|
||||
{{chainId}}
|
||||
);`,
|
||||
},
|
||||
write: {
|
||||
javascript: `import { prepareContractCall, sendTransaction } from "thirdweb";
|
||||
@@ -213,6 +268,123 @@ export default function Component() {
|
||||
sendTransaction(transaction);
|
||||
}
|
||||
}`,
|
||||
unity: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = await ThirdwebManager.Instance.GetContract(
|
||||
address: "{{contract_address}}",
|
||||
chainId: {{chainId}}
|
||||
);
|
||||
|
||||
// Write to the contract
|
||||
var transactionReceipt = await contract.Write(
|
||||
wallet,
|
||||
contract,
|
||||
"{{function}}",
|
||||
weiValue,
|
||||
{{args}}
|
||||
);`,
|
||||
dotnet: `using Thirdweb;
|
||||
|
||||
// Get your contract
|
||||
var contract = await ThirdwebContract.Create(
|
||||
client,
|
||||
"{{contract_address}}",
|
||||
{{chainId}}
|
||||
);
|
||||
|
||||
// Write to the contract
|
||||
var transactionReceipt = await contract.Write(
|
||||
wallet,
|
||||
contract,
|
||||
"{{function}}",
|
||||
weiValue,
|
||||
{{args}}
|
||||
);`,
|
||||
},
|
||||
api: {
|
||||
read: `const response = await fetch('https://api.thirdweb.com/v1/contracts/read', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-secret-key': '<YOUR_SECRET_KEY>'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
calls: [
|
||||
{
|
||||
contractAddress: "{{contract_address}}",
|
||||
method: "{{function}}",
|
||||
params: [{{args}}]
|
||||
}
|
||||
],
|
||||
chainId: {{chainId}}
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();`,
|
||||
write: `const response = await fetch('https://api.thirdweb.com/v1/contracts/write', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-secret-key': '<YOUR_SECRET_KEY>'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
calls: [
|
||||
{
|
||||
contractAddress: "{{contract_address}}",
|
||||
method: "{{function}}",
|
||||
params: [{{args}}]
|
||||
}
|
||||
],
|
||||
chainId: {{chainId}},
|
||||
from: "<YOUR_WALLET_ADDRESS>"
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();`,
|
||||
events: `const response = await fetch('https://api.thirdweb.com/v1/contracts/{{chainId}}/{{contract_address}}/events?eventSignature={{function}}', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-secret-key': '<YOUR_SECRET_KEY>'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();`,
|
||||
},
|
||||
curl: {
|
||||
read: `curl https://api.thirdweb.com/v1/contracts/read \\
|
||||
--request POST \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
--header 'x-secret-key: <YOUR_SECRET_KEY>' \\
|
||||
--data '{
|
||||
"calls": [
|
||||
{
|
||||
"contractAddress": "{{contract_address}}",
|
||||
"method": "{{function}}",
|
||||
"params": [{{args}}]
|
||||
}
|
||||
],
|
||||
"chainId": {{chainId}}
|
||||
}'`,
|
||||
write: `curl -X POST https://api.thirdweb.com/v1/contracts/write \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "x-secret-key: <YOUR_SECRET_KEY>" \\
|
||||
-d '{
|
||||
"calls": [
|
||||
{
|
||||
"contractAddress": "{{contract_address}}",
|
||||
"method": "{{function}}",
|
||||
"params": [{{args}}]
|
||||
}
|
||||
],
|
||||
"chainId": {{chainId}},
|
||||
"from": "<YOUR_WALLET_ADDRESS>"
|
||||
}'`,
|
||||
events: `curl https://api.thirdweb.com/v1/contracts/{{chainId}}/{{contract_address}}/events?eventSignature={{function}} \\
|
||||
--request GET \\
|
||||
--header 'Content-Type: application/json' \\
|
||||
--header 'x-secret-key: <YOUR_SECRET_KEY>'`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -269,20 +441,17 @@ return (
|
||||
|
||||
public async void ConnectWallet()
|
||||
{
|
||||
// Reference to your Thirdweb SDK
|
||||
var sdk = ThirdwebManager.Instance.SDK;
|
||||
|
||||
// Configure the connection
|
||||
var connection = new WalletConnection(
|
||||
provider: WalletProvider.SmartWallet, // The wallet provider you want to connect to (Required)
|
||||
chainId: 1, // The chain you want to connect to (Required)
|
||||
password: "myEpicPassword", // If using a local wallet as personal wallet (Optional)
|
||||
email: "[email protected]", // If using an email wallet as personal wallet (Optional)
|
||||
personalWallet: WalletProvider.LocalWallet // The personal wallet you want to use with your Account (Optional)
|
||||
var wallet = await ConnectWallet(
|
||||
new WalletOptions(
|
||||
provider: WalletProvider.InAppWallet,
|
||||
chainId: {{chainId}},
|
||||
inAppWalletOptions: new InAppWalletOptions(
|
||||
authprovider: AuthProvider.Google,
|
||||
executionMode: ExecutionMode.EIP7702Sponsored
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Connect the wallet
|
||||
string address = await sdk.wallet.Connect(connection);
|
||||
string address = await wallet.GetAddress();
|
||||
}`,
|
||||
},
|
||||
},
|
||||
@@ -490,6 +659,9 @@ export function formatSnippet(
|
||||
: "event",
|
||||
});
|
||||
break;
|
||||
case "api":
|
||||
// For API, we don't need special extension handling, just use the standard templates
|
||||
break;
|
||||
}
|
||||
}
|
||||
// end hacks on hacks on hacks -- now just hacks on hacks from here on out
|
||||
@@ -533,7 +705,7 @@ export function CodeOverview(props: {
|
||||
| undefined;
|
||||
|
||||
const [environment, setEnvironment] = useState<CodeEnvironment>(
|
||||
defaultEnvironment || "javascript",
|
||||
defaultEnvironment || "api",
|
||||
);
|
||||
|
||||
const [tab, setTab] = useState("write");
|
||||
@@ -677,7 +849,13 @@ export function CodeOverview(props: {
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-2">
|
||||
{environment === "react-native" || environment === "unity" ? (
|
||||
{environment === "api" || environment === "curl" ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No SDK install required for API/cURL. Call the HTTP endpoints
|
||||
directly. Keep your secret key server-side; use client IDs in
|
||||
frontends.
|
||||
</p>
|
||||
) : environment === "react-native" || environment === "unity" ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Install the latest version of the SDK.{" "}
|
||||
<UnderlineLink
|
||||
@@ -800,32 +978,76 @@ export function CodeOverview(props: {
|
||||
<CodeSegment
|
||||
environment={environment}
|
||||
setEnvironment={setEnvironment}
|
||||
snippet={formatSnippet(
|
||||
// biome-ignore lint/suspicious/noExplicitAny: FIXME
|
||||
COMMANDS[tab as keyof typeof COMMANDS] as any,
|
||||
{
|
||||
snippet={(() => {
|
||||
const selectedFn =
|
||||
tab === "read" ? read : tab === "write" ? write : event;
|
||||
const commandsKey = tab as "read" | "write" | "events";
|
||||
|
||||
const baseSnippet = formatSnippet(COMMANDS[commandsKey], {
|
||||
args:
|
||||
abi
|
||||
?.filter(
|
||||
(f) => f.type === "function" || f.type === "event",
|
||||
)
|
||||
?.find(
|
||||
(f) =>
|
||||
f.name ===
|
||||
(tab === "read"
|
||||
? read?.name
|
||||
: tab === "write"
|
||||
? write?.name
|
||||
: event?.name),
|
||||
)
|
||||
?.find((f) => f.name === selectedFn?.name)
|
||||
?.inputs.map((i) => i.name || "") || [],
|
||||
|
||||
chainId,
|
||||
contractAddress,
|
||||
extensionNamespace,
|
||||
fn: tab === "read" ? read : tab === "write" ? write : event,
|
||||
},
|
||||
)}
|
||||
fn: selectedFn,
|
||||
});
|
||||
|
||||
// Add API snippet if it exists
|
||||
const apiSnippet = COMMANDS.api[commandsKey]
|
||||
? formatSnippet(
|
||||
{ api: COMMANDS.api[commandsKey] },
|
||||
{
|
||||
args:
|
||||
abi
|
||||
?.filter(
|
||||
(f) =>
|
||||
f.type === "function" || f.type === "event",
|
||||
)
|
||||
?.find((f) => f.name === selectedFn?.name)
|
||||
?.inputs.map((i) => i.name || "") || [],
|
||||
|
||||
chainId,
|
||||
contractAddress,
|
||||
extensionNamespace,
|
||||
fn: selectedFn,
|
||||
},
|
||||
)
|
||||
: {};
|
||||
|
||||
// Add cURL snippet if it exists
|
||||
const curlSnippet = COMMANDS.curl[commandsKey]
|
||||
? formatSnippet(
|
||||
{ curl: COMMANDS.curl[commandsKey] },
|
||||
{
|
||||
args:
|
||||
abi
|
||||
?.filter(
|
||||
(f) =>
|
||||
f.type === "function" || f.type === "event",
|
||||
)
|
||||
?.find((f) => f.name === selectedFn?.name)
|
||||
?.inputs.map((i) => i.name || "") || [],
|
||||
|
||||
chainId,
|
||||
contractAddress,
|
||||
extensionNamespace,
|
||||
fn: selectedFn,
|
||||
},
|
||||
)
|
||||
: {};
|
||||
|
||||
return {
|
||||
...baseSnippet,
|
||||
...apiSnippet,
|
||||
...curlSnippet,
|
||||
};
|
||||
})()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ const ContractFunction: React.FC<{
|
||||
|
||||
function ContractFunctionInner(props: ContractFunctionProps) {
|
||||
const { contract, fn } = props;
|
||||
const [environment, setEnvironment] = useState<CodeEnvironment>("javascript");
|
||||
const [environment, setEnvironment] = useState<CodeEnvironment>("api");
|
||||
const functionSelectorQuery = useContractFunctionSelectors(contract);
|
||||
const functionSelectors = functionSelectorQuery.data || [];
|
||||
const isERC721Query = useReadContract(ERC721Ext.isERC721, { contract });
|
||||
@@ -112,7 +112,7 @@ function ContractFunctionInner(props: ContractFunctionProps) {
|
||||
: "write"
|
||||
: ("events" as const);
|
||||
|
||||
const codeSnippet = formatSnippet(COMMANDS[commandsKey], {
|
||||
const baseSnippet = formatSnippet(COMMANDS[commandsKey], {
|
||||
args: fn.inputs?.map((i) => i.name || ""),
|
||||
chainId: contract.chain.id,
|
||||
contractAddress: contract.address,
|
||||
@@ -120,6 +120,40 @@ function ContractFunctionInner(props: ContractFunctionProps) {
|
||||
fn,
|
||||
});
|
||||
|
||||
// Safety check: Only include API snippet if the command exists in COMMANDS.api
|
||||
const apiSnippet = COMMANDS.api[commandsKey]
|
||||
? formatSnippet(
|
||||
{ api: COMMANDS.api[commandsKey] },
|
||||
{
|
||||
args: fn.inputs?.map((i) => i.name || ""),
|
||||
chainId: contract.chain.id,
|
||||
contractAddress: contract.address,
|
||||
extensionNamespace,
|
||||
fn,
|
||||
},
|
||||
)
|
||||
: {};
|
||||
|
||||
// Safety check: Only include cURL snippet if the command exists in COMMANDS.curl
|
||||
const curlSnippet = COMMANDS.curl[commandsKey]
|
||||
? formatSnippet(
|
||||
{ curl: COMMANDS.curl[commandsKey] },
|
||||
{
|
||||
args: fn.inputs?.map((i) => i.name || ""),
|
||||
chainId: contract.chain.id,
|
||||
contractAddress: contract.address,
|
||||
extensionNamespace,
|
||||
fn,
|
||||
},
|
||||
)
|
||||
: {};
|
||||
|
||||
const codeSnippet = {
|
||||
...baseSnippet,
|
||||
...apiSnippet,
|
||||
...curlSnippet,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="flex items-center flex-wrap gap-2 border-b pb-3 mb-3">
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
toSerializableTransaction,
|
||||
toWei,
|
||||
} from "thirdweb";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { parseAbiParams, stringify, toFunctionSelector } from "thirdweb/utils";
|
||||
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
|
||||
import { SolidityInput } from "@/components/solidity-inputs";
|
||||
@@ -34,6 +34,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Spinner } from "@/components/ui/Spinner";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
|
||||
function formatResponseData(data: unknown): {
|
||||
type: "json" | "text";
|
||||
@@ -264,7 +265,7 @@ export const InteractiveAbiFunction: React.FC<InteractiveAbiFunctionProps> = (
|
||||
data: mutationData,
|
||||
error: mutationError,
|
||||
isPending: mutationLoading,
|
||||
} = useSendAndConfirmTransaction();
|
||||
} = useSendAndConfirmTx();
|
||||
|
||||
const {
|
||||
mutate: readFn,
|
||||
|
||||
@@ -231,13 +231,13 @@ function ImportForm(props: {
|
||||
</Button>
|
||||
) : (
|
||||
<Button className="gap-2" type="submit">
|
||||
{addContractToProject.isPending ? (
|
||||
{form.formState.isSubmitting ? (
|
||||
<Spinner className="size-4" />
|
||||
) : (
|
||||
<ArrowDownToLineIcon className="size-4" />
|
||||
)}
|
||||
|
||||
{addContractToProject.isPending ? "Importing" : "Import"}
|
||||
{form.formState.isSubmitting ? "Importing" : "Import"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,79 @@
|
||||
import type {
|
||||
ListUserWalletsData,
|
||||
ListUserWalletsResponses,
|
||||
} from "@thirdweb-dev/api";
|
||||
import { configure, listUserWallets } from "@thirdweb-dev/api";
|
||||
import type { WalletUser } from "thirdweb/wallets";
|
||||
import { THIRDWEB_EWS_API_HOST } from "@/constants/urls";
|
||||
import { THIRDWEB_API_HOST } from "@/constants/urls";
|
||||
import type { SearchType } from "./types";
|
||||
|
||||
// Configure the API client to use the correct base URL
|
||||
configure({
|
||||
override: {
|
||||
baseUrl: THIRDWEB_API_HOST,
|
||||
},
|
||||
});
|
||||
|
||||
// Extract types from the generated API
|
||||
type APIWallet = ListUserWalletsResponses[200]["result"]["wallets"][0];
|
||||
type APIProfile = APIWallet["profiles"][0];
|
||||
|
||||
// Transform API response to match existing WalletUser format
|
||||
function transformToWalletUser(apiWallet: APIWallet): WalletUser {
|
||||
return {
|
||||
id: getProfileId(apiWallet.profiles[0]) || "",
|
||||
linkedAccounts: apiWallet.profiles.map((profile) => {
|
||||
// Create details object based on the profile data
|
||||
let details:
|
||||
| { email: string; [key: string]: string }
|
||||
| { phone: string; [key: string]: string }
|
||||
| { address: string; [key: string]: string }
|
||||
| { id: string; [key: string]: string };
|
||||
|
||||
const profileId = getProfileId(profile);
|
||||
|
||||
if ("email" in profile && profile.email) {
|
||||
details = { email: profile.email, id: profileId };
|
||||
} else if ("phone" in profile && profile.phone) {
|
||||
details = { phone: profile.phone, id: profileId };
|
||||
} else if ("walletAddress" in profile && profile.walletAddress) {
|
||||
details = { address: profile.walletAddress, id: profileId };
|
||||
} else {
|
||||
details = { id: profileId };
|
||||
}
|
||||
|
||||
return {
|
||||
type: profile.type,
|
||||
details,
|
||||
};
|
||||
}),
|
||||
wallets: apiWallet.address
|
||||
? [
|
||||
{
|
||||
address: apiWallet.address,
|
||||
createdAt: apiWallet.createdAt || new Date().toISOString(),
|
||||
type: "enclave" as const,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to safely get ID from any profile type
|
||||
function getProfileId(profile: APIProfile | undefined): string {
|
||||
if (!profile) return "";
|
||||
|
||||
if ("id" in profile) {
|
||||
return profile.id;
|
||||
} else if ("credentialId" in profile) {
|
||||
return profile.credentialId;
|
||||
} else if ("identifier" in profile) {
|
||||
return profile.identifier;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
export async function searchUsers(
|
||||
authToken: string,
|
||||
clientId: string | undefined,
|
||||
@@ -10,50 +82,57 @@ export async function searchUsers(
|
||||
searchType: SearchType,
|
||||
query: string,
|
||||
): Promise<WalletUser[]> {
|
||||
const url = new URL(`${THIRDWEB_EWS_API_HOST}/api/2024-05-05/account/list`);
|
||||
try {
|
||||
// Prepare query parameters
|
||||
const queryParams: ListUserWalletsData["query"] = {
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
// Add clientId or ecosystemSlug parameter
|
||||
if (ecosystemSlug) {
|
||||
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`);
|
||||
} else if (clientId) {
|
||||
url.searchParams.append("clientId", clientId);
|
||||
// Add search parameter based on search type
|
||||
switch (searchType) {
|
||||
case "email":
|
||||
queryParams.email = query;
|
||||
break;
|
||||
case "phone":
|
||||
queryParams.phone = query;
|
||||
break;
|
||||
case "id":
|
||||
queryParams.id = query;
|
||||
break;
|
||||
case "address":
|
||||
queryParams.address = query;
|
||||
break;
|
||||
case "externalWallet":
|
||||
queryParams.externalWalletAddress = query;
|
||||
break;
|
||||
}
|
||||
|
||||
// Use the generated API function with Bearer authentication
|
||||
const response = await listUserWallets({
|
||||
query: queryParams,
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"x-thirdweb-team-id": teamId,
|
||||
...(clientId && { "x-client-id": clientId }),
|
||||
...(ecosystemSlug && {
|
||||
"x-ecosystem-id": `ecosystem.${ecosystemSlug}`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Handle response
|
||||
if (response.error || !response.data) {
|
||||
console.error(
|
||||
"Error searching users:",
|
||||
response.error || "No data returned",
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.data.result.wallets.map(transformToWalletUser);
|
||||
} catch (error) {
|
||||
console.error("Error searching users:", error);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Add search parameter based on search type
|
||||
switch (searchType) {
|
||||
case "email":
|
||||
url.searchParams.append("email", query);
|
||||
break;
|
||||
case "phone":
|
||||
url.searchParams.append("phone", query);
|
||||
break;
|
||||
case "id":
|
||||
url.searchParams.append("id", query);
|
||||
break;
|
||||
case "address":
|
||||
url.searchParams.append("address", query);
|
||||
break;
|
||||
case "externalWallet":
|
||||
url.searchParams.append("externalWalletAddress", query);
|
||||
break;
|
||||
}
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
"Content-Type": "application/json",
|
||||
"x-thirdweb-team-id": teamId,
|
||||
...(clientId && { "x-client-id": clientId }),
|
||||
},
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to search users: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.users as WalletUser[];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { ThirdwebClient } from "thirdweb";
|
||||
import type { UseNetworkSwitcherModalOptions } from "thirdweb/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/Spinner";
|
||||
import { OPSponsoredChains } from "@/constants/chains";
|
||||
import { useAllChainsData } from "@/hooks/chains/allChains";
|
||||
import { ChainIconClient } from "@/icons/ChainIcon";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -32,7 +31,6 @@ export const CustomChainRenderer = ({
|
||||
const { idToChain } = useAllChainsData();
|
||||
const storedChain = idToChain.get(chain.id);
|
||||
const isDeprecated = storedChain?.status === "deprecated";
|
||||
const isSponsored = OPSponsoredChains.includes(chain.id);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[48px] w-full cursor-pointer justify-start rounded-lg px-2 py-1 hover:bg-accent">
|
||||
@@ -70,16 +68,6 @@ export const CustomChainRenderer = ({
|
||||
Deprecated
|
||||
</div>
|
||||
)}
|
||||
{isSponsored && (
|
||||
<div
|
||||
className="flex shrink-0 cursor-not-allowed items-center overflow-hidden rounded-xl px-2 py-1 font-medium text-xs"
|
||||
style={{
|
||||
background: "linear-gradient(to right, #701953, #5454B2)",
|
||||
}}
|
||||
>
|
||||
Sponsored
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{switching && (
|
||||
<div className="flex items-center gap-1 font-medium text-link-foreground text-xs">
|
||||
|
||||
@@ -56,7 +56,8 @@ export function NotificationsButton(props: { accountId: string }) {
|
||||
<PopoverTrigger asChild>{trigger}</PopoverTrigger>
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="max-h-[90vh] min-h-[500px] w-[400px] max-w-md p-0"
|
||||
sideOffset={8}
|
||||
className="max-h-[90vh] min-h-[550px] w-[450px] max-w-md p-0 flex flex-col rounded-xl overflow-hidden"
|
||||
>
|
||||
<NotificationList {...manager} />
|
||||
</PopoverContent>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
parseISO,
|
||||
subDays,
|
||||
} from "date-fns";
|
||||
import { ArchiveIcon } from "lucide-react";
|
||||
import { ArchiveIcon, ArrowUpRightIcon, BellIcon } from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import type { Notification } from "@/api/notifications";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -42,39 +42,48 @@ export function NotificationEntry({
|
||||
}, [notification.createdAt]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row py-1.5">
|
||||
{onMarkAsRead && (
|
||||
<div className="min-h-full w-1 shrink-0 rounded-r-lg bg-primary" />
|
||||
)}
|
||||
<div className="flex w-full flex-row justify-between gap-2 border-b px-4 py-2 transition-colors last:border-b-0">
|
||||
<div className="flex items-start gap-3 p-4 border-b group hover:bg-card last:border-b-0">
|
||||
<div className="p-2.5 rounded-full border bg-card">
|
||||
<BellIcon className="size-4 text-muted-foreground" />
|
||||
</div>
|
||||
<div className="flex w-full flex-row justify-between gap-2 transition-colors">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<p className="text-sm">{notification.description}</p>
|
||||
{timeAgo && (
|
||||
<p className="text-muted-foreground text-xs">{timeAgo}</p>
|
||||
)}
|
||||
<div className="flex flex-row justify-between gap-2 pt-1">
|
||||
<Button asChild className="px-0" size="sm" variant="link">
|
||||
<div className="flex flex-row justify-between gap-2 pt-2">
|
||||
<Button
|
||||
asChild
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="rounded-full bg-card gap-2"
|
||||
>
|
||||
<a
|
||||
href={notification.ctaUrl}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{notification.ctaText}
|
||||
<ArrowUpRightIcon className="size-4 text-muted-foreground" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{onMarkAsRead && (
|
||||
<Button
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
onClick={() => onMarkAsRead(notification.id)}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<ArchiveIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="p-2 h-auto opacity-0 group-hover:opacity-100 rounded-full text-muted-foreground hover:text-foreground"
|
||||
onClick={() => onMarkAsRead(notification.id)}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
aria-label="Archive notification"
|
||||
>
|
||||
<ArchiveIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,15 +21,15 @@ export function NotificationList(props: ReturnType<typeof useNotifications>) {
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-1 flex-col">
|
||||
<div className="flex flex-col overflow-hidden grow">
|
||||
<TabButtons
|
||||
tabClassName="!text-sm hover:!bg-transparent !px-0 !py-1"
|
||||
tabContainerClassName="space-x-6 px-4 pt-2"
|
||||
tabClassName="hover:bg-transparent"
|
||||
tabContainerClassName="pt-1.5 px-4"
|
||||
tabs={[
|
||||
{
|
||||
isActive: activeTab === "inbox",
|
||||
name: (
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
Inbox
|
||||
{props.unreadNotificationsCount > 0 && (
|
||||
<Badge variant="secondary">
|
||||
@@ -49,7 +49,7 @@ export function NotificationList(props: ReturnType<typeof useNotifications>) {
|
||||
/>
|
||||
|
||||
<div
|
||||
className="h-full w-full flex-1 overflow-y-auto"
|
||||
className="flex-1 overflow-y-auto overscroll-contain flex flex-col grow"
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
{activeTab === "inbox" ? (
|
||||
@@ -93,9 +93,9 @@ function InboxTab(
|
||||
},
|
||||
) {
|
||||
return props.unreadNotifications.length === 0 ? (
|
||||
<div className="flex h-full flex-col items-center justify-center gap-4 p-8 text-center">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
|
||||
<BellIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex grow flex-col items-center justify-center gap-4 p-4 text-center">
|
||||
<div className="p-2 bg-card rounded-full border">
|
||||
<BellIcon className="size-5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">No new notifications</p>
|
||||
</div>
|
||||
@@ -131,9 +131,9 @@ function ArchiveTab(
|
||||
},
|
||||
) {
|
||||
return props.archivedNotifications.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center gap-4 p-8 text-center">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
|
||||
<ArchiveIcon className="h-4 w-4 text-muted-foreground" />
|
||||
<div className="flex grow flex-col items-center justify-center gap-4 p-4 text-center">
|
||||
<div className="p-2 bg-card rounded-full border">
|
||||
<ArchiveIcon className="size-5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">No archived notifications</p>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { forwardRef, useCallback, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
prepareTransaction,
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
sendTransaction,
|
||||
type ThirdwebClient,
|
||||
toWei,
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface ImageUploadProps {
|
||||
value: File | undefined;
|
||||
onChange?: (files: File[]) => void;
|
||||
children?: React.ReactNode;
|
||||
variant?: React.ComponentProps<typeof Button>["variant"];
|
||||
className?: string;
|
||||
multiple?: boolean;
|
||||
accept: string;
|
||||
}
|
||||
|
||||
export function ImageUploadButton(props: ImageUploadProps) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = Array.from(e.target.files || []);
|
||||
props.onChange?.(files);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
className={props.className}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
variant={props.variant}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
<input
|
||||
accept={props.accept}
|
||||
aria-label="Upload image"
|
||||
className="hidden"
|
||||
multiple={props.multiple}
|
||||
onChange={handleFileChange}
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// TODO: move to API
|
||||
export const OPSponsoredChains = [
|
||||
// Optimism
|
||||
10,
|
||||
// Base
|
||||
8453,
|
||||
// Zora
|
||||
7777777,
|
||||
// Mode
|
||||
34443,
|
||||
// Frax
|
||||
252,
|
||||
// Cyber
|
||||
7560,
|
||||
// Redstone
|
||||
690,
|
||||
// Ancient8
|
||||
888888888,
|
||||
//Donatuz
|
||||
42026,
|
||||
//Mantle
|
||||
5000,
|
||||
];
|
||||
@@ -29,3 +29,6 @@ export const NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET =
|
||||
|
||||
export const NEXT_PUBLIC_DEMO_ENGINE_URL =
|
||||
process.env.NEXT_PUBLIC_DEMO_ENGINE_URL || "";
|
||||
|
||||
export const NEXT_PUBLIC_THIRDWEB_AI_HOST =
|
||||
process.env.NEXT_PUBLIC_THIRDWEB_AI_HOST || "https://nebula-api.thirdweb.com";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export const THIRDWEB_EWS_API_HOST =
|
||||
process.env.NEXT_PUBLIC_THIRDWEB_EWS_API_HOST ||
|
||||
"https://in-app-wallet.thirdweb.com";
|
||||
export const THIRDWEB_API_HOST =
|
||||
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb-dev.com";
|
||||
|
||||
export const THIRDWEB_PAY_DOMAIN =
|
||||
process.env.NEXT_PUBLIC_PAY_URL || "pay.thirdweb-dev.com";
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type {
|
||||
ListUserWalletsData,
|
||||
ListUserWalletsResponses,
|
||||
} from "@thirdweb-dev/api";
|
||||
import { configure, listUserWallets } from "@thirdweb-dev/api";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import type { WalletUser } from "thirdweb/wallets";
|
||||
import { THIRDWEB_EWS_API_HOST } from "@/constants/urls";
|
||||
import { THIRDWEB_API_HOST } from "@/constants/urls";
|
||||
import { embeddedWalletsKeys } from "../query-keys/cache-keys";
|
||||
|
||||
// Configure the API client to use the correct base URL
|
||||
configure({
|
||||
override: {
|
||||
baseUrl: THIRDWEB_API_HOST,
|
||||
},
|
||||
});
|
||||
|
||||
// Extract types from the generated API
|
||||
type APIWallet = ListUserWalletsResponses[200]["result"]["wallets"][0];
|
||||
type APIProfile = APIWallet["profiles"][0];
|
||||
|
||||
const fetchAccountList = ({
|
||||
jwt,
|
||||
clientId,
|
||||
@@ -18,37 +34,104 @@ const fetchAccountList = ({
|
||||
pageNumber: number;
|
||||
}) => {
|
||||
return async () => {
|
||||
const url = new URL(`${THIRDWEB_EWS_API_HOST}/api/2024-05-05/account/list`);
|
||||
try {
|
||||
// Prepare query parameters for the new API
|
||||
const queryParams: ListUserWalletsData["query"] = {
|
||||
page: pageNumber,
|
||||
limit: 50, // Keep the same page size
|
||||
};
|
||||
|
||||
// Add clientId or ecosystemSlug parameter
|
||||
if (ecosystemSlug) {
|
||||
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`);
|
||||
} else if (clientId) {
|
||||
url.searchParams.append("clientId", clientId);
|
||||
// Use the generated API function with Bearer authentication
|
||||
const response = await listUserWallets({
|
||||
query: queryParams,
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
"Content-Type": "application/json",
|
||||
"x-thirdweb-team-id": teamId,
|
||||
...(clientId && { "x-client-id": clientId }),
|
||||
...(ecosystemSlug && {
|
||||
"x-ecosystem-id": `ecosystem.${ecosystemSlug}`,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
// Handle response
|
||||
if (response.error || !response.data) {
|
||||
const errorMessage =
|
||||
typeof response.error === "string"
|
||||
? response.error
|
||||
: "No data returned";
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
// Transform the response to match the expected format
|
||||
return {
|
||||
users: response.data.result.wallets.map(transformToWalletUser),
|
||||
hasMore: response.data.result.pagination.hasMore ?? false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch wallets:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
url.searchParams.append("page", pageNumber.toString());
|
||||
|
||||
const res = await fetch(url.href, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
"Content-Type": "application/json",
|
||||
"x-thirdweb-team-id": teamId,
|
||||
...(clientId && { "x-client-id": clientId }),
|
||||
},
|
||||
method: "GET",
|
||||
});
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch wallets: ${await res.text()}`);
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return json as {
|
||||
users: WalletUser[];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// Transform API response to match existing WalletUser format
|
||||
function transformToWalletUser(apiWallet: APIWallet): WalletUser {
|
||||
return {
|
||||
id: getProfileId(apiWallet.profiles[0]) || "",
|
||||
linkedAccounts: apiWallet.profiles.map((profile) => {
|
||||
// Create details object based on the profile data
|
||||
let details:
|
||||
| { email: string; [key: string]: string }
|
||||
| { phone: string; [key: string]: string }
|
||||
| { address: string; [key: string]: string }
|
||||
| { id: string; [key: string]: string };
|
||||
|
||||
const profileId = getProfileId(profile);
|
||||
|
||||
if ("email" in profile && profile.email) {
|
||||
details = { email: profile.email, id: profileId };
|
||||
} else if ("phone" in profile && profile.phone) {
|
||||
details = { phone: profile.phone, id: profileId };
|
||||
} else if ("walletAddress" in profile && profile.walletAddress) {
|
||||
details = { address: profile.walletAddress, id: profileId };
|
||||
} else {
|
||||
details = { id: profileId };
|
||||
}
|
||||
|
||||
return {
|
||||
type: profile.type,
|
||||
details,
|
||||
};
|
||||
}),
|
||||
wallets: apiWallet.address
|
||||
? [
|
||||
{
|
||||
address: apiWallet.address,
|
||||
createdAt: apiWallet.createdAt || new Date().toISOString(),
|
||||
type: "enclave" as const,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to safely get ID from any profile type
|
||||
function getProfileId(profile: APIProfile | undefined): string {
|
||||
if (!profile) return "";
|
||||
|
||||
if ("id" in profile) {
|
||||
return profile.id;
|
||||
} else if ("credentialId" in profile) {
|
||||
return profile.credentialId;
|
||||
} else if ("identifier" in profile) {
|
||||
return profile.identifier;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
export function useEmbeddedWallets(params: {
|
||||
clientId?: string;
|
||||
ecosystemSlug?: string;
|
||||
@@ -98,6 +181,7 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
|
||||
while (true) {
|
||||
const res = await queryClient.fetchQuery<{
|
||||
users: WalletUser[];
|
||||
hasMore: boolean;
|
||||
}>({
|
||||
queryFn: fetchAccountList({
|
||||
clientId,
|
||||
@@ -113,12 +197,13 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
|
||||
),
|
||||
});
|
||||
|
||||
if (res.users.length === 0) {
|
||||
responses.push(...res.users);
|
||||
|
||||
if (!res.hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
page++;
|
||||
responses.push(...res.users);
|
||||
}
|
||||
|
||||
return responses;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useTheme } from "next-themes";
|
||||
import {
|
||||
type SendTransactionConfig,
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
|
||||
export function useSendAndConfirmTx(config?: SendTransactionConfig) {
|
||||
const { theme } = useTheme();
|
||||
const sendAndConfirmTransaction = useSendAndConfirmTransaction({
|
||||
payModal: {
|
||||
theme: getSDKTheme(theme === "light" ? "light" : "dark"),
|
||||
},
|
||||
...config,
|
||||
});
|
||||
|
||||
return sendAndConfirmTransaction;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
type Chain,
|
||||
getAddress,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebClient,
|
||||
type ThirdwebContract,
|
||||
} from "thirdweb";
|
||||
@@ -21,6 +20,7 @@ import { getWalletBalance } from "thirdweb/wallets";
|
||||
import invariant from "tiny-invariant";
|
||||
import { parseError } from "../utils/errorParser";
|
||||
import { tryCatch } from "../utils/try-catch";
|
||||
import { useSendAndConfirmTx } from "./useSendTx";
|
||||
|
||||
function getTokenBalancesQuery(params: {
|
||||
ownerAddress: string;
|
||||
@@ -79,6 +79,7 @@ export function useOwnedTokenBalances(params: {
|
||||
export function useSplitDistributeFunds(contract: ThirdwebContract) {
|
||||
const account = useActiveAccount();
|
||||
const queryClient = useQueryClient();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const params = {
|
||||
ownerAddress: contract.address, // because we want to fetch the balance of split contract
|
||||
@@ -106,10 +107,7 @@ export function useSplitDistributeFunds(contract: ThirdwebContract) {
|
||||
contract,
|
||||
tokenAddress: currency.tokenAddress,
|
||||
});
|
||||
const promise = sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction,
|
||||
});
|
||||
const promise = sendAndConfirmTx.mutateAsync(transaction);
|
||||
|
||||
toast.promise(promise, {
|
||||
error: (err) => ({
|
||||
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
} from "thirdweb";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import * as VoteExt from "thirdweb/extensions/vote";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import type { Account } from "thirdweb/wallets";
|
||||
import invariant from "tiny-invariant";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
|
||||
export async function tokensDelegated(
|
||||
options: BaseTransactionOptions<{ account: Account | undefined }>,
|
||||
@@ -81,7 +81,7 @@ export async function votingTokenDecimals(options: BaseTransactionOptions) {
|
||||
}
|
||||
|
||||
export function useDelegateMutation() {
|
||||
const { mutateAsync } = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (contract: ThirdwebContract) => {
|
||||
@@ -97,7 +97,7 @@ export function useDelegateMutation() {
|
||||
contract: tokenContract,
|
||||
delegatee: contract.address,
|
||||
});
|
||||
return await mutateAsync(transaction);
|
||||
return await sendAndConfirmTx.mutateAsync(transaction);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { BringToFrontIcon as BridgeIcon } from "lucide-react";
|
||||
@@ -17,18 +17,21 @@ export async function resolveEns(
|
||||
ensName: await resolveName({
|
||||
address: ensNameOrAddress,
|
||||
client,
|
||||
}),
|
||||
}).catch(() => null),
|
||||
};
|
||||
}
|
||||
|
||||
if (!isValidENSName(ensNameOrAddress)) {
|
||||
throw new Error("Invalid ENS name");
|
||||
return {
|
||||
address: null,
|
||||
ensName: null,
|
||||
};
|
||||
}
|
||||
|
||||
const address = await resolveAddress({
|
||||
client,
|
||||
name: ensNameOrAddress,
|
||||
});
|
||||
}).catch(() => null);
|
||||
|
||||
if (!address) {
|
||||
return {
|
||||
|
||||
@@ -184,7 +184,7 @@ async function fetchContractName(chainId: number, contractAddress: string) {
|
||||
return name;
|
||||
}
|
||||
|
||||
const formatNumber = (num: number) => {
|
||||
export const formatNumber = (num: number) => {
|
||||
if (num >= 1000000) {
|
||||
return `${(num / 1000000).toLocaleString(undefined, {
|
||||
maximumFractionDigits: 1,
|
||||
|
||||
@@ -147,4 +147,4 @@ export type ApiKeyPayConfigValidationSchema = z.infer<
|
||||
>;
|
||||
|
||||
// FIXME: Remove
|
||||
export const HIDDEN_SERVICES = ["relayer", "chainsaw"];
|
||||
export const HIDDEN_SERVICES = ["relayer", "chainsaw", "engineCloud"];
|
||||
|
||||
@@ -279,3 +279,32 @@ export function newAccountStub(overrides?: Partial<Account>): Account {
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
export function randomLorem(length: number) {
|
||||
const loremWords = [
|
||||
"lorem",
|
||||
"ipsum",
|
||||
"dolor",
|
||||
"sit",
|
||||
"amet",
|
||||
"consectetur",
|
||||
"adipiscing",
|
||||
"elit",
|
||||
"sed",
|
||||
"do",
|
||||
"eiusmod",
|
||||
"tempor",
|
||||
"incididunt",
|
||||
"ut",
|
||||
"labore",
|
||||
"et",
|
||||
"dolore",
|
||||
"magna",
|
||||
"aliqua",
|
||||
];
|
||||
|
||||
return Array.from({ length }, () => {
|
||||
const randomIndex = Math.floor(Math.random() * loremWords.length);
|
||||
return loremWords[randomIndex];
|
||||
}).join(" ");
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,24 +1,24 @@
|
||||
"use client";
|
||||
import { useTheme } from "next-themes";
|
||||
import { defineChain, type ThirdwebClient } from "thirdweb";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { BuyWidget } from "thirdweb/react";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
import { BuyAndSwapEmbed } from "@/components/blocks/BuyAndSwapEmbed";
|
||||
import { GridPatternEmbedContainer } from "@/components/blocks/grid-pattern-embed-container";
|
||||
import { defineDashboardChain } from "@/lib/defineDashboardChain";
|
||||
|
||||
export function BuyFundsSection(props: {
|
||||
chain: ChainMetadata;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<section className="flex flex-col gap-4 items-center justify-center">
|
||||
<BuyWidget
|
||||
amount="0"
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
chain={defineChain(props.chain.chainId)}
|
||||
<GridPatternEmbedContainer>
|
||||
<BuyAndSwapEmbed
|
||||
client={props.client}
|
||||
theme={getSDKTheme(theme === "dark" ? "dark" : "light")}
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
chain={defineDashboardChain(props.chain.chainId, props.chain)}
|
||||
buyAmount={undefined}
|
||||
tokenAddress={undefined}
|
||||
pageType="chain"
|
||||
/>
|
||||
</section>
|
||||
</GridPatternEmbedContainer>
|
||||
);
|
||||
}
|
||||
|
||||
+5
-5
@@ -17,7 +17,6 @@ import type { Chain, ChainMetadata } from "thirdweb/chains";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useActiveWalletChain,
|
||||
useSendTransaction,
|
||||
useSwitchActiveWalletChain,
|
||||
useWalletBalance,
|
||||
} from "thirdweb/react";
|
||||
@@ -48,6 +47,7 @@ import {
|
||||
NEXT_PUBLIC_THIRDWEB_ENGINE_FAUCET_WALLET,
|
||||
NEXT_PUBLIC_TURNSTILE_SITE_KEY,
|
||||
} from "@/constants/public-envs";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { mapV4ChainToV5Chain } from "@/utils/map-chains";
|
||||
|
||||
@@ -310,7 +310,7 @@ function SendFundsToFaucetModalContent(props: {
|
||||
const account = useActiveAccount();
|
||||
const activeChain = useActiveWalletChain();
|
||||
const switchActiveWalletChain = useSwitchActiveWalletChain();
|
||||
const sendTxMutation = useSendTransaction({
|
||||
const sendAndConfirmTx = useSendAndConfirmTx({
|
||||
payModal: false,
|
||||
});
|
||||
const switchChainMutation = useMutation({
|
||||
@@ -334,7 +334,7 @@ function SendFundsToFaucetModalContent(props: {
|
||||
value: toWei(values.amount.toString()),
|
||||
});
|
||||
|
||||
const promise = sendTxMutation.mutateAsync(sendNativeTokenTx);
|
||||
const promise = sendAndConfirmTx.mutateAsync(sendNativeTokenTx);
|
||||
|
||||
toast.promise(promise, {
|
||||
error: `Failed to send ${values.amount} ${props.chainMeta.nativeCurrency.symbol} to faucet`,
|
||||
@@ -404,11 +404,11 @@ function SendFundsToFaucetModalContent(props: {
|
||||
{activeChain.id === props.chain.id ? (
|
||||
<Button
|
||||
className="mt-4 w-full gap-2"
|
||||
disabled={sendTxMutation.isPending}
|
||||
disabled={sendAndConfirmTx.isPending}
|
||||
key="submit"
|
||||
type="submit"
|
||||
>
|
||||
{sendTxMutation.isPending && <Spinner className="size-4" />}
|
||||
{sendAndConfirmTx.isPending && <Spinner className="size-4" />}
|
||||
Send funds to faucet
|
||||
</Button>
|
||||
) : (
|
||||
|
||||
+94
-13
@@ -43,9 +43,7 @@ function useChainStatswithRPC(_rpcUrl: string) {
|
||||
const latency = (performance.now() - startTimeStamp).toFixed(0);
|
||||
|
||||
const blockNumber = Number.parseInt(json.result.number, 16);
|
||||
const blockGasLimit = Number.parseInt(json.result.gasLimit, 16);
|
||||
return {
|
||||
blockGasLimit,
|
||||
blockNumber,
|
||||
latency,
|
||||
};
|
||||
@@ -61,8 +59,70 @@ function useChainStatswithRPC(_rpcUrl: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function useEIP7702Support(_rpcUrl: string) {
|
||||
let rpcUrl = _rpcUrl.replace(
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: this is expected in this case
|
||||
"${THIRDWEB_API_KEY}",
|
||||
NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
|
||||
);
|
||||
|
||||
// based on the environment hit dev or production
|
||||
if (hostnameEndsWith(rpcUrl, "rpc.thirdweb.com")) {
|
||||
if (!isProd) {
|
||||
rpcUrl = rpcUrl.replace("rpc.thirdweb.com", "rpc.thirdweb-dev.com");
|
||||
}
|
||||
}
|
||||
|
||||
return useQuery({
|
||||
enabled: !!rpcUrl,
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const res = await fetch(rpcUrl, {
|
||||
body: JSON.stringify({
|
||||
id: 1,
|
||||
jsonrpc: "2.0",
|
||||
method: "eth_estimateGas",
|
||||
params: [
|
||||
{
|
||||
from: "0xdeadbeef00000000000000000000000000000000",
|
||||
to: "0xdeadbeef00000000000000000000000000000000",
|
||||
data: "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
value: "0x0",
|
||||
},
|
||||
"latest",
|
||||
{
|
||||
"0xdeadbeef00000000000000000000000000000000": {
|
||||
code: "0xef01000000000000000000000000000000000000000001",
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
// If the response has a valid result object, EIP-7702 is enabled
|
||||
return {
|
||||
isSupported: !!json.result,
|
||||
};
|
||||
} catch {
|
||||
// If the request fails or errors, EIP-7702 is not supported
|
||||
return {
|
||||
isSupported: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
queryKey: ["eip-7702-support", { rpcUrl }],
|
||||
refetchInterval: false, // Don't refetch this as it's unlikely to change
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function ChainLiveStats(props: { rpc: string }) {
|
||||
const stats = useChainStatswithRPC(props.rpc);
|
||||
const eip7702Support = useEIP7702Support(props.rpc);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -107,8 +167,8 @@ export function ChainLiveStats(props: { rpc: string }) {
|
||||
)}
|
||||
</PrimaryInfoItem>
|
||||
|
||||
{/* Block Height */}
|
||||
<PrimaryInfoItem title="Block Height" titleIcon={<PulseDot />}>
|
||||
{/* Latest Block */}
|
||||
<PrimaryInfoItem title="Latest Block" titleIcon={<PulseDot />}>
|
||||
{stats.isError ? (
|
||||
<p className="fade-in-0 animate-in text-destructive-text">N/A</p>
|
||||
) : stats.data ? (
|
||||
@@ -120,16 +180,37 @@ export function ChainLiveStats(props: { rpc: string }) {
|
||||
)}
|
||||
</PrimaryInfoItem>
|
||||
|
||||
{/* Block Gas Limit */}
|
||||
<PrimaryInfoItem title="Block Gas Limit" titleIcon={<PulseDot />}>
|
||||
{stats.isError ? (
|
||||
<p className="fade-in-0 animate-in text-destructive-text">N/A</p>
|
||||
) : stats.data ? (
|
||||
<p className="fade-in-0 animate-in">
|
||||
{stats.data.blockGasLimit ?? "N/A"}
|
||||
</p>
|
||||
{/* EIP-7702 */}
|
||||
<PrimaryInfoItem
|
||||
title="EIP-7702"
|
||||
titleIcon={
|
||||
eip7702Support.data ? (
|
||||
eip7702Support.data.isSupported ? (
|
||||
<ToolTipLabel label="Enabled">
|
||||
<CircleCheckIcon className="size-4 text-success-text" />
|
||||
</ToolTipLabel>
|
||||
) : (
|
||||
<ToolTipLabel label="Disabled">
|
||||
<XIcon className="size-4 text-destructive-text" />
|
||||
</ToolTipLabel>
|
||||
)
|
||||
) : eip7702Support.isError ? (
|
||||
<ToolTipLabel label="Disabled">
|
||||
<XIcon className="size-4 text-destructive-text" />
|
||||
</ToolTipLabel>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{eip7702Support.data ? (
|
||||
eip7702Support.data.isSupported ? (
|
||||
"Available"
|
||||
) : (
|
||||
"Not Available"
|
||||
)
|
||||
) : eip7702Support.isError ? (
|
||||
"Not Available"
|
||||
) : (
|
||||
<div className="flex h-[28px] w-[140px] py-1">
|
||||
<div className="flex h-[28px] w-[80px] py-1">
|
||||
<Skeleton className="h-full w-full" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { cancelAuction, cancelListing } from "thirdweb/extensions/marketplace";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
|
||||
interface CancelTabProps {
|
||||
id: string;
|
||||
@@ -21,15 +21,15 @@ export const CancelTab: React.FC<CancelTabProps> = ({
|
||||
const transaction = isAuction
|
||||
? cancelAuction({ auctionId: BigInt(id), contract })
|
||||
: cancelListing({ contract, listingId: BigInt(id) });
|
||||
const cancelQuery = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
return (
|
||||
<TransactionButton
|
||||
className="self-end"
|
||||
client={contract.client}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={cancelQuery.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
onClick={() => {
|
||||
const promise = cancelQuery.mutateAsync(transaction, {
|
||||
const promise = sendAndConfirmTx.mutateAsync(transaction, {
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
},
|
||||
|
||||
+3
-2
@@ -28,7 +28,7 @@ import type {
|
||||
CreateListingParams,
|
||||
} from "thirdweb/extensions/marketplace";
|
||||
import { createAuction, createListing } from "thirdweb/extensions/marketplace";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { shortenAddress } from "thirdweb/utils";
|
||||
import { CurrencySelector } from "@/components/blocks/CurrencySelector";
|
||||
import { NFTMediaWithEmptyState } from "@/components/blocks/nft-media";
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useDashboardOwnedNFTs } from "@/hooks/useDashboardOwnedNFTs";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { useOwnedNFTsInsight } from "@/hooks/useWalletNFTs";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -122,7 +123,7 @@ export const CreateListingsForm: React.FC<CreateListingsFormProps> = ({
|
||||
chain: contract.chain,
|
||||
});
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const listingNotifications = useTxNotifications(
|
||||
"NFT listed Successfully",
|
||||
"Failed to list NFT",
|
||||
|
||||
+43
-3
@@ -1,4 +1,6 @@
|
||||
import { UploadIcon } from "lucide-react";
|
||||
import { ArrowDownToLineIcon, UploadIcon } from "lucide-react";
|
||||
import Papa from "papaparse";
|
||||
import { handleDownload } from "@/components/blocks/download-file-button";
|
||||
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -9,6 +11,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { SnapshotEntry } from "../../legacy-zod-schema";
|
||||
import { useClaimConditionsFormContext } from "../index";
|
||||
|
||||
/**
|
||||
@@ -118,10 +121,10 @@ export const ClaimerSelection = () => {
|
||||
|
||||
{/* Edit or See Snapshot */}
|
||||
{snapshot ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3">
|
||||
{/* disable the "Edit" button when form is disabled, but not when it's a "See" button */}
|
||||
<Button
|
||||
className="gap-2 rounded-md"
|
||||
className="gap-2"
|
||||
disabled={disabledSnapshotButton}
|
||||
onClick={() => setOpenIndex(phaseIndex)}
|
||||
size="sm"
|
||||
@@ -130,6 +133,21 @@ export const ClaimerSelection = () => {
|
||||
<UploadIcon className="size-4" />
|
||||
</Button>
|
||||
|
||||
{snapshot && snapshot.length > 0 && (
|
||||
<Button
|
||||
className="gap-2 rounded bg-background"
|
||||
variant="outline"
|
||||
disabled={disabledSnapshotButton}
|
||||
onClick={() => {
|
||||
downloadSnapshotAsCSV(snapshot);
|
||||
}}
|
||||
size="sm"
|
||||
>
|
||||
<ArrowDownToLineIcon className="size-4" />
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-2 items-center",
|
||||
@@ -151,3 +169,25 @@ export const ClaimerSelection = () => {
|
||||
</FormFieldSetup>
|
||||
);
|
||||
};
|
||||
|
||||
function downloadSnapshotAsCSV(snapshot: SnapshotEntry[]) {
|
||||
const csvData = snapshot.map((entry) => ({
|
||||
address: entry.address,
|
||||
maxClaimable:
|
||||
entry.maxClaimable === "0" ? "" : entry.maxClaimable?.toString() || "",
|
||||
price: entry.price?.toString() || "",
|
||||
currencyAddress: entry.currencyAddress || "",
|
||||
}));
|
||||
|
||||
const csvContent = Papa.unparse(csvData, {
|
||||
header: true,
|
||||
delimiter: ",",
|
||||
quotes: false,
|
||||
});
|
||||
|
||||
handleDownload({
|
||||
fileContent: csvContent,
|
||||
fileNameWithExtension: "snapshot.csv",
|
||||
fileFormat: "text/csv",
|
||||
});
|
||||
}
|
||||
|
||||
+10
-18
@@ -1,11 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ArrowDownToLineIcon,
|
||||
CircleAlertIcon,
|
||||
CircleHelpIcon,
|
||||
PlusIcon,
|
||||
} from "lucide-react";
|
||||
import { CircleAlertIcon, CircleHelpIcon, PlusIcon } from "lucide-react";
|
||||
import {
|
||||
createContext,
|
||||
Fragment,
|
||||
@@ -28,11 +23,7 @@ import {
|
||||
ZERO_ADDRESS,
|
||||
} from "thirdweb";
|
||||
import { decimals } from "thirdweb/extensions/erc20";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import invariant from "tiny-invariant";
|
||||
import * as z from "zod";
|
||||
import { ZodError } from "zod";
|
||||
@@ -49,6 +40,7 @@ import { Form } from "@/components/ui/form";
|
||||
import { Spinner } from "@/components/ui/Spinner";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useIsAdmin } from "@/hooks/useContractRoles";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import {
|
||||
type ClaimConditionInput,
|
||||
@@ -222,7 +214,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
Record<number, SnapshotEntry[] | undefined>
|
||||
>({});
|
||||
|
||||
const sendTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const tokenDecimals = useReadContract(decimals, {
|
||||
contract,
|
||||
@@ -305,7 +297,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
|
||||
const isFetchingData =
|
||||
claimConditionsQuery.isFetching ||
|
||||
sendTx.isPending ||
|
||||
sendAndConfirmTx.isPending ||
|
||||
// Need to make sure the tokenDecimals.data is present when interacting with ERC20 claim conditions
|
||||
(isErc20 && tokenDecimals.isLoading);
|
||||
|
||||
@@ -428,7 +420,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
},
|
||||
phasesWithSnapshots,
|
||||
);
|
||||
await sendTx.mutateAsync(tx);
|
||||
await sendAndConfirmTx.mutateAsync(tx);
|
||||
|
||||
saveClaimPhaseNotification.onSuccess();
|
||||
} catch (error) {
|
||||
@@ -559,7 +551,7 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
>
|
||||
<ClaimConditionsPhase
|
||||
contract={contract}
|
||||
isPending={sendTx.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
onRemove={() => {
|
||||
formFields.remove(index);
|
||||
// Clean up snapshot when phase is removed
|
||||
@@ -594,7 +586,8 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
disabled={
|
||||
sendTx.isPending || (!isMultiPhase && phases?.length > 0)
|
||||
sendAndConfirmTx.isPending ||
|
||||
(!isMultiPhase && phases?.length > 0)
|
||||
}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -649,12 +642,11 @@ export const ClaimConditionsForm: React.FC<ClaimConditionsFormProps> = ({
|
||||
client={contract.client}
|
||||
disabled={claimConditionsQuery.isPending}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={sendTx.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={undefined}
|
||||
txChainID={contract.chain.id}
|
||||
type="submit"
|
||||
>
|
||||
<ArrowDownToLineIcon className="size-3.5" />
|
||||
{claimConditionsQuery.isPending
|
||||
? "Saving Phases"
|
||||
: "Save Phases"}
|
||||
|
||||
+5
-5
@@ -5,9 +5,9 @@ import type { ThirdwebContract } from "thirdweb";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import * as ERC721Ext from "thirdweb/extensions/erc721";
|
||||
import * as ERC1155Ext from "thirdweb/extensions/erc1155";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
|
||||
export function ResetClaimEligibility({
|
||||
@@ -23,7 +23,7 @@ export function ResetClaimEligibility({
|
||||
isLoggedIn: boolean;
|
||||
isMultiphase: boolean;
|
||||
}) {
|
||||
const sendTxMutation = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const txNotification = useTxNotifications(
|
||||
"Successfully reset claim eligibility",
|
||||
@@ -52,7 +52,7 @@ export function ResetClaimEligibility({
|
||||
}
|
||||
})();
|
||||
|
||||
sendTxMutation.mutate(tx, {
|
||||
sendAndConfirmTx.mutate(tx, {
|
||||
onError: (error) => {
|
||||
txNotification.onError(error);
|
||||
},
|
||||
@@ -70,7 +70,7 @@ export function ResetClaimEligibility({
|
||||
<TransactionButton
|
||||
client={contract.client}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={sendTxMutation.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
onClick={handleResetClaimEligibility}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@@ -79,7 +79,7 @@ export function ResetClaimEligibility({
|
||||
type="button"
|
||||
className="text-destructive-text bg-card"
|
||||
>
|
||||
{sendTxMutation.isPending ? (
|
||||
{sendAndConfirmTx.isPending ? (
|
||||
"Resetting Eligibility"
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export function ContractPageLayout(props: {
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col grow">
|
||||
<div className="border-border border-b py-8">
|
||||
<div className="container flex flex-col gap-4">
|
||||
<div className="flex flex-col justify-between gap-4 md:flex-row">
|
||||
|
||||
+6
@@ -19,6 +19,12 @@ export async function isInsightSupportedForChain(
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
console.error(
|
||||
"failed to fetch chain services for chain",
|
||||
chainId,
|
||||
errorText,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -5,9 +5,9 @@ import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { prepareTransaction, type ThirdwebClient, toWei } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { mapV4ChainToV5Chain } from "@/utils/map-chains";
|
||||
|
||||
export function DepositNative(props: {
|
||||
@@ -17,7 +17,7 @@ export function DepositNative(props: {
|
||||
isLoggedIn: boolean;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const sendTransaction = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const [amount, setAmount] = useState("");
|
||||
|
||||
return (
|
||||
@@ -36,7 +36,7 @@ export function DepositNative(props: {
|
||||
disabled={amount.length === 0 || Number.parseFloat(amount) <= 0}
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
className="border-l-0 rounded-l-none px-6"
|
||||
isPending={sendTransaction.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
onClick={() => {
|
||||
const transaction = prepareTransaction({
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
@@ -45,7 +45,7 @@ export function DepositNative(props: {
|
||||
to: props.address,
|
||||
value: toWei(amount),
|
||||
});
|
||||
sendTransaction.mutate(transaction, {
|
||||
sendAndConfirmTx.mutate(transaction, {
|
||||
onSuccess: () => {
|
||||
toast.success("Deposit successful");
|
||||
setAmount("");
|
||||
|
||||
+5
-8
@@ -7,20 +7,17 @@ import {
|
||||
getAccountsOfSigner,
|
||||
isAccountDeployed,
|
||||
} from "thirdweb/extensions/erc4337";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
|
||||
export function CreateAccountButton(props: {
|
||||
contract: ThirdwebContract;
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const sendTxMutation = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const address = useActiveAccount()?.address;
|
||||
|
||||
@@ -63,14 +60,14 @@ export function CreateAccountButton(props: {
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
variant="default"
|
||||
size="sm"
|
||||
isPending={sendTxMutation.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
onClick={() => {
|
||||
const tx = createAccount({
|
||||
admin: address,
|
||||
contract: props.contract,
|
||||
data: "0x",
|
||||
});
|
||||
sendTxMutation.mutate(tx);
|
||||
sendAndConfirmTx.mutate(tx);
|
||||
}}
|
||||
transactionCount={undefined}
|
||||
txChainID={props.contract.chain.id}
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ import {
|
||||
} from "@/components/analytics/date-range-selector";
|
||||
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
|
||||
import { SkeletonContainer } from "@/components/ui/skeleton";
|
||||
import { formatNumber } from "@/lib/search";
|
||||
import {
|
||||
type AnalyticsQueryParams,
|
||||
type TotalQueryResult,
|
||||
@@ -376,7 +377,7 @@ function AnalyticsStatUI(props: { label: string; data: number | undefined }) {
|
||||
<SkeletonContainer
|
||||
loadedData={props.data}
|
||||
render={(v) => {
|
||||
return <dd className="font-normal text-xl">{v.toLocaleString()}</dd>;
|
||||
return <dd className="font-normal text-xl">{formatNumber(v)}</dd>;
|
||||
}}
|
||||
skeletonData={10000}
|
||||
/>
|
||||
|
||||
+28
-24
@@ -3,8 +3,8 @@ import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs";
|
||||
import { getVercelEnv } from "@/utils/vercel";
|
||||
|
||||
type InsightAggregationEntry = {
|
||||
event_signature: string;
|
||||
time: string;
|
||||
topic_0: string;
|
||||
day: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
@@ -26,12 +26,18 @@ export async function getContractEventBreakdown(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<EventBreakdownEntry[]> {
|
||||
const daysDifference =
|
||||
params.startDate && params.endDate
|
||||
? Math.ceil(
|
||||
(params.endDate.getTime() - params.startDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
: 30;
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"group_by=time",
|
||||
"group_by=topic_0 as event_signature",
|
||||
"aggregate=toStartOfDay(toDate(block_timestamp)) as time",
|
||||
"aggregate=count(*) as count",
|
||||
"group_by=day",
|
||||
"group_by=topic_0",
|
||||
`limit=${daysDifference * 10}`, // at most 10 topics per day
|
||||
params.startDate
|
||||
? `filter_block_timestamp_gte=${getUnixTime(params.startDate)}`
|
||||
: "",
|
||||
@@ -42,14 +48,13 @@ export async function getContractEventBreakdown(params: {
|
||||
.filter(Boolean)
|
||||
.join("&");
|
||||
|
||||
const res = await fetch(
|
||||
`https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`,
|
||||
{
|
||||
headers: {
|
||||
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
|
||||
},
|
||||
const url = `https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
@@ -63,17 +68,16 @@ export async function getContractEventBreakdown(params: {
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"time" in value &&
|
||||
"day" in value &&
|
||||
"count" in value &&
|
||||
"event_signature" in value &&
|
||||
typeof value.event_signature === "string" &&
|
||||
typeof value.time === "string" &&
|
||||
typeof value.count === "number"
|
||||
"topic_0" in value &&
|
||||
typeof value.topic_0 === "string" &&
|
||||
typeof value.day === "string"
|
||||
) {
|
||||
collectedAggregations.push({
|
||||
count: value.count,
|
||||
event_signature: value.event_signature,
|
||||
time: value.time,
|
||||
count: Number(value.count),
|
||||
topic_0: value.topic_0,
|
||||
day: value.day,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -84,15 +88,15 @@ export async function getContractEventBreakdown(params: {
|
||||
> = new Map();
|
||||
|
||||
for (const value of collectedAggregations) {
|
||||
const mapKey = value.time;
|
||||
const mapKey = value.day;
|
||||
let valueForDay = dayToFunctionBreakdownMap.get(mapKey);
|
||||
if (!valueForDay) {
|
||||
valueForDay = {};
|
||||
dayToFunctionBreakdownMap.set(mapKey, valueForDay);
|
||||
}
|
||||
|
||||
valueForDay[value.event_signature] =
|
||||
(valueForDay[value.event_signature] || 0) + value.count;
|
||||
valueForDay[value.topic_0] =
|
||||
(valueForDay[value.topic_0] || 0) + value.count;
|
||||
}
|
||||
|
||||
const values: EventBreakdownEntry[] = [];
|
||||
|
||||
+21
-16
@@ -30,11 +30,17 @@ export async function getContractEventAnalytics(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<AnalyticsEntry[]> {
|
||||
const daysDifference =
|
||||
params.startDate && params.endDate
|
||||
? Math.ceil(
|
||||
(params.endDate.getTime() - params.startDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
: 30;
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"group_by=time",
|
||||
"aggregate=toStartOfDay(toDate(block_timestamp)) as time",
|
||||
"aggregate=count(block_timestamp) as count",
|
||||
"group_by=day",
|
||||
`limit=${daysDifference}`,
|
||||
params.startDate
|
||||
? `filter_block_timestamp_gte=${getUnixTime(params.startDate)}`
|
||||
: "",
|
||||
@@ -45,17 +51,17 @@ export async function getContractEventAnalytics(params: {
|
||||
.filter(Boolean)
|
||||
.join("&");
|
||||
|
||||
const res = await fetch(
|
||||
`https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`,
|
||||
{
|
||||
headers: {
|
||||
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
|
||||
},
|
||||
const url = `https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
"x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Failed to fetch events analytics data: ${errorText}`);
|
||||
}
|
||||
|
||||
const json = (await res.json()) as InsightResponse;
|
||||
@@ -67,14 +73,13 @@ export async function getContractEventAnalytics(params: {
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"time" in value &&
|
||||
"day" in value &&
|
||||
"count" in value &&
|
||||
typeof value.time === "string" &&
|
||||
typeof value.count === "number"
|
||||
typeof value.day === "string"
|
||||
) {
|
||||
values.push({
|
||||
count: value.count,
|
||||
time: new Date(value.time),
|
||||
count: Number(value.count),
|
||||
time: new Date(value.day),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+13
-8
@@ -26,12 +26,18 @@ export async function getContractFunctionBreakdown(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<FunctionBreakdownEntry[]> {
|
||||
const daysDifference =
|
||||
params.startDate && params.endDate
|
||||
? Math.ceil(
|
||||
(params.endDate.getTime() - params.startDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
: 30;
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"group_by=time",
|
||||
"group_by=day",
|
||||
"group_by=function_selector",
|
||||
"aggregate=toStartOfDay(toDate(block_timestamp)) as time",
|
||||
"aggregate=count(*) as count",
|
||||
`limit=${daysDifference * 10}`, // at most 10 functions per day
|
||||
params.startDate
|
||||
? `filter_block_timestamp_gte=${getUnixTime(params.startDate)}`
|
||||
: "",
|
||||
@@ -68,17 +74,16 @@ export async function getContractFunctionBreakdown(params: {
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"time" in value &&
|
||||
"day" in value &&
|
||||
"count" in value &&
|
||||
"function_selector" in value &&
|
||||
typeof value.function_selector === "string" &&
|
||||
typeof value.time === "string" &&
|
||||
typeof value.count === "number"
|
||||
typeof value.day === "string"
|
||||
) {
|
||||
collectedAggregations.push({
|
||||
count: value.count,
|
||||
count: Number(value.count),
|
||||
function_selector: value.function_selector,
|
||||
time: value.time,
|
||||
time: value.day,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+15
-9
@@ -30,11 +30,17 @@ export async function getContractTransactionAnalytics(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<TransactionAnalyticsEntry[]> {
|
||||
const daysDifference =
|
||||
params.startDate && params.endDate
|
||||
? Math.ceil(
|
||||
(params.endDate.getTime() - params.startDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
: 30;
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"group_by=time",
|
||||
"aggregate=toStartOfDay(toDate(block_timestamp)) as time",
|
||||
"aggregate=count(block_timestamp) as count",
|
||||
"group_by=day",
|
||||
`limit=${daysDifference}`,
|
||||
params.startDate
|
||||
? `filter_block_timestamp_gte=${getUnixTime(params.startDate)}`
|
||||
: "",
|
||||
@@ -55,7 +61,8 @@ export async function getContractTransactionAnalytics(params: {
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Failed to fetch transaction analytics data: ${errorText}`);
|
||||
}
|
||||
|
||||
const json = (await res.json()) as InsightResponse;
|
||||
@@ -67,14 +74,13 @@ export async function getContractTransactionAnalytics(params: {
|
||||
if (
|
||||
typeof tx === "object" &&
|
||||
tx !== null &&
|
||||
"time" in tx &&
|
||||
"day" in tx &&
|
||||
"count" in tx &&
|
||||
typeof tx.time === "string" &&
|
||||
typeof tx.count === "number"
|
||||
typeof tx.day === "string"
|
||||
) {
|
||||
returnValue.push({
|
||||
count: tx.count,
|
||||
time: new Date(tx.time),
|
||||
count: Number(tx.count),
|
||||
time: new Date(tx.day),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+17
-9
@@ -30,11 +30,18 @@ export async function getContractUniqueWalletAnalytics(params: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
}): Promise<TransactionAnalyticsEntry[]> {
|
||||
const daysDifference =
|
||||
params.startDate && params.endDate
|
||||
? Math.ceil(
|
||||
(params.endDate.getTime() - params.startDate.getTime()) /
|
||||
(1000 * 60 * 60 * 24),
|
||||
)
|
||||
: 30;
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"group_by=time",
|
||||
"aggregate=toStartOfDay(toDate(block_timestamp)) as time",
|
||||
"aggregate=count(distinct from_address) as count",
|
||||
"group_by=day",
|
||||
"aggregate=count(distinct from_address)",
|
||||
`limit=${daysDifference}`,
|
||||
params.startDate
|
||||
? `filter_block_timestamp_gte=${getUnixTime(params.startDate)}`
|
||||
: "",
|
||||
@@ -55,10 +62,12 @@ export async function getContractUniqueWalletAnalytics(params: {
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Failed to fetch wallet analytics data: ${errorText}`);
|
||||
}
|
||||
|
||||
const json = (await res.json()) as InsightResponse;
|
||||
console.log("wallet analytics json", json);
|
||||
const aggregations = Object.values(json.aggregations[0]);
|
||||
|
||||
const returnValue: TransactionAnalyticsEntry[] = [];
|
||||
@@ -67,14 +76,13 @@ export async function getContractUniqueWalletAnalytics(params: {
|
||||
if (
|
||||
typeof tx === "object" &&
|
||||
tx !== null &&
|
||||
"time" in tx &&
|
||||
"day" in tx &&
|
||||
"count" in tx &&
|
||||
typeof tx.time === "string" &&
|
||||
typeof tx.count === "number"
|
||||
typeof tx.day === "string"
|
||||
) {
|
||||
returnValue.push({
|
||||
count: tx.count,
|
||||
time: new Date(tx.time),
|
||||
count: Number(tx.count),
|
||||
time: new Date(tx.day),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -6,7 +6,7 @@ type InsightResponse = {
|
||||
aggregations: [
|
||||
{
|
||||
0: {
|
||||
total: number;
|
||||
count: number;
|
||||
};
|
||||
},
|
||||
];
|
||||
@@ -19,10 +19,9 @@ export async function getTotalContractEvents(params: {
|
||||
contractAddress: string;
|
||||
chainId: number;
|
||||
}): Promise<{ count: number }> {
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"aggregate=count(block_number) as total",
|
||||
].join("&");
|
||||
const queryParams = [`chain=${params.chainId}`, "aggregate=count()"].join(
|
||||
"&",
|
||||
);
|
||||
|
||||
const res = await fetch(
|
||||
`https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`,
|
||||
@@ -34,12 +33,13 @@ export async function getTotalContractEvents(params: {
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
const errorText = await res.text();
|
||||
throw new Error(`Failed to fetch events analytics data: ${errorText}`);
|
||||
}
|
||||
|
||||
const json = (await res.json()) as InsightResponse;
|
||||
|
||||
return {
|
||||
count: json.aggregations[0][0].total,
|
||||
count: json.aggregations[0][0].count,
|
||||
};
|
||||
}
|
||||
|
||||
+9
-7
@@ -6,7 +6,7 @@ type InsightResponse = {
|
||||
aggregations: [
|
||||
{
|
||||
0: {
|
||||
total: number;
|
||||
count: number;
|
||||
};
|
||||
},
|
||||
];
|
||||
@@ -19,10 +19,9 @@ export async function getTotalContractTransactions(params: {
|
||||
contractAddress: string;
|
||||
chainId: number;
|
||||
}): Promise<{ count: number }> {
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"aggregate=count(block_number) as total",
|
||||
].join("&");
|
||||
const queryParams = [`chain=${params.chainId}`, "aggregate=count()"].join(
|
||||
"&",
|
||||
);
|
||||
|
||||
const res = await fetch(
|
||||
`https://insight.${thirdwebDomain}.com/v1/transactions/${params.contractAddress}?${queryParams}`,
|
||||
@@ -34,12 +33,15 @@ export async function getTotalContractTransactions(params: {
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
const errorText = await res.text();
|
||||
throw new Error(
|
||||
`Failed to fetch transactions analytics data: ${errorText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const json = (await res.json()) as InsightResponse;
|
||||
|
||||
return {
|
||||
count: json.aggregations[0][0].total,
|
||||
count: json.aggregations[0][0].count,
|
||||
};
|
||||
}
|
||||
|
||||
+7
-4
@@ -6,7 +6,7 @@ type InsightResponse = {
|
||||
aggregations: [
|
||||
{
|
||||
0: {
|
||||
total: number;
|
||||
count: number;
|
||||
};
|
||||
},
|
||||
];
|
||||
@@ -21,7 +21,7 @@ export async function getTotalContractUniqueWallets(params: {
|
||||
}): Promise<{ count: number }> {
|
||||
const queryParams = [
|
||||
`chain=${params.chainId}`,
|
||||
"aggregate=count(distinct from_address) as total",
|
||||
"aggregate=count(distinct from_address)",
|
||||
].join("&");
|
||||
|
||||
const res = await fetch(
|
||||
@@ -34,12 +34,15 @@ export async function getTotalContractUniqueWallets(params: {
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to fetch analytics data");
|
||||
const errorText = await res.text();
|
||||
throw new Error(
|
||||
`Failed to fetch unique wallets analytics data: ${errorText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const json = (await res.json()) as InsightResponse;
|
||||
|
||||
return {
|
||||
count: json.aggregations[0][0].total,
|
||||
count: json.aggregations[0][0].count,
|
||||
};
|
||||
}
|
||||
|
||||
+5
-9
@@ -18,7 +18,6 @@ import {
|
||||
getRpcClient,
|
||||
prepareContractCall,
|
||||
prepareTransaction,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebClient,
|
||||
} from "thirdweb";
|
||||
import type {
|
||||
@@ -52,6 +51,7 @@ import {
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import type { ProjectMeta } from "../../../../../team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/types";
|
||||
import {
|
||||
@@ -178,6 +178,8 @@ export function DataTable({
|
||||
return Array.from(chainMap.values());
|
||||
}, [data, customChainData]);
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const form = useForm<FormSchema>({
|
||||
defaultValues: {
|
||||
amounts: {
|
||||
@@ -216,10 +218,7 @@ export function DataTable({
|
||||
],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: activeAccount,
|
||||
transaction: sendErc20Tx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(sendErc20Tx);
|
||||
};
|
||||
|
||||
const crossChainTransferNotifications = useTxNotifications(
|
||||
@@ -368,10 +367,7 @@ export function DataTable({
|
||||
to: "0x4e59b44847b379578588920cA78FbF26c0B4956C",
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: activeAccount,
|
||||
transaction: tx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(tx);
|
||||
|
||||
const code = await eth_getCode(
|
||||
getRpcClient({
|
||||
|
||||
+10
-7
@@ -4,7 +4,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { sendAndConfirmTransaction, type ThirdwebClient } from "thirdweb";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { BatchMetadataERC721, BatchMetadataERC1155 } from "thirdweb/modules";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { parseAttributes } from "@/utils/parseAttributes";
|
||||
import { fileBufferOrStringSchema } from "../zod-schemas";
|
||||
@@ -63,7 +64,7 @@ export type UploadMetadataFormValues = z.infer<typeof uploadMetadataFormSchema>;
|
||||
|
||||
function BatchMetadataModule(props: ModuleInstanceProps) {
|
||||
const { contract, ownerAccount } = props;
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const uploadMetadata = useCallback(
|
||||
async (values: UploadMetadataFormValues) => {
|
||||
if (!ownerAccount) {
|
||||
@@ -80,12 +81,14 @@ function BatchMetadataModule(props: ModuleInstanceProps) {
|
||||
metadatas: [nft],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: uploadMetadataTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(uploadMetadataTx);
|
||||
},
|
||||
[contract, ownerAccount, props.contractInfo.name],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
props.contractInfo.name,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+18
-17
@@ -15,7 +15,6 @@ import {
|
||||
getContract,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
type PreparedTransaction,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebClient,
|
||||
toTokens,
|
||||
ZERO_ADDRESS,
|
||||
@@ -53,6 +52,7 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useAllChainsData } from "@/hooks/chains/allChains";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { addressSchema } from "../zod-schemas";
|
||||
import { CurrencySelector } from "./CurrencySelector";
|
||||
@@ -76,7 +76,7 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
const { contract, ownerAccount } = props;
|
||||
const account = useActiveAccount();
|
||||
const [tokenId, setTokenId] = useState<string>("");
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const isValidTokenId = positiveIntegerRegex.test(tokenId);
|
||||
|
||||
const primarySaleRecipientQuery = useReadContract(
|
||||
@@ -189,12 +189,9 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
throw new Error("Invalid tokenId");
|
||||
}
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: account,
|
||||
transaction: mintTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(mintTx);
|
||||
},
|
||||
[contract, account, props.contractInfo.name],
|
||||
[contract, account, props.contractInfo.name, sendAndConfirmTx.mutateAsync],
|
||||
);
|
||||
|
||||
const setClaimCondition = useCallback(
|
||||
@@ -236,12 +233,14 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
throw new Error("Invalid tokenId");
|
||||
}
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setClaimConditionTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setClaimConditionTx);
|
||||
},
|
||||
[contract, ownerAccount, props.contractInfo.name],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
props.contractInfo.name,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
const setPrimarySaleRecipient = useCallback(
|
||||
@@ -260,12 +259,14 @@ function ClaimableModule(props: ModuleInstanceProps) {
|
||||
primarySaleRecipient: values.primarySaleRecipient,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setSaleConfigTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setSaleConfigTx);
|
||||
},
|
||||
[contract, ownerAccount, props.contractInfo.name],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
props.contractInfo.name,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+19
-20
@@ -4,11 +4,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
type PreparedTransaction,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebClient,
|
||||
} from "thirdweb";
|
||||
import type { PreparedTransaction, ThirdwebClient } from "thirdweb";
|
||||
import {
|
||||
grantRoles,
|
||||
hasAllRoles,
|
||||
@@ -40,6 +36,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import type { NFTMetadataInputLimited } from "@/types/modified-types";
|
||||
import { parseAttributes } from "@/utils/parseAttributes";
|
||||
@@ -73,7 +70,7 @@ const MINTER_ROLE = 1n;
|
||||
|
||||
function MintableModule(props: ModuleInstanceProps) {
|
||||
const { contract, ownerAccount } = props;
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const isErc721 = props.contractInfo.name === "MintableERC721";
|
||||
|
||||
const primarySaleRecipientQuery = useReadContract(
|
||||
@@ -106,10 +103,7 @@ function MintableModule(props: ModuleInstanceProps) {
|
||||
user: ownerAccount.address,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: grantRoleTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(grantRoleTx);
|
||||
}
|
||||
|
||||
let mintTx: PreparedTransaction;
|
||||
@@ -137,12 +131,15 @@ function MintableModule(props: ModuleInstanceProps) {
|
||||
throw new Error("Invalid token ID");
|
||||
}
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: mintTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(mintTx);
|
||||
},
|
||||
[contract, ownerAccount, hasMinterRole.data, props.contractInfo.name],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
hasMinterRole.data,
|
||||
props.contractInfo.name,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
const update = useCallback(
|
||||
@@ -163,12 +160,14 @@ function MintableModule(props: ModuleInstanceProps) {
|
||||
primarySaleRecipient: values.primarySaleRecipient,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setSaleConfigTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setSaleConfigTx);
|
||||
},
|
||||
[contract, ownerAccount, props.contractInfo.name],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
props.contractInfo.name,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+3
-9
@@ -7,10 +7,8 @@ import { toast } from "sonner";
|
||||
import {
|
||||
type Chain,
|
||||
getContract,
|
||||
sendTransaction,
|
||||
type ThirdwebClient,
|
||||
type ThirdwebContract,
|
||||
waitForReceipt,
|
||||
} from "thirdweb";
|
||||
import {
|
||||
checkModulesCompatibility,
|
||||
@@ -40,6 +38,7 @@ import {
|
||||
useAllVersions,
|
||||
usePublishedContractsQuery,
|
||||
} from "@/hooks/contract-hooks";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import {
|
||||
ModuleInstallParams,
|
||||
useModuleInstallParams,
|
||||
@@ -76,7 +75,7 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
|
||||
const { register, watch, formState, resetField, reset, setValue } = form;
|
||||
const { contract, account } = props;
|
||||
const { errors } = formState;
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const { data, isPending, isFetching } = usePublishedContractsQuery({
|
||||
address: watch("publisherAddress"),
|
||||
client: contract.client,
|
||||
@@ -124,12 +123,7 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => {
|
||||
version: watch("version"),
|
||||
});
|
||||
|
||||
const txResult = await sendTransaction({
|
||||
account,
|
||||
transaction: installTransaction,
|
||||
});
|
||||
|
||||
await waitForReceipt(txResult);
|
||||
await sendAndConfirmTx.mutateAsync(installTransaction);
|
||||
},
|
||||
onError(err) {
|
||||
toast.error("Failed to install module");
|
||||
|
||||
+5
-7
@@ -4,7 +4,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { sendAndConfirmTransaction, type ThirdwebClient } from "thirdweb";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { OpenEditionMetadataERC721 } from "thirdweb/modules";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { ModuleCardUI, type ModuleCardUIProps } from "./module-card";
|
||||
import type { ModuleInstanceProps } from "./module-instance";
|
||||
@@ -42,7 +43,7 @@ export type SetSharedMetadataFormValues = z.infer<
|
||||
|
||||
function OpenEditionMetadataModule(props: ModuleInstanceProps) {
|
||||
const { contract, ownerAccount } = props;
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const setSharedMetadata = useCallback(
|
||||
async (values: SetSharedMetadataFormValues) => {
|
||||
if (!ownerAccount) {
|
||||
@@ -59,12 +60,9 @@ function OpenEditionMetadataModule(props: ModuleInstanceProps) {
|
||||
},
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setSharedMetadataTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setSharedMetadataTx);
|
||||
},
|
||||
[contract, ownerAccount],
|
||||
[contract, ownerAccount, sendAndConfirmTx.mutateAsync],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+21
-17
@@ -4,7 +4,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { sendAndConfirmTransaction, type ThirdwebClient } from "thirdweb";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { RoyaltyERC721, RoyaltyERC1155 } from "thirdweb/modules";
|
||||
import { useReadContract } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { addressSchema } from "../zod-schemas";
|
||||
import { ModuleCardUI, type ModuleCardUIProps } from "./module-card";
|
||||
@@ -33,7 +34,7 @@ import type { ModuleInstanceProps } from "./module-instance";
|
||||
|
||||
function RoyaltyModule(props: ModuleInstanceProps) {
|
||||
const { contract, ownerAccount } = props;
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const isErc721 = props.contractInfo.name === "RoyaltyERC721";
|
||||
|
||||
const defaultRoyaltyInfoQuery = useReadContract(
|
||||
@@ -70,12 +71,9 @@ function RoyaltyModule(props: ModuleInstanceProps) {
|
||||
tokenId: BigInt(values.tokenId),
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setRoyaltyForTokenTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setRoyaltyForTokenTx);
|
||||
},
|
||||
[contract, ownerAccount, isErc721],
|
||||
[contract, ownerAccount, isErc721, sendAndConfirmTx.mutateAsync],
|
||||
);
|
||||
|
||||
const setTransferValidator = useCallback(
|
||||
@@ -95,13 +93,16 @@ function RoyaltyModule(props: ModuleInstanceProps) {
|
||||
validator: values.transferValidator,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setTransferValidatorTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setTransferValidatorTx);
|
||||
}
|
||||
},
|
||||
[contract, ownerAccount, transferValidatorQuery.data, isErc721],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
transferValidatorQuery.data,
|
||||
isErc721,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
const setDefaultRoyaltyInfo = useCallback(
|
||||
@@ -128,13 +129,16 @@ function RoyaltyModule(props: ModuleInstanceProps) {
|
||||
royaltyRecipient: values.recipient,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setSaleConfigTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setSaleConfigTx);
|
||||
}
|
||||
},
|
||||
[contract, ownerAccount, defaultRoyaltyInfoQuery.data, isErc721],
|
||||
[
|
||||
contract,
|
||||
ownerAccount,
|
||||
defaultRoyaltyInfoQuery.data,
|
||||
isErc721,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+5
-10
@@ -6,7 +6,6 @@ import { CircleAlertIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
import { useFieldArray, useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { sendAndConfirmTransaction } from "thirdweb";
|
||||
import {
|
||||
TransferableERC20,
|
||||
TransferableERC721,
|
||||
@@ -29,6 +28,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { addressSchema } from "../zod-schemas";
|
||||
import { ModuleCardUI, type ModuleCardUIProps } from "./module-card";
|
||||
import type { ModuleInstanceProps } from "./module-instance";
|
||||
@@ -46,7 +46,7 @@ export type TransferableModuleFormValues = z.infer<typeof formSchema>;
|
||||
|
||||
function TransferableModule(props: ModuleInstanceProps) {
|
||||
const { contract, ownerAccount } = props;
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const isTransferEnabledQuery = useReadContract(
|
||||
props.contractInfo.name === "TransferableERC721"
|
||||
? TransferableERC721.isTransferEnabled
|
||||
@@ -79,10 +79,7 @@ function TransferableModule(props: ModuleInstanceProps) {
|
||||
enableTransfer: transferEnabled,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setTransferableTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setTransferableTx);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
@@ -99,10 +96,7 @@ function TransferableModule(props: ModuleInstanceProps) {
|
||||
target: address,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: ownerAccount,
|
||||
transaction: setTransferableForTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(setTransferableForTx);
|
||||
}),
|
||||
);
|
||||
},
|
||||
@@ -111,6 +105,7 @@ function TransferableModule(props: ModuleInstanceProps) {
|
||||
ownerAccount,
|
||||
isTransferEnabledQuery.data,
|
||||
props.contractInfo.name,
|
||||
sendAndConfirmTx.mutateAsync,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+5
-15
@@ -8,10 +8,8 @@ import { ErrorBoundary, type FallbackProps } from "react-error-boundary";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
getContract,
|
||||
sendTransaction,
|
||||
type ThirdwebClient,
|
||||
type ThirdwebContract,
|
||||
waitForReceipt,
|
||||
} from "thirdweb";
|
||||
import { uninstallModuleByProxy } from "thirdweb/modules";
|
||||
import type { Account } from "thirdweb/wallets";
|
||||
@@ -30,6 +28,7 @@ import {
|
||||
} from "@/components/ui/dialog";
|
||||
import { Spinner } from "@/components/ui/Spinner";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { ModuleInstance } from "./module-instance";
|
||||
import { useModuleContractInfo } from "./moduleContractInfo";
|
||||
|
||||
@@ -50,6 +49,7 @@ type ModuleCardProps = {
|
||||
export function ModuleCard(props: ModuleCardProps) {
|
||||
const { contract, moduleAddress, ownerAccount } = props;
|
||||
const [isUninstallModalOpen, setIsUninstallModalOpen] = useState(false);
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const contractInfo = useModuleContractInfo(
|
||||
getContract({
|
||||
@@ -60,7 +60,7 @@ export function ModuleCard(props: ModuleCardProps) {
|
||||
);
|
||||
|
||||
const uninstallMutation = useMutation({
|
||||
mutationFn: async (account: Account) => {
|
||||
mutationFn: async () => {
|
||||
const uninstallTransaction = uninstallModuleByProxy({
|
||||
chain: contract.chain,
|
||||
client: contract.client,
|
||||
@@ -69,12 +69,7 @@ export function ModuleCard(props: ModuleCardProps) {
|
||||
moduleProxyAddress: moduleAddress,
|
||||
});
|
||||
|
||||
const txResult = await sendTransaction({
|
||||
account,
|
||||
transaction: uninstallTransaction,
|
||||
});
|
||||
|
||||
await waitForReceipt(txResult);
|
||||
await sendAndConfirmTx.mutateAsync(uninstallTransaction);
|
||||
},
|
||||
onError(error) {
|
||||
toast.error("Failed to uninstall module");
|
||||
@@ -87,12 +82,7 @@ export function ModuleCard(props: ModuleCardProps) {
|
||||
});
|
||||
|
||||
const handleRemove = async () => {
|
||||
if (!ownerAccount) {
|
||||
toast.error("Wallet is not connected");
|
||||
return;
|
||||
}
|
||||
|
||||
uninstallMutation.mutate(ownerAccount);
|
||||
uninstallMutation.mutate();
|
||||
};
|
||||
|
||||
if (!contractInfo) {
|
||||
|
||||
+3
-2
@@ -7,7 +7,7 @@ import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { multicall } from "thirdweb/extensions/common";
|
||||
import { balanceOf, encodeSafeTransferFrom } from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
@@ -44,7 +45,7 @@ const AirdropTab: React.FC<AirdropTabProps> = ({
|
||||
}>({
|
||||
defaultValues: { addresses: [] },
|
||||
});
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const addresses = watch("addresses");
|
||||
const [open, setOpen] = useState(false);
|
||||
const airdropNotifications = useTxNotifications(
|
||||
|
||||
+2
-2
@@ -8,7 +8,6 @@ import {
|
||||
useActiveAccount,
|
||||
useInvalidateContractQuery,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import {
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
|
||||
type BurnTabProps = {
|
||||
@@ -48,7 +48,7 @@ function BurnTab({ contract, tokenId, isLoggedIn }: BurnTabProps) {
|
||||
isERC1155,
|
||||
{ contract },
|
||||
);
|
||||
const { mutate, isPending } = useSendAndConfirmTransaction();
|
||||
const { mutate, isPending } = useSendAndConfirmTx();
|
||||
|
||||
const onSubmit = (data: { to: string; amount: string }) => {
|
||||
const transaction = isErc721
|
||||
|
||||
+3
-2
@@ -6,7 +6,7 @@ import { toast } from "sonner";
|
||||
import { isAddress, type ThirdwebContract } from "thirdweb";
|
||||
import { getApprovalForTransaction } from "thirdweb/extensions/erc20";
|
||||
import { claimTo } from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import {
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const claimFormSchema = z.object({
|
||||
@@ -54,7 +55,7 @@ const ClaimTabERC1155: React.FC<ClaimTabProps> = ({
|
||||
resolver: zodResolver(claimFormSchema),
|
||||
defaultValues: { amount: "1", to: address },
|
||||
});
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const account = useActiveAccount();
|
||||
|
||||
return (
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { isAddress, type ThirdwebContract } from "thirdweb";
|
||||
import { mintAdditionalSupplyTo } from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import {
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
|
||||
interface MintSupplyTabProps {
|
||||
contract: ThirdwebContract;
|
||||
@@ -47,7 +48,7 @@ const MintSupplyTab: React.FC<MintSupplyTabProps> = ({
|
||||
resolver: zodResolver(mintAdditionalSupplyFormSchema),
|
||||
});
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
function onSubmit(values: z.input<typeof mintAdditionalSupplyFormSchema>) {
|
||||
const transaction = mintAdditionalSupplyTo({
|
||||
|
||||
+3
-6
@@ -4,11 +4,7 @@ import { useForm } from "react-hook-form";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { transferFrom } from "thirdweb/extensions/erc721";
|
||||
import { isERC1155, safeTransferFrom } from "thirdweb/extensions/erc1155";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
import { SolidityInput } from "@/components/solidity-inputs";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
@@ -21,6 +17,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
|
||||
type TransferTabProps = {
|
||||
@@ -47,7 +44,7 @@ function TransferTab({ contract, tokenId, isLoggedIn }: TransferTabProps) {
|
||||
{ contract },
|
||||
);
|
||||
|
||||
const sendTxAndConfirm = useSendAndConfirmTransaction();
|
||||
const sendTxAndConfirm = useSendAndConfirmTx();
|
||||
|
||||
if (checking1155) {
|
||||
return <GenericLoadingPage />;
|
||||
|
||||
+3
-2
@@ -12,7 +12,7 @@ import {
|
||||
updateMetadata as updateMetadata1155,
|
||||
updateTokenURI as updateTokenURI1155,
|
||||
} from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import type { NFTMetadata } from "thirdweb/utils";
|
||||
import { OpenSeaPropertyBadge } from "@/components/badges/opensea";
|
||||
import { FileInput } from "@/components/blocks/FileInput";
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import type { NFTMetadataInputLimited } from "@/types/modified-types";
|
||||
import { parseAttributes } from "@/utils/parseAttributes";
|
||||
@@ -110,7 +111,7 @@ export function UpdateNftMetadata({
|
||||
external_url,
|
||||
} = getUploadedNFTMediaMeta(form);
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const updateMetadataNotifications = useTxNotifications(
|
||||
"NFT metadata updated successfully",
|
||||
"Failed to update NFT metadata",
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import { useState } from "react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import * as ERC721Ext from "thirdweb/extensions/erc721";
|
||||
import * as ERC1155Ext from "thirdweb/extensions/erc1155";
|
||||
import { useReadContract, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useReadContract } from "thirdweb/react";
|
||||
import { MinterOnly } from "@/components/contracts/roles/minter-only";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { BatchLazyMint } from "./batch-upload/batch-lazy-mint";
|
||||
|
||||
@@ -40,7 +41,7 @@ export const BatchLazyMintButton: React.FC<BatchLazyMintButtonProps> = ({
|
||||
},
|
||||
);
|
||||
|
||||
const sendTxMutation = useSendAndConfirmTransaction();
|
||||
const sendTxMutation = useSendAndConfirmTx();
|
||||
|
||||
const txNotifications = useTxNotifications(
|
||||
"Batch uploaded successfully",
|
||||
|
||||
+3
-2
@@ -8,7 +8,7 @@ import { toast } from "sonner";
|
||||
import { isAddress, type ThirdwebContract } from "thirdweb";
|
||||
import { getApprovalForTransaction } from "thirdweb/extensions/erc20";
|
||||
import { claimTo } from "thirdweb/extensions/erc721";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const claimFormSchema = z.object({
|
||||
@@ -61,7 +62,7 @@ export function NFTClaimButton({ contract, isLoggedIn }: NFTClaimButtonProps) {
|
||||
resolver: zodResolver(claimFormSchema),
|
||||
defaultValues: { amount: "1", to: address },
|
||||
});
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const account = useActiveAccount();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
|
||||
+3
-2
@@ -6,7 +6,7 @@ import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { lazyMint as lazyMint721 } from "thirdweb/extensions/erc721";
|
||||
import { lazyMint as lazyMint1155 } from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { OpenSeaPropertyBadge } from "@/components/badges/opensea";
|
||||
import { FileInput } from "@/components/blocks/FileInput";
|
||||
import { PropertiesFormControl } from "@/components/contracts/properties.shared";
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import type { NFTMetadataInputLimited } from "@/types/modified-types";
|
||||
import { parseAttributes } from "@/utils/parseAttributes";
|
||||
@@ -53,7 +54,7 @@ export function LazyMintNftForm({
|
||||
isLoggedIn,
|
||||
}: LazyMintNftFormParams) {
|
||||
const address = useActiveAccount()?.address;
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const form = useForm<NFTMetadataInputLimited>();
|
||||
|
||||
|
||||
+3
-2
@@ -6,7 +6,7 @@ import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { mintTo as erc721MintTo } from "thirdweb/extensions/erc721";
|
||||
import { mintTo as erc1155MintTo } from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { OpenSeaPropertyBadge } from "@/components/badges/opensea";
|
||||
import { FileInput } from "@/components/blocks/FileInput";
|
||||
import { PropertiesFormControl } from "@/components/contracts/properties.shared";
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import type { NFTMetadataInputLimited } from "@/types/modified-types";
|
||||
import { parseAttributes } from "@/utils/parseAttributes";
|
||||
@@ -81,7 +82,7 @@ export function NFTMintForm({
|
||||
external_url,
|
||||
} = getUploadedNFTMediaMeta(form);
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const nftMintNotifications = useTxNotifications(
|
||||
"NFT minted successfully",
|
||||
"Failed to mint NFT",
|
||||
|
||||
+3
-2
@@ -7,7 +7,7 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { getBatchesToReveal, reveal } from "thirdweb/extensions/erc721";
|
||||
import { useReadContract, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useReadContract } from "thirdweb/react";
|
||||
import * as z from "zod";
|
||||
import { MinterOnly } from "@/components/contracts/roles/minter-only";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const revealFormSchema = z.object({
|
||||
@@ -56,7 +57,7 @@ export function NFTRevealButton({
|
||||
contract,
|
||||
});
|
||||
|
||||
const sendTxMutation = useSendAndConfirmTransaction();
|
||||
const sendTxMutation = useSendAndConfirmTx();
|
||||
|
||||
const form = useForm<RevealFormData>({
|
||||
resolver: zodResolver(revealFormSchema),
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { setSharedMetadata } from "thirdweb/extensions/erc721";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { FileInput } from "@/components/blocks/FileInput";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import {
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import type { NFTMetadataInputLimited } from "@/types/modified-types";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { parseAttributes } from "@/utils/parseAttributes";
|
||||
@@ -44,7 +45,7 @@ export function SharedMetadataForm({
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const address = useActiveAccount()?.address;
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const form = useForm<NFTMetadataInputLimited>();
|
||||
|
||||
const setFile = (file: File) => {
|
||||
|
||||
+9
-10
@@ -5,11 +5,7 @@ import { useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form } from "@/components/ui/form";
|
||||
@@ -18,6 +14,7 @@ import {
|
||||
createSetAllRoleMembersTx,
|
||||
getAllRoleMembers,
|
||||
} from "@/hooks/contract-ui/permissions";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { ContractPermission } from "./contract-permission";
|
||||
|
||||
@@ -36,7 +33,7 @@ export function Permissions({
|
||||
const allRoleMembers = useReadContract(getAllRoleMembers, {
|
||||
contract,
|
||||
});
|
||||
const sendTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const transformedQueryData = useMemo(() => {
|
||||
if (!allRoleMembers.data) {
|
||||
@@ -86,7 +83,7 @@ export function Permissions({
|
||||
roleMemberMap: cleanedValues,
|
||||
});
|
||||
|
||||
sendTx.mutate(tx, {
|
||||
sendAndConfirmTx.mutate(tx, {
|
||||
onError: (error) => {
|
||||
toast.error("Failed to update permissions", {
|
||||
description: parseError(error),
|
||||
@@ -115,7 +112,7 @@ export function Permissions({
|
||||
<div className="flex justify-end gap-3 p-4 lg:p-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={!allRoleMembers.data || sendTx.isPending}
|
||||
disabled={!allRoleMembers.data || sendAndConfirmTx.isPending}
|
||||
onClick={() => form.reset(allRoleMembers.data)}
|
||||
className="gap-2 bg-background"
|
||||
>
|
||||
@@ -125,13 +122,15 @@ export function Permissions({
|
||||
<TransactionButton
|
||||
client={contract.client}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={sendTx.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={undefined}
|
||||
txChainID={contract.chain.id}
|
||||
type="submit"
|
||||
variant="default"
|
||||
>
|
||||
{sendTx.isPending ? "Updating permissions" : "Update permissions"}
|
||||
{sendAndConfirmTx.isPending
|
||||
? "Updating permissions"
|
||||
: "Update permissions"}
|
||||
</TransactionButton>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+5
-5
@@ -6,7 +6,6 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import * as VoteExt from "thirdweb/extensions/vote";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
export function ProposalButton({
|
||||
@@ -35,7 +35,7 @@ export function ProposalButton({
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const sendTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const form = useForm<{ description: string }>({
|
||||
defaultValues: {
|
||||
description: "",
|
||||
@@ -51,7 +51,7 @@ export function ProposalButton({
|
||||
values: [0n],
|
||||
});
|
||||
|
||||
await sendTx.mutateAsync(tx, {
|
||||
await sendAndConfirmTx.mutateAsync(tx, {
|
||||
onError: (error) => {
|
||||
toast.error("Failed to create proposal", {
|
||||
description: parseError(error),
|
||||
@@ -105,7 +105,7 @@ export function ProposalButton({
|
||||
|
||||
<div className="mt-6 flex flex-row justify-end gap-3">
|
||||
<Button
|
||||
disabled={sendTx.isPending}
|
||||
disabled={sendAndConfirmTx.isPending}
|
||||
onClick={() => setOpen(false)}
|
||||
variant="outline"
|
||||
>
|
||||
@@ -114,7 +114,7 @@ export function ProposalButton({
|
||||
<TransactionButton
|
||||
client={contract.client}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={sendTx.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={1}
|
||||
txChainID={contract.chain.id}
|
||||
type="submit"
|
||||
|
||||
+3
-6
@@ -6,16 +6,13 @@ import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { type ThirdwebContract, toTokens } from "thirdweb";
|
||||
import * as VoteExt from "thirdweb/extensions/vote";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import { WalletAddress } from "@/components/blocks/wallet-address";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { tokensDelegated, votingTokenDecimals } from "@/hooks/useVote";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
@@ -62,7 +59,7 @@ export function Proposal({ proposal, contract, isLoggedIn }: ProposalProps) {
|
||||
queryOptions: { enabled: !!account },
|
||||
});
|
||||
|
||||
const sendTx = useSendAndConfirmTransaction();
|
||||
const sendTx = useSendAndConfirmTx();
|
||||
|
||||
const [voteType, setVoteType] = useState<VoteExt.VoteType | null>(null);
|
||||
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
import "server-only";
|
||||
import { isProd } from "@/constants/env-utils";
|
||||
import { DASHBOARD_THIRDWEB_SECRET_KEY } from "@/constants/server-envs";
|
||||
|
||||
export type TokenPriceData = {
|
||||
price_usd: number;
|
||||
price_usd_cents: number;
|
||||
percent_change_24h: number;
|
||||
market_cap_usd: number;
|
||||
volume_24h_usd: number;
|
||||
volume_change_24h: number;
|
||||
holders: number;
|
||||
historical_prices: Array<{
|
||||
date: string;
|
||||
price_usd: number;
|
||||
price_usd_cents: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
export async function getTokenPriceData(params: {
|
||||
chainId: number;
|
||||
contractAddress: string;
|
||||
}) {
|
||||
try {
|
||||
const url = new URL(
|
||||
`https://insight.${isProd ? "thirdweb" : "thirdweb-dev"}.com/v1/tokens/price`,
|
||||
);
|
||||
|
||||
url.searchParams.set("include_historical_prices", "true");
|
||||
url.searchParams.set("chain_id", params.chainId.toString());
|
||||
url.searchParams.set("address", params.contractAddress);
|
||||
url.searchParams.set("include_holders", "true");
|
||||
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
"x-secret-key": DASHBOARD_THIRDWEB_SECRET_KEY,
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error("Failed to fetch token price data", await res.text());
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
const priceData = json.data[0] as TokenPriceData | undefined;
|
||||
|
||||
return priceData;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import type { Chain, ThirdwebClient } from "thirdweb";
|
||||
import { BuyWidget } from "thirdweb/react";
|
||||
import {
|
||||
reportAssetBuyFailed,
|
||||
reportAssetBuySuccessful,
|
||||
} from "@/analytics/report";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
|
||||
export function BuyTokenEmbed(props: {
|
||||
client: ThirdwebClient;
|
||||
chain: Chain;
|
||||
tokenAddress: string;
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
return (
|
||||
<BuyWidget
|
||||
amount="1"
|
||||
chain={props.chain}
|
||||
className="!rounded-xl !w-full"
|
||||
client={props.client}
|
||||
connectOptions={{
|
||||
autoConnect: false,
|
||||
}}
|
||||
onError={(e) => {
|
||||
const errorMessage = parseError(e);
|
||||
reportAssetBuyFailed({
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: "DropERC20",
|
||||
error: errorMessage,
|
||||
});
|
||||
}}
|
||||
onSuccess={() => {
|
||||
reportAssetBuySuccessful({
|
||||
assetType: "coin",
|
||||
chainId: props.chain.id,
|
||||
contractType: "DropERC20",
|
||||
});
|
||||
}}
|
||||
theme={getSDKTheme(theme === "light" ? "light" : "dark")}
|
||||
tokenAddress={props.tokenAddress as `0x${string}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
-264
@@ -1,264 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { differenceInCalendarDays, format } from "date-fns";
|
||||
import { ArrowDownIcon, ArrowUpIcon, InfoIcon } from "lucide-react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { TokenPriceData } from "../_apis/token-price-data";
|
||||
|
||||
function PriceChartUI(props: {
|
||||
showTimeOfDay: boolean;
|
||||
data: Array<{
|
||||
date: string;
|
||||
price_usd: number;
|
||||
price_usd_cents: number;
|
||||
}>;
|
||||
}) {
|
||||
const data = props.data.map((item) => ({
|
||||
price: item.price_usd,
|
||||
time: new Date(item.date).getTime(),
|
||||
}));
|
||||
|
||||
return (
|
||||
<ThirdwebAreaChart
|
||||
cardContentClassName="p-0"
|
||||
chartClassName="aspect-[1.5] lg:aspect-[3]"
|
||||
className="border-none bg-transparent p-0"
|
||||
config={{
|
||||
price: {
|
||||
color: "hsl(var(--chart-1))",
|
||||
label: "Price",
|
||||
},
|
||||
}}
|
||||
data={data}
|
||||
hideLabel={false}
|
||||
isPending={false}
|
||||
toolTipLabelFormatter={getTooltipLabelFormatter(props.showTimeOfDay)}
|
||||
toolTipValueFormatter={(value) => {
|
||||
return tokenPriceUSDFormatter.format(value as number);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const tokenPriceUSDFormatter = new Intl.NumberFormat("en-US", {
|
||||
currency: "USD",
|
||||
maximumFractionDigits: 8,
|
||||
minimumFractionDigits: 0,
|
||||
notation: "compact",
|
||||
roundingMode: "halfEven",
|
||||
style: "currency",
|
||||
});
|
||||
|
||||
const marketCapFormatter = new Intl.NumberFormat("en-US", {
|
||||
currency: "USD",
|
||||
maximumFractionDigits: 0,
|
||||
minimumFractionDigits: 0,
|
||||
notation: "compact",
|
||||
roundingMode: "halfEven",
|
||||
style: "currency",
|
||||
});
|
||||
|
||||
const holdersFormatter = new Intl.NumberFormat("en-US", {
|
||||
maximumFractionDigits: 0,
|
||||
minimumFractionDigits: 0,
|
||||
notation: "compact",
|
||||
});
|
||||
|
||||
const percentChangeFormatter = new Intl.NumberFormat("en-US", {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 0,
|
||||
});
|
||||
|
||||
function getTooltipLabelFormatter(includeTimeOfDay: boolean) {
|
||||
return (_v: string, item: unknown) => {
|
||||
if (Array.isArray(item)) {
|
||||
const time = item[0].payload.time as number;
|
||||
return format(
|
||||
new Date(time),
|
||||
includeTimeOfDay ? "MMM d, yyyy hh:mm a" : "MMM d, yyyy",
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export function TokenStats(params: {
|
||||
chainId: number;
|
||||
contractAddress: string;
|
||||
tokenPriceData: TokenPriceData;
|
||||
}) {
|
||||
const [interval, setInterval] = useState<Interval>("max");
|
||||
|
||||
const filteredHistoricalPrices = useMemo(() => {
|
||||
const currentDate = new Date();
|
||||
|
||||
return params.tokenPriceData.historical_prices.filter((item) => {
|
||||
const date = new Date(item.date);
|
||||
const maxDiff =
|
||||
interval === "24h"
|
||||
? 1
|
||||
: interval === "7d"
|
||||
? 7
|
||||
: interval === "30d"
|
||||
? 30
|
||||
: interval === "1y"
|
||||
? 365
|
||||
: Number.MAX_SAFE_INTEGER;
|
||||
|
||||
return differenceInCalendarDays(currentDate, date) <= maxDiff;
|
||||
});
|
||||
}, [params.tokenPriceData, interval]);
|
||||
|
||||
const priceUsd = params.tokenPriceData.price_usd;
|
||||
const percentChange24h = params.tokenPriceData.percent_change_24h;
|
||||
const marketCap = params.tokenPriceData.market_cap_usd;
|
||||
const holders = params.tokenPriceData.holders;
|
||||
|
||||
const formattedAbsChange = percentChangeFormatter.format(
|
||||
Math.abs(percentChange24h),
|
||||
);
|
||||
|
||||
const isAlmostZeroChange =
|
||||
formattedAbsChange === percentChangeFormatter.format(0);
|
||||
|
||||
const formattedPriceUSD = tokenPriceUSDFormatter.format(priceUsd);
|
||||
|
||||
return (
|
||||
<div className="bg-card rounded-lg border">
|
||||
{/* price and change */}
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between p-4 lg:p-6">
|
||||
<div>
|
||||
<p className="mb-1 text-muted-foreground text-sm">Current Price</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<p
|
||||
className={cn(
|
||||
"font-bold text-3xl lg:text-4xl tracking-tight",
|
||||
formattedPriceUSD.length > 8 && "text-2xl lg:text-4xl",
|
||||
)}
|
||||
>
|
||||
{typeof priceUsd === "number" ? formattedPriceUSD : priceUsd}
|
||||
</p>
|
||||
<Badge
|
||||
className="gap-2 lg:text-sm text-xs px-1.5"
|
||||
variant={
|
||||
isAlmostZeroChange
|
||||
? "default"
|
||||
: percentChange24h > 0
|
||||
? "success"
|
||||
: "destructive"
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{isAlmostZeroChange ? null : percentChange24h > 0 ? (
|
||||
<ArrowUpIcon className="size-3" />
|
||||
) : (
|
||||
<ArrowDownIcon className="size-3" />
|
||||
)}
|
||||
<span>
|
||||
{isAlmostZeroChange ? "~0%" : `${formattedAbsChange}%`}
|
||||
</span>
|
||||
</div>
|
||||
<span>(1d)</span>
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IntervalSelector interval={interval} setInterval={setInterval} />
|
||||
</div>
|
||||
|
||||
<div className="px-0">
|
||||
<PriceChartUI
|
||||
data={filteredHistoricalPrices || []}
|
||||
showTimeOfDay={interval === "24h"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="px-4 lg:px-6 py-4 mt-6 border-t border-dashed flex gap-6 [&>*:not(:first-child)]:border-l [&>*:not(:first-child)]:pl-5 [&>*]:grow">
|
||||
<TokenStat
|
||||
label="Market Cap"
|
||||
tooltip="Market capitalization is the total value of all tokens in circulation, calculated by multiplying the current price by the circulating supply"
|
||||
value={
|
||||
typeof marketCap === "number"
|
||||
? marketCapFormatter.format(marketCap)
|
||||
: marketCap
|
||||
}
|
||||
/>
|
||||
|
||||
<TokenStat
|
||||
label="Number of Holders"
|
||||
tooltip="The total number of unique wallet addresses that currently hold this token on the blockchain"
|
||||
value={
|
||||
// show 0 value as N/A
|
||||
holders === 0
|
||||
? "N/A"
|
||||
: typeof holders === "number"
|
||||
? holdersFormatter.format(holders)
|
||||
: holders
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TokenStat(props: {
|
||||
value: string | number;
|
||||
label: string;
|
||||
tooltip: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p className="text-muted-foreground text-sm">{props.label}</p>
|
||||
<ToolTipLabel label={props.tooltip}>
|
||||
<InfoIcon className="size-3.5 text-muted-foreground" />
|
||||
</ToolTipLabel>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<p className="font-semibold text-foreground text-xl tracking-tight">
|
||||
{props.value}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type Interval = "24h" | "7d" | "30d" | "1y" | "max";
|
||||
|
||||
function IntervalSelector(props: {
|
||||
interval: Interval;
|
||||
setInterval: (timeframe: Interval) => void;
|
||||
}) {
|
||||
const intervals: Record<Interval, string> = {
|
||||
"1y": "1Y",
|
||||
"7d": "1W",
|
||||
"24h": "1D",
|
||||
"30d": "1M",
|
||||
max: "MAX",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{Object.entries(intervals).map(([key, value]) => (
|
||||
<Button
|
||||
className={cn(
|
||||
"rounded-full px-2.5 py-1.5 h-auto bg-background text-xs lg:text-sm",
|
||||
props.interval === key && "border-active-border bg-accent",
|
||||
)}
|
||||
key={key}
|
||||
onClick={() => props.setInterval(key as Interval)}
|
||||
size="sm"
|
||||
tabIndex={0}
|
||||
variant="outline"
|
||||
>
|
||||
{value}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+8
-23
@@ -9,23 +9,16 @@ import {
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useId, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
padHex,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebContract,
|
||||
toTokens,
|
||||
waitForReceipt,
|
||||
} from "thirdweb";
|
||||
import { padHex, type ThirdwebContract, toTokens } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import {
|
||||
claimTo,
|
||||
type getActiveClaimCondition,
|
||||
getApprovalForTransaction,
|
||||
} from "thirdweb/extensions/erc20";
|
||||
import { useActiveAccount, useSendTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { getClaimParams, maxUint256 } from "thirdweb/utils";
|
||||
import {
|
||||
reportAssetBuyFailed,
|
||||
@@ -37,9 +30,9 @@ import { DecimalInput } from "@/components/ui/decimal-input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Spinner } from "@/components/ui/Spinner";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { getSDKTheme } from "@/utils/sdk-component-theme";
|
||||
import { tryCatch } from "@/utils/try-catch";
|
||||
import { PublicPageConnectButton } from "../../../_components/PublicPageConnectButton";
|
||||
import { SupplyClaimedProgress } from "../../../_components/supply-claimed-progress";
|
||||
@@ -65,13 +58,8 @@ export function TokenDropClaim(props: {
|
||||
const [quantity, setQuantity] = useState("1");
|
||||
const account = useActiveAccount();
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const sendClaimTx = useSendTransaction({
|
||||
payModal: {
|
||||
theme: getSDKTheme(theme === "light" ? "light" : "dark"),
|
||||
},
|
||||
});
|
||||
const sendAndConfirmApproveTx = useSendAndConfirmTx();
|
||||
const sendAndConfirmClaimTx = useSendAndConfirmTx();
|
||||
|
||||
const [successScreen, setSuccessScreen] = useState<
|
||||
| undefined
|
||||
@@ -116,10 +104,7 @@ export function TokenDropClaim(props: {
|
||||
});
|
||||
|
||||
const approveTxResult = await tryCatch(
|
||||
sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: approveTx,
|
||||
}),
|
||||
sendAndConfirmApproveTx.mutateAsync(approveTx),
|
||||
);
|
||||
|
||||
if (approveTxResult.error) {
|
||||
@@ -152,8 +137,8 @@ export function TokenDropClaim(props: {
|
||||
}
|
||||
|
||||
async function sendAndConfirm() {
|
||||
const result = await sendClaimTx.mutateAsync(transaction);
|
||||
return await waitForReceipt(result);
|
||||
const result = await sendAndConfirmClaimTx.mutateAsync(transaction);
|
||||
return result;
|
||||
}
|
||||
|
||||
setStepsUI({
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
export const mapChainIdToDexScreenerChainSlug = {
|
||||
8453: "base",
|
||||
1: "ethereum",
|
||||
56: "bsc",
|
||||
369: "pulsechain",
|
||||
137: "polygon",
|
||||
2741: "abstract",
|
||||
43114: "avalanche",
|
||||
999: "hyperliquid",
|
||||
480: "worldchain",
|
||||
42161: "arbitrum",
|
||||
388: "cronos",
|
||||
59144: "linea",
|
||||
1514: "story",
|
||||
397: "near",
|
||||
295: "hedera",
|
||||
146: "sonic",
|
||||
10: "optimism",
|
||||
80094: "berachain",
|
||||
57073: "ink",
|
||||
130: "unichain",
|
||||
5000: "mantle",
|
||||
324: "zksync",
|
||||
466: "apechain",
|
||||
1116: "core",
|
||||
250: "fantom",
|
||||
1868: "soneium",
|
||||
81457: "blast",
|
||||
2000: "dogechain",
|
||||
14: "flare",
|
||||
2040: "vana",
|
||||
4337: "beam",
|
||||
109: "shibarium",
|
||||
747: "flowevm",
|
||||
1088: "metis",
|
||||
1030: "conflux",
|
||||
43113: "avalanchedfk",
|
||||
534352: "scroll",
|
||||
747474: "katana",
|
||||
42220: "celo",
|
||||
1284: "moonbeam",
|
||||
4200: "merlinchain",
|
||||
2222: "kava",
|
||||
39797: "energi",
|
||||
34443: "mode",
|
||||
252: "fraxtal",
|
||||
48900: "zircuit",
|
||||
20: "elastos",
|
||||
100: "gnosischain",
|
||||
204: "opbnb",
|
||||
169: "manta",
|
||||
1313161554: "aurora",
|
||||
3073: "movement",
|
||||
4689: "iotex",
|
||||
23294: "oasissapphire",
|
||||
6001: "bouncebit",
|
||||
42170: "arbitrumnova",
|
||||
1101: "polygonzkevm",
|
||||
40: "telos",
|
||||
592: "astar",
|
||||
42262: "oasisemerald",
|
||||
1285: "moonriver",
|
||||
245022934: "neonevm",
|
||||
7777777: "zora",
|
||||
122: "fuse",
|
||||
321: "kcc",
|
||||
1234: "stepnetwork",
|
||||
106: "velas",
|
||||
167000: "taiko",
|
||||
288: "boba",
|
||||
42766: "zkfair",
|
||||
32520: "bitgert",
|
||||
82: "meter",
|
||||
} as const;
|
||||
|
||||
export type DexScreenerChainSlug =
|
||||
(typeof mapChainIdToDexScreenerChainSlug)[keyof typeof mapChainIdToDexScreenerChainSlug];
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
import { Spinner } from "@workspace/ui/components/spinner";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useMemo } from "react";
|
||||
import { ClientOnly } from "@/components/blocks/client-only";
|
||||
import type { DexScreenerChainSlug } from "./dex-screener-chains";
|
||||
|
||||
function DexScreenerIframe(props: {
|
||||
chain: DexScreenerChainSlug;
|
||||
contractAddress: string;
|
||||
}) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const iframeUrl = useMemo(() => {
|
||||
const resolvedTheme = theme === "light" ? "light" : "dark";
|
||||
const url = new URL("https://dexscreener.com");
|
||||
url.pathname = `${props.chain}/${props.contractAddress}`;
|
||||
url.searchParams.set("embed", "1");
|
||||
url.searchParams.set("loadChartSettings", "0");
|
||||
url.searchParams.set("chartTheme", resolvedTheme);
|
||||
url.searchParams.set("theme", resolvedTheme);
|
||||
url.searchParams.set("trades", "1");
|
||||
url.searchParams.set("chartStyle", "1");
|
||||
url.searchParams.set("chartLeftToolbar", "0");
|
||||
url.searchParams.set("chartType", "usd");
|
||||
url.searchParams.set("interval", "15");
|
||||
url.searchParams.set("chartDefaultOnMobile", "1");
|
||||
return url.toString();
|
||||
}, [theme, props.chain, props.contractAddress]);
|
||||
|
||||
return (
|
||||
<iframe
|
||||
className="w-full h-[500px] lg:h-[1000px] rounded-lg overflow-hidden border bg-card"
|
||||
src={iframeUrl}
|
||||
title="DexScreener"
|
||||
></iframe>
|
||||
);
|
||||
}
|
||||
|
||||
export function DexScreener(props: {
|
||||
chain: DexScreenerChainSlug;
|
||||
contractAddress: string;
|
||||
}) {
|
||||
return (
|
||||
<ClientOnly
|
||||
ssr={
|
||||
<div className="w-full h-[500px] lg:h-[1000px] rounded-lg border bg-card items-center justify-center flex">
|
||||
<Spinner className="size-10" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DexScreenerIframe
|
||||
chain={props.chain}
|
||||
contractAddress={props.contractAddress}
|
||||
/>
|
||||
</ClientOnly>
|
||||
);
|
||||
}
|
||||
+59
-71
@@ -3,18 +3,18 @@ import type { ThirdwebContract } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { getContractMetadata } from "thirdweb/extensions/common";
|
||||
import { decimals, getActiveClaimCondition } from "thirdweb/extensions/erc20";
|
||||
import { GridPattern } from "@/components/ui/background-patterns";
|
||||
import { BuyAndSwapEmbed } from "@/components/blocks/BuyAndSwapEmbed";
|
||||
import { GridPatternEmbedContainer } from "@/components/blocks/grid-pattern-embed-container";
|
||||
import { HAS_USED_DASHBOARD } from "@/constants/cookie";
|
||||
import { resolveFunctionSelectors } from "@/lib/selectors";
|
||||
import { AssetPageView } from "../_components/asset-page-view";
|
||||
import { getContractCreator } from "../_components/getContractCreator";
|
||||
import { PageHeader } from "../_components/PageHeader";
|
||||
import { getTokenPriceData } from "./_apis/token-price-data";
|
||||
import { ContractHeaderUI } from "./_components/ContractHeader";
|
||||
import { TokenDropClaim } from "./_components/claim-tokens/claim-tokens-ui";
|
||||
import { ContractAnalyticsOverview } from "./_components/contract-analytics/contract-analytics";
|
||||
import { BuyTokenEmbed } from "./_components/PayEmbedSection";
|
||||
import { TokenStats } from "./_components/PriceChart";
|
||||
import { DexScreener } from "./_components/dex-screener";
|
||||
import { mapChainIdToDexScreenerChainSlug } from "./_components/dex-screener-chains";
|
||||
import { RecentTransfers } from "./_components/RecentTransfers";
|
||||
import { fetchTokenInfoFromBridge } from "./_utils/fetch-coin-info";
|
||||
import { getCurrencyMeta } from "./_utils/getCurrencyMeta";
|
||||
@@ -28,9 +28,8 @@ export async function ERC20PublicPage(props: {
|
||||
contractMetadata,
|
||||
activeClaimCondition,
|
||||
tokenDecimals,
|
||||
tokenInfo,
|
||||
tokenInfoFromUB,
|
||||
functionSelectors,
|
||||
tokenPriceData,
|
||||
] = await Promise.all([
|
||||
getContractMetadata({
|
||||
contract: props.serverContract,
|
||||
@@ -45,14 +44,10 @@ export async function ERC20PublicPage(props: {
|
||||
tokenAddress: props.serverContract.address,
|
||||
}),
|
||||
resolveFunctionSelectors(props.serverContract),
|
||||
getTokenPriceData({
|
||||
chainId: props.serverContract.chain.id,
|
||||
contractAddress: props.serverContract.address,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (!contractMetadata.image && tokenInfo) {
|
||||
contractMetadata.image = tokenInfo.iconUri;
|
||||
if (!contractMetadata.image && tokenInfoFromUB) {
|
||||
contractMetadata.image = tokenInfoFromUB.iconUri;
|
||||
}
|
||||
|
||||
const [contractCreator, claimConditionCurrencyMeta] = await Promise.all([
|
||||
@@ -70,32 +65,23 @@ export async function ERC20PublicPage(props: {
|
||||
const cookieStore = await cookies();
|
||||
const isDashboardUser = cookieStore.has(HAS_USED_DASHBOARD);
|
||||
|
||||
const buyEmbed = (
|
||||
<BuyEmbed
|
||||
chainMetadata={props.chainMetadata}
|
||||
claimConditionMeta={
|
||||
activeClaimCondition && claimConditionCurrencyMeta
|
||||
? {
|
||||
activeClaimCondition,
|
||||
claimConditionCurrency: claimConditionCurrencyMeta,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
clientContract={props.clientContract}
|
||||
tokenAddress={props.clientContract.address}
|
||||
tokenDecimals={tokenDecimals}
|
||||
tokenName={contractMetadata.name}
|
||||
tokenSymbol={contractMetadata.symbol}
|
||||
/>
|
||||
);
|
||||
const claimConditionMeta =
|
||||
activeClaimCondition && claimConditionCurrencyMeta
|
||||
? {
|
||||
activeClaimCondition,
|
||||
claimConditionCurrency: claimConditionCurrencyMeta,
|
||||
}
|
||||
: undefined;
|
||||
const isUBSupported = !!tokenInfoFromUB;
|
||||
const showBuyEmbed = isUBSupported || claimConditionMeta;
|
||||
|
||||
return (
|
||||
<div className="flex grow flex-col">
|
||||
<AssetPageView assetType="coin" chainId={props.chainMetadata.chainId} />
|
||||
<PageHeader containerClassName="max-w-5xl" />
|
||||
<PageHeader containerClassName="max-w-7xl" />
|
||||
|
||||
<div className="border-b">
|
||||
<div className="container max-w-5xl">
|
||||
<div className="container max-w-7xl">
|
||||
<ContractHeaderUI
|
||||
chainMetadata={props.chainMetadata}
|
||||
clientContract={props.clientContract}
|
||||
@@ -114,45 +100,45 @@ export async function ERC20PublicPage(props: {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container flex max-w-5xl grow flex-col pt-8 pb-10">
|
||||
<div className="h-8" />
|
||||
|
||||
{showBuyEmbed && (
|
||||
<div className="container max-w-7xl pb-10">
|
||||
<GridPatternEmbedContainer>
|
||||
<BuyEmbed
|
||||
chainMetadata={props.chainMetadata}
|
||||
claimConditionMeta={claimConditionMeta}
|
||||
clientContract={props.clientContract}
|
||||
tokenAddress={props.clientContract.address}
|
||||
tokenDecimals={tokenDecimals}
|
||||
tokenName={contractMetadata.name}
|
||||
tokenSymbol={contractMetadata.symbol}
|
||||
/>
|
||||
</GridPatternEmbedContainer>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{props.chainMetadata.chainId in mapChainIdToDexScreenerChainSlug && (
|
||||
<div className="container max-w-7xl pb-10">
|
||||
<DexScreener
|
||||
chain={
|
||||
mapChainIdToDexScreenerChainSlug[
|
||||
props.chainMetadata
|
||||
.chainId as keyof typeof mapChainIdToDexScreenerChainSlug
|
||||
]
|
||||
}
|
||||
contractAddress={props.clientContract.address}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="container flex max-w-7xl grow flex-col pb-10">
|
||||
<div className="flex grow flex-col gap-8">
|
||||
<div className="sm:flex sm:justify-center w-full sm:border sm:border-dashed sm:bg-accent/20 sm:py-12 rounded-lg overflow-hidden relative">
|
||||
<GridPattern
|
||||
width={30}
|
||||
height={30}
|
||||
x={-1}
|
||||
y={-1}
|
||||
strokeDasharray={"4 2"}
|
||||
className="text-border dark:text-border/70"
|
||||
style={{
|
||||
maskImage:
|
||||
"linear-gradient(to bottom right,white,transparent,transparent)",
|
||||
}}
|
||||
/>
|
||||
<div className="sm:w-[420px] z-10">{buyEmbed}</div>
|
||||
</div>
|
||||
|
||||
{tokenPriceData ? (
|
||||
<>
|
||||
<TokenStats
|
||||
chainId={props.chainMetadata.chainId}
|
||||
contractAddress={props.clientContract.address}
|
||||
tokenPriceData={tokenPriceData}
|
||||
/>
|
||||
|
||||
<ContractAnalyticsOverview
|
||||
chainId={props.chainMetadata.chainId}
|
||||
chainSlug={props.chainMetadata.slug}
|
||||
contractAddress={props.clientContract.address}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<ContractAnalyticsOverview
|
||||
chainId={props.chainMetadata.chainId}
|
||||
chainSlug={props.chainMetadata.slug}
|
||||
contractAddress={props.clientContract.address}
|
||||
/>
|
||||
)}
|
||||
<ContractAnalyticsOverview
|
||||
chainId={props.chainMetadata.chainId}
|
||||
chainSlug={props.chainMetadata.slug}
|
||||
contractAddress={props.clientContract.address}
|
||||
/>
|
||||
|
||||
<RecentTransfers
|
||||
chainMetadata={props.chainMetadata}
|
||||
@@ -185,10 +171,12 @@ function BuyEmbed(props: {
|
||||
}) {
|
||||
if (!props.claimConditionMeta) {
|
||||
return (
|
||||
<BuyTokenEmbed
|
||||
<BuyAndSwapEmbed
|
||||
chain={props.clientContract.chain}
|
||||
client={props.clientContract.client}
|
||||
tokenAddress={props.clientContract.address}
|
||||
buyAmount={undefined}
|
||||
pageType="asset"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+3
-2
@@ -9,7 +9,7 @@ import { type ThirdwebContract, toTokens } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { getApprovalForTransaction } from "thirdweb/extensions/erc20";
|
||||
import { claimTo } from "thirdweb/extensions/erc1155";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { maxUint256 } from "thirdweb/utils";
|
||||
import * as z from "zod";
|
||||
import {
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { PublicPageConnectButton } from "../../../_components/PublicPageConnectButton";
|
||||
import { SupplyClaimedProgress } from "../../../_components/supply-claimed-progress";
|
||||
@@ -57,7 +58,7 @@ export function BuyEditionDrop(props: BuyEditionDropProps) {
|
||||
reValidateMode: "onChange",
|
||||
});
|
||||
const nftAmountToClaim = Number(form.watch("amount"));
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const account = useActiveAccount();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
+3
-2
@@ -4,12 +4,13 @@ import type { NFT, ThirdwebContract } from "thirdweb";
|
||||
import type { ChainMetadata } from "thirdweb/chains";
|
||||
import { getApprovalForTransaction } from "thirdweb/extensions/erc20";
|
||||
import { claimTo, getNFT } from "thirdweb/extensions/erc721";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { getClaimParams } from "thirdweb/utils";
|
||||
import {
|
||||
reportAssetBuyFailed,
|
||||
reportAssetBuySuccessful,
|
||||
} from "@/analytics/report";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { getCurrencyMeta } from "../../../erc20/_utils/getCurrencyMeta";
|
||||
import {
|
||||
@@ -27,7 +28,7 @@ export type BuyNFTDropProps = Omit<
|
||||
>;
|
||||
|
||||
export function BuyNFTDrop(props: BuyNFTDropProps) {
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const account = useActiveAccount();
|
||||
|
||||
const handleSubmit = async (form: BuyNFTDropForm) => {
|
||||
|
||||
+3
-2
@@ -10,7 +10,7 @@ import {
|
||||
getContractMetadata,
|
||||
setContractMetadata,
|
||||
} from "thirdweb/extensions/common";
|
||||
import { useReadContract, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useReadContract } from "thirdweb/react";
|
||||
import { resolveScheme } from "thirdweb/storage";
|
||||
import { z } from "zod";
|
||||
import { FileInput } from "@/components/blocks/FileInput";
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { CommonContractSchema } from "@/schema/schemas";
|
||||
import type { ExtensionDetectedState } from "@/types/ExtensionDetectedState";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
@@ -68,7 +69,7 @@ export const SettingsMetadata = ({
|
||||
isLoggedIn: boolean;
|
||||
}) => {
|
||||
const metadata = useReadContract(getContractMetadata, { contract });
|
||||
const sendTransaction = useSendAndConfirmTransaction();
|
||||
const sendTransaction = useSendAndConfirmTx();
|
||||
|
||||
const transformedQueryData = useMemo(() => {
|
||||
let socialUrls: z.infer<typeof SocialUrlSchema> = {
|
||||
|
||||
+3
-6
@@ -7,11 +7,7 @@ import {
|
||||
getPlatformFeeInfo,
|
||||
setPlatformFeeInfo,
|
||||
} from "thirdweb/extensions/common";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import z from "zod";
|
||||
import { BasisPointsInput } from "@/components/blocks/BasisPointsInput";
|
||||
import { AdminOnly } from "@/components/contracts/roles/admin-only";
|
||||
@@ -26,6 +22,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { AddressOrEnsSchema, BasisPointsSchema } from "@/schema/schemas";
|
||||
import type { ExtensionDetectedState } from "@/types/ExtensionDetectedState";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
@@ -53,7 +50,7 @@ export function SettingsPlatformFees({
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const address = useActiveAccount()?.address;
|
||||
const sendAndConfirmTx = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const platformFeesQuery = useReadContract(getPlatformFeeInfo, { contract });
|
||||
const platformFeeInfo = platformFeesQuery.data
|
||||
? {
|
||||
|
||||
+5
-8
@@ -7,11 +7,7 @@ import {
|
||||
primarySaleRecipient,
|
||||
setPrimarySaleRecipient,
|
||||
} from "thirdweb/extensions/common";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { AdminOnly } from "@/components/contracts/roles/admin-only";
|
||||
import { SolidityInput } from "@/components/solidity-inputs";
|
||||
@@ -25,6 +21,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { AddressOrEnsSchema } from "@/schema/schemas";
|
||||
import type { ExtensionDetectedState } from "@/types/ExtensionDetectedState";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
@@ -48,7 +45,7 @@ export const SettingsPrimarySale = ({
|
||||
const query = useReadContract(primarySaleRecipient, {
|
||||
contract,
|
||||
});
|
||||
const mutation = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
const transformedQueryData = {
|
||||
primary_sale_recipient: query.data,
|
||||
@@ -70,7 +67,7 @@ export const SettingsPrimarySale = ({
|
||||
saleRecipient,
|
||||
});
|
||||
|
||||
mutation.mutate(transaction, {
|
||||
sendAndConfirmTx.mutate(transaction, {
|
||||
onError: (error) => {
|
||||
toast.error("Error updating primary sale address", {
|
||||
description: parseError(error),
|
||||
@@ -131,7 +128,7 @@ export const SettingsPrimarySale = ({
|
||||
client={contract.client}
|
||||
disabled={query.isPending}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={mutation.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={undefined}
|
||||
txChainID={contract.chain.id}
|
||||
type="submit"
|
||||
|
||||
+6
-8
@@ -6,11 +6,8 @@ import {
|
||||
getDefaultRoyaltyInfo,
|
||||
setDefaultRoyaltyInfo,
|
||||
} from "thirdweb/extensions/common";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useSendAndConfirmTransaction,
|
||||
} from "thirdweb/react";
|
||||
import { useActiveAccount, useReadContract } from "thirdweb/react";
|
||||
|
||||
import { z } from "zod";
|
||||
import { BasisPointsInput } from "@/components/blocks/BasisPointsInput";
|
||||
import { AdminOnly } from "@/components/contracts/roles/admin-only";
|
||||
@@ -25,6 +22,7 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { AddressOrEnsSchema, BasisPointsSchema } from "@/schema/schemas";
|
||||
import type { ExtensionDetectedState } from "@/types/ExtensionDetectedState";
|
||||
@@ -70,7 +68,7 @@ export function SettingsRoyalties({
|
||||
const royaltyInfo = query.data
|
||||
? { fee_recipient: query.data[0], seller_fee_basis_points: query.data[1] }
|
||||
: undefined;
|
||||
const mutation = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const form = useForm<z.input<typeof CommonRoyaltySchema>>({
|
||||
defaultValues: royaltyInfo,
|
||||
resolver: zodResolver(CommonRoyaltySchema),
|
||||
@@ -96,7 +94,7 @@ export function SettingsRoyalties({
|
||||
royaltyBps: BigInt(d.seller_fee_basis_points),
|
||||
royaltyRecipient: d.fee_recipient,
|
||||
});
|
||||
mutation.mutate(transaction, {
|
||||
sendAndConfirmTx.mutate(transaction, {
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
onError(error);
|
||||
@@ -174,7 +172,7 @@ export function SettingsRoyalties({
|
||||
variant="default"
|
||||
disabled={query.isPending || !form.formState.isDirty}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={mutation.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={undefined}
|
||||
txChainID={contract.chain.id}
|
||||
type="submit"
|
||||
|
||||
+4
-4
@@ -4,8 +4,8 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { transferBatch } from "thirdweb/extensions/erc20";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { type AirdropAddressInput, AirdropUpload } from "./airdrop-upload";
|
||||
|
||||
@@ -24,7 +24,7 @@ export function TokenAirdropForm(props: {
|
||||
defaultValues: { addresses: [] },
|
||||
});
|
||||
|
||||
const sendTransaction = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const addresses = form.watch("addresses");
|
||||
// The real number should be slightly higher since there's a lil bit of overhead cost
|
||||
const estimateGasCost =
|
||||
@@ -41,7 +41,7 @@ export function TokenAirdropForm(props: {
|
||||
contract,
|
||||
});
|
||||
|
||||
const sendTxPromise = sendTransaction.mutateAsync(tx);
|
||||
const sendTxPromise = sendAndConfirmTx.mutateAsync(tx);
|
||||
toast.promise(sendTxPromise, {
|
||||
success: "Tokens airdropped successfully",
|
||||
error: (err) => ({
|
||||
@@ -85,7 +85,7 @@ export function TokenAirdropForm(props: {
|
||||
<TransactionButton
|
||||
client={contract.client}
|
||||
isLoggedIn={isLoggedIn}
|
||||
isPending={sendTransaction.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={1}
|
||||
txChainID={contract.chain.id}
|
||||
type="submit"
|
||||
|
||||
+4
-4
@@ -6,7 +6,6 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { type ThirdwebContract, toUnits } from "thirdweb";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const burnSchema = z.object({
|
||||
@@ -45,7 +45,7 @@ export function TokenBurnButton(props: {
|
||||
contract: ThirdwebContract;
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const sendConfirmation = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const form = useForm<z.infer<typeof burnSchema>>({
|
||||
defaultValues: { amount: "0" },
|
||||
resolver: zodResolver(burnSchema),
|
||||
@@ -65,7 +65,7 @@ export function TokenBurnButton(props: {
|
||||
contract: props.contract,
|
||||
});
|
||||
|
||||
const txPromise = sendConfirmation.mutateAsync(burnTokensTx);
|
||||
const txPromise = sendAndConfirmTx.mutateAsync(burnTokensTx);
|
||||
|
||||
toast.promise(txPromise, {
|
||||
error: (error) => ({
|
||||
@@ -123,7 +123,7 @@ export function TokenBurnButton(props: {
|
||||
client={props.contract.client}
|
||||
disabled={!form.formState.isDirty}
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
isPending={sendConfirmation.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={1}
|
||||
txChainID={props.contract.chain.id}
|
||||
type="submit"
|
||||
|
||||
+5
-4
@@ -6,7 +6,7 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { isAddress, type ThirdwebContract } from "thirdweb";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const claimFormSchema = z.object({
|
||||
@@ -54,7 +55,7 @@ export function TokenClaimButton(props: {
|
||||
contract: ThirdwebContract;
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const sendAndConfirmTransaction = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const account = useActiveAccount();
|
||||
|
||||
const form = useForm<ClaimFormValues>({
|
||||
@@ -80,7 +81,7 @@ export function TokenClaimButton(props: {
|
||||
});
|
||||
|
||||
if (approveTx) {
|
||||
const approveTxPromise = sendAndConfirmTransaction.mutateAsync(approveTx);
|
||||
const approveTxPromise = sendAndConfirmTx.mutateAsync(approveTx);
|
||||
toast.promise(approveTxPromise, {
|
||||
error: (error) => ({
|
||||
loading: "Approve Spending for claiming tokens",
|
||||
@@ -93,7 +94,7 @@ export function TokenClaimButton(props: {
|
||||
await approveTxPromise;
|
||||
}
|
||||
|
||||
const claimTxPromise = sendAndConfirmTransaction.mutateAsync(transaction);
|
||||
const claimTxPromise = sendAndConfirmTx.mutateAsync(transaction);
|
||||
|
||||
toast.promise(claimTxPromise, {
|
||||
error: (error) => ({
|
||||
|
||||
+5
-4
@@ -6,7 +6,7 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { MinterOnly } from "@/components/contracts/roles/minter-only";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const mintFormSchema = z.object({
|
||||
@@ -47,7 +48,7 @@ export function TokenMintButton(props: {
|
||||
isLoggedIn: boolean;
|
||||
}) {
|
||||
const address = useActiveAccount()?.address;
|
||||
const sendAndConfirmTransaction = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const form = useForm<MintFormValues>({
|
||||
resolver: zodResolver(mintFormSchema),
|
||||
defaultValues: { amount: "" },
|
||||
@@ -66,7 +67,7 @@ export function TokenMintButton(props: {
|
||||
to: address,
|
||||
});
|
||||
|
||||
const mintTxPromise = sendAndConfirmTransaction.mutateAsync(mintTx);
|
||||
const mintTxPromise = sendAndConfirmTx.mutateAsync(mintTx);
|
||||
toast.promise(mintTxPromise, {
|
||||
error: (err) => ({
|
||||
message: "Failed to mint tokens",
|
||||
@@ -118,7 +119,7 @@ export function TokenMintButton(props: {
|
||||
client={props.contract.client}
|
||||
disabled={!form.formState.isDirty || !form.formState.isValid}
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
isPending={sendAndConfirmTransaction.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
transactionCount={1}
|
||||
txChainID={props.contract.chain.id}
|
||||
type="submit"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user