Compare commits

..
Author SHA1 Message Date
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
b8f496603c Version Packages (#7967)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-02 16:46:46 -07:00
Joey BrightandGitHub 4c782ad64d docs: Fix Typos in docs (#7963) 2025-09-02 16:46:17 -07:00
gregandGitHub af6a306955 [SDK] Feature: country Code on Payment Widgets (#7966) 2025-09-02 16:44:56 -07:00
MananTank 4b319c353d [BLD-227] Dashboard: Add Button to download snapshot as CSV file in Claim Phase UI (#7965)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on refining the `ClaimConditionsForm` and enhancing the `ClaimerSelection` component by adjusting imports, improving layout, and adding functionality to download snapshot data as a CSV file.

### Detailed summary
- Removed `ArrowDownToLineIcon` from `ClaimConditionsForm`.
- Updated layout in `ClaimerSelection` to use a flexible column layout.
- Added a button to download snapshots as CSV in `ClaimerSelection`.
- Introduced `downloadSnapshotAsCSV` function to handle CSV generation and downloading.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- New Features
  - Added CSV export for Claimer snapshots with a Download button visible when a snapshot exists.

- Style
  - Updated snapshot actions to a responsive layout (stack on small screens, inline on larger screens).
  - Added a distinct Download button with an icon and adjusted Edit button styling.
  - Removed the icon from the Save Phases button, showing text only.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-02 20:57:23 +00:00
saminacodes aadaf209b5 deprecated and redirected docs (#7960)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on removing several files related to the `infrastructure` section of the application, including markdown files, images, and TypeScript components, while also updating the `redirects.mjs` file to include new external portal redirects.

### Detailed summary
- Deleted the following files:
  - `apps/portal/src/app/infrastructure/page.mdx`
  - `apps/portal/src/app/infrastructure/layout.tsx`
  - `apps/portal/src/app/infrastructure/sidebar.tsx`
  - `apps/portal/src/app/infrastructure/assets/test.jpg`
  - `apps/portal/src/app/infrastructure/storage/overview/page.mdx`
  - `apps/portal/src/app/infrastructure/rpc-edge/overview/page.mdx`
  - `apps/portal/src/app/infrastructure/rpc-edge/get-started/page.mdx`
  - `apps/portal/src/app/infrastructure/storage/how-storage-works/page.mdx`
  - `apps/portal/src/app/infrastructure/rpc-edge/get-started/assets/api-key.png`
  - `apps/portal/src/app/infrastructure/rpc-edge/overview/assets/rpc-diagram.svg`
  - `apps/portal/src/app/infrastructure/rpc-edge/get-started/assets/public-rpcs.png`
  - `apps/portal/src/app/infrastructure/rpc-edge/get-started/assets/usage-dashboard.png`
  - `apps/portal/src/app/infrastructure/storage/how-to-use-storage/host-web-app/page.mdx`
  - `apps/portal/src/app/infrastructure/storage/how-storage-works/assets/storage-diagram.svg`
  - `apps/portal/src/app/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs/page.mdx`
  - `apps/portal/src/app/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs/assets/storage-urls.png`
  - `apps/portal/src/app/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs/assets/storage-upload.png`
  - `apps/portal/src/app/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs/assets/storage-dashboard.png`
- Updated `apps/portal/redirects.mjs` to:
  - Redirect `"/storage/upload-to-ipfs"` to `"/infrastructure/storage/how-to-use-storage/upload-files-to-ipfs"`
  - Add infrastructure redirects to external portal links for `"/infrastructure/storage/:path*"` and `"/infrastructure/rpc-edge/:path*"`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- Documentation
  - Removed the Infrastructure section from the portal, including Overview, Storage (Overview, How Storage Works, Upload Files to IPFS, Host Web App), and RPC Edge (Overview, Get Started) pages.
  - Updated navigation accordingly to reflect the removal of these pages.

- New Features
  - Added automatic redirects for Infrastructure content:
    - /infrastructure/storage/:path* now points to the external Storage documentation.
    - /infrastructure/rpc-edge/:path* now points to the external RPC documentation.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-09-02 16:56:21 +00:00
Joaquim VergesandGitHub df6f8e2646 [Playground] Refactor AI types and move definitions to types.ts (#7964) 2025-09-02 02:05:40 -07:00
5b871ac281 [Portal] docs: fix broken link (#7962)
Signed-off-by: VolodymyrBg <[email protected]>
Signed-off-by: Joaquim Verges <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
2025-09-02 20:54:54 +12:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
7eed8e61db Version Packages (#7957)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-01 00:06:04 -07:00
Joaquim VergesandGitHub 6ef2f3b37a [AI SDK Provider] Auto handle session ids (#7956) 2025-08-31 22:10:49 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
596aaa5495 Version Packages (#7949)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-31 16:18:40 -07:00
MananTank 89eeac1f8a Dashboard: Fix footer not sticked to bottom on short contracts page (#7951)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on enhancing the layout of the `contract-page-layout.tsx` component by adding a CSS class to the main `div` element for improved styling and structure.

### Detailed summary
- Changed the main `div` element from `<div>` to `<div className="flex flex-col grow">` to apply flexbox layout and allow the element to grow.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **Style**
  * Updated contract page layout to use a flexible, column-based structure for more consistent spacing.
  * Improves use of available screen space so content expands naturally within the dashboard.
  * Enhances responsiveness across viewports, reducing awkward white space and unnecessary scrolling.
  * Provides more stable alignment between sidebar and main content on contract detail pages.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-30 04:03:07 +00:00
MananTank c331d189bb Dashboard: Remove OP Sponsored badge in Chain selector (#7948)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR removes the handling of sponsored chains from the `CustomChainRenderer` component, including the related constant and UI elements that indicate whether a chain is sponsored.

### Detailed summary
- Deleted the import of `OPSponsoredChains` from `@/constants/chains`.
- Removed the `isSponsored` variable that checks if a chain is sponsored.
- Eliminated the UI block that displayed "Sponsored" for sponsored chains.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- Refactor
  - Removed the Sponsored badge from the chain list, simplifying the chain renderer.
  - Deprecated chains still display a Deprecated badge and remain non-interactive.
  - Clicking non-deprecated chains continues to switch networks; the Configure Network button is unchanged.

- Chores
  - Removed obsolete sponsor-related configuration and references.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 18:13:06 +00:00
MananTank f867799301 [MNY-129] Dashboard: Use useSendAndConfirmTx everywhere, Replace createToken with prepareCreateToken (#7947)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR primarily focuses on replacing the usage of `useSendAndConfirmTransaction` with `useSendAndConfirmTx` across various components in the dashboard application, streamlining transaction handling.

### Detailed summary
- Replaced `useSendAndConfirmTransaction` with `useSendAndConfirmTx` in multiple components.
- Updated imports to reflect the new hook in relevant files.
- Modified transaction handling logic to utilize the new hook consistently across the codebase.

> The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Mintable.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Claimable.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/_components/claim-tokens/claim-tokens-ui.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Royalty.tsx`, `apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/index.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/nft/create-nft-page.tsx`, `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page-impl.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
2025-08-29 16:25:21 +00:00
MananTank 597431ec1d [MNY-131] Add Pay Modal integration in useSendAndConfirmTransaction hook (#7946)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a `Pay Modal` integration in the `useSendAndConfirmTransaction` hook, allowing users to handle insufficient funds by prompting them to purchase more. It also renames the hook and updates related components to utilize the new configuration.

### Detailed summary
- Renamed `useSendAndConfirmTransaction` to `useSendAndConfirmTransactionCore`.
- Updated exports to reflect the renaming.
- Added `payModal` configuration to `useSendAndConfirmTransaction`.
- Updated `CreateDirectListingButton`, `ClaimButton`, and `BuyDirectListingButton` to utilize the new `payModal` configuration.
- Enhanced documentation for `useSendAndConfirmTransaction` with usage examples for the `Pay Modal`.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Pay Modal integrated into send-and-confirm transaction flows, matching send-only behavior.
  * Prebuilt buttons (Buy Direct Listing, Claim, Create Direct Listing) pass Pay Modal config and auto-fill metadata with sensible fallbacks for smoother top-ups.
  * Pay Modal options are configurable and compatible with gasless workflows.

* **Documentation**
  * Added examples and guidance for configuring the Pay Modal (theme, disabling, and usage).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 16:25:20 +00:00
GiselleNessi d5ff9f2f5d feat: add support siwa feedback system for closed support tickets (#7916)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR introduces a feedback system for support tickets, allowing users to submit ratings and comments. It includes API calls for submitting feedback and checking its status, along with UI updates to handle user interactions and display feedback status.

### Detailed summary
- Added `submitSupportFeedback` function to submit feedback.
- Added `checkFeedbackStatus` function to verify if feedback has been submitted.
- Integrated feedback submission UI in `SupportCaseDetails` component.
- Implemented rating selection using stars.
- Added input field for optional feedback comments.
- Displayed loading and error states for feedback status checks.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * 5‑star rating UI and optional comment for closed support cases, with Send Feedback button, “Checking feedback status…” and sending spinners, disabled send until a rating is chosen, and a thank‑you message after submission.

* **Improvements**
  * Server-backed feedback status check and submission with input validation, 1–5 rating, 1000‑char comment limit, 10s request timeout, and contextual success/error toasts.
  * Accessible star controls with ARIA labels and filled‑star visuals.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 15:27:15 +00:00
saminacodes 76694f0cec Update Contracts FAQ (#7930)
<!--

## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes"

If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000):

## Notes for the reviewer

Anything important to call out? Be sure to also clarify these in your comments.

## How to test

Unit tests, playground, etc.

-->

<!-- start pr-codex -->

---

## PR-Codex overview
This PR adds new information to the `page.mdx` file regarding deployment costs and auditing of thirdweb contracts.

### Detailed summary
- Added a new `Details` section on deployment costs for thirdweb contracts, outlining:
  - Mint fees and primary sales fees for specific contract types.
  - A 2% convenience fee and a 0.5% protocol fee on primary sales.
  - Clarified that fees apply only to prebuilt contracts via the dashboard.
- Included information about thirdweb contracts being audited by third-party firms.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Documentation**
  * Added an FAQ explaining costs for deploying prebuilt contracts via the dashboard.
  * Clarifies a 2% dashboard deployment fee plus a 0.5% protocol fee (total 2.5%) taken from primary sales.
  * Includes an example payout and lists affected contract categories.
  * Notes that custom or externally deployed contracts are exempt.
  * Specifies fees are deducted in the sale’s currency and placed after the existing FAQ on using thirdweb with existing contracts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-29 01:16:31 +00:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
65a8c39ce0 Version Packages (#7945)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-28 18:15:35 -07:00
Joaquim VergesandGitHub ac3a95ebbb [AI SDK Provider] Fix type inference for tools (#7944) 2025-08-28 18:10:21 -07:00
Joaquim VergesGitHubgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
00ffe3e46e Version Packages (#7943)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-28 15:46:28 -07:00
gregandGitHub 50c6371e1e [SDK] Feature: add ability to exclude token prices from Bridge.tokens (#7942) 2025-08-28 15:22:12 -07:00
136 changed files with 1333 additions and 1725 deletions
+24
View File
@@ -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,
-23
View File
@@ -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,
];
+19
View File
@@ -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;
}
+3 -5
View File
@@ -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) => ({
+3 -3
View File
@@ -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
+5 -29
View File
@@ -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} />
</>
);
}`}
+7 -41
View File
@@ -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>
+2 -14
View File
@@ -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,
};
}
},
});
}
+5
View File
@@ -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 = {
+25 -62
View File
@@ -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
+38 -2
View File
@@ -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.
Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

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" />
File diff suppressed because one or more lines are too long

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",
};
File diff suppressed because one or more lines are too long

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 KiB

Binary file not shown.

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)!
+12
View File
@@ -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
+1 -1
View File
@@ -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"
}
+80 -11
View File
@@ -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);
+26 -26
View File
@@ -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;
}
+21
View File
@@ -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
+1 -1
View File
@@ -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"
}
+18
View File
@@ -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
+1 -1
View File
@@ -417,5 +417,5 @@
}
},
"typings": "./dist/types/exports/thirdweb.d.ts",
"version": "5.105.38"
"version": "5.105.41"
}
+2 -2
View File
@@ -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[];

Some files were not shown because too many files have changed in this diff Show More