mirror of
https://github.com/luxfi/docs.git
synced 2026-09-24 00:25:32 +00:00
docs: add regulated products guides -- ATS, BD, TA from luxfi OSS components
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
"cex",
|
||||
"broker",
|
||||
"compliance",
|
||||
"regulated",
|
||||
"---Cross-Chain---",
|
||||
"warp",
|
||||
"teleport",
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
---
|
||||
title: Alternative Trading System (ATS)
|
||||
description: Build an SEC-registered ATS with CLOB matching, compliance, and multi-venue routing
|
||||
icon: Building2
|
||||
---
|
||||
|
||||
An Alternative Trading System (ATS) is an SEC-registered trading venue that matches buyer and seller orders internally. Unlike a national securities exchange, an ATS operates under Regulation ATS (Rules 300-303) and files Form ATS-N. It must maintain at least $250,000 in net capital and comply with fair access, order display, and record-keeping obligations.
|
||||
|
||||
The Lux stack provides every technical component needed to build and operate an ATS.
|
||||
|
||||
## Architecture
|
||||
|
||||
An ATS uses all four core modules:
|
||||
|
||||
```
|
||||
┌────────────────────┐
|
||||
│ atsd │
|
||||
│ :8080 │
|
||||
└─────────┬──────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
|
||||
│ Broker │ │ CEX Engine │ │ Compliance │
|
||||
│ Providers │ │ CLOB Match │ │ KYC / AML │
|
||||
└──────┬──────┘ └──────┬──────┘ └─────────────┘
|
||||
│ │
|
||||
16 external venues Internal order book
|
||||
```
|
||||
|
||||
- **Broker** provides external venue connectivity for routing unmatched orders and sourcing liquidity
|
||||
- **CEX Engine** runs the central limit order book that matches orders internally
|
||||
- **Compliance** screens every participant (KYC) and every transaction (AML) before execution
|
||||
|
||||
## Step-by-Step Build
|
||||
|
||||
### 1. Create Go project
|
||||
|
||||
```bash
|
||||
mkdir atsd && cd atsd
|
||||
go mod init github.com/yourorg/atsd
|
||||
go get github.com/hanzoai/base
|
||||
go get github.com/luxfi/broker
|
||||
go get github.com/luxfi/cex
|
||||
go get github.com/luxfi/compliance
|
||||
```
|
||||
|
||||
### 2. Register providers from environment
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/broker/pkg/provider"
|
||||
"github.com/luxfi/broker/pkg/provider/envconfig"
|
||||
)
|
||||
|
||||
registry := provider.NewRegistry()
|
||||
n := envconfig.RegisterFromEnv(registry)
|
||||
slog.Info("providers ready", "count", n)
|
||||
```
|
||||
|
||||
`RegisterFromEnv` reads environment variables for all 16 providers and registers whichever ones have credentials set. See [Provider Integration](/docs/regulated/providers) for the full list.
|
||||
|
||||
### 3. Create the broker server
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/broker/pkg/api"
|
||||
|
||||
brokerSrv := api.NewServer(api.Config{
|
||||
Registry: registry,
|
||||
ListenAddr: ":8090",
|
||||
})
|
||||
```
|
||||
|
||||
### 4. Create the CEX matching engine
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/cex/pkg/engine"
|
||||
|
||||
eng := engine.New(engine.Config{
|
||||
EnableSurveillance: true,
|
||||
EnableAudit: true,
|
||||
})
|
||||
```
|
||||
|
||||
### 5. Register markets
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/cex/pkg/markets"
|
||||
|
||||
// Auto-discover tradable assets from all connected providers
|
||||
markets.RegisterFromProviders(ctx, registry, eng)
|
||||
|
||||
// Add explicit default markets with custom fee schedules
|
||||
markets.RegisterDefaults(eng, []markets.DefaultMarket{
|
||||
{Symbol: "BTC-USD", Base: "BTC", Quote: "USD", Class: types.AssetClassCrypto,
|
||||
Tick: "0.01", Lot: "0.00001", MakerFee: "0.001", TakerFee: "0.002"},
|
||||
{Symbol: "ETH-USD", Base: "ETH", Quote: "USD", Class: types.AssetClassCrypto,
|
||||
Tick: "0.01", Lot: "0.0001", MakerFee: "0.001", TakerFee: "0.002"},
|
||||
})
|
||||
```
|
||||
|
||||
`RegisterFromProviders` queries every registered provider for tradable assets and adds them to the matching engine. `RegisterDefaults` adds specific markets with explicit parameters (tick size, lot size, fees).
|
||||
|
||||
### 6. Install compliance rules
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
"github.com/luxfi/compliance/pkg/jube"
|
||||
)
|
||||
|
||||
// Install FinCEN-mandated baseline rules
|
||||
monitoringSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monitoringSvc)
|
||||
|
||||
// Optional: connect Jube for ML-based transaction scoring
|
||||
jubeClient := jube.NewClient(jube.Config{
|
||||
BaseURL: os.Getenv("JUBE_BASE_URL"),
|
||||
ModelID: os.Getenv("JUBE_MODEL_ID"),
|
||||
FailOpen: false, // reject transactions when Jube is unreachable
|
||||
Timeout: 5 * time.Second,
|
||||
})
|
||||
```
|
||||
|
||||
`InstallDefaultRules` adds three mandatory FinCEN rules:
|
||||
- **CTR threshold**: flag transactions over $10,000 (31 CFR 1010.311)
|
||||
- **Structuring**: detect $9,000-$9,999 patterns (31 USC 5324)
|
||||
- **Velocity**: block if 24h volume exceeds $50,000 without enhanced KYC
|
||||
|
||||
### 7. Mount and serve
|
||||
|
||||
```go
|
||||
import "github.com/hanzoai/base"
|
||||
|
||||
srv := base.NewServer(base.Config{
|
||||
Addr: ":8080",
|
||||
})
|
||||
srv.Mount("/broker", brokerSrv.Handler())
|
||||
srv.Mount("/exchange", cexGateway.Handler())
|
||||
srv.Mount("/compliance", complianceSrv.Handler())
|
||||
srv.ListenAndServe()
|
||||
```
|
||||
|
||||
## Minimal ATS main.go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/base"
|
||||
"github.com/luxfi/broker/pkg/api"
|
||||
"github.com/luxfi/broker/pkg/provider"
|
||||
"github.com/luxfi/broker/pkg/provider/envconfig"
|
||||
"github.com/luxfi/cex/pkg/engine"
|
||||
"github.com/luxfi/cex/pkg/gateway"
|
||||
"github.com/luxfi/cex/pkg/markets"
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
"github.com/luxfi/compliance/pkg/jube"
|
||||
compapi "github.com/luxfi/compliance/pkg/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
|
||||
// 1. Providers
|
||||
registry := provider.NewRegistry()
|
||||
n := envconfig.RegisterFromEnv(registry)
|
||||
slog.Info("providers registered", "count", n)
|
||||
|
||||
// 2. CEX matching engine
|
||||
eng := engine.New(engine.Config{EnableSurveillance: true, EnableAudit: true})
|
||||
markets.RegisterFromProviders(ctx, registry, eng)
|
||||
|
||||
// 3. Compliance
|
||||
monSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monSvc)
|
||||
|
||||
if url := os.Getenv("JUBE_BASE_URL"); url != "" {
|
||||
jc := jube.NewClient(jube.Config{
|
||||
BaseURL: url, ModelID: os.Getenv("JUBE_MODEL_ID"),
|
||||
FailOpen: false, Timeout: 5 * time.Second,
|
||||
})
|
||||
monSvc.SetExternalScorer(jc)
|
||||
}
|
||||
|
||||
// 4. HTTP servers
|
||||
brokerSrv := api.NewServer(api.Config{Registry: registry})
|
||||
cexGw := gateway.New(gateway.Config{Engine: eng})
|
||||
compSrv := compapi.NewServer(compapi.Config{Monitoring: monSvc})
|
||||
|
||||
// 5. Mount on base and serve
|
||||
srv := base.NewServer(base.Config{Addr: ":8080"})
|
||||
srv.Mount("/broker", brokerSrv.Handler())
|
||||
srv.Mount("/exchange", cexGw.Handler())
|
||||
srv.Mount("/compliance", compSrv.Handler())
|
||||
|
||||
slog.Info("ATS starting", "addr", ":8080", "providers", n)
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
slog.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| At least one provider key pair | Yes | See [Provider Integration](/docs/regulated/providers) |
|
||||
| `JUBE_BASE_URL` | No | Jube transaction monitoring endpoint |
|
||||
| `JUBE_MODEL_ID` | No | Jube EntityAnalysisModel GUID |
|
||||
| `COMPLIANCE_API_KEY` | Yes | API key for compliance endpoints |
|
||||
| `JUMIO_API_TOKEN` | Recommended | Jumio IDV for subscriber KYC |
|
||||
| `BROKER_API_KEY` | Yes | API key for broker endpoints |
|
||||
|
||||
## SEC Compliance Checklist
|
||||
|
||||
An ATS operating under Regulation ATS must:
|
||||
|
||||
- [ ] File Form ATS-N with the SEC before commencing operations
|
||||
- [ ] Register as a broker-dealer (Form BD) with FINRA
|
||||
- [ ] Maintain minimum $250,000 net capital (Rule 15c3-1)
|
||||
- [ ] Implement fair access policies (Rule 301(b)(5)) if volume exceeds 5%
|
||||
- [ ] Display best-priced orders (Rule 301(b)(3)) if volume exceeds 5%
|
||||
- [ ] Maintain records of all orders, executions, and cancellations
|
||||
- [ ] File quarterly reports on Form ATS-R
|
||||
- [ ] Implement and test business continuity / disaster recovery plans
|
||||
- [ ] Screen all subscribers via KYC/AML before granting access
|
||||
- [ ] Monitor all transactions for CTR, structuring, and suspicious activity
|
||||
- [ ] File SARs within 30 days of detection
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 -t ghcr.io/yourorg/atsd:latest .
|
||||
docker run -p 8080:8080 \
|
||||
-e ALPACA_API_KEY=pk_... \
|
||||
-e ALPACA_API_SECRET=sk_... \
|
||||
-e COMPLIANCE_API_KEY=... \
|
||||
-e JUMIO_API_TOKEN=... \
|
||||
-e JUMIO_API_SECRET=... \
|
||||
ghcr.io/yourorg/atsd:latest
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
See [luxfi/ats](https://github.com/luxfi/ats) for a working ATS implementation built on these components.
|
||||
@@ -0,0 +1,214 @@
|
||||
---
|
||||
title: Broker-Dealer (BD)
|
||||
description: Build a FINRA-registered broker-dealer with multi-venue order routing
|
||||
icon: ArrowLeftRight
|
||||
---
|
||||
|
||||
A Broker-Dealer (BD) is a firm registered with the SEC and FINRA that executes securities transactions on behalf of customers. Unlike an ATS, a BD does not operate an internal matching engine -- it routes customer orders to external venues (exchanges, ATSs, market makers) and earns commissions or markups. A BD must maintain at least $250,000 in net capital under Rule 15c3-1 and join SIPC for customer asset protection.
|
||||
|
||||
The Lux stack provides broker connectivity, smart order routing, risk management, and compliance -- everything a BD needs except the internal matching engine.
|
||||
|
||||
## Architecture
|
||||
|
||||
A BD uses Broker and Compliance but not CEX:
|
||||
|
||||
```
|
||||
┌────────────────────┐
|
||||
│ bdd │
|
||||
│ :8080 │
|
||||
└─────────┬──────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────┐
|
||||
│ │ │
|
||||
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
|
||||
│ Broker │ │ Risk │ │ Compliance │
|
||||
│ 16 Venues │ │ Engine │ │ KYC / AML │
|
||||
└──────┬──────┘ └────────────┘ └─────────────┘
|
||||
│
|
||||
┌──────┴──────┐
|
||||
│ Smart Order │
|
||||
│ Router │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
The key difference from an ATS: orders always route to external venues. There is no internal order book. The Smart Order Router selects the best venue based on net price (spread + fees) and can split large orders across multiple venues using VWAP.
|
||||
|
||||
## Step-by-Step Build
|
||||
|
||||
### 1. Create Go project
|
||||
|
||||
```bash
|
||||
mkdir bdd && cd bdd
|
||||
go mod init github.com/yourorg/bdd
|
||||
go get github.com/hanzoai/base
|
||||
go get github.com/luxfi/broker
|
||||
go get github.com/luxfi/compliance
|
||||
```
|
||||
|
||||
### 2. Register providers
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/broker/pkg/provider"
|
||||
"github.com/luxfi/broker/pkg/provider/envconfig"
|
||||
)
|
||||
|
||||
registry := provider.NewRegistry()
|
||||
n := envconfig.RegisterFromEnv(registry)
|
||||
```
|
||||
|
||||
A BD typically connects to multiple venues for best execution. At minimum, register one equities provider (Alpaca, IBKR, or Tradier) and one crypto provider if offering digital assets.
|
||||
|
||||
### 3. Create broker with smart order routing
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/broker/pkg/api"
|
||||
|
||||
brokerSrv := api.NewServer(api.Config{
|
||||
Registry: registry,
|
||||
EnableSOR: true, // smart order routing
|
||||
EnableRisk: true, // pre-trade risk checks
|
||||
EnableSettlement: true, // prefunding pool for instant buys
|
||||
EnableAudit: true, // immutable audit trail
|
||||
})
|
||||
```
|
||||
|
||||
### 4. Install compliance
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
"github.com/luxfi/compliance/pkg/kyc"
|
||||
"github.com/luxfi/compliance/pkg/idv"
|
||||
)
|
||||
|
||||
// KYC service with IDV provider
|
||||
kycSvc := kyc.NewService()
|
||||
kycSvc.RegisterProvider(idv.NewJumio(idv.JumioConfig{
|
||||
APIToken: os.Getenv("JUMIO_API_TOKEN"),
|
||||
APISecret: os.Getenv("JUMIO_API_SECRET"),
|
||||
}))
|
||||
|
||||
// AML monitoring with FinCEN defaults
|
||||
monSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monSvc)
|
||||
```
|
||||
|
||||
### 5. Mount and serve
|
||||
|
||||
```go
|
||||
import "github.com/hanzoai/base"
|
||||
|
||||
srv := base.NewServer(base.Config{Addr: ":8080"})
|
||||
srv.Mount("/broker", brokerSrv.Handler())
|
||||
srv.Mount("/compliance", compSrv.Handler())
|
||||
|
||||
slog.Info("BD starting", "addr", ":8080", "providers", n)
|
||||
srv.ListenAndServe()
|
||||
```
|
||||
|
||||
## Minimal BD main.go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/hanzoai/base"
|
||||
"github.com/luxfi/broker/pkg/api"
|
||||
"github.com/luxfi/broker/pkg/provider"
|
||||
"github.com/luxfi/broker/pkg/provider/envconfig"
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
compapi "github.com/luxfi/compliance/pkg/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. Providers
|
||||
registry := provider.NewRegistry()
|
||||
n := envconfig.RegisterFromEnv(registry)
|
||||
slog.Info("providers registered", "count", n)
|
||||
|
||||
// 2. Broker (SOR, risk, audit -- no matching engine)
|
||||
brokerSrv := api.NewServer(api.Config{
|
||||
Registry: registry,
|
||||
EnableSOR: true,
|
||||
EnableRisk: true,
|
||||
EnableAudit: true,
|
||||
})
|
||||
|
||||
// 3. Compliance
|
||||
monSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monSvc)
|
||||
compSrv := compapi.NewServer(compapi.Config{Monitoring: monSvc})
|
||||
|
||||
// 4. Serve
|
||||
srv := base.NewServer(base.Config{Addr: ":8080"})
|
||||
srv.Mount("/broker", brokerSrv.Handler())
|
||||
srv.Mount("/compliance", compSrv.Handler())
|
||||
|
||||
slog.Info("BD starting", "addr", ":8080", "providers", n)
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
slog.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key Difference from ATS
|
||||
|
||||
| Aspect | ATS | BD |
|
||||
|--------|-----|-----|
|
||||
| Internal matching | Yes (CLOB engine) | No |
|
||||
| Order routing | Internal first, then external | External only |
|
||||
| `luxfi/cex` dependency | Required | Not used |
|
||||
| Registration | Form ATS-N + Form BD | Form BD only |
|
||||
| Fair access rules | Yes (if >5% volume) | No |
|
||||
| Best execution | Rule 5310 | Rule 5310 |
|
||||
|
||||
A BD has simpler technology requirements but the same compliance obligations for KYC, AML, and transaction monitoring.
|
||||
|
||||
## FINRA Compliance
|
||||
|
||||
A BD operating under SEC/FINRA rules must:
|
||||
|
||||
- [ ] Register with SEC on Form BD and join FINRA
|
||||
- [ ] Join SIPC (Securities Investor Protection Corporation)
|
||||
- [ ] Maintain $250,000 minimum net capital (Rule 15c3-1)
|
||||
- [ ] Satisfy best execution obligations (Rule 5310) -- document venue selection rationale
|
||||
- [ ] Perform suitability analysis for customer recommendations (Rule 2111 / Reg BI)
|
||||
- [ ] Maintain books and records (Rule 17a-3, 17a-4)
|
||||
- [ ] File FOCUS reports quarterly
|
||||
- [ ] Screen all customers via KYC/AML before account opening
|
||||
- [ ] Monitor transactions for CTR, structuring, and suspicious activity
|
||||
- [ ] File SARs within 30 days of detection
|
||||
- [ ] Implement supervisory procedures (Rule 3110)
|
||||
- [ ] Designate a Chief Compliance Officer
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| At least one provider key pair | Yes | See [Provider Integration](/docs/regulated/providers) |
|
||||
| `BROKER_API_KEY` | Yes | API key for broker endpoints |
|
||||
| `COMPLIANCE_API_KEY` | Yes | API key for compliance endpoints |
|
||||
| `JUMIO_API_TOKEN` | Recommended | Jumio IDV for customer KYC |
|
||||
| `JUMIO_API_SECRET` | Recommended | Jumio API secret |
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 -t ghcr.io/yourorg/bdd:latest .
|
||||
docker run -p 8080:8080 \
|
||||
-e ALPACA_API_KEY=pk_... \
|
||||
-e ALPACA_API_SECRET=sk_... \
|
||||
-e IBKR_ACCESS_TOKEN=... \
|
||||
-e BROKER_API_KEY=... \
|
||||
-e COMPLIANCE_API_KEY=... \
|
||||
ghcr.io/yourorg/bdd:latest
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
See [luxfi/bd](https://github.com/luxfi/bd) for a working broker-dealer implementation built on these components.
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
title: Compliance Integration
|
||||
description: KYC, AML, sanctions screening, and regulatory frameworks
|
||||
icon: ShieldCheck
|
||||
---
|
||||
|
||||
The `luxfi/compliance` module provides a complete regulated financial compliance stack: identity verification, KYC orchestration, AML/sanctions screening, transaction monitoring, payment compliance, and multi-jurisdiction regulatory frameworks. Zero external dependencies -- standard library only.
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ complianced │
|
||||
│ :8091 │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌────────────────┼────────────────┐
|
||||
│ │ │
|
||||
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
|
||||
│ IDV │ │ AML │ │ Payments │
|
||||
│ Providers │ │ Screening │ │Compliance │
|
||||
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
|
||||
│ │ │
|
||||
┌────┼────┐ ┌───┼───┐ ┌───┼───┐
|
||||
│ │ │ │ │ │ │ │ │
|
||||
Jumio Onfido Plaid OFAC EU PEP Travel CTR Stablecoin
|
||||
SDN HMT Rule Validation
|
||||
```
|
||||
|
||||
## Identity Verification Providers
|
||||
|
||||
Three IDV providers are supported out of the box. Each implements the same provider interface: initiate verification, check status, and parse webhooks.
|
||||
|
||||
| Provider | API | Features | Env Vars |
|
||||
|----------|-----|----------|----------|
|
||||
| Jumio | Netverify v4 | ID + selfie, liveness, document verification | `JUMIO_API_TOKEN`, `JUMIO_API_SECRET`, `JUMIO_WEBHOOK_SECRET` |
|
||||
| Onfido | v3.6 | Applicant, check, SDK token, watchlist screening | `ONFIDO_API_TOKEN`, `ONFIDO_WEBHOOK_SECRET` |
|
||||
| Plaid | Identity Verification | Session-based IDV, bank-linked identity | `PLAID_CLIENT_ID`, `PLAID_SECRET`, `PLAID_WEBHOOK_SECRET` |
|
||||
|
||||
All providers validate webhook signatures via HMAC-SHA256.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/compliance/pkg/kyc"
|
||||
"github.com/luxfi/compliance/pkg/idv"
|
||||
)
|
||||
|
||||
svc := kyc.NewService()
|
||||
svc.RegisterProvider(idv.NewJumio(idv.JumioConfig{
|
||||
APIToken: os.Getenv("JUMIO_API_TOKEN"),
|
||||
APISecret: os.Getenv("JUMIO_API_SECRET"),
|
||||
WebhookSecret: os.Getenv("JUMIO_WEBHOOK_SECRET"),
|
||||
}))
|
||||
```
|
||||
|
||||
**Application status flow:**
|
||||
```
|
||||
draft -> pending -> pending_kyc -> approved
|
||||
-> rejected
|
||||
```
|
||||
|
||||
**KYC status flow:**
|
||||
```
|
||||
not_started -> pending -> verified
|
||||
-> failed
|
||||
```
|
||||
|
||||
## AML Screening
|
||||
|
||||
### Sanctions & PEP Screening
|
||||
|
||||
Screen individuals against global sanctions lists and politically exposed persons databases:
|
||||
|
||||
- **OFAC SDN** -- US Treasury Office of Foreign Assets Control
|
||||
- **EU consolidated sanctions** -- European Union sanctions list
|
||||
- **UK HM Treasury** -- Her Majesty's Treasury sanctions list
|
||||
- **PEP databases** -- Politically Exposed Persons
|
||||
- **Adverse media** -- Negative news screening
|
||||
|
||||
Match types: exact, fuzzy (Levenshtein distance), partial. Risk scoring: low, medium, high, critical.
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/compliance/pkg/aml"
|
||||
|
||||
screeningSvc := aml.NewScreeningService()
|
||||
result, err := screeningSvc.Screen(ctx, aml.ScreenRequest{
|
||||
FullName: "John Doe",
|
||||
DateOfBirth: "1985-03-15",
|
||||
Country: "US",
|
||||
})
|
||||
// result.Matches contains any sanctions/PEP hits with scores
|
||||
```
|
||||
|
||||
### Transaction Monitoring
|
||||
|
||||
The rules engine detects suspicious transaction patterns. `InstallDefaultRules` adds the three mandatory FinCEN baselines:
|
||||
|
||||
```go
|
||||
monSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monSvc)
|
||||
```
|
||||
|
||||
This installs:
|
||||
|
||||
| Rule | Type | Threshold | Regulation |
|
||||
|------|------|-----------|------------|
|
||||
| CTR threshold | Single amount | $10,000 | 31 CFR 1010.311 |
|
||||
| Structuring | Pattern detection | $9,000-$9,999 | 31 USC 5324 |
|
||||
| Velocity (24h) | Aggregate volume | $50,000 | Enhanced KYC trigger |
|
||||
|
||||
Add custom rules:
|
||||
|
||||
```go
|
||||
monSvc.AddRule(aml.Rule{
|
||||
ID: "high-value-crypto",
|
||||
Type: aml.RuleSingleAmount,
|
||||
ThresholdAmount: 25000,
|
||||
Currency: "USD",
|
||||
Enabled: true,
|
||||
Severity: aml.SeverityHigh,
|
||||
Description: "Flag crypto transactions over $25,000",
|
||||
})
|
||||
```
|
||||
|
||||
Alert lifecycle: `open -> investigating -> escalated -> closed/filed`.
|
||||
|
||||
### Jube Integration
|
||||
|
||||
For ML-based transaction scoring beyond static rules, connect the Jube client:
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/compliance/pkg/jube"
|
||||
|
||||
client := jube.NewClient(jube.Config{
|
||||
BaseURL: "http://jube.svc.cluster.local:5001",
|
||||
ModelID: "your-model-guid",
|
||||
FailOpen: false, // reject when Jube is unreachable
|
||||
Timeout: 5 * time.Second,
|
||||
})
|
||||
```
|
||||
|
||||
Jube scores transactions in real time against configurable ML models, returning activation alerts, sanctions matches, and response elevations. The `FailOpen` flag controls behavior when Jube is unavailable -- set to `false` for regulated products.
|
||||
|
||||
## Regulatory Frameworks
|
||||
|
||||
Multi-jurisdiction compliance rules are built in:
|
||||
|
||||
| Jurisdiction | Regulator | Key Requirements |
|
||||
|-------------|-----------|------------------|
|
||||
| USA | FinCEN, SEC, FINRA | BSA (CIP, CTR $10k+, SAR), suitability, accredited investor |
|
||||
| UK | FCA | Registration, 5AMLD CDD/EDD, HM Treasury sanctions |
|
||||
| Isle of Man | IOMFSA | Designated Business, AML/CFT Code 2019, source of wealth/funds |
|
||||
|
||||
Each jurisdiction defines: required identity fields, application validation rules, and transaction limits.
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/compliance/pkg/regulatory"
|
||||
|
||||
jur := regulatory.Get("USA")
|
||||
err := jur.ValidateApplication(application)
|
||||
limits := jur.TransactionLimits()
|
||||
```
|
||||
|
||||
## Payment Compliance
|
||||
|
||||
### Travel Rule
|
||||
|
||||
FATF Recommendation 16 requires originator and beneficiary information for transfers over $3,000:
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/compliance/pkg/payments"
|
||||
|
||||
result := payments.ValidateTravelRule(payments.TransferInfo{
|
||||
Amount: 5000,
|
||||
OriginatorName: "Alice",
|
||||
BeneficiaryName: "Bob",
|
||||
OriginatorAddr: "123 Main St",
|
||||
})
|
||||
// result.Compliant = true if all required fields present
|
||||
```
|
||||
|
||||
### CTR Detection
|
||||
|
||||
Flags transactions at or above $10,000 for Currency Transaction Report filing.
|
||||
|
||||
### Stablecoin Validation
|
||||
|
||||
Token allowlists, per-jurisdiction policies, and address risk scoring with chain analysis integration point.
|
||||
|
||||
## Integration Methods
|
||||
|
||||
### Go Library (import directly)
|
||||
|
||||
The most common integration. Import packages and use them in your Go application:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/compliance/pkg/kyc"
|
||||
"github.com/luxfi/compliance/pkg/idv"
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
"github.com/luxfi/compliance/pkg/regulatory"
|
||||
"github.com/luxfi/compliance/pkg/payments"
|
||||
)
|
||||
```
|
||||
|
||||
### HTTP API (standalone server)
|
||||
|
||||
Run `complianced` as a standalone service and call it over HTTP:
|
||||
|
||||
```bash
|
||||
go build -o complianced ./cmd/complianced/
|
||||
JUMIO_API_TOKEN=... COMPLIANCE_API_KEY=... ./complianced
|
||||
```
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/v1/kyc/verify` | Initiate KYC verification |
|
||||
| GET | `/v1/kyc/status/{id}` | Check verification status |
|
||||
| POST | `/v1/aml/screen` | Screen against sanctions/PEP |
|
||||
| POST | `/v1/aml/monitor` | Monitor transaction |
|
||||
| GET | `/v1/aml/alerts` | List alerts |
|
||||
| POST | `/v1/payments/validate` | Validate payment compliance |
|
||||
| GET | `/v1/regulatory/{jurisdiction}` | Get jurisdiction requirements |
|
||||
|
||||
Auth: `X-Api-Key` header. Health check at `/healthz` requires no auth.
|
||||
|
||||
### TypeScript SDK
|
||||
|
||||
The `@luxbank/compliance` SDK provides a TypeScript client for NestJS applications:
|
||||
|
||||
```typescript
|
||||
import { ComplianceModule } from '@luxbank/compliance'
|
||||
|
||||
ComplianceModule.forRoot({
|
||||
baseUrl: process.env.COMPLIANCE_BASE_URL || 'http://compliance:8091',
|
||||
apiKey: process.env.COMPLIANCE_API_KEY || '',
|
||||
})
|
||||
```
|
||||
|
||||
The TypeScript SDK lives in [luxfi/bank/pkg/compliance](https://github.com/luxfi/bank/tree/main/pkg/compliance).
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
title: Building Regulated Products
|
||||
description: Assemble ATS, Broker-Dealer, and Transfer Agent systems from Lux OSS components
|
||||
icon: Scale
|
||||
---
|
||||
|
||||
The Lux regulated stack provides composable Go modules for building SEC/FINRA-registered financial products. Instead of building from scratch, you assemble proven components -- broker connectivity, matching engines, compliance pipelines, and custody integration -- into a product that meets specific regulatory requirements.
|
||||
|
||||
## Product Types
|
||||
|
||||
Three regulated product types can be built from the same set of modules:
|
||||
|
||||
| Product | Registration | What It Does | Key Modules |
|
||||
|---------|-------------|--------------|-------------|
|
||||
| **ATS** | SEC Reg ATS, Form ATS-N | Operates an internal matching engine (CLOB) with multi-venue routing | broker + cex + compliance + mpc |
|
||||
| **Broker-Dealer** | SEC/FINRA, SIPC | Routes orders to external venues on behalf of customers | broker + compliance |
|
||||
| **Transfer Agent** | SEC Rule 17Ad, Form TA-1 | Maintains shareholder records, processes transfers, manages cap tables | compliance + settlement webhooks |
|
||||
|
||||
## Architecture
|
||||
|
||||
All three products compose from the same building blocks:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Your Application │
|
||||
│ (ATS, BD, or TA main.go) │
|
||||
└──────────────┬──────────────────────┘
|
||||
│
|
||||
┌──────────────────────┼──────────────────────┐
|
||||
│ │ │
|
||||
┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐
|
||||
│ luxfi/broker │ │ luxfi/cex │ │ luxfi/compliance│
|
||||
│ 16 providers │ │ matching engine │ │ KYC/AML/regs │
|
||||
│ SOR, risk, │ │ order book, │ │ IDV, sanctions │
|
||||
│ settlement │ │ markets │ │ monitoring │
|
||||
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
|
||||
│ │ │
|
||||
┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐
|
||||
│ luxfi/mpc │ │ hanzoai/base │ │ luxfi/kms │
|
||||
│ MPC custody │ │ HTTP framework │ │ secret mgmt │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
**luxfi/broker** -- Multi-venue trading router with 16 providers (Alpaca, IBKR, Coinbase, Binance, Kraken, etc.), smart order routing, pre-trade risk, settlement engine, and audit trail.
|
||||
|
||||
**luxfi/cex** -- Central limit order book (CLOB) matching engine with market registration, fee schedules, and surveillance hooks. Used by ATS products that match orders internally.
|
||||
|
||||
**luxfi/compliance** -- KYC identity verification (Jumio, Onfido, Plaid), AML sanctions screening (OFAC, EU, UK, PEP), transaction monitoring (CTR, structuring, velocity), and multi-jurisdiction regulatory frameworks (USA, UK, IOM).
|
||||
|
||||
**luxfi/mpc** -- Multi-party computation custody for institutional key management. Threshold signatures without single points of failure.
|
||||
|
||||
**hanzoai/base** -- HTTP server framework with middleware, health checks, structured logging, and graceful shutdown. All regulated products mount their handlers on a Base server.
|
||||
|
||||
## How the Pieces Compose
|
||||
|
||||
Every regulated product follows the same initialization pattern:
|
||||
|
||||
1. Create a `hanzoai/base` server
|
||||
2. Create a `provider.Registry` and call `envconfig.RegisterFromEnv()` to wire up trading venues
|
||||
3. Optionally create a `cex/engine.Engine` and call `markets.RegisterFromProviders()` for internal matching
|
||||
4. Call `aml.InstallDefaultRules()` to install baseline FinCEN monitoring rules
|
||||
5. Optionally connect a Jube client for ML-based transaction scoring
|
||||
6. Mount HTTP handlers and start the server
|
||||
|
||||
The difference between products is which steps you include and which regulatory obligations you satisfy.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.26+**
|
||||
- **Hanzo Base** (`go get github.com/hanzoai/base`)
|
||||
- At least one provider API key (see [Provider Integration](/docs/regulated/providers))
|
||||
- For compliance: at least one IDV provider credential (Jumio, Onfido, or Plaid)
|
||||
|
||||
## Component Usage by Product
|
||||
|
||||
| Component | ATS | BD | TA |
|
||||
|-----------|-----|----|----|
|
||||
| `luxfi/broker` provider registry | Yes | Yes | -- |
|
||||
| `luxfi/broker` smart order router | Yes | Yes | -- |
|
||||
| `luxfi/broker` risk engine | Yes | Yes | -- |
|
||||
| `luxfi/broker` settlement engine | Yes | Optional | -- |
|
||||
| `luxfi/cex` matching engine | Yes | -- | -- |
|
||||
| `luxfi/cex` market registration | Yes | -- | -- |
|
||||
| `luxfi/compliance` KYC/IDV | Yes | Yes | Yes |
|
||||
| `luxfi/compliance` AML screening | Yes | Yes | Yes |
|
||||
| `luxfi/compliance` transaction monitoring | Yes | Yes | Yes |
|
||||
| `luxfi/compliance` regulatory frameworks | Yes | Yes | Yes |
|
||||
| `luxfi/compliance` payment compliance | Yes | Yes | Yes |
|
||||
| `luxfi/mpc` custody | Yes | Optional | -- |
|
||||
| Settlement webhooks | -- | -- | Yes |
|
||||
| Cap table management | -- | -- | Yes |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Alternative Trading System (ATS)](/docs/regulated/ats) -- Build an SEC-registered ATS with internal matching
|
||||
- [Broker-Dealer (BD)](/docs/regulated/bd) -- Build a FINRA-registered BD with multi-venue routing
|
||||
- [Transfer Agent (TA)](/docs/regulated/ta) -- Build an SEC-registered TA for securities records
|
||||
- [Provider Integration](/docs/regulated/providers) -- Connect to 16+ trading venues
|
||||
- [Compliance Integration](/docs/regulated/compliance-integration) -- KYC, AML, and regulatory frameworks
|
||||
@@ -0,0 +1 @@
|
||||
{"pages":["index","ats","bd","ta","providers","compliance-integration"]}
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: Provider Integration
|
||||
description: Connect to 16+ trading venues via the broker provider interface
|
||||
icon: Plug
|
||||
---
|
||||
|
||||
The `luxfi/broker` provider system abstracts trading venues behind a unified interface. Each provider implements the same `Provider` interface, and `envconfig.RegisterFromEnv()` automatically registers every provider that has credentials in the environment.
|
||||
|
||||
## The Provider Interface
|
||||
|
||||
Every broker backend implements this core interface:
|
||||
|
||||
```go
|
||||
type Provider interface {
|
||||
Name() string
|
||||
|
||||
// Accounts
|
||||
CreateAccount(ctx context.Context, req *types.CreateAccountRequest) (*types.Account, error)
|
||||
GetAccount(ctx context.Context, providerAccountID string) (*types.Account, error)
|
||||
ListAccounts(ctx context.Context) ([]*types.Account, error)
|
||||
|
||||
// Portfolio
|
||||
GetPortfolio(ctx context.Context, providerAccountID string) (*types.Portfolio, error)
|
||||
|
||||
// Orders
|
||||
CreateOrder(ctx context.Context, providerAccountID string, req *types.CreateOrderRequest) (*types.Order, error)
|
||||
ListOrders(ctx context.Context, providerAccountID string) ([]*types.Order, error)
|
||||
GetOrder(ctx context.Context, providerAccountID, providerOrderID string) (*types.Order, error)
|
||||
CancelOrder(ctx context.Context, providerAccountID, providerOrderID string) error
|
||||
|
||||
// Transfers
|
||||
CreateTransfer(ctx context.Context, providerAccountID string, req *types.CreateTransferRequest) (*types.Transfer, error)
|
||||
ListTransfers(ctx context.Context, providerAccountID string) ([]*types.Transfer, error)
|
||||
|
||||
// Banking
|
||||
CreateBankRelationship(ctx context.Context, providerAccountID string, ownerName, accountType, accountNumber, routingNumber string) (*types.BankRelationship, error)
|
||||
ListBankRelationships(ctx context.Context, providerAccountID string) ([]*types.BankRelationship, error)
|
||||
|
||||
// Assets & Market Data
|
||||
ListAssets(ctx context.Context, class string) ([]*types.Asset, error)
|
||||
GetAsset(ctx context.Context, symbolOrID string) (*types.Asset, error)
|
||||
GetSnapshot(ctx context.Context, symbol string) (*types.MarketSnapshot, error)
|
||||
GetSnapshots(ctx context.Context, symbols []string) (map[string]*types.MarketSnapshot, error)
|
||||
GetBars(ctx context.Context, symbol, timeframe, start, end string, limit int) ([]*types.Bar, error)
|
||||
GetLatestTrades(ctx context.Context, symbols []string) (map[string]*types.Trade, error)
|
||||
GetLatestQuotes(ctx context.Context, symbols []string) (map[string]*types.Quote, error)
|
||||
GetClock(ctx context.Context) (*types.MarketClock, error)
|
||||
GetCalendar(ctx context.Context, start, end string) ([]*types.MarketCalendarDay, error)
|
||||
}
|
||||
```
|
||||
|
||||
## Built-in Providers
|
||||
|
||||
| Provider | Asset Classes | Type | Env Vars |
|
||||
|----------|-------------|------|----------|
|
||||
| Alpaca | Equities, Crypto | Broker-dealer (SEC/FINRA) | `ALPACA_API_KEY`, `ALPACA_API_SECRET` |
|
||||
| Interactive Brokers | Equities, Options, Futures, FX | Prime broker | `IBKR_ACCESS_TOKEN`, `IBKR_ACCOUNT_ID`, `IBKR_CONSUMER_KEY` |
|
||||
| Tradier | Equities, Options | Broker-dealer | `TRADIER_ACCESS_TOKEN`, `TRADIER_ACCOUNT_ID` |
|
||||
| Coinbase | Crypto | Exchange (Advanced Trade) | `COINBASE_API_KEY`, `COINBASE_API_SECRET` |
|
||||
| Binance | Crypto | Exchange | `BINANCE_API_KEY`, `BINANCE_API_SECRET` |
|
||||
| Kraken | Crypto | Exchange | `KRAKEN_API_KEY`, `KRAKEN_API_SECRET` |
|
||||
| Gemini | Crypto | Exchange | `GEMINI_API_KEY`, `GEMINI_API_SECRET` |
|
||||
| SFOX | Crypto | Prime dealer / SOR | `SFOX_API_KEY` |
|
||||
| FalconX | Crypto | Institutional RFQ | `FALCON_API_KEY`, `FALCON_API_SECRET`, `FALCON_PASSPHRASE` |
|
||||
| Fireblocks | Crypto | Custody + OTC | `FIREBLOCKS_API_KEY`, `FIREBLOCKS_PRIVATE_KEY` |
|
||||
| BitGo | Crypto | Custody + Prime | `BITGO_ACCESS_TOKEN`, `BITGO_ENTERPRISE` |
|
||||
| Circle | USDC / Stablecoins | Stablecoin infrastructure | `CIRCLE_API_KEY` |
|
||||
| CurrencyCloud | FX (35+ pairs) | Institutional FX (Visa) | `CURRENCYCLOUD_LOGIN_ID`, `CURRENCYCLOUD_API_KEY` |
|
||||
| LMAX | FX, Crypto, Metals | Institutional CLOB | `LMAX_API_KEY`, `LMAX_USERNAME`, `LMAX_PASSWORD` |
|
||||
| Polygon.io | Equities, FX, Crypto | Market data only | `POLYGON_API_KEY` |
|
||||
| Finix | Payments | Payment processing | `FINIX_USERNAME`, `FINIX_PASSWORD` |
|
||||
|
||||
## Automatic Registration
|
||||
|
||||
`envconfig.RegisterFromEnv()` checks environment variables for all 16 providers and registers whichever ones have credentials:
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/broker/pkg/provider"
|
||||
"github.com/luxfi/broker/pkg/provider/envconfig"
|
||||
)
|
||||
|
||||
registry := provider.NewRegistry()
|
||||
n := envconfig.RegisterFromEnv(registry)
|
||||
// n = number of providers successfully registered
|
||||
```
|
||||
|
||||
Set `ALPACA_API_KEY` and `ALPACA_API_SECRET` in the environment, and Alpaca is registered. Set `COINBASE_API_KEY` and `COINBASE_API_SECRET`, and Coinbase is added. No code changes needed.
|
||||
|
||||
Each provider also accepts an optional base URL override for sandbox/testing:
|
||||
|
||||
| Provider | URL Override Var | Default |
|
||||
|----------|-----------------|---------|
|
||||
| Alpaca | `ALPACA_BASE_URL` | Sandbox |
|
||||
| IBKR | `IBKR_GATEWAY_URL` | Default gateway |
|
||||
| BitGo | `BITGO_BASE_URL` | Test |
|
||||
| FalconX | `FALCON_BASE_URL` | Sandbox |
|
||||
| Finix | `FINIX_BASE_URL` | Sandbox |
|
||||
| Circle | `CIRCLE_BASE_URL` | Sandbox |
|
||||
| Tradier | `TRADIER_BASE_URL` | Sandbox |
|
||||
| LMAX | `LMAX_BASE_URL` | Sandbox |
|
||||
| CurrencyCloud | `CURRENCYCLOUD_BASE_URL` | Demo |
|
||||
|
||||
Production URLs are used by default for: Coinbase, Binance, Kraken, Gemini, SFOX, Fireblocks, Polygon.
|
||||
|
||||
## Adding a Custom Provider
|
||||
|
||||
Implement the `Provider` interface and register it on the registry:
|
||||
|
||||
```go
|
||||
package myprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/luxfi/broker/pkg/provider"
|
||||
"github.com/luxfi/broker/pkg/types"
|
||||
)
|
||||
|
||||
type MyProvider struct {
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func New(apiKey string) *MyProvider {
|
||||
return &MyProvider{apiKey: apiKey}
|
||||
}
|
||||
|
||||
func (p *MyProvider) Name() string { return "myprovider" }
|
||||
|
||||
func (p *MyProvider) CreateAccount(ctx context.Context, req *types.CreateAccountRequest) (*types.Account, error) {
|
||||
// Your implementation
|
||||
}
|
||||
|
||||
// ... implement remaining Provider interface methods
|
||||
```
|
||||
|
||||
Register manually:
|
||||
|
||||
```go
|
||||
registry := provider.NewRegistry()
|
||||
envconfig.RegisterFromEnv(registry) // built-in providers
|
||||
registry.Register(myprovider.New(os.Getenv("MY_API_KEY"))) // custom provider
|
||||
```
|
||||
|
||||
## Optional Capability Interfaces
|
||||
|
||||
Providers can implement additional interfaces for extended functionality. The broker API detects these at runtime and exposes the additional endpoints only for providers that support them.
|
||||
|
||||
| Interface | Methods | Used By |
|
||||
|-----------|---------|---------|
|
||||
| `TradingExtended` | ReplaceOrder, CancelAllOrders, GetPosition, ClosePosition, CloseAllPositions | Alpaca, IBKR, Tradier |
|
||||
| `AccountManager` | UpdateAccount, CloseAccount, GetAccountActivities | Alpaca, IBKR |
|
||||
| `DocumentManager` | UploadDocument, ListDocuments, GetDocument, DownloadDocument | Alpaca |
|
||||
| `JournalManager` | CreateJournal, ListJournals, BatchJournal, ReverseBatchJournal | Alpaca |
|
||||
| `TransferExtended` | CancelTransfer, DeleteACHRelationship, RecipientBanks | Alpaca, IBKR |
|
||||
| `CryptoDataProvider` | CryptoBars, CryptoQuotes, CryptoTrades, CryptoSnapshots | Coinbase, Binance, Kraken |
|
||||
| `EventStreamer` | StreamTradeEvents, StreamAccountEvents, StreamTransferEvents | Alpaca |
|
||||
| `PortfolioAnalyzer` | GetPortfolioHistory | Alpaca, IBKR |
|
||||
| `WatchlistManager` | CreateWatchlist, ListWatchlists, CRUD operations | Alpaca |
|
||||
|
||||
You do not need to implement these. The base `Provider` interface is sufficient. Add optional interfaces only for capabilities your provider actually supports.
|
||||
@@ -0,0 +1,245 @@
|
||||
---
|
||||
title: Transfer Agent (TA)
|
||||
description: Build an SEC-registered transfer agent for securities ownership records
|
||||
icon: FileCheck
|
||||
---
|
||||
|
||||
A Transfer Agent (TA) is an SEC-registered entity that maintains the official record of securities ownership. It processes transfers between holders, issues and cancels certificates, handles lost/stolen certificates, and distributes dividends. A TA registers on Form TA-1 and operates under SEC Rules 17Ad-1 through 17Ad-17, with a minimum $25,000 net capital bond.
|
||||
|
||||
The Lux stack provides compliance infrastructure and settlement webhook integration. Unlike an ATS or BD, a TA does not route orders or operate a matching engine -- it receives settlement notifications from trading venues and updates the ownership ledger.
|
||||
|
||||
## Architecture
|
||||
|
||||
A TA uses Compliance plus settlement webhooks and a cap table store:
|
||||
|
||||
```
|
||||
┌────────────────────┐
|
||||
│ tad │
|
||||
│ :8080 │
|
||||
└─────────┬──────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────┐
|
||||
│ │ │
|
||||
┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐
|
||||
│ Compliance │ │ Settlement │ │ Cap Table │
|
||||
│ KYC / AML │ │ Webhooks │ │ Ledger │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
- **Compliance** screens every shareholder (KYC) and every transfer (AML) before recording ownership changes
|
||||
- **Settlement Webhooks** receive trade completion notifications from ATSs and clearing houses
|
||||
- **Cap Table Ledger** is the authoritative record of who owns what
|
||||
|
||||
## Step-by-Step Build
|
||||
|
||||
### 1. Create Go project
|
||||
|
||||
```bash
|
||||
mkdir tad && cd tad
|
||||
go mod init github.com/yourorg/tad
|
||||
go get github.com/hanzoai/base
|
||||
go get github.com/luxfi/compliance
|
||||
```
|
||||
|
||||
A TA does not need `luxfi/broker` or `luxfi/cex` since it does not route or match orders.
|
||||
|
||||
### 2. Set up compliance
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
"github.com/luxfi/compliance/pkg/kyc"
|
||||
"github.com/luxfi/compliance/pkg/idv"
|
||||
)
|
||||
|
||||
// KYC -- verify every shareholder identity
|
||||
kycSvc := kyc.NewService()
|
||||
kycSvc.RegisterProvider(idv.NewJumio(idv.JumioConfig{
|
||||
APIToken: os.Getenv("JUMIO_API_TOKEN"),
|
||||
APISecret: os.Getenv("JUMIO_API_SECRET"),
|
||||
}))
|
||||
|
||||
// AML -- screen transfers against sanctions
|
||||
monSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monSvc)
|
||||
```
|
||||
|
||||
### 3. Set up settlement webhook handler
|
||||
|
||||
The TA receives webhook notifications when trades settle on an ATS or clearing house. Each notification contains the security, buyer, seller, quantity, and settlement timestamp.
|
||||
|
||||
```go
|
||||
import "github.com/luxfi/compliance/pkg/webhook"
|
||||
|
||||
webhookHandler := webhook.NewHandler(webhook.Config{
|
||||
MaxRetries: 3,
|
||||
DeduplicationTTL: 24 * time.Hour,
|
||||
})
|
||||
|
||||
// Register settlement callback
|
||||
webhookHandler.OnSettlement(func(ctx context.Context, evt *SettlementEvent) error {
|
||||
// 1. AML screen the transfer
|
||||
result, err := monSvc.Monitor(ctx, aml.Transaction{
|
||||
SenderID: evt.SellerID,
|
||||
ReceiverID: evt.BuyerID,
|
||||
Amount: evt.NotionalValue,
|
||||
Currency: "USD",
|
||||
})
|
||||
if err != nil || result.Blocked {
|
||||
return fmt.Errorf("transfer blocked: %s", result.Reason)
|
||||
}
|
||||
|
||||
// 2. Update cap table
|
||||
return capTable.Transfer(ctx, evt.SecurityID, evt.SellerID, evt.BuyerID, evt.Quantity)
|
||||
})
|
||||
```
|
||||
|
||||
### 4. Set up cap table ledger
|
||||
|
||||
```go
|
||||
// Cap table is the authoritative ownership record
|
||||
capTable := captable.New(captable.Config{
|
||||
DB: db, // PostgreSQL via hanzoai/sql
|
||||
})
|
||||
```
|
||||
|
||||
The cap table tracks: holder ID, security ID, share quantity, acquisition date, cost basis, restriction status, and certificate number.
|
||||
|
||||
### 5. Mount and serve
|
||||
|
||||
```go
|
||||
import "github.com/hanzoai/base"
|
||||
|
||||
srv := base.NewServer(base.Config{Addr: ":8080"})
|
||||
srv.Mount("/compliance", compSrv.Handler())
|
||||
srv.Mount("/webhooks", webhookHandler.Handler())
|
||||
srv.Mount("/captable", capTableAPI.Handler())
|
||||
|
||||
slog.Info("TA starting", "addr", ":8080")
|
||||
srv.ListenAndServe()
|
||||
```
|
||||
|
||||
## Minimal TA main.go
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/base"
|
||||
"github.com/luxfi/compliance/pkg/aml"
|
||||
compapi "github.com/luxfi/compliance/pkg/api"
|
||||
"github.com/luxfi/compliance/pkg/webhook"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. Compliance
|
||||
monSvc := aml.NewMonitoringService()
|
||||
aml.InstallDefaultRules(monSvc)
|
||||
compSrv := compapi.NewServer(compapi.Config{Monitoring: monSvc})
|
||||
|
||||
// 2. Settlement webhooks
|
||||
wh := webhook.NewHandler(webhook.Config{
|
||||
MaxRetries: 3,
|
||||
DeduplicationTTL: 24 * time.Hour,
|
||||
})
|
||||
|
||||
// 3. Cap table (your application logic)
|
||||
// capTable := captable.New(...)
|
||||
// capTableAPI := captableapi.New(capTable)
|
||||
|
||||
// 4. Serve
|
||||
srv := base.NewServer(base.Config{Addr: ":8080"})
|
||||
srv.Mount("/compliance", compSrv.Handler())
|
||||
srv.Mount("/webhooks", wh.Handler())
|
||||
// srv.Mount("/captable", capTableAPI.Handler())
|
||||
|
||||
slog.Info("TA starting", "addr", ":8080")
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
slog.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Settlement Webhook Flow
|
||||
|
||||
When an ATS or clearing house completes a trade, it sends a webhook to the TA:
|
||||
|
||||
```
|
||||
ATS settles trade
|
||||
│
|
||||
▼
|
||||
POST /webhooks/settlement
|
||||
│
|
||||
├── HMAC-SHA256 signature validation
|
||||
├── Idempotency check (deduplication)
|
||||
│
|
||||
▼
|
||||
AML screen transfer
|
||||
│
|
||||
├── OFAC / sanctions check on buyer & seller
|
||||
├── CTR check on notional value
|
||||
├── Velocity check on both parties
|
||||
│
|
||||
▼
|
||||
Update cap table
|
||||
│
|
||||
├── Debit seller's position
|
||||
├── Credit buyer's position
|
||||
├── Record transfer with timestamp
|
||||
└── Generate confirmation notices
|
||||
```
|
||||
|
||||
## Compliance: KYC on Shareholders
|
||||
|
||||
Every shareholder must be KYC-verified before appearing in the cap table. The TA verifies identity when:
|
||||
|
||||
- A new shareholder acquires shares for the first time
|
||||
- A shareholder requests a certificate transfer
|
||||
- Regulatory re-verification is triggered (e.g., change of address, dormant account reactivation)
|
||||
|
||||
## Record-Keeping: 17Ad-6/7 Requirements
|
||||
|
||||
SEC Rules 17Ad-6 and 17Ad-7 require a TA to maintain:
|
||||
|
||||
| Record | Retention | Description |
|
||||
|--------|-----------|-------------|
|
||||
| Transfer journal | 6 years | Every transfer with date, parties, quantity |
|
||||
| Shareholder list | Current + 2 years | Name, address, holdings for each holder |
|
||||
| Dividend records | 6 years | Payments, dates, amounts per holder |
|
||||
| Certificate log | 6 years | Issuance, cancellation, replacement |
|
||||
| Correspondence | 3 years | All written communications with holders |
|
||||
| Complaint log | 3 years | Every complaint and resolution |
|
||||
|
||||
The cap table ledger with immutable audit trail satisfies the transfer journal and shareholder list requirements. Pair with the `luxfi/compliance` webhook handler's idempotency and dead letter queue for complete audit coverage.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `COMPLIANCE_API_KEY` | Yes | API key for compliance endpoints |
|
||||
| `JUMIO_API_TOKEN` | Recommended | Jumio IDV for shareholder KYC |
|
||||
| `JUMIO_API_SECRET` | Recommended | Jumio API secret |
|
||||
| `DATABASE_URL` | Yes | PostgreSQL connection string for cap table |
|
||||
| `SETTLEMENT_WEBHOOK_SECRET` | Yes | HMAC secret for validating ATS webhooks |
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
```bash
|
||||
docker build --platform linux/amd64 -t ghcr.io/yourorg/tad:latest .
|
||||
docker run -p 8080:8080 \
|
||||
-e COMPLIANCE_API_KEY=... \
|
||||
-e JUMIO_API_TOKEN=... \
|
||||
-e JUMIO_API_SECRET=... \
|
||||
-e DATABASE_URL=postgres://... \
|
||||
-e SETTLEMENT_WEBHOOK_SECRET=... \
|
||||
ghcr.io/yourorg/tad:latest
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
See [partner/app](https://github.com/luxfi/app) for a working transfer agent implementation built on these components.
|
||||
Reference in New Issue
Block a user