Solana 上 SPL 代币转账完整指南

·

在学习 Solana 开发的过程中,你一定会遇到 SPL 代币转账 这个关键环节:从给社区空投打卡 NFT,到批量分发白名单通证,乃至在多签托管账户之间划拨资金,无一例外都要借助 SPL Token Program。抓住本文,你会用最短时间完成「发送 SPL 代币」从概念到脚本的全部环节。

本文你会做什么?

本次实操将基于 Solana Devnet,用 TypeScript 编写一段 40 行核心代码,完成以下任务:

  1. 创建钱包并领取测试 SOL
  2. 计算正确的小数位
  3. 自动检测并创建接收方 ATA
  4. 发起链上转账并获取 交易签名
  5. 在终端查看绿色成功提示 ✅

一分钟了解 SPL 账户模型

SPL 代币转账与原生 SOL 转账的最大差异在于——账户结构。牢牢记住下面两个小概念,后面的代码就水到渠成。

Mint ID:每个代币的身份证

无论你用的是 USDC、SAMO,还是独创的游戏币,都有一条独一无二 Mint ID。NFT 也一样:每只 Famous Fox 均对应专属的 Mint ID,所以才能保持“非同质化”。

ATA(Associated Token Account):一对一的收纳格

把钱包地址想象成房间,ATA 就是房间里给某个特定代币 单独预留的抽屉。必须「币种相同」的 ATA 才能互转,否则 Solana 会直接报错。若接收方首次接触该代币,还需 顺带创建 ATA 并预存租金。

动手前:环境与资产

👉 亲手领 1 亿枚测试 $DUMMY,五分钟就能开箱实验!

步骤 1:初始化项目

mkdir spl-transfer-demo && cd spl-transfer-demo
npm init -y
npm i @solana/kit @solana-program/token ts-node
npx tsc --init

tsconfig.json 中加入:

{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "noEmit": true,
    "target": "ESNext"
  }
}

步骤 2:变量声明与助记词

新建文件 app.ts,头几行引入依赖并声明变量(真实开发请改用 .env):

import { address } from '@solana/kit';

const SECRET = [...]; // 32 字节助记词
const DESTINATION = address('DemoKMZWkk483hX4mUrcJoo3zVvsKhm8XXs28TuwZw9H');
const MINT = address('DoJuta7joTSuuoozqQtjtnASRYiVsT435gh4srh5LLGK'); // 换成你的 Mint ID
const AMOUNT = 1n; // 注意使用 BigInt

若不确定代币 Mint ID,可打开 Solana Explorer → Tokens 粘贴钱包地址查看并复制。

步骤 3:获取小数位(decimals)

链上记录的是 最小单位整数,需要换算:

async function getDecimals(mint: string) {
  const info = await rpc.getAccountInfo(address(mint)).send();
  if (!info.value?.data) throw new Error('找不到 Mint');
  const data = Uint8Array.from(Buffer.from(info.value.data[0], 'base64'));
  return data[44];
}

使用场景:USDC 通常 6 位小数,因此转账 1 USDC 实际填 1_000_000 单位。

步骤 4:完整转账函数

下面用函数式写法拼装交易:

async function sendTokens() {
  console.log(`转账 ${AMOUNT} 枚代币至 ${DESTINATION}`);

  // 1. 创建并充值钱包
  const fromKP = await createKeyPairFromBytes(new Uint8Array(SECRET));
  const fromSigner = createSignerFromKeyPair(fromKP);

  // 2. 查找或创建 ATA
  const [srcATA] = await findAssociatedTokenPda({
    owner: fromSigner.address,
    mint: MINT,
  });
  const [dstATA] = await findAssociatedTokenPda({
    owner: DESTINATION,
    mint: MINT,
  });

  // 3. 判断目标 ATA 是否已存在
  let createDstATA = false;
  try {
    await rpc.getAccountInfo(dstATA).send();
  } catch {
    createDstATA = true;
  }

  // 4. 计算带小数位的总量
  const decimals = await getDecimals(MINT);
  const amountWithDecimals = AMOUNT * BigInt(10 ** decimals);

  // 5. 拼装并发送交易
  const { value: blockhash } = await rpc.getLatestBlockhash().send();
  const tx = pipe(
    createTransactionMessage({ version: 0 }),
    (m) => setTransactionMessageFeePayerSigner(fromSigner, m),
    (m) => setTransactionMessageLifetimeUsingBlockhash(blockhash, m),
    (m) =>
      createDstATA
        ? appendTransactionMessageInstruction(
            getCreateAssociatedTokenIdempotentInstruction({
              payer: fromSigner,
              ata: dstATA,
              owner: DESTINATION,
              mint: MINT,
            }),
            m
          )
        : m,
    (m) =>
      appendTransactionMessageInstruction(
        getTransferInstruction({
          source: srcATA,
          destination: dstATA,
          authority: fromSigner,
          amount: amountWithDecimals,
        }),
        m
      )
  );

  const signed = await signTransactionMessageWithSigners(tx);
  const sig = await sendAndConfirmTransaction(signed);
  console.log(`\x1B[32m转账成功!\x1B[0m\nhttps://explorer.solana.com/tx/${sig}?cluster=devnet`);
}

sendTokens().catch(console.error);

一条命令跑起来:

npx ts-node app.ts

看到绿色 转账成功! 加上交易哈希即大功告成。

深度扩展:你还可以做什么?

👉 低延迟节点一次到位,批量操作节点响应飙升 8 倍!


常见问题 FAQ

Q1: 为什么会出现「0x11 - Insufficient funds for transaction」报错?
A: 检查 SOL 余额是否 >= 0.001 SOL(包含 ATA rent + compute units)。若不足,请重新领水后再试。

Q2: 代币明明拿到手,为什么搜索不到?
A: 大多数钱包默认隐藏 零余额 ATA。打开钱包设置里的「显示隐藏资产」即可。

Q3: BigInt 不支持小数点,如何转账 0.5 枚代币?
A: 第一次计算完整单位,再乘以 10 位小数次方即可。例如 USDC 的 0.5 等效 BigInt(500000)

Q4: 能否一次性往几千地址空投?
A: 可以,但强烈采用 指令打包批量交易 的方式优化;否则每笔单交易,上千笔会出现速率瓶颈。

Q5: 生产环境应如何保管私钥?
A: 建议将密钥写入 环境变量 或安全硬件(Ledger、Trezor),并开启 白名单 IP 访问节点,降低泄露风险。

现在就打开编辑器,亲手跑一次脚本——你将深刻体会到 SPL Token Program 的简洁与强大!