Skip to main content
In this guide, you’ll learn how to incorporate a company for your token using the Bags TypeScript SDK. The incorporation flow involves creating a payment, submitting incorporation details with founder information, having each founder complete their verification forms, and then starting the incorporation process once all prerequisites are met.

Prerequisites

Before starting, make sure you have:
  • Completed our TypeScript and Node.js Setup Guide.
  • Got your API key from the Bags Developer Portal.
  • A Solana wallet with SOL or USDC to pay the incorporation fee.
  • A launched token mint address.
  • Installed the additional dependencies for this guide:
    npm install @solana/web3.js bs58
    

1. Set Up Environment Variables

This guide requires your wallet’s private key. Add it to your base .env file:
# .env
BAGS_API_KEY=your_api_key_here
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
PRIVATE_KEY=your_base58_encoded_private_key_here  # Required for this guide
You can export your private key from wallets like Bags, Phantom, or Backpack.

2. The Incorporation Script

Here is the complete script for incorporating a token. Save it as incorporate-token.ts. The incorporation flow is split into three separate functions that you call at different stages:
  1. createPaymentAndSubmitDetails β€” creates the payment, signs it on-chain, and submits incorporation details. Returns each founder’s form URL.
  2. checkIncorporationStatus β€” checks whether all founders have completed their forms and the project is ready for incorporation.
  3. startIncorporationProcess β€” triggers the incorporation once all founders have completed their forms.
After submitting details, share each founder’s formUrl with them so they can complete their verification. Founders may take hours or days to finish, so these functions are designed to be called independently.
import dotenv from "dotenv";
dotenv.config({ quiet: true });

import { BagsSDK, signAndSendTransaction } from "@bagsfm/bags-sdk";
import { Keypair, Connection, PublicKey } from "@solana/web3.js";
import bs58 from "bs58";

const BAGS_API_KEY = process.env.BAGS_API_KEY;
const SOLANA_RPC_URL = process.env.SOLANA_RPC_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;

if (!BAGS_API_KEY || !SOLANA_RPC_URL || !PRIVATE_KEY) {
    throw new Error("BAGS_API_KEY, SOLANA_RPC_URL, and PRIVATE_KEY are required");
}

const connection = new Connection(SOLANA_RPC_URL);
const sdk = new BagsSDK(BAGS_API_KEY, connection, "processed");

async function createPaymentAndSubmitDetails(params: {
    tokenAddress: string;
    projectName: string;
    preferredCompanyNames: [string, string, string];
    incorporationShareBasisPoint: number;
    founders: Array<{
        firstName: string;
        lastName: string;
        email: string;
        nationalityCountry: string;
        taxResidencyCountry: string;
        residentialAddress: string;
        shareBasisPoint: number;
    }>;
    category?: "RWA" | "AI" | "DEFI" | "INFRA" | "DEPIN" | "LEGAL" | "GAMING" | "NFT" | "MEME";
    twitterHandle?: string;
    payWithSol?: boolean;
}) {
    try {
        const keypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY!));
        const commitment = sdk.state.getCommitment();
        const tokenMint = new PublicKey(params.tokenAddress);

        console.log(`🏒 Starting incorporation for token ${params.tokenAddress}`);
        console.log(`πŸ‘€ Payer wallet: ${keypair.publicKey.toBase58()}`);

        // 1a. Create payment
        console.log("\nπŸ“ Creating incorporation payment...");
        const payment = await sdk.incorporation.startPayment({
            payerWallet: keypair.publicKey,
            payWithSol: params.payWithSol,
        });

        console.log(`πŸ’° Incorporation price: ${payment.priceUSDC} USDC`);
        console.log(`πŸ“‹ Order UUID: ${payment.orderUUID}`);

        // 1b. Sign and send the payment transaction
        console.log("\n✍️  Signing and sending payment transaction...");
        const paymentSignature = await signAndSendTransaction(
            connection,
            commitment,
            payment.transaction,
            keypair
        );

        console.log(`βœ… Payment confirmed: ${paymentSignature}`);

        // 1c. Submit incorporation details
        console.log("\nπŸ“‹ Submitting incorporation details...");
        const incorporation = await sdk.incorporation.incorporate({
            orderUUID: payment.orderUUID,
            paymentSignature,
            projectName: params.projectName,
            tokenAddress: tokenMint,
            founders: params.founders,
            incorporationShareBasisPoint: params.incorporationShareBasisPoint,
            preferredCompanyNames: params.preferredCompanyNames,
            category: params.category,
            twitterHandle: params.twitterHandle,
        });

        console.log(`βœ… Incorporation details submitted!`);
        console.log(`πŸ“Š Status: ${incorporation.incorporationStatus}`);

        // 1d. Display founder form URLs
        console.log("\nπŸ”— Founder form URLs β€” share these with each founder:");
        console.log("   Each founder must open their URL and complete the required forms.\n");

        for (const founder of incorporation.founders) {
            console.log(`  πŸ‘€ ${founder.firstName} ${founder.lastName}`);
            if (founder.formUrl) {
                console.log(`     Form URL: ${founder.formUrl}`);
            }
            console.log();
        }

        console.log("⏳ Once all founders have completed their forms, run checkIncorporationStatus() to verify readiness.");
    } catch (error) {
        console.error("🚨 Incorporation setup failed:", error);
        throw error;
    }
}

async function checkIncorporationStatus(tokenAddress: string) {
    try {
        const tokenMint = new PublicKey(tokenAddress);
        const details = await sdk.incorporation.getDetails({ tokenAddress: tokenMint });

        console.log(`\nπŸ“Š Incorporation status for ${tokenAddress}`);
        console.log(`   Status: ${details.incorporationStatus}`);
        console.log(`   Ready for incorporation: ${details.isReadyForIncorporation ? "βœ… Yes" : "❌ No"}`);

        if (!details.isReadyForIncorporation) {
            console.log("\n   Not all founders have completed their forms yet.");
            console.log("   You can also check individual founder progress with sdk.incorporation.list():");

            const projects = await sdk.incorporation.list();
            const project = projects.find((p) => p.tokenAddress === tokenAddress);

            if (project) {
                for (const founder of project.founders) {
                    const kycDone = founder.kycStatus === "VERIFIED";
                    console.log(`\n   πŸ‘€ ${founder.firstName} ${founder.lastName}`);
                    console.log(`      KYC: ${kycDone ? "βœ…" : "❌"} ${founder.kycStatus}`);
                    console.log(`      PEP form: ${founder.pepCompleted ? "βœ…" : "❌"}`);
                    console.log(`      IP attribution: ${founder.ipAttributionAcknowledged ? "βœ…" : "❌"}`);
                }
            }
        }

        return details;
    } catch (error) {
        console.error("🚨 Failed to check status:", error);
        throw error;
    }
}

async function startIncorporationProcess(tokenAddress: string) {
    try {
        const tokenMint = new PublicKey(tokenAddress);

        console.log(`\nπŸš€ Starting incorporation process for ${tokenAddress}...`);
        const result = await sdk.incorporation.startIncorporation({ tokenAddress: tokenMint });

        console.log(`πŸŽ‰ Incorporation started successfully!`);
        console.log(`πŸͺ™ Token: ${result.tokenAddress}`);
        console.log(`βœ… Incorporation started: ${result.incorporationStarted}`);

        return result;
    } catch (error) {
        console.error("🚨 Failed to start incorporation:", error);
        throw error;
    }
}

// ──────────────────────────────────────────────
// Usage β€” run one function at a time
// ──────────────────────────────────────────────

// 1. First, create the payment and submit incorporation details:
createPaymentAndSubmitDetails({
    tokenAddress: "YOUR_TOKEN_MINT_ADDRESS",
    projectName: "My Web3 Project",
    preferredCompanyNames: ["My Project Inc", "My Project Labs", "My Project Foundation"],
    incorporationShareBasisPoint: 3000, // 30% incorporation share
    founders: [
        {
            firstName: "Alice",
            lastName: "Smith",
            email: "[email protected]",
            nationalityCountry: "USA",
            taxResidencyCountry: "USA",
            residentialAddress: "123 Main St, New York, NY 10001",
            shareBasisPoint: 4000, // 40%
        },
        {
            firstName: "Bob",
            lastName: "Johnson",
            email: "[email protected]",
            nationalityCountry: "GBR",
            taxResidencyCountry: "GBR",
            residentialAddress: "456 High St, London, UK",
            shareBasisPoint: 3000, // 30%
        },
        // Total: 4000 + 3000 + 3000 (incorporation) = 10000 (100%)
    ],
    category: "DEFI",
    twitterHandle: "myproject",
    payWithSol: false,
});

// 2. After founders have completed their forms, check readiness:
// checkIncorporationStatus("YOUR_TOKEN_MINT_ADDRESS");

// 3. Once isReadyForIncorporation is true, start the process:
// startIncorporationProcess("YOUR_TOKEN_MINT_ADDRESS");

3. Understanding Share Allocation

The incorporation share and all founder shares are expressed in basis points (1 bps = 0.01%). The total must always equal exactly 10,000 bps (100%).
ParameterRangeDescription
incorporationShareBasisPoint2000–3000The incorporation entity’s share (20–30%)
Each founder’s shareBasisPoint0–10000Each founder’s ownership share
The rule: Sum of all founder shareBasisPoint values + incorporationShareBasisPoint = 10,000. Here are some valid allocation examples:
FoundersFounder SharesIncorporation ShareTotal
1 founder7000 (70%)3000 (30%)10000
2 founders4000 + 3000 (70%)3000 (30%)10000
3 founders3000 + 3000 + 2000 (80%)2000 (20%)10000
You can have between 1 and 10 founders per incorporation. Each founder must provide a valid email, ISO 3166-1 alpha-3 country codes for nationality and tax residency (e.g. USA, GBR, DEU), and a residential address.

4. Payment Options

The payWithSol parameter controls how the incorporation fee is paid:
payWithSolBehavior
false (default)The payer pays directly in USDC. The wallet must hold enough USDC to cover the fee.
trueThe payment transaction performs an exact-output swap from SOL to USDC, matching the required USDC amount. The wallet must hold enough SOL.
In both cases, the priceUSDC field in the response reflects the incorporation price in USDC. When paying with SOL, the transaction handles the swap automatically β€” no extra steps are needed.

5. Understanding Founder Forms

After you submit incorporation details, each founder in the response will have a formUrl β€” a unique URL where the founder completes all required verification steps (KYC, PEP questionnaire, and IP attribution acknowledgment). Each founder must open their formUrl and complete all required fields. The project’s isReadyForIncorporation flag becomes true only when every founder has completed their form. You can check individual founder progress at any time using checkIncorporationStatus() (which calls sdk.incorporation.getDetails() and sdk.incorporation.list() under the hood).

6. Run Your Script

The script has three functions you call at different stages. Update the bottom of incorporate-token.ts to uncomment the function you need, then run:
npx ts-node incorporate-token.ts
Stage 1 β€” Create payment and submit details (uncommented by default):
createPaymentAndSubmitDetails({ ... });
This pays the incorporation fee, submits founder details, and prints out the form URLs that each founder needs to complete. Stage 2 β€” After founders have completed their forms, check readiness:
checkIncorporationStatus("YOUR_TOKEN_MINT_ADDRESS");
This shows the current status and whether each founder has completed KYC, PEP, and IP attribution. Stage 3 β€” Once isReadyForIncorporation is true, start the incorporation:
startIncorporationProcess("YOUR_TOKEN_MINT_ADDRESS");
Founders may take hours or days to complete their forms. You can call checkIncorporationStatus() as many times as needed to monitor progress before triggering the final step.

7. Troubleshooting

Common errors you may encounter:
  • Share basis points don’t sum to 10000: The sum of all founder shareBasisPoint values plus incorporationShareBasisPoint must equal exactly 10,000.
  • Incorporation share out of range: incorporationShareBasisPoint must be between 2000 (20%) and 3000 (30%).
  • Invalid country code: Nationality and tax residency must use ISO 3166-1 alpha-3 codes (3 letters, e.g. USA, GBR, DEU).
  • Invalid payer wallet: Ensure the wallet address is a valid Solana public key.
  • Insufficient funds: The payer wallet needs enough SOL or USDC to cover the incorporation fee.
  • Payment not confirmed: The payment transaction must be confirmed on-chain before submitting incorporation details.
  • Prerequisites not met: startIncorporation will fail if any founder hasn’t completed KYC, PEP forms, or IP attribution.
  • Too many/few founders: You must have between 1 and 10 founders.
  • Company names: You must provide exactly 3 preferred company names.
Check the console output for detailed error messages.