@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/CHANGELOG.md +16 -0
- package/README.md +6 -4
- package/earn.cjs +970 -172
- package/earn.mjs +970 -172
- package/index.cjs +739 -272
- package/index.d.ts +339 -5
- package/index.mjs +739 -272
- package/package.json +6 -6
package/index.mjs
CHANGED
|
@@ -2301,7 +2301,8 @@ const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
|
2301
2301
|
*
|
|
2302
2302
|
* @remarks
|
|
2303
2303
|
* Check order for KitError instances:
|
|
2304
|
-
* 1. If `recoverability === 'RETRYABLE'
|
|
2304
|
+
* 1. If `recoverability === 'RETRYABLE'` or `recoverability === 'RESUMABLE'`,
|
|
2305
|
+
* return `true` immediately (priority check).
|
|
2305
2306
|
* 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
|
|
2306
2307
|
* 3. Non-KitError instances always return `false`.
|
|
2307
2308
|
*
|
|
@@ -2312,6 +2313,12 @@ const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
|
2312
2313
|
* subsequent attempts, such as network timeouts or temporary service
|
|
2313
2314
|
* unavailability. These errors are safe to retry after a delay.
|
|
2314
2315
|
*
|
|
2316
|
+
* RESUMABLE errors indicate a multi-phase operation that completed some phases
|
|
2317
|
+
* before failing (for example, a token approval landed but the execution
|
|
2318
|
+
* transaction failed). They are also retryable — re-running the operation is
|
|
2319
|
+
* safe — but callers that have a kit-level `retry()` should prefer it so that
|
|
2320
|
+
* already-completed phases are skipped.
|
|
2321
|
+
*
|
|
2315
2322
|
* @param error - Unknown error to check
|
|
2316
2323
|
* @returns True if error is retryable
|
|
2317
2324
|
*
|
|
@@ -2357,16 +2364,29 @@ const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
|
2357
2364
|
* })
|
|
2358
2365
|
* isRetryableError(error3) // false
|
|
2359
2366
|
*
|
|
2367
|
+
* // KitError with RESUMABLE recoverability (partially-completed operation)
|
|
2368
|
+
* const error4 = new KitError({
|
|
2369
|
+
* code: 8101,
|
|
2370
|
+
* name: 'EARN_EXECUTION_FAILED',
|
|
2371
|
+
* type: 'SERVICE',
|
|
2372
|
+
* recoverability: 'RESUMABLE',
|
|
2373
|
+
* message: 'Execution failed after approval',
|
|
2374
|
+
* })
|
|
2375
|
+
* isRetryableError(error4) // true
|
|
2376
|
+
*
|
|
2360
2377
|
* // Non-KitError
|
|
2361
|
-
* const
|
|
2362
|
-
* isRetryableError(
|
|
2378
|
+
* const error5 = new Error('Standard error')
|
|
2379
|
+
* isRetryableError(error5) // false
|
|
2363
2380
|
* ```
|
|
2364
2381
|
*/
|
|
2365
2382
|
function isRetryableError$1(error) {
|
|
2366
2383
|
// Use proper type guard to check if it's a KitError
|
|
2367
2384
|
if (isKitError(error)) {
|
|
2368
|
-
// Priority check: explicit recoverability
|
|
2369
|
-
|
|
2385
|
+
// Priority check: explicit recoverability. RESUMABLE errors are a subset of
|
|
2386
|
+
// retryable errors — re-running the operation is safe, but a kit-level
|
|
2387
|
+
// retry() can resume from the failed phase instead.
|
|
2388
|
+
if (error.recoverability === 'RETRYABLE' ||
|
|
2389
|
+
error.recoverability === 'RESUMABLE') {
|
|
2370
2390
|
return true;
|
|
2371
2391
|
}
|
|
2372
2392
|
// Fallback check: error code against default retryable codes
|
|
@@ -11039,7 +11059,7 @@ function emitResultStepErrorTelemetry(failedStep, stepEventMap, fallbackEventTyp
|
|
|
11039
11059
|
}
|
|
11040
11060
|
|
|
11041
11061
|
var name$3 = "@circle-fin/bridge-kit";
|
|
11042
|
-
var version$4 = "1.10.
|
|
11062
|
+
var version$4 = "1.10.2";
|
|
11043
11063
|
var pkg$4 = {
|
|
11044
11064
|
name: name$3,
|
|
11045
11065
|
version: version$4};
|
|
@@ -11530,60 +11550,6 @@ const validateBalanceForTransaction = async (params) => {
|
|
|
11530
11550
|
}
|
|
11531
11551
|
};
|
|
11532
11552
|
|
|
11533
|
-
/**
|
|
11534
|
-
* Validate that the adapter has sufficient native token balance for transaction fees.
|
|
11535
|
-
*
|
|
11536
|
-
* This function checks if the adapter's current native token balance (ETH, SOL, etc.)
|
|
11537
|
-
* is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
|
|
11538
|
-
* if the balance is zero, indicating the wallet cannot pay for transaction fees.
|
|
11539
|
-
*
|
|
11540
|
-
* @param params - The validation parameters containing adapter and operation context.
|
|
11541
|
-
* @returns A promise that resolves to void if validation passes.
|
|
11542
|
-
* @throws {KitError} When the adapter's native balance is zero (code: 9002).
|
|
11543
|
-
*
|
|
11544
|
-
* @example
|
|
11545
|
-
* ```typescript
|
|
11546
|
-
* import { validateNativeBalanceForTransaction } from '@core/adapter'
|
|
11547
|
-
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
11548
|
-
* import { isKitError, ERROR_TYPES } from '@core/errors'
|
|
11549
|
-
*
|
|
11550
|
-
* const adapter = createViemAdapterFromPrivateKey({
|
|
11551
|
-
* privateKey: '0x...',
|
|
11552
|
-
* chain: 'Ethereum',
|
|
11553
|
-
* })
|
|
11554
|
-
*
|
|
11555
|
-
* try {
|
|
11556
|
-
* await validateNativeBalanceForTransaction({
|
|
11557
|
-
* adapter,
|
|
11558
|
-
* operationContext: { chain: 'Ethereum' },
|
|
11559
|
-
* })
|
|
11560
|
-
* console.log('Native balance validation passed')
|
|
11561
|
-
* } catch (error) {
|
|
11562
|
-
* if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
|
|
11563
|
-
* console.error('Insufficient gas funds:', error.message)
|
|
11564
|
-
* }
|
|
11565
|
-
* }
|
|
11566
|
-
* ```
|
|
11567
|
-
*/
|
|
11568
|
-
const validateNativeBalanceForTransaction = async (params) => {
|
|
11569
|
-
const { adapter, operationContext } = params;
|
|
11570
|
-
const balancePrepared = await adapter.prepareAction('native.balanceOf', {
|
|
11571
|
-
walletAddress: operationContext.address,
|
|
11572
|
-
}, operationContext);
|
|
11573
|
-
const balance = await balancePrepared.execute();
|
|
11574
|
-
if (BigInt(balance) === 0n) {
|
|
11575
|
-
const { chain } = operationContext;
|
|
11576
|
-
const chainName = extractChainInfo(chain).name;
|
|
11577
|
-
const nativeSymbol = typeof chain === 'object' && chain !== null && 'nativeCurrency' in chain
|
|
11578
|
-
? chain.nativeCurrency.symbol
|
|
11579
|
-
: undefined;
|
|
11580
|
-
throw createInsufficientGasError(chainName, nativeSymbol, {
|
|
11581
|
-
balance: '0',
|
|
11582
|
-
walletAddress: operationContext.address,
|
|
11583
|
-
});
|
|
11584
|
-
}
|
|
11585
|
-
};
|
|
11586
|
-
|
|
11587
11553
|
/**
|
|
11588
11554
|
* Permit signature standards for gasless token approvals.
|
|
11589
11555
|
*
|
|
@@ -16015,7 +15981,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCo
|
|
|
16015
15981
|
return step;
|
|
16016
15982
|
}
|
|
16017
15983
|
|
|
16018
|
-
var version$3 = "1.8.
|
|
15984
|
+
var version$3 = "1.8.3";
|
|
16019
15985
|
var pkg$3 = {
|
|
16020
15986
|
version: version$3};
|
|
16021
15987
|
|
|
@@ -16833,7 +16799,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
16833
16799
|
async bridge(params) {
|
|
16834
16800
|
// CCTP-specific bridge params validation (includes base validation)
|
|
16835
16801
|
assertCCTPv2BridgeParams(params);
|
|
16836
|
-
const { source,
|
|
16802
|
+
const { source, amount, token } = params;
|
|
16837
16803
|
// Extract operation context from source wallet context for balance validation
|
|
16838
16804
|
const sourceOperationContext = this.extractOperationContext(source);
|
|
16839
16805
|
// Validate USDC balance for transaction on source chain
|
|
@@ -16844,24 +16810,6 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
16844
16810
|
tokenAddress: source.chain.usdcAddress,
|
|
16845
16811
|
operationContext: sourceOperationContext,
|
|
16846
16812
|
});
|
|
16847
|
-
// Validate native balance > 0 for gas fees on source chain
|
|
16848
|
-
await validateNativeBalanceForTransaction({
|
|
16849
|
-
adapter: source.adapter,
|
|
16850
|
-
operationContext: sourceOperationContext,
|
|
16851
|
-
});
|
|
16852
|
-
// Only validate destination native balance if there's an adapter
|
|
16853
|
-
// and forwarder is not being used (forwarder handles mint, no gas needed)
|
|
16854
|
-
if ('adapter' in destination &&
|
|
16855
|
-
destination.adapter &&
|
|
16856
|
-
!destination.useForwarder) {
|
|
16857
|
-
// Extract operation context from destination wallet context
|
|
16858
|
-
const destinationOperationContext = this.extractOperationContext(destination);
|
|
16859
|
-
// Validate native balance > 0 for gas fees on destination chain
|
|
16860
|
-
await validateNativeBalanceForTransaction({
|
|
16861
|
-
adapter: destination.adapter,
|
|
16862
|
-
operationContext: destinationOperationContext,
|
|
16863
|
-
});
|
|
16864
|
-
}
|
|
16865
16813
|
return bridge$1(params, this);
|
|
16866
16814
|
}
|
|
16867
16815
|
/**
|
|
@@ -18414,7 +18362,7 @@ const createBridgeKit = (context) => {
|
|
|
18414
18362
|
};
|
|
18415
18363
|
|
|
18416
18364
|
var name$2 = "@circle-fin/swap-kit";
|
|
18417
|
-
var version$2 = "1.2.
|
|
18365
|
+
var version$2 = "1.2.3";
|
|
18418
18366
|
var pkg$2 = {
|
|
18419
18367
|
name: name$2,
|
|
18420
18368
|
version: version$2};
|
|
@@ -29577,7 +29525,7 @@ const createSwapKit = (context) => {
|
|
|
29577
29525
|
};
|
|
29578
29526
|
|
|
29579
29527
|
var name$1 = "@circle-fin/earn-kit";
|
|
29580
|
-
var version$1 = "1.
|
|
29528
|
+
var version$1 = "1.1.0";
|
|
29581
29529
|
var pkg$1 = {
|
|
29582
29530
|
name: name$1,
|
|
29583
29531
|
version: version$1};
|
|
@@ -29987,6 +29935,253 @@ function validateExecutionDeadline(executionParams) {
|
|
|
29987
29935
|
}
|
|
29988
29936
|
}
|
|
29989
29937
|
|
|
29938
|
+
/** Base discriminators shared by every earn action payload. */
|
|
29939
|
+
const EARN_ACTION_BASE = {
|
|
29940
|
+
protocol: 'earn',
|
|
29941
|
+
service: 'earn-service',
|
|
29942
|
+
};
|
|
29943
|
+
/**
|
|
29944
|
+
* Dispatch a step event for an earn operation through the kit's action
|
|
29945
|
+
* dispatcher.
|
|
29946
|
+
*
|
|
29947
|
+
* Builds the action payload for the given action/operation/step combination
|
|
29948
|
+
* and forwards it to any registered listeners (including wildcard `'*'`
|
|
29949
|
+
* listeners). The call is a no-op when no dispatcher has been registered, so
|
|
29950
|
+
* callers can invoke it unconditionally.
|
|
29951
|
+
*
|
|
29952
|
+
* The `approve` action only fires for `deposit` and `withdraw` operations and
|
|
29953
|
+
* only carries `approve` steps; the `deposit`, `withdraw`, and `claimRewards`
|
|
29954
|
+
* actions carry only `fetchParams` and `execute` steps. Mismatched
|
|
29955
|
+
* combinations are ignored defensively.
|
|
29956
|
+
*
|
|
29957
|
+
* @param dispatcher - The action dispatcher, or `undefined` if none registered.
|
|
29958
|
+
* @param action - The action name to dispatch under.
|
|
29959
|
+
* @param operation - The earn operation the step belongs to.
|
|
29960
|
+
* @param step - The step to deliver as the event payload.
|
|
29961
|
+
*
|
|
29962
|
+
* @example
|
|
29963
|
+
* ```typescript
|
|
29964
|
+
* dispatchEarnEvent(dispatcher, 'deposit', 'deposit', {
|
|
29965
|
+
* name: 'execute',
|
|
29966
|
+
* state: 'success',
|
|
29967
|
+
* txHash: '0xabc...',
|
|
29968
|
+
* })
|
|
29969
|
+
* ```
|
|
29970
|
+
*/
|
|
29971
|
+
function dispatchEarnEvent(dispatcher, action, operation, step) {
|
|
29972
|
+
if (dispatcher === undefined) {
|
|
29973
|
+
return;
|
|
29974
|
+
}
|
|
29975
|
+
// Listener errors must never abort the financial operation flow. A throwing
|
|
29976
|
+
// listener on a 'success' event after a transaction has landed on-chain
|
|
29977
|
+
// would otherwise propagate to the caller as a RESUMABLE error and trigger
|
|
29978
|
+
// a retry that re-submits the same transaction (double-spend risk).
|
|
29979
|
+
try {
|
|
29980
|
+
if (action === 'approve') {
|
|
29981
|
+
// The approve action only applies to deposit/withdraw approval steps.
|
|
29982
|
+
if (step.name !== 'approve' || operation === 'claimRewards') {
|
|
29983
|
+
return;
|
|
29984
|
+
}
|
|
29985
|
+
dispatcher.dispatch('approve', {
|
|
29986
|
+
...EARN_ACTION_BASE,
|
|
29987
|
+
operation,
|
|
29988
|
+
method: 'approve',
|
|
29989
|
+
values: step,
|
|
29990
|
+
});
|
|
29991
|
+
return;
|
|
29992
|
+
}
|
|
29993
|
+
// deposit / withdraw / claimRewards actions carry fetchParams + execute steps.
|
|
29994
|
+
if (step.name === 'approve') {
|
|
29995
|
+
return;
|
|
29996
|
+
}
|
|
29997
|
+
const payload = { ...EARN_ACTION_BASE, method: step.name, values: step };
|
|
29998
|
+
if (action === 'deposit') {
|
|
29999
|
+
dispatcher.dispatch('deposit', { ...payload, operation: 'deposit' });
|
|
30000
|
+
return;
|
|
30001
|
+
}
|
|
30002
|
+
if (action === 'withdraw') {
|
|
30003
|
+
dispatcher.dispatch('withdraw', { ...payload, operation: 'withdraw' });
|
|
30004
|
+
return;
|
|
30005
|
+
}
|
|
30006
|
+
if (action === 'claimRewards') {
|
|
30007
|
+
dispatcher.dispatch('claimRewards', {
|
|
30008
|
+
...payload,
|
|
30009
|
+
operation: 'claimRewards',
|
|
30010
|
+
});
|
|
30011
|
+
return;
|
|
30012
|
+
}
|
|
30013
|
+
}
|
|
30014
|
+
catch {
|
|
30015
|
+
// Swallow listener errors. Observability of the failure is the listener's
|
|
30016
|
+
// responsibility; the operation flow continues unaffected.
|
|
30017
|
+
return;
|
|
30018
|
+
}
|
|
30019
|
+
// Reached only when `action` is not a known earn action. Thrown outside the
|
|
30020
|
+
// try/catch so a missing case surfaces as a developer error instead of being
|
|
30021
|
+
// silently swallowed alongside listener failures.
|
|
30022
|
+
const exhaustive = action;
|
|
30023
|
+
throw new Error(`Unhandled earn action: ${String(exhaustive)}`);
|
|
30024
|
+
}
|
|
30025
|
+
|
|
30026
|
+
const EARN_OPERATIONS = new Set([
|
|
30027
|
+
'deposit',
|
|
30028
|
+
'withdraw',
|
|
30029
|
+
'claimRewards',
|
|
30030
|
+
]);
|
|
30031
|
+
/**
|
|
30032
|
+
* Operations whose service params carry a top-level `amount` input field
|
|
30033
|
+
* (`deposit` and `withdraw`); `claimRewards` params have no `amount`. This
|
|
30034
|
+
* refers to the request input — distinct from the reward `amount` that appears
|
|
30035
|
+
* inside a claimRewards *result*.
|
|
30036
|
+
*/
|
|
30037
|
+
const OPERATIONS_WITH_AMOUNT = new Set([
|
|
30038
|
+
'deposit',
|
|
30039
|
+
'withdraw',
|
|
30040
|
+
]);
|
|
30041
|
+
/**
|
|
30042
|
+
* Validate that `params` carries the minimum shape `retry()` needs to resume
|
|
30043
|
+
* the given operation: an adapter context (`from`) and a `vaultAddress`, plus
|
|
30044
|
+
* an `amount` for `deposit`/`withdraw`. This is a structural sanity check, not
|
|
30045
|
+
* a full schema validation — operations re-validate their inputs when re-run —
|
|
30046
|
+
* but it lets a mismatched or forged trace fail the {@link isEarnErrorTrace}
|
|
30047
|
+
* guard cleanly instead of crashing deep inside the resume path.
|
|
30048
|
+
*/
|
|
30049
|
+
function hasEarnServiceParamsShape(operation, params) {
|
|
30050
|
+
if (params['from'] === null || typeof params['from'] !== 'object') {
|
|
30051
|
+
return false;
|
|
30052
|
+
}
|
|
30053
|
+
if (typeof params['vaultAddress'] !== 'string') {
|
|
30054
|
+
return false;
|
|
30055
|
+
}
|
|
30056
|
+
if (OPERATIONS_WITH_AMOUNT.has(operation) &&
|
|
30057
|
+
typeof params['amount'] !== 'string') {
|
|
30058
|
+
return false;
|
|
30059
|
+
}
|
|
30060
|
+
return true;
|
|
30061
|
+
}
|
|
30062
|
+
/**
|
|
30063
|
+
* Wrap an error thrown during a multi-phase earn operation with step-tracking
|
|
30064
|
+
* and resume context.
|
|
30065
|
+
*
|
|
30066
|
+
* Produces a new {@link KitError} that preserves the original error's code,
|
|
30067
|
+
* name, type, and message, merges any existing `cause.trace`, and adds an
|
|
30068
|
+
* {@link EarnErrorTrace} (`provider`, `operation`, `steps`, `params`). When a
|
|
30069
|
+
* prior phase had already committed work (a successful step exists) and the
|
|
30070
|
+
* underlying error is retryable, the recoverability is upgraded to
|
|
30071
|
+
* `RESUMABLE` so callers know `retry()` can resume from the failed phase. A
|
|
30072
|
+
* fatal underlying error stays fatal — it cannot be resumed.
|
|
30073
|
+
*
|
|
30074
|
+
* Non-{@link KitError} inputs (which should not occur on provider code paths)
|
|
30075
|
+
* are normalized to a retryable {@link ServiceError.INTERNAL_ERROR}.
|
|
30076
|
+
*
|
|
30077
|
+
* @param error - The error caught from a phase of the operation.
|
|
30078
|
+
* @param context - The operation context used to build the trace.
|
|
30079
|
+
* @returns A new {@link KitError} carrying the {@link EarnErrorTrace}.
|
|
30080
|
+
*
|
|
30081
|
+
* @example
|
|
30082
|
+
* ```typescript
|
|
30083
|
+
* try {
|
|
30084
|
+
* await executeEarnAction(...)
|
|
30085
|
+
* } catch (error) {
|
|
30086
|
+
* throw augmentEarnError(error, {
|
|
30087
|
+
* providerName: 'EarnService',
|
|
30088
|
+
* operation: 'deposit',
|
|
30089
|
+
* steps,
|
|
30090
|
+
* params,
|
|
30091
|
+
* })
|
|
30092
|
+
* }
|
|
30093
|
+
* ```
|
|
30094
|
+
*/
|
|
30095
|
+
function augmentEarnError(error, context) {
|
|
30096
|
+
const base = isKitError(error)
|
|
30097
|
+
? error
|
|
30098
|
+
: new KitError({
|
|
30099
|
+
...ServiceError.INTERNAL_ERROR,
|
|
30100
|
+
recoverability: 'RETRYABLE',
|
|
30101
|
+
message: getErrorMessage(error),
|
|
30102
|
+
});
|
|
30103
|
+
// Only upgrade to RESUMABLE when a prior *on-chain* phase succeeded. The
|
|
30104
|
+
// RESUMABLE label promises retry() can skip already-committed work — true
|
|
30105
|
+
// for a landed `approve` or `execute`, but a successful `fetchParams` saves
|
|
30106
|
+
// nothing (retry() re-fetches it anyway, since execution params/deadlines
|
|
30107
|
+
// expire), so it stays a plain RETRYABLE.
|
|
30108
|
+
const priorOnChainPhaseSucceeded = context.steps.some((step) => step.state === 'success' &&
|
|
30109
|
+
(step.name === 'approve' || step.name === 'execute'));
|
|
30110
|
+
const recoverability = priorOnChainPhaseSucceeded && base.recoverability === 'RETRYABLE'
|
|
30111
|
+
? 'RESUMABLE'
|
|
30112
|
+
: base.recoverability;
|
|
30113
|
+
const existingTrace = base.cause?.trace !== undefined &&
|
|
30114
|
+
base.cause.trace !== null &&
|
|
30115
|
+
typeof base.cause.trace === 'object'
|
|
30116
|
+
? base.cause.trace
|
|
30117
|
+
: {};
|
|
30118
|
+
// Snapshot the steps so the trace `retry()` reads can't be mutated by a
|
|
30119
|
+
// future code path that retains the live `EarnRunContext.steps` array.
|
|
30120
|
+
const trace = {
|
|
30121
|
+
...existingTrace,
|
|
30122
|
+
provider: context.providerName,
|
|
30123
|
+
operation: context.operation,
|
|
30124
|
+
steps: [...context.steps],
|
|
30125
|
+
};
|
|
30126
|
+
// Store the original service params non-enumerably. `retry()` reads them by
|
|
30127
|
+
// direct property access (`trace.params`), but JSON.stringify, console.log,
|
|
30128
|
+
// and telemetry serializers skip non-enumerable properties — so a
|
|
30129
|
+
// permissioned call's `config.kitKey` (a `KIT_KEY:<id>:<secret>` value) and
|
|
30130
|
+
// the in-memory adapter never leak through a logged error. defineProperty
|
|
30131
|
+
// also redefines any enumerable `params` an upstream trace may have carried.
|
|
30132
|
+
Object.defineProperty(trace, 'params', {
|
|
30133
|
+
value: context.params,
|
|
30134
|
+
enumerable: false,
|
|
30135
|
+
writable: true,
|
|
30136
|
+
configurable: true,
|
|
30137
|
+
});
|
|
30138
|
+
return new KitError({
|
|
30139
|
+
code: base.code,
|
|
30140
|
+
name: base.name,
|
|
30141
|
+
type: base.type,
|
|
30142
|
+
recoverability,
|
|
30143
|
+
message: base.message,
|
|
30144
|
+
cause: { trace },
|
|
30145
|
+
});
|
|
30146
|
+
}
|
|
30147
|
+
/**
|
|
30148
|
+
* Runtime type guard for {@link EarnErrorTrace}.
|
|
30149
|
+
*
|
|
30150
|
+
* Verifies the value carries the fields `retry()` needs to resume an earn
|
|
30151
|
+
* operation: a provider name, a known operation, a steps array, and a `params`
|
|
30152
|
+
* object whose shape matches that operation (an adapter context and vault
|
|
30153
|
+
* address, plus an amount for `deposit`/`withdraw`). Used to validate
|
|
30154
|
+
* `KitError.cause.trace` before resuming so a mismatched or forged trace is
|
|
30155
|
+
* rejected cleanly rather than crashing inside the resume path.
|
|
30156
|
+
*
|
|
30157
|
+
* @param value - The candidate value (typically `error.cause?.trace`).
|
|
30158
|
+
* @returns `true` if `value` is a usable {@link EarnErrorTrace}.
|
|
30159
|
+
*
|
|
30160
|
+
* @example
|
|
30161
|
+
* ```typescript
|
|
30162
|
+
* const trace = error.cause?.trace
|
|
30163
|
+
* if (isEarnErrorTrace(trace)) {
|
|
30164
|
+
* console.log(trace.operation, trace.steps.length)
|
|
30165
|
+
* }
|
|
30166
|
+
* ```
|
|
30167
|
+
*/
|
|
30168
|
+
function isEarnErrorTrace(value) {
|
|
30169
|
+
if (value === null || typeof value !== 'object') {
|
|
30170
|
+
return false;
|
|
30171
|
+
}
|
|
30172
|
+
const candidate = value;
|
|
30173
|
+
const operation = candidate['operation'];
|
|
30174
|
+
if (typeof candidate['provider'] !== 'string' ||
|
|
30175
|
+
typeof operation !== 'string' ||
|
|
30176
|
+
!EARN_OPERATIONS.has(operation) ||
|
|
30177
|
+
!Array.isArray(candidate['steps']) ||
|
|
30178
|
+
candidate['params'] === null ||
|
|
30179
|
+
typeof candidate['params'] !== 'object') {
|
|
30180
|
+
return false;
|
|
30181
|
+
}
|
|
30182
|
+
return hasEarnServiceParamsShape(operation, candidate['params']);
|
|
30183
|
+
}
|
|
30184
|
+
|
|
29990
30185
|
// ---------------------------------------------------------------------------
|
|
29991
30186
|
// Shared primitives
|
|
29992
30187
|
// ---------------------------------------------------------------------------
|
|
@@ -30974,12 +31169,63 @@ async function fetchClaimRewardsQuote(params) {
|
|
|
30974
31169
|
}
|
|
30975
31170
|
}
|
|
30976
31171
|
|
|
31172
|
+
/**
|
|
31173
|
+
* Build the `pending` {@link EarnStep} for a phase about to start,
|
|
31174
|
+
* discriminating on `stepName` so the result is a typed union member rather
|
|
31175
|
+
* than an unsafe `as EarnStep` cast.
|
|
31176
|
+
*
|
|
31177
|
+
* @param stepName - The name of the phase about to start.
|
|
31178
|
+
* @returns The typed pending step for the phase.
|
|
31179
|
+
*/
|
|
31180
|
+
function buildPendingStep(stepName) {
|
|
31181
|
+
if (stepName === 'fetchParams') {
|
|
31182
|
+
return { name: 'fetchParams', state: 'pending' };
|
|
31183
|
+
}
|
|
31184
|
+
return { name: stepName, state: 'pending' };
|
|
31185
|
+
}
|
|
31186
|
+
/**
|
|
31187
|
+
* Build the `success` {@link EarnStep} for a completed phase, discriminating
|
|
31188
|
+
* on `stepName` so the `fetchParams` variant (which has no `txHash` in its
|
|
31189
|
+
* type) cannot accidentally carry a transaction hash.
|
|
31190
|
+
*
|
|
31191
|
+
* @param stepName - The name of the completed phase.
|
|
31192
|
+
* @param txHash - The on-chain transaction hash, if any.
|
|
31193
|
+
* @returns The typed success step for the phase.
|
|
31194
|
+
*/
|
|
31195
|
+
function buildSuccessStep(stepName, txHash) {
|
|
31196
|
+
if (stepName === 'fetchParams') {
|
|
31197
|
+
return { name: 'fetchParams', state: 'success' };
|
|
31198
|
+
}
|
|
31199
|
+
if (txHash === undefined) {
|
|
31200
|
+
return { name: stepName, state: 'success' };
|
|
31201
|
+
}
|
|
31202
|
+
return { name: stepName, state: 'success', txHash };
|
|
31203
|
+
}
|
|
31204
|
+
/**
|
|
31205
|
+
* Build the `error` {@link EarnStep} for a failed phase, discriminating on
|
|
31206
|
+
* `stepName` so the result is a typed union member rather than an unsafe
|
|
31207
|
+
* `as EarnStep` cast.
|
|
31208
|
+
*
|
|
31209
|
+
* @param stepName - The name of the failed phase.
|
|
31210
|
+
* @param errorMessage - The human-readable failure message.
|
|
31211
|
+
* @param error - The raw error that caused the phase to fail.
|
|
31212
|
+
* @returns The typed error step for the phase.
|
|
31213
|
+
*/
|
|
31214
|
+
function buildErrorStep(stepName, errorMessage, error) {
|
|
31215
|
+
if (stepName === 'fetchParams') {
|
|
31216
|
+
return { name: 'fetchParams', state: 'error', errorMessage, error };
|
|
31217
|
+
}
|
|
31218
|
+
return { name: stepName, state: 'error', errorMessage, error };
|
|
31219
|
+
}
|
|
30977
31220
|
/**
|
|
30978
31221
|
* Earn Service provider for the Circle earn service API.
|
|
30979
31222
|
*
|
|
30980
31223
|
* Implement the {@link EarningProvider} interface for yield-bearing vault
|
|
30981
31224
|
* operations including vault discovery, position queries, deposits,
|
|
30982
|
-
* withdrawals, and reward claiming.
|
|
31225
|
+
* withdrawals, and reward claiming. Multi-phase operations (deposit,
|
|
31226
|
+
* withdraw, claimRewards) emit step-level events through the kit's action
|
|
31227
|
+
* dispatcher and attach step progress to thrown errors so callers can resume
|
|
31228
|
+
* via {@link EarnServiceProvider.retry}.
|
|
30983
31229
|
*
|
|
30984
31230
|
* @example
|
|
30985
31231
|
* ```typescript
|
|
@@ -31010,6 +31256,8 @@ class EarnServiceProvider {
|
|
|
31010
31256
|
name = 'EarnService';
|
|
31011
31257
|
/** {@inheritdoc} */
|
|
31012
31258
|
supportedChains = Object.keys(CHAIN_TO_API).map((chain) => resolveChainIdentifier(chain));
|
|
31259
|
+
/** {@inheritdoc} */
|
|
31260
|
+
actionDispatcher = undefined;
|
|
31013
31261
|
defaultConfig;
|
|
31014
31262
|
/**
|
|
31015
31263
|
* Create a new EarnServiceProvider.
|
|
@@ -31020,12 +31268,49 @@ class EarnServiceProvider {
|
|
|
31020
31268
|
constructor(config) {
|
|
31021
31269
|
this.defaultConfig = config;
|
|
31022
31270
|
}
|
|
31271
|
+
/** {@inheritdoc} */
|
|
31272
|
+
registerDispatcher(dispatcher) {
|
|
31273
|
+
this.actionDispatcher = dispatcher;
|
|
31274
|
+
}
|
|
31023
31275
|
resolveConfig(config) {
|
|
31024
31276
|
const resolvedConfig = this.defaultConfig === undefined
|
|
31025
31277
|
? config
|
|
31026
31278
|
: { ...this.defaultConfig, ...config };
|
|
31027
31279
|
return resolvedConfig;
|
|
31028
31280
|
}
|
|
31281
|
+
/**
|
|
31282
|
+
* Run one phase of a multi-phase earn operation: dispatch a `pending` event,
|
|
31283
|
+
* execute it, then dispatch a `success` event (recording the resulting step)
|
|
31284
|
+
* or — on failure — dispatch an `error` event, record the failed step, and
|
|
31285
|
+
* re-throw the original error for the caller to wrap with full step context.
|
|
31286
|
+
*
|
|
31287
|
+
* @typeParam R - The phase's result type.
|
|
31288
|
+
* @param ctx - The operation context (dispatcher, operation, steps, params).
|
|
31289
|
+
* @param action - The action name to dispatch the events under.
|
|
31290
|
+
* @param stepName - The phase name.
|
|
31291
|
+
* @param run - The phase work to execute.
|
|
31292
|
+
* @param txHashOf - Extracts the on-chain transaction hash from the result, if any.
|
|
31293
|
+
* @returns The phase result.
|
|
31294
|
+
*/
|
|
31295
|
+
async runPhase(ctx, action, stepName, run, txHashOf) {
|
|
31296
|
+
dispatchEarnEvent(ctx.dispatcher, action, ctx.operation, buildPendingStep(stepName));
|
|
31297
|
+
let result;
|
|
31298
|
+
try {
|
|
31299
|
+
result = await run();
|
|
31300
|
+
}
|
|
31301
|
+
catch (error) {
|
|
31302
|
+
const failedStep = buildErrorStep(stepName, getErrorMessage(error), error);
|
|
31303
|
+
ctx.steps.push(failedStep);
|
|
31304
|
+
dispatchEarnEvent(ctx.dispatcher, action, ctx.operation, failedStep);
|
|
31305
|
+
throw error;
|
|
31306
|
+
}
|
|
31307
|
+
// Discriminate on stepName so the `fetchParams` variant (which has no
|
|
31308
|
+
// `txHash` field in its type) cannot accidentally pick up a tx hash.
|
|
31309
|
+
const successStep = buildSuccessStep(stepName, txHashOf(result));
|
|
31310
|
+
ctx.steps.push(successStep);
|
|
31311
|
+
dispatchEarnEvent(ctx.dispatcher, action, ctx.operation, successStep);
|
|
31312
|
+
return result;
|
|
31313
|
+
}
|
|
31029
31314
|
/** {@inheritdoc} */
|
|
31030
31315
|
async getVaults(params) {
|
|
31031
31316
|
const config = this.resolveConfig(params.config);
|
|
@@ -31047,146 +31332,246 @@ class EarnServiceProvider {
|
|
|
31047
31332
|
}
|
|
31048
31333
|
/** {@inheritdoc} */
|
|
31049
31334
|
async deposit(params) {
|
|
31050
|
-
|
|
31051
|
-
|
|
31052
|
-
|
|
31053
|
-
const
|
|
31054
|
-
|
|
31055
|
-
|
|
31056
|
-
|
|
31057
|
-
|
|
31058
|
-
|
|
31059
|
-
|
|
31060
|
-
|
|
31061
|
-
address,
|
|
31062
|
-
chain
|
|
31063
|
-
|
|
31064
|
-
|
|
31065
|
-
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31335
|
+
return this.runDepositFlow(params, { skipApprove: false });
|
|
31336
|
+
}
|
|
31337
|
+
async runDepositFlow(params, options) {
|
|
31338
|
+
const ctx = {
|
|
31339
|
+
dispatcher: this.actionDispatcher,
|
|
31340
|
+
operation: 'deposit',
|
|
31341
|
+
steps: [],
|
|
31342
|
+
params,
|
|
31343
|
+
};
|
|
31344
|
+
try {
|
|
31345
|
+
const config = this.resolveConfig(params.config);
|
|
31346
|
+
const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
|
|
31347
|
+
const adapterContractAddress = requireAdapterContract(chain);
|
|
31348
|
+
const rawUsdcAddress = chain.usdcAddress;
|
|
31349
|
+
if (rawUsdcAddress === null) {
|
|
31350
|
+
throw createUnsupportedTokenError('USDC', chain.name);
|
|
31351
|
+
}
|
|
31352
|
+
const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
|
|
31353
|
+
const { adapter } = params.from;
|
|
31354
|
+
const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async () => fetchDeposit({
|
|
31355
|
+
vaultAddress: params.vaultAddress,
|
|
31356
|
+
amount: params.amount,
|
|
31357
|
+
address,
|
|
31358
|
+
chain: apiChain,
|
|
31359
|
+
config,
|
|
31360
|
+
}), () => undefined);
|
|
31361
|
+
validateExecutionDeadline(executionParams);
|
|
31362
|
+
const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
|
|
31363
|
+
const approvalToken = tokenInputs[0]?.token;
|
|
31364
|
+
if (!options.skipApprove && approvalToken !== undefined) {
|
|
31365
|
+
await this.runPhase(ctx, 'approve', 'approve', async () => approveMaxIfNeeded({
|
|
31366
|
+
adapter,
|
|
31367
|
+
chain,
|
|
31368
|
+
tokenAddress: approvalToken,
|
|
31369
|
+
delegate: adapterContractAddress,
|
|
31370
|
+
address,
|
|
31371
|
+
revertMessage: 'USDC approval reverted on-chain',
|
|
31372
|
+
}), (txHash) => txHash);
|
|
31373
|
+
}
|
|
31374
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async () => executeEarnAction({
|
|
31071
31375
|
adapter,
|
|
31072
31376
|
chain,
|
|
31073
|
-
tokenAddress: approvalToken,
|
|
31074
|
-
delegate: adapterContractAddress,
|
|
31075
31377
|
address,
|
|
31076
|
-
|
|
31378
|
+
actionKey: 'earn.deposit',
|
|
31379
|
+
actionParams: {
|
|
31380
|
+
executeParams: executionParams,
|
|
31381
|
+
tokenInputs,
|
|
31382
|
+
signature,
|
|
31383
|
+
},
|
|
31384
|
+
revertMessage: 'Earn deposit reverted on-chain',
|
|
31385
|
+
}), ({ txHash }) => txHash);
|
|
31386
|
+
return {
|
|
31387
|
+
txHash,
|
|
31388
|
+
explorerUrl,
|
|
31389
|
+
vaultAddress: params.vaultAddress,
|
|
31390
|
+
amount: params.amount,
|
|
31391
|
+
};
|
|
31392
|
+
}
|
|
31393
|
+
catch (error) {
|
|
31394
|
+
throw augmentEarnError(error, {
|
|
31395
|
+
providerName: this.name,
|
|
31396
|
+
operation: ctx.operation,
|
|
31397
|
+
steps: ctx.steps,
|
|
31398
|
+
params: ctx.params,
|
|
31077
31399
|
});
|
|
31078
31400
|
}
|
|
31079
|
-
const { txHash, explorerUrl } = await executeEarnAction({
|
|
31080
|
-
adapter,
|
|
31081
|
-
chain,
|
|
31082
|
-
address,
|
|
31083
|
-
actionKey: 'earn.deposit',
|
|
31084
|
-
actionParams: {
|
|
31085
|
-
executeParams: executionParams,
|
|
31086
|
-
tokenInputs,
|
|
31087
|
-
signature,
|
|
31088
|
-
},
|
|
31089
|
-
revertMessage: 'Earn deposit reverted on-chain',
|
|
31090
|
-
});
|
|
31091
|
-
return {
|
|
31092
|
-
txHash,
|
|
31093
|
-
explorerUrl,
|
|
31094
|
-
vaultAddress: params.vaultAddress,
|
|
31095
|
-
amount: params.amount,
|
|
31096
|
-
};
|
|
31097
31401
|
}
|
|
31098
31402
|
/** {@inheritdoc} */
|
|
31099
31403
|
async withdraw(params) {
|
|
31100
|
-
|
|
31101
|
-
|
|
31102
|
-
|
|
31103
|
-
const
|
|
31104
|
-
|
|
31105
|
-
|
|
31106
|
-
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
|
|
31112
|
-
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
|
|
31404
|
+
return this.runWithdrawFlow(params, { skipApprove: false });
|
|
31405
|
+
}
|
|
31406
|
+
async runWithdrawFlow(params, options) {
|
|
31407
|
+
const ctx = {
|
|
31408
|
+
dispatcher: this.actionDispatcher,
|
|
31409
|
+
operation: 'withdraw',
|
|
31410
|
+
steps: [],
|
|
31411
|
+
params,
|
|
31412
|
+
};
|
|
31413
|
+
try {
|
|
31414
|
+
const config = this.resolveConfig(params.config);
|
|
31415
|
+
const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
|
|
31416
|
+
const adapterContractAddress = requireAdapterContract(chain);
|
|
31417
|
+
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
31418
|
+
const { adapter } = params.from;
|
|
31419
|
+
const { executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async () => fetchWithdraw({
|
|
31420
|
+
vaultAddress,
|
|
31421
|
+
amount: params.amount,
|
|
31422
|
+
address,
|
|
31423
|
+
chain: apiChain,
|
|
31424
|
+
config,
|
|
31425
|
+
}), () => undefined);
|
|
31426
|
+
validateExecutionDeadline(executionParams);
|
|
31427
|
+
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
31428
|
+
const approvalToken = tokenInputs[0]?.token;
|
|
31429
|
+
if (!options.skipApprove && approvalToken !== undefined) {
|
|
31430
|
+
await this.runPhase(ctx, 'approve', 'approve', async () => approveMaxIfNeeded({
|
|
31431
|
+
adapter,
|
|
31432
|
+
chain,
|
|
31433
|
+
tokenAddress: approvalToken,
|
|
31434
|
+
delegate: adapterContractAddress,
|
|
31435
|
+
address,
|
|
31436
|
+
revertMessage: 'Vault share token approval reverted on-chain',
|
|
31437
|
+
}), (txHash) => txHash);
|
|
31438
|
+
}
|
|
31439
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async () => executeEarnAction({
|
|
31117
31440
|
adapter,
|
|
31118
31441
|
chain,
|
|
31119
|
-
tokenAddress: approvalToken,
|
|
31120
|
-
delegate: adapterContractAddress,
|
|
31121
31442
|
address,
|
|
31122
|
-
|
|
31443
|
+
actionKey: 'earn.withdraw',
|
|
31444
|
+
actionParams: {
|
|
31445
|
+
executeParams: executionParams,
|
|
31446
|
+
tokenInputs,
|
|
31447
|
+
signature,
|
|
31448
|
+
},
|
|
31449
|
+
revertMessage: 'Earn withdraw reverted on-chain',
|
|
31450
|
+
}), ({ txHash }) => txHash);
|
|
31451
|
+
return {
|
|
31452
|
+
txHash,
|
|
31453
|
+
explorerUrl,
|
|
31454
|
+
vaultAddress,
|
|
31455
|
+
amount: params.amount,
|
|
31456
|
+
};
|
|
31457
|
+
}
|
|
31458
|
+
catch (error) {
|
|
31459
|
+
throw augmentEarnError(error, {
|
|
31460
|
+
providerName: this.name,
|
|
31461
|
+
operation: ctx.operation,
|
|
31462
|
+
steps: ctx.steps,
|
|
31463
|
+
params: ctx.params,
|
|
31123
31464
|
});
|
|
31124
31465
|
}
|
|
31125
|
-
const { txHash, explorerUrl } = await executeEarnAction({
|
|
31126
|
-
adapter,
|
|
31127
|
-
chain,
|
|
31128
|
-
address,
|
|
31129
|
-
actionKey: 'earn.withdraw',
|
|
31130
|
-
actionParams: {
|
|
31131
|
-
executeParams: executionParams,
|
|
31132
|
-
tokenInputs,
|
|
31133
|
-
signature,
|
|
31134
|
-
},
|
|
31135
|
-
revertMessage: 'Earn withdraw reverted on-chain',
|
|
31136
|
-
});
|
|
31137
|
-
return {
|
|
31138
|
-
txHash,
|
|
31139
|
-
explorerUrl,
|
|
31140
|
-
vaultAddress,
|
|
31141
|
-
amount: params.amount,
|
|
31142
|
-
};
|
|
31143
31466
|
}
|
|
31144
31467
|
/** {@inheritdoc} */
|
|
31145
31468
|
async claimRewards(params) {
|
|
31146
|
-
|
|
31147
|
-
|
|
31148
|
-
|
|
31149
|
-
|
|
31150
|
-
|
|
31151
|
-
|
|
31152
|
-
|
|
31153
|
-
|
|
31154
|
-
|
|
31155
|
-
|
|
31156
|
-
|
|
31157
|
-
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
|
|
31164
|
-
|
|
31165
|
-
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
31170
|
-
|
|
31171
|
-
|
|
31469
|
+
return this.runClaimRewardsFlow(params);
|
|
31470
|
+
}
|
|
31471
|
+
async runClaimRewardsFlow(params) {
|
|
31472
|
+
const ctx = {
|
|
31473
|
+
dispatcher: this.actionDispatcher,
|
|
31474
|
+
operation: 'claimRewards',
|
|
31475
|
+
steps: [],
|
|
31476
|
+
params,
|
|
31477
|
+
};
|
|
31478
|
+
try {
|
|
31479
|
+
const config = this.resolveConfig(params.config);
|
|
31480
|
+
const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
|
|
31481
|
+
// Claim rewards has no approval step, but still requires adapter support.
|
|
31482
|
+
requireAdapterContract(chain);
|
|
31483
|
+
const { rewards, executionParams, signature } = await this.runPhase(ctx, 'claimRewards', 'fetchParams', async () => fetchClaimRewards({
|
|
31484
|
+
address,
|
|
31485
|
+
chain: apiChain,
|
|
31486
|
+
vaultAddress: params.vaultAddress,
|
|
31487
|
+
config,
|
|
31488
|
+
}), () => undefined);
|
|
31489
|
+
const nothingToClaim = rewards.length === 0;
|
|
31490
|
+
if (nothingToClaim) {
|
|
31491
|
+
return { status: 'no_rewards', rewards: [] };
|
|
31492
|
+
}
|
|
31493
|
+
const missingExecutionParams = executionParams === undefined;
|
|
31494
|
+
const missingSignature = signature === undefined;
|
|
31495
|
+
if (missingExecutionParams || missingSignature) {
|
|
31496
|
+
throw new KitError({
|
|
31497
|
+
...EarnError.INTERNAL_ERROR,
|
|
31498
|
+
recoverability: 'RETRYABLE',
|
|
31499
|
+
message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
|
|
31500
|
+
cause: {
|
|
31501
|
+
trace: {
|
|
31502
|
+
rewardsCount: rewards.length,
|
|
31503
|
+
missingExecutionParams,
|
|
31504
|
+
missingSignature,
|
|
31505
|
+
},
|
|
31172
31506
|
},
|
|
31507
|
+
});
|
|
31508
|
+
}
|
|
31509
|
+
validateExecutionDeadline(executionParams);
|
|
31510
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'claimRewards', 'execute', async () => executeEarnAction({
|
|
31511
|
+
adapter: params.from.adapter,
|
|
31512
|
+
chain,
|
|
31513
|
+
address,
|
|
31514
|
+
actionKey: 'earn.claimRewards',
|
|
31515
|
+
actionParams: {
|
|
31516
|
+
executeParams: executionParams,
|
|
31517
|
+
tokenInputs: [],
|
|
31518
|
+
signature,
|
|
31173
31519
|
},
|
|
31520
|
+
revertMessage: 'Earn claim rewards reverted on-chain',
|
|
31521
|
+
}), ({ txHash }) => txHash);
|
|
31522
|
+
return { status: 'claimed', rewards, txHash, explorerUrl };
|
|
31523
|
+
}
|
|
31524
|
+
catch (error) {
|
|
31525
|
+
throw augmentEarnError(error, {
|
|
31526
|
+
providerName: this.name,
|
|
31527
|
+
operation: ctx.operation,
|
|
31528
|
+
steps: ctx.steps,
|
|
31529
|
+
params: ctx.params,
|
|
31174
31530
|
});
|
|
31175
31531
|
}
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
31182
|
-
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31532
|
+
}
|
|
31533
|
+
/** {@inheritdoc} */
|
|
31534
|
+
supportsRetry(error) {
|
|
31535
|
+
return (isKitError(error) &&
|
|
31536
|
+
isRetryableError$1(error) &&
|
|
31537
|
+
isEarnErrorTrace(error.cause?.trace) &&
|
|
31538
|
+
error.cause.trace.provider === this.name);
|
|
31539
|
+
}
|
|
31540
|
+
/** {@inheritdoc} */
|
|
31541
|
+
async retry(error) {
|
|
31542
|
+
// Validation order mirrors EarnKit.retry().
|
|
31543
|
+
if (!isKitError(error)) {
|
|
31544
|
+
throw createValidationFailedError$1('error', error, 'retry() requires a KitError thrown by a previous earn operation');
|
|
31545
|
+
}
|
|
31546
|
+
if (!isRetryableError$1(error)) {
|
|
31547
|
+
throw createValidationFailedError$1('error.recoverability', error.recoverability, 'retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
31548
|
+
}
|
|
31549
|
+
const trace = error.cause?.trace;
|
|
31550
|
+
if (!isEarnErrorTrace(trace)) {
|
|
31551
|
+
throw createValidationFailedError$1('error.cause.trace', trace, 'retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
31552
|
+
}
|
|
31553
|
+
if (trace.provider !== this.name) {
|
|
31554
|
+
throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `Cannot retry: error was produced by provider "${trace.provider}", not "${this.name}"`);
|
|
31555
|
+
}
|
|
31556
|
+
const approveCompleted = trace.steps.some((step) => step.name === 'approve' && step.state === 'success');
|
|
31557
|
+
// `trace` is a discriminated union on `operation`, so each branch narrows
|
|
31558
|
+
// `trace.params` to the matching service-params type — no cast needed.
|
|
31559
|
+
switch (trace.operation) {
|
|
31560
|
+
case 'deposit':
|
|
31561
|
+
return this.runDepositFlow(trace.params, {
|
|
31562
|
+
skipApprove: approveCompleted,
|
|
31563
|
+
});
|
|
31564
|
+
case 'withdraw':
|
|
31565
|
+
return this.runWithdrawFlow(trace.params, {
|
|
31566
|
+
skipApprove: approveCompleted,
|
|
31567
|
+
});
|
|
31568
|
+
case 'claimRewards':
|
|
31569
|
+
return this.runClaimRewardsFlow(trace.params);
|
|
31570
|
+
default: {
|
|
31571
|
+
const exhaustive = trace;
|
|
31572
|
+
throw createValidationFailedError$1('error.cause.trace.operation', exhaustive, 'retry() does not support this earn operation');
|
|
31573
|
+
}
|
|
31574
|
+
}
|
|
31190
31575
|
}
|
|
31191
31576
|
/** {@inheritdoc} */
|
|
31192
31577
|
async getDepositQuote(params) {
|
|
@@ -31270,54 +31655,6 @@ function createEarnKitContext(config = {}) {
|
|
|
31270
31655
|
return context;
|
|
31271
31656
|
}
|
|
31272
31657
|
|
|
31273
|
-
/**
|
|
31274
|
-
* Symbol used to track that assertEarnParams has validated an object.
|
|
31275
|
-
* @internal
|
|
31276
|
-
*/
|
|
31277
|
-
const ASSERT_EARN_PARAMS_SYMBOL = Symbol('assertEarnParams');
|
|
31278
|
-
/**
|
|
31279
|
-
* Assert that the provided value conforms to the given earn params schema.
|
|
31280
|
-
*
|
|
31281
|
-
* Validate earn parameters using the provided Zod schema and track
|
|
31282
|
-
* validation state to avoid duplicate checks. Throw a structured
|
|
31283
|
-
* error with detailed validation messages if any parameter is invalid.
|
|
31284
|
-
*
|
|
31285
|
-
* @typeParam T - The expected type after validation
|
|
31286
|
-
* @param params - The earn parameters to validate
|
|
31287
|
-
* @param schema - The Zod schema to validate against
|
|
31288
|
-
* @throws {@link KitError} If the parameters fail validation
|
|
31289
|
-
*
|
|
31290
|
-
* @example
|
|
31291
|
-
* ```typescript
|
|
31292
|
-
* import { assertEarnParams, depositParamsSchema } from '@circle-fin/earn-kit'
|
|
31293
|
-
*
|
|
31294
|
-
* assertEarnParams(params, depositParamsSchema)
|
|
31295
|
-
* ```
|
|
31296
|
-
*/
|
|
31297
|
-
function assertEarnParams(params, schema) {
|
|
31298
|
-
validateWithStateTracking(params, schema, 'earn parameters', ASSERT_EARN_PARAMS_SYMBOL);
|
|
31299
|
-
}
|
|
31300
|
-
|
|
31301
|
-
function findProvider(context, chain, operation = 'earn') {
|
|
31302
|
-
let fallback;
|
|
31303
|
-
for (const provider of context.providers) {
|
|
31304
|
-
fallback ??= provider;
|
|
31305
|
-
if (provider.supportedChains.length === 0)
|
|
31306
|
-
continue;
|
|
31307
|
-
if (chain === undefined ||
|
|
31308
|
-
provider.supportedChains.some((c) => c.chain === chain.chain)) {
|
|
31309
|
-
return provider;
|
|
31310
|
-
}
|
|
31311
|
-
}
|
|
31312
|
-
if (fallback === undefined) {
|
|
31313
|
-
throw createValidationFailedError$1('context.providers', [], 'No earn providers configured');
|
|
31314
|
-
}
|
|
31315
|
-
if (chain !== undefined) {
|
|
31316
|
-
throw createUnsupportedEarnRouteError(operation, chain.name);
|
|
31317
|
-
}
|
|
31318
|
-
return fallback;
|
|
31319
|
-
}
|
|
31320
|
-
|
|
31321
31658
|
/**
|
|
31322
31659
|
* Format a provider amount object as a human-readable decimal string.
|
|
31323
31660
|
*
|
|
@@ -31460,6 +31797,54 @@ function formatClaimRewardsResult(result) {
|
|
|
31460
31797
|
};
|
|
31461
31798
|
}
|
|
31462
31799
|
|
|
31800
|
+
/**
|
|
31801
|
+
* Symbol used to track that assertEarnParams has validated an object.
|
|
31802
|
+
* @internal
|
|
31803
|
+
*/
|
|
31804
|
+
const ASSERT_EARN_PARAMS_SYMBOL = Symbol('assertEarnParams');
|
|
31805
|
+
/**
|
|
31806
|
+
* Assert that the provided value conforms to the given earn params schema.
|
|
31807
|
+
*
|
|
31808
|
+
* Validate earn parameters using the provided Zod schema and track
|
|
31809
|
+
* validation state to avoid duplicate checks. Throw a structured
|
|
31810
|
+
* error with detailed validation messages if any parameter is invalid.
|
|
31811
|
+
*
|
|
31812
|
+
* @typeParam T - The expected type after validation
|
|
31813
|
+
* @param params - The earn parameters to validate
|
|
31814
|
+
* @param schema - The Zod schema to validate against
|
|
31815
|
+
* @throws {@link KitError} If the parameters fail validation
|
|
31816
|
+
*
|
|
31817
|
+
* @example
|
|
31818
|
+
* ```typescript
|
|
31819
|
+
* import { assertEarnParams, depositParamsSchema } from '@circle-fin/earn-kit'
|
|
31820
|
+
*
|
|
31821
|
+
* assertEarnParams(params, depositParamsSchema)
|
|
31822
|
+
* ```
|
|
31823
|
+
*/
|
|
31824
|
+
function assertEarnParams(params, schema) {
|
|
31825
|
+
validateWithStateTracking(params, schema, 'earn parameters', ASSERT_EARN_PARAMS_SYMBOL);
|
|
31826
|
+
}
|
|
31827
|
+
|
|
31828
|
+
function findProvider(context, chain, operation = 'earn') {
|
|
31829
|
+
let fallback;
|
|
31830
|
+
for (const provider of context.providers) {
|
|
31831
|
+
fallback ??= provider;
|
|
31832
|
+
if (provider.supportedChains.length === 0)
|
|
31833
|
+
continue;
|
|
31834
|
+
if (chain === undefined ||
|
|
31835
|
+
provider.supportedChains.some((c) => c.chain === chain.chain)) {
|
|
31836
|
+
return provider;
|
|
31837
|
+
}
|
|
31838
|
+
}
|
|
31839
|
+
if (fallback === undefined) {
|
|
31840
|
+
throw createValidationFailedError$1('context.providers', [], 'No earn providers configured');
|
|
31841
|
+
}
|
|
31842
|
+
if (chain !== undefined) {
|
|
31843
|
+
throw createUnsupportedEarnRouteError(operation, chain.name);
|
|
31844
|
+
}
|
|
31845
|
+
return fallback;
|
|
31846
|
+
}
|
|
31847
|
+
|
|
31463
31848
|
/**
|
|
31464
31849
|
* Schema for the adapter context within earn operations.
|
|
31465
31850
|
*
|
|
@@ -32109,6 +32494,19 @@ async function getClaimRewardsQuote$1(context, params) {
|
|
|
32109
32494
|
return formatClaimRewardsQuoteInfo(result);
|
|
32110
32495
|
}
|
|
32111
32496
|
|
|
32497
|
+
function formatRetryResult(operation, result) {
|
|
32498
|
+
switch (operation) {
|
|
32499
|
+
case 'deposit':
|
|
32500
|
+
case 'withdraw':
|
|
32501
|
+
return result;
|
|
32502
|
+
case 'claimRewards':
|
|
32503
|
+
return formatClaimRewardsResult(result);
|
|
32504
|
+
default: {
|
|
32505
|
+
const exhaustive = operation;
|
|
32506
|
+
throw createValidationFailedError$1('error.cause.trace.operation', exhaustive, 'EarnKit.retry() does not support this earn operation');
|
|
32507
|
+
}
|
|
32508
|
+
}
|
|
32509
|
+
}
|
|
32112
32510
|
/**
|
|
32113
32511
|
* A high-level class-based interface for DeFi lending vault operations.
|
|
32114
32512
|
*
|
|
@@ -32166,6 +32564,12 @@ async function getClaimRewardsQuote$1(context, params) {
|
|
|
32166
32564
|
*/
|
|
32167
32565
|
class EarnKit {
|
|
32168
32566
|
context;
|
|
32567
|
+
/**
|
|
32568
|
+
* Event dispatcher for step-level events emitted during multi-phase earn
|
|
32569
|
+
* operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
|
|
32570
|
+
* this directly.
|
|
32571
|
+
*/
|
|
32572
|
+
actionDispatcher;
|
|
32169
32573
|
/**
|
|
32170
32574
|
* Create a new EarnKit instance.
|
|
32171
32575
|
*
|
|
@@ -32185,6 +32589,89 @@ class EarnKit {
|
|
|
32185
32589
|
*/
|
|
32186
32590
|
constructor(config = {}) {
|
|
32187
32591
|
this.context = createEarnKitContext(config);
|
|
32592
|
+
this.actionDispatcher = new Actionable();
|
|
32593
|
+
for (const provider of this.context.providers) {
|
|
32594
|
+
provider.registerDispatcher(this.actionDispatcher);
|
|
32595
|
+
}
|
|
32596
|
+
}
|
|
32597
|
+
on(action, handler) {
|
|
32598
|
+
if (action === '*') {
|
|
32599
|
+
this.actionDispatcher.on('*', handler);
|
|
32600
|
+
}
|
|
32601
|
+
else {
|
|
32602
|
+
this.actionDispatcher.on(action, handler);
|
|
32603
|
+
}
|
|
32604
|
+
}
|
|
32605
|
+
off(action, handler) {
|
|
32606
|
+
if (action === '*') {
|
|
32607
|
+
this.actionDispatcher.off('*', handler);
|
|
32608
|
+
}
|
|
32609
|
+
else {
|
|
32610
|
+
this.actionDispatcher.off(action, handler);
|
|
32611
|
+
}
|
|
32612
|
+
}
|
|
32613
|
+
/**
|
|
32614
|
+
* Resume a multi-phase earn operation that previously failed.
|
|
32615
|
+
*
|
|
32616
|
+
* Pass the {@link KitError} caught from a `deposit`, `withdraw`, or
|
|
32617
|
+
* `claimRewards` call. The error carries the original operation, inputs,
|
|
32618
|
+
* and step progress, so the operation can be re-run while skipping phases
|
|
32619
|
+
* that already completed (for example a successful token approval). Use
|
|
32620
|
+
* `isRetryableError(error)` to check whether retrying is worthwhile before
|
|
32621
|
+
* calling this.
|
|
32622
|
+
*
|
|
32623
|
+
* @remarks
|
|
32624
|
+
* Resuming re-fetches execution params and re-submits the `execute`
|
|
32625
|
+
* transaction; the earn service deduplicates execution server-side, which
|
|
32626
|
+
* makes this safe in the common case. But if a prior attempt broadcast the
|
|
32627
|
+
* `execute` transaction and then failed before its receipt was observed,
|
|
32628
|
+
* that transaction may still be in flight when `retry()` re-broadcasts.
|
|
32629
|
+
* Treat `retry()` as best-effort recovery, not an atomic operation.
|
|
32630
|
+
*
|
|
32631
|
+
* @param error - The error caught from a previous multi-phase earn operation.
|
|
32632
|
+
* @returns A promise resolving to the result of the resumed operation.
|
|
32633
|
+
* @throws {@link KitError} If `error` is not a retryable {@link KitError}
|
|
32634
|
+
* carrying earn retry context, names an unknown provider, or the resumed
|
|
32635
|
+
* operation itself fails.
|
|
32636
|
+
*
|
|
32637
|
+
* @example
|
|
32638
|
+
* ```typescript
|
|
32639
|
+
* import { isRetryableError } from '@circle-fin/earn-kit'
|
|
32640
|
+
*
|
|
32641
|
+
* try {
|
|
32642
|
+
* await kit.deposit(params)
|
|
32643
|
+
* } catch (error) {
|
|
32644
|
+
* if (isRetryableError(error)) {
|
|
32645
|
+
* const result = await kit.retry(error)
|
|
32646
|
+
* console.log('recovered, tx:', 'txHash' in result ? result.txHash : undefined)
|
|
32647
|
+
* }
|
|
32648
|
+
* }
|
|
32649
|
+
* ```
|
|
32650
|
+
*/
|
|
32651
|
+
async retry(error) {
|
|
32652
|
+
if (!isKitError(error)) {
|
|
32653
|
+
throw createValidationFailedError$1('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
|
|
32654
|
+
}
|
|
32655
|
+
if (!isRetryableError$1(error)) {
|
|
32656
|
+
throw createValidationFailedError$1('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
32657
|
+
}
|
|
32658
|
+
const trace = error.cause?.trace;
|
|
32659
|
+
if (!isEarnErrorTrace(trace)) {
|
|
32660
|
+
throw createValidationFailedError$1('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
32661
|
+
}
|
|
32662
|
+
const provider = this.context.providers.find((candidate) => candidate.name === trace.provider);
|
|
32663
|
+
if (provider === undefined) {
|
|
32664
|
+
throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
|
|
32665
|
+
}
|
|
32666
|
+
const result = await provider.retry(error);
|
|
32667
|
+
// `provider.retry` returns a flat result union with no compile-time link to
|
|
32668
|
+
// `trace.operation`, so narrow the operation here to select the matching
|
|
32669
|
+
// overload. The result cast in each branch is sound: the provider always
|
|
32670
|
+
// returns the result type corresponding to the resumed operation.
|
|
32671
|
+
if (trace.operation === 'claimRewards') {
|
|
32672
|
+
return formatRetryResult(trace.operation, result);
|
|
32673
|
+
}
|
|
32674
|
+
return formatRetryResult(trace.operation, result);
|
|
32188
32675
|
}
|
|
32189
32676
|
/**
|
|
32190
32677
|
* Return the chains supported by configured earn providers.
|
|
@@ -33527,7 +34014,7 @@ async function getClaimRewardsQuote(context, params) {
|
|
|
33527
34014
|
}
|
|
33528
34015
|
|
|
33529
34016
|
var name = "@circle-fin/unified-balance-kit";
|
|
33530
|
-
var version = "1.1.
|
|
34017
|
+
var version = "1.1.3";
|
|
33531
34018
|
var pkg = {
|
|
33532
34019
|
name: name,
|
|
33533
34020
|
version: version};
|
|
@@ -34000,10 +34487,6 @@ async function deposit$1(params) {
|
|
|
34000
34487
|
const signerAddress = await resolveSignerAddress(params.from, chain);
|
|
34001
34488
|
const resolvedContext = { ...operationContext, address: signerAddress };
|
|
34002
34489
|
await validateDepositBalance(params.from.adapter, chain, valueBigInt, resolvedContext, params.token);
|
|
34003
|
-
await validateNativeBalanceForTransaction({
|
|
34004
|
-
adapter: params.from.adapter,
|
|
34005
|
-
operationContext: { ...resolvedContext, chain },
|
|
34006
|
-
});
|
|
34007
34490
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
34008
34491
|
const strategy = params.allowanceStrategy ?? 'authorize';
|
|
34009
34492
|
let txHash;
|
|
@@ -34048,10 +34531,6 @@ async function depositFor$1(params) {
|
|
|
34048
34531
|
const signerAddress = await resolveSignerAddress(params.from, chain);
|
|
34049
34532
|
const resolvedContext = { ...operationContext, address: signerAddress };
|
|
34050
34533
|
await validateDepositBalance(params.from.adapter, chain, valueBigInt, resolvedContext, params.token);
|
|
34051
|
-
await validateNativeBalanceForTransaction({
|
|
34052
|
-
adapter: params.from.adapter,
|
|
34053
|
-
operationContext: { ...resolvedContext, chain },
|
|
34054
|
-
});
|
|
34055
34534
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
34056
34535
|
let txHash;
|
|
34057
34536
|
if (chain.type === 'solana') {
|
|
@@ -37335,10 +37814,6 @@ async function updateDelegate(params, action, state) {
|
|
|
37335
37814
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
37336
37815
|
assertValidAddress(delegateAddress, chain, 'delegate');
|
|
37337
37816
|
assertNotSelfDelegation(chain, signerAddress, delegateAddress, action);
|
|
37338
|
-
await validateNativeBalanceForTransaction({
|
|
37339
|
-
adapter,
|
|
37340
|
-
operationContext: { ...operationContext, chain, address: signerAddress },
|
|
37341
|
-
});
|
|
37342
37817
|
const request = await adapter.prepareAction(action, { token: tokenAddress, delegate: delegateAddress, chain }, operationContext);
|
|
37343
37818
|
const txHash = await executeAndWait(adapter, request, chain);
|
|
37344
37819
|
return {
|
|
@@ -37541,10 +38016,6 @@ async function initiateRemoveFund$1(params) {
|
|
|
37541
38016
|
const signerAddress = await resolveSignerAddress(from, chain);
|
|
37542
38017
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
37543
38018
|
const value = parseAmountSafe(amount);
|
|
37544
|
-
await validateNativeBalanceForTransaction({
|
|
37545
|
-
adapter,
|
|
37546
|
-
operationContext: { ...operationContext, chain, address: signerAddress },
|
|
37547
|
-
});
|
|
37548
38019
|
const request = await adapter.prepareAction('gateway.v1.initiateWithdrawal', { token: tokenAddress, value, chain }, operationContext);
|
|
37549
38020
|
let txHash;
|
|
37550
38021
|
try {
|
|
@@ -37603,10 +38074,6 @@ async function removeFund$1(params) {
|
|
|
37603
38074
|
const operationContext = extractOperationContext(from);
|
|
37604
38075
|
const signerAddress = await resolveSignerAddress(from, chain);
|
|
37605
38076
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
37606
|
-
await validateNativeBalanceForTransaction({
|
|
37607
|
-
adapter,
|
|
37608
|
-
operationContext: { ...operationContext, chain, address: signerAddress },
|
|
37609
|
-
});
|
|
37610
38077
|
// Read the pending balance before withdrawing — the contract resets it to 0
|
|
37611
38078
|
// after withdraw() executes, so this is the only way to capture the amount.
|
|
37612
38079
|
const withdrawingBalanceReq = await adapter.prepareAction('gateway.v1.withdrawingBalance', { token: tokenAddress, depositor: signerAddress, chain }, operationContext);
|