Solana 钱包开发详细教程

Solana 是一个高性能的区块链平台,旨在实现快速、安全且可扩展的去中心化应用(dApps)和加密货币交易。它的设计初衷是解决传统区块链网络在扩展性和速度方面的局限,特别是比特币和以太坊在交易吞吐量和确认时间上的瓶颈。

一. Solana 核心特点

  • 高吞吐量:Solana 通过独特的共识机制和优化的网络协议,能够处理高达 65,000 笔交易每秒(TPS),远高于比特币的 7 TPS 和以太坊的 15 TPS。
  • 低延迟:Solana 网络的交易确认时间通常在 400 毫秒左右,确保了几乎即时的交易确认
  • 低交易费用:由于其高效的网络设计,Solana 的交易费用非常低,通常只有几美分。这使得它在处理大量小额交易时非常经济。

二. 技术亮点

  • Proof of History (PoH): Solana 的 PoH 是一种时间戳机制,创建了一个历史记录,证明了事件发生的顺序。这减少了节点之间的通信需求,极大地提高了网络的效率。
  • Tower BFT: 基于 PoH,Solana 实现了一种改进的拜占庭容错机制,称为 Tower BFT。这种机制确保了网络的安全性和一致性。
  • Turbine: 这是一种区块传播协议,通过将数据拆分成小包并在节点之间传输,优化了数据传播的速度和效率。
  • Gulf Stream:Solana 采用的这项技术可以提前确认和转发交易,从而减少内存池中的未确认交易数量,提高网络吞吐量。
  • Sealevel: Solana 的智能合约运行环境,允许并行处理数千个智能合约调用,从而实现高效的计算性能。
  • Pipelining: 通过流水线处理,Solana 能够优化区块验证和传播的速度,使得整个网络保持高效运行。
  • Cloudbreak: 这是一种水平扩展的账户数据库,支持并行读取和写入操作,从而优化了存储访问的性能。
  • Archivers: 用于存储区块链数据的去中心化节点,确保数据的可靠存储和访问。

1. 共识算法流程

  • PoH 生成时间序列:Solana 网络通过 PoH 生成一个全局时间序列,所有节点都遵循这个时间序列。PoH 是通过连续的 SHA-256 哈希运算产生的,每个哈希值都是前一个哈希值的输入,从而形成一个不可篡改的时间链。
  • 节点状态同步:所有参与共识的节点都使用 PoH 时间序列来同步它们的状态。这意味着节点可以在不频繁通信的情况下验证和确认交易顺序。
  • 验证者投票:网络中的验证者(validators)对每个区块的有效性进行投票。每个验证者根据其在时间序列中的位置,对当前区块进行投票。如果区块被足够多的验证者投票通过,它将被确认并添加到区块链中。
  • 投票计数和锁定机制:Tower BFT 使用一种称为“锁定投票”的机制。验证者在投票时会锁定其投票,如果某个区块在指定的时间内没有获得足够多的投票,验证者将锁定其投票,直到达成共识。锁定机制防止网络分叉,并确保共识的稳定性。
  • 区块确认和传播:一旦区块获得足够多的验证者投票通过,它将被确认并添加到区块链中。确认后的区块通过网络传播给其他节点,确保全网状态一致。
  • 冲突处理:如果出现分叉或投票冲突,Tower BFT 依赖于 PoH 时间序列来决定最优链。节点会选择包含更多已确认区块且时间戳最新的链作为主链,并放弃较短或较旧的分叉链。

2.算法优势

  • 高效性:由于 PoH 提供了一个全局时间序列,Tower BFT 大幅减少了节点之间的通信需求,提高了共识达成的效率。
  • 安全性:Tower BFT 的锁定投票机制和基于 PoH 的冲突处理策略确保了网络的安全性,能够抵抗拜占庭节点的攻击。
  • 低延迟:Tower BFT 结合 PoH 使得区块确认时间显著降低,通常在几百毫秒内完成交易确认。

三. 生态系统

  • Solana 的原生代币 SOL:SOL 是 Solana 网络的原生加密货币,用于支付交易费用和参与网络共识(通过质押)。
  • dApps 和项目:Solana 上已经构建了多个去中心化应用和项目,包括去中心化金融(DeFi)平台、NFT 市场、游戏等。例如,知名的 Serum 去中心化交易所就是基于 Solana 构建的。
  • 社区和开发者支持:Solana 具有活跃的开发者社区,并提供了丰富的开发工具和资源,如 Solana SDK 和开发者文档,以支持开发者在其平台上构建应用。

四. Solana 的优势与挑战

1.优势

  • 高性能和可扩展性使其适合处理大量交易和复杂应用。
  • 低交易费用吸引了大量用户和开发者。
  • 强大的技术基础和不断扩展的生态系统增加了其市场竞争力。

2. 挑战

  • 与其他区块链平台(如以太坊、Cardano 等)的竞争依然激烈。
  • 面临着去中心化程度和安全性的平衡问题,需要持续改进和创新。

五. 离线地址

export function createSolAddress (seedHex: string, addressIndex: string) {
  const { key } = derivePath("m/44'/501'/1'/" + addressIndex + "'", seedHex);
  const publicKey = getPublicKey(new Uint8Array(key), false).toString('hex');
  const buffer = Buffer.from(getPublicKey(new Uint8Array(key), false).toString('hex'), 'hex');
  const address = bs58.encode(buffer);
  const hdWallet = {
    privateKey: key.toString('hex') + publicKey,
    publicKey,
    address
  };
  return JSON.stringify(hdWallet);
}

Solana 的 BIP 44 编号是 501;并且将 BIP44 协议中的是否找零项目去掉了。

六. 离线签名

export async function signSolTransaction (params) {
  const { from, amount, to, mintAddress, nonce, decimal, privateKey } = params;
  const fromAccount = Keypair.fromSecretKey(new Uint8Array(Buffer.from(privateKey, 'hex')), { skipValidation: true });
  const calcAmount = new BigNumber(amount).times(new BigNumber(10).pow(decimal)).toString();
  if (calcAmount.indexOf('.') !== -1) throw new Error('decimal 无效');
  const tx = new Transaction();
  const toPubkey = new PublicKey(to);
  const fromPubkey = new PublicKey(from);
  tx.recentBlockhash = nonce;
  if (mintAddress) {
    const mint = new PublicKey(mintAddress);
    const fromTokenAccount = await SPLToken.Token.getAssociatedTokenAddress(
      SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID,
      SPLToken.TOKEN_PROGRAM_ID,
      mint,
      fromPubkey
    );
    const toTokenAccount = await SPLToken.Token.getAssociatedTokenAddress(
      SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID,
      SPLToken.TOKEN_PROGRAM_ID,
      mint,
      toPubkey
    );
    tx.add(
      SPLToken.Token.createTransferInstruction(
        SPLToken.TOKEN_PROGRAM_ID,
        fromTokenAccount,
        toTokenAccount,
        fromPubkey,
        [fromAccount],
        calcAmount
      )
    );
  } else {
    tx.add(
      SystemProgram.transfer({
        fromPubkey: fromAccount.publicKey,
        toPubkey: new PublicKey(to),
        lamports: calcAmount
      })
    );
  }
  tx.sign(fromAccount);
  return tx.serialize().toString('base64');
}

本代码中有一点值得注意的是,solana 交易签名中的 nonce 是使用 recentBlockHash 做为签名的 nonce, 而且这个 recentBlockHash 签名的交易在一定时间内交易发出去有效,过了这个时间之后再发送交易会报 Block Not Found 的错误。

要解决交易 recentBlockHash 失效问题,需要授权一个新的地址做为获取签名的 Nonce 的地址,这样签名的消息才能很长时间内都有效。

代码如下:

export function prepareAccount(params){
  const {
    authorAddress, from, recentBlockhash, minBalanceForRentExemption, privs,
  } = params;

  const authorPrivateKey = (privs?.find(ele=>ele.address===authorAddress))?.key;
  if(!authorPrivateKey) throw new Error("authorPrivateKey 为空");
  const nonceAcctPrivateKey = (privs?.find(ele=>ele.address===from))?.key;
  if(!nonceAcctPrivateKey) throw new Error("nonceAcctPrivateKey 为空");

  const author = Keypair.fromSecretKey(new Uint8Array(Buffer.from(authorPrivateKey, "hex")));
  const nonceAccount = Keypair.fromSecretKey(new Uint8Array(Buffer.from(nonceAcctPrivateKey, "hex")));


  let tx = new Transaction();
  tx.add(
      SystemProgram.createAccount({
        fromPubkey: author.publicKey,
        newAccountPubkey: nonceAccount.publicKey,
        lamports: minBalanceForRentExemption,
        space: NONCE_ACCOUNT_LENGTH,
        programId: SystemProgram.programId,
      }),

      SystemProgram.nonceInitialize({
        noncePubkey: nonceAccount.publicKey,  
        authorizedPubkey: author.publicKey, 
      })
  );
  tx.recentBlockhash = recentBlockhash;


  tx.sign(author, nonceAccount);
  return tx.serialize().toString("base64");
}

七. 钱包开发相关的 RPC 接口

1.获取账户信息

  • 接口作用:本接口的作用是判断账户是否可用,如果 value 为 null, 说明不可用,否则可用
  • 接口名称:getAccountInfo
  • 接口参数:地址
  • 请求示范
curl --location 'https://sly-yolo-dinghy.solana-mainnet.quiknode.pro/2ac2af5b8c2e5e9e74c7906e949f1976314aa996' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getAccountInfo",
    "params": [
      "4wHd9tf4x4FkQ3JtgsMKyiEofEHSaZH5rYzfFKLvtESD",
      {
        "encoding": "base58"
      }
    ]
  }'
  • 返回值
{
    "jsonrpc": "2.0",
    "result": {
        "context": {
            "apiVersion": "1.17.34",
            "slot": 268835746
        },
        "value": {
            "data": [
                "",
                "base58"
            ],
            "executable": false,
            "lamports": 289995950,
            "owner": "11111111111111111111111111111111",
            "rentEpoch": 18446744073709551615,
            "space": 0
        }
    },
    "id": 1
}
  • value 不为 null, 说明该地址能用

2.获取 recentBlochHash, 直接签名的话 recentBlochHash 相当于 nonce

  • 接口作用:获取最近的区块的 blockHash, 做为交易签名的 Nonce
  • 接口名称:getRecentBlockhash
  • 接口参数:无
  • 请求示范
curl --location 'https://sly-yolo-dinghy.solana-mainnet.quiknode.pro/2ac2af5b8c2e5e9e74c7906e949f1976314aa996' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc":"2.0",
    "id":1, 
    "method":"getRecentBlockhash"
}'
  • 返回值
{
    "jsonrpc": "2.0",
    "result": {
        "context": {
            "apiVersion": "1.17.34",
            "slot": 268809612
        },
        "value": {
            "blockhash": "5k5Hh3gfRX4hJv9dqxfdMa6wSFkkZ631sf71kPqtCFv7",
            "feeCalculator": {
                "lamportsPerSignature": 5000
            }
        }
    },
    "id": 1
}
  • blockhash 为要使用的值

3. 获取准备 nonce 账户的 Minimum Balance For Rent 数据

  • 接口作用:获取 rent 账户的最小 Rent
  • 接口名称:getMinimumBalanceForRentExemption
  • 接口参数:账户的数据长度
  • 请求示范
curl --location 'https://sly-yolo-dinghy.solana-mainnet.quiknode.pro/2ac2af5b8c2e5e9e74c7906e949f1976314aa996' \
--header 'Content-Type: application/json' \
--data ' {
    "jsonrpc": "2.0", "id": 1,
    "method": "getMinimumBalanceForRentExemption",
    "params": [50]
  }'
  • 返回值
{
    "jsonrpc": "2.0",
    "result": 1238880,
    "id": 1
}
  • result 的结果为 prepareAccount 的 minBalanceForRentExemption 值

4. 获取最新块高 (Slot)

  • 接口作用:获取最新的 slot
  • 接口名称:getSlot
  • 接口参数:无
  • 请求示范
curl --location 'https://sly-yolo-dinghy.solana-mainnet.quiknode.pro/2ac2af5b8c2e5e9e74c7906e949f1976314aa996' \
--header 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1, "method":"getSlot"}'
  • 返回值
{
    "jsonrpc": "2.0",
    "result": 268840179,
    "id": 1
}
  • 返回值为最新的块高(Slot)

5. 根据块高获取交易

  • 接口作用:根据区块号获取里面的交易
  • 接口名称:getConfirmedBlock
  • 接口参数:区块高度和编码方式
  • 请求示范
curl --location 'https://api.devnet.solana.com' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0", "id": 1,
    "method": "getConfirmedBlock",
    "params": [
        268992938, 
         {
          "encoding":"base64",
           "maxSupportedTransactionVersion": 0
      }
    ]
  }'
  • 返回值
{
  "jsonrpc": "2.0",
  "result": {
    "blockTime": null,
    "blockhash": "3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA",
    "parentSlot": 429,
    "previousBlockhash": "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B",
    "rewards": [],
    "transactions": [
      {
        "meta": {
          "err": null,
          "fee": 5000,
          "innerInstructions": [],
          "logMessages": [],
          "postBalances": [499998932500, 26858640, 1, 1, 1],
          "postTokenBalances": [],
          "preBalances": [499998937500, 26858640, 1, 1, 1],
          "preTokenBalances": [],
          "status": {
            "Ok": null
          }
        },
        "transaction": [
          "AVj7dxHlQ9IrvdYVIjuiRFs1jLaDMHixgrv+qtHBwz51L4/ImLZhszwiyEJDIp7xeBSpm/TX5B7mYzxa+fPOMw0BAAMFJMJVqLw+hJYheizSoYlLm53KzgT82cDVmazarqQKG2GQsLgiqktA+a+FDR4/7xnDX7rsusMwryYVUdixfz1B1Qan1RcZLwqvxvJl4/t3zHragsUp0L47E24tAFUgAAAABqfVFxjHdMkoVmOYaR1etoteuKObS21cc1VbIQAAAAAHYUgdNXR0u3xNdiTr072z2DVec9EQQ/wNo1OAAAAAAAtxOUhPBp2WSjUNJEgfvy70BbxI00fZyEPvFHNfxrtEAQQEAQIDADUCAAAAAQAAAAAAAACtAQAAAAAAAAdUE18R96XTJCe+YfRfUp6WP+YKCy/72ucOL8AoBFSpAA==",
          "base64"
        ]
      }
    ]
  },
  "id": 1
}

或者内部包含

{
   "parsed":{
      "info":{
         "amount":"12500",
         "authority":"CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd",
         "destination":"Ezts8ufHLpKmJsvXzPmguvvdtC18G8aQngQ9EyUAJrHf",
         "source":"FLffM77tZpRP7eRqQGfH7UA1j1j31csZ4f9CwQ4qbVjp"
      },
      "type":"transfer"
   },
   "program":"spl-token",
   "programId":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
   "stackHeight":2
}
  • source 是转出地址
  • destination 转入地址
  • amount 是转账金额
  • type:转账类型

6. 根据交易 Hash 获取交易详情

  • 接口作用:根据交易 Hash 获取交易详情
  • 接口名称:getConfirmedTransaction
  • 接口参数:区块高度和编码方式
  • 请求示范
curl --location 'https://sly-yolo-dinghy.solana-mainnet.quiknode.pro/2ac2af5b8c2e5e9e74c7906e949f1976314aa996' \
--header 'Content-Type: application/json' \
--data ' {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getConfirmedTransaction",
    "params": [
      "5X6y7aVzC93nfHbPbcy1yU8jHeFj996ZrVzMfNFYqC59jJrVMPMj3zzCtfwnoJB8H7PcgZRhyMbz1znVt8CSoT35",
      {
          "encoding":"jsonParsed",
           "maxSupportedTransactionVersion": 0
      }
    ]
  }'
  • 返回值
{
   "jsonrpc":"2.0",
   "result":{
      "blockTime":1717073604,
      "transaction":{
         "message":{
            "accountKeys":[

            ],
            "instructions":[
               {
                  "parsed":{
                     "info":{
                        "amount":"5000000000",
                        "authority":"7JnucyofTX4Zk74jJ18HRhfKoBc8GgPM6ofQZ1EQLSpZ",
                        "destination":"EgScKCKKWX1d7yEp7SBpErdvYsBXzYYgxtMWiSdbZL9S",
                        "source":"FSkKxc5WBa7g6obBmXMavNmpuNPs25nukJz32DyHXuTM"
                     },
                     "type":"transfer"
                  },
                  "program":"spl-token",
                  "programId":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
                  "stackHeight":null
               },
               {
                  "accounts":[

                  ],
                  "data":"3MuSQQNShkST",
                  "programId":"ComputeBudget111111111111111111111111111111",
                  "stackHeight":null
               },
               {
                  "accounts":[

                  ],
                  "data":"FtDxo9",
                  "programId":"ComputeBudget111111111111111111111111111111",
                  "stackHeight":null
               }
            ],
            "recentBlockhash":"HMzYzdmwMez5ueZA9yrWHT6V5ZT4VtD3CC4hsaN7a9m4"
         },
         "signatures":[
            "5X6y7aVzC93nfHbPbcy1yU8jHeFj996ZrVzMfNFYqC59jJrVMPMj3zzCtfwnoJB8H7PcgZRhyMbz1znVt8CSoT35"
         ]
      }
   },
   "id":1
}
  • source 是转出地址
  • destination 转入地址
  • amount 是转账金额
  • type:转账类型

7.发送交易到区块链网络

  • 接口作用:发送交易到区块链网络
  • 接口名称:sendTransaction
  • 接口参数:区块高度和编码方式
  • 请求示范
curl --location 'https://sly-yolo-dinghy.solana-mainnet.quiknode.pro/2ac2af5b8c2e5e9e74c7906e949f1976314aa996' \
--header 'Content-Type: application/json' \
--data '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "sendTransaction",
    "params": [
      "ASG4jVQEsZhgMGnkXIYEvyhvGMYBfwL8lFd40mPz4AjpeWqZlVM122Vx8iCZIUNmbGdqDcdplm0xMUGri4WiCAIBAAEDOns4dLpGe+a4HqNh49dFOvi4HIiu3SS1Ax/doLxxrTJa8yfsHKLzR/zaYPv8xS5VGKfAp4PkO8pQN4Jn396IRgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKy6/vKD5x9Dmbz1bBJ85fYZK94CfRhjYwMBlQyb4mz8BAgIAAQwCAAAAQEIPAAAAAAA=",
      {
        "encoding":"base64"
      }

    ]
  }'
  • 返回值
{
    "jsonrpc": "2.0",
    "result": "g6yMJUd16jAcPiQfi7QK1M5tN7yheeKAC7NbUh3BQCSfav3pgv74ovCSGuhgApzma1s6ew8WEmX1Bzfk6sCfg9P",
    "id": 1
}
  • 成功返回交易 Hash, 失败返回各种错误

八. 中心化钱包开发

1. 离线地址生成

  • 调度签名机生成密钥对,签名机吐出公钥
  • 使用公钥匙导出地址

2.充值逻辑

  • 获得最新块高;更新到数据库
  • 从数据库中获取上次解析交易的块高做为起始块高,最新块高为截止块高,如果数据库中没有记录,说明需要从头开始扫,起始块高为 0;
  • 解析区块里面的交易,to 地址是系统内部的用户地址,说明用户充值,更新交易到数据库中,将交易的状态设置为待确认。
  • 所在块的交易过了确认位,将交易状态更新位充值成功并通知业务层。

3. 提现逻辑

  • 获取离线签名需要的参数,给合适的手续费
  • 构建未签名的交易消息摘要,将消息摘要递给签名机签名
  • 构建完整的交易并进行序列化
  • 发送交易到区块链网络
  • 扫链获取到交易之后更新交易状态并上报业务层

4.归集逻辑

  • 将用户地址上的资金转到归集地址,签名流程类似提现
  • 发送交易到区块链网络
  • 扫链获取到交易之后更新交易状态

5. 转冷逻辑

  • 将热钱包地址上的资金转到冷钱包地址,签名流程类似提现
  • 发送交易到区块链网络
  • 扫链获取到交易之后更新交易状态

6.冷转热逻辑

  • 手动操作转账到热钱包地址
  • 扫链获取到交易之后更新交易状态

注意:交费的学员需要完整的项目实战代码可寻求 The Web3 社区索取

九. HD 钱包开发

1.离线地生成和离线签名

参考上面的代码

2.和链上交互的接口

  • 获取账户余额
  • 根据地址获取交易记录
  • 获取预估手续费

对应代码库:https://github.com/the-web3/wallet-chain-node

十. 总结

HD 钱包和交易所钱包不同之处有以下几点

1.密钥管理方式不同

  • HD 钱包私钥在本地设备,私钥用户自己控制
  • 交易所钱包中心化服务器(CloadHSM, TEE 等),私钥项目方控制

2.资金存在方式不同

  • HD 资金在用户钱包地址
  • 交易所钱包资金在交易所热钱包或者冷钱包里面, 用户提现的时候从交易所热钱包提取

3.业务逻辑不一致

  • 中心化钱包:实时不断扫链更新交易数据和状态
  • HD 钱包:根据用户的操作通过请求接口实现业务逻辑

十一. 附录

  • RPC 文档:https://solana.com/docs/rpc
  • github: https://github.com/solana-labs
  • 浏览器 1:https://explorer.solana.com/
  • 浏览器 2: https://solscan.io/
  • 钱包相关的资料:https://solana.com/developers/cookbook
  • solana web3js: https://github.com/solana-labs/solana-web3.js

全部评论(0)