Skip to main content
Creating a token on Solana requires three steps: create a mint account, create a holding account for the recipient, and mint tokens to it. This guide covers both SPL Token and Token-2022 paths using the Protochain Token Program Service.

Choose your token program

Both token programs use the same Protochain API shape — you choose the program once, at mint creation, and it cannot be changed. See Token Programs for the full trade-off comparison.
SPL TokenToken-2022
MetadataMetaplex PDA (external)On-chain extension (native)
ExtensionsNot supportedTransfer fees, interest-bearing, etc.
CompatibilityMaximum ecosystem supportNewer wallets/DEXs only
If you need extensions or native on-chain metadata, use Token-2022. If you need maximum wallet compatibility, use SPL Token.

Prerequisites

Create the mint

Call CreateToken2022Mint or CreateSPLTokenMint depending on your token program choice. Both return a list of instructions — include all of them in the transaction in order (the service handles ordering).

Token-2022 mint

resp, err := tokenClient.CreateToken2022Mint(ctx, &token_v1.CreateToken2022MintRequest{
    PayerPubKey:         "PayerPubKey1111111111111111111111111111111",
    MintPubKey:          "MintPubKey111111111111111111111111111111111",
    MintAuthorityPubKey: "PayerPubKey1111111111111111111111111111111",
    Decimals:            6, // like USDC; use 0 for NFTs
    Extensions:          []*token_v1.Token2022Extension{}, // add Token2022Extension values here for transfer fees, etc.
})
if err != nil {
    log.Fatal(err)
}
// Collect all instructions — the response may include multiple
mintInstructions := resp.Instructions

SPL Token mint

resp, err := tokenClient.CreateSPLTokenMint(ctx, &token_v1.CreateSPLTokenMintRequest{
    PayerPubKey:         "PayerPubKey1111111111111111111111111111111",
    MintPubKey:          "MintPubKey111111111111111111111111111111111",
    MintAuthorityPubKey: "PayerPubKey1111111111111111111111111111111",
    Decimals:            6, // like USDC; use 0 for NFTs
    // Metadata: &token_v1.MetaplexTokenMetadata{
    //     Name:   "My Token",
    //     Symbol: "MTK",
    //     Uri:    "https://example.com/metadata.json",
    // },
})
if err != nil {
    log.Fatal(err)
}
// Collect all instructions — the response may include multiple
mintInstructions := resp.Instructions

Create a holding account

Before minting, the recipient needs a token holding account (Associated Token Account) for this mint. Call CreateToken2022HoldingAccount or CreateSPLTokenHoldingAccount — use the same token program as the mint.
Pass owner_pub_key as the recipient’s system wallet address, not a pre-computed ATA address. Protochain derives the ATA automatically.

Token-2022 holding account

holdResp, err := tokenClient.CreateToken2022HoldingAccount(ctx, &token_v1.CreateToken2022HoldingAccountRequest{
    PayerPubKey: "PayerPubKey1111111111111111111111111111111",
    OwnerPubKey: "RecipientPubKey11111111111111111111111111111",
    MintPubKey:  "MintPubKey111111111111111111111111111111111",
    Extensions:  []*token_v1.Token2022HoldingAccountExtension{}, // add holding account extensions here
})
if err != nil {
    log.Fatal(err)
}
holdInstructions := holdResp.Instructions

SPL Token holding account

holdResp, err := tokenClient.CreateSPLTokenHoldingAccount(ctx, &token_v1.CreateSPLTokenHoldingAccountRequest{
    PayerPubKey: "PayerPubKey1111111111111111111111111111111",
    OwnerPubKey: "RecipientPubKey11111111111111111111111111111",
    MintPubKey:  "MintPubKey111111111111111111111111111111111",
})
if err != nil {
    log.Fatal(err)
}
holdInstructions := holdResp.Instructions

Compile and submit the mint transaction

Collect all instructions from the mint creation and holding account responses, assemble them into a single transaction, compile with CompileTransaction, sign with both the fee payer key and the mint keypair, then submit.
The mint account keypair must be included as a signer in SignTransaction — it is not just a public key reference. Both the payer and the mint keypair are required signers.
// Assemble all instructions into one transaction
tx := &transaction_v1.Transaction{State: transaction_v1.TransactionState_TRANSACTION_STATE_DRAFT}
for _, instr := range mintInstructions {
    tx.Instructions = append(tx.Instructions, instr)
}
for _, instr := range holdInstructions {
    tx.Instructions = append(tx.Instructions, instr)
}

// Compile
compileResp, err := txClient.CompileTransaction(ctx, &transaction_v1.CompileTransactionRequest{
    Transaction: tx,
    FeePayer:    "PayerPubKey1111111111111111111111111111111",
})
if err != nil {
    log.Fatal(err)
}

// Sign with both payer and mint keypair
signResp, err := txClient.SignTransaction(ctx, &transaction_v1.SignTransactionRequest{
    Transaction: compileResp.Transaction,
    PrivateKeys: []string{"payerPrivateKey...", "mintPrivateKey..."},
})
if err != nil {
    log.Fatal(err)
}

// Submit
submitResp, err := txClient.SubmitTransaction(ctx, &transaction_v1.SubmitTransactionRequest{
    Transaction: signResp.Transaction,
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("mint tx signature: %s\n", submitResp.Signature)

Mint tokens

With the mint account created, call Mint to issue tokens. Mint is token-program agnostic — Protochain reads the mint on-chain to determine the token program automatically.
Pass destination_owner_pub_key as the recipient’s system wallet address, not the ATA address. The ATA is derived automatically.
mintResp, err := tokenClient.Mint(ctx, &token_v1.MintRequest{
    MintPubKey:             "MintPubKey111111111111111111111111111111111",
    DestinationOwnerPubKey: "RecipientPubKey11111111111111111111111111111",
    Amount:                 "1000.0", // human-readable; 1000 tokens at 6 decimals = 1,000,000,000 base units
})
if err != nil {
    log.Fatal(err)
}
Add the returned instruction to a transaction, then compile, sign (mint authority only this time), and submit using the same compile → sign → submit pattern from the previous section.

Next steps