feat(devops): add Solana quant bot K8s/Helm, CI/CD, observability, and funding

- Add solana-quant-bot deployment + ClusterIP service templates
- Add solana-rpc ExternalName service for Helius/QuickNode
- Add Alembic migration init job template
- Update values.yaml: solana-rpc, bot config, new Kafka topics, observability
- Update configmap with Solana RPC endpoint via internal DNS
- Update network policies: bot ingress/egress, postgres/redis/kafka access
- Update CI/CD workflow: build/push/deploy solana-quant-bot service
- Add Prometheus ServiceMonitor, PrometheusRules (alerts)
- Add Grafana dashboard ConfigMap (trades, latency, balance, P&L)
- Add initial funding script with dry-run and verification modes
This commit is contained in:
Hermes Agent 2026-05-24 04:46:58 +00:00
parent 99a864171b
commit da8fe22f6e
13 changed files with 927 additions and 3 deletions

View file

@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
service: [execute-service, data-service, news-service] service: [execute-service, data-service, news-service, solana-quant-bot]
defaults: defaults:
run: run:
working-directory: trading-platform/${{ matrix.service }} working-directory: trading-platform/${{ matrix.service }}
@ -109,7 +109,7 @@ jobs:
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
strategy: strategy:
matrix: matrix:
service: [execute-service, data-service, news-service, api-gateway, dashboard] service: [execute-service, data-service, news-service, api-gateway, dashboard, solana-quant-bot]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -184,6 +184,7 @@ jobs:
kubectl rollout status deployment/news-service -n customer1-staging --timeout=5m kubectl rollout status deployment/news-service -n customer1-staging --timeout=5m
kubectl rollout status deployment/api-gateway -n customer1-staging --timeout=5m kubectl rollout status deployment/api-gateway -n customer1-staging --timeout=5m
kubectl rollout status deployment/dashboard -n customer1-staging --timeout=5m kubectl rollout status deployment/dashboard -n customer1-staging --timeout=5m
kubectl rollout status deployment/solana-quant-bot -n customer1-staging --timeout=5m
deploy-production: deploy-production:
needs: [deploy-staging] needs: [deploy-staging]

View file

@ -19,3 +19,7 @@ data:
# PostgreSQL connection # PostgreSQL connection
POSTGRES_HOST: postgres-primary.{{ .Values.global.namespace }}.svc.cluster.local POSTGRES_HOST: postgres-primary.{{ .Values.global.namespace }}.svc.cluster.local
POSTGRES_PORT: "5432" POSTGRES_PORT: "5432"
# Solana RPC (ExternalName service)
{{- if .Values.solanaRpc.enabled }}
SOLANA_RPC_URL: "http://solana-rpc.{{ .Values.global.namespace }}.svc.cluster.local:{{ .Values.solanaRpc.port }}"
{{- end }}

View file

@ -179,6 +179,9 @@ spec:
- podSelector: - podSelector:
matchLabels: matchLabels:
app: data-service app: data-service
- podSelector:
matchLabels:
app: solana-quant-bot
ports: ports:
- port: 5432 - port: 5432
protocol: TCP protocol: TCP
@ -204,6 +207,9 @@ spec:
- podSelector: - podSelector:
matchLabels: matchLabels:
app: data-service app: data-service
- podSelector:
matchLabels:
app: solana-quant-bot
ports: ports:
- port: 6379 - port: 6379
protocol: TCP protocol: TCP
@ -231,6 +237,9 @@ spec:
- podSelector: - podSelector:
matchLabels: matchLabels:
app: data-service app: data-service
- podSelector:
matchLabels:
app: solana-quant-bot
ports: ports:
- port: 9092 - port: 9092
protocol: TCP protocol: TCP
@ -308,4 +317,93 @@ spec:
protocol: TCP protocol: TCP
--- ---
{{- end }} {{- end }}
# Solana Quant Bot networking
{{- if .Values.solanaQuantBot.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: solana-quant-bot-networking
namespace: {{ .Values.global.namespace }}
spec:
podSelector:
matchLabels:
app: solana-quant-bot
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- port: {{ .Values.solanaQuantBot.port }}
protocol: TCP
- port: {{ .Values.solanaQuantBot.metricsPort }}
protocol: TCP
egress:
# PostgreSQL
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
protocol: TCP
# Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- port: 6379
protocol: TCP
# Kafka
- to:
- podSelector:
matchLabels:
app: kafka
ports:
- port: 9092
protocol: TCP
# Solana RPC (ExternalName — allows outbound to external RPC provider)
- to:
- podSelector:
matchLabels:
app: solana-rpc
ports:
- port: {{ .Values.solanaRpc.port }}
protocol: TCP
# External RPC provider (Helius/QuickNode — outbound HTTPS)
- to: []
ports:
- port: 443
protocol: TCP
- port: 80
protocol: TCP
---
{{- end }}
# Solana RPC networking (ExternalName allows outbound to external provider)
{{- if .Values.solanaRpc.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: solana-rpc-networking
namespace: {{ .Values.global.namespace }}
spec:
podSelector:
matchLabels:
app: solana-rpc
policyTypes:
- Egress
egress:
# Allow outbound HTTPS to external RPC provider
- to: []
ports:
- port: 443
protocol: TCP
- port: 80
protocol: TCP
---
{{- end }}
{{- end }} {{- end }}

View file

@ -0,0 +1,139 @@
{{- if .Values.solanaQuantBot.enabled }}
{{- if .Values.grafana.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.solanaQuantBot.name }}-grafana-dashboard
namespace: {{ .Values.grafana.namespace | default .Values.global.namespace }}
labels:
{{- include "trading-platform.labels" . | nindent 4 }}
app.kubernetes.io/part-of: solana-quant-bot
grafana_dashboard: "1"
data:
solana-quant-bot-dashboard.json: |
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"title": "Bot Overview",
"type": "row",
"gridPos": {"h": 1, "w": 24, "x": 0, "y": 0},
"collapsed": false
},
{
"title": "Pods Running",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 1},
"targets": [
{"expr": "up{job=\"solana-quant-bot\"}", "legendFormat": "{{instance}}"}
],
"fieldConfig": {
"defaults": {
"thresholds": {"steps": [{"color": "red", "value": null}, {"color": "green", "value": 1}]},
"unit": "short"
}
}
},
{
"title": "Total Trades (24h)",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 1},
"targets": [
{"expr": "increase(solana_quant_bot_trades_total[24h])"}
],
"fieldConfig": {"defaults": {"unit": "short"}}
},
{
"title": "Trade Error Rate (5m)",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 12, "y": 1},
"targets": [
{"expr": "sum(rate(solana_quant_bot_trade_errors_total[5m])) / sum(rate(solana_quant_bot_trades_total[5m])) * 100"}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"thresholds": {"steps": [{"color": "green", "value": null}, {"color": "yellow", "value": 3}, {"color": "red", "value": 10}]}
}
}
},
{
"title": "SOL Balance",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 18, "y": 1},
"targets": [
{"expr": "solana_quant_bot_sol_balance"}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"thresholds": {"steps": [{"color": "red", "value": null}, {"color": "orange", "value": 10}, {"color": "green", "value": 25}]}
}
}
},
{
"title": "Trade Volume (USDC)",
"type": "timeseries",
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 5},
"targets": [
{"expr": "sum(rate(solana_quant_bot_trade_volume_usdc[5m])) * 60", "legendFormat": "USDC/min"}
]
},
{
"title": "Signal Processing Latency",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 13},
"targets": [
{"expr": "histogram_quantile(0.50, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p50"},
{"expr": "histogram_quantile(0.95, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p95"},
{"expr": "histogram_quantile(0.99, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le))", "legendFormat": "p99"}
],
"fieldConfig": {"defaults": {"unit": "s"}}
},
{
"title": "Trades per Minute",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 13},
"targets": [
{"expr": "sum(rate(solana_quant_bot_trades_total[5m])) * 60", "legendFormat": "trades/min"}
]
},
{
"title": "Token Score Distribution",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 21},
"targets": [
{"expr": "avg(solana_quant_bot_token_score)", "legendFormat": "avg score"},
{"expr": "max(solana_quant_bot_token_score)", "legendFormat": "max score"}
]
},
{
"title": "CPU / Memory Usage",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 21},
"targets": [
{"expr": "sum(rate(container_cpu_usage_seconds_total{pod=~\"solana-quant-bot-.*\"}[5m])) * 1000", "legendFormat": "CPU (m)"},
{"expr": "sum(container_memory_usage_bytes{pod=~\"solana-quant-bot-.*\"}) / 1024 / 1024", "legendFormat": "Memory (MB)"}
],
"fieldConfig": {"defaults": {"unit": "short"}}
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["solana", "quant-bot", "trading"],
"templating": {"list": []},
"time": {"from": "now-3h", "to": "now"},
"title": "Solana Quant Bot",
"uid": "solana-quant-bot",
"version": 1
}
{{- end }}
{{- end }}

View file

@ -0,0 +1,70 @@
{{- if .Values.solanaQuantBot.enabled }}
{{- if .Values.alertmanager.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ .Values.solanaQuantBot.name }}-alerts
namespace: {{ .Values.global.namespace }}
labels:
{{- include "trading-platform.labels" . | nindent 4 }}
app.kubernetes.io/part-of: solana-quant-bot
release: prometheus
spec:
groups:
- name: solana-quant-bot.rules
rules:
# Alert if bot pod is down
- alert: SolanaQuantBotDown
expr: up{job="{{ .Values.solanaQuantBot.name }}"} == 0
for: 5m
labels:
severity: critical
annotations:
summary: "Solana Quant Bot instance {{ '{{ $instance }}' }} is down"
description: "Bot has been unreachable for more than 5 minutes."
# High error rate on trades
- alert: SolanaQuantBotHighErrorRate
expr: |
sum(rate(solana_quant_bot_trade_errors_total[5m]))
/ sum(rate(solana_quant_bot_trades_total[5m])) > 0.05
for: 10m
labels:
severity: warning
annotations:
summary: "Solana Quant Bot trade error rate is >5%"
description: "Error rate: {{ '{{ $value }}' }} over 5m window."
# No trades executed in a long time (stale bot)
- alert: SolanaQuantBotNoTrades
expr: |
time() - increase(solana_quant_bot_last_trade_timestamp[1h]) > 3600
for: 15m
labels:
severity: warning
annotations:
summary: "Solana Quant Bot has not executed trades in 1 hour"
description: "Bot may be stuck or signals pipeline is blocked."
# High signal processing latency
- alert: SolanaQuantBotHighLatency
expr: |
histogram_quantile(0.95, sum(rate(solana_quant_bot_signal_latency_seconds_bucket[5m])) by (le)) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "Solana Quant Bot p95 signal latency > 2s"
description: "p95 latency: {{ '{{ $value }}' }}s."
# Wallet balance below threshold
- alert: SolanaQuantBotLowBalance
expr: solana_quant_bot_sol_balance < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Solana Quant Bot SOL balance is below 10 SOL"
description: "Current balance: {{ '{{ $value }}' }} SOL."
{{- end }}
{{- end }}

View file

@ -0,0 +1,25 @@
{{- if .Values.solanaQuantBot.enabled }}
{{- if .Values.prometheus.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ .Values.solanaQuantBot.name }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "trading-platform.labels" . | nindent 4 }}
app.kubernetes.io/part-of: solana-quant-bot
release: prometheus
spec:
selector:
matchLabels:
app: {{ .Values.solanaQuantBot.name }}
endpoints:
- port: metrics
path: /metrics
interval: {{ .Values.prometheus.scrapeInterval | default "15s" }}
scrapeTimeout: 10s
namespaceSelector:
matchNames:
- {{ .Values.global.namespace }}
{{- end }}
{{- end }}

View file

@ -0,0 +1,58 @@
{{- if .Values.solanaQuantBot.enabled }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Values.solanaQuantBot.name }}-db-migrate
namespace: {{ .Values.global.namespace }}
labels:
{{- include "trading-platform.labels" . | nindent 4 }}
app: {{ .Values.solanaQuantBot.name }}
app.kubernetes.io/part-of: solana-quant-bot
annotations:
# Prevent re-running on helm upgrade if already succeeded
"helm.sh/hook": post-install,post-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
metadata:
labels:
app: {{ .Values.solanaQuantBot.name }}
app.kubernetes.io/part-of: solana-quant-bot
spec:
serviceAccountName: {{ .Values.solanaQuantBot.name }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: alembic-migrate
image: "{{ .Values.solanaQuantBot.image.repository }}:{{ .Values.solanaQuantBot.image.tag }}"
imagePullPolicy: {{ .Values.solanaQuantBot.image.pullPolicy }}
command: ["alembic"]
args: ["upgrade", "head"]
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: {{ include "trading-platform.fullname" . }}-config
key: POSTGRES_HOST
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: {{ .Values.solanaQuantBot.env.DB_NAME | quote }}
envFrom:
- secretRef:
name: trading-secrets
resources:
{{- toYaml .Values.solanaQuantBot.migrationResources | nindent 12 }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
restartPolicy: Never
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
nodeSelector:
kubernetes.io/os: linux
backoffLimit: 3
activeDeadlineSeconds: 300
{{- end }}

View file

@ -0,0 +1,138 @@
{{- if .Values.solanaQuantBot.enabled }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.solanaQuantBot.name }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "trading-platform.labels" . | nindent 4 }}
app: {{ .Values.solanaQuantBot.name }}
app.kubernetes.io/part-of: solana-quant-bot
spec:
replicas: {{ .Values.solanaQuantBot.replicaCount }}
selector:
matchLabels:
{{- include "trading-platform.selectorLabels" . | nindent 6 }}
app: {{ .Values.solanaQuantBot.name }}
template:
metadata:
labels:
{{- include "trading-platform.selectorLabels" . | nindent 8 }}
app: {{ .Values.solanaQuantBot.name }}
app.kubernetes.io/part-of: solana-quant-bot
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "{{ .Values.solanaQuantBot.metricsPort }}"
prometheus.io/path: "/metrics"
spec:
serviceAccountName: {{ .Values.solanaQuantBot.name }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
initContainers:
# Wait for database migrations to complete
- name: wait-for-migrations
image: "{{ .Values.solanaQuantBot.image.repository }}:{{ .Values.solanaQuantBot.image.tag }}"
imagePullPolicy: {{ .Values.solanaQuantBot.image.pullPolicy }}
command: ['sh', '-c', 'until python -c "import asyncio; from app.db import check_migrations; asyncio.run(check_migrations())"; do echo waiting for migrations; sleep 5; done']
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: {{ include "trading-platform.fullname" . }}-config
key: POSTGRES_HOST
- name: DB_PORT
value: "5432"
- name: DB_NAME
value: {{ .Values.solanaQuantBot.env.DB_NAME | quote }}
envFrom:
- secretRef:
name: trading-secrets
resources:
{{- toYaml .Values.solanaQuantBot.resources | nindent 12 }}
containers:
- name: {{ .Values.solanaQuantBot.name }}
image: "{{ .Values.solanaQuantBot.image.repository }}:{{ .Values.solanaQuantBot.image.tag }}"
imagePullPolicy: {{ .Values.solanaQuantBot.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.solanaQuantBot.port }}
protocol: TCP
- name: metrics
containerPort: {{ .Values.solanaQuantBot.metricsPort }}
protocol: TCP
env:
{{- range $key, $value := .Values.solanaQuantBot.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
# Shared config from ConfigMap
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: {{ include "trading-platform.fullname" . }}-config
key: POSTGRES_HOST
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
name: {{ include "trading-platform.fullname" . }}-config
key: REDIS_HOST
- name: KAFKA_BOOTSTRAP_SERVERS
valueFrom:
configMapKeyRef:
name: {{ include "trading-platform.fullname" . }}-config
key: KAFKA_BOOTSTRAP
- name: SOLANA_RPC_URL
valueFrom:
configMapKeyRef:
name: {{ include "trading-platform.fullname" . }}-config
key: SOLANA_RPC_URL
envFrom:
- secretRef:
name: trading-secrets
- secretRef:
name: solana-quant-bot-secrets
resources:
{{- toYaml .Values.solanaQuantBot.resources | nindent 12 }}
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 30
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumes:
- name: tmp
emptyDir: {}
{{- with .Values.global.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
nodeSelector:
kubernetes.io/os: linux
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- {{ .Values.solanaQuantBot.name }}
topologyKey: kubernetes.io/hostname
{{- end }}

View file

@ -0,0 +1,58 @@
{{- if .Values.solanaQuantBot.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.solanaQuantBot.name }}
namespace: {{ .Values.global.namespace }}
labels:
app: {{ .Values.solanaQuantBot.name }}
app.kubernetes.io/part-of: solana-quant-bot
spec:
type: ClusterIP
ports:
- port: {{ .Values.solanaQuantBot.port }}
targetPort: http
protocol: TCP
name: http
- port: {{ .Values.solanaQuantBot.metricsPort }}
targetPort: metrics
protocol: TCP
name: metrics
selector:
app: {{ .Values.solanaQuantBot.name }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.solanaQuantBot.name }}
namespace: {{ .Values.global.namespace }}
labels:
app: {{ .Values.solanaQuantBot.name }}
app.kubernetes.io/part-of: solana-quant-bot
annotations:
iam.gke.io/gcp-service-account: solana-quant-bot@{{ .Values.global.clusterName }}.iam.gserviceaccount.com
---
# Dedicated secrets for Solana quant bot (wallet keys, RPC auth)
apiVersion: v1
kind: Secret
metadata:
name: solana-quant-bot-secrets
namespace: {{ .Values.global.namespace }}
labels:
{{- include "trading-platform.labels" . | nindent 4 }}
app.kubernetes.io/part-of: solana-quant-bot
annotations:
secrets.yaml.sops.io/v2: encrypted
type: Opaque
stringData:
# Solana wallet private key (base58 encoded)
SOLANA_BOT_PRIVATE_KEY_BASE58: {{ .Values.solanaQuantBot.env.SOLANA_BOT_PRIVATE_KEY_BASE58 | default "<CHANGE_ME_WALLET_KEY>" | quote }}
# Helius/QuickNode API key for RPC
SOLANA_RPC_API_KEY: {{ .Values.solanaQuantBot.env.SOLANA_RPC_API_KEY | default "<CHANGE_ME_RPC_KEY>" | quote }}
# Jupiter API key (optional, for better rate limits)
JUPITER_API_KEY: {{ .Values.solanaQuantBot.env.JUPITER_API_KEY | default "" | quote }}
# Birdeye API key for price data
BIRDEYE_API_KEY: {{ .Values.solanaQuantBot.env.BIRDEYE_API_KEY | default "<CHANGE_ME_BIRDEYE_KEY>" | quote }}
# Dexscreener API key (if using authenticated endpoints)
DEXSCREENER_API_KEY: {{ .Values.solanaQuantBot.env.DEXSCREENER_API_KEY | default "" | quote }}
{{- end }}

View file

@ -0,0 +1,16 @@
{{- if .Values.solanaRpc.enabled }}
# Solana RPC proxy service (Helius/QuickNode endpoint)
# Exposes a stable internal DNS name for Solana RPC access
apiVersion: v1
kind: Service
metadata:
name: {{ .Values.solanaRpc.name }}
namespace: {{ .Values.global.namespace }}
labels:
app: {{ .Values.solanaRpc.name }}
app.kubernetes.io/part-of: solana-quant-bot
spec:
type: ExternalName
externalName: {{ .Values.solanaRpc.externalHost }}
---
{{- end }}

View file

@ -148,7 +148,7 @@ dashboard:
name: dashboard name: dashboard
image: image:
repository: "" repository: ""
tag: "0.1.0" tag: "0.2.0-autonomous-bot"
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
port: 3000 port: 3000
resources: resources:
@ -166,6 +166,8 @@ dashboard:
env: env:
NODE_ENV: "production" NODE_ENV: "production"
NEXT_PUBLIC_API_BASE_URL: "/api" NEXT_PUBLIC_API_BASE_URL: "/api"
NEXT_PUBLIC_EXEC_SERVICE_URL: "http://execute-service:8000"
NEXT_PUBLIC_NEWS_SERVICE_URL: "http://news-service:8001"
# ================================================================= # =================================================================
# Ingress # Ingress
@ -287,6 +289,89 @@ kafka:
- name: news.analysis - name: news.analysis
partitions: 6 partitions: 6
replicationFactor: 3 replicationFactor: 3
# Solana quant bot topics
- name: solana.bot.signals
partitions: 6
replicationFactor: 3
- name: solana.bot.trades
partitions: 6
replicationFactor: 3
- name: solana.bot.alerts
partitions: 3
replicationFactor: 3
- name: solana.token.data
partitions: 6
replicationFactor: 3
- name: solana.pool.data
partitions: 6
replicationFactor: 3
# =================================================================
# Solana RPC (ExternalName service for external RPC providers)
# =================================================================
solanaRpc:
enabled: true
name: solana-rpc
# ExternalName points to external RPC provider (Helius, QuickNode, etc.)
externalName: "mainnet.helius-rpc.com"
port: 443
# env var for internal DNS: http://solana-rpc.<namespace>.svc.cluster.local:443
# =================================================================
# Solana Quant Bot (autonomous trading agent)
# =================================================================
solanaQuantBot:
enabled: true
name: solana-quant-bot
replicaCount: 2
image:
repository: ""
tag: "0.1.0"
pullPolicy: IfNotPresent
port: 8000
metricsPort: 9090
resources:
requests:
cpu: 1
memory: 1Gi
limits:
cpu: "2"
memory: 2Gi
migrationResources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "500m"
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
targetCPUUtilizationPercentage: 70
env:
LOG_LEVEL: "INFO"
DB_NAME: "solana_quant_db"
KAFKA_GROUP_ID: "solana-quant-bot"
KAFKA_TOPIC_SIGNALS: "solana.bot.signals"
KAFKA_TOPIC_TRADES: "solana.bot.trades"
KAFKA_TOPIC_ALERTS: "solana.bot.alerts"
KAFKA_TOPIC_TOKEN_DATA: "solana.token.data"
KAFKA_TOPIC_POOL_DATA: "solana.pool.data"
# Solana RPC via internal DNS (ExternalName service)
SOLANA_RPC_ENDPOINT: "http://solana-rpc:443"
# Trading config
MIN_LIQUIDITY_USD: "50000"
MAX_POSITION_SIZE_USD: "10000"
SLIPPAGE_BPS: "100"
SCORING_THRESHOLD: "60"
HEALTHCHECK_INTERVAL: "300"
# Secrets (override via secret, not here)
SOLANA_BOT_PRIVATE_KEY_BASE58: "<CHANGE_ME>"
SOLANA_RPC_API_KEY: "<CHANGE_ME>"
BIRDEYE_API_KEY: "<CHANGE_ME>"
JUPITER_API_KEY: ""
DEXSCREENER_API_KEY: ""
# ================================================================= # =================================================================
# Network Policies (from security audit) # Network Policies (from security audit)
@ -297,6 +382,27 @@ networkPolicies:
defaultDenyEgress: true defaultDenyEgress: true
allowDNS: true allowDNS: true
# =================================================================
# Observability (Prometheus + Grafana + Alertmanager)
# =================================================================
prometheus:
enabled: true
scrapeInterval: "15s"
retention: "15d"
retentionSize: "10GB"
alertmanager:
enabled: true
# Alert routes configured separately
grafana:
enabled: true
namespace: "" # defaults to global.namespace
adminUser: admin
# adminPassword comes from secret
dashboards:
- solana-quant-bot-dashboard
# ================================================================= # =================================================================
# SOPS Encryption # SOPS Encryption
# ================================================================= # =================================================================

View file

@ -0,0 +1,34 @@
# Solana Quant Bot Funding
Scripts for funding the Solana Quant Bot wallet and verifying balances.
## Initial Funding
```bash
# Dry run first
./fund-bot-wallet.sh --amount 50 --from-wallet <YOUR_WALLET> --namespace customer1 --dry-run
# Execute funding
./fund-bot-wallet.sh --amount 50 --from-wallet <YOUR_WALLET> --namespace customer1
# Verify balance after
./fund-bot-wallet.sh --bot-wallet <BOT_ADDRESS> --verify
```
## Options
| Option | Description |
|--------|-------------|
| `--amount SOL` | Amount in SOL to fund (default: 50) |
| `--from-wallet ADDR` | Source wallet (required for transfers) |
| `--bot-wallet ADDR` | Bot wallet (auto-read from K8s secret if omitted) |
| `--namespace NS` | K8s namespace (default: customer1) |
| `--dry-run` | Simulate without sending |
| `--verify` | Check bot wallet balance |
## Security Notes
- The bot wallet private key is stored as a SOPS-encrypted K8s secret.
- Initial funding should use a dedicated hot wallet, not a multi-sig for simplicity.
- sec-ops may require multi-sig for larger seed amounts.
- Monitor the bot wallet for anomalous transactions via the Prometheus alerts.

View file

@ -0,0 +1,177 @@
#!/usr/bin/env bash
# fund-bot-wallet.sh — Initial funding script for Solana Quant Bot
#
# Usage: ./fund-bot-wallet.sh [OPTIONS]
#
# Options:
# --amount SOL Amount in SOL to fund (default: 50)
# --from-wallet ADDR Source wallet address (required)
# --bot-wallet ADDR Bot wallet address (default: reads from K8s secret)
# --namespace NS K8s namespace (default: customer1)
# --dry-run Simulate transfer without sending
# --verify Verify current bot wallet balance (no transfer)
# --help Show this help
#
# This script:
# 1. Reads the bot's wallet address from the K8s secret
# 2. Sends SOL from --from-wallet to the bot wallet
# 3. Verifies the balance on-chain and in K8s
#
# Prerequisites:
# - solana-cli installed and configured
# - kubectl configured for the cluster
# - --from-wallet must have enough SOL for the transfer + fees
set -euo pipefail
# Defaults
AMOUNT="50"
FROM_WALLET=""
BOT_WALLET=""
NAMESPACE="customer1"
DRY_RUN=false
VERIFY_ONLY=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--amount) AMOUNT="$2"; shift 2 ;;
--from-wallet) FROM_WALLET="$2"; shift 2 ;;
--bot-wallet) BOT_WALLET="$2"; shift 2 ;;
--namespace) NAMESPACE="$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
--verify) VERIFY_ONLY=true; shift ;;
--help)
head -30 "$0" | tail -25
exit 0
;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
# Helper: get bot wallet from K8s secret if not explicitly provided
get_bot_wallet_from_secret() {
if [[ -n "$BOT_WALLET" ]]; then
echo "$BOT_WALLET"
return
fi
echo "Reading bot wallet from K8s secret..."
BOT_WALLET=$(kubectl -n "$NAMESPACE" get secret solana-quant-bot-secrets \
-o jsonpath='{.data.SOLANA_BOT_WALLET_ADDRESS}' 2>/dev/null | base64 -d 2>/dev/null || true)
if [[ -z "$BOT_WALLET" ]]; then
echo "ERROR: Could not read bot wallet from secret solana-quant-bot-secrets in namespace $NAMESPACE"
echo "Either deploy the secret first or pass --bot-wallet explicitly."
exit 1
fi
echo "Bot wallet: $BOT_WALLET"
}
# Verify balance
verify_balance() {
local wallet="$1"
echo "=== Verifying balance for $wallet ==="
# On-chain balance
local on_chain_balance
on_chain_balance=$(solana balance "$wallet" --output json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin))" 2>/dev/null || echo "0")
echo "On-chain SOL balance: $on_chain_balance"
# Expected minimum
local expected
expected=$(echo "$AMOUNT * 0.99" | bc) # account for fees
local is_ok
is_ok=$(echo "$on_chain_balance >= $expected" | bc -l)
if [[ "$is_ok" -eq 1 ]]; then
echo "OK: Balance >= $expected SOL (transferred amount minus fees)"
else
echo "WARNING: Balance ($on_chain_balance) < expected ($expected SOL)"
echo " The transfer may still be pending confirmation."
fi
}
# --- Main ---
echo "============================================"
echo " Solana Quant Bot — Initial Funding"
echo "============================================"
echo " Amount: $AMOUNT SOL"
echo " Namespace: $NAMESPACE"
echo " Dry run: $DRY_RUN"
echo "============================================"
# Validate from-wallet
if [[ -z "$FROM_WALLET" ]]; then
echo "ERROR: --from-wallet is required (source wallet address)"
exit 1
fi
# Get bot wallet
get_bot_wallet_from_secret
# Verify mode
if [[ "$VERIFY_ONLY" == true ]]; then
verify_balance "$BOT_WALLET"
exit 0
fi
# Dry run
if [[ "$DRY_RUN" == true ]]; then
echo ""
echo "DRY RUN — Would transfer $AMOUNT SOL from:"
echo " $FROM_WALLET -> $BOT_WALLET"
echo ""
echo "Source wallet balance:"
solana balance "$FROM_WALLET"
echo ""
echo "Bot wallet balance:"
solana balance "$BOT_WALLET"
echo ""
echo "To execute, remove --dry-run."
exit 0
fi
# Check source wallet has enough
echo "Checking source wallet balance..."
SOURCE_BALANCE=$(solana balance "$FROM_WALLET" --output json 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin))" 2>/dev/null || echo "0")
echo "Source wallet: $SOURCE_BALANCE SOL"
HAS_ENOUGH=$(echo "$SOURCE_BALANCE >= $AMOUNT" | bc -l)
if [[ "$HAS_ENOUGH" -ne 1 ]]; then
echo "ERROR: Source wallet ($SOURCE_BALANCE SOL) has less than $AMOUNT SOL"
exit 1
fi
# Confirm before sending (unless CI)
if [[ "${CI:-false}" != "true" ]]; then
echo ""
read -p "Send $AMOUNT SOL from $FROM_WALLET to $BOT_WALLET? (yes/no): " CONFIRM
if [[ "$CONFIRM" != "yes" ]]; then
echo "Aborted."
exit 0
fi
fi
# Send transaction
echo "Sending transaction..."
TX_SIGNATURE=$(solana transfer "$FROM_WALLET" \
"$BOT_WALLET" \
"$AMOUNT" \
--with-block-time \
--output json 2>&1 | tee /tmp/solana-fund-tx.json)
echo "Transaction: $TX_SIGNATURE"
# Wait for confirmation
echo "Waiting for confirmation..."
solana confirm "$(echo "$TX_SIGNATURE" | python3 -c "import sys,json; print(json.load(sys.stdin)['signature'])")" --with-max-confirm-retries 20
# Verify balance
echo ""
verify_balance "$BOT_WALLET"
echo ""
echo "Funding complete. Bot wallet: $BOT_WALLET"
echo "Transferred: $AMOUNT SOL"