Skip to content

feat: propagate structured error classes across payment module#267

Open
mehmetkr-31 wants to merge 1 commit intobase:masterfrom
mehmetkr-31:feat/propagate-structured-errors-payment-module
Open

feat: propagate structured error classes across payment module#267
mehmetkr-31 wants to merge 1 commit intobase:masterfrom
mehmetkr-31:feat/propagate-structured-errors-payment-module

Conversation

@mehmetkr-31
Copy link
Copy Markdown

Summary

  • Introduce ValidationError and PaymentError custom error classes and apply them consistently across the entire payment module, replacing 18 raw throw new Error() calls in 10 files
  • Export both error classes from the public API so consumers can use instanceof checks for programmatic error handling
  • Refactor getSubscriptionStatus to reuse validateUSDCBasePermission() instead of duplicating validation logic inline

Motivation

Currently, the payment module uses generic throw new Error(message) throughout, making it impossible for developers to programmatically distinguish between input validation errors and runtime payment failures. This forces consumers to parse error message strings, which is fragile and not a good DX.

This PR introduces two structured error classes:

import { PaymentError, ValidationError } from '@base-org/account';

try {
  await pay({ amount: '10.50', to: '0x...' });
} catch (e) {
  if (e instanceof ValidationError) {
    console.log(e.field);          // 'amount'
    console.log(e.providedValue);  // '10.50'
    console.log(e.expectedFormat); // 'a positive decimal number'
  }
  if (e instanceof PaymentError) {
    console.log(e.code);       // 'USER_OP_FAILED'
    console.log(e.retryable);  // true
  }
}

Error classification

Error Class Used For Key Properties
ValidationError Input validation (amounts, addresses, chain/token mismatch) field, providedValue, expectedFormat
PaymentError Runtime failures (user ops, RPC, wallet retrieval, CDP init) code, retryable

Both extend Error, so existing catch blocks continue to work — zero breaking changes.

Files Changed

New

  • core/error/sdkErrors.tsValidationError and PaymentError classes

Updated (error propagation)

  • utils/validation.tsValidationError with field metadata
  • utils/sendUserOpAndWait.tsPaymentError with error codes and retryability
  • utils/validateUSDCBasePermission.tsValidationError for chain/token mismatches
  • getPaymentStatus.tsPaymentError for RPC and transfer parsing errors
  • utils/getExistingSmartWalletOrThrow.tsPaymentError for wallet lookup failures
  • utils/sdkManager.tsPaymentError for unexpected response formats
  • subscribe.tsValidationError + PaymentError for wallet responses
  • utils/createCdpClientOrThrow.tsPaymentError for CDP initialization
  • prepareCharge.ts / prepareRevoke.tsPaymentError for subscription not found
  • getSubscriptionStatus.ts — Refactored to call validateUSDCBasePermission() (removed 25 lines of duplicated inline validation)
  • getOrCreateSubscriptionOwnerWallet.tsPaymentError for CDP init and wallet creation

Updated (exports)

  • interface/payment/index.ts — Export error classes (browser)
  • interface/payment/index.node.ts — Export error classes (Node.js)
  • src/index.ts / src/index.node.ts — Re-export from top-level

Updated (tests)

  • utils/validation.test.ts — Updated assertions + added instanceof checks
  • utils/sendUserOpAndWait.test.ts — Updated error messages + PaymentError checks
  • charge.test.ts — Updated error message assertions
  • pay.test.ts — Updated dataSuffix error assertion

Verification

  • All 986 tests pass (6 pre-existing failures in createSmartAccount.test.ts due to network/viem AbortSignal issue — unrelated)
  • All payment module tests pass: charge (18), pay (14), subscribe (8), getPaymentStatus (19), getSubscriptionStatus (18), sendUserOpAndWait (11), validation (19)

Addresses #231

@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Apr 7, 2026

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

Introduce ValidationError and PaymentError classes and apply them
consistently across the entire payment module, replacing raw
throw new Error() calls with structured, actionable errors.

Changes:
- Add sdkErrors.ts with ValidationError and PaymentError classes
- Replace 18 raw Error throws across 10 payment files:
  - validation.ts: ValidationError with field metadata
  - sendUserOpAndWait.ts: PaymentError with error codes
  - validateUSDCBasePermission.ts: ValidationError for chain/token
  - getPaymentStatus.ts: PaymentError for RPC and transfer errors
  - getExistingSmartWalletOrThrow.ts: PaymentError for wallet lookup
  - sdkManager.ts: PaymentError for response format issues
  - subscribe.ts: ValidationError + PaymentError
  - createCdpClientOrThrow.ts: PaymentError for CDP init
  - prepareCharge.ts / prepareRevoke.ts: PaymentError
  - getSubscriptionStatus.ts: reuse validateUSDCBasePermission()
  - getOrCreateSubscriptionOwnerWallet.ts: PaymentError
- Export both error classes from public API entry points
- Refactor getSubscriptionStatus to reuse validateUSDCBasePermission
  instead of duplicating validation logic inline
- Update all affected tests to match new error messages

Developers can now use instanceof checks for programmatic error
handling:
  import { PaymentError, ValidationError } from '@base-org/account';

  try { await pay(...) } catch (e) {
    if (e instanceof ValidationError) { /* fix input */ }
    if (e instanceof PaymentError) { /* retry or report */ }
  }

Addresses base#231
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants