mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
dexkeeper: CI image build + devnet deploy manifests (KMSSecret + Deployment + drive-swap Job)
- Dockerfile.dexkeeper: pure-Go (CGO off) static keeper image; documents the zap>=v0.8.10 + chains-RPC-seam tag prereqs. - docker.yml: add ghcr.io/luxfi/dexkeeper to the build matrix. - k8s/devnet-keeper/kmssecret-mnemonic.yaml: SANCTIONED maker-key path — a KMSSecret (operator-reconciled) that syncs the devnet mnemonic from lux KMS (lux-infra/devnet, /staking key=mnemonic, the proven luxd-staking-kms-sync scope) into LUX_MNEMONIC. No plaintext handled out-of-band. Server-dry-run validated against the live CRD. - deployment.yaml: the keeper in watch mode against devnet C/D RPC + dchain-venue:9099, maker key from the synced secret, single replica (proposer-relays-once). - job-drive-first-swap.yaml: one-shot seed + Phase-A swap to kick the first DEXFill. - README.md: the full deploy sequence + verification (DEXFill eth_getLogs, subgraph, UI). DEVNET ONLY. All manifests dry-run clean.
This commit is contained in:
@@ -19,6 +19,8 @@ jobs:
|
||||
dockerfile: Dockerfile
|
||||
- image: ghcr.io/luxfi/dex-gateway
|
||||
dockerfile: Dockerfile.gateway
|
||||
- image: ghcr.io/luxfi/dexkeeper
|
||||
dockerfile: Dockerfile.dexkeeper
|
||||
uses: hanzoai/.github/.github/workflows/docker-build.yml@main
|
||||
with:
|
||||
image: ${{ matrix.image }}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#
|
||||
# dexkeeper — the off-chain keeper that drives the on-chain 0x9999 two-phase atomic
|
||||
# DEX seam (watch IntentSubmitted -> dexvm ImportTx + RelayOrderTx via dex.submitTx ->
|
||||
# poll dex.getSettlement -> C-side Phase-B settle -> DEXFill). Pure-Go (CGO disabled);
|
||||
# the maker key is derived in-process from the LUX_MNEMONIC env (KMS-synced secret).
|
||||
#
|
||||
# BUILD DEP NOTE: the keeper imports github.com/luxfi/zap (>= v0.8.10, the
|
||||
# EncodeIntentHookData/EncodeSettlementHookData calldata) and github.com/luxfi/chains
|
||||
# (the dexvm txs + the dex.submitTx/getSettlement RPC server). Both tags must be cut
|
||||
# before this image builds from published modules alone (go.work is NOT used in CI).
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache git ca-certificates
|
||||
|
||||
ARG GITHUB_TOKEN
|
||||
RUN git config --global url."https://${GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
|
||||
ENV GOPRIVATE=github.com/luxfi/*,github.com/hanzoai/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
|
||||
ARG TARGETARCH=amd64
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -ldflags="-s -w" -o dexkeeper ./cmd/dexkeeper
|
||||
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache ca-certificates && \
|
||||
addgroup -g 1000 -S keeper && \
|
||||
adduser -u 1000 -S keeper -G keeper
|
||||
|
||||
WORKDIR /home/keeper
|
||||
COPY --from=builder /app/dexkeeper .
|
||||
RUN chown keeper:keeper dexkeeper
|
||||
|
||||
USER keeper
|
||||
|
||||
# No ports: the keeper is an egress-only client (C-Chain RPC + dexvm RPC + venue ZAP).
|
||||
ENTRYPOINT ["./dexkeeper"]
|
||||
@@ -0,0 +1,66 @@
|
||||
# dexkeeper — devnet 0x9999 DEX-seam deploy
|
||||
|
||||
The keeper that makes the on-chain **0x9999 two-phase atomic DEX swap flow**
|
||||
operational on **devnet** so the first real `DEXFill` lands and the V4/DEX markets
|
||||
populate. DEVNET ONLY.
|
||||
|
||||
## The seam (what the keeper drives)
|
||||
|
||||
```
|
||||
C Phase-A intent (signed swap to 0x9999, DI01 hookData)
|
||||
-> SubmitSwapIntent locks input + stages C->D object (key=intentID) + emits IntentSubmitted
|
||||
KEEPER watches IntentSubmitted
|
||||
-> dexvm ImportTx (consume C->D object, fund D order) via dex.submitTx
|
||||
-> dexvm settling RelayOrderTx (clob_submit, CollateralRef=intentID) via dex.submitTx
|
||||
dexvm proposer relays ONCE (obtainFills) -> fills carried -> settleFromFills exports D->C proceeds
|
||||
KEEPER polls dex.getSettlement(intentID) -> {outputID, amount}
|
||||
-> C Phase-B settle (signed swap to 0x9999, DS01 outputID|amount|intentID)
|
||||
-> ImportSettlement credits maker + emits DEXFill <-- the goal
|
||||
```
|
||||
|
||||
## Prerequisites (status)
|
||||
|
||||
1. **dexvm relay wired** — DONE. All 5 `lux-devnet` validators log
|
||||
`relayConfigured:true -> dchain-venue:9099` (per-chain config.json seeded by the
|
||||
`luxd-startup` CM; mirrored to source in `universe` `feat/dexvm-relay-devnet`).
|
||||
2. **dexvm RPC seam** — code in `chains` `feat/dexvm-submit-tx-rpc`: `dex.submitTx`
|
||||
(mempool entry) + `dex.getSettlement` (proceeds-coordinate query). The luxd node
|
||||
image must be rebuilt with the new `dexvm-linux-amd64` plugin (S3) and the
|
||||
validators rolled onto it.
|
||||
3. **keeper image** — `ghcr.io/luxfi/dexkeeper` (this repo, `docker.yml` matrix +
|
||||
`Dockerfile.dexkeeper`). Requires `zap >= v0.8.10` and a `chains` tag carrying the
|
||||
RPC seam to be cut first (the keeper imports both).
|
||||
4. **maker key in KMS** — the devnet mnemonic is already in lux KMS; `kmssecret-mnemonic.yaml`
|
||||
syncs it to the `dexkeeper-mnemonic` secret. The keeper derives the maker
|
||||
(`0x9011…`, `m/44'/9000'/0'/0/i`) in-process.
|
||||
|
||||
## Deploy sequence (CI/CD + operator only)
|
||||
|
||||
```sh
|
||||
# 0) Cut tags so the keeper image builds from published modules:
|
||||
# - luxfi/zap v0.8.10 (EncodeIntentHookData / EncodeSettlementHookData)
|
||||
# - luxfi/chains <tag> (dexvm dex.submitTx + dex.getSettlement)
|
||||
# 1) Rebuild the node image + dexvm-linux-amd64 plugin (chains tag), publish to S3,
|
||||
# bump the lux-devnet LuxNetwork CR pluginSource, roll the 5 luxd pods (OnDelete).
|
||||
# 2) Build the keeper image (push to main / tag v* triggers docker.yml).
|
||||
# 3) Sync the maker key + deploy the keeper:
|
||||
kubectl --context do-sfo3-lux-k8s apply -f kmssecret-mnemonic.yaml
|
||||
kubectl --context do-sfo3-lux-k8s apply -f deployment.yaml
|
||||
# 4) Seed venue liquidity + drive the first swap:
|
||||
kubectl --context do-sfo3-lux-k8s apply -f job-drive-first-swap.yaml
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
# first DEXFill on devnet C (report the tx hash):
|
||||
curl -s -X POST https://api.lux-dev.network/ext/bc/C/rpc -H 'content-type:application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{"address":"0x0000000000000000000000000000000000009999","fromBlock":"0x0","toBlock":"latest"}]}'
|
||||
# DEXFill topic0 = keccak256("DEXFill(bytes32,address,uint256,uint256)")
|
||||
|
||||
# dex subgraph shows the market + fill (not markets:null):
|
||||
curl -s -X POST https://explorer.lux-dev.network/v1/graph/cchain/dex/graphql \
|
||||
-H 'content-type:application/json' -d '{"query":"{ markets { id baseAsset quoteAsset } fills(first:3){ id } }"}'
|
||||
|
||||
# UI: lux.exchange?network=devnet renders the V4/DEX market.
|
||||
```
|
||||
@@ -0,0 +1,68 @@
|
||||
# dexkeeper Deployment (lux-devnet): the off-chain keeper that drives the on-chain
|
||||
# 0x9999 two-phase atomic DEX seam so DEXFill lands on the devnet C-Chain.
|
||||
#
|
||||
# It watches the C-Chain 0x9999 IntentSubmitted event, submits the dexvm ImportTx +
|
||||
# settling RelayOrderTx (via dex.submitTx), polls dex.getSettlement for the D->C
|
||||
# proceeds object, then has the maker sign + send the C-side Phase-B settle (which
|
||||
# emits DEXFill). The maker key is derived in-process from LUX_MNEMONIC (KMS-synced;
|
||||
# see kmssecret-mnemonic.yaml).
|
||||
#
|
||||
# PRECONDITIONS (all in this work's branches):
|
||||
# - luxd validators on a node image whose dexvm includes dex.submitTx + dex.getSettlement
|
||||
# (chains feat/dexvm-submit-tx-rpc) and relayConfigured:true (done — luxd-startup CM).
|
||||
# - dchain-venue:9099 reachable (done).
|
||||
# - dexkeeper image built (ghcr.io/luxfi/dexkeeper, docker.yml matrix).
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dexkeeper
|
||||
namespace: lux-devnet
|
||||
labels:
|
||||
app.kubernetes.io/name: dexkeeper
|
||||
app.kubernetes.io/part-of: lux-dex
|
||||
spec:
|
||||
replicas: 1 # single keeper: relays are proposer-relays-once; a 2nd keeper would
|
||||
# double-submit ImportTx/RelayOrderTx (idempotent on the dexvm side by
|
||||
# content-addressed UTXO/receipt, but no need). Scale only with leader-election.
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: dexkeeper
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: dexkeeper
|
||||
app.kubernetes.io/part-of: lux-dex
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-luxfi
|
||||
containers:
|
||||
- name: dexkeeper
|
||||
image: ghcr.io/luxfi/dexkeeper:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- --mode=watch
|
||||
- --c-rpc=http://luxd-0.luxd-headless.lux-devnet.svc.cluster.local:9650/ext/bc/C/rpc
|
||||
- --d-rpc=http://luxd-0.luxd-headless.lux-devnet.svc.cluster.local:9650/ext/bc/28mZwmZtKAdGWoJRWSYw8RXxUcHZUDVsNgjMH4nnMfoJGJi2SX/rpc
|
||||
- --d-chain-id=28mZwmZtKAdGWoJRWSYw8RXxUcHZUDVsNgjMH4nnMfoJGJi2SX
|
||||
- --venue=dchain-venue.lux-devnet.svc.cluster.local:9099
|
||||
- --maker=0x9011E888251AB053B7bD1cdB598Db4f9DEd94714
|
||||
env:
|
||||
# The BIP-39 mnemonic the keeper derives the maker key from (KMS-synced).
|
||||
- name: LUX_MNEMONIC
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dexkeeper-mnemonic
|
||||
key: LUX_MNEMONIC
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
@@ -0,0 +1,62 @@
|
||||
# One-shot Job: seed venue liquidity + drive the FIRST Phase-A 0x9999 swap from the
|
||||
# maker, in-cluster (the maker key is derived from the KMS-synced LUX_MNEMONIC; nothing
|
||||
# is handled out-of-band). The running dexkeeper Deployment then drives Phase B -> the
|
||||
# first DEXFill. Use this to seed + kick the first trade; delete after it Completes.
|
||||
#
|
||||
# Modes (--mode):
|
||||
# seed — only seed the venue (clob_deposit + maker resting ask) over ZAP.
|
||||
# phase-a — seed + send ONE Phase-A intent swap from the maker; prints intentID + txHash.
|
||||
#
|
||||
# After this Completes, verify:
|
||||
# eth_getLogs(address=0x9999, IntentSubmitted) on devnet C returns the intent;
|
||||
# the dexkeeper logs show ImportTx + RelayOrderTx submitted + Phase-B settle;
|
||||
# eth_getLogs(address=0x9999, DEXFill) returns the first fill.
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: dexkeeper-drive-first-swap
|
||||
namespace: lux-devnet
|
||||
labels:
|
||||
app.kubernetes.io/name: dexkeeper
|
||||
app.kubernetes.io/part-of: lux-dex
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 600
|
||||
backoffLimit: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: dexkeeper
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
imagePullSecrets:
|
||||
- name: ghcr-luxfi
|
||||
containers:
|
||||
- name: dexkeeper
|
||||
image: ghcr.io/luxfi/dexkeeper:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- --mode=phase-a
|
||||
- --c-rpc=http://luxd-0.luxd-headless.lux-devnet.svc.cluster.local:9650/ext/bc/C/rpc
|
||||
- --d-rpc=http://luxd-0.luxd-headless.lux-devnet.svc.cluster.local:9650/ext/bc/28mZwmZtKAdGWoJRWSYw8RXxUcHZUDVsNgjMH4nnMfoJGJi2SX/rpc
|
||||
- --d-chain-id=28mZwmZtKAdGWoJRWSYw8RXxUcHZUDVsNgjMH4nnMfoJGJi2SX
|
||||
- --venue=dchain-venue.lux-devnet.svc.cluster.local:9099
|
||||
- --maker=0x9011E888251AB053B7bD1cdB598Db4f9DEd94714
|
||||
env:
|
||||
- name: LUX_MNEMONIC
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dexkeeper-mnemonic
|
||||
key: LUX_MNEMONIC
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
Reference in New Issue
Block a user