# Launch token through Portal

This page explains how to launch a token directly through `Portal`. The default method is `newTokenV5`, which supports standard tokens and tax tokens (including FlapTaxTokenV2 features). Legacy methods (`newTokenV2`, `newTokenV3`, `newTokenV4`) remain available for backward compatibility.

## 1) Prepare token metadata

Token metadata is stored on IPFS. You can upload your image and metadata JSON through the Flap upload API.

Example (TypeScript):

```typescript
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"],
        })
    );

    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}`);
    }

    const cid = res.data.data.create;
    return cid;
}
```

{% hint style="warning" %}
You must upload the image to IPFS and pin the metadata using our API: <https://funcs.flap.sh/api/upload>.

Our indexer will not be able to fetch your file if it is not pinned on our gateway. Besides, many terminals fetch files through our gateway and we will warm the CDN on upload to improve loading speed.
{% endhint %}

## 2) Call `newTokenV5`

`newTokenV5` is the recommended default. It supports standard tokens (set `taxRate = 0`) and tax tokens (set `taxRate > 0` and fill the tax fields).

```solidity
/// @notice Create a new token (V5) with tax V2 support
/// @param params The parameters for the new token including advanced tax features
/// @return token The address of the created token
function newTokenV5(NewTokenV5Params calldata params) external payable returns (address token);
```

`NewTokenV5Params` includes all fields from V4 plus advanced tax configuration:

```solidity
struct NewTokenV5Params {
    string name;
    string symbol;
    string meta;
    DexThreshType dexThresh;
    bytes32 salt;
    uint16 taxRate;
    MigratorType migratorType;
    address quoteToken;
    uint256 quoteAmt;
    address beneficiary;
    bytes permitData;
    bytes32 extensionID;
    bytes extensionData;
    DEXId dexId;
    V3LPFeeProfile lpFeeProfile;
    uint64 taxDuration;
    uint64 antiFarmerDuration;
    uint16 mktBps;
    uint16 deflationBps;
    uint16 dividendBps;
    uint16 lpBps;
    uint256 minimumShareBalance;
}
```

Key notes:

* `meta` is the IPFS CID of your metadata JSON.
* `taxRate = 0` creates a standard token. When `taxRate > 0`, tax fields are applied (FlapTaxTokenV2).
* `migratorType` should be `V2_MIGRATOR` for tax tokens.
* `quoteToken = address(0)` uses the native gas token.
* `salt` must produce the required vanity suffix (for example, 8888 for non-tax tokens and 7777 for tax tokens).

## 3) Find the salt (vanity suffix)

`Portal` uses CREATE2 for deterministic deployments. The `salt` must produce a token address with the required suffix:

* Non-tax token: address ends with `8888`.
* Tax token: address ends with `7777`.

To find a valid `salt`, repeatedly hash a random seed and check the predicted address until it matches the suffix. You can predict the address using CREATE2 with the `Portal` address and the token implementation address for the token type you’re launching.

Example (TypeScript):

```typescript
/// Find a vanity salt by predicting the CREATE2 address.
async function findVanityTokenSalt(suffix: string, tokenImpl: Address, portal: Address) {
    if (suffix.length !== 4) {
        throw new Error("Suffix must be exactly 4 characters");
    }

    const predictVanityTokenAddress = (salt: Hex): Address => {
        const bytecode = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73"
            + tokenImpl.slice(2).toLowerCase()
            + "5af43d82803e903d91602b57fd5bf3" as Hex;

        return getContractAddress({
            from: portal,
            salt: toBytes(salt),
            bytecode,
            opcode: "CREATE2",
        });
    };

    const seed = generatePrivateKey();
    let salt = keccak256(toHex(seed));
    let iterations = 0;

    while (!predictVanityTokenAddress(salt).endsWith(suffix)) {
        salt = keccak256(salt);
        iterations++;
    }

    return {
        salt,
        address: predictVanityTokenAddress(salt),
        iterations,
    };
}
```

Use the correct `tokenImpl` and `portal` addresses for your network (see the deployed addresses page in this section).

Token implementation selection:

* If `taxRate = 0` (no tax), use the **Standard Token Impl**.
* If `mktBps == 10000`, use the **Tax Token V1 Impl**.
* If `mktBps < 10000`, use the **Tax Token V2 Impl**.

See [deployed-contract-addresses.md](https://docs.flap.sh/flap/developers/token-launcher-developers/deployed-contract-addresses) for the exact implementation addresses.

## 4) Legacy methods

`newTokenV2`, `newTokenV3`, and `newTokenV4` are still supported for legacy integrations. They follow the same flow but provide fewer features than `newTokenV5`.
