unrip/docs/operator-runbook.md
philipp b4186d9715
All checks were successful
deploy / deploy (push) Successful in 22s
Add configured withdrawal defaults
Proof: The funded NEAR Intents operator path should have a stable configured withdrawal destination for the active assets so exits do not depend on retyping recipient addresses.

Assumptions: Active asset withdrawal destinations are long-lived operator settings and can safely live in runtime config; actual withdrawals still require explicit unfreeze and operator action.

Still fake: Strategy and executor remain disarmed, no live trade quote has been submitted, and the live withdrawal transaction itself has not been exercised yet.
2026-04-02 12:38:19 +02:00

5.9 KiB

Operator Runbook

This turn implements the first funded market-maker loop for the active BTC/EURe pair on NEAR Intents.

Verified venue flow

The implementation follows the official NEAR Intents market-maker path:

  1. Funding handles come from the Passive Deposit/Withdrawal Service deposit_address RPC for the configured treasury chains.
  2. Spendable inventory comes from the Verifier internal ledger on intents.near via mt_batch_balance_of.
  3. Pending deposits remain non-spendable and are tracked from recent_deposits.
  4. Real market-maker execution is a Solver Relay quote_response carrying a signed token_diff.
  5. Named NEAR accounts need the executor public key registered on intents.near via add_public_key before live submission will succeed.

The Message Bus settles matched intents on-chain after a user accepts the quote. The executor therefore submits quote responses; it does not bridge or top up inventory on the hot path.

Required env and secrets

Minimum required runtime values:

  • NEAR_INTENTS_API_KEY
  • NEAR_INTENTS_ACCOUNT_ID
  • NEAR_INTENTS_SIGNER_PRIVATE_KEY
  • POSTGRES_URL

Before the first live attempt on a named NEAR account, register the executor public key on intents.near from that named account:

near contract call-function as-transaction \
  intents.near add_public_key json-args '{
    "public_key": "ed25519:<executor-public-key>"
  }' prepaid-gas '100.0 Tgas' attached-deposit '1 yoctoNEAR' \
  sign-as <ACCOUNT_ID> network-config mainnet sign-with-keychain send

The executor stays disarmed by default even after the key is registered.

Local bring-up

npm install
cp .env.example .env
# fill NEAR_INTENTS_API_KEY, NEAR_INTENTS_ACCOUNT_ID, NEAR_INTENTS_SIGNER_PRIVATE_KEY
docker compose up -d --build

Services:

  • near-intents-ingest
  • market-reference-ingest
  • liquidity-manager
  • inventory-sync
  • history-writer
  • strategy-engine
  • trade-executor

Control APIs

Default local ports:

  • 8081 near-intents-ingest
  • 8082 market-reference-ingest
  • 8083 inventory-sync
  • 8084 liquidity-manager
  • 8085 history-writer
  • 8086 strategy-engine
  • 8087 trade-executor

Common inspection:

curl -s http://127.0.0.1:8081/healthz
curl -s http://127.0.0.1:8081/state
curl -s http://127.0.0.1:8086/state
curl -s http://127.0.0.1:8087/state

Useful controls:

curl -s -X POST http://127.0.0.1:8082/refresh
curl -s -X POST http://127.0.0.1:8083/refresh
curl -s -X POST http://127.0.0.1:8084/refresh

curl -s -X POST http://127.0.0.1:8084/withdrawal-estimate \
  -H 'content-type: application/json' \
  -d '{"asset_id":"nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near","amount":"5000000000000000000","destination_address":"0xYourGnosisAddress"}'

curl -s -X POST http://127.0.0.1:8084/freeze-withdrawals \
  -H 'content-type: application/json' \
  -d '{"frozen":false}'

curl -s -X POST http://127.0.0.1:8084/withdraw \
  -H 'content-type: application/json' \
  -d '{"asset_id":"nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near","amount":"5000000000000000000","destination_address":"0xYourGnosisAddress"}'

curl -s -X POST http://127.0.0.1:8086/arm
curl -s -X POST http://127.0.0.1:8086/disarm
curl -s -X PUT http://127.0.0.1:8086/limits \
  -H 'content-type: application/json' \
  -d '{"max_notional_eure":5}'

curl -s -X POST http://127.0.0.1:8087/arm
curl -s -X POST http://127.0.0.1:8087/disarm

Track a withdrawal so it stays visible in liquidity and inventory state:

curl -s -X POST http://127.0.0.1:8084/track-withdrawal \
  -H 'content-type: application/json' \
  -d '{"withdrawal_hash":"<near-burn-tx-hash>","asset_id":"nep141:btc.omft.near","chain":"btc:mainnet","amount":"1000"}'

Notes:

  • Deposit addresses are built in. liquidity-manager refreshes them from the bridge deposit_address RPC and exposes them through /state.
  • The repo withdrawal action is for external-chain exits on the active assets. It submits intents.near::ft_withdraw, using the active OMFT token contract as receiver_id and memo=WITHDRAW_TO:<destination>, then tracks the returned NEAR transaction hash through bridge withdrawal_status.
  • destination_address can be omitted only when a default withdrawal address is configured for that asset via TRADING_BTC_WITHDRAW_ADDRESS or TRADING_EURE_WITHDRAW_ADDRESS.
  • Withdrawals stay frozen by default. Unfreeze explicitly before calling /withdraw, then freeze them again after the operation if you do not want further exits.

Safe arming sequence

  1. Confirm market-reference-ingest is publishing fresh BTC/EUR data.
  2. Confirm inventory-sync shows credited spendable balances on intents.near.
  3. Confirm liquidity-manager shows the expected deposit handle and any pending funding separately from spendable inventory.
  4. Confirm history-writer has PostgreSQL connectivity.
  5. Keep STRATEGY_MAX_NOTIONAL_EURE=5 for the first live test.
  6. Arm strategy-engine first.
  7. Observe actionable decisions without venue errors.
  8. Arm trade-executor only when the signer key is registered and funded inventory is already credited.

What to inspect after a live attempt

  • decision.trade_decision for the reasoning chain.
  • cmd.execute_trade for the emitted quote response command.
  • exec.trade_result for submission outcome.
  • PostgreSQL tables:
    • swap_demand_events
    • market_price_events
    • intent_inventory_snapshots
    • liquidity_actions
    • trade_decisions
    • execute_trade_commands
    • trade_execution_results

Still fake

  • Settlement follow-up after user quote acceptance is only visible indirectly through Solver Relay quote-status observations; the repo records the live quote-response attempt, not an end-user acceptance flow it does not control.
  • The executor checks signer registration best-effort. If the verifier key-check view surface changes, the live submission itself remains the definitive signal.