ERC-20 su Solana

I Token Non Hanno Indirizzi di Contratto, Solo Indirizzi di Mint

Su Ethereum, ogni token è identificato univocamente dal suo indirizzo di contratto. Su Solana, ogni token è identificato univocamente da un indirizzo di mint. L'idea sottostante è simile: questo è il puntatore onchain per quello specifico token. Ma poiché il Token Program di Solana è universale, non si distribuisce un "indirizzo di contratto" separato. Invece, si fornisce al runtime di Solana un nuovo mint account.

solidity
// SPDX-License-Identifier: MIT license
pragma solidity =0.8.28;

struct Mint {
    uint8 decimals;
    uint256 supply;
    address mintAuthority;
    address freezeAuthority;
    address mintAddress;
}

struct TokenAccount {
    address mintAddress;
    address owner;
    uint256 balance;
    bool isFrozen;
}

contract Spl20 {
    mapping(address => Mint) public mints;
    mapping(address => TokenAccount) public tokenAccounts;
    mapping(address => bool) public mintAddresses;
    mapping(address => bool) public tokenAddresses;

    function initializeMint(uint8 decimals, address mintAuthority, address freezeAuthority, address mintAddress)
        public
        returns (Mint memory)
    {
        require(mintAddresses[mintAddress] == false, "Mint already exists");
        mints[mintAddress] = Mint(decimals, 0, mintAuthority, freezeAuthority, mintAddress);
        mintAddresses[mintAddress] = true;
        return Mint(decimals, 0, mintAuthority, freezeAuthority, mintAddress);
    }

    function mintTokens(address toMintTokens, address mintAddress, uint256 amount) public {
        require(mints[mintAddress].mintAuthority == msg.sender, "Only the mint authority can mint tokens");
        require(mints[mintAddress].mintAddress != address(0), "Token does not exist");
        require(mints[mintAddress].supply + amount <= type(uint256).max, "Supply overflow");

        mints[mintAddress].supply += amount;

        address tokenAddress = address(uint160(uint256(keccak256(abi.encodePacked(toMintTokens, mintAddress)))));

        if (tokenAccounts[tokenAddress].mintAddress == address(0)) {
            tokenAccounts[tokenAddress] = TokenAccount(mintAddress, toMintTokens, 0, false);
            tokenAddresses[tokenAddress] = true;
        }
        tokenAccounts[tokenAddress].balance += amount;
        tokenAccounts[tokenAddress].owner = toMintTokens;
    }

    function transfer(address to, address mintAddress, uint256 amount) public {
        address toTokenAddress = address(uint160(uint256(keccak256(abi.encodePacked(to, mintAddress)))));
        address fromTokenAddress = address(uint160(uint256(keccak256(abi.encodePacked(msg.sender, mintAddress)))));

        require(tokenAccounts[fromTokenAddress].balance >= amount, "Insufficient balance");
        require(tokenAccounts[toTokenAddress].balance + amount <= type(uint256).max, "Supply overflow");
        require(tokenAccounts[fromTokenAddress].owner == msg.sender, "fromToken owner is not msg.sender");
        require(tokenAccounts[fromTokenAddress].isFrozen == false, "fromToken is frozen");
        require(tokenAccounts[toTokenAddress].isFrozen == false, "toToken is frozen");

        if (tokenAccounts[toTokenAddress].mintAddress == address(0)) {
            tokenAccounts[toTokenAddress] = TokenAccount(mintAddress, to, 0, false);
            tokenAddresses[toTokenAddress] = true;
        }

        tokenAccounts[fromTokenAddress].balance -= amount;
        tokenAccounts[toTokenAddress].balance += amount;
    }

    function getMint(address token) public view returns (Mint memory) {
        return mints[token];
    }

    function getTokenAccount(address owner, address token) public view returns (TokenAccount memory) {
        return tokenAccounts[address(uint160(uint256(keccak256(abi.encodePacked(owner, token)))))];
    }
}
FaseEthereum (ERC-20)Solana (SPL Token)
1. Preparare il Codice del TokenSolitamente utilizza OpenZeppelin. Si crea un file Solidity (ad es., MyToken.sol).Non è necessario un contratto personalizzato. Il Token Program è già distribuito. Basta creare un Mint Account.
2. Compilare e DistribuireCompilare e distribuire usando Hardhat/Truffle (ad es., npx hardhat run deploy.js --network ...).Usare spl-token create-token. Nessuna distribuzione di contratto separata. Una singola chiamata RPC completa il processo.
3. Mint InizialeChiamare il costruttore del contratto o la funzione mint(). Comporta commissioni gas che possono variare.Eseguire spl-token mint <MINT_ADDRESS> <AMOUNT>. Tipicamente commissioni di transazione molto basse.
4. Creare il DestinatarioTipicamente solo un normale indirizzo Ethereum. Gli utenti devono aggiungere manualmente l'indirizzo del contratto nei wallet.Un Associated Token Account (ATA) viene riconosciuto automaticamente dai wallet come Phantom o Solflare.
5. Verificare i RisultatiVisualizzare su Etherscan. Gli utenti spesso aggiungono manualmente l'indirizzo del contratto al proprio wallet.Eseguire solana balance <ADDRESS> o spl-token accounts. È inoltre possibile cercare per indirizzo di mint su Solana Explorer.
6. Aggiornamenti del CodicePer aggiornamenti importanti, si potrebbe utilizzare un pattern proxy o ridistribuire il contratto.Il Token Program è fisso. Si possono abilitare estensioni o scrivere un programma onchain separato per logiche più complesse.
7. Requisiti di AuditOgni contratto richiede tipicamente un audit, specialmente se si aggiunge logica personalizzata.Il Token Program principale è già stato sottoposto a molteplici audit. Per il semplice conio di token, audit aggiuntivi sono raramente necessari.
bash
function name() public view returns (string) // Returns the name of the token
jsx
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { fetchDigitalAsset, mplTokenMetadata } from "@metaplex-foundation/mpl-token-metadata";
import { PublicKey } from "@metaplex-foundation/js";
const mintAddress = new PublicKey("Token Address");

async function name() {
  try {
    const umi = createUmi("https://api.devnet.solana.com");
    umi.use(mplTokenMetadata());
    const digitalAsset = await fetchDigitalAsset(umi, mintAddress);
    return digitalAsset.metadata.name;
  } catch (error) {
    console.error("Error fetching token name:", error);
    return null;
  }
}

name().then(name => {
    console.log("token Name:", name);
  })
  .catch(error => {
    console.error("Error:", error);
  });
bash
function symbol() public view returns (string) // Returns the symbol of the token
jsx
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { fetchDigitalAsset, mplTokenMetadata } from "@metaplex-foundation/mpl-token-metadata";
import { PublicKey } from "@metaplex-foundation/js";
const mintAddress = new PublicKey("Token Address");

async function symbol() {
  try {
    const umi = createUmi("https://api.devnet.solana.com");
    umi.use(mplTokenMetadata());
    const asset = await fetchDigitalAsset(umi, mintAddress);
    return asset.metadata.symbol;
  } catch (error) {
    console.error("Error fetching NFT symbol:", error);
    return null;
  }
}

symbol().then(symbol => {
  console.log("Symbol:", symbol);
}).catch(error => {
  console.error("Error:", error);
});
bash
function decimals() public view returns (uint8) // Returns the number of decimals the token use
jsx
import { Connection, PublicKey } from "@solana/web3.js";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const mintAddress = new PublicKey("Token Address");

async function decimals() {
    let response = await connection.getTokenSupply(mintAddress);
    return response.value.decimals;
}

decimals().then(decimals => {
  console.log(decimals);
}).catch(error => {
  console.error("Error:", error);
});
bash
function balanceOf(address _owner) public view returns (uint256 balance) // Returns the number of tokens in owner's account
jsx
import { Connection, PublicKey } from "@solana/web3.js";
import { AccountLayout }from "@solana/spl-token";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const mintAddress = new PublicKey("Token Address");
const ownerAddress = new PublicKey("Your Wallet Address");

async function balanceOf(_owner){
  let response = await connection.getTokenAccountsByOwner(_owner, { mint: mintAddress });
  const accountInfo = AccountLayout.decode(response.value[0].account.data);
  return accountInfo.amount;
}

balanceOf(ownerAddress).then(balance => {
  console.log(balance); // need to multiply the result by the decimal precision to get the correct value.
}).catch(error => {
  console.error("Error:", error);
});
bash
function totalSupply() public view returns (uint256) // Returns the total issuance of tokens
jsx
import { Connection, PublicKey } from "@solana/web3.js";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const mintAddress = new PublicKey("Token Address");

async function totalSupply() {
    let response = await connection.getTokenSupply(mintAddress);
    return response.value.amount;
}

totalSupply().then(supply => {
  console.log(supply); // need to multiply the result by the decimal precision to get the correct value
}).catch(error => {
  console.error("Error:", error);
});
solidity
function transfer(address _to, uint256 _value) public returns (bool success) // Moves a value amount of tokens from the caller’s account to _to 
jsx
import { Keypair, Transaction, Connection, PublicKey } from "@solana/web3.js";
import { createTransferCheckedInstruction } from "@solana/spl-token";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");

// Must contain your private key as a Uint8Array
const ownerSecretkey = [];
const ownerPrivatekeypair = Keypair.fromSecretKey(new Uint8Array(ownerSecretkey));

const receiverAddress = new PublicKey("Receiver's Wallet Address");
const mintAddress = new PublicKey("Token Address");
const ownerTokenAccount = new PublicKey("Your Associated Token Account Address");
const receiverTokenAccount = new PublicKey("Receiver's Associated Token Account Address");

// For a token with 9 decimals, transferring 1 => 1 * 10^9
const amount = 1;

async function transfer(_to, _value) {
  try {
    // Create a transaction with the transfer instruction
    const tx = new Transaction().add(
      createTransferCheckedInstruction(
        ownerTokenAccount,
        mintAddress,
        receiverTokenAccount,
        ownerPrivatekeypair.publicKey,
        _value * Math.pow(10, 9), // Decimal correction
        9 // decimals
      )
    );

    // Send the transaction (simplified, no explicit blockhash or feePayer set)
    await connection.sendTransaction(tx, [ownerPrivatekeypair]);
    return true;
  } catch (error) {
    console.error("Error in transfer:", error);
    return false;
  }
}

transfer(receiverAddress, amount)
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error("Error:", error);
  });
jsx
import { Keypair, Transaction, Connection, PublicKey } from "@solana/web3.js";
import { createTransferCheckedInstruction, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";

const connection = new Connection("https://api.devnet.solana.com", "confirmed");

// Must contain your private key as a Uint8Array
const ownerSecretkey = [];
const ownerPrivatekeypair = Keypair.fromSecretKey(new Uint8Array(ownerSecretkey));

const receiverAddress = new PublicKey("Receiver's Wallet Address");
const mintAddress = new PublicKey("Token Address");
const amount = 1; // Amount to transfer

async function transfer(_to, _value) {
  try {
    // Get or create the sender's ATA
    const ownerTokenAccount = await getOrCreateAssociatedTokenAccount(
      connection,
      ownerPrivatekeypair, // Fee payer
      mintAddress,
      ownerPrivatekeypair.publicKey
    );

    // Get or create the receiver's ATA
    const receiverTokenAccount = await getOrCreateAssociatedTokenAccount(
      connection,
      ownerPrivatekeypair, // Fee payer
      mintAddress,
      _to
    );

    // Build the transaction
    const tx = new Transaction().add(
      createTransferCheckedInstruction(
        ownerTokenAccount.address,
        mintAddress,
        receiverTokenAccount.address,
        ownerPrivatekeypair.publicKey,
        _value * Math.pow(10, 9), // Decimal correction (9 decimals)
        9 // decimals
      )
    );

    // Send the transaction (simple version)
    await connection.sendTransaction(tx, [ownerPrivatekeypair]);
    return true;
  } catch (error) {
    console.error("Error in transfer:", error);
    return false;
  }
}

// Execute the transfer function
transfer(receiverAddress, amount)
  .then(result => {
    console.log("Transaction result:", result);
  })
  .catch(error => {
    console.error("Error:", error);
  });
Guide EVM → SVM

Risorse aggiuntive

Gestito da

© 2026 Solana Foundation.
Tutti i diritti riservati.
Resta connesso
ERC-20 su Solana: Guida completa | Solana