---
title: "WalletConductor"
description: "SSOT facade for custodial native-token wallets, fiat credit ledger, Token Desk, card top-up, confidential native onramp, and NFT market buys — money counterpart to PaymentConductor"
locale: "en"
---
# WalletConductor

**WalletConductor** is Ring Platform's orchestration facade for **custodial native-token web3** and **fiat credit** money paths. Thin adapters (`app/_actions/wallet.ts`, `/api/wallet/token/*`, `/api/wallet/desk/*`, NFT market buy) call the conductor; it delegates PSPs to [PaymentConductor](/docs/features/payment-conductor.md) and ledger math to `creditBalanceService`.

> **Info**
> Use **Founder** / **Developer** tabs in the docs sidebar to filter this page. Member-facing product UI: [Wallet](/docs/features/wallet.md). HTTP/action inventory: [Wallet API](/docs/api/wallet.md).

One layer for wallet money: card → **credit points**, desk → **native token**, gasless custodial sends, ad-hoc credit spend, and NFT market purchases. Fiat spend accounting uses `credit.creditBalanceUnitToMainCurrency` (usually `1`) — never the desk oracle. External EVM POL/USDT via `POST /api/wallet/transfer` stays **outside** this conductor.

```mermaid
flowchart TB
  subgraph surfaces["Surfaces"]
    SA["Server Actions\napp/_actions/wallet.ts"]
    API["HTTP /api/wallet/*"]
    NFT["NFT market buy"]
    OAuth["OAuth / ensure"]
  end

  subgraph WC["WalletConductor"]
    TOP["initiateTopUp"]
    ONR["initiateNativeOnramp"]
    DESK["quoteDesk / executeDesk"]
    SEND["transferNative"]
    SPEND["spendCredits"]
    ENS["ensureNativeWallet"]
    NFTB["purchaseNftListing"]
  end

  subgraph deps["Dependencies"]
    PC["PaymentConductor\nwallet_topup / native_token_onramp"]
    CBS["creditBalanceService"]
    DS["desk-service + native-token-oracle"]
    XFER["native-token-transfer-service"]
    EW["ensure-wallet"]
    SOL["SolanaMarketClient"]
  end

  SA --> WC
  API --> WC
  NFT --> NFTB
  OAuth --> ENS
  TOP --> PC
  ONR --> PC
  DESK --> DS
  SPEND --> CBS
  SEND --> XFER
  ENS --> EW
  NFTB --> SOL
  DS --> CBS
```

## What WalletConductor owns

| Capability | Method | Result |
|------------|--------|--------|
| **Card → credit points** | `initiateTopUp` | PaymentConductor `wallet_topup` → fiat ledger credit |
| **Card/PayPal → treasury native** | `initiateNativeOnramp` | PaymentConductor `native_token_onramp` (confidential+) |
| **Token Desk** | `quoteDesk` / `executeDesk` | Credit points ↔ native at desk oracle (subscriber+) |
| **Custodial send** | `transferNative` | Gasless native transfer + `wallet_transactions` row |
| **Native balance** | `getNativeBalance` | Custodial balance for platform native chain |
| **Ad-hoc credit spend** | `spendCredits` | Fiat ledger debit at `creditBalanceUnitToMainCurrency` |
| **Wallet provision** | `ensureNativeWallet` | Atomic multi-chain wallets (OAuth-safe, no session) |
| **Min credit gate** | `ensureFunded` | Session-gated provision + credit floor |
| **NFT market buy** | `purchaseNftListing` | Idempotent RING buy via Solana market client |

**Not in WalletConductor:** store/membership **checkout** debit (PaymentConductor `credit_balance` / SubscriptionConductor), and external EVM `POST /api/wallet/transfer`. Those remain separate SSOT paths by design.

## Two rates — do not mix

| Rate | SSOT | Used by |
|------|------|---------|
| **Fiat credit accounting** | `ring-config.json` → `credit.creditBalanceUnitToMainCurrency` (usually `1`) via `getMainCurrencyCreditAccountingRate()` | `spendCredits`, desk debit side, credit_balance checkout |
| **Token Desk oracle** | `platform_settings.web3.oracle.nativePerMainCurrency` via `native-token-oracle.ts` | `quoteDesk` / `executeDesk` points ↔ native only |

### For founders

## Why this matters for your clone

WalletConductor is the operator-facing money spine behind the [Wallet](/docs/features/wallet.md) product. Members see top-up, desk, and send UI; your clone stays coherent because every path hits one facade — not scattered PSP and ledger calls.

  
- **[Credit top-up](/docs/features/wallet.md)** — Any signed-in member buys credit points with a card. Points are fiat ledger units (1:1 with store `mainCurrency` when `creditBalanceUnitToMainCurrency` is `1`).

  
- **[Token Desk](/docs/features/wallet.md)** — Subscriber+ converts points ↔ native at the desk oracle. Changing `nativePerMainCurrency` never rewrites store or ad-hoc credit spend math.

  
- **[Native onramp](/docs/features/payment-conductor.md)** — Confidential+ card/PayPal → treasury native (feature-flagged). Does **not** add credit points.

  
- **[NFT market buy](/docs/features/nft-market.md)** — Eligible buyers pay RING from the custodial wallet through `purchaseNftListing` (idempotent sale row).

### Operator checklist

**Confirm credit unit SSOT**

Keep `credit.creditBalanceUnitToMainCurrency: 1` in `ring-config.json` unless you intentionally scale points vs fiat. Desk oracle changes never affect store checkout or `POST /api/wallet/credit/spend`.

**Separate desk oracle from fiat**

Superadmins set `nativePerMainCurrency` for desk conversion only. Do not expect that rate on ad-hoc credit spend.

**Gate native onramp carefully**

Native card onramp is confidential+ and feature-flagged (`CONFIDENTIAL_TOKEN_ONRAMP` / desk `nativeTokenOnramp`). Leave it off until treasury ops and compliance are ready.

> **Tip**
> Prefer Server Actions from UI. Keep HTTP wallet routes for MCP and non-React clients. Prefer `/api/wallet/token/*` — retired `/api/wallet/ring/*` aliases are removed.

### For developers

## Implementation

Facade: `features/wallet/conductor/wallet-conductor.ts` (`server-only`).

{`import { WalletConductor } from '@/features/wallet/conductor/wallet-conductor'

// OAuth / events — no session required
const ensured = await WalletConductor.ensureNativeWallet({ id: userId, role })

// Card → credit points (amount 25–2000)
const topUp = await WalletConductor.initiateTopUp(null, formData)

// Ad-hoc fiat debit — rate defaults to creditBalanceUnitToMainCurrency
const spent = await WalletConductor.spendCredits({
  userId,
  amount: '10.00',
  description: 'API debit',
})

// Desk (subscriber+)
const quote = await WalletConductor.quoteDesk({ userId, role, side: 'buy', amount: '100' })
await WalletConductor.executeDesk({ userId, role, idempotencyKey, quoteToken })`}

### Wire a surface

**Call the facade, not the ledger**

UI and routes should import `WalletConductor` (or the thin wrappers in `app/_actions/wallet.ts`). Do not debit `creditBalanceService` from feature UI for paths the conductor owns.

**Keep fiat rate SSOT**

For spend paths, omit `usdRate` or pass `getMainCurrencyCreditAccountingRate()`. Never feed `nativePerMainCurrency` / desk oracle into fiat ledger debits.

**Respect the EVM boundary**

Custodial native send is `transferNative`. External SupportedCrypto (POL/USDT) stays on `POST /api/wallet/transfer` — do not fold that into WalletConductor without an explicit redesign.

### Module map

| Layer | Path |
|-------|------|
| Facade | `features/wallet/conductor/wallet-conductor.ts` |
| Provision | `features/wallet/services/ensure-wallet.ts` |
| Credit ledger | `features/wallet/services/credit-balance-service.ts` |
| Fiat rate | `lib/ring-config-core.ts` → `getCreditUnitToMainCurrencyRate` / `lib/payments/credit-balance.ts` → `getMainCurrencyCreditAccountingRate` |
| Desk | `features/wallet/chains/solana/desk-service.ts` |
| Desk oracle | `features/wallet/services/native-token-oracle.ts` |
| Custodial send | `features/wallet/chains/native-token-transfer-service.ts` |
| PSP checkout | `lib/payments/conductor/payment-conductor.ts` |
| Store credit rail | `lib/payments/processors/credit-balance.processor.ts` (PaymentConductor — not WC) |
| Actions | `app/_actions/wallet.ts` |
| NFT buy | `features/nft-market/services/solana-market-client.ts` |

### Method contracts

| Method | Auth / gates | Notes |
|--------|--------------|-------|
| `ensureNativeWallet` | Caller supplies `id` | OAuth-safe; returns `{ ok, native, wallets }` |
| `ensureFunded` | Session | Provisions + optional credit minimum |
| `initiateTopUp` | Session | Amount 25–2000; purpose `wallet_topup` |
| `initiateNativeOnramp` | Session + confidential metadata | Optional `processor` paypal/stripe/wayforpay |
| `quoteDesk` / `executeDesk` | `assertTokenDeskSubscriberAccess` | Solana desk SSOT |
| `transferNative` | Caller supplies `userId` | Writes `wallet_transactions`; touches contacts |
| `spendCredits` | Caller supplies `userId` | Defaults `usdRate` to `getMainCurrencyCreditAccountingRate()` |
| `purchaseNftListing` | Idempotency key | Status: pending → submitted → confirmed / failed |
| `getNativeBalance` | Caller supplies `userId` | Thin wrap of transfer service |

### Sequence — card credit top-up

```mermaid
sequenceDiagram
  participant UI as CreditAddFsModal
  participant WC as WalletConductor
  participant PC as PaymentConductor
  participant PSP as WayForPay_or_Stripe_or_PayPal
  participant WH as wallet_topup_webhook
  participant CBS as creditBalanceService
  UI->>WC: initiateTopUp formData
  WC->>PC: createCheckout purpose=wallet_topup
  PC-->>UI: redirect navigate_or_form_post
  UI->>PSP: followCheckoutResult
  Note over UI,PSP: returnUrl browser UX only
  PSP->>WH: webhook Approved_or_capture
  WH->>CBS: addFiatUsd fiat points
```

Card tab omits `processor` (env SSOT). PayPal tab sets `processor=paypal`. Client: `lib/payments/checkout-redirect.ts`.

### Sequence — ad-hoc credit spend

```mermaid
sequenceDiagram
  participant API as POST /api/wallet/credit/spend
  participant WC as WalletConductor.spendCredits
  participant RATE as getMainCurrencyCreditAccountingRate
  participant CBS as creditBalanceService
  API->>WC: amount, description
  WC->>RATE: creditBalanceUnitToMainCurrency
  WC->>CBS: spendCredits(..., usdRate)
  CBS-->>API: newBalance, transactionId
```

### Environment & config (verified)

| Key | Role |
|-----|------|
| `WALLET_ENCRYPTION_KEY` | Custodial key encryption |
| `SOLANA_RPC_URL` / `SOLANA_TREASURY_PRIVATE_KEY` | Custodial Solana + gas sponsorship |
| `CONFIDENTIAL_TOKEN_ONRAMP` / desk `nativeTokenOnramp` | Native card onramp gate |
| `credit.creditBalanceUnitToMainCurrency` | Fiat ledger accounting multiplier |
| `ORACLE_QUOTE_SECRET` / `RING_ORACLE_DEFAULT_RATE` | Desk quote HMAC + fallback |

## Related documentation

  
- **[Generative Gallery](/docs/features/generative-media.md)** — Credit-first generative billing via `spendCredits` + `generative_usage` ledger.

  
- **[WalletConductor architecture](/docs/architecture/wallet-conductor.md)** — Adapters, route map, sequences, env SSOT.

  
- **[Wallet feature](/docs/features/wallet.md)** — Product architecture and member flows.

  
- **[Wallet API](/docs/api/wallet.md)** — Actions, HTTP routes, rate SSOT tables.

  
- **[PaymentConductor](/docs/features/payment-conductor.md)** — PSP rails used by top-up and onramp.
