Skip to content

Wallet: self-service USDC withdrawal to external address (any user) #68

Description

@manoahLinks

Summary

Self-service USDC withdrawal: let any HostIT user move their custodial USDC balance out to an external on-chain address of their choosing (full balance or a specified amount).

This replaces the original "accumulate-then-pay-organizer-out" framing. Under the real-time split (#96), USDC lands directly in a user's custodial Circle wallet:

Both cases leave a spendable USDC balance in the user's Circle wallet. This issue covers letting them sweep it to an external wallet.

Scope

  • Endpoint: POST /api/wallet/withdraw (JWT — any authenticated user)
    • Body: { toAddress: string, amount?: string }amount omitted ⇒ withdraw the entire available balance.
    • Validate toAddress (EVM checksum, non-zero); reject if balance is 0 or amount > available.
  • Service CircleWalletService.withdraw(userId, toAddress, amount?):
    • Source: the user's own custodial Circle wallet (user.circleWalletId).
    • Transfer via Circle developer-controlled wallet USDC transfer (Base). Gas sponsored by Circle Gas Station (same policy the epic requires).
    • Idempotency: use WithdrawalRecord.id as the Circle transfer refId.
    • Persist WithdrawalRecord { userId, toAddress, amount, fee, status, circleTransferId, txHash }.
  • Status updated from the Circle webhook (Webhooks: Circle webhook handler (/webhooks/circle) #65), not by polling.
  • "Withdraw all" resolves the on-chain balance at request time and transfers the full amount (account for any gas/fee handling).

Out of scope (tracked elsewhere)

Acceptance criteria

  • User can withdraw a specified USDC amount to an external address, end-to-end
  • User can withdraw their entire balance (no amount given)
  • Invalid/zero address and over-balance requests rejected with clear errors
  • Withdrawal status reconciled from the Circle webhook (Webhooks: Circle webhook handler (/webhooks/circle) #65)
  • Idempotent — retrying the same WithdrawalRecord never double-sends
  • Audit log captures source wallet, destination, amount, fee, tx hash
  • Dry-run mode for staging (skips the actual transfer)

Depends on

Refs #96, #37

Metadata

Metadata

Assignees

No one assigned

    Labels

    cryptoCryptocurrency and stablecoin operationsphase-5Phase 5: Payments + Webhooks

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions