Zenith EVM: Testnetドキュメント

EVM クイックスタート

以下のセクションでは、標準的なEthereumツールを使用して、シンプルなSolidityコントラクトをZenith EVM Testnetにデプロイする手順を説明します。

Canton側のインタラクションおよびGatewayについて(Zenith EVM testnetと基盤となるCantonテストネットワークを接続するもの)を詳しく調べるには、Canton and Gateway developer guidesをご覧ください。

Zenith EVMはRethベースの環境であり、標準的なEthereumのJSON-RPCエンドポイントを公開しています。ほとんどのEVMワークフローは、ネットワーク設定を変更するだけで利用できます。

ウォレットにtestnetを追加する

MetaMaskまたは他のEVMウォレットに、カスタムネットワークとしてZenith EVM Testnetを追加してください。

FieldValue
Network nameZenith EVM Testnet
RPC URLhttps://rpc.testnet.zenith.network/
RPC nameZenith EVM Testnet RPC
Chain ID936485
Token symbolZTH
Block explorer URLhttps://explorer.testnet.zenith.network/

ウォレットへの入金

テストネットトークンの取得

Zenith EVM testnetフォーセットをご利用ください:

ZTHはZenith EVM testnetのネイティブガストークンです。小数点以下18桁を持ち、コントラクトのデプロイやトランザクションの送信に必要です。

基本的なEVMクイックスタートでは、ZTHのみが必要です。テストネット用のwCTN(Wrapped Canton)は、ローカルのCantonテストネットワークと直接やり取りするCantonネイティブのワークフローにのみ関連します。

シンプルなコントラクトの作成

以下は、Zenith EVMへのコントラクトデプロイをテストするために使用できるシンプルなコントラクトの例です。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Counter {
uint256 public number;
event NumberChanged(uint256 newNumber);
function setNumber(uint256 newNumber) external {
number = newNumber;
emit NumberChanged(newNumber);
}
function increment() external {
number += 1;
emit NumberChanged(number);
}
}

環境の設定

テスト用に、RPC URL、チェーンID、デプロイヤーの秘密鍵を環境変数として設定できます。

export ZENITH_RPC_URL="<zenith-evm-rpc-url>"
export ZENITH_CHAIN_ID="<zenith-evm-chain-id>"
export PRIVATE_KEY="<your-private-key>"

重要な注意事項: 秘密鍵を環境変数として保存することは開発・テスト目的では一般的ですが、本番環境では以下の点に注意してください:

  • .envファイルをバージョン管理にコミットしない
  • .gitignoreに.envを追加する
  • 本番環境へのデプロイにはシークレットマネージャー(例:AWS Secrets Manager、HashiCorp Vault)の使用を検討する
  • 必要な資金のみを持つ専用のデプロイヤーウォレットを使用する

Foundryでデプロイする

以下の手順に従って、Foundryを使用してコントラクトをデプロイしてください:

forge init zenith-counter
cd zenith-counter
# Replace the default Counter with the contract above.
forge build
forge create \
--rpc-url "$ZENITH_RPC_URL" \
--private-key "$PRIVATE_KEY" \
src/Counter.sol:Counter

デプロイコマンドは、デプロイされたコントラクトアドレスとトランザクションハッシュを返します。

コントラクトとのインタラクション

コントラクトがデプロイされたら、Foundryのcastツールを使用して、コマンドラインから直接その状態を読み取ったり、トランザクションを送信したりすることができます。

export COUNTER_ADDRESS="<deployed-contract-address>"
cast call \
--rpc-url "$ZENITH_RPC_URL" \
"$COUNTER_ADDRESS" \
"number()(uint256)"
cast send \
--rpc-url "$ZENITH_RPC_URL" \
--private-key "$PRIVATE_KEY" \
"$COUNTER_ADDRESS" \
"increment()"

トランザクションを確認する

エクスプローラーはCantonレイヤーとEVMレイヤーの両方をリアルタイムで監視しており、現在CantonのアップデートとEVMブロックを同期して追跡しています。

Zenith Explorerを開く:

上部の検索バーを使用してEVMトランザクションハッシュを検索してください。エクスプローラーでは以下のビューが利用できます:

  • ブロック: EVMブロック履歴を閲覧
  • トランザクション: インデックスされたEVMトランザクションの全リスト
  • リンクされたトランザクション: 対応するCantonアップデートにリンクされたEVMトランザクションを表示
  • コントラクト: デプロイされたコントラクトの詳細を表示

トランザクションページには、標準的なEVMトランザクションの詳細、ログ、およびコントラクトのインタラクションが表示されます。

リンクされたトランザクションビューはZenith独自の機能であり、各EVMトランザクションに対応するCantonアップデートIDを表示することで、EVMからCantonまでのトランザクションの完全なライフサイクルを追跡できます。

平均レイテンシーに関する注意: ダッシュボード上の3〜4秒の平均レイテンシーは、Canonテストネットワーク上での送信から最終確定までのエンドツーエンドのトランザクション処理時間を意味します。

内部では、RPC経由で送信されたEVMトランザクションはZenith EVMシーケンサーによって収集され、Canton仲介のブロックプロポーザルに組み込まれます。これにより、標準的なEVM開発者のワークフローは変わりません。

EVM開発者向けの追加リソース

EVMの基礎、Solidityの開発パターン、およびZenith EVMが完全に互換性を持つ標準ツールについてより深く理解するには、Ethereumの開発者ドキュメントを参照してください:

EVM向け開発者ガイド

以下に、Zenith EVMと直接やり取りするための開発者ガイドを掲載しています。

Canonサイドのインタラクションおよびゲートウェイ(Zenith EVMテストネットと基盤となるCantonテストネットワークを接続)については、CantonおよびGateway開発者ガイドの専用セクションをご覧ください。

MetaMaskの設定

  1. MetaMaskを開く
  2. ネットワークのドロップダウンを選択
  3. カスタムネットワークを追加を選択
  4. 以下のようにZenith EVMテストネットの詳細を入力
FieldValue
Network nameZenith EVM Testnet
RPC URLhttps://rpc.testnet.zenith.network/
RPC nameZenith EVM Testnet RPC
Chain ID936485
Token symbolZTH
Block explorer URLhttps://explorer.testnet.zenith.network/

または、ワンクリックでネットワークを追加することもできます:

ネットワークを追加した後、https://explorer.testnet.zenith.network/faucetでテストネットのZTHをリクエストしてください。

ガスと手数料

現在のZenith EVMテストネットは、実行レイヤーで標準的なEVMガスセマンティクスを使用しています。ガスの見積もりとトランザクションレシートは、eth_estimateGasやeth_getTransactionReceiptなどの標準的なEthereum JSON-RPCメソッドを通じて利用できます。

ガスはZenith EVMテストネットのネイティブ通貨であるZTHで支払われます。

Foundryの設定

FoundryはZenith EVMですぐに使用できます。必要な設定はテストネットのRPCエンドポイントを追加することだけです。以下の例では、ビルド → デプロイ → トランザクション → 読み取りの完全なワークフローを示すために、シンプルなCounterコントラクトを使用しています。

foundry.tomlにZenith EVMのRPCエイリアスを追加します。

[rpc_endpoints]
zenith_testnet = "${ZENITH_RPC_URL}"

環境変数は、変更される可能性のある値に使用してください。

export ZENITH_RPC_URL="<zenith-evm-rpc-url>"
export PRIVATE_KEY="<your-private-key>"

ビルドとデプロイ:

forge build
forge create \
--rpc-url zenith_testnet \
--private-key "$PRIVATE_KEY" \
src/Counter.sol:Counter

トランザクションの送信:

cast send \
--rpc-url zenith_testnet \
--private-key "$PRIVATE_KEY" \
"$COUNTER_ADDRESS" \
"increment()"

状態の読み取り:

cast call \
--rpc-url zenith_testnet \
"$COUNTER_ADDRESS" \
"number()(uint256)"

Hardhatの設定

HardhatプロジェクトをZenith EVMに接続するには、testnet RPCを指すネットワークエントリを追加します。以下のセットアップではTypeScriptと公式のHardhat Toolboxプラグインを使用していますが、JavaScriptの設定でも同様に動作します。

必要に応じてHardhatをインストールしてください:

npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox

Hardhatの設定ファイルを作成または更新してください:

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: "0.8.24",
networks: {
zenithTestnet: {
url: process.env.ZENITH_RPC_URL || "",
chainId: Number(process.env.ZENITH_CHAIN_ID || "0"),
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
};
export default config;

環境を設定してください:

export ZENITH_RPC_URL="<zenith-evm-rpc-url>"
export ZENITH_CHAIN_ID="<zenith-evm-chain-id>"
export PRIVATE_KEY="<your-private-key>"

デプロイ:

npx hardhat compile
npx hardhat run scripts/deploy.ts --network zenithTestnet

デプロイスクリプトの例:

import { ethers } from "hardhat";
async function main() {
const Counter = await ethers.getContractFactory("Counter");
const counter = await Counter.deploy();
await counter.waitForDeployment();
console.log("Counter deployed to:", await counter.getAddress());
console.log("Deployment tx:", counter.deploymentTransaction()?.hash);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

Remixの設定

Remixはローカルへのインストールが不要で、MetaMaskをZenith EVM testnetに接続するだけで、ブラウザから直接デプロイできます。

MetaMaskを通じてRemixを使用してください。

  1. MetaMaskにZenith EVM Testnetを追加してください。
  2. testnet ZTHでアカウントに資金を追加してください。
  3. Remixを開いてください。
  4. Solidityコントラクトをコンパイルしてください。
  5. Deploy & Run Transactionsで、Injected Provider - MetaMaskを選択してください。
  6. MetaMaskがZenith EVM Testnetに接続されていることを確認してください。
  7. 任意のEVMチェーンと同様にデプロイしてください。

RemixはMetaMaskを通じてZenith EVM RPCエンドポイントにトランザクションを送信します。

注意:開発者はRemixの「External HTTP Provider」オプションを使用して、RPC URLを直接入力することもできます。

スマートコントラクトのデプロイ

Zenith EVMへのデプロイは、EthereumまたはEVM互換チェーンへのデプロイと同一です。以下の表は、変わらない点と注意すべき点をまとめたものです。

トピックガイダンス
コンパイラー通常のSolidityコンパイラー設定を使用してください。
デプロイメント標準的なEVMデプロイメントトランザクションを使用してください。
ガストークンZTHでガスを支払います。
ノンス通常のEthereumアカウントのノンス動作を使用します。
レシート標準的なトランザクションレシートを使用します。
イベント標準的なEVMログとABIデコードを使用します。
検証コントラクトの検証は、Zenith EVMのtestnetエクスプローラーでサポートされています。
ランダム性Ethereum Beacon Chainスタイルのバリデーター選択ランダム性にblock.prevrandaoを使用しないでください。コントラクトがランダム性を必要とする場合は、Zenith EVMに適したアプリケーションレベルのランダム性ソースまたはオラクルパターンを使用してください。

コントラクトとのインタラクション

コントラクトがデプロイされたら、標準的なEthereumライブラリを使用してその状態を読み取り、トランザクションを送信できます。以下の例は、デプロイ手順で使用した同じCounterコントラクトを使用しており、Ethereumでの動作と同様に機能します。プロバイダーをZenith EVM RPCエンドポイントに向けるだけです。

Ethers.jsの例

import { Contract, JsonRpcProvider, Wallet } from "ethers";
import counterAbi from "./Counter.abi.json" assert { type: "json" };
const provider = new JsonRpcProvider(process.env.ZENITH_RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const counter = new Contract(
process.env.COUNTER_ADDRESS!,
counterAbi,
wallet
);
const before = await counter.number();
console.log("Before:", before.toString());
const tx = await counter.increment();
console.log("Tx hash:", tx.hash);
const receipt = await tx.wait();
console.log("Included in block:", receipt?.blockNumber);
const after = await counter.number();
console.log("After:", after.toString());

Web3.jsの例

import Web3 from "web3";
import counterAbi from "./Counter.abi.json" assert { type: "json" };
const web3 = new Web3(process.env.ZENITH_RPC_URL!);
const account = web3.eth.accounts.privateKeyToAccount(process.env.PRIVATE_KEY!);
web3.eth.accounts.wallet.add(account);
const counter = new web3.eth.Contract(
counterAbi as any,
process.env.COUNTER_ADDRESS
);
const before = await counter.methods.number().call();
console.log("Before:", before);
const receipt = await counter.methods.increment().send({
from: account.address,
});
console.log("Tx hash:", receipt.transactionHash);

Zenith EVM RPCからトランザクションレシートが返された時点で、トランザクションはCantonを通じてファイナライズされています。確率的ファイナリティを持つチェーンのように、複数のブロック確認を待つ必要はありません。

Cantonワークフローの詳細

Canton固有のワークフローをさらに詳しく調べるには、以下のリソースが参考になります:

リソースリンク用途
Canton Network ドキュメントhttps://docs.canton.network/Canton、Daml、SDK、およびアプリケーション開発のためのシングルエントリーポイント。
Global Synchronizer ドキュメントhttps://docs.canton.network/global-synchronizer/understand/overviewバリデーターおよびノードの運用と、LocalNet / DevNet / TestNet / MainNet 環境モデル。
Canton API リファレンスhttps://docs.canton.network/api-referenceLedger API(gRPC および JSON)、トークン標準および Canton Coin OpenAPI エンドポイント、Wallet SDK および dApp SDK クライアントライブラリ。
Canton 上でのアプリ開発https://docs.canton.network/appdev/get-started/choose-your-pathロールベースの学習パス、Daml SDK およびツールのセットアップ、段階的なモジュールトラック。

ネットワーク情報

Testnet の詳細

Zenith EVM Testnet は、コントラクトのデプロイ、トランザクションの開始、本番環境への移行前のネットワーク検証を行える公開テスト環境です。以下の情報を使用してウォレットまたはツールを設定し、フォーセットから testnet トークンを入手してください。

FieldValue
Network nameZenith EVM Testnet
Chain ID936485
Zenit EVM Testnet RPChttps://rpc.testnet.zenith.network/
Explorerhttps://explorer.testnet.zenith.network/
Faucethttps://explorer.testnet.zenith.network/faucet
Native currency nameZenith token
Currency symbolZTH
Decimals18

JSON-RPC

Zenith EVM は標準の Ethereum JSON-RPC エンドポイントを公開しており、Ethereum JSON-RPC API を実装した Reth に含まれる標準 JSON-RPC メソッドをサポートしています。よく使用されるメソッドは以下のとおりです:

カテゴリメソッド
チェーンのステータスeth_chainId, net_version, web3_clientVersion, eth_blockNumber
アカウントと残高eth_getBalance, eth_getTransactionCount, eth_getCode, eth_getStorageAt
呼び出しとガスeth_call, eth_estimateGas, eth_gasPrice, eth_feeHistory
トランザクションeth_sendRawTransaction, eth_getTransactionByHash, eth_getTransactionReceipt
ブロックeth_getBlockByHash, eth_getBlockByNumber, eth_getBlockTransactionCountByHash, eth_getBlockTransactionCountByNumber
ログeth_getLogs, eth_newFilter, eth_getFilterChanges, eth_uninstallFilter

サポートされているメソッドの完全なリストについては、Ethereum JSON-RPC API ドキュメントを参照してください。

公開 Ethereum JSON-RPC インターフェースを通じて送信されたトランザクションは、開発者の観点からは標準の EVM 送信モデルに従います。内部的には、Zenith EVM がそれらを Sequencer および Canton を介したブロックプロポーザルフローを通じてルーティングします。

基本的な JSON-RPC リクエスト

RPC リクエストを送信するには、Ethereum ノードに対して使用するのと同じリクエスト形式を使用できます。

curl "$ZENITH_RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_chainId",
"params": [],
"id": 1
}'
curl "$ZENITH_RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'

カスタム RPC メソッド

Zenith EVM は、公開 testnet RPC エンドポイントでカスタム RPC メソッドを公開していません。

一部の Zenith バリデーターインフラストラクチャは、Canton を介したブロック生成中に blockBuilder_buildBlock などの内部ブロックビルダー RPC メソッドを使用します。これらのメソッドは公開開発者 RPC サーフェスの一部ではなく、標準的な EVM アプリケーション開発には必要ありません。

Explorer

エクスプローラーはCantonレイヤーとEVMレイヤーの両方をリアルタイムで監視しており、現在CantonのアップデートとEVMブロックを同期してトラッキングしています。

Zenith Explorerを開く:

上部の検索バーを使用して、EVMトランザクションハッシュを検索してください。エクスプローラーでは以下のビューを提供しています:

  • Blocks:EVMブロック履歴の閲覧
  • Transactions:インデックス済みEVMトランザクションの全一覧
  • Linked Transactions:対応するCantonアップデートにリンクされたEVMトランザクションを表示
  • Contracts:デプロイ済みコントラクトの詳細を表示

トランザクションページには、標準的なEVMトランザクションの詳細、ログ、およびコントラクトのインタラクションが表示されます。

平均レイテンシーに関する注記: ダッシュボードに表示される3〜4秒の平均レイテンシーは、Cantonテストネットワーク上での送信から最終確定までのエンドツーエンドのトランザクション処理時間を意味します。

Linked TransactionsビューはZenith独自の機能であり、各EVMトランザクションに対応するCantonアップデートIDを表示することで、EVMからCantonまでのトランザクションのライフサイクル全体をトレースすることができます。

CantonおよびGateway開発者ガイド

このガイドでは、お客様独自のCantonパーティシパントをZenith EVM Testnetネットワークに接続する方法と、Zenith Gatewayを使用してCantonとZenith EVM間でトークンを移動させる方法について説明します。

目次

CantonからZenith EVMを操作する

Zenith EVM TestnetはZenithが運営するフル機能のCantonテストネットワーク上で動作しています。お客様独自のCantonパーティシパントノードをこのネットワークに接続し、独自のパーティやDamlアプリケーションをホストし、Zenith Gatewayを通じてCantonとZenith EVM間でトークンを移動させることができます。

このセクションでは、パーティシパントの接続方法、Gatewayに向けた準備、およびアプリケーションの接続方法について説明します。

  • 注記: Zenith EVM Testnetの背後にあるCantonテストネットワークは、Zenithが運営する独自のプライベートシンクロナイザーを持つ独立したネットワークです。Cantonテストネットワーク情報に記載されているCantonバージョンを実行しているパーティシパントであれば接続可能です。これはCanton Networkの公式DevNetまたはTestNetではなく、Global Synchronizerでもありません。Canton DevNetまたはTestNet上のパーティ、ホールディング、DARはここでは表示されず、逆も同様です。

独自パーティシパントでできること

Zenith testnetシンクロナイザーに接続されたパーティシパントでは、以下のことが可能です:

  • 独自のCantonパーティをホストし、Zenith EVMから送信されたトークンを受信する;
  • そのトークンのデスティネーションアドレスのメールボックスパーティを通じて(仕組みの説明を参照)、登録済みトークンを自分のパーティからZenith EVMアドレスに送信する。CantonパーティのInboxアドレスはデスティネーションには指定できない;
  • EVMに触れることなく、Cantonパーティ間でGateway発行のトークンを転送する;
  • Zenith EVM testnetが決済を行う同じ台帳に対して、独自のDamlアプリケーションを実行する。

パーティシパントを接続しても、以下のことは行われません:

  • Zenith EVMバリデーターになること、またはEVMブロックを確認するパーティシパントのセットへの追加。ただし、自分のパーティのZenith EVMへの転送に関するGatewayの決済は確認するため、そのような転送が保留中の間はオンラインを維持してください(パーティシパントをオンラインに保つを参照);
  • 他のパーティシパントのプライベートCantonデータへのアクセス;
  • Zenithや他者のコントラクト上でパーティが行動することへの自動的な承認;
  • EVMブロック自体のビルド。Gatewayの2つのルートはこれを必要とします:GatewayService_CrossToEvmと、WrapperFactoryを経由したワンステップリターン。このtestnetでパーティシパントができないことを参照してください。

オンボーディングの概要

  1. Cantonビルドをダウンロードし、Zenithバンドル(DAR)へのアクセス権を取得するためにZenithに連絡する。
  2. パーティシパントを設定して起動する。
  3. Zenith testnetシンクロナイザーに接続する。
  4. 接続を確認する。
  5. 独自のDARとともに、Zenith DARをアップロードしてベットする。
  6. パーティとLedger APIユーザーを作成する。
  7. アプリケーションを接続する。

お客様のCantonパーティシパントをZenithのシンクロナイザーに接続するには、Zenithバンドルへのアクセスが必要ですが、このアクセスは公開されていません。Gatewayへのアクセスをリクエストするには、[email protected]からZenithに連絡してください。Zenithがバンドルを共有します。

シンクロナイザーは、以下の Canton バージョンを実行している参加者であれば誰でも接続できます。接続の詳細は下の表で確認できます。Gateway の値は Gateway network information に記載されています。

  • 注意: EVM 側のすべての機能は、参加者を接続したり Zenith バンドルへのアクセス権を持ったりしなくてもテストできます。

Canton テストネットワーク情報

FieldValue
Participant registration policyOpen: the synchronizer admits any participant that runs the Canton build listed below
Canton buildCanton open source 3.6.0-snapshot.20260902.20200.0.v90a37b4c, a snapshot published by Digital Asset. Direct download: a gzipped tar archive (about 300 MB) that contains lib/canton-open-source-3.6.0-snapshot.20260902.20200.0.v90a37b4c.jar. Step 1 shows how to extract it. SHA-256 of the jar: ab628b95ef6211ff978bd6bcbbae18f4f17ed8033eb7691a704ebbf32bbe030b.
Zenith bundleThe Zenith DARs and a MANIFEST file, in one directory. Contact Zenith at [email protected] to have access The DARs are listed in step 5.
Protocol versiondev (a development protocol version; see the note below)
Recommended Daml SDK version3.6.1-snapshot.20260903.34.0.v027a1da5
Sequencer public API endpointhttps://synchronizer.testnet.zenith.network
Synchronizer IDda::1220daf7fcc9d67e83d9d884ea3a30bc4d0f7927d3f8d1b570d6296a27569dc59943. Physical synchronizer ID, for pinning the connection in step 3: da::1220daf7fcc9d67e83d9d884ea3a30bc4d0f7927d3f8d1b570d6296a27569dc59943::dev-0
TLSTLS, with a publicly trusted certificate (issued by Google Trust Services). Java’s default trust store already trusts it, so you do not need a CA certificate or certificatesPath.
Zenith EVM Testnet RPChttps://rpc.testnet.zenith.network
Zenith EVM chain ID936485

Canton Sequencer エンドポイントは、Zenith EVM Testnet RPC とは異なるエンドポイントです。参加者は Sequencer と通信します。EVM ウォレットやツールは RPC と通信します。

  • 重要: testnet シンクロナイザーは開発用の Canton プロトコルバージョンで動作します。ストックリリースを含む他の Canton バージョンはサポートされていません。 dev プロトコルバージョンは Canton ビルドごとに変わる可能性があるため、上記に記載された Canton スナップショットを厳密に実行し、起動前に SHA-256 を確認してください。

前提条件

接続する前に、以下が必要です:

  • Digital Asset からダウンロードできる Canton ビルドと、Zenith からアクセスをリクエストできる Zenith バンドル(DAR ファイル群)(下記ステップ 1 参照)、およびビルドを実行するための Java 17 または Java 21(両バージョンでの動作が確認されており、他のバージョンは未検証);
  • 参加者用の永続ストレージ(例:PostgreSQL);
  • 永続的な参加者 ID と永続的な暗号鍵;
  • Sequencer エンドポイントへのアウトバウンドネットワークアクセス;
  • 参加者の Canton コンソールまたは Admin API へのアクセス;
  • 信頼できる環境のみがアクセスできる、または認証を必要とする Daml アプリケーション向けの Ledger API(gRPC または JSON)。

警告: 参加者の Admin API を公開しないでください。Ledger API が信頼できる環境の外部からアクセス可能な場合は、必ず認証を要求するよう設定してください。

1. Canton ビルドと Zenith バンドルのダウンロード

Zenith Gateway を使用したワークフローのテストや、Canton 参加者を Zenith EVM Testnet に接続したい場合は、[email protected] 宛てに Zenith へご連絡いただき、バンドルへのアクセスをリクエストしてください。バンドルは ステップ 5 の dars/ に記載された 6 つの DAR ファイルと、MANIFEST ファイルを含むディレクトリです。MANIFEST には、バンドルがビルドされたコミットが記載されており、Canton jar を bin/canton.jar として含むそのビルドの各ファイルの SHA-256 がリストアップされています。また、Zenith のサービスバイナリなど、お手元のコピーに含まれていないファイルが記載されている場合もあるため、以下のチェックでは jar と DAR の行のみを読み込みます。

Canton ビルドを Digital Asset の公開レジストリからダウンロードし、その jar をバンドルディレクトリに bin/canton.jar として保存してください。このガイドのコマンドはそこから実行します。ダウンロードファイルは約 300 MB の gzip 圧縮 tar アーカイブで、tar コマンドは jar のみを抽出します:

export BUNDLE=<the bundle directory>
mkdir -p "$BUNDLE/bin"
curl -fsSL https://europe-docker.pkg.dev/v2/da-images/public-unstable/components/canton-open-source/blobs/sha256:af613193d67938f910817a8737bd7dc0ab2ccace96d96581895ed6ef866e92fa \
| tar -xzO lib/canton-open-source-3.6.0-snapshot.20260902.20200.0.v90a37b4c.jar > "$BUNDLE/bin/canton.jar"

次に、jar、DAR、および Java バージョンを確認します:

shasum -a 256 "$BUNDLE/bin/canton.jar"
sed -n '/^# sha256$/,$p' "$BUNDLE/MANIFEST" | grep -v '^#' | grep -E ' (bin/canton\.jar|dars/.*\.dar)$' | (cd "$BUNDLE" && shasum -a 256 -c)
java -version

shasum コマンドは bin/canton.jar の公開 SHA-256 を出力する必要があります。MANIFEST チェックは 7 行(jar 用に 1 行、各 DAR 用に 1 行ずつ)を出力し、それぞれが OK で終わる必要があります。いずれかのチェックが失敗した場合は、ビルドを実行しないでください。jar が失敗した場合は再ダウンロードしてください。DAR が失敗した場合は、バンドルの新しいコピーを Zenith にご連絡ください。java -version コマンドは Java 17 または Java 21 を表示する必要があります。

2. 参加者の設定と起動

一般的なインストールガイダンスについては、Canton の Install Canton ドキュメントを参照してください。シンクロナイザーが開発プロトコルバージョンで動作しているため、3 つの設定が必要です。alpha-version-support と dev-version-support をグローバルおよび参加者自身の parameters の両方で yes に設定し、non-standard-config = yes をグローバルに設定してください。Canton は、non-standard-config なしにバージョンサポート設定のいずれかを有効にしたノードの起動を拒否します。

参加者の状態は PostgreSQL に保存されます。最初の起動前に、たとえば createdb zenith_testnet_participant を使用して空のデータベースを作成してください。

最小限の設定は次のとおりです(participant.conf)。データベースのユーザーとパスワードはご自身のものに置き換えてください:

canton {
parameters {
non-standard-config = yes
alpha-version-support = yes
dev-version-support = yes
}
participants {
myparticipant {
storage {
type = postgres
config {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
properties = {
serverName = "localhost"
portNumber = "5432"
databaseName = "zenith_testnet_participant"
user = "<database user>"
password = "<database password>"
}
}
}
admin-api { address = "127.0.0.1", port = 5012 }
ledger-api { address = "127.0.0.1", port = 5011 }
http-ledger-api { address = "127.0.0.1", port = 7575 }
parameters {
alpha-version-support = yes
dev-version-support = yes
}
}
}
}

この設定では ledger-api 配下に auth-services を設定していないため、Ledger API と JSON Ledger API はトークンなしですべてのリクエストを受け付けます。これは両方が 127.0.0.1 でリッスンしている場合にのみ許容されます。いずれかを別のアドレスにバインドする前に、ledger-api 配下で auth-services と tls を設定してください。

参加者データベースと暗号 ID の両方に永続ストレージを使用してください。この状態を削除または再初期化すると、参加者は新しい参加者 ID を取得します。また、ホストしていたパーティとその保有資産の追跡も失われます。

  • 警告: storage.type = memory は簡単な実験には適していますが、メモリストレージを使用する参加者は停止するとすべての状態(鍵、パーティ、シンクロナイザー接続、DAR、Ledger API ユーザー)を失います。再起動すると新しい参加者 ID が割り当てられるため、ステップ 3、5、6 を繰り返す必要があります。停止前に作成されたパーティは復元できません。その保有資産は台帳に残りますが、誰もそれに対して操作を行えません。保持したいデータには永続ストレージを使用してください。

インタラクティブコンソールを使用して参加者を起動してください。ステップ 2 から 6 のコンソールコマンドはそこで入力します:

java -jar "$BUNDLE/bin/canton.jar" -c participant.conf

コンソールなしで参加者を実行する場合は、daemon とステップ 3 のブートストラップスクリプト connect.canton を使用して起動すると、シンクロナイザーに接続されます:

java -jar "$BUNDLE/bin/canton.jar" daemon -c participant.conf --bootstrap connect.canton

daemon で起動した参加者に対してこのガイドのコンソールコマンドを実行するには、リモートコンソール設定を一度生成し(これにより remote-myparticipant.conf が書き込まれます)、それを使用してコンソールを開いてください:

java -jar "$BUNDLE/bin/canton.jar" generate remote-config -c participant.conf
java -jar "$BUNDLE/bin/canton.jar" -c remote-myparticipant.conf

参加者が起動したら、Canton コンソールでそのステータスを確認してください:

myparticipant.health.status

完全な参加者IDを記録してください。コンソールは表示時に識別子を短縮するため、完全な文字列を出力してください:

myparticipant.id.toProtoPrimitive

形式は PAR::myparticipant::1220 の後に64桁の16進数が続きます。この識別子はネットワークのCanton トポロジにおけるあなたの参加者を識別するため、安定したものとして扱ってください。最後の :: の後の部分があなたの参加者のネームスペースです。ステップ6のコマンドで作成したパーティはこのネームスペースを持つため、そのIDは <hint>::<namespace> の形式になります。

3. Zenith testnet シンクロナイザーへの接続

参加者のCanton コンソールで、Sequencerに接続します。最初の引数はシンクロナイザーエイリアスです。これはご自身で選択するもので、参加者にローカルなものであり、以降のコマンドでこの接続に名前を付けます。このガイドでは zenith-testnet を使用します。

val zenithSequencerUrl = "https://synchronizer.testnet.zenith.network"
myparticipant.synchronizers.connect("zenith-testnet", zenithSequencerUrl)

エンドポイントはスキームを含めて公開されているとおり正確に指定してください:http:// はプレーンgRPCで接続し、https:// はTLSで接続します。connect はスキームのないエンドポイントをエラー Scheme `null` is invalid で拒否し、certificatesPath は http:// エンドポイントに対して効果がありません。

参加者は接続を保存し、再起動後に自動で再接続します。

testnet SequencerはパブリックトラストされたTLS証明書を使用しているため、上記の呼び出しに certificatesPath は必要ありません。Javaランタイムがパブリックルート証明書を含まないカスタムトラストストアを使用している場合のみ、発行CAの証明書を含むファイルのパスを渡してください:

myparticipant.synchronizers.connect(
"zenith-testnet",
zenithSequencerUrl,
certificatesPath = "/path/to/zenith-testnet-ca.crt"
)

シンクロナイザーIDをピンする場合、 Canton test network information に公開されている物理シンクロナイザーIDを physicalSynchronizerId として渡してください。物理シンクロナイザーIDはシンクロナイザーIDに ::<protocol version>-<serial> を加えたものです。このパラメーターはプレーンなシンクロナイザーIDを受け付けません。参加者はそのURLで他のシンクロナイザーへの接続を拒否します:connect は SYNC_SERVICE_BAD_CONNECTIVITY で失敗し、メッセージにはシンクロナイザーID does not match expected が記載されます。

myparticipant.synchronizers.connect(
"zenith-testnet",
zenithSequencerUrl,
physicalSynchronizerId = Some(PhysicalSynchronizerId.tryFromString("da::1220daf7fcc9d67e83d9d884ea3a30bc4d0f7927d3f8d1b570d6296a27569dc59943::dev-0"))
)

physicalSynchronizerId と、必要な場合は certificatesPath を、最初の connect 呼び出しで渡してください。両方必要な場合は同じ呼び出しで渡してください。エイリアス zenith-testnet が登録されると、そのエイリアスでの connect は再接続のみを行います:新しいエンドポイントURLや新しい physicalSynchronizerId および certificatesPath の値は無視され、エラーは報告されません。コンソールは呼び出し前に certificatesPath ファイルを読み込むため、存在しないファイルへのパスは failed to load で失敗します。connect 呼び出しが失敗した場合はエイリアスが登録されないため、引数を修正して connect を再度呼び出すことができます。参加者を connect.canton で起動する場合は、そのスクリプト内の connect 呼び出しにこれらの引数を追加してください。

すでに登録済みの接続のシンクロナイザーIDをピンするには、切断し、modify で保存された接続を変更してから再接続してください:

myparticipant.synchronizers.disconnect("zenith-testnet")
myparticipant.synchronizers.modify(
"zenith-testnet",
_.copy(synchronizerId = Some(PhysicalSynchronizerId.tryFromString("da::1220daf7fcc9d67e83d9d884ea3a30bc4d0f7927d3f8d1b570d6296a27569dc59943::dev-0")))
)
myparticipant.synchronizers.reconnect("zenith-testnet")

無人起動の場合は、接続をブートストラップスクリプト connect.canton として保存してください。参加者の起動を待ち、未接続であれば接続し、接続がアクティブになるまで待機します:

val p = participants.local.head
utils.retry_until_true(timeout = 120.seconds) {
try { p.health.initialized() } catch { case _: Exception => false }
}
if (p.synchronizers.list_connected().isEmpty) {
p.synchronizers.connect("zenith-testnet", "https://synchronizer.testnet.zenith.network")
}
utils.retry_until_true(timeout = 60.seconds) { p.synchronizers.active("zenith-testnet") }
println("connected")

エンドポイントがJavaランタイムのデフォルトトラストストアに含まれない証明書でTLSを使用している場合は、connect 呼び出しに certificatesPath = "/path/to/zenith-testnet-ca.crt" を追加してください。参加者を java -jar "$BUNDLE/bin/canton.jar" daemon -c participant.conf --bootstrap connect.canton で起動します。参加者がシンクロナイザーに接続されると connected と表示されます。Sequencerに到達できない場合、または60秒以内に接続がアクティブにならない場合、スクリプトは失敗し参加者プロセスはステータス3で終了します\。参加者は最初の起動成功後に接続を保存するため、以降の起動では --bootstrap なしで java -jar "$BUNDLE/bin/canton.jar" daemon -c participant.conf を使用できます。その方法で起動した参加者は、Sequencerに到達できない場合に終了せず接続を再試行し続けます。

4. 接続の確認

参加者が接続しているシンクロナイザーをリストアップします:

myparticipant.synchronizers.list_connected()

結果には以下を含む1つのエントリが表示されるはずです:

  • シンクロナイザーエイリアス zenith-testnet;
  • Canton test network information に公開されているシンクロナイザーIDを最初の部分に持つ physicalSynchronizerId。コンソールはこのIDを短縮して ::<protocol version>-<serial> を付加するため、公開されているIDと文字単位で比較することはできません;
  • healthy = true。

これはインタラクティブコンソールが結果を表示する方法です。ブートストラップスクリプトから println などで出力する場合、フィールドは名前なしで表示されます:ListConnectedSynchronizersResult(Synchronizer 'zenith-testnet',da::1220daf7fcc9...::dev-0,true)、最後の true は healthy です。

シンクロナイザーIDを公開されているものと比較するには、完全な形式で出力してください:

myparticipant.synchronizers.id_of("zenith-testnet").toProtoPrimitive

接続を直接確認することもできます:

myparticipant.synchronizers.active("zenith-testnet")

接続がアクティブになると true が返されます。

5. DARのアップロードとベット

参加者がトランザクションに参加できるのは、そのパーティが参照するトランザクション部分で使用されるDaml パッケージをベット済みの場合のみです。Gatewayトークンを保有または送信するには、パーティを作成する前に Zenith バンドルからDARをアップロードしてください:

DAR内容
zenith-evm-0.1.0.darZenith EVMのコアオンレジャーコントラクト
zenith-gateway-proto-0.1.0.darGateway: トークン登録、メールボックスコントラクト、Gateway発行トークンおよびその転送ファクトリー。また、testnetのCanton テストトークンTTAとそのレジストリ
zenith-gateway-faucet-0.1.0.darTTAをミントするフォーセット
splice-api-token-metadata-v1-1.0.0.darCIP-56トークン標準: メタデータインターフェース
splice-api-token-holding-v1-1.0.0.darCIP-56トークン標準: 保有インターフェース
splice-api-token-transfer-instruction-v1-1.0.0.darCIP-56トークン標準: 転送インターフェース

順序が重要な理由: Gatewayは、パーティが作成された直後に、あなたのパーティシパントがGatewayのパッケージを審査済みかどうかにかかわらず、そのパーティを受信トレイインデックスに追加します。パーティシパントが審査を完了するまで、Gatewayはそのパーティシパントがホストするいかなるパーティにも入金できません。そのようなパーティへの入金は拒否され、3回拒否されるとGatewayは同じブロック提案内の他の入金とともに、ブロックに含まれる前にその入金EVMトランザクションを破棄します。送信者はトークンを保持したまま再送信する必要があります。審査はパーティシパント単位で記録され、各パーティごとではないため、有効になった時点で、アップロード前に作成されたパーティを含め、そのパーティシパントがホストするすべてのパーティに適用されます。接続し、DARをアップロードし、その後パーティを作成してください。そうすることで、アップロード前にあなたのパーティに入金が届くことはありません。

  • 重要: DARはZenithバンドルに含まれるものを使用し、ソースからビルドしたDARは使用しないでください。バンドルのビルド後にDamlのソースが変更される場合があり、変更されたソースや異なるSDKバージョンでビルドされたパッケージは異なるパッケージIDを持ちます。以下のチェックにより、DARが一致しているかどうかを確認できます。

アップロードの前にGatewayパッケージIDを確認してください。 このステップのシェルコマンドとステップ6を2番目のターミナルで実行してください。Canton console(またはdaemonプロセス)は最初のターミナルで動作するため、そちらにもBUNDLEを設定してください(export BUNDLE=<the bundle directory>、ステップ1と同様)。チェックにはjqとunzip、およびこれらの変数が必要です。これらはGatewayワークフローが後で使用するテストトークンのexportブロックと同じ値です:

export REGISTRY=https://gateway.testnet.zenith.network
export GATEWAY_PARTY=gateway::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8
export TKA=0x8464135c8f25da09e49bc8782676a84730c318bc
export TKA_ID="erc20:$(printf %s "${TKA#0x}" | tr 'A-F' 'a-f')"

次にチェックを実行します:

PKG=$(curl -s -X POST "$REGISTRY/registry/transfer-instruction/v1/transfer-factory" \
-H 'content-type: application/json' \
-d '{"choiceArguments":{"expectedAdmin":"'"$GATEWAY_PARTY"'","transfer":{"sender":"anyone::1220","receiver":"anyone::1220","amount":"1.0","instrumentId":{"admin":"'"$GATEWAY_PARTY"'","id":"'"$TKA_ID"'"}}}}' \
| jq -r '.choiceContext.disclosedContracts[0].templateId | split(":")[0]')
echo "$PKG"
if echo "$PKG" | grep -Eqx '[0-9a-f]{64}'; then
unzip -Z1 "$BUNDLE"/dars/zenith-gateway-proto-*.dar | grep -F "$PKG.dalf"
else
echo "error: the registry returned no package ID; check REGISTRY, GATEWAY_PARTY and TKA_ID"
fi

echoはパッケージID(64桁の16進数)を出力します。続いてunzip行が、そのIDで終わる名前の.dalfファイルをちょうど1つ出力します。何も出力されない場合、あなたのバンドルはデプロイされたGatewayとは異なるビルドのものです。アップロードしないでください。現在のバンドルについてZenithにお問い合わせください。error:行が表示された場合は、レジストリリクエストが失敗し、何も確認されていません。

チェックが通過した後にのみDARをアップロードしてください。Zenithパッケージのすべてのビルドは同じ名前とバージョンを持ち、Cantonは同じ名前とバージョンを持つ異なる2番目のパッケージの審査を拒否します(KNOWN_PACKAGE_VERSION)。誤ったビルドをすでに審査済みのパーティシパントは、現在のバンドルを使用できません。最も簡単な解決策は、空のデータベースを持つ新しいパーティシパントを用意することです。この時点ではパーティがまだないため、コストはかかりません。

LEDGERには、ステップ2のhttp-ledger-apiアドレスであるパーティシパントのJSON Ledger APIを設定してください。ステップ5および6のJSON Ledger APIコマンドはこれを使用します:

export LEDGER=http://127.0.0.1:7575

Ledger APIで認証が必要な場合は、ステップ5および6の各curl "$LEDGER…"呼び出しに-H "authorization: Bearer <token>"を追加してください。

コンソールから各DARをアップロードします。sys.env("BUNDLE")は、コンソールを起動したシェルからステップ1のバンドルディレクトリであるBUNDLEを読み取ります:

myparticipant.dars.upload(sys.env("BUNDLE") + "/dars/zenith-evm-0.1.0.dar")
myparticipant.dars.upload(sys.env("BUNDLE") + "/dars/zenith-gateway-proto-0.1.0.dar")
// ...and the remaining four

またはJSON Ledger APIを通じてすべてをアップロードします:

for d in "$BUNDLE"/dars/*.dar; do
curl -s -w " %{http_code} $d\n" -X POST \
-H 'content-type: application/octet-stream' \
--data-binary @"$d" "$LEDGER/v2/dars"
done

出力の各行はレスポンスボディ、HTTPステータス、およびファイルを示します(例: {} 200 /path/to/bundle/dars/zenith-evm-0.1.0.dar)。6つすべてのステータスが200でなければなりません。パーティシパントがすでに保持しているDARをアップロードした場合も200が返されるため、コンソールからDARの一部をアップロードした後を含め、ループを安全に再実行できます。

DARをアップロードすると、デフォルトでそのパッケージも審査されます。審査とは、パーティシパントがそれらのパッケージバージョンをロードし、解釈することを宣言することを意味します。DARがインストールされていることを確認します:

myparticipant.dars.list()

パーティシパントにGatewayパッケージが存在することを確認します: curl -s "$LEDGER/v2/packages" | jq -r '.packageIds[]' | grep -x "$PKG"はIDを一度出力します。

次に、Canton test network informationに記載されているDaml SDKバージョンを使用してビルドした、あなた自身のアプリケーションのDARを同様の方法でアップロードします。

パーティシパントへのDARのアップロードは、そのパーティシパントにのみインストールされます。Zenithのパーティシパントやその他のパーティシパントには何もインストールされません。トランザクションが複数のパーティシパントにホストされるパーティを含む場合、それらの各パーティシパントは、自身の担当部分を解釈するために必要なパッケージをロードし、審査済みにしておく必要があります。

6. パーティおよびLedger APIユーザーの作成

アプリケーションが使用するCantonパーティを作成します。送信権限を持つパーティシパントにホストします。コンソールから:

val alice = myparticipant.parties.enable("alice")

alice.toProtoPrimitiveでフルパーティIDを出力します。コンソールに表示されるaliceは短縮されているため、コピーしないでください。

またはJSON Ledger APIを通じて:

curl -s -X POST "$LEDGER/v2/parties" -H 'content-type: application/json' \
-d '{"partyIdHint": "alice", "identityProviderId": ""}' | jq -r .partyDetails.party

フルパーティIDはalice::<your namespace>です。トークンを送信する相手には、ネームスペースを含むフルパーティIDを必ず渡してください。

次に、アプリケーション用のLedger APIユーザーを作成し、権限を付与します:

  • actAs アプリケーションがコマンドを送信する代理パーティに対して;
  • readAs アプリケーションが台帳状態を読み取る必要がある追加パーティに対して。
curl -s -X POST "$LEDGER/v2/users" -H 'content-type: application/json' \
-d '{"user": {"id": "my-app", "primaryParty": "", "isDeactivated": false, "identityProviderId": ""}, "rights": []}'
curl -s -X POST "$LEDGER/v2/users/my-app/rights" -H 'content-type: application/json' \
-d '{"userId": "my-app", "identityProviderId": "", "rights": [
{"kind": {"CanActAs": {"value": {"party": "<alice-party-id>"}}}},
{"kind": {"CanReadAs": {"value": {"party": "<alice-party-id>"}}}}
]}'

ユーザーとその権限はご利用の参加者にローカルなものです。パーティIDはネットワーク上のIDであり、同期ドメイン上の任意の参加者がそれらを参照できます。

パーティはGatewayをご利用いただける状態です。 Zenithの参加者は、同期ドメインのトポロジーを通じて新しいパーティを認識します。その後、GatewayはパーティをInboxインデックスに追加します。通常は30秒以内に完了します。Gatewayが他の転送の決済処理で混み合っている場合は、それより時間がかかることがあります。それ以降、Zenith EVMでパーティのinboxアドレスに送信されたトークンがパーティにクレジットされます。それ以前に送信されたトークンは通常の転送としてマイニングされ、GatewayがそれらをのちのブロックでクレジットするまでInboxに留まります。wTTAは例外です。ワークフロー2/bをご参照ください。申請や手数料の支払いは不要です。

7. アプリケーションを接続する

アプリケーションが参加者のLedger APIを参照するよう設定します:

Application settingValue
Ledger API endpointYour participant’s gRPC or JSON Ledger API.
Ledger API authenticationConfigured locally.
Application userThe user you created in step 6.
Application party IDsThe parties you created or host on your participant.

アプリケーションは、参加者を通じて通常のDamlコマンドを送信できるようになりました。Zenith EVMとの間でトークンを移動するには、GatewayとのインタラクションおよびGatewayサンプルワークフローに進んでください。

参加者をオンラインに保つ

パーティのいずれかがZenith EVMにトークンを送信すると、Gatewayはパーティが承認しなければならないトランザクションで転送を決済します。これは、ウォレットおよびEVM拡張を持たない参加者が使用する標準ルートをカバーします(転送の方向を参照)。

  • 警告: パーティからZenith EVMへの転送が保留中の間は、参加者を起動したまま接続を維持してください。参加者が応答しなくなると、転送の決済も拒否もできなくなります。パーティが両方を承認しなければならないためです。また、引き出しも自身の参加者を経由するため、引き出すこともできません。各決済の試行では、メディエーターがタイムアウトするまで約30秒待機します。これにより、testnetのすべてのユーザーに対してブロック生成が遅くなります。参加者が再び応答するようになると、拒否されたように見えた試行がコミットされる可能性があります。転送を再送信する前に、Zenith EVM上の残高を確認するか、GatewayコントラクトからのMintedまたはReleasedイベントで、toとしてご自身のアドレスが含まれるものを探してください。これらの転送には短いexecuteBefore(数分程度)を設定してください。pending_count <party>がパーティごとに0を出力する(pending_countはアカウントとツールのセットアップのヘルパー関数の一つです)まで、シャットダウンしないでください。error:で始まる行は読み取りに失敗したことを意味し、保留中の転送については何も示しません。

  • 警告: ワークフロー2/bのTTAスタンディングアクセプタンスなど、スタンディングアクセプタンス(転送事前承認)を持つパーティも、誰かがwTTAまたはCantonトークンを返す別のGateway ERC-20を送信する際に、参加者が必要です。パーティはスタンディングアクセプタンスに署名しており、Gatewayの決済がそれを読み取るため、参加者はその決済を承認しなければなりません。参加者が応答しない間、各決済の試行はメディエーターがタイムアウトするまで約30秒待機し、その間は誰に対してもブロックが生成されません。3回の試行後、Gatewayはデポジットをドロップします。パーティは単独でTTAスタンディングアクセプタンスを削除することができません。スタンディングアクセプタンスは、参加者が常時オンラインを維持できるパーティに対してのみ作成し、storage.type = memoryの参加者上のパーティには絶対に作成しないでください。

参加者の切断

保存済み接続を削除せずに切断するには:

myparticipant.synchronizers.disconnect("zenith-testnet")

後で再接続するには:

myparticipant.synchronizers.reconnect("zenith-testnet")

切断しても、参加者のID、DAR、パーティ、ローカル台帳の状態は削除されません。切断する前に、pending_count <party>がパーティごとに0を出力することを確認してください。これにより、メールボックスパーティへの保留中の転送がないことを確かめられます。error:で始まる行は読み取りに失敗したことを意味し、保留中の転送については何も示しません。また、切断中に、スタンディングアクセプタンスを持つパーティにwTTA(またはCantonトークンを返す別のGateway ERC-20)が送信されないことを確認してください(参加者をオンラインに保つを参照)。

このtestnetで参加者ができないこと

  • EVMブロックの構築: Gatewayの1トランザクションルート(GatewayService_CrossToEvmおよびWrapperFactoryを通じた1ステップのリターン)は、送信者自身のトランザクション内でEVMブロックを構築します。これには4つの要件があります:参加者上のzenith-evm外部呼び出し拡張、独自の外部呼び出しサービス、チェーンと同期した独自のブロックビルダー、そしてオペレーターのみが開示できる8〜9のコントラクトです。外部呼び出し拡張を持たない参加者は、送信時にINTERPRETATION_EXTERNAL_CALL_ERROR_EXECUTION_FAILEDおよびExternal calls not supportedで終わる原因によって拒否され、何も移動しません。オペレーターのコントラクトがない場合、コマンドはより早い段階で別のエラーとともに失敗します。
    • 必要なすべての交差には、それらなしで機能する標準ルートがあります。 転送の方向を参照してください。
  • トークンの登録: Gatewayパーティを運営するZenithのみがトークンを登録できます。Zenithに登録をリクエストすることができますが、登録するかどうかはZenithが判断します。詳細は独自トークンの登録を参照してください。
  • 外部パーティの使用: 参加者の外部でキーが保持され、インタラクティブ送信を通じて署名するパーティは、Gatewayではまだテストされていません。

オンボーディングのトラブルシューティング

症状考えられる原因対処方法
参加者が接続しない、またはプロトコルバージョンエラーで失敗するCanton テストネットワーク情報に記載されているもの以外のCantonバージョンを使用している、またはalpha-version-support/dev-version-supportが設定されていないステップ1のCantonビルドを使用し、non-standard-config = yesをグローバルに設定し、両方のフラグをグローバルおよび参加者のparametersに設定する
参加者が起動せずEnabling dev-version-support for participant myparticipant requires you to explicitly set canton.parameters.non-standard-config = yes(またはalpha-version-supportに対する同じメッセージ)を報告するnon-standard-config が設定されていませんnon-standard-config = yes をグローバルの canton { parameters { ... } } ブロックに追加してください
connect が で失敗しました。Scheme `null` は無効ですエンドポイントにスキームがありませんスキームを含めてエンドポイントを指定してください: https://synchronizer.testnet.zenith.network
connect が SYNC_SERVICE_BAD_CONNECTIVITY で失敗しましたSequencer エンドポイントに到達できません。スキーム(http:// または https://)または TLS 設定が誤っているか、指定した physicalSynchronizerId がシンクロナイザーのものではありません(その場合、メッセージには does not match expected と表示されます)エンドポイントへのアウトバウンドアクセス、エンドポイントのスキーム、certificatesPath、および物理シンクロナイザー ID を確認してください
あなたのパーティーのインボックスへの EVM 送金がレシートを受け取りませんでした入金が到着した時点で、あなたの参加者が Gateway DAR を審査していなかったため、Gateway は送金を破棄しました。送信者はまだトークンを保持しています。DAR をアップロードしてください(ステップ 5)。新しいパーティーは不要です。その後、送信者に破棄を確認し、最初の行 Common problems に示すように同じ nonce で再送するよう依頼してください。
送信者の送金はレシートを受け取りましたが、あなたのパーティーに何もクレジットされませんでした送信者が不完全なパーティー ID(例: ::namespace のないヒント部分のみ)からインボックスを導出しました。そのような ID を持つパーティーは存在しないため、Gateway はそのインボックスをインデックスしません。送信者にあなたの完全なパーティー ID(hint::namespace)を伝えてください。誤ったインボックスに送られたトークンはそこに留まり、移動させることはできません。
Gateway パッケージ ID チェックで、echo "$PKG" は 64 文字の ID を出力しますが、unzip 行には何も表示されませんDAR がデプロイ済みの Gateway とは異なるビルドから取得されていますアップロードしないでください。現在の Zenith バンドルについては Zenith にお問い合わせください。
Gateway パッケージ ID チェックで、echo "$PKG" が空の行を出力しますREGISTRY、GATEWAY_PARTY または TKA_ID が設定されていないか、レジストリに到達できませんステップ 5 のチェック前に示されている 4 つの変数を設定してから、チェックを再実行してください。
送信時に External calls not supportedone-transaction Gateway ルートを使用しました代わりにメールボックスパーティーへの標準送金を使用してください(Transfer directions を参照)

Zenith Gateway 概要

Zenith Gateway は Canton と Zenith EVM の間で双方向にトークンを移動させます。各クロッシングはアトミックです。Gateway がインボックスから入金を収集するか、オファーを承認すると、Canton 上の変更と Zenith EVM 上の変更が単一の Canton トランザクションとして一緒にコミットされるか、まったくコミットされません。トークンが両側に同時に存在することはありません。そのステップの前に、トークンはインボックスまたは保留中のオファーで待機することができます(Transfer directions を参照)。

仕組み

インボックスとは、Canton のパーティー ID から導出された EVM アドレスです。登録済みの ERC-20 を Canton へ送るには、受取人のインボックスにトークンを転送してください。

メールボックスパーティーとは、1 つの EVM 宛先アドレスと 1 つのインストゥルメントに紐付けられた Canton パーティーです。そのインストゥルメントを Canton から Zenith EVM へ送るには、メールボックスパーティーへ転送してください。メールボックスは使用前に作成しておく必要があります。

これらの宛先はそれぞれ異なる方向に対応しています。メールボックスパーティーは、インボックスへの入金における Canton の受取人ではありません。重要: Zenith EVM 上で、メールボックスパーティーのインボックスアドレス(m- で始まる ID)や Gateway パーティーのインボックスアドレスにトークンを送らないでください。Gateway はいずれに対してもクレジットを行いません。転送はマイニングされますが、トークンはそのアドレスに留まり、失われます。

  • Canton へトークンを送るには、Zenith EVM 上で受取人パーティーのインボックスアドレスへトークンを転送してください。これはひとつの Canton パーティーに属する EVM アドレスであり、パーティー ID から誰でも参照できます。TKA のように Zenith EVM 上で発行されたトークンの場合、受取人は事前設定不要です。Canton トークンの Gateway ERC-20(例: wTTA、Canton 上では TTA として着金)の場合、受取人はそのトークンのレジストリで事前に承諾(転送事前承認)を設定しておく必要があります(Workflow 2/b、ステップ 1)。それがなければ、Gateway はトークンを届けられず、あなたの EVM トランザクションを破棄します。
  • Zenith EVM へトークンを送るには、Canton 上で自分の EVM アドレスのメールボックスパーティーへ標準的な CIP-56 転送を行ってください。これは 1 つの EVM アドレスと 1 つのトークンを表す Canton パーティーです。誰でも送れる 1 回の EVM トランザクションでリクエストすると、Gateway が 1〜2 ブロック以内に作成します。登録されていないトークンへのリクエストは Gateway に無視され、ガスは消費されます。

Gateway は、相手側に対応するトークンが存在している間、元のトークンを保管します(1 対 1)。対応するトークンを返送すると、元のトークンが解放されます。

トークンの発行元相手側への着金形式Testnet の例
Zenith EVM (ERC-20)Gateway パーティーが発行した CIP-56 トークン(ID: erc20:<token address in lower case, without 0x>)TKA → Canton 上の erc20:<TKA address in lower case, without 0x>
Canton (CIP-56)Gateway がデプロイした ERC-20 トークンTTA → Zenith EVM 上の wTTA

下図は、登録済みトークンを Zenith EVM から Gateway がインデックス済みの Canton パーティーのインボックスへ転送する流れを示しています。Gateway サービスはブロックプロポーザルをシミュレーションすることで入金を検出します。その後、Canton トランザクションを 1 件送信し、まず Canton 上で受取人へのクレジットを行い、続いてあなたの転送とインボックスからのコレクションを含む新しい EVM ブロックを構築します。アドミッションは、ブロックの Gateway イベントがクレジットと完全に一致する場合にのみブロックを受理します。クレジットと新しい EVM ブロックはともにコミットされるか、まったくコミットされないかのどちらかです。

インバウンド Gateway 転送: シーケンス図

  • 注意: Canton は、インデックス済みインボックスへの登録済みトークンの未収集入金が含まれるブロックプロポーザルから、通常のブロックを構築することを拒否します。したがって、Gateway がコレクションとクレジットを伴って構築するブロックのみが、あなたの転送を含められる唯一のブロックとなります。

転送の方向

移動対象操作内容ガイド
ERC-20 トークンを Canton へERC-20 transfer で受取人のインボックスアドレスへ送信するWorkflow 1/a
GatewayのCantonトークンをZenith EVMに戻すCIP-56転送でメールボックスパーティに送信するWorkflow 1/b
Canton(CIP-56)トークンをZenith EVMに送るCIP-56転送でメールボックスパーティに送信するWorkflow 2/a
GatewayのERC-20をCantonに戻す受取人が元のトークンに対する常設受諾を持っていることを確認し、ERC-20 transferで受取人のinboxアドレスにERC-20を送信するWorkflow 2/b

Cantonへの転送:Gatewayがインデックスしているパーティのinboxに登録済みトークンを転送すると、EVMトランザクションと同じブロックでクレジットされます。Gatewayがそのような入金を配信できない場合(例:常設受諾のないパーティへのwTTA送信)、トランザクションは取り込まれる前にドロップされ、何も移動しません。まだ登録されていないトークン、またはGatewayがまだインデックスしていないパーティへの転送は、通常のブロックにマイニングされ、トークンはinboxで待機します。Gatewayは、パーティをインデックスした後またはトークンが登録された後、約1分以内にクレジットします。wTTAは例外です:常設受諾のない受取人はクレジットできず、待機中のすべてのinboxのGatewayのクレジットが失敗します(Workflow 2/bを参照)。メールボックスパーティまたはGatewayパーティのinboxに送られたトークンはクレジットされず、失われます。

Zenith EVMへの転送は通常、オファーと呼ばれるペンディング転送として開始されます:トークンのレジストリがメールボックスパーティ宛てのペンディング転送指示を残し、Gatewayがそれを受理するまでその指示は待機します。Gateway自身のトークンおよびtestnetのTTAは常にこの方式で動作します。トークンのレジストリが転送を即時完了する場合、オファーは作成されません:トークンは直接メールボックスパーティに送られ、Gatewayは同種のトランザクションでそこから決済します。そのような転送は撤回できず、Gatewayが決済できない場合、トークンはメールボックスパーティに留まります。

  • Gatewayがオファーを受理すると、同じCantonトランザクション内でトークンが移動し、EVMアドレスにミントまたはリリースされます。アトミックなステップはオファー送信時ではなく、ここで発生します。
  • Gatewayがオファーを受理するまでの間、TransferInstruction_Withdrawで撤回してトークンを取り戻すことができます。GatewayはexecuteBeforeより前のいつでも受理できるため(通常約1ブロック以内)、撤回が先にコミットされた場合にのみ有効です。executeBeforeの後、Gatewayはオファーを受理できなくなり、撤回されるまでペンディング状態が続きます。
  • Gatewayがオファーを決済できない場合(トークンが未登録、またはメールボックスパーティが別のトークン用)、または決済が3回連続で拒否された場合、Gatewayはオファーを拒否し、トークンのレジストリが金額をあなたに返却します。TTAおよびGateway自身のCantonトークンは全額を新しいホールディングとして返却し、他のレジストリは独自のルールで返却します。拒否にはあなたのparticipantがオンラインである必要があります。GatewayはexecuteBefore以降にオファーを拒否しません。期限切れのオファーは自分で撤回してください。
  • 送信時に拒否されて何もホールディングから出ないミスがあります:存在しないメールボックスパーティ(UNKNOWN_INFORMEES、またはGatewayのトークンの場合はNo mailbox for this party is visible to you…)、別のトークンのメールボックスパーティに送られたGateway発行トークン(Mailbox belongs to another instrument)、ゼロ金額(Nonpositive amount)。
  • トークンをメールボックスパーティに送るのは、トークンが登録された後にしてください。

このリリースに含まれる内容

Zenith EVM TestnetのGatewayリリースはトークン転送のみをサポートします。

  • 含まれるもの: 登録済みトークンの双方向の転送。これには、プレーンなERC-20トークン(fee-on-transferなし、リベースなし)および、トークン標準のレジストリAPIを提供するレジストリを持つCIP-56トークンが含まれます。任意のEVMウォレットまたはコントラクトから送信できます。Canton側では、標準的なCIP-56転送を行うウォレットまたはアプリケーションを通じて、Zenith testnetシンクロナイザーに接続されたparticipantにホストされているパーティから送信できます。外部パーティはテストされていません。Gatewayの手数料はありません:署名するEVMトランザクションのガスをZTHで支払うだけです。
  • 含まれないもの: DamlとEVMコントラクト間の呼び出し、ネイティブZTHまたはCanton Coinの転送、セルフサービスのトークン登録。ネイティブZTHをinboxアドレスに送らないでください。Gatewayは登録済みのERC-20トークンのみを収集し、ZTHをinboxから移動させることはできません。

testnet上のGatewayはZenithによって運営されています。実際の価値のあるものは送らないでください。

独自トークンの登録

ZenithにGatewayへのトークン登録を依頼できます。 登録を実施できるのはZenithのみであり、トークンを登録するかどうかはZenithが決定します。CIP-56トークンの場合、これは信頼に関する決定です:GatewayはGatewayパーティの権限であなたのトークンのDamlコードを実行し、1つのGatewayパーティがすべての登録済みCantonトークンのバッキングを保持します。以下の内容を添えてリクエストを[email protected]に送信してください:

トークン情報送付内容
Zenith EVM上のERC-20トークンのアドレス。fee-on-transferおよびリベースのないプレーンなERC-20である必要があります。
Canton上のCIP-56トークンレジストリAPIのURL;トークンのDamlパッケージのDARファイル(ホールディング、転送ファクトリー、転送指示テンプレートとその依存関係);GatewayがデプロイするERC-20に使用する名前とシンボル;そして1単位のトークン(ZenithがDARのアップロードを確認した後にGatewayパーティに転送してください。それまでは転送が拒否されます)。Gatewayはそのユニットを恒久的に保持し、そこからトークンのホールディングに署名するパーティを読み取ります。トークンパッケージの新バージョンは同じ方法でアップロードする必要があるため、アップグレード前にZenithに連絡してください。

CIP-56トークンの場合、レジストリはトークン標準のtransfer-factoryおよびchoice-contexts/acceptエンドポイントを提供し、常時到達可能である必要があります。登録は永続的です。

トークンが登録されると、Gatewayコントラクトがそれを報告します。Test tokensで設定した変数を使用して:ERC-20の場合、cast call $GATEWAY 'kind(address)(uint256)' <token address> --rpc-url $RPCは2を返します(Workflow 1/a, step 1を参照)。CIP-56トークンの場合、cast call $GATEWAY 'wrapperOf(bytes32)(address)' $(cast keccak "cip56:<admin's full party ID>:<id>") --rpc-url $RPCはゼロでないアドレス(GatewayがあなたのトークンのためにデプロイしたERC-20)を返します。トークンが登録されるまで、inboxアドレスやmailboxパーティにトークンを送らないでください。

Gatewayとのインタラクション

Gatewayネットワーク情報

Value
Zenith EVM RPChttps://rpc.testnet.zenith.network
Chain ID936485
Block time5 seconds
Explorerhttps://explorer.testnet.zenith.network/
Gateway contract0x663f3ad617193148711d28f5334ee4ed07016602
MailboxRequests contract0x2e983a1ba5e8b38aaaec4b440b9ddcfbf72e15d1
Gateway partygateway::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8
Operator namespace12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8
Gateway registry APIhttps://gateway.testnet.zenith.network

テストトークン

TokenValueWhat it is
TKA0x8464135c8f25da09e49bc8782676a84730c318bcan ERC-20 test token on Zenith EVM (18 decimals)
TKA drip0x71c95911e9a5d330f4d621842ec243ee1343292econtract that gives any caller 10 TKA, once a minute per address
TTAadmin issuer::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8, ID TTAa CIP-56 test token on Canton (10 decimals)
wTTA0x12a62529c83cfcdd6650b4cce477d98cc9945d66TTA’s ERC-20 counterpart on Zenith EVM (10 decimals)
Test token registry APIhttps://gateway-partner.testnet.zenith.networkTTA’s registry API. GET /test-token/v1/faucets lists TTA’s faucet, which is a Canton contract. To mint TTA, you exercise Faucet_Mint on it through your own Ledger API (Workflow 2/a).

これらをシェルに貼り付けてください。以下のすべてのコマンドはこれらを使用します:

export RPC=https://rpc.testnet.zenith.network
export GATEWAY=0x663f3ad617193148711d28f5334ee4ed07016602
export REQUESTS=0x2e983a1ba5e8b38aaaec4b440b9ddcfbf72e15d1
export GATEWAY_PARTY=gateway::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8
export NAMESPACE=12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8
export REGISTRY=https://gateway.testnet.zenith.network
export TKA=0x8464135c8f25da09e49bc8782676a84730c318bc
export DRIP=0x71c95911e9a5d330f4d621842ec243ee1343292e
export wTTA=0x12a62529c83cfcdd6650b4cce477d98cc9945d66
export TREGISTRY=https://gateway-partner.testnet.zenith.network
export TKA_ID="erc20:$(printf %s "${TKA#0x}" | tr 'A-F' 'a-f')"

Gateway network informationから2つの値をネットワークと照合できます。最初のコマンドはチェーンID(936485)を表示し、2番目のコマンドはGatewayパーティを表示します:

cast chain-id --rpc-url $RPC
curl -s $REGISTRY/registry/metadata/v1/info | jq -r .adminId

金額と小数点

EVMの1基本単位はCantonでは0.0000000001(1e-10)に相当します。転送は正確であり、Gatewayは決して丸めません。実際には:

  • Gatewayが発行するトークンは小数点以下10桁:wTTA、およびERC-20トークンのCanton対応版。
  • Zenith EVMでは基本単位を入力:3000000000000000000は3.0 TKA(18桁)、20000000000は2.0 wTTA(10桁)。
  • Canonでは小数金額を入力:4.0 TTA。Canton金額の小数点以下は最大10桁です。小数点以下10桁目より後にゼロでない数字がある場合、参加者はCOMMAND_PREPROCESSING_FAILED(Cannot represent ... as (Numeric 10) without loss of precision)でコマンドを拒否します。何も移動せず、丸めも行われません。
  • 18桁のERC-20はCantonで100,000,000倍大きい数値として表示されます: 3.0 TKAは300000000.0の保有として到着します。これは同じ価値です。

アカウントとツールのセットアップ

ツール: Foundryのcast、curl、jq、unzip、およびpython3(3.9以上)。

EVMアカウントと資金: テストキーを作成し、faucetからガス用のZTHを取得し、dripから10 TKAを受け取ります:

export KEY=$(cast wallet new --json | jq -r '[.. | .private_key? // empty][0]')
export ME=$(cast wallet address --private-key "$KEY")
echo "$ME"
cast send $DRIP 'drip()' --private-key $KEY --rpc-url $RPC

dripの呼び出しは、faucetのZTHが到着した後にのみ機能します。

秘密鍵はこのシェルのKEY変数にのみ存在し、すべてのワークフローはこれで署名します。このアドレスを後で使用するには、echo "$KEY"でキーを表示して保存してください。他には保存されません。

Cantonパーティ(全4つのワークフロー用):Interacting with Zenith EVM from Cantonに記載されているように参加者を接続してパーティを作成し、次を設定します:

export LEDGER=http://127.0.0.1:7575
export USER_ID=my-app
export P1=<your full party ID>

LEDGERは参加者のJSON Ledger APIで、USER_IDはP1に対してactAsおよびreadAs権限を持つユーザーです。Ledger APIがトークンを確認する場合は、以下のlcurlヘルパーにトークンを追加します。

ヘルパー関数:Cantonにはcastのようなコマンドラインツールがないため、これらのシェル関数はワークフローが必要とするJSON Ledger APIおよびレジストリAPIの呼び出しをラップします。独自のアプリケーションはLedger APIクライアントを通じて同じ呼び出しを行います。コードブロックにはコメントが含まれていないため、bashまたはzshに直接貼り付けられます。

まず、submitが送信前に各リクエストを保存するディレクトリを作成します:

export RUN=$(date +%H%M%S)
export REQ_DIR="$HOME/zenith-requests-$RUN"
mkdir -p "$REQ_DIR"

次に関数を貼り付けます:

ts() {
python3 -c 'import datetime, sys; t = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=int(sys.argv[1])); print(t.strftime("%Y-%m-%dT%H:%M:%SZ"))' "$1"
}
jcurl() {
local out rc code
out=$(curl -sS -w '\n%{http_code}' "$@") || { rc=$?; echo "error: no answer from $1 (curl exit $rc)" >&2; return 1; }
code=${out##*$'\n'}
printf '%s\n' "${out%$'\n'*}"
case "$code" in 2??) return 0 ;; esac
echo "error: HTTP $code from $1" >&2
return 1
}
lcurl() {
local p=$1
shift
jcurl "$LEDGER$p" "$@"
}
acs() {
local r off
r=$(lcurl /v2/state/ledger-end) || return 1
off=$(printf '%s\n' "$r" | jq -e '.offset | numbers') || { echo "error: unexpected ledger-end answer: $(printf '%.300s' "$r")" >&2; return 1; }
r=$(jq -n --arg p "$1" --arg i "$2" --argjson off "$off" '{eventFormat: {filtersByParty: {($p): {cumulative: [{identifierFilter: {InterfaceFilter: {value: {interfaceId: $i, includeInterfaceView: true, includeCreatedEventBlob: false}}}}]}}, verbose: true}, activeAtOffset: $off}' | lcurl /v2/state/active-contracts -X POST -H 'content-type: application/json' -d @-) || return 1
printf '%s\n' "$r" | jq -e 'type == "array" and all(.[]; .contractEntry.JsActiveContract.createdEvent.interfaceViews[0].viewValue | type == "object")' >/dev/null || { echo "error: unexpected active-contracts answer: $(printf '%.300s' "$r")" >&2; return 1; }
printf '%s\n' "$r"
}
holdings() {
local r
r=$(acs "$1" '#splice-api-token-holding-v1:Splice.Api.Token.HoldingV1:Holding') || return 1
printf '%s\n' "$r" | jq -c '.[].contractEntry.JsActiveContract.createdEvent | {cid: .contractId, owner: .interfaceViews[0].viewValue.owner, amount: .interfaceViews[0].viewValue.amount, id: .interfaceViews[0].viewValue.instrumentId.id, admin: .interfaceViews[0].viewValue.instrumentId.admin}'
}
pending() {
local r
r=$(acs "$1" '#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferInstruction') || return 1
printf '%s\n' "$r" | jq -c '.[].contractEntry.JsActiveContract.createdEvent | {cid: .contractId, receiver: .interfaceViews[0].viewValue.transfer.receiver, amount: .interfaceViews[0].viewValue.transfer.amount, executeBefore: .interfaceViews[0].viewValue.transfer.executeBefore}'
}
pending_count() {
local r
r=$(acs "$1" '#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferInstruction') || return 1
printf '%s\n' "$r" | jq length
}
transfer_args() {
local h inputs
h=$(holdings "$1") || return 1
inputs=$(printf '%s' "$h" | jq -s -c --arg id "$5" --arg a "$4" --arg o "$1" '[.[] | select(.id == $id and .admin == $a and .owner == $o) | .cid]')
jq -n -c --arg admin "$4" --arg id "$5" --arg s "$1" --arg r "$2" --arg amt "$3" --arg req "$(ts -60)" --arg exp "$(ts "${6:-180}")" --argjson in "$inputs" '{expectedAdmin: $admin, transfer: {sender: $s, receiver: $r, amount: $amt, instrumentId: {admin: $admin, id: $id}, requestedAt: $req, executeBefore: $exp, inputHoldingCids: $in, meta: {values: {}}}, extraArgs: {context: {values: {}}, meta: {values: {}}}}'
}
ask() {
jq -n -c --argjson a "$1" '{choiceArguments: $a, excludeDebugFields: true}' | jcurl "$2/registry/transfer-instruction/v1/transfer-factory" -X POST -H 'content-type: application/json' -d @-
}
new_cmd() {
local id f
id="zenith-$RUN-$1-$(python3 -c 'import uuid; print(uuid.uuid4().hex[:16])')"
f="$REQ_DIR/$id.json"
jq -c --arg id "$id" '.commands.commandId = $id' >"$f" && [ -s "$f" ] || { echo "error: could not build the $1 command; nothing was sent" >&2; return 1; }
echo "command id $id, request saved in $f" >&2
printf '%s\n' "$f"
}
send_saved() {
local r
[ -s "$1" ] || { echo "error: no saved request at '$1'; nothing was sent" >&2; return 1; }
r=$(lcurl /v2/commands/submit-and-wait-for-transaction -X POST -H 'content-type: application/json' -d @"$1")
printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${r:-no answer}" >>"${1%.json}.answers"
if printf '%s\n' "$r" | jq -e '.transaction.updateId | strings' >/dev/null 2>&1; then
printf '%s\n' "$r" | jq -c '{updateId: .transaction.updateId} + ([.transaction.events[] | .ExercisedEvent // empty | select(.choice == "TransferFactory_Transfer") | .exerciseResult.output.tag] | if length > 0 then {result: first} else {} end)'
return 0
fi
if printf '%s\n' "$r" | jq -e '.code | strings' >/dev/null 2>&1; then
printf '%s\n' "$r" | jq -c '{code, cause}'
else
echo "error: outcome unknown; send the same request again with: send_saved $1" >&2
fi
return 1
}
submit() {
local f
jq -e -n --argjson a "$2" --argjson ans "$3" '($a | type == "object") and ($ans.factoryId | type == "string")' >/dev/null || { echo "error: bad transfer arguments or registry answer; nothing was sent" >&2; return 1; }
f=$(jq -n --arg p "$1" --arg u "$USER_ID" --argjson a "$2" --argjson ans "$3" '{commands: {commands: [{ExerciseCommand: {templateId: "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory", contractId: $ans.factoryId, choice: "TransferFactory_Transfer", choiceArgument: ($a + {extraArgs: {context: {values: $ans.choiceContext.choiceContextData}, meta: {values: {}}}})}}], commandId: "", userId: $u, actAs: [$p], readAs: [], disclosedContracts: $ans.choiceContext.disclosedContracts}, transactionFormat: {eventFormat: {filtersByParty: {($p): {cumulative: [{identifierFilter: {WildcardFilter: {value: {includeCreatedEventBlob: false}}}}]}}, verbose: true}, transactionShape: "TRANSACTION_SHAPE_LEDGER_EFFECTS"}}' | new_cmd transfer) || return 1
send_saved "$f"
}
mailbox_party() {
python3 -c 'import hashlib, sys; a = sys.argv[1].lower().removeprefix("0x"); h = hashlib.sha256(("zenith-mailbox-v1|" + a + "|" + sys.argv[2]).encode()).hexdigest(); print("m-" + h + "::" + sys.argv[3])' "$1" "$2" "$NAMESPACE"
}

各関数の説明:

  • jcurl <url> [curl options]はcurlを実行し、レスポンスのボディを表示します。レスポンスがない場合、またはHTTPステータスが2xxでない場合は、error:で始まる行も表示してエラーを返します。lcurl <path> [curl options]は参加者のJSON Ledger API($LEDGER)のjcurlです。Ledger APIがトークンを確認する場合は、lcurl内の"$@"の後に-H "authorization: Bearer <token>"を追加します。以降、このガイドのすべてのレジャーリクエストはlcurlを経由します(ステップ5と6は先ほどcurlを直接呼び出します)。レジストリリクエストはlcurlを経由しないため、トークンがレジストリに送られることはありません。
  • holdings <party>とpending <party>はパーティの保有と保留中の転送を一覧表示します。pendingは各転送のexecuteBeforeも表示します。pending_count <party>は保留中の転送数を表示します。読み取りに失敗した場合、3つすべてがerror:で始まる行を表示してエラーを返します。holdingsまたはpendingが何も表示せず、error:行がない場合、パーティには何もありません。
  • transfer_args <sender> <receiver> <amount> <admin> <id> [expiry-seconds]は転送を構築します。有効期限はデフォルトで180秒です。ask <transfer-args> <registry-url>はトークンのレジストリに実行方法を問い合わせます。submit <party> <transfer-args> <registry-answer>はそれを送信します。
  • submitは各転送に新しいコマンドIDを付与し、リクエスト全体を$REQ_DIR/<command id>.jsonとして保存し、command id <id>, request saved in <file>を表示してからsend_savedで送信します。send_saved <file>は保存されたリクエストをそのまま送信し、3つのうち1つの結果を表示します:転送の場合は転送のresultを伴う{"updateId":…}、レジャーがコマンドを拒否したことを意味するerror: HTTP <status> from …行の後に続くレジャーの{"code":…,"cause":…}、またはerror: outcome unknown; send the same request again with: send_saved <file>。ファイルが存在しないか空の場合、またはファイルが指定されていない場合(例:error: could not build the … commandの後など)、send_savedはerror: no saved request at '<file>'; nothing was sentを表示して何も送信しません。
  • mailbox_party <evm-address> <instrument>は$NAMESPACEからmailboxパーティIDを計算します。

送信の再試行: error: outcome unknownは、コマンドがコミットされたかどうかが不明であることを意味します。レジャーがタイムアウトしたと示すcodeも同様に扱います。send_saved <file>を使って同じ保存済みリクエストを再送してください。error: outcome unknownの後は、エラーが表示した行をコピーします。タイムアウトcodeの後は、以前のrequest saved in <file>行のファイルを使用します。同じコマンドIDと同じ入力保有を持つため、レジャーは最大1回しか適用しません。最初の試みがすでにコミットされていた場合、再送はCONTRACT_NOT_FOUNDまたはDUPLICATE_COMMANDで拒否されます。その後holdingsを実行して何が起きたかを確認してください。再試行のためにtransfer_argsでリクエストを再構築しないでください。それは別の保有を選択し、新しい有効期限を設定し、新しいコマンドIDを使用するため、2回目の転送が発生します。

Gatewayサンプルワークフロー

Workflow 1/a:ERC-20トークンをCantonに送る

あなたのEVMアドレスからCantonパーティP1にTKAを送ります。受信者は何も署名する必要がなく、事前承認(転送プリアプルーバル)も不要です。

1. トークンが登録されていることを確認する: 2 という結果は、TKA が登録済みの ERC-20 トークンであることを意味します。0 は登録されていないことを意味します。その場合は中止してください。

cast call $GATEWAY 'kind(address)(uint256)' $TKA --rpc-url $RPC

2. 受取人のインボックスアドレスを調べる: 受取人の完全な party ID(hint::namespace)を使用します:

export INBOX=$(cast call $GATEWAY 'inboxOf(bytes)(address)' $(cast from-utf8 "${P1:?set P1 to your full party ID}") --rpc-url $RPC)

P1 が設定されていない場合、コマンドはエラーで停止します。このチェックを行わないと、inboxOf は空の party ID のインボックスを返し、そこに送られたトークンは失われます。

3. インボックスに 3.0 TKA を送信する:

cast send $TKA 'transfer(address,uint256)' $INBOX 3000000000000000000 --private-key $KEY --rpc-url $RPC

4. 結果を確認する: holdings "$P1" は、amount が 300000000.0000000000、id が $TKA_ID、admin が $GATEWAY_PARTY の新しいホールディングを表示します。これは 3.0 TKA、すなわち 3000000000000000000 基本単位を 10^10 で割った値です。

holdings "$P1"

パーティが作成されたばかりの場合、クレジットには最大約 1 分かかることがあります。Gateway がクレジットするまで、トークンはインボックスで安全に待機します。2 分経過しても何も届かない場合は、完全な party ID と EVM トランザクションハッシュを添えて Zenith にお問い合わせください。

ワークフロー 1/b: Zenith EVM に送り返す

P1 の Canton トークン 1.0 TKA 相当を、TKA として自分の EVM アドレスに送り返します。

1. TKA のメールボックスパーティをリクエストする: Gateway は、リクエストを受け取ると Zenith のパーティシパント上にパーティを作成します。これは EVM アドレスとトークンごとに 1 回だけ必要です:

export MBOX_TKA=$(mailbox_party $ME "$TKA_ID")
cast send $REQUESTS 'requestMailbox(address,address)' $ME $TKA --private-key $KEY --rpc-url $RPC

1〜2 ブロック以内に存在するようになります。Gateway のレジストリに問い合わせることで確認できます。存在するようになると、回答が "offer" に切り替わります:

ask "$(transfer_args "$P1" "$MBOX_TKA" 1.0 "$GATEWAY_PARTY" "$TKA_ID")" "$REGISTRY" | jq -c '{transferKind}'

2. 転送を送信する: Canton における 1.0 TKA は 100000000.0 です。最後の引数はオファーを 3 分で期限切れにするよう設定します:

export ARGS=$(transfer_args "$P1" "$MBOX_TKA" 100000000.0 "$GATEWAY_PARTY" "$TKA_ID" 180)
export ANS=$(ask "$ARGS" "$REGISTRY")
submit "$P1" "$ARGS" "$ANS"

submit は {"updateId":"…","result":"TransferInstructionResult_Pending"} を表示します。あなたのオファーは Gateway を待っています。

3. 結果を確認する: 1 ブロック程度で TKA 残高が 1000000000000000000 増加し、pending_count "$P1" は 0 を表示します。残高がその確認となります。

pending_count "$P1"
cast call $TKA 'balanceOf(address)(uint256)' $ME --rpc-url $RPC

オファーが期限切れ後もまだ保留中の場合は、オファーを撤回して Zenith に連絡してください。Gateway は通常 1 ブロック程度でオファーを決済するからです。pending "$P1" はオファーの executeBefore を表示します。撤回すると全額が返還されます:

export OFFER=$(pending "$P1" | jq -r --arg m "$MBOX_TKA" 'select(.receiver == $m) | .cid' | head -1)
export REQ=$(jq -n --arg p "$P1" --arg u "$USER_ID" --arg c "$OFFER" \
'{commands: {commands: [{ExerciseCommand: {templateId: "#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferInstruction", contractId: $c, choice: "TransferInstruction_Withdraw", choiceArgument: {extraArgs: {context: {values: {}}, meta: {values: {}}}}}}], commandId: "", userId: $u, actAs: [$p], readAs: [], disclosedContracts: []}}' \
| new_cmd withdraw)
send_saved "$REQ"
  • オファーが保留中の間はパーティシパントを稼働させ続けてください: あなたのパーティは Gateway の決済を確認する必要があります。パーティシパントがオフラインの場合、オファーは決済も返金もできず、オファーの executeBefore が経過するかパーティシパントが再び応答するまで、testnet 上の全員のブロック生産が遅くなります。これが、ステップ 2 でオファーを 3 分で期限切れに設定する理由です。拒否されたように見えた決済試行も、パーティシパントが再び応答すれば確定することがあるため、再度オファーする前に TKA 残高を確認してください。pending_count <party> が各パーティについて 0 を表示している場合にのみ、パーティシパントを停止または切断してください。error: 行は読み取りに失敗したことを意味し、保留中の転送については何も示しません。

ワークフロー 2/a: Canton トークンを Zenith EVM に送る

P1 から自分の EVM アドレスに TTA を送ります。EVM 側では wTTA として届きます。

1. フォーセットから TTA を入手する: TTA とそのフォーセットを調べます:

export FAUCETS=$(curl -s $TREGISTRY/test-token/v1/faucets)
export TTA_FAUCET=$(echo "$FAUCETS" | jq -c '.faucets[]' | while read -r f; do
k=$(echo "$f" | jq -r '"cip56:" + .instrumentId.admin + ":" + .instrumentId.id')
[ "$(cast call $GATEWAY 'wrapperOf(bytes32)(address)' "$(cast keccak "$k")" --rpc-url $RPC | tr 'A-F' 'a-f')" = "$(printf %s "$wTTA" | tr 'A-F' 'a-f')" ] && echo "$f"
done)
export TTA_ADMIN=$(echo "$TTA_FAUCET" | jq -r .instrumentId.admin)
export TTA_ID=$(echo "$TTA_FAUCET" | jq -r .instrumentId.id)

P1 のために TTA を 10 枚ミントします。フォーセットは 1 回の呼び出しで最大 100 TTA を付与し(その maxPerCall;echo "$TTA_FAUCET" | jq -r .maxPerCall で表示できます)、自分のパーティにのみ付与されます:

export REQ=$(jq -n --arg p "$P1" --arg u "$USER_ID" --argjson f "$TTA_FAUCET" \
'{commands: {commands: [{ExerciseCommand: {templateId: "#zenith-gateway-faucet:Zenith.GatewayFaucet.Faucet:Faucet", contractId: $f.faucet.contractId, choice: "Faucet_Mint", choiceArgument: {requester: $p, amount: "10.0"}}}], commandId: "", userId: $u, actAs: [$p], readAs: [], disclosedContracts: [$f.faucet]}}' \
| new_cmd mint)
send_saved "$REQ"

2. TTA のメールボックスパーティをリクエストする: あなたの EVM トランザクションがパーティをリクエストすると、Gateway は 1〜2 ブロック以内に Zenith のパーティシパント上にパーティを作成します。party ID は EVM アドレスと TTA のフル ID(cip56:<admin>:<id>)から導出されますが、リクエストでは EVM 側のトークンである wTTA を指定します。パーティシパントがパーティを認識するまで待機してください:

export MBOX_TTA=$(mailbox_party $ME "cip56:$TTA_ADMIN:$TTA_ID")
cast send $REQUESTS 'requestMailbox(address,address)' $ME $wTTA --private-key $KEY --rpc-url $RPC
for i in $(seq 1 30); do k=$(lcurl "/v2/parties/$MBOX_TTA" | jq -r '.partyDetails[0].party // "unknown"'); echo "$(date +%T) $k"; [ "$k" = "$MBOX_TTA" ] && break; sleep 1; done

すべての行が unknown を表示する場合は、$TTA_ADMIN と $TTA_ID が設定されていること、$MBOX_TTA が正確に cip56:<admin>:<id> から計算されていること、$wTTA が公開済みのアドレスであることを確認してください。

3. 4.0 TTA を送信する: Gateway のレジストリではなく、TTA 自身のレジストリ($TREGISTRY)に問い合わせます:

export ARGS=$(transfer_args "$P1" "$MBOX_TTA" 4.0 "$TTA_ADMIN" "$TTA_ID" 180)
export ANS=$(ask "$ARGS" "$TREGISTRY")
submit "$P1" "$ARGS" "$ANS"

submit は {"updateId":"…","result":"TransferInstructionResult_Pending"} を表示します。あなたのオファーは Gateway を待っています。オファーが決済されるまでパーティシパントを稼働させ続けてください。期限切れ後もまだ保留中の場合は、ワークフロー 1/b のコマンドを使って撤回してください。その際、$MBOX_TKA の代わりに $MBOX_TTA を使用します。

4. 結果を確認する: 1 ブロック程度で wTTA 残高が 40000000000 になります。これは小数点以下 10 桁で 4.0 に相当します:

cast call $wTTA 'balanceOf(address)(uint256)' $ME --rpc-url $RPC

ワークフロー 2/b: Canton に送り返す

2.0 wTTA を P1 に送り返すと、TTA として届きます。

wTTA は、Gateway がすでにインデックス済みで、TTA の常設承認(転送事前承認)を持つ当事者にのみ送信してください。P1 は Workflow 1/a とステップ 1. の後、両方の条件を満たしています。まだインデックスされていない当事者への wTTA 転送は、通常の転送としてマイニングされ、受信トレイで待機します。その当事者に常設承認がない場合、Gateway はクレジットできません。Gateway はすべての待機中の受信トレイ残高を 1 つの Canton トランザクションでクレジットするため、そのトランザクションはすべての当事者の受信トレイで失敗します。

1. P1 が TTA を直接受け取れるようにする: Gateway はバッキングから TTA を通常の TTA 転送として返します。その転送はクレジットと同じ Canton トランザクション内で完了する必要があります。TTA のレジストリが転送を即座に完了するのは、受信者が TTA の 常設承認 を持っている場合のみです。ない場合、転送は保留中のオファーを作成するだけで、Gateway はクレジットを拒否し、3 回拒否されると wTTA 転送を破棄します。P1 の常設承認を作成してください:

export REQ=$(jq -n --arg p "$P1" --arg u "$USER_ID" --argjson f "$TTA_FAUCET" \
'{commands: {commands: [{ExerciseCommand: {templateId: "#zenith-gateway-proto:Zenith.GatewayProto.TestToken:TestTokenRegistry", contractId: $f.registry.contractId, choice: "TestTokenRegistry_Preapprove", choiceArgument: {receiver: $p}}}], commandId: "", userId: $u, actAs: [$p], readAs: [], disclosedContracts: [$f.registry]}}' \
| new_cmd accept)
send_saved "$REQ"

送信前に確認してください。Gateway 当事者から P1 への転送について TTA のレジストリに問い合わせます。答えは direct でなければなりません。offer は常設承認が存在しないことを意味します。

jq -n -c --arg a "$TTA_ADMIN" --arg id "$TTA_ID" --arg g "$GATEWAY_PARTY" --arg r "$P1" '{choiceArguments: {expectedAdmin: $a, transfer: {sender: $g, receiver: $r, amount: "1.0", instrumentId: {admin: $a, id: $id}}}}' \
| curl -s -X POST "$TREGISTRY/registry/transfer-instruction/v1/transfer-factory" -H 'content-type: application/json' -d @- | jq -c '{transferKind}'

独自の CIP-56 トークンの場合、受信者はトークンのレジストリが提供する任意の方法で常設承認を作成します。

2. 2.0 wTTA を P1 の受信トレイに送信する:

export INBOX=$(cast call $GATEWAY 'inboxOf(bytes)(address)' $(cast from-utf8 "${P1:?set P1 to your full party ID}") --rpc-url $RPC)
cast send $wTTA 'transfer(address,uint256)' $INBOX 20000000000 --private-key $KEY --rpc-url $RPC

3. 結果を確認する: P1 は 2.0000000000 の新しい TTA 保有を持っています:

holdings "$P1"

アプリケーションから Gateway を使用する

上記のワークフローは、アプリケーションが行う呼び出しと同じものを使用します。

Canton アプリケーションから

Zenith EVM からの受け取り は、TKA など Zenith EVM 上で発行されたトークンについてはコードが不要です。wTTA から TTA など、Gateway ERC-20 から戻ってくる Canton トークンを受け取るには、当事者がまずそのトークンのレジストリで常設承認(転送事前承認)を設定する必要があります(Workflow 2/b、ステップ 1)。当事者の受信トレイアドレス(Gateway コントラクト上の inboxOf)を送信者と共有してください。クレジットは当事者の通常の CIP-56 保有として表示されます。

Zenith EVM への送信 は標準的な CIP-56 トークン転送であり、ウォレットと同じ方法で行います:

  1. mailbox_party を使用して、送信先 EVM アドレスとトークンのメールボックス当事者を計算します。インストゥルメントテキストは以下の通りです:

    • ERC-20 オリジナルの Gateway Canton トークンの場合:erc20:<the ERC-20 original's address in lower case, without 0x>(TKA の Canton トークンの場合は $TKA_ID);
    • Canton(CIP-56)オリジナルの場合:cip56:<admin's full party ID>:<id>。Gateway の ERC-20(wTTA)のアドレスを erc20: テキストに入れないでください。

    当事者がまだ存在しない場合は、MailboxRequests コントラクト($REQUESTS)の requestMailbox(account, token) でリクエストしてください。account は送信先 EVM アドレスです。token は Zenith EVM 上のトークンの ERC-20 です:オリジナル自体(TKA)、または Canton オリジナル用の Gateway ERC-20(wTTA)。トークンが登録されている場合、Gateway は 1〜2 ブロック以内に当事者を作成し、そうでない場合はリクエストを無視します。送信前に参加者が当事者を認識するまで待ってください。

  2. requestedAt を少し過去の時刻で(台帳は台帳時刻より後のものを拒否します)、短い executeBefore(例えば数分先)で転送を構築します。トークンレジストリの POST /registry/transfer-instruction/v1/transfer-factory に choiceArguments として送信します:

    • Gateway 発行トークンの場合は Gateway のレジストリを呼び出します;
    • その他のトークンの場合は、トークン独自のレジストリを呼び出します。
  3. 返された factoryId に対して TransferFactory_Transfer を実行し、送信した転送を渡します。返された choiceContextData を extraArgs.context.values として渡し、返された disclosedContracts をコマンドの disclosedContracts に追加します。

  4. 転送は保留中の転送指示を残し、Gateway がそれを決済します。指示が決済、拒否、または取り消されるまで(参加者をオンラインに保つ を参照)、参加者を実行・接続し続けてください。executeBefore 後もまだ保留中の場合は、TransferInstruction_Withdraw で取り消してください。Gateway 以外のトークンの場合は、まずトークンのレジストリに取り消しコンテキスト(POST <registry>/registry/transfer-instruction/v1/<instruction-id>/choice-contexts/withdraw)を問い合わせ、ステップ 3. と同様に渡してください。Gateway と TTA のレジストリは空のコンテキストを返します。

submit ヘルパーは正確な JSON Ledger API コマンドを示しています。

  • 注: Gateway のパッケージには 1 トランザクションのルート(GatewayService_CrossToEvm)もあります。この testnet では外部参加者は利用できません。外部呼び出し拡張と、オペレーターのみが開示できるコントラクトが必要です(この testnet で参加者ができないこと を参照)。上記の標準転送を使用してください。

EVM コントラクトまたはアプリケーションから

Canton 当事者への送信は、当事者の受信トレイへの通常の ERC-20 転送です。Gateway ライブラリ、承認、コールバックは不要です。

interface IZenithGateway {
function inboxOf(bytes calldata partyId) external view returns (address);
function kind(address token) external view returns (uint256); // 0 = not registered
}
// partyId is the recipient's full Canton party ID, e.g. "alice::1220…"
address inbox = IZenithGateway(gateway).inboxOf(bytes(partyId));
IERC20(token).transfer(inbox, amount);

ethers v6 を使用する場合:

const gateway = new Contract(GATEWAY, ["function inboxOf(bytes) view returns (address)"], provider);
const inbox = await gateway.inboxOf(toUtf8Bytes(partyId));
await new Contract(token, ["function transfer(address,uint256) returns (bool)"], wallet).transfer(inbox, amount);

当事者の受信トレイアドレスは、フル当事者 ID、チェーン ID、Gateway コントラクトアドレス、受信トレイコードハッシュから計算されます。これらはデプロイ中に変わらないため、アドレスを保存できます。Zenith が testnet または Gateway コントラクトを再デプロイした場合は、保存されているすべての受信トレイを inboxOf で再検索してください。以前のデプロイ用に計算されたアドレスに送られたトークンは失われます。

コントラクトはCanton creditを参照できず、レシートだけではその証明になりません。 Gatewayがある転送をデポジットとして扱うのは、その転送がGatewayにインデックスされているパーティのinboxに対し、登録済みトークンのゼロ以外の金額を送信する場合に限られます。そのような転送は、creditが行われるブロック内でのみマイニングされます。そのため、その転送においてレシートはcreditが行われたことを意味します。GatewayがデポジットをsettleできなかったIf場合、そのトランザクションはブロックから除外されます。トランザクションはマイニングされず、レシートも受け取れず、トークンもガスも消費されません。約3ブロック後、sequencerはそのトランザクションを、同じアドレスから送信されたその後のすべてのトランザクションとともに破棄します。タイムリミットなしにレシートを待ち続けないでください。ethers v6では、タイムアウトを指定してください(例:tx.wait(1, 120_000))。ドロップを確認して再送する方法については、Common problemsの最初の行を参照してください。inboxアドレスへのその他の転送は、creditなしで通常のブロックにマイニングされます(Transfer directionsを参照)。送信前にkind(token)を確認し、受信者の完全なparty IDを使用してください。

Gateway troubleshooting

送信前の確認事項

  • トークンが登録済みであることを確認してからどこかに送信してください。ERC-20の場合、kind(<token address>)はゼロ以外の値を返します。Cantonトークンの場合、cast keccak "cip56:<admin>:<id>"のwrapperOfはゼロ以外のアドレスを返します。
  • 完全なparty IDを使用してください(hint::namespace):短縮IDを指定すると、いかなるパーティも持てないIDのinboxアドレスが生成されます。Gatewayはそこに送られたトークンをcreditせず、トークンは失われます。
  • mailboxパーティのinbox、Gatewayパーティのinbox、またはchainパーティのinbox(m-で始まるID)、Gatewayパーティ、もしくはchainパーティ(チェーンのブロックに署名するZenithパーティ、<to be added>)にトークンを送らないでください:Gatewayはそれらをcreditせず、トークンは失われます。
  • 送信するトークンに対応した正確なmailboxパーティを使用してください。
  • トークンのdecimalsを確認してください: Gatewayが発行したトークンのdecimalsは10です.

よくある問題

症状原因対処方法
EVM転送のレシートが届かず、その後のトランザクションも処理されないGatewayがデポジットを配信できず、トランザクションをブロックから除外した可能性が高いです。約3ブロック後、sequencerはそのトランザクションを、同じアドレスからの後続トランザクションとともに破棄します。何も移動していません。レシートがないだけではドロップの証明にはなりません。次の4つをすべて確認してください:cast rpc eth_getTransactionByHash <hash> --rpc-url $RPCがnullを返す;cast nonce $ME --rpc-url $RPCがそのトランザクションのnonceを依然として示している;変更されるべき残高が変化していない;2回の読み取りの間にcast block-number --rpc-url $RPCが増加している。lookupがトランザクションを返す場合は待機中です:再送しないでください。ブロック番号が増加しない場合はブロックが停止しています:待機し、継続する場合はZenithに連絡してください。4つの条件がすべて満たされたら、原因を修正し(通常は次の行)、同じnonce(cast send ... --nonce <nonce>)で転送を再送し、その後の各トランザクションをnonce順に送信してください。
wTTA(または別のGateway ERC-20)をCantonに送信した後、レシートが届かない受信者がトークンのstanding acceptance(transfer preapproval)を持っていない受信者にトークン独自のregistryでstanding acceptanceを作成させてください。TTAの場合、受信者はWorkflow 2/b、ステップ1を実行します。その後、上記の行と同様にドロップを確認し、同じnonceで再送してください。
レシートはあるが、トークンがまだinboxにある受信者パーティが新規作成されたばかり、またはトークンがまだ登録されていない約1分待ってください。未登録のトークンは、トークンが登録されると一度creditされます。wTTA(または別のGateway ERC-20)は、受信者がstanding acceptanceを持った時点で初めてcreditされます(Workflow 2/b、ステップ1)。mailboxパーティのinbox、Gatewayパーティのinbox、または不完全なparty IDのinboxに送られたトークンは届きません。2分以内に何も届かない場合は、Zenithに連絡してください。
オファーが数ブロック経過してもpendingのままになっているGatewayは一度に一つのオファーをsettleし、Zenith EVMからのデポジットを優先します。また、participantが停止または切断されている間、あるいはGatewayが停止している間もオファーはpendingのままになります。participantを実行し続け、接続を維持してください。停止または切断されていた場合は、すぐに再接続してください。settlementが完了する可能性があります。それ以外の場合は、さらに数ブロック待ってください。executeBeforeが経過してもオファーがpendingのままであれば、撤回してZenithに連絡してください。
オファーが消え、全額がholdingsに戻っているGatewayが拒否しました:mailboxパーティが別のトークン用である、トークンが登録されていない、またはsettlementが3回連続で拒否された最初の2つの原因のいずれかに該当する場合は修正して再送してください。それ以外の場合は、オファーのCanton update IDとUTC時刻をZenithに連絡してください。
No mailbox for this party is visible to you…mailboxパーティがまだ存在しないか、registryが返したコントラクトを開示していないmailboxパーティをリクエストし、registryが開示したコントラクトと共に送信してください。
UNKNOWN_INFORMEES (not connected to any synchronizer where the given informees are known)受信者パーティが存在しません。通常、誤ったインストゥルメントテキストから計算されたメールボックスパーティ、またはまだ作成されていないパーティです。これは接続の問題ではありません。完全なインストゥルメントテキスト(Canton トークンの場合はcip56:<admin>:<id>)からメールボックスパーティを再計算し、必要に応じてリクエストを行い、lcurl "/v2/parties/<id>"がそれを返すまで待機してください。
askはerror: HTTP 400 from …/transfer-factoryを出力し、submitはerror: bad transfer arguments or registry answer; nothing was sentを出力し、echo "$ANS"は{"error":"the receiver cannot receive this instrument"}を表示します。またはサブミッションがMailbox belongs to another instrumentで失敗します。このトークンに対するメールボックスパーティが正しくありません。EVMアドレスとerc20:<lowercase address of the token you are sending, no 0x>からメールボックスパーティを再計算してください。
External calls not supported1トランザクションのルートを使用しました。メールボックスパーティへの標準転送をご利用ください。
drip()の呼び出しがcooldownで失敗しています。直近1分以内にすでにドリップが実行されました。1分お待ちください。
drip()の呼び出しがemptyで失敗しています。ドリップの残高が10 TKA未満です。待機しても解決しません。補充のためZenithにお問い合わせください。

送金の原因が不明な場合は、Zenithに[email protected]までご連絡ください。EVMトランザクションハッシュ、お持ちであればCantonアップデートID、およびUTCでの時刻をご記載ください。

概念とアーキテクチャ

このセクションでは、Zenith EVMの技術的な概要、その仕組み、Cantonとの関係、およびCantonを介したブロック生成によってEVM実行がどのように調整されるかについて説明します。

Zenith EVMとは?

Zenith EVMは、高性能かつモジュラーなEthereumの実行クライアントであるReth上に構築されたリファレンスEVM実行環境です。標準的なEthereumのJSON-RPCインターフェースを公開しているため、開発者はZenith EVMをEthernet互換チェーンとまったく同じように操作できます。

開発者の視点から:

  • スマートコントラクトのデプロイはSolidity、Foundry、Hardhat、またはその他のEVMツールチェーンを使用して行え、変更は不要です。
  • ウォレットの接続はMetaMaskなどでZenith EVMをカスタムネットワークとして追加することで可能です。
  • 既存ライブラリの利用(ethers.js、viem、web3.js、OpenZeppelinなど)はすべてそのまま使用できます。

Zenith EVMがスタンドアロンのEVMチェーンと異なる点は、Cantonとの関係にあります。すべてのEVMアクティビティはCantonを経由してルーティングされ、Canton MainNet上で決済されます。これにより、Zenith EVMはCantonの規制およびコンプライアンス上の保証を備えながら、Ethereumの開発者体験を完全に維持します。

Zenith EVMは、登録済みのCanton Super ValidatorであるZenithによって管理されています。独自のカスタマイズ可能なEVM環境を立ち上げたいエンタープライズや開発者は、Zenith Stackを通じて実現できます。

Zenith EVMとCantonの関係

Zenith EVMは、Canton上のEVMベースのサブネットとして理解するのが最適です。独自のEthereumスタイルのコンセンサスレイヤーを持つ独立したチェーンではありません。その代わり、Zenith EVMのブロック生成、正規チェーンの状態、およびファイナリティはCantonを通じて調整されます。

概要:

  • EVMユーザーはJSON-RPCを通じて標準的なEVMトランザクションを送信します。
  • トランザクションは収集され、Canton側のブロック提案としてまとめられます。
  • バリデーターは同じ順序付きインプットから提案されたEVMブロックを再構築します。
  • Cantonのファイナリティにより、どの提案されたEVMブロックが正規となるかが決定されます。
  • ファイナライズされたブロックはEVM実行側へと伝播されます。

このアーキテクチャにより、Zenith EVMは通常のEthereumツールおよび実行セマンティクスを維持しながら、決定論的なファイナリティとCantonネイティブのアトミックコンポーザビリティをCantonに依存することができます。

Canton上のオンレジャー表現

Zenith EVMは、単一のモノリシックなステートコントラクトではなく、一連のDamlコントラクトによってCanton上で表現されます。これらのコントラクトは共同で、正規のEVMチェーンの状態を記録し、新しいブロックの生成とファイナライズを管理します。

Canton側でこのオンレジャー状態を整理するために使用される識別子は、ウォレットおよびEVMトランザクションで使用されるEthereum EIP-155チェーンIDとは区別されます。

ブロック生成と正規チェーン状態は、独立したEthereumコンセンサスレイヤーではなく、Cantonコントラクトとバリデーターの確認を通じて管理されるため、Zenith EVMはCantonのファイナリティとトラストモデルを継承します。具体的なオンレジャーコントラクト設計はZenith EVMの実装内部に属します。

external_call()プリミティブ

Zenith EVMはexternal_call()を使用して、Canton側のDamlワークフローと決定論的なEVM実行を接続します。

external_call()プリミティブはCantonネイティブの二段階バリデーションプロセスに従います:

  1. サブミッションフェーズ: サブミットする参加者はインタープリテーション中にexternal_call()を実行し、結果・ハッシュ・メタデータをトランザクションビューに含めます。
  2. バリデーションフェーズ: バリデーターは各自のEVMノードに対してローカルで同じオペレーションを独立して再実行します。
  3. コンセンサスとファイナリティ: 閾値のバリデーターが出力に合意した時点でトランザクションが確認されます。BFTスタイルのクォーラムモデルに従い、少なくとも3分の2のバリデーターが合意する必要があり、合意が得られない場合はトランザクションが拒否されます。Cantonメディエーターは、必要なバリデーター確認の閾値に達した時点でトランザクションをファイナライズします。

この設計は、Cantonの既存モデルを超える追加のトラスト仮定を導入しません。

CantonとZenith EVMにまたがるトランザクションの実際的な保証は次のとおりです:

  • Canton側とEVM側の両方が成功した場合、結合されたトランザクションはコミットされます。
  • どちらかが失敗した場合、トランザクション全体が失敗します。
  • 失敗したワークフローは、EVMまたはCanton側のステートトランジションを部分的にコミットした状態のままにしません。

バリデーターノード

Zenith EVMバリデーターとは、EVMチェーンのブロック生成を検証・確認する権限を持つCanton参加者です。バリデーターはこのプロセスに参加するために必要なCantonおよびEVMノードソフトウェアの両方を実行しますが、正規チェーンは単一のノードによって決定されるわけではありません。

Zenith EVMチェーンの信頼できる情報源は、個々のノードのローカルビューではなく、Cantonによってファイナライズされたブロックのシーケンスです。ノードは候補ブロックを構築できますが、Cantonのファイナリティが確立された後にのみ正規状態が更新されます。

ブロック構築

Zenith EVMにおけるブロック構築は、Ethereum mainnetとは異なります。Zenith EVMは、Ethereumのメンプール駆動のプロポーザー・ビルダーフローやEthereum Beacon Chainのバリデーター選択を使用しません。代わりに、ブロックはCantonを介したプロポーザルフローを通じて構築されます。

大まかに言えば、次のブロック向けのトランザクションはCanton側で収集・順序付けされます。次のブロックの準備が整うと、認可されたバリデーターがビルドを主導します:

  • 候補ブロックがトランザクションの順序付きセットから組み立てられ実行され、
  • 他のバリデーターが同じインプットから同じブロックを独立して再構築します。

Cantonのファイナリティにより、提案されたブロックが正規となるかどうかが決定され、その後EVM実行レイヤーに伝播されてステートが更新されます。

バリデーションに失敗したトランザクションは拒否され、結果のブロックから除外される場合があります。送信されたトランザクションが拒否された場合、結果のブロックには表示されず、そのレシートも存在しません。

ファイナリティとレイテンシー

Zenith EVMトランザクションのファイナリティはCantonコンセンサスによって提供されます。バリデーターは同じインプットから提案されたEVMブロックを独立して再構築し、Cantonメディエーターは必要なバリデーター確認の閾値に達した時点でブロック構築トランザクションをファイナライズします。

EVM実行がCantonトランザクションフローを通じて調整されているため:

  • 実行はアトミック: Canton/EVMの複合ワークフローはコミットされるか、全体として失敗するかのいずれかです。
  • ファイナリティは決定論的: Cantontransactionが確定されると、EVMの状態遷移は最終的なものとなります。
  • Cantonfinalityの後にEVM reorgは発生しない: 正規のEVM状態は、ファイナライズされたCanton仲介のブロックシーケンスに従います。

現在のtestnetでは、Canton-nativeトランザクション内の追加EVM操作のエンドツーエンドの典型的なレイテンシは、400ミリ秒から1.5秒の間で変動します。これは、約4〜5秒でファイナリティに達するCanton専用トランザクションと比較して、わずかなレイテンシの増加にとどまります。

注記: これらのレイテンシの数値は初期testnet計測によるものであり、まだ最適化されていません。

Ethereum Mainnetとの相違点

Zenith EVMはReth上に構築され、標準的なEthereum RPCインターフェースを公開しているため、同じ開発パターンをZenith上で直接適用できます。このセクションでは、異なる点を説明します。

同じ点は?

領域Zenith EVMの動作
EVM実行同一のバイトコード実行、同一のオペコード。
Solidityコントラクト標準的なSolidityコントラクトは変更なし、または最小限の変更でデプロイ可能。
RPC標準Ethereum JSON-RPCインターフェース
ツールMetaMask、Foundry、Hardhat、Remix、ethers.jsなど一般的なEVMツールと互換性あり。
トランザクション標準的なEVMトランザクションの署名と送信。
ガス計算現在のZenith EVM testnetは、実行レイヤーで標準的なEVMガスセマンティクスを使用しています。
標準規格標準的なERCコントラクトはネイティブにデプロイおよび使用できます。
フォークEthereumのジェネシスからの現在のすべてのフォークは、Zenith EVMで有効化されます。

注記: Zenith EVM mainnetにおけるガス計算には、Cantontransaction手数料をカバーする追加要素が含まれます。

何が違うの?

エリアEthereum mainnetZenith EVM
コンセンサスEthereum プルーフ・オブ・ステーク・コンセンサス。Canton を介した検証とファイナリティ。
ファイナリティ確率的(ファイナリティまで2エポック / 約13分)数秒以内に決定論的。リオーグなし。
リオーグファイナリティ前は発生する可能性あり。Canton ファイナリティ後は EVM リオーグなし。
ビーコンチェーンEthereum はコンセンサスとバリデーター選択のランダム性に Beacon Chain を使用します。Zenith EVM は Ethereum Beacon Chain を使用しません。
PREVRANDAOEthereum コンセンサスコンテキストから取得されます。Zenith の Canton を介したブロック構築フローにより、ブロック構築環境の属性(Prev RANDAO)として提供されます。Ethereum Beacon Chain のランダム性によるものではありません。コントラクトは Ethereum mainnet のバリデーター選択ランダム性のセマンティクスを前提としないでください。
ブロック生成Ethereum バリデーターは Ethereum コンセンサスルールに従ってブロックを提案します。ブロック提案者は Canton 側のブロック提案を送信し、バリデーターは Canton コンセンサスを通じてブロックを構築・確認します。
ブロック時間12秒のブロック時間当社の testnet のブロック時間は5秒になります。mainnet については、1秒のブロック時間を目指しています。
決済「Ethereum mainnet決済。」EVMブロックとステートルートがCantonで決済されます。
Cantonとのアトミックな相互運用ネイティブでは利用できません。Canton固有のワークフローとZenith EVM実行は、単一のアトミックなDamlワークフローの一部として調整できます。
ステーキングバリデーターは32 ETHをステークバリデーターは認可されたCantonの参加者であり、ステーキングの仕組みはありません
トラストモデルEthereumバリデーターセットおよびethereumコンセンサスルール。Cantonのトラストモデルに従います
エクスプローラーメタデータEVMのみのトランザクションメタデータ。EVMトランザクションメタデータとCantonアップデートメタデータ。

開発者への影響

  • 標準的なEVMワークフローは引き続き利用可能: デプロイ、インタラクション、イベント、レシート、ツールはこれまでと同様に使用できます。
  • 確定したトランザクションはファイナル: Cantonを通じてトランザクションがファイナライズされると、確率的ファイナリティチェーンのように複数のブロック確認を待つ必要は基本的にありません。
  • デュアルトランザクションID: EVMトランザクションは、そのトランザクションが含まれたEVMブロックに対応するCantonのアップデートIDで追跡できます。
  • Canton固有のワークフローがEVM実行を調整可能: すべてのZenith EVMトランザクションバッチは、Cantonを介したアトミックなブロック構築パスで処理されます。
  • アトミックワークフローにおけるレシートの確認: EVMトランザクションが拒否された場合、そのレシートは存在しません。Canton側のワークフローはこれをEVMレッグの失敗として扱う必要があります。
  • Ethereum Beacon Chainのランダム性セマンティクスに依存しないでください: block.prevrandaoを使用するコントラクトは、Zenith EVMがこの値をEthereum Beacon Chainのバリデーター選択ランダム性から取得しないことを考慮してください。

相互運用性

Zenith EVMは、相互補完的な2つの相互運用性モデルをサポートしています。

  1. Gateway、Canton固有のワークフローとZenith EVMの間のZenithのアトミックな相互運用性パス。
  2. 外部エコシステムの相互運用性:Zenith EVMを外部チェーンおよびメッセージングエコシステムに接続します。

Zenith Gateway

GatewayはZenithのネイティブな相互運用性プロトコルであり、Canton上のアセットとアプリケーションをZenith EVMと接続します。単一のトランザクション内でCantonとEVMの操作を組み合わせたアプリケーションを実現するため、双方向のアセット転送とコントラクト呼び出しをサポートするよう設計されています。

例として、Cantonのワークフローが対象アセットをZenith EVMに移動し、アプリケーション内で使用した後、一つの協調した操作としてCantonに価値を返すことができます。

Gatewayと外部ブリッジの比較

GatewayはZenithのコアプロトコルの一部であり、そのトラストモデルをCantonから継承しています。そのため、サードパーティのブリッジやクロスチェーンメッセージングプロトコルとは区別する必要があります。

プロパティゲートウェイサードパーティ相互運用 / メッセージングプロトコル
主な対象範囲Canton ↔ Zenith EVMZenith EVM ↔ 外部エコシステム
実行モデルCanton を介したブロック生成クロスチェーンメッセージパッシングまたはトークン転送
ファイナリティモデル組み合わせたワークフロー全体に対する Canton のトラストおよびファイナリティモデルソースチェーン、デスティネーションチェーン、およびプロトコルの前提条件に依存
障害時の動作単一のアトミックワークフローにおいて Canton / EVM の部分的なコミットは発生しない通常は非同期であり、障害およびリトライの動作はプロトコル固有
最適なユースケースCanton ネイティブおよび EVM ネイティブのアセットを組み合わせたワークフロー、Zenith EVM コントラクトと連携する Daml ワークフローEthereum、Base、Solana、およびその他の外部エコシステムとの相互運用性

アトミックセトルメント

Canton と Zenith EVM 間における Gateway を通じたアセット転送は クロッシング と呼ばれます。各クロッシングは アトミック です。送信側での控除、デスティネーション側での受領、および対応する EVM ブロックは、完了した不可分の単一 Canton トランザクションとして一括記録されます。

バリデーターはセトルメント前に実行とアセット移動の全体を検証します。包含するワークフローがアボートした場合、そのアセット移動およびアプリケーションの変更はコミットされません。

Canton と Zenith EVM 間のアセット移動

Gateway は対応する表現を作成することで、対象アセットをもう一方の環境で利用可能にします。

  • Canton のアセットは Zenith EVM 上で ERC-20 トークンとして表現できます。また
  • EVM のアセットは Canton の CIP-56(または将来的には CIP-112)トークン標準を使用して表現できます。

表現は常に原資産によって裏付けられています。

アセットがクロスする際、Gateway は原資産のロックと受信側における表現のミントをアトミックに調整します。ラップされたアセットをソースチェーンに戻すと、同一トランザクション内でその表現がバーンされ、原資産が解放されます。

資産の移転経路は、その資産のルールに依存します。一部の資産では、移転を受け取るために受取人の承諾または事前の許可が必要です。保留中の移転オファーは完了したクロッシングではなく、承諾と引渡しが同時に決済されたときにクロッシングが成立します。

DamlとSolidityアプリケーションの連携

Damlアプリケーションは、同じワークフローの実行中にSolidityコントラクトを呼び出して結果を受け取ることができます。EVMアプリケーションは、Gateway経由で公開されたDamlコントラクトの操作を呼び出すことができます。これにより、開発者は両方の環境にまたがってロジックを組み合わせ、ある呼び出しの結果を次のアクションの決定に利用できます。

Cantonユーザーは、Cantonを通じてアクションを承認し、EVMの秘密鍵を管理することなくEVMアプリケーションと対話できます。EVMユーザーは、Cantonパーティを維持することなく、既存のウォレットで署名できます。呼び出しは、対象アプリケーションが要求するパーミッションに引き続き従います。

Gatewayは標準的なEthereumトランザクションとコントラクトインターフェースを使用するため、開発者は使い慣れたSolidityコントラクト、ウォレット、およびEVMツールで作業できます。アプリケーションは、必要に応じて個々の呼び出し失敗を処理できます。未処理の失敗または明示的な中断は、両方の環境にまたがる包含ワークフロー全体をロールバックします。

実行の決済方法

GatewayはDamlとEVMの実行を単一のHead、すなわちプロトコルが記録した最新の決済済みEVMブロックを中心にまとめます。新しいブロックはそのレコードを次のように拡張します:

  1. ユーザーはCantonコマンドまたは署名済みEVMトランザクションでアクションを承認します。
  2. 次のブロックを準備するブロックプロデューサーは、Damlワークフローと EVM呼び出しをまとめて評価します。各呼び出しは、呼び出し元ワークフローの実行中に結果を返します。
  3. プロデューサーは通常のEthereumブロックと対応するCantonの変更を構築します。失敗した呼び出しや後に取り消された呼び出しによる変更は、両側で破棄されます。
  4. 独立したバリデーターがDamlおよびEVMの操作を再実行します。ユーザーの承認、実際の資産移動、呼び出し結果、および新しいブロックが最新の決済済みブロックを拡張していることを確認します。
  5. 必要なCantonの確認が得られると、結合されたトランザクションはCantonにコミットされ、Headが更新されます。その後、EVMブロックは新しいチェーンヘッドを記録するEVMノードに公開されます。

結果と決済の検証

アプリケーションは同じトランザクション内で呼び出し結果を受け取り、通常のEVMレシートとログを確認できます。レシートが存在するだけでは成功を証明しません。決済前に評価されたEVM転送も、Canton上の引渡しやバッキングリザーブへの収集が完了したことを証明しません。Gatewayは、ブロックをチェーンに受け入れる前に、完全な実行と一致する資産移動を確認します。

トランザクションの追跡には、承認済みブロックからのレシート、その成功または失敗のステータス、および期待される結果またはイベントを確認してください。それをワークフローを記録したCantonアップデートと照合してください。Canton更新IDは標準のEthereumレシートとは別のものです。Damlアプリケーションは、ブロックを生成したりEVMガスを消費したりすることなく、特定の決済済みHeadにおけるEVM残高、ストレージ、および呼び出し結果を読み取ることもできます。

Zenith EVM testnetでGatewayをテストするには、CantonおよびGateway開発者ガイド](をご覧ください。

外部エコシステムとの相互運用性

外部エコシステムとの相互運用性はGatewayとは別のものです。

GatewayはCanton ↔ Zenith EVMのアトミックな合成可能性のために設計されており、ワークフローの両側がCantonを通じて仲介されます。Ethereum、Base、Solana、またはその他のL1/L2ネットワークなどのエコシステムとの外部相互運用性は、異なるモデルに従います:独立したネットワーク間のクロスチェーンメッセージング、ブリッジング、またはトークン転送です。

これらのインテグレーションは、CCIPやLayerZeroなどの確立されたクロスチェーンメッセージングまたはブリッジプロトコルを使用する場合があります。GatewayのCanton仲介によるアトミック性ではなく、選択した外部プロトコルの信頼性、ファイナリティ、リトライ、および失敗の前提に従います。

現状: GatewayはアトミックなワークフローのためのCanton ↔ Zenith EVMの主要な相互運用性メカニズムです。外部エコシステムとのインテグレーションは、別のロードマップ項目です。