Skip to content

Commit 84e5e24

Browse files
committed
[MNY-11] Dashboard: Add ERC20 token selector for starting price in token creation flow
1 parent 3a289e8 commit 84e5e24

File tree

6 files changed

+103
-51
lines changed

6 files changed

+103
-51
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/_common/form.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { UseFormReturn } from "react-hook-form";
22
import * as z from "zod";
33
import { addressSchema, socialUrlsSchema } from "../../_common/schema";
4-
import { getInitialTickValue, isValidTickValue } from "../utils/calculate-tick";
54

65
export const tokenInfoFormSchema = z.object({
76
chain: z.string().min(1, "Chain is required"),
@@ -37,17 +36,8 @@ export const tokenDistributionFormSchema = z.object({
3736
airdropEnabled: z.boolean(),
3837
// sales ---
3938
erc20Asset_poolMode: z.object({
40-
startingPricePerToken: priceAmountSchema.refine((value) => {
41-
const numValue = Number(value);
42-
if (numValue === 0) {
43-
return false;
44-
}
45-
const tick = getInitialTickValue({
46-
startingPricePerToken: Number(value),
47-
});
48-
49-
return isValidTickValue(tick);
50-
}, "Invalid price"),
39+
startingPricePerToken: priceAmountSchema,
40+
tokenAddress: addressSchema,
5141
saleAllocationPercentage: z.string().refine(
5242
(value) => {
5343
const number = Number(value);

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page-impl.tsx

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { pollWithTimeout } from "@/utils/pollWithTimeout";
3838
import { createTokenOnUniversalBridge } from "../_apis/create-token-on-bridge";
3939
import type { CreateAssetFormValues } from "./_common/form";
4040
import { CreateTokenAssetPageUI } from "./create-token-page.client";
41-
import { getInitialTickValue } from "./utils/calculate-tick";
41+
import { getInitialTickValue, isValidTickValue } from "./utils/calculate-tick";
4242

4343
export function CreateTokenAssetPage(props: {
4444
accountAddress: string;
@@ -113,6 +113,21 @@ export function CreateTokenAssetPage(props: {
113113

114114
const chain = getChain(Number(params.values.chain));
115115

116+
const initialTick = await getInitialTickValue({
117+
startingPricePerToken: Number(
118+
params.values.erc20Asset_poolMode.startingPricePerToken,
119+
),
120+
tokenAddress: params.values.erc20Asset_poolMode.tokenAddress,
121+
chain,
122+
client: props.client,
123+
});
124+
125+
if (!isValidTickValue(initialTick)) {
126+
throw new Error(
127+
"Invalid starting price per token. Change price and try again",
128+
);
129+
}
130+
116131
const contractAddress = await createToken({
117132
account,
118133
chain: chain,
@@ -123,12 +138,13 @@ export function CreateTokenAssetPage(props: {
123138
kind: "pool",
124139
config: {
125140
amount: BigInt(saleAmount),
126-
initialTick: getInitialTickValue({
127-
startingPricePerToken: Number(
128-
params.values.erc20Asset_poolMode.startingPricePerToken,
129-
),
130-
}),
141+
initialTick: initialTick,
131142
developerRewardBps: 1250, // 12.5%
143+
currency:
144+
getAddress(params.values.erc20Asset_poolMode.tokenAddress) ===
145+
getAddress(NATIVE_TOKEN_ADDRESS)
146+
? undefined
147+
: params.values.erc20Asset_poolMode.tokenAddress,
132148
},
133149
}
134150
: undefined,
@@ -442,14 +458,16 @@ export function CreateTokenAssetPage(props: {
442458
setClaimConditions: DropERC20_setClaimConditions,
443459
},
444460
}}
445-
onLaunchSuccess={(params) => {
446-
createTokenOnUniversalBridge({
447-
chainId: params.chainId,
448-
client: props.client,
449-
tokenAddress: params.contractAddress,
450-
// TODO: UPDATE THIS WHEN WE ALLOW CUSTOM CURRENCY PAIRING
451-
pairedTokenAddress: NATIVE_TOKEN_ADDRESS,
452-
});
461+
onLaunchSuccess={(values, contractAddress) => {
462+
if (values.saleMode === "erc20-asset:pool") {
463+
createTokenOnUniversalBridge({
464+
chainId: Number(values.chain),
465+
client: props.client,
466+
tokenAddress: contractAddress,
467+
pairedTokenAddress: values.erc20Asset_poolMode.tokenAddress,
468+
});
469+
}
470+
453471
revalidatePathAction(
454472
`/team/${props.teamSlug}/project/${props.projectId}/tokens`,
455473
"page",

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/create-token-page.client.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ export function CreateTokenAssetPageUI(props: {
5959
accountAddress: string;
6060
client: ThirdwebClient;
6161
createTokenFunctions: CreateTokenFunctions;
62-
onLaunchSuccess: (params: {
63-
chainId: number;
64-
contractAddress: string;
65-
}) => void;
62+
onLaunchSuccess: (
63+
formValues: CreateAssetFormValues,
64+
contractAddress: string,
65+
) => void;
6666
teamSlug: string;
6767
projectSlug: string;
6868
teamPlan: Team["billingPlan"];
@@ -138,6 +138,7 @@ export function CreateTokenAssetPageUI(props: {
138138
erc20Asset_poolMode: {
139139
startingPricePerToken: "0.000000001", // 1gwei per token
140140
saleAllocationPercentage: "100",
141+
tokenAddress: nativeTokenAddress,
141142
},
142143
dropERC20Mode: {
143144
pricePerToken: "0.1",
@@ -166,11 +167,15 @@ export function CreateTokenAssetPageUI(props: {
166167
client={props.client}
167168
form={tokenInfoForm}
168169
onChainUpdated={() => {
169-
// reset the token address to the native token address on chain change
170170
tokenDistributionForm.setValue(
171171
"dropERC20Mode.saleTokenAddress",
172172
nativeTokenAddress,
173173
);
174+
175+
tokenDistributionForm.setValue(
176+
"erc20Asset_poolMode.tokenAddress",
177+
nativeTokenAddress,
178+
);
174179
}}
175180
onNext={() => {
176181
reportAssetCreationStepConfigured({

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/distribution/token-sale.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DollarSignIcon, XIcon } from "lucide-react";
44
import type { ThirdwebClient } from "thirdweb";
55
import { DistributionBarChart } from "@/components/blocks/distribution-chart";
66
import { FormFieldSetup } from "@/components/blocks/FormFieldSetup";
7+
import { TokenSelector } from "@/components/blocks/TokenSelector";
78
import { Badge } from "@/components/ui/badge";
89
import { DynamicHeight } from "@/components/ui/DynamicHeight";
910
import { DecimalInput } from "@/components/ui/decimal-input";
@@ -147,9 +148,6 @@ function PoolConfig(props: {
147148
chainId: string;
148149
client: ThirdwebClient;
149150
}) {
150-
const { idToChain } = useAllChainsData();
151-
const chainMeta = idToChain.get(Number(props.chainId));
152-
153151
const totalSupply = Number(props.form.watch("supply"));
154152
const sellSupply = Math.floor(
155153
(totalSupply *
@@ -189,7 +187,7 @@ function PoolConfig(props: {
189187
</Select>
190188
</FormFieldSetup>
191189

192-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:max-w-[536px]">
190+
<div className="flex flex-col lg:flex-row gap-4">
193191
{/* supply % */}
194192
<FormFieldSetup
195193
errorMessage={
@@ -199,6 +197,7 @@ function PoolConfig(props: {
199197
helperText={`${compactNumberFormatter.format(sellSupply)} tokens`}
200198
isRequired
201199
label="Sell % of Total Supply"
200+
className="lg:max-w-[220px] grow"
202201
>
203202
<div className="relative">
204203
<DecimalInput
@@ -230,10 +229,11 @@ function PoolConfig(props: {
230229
}
231230
isRequired
232231
label="Starting price per token"
232+
className="grow lg:max-w-sm"
233233
>
234-
<div className="relative">
234+
<div className="relative grid grid-cols-2">
235235
<DecimalInput
236-
className="pr-10"
236+
className="pr-10 rounded-r-none"
237237
onChange={(value) => {
238238
props.form.setValue(
239239
"erc20Asset_poolMode.startingPricePerToken",
@@ -247,9 +247,26 @@ function PoolConfig(props: {
247247
"erc20Asset_poolMode.startingPricePerToken",
248248
)}
249249
/>
250-
<span className="-translate-y-1/2 absolute top-1/2 right-3 text-sm text-muted-foreground">
251-
{chainMeta?.nativeCurrency.symbol || "ETH"}
252-
</span>
250+
251+
<TokenSelector
252+
addNativeTokenIfMissing={true}
253+
chainId={Number(props.chainId)}
254+
className="bg-background border-l-0 rounded-l-none"
255+
client={props.client}
256+
disableAddress={true}
257+
popoverContentClassName="!w-[320px]"
258+
onChange={(value) => {
259+
props.form.setValue(
260+
"erc20Asset_poolMode.tokenAddress",
261+
value.address,
262+
);
263+
}}
264+
selectedToken={{
265+
address: props.form.watch("erc20Asset_poolMode.tokenAddress"),
266+
chainId: Number(props.chainId),
267+
}}
268+
showCheck={true}
269+
/>
253270
</div>
254271
</FormFieldSetup>
255272
</div>

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/launch/launch-token.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ export function LaunchTokenStatus(props: {
5656
values: CreateAssetFormValues;
5757
onPrevious: () => void;
5858
client: ThirdwebClient;
59-
onLaunchSuccess: (params: {
60-
chainId: number;
61-
contractAddress: string;
62-
}) => void;
59+
onLaunchSuccess: (
60+
formValues: CreateAssetFormValues,
61+
contractAddress: string,
62+
) => void;
6363
teamSlug: string;
6464
projectSlug: string;
6565
teamPlan: Team["billingPlan"];
@@ -251,10 +251,7 @@ export function LaunchTokenStatus(props: {
251251
});
252252

253253
if (contractAddressRef.current) {
254-
props.onLaunchSuccess({
255-
chainId: Number(formValues.chain),
256-
contractAddress: contractAddressRef.current,
257-
});
254+
props.onLaunchSuccess(formValues, contractAddressRef.current);
258255
}
259256
}
260257

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/tokens/create/token/utils/calculate-tick.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
1+
import type { Chain, ThirdwebClient } from "thirdweb";
2+
import { getContract, NATIVE_TOKEN_ADDRESS } from "thirdweb";
3+
import { decimals } from "thirdweb/extensions/erc20";
4+
import { getAddress } from "thirdweb/utils";
5+
16
const MIN_TICK = -887200;
27
const MAX_TICK = 887200;
38
const TICK_SPACING = 200;
49

5-
export function getInitialTickValue(params: { startingPricePerToken: number }) {
6-
const calculatedTick =
7-
Math.log(params.startingPricePerToken) / Math.log(1.0001);
10+
export async function getInitialTickValue(params: {
11+
startingPricePerToken: number;
12+
tokenAddress: string;
13+
chain: Chain;
14+
client: ThirdwebClient;
15+
}) {
16+
const isNativeToken =
17+
getAddress(params.tokenAddress) === getAddress(NATIVE_TOKEN_ADDRESS);
18+
19+
const pairTokenDecimals = isNativeToken
20+
? 18
21+
: await decimals({
22+
contract: getContract({
23+
address: params.tokenAddress,
24+
chain: params.chain,
25+
client: params.client,
26+
}),
27+
});
28+
29+
const decimalAdjustedPrice =
30+
params.startingPricePerToken * 10 ** (pairTokenDecimals - 18);
31+
32+
const calculatedTick = Math.log(decimalAdjustedPrice) / Math.log(1.0001);
833

934
// Round to nearest tick spacing
1035
const tick = Math.round(calculatedTick / TICK_SPACING) * TICK_SPACING;

0 commit comments

Comments
 (0)