DEV Community

lifeme2027
lifeme2027

Posted on

在 C# 里连接 Kaspa 节点并发一笔交易

免责声明: NKaspa 目前是 alpha 版本(0.1.11-alpha),未经安全审计。它是社区项目,不是 Kaspa 官方项目。本教程只在 Kaspa testnet 上演示(仅 testnet-10),请只用测试币和小额。不要用于真实资金。公共节点由第三方运营,不保证可用。

本文介绍如何用社区 .NET 库 NKaspa(NuGet 上可直接安装)在 C# 里使用 Kaspa,也就是一个 Kaspa .NET SDK 的入门:通过 wRPC 连接 testnet-10 节点、检查节点状态、读取 UTXO、构建并签名交易、在本地校验,然后提交。

流程:KaspaRpcClient.Create → ConnectAsync → GetServerInfoAsync → GetUtxosByAddressesAsync → TransactionBuilder → tx.Verify → SubmitTransactionAsync。

用的是 wRPC,不是 gRPC

NKaspa 不包含 gRPC 客户端。它通过 WebSocket 上的 wRPC 与 kaspad 通信,编码可选 JSON 或 Borsh。如果你需要 gRPC,请改用 Kaspa 官方的 proto 文件(见 docs.kaspa.org)。

准备工作

  • .NET 8 SDK(仓库的 global.json 固定为 8.0.427,rollForward: latestFeature)。
  • 安装包:
dotnet add package NKaspa --version 0.1.11-alpha
Enter fullscreen mode Exit fullscreen mode

0.1.11-alpha 已在 nuget.org 上线,可在新建的 net8.0 控制台项目中正常还原(restore)。

  • 一个 testnet-10 wRPC 节点。可以自建 kaspad,也可以使用 KaspaResolver 源码(src/NKaspa/RPC/KaspaResolver.cs)中列出的两个 testnet-10 回退节点之一:
    • wss://vector-10.kaspa.green/kaspa/testnet-10/wrpc/json(或 /borsh)
    • wss://muon-10.kaspa.blue/kaspa/testnet-10/wrpc/json(或 /borsh)

这些是第三方节点,不由 NKaspa 运营,不保证可用。撰写时 vector-10 可以连通,muon-10 连不上。

  • testnet 测试币:Kaspa 官方 Wiki 列出的 testnet-10 水龙头 https://faucet-tn10.kaspanet.io/ 。请在浏览器中打开,页面会要求人机验证(captcha)。

端口表:选 JSON 还是 Borsh?

网络 gRPC(会被拒绝) wRPC Borsh wRPC JSON
mainnet 16110 17110 18110
testnet-10 16210 17210 18210
simnet 16510 17510 18510
devnet 16610 17610 18610

使用 KaspaRpcEncoding.Auto(默认值)时,URL 路径优先于端口:路径含 /wrpc/borsh 选 Borsh,含 /wrpc/json 选 JSON;否则按默认 wRPC 端口决定。如果路径和端口都无法识别(例如 443 端口的 wss 且路径没有提示),Create 会抛异常,不会自行猜测。填 gRPC 端口时一律抛 ArgumentException,错误信息会提示改用 wRPC 端口。

如果服务器发来的帧类型不对(JSON 客户端收到二进制帧,或 Borsh 客户端收到文本帧),挂起的请求会立即以 KaspaRpcEncodingMismatchException 失败。客户端不会等到超时,也不会重连。

两种编码功能相同,跟着节点开放的端口选即可。

连接节点

using NKaspa;
using NKaspa.RPC;

await using var rpc = KaspaRpcClient.Create(url); // returns a KaspaRpcConnection; does NOT connect
await rpc.ConnectAsync(cancellationToken);
Enter fullscreen mode Exit fullscreen mode

方法签名:

public static KaspaRpcConnection Create(string url,
    KaspaRpcEncoding encoding = KaspaRpcEncoding.Auto,
    KaspaWebSocketOptions? options = null)
Enter fullscreen mode Exit fullscreen mode

KaspaRpcConnection 实现了 IKaspaRpcClient 和 IAsyncDisposable,请用 await using 释放。KaspaWebSocketOptions 可设置连接超时(默认 15 秒)、请求超时(默认 30 秒)、AutoReconnect 和 MaxMessageBytes(默认 16 MiB)。

先检查节点

var info = await rpc.GetServerInfoAsync(cancellationToken);
Console.WriteLine($"network={info.NetworkId} synced={info.IsSynced} utxoindex={info.HasUtxoIndex}");
if (!info.IsSynced || !info.HasUtxoIndex) return; // stop here
Enter fullscreen mode Exit fullscreen mode

节点未同步或没有开启 UTXO index 时请停止;没有 UTXO index 就无法列出 UTXO。在 testnet-10 上,NetworkId 为 testnet-10。

从助记词派生 testnet 地址

从环境变量读取助记词。不要把它写进源代码、日志或截图。

var words = Environment.GetEnvironmentVariable("NKASPA_MNEMONIC");
var mnemonic = new Mnemonic(words, Wordlist.English);
if (!mnemonic.IsValidChecksum) return;

using var master = mnemonic.DeriveExtKey();
using var receive = master.Derive(KeyPath.KaspaReceive(0));
using var changeKey = master.Derive(KeyPath.KaspaChange(0));
using var spendKey = receive.PrivateKey;

var network = Network.Testnet;
var own = spendKey.GetAddress(network);                      // kaspatest:...
var changeAddress = changeKey.PrivateKey.GetAddress(network);
Enter fullscreen mode Exit fullscreen mode

请始终显式传入 Network.Testnet。在 0.1.11-alpha 中,ExtPubKey.Parse 无法区分主网和 testnet 的扩展公钥,网络由你传给 GetAddress 的参数决定。testnet 地址以 kaspatest: 开头。

另见仓库中的 docs/tutorials/generate-kaspa-address-csharp.md。

读取 UTXO 并转换为 Coin

var listed = await rpc.GetUtxosByAddressesAsync(new[] { own.ToString() }, cancellationToken);
if (listed.Count == 0) { Console.WriteLine($"No UTXO on {own}. Nothing was submitted."); return; }

var coins = new Coin[listed.Count];
for (var i = 0; i < listed.Count; i++)
    coins[i] = new Coin(listed[i].Outpoint, listed[i].Entry);
Enter fullscreen mode Exit fullscreen mode

其他读取方法:GetBalanceByAddressAsync、GetBalancesByAddressesAsync、GetFeeEstimateAsync。

构建并签名交易

var payee = KaspaAddress.Parse(to, network);
var amount = Money.Coins(0.1m); // small amount

var builder = new TransactionBuilder(network).AddKeys(spendKey).AddCoins(coins);
var tx = builder
    .Send(payee, amount)
    .SetChange(changeAddress)
    .SendMinimumRelayFee()
    .BuildTransaction(true); // true = sign
Enter fullscreen mode Exit fullscreen mode

说明:

  • 也可以用 SendEstimatedFees(FeeRate) 代替最低中继手续费。
  • SetVersion(1) 构建版本 1 的交易。
  • 低于 MinimumChange(默认 0.02 KAS)的找零会并入手续费。可通过 builder.ChangeFoldedIntoFee 查看并入了多少。
  • 参考数据来自仓库的离线示例(不联网):单输入支付 203,600 sompi(0.002036 KAS),中继 mass 为 2,036。这些只是离线示例数值,不代表节点实际收取的费用。

广播前在本地校验

以下各项全部通过才提交:

  1. 有一个输出按 amount 准确支付到收款方脚本。
  2. 输入总额 ≥ 输出总额。
  3. tx.Verify(entries) 返回 true(entries 为每个输入对应的 UtxoEntry,按输入顺序)。也可以用 Script.VerifyScript 逐个输入检查。
  4. 手续费等于 MassCalculator.MinimumRelayFee(MassCalculator.Calculate(tx, entries))。
if (!paid || outputSum > inputSum || !tx.Verify(entries)) return; // nothing submitted
var fee = inputSum - outputSum;
var minimum = MassCalculator.MinimumRelayFee(MassCalculator.Calculate(tx, entries));
if (fee != minimum) return;
Enter fullscreen mode Exit fullscreen mode

构建 entries、inputSum、outputSum 和 paid 的完整循环见 samples/Tutorials/SendTutorial.cs。

提交到 testnet

uint256 id = await rpc.SubmitTransactionAsync(tx, false, cancellationToken);
Console.WriteLine($"submitted: {id}");
Enter fullscreen mode Exit fullscreen mode

签名:Task<uint256> SubmitTransactionAsync(Transaction transaction, bool allowOrphan = false, CancellationToken cancellationToken = default),返回交易 id。

输出示例:

network: testnet
fee-sompi: <fee>
submitted: <txid>
Enter fullscreen mode Exit fullscreen mode

完整方法是 samples/Tutorials 中的 SendTutorial.SendOnTestnetAsync。它默认使用 Network.Testnet,未设置 NKASPA_MNEMONIC、NKASPA_WRPC_URL、NKASPA_TO 和 NKASPA_AMOUNT 时拒绝运行,默认也拒绝主网。示例的 Main 不会调用它。

未做端到端实测,仅为示例代码。 上文的连接和查询步骤(ConnectAsync、GetServerInfoAsync、余额和 UTXO 读取)已通过 JSON 和 Borsh 两种编码在 vector-10 testnet 节点上实测成功。构建、签名和提交步骤尚未在真实节点上运行。请在你自己的环境中用测试币自行验证。

samples/Tutorials 只运行离线检查,不会发送。要试发送路径,请新建一个引用 NKaspa 0.1.11-alpha 的控制台项目,把 SendTutorial.cs 复制进去,然后自己调用:

return await NKaspa.Samples.Tutorials.SendTutorial.SendOnTestnetAsync(Console.Out, Console.Error);
Enter fullscreen mode Exit fullscreen mode
export NKASPA_MNEMONIC='<throwaway testnet mnemonic>'
export NKASPA_WRPC_URL='wss://vector-10.kaspa.green/kaspa/testnet-10/wrpc/borsh'
export NKASPA_TO='<another kaspatest: address>'
export NKASPA_AMOUNT='0.1'
dotnet run
Enter fullscreen mode Exit fullscreen mode

断线重连与订阅

在 KaspaWebSocketOptions 中设置 AutoReconnect 可在断线后重连。订阅不会自动恢复。 重连后,请重新调用 NotifyUtxosChangedAsync(以及其他 Notify*),并重新查询余额,以免漏掉变化。

KaspaRpcConnection 暴露两个事件:Reconnected(Action,在断开的 socket 重新打开后触发;首次 ConnectAsync 不触发)和 StateChanged(Action<KaspaWebSocketState>)。State 是只读属性,不是事件。

rpc.StateChanged += state => Console.WriteLine($"state: {state}");
rpc.Reconnected += () =>
{
    // Subscriptions are gone: subscribe again with NotifyUtxosChangedAsync
    // and re-read the balance (do async work off this handler).
};
Enter fullscreen mode Exit fullscreen mode

完整的只读(watch-only)示例见 samples/AspNetCheckout(仅在 main 上,于 0.1.11-alpha 发布之后加入;它是示例,不是库 API)。

常见错误排查

  • 提到 gRPC 的 ArgumentException — 你用了 16x10 端口。testnet-10 请用 17210(Borsh)或 18210(JSON)。
  • 443 端口的 URL 让 Create 抛异常 — 在路径中加上 /wrpc/json 或 /wrpc/borsh,或显式传入编码。
  • KaspaRpcEncodingMismatchException — 节点使用的是另一种编码。请更换路径或编码。
  • 没有 UTXO — 先从水龙头给地址充值并等待;同时确认节点开启了 UTXO index。
  • 节点拒绝手续费 — 按 Toccata 之前的费率(1 sompi/gram)算出的手续费低于 Toccata 之后的下限(100 sompi/gram)。请使用 SendMinimumRelayFee() 或当前网络的手续费估算。
  • 找零“消失”了 — 找零低于 MinimumChange,已并入手续费;查看 ChangeFoldedIntoFee。

常见问题

C# 有 Kaspa SDK 吗? 有一个社区库 NKaspa(NuGet NKaspa,0.1.11-alpha,未审计)。它提供地址、HD 派生、wRPC 客户端(JSON 和 Borsh)以及交易构建和签名。它不是 Kaspa 官方项目。

NKaspa 支持 gRPC 吗? 不支持,只支持 WebSocket 上的 wRPC。把 16x10 的 gRPC 端口传给 KaspaRpcClient.Create 会抛异常,并提示改用 17x10(Borsh)或 18x10(JSON)。

JSON 和 Borsh 选哪个? 都可以,两者提供相同的调用。KaspaRpcEncoding.Auto 会根据 URL 路径或端口自动选择,用节点提供的那种即可。

KaspaRpcClient.Create 会自动连接吗? 不会。它返回 KaspaRpcConnection;需要调用 ConnectAsync,并用 await using 释放。

怎么在 C# 里发送 Kaspa 交易? 读取 UTXO,用 TransactionBuilder 构建并签名,用 tx.Verify 在本地校验,然后调用 SubmitTransactionAsync。本教程只在 testnet 上演示。

断线后订阅还在吗? 不在。开启 AutoReconnect 后连接会恢复,但需要重新订阅。

怎么确保用的是 testnet? 显式传入 Network.Testnet,并检查地址以 kaspatest: 开头。不要依赖 ExtPubKey.Parse 来判断网络。

能离线签名吗? 可以。构建和签名不需要连接。BuildPSKT 可生成部分签名交易。

能用于真实资金吗? 不建议。NKaspa 是 alpha 版本、未经审计。请先在 testnet 上用小额测试,并自行评估风险。

下一步

  • 收款:samples/AspNetCheckout(仅在 main 上)以及收款教程(发布后补链接)。
  • 生成地址:docs/tutorials/generate-kaspa-address-csharp.md。
  • NKaspa 仓库中的发送教程:docs/tutorials/send-kaspa-transaction-dotnet.md。
  • PSKT 多签:samples/PsktMultisig。节点查询:samples/NodeQuery。花费:samples/Spend。
  • NBitcoin 用户:docs/NBitcoin-Mapping.en.md。
  • NKaspaDoc(API 参考与指南,GitHub 仓库):https://github.com/lifeme2027/NKaspaDoc

觉得有用就去 GitHub 给 NKaspa 点个 Star:github.com/lifeme2027/NKaspa。dotnet add package NKaspa --version 0.1.11-alpha,在 testnet 上跑一遍,遇到问题欢迎提 issue。

免责声明: NKaspa 目前是 alpha 版本(0.1.11-alpha),未经安全审计。它是社区项目,不是 Kaspa 官方项目。本教程只在 Kaspa testnet 上演示(仅 testnet-10),请只用测试币和小额。不要用于真实资金。公共节点由第三方运营,不保证可用。

Top comments (0)