-
Notifications
You must be signed in to change notification settings - Fork 568
Dashboard, SDK: ERC20 assets #7321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🦋 Changeset detectedLatest commit: d227bc3 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The latest updates on your projects. Learn more about Vercel for GitHub.
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. |
WalkthroughAdds ERC20 asset support and rewards features: new asset type "ERC20Asset", bifurcated token creation/launch flows (ERC20Asset vs DropERC20), form/schema and UI updates for dual sale modes, rewards page and helpers, a tokens SDK export barrel, and assorted component prop and UI tweaks. Includes a release changeset. Changes
Sequence Diagram(s)mermaid UI->>UI: User selects saleMode (erc20-asset or drop-erc20) Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/thirdweb/src/assets/index.ts (1)
1-4
: Same extension concern & future export growth
- Ensure the
.js
suffix plays nicely with the compiler/bundler (see previous note).- As
assets
utilities grow, consider grouping exports in anindex.ts
barrel only once they stabilise; otherwise tree-shaking may pull in unfinished stubs.packages/thirdweb/scripts/generate/abis/assets/AssetEntrypointERC20.json (1)
1-34
: Nit: store ABI as generated artefact, not sourceCommitted raw ABI arrays bloat the repo (~4 KB here) and create noisy diffs. If this file is generated by a script in
/scripts/generate
, prefer ignoring it in Git and generating during build/publish instead.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (19)
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/events/AssetCreated.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/events/AssetDistributed.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/events/ImplementationAdded.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/events/RewardLockerUpdated.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/events/RouterUpdated.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/read/getImplementation.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/read/getRewardLocker.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/read/getRouter.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/addImplementation.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/buyAsset.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAsset.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAssetById.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAssetByImplementationConfig.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/distributeAsset.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/initialize.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/listAsset.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/sellAsset.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/setRewardLocker.ts
is excluded by!**/__generated__/**
packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/setRouter.ts
is excluded by!**/__generated__/**
📒 Files selected for processing (4)
packages/thirdweb/scripts/generate/abis/assets/AssetEntrypointERC20.json
(1 hunks)packages/thirdweb/src/assets/create-token.ts
(1 hunks)packages/thirdweb/src/assets/index.ts
(1 hunks)packages/thirdweb/src/exports/assets.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/thirdweb/src/assets/create-token.ts
[error] 1-1: Don't use '{}' as a type.
Prefer explicitly define the object shape. '{}' means "any non-nullable value".
(lint/complexity/noBannedTypes)
[error] 3-3: This parameter is unused.
Unused variables usually are result of incomplete refactoring, typos and other source of bugs.
Unsafe fix: If this is intentional, prepend params with an underscore.
(lint/correctness/noUnusedVariables)
⏰ Context from checks skipped due to timeout of 90000ms (8)
- GitHub Check: Size
- GitHub Check: E2E Tests (pnpm, vite)
- GitHub Check: Lint Packages
- GitHub Check: Build Packages
- GitHub Check: E2E Tests (pnpm, webpack)
- GitHub Check: E2E Tests (pnpm, esbuild)
- GitHub Check: Unit Tests
- GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/thirdweb/src/exports/assets.ts (1)
1-1
: Double-check relative path & extensionThe re-export targets
"../assets/index.js"
, but the source file isindex.ts
in the same tree.
This pattern is okay iftsc
emits.js
files beside.d.ts
and the runtime resolution happens on compiled output. Otherwise you’ll hit module-not-found in tests or ts-node. Verify the build pipeline supports “import with .js extension from TS”.packages/thirdweb/scripts/generate/abis/assets/AssetEntrypointERC20.json (1)
7-12
:❌ Incorrect review comment
Struct/event field size mismatch – potential ABI desync
Struct
addImplementation.config
usesbytes createHookData
, butImplementationAdded
event declaresbytes32 createHookData
.
If the underlying Solidity contract really emitsbytes32
, calls encodingbytes
will still compile but the event parser will truncate data.Confirm the canonical Solidity source and align both the function parameter and event field to the same type.
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "Searching for the ImplementationAdded event in Solidity files..." rg -n "event ImplementationAdded" --glob "*.sol" echo echo "Context around event ImplementationAdded:" rg -A3 -B3 "event ImplementationAdded" --glob "*.sol" echo echo "Searching for function addImplementation in Solidity files..." rg -n "function addImplementation" --glob "*.sol" echo echo "Context around function addImplementation:" rg -A3 -B3 "function addImplementation" --glob "*.sol" echo echo "Searching for structs related to ImplementationConfig or config:" rg -n "struct .*Implementation.*" --glob "*.sol" echo echo "Context around those struct definitions:" rg -A3 -B3 "struct .*Implementation.*" --glob "*.sol"Length of output: 358
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "Searching for the ImplementationAdded event in JSON ABI files..." rg -n '"name":\s*"ImplementationAdded"' --glob "*.json" echo echo "Context around the ImplementationAdded event:" rg -A5 -B5 '"name":\s*"ImplementationAdded"' --glob "*.json" echo echo "Inspecting createHookData field in this event:" rg -A2 -B2 '"createHookData"' --glob "*.json"Length of output: 216
No
ImplementationAdded
event in ABI – review comment can be ignoredThe ABI at
packages/thirdweb/scripts/generate/abis/assets/AssetEntrypointERC20.json
only contains function definitions and does not declare anyImplementationAdded
event (nor abytes32 createHookData
field). There is no mismatch to resolve.Likely an incorrect or invalid review comment.
size-limit report 📦
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7321 +/- ##
==========================================
- Coverage 56.34% 56.33% -0.02%
==========================================
Files 905 907 +2
Lines 58834 58965 +131
Branches 4150 4159 +9
==========================================
+ Hits 33151 33216 +65
- Misses 25577 25643 +66
Partials 106 106
🚀 New features to boost your workflow:
|
Merge activity
|
<!-- ## 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 `thirdweb` package by introducing new functionalities and updates for managing ERC20 assets, including the implementation of various events, functions, and utility methods for asset deployment and interaction. ### Detailed summary - Added `initialize` functions to various asset-related contracts. - Introduced event handling for `AssetDistributed`, `RouterUpdated`, and `RewardLockerUpdated`. - Implemented methods for checking router and reward locker support. - Enhanced the `distributeToken` function to handle multiple recipients. - Updated ABI files for new function signatures and events. - Improved utility functions for encoding parameters and transaction preparation. > The following files were skipped due to too many changes: `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/initialize.ts`, `packages/thirdweb/src/extensions/assets/__generated__/FeeManager/write/initialize.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/listAsset.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/distributeAsset.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAsset.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/sellAsset.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/buyAsset.ts`, `packages/thirdweb/src/extensions/assets/__generated__/ERC20Asset/write/initialize.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/addImplementation.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAssetById.ts`, `packages/thirdweb/src/extensions/assets/__generated__/AssetInfraDeployer/write/deployInfraProxyDeterministic.ts`, `packages/thirdweb/package.json`, `packages/thirdweb/src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAssetByImplementationConfig.ts`, `packages/thirdweb/src/assets/bootstrap.ts` > ✨ 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 on-page Rewards workflow (Claim Rewards page, UI, and sidebar link) and expanded token creation flows for ERC20 assets with dedicated sale/airdrop options. * **UI / UX** * Improved token sale & distribution UIs, grouped recent transfers, loading placeholders, styling tweaks, chain prioritization in network selectors, customizable wallet fallback icon, and chart legend/value display. * **API / Props** * Several component props and callbacks updated to include chain/contract info and router availability. * **Chores** * Release metadata added for an ERC20 assets patch. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
b22b7b7
to
d227bc3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🔭 Outside diff range comments (2)
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/distribution/token-distribution.tsx (2)
108-116
: Guard against invalid/zero supply in distribution calculationsIf supply is 0 or NaN, downstream calculations produce NaN/Infinity and can let the Next button be enabled erroneously. Add an early validation error here (complements the schema change).
function getDistributionError(form: TokenDistributionForm) { - const supply = Number(form.watch("supply")); + const supply = Number(form.watch("supply")); + if (!Number.isFinite(supply) || supply <= 0) { + return "Enter a valid total supply"; + }
143-154
: Harden bar chart math against NaN/Infinity and improve numeric coercionUse SafeNumber consistently and avoid division by zero.
- const totalSupply = Number(props.distributionFormValues.supply); - const totalAirdropSupply = - props.distributionFormValues.airdropAddresses.reduce( - (acc, curr) => acc + Number(curr.quantity), - 0, - ); - const airdropPercentage = (totalAirdropSupply / totalSupply) * 100; + const totalSupply = SafeNumber(props.distributionFormValues.supply as string); + const totalAirdropSupply = + props.distributionFormValues.airdropAddresses.reduce( + (acc, curr) => acc + SafeNumber(curr.quantity), + 0, + ); + const airdropPercentage = + totalSupply > 0 ? (totalAirdropSupply / totalSupply) * 100 : 0; const salePercentage = Number( props.distributionFormValues.saleMode === "erc20-asset:pool" ? props.distributionFormValues.erc20Asset_poolMode .saleAllocationPercentage : props.distributionFormValues.dropERC20Mode.saleAllocationPercentage, ); const ownerPercentage = Math.max(100 - airdropPercentage - salePercentage, 0); const tokenAllocations: Segment[] = [ { color: "hsl(var(--chart-1))", label: "Owner", percent: ownerPercentage, - value: `${ownerPercentage}%`, + value: `${ownerPercentage}%`, }, { color: "hsl(var(--chart-3))", label: "Airdrop", percent: airdropPercentage, - value: `${airdropPercentage}%`, + value: `${airdropPercentage}%`, }, { color: "hsl(var(--chart-4))", label: "Sale", percent: salePercentage, - value: `${salePercentage}%`, + value: `${salePercentage}%`, }, ];Optionally format to 2 decimals for cleaner display: use toFixed(2) before string interpolation.
Also applies to: 156-176
🧹 Nitpick comments (24)
apps/dashboard/src/@/hooks/project-contracts.ts (6)
14-19
: Centralize contractType and make it optional instead of| undefined
Defining the union inline increases drift risk as more asset types are added. Prefer a local type alias (or reuse a shared type barrel if one exists) and mark the field optional.
Apply this diff here:
- contractType: - | "ERC20Asset" - | "DropERC721" - | "DropERC1155" - | "DropERC20" - | undefined; + contractType?: ContractType;And add a local type alias near the top of the file (or import from a shared types barrel if available):
type ContractType = "ERC20Asset" | "DropERC721" | "DropERC1155" | "DropERC20";
13-13
: Use optional property instead of union-with-undefined for deploymentTypeThis is more idiomatic and signals the field can be omitted (JSON.stringify will skip it).
- deploymentType: "asset" | undefined; + deploymentType?: "asset";
7-8
: Add a mutationKey for React Query devtools clarity and dedupingNot required, but helpful for debugging and potential deduplication.
return useMutation({ + mutationKey: ["project-contracts", "add"], mutationFn: async (params: {
35-38
: Avoid leaking raw server errors to the browser consoleThis hook runs on the client; logging raw backend errors can expose internals. Log only in dev and throw a user-safe message.
- if (!res.ok) { - console.error(res.error); - throw new Error(res.error); - } + if (!res.ok) { + if (process.env.NODE_ENV !== "production") { + console.error(res.error); + } + throw new Error("Failed to add contract to project"); + }
54-58
: Add explicit return type to align with TS guidelinesThe dashboard guidelines prefer explicit return types. Add the Promise shape to the signature.
-export async function removeContractFromProject(params: { +export async function removeContractFromProject(params: { teamId: string; projectId: string; contractId: string; -}) { +}): Promise<{ result: { success: boolean } }> {
64-66
: Mirror error handling pattern from the add hook and avoid exposing raw messagesFor consistency and to avoid surfacing backend messages directly to users, guard logs in dev and throw a generic error.
- if (!res.ok) { - throw new Error(res.error); - } + if (!res.ok) { + if (process.env.NODE_ENV !== "production") { + console.error(res.error); + } + throw new Error("Failed to remove contract from project"); + }.changeset/young-carrots-burn.md (1)
5-5
: Make the changeset description user-facing and specific.“ERC20 assets” is too terse for release notes. Briefly summarize the new surface (tokens export), key ABIs, and dashboard capability so downstream consumers understand what’s in this patch.
Apply this diff to improve the description:
-ERC20 assets +Add ERC20 asset support: +- expose a new SDK surface at `thirdweb/tokens` +- add ERC20Entrypoint/RewardLocker/PoolRouter/FeeManager ABIs for generation +- enable rewards claim flow in the dashboard +- no breaking API changesapps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/page.tsx (3)
1-1
: Mark this as a Server Component.Per dashboard guidelines, add
import "server-only";
to ensure this page isn’t accidentally bundled for the client.+import "server-only"; import { notFound, redirect } from "next/navigation";
70-77
: Avoid passing client instances across the server/client boundary.To keep the payload lean and avoid serialization pitfalls, prefer passing primitives (address, chain slug/id) to the client component and creating the client-side contract within it. You’re already doing the server-side variant above for reward discovery; mirror that pattern client-side within
ClaimRewardsPage
.Example direction (requires adjusting
ClaimRewardsPage
props):- <ClaimRewardsPage - assetContractClient={info.clientContract} - entrypointContractClient={getContract({ - address: entrypointContractAddress, - chain: chain, - client: info.clientContract.client, - })} - reward={reward} - unclaimedFees={unclaimedFees} - chainSlug={info.chainMetadata.slug} - /> + <ClaimRewardsPage + assetAddress={info.clientContract.address} + entrypointAddress={entrypointContractAddress} + chainSlug={info.chainMetadata.slug} + reward={reward} + unclaimedFees={unclaimedFees} + />Then, inside
ClaimRewardsPage
(client), construct the contract viagetContract
using the client-side SDK.
35-39
: Minor: handle missing entrypoint defensively.If
getDeployedEntrypointERC20
fails or returns no address on unsupported chains, consider short-circuiting tonotFound()
early to avoid downstream errors.apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page.stories.tsx (1)
30-54
: DRY the async delay with a small sleep helperYou repeat the 1s delay snippet several times. Centralize to a tiny helper for readability.
Apply this diff within the selected ranges to use a helper:
- airdropTokens: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }, + airdropTokens: async () => { + await sleep(1000); + }, - approveAirdropTokens: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }, + approveAirdropTokens: async () => { + await sleep(1000); + }, - deployContract: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); + deployContract: async () => { + await sleep(1000); return { contractAddress: "0x123" }; },- airdropTokens: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }, + airdropTokens: async () => { + await sleep(1000); + }, - deployContract: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); + deployContract: async () => { + await sleep(1000); return { contractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" }; }, - setClaimConditions: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }, + setClaimConditions: async () => { + await sleep(1000); + }, - mintTokens: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - }, + mintTokens: async () => { + await sleep(1000); + },- deployContract: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); + deployContract: async () => { + await sleep(1000); throw new Error("Failed to deploy contract"); },- deployContract: async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); + deployContract: async () => { + await sleep(1000); throw new Error( "You have reached your storage limit. Please add a valid payment method to continue using the service.", ); },Additionally add this helper near the top (outside the selected ranges):
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));Also applies to: 42-54, 78-81, 99-104
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/contract/[chainIdOrSlug]/[contractAddress]/rewards/components/claim-rewards-page.tsx (1)
209-214
: Replace inline styles with Tailwind arbitrary values to match dashboard guidelinesInline style is discouraged. Use Tailwind’s arbitrary value syntax with your existing CSS vars.
Apply this diff:
- fallbackIcon={ - <div - className="size-3 rounded-full" - style={{ - backgroundColor: recipientColor, - }} - /> - } + fallbackIcon={ + <div className="size-3 rounded-full bg-[hsl(var(--chart-1))]" /> + }- fallbackIcon={ - <div - className="size-3 rounded-full" - style={{ - backgroundColor: developerColor, - }} - /> - } + fallbackIcon={ + <div className="size-3 rounded-full bg-[hsl(var(--chart-2))]" /> + }Also applies to: 227-233
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/_common/form.ts (3)
25-26
: Grammar nit in validation messageMinor copy edit for user-facing error.
- message: "Amount must be number larger than or equal to 0", + message: "Amount must be a number greater than or equal to 0",
41-50
: Avoid double Number() conversion; reuse parsed valueYou already computed numValue. Reuse it for getInitialTickValue for clarity.
- const tick = getInitialTickValue({ - startingPricePerToken: Number(value), - }); + const tick = getInitialTickValue({ + startingPricePerToken: numValue, + });
31-36
: Optional: validate airdrop quantities are numeric and non-negativeRight now quantity is any string; you later coerce with SafeNumber. A light schema guard improves UX.
airdropAddresses: z.array( z.object({ address: addressSchema, - quantity: z.string(), + quantity: z + .string() + .refine((v) => { + const n = Number(v); + return Number.isFinite(n) && n >= 0; + }, "Quantity must be a non-negative number"), }), ),apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/distribution/token-sale.tsx (2)
205-222
: Constrain percentage input to non-negative valuesYou cap max at 100; also cap min at 0 to guard accidental negatives.
- <DecimalInput - maxValue={100} + <DecimalInput + minValue={0} + maxValue={100}
149-157
: Unusedclient
prop inside PoolConfigPoolConfig receives and forwards client but does not use it. Consider removing from the component signature and call to reduce surface area.
-function PoolConfig(props: { - form: TokenDistributionForm; - chainId: string; - client: ThirdwebClient; -}) { +function PoolConfig(props: { + form: TokenDistributionForm; + chainId: string; +}) {- <PoolConfig - chainId={props.chainId} - client={props.client} - form={props.form} - /> + <PoolConfig chainId={props.chainId} form={props.form} />Also applies to: 94-98
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/launch/launch-token.tsx (2)
95-156
: Step orchestration is correct; small async hygiene improvementBoth branches build steps deterministically and gate optional steps correctly (approve-airdrop before airdrop). To avoid unhandled rejections from the async runner, prefix the calls with void (and see the companion comment about not rethrowing inside executeSteps).
- executeSteps(initialSteps, 0, isGasless); + void executeSteps(initialSteps, 0, isGasless); @@ - executeSteps(initialSteps, 0, isGasless); + void executeSteps(initialSteps, 0, isGasless);
217-233
: Don’t rethrow inside executeSteps to prevent unhandled promise rejectionsThe error state is already surfaced via updateStatus and UI retry. Rethrowing here causes an unhandled rejection since callers don’t await/catch. Remove the throw.
updateStatus(i, { message: errorMessage, type: "error", }); - throw error; + // Do not rethrow; error is reflected in step status and UI handles retries.apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page-impl.tsx (5)
146-153
: Avoid unhandled rejections on background addContractToProjectmutateAsync returns a Promise; if you don't await it, swallow/capture errors to prevent unhandled rejections.
- addContractToProject.mutateAsync({ + void addContractToProject + .mutateAsync({ chainId: params.values.chain, contractAddress: contractAddress, contractType: "ERC20Asset", deploymentType: "asset", projectId: props.projectId, teamId: props.teamId, - }); + }) + .catch((err) => console.warn("addContractToProject failed", err));
272-279
: Do the same for DropERC20 addContractToProjectMirror the background error handling here as well.
- addContractToProject.mutateAsync({ + void addContractToProject + .mutateAsync({ chainId: values.chain, contractAddress: contractAddress, contractType: "DropERC20", deploymentType: "asset", projectId: props.projectId, teamId: props.teamId, - }); + }) + .catch((err) => console.warn("addContractToProject failed", err));
443-449
: Make bridge trigger fire-and-forget safelycreateTokenOnUniversalBridge returns a Promise. If you’re intentionally not awaiting, prefix with void and catch to avoid unhandled rejections.
- createTokenOnUniversalBridge({ + void createTokenOnUniversalBridge({ chainId: params.chainId, client: props.client, tokenAddress: params.contractAddress, - }); + }).catch((err) => + console.warn("createTokenOnUniversalBridge failed", err), + );
90-94
: Name consistency: Erc20Asset_deployContract → ERC20Asset_deployContractNit but helps keep internal helpers consistent with the group name.
- async function Erc20Asset_deployContract(params: { + async function ERC20Asset_deployContract(params: { @@ - deployContract: Erc20Asset_deployContract, + deployContract: ERC20Asset_deployContract,Also applies to: 431-435
90-168
: Consider adding explicit return types to exported helpersFor readability and to match the repo guidelines, annotate returns:
- ERC20Asset_deployContract: Promise<{ contractAddress: string }>
- ERC20Asset_airdropTokens: Promise
- ERC20Asset_approveAirdropTokens: Promise
- DropERC20_*: Promise except deployContract which returns Promise<{ contractAddress: string }>
Also applies to: 170-226, 248-292, 294-424
supply: z.string().min(1, "Supply is required"), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate supply is a positive numeric value to prevent NaN/zero edge cases downstream
Many consumers cast supply with Number(...) and do math, which can yield NaN/Infinity and bypass errors. Enforce numeric > 0 at the schema level.
- supply: z.string().min(1, "Supply is required"),
+ supply: z
+ .string()
+ .min(1, "Supply is required")
+ .refine((v) => {
+ const n = Number(v);
+ return Number.isFinite(n) && n > 0;
+ }, "Supply must be a positive number"),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
supply: z.string().min(1, "Supply is required"), | |
}); | |
supply: z | |
.string() | |
.min(1, "Supply is required") | |
.refine((v) => { | |
const n = Number(v); | |
return Number.isFinite(n) && n > 0; | |
}, "Supply must be a positive number"), | |
}); |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/_common/form.ts
around lines 82-83, the schema currently accepts supply as a non-empty string
which allows consumers to cast to Number(...) and get NaN/0/Infinity later;
change the schema to coerce and validate a positive number: use z.preprocess to
convert incoming strings to Number and then validate with z.number().positive()
(with a clear error message like "Supply must be a number greater than 0") so
downstream math never receives NaN/zero.
reportContractDeployed({ | ||
address: contractAddress, | ||
chainId: Number(params.values.chain), | ||
contractName: "DropERC20", | ||
deploymentType: "asset", | ||
publisher: account.address, | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix analytics: wrong contractName for ERC20Asset deployment
You're reporting ERC20Asset deployments as "DropERC20". This skews analytics and downstream reporting.
reportContractDeployed({
address: contractAddress,
chainId: Number(params.values.chain),
- contractName: "DropERC20",
+ contractName: "ERC20Asset",
deploymentType: "asset",
publisher: account.address,
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
reportContractDeployed({ | |
address: contractAddress, | |
chainId: Number(params.values.chain), | |
contractName: "DropERC20", | |
deploymentType: "asset", | |
publisher: account.address, | |
}); | |
reportContractDeployed({ | |
address: contractAddress, | |
chainId: Number(params.values.chain), | |
contractName: "ERC20Asset", | |
deploymentType: "asset", | |
publisher: account.address, | |
}); |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page-impl.tsx
around lines 155–162, the analytics event is reporting ERC20Asset deployments
with contractName "DropERC20"; change the contractName string to "ERC20Asset" so
the reportContractDeployed call uses contractName: "ERC20Asset" (keep chainId,
deploymentType and publisher unchanged) to correct analytics; run and verify any
affected analytics dashboards/tests after the change.
async function ERC20Asset_airdropTokens(params: { | ||
values: CreateAssetFormValues; | ||
gasless: boolean; | ||
}) { | ||
const { values, gasless } = params; | ||
const account = getAccount(gasless); | ||
const contract = getDeployedContract({ chain: values.chain }); | ||
|
||
const airdropTx = await distributeToken({ | ||
chain: contract.chain, | ||
client: props.client, | ||
contents: values.airdropAddresses.map((recipient) => ({ | ||
amount: BigInt(recipient.quantity), | ||
recipient: recipient.address, | ||
})), | ||
tokenAddress: contract.address, | ||
}); | ||
|
||
await sendAndConfirmTransaction({ | ||
account, | ||
transaction: airdropTx, | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Confirm distributeToken expects base units vs human-readable amounts
Here, amount is built via BigInt(recipient.quantity). If the form captures human-readable quantities (e.g., "1.5"), this will throw or mis-scale. The tokens API may accept human-readable amounts as strings and convert internally (similar to transfer/transferBatch).
Run this to inspect local usage patterns and the function signature:
If distributeToken accepts human-readable amounts, prefer:
- amount: recipient.quantity (string), letting the SDK handle decimals; or
- if base units are required, fetch decimals and use toUnits(amount, decimals).
🏁 Script executed:
#!/bin/bash
# Find distributeToken usage and typical parameter shapes
rg -nP -C3 '\bdistributeToken\s*\('
# Also inspect approve usage to confirm preferred 'amount' vs 'amountWei'
rg -nP -C3 '\bapprove\s*\(\s*\{' | sed -n '1,200p'
Length of output: 18675
🏁 Script executed:
#!/bin/bash
# Display the beginning of distribute-token.ts to inspect types and parameter expectations
sed -n '1,200p' packages/thirdweb/src/tokens/distribute-token.ts
Length of output: 944
🏁 Script executed:
#!/bin/bash
# Locate DistributeContent type definition
rg -nP "export type DistributeContent" -n packages/thirdweb/src/tokens/types.ts
# Print the definition for context
sed -n '1,200p' packages/thirdweb/src/tokens/types.ts
Length of output: 1335
Align amount
with distributeToken’s human-readable inputs
The SDK’s distributeToken implementation calls
toUnits(a.amount.toString(), 18)
internally, so you must pass the human-readable token amount (string or number)—not a base-unit BigInt. Wrapping recipient.quantity
in BigInt
:
- Forbids fractional quantities (
BigInt("1.5")
throws) - If
quantity
were already in base units, it would be scaled again
Recommended fixes in ERC20Asset_airdropTokens
(around lines 178–184):
• If your form’s quantity
is an integer or decimal string, drop the BigInt
and pass it directly:
- contents: values.airdropAddresses.map((recipient) => ({
- amount: BigInt(recipient.quantity),
+ contents: values.airdropAddresses.map((recipient) => ({
+ amount: recipient.quantity, // human-readable number or numeric string
recipient: recipient.address,
})),
• If you need to support decimals but keep a bigint type locally, convert at the call site and cast:
import { toUnits } from "thirdweb/utils/units";
const decimals = 18; // or fetch from the token metadata
contents: values.airdropAddresses.map((r) => ({
amount: BigInt(toUnits(r.quantity.toString(), decimals)),
recipient: r.address,
}));
• Verify your CreateAssetFormValues.airdropAddresses[].quantity
type (string vs. number) and adjust accordingly.
const approvalTx = approve({ | ||
amountWei: toWei(totalAmountToAirdrop.toString()), | ||
contract: contract, | ||
spender: entrypoint.address, | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Approve with amount, not amountWei, to avoid decimals mismatch
Using toWei assumes 18 decimals; ERC20 decimals can vary. The ERC20 approve helper supports human-readable amounts and performs conversion internally.
- const approvalTx = approve({
- amountWei: toWei(totalAmountToAirdrop.toString()),
- contract: contract,
- spender: entrypoint.address,
- });
+ const approvalTx = approve({
+ amount: totalAmountToAirdrop.toString(),
+ contract,
+ spender: entrypoint.address,
+ });
If this is the only toWei usage, also remove the unused import:
- toWei,
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const approvalTx = approve({ | |
amountWei: toWei(totalAmountToAirdrop.toString()), | |
contract: contract, | |
spender: entrypoint.address, | |
}); | |
const approvalTx = approve({ | |
amount: totalAmountToAirdrop.toString(), | |
contract, | |
spender: entrypoint.address, | |
}); |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page-impl.tsx
around lines 216–221, the approve call incorrectly passes an amountWei (using
toWei) which assumes 18 decimals; change the call to pass a human-readable
amount instead (e.g., amount: totalAmountToAirdrop.toString()) so the ERC20
helper can convert using the token's actual decimals, and if toWei is now unused
remove its import to avoid dead code.
<SelectItem value="bonding-curve">Bonding Curve</SelectItem> | ||
<SelectItem value="fixed-price" disabled> | ||
<span className="flex items-center gap-2"> | ||
Dynamic Bonding Curve{" "} | ||
<Badge variant="secondary">Coming Soon</Badge> | ||
</span> | ||
</SelectItem> | ||
<SelectItem value="dutch-auction" disabled> | ||
<span className="flex items-center gap-2"> | ||
Fixed Price <Badge variant="secondary">Coming Soon</Badge> | ||
</span> | ||
</SelectItem> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix mislabeled pricing strategy options
“Dynamic Bonding Curve” is shown under value="fixed-price" and “Fixed Price” under value="dutch-auction". This is confusing for users.
- <SelectItem value="fixed-price" disabled>
+ <SelectItem value="dynamic-bonding-curve" disabled>
<span className="flex items-center gap-2">
- Dynamic Bonding Curve{" "}
+ Dynamic Bonding Curve{" "}
<Badge variant="secondary">Coming Soon</Badge>
</span>
</SelectItem>
- <SelectItem value="dutch-auction" disabled>
+ <SelectItem value="fixed-price" disabled>
<span className="flex items-center gap-2">
- Fixed Price <Badge variant="secondary">Coming Soon</Badge>
+ Fixed Price <Badge variant="secondary">Coming Soon</Badge>
</span>
</SelectItem>
If “dutch-auction” is planned, add it as a separate disabled option with the correct label.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<SelectItem value="bonding-curve">Bonding Curve</SelectItem> | |
<SelectItem value="fixed-price" disabled> | |
<span className="flex items-center gap-2"> | |
Dynamic Bonding Curve{" "} | |
<Badge variant="secondary">Coming Soon</Badge> | |
</span> | |
</SelectItem> | |
<SelectItem value="dutch-auction" disabled> | |
<span className="flex items-center gap-2"> | |
Fixed Price <Badge variant="secondary">Coming Soon</Badge> | |
</span> | |
</SelectItem> | |
<SelectItem value="bonding-curve">Bonding Curve</SelectItem> | |
<SelectItem value="dynamic-bonding-curve" disabled> | |
<span className="flex items-center gap-2"> | |
Dynamic Bonding Curve{" "} | |
<Badge variant="secondary">Coming Soon</Badge> | |
</span> | |
</SelectItem> | |
<SelectItem value="fixed-price" disabled> | |
<span className="flex items-center gap-2"> | |
Fixed Price <Badge variant="secondary">Coming Soon</Badge> | |
</span> | |
</SelectItem> |
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/distribution/token-sale.tsx
around lines 176 to 187, the SelectItem labels are swapped: the option with
value="fixed-price" is labeled "Dynamic Bonding Curve" and value="dutch-auction"
is labeled "Fixed Price". Fix by renaming the labels to match their values
(value="fixed-price" should show "Fixed Price" and value="dutch-auction" should
show "Dutch Auction" or similar), and if dutch-auction is planned but not
available, add a separate disabled SelectItem for it with the correct label and
an appropriate "Coming Soon" Badge so each value matches its displayed label.
onLaunchSuccess: (params: { | ||
chainId: number; | ||
contractAddress: string; | ||
}) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Prop signature change for onLaunchSuccess: verify all call sites are updated
The new signature is (params: { chainId: number; contractAddress: string }) => void. Ensure no downstream usages still expect the zero-arg callback.
Use this script to find and inspect call sites:
🏁 Script executed:
#!/bin/bash
# Find usages of onLaunchSuccess prop across the repo (and check param shape)
rg -nP -C3 '\bonLaunchSuccess\s*=\s*\(' --type ts --type tsx
Length of output: 90
To cover both .ts
and .tsx
files without relying on ripgrep’s file‐type detection, let’s broaden the search:
🏁 Script executed:
#!/bin/bash
# Find all usages of onLaunchSuccess across TS and TSX files
rg -nP -C3 'onLaunchSuccess' -g '*.ts' -g '*.tsx'
Length of output: 13512
Update Storybook stories to match the new onLaunchSuccess signature
All internal usages of onLaunchSuccess
in the token launcher have been updated to accept a { chainId, contractAddress }
parameter, but the Storybook examples still use the old zero-arg callback. Please update them to take (and ignore) the new params:
• apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page.stories.tsx
– Lines 63, 84, 107: change
onLaunchSuccess: () => {}
to
onLaunchSuccess: (_params) => {}
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page.stories.tsx
around lines 63, 84, and 107, the Storybook stories still use the old zero-arg
onLaunchSuccess signature; update each onLaunchSuccess: () => {} to accept the
new parameter shape by changing them to onLaunchSuccess: (_params) => {} so the
stories match the component's new signature and ignore the passed { chainId,
contractAddress } argument.
token0: { | ||
address: string; | ||
amount: bigint; | ||
symbol: string; | ||
}; | ||
token1: { | ||
address: string; | ||
amount: bigint; | ||
symbol: string; | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Token amount formatting assumes 18 decimals — add decimals to avoid misreporting values
Using toTokens(amount, 18) will display incorrect amounts for non-18-decimal tokens (e.g., USDC 6). This is a correctness issue in a financial UI.
Apply these diffs to thread decimals through and format correctly:
export function ClaimRewardsPage(props: {
assetContractClient: ThirdwebContract;
entrypointContractClient: ThirdwebContract;
reward: NonNullable<Awaited<ReturnType<typeof getValidReward>>>;
unclaimedFees: {
token0: {
address: string;
amount: bigint;
symbol: string;
+ decimals: number;
};
token1: {
address: string;
amount: bigint;
symbol: string;
+ decimals: number;
};
};
chainSlug: string;
}) {
export function ClaimRewardsPageUI(props: {
unclaimedFees: {
token0: {
address: string;
amount: bigint;
symbol: string;
+ decimals: number;
};
token1: {
address: string;
amount: bigint;
symbol: string;
+ decimals: number;
};
};
function TokenReward(props: {
token: {
address: string;
amount: bigint;
symbol: string;
+ decimals: number;
};
client: ThirdwebClient;
chain: Chain;
chainSlug: string;
}) {
- <p className="font-bold text-sm">
- {toTokens(props.token.amount, 18)} {props.token.symbol}
- </p>
+ <p className="font-bold text-sm">
+ {toTokens(props.token.amount, props.token.decimals)} {props.token.symbol}
+ </p>
Note: You’ll need to pass decimals from the data source (e.g., alongside address/symbol when constructing unclaimedFees).
Also applies to: 94-106, 272-281, 304-306
PR-Codex overview
This PR focuses on enhancing the
thirdweb
package by introducing new functionalities and updates for managing ERC20 assets, including the implementation of various events, functions, and utility methods for asset deployment and interaction.Detailed summary
initialize
functions to various asset-related contracts.AssetDistributed
,RouterUpdated
, andRewardLockerUpdated
.distributeToken
function to handle multiple recipients.Summary by CodeRabbit
New Features
UI / UX
API / Props
Chores