Zenith EVM: Testnet 문서
EVM 빠른 시작
아래 섹션에서는 표준 Ethereum 툴링을 사용하여 간단한 Solidity 계약을 Zenith EVM Testnet에 배포하는 단계를 안내합니다.
Canton 측 상호작용 및 Zenith EVM testnet과 기반 Canton 테스트 네트워크를 연결하는 게이트웨이를 살펴보려면, Canton 및 게이트웨이 개발자 가이드 아래의 전용 섹션을 확인하세요.
Zenith EVM은 Reth 기반 환경으로 표준 Ethereum JSON-RPC 엔드포인트를 제공합니다. 대부분의 EVM 워크플로우는 네트워크 구성만 변경하면 됩니다.
지갑에 testnet 추가
MetaMask 또는 다른 EVM 지갑에서 Zenith EVM Testnet을 커스텀 네트워크로 추가하세요.
| Field | Value |
|---|---|
| Network name | Zenith EVM Testnet |
| RPC URL | https://rpc.testnet.zenith.network/ |
| RPC name | Zenith EVM Testnet RPC |
| Chain ID | 936485 |
| Token symbol | ZTH |
| Block explorer URL | https://explorer.testnet.zenith.network/ |
지갑에 자금 충전
Testnet 토큰 받기
Zenith EVM testnet 파우셋을 사용하세요:
ZTH는 Zenith EVM testnet의 기본 가스 토큰입니다. 소수점 18자리를 가지며 계약 배포 및 트랜잭션 전송에 필요합니다.
기본 EVM 빠른 시작의 경우 ZTH만 있으면 됩니다. Testnet wCTN(Wrapped Canton)은 로컬 Canton 테스트 네트워크와 직접 상호작용하는 Canton 네이티브 워크플로우에만 관련이 있습니다.
간단한 계약 만들기
아래는 Zenith EVM에 계약 배포를 테스트하는 데 사용할 수 있는 간단한 계약 예시입니다.
// SPDX-License-Identifier: MITpragma 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 파일을 버전 관리에 커밋하지 마세요
- .env를 .gitignore에 추가하세요
- 프로덕션 배포 시 시크릿 매니저(예: AWS Secrets Manager, HashiCorp Vault) 사용을 고려하세요
- 필요한 자금만 보유한 전용 배포자 지갑을 사용하세요
Foundry로 배포
아래 지침에 따라 Foundry를 사용하여 계약을 배포하세요:
forge init zenith-countercd zenith-counter# Replace the default Counter with the contract above.forge buildforge 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 트랜잭션 전체 목록
- 연결된 트랜잭션: EVM 트랜잭션과 해당 Canton 업데이트를 연결하여 표시
- 컨트랙트: 배포된 컨트랙트 세부 정보 보기
트랜잭션 페이지에는 표준 EVM 트랜잭션 세부 정보, 로그, 컨트랙트 상호작용이 표시됩니다.
연결된 트랜잭션 뷰는 Zenith 고유의 기능으로, 각 EVM 트랜잭션에 해당하는 Canton 업데이트 ID를 표시하여 EVM에서 Canton까지 트랜잭션의 전체 생명주기를 추적할 수 있습니다.
평균 지연 시간에 대한 참고: 대시보드에서 3~4초의 평균 지연 시간은 Canton 테스트 네트워크에서 제출부터 최종 확정까지의 종단간 트랜잭션 처리 시간을 의미합니다.
내부적으로, RPC를 통해 제출된 EVM 트랜잭션은 Zenith EVM Sequencer에 의해 수집되어 Canton 중재 블록 제안에 포함됩니다. 이는 표준 EVM 개발자 워크플로를 변경하지 않습니다.
추가 EVM 개발자 리소스
EVM 기초, Solidity 개발 패턴, 그리고 Zenith EVM과 완전히 호환되는 표준 툴링에 대해 더 자세히 알아보려면 Ethereum 개발자 문서를 참조하세요:
- Ethereum 개발자 문서: 핵심 개념, 스마트 컨트랙트 개발 및 배포 가이드.
- Ethereum JSON-RPC 레퍼런스: Zenith EVM이 지원하는
eth_*메서드의 전체 사양. - Ethereum JavaScript APIs: ethers.js, web3.js 및 기타 클라이언트 라이브러리 가이드.
EVM 개발자 가이드
아래에서 Zenith EVM과 직접 상호작용하기 위한 개발자 가이드를 확인하세요.
Canton 측 상호작용 및 Zenith EVM testnet과 기반 Canton 테스트 네트워크를 연결하는 게이트웨이를 탐색하려면 Canton 및 Gateway 개발자 가이드 전용 섹션을 확인하세요.
MetaMask 설정
- MetaMask 열기
- 네트워크 드롭다운 선택
- 사용자 정의 네트워크 추가 선택
- 다음과 같이 Zenith EVM Testnet 세부 정보 입력
| Field | Value |
|---|---|
| Network name | Zenith EVM Testnet |
| RPC URL | https://rpc.testnet.zenith.network/ |
| RPC name | Zenith EVM Testnet RPC |
| Chain ID | 936485 |
| Token symbol | ZTH |
| Block explorer URL | https://explorer.testnet.zenith.network/ |
또는 원클릭으로 네트워크를 추가하세요:
네트워크를 추가한 후 https://explorer.testnet.zenith.network/faucet에서 testnet ZTH를 요청하세요.
가스 및 수수료
현재 Zenith EVM testnet은 실행 레이어에서 표준 EVM 가스 시맨틱을 사용합니다. 가스 예측 및 트랜잭션 영수증은 eth_estimateGas 및 eth_getTransactionReceipt과 같은 표준 Ethereum JSON-RPC 메서드를 통해 확인할 수 있습니다.
가스는 Zenith EVM Testnet의 기본 통화인 ZTH로 지불됩니다.
Foundry 설정
Foundry는 Zenith EVM과 별도의 설정 없이 바로 작동하며, 필요한 설정은 testnet 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 buildforge 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 프로젝트는 testnet RPC를 가리키는 네트워크 항목을 추가하여 Zenith EVM에 연결합니다. 아래 설정은 TypeScript와 공식 Hardhat Toolbox 플러그인을 사용하지만, JavaScript 설정도 동일하게 작동합니다.
필요한 경우 Hardhat을 설치하세요:
npm install --save-dev hardhat @nomicfoundation/hardhat-toolboxHardhat 설정을 생성하거나 업데이트하세요:
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 compilenpx 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를 사용하세요.
- MetaMask에 Zenith EVM Testnet을 추가하세요.
- testnet ZTH로 계정에 자금을 충전하세요.
- Remix를 여세요.
- Solidity 컨트랙트를 컴파일하세요.
- Deploy & Run Transactions에서 Injected Provider - MetaMask를 선택하세요.
- MetaMask가 Zenith EVM Testnet에 연결되어 있는지 확인하세요.
- 다른 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 및 애플리케이션 개발을 위한 단일 진입점입니다. |
| 글로벌 동기화 문서 | https://docs.canton.network/global-synchronizer/understand/overview | Validator 및 노드 운영, 그리고 LocalNet / DevNet / TestNet / MainNet 환경 모델. |
| Canton API 참조 | https://docs.canton.network/api-reference | Ledger 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은 컨트랙트를 배포하고, 트랜잭션을 시작하며, 프로덕션 전환 전에 네트워크를 탐색할 수 있는 공개 테스트 환경입니다. 아래의 세부 정보를 사용하여 지갑 또는 툴링을 구성하고, faucet에서 testnet 토큰을 받아 시작하세요.
| Field | Value |
|---|---|
| Network name | Zenith EVM Testnet |
| Chain ID | 936485 |
| Zenit EVM Testnet RPC | https://rpc.testnet.zenith.network/ |
| Explorer | https://explorer.testnet.zenith.network/ |
| Faucet | https://explorer.testnet.zenith.network/faucet |
| Native currency name | Zenith token |
| Currency symbol | ZTH |
| Decimals | 18 |
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 validator 인프라는 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 Gateway 개요: 토큰 전송 작동 방식 및 자체 토큰 등록
- Gateway와 상호작용하기: 네트워크 정보, 수량, 설정
- Gateway 샘플 워크플로우: 네 가지 단계별 전송
- 애플리케이션에서 Gateway 사용하기
- Gateway 문제 해결
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 파티의 인박스 주소는 목적지가 될 수 없음;
- EVM을 거치지 않고 Canton 파티 간에 Gateway 발행 토큰을 전송;
- Zenith EVM testnet이 정산하는 동일한 원장을 기반으로 자체 Daml 애플리케이션을 실행.
참가자를 연결한다고 해서 다음 사항이 적용되지는 않습니다:
- Zenith EVM 검증자가 되거나, EVM 블록을 확인하는 참가자 집합에 추가되지 않습니다. 단, Zenith EVM에 대한 자체 파티 전송의 Gateway 정산은 확인하므로, 해당 전송이 진행 중인 동안에는 온라인 상태를 유지하세요 (자세한 내용은 참가자 온라인 유지 참조);
- 다른 참가자의 프라이빗 Canton 데이터에 대한 접근 권한이 부여되지 않습니다;
- Zenith 또는 다른 누군가의 컨트랙트에서 파티가 자동으로 행동 권한을 얻지 않습니다;
- EVM 블록을 직접 생성할 수 없습니다. 두 가지 Gateway 경로에는 이 권한이 필요합니다:
GatewayService_CrossToEvm및WrapperFactory을 통한 단일 단계 반환. 자세한 내용은 이 testnet에서 참가자가 할 수 없는 것을 참조하세요.
온보딩 요약
- Canton 빌드를 다운로드하고, Zenith에 연락하여 Zenith 번들(DAR)에 대한 접근 권한을 요청하세요.
- 참가자를 구성하고 시작합니다.
- Zenith testnet 동기화 장치에 연결합니다.
- 연결을 확인합니다.
- 자체 DAR과 함께 Zenith DAR을 업로드하고 검증합니다.
- 파티와 Ledger API 사용자를 생성합니다.
- 애플리케이션을 연결합니다.
Canton 참가자를 Zenith의 동기화 장치에 연결하려면 Zenith 번들에 대한 접근이 필요하지만, 이는 공개적으로 제공되지 않습니다. Gateway 접근을 요청하려면 [email protected]으로 Zenith에 문의하시면 번들을 공유해 드립니다.
同期プログラムは、以下のCantonバージョンを実行する任意の参加者の接続を許可します。接続の詳細は以下の表で確認でき、Gatewayの値はGatewayネットワーク情報で確認できます。
- 注意: EVM側のすべての機能は、参加者を接続したり、Zenithバンドルへのアクセスがなくてもテストできます。
Cantonテストネットワーク情報
| Field | Value |
|---|---|
| Participant registration policy | Open: the synchronizer admits any participant that runs the Canton build listed below |
| Canton build | Canton 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 bundle | The 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 version | dev (a development protocol version; see the note below) |
| Recommended Daml SDK version | 3.6.1-snapshot.20260903.34.0.v027a1da5 |
| Sequencer public API endpoint | https://synchronizer.testnet.zenith.network |
| Synchronizer ID | da::1220daf7fcc9d67e83d9d884ea3a30bc4d0f7927d3f8d1b570d6296a27569dc59943. Physical synchronizer ID, for pinning the connection in step 3: da::1220daf7fcc9d67e83d9d884ea3a30bc4d0f7927d3f8d1b570d6296a27569dc59943::dev-0 |
| TLS | TLS, 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 RPC | https://rpc.testnet.zenith.network |
| Zenith EVM chain ID | 936485 |
Canton SequencerエンドポイントはZenith EVM Testnet RPCとは異なるエンドポイントです。参加者はSequencerと通信します。EVMウォレットやツールはRPCと通信します。
- 重要: testnet同期プログラムは開発用Cantonプロトコルバージョンを実行しています。ストックリリースを含む他のCantonバージョンはサポートされていません。
devプロトコルバージョンはCantonビルドごとに変更される可能性があるため、上記に記載されているCantonスナップショットを正確に実行し、起動前にそのSHA-256を確認してください。
前提条件
接続する前に、以下が必要です:
- Digital Assetからダウンロード可能なCantonビルド、およびZenithバンドル(DAR群)(以下のステップ1を参照してZenithへのアクセスをリクエストしてください)、ならびにビルドを実行するための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には、バンドルがビルドされたコミットが記載されており、bin/canton.jarとしてのCanton jarを含む、そのビルドの各ファイルのSHA-256がリストされています。Zenithのサービスバイナリなど、お手元のコピーに含まれていないファイルもリストされる場合があるため、以下のチェックではjarとDARの行のみを読み取ります。
Digital Assetの公開レジストリからCantonビルドをダウンロードし、その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 -versionshasumコマンドはbin/canton.jarの公開済みSHA-256を出力する必要があります。MANIFESTチェックは7行を出力します(jarに1行、各DARに1行)。各行の末尾はOKでなければなりません。いずれかのチェックが失敗した場合は、ビルドを実行しないでください。jarが失敗した場合は、再ダウンロードしてください。DARが失敗した場合は、Zenithに連絡してバンドルの新しいコピーを入手してください。java -versionコマンドはJava 17またはJava 21を表示する必要があります。
2. 参加者の設定と起動
一般的なインストールガイダンスについては、CantonのCanton インストールドキュメントを参照してください。同期プログラムが開発プロトコルバージョンを実行しているため、3つの設定が必要です。alpha-version-supportとdev-version-supportをyesに設定してください(グローバルと参加者自身のparametersの両方)。また、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.cantondaemonで起動した参加者に対してこのガイドのコンソールコマンドを実行するには、リモートコンソール設定を一度生成し(remote-myparticipant.confが書き込まれます)、それでコンソールを開いてください:
java -jar "$BUNDLE/bin/canton.jar" generate remote-config -c participant.confjava -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 테스트 네트워크 정보에 게시된 물리적 동기화기 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.headutils.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()결과에 다음을 포함하는 항목이 하나 있어야 합니다:
- 동기화기 별칭
zenith-testnet; - Canton 테스트 네트워크 정보에 게시된 동기화기 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.dar | Zenith EVM의 온레저 핵심 컨트랙트 |
zenith-gateway-proto-0.1.0.dar | Gateway: 토큰 등록, 메일박스 컨트랙트, Gateway 발행 토큰 및 해당 전송 팩토리, 그리고 testnet의 Canton 테스트 토큰 TTA와 그 레지스트리 |
zenith-gateway-faucet-0.1.0.dar | TTA를 발행하는 파우셋 |
splice-api-token-metadata-v1-1.0.0.dar | CIP-56 토큰 표준: 메타데이터 인터페이스 |
splice-api-token-holding-v1-1.0.0.dar | CIP-56 토큰 표준: 보유 인터페이스 |
splice-api-token-transfer-instruction-v1-1.0.0.dar | CIP-56 토큰 표준: 전송 인터페이스 |
순서가 중요한 이유: Gateway는 파티가 생성된 직후, 귀하의 참가자가 Gateway 패키지를 심사했는지 여부와 관계없이 인박스 인덱스에 새 파티를 추가합니다. 참가자가 패키지를 심사하기 전까지는 Gateway가 해당 참가자가 호스팅하는 어떤 파티에도 크레딧을 지급할 수 없습니다. 그러한 파티로의 입금은 거부되며, 3번 거부된 후 Gateway는 해당 EVM 트랜잭션을 블록에 포함되기 전에, 동일한 블록 제안에 포함된 다른 입금과 함께 삭제합니다. 발신자는 토큰을 보유한 채 다시 전송해야 합니다. 심사는 각 파티가 아닌 참가자 단위로 기록되므로, 효력이 발생하면 업로드 이전에 생성된 파티를 포함하여 해당 참가자가 호스팅하는 모든 파티에 적용됩니다. 연결하고, DAR을 업로드한 후에 파티를 생성하여, 업로드 전에 어떤 입금도 귀하의 파티에 도달하지 않도록 하십시오.
- 중요: 소스에서 직접 빌드한 DAR이 아닌 Zenith 번들의 DAR을 사용하십시오. 번들이 빌드된 이후 Daml 소스가 변경될 수 있으며, 변경된 소스에서 빌드되거나 다른 SDK 버전으로 빌드된 패키지는 다른 패키지 ID를 갖게 됩니다. 아래 확인 절차를 통해 귀하의 DAR이 일치하는지 여부를 확인할 수 있습니다.
업로드 전에 반드시 Gateway 패키지 ID를 확인하십시오. Canton 콘솔(또는 daemon 프로세스)이 첫 번째 터미널에서 실행되므로, 이 단계와 6단계의 셸 명령을 두 번째 터미널에서 실행하고, 그 터미널에서도 BUNDLE을 설정하십시오(export BUNDLE=<the bundle directory>, 1단계와 같이). 확인에는 jq과 unzip, 그리고 다음 변수들이 필요합니다. 이 값들은 Gateway 워크플로우가 나중에 사용하는 테스트 토큰 아래 export 블록의 값과 동일합니다:
export REGISTRY=https://gateway.testnet.zenith.networkexport GATEWAY_PARTY=gateway::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8export TKA=0x8464135c8f25da09e49bc8782676a84730c318bcexport 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"fiecho은 64자리 16진수로 구성된 패키지 ID를 출력합니다. unzip 줄은 해당 ID로 끝나는 이름을 가진 .dalf 파일을 정확히 하나 출력합니다. 아무것도 출력되지 않으면, 귀하의 번들은 배포된 Gateway와 다른 빌드에서 생성된 것입니다: 업로드하지 마십시오. 현재 번들은 Zenith에 문의하십시오. error: 줄이 표시되면 레지스트리 요청이 실패한 것이며 아무것도 확인되지 않은 것입니다.
확인이 통과된 후에만 DAR을 업로드하십시오. Zenith 패키지의 모든 빌드는 동일한 이름과 버전을 가지며, Canton은 동일한 이름과 버전을 가진 두 번째의 다른 패키지 심사를 거부합니다(KNOWN_PACKAGE_VERSION). 이미 잘못된 빌드를 심사한 참가자는 현재 번들을 사용할 수 없습니다. 가장 간단한 해결책은 빈 데이터베이스로 새 참가자를 생성하는 것이며, 이 단계에서는 아직 파티가 없으므로 비용이 발생하지 않습니다.
LEDGER을 귀하의 참가자의 JSON Ledger API, 즉 2단계의 http-ledger-api 주소로 설정하십시오. 5단계와 6단계의 JSON Ledger API 명령이 이를 사용합니다:
export LEDGER=http://127.0.0.1:7575Ledger 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 테스트 네트워크 정보에 나열된 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는 해당 당사자를 수신함 인덱스에 추가하며, 일반적으로 30초 이내에 완료됩니다. Gateway가 다른 전송을 처리하는 중에는 더 오래 걸릴 수 있습니다. 그 이후에는 Zenith EVM의 당사자 수신함 주소로 전송된 토큰이 해당 당사자에게 적립됩니다. 그 이전에 전송된 토큰은 일반 전송으로 채굴되어 Gateway가 이후 블록에서 적립할 때까지 수신함에 남아 있습니다. wTTA는 예외이며, 워크플로 2/b를 참조하십시오. 별도의 요청이나 수수료 지불은 필요하지 않습니다.
7. 애플리케이션 연결
애플리케이션을 참여자의 Ledger API에 연결하십시오:
| Application setting | Value |
|---|---|
| Ledger API endpoint | Your participant’s gRPC or JSON Ledger API. |
| Ledger API authentication | Configured locally. |
| Application user | The user you created in step 6. |
| Application party IDs | The parties you created or host on your participant. |
이제 애플리케이션은 참여자를 통해 일반 Daml 명령을 제출할 수 있습니다. Zenith EVM으로 또는 Zenith EVM에서 토큰을 이동하려면, Gateway와의 상호작용 및 Gateway 샘플 워크플로를 계속 진행하십시오.
참여자를 온라인 상태로 유지하기
귀하의 당사자 중 하나가 Zenith EVM으로 토큰을 전송할 때, Gateway는 해당 당사자가 확인해야 하는 트랜잭션에서 전송을 처리합니다. 이는 지갑 및 EVM 확장 기능이 없는 모든 참여자가 사용하는 표준 경로를 포함합니다 (전송 방향 참조).
-
경고: 귀하의 당사자들이 Zenith EVM으로 전송하는 작업이 아직 처리 중인 동안에는 참여자를 실행 상태로 유지하고 연결을 끊지 마십시오. 참여자가 응답하지 않으면, 당사자가 양쪽 모두를 확인해야 하기 때문에 전송이 처리되거나 거부될 수 없습니다. 출금도 불가능한데, 출금이 귀하 자신의 참여자를 통해 처리되기 때문입니다. 각 처리 시도는 또한 중재자의 타임아웃을 위해 약 30초를 대기하며, 이는 testnet의 모든 사용자에 대해 블록 생성을 느리게 합니다. 참여자가 다시 응답하면, 거부된 것으로 보였던 시도가 여전히 커밋될 수 있습니다. 전송을 다시 보내기 전에 Zenith EVM에서 잔액을 확인하거나, 귀하의 주소를
to로 하는 Gateway 컨트랙트의Minted또는Released이벤트를 확인하십시오. 이러한 전송에는 짧은executeBefore(몇 분)을 설정하십시오.pending_count <party>가 귀하의 각 당사자에 대해0를 출력할 때만 종료하십시오(pending_count은 계정 및 도구 설정의 헬퍼 함수 중 하나입니다).error:로 시작하는 줄은 읽기에 실패했음을 의미하며 처리 중인 전송에 대해서는 아무것도 알려주지 않습니다. -
경고: 워크플로 2/b의 TTA 상시 수락과 같이 상시 수락(전송 사전 승인)이 설정된 당사자는, 누군가가 해당 당사자에게 wTTA 또는 Canton 토큰을 반환하는 다른 Gateway ERC-20을 전송할 때마다 귀하의 참여자가 필요합니다. 당사자가 상시 수락에 서명했고 Gateway의 처리가 이를 읽기 때문에, 귀하의 참여자는 해당 처리를 확인해야 합니다. 참여자가 응답하지 않는 동안, 각 처리 시도는 중재자의 타임아웃을 위해 약 30초를 대기하며, 그 시간 동안 누구를 위해서도 블록이 생성되지 않습니다. 세 번의 시도 후 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의 단일 트랜잭션 경로(
GatewayService_CrossToEvm및WrapperFactory을 통한 단일 단계 반환)는 발신자 자신의 트랜잭션 내부에서 EVM 블록을 구성합니다. 이를 위해서는 네 가지가 필요합니다: 참여자에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이(가) 로 실패합니다`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단계). 새 파티는 필요하지 않습니다. 그런 다음 발신자에게 폐기를 확인하고 일반적인 문제의 첫 번째 행과 같이 동일한 논스로 재전송하도록 요청하십시오. |
| 발신자의 전송은 영수증을 받았지만 귀하의 파티에는 아무것도 적립되지 않았습니다 | 발신자가 불완전한 파티 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단계의 확인 전에 표시된 네 가지 변수를 설정한 후 확인을 다시 실행하십시오. |
제출 시 External calls not supported | 단일 트랜잭션 Gateway 경로를 사용했습니다 | 대신 표준 전송 방식으로 메일박스 파티에 전송하십시오(전송 방향 참조) |
Zenith Gateway 개요
Zenith Gateway는 Canton과 Zenith EVM 간에 양방향으로 토큰을 이동시킵니다. 각 전환은 원자적입니다. Gateway가 수신함에서 입금을 수집하거나 오퍼를 수락할 때, Canton에서의 변경과 Zenith EVM에서의 변경이 단일 Canton 트랜잭션으로 함께 커밋되거나 전혀 커밋되지 않습니다. 토큰은 양쪽에 동시에 존재할 수 없습니다. 해당 단계 이전에는 토큰이 수신함 또는 보류 중인 오퍼에서 대기할 수 있습니다(전송 방향 참조).
작동 방식
**인박스(inbox)**는 Canton 파티 ID에서 파생된 EVM 주소입니다. 등록된 ERC-20 토큰을 Canton으로 전송하려면, 수신자의 인박스로 이체하세요.
**메일박스 파티(mailbox party)**는 하나의 EVM 목적지 주소와 하나의 인스트루먼트에 연결된 Canton 파티입니다. 해당 인스트루먼트를 Canton에서 Zenith EVM으로 전송하려면, 메일박스 파티로 이체하세요. 메일박스는 사용 전에 먼저 생성되어야 합니다.
이 목적지들은 서로 다른 방향을 위해 사용됩니다. 메일박스 파티는 인박스 입금을 위한 Canton 수신자가 아닙니다. 중요: Zenith EVM에서 메일박스 파티의 인박스 주소(m-으로 시작하는 ID)나 Gateway 파티의 인박스 주소로 토큰을 전송하지 마세요. Gateway는 어느 쪽도 크레딧을 부여하지 않습니다. 이체는 채굴되지만, 토큰은 해당 주소에 남아 영구적으로 손실됩니다.
- Canton으로 토큰을 전송하려면, Zenith EVM에서 수신자 파티의 인박스 주소로 이체하세요. 이는 하나의 Canton 파티에 속하는 EVM 주소로, 누구든지 파티 ID로 조회할 수 있습니다. TKA와 같이 Zenith EVM에서 시작된 토큰의 경우, 수신자는 별도의 설정이 필요하지 않습니다. Canton 토큰의 Gateway ERC-20(예: Canton에서 TTA로 도착하는 wTTA)의 경우, 수신자는 먼저 해당 토큰의 레지스트리와 스탠딩 수락(이체 사전 승인)을 설정해야 합니다 (Workflow 2/b, step 1). 이 설정이 없으면 Gateway가 토큰을 전달할 수 없어 귀하의 EVM 트랜잭션이 거부됩니다.
- Zenith EVM으로 토큰을 전송하려면, Canton에서 귀하의 EVM 주소의 메일박스 파티로 표준 CIP-56 이체를 실행하세요. 이는 하나의 EVM 주소와 하나의 토큰을 나타내는 Canton 파티입니다. 누구든지 전송할 수 있는 EVM 트랜잭션 한 번으로 요청하면, Gateway가 한두 블록 내에 생성합니다. 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 트랜잭션 하나를 제출하는데, 이 트랜잭션은 먼저 Canton의 수신자에게 크레딧을 부여한 후, 귀하의 이체와 인박스 수금을 포함하는 새 EVM 블록을 생성합니다. 승인은 Gateway 이벤트가 크레딧과 정확히 일치할 때만 블록을 수락합니다. 크레딧과 새 EVM 블록은 함께 커밋되거나, 아예 커밋되지 않습니다.

- 참고: Canton은 인덱싱된 인박스에 등록 토큰의 미수금 입금이 포함된 블록 제안으로부터 일반 블록 생성을 거부합니다. 따라서 수금 및 크레딧과 함께 Gateway가 생성하는 블록만이 귀하의 이체를 포함할 수 있는 유일한 블록입니다.
이체 방향
| 이동 대상 | 수행 작업 | 가이드 |
|---|---|---|
| Canton으로의 ERC-20 토큰 | ERC-20 transfer으로 수신자의 인박스 주소로 전송 | Workflow 1/a |
| Gateway의 Canton 토큰을 Zenith EVM으로 전송 | CIP-56 전송으로 수신함 당사자에게 전송 | 워크플로 1/b |
| Canton (CIP-56) 토큰을 Zenith EVM으로 전송 | CIP-56 전송으로 수신함 당사자에게 전송 | 워크플로 2/a |
| Gateway의 ERC-20을 Canton으로 전송 | 수신자가 원본 토큰에 대한 상시 수락을 보유하고 있는지 확인한 후, ERC-20 transfer으로 수신자의 수신함 주소에 ERC-20을 전송 | 워크플로 2/b |
Canton으로의 전송: Gateway가 색인한 당사자의 수신함으로 등록된 토큰을 전송하면 EVM 트랜잭션과 동일한 블록에서 크레딧됩니다. Gateway가 해당 입금을 처리할 수 없는 경우(예: 상시 수락이 없는 당사자에게 wTTA를 전송하는 경우), 트랜잭션이 포함되기 전에 드롭되며 아무것도 이동하지 않습니다. 아직 등록되지 않은 토큰 또는 Gateway가 아직 색인하지 않은 당사자에게 전송하는 경우, 일반 블록에 채굴되며 토큰은 수신함에서 대기합니다. Gateway는 당사자를 색인하거나 토큰이 등록된 후 약 1분 이내에 크레딧합니다. wTTA는 예외입니다: 상시 수락이 없는 수신자는 크레딧될 수 없으며, 대기 중인 모든 수신함에 대한 Gateway의 크레딧 처리가 실패합니다(워크플로 2/b 참조). 수신함 당사자 또는 Gateway 당사자의 수신함으로 전송된 토큰은 크레딧되지 않으며 손실됩니다.
Zenith EVM으로의 전송은 일반적으로 오퍼라고 불리는 대기 중 전송으로 시작됩니다. 토큰의 레지스트리가 수신함 당사자에게 대기 중 전송 지시를 남기며, 해당 지시는 Gateway가 수락할 때까지 그곳에서 대기합니다. Gateway 자체 토큰과 testnet의 TTA는 항상 이 방식으로 작동합니다. 토큰의 레지스트리가 즉시 전송을 완료하는 경우, 오퍼가 생성되지 않습니다. 토큰이 수신함 당사자에게 바로 전달되며, Gateway는 동일한 종류의 트랜잭션으로 이를 정산합니다. 이러한 전송은 철회할 수 없으며, Gateway가 정산할 수 없는 경우 토큰은 수신함 당사자에게 남아 있습니다.
- Gateway가 오퍼를 수락하면, 동일한 Canton 트랜잭션에서 토큰을 이동하고 귀하의 EVM 주소로 민팅하거나 해제합니다. 원자적 단계는 오퍼를 전송할 때가 아니라 여기서 발생합니다.
- Gateway가 오퍼를 수락하기 전까지,
TransferInstruction_Withdraw으로 오퍼를 철회하고 토큰을 돌려받을 수 있습니다. Gateway는executeBefore이전 언제든지 수락할 수 있으며, 보통 약 한 블록 이내에 처리되므로 철회는 먼저 확정된 경우에만 가능합니다.executeBefore이후에는 Gateway가 더 이상 오퍼를 수락할 수 없으며, 귀하가 철회할 때까지 대기 상태로 유지됩니다. - Gateway가 오퍼를 절대 정산할 수 없거나(토큰이 등록되지 않았거나 수신함 당사자가 다른 토큰용인 경우), 정산이 연속 3회 거부된 경우, Gateway는 오퍼를 거부하고 토큰의 레지스트리가 금액을 반환합니다. TTA와 Gateway의 자체 Canton 토큰은 전체 금액을 새로운 보유분으로 반환하며, 다른 레지스트리는 자체 규칙에 따라 반환합니다. 거부 처리를 위해서는 귀하의 참가자가 온라인 상태여야 합니다. Gateway는
executeBefore이후에는 오퍼를 거부하지 않습니다. 만료된 오퍼는 직접 철회하십시오. - 일부 오류는 제출 시 거부되며 보유분에서 아무것도 떠나지 않습니다: 존재하지 않는 수신함 당사자(
UNKNOWN_INFORMEES, 또는 Gateway 토큰의 경우No mailbox for this party is visible to you…), 다른 토큰의 수신함 당사자로 전송된 Gateway 발행 토큰(Mailbox belongs to another instrument), 그리고 0 금액(Nonpositive amount). - 토큰이 등록된 후에만 수신함 당사자에게 토큰을 전송하십시오.
이번 릴리스에 포함된 내용
Zenith EVM Testnet의 Gateway 릴리스는 토큰 전송만 지원합니다.
- 포함됨: 등록된 토큰의 양방향 전송. 여기에는 일반 ERC-20 토큰(이전 수수료 없음, 리베이싱 없음)과 토큰 표준의 레지스트리 API를 제공하는 레지스트리를 가진 CIP-56 토큰이 포함됩니다. 모든 EVM 지갑 또는 컨트랙트에서 전송할 수 있습니다. Canton에서는 Zenith testnet 동기화기에 연결된 참가자에서 호스팅된 당사자가 표준 CIP-56 전송을 수행하는 모든 지갑 또는 애플리케이션을 통해 전송할 수 있습니다. 외부 당사자는 테스트되지 않았습니다. Gateway 수수료는 없으며, 서명하는 EVM 트랜잭션에 대해 ZTH로 가스비만 지불합니다.
- 미포함: Daml과 EVM 컨트랙트 간의 호출, 네이티브 ZTH 또는 Canton Coin 전송, 셀프서비스 토큰 등록. 수신함 주소로 네이티브 ZTH를 전송하지 마십시오. Gateway는 등록된 ERC-20 토큰만 수집하며, 수신함에서 ZTH를 이동시킬 수 있는 방법은 없습니다.
testnet의 Gateway는 Zenith가 운영합니다. 실제 가치가 있는 것은 전송하지 마십시오.
자체 토큰 등록
Zenith에 토큰을 Gateway에 등록해 달라고 요청할 수 있습니다. 등록은 Zenith만 수행할 수 있으며, 토큰 등록 여부는 Zenith가 결정합니다. CIP-56 토큰의 경우 이는 신뢰 결정입니다. Gateway는 Gateway 당사자의 권한으로 귀하 토큰의 Daml 코드를 실행하며, 하나의 Gateway 당사자가 등록된 모든 Canton 토큰의 담보를 보유합니다. 다음 내용을 포함하여 [email protected]으로 요청을 전송하십시오.
| 귀하의 토큰 | 제출 내용 |
|---|---|
| Zenith EVM의 ERC-20 | 토큰의 주소. 이전 수수료 및 리베이싱이 없는 일반 ERC-20이어야 합니다. |
| Canton의 CIP-56 토큰 | 레지스트리 API URL; 토큰의 Daml 패키지 DAR 파일(보유, 전송 팩토리 및 전송 지시 템플릿, 해당 종속성 포함); Gateway가 배포할 ERC-20에 사용할 이름 및 심볼; 그리고 Zenith가 DARs 업로드를 확인한 후(그 전까지는 전송이 거부됨) Gateway 당사자에게 전송된 토큰 1단위. Gateway는 해당 단위를 영구적으로 보유하며, 이를 통해 토큰 보유를 서명하는 당사자를 확인합니다. 토큰 패키지의 새 버전도 동일한 방식으로 업로드해야 하므로, 업그레이드 전에 Zenith에 알려주십시오. |
CIP-56 토큰의 경우, 레지스트리는 토큰 표준의 transfer-factory 및 choice-contexts/accept 엔드포인트를 제공해야 하며 항상 접근 가능한 상태를 유지해야 합니다. 등록은 영구적입니다.
토큰이 등록되면 Gateway 컨트랙트가 이를 보고합니다. 테스트 토큰에서 설정한 변수를 사용하여: ERC-20의 경우 cast call $GATEWAY 'kind(address)(uint256)' <token address> --rpc-url $RPC은 2을 반환하고(워크플로 1/a, 1단계 참조), CIP-56 토큰의 경우 cast call $GATEWAY 'wrapperOf(bytes32)(address)' $(cast keccak "cip56:<admin's full party ID>:<id>") --rpc-url $RPC는 Gateway가 해당 토큰을 위해 배포한 ERC-20인 0이 아닌 주소를 반환합니다. 토큰이 등록되기 전에는 인박스 주소나 메일박스 파티에 토큰을 전송하지 마십시오.
Gateway와 상호작용하기
Gateway 네트워크 정보
| Value | |
|---|---|
| Zenith EVM RPC | https://rpc.testnet.zenith.network |
| Chain ID | 936485 |
| Block time | 5 seconds |
| Explorer | https://explorer.testnet.zenith.network/ |
| Gateway contract | 0x663f3ad617193148711d28f5334ee4ed07016602 |
| MailboxRequests contract | 0x2e983a1ba5e8b38aaaec4b440b9ddcfbf72e15d1 |
| Gateway party | gateway::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8 |
| Operator namespace | 12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8 |
| Gateway registry API | https://gateway.testnet.zenith.network |
테스트 토큰
| Token | Value | What it is |
|---|---|---|
| TKA | 0x8464135c8f25da09e49bc8782676a84730c318bc | an ERC-20 test token on Zenith EVM (18 decimals) |
| TKA drip | 0x71c95911e9a5d330f4d621842ec243ee1343292e | contract that gives any caller 10 TKA, once a minute per address |
| TTA | admin issuer::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8, ID TTA | a CIP-56 test token on Canton (10 decimals) |
| wTTA | 0x12a62529c83cfcdd6650b4cce477d98cc9945d66 | TTA’s ERC-20 counterpart on Zenith EVM (10 decimals) |
| Test token registry API | https://gateway-partner.testnet.zenith.network | TTA’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.networkexport GATEWAY=0x663f3ad617193148711d28f5334ee4ed07016602export REQUESTS=0x2e983a1ba5e8b38aaaec4b440b9ddcfbf72e15d1export GATEWAY_PARTY=gateway::12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8export NAMESPACE=12205db909e5f8c8d6b0c8033d4b52439195b73d8235c374fb653b280ca78272beb8export REGISTRY=https://gateway.testnet.zenith.networkexport TKA=0x8464135c8f25da09e49bc8782676a84730c318bcexport DRIP=0x71c95911e9a5d330f4d621842ec243ee1343292eexport wTTA=0x12a62529c83cfcdd6650b4cce477d98cc9945d66export TREGISTRY=https://gateway-partner.testnet.zenith.networkexport TKA_ID="erc20:$(printf %s "${TKA#0x}" | tr 'A-F' 'a-f')"Gateway 네트워크 정보에서 두 가지 값을 네트워크와 대조하여 확인할 수 있습니다. 첫 번째 명령은 체인 ID인 936485을 출력하고, 두 번째 명령은 Gateway 파티를 출력합니다:
cast chain-id --rpc-url $RPCcurl -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자리)입니다. - Canton에서는 소수 금액을 입력합니다:
4.0TTA. Canton 금액은 소수점 이하 최대 10자리까지 허용됩니다. 소수점 10번째 자리 이후에 0이 아닌 숫자가 있을 경우, 참여자는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를 받은 후, 드립에서 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드립 호출은 faucet ZTH가 도착한 후에만 작동합니다.
개인 키는 이 쉘의 KEY 변수에만 존재하며, 모든 워크플로는 이 키로 서명합니다. 이 주소를 나중에 사용하려면 echo "$KEY"으로 키를 출력하여 저장하십시오. 다른 곳에는 저장되지 않습니다.
Canton 파티 (4개 워크플로 모두 해당): Canton에서 Zenith EVM과 상호작용하기에 설명된 대로 참여자를 연결하고 파티를 생성한 후 설정하십시오:
export LEDGER=http://127.0.0.1:7575export USER_ID=my-appexport 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>은 대기 중인 전송 수를 출력합니다. 읽기에 실패하면 세 함수 모두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>은 저장된 요청을 그대로 전송하고 세 가지 결과 중 하나를 출력합니다: 전송의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로부터 메일박스 파티 ID를 계산합니다.
제출 재시도: error: outcome unknown은 명령이 커밋되었는지 알 수 없음을 의미합니다. 원장이 시간 초과되었다는 code도 동일하게 처리하십시오. send_saved <file>로 동일한 저장된 요청을 다시 전송하십시오. error: outcome unknown 이후에는 오류가 출력한 줄을 복사하십시오. 시간 초과 code 이후에는 앞서 request saved in <file> 줄에 있는 파일을 사용하십시오. 해당 파일은 동일한 명령 ID와 동일한 입력 보유량을 포함하므로, 원장은 이를 최대 한 번만 적용합니다. 첫 번째 시도가 이미 커밋된 경우, 재전송은 CONTRACT_NOT_FOUND 또는 DUPLICATE_COMMAND로 거부됩니다. 이후 holdings을 실행하여 어떤 일이 발생했는지 확인하십시오. 재시도 시 transfer_args로 요청을 다시 구성하지 마십시오: 그렇게 하면 다른 보유량이 선택되고, 새 만료 시간이 설정되며, 새 명령 ID가 사용되어 두 번째 전송이 이루어집니다.
Gateway 샘플 워크플로
워크플로 1/a: ERC-20 토큰을 Canton으로 전송
EVM 주소에서 Canton 파티 P1으로 TKA를 전송합니다. 수신자는 서명이 필요 없으며 사전 수락(전송 사전 승인)도 필요하지 않습니다.
1. 토큰이 등록되어 있는지 확인하세요: 결과값이 2이면 TKA는 등록된 ERC-20 토큰입니다. 0이면 등록되지 않은 것이므로 중단하세요.
cast call $GATEWAY 'kind(address)(uint256)' $TKA --rpc-url $RPC2. 수신자의 인박스 주소를 조회하세요: 수신자의 전체 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 $RPC4. 결과를 확인하세요: 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에 문의하세요.
Workflow 1/b: Zenith EVM으로 다시 전송하기
P1의 Canton 토큰 1.0 TKA 상당을 TKA로서 자신의 EVM 주소로 다시 전송하세요.
1. TKA에 대한 메일박스 파티를 요청하세요: Gateway는 요청을 확인하면 Zenith의 참여자 노드에 파티를 생성합니다. 이 작업은 EVM 주소와 토큰별로 한 번씩 필요합니다:
export MBOX_TKA=$(mailbox_party $ME "$TKA_ID")cast send $REQUESTS 'requestMailbox(address,address)' $ME $TKA --private-key $KEY --rpc-url $RPC한두 블록 내에 생성됩니다. 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. 결과를 확인하세요: 한 블록 내외로 TKA 잔액이 1000000000000000000만큼 증가하고, pending_count "$P1"은 0를 출력합니다. 잔액이 확인의 증거입니다.
pending_count "$P1"cast call $TKA 'balanceOf(address)(uint256)' $ME --rpc-url $RPC오퍼가 만료된 후에도 여전히 처리 중인 경우, 오퍼를 철회하고 Zenith에 알리세요. Gateway는 일반적으로 한 블록 내외로 오퍼를 정산하기 때문입니다. 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:라인은 읽기에 실패했음을 의미하며 처리 중인 전송에 대해서는 아무것도 알려주지 않습니다.
Workflow 2/a: Canton 토큰을 Zenith EVM으로 전송하기
P1에서 자신의 EVM 주소로 TTA를 전송하면 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개를 발행하세요. 파우셋은 호출당 최대 TTA 100개를 제공하며(파우셋의 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가 한두 블록 내에 Zenith의 참여자 노드에 파티를 생성합니다. 파티 ID는 귀하의 EVM 주소와 TTA의 전체 식별자인 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 $RPCfor 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. TTA 4.0을 전송하세요: 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를 기다리고 있습니다. 오퍼가 정산될 때까지 참여자 노드를 계속 실행하세요. 만료 후에도 여전히 처리 중이라면, Workflow 1/b의 명령을 사용하여 오퍼를 철회하되, $MBOX_TKA 대신 $MBOX_TTA를 사용하세요.
4. 결과를 확인하세요: 한 블록 내외로 wTTA 잔액이 40000000000이 되며, 이는 소수점 10자리 기준 4.0입니다:
cast call $wTTA 'balanceOf(address)(uint256)' $ME --rpc-url $RPCWorkflow 2/b: Canton으로 다시 전송하기
2.0 wTTA를 P1으로 되돌려 보내면, TTA로 도착합니다.
wTTA는 Gateway가 이미 인덱싱했고 TTA에 대한 상시 수락(이체 사전승인)이 설정된 당사자에게만 보내십시오. P1은 Workflow 1/a 및 1단계. 이후 두 조건을 모두 충족합니다. 아직 인덱싱되지 않은 당사자에게 wTTA를 이체하면 일반 이체로 채굴되어 받은편지함에서 대기합니다. 해당 당사자에게 상시 수락이 없으면 Gateway가 크레딧을 처리할 수 없습니다. Gateway는 대기 중인 모든 받은편지함 잔액을 하나의 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"보내기 전에 확인하십시오. TTA 레지스트리에 Gateway 당사자에서 P1으로의 이체를 문의하십시오. 응답은 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 $RPC3. 결과 확인: 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 토큰 이체로, 지갑과 동일한 방식으로 수행됩니다:
-
위의
mailbox_party을 사용하여 목적지 EVM 주소와 전송할 토큰의 mailbox 당사자를 계산합니다. instrument 텍스트는 다음과 같습니다:- Gateway의 ERC-20 오리지널 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는 한두 블록 내에 당사자를 생성하며, 그렇지 않으면 요청을 무시합니다. 당사자를 파악한 후 전송하십시오. - Gateway의 ERC-20 오리지널 Canton 토큰의 경우:
-
약간 과거 시점의
requestedAt(원장은 원장 시간보다 늦은 시점을 거부함)과 예를 들어 몇 분 앞의 짧은executeBefore으로 이체를 구성하십시오. 토큰 레지스트리의POST /registry/transfer-instruction/v1/transfer-factory에choiceArguments로 전송하십시오:- Gateway 발행 토큰의 경우 Gateway 레지스트리를 호출하고,
- 다른 토큰의 경우 해당 토큰의 자체 레지스트리를 호출합니다.
-
반환된
factoryId에 대해 전송한 이체와 함께TransferFactory_Transfer을 실행합니다. 반환된choiceContextData를extraArgs.context.values으로 전달하고, 반환된disclosedContracts를 커맨드의disclosedContracts에 추가합니다. -
이체가 대기 중인 이체 명령을 남기면 Gateway가 이를 처리합니다. 명령이 처리, 거부 또는 철회될 때까지 참여자를 실행 및 연결 상태로 유지하십시오(참여자를 온라인 상태로 유지 참조).
executeBefore이후에도 여전히 대기 중이면TransferInstruction_Withdraw으로 철회하십시오. Gateway가 아닌 토큰의 경우, 먼저 해당 토큰의 레지스트리에 철회 컨텍스트(POST <registry>/registry/transfer-instruction/v1/<instruction-id>/choice-contexts/withdraw)를 요청하고 3단계\와 같이 전달하십시오. Gateway와 TTA 레지스트리는 빈 컨텍스트를 반환합니다.
위의 submit 헬퍼는 정확한 JSON Ledger API 커맨드를 보여줍니다.
- 참고: Gateway 패키지에는 단일 트랜잭션 경로(
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 크레딧을 볼 수 없으며, 영수증만으로는 크레딧을 증명할 수 없습니다. Gateway는 등록된 토큰의 0이 아닌 금액이 Gateway가 인덱싱한 파티의 인박스로 전송된 경우에만 해당 전송을 입금으로 처리합니다. 이러한 전송은 크레딧이 함께 이루어지는 블록에서만 채굴될 수 있으므로, 해당 전송에 대한 영수증은 크레딧이 완료되었음을 의미합니다. Gateway가 입금을 처리하지 못할 경우, 해당 트랜잭션을 블록에서 제외합니다: 트랜잭션은 채굴되지 않고, 영수증도 받지 못하며, 토큰과 가스도 소비되지 않습니다. 약 세 블록 후 시퀀서는 해당 트랜잭션과 동일한 주소에서 이후에 전송된 모든 트랜잭션을 폐기합니다. 시간 제한 없이 영수증을 기다리지 마십시오; ethers v6에서는 타임아웃을 지정하십시오, 예를 들어 tx.wait(1, 120_000). 드롭을 확인하고 재전송하려면 일반적인 문제의 첫 번째 행을 참조하십시오. 인박스 주소로의 다른 모든 전송은 크레딧 없이 일반 블록에서 채굴됩니다(전송 방향 참조). 전송 전에 kind(token)을 확인하고 수신자의 전체 파티 ID를 사용하십시오.
Gateway 문제 해결
전송 전 확인 사항
- 토큰이 등록되어 있는지 확인하십시오 — 어디에든 전송하기 전에. ERC-20의 경우,
kind(<token address>)이 0이 아닌 값을 반환합니다. Canton 토큰의 경우,cast keccak "cip56:<admin>:<id>"의wrapperOf이 0이 아닌 주소를 반환합니다. - 전체 파티 ID를 사용하십시오 (
hint::namespace): 단축된 ID는 어떤 파티도 가질 수 없는 ID의 인박스 주소를 반환합니다. Gateway는 그곳으로 전송된 토큰을 크레딧하지 않으며, 토큰은 손실됩니다. - 메일박스 파티의 인박스, Gateway 파티의 인박스, 또는 체인 파티의 인박스로 토큰을 전송하지 마십시오 (
m-로 시작하는 ID), Gateway 파티 또는 체인 파티 (블록에 서명하는 Zenith 파티,<to be added>): Gateway는 이들을 크레딧하지 않으며, 토큰은 손실됩니다. - 전송하려는 정확한 토큰에 해당하는 메일박스 파티를 사용하십시오.
- 토큰의 소수점 자리수를 확인하십시오: Gateway에서 발행한 토큰은 소수점 10자리입니다.
일반적인 문제
| 증상 | 원인 | 해결 방법 |
|---|---|---|
| EVM 전송에 영수증이 수신되지 않고, 이후 트랜잭션도 처리되지 않음 | Gateway가 입금을 처리하지 못하고 해당 트랜잭션을 블록에서 제외했을 가능성이 높습니다. 약 세 블록 후 시퀀서는 해당 트랜잭션과 해당 주소에서 이후에 전송된 트랜잭션들을 폐기합니다. 아무것도 이동하지 않았습니다. | 영수증 미수신만으로는 드롭을 증명할 수 없습니다. 다음 네 가지를 모두 확인하십시오: cast rpc eth_getTransactionByHash <hash> --rpc-url $RPC이 null을 출력함; cast nonce $ME --rpc-url $RPC가 여전히 해당 트랜잭션의 논스를 표시함; 변경되어야 할 잔액이 변경되지 않음; cast block-number --rpc-url $RPC이 두 번의 읽기 사이에 증가함. 조회 결과 트랜잭션이 여전히 반환되면 대기 중인 것이므로 재전송하지 마십시오. 블록 번호가 증가하지 않으면 블록 생성이 중단된 것이므로 기다리고, 지속될 경우 Zenith에 문의하십시오. 네 가지 조건이 모두 충족되면 원인을 수정하십시오(보통 다음 행 참조). 그런 다음 동일한 논스(cast send ... --nonce <nonce>)로 전송을 다시 보내고, 이후 각 트랜잭션을 논스 순서대로 전송하십시오. |
| wTTA(또는 다른 Gateway ERC-20)를 Canton으로 전송했으나 영수증이 없음 | 수신자가 해당 토큰에 대한 상시 수락(전송 사전 승인)이 없음 | 수신자가 해당 토큰의 자체 레지스트리로 상시 수락을 생성하도록 하십시오. TTA의 경우, 수신자는 워크플로우 2/b, 1단계를 실행합니다. 그런 다음 위 행과 같이 드롭을 확인하고 동일한 논스로 재전송하십시오. |
| 영수증은 있으나 토큰이 아직 인박스에 있음 | 수신자 파티가 완전히 새로 생성되었거나, 토큰이 아직 등록되지 않음 | 약 1분 기다리십시오. 등록되지 않은 토큰은 등록 후 크레딧됩니다. wTTA(또는 다른 Gateway ERC-20)는 수신자에게 상시 수락이 있을 때만 크레딧됩니다(워크플로우 2/b, 1단계). 메일박스 파티, Gateway 파티 또는 불완전한 파티 ID의 인박스로 전송된 토큰은 도착하지 않습니다. 2분 이내에 아무것도 도착하지 않으면 Zenith에 문의하십시오. |
| 여러 블록이 지난 후에도 오퍼가 대기 중 | Gateway는 한 번에 하나의 오퍼를 처리하며, Zenith EVM의 입금을 먼저 처리합니다. 또한 참여자가 중지되거나 연결이 끊어진 경우, 또는 Gateway가 중지된 경우에도 오퍼는 대기 상태를 유지합니다. | 참여자를 계속 실행하고 연결된 상태로 유지하십시오. 중지되었거나 연결이 끊어진 경우 즉시 재연결하십시오; 그러면 처리가 완료될 수 있습니다. 그렇지 않으면 몇 블록 더 기다리십시오. executeBefore 이후에도 오퍼가 대기 중이면 취소하고 Zenith에 문의하십시오. |
| 오퍼가 사라지고 전체 금액이 보유량으로 돌아옴 | Gateway가 거부함: 메일박스 파티가 다른 토큰용이거나, 토큰이 등록되지 않았거나, 처리가 연속 3회 거부됨 | 처음 두 가지 원인 중 하나에 해당하면 수정 후 다시 전송하십시오. 그 외의 경우, 오퍼의 Canton 업데이트 ID와 UTC 기준 시간을 포함하여 Zenith에 문의하십시오. |
No mailbox for this party is visible to you… | 메일박스 파티가 아직 존재하지 않거나, 레지스트리가 반환한 컨트랙트를 공개하지 않음 | 메일박스 파티를 요청한 후, 레지스트리의 공개된 컨트랙트와 함께 제출하십시오. |
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 supported | 단일 트랜잭션 경로를 사용하셨습니다. | 메일박스 당사자로의 표준 전송을 사용하십시오. |
drip() 호출이 cooldown과 함께 실패합니다. | 지난 1분 이내에 이미 드립을 실행하셨습니다. | 1분 기다리십시오. |
drip() 호출이 empty과 함께 실패합니다. | 드립에 TKA가 10개 미만으로 남아 있습니다. 기다려도 도움이 되지 않습니다. | 충전을 위해 Zenith에 문의하십시오. |
설명되지 않은 전송이 있는 경우, [email protected]으로 Zenith에 문의하십시오. EVM 트랜잭션 해시, 보유하고 있는 경우 Canton 업데이트 ID, 그리고 UTC 기준 시간을 포함해 주십시오.
개념 및 아키텍처
이 섹션은 Zenith EVM에 대한 기술적 개요, 작동 방식, Canton과의 관계, 그리고 Canton을 통해 중재된 블록 생성으로 EVM 실행이 어떻게 조정되는지를 설명합니다.
Zenith EVM이란?
Zenith EVM은 고성능 모듈식 Ethereum 실행 클라이언트인 Reth를 기반으로 구축된 참조 EVM 실행 환경입니다. 표준 Ethereum JSON-RPC 인터페이스를 제공하므로, 개발자는 Ethereum 호환 체인과 동일한 방식으로 Zenith EVM과 상호작용할 수 있습니다.
개발자 관점에서:
- 스마트 컨트랙트 배포: Solidity, Foundry, Hardhat 또는 모든 EVM 툴체인을 사용할 수 있으며, 별도의 수정이 필요하지 않습니다.
- 지갑 연결: MetaMask와 같은 지갑에서 Zenith EVM을 커스텀 네트워크로 추가하여 연결할 수 있습니다.
- 기존 라이브러리 사용: ethers.js, viem, web3.js, OpenZeppelin 등 모든 라이브러리가 별도 설정 없이 바로 작동합니다.
Zenith EVM이 독립형 EVM 체인과 다른 점은 Canton과의 관계에 있습니다. 모든 EVM 활동은 Canton을 통해 라우팅되며 Canton MainNet에서 정산됩니다. 이를 통해 Zenith EVM은 전체 Ethereum 개발자 경험을 유지하면서 Canton의 규제 및 컴플라이언스 보장을 제공합니다.
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은 결정론적 최종성과 Canton 네이티브 원자적 구성 가능성을 Canton에 의존하면서도 일반적인 Ethereum 툴링 및 실행 시맨틱을 유지할 수 있습니다.
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의 네이티브 2단계 검증 프로세스를 따릅니다:
- 제출 단계: 제출 참여자는 해석 과정에서
external_call()을 실행하고, 그 결과, 해시 및 메타데이터를 트랜잭션 뷰에 포함시킵니다. - 검증 단계: 검증자는 자신의 EVM 노드에 대해 동일한 작업을 독립적으로 로컬에서 재실행합니다.
- 합의 및 최종성: 검증자의 임계값이 출력에 동의하면 트랜잭션이 확인됩니다. 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 결합 워크플로가 전체적으로 커밋되거나, 전체적으로 실패합니다.
- 최종성은 결정론적입니다: Canton 트랜잭션이 확인되면 EVM 상태 전환은 최종적입니다.
- Canton 최종성 이후에는 EVM 재편성이 없습니다: 정규 EVM 상태는 최종화된 Canton 중재 블록 시퀀스를 따릅니다.
현재 testnet에서 Canton 네이티브 트랜잭션 내 추가 EVM 작업의 일반적인 엔드투엔드 지연 시간은 400ms에서 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의 가스 회계에는 Canton 트랜잭션 수수료를 포함하는 추가 요소가 포함될 예정입니다.
What's different?
| 영역 | Ethereum mainnet | Zenith EVM |
|---|---|---|
| 합의 | Ethereum 지분 증명 합의. | Canton 중재 검증 및 완결성. |
| 완결성 | 확률적 (완결성까지 2 에포크 / ~13분) | 결정론적, 수 초 이내. 리오그 없음. |
| 리오그 | 완결성 이전에는 가능. | Canton 완결성 이후에는 EVM 리오그 없음. |
| 비콘 체인 | Ethereum은 합의 및 검증자 선택 무작위성을 위해 비콘 체인을 사용합니다. | Zenith EVM은 Ethereum 비콘 체인을 사용하지 않습니다. |
PREVRANDAO | Ethereum 합의 컨텍스트에서 채워집니다. | Ethereum 비콘 체인의 무작위성이 아닌, Zenith의 Canton 중재 블록 빌딩 흐름에 의해 블록 빌딩 환경 속성(Prev RANDAO)으로 제공됩니다. 컨트랙트는 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을 통해 완료되면, 애플리케이션은 일반적으로 확률적 최종성 체인에서처럼 여러 블록 확인을 기다릴 필요가 없습니다.
- 이중 트랜잭션 식별: EVM 트랜잭션은 해당 트랜잭션이 포함된 EVM 블록의 Canton 업데이트 ID와 함께 추적할 수 있습니다.
- Canton 기본 워크플로우는 EVM 실행을 조율할 수 있습니다: 모든 Zenith EVM 트랜잭션 배치는 원자적 Canton 중재 블록 구축 경로를 통해 처리됩니다.
- 원자적 워크플로우를 위한 영수증 확인: EVM 트랜잭션이 거부되면 해당 영수증이 존재하지 않으며, Canton 측 워크플로우는 이를 EVM 단계 실패로 처리해야 합니다.
- Ethereum Beacon Chain 무작위성 시맨틱에 의존하지 마십시오: block.prevrandao를 사용하는 컨트랙트는 Zenith EVM이 이 값을 Ethereum Beacon Chain 검증자 선택 무작위성으로부터 가져오지 않는다는 점을 고려해야 합니다.
상호운용성
Zenith EVM은 두 가지 상호 보완적인 상호운용성 모델을 지원합니다:
- Gateway, Canton 기본 워크플로우와 Zenith EVM 간의 Zenith 원자적 상호운용성 경로; 그리고
- 외부 생태계 상호운용성, Zenith EVM을 외부 체인 및 메시징 생태계와 연결합니다.
Zenith Gateway
Gateway는 Zenith의 기본 상호운용성 프로토콜로, Canton의 자산 및 애플리케이션을 Zenith EVM과 연결합니다. 양방향 자산 이전 및 컨트랙트 호출을 지원하도록 설계되어, 애플리케이션이 단일 트랜잭션에서 Canton과 EVM 작업을 결합할 수 있습니다.
예를 들어, Canton 워크플로우는 적격 자산을 Zenith EVM으로 이동하고, 애플리케이션에서 이를 사용한 후, 하나의 조율된 작업으로 Canton에 가치를 반환할 수 있습니다.
Gateway와 외부 브리지 비교
Gateway는 Zenith의 핵심 프로토콜의 일부이며 Canton으로부터 신뢰 모델을 상속받습니다. 따라서 제3자 브리지나 크로스체인 메시징 프로토콜과 구별되어야 합니다.
| Property | Gateway | 서드파티 상호운용 / 메시징 프로토콜 |
|---|---|---|
| 주요 범위 | Canton ↔ Zenith EVM | Zenith EVM ↔ 외부 생태계 |
| 실행 모델 | Canton 중재 블록 생성 | 크로스체인 메시지 전달 또는 토큰 전송 |
| 최종성 모델 | 결합된 워크플로에 대한 Canton의 신뢰 및 최종성 모델 | 소스 체인, 대상 체인, 그리고 프로토콜 가정에 따라 다름 |
| 실패 동작 | 단일 원자적 워크플로에 대한 부분적인 Canton/EVM 커밋 없음 | 일반적으로 비동기적이며, 실패 및 재시도 동작은 프로토콜별로 다름 |
| 최적 사용 사례 | 결합된 워크플로에서의 Canton 네이티브 및 EVM 네이티브 자산, Zenith EVM 컨트랙트와 상호작용하는 Daml 워크플로 | Ethereum, Base, Solana 및 기타 외부 생태계와의 상호운용성 |
원자적 결제
Gateway를 통한 Canton과 Zenith EVM 간의 자산 전송을 **크로싱(crossing)**이라고 합니다. 각 크로싱은 원자적입니다. 즉, 송신 측의 차감, 수신 측의 수령, 그리고 이에 대응하는 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는 단일 Head—프로토콜의 최신 정산된 EVM 블록 기록—를 중심으로 Daml과 EVM 실행을 결합합니다. 새로운 블록은 각각 해당 기록을 확장합니다:
- 사용자가 Canton 명령 또는 서명된 EVM 트랜잭션으로 작업을 승인합니다.
- 블록 생성자는 다음 블록을 준비하면서 Daml 워크플로우와 EVM 호출을 함께 평가합니다. 각 호출은 호출 워크플로우가 아직 실행 중인 동안 결과를 반환합니다.
- 생성자는 일반적인 Ethereum 블록과 그에 대응하는 Canton 변경 사항을 빌드합니다. 실패하거나 이후 취소된 호출의 변경 사항은 양쪽 모두에서 폐기됩니다.
- 독립적인 검증자들이 Daml 및 EVM 작업을 재실행합니다. 이들은 사용자의 승인, 실제 자산 이동, 호출 결과, 그리고 새 블록이 최신 정산 블록을 확장하는지를 검증합니다.
- 필요한 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을 통해 중재되기 때문에 Canton ↔ Zenith EVM 원자적 구성 가능성을 위해 설계되었습니다. Ethereum, Base, Solana 또는 기타 L1/L2 네트워크와 같은 생태계와의 외부 상호운용성은 독립적인 네트워크 간 크로스체인 메시징, 브리징, 또는 토큰 이전이라는 다른 모델을 따릅니다.
이러한 통합은 CCIP 또는 LayerZero와 같은 확립된 크로스체인 메시징 또는 브리지 프로토콜을 사용할 수 있습니다. 이는 Gateway의 Canton 중재 원자성이 아닌, 선택된 외부 프로토콜의 신뢰, 최종성, 재시도 및 실패 가정을 따릅니다.
현재 상태: Gateway는 원자적 워크플로우를 위한 기본 Canton ↔ Zenith EVM 상호운용성 메커니즘입니다. 외부 생태계 통합은 별도의 로드맵 항목입니다.