Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runbook

How to stand up the stack and deploy a pool. Four repositories take part.

RepositoryRole
port0_contractsPool component, registry component, and note scripts
port0_sdkTypeScript SDK: contract artifacts, the registry account builder, and the pool operations
port0_indexerRust indexer: reads the registry and pools over node RPC, serves HTTP
port0_appStatic web app: browse pools, trade, provide liquidity, create pools

Testnet version: 0.17.1. Every @miden-sdk/* and miden-node-proto pin matches it.

Part 1: one-time operator setup

1. Contracts

In the port0_contracts checkout:

cargo miden build --manifest-path contracts/port0/Cargo.toml --release

The SDK’s artifact exporter builds the packages itself, so this step is only needed to seed a fresh checkout.

2. SDK artifacts

In the port0_sdk checkout:

npm install
npm run generate   # exports the compiled packages into src/generated
npm run build

3. Pool code commitment

npm run pool:commitment

Prints the accepted pool code commitment, currently 0x51c52cca051456d35329f2add92319f6447f1437a8b716279e536c1c0f6fab09. The value covers the pool’s full component set: the port0 component, CodeInspection, the network-account auth, and the fee-policy components. It changes whenever any of those change, and the registry must be seeded with the value the deployed pool produces.

4. Registry account

cp .env.example .env       # set PORT0_FEE_FAUCET_ID and PORT0_POOL_COMMITMENT
npm run registry:deploy

The tool is interactive. On first run it creates an operator wallet, prints its bech32 address, and polls until the fee asset arrives. It consumes the incoming note, then funds the registry with whatever the operator holds above the transaction fee, capped at PORT0_FUND. Send at least 134 base units to proceed; 1000 + 133 funds the registry at the default. The tool then builds the registry with createRegistryAccount, funds it, and deploys it, printing the registry id. No re-run is needed.

A stored registry is reused only when it accepts the configured pool code commitment and admits both registry note kinds. A registry that predates the registry-collect entry is reported and a fresh registry is deployed instead.

Wait timeout: PORT0_WAIT_SECS (default 600). On timeout it prints the address and exits. Keys and store: ~/.miden/stores/<PORT0_STORE>/.

5. Indexer

In the port0_indexer checkout:

cp .env.example .env       # set PORT0_REGISTRY_ID to the registry id from step 4
cargo run --release

Serves http://127.0.0.1:8080, loopback by default for an API behind a reverse proxy. GET /status reports the registry id, the last registry block, and the known and fetched pool counts. Pools appear after the registry poll (default 60 s) and the pool poll (default 15 s).

The API answers cross-origin only for the origins in INDEXER_ALLOWED_ORIGINS. The shipped .env.example lists the Vite dev ports, so npm run dev at http://localhost:5173 is admitted. A deployment that serves the app and the API from one origin needs no entry.

6. App

In the port0_app checkout:

cp .env.example .env       # set VITE_REGISTRY_ID and VITE_INDEXER_URL=http://localhost:8080
npm run dev

VITE_REGISTRY_ID enables registration during pool creation. VITE_INDEXER_URL is the pool source for browsing and the pool page. An empty value disables both.

Part 2: deploy a pool through the app

Prerequisite: the connected wallet holds USDCX. The wallet pays the pool funding note and the register note fee. The measured per-transaction fee is 133 base units.

  1. Open /create.html and connect the wallet.
  2. Choose the token pair. Either launch a fixed-supply token, which creates a faucet and funds it with 2 x 133 = 266 base units of USDCX, or enter two existing faucet ids.
  3. Choose the LP fee tier: 5, 30, or 100 basis points, whichever the registry lists. The protocol’s cut is a share of that fee, read from the registry and carved out of it, so the swapper pays the LP fee tier and nothing more.
  4. Submit. The page runs three on-chain steps and reports each.
StepWhat happensWho pays
Create pool accountBuilt locally, not yet on chain. Fees, the P2ID root, and the registry pointer are initial storage; the LP token key is derived by the contractNothing
DeployThe wallet sends 133 base units of USDCX to the pool; then the app consumes that note as the pool’s first transactionWallet funds; the pool pays its first fee from the received funds
RegisterRegister note via the wallet, sponsored with the registry’s register fee; the node’s transaction builder consumes it against the registry, which verifies the pool code commitment, the registry pointer, the token pair, and the fee values by foreign procedure invocationWallet
  1. The page redirects to /pool.html?id=<poolid>. The pool appears on the home page once the indexer has polled the registry and fetched the pool. Until then the pool page shows a spinner and re-checks the indexer every 5 s, so the redirect lands on a page that fills itself in.

If a stage fails, the page keeps the flow state and shows Retry and Restart. Retry resumes at the failed stage; it does not recreate the pool, and the deploy stage reuses an existing funding note instead of sending a second one. Restart clears the state and starts from the pool account.

Part 3: deploy a pool programmatically

The same steps without the UI. The wallet-signed steps can be submitted by the client directly when the account’s key is in the client keystore.

import { MidenClient } from "@miden-sdk/miden-sdk";
import { XykClient } from "@port0.sh/sdk";

const client = await MidenClient.create({
  rpcUrl,
  feeFaucetId,
  proverUrl: "testnet",
  autoSync: true,
});
const xyk = XykClient.from(client);

// 1. Build the pool account locally. Fees, the P2ID root, and the registry
// pointer are initial storage; the LP token key is derived by the contract.
const account = await xyk.createPoolAccount({
  token0Key,
  token1Key,
  registryId,
  lpFeeBps: 30n,
  protocolFeeShareBps,
  p2idRoot,
  feeFaucetId,
  noteFees,
});
const poolId = account.id();

// 2. Fund it, then 3. deploy by consuming the funding note.
const { txId: fundTx } = await client.transactions.send({
  account: sender.toString(),
  to: poolId.toString(),
  token: feeFaucetId,
  amount: 133n,
  type: "public",
  waitForConfirmation: true,
});
await client.syncChain();
const consumable = await client.notes.listAvailable({ account: poolId.toString() });
await xyk.deployPoolAccount(poolId, consumable[0].id().toString(), {
  waitForConfirmation: true,
});

// 4. Register. The pool was configured when it was created: fees, the P2ID
// root, and the registry pointer are initial storage, and the LP token key is
// derived by the contract.
await xyk.registerPool(
  { registryId, feeFaucetId, registerFee },
  { sender, poolId, asset0: token0FaucetId, asset1: token1FaucetId },
  { submit: true, waitForConfirmation: true },
);

Failure conditions

SymptomCause and fix
PORT0_REGISTRY_ID is requiredThe indexer has no registry id. Set it in .env.
registry response is missing slot pool_registry::pool_registry::poolsThe indexer points at an account that is not the registry. Correct the id.
Pool absent from the home pageThe indexer is not running, the registry id is wrong, or the register note has not been consumed by the node’s builder yet. Check GET /status.
Pool code commitment not accepted at registrationThe registry was seeded with a stale commitment. Re-run npm run pool:commitment and redeploy the registry, or register a pool built with the accepted component set.
Trading disabled on the pool pageThe indexer could not read the pool’s fee asset id or fee schedule. Check the pool’s network-account slots.
funding not received within Ns (balance X/Y)The operator address was not funded within the wait. Send it fee asset base units and re-run, or raise PORT0_WAIT_SECS.
SummaryAnchorMismatchError at registerThe wallet captured its chain anchor at a block other than the request’s bound block; a block landed between the two syncs. The page syncs the wallet immediately before the build to narrow the window. Retry rebuilds the request at a fresh tip.
ERR_NETWORK_ACCOUNT_TRANSACTION_HAS_NO_EFFECTA network-account deploy with no effect. Deploy by consuming a funding note.
procedure with root digest ... could not be foundThe client minor is behind the node. Bump every @miden-sdk/* and miden-node-proto pin to the node’s version.