BCSkill (Block chain skill )
区块链中文技术社区

只讨论区块链底层技术
遵守一切相关法律政策!

Solana基础 - Solana RPC HTTP 方法

getAccountInfo

返回与提供的公钥的账户相关的所有信息,和以太坊 eth_getAccount类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getAccountInfo",
    "params": [
      "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
      {
        "encoding": "base58"
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.2.3",
      "slot": 370321974
    },
    "value": {
      "data": [
        "",
        "base58"
      ],
      "executable": false,
      "lamports": 4994414840,
      "owner": "11111111111111111111111111111111",
      "rentEpoch": 18446744073709551615,
      "space": 0
    }
  },
  "id": 1
}

getBalance

返回所提供公钥的账户的 Lamport 余额, 和以太坊eth_getBalance类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBalance",
    "params": [
      "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5"
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.2.3",
      "slot": 370322177
    },
    "value": 4994414840
  },
  "id": 1
}

getBlock

返回账本中已确认区块的身份和交易信息,和以太坊 eth_getBlockByNumber类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlock",
    "params": [
      358300848,
      {
        "encoding": "json",
        "maxSupportedTransactionVersion": 0,
        "transactionDetails": "full",
        "rewards": false
      }
    ]
  }
'

如果类似如下提示,这通常意味着你尝试访问的特定槽(Slot)存在问题,具体可能是该槽被跳过或者在长期存储中缺失

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32009,
    "message": "Slot 100000430 was skipped, or missing in long-term storage"
  },
  "id": 1
}
{
  "jsonrpc": "2.0",
  "result": {
    "blockHeight": 346328196,
    "blockTime": 1738488135,
    "blockhash": "HGrdL4Z5vEvQVWchG2pnJEQ5dMxQhqiXncpL6GJvZimx",
    "parentSlot": 358300847,
    "previousBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ",
    "transactions": [
      ...
          {
            "meta": {
                "computeUnitsConsumed": 2100,
                "err": null,
                "fee": 5000,
                "innerInstructions": [],
                "loadedAddresses": {
                    "readonly": [],
                    "writable": []
                },
                "logMessages": [
                    "Program Vote111111111111111111111111111111111111111 invoke [1]",
                    "Program Vote111111111111111111111111111111111111111 success"
                ],
                "postBalances": [
                    912514572484583,
                    8402660357128602,
                    1
                ],
                "postTokenBalances": [],
                "preBalances": [
                    912514572489583,
                    8402660357128602,
                    1
                ],
                "preTokenBalances": [],
                "rewards": null,
                "status": {
                    "Ok": null
                }
            },
            "transaction": {
                "message": {
                    "accountKeys": [
                        "dv4ACNkpYPcE3aKmYDqZm9G5EB3J4MRoeE7WNDRBVJB",
                        "23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF",
                        "Vote111111111111111111111111111111111111111"
                    ],
                    "header": {
                        "numReadonlySignedAccounts": 0,
                        "numReadonlyUnsignedAccounts": 1,
                        "numRequiredSignatures": 1
                    },
                    "instructions": [
                        {
                            "accounts": [
                                1,
                                0
                            ],
                            "data": "67MGn8CZqesiDCbWWYBQre79LVuia8eBTRNppRhYeXWhLFMWQsHQrrLxehkcbeCZ9ncYWSaBVdzbL6iUumunuVXWuVgnYAZ6oLZVMsEUZohcJMkAa7sAhbmGAT4ZUjfcZd7LH99cRPUwUZu7HUaG5Q8Y7TM98PR6bJJoySwh8QpuxtWzBqpLAH4nv2cDY946EQPmXS5zvB",
                            "programIdIndex": 2,
                            "stackHeight": null
                        }
                    ],
                    "recentBlockhash": "3QuyTMBRAK5PhLXehDCTMH4zgnfn7xgZnHG3fBnZFsiQ"
                },
                "signatures": [
                    "56uXBR4rgLNbDCtCr8gyTktGYCBBsKdJ4mUEGS9rEkcS9tG9ZghksbZCUdjXdoLJqnScG2e4zQCxvUKE6ctwyjpw"
                ]
            },
            "version": "legacy"
        }
    ...
    ]
  },
  "id": 1
}

getBlockCommitment

返回特定区块的承诺

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockCommitment",
    "params": [
      346328196
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "commitment": null,
    "totalStake": 157541826450322300
  },
  "id": 1
}

getBlockHeight

返回节点当前的区块高度,和以太坊eth_blockNumber 类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockHeight",
    "params": []
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": 358302920,
  "id": 1
}

getBlockProduction

获取当前epoch期间内,所有产生区块的地址和相应的产块结果[应产出:实际产出]
range是当前周期的起始和当前slot区间

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockProduction"
  }
'

注:对于产块结果,可以作为对应验证节点的稳定性判定

getBlocks

返回两个 slot 之间已确认的区块列表

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlocks",
    "params": [
      5, // 起始Slot
      10 // 结束Slot (间距<= 500000)
    ]
  }
'

getBlocksWithLimit

返回从给定槽开始的已确认区块列表
和getBlocks类似,区别就是由结束Slot改为了Limit

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlocksWithLimit",
    "params": [
      5, // 起始Slot
      3 // Limit (间距<= 500000)
    ]
  }
'

getBlockTime

返回某个区块的预计生产时间。
每个验证者都会定期向账本报告其 UTC 时间,方法是间歇性地为特定区块的投票添加时间戳。请求的区块时间是根据账本上记录的一组最近区块中投票时间戳的权益加权平均值计算得出的。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlockTime",
    "params": [
      358319114
    ]
  }
'

getClusterNodes

返回有关参与集群的所有节点的信息

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getClusterNodes"
  }
'

getEpochInfo

返回有关当前纪元的信息

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getEpochInfo"
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "absoluteSlot": 370353885, // 当前槽位
    "blockHeight": 358332171, // 当前区块高度
    "epoch": 857, // 当前epoch
    "slotIndex": 129885, // 当前 slot 相对于当前 epoch 的开始
    "slotsInEpoch": 432000, // 此纪元中的时隙数
    "transactionCount": 15406146576 // 自创世以来无错误处理的交易总数
  },
  "id": 1
}

getEpochSchedule

从此集群的创世配置中返回纪元调度信息

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getEpochSchedule"
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "firstNormalEpoch": 0,
    "firstNormalSlot": 0,
    "leaderScheduleSlotOffset": 432000,
    "slotsPerEpoch": 432000,
    "warmup": false // 是否周期一开始很短,然后逐渐增长
  },
  "id": 1
}

getFeeForMessage

获取网络针对特定消息收取的费用,和以太坊eth_estimateGas类似

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getFeeForMessage",
    "params": [
      "AQABAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAA",
      {
        "commitment": "processed"
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": { "slot": 5068 },
    "value": 5000
  },
  "id": 1
}

getFirstAvailableBlock

返回当前节点上可访问的最早的区块编号(block number)

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getFirstAvailableBlock"
  }
'

注:简单说就是查询当前RPC节点存储的历史区块最早的高度,由于存储成本问题,对于官方devnet testnet都只保留近期一部分数据,对于主网保留所有数据,对于历史数据保存需要使用bigtable外部存储,对于设置启动参数--limit-ledger-size,大概保存近期 600w左右区块数据

getGenesisHash

查询genesis hash

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getGenesisHash"
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
  "id": 1
}

getHealth

返回节点的当前健康状况。健康节点是指位于 HEALTH_CHECK_SLOT_DISTANCE最新集群确认槽内的节点。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getHealth"
  }
'

注:如果节点健康将返回ok,可用于节点的监控指标

getHighestSnapshotSlot

返回节点具有快照的最高插槽信息。
这将找到最高的完整快照槽,以及基于完整快照槽的最高增量快照槽(如果有)

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getHighestSnapshotSlot"
  }
'

getIdentity

返回当前节点的身份公钥

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getIdentity"
  }
'

注:也就是返回对应RPC 配置的--identity的pubkey

getInflationGovernor

返回当前通货膨胀调控器

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getInflationGovernor"
  }
'

getInflationRate

返回当前时期的具体通胀值

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getInflationRate"
  }
'

getInflationReward

返回某个时期地址列表的通胀/权益奖励

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getInflationReward",
    "params": [
      [
        "6dmNQ5jwLeLk5REvio1JcMshcbvkYMwy26sJ8pbkvStu",
        "BGsqMegLpV6n6Ve146sSX2dTjUMj3M92HnU8BbNRMhF2"
      ],
      {
        "epoch": 2
      }
    ]
  }
'

getLargestAccounts

按 Lamport 余额返回前 20 个最大的账户(结果可能缓存长达两小时)

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLargestAccounts",
    "params": []
  }
'

getLatestBlockhash

返回最新的区块哈希

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLatestBlockhash",
    "params": [
      {
        "commitment": "processed"
      }
    ]
  }
'

注:当前值用于常规交易发送时的recentBlockhash

getLeaderSchedule

返回一个时期的领导者时间表

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLeaderSchedule",
    "params": [
      null,
      {
        "identity": "4Qkev8aNZcqFNSRhQzwyLMFSsi94jHqE8WNVTJzTP99F"
      }
    ]
  }
'

getMaxRetransmitSlot

获取重传阶段看到的最大时隙。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMaxRetransmitSlot"
  }
'

getMaxShredInsertSlot

获取撕碎插入后看到的最大插槽。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMaxShredInsertSlot"
  }
'

getMinimumBalanceForRentExemption

返回免除账户租金所需的最低余额。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMinimumBalanceForRentExemption",
    "params": [
      50
    ]
  }
'

getMultipleAccounts

返回公钥列表的帐户信息。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getMultipleAccounts",
    "params": [
      [
        "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg",
        "4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA"
      ],
      {
        "encoding": "base58"
      }
    ]
  }
'

getProgramAccounts

返回所提供程序 Pubkey 拥有的所有账户

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T",
      {
        "filters": [
          { "dataSize": 17 },
          {
            "memcmp": {
              "offset": 4,
              "bytes": "3Mc6vR"
            }
          }
        ]
      }
    ]
  }
'

getRecentPerformanceSamples

返回最近性能样本的列表(按槽位倒序排列)。性能样本每 60 秒采集一次,包括给定时间窗口内发生的事务和槽位数量。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getRecentPerformanceSamples",
    "params": [
      2
    ]
  }
'

getRecentPrioritizationFees

返回最近区块的优先费用列表。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getRecentPrioritizationFees",
    "params": [
      ["CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY"]
    ]
  }
'

getSignaturesForAddress

返回已确认交易的签名,这些交易的列表中包含指定地址accountKeys。按时间倒序返回提供的签名或最近确认的区块的签名

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSignaturesForAddress",
    "params": [
      "Vote111111111111111111111111111111111111111",
      {
        "limit": 1 // 1-1000, 默认1000
      }
    ]
  }
'

getSignatureStatuses

返回签名列表的状态。每个签名必须是 txid,即交易的第一个签名。
除非searchTransactionHistory包含配置参数,否则此方法仅搜索签名的近期状态缓存,其中保留所有活动插槽和MAX_RECENT_BLOCKHASHES根插槽的状态。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSignatureStatuses",
    "params": [
      [
        "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"
      ],
      {
        "searchTransactionHistory": true
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.0.18",
      "slot": 24219064
    },
    "value": [
      {
        "confirmationStatus": "finalized", // processed->confirmed->finalized
        "confirmations": null,
        "err": null,
        "slot": 24218468,
        "status": {
          "Ok": null
        }
      }
    ]
  },
  "id": 1
}

getSlot

返回已达到给定或默认承诺级别的槽

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSlot"
  }
'

注:默认是返回最近finalized的Slot

getSlotLeader

返回当前 slot 领导者

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSlotLeader"
  }
'

注:如果定制链做Rollup,那返回的为sequencer的账户地址

getSlotLeaders

返回给定槽范围的槽领导者

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSlotLeaders",
    "params": [
      100, // 起始Slot
      10 //Limit (1-5000)
    ]
  }
'

getStakeMinimumDelegation

以 lampors 为单位,返回股权最低授权。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getStakeMinimumDelegation"
  }
'

注:如果限制外部节点的加入,可以修改链代码,将最小委托量修改为10000000000000000000

getSupply

返回有关当前供应的信息。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getSupply"
  }
'

getTokenAccountBalance

返回 SPL 代币账户的代币余额

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenAccountBalance",
    "params": [
      "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2" // 对应账户地址->ATA账户
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.1.15",
      "slot": 370370345
    },
    "value": {
      "amount": "100000000000",
      "decimals": 9,
      "uiAmount": 100.0, // amount /(10的decimals次方)
      "uiAmountString": "100" // uiAmount 转字符串
    }
  },
  "id": 1
}

注:查询地址为对应账户->对应Token(mint)->ATA账户地址

getTokenAccountsByDelegate

返回已获批准的代表的所有 SPL 代币账户

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenAccountsByDelegate",
    "params": [
      "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T",
      {
        "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
      },
      {
        "encoding": "jsonParsed"
      }
    ]
  }
'

getTokenAccountsByOwner

按代币所有者返回所有 SPL 代币账户

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenAccountsByOwner",
    "params": [
      "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5", // 这里是Keypair原地址
      {
        "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
      },
      {
        "encoding": "jsonParsed"
      }
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.2.3",
      "slot": 370371798
    },
    "value": [
      {
        "account": {
          "data": {
            "parsed": {
              "info": {
                "isNative": false,
                "mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT", // 代币标志位
                "owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
                "state": "initialized",
                "tokenAmount": {
                  "amount": "100000000000",
                  "decimals": 9,
                  "uiAmount": 100.0,
                  "uiAmountString": "100"
                }
              },
              "type": "account"
            },
            "program": "spl-token",
            "space": 165
          },
          "executable": false,
          "lamports": 2039280,
          "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "rentEpoch": 18446744073709551615,
          "space": 165
        },
        "pubkey": "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2" // ATA账户地址
      }
    ]
  },
  "id": 1
}

getTokenLargestAccounts

返回特定 SPL 代币类型的 20 个最大余额账户。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenLargestAccounts",
    "params": [
      "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT" // 代币mint地址
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.1.15",
      "slot": 370372669
    },
    "value": [
      {
        "address": "ChpLTT95FAZNVSeM79C7iun141Lp54dxjoURegsQAtZN",
        "amount": "100000000000",
        "decimals": 9,
        "uiAmount": 100.0,
        "uiAmountString": "100"
      },
      {
        "address": "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2",
        "amount": "100000000000",
        "decimals": 9,
        "uiAmount": 100.0,
        "uiAmountString": "100"
      }
    ]
  },
  "id": 1
}

getTokenSupply

返回 SPL 代币类型的总供应量

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTokenSupply",
    "params": [
      "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT"
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "context": {
      "apiVersion": "2.1.15",
      "slot": 370372872
    },
    "value": {
      "amount": "200000000000",
      "decimals": 9,
      "uiAmount": 200.0,
      "uiAmountString": "200"
    }
  },
  "id": 1
}

getTransaction

返回已确认交易的交易详情

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTransaction",
    "params": [
      "5cvy15goDmKA81kaMKk8nuuWuSN3AgCssjTK4qTvm49FyeZPoHXp5DjatqvSoUTDYArjio5PHE2LCuzBss9P7BDK",
      "json"
    ]
  }
'

返回

{
  "jsonrpc": "2.0",
  "result": {
    "blockTime": 1743069997,
    "meta": {
      "computeUnitsConsumed": 4648,
      "err": null,
      "fee": 5000,
      "innerInstructions": [],
      "loadedAddresses": {
        "readonly": [],
        "writable": []
      },
      "logMessages": [
        "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]",
        "Program log: Instruction: MintToChecked",
        "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4498 of 4648 compute units",
        "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success",
        "Program ComputeBudget111111111111111111111111111111 invoke [1]",
        "Program ComputeBudget111111111111111111111111111111 success"
      ],
      "postBalances": [
        4994414840,
        1461600,
        2039280,
        1,
        934087680
      ],
      "postTokenBalances": [
        {
          "accountIndex": 2,
          "mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
          "owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
          "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "uiTokenAmount": {
            "amount": "100000000000",
            "decimals": 9,
            "uiAmount": 100.0,
            "uiAmountString": "100"
          }
        }
      ],
      "preBalances": [
        4994419840,
        1461600,
        2039280,
        1,
        934087680
      ],
      "preTokenBalances": [
        {
          "accountIndex": 2,
          "mint": "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
          "owner": "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
          "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
          "uiTokenAmount": {
            "amount": "0",
            "decimals": 9,
            "uiAmount": null,
            "uiAmountString": "0"
          }
        }
      ],
      "rewards": [],
      "status": {
        "Ok": null
      }
    },
    "slot": 370161711,
    "transaction": {
      "message": {
        "accountKeys": [
          "8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5",
          "34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT",
          "3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2",
          "ComputeBudget111111111111111111111111111111",
          "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
        ],
        "header": {
          "numReadonlySignedAccounts": 0,
          "numReadonlyUnsignedAccounts": 2,
          "numRequiredSignatures": 1
        },
        "instructions": [
          {
            "accounts": [
              1,
              2,
              0
            ],
            "data": "ndcLHYJ5qd8F2",
            "programIdIndex": 4,
            "stackHeight": null
          },
          {
            "accounts": [],
            "data": "F7UDE7",
            "programIdIndex": 3,
            "stackHeight": null
          }
        ],
        "recentBlockhash": "BUuLwgC1zPLC1VcQ5xRrKSnDz2vheZaxW82GUJX59CLM"
      },
      "signatures": [
        "5cvy15goDmKA81kaMKk8nuuWuSN3AgCssjTK4qTvm49FyeZPoHXp5DjatqvSoUTDYArjio5PHE2LCuzBss9P7BDK"
      ]
    }
  },
  "id": 1
}

getTransactionCount

返回当前网络从创世以来所有的交易总数

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getTransactionCount"
  }
'

getVersion

返回当前查询RPC节点所运行的Solana 版本

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getVersion"
  }
'

getVoteAccounts

返回当前银行所有投票账户的账户信息和相关股份。

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getVoteAccounts",
    "params": [
      {
        "votePubkey": "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"
      }
    ]
  }
'

isBlockhashValid

查询指定块hash是否仍然有效

curl https://api.devnet.solana.com -s -X \
  POST -H "Content-Type: application/json" -d ' 
  {
    "jsonrpc": "2.0",
    "id": 45,
    "method": "isBlockhashValid",
    "params": [
      "J7rBdM6AecPDEZp8aPq5iPSNKVkU5Q76F3oAV4eW5wsW",
      {
        "commitment": "processed"
      }
    ]
  }
'

参考:https://solana.com/zh/docs/rpc/http

solana 交易 confirmationStatus 有几种状态

在 Solana 中,交易的 confirmationStatus 用于表示交易的确认状态,主要有以下几种状态:

1. finalized

  • 含义 :这是最高级别的确认状态。当交易达到 finalized 状态时,意味着该交易已经被集群中的大多数验证节点确认,并且不会被回滚。在区块链的共识机制下,这是一个非常安全的状态,表明交易已经被永久记录在区块链上。
  • 应用场景 :在需要确保交易不可逆转的场景中,如涉及资金转移、重要数据记录等,通常会等待交易达到 finalized 状态。

2. confirmed

  • 含义 :表示交易已经被集群中的一个验证节点确认。虽然交易处于 confirmed 状态,但仍然存在一定的风险,因为在某些情况下,该交易可能会被回滚。不过,这种情况相对较少发生。
  • 应用场景 :在一些对交易确认速度要求较高,而对交易最终确定性要求不是特别严格的场景中,可以使用 confirmed 状态。例如,在一些实时性要求较高的游戏或交互场景中,当用户完成操作后,可以快速告知用户交易已 confirmed ,让用户继续后续操作。

3. processed

  • 含义 :表示交易已经被集群接收并处理,但尚未被任何验证节点确认。这是交易确认过程中的初始状态,意味着交易已经进入了集群的处理流程,但还没有得到足够的确认。
  • 应用场景 :在开发过程中,当需要快速反馈交易是否已经被集群接收时,可以使用 processed 状态。例如,在用户提交交易后,立即告知用户交易已被接收并正在处理中。

代码示例

在使用 @solana/web3.js 进行交易时,可以指定 confirmationStatus 参数。以下是一个示例代码:

const { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } = require('@solana/web3.js');

// 连接到 Solana 网络
const connection = new Connection('https://api-devnet.solana.com', 'confirmed');

// 生成发送方和接收方的密钥对
const fromKeypair = Keypair.generate();
const toPublicKey = new PublicKey('...'); // 替换为实际的接收方公钥

// 创建交易
const transaction = new Transaction().add(
    SystemProgram.transfer({
        fromPubkey: fromKeypair.publicKey,
        toPubkey: toPublicKey,
        lamports: 1 * LAMPORTS_PER_SOL,
    })
);

// 发送并确认交易,指定 confirmationStatus 为 'finalized'
sendAndConfirmTransaction(connection, transaction, [fromKeypair], {
    commitment: 'finalized'
})
.then((signature) => {
    console.log(`交易已成功发送,签名: ${signature}`);
})
.catch((error) => {
    console.error('转账过程中出现错误:', error);
});


在上述代码中, commitment 参数指定了交易的确认状态,这里设置为 finalized ,表示等待交易达到最终确认状态。你可以根据实际需求将其设置为 confirmed 或 processed 。

Solana RPC 方法的常见 JSON 数据结构

各种 Solana RPC 方法将返回更复杂的响应作为结构化 JSON 对象,并填充特定的键值。
这些 JSON 数据结构中最常见的包括:

交易

交易与其他区块链上的交易截然不同。请务必查看交易剖析以了解 Solana 上的交易。

JSON

交易的JSON结构定义如下:

"transaction": {
  "message": {
    "accountKeys": [
      "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR",
      "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
      "11111111111111111111111111111111"
    ],
    "header": {
      "numReadonlySignedAccounts": 0,
      "numReadonlyUnsignedAccounts": 1,
      "numRequiredSignatures": 1
    },
    "instructions": [
      {
        "accounts": [
          0,
          1
        ],
        "data": "3Bxs411Dtc7pkFQj",
        "programIdIndex": 2,
        "stackHeight": null
      }
    ],
    "recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec"
  },
  "signatures": [
    "2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh"
  ]
}
  • message: <object> - 定义交易的内容。

  • accountKeys: <array[string]>- 交易使用的 base-58 编码公钥列表,包括指令和签名。第一个 message.header.numRequiredSignatures公钥必须签署交易。

  • header: <object>- 详细说明交易所需的账户类型和签名。

  • numRequiredSignatures: <number>- 使交易有效所需的签名总数。签名必须与第一个numRequiredSignatures签名相匹配message.accountKeys

  • numReadonlySignedAccounts: <number>- 最后 numReadonlySignedAccounts一个签名密钥是只读帐户。程序可以处理在单个 PoH 条目中加载只读帐户的多个交易,但不允许贷记或借记 lamport 或修改帐户数据。针对同一读写帐户的交易将按顺序进行评估。

  • numReadonlyUnsignedAccounts: <number>- 最后一个 numReadonlyUnsignedAccounts未签名的密钥是只读账户。

  • recentBlockhash: <string>- 账本中最近一个区块的 Base58 编码哈希值,用于防止交易重复并提供交易生命周期。

  • instructions: <array[object]> - 将按顺序执行并在一个原子事务中提交的程序指令列表(如果全部成功)。

  • programIdIndex: <number>- 数组索引,message.accountKeys指示执行该指令的程序账户。

  • accounts: <array[number]>- 数组中有序索引的列表, message.accountKeys指示要传递给程序的帐户。

  • data: <string>- 程序输入以 base-58 字符串编码的数据。

  • addressTableLookups: <array[object]|undefined> - 交易使用的地址表查找列表,用于从链上地址查找表中动态加载地址。如果maxSupportedTransactionVersion 未设置,则为未定义。

  • accountKey: <string>- 用于地址查找表帐户的 base-58 编码公钥。

  • writableIndexes: <array[number]>- 用于从查找表加载可写帐户地址的索引列表。

  • readonlyIndexes: <array[number]>- 用于从查找表加载只读帐户地址的索引列表。

  • signatures: <array[string]>- 应用于交易的 base-58 编码签名列表。列表的长度始终为 message.header.numRequiredSignatures且不为空。索引处的签名 对应于中i索引处的公钥。第一个用作 交易 IDi``message.accountKeys

JSON 解析

交易的 JSON 解析结构遵循与常规 JSON 格式类似的结构,并附加了帐户和指令信息的解析:

"transaction": {
  "message": {
    "accountKeys": [
      {
        "pubkey": "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR",
        "signer": true,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
        "signer": false,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "11111111111111111111111111111111",
        "signer": false,
        "source": "transaction",
        "writable": false
      }
    ],
    "instructions": [
      {
        "parsed": {
          "info": {
            "destination": "4LAyP5B5jNyNm7Ar2dG8sNipEiwTMEyCHd1iCHhhXYkY",
            "lamports": 100000000,
            "source": "EF3cbGuhJus5mCdGZkVz7GQce7QHbswBhZu6fmK9zkCR"
          },
          "type": "transfer"
        },
        "program": "system",
        "programId": "11111111111111111111111111111111",
        "stackHeight": null
      }
    ],
    "recentBlockhash": "6pw7JBwq9tb5GHiBQgVY6RAp5otbouwYvEc1kbbxKFec"
  },
  "signatures": [
    "2M8mvwhtxyz3vAokXESVeR9FQ4t9QQxF5ek6ENNBBHVkW5XyZvJVK5MQej5ccwTZH6iWBJJoZ2CcizBs89pvpPBh"
  ]
}
  • message: <object> - 定义交易的内容。

    • accountKeys: <array[object]> - 交易使用的账户信息列表。

    • pubkey: <string>- 账户的 Base58 编码公钥。

    • signer: <boolean>- 表明该账户是否是必需的交易签名者。

    • writable: <boolean>- 指示该帐户是否可写。

    • source: <string>- 账户来源(交易或查找表)。

    • recentBlockhash: <string>- 账本中最近一个区块的 Base58 编码哈希值,用于防止交易重复并提供交易生命周期。

    • instructions: <array[object]> - 已解析的程序指令列表。

    • program: <string>- 被调用的程序的名称。

    • programId: <string>- 该程序的 Base58 编码公钥。

    • stackHeight: <number|null>- 指令的堆栈高度。

    • parsed: <object> - 特定程序的解析数据。

    • type: <string>- 指令的类型(例如“转移”)。

    • info: <object>- 解析特定于程序和指令类型的指令信息。

  • signatures: <array[string]>- 应用于交易的 Base-58 编码签名列表。

交易状态元数据

{
  "meta": {
    "err": null,
    "fee": 5000,
    "innerInstructions": [],
    "logMessages": [],
    "postBalances": [499998932500, 26858640, 1, 1, 1],
    "postTokenBalances": [],
    "preBalances": [499998937500, 26858640, 1, 1, 1],
    "preTokenBalances": [],
    "rewards": null,
    "status": {
      "Ok": null
    }
  }
}
  • err: <object|null>- 如果交易失败则返回错误,如果交易成功则返回 null。TransactionError 定义

  • fee: <u64>- 本次交易收取的费用,以 u64 整数形式

  • preBalances: <array>- 交易处理前的 u64 账户余额数组

  • postBalances: <array>- 交易处理后的 u64 账户余额数组

  • innerInstructions: <array|null>-内部指令列表 或null 此交易期间是否未启用内部指令记录

  • preTokenBalances: <array|undefined>- 交易处理之前的代币余额列表 ,如果在此交易期间尚未启用代币余额记录,则省略

  • postTokenBalances: <array|undefined>- 交易处理后的代币余额列表 ,如果在此交易期间尚未启用代币余额记录,则省略

  • logMessages: <array|null>- 字符串日志消息数组或null如果在此事务期间未启用日志消息记录

  • rewards: <array|null> - 交易级奖励;包含以下内容的 JSON 对象数组:

    • pubkey: <string>- 收到奖励的账户的公钥,以 base58 编码的字符串形式
    • lamports: <i64>- 账户记入或扣除的奖励灯数量,以 i64 表示
    • postBalance: <u64>- 奖励使用后 lampors 账户余额
    • rewardType: <string|undefined>- 奖励类型:“费用”、“租金”、“投票”、“质押”
    • commission: <u8|undefined>- 奖励到账时投票账户佣金,仅适用于投票和质押奖励
  • 已弃用:

    • status: <object> - 交易状态
      • "Ok": <null>- 交易成功
      • "Err": <ERR>- 交易失败,出现 TransactionError
  • loadedAddresses: <object|undefined> - 从地址查找表加载交易地址。如果 axSupportedTransactionVersion 请求参数中未设置,或者 jsonParsed 请求参数中设置了编码,则未定义。

    • writable: <array[string]>- 可写加载账户的 Base58 编码地址的有序列表
    • readonly: <array[string]>- 只读加载帐户的 Base58 编码地址的有序列表
  • returnData: <object|undefined> - 交易中指令生成的最新返回数据,包含以下字段:

    • programId: <string>- 生成返回数据的程序,以 base-58 编码的公钥形式
    • data: <[string, encoding]>- 返回数据本身,以 base64 编码的二进制数据
  • computeUnitsConsumed: <u64|undefined>-交易消耗的计算单元数量

  • version: <"legacy"|number|undefined>- 交易版本。如果 maxSupportedTransactionVersion请求参数中未设置,则为未定义。

  • signatures: <array>- 如果请求“签名”以获取交易详细信息,则存在;签名字符串数组,对应于块中的交易顺序

内部指示

Solana 运行时记录在交易处理期间调用的跨程序指令,并使这些指令可用,从而提高每个交易指令在链上执行内容的透明度。调用的指令按原始交易指令分组,并按处理顺序列出。

内部指令的 JSON 结构定义为以下结构的对象列表

"innerInstructions": [
  {
    "index": 0,
    "instructions": [
      {
        "accounts": [
          0,
          1,
          2
        ],
        "data": "WPNHsFPyEMr",
        "programIdIndex": 3,
        "stackHeight": 2
      },
      {
        "accounts": [
          0,
          1
        ],
        "data": "11111dBUPbGETd4QtNMQVg8HqgcZtKy6DcJm6R4TZufJkuhkDS47VsauCCGhLf2xrm5BQ",
        "programIdIndex": 2,
        "stackHeight": 3
      }
    ]
  }
]

参考交易:

"transaction": {
  "message": {
    "accountKeys": [
      "4kh6HxYZiAebF8HWLsUWod2EaQQ6iWHpHYCz8UcmFbM1",
      "Bpo7aaM9kqfCjM6JgZCSdev7HsaUFj51mBPPEhQcDpUR",
      "11111111111111111111111111111111",
      "8HupNBr7SBhBLcBsLhbtes3tCarBm6Bvpqp5AfVjHuj8",
      "GENmb1D59wqCKRwujq4PJ8461EccQ5srLHrXyXp4HMTH"
    ],
    "header": {
      "numReadonlySignedAccounts": 0,
      "numReadonlyUnsignedAccounts": 3,
      "numRequiredSignatures": 2
    },
    "instructions": [
      {
        "accounts": [
          0,
          1,
          2,
          3
        ],
        "data": "H2ot5wbZsmL",
        "programIdIndex": 4,
        "stackHeight": null
      }
    ],
    "recentBlockhash": "28CroH2jyCaCFF6ssyUK989zBZY6dBxnUNU9A4oPUbER"
  },
  "signatures": [
    "4i4EuRQ1sNzKWEBYwg28VAMkQbaAeHyRRwu1tQRksowtQhGRJtgaHXpDAhBfpYZnVodGoQYwiUiB5yBRDoWbZ7VH",
    "2dipFcFF4CvwtbCFbRdctQmyzAYcq8RWrLryZErbKPhnriCJ6wDmKfJoSJfDjFNzUEcJDKkfasS2pcjvGEUjdYN6"
  ]
}
  • index: number- 内部指令来源的交易指令索引

  • instructions: <array[object]>- 在单个事务指令期间调用的内部程序指令的有序列表。

    • programIdIndex: <number>- 数组索引,message.accountKeys指示执行该指令的程序账户。
    • accounts: <array[number]>- 数组中有序索引的列表, message.accountKeys指示要传递给程序的帐户。
    • data: <string>- 程序输入以 base-58 字符串编码的数据。

代币余额

"postTokenBalances": [
  {
    "accountIndex": 1,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "0",
      "decimals": 2,
      "uiAmount": null,
      "uiAmountString": "0"
    }
  },
  {
    "accountIndex": 2,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "8xm9beCpBH7SgqRz1mKua7KJF52whAVCiDEV1qREGHNV",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "100",
      "decimals": 2,
      "uiAmount": 1.0,
      "uiAmountString": "1"
    }
  }
],

"preTokenBalances": [
  {
    "accountIndex": 1,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "100",
      "decimals": 2,
      "uiAmount": 1.0,
      "uiAmountString": "1"
    }
  },
  {
    "accountIndex": 2,
    "mint": "6HvU8PbqP3nZLkFF59rr2zkTHqetPLgb6NnxKZLHQxNp",
    "owner": "8xm9beCpBH7SgqRz1mKua7KJF52whAVCiDEV1qREGHNV",
    "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
    "uiTokenAmount": {
      "amount": "0",
      "decimals": 2,
      "uiAmount": null,
      "uiAmountString": "0"
    }
  }
]

参考交易:

"transaction": {
  "message": {
    "accountKeys": [
      {
        "pubkey": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
        "signer": true,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "39nzuQ2WYHf231DJRPt1TLfaXSWXEKYGcqP3NQf6zK7G",
        "signer": false,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "DtCPWGmvCTov7CNmNTx8AFe3SEFSxgy265kZawv8SVL3",
        "signer": false,
        "source": "transaction",
        "writable": true
      },
      {
        "pubkey": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
        "signer": false,
        "source": "transaction",
        "writable": false
      }
    ],
    "addressTableLookups": [],
    "instructions": [
      {
        "parsed": {
          "info": {
            "amount": "100",
            "authority": "DKypunNAGLPGBj3SocY8fF4ZrnDNVTf6QcUyW4trvkB",
            "destination": "DtCPWGmvCTov7CNmNTx8AFe3SEFSxgy265kZawv8SVL3",
            "source": "39nzuQ2WYHf231DJRPt1TLfaXSWXEKYGcqP3NQf6zK7G"
          },
          "type": "transfer"
        },
        "program": "spl-token",
        "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
        "stackHeight": null
      }
    ],
    "recentBlockhash": "42mjf871LtzDK8NVZUAht1xBoCvNMagQGSM7BnFWZD6M"
  },
  "signatures": [
    "5ZQqsF4tTFJDR5vuNJxejtw2GMc8KEtnPXnQjwhGzAtdbPTKtrLfPkFAbBTyPjZSVB3CbR5BiP5S8zAfZNtuwh88"
  ]
}

代币余额的 JSON 结构定义为以下结构的对象列表:

  • accountIndex: <number>- 提供代币余额的账户索引。

  • mint: <string>- 代币铸币厂的公钥。

  • owner: <string|undefined>- 代币余额所有者的公钥。

  • programId: <string|undefined>- 拥有该账户的Token程序的公钥。

  • uiTokenAmount: <object>

    • amount: <string>- 代币的原始数量为字符串,忽略小数。
    • decimals: <number>- 为代币铸造配置的小数位数。
    • uiAmount: <number|null>- 代币数量为浮点数,包含小数。已弃用
    • uiAmountString: <string>- Token数量为字符串,保留小数位。

原文:https://solana.com/zh/docs/rpc/json-structures

使用 spl-token 命令行工具测试Solana代币发行

安装 CLI

安装Solana CLI

sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"

添加环境变量

export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
sudo vi /etc/profile
export PATH=$PATH:/usr/local/go/bin
source /etc/profile

设置集群

solana config set --url https://api.devnet.solana.com

领取测试代币

solana airdrop 5

创建代币

spl-token create-token

返回

Creating token 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT under program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA

Address:  34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT
Decimals:  9

Signature: adBFA3wdYahsRPZGWuHhFMSZ83D7Znr9M5D7SKtif31ArQWRS5HdzXa1ifEbtNZHNRmnPSce91R96fK9BdE6PwP

https://explorer.solana.com/tx/adBFA3wdYahsRPZGWuHhFMSZ83D7Znr9M5D7SKtif31ArQWRS5HdzXa1ifEbtNZHNRmnPSce91R96fK9BdE6PwP?cluster=devnet

  • 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 是部署时临时创建的一个Keypair,这个作为当前创建Token的唯一标识符
  • 8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5 是当前部署的地址,

js代码

import { createMint } from '@solana/spl-token';
import { clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js';

const payer = Keypair.generate();
const mintAuthority = Keypair.generate();
const freezeAuthority = Keypair.generate();

const connection = new Connection(
  clusterApiUrl('devnet'),
  'confirmed'
);

const mint = await createMint(
  connection,
  payer,
  mintAuthority.publicKey,
  freezeAuthority.publicKey,
  9 // We are using 9 to match the CLI decimal default exactly
);

console.log(mint.toBase58());
// 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT

刚部署完,没有供应量,因为还没有mint

spl-token supply 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT

js代码

const mintInfo = await getMint(
  connection,
  mint
)

console.log(mintInfo.supply);
// 0

创建代币持有ATA账户

spl-token create-account 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT

返回

Creating account 3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2

Signature: 3jK2bbob5JqpAiXxUFTqDfKJZMZ5kpgUASVUNaWi8dp3N9bsZPEXba2rB4E9KUyUUYCmAFuYQwB7uRXGQihQfvG7

https://solscan.io/tx/3jK2bbob5JqpAiXxUFTqDfKJZMZ5kpgUASVUNaWi8dp3N9bsZPEXba2rB4E9KUyUUYCmAFuYQwB7uRXGQihQfvG7?cluster=devnet

对应js代码

const tokenAccount = await getOrCreateAssociatedTokenAccount(
  connection,
  payer,
  mint,
  payer.publicKey
)

console.log(tokenAccount.address.toBase58());
// 3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2

查询当前账户的余额,还没mint前,余额是0

spl-token balance 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT

查询的账户地址是 mint地址

铸造100个

spl-token mint 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 100

返回

Minting 100 tokens
  Token: 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT
  Recipient: 3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2

Signature: 37LD7EVARr5vtA5kHLMmjXk176gxXhTakNdRnFZ4u7GKWA62oAnhtix2xBAYoyNhWECPhdvyqf1XyZBupGqoqvzF

命令行自动计算的ATA地址,逻辑简单来说
交易fee提供地址(8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5)-> 调用 SPL Token程序(TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)-> 根据Token唯一标志(mint 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT)找到对应的Token->mint 100个代币-> 发送给交易Fee地址(8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5)的ATA账户地址(3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2)

对应的js代码

await mintTo(
  connection,
  payer,
  mint,
  tokenAccount.address,
  mintAuthority,
  100000000000 // because decimals for the mint are set to 9 
)

查询余额

spl-token balance 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT

返回 100
命令行会自动根据当前交易地址,计算对应的ATA地址,并查询余额

js代码

const tokenAccountInfo = await getAccount(
  connection,
  tokenAccount.address
)

console.log(tokenAccountInfo.amount);
// 0

查询已发行量

spl-token supply 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT

返回 100

查看拥有的所有代币

spl-token accounts
Token                                         Balance
-----------------------------------------------------
34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT  100

js 代码

import {AccountLayout, TOKEN_PROGRAM_ID} from "@solana/spl-token";
import {clusterApiUrl, Connection, PublicKey} from "@solana/web3.js";

(async () => {

  const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

  const tokenAccounts = await connection.getTokenAccountsByOwner(
    new PublicKey('8YLKoCu7NwqHNS8GzuvA2ibsvLrsg22YMfMDafxh1B15'),
    {
      programId: TOKEN_PROGRAM_ID,
    }
  );

  console.log("Token                                         Balance");
  console.log("------------------------------------------------------------");
  tokenAccounts.value.forEach((tokenAccount) => {
    const accountData = AccountLayout.decode(tokenAccount.account.data);
    console.log(`${new PublicKey(accountData.mint)}   ${accountData.amount}`);
  })

})();

将 SOL 包装在代币中(SOL->WSOL)

spl-token wrap 1
Wrapping 1 SOL into F3XA5sEfzS5AJbZ8733YygzcGV3UKizrGS2FteHjwemB

Signature: 3BNYUpykfpcDjFJnJMkzXPKG5Rqego3ypLx9yRfRFxKXhR25WQtGxLhyT65VtPv72bpsShBDM2b7Swuy1LwwzvkL

js代码

import {NATIVE_MINT, createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, createSyncNativeInstruction, getAccount} from "@solana/spl-token";
import {clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL, SystemProgram, Transaction, sendAndConfirmTransaction} from "@solana/web3.js";

(async () => {

const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

const wallet = Keypair.generate();

const airdropSignature = await connection.requestAirdrop(
  wallet.publicKey,
  2 * LAMPORTS_PER_SOL,
);

await connection.confirmTransaction(airdropSignature);

const associatedTokenAccount = await getAssociatedTokenAddress(
  NATIVE_MINT,
  wallet.publicKey
)

// Create token account to hold your wrapped SOL
const ataTransaction = new Transaction()
  .add(
    createAssociatedTokenAccountInstruction(
      wallet.publicKey,
      associatedTokenAccount,
      wallet.publicKey,
      NATIVE_MINT
    )
  );

await sendAndConfirmTransaction(connection, ataTransaction, [wallet]);

// Transfer SOL to associated token account and use SyncNative to update wrapped SOL balance
const solTransferTransaction = new Transaction()
  .add(
    SystemProgram.transfer({
        fromPubkey: wallet.publicKey,
        toPubkey: associatedTokenAccount,
        lamports: LAMPORTS_PER_SOL
      }),
      createSyncNativeInstruction(
        associatedTokenAccount
    )
  )

await sendAndConfirmTransaction(connection, solTransferTransaction, [wallet]);

const accountInfo = await getAccount(connection, associatedTokenAccount);

console.log(`Native: ${accountInfo.isNative}, Lamports: ${accountInfo.amount}`);

})();

逻辑上简单说就是给对应的NATIVE_MINT所属ATA地址转SOL

WSOL ->SOL 赎回

spl-token unwrap F3XA5sEfzS5AJbZ8733YygzcGV3UKizrGS2FteHjwemB
Unwrapping F3XA5sEfzS5AJbZ8733YygzcGV3UKizrGS2FteHjwemB
  Amount: 1 SOL
  Recipient: 8TKG1ez28ZYzNgGTein6Pc97yvxWwnHecVL9ZhdZTxd5


Signature: 3dsKnJHHFhBqUxeVf2JLX5WnGmBFqhy2hEC5jeGBhBHFFKQMWmVtVMCcoyjSdBTF8Y61eArYYizjZVT9yp3TXKhZ

代币转移

逻辑简单来说
发送From账户地址的ATA账户->给接收To地址对应的ATA地址进行转账

spl-token transfer 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 50 C1XZNEpofMrDmV17SNzyqaygGo82yVPvGYNQFufMEZCd

如果接收地址没有该代币的ATA,需要添加 --allow-unfunded-recipient,已表明为该ATA提供创建租金

spl-token transfer 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT 50 C1XZNEpofMrDmV17SNzyqaygGo82yVPvGYNQFufMEZCd --allow-unfunded-recipient --fund-recipient

js代码

import { clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js';
import { createMint, getOrCreateAssociatedTokenAccount, mintTo, transfer } from '@solana/spl-token';

(async () => {
    // Connect to cluster
    const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

    // Generate a new wallet keypair and airdrop SOL
    const fromWallet = Keypair.generate();
    const fromAirdropSignature = await connection.requestAirdrop(fromWallet.publicKey, LAMPORTS_PER_SOL);

    // Wait for airdrop confirmation
    await connection.confirmTransaction(fromAirdropSignature);

    // Generate a new wallet to receive newly minted token
    const toWallet = Keypair.generate();

    // Create new token mint
    const mint = await createMint(connection, fromWallet, fromWallet.publicKey, null, 9);

    // Get the token account of the fromWallet address, and if it does not exist, create it
    const fromTokenAccount = await getOrCreateAssociatedTokenAccount(
        connection,
        fromWallet,
        mint,
        fromWallet.publicKey
    );

    // Get the token account of the toWallet address, and if it does not exist, create it
    const toTokenAccount = await getOrCreateAssociatedTokenAccount(connection, fromWallet, mint, toWallet.publicKey);

    // Mint 1 new token to the "fromTokenAccount" account we just created
    let signature = await mintTo(
        connection,
        fromWallet,
        mint,
        fromTokenAccount.address,
        fromWallet.publicKey,
        1000000000
    );
    console.log('mint tx:', signature);

    // Transfer the new token to the "toTokenAccount" we just created
    signature = await transfer(
        connection,
        fromWallet,
        fromTokenAccount.address,
        toTokenAccount.address,
        fromWallet.publicKey,
        50
    );
})();

查询余额

查询本地账户下

spl-token accounts 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT -v
Program                                       Account                                       Delegated  Close Authority  Balance
-------------------------------------------------------------------------------------------------------------------------------
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA   3tykH6gHjjyqYHBigukDXZaj3K6XupjKxYLMA2Srfbc2                              100

js 代码

import {getAccount, createMint, createAccount, mintTo, getOrCreateAssociatedTokenAccount, transfer} from "@solana/spl-token";
import {clusterApiUrl, Connection, Keypair, LAMPORTS_PER_SOL} from "@solana/web3.js";

(async () => {

  const connection = new Connection(clusterApiUrl('devnet'), 'confirmed');

  const wallet = Keypair.generate();
  const auxiliaryKeypair = Keypair.generate();

  const airdropSignature = await connection.requestAirdrop(
    wallet.publicKey,
    LAMPORTS_PER_SOL,
  );

  await connection.confirmTransaction(airdropSignature);

  const mint = await createMint(
    connection,
    wallet,
    wallet.publicKey,
    wallet.publicKey,
    9
  );

  // Create custom token account
  const auxiliaryTokenAccount = await createAccount(
    connection,
    wallet,
    mint,
    wallet.publicKey,
    auxiliaryKeypair
  );

  const associatedTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    wallet,
    mint,
    wallet.publicKey
  );

  await mintTo(
    connection,
    wallet,
    mint,
    associatedTokenAccount.address,
    wallet,
    50
  );

  const accountInfo = await getAccount(connection, associatedTokenAccount.address);

  console.log(accountInfo.amount);
  // 50

  await transfer(
    connection,
    wallet,
    associatedTokenAccount.address,
    auxiliaryTokenAccount,
    wallet,
    50
  );

  const auxAccountInfo = await getAccount(connection, auxiliaryTokenAccount);

  console.log(auxAccountInfo.amount);
  // 50
})();

创建非同质化代币

创建小数点后为零的 token 类型,
流程与上面的一致

禁用mint

spl-token authorize 34GbKbRzLMfvpHEFywhe2KyheKMMxab8tQAFj5t2rBYT mint --disable

js代码

let transaction = new Transaction()
  .add(createSetAuthorityInstruction(
    mint,
    wallet.publicKey,
    AuthorityType.MintTokens,
    null
  ));

await web3.sendAndConfirmTransaction(connection, transaction, [wallet]);

将mint权限更新为null

多重签名的使用

引用多重签名账户时命令行用法的主要区别spl-token在于指定--owner参数。通常,此参数指定的签名者直接提供授予其权限的签名,但在多重签名的情况下,它仅指向多重签名账户的地址。然后由参数指定的多重签名签名者集成员提供签名 --multisig-signer

任何拥有 SPL Token 铸币或代币账户的机构都可以使用多重签名账户。{

  • 铸币账户铸币权限: spl-token mint ...spl-token authorize ... mint ...
  • Mint账户 冻结权限: spl-token freeze ...,, spl-token thaw ...``spl-token authorize ... freeze ...
  • Token 账户 所有者 权限 : spl-token transfer ...,,,,,,, spl-token approve ...``spl-token revoke ...``spl-token burn ...``spl-token wrap ...``spl-token unwrap ...``spl-token authorize ... owner ...
  • Token 账户关闭权限: spl-token close ..., spl-token authorize ... close ...

}

使用多重签名的主要区别在于将所有者指定为多重签名密钥,并在构建交易时提供签名者列表。通常,您会提供有权运行交易的签名者作为所有者,但在多重签名的情况下,所有者将是多重签名密钥。

任何拥有 SPL Token 铸币或代币账户的机构都可以使用多重签名账户。

{

  • 铸币账户铸币权限: createMint(/* ... */, mintAuthority: multisigKey, /* ... */)
  • Mint账户冻结权限: createMint(/* ... */, freezeAuthority: multisigKey, /* ... */)
  • 代币账户所有者权限: getOrCreateAssociatedTokenAccount(/* ... */, mintAuthority: multisigKey, /* ... */)
  • Token账户关闭权限: closeAccount(/* ... */, authority: multisigKey, /* ... */)

}

示例:具有多重签名权限的 Mint

首先创建密钥对作为多重签名者集。实际上,这些可以是任何受支持的签名者,例如:Ledger 硬件钱包、密钥对文件或纸钱包。为方便起见,本示例将使用生成的密钥对。

$ for i in $(seq 3); do solana-keygen new --no-passphrase -so "signer-${i}.json"; done
Wrote new keypair to signer-1.json
Wrote new keypair to signer-2.json
Wrote new keypair to signer-3.json
const signer1 = Keypair.generate();
const signer2 = Keypair.generate();
const signer3 = Keypair.generate();

为了创建多重签名账户,必须收集签名者集的公钥。

$ for i in $(seq 3); do SIGNER="signer-${i}.json"; echo "$SIGNER: $(solana-keygen pubkey "$SIGNER")"; done
signer-1.json: BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ
signer-2.json: DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY
signer-3.json: D7ssXHrZJjfpZXsmDf8RwfPxe1BMMMmP1CtmX3WojPmG
console.log(signer1.publicKey.toBase58());
console.log(signer2.publicKey.toBase58());
console.log(signer3.publicKey.toBase58());
/*
  BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ
  DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY
  D7ssXHrZJjfpZXsmDf8RwfPxe1BMMMmP1CtmX3WojPmG
 */

现在可以使用子命令创建多重签名帐户。其第一个位置参数是必须签署影响此多重签名帐户控制的 token/mint 帐户的交易的spl-token create-multisig 最小签名者数量 ( )。其余位置参数是允许 ( ) 为多重签名帐户签名的所有密钥对的公钥。此示例将使用“2 of 3”多重签名帐户。也就是说,三个允许的密钥对中的两个必须签署所有交易。M``N

注意:SPL Token Multisig 帐户仅限于 11 个签名者(1 {'<='} N{'<='} 11),并且最低签名者数量不得超过N(1 {'<='} M{'<='} N

$ spl-token create-multisig 2 BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY D7ssXHrZJjfpZXsmDf8RwfPxe1BMMMmP1CtmX3WojPmG
Creating 2/3 multisig 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re
Signature: 2FN4KXnczAz33SAxwsuevqrD1BvikP6LUhLie5Lz4ETt594X8R7yvMZzZW2zjmFLPsLQNHsRuhQeumExHbnUGC9A
const multisigKey = await createMultisig(
  connection,
  payer,
  [
    signer1.publicKey,
    signer2.publicKey,
    signer3.publicKey
  ],
  2
);

console.log(`Created 2/3 multisig ${multisigKey.toBase58()}`);
// Created 2/3 multisig 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re

接下来,按照前面描述的方式创建代币铸造账户和接收账户 ,并将铸造账户的铸造权限设置为多重签名账户

$ spl-token create-token
Creating token 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
Signature: 3n6zmw3hS5Hyo5duuhnNvwjAbjzC42uzCA3TTsrgr9htUonzDUXdK1d8b8J77XoeSherqWQM8mD8E1TMYCpksS2r

$ spl-token create-account 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
Creating account EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
Signature: 5mVes7wjE7avuFqzrmSCWneKBQyPAjasCLYZPNSkmqmk2YFosYWAP9hYSiZ7b7NKpV866x5gwyKbbppX3d8PcE9s

$ spl-token authorize 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o mint 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re
Updating 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Current mint authority: 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
  New mint authority: 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re
Signature: yy7dJiTx1t7jvLPCRX5RQWxNRNtFwvARSfbMJG94QKEiNS4uZcp3GhhjnMgZ1CaWMWe4jVEMy9zQBoUhzomMaxC
const mint = await createMint(
    connection,
    payer,
    multisigKey,
    multisigKey,
    9
  );

const associatedTokenAccount = await getOrCreateAssociatedTokenAccount(
  connection,
  payer,
  mint,
  signer1.publicKey
);

为了证明铸币账户现在处于多重签名账户的控制之下,尝试使用一个多重签名者进行铸币会失败

$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
--multisig-signer signer-1.json
Minting 1 tokens
  Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: missing required signature for instruction
try {
  await mintTo(
    connection,
    payer,
    mint,
    associatedTokenAccount.address,
    multisigKey,
    1
  )
} catch (error) {
  console.log(error);
}
// Error: Signature verification failed

但使用第二个多重签名者重复操作,成功了

$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
--multisig-signer signer-1.json \
--multisig-signer signer-2.json
Minting 1 tokens
  Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
Signature: 2ubqWqZb3ooDuc8FLaBkqZwzguhtMgQpgMAHhKsWcUzjy61qtJ7cZ1bfmYktKUfnbMYWTC1S8zdKgU6m4THsgspT
await mintTo(
  connection,
  payer,
  mint,
  associatedTokenAccount.address,
  multisigKey,
  1,
  [
    signer1,
    signer2
  ]
)

const mintInfo = await getMint(
  connection,
  mint
)

console.log(`Minted ${mintInfo.supply} token`);
// Minted 1 token

示例:使用多重签名进行离线签名

有时在线签名不可行或不受欢迎。例如,当签名者不在同一地理位置,或使用未连接到网络的隔离设备时。在这种情况下,我们使用离线签名,它将前面的多重签名示例与离线签名nonce 帐户结合起来。

此示例将使用与在线示例相同的 mint 帐户、token 帐户、多重签名帐户和多重签名者设置密钥对文件名,以及我们在此处创建的 nonce 帐户:

$ solana-keygen new -o nonce-keypair.json
...
======================================================================
pubkey: Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj
======================================================================
$ solana create-nonce-account nonce-keypair.json 1
Signature: 3DALwrAAmCDxqeb4qXZ44WjpFcwVtgmJKhV4MW5qLJVtWeZ288j6Pzz1F4BmyPpnGLfx2P8MEJXmqPchX5y2Lf3r
$ solana nonce-account Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj
Balance: 0.01 SOL
Minimum Balance Required: 0.00144768 SOL
Nonce blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
Fee: 5000 lamports per signature
Authority: 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
const connection = new Connection(
  clusterApiUrl('devnet'),
  'confirmed',
);

const onlineAccount = Keypair.generate();
const nonceAccount = Keypair.generate();

const minimumAmount = await connection.getMinimumBalanceForRentExemption(
  NONCE_ACCOUNT_LENGTH,
);

// Form CreateNonceAccount transaction
const transaction = new Transaction()
  .add(
  SystemProgram.createNonceAccount({
    fromPubkey: onlineAccount.publicKey,
    noncePubkey: nonceAccount.publicKey,
    authorizedPubkey: onlineAccount.publicKey,
    lamports: minimumAmount,
  }),
);

await web3.sendAndConfirmTransaction(connection, transaction, [onlineAccount, nonceAccount])

const nonceAccountData = await connection.getNonce(
  nonceAccount.publicKey,
  'confirmed',
);

console.log(nonceAccountData);
/*
NonceAccount {
  authorizedPubkey: '5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE'
  nonce: '6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E',
  feeCalculator: { lamportsPerSignature: 5000 }
}
 */

对于费用支付者和随机数授权者角色, 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE将使用本地热钱包。

首先,通过指定所有签名者的公钥来构建模板命令。运行此命令后,所有签名者将在输出中列为“缺席签名者”。每个离线签名者都将运行此命令以生成相应的签名。

注意:该参数的参数--blockhash是来自指定持久随机数账户的“Nonce blockhash:”字段。

$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
--multisig-signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
--multisig-signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY \
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
--fee-payer 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
--nonce-authority 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
--sign-only \
--mint-decimals 9
Minting 1 tokens
  Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC

Blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
Absent Signers (Pubkey):
 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
 BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ
 DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY

接下来,每个离线签名者执行模板命令,用相应的密钥对替换他们的公钥的每个实例。

$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
--multisig-signer signer-1.json \
--multisig-signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY \
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
--fee-payer 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
--nonce-authority 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
--sign-only \
--mint-decimals 9
Minting 1 tokens
  Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC

Blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
Signers (Pubkey=Signature):
 BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ=2QVah9XtvPAuhDB2QwE7gNaY962DhrGP6uy9zeN4sTWvY2xDUUzce6zkQeuT3xg44wsgtUw2H5Rf8pEArPSzJvHX
Absent Signers (Pubkey):
 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
 DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY
$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
--multisig-signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
--multisig-signer signer-2.json \
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
--fee-payer 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
--nonce-authority 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
--sign-only \
--mint-decimals 9
Minting 1 tokens
  Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC

Blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
Signers (Pubkey=Signature):
 DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY=2brZbTiCfyVYSCp6vZE3p7qCDeFf3z1JFmJHPBrz8SnWSDZPjbpjsW2kxFHkktTNkhES3y6UULqS4eaWztLW7FrU
Absent Signers (Pubkey):
 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
 BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ

最后,离线签名者将Pubkey=Signature其命令输出的配对传达给将交易广播到集群的一方。广播方随后在修改模板命令后运行该命令,如下所示:

  1. 用其密钥对替换任何相应的公钥(在此示例中--fee-payer ... 为)--nonce-authority ...
  2. 删除--sign-only参数,如果是mint子命令,则--mint-decimals ...删除将从集群中查询的参数
  3. --signer通过参数将离线签名添加到模板命令
$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
--multisig-signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
--multisig-signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY \
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
--fee-payer hot-wallet.json \
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
--nonce-authority hot-wallet.json \
--signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ=2QVah9XtvPAuhDB2QwE7gNaY962DhrGP6uy9zeN4sTWvY2xDUUzce6zkQeuT3xg44wsgtUw2H5Rf8pEArPSzJvHX \
--signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY=2brZbTiCfyVYSCp6vZE3p7qCDeFf3z1JFmJHPBrz8SnWSDZPjbpjsW2kxFHkktTNkhES3y6UULqS4eaWztLW7FrU
Minting 1 tokens
  Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
  Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
Signature: 2AhZXVPDBVBxTQLJohyH1wAhkkSuxRiYKomSSXtwhPL9AdF3wmhrrJGD7WgvZjBPLZUFqWrockzPp9S3fvzbgicy

首先使用 nonceAccountInformation 和 tokenAccount 密钥构建原始交易。交易的所有签名者都被视为原始交易的一部分。此交易稍后将交给签名者进行签名。

const nonceAccountInfo = await connection.getAccountInfo(
  nonceAccount.publicKey,
  'confirmed'
);

const nonceAccountFromInfo = web3.NonceAccount.fromAccountData(nonceAccountInfo.data);

console.log(nonceAccountFromInfo);

const nonceInstruction = web3.SystemProgram.nonceAdvance({
  authorizedPubkey: onlineAccount.publicKey,
  noncePubkey: nonceAccount.publicKey
});

const nonce = nonceAccountFromInfo.nonce;

const mintToTransaction = new web3.Transaction({
  feePayer: onlineAccount.publicKey,
  nonceInfo: {nonce, nonceInstruction}
})
  .add(
    createMintToInstruction(
      mint,
      associatedTokenAccount.address,
      multisigkey,
      1,
      [
        signer1,
        onlineAccount
      ],
      TOKEN_PROGRAM_ID
    )
  );

接下来,每个离线签名者将获取交易缓冲区并使用其相应的密钥对其进行签名。

let mintToTransactionBuffer = mintToTransaction.serializeMessage();

let onlineSIgnature = nacl.sign.detached(mintToTransactionBuffer, onlineAccount.secretKey);
mintToTransaction.addSignature(onlineAccount.publicKey, onlineSIgnature);

// Handed to offline signer for signature
let offlineSignature = nacl.sign.detached(mintToTransactionBuffer, signer1.secretKey);
mintToTransaction.addSignature(signer1.publicKey, offlineSignature);

let rawMintToTransaction = mintToTransaction.serialize();

最后,热钱包将接收交易、对其进行序列化并将其广播到网络。

// Send to online signer for broadcast to network
await web3.sendAndConfirmRawTransaction(connection, rawMintToTransaction);

JSON RPC 方法

有一组丰富的 JSON RPC 方法可用于 SPL Token:

  • getTokenAccountBalance
  • getTokenAccountsByDelegate
  • getTokenAccountsByOwner
  • getTokenLargestAccounts
  • getTokenSupply

有关更多详细信息,请参阅https://docs.solana.com/apps/jsonrpc-api。

getProgramAccounts此外,可以以多种方式采用多功能JSON RPC 方法来获取感兴趣的 SPL 代币账户。

查找特定铸币厂的所有代币账户

查找TESTpKgj42ya3st2SQTKiANjTBmncQSCqLAZGcSPLGM铸币厂的所有代币账户:

curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        "encoding": "jsonParsed",
        "filters": [
          {
            "dataSize": 165
          },
          {
            "memcmp": {
              "offset": 0,
              "bytes": "TESTpKgj42ya3st2SQTKiANjTBmncQSCqLAZGcSPLGM"
            }
          }
        ]
      }
    ]
  }
'

过滤"dataSize": 165器选择所有代币账户,然后"memcmp": ...过滤器根据 每个代币账户内的铸币 地址进行选择。

查找钱包的所有代币账户

查找用户拥有的所有代币账户vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg

curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getProgramAccounts",
    "params": [
      "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      {
        "encoding": "jsonParsed",
        "filters": [
          {
            "dataSize": 165
          },
          {
            "memcmp": {
              "offset": 32,
              "bytes": "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg"
            }
          }
        ]
      }
    ]
  }
'

过滤"dataSize": 165器选择所有代币账户,然后"memcmp": ...过滤器根据 每个代币账户内的所有者 地址进行选择。

运营概况

创建新的令牌类型

可以通过使用指令初始化新的 Mint 来创建新的代币类型 InitializeMint。Mint 用于创建或“铸造”新代币,这些代币存储在帐户中。Mint 与每个帐户相关联,这意味着特定代币类型的总供应量等于所有关联帐户的余额。

需要注意的是,该InitializeMint指令不需要 Solana 帐户初始化,也不需要签名者。该InitializeMint 指令应与创建 Solana 帐户的系统指令一起进行原子处理,方法是将两个指令都包含在同一笔交易中。

一旦铸币厂初始化完毕,mint_authority就可以使用 指令创建新代币 MintTo。只要铸币厂包含有效的mint_authority,铸币厂就被视为具有非固定供应,可以随时mint_authority使用 指令创建新代币。 指令可用于不可逆地将铸币厂的权限设置为,从而使铸币厂的供应固定。 永远无法铸造更多代币。MintTo``SetAuthority``None

可以随时通过发出Burn从账户中删除和丢弃代币的指令来减少代币供应。

创建帐户

账户持有代币余额,并使用指令创建InitializeAccount 。每个账户都有一个所有者,该所有者必须作为签名者出现在某些指令中。

账户所有者可以使用 SetAuthority指令将账户所有权转让给另一个账户。

需要注意的是,该InitializeAccount指令不需要 Solana 帐户初始化,也不需要签名者。该InitializeAccount 指令应与创建 Solana 帐户的系统指令一起进行原子处理,方法是将两个指令都包含在同一笔交易中。

转移代币

可以使用指令在账户之间转移余额。 当源账户和目标账户不同时,Transfer源账户的所有者必须作为指令中的签名者在场。Transfer

需要注意的是,当 的源和目标相同时Transfer,总是成功。因此, 成功并不一定意味着所涉及的帐户是有效的 SPL 代币帐户,任何代币都被移动,或者源帐户作为签名者存在。我们强烈建议开发人员在 从其程序中调用指令之前 仔细检查源和目标是否不同。Transfer``Transfer``Transfer

燃烧

Burn指令会减少一个账户的代币余额,而无需转移到另一个账户,从而有效地永久地将代币从流通中移除。

链上没有其他方式可以减少供应量。这类似于向未知私钥的账户转账或销毁私钥。但使用Burn指令进行销毁的行为更明确,并且可以被任何一方在链上确认。

权限委托

账户所有者可以使用指令委托部分或全部代币余额的权限Approve。被委托的权限可以转移或销毁其被委托的金额。账户所有者可以通过Revoke指令撤销权限委托。

多重签名

支持 M of N 多重签名,可用于代替 Mint 授权机构或帐户所有者或代表。多重签名授权机构必须使用InitializeMultisig指令进行初始化。初始化指定一组有效的 N 个公钥,以及必须作为指令签名者存在的 N 个公钥的数量 M,以使授权机构合法。

需要注意的是,该InitializeMultisig指令不需要 Solana 帐户初始化,也不需要签名者。该 InitializeMultisig指令应与创建 Solana 帐户的系统指令一起进行原子处理,方法是将两个指令都包含在同一笔交易中。

此外,多重签名允许在签名者集合中存在重复账户,从而实现非常简单的加权系统。例如,可以使用 3 个唯一公钥构建 2/4 多重签名,并将一个公钥指定两次以赋予该公钥双倍投票权。

冻结账户

铸币厂还可能包含一个freeze_authority,可用于发出 FreezeAccount指令,使帐户无法使用。包含冻结帐户的代币指令将失败,直到使用该ThawAccount指令解冻帐户。该SetAuthority指令可用于更改铸币厂的freeze_authority。如果铸币厂的freeze_authority设置为, None则帐户冻结和解冻将被永久禁用,并且所有当前冻结的帐户也将永久冻结。

包装 SOL

Token 程序可用于包装本机 SOL。这样做允许本机 SOL 像任何其他 Token 程序令牌类型一样被处理,并且在从与 Token 程序接口交互的其他程序调用时非常有用。

包含包装的 SOL 的账户使用公钥与称为“Native Mint”的特定 Mint 相关联 So11111111111111111111111111111111111111112

这些帐户有一些独特的行为

  • InitializeAccount将初始化 Account 的余额设置为正在初始化的 Solana 账户的 SOL 余额,从而得到与 SOL 余额相等的代币余额。
  • 转账不仅会修改代币余额,还会将等量的 SOL 从源账户转移到目标账户。
  • 不支持刻录
  • 关闭帐户时余额可能不为零。

无论当前包装了多少 SOL,Native Mint 的供应量始终会报告 0。

免租

为了确保供应量计算的可靠性、Mint 的一致性和 Multisig 账户的一致性,所有持有 Account、Mint 或 Multisig 的 Solana 账户必须包含足够的 SOL 才能被视为免租

关闭账户

可以使用指令关闭帐户CloseAccount。关闭帐户时,所有剩余的 SOL 将转移到另一个 Solana 帐户(不必与代币计划相关联)。非本地帐户的余额必须为零才能关闭。

非同质化代币

NFT 只是一种代币类型,其中只铸造了一个代币。

钱包集成指南

本节介绍如何将 SPL Token 支持集成到支持原生 SOL 的现有钱包中。它假设一个模型,即用户有一个系统帐户作为他们发送和接收 SOL 的主要钱包地址。

虽然所有 SPL Token 账户都有自己的链上地址,但没有必要向用户显示这些额外的地址。

钱包使用两个程序:

  • SPL 代币程序:所有 SPL 代币使用的通用程序
  • SPL 关联代币账户计划:定义约定并提供将用户的钱包地址映射到其持有的关联代币账户的机制。

如何获取并显示代币持有量

getTokenAccountsByOwner JSON RPC方法 可用于获取钱包地址的所有代币账户。

对于每个代币铸造,钱包可以有多个代币账户:相关代币账户和/或其他辅助代币账户

按照惯例,建议钱包将同一代币铸币厂的所有代币账户的余额汇总为用户的单一余额,以免受这种复杂性的影响。

请参阅垃圾收集辅助代币账户 部分,了解有关钱包如何代表用户清理辅助代币账户的建议。

关联代币账户

在用户可以接收代币之前,必须在链上创建其关联的代币账户,并需要少量 SOL 将该账户标记为免租金。

对于谁可以创建用户的关联代币账户没有任何限制。它可以由钱包代表用户创建,也可以由第三方通过空投活动提供资金。

创建过程描述于此

强烈建议钱包在向用户表明他们能够接收该类型的 SPL 代币(通常通过向用户显示其接收地址来完成)之前,先为给定的 SPL 代币创建关联的代币账户。选择不执行此步骤的钱包可能会限制其用户从其他钱包接收 SPL 代币的能力。

“添加令牌”工作流程示例

当用户想要接收某种类型的 SPL 代币时,应首先为其关联的代币账户注资,以便:

  1. 最大限度地提高与其他钱包实现的互操作性
  2. 避免将创建相关代币账户的成本转嫁给第一个发送者

钱包应提供允许用户“添加代币”的用户界面。用户选择代币类型,系统会显示添加代币需要花费多少 SOL 的信息。

确认后,钱包将创建如此处所述的相关代币 类型

“空投活动”工作流程示例

对于每个收件人的钱包地址,发送包含以下内容的交易:

  1. 代表接收者创建相关的令牌账户。
  2. 使用TokenInstruction::Transfer完成转移

关联代币账户所有权

⚠️钱包绝不应该将关联代币账户的权限TokenInstruction::SetAuthority设置 为另一个地址。AccountOwner

辅助代币账户

可以随时将现有 SPL 代币帐户的所有权分配给用户。实现此目的的一种方法是使用 spl-token authorize <TOKEN_ADDRESS> owner <USER_ADDRESS>命令。钱包应该准备好妥善管理他们自己没有为用户创建的代币帐户。

在钱包之间转移代币

在钱包之间转移代币的首选方法是转移到接收者的关联代币账户。

收件人必须向发件人提供其主钱包地址。然后,发件人:

  1. 为接收者获取关联的代币账户
  2. 通过 RPC 获取收件人的关联代币账户并检查其是否存在
  3. 如果收件人的关联代币账户尚不存在,则发送方钱包应按 此处所述创建收件人的关联代币账户。发送方的钱包可以选择通知用户,由于创建了账户,转账将需要比平时更多的 SOL。但是,此时选择不支持创建收件人关联代币账户的钱包应向用户显示一条消息,其中包含足够的信息,以找到解决方法来实现他们的目标
  4. 使用TokenInstruction::Transfer完成转移

发送者的钱包不得要求接收者的主钱包地址保留余额才允许转账。

代币详细信息注册表

目前,Token Mint 注册中心有几种解决方案:

分散式解决方案正在进行中。

垃圾收集辅助代币账户

钱包应尽快将辅助代币账户中的资金转入用户关联的代币账户,从而清空辅助代币账户。此举有两个目的:

  • 如果用户是辅助账户的关闭权限,钱包可以通过关闭账户为用户回收SOL。
  • 如果辅助账户由第三方提供资金,一旦账户清空,第三方可能会关闭该账户并收回 SOL。

垃圾回收辅助代币账户的一个自然时机是用户下次发送代币时。执行此操作的附加指令可以添加到现有交易中,并且不需要额外费用。

清理伪步骤:

  1. 对于所有非空的辅助代币账户,添加一条 TokenInstruction::Transfer指令,将全部代币金额转移到用户关联的代币账户中。
  2. 对于所有空的辅助令牌账户,其中用户是关闭权限,添加一条TokenInstruction::CloseAccount指令

如果添加一个或多个清理指令导致事务超出允许的最大事务大小,请删除这些额外的清理指令。它们可以在下一次发送操作期间被清理。

spl-token gc命令提供了此清理过程的示例实现。

代币归属

目前有两种解决方案可用于归属 SPL 代币:

1)Bonfida 代币归属

此程序允许您锁定任意 SPL 代币,并按照确定的解锁计划释放锁定的代币。一个由一个和一个代币unlock schedule组成,在初始化归属合约时,创建者可以传递一个任意大小的数组,从而使合约创建者可以完全控制代币随时间如何解锁。unix timestamp``amount``unlock schedule

解锁的工作原理是推动合约上的无权限曲柄,将代币移动到预先指定的地址。当前接收者密钥的所有者可以修改授权合约的接收者地址,这意味着授权合约锁定的代币可以进行交易。

2)Streamflow 时间锁

使用基于时间的锁定和托管账户,可以创建、提取、取消和转移代币归属合约。合约默认可由创建者取消,并可由接收者转移。

归属合约创建者在创建时可以选择多种选项,例如:

  • SPL 代币及归属金额
  • 接受者
  • 确切的开始和结束日期
  • (可选)悬崖日期和金额
  • (可选)发布频率

即将推出:

  • 合同是否可以由创作者/接收者转让
  • 合同是否可以由创作者/接收者取消
  • 主題/記錄

资源:

参考文档

https://github.com/solana-labs/solana-program-library/blob/master/docs/src/token.mdx

error: rustc 1.79.0-dev is not supported by the following package:

anchor build时报以下错误

info: uninstalling toolchain 'solana'
info: toolchain 'solana' uninstalled
error: rustc 1.79.0-dev is not supported by the following package:

                 Note that this is the rustc version that ships with Solana tools and not your system's rustc version. Use `solana-install update` or head over to https://docs.solanalabs.com/cli/install to install a newer version.
  bytemuck_derive@1.9.2 requires rustc 1.84
Either upgrade rustc or select compatible dependency versions with
`cargo update <name>@<current-ver> --precise <compatible-ver>`
where `<compatible-ver>` is the latest version supporting rustc 1.79.0-dev

执行以下命令

cargo update -p bytemuck_derive --precise 1.7.0
搜索