Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8f496603c | ||
|
|
4c782ad64d | ||
|
|
af6a306955 | ||
|
|
4b319c353d | ||
|
|
aadaf209b5 | ||
|
|
df6f8e2646 | ||
|
|
5b871ac281 | ||
|
|
7eed8e61db | ||
|
|
6ef2f3b37a | ||
|
|
596aaa5495 | ||
|
|
89eeac1f8a | ||
|
|
c331d189bb | ||
|
|
f867799301 | ||
|
|
597431ec1d | ||
|
|
d5ff9f2f5d | ||
|
|
76694f0cec | ||
|
|
65a8c39ce0 | ||
|
|
ac3a95ebbb | ||
|
|
00ffe3e46e | ||
|
|
50c6371e1e |
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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("");
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 />;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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> = {
|
||||
|
||||
@@ -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
|
||||
? {
|
||||
|
||||
@@ -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,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,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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) => ({
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { isAddress, type ThirdwebContract, ZERO_ADDRESS } from "thirdweb";
|
||||
import * as ERC20Ext from "thirdweb/extensions/erc20";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { z } from "zod";
|
||||
import { SolidityInput } from "@/components/solidity-inputs";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
@@ -28,6 +27,7 @@ import {
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
|
||||
const transferFormSchema = z.object({
|
||||
@@ -62,7 +62,7 @@ export function TokenTransferButton(props: {
|
||||
defaultValues: { amount: "0", to: "" },
|
||||
resolver: zodResolver(transferFormSchema),
|
||||
});
|
||||
const sendAndConfirmTransaction = useSendAndConfirmTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
async function onSubmit(values: TransferFormValues) {
|
||||
const transferTx = ERC20Ext.transfer({
|
||||
@@ -71,7 +71,7 @@ export function TokenTransferButton(props: {
|
||||
to: values.to,
|
||||
});
|
||||
|
||||
const transferTxPromise = sendAndConfirmTransaction.mutateAsync(transferTx);
|
||||
const transferTxPromise = sendAndConfirmTx.mutateAsync(transferTx);
|
||||
|
||||
toast.promise(transferTxPromise, {
|
||||
error: (error) => ({
|
||||
@@ -149,7 +149,7 @@ export function TokenTransferButton(props: {
|
||||
<TransactionButton
|
||||
client={props.contract.client}
|
||||
isLoggedIn={props.isLoggedIn}
|
||||
isPending={sendAndConfirmTransaction.isPending}
|
||||
isPending={sendAndConfirmTx.isPending}
|
||||
type="submit"
|
||||
transactionCount={1}
|
||||
txChainID={props.contract.chain.id}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
getContractPublisher,
|
||||
publishContract,
|
||||
} from "thirdweb/extensions/thirdweb";
|
||||
import { useActiveAccount, useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import { reportContractPublished } from "@/analytics/report";
|
||||
import { CustomConnectWallet } from "@/components/connect-wallet";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
DASHBOARD_FORWARDER_ADDRESS,
|
||||
} from "@/constants/misc";
|
||||
import { useEns, useFunctionParamsFromABI } from "@/hooks/contract-hooks";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useTxNotifications } from "@/hooks/useTxNotifications";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { ContractParamsFieldset } from "./contract-params-fieldset";
|
||||
@@ -44,7 +45,7 @@ export function ContractPublishForm(props: {
|
||||
"Failed to publish contract",
|
||||
);
|
||||
const account = useActiveAccount();
|
||||
const sendTx = useSendAndConfirmTransaction({
|
||||
const sendAndConfirmTx = useSendAndConfirmTx({
|
||||
gasless: {
|
||||
experimentalChainlessSupport: true,
|
||||
provider: "engine",
|
||||
@@ -179,7 +180,7 @@ export function ContractPublishForm(props: {
|
||||
form.watch("deployType") === "autoFactory" ? constructorParams : [];
|
||||
|
||||
// during loading and after success we should stay in loading state
|
||||
const isPending = sendTx.isPending || sendTx.isSuccess;
|
||||
const isPending = sendAndConfirmTx.isPending || sendAndConfirmTx.isSuccess;
|
||||
|
||||
const formId = useId();
|
||||
|
||||
@@ -252,7 +253,7 @@ export function ContractPublishForm(props: {
|
||||
previousMetadata: props.publishMetadata,
|
||||
});
|
||||
|
||||
sendTx.mutate(tx, {
|
||||
sendAndConfirmTx.mutate(tx, {
|
||||
onError: (err) => {
|
||||
console.error("Failed to publish contract", err);
|
||||
onError(err);
|
||||
@@ -390,9 +391,9 @@ export function ContractPublishForm(props: {
|
||||
type="submit"
|
||||
>
|
||||
{isPending && <Spinner className="size-4" />}
|
||||
{sendTx.isSuccess
|
||||
{sendAndConfirmTx.isSuccess
|
||||
? "Preparing page"
|
||||
: sendTx.isPending
|
||||
: sendAndConfirmTx.isPending
|
||||
? "Publishing contract"
|
||||
: "Publish Contract"}
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { format } from "date-fns";
|
||||
import { ChevronDownIcon, UserIcon } from "lucide-react";
|
||||
import { ChevronDownIcon, StarIcon, UserIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
@@ -20,6 +21,7 @@ import { Spinner } from "@/components/ui/Spinner";
|
||||
import { AutoResizeTextarea } from "@/components/ui/textarea";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ThirdwebMiniLogo } from "../../../../../../components/ThirdwebMiniLogo";
|
||||
import { checkFeedbackStatus, submitSupportFeedback } from "../apis/feedback";
|
||||
import { sendMessageToTicket } from "../apis/support";
|
||||
import type { SupportMessage, SupportTicket } from "../types/tickets";
|
||||
import {
|
||||
@@ -37,6 +39,77 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
|
||||
const [isSubmittingReply, setIsSubmittingReply] = useState(false);
|
||||
const [localMessages, setLocalMessages] = useState(ticket.messages || []);
|
||||
|
||||
// rating/feedback
|
||||
const [rating, setRating] = useState(0);
|
||||
const [feedback, setFeedback] = useState("");
|
||||
|
||||
// Check if feedback has already been submitted for this ticket
|
||||
const feedbackStatusQuery = useQuery({
|
||||
queryKey: ["feedbackStatus", ticket.id],
|
||||
queryFn: async () => {
|
||||
const result = await checkFeedbackStatus(ticket.id);
|
||||
if ("error" in result) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result.hasFeedback;
|
||||
},
|
||||
enabled: ticket.status === "closed",
|
||||
staleTime: 60_000,
|
||||
gcTime: 5 * 60_000,
|
||||
});
|
||||
|
||||
const feedbackSubmitted = feedbackStatusQuery.data ?? false;
|
||||
const isLoading = feedbackStatusQuery.isLoading;
|
||||
const hasError = feedbackStatusQuery.isError;
|
||||
|
||||
const handleStarClick = (starIndex: number) => {
|
||||
setRating(starIndex + 1);
|
||||
};
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const submitFeedbackMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (rating === 0) {
|
||||
throw new Error("Please select a rating");
|
||||
}
|
||||
const result = await submitSupportFeedback({
|
||||
rating,
|
||||
feedback,
|
||||
ticketId: ticket.id,
|
||||
});
|
||||
if ("error" in result) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Thank you for your feedback!");
|
||||
setRating(0);
|
||||
setFeedback("");
|
||||
// mark as submitted immediately
|
||||
queryClient.setQueryData(["feedbackStatus", ticket.id], true);
|
||||
},
|
||||
onError: (err) => {
|
||||
console.error("Failed to submit feedback:", err);
|
||||
const msg = err instanceof Error ? err.message : String(err ?? "");
|
||||
let message = "Failed to submit feedback. Please try again.";
|
||||
if (/network|fetch/i.test(msg)) {
|
||||
message = "Network error. Please check your connection and try again.";
|
||||
} else if (
|
||||
/validation|Rating must be|Please select a rating/i.test(msg)
|
||||
) {
|
||||
message = msg; // show precise user-facing validation error
|
||||
} else if (/API Server error/i.test(msg)) {
|
||||
message = "Server error. Please try again later.";
|
||||
}
|
||||
toast.error(message);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSendFeedback = () => {
|
||||
submitFeedbackMutation.mutate();
|
||||
};
|
||||
|
||||
const handleSendReply = async () => {
|
||||
if (!team.unthreadCustomerId) {
|
||||
toast.error("No unthread customer id found for this team");
|
||||
@@ -149,11 +222,88 @@ export function SupportCaseDetails({ ticket, team }: SupportCaseDetailsProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{ticket.status === "closed" && (
|
||||
{ticket.status === "closed" && isLoading && (
|
||||
<div className="border-t p-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Spinner className="size-4" />
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Checking feedback status...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ticket.status === "closed" && !isLoading && !feedbackSubmitted && (
|
||||
<div className="border-t p-6">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
This ticket is closed. If you need further assistance, please
|
||||
create a new ticket.
|
||||
This ticket is closed. Give us a quick rating to let us know how
|
||||
we did!
|
||||
</p>
|
||||
{hasError && (
|
||||
<p className="text-destructive text-xs mt-2">
|
||||
Couldn't verify prior feedback right now — you can still submit
|
||||
a rating.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex gap-2 mb-6 mt-4">
|
||||
{[1, 2, 3, 4, 5].map((starValue) => (
|
||||
<button
|
||||
key={`star-${starValue}`}
|
||||
type="button"
|
||||
onClick={() => handleStarClick(starValue - 1)}
|
||||
className="transition-colors"
|
||||
aria-label={`Rate ${starValue} out of 5 stars`}
|
||||
>
|
||||
<StarIcon
|
||||
size={32}
|
||||
className={cn(
|
||||
"transition-colors",
|
||||
starValue <= rating
|
||||
? "text-pink-500 fill-current stroke-current"
|
||||
: "text-muted-foreground fill-current stroke-current",
|
||||
"hover:text-pink-500",
|
||||
)}
|
||||
strokeWidth={starValue <= rating ? 2 : 1}
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<AutoResizeTextarea
|
||||
value={feedback}
|
||||
onChange={(e) => setFeedback(e.target.value)}
|
||||
placeholder="Optional: Tell us how we can improve."
|
||||
maxLength={1000}
|
||||
className="text-sm w-full bg-card text-foreground rounded-lg p-4 pr-28 min-h-[100px] resize-none border border-border focus:outline-none placeholder:text-muted-foreground"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSendFeedback}
|
||||
disabled={submitFeedbackMutation.isPending || rating === 0}
|
||||
className="absolute bottom-3 right-3 rounded-full h-auto py-2 px-4"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
>
|
||||
{submitFeedbackMutation.isPending ? (
|
||||
<>
|
||||
<Spinner className="size-4 mr-2" />
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
"Send Feedback"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ticket.status === "closed" && feedbackSubmitted && (
|
||||
<div className="border-t p-6">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Thank you for your feedback! We appreciate your input and will use
|
||||
it to improve our service.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
"use server";
|
||||
|
||||
type FeedbackSubmitResult = { success: true } | { error: string };
|
||||
type FeedbackStatusResult = { hasFeedback: boolean } | { error: string };
|
||||
|
||||
export async function submitSupportFeedback({
|
||||
rating,
|
||||
feedback,
|
||||
ticketId,
|
||||
}: {
|
||||
rating: number;
|
||||
feedback?: string;
|
||||
ticketId: string;
|
||||
}): Promise<FeedbackSubmitResult> {
|
||||
try {
|
||||
// Fail fast on missing configuration
|
||||
const siwaUrl =
|
||||
process.env.SIWA_URL ?? process.env.NEXT_PUBLIC_SIWA_URL ?? "";
|
||||
|
||||
if (!siwaUrl) {
|
||||
throw new Error("SIWA URL not configured");
|
||||
}
|
||||
|
||||
const apiKey = process.env.SERVICE_AUTH_KEY_SIWA;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error("SERVICE_AUTH_KEY_SIWA not configured");
|
||||
}
|
||||
|
||||
// Basic input validation/normalization
|
||||
if (!ticketId?.trim()) {
|
||||
return { error: "ticketId is required." };
|
||||
}
|
||||
|
||||
if (!Number.isInteger(rating) || rating < 1 || rating > 5) {
|
||||
return { error: "Rating must be an integer between 1 and 5." };
|
||||
}
|
||||
|
||||
const normalizedFeedback = (feedback ?? "")
|
||||
.toString()
|
||||
.trim()
|
||||
.slice(0, 1000); // hard cap length
|
||||
|
||||
const payload = {
|
||||
rating: rating.toString(),
|
||||
feedback: normalizedFeedback,
|
||||
ticket_id: ticketId,
|
||||
};
|
||||
|
||||
const ac = new AbortController();
|
||||
const t = setTimeout(() => ac.abort(), 10_000);
|
||||
const response = await fetch(`${siwaUrl}/v1/csat/saveCSATFeedback`, {
|
||||
method: "POST",
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-service-api-key": apiKey,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: ac.signal,
|
||||
}).finally(() => clearTimeout(t));
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return { error: `API Server error: ${response.status} - ${errorText}` };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Feedback submission error:", error);
|
||||
return { error: "Internal server error" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkFeedbackStatus(
|
||||
ticketId: string,
|
||||
): Promise<FeedbackStatusResult> {
|
||||
try {
|
||||
// Basic input validation
|
||||
if (!ticketId?.trim()) {
|
||||
return { error: "ticketId is required." };
|
||||
}
|
||||
|
||||
// Fail fast on missing configuration
|
||||
const siwaUrl =
|
||||
process.env.SIWA_URL ?? process.env.NEXT_PUBLIC_SIWA_URL ?? "";
|
||||
|
||||
if (!siwaUrl) {
|
||||
throw new Error("SIWA URL not configured");
|
||||
}
|
||||
|
||||
const apiKey = process.env.SERVICE_AUTH_KEY_SIWA;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new Error("SERVICE_AUTH_KEY_SIWA not configured");
|
||||
}
|
||||
|
||||
const fullUrl = `${siwaUrl}/v1/csat/getCSATFeedback?ticket_id=${encodeURIComponent(ticketId)}`;
|
||||
|
||||
const ac = new AbortController();
|
||||
const t = setTimeout(() => ac.abort(), 10_000);
|
||||
const response = await fetch(fullUrl, {
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-service-api-key": apiKey,
|
||||
},
|
||||
signal: ac.signal,
|
||||
}).finally(() => clearTimeout(t));
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
return { error: `API Server error: ${response.status} - ${errorText}` };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return { hasFeedback: data.has_feedback };
|
||||
} catch (error) {
|
||||
console.error("Feedback status check error:", error);
|
||||
return { error: "Internal server error" };
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
"use client";
|
||||
import { useRef } from "react";
|
||||
import {
|
||||
encode,
|
||||
getContract,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebClient,
|
||||
} from "thirdweb";
|
||||
import { encode, getContract, type ThirdwebClient } from "thirdweb";
|
||||
import { deployERC721Contract, deployERC1155Contract } from "thirdweb/deploys";
|
||||
import { multicall } from "thirdweb/extensions/common";
|
||||
import {
|
||||
@@ -26,6 +21,7 @@ import { reportContractDeployed } from "@/analytics/report";
|
||||
import type { Team } from "@/api/team/get-team";
|
||||
import { useGetV5DashboardChain } from "@/hooks/chains/v5-adapter";
|
||||
import { useAddContractToProject } from "@/hooks/project-contracts";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import type { CreateNFTCollectionAllValues } from "./_common/form";
|
||||
import { CreateNFTPageUI } from "./create-nft-page-ui";
|
||||
|
||||
@@ -42,7 +38,7 @@ export function CreateNFTPage(props: {
|
||||
const addContractToProject = useAddContractToProject();
|
||||
const contractAddressRef = useRef<string | undefined>(undefined);
|
||||
const getChain = useGetV5DashboardChain();
|
||||
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
function getAccount(params: { gasless: boolean }) {
|
||||
if (!activeAccount) {
|
||||
throw new Error("Wallet is not connected");
|
||||
@@ -163,10 +159,10 @@ export function CreateNFTPage(props: {
|
||||
const contract = getDeployedContract({
|
||||
chain: values.collectionInfo.chain,
|
||||
});
|
||||
|
||||
const account = getAccount({
|
||||
gasless: params.gasless,
|
||||
});
|
||||
// TODO - when gasless is enabled - change how the tx is sent
|
||||
// const account = getAccount({
|
||||
// gasless: params.gasless,
|
||||
// });
|
||||
|
||||
const lazyMintFn = ercType === "erc721" ? lazyMint721 : lazyMint1155;
|
||||
|
||||
@@ -175,10 +171,7 @@ export function CreateNFTPage(props: {
|
||||
nfts: values.nfts,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(transaction);
|
||||
}
|
||||
|
||||
async function handleSetClaimConditionsERC721(params: {
|
||||
@@ -189,10 +182,10 @@ export function CreateNFTPage(props: {
|
||||
const contract = getDeployedContract({
|
||||
chain: values.collectionInfo.chain,
|
||||
});
|
||||
|
||||
const account = getAccount({
|
||||
gasless: params.gasless,
|
||||
});
|
||||
// TODO - when gasless is enabled - change how the tx is sent
|
||||
// const account = getAccount({
|
||||
// gasless: params.gasless,
|
||||
// });
|
||||
|
||||
const firstNFT = values.nfts[0];
|
||||
if (!firstNFT) {
|
||||
@@ -215,10 +208,7 @@ export function CreateNFTPage(props: {
|
||||
],
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(transaction);
|
||||
}
|
||||
|
||||
async function handleSetClaimConditionsERC1155(params: {
|
||||
@@ -234,9 +224,10 @@ export function CreateNFTPage(props: {
|
||||
chain: values.collectionInfo.chain,
|
||||
});
|
||||
|
||||
const account = getAccount({
|
||||
gasless: params.gasless,
|
||||
});
|
||||
// TODO - when gasless is enabled - change how the tx is sent
|
||||
// const account = getAccount({
|
||||
// gasless: params.gasless,
|
||||
// });
|
||||
|
||||
const endIndexExclusive = batch.startIndex + batch.count;
|
||||
const nfts = values.nfts.slice(batch.startIndex, endIndexExclusive);
|
||||
@@ -286,10 +277,7 @@ export function CreateNFTPage(props: {
|
||||
data: encodedTransactions,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: tx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(tx);
|
||||
}
|
||||
|
||||
async function handleSetAdmins(params: {
|
||||
@@ -331,10 +319,7 @@ export function CreateNFTPage(props: {
|
||||
data: encodedTxs,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: tx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(tx);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
getAddress,
|
||||
getContract,
|
||||
NATIVE_TOKEN_ADDRESS,
|
||||
sendAndConfirmTransaction,
|
||||
type ThirdwebClient,
|
||||
toUnits,
|
||||
toWei,
|
||||
@@ -19,9 +18,10 @@ import {
|
||||
} from "thirdweb/extensions/erc20";
|
||||
import { useActiveAccount } from "thirdweb/react";
|
||||
import {
|
||||
createToken,
|
||||
distributeToken,
|
||||
getDeployedEntrypointERC20,
|
||||
getTokenAddressFromReceipt,
|
||||
prepareCreateToken,
|
||||
} from "thirdweb/tokens";
|
||||
import type { ClaimConditionsInput } from "thirdweb/utils";
|
||||
import { create7702MinimalAccount } from "thirdweb/wallets/smart";
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
} from "@/constants/addresses";
|
||||
import { useGetV5DashboardChain } from "@/hooks/chains/v5-adapter";
|
||||
import { useAddContractToProject } from "@/hooks/project-contracts";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { pollWithTimeout } from "@/utils/pollWithTimeout";
|
||||
import { createTokenOnUniversalBridge } from "../_apis/create-token-on-bridge";
|
||||
import type { CreateAssetFormValues } from "./_common/form";
|
||||
@@ -53,6 +54,7 @@ export function CreateTokenAssetPage(props: {
|
||||
const addContractToProject = useAddContractToProject();
|
||||
const contractAddressRef = useRef<string | undefined>(undefined);
|
||||
const getChain = useGetV5DashboardChain();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
|
||||
function getAccount(gasless: boolean) {
|
||||
if (!activeAccount) {
|
||||
@@ -132,7 +134,7 @@ export function CreateTokenAssetPage(props: {
|
||||
}
|
||||
}
|
||||
|
||||
const contractAddress = await createToken({
|
||||
const createTokenTx = await prepareCreateToken({
|
||||
account,
|
||||
chain: chain,
|
||||
client: props.client,
|
||||
@@ -163,6 +165,9 @@ export function CreateTokenAssetPage(props: {
|
||||
developerAddress: "0x1Af20C6B23373350aD464700B5965CE4B0D2aD94",
|
||||
});
|
||||
|
||||
const receipt = await sendAndConfirmTx.mutateAsync(createTokenTx);
|
||||
const contractAddress = await getTokenAddressFromReceipt(receipt);
|
||||
|
||||
// add contract to project in background
|
||||
addContractToProject.mutateAsync({
|
||||
chainId: params.values.chain,
|
||||
@@ -192,8 +197,7 @@ export function CreateTokenAssetPage(props: {
|
||||
values: CreateAssetFormValues;
|
||||
gasless: boolean;
|
||||
}) {
|
||||
const { values, gasless } = params;
|
||||
const account = getAccount(gasless);
|
||||
const { values } = params;
|
||||
const contract = getDeployedContract({ chain: values.chain });
|
||||
|
||||
const airdropTx = await distributeToken({
|
||||
@@ -206,18 +210,16 @@ export function CreateTokenAssetPage(props: {
|
||||
tokenAddress: contract.address,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: airdropTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(airdropTx);
|
||||
}
|
||||
|
||||
async function ERC20Asset_approveAirdropTokens(params: {
|
||||
values: CreateAssetFormValues;
|
||||
gasless: boolean;
|
||||
}) {
|
||||
const { values, gasless } = params;
|
||||
const account = getAccount(gasless);
|
||||
const { values } = params;
|
||||
// TODO - when gasless is enabled - change how the tx is sent
|
||||
// const account = getAccount(params.gasless);
|
||||
const contract = getDeployedContract({ chain: values.chain });
|
||||
|
||||
const totalAmountToAirdrop = values.airdropAddresses.reduce(
|
||||
@@ -240,10 +242,7 @@ export function CreateTokenAssetPage(props: {
|
||||
spender: entrypoint.address,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: approvalTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(approvalTx);
|
||||
}
|
||||
|
||||
// DropERC20 ----
|
||||
@@ -337,10 +336,7 @@ export function CreateTokenAssetPage(props: {
|
||||
contract,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: airdropTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(airdropTx);
|
||||
}
|
||||
|
||||
async function DropERC20_mintTokens(params: {
|
||||
@@ -380,10 +376,7 @@ export function CreateTokenAssetPage(props: {
|
||||
to: account.address,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: claimTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(claimTx);
|
||||
}
|
||||
|
||||
async function DropERC20_setClaimConditions(params: {
|
||||
@@ -439,10 +432,7 @@ export function CreateTokenAssetPage(props: {
|
||||
phases,
|
||||
});
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account,
|
||||
transaction: preparedTx,
|
||||
});
|
||||
await sendAndConfirmTx.mutateAsync(preparedTx);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
type ThirdwebContract,
|
||||
toTokens,
|
||||
} from "thirdweb";
|
||||
import { TokenIcon, TokenProvider, useSendTransaction } from "thirdweb/react";
|
||||
import { TokenIcon, TokenProvider } from "thirdweb/react";
|
||||
import { claimRewards } from "thirdweb/tokens";
|
||||
import { DistributionBarChart } from "@/components/blocks/distribution-chart";
|
||||
import { WalletAddress } from "@/components/blocks/wallet-address";
|
||||
import { TransactionButton } from "@/components/tx-button";
|
||||
import { useSendAndConfirmTx } from "@/hooks/useSendTx";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { parseError } from "@/utils/errorParser";
|
||||
import { tryCatch } from "@/utils/try-catch";
|
||||
@@ -37,7 +38,7 @@ export function ClaimRewardsPage(props: {
|
||||
};
|
||||
chainSlug: string;
|
||||
}) {
|
||||
const sendTx = useSendTransaction();
|
||||
const sendAndConfirmTx = useSendAndConfirmTx();
|
||||
const router = useDashboardRouter();
|
||||
|
||||
async function handleClaim() {
|
||||
@@ -47,7 +48,7 @@ export function ClaimRewardsPage(props: {
|
||||
});
|
||||
|
||||
const claimRewardsResult = await tryCatch(
|
||||
sendTx.mutateAsync(claimRewardsTx),
|
||||
sendAndConfirmTx.mutateAsync(claimRewardsTx),
|
||||
);
|
||||
|
||||
if (claimRewardsResult.error) {
|
||||
@@ -66,7 +67,7 @@ export function ClaimRewardsPage(props: {
|
||||
recipient={props.reward.recipient}
|
||||
developer={props.reward.developer}
|
||||
handleClaim={handleClaim}
|
||||
isClaimPending={sendTx.isPending}
|
||||
isClaimPending={sendAndConfirmTx.isPending}
|
||||
client={props.assetContractClient.client}
|
||||
chain={props.assetContractClient.chain}
|
||||
chainSlug={props.chainSlug}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { type UseChatHelpers, useChat } from "@ai-sdk/react";
|
||||
import type { ThirdwebAiMessage } from "@thirdweb-dev/ai-sdk-provider";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
import {
|
||||
DefaultChatTransport,
|
||||
lastAssistantMessageIsCompleteWithToolCalls,
|
||||
} from "ai";
|
||||
import { useMemo, useState } from "react";
|
||||
import { defineChain, prepareTransaction } from "thirdweb";
|
||||
import {
|
||||
@@ -31,30 +34,19 @@ import { Loader } from "../../../../components/loader";
|
||||
import { THIRDWEB_CLIENT } from "../../../../lib/client";
|
||||
|
||||
export function ChatContainer() {
|
||||
const [sessionId, setSessionId] = useState("");
|
||||
|
||||
const { messages, sendMessage, status, addToolResult } =
|
||||
useChat<ThirdwebAiMessage>({
|
||||
transport: new DefaultChatTransport({
|
||||
api: "/api/chat",
|
||||
}),
|
||||
onFinish: ({ message }) => {
|
||||
setSessionId(message.metadata?.session_id ?? "");
|
||||
},
|
||||
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
||||
});
|
||||
const [input, setInput] = useState("");
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
if (input.trim()) {
|
||||
sendMessage(
|
||||
{ text: input },
|
||||
{
|
||||
body: {
|
||||
sessionId,
|
||||
},
|
||||
},
|
||||
);
|
||||
sendMessage({ text: input });
|
||||
setInput("");
|
||||
}
|
||||
};
|
||||
@@ -101,7 +93,6 @@ export function ChatContainer() {
|
||||
addToolResult={addToolResult}
|
||||
sendMessage={sendMessage}
|
||||
toolCallId={part.toolCallId}
|
||||
sessionId={sessionId}
|
||||
/>
|
||||
);
|
||||
case "tool-sign_swap":
|
||||
@@ -113,7 +104,6 @@ export function ChatContainer() {
|
||||
addToolResult={addToolResult}
|
||||
sendMessage={sendMessage}
|
||||
toolCallId={part.toolCallId}
|
||||
sessionId={sessionId}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
@@ -158,14 +148,13 @@ type SignTransactionButtonProps = {
|
||||
{ type: "tool-sign_transaction" }
|
||||
>["input"]
|
||||
| undefined;
|
||||
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
|
||||
addToolResult: UseChatHelpers<ThirdwebAiMessage>["addToolResult"];
|
||||
sendMessage: UseChatHelpers<ThirdwebAiMessage>["sendMessage"];
|
||||
toolCallId: string;
|
||||
sendMessage: ReturnType<typeof useChat<ThirdwebAiMessage>>["sendMessage"];
|
||||
sessionId: string;
|
||||
};
|
||||
|
||||
const SignTransactionButton = (props: SignTransactionButtonProps) => {
|
||||
const { input, addToolResult, toolCallId, sendMessage, sessionId } = props;
|
||||
const { input, addToolResult, toolCallId, sendMessage } = props;
|
||||
const transactionData: {
|
||||
chain_id: number;
|
||||
to: string;
|
||||
@@ -209,21 +198,9 @@ const SignTransactionButton = (props: SignTransactionButtonProps) => {
|
||||
chain_id: transaction.chain.id,
|
||||
},
|
||||
});
|
||||
sendMessage(undefined, {
|
||||
body: {
|
||||
sessionId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={(error) => {
|
||||
sendMessage(
|
||||
{ text: `Transaction failed: ${error.message}` },
|
||||
{
|
||||
body: {
|
||||
sessionId,
|
||||
},
|
||||
},
|
||||
);
|
||||
sendMessage({ text: `Transaction failed: ${error.message}` });
|
||||
}}
|
||||
>
|
||||
Sign Transaction
|
||||
@@ -241,13 +218,12 @@ type SignSwapButtonProps = {
|
||||
{ type: "tool-sign_swap" }
|
||||
>["input"]
|
||||
| undefined;
|
||||
addToolResult: ReturnType<typeof useChat<ThirdwebAiMessage>>["addToolResult"];
|
||||
addToolResult: UseChatHelpers<ThirdwebAiMessage>["addToolResult"];
|
||||
sendMessage: UseChatHelpers<ThirdwebAiMessage>["sendMessage"];
|
||||
toolCallId: string;
|
||||
sendMessage: ReturnType<typeof useChat<ThirdwebAiMessage>>["sendMessage"];
|
||||
sessionId: string;
|
||||
};
|
||||
const SignSwapButton = (props: SignSwapButtonProps) => {
|
||||
const { input, addToolResult, toolCallId, sendMessage, sessionId } = props;
|
||||
const { input, addToolResult, toolCallId, sendMessage } = props;
|
||||
const transactionData: {
|
||||
chain_id: number;
|
||||
to: string;
|
||||
@@ -293,21 +269,9 @@ const SignSwapButton = (props: SignSwapButtonProps) => {
|
||||
chain_id: transaction.chain.id,
|
||||
},
|
||||
});
|
||||
sendMessage(undefined, {
|
||||
body: {
|
||||
sessionId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={(error) => {
|
||||
sendMessage(
|
||||
{ text: `Transaction failed: ${error.message}` },
|
||||
{
|
||||
body: {
|
||||
sessionId,
|
||||
},
|
||||
},
|
||||
);
|
||||
sendMessage({ text: `Transaction failed: ${error.message}` });
|
||||
}}
|
||||
>
|
||||
Sign swap
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function Page() {
|
||||
icon={BotIcon}
|
||||
title={title}
|
||||
description={description}
|
||||
docsLink="https://portal.thirdweb.com/ai/chat?utm_source=playground"
|
||||
docsLink="https://portal.thirdweb.com/ai/chat/ai-sdk?utm_source=playground"
|
||||
>
|
||||
<ChatExample />
|
||||
<div className="h-8" />
|
||||
@@ -69,12 +69,10 @@ const thirdwebAI = createThirdwebAI({
|
||||
});
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages, sessionId } = await req.json();
|
||||
|
||||
const { messages, id } = await req.json();
|
||||
const result = streamText({
|
||||
model: thirdwebAI.chat({
|
||||
model: thirdwebAI.chat(id, {
|
||||
context: {
|
||||
session_id: sessionId, // session id for continuity
|
||||
chain_ids: [8453], // optional chain ids
|
||||
from: "0x...", // optional from address
|
||||
auto_execute_transactions: true, // optional, defaults to false
|
||||
@@ -86,14 +84,6 @@ export async function POST(req: Request) {
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
sendReasoning: true, // optional, to send reasoning steps to the client
|
||||
messageMetadata({ part }) {
|
||||
// record session id for continuity
|
||||
if (part.type === "finish-step") {
|
||||
return {
|
||||
session_id: part.response.id,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -122,33 +112,19 @@ import { useState } from 'react';
|
||||
import { ThirdwebAiMessage } from '@thirdweb-dev/ai-sdk-provider';
|
||||
|
||||
export default function Page() {
|
||||
const [sessionId, setSessionId] = useState('');
|
||||
const { messages, sendMessage, status } = useChat<ThirdwebAiMessage>({
|
||||
const { messages, sendMessage } = useChat<ThirdwebAiMessage>({
|
||||
transport: new DefaultChatTransport({
|
||||
// see server implementation below
|
||||
api: '/api/chat',
|
||||
}),
|
||||
onFinish: ({ message }) => {
|
||||
// record session id for continuity
|
||||
setSessionId(message.metadata?.session_id ?? '');
|
||||
},
|
||||
});
|
||||
|
||||
const send = (message: string) => {
|
||||
sendMessage({ text: message }, {
|
||||
body: {
|
||||
// send session id for continuity
|
||||
sessionId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{messages.map(message => (
|
||||
<RenderMessage message={message} />
|
||||
))}
|
||||
<ChatInputBox send={send} />
|
||||
<ChatInputBox send={sendMessage} />
|
||||
</>
|
||||
);
|
||||
}`}
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
import { stream } from "fetch-event-stream";
|
||||
import type { NebulaTxData, NebulaUserMessage } from "./types";
|
||||
|
||||
const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
|
||||
|
||||
export type NebulaContext = {
|
||||
chainIds: string[] | null;
|
||||
walletAddress: string | null;
|
||||
sessionId: string | null;
|
||||
};
|
||||
|
||||
type NebulaSwapData = {
|
||||
action: string;
|
||||
transaction: {
|
||||
chainId: number;
|
||||
to: `0x${string}`;
|
||||
data: `0x${string}`;
|
||||
};
|
||||
to: {
|
||||
address: `0x${string}`;
|
||||
amount: string;
|
||||
chain_id: number;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
};
|
||||
from: {
|
||||
address: `0x${string}`;
|
||||
amount: string;
|
||||
chain_id: number;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
};
|
||||
intent: {
|
||||
amount: string;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: `0x${string}`;
|
||||
originChainId: number;
|
||||
originTokenAddress: `0x${string}`;
|
||||
receiver: `0x${string}`;
|
||||
sender: `0x${string}`;
|
||||
};
|
||||
};
|
||||
import {
|
||||
API_URL,
|
||||
type NebulaContext,
|
||||
type NebulaSwapData,
|
||||
type NebulaTxData,
|
||||
type NebulaUserMessage,
|
||||
} from "./types";
|
||||
|
||||
export async function promptNebula(params: {
|
||||
message: NebulaUserMessage;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const API_URL = `https://${process.env.NEXT_PUBLIC_API_URL || "api.thirdweb.com"}`;
|
||||
|
||||
type NebulaUserMessageContentItem =
|
||||
| {
|
||||
type: "image";
|
||||
@@ -27,3 +29,75 @@ export type NebulaTxData = {
|
||||
to: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type NebulaContext = {
|
||||
chainIds: string[] | null;
|
||||
walletAddress: string | null;
|
||||
sessionId: string | null;
|
||||
};
|
||||
|
||||
export type NebulaSwapData = {
|
||||
action: string;
|
||||
transaction: {
|
||||
chain_id: number;
|
||||
to: `0x${string}`;
|
||||
data: `0x${string}`;
|
||||
value: string;
|
||||
};
|
||||
intent: {
|
||||
amount: string;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: `0x${string}`;
|
||||
originChainId: number;
|
||||
originTokenAddress: `0x${string}`;
|
||||
receiver: `0x${string}`;
|
||||
sender: `0x${string}`;
|
||||
};
|
||||
};
|
||||
|
||||
// Simplified types for the playground version
|
||||
export type WalletMeta = {
|
||||
walletId: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
export type ChatMessage =
|
||||
| {
|
||||
type: "user";
|
||||
content: NebulaUserMessageContent;
|
||||
}
|
||||
| {
|
||||
text: string;
|
||||
type: "error";
|
||||
}
|
||||
| {
|
||||
texts: string[];
|
||||
type: "presence";
|
||||
}
|
||||
| {
|
||||
// assistant type message loaded from history doesn't have request_id
|
||||
request_id: string | undefined;
|
||||
text: string;
|
||||
type: "assistant";
|
||||
}
|
||||
| {
|
||||
type: "action";
|
||||
subtype: "sign_transaction";
|
||||
request_id: string;
|
||||
data: NebulaTxData;
|
||||
}
|
||||
| {
|
||||
type: "action";
|
||||
subtype: "sign_swap";
|
||||
request_id: string;
|
||||
data: NebulaSwapData;
|
||||
}
|
||||
| {
|
||||
type: "image";
|
||||
request_id: string;
|
||||
data: {
|
||||
width: number;
|
||||
height: number;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,116 +31,16 @@ import { MarkdownRenderer } from "@/components/ui/markdown-renderer";
|
||||
import { Img } from "../../../components/ui/Img";
|
||||
import { Spinner } from "../../../components/ui/Spinner";
|
||||
import { THIRDWEB_CLIENT } from "../../../lib/client";
|
||||
import { type NebulaContext, promptNebula } from "../api/chat";
|
||||
import type { NebulaUserMessage } from "../api/types";
|
||||
import { promptNebula } from "../api/chat";
|
||||
import type {
|
||||
ChatMessage,
|
||||
NebulaContext,
|
||||
NebulaUserMessage,
|
||||
WalletMeta,
|
||||
} from "../api/types";
|
||||
import { examplePrompts } from "../data/examplePrompts";
|
||||
import { resolveSchemeWithErrorHandler } from "./resolveSchemeWithErrorHandler";
|
||||
|
||||
// Simplified types for the playground version
|
||||
type WalletMeta = {
|
||||
walletId: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
type NebulaUserMessageContentItem =
|
||||
| {
|
||||
type: "image";
|
||||
image_url: string | null;
|
||||
b64: string | null;
|
||||
}
|
||||
| {
|
||||
type: "text";
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: "transaction";
|
||||
transaction_hash: string;
|
||||
chain_id: number;
|
||||
};
|
||||
|
||||
type NebulaUserMessageContent = NebulaUserMessageContentItem[];
|
||||
|
||||
type ChatMessage =
|
||||
| {
|
||||
type: "user";
|
||||
content: NebulaUserMessageContent;
|
||||
}
|
||||
| {
|
||||
text: string;
|
||||
type: "error";
|
||||
}
|
||||
| {
|
||||
texts: string[];
|
||||
type: "presence";
|
||||
}
|
||||
| {
|
||||
// assistant type message loaded from history doesn't have request_id
|
||||
request_id: string | undefined;
|
||||
text: string;
|
||||
type: "assistant";
|
||||
}
|
||||
| {
|
||||
type: "action";
|
||||
subtype: "sign_transaction";
|
||||
request_id: string;
|
||||
data: NebulaTxData;
|
||||
}
|
||||
| {
|
||||
type: "action";
|
||||
subtype: "sign_swap";
|
||||
request_id: string;
|
||||
data: NebulaSwapData;
|
||||
}
|
||||
| {
|
||||
type: "image";
|
||||
request_id: string;
|
||||
data: {
|
||||
width: number;
|
||||
height: number;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
|
||||
type NebulaTxData = {
|
||||
chain_id: number;
|
||||
data: `0x${string}`;
|
||||
to: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
type NebulaSwapData = {
|
||||
action: string;
|
||||
transaction: {
|
||||
chainId: number;
|
||||
to: `0x${string}`;
|
||||
data: `0x${string}`;
|
||||
value?: string;
|
||||
};
|
||||
to: {
|
||||
address: `0x${string}`;
|
||||
amount: string;
|
||||
chain_id: number;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
};
|
||||
from: {
|
||||
address: `0x${string}`;
|
||||
amount: string;
|
||||
chain_id: number;
|
||||
decimals: number;
|
||||
symbol: string;
|
||||
};
|
||||
intent: {
|
||||
amount: string;
|
||||
destinationChainId: number;
|
||||
destinationTokenAddress: `0x${string}`;
|
||||
originChainId: number;
|
||||
originTokenAddress: `0x${string}`;
|
||||
receiver: `0x${string}`;
|
||||
sender: `0x${string}`;
|
||||
};
|
||||
};
|
||||
|
||||
export function ChatPageContent(props: {
|
||||
client: ThirdwebClient;
|
||||
type: "landing" | "new-chat";
|
||||
@@ -752,7 +652,7 @@ function RenderMessage(props: {
|
||||
transaction={() =>
|
||||
prepareTransaction({
|
||||
client: THIRDWEB_CLIENT,
|
||||
chain: defineChain(message.data.transaction.chainId),
|
||||
chain: defineChain(message.data.transaction.chain_id),
|
||||
data: message.data.transaction.data,
|
||||
to: message.data.transaction.to,
|
||||
value: message.data.transaction.value
|
||||
@@ -768,7 +668,7 @@ function RenderMessage(props: {
|
||||
props.sendMessage({
|
||||
content: [
|
||||
{
|
||||
chain_id: message.data.transaction.chainId,
|
||||
chain_id: message.data.transaction.chain_id,
|
||||
transaction_hash: tx.transactionHash,
|
||||
type: "transaction",
|
||||
},
|
||||
@@ -785,7 +685,7 @@ function RenderMessage(props: {
|
||||
) : (
|
||||
<ConnectButton
|
||||
client={THIRDWEB_CLIENT}
|
||||
chain={defineChain(message.data.transaction.chainId)}
|
||||
chain={defineChain(message.data.transaction.chain_id)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,27 +11,15 @@ const thirdwebAI = createThirdwebAI({
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const body = await req.json();
|
||||
const { messages, sessionId }: { messages: UIMessage[]; sessionId: string } =
|
||||
body;
|
||||
const { messages, id }: { messages: UIMessage[]; id: string } = body;
|
||||
|
||||
const result = streamText({
|
||||
model: thirdwebAI.chat({
|
||||
context: {
|
||||
session_id: sessionId,
|
||||
},
|
||||
}),
|
||||
model: thirdwebAI.chat(id),
|
||||
messages: convertToModelMessages(messages),
|
||||
tools: thirdwebAI.tools(),
|
||||
});
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
sendReasoning: true,
|
||||
messageMetadata({ part }) {
|
||||
if (part.type === "finish-step") {
|
||||
return {
|
||||
session_id: part.response.id,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -865,6 +865,11 @@ const infrastructureRedirects = {
|
||||
"/storage/how-storage-works": "/infrastructure/storage/how-storage-works",
|
||||
"/storage/upload-to-ipfs":
|
||||
"/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs",
|
||||
// infrastructure redirects to external portal
|
||||
"/infrastructure/storage/:path*":
|
||||
"https://portal.thirdweb.com/references/typescript/v5/functions#storage",
|
||||
"/infrastructure/rpc-edge/:path*":
|
||||
"https://portal.thirdweb.com/references/typescript/v5/functions#rpc",
|
||||
};
|
||||
|
||||
const glossaryRedirects = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Usage with the Vercel AI SDK
|
||||
|
||||
The `@thirdweb-dev/ai-sdk-provider` A thin provider that plugs thirdweb into the [Vercel AI SDK](https://ai-sdk.dev/docs/introduction).
|
||||
The `@thirdweb-dev/ai-sdk-provider` is a lightweight provider that lets you use thirdweb AI with the [Vercel AI SDK](https://ai-sdk.dev/docs/introduction).
|
||||
|
||||
It standardizes message parts, exposes wallet-aware tools (`sign_transaction`, `sign_swap`), and ships types/utilities so you can build chat UIs that execute on-chain actions from AI responses.
|
||||
|
||||
@@ -24,8 +24,6 @@ Create a thirdweb ai provider instance and compatible with the Vercel AI SDK by
|
||||
|
||||
Then pass a `thirdwebAI.chat()` instance as the model for the `streamText` function and configure the model context with the `context` option.
|
||||
|
||||
For continuous conversations, you can extract the session id from the response and send it back to the client by overriding the `messageMetadata` function as shown below.
|
||||
|
||||
```ts
|
||||
// src/app/api/chat/route.ts
|
||||
|
||||
@@ -40,12 +38,11 @@ const thirdwebAI = createThirdwebAI({
|
||||
});
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { messages, sessionId } = await req.json();
|
||||
const { messages, id } = await req.json();
|
||||
|
||||
const result = streamText({
|
||||
model: thirdwebAI.chat({
|
||||
model: thirdwebAI.chat(id, {
|
||||
context: {
|
||||
session_id: sessionId, // session id for continuity
|
||||
chain_ids: [8453], // optional chain ids
|
||||
from: "0x...", // optional from address
|
||||
auto_execute_transactions: true, // optional, defaults to false
|
||||
@@ -57,19 +54,12 @@ export async function POST(req: Request) {
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
sendReasoning: true, // optional, to send reasoning steps to the client
|
||||
messageMetadata({ part }) {
|
||||
// record session id for continuity
|
||||
if (part.type === "finish-step") {
|
||||
return {
|
||||
session_id: part.response.id,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Continuous conversations are handled automatically. You can create a new conversation by passing a new `id` to the `thirdwebAI.chat()` function.
|
||||
|
||||
#### Client side (React, using `useChat`)
|
||||
|
||||
Use `useChat<ThirdwebAiMessage>()` to get typed objects from `useChat()`. This will give you strongly typed tool results in your UI like `tool-sign_transaction`, `tool-sign_swap` or `tool-monitor_transaction`.
|
||||
@@ -78,45 +68,34 @@ Use `useChat<ThirdwebAiMessage>()` to get typed objects from `useChat()`. This w
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useChat, DefaultChatTransport } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from "ai";
|
||||
import { useChat, type UseChatHelpers } from "@ai-sdk/react";
|
||||
import type { ThirdwebAiMessage } from "@thirdweb-dev/ai-sdk-provider";
|
||||
|
||||
export function Chat() {
|
||||
const [sessionId, setSessionId] = useState("");
|
||||
|
||||
const { messages, sendMessage, addToolResult, status } =
|
||||
useChat<ThirdwebAiMessage>({
|
||||
transport: new DefaultChatTransport({ api: "/api/chat" }),
|
||||
onFinish: ({ message }) => {
|
||||
// save session id for continuity
|
||||
setSessionId(message.metadata?.session_id ?? "");
|
||||
},
|
||||
// send tool results automatically
|
||||
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls
|
||||
});
|
||||
|
||||
const send = (message: string) => {
|
||||
sendMessage(
|
||||
{ text: message },
|
||||
{
|
||||
body: {
|
||||
// send session id for continuity
|
||||
sessionId,
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// You can render messages and reasoning steps as you normally would
|
||||
// Use your own UI or the vercel ai sdk UI
|
||||
// Use your own UI or the vercel ai elements UI components
|
||||
// When a tool part arrives (e.g., message.parts[0].type === "tool-sign_transaction"),
|
||||
// you can render a thirdweb `TransactionButton` wired to the provided input to execute the transaction.
|
||||
return return (
|
||||
// you can render a transaction button with the input to execute the transaction.
|
||||
return (
|
||||
<>
|
||||
{messages.map((message) => (
|
||||
<RenderMessage message={message} />
|
||||
<RenderMessage
|
||||
key={message.id}
|
||||
message={message}
|
||||
addToolResult={addToolResult}
|
||||
/>
|
||||
))}
|
||||
<ChatInputBox send={send} />
|
||||
<ChatInputBox send={sendMessage} />
|
||||
</>
|
||||
);;
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -135,11 +114,9 @@ Then on result, you can call the `addToolResult` function to add the tool result
|
||||
```tsx
|
||||
export function RenderMessage(props: {
|
||||
message: ThirdwebAiMessage;
|
||||
sessionId: string;
|
||||
addToolResult: (toolResult: ThirdwebAiMessage) => void;
|
||||
sendMessage: (message: string) => void;
|
||||
addToolResult: UseChatHelpers<ThirdwebAiMessage>["addToolResult"];
|
||||
}) {
|
||||
const { message, sessionId, addToolResult, sendMessage } = props;
|
||||
const { message, addToolResult } = props;
|
||||
return (
|
||||
<>
|
||||
{message.parts.map((part, i) => {
|
||||
@@ -164,33 +141,19 @@ export function RenderMessage(props: {
|
||||
})
|
||||
}
|
||||
onTransactionSent={(transaction) => {
|
||||
// add the tool result to the messages array
|
||||
// send the tool result to the model to continue the conversation
|
||||
addToolResult({
|
||||
tool: "sign_transaction",
|
||||
toolCallId,
|
||||
toolCallId: part.toolCallId,
|
||||
output: {
|
||||
transaction_hash: transaction.transactionHash,
|
||||
chain_id: transaction.chain.id,
|
||||
},
|
||||
});
|
||||
// send the message to the model to continue the conversation
|
||||
sendMessage(undefined, {
|
||||
body: {
|
||||
// send session id for continuity
|
||||
sessionId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onError={(error) => {
|
||||
// in case of error, send the error message to the model
|
||||
sendMessage(
|
||||
{ text: `Transaction failed: ${error.message}` },
|
||||
{
|
||||
body: {
|
||||
sessionId,
|
||||
},
|
||||
},
|
||||
);
|
||||
// in case of error, show error UI or send the error message to the model
|
||||
console.error(error);
|
||||
}}
|
||||
>
|
||||
Sign Transaction
|
||||
|
||||
@@ -14,16 +14,50 @@ The thirdweb API exposes a standard OpenAI-compatible chat completion API that a
|
||||
- Search the web
|
||||
- And more!
|
||||
|
||||
You can use the API with the HTTP API directly, or with any OpenAI-compatible client library.
|
||||
You can use the API with the HTTP API directly, the AI SDK or with any OpenAI-compatible client library.
|
||||
|
||||
<Tabs defaultValue="api">
|
||||
<TabsList>
|
||||
<TabsTrigger value="api">HTTP API</TabsTrigger>
|
||||
<TabsTrigger value="ai-sdk">Vercel AI SDK</TabsTrigger>
|
||||
<TabsTrigger value="openai">OpenAI Client</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="api">
|
||||
<OpenApiEndpoint path="/ai/chat" method="POST" />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="ai-sdk">
|
||||
### Usage with the Vercel AI SDK
|
||||
|
||||
The `@thirdweb-dev/ai-sdk-provider` is a lightweight provider that lets you use thirdweb AI with the [Vercel AI SDK](https://ai-sdk.dev/docs/introduction).
|
||||
|
||||
```ts
|
||||
import { streamText } from "ai";
|
||||
import { createThirdwebAI } from "@thirdweb-dev/ai-sdk-provider";
|
||||
|
||||
const thirdwebAI = createThirdwebAI({
|
||||
secretKey: "<your-project-secret-key>",
|
||||
});
|
||||
|
||||
const result = streamText({
|
||||
model: thirdwebAI.chat({
|
||||
context: {
|
||||
chain_ids: [8453], // optional chain ids
|
||||
from: "0x...", // optional wallet address
|
||||
auto_execute_transactions: true, // defaults to false
|
||||
},
|
||||
}),
|
||||
tools: thirdwebAI.tools(),
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Swap 0.01 ETH to USDC",
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="openai">
|
||||
|
||||
### Using the OpenAI Client
|
||||
@@ -41,7 +75,7 @@ chat_completion = client.chat.completions.create(
|
||||
model="t0",
|
||||
messages=[{"role": "user", "content": "Transfer 10 USDC to vitalik.eth"}],
|
||||
stream=False,
|
||||
extra_body={ "context": { "from": "0x...", "chain_ids": [8453] }}
|
||||
extra_body={ "context": { "from": "0x...", "chain_ids": [8453] }}
|
||||
)
|
||||
|
||||
print(chat_completion)
|
||||
@@ -52,4 +86,6 @@ print(chat_completion)
|
||||
### Going further
|
||||
|
||||
- [Handle streaming responses](/ai/chat/streaming)
|
||||
- [Transaction Execution](/ai/chat/execution)
|
||||
- [Use the Vercel AI SDK](/ai/chat/ai-sdk)
|
||||
- [Full API Reference](https://api.thirdweb.com/reference#tag/ai/post/ai/chat)
|
||||
@@ -21,6 +21,23 @@ For any already deployed contracts, you can use the thirdweb dashboard to import
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summary="Does it cost money to deploy contracts on thirdweb?">
|
||||
Only thirdweb pre-built contracts have mint fees or fees taken on primary sales for the following contracts which have a primary recipient field:
|
||||
|
||||
- Drop Contracts: DropERC20, DropERC721, DropERC1155, OpenEdition
|
||||
- Token Contracts: TokenERC20, TokenERC721, TokenERC1155
|
||||
- Specialized Contracts: , LoyaltyCard, BurnToClaim, MarketplaceV3
|
||||
|
||||
There is a 2% convenience fee for deploying from the dashboard and a 0.5% protocol fee that is integrated into the contract.
|
||||
|
||||
The 2.5% fee will be automatically deducted from primary sales executed through these contracts. For example, an asset listed at 0.1 ETH will distribute 0.0975 ETH to the seller and 0.0025 ETH to the platform.
|
||||
|
||||
The fee applies only to prebuilt contracts deployed via the thirdweb dashboard. Custom contracts or externally deployed contracts are not subject to fees.
|
||||
|
||||
The fee is charged from the primary sale, including a claim or mint action when there is a price on the NFT (non-zero). The fee is paid in the same currency as the claiming or minting price.
|
||||
|
||||
</Details>
|
||||
|
||||
<Details summary="Are thirdweb contracts audited?">
|
||||
Yes, thirdweb pre-built contracts are audited by third-party security firms. You can see the attached
|
||||
audit reports for each contract linked on the contract page on Explore.
|
||||
|
||||
|
Before Width: | Height: | Size: 867 KiB |
@@ -1,17 +0,0 @@
|
||||
import { createMetadata } from "@doc";
|
||||
import { DocLayout } from "@/components/Layouts/DocLayout";
|
||||
import { sidebar } from "./sidebar";
|
||||
|
||||
export default async function Layout(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<DocLayout editPageButton={true} noIndex sideBar={sidebar}>
|
||||
{props.children}
|
||||
</DocLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export const metadata = createMetadata({
|
||||
description:
|
||||
"All the infrastructure to scale and build production grade web3 applications",
|
||||
title: "thirdweb Infrastructure",
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
import { ArticleCard, GithubTemplateCard, Grid } from "@doc";
|
||||
import { createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "thirdweb Infrastructure",
|
||||
icon: "infra",
|
||||
},
|
||||
title: "thirdweb Infrastructure",
|
||||
description:
|
||||
"All the infrastructure to scale and build production grade applications",
|
||||
});
|
||||
|
||||
# Infrastructure
|
||||
|
||||
All the infrastructure to scale and build production grade applications.
|
||||
|
||||
## Products
|
||||
|
||||
- [Storage:](/infrastructure/storage) Secure, fast, decentralized storage
|
||||
- [RPC Edge:](/infrastructure/rpc-edge/overview) Enterprise-grade RPCs, for free
|
||||
|
||||
### Guides
|
||||
|
||||
<Grid>
|
||||
<ArticleCard
|
||||
title="Airdrop NFTs"
|
||||
description="Learn how to airdrop NFTs to your community using Engine."
|
||||
href="/engine/v2/guides/airdrop-nfts"
|
||||
/>
|
||||
|
||||
<ArticleCard
|
||||
title="NFT Checkout"
|
||||
description="Learn how to add NFT Checkout with Engine."
|
||||
href="/engine/v2/guides/nft-checkout"
|
||||
/>
|
||||
|
||||
<ArticleCard
|
||||
title="Upload files to IPFS"
|
||||
description="Learn how to upload files to IPFS using Storage."
|
||||
href="/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
### Templates
|
||||
|
||||
<Grid>
|
||||
<GithubTemplateCard
|
||||
title="Scoped Sessions"
|
||||
href="https://github.com/thirdweb-example/smart-account-engine-sessions-demo"
|
||||
/>
|
||||
|
||||
<GithubTemplateCard
|
||||
title="TappyFowl"
|
||||
href="https://github.com/thirdweb-example/TappyFowl"
|
||||
/>
|
||||
|
||||
<GithubTemplateCard
|
||||
title="NFT Checkout"
|
||||
href="https://github.com/thirdweb-example/engine-nft-checkout"
|
||||
/>
|
||||
|
||||
<GithubTemplateCard
|
||||
title="Minting API"
|
||||
href="https://github.com/thirdweb-example/engine-minting-api"
|
||||
/>
|
||||
|
||||
<GithubTemplateCard
|
||||
title="Phygital Experience"
|
||||
href="https://github.com/thirdweb-example/phygital-experience"
|
||||
/>
|
||||
|
||||
<GithubTemplateCard
|
||||
title="Unreal Engine Demo"
|
||||
href="https://github.com/thirdweb-example/TappyFowl"
|
||||
/>
|
||||
</Grid>
|
||||
|
Before Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 191 KiB |
@@ -1,153 +0,0 @@
|
||||
import { DocImage, Callout } from "@doc";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { createMetadata } from "@doc";
|
||||
import usageDashboardImage from "./assets/usage-dashboard.png";
|
||||
|
||||
import apiKeyImage from "./assets/api-key.png";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "Add RPC Edge to your application | thirdweb",
|
||||
description:
|
||||
"To enable RPC Edge usage in an application, obtain a thirdweb Client ID & Secret Key with RPC Edge enabled and pass it into the app's configuration.",
|
||||
});
|
||||
|
||||
# Add RPC Edge to your application
|
||||
|
||||
To enable RPC Edge usage in an application, obtain a thirdweb Client ID & Secret Key with RPC Edge enabled and pass it into the app's configuration.
|
||||
|
||||
Obtain a Client ID & Secret Key by navigating to Settings > API Keys > `Create API Key`.
|
||||
|
||||
<DocImage
|
||||
src={apiKeyImage}
|
||||
alt="Screenshot of API key creation page on thirdweb's dashboard"
|
||||
/>
|
||||
|
||||
Pass in the Client ID and Secret Keys into the application’s configuration.
|
||||
|
||||
<Tabs defaultValue="react">
|
||||
<TabsList>
|
||||
<TabsTrigger value="react">React</TabsTrigger>
|
||||
<TabsTrigger value="react-native">React Native</TabsTrigger>
|
||||
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="react">
|
||||
|
||||
```jsx
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
function MyApp() {
|
||||
return (
|
||||
<ThirdwebProvider activeChain="ethereum" clientId="your-client-id">
|
||||
<YourApp />
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="react-native">
|
||||
|
||||
```jsx
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react-native";
|
||||
|
||||
function MyApp() {
|
||||
return (
|
||||
<ThirdwebProvider activeChain="ethereum" clientId="your-client-id">
|
||||
<YourApp />
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="typescript">
|
||||
```ts
|
||||
// Read-only mode
|
||||
const readOnlySdk = new ThirdwebSDK("<chain_id>", {
|
||||
secretKey: "<your_secret_key>",
|
||||
});
|
||||
```
|
||||
|
||||
When using the Typescript SDK for frontend applications, use the client id:
|
||||
|
||||
```ts
|
||||
const readOnlySdk = new ThirdwebSDK("<chain_id>", {
|
||||
clientId: "<your_client_id>",
|
||||
});
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="unity">
|
||||
|
||||
When using the Unity SDK, you can use the client id to instantiate the SDK:
|
||||
|
||||
```csharp
|
||||
ThirdwebSDK sdk = new ThirdwebSDK("<chain_id>", 5, new ThirdwebSDK.Options()
|
||||
{
|
||||
clientId = "<your_client_id>",
|
||||
});
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Override default RPC
|
||||
|
||||
To override the default RPC for any given chain:
|
||||
|
||||
<Tabs defaultValue="react">
|
||||
|
||||
<TabsList>
|
||||
<TabsTrigger value="react">React</TabsTrigger>
|
||||
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="react">
|
||||
|
||||
```jsx
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ThirdwebProvider
|
||||
activeChain={{
|
||||
rpc: ["https://<your-rpc-to-use>.com"],
|
||||
}}
|
||||
>
|
||||
<YourApp />
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="typescript">
|
||||
|
||||
```ts
|
||||
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
|
||||
|
||||
const sdk = new ThirdwebSDK({
|
||||
rpc: ["https://<your-rpc-to-use>.com"],
|
||||
});
|
||||
```
|
||||
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
|
||||
<Callout title="Public RPCs" variant="info">
|
||||
Public RPCs can be used without an API Key. Refer to the [configured chains
|
||||
page](https://thirdweb.com/chainlist).
|
||||
</Callout>
|
||||
|
||||
## View RPC usage
|
||||
|
||||
[View RPC request usage](https://thirdweb.com/team/~/~/usage) in the dashboard settings.
|
||||
|
||||
<DocImage src={usageDashboardImage} alt="Usage dashboard" />
|
||||
|
Before Width: | Height: | Size: 70 KiB |
@@ -1,37 +0,0 @@
|
||||
import { Callout, DocImage } from "@doc";
|
||||
import { createMetadata } from "@doc";
|
||||
import rpcDiagramImage from "./assets/rpc-diagram.svg";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "thirdweb RPC Edge",
|
||||
icon: "rpc",
|
||||
},
|
||||
title: "thirdweb RPC Edge",
|
||||
description:
|
||||
"Remote Procedure Call (RPC) Edge provides reliable access to querying data and interacting with the blockchain through global edge RPCs.",
|
||||
});
|
||||
|
||||
# RPC Edge
|
||||
|
||||
Remote Procedure Call (RPC) Edge provides reliable access to querying data and interacting with the blockchain through global edge RPCs.
|
||||
|
||||
<DocImage src={rpcDiagramImage} alt="RPC Diagram" />
|
||||
|
||||
By default, we provide publicly available RPCs to every EVM chain. [View the default RPC endpoints](https://thirdweb.com/chainlist) for each supported chain.
|
||||
|
||||
<Callout variant="info" title="Accessing your RPC URL">
|
||||
To use RPC Edge with other tools like Anvil, take a chain's [public RPC endpoint](https://thirdweb.com/chainlist) and attach your app's client ID from Project > Settings page in [dashboard](https://thirdweb.com/team) `https://<chainId>.rpc.thirdweb.com/<clientId>`.
|
||||
</Callout>
|
||||
|
||||
#### Features
|
||||
|
||||
- **Reduced Latency:** Global edge caching reduces latency by utilizing geographically distributed servers, smart routing, and caching RPC responses. RPC endpoints run on the edge with over 150+ worldwide locations, resulting in minimized latency, especially for frequently requested responses.
|
||||
- **Improved Performance:** Create critical and scalable applications with maintained uptime and low latency, thanks to reduced response times.
|
||||
- **High Reliability:** Utilize fallback across multiple providers to ensure high reliability.
|
||||
- **Cost Efficient:** More cost-efficient at scale compared to competitors.
|
||||
- **Compatible with any EVM Blockchain:** Use RPC Edge with any deployed contract on any EVM blockchain.
|
||||
|
||||
#### Pricing & Billing
|
||||
|
||||
RPC pricing is based on requests per second. If you're interested in upgrading your RPC Edge limits, you can find more information on the different tiers by visiting [thirdweb's pricing page](https://thirdweb.com/pricing).
|
||||
@@ -1,66 +0,0 @@
|
||||
import type { SideBar } from "@/components/Layouts/DocLayout";
|
||||
import { InfraRPCIcon, InfraStorageIcon } from "@/icons";
|
||||
|
||||
const storageSlug = "/infrastructure/storage";
|
||||
const rpcSlug = "/infrastructure/rpc-edge";
|
||||
|
||||
export const sidebar: SideBar = {
|
||||
links: [
|
||||
{
|
||||
href: "/infrastructure",
|
||||
name: "Overview",
|
||||
},
|
||||
// Storage
|
||||
{ separator: true },
|
||||
{
|
||||
icon: <InfraStorageIcon />,
|
||||
isCollapsible: false,
|
||||
links: [
|
||||
{
|
||||
href: `${storageSlug}/overview`,
|
||||
name: "Overview",
|
||||
},
|
||||
{
|
||||
href: `${storageSlug}/how-storage-works`,
|
||||
name: "How Storage Works",
|
||||
},
|
||||
{
|
||||
links: [
|
||||
{
|
||||
href: "/wallets",
|
||||
name: "TypeScript SDK",
|
||||
},
|
||||
{
|
||||
href: `${storageSlug}/how-to-use-storage/upload-files-to-ipfs`,
|
||||
name: "Upload Files to IPFS",
|
||||
},
|
||||
{
|
||||
href: `${storageSlug}/how-to-use-storage/host-web-app`,
|
||||
name: "Host Web App",
|
||||
},
|
||||
],
|
||||
name: "How to Use Storage",
|
||||
},
|
||||
],
|
||||
name: "Storage",
|
||||
},
|
||||
//RPC Edge
|
||||
{ separator: true },
|
||||
{
|
||||
icon: <InfraRPCIcon />,
|
||||
isCollapsible: false,
|
||||
links: [
|
||||
{
|
||||
href: `${rpcSlug}/overview`,
|
||||
name: "Overview",
|
||||
},
|
||||
{
|
||||
href: `${rpcSlug}/get-started`,
|
||||
name: "Get Started",
|
||||
},
|
||||
],
|
||||
name: "RPC Edge",
|
||||
},
|
||||
],
|
||||
name: "Infrastructure",
|
||||
};
|
||||
|
Before Width: | Height: | Size: 59 KiB |
@@ -1,40 +0,0 @@
|
||||
import { Callout, DocImage } from "@doc";
|
||||
import { createMetadata } from "@doc";
|
||||
import storageDiagramImage from "./assets/storage-diagram.svg";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "how storage works | thirdweb",
|
||||
description:
|
||||
"Storage is a tool that simplifies the uploading, pinning, and downloading of files to and from IPFS. It utilizes a pinning service, Pinata, to pin files to IPFS and other solutions simultaneously.",
|
||||
});
|
||||
|
||||
# Understand how storage works
|
||||
|
||||
Storage is a tool that simplifies the uploading, pinning, and downloading of files to and from IPFS. It utilizes a pinning service, Pinata, to pin files to IPFS and other solutions simultaneously.
|
||||
|
||||
This mechanism stops data from being deleted automatically because it's not being used or is unpopular. This can happen in decentralized storage systems where users need incentives to provide storage space.
|
||||
|
||||
<Callout title="Storage in thirdweb" variant="info">
|
||||
Storage powers various functionalities within thirdweb products, including
|
||||
smart contract ABIs for deployment, the IPFS Renderer UI, NFT metadata
|
||||
fetching in the SDK, and NFT metadata uploading via the dashboard.
|
||||
</Callout>
|
||||
|
||||
## Uploading Data To IPFS
|
||||
|
||||
Uploading data to IPFS or decentralized storage is a process that involves:
|
||||
|
||||
1. **Data Splitting:** Data is broken into smaller pieces by the IPFS Server to make it easier to manage.
|
||||
2. **Upload:** We choose IPFS to store these pieces of data, and within the IPFS network, several computers (nodes) store the uploaded data.
|
||||
3. **Access:** When you want to retrieve your data, the network puts all the pieces together and unlocks it for you. You can access the file through a Gateway
|
||||
|
||||
Learn how to upload to IPFS using Storage in the Upload Files to IPFS guide.
|
||||
|
||||
## Accessing Data Through IPFS Gateway
|
||||
|
||||
To read data from IPFS, you need an [IPFS Gateway](https://docs.ipfs.tech/concepts/ipfs-gateway/). This allows you to access data from the IPFS protocol on browsers and other HTTP clients,
|
||||
such as when building an application using the Contract SDK.
|
||||
|
||||
<DocImage src={storageDiagramImage} alt="storage diagram" />
|
||||
|
||||
When a user requests data from the network, the request is sent to the gateway. The gateway then retrieves the data from the decentralized storage network and returns it to the user.
|
||||
@@ -1,154 +0,0 @@
|
||||
import { InstallTabs } from "@doc";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "Host web application with IPFS | thirdweb Storage",
|
||||
description:
|
||||
"Uploading an app to decentralized storage offers perks such as reliable data, resistance to censorship, and better user data control. It is also more secure, scalable, and globally accessible.",
|
||||
});
|
||||
|
||||
# Host web application with IPFS
|
||||
|
||||
Uploading an app to decentralized storage offers perks such as reliable data, resistance to censorship, and better user data control. It is also more secure, scalable, and globally accessible.
|
||||
|
||||
Deploy a web app to decentralized storage (IPFS) using the CLI.
|
||||
|
||||
**Prerequisites**
|
||||
|
||||
- configure clientId through `<ThirdwebProvider>` in your app
|
||||
- login to the thirdweb CLI using your clientId and SecretKey
|
||||
|
||||
## Using Starter Templates
|
||||
|
||||
[Starter templates](https://thirdweb.com/templates) are already pre-configured and set up for IPFS deployments.
|
||||
|
||||
To deploy an app to IPFS from a starter template:
|
||||
|
||||
1. Run the following command at the root of your project's directory:
|
||||
|
||||
<InstallTabs npm="npm run deploy" yarn="yarn deploy" pnpm="pnpm run deploy" />
|
||||
|
||||
Upon executing this script, the application will be constructed, and subsequently, the contents of the `dist` output directory will be uploaded to IPFS.
|
||||
|
||||
2. Once deployed successfully, the CLI will return the corresponding deployed URI and Gateway link.
|
||||
|
||||
## Upload in Your Existing App
|
||||
|
||||
To deploy to IPFS using your existing app, we will highlight the fields needed in your configuration files before running `deploy` in the following frameworks: React, Next, and Vite
|
||||
|
||||
<Tabs defaultValue="react">
|
||||
<TabsList>
|
||||
<TabsTrigger value="react">React</TabsTrigger>
|
||||
<TabsTrigger value="next">Next</TabsTrigger>
|
||||
<TabsTrigger value="vite">Vite</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="react">
|
||||
|
||||
To deploy your app to IPFS in React:
|
||||
|
||||
1. Navigate to your `package.json` file in your project
|
||||
2. Add a `homepage` field and set it to `"."`
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"homepage": ".",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
3. In the same file, add the following `deploy` script.
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"scripts": {
|
||||
...
|
||||
"deploy": "yarn build && npx thirdweb@latest upload build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Depending on your package manager, run one of the following commands at the root directory of your project:
|
||||
|
||||
<div>
|
||||
<InstallTabs
|
||||
npm="npm run deploy"
|
||||
yarn="yarn deploy"
|
||||
pnpm="pnpm run deploy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
5. Once deployed successfully, the CLI will return the corresponding deployed URI and Gateway link.
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="next">
|
||||
|
||||
To deploy your app to IPFS from Next:
|
||||
|
||||
1. Navigate to the `package.json` file in your project.
|
||||
2. Add the following `deploy` script
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"scripts": {
|
||||
...
|
||||
"deploy": "yarn build && npx thirdweb@latest upload build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Depending on your package manager, run one of the following commands at the root directory of your project to run the script:
|
||||
|
||||
<div>
|
||||
<InstallTabs
|
||||
npm="npm run deploy"
|
||||
yarn="yarn deploy"
|
||||
pnpm="pnpm run deploy"
|
||||
/>
|
||||
</div>
|
||||
|
||||
4. Once deployed successfully, the CLI will return the corresponding deployed URI and Gateway link.
|
||||
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="vite">
|
||||
|
||||
To deploy your app to IPFS from Vite:
|
||||
|
||||
1. Navigate to the `vite.config.js`
|
||||
2. Set the `base` field in your `defaultConfig` to `./`
|
||||
|
||||
```json
|
||||
const defaultConfig = {
|
||||
base: "./",
|
||||
plugins: [react()],
|
||||
};
|
||||
```
|
||||
|
||||
3. Navigate to your `package.json` file and add the following deploy script
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"scripts": {
|
||||
...
|
||||
"deploy": "yarn build && npx thirdweb@latest upload dist"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When running this script, your application will be built, and the output directory `dist` will be uploaded to IPFS, effectively deploying your application.
|
||||
|
||||
4. Depending on your package manager, run one of the following commands at the root directory of your project to run the script:
|
||||
|
||||
<div><InstallTabs npm="npm run deploy" yarn="yarn deploy" pnpm="pnpm run deploy" /></div>
|
||||
|
||||
5. Once deployed successfully, the CLI will return the corresponding deployed URI and Gateway link.
|
||||
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
Before Width: | Height: | Size: 558 KiB |
|
Before Width: | Height: | Size: 615 KiB |
|
Before Width: | Height: | Size: 709 KiB |
@@ -1,201 +0,0 @@
|
||||
import { Steps, Step, Callout, DocImage } from "@doc";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
||||
import { createMetadata } from "@doc";
|
||||
import storageDashboardImage from "./assets/storage-dashboard.png";
|
||||
import stroageUploadImage from "./assets/storage-upload.png";
|
||||
import storageUrlsImage from "./assets/storage-urls.png";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
title: "Upload Files to IPFS | thirdweb Storage",
|
||||
description:
|
||||
"Upload and pin files and directories directly to IPFS using the dashboard, CLI or via a client or server environment using SDKs",
|
||||
});
|
||||
|
||||
# Upload Files to IPFS
|
||||
|
||||
Upload and pin files and directories directly to IPFS using the dashboard, CLI or via a client or server environment using SDKs.
|
||||
|
||||
## Upload files to IPFS using dashboard
|
||||
|
||||
To upload files using dashboard:
|
||||
|
||||
<Steps>
|
||||
|
||||
<Step title="Connect Wallet">
|
||||
To authenticate to your account, connect your wallet.
|
||||
</Step>
|
||||
|
||||
<Step title="Storage Dashboard">
|
||||
Navigate to the [storage dashboard](https://thirdweb.com/team/~/~/usage/storage)
|
||||
|
||||
<DocImage src={storageDashboardImage} alt="storage diagram" />
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Select files">
|
||||
Select your files or drag them into the upload box. Confirm that your files are correct, and then select "Start Upload.”
|
||||
|
||||
<DocImage src={stroageUploadImage} alt="storage diagram" />
|
||||
|
||||
<Callout title="Info" variant="info">
|
||||
The duration of this process will depend on the amount of data uploaded.
|
||||
</Callout>
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Access via Gateway">
|
||||
Upon completion, the corresponding IPFS addresses refers to where your content is stored on the IPFS Network. You may access these files using your unique gateway URL.
|
||||
|
||||
<DocImage
|
||||
src={storageUrlsImage}
|
||||
alt="storage diagram"
|
||||
/>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<hr />
|
||||
|
||||
## Upload files to IPFS using CLI
|
||||
|
||||
To upload files using the CLI, use the `upload` command and specify the relative path to the file name.
|
||||
|
||||
```bash
|
||||
npx thirdweb@latest upload path/to/file.extension
|
||||
```
|
||||
|
||||
### Upload multiple files
|
||||
|
||||
To upload multiple files, specify all the file names separated with spaces:
|
||||
|
||||
```bash
|
||||
npx thirdweb upload image1.png image2.png image3.png
|
||||
```
|
||||
|
||||
### Upload entire directory
|
||||
|
||||
To upload an entire directory, specify the relative directory name:
|
||||
|
||||
```bash
|
||||
npx thirdweb upload directory_name
|
||||
```
|
||||
|
||||
<hr />
|
||||
## Upload files to IPFS using SDKs
|
||||
|
||||
<Tabs defaultValue="react">
|
||||
<TabsList>
|
||||
<TabsTrigger value="react">React</TabsTrigger>
|
||||
<TabsTrigger value="react-native">React Native</TabsTrigger>
|
||||
<TabsTrigger value="typescript">TypeScript</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="react">
|
||||
```jsx
|
||||
// Initialize your provider
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
function Provider() {
|
||||
return (
|
||||
<ThirdwebProvider
|
||||
clientId="YOUR_CLIENT_ID" // You can get a client id from dashboard settings
|
||||
activeChain="goerli"
|
||||
>
|
||||
...
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Upload files to IPFS
|
||||
import { useStorageUpload } from "@thirdweb-dev/react";
|
||||
|
||||
function App() {
|
||||
const { mutateAsync: upload } = useStorageUpload();
|
||||
|
||||
const uploadData = () => {
|
||||
// Get any data that you want to upload
|
||||
const dataToUpload = [...];
|
||||
|
||||
// And upload the data with the upload function
|
||||
const uris = await upload({ data: dataToUpload });
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
// Render files from IPFS
|
||||
import { MediaRenderer } from "@thirdweb-dev/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
// Supported types: image, video, audio, 3d model, html
|
||||
<MediaRenderer src="ipfs://QmamvVM5kvsYjQJYs7x8LXKYGFkwtGvuRvqZsuzvpHmQq9/0" />
|
||||
);
|
||||
}
|
||||
```
|
||||
</TabsContent>
|
||||
<TabsContent value="react-native">
|
||||
```jsx
|
||||
// Initialize your provider
|
||||
import { ThirdwebProvider } from "@thirdweb-dev/react";
|
||||
|
||||
function Provider() {
|
||||
return (
|
||||
<ThirdwebProvider
|
||||
clientId="YOUR_CLIENT_ID" // You can get a client id from dashboard settings
|
||||
activeChain="goerli"
|
||||
>
|
||||
...
|
||||
</ThirdwebProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// Upload files to IPFS
|
||||
import { useStorageUpload } from "@thirdweb-dev/react";
|
||||
|
||||
function App() {
|
||||
const { mutateAsync: upload } = useStorageUpload();
|
||||
|
||||
const uploadData = () => {
|
||||
// Get any data that you want to upload
|
||||
const dataToUpload = [...];
|
||||
|
||||
// And upload the data with the upload function
|
||||
const uris = await upload({ data: dataToUpload });
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
// Render files from IPFS
|
||||
import { MediaRenderer } from "@thirdweb-dev/react";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
// Supported types: image, video, audio, 3d model, html
|
||||
<MediaRenderer src="ipfs://QmamvVM5kvsYjQJYs7x8LXKYGFkwtGvuRvqZsuzvpHmQq9/0" />
|
||||
);
|
||||
}
|
||||
```
|
||||
</TabsContent>
|
||||
<TabsContent value="typescript">
|
||||
```jsx
|
||||
import { ThirdwebStorage } from "@thirdweb-dev/storage";
|
||||
|
||||
// First, instantiate the thirdweb IPFS storage
|
||||
const storage = new ThirdwebStorage({
|
||||
secretKey: "YOUR_SECRET_KEY", // You can get one from dashboard settings
|
||||
});
|
||||
|
||||
// Here we get the IPFS URI of where our metadata has been uploaded
|
||||
const uri = await storage.upload(metadata);
|
||||
// This will log a URL like ipfs://QmWgbcjKWCXhaLzMz4gNBxQpAHktQK6MkLvBkKXbsoWEEy/0
|
||||
console.info(uri);
|
||||
|
||||
// Here we a URL with a gateway that we can look at in the browser
|
||||
const url = await storage.resolveScheme(uri);
|
||||
// This will log a URL like https://ipfs.thirdwebstorage.com/ipfs/QmWgbcjKWCXhaLzMz4gNBxQpAHktQK6MkLvBkKXbsoWEEy/0
|
||||
console.info(url);
|
||||
|
||||
// You can also download the data from the uri
|
||||
const data = await storage.downloadJSON(uri);
|
||||
```
|
||||
</TabsContent>
|
||||
|
||||
</Tabs>
|
||||
@@ -1,38 +0,0 @@
|
||||
import { createMetadata } from "@doc";
|
||||
|
||||
export const metadata = createMetadata({
|
||||
image: {
|
||||
title: "thirdweb Storage",
|
||||
icon: "storage",
|
||||
},
|
||||
title: "thirdweb Storage",
|
||||
description:
|
||||
"Storage is a decentralized file management solution that allows for the storage and retrieval of information off-chain using IPFS",
|
||||
});
|
||||
|
||||
# Storage
|
||||
|
||||
Storage is a decentralized file management solution that allows for the storage and retrieval of information "off-chain" using [IPFS](/glossary/ipfs). With Storage, you can upload and pin files to IPFS and retrieve them at any time using IPFS gateway.
|
||||
|
||||
You can easily upload and pin individual files or entire directories to IPFS using either the dashboard, CLI, or SDK. This makes Storage ideal for storing files in a permanent, decentralized, and censorship-resistant manner.
|
||||
|
||||
Storage supports all types of files, including images, 3D models, videos, audio, full applications, and more.
|
||||
|
||||
#### Gateway
|
||||
|
||||
A gateway is a server that acts as an intermediary between a client and a decentralized storage network. It allows users to access and retrieve data stored on the network.
|
||||
|
||||
#### Features
|
||||
|
||||
- **Save Development Time:** Storage manages the difficult aspects of managing decentralized files, such as handling, pinning, and uploading metadata formats.
|
||||
- **Improved Performance and Availability:** Benefit from faster upload speeds for decentralized storage, facilitated by our global edge infrastructure.
|
||||
- **Unlock Product Suite:** Access powerful tools that allow you to easily build apps on top of your contracts, including SDKs and a dashboard.
|
||||
- **User-friendly interface:** You can easily upload files directly through the dashboard, CLI, or SDKs.
|
||||
- **Faster and high availability:** Enable fast downloads for decentralized storage through a reliable global infrastructure, simplifying data access and retrieval.
|
||||
- **Censorship resistant:** Store your data with greater privacy and security using our decentralized storage solution.
|
||||
|
||||
#### Pricing & Billing
|
||||
|
||||
Storage offers usage-based pricing based on the amount of data uploaded and pinned per account. The first 1 GB is free, and each additional gigabyte incurs a charge of $0.10 per billing cycle.
|
||||
|
||||
If you're interested in upgrading your storage limits, you can find more information on the different tiers by visiting [thirdweb's pricing page](https://thirdweb.com/pricing).
|
||||
@@ -74,7 +74,7 @@ If you are signed in with an EOA wallet, you will be prompted to sign the transa
|
||||
|
||||
### Modular Contract
|
||||
|
||||
This contract is created using the modular contract framework. Modules on this contract are upgradeable. [Learn more about Modular Contracts.](https://portal.thirdweb.com/contracts/modular-contracts/overview)
|
||||
This contract is created using the modular contract framework. Modules on this contract are upgradeable. [Learn more about Modular Contracts.](/contracts/modular-contracts)
|
||||
|
||||
### Configure Modules
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Transfering (sending) tokens using thirdweb extensions
|
||||
# Transferring (sending) tokens using thirdweb extensions
|
||||
|
||||
## ERC721
|
||||
```typescript
|
||||
|
||||
@@ -58,7 +58,7 @@ const transactionResult = await sendTransaction({
|
||||
|
||||
### Example: `transfer()` extension for ERC20 tokens
|
||||
|
||||
This extension conveniently handles unit conversion when transfering ERC20 tokens. You can pass the amount in tokens, the extension will convert it to the right unit before calling the contract.
|
||||
This extension conveniently handles unit conversion when Transferring ERC20 tokens. You can pass the amount in tokens, the extension will convert it to the right unit before calling the contract.
|
||||
|
||||
```typescript
|
||||
import { getContract, sendTransaction } from "thirdweb";
|
||||
|
||||
@@ -72,7 +72,7 @@ sendTx(transaction);
|
||||
|
||||
As you can see, by pairing the contract extensions with `useReadContract` (for read) and `useSendTransaction` (for write),
|
||||
we are able to greatly reduce the amount of code that is packaged & shipped to the end users. Plus, with this approach we can dedicate more time
|
||||
to building contract extensions. The SDK v5 currenty supports over hundreds of extensions, with some popular protocols like Uniswap, Farcaster, Lens & more to come.
|
||||
to building contract extensions. The SDK v5 currently supports over hundreds of extensions, with some popular protocols like Uniswap, Farcaster, Lens & more to come.
|
||||
|
||||
View a list of [supported extensions](/typescript/v5/extensions/built-in) here, or [build your own](/typescript/v5/extensions/create)!
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# @thirdweb-dev/ai-sdk-provider
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7956](https://github.com/thirdweb-dev/js/pull/7956) [`6ef2f3b`](https://github.com/thirdweb-dev/js/commit/6ef2f3b37a0e66a737d26d6753d507baa9329d56) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Auto handle session ids
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7944](https://github.com/thirdweb-dev/js/pull/7944) [`ac3a95e`](https://github.com/thirdweb-dev/js/commit/ac3a95ebbb2aa6b8fe068606e019e9cb9d10b151) Thanks [@joaquim-verges](https://github.com/joaquim-verges)! - Proper type inference
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -56,5 +56,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.1.0"
|
||||
"version": "0.1.2"
|
||||
}
|
||||
|
||||
@@ -26,15 +26,24 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
|
||||
private config: ThirdwebConfig;
|
||||
private settings: ThirdwebSettings;
|
||||
private sessionStore: SessionStore;
|
||||
private chatId: string;
|
||||
|
||||
constructor(
|
||||
modelId: string,
|
||||
settings: ThirdwebSettings,
|
||||
config: ThirdwebConfig,
|
||||
sessionStore: SessionStore,
|
||||
chatId?: string,
|
||||
) {
|
||||
this.modelId = modelId;
|
||||
this.settings = settings;
|
||||
this.config = config;
|
||||
this.sessionStore = sessionStore;
|
||||
this.chatId = chatId || this.generateRandomChatId();
|
||||
if (this.chatId && settings.context?.session_id) {
|
||||
this.sessionStore.setSessionId(this.chatId, settings.context.session_id);
|
||||
}
|
||||
}
|
||||
|
||||
private getHeaders() {
|
||||
@@ -61,6 +70,39 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
return headers;
|
||||
}
|
||||
|
||||
private getSessionId() {
|
||||
return this.sessionStore.getSessionId(this.chatId);
|
||||
}
|
||||
|
||||
private setSessionId(sessionId: string) {
|
||||
this.sessionStore.setSessionId(this.chatId, sessionId);
|
||||
}
|
||||
|
||||
private generateRandomChatId(): string {
|
||||
// Use crypto.randomUUID if available (modern browsers and Node.js 14.17+)
|
||||
if (
|
||||
typeof crypto !== "undefined" &&
|
||||
typeof crypto.randomUUID === "function"
|
||||
) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Fallback for older environments - generate a random string
|
||||
const chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
|
||||
// Generate timestamp prefix for uniqueness
|
||||
const timestamp = Date.now().toString(36);
|
||||
|
||||
// Add random suffix
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
|
||||
return `chat_${timestamp}_${result}`;
|
||||
}
|
||||
|
||||
private convertMessages(prompt: LanguageModelV2CallOptions["prompt"]) {
|
||||
return prompt.map((message) => {
|
||||
switch (message.role) {
|
||||
@@ -122,14 +164,12 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
.reverse()
|
||||
.find((m) => m.role === "user" || m.role === "tool");
|
||||
const messages =
|
||||
this.settings.context?.session_id && lastUserMessage
|
||||
? [lastUserMessage]
|
||||
: allMessages;
|
||||
this.getSessionId() && lastUserMessage ? [lastUserMessage] : allMessages;
|
||||
|
||||
const body = {
|
||||
messages,
|
||||
stream: false,
|
||||
context: this.settings.context,
|
||||
context: { ...this.settings.context, session_id: this.getSessionId() },
|
||||
};
|
||||
|
||||
const response = await fetch(
|
||||
@@ -187,14 +227,12 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
.reverse()
|
||||
.find((m) => m.role === "user" || m.role === "tool");
|
||||
const messages =
|
||||
this.settings.context?.session_id && lastUserMessage
|
||||
? [lastUserMessage]
|
||||
: allMessages;
|
||||
this.getSessionId() && lastUserMessage ? [lastUserMessage] : allMessages;
|
||||
|
||||
const body = {
|
||||
messages,
|
||||
stream: true,
|
||||
context: this.settings.context,
|
||||
context: { ...this.settings.context, session_id: this.getSessionId() },
|
||||
};
|
||||
|
||||
const response = await fetch(
|
||||
@@ -220,6 +258,7 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
stream: new ReadableStream<LanguageModelV2StreamPart>({
|
||||
@@ -358,6 +397,7 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
if (currentEvent === "init") {
|
||||
const parsed = JSON.parse(data);
|
||||
if (parsed.session_id) {
|
||||
self.setSessionId(parsed.session_id);
|
||||
controller.enqueue({
|
||||
type: "response-metadata",
|
||||
id: parsed.session_id || "",
|
||||
@@ -498,13 +538,21 @@ class ThirdwebLanguageModel implements LanguageModelV2 {
|
||||
|
||||
export class ThirdwebProvider implements ProviderV2 {
|
||||
private config: ThirdwebConfig;
|
||||
private session: SessionStore;
|
||||
|
||||
constructor(config: ThirdwebConfig = {}) {
|
||||
this.config = config;
|
||||
this.session = new SessionStore();
|
||||
}
|
||||
|
||||
chat(settings: ThirdwebSettings = {}) {
|
||||
return new ThirdwebLanguageModel("t0-latest", settings, this.config);
|
||||
chat(id?: string, settings: ThirdwebSettings = {}) {
|
||||
return new ThirdwebLanguageModel(
|
||||
"t0-latest",
|
||||
settings,
|
||||
this.config,
|
||||
this.session,
|
||||
id,
|
||||
);
|
||||
}
|
||||
|
||||
tools() {
|
||||
@@ -512,7 +560,12 @@ export class ThirdwebProvider implements ProviderV2 {
|
||||
}
|
||||
|
||||
languageModel(modelId: string, settings: ThirdwebSettings = {}) {
|
||||
return new ThirdwebLanguageModel(modelId, settings, this.config);
|
||||
return new ThirdwebLanguageModel(
|
||||
modelId,
|
||||
settings,
|
||||
this.config,
|
||||
this.session,
|
||||
);
|
||||
}
|
||||
|
||||
textEmbeddingModel(_modelId: string): EmbeddingModelV2<string> {
|
||||
@@ -526,6 +579,22 @@ export class ThirdwebProvider implements ProviderV2 {
|
||||
}
|
||||
}
|
||||
|
||||
class SessionStore {
|
||||
private sessionId: Map<string, string> = new Map();
|
||||
|
||||
getSessionId(chatId: string) {
|
||||
return this.sessionId.get(chatId) || null;
|
||||
}
|
||||
|
||||
setSessionId(chatId: string, sessionId: string) {
|
||||
this.sessionId.set(chatId, sessionId);
|
||||
}
|
||||
|
||||
clearSessionId(chatId: string) {
|
||||
this.sessionId.delete(chatId);
|
||||
}
|
||||
}
|
||||
|
||||
// Factory function for easier usage
|
||||
export function createThirdwebAI(config: ThirdwebConfig = {}) {
|
||||
return new ThirdwebProvider(config);
|
||||
|
||||
@@ -48,32 +48,6 @@ export type MonitorTransactionOutput = z.infer<
|
||||
typeof AgentActionMonitorTransactionOutputData
|
||||
>;
|
||||
|
||||
export function createTools(_config: ThirdwebConfig): Record<string, Tool> {
|
||||
return {
|
||||
sign_transaction: {
|
||||
id: "thirdweb.sign_transaction" as const,
|
||||
name: "sign_transaction",
|
||||
description: "Sign a transaction",
|
||||
inputSchema: AgentActionSignTransactionData,
|
||||
outputSchema: AgentActionTransactionOutputData,
|
||||
} as const,
|
||||
sign_swap: {
|
||||
id: "thirdweb.sign_swap" as const,
|
||||
name: "sign_swap",
|
||||
description: "Sign a swap transaction",
|
||||
inputSchema: AgentActionSignSwapData,
|
||||
outputSchema: AgentActionTransactionOutputData,
|
||||
} as const,
|
||||
monitor_transaction: {
|
||||
id: "thirdweb.monitor_transaction" as const,
|
||||
name: "monitor_transaction",
|
||||
description: "Monitor a transaction",
|
||||
inputSchema: AgentActionMonitorTransactionData,
|
||||
outputSchema: AgentActionMonitorTransactionOutputData,
|
||||
} as const,
|
||||
} as const;
|
||||
}
|
||||
|
||||
export type SignTransactionInput = z.infer<
|
||||
typeof AgentActionSignTransactionData
|
||||
>;
|
||||
@@ -81,3 +55,29 @@ export type SignSwapInput = z.infer<typeof AgentActionSignSwapData>;
|
||||
export type MonitorTransactionInput = z.infer<
|
||||
typeof AgentActionMonitorTransactionData
|
||||
>;
|
||||
|
||||
export function createTools(_config: ThirdwebConfig) {
|
||||
return {
|
||||
sign_transaction: {
|
||||
id: "thirdweb.sign_transaction" as const,
|
||||
name: "sign_transaction",
|
||||
description: "Sign a transaction",
|
||||
inputSchema: AgentActionSignTransactionData,
|
||||
outputSchema: AgentActionTransactionOutputData,
|
||||
} satisfies Tool<SignTransactionInput, TransactionOutput>,
|
||||
sign_swap: {
|
||||
id: "thirdweb.sign_swap" as const,
|
||||
name: "sign_swap",
|
||||
description: "Sign a swap transaction",
|
||||
inputSchema: AgentActionSignSwapData,
|
||||
outputSchema: AgentActionTransactionOutputData,
|
||||
} satisfies Tool<SignSwapInput, TransactionOutput>,
|
||||
monitor_transaction: {
|
||||
id: "thirdweb.monitor_transaction" as const,
|
||||
name: "monitor_transaction",
|
||||
description: "Monitor a transaction",
|
||||
inputSchema: AgentActionMonitorTransactionData,
|
||||
outputSchema: AgentActionMonitorTransactionOutputData,
|
||||
} satisfies Tool<MonitorTransactionInput, MonitorTransactionOutput>,
|
||||
} as const;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @thirdweb-dev/nebula
|
||||
|
||||
## 0.2.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`af6a306`](https://github.com/thirdweb-dev/js/commit/af6a306955aaf6a76fc22e3f8fd4e78854680d9c)]:
|
||||
- thirdweb@5.105.41
|
||||
|
||||
## 0.2.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`597431e`](https://github.com/thirdweb-dev/js/commit/597431ec1dc7b6a8b52b6676256e77d96b9a16b1)]:
|
||||
- thirdweb@5.105.40
|
||||
|
||||
## 0.2.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`50c6371`](https://github.com/thirdweb-dev/js/commit/50c6371e1ec12e296839987a6c25d9814dea2208)]:
|
||||
- thirdweb@5.105.39
|
||||
|
||||
## 0.2.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.38"
|
||||
"version": "0.2.41"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.105.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7966](https://github.com/thirdweb-dev/js/pull/7966) [`af6a306`](https://github.com/thirdweb-dev/js/commit/af6a306955aaf6a76fc22e3f8fd4e78854680d9c) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Adds `country` code option to payment widgets
|
||||
|
||||
## 5.105.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7946](https://github.com/thirdweb-dev/js/pull/7946) [`597431e`](https://github.com/thirdweb-dev/js/commit/597431ec1dc7b6a8b52b6676256e77d96b9a16b1) Thanks [@MananTank](https://github.com/MananTank)! - Add Pay Modal integration in `useSendAndConfirmTransaction` hook similar to `useSendTransaction` hook
|
||||
|
||||
## 5.105.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7942](https://github.com/thirdweb-dev/js/pull/7942) [`50c6371`](https://github.com/thirdweb-dev/js/commit/50c6371e1ec12e296839987a6c25d9814dea2208) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Adds the ability to exclude prices from Bridge.tokens
|
||||
|
||||
## 5.105.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -417,5 +417,5 @@
|
||||
}
|
||||
},
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "5.105.38"
|
||||
"version": "5.105.41"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getClientFetch } from "../utils/fetch.js";
|
||||
import { stringify } from "../utils/json.js";
|
||||
import { ApiError } from "./types/Errors.js";
|
||||
import type { RouteStep } from "./types/Route.js";
|
||||
import type { Token } from "./types/Token.js";
|
||||
import type { TokenWithPrices } from "./types/Token.js";
|
||||
|
||||
// export status within the Onramp module
|
||||
export { status } from "./OnrampStatus.js";
|
||||
@@ -33,7 +33,7 @@ type OnrampPrepareQuoteResponseData = {
|
||||
currency: string;
|
||||
currencyAmount: number;
|
||||
destinationAmount: bigint;
|
||||
destinationToken: Token;
|
||||
destinationToken: TokenWithPrices;
|
||||
timestamp?: number;
|
||||
expiration?: number;
|
||||
steps: RouteStep[];
|
||||
|
||||