mirror of
https://github.com/luxfi/wallet.git
synced 2026-10-30 15:13:19 +00:00
116 lines
3.2 KiB
Go
116 lines
3.2 KiB
Go
// wallet-backend — the canonical App(Wallet) custody server.
|
|
//
|
|
// Config is env-only (12-factor); secrets arrive from KMS via the operator
|
|
// Service CR, never hardcoded. White-label is per-tenant: IAM_ISSUER (lux →
|
|
// lux.id) and MPC_ENDPOINT (M-Chain by default) are injected, so the same
|
|
// binary serves any brand against their own IAM + MPC.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/luxfi/wallet/backend/internal/api"
|
|
"github.com/luxfi/wallet/backend/internal/custody"
|
|
"github.com/luxfi/wallet/backend/internal/iam"
|
|
"github.com/luxfi/wallet/backend/internal/store"
|
|
)
|
|
|
|
func main() {
|
|
log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
|
|
|
cfg := loadConfig()
|
|
if err := run(cfg, log); err != nil {
|
|
log.Error("fatal", "err", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
type config struct {
|
|
addr string
|
|
iamIssuer string
|
|
iamAudience string
|
|
mpcEndpoint string
|
|
mpcToken string
|
|
}
|
|
|
|
func loadConfig() config {
|
|
return config{
|
|
addr: env("LISTEN_ADDR", ":8080"),
|
|
iamIssuer: env("IAM_ISSUER", "https://lux.id"),
|
|
iamAudience: env("IAM_AUDIENCE", "lux-wallet"),
|
|
// M-Chain, which is where threshold custody lives. The ceremony runs
|
|
// natively across the chain's own validators — leaderless, the
|
|
// committee IS the validator set — rather than on a REST cluster of
|
|
// its own. mpcvm/transport.go calls that cluster "the old off-chain
|
|
// REST cluster" in as many words; M-Chain is what replaced it.
|
|
//
|
|
// MPC_ENDPOINT still overrides, because a tenant running centralized
|
|
// or bring-your-own MPC points at mpc.lux.cloud instead. That is the
|
|
// exception, and it is now the one that has to be asked for.
|
|
mpcEndpoint: env("MPC_ENDPOINT", "https://api.lux.network/v1/chain/M"),
|
|
mpcToken: os.Getenv("MPC_SERVICE_TOKEN"),
|
|
}
|
|
}
|
|
|
|
func run(cfg config, log *slog.Logger) error {
|
|
hc := &http.Client{Timeout: 15 * time.Second}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
verifier, err := iam.New(ctx, cfg.iamIssuer, cfg.iamAudience, hc)
|
|
cancel()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
custodian, err := custody.NewMPC(custody.MPCConfig{
|
|
Endpoint: cfg.mpcEndpoint,
|
|
ServiceToken: cfg.mpcToken,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
srv := &http.Server{
|
|
Addr: cfg.addr,
|
|
Handler: api.New(custodian, store.NewMemory(), verifier, log).Handler(),
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
WriteTimeout: 120 * time.Second, // MPC keygen can take ~60s
|
|
}
|
|
|
|
// Graceful shutdown on SIGINT/SIGTERM.
|
|
idle := make(chan struct{})
|
|
go func() {
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
|
<-sig
|
|
log.Info("shutting down")
|
|
sctx, scancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer scancel()
|
|
if err := srv.Shutdown(sctx); err != nil {
|
|
log.Error("shutdown", "err", err)
|
|
}
|
|
close(idle)
|
|
}()
|
|
|
|
log.Info("wallet-backend listening", "addr", cfg.addr, "iam", cfg.iamIssuer, "mpc", cfg.mpcEndpoint)
|
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
return err
|
|
}
|
|
<-idle
|
|
return nil
|
|
}
|
|
|
|
func env(k, def string) string {
|
|
if v := os.Getenv(k); v != "" {
|
|
return v
|
|
}
|
|
return def
|
|
}
|