SDK v0.x → v1.0 Migration

Typed errors, retry/timeout options, and the mainnet network flag.

v1.0 is additive and opt-in for most setups, but the Error class hierarchy is a breaking change: callers that currently inspect raw viem/ethers error strings must migrate to instanceof ClearPactError (and its subclasses) before upgrading.

1. Typed Error Classes

v0.x only exposed a single base ClearPactError. Callers that wanted to distinguish a 404 from a 422 had to string-match raw messages from viem or ethers, which silently broke when chains renamed their revert reasons.

v1.0 ships a typed subclass hierarchy. Every error carries a stable err.code, the upstream err.status (HTTP code), and retry-aware fields (err.attempts, err.lastError) on the retry-exhausted subclass.

ClassTriggererr.code
NetworkError DNS / TLS failure or non-JSON body NETWORK_ERROR
EscrowNotFoundError HTTP 404 ESCROW_NOT_FOUND
ContractRevertedError HTTP 422, or body contains "revert" CONTRACT_REVERTED
InsufficientFundsError HTTP 402, or body contains "insufficient" INSUFFICIENT_FUNDS
InvalidAddressError assertAddress() failure INVALID_ADDRESS
ClearPactTimeoutError Per-attempt timeoutMs reached CLEAR_PACT_TIMEOUT
ClearPactRetryExhaustedError Final attempt failed extends ClearPactTimeoutError CLEAR_PACT_RETRY_EXHAUSTED

Catching the base ClearPactError catches every subclass. The retry-exhausted class extends ClearPactTimeoutError, so a single instanceof ClearPactTimeoutError branch handles both.

2. Retry & Timeout Constructor Options

Five new options on the new ClearPact({...}) constructor control per-attempt timeout, total retry count, and the exponential backoff curve. Defaults are conservative; tune them for the latency profile of your upstream RPC.

OptionDefaultMeaning
timeoutMs 30_000 Per-attempt timeout. AbortController cancels the fetch when this fires.
retries 3 Additional attempts after the initial (so 4 total when default).
baseDelayMs 200 Exponential backoff base.
jitterMs ±100 Random jitter added to each backoff sleep.
backoffMaxMs 5_000 Cap on any single backoff sleep so a high retry count can't queue a long wait.

The SDK retries retriable failures only. The rule is: NetworkError, ClearPactTimeoutError, HTTP 429, and any 5xx response retry; everything else (including EscrowNotFoundError and InvalidAddressError) does not — a retry on a 4xx risks double-submitting a mutation, so the SDK surfaces it immediately.

3. Mainnet Network Config Flag

v1.0 makes the target network explicit through a network constructor option. Allowed values: 'testnet' (default) and 'mainnet'. The constructor throws a ClearPactError on any other value.

On 'testnet' the SDK resolves the Base Sepolia USDC contract and the testnet escrow proxy automatically. On 'mainnet' the SDK targets Base (chain 8453) USDC plus the mainnet escrow proxy.

Mainnet is server-gated. Calls against network: 'mainnet' on the deployed platform only execute when MAINNET_ESCROW_CONTRACT_ADDRESS and DEPLOYER_PRIVATE_KEY are configured on the server. You can build and unit-test with network: 'mainnet' against any ClearPact API instance, but live writes require the server-side env vars to be set.

Before / After: new ClearPact() init

v0.x
const client = new ClearPact({
  apiKey: 'cpk_live_…',
});
v1.0
const client = new ClearPact({
  apiKey:    'cpk_live_…',
  network:   'mainnet',   // new; defaults to 'testnet'
  timeoutMs: 15_000,      // new; AbortController per attempt
  retries:   5,           // new; exponential backoff + jitter
});

All four new fields are optional. Omit them to inherit the defaults from TIMEOUT_DEFAULTS: 30_000 ms timeout, 3 retries, 200 ms backoff base, ±100 ms jitter, 5 s cap.

Before / After: catch block

v0.x
try {
  await client.escrow.get('not-a-real-id');
} catch (err) {
  if (err.message?.includes('not found')) {
    return null;
  }
  throw err;
}
v1.0
try {
  await client.jobs.get('not-a-real-id');
} catch (err) {
  if (err instanceof EscrowNotFoundError)          { return null; }
  if (err instanceof ClearPactRetryExhaustedError) { throw err.lastError; }
  if (err instanceof ClearPactError)               { /* err.code + err.status */ }
  throw err;
}

The retry-exhausted branch unwraps err.lastError so handlers see the underlying network/timeout failure rather than the wrapper. Reaching for err.code lets you log by category without enumerating every subclass.