コンテンツにスキップ
このページの翻訳は LLM によって生成されています。誤りに気づいた場合は GitHub で issue を開いてお知らせください。

SaiコントラクトのWASM・EVM連携

Cosmos、WASMとEVMのウォレットから、Saiの無期限先物とSLPボールトに連携するためのリファレンスです。提示されたコードの全呼び出しを、パラメーター、前提条件、例とともに説明します。

環境: メインネットとTestnet-2に対応します。ネットワークごとの資産経路とアドレスは「担保・支払いトークン」を参照してください。


  • BANK: Cosmos側のコイン。tf/... などのデノム。
  • 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のガス設定

    • contract.method.estimateGas(...) に1.1倍の余裕を加えます。
    • 推定失敗時は gasLimit = 5_000_000、gasPrice = 1 gwei。
  • ExecuteResultの正規化

    • EVMレシートを、height、transactionHash、gasUsed/Wanted、ログごとのtopics・dataを含む events を持つCosmJS形式に変換します。
  • エクスプローラーのリンク

    • UI例は height と hash から blockUrl と txUrl を作ります。

EVM連携、インターフェース経由

Section titled “EVM連携、インターフェース経由”

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が変換・結合します。

前提条件:

  • 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> } }

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 を追加し、不足分を変換します。

クライアント: 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>" }]

持分デノムの取得: 実行前に一度取得します。

{ "get_vault_share_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はrunnerだけでなく signer、WASMは runner が必要。
  • 通知: 待機・成功・失敗を表示し、成功時は height・hash とエクスプローラーを示します。
  • EVMデバッグ: リバート時はarchive 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は持分デノム取得済み。署名者。
  • 償還: 持分はBANK単位。EVMは sendToEvm 指定。署名者。
  • 取り消し: unlock_epoch と署名者。

  • estimateGasWithFallback は推定を2倍にし、最低 2_500_000、失敗時 5_000_000 / 1 gwei にします。Wasmプリコンパイルの推定は下限として信頼できず、料率ゼロではガス代も無料なので、大きめの余裕を設けます。
  • receiptToExecuteResult はCosmJSに似た形式へ正規化します。
  • MsgConvertEvmToCoin はWASMでBANK不足時に任意で追加できます。
  • 自動配分: 残高と希望に応じてBANK・ERC-20に分け、各側で桁変換して合計します。