Aggregator Market Access Plan
LI.FI-first quote-to-fill plan for Steer signed-quote liquidity.
Purpose
This pass makes Steer liquidity usable through LI.FI Intents without requiring the LI.FI frontend or integrators to understand M1, registered-issuer payloads, or Uniswap v4 hook data.
Current scope: LI.FI Intents only. 0x and UniswapX remain deferred.
Target Outcome
By the end of this work we should be able to:
- Register the solver fill account with LI.FI.
- Push a bounded quote to LI.FI.
- Receive one selected LI.FI order without manually pasting JSON.
- Build real hook material for that selected order.
- Generate LI.FI fill calldata.
- Dry-run the fill plan.
- Submit the fill in live mode.
- Reconcile fill status.
Architecture
Use a small vertical slice instead of a full venue operating platform.
lifi:register-solver
-> quote routes loop
-> live M1 capacity preview
-> LI.FI quote body
-> LI.FI /quotes/submit
-> selected-order intake
-> build request derivation
-> hook material generation
-> LifiHookExecutor.finaliseEscrowOrder calldata
-> escrowed input callback into SignedCollateralSwapRouter
-> hook-generated output delivery into LI.FI output settler
-> guarded transaction submit
-> reconciliationThe offchain signer does not discover price. The preview/pricing path selects a price, and the build/fill path only enforces the selected price envelope. In the default LI.FI path, the solver key submits and signs identity/quote material, but the output inventory is produced by the hook executor during the LI.FI same-chain escrow callback.
Kept Code
- quote-server startup integration for LI.FI quote publishing
lifi:register-solverfor solver account registrationlifi:runfor dry-run checks and one-off fillslifi:servefor the real quote-publish/order-poll/fill loop- LI.FI client
- quote publication helpers
- selected-order normalization
- order validation primitives
- fill calldata builder
- approval/fill submitter
- PGlite replay journal for approval/fill idempotency
Current Runtime
The retained commands are for registration, readiness, quote serving, explicit test order creation, and one-off fill debugging:
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:register-solver -- --live
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:submit-trust-components
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:submit-trust-components -- --live --strict
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:readiness -- --strict
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:serve
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:serve -- --live --once
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:run -- --publish-quotes
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:run -- --fill-once
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:run -- --publish-quotes --fill-once
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:open-escrow-order -- --quote-backed
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:open-escrow-order -- --quote-backed --liveAdd --live only after reviewing the dry-run output.
LI.FI solver API auth uses the x-api-key header in the current provisioned
environment.
Quote submission uses the live solver API chain fields fromChain and
toChain, even though older docs have shown fromChainId and toChainId.
Do not change the payload field names without rechecking the provisioned API.
lifi:serve is the preferred bounded check before unattended operation. Use
--once for a single publish/poll/fill iteration, then remove --once for the
continuous solver loop.
Required Work For Unattended Operation
1. Registration gate
- Keep
LI_FI_SOLVER_ACCOUNT_ADDRESS,LI_FI_SOLVER_ACCOUNT_MESSAGE, andLI_FI_SOLVER_ACCOUNT_SIGNATUREin server-only env. - Run
lifi:register-solver -- --liveduring setup. - Verify the fill private key derives the registered solver account.
- Treat registration, API key, inventory, and trust components as startup readiness gates.
Current support: lifi:serve emits a solver readiness report before loop work.
In live mode, LI_FI_SERVE_VERIFY_IDENTITY=true checks
/solver-api/solver/identities and fails closed before quote/order/fill work
if the signer is not registered for the active API key.
lifi:readiness provides the same non-mutating identity readback as a
standalone command.
Current live evidence: the active LI.FI dev API key reports one identity and
the quote signer is registered.
lifi:submit-trust-components validates the configured oracle/settler
components locally and can submit them once LI.FI confirms the live dev route.
The docs currently name /quotes/trustComponents, but dev returns 404 for that
path.
2. Quote refresh loop
- Wrap current
--publish-quotesbehavior in a supervised interval runner. - Add per-route logging for stale capacity, no-quote, quote body generation, and LI.FI submit result.
- Continue using live M1 capacity as the only v1 quote source.
- Keep range, TTL, decimals,
exclusiveFor, and max quote count explicit in config.
Current support: lifi:readiness -- --strict reads LI.FI uploaded quotes and
checks that the configured quote routes are represented by uploaded quote
ranges for the expected chains, assets, exclusiveFor, and amount.
Live evidence: after publishing, LI.FI dev reports one uploaded quote matching
sepolia-collateral-usdm1-route-0 and zero current orders.
3. Selected-order intake
- Replace
LI_FI_SELECTED_ORDER_JSONwith live order fetch/polling first. - Add WebSocket intake only after polling proves the fill path.
- Deduplicate by
orderId. - Validate every selected order against configured chains, tokens,
trusted components,
exclusiveFor, and deadline buffers.
Current one-shot support: if LI_FI_SELECTED_ORDER_JSON is omitted,
lifi:run -- --fill-once fetches orders from LI_FI_ORDER_FETCH_PATH
or /orders. The solver path consumes the open-order feed rather than
targeting a single order id.
Controlled order support: lifi:open-escrow-order -- --quote-backed requests a
LI.FI integrator quote, selects the quote assigned to our configured hook
executor/exclusive filler, follows the returned approval check, and opens using
LI.FI's returned order.openIntentTx. In live mode it approves the returned
spender when needed and opens the order on-chain so the order server can
discover it without manual selected-order JSON. It reports open.onChainOrderId
when the order id can be extracted from the receipt; that value can be used as
operator evidence. The raw local StandardOrder builder still exists for
compatibility/debugging but is not the intended solver queue path.
The order opener can use LI_FI_TEST_ORDER_PRIVATE_KEY and
LI_FI_TEST_ORDER_ADDRESS for the funded escrow user while
SPONSOR_SIGNER_PRIVATE_KEY remains the registered solver/fill key. If the order
key is omitted, the opener falls back to the quote signer for single-wallet
testing.
In live mode the opener preflights signer native balance, input-token balance,
and allowance before submitting approval/open transactions. It fails closed when
native balance is below LI_FI_MIN_NATIVE_BALANCE_WEI or token balance is below
LI_FI_TEST_INPUT_AMOUNT.
For same-chain controlled tests, the opener defaults missing settlement
addresses to LI.FI's maintained InputSettlerEscrow
0x000025c3226C00B2Cdc200005a1600509f4e00C0 and Output Settler
0x0000000000eC36B683C2E6AC89e9A75989C22a2e, and defaults both oracle fields
to the Output Settler.
4. Build request derivation
- Derive
BuildRequestfrom the validated selected order. - Bind
orderId, LI.FI quote reference, selected deadline, pool id, output token, output amount, settlement executor, solver account, and replay key. - Persist the replay key before transaction submission.
Current one-shot support: if LI_FI_BUILD_REQUEST_JSON is omitted, the runner
derives the build request from the selected order and requires TARGET_POOL_ID.
Plain and non-M1 registered-issuer fills still need a manual
LI_FI_QUOTED_PRICE_E18; registered-issuer-m1 replaces that manual price
with the M1 collateral/usdm ratio. registered-issuer-m1-deposit remains a
legacy alias for existing environments.
5. Hook material generation
- Replace
LI_FI_HOOK_MATERIAL_JSONwith internal hook material generation. - Ask the quote-server signed-quote path for the selected price envelope.
- Fetch optional registered-issuer/M1 payloads when the route requires them.
- Refuse fill if hook material cannot be built for the selected order.
Current one-shot support: if LI_FI_HOOK_MATERIAL_JSON is omitted, the runner
uses LI_FI_HOOK_MATERIAL_MODE to generate hook material internally. plain
signs standard CollateralManager hook data, registered-issuer signs an
AtomicBroker registered-issuer payload from LI_FI_BROKER_PAYLOAD, and
registered-issuer-m1 derives the M1 direction from the selected order tokens:
collateral -> USDM1 fetches deposit material, while USDM1 -> collateral fetches
redeem material. The legacy registered-issuer-m1-deposit value maps to the
same direction-aware path. In that mode the signed
quotedPriceE18 is derived as collateralPrice.index / usdmPrice.index; a
caller-supplied price is accepted only if it matches the M1 ratio.
For LI_FI_FILL_MODE=hook-executor, the default quote trader is
LI_FI_HOOK_EXECUTOR. This matches the v1 accounting path: the executor
receives escrowed inputs, calls the signed swap router, and owns the resulting
CollateralManager collateral/debt position while generated output tokens are
handed to LI.FI.
The executor contract is the registered LI.FI solver identity for v1. The
operator EOA submits transactions through the executor, and the executor
validates EIP-1271 registration signatures from SOLVER_SIGNER_ROLE.
6. Fill and replay safety
- Default fills use the hook executor path. The fill transaction calls
LifiHookExecutor.finaliseEscrowOrder(...); LI.FI's input settler transfers escrowed inputs to the executor and callsorderFinalised(...). - The callback approves and calls
SignedCollateralSwapRouterwith exact-input calldata built from the LI.FI order,LI_FI_POOL_KEY_JSON, and signed hook material. It then approves LI.FI's output settler and callsfillOrderOutputswith hook-generated output tokens. - Keep
LI_FI_FILL_MODE=direct-output-settleronly as a compatibility test; it requires solver-side output inventory and is not the Steer production path. - Use
LI_FI_REPLAY_PGLITE_DATA_DIRfor durable PGlite replay storage. - Make approval and fill submission idempotent by replay key.
- Add pending-fill timeout and operator alerts.
Current one-shot support: live fills fail closed unless
LI_FI_REPLAY_PGLITE_DATA_DIR is configured. Approval and fill submissions are
stored durably in PGlite and conflicting replay-key reuse is rejected.
7. Reconciliation
- Query LI.FI/order status or on-chain receipts after submission.
- Record confirmed and reverted approvals/fills.
- Add settlement/finalisation after output delivery is proven.
Current support: lifi:serve queries GET /orders/status with the order's
onChainOrderId after each attempted fill and includes the raw status payload
in its report. It also reads durable replay storage and reports fills older
than LI_FI_PENDING_FILL_ALERT_SECONDS that remain unconfirmed.
When status reports a delivered/proven output and finalise config is present,
it builds escrow or compact input-settler finalise calldata and reports the
plan. Live mode can submit finalise only when LI_FI_SERVE_SUBMIT_FINALISE=true,
LI_FI_REPLAY_PGLITE_DATA_DIR, and RPC config are present; submissions are
recorded through the same PGlite replay store.
8. Unattended command
pnpm --filter @steerprotocol/collateral-rail-quote-server lifi:servelifi:serve should own quote refresh, selected-order intake, build request
derivation, hook material generation, fill execution, and reconciliation. It
must fail closed unless live mode, API key, registered solver account, durable
storage, RPC, quote routes, and trust components are configured.
Current support: lifi:serve refreshes quotes, polls the order API, dedupes
orders by orderId in-process, and delegates each new order to the validated
fill path. Use --once for a bounded single iteration. Use
LI_FI_SERVE_PUBLISH_QUOTES, LI_FI_SERVE_FILL_ORDERS,
LI_FI_SERVE_POLL_INTERVAL_MS, and LI_FI_SERVE_MAX_ITERATIONS for loop
control.
Quote Inputs
LI_FI_INTENTS_API_KEYLI_FI_ORDER_SERVER_BASELI_FI_INVENTORY_ROUTES_JSON, static quote route list; each route includes token decimalsLI_FI_QUOTE_TTL_SECONDSLI_FI_PREVIEW_POLICY_JSONLI_FI_TRUST_COMPONENTS_JSONLI_FI_ALLOWED_ORIGIN_CHAINSLI_FI_ALLOWED_OUTPUT_CHAINSLI_FI_ALLOWED_INPUT_TOKENSLI_FI_ALLOWED_OUTPUT_TOKENSLI_FI_MAX_CALL_BYTESLI_FI_MAX_CONTEXT_BYTES
LI_FI_SOLVER_ACCOUNT_ADDRESS and LI_FI_EXCLUSIVE_FOR are derived from the
deployed LI.FI hook executor when CHAIN_ID and
deployments/manifests/<chainId>-lifi-hook-executor.json are present. Keep
LI_FI_SOLVER_ACCOUNT_MESSAGE and LI_FI_SOLVER_ACCOUNT_SIGNATURE only for the
one-time lifi:register-solver operation.
Fill Inputs
CHAIN_IDQUOTE_RPC_URLorRPC_URLfor live fills. If omitted,loadRuntimeEnvfalls back toSEPOLIA_RPC_URL.SPONSOR_SIGNER_PRIVATE_KEY. If omitted,loadRuntimeEnvfalls back toREGISTERED_ISSUER_QUOTE_SIGNER_PRIVATE_KEY.LI_FI_ORDER_FETCH_PATH, used when selected-order JSON is omittedLI_FI_SERVE_PUBLISH_QUOTESLI_FI_SERVE_VERIFY_IDENTITYLI_FI_SERVE_FILL_ORDERSLI_FI_SERVE_POLL_INTERVAL_MSLI_FI_SERVE_MAX_ITERATIONSLI_FI_PENDING_FILL_ALERT_SECONDSLI_FI_SERVE_PLAN_FINALISELI_FI_SERVE_SUBMIT_FINALISELI_FI_INPUT_SETTLER_KIND,escroworcompactTARGET_POOL_ID, required when build-request JSON is omitted. If omitted,loadRuntimeEnvfalls back toREGISTERED_ISSUER_POOL_ID.LI_FI_REPLAY_PGLITE_DATA_DIR, required for live fillsLI_FI_MIN_FILL_BUFFER_SECONDSLI_FI_FILL_MODE, defaults tohook-executor; usedirect-output-settleronly for compatibility testsLI_FI_POOL_KEY_JSONM1_JWTLI_FI_M1_RISK_AWARE, optionalLI_FI_M1_DEPOSIT_REQUEST_JSON/LI_FI_M1_REDEEM_REQUEST_JSON, optional signer request overrides
Deployed registered-issuer profiles derive the LI.FI executor, input/output
settlers, broker adapter, collateral, USDM1, issuer id, settlement controller,
collateral manager, router, solver address, exclusive-for address, and
quote-backed M1 defaults from deployments/manifests/<chainId>-*.json, with
broadcast/DeployLifiHookExecutor.s.sol/<chainId>/run-latest.json as the
executor fallback.
Keep these only for controlled compatibility tests:
LI_FI_SELECTED_ORDER_JSONLI_FI_BUILD_REQUEST_JSONLI_FI_HOOK_MATERIAL_JSONLI_FI_QUOTED_PRICE_E18LI_FI_HOOK_EXECUTOR_ROUTER_CALLDATALI_FI_HOOK_EXECUTOR_APPROVALS_JSONLI_FI_HOOK_EXECUTOR_POST_SWAP_APPROVALS_JSONLI_FI_HOOK_EXECUTOR_REFUND_RECIPIENTLI_FI_FINALISE_DESTINATIONLI_FI_FINALISE_CALLLI_FI_FINALISE_SIGNATURESLI_FI_FINALISE_SOLVERLI_FI_SOLVE_TIMESTAMPS_JSONLI_FI_TEST_INPUT_TOKEN,LI_FI_TEST_OUTPUT_TOKEN,LI_FI_TEST_INPUT_AMOUNT, andLI_FI_TEST_OUTPUT_AMOUNT, optional controlled escrow order overrides. When omitted, the opener uses the firstLI_FI_INVENTORY_ROUTES_JSONroute and reuses routemaxToAmountwhen it is present. Manual plain tests may still compute output fromLI_FI_QUOTED_PRICE_E18.- optional
LI_FI_TEST_*order overrides for controlled escrow order opening LI_FI_MIN_NATIVE_BALANCE_WEI, optional live opener gas-balance floor
Testing
The first pass tests only the retained vertical slice:
- dry-run quote publication produces LI.FI quote bodies without submitting
- M1 route config uses live capacity for published quote ranges
- solver registration command builds and submits the expected registration body
- escrow order opener builds
StandardOrderbytes andopen(StandardOrder)calldata - selected order + build request + hook material produce fill calldata
- fetched selected order can derive build request and plain hook material
- fetched selected order can derive registered-issuer and M1 deposit hook material without manual hook JSON
- PGlite replay journal persists approval and fill submissions across process opens
- serve loop refreshes quotes, polls orders, fills new order ids, and skips duplicate order ids
- serve loop queries LI.FI order status after attempted fills
- serve loop plans escrow/compact finalise calldata after delivered/proven status when finalise config is present
- finalise submitter preflights, waits for receipts, and reuses replay records
- serve loop reports stale pending fills from durable replay storage
- dry-run fill reports replay key, target, chain id, and no transaction hash
- existing signer, quote, route, config, and HTTP tests continue passing
Detailed test notes live in LI.FI Intents Testing.
Next Phases
- Live solver registration.
- Live quote publish for one narrow dev route.
- Controlled escrow order opened on LI.FI dev and ingested from
/orders. - Live controlled fill.
- Live finalise submission against real delivered/proven LI.FI status payloads.