跳转到内容
本页面的翻译由 LLM 生成。如果你发现翻译有误,请在 GitHub 上提交 issue 告诉我们。

Sai 合约的 WASM 与 EVM 集成

本参考介绍从 Cosmos、WASM 和 EVM 钱包集成 Sai 永续合约及 SLP 金库,涵盖所示代码的全部调用、参数、前提条件和示例。

环境: 支持主网和 Testnet-2。代币路径和地址随网络变化,见“抵押资产与支付代币”。


  • BANK: Cosmos 侧代币,如 tf/... denom。
  • ERC-20: EVM 侧代币,如 0x...。
  • Interface,EVM: 接收 JSON 编码 WASM 消息并跨栈转发给 Sai 模块的单一合约。
  • Perp: Cosmos 侧永续合约路由,通过 saiContracts.perp 引用。
  • Vault: 支持充值、提现和赎回的 SLP 金库,使用 Cosmos Bech32 地址。

所示代码支持 usd、stnibi。这些值用于 WASM 的附带资金和 EVM 接口的 ERC-20 参数。

路径符号显示名称bankDenomevmAddr默认 ERC-20 小数位数
usdUSDCUSDCerc20/0x0829F361A05D993d5CEb035cA6DF3446b060970b0x0829F361A05D993d5CEb035cA6DF3446b060970b6
stnibistNIBI流动性质押 Nibiru,封装版tf/nibi1udqqx30cw8nwjxtl4l28ym9hhrp933zlq8dqxfjzcdhvl8y24zcqpzmh8m/ampNIBI0xcA0a9Fb5FBF692fa12fD13c0A900EC56Bb3f0a7b6
路径符号显示名称bankDenomevmAddr默认 ERC-20 小数位数
usdUSDCUSDCtf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/usdc0xAb68f1D1d91854383fd4Df9016E3040D03e8191a6
stnibistNIBI流动性质押 Nibiru,封装版tf/nibi1pc2mmwcqhvzn9vsm0umpu40yzl6gfy6nucwn7g/stnibi0xCae3d404AFB50016154a4B18091351065154E9bD6

WASM 使用 BANK 最小单位,默认按 6 位小数缩放。EVM 同时传入 BANK 和 ERC-20 部分,再合并为总额。


  • 签名者

    • WASM:{ fromBech32Addr, runner: NibiruTxClient }。
    • EVM:{ fromHexAddr, runner?: ContractRunner, signer?: Signer }。发送交易需要 signer。
  • TradeType 推导

    • 市价:Market → "trade"。

    • 限价和止损入场通过比较 limitPriceUsd 与 open_price 推导。

      • 多头:limit > open_price 为 stop,否则为 limit。
      • 空头:limit < open_price 为 stop,否则为 limit。
  • EVM Gas 策略

    • 用 contract.method.estimateGas(...) 估算并乘 1.1。
    • 失败时回退为 gasLimit = 5_000_000、gasPrice = 1 gwei。
  • ExecuteResult 归一化

    • EVM 收据转换为类似 CosmJS 的结果,包含 height、transactionHash、gasUsed/Wanted 及按日志保留 topics、data 的 events。
  • 浏览器链接

    • 界面示例根据 height 和 hash 生成 blockUrl、txUrl。

全部 EVM 流程调用 saiEvmInterfaceFromChainType(chainType) 返回的单一 Interface。发送 JSON 编码的 wasmMsgBytes 及代币数量、地址。必须具备 EVM signer。

方法: openTrade(wasmMsgBytes, collateralIndex, totalAmountBankUnits, useErc20Amount)

wasmMsg,JSON:

{
"open_trade": {
"market_index": "MarketIndex(N)",
"leverage": "string",
"long": true,
"collateral_index": "TokenIndex(N)",
"trade_type": "trade|limit|stop",
"open_price": "<market price or limit price>",
"tp": "optional string",
"sl": "optional string",
"slippage_p": "0.01",
"is_evm_origin": true
}
}

slippage_p 是 CosmWasm Decimal 比例,"0.01" 表示 1%。

数量计算:

  • BANK 部分按 6 位小数计算。
  • ERC-20 按代币小数位,默认 6;存在时使用 collateral.evmDefaultDecimals。
  • totalAmount = bankAmount + evmAmount,链上 Interface 处理跨 VM 转换和合并。

前提条件:

  • orderType === "Market" 需要 open_price。
  • 必须有 collateral,且 amtTrade > 0。
  • 智能分配按钱包余额和偏好拆分 BANK、ERC-20。

方法: executeSimpleFunctions(wasmMsgBytes)

wasmMsg:

{ "close_trade": { "trade_index": "UserTradeIndex(<N>)" } }

方法: executeSimpleFunctions(wasmMsgBytes)

wasmMsg:

{ "create_referrer_code": { "code": "<your_code>" } }

方法: executeSimpleFunctions(wasmMsgBytes)

wasmMsg:

{ "redeem_referrer_code": { "code": "<partner_code>" } }

方法:

contract.deposit(
wasmMsgBytes, // { "deposit": {} }
depositAmountTotalBankUnits, // BANK units (includes ERC‑20 converted -> BANK)
useErc20Amount, // ERC‑20 units (token decimals)
vaultAddress, // bech32 vault contract
collateralErc20, // ERC‑20 address
true, // sendToEvm on triggers
overrides
)

说明:

  • 必要时将 ERC-20 部分换算为 BANK 单位后加入 depositAmountTotal,小数位可能不同。

方法:

contract.makeWithdrawRequest(
vaultAddress,
wasmMsgBytes, // { "make_withdraw_request": {} }
totalShares, // BANK units to lock (sharesBANK + sharesFromERC20)
useErc20Amount, // shares to bridge from ERC‑20 (BANK-scaled already)
overrides
)

方法:

contract.redeem(
wasmMsgBytes, // { "redeem": { "shares": "..." } }
vaultAddress,
shares, // BANK units
sendToEvm, // boolean
overrides
)

方法: executeVaultSimpleFunctions(wasmMsgBytes, vaultAddress)

wasmMsg:

{ "cancel_withdraw_request": { "unlock_epoch": <number> } }

WASM 使用 NibiruTxClient 的 signer.runner.wasmClient。资金以 Coin { denom, amount } 传入,amount 为 BANK 单位,默认缩放 1e6 倍。

客户端: wasmClient.execute(sender, saiContracts.perp, msg, fee, memo, funds)

消息:

{
"open_trade": {
"market_index": "MarketIndex(N)",
"leverage": "string",
"long": true,
"collateral_index": "TokenIndex(N)",
"trade_type": "trade|limit|stop",
"open_price": "<market price or limit price>",
"slippage_p": "0.01",
"tp": "optional string",
"sl": "optional string",
"is_evm_origin": false
}
}

资金:

[{ "denom": "<collateral.bankDenom>", "amount": "<BANK units>" }]

前提条件:

  • Market 需要 open_price。
  • Limit/Stop 需要 limitPriceUsd。

可选的 ERC-20 → BANK 转换

  • BANK 余额不足时,前置 MsgConvertEvmToCoin 将不足部分由 ERC-20 转为 BANK。

客户端: execute(sender, saiContracts.perp, { close_trade: { trade_index: "UserTradeIndex(N)" } }, fee)

客户端: execute(sender, saiContracts.perp, { create_referrer_code: { code } }, fee)

客户端: execute(sender, saiContracts.perp, { redeem_referrer_code: { code } }, fee)

客户端: execute(sender, vaultSelection.address, { deposit: {} }, fee, memo, [funds])

资金:

[{ "denom": "<collateral.bankDenom>", "amount": "<BANK units>" }]

查询份额 denom: 执行前查询一次。

{ "get_vault_share_denom": {} }

客户端:

  • 使用份额 denom 的资金执行。
[{ "denom": "<shareDenom>", "amount": "<shares BANK units>" }]

消息: { "make_withdraw_request": {} }

客户端: execute(sender, vaultAddress, { redeem: { shares: "..." } }, fee),无需附带资金。

客户端: execute(sender, vaultAddress, { cancel_withdraw_request: { unlock_epoch } }, fee)


  • BANK 默认 6 位小数,界面金额按 display × 10^decimals 缩放。
  • EVM 充值、提现混用 BANK 和 ERC-20 时,先统一小数位再求和。
  • 验证缩放后金额有限、非负且非零。

  • 签名者: EVM 需要 signer,仅 runner 不够;WASM 需要 runner。
  • 通知: 显示待处理、成功、失败,成功时附 height、hash 和浏览器链接。
  • EVM 调试: 回退时,用归档 RPC 的 debug_traceTransaction 提取最深层 calls[-1].error。

以下简化示例假设已选择 chainType、saiContracts、interfaceAddress、collateral 等。

const msg = { open_trade: { /* ...see schema above... */ is_evm_origin: true } }
const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(msg))
const gas = await estimateGasWithFallback(
() => contract.openTrade.estimateGas(wasmMsgBytes, collateralIndex, totalAmount, useErc20),
{ msg }
)
const tx = await contract.openTrade(wasmMsgBytes, collateralIndex, totalAmount, useErc20, toTxOverrides(gas))
const res = await tx.wait() // -> map to ExecuteResult if desired
await wasm.execute(
bech32,
saiContracts.perp,
{ open_trade: { /* ... */, is_evm_origin: false } },
"auto",
undefined,
[ { denom: collateral.bankDenom, amount: scaledAmount } ]
)
const msg = { deposit: {} }
const wasmMsgBytes = ethers.toUtf8Bytes(JSON.stringify(msg))
const bankTotal = bankAmount + toBankUnits(erc20Amount, erc20Dec, 6)
const tx = await contract.deposit(wasmMsgBytes, bankTotal, erc20Amount, vaultAddr, collateral.evmAddr, true, overrides)
await tx.wait()
const shareDenom = await wasm.queryContractSmart(vaultAddr, { get_vault_share_denom: {} })
await wasm.execute(
bech32,
vaultAddr,
{ make_withdraw_request: {} },
"auto",
undefined,
[ { denom: shareDenom, amount: shares } ]
)

  • 开仓: 金额>0;市价有 open_price;限价、止损入场有 limitPriceUsd;collateral.index 可解析;签名者就绪。
  • 平仓: 设置 userTradeIndex,签名者就绪。
  • 推荐: code 非空,签名者就绪。
  • 充值: 正确缩放,EVM 中将 ERC-20 转为 BANK 后合计,签名者就绪。
  • 提现申请: 份额用 BANK 单位;WASM 已获取份额 denom;签名者就绪。
  • 赎回: 份额用 BANK 单位;EVM 设置 sendToEvm;签名者就绪。
  • 取消: 设置 unlock_epoch,签名者就绪。

  • estimateGasWithFallback 将估算翻倍,最低 2_500_000,失败时用 5_000_000 / 1 gwei。Wasm 预编译的估算不是可靠下限,零费率下 Gas 免费,因此有意保留较大余量。
  • receiptToExecuteResult 归一化为类似 CosmJS 的格式。
  • MsgConvertEvmToCoin 可在 WASM 的 BANK 不足时追加。
  • 智能分配: 按余额和偏好拆分 BANK、ERC-20,分别缩放再合并。