Scripts and keeper commands
Run all commands from the repository root. The scripts are in scripts/. The keeper is in services/src/keeper.ts.
Root scripts
| Command | Effect |
|---|---|
pnpm local:chain | Starts Anvil on 127.0.0.1:8545 with chain ID 31337 and one block each second |
pnpm local:deploy | Deploys the contracts to the local chain and writes deployments/local.json |
pnpm demo <command> | Local demo commands: seed, serve, run, shock |
pnpm testnet <command> | Monad testnet commands. Loads the root .env file. |
pnpm testnet:estimate-onchain | Read-only gas estimates on Monad testnet |
pnpm dev:services | Starts the API, the relayer, and the indexer. Loads the root .env file. |
pnpm keeper <command> | One keeper action. Loads the root .env file. |
pnpm dev:web | Starts the web application on port 3000 |
pnpm dev:docs | Starts this documentation site on port 5173 |
pnpm build:docs | Builds this documentation site |
pnpm contracts:build, pnpm contracts:test | forge build and forge test for contracts/ |
pnpm typecheck, pnpm test, pnpm build | Workspace checks. See Checks and troubleshooting. |
pnpm test:abi, pnpm test:smoke, pnpm test:ui, pnpm test:budget | Separate checks. See the same page. |
Local demo commands
The local commands use the public Anvil test mnemonic. They check that the RPC has chain ID 31337 before they send a transaction. Use RPC_URL to select a different local Anvil endpoint.
| Command | Effect |
|---|---|
pnpm demo seed | Deposits 200,000 test USDC for Anvil accounts 0 and 1 in each market. Registers one ladder for each account. Sends the first quote of each market. |
pnpm demo serve | Runs the quote loop and the random taker. This is the default command. |
pnpm demo run | Does local:deploy, seed, and serve in one command |
pnpm demo shock <MON|ETH> <percent> | Records a price shock for the running loop |
pnpm demo serve
| Setting | Value |
|---|---|
| Quote period | One quote call for each market each second |
| Quote life | 30 seconds |
| Quote center | Feed mark (MON 1.573, ETH 0.36) plus drift plus jump |
| Drift | drift += -0.02 * drift + noise, noise uniform from -2 to 2 ticks, limit 40 ticks, tick 0.001 |
| Jump | Adds the squared log return of each observation times 365 / 7. Loses half of its size each 60 seconds. |
| Observation | Attached to the quote when it is due. A pending shock, or else calm noise. |
| Calm noise | Annual volatility 110% for MON and 60% for ETH, limited to a log return of 1% |
| Taker period | One IOC order for each market each 2 seconds, from Anvil account 1 |
| Taker order | 1 to 8 contracts, limit 2 ticks through the best price, at most 4 match attempts |
| Taker bias | Above a position of 30 contracts, the order reduces the position with a chance of 4 in 5 |
Each tenth quote logs the center, the drift, and the jump in ticks. See The demo loop.
pnpm demo shock
pnpm demo shock MON -4
pnpm demo shock ETH 3The percent is a simple price change, above -100 and at most 100. The default is -4. The command writes .local/demo-shock.json. The loop takes the value at the next observation of that market and removes it from the file. A new request for a market replaces a pending one.
The API route POST /demo/shock writes the same file.
Deployment variables
| Variable | Default | Effect |
|---|---|---|
LOCAL_OBSERVATION_SECONDS | 60 | Observation interval of a new local deployment. The Hedging demo uses 10. |
LOCAL_MARKET_CONTRACT | Market | Market or QueuedMarket |
RPC_URL | http://127.0.0.1:8545 | Local Anvil endpoint |
The initial values of a deployment are in scripts/common.ts: MON starts at an oracle price of 0.03 and a mark of 1.573. ETH starts at an oracle price of 3000 and a mark of 0.36.
Testnet commands
All testnet commands need DEPLOYER_PRIVATE_KEY. They check that the RPC has chain ID 10143. After deployment, the deployer must be the owner of the TestOracle.
| Command | Effect | Extra key |
|---|---|---|
pnpm testnet deploy | Deploys the registry, the oracle, the token, and one Market for each asset | |
pnpm testnet deploy-fast | The same with QueuedMarket | |
pnpm testnet seed | Funds the two makers and registers their ladders | MAKER_PRIVATE_KEY |
pnpm testnet seed-fast | The same, with the ladder shapes exchanged, so the other maker has the inside level | MAKER_PRIVATE_KEY |
pnpm testnet serve | Quote loop and random taker | MAKER_PRIVATE_KEY, unless TAKER_SECONDS=0 |
pnpm testnet serve-fast | One quoteAndTake for each market each FAST_SECONDS | |
pnpm testnet recover | Submits a current test price for each market | |
pnpm testnet shock [MON|ETH] | Applies a log return of -0.04 at the next boundary. The default market is MON. | |
pnpm testnet rebound [MON|ETH] | Applies a log return of +0.03 at the next boundary |
The testnet markets use 300-second observations. See Run on Monad testnet for the sequence and Configuration for the variables.
Local shock and testnet shock use different units
pnpm demo shock MON -4 is a simple price change of -4%. pnpm testnet shock MON and the keeper shock are a log return of -0.04, which is about -3.92%.
Gas estimate
pnpm testnet:estimate-onchainThe script compiles the contracts with Foundry, then calls eth_call and eth_estimateGas on testnet with code and storage overrides. It never sends a transaction. It saves .local/onchain-gas-estimate.json. Set FORGE_BIN if Foundry is not at ~/.foundry/bin/forge.
Keeper commands
pnpm keeper oracle --market MON --price 0.05
pnpm keeper shock --market MON
pnpm keeper rebound --market MON
pnpm keeper recover --market MON --price 0.05
pnpm keeper liquidate --market MON --account 0x...The keeper uses the same deployment file and RPC as the service. It signs with KEEPER_PRIVATE_KEY. Without that variable, it uses RELAYER_PRIVATE_KEY. Each command does one action and then stops.
| Command | Effect |
|---|---|
oracle | Submits an observation at the first pending boundary. --price sets the price. Without it, the price does not change. |
shock | The same, with the price multiplied by exp(-0.04) |
rebound | The same, with the price multiplied by exp(0.03) |
recover | Submits an observation at the current chain time. It does not restore missing returns. |
liquidate | Checks an account and sends a liquidation attempt |
| Option | Default | Use |
|---|---|---|
--market | MON | Market symbol |
--price | Current oracle price | Decimal price for oracle and recover |
--account | None | Account for liquidate |
--timestamp | The pending boundary, or the current time for recover | Observation timestamp |
Rules of the oracle commands
- The keeper key must be the owner of the
TestOracle. On the local chain, that is Anvil account 0. - The keeper signs the test price. The market verifies the signature. No publish transaction is necessary.
oracle,shock, andreboundmust run within 5 seconds after the boundary. If the boundary is older, userecover.- The commands refuse a deployment that is not marked as a test oracle.
- Stop the operator loop before you use the same account. The loop and the keeper would use the same nonces and the same boundary.
A permitted short gap uses one endpoint return. A long gap skips funding and rebases. See Oracle and observations.
Rules of the liquidate command
The command stops if the mark is frozen. It reads the equity and the maintenance margin of the account, simulates the close, sends it, and waits for a receipt. It uses a match limit of 32.
Without --account, it scans the accounts in the indexed market events. It skips an account with a zero book position and an account above maintenance margin. A confirmed attempt can leave a part of the position or a funding obligation.
On the local chain, give the keeper the same variables as the API:
DEPLOYMENT_FILE=deployments/local.json RPC_URL=http://127.0.0.1:8545 \
STATE_FILE=.local/local-indexer.json KEEPER_PRIVATE_KEY=0x... \
pnpm keeper liquidate --market MONType check of the scripts
pnpm exec tsc -p scripts/tsconfig.json