推送 RPC

RPC 节点允许连接通过 WebSocket 进行长连接。

通过在该长连接上发送订阅请求, RPC 节点会将相关事件在长连接上推送过来。

当前订阅主要分为:

  • accountSubscribe :  订阅 Account 的变化,比如 lamports
  • logsSubscribe :  订阅交易的日志
  • programSubscribe : 订阅合约 Account 的变化
  • signatureSubscribe : 订阅签名状态变化
  • slotSubscribe : 订阅 slot 的变化

每个事件,还有对应的取消订阅 Unsubscribe 动作,将上面的 Subscribe 替换成 Unsubscribe 即可。

这里我们通过 wscat 命令行工具来模拟 wss 客户端。

首先安装工具:

npm install -g ws wscat

然后建立连接:

wscat -c wss://api.devnet.solana.com

1. 订阅账号变化

这里的 Account 就是每个地址的 Account 元数据,主要变化的就是 data 部分和 lamports 部分。

比如,我们要订阅我们的账号余额的变化。

{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "accountSubscribe",
        "params": [
            "CnjrCefFBHmWnKcwH5T8DFUQuVEmUJwfBL3Goqj6YhKw",
            {
            "encoding": "jsonParsed",
            "commitment": "finalized"
            }
        ]
    }

这里订阅对账号的变化的事件,我们通过 wscat 来模拟:

wscat -c wss://api.devnet.solana.com
    Connected (press CTRL+C to quit)
    > {"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["EZhhUANUMKsRhRMArczio1kLc9axefTUAh5xofGX35AK",{"encoding":"jsonParsed","commitment":"finalized"}]}
    < {"jsonrpc":"2.0","result":3283925,"id":1}

然后,我们在另外一个终端里面进行转账:

solana transfer --allow-unfunded-recipient CZmVK1DymrSVWHiQCGXx6VG5zgHVrh5J1P514jHKRDxA 0.01

接着我们注意观察上面的 wscat:

Connected (press CTRL+C to quit)
    > {"jsonrpc":"2.0","id":1,"method":"accountSubscribe","params":["CnjrCefFBHmWnKcwH5T8DFUQuVEmUJwfBL3Goqj6YhKw",{"encoding":"jsonParsed","commitment":"finalized"}]}
    < {"jsonrpc":"2.0","result":3283925,"id":1}
    < {"jsonrpc":"2.0","method":"accountNotification","params":{"result":{"context":{"slot":209127027},"value":{"lamports":989995000,"data":["","base64"],"owner":"11111111111111111111111111111111","executable":false,"rentEpoch":0,"space":0}},"subscription":3283925}}

我们会发现,一段时间后,也就是到达了 “finalized”状态后,就会将修改过后的 Account 信息推送过来:

{
        "lamports": 989995000,
        "data": [
            "",
            "base64"
        ],
        "owner": "11111111111111111111111111111111",
        "executable": false,
        "rentEpoch": 0,
        "space": 0
    }

可以看到这里余额发生了变化。

2. 订阅日志

订阅日志可能是做应用最常见到的,任何在 log 里面打印了相关事件的交易都会被通知。

{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "logsSubscribe",
        "params": [
            {
            "mentions": [ "CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi" ]
            },
            {
            "commitment": "finalized"
            }
        ]
    }

这里 mentions 来指定,通知了哪个程序或者账号的地址。

比如这里我们订阅我们的一个ATA 的账号:

wscat -c wss://api.devnet.solana.com
    Connected (press CTRL+C to quit)
    > {"jsonrpc":"2.0","id":1,"method":"logsSubscribe","params":[{"mentions":["CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi"]},{"commitment":"finalized"}]}
    < {"jsonrpc":"2.0","result":610540,"id":1}

然后我们给这个地址做 mint 增加余额:

spl-token mint 7dyTPp6Jd1nWWyz3y7CXqdSG86yFpVF7u45ARKnqDhRF 1000000000
    Minting 1000000000 tokens
    Token: 7dyTPp6Jd1nWWyz3y7CXqdSG86yFpVF7u45ARKnqDhRF
    Recipient: CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi

    Signature: 5NVHNccPo4ADxnHZjVSYZzxk3fuZfZvuLP6MwkhSNBbQRNcGfC2gwScz24XYictZuqaMKFEcmsXuHV4WZDiFUD3r

可以在事件通知中看到:

wscat -c wss://api.devnet.solana.com
    Connected (press CTRL+C to quit)
    > {"jsonrpc":"2.0","id":1,"method":"logsSubscribe","params":[{"mentions":["CdJp6W7S8muM85UXq7u2P42ryytDacqEo8JgoHENSiUi"]},{"commitment":"finalized"}]}
    < {"jsonrpc":"2.0","result":610540,"id":1}
    < {"jsonrpc":"2.0","method":"logsNotification","params":{"result":{"context":{"slot":209131722},"value":{"signature":"5NVHNccPo4ADxnHZjVSYZzxk3fuZfZvuLP6MwkhSNBbQRNcGfC2gwScz24XYictZuqaMKFEcmsXuHV4WZDiFUD3r","err":null,"logs":["Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]","Program log: Instruction: MintToChecked","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4498 of 200000 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success"]}},"subscription":610540}}

这里有个 "MintToChecked" 指令。

3. 订阅合约所属的账号事件

比如我们希望知道所有 Token 合约管理的账号的余额变化是,我们可以通过订阅合约管理的账号事件来发现:

{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "programSubscribe",
        "params": [
            "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
            {
            "encoding": "jsonParsed"
            }
        ]
    }

对应的命令:

wscat -c wss://api.devnet.solana.com
    Connected (press CTRL+C to quit)
    > {"jsonrpc":"2.0","id":1,"method":"programSubscribe","params":["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",{"encoding":"jsonParsed"}]}
    < {"jsonrpc":"2.0","result":142408,"id":1}
    < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUY45VyYy9j7vFdHRP3ecyMYhFCfrCBpVQaUxoEtHfv","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"AV1JYHgShqNdbza84sLi7Hgbtfgd1hn9mNMgez4twBuG","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}}
    < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUXUncym8riA1izYZnBWspYL1k4rVBnLuZ3KbUnc6WG","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"2E7BD9ibbHinwohM4pLFsjdFYq1S2o4wqKmfaQXXg8Dr","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}}
    < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUZyCzhCKEFZMdf8mDfUU4L1tr4q2xh3FHRWpRM8cPB","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"7PydWu5QtMcbdj7qgdgn42Rwp247GFf3e2pQ5fQ8LRGY","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}}
    < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUZzsex1ybU4V1duGetLHqFc7zz74jPbLp6rvNoScrR","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"2E7BD9ibbHinwohM4pLFsjdFYq1S2o4wqKmfaQXXg8Dr","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}}
    < {"jsonrpc":"2.0","method":"programNotification","params":{"result":{"context":{"slot":209131042},"value":{"pubkey":"GGUV4mWenyQGyVVCNV3xPjmioJoMCCYSPFxFzFB3AmBt","account":{"lamports":2039280,"data":{"program":"spl-token","parsed":{"info":{"isNative":false,"mint":"GCBnu9k28isstJjCcYoZZcyTkMh5cXTsk7abpgWJesQT","owner":"2E7BD9ibbHinwohM4pLFsjdFYq1S2o4wqKmfaQXXg8Dr","state":"initialized","tokenAmount":{"amount":"0","decimals":9,"uiAmount":0.0,"uiAmountString":"0"}},"type":"account"},"space":165},"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","executable":false,"rentEpoch":0,"space":165}}},"subscription":142408}}

这里里面就可以看到。有很多的 SPL-Token 账号都在变化。并且因为我们加了 "jsonParsed",所以这里 SPL-Token 的内容也展示出来了。

4. 订阅交易状态

比如,我们希望在我们发起交易后,第一时间知道交易的确定状态,我们可以通过订阅该事件来实现:

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "signatureSubscribe",
    "params": [
            "BfQAbgqQZMfsxFHwh6Hve8yGb843QfZcYtD2j2nN3K1hLHZrQjzdwG9uWgNkGXs4tBNVLE3JAzvNLtwJBt3zDsN",
            {
            "commitment": "finalized",
            "enableReceivedNotification": false
            }
        ]
    }

我们再次发起一笔转账交易:

solana transfer --allow-unfunded-recipient CZmVK1DymrSVWHiQCGXx6VG5zgHVrh5J1P514jHKRDxA 0.01

    Signature: BfQAbgqQZMfsxFHwh6Hve8yGb843QfZcYtD2j2nN3K1hLHZrQjzdwG9uWgNkGXs4tBNVLE3JAzvNLtwJBt3zDsN

然后,在另外一个终端,迅速建立 wscat 连接,并订阅该事件:

wscat -c wss://api.devnet.solana.com
    Connected (press CTRL+C to quit)
    > {"jsonrpc":"2.0","id":1,"method":"signatureSubscribe","params":["BfQAbgqQZMfsxFHwh6Hve8yGb843QfZcYtD2j2nN3K1hLHZrQjzdwG9uWgNkGXs4tBNVLE3JAzvNLtwJBt3zDsN",{"commitment":"finalized","enableReceivedNotification":false}]}
    < {"jsonrpc":"2.0","result":3285176,"id":1}
    < {"jsonrpc":"2.0","method":"signatureNotification","params":{"result":{"context":{"slot":209127740},"value":{"err":null}},"subscription":3285176}}

当到达 "finalized" 状态时,会通知我们,该交易已经成功。