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:
parent
99a864171b
commit
da8fe22f6e
13 changed files with 927 additions and 3 deletions
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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 }}
|
||||||
|
|
@ -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
|
||||||
# =================================================================
|
# =================================================================
|
||||||
|
|
|
||||||
34
trading-platform/scripts/solana-funding/README.md
Normal file
34
trading-platform/scripts/solana-funding/README.md
Normal 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.
|
||||||
177
trading-platform/scripts/solana-funding/fund-bot-wallet.sh
Executable file
177
trading-platform/scripts/solana-funding/fund-bot-wallet.sh
Executable 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"
|
||||||
Loading…
Add table
Reference in a new issue