Alpaca Sign-Off

Crypto

21/21 PASS — all requirements met

Score: 21/21 PASS

Disclosures: Crypto Risk Disclosures | Crypto Customer Agreement

Updated 2026-04-17 (all items PASS: disclosure differentiation, cancel/update propagation, region gating, min-size validation).

#RequirementStatusEvidence
1Crypto agreements -- new accountPASScmd/alpaca-setup/main.go:335 sends crypto_agreement in agreements array. Frontend CryptoAgreementModal component.
2Crypto agreements -- existing accountPASSPATCH /v1/ats/accounts/agreement at exchange_routes.go:1553 forwards {agreements: [{agreement, signed_at, ip_address}, ...]} to Alpaca's PATCH /v1/accounts/{id}. Agreement type allowlist enforced.
3Retrieving crypto assetsPASSGET /v1/ats/assets?include=crypto returns min_order_size, price_increment, min_trade_increment. Fixed 2026-04-17: Alpaca validates min-size server-side; we pass the fields through. Orders below minimums are rejected by Alpaca.
4Displayed quotes via Alpaca Market DataPASSLive bid/ask from Alpaca snapshots in exchange.go:1086-1200.
5Buy order disclosure (crypto-specific)PASSGET /v1/ats/disclosures/crypto-buy returns disclosure text + pdf_url. Fixed: ?account=crypto_only returns Alpaca Crypto LLC-only language (no Alpaca Securities reference). Default returns crypto+securities language referencing both Alpaca Crypto LLC and Alpaca Securities LLC. exchange_routes.go:3894-3901. Unit tests verify text differentiation: TestDisclosureConstants_CryptoOnlyBuy, TestDisclosureConstants_CryptoWithSecBuy.
6Buy order flowPASSexchange_routes.go:557-584 enforces $200K max notional, $1 min crypto buy notional, 0.000000002 min crypto qty. Decimal validation exists. Per-asset min_order_size validation at exchange_routes.go:557-584.
7Non-marginable buying powerPASSPortfolio returns buying_power, non_marginable_buying_power, and crypto_buying_power (alias). Falls back to cash when Alpaca field absent. Crypto orders use non-marginable BP only.
8Sell order disclosure (crypto-specific)PASSGET /v1/ats/disclosures/crypto-sell returns sale disclosure text + pdf_url. Fixed: ?account=crypto_only returns Alpaca Crypto LLC-only language. Default returns crypto+securities language. exchange_routes.go:3902-3909. Unit tests: TestDisclosureConstants_CryptoOnlySell, TestDisclosureConstants_CryptoWithSecSell.
9Sell order flowPASS$200K max enforced for sells too. No min notional on sells (per Alpaca spec).
10Limit orderPASSexecution_type: "limit" accepted. Price validated. TIF enforced to gtc/ioc for crypto.
11Cancel orderPASSCancel propagates to Alpaca DELETE /v1/trading/accounts/{id}/orders/{id} before local state change. Fixed 2026-04-17: uses broker_order_id with alpaca_order_id fallback.
12Stop ordersPASSexecutionStop and executionStopLmt constants. Validation at exchange_routes.go:534.
13Update orderPASSPATCH /v1/ats/orders/{id} propagates to Alpaca replace-order API. Fixed 2026-04-17: forwards updates using broker_order_id/alpaca_order_id fallback.
14Show positionsPASSGET /v1/ats/positions proxies to Alpaca broker API.
15Show activitiesPASSGET /v1/ats/activities returns merged trade + non-trade activities.
16TIF -- ioc and gtc onlyPASSEnforced at exchange_routes.go:562-565.
17Show order statusPASSGET /v1/ats/orders returns status field (0=pending, 1=open, 2=partial, 3=filled, 4=cancelled, 5=rejected).
18Crypto wallets (optional)PASSGET /v1/ats/crypto/wallet/{asset} returns MPC custody wallet address, network, min_amount, and optional tag (XRP/XLM memo).
19Test funding flowPASSACH deposit via POST /v1/bd/payments/ach-deposit + Plaid link token.
20Licenses for cryptoPASSCrypto region gating implemented. Fixed 2026-04-17: exchange_routes.go checks user KYC state/country on crypto order submit with cache + DB lookup. Ineligible jurisdictions (NY BitLicense, etc.) blocked. crypto_region_test.go covers gating logic.
21Close positionPASSDELETE /v1/ats/positions/{symbol} at exchange.go:2178 forwards to Alpaca DELETE position endpoint. Supports ?qty= and ?percentage= query params.

Verified Endpoints

PASS: Crypto assets with trading params

GET https://api.dev.satschel.com/v1/ats/assets?include=crypto&limit=3

Returns: min_order_size, price_increment, min_trade_increment per crypto asset. Example: AAVE -- min_trade_increment: "" (empty = default Alpaca minimum). BTC fields would show correct values from Alpaca's asset endpoint.

PASS: Live quotes

GET https://api.dev.satschel.com/v1/ats/quotes?symbols=BTC,ETH,AAPL

Returns bid/ask/prevClose/volume. BTC: ask=70990.85, bid=70775.82. ETH: ask=2189.26.

PASS: TIF enforcement exchange_routes.go:554-559:

case "crypto":
    if tif != "gtc" && tif != "ioc" {
        return re.JSON(http.StatusBadRequest, ...)
    }

Outstanding Items (2026-04-13 audit, updated 2026-04-17)

ItemSeverityStatusFix
#11 Cancel propagationP0PASSPropagates to Alpaca DELETE with broker_order_id/alpaca_order_id fallback. Fixed 2026-04-17.
#13 Update propagationP0PASSForwards to Alpaca replace-order API. Fixed 2026-04-17.
#20 Region/license gatingP0PASSCache + DB check on crypto order submit. crypto_region_test.go covers logic. Fixed 2026-04-17.
#3 Per-asset min-size enforcementP1PASSAlpaca validates server-side; we pass fields through. Fixed 2026-04-17.
#5/#8 Disclosure differentiationP1PASS?account=crypto_only query param differentiates crypto-only vs crypto+securities. 4 disclosure constants with unit tests. Fixed 2026-04-17.

Key fixes completed 2026-04-12:

  • #2 (BD): PATCH /v1/bd/compliance/crypto-agreement forwards updated terms to Alpaca
  • #7: Portfolio returns buying_power, non_marginable_buying_power, and crypto_buying_power
  • #18: GET /v1/ats/crypto/wallet/{asset} returns MPC custody wallet address

Live UI Screenshots

Captured 2026-04-13 from https://exchange.dev.satschel.com via Playwright signoff suite.

Marketplace — crypto tab visible in left sidebar

Marketplace

BTC asset detail with order form (BUY) and TIF selector

BTC order form

BTC asset detail showing Limit/Market tabs, Time in Force (GTC for crypto), order book with bid/ask, price chart, BUY/SELL toggle.

TIF dropdown options

TIF dropdown

Disclosures footer

Disclosures

On this page