Orchestration Key Concepts and APIs
This document provides an overview of the fundamental concepts involved in building Orchestration smart contracts, focusing on Orchestrator Interface, Orchestration Accounts, and ChainHub. The methods described here are called from inside orchestration flows; see Async Flow for how those flows run, and how they survive a contract upgrade by deterministic replay.
Orchestrator Interface
The Orchestrator interface provides a set of high-level methods to manage and interact with local and remote chains. Below are the primary methods:
Access Chain Object
getChainretrieves a chain object for the givenchainNameto get access to chain-specific methods. See getChain.
const chain = await orchestrator.getChain('chainName');Denom Utility Functions
getDenomInforeturns information about adenom: the equivalent localBrand(if any), theChainwhere the denom is currently held, and theChainthat issues the underlying asset. It is synchronous and returns theDenomInforecord directly (not a promise). It requires thatgetChainhas already been called (and resolved) for both the holding chain and the issuing chain earlier in the flow; otherwise it throws. See getDenomInfo.
// getChain must be awaited first so the Orchestrator has resolved the chains
// that back this denom.
await orch.getChain(srcChainName);
await orch.getChain(issuingChainName);
const denomInfo = orch.getDenomInfo(denom, srcChainName);
// => { brand, chain, base, baseDenom }asAmountis declared to convert aDenomAmountto a local, structuredAmount, but as of this writing it is not yet implemented in@agoric/orchestration(it throwsnot yet implemented). Do not rely on it; look up theBrandviagetDenomInfoor a chain'sgetVBankAssetInfo()and construct theAmountwithAmountMathinstead.
Orchestration Account
Orchestration accounts are a key concept in the Agoric Orchestration API, represented by the OrchestrationAccountCommon interface, which every account (local or remote) implements. These accounts provide high-level operations for managing accounts on remote chains, allowing seamless interaction and management of interchain accounts. The Orchestration accounts abstract the complexity of interchain interactions, providing a unified and simplified interface for developers.
Account Creation
makeAccount(for a chain object) creates a new account on local and/or remote chain as below.
const [agoric, remoteChain] = await Promise.all([
orch.getChain('agoric'),
orch.getChain(chainName),
]);
const [localAccount, remoteAccount] = await Promise.all([
agoric.makeAccount(),
remoteChain.makeAccount(),
]);Address Management
getAddressretrieves the address of the account on the remote chain. It is synchronous; do notawaitit.
const address = orchestrationAccount.getAddress();Balance Management
getBalancesreturns an array of amounts for every balance in the account.getBalanceretrieves the balance of a specific denom for the account.
Both throw if the chain does not have ICQ enabled (icqEnabled in its CosmosChainInfo); see how-orch-works.
const balances = await orchestrationAccount.getBalances();
const balance = await orchestrationAccount.getBalance('uatom');Funds Transfer
sendtransfers an amount to another account on the same chain:send(toAccount, amount, opts?).sendAlltransfers multiple amounts to another account on the same chain in one transaction:sendAll(toAccount, amounts, opts?).transfertransfers an amount to another account, typically on another chain. The destination comes first:transfer(destination, amount, opts?).transferStepstransfers an amount across a multi-hop path, handling complex transfer paths. Here the amount comes first:transferSteps(amount, transferMsg).makeProgressTrackerreturns aProgressTrackerthat can be passed as{ progressTracker }in the options of a transfer-like call to receive incremental updates as a multi-step transfer progresses.getPublicTopicsreturns the account's published vstorage topics (used together withasContinuingOfferto expose account state to a smart-wallet client).
await orchestrationAccount.send(receiverAddress, amount);
await orchestrationAccount.sendAll(receiverAddress, [amount1, amount2]);
await orchestrationAccount.transfer(destinationAddress, amount);
await orchestrationAccount.transferSteps(amount, transferMsg);deposit (move a Zoe Payment into the account) is not part of OrchestrationAccountCommon. It is only available on the local (Agoric) account facet, via LocalAccountMethods:
// only on the local Agoric chain account, for example agoricAccount from agoric.makeAccount()
const depositedAmount = await agoricAccount.deposit(payment);Cosmos accounts do not have deposit; move funds onto a cosmos account with transfer instead. A Noble account has its own depositForBurn for CCTP transfers; see the send-anywhere walkthrough.
ChainHub
ChainHub is a centralized registry of chains, connections, and denoms that simplifies accessing and interacting with multiple chains, providing a unified interface for the Orchestration logic to manage cross-chain operations effectively. The resulting object is an Exo singleton. It has no precious state: its only state is a cache of queries to agoricNames and the info provided in registration calls. When you need a newer version you can simply make a hub and repeat the registrations.
withOrchestration (see start-helper.js) creates and provides a chainHub instance as part of the tools passed to a contract's start function, so most contracts do not call makeChainHub directly; it is mainly useful when constructing a ChainHub outside of a contract, such as in tests.
Registering chains and assets
The idiomatic way for a contract to populate its chainHub is registerChainsAndAssets, called once during start with the chainInfo/assetInfo the contract received in privateArgs:
import { registerChainsAndAssets } from '@agoric/orchestration/src/utils/chain-hub-helper.js';
// inside the contract's start function, given `{ chainHub }` from `tools`
registerChainsAndAssets(
chainHub,
zcf.getTerms().brands,
privateArgs.chainInfo,
privateArgs.assetInfo,
);This registers every chain in chainInfo (and the connections nested under each chain's connections field), then every asset in assetInfo, resolving each asset's brandKey against the contract's brands. Well-known chains (the ones already indexed in agoricNames, such as agoric, cosmoshub, osmosis, noble) are looked up automatically and do not need to be registered this way.
For lower-level, one-off registration (for example from a creator facet after the contract has started), chainHub also exposes the primitives registerChain, registerConnection, and registerAsset directly:
chainHub.registerChain(chainKey, chainInfo);
chainHub.registerConnection(
agoricChainInfo.chainId,
chainInfo.chainId,
connectionInfo,
);
chainHub.registerAsset(denom, denomDetail);prepareChainHubAdmin (in chain-hub-admin.js) wraps registerChain/registerAsset as a durable creator-facet API for adding chain configuration after the contract has already started.
Information Retrieval
getChainInfo(chainName)returns aVow<ChainInfo>for a chain name.getChainInfoByChainId(chainId)returns theChainInfo(synchronously) for a CAIP chain ID, once that chain has been registered or looked up.getConnectionInfo(primary, counter)returns aVow<IBCConnectionInfo>for two given chain IDs.getChainsAndConnection(primaryName, counterName)returns aVowof chain and connection info given primary and counterparty chain names.getAsset(denom, srcChainName)retrieves theDenomDetail(holding/issuing chain names, base denom, brand) for a denom.getDenom(brand)retrieves the denom (string) for aBrand, orundefinedif it is not registered.