@circle-fin/app-kit 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -8399,7 +8399,8 @@ declare function isFatalError(error: unknown): boolean;
8399
8399
  *
8400
8400
  * @remarks
8401
8401
  * Check order for KitError instances:
8402
- * 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
8402
+ * 1. If `recoverability === 'RETRYABLE'` or `recoverability === 'RESUMABLE'`,
8403
+ * return `true` immediately (priority check).
8403
8404
  * 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
8404
8405
  * 3. Non-KitError instances always return `false`.
8405
8406
  *
@@ -8410,6 +8411,12 @@ declare function isFatalError(error: unknown): boolean;
8410
8411
  * subsequent attempts, such as network timeouts or temporary service
8411
8412
  * unavailability. These errors are safe to retry after a delay.
8412
8413
  *
8414
+ * RESUMABLE errors indicate a multi-phase operation that completed some phases
8415
+ * before failing (for example, a token approval landed but the execution
8416
+ * transaction failed). They are also retryable — re-running the operation is
8417
+ * safe — but callers that have a kit-level `retry()` should prefer it so that
8418
+ * already-completed phases are skipped.
8419
+ *
8413
8420
  * @param error - Unknown error to check
8414
8421
  * @returns True if error is retryable
8415
8422
  *
@@ -8455,9 +8462,19 @@ declare function isFatalError(error: unknown): boolean;
8455
8462
  * })
8456
8463
  * isRetryableError(error3) // false
8457
8464
  *
8465
+ * // KitError with RESUMABLE recoverability (partially-completed operation)
8466
+ * const error4 = new KitError({
8467
+ * code: 8101,
8468
+ * name: 'EARN_EXECUTION_FAILED',
8469
+ * type: 'SERVICE',
8470
+ * recoverability: 'RESUMABLE',
8471
+ * message: 'Execution failed after approval',
8472
+ * })
8473
+ * isRetryableError(error4) // true
8474
+ *
8458
8475
  * // Non-KitError
8459
- * const error4 = new Error('Standard error')
8460
- * isRetryableError(error4) // false
8476
+ * const error5 = new Error('Standard error')
8477
+ * isRetryableError(error5) // false
8461
8478
  * ```
8462
8479
  */
8463
8480
  declare function isRetryableError(error: unknown): boolean;
@@ -14844,6 +14861,134 @@ interface ClaimedRewardsResult {
14844
14861
  * {@link EarningProvider.claimRewards}.
14845
14862
  */
14846
14863
  type ClaimRewardsResult = NoClaimableRewardsResult | ClaimedRewardsResult;
14864
+ /**
14865
+ * Fields shared by every {@link EarnStep} variant.
14866
+ *
14867
+ * @example
14868
+ * ```typescript
14869
+ * const successBase: EarnStepBase = { state: 'success' }
14870
+ *
14871
+ * const errorBase: EarnStepBase = {
14872
+ * state: 'error',
14873
+ * errorMessage: 'Transaction reverted',
14874
+ * error: new Error('Transaction reverted'),
14875
+ * }
14876
+ * ```
14877
+ */
14878
+ interface EarnStepBase {
14879
+ /** Lifecycle state of the phase. */
14880
+ readonly state: 'pending' | 'success' | 'error';
14881
+ /** Human-readable error message, present only when `state` is `'error'`. */
14882
+ readonly errorMessage?: string | undefined;
14883
+ /** Raw error that caused the phase to fail, present only when `state` is `'error'`. */
14884
+ readonly error?: unknown;
14885
+ }
14886
+ /**
14887
+ * A single phase of a multi-phase earn operation.
14888
+ *
14889
+ * Discriminated on `name`. The `approve` and `execute` variants carry an
14890
+ * optional `txHash` once an on-chain transaction has been submitted; the
14891
+ * `fetchParams` variant never has a `txHash` because it is an API call.
14892
+ *
14893
+ * @example
14894
+ * ```typescript
14895
+ * const fetchStep: EarnStep = { name: 'fetchParams', state: 'success' }
14896
+ * const approveStep: EarnStep = { name: 'approve', state: 'success', txHash: '0xabc...' }
14897
+ * const executeStep: EarnStep = { name: 'execute', state: 'error', errorMessage: 'reverted' }
14898
+ * ```
14899
+ */
14900
+ type EarnStep = (EarnStepBase & {
14901
+ readonly name: 'fetchParams';
14902
+ }) | (EarnStepBase & {
14903
+ readonly name: 'approve';
14904
+ readonly txHash?: string | undefined;
14905
+ }) | (EarnStepBase & {
14906
+ readonly name: 'execute';
14907
+ readonly txHash?: string | undefined;
14908
+ });
14909
+ /**
14910
+ * Name of an action that earn operations dispatch through the event system.
14911
+ *
14912
+ * `approve` fires during deposits and withdrawals; the others fire for the
14913
+ * operation of the same name.
14914
+ *
14915
+ * @example
14916
+ * ```typescript
14917
+ * const action: EarnActionName = 'approve'
14918
+ * ```
14919
+ */
14920
+ type EarnActionName = 'approve' | 'deposit' | 'withdraw' | 'claimRewards';
14921
+ /**
14922
+ * Common fields on every earn action payload.
14923
+ *
14924
+ * The `protocol` / `service` discriminators play the same role as the
14925
+ * `protocol` / `version` discriminators on BridgeKit's `CCTPV2ActionBase`:
14926
+ * they keep events distinguishable when earn operations are composed into
14927
+ * multi-protocol flows.
14928
+ *
14929
+ * @example
14930
+ * ```typescript
14931
+ * const base: EarnActionBase = { protocol: 'earn', service: 'earn-service' }
14932
+ * ```
14933
+ */
14934
+ interface EarnActionBase {
14935
+ /** The protocol identifier. */
14936
+ readonly protocol: 'earn';
14937
+ /** The backing service identifier. */
14938
+ readonly service: 'earn-service';
14939
+ }
14940
+ /**
14941
+ * Action map for earn operation event dispatching.
14942
+ *
14943
+ * Each key is an action name and the value is the payload delivered to
14944
+ * handlers registered via the kit's `on()` method. `values` carries the full
14945
+ * {@link EarnStep} for the phase that triggered the event.
14946
+ *
14947
+ * @example
14948
+ * ```typescript
14949
+ * import { EarnKit } from '@circle-fin/earn-kit'
14950
+ *
14951
+ * const kit = new EarnKit()
14952
+ *
14953
+ * kit.on('approve', (payload) => {
14954
+ * if (payload.values.state === 'success') {
14955
+ * console.log('approval tx:', payload.values.txHash)
14956
+ * }
14957
+ * })
14958
+ *
14959
+ * kit.on('deposit', (payload) => {
14960
+ * console.log(`deposit ${payload.method}: ${payload.values.state}`)
14961
+ * })
14962
+ * ```
14963
+ */
14964
+ interface EarnActions {
14965
+ /** Token-approval phase of a deposit or withdrawal. */
14966
+ approve: EarnActionBase & {
14967
+ readonly operation: 'deposit' | 'withdraw';
14968
+ readonly method: 'approve';
14969
+ readonly values: Extract<EarnStep, {
14970
+ name: 'approve';
14971
+ }>;
14972
+ };
14973
+ /** A deposit's `fetchParams` and `execute` phases. */
14974
+ deposit: EarnActionBase & {
14975
+ readonly operation: 'deposit';
14976
+ readonly method: 'fetchParams' | 'execute';
14977
+ readonly values: EarnStep;
14978
+ };
14979
+ /** A withdrawal's `fetchParams` and `execute` phases. */
14980
+ withdraw: EarnActionBase & {
14981
+ readonly operation: 'withdraw';
14982
+ readonly method: 'fetchParams' | 'execute';
14983
+ readonly values: EarnStep;
14984
+ };
14985
+ /** A claim-rewards operation's `fetchParams` and `execute` phases. */
14986
+ claimRewards: EarnActionBase & {
14987
+ readonly operation: 'claimRewards';
14988
+ readonly method: 'fetchParams' | 'execute';
14989
+ readonly values: EarnStep;
14990
+ };
14991
+ }
14847
14992
  /**
14848
14993
  * Result from a deposit quote operation.
14849
14994
  *
@@ -15063,17 +15208,22 @@ interface GetClaimRewardsQuoteServiceParams<T extends AdapterCapabilities = Adap
15063
15208
  *
15064
15209
  * @example
15065
15210
  * ```typescript
15066
- * import type { EarningProvider } from '@circle-fin/provider-earn-service'
15211
+ * import type { EarningProvider, EarnActions } from '@circle-fin/provider-earn-service'
15212
+ * import type { Actionable } from '@core/utils'
15067
15213
  * import { ArcTestnet } from '@core/chains'
15068
15214
  *
15069
15215
  * class CustomProvider implements EarningProvider {
15070
15216
  * readonly name = 'CustomProvider'
15071
15217
  * readonly supportedChains = [ArcTestnet]
15218
+ * actionDispatcher: Actionable<EarnActions> | undefined = undefined
15219
+ * registerDispatcher(dispatcher: Actionable<EarnActions>) { this.actionDispatcher = dispatcher }
15072
15220
  * async getVaults() { return { vaults: [], errors: [] } }
15073
15221
  * async getPosition() { throw new Error('Not implemented') }
15074
15222
  * async deposit() { throw new Error('Not implemented') }
15075
15223
  * async withdraw() { throw new Error('Not implemented') }
15076
15224
  * async claimRewards() { throw new Error('Not implemented') }
15225
+ * supportsRetry(_error: unknown) { return false }
15226
+ * async retry(_error: unknown): Promise<never> { throw new Error('Not implemented') }
15077
15227
  * async getDepositQuote() { throw new Error('Not implemented') }
15078
15228
  * async getWithdrawalQuote() { throw new Error('Not implemented') }
15079
15229
  * async getClaimRewardsQuote() { throw new Error('Not implemented') }
@@ -15085,6 +15235,21 @@ interface EarningProvider {
15085
15235
  readonly name: string;
15086
15236
  /** Chains supported by this provider. */
15087
15237
  readonly supportedChains: readonly ChainDefinition[];
15238
+ /**
15239
+ * Action dispatcher used to emit step-level events during multi-phase
15240
+ * operations. Populated by {@link EarningProvider.registerDispatcher};
15241
+ * `undefined` until a dispatcher is registered.
15242
+ */
15243
+ actionDispatcher?: Actionable<EarnActions> | undefined;
15244
+ /**
15245
+ * Register the action dispatcher the kit uses to broadcast step events.
15246
+ *
15247
+ * Called once by the kit during construction. Implementations should store
15248
+ * the dispatcher and pass it through to every event emission point.
15249
+ *
15250
+ * @param dispatcher - The kit's shared action dispatcher.
15251
+ */
15252
+ registerDispatcher(dispatcher: Actionable<EarnActions>): void;
15088
15253
  /**
15089
15254
  * Retrieve information about one or more vaults.
15090
15255
  *
@@ -15186,6 +15351,43 @@ interface EarningProvider {
15186
15351
  * cannot be validated
15187
15352
  */
15188
15353
  getClaimRewardsQuote<T extends AdapterCapabilities>(params: GetClaimRewardsQuoteServiceParams<T>): Promise<ClaimRewardsQuoteInfo>;
15354
+ /**
15355
+ * Report whether {@link EarningProvider.retry} can resume the operation
15356
+ * that produced `error`.
15357
+ *
15358
+ * Returns `true` only for retryable {@link KitError} instances that carry
15359
+ * earn retry context (an {@link EarnErrorTrace} in `cause.trace`) naming
15360
+ * this provider.
15361
+ *
15362
+ * @param error - The error caught from a previous earn operation.
15363
+ * @returns `true` if the operation can be retried via this provider.
15364
+ */
15365
+ supportsRetry(error: unknown): boolean;
15366
+ /**
15367
+ * Resume a multi-phase earn operation that previously failed.
15368
+ *
15369
+ * Reads the {@link EarnErrorTrace} from `error.cause.trace` to determine
15370
+ * the original operation and inputs, then re-runs it, skipping phases that
15371
+ * already completed (for example a successful token approval).
15372
+ *
15373
+ * @remarks
15374
+ * `retry()` re-runs from the first incomplete phase, so it re-fetches
15375
+ * execution params and re-submits the `execute` transaction. The earn
15376
+ * service deduplicates execution requests server-side, which makes this safe
15377
+ * in the common case. However, if a previous attempt broadcast the `execute`
15378
+ * transaction but failed before its receipt was observed (for example a
15379
+ * transient RPC error), that transaction may still be in flight when
15380
+ * `retry()` re-broadcasts. Treat `retry()` as best-effort recovery, not an
15381
+ * atomic operation; for high-value flows, confirm the original transaction's
15382
+ * status before retrying.
15383
+ *
15384
+ * @param error - The error caught from a previous earn operation. Must be a
15385
+ * retryable {@link KitError} produced by this provider.
15386
+ * @returns Promise resolving to the result of the resumed operation.
15387
+ * @throws {@link KitError} If the error is not a retryable earn error, lacks
15388
+ * resume context, or the resumed operation itself fails.
15389
+ */
15390
+ retry(error: unknown): Promise<EarnDepositResult | EarnWithdrawResult | ClaimRewardsResult>;
15189
15391
  }
15190
15392
 
15191
15393
  /**
@@ -15193,7 +15395,10 @@ interface EarningProvider {
15193
15395
  *
15194
15396
  * Implement the {@link EarningProvider} interface for yield-bearing vault
15195
15397
  * operations including vault discovery, position queries, deposits,
15196
- * withdrawals, and reward claiming.
15398
+ * withdrawals, and reward claiming. Multi-phase operations (deposit,
15399
+ * withdraw, claimRewards) emit step-level events through the kit's action
15400
+ * dispatcher and attach step progress to thrown errors so callers can resume
15401
+ * via {@link EarnServiceProvider.retry}.
15197
15402
  *
15198
15403
  * @example
15199
15404
  * ```typescript
@@ -15224,6 +15429,8 @@ declare class EarnServiceProvider implements EarningProvider {
15224
15429
  readonly name = "EarnService";
15225
15430
  /** {@inheritdoc} */
15226
15431
  readonly supportedChains: readonly ChainDefinition[];
15432
+ /** {@inheritdoc} */
15433
+ actionDispatcher: Actionable<EarnActions> | undefined;
15227
15434
  private readonly defaultConfig?;
15228
15435
  /**
15229
15436
  * Create a new EarnServiceProvider.
@@ -15232,17 +15439,41 @@ declare class EarnServiceProvider implements EarningProvider {
15232
15439
  * Per-operation config takes precedence when provided.
15233
15440
  */
15234
15441
  constructor(config?: EarnServiceConfig);
15442
+ /** {@inheritdoc} */
15443
+ registerDispatcher(dispatcher: Actionable<EarnActions>): void;
15235
15444
  private resolveConfig;
15445
+ /**
15446
+ * Run one phase of a multi-phase earn operation: dispatch a `pending` event,
15447
+ * execute it, then dispatch a `success` event (recording the resulting step)
15448
+ * or — on failure — dispatch an `error` event, record the failed step, and
15449
+ * re-throw the original error for the caller to wrap with full step context.
15450
+ *
15451
+ * @typeParam R - The phase's result type.
15452
+ * @param ctx - The operation context (dispatcher, operation, steps, params).
15453
+ * @param action - The action name to dispatch the events under.
15454
+ * @param stepName - The phase name.
15455
+ * @param run - The phase work to execute.
15456
+ * @param txHashOf - Extracts the on-chain transaction hash from the result, if any.
15457
+ * @returns The phase result.
15458
+ */
15459
+ private runPhase;
15236
15460
  /** {@inheritdoc} */
15237
15461
  getVaults(params: GetVaultsServiceParams): Promise<GetVaultsResult>;
15238
15462
  /** {@inheritdoc} */
15239
15463
  getPosition<T extends AdapterCapabilities>(params: GetPositionServiceParams<T>): Promise<PositionInfo>;
15240
15464
  /** {@inheritdoc} */
15241
15465
  deposit<T extends AdapterCapabilities>(params: DepositServiceParams<T>): Promise<EarnDepositResult>;
15466
+ private runDepositFlow;
15242
15467
  /** {@inheritdoc} */
15243
15468
  withdraw<T extends AdapterCapabilities>(params: WithdrawServiceParams<T>): Promise<EarnWithdrawResult>;
15469
+ private runWithdrawFlow;
15244
15470
  /** {@inheritdoc} */
15245
15471
  claimRewards<T extends AdapterCapabilities>(params: ClaimRewardsServiceParams<T>): Promise<ClaimRewardsResult>;
15472
+ private runClaimRewardsFlow;
15473
+ /** {@inheritdoc} */
15474
+ supportsRetry(error: unknown): boolean;
15475
+ /** {@inheritdoc} */
15476
+ retry(error: unknown): Promise<EarnDepositResult | EarnWithdrawResult | ClaimRewardsResult>;
15246
15477
  /** {@inheritdoc} */
15247
15478
  getDepositQuote<T extends AdapterCapabilities>(params: GetDepositQuoteServiceParams<T>): Promise<DepositQuoteInfo>;
15248
15479
  /** {@inheritdoc} */
@@ -15674,6 +15905,12 @@ interface EarnKitConfig<TExtraProviders extends FlexibleEarningProvider[] = []>
15674
15905
  */
15675
15906
  declare class EarnKit {
15676
15907
  private readonly context;
15908
+ /**
15909
+ * Event dispatcher for step-level events emitted during multi-phase earn
15910
+ * operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
15911
+ * this directly.
15912
+ */
15913
+ readonly actionDispatcher: Actionable<EarnActions>;
15677
15914
  /**
15678
15915
  * Create a new EarnKit instance.
15679
15916
  *
@@ -15692,6 +15929,103 @@ declare class EarnKit {
15692
15929
  * ```
15693
15930
  */
15694
15931
  constructor(config?: EarnKitConfig<FlexibleEarningProvider[]>);
15932
+ /**
15933
+ * Register an event handler for an earn operation action.
15934
+ *
15935
+ * Subscribe to step-level events emitted during deposits, withdrawals, and
15936
+ * reward claims. Handlers receive strongly typed payloads based on the
15937
+ * action name. Use the wildcard `'*'` to receive every event. Multiple
15938
+ * handlers can be registered for the same action.
15939
+ *
15940
+ * Action names:
15941
+ * - `'deposit'` — a deposit's `fetchParams` and `execute` phases
15942
+ * - `'withdraw'` — a withdrawal's `fetchParams` and `execute` phases
15943
+ * - `'claimRewards'` — a claim's `fetchParams` and `execute` phases
15944
+ * - `'approve'` — the token-approval phase of a deposit or withdrawal
15945
+ * - `'*'` — all of the above
15946
+ *
15947
+ * @typeParam K - The action name to listen for.
15948
+ * @param action - The action name, or `'*'` for all actions.
15949
+ * @param handler - Callback invoked when the action occurs.
15950
+ *
15951
+ * @example
15952
+ * ```typescript
15953
+ * const kit = new EarnKit()
15954
+ *
15955
+ * kit.on('withdraw', (payload) => {
15956
+ * if (payload.values.name === 'execute' && payload.values.state === 'success') {
15957
+ * console.log('withdrawal tx:', payload.values.txHash)
15958
+ * }
15959
+ * })
15960
+ *
15961
+ * kit.on('*', (payload) => {
15962
+ * console.log(`${payload.operation}/${payload.method}: ${payload.values.state}`)
15963
+ * })
15964
+ * ```
15965
+ */
15966
+ on<K extends EarnActionName>(action: K, handler: (payload: EarnActions[K]) => void): void;
15967
+ on(action: '*', handler: (payload: EarnActions[EarnActionName]) => void): void;
15968
+ /**
15969
+ * Unregister a previously registered event handler.
15970
+ *
15971
+ * Pass the same handler function reference used during registration. Use
15972
+ * the wildcard `'*'` to remove a handler that was listening to all actions.
15973
+ *
15974
+ * @typeParam K - The action name to stop listening for.
15975
+ * @param action - The action name, or `'*'` for all actions.
15976
+ * @param handler - The handler reference to remove.
15977
+ *
15978
+ * @example
15979
+ * ```typescript
15980
+ * const kit = new EarnKit()
15981
+ *
15982
+ * const handler = (payload: EarnActions['deposit']) => console.log(payload.method)
15983
+ * kit.on('deposit', handler)
15984
+ * // ...later
15985
+ * kit.off('deposit', handler)
15986
+ * ```
15987
+ */
15988
+ off<K extends EarnActionName>(action: K, handler: (payload: EarnActions[K]) => void): void;
15989
+ off(action: '*', handler: (payload: EarnActions[EarnActionName]) => void): void;
15990
+ /**
15991
+ * Resume a multi-phase earn operation that previously failed.
15992
+ *
15993
+ * Pass the {@link KitError} caught from a `deposit`, `withdraw`, or
15994
+ * `claimRewards` call. The error carries the original operation, inputs,
15995
+ * and step progress, so the operation can be re-run while skipping phases
15996
+ * that already completed (for example a successful token approval). Use
15997
+ * `isRetryableError(error)` to check whether retrying is worthwhile before
15998
+ * calling this.
15999
+ *
16000
+ * @remarks
16001
+ * Resuming re-fetches execution params and re-submits the `execute`
16002
+ * transaction; the earn service deduplicates execution server-side, which
16003
+ * makes this safe in the common case. But if a prior attempt broadcast the
16004
+ * `execute` transaction and then failed before its receipt was observed,
16005
+ * that transaction may still be in flight when `retry()` re-broadcasts.
16006
+ * Treat `retry()` as best-effort recovery, not an atomic operation.
16007
+ *
16008
+ * @param error - The error caught from a previous multi-phase earn operation.
16009
+ * @returns A promise resolving to the result of the resumed operation.
16010
+ * @throws {@link KitError} If `error` is not a retryable {@link KitError}
16011
+ * carrying earn retry context, names an unknown provider, or the resumed
16012
+ * operation itself fails.
16013
+ *
16014
+ * @example
16015
+ * ```typescript
16016
+ * import { isRetryableError } from '@circle-fin/earn-kit'
16017
+ *
16018
+ * try {
16019
+ * await kit.deposit(params)
16020
+ * } catch (error) {
16021
+ * if (isRetryableError(error)) {
16022
+ * const result = await kit.retry(error)
16023
+ * console.log('recovered, tx:', 'txHash' in result ? result.txHash : undefined)
16024
+ * }
16025
+ * }
16026
+ * ```
16027
+ */
16028
+ retry(error: unknown): Promise<EarnDepositResult | EarnWithdrawResult | EarnClaimRewardsResult>;
15695
16029
  /**
15696
16030
  * Return the chains supported by configured earn providers.
15697
16031
  *