@circle-fin/app-kit 1.6.1 → 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 +10 -0
- 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.cjs
CHANGED
|
@@ -2308,7 +2308,8 @@ const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
|
2308
2308
|
*
|
|
2309
2309
|
* @remarks
|
|
2310
2310
|
* Check order for KitError instances:
|
|
2311
|
-
* 1. If `recoverability === 'RETRYABLE'
|
|
2311
|
+
* 1. If `recoverability === 'RETRYABLE'` or `recoverability === 'RESUMABLE'`,
|
|
2312
|
+
* return `true` immediately (priority check).
|
|
2312
2313
|
* 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
|
|
2313
2314
|
* 3. Non-KitError instances always return `false`.
|
|
2314
2315
|
*
|
|
@@ -2319,6 +2320,12 @@ const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
|
2319
2320
|
* subsequent attempts, such as network timeouts or temporary service
|
|
2320
2321
|
* unavailability. These errors are safe to retry after a delay.
|
|
2321
2322
|
*
|
|
2323
|
+
* RESUMABLE errors indicate a multi-phase operation that completed some phases
|
|
2324
|
+
* before failing (for example, a token approval landed but the execution
|
|
2325
|
+
* transaction failed). They are also retryable — re-running the operation is
|
|
2326
|
+
* safe — but callers that have a kit-level `retry()` should prefer it so that
|
|
2327
|
+
* already-completed phases are skipped.
|
|
2328
|
+
*
|
|
2322
2329
|
* @param error - Unknown error to check
|
|
2323
2330
|
* @returns True if error is retryable
|
|
2324
2331
|
*
|
|
@@ -2364,16 +2371,29 @@ const DEFAULT_RETRYABLE_ERROR_CODES = [
|
|
|
2364
2371
|
* })
|
|
2365
2372
|
* isRetryableError(error3) // false
|
|
2366
2373
|
*
|
|
2374
|
+
* // KitError with RESUMABLE recoverability (partially-completed operation)
|
|
2375
|
+
* const error4 = new KitError({
|
|
2376
|
+
* code: 8101,
|
|
2377
|
+
* name: 'EARN_EXECUTION_FAILED',
|
|
2378
|
+
* type: 'SERVICE',
|
|
2379
|
+
* recoverability: 'RESUMABLE',
|
|
2380
|
+
* message: 'Execution failed after approval',
|
|
2381
|
+
* })
|
|
2382
|
+
* isRetryableError(error4) // true
|
|
2383
|
+
*
|
|
2367
2384
|
* // Non-KitError
|
|
2368
|
-
* const
|
|
2369
|
-
* isRetryableError(
|
|
2385
|
+
* const error5 = new Error('Standard error')
|
|
2386
|
+
* isRetryableError(error5) // false
|
|
2370
2387
|
* ```
|
|
2371
2388
|
*/
|
|
2372
2389
|
function isRetryableError$1(error) {
|
|
2373
2390
|
// Use proper type guard to check if it's a KitError
|
|
2374
2391
|
if (isKitError(error)) {
|
|
2375
|
-
// Priority check: explicit recoverability
|
|
2376
|
-
|
|
2392
|
+
// Priority check: explicit recoverability. RESUMABLE errors are a subset of
|
|
2393
|
+
// retryable errors — re-running the operation is safe, but a kit-level
|
|
2394
|
+
// retry() can resume from the failed phase instead.
|
|
2395
|
+
if (error.recoverability === 'RETRYABLE' ||
|
|
2396
|
+
error.recoverability === 'RESUMABLE') {
|
|
2377
2397
|
return true;
|
|
2378
2398
|
}
|
|
2379
2399
|
// Fallback check: error code against default retryable codes
|
|
@@ -11046,7 +11066,7 @@ function emitResultStepErrorTelemetry(failedStep, stepEventMap, fallbackEventTyp
|
|
|
11046
11066
|
}
|
|
11047
11067
|
|
|
11048
11068
|
var name$3 = "@circle-fin/bridge-kit";
|
|
11049
|
-
var version$4 = "1.10.
|
|
11069
|
+
var version$4 = "1.10.2";
|
|
11050
11070
|
var pkg$4 = {
|
|
11051
11071
|
name: name$3,
|
|
11052
11072
|
version: version$4};
|
|
@@ -11537,60 +11557,6 @@ const validateBalanceForTransaction = async (params) => {
|
|
|
11537
11557
|
}
|
|
11538
11558
|
};
|
|
11539
11559
|
|
|
11540
|
-
/**
|
|
11541
|
-
* Validate that the adapter has sufficient native token balance for transaction fees.
|
|
11542
|
-
*
|
|
11543
|
-
* This function checks if the adapter's current native token balance (ETH, SOL, etc.)
|
|
11544
|
-
* is greater than zero. It throws a KitError with code 9002 (BALANCE_INSUFFICIENT_GAS)
|
|
11545
|
-
* if the balance is zero, indicating the wallet cannot pay for transaction fees.
|
|
11546
|
-
*
|
|
11547
|
-
* @param params - The validation parameters containing adapter and operation context.
|
|
11548
|
-
* @returns A promise that resolves to void if validation passes.
|
|
11549
|
-
* @throws {KitError} When the adapter's native balance is zero (code: 9002).
|
|
11550
|
-
*
|
|
11551
|
-
* @example
|
|
11552
|
-
* ```typescript
|
|
11553
|
-
* import { validateNativeBalanceForTransaction } from '@core/adapter'
|
|
11554
|
-
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
11555
|
-
* import { isKitError, ERROR_TYPES } from '@core/errors'
|
|
11556
|
-
*
|
|
11557
|
-
* const adapter = createViemAdapterFromPrivateKey({
|
|
11558
|
-
* privateKey: '0x...',
|
|
11559
|
-
* chain: 'Ethereum',
|
|
11560
|
-
* })
|
|
11561
|
-
*
|
|
11562
|
-
* try {
|
|
11563
|
-
* await validateNativeBalanceForTransaction({
|
|
11564
|
-
* adapter,
|
|
11565
|
-
* operationContext: { chain: 'Ethereum' },
|
|
11566
|
-
* })
|
|
11567
|
-
* console.log('Native balance validation passed')
|
|
11568
|
-
* } catch (error) {
|
|
11569
|
-
* if (isKitError(error) && error.type === ERROR_TYPES.BALANCE) {
|
|
11570
|
-
* console.error('Insufficient gas funds:', error.message)
|
|
11571
|
-
* }
|
|
11572
|
-
* }
|
|
11573
|
-
* ```
|
|
11574
|
-
*/
|
|
11575
|
-
const validateNativeBalanceForTransaction = async (params) => {
|
|
11576
|
-
const { adapter, operationContext } = params;
|
|
11577
|
-
const balancePrepared = await adapter.prepareAction('native.balanceOf', {
|
|
11578
|
-
walletAddress: operationContext.address,
|
|
11579
|
-
}, operationContext);
|
|
11580
|
-
const balance = await balancePrepared.execute();
|
|
11581
|
-
if (BigInt(balance) === 0n) {
|
|
11582
|
-
const { chain } = operationContext;
|
|
11583
|
-
const chainName = extractChainInfo(chain).name;
|
|
11584
|
-
const nativeSymbol = typeof chain === 'object' && chain !== null && 'nativeCurrency' in chain
|
|
11585
|
-
? chain.nativeCurrency.symbol
|
|
11586
|
-
: undefined;
|
|
11587
|
-
throw createInsufficientGasError(chainName, nativeSymbol, {
|
|
11588
|
-
balance: '0',
|
|
11589
|
-
walletAddress: operationContext.address,
|
|
11590
|
-
});
|
|
11591
|
-
}
|
|
11592
|
-
};
|
|
11593
|
-
|
|
11594
11560
|
/**
|
|
11595
11561
|
* Permit signature standards for gasless token approvals.
|
|
11596
11562
|
*
|
|
@@ -16022,7 +15988,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCo
|
|
|
16022
15988
|
return step;
|
|
16023
15989
|
}
|
|
16024
15990
|
|
|
16025
|
-
var version$3 = "1.8.
|
|
15991
|
+
var version$3 = "1.8.3";
|
|
16026
15992
|
var pkg$3 = {
|
|
16027
15993
|
version: version$3};
|
|
16028
15994
|
|
|
@@ -16840,7 +16806,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
16840
16806
|
async bridge(params) {
|
|
16841
16807
|
// CCTP-specific bridge params validation (includes base validation)
|
|
16842
16808
|
assertCCTPv2BridgeParams(params);
|
|
16843
|
-
const { source,
|
|
16809
|
+
const { source, amount, token } = params;
|
|
16844
16810
|
// Extract operation context from source wallet context for balance validation
|
|
16845
16811
|
const sourceOperationContext = this.extractOperationContext(source);
|
|
16846
16812
|
// Validate USDC balance for transaction on source chain
|
|
@@ -16851,24 +16817,6 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
16851
16817
|
tokenAddress: source.chain.usdcAddress,
|
|
16852
16818
|
operationContext: sourceOperationContext,
|
|
16853
16819
|
});
|
|
16854
|
-
// Validate native balance > 0 for gas fees on source chain
|
|
16855
|
-
await validateNativeBalanceForTransaction({
|
|
16856
|
-
adapter: source.adapter,
|
|
16857
|
-
operationContext: sourceOperationContext,
|
|
16858
|
-
});
|
|
16859
|
-
// Only validate destination native balance if there's an adapter
|
|
16860
|
-
// and forwarder is not being used (forwarder handles mint, no gas needed)
|
|
16861
|
-
if ('adapter' in destination &&
|
|
16862
|
-
destination.adapter &&
|
|
16863
|
-
!destination.useForwarder) {
|
|
16864
|
-
// Extract operation context from destination wallet context
|
|
16865
|
-
const destinationOperationContext = this.extractOperationContext(destination);
|
|
16866
|
-
// Validate native balance > 0 for gas fees on destination chain
|
|
16867
|
-
await validateNativeBalanceForTransaction({
|
|
16868
|
-
adapter: destination.adapter,
|
|
16869
|
-
operationContext: destinationOperationContext,
|
|
16870
|
-
});
|
|
16871
|
-
}
|
|
16872
16820
|
return bridge$1(params, this);
|
|
16873
16821
|
}
|
|
16874
16822
|
/**
|
|
@@ -18421,7 +18369,7 @@ const createBridgeKit = (context) => {
|
|
|
18421
18369
|
};
|
|
18422
18370
|
|
|
18423
18371
|
var name$2 = "@circle-fin/swap-kit";
|
|
18424
|
-
var version$2 = "1.2.
|
|
18372
|
+
var version$2 = "1.2.3";
|
|
18425
18373
|
var pkg$2 = {
|
|
18426
18374
|
name: name$2,
|
|
18427
18375
|
version: version$2};
|
|
@@ -29584,7 +29532,7 @@ const createSwapKit = (context) => {
|
|
|
29584
29532
|
};
|
|
29585
29533
|
|
|
29586
29534
|
var name$1 = "@circle-fin/earn-kit";
|
|
29587
|
-
var version$1 = "1.0
|
|
29535
|
+
var version$1 = "1.1.0";
|
|
29588
29536
|
var pkg$1 = {
|
|
29589
29537
|
name: name$1,
|
|
29590
29538
|
version: version$1};
|
|
@@ -29994,6 +29942,253 @@ function validateExecutionDeadline(executionParams) {
|
|
|
29994
29942
|
}
|
|
29995
29943
|
}
|
|
29996
29944
|
|
|
29945
|
+
/** Base discriminators shared by every earn action payload. */
|
|
29946
|
+
const EARN_ACTION_BASE = {
|
|
29947
|
+
protocol: 'earn',
|
|
29948
|
+
service: 'earn-service',
|
|
29949
|
+
};
|
|
29950
|
+
/**
|
|
29951
|
+
* Dispatch a step event for an earn operation through the kit's action
|
|
29952
|
+
* dispatcher.
|
|
29953
|
+
*
|
|
29954
|
+
* Builds the action payload for the given action/operation/step combination
|
|
29955
|
+
* and forwards it to any registered listeners (including wildcard `'*'`
|
|
29956
|
+
* listeners). The call is a no-op when no dispatcher has been registered, so
|
|
29957
|
+
* callers can invoke it unconditionally.
|
|
29958
|
+
*
|
|
29959
|
+
* The `approve` action only fires for `deposit` and `withdraw` operations and
|
|
29960
|
+
* only carries `approve` steps; the `deposit`, `withdraw`, and `claimRewards`
|
|
29961
|
+
* actions carry only `fetchParams` and `execute` steps. Mismatched
|
|
29962
|
+
* combinations are ignored defensively.
|
|
29963
|
+
*
|
|
29964
|
+
* @param dispatcher - The action dispatcher, or `undefined` if none registered.
|
|
29965
|
+
* @param action - The action name to dispatch under.
|
|
29966
|
+
* @param operation - The earn operation the step belongs to.
|
|
29967
|
+
* @param step - The step to deliver as the event payload.
|
|
29968
|
+
*
|
|
29969
|
+
* @example
|
|
29970
|
+
* ```typescript
|
|
29971
|
+
* dispatchEarnEvent(dispatcher, 'deposit', 'deposit', {
|
|
29972
|
+
* name: 'execute',
|
|
29973
|
+
* state: 'success',
|
|
29974
|
+
* txHash: '0xabc...',
|
|
29975
|
+
* })
|
|
29976
|
+
* ```
|
|
29977
|
+
*/
|
|
29978
|
+
function dispatchEarnEvent(dispatcher, action, operation, step) {
|
|
29979
|
+
if (dispatcher === undefined) {
|
|
29980
|
+
return;
|
|
29981
|
+
}
|
|
29982
|
+
// Listener errors must never abort the financial operation flow. A throwing
|
|
29983
|
+
// listener on a 'success' event after a transaction has landed on-chain
|
|
29984
|
+
// would otherwise propagate to the caller as a RESUMABLE error and trigger
|
|
29985
|
+
// a retry that re-submits the same transaction (double-spend risk).
|
|
29986
|
+
try {
|
|
29987
|
+
if (action === 'approve') {
|
|
29988
|
+
// The approve action only applies to deposit/withdraw approval steps.
|
|
29989
|
+
if (step.name !== 'approve' || operation === 'claimRewards') {
|
|
29990
|
+
return;
|
|
29991
|
+
}
|
|
29992
|
+
dispatcher.dispatch('approve', {
|
|
29993
|
+
...EARN_ACTION_BASE,
|
|
29994
|
+
operation,
|
|
29995
|
+
method: 'approve',
|
|
29996
|
+
values: step,
|
|
29997
|
+
});
|
|
29998
|
+
return;
|
|
29999
|
+
}
|
|
30000
|
+
// deposit / withdraw / claimRewards actions carry fetchParams + execute steps.
|
|
30001
|
+
if (step.name === 'approve') {
|
|
30002
|
+
return;
|
|
30003
|
+
}
|
|
30004
|
+
const payload = { ...EARN_ACTION_BASE, method: step.name, values: step };
|
|
30005
|
+
if (action === 'deposit') {
|
|
30006
|
+
dispatcher.dispatch('deposit', { ...payload, operation: 'deposit' });
|
|
30007
|
+
return;
|
|
30008
|
+
}
|
|
30009
|
+
if (action === 'withdraw') {
|
|
30010
|
+
dispatcher.dispatch('withdraw', { ...payload, operation: 'withdraw' });
|
|
30011
|
+
return;
|
|
30012
|
+
}
|
|
30013
|
+
if (action === 'claimRewards') {
|
|
30014
|
+
dispatcher.dispatch('claimRewards', {
|
|
30015
|
+
...payload,
|
|
30016
|
+
operation: 'claimRewards',
|
|
30017
|
+
});
|
|
30018
|
+
return;
|
|
30019
|
+
}
|
|
30020
|
+
}
|
|
30021
|
+
catch {
|
|
30022
|
+
// Swallow listener errors. Observability of the failure is the listener's
|
|
30023
|
+
// responsibility; the operation flow continues unaffected.
|
|
30024
|
+
return;
|
|
30025
|
+
}
|
|
30026
|
+
// Reached only when `action` is not a known earn action. Thrown outside the
|
|
30027
|
+
// try/catch so a missing case surfaces as a developer error instead of being
|
|
30028
|
+
// silently swallowed alongside listener failures.
|
|
30029
|
+
const exhaustive = action;
|
|
30030
|
+
throw new Error(`Unhandled earn action: ${String(exhaustive)}`);
|
|
30031
|
+
}
|
|
30032
|
+
|
|
30033
|
+
const EARN_OPERATIONS = new Set([
|
|
30034
|
+
'deposit',
|
|
30035
|
+
'withdraw',
|
|
30036
|
+
'claimRewards',
|
|
30037
|
+
]);
|
|
30038
|
+
/**
|
|
30039
|
+
* Operations whose service params carry a top-level `amount` input field
|
|
30040
|
+
* (`deposit` and `withdraw`); `claimRewards` params have no `amount`. This
|
|
30041
|
+
* refers to the request input — distinct from the reward `amount` that appears
|
|
30042
|
+
* inside a claimRewards *result*.
|
|
30043
|
+
*/
|
|
30044
|
+
const OPERATIONS_WITH_AMOUNT = new Set([
|
|
30045
|
+
'deposit',
|
|
30046
|
+
'withdraw',
|
|
30047
|
+
]);
|
|
30048
|
+
/**
|
|
30049
|
+
* Validate that `params` carries the minimum shape `retry()` needs to resume
|
|
30050
|
+
* the given operation: an adapter context (`from`) and a `vaultAddress`, plus
|
|
30051
|
+
* an `amount` for `deposit`/`withdraw`. This is a structural sanity check, not
|
|
30052
|
+
* a full schema validation — operations re-validate their inputs when re-run —
|
|
30053
|
+
* but it lets a mismatched or forged trace fail the {@link isEarnErrorTrace}
|
|
30054
|
+
* guard cleanly instead of crashing deep inside the resume path.
|
|
30055
|
+
*/
|
|
30056
|
+
function hasEarnServiceParamsShape(operation, params) {
|
|
30057
|
+
if (params['from'] === null || typeof params['from'] !== 'object') {
|
|
30058
|
+
return false;
|
|
30059
|
+
}
|
|
30060
|
+
if (typeof params['vaultAddress'] !== 'string') {
|
|
30061
|
+
return false;
|
|
30062
|
+
}
|
|
30063
|
+
if (OPERATIONS_WITH_AMOUNT.has(operation) &&
|
|
30064
|
+
typeof params['amount'] !== 'string') {
|
|
30065
|
+
return false;
|
|
30066
|
+
}
|
|
30067
|
+
return true;
|
|
30068
|
+
}
|
|
30069
|
+
/**
|
|
30070
|
+
* Wrap an error thrown during a multi-phase earn operation with step-tracking
|
|
30071
|
+
* and resume context.
|
|
30072
|
+
*
|
|
30073
|
+
* Produces a new {@link KitError} that preserves the original error's code,
|
|
30074
|
+
* name, type, and message, merges any existing `cause.trace`, and adds an
|
|
30075
|
+
* {@link EarnErrorTrace} (`provider`, `operation`, `steps`, `params`). When a
|
|
30076
|
+
* prior phase had already committed work (a successful step exists) and the
|
|
30077
|
+
* underlying error is retryable, the recoverability is upgraded to
|
|
30078
|
+
* `RESUMABLE` so callers know `retry()` can resume from the failed phase. A
|
|
30079
|
+
* fatal underlying error stays fatal — it cannot be resumed.
|
|
30080
|
+
*
|
|
30081
|
+
* Non-{@link KitError} inputs (which should not occur on provider code paths)
|
|
30082
|
+
* are normalized to a retryable {@link ServiceError.INTERNAL_ERROR}.
|
|
30083
|
+
*
|
|
30084
|
+
* @param error - The error caught from a phase of the operation.
|
|
30085
|
+
* @param context - The operation context used to build the trace.
|
|
30086
|
+
* @returns A new {@link KitError} carrying the {@link EarnErrorTrace}.
|
|
30087
|
+
*
|
|
30088
|
+
* @example
|
|
30089
|
+
* ```typescript
|
|
30090
|
+
* try {
|
|
30091
|
+
* await executeEarnAction(...)
|
|
30092
|
+
* } catch (error) {
|
|
30093
|
+
* throw augmentEarnError(error, {
|
|
30094
|
+
* providerName: 'EarnService',
|
|
30095
|
+
* operation: 'deposit',
|
|
30096
|
+
* steps,
|
|
30097
|
+
* params,
|
|
30098
|
+
* })
|
|
30099
|
+
* }
|
|
30100
|
+
* ```
|
|
30101
|
+
*/
|
|
30102
|
+
function augmentEarnError(error, context) {
|
|
30103
|
+
const base = isKitError(error)
|
|
30104
|
+
? error
|
|
30105
|
+
: new KitError({
|
|
30106
|
+
...ServiceError.INTERNAL_ERROR,
|
|
30107
|
+
recoverability: 'RETRYABLE',
|
|
30108
|
+
message: getErrorMessage(error),
|
|
30109
|
+
});
|
|
30110
|
+
// Only upgrade to RESUMABLE when a prior *on-chain* phase succeeded. The
|
|
30111
|
+
// RESUMABLE label promises retry() can skip already-committed work — true
|
|
30112
|
+
// for a landed `approve` or `execute`, but a successful `fetchParams` saves
|
|
30113
|
+
// nothing (retry() re-fetches it anyway, since execution params/deadlines
|
|
30114
|
+
// expire), so it stays a plain RETRYABLE.
|
|
30115
|
+
const priorOnChainPhaseSucceeded = context.steps.some((step) => step.state === 'success' &&
|
|
30116
|
+
(step.name === 'approve' || step.name === 'execute'));
|
|
30117
|
+
const recoverability = priorOnChainPhaseSucceeded && base.recoverability === 'RETRYABLE'
|
|
30118
|
+
? 'RESUMABLE'
|
|
30119
|
+
: base.recoverability;
|
|
30120
|
+
const existingTrace = base.cause?.trace !== undefined &&
|
|
30121
|
+
base.cause.trace !== null &&
|
|
30122
|
+
typeof base.cause.trace === 'object'
|
|
30123
|
+
? base.cause.trace
|
|
30124
|
+
: {};
|
|
30125
|
+
// Snapshot the steps so the trace `retry()` reads can't be mutated by a
|
|
30126
|
+
// future code path that retains the live `EarnRunContext.steps` array.
|
|
30127
|
+
const trace = {
|
|
30128
|
+
...existingTrace,
|
|
30129
|
+
provider: context.providerName,
|
|
30130
|
+
operation: context.operation,
|
|
30131
|
+
steps: [...context.steps],
|
|
30132
|
+
};
|
|
30133
|
+
// Store the original service params non-enumerably. `retry()` reads them by
|
|
30134
|
+
// direct property access (`trace.params`), but JSON.stringify, console.log,
|
|
30135
|
+
// and telemetry serializers skip non-enumerable properties — so a
|
|
30136
|
+
// permissioned call's `config.kitKey` (a `KIT_KEY:<id>:<secret>` value) and
|
|
30137
|
+
// the in-memory adapter never leak through a logged error. defineProperty
|
|
30138
|
+
// also redefines any enumerable `params` an upstream trace may have carried.
|
|
30139
|
+
Object.defineProperty(trace, 'params', {
|
|
30140
|
+
value: context.params,
|
|
30141
|
+
enumerable: false,
|
|
30142
|
+
writable: true,
|
|
30143
|
+
configurable: true,
|
|
30144
|
+
});
|
|
30145
|
+
return new KitError({
|
|
30146
|
+
code: base.code,
|
|
30147
|
+
name: base.name,
|
|
30148
|
+
type: base.type,
|
|
30149
|
+
recoverability,
|
|
30150
|
+
message: base.message,
|
|
30151
|
+
cause: { trace },
|
|
30152
|
+
});
|
|
30153
|
+
}
|
|
30154
|
+
/**
|
|
30155
|
+
* Runtime type guard for {@link EarnErrorTrace}.
|
|
30156
|
+
*
|
|
30157
|
+
* Verifies the value carries the fields `retry()` needs to resume an earn
|
|
30158
|
+
* operation: a provider name, a known operation, a steps array, and a `params`
|
|
30159
|
+
* object whose shape matches that operation (an adapter context and vault
|
|
30160
|
+
* address, plus an amount for `deposit`/`withdraw`). Used to validate
|
|
30161
|
+
* `KitError.cause.trace` before resuming so a mismatched or forged trace is
|
|
30162
|
+
* rejected cleanly rather than crashing inside the resume path.
|
|
30163
|
+
*
|
|
30164
|
+
* @param value - The candidate value (typically `error.cause?.trace`).
|
|
30165
|
+
* @returns `true` if `value` is a usable {@link EarnErrorTrace}.
|
|
30166
|
+
*
|
|
30167
|
+
* @example
|
|
30168
|
+
* ```typescript
|
|
30169
|
+
* const trace = error.cause?.trace
|
|
30170
|
+
* if (isEarnErrorTrace(trace)) {
|
|
30171
|
+
* console.log(trace.operation, trace.steps.length)
|
|
30172
|
+
* }
|
|
30173
|
+
* ```
|
|
30174
|
+
*/
|
|
30175
|
+
function isEarnErrorTrace(value) {
|
|
30176
|
+
if (value === null || typeof value !== 'object') {
|
|
30177
|
+
return false;
|
|
30178
|
+
}
|
|
30179
|
+
const candidate = value;
|
|
30180
|
+
const operation = candidate['operation'];
|
|
30181
|
+
if (typeof candidate['provider'] !== 'string' ||
|
|
30182
|
+
typeof operation !== 'string' ||
|
|
30183
|
+
!EARN_OPERATIONS.has(operation) ||
|
|
30184
|
+
!Array.isArray(candidate['steps']) ||
|
|
30185
|
+
candidate['params'] === null ||
|
|
30186
|
+
typeof candidate['params'] !== 'object') {
|
|
30187
|
+
return false;
|
|
30188
|
+
}
|
|
30189
|
+
return hasEarnServiceParamsShape(operation, candidate['params']);
|
|
30190
|
+
}
|
|
30191
|
+
|
|
29997
30192
|
// ---------------------------------------------------------------------------
|
|
29998
30193
|
// Shared primitives
|
|
29999
30194
|
// ---------------------------------------------------------------------------
|
|
@@ -30981,12 +31176,63 @@ async function fetchClaimRewardsQuote(params) {
|
|
|
30981
31176
|
}
|
|
30982
31177
|
}
|
|
30983
31178
|
|
|
31179
|
+
/**
|
|
31180
|
+
* Build the `pending` {@link EarnStep} for a phase about to start,
|
|
31181
|
+
* discriminating on `stepName` so the result is a typed union member rather
|
|
31182
|
+
* than an unsafe `as EarnStep` cast.
|
|
31183
|
+
*
|
|
31184
|
+
* @param stepName - The name of the phase about to start.
|
|
31185
|
+
* @returns The typed pending step for the phase.
|
|
31186
|
+
*/
|
|
31187
|
+
function buildPendingStep(stepName) {
|
|
31188
|
+
if (stepName === 'fetchParams') {
|
|
31189
|
+
return { name: 'fetchParams', state: 'pending' };
|
|
31190
|
+
}
|
|
31191
|
+
return { name: stepName, state: 'pending' };
|
|
31192
|
+
}
|
|
31193
|
+
/**
|
|
31194
|
+
* Build the `success` {@link EarnStep} for a completed phase, discriminating
|
|
31195
|
+
* on `stepName` so the `fetchParams` variant (which has no `txHash` in its
|
|
31196
|
+
* type) cannot accidentally carry a transaction hash.
|
|
31197
|
+
*
|
|
31198
|
+
* @param stepName - The name of the completed phase.
|
|
31199
|
+
* @param txHash - The on-chain transaction hash, if any.
|
|
31200
|
+
* @returns The typed success step for the phase.
|
|
31201
|
+
*/
|
|
31202
|
+
function buildSuccessStep(stepName, txHash) {
|
|
31203
|
+
if (stepName === 'fetchParams') {
|
|
31204
|
+
return { name: 'fetchParams', state: 'success' };
|
|
31205
|
+
}
|
|
31206
|
+
if (txHash === undefined) {
|
|
31207
|
+
return { name: stepName, state: 'success' };
|
|
31208
|
+
}
|
|
31209
|
+
return { name: stepName, state: 'success', txHash };
|
|
31210
|
+
}
|
|
31211
|
+
/**
|
|
31212
|
+
* Build the `error` {@link EarnStep} for a failed phase, discriminating on
|
|
31213
|
+
* `stepName` so the result is a typed union member rather than an unsafe
|
|
31214
|
+
* `as EarnStep` cast.
|
|
31215
|
+
*
|
|
31216
|
+
* @param stepName - The name of the failed phase.
|
|
31217
|
+
* @param errorMessage - The human-readable failure message.
|
|
31218
|
+
* @param error - The raw error that caused the phase to fail.
|
|
31219
|
+
* @returns The typed error step for the phase.
|
|
31220
|
+
*/
|
|
31221
|
+
function buildErrorStep(stepName, errorMessage, error) {
|
|
31222
|
+
if (stepName === 'fetchParams') {
|
|
31223
|
+
return { name: 'fetchParams', state: 'error', errorMessage, error };
|
|
31224
|
+
}
|
|
31225
|
+
return { name: stepName, state: 'error', errorMessage, error };
|
|
31226
|
+
}
|
|
30984
31227
|
/**
|
|
30985
31228
|
* Earn Service provider for the Circle earn service API.
|
|
30986
31229
|
*
|
|
30987
31230
|
* Implement the {@link EarningProvider} interface for yield-bearing vault
|
|
30988
31231
|
* operations including vault discovery, position queries, deposits,
|
|
30989
|
-
* withdrawals, and reward claiming.
|
|
31232
|
+
* withdrawals, and reward claiming. Multi-phase operations (deposit,
|
|
31233
|
+
* withdraw, claimRewards) emit step-level events through the kit's action
|
|
31234
|
+
* dispatcher and attach step progress to thrown errors so callers can resume
|
|
31235
|
+
* via {@link EarnServiceProvider.retry}.
|
|
30990
31236
|
*
|
|
30991
31237
|
* @example
|
|
30992
31238
|
* ```typescript
|
|
@@ -31017,6 +31263,8 @@ class EarnServiceProvider {
|
|
|
31017
31263
|
name = 'EarnService';
|
|
31018
31264
|
/** {@inheritdoc} */
|
|
31019
31265
|
supportedChains = Object.keys(CHAIN_TO_API).map((chain) => resolveChainIdentifier(chain));
|
|
31266
|
+
/** {@inheritdoc} */
|
|
31267
|
+
actionDispatcher = undefined;
|
|
31020
31268
|
defaultConfig;
|
|
31021
31269
|
/**
|
|
31022
31270
|
* Create a new EarnServiceProvider.
|
|
@@ -31027,12 +31275,49 @@ class EarnServiceProvider {
|
|
|
31027
31275
|
constructor(config) {
|
|
31028
31276
|
this.defaultConfig = config;
|
|
31029
31277
|
}
|
|
31278
|
+
/** {@inheritdoc} */
|
|
31279
|
+
registerDispatcher(dispatcher) {
|
|
31280
|
+
this.actionDispatcher = dispatcher;
|
|
31281
|
+
}
|
|
31030
31282
|
resolveConfig(config) {
|
|
31031
31283
|
const resolvedConfig = this.defaultConfig === undefined
|
|
31032
31284
|
? config
|
|
31033
31285
|
: { ...this.defaultConfig, ...config };
|
|
31034
31286
|
return resolvedConfig;
|
|
31035
31287
|
}
|
|
31288
|
+
/**
|
|
31289
|
+
* Run one phase of a multi-phase earn operation: dispatch a `pending` event,
|
|
31290
|
+
* execute it, then dispatch a `success` event (recording the resulting step)
|
|
31291
|
+
* or — on failure — dispatch an `error` event, record the failed step, and
|
|
31292
|
+
* re-throw the original error for the caller to wrap with full step context.
|
|
31293
|
+
*
|
|
31294
|
+
* @typeParam R - The phase's result type.
|
|
31295
|
+
* @param ctx - The operation context (dispatcher, operation, steps, params).
|
|
31296
|
+
* @param action - The action name to dispatch the events under.
|
|
31297
|
+
* @param stepName - The phase name.
|
|
31298
|
+
* @param run - The phase work to execute.
|
|
31299
|
+
* @param txHashOf - Extracts the on-chain transaction hash from the result, if any.
|
|
31300
|
+
* @returns The phase result.
|
|
31301
|
+
*/
|
|
31302
|
+
async runPhase(ctx, action, stepName, run, txHashOf) {
|
|
31303
|
+
dispatchEarnEvent(ctx.dispatcher, action, ctx.operation, buildPendingStep(stepName));
|
|
31304
|
+
let result;
|
|
31305
|
+
try {
|
|
31306
|
+
result = await run();
|
|
31307
|
+
}
|
|
31308
|
+
catch (error) {
|
|
31309
|
+
const failedStep = buildErrorStep(stepName, getErrorMessage(error), error);
|
|
31310
|
+
ctx.steps.push(failedStep);
|
|
31311
|
+
dispatchEarnEvent(ctx.dispatcher, action, ctx.operation, failedStep);
|
|
31312
|
+
throw error;
|
|
31313
|
+
}
|
|
31314
|
+
// Discriminate on stepName so the `fetchParams` variant (which has no
|
|
31315
|
+
// `txHash` field in its type) cannot accidentally pick up a tx hash.
|
|
31316
|
+
const successStep = buildSuccessStep(stepName, txHashOf(result));
|
|
31317
|
+
ctx.steps.push(successStep);
|
|
31318
|
+
dispatchEarnEvent(ctx.dispatcher, action, ctx.operation, successStep);
|
|
31319
|
+
return result;
|
|
31320
|
+
}
|
|
31036
31321
|
/** {@inheritdoc} */
|
|
31037
31322
|
async getVaults(params) {
|
|
31038
31323
|
const config = this.resolveConfig(params.config);
|
|
@@ -31054,146 +31339,246 @@ class EarnServiceProvider {
|
|
|
31054
31339
|
}
|
|
31055
31340
|
/** {@inheritdoc} */
|
|
31056
31341
|
async deposit(params) {
|
|
31057
|
-
|
|
31058
|
-
|
|
31059
|
-
|
|
31060
|
-
const
|
|
31061
|
-
|
|
31062
|
-
|
|
31063
|
-
|
|
31064
|
-
|
|
31065
|
-
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
address,
|
|
31069
|
-
chain
|
|
31070
|
-
|
|
31071
|
-
|
|
31072
|
-
|
|
31073
|
-
|
|
31074
|
-
|
|
31075
|
-
|
|
31076
|
-
|
|
31077
|
-
|
|
31342
|
+
return this.runDepositFlow(params, { skipApprove: false });
|
|
31343
|
+
}
|
|
31344
|
+
async runDepositFlow(params, options) {
|
|
31345
|
+
const ctx = {
|
|
31346
|
+
dispatcher: this.actionDispatcher,
|
|
31347
|
+
operation: 'deposit',
|
|
31348
|
+
steps: [],
|
|
31349
|
+
params,
|
|
31350
|
+
};
|
|
31351
|
+
try {
|
|
31352
|
+
const config = this.resolveConfig(params.config);
|
|
31353
|
+
const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
|
|
31354
|
+
const adapterContractAddress = requireAdapterContract(chain);
|
|
31355
|
+
const rawUsdcAddress = chain.usdcAddress;
|
|
31356
|
+
if (rawUsdcAddress === null) {
|
|
31357
|
+
throw createUnsupportedTokenError('USDC', chain.name);
|
|
31358
|
+
}
|
|
31359
|
+
const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
|
|
31360
|
+
const { adapter } = params.from;
|
|
31361
|
+
const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async () => fetchDeposit({
|
|
31362
|
+
vaultAddress: params.vaultAddress,
|
|
31363
|
+
amount: params.amount,
|
|
31364
|
+
address,
|
|
31365
|
+
chain: apiChain,
|
|
31366
|
+
config,
|
|
31367
|
+
}), () => undefined);
|
|
31368
|
+
validateExecutionDeadline(executionParams);
|
|
31369
|
+
const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
|
|
31370
|
+
const approvalToken = tokenInputs[0]?.token;
|
|
31371
|
+
if (!options.skipApprove && approvalToken !== undefined) {
|
|
31372
|
+
await this.runPhase(ctx, 'approve', 'approve', async () => approveMaxIfNeeded({
|
|
31373
|
+
adapter,
|
|
31374
|
+
chain,
|
|
31375
|
+
tokenAddress: approvalToken,
|
|
31376
|
+
delegate: adapterContractAddress,
|
|
31377
|
+
address,
|
|
31378
|
+
revertMessage: 'USDC approval reverted on-chain',
|
|
31379
|
+
}), (txHash) => txHash);
|
|
31380
|
+
}
|
|
31381
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async () => executeEarnAction({
|
|
31078
31382
|
adapter,
|
|
31079
31383
|
chain,
|
|
31080
|
-
tokenAddress: approvalToken,
|
|
31081
|
-
delegate: adapterContractAddress,
|
|
31082
31384
|
address,
|
|
31083
|
-
|
|
31385
|
+
actionKey: 'earn.deposit',
|
|
31386
|
+
actionParams: {
|
|
31387
|
+
executeParams: executionParams,
|
|
31388
|
+
tokenInputs,
|
|
31389
|
+
signature,
|
|
31390
|
+
},
|
|
31391
|
+
revertMessage: 'Earn deposit reverted on-chain',
|
|
31392
|
+
}), ({ txHash }) => txHash);
|
|
31393
|
+
return {
|
|
31394
|
+
txHash,
|
|
31395
|
+
explorerUrl,
|
|
31396
|
+
vaultAddress: params.vaultAddress,
|
|
31397
|
+
amount: params.amount,
|
|
31398
|
+
};
|
|
31399
|
+
}
|
|
31400
|
+
catch (error) {
|
|
31401
|
+
throw augmentEarnError(error, {
|
|
31402
|
+
providerName: this.name,
|
|
31403
|
+
operation: ctx.operation,
|
|
31404
|
+
steps: ctx.steps,
|
|
31405
|
+
params: ctx.params,
|
|
31084
31406
|
});
|
|
31085
31407
|
}
|
|
31086
|
-
const { txHash, explorerUrl } = await executeEarnAction({
|
|
31087
|
-
adapter,
|
|
31088
|
-
chain,
|
|
31089
|
-
address,
|
|
31090
|
-
actionKey: 'earn.deposit',
|
|
31091
|
-
actionParams: {
|
|
31092
|
-
executeParams: executionParams,
|
|
31093
|
-
tokenInputs,
|
|
31094
|
-
signature,
|
|
31095
|
-
},
|
|
31096
|
-
revertMessage: 'Earn deposit reverted on-chain',
|
|
31097
|
-
});
|
|
31098
|
-
return {
|
|
31099
|
-
txHash,
|
|
31100
|
-
explorerUrl,
|
|
31101
|
-
vaultAddress: params.vaultAddress,
|
|
31102
|
-
amount: params.amount,
|
|
31103
|
-
};
|
|
31104
31408
|
}
|
|
31105
31409
|
/** {@inheritdoc} */
|
|
31106
31410
|
async withdraw(params) {
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
const
|
|
31111
|
-
|
|
31112
|
-
|
|
31113
|
-
|
|
31114
|
-
|
|
31115
|
-
|
|
31116
|
-
|
|
31117
|
-
|
|
31118
|
-
|
|
31119
|
-
|
|
31120
|
-
|
|
31121
|
-
|
|
31122
|
-
|
|
31123
|
-
|
|
31411
|
+
return this.runWithdrawFlow(params, { skipApprove: false });
|
|
31412
|
+
}
|
|
31413
|
+
async runWithdrawFlow(params, options) {
|
|
31414
|
+
const ctx = {
|
|
31415
|
+
dispatcher: this.actionDispatcher,
|
|
31416
|
+
operation: 'withdraw',
|
|
31417
|
+
steps: [],
|
|
31418
|
+
params,
|
|
31419
|
+
};
|
|
31420
|
+
try {
|
|
31421
|
+
const config = this.resolveConfig(params.config);
|
|
31422
|
+
const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
|
|
31423
|
+
const adapterContractAddress = requireAdapterContract(chain);
|
|
31424
|
+
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
31425
|
+
const { adapter } = params.from;
|
|
31426
|
+
const { executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async () => fetchWithdraw({
|
|
31427
|
+
vaultAddress,
|
|
31428
|
+
amount: params.amount,
|
|
31429
|
+
address,
|
|
31430
|
+
chain: apiChain,
|
|
31431
|
+
config,
|
|
31432
|
+
}), () => undefined);
|
|
31433
|
+
validateExecutionDeadline(executionParams);
|
|
31434
|
+
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
31435
|
+
const approvalToken = tokenInputs[0]?.token;
|
|
31436
|
+
if (!options.skipApprove && approvalToken !== undefined) {
|
|
31437
|
+
await this.runPhase(ctx, 'approve', 'approve', async () => approveMaxIfNeeded({
|
|
31438
|
+
adapter,
|
|
31439
|
+
chain,
|
|
31440
|
+
tokenAddress: approvalToken,
|
|
31441
|
+
delegate: adapterContractAddress,
|
|
31442
|
+
address,
|
|
31443
|
+
revertMessage: 'Vault share token approval reverted on-chain',
|
|
31444
|
+
}), (txHash) => txHash);
|
|
31445
|
+
}
|
|
31446
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async () => executeEarnAction({
|
|
31124
31447
|
adapter,
|
|
31125
31448
|
chain,
|
|
31126
|
-
tokenAddress: approvalToken,
|
|
31127
|
-
delegate: adapterContractAddress,
|
|
31128
31449
|
address,
|
|
31129
|
-
|
|
31450
|
+
actionKey: 'earn.withdraw',
|
|
31451
|
+
actionParams: {
|
|
31452
|
+
executeParams: executionParams,
|
|
31453
|
+
tokenInputs,
|
|
31454
|
+
signature,
|
|
31455
|
+
},
|
|
31456
|
+
revertMessage: 'Earn withdraw reverted on-chain',
|
|
31457
|
+
}), ({ txHash }) => txHash);
|
|
31458
|
+
return {
|
|
31459
|
+
txHash,
|
|
31460
|
+
explorerUrl,
|
|
31461
|
+
vaultAddress,
|
|
31462
|
+
amount: params.amount,
|
|
31463
|
+
};
|
|
31464
|
+
}
|
|
31465
|
+
catch (error) {
|
|
31466
|
+
throw augmentEarnError(error, {
|
|
31467
|
+
providerName: this.name,
|
|
31468
|
+
operation: ctx.operation,
|
|
31469
|
+
steps: ctx.steps,
|
|
31470
|
+
params: ctx.params,
|
|
31130
31471
|
});
|
|
31131
31472
|
}
|
|
31132
|
-
const { txHash, explorerUrl } = await executeEarnAction({
|
|
31133
|
-
adapter,
|
|
31134
|
-
chain,
|
|
31135
|
-
address,
|
|
31136
|
-
actionKey: 'earn.withdraw',
|
|
31137
|
-
actionParams: {
|
|
31138
|
-
executeParams: executionParams,
|
|
31139
|
-
tokenInputs,
|
|
31140
|
-
signature,
|
|
31141
|
-
},
|
|
31142
|
-
revertMessage: 'Earn withdraw reverted on-chain',
|
|
31143
|
-
});
|
|
31144
|
-
return {
|
|
31145
|
-
txHash,
|
|
31146
|
-
explorerUrl,
|
|
31147
|
-
vaultAddress,
|
|
31148
|
-
amount: params.amount,
|
|
31149
|
-
};
|
|
31150
31473
|
}
|
|
31151
31474
|
/** {@inheritdoc} */
|
|
31152
31475
|
async claimRewards(params) {
|
|
31153
|
-
|
|
31154
|
-
|
|
31155
|
-
|
|
31156
|
-
|
|
31157
|
-
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
|
|
31164
|
-
|
|
31165
|
-
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
31170
|
-
|
|
31171
|
-
|
|
31172
|
-
|
|
31173
|
-
|
|
31174
|
-
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31476
|
+
return this.runClaimRewardsFlow(params);
|
|
31477
|
+
}
|
|
31478
|
+
async runClaimRewardsFlow(params) {
|
|
31479
|
+
const ctx = {
|
|
31480
|
+
dispatcher: this.actionDispatcher,
|
|
31481
|
+
operation: 'claimRewards',
|
|
31482
|
+
steps: [],
|
|
31483
|
+
params,
|
|
31484
|
+
};
|
|
31485
|
+
try {
|
|
31486
|
+
const config = this.resolveConfig(params.config);
|
|
31487
|
+
const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
|
|
31488
|
+
// Claim rewards has no approval step, but still requires adapter support.
|
|
31489
|
+
requireAdapterContract(chain);
|
|
31490
|
+
const { rewards, executionParams, signature } = await this.runPhase(ctx, 'claimRewards', 'fetchParams', async () => fetchClaimRewards({
|
|
31491
|
+
address,
|
|
31492
|
+
chain: apiChain,
|
|
31493
|
+
vaultAddress: params.vaultAddress,
|
|
31494
|
+
config,
|
|
31495
|
+
}), () => undefined);
|
|
31496
|
+
const nothingToClaim = rewards.length === 0;
|
|
31497
|
+
if (nothingToClaim) {
|
|
31498
|
+
return { status: 'no_rewards', rewards: [] };
|
|
31499
|
+
}
|
|
31500
|
+
const missingExecutionParams = executionParams === undefined;
|
|
31501
|
+
const missingSignature = signature === undefined;
|
|
31502
|
+
if (missingExecutionParams || missingSignature) {
|
|
31503
|
+
throw new KitError({
|
|
31504
|
+
...EarnError.INTERNAL_ERROR,
|
|
31505
|
+
recoverability: 'RETRYABLE',
|
|
31506
|
+
message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
|
|
31507
|
+
cause: {
|
|
31508
|
+
trace: {
|
|
31509
|
+
rewardsCount: rewards.length,
|
|
31510
|
+
missingExecutionParams,
|
|
31511
|
+
missingSignature,
|
|
31512
|
+
},
|
|
31179
31513
|
},
|
|
31514
|
+
});
|
|
31515
|
+
}
|
|
31516
|
+
validateExecutionDeadline(executionParams);
|
|
31517
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'claimRewards', 'execute', async () => executeEarnAction({
|
|
31518
|
+
adapter: params.from.adapter,
|
|
31519
|
+
chain,
|
|
31520
|
+
address,
|
|
31521
|
+
actionKey: 'earn.claimRewards',
|
|
31522
|
+
actionParams: {
|
|
31523
|
+
executeParams: executionParams,
|
|
31524
|
+
tokenInputs: [],
|
|
31525
|
+
signature,
|
|
31180
31526
|
},
|
|
31527
|
+
revertMessage: 'Earn claim rewards reverted on-chain',
|
|
31528
|
+
}), ({ txHash }) => txHash);
|
|
31529
|
+
return { status: 'claimed', rewards, txHash, explorerUrl };
|
|
31530
|
+
}
|
|
31531
|
+
catch (error) {
|
|
31532
|
+
throw augmentEarnError(error, {
|
|
31533
|
+
providerName: this.name,
|
|
31534
|
+
operation: ctx.operation,
|
|
31535
|
+
steps: ctx.steps,
|
|
31536
|
+
params: ctx.params,
|
|
31181
31537
|
});
|
|
31182
31538
|
}
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
|
|
31192
|
-
|
|
31193
|
-
|
|
31194
|
-
|
|
31195
|
-
|
|
31196
|
-
|
|
31539
|
+
}
|
|
31540
|
+
/** {@inheritdoc} */
|
|
31541
|
+
supportsRetry(error) {
|
|
31542
|
+
return (isKitError(error) &&
|
|
31543
|
+
isRetryableError$1(error) &&
|
|
31544
|
+
isEarnErrorTrace(error.cause?.trace) &&
|
|
31545
|
+
error.cause.trace.provider === this.name);
|
|
31546
|
+
}
|
|
31547
|
+
/** {@inheritdoc} */
|
|
31548
|
+
async retry(error) {
|
|
31549
|
+
// Validation order mirrors EarnKit.retry().
|
|
31550
|
+
if (!isKitError(error)) {
|
|
31551
|
+
throw createValidationFailedError$1('error', error, 'retry() requires a KitError thrown by a previous earn operation');
|
|
31552
|
+
}
|
|
31553
|
+
if (!isRetryableError$1(error)) {
|
|
31554
|
+
throw createValidationFailedError$1('error.recoverability', error.recoverability, 'retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
31555
|
+
}
|
|
31556
|
+
const trace = error.cause?.trace;
|
|
31557
|
+
if (!isEarnErrorTrace(trace)) {
|
|
31558
|
+
throw createValidationFailedError$1('error.cause.trace', trace, 'retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
31559
|
+
}
|
|
31560
|
+
if (trace.provider !== this.name) {
|
|
31561
|
+
throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `Cannot retry: error was produced by provider "${trace.provider}", not "${this.name}"`);
|
|
31562
|
+
}
|
|
31563
|
+
const approveCompleted = trace.steps.some((step) => step.name === 'approve' && step.state === 'success');
|
|
31564
|
+
// `trace` is a discriminated union on `operation`, so each branch narrows
|
|
31565
|
+
// `trace.params` to the matching service-params type — no cast needed.
|
|
31566
|
+
switch (trace.operation) {
|
|
31567
|
+
case 'deposit':
|
|
31568
|
+
return this.runDepositFlow(trace.params, {
|
|
31569
|
+
skipApprove: approveCompleted,
|
|
31570
|
+
});
|
|
31571
|
+
case 'withdraw':
|
|
31572
|
+
return this.runWithdrawFlow(trace.params, {
|
|
31573
|
+
skipApprove: approveCompleted,
|
|
31574
|
+
});
|
|
31575
|
+
case 'claimRewards':
|
|
31576
|
+
return this.runClaimRewardsFlow(trace.params);
|
|
31577
|
+
default: {
|
|
31578
|
+
const exhaustive = trace;
|
|
31579
|
+
throw createValidationFailedError$1('error.cause.trace.operation', exhaustive, 'retry() does not support this earn operation');
|
|
31580
|
+
}
|
|
31581
|
+
}
|
|
31197
31582
|
}
|
|
31198
31583
|
/** {@inheritdoc} */
|
|
31199
31584
|
async getDepositQuote(params) {
|
|
@@ -31277,54 +31662,6 @@ function createEarnKitContext(config = {}) {
|
|
|
31277
31662
|
return context;
|
|
31278
31663
|
}
|
|
31279
31664
|
|
|
31280
|
-
/**
|
|
31281
|
-
* Symbol used to track that assertEarnParams has validated an object.
|
|
31282
|
-
* @internal
|
|
31283
|
-
*/
|
|
31284
|
-
const ASSERT_EARN_PARAMS_SYMBOL = Symbol('assertEarnParams');
|
|
31285
|
-
/**
|
|
31286
|
-
* Assert that the provided value conforms to the given earn params schema.
|
|
31287
|
-
*
|
|
31288
|
-
* Validate earn parameters using the provided Zod schema and track
|
|
31289
|
-
* validation state to avoid duplicate checks. Throw a structured
|
|
31290
|
-
* error with detailed validation messages if any parameter is invalid.
|
|
31291
|
-
*
|
|
31292
|
-
* @typeParam T - The expected type after validation
|
|
31293
|
-
* @param params - The earn parameters to validate
|
|
31294
|
-
* @param schema - The Zod schema to validate against
|
|
31295
|
-
* @throws {@link KitError} If the parameters fail validation
|
|
31296
|
-
*
|
|
31297
|
-
* @example
|
|
31298
|
-
* ```typescript
|
|
31299
|
-
* import { assertEarnParams, depositParamsSchema } from '@circle-fin/earn-kit'
|
|
31300
|
-
*
|
|
31301
|
-
* assertEarnParams(params, depositParamsSchema)
|
|
31302
|
-
* ```
|
|
31303
|
-
*/
|
|
31304
|
-
function assertEarnParams(params, schema) {
|
|
31305
|
-
validateWithStateTracking(params, schema, 'earn parameters', ASSERT_EARN_PARAMS_SYMBOL);
|
|
31306
|
-
}
|
|
31307
|
-
|
|
31308
|
-
function findProvider(context, chain, operation = 'earn') {
|
|
31309
|
-
let fallback;
|
|
31310
|
-
for (const provider of context.providers) {
|
|
31311
|
-
fallback ??= provider;
|
|
31312
|
-
if (provider.supportedChains.length === 0)
|
|
31313
|
-
continue;
|
|
31314
|
-
if (chain === undefined ||
|
|
31315
|
-
provider.supportedChains.some((c) => c.chain === chain.chain)) {
|
|
31316
|
-
return provider;
|
|
31317
|
-
}
|
|
31318
|
-
}
|
|
31319
|
-
if (fallback === undefined) {
|
|
31320
|
-
throw createValidationFailedError$1('context.providers', [], 'No earn providers configured');
|
|
31321
|
-
}
|
|
31322
|
-
if (chain !== undefined) {
|
|
31323
|
-
throw createUnsupportedEarnRouteError(operation, chain.name);
|
|
31324
|
-
}
|
|
31325
|
-
return fallback;
|
|
31326
|
-
}
|
|
31327
|
-
|
|
31328
31665
|
/**
|
|
31329
31666
|
* Format a provider amount object as a human-readable decimal string.
|
|
31330
31667
|
*
|
|
@@ -31467,6 +31804,54 @@ function formatClaimRewardsResult(result) {
|
|
|
31467
31804
|
};
|
|
31468
31805
|
}
|
|
31469
31806
|
|
|
31807
|
+
/**
|
|
31808
|
+
* Symbol used to track that assertEarnParams has validated an object.
|
|
31809
|
+
* @internal
|
|
31810
|
+
*/
|
|
31811
|
+
const ASSERT_EARN_PARAMS_SYMBOL = Symbol('assertEarnParams');
|
|
31812
|
+
/**
|
|
31813
|
+
* Assert that the provided value conforms to the given earn params schema.
|
|
31814
|
+
*
|
|
31815
|
+
* Validate earn parameters using the provided Zod schema and track
|
|
31816
|
+
* validation state to avoid duplicate checks. Throw a structured
|
|
31817
|
+
* error with detailed validation messages if any parameter is invalid.
|
|
31818
|
+
*
|
|
31819
|
+
* @typeParam T - The expected type after validation
|
|
31820
|
+
* @param params - The earn parameters to validate
|
|
31821
|
+
* @param schema - The Zod schema to validate against
|
|
31822
|
+
* @throws {@link KitError} If the parameters fail validation
|
|
31823
|
+
*
|
|
31824
|
+
* @example
|
|
31825
|
+
* ```typescript
|
|
31826
|
+
* import { assertEarnParams, depositParamsSchema } from '@circle-fin/earn-kit'
|
|
31827
|
+
*
|
|
31828
|
+
* assertEarnParams(params, depositParamsSchema)
|
|
31829
|
+
* ```
|
|
31830
|
+
*/
|
|
31831
|
+
function assertEarnParams(params, schema) {
|
|
31832
|
+
validateWithStateTracking(params, schema, 'earn parameters', ASSERT_EARN_PARAMS_SYMBOL);
|
|
31833
|
+
}
|
|
31834
|
+
|
|
31835
|
+
function findProvider(context, chain, operation = 'earn') {
|
|
31836
|
+
let fallback;
|
|
31837
|
+
for (const provider of context.providers) {
|
|
31838
|
+
fallback ??= provider;
|
|
31839
|
+
if (provider.supportedChains.length === 0)
|
|
31840
|
+
continue;
|
|
31841
|
+
if (chain === undefined ||
|
|
31842
|
+
provider.supportedChains.some((c) => c.chain === chain.chain)) {
|
|
31843
|
+
return provider;
|
|
31844
|
+
}
|
|
31845
|
+
}
|
|
31846
|
+
if (fallback === undefined) {
|
|
31847
|
+
throw createValidationFailedError$1('context.providers', [], 'No earn providers configured');
|
|
31848
|
+
}
|
|
31849
|
+
if (chain !== undefined) {
|
|
31850
|
+
throw createUnsupportedEarnRouteError(operation, chain.name);
|
|
31851
|
+
}
|
|
31852
|
+
return fallback;
|
|
31853
|
+
}
|
|
31854
|
+
|
|
31470
31855
|
/**
|
|
31471
31856
|
* Schema for the adapter context within earn operations.
|
|
31472
31857
|
*
|
|
@@ -32116,6 +32501,19 @@ async function getClaimRewardsQuote$1(context, params) {
|
|
|
32116
32501
|
return formatClaimRewardsQuoteInfo(result);
|
|
32117
32502
|
}
|
|
32118
32503
|
|
|
32504
|
+
function formatRetryResult(operation, result) {
|
|
32505
|
+
switch (operation) {
|
|
32506
|
+
case 'deposit':
|
|
32507
|
+
case 'withdraw':
|
|
32508
|
+
return result;
|
|
32509
|
+
case 'claimRewards':
|
|
32510
|
+
return formatClaimRewardsResult(result);
|
|
32511
|
+
default: {
|
|
32512
|
+
const exhaustive = operation;
|
|
32513
|
+
throw createValidationFailedError$1('error.cause.trace.operation', exhaustive, 'EarnKit.retry() does not support this earn operation');
|
|
32514
|
+
}
|
|
32515
|
+
}
|
|
32516
|
+
}
|
|
32119
32517
|
/**
|
|
32120
32518
|
* A high-level class-based interface for DeFi lending vault operations.
|
|
32121
32519
|
*
|
|
@@ -32173,6 +32571,12 @@ async function getClaimRewardsQuote$1(context, params) {
|
|
|
32173
32571
|
*/
|
|
32174
32572
|
class EarnKit {
|
|
32175
32573
|
context;
|
|
32574
|
+
/**
|
|
32575
|
+
* Event dispatcher for step-level events emitted during multi-phase earn
|
|
32576
|
+
* operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
|
|
32577
|
+
* this directly.
|
|
32578
|
+
*/
|
|
32579
|
+
actionDispatcher;
|
|
32176
32580
|
/**
|
|
32177
32581
|
* Create a new EarnKit instance.
|
|
32178
32582
|
*
|
|
@@ -32192,6 +32596,89 @@ class EarnKit {
|
|
|
32192
32596
|
*/
|
|
32193
32597
|
constructor(config = {}) {
|
|
32194
32598
|
this.context = createEarnKitContext(config);
|
|
32599
|
+
this.actionDispatcher = new Actionable();
|
|
32600
|
+
for (const provider of this.context.providers) {
|
|
32601
|
+
provider.registerDispatcher(this.actionDispatcher);
|
|
32602
|
+
}
|
|
32603
|
+
}
|
|
32604
|
+
on(action, handler) {
|
|
32605
|
+
if (action === '*') {
|
|
32606
|
+
this.actionDispatcher.on('*', handler);
|
|
32607
|
+
}
|
|
32608
|
+
else {
|
|
32609
|
+
this.actionDispatcher.on(action, handler);
|
|
32610
|
+
}
|
|
32611
|
+
}
|
|
32612
|
+
off(action, handler) {
|
|
32613
|
+
if (action === '*') {
|
|
32614
|
+
this.actionDispatcher.off('*', handler);
|
|
32615
|
+
}
|
|
32616
|
+
else {
|
|
32617
|
+
this.actionDispatcher.off(action, handler);
|
|
32618
|
+
}
|
|
32619
|
+
}
|
|
32620
|
+
/**
|
|
32621
|
+
* Resume a multi-phase earn operation that previously failed.
|
|
32622
|
+
*
|
|
32623
|
+
* Pass the {@link KitError} caught from a `deposit`, `withdraw`, or
|
|
32624
|
+
* `claimRewards` call. The error carries the original operation, inputs,
|
|
32625
|
+
* and step progress, so the operation can be re-run while skipping phases
|
|
32626
|
+
* that already completed (for example a successful token approval). Use
|
|
32627
|
+
* `isRetryableError(error)` to check whether retrying is worthwhile before
|
|
32628
|
+
* calling this.
|
|
32629
|
+
*
|
|
32630
|
+
* @remarks
|
|
32631
|
+
* Resuming re-fetches execution params and re-submits the `execute`
|
|
32632
|
+
* transaction; the earn service deduplicates execution server-side, which
|
|
32633
|
+
* makes this safe in the common case. But if a prior attempt broadcast the
|
|
32634
|
+
* `execute` transaction and then failed before its receipt was observed,
|
|
32635
|
+
* that transaction may still be in flight when `retry()` re-broadcasts.
|
|
32636
|
+
* Treat `retry()` as best-effort recovery, not an atomic operation.
|
|
32637
|
+
*
|
|
32638
|
+
* @param error - The error caught from a previous multi-phase earn operation.
|
|
32639
|
+
* @returns A promise resolving to the result of the resumed operation.
|
|
32640
|
+
* @throws {@link KitError} If `error` is not a retryable {@link KitError}
|
|
32641
|
+
* carrying earn retry context, names an unknown provider, or the resumed
|
|
32642
|
+
* operation itself fails.
|
|
32643
|
+
*
|
|
32644
|
+
* @example
|
|
32645
|
+
* ```typescript
|
|
32646
|
+
* import { isRetryableError } from '@circle-fin/earn-kit'
|
|
32647
|
+
*
|
|
32648
|
+
* try {
|
|
32649
|
+
* await kit.deposit(params)
|
|
32650
|
+
* } catch (error) {
|
|
32651
|
+
* if (isRetryableError(error)) {
|
|
32652
|
+
* const result = await kit.retry(error)
|
|
32653
|
+
* console.log('recovered, tx:', 'txHash' in result ? result.txHash : undefined)
|
|
32654
|
+
* }
|
|
32655
|
+
* }
|
|
32656
|
+
* ```
|
|
32657
|
+
*/
|
|
32658
|
+
async retry(error) {
|
|
32659
|
+
if (!isKitError(error)) {
|
|
32660
|
+
throw createValidationFailedError$1('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
|
|
32661
|
+
}
|
|
32662
|
+
if (!isRetryableError$1(error)) {
|
|
32663
|
+
throw createValidationFailedError$1('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
32664
|
+
}
|
|
32665
|
+
const trace = error.cause?.trace;
|
|
32666
|
+
if (!isEarnErrorTrace(trace)) {
|
|
32667
|
+
throw createValidationFailedError$1('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
32668
|
+
}
|
|
32669
|
+
const provider = this.context.providers.find((candidate) => candidate.name === trace.provider);
|
|
32670
|
+
if (provider === undefined) {
|
|
32671
|
+
throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
|
|
32672
|
+
}
|
|
32673
|
+
const result = await provider.retry(error);
|
|
32674
|
+
// `provider.retry` returns a flat result union with no compile-time link to
|
|
32675
|
+
// `trace.operation`, so narrow the operation here to select the matching
|
|
32676
|
+
// overload. The result cast in each branch is sound: the provider always
|
|
32677
|
+
// returns the result type corresponding to the resumed operation.
|
|
32678
|
+
if (trace.operation === 'claimRewards') {
|
|
32679
|
+
return formatRetryResult(trace.operation, result);
|
|
32680
|
+
}
|
|
32681
|
+
return formatRetryResult(trace.operation, result);
|
|
32195
32682
|
}
|
|
32196
32683
|
/**
|
|
32197
32684
|
* Return the chains supported by configured earn providers.
|
|
@@ -33534,7 +34021,7 @@ async function getClaimRewardsQuote(context, params) {
|
|
|
33534
34021
|
}
|
|
33535
34022
|
|
|
33536
34023
|
var name = "@circle-fin/unified-balance-kit";
|
|
33537
|
-
var version = "1.1.
|
|
34024
|
+
var version = "1.1.3";
|
|
33538
34025
|
var pkg = {
|
|
33539
34026
|
name: name,
|
|
33540
34027
|
version: version};
|
|
@@ -34007,10 +34494,6 @@ async function deposit$1(params) {
|
|
|
34007
34494
|
const signerAddress = await resolveSignerAddress(params.from, chain);
|
|
34008
34495
|
const resolvedContext = { ...operationContext, address: signerAddress };
|
|
34009
34496
|
await validateDepositBalance(params.from.adapter, chain, valueBigInt, resolvedContext, params.token);
|
|
34010
|
-
await validateNativeBalanceForTransaction({
|
|
34011
|
-
adapter: params.from.adapter,
|
|
34012
|
-
operationContext: { ...resolvedContext, chain },
|
|
34013
|
-
});
|
|
34014
34497
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
34015
34498
|
const strategy = params.allowanceStrategy ?? 'authorize';
|
|
34016
34499
|
let txHash;
|
|
@@ -34055,10 +34538,6 @@ async function depositFor$1(params) {
|
|
|
34055
34538
|
const signerAddress = await resolveSignerAddress(params.from, chain);
|
|
34056
34539
|
const resolvedContext = { ...operationContext, address: signerAddress };
|
|
34057
34540
|
await validateDepositBalance(params.from.adapter, chain, valueBigInt, resolvedContext, params.token);
|
|
34058
|
-
await validateNativeBalanceForTransaction({
|
|
34059
|
-
adapter: params.from.adapter,
|
|
34060
|
-
operationContext: { ...resolvedContext, chain },
|
|
34061
|
-
});
|
|
34062
34541
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
34063
34542
|
let txHash;
|
|
34064
34543
|
if (chain.type === 'solana') {
|
|
@@ -37342,10 +37821,6 @@ async function updateDelegate(params, action, state) {
|
|
|
37342
37821
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
37343
37822
|
assertValidAddress(delegateAddress, chain, 'delegate');
|
|
37344
37823
|
assertNotSelfDelegation(chain, signerAddress, delegateAddress, action);
|
|
37345
|
-
await validateNativeBalanceForTransaction({
|
|
37346
|
-
adapter,
|
|
37347
|
-
operationContext: { ...operationContext, chain, address: signerAddress },
|
|
37348
|
-
});
|
|
37349
37824
|
const request = await adapter.prepareAction(action, { token: tokenAddress, delegate: delegateAddress, chain }, operationContext);
|
|
37350
37825
|
const txHash = await executeAndWait(adapter, request, chain);
|
|
37351
37826
|
return {
|
|
@@ -37548,10 +38023,6 @@ async function initiateRemoveFund$1(params) {
|
|
|
37548
38023
|
const signerAddress = await resolveSignerAddress(from, chain);
|
|
37549
38024
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
37550
38025
|
const value = parseAmountSafe(amount);
|
|
37551
|
-
await validateNativeBalanceForTransaction({
|
|
37552
|
-
adapter,
|
|
37553
|
-
operationContext: { ...operationContext, chain, address: signerAddress },
|
|
37554
|
-
});
|
|
37555
38026
|
const request = await adapter.prepareAction('gateway.v1.initiateWithdrawal', { token: tokenAddress, value, chain }, operationContext);
|
|
37556
38027
|
let txHash;
|
|
37557
38028
|
try {
|
|
@@ -37610,10 +38081,6 @@ async function removeFund$1(params) {
|
|
|
37610
38081
|
const operationContext = extractOperationContext(from);
|
|
37611
38082
|
const signerAddress = await resolveSignerAddress(from, chain);
|
|
37612
38083
|
const tokenAddress = getTokenAddress(chain, params.token);
|
|
37613
|
-
await validateNativeBalanceForTransaction({
|
|
37614
|
-
adapter,
|
|
37615
|
-
operationContext: { ...operationContext, chain, address: signerAddress },
|
|
37616
|
-
});
|
|
37617
38084
|
// Read the pending balance before withdrawing — the contract resets it to 0
|
|
37618
38085
|
// after withdraw() executes, so this is the only way to capture the amount.
|
|
37619
38086
|
const withdrawingBalanceReq = await adapter.prepareAction('gateway.v1.withdrawingBalance', { token: tokenAddress, depositor: signerAddress, chain }, operationContext);
|