Launch A Token
newTokenV2
To Launch a new token, you send a transaction to call the newTokenV2
method:
// solidity interface
/// @notice Create a new token (V2) with flexible parameters
/// @param params The parameters for the new token
/// @return token The address of the created token
function newTokenV2(NewTokenV2Params calldata params) external payable returns (address token);
/// @notice Parameters for creating a new token (V2)
struct NewTokenV2Params {
/// The name of the token
string name;
/// The symbol of the token
string symbol;
/// The ipfs cid of the metadata
string meta;
/// The DEX supply threshold type
DexThreshType dexThresh;
/// The salt for deterministic deployment
bytes32 salt;
/// The tax rate in basis points (if non-zero, this is a tax token)
uint16 taxRate;
/// The migrator type (see MigratorType enum)
MigratorType migratorType;
/// The quote token address (native gas token if zero address)
address quoteToken;
/// The initial quote token amount to spend for buying
uint256 quoteAmt;
/// The beneficiary address for the token
/// For rev share tokens, this is the address that can claim the LP fees
/// For tax tokens, this is the address that receives the tax fees
address beneficiary;
/// The optional permit data for the quote token
bytes permitData;
}
/// @notice the migrator type
/// @dev the migrator type determines how the liquidity is added to the DEX.
/// Note: To mitigate the risk of DOS, if a V3 migrator is used but the liquidity cannot
/// be added to v3 pools, the migrator will fallback to a V2 migrator.
enum MigratorType {
V3_MIGRATOR, // Migrate the liquidity to a Uniswap V3 like pool
V2_MIGRATOR // Migrate the liquidity to a Uniswap V2 like pool
}
/// @dev dex threshold types
enum DexThreshType {
TWO_THIRDS, // 66.67% supply
FOUR_FIFTHS, // 80% supply
HALF, // 50% supply
_95_PERCENT, // 95% supply
_81_PERCENT, // 81% supply
_1_PERCENT // 1% supply => mainly for testing
}
Before sending the transaction, we should prepare the metadata:
All the metadata are stored on IPFS. You can upload your image and the meta of your token to ipfs through our API.
Check the following example to learn how to upload the image along with the meta data json to IPFS:
/// Upload the meta of the token
async function uploadTokenMeta(cfg: {
buy: string | null;
creator: string;
description: string;
sell: string | null;
telegram: string | null;
twitter: string | null;
website: string | null;
image_path: string;
}) {
const form = new FormData();
const MUTATION_CREATE = `
mutation Create($file: Upload!, $meta: MetadataInput!) {
create(file: $file, meta: $meta)
}
`;
form.append(
"operations",
JSON.stringify({
query: MUTATION_CREATE,
variables: {
file: null, meta: {
website: cfg.website,
twitter: cfg.twitter,
telegram: cfg.telegram,
description: cfg.description,
creator: "0x0000000000000000000000000000000000000000",
}
},
})
);
form.append(
"map",
JSON.stringify({
"0": ["variables.file"],
})
);
// read local file and convert to File object, with type "image/png"
const file = new File([fs.readFileSync(cfg.image_path)], "image.png", {
type: "image/png",
});
form.append("0", file);
const res = await axios.postForm(FlapConfig.api, form, {
headers: {
"Content-Type": "multipart/form-data",
},
});
if (res.status !== 200) {
throw new Error(`failed to upload the token meta: ${res.statusText}`);
}
console.log("token meta uploading response: ", res.data);
// the cid is in the response data
const cid = res.data.data.create;
return cid;
}
After successfully uploading the image and metadata to IPFS, you will get an IPFS cid which is similar to bafkreicwlkpvrcqg4bbhyp2fnhdwbqos5ghka6gdjra3tdkgxxs74hqsze
(e.g. this is the cid of the BROCCOLI token’s metadata
After that, you can get the meta json from any ipfs gateway, eg:
Note the meta json has the following format:
{
"buy": null,
"creator": "0x9c7FC419212461342201495ffCdE6366DC32CE1c",
"description": "CZ wanted a name for his dog that started with B and had some green in it, so he named him Broccoli. It also had a blocky sound, as in blockiichain. Could not get away from crypto, haha.",
"image": "bafkreiccy2x5735r2q3zvcce3ub3hcgpslnn5n3dqa7fn3tgr7qgtlbjhi",
"sell": null,
"telegram": null,
"twitter": null,
"website": null
}
The image
field itself is another ipfs cid for the image. Then you can get the image here: https://ipfs.io/ipfs/bafkreiccy2x5735r2q3zvcce3ub3hcgpslnn5n3dqa7fn3tgr7qgtlbjhi
Now we have the ipfs cid of the meta json, let's proceed to construct the transaction call.
The new newTokenV2
method takes a single struct (or tuple in solidity’s jargon) as its input. Let’s explain some fields of the NewTokenV2Params
struct:
meta
: this is the ipfs cid you get from last section , eg,bafkreicwlkpvrcqg4bbhyp2fnhdwbqos5ghka6gdjra3tdkgxxs74hqsze
dexThresh
: This determines the threshold to migrate token to DEX. Our UI usesFOUR_FIFTHS
by default, which means when 80% or more of the total supply has been sold from the bonding curve , the token will be migrated to DEX.taxRate
: since v3.3.0, we support creating a tax token. The tax is only applied for the first 30days after the token being migrated to DEX. The maximum tax rate is10%
migratorType
: The migrator for the token. Note, if a token has tax, it can only be migrated to a V2 pool, which means only aV2_MIGRATOR
is allowed for a tax token. For other tokens, it is recommended to use aV3_MIGRATOR
. With aV3_MIGRATOR
we will optimize your liquidity and enable the revenue share feature for you token.quoteToken
: The quote token you want to use. When your quote token is the native gas token (i.e ETH or BNB), leave quoteToken as the zero address. Note: only enabled quoteToken can be used.quoteAmt
: The amount of quoteToken you want to spend to buy token on creation.beneficiary
: If the token has not TAX and using aV3_MIGRATOR
, the rev share fees can be claimed with this address; If the token has TAX, this address will receive the tax fee.permitData
: If your quoteToken is not zero address (i.e, not native gas token ETH or BNB, but some ERC20 token). You can construct thepermitData
to avoid another approve TX. Check our example below to see how to construct thepermitData
.salt
: The salt is mandate. All tokens created from thenewTokenV2
method have a vanity ending. If the token has no tax, it has an ending of 8888 or it has an ending of 7777.
Here is an example script for generating the salt
:
The suffix is
8888
if your token has not tax, otherwise it is7777
The token impl address is :
0x8B4329947e34B6d56D71A3385caC122BaDe7d78D
for non tax token0x5dd913731C12aD8DF3E574859FDe45412bF4aaD9
for tax token
Portal address on BSC is
0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0
/// get vanity token address and salt
async function findVanityTokenSalt(suffix: string, token_impl: Address, portal: Address) {
if (suffix.length !== 4) {
throw new Error("Suffix must be exactly 4 characters");
}
// predict the vanity token address based on the saltå
const predictVanityTokenAddress = (salt: Hex): Address => {
const bytecode = '0x3d602d80600a3d3981f3363d3d373d3d3d363d73'
+ token_impl.slice(2).toLowerCase() // remove 0x prefix
+ '5af43d82803e903d91602b57fd5bf3' as Hex;
return getContractAddress({
from: portal,
salt: toBytes(salt),
bytecode,
opcode: "CREATE2",
});
}
// Note: you don't have to use a private key as the starting seed, you can use
// any pseudo-random string as the starting seed.
//
// Here, we use a random private key as the starting seed
// Then repeatedly hash the seed until we get the vanity address
const seed = generatePrivateKey();
let salt = keccak256(toHex(seed));
let iterations = 0; // Track the number of iterations
while (!predictVanityTokenAddress(salt).endsWith(suffix)) {
salt = keccak256(salt);
iterations++; // Increment the iteration count
}
console.log(`Iterations: ${iterations}`); // Print the number of iterations
return {
salt,
address: predictVanityTokenAddress(salt),
}
}
In the above example script, we using a random private key as the seed. In reality, you can use any random number or string as your seed to find the salt.
Events
For backward compatibility, each newly launched token will emit one or more events rather than a single event:
TokenCreated
: Every token launch will emit aTokenCreated
event.TokenCurveSet
: (optional), if a token launch does not emit this event, its curve is the first one in theCurveType
enum , which is 16 ( i.ecurveParameter
= 16 ether )TokenDexSupplyThreshSet
: (optional), if a token does not emit this event, its dex threshold is by default the first one in theDexThreshType
enum, which is 6.67e8 ether.TokenQuoteSet
: (optional), if a token does not emit this event, its quote token is the native gas token (i.e, zero address). Otherwise the quote token 's address is thequoteToken
arg in the event (Currently, we support USD1 and lisUSD on BSC)TokenMigratorSet
: (optional), if a token does not emit this event, its Migrator type is by defaultV3_MIGRATOR
(i.e, the first one in theMigratorType
enum).FlapTokenTaxSet
: (optional), if a token does not emit this event, the tax is 0 or it is not a tax token.
/// @notice emitted when a new token is created
///
/// @param ts The timestamp of the event
/// @param creator The address of the creator
/// @param nonce The nonce of the token
/// @param token The address of the token
/// @param name The name of the token
/// @param symbol The symbol of the token
/// @param meta The meta URI of the token
event TokenCreated(
uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta
);
/// emitted when a token's curve is set
/// @param token The address of the token
/// @param curve The address of the curve
/// @param curveParameter The parameter of the curve
event TokenCurveSet(address token, address curve, uint256 curveParameter);
/// emitted when a token's dexSupplyThresh is set
/// @param token The address of the token
/// @param dexSupplyThresh The new dexSupplyThresh of the token
event TokenDexSupplyThreshSet(address token, uint256 dexSupplyThresh);
/// emitted when a token's implementation is set
/// @param token The address of the token
/// @param version The version of the token
event TokenVersionSet(address token, TokenVersion version);
/// @notice emitted when a token's quote token is set
/// @param token The address of the token
/// @param quoteToken The address of the quote token
event TokenQuoteSet(address token, address quoteToken);
/// @notice emitted when a token's migrator is set
/// @param token The address of the token
/// @param migratorType The migrator type
event TokenMigratorSet(address token, MigratorType migratorType);
/// @notice emitted when a new tax is set for a token
/// @param token The address of the token
/// @param tax The tax value set for the token
event FlapTokenTaxSet(address token, uint256 tax);
/// @dev curve Types
enum CurveType {
CURVE_LEGACY_15, // r = 15
CURVE_4, // r = 4
CURVE_0_974, // r = 0.974
CURVE_0_5, // r = 0.5
CURVE_1000, // r = 1000
CURVE_20000, // r = 20000
CURVE_2500, // r = 2500
CURVE_3, // r = 3
CURVE_2, // r = 2
CURVE_6, // r = 6
CURVE_75, // r = 75
CURVE_4M // r= 4 M
}
/// @dev Token version
/// Which token implementation is used
enum TokenVersion {
TOKEN_LEGACY_MINT_NO_PERMIT,
TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE, // for historical reasons, both 0 and 1 are the same: TOKEN_LEGACY_MINT_NO_PERMIT
TOKEN_V2_PERMIT, // 2
TOKEN_GOPLUS, // 3
TOKEN_TAXED // 4: The token is tax enabled
}
/// @dev dex threshold types
enum DexThreshType {
TWO_THIRDS, // 66.67% supply
FOUR_FIFTHS, // 80% supply
HALF, // 50% supply
_95_PERCENT, // 95% supply
_81_PERCENT, // 81% supply
_1_PERCENT // 1% supply => mainly for testing
}
/// @notice the migrator type
/// @dev the migrator type determines how the liquidity is added to the DEX.
/// Note: To mitigate the risk of DOS, if a V3 migrator is used but the liquidity cannot
/// be added to v3 pools, the migrator will fallback to a V2 migrator.
/// A TAX token must use a V2 migrator.
enum MigratorType {
V3_MIGRATOR, // Migrate the liquidity to a Uniswap V3 like pool
V2_MIGRATOR // Migrate the liquidity to a Uniswap V2 like pool
}
Legacy Methods (Deprecated)
These methods will be removed in next release, please do not use them any more.
To launch a new token, you call the newToken
function of our portal
contract.
/// @notice Create a new meme token
/// @param name The name of the token
/// @param symbol The symbol of the token
/// @param meta The metadata ipfs cid of the token
/// @dev if msg.value is not zero, the caller would be the initial buyer of the token
function newToken(string calldata name, string calldata symbol, string calldata meta)
external
payable
returns (address token);
To launch a token with the "revenue share" enabled, you call the newVanityToken
function of our portal contract:
/// @notice Create a new vanity token
/// @param name The name of the token
/// @param symbol The symbol of the token
/// @param meta The metadata URI of the token
/// @param salt The salt for deterministic deployment
/// @param beneficiary The address of the beneficiary
/// @return token The address of the created token
function newVanityToken(
string calldata name,
string calldata symbol,
string calldata meta,
bytes32 salt,
address beneficiary
) external payable returns (address token);
The vanity token must end with "8888" , you must find a possible salt to satisfy this condition. Here is an example typescript to find such a salt:
import { Address, getContractAddress, Hex, keccak256,toBytes, toHex } from 'viem';
import { generatePrivateKey } from 'viem/accounts';
// BSC mainent token implementation address
const TOKEN_IMPLEMENTATION_ADDDRESS = "0x8b4329947e34b6d56d71a3385cac122bade7d78d";
// BSC mainnet portal address
const PORTAL_ADDRESS = "0xe2cE6ab80874Fa9Fa2aAE65D277Dd6B8e65C9De0" as Address;
/// get vanity token address and salt
export async function findSalt(){
// predict the vanity token address based on the salt
const predictVanityTokenAddress = (salt: Hex):Address => {
const bytecode = '0x3d602d80600a3d3981f3363d3d373d3d3d363d73'
+ TOKEN_IMPLEMENTATION_ADDDRESS.slice(2).toLowerCase() // remove 0x prefix
+ '5af43d82803e903d91602b57fd5bf3' as Hex;
return getContractAddress({
from: PORTAL_ADDRESS,
salt: toBytes(salt),
bytecode,
opcode: "CREATE2",
});
}
// the starting seed is a random string (you can use anyting, e.g: UUID, timestamp , etc)
// Here, we use a random private key as the starting seed.
// Then repetively hash the seed until we find a salt that ends with 8888
const seed = generatePrivateKey();
let salt = keccak256(toHex(seed));
while (!predictVanityTokenAddress(salt).endsWith("8888")) {
salt = keccak256(salt);
}
return salt;
}
Events
Whenever a new token is created, a TokenCreated
event would emitted from our Portal
contract:
/// @notice emitted when a new token is created
///
/// @param ts The timestamp of the event
/// @param creator The address of the creator
/// @param nonce The nonce of the token
/// @param token The address of the token
/// @param name The name of the token
/// @param symbol The symbol of the token
/// @param meta The meta URI of the token
event TokenCreated(
uint256 ts, address creator, uint256 nonce, address token, string name, string symbol, string meta
);
Last updated