Skip to content

Commit ca39e62

Browse files
committed
create token by impl
1 parent 9319cec commit ca39e62

File tree

6 files changed

+233
-9
lines changed

6 files changed

+233
-9
lines changed

packages/thirdweb/src/assets/bootstrap.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export async function deployRouter(options: ClientAndChainAndAccount) {
6161
contractId: "Router",
6262
constructorParams: {
6363
_marketSaleImplementation: marketSaleImpl.address,
64-
_feeManager: feeManager,
64+
_feeManager: feeManager.address,
6565
},
6666
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
6767
});
@@ -107,7 +107,7 @@ export async function deployRewardLocker(options: ClientAndChainAndAccount) {
107107
...options,
108108
contractId: "RewardLocker",
109109
constructorParams: {
110-
_feeManager: feeManager,
110+
_feeManager: feeManager.address,
111111
_v3PositionManager: v3PositionManager,
112112
_v4PositionManager: v4PositionManager,
113113
},
@@ -205,7 +205,7 @@ export async function getDeployedRouter(options: ClientAndChain) {
205205
contractId: "Router",
206206
constructorParams: {
207207
_marketSaleImplementation: marketSaleImpl.address,
208-
_feeManager: feeManager,
208+
_feeManager: feeManager.address,
209209
},
210210
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
211211
});
@@ -265,7 +265,7 @@ export async function getDeployedRewardLocker(options: ClientAndChain) {
265265
...options,
266266
contractId: "RewardLocker",
267267
constructorParams: {
268-
_feeManager: feeManager,
268+
_feeManager: feeManager.address,
269269
_v3PositionManager: v3PositionManager,
270270
_v4PositionManager: v4PositionManager,
271271
},

packages/thirdweb/src/assets/constants.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,25 @@ export const DEFAULT_SALT = "thirdweb";
1010
export const IMPLEMENTATIONS: Record<number, Record<string, string>> = {
1111
[84532]: {
1212
AssetEntrypointERC20: "0x79C1236cFe59f1f088A15Da08b0D8667387d9703",
13+
ERC20AssetImpl: "",
1314
V3PositionManager: "",
1415
V4PositionManager: "",
1516
},
1617
};
18+
19+
// biome-ignore lint/nursery/noEnum: FIXME
20+
export enum ImplementationType {
21+
CLONE = 0,
22+
CLONE_WITH_IMMUTABLE_ARGS = 1,
23+
ERC1967 = 2,
24+
ERC1967_WITH_IMMUTABLE_ARGS = 3,
25+
}
26+
27+
// biome-ignore lint/nursery/noEnum: FIXME
28+
export enum CreateHook {
29+
NONE = 0, // do nothing
30+
CREATE_POOL = 1, // create a DEX pool via Router
31+
CREATE_MARKET = 2, // create a market sale via Router
32+
DISTRIBUTE = 3, // distribute tokens to recipients
33+
EXTERNAL_HOOK = 4, // call an external hook contract
34+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { createAssetByImplementationConfig } from "src/extensions/assets/__generated__/AssetEntrypointERC20/write/createAssetByImplementationConfig.js";
2+
import type { Hex } from "viem";
3+
import type { ThirdwebClient } from "../client/client.js";
4+
import { NATIVE_TOKEN_ADDRESS, ZERO_ADDRESS } from "../constants/addresses.js";
5+
import { encodeInitialize } from "../extensions/assets/__generated__/ERC20Asset/write/initialize.js";
6+
import { eth_blockNumber } from "../rpc/actions/eth_blockNumber.js";
7+
import { getRpcClient } from "../rpc/rpc.js";
8+
import { upload } from "../storage/upload.js";
9+
import type { FileOrBufferOrString } from "../storage/upload/types.js";
10+
import { sendTransaction } from "../transaction/actions/send-transaction.js";
11+
import { encodeAbiParameters } from "../utils/abi/encodeAbiParameters.js";
12+
import { keccakId } from "../utils/any-evm/keccak-id.js";
13+
import { toHex } from "../utils/encoding/hex.js";
14+
import type { ClientAndChainAndAccount } from "../utils/types.js";
15+
import {
16+
CreateHook,
17+
DEFAULT_MAX_SUPPLY_ERC20,
18+
DEFAULT_POOL_FEE,
19+
DEFAULT_POOL_INITIAL_TICK,
20+
ImplementationType,
21+
} from "./constants.js";
22+
import { getOrDeployEntrypointERC20 } from "./get-entrypoint-erc20.js";
23+
import { getOrDeployERC20AssetImpl } from "./get-erc20-asset-impl.js";
24+
25+
export type TokenParams = {
26+
name: string;
27+
description?: string;
28+
image?: FileOrBufferOrString;
29+
external_link?: string;
30+
social_urls?: Record<string, string>;
31+
symbol?: string;
32+
contractURI?: string;
33+
maxSupply?: bigint;
34+
owner?: string;
35+
};
36+
37+
export type PoolConfig = {
38+
amount: bigint;
39+
currency?: string;
40+
fee?: number;
41+
initialTick?: number;
42+
};
43+
44+
export type CreateTokenOptions = ClientAndChainAndAccount & {
45+
salt?: string;
46+
params: TokenParams;
47+
poolConfig?: PoolConfig;
48+
};
49+
50+
export async function createTokenByImplConfig(options: CreateTokenOptions) {
51+
const { client, account, params, poolConfig } = options;
52+
53+
const creator = params.owner || account.address;
54+
55+
const encodedInitData = await encodeInitParams({
56+
client,
57+
params,
58+
creator,
59+
});
60+
61+
const rpcRequest = getRpcClient({
62+
...options,
63+
});
64+
const blockNumber = await eth_blockNumber(rpcRequest);
65+
const salt = options.salt
66+
? options.salt.startsWith("0x") && options.salt.length === 66
67+
? (options.salt as `0x${string}`)
68+
: keccakId(options.salt)
69+
: toHex(blockNumber, {
70+
size: 32,
71+
});
72+
73+
const entrypoint = await getOrDeployEntrypointERC20(options);
74+
const tokenImpl = await getOrDeployERC20AssetImpl(options);
75+
76+
const hookData = poolConfig ? encodePoolConfig(poolConfig) : "0x";
77+
78+
const transaction = createAssetByImplementationConfig({
79+
contract: entrypoint,
80+
creator,
81+
config: {
82+
contractId: keccakId("ERC20Asset"),
83+
implementation: tokenImpl.address,
84+
implementationType: ImplementationType.ERC1967,
85+
createHook: poolConfig ? CreateHook.CREATE_POOL : CreateHook.NONE,
86+
createHookData: hookData,
87+
},
88+
params: {
89+
amount: params.maxSupply || DEFAULT_MAX_SUPPLY_ERC20,
90+
referrer: ZERO_ADDRESS,
91+
salt,
92+
data: encodedInitData,
93+
hookData,
94+
},
95+
});
96+
97+
return await sendTransaction({ account, transaction });
98+
}
99+
100+
async function encodeInitParams(options: {
101+
client: ThirdwebClient;
102+
params: TokenParams;
103+
creator: string;
104+
}): Promise<Hex> {
105+
const { client, params, creator } = options;
106+
107+
const contractURI =
108+
options.params.contractURI ||
109+
(await upload({
110+
client,
111+
files: [
112+
{
113+
name: params.name,
114+
description: params.description,
115+
symbol: params.symbol,
116+
image: params.image,
117+
external_link: params.external_link,
118+
social_urls: params.social_urls,
119+
},
120+
],
121+
})) ||
122+
"";
123+
124+
return encodeInitialize({
125+
name: params.name,
126+
symbol: params.symbol || params.name,
127+
contractURI,
128+
maxSupply: params.maxSupply || DEFAULT_MAX_SUPPLY_ERC20,
129+
owner: creator,
130+
});
131+
}
132+
133+
function encodePoolConfig(poolConfig: PoolConfig): Hex {
134+
const POOL_PARAMS = [
135+
{
136+
type: "address",
137+
name: "currency",
138+
},
139+
{
140+
type: "uint256",
141+
name: "amount",
142+
},
143+
{
144+
type: "uint24",
145+
name: "fee",
146+
},
147+
{
148+
type: "uint24",
149+
name: "initialTick",
150+
},
151+
] as const;
152+
153+
return encodeAbiParameters(POOL_PARAMS, [
154+
poolConfig.currency || NATIVE_TOKEN_ADDRESS,
155+
poolConfig.amount,
156+
poolConfig.fee || DEFAULT_POOL_FEE,
157+
poolConfig.initialTick || DEFAULT_POOL_INITIAL_TICK,
158+
]);
159+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, expect, it } from "vitest";
2+
import { ANVIL_CHAIN } from "../../test/src/chains.js";
3+
import { TEST_CLIENT } from "../../test/src/test-clients.js";
4+
import { TEST_ACCOUNT_A } from "../../test/src/test-wallets.js";
5+
import { createTokenByImplConfig } from "./create-token-by-impl-config.js";
6+
7+
describe.runIf(process.env.TW_SECRET_KEY)("create token by impl config", () => {
8+
it("should create token without pool", async () => {
9+
const token = await createTokenByImplConfig({
10+
chain: ANVIL_CHAIN,
11+
client: TEST_CLIENT,
12+
account: TEST_ACCOUNT_A,
13+
params: {
14+
name: "Test",
15+
},
16+
salt: "salt123",
17+
});
18+
19+
expect(token).toBeDefined();
20+
});
21+
});

packages/thirdweb/src/assets/get-entrypoint-erc20.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { type ThirdwebContract, getContract } from "src/contract/contract.js";
2-
import { getOrDeployInfraContract } from "src/contract/deployment/utils/bootstrap.js";
3-
import { encodeInitialize } from "src/extensions/assets/__generated__/AssetEntrypointERC20/write/initialize.js";
1+
import { type ThirdwebContract, getContract } from "../contract/contract.js";
2+
import { getOrDeployInfraContract } from "../contract/deployment/utils/bootstrap.js";
3+
import { encodeInitialize } from "../extensions/assets/__generated__/AssetEntrypointERC20/write/initialize.js";
44
import type { ClientAndChainAndAccount } from "../utils/types.js";
55
import {
66
deployRewardLocker,
@@ -47,6 +47,7 @@ export async function getOrDeployEntrypointERC20(
4747
...options,
4848
contractId: "AssetEntrypointERC20",
4949
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
50+
version: "0.0.2",
5051
});
5152

5253
// encode init data
@@ -56,7 +57,7 @@ export async function getOrDeployEntrypointERC20(
5657
rewardLocker: rewardLocker.address,
5758
});
5859

59-
const routerProxyAddress = await deployInfraProxy({
60+
const entyrpointProxyAddress = await deployInfraProxy({
6061
...options,
6162
initData,
6263
extraData: "0x",
@@ -67,6 +68,6 @@ export async function getOrDeployEntrypointERC20(
6768
return getContract({
6869
client: options.client,
6970
chain: options.chain,
70-
address: routerProxyAddress,
71+
address: entyrpointProxyAddress,
7172
});
7273
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { type ThirdwebContract, getContract } from "../contract/contract.js";
2+
import { getOrDeployInfraContract } from "../contract/deployment/utils/bootstrap.js";
3+
import type { ClientAndChainAndAccount } from "../utils/types.js";
4+
import {} from "./bootstrap.js";
5+
import { IMPLEMENTATIONS } from "./constants.js";
6+
7+
export async function getOrDeployERC20AssetImpl(
8+
options: ClientAndChainAndAccount,
9+
): Promise<ThirdwebContract> {
10+
const implementations = IMPLEMENTATIONS[options.chain.id];
11+
12+
if (implementations?.ERC20AssetImpl) {
13+
return getContract({
14+
client: options.client,
15+
chain: options.chain,
16+
address: implementations.ERC20AssetImpl,
17+
});
18+
}
19+
20+
return await getOrDeployInfraContract({
21+
...options,
22+
contractId: "ERC20Asset",
23+
publisher: "0x6453a486d52e0EB6E79Ec4491038E2522a926936",
24+
});
25+
}

0 commit comments

Comments
 (0)