Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions k8s/base/dca/server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ spec:
value: "8088"
- name: TASK_QUEUE_NAME
value: "dca_plugin_queue"
- name: SERVER_TASK_QUEUE_NAME
value: "dca_plugin_queue"
- name: SERVER_HOST
value: "0.0.0.0"
- name: SERVER_PORT
Expand Down Expand Up @@ -157,6 +159,8 @@ spec:
value: "8089"
- name: TASK_QUEUE_NAME
value: "dca_plugin_queue"
- name: SERVER_TASK_QUEUE_NAME
value: "dca_plugin_queue"
- name: SERVER_HOST
value: "0.0.0.0"
- name: SERVER_PORT
Expand Down
30 changes: 26 additions & 4 deletions k8s/overlays/production/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ patchesStrategicMerge:
# Image configuration for production
# Override base images with proper registry and tags
images:
# DCA Plugin (app-recurring) v1.0.82
# DCA Plugin (app-recurring) - server v1.0.84 has TaskQueueName fix
- name: docker.io/library/app-recurring-server:local-amd64
newName: ghcr.io/vultisig/app-recurring/server
newTag: v1.0.82
newTag: v1.0.84
- name: docker.io/library/app-recurring-worker:local-amd64
newName: ghcr.io/vultisig/app-recurring/worker
newTag: v1.0.82
Expand All @@ -40,10 +40,10 @@ images:
- name: docker.io/library/vultisig-tx-indexer:local-amd64
newName: ghcr.io/vultisig/verifier/tx_indexer
newTag: v0.1.16
# VCLI v1.0.1 - fix: GetPluginServerURL reads env vars before defaults
# VCLI v1.0.3 - fix: billing fetch + shared libraries included in image
- name: docker.io/library/vcli:local-amd64
newName: ghcr.io/vultisig/vcli
newTag: v1.0.1
newTag: v1.0.3

# Patch deployments for production GHCR images
patches:
Expand Down Expand Up @@ -104,3 +104,25 @@ patches:
kind: Deployment
name: tx-indexer
namespace: verifier
# Remove nodeSelector constraints from infra (allows any region)
- patch: |-
- op: remove
path: /spec/template/spec/nodeSelector
target:
kind: StatefulSet
name: postgres
namespace: infra
- patch: |-
- op: remove
path: /spec/template/spec/nodeSelector
target:
kind: StatefulSet
name: redis
namespace: infra
- patch: |-
- op: remove
path: /spec/template/spec/nodeSelector
target:
kind: StatefulSet
name: minio
namespace: infra
30 changes: 19 additions & 11 deletions local/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
# Build stage
FROM golang:1.23-alpine AS builder
# Build stage - use Debian for glibc compatibility with go-wrappers
FROM golang:1.25-bookworm AS builder

RUN apk add --no-cache git
ARG TARGETARCH=amd64

RUN apt-get update && apt-get install -y git build-essential && rm -rf /var/lib/apt/lists/*

WORKDIR /build

# Copy go mod files
COPY go.mod go.sum ./

# Remove local replace directives for Docker build (use remote versions)
RUN sed -i '/^replace/,/^)/d' go.mod && \
sed -i '/replace.*=>.*\.\.\//d' go.mod
# Remove only local path replace directives (keep version fixes)
RUN sed -i '/=> *\.\./d' go.mod

# Download dependencies
RUN go mod download

# Copy source code
COPY cmd/ cmd/

# Build binary
RUN CGO_ENABLED=0 GOOS=linux go build -o vcli ./cmd/vcli
# Build binary with CGO enabled for crypto libs
RUN CGO_ENABLED=1 GOOS=linux GOARCH=${TARGETARCH} go build -o vcli ./cmd/vcli

# Find and copy shared libraries (from includes/linux/ subdirectory)
RUN mkdir -p /build/libs && \
find /go/pkg/mod/github.com/vultisig/go-wrappers*/includes/linux -name "*.so" -exec cp {} /build/libs/ \;

# Runtime stage
FROM alpine:3.19
# Runtime stage - use Debian slim for glibc compatibility
FROM debian:bookworm-slim

RUN apk add --no-cache ca-certificates
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy shared libraries
COPY --from=builder /build/libs/*.so /usr/lib/

# Copy binary from builder
COPY --from=builder /build/vcli /usr/local/bin/vcli

Expand Down
97 changes: 82 additions & 15 deletions local/cmd/vcli/cmd/policy_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,22 +123,17 @@ func runPolicyGenerate(pluginID, from, to, amount, frequency, vaultName, toVault
return fmt.Errorf("recipe validation failed: %w", err)
}

// Include default free billing entries (required by DCA plugin)
// Both "once" and "per-tx" types are needed to match plugin pricing
// Fetch plugin pricing to build matching billing entries
billing, err := fetchPluginBilling(pluginID)
if err != nil {
// If we can't fetch pricing, use empty billing (plugin may not have pricing)
billing = []map[string]any{}
}

// Build policy with recipe and billing
policy := map[string]any{
"recipe": recipe,
"billing": []map[string]any{
{
"type": "once",
"amount": "0",
"asset": "usdc",
},
{
"type": "per-tx",
"amount": "0",
"asset": "usdc",
},
},
"recipe": recipe,
"billing": billing,
}

jsonBytes, err := json.MarshalIndent(policy, "", " ")
Expand Down Expand Up @@ -234,3 +229,75 @@ func extractErrorMessage(body []byte) string {
}
return string(body)
}

// fetchPluginBilling fetches the plugin's pricing and converts it to billing entries.
// The billing entries must match the plugin's pricing count for policy creation to succeed.
// Uses uint64 for amount to match verifier's expected type.
// Uses the public /plugins endpoint (no auth required) instead of /plugin/{id} (requires auth).
func fetchPluginBilling(pluginID string) ([]map[string]any, error) {
cfg, err := LoadConfig()
if err != nil {
return nil, err
}

resolvedID := ResolvePluginID(pluginID)

url := fmt.Sprintf("%s/plugins", cfg.Verifier)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to fetch plugins: status %d", resp.StatusCode)
}

var result struct {
Data struct {
Plugins []struct {
ID string `json:"id"`
Pricing []struct {
Type string `json:"type"`
Frequency *string `json:"frequency"`
Amount int64 `json:"amount"`
Asset string `json:"asset"`
} `json:"pricing"`
} `json:"plugins"`
} `json:"data"`
}

err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return nil, err
}

var targetPricing []struct {
Type string `json:"type"`
Frequency *string `json:"frequency"`
Amount int64 `json:"amount"`
Asset string `json:"asset"`
}
for _, p := range result.Data.Plugins {
if p.ID == resolvedID {
targetPricing = p.Pricing
break
}
}

var billing []map[string]any
for _, p := range targetPricing {
frequency := ""
if p.Frequency != nil {
frequency = *p.Frequency
}
entry := map[string]any{
"type": p.Type,
"frequency": frequency,
"amount": uint64(p.Amount),
}
billing = append(billing, entry)
}

return billing, nil
}
2 changes: 1 addition & 1 deletion local/scripts/run-services.sh
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ cd "$ROOT_DIR/app-recurring"
export MODE="swap"
export SERVER_PORT="8082"
export SERVER_HOST="0.0.0.0"
export SERVER_TASKQUEUENAME="dca_plugin_queue"
export TASK_QUEUE_NAME="dca_plugin_queue"
export SERVER_ENCRYPTIONSECRET="dev-encryption-secret-32b"
export POSTGRES_DSN="postgres://vultisig:vultisig@localhost:5432/vultisig-dca?sslmode=disable"
export REDIS_URI="redis://:vultisig@localhost:6379"
Expand Down
Loading