Skip to content

feat: implement SDK unsupported feature and capability error standard (#284) - #299

Merged
El-swaggerito merged 1 commit into
Axionvera:mainfrom
XxHugheadxX:feat/unsupported-feature-capability-errors
Jul 27, 2026
Merged

feat: implement SDK unsupported feature and capability error standard (#284)#299
El-swaggerito merged 1 commit into
Axionvera:mainfrom
XxHugheadxX:feat/unsupported-feature-capability-errors

Conversation

@XxHugheadxX

Copy link
Copy Markdown
Contributor

Closes #284

Summary

Implements the standard specified in docs/capability_error_standard.md, adding
UnsupportedFeatureError and CapabilityMismatchError and routing the
config-gated vault, Soroban and wallet paths through them.

Root cause

ERROR_CODES[VAULT_CONTRACT_NOT_CONFIGURED].developerHint (src/errors/codes.ts:288)
tells integrators to "Set SDKConfig.contractId before vault calls". But
resolveContractId (src/soroban/index.ts:51) only read params.contractId and
process.env.VAULT_CONTRACT_ID — never the config, even though all three vault
entry points accept a Partial<SDKConfig>.

It also threw the raw string MISSING_CONTRACT_ID, absent from the registry, so
isKnownErrorCode returned false and describeError fell through to
"An unexpected error occurred." fundTestnetAccount had the identical defect with
TESTNET_ONLY. Following the SDK's own documented path produced a generic error.

Changes

  • src/errors/unsupported.ts (new) — both error classes, extending
    PocketPayError, carrying module, operation, capability and a
    suggestedNextStep read from the registry's developerHint so guidance cannot
    drift. toJSON() gives a log-safe view.
  • src/errors/capabilities.ts (new) — capability registry with four statuses:
    supported, config-gated, planned, unsupported, plus assertCapability().
  • src/soroban/index.tsresolveContractId resolves params →
    SDKConfig.contractIdVAULT_CONTRACT_IDSTELLAR_CONTRACT_ID.
  • src/soroban/client-factory.ts — simulation failures use the registry's
    SOROBAN_SIMULATION_FAILED; the vec parameter type reports through
    UnsupportedFeatureError.
  • src/wallet/index.tsfundTestnetAccount uses WALLET_TESTNET_ONLY.
  • Docs — expanded capability_error_standard.md; updated configuration.md,
    error-handling.md, soroban-vault.md.

Planned versus supported

The registry classifies real cases rather than aspirational ones:

  • signer.localsupported.
  • signer.remoteplanned. The Signer interface is deliberately async so
    remote signers can replace LocalSigner (src/account/signer.ts:83-86), but
    none ships; consumers may implement Signer themselves.
  • soroban.param-type.vecunsupported. ScValType advertises vec, but the
    underlying Stellar SDK rejects it. Verified against @stellar/stellar-sdk 13.3.0:
    of the 17 declared types exactly one fails (invalid type: vec). A test asserts
    that throw, so this is reclassified if a future SDK adds support.

No status promises a delivery date; a test asserts that no registry or capability
message contains one.

Error codes

SDK_NOT_IMPLEMENTED and VAULT_CONTRACT_NOT_CONFIGURED were declared with full
specs but had zero call sites; they get their first ones here. One code is added,
WALLET_TESTNET_ONLY, for a network-gated capability that had no registry entry —
codes.ts explicitly allows this ("Add new ones; never edit/recycle existing
ones"). Nothing is renamed or recycled.

Backwards compatibility

  • params.contractId and VAULT_CONTRACT_ID keep working unchanged.
  • Both classes extend PocketPayError, so existing catch blocks and the
    instanceof PocketPayError re-throws in the vault functions are unaffected.
  • Breaking: MISSING_CONTRACT_IDVAULT_CONTRACT_NOT_CONFIGURED and
    TESTNET_ONLYWALLET_TESTNET_ONLY. Neither old string was in the registry.
    Documented in a Migration section. The vault message still contains "contract ID",
    so mapSorobanContractError() and its existing test are unaffected.

Implementation note

PocketPayError's constructor ends with
Object.setPrototypeOf(this, PocketPayError.prototype), which overwrites the
prototype a subclass gets from new.target. Both subclasses restore their own
prototype after super(); without it, instanceof on the subclass silently
returns false and name stays 'PocketPayError'. Two tests cover this.

Tests

34 new tests in tests/unsupported-feature.test.ts covering both classes, the
capability registry, describeError(err.code).known flipping false to true on the
vault and wallet paths, each of the four contract-ID resolution sources,
per-operation metadata, redactError staying safe, instanceof PocketPayError
preserved, and the no-overpromising assertion. tests/fund.test.ts updated for the
new wallet code.

Verification

lint, check:circular (39 modules, no cycles) and build all pass.

Full suite: 46 failed · 677 passed · 1 skipped. The 46 failures are pre-existing
on the base commit — verified by running the suite against a clean checkout of
d7403d8, which gives 46 failed · 643 passed · 1 skipped: same failures in the
same five files (config-validation, destination-validation, types/asset,
retry-policy, payments). This branch adds exactly the 34 new passing tests and
introduces no regressions.

Scope

Limited to the capability-gated paths the issue names. A wider grep found ~25
further error codes outside the registry in payments, transactions and utils; those
are validation errors rather than capability errors and are left untouched rather
than widening this PR.

@El-swaggerito
El-swaggerito merged commit fe0d5f8 into Axionvera:main Jul 27, 2026
1 check passed
@XxHugheadxX
XxHugheadxX deleted the feat/unsupported-feature-capability-errors branch July 27, 2026 22:02
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.

Implement SDK unsupported feature and capability error standard

2 participants