@circle-fin/app-kit 1.12.1 → 1.14.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 +115 -0
- package/README.md +31 -12
- package/bridge.cjs +3231 -340
- package/bridge.d.cts +141 -17
- package/bridge.d.mts +141 -17
- package/bridge.d.ts +141 -17
- package/bridge.mjs +3233 -342
- package/chains.cjs +261 -8
- package/chains.d.cts +135 -1
- package/chains.d.mts +135 -1
- package/chains.d.ts +135 -1
- package/chains.mjs +260 -9
- package/context.d.cts +141 -17
- package/context.d.mts +141 -17
- package/context.d.ts +141 -17
- package/earn.cjs +887 -63
- package/earn.d.cts +141 -17
- package/earn.d.mts +141 -17
- package/earn.d.ts +141 -17
- package/earn.mjs +883 -63
- package/estimateBridge.cjs +3231 -340
- package/estimateBridge.d.cts +257 -18
- package/estimateBridge.d.mts +257 -18
- package/estimateBridge.d.ts +257 -18
- package/estimateBridge.mjs +3233 -342
- package/estimateSwap.cjs +988 -146
- package/estimateSwap.d.cts +141 -17
- package/estimateSwap.d.mts +141 -17
- package/estimateSwap.d.ts +141 -17
- package/estimateSwap.mjs +987 -146
- package/index.cjs +6828 -894
- package/index.d.cts +1621 -258
- package/index.d.mts +1621 -258
- package/index.d.ts +1621 -258
- package/index.mjs +6830 -896
- package/package.json +6 -6
- package/swap.cjs +988 -146
- package/swap.d.cts +148 -18
- package/swap.d.mts +148 -18
- package/swap.d.ts +148 -18
- package/swap.mjs +987 -146
- package/unifiedBalance.cjs +22219 -9689
- package/unifiedBalance.d.cts +620 -162
- package/unifiedBalance.d.mts +620 -162
- package/unifiedBalance.d.ts +620 -162
- package/unifiedBalance.mjs +22348 -9819
package/estimateBridge.mjs
CHANGED
|
@@ -30,9 +30,9 @@ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
|
30
30
|
import { z } from 'zod';
|
|
31
31
|
import pino from 'pino';
|
|
32
32
|
import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ethersproject/units';
|
|
33
|
-
import { hexlify, hexZeroPad } from '@ethersproject/bytes';
|
|
34
|
-
import '@ethersproject/abi';
|
|
35
|
-
import { getAddress } from '@ethersproject/address';
|
|
33
|
+
import { hexlify, hexZeroPad, isHexString, concat } from '@ethersproject/bytes';
|
|
34
|
+
import { Interface, defaultAbiCoder } from '@ethersproject/abi';
|
|
35
|
+
import { getAddress, isAddress } from '@ethersproject/address';
|
|
36
36
|
import bs58 from 'bs58';
|
|
37
37
|
import { PublicKey } from '@solana/web3.js';
|
|
38
38
|
import 'bn.js';
|
|
@@ -785,6 +785,32 @@ class KitError extends Error {
|
|
|
785
785
|
type: 'ONCHAIN'
|
|
786
786
|
}
|
|
787
787
|
};
|
|
788
|
+
/**
|
|
789
|
+
* Standardized error definitions for LIQUIDITY type errors.
|
|
790
|
+
*
|
|
791
|
+
* LIQUIDITY errors indicate that an upstream provider or AMM cannot fulfill
|
|
792
|
+
* the requested swap size due to insufficient liquidity at the moment.
|
|
793
|
+
* These are typically transient — retrying later or reducing the amount
|
|
794
|
+
* may succeed once liquidity replenishes.
|
|
795
|
+
*
|
|
796
|
+
* @example
|
|
797
|
+
* ```typescript
|
|
798
|
+
* import { LiquidityError } from '@core/errors'
|
|
799
|
+
*
|
|
800
|
+
* const error = new KitError({
|
|
801
|
+
* ...LiquidityError.INSUFFICIENT_LIQUIDITY,
|
|
802
|
+
* recoverability: 'RETRYABLE',
|
|
803
|
+
* message: 'Insufficient liquidity for the requested swap',
|
|
804
|
+
* cause: { trace: { token: '0xA0b86991...' } }
|
|
805
|
+
* })
|
|
806
|
+
* ```
|
|
807
|
+
*/ const LiquidityError = {
|
|
808
|
+
/** Upstream provider has a route but cannot fulfill the requested size right now */ INSUFFICIENT_LIQUIDITY: {
|
|
809
|
+
code: 6001,
|
|
810
|
+
name: 'LIQUIDITY_INSUFFICIENT',
|
|
811
|
+
type: 'LIQUIDITY'
|
|
812
|
+
}
|
|
813
|
+
};
|
|
788
814
|
/**
|
|
789
815
|
* Standardized error definitions for RPC type errors.
|
|
790
816
|
*
|
|
@@ -832,7 +858,10 @@ class KitError extends Error {
|
|
|
832
858
|
type: 'NETWORK'
|
|
833
859
|
},
|
|
834
860
|
/** Network request timeout */ TIMEOUT: {
|
|
835
|
-
code: 3002
|
|
861
|
+
code: 3002,
|
|
862
|
+
name: 'NETWORK_TIMEOUT',
|
|
863
|
+
type: 'NETWORK'
|
|
864
|
+
},
|
|
836
865
|
/** Circle relayer failed to process the forwarding/mint transaction */ RELAYER_FORWARD_FAILED: {
|
|
837
866
|
code: 3003,
|
|
838
867
|
name: 'NETWORK_RELAYER_FORWARD_FAILED',
|
|
@@ -843,6 +872,58 @@ class KitError extends Error {
|
|
|
843
872
|
name: 'NETWORK_RELAYER_PENDING',
|
|
844
873
|
type: 'NETWORK'
|
|
845
874
|
}};
|
|
875
|
+
/**
|
|
876
|
+
* Standardized error definitions for RATE_LIMIT type errors.
|
|
877
|
+
*
|
|
878
|
+
* RATE_LIMIT errors indicate API throttling, request frequency limits errors.
|
|
879
|
+
*
|
|
880
|
+
* @example
|
|
881
|
+
* ```typescript
|
|
882
|
+
* import { RateLimitError } from '@core/errors'
|
|
883
|
+
*
|
|
884
|
+
* const error = new KitError({
|
|
885
|
+
* ...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
886
|
+
* recoverability: 'RETRYABLE',
|
|
887
|
+
* message: 'Rate limit exceeded, please retry later',
|
|
888
|
+
* cause: { trace: { error: '429 Too Many Requests' } }
|
|
889
|
+
* })
|
|
890
|
+
* ```
|
|
891
|
+
*/ const RateLimitError = {
|
|
892
|
+
/** Rate limit exceeded */ RATE_LIMIT_EXCEEDED: {
|
|
893
|
+
code: 7001,
|
|
894
|
+
name: 'RATE_LIMIT_EXCEEDED',
|
|
895
|
+
type: 'RATE_LIMIT'
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
/**
|
|
899
|
+
* Standardized error definitions for SERVICE type errors.
|
|
900
|
+
*
|
|
901
|
+
* SERVICE errors indicate internal service failures, HTTP 5xx errors,
|
|
902
|
+
* or backend processing issues that are retryable.
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* ```typescript
|
|
906
|
+
* import { ServiceError } from '@core/errors'
|
|
907
|
+
*
|
|
908
|
+
* const error = new KitError({
|
|
909
|
+
* ...ServiceError.INTERNAL_ERROR,
|
|
910
|
+
* recoverability: 'RETRYABLE',
|
|
911
|
+
* message: 'Service encountered an internal error (500)',
|
|
912
|
+
* cause: { trace: { statusCode: 500 } }
|
|
913
|
+
* })
|
|
914
|
+
* ```
|
|
915
|
+
*/ const ServiceError = {
|
|
916
|
+
/** Internal server error (HTTP 5xx) */ INTERNAL_ERROR: {
|
|
917
|
+
code: 8001,
|
|
918
|
+
name: 'SERVICE_INTERNAL_ERROR',
|
|
919
|
+
type: 'SERVICE'
|
|
920
|
+
},
|
|
921
|
+
/** Unknown or unclassified error that cannot be categorized */ UNKNOWN_ERROR: {
|
|
922
|
+
code: 8002,
|
|
923
|
+
name: 'SERVICE_UNKNOWN_ERROR',
|
|
924
|
+
type: 'SERVICE'
|
|
925
|
+
}
|
|
926
|
+
};
|
|
846
927
|
|
|
847
928
|
/**
|
|
848
929
|
* Creates error for network type mismatch between source and destination.
|
|
@@ -2214,6 +2295,32 @@ class KitError extends Error {
|
|
|
2214
2295
|
}
|
|
2215
2296
|
return false;
|
|
2216
2297
|
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Type guard to check if error is KitError with RATE_LIMIT type.
|
|
2300
|
+
*
|
|
2301
|
+
* RATE_LIMIT errors indicate API throttling or request frequency limits.
|
|
2302
|
+
* These errors are typically RETRYABLE after a delay.
|
|
2303
|
+
*
|
|
2304
|
+
* @param error - Unknown error to check
|
|
2305
|
+
* @returns True if error is KitError with RATE_LIMIT type
|
|
2306
|
+
*
|
|
2307
|
+
* @example
|
|
2308
|
+
* ```typescript
|
|
2309
|
+
* import { isRateLimitError } from '@core/errors'
|
|
2310
|
+
*
|
|
2311
|
+
* try {
|
|
2312
|
+
* await kit.bridge(params)
|
|
2313
|
+
* } catch (error) {
|
|
2314
|
+
* if (isRateLimitError(error)) {
|
|
2315
|
+
* console.log('Rate limited, retrying in 60s')
|
|
2316
|
+
* await sleep(60000)
|
|
2317
|
+
* retry()
|
|
2318
|
+
* }
|
|
2319
|
+
* }
|
|
2320
|
+
* ```
|
|
2321
|
+
*/ function isRateLimitError(error) {
|
|
2322
|
+
return isKitError(error) && error.type === ERROR_TYPES.RATE_LIMIT;
|
|
2323
|
+
}
|
|
2217
2324
|
/**
|
|
2218
2325
|
* Safely extracts error message from any error type.
|
|
2219
2326
|
*
|
|
@@ -2463,6 +2570,478 @@ class KitError extends Error {
|
|
|
2463
2570
|
return chain;
|
|
2464
2571
|
}
|
|
2465
2572
|
|
|
2573
|
+
/**
|
|
2574
|
+
* Proxy-specific structured error codes carried in `responseBody.code`.
|
|
2575
|
+
*
|
|
2576
|
+
* These are NOT HTTP status codes — they are application-level identifiers
|
|
2577
|
+
* the stablecoin-kits-proxy emits inside JSON error bodies so the kit can
|
|
2578
|
+
* distinguish conditions that share the same HTTP status (e.g. a 400 caused
|
|
2579
|
+
* by an out-of-range swap amount vs. a generic validation failure).
|
|
2580
|
+
*
|
|
2581
|
+
* @internal
|
|
2582
|
+
*/ const ProxyErrorCode = {
|
|
2583
|
+
/** Swap amount outside the upstream provider's accepted bounds (HTTP 400) */ INVALID_SWAP_AMOUNT: 331017,
|
|
2584
|
+
/** Upstream liquidity insufficient for the requested size (HTTP 503) */ LOW_LIQUIDITY: 331018
|
|
2585
|
+
};
|
|
2586
|
+
/**
|
|
2587
|
+
* Parses raw HTTP API errors into structured KitError instances.
|
|
2588
|
+
*
|
|
2589
|
+
* This function uses pattern matching to identify common HTTP error types
|
|
2590
|
+
* and converts them into standardized KitError format. It handles errors
|
|
2591
|
+
* from fetch, HTTP status codes, timeouts, and network failures.
|
|
2592
|
+
*
|
|
2593
|
+
* The parser recognizes the following error patterns:
|
|
2594
|
+
* - Client errors (4xx) - validation, authentication, not found
|
|
2595
|
+
* - Server errors (5xx) - service unavailability
|
|
2596
|
+
* - Timeout errors
|
|
2597
|
+
* - Network connectivity errors
|
|
2598
|
+
* - Rate limiting
|
|
2599
|
+
*
|
|
2600
|
+
* Unrecognized errors are treated as fatal `SERVICE` errors, as their
|
|
2601
|
+
* cause and recoverability are unknown.
|
|
2602
|
+
*
|
|
2603
|
+
* @param error - The raw error from the API call
|
|
2604
|
+
* @param context - Context information including operation name
|
|
2605
|
+
* @returns A structured KitError instance
|
|
2606
|
+
*
|
|
2607
|
+
* @example
|
|
2608
|
+
* ```typescript
|
|
2609
|
+
* try {
|
|
2610
|
+
* const response = await fetch(url)
|
|
2611
|
+
* } catch (error) {
|
|
2612
|
+
* throw parseApiError(error, { operation: 'getQuote' })
|
|
2613
|
+
* }
|
|
2614
|
+
* ```
|
|
2615
|
+
*/ function parseApiError(error, context) {
|
|
2616
|
+
// If it's already a KitError, return it as-is
|
|
2617
|
+
if (error instanceof KitError) {
|
|
2618
|
+
return error;
|
|
2619
|
+
}
|
|
2620
|
+
const msg = getErrorMessage(error);
|
|
2621
|
+
const statusCode = extractHttpStatusCode(msg);
|
|
2622
|
+
const serviceName = context.service ?? 'Stablecoin Service';
|
|
2623
|
+
const operation = context.operation ?? 'API';
|
|
2624
|
+
const responseBody = extractResponseBody(error);
|
|
2625
|
+
// Rate limit errors (429)
|
|
2626
|
+
if (statusCode === 429 || /too many requests|rate limit exceeded/i.test(msg)) {
|
|
2627
|
+
return handleRateLimitError(serviceName, operation, error);
|
|
2628
|
+
}
|
|
2629
|
+
// HTTP 4xx Client Errors
|
|
2630
|
+
if (statusCode !== null && statusCode >= 400 && statusCode < 500) {
|
|
2631
|
+
return handleClientError(statusCode, serviceName, operation, error, msg, responseBody);
|
|
2632
|
+
}
|
|
2633
|
+
// HTTP 5xx Server Errors
|
|
2634
|
+
if (statusCode !== null && statusCode >= 500 && statusCode < 600) {
|
|
2635
|
+
return handleServerError(statusCode, serviceName, operation, error, responseBody);
|
|
2636
|
+
}
|
|
2637
|
+
// Timeout errors
|
|
2638
|
+
if (/timeout|timed out/i.test(msg)) {
|
|
2639
|
+
return handleTimeoutError(serviceName, operation, error);
|
|
2640
|
+
}
|
|
2641
|
+
// Network connectivity errors
|
|
2642
|
+
if (/connection (refused|failed)|network|ENOTFOUND|ECONNREFUSED|ETIMEDOUT/i.test(msg)) {
|
|
2643
|
+
return handleConnectionError(serviceName, operation, error);
|
|
2644
|
+
}
|
|
2645
|
+
// Fallback: Unknown error - use UNKNOWN_ERROR with fatal recoverability since we don't know what the error is
|
|
2646
|
+
return new KitError({
|
|
2647
|
+
...ServiceError.UNKNOWN_ERROR,
|
|
2648
|
+
recoverability: 'FATAL',
|
|
2649
|
+
message: `${serviceName} ${operation} failed: ${msg.length > 0 ? msg : 'Unknown error'}`,
|
|
2650
|
+
cause: {
|
|
2651
|
+
trace: error
|
|
2652
|
+
}
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
/**
|
|
2656
|
+
* Handles HTTP 4xx client errors and maps them to appropriate KitError instances.
|
|
2657
|
+
*
|
|
2658
|
+
* @param statusCode - The HTTP status code
|
|
2659
|
+
* @param serviceName - The name of the service
|
|
2660
|
+
* @param operation - The operation name
|
|
2661
|
+
* @param error - The raw error object
|
|
2662
|
+
* @param msg - The message extracted from the error
|
|
2663
|
+
* @param responseBody - The parsed JSON response body from the server, if available
|
|
2664
|
+
* @returns A KitError instance
|
|
2665
|
+
*/ function handleClientError(statusCode, serviceName, operation, error, msg, responseBody) {
|
|
2666
|
+
const detail = extractDetailFromBody(responseBody) ?? msg;
|
|
2667
|
+
switch(statusCode){
|
|
2668
|
+
// 401/403 - Authentication/Authorization
|
|
2669
|
+
case 401:
|
|
2670
|
+
case 403:
|
|
2671
|
+
return new KitError({
|
|
2672
|
+
...InputError.VALIDATION_FAILED,
|
|
2673
|
+
recoverability: 'FATAL',
|
|
2674
|
+
message: `${serviceName} ${operation} failed: Invalid or missing API key or authorization`,
|
|
2675
|
+
cause: {
|
|
2676
|
+
trace: error
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
// 404 - Not found - unsupported route OR stop-limit / slippage constraint not met
|
|
2680
|
+
case 404:
|
|
2681
|
+
if (isSlippageConstraintFailure(responseBody)) {
|
|
2682
|
+
return new KitError({
|
|
2683
|
+
...InputError.SLIPPAGE_CONSTRAINT_NOT_MET,
|
|
2684
|
+
recoverability: 'RETRYABLE',
|
|
2685
|
+
message: `${serviceName} ${operation} failed: ${detail}. ` + 'Try increasing slippageBps or adjusting stopLimit.',
|
|
2686
|
+
cause: {
|
|
2687
|
+
trace: error
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
}
|
|
2691
|
+
return new KitError({
|
|
2692
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
2693
|
+
recoverability: 'FATAL',
|
|
2694
|
+
message: `${serviceName} ${operation} failed: Route or resource not found. Details: ${detail}`,
|
|
2695
|
+
cause: {
|
|
2696
|
+
trace: error
|
|
2697
|
+
}
|
|
2698
|
+
});
|
|
2699
|
+
// 422 Unprocessable Entity
|
|
2700
|
+
// Proxy service is mapping 422 to INSUFFICIENT_SWAP_AMOUNT
|
|
2701
|
+
case 422:
|
|
2702
|
+
return new KitError({
|
|
2703
|
+
...InputError.INSUFFICIENT_SWAP_AMOUNT,
|
|
2704
|
+
recoverability: 'FATAL',
|
|
2705
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2706
|
+
cause: {
|
|
2707
|
+
trace: error
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
// 400 Bad Request - Invalid token, amount-out-of-range, or validation failed
|
|
2711
|
+
// Proxy maps 400 to UNSUPPORTED_TOKEN | AMOUNT_OUT_OF_RANGE | VALIDATION_FAILED
|
|
2712
|
+
case 400:
|
|
2713
|
+
if (responseBody?.code === ProxyErrorCode.INVALID_SWAP_AMOUNT) {
|
|
2714
|
+
const amountErr = extractAmountError(responseBody);
|
|
2715
|
+
return new KitError({
|
|
2716
|
+
...InputError.AMOUNT_OUT_OF_RANGE,
|
|
2717
|
+
recoverability: 'FATAL',
|
|
2718
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2719
|
+
cause: {
|
|
2720
|
+
trace: {
|
|
2721
|
+
rawError: error,
|
|
2722
|
+
minAmount: amountErr?.minAmount,
|
|
2723
|
+
maxAmount: amountErr?.maxAmount,
|
|
2724
|
+
token: amountErr?.token
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
}
|
|
2729
|
+
return new KitError({
|
|
2730
|
+
...InputError.VALIDATION_FAILED,
|
|
2731
|
+
recoverability: 'FATAL',
|
|
2732
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2733
|
+
cause: {
|
|
2734
|
+
trace: error
|
|
2735
|
+
}
|
|
2736
|
+
});
|
|
2737
|
+
default:
|
|
2738
|
+
// Other 4xx errors - treat as validation failures
|
|
2739
|
+
return new KitError({
|
|
2740
|
+
...InputError.VALIDATION_FAILED,
|
|
2741
|
+
recoverability: 'FATAL',
|
|
2742
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2743
|
+
cause: {
|
|
2744
|
+
trace: error
|
|
2745
|
+
}
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
/**
|
|
2750
|
+
* Pattern that matches proxy response body text indicating the 404 was
|
|
2751
|
+
* caused by a slippage / price constraint rather than a truly unsupported
|
|
2752
|
+
* route. Kept case-insensitive so future proxy wording changes are tolerated.
|
|
2753
|
+
*
|
|
2754
|
+
* @internal
|
|
2755
|
+
*/ const SLIPPAGE_BODY_PATTERN = /slippage|stop.?limit|price.?impact|minimum.?output|SLIPPAGE_CONSTRAINT_NOT_MET/i;
|
|
2756
|
+
/**
|
|
2757
|
+
* Determine whether a 404 was caused by an unmet slippage or price
|
|
2758
|
+
* constraint rather than a genuinely unsupported route.
|
|
2759
|
+
*
|
|
2760
|
+
* Detection relies on the proxy response body containing slippage-related
|
|
2761
|
+
* language or a structured reason code. This avoids false positives that
|
|
2762
|
+
* would occur if we guessed based on request parameters alone (a user
|
|
2763
|
+
* can set `slippageBps` and still hit a truly unsupported route).
|
|
2764
|
+
*
|
|
2765
|
+
* @param responseBody - The parsed JSON body returned by the proxy
|
|
2766
|
+
* @returns `true` when the 404 should be treated as a slippage constraint failure
|
|
2767
|
+
* @internal
|
|
2768
|
+
*/ function isSlippageConstraintFailure(responseBody) {
|
|
2769
|
+
if (responseBody === undefined) {
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
const textsToCheck = [
|
|
2773
|
+
responseBody.externalMessage,
|
|
2774
|
+
responseBody.message,
|
|
2775
|
+
extractDetailFromBody(responseBody)
|
|
2776
|
+
];
|
|
2777
|
+
return textsToCheck.some((t)=>typeof t === 'string' && SLIPPAGE_BODY_PATTERN.test(t));
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* Handles HTTP 5xx server errors and maps them to appropriate KitError instances.
|
|
2781
|
+
*
|
|
2782
|
+
* Recognizes proxy-specific structured codes in `responseBody.code` and routes
|
|
2783
|
+
* known conditions (e.g. {@link ProxyErrorCode.LOW_LIQUIDITY} on 503) to their
|
|
2784
|
+
* dedicated KitError. Falls back to a generic retryable `SERVICE_INTERNAL_ERROR`
|
|
2785
|
+
* when no specific code is present.
|
|
2786
|
+
*
|
|
2787
|
+
* @param statusCode - The HTTP status code
|
|
2788
|
+
* @param serviceName - The name of the service
|
|
2789
|
+
* @param operation - The operation name
|
|
2790
|
+
* @param error - The raw error object
|
|
2791
|
+
* @param responseBody - The parsed JSON response body from the server, if available
|
|
2792
|
+
* @returns A KitError instance
|
|
2793
|
+
*/ function handleServerError(statusCode, serviceName, operation, error, responseBody) {
|
|
2794
|
+
// 503 + 331018 = upstream liquidity insufficient (proxy)
|
|
2795
|
+
if (statusCode === 503 && responseBody?.code === ProxyErrorCode.LOW_LIQUIDITY) {
|
|
2796
|
+
const amountErr = extractAmountError(responseBody);
|
|
2797
|
+
const detail = extractDetailFromBody(responseBody) ?? getErrorMessage(error);
|
|
2798
|
+
return new KitError({
|
|
2799
|
+
...LiquidityError.INSUFFICIENT_LIQUIDITY,
|
|
2800
|
+
recoverability: 'RETRYABLE',
|
|
2801
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2802
|
+
cause: {
|
|
2803
|
+
trace: {
|
|
2804
|
+
rawError: error,
|
|
2805
|
+
minAmount: amountErr?.minAmount,
|
|
2806
|
+
maxAmount: amountErr?.maxAmount,
|
|
2807
|
+
token: amountErr?.token
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2812
|
+
return new KitError({
|
|
2813
|
+
...ServiceError.INTERNAL_ERROR,
|
|
2814
|
+
recoverability: 'RETRYABLE',
|
|
2815
|
+
message: `${serviceName} ${operation} failed: Server error (${statusCode.toString()})`,
|
|
2816
|
+
cause: {
|
|
2817
|
+
trace: error
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Handles network connection errors and maps them to appropriate KitError instances.
|
|
2823
|
+
*
|
|
2824
|
+
* @param serviceName - The name of the service
|
|
2825
|
+
* @param operation - The operation name
|
|
2826
|
+
* @param error - The raw error object
|
|
2827
|
+
* @returns A KitError instance
|
|
2828
|
+
*/ function handleConnectionError(serviceName, operation, error) {
|
|
2829
|
+
return new KitError({
|
|
2830
|
+
...NetworkError.CONNECTION_FAILED,
|
|
2831
|
+
recoverability: 'RETRYABLE',
|
|
2832
|
+
message: `${serviceName} ${operation} failed: Network connection error`,
|
|
2833
|
+
cause: {
|
|
2834
|
+
trace: error
|
|
2835
|
+
}
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Handles rate limit errors and maps them to appropriate KitError instances.
|
|
2840
|
+
*
|
|
2841
|
+
* @param serviceName - The name of the service
|
|
2842
|
+
* @param operation - The operation name
|
|
2843
|
+
* @param error - The raw error object
|
|
2844
|
+
* @returns A KitError instance
|
|
2845
|
+
*/ function handleRateLimitError(serviceName, operation, error) {
|
|
2846
|
+
return new KitError({
|
|
2847
|
+
...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
2848
|
+
recoverability: 'RETRYABLE',
|
|
2849
|
+
message: `${serviceName} ${operation} failed: Too many requests, please retry later`,
|
|
2850
|
+
cause: {
|
|
2851
|
+
trace: error
|
|
2852
|
+
}
|
|
2853
|
+
});
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Handles timeout errors and maps them to appropriate KitError instances.
|
|
2857
|
+
*
|
|
2858
|
+
* @param serviceName - The name of the service
|
|
2859
|
+
* @param operation - The operation name
|
|
2860
|
+
* @param error - The raw error object
|
|
2861
|
+
* @returns A KitError instance
|
|
2862
|
+
*/ function handleTimeoutError(serviceName, operation, error) {
|
|
2863
|
+
return new KitError({
|
|
2864
|
+
...NetworkError.TIMEOUT,
|
|
2865
|
+
recoverability: 'RETRYABLE',
|
|
2866
|
+
message: `${serviceName} ${operation} failed: Request timeout`,
|
|
2867
|
+
cause: {
|
|
2868
|
+
trace: error
|
|
2869
|
+
}
|
|
2870
|
+
});
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Type guard that narrows an unknown error to one carrying a non-null
|
|
2874
|
+
* object `responseBody` property (attached by `makeApiRequest`).
|
|
2875
|
+
*
|
|
2876
|
+
* @param error - The raw error from the HTTP layer
|
|
2877
|
+
* @returns `true` when `error.responseBody` is a non-null object
|
|
2878
|
+
* @internal
|
|
2879
|
+
*/ function hasResponseBody(error) {
|
|
2880
|
+
return typeof error === 'object' && error !== null && 'responseBody' in error && typeof error['responseBody'] === 'object' && error['responseBody'] !== null;
|
|
2881
|
+
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Extract the `responseBody` property that `makeApiRequest` attaches to
|
|
2884
|
+
* HTTP error instances when the server returns a JSON body.
|
|
2885
|
+
*
|
|
2886
|
+
* @param error - The raw error from the HTTP layer
|
|
2887
|
+
* @returns The parsed body cast to {@link ApiErrorResponseBody}, or undefined
|
|
2888
|
+
* @throws Never. Returns `undefined` when the error does not carry a valid
|
|
2889
|
+
* `responseBody`.
|
|
2890
|
+
* @internal
|
|
2891
|
+
*/ function extractResponseBody(error) {
|
|
2892
|
+
if (!hasResponseBody(error)) {
|
|
2893
|
+
return undefined;
|
|
2894
|
+
}
|
|
2895
|
+
return error.responseBody;
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Extract a field name from an {@link ApiFieldError}.
|
|
2899
|
+
*
|
|
2900
|
+
* The proxy service may provide the field name as a plain `field` string
|
|
2901
|
+
* or as a `path` array (e.g. `["tokenInChain"]`). This helper resolves
|
|
2902
|
+
* whichever is available, preferring `field` when both exist.
|
|
2903
|
+
*
|
|
2904
|
+
* @param entry - A single error entry from the `errors` array
|
|
2905
|
+
* @returns The field name, or `undefined` when neither is available
|
|
2906
|
+
* @internal
|
|
2907
|
+
*/ function extractFieldName(entry) {
|
|
2908
|
+
if (typeof entry.field === 'string' && entry.field.length > 0) {
|
|
2909
|
+
return entry.field;
|
|
2910
|
+
}
|
|
2911
|
+
if (Array.isArray(entry.path) && entry.path.length > 0) {
|
|
2912
|
+
const first = entry.path[0];
|
|
2913
|
+
if (typeof first === 'string' && first.length > 0) {
|
|
2914
|
+
return first;
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
return undefined;
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Join field-level error entries into a single human-readable string.
|
|
2921
|
+
*
|
|
2922
|
+
* Each entry is formatted as `"field: message"` when a field name is
|
|
2923
|
+
* available (via `field` or `path`), or just the message otherwise.
|
|
2924
|
+
* Entries without a usable message (including amount-bound entries that
|
|
2925
|
+
* only carry `minAmount`/`maxAmount`/`token`) are skipped.
|
|
2926
|
+
*
|
|
2927
|
+
* @param errors - The `errors` array from the response body
|
|
2928
|
+
* @returns A joined string, or `undefined` when no usable entries exist
|
|
2929
|
+
* @internal
|
|
2930
|
+
*/ function joinFieldErrors(errors) {
|
|
2931
|
+
const parts = errors.map((entry)=>{
|
|
2932
|
+
if (!isFieldEntry(entry)) {
|
|
2933
|
+
return undefined;
|
|
2934
|
+
}
|
|
2935
|
+
const fieldMsg = typeof entry.message === 'string' && entry.message.length > 0 ? entry.message : undefined;
|
|
2936
|
+
if (fieldMsg === undefined) {
|
|
2937
|
+
return undefined;
|
|
2938
|
+
}
|
|
2939
|
+
const fieldName = extractFieldName(entry);
|
|
2940
|
+
return fieldName === undefined ? fieldMsg : `${fieldName}: ${fieldMsg}`;
|
|
2941
|
+
}).filter((s)=>s !== undefined);
|
|
2942
|
+
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
2943
|
+
}
|
|
2944
|
+
/**
|
|
2945
|
+
* Narrow an {@link ApiErrorItem} to the field-error shape used for
|
|
2946
|
+
* validation messages. Amount-bound entries (which carry no `message`)
|
|
2947
|
+
* are excluded.
|
|
2948
|
+
*
|
|
2949
|
+
* @internal
|
|
2950
|
+
*/ function isFieldEntry(entry) {
|
|
2951
|
+
return 'message' in entry || 'field' in entry || 'path' in entry;
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Narrow an {@link ApiErrorItem} to the amount-bound shape emitted by the
|
|
2955
|
+
* proxy for {@link ProxyErrorCode.INVALID_SWAP_AMOUNT}.
|
|
2956
|
+
*
|
|
2957
|
+
* @internal
|
|
2958
|
+
*/ function isAmountEntry(entry) {
|
|
2959
|
+
return 'minAmount' in entry || 'maxAmount' in entry || 'token' in entry;
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Extract the first amount-bound entry from a response body, if any.
|
|
2963
|
+
*
|
|
2964
|
+
* @internal
|
|
2965
|
+
*/ function extractAmountError(body) {
|
|
2966
|
+
if (body === undefined || !Array.isArray(body.errors)) {
|
|
2967
|
+
return undefined;
|
|
2968
|
+
}
|
|
2969
|
+
return body.errors.find(isAmountEntry);
|
|
2970
|
+
}
|
|
2971
|
+
/**
|
|
2972
|
+
* Derive a human-readable detail string from an {@link ApiErrorResponseBody}.
|
|
2973
|
+
*
|
|
2974
|
+
* Resolution order:
|
|
2975
|
+
* 1. `body.externalMessage` -- the user-facing string the proxy intends
|
|
2976
|
+
* consumers to display (e.g. "No route found that satisfies the
|
|
2977
|
+
* requested stop limit"). Preferred when available.
|
|
2978
|
+
* 2. `body.message` **and** `body.errors` -- when both are present the
|
|
2979
|
+
* top-level message is combined with the field-level detail so
|
|
2980
|
+
* developers see the full picture
|
|
2981
|
+
* (e.g. `"Validation error: tokenInChain: Invalid input; amount: …"`).
|
|
2982
|
+
* 3. `body.message` alone -- used as-is.
|
|
2983
|
+
* 4. `body.errors` alone -- field-level entries joined with "; ".
|
|
2984
|
+
* 5. `undefined` -- caller should fall back to the raw HTTP status text.
|
|
2985
|
+
*
|
|
2986
|
+
* @param body - The parsed response body, may be undefined
|
|
2987
|
+
* @returns A detail string, or undefined when no useful info is available
|
|
2988
|
+
* @internal
|
|
2989
|
+
*/ function extractDetailFromBody(body) {
|
|
2990
|
+
if (body === undefined) {
|
|
2991
|
+
return undefined;
|
|
2992
|
+
}
|
|
2993
|
+
const externalMessage = typeof body.externalMessage === 'string' && body.externalMessage.length > 0 ? body.externalMessage : undefined;
|
|
2994
|
+
if (externalMessage !== undefined) {
|
|
2995
|
+
return externalMessage;
|
|
2996
|
+
}
|
|
2997
|
+
const topMessage = typeof body.message === 'string' && body.message.length > 0 ? body.message : undefined;
|
|
2998
|
+
const fieldDetail = Array.isArray(body.errors) && body.errors.length > 0 ? joinFieldErrors(body.errors) : undefined;
|
|
2999
|
+
if (topMessage !== undefined && fieldDetail !== undefined) {
|
|
3000
|
+
return `${topMessage}: ${fieldDetail}`;
|
|
3001
|
+
}
|
|
3002
|
+
return topMessage ?? fieldDetail;
|
|
3003
|
+
}
|
|
3004
|
+
/**
|
|
3005
|
+
* Extracts the HTTP status code from an error message.
|
|
3006
|
+
*
|
|
3007
|
+
* Attempts to parse HTTP status codes from common error message formats,
|
|
3008
|
+
* such as "HTTP 404" or "Status: 500".
|
|
3009
|
+
*
|
|
3010
|
+
* @param msg - The error message to extract from
|
|
3011
|
+
* @returns The extracted HTTP status code, or null if not found
|
|
3012
|
+
*
|
|
3013
|
+
* @example
|
|
3014
|
+
* ```typescript
|
|
3015
|
+
* const code = extractHttpStatusCode('HTTP 404')
|
|
3016
|
+
* // Returns: 404
|
|
3017
|
+
* ```
|
|
3018
|
+
*
|
|
3019
|
+
* @example
|
|
3020
|
+
* ```typescript
|
|
3021
|
+
* const code = extractHttpStatusCode('Status: 500 Internal Server Error')
|
|
3022
|
+
* // Returns: 500
|
|
3023
|
+
* ```
|
|
3024
|
+
*/ function extractHttpStatusCode(msg) {
|
|
3025
|
+
// Pattern: "HTTP 404" or "HTTP 404 - some message" or "Status: 404"
|
|
3026
|
+
const patterns = [
|
|
3027
|
+
/HTTP (\d{3})/i,
|
|
3028
|
+
/Status:\s*(\d{3})/i,
|
|
3029
|
+
/^(\d{3}) -/
|
|
3030
|
+
];
|
|
3031
|
+
for (const pattern of patterns){
|
|
3032
|
+
const match = pattern.exec(msg);
|
|
3033
|
+
const codeStr = match?.at(1);
|
|
3034
|
+
if (codeStr !== undefined) {
|
|
3035
|
+
const code = Number.parseInt(codeStr, 10);
|
|
3036
|
+
// Validate it's a valid HTTP status code
|
|
3037
|
+
if (code >= 100 && code < 600) {
|
|
3038
|
+
return code;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
return null;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
2466
3045
|
/**
|
|
2467
3046
|
* @packageDocumentation
|
|
2468
3047
|
* @module ChainDefinitions
|
|
@@ -2534,6 +3113,8 @@ class KitError extends Error {
|
|
|
2534
3113
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
2535
3114
|
Blockchain["Pharos"] = "Pharos";
|
|
2536
3115
|
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
3116
|
+
Blockchain["Plasma"] = "Plasma";
|
|
3117
|
+
Blockchain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
2537
3118
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
2538
3119
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
2539
3120
|
Blockchain["Plume"] = "Plume";
|
|
@@ -2603,6 +3184,7 @@ var BridgeChain;
|
|
|
2603
3184
|
BridgeChain["Morph"] = "Morph";
|
|
2604
3185
|
BridgeChain["Optimism"] = "Optimism";
|
|
2605
3186
|
BridgeChain["Pharos"] = "Pharos";
|
|
3187
|
+
BridgeChain["Plasma"] = "Plasma";
|
|
2606
3188
|
BridgeChain["Plume"] = "Plume";
|
|
2607
3189
|
BridgeChain["Polygon"] = "Polygon";
|
|
2608
3190
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -2629,6 +3211,7 @@ var BridgeChain;
|
|
|
2629
3211
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
2630
3212
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
2631
3213
|
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
3214
|
+
BridgeChain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
2632
3215
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
2633
3216
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
2634
3217
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -3075,17 +3658,56 @@ var EarnChain;
|
|
|
3075
3658
|
* This program handles minting operations for Gateway transactions
|
|
3076
3659
|
* on Solana devnet.
|
|
3077
3660
|
*/ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
|
|
3078
|
-
|
|
3079
3661
|
/**
|
|
3080
|
-
*
|
|
3081
|
-
*
|
|
3082
|
-
*
|
|
3083
|
-
*
|
|
3084
|
-
*
|
|
3085
|
-
*
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3662
|
+
* The `TokenMessengerWithFees` proxy contract address for EVM mainnet networks
|
|
3663
|
+
* (all chains except Edge).
|
|
3664
|
+
*
|
|
3665
|
+
* Deployed at a CREATE3-derived address; identical across all mainnet EVM
|
|
3666
|
+
* source chains. Present on any chain that supports the prepaid FORWARD path
|
|
3667
|
+
* via `depositForBurnWithHookAndFees`.
|
|
3668
|
+
*/ const TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET = '0x71f54F818671cD0D7ea140Da213e5C8b5C92a408';
|
|
3669
|
+
/**
|
|
3670
|
+
* The `TokenMessengerWithFees` proxy contract address for EVM testnet networks.
|
|
3671
|
+
*
|
|
3672
|
+
* Identical across all testnet EVM source chains. Present on any testnet chain
|
|
3673
|
+
* that supports the prepaid FORWARD path via `depositForBurnWithHookAndFees`.
|
|
3674
|
+
*/ const TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET = '0x8745D906D67C346E5eb1aEEED38Eb87F34DF0C0A';
|
|
3675
|
+
/**
|
|
3676
|
+
* The `DepositForHandler` proxy contract address for EVM mainnet networks.
|
|
3677
|
+
*
|
|
3678
|
+
* The handler the GenericExecutor calls on a fast-deposit destination chain to
|
|
3679
|
+
* run a cross-chain deposit into the GatewayWallet. Deployed at the same
|
|
3680
|
+
* address across all mainnet EVM destination chains.
|
|
3681
|
+
*/ const DEPOSIT_FOR_HANDLER_EVM_MAINNET = '0x16529813203f77E036576666336554a1210dce4D';
|
|
3682
|
+
/**
|
|
3683
|
+
* The `DepositForHandler` proxy contract address for EVM testnet networks.
|
|
3684
|
+
*
|
|
3685
|
+
* Identical across all testnet EVM destination chains.
|
|
3686
|
+
*/ const DEPOSIT_FOR_HANDLER_EVM_TESTNET = '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48';
|
|
3687
|
+
/**
|
|
3688
|
+
* The `GenericExecutor` proxy contract address for EVM mainnet networks.
|
|
3689
|
+
*
|
|
3690
|
+
* The GenericExecutor is the `mintRecipient` and `destinationCaller` on the
|
|
3691
|
+
* destination chain for the CCTP v2 prepaid FORWARD path. It receives the CCTP
|
|
3692
|
+
* mint and calls the `DepositForHandler` to complete the fast deposit.
|
|
3693
|
+
* Deployed at the same address across all mainnet EVM destination chains.
|
|
3694
|
+
*/ const GENERIC_EXECUTOR_EVM_MAINNET = '0xFa7be2f04F3Ad4ca969260729c6d45B5625984A7';
|
|
3695
|
+
/**
|
|
3696
|
+
* The `GenericExecutor` proxy contract address for EVM testnet networks.
|
|
3697
|
+
*
|
|
3698
|
+
* Identical across all testnet EVM destination chains.
|
|
3699
|
+
*/ const GENERIC_EXECUTOR_EVM_TESTNET = '0xEdC81040756AcCfF070c21D37b265b9D0b5Ba45e';
|
|
3700
|
+
|
|
3701
|
+
/**
|
|
3702
|
+
* Arc Testnet chain definition
|
|
3703
|
+
* @remarks
|
|
3704
|
+
* This represents the test network for the Arc blockchain,
|
|
3705
|
+
* Circle's EVM-compatible Layer-1 designed for stablecoin finance
|
|
3706
|
+
* and asset tokenization. Arc uses USDC as the native gas token and
|
|
3707
|
+
* features the Malachite Byzantine Fault Tolerant (BFT) consensus
|
|
3708
|
+
* engine for sub-second finality.
|
|
3709
|
+
*/ const ArcTestnet = defineChain({
|
|
3710
|
+
type: 'evm',
|
|
3089
3711
|
chain: Blockchain.Arc_Testnet,
|
|
3090
3712
|
name: 'Arc Testnet',
|
|
3091
3713
|
title: 'ArcTestnet',
|
|
@@ -3112,6 +3734,7 @@ var EarnChain;
|
|
|
3112
3734
|
v2: {
|
|
3113
3735
|
type: 'split',
|
|
3114
3736
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
3737
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3115
3738
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3116
3739
|
confirmations: 1,
|
|
3117
3740
|
fastConfirmations: 1
|
|
@@ -3132,9 +3755,8 @@ var EarnChain;
|
|
|
3132
3755
|
v1: {
|
|
3133
3756
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3134
3757
|
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
3758
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
|
|
3759
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
|
|
3138
3760
|
}
|
|
3139
3761
|
},
|
|
3140
3762
|
forwarderSupported: {
|
|
@@ -3179,6 +3801,7 @@ var EarnChain;
|
|
|
3179
3801
|
v2: {
|
|
3180
3802
|
type: 'split',
|
|
3181
3803
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
3804
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3182
3805
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3183
3806
|
confirmations: 65,
|
|
3184
3807
|
fastConfirmations: 1
|
|
@@ -3243,6 +3866,7 @@ var EarnChain;
|
|
|
3243
3866
|
v2: {
|
|
3244
3867
|
type: 'split',
|
|
3245
3868
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
3869
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3246
3870
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3247
3871
|
confirmations: 65,
|
|
3248
3872
|
fastConfirmations: 1
|
|
@@ -3307,6 +3931,7 @@ var EarnChain;
|
|
|
3307
3931
|
v2: {
|
|
3308
3932
|
type: 'split',
|
|
3309
3933
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
3934
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3310
3935
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3311
3936
|
confirmations: 1,
|
|
3312
3937
|
fastConfirmations: 1
|
|
@@ -3326,7 +3951,9 @@ var EarnChain;
|
|
|
3326
3951
|
contracts: {
|
|
3327
3952
|
v1: {
|
|
3328
3953
|
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
3329
|
-
minter: GATEWAY_MINTER_EVM_MAINNET
|
|
3954
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
3955
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
|
|
3956
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
|
|
3330
3957
|
}
|
|
3331
3958
|
},
|
|
3332
3959
|
forwarderSupported: {
|
|
@@ -3368,6 +3995,7 @@ var EarnChain;
|
|
|
3368
3995
|
v2: {
|
|
3369
3996
|
type: 'split',
|
|
3370
3997
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
3998
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3371
3999
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3372
4000
|
confirmations: 1,
|
|
3373
4001
|
fastConfirmations: 1
|
|
@@ -3389,7 +4017,9 @@ var EarnChain;
|
|
|
3389
4017
|
contracts: {
|
|
3390
4018
|
v1: {
|
|
3391
4019
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3392
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
4020
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
4021
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
|
|
4022
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
|
|
3393
4023
|
}
|
|
3394
4024
|
},
|
|
3395
4025
|
forwarderSupported: {
|
|
@@ -3435,6 +4065,7 @@ var EarnChain;
|
|
|
3435
4065
|
v2: {
|
|
3436
4066
|
type: 'split',
|
|
3437
4067
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4068
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3438
4069
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3439
4070
|
confirmations: 65,
|
|
3440
4071
|
fastConfirmations: 1
|
|
@@ -3499,6 +4130,7 @@ var EarnChain;
|
|
|
3499
4130
|
v2: {
|
|
3500
4131
|
type: 'split',
|
|
3501
4132
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4133
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3502
4134
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3503
4135
|
confirmations: 65,
|
|
3504
4136
|
fastConfirmations: 1
|
|
@@ -3609,6 +4241,7 @@ var EarnChain;
|
|
|
3609
4241
|
v2: {
|
|
3610
4242
|
type: 'split',
|
|
3611
4243
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4244
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3612
4245
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3613
4246
|
confirmations: 65,
|
|
3614
4247
|
fastConfirmations: 1
|
|
@@ -3653,6 +4286,7 @@ var EarnChain;
|
|
|
3653
4286
|
v2: {
|
|
3654
4287
|
type: 'split',
|
|
3655
4288
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4289
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3656
4290
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3657
4291
|
confirmations: 65,
|
|
3658
4292
|
fastConfirmations: 1
|
|
@@ -3698,6 +4332,7 @@ var EarnChain;
|
|
|
3698
4332
|
v2: {
|
|
3699
4333
|
type: 'split',
|
|
3700
4334
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4335
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3701
4336
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3702
4337
|
confirmations: 1,
|
|
3703
4338
|
fastConfirmations: 1
|
|
@@ -3743,6 +4378,7 @@ var EarnChain;
|
|
|
3743
4378
|
v2: {
|
|
3744
4379
|
type: 'split',
|
|
3745
4380
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4381
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3746
4382
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3747
4383
|
confirmations: 1,
|
|
3748
4384
|
fastConfirmations: 1
|
|
@@ -3788,6 +4424,7 @@ var EarnChain;
|
|
|
3788
4424
|
v2: {
|
|
3789
4425
|
type: 'split',
|
|
3790
4426
|
tokenMessenger: '0x98706A006bc632Df31CAdFCBD43F38887ce2ca5c',
|
|
4427
|
+
tokenMessengerWithFees: '0x3Ac96675F9a3E6922713e041645D82f3561d3686',
|
|
3791
4428
|
messageTransmitter: '0x5b61381Fc9e58E70EfC13a4A97516997019198ee',
|
|
3792
4429
|
confirmations: 65,
|
|
3793
4430
|
fastConfirmations: 1
|
|
@@ -3833,6 +4470,7 @@ var EarnChain;
|
|
|
3833
4470
|
v2: {
|
|
3834
4471
|
type: 'split',
|
|
3835
4472
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4473
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3836
4474
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3837
4475
|
confirmations: 65,
|
|
3838
4476
|
fastConfirmations: 1
|
|
@@ -3884,6 +4522,7 @@ var EarnChain;
|
|
|
3884
4522
|
v2: {
|
|
3885
4523
|
type: 'split',
|
|
3886
4524
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4525
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3887
4526
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3888
4527
|
confirmations: 65,
|
|
3889
4528
|
fastConfirmations: 2
|
|
@@ -3948,6 +4587,7 @@ var EarnChain;
|
|
|
3948
4587
|
v2: {
|
|
3949
4588
|
type: 'split',
|
|
3950
4589
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4590
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3951
4591
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3952
4592
|
confirmations: 65,
|
|
3953
4593
|
fastConfirmations: 2
|
|
@@ -4058,6 +4698,7 @@ var EarnChain;
|
|
|
4058
4698
|
v2: {
|
|
4059
4699
|
type: 'split',
|
|
4060
4700
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4701
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4061
4702
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4062
4703
|
confirmations: 1,
|
|
4063
4704
|
fastConfirmations: 1
|
|
@@ -4117,6 +4758,7 @@ var EarnChain;
|
|
|
4117
4758
|
v2: {
|
|
4118
4759
|
type: 'split',
|
|
4119
4760
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4761
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4120
4762
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4121
4763
|
confirmations: 1,
|
|
4122
4764
|
fastConfirmations: 1
|
|
@@ -4177,6 +4819,7 @@ var EarnChain;
|
|
|
4177
4819
|
v2: {
|
|
4178
4820
|
type: 'split',
|
|
4179
4821
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4822
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4180
4823
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4181
4824
|
confirmations: 1,
|
|
4182
4825
|
fastConfirmations: 1
|
|
@@ -4224,6 +4867,7 @@ var EarnChain;
|
|
|
4224
4867
|
v2: {
|
|
4225
4868
|
type: 'split',
|
|
4226
4869
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4870
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4227
4871
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4228
4872
|
confirmations: 1,
|
|
4229
4873
|
fastConfirmations: 1
|
|
@@ -4271,6 +4915,7 @@ var EarnChain;
|
|
|
4271
4915
|
v2: {
|
|
4272
4916
|
type: 'split',
|
|
4273
4917
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4918
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4274
4919
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4275
4920
|
confirmations: 65,
|
|
4276
4921
|
fastConfirmations: 1
|
|
@@ -4318,6 +4963,7 @@ var EarnChain;
|
|
|
4318
4963
|
v2: {
|
|
4319
4964
|
type: 'split',
|
|
4320
4965
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4966
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4321
4967
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4322
4968
|
confirmations: 65,
|
|
4323
4969
|
fastConfirmations: 1
|
|
@@ -4362,6 +5008,7 @@ var EarnChain;
|
|
|
4362
5008
|
v2: {
|
|
4363
5009
|
type: 'split',
|
|
4364
5010
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5011
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4365
5012
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4366
5013
|
confirmations: 1,
|
|
4367
5014
|
fastConfirmations: 1
|
|
@@ -4407,6 +5054,7 @@ var EarnChain;
|
|
|
4407
5054
|
v2: {
|
|
4408
5055
|
type: 'split',
|
|
4409
5056
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5057
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4410
5058
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
4411
5059
|
confirmations: 1,
|
|
4412
5060
|
fastConfirmations: 1
|
|
@@ -4453,6 +5101,7 @@ var EarnChain;
|
|
|
4453
5101
|
v2: {
|
|
4454
5102
|
type: 'split',
|
|
4455
5103
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5104
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4456
5105
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4457
5106
|
confirmations: 1,
|
|
4458
5107
|
fastConfirmations: 1
|
|
@@ -4500,6 +5149,7 @@ var EarnChain;
|
|
|
4500
5149
|
v2: {
|
|
4501
5150
|
type: 'split',
|
|
4502
5151
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5152
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4503
5153
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4504
5154
|
confirmations: 1,
|
|
4505
5155
|
fastConfirmations: 1
|
|
@@ -4545,6 +5195,7 @@ var EarnChain;
|
|
|
4545
5195
|
v2: {
|
|
4546
5196
|
type: 'split',
|
|
4547
5197
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5198
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4548
5199
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4549
5200
|
confirmations: 64,
|
|
4550
5201
|
fastConfirmations: 1
|
|
@@ -4590,6 +5241,7 @@ var EarnChain;
|
|
|
4590
5241
|
v2: {
|
|
4591
5242
|
type: 'split',
|
|
4592
5243
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5244
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4593
5245
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4594
5246
|
confirmations: 64,
|
|
4595
5247
|
fastConfirmations: 1
|
|
@@ -4766,6 +5418,7 @@ var EarnChain;
|
|
|
4766
5418
|
v2: {
|
|
4767
5419
|
type: 'split',
|
|
4768
5420
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5421
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4769
5422
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4770
5423
|
confirmations: 65,
|
|
4771
5424
|
fastConfirmations: 1
|
|
@@ -4830,6 +5483,7 @@ var EarnChain;
|
|
|
4830
5483
|
v2: {
|
|
4831
5484
|
type: 'split',
|
|
4832
5485
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5486
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4833
5487
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
4834
5488
|
confirmations: 65,
|
|
4835
5489
|
fastConfirmations: 1
|
|
@@ -4889,6 +5543,7 @@ var EarnChain;
|
|
|
4889
5543
|
v2: {
|
|
4890
5544
|
type: 'split',
|
|
4891
5545
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5546
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4892
5547
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4893
5548
|
confirmations: 1,
|
|
4894
5549
|
fastConfirmations: 1
|
|
@@ -4935,6 +5590,7 @@ var EarnChain;
|
|
|
4935
5590
|
v2: {
|
|
4936
5591
|
type: 'split',
|
|
4937
5592
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5593
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4938
5594
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4939
5595
|
confirmations: 1,
|
|
4940
5596
|
fastConfirmations: 1
|
|
@@ -4950,6 +5606,98 @@ var EarnChain;
|
|
|
4950
5606
|
}
|
|
4951
5607
|
});
|
|
4952
5608
|
|
|
5609
|
+
/**
|
|
5610
|
+
* Plasma Mainnet chain definition
|
|
5611
|
+
* @remarks
|
|
5612
|
+
* This represents the official production network for the Plasma blockchain.
|
|
5613
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
5614
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
5615
|
+
*/ const Plasma = defineChain({
|
|
5616
|
+
type: 'evm',
|
|
5617
|
+
chain: Blockchain.Plasma,
|
|
5618
|
+
name: 'Plasma',
|
|
5619
|
+
title: 'Plasma Mainnet',
|
|
5620
|
+
nativeCurrency: {
|
|
5621
|
+
name: 'Plasma',
|
|
5622
|
+
symbol: 'XPL',
|
|
5623
|
+
decimals: 18
|
|
5624
|
+
},
|
|
5625
|
+
chainId: 9745,
|
|
5626
|
+
isTestnet: false,
|
|
5627
|
+
explorerUrl: 'https://plasmascan.to/tx/{hash}',
|
|
5628
|
+
rpcEndpoints: [
|
|
5629
|
+
'https://rpc.plasma.to'
|
|
5630
|
+
],
|
|
5631
|
+
eurcAddress: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
5632
|
+
usdcAddress: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
5633
|
+
usdtAddress: null,
|
|
5634
|
+
cctp: {
|
|
5635
|
+
domain: 33,
|
|
5636
|
+
contracts: {
|
|
5637
|
+
v2: {
|
|
5638
|
+
type: 'split',
|
|
5639
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5640
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5641
|
+
confirmations: 3,
|
|
5642
|
+
fastConfirmations: 1
|
|
5643
|
+
}
|
|
5644
|
+
},
|
|
5645
|
+
forwarderSupported: {
|
|
5646
|
+
source: false,
|
|
5647
|
+
destination: false
|
|
5648
|
+
}
|
|
5649
|
+
},
|
|
5650
|
+
kitContracts: {
|
|
5651
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
5652
|
+
}
|
|
5653
|
+
});
|
|
5654
|
+
|
|
5655
|
+
/**
|
|
5656
|
+
* Plasma Testnet chain definition
|
|
5657
|
+
* @remarks
|
|
5658
|
+
* This represents the official test network for the Plasma blockchain.
|
|
5659
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
5660
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
5661
|
+
*/ const PlasmaTestnet = defineChain({
|
|
5662
|
+
type: 'evm',
|
|
5663
|
+
chain: Blockchain.Plasma_Testnet,
|
|
5664
|
+
name: 'Plasma Testnet',
|
|
5665
|
+
title: 'Plasma Testnet',
|
|
5666
|
+
nativeCurrency: {
|
|
5667
|
+
name: 'Plasma',
|
|
5668
|
+
symbol: 'XPL',
|
|
5669
|
+
decimals: 18
|
|
5670
|
+
},
|
|
5671
|
+
chainId: 9746,
|
|
5672
|
+
isTestnet: true,
|
|
5673
|
+
explorerUrl: 'https://testnet.plasmascan.to/tx/{hash}',
|
|
5674
|
+
rpcEndpoints: [
|
|
5675
|
+
'https://testnet-rpc.plasma.to'
|
|
5676
|
+
],
|
|
5677
|
+
eurcAddress: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330',
|
|
5678
|
+
usdcAddress: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
5679
|
+
usdtAddress: null,
|
|
5680
|
+
cctp: {
|
|
5681
|
+
domain: 33,
|
|
5682
|
+
contracts: {
|
|
5683
|
+
v2: {
|
|
5684
|
+
type: 'split',
|
|
5685
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5686
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5687
|
+
confirmations: 3,
|
|
5688
|
+
fastConfirmations: 1
|
|
5689
|
+
}
|
|
5690
|
+
},
|
|
5691
|
+
forwarderSupported: {
|
|
5692
|
+
source: false,
|
|
5693
|
+
destination: false
|
|
5694
|
+
}
|
|
5695
|
+
},
|
|
5696
|
+
kitContracts: {
|
|
5697
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
5698
|
+
}
|
|
5699
|
+
});
|
|
5700
|
+
|
|
4953
5701
|
/**
|
|
4954
5702
|
* Plume Mainnet chain definition
|
|
4955
5703
|
* @remarks
|
|
@@ -4981,6 +5729,7 @@ var EarnChain;
|
|
|
4981
5729
|
v2: {
|
|
4982
5730
|
type: 'split',
|
|
4983
5731
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5732
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4984
5733
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4985
5734
|
confirmations: 65,
|
|
4986
5735
|
fastConfirmations: 1
|
|
@@ -5027,6 +5776,7 @@ var EarnChain;
|
|
|
5027
5776
|
v2: {
|
|
5028
5777
|
type: 'split',
|
|
5029
5778
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5779
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5030
5780
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5031
5781
|
confirmations: 65,
|
|
5032
5782
|
fastConfirmations: 1
|
|
@@ -5128,6 +5878,7 @@ var EarnChain;
|
|
|
5128
5878
|
v2: {
|
|
5129
5879
|
type: 'split',
|
|
5130
5880
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5881
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5131
5882
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5132
5883
|
confirmations: 33,
|
|
5133
5884
|
fastConfirmations: 13
|
|
@@ -5147,7 +5898,9 @@ var EarnChain;
|
|
|
5147
5898
|
contracts: {
|
|
5148
5899
|
v1: {
|
|
5149
5900
|
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
5150
|
-
minter: GATEWAY_MINTER_EVM_MAINNET
|
|
5901
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
5902
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
|
|
5903
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
|
|
5151
5904
|
}
|
|
5152
5905
|
},
|
|
5153
5906
|
forwarderSupported: {
|
|
@@ -5193,6 +5946,7 @@ var EarnChain;
|
|
|
5193
5946
|
v2: {
|
|
5194
5947
|
type: 'split',
|
|
5195
5948
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5949
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5196
5950
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5197
5951
|
confirmations: 33,
|
|
5198
5952
|
fastConfirmations: 13
|
|
@@ -5211,7 +5965,9 @@ var EarnChain;
|
|
|
5211
5965
|
contracts: {
|
|
5212
5966
|
v1: {
|
|
5213
5967
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
5214
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
5968
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
5969
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
|
|
5970
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
|
|
5215
5971
|
}
|
|
5216
5972
|
},
|
|
5217
5973
|
forwarderSupported: {
|
|
@@ -5252,6 +6008,7 @@ var EarnChain;
|
|
|
5252
6008
|
v2: {
|
|
5253
6009
|
type: 'split',
|
|
5254
6010
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6011
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5255
6012
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5256
6013
|
confirmations: 1,
|
|
5257
6014
|
fastConfirmations: 1
|
|
@@ -5311,6 +6068,7 @@ var EarnChain;
|
|
|
5311
6068
|
v2: {
|
|
5312
6069
|
type: 'split',
|
|
5313
6070
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6071
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5314
6072
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5315
6073
|
confirmations: 1,
|
|
5316
6074
|
fastConfirmations: 1
|
|
@@ -5368,6 +6126,7 @@ var EarnChain;
|
|
|
5368
6126
|
v2: {
|
|
5369
6127
|
type: 'split',
|
|
5370
6128
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6129
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5371
6130
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5372
6131
|
confirmations: 1,
|
|
5373
6132
|
fastConfirmations: 1
|
|
@@ -5426,6 +6185,7 @@ var EarnChain;
|
|
|
5426
6185
|
v2: {
|
|
5427
6186
|
type: 'split',
|
|
5428
6187
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6188
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5429
6189
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5430
6190
|
confirmations: 1,
|
|
5431
6191
|
fastConfirmations: 1
|
|
@@ -5741,6 +6501,7 @@ var EarnChain;
|
|
|
5741
6501
|
v2: {
|
|
5742
6502
|
type: 'split',
|
|
5743
6503
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6504
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5744
6505
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5745
6506
|
confirmations: 65,
|
|
5746
6507
|
fastConfirmations: 1
|
|
@@ -5805,6 +6566,7 @@ var EarnChain;
|
|
|
5805
6566
|
v2: {
|
|
5806
6567
|
type: 'split',
|
|
5807
6568
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6569
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5808
6570
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5809
6571
|
confirmations: 65,
|
|
5810
6572
|
fastConfirmations: 1
|
|
@@ -5862,6 +6624,7 @@ var EarnChain;
|
|
|
5862
6624
|
v2: {
|
|
5863
6625
|
type: 'split',
|
|
5864
6626
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cF5d',
|
|
6627
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5865
6628
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5866
6629
|
confirmations: 65,
|
|
5867
6630
|
fastConfirmations: 1
|
|
@@ -5921,6 +6684,7 @@ var EarnChain;
|
|
|
5921
6684
|
v2: {
|
|
5922
6685
|
type: 'split',
|
|
5923
6686
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
6687
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5924
6688
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5925
6689
|
confirmations: 65,
|
|
5926
6690
|
fastConfirmations: 1
|
|
@@ -5981,6 +6745,7 @@ var EarnChain;
|
|
|
5981
6745
|
v2: {
|
|
5982
6746
|
type: 'split',
|
|
5983
6747
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6748
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5984
6749
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5985
6750
|
confirmations: 3,
|
|
5986
6751
|
fastConfirmations: 3
|
|
@@ -6026,6 +6791,7 @@ var EarnChain;
|
|
|
6026
6791
|
v2: {
|
|
6027
6792
|
type: 'split',
|
|
6028
6793
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6794
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6029
6795
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6030
6796
|
confirmations: 3,
|
|
6031
6797
|
fastConfirmations: 1
|
|
@@ -6236,6 +7002,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
6236
7002
|
OptimismSepolia: OptimismSepolia,
|
|
6237
7003
|
Pharos: Pharos,
|
|
6238
7004
|
PharosTestnet: PharosTestnet,
|
|
7005
|
+
Plasma: Plasma,
|
|
7006
|
+
PlasmaTestnet: PlasmaTestnet,
|
|
6239
7007
|
Plume: Plume,
|
|
6240
7008
|
PlumeTestnet: PlumeTestnet,
|
|
6241
7009
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -6285,6 +7053,71 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
6285
7053
|
return chain.cctp?.contracts.v2 !== undefined;
|
|
6286
7054
|
}
|
|
6287
7055
|
|
|
7056
|
+
/**
|
|
7057
|
+
* Chains the Fee Service accepts as a SOURCE for source-paid ("receive-exact")
|
|
7058
|
+
* CCTP v2 fees. An explicit allowlist is required because the
|
|
7059
|
+
* `TokenMessengerWithFees` wrapper address is now shared with the fast-deposit
|
|
7060
|
+
* forwarder path, so wrapper presence no longer implies source-fee support.
|
|
7061
|
+
* Keep in sync with backend coverage.
|
|
7062
|
+
*/ const SOURCE_FEE_SUPPORTED_ALLOWLIST = new Set([
|
|
7063
|
+
// Mainnet
|
|
7064
|
+
Blockchain.Ethereum,
|
|
7065
|
+
Blockchain.Base,
|
|
7066
|
+
Blockchain.Arbitrum,
|
|
7067
|
+
Blockchain.Unichain,
|
|
7068
|
+
Blockchain.Optimism,
|
|
7069
|
+
Blockchain.Codex,
|
|
7070
|
+
Blockchain.Ink,
|
|
7071
|
+
Blockchain.Plume,
|
|
7072
|
+
Blockchain.Linea,
|
|
7073
|
+
Blockchain.World_Chain,
|
|
7074
|
+
// Testnet counterparts
|
|
7075
|
+
Blockchain.Ethereum_Sepolia,
|
|
7076
|
+
Blockchain.Base_Sepolia,
|
|
7077
|
+
Blockchain.Arbitrum_Sepolia,
|
|
7078
|
+
Blockchain.Unichain_Sepolia,
|
|
7079
|
+
Blockchain.Optimism_Sepolia,
|
|
7080
|
+
Blockchain.Codex_Testnet,
|
|
7081
|
+
Blockchain.Ink_Testnet,
|
|
7082
|
+
Blockchain.Plume_Testnet,
|
|
7083
|
+
Blockchain.Linea_Sepolia,
|
|
7084
|
+
Blockchain.World_Chain_Sepolia
|
|
7085
|
+
]);
|
|
7086
|
+
/**
|
|
7087
|
+
* Check whether a chain supports source-paid ("receive-exact") CCTP v2 fees.
|
|
7088
|
+
*
|
|
7089
|
+
* A chain qualifies when it supports CCTP v2, carries a `TokenMessengerWithFees`
|
|
7090
|
+
* wrapper, and is in {@link SOURCE_FEE_SUPPORTED_ALLOWLIST}.
|
|
7091
|
+
*
|
|
7092
|
+
* @param chain - The chain definition to check. A nullish or non-object value
|
|
7093
|
+
* returns `false` rather than throwing, since consumers may call from plain
|
|
7094
|
+
* JavaScript.
|
|
7095
|
+
* @returns `true` when the chain supports CCTP v2, carries a
|
|
7096
|
+
* `TokenMessengerWithFees` wrapper, and is on the source-fee allowlist;
|
|
7097
|
+
* `false` otherwise.
|
|
7098
|
+
*
|
|
7099
|
+
* @example
|
|
7100
|
+
* ```typescript
|
|
7101
|
+
* import { Chains, hasSourceFeeSupport } from '@core/chains'
|
|
7102
|
+
*
|
|
7103
|
+
* hasSourceFeeSupport(Chains.Optimism) // true
|
|
7104
|
+
* hasSourceFeeSupport(Chains.Solana) // false
|
|
7105
|
+
* ```
|
|
7106
|
+
*/ function hasSourceFeeSupport(chain) {
|
|
7107
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for nullish/non-object input from plain JS
|
|
7108
|
+
if (chain === null || typeof chain !== 'object') {
|
|
7109
|
+
return false;
|
|
7110
|
+
}
|
|
7111
|
+
if (!isCCTPV2Supported(chain)) {
|
|
7112
|
+
return false;
|
|
7113
|
+
}
|
|
7114
|
+
const wrapper = chain.cctp.contracts.v2.tokenMessengerWithFees;
|
|
7115
|
+
if (typeof wrapper !== 'string' || wrapper.length === 0) {
|
|
7116
|
+
return false;
|
|
7117
|
+
}
|
|
7118
|
+
return SOURCE_FEE_SUPPORTED_ALLOWLIST.has(chain.chain);
|
|
7119
|
+
}
|
|
7120
|
+
|
|
6288
7121
|
/**
|
|
6289
7122
|
* Check if a chain supports a specific type of custom smart contract logic.
|
|
6290
7123
|
*
|
|
@@ -6335,6 +7168,73 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
6335
7168
|
return typeof contractAddress === 'string' && contractAddress.trim().length > 0;
|
|
6336
7169
|
}
|
|
6337
7170
|
|
|
7171
|
+
/**
|
|
7172
|
+
* Check whether a given chain supports Gateway protocol version 1.
|
|
7173
|
+
*
|
|
7174
|
+
* This type guard function examines a chain definition to determine if it has Gateway v1
|
|
7175
|
+
* contract configurations. It checks that the chain has a gateway object with a
|
|
7176
|
+
* `contracts.v1` entry present.
|
|
7177
|
+
*
|
|
7178
|
+
* @param chain - The chain definition to check for Gateway v1 support
|
|
7179
|
+
* @returns `true` if `chain.gateway?.contracts?.v1` is defined, `false` otherwise
|
|
7180
|
+
*
|
|
7181
|
+
* @example
|
|
7182
|
+
* ```typescript
|
|
7183
|
+
* import { isGatewayV1Supported, Base } from '@core/chains'
|
|
7184
|
+
*
|
|
7185
|
+
* if (isGatewayV1Supported(Base)) {
|
|
7186
|
+
* // TypeScript knows Base.gateway is defined here
|
|
7187
|
+
* console.log('Gateway domain:', Base.gateway.domain)
|
|
7188
|
+
* console.log('Wallet address:', Base.gateway.contracts.v1.wallet)
|
|
7189
|
+
* console.log('Minter address:', Base.gateway.contracts.v1.minter)
|
|
7190
|
+
* }
|
|
7191
|
+
* ```
|
|
7192
|
+
*
|
|
7193
|
+
* @example
|
|
7194
|
+
* ```typescript
|
|
7195
|
+
* // Usage in conditional flow
|
|
7196
|
+
* function getGatewayWalletAddress(chain: ChainDefinition): string | null {
|
|
7197
|
+
* if (isGatewayV1Supported(chain)) {
|
|
7198
|
+
* return chain.gateway.contracts.v1.wallet
|
|
7199
|
+
* }
|
|
7200
|
+
* return null
|
|
7201
|
+
* }
|
|
7202
|
+
* ```
|
|
7203
|
+
*/ function isGatewayV1Supported(chain) {
|
|
7204
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass a gateway object without contracts
|
|
7205
|
+
return chain.gateway?.contracts?.v1 !== undefined;
|
|
7206
|
+
}
|
|
7207
|
+
|
|
7208
|
+
/**
|
|
7209
|
+
* Temporary allowlist of chains permitted to initiate Gateway fast deposits.
|
|
7210
|
+
* Only chains keyed here are eligible; all others are rejected. Using the
|
|
7211
|
+
* {@link Blockchain} enum keeps entries type-safe and catches typos at compile
|
|
7212
|
+
* time. Remove this allowlist once roll-out is complete.
|
|
7213
|
+
*/ new Set([
|
|
7214
|
+
// Mainnet
|
|
7215
|
+
Blockchain.Ethereum,
|
|
7216
|
+
Blockchain.Base,
|
|
7217
|
+
Blockchain.Arbitrum,
|
|
7218
|
+
Blockchain.Unichain,
|
|
7219
|
+
Blockchain.Optimism,
|
|
7220
|
+
Blockchain.Codex,
|
|
7221
|
+
Blockchain.Ink,
|
|
7222
|
+
Blockchain.Plume,
|
|
7223
|
+
Blockchain.Linea,
|
|
7224
|
+
Blockchain.World_Chain,
|
|
7225
|
+
// Testnet counterparts
|
|
7226
|
+
Blockchain.Ethereum_Sepolia,
|
|
7227
|
+
Blockchain.Base_Sepolia,
|
|
7228
|
+
Blockchain.Arbitrum_Sepolia,
|
|
7229
|
+
Blockchain.Unichain_Sepolia,
|
|
7230
|
+
Blockchain.Optimism_Sepolia,
|
|
7231
|
+
Blockchain.Codex_Testnet,
|
|
7232
|
+
Blockchain.Ink_Testnet,
|
|
7233
|
+
Blockchain.Plume_Testnet,
|
|
7234
|
+
Blockchain.Linea_Sepolia,
|
|
7235
|
+
Blockchain.World_Chain_Sepolia
|
|
7236
|
+
]);
|
|
7237
|
+
|
|
6338
7238
|
/**
|
|
6339
7239
|
* Zod schema for validating Gateway v1 contract addresses.
|
|
6340
7240
|
*
|
|
@@ -6356,7 +7256,10 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
6356
7256
|
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
6357
7257
|
depositForHandler: z.string({
|
|
6358
7258
|
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
6359
|
-
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
7259
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional(),
|
|
7260
|
+
genericExecutor: z.string({
|
|
7261
|
+
invalid_type_error: 'Gateway genericExecutor address must be a string.'
|
|
7262
|
+
}).min(1, 'Gateway genericExecutor address cannot be empty.').optional()
|
|
6360
7263
|
}).strict() // Reject any additional properties not defined in the schema
|
|
6361
7264
|
;
|
|
6362
7265
|
/**
|
|
@@ -8303,6 +9206,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
8303
9206
|
[Blockchain.Noble]: 'uusdc',
|
|
8304
9207
|
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
8305
9208
|
[Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
9209
|
+
[Blockchain.Plasma]: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
8306
9210
|
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
8307
9211
|
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
8308
9212
|
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -8339,6 +9243,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
8339
9243
|
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
8340
9244
|
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
8341
9245
|
[Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
9246
|
+
[Blockchain.Plasma_Testnet]: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
8342
9247
|
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
8343
9248
|
[Blockchain.Polkadot_Westmint]: '31337',
|
|
8344
9249
|
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -8401,6 +9306,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
8401
9306
|
[Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
|
|
8402
9307
|
[Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
|
|
8403
9308
|
[Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
|
|
9309
|
+
[Blockchain.Plasma]: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
8404
9310
|
[Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
|
|
8405
9311
|
[Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
|
|
8406
9312
|
// =========================================================================
|
|
@@ -8409,7 +9315,8 @@ const swapTokenEnumSchema = z.enum([
|
|
|
8409
9315
|
[Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
|
|
8410
9316
|
[Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
|
|
8411
9317
|
[Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
|
|
8412
|
-
[Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
|
|
9318
|
+
[Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
|
|
9319
|
+
[Blockchain.Plasma_Testnet]: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330'
|
|
8413
9320
|
}
|
|
8414
9321
|
};
|
|
8415
9322
|
|
|
@@ -9223,6 +10130,9 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9223
10130
|
* The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
|
|
9224
10131
|
* This prefix is right-padded to 24 bytes in the final hookData.
|
|
9225
10132
|
*/ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
|
|
10133
|
+
/**
|
|
10134
|
+
* Maximum value of the 32-bit `version` field in a `cctp-forward` frame.
|
|
10135
|
+
*/ const MAX_UINT32 = 0xffffffff;
|
|
9226
10136
|
/**
|
|
9227
10137
|
* CCTP forwarding version number.
|
|
9228
10138
|
*
|
|
@@ -9233,6 +10143,16 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9233
10143
|
*
|
|
9234
10144
|
* Set to 0 when no additional Circle-reserved data is needed.
|
|
9235
10145
|
*/ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
|
|
10146
|
+
/**
|
|
10147
|
+
* Length in bytes of a Solana owner (ed25519 / PDA) public key.
|
|
10148
|
+
*/ const SOLANA_PUBKEY_LENGTH = 32;
|
|
10149
|
+
/**
|
|
10150
|
+
* Byte length of the Solana ATA-creation forwarding payload appended after the
|
|
10151
|
+
* `cctp-forward` frame: `createAta` (1 byte) + `ataOwner` (32 bytes).
|
|
10152
|
+
*
|
|
10153
|
+
* Circle's Orbit relayer decodes exactly this many bytes; see
|
|
10154
|
+
* {@link buildSolanaAtaForwardingHookData}.
|
|
10155
|
+
*/ const SOLANA_ATA_FORWARD_PAYLOAD_LENGTH = 1 + SOLANA_PUBKEY_LENGTH;
|
|
9236
10156
|
/**
|
|
9237
10157
|
* Build the hookData bytes for CCTP forwarding.
|
|
9238
10158
|
*
|
|
@@ -9289,6 +10209,518 @@ function buildForwardingHookData() {
|
|
|
9289
10209
|
cachedHookDataHex = '0x' + Array.from(buffer).map((b)=>b.toString(16).padStart(2, '0')).join('');
|
|
9290
10210
|
return cachedHookDataHex;
|
|
9291
10211
|
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Build a `cctp-forward` hookData frame with a versioned header and an appended
|
|
10214
|
+
* opaque payload.
|
|
10215
|
+
*
|
|
10216
|
+
* Produces the 32-byte `cctp-forward` header (24-byte ASCII magic + `uint32`
|
|
10217
|
+
* version + `uint32` `dataLength = 0`) followed by `payload` appended verbatim.
|
|
10218
|
+
* Unlike {@link buildForwardingHookData} — which emits only the fixed,
|
|
10219
|
+
* version-0 empty frame — this lets the caller set the frame `version` and
|
|
10220
|
+
* carry an inner payload such as a GenericExecutor blob.
|
|
10221
|
+
*
|
|
10222
|
+
* @remarks
|
|
10223
|
+
* The forwarder reads only the 32-byte header to decide that a hook is
|
|
10224
|
+
* forwardable, then strips it before the inner payload is consumed downstream
|
|
10225
|
+
* (e.g. the GenericExecutor `abi.decode`s the appended blob, never the frame).
|
|
10226
|
+
* `dataLength` stays `0` because the appended bytes are opaque to the forwarder
|
|
10227
|
+
* — it is not the payload's length.
|
|
10228
|
+
*
|
|
10229
|
+
* @param version - The `uint32` frame version (e.g. `1` for the GenericExecutor
|
|
10230
|
+
* FORWARD path). Must be an integer in `[0, 0xFFFFFFFF]`.
|
|
10231
|
+
* @param payload - A 0x-prefixed hex string appended after the header (e.g. the
|
|
10232
|
+
* bare GenericExecutor blob from `buildDepositForGenericExecutorPayload`).
|
|
10233
|
+
* @returns A 0x-prefixed hex string: the 32-byte frame followed by `payload`.
|
|
10234
|
+
* @throws {KitError} If `version` is out of `uint32` range or `payload` is not
|
|
10235
|
+
* a 0x-prefixed hex string (INPUT_VALIDATION_FAILED).
|
|
10236
|
+
*
|
|
10237
|
+
* @example
|
|
10238
|
+
* ```typescript
|
|
10239
|
+
* import {
|
|
10240
|
+
* buildDepositForGenericExecutorPayload,
|
|
10241
|
+
* buildForwardingHookDataWithPayload,
|
|
10242
|
+
* padAddressToBytes32,
|
|
10243
|
+
* } from '@core/utils'
|
|
10244
|
+
*
|
|
10245
|
+
* const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
|
|
10246
|
+
* dappId: 'gateway_deposit',
|
|
10247
|
+
* domainId: 26,
|
|
10248
|
+
* handler: '0xHandlerAddressOnDestinationChain',
|
|
10249
|
+
* params: [USDC_ARC, user, 0],
|
|
10250
|
+
* recoveryAddress: padAddressToBytes32(user),
|
|
10251
|
+
* })
|
|
10252
|
+
*
|
|
10253
|
+
* // Wrap for the prepaid Quote-API FORWARD path (frame version 1).
|
|
10254
|
+
* const hookData = buildForwardingHookDataWithPayload(1, geBlob)
|
|
10255
|
+
* ```
|
|
10256
|
+
*/ function buildForwardingHookDataWithPayload(version, payload) {
|
|
10257
|
+
if (!Number.isInteger(version) || version < 0 || version > MAX_UINT32) {
|
|
10258
|
+
throw createValidationFailedError$1('version', version, 'Expected an integer in the uint32 range [0, 4294967295]');
|
|
10259
|
+
}
|
|
10260
|
+
if (!isHexString(payload)) {
|
|
10261
|
+
throw createValidationFailedError$1('payload', payload, 'Expected a 0x-prefixed hex string');
|
|
10262
|
+
}
|
|
10263
|
+
// `isHexString` accepts odd-length hex (e.g. '0xabc'); catch it here so it
|
|
10264
|
+
// surfaces as a KitError rather than ethers' raw "hex data is odd-length"
|
|
10265
|
+
// from `concat` below.
|
|
10266
|
+
if (payload.length % 2 !== 0) {
|
|
10267
|
+
throw createValidationFailedError$1('payload', payload, 'Expected an even-length (whole-byte) hex string');
|
|
10268
|
+
}
|
|
10269
|
+
// 32-byte header: 24-byte magic + uint32 version + uint32 dataLength (0).
|
|
10270
|
+
const frame = new Uint8Array(32);
|
|
10271
|
+
frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
|
|
10272
|
+
const view = new DataView(frame.buffer);
|
|
10273
|
+
view.setUint32(24, version, false) // big-endian
|
|
10274
|
+
;
|
|
10275
|
+
view.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 0
|
|
10276
|
+
;
|
|
10277
|
+
return hexlify(concat([
|
|
10278
|
+
frame,
|
|
10279
|
+
payload
|
|
10280
|
+
]));
|
|
10281
|
+
}
|
|
10282
|
+
/**
|
|
10283
|
+
* Build a `cctp-forward` hookData frame that instructs Circle's Orbit relayer to
|
|
10284
|
+
* create the recipient's Associated Token Account (ATA) before minting on Solana.
|
|
10285
|
+
*
|
|
10286
|
+
* When an EVM→Solana bridge is forwarded, the destination mint targets the
|
|
10287
|
+
* recipient's USDC ATA — which does not exist for a fresh wallet. This frame
|
|
10288
|
+
* tells the relayer to prepend an idempotent `createAssociatedTokenAccount`
|
|
10289
|
+
* instruction (the relayer pays the rent) so the mint always succeeds.
|
|
10290
|
+
*
|
|
10291
|
+
* Unlike {@link buildForwardingHookData} (an empty version-0 frame), this emits
|
|
10292
|
+
* a version-0 frame whose 32-bit `dataLength` is set to
|
|
10293
|
+
* {@link SOLANA_ATA_FORWARD_PAYLOAD_LENGTH} (33), followed by the payload the
|
|
10294
|
+
* relayer decodes:
|
|
10295
|
+
* - Byte 0: `createAta` flag, always `1`
|
|
10296
|
+
* - Bytes 1-32: the recipient's 32-byte Solana owner public key (`ataOwner`)
|
|
10297
|
+
*
|
|
10298
|
+
* @remarks
|
|
10299
|
+
* `ataOwner` is the recipient's *wallet* public key, not the derived ATA. The
|
|
10300
|
+
* relayer re-derives the ATA from `ataOwner` and the USDC mint and requires it
|
|
10301
|
+
* to equal the burn's `mintRecipient`, so callers must pass the same owner used
|
|
10302
|
+
* to derive `mintRecipient`. The all-zero key is reserved as "absent owner" and
|
|
10303
|
+
* is rejected.
|
|
10304
|
+
*
|
|
10305
|
+
* @param ataOwner - The recipient's 32-byte Solana owner public key.
|
|
10306
|
+
* @returns A 0x-prefixed hex string: the 32-byte frame followed by the 33-byte
|
|
10307
|
+
* Solana ATA payload.
|
|
10308
|
+
* @throws {KitError} If `ataOwner` is not exactly 32 bytes, or is the all-zero
|
|
10309
|
+
* key (INPUT_VALIDATION_FAILED).
|
|
10310
|
+
*
|
|
10311
|
+
* @example
|
|
10312
|
+
* ```typescript
|
|
10313
|
+
* import { PublicKey } from '@solana/web3.js'
|
|
10314
|
+
* import { buildSolanaAtaForwardingHookData } from '@core/utils'
|
|
10315
|
+
*
|
|
10316
|
+
* const owner = new PublicKey('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM')
|
|
10317
|
+
* const hookData = buildSolanaAtaForwardingHookData(owner.toBytes())
|
|
10318
|
+
*
|
|
10319
|
+
* // Use with the forwarded depositForBurnWithHook action so the relayer
|
|
10320
|
+
* // creates the recipient ATA before minting.
|
|
10321
|
+
* await adapter.prepareAction('cctp.v2.depositForBurnWithHook', {
|
|
10322
|
+
* amount: BigInt('1000000'),
|
|
10323
|
+
* mintRecipient: '0x...',
|
|
10324
|
+
* maxFee: BigInt('50000'),
|
|
10325
|
+
* minFinalityThreshold: 1000,
|
|
10326
|
+
* fromChain: ethereum,
|
|
10327
|
+
* toChain: solana,
|
|
10328
|
+
* hookData,
|
|
10329
|
+
* })
|
|
10330
|
+
* ```
|
|
10331
|
+
*/ function buildSolanaAtaForwardingHookData(ataOwner) {
|
|
10332
|
+
if (!(ataOwner instanceof Uint8Array) || ataOwner.length !== SOLANA_PUBKEY_LENGTH) {
|
|
10333
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, `Expected a ${String(SOLANA_PUBKEY_LENGTH)}-byte Solana owner public key`);
|
|
10334
|
+
}
|
|
10335
|
+
if (ataOwner.every((byte)=>byte === 0)) {
|
|
10336
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, 'Expected a non-zero Solana owner public key; the all-zero key is reserved as "absent owner"');
|
|
10337
|
+
}
|
|
10338
|
+
// Inner payload: createAta(1) + ataOwner(32).
|
|
10339
|
+
const payload = new Uint8Array(SOLANA_ATA_FORWARD_PAYLOAD_LENGTH);
|
|
10340
|
+
payload[0] = 1 // createAta = true
|
|
10341
|
+
;
|
|
10342
|
+
payload.set(ataOwner, 1);
|
|
10343
|
+
// 32-byte header: 24-byte magic + uint32 version(0) + uint32 dataLength(33).
|
|
10344
|
+
// The relayer reads dataLength from the v0 frame to slice the inner payload,
|
|
10345
|
+
// so it MUST reflect the appended byte count (unlike the GenericExecutor path).
|
|
10346
|
+
const frame = new Uint8Array(32);
|
|
10347
|
+
frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
|
|
10348
|
+
const view = new DataView(frame.buffer);
|
|
10349
|
+
view.setUint32(24, CCTP_FORWARD_VERSION, false) // big-endian, 0
|
|
10350
|
+
;
|
|
10351
|
+
view.setUint32(28, SOLANA_ATA_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 33
|
|
10352
|
+
;
|
|
10353
|
+
return hexlify(concat([
|
|
10354
|
+
frame,
|
|
10355
|
+
payload
|
|
10356
|
+
]));
|
|
10357
|
+
}
|
|
10358
|
+
|
|
10359
|
+
/**
|
|
10360
|
+
* `version` field of the GenericExecutor hookData, in both the
|
|
10361
|
+
* `circle-generic-executor` header (`uint32`) and the ABI tuple (`uint8`). The
|
|
10362
|
+
* executor reverts if it is not `1`.
|
|
10363
|
+
*
|
|
10364
|
+
* @see https://circlepay.atlassian.net/wiki/spaces/~712020cd79585b52ea4353b4720c277fbfcca6/pages/3049291839
|
|
10365
|
+
*/ const GENERIC_EXECUTOR_HOOK_DATA_VERSION = 1;
|
|
10366
|
+
/**
|
|
10367
|
+
* ASCII magic that prefixes a GenericExecutor hookData blob.
|
|
10368
|
+
*
|
|
10369
|
+
* The executor auto-detects its payload by this string. It is left-aligned and
|
|
10370
|
+
* zero-padded to 24 bytes in the header, mirroring the `cctp-forward` frame
|
|
10371
|
+
* layout (magic + `uint32` version + `uint32` dataLength).
|
|
10372
|
+
*/ const GENERIC_EXECUTOR_MAGIC_PREFIX = 'circle-generic-executor';
|
|
10373
|
+
/**
|
|
10374
|
+
* Prepend the 32-byte `circle-generic-executor` header to the ABI tuple.
|
|
10375
|
+
*
|
|
10376
|
+
* Header layout (mirrors the `cctp-forward` frame): 24-byte zero-padded ASCII
|
|
10377
|
+
* magic + `uint32` version + `uint32` dataLength. Unlike the `cctp-forward`
|
|
10378
|
+
* frame (which the forwarder strips and so carries `dataLength = 0`), this
|
|
10379
|
+
* header's dataLength is the byte length of the ABI tuple that follows, since
|
|
10380
|
+
* the executor consumes both.
|
|
10381
|
+
*/ function prependGenericExecutorHeader(abiTuple) {
|
|
10382
|
+
const header = new Uint8Array(32);
|
|
10383
|
+
header.set(new TextEncoder().encode(GENERIC_EXECUTOR_MAGIC_PREFIX), 0);
|
|
10384
|
+
// Byte length of the ABI tuple that the header announces.
|
|
10385
|
+
const tupleByteLength = (abiTuple.length - 2) / 2;
|
|
10386
|
+
const view = new DataView(header.buffer);
|
|
10387
|
+
view.setUint32(24, GENERIC_EXECUTOR_HOOK_DATA_VERSION, false) // big-endian
|
|
10388
|
+
;
|
|
10389
|
+
view.setUint32(28, tupleByteLength, false) // big-endian
|
|
10390
|
+
;
|
|
10391
|
+
return hexlify(concat([
|
|
10392
|
+
header,
|
|
10393
|
+
abiTuple
|
|
10394
|
+
]));
|
|
10395
|
+
}
|
|
10396
|
+
/**
|
|
10397
|
+
* Left-pad a 20-byte EVM address to a 32-byte (`bytes32`) hex string.
|
|
10398
|
+
*
|
|
10399
|
+
* Mirrors viem's `pad(address, size 32)` and CCTP's `mintRecipient`
|
|
10400
|
+
* convention. Solana addresses are already 32 bytes and need no padding.
|
|
10401
|
+
*
|
|
10402
|
+
* @param address - A 0x-prefixed 20-byte EVM address.
|
|
10403
|
+
* @returns The address left-zero-padded to a 0x-prefixed 32-byte hex string.
|
|
10404
|
+
* @throws {KitError} If `address` is not a valid EVM address (INPUT_VALIDATION_FAILED).
|
|
10405
|
+
*
|
|
10406
|
+
* @example
|
|
10407
|
+
* ```typescript
|
|
10408
|
+
* import { padAddressToBytes32 } from '@core/utils'
|
|
10409
|
+
*
|
|
10410
|
+
* padAddressToBytes32('0x75275Aff2D01699D922f045b69ed291311209738')
|
|
10411
|
+
* // '0x00000000000000000000000075275aff2d01699d922f045b69ed291311209738'
|
|
10412
|
+
* ```
|
|
10413
|
+
*/ function padAddressToBytes32(address) {
|
|
10414
|
+
if (!isAddress(address)) {
|
|
10415
|
+
throw createValidationFailedError$1('address', address, 'Expected a valid 20-byte EVM address');
|
|
10416
|
+
}
|
|
10417
|
+
// bytes32 is raw bytes, not a checksummed address — emit lowercase so it
|
|
10418
|
+
// matches ABI-decoded output.
|
|
10419
|
+
return hexZeroPad(getAddress(address), 32).toLowerCase();
|
|
10420
|
+
}
|
|
10421
|
+
/**
|
|
10422
|
+
* Encode the bare GenericExecutor + DepositForHandler payload for a CCTP v2
|
|
10423
|
+
* fast-transfer deposit into a dApp.
|
|
10424
|
+
*
|
|
10425
|
+
* Builds the layers inner→outer:
|
|
10426
|
+
* 1. dApp calldata — the dApp function selector + ABI params, with each amount
|
|
10427
|
+
* slot left as the caller-supplied placeholder.
|
|
10428
|
+
* 2. handler calldata — `(depositContract, approvalTarget, depositCalldata, amountIndices)`
|
|
10429
|
+
* for `DepositForHandler`.
|
|
10430
|
+
* 3. ABI tuple — `(uint8 version, bytes32 recoveryAddress, address handler, bytes handlerCalldata)`.
|
|
10431
|
+
* No handler selector travels on the wire; the executor applies a fixed one.
|
|
10432
|
+
* 4. header — the 32-byte `circle-generic-executor` magic frame prepended to the
|
|
10433
|
+
* tuple, by which the executor auto-detects the payload.
|
|
10434
|
+
*
|
|
10435
|
+
* The returned `hookData` is the bare GenericExecutor blob (header ‖ tuple) — the
|
|
10436
|
+
* exact bytes the executor consumes. It carries no `cctp-forward` envelope. For
|
|
10437
|
+
* the prepaid Quote-API FORWARD path, wrap it with
|
|
10438
|
+
* {@link buildForwardingHookDataWithPayload}; the forwarder strips that envelope
|
|
10439
|
+
* before the executor reads the blob.
|
|
10440
|
+
*
|
|
10441
|
+
* @param options - See {@link BuildDepositForGenericExecutorPayloadParams}.
|
|
10442
|
+
* @returns The encoded {@link DepositForGenericExecutorPayload} layers.
|
|
10443
|
+
* @throws {KitError} If `options` is not an object, `dappId` is unknown,
|
|
10444
|
+
* `config.deployments` is not an array, neither `domainId` nor
|
|
10445
|
+
* `destinationChain` resolves a domain (or the two disagree), no deployment
|
|
10446
|
+
* exists for the resolved domain, the deposit contract cannot be resolved (a
|
|
10447
|
+
* built-in deployment supplied without a `destinationChain`), no `handler` is
|
|
10448
|
+
* supplied and it cannot be resolved from `destinationChain`,
|
|
10449
|
+
* `recoveryAddress`/`handler`/contract addresses are malformed,
|
|
10450
|
+
* `config.function` is not a valid Solidity function signature, `params`
|
|
10451
|
+
* length does not match the dApp signature, `params` values fail ABI encoding
|
|
10452
|
+
* (type mismatch), `config.dynamicAmountIndices` is not an array, or an amount
|
|
10453
|
+
* index is out of range (all INPUT_VALIDATION_FAILED).
|
|
10454
|
+
*
|
|
10455
|
+
* @example Encode a 1-click cross-chain Circle Gateway deposit
|
|
10456
|
+
* ```typescript
|
|
10457
|
+
* import { buildDepositForGenericExecutorPayload, padAddressToBytes32 } from '@core/utils'
|
|
10458
|
+
* import { ArcTestnet } from '@core/chains'
|
|
10459
|
+
*
|
|
10460
|
+
* const user = '0x75275Aff2D01699D922f045b69ed291311209738'
|
|
10461
|
+
* const usdcArc = '0x3600000000000000000000000000000000000000'
|
|
10462
|
+
* const { hookData } = buildDepositForGenericExecutorPayload({
|
|
10463
|
+
* dappId: 'gateway_deposit',
|
|
10464
|
+
* destinationChain: ArcTestnet, // resolves the GatewayWallet + DepositForHandler
|
|
10465
|
+
* // depositFor(address token, address depositor, uint256 value), amount idx [2]
|
|
10466
|
+
* params: [usdcArc, user, 0],
|
|
10467
|
+
* recoveryAddress: padAddressToBytes32(user),
|
|
10468
|
+
* })
|
|
10469
|
+
*
|
|
10470
|
+
* // Pass hookData straight into the CCTP v2 fast transfer.
|
|
10471
|
+
* console.log(hookData)
|
|
10472
|
+
* ```
|
|
10473
|
+
*/ function buildDepositForGenericExecutorPayload(options) {
|
|
10474
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for plain-JS callers
|
|
10475
|
+
if (options === null || typeof options !== 'object') {
|
|
10476
|
+
throw createValidationFailedError$1('options', options, 'Expected an options object');
|
|
10477
|
+
}
|
|
10478
|
+
const { dappId, params: dappParams } = options;
|
|
10479
|
+
const registry = options.config ?? DAPP_CONFIG;
|
|
10480
|
+
// Look up the dApp before resolving the deposit contract or handler, so an
|
|
10481
|
+
// unknown dApp reports the actionable "Unknown dApp" error rather than an
|
|
10482
|
+
// unresolved-handler error.
|
|
10483
|
+
const config = registry[dappId];
|
|
10484
|
+
if (config === undefined) {
|
|
10485
|
+
throw createValidationFailedError$1('dappId', dappId, `Unknown dApp. Known dApps: ${Object.keys(registry).join(', ')}`);
|
|
10486
|
+
}
|
|
10487
|
+
if (!Array.isArray(config.deployments)) {
|
|
10488
|
+
throw createValidationFailedError$1('deployments', config.deployments, `Expected an array of deployments for dApp '${dappId}'`);
|
|
10489
|
+
}
|
|
10490
|
+
// Array.isArray narrows to `any[]`; re-assert the concrete type.
|
|
10491
|
+
const deployments = config.deployments;
|
|
10492
|
+
// Destination CCTP domain: taken from `destinationChain` when supplied (the
|
|
10493
|
+
// chain is the (network, domain) key), else the explicit `domainId`.
|
|
10494
|
+
const domainId = resolveDomainId(options);
|
|
10495
|
+
const deployment = deployments.find((d)=>d.domainId === domainId);
|
|
10496
|
+
if (deployment === undefined) {
|
|
10497
|
+
throw createValidationFailedError$1('domainId', domainId, `No '${dappId}' deployment for domain ${String(domainId)}`);
|
|
10498
|
+
}
|
|
10499
|
+
if (!Array.isArray(dappParams)) {
|
|
10500
|
+
throw createValidationFailedError$1('params', dappParams, 'Expected an array of ABI-ordered parameters');
|
|
10501
|
+
}
|
|
10502
|
+
if (!isHexString(options.recoveryAddress, 32)) {
|
|
10503
|
+
throw createValidationFailedError$1('recoveryAddress', options.recoveryAddress, 'Expected a 0x-prefixed 32-byte (bytes32) hex string');
|
|
10504
|
+
}
|
|
10505
|
+
// Deposit contract: the deployment's own address, or — for the built-in
|
|
10506
|
+
// gateway_deposit deployment, which carries none — the destination chain's
|
|
10507
|
+
// GatewayWallet, so the address lives only in @core/chains.
|
|
10508
|
+
const resolvedDepositContract = resolveDepositContract(deployment, options, dappId);
|
|
10509
|
+
// Handler: an explicit `handler` always wins; otherwise resolve the
|
|
10510
|
+
// DepositForHandler from `destinationChain`. The chain is the (network, domain)
|
|
10511
|
+
// key, so a shared CCTP domain (Arc is 26 on both testnet and mainnet) can
|
|
10512
|
+
// never resolve the wrong network's handler and strand funds.
|
|
10513
|
+
const handler = resolveDepositForHandler(options, domainId);
|
|
10514
|
+
if (!isAddress(handler)) {
|
|
10515
|
+
throw createValidationFailedError$1('handler', handler, 'Expected a valid EVM address');
|
|
10516
|
+
}
|
|
10517
|
+
const depositContract = assertAddress(resolvedDepositContract, 'depositContract');
|
|
10518
|
+
const approvalTarget = assertAddress(deployment.approvalTarget ?? resolvedDepositContract, 'approvalTarget');
|
|
10519
|
+
// 1. dApp calldata: selector + ABI-encoded params (amount slots stay as placeholders).
|
|
10520
|
+
let dappInterface;
|
|
10521
|
+
try {
|
|
10522
|
+
dappInterface = new Interface([
|
|
10523
|
+
`function ${config.function}`
|
|
10524
|
+
]);
|
|
10525
|
+
} catch {
|
|
10526
|
+
throw createValidationFailedError$1('function', config.function, 'Expected a valid Solidity function signature');
|
|
10527
|
+
}
|
|
10528
|
+
const rawFragment = dappInterface.fragments[0];
|
|
10529
|
+
/* v8 ignore start -- defensive: guards against unexpected library behavior */ if (rawFragment === undefined || rawFragment.type !== 'function') {
|
|
10530
|
+
throw createValidationFailedError$1('function', config.function, 'Expected a valid Solidity function signature');
|
|
10531
|
+
}
|
|
10532
|
+
/* v8 ignore stop */ const fragment = rawFragment;
|
|
10533
|
+
if (dappParams.length !== fragment.inputs.length) {
|
|
10534
|
+
throw createValidationFailedError$1('params', dappParams, `'${config.function}' expects ${String(fragment.inputs.length)} params, got ${String(dappParams.length)}`);
|
|
10535
|
+
}
|
|
10536
|
+
let depositCalldata;
|
|
10537
|
+
try {
|
|
10538
|
+
depositCalldata = dappInterface.encodeFunctionData(fragment, dappParams);
|
|
10539
|
+
} catch {
|
|
10540
|
+
throw createValidationFailedError$1('params', dappParams, 'ABI encoding failed — check that each param matches the expected Solidity type');
|
|
10541
|
+
}
|
|
10542
|
+
if (!Array.isArray(config.dynamicAmountIndices)) {
|
|
10543
|
+
throw createValidationFailedError$1('dynamicAmountIndices', config.dynamicAmountIndices, `Expected an array of amount indices for dApp '${dappId}'`);
|
|
10544
|
+
}
|
|
10545
|
+
// Array.isArray narrows to `any[]`; re-assert the concrete type.
|
|
10546
|
+
const dynamicAmountIndices = config.dynamicAmountIndices;
|
|
10547
|
+
// Amount byte offsets in depositCalldata: 4 (selector) + paramIndex * 32.
|
|
10548
|
+
const amountIndices = dynamicAmountIndices.map((paramIndex)=>{
|
|
10549
|
+
if (!Number.isInteger(paramIndex) || paramIndex < 0 || paramIndex >= fragment.inputs.length) {
|
|
10550
|
+
throw createValidationFailedError$1('dynamicAmountIndices', paramIndex, `Index out of range for '${config.function}' (${String(fragment.inputs.length)} params)`);
|
|
10551
|
+
}
|
|
10552
|
+
return BigInt(4 + paramIndex * 32);
|
|
10553
|
+
});
|
|
10554
|
+
// 2. Handler layer.
|
|
10555
|
+
const handlerCalldata = defaultAbiCoder.encode([
|
|
10556
|
+
'address',
|
|
10557
|
+
'address',
|
|
10558
|
+
'bytes',
|
|
10559
|
+
'uint256[]'
|
|
10560
|
+
], [
|
|
10561
|
+
depositContract,
|
|
10562
|
+
approvalTarget,
|
|
10563
|
+
depositCalldata,
|
|
10564
|
+
amountIndices
|
|
10565
|
+
]);
|
|
10566
|
+
// 3. Executor ABI tuple. No handler selector travels on the wire — the
|
|
10567
|
+
// executor applies a fixed selector internally.
|
|
10568
|
+
const executorTuple = defaultAbiCoder.encode([
|
|
10569
|
+
'uint8',
|
|
10570
|
+
'bytes32',
|
|
10571
|
+
'address',
|
|
10572
|
+
'bytes'
|
|
10573
|
+
], [
|
|
10574
|
+
GENERIC_EXECUTOR_HOOK_DATA_VERSION,
|
|
10575
|
+
options.recoveryAddress,
|
|
10576
|
+
getAddress(handler),
|
|
10577
|
+
handlerCalldata
|
|
10578
|
+
]);
|
|
10579
|
+
// 4. Prepend the circle-generic-executor magic header; this is the final
|
|
10580
|
+
// bare GE blob the executor consumes.
|
|
10581
|
+
const hookData = prependGenericExecutorHeader(executorTuple);
|
|
10582
|
+
return {
|
|
10583
|
+
hookData,
|
|
10584
|
+
handlerCalldata,
|
|
10585
|
+
depositCalldata,
|
|
10586
|
+
amountIndices,
|
|
10587
|
+
depositContract,
|
|
10588
|
+
approvalTarget
|
|
10589
|
+
};
|
|
10590
|
+
}
|
|
10591
|
+
/**
|
|
10592
|
+
* Config key of the built-in Circle Gateway deposit dApp. Its deposit contract
|
|
10593
|
+
* is the destination chain's GatewayWallet, resolved from `destinationChain`
|
|
10594
|
+
* (not a hardcoded address), so this is the only dApp whose deployment may omit
|
|
10595
|
+
* `depositContract`.
|
|
10596
|
+
*/ const GATEWAY_DEPOSIT_DAPP_ID = 'gateway_deposit';
|
|
10597
|
+
/**
|
|
10598
|
+
* Built-in dApp registry. Adding a new `depositFor`-style dApp is a config entry
|
|
10599
|
+
* here (or via {@link BuildDepositForGenericExecutorPayloadParams.config}).
|
|
10600
|
+
*
|
|
10601
|
+
* @remarks
|
|
10602
|
+
* Only dApps with confirmed deployment addresses and active callers are included.
|
|
10603
|
+
* The built-in `gateway_deposit` entry omits `depositContract` — it is resolved
|
|
10604
|
+
* from the destination chain's GatewayWallet (`@core/chains`) rather than
|
|
10605
|
+
* duplicated here. Pass a custom registry via `config` for unlisted dApps.
|
|
10606
|
+
*/ const DAPP_CONFIG = {
|
|
10607
|
+
// Circle Gateway: depositFor(address token, address depositor, uint256 value)
|
|
10608
|
+
[GATEWAY_DEPOSIT_DAPP_ID]: {
|
|
10609
|
+
function: 'depositFor(address,address,uint256)',
|
|
10610
|
+
dynamicAmountIndices: [
|
|
10611
|
+
2
|
|
10612
|
+
],
|
|
10613
|
+
deployments: [
|
|
10614
|
+
// Arc Testnet (CCTP domain 26). The deposit contract is the chain's
|
|
10615
|
+
// GatewayWallet, resolved from `destinationChain` (@core/chains) rather
|
|
10616
|
+
// than duplicated here.
|
|
10617
|
+
{
|
|
10618
|
+
domainId: 26
|
|
10619
|
+
}
|
|
10620
|
+
]
|
|
10621
|
+
}
|
|
10622
|
+
};
|
|
10623
|
+
/**
|
|
10624
|
+
* Resolve the destination CCTP domain for an encode request.
|
|
10625
|
+
*
|
|
10626
|
+
* Prefers {@link BuildDepositForGenericExecutorPayloadParams.destinationChain}
|
|
10627
|
+
* (`chain.cctp.domain`) — the chain is the (network, domain) key. Falls back to
|
|
10628
|
+
* an explicit `domainId`. When both are supplied they must agree.
|
|
10629
|
+
*
|
|
10630
|
+
* @param options - The encode request.
|
|
10631
|
+
* @returns The destination CCTP domain.
|
|
10632
|
+
* @throws {KitError} If no domain is available, or `domainId` disagrees with
|
|
10633
|
+
* `destinationChain` (INPUT_VALIDATION_FAILED).
|
|
10634
|
+
* @internal
|
|
10635
|
+
*/ function resolveDomainId(options) {
|
|
10636
|
+
const chain = options.destinationChain;
|
|
10637
|
+
if (chain !== undefined) {
|
|
10638
|
+
const chainDomain = chain.cctp?.domain;
|
|
10639
|
+
if (chainDomain !== undefined) {
|
|
10640
|
+
if (options.domainId !== undefined && options.domainId !== chainDomain) {
|
|
10641
|
+
throw createValidationFailedError$1('domainId', options.domainId, `does not match destinationChain '${chain.name}' CCTP domain ` + String(chainDomain));
|
|
10642
|
+
}
|
|
10643
|
+
return chainDomain;
|
|
10644
|
+
}
|
|
10645
|
+
}
|
|
10646
|
+
if (options.domainId !== undefined) {
|
|
10647
|
+
return options.domainId;
|
|
10648
|
+
}
|
|
10649
|
+
throw createValidationFailedError$1('domainId', options.domainId, "Provide 'domainId', or a 'destinationChain' with a CCTP domain");
|
|
10650
|
+
}
|
|
10651
|
+
/**
|
|
10652
|
+
* Resolve the deposit contract the handler calls.
|
|
10653
|
+
*
|
|
10654
|
+
* Uses the deployment's own `depositContract` when present. Only the built-in
|
|
10655
|
+
* {@link GATEWAY_DEPOSIT_DAPP_ID} may omit it: its deposit contract is the
|
|
10656
|
+
* destination chain's Gateway v1 wallet, resolved from
|
|
10657
|
+
* {@link BuildDepositForGenericExecutorPayloadParams.destinationChain} so the
|
|
10658
|
+
* address is owned once in `@core/chains`. Any other dApp that omits
|
|
10659
|
+
* `depositContract` is a config error and fails here rather than silently
|
|
10660
|
+
* targeting the GatewayWallet.
|
|
10661
|
+
*
|
|
10662
|
+
* @param deployment - The resolved dApp deployment.
|
|
10663
|
+
* @param options - The encode request.
|
|
10664
|
+
* @param dappId - The dApp key, checked against {@link GATEWAY_DEPOSIT_DAPP_ID}.
|
|
10665
|
+
* @returns The deposit contract address (unvalidated; the caller checks it).
|
|
10666
|
+
* @throws {KitError} If a non-`gateway_deposit` deployment omits
|
|
10667
|
+
* `depositContract`, or if `gateway_deposit` has no Gateway v1
|
|
10668
|
+
* `destinationChain` to resolve one (INPUT_VALIDATION_FAILED).
|
|
10669
|
+
* @internal
|
|
10670
|
+
*/ function resolveDepositContract(deployment, options, dappId) {
|
|
10671
|
+
if (deployment.depositContract !== undefined) {
|
|
10672
|
+
return deployment.depositContract;
|
|
10673
|
+
}
|
|
10674
|
+
// Only gateway_deposit may omit its address (it targets the chain's
|
|
10675
|
+
// GatewayWallet). Any other addressless deployment is a config mistake and
|
|
10676
|
+
// must fail rather than silently resolve to the GatewayWallet.
|
|
10677
|
+
if (dappId !== GATEWAY_DEPOSIT_DAPP_ID) {
|
|
10678
|
+
throw createValidationFailedError$1('depositContract', dappId, `dApp '${dappId}' must declare a 'depositContract'; only the built-in ` + `'${GATEWAY_DEPOSIT_DAPP_ID}' resolves its address from the ` + "destination chain's GatewayWallet");
|
|
10679
|
+
}
|
|
10680
|
+
const chain = options.destinationChain;
|
|
10681
|
+
if (chain === undefined || !isGatewayV1Supported(chain)) {
|
|
10682
|
+
throw createValidationFailedError$1('depositContract', dappId, `'${GATEWAY_DEPOSIT_DAPP_ID}' needs a 'destinationChain' with Gateway v1 ` + 'support to resolve its GatewayWallet, or an explicit deployment address');
|
|
10683
|
+
}
|
|
10684
|
+
return chain.gateway.contracts.v1.wallet;
|
|
10685
|
+
}
|
|
10686
|
+
/**
|
|
10687
|
+
* Resolve the `DepositForHandler` address for an encode request.
|
|
10688
|
+
*
|
|
10689
|
+
* An explicit `options.handler` always wins. Otherwise the handler is resolved
|
|
10690
|
+
* from {@link BuildDepositForGenericExecutorPayloadParams.destinationChain}
|
|
10691
|
+
* (`chain.gateway.contracts.v1.depositForHandler`). The chain is the (network,
|
|
10692
|
+
* domain) key, so a CCTP domain shared across a chain's testnet and mainnet
|
|
10693
|
+
* cannot resolve the wrong network's handler; a missing chain or an unregistered
|
|
10694
|
+
* handler throws rather than guessing.
|
|
10695
|
+
*
|
|
10696
|
+
* @param options - The encode request.
|
|
10697
|
+
* @param domainId - The resolved destination domain, reported in the error.
|
|
10698
|
+
* @returns The resolved handler address (unvalidated; the caller checks it).
|
|
10699
|
+
* @throws {KitError} If `handler` is omitted and cannot be resolved
|
|
10700
|
+
* (INPUT_VALIDATION_FAILED).
|
|
10701
|
+
* @internal
|
|
10702
|
+
*/ function resolveDepositForHandler(options, domainId) {
|
|
10703
|
+
if (options.handler !== undefined) {
|
|
10704
|
+
return options.handler;
|
|
10705
|
+
}
|
|
10706
|
+
const chain = options.destinationChain;
|
|
10707
|
+
if (chain === undefined) {
|
|
10708
|
+
throw createValidationFailedError$1('handler', domainId, "No 'handler' supplied; pass 'handler' explicitly, or a " + "'destinationChain' whose Gateway config registers a DepositForHandler");
|
|
10709
|
+
}
|
|
10710
|
+
const registered = isGatewayV1Supported(chain) ? chain.gateway.contracts.v1.depositForHandler : undefined;
|
|
10711
|
+
if (registered === undefined) {
|
|
10712
|
+
throw createValidationFailedError$1('handler', domainId, `No DepositForHandler registered for domain ${String(domainId)} on ` + `chain '${chain.name}'; pass 'handler' explicitly`);
|
|
10713
|
+
}
|
|
10714
|
+
return registered;
|
|
10715
|
+
}
|
|
10716
|
+
/**
|
|
10717
|
+
* Validate and checksum an EVM address, throwing a consistent validation error.
|
|
10718
|
+
*/ function assertAddress(address, field) {
|
|
10719
|
+
if (!isAddress(address)) {
|
|
10720
|
+
throw createValidationFailedError$1(field, address, 'Expected a valid EVM address');
|
|
10721
|
+
}
|
|
10722
|
+
return getAddress(address);
|
|
10723
|
+
}
|
|
9292
10724
|
|
|
9293
10725
|
/**
|
|
9294
10726
|
* Configuration for {@link retryAsync}.
|
|
@@ -9378,7 +10810,7 @@ function resolveOptions(options) {
|
|
|
9378
10810
|
* allowlisted {@link ClientLogPayload} fields (and the allowlisted
|
|
9379
10811
|
* sub-fields of `errorDetails` / `clientContext`) are copied across.
|
|
9380
10812
|
* A regressing upstream mapper — or a plain-JS caller that bypasses the
|
|
9381
|
-
* type — therefore cannot exfiltrate stray properties (secrets,
|
|
10813
|
+
* type — therefore cannot exfiltrate stray properties (secrets,
|
|
9382
10814
|
* raw error stacks) through the analytics channel. Optional fields are
|
|
9383
10815
|
* only included when present so the serialised shape matches the
|
|
9384
10816
|
* server's strict schema.
|
|
@@ -9401,6 +10833,9 @@ function resolveOptions(options) {
|
|
|
9401
10833
|
if (payload.destinationChain !== undefined) safe['destinationChain'] = payload.destinationChain;
|
|
9402
10834
|
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
9403
10835
|
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
10836
|
+
if (payload.amountIn !== undefined) safe['amountIn'] = payload.amountIn;
|
|
10837
|
+
if (payload.durationMs !== undefined) safe['durationMs'] = payload.durationMs;
|
|
10838
|
+
if (payload.sourceAddress !== undefined) safe['sourceAddress'] = payload.sourceAddress;
|
|
9404
10839
|
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
9405
10840
|
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
9406
10841
|
if (payload.errorDetails !== undefined) {
|
|
@@ -9597,22 +11032,36 @@ function resolveOptions(options) {
|
|
|
9597
11032
|
}
|
|
9598
11033
|
|
|
9599
11034
|
/**
|
|
9600
|
-
*
|
|
9601
|
-
*
|
|
11035
|
+
* Emit a stable console warning when building or emitting a telemetry payload
|
|
11036
|
+
* throws — for example, a buggy `TelemetryContextResolver`, a regression in
|
|
9602
11037
|
* `extractErrorDetails`, or a synchronous failure inside `emitAnalyticsLog`
|
|
9603
|
-
* before it could swallow the error itself.
|
|
9604
|
-
*
|
|
9605
|
-
*
|
|
11038
|
+
* before it could swallow the error itself. Uses a stable prefix so the
|
|
11039
|
+
* drop is discoverable via grep. Never re-throws: the caller's original
|
|
11040
|
+
* operation error must always win.
|
|
9606
11041
|
*
|
|
9607
11042
|
* @internal
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
11043
|
+
*
|
|
11044
|
+
* @param eventType - The telemetry event type that was being emitted.
|
|
11045
|
+
* @param cause - The error or value that caused the drop.
|
|
11046
|
+
*
|
|
11047
|
+
* @example
|
|
11048
|
+
* ```typescript
|
|
11049
|
+
* import { warnTelemetryDrop } from '@core/utils'
|
|
11050
|
+
*
|
|
11051
|
+
* try {
|
|
11052
|
+
* void emitAnalyticsLog(payload)
|
|
11053
|
+
* } catch (err) {
|
|
11054
|
+
* warnTelemetryDrop('my_event', err)
|
|
11055
|
+
* }
|
|
11056
|
+
* ```
|
|
11057
|
+
*/ function warnTelemetryDrop(eventType, cause) {
|
|
11058
|
+
try {
|
|
11059
|
+
// Pass `cause` as the second console.warn argument rather than
|
|
11060
|
+
// string-coercing it. `String(err)` (and `err.message` alone)
|
|
11061
|
+
// discards the stack trace, nested `cause`, and any custom Error
|
|
11062
|
+
// properties — exactly the context an on-call needs when a
|
|
11063
|
+
// resolver-closure regression triggers this path.
|
|
11064
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
9616
11065
|
} catch {
|
|
9617
11066
|
// console.warn itself throwing is the user's environment; nothing more we
|
|
9618
11067
|
// can do without risking the original operation error.
|
|
@@ -9644,6 +11093,9 @@ function resolveOptions(options) {
|
|
|
9644
11093
|
...context?.tokenOut != null && {
|
|
9645
11094
|
tokenOut: context.tokenOut
|
|
9646
11095
|
},
|
|
11096
|
+
...context?.amountIn != null && {
|
|
11097
|
+
amountIn: context.amountIn
|
|
11098
|
+
},
|
|
9647
11099
|
...context?.txHash != null && {
|
|
9648
11100
|
txHash: context.txHash
|
|
9649
11101
|
},
|
|
@@ -9745,7 +11197,7 @@ function resolveOptions(options) {
|
|
|
9745
11197
|
const stepEntry = stepEventMap.find(([name])=>name === failedStep?.name);
|
|
9746
11198
|
// `failedStep.errorMessage` is intentionally **not** copied into the payload.
|
|
9747
11199
|
// Provider messages are unbounded and frequently contain operator data
|
|
9748
|
-
// (
|
|
11200
|
+
// (signatures, partial intent payloads, raw RPC responses). The
|
|
9749
11201
|
// step name plus the surrounding context fields already identify *which*
|
|
9750
11202
|
// phase failed; the *why* is left to the corresponding step-level logs that
|
|
9751
11203
|
// the provider emits separately. The thrown-error path (`extractErrorDetails`
|
|
@@ -9762,7 +11214,7 @@ function resolveOptions(options) {
|
|
|
9762
11214
|
}
|
|
9763
11215
|
|
|
9764
11216
|
var name$2 = "@circle-fin/bridge-kit";
|
|
9765
|
-
var version$3 = "1.
|
|
11217
|
+
var version$3 = "1.14.1";
|
|
9766
11218
|
var pkg$3 = {
|
|
9767
11219
|
name: name$2,
|
|
9768
11220
|
version: version$3};
|
|
@@ -10143,7 +11595,7 @@ const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
|
|
|
10143
11595
|
* const result = evmAddressSchema.safeParse(validAddress)
|
|
10144
11596
|
* console.log(result.success) // true
|
|
10145
11597
|
* ```
|
|
10146
|
-
*/ const evmAddressSchema = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
11598
|
+
*/ const evmAddressSchema$1 = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
10147
11599
|
/**
|
|
10148
11600
|
* Schema for validating transaction hashes.
|
|
10149
11601
|
*
|
|
@@ -10832,6 +12284,10 @@ var TransferSpeed;
|
|
|
10832
12284
|
token: z.literal('USDC').optional(),
|
|
10833
12285
|
config: z.object({
|
|
10834
12286
|
transferSpeed: z.nativeEnum(TransferSpeed).optional(),
|
|
12287
|
+
feePayment: z.enum([
|
|
12288
|
+
'source',
|
|
12289
|
+
'destination'
|
|
12290
|
+
]).optional(),
|
|
10835
12291
|
maxFee: z.string().min(1, 'Required').pipe(createDecimalStringValidator({
|
|
10836
12292
|
allowZero: true,
|
|
10837
12293
|
regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
|
|
@@ -10839,7 +12295,8 @@ var TransferSpeed;
|
|
|
10839
12295
|
maxDecimals: 6
|
|
10840
12296
|
})(z.string())).optional(),
|
|
10841
12297
|
customFee: customFeeSchema.optional()
|
|
10842
|
-
}).optional()
|
|
12298
|
+
}).optional(),
|
|
12299
|
+
quote: z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex').optional()
|
|
10843
12300
|
});
|
|
10844
12301
|
|
|
10845
12302
|
/**
|
|
@@ -11451,7 +12908,7 @@ var TransferSpeed;
|
|
|
11451
12908
|
* })
|
|
11452
12909
|
* ```
|
|
11453
12910
|
*/ function createLogger(options, stream) {
|
|
11454
|
-
const { redact, ...pinoOptions } = {};
|
|
12911
|
+
const { redact, ...pinoOptions } = options ?? {};
|
|
11455
12912
|
// Build redaction config
|
|
11456
12913
|
const redactConfig = buildRedactConfig(redact);
|
|
11457
12914
|
// Build final pino options, only include redact if defined
|
|
@@ -12014,16 +13471,73 @@ var TransferSpeed;
|
|
|
12014
13471
|
};
|
|
12015
13472
|
}
|
|
12016
13473
|
|
|
13474
|
+
/**
|
|
13475
|
+
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
13476
|
+
*
|
|
13477
|
+
* Constructs the appropriate action payload and dispatches it to any registered
|
|
13478
|
+
* event listeners. Handles type-safe dispatching for different step types.
|
|
13479
|
+
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
13480
|
+
*
|
|
13481
|
+
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
13482
|
+
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
13483
|
+
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
13484
|
+
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
13485
|
+
*
|
|
13486
|
+
* @example
|
|
13487
|
+
* ```typescript
|
|
13488
|
+
* const step: BridgeStep = {
|
|
13489
|
+
* name: 'burn',
|
|
13490
|
+
* state: 'success',
|
|
13491
|
+
* txHash: '0xabc...',
|
|
13492
|
+
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
13493
|
+
* data: { ... }
|
|
13494
|
+
* }
|
|
13495
|
+
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
13496
|
+
* ```
|
|
13497
|
+
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
13498
|
+
if (!provider.actionDispatcher) {
|
|
13499
|
+
return;
|
|
13500
|
+
}
|
|
13501
|
+
// Extract traceId from invocation context if provided
|
|
13502
|
+
const traceId = invocation?.traceId;
|
|
13503
|
+
const actionValues = {
|
|
13504
|
+
protocol: 'cctp',
|
|
13505
|
+
version: 'v2',
|
|
13506
|
+
...traceId !== undefined && {
|
|
13507
|
+
traceId
|
|
13508
|
+
},
|
|
13509
|
+
values: step
|
|
13510
|
+
};
|
|
13511
|
+
switch(name){
|
|
13512
|
+
case 'approve':
|
|
13513
|
+
case 'burn':
|
|
13514
|
+
case 'mint':
|
|
13515
|
+
provider.actionDispatcher.dispatch(name, {
|
|
13516
|
+
...actionValues,
|
|
13517
|
+
method: name
|
|
13518
|
+
});
|
|
13519
|
+
break;
|
|
13520
|
+
case 'fetchAttestation':
|
|
13521
|
+
case 'reAttest':
|
|
13522
|
+
provider.actionDispatcher.dispatch(name, {
|
|
13523
|
+
...actionValues,
|
|
13524
|
+
method: name,
|
|
13525
|
+
values: step
|
|
13526
|
+
});
|
|
13527
|
+
break;
|
|
13528
|
+
}
|
|
13529
|
+
}
|
|
13530
|
+
|
|
12017
13531
|
/**
|
|
12018
13532
|
* Base URL for Circle's IRIS API (mainnet/production).
|
|
12019
13533
|
*
|
|
12020
13534
|
* The IRIS API provides attestation services for CCTP cross-chain transfers.
|
|
12021
|
-
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
13535
|
+
*/ const IRIS_API_BASE_URL$1 = 'https://iris-api.circle.com';
|
|
12022
13536
|
/**
|
|
12023
13537
|
* Base URL for Circle's IRIS API (testnet/sandbox).
|
|
12024
13538
|
*
|
|
12025
13539
|
* Used for development and testing on testnet chains.
|
|
12026
|
-
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
13540
|
+
*/ const IRIS_API_SANDBOX_BASE_URL$1 = 'https://iris-api-sandbox.circle.com';
|
|
12027
13541
|
|
|
12028
13542
|
/**
|
|
12029
13543
|
* Type guard to validate the API response structure.
|
|
@@ -12066,7 +13580,7 @@ const isFastBurnFeeResponse = (data)=>{
|
|
|
12066
13580
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
12067
13581
|
* @returns The complete API URL
|
|
12068
13582
|
*/ function buildFastBurnFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
12069
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
13583
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
12070
13584
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}`;
|
|
12071
13585
|
}
|
|
12072
13586
|
const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
@@ -12277,6 +13791,77 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12277
13791
|
}
|
|
12278
13792
|
};
|
|
12279
13793
|
|
|
13794
|
+
/**
|
|
13795
|
+
* Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
|
|
13796
|
+
* tailored to the destination chain.
|
|
13797
|
+
*
|
|
13798
|
+
* For EVM destinations the recipient already holds ERC-20 USDC directly, so the
|
|
13799
|
+
* empty version-0 `cctp-forward` frame is sufficient. For Solana destinations
|
|
13800
|
+
* USDC is held in an Associated Token Account (ATA) that may not exist for a
|
|
13801
|
+
* fresh wallet, so this emits a frame carrying `createAta` + `ataOwner` that
|
|
13802
|
+
* instructs the relayer to create the recipient ATA (idempotently, at the
|
|
13803
|
+
* relayer's expense) before minting.
|
|
13804
|
+
*
|
|
13805
|
+
* `@solana/web3.js` is imported lazily so EVM-only consumers never load Solana
|
|
13806
|
+
* code, mirroring {@link getMintRecipientAccount}.
|
|
13807
|
+
*
|
|
13808
|
+
* @param chainType - The destination blockchain type ('evm' or 'solana').
|
|
13809
|
+
* @param ownerAddress - The recipient's wallet address on the destination chain
|
|
13810
|
+
* (base58 for Solana). Must be the same owner used to derive `mintRecipient`.
|
|
13811
|
+
* @returns A 0x-prefixed hookData hex string for the forwarded burn.
|
|
13812
|
+
* @throws {KitError} If `chainType` is neither 'evm' nor 'solana', if
|
|
13813
|
+
* `@solana/web3.js` cannot be loaded, or if `ownerAddress` is not a valid
|
|
13814
|
+
* Solana public key (all FATAL).
|
|
13815
|
+
*
|
|
13816
|
+
* @example
|
|
13817
|
+
* ```typescript
|
|
13818
|
+
* import { getForwarderHookData } from './getForwarderHookData'
|
|
13819
|
+
*
|
|
13820
|
+
* // EVM: empty forwarding frame
|
|
13821
|
+
* const evmHook = await getForwarderHookData('evm', '0x742d35Cc...')
|
|
13822
|
+
*
|
|
13823
|
+
* // Solana: frame instructing the relayer to create the recipient ATA
|
|
13824
|
+
* const solanaHook = await getForwarderHookData(
|
|
13825
|
+
* 'solana',
|
|
13826
|
+
* '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
13827
|
+
* )
|
|
13828
|
+
* ```
|
|
13829
|
+
*/ const getForwarderHookData = async (/** The destination blockchain type - determines the hookData shape */ chainType, /** The recipient's wallet address (hex for EVM, base58 for Solana) */ ownerAddress)=>{
|
|
13830
|
+
if (chainType === 'evm') {
|
|
13831
|
+
// EVM: the recipient holds USDC directly; no ATA setup is needed.
|
|
13832
|
+
return buildForwardingHookData();
|
|
13833
|
+
}
|
|
13834
|
+
// Fail closed: only EVM and Solana forwarding destinations are supported.
|
|
13835
|
+
// Without this guard any future non-EVM chain type would silently fall
|
|
13836
|
+
// through to the Solana path and mis-encode hookData on a money-movement path.
|
|
13837
|
+
if (chainType !== 'solana') {
|
|
13838
|
+
throw new KitError({
|
|
13839
|
+
...InputError.VALIDATION_FAILED,
|
|
13840
|
+
recoverability: 'FATAL',
|
|
13841
|
+
message: `Forwarded burns are not supported for destination chain type "${chainType}"`
|
|
13842
|
+
});
|
|
13843
|
+
}
|
|
13844
|
+
// Solana: encode the owner so the relayer creates the recipient ATA.
|
|
13845
|
+
// Resolve @solana/web3.js lazily so EVM-only consumers never load Solana code.
|
|
13846
|
+
const { PublicKey } = await import('@solana/web3.js').catch(()=>{
|
|
13847
|
+
throw new KitError({
|
|
13848
|
+
...InputError.VALIDATION_FAILED,
|
|
13849
|
+
recoverability: 'FATAL',
|
|
13850
|
+
message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js'
|
|
13851
|
+
});
|
|
13852
|
+
});
|
|
13853
|
+
try {
|
|
13854
|
+
const owner = new PublicKey(ownerAddress);
|
|
13855
|
+
return buildSolanaAtaForwardingHookData(owner.toBytes());
|
|
13856
|
+
} catch (error) {
|
|
13857
|
+
throw new KitError({
|
|
13858
|
+
...InputError.INVALID_ADDRESS,
|
|
13859
|
+
recoverability: 'FATAL',
|
|
13860
|
+
message: `Failed to build Solana forwarder hookData for recipient "${ownerAddress}": ${error instanceof Error ? error.message : String(error)}`
|
|
13861
|
+
});
|
|
13862
|
+
}
|
|
13863
|
+
};
|
|
13864
|
+
|
|
12280
13865
|
/**
|
|
12281
13866
|
* Validates and converts a fee value to bigint.
|
|
12282
13867
|
*
|
|
@@ -12379,7 +13964,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12379
13964
|
|
|
12380
13965
|
/**
|
|
12381
13966
|
* The zero address, denoting a native-currency fee in a signed quote.
|
|
12382
|
-
*/ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
13967
|
+
*/ const ZERO_ADDRESS$1 = '0x0000000000000000000000000000000000000000';
|
|
12383
13968
|
/**
|
|
12384
13969
|
* Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
|
|
12385
13970
|
*
|
|
@@ -12420,7 +14005,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12420
14005
|
if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
|
|
12421
14006
|
throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
|
|
12422
14007
|
}
|
|
12423
|
-
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS;
|
|
14008
|
+
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$1;
|
|
12424
14009
|
const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
|
|
12425
14010
|
if (isNativeFee) {
|
|
12426
14011
|
return {
|
|
@@ -12558,7 +14143,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12558
14143
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
12559
14144
|
* @returns The complete API URL with forward=true query parameter
|
|
12560
14145
|
*/ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
12561
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
14146
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
12562
14147
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true`;
|
|
12563
14148
|
}
|
|
12564
14149
|
/**
|
|
@@ -12677,6 +14262,8 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
|
|
|
12677
14262
|
;
|
|
12678
14263
|
const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
|
|
12679
14264
|
;
|
|
14265
|
+
/** Gas units consumed by `depositForBurnWithHookAndFees` on an EVM chain (prepaid-FORWARD path). */ const DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM = 626_584n // avg of the last 10 txns
|
|
14266
|
+
;
|
|
12680
14267
|
// Gas FLOORS, not ceilings — kept separate from the fee-estimate averages
|
|
12681
14268
|
// above. `executePreparedChainRequest` submits
|
|
12682
14269
|
// max(estimate * buffer, floor), so a chain whose real cost exceeds the floor
|
|
@@ -13619,7 +15206,7 @@ function hasPendingState(analysis, result) {
|
|
|
13619
15206
|
* // => 'https://iris-api-sandbox.circle.com/v2/messages/1?transactionHash=0xdef...'
|
|
13620
15207
|
* ```
|
|
13621
15208
|
*/ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet)=>{
|
|
13622
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
15209
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
13623
15210
|
const url = new URL(`${baseUrl}/v2/messages/${String(sourceDomainId)}`);
|
|
13624
15211
|
url.searchParams.set('transactionHash', transactionHash);
|
|
13625
15212
|
return url.toString();
|
|
@@ -13787,7 +15374,7 @@ function hasPendingState(analysis, result) {
|
|
|
13787
15374
|
* // => 'https://iris-api-sandbox.circle.com/v2/reattest/0xabc'
|
|
13788
15375
|
* ```
|
|
13789
15376
|
*/ const buildReAttestUrl = (nonce, isTestnet)=>{
|
|
13790
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
15377
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
13791
15378
|
const url = new URL(`${baseUrl}/v2/reattest/${nonce}`);
|
|
13792
15379
|
return url.toString();
|
|
13793
15380
|
};
|
|
@@ -14242,7 +15829,8 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14242
15829
|
* - `destinationChain` — present and supports CCTP v2
|
|
14243
15830
|
* - source and destination chains must both be testnet or both mainnet
|
|
14244
15831
|
* - source and destination chains must differ
|
|
14245
|
-
* - `executor
|
|
15832
|
+
* - destination — either `executor`, or both `mintRecipient` and
|
|
15833
|
+
* `destinationCaller`; not both
|
|
14246
15834
|
* - `amount` — bigint or non-empty string coercible to bigint
|
|
14247
15835
|
* - `feeTotalAmount` — bigint or non-empty string coercible to bigint
|
|
14248
15836
|
* - `feeToken` — valid EVM address (`0x` + 40 hex chars)
|
|
@@ -14284,10 +15872,17 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14284
15872
|
if (source.chain.name === dest.name) {
|
|
14285
15873
|
throw createUnsupportedRouteError(source.chain.name, dest.name);
|
|
14286
15874
|
}
|
|
14287
|
-
//
|
|
15875
|
+
// Destination: GenericExecutor shorthand or explicit recipient + caller.
|
|
14288
15876
|
const executor = p['executor'];
|
|
14289
|
-
|
|
14290
|
-
|
|
15877
|
+
const mintRecipient = p['mintRecipient'];
|
|
15878
|
+
const destinationCaller = p['destinationCaller'];
|
|
15879
|
+
const hasExecutor = typeof executor === 'string' && executor !== '';
|
|
15880
|
+
const hasDirectDestination = typeof mintRecipient === 'string' && mintRecipient !== '' && typeof destinationCaller === 'string' && destinationCaller !== '';
|
|
15881
|
+
if (!hasExecutor && !hasDirectDestination) {
|
|
15882
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor, or both mintRecipient and destinationCaller');
|
|
15883
|
+
}
|
|
15884
|
+
if (hasExecutor && hasDirectDestination) {
|
|
15885
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor or direct destination fields, not both');
|
|
14291
15886
|
}
|
|
14292
15887
|
// amount
|
|
14293
15888
|
const rawAmount = p['amount'];
|
|
@@ -14310,7 +15905,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14310
15905
|
throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
|
|
14311
15906
|
}
|
|
14312
15907
|
// feeToken
|
|
14313
|
-
if (!evmAddressSchema.safeParse(p['feeToken']).success) {
|
|
15908
|
+
if (!evmAddressSchema$1.safeParse(p['feeToken']).success) {
|
|
14314
15909
|
throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
|
|
14315
15910
|
}
|
|
14316
15911
|
// claim
|
|
@@ -14322,7 +15917,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14322
15917
|
if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
|
|
14323
15918
|
throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
|
|
14324
15919
|
}
|
|
14325
|
-
if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
|
|
15920
|
+
if (!evmAddressSchema$1.safeParse(claim['refundAddress']).success) {
|
|
14326
15921
|
throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
|
|
14327
15922
|
}
|
|
14328
15923
|
// hookData (optional)
|
|
@@ -14704,63 +16299,6 @@ const mockAttestationMessage = {
|
|
|
14704
16299
|
});
|
|
14705
16300
|
}
|
|
14706
16301
|
|
|
14707
|
-
/**
|
|
14708
|
-
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
14709
|
-
*
|
|
14710
|
-
* Constructs the appropriate action payload and dispatches it to any registered
|
|
14711
|
-
* event listeners. Handles type-safe dispatching for different step types.
|
|
14712
|
-
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
14713
|
-
*
|
|
14714
|
-
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
14715
|
-
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
14716
|
-
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
14717
|
-
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
14718
|
-
*
|
|
14719
|
-
* @example
|
|
14720
|
-
* ```typescript
|
|
14721
|
-
* const step: BridgeStep = {
|
|
14722
|
-
* name: 'burn',
|
|
14723
|
-
* state: 'success',
|
|
14724
|
-
* txHash: '0xabc...',
|
|
14725
|
-
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
14726
|
-
* data: { ... }
|
|
14727
|
-
* }
|
|
14728
|
-
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
14729
|
-
* ```
|
|
14730
|
-
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
14731
|
-
if (!provider.actionDispatcher) {
|
|
14732
|
-
return;
|
|
14733
|
-
}
|
|
14734
|
-
// Extract traceId from invocation context if provided
|
|
14735
|
-
const traceId = invocation?.traceId;
|
|
14736
|
-
const actionValues = {
|
|
14737
|
-
protocol: 'cctp',
|
|
14738
|
-
version: 'v2',
|
|
14739
|
-
...traceId !== undefined && {
|
|
14740
|
-
traceId
|
|
14741
|
-
},
|
|
14742
|
-
values: step
|
|
14743
|
-
};
|
|
14744
|
-
switch(name){
|
|
14745
|
-
case 'approve':
|
|
14746
|
-
case 'burn':
|
|
14747
|
-
case 'mint':
|
|
14748
|
-
provider.actionDispatcher.dispatch(name, {
|
|
14749
|
-
...actionValues,
|
|
14750
|
-
method: name
|
|
14751
|
-
});
|
|
14752
|
-
break;
|
|
14753
|
-
case 'fetchAttestation':
|
|
14754
|
-
case 'reAttest':
|
|
14755
|
-
provider.actionDispatcher.dispatch(name, {
|
|
14756
|
-
...actionValues,
|
|
14757
|
-
method: name,
|
|
14758
|
-
values: step
|
|
14759
|
-
});
|
|
14760
|
-
break;
|
|
14761
|
-
}
|
|
14762
|
-
}
|
|
14763
|
-
|
|
14764
16302
|
/**
|
|
14765
16303
|
* Check whether the source adapter supports EIP-5792 atomic batching and
|
|
14766
16304
|
* the consumer has not explicitly opted out via `config.batchTransactions`.
|
|
@@ -15007,7 +16545,7 @@ const mockAttestationMessage = {
|
|
|
15007
16545
|
return step;
|
|
15008
16546
|
}
|
|
15009
16547
|
|
|
15010
|
-
var version$2 = "1.
|
|
16548
|
+
var version$2 = "1.13.0";
|
|
15011
16549
|
var pkg$2 = {
|
|
15012
16550
|
version: version$2};
|
|
15013
16551
|
|
|
@@ -15713,6 +17251,9 @@ var pkg$2 = {
|
|
|
15713
17251
|
}
|
|
15714
17252
|
}
|
|
15715
17253
|
|
|
17254
|
+
const logger = createLogger({
|
|
17255
|
+
name: 'provider-cctp-v2'
|
|
17256
|
+
});
|
|
15716
17257
|
function isPlainObject(value) {
|
|
15717
17258
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
15718
17259
|
return false;
|
|
@@ -15818,6 +17359,34 @@ function assertCCTPV2Config(config) {
|
|
|
15818
17359
|
this.config = config;
|
|
15819
17360
|
}
|
|
15820
17361
|
/**
|
|
17362
|
+
* Emit a bridge step event through the provider's registered action
|
|
17363
|
+
* dispatcher.
|
|
17364
|
+
*
|
|
17365
|
+
* Kit-level orchestration that drives the burn primitives directly instead
|
|
17366
|
+
* of {@link CCTPV2BridgingProvider.bridge} (for example the receive-exact
|
|
17367
|
+
* source-fee flow) uses this to surface the same `approve`/`burn`/`mint`
|
|
17368
|
+
* events as the standard bridge path. It is a no-op when no dispatcher is
|
|
17369
|
+
* registered.
|
|
17370
|
+
*
|
|
17371
|
+
* @param name - The step name (`approve`, `burn`, `mint`, ...).
|
|
17372
|
+
* @param step - The completed bridge step to broadcast.
|
|
17373
|
+
* @param invocation - Optional invocation context carrying a `traceId` for
|
|
17374
|
+
* end-to-end correlation.
|
|
17375
|
+
* @returns Nothing.
|
|
17376
|
+
*
|
|
17377
|
+
* @example
|
|
17378
|
+
* ```typescript
|
|
17379
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
17380
|
+
* provider.emitBridgeStep('burn', {
|
|
17381
|
+
* name: 'burn',
|
|
17382
|
+
* state: 'success',
|
|
17383
|
+
* txHash: '0xabc...',
|
|
17384
|
+
* })
|
|
17385
|
+
* ```
|
|
17386
|
+
*/ emitBridgeStep(name, step, invocation) {
|
|
17387
|
+
dispatchStepEvent(name, step, this, invocation);
|
|
17388
|
+
}
|
|
17389
|
+
/**
|
|
15821
17390
|
* Resolves the effective polling configuration for an attestation request.
|
|
15822
17391
|
*
|
|
15823
17392
|
* Precedence (lowest to highest): provider `config.attestation`, then the
|
|
@@ -16047,6 +17616,116 @@ function assertCCTPV2Config(config) {
|
|
|
16047
17616
|
return estimateResult;
|
|
16048
17617
|
}
|
|
16049
17618
|
/**
|
|
17619
|
+
* Estimate source-chain gas for a prepaid-FORWARD deposit burn via
|
|
17620
|
+
* `TokenMessengerWithFees.depositForBurnWithHookAndFees`.
|
|
17621
|
+
*
|
|
17622
|
+
* Builds a size-correct GenericExecutor hookData placeholder — using the
|
|
17623
|
+
* pre-validated `contracts.executor` and `contracts.depositForHandler` — so
|
|
17624
|
+
* the EVM calldata length matches production. Attempts a live
|
|
17625
|
+
* `eth_estimateGas` via the adapter's `cctp.v2.depositForBurnWithFees`
|
|
17626
|
+
* action and falls back to the static
|
|
17627
|
+
* {@link DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM} constant when the live
|
|
17628
|
+
* RPC call fails.
|
|
17629
|
+
*
|
|
17630
|
+
* Gateway eligibility is the caller's responsibility: validate the
|
|
17631
|
+
* destination chain with `resolveGatewayExecutorContracts` (UBK) before
|
|
17632
|
+
* calling this method.
|
|
17633
|
+
*
|
|
17634
|
+
* @param params - Estimation parameters including adapter, chains, amount,
|
|
17635
|
+
* the signed fee quote returned by the Quote API, and the pre-validated
|
|
17636
|
+
* Gateway executor contracts resolved by the caller.
|
|
17637
|
+
* @returns Promise resolving to the estimated (or fallback) gas cost.
|
|
17638
|
+
* @throws KitError `SERVICE_INTERNAL_ERROR` (FATAL) if `dstChain`'s
|
|
17639
|
+
* `usdcAddress` is `null`.
|
|
17640
|
+
*
|
|
17641
|
+
* @example
|
|
17642
|
+
* ```typescript
|
|
17643
|
+
* const contracts = resolveGatewayExecutorContracts(srcChain, dstChain)
|
|
17644
|
+
* const gasEstimate = await CCTPV2BridgingProvider.estimateDepositBurn({
|
|
17645
|
+
* adapter: evmAdapter,
|
|
17646
|
+
* srcChain: Ethereum,
|
|
17647
|
+
* dstChain: ArcTestnet,
|
|
17648
|
+
* amountMinorUnits: 100_000_000n,
|
|
17649
|
+
* refundAddress: '0xUserWallet',
|
|
17650
|
+
* signedQuote: quote.signedQuote,
|
|
17651
|
+
* feeToken: quote.feeToken,
|
|
17652
|
+
* feeTotalAmount: BigInt(quote.feeTotalAmount),
|
|
17653
|
+
* resolvedContext,
|
|
17654
|
+
* contracts,
|
|
17655
|
+
* })
|
|
17656
|
+
* ```
|
|
17657
|
+
*/ static async estimateDepositBurn(params) {
|
|
17658
|
+
const { adapter, srcChain, dstChain, amountMinorUnits, refundAddress, signedQuote, feeToken, feeTotalAmount, resolvedContext, contracts } = params;
|
|
17659
|
+
const { executor, depositForHandler } = contracts;
|
|
17660
|
+
if (dstChain.usdcAddress === null) {
|
|
17661
|
+
throw new KitError({
|
|
17662
|
+
...ServiceError.INTERNAL_ERROR,
|
|
17663
|
+
recoverability: 'FATAL',
|
|
17664
|
+
message: `Destination chain ${dstChain.name} has no USDC address configured`
|
|
17665
|
+
});
|
|
17666
|
+
}
|
|
17667
|
+
// Build a size-correct hookData for eth_estimateGas. The EVM uses calldata
|
|
17668
|
+
// length to compute gas, so the byte layout must match production even
|
|
17669
|
+
// though field values are not final.
|
|
17670
|
+
const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
|
|
17671
|
+
dappId: 'gateway_deposit',
|
|
17672
|
+
destinationChain: dstChain,
|
|
17673
|
+
handler: depositForHandler,
|
|
17674
|
+
params: [
|
|
17675
|
+
dstChain.usdcAddress,
|
|
17676
|
+
refundAddress,
|
|
17677
|
+
0n
|
|
17678
|
+
],
|
|
17679
|
+
recoveryAddress: padAddressToBytes32(refundAddress),
|
|
17680
|
+
// Override deployments only — spread the canonical function signature
|
|
17681
|
+
// and amount indices from DAPP_CONFIG so they stay in sync.
|
|
17682
|
+
config: {
|
|
17683
|
+
gateway_deposit: {
|
|
17684
|
+
...DAPP_CONFIG.gateway_deposit,
|
|
17685
|
+
deployments: [
|
|
17686
|
+
{
|
|
17687
|
+
domainId: dstChain.cctp.domain
|
|
17688
|
+
}
|
|
17689
|
+
]
|
|
17690
|
+
}
|
|
17691
|
+
}
|
|
17692
|
+
});
|
|
17693
|
+
const hookData = buildForwardingHookDataWithPayload(GENERIC_EXECUTOR_HOOK_DATA_VERSION, geBlob);
|
|
17694
|
+
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee
|
|
17695
|
+
// item, so the hookData must carry a `cctp-forward` frame — matches the
|
|
17696
|
+
// assertion in `prepareDepositForBurn`.
|
|
17697
|
+
assertForwardHookData(hookData);
|
|
17698
|
+
try {
|
|
17699
|
+
const prepared = await adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
17700
|
+
fromChain: srcChain,
|
|
17701
|
+
toChain: dstChain,
|
|
17702
|
+
amount: amountMinorUnits,
|
|
17703
|
+
mintRecipient: executor,
|
|
17704
|
+
destinationCaller: executor,
|
|
17705
|
+
claim: {
|
|
17706
|
+
signedQuote,
|
|
17707
|
+
refundAddress
|
|
17708
|
+
},
|
|
17709
|
+
feeToken,
|
|
17710
|
+
feeTotalAmount,
|
|
17711
|
+
hookData
|
|
17712
|
+
}, resolvedContext);
|
|
17713
|
+
return await prepared.estimate(undefined);
|
|
17714
|
+
} catch (err) {
|
|
17715
|
+
logger.debug('estimateDepositBurn: live eth_estimateGas failed, using static fallback', {
|
|
17716
|
+
err,
|
|
17717
|
+
chain: srcChain.name
|
|
17718
|
+
});
|
|
17719
|
+
try {
|
|
17720
|
+
return await adapter.calculateTransactionFee(DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM, undefined, srcChain);
|
|
17721
|
+
} catch (feeErr) {
|
|
17722
|
+
throw createRpcEndpointError(srcChain.name, {
|
|
17723
|
+
rawError: feeErr
|
|
17724
|
+
});
|
|
17725
|
+
}
|
|
17726
|
+
}
|
|
17727
|
+
}
|
|
17728
|
+
/**
|
|
16050
17729
|
* Extracts OperationContext from bridge parameters for a given wallet context.
|
|
16051
17730
|
*
|
|
16052
17731
|
* This method extracts the chain and address information from the wallet context
|
|
@@ -16643,8 +18322,11 @@ function assertCCTPV2Config(config) {
|
|
|
16643
18322
|
// 2. Forwarder: Does the user want Circle's relayer to handle attestation/mint?
|
|
16644
18323
|
const useCustomBurn = hasCustomContractSupport(source.chain, 'bridge');
|
|
16645
18324
|
const useForwarder = destination.useForwarder === true;
|
|
16646
|
-
// Build hookData once if forwarder is enabled
|
|
16647
|
-
|
|
18325
|
+
// Build hookData once if forwarder is enabled. EVM destinations get the
|
|
18326
|
+
// empty forwarding frame; Solana destinations get a frame instructing the
|
|
18327
|
+
// relayer to create the recipient's ATA (using the same owner that derived
|
|
18328
|
+
// `mintRecipient`) so the mint succeeds even for a fresh wallet.
|
|
18329
|
+
const hookData = useForwarder ? await getForwarderHookData(destination.chain.type, destinationAddressForMint) : undefined;
|
|
16648
18330
|
if (useCustomBurn) {
|
|
16649
18331
|
// Custom burn path: use bridge contract (with or without hook)
|
|
16650
18332
|
const customBurnParams = {
|
|
@@ -16670,15 +18352,109 @@ function assertCCTPV2Config(config) {
|
|
|
16670
18352
|
return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
|
|
16671
18353
|
}
|
|
16672
18354
|
/**
|
|
18355
|
+
* Prepare the source-chain `depositForBurnWithHookAndFees` call for the
|
|
18356
|
+
* GenericExecutor FORWARD path.
|
|
18357
|
+
*
|
|
18358
|
+
* Exposed as a public static method so the UBK fast-deposit flow can invoke
|
|
18359
|
+
* it directly without holding a provider instance. The byte layout mirrors
|
|
18360
|
+
* {@link CCTPV2BridgingProvider.estimateDepositBurn} so gas estimates and the
|
|
18361
|
+
* executed call agree.
|
|
18362
|
+
*
|
|
18363
|
+
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
18364
|
+
* @param params - Burn parameters including adapter, chains, deposit action,
|
|
18365
|
+
* amount, signer address, signed fee quote, and pre-resolved adapter context.
|
|
18366
|
+
* @returns The prepared `depositForBurnWithHookAndFees` burn transaction.
|
|
18367
|
+
* @throws {KitError} `UNSUPPORTED_ROUTE` when `dstChain` lacks a
|
|
18368
|
+
* GenericExecutor or DepositForHandler, or the `deposit.dappId` is unknown.
|
|
18369
|
+
*
|
|
18370
|
+
* @example
|
|
18371
|
+
* ```typescript
|
|
18372
|
+
* import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
|
|
18373
|
+
* import { Ethereum, ArcTestnet } from '@core/chains'
|
|
18374
|
+
*
|
|
18375
|
+
* const prepared = await CCTPV2BridgingProvider.prepareDepositForBurn({
|
|
18376
|
+
* adapter,
|
|
18377
|
+
* srcChain: Ethereum,
|
|
18378
|
+
* dstChain: ArcTestnet,
|
|
18379
|
+
* deposit: { dappId: 'gateway_deposit', params: [usdcAddress, recipient, 0n] },
|
|
18380
|
+
* amountMinorUnits: 100_000_000n,
|
|
18381
|
+
* refundAddress: '0xSender...',
|
|
18382
|
+
* signedQuote: quote.signedQuote,
|
|
18383
|
+
* feeToken: quote.feeToken,
|
|
18384
|
+
* feeTotalAmount: BigInt(quote.feeTotalAmount),
|
|
18385
|
+
* resolvedContext,
|
|
18386
|
+
* })
|
|
18387
|
+
* const txHash = await prepared.execute()
|
|
18388
|
+
* ```
|
|
18389
|
+
*/ static async prepareDepositForBurn(params) {
|
|
18390
|
+
const { adapter, srcChain, dstChain, deposit, amountMinorUnits, refundAddress, signedQuote, feeToken, feeTotalAmount, resolvedContext } = params;
|
|
18391
|
+
// Resolve executor and depositForHandler from the destination chain's
|
|
18392
|
+
// gateway config. Throw an unsupported-route error if either is absent.
|
|
18393
|
+
const executor = dstChain.gateway?.contracts?.v1?.genericExecutor;
|
|
18394
|
+
const depositForHandler = dstChain.gateway?.contracts?.v1?.depositForHandler;
|
|
18395
|
+
if (!executor || !depositForHandler) {
|
|
18396
|
+
throw createUnsupportedRouteError(srcChain.name, dstChain.name);
|
|
18397
|
+
}
|
|
18398
|
+
// Resolve the canonical dApp config (function signature + amount indices)
|
|
18399
|
+
// for THIS deposit's `dappId`. Reject an unknown `dappId` rather than
|
|
18400
|
+
// encoding the wrong ABI selector, which would burn on the source but
|
|
18401
|
+
// revert in the executor call on the destination.
|
|
18402
|
+
const dappConfig = DAPP_CONFIG[deposit.dappId];
|
|
18403
|
+
if (dappConfig === undefined) {
|
|
18404
|
+
throw createUnsupportedRouteError(srcChain.name, dstChain.name);
|
|
18405
|
+
}
|
|
18406
|
+
// Build the bare GenericExecutor payload, then wrap it in the `cctp-forward`
|
|
18407
|
+
// frame required by the prepaid-FORWARD wrapper.
|
|
18408
|
+
const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
|
|
18409
|
+
dappId: deposit.dappId,
|
|
18410
|
+
destinationChain: dstChain,
|
|
18411
|
+
handler: depositForHandler,
|
|
18412
|
+
params: deposit.params,
|
|
18413
|
+
recoveryAddress: padAddressToBytes32(refundAddress),
|
|
18414
|
+
config: {
|
|
18415
|
+
[deposit.dappId]: {
|
|
18416
|
+
...dappConfig,
|
|
18417
|
+
deployments: [
|
|
18418
|
+
{
|
|
18419
|
+
domainId: dstChain.cctp.domain
|
|
18420
|
+
}
|
|
18421
|
+
]
|
|
18422
|
+
}
|
|
18423
|
+
}
|
|
18424
|
+
});
|
|
18425
|
+
const hookData = buildForwardingHookDataWithPayload(GENERIC_EXECUTOR_HOOK_DATA_VERSION, geBlob);
|
|
18426
|
+
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee
|
|
18427
|
+
// item, so the hookData must carry a `cctp-forward` frame; otherwise the
|
|
18428
|
+
// wrapper reverts `ForwardFeeWithoutHook`.
|
|
18429
|
+
assertForwardHookData(hookData);
|
|
18430
|
+
// Source-chain burn: `mintRecipient` AND `destinationCaller` are both the
|
|
18431
|
+
// executor; fees are prepaid against the signed quote.
|
|
18432
|
+
return adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
18433
|
+
fromChain: srcChain,
|
|
18434
|
+
toChain: dstChain,
|
|
18435
|
+
amount: amountMinorUnits,
|
|
18436
|
+
mintRecipient: executor,
|
|
18437
|
+
destinationCaller: executor,
|
|
18438
|
+
hookData,
|
|
18439
|
+
claim: {
|
|
18440
|
+
signedQuote,
|
|
18441
|
+
refundAddress
|
|
18442
|
+
},
|
|
18443
|
+
feeToken,
|
|
18444
|
+
feeTotalAmount
|
|
18445
|
+
}, resolvedContext);
|
|
18446
|
+
}
|
|
18447
|
+
/**
|
|
16673
18448
|
* Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
|
|
16674
18449
|
*
|
|
16675
|
-
*
|
|
16676
|
-
*
|
|
16677
|
-
*
|
|
16678
|
-
*
|
|
18450
|
+
* Build the source-chain `depositForBurnWithHookAndFees` call. Fees are
|
|
18451
|
+
* collected up front on the source chain against a signed quote. The
|
|
18452
|
+
* destination may use the GenericExecutor shorthand, or an explicit mint
|
|
18453
|
+
* recipient and destination caller for direct forwarding.
|
|
16679
18454
|
*
|
|
16680
|
-
* This is the low-level on-chain primitive behind the
|
|
16681
|
-
*
|
|
18455
|
+
* This is the low-level on-chain primitive behind the Unified Balance Kit
|
|
18456
|
+
* `fastCrossChainDeposit` and the Bridge Kit source-fee
|
|
18457
|
+
* (`feePayment: 'source'`) flow. The `hookData` and signed-quote
|
|
16682
18458
|
* `claim` are produced elsewhere and passed in here:
|
|
16683
18459
|
* - `hookData`: `buildForwardingHookDataWithPayload(version,
|
|
16684
18460
|
* buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
|
|
@@ -16695,32 +18471,48 @@ function assertCCTPV2Config(config) {
|
|
|
16695
18471
|
* approval covers both; the redundant second approval is skipped.
|
|
16696
18472
|
*
|
|
16697
18473
|
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
16698
|
-
* @param params - The burn amount,
|
|
18474
|
+
* @param params - The burn amount, destination, hook data, signed quote, and fee.
|
|
16699
18475
|
* @returns The prepared approvals, the prepared burn, and the resolved fee plan.
|
|
16700
18476
|
* @throws {KitError} If the wallet context is invalid, `destinationChain` does not
|
|
16701
|
-
* support CCTP v2, the
|
|
16702
|
-
* a bigint or a numeric string coercible to bigint,
|
|
16703
|
-
* `cctp-forward` frame (guaranteed
|
|
16704
|
-
* context cannot be resolved.
|
|
18477
|
+
* support CCTP v2, the destination fields are missing, `amount` or
|
|
18478
|
+
* `feeTotalAmount` is not a bigint or a numeric string coercible to bigint,
|
|
18479
|
+
* the hook data lacks a `cctp-forward` frame (guaranteed
|
|
18480
|
+
* `ForwardFeeWithoutHook`), or the operation context cannot be resolved.
|
|
16705
18481
|
*
|
|
16706
18482
|
* @example
|
|
16707
18483
|
* ```typescript
|
|
18484
|
+
* import {
|
|
18485
|
+
* CCTPV2BridgingProvider,
|
|
18486
|
+
* type BurnWithFeesParams,
|
|
18487
|
+
* } from '@circle-fin/provider-cctp-v2'
|
|
18488
|
+
*
|
|
18489
|
+
* declare const source: BurnWithFeesParams['source']
|
|
18490
|
+
* declare const destinationChain: BurnWithFeesParams['destinationChain']
|
|
18491
|
+
* declare const recipient: string
|
|
18492
|
+
* declare const hookData: string
|
|
18493
|
+
* declare const claim: BurnWithFeesParams['claim']
|
|
18494
|
+
*
|
|
18495
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
16708
18496
|
* const { approvals, burn } = await provider.burnWithFees({
|
|
16709
18497
|
* source,
|
|
16710
|
-
* destinationChain
|
|
18498
|
+
* destinationChain,
|
|
16711
18499
|
* amount: 1_000_000n,
|
|
16712
|
-
*
|
|
16713
|
-
*
|
|
16714
|
-
*
|
|
16715
|
-
*
|
|
16716
|
-
*
|
|
18500
|
+
* mintRecipient: recipient,
|
|
18501
|
+
* destinationCaller: '0x0000000000000000000000000000000000000000',
|
|
18502
|
+
* hookData,
|
|
18503
|
+
* claim,
|
|
18504
|
+
* feeToken: source.chain.usdcAddress,
|
|
18505
|
+
* feeTotalAmount: 10_000n,
|
|
16717
18506
|
* })
|
|
16718
18507
|
* for (const approval of approvals) await approval.execute()
|
|
16719
18508
|
* const txHash = await burn.execute()
|
|
16720
18509
|
* ```
|
|
16721
18510
|
*/ async burnWithFees(params) {
|
|
16722
18511
|
assertBurnWithFeesParams(params);
|
|
16723
|
-
const { source, destinationChain,
|
|
18512
|
+
const { source, destinationChain, hookData, claim, feeToken } = params;
|
|
18513
|
+
const hasExecutor = 'executor' in params && params.executor !== undefined;
|
|
18514
|
+
const mintRecipient = hasExecutor ? params.executor : params.mintRecipient;
|
|
18515
|
+
const destinationCaller = hasExecutor ? params.executor : params.destinationCaller;
|
|
16724
18516
|
const amount = BigInt(params.amount);
|
|
16725
18517
|
const feeTotalAmount = BigInt(params.feeTotalAmount);
|
|
16726
18518
|
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
|
|
@@ -16751,168 +18543,1171 @@ function assertCCTPV2Config(config) {
|
|
|
16751
18543
|
delegate: wrapperAddress,
|
|
16752
18544
|
amount: approval.amount
|
|
16753
18545
|
}, context)));
|
|
16754
|
-
// Build the burn
|
|
18546
|
+
// Build the burn with either the GenericExecutor shorthand or the explicit
|
|
18547
|
+
// direct-forwarding recipient and caller.
|
|
16755
18548
|
const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
16756
18549
|
fromChain: source.chain,
|
|
16757
18550
|
toChain: destinationChain,
|
|
16758
18551
|
amount,
|
|
16759
|
-
mintRecipient
|
|
16760
|
-
destinationCaller
|
|
18552
|
+
mintRecipient,
|
|
18553
|
+
destinationCaller,
|
|
16761
18554
|
hookData,
|
|
16762
18555
|
claim,
|
|
16763
18556
|
feeToken,
|
|
16764
18557
|
feeTotalAmount
|
|
16765
18558
|
}, context);
|
|
16766
18559
|
return {
|
|
16767
|
-
approvals,
|
|
16768
|
-
burn,
|
|
16769
|
-
feePayment
|
|
18560
|
+
approvals,
|
|
18561
|
+
burn,
|
|
18562
|
+
feePayment
|
|
18563
|
+
};
|
|
18564
|
+
}
|
|
18565
|
+
/**
|
|
18566
|
+
* Waits for a transaction to be mined and confirmed on the blockchain.
|
|
18567
|
+
*
|
|
18568
|
+
* This method should block until the transaction is confirmed on the blockchain.
|
|
18569
|
+
*
|
|
18570
|
+
* @param adapter - The adapter to use for transaction waiting
|
|
18571
|
+
* @param txHash - The hash of the transaction to wait for
|
|
18572
|
+
* @param chain - The chain definition where the transaction was executed
|
|
18573
|
+
* @param config - Optional configuration for transaction waiting (confirmations, timeout)
|
|
18574
|
+
* @returns The hash of the confirmed transaction
|
|
18575
|
+
* @example
|
|
18576
|
+
* ```typescript
|
|
18577
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
18578
|
+
* const txHash = await provider.waitForTransaction(
|
|
18579
|
+
* adapter,
|
|
18580
|
+
* '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
18581
|
+
* Ethereum,
|
|
18582
|
+
* )
|
|
18583
|
+
* console.log('Transaction confirmed:', txHash)
|
|
18584
|
+
* ```
|
|
18585
|
+
*/ async waitForTransaction(adapter, txHash, chain, config) {
|
|
18586
|
+
return adapter.waitForTransaction(txHash, config, chain);
|
|
18587
|
+
}
|
|
18588
|
+
}
|
|
18589
|
+
|
|
18590
|
+
/**
|
|
18591
|
+
* The default providers that will be used in addition to the providers provided
|
|
18592
|
+
* to the BridgeKit constructor.
|
|
18593
|
+
*
|
|
18594
|
+
* @param config - Optional configuration forwarded to the default providers
|
|
18595
|
+
* @returns The default bridging providers
|
|
18596
|
+
*/ const getDefaultProviders = (config = {})=>[
|
|
18597
|
+
new CCTPV2BridgingProvider(config.headers ? {
|
|
18598
|
+
headers: config.headers
|
|
18599
|
+
} : {})
|
|
18600
|
+
];
|
|
18601
|
+
|
|
18602
|
+
/**
|
|
18603
|
+
* A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
|
|
18604
|
+
* @param formatDirection - The direction to format the amount in.
|
|
18605
|
+
* @returns A function that transforms an amount into a human-readable string or a bigint string.
|
|
18606
|
+
*/ const getAmountTransformer = (formatDirection)=>formatDirection === 'to-human-readable' ? (params)=>formatAmount(params) : (params)=>parseAmount(params).toString();
|
|
18607
|
+
/**
|
|
18608
|
+
* Format the bridge result into human-readable string values for the user or bigint string values for internal use.
|
|
18609
|
+
*
|
|
18610
|
+
* @typeParam T - The specific result type (must extend BridgeResult or EstimateResult). Preserves the exact type passed in.
|
|
18611
|
+
* @param result - The bridge result to format.
|
|
18612
|
+
* @param formatDirection - The direction to format the result in.
|
|
18613
|
+
* - If 'to-human-readable', the result will be converted to human-readable string values.
|
|
18614
|
+
* - If 'to-internal', the result will be converted to bigint string values (usually for internal use).
|
|
18615
|
+
* @returns The formatted bridge result.
|
|
18616
|
+
*
|
|
18617
|
+
* @example
|
|
18618
|
+
* ```typescript
|
|
18619
|
+
* const result = await kit.bridge({
|
|
18620
|
+
* amount: '1000000',
|
|
18621
|
+
* token: 'USDC',
|
|
18622
|
+
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
18623
|
+
* to: { adapter: adapter, chain: 'Base' },
|
|
18624
|
+
* })
|
|
18625
|
+
*
|
|
18626
|
+
* // Format the bridge result into human-readable string values for the user
|
|
18627
|
+
* const formattedResultHumanReadable = formatBridgeResult(result, 'to-human-readable')
|
|
18628
|
+
* console.log(formattedResultHumanReadable)
|
|
18629
|
+
*
|
|
18630
|
+
* // Format the bridge result into bigint string values for internal use
|
|
18631
|
+
* const formattedResultInternal = formatBridgeResult(result, 'to-internal')
|
|
18632
|
+
* console.log(formattedResultInternal)
|
|
18633
|
+
* ```
|
|
18634
|
+
*/ const formatBridgeResult = (result, formatDirection)=>{
|
|
18635
|
+
const transform = getAmountTransformer(formatDirection);
|
|
18636
|
+
return {
|
|
18637
|
+
...result,
|
|
18638
|
+
amount: transform({
|
|
18639
|
+
value: result.amount,
|
|
18640
|
+
token: result.token
|
|
18641
|
+
}),
|
|
18642
|
+
...'config' in result && result.config && Object.keys(result.config).length > 0 && {
|
|
18643
|
+
config: {
|
|
18644
|
+
...result.config,
|
|
18645
|
+
...result.config.maxFee && {
|
|
18646
|
+
maxFee: transform({
|
|
18647
|
+
value: result.config.maxFee,
|
|
18648
|
+
token: result.token
|
|
18649
|
+
})
|
|
18650
|
+
},
|
|
18651
|
+
...result.config.customFee && {
|
|
18652
|
+
customFee: {
|
|
18653
|
+
...result.config.customFee,
|
|
18654
|
+
...result.config.customFee.value && {
|
|
18655
|
+
value: transform({
|
|
18656
|
+
value: result.config.customFee.value,
|
|
18657
|
+
token: result.token
|
|
18658
|
+
})
|
|
18659
|
+
}
|
|
18660
|
+
}
|
|
18661
|
+
}
|
|
18662
|
+
}
|
|
18663
|
+
}
|
|
18664
|
+
};
|
|
18665
|
+
};
|
|
18666
|
+
|
|
18667
|
+
/**
|
|
18668
|
+
* Register all bridge-kit event type strings with the shared registry so
|
|
18669
|
+
* callers of `withErrorTelemetry` / `emitResultStepErrorTelemetry` are
|
|
18670
|
+
* compile-time checked.
|
|
18671
|
+
*
|
|
18672
|
+
* @internal
|
|
18673
|
+
*/ /**
|
|
18674
|
+
* Telemetry event type identifiers for bridge-kit operations.
|
|
18675
|
+
*
|
|
18676
|
+
* @internal
|
|
18677
|
+
*/ const BRIDGE_EVENT_TYPES = {
|
|
18678
|
+
BRIDGE: 'bridge_bridge',
|
|
18679
|
+
RETRY: 'bridge_retry',
|
|
18680
|
+
ESTIMATE: 'bridge_estimate'
|
|
18681
|
+
};
|
|
18682
|
+
/**
|
|
18683
|
+
* Ordered mapping from provider step event names to telemetry event types.
|
|
18684
|
+
*
|
|
18685
|
+
* @remarks
|
|
18686
|
+
* The order matches the CCTP v2 bridge execution sequence. During
|
|
18687
|
+
* `bridge()`, completed step events are counted so the failing step
|
|
18688
|
+
* can be identified by its index.
|
|
18689
|
+
*
|
|
18690
|
+
* @internal
|
|
18691
|
+
*/ const BRIDGE_STEP_EVENT_MAP = [
|
|
18692
|
+
[
|
|
18693
|
+
'approve',
|
|
18694
|
+
'bridge_approve'
|
|
18695
|
+
],
|
|
18696
|
+
[
|
|
18697
|
+
'burn',
|
|
18698
|
+
'bridge_burn'
|
|
18699
|
+
],
|
|
18700
|
+
[
|
|
18701
|
+
'fetchAttestation',
|
|
18702
|
+
'bridge_fetch_attestation'
|
|
18703
|
+
],
|
|
18704
|
+
[
|
|
18705
|
+
'mint',
|
|
18706
|
+
'bridge_mint'
|
|
18707
|
+
]
|
|
18708
|
+
];
|
|
18709
|
+
|
|
18710
|
+
/**
|
|
18711
|
+
* Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
|
|
18712
|
+
*
|
|
18713
|
+
* @internal
|
|
18714
|
+
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
18715
|
+
/**
|
|
18716
|
+
* Base URL for Circle's Quote API (hosted in Iris) on testnet/sandbox.
|
|
18717
|
+
*
|
|
18718
|
+
* @internal
|
|
18719
|
+
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
18720
|
+
/**
|
|
18721
|
+
* Native fee-token sentinel (the zero address).
|
|
18722
|
+
*
|
|
18723
|
+
* When `feeToken` is the zero address the quote prices fees in the source
|
|
18724
|
+
* chain's native gas token (paid as `msg.value` on-chain). Pass a USDC token
|
|
18725
|
+
* address instead to denominate fees in USDC.
|
|
18726
|
+
*
|
|
18727
|
+
* @internal
|
|
18728
|
+
*/ const NATIVE_FEE_TOKEN = '0x0000000000000000000000000000000000000000';
|
|
18729
|
+
/**
|
|
18730
|
+
* API path prefix for the CCTP v2 USDC burn quote endpoint.
|
|
18731
|
+
*
|
|
18732
|
+
* The full path is `${QUOTE_BURN_USDC_PATH}/{sourceDomain}/{destinationDomain}`;
|
|
18733
|
+
* `usdc` is a fixed literal, not a token parameter.
|
|
18734
|
+
*
|
|
18735
|
+
* @internal
|
|
18736
|
+
*/ const QUOTE_BURN_USDC_PATH = '/v2/quote/burn/usdc';
|
|
18737
|
+
/**
|
|
18738
|
+
* API path prefix for the CCTP v2 USDC quote validate endpoint.
|
|
18739
|
+
*
|
|
18740
|
+
* The full path is `${QUOTE_VALIDATE_USDC_PATH}/{sourceDomain}`; accepts a
|
|
18741
|
+
* `POST { abiSignature, args }` body and returns `claimable`, `failedChecks`,
|
|
18742
|
+
* and the decoded `expiry`, `feeToken`, and `feeTotalAmount`.
|
|
18743
|
+
*
|
|
18744
|
+
* @internal
|
|
18745
|
+
*/ const QUOTE_VALIDATE_USDC_PATH = '/v2/quote/validate/usdc';
|
|
18746
|
+
/**
|
|
18747
|
+
* Default polling configuration for Quote API calls.
|
|
18748
|
+
*
|
|
18749
|
+
* A signed quote is short-lived (typically ~2 minutes, varying per chain) and
|
|
18750
|
+
* a feature-flag-disabled source chain returns a
|
|
18751
|
+
* permanent `503 SERVICE_NOT_ENABLED`, so retrying buys little and risks
|
|
18752
|
+
* outliving the quote. The client therefore makes a single attempt
|
|
18753
|
+
* (`maxRetries: 1`) with a 15s timeout, mirroring the reference
|
|
18754
|
+
* implementation; callers refresh by requesting a new quote rather than
|
|
18755
|
+
* relying on transport retries.
|
|
18756
|
+
*
|
|
18757
|
+
* No `headers` are set here: `pollApiWithValidation` always injects
|
|
18758
|
+
* `Content-Type: application/json` and adds `User-Agent` in Node. Browser
|
|
18759
|
+
* requests omit a user-agent header to avoid a CORS preflight, so duplicating
|
|
18760
|
+
* either header here would be dead configuration.
|
|
18761
|
+
*
|
|
18762
|
+
* @internal
|
|
18763
|
+
*/ const FEE_QUOTE_DEFAULT_CONFIG = {
|
|
18764
|
+
timeout: 15_000,
|
|
18765
|
+
maxRetries: 1,
|
|
18766
|
+
retryDelay: 200
|
|
18767
|
+
};
|
|
18768
|
+
|
|
18769
|
+
/**
|
|
18770
|
+
* Decimal string in token minor units, constrained to be strictly positive.
|
|
18771
|
+
*
|
|
18772
|
+
* @internal
|
|
18773
|
+
*/ const positiveAmountSchema = z.string().regex(/^\d+$/, 'must be a non-negative integer string')// Re-check the digit shape here: zod still runs this refinement when the
|
|
18774
|
+
// regex check above fails ("dirty"), so guard BigInt() against throwing on a
|
|
18775
|
+
// non-numeric value before comparing.
|
|
18776
|
+
.refine((value)=>/^\d+$/.test(value) && BigInt(value) > 0n, 'must be greater than zero');
|
|
18777
|
+
/**
|
|
18778
|
+
* A 20-byte EVM address in `0x` hex.
|
|
18779
|
+
*
|
|
18780
|
+
* The MVP prepaid-`FORWARD` `burn/usdc` path targets EVM contracts
|
|
18781
|
+
* (`TokenMessengerWithFees` / `GenericExecutor`), so `feeToken` and
|
|
18782
|
+
* `destinationCaller` are constrained to EVM addresses by design. This is an
|
|
18783
|
+
* intentional scope limit, not a permanent one: it can be widened to other
|
|
18784
|
+
* address formats as the fee service expands to more chains.
|
|
18785
|
+
*/ const evmAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'must be a 20-byte 0x address');
|
|
18786
|
+
/** Even-length `0x` hex (the empty `0x` is allowed). */ const hexSchema = z.string().regex(/^0x([a-fA-F0-9]{2})*$/, 'must be even-length 0x hex');
|
|
18787
|
+
/** Non-empty, even-length `0x` hex. */ const nonEmptyHexSchema = z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex');
|
|
18788
|
+
/** A 32-byte `0x` hex hash. */ const bytes32Schema = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'must be a 32-byte 0x hash');
|
|
18789
|
+
/** Decimal string in minor units, allowing zero. */ const numericStringSchema = z.string().regex(/^\d+$/, 'must be a non-negative integer string');
|
|
18790
|
+
/** An `https:` URL, used for the optional base-URL override. */ const httpsUrlSchema = z.string().refine((value)=>{
|
|
18791
|
+
try {
|
|
18792
|
+
return new URL(value).protocol === 'https:';
|
|
18793
|
+
} catch {
|
|
18794
|
+
return false;
|
|
18795
|
+
}
|
|
18796
|
+
}, 'must be an https URL');
|
|
18797
|
+
const forwardParamsSchema = z.object({
|
|
18798
|
+
hookData: hexSchema.optional(),
|
|
18799
|
+
destinationCaller: evmAddressSchema.optional()
|
|
18800
|
+
}).strict();
|
|
18801
|
+
const forwardRequestSchema = z.object({
|
|
18802
|
+
type: z.literal('FORWARD'),
|
|
18803
|
+
params: forwardParamsSchema.optional()
|
|
18804
|
+
}).strict();
|
|
18805
|
+
const preFinalityRequestSchema = z.object({
|
|
18806
|
+
type: z.literal('PRE_FINALITY')
|
|
18807
|
+
}).strict();
|
|
18808
|
+
/**
|
|
18809
|
+
* A single quote request item (`FORWARD` or `PRE_FINALITY`).
|
|
18810
|
+
*
|
|
18811
|
+
* @internal
|
|
18812
|
+
*/ const feeQuoteRequestSchema = z.discriminatedUnion('type', [
|
|
18813
|
+
forwardRequestSchema,
|
|
18814
|
+
preFinalityRequestSchema
|
|
18815
|
+
]);
|
|
18816
|
+
/**
|
|
18817
|
+
* A non-empty list of quote request items with unique types.
|
|
18818
|
+
*
|
|
18819
|
+
* @internal
|
|
18820
|
+
*/ const feeQuoteRequestsSchema = z.array(feeQuoteRequestSchema).min(1, 'at least one request item is required').refine((items)=>new Set(items.map((item)=>item.type)).size === items.length, 'request item types must be unique');
|
|
18821
|
+
/**
|
|
18822
|
+
* A structured `Partial<ApiPollingConfig>` polling override.
|
|
18823
|
+
*
|
|
18824
|
+
* Validates the field types callers actually set, so a plain-JS caller passing
|
|
18825
|
+
* `{ timeout: 'soon' }` is rejected at the boundary rather than failing opaquely
|
|
18826
|
+
* inside the transport. Unknown keys pass through so a future `ApiPollingConfig`
|
|
18827
|
+
* field is forwarded rather than silently dropped.
|
|
18828
|
+
*/ const apiPollingConfigSchema = z.object({
|
|
18829
|
+
timeout: z.number().int().positive().optional(),
|
|
18830
|
+
maxRetries: z.number().int().nonnegative().optional(),
|
|
18831
|
+
retryDelay: z.number().int().nonnegative().optional(),
|
|
18832
|
+
backoff: z.enum([
|
|
18833
|
+
'fixed',
|
|
18834
|
+
'exponential'
|
|
18835
|
+
]).optional(),
|
|
18836
|
+
maxRetryDelayMs: z.number().int().positive().optional(),
|
|
18837
|
+
headers: z.record(z.string()).optional()
|
|
18838
|
+
}).passthrough();
|
|
18839
|
+
/**
|
|
18840
|
+
* The validatable input for {@link fetchFeeQuote}.
|
|
18841
|
+
*
|
|
18842
|
+
* This is the single source of truth for input validation, including the CCTP
|
|
18843
|
+
* domains and the `isTestnet` environment flag. Validating `isTestnet` at
|
|
18844
|
+
* runtime matters because a plain-JS caller who omits it would otherwise leave
|
|
18845
|
+
* it `undefined`, which is falsy and silently selects the production base URL.
|
|
18846
|
+
* (`buildFeeQuoteUrl` independently re-validates the domains for standalone
|
|
18847
|
+
* callers.)
|
|
18848
|
+
*
|
|
18849
|
+
* @internal
|
|
18850
|
+
*/ const fetchFeeQuoteInputSchema = z.object({
|
|
18851
|
+
sourceDomain: z.number().int().nonnegative(),
|
|
18852
|
+
destinationDomain: z.number().int().nonnegative(),
|
|
18853
|
+
amount: positiveAmountSchema,
|
|
18854
|
+
feeToken: evmAddressSchema.optional(),
|
|
18855
|
+
requests: feeQuoteRequestsSchema,
|
|
18856
|
+
isTestnet: z.boolean(),
|
|
18857
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
18858
|
+
config: apiPollingConfigSchema.optional()
|
|
18859
|
+
}).strict();
|
|
18860
|
+
const feeQuoteItemSchema = z.object({
|
|
18861
|
+
type: z.string().min(1),
|
|
18862
|
+
amount: numericStringSchema,
|
|
18863
|
+
args: z.array(z.string()),
|
|
18864
|
+
argsHash: bytes32Schema
|
|
18865
|
+
}).passthrough();
|
|
18866
|
+
const exchangeRatesSchema = z.object({
|
|
18867
|
+
feeTokenUsd: z.string(),
|
|
18868
|
+
destinationTokenUsd: z.string()
|
|
18869
|
+
}).passthrough();
|
|
18870
|
+
const metadataSchema = z.object({
|
|
18871
|
+
destinationGasPrice: z.string().optional(),
|
|
18872
|
+
exchangeRates: exchangeRatesSchema.optional()
|
|
18873
|
+
}).passthrough();
|
|
18874
|
+
/** The `expiry` object the Quote API nests the quote TTL under. */ const expirySchema = z.discriminatedUnion('mode', [
|
|
18875
|
+
z.object({
|
|
18876
|
+
mode: z.literal('TIMESTAMP'),
|
|
18877
|
+
expiresAt: z.number().int().nonnegative()
|
|
18878
|
+
}).passthrough(),
|
|
18879
|
+
z.object({
|
|
18880
|
+
mode: z.literal('BLOCK_NUMBER'),
|
|
18881
|
+
expiresAtBlock: z.number().int().nonnegative(),
|
|
18882
|
+
blockEstimatedAt: z.number().int().nonnegative().optional()
|
|
18883
|
+
}).passthrough()
|
|
18884
|
+
]);
|
|
18885
|
+
/**
|
|
18886
|
+
* Schema for a signed fee quote returned by the Quote API.
|
|
18887
|
+
*
|
|
18888
|
+
* @internal
|
|
18889
|
+
*/ const signedFeeQuoteSchema = z.object({
|
|
18890
|
+
// The runtime YAML spec maps signedQuote to a looser `hex` (which allows
|
|
18891
|
+
// an empty `0x`); we keep the stricter non-empty form. Do not relax
|
|
18892
|
+
// without a reason.
|
|
18893
|
+
signedQuote: nonEmptyHexSchema,
|
|
18894
|
+
issuedAt: z.number().int().nonnegative(),
|
|
18895
|
+
// The API returns a mode-specific timestamp or source-block deadline.
|
|
18896
|
+
expiry: expirySchema,
|
|
18897
|
+
feeTotalAmount: numericStringSchema,
|
|
18898
|
+
feeToken: evmAddressSchema,
|
|
18899
|
+
nonce: numericStringSchema,
|
|
18900
|
+
items: z.array(feeQuoteItemSchema),
|
|
18901
|
+
metadata: metadataSchema.optional()
|
|
18902
|
+
}).passthrough();
|
|
18903
|
+
/**
|
|
18904
|
+
* Validate that an unknown value is a signed fee quote.
|
|
18905
|
+
*
|
|
18906
|
+
* @param value - The unknown value to validate.
|
|
18907
|
+
* @returns `true` when the value matches the signed-quote response shape.
|
|
18908
|
+
*
|
|
18909
|
+
* @example
|
|
18910
|
+
* ```typescript
|
|
18911
|
+
* import { isSignedFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
18912
|
+
*
|
|
18913
|
+
* declare const payload: unknown
|
|
18914
|
+
* if (isSignedFeeQuote(payload)) {
|
|
18915
|
+
* console.log(payload.feeTotalAmount)
|
|
18916
|
+
* }
|
|
18917
|
+
* ```
|
|
18918
|
+
*
|
|
18919
|
+
* @internal
|
|
18920
|
+
*/ function isSignedFeeQuote(value) {
|
|
18921
|
+
return signedFeeQuoteSchema.safeParse(value).success;
|
|
18922
|
+
}
|
|
18923
|
+
/**
|
|
18924
|
+
* Validates input to {@link validateQuote}.
|
|
18925
|
+
*
|
|
18926
|
+
* @internal
|
|
18927
|
+
*/ const validateQuoteInputSchema = z.object({
|
|
18928
|
+
sourceDomain: z.number().int().nonnegative(),
|
|
18929
|
+
abiSignature: z.string().min(1),
|
|
18930
|
+
args: z.array(z.union([
|
|
18931
|
+
z.string(),
|
|
18932
|
+
z.array(z.string())
|
|
18933
|
+
])),
|
|
18934
|
+
isTestnet: z.boolean(),
|
|
18935
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
18936
|
+
config: apiPollingConfigSchema.optional()
|
|
18937
|
+
}).strict();
|
|
18938
|
+
const quoteExpiryStatusSchema = z.discriminatedUnion('mode', [
|
|
18939
|
+
z.object({
|
|
18940
|
+
mode: z.literal('TIMESTAMP'),
|
|
18941
|
+
expired: z.boolean(),
|
|
18942
|
+
secondsRemaining: z.number().int().nonnegative(),
|
|
18943
|
+
expiresAt: z.number().int().nonnegative()
|
|
18944
|
+
}).passthrough(),
|
|
18945
|
+
z.object({
|
|
18946
|
+
mode: z.literal('BLOCK_NUMBER'),
|
|
18947
|
+
expired: z.boolean(),
|
|
18948
|
+
secondsRemaining: z.number().int().nonnegative(),
|
|
18949
|
+
expiresAtBlock: z.number().int().nonnegative(),
|
|
18950
|
+
blockEstimatedAt: z.number().int().nonnegative().optional()
|
|
18951
|
+
}).passthrough()
|
|
18952
|
+
]);
|
|
18953
|
+
const validateQuoteItemSchema = z.object({
|
|
18954
|
+
type: z.string().min(1),
|
|
18955
|
+
argsMatch: z.boolean(),
|
|
18956
|
+
amount: numericStringSchema.optional(),
|
|
18957
|
+
args: z.array(z.string()).optional(),
|
|
18958
|
+
argsHash: bytes32Schema.optional(),
|
|
18959
|
+
computedArgsHash: bytes32Schema.optional()
|
|
18960
|
+
}).passthrough();
|
|
18961
|
+
/**
|
|
18962
|
+
* Schema for a validate-quote result returned by the Iris `/validate/usdc/:sourceDomain` endpoint.
|
|
18963
|
+
*
|
|
18964
|
+
* The endpoint takes the source domain as a URL path parameter and does not
|
|
18965
|
+
* return it in the response body, so `sourceDomain` is intentionally not part
|
|
18966
|
+
* of this schema.
|
|
18967
|
+
*
|
|
18968
|
+
* @internal
|
|
18969
|
+
*/ const validateQuoteResultSchema = z.object({
|
|
18970
|
+
signedQuote: nonEmptyHexSchema,
|
|
18971
|
+
expiry: quoteExpiryStatusSchema,
|
|
18972
|
+
feeTotalAmount: numericStringSchema,
|
|
18973
|
+
feeToken: evmAddressSchema,
|
|
18974
|
+
nonce: numericStringSchema,
|
|
18975
|
+
claimable: z.boolean(),
|
|
18976
|
+
// Preserve newly introduced server-side reasons as opaque strings rather
|
|
18977
|
+
// than rejecting the entire safety response before the SDK is updated.
|
|
18978
|
+
failedChecks: z.array(z.string().min(1)),
|
|
18979
|
+
items: z.array(validateQuoteItemSchema)
|
|
18980
|
+
}).passthrough();
|
|
18981
|
+
/**
|
|
18982
|
+
* Validate that an unknown value is a validate-quote result.
|
|
18983
|
+
*
|
|
18984
|
+
* @param value - The unknown value to validate.
|
|
18985
|
+
* @returns `true` when the value matches the validate-quote response shape.
|
|
18986
|
+
*
|
|
18987
|
+
* @example
|
|
18988
|
+
* ```typescript
|
|
18989
|
+
* import { isValidateQuoteResult } from '@circle-fin/provider-fee-v1'
|
|
18990
|
+
*
|
|
18991
|
+
* declare const payload: unknown
|
|
18992
|
+
* if (isValidateQuoteResult(payload)) {
|
|
18993
|
+
* console.log(payload.claimable, payload.failedChecks)
|
|
18994
|
+
* }
|
|
18995
|
+
* ```
|
|
18996
|
+
*
|
|
18997
|
+
* @internal
|
|
18998
|
+
*/ function isValidateQuoteResult(value) {
|
|
18999
|
+
return validateQuoteResultSchema.safeParse(value).success;
|
|
19000
|
+
}
|
|
19001
|
+
|
|
19002
|
+
/**
|
|
19003
|
+
* Validate that a CCTP domain id is a non-negative integer.
|
|
19004
|
+
*
|
|
19005
|
+
* @param value - The domain id to validate.
|
|
19006
|
+
* @param label - The parameter name, used in the error message.
|
|
19007
|
+
* @returns Nothing.
|
|
19008
|
+
* @throws {@link KitError} When the value is not a non-negative integer.
|
|
19009
|
+
* @internal
|
|
19010
|
+
*/ function assertDomain(value, label) {
|
|
19011
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
19012
|
+
throw new KitError({
|
|
19013
|
+
...InputError.VALIDATION_FAILED,
|
|
19014
|
+
recoverability: 'FATAL',
|
|
19015
|
+
message: `Quote API getFeeQuote failed: ${label} must be a ` + `non-negative integer, received ${String(value)}`,
|
|
19016
|
+
cause: {
|
|
19017
|
+
trace: {
|
|
19018
|
+
[label]: value
|
|
19019
|
+
}
|
|
19020
|
+
}
|
|
19021
|
+
});
|
|
19022
|
+
}
|
|
19023
|
+
}
|
|
19024
|
+
/**
|
|
19025
|
+
* Build the Quote API URL for a CCTP v2 USDC burn quote.
|
|
19026
|
+
*
|
|
19027
|
+
* Resolves the environment base URL (or an explicit `baseUrl` override) and
|
|
19028
|
+
* appends the burn/usdc path with the source and destination CCTP domains.
|
|
19029
|
+
* `usdc` is a fixed path literal, not a token parameter.
|
|
19030
|
+
*
|
|
19031
|
+
* @param params - The domains and environment selector.
|
|
19032
|
+
* @returns The fully-qualified Quote API URL.
|
|
19033
|
+
* @throws {@link KitError} When either domain is not a non-negative integer.
|
|
19034
|
+
*
|
|
19035
|
+
* @example
|
|
19036
|
+
* ```typescript
|
|
19037
|
+
* import { buildFeeQuoteUrl } from '@circle-fin/provider-fee-v1'
|
|
19038
|
+
*
|
|
19039
|
+
* const url = buildFeeQuoteUrl({
|
|
19040
|
+
* sourceDomain: 3,
|
|
19041
|
+
* destinationDomain: 26,
|
|
19042
|
+
* isTestnet: false,
|
|
19043
|
+
* })
|
|
19044
|
+
* // => 'https://iris-api.circle.com/v2/quote/burn/usdc/3/26'
|
|
19045
|
+
* ```
|
|
19046
|
+
*
|
|
19047
|
+
* @internal
|
|
19048
|
+
*/ function buildFeeQuoteUrl(params) {
|
|
19049
|
+
const { sourceDomain, destinationDomain, isTestnet, baseUrl } = params;
|
|
19050
|
+
assertDomain(sourceDomain, 'sourceDomain');
|
|
19051
|
+
assertDomain(destinationDomain, 'destinationDomain');
|
|
19052
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
19053
|
+
return new URL(`${QUOTE_BURN_USDC_PATH}/${String(sourceDomain)}/${String(destinationDomain)}`, resolvedBaseUrl).toString();
|
|
19054
|
+
}
|
|
19055
|
+
|
|
19056
|
+
/**
|
|
19057
|
+
* Assert that a quote's per-item fee amounts sum to its `feeTotalAmount`.
|
|
19058
|
+
*
|
|
19059
|
+
* A defensive integrity check on the Quote API response, enforced internally
|
|
19060
|
+
* by `fetchFeeQuote`. It does not compare against the destination-side
|
|
19061
|
+
* `feeExecuted`, which is expected to be zero for prepaid forward-only burns.
|
|
19062
|
+
*
|
|
19063
|
+
* @param quote - The signed fee quote to check.
|
|
19064
|
+
* @returns Nothing.
|
|
19065
|
+
* @throws {@link KitError} When the item amounts do not sum to `feeTotalAmount`.
|
|
19066
|
+
* @internal
|
|
19067
|
+
*/ function assertFeeItemsSumToTotal(quote) {
|
|
19068
|
+
const itemsTotal = quote.items.reduce((sum, item)=>sum + BigInt(item.amount), 0n);
|
|
19069
|
+
const declaredTotal = BigInt(quote.feeTotalAmount);
|
|
19070
|
+
if (itemsTotal !== declaredTotal) {
|
|
19071
|
+
throw new KitError({
|
|
19072
|
+
...InputError.VALIDATION_FAILED,
|
|
19073
|
+
recoverability: 'FATAL',
|
|
19074
|
+
message: `Quote API getFeeQuote failed: fee items sum ` + `(${itemsTotal.toString()}) does not equal feeTotalAmount ` + `(${declaredTotal.toString()})`,
|
|
19075
|
+
cause: {
|
|
19076
|
+
trace: {
|
|
19077
|
+
itemsTotal: itemsTotal.toString(),
|
|
19078
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19079
|
+
}
|
|
19080
|
+
}
|
|
19081
|
+
});
|
|
19082
|
+
}
|
|
19083
|
+
}
|
|
19084
|
+
|
|
19085
|
+
/**
|
|
19086
|
+
* Determine whether a Quote API error represents a disabled source chain.
|
|
19087
|
+
*
|
|
19088
|
+
* @param error - The error thrown by the HTTP layer.
|
|
19089
|
+
* @returns `true` when the error contains the `SERVICE_NOT_ENABLED` marker.
|
|
19090
|
+
* @internal
|
|
19091
|
+
*/ function isServiceNotEnabled(error) {
|
|
19092
|
+
const body = typeof error === 'object' && error !== null && 'responseBody' in error ? error.responseBody : undefined;
|
|
19093
|
+
if (typeof body === 'object' && body !== null) {
|
|
19094
|
+
const fields = body;
|
|
19095
|
+
const candidates = [
|
|
19096
|
+
fields['errorCode'],
|
|
19097
|
+
fields['code'],
|
|
19098
|
+
fields['message'],
|
|
19099
|
+
fields['externalMessage'],
|
|
19100
|
+
fields['error']
|
|
19101
|
+
];
|
|
19102
|
+
if (candidates.some((value)=>typeof value === 'string' && value.toUpperCase().includes('SERVICE_NOT_ENABLED'))) {
|
|
19103
|
+
return true;
|
|
19104
|
+
}
|
|
19105
|
+
}
|
|
19106
|
+
return getErrorMessage(error).toUpperCase().includes('SERVICE_NOT_ENABLED');
|
|
19107
|
+
}
|
|
19108
|
+
|
|
19109
|
+
const SERVICE$1 = 'Quote API';
|
|
19110
|
+
const OPERATION$1 = 'getFeeQuote';
|
|
19111
|
+
/**
|
|
19112
|
+
* Serialize request items for the wire body.
|
|
19113
|
+
*
|
|
19114
|
+
* `PRE_FINALITY` is emitted with no `params` key, and a `FORWARD` item only
|
|
19115
|
+
* carries the binding fields that are present.
|
|
19116
|
+
*
|
|
19117
|
+
* @param requests - The request items to serialize.
|
|
19118
|
+
* @returns The serialized request items.
|
|
19119
|
+
* @internal
|
|
19120
|
+
*/ function serializeRequests(requests) {
|
|
19121
|
+
return requests.map((request)=>{
|
|
19122
|
+
if (request.type === 'PRE_FINALITY') {
|
|
19123
|
+
return {
|
|
19124
|
+
type: 'PRE_FINALITY'
|
|
19125
|
+
};
|
|
19126
|
+
}
|
|
19127
|
+
const params = request.params;
|
|
19128
|
+
if (params === undefined) {
|
|
19129
|
+
return {
|
|
19130
|
+
type: 'FORWARD'
|
|
19131
|
+
};
|
|
19132
|
+
}
|
|
19133
|
+
const forwardParams = {};
|
|
19134
|
+
if (params.hookData !== undefined) {
|
|
19135
|
+
forwardParams.hookData = params.hookData;
|
|
19136
|
+
}
|
|
19137
|
+
if (params.destinationCaller !== undefined) {
|
|
19138
|
+
forwardParams.destinationCaller = params.destinationCaller;
|
|
19139
|
+
}
|
|
19140
|
+
return {
|
|
19141
|
+
type: 'FORWARD',
|
|
19142
|
+
params: forwardParams
|
|
19143
|
+
};
|
|
19144
|
+
});
|
|
19145
|
+
}
|
|
19146
|
+
/**
|
|
19147
|
+
* Fetch a signed fee quote from Circle's Quote API for a CCTP v2 USDC burn.
|
|
19148
|
+
*
|
|
19149
|
+
* Validates inputs, POSTs to
|
|
19150
|
+
* `/v2/quote/burn/usdc/{sourceDomain}/{destinationDomain}` with a single
|
|
19151
|
+
* attempt (the signed quote is short-lived), and returns the typed quote. A
|
|
19152
|
+
* disabled source chain (`503 SERVICE_NOT_ENABLED`) surfaces as a fatal,
|
|
19153
|
+
* non-retryable error; other failures are mapped to a {@link KitError} via the
|
|
19154
|
+
* shared API error parser.
|
|
19155
|
+
*
|
|
19156
|
+
* @param params - The domains, amount, request items, and environment.
|
|
19157
|
+
* @returns The signed fee quote.
|
|
19158
|
+
* @throws {@link KitError} On invalid input, a disabled source chain, an HTTP
|
|
19159
|
+
* error, or an invalid response shape.
|
|
19160
|
+
*
|
|
19161
|
+
* @example
|
|
19162
|
+
* ```typescript
|
|
19163
|
+
* import { fetchFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
19164
|
+
*
|
|
19165
|
+
* const quote = await fetchFeeQuote({
|
|
19166
|
+
* sourceDomain: 3,
|
|
19167
|
+
* destinationDomain: 26,
|
|
19168
|
+
* amount: '1000000',
|
|
19169
|
+
* requests: [{ type: 'FORWARD' }, { type: 'PRE_FINALITY' }],
|
|
19170
|
+
* isTestnet: false,
|
|
19171
|
+
* })
|
|
19172
|
+
* console.log(quote.feeTotalAmount, quote.expiry)
|
|
19173
|
+
* ```
|
|
19174
|
+
*
|
|
19175
|
+
* @internal
|
|
19176
|
+
*/ async function fetchFeeQuote(params) {
|
|
19177
|
+
const { sourceDomain, destinationDomain, amount, requests, feeToken, isTestnet, baseUrl, config } = params;
|
|
19178
|
+
const parsed = fetchFeeQuoteInputSchema.safeParse({
|
|
19179
|
+
sourceDomain,
|
|
19180
|
+
destinationDomain,
|
|
19181
|
+
amount,
|
|
19182
|
+
feeToken,
|
|
19183
|
+
requests,
|
|
19184
|
+
isTestnet,
|
|
19185
|
+
baseUrl,
|
|
19186
|
+
config
|
|
19187
|
+
});
|
|
19188
|
+
if (!parsed.success) {
|
|
19189
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
19190
|
+
throw new KitError({
|
|
19191
|
+
...InputError.VALIDATION_FAILED,
|
|
19192
|
+
recoverability: 'FATAL',
|
|
19193
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: ${detail}`,
|
|
19194
|
+
cause: {
|
|
19195
|
+
trace: parsed.error.issues
|
|
19196
|
+
}
|
|
19197
|
+
});
|
|
19198
|
+
}
|
|
19199
|
+
const url = baseUrl === undefined ? buildFeeQuoteUrl({
|
|
19200
|
+
sourceDomain,
|
|
19201
|
+
destinationDomain,
|
|
19202
|
+
isTestnet
|
|
19203
|
+
}) : buildFeeQuoteUrl({
|
|
19204
|
+
sourceDomain,
|
|
19205
|
+
destinationDomain,
|
|
19206
|
+
isTestnet,
|
|
19207
|
+
baseUrl
|
|
19208
|
+
});
|
|
19209
|
+
const body = {
|
|
19210
|
+
amount,
|
|
19211
|
+
feeToken: feeToken ?? NATIVE_FEE_TOKEN,
|
|
19212
|
+
requests: serializeRequests(requests)
|
|
19213
|
+
};
|
|
19214
|
+
const pollingConfig = {
|
|
19215
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
19216
|
+
...config
|
|
19217
|
+
};
|
|
19218
|
+
let quote;
|
|
19219
|
+
try {
|
|
19220
|
+
quote = await pollApiPost(url, body, isSignedFeeQuote, pollingConfig);
|
|
19221
|
+
} catch (error) {
|
|
19222
|
+
// Only one service-specific code (SERVICE_NOT_ENABLED) needs bespoke
|
|
19223
|
+
// mapping, so it is detected inline rather than via a dedicated
|
|
19224
|
+
// `parseFeeQuoteApiError` parser; everything else flows through the shared
|
|
19225
|
+
// `parseApiError`. Promote to a parser if more coded errors appear.
|
|
19226
|
+
if (isServiceNotEnabled(error)) {
|
|
19227
|
+
throw new KitError({
|
|
19228
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
19229
|
+
recoverability: 'FATAL',
|
|
19230
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: source chain not enabled for fee ` + `quotes (SERVICE_NOT_ENABLED)`,
|
|
19231
|
+
cause: {
|
|
19232
|
+
trace: error
|
|
19233
|
+
}
|
|
19234
|
+
});
|
|
19235
|
+
}
|
|
19236
|
+
throw parseApiError(error, {
|
|
19237
|
+
service: SERVICE$1,
|
|
19238
|
+
operation: OPERATION$1
|
|
19239
|
+
});
|
|
19240
|
+
}
|
|
19241
|
+
// Defense-in-depth: a self-consistent quote's per-item fees sum to the
|
|
19242
|
+
// declared total. Enforced here so callers cannot forget the check.
|
|
19243
|
+
assertFeeItemsSumToTotal(quote);
|
|
19244
|
+
return quote;
|
|
19245
|
+
}
|
|
19246
|
+
|
|
19247
|
+
const SERVICE = 'Quote API';
|
|
19248
|
+
const OPERATION = 'validateQuote';
|
|
19249
|
+
/**
|
|
19250
|
+
* Validate a signed fee quote against a full on-chain call via Circle's Iris
|
|
19251
|
+
* `/v2/quote/validate/usdc/:sourceDomain` endpoint.
|
|
19252
|
+
*
|
|
19253
|
+
* POSTs the ABI function signature and the encoded call arguments to Iris,
|
|
19254
|
+
* which verifies the signature, checks expiry, confirms the argsHash committed
|
|
19255
|
+
* in the quote matches the submitted args, and returns `claimable` plus the
|
|
19256
|
+
* decoded `expiry`, `feeToken`, and `feeTotalAmount`.
|
|
19257
|
+
*
|
|
19258
|
+
* @param params - The source domain, ABI signature, call arguments, and environment.
|
|
19259
|
+
* @returns The claimability, binding checks, and authoritative expiry status.
|
|
19260
|
+
* @throws {@link KitError} When input, transport, or response validation fails.
|
|
19261
|
+
*
|
|
19262
|
+
* @example
|
|
19263
|
+
* ```typescript
|
|
19264
|
+
* import { validateQuote } from '@circle-fin/provider-fee-v1'
|
|
19265
|
+
*
|
|
19266
|
+
* const result = await validateQuote({
|
|
19267
|
+
* sourceDomain: 3,
|
|
19268
|
+
* // The exact function + arguments, in ABI order, that will be burned on-chain.
|
|
19269
|
+
* abiSignature:
|
|
19270
|
+
* 'depositForBurnWithHookAndFees(uint256,uint32,bytes32,address,bytes32,bytes,(bytes,address))',
|
|
19271
|
+
* args: [
|
|
19272
|
+
* '1000000',
|
|
19273
|
+
* '26',
|
|
19274
|
+
* '0x0000000000000000000000001111111111111111111111111111111111111111',
|
|
19275
|
+
* '0x2222222222222222222222222222222222222222',
|
|
19276
|
+
* '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
19277
|
+
* '0x636374702d666f72776172640000000000000000000000000000000000000000',
|
|
19278
|
+
* ['0x01abcd', '0x3333333333333333333333333333333333333333'],
|
|
19279
|
+
* ],
|
|
19280
|
+
* isTestnet: false,
|
|
19281
|
+
* })
|
|
19282
|
+
* console.log(result.claimable, result.expiry.secondsRemaining)
|
|
19283
|
+
* if (!result.claimable) {
|
|
19284
|
+
* console.error('Quote rejected:', result.failedChecks)
|
|
19285
|
+
* }
|
|
19286
|
+
* ```
|
|
19287
|
+
*
|
|
19288
|
+
* @internal
|
|
19289
|
+
*/ async function validateQuote(params) {
|
|
19290
|
+
const parsed = validateQuoteInputSchema.safeParse(params);
|
|
19291
|
+
if (!parsed.success) {
|
|
19292
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
19293
|
+
throw new KitError({
|
|
19294
|
+
...InputError.VALIDATION_FAILED,
|
|
19295
|
+
recoverability: 'FATAL',
|
|
19296
|
+
message: `${SERVICE} ${OPERATION} failed: ${detail}`,
|
|
19297
|
+
cause: {
|
|
19298
|
+
trace: parsed.error.issues
|
|
19299
|
+
}
|
|
19300
|
+
});
|
|
19301
|
+
}
|
|
19302
|
+
const { sourceDomain, abiSignature, args, isTestnet, baseUrl } = parsed.data;
|
|
19303
|
+
// `config` was validated by the schema above; spread the caller's original,
|
|
19304
|
+
// precisely typed `Partial<ApiPollingConfig>` so the merged polling config
|
|
19305
|
+
// stays assignable under `exactOptionalPropertyTypes`.
|
|
19306
|
+
const pollingConfig = {
|
|
19307
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
19308
|
+
...params.config
|
|
19309
|
+
};
|
|
19310
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
19311
|
+
const url = new URL(`${QUOTE_VALIDATE_USDC_PATH}/${String(sourceDomain)}`, resolvedBaseUrl).toString();
|
|
19312
|
+
try {
|
|
19313
|
+
return await pollApiPost(url, {
|
|
19314
|
+
abiSignature,
|
|
19315
|
+
args
|
|
19316
|
+
}, isValidateQuoteResult, pollingConfig);
|
|
19317
|
+
} catch (error) {
|
|
19318
|
+
if (isServiceNotEnabled(error)) {
|
|
19319
|
+
throw new KitError({
|
|
19320
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
19321
|
+
recoverability: 'FATAL',
|
|
19322
|
+
message: `${SERVICE} ${OPERATION} failed: source chain not enabled for ` + `quote validation (SERVICE_NOT_ENABLED)`,
|
|
19323
|
+
cause: {
|
|
19324
|
+
trace: error
|
|
19325
|
+
}
|
|
19326
|
+
});
|
|
19327
|
+
}
|
|
19328
|
+
throw parseApiError(error, {
|
|
19329
|
+
service: SERVICE,
|
|
19330
|
+
operation: OPERATION
|
|
19331
|
+
});
|
|
19332
|
+
}
|
|
19333
|
+
}
|
|
19334
|
+
|
|
19335
|
+
/** Refresh timestamp quotes this many seconds before submission. */ const QUOTE_EXPIRY_SAFETY_SECONDS = 30;
|
|
19336
|
+
/** ABI signature validated by the Quote API before source-chain submission. */ const BURN_WITH_FEES_ABI_SIGNATURE = 'depositForBurnWithHookAndFees(uint256,uint32,bytes32,address,bytes32,bytes,(bytes,address))';
|
|
19337
|
+
/** Unrestricted CCTP destination caller used by the forwarding relayer. */ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
19338
|
+
function isQuoteNearEstimatedExpiry(quote) {
|
|
19339
|
+
const currentSeconds = Math.floor(Date.now() / 1_000);
|
|
19340
|
+
const expiresAt = quote.expiry.mode === 'TIMESTAMP' ? quote.expiry.expiresAt : quote.expiry.blockEstimatedAt;
|
|
19341
|
+
// A BLOCK_NUMBER quote may omit the advisory `blockEstimatedAt` estimate; when
|
|
19342
|
+
// it is absent, skip this wall-clock pre-check and defer to the authoritative
|
|
19343
|
+
// source-chain-tip validation performed downstream.
|
|
19344
|
+
if (expiresAt === undefined) {
|
|
19345
|
+
return false;
|
|
19346
|
+
}
|
|
19347
|
+
return expiresAt <= currentSeconds + QUOTE_EXPIRY_SAFETY_SECONDS;
|
|
19348
|
+
}
|
|
19349
|
+
function assertSourceFeeRoute(params) {
|
|
19350
|
+
const { source, destination, config } = params;
|
|
19351
|
+
// Keep these checks explicit so a successful assertion guarantees the CCTP
|
|
19352
|
+
// v2 narrowing; hasSourceFeeSupport returns a plain boolean.
|
|
19353
|
+
if (source.chain.type !== 'evm' || destination.chain.type !== 'evm' || !isCCTPV2Supported(source.chain) || !isCCTPV2Supported(destination.chain)) {
|
|
19354
|
+
throw createUnsupportedRouteError(source.chain.name, destination.chain.name);
|
|
19355
|
+
}
|
|
19356
|
+
if (!hasSourceFeeSupport(source.chain)) {
|
|
19357
|
+
throw createValidationFailedError$1('config.feePayment', 'source', `Source-paid ("receive-exact") fees are not supported from ${source.chain.name}. ` + 'Use the default destination-paid fees for this source chain');
|
|
19358
|
+
}
|
|
19359
|
+
const useForwarder = destination.useForwarder;
|
|
19360
|
+
if (useForwarder !== true) {
|
|
19361
|
+
throw createValidationFailedError$1('to.useForwarder', useForwarder, "feePayment: 'source' requires useForwarder: true");
|
|
19362
|
+
}
|
|
19363
|
+
if (config.customFee !== undefined) {
|
|
19364
|
+
throw createValidationFailedError$1('config.customFee', config.customFee, "Custom fees are not supported with feePayment: 'source'. Remove the " + 'per-call customFee or the kit-level custom fee policy for this route.');
|
|
19365
|
+
}
|
|
19366
|
+
}
|
|
19367
|
+
function buildQuoteBinding(params) {
|
|
19368
|
+
assertSourceFeeRoute(params);
|
|
19369
|
+
const mintRecipient = params.destination.recipientAddress ?? params.destination.address;
|
|
19370
|
+
const hookData = buildForwardingHookData();
|
|
19371
|
+
const requests = [
|
|
19372
|
+
{
|
|
19373
|
+
type: 'FORWARD',
|
|
19374
|
+
params: {
|
|
19375
|
+
hookData,
|
|
19376
|
+
destinationCaller: ZERO_ADDRESS
|
|
19377
|
+
}
|
|
19378
|
+
}
|
|
19379
|
+
];
|
|
19380
|
+
if ((params.config.transferSpeed ?? TransferSpeed.FAST) === TransferSpeed.FAST) {
|
|
19381
|
+
requests.push({
|
|
19382
|
+
type: 'PRE_FINALITY'
|
|
19383
|
+
});
|
|
19384
|
+
}
|
|
19385
|
+
return {
|
|
19386
|
+
sourceDomain: params.source.chain.cctp.domain,
|
|
19387
|
+
destinationDomain: params.destination.chain.cctp.domain,
|
|
19388
|
+
isTestnet: params.source.chain.isTestnet,
|
|
19389
|
+
amount: params.amount,
|
|
19390
|
+
mintRecipient,
|
|
19391
|
+
hookData,
|
|
19392
|
+
destinationCaller: ZERO_ADDRESS,
|
|
19393
|
+
feeToken: params.source.chain.usdcAddress,
|
|
19394
|
+
requests
|
|
19395
|
+
};
|
|
19396
|
+
}
|
|
19397
|
+
async function fetchBoundQuote(binding) {
|
|
19398
|
+
try {
|
|
19399
|
+
const quote = await fetchFeeQuote({
|
|
19400
|
+
sourceDomain: binding.sourceDomain,
|
|
19401
|
+
destinationDomain: binding.destinationDomain,
|
|
19402
|
+
amount: binding.amount,
|
|
19403
|
+
feeToken: binding.feeToken,
|
|
19404
|
+
requests: binding.requests,
|
|
19405
|
+
isTestnet: binding.isTestnet
|
|
19406
|
+
});
|
|
19407
|
+
if (quote.feeToken.toLowerCase() !== binding.feeToken.toLowerCase()) {
|
|
19408
|
+
throw createValidationFailedError$1('feeToken', quote.feeToken, 'Fee Service must return source-chain USDC for source-fee bridging');
|
|
19409
|
+
}
|
|
19410
|
+
return quote;
|
|
19411
|
+
} catch (error) {
|
|
19412
|
+
if (isRateLimitError(error)) {
|
|
19413
|
+
throw new KitError({
|
|
19414
|
+
...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
19415
|
+
recoverability: 'RETRYABLE',
|
|
19416
|
+
message: 'Fee Service rate limit exceeded. Retry with caller-managed exponential backoff; Bridge Kit does not retry signed quote requests automatically.',
|
|
19417
|
+
cause: {
|
|
19418
|
+
trace: error
|
|
19419
|
+
}
|
|
19420
|
+
});
|
|
19421
|
+
}
|
|
19422
|
+
throw error;
|
|
19423
|
+
}
|
|
19424
|
+
}
|
|
19425
|
+
async function fetchSubmissionQuote(binding) {
|
|
19426
|
+
let quote = await fetchBoundQuote(binding);
|
|
19427
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
19428
|
+
quote = await fetchBoundQuote(binding);
|
|
19429
|
+
}
|
|
19430
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
19431
|
+
throw createValidationFailedError$1('quote', undefined, 'Fee Service returned a quote too close to expiry for safe submission');
|
|
19432
|
+
}
|
|
19433
|
+
return quote;
|
|
19434
|
+
}
|
|
19435
|
+
async function validateBoundQuote(binding, signedQuote, refundAddress) {
|
|
19436
|
+
return validateQuote({
|
|
19437
|
+
sourceDomain: binding.sourceDomain,
|
|
19438
|
+
abiSignature: BURN_WITH_FEES_ABI_SIGNATURE,
|
|
19439
|
+
args: [
|
|
19440
|
+
binding.amount,
|
|
19441
|
+
String(binding.destinationDomain),
|
|
19442
|
+
padAddressToBytes32(binding.mintRecipient),
|
|
19443
|
+
binding.feeToken,
|
|
19444
|
+
padAddressToBytes32(binding.destinationCaller),
|
|
19445
|
+
binding.hookData,
|
|
19446
|
+
[
|
|
19447
|
+
signedQuote,
|
|
19448
|
+
refundAddress
|
|
19449
|
+
]
|
|
19450
|
+
],
|
|
19451
|
+
isTestnet: binding.isTestnet
|
|
19452
|
+
});
|
|
19453
|
+
}
|
|
19454
|
+
function isValidationSafe(binding, signedQuote, validation, expectedQuote) {
|
|
19455
|
+
return validation.claimable && !validation.expiry.expired && validation.expiry.secondsRemaining > QUOTE_EXPIRY_SAFETY_SECONDS && validation.signedQuote.toLowerCase() === signedQuote.toLowerCase() && validation.feeToken.toLowerCase() === binding.feeToken.toLowerCase() && (expectedQuote === undefined || validation.feeTotalAmount === expectedQuote.feeTotalAmount && validation.nonce === expectedQuote.nonce);
|
|
19456
|
+
}
|
|
19457
|
+
function unsafeQuoteError(validation) {
|
|
19458
|
+
const detail = validation.failedChecks.length > 0 ? ` (${validation.failedChecks.join(', ')})` : '';
|
|
19459
|
+
return createValidationFailedError$1('quote', undefined, `The fee quote is not safe for submission${detail}; ` + 'call estimate again to obtain a valid quote');
|
|
19460
|
+
}
|
|
19461
|
+
function toExecutionFeeQuote(quote) {
|
|
19462
|
+
return {
|
|
19463
|
+
signedQuote: quote.signedQuote,
|
|
19464
|
+
feeToken: quote.feeToken,
|
|
19465
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19466
|
+
};
|
|
19467
|
+
}
|
|
19468
|
+
function toFeeItems(quote) {
|
|
19469
|
+
return quote.items.map((item)=>({
|
|
19470
|
+
type: item.type,
|
|
19471
|
+
amount: formatUnits(item.amount, 6),
|
|
19472
|
+
args: item.args,
|
|
19473
|
+
argsHash: item.argsHash
|
|
19474
|
+
}));
|
|
19475
|
+
}
|
|
19476
|
+
/**
|
|
19477
|
+
* Estimate a source-fee bridge using a source-denominated signed fee quote.
|
|
19478
|
+
*
|
|
19479
|
+
* @internal
|
|
19480
|
+
*/ async function estimateSourceFeeBridge(params) {
|
|
19481
|
+
const binding = buildQuoteBinding(params);
|
|
19482
|
+
const quote = await fetchSubmissionQuote(binding);
|
|
19483
|
+
const feeTotal = formatUnits(quote.feeTotalAmount, 6);
|
|
19484
|
+
return {
|
|
19485
|
+
token: 'USDC',
|
|
19486
|
+
amount: formatUnits(params.amount, 6),
|
|
19487
|
+
source: {
|
|
19488
|
+
address: params.source.address,
|
|
19489
|
+
chain: params.source.chain.chain
|
|
19490
|
+
},
|
|
19491
|
+
destination: {
|
|
19492
|
+
address: params.destination.address,
|
|
19493
|
+
chain: params.destination.chain.chain,
|
|
19494
|
+
...params.destination.recipientAddress !== undefined && {
|
|
19495
|
+
recipientAddress: params.destination.recipientAddress
|
|
19496
|
+
}
|
|
19497
|
+
},
|
|
19498
|
+
gasFees: [],
|
|
19499
|
+
fees: quote.items.map((item)=>({
|
|
19500
|
+
type: item.type === 'FORWARD' ? 'forwarder' : 'provider',
|
|
19501
|
+
token: 'USDC',
|
|
19502
|
+
amount: formatUnits(item.amount, 6)
|
|
19503
|
+
})),
|
|
19504
|
+
amountReceived: formatUnits(params.amount, 6),
|
|
19505
|
+
feeTotal,
|
|
19506
|
+
feeItems: toFeeItems(quote),
|
|
19507
|
+
totalDebit: formatUnits((BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString(), 6),
|
|
19508
|
+
quoteExpiry: quote.expiry,
|
|
19509
|
+
quote: quote.signedQuote
|
|
19510
|
+
};
|
|
19511
|
+
}
|
|
19512
|
+
async function readAllowance(params, delegate) {
|
|
19513
|
+
const operationContext = {
|
|
19514
|
+
chain: params.source.chain,
|
|
19515
|
+
address: params.source.address
|
|
19516
|
+
};
|
|
19517
|
+
const prepared = await params.source.adapter.prepareAction('usdc.allowance', {
|
|
19518
|
+
walletAddress: params.source.address,
|
|
19519
|
+
delegate
|
|
19520
|
+
}, operationContext);
|
|
19521
|
+
return BigInt(String(await prepared.execute()));
|
|
19522
|
+
}
|
|
19523
|
+
async function executeAndConfirm(request, params, provider) {
|
|
19524
|
+
const txHash = await request.execute();
|
|
19525
|
+
const data = await provider.waitForTransaction(params.source.adapter, txHash, params.source.chain);
|
|
19526
|
+
return {
|
|
19527
|
+
txHash,
|
|
19528
|
+
data
|
|
19529
|
+
};
|
|
19530
|
+
}
|
|
19531
|
+
async function prepareAndPreflight(params, binding, quote, provider) {
|
|
19532
|
+
assertSourceFeeRoute(params);
|
|
19533
|
+
const totalDebit = (BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString();
|
|
19534
|
+
const operationContext = {
|
|
19535
|
+
chain: params.source.chain,
|
|
19536
|
+
address: params.source.address
|
|
19537
|
+
};
|
|
19538
|
+
await validateBalanceForTransaction({
|
|
19539
|
+
adapter: params.source.adapter,
|
|
19540
|
+
amount: totalDebit,
|
|
19541
|
+
token: 'USDC',
|
|
19542
|
+
tokenAddress: params.source.chain.usdcAddress,
|
|
19543
|
+
operationContext
|
|
19544
|
+
});
|
|
19545
|
+
const prepared = await provider.burnWithFees({
|
|
19546
|
+
source: params.source,
|
|
19547
|
+
destinationChain: params.destination.chain,
|
|
19548
|
+
amount: params.amount,
|
|
19549
|
+
mintRecipient: binding.mintRecipient,
|
|
19550
|
+
destinationCaller: binding.destinationCaller,
|
|
19551
|
+
hookData: binding.hookData,
|
|
19552
|
+
claim: {
|
|
19553
|
+
signedQuote: quote.signedQuote,
|
|
19554
|
+
refundAddress: params.source.address
|
|
19555
|
+
},
|
|
19556
|
+
feeToken: quote.feeToken,
|
|
19557
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19558
|
+
});
|
|
19559
|
+
const wrapper = resolveCCTPV2ContractAddress(params.source.chain, 'tokenMessengerWithFees');
|
|
19560
|
+
const allowance = await readAllowance(params, wrapper);
|
|
19561
|
+
if (allowance >= BigInt(totalDebit)) {
|
|
19562
|
+
return {
|
|
19563
|
+
prepared,
|
|
19564
|
+
approveStep: {
|
|
19565
|
+
name: 'approve',
|
|
19566
|
+
state: 'noop'
|
|
19567
|
+
}
|
|
16770
19568
|
};
|
|
16771
19569
|
}
|
|
16772
|
-
|
|
16773
|
-
|
|
16774
|
-
|
|
16775
|
-
* This method should block until the transaction is confirmed on the blockchain.
|
|
16776
|
-
*
|
|
16777
|
-
* @param adapter - The adapter to use for transaction waiting
|
|
16778
|
-
* @param txHash - The hash of the transaction to wait for
|
|
16779
|
-
* @param chain - The chain definition where the transaction was executed
|
|
16780
|
-
* @param config - Optional configuration for transaction waiting (confirmations, timeout)
|
|
16781
|
-
* @returns The hash of the confirmed transaction
|
|
16782
|
-
* @example
|
|
16783
|
-
* ```typescript
|
|
16784
|
-
* const provider = new CCTPV2BridgingProvider()
|
|
16785
|
-
* const txHash = await provider.waitForTransaction(
|
|
16786
|
-
* adapter,
|
|
16787
|
-
* '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
16788
|
-
* Ethereum,
|
|
16789
|
-
* )
|
|
16790
|
-
* console.log('Transaction confirmed:', txHash)
|
|
16791
|
-
* ```
|
|
16792
|
-
*/ async waitForTransaction(adapter, txHash, chain, config) {
|
|
16793
|
-
return adapter.waitForTransaction(txHash, config, chain);
|
|
19570
|
+
let lastApproval;
|
|
19571
|
+
for (const approval of prepared.approvals){
|
|
19572
|
+
lastApproval = await executeAndConfirm(approval, params, provider);
|
|
16794
19573
|
}
|
|
16795
|
-
}
|
|
16796
|
-
|
|
16797
|
-
/**
|
|
16798
|
-
* The default providers that will be used in addition to the providers provided
|
|
16799
|
-
* to the BridgeKit constructor.
|
|
16800
|
-
*
|
|
16801
|
-
* @param config - Optional configuration forwarded to the default providers
|
|
16802
|
-
* @returns The default bridging providers
|
|
16803
|
-
*/ const getDefaultProviders = (config = {})=>[
|
|
16804
|
-
new CCTPV2BridgingProvider(config.headers ? {
|
|
16805
|
-
headers: config.headers
|
|
16806
|
-
} : {})
|
|
16807
|
-
];
|
|
16808
|
-
|
|
16809
|
-
/**
|
|
16810
|
-
* A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
|
|
16811
|
-
* @param formatDirection - The direction to format the amount in.
|
|
16812
|
-
* @returns A function that transforms an amount into a human-readable string or a bigint string.
|
|
16813
|
-
*/ const getAmountTransformer = (formatDirection)=>formatDirection === 'to-human-readable' ? (params)=>formatAmount(params) : (params)=>parseAmount(params).toString();
|
|
16814
|
-
/**
|
|
16815
|
-
* Format the bridge result into human-readable string values for the user or bigint string values for internal use.
|
|
16816
|
-
*
|
|
16817
|
-
* @typeParam T - The specific result type (must extend BridgeResult or EstimateResult). Preserves the exact type passed in.
|
|
16818
|
-
* @param result - The bridge result to format.
|
|
16819
|
-
* @param formatDirection - The direction to format the result in.
|
|
16820
|
-
* - If 'to-human-readable', the result will be converted to human-readable string values.
|
|
16821
|
-
* - If 'to-internal', the result will be converted to bigint string values (usually for internal use).
|
|
16822
|
-
* @returns The formatted bridge result.
|
|
16823
|
-
*
|
|
16824
|
-
* @example
|
|
16825
|
-
* ```typescript
|
|
16826
|
-
* const result = await kit.bridge({
|
|
16827
|
-
* amount: '1000000',
|
|
16828
|
-
* token: 'USDC',
|
|
16829
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
16830
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
16831
|
-
* })
|
|
16832
|
-
*
|
|
16833
|
-
* // Format the bridge result into human-readable string values for the user
|
|
16834
|
-
* const formattedResultHumanReadable = formatBridgeResult(result, 'to-human-readable')
|
|
16835
|
-
* console.log(formattedResultHumanReadable)
|
|
16836
|
-
*
|
|
16837
|
-
* // Format the bridge result into bigint string values for internal use
|
|
16838
|
-
* const formattedResultInternal = formatBridgeResult(result, 'to-internal')
|
|
16839
|
-
* console.log(formattedResultInternal)
|
|
16840
|
-
* ```
|
|
16841
|
-
*/ const formatBridgeResult = (result, formatDirection)=>{
|
|
16842
|
-
const transform = getAmountTransformer(formatDirection);
|
|
16843
19574
|
return {
|
|
16844
|
-
|
|
16845
|
-
|
|
16846
|
-
|
|
16847
|
-
|
|
16848
|
-
|
|
16849
|
-
|
|
16850
|
-
|
|
16851
|
-
|
|
16852
|
-
...result.config.maxFee && {
|
|
16853
|
-
maxFee: transform({
|
|
16854
|
-
value: result.config.maxFee,
|
|
16855
|
-
token: result.token
|
|
16856
|
-
})
|
|
16857
|
-
},
|
|
16858
|
-
...result.config.customFee && {
|
|
16859
|
-
customFee: {
|
|
16860
|
-
...result.config.customFee,
|
|
16861
|
-
...result.config.customFee.value && {
|
|
16862
|
-
value: transform({
|
|
16863
|
-
value: result.config.customFee.value,
|
|
16864
|
-
token: result.token
|
|
16865
|
-
})
|
|
16866
|
-
}
|
|
16867
|
-
}
|
|
16868
|
-
}
|
|
19575
|
+
prepared,
|
|
19576
|
+
approveStep: {
|
|
19577
|
+
name: 'approve',
|
|
19578
|
+
state: 'success',
|
|
19579
|
+
data: lastApproval?.data,
|
|
19580
|
+
...lastApproval?.txHash !== undefined && {
|
|
19581
|
+
txHash: lastApproval.txHash,
|
|
19582
|
+
explorerUrl: buildExplorerUrl(params.source.chain, lastApproval.txHash)
|
|
16869
19583
|
}
|
|
16870
19584
|
}
|
|
16871
19585
|
};
|
|
16872
|
-
}
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
};
|
|
19586
|
+
}
|
|
19587
|
+
async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
19588
|
+
if (suppliedQuote === undefined) {
|
|
19589
|
+
const fetchedQuote = await fetchSubmissionQuote(binding);
|
|
19590
|
+
return {
|
|
19591
|
+
quote: toExecutionFeeQuote(fetchedQuote),
|
|
19592
|
+
fetchedQuote
|
|
19593
|
+
};
|
|
19594
|
+
}
|
|
19595
|
+
const validation = await validateBoundQuote(binding, suppliedQuote, refundAddress);
|
|
19596
|
+
if (!isValidationSafe(binding, suppliedQuote, validation)) {
|
|
19597
|
+
throw unsafeQuoteError(validation);
|
|
19598
|
+
}
|
|
19599
|
+
return {
|
|
19600
|
+
quote: toExecutionFeeQuote(validation),
|
|
19601
|
+
fetchedQuote: undefined
|
|
19602
|
+
};
|
|
19603
|
+
}
|
|
16889
19604
|
/**
|
|
16890
|
-
*
|
|
16891
|
-
*
|
|
16892
|
-
* @remarks
|
|
16893
|
-
* The order matches the CCTP v2 bridge execution sequence. During
|
|
16894
|
-
* `bridge()`, completed step events are counted so the failing step
|
|
16895
|
-
* can be identified by its index.
|
|
19605
|
+
* Execute a source-fee bridge while preserving receive-exact semantics.
|
|
16896
19606
|
*
|
|
16897
19607
|
* @internal
|
|
16898
|
-
*/
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
19608
|
+
*/ async function executeSourceFeeBridge(rawParams, params, provider) {
|
|
19609
|
+
// Narrows `params` to the CCTP v2 route type for the rest of this function.
|
|
19610
|
+
// buildQuoteBinding asserts too, but that narrows its own scope, not this one.
|
|
19611
|
+
assertSourceFeeRoute(params);
|
|
19612
|
+
const binding = buildQuoteBinding(params);
|
|
19613
|
+
const suppliedQuote = rawParams.quote;
|
|
19614
|
+
let { quote, fetchedQuote } = await resolveExecutionQuote(binding, suppliedQuote, params.source.address);
|
|
19615
|
+
let { prepared, approveStep } = await prepareAndPreflight(params, binding, quote, provider);
|
|
19616
|
+
// Validate against the current source-chain tip after approval confirmation.
|
|
19617
|
+
// BLOCK_NUMBER expiries cannot be checked safely with wall-clock time alone.
|
|
19618
|
+
let validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
19619
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
19620
|
+
if (suppliedQuote === undefined) {
|
|
19621
|
+
fetchedQuote = await fetchSubmissionQuote(binding);
|
|
19622
|
+
quote = toExecutionFeeQuote(fetchedQuote);
|
|
19623
|
+
const refreshedPreparation = await prepareAndPreflight(params, binding, quote, provider);
|
|
19624
|
+
prepared = refreshedPreparation.prepared;
|
|
19625
|
+
if (refreshedPreparation.approveStep.state !== 'noop') {
|
|
19626
|
+
approveStep = refreshedPreparation.approveStep;
|
|
19627
|
+
}
|
|
19628
|
+
validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
19629
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
19630
|
+
throw unsafeQuoteError(validation);
|
|
19631
|
+
}
|
|
19632
|
+
} else {
|
|
19633
|
+
throw unsafeQuoteError(validation);
|
|
19634
|
+
}
|
|
19635
|
+
}
|
|
19636
|
+
// Surface the same step events as the standard bridge() path so
|
|
19637
|
+
// kit.on('approve'|'burn'|'mint', ...) handlers fire for source-fee bridges.
|
|
19638
|
+
if (approveStep.state !== 'noop') {
|
|
19639
|
+
provider.emitBridgeStep('approve', approveStep);
|
|
19640
|
+
}
|
|
19641
|
+
const burn = await executeAndConfirm(prepared.burn, params, provider);
|
|
19642
|
+
const burnStep = {
|
|
19643
|
+
name: 'burn',
|
|
19644
|
+
state: 'success',
|
|
19645
|
+
txHash: burn.txHash,
|
|
19646
|
+
data: burn.data,
|
|
19647
|
+
explorerUrl: buildExplorerUrl(params.source.chain, burn.txHash)
|
|
19648
|
+
};
|
|
19649
|
+
provider.emitBridgeStep('burn', burnStep);
|
|
19650
|
+
const resultBase = {
|
|
19651
|
+
amount: params.amount,
|
|
19652
|
+
token: 'USDC',
|
|
19653
|
+
config: params.config,
|
|
19654
|
+
provider: provider.name,
|
|
19655
|
+
source: {
|
|
19656
|
+
address: params.source.address,
|
|
19657
|
+
chain: params.source.chain
|
|
19658
|
+
},
|
|
19659
|
+
destination: {
|
|
19660
|
+
address: params.destination.address,
|
|
19661
|
+
chain: params.destination.chain,
|
|
19662
|
+
...params.destination.recipientAddress !== undefined && {
|
|
19663
|
+
recipientAddress: params.destination.recipientAddress
|
|
19664
|
+
},
|
|
19665
|
+
useForwarder: true
|
|
19666
|
+
}
|
|
19667
|
+
};
|
|
19668
|
+
const attestation = await provider.fetchRelayerMint(params.source, burn.txHash);
|
|
19669
|
+
const forwardTxHash = attestation.forwardTxHash;
|
|
19670
|
+
// The burn already moved funds. If the relayer confirms without a
|
|
19671
|
+
// destination hash, surface an error-state result that preserves the burn
|
|
19672
|
+
// step (so `retry()` can resume the mint) instead of throwing and discarding
|
|
19673
|
+
// the completed burn.
|
|
19674
|
+
if (typeof forwardTxHash !== 'string' || forwardTxHash.trim() === '') {
|
|
19675
|
+
const mintStep = {
|
|
19676
|
+
name: 'mint',
|
|
19677
|
+
state: 'error',
|
|
19678
|
+
forwarded: true,
|
|
19679
|
+
errorCategory: 'failed_offchain',
|
|
19680
|
+
errorMessage: 'Relayer confirmation did not include a destination transaction hash'
|
|
19681
|
+
};
|
|
19682
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
19683
|
+
return {
|
|
19684
|
+
...resultBase,
|
|
19685
|
+
state: 'error',
|
|
19686
|
+
steps: [
|
|
19687
|
+
approveStep,
|
|
19688
|
+
burnStep,
|
|
19689
|
+
mintStep
|
|
19690
|
+
]
|
|
19691
|
+
};
|
|
19692
|
+
}
|
|
19693
|
+
const mintStep = {
|
|
19694
|
+
name: 'mint',
|
|
19695
|
+
state: 'success',
|
|
19696
|
+
forwarded: true,
|
|
19697
|
+
txHash: forwardTxHash,
|
|
19698
|
+
explorerUrl: buildExplorerUrl(params.destination.chain, forwardTxHash)
|
|
19699
|
+
};
|
|
19700
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
19701
|
+
return {
|
|
19702
|
+
...resultBase,
|
|
19703
|
+
state: 'success',
|
|
19704
|
+
steps: [
|
|
19705
|
+
approveStep,
|
|
19706
|
+
burnStep,
|
|
19707
|
+
mintStep
|
|
19708
|
+
]
|
|
19709
|
+
};
|
|
19710
|
+
}
|
|
16916
19711
|
|
|
16917
19712
|
/** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg$3.name);
|
|
16918
19713
|
/**
|
|
@@ -17141,11 +19936,18 @@ function assertCCTPV2Config(config) {
|
|
|
17141
19936
|
this.validateNetworkCompatibility(resolvedParams);
|
|
17142
19937
|
// Merge the custom fee config into the resolved params
|
|
17143
19938
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
17144
|
-
|
|
17145
|
-
|
|
17146
|
-
//
|
|
17147
|
-
|
|
17148
|
-
|
|
19939
|
+
let result;
|
|
19940
|
+
// Execute the explicit source-fee path without changing legacy
|
|
19941
|
+
// useForwarder behavior for callers that did not opt in.
|
|
19942
|
+
if (params.config?.feePayment === 'source') {
|
|
19943
|
+
const sourceFeeProvider = this.findSourceFeeProvider(finalResolvedParams);
|
|
19944
|
+
result = formatBridgeResult(await executeSourceFeeBridge(params, finalResolvedParams, sourceFeeProvider), 'to-human-readable');
|
|
19945
|
+
} else {
|
|
19946
|
+
// Find a provider that supports this route
|
|
19947
|
+
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
19948
|
+
// Execute the transfer using the provider and format the result.
|
|
19949
|
+
result = formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
|
|
19950
|
+
}
|
|
17149
19951
|
// Emit error telemetry when the provider returns an error state
|
|
17150
19952
|
// (provider records step failures in the result instead of throwing).
|
|
17151
19953
|
if (result.state === 'error') {
|
|
@@ -17268,42 +20070,7 @@ function assertCCTPV2Config(config) {
|
|
|
17268
20070
|
tokenIn: result.token
|
|
17269
20071
|
});
|
|
17270
20072
|
}
|
|
17271
|
-
|
|
17272
|
-
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
17273
|
-
*
|
|
17274
|
-
* This method calculates the expected gas fees and protocol costs for bridging
|
|
17275
|
-
* without actually executing the transaction. It performs the same validation
|
|
17276
|
-
* as the bridge method but stops before execution.
|
|
17277
|
-
*
|
|
17278
|
-
* @param params - The bridge parameters for cost estimation, including optional invocation metadata
|
|
17279
|
-
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
17280
|
-
* @throws {KitError} When the parameters are invalid.
|
|
17281
|
-
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
17282
|
-
*
|
|
17283
|
-
* @example
|
|
17284
|
-
* ```typescript
|
|
17285
|
-
* // Basic usage
|
|
17286
|
-
* const estimate = await kit.estimate({
|
|
17287
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
17288
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
17289
|
-
* amount: '10.50',
|
|
17290
|
-
* token: 'USDC'
|
|
17291
|
-
* })
|
|
17292
|
-
* console.log('Estimated cost:', estimate.totalCost)
|
|
17293
|
-
*
|
|
17294
|
-
* // With custom invocation metadata
|
|
17295
|
-
* const estimate = await kit.estimate({
|
|
17296
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
17297
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
17298
|
-
* amount: '10.50',
|
|
17299
|
-
* token: 'USDC',
|
|
17300
|
-
* invocationMeta: {
|
|
17301
|
-
* traceId: 'custom-trace-id',
|
|
17302
|
-
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
17303
|
-
* },
|
|
17304
|
-
* })
|
|
17305
|
-
* ```
|
|
17306
|
-
*/ async estimate(params) {
|
|
20073
|
+
async estimate(params) {
|
|
17307
20074
|
return withErrorTelemetry(async ()=>{
|
|
17308
20075
|
// First validate the parameters
|
|
17309
20076
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
@@ -17313,6 +20080,10 @@ function assertCCTPV2Config(config) {
|
|
|
17313
20080
|
this.validateNetworkCompatibility(resolvedParams);
|
|
17314
20081
|
// Merge the custom fee config into the resolved params
|
|
17315
20082
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
20083
|
+
if (params.config?.feePayment === 'source') {
|
|
20084
|
+
this.findSourceFeeProvider(finalResolvedParams);
|
|
20085
|
+
return estimateSourceFeeBridge(finalResolvedParams);
|
|
20086
|
+
}
|
|
17316
20087
|
// Find a provider that supports this route
|
|
17317
20088
|
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
17318
20089
|
// Estimate the transfer using the provider and format amounts to human-readable strings
|
|
@@ -17361,6 +20132,9 @@ function assertCCTPV2Config(config) {
|
|
|
17361
20132
|
* // Get only chains that support forwarding
|
|
17362
20133
|
* const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
|
|
17363
20134
|
*
|
|
20135
|
+
* // Get only chains that can pay fees on the source chain (receive-exact)
|
|
20136
|
+
* const sourceFeeChains = kit.getSupportedChains({ sourceFeeSupported: true })
|
|
20137
|
+
*
|
|
17364
20138
|
* console.log('Supported chains:')
|
|
17365
20139
|
* allChains.forEach(chain => {
|
|
17366
20140
|
* console.log(`- ${chain.name} (${chain.type})`)
|
|
@@ -17409,6 +20183,10 @@ function assertCCTPV2Config(config) {
|
|
|
17409
20183
|
return options.forwarderSupported ? fs.source || fs.destination : !fs.source && !fs.destination;
|
|
17410
20184
|
});
|
|
17411
20185
|
}
|
|
20186
|
+
// Apply source-paid ("receive-exact") fee support filter if provided
|
|
20187
|
+
if (options?.sourceFeeSupported !== undefined) {
|
|
20188
|
+
chains = chains.filter((chain)=>hasSourceFeeSupport(chain) === options.sourceFeeSupported);
|
|
20189
|
+
}
|
|
17412
20190
|
return chains;
|
|
17413
20191
|
}
|
|
17414
20192
|
/**
|
|
@@ -17443,6 +20221,20 @@ function assertCCTPV2Config(config) {
|
|
|
17443
20221
|
return provider;
|
|
17444
20222
|
}
|
|
17445
20223
|
/**
|
|
20224
|
+
* Find the default CCTP v2 provider for a source-fee forwarding route.
|
|
20225
|
+
*
|
|
20226
|
+
* @param params - The resolved provider parameters.
|
|
20227
|
+
* @returns The CCTP v2 provider that supports the forwarded route.
|
|
20228
|
+
* @throws {UnsupportedRouteError} When no source-fee provider supports the route.
|
|
20229
|
+
* @internal
|
|
20230
|
+
*/ findSourceFeeProvider(params) {
|
|
20231
|
+
const provider = this.providers.find((candidate)=>candidate instanceof CCTPV2BridgingProvider && candidate.supportsRoute(params.source.chain, params.destination.chain, params.token, true));
|
|
20232
|
+
if (!(provider instanceof CCTPV2BridgingProvider)) {
|
|
20233
|
+
throw createUnsupportedRouteError(params.source.chain.name, params.destination.chain.name);
|
|
20234
|
+
}
|
|
20235
|
+
return provider;
|
|
20236
|
+
}
|
|
20237
|
+
/**
|
|
17446
20238
|
* Merge custom fee configuration into provider parameters.
|
|
17447
20239
|
*
|
|
17448
20240
|
* Prioritizes any custom fee configuration already present on the
|
|
@@ -17651,7 +20443,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
17651
20443
|
};
|
|
17652
20444
|
|
|
17653
20445
|
var name$1 = "@circle-fin/swap-kit";
|
|
17654
|
-
var version$1 = "1.
|
|
20446
|
+
var version$1 = "1.6.1";
|
|
17655
20447
|
var pkg$1 = {
|
|
17656
20448
|
name: name$1,
|
|
17657
20449
|
version: version$1};
|
|
@@ -17665,7 +20457,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17665
20457
|
* Catches obviously malformed addresses at parse time; chain-specific validation
|
|
17666
20458
|
* is performed in buildServiceParams.
|
|
17667
20459
|
*/ const destinationAddressSchema = z.union([
|
|
17668
|
-
evmAddressSchema,
|
|
20460
|
+
evmAddressSchema$1,
|
|
17669
20461
|
solanaAddressSchema
|
|
17670
20462
|
]);
|
|
17671
20463
|
/**
|
|
@@ -17711,9 +20503,16 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17711
20503
|
message: 'stopLimit must be greater than 0'
|
|
17712
20504
|
}).optional(),
|
|
17713
20505
|
customFee: serviceSwapCustomFeeSchema.optional(),
|
|
20506
|
+
apiKey: z.string({
|
|
20507
|
+
invalid_type_error: 'apiKey must be a string'
|
|
20508
|
+
})// Tolerate '' so the `process.env.CIRCLE_API_KEY ?? ''` idiom falls back to
|
|
20509
|
+
// kitKey via resolveApiKey instead of being rejected here.
|
|
20510
|
+
.optional(),
|
|
17714
20511
|
kitKey: z.string({
|
|
17715
20512
|
invalid_type_error: 'kitKey must be a string'
|
|
17716
|
-
})
|
|
20513
|
+
})// Tolerate '' so an unset kit-key env var yields the permissionless path,
|
|
20514
|
+
// matching buildServiceParams (which already omits an empty credential).
|
|
20515
|
+
.optional(),
|
|
17717
20516
|
provider: z.string({
|
|
17718
20517
|
invalid_type_error: 'provider must be a string'
|
|
17719
20518
|
}).min(1, 'provider must be a non-empty string').optional(),
|
|
@@ -17886,6 +20685,41 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17886
20685
|
*
|
|
17887
20686
|
* @internal
|
|
17888
20687
|
*/ const MAX_RATE_ADDRESSES_PER_REQUEST = 100;
|
|
20688
|
+
/**
|
|
20689
|
+
* Environment prefixes carried by Circle platform API keys.
|
|
20690
|
+
*
|
|
20691
|
+
* A Circle API key is `<ENV>_API_KEY:<keyId>:<keySecret>`, where `<ENV>` is one
|
|
20692
|
+
* of these prefixes.
|
|
20693
|
+
*
|
|
20694
|
+
* @internal
|
|
20695
|
+
*/ const API_KEY_ENV_PREFIXES = [
|
|
20696
|
+
'TEST',
|
|
20697
|
+
'LIVE',
|
|
20698
|
+
'SAND',
|
|
20699
|
+
'SANDBOX',
|
|
20700
|
+
'SMOK',
|
|
20701
|
+
'PROD',
|
|
20702
|
+
'STAG',
|
|
20703
|
+
'DEV'
|
|
20704
|
+
];
|
|
20705
|
+
/**
|
|
20706
|
+
* Accepted credential formats for Stablecoin Service authentication.
|
|
20707
|
+
*
|
|
20708
|
+
* Matches a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
20709
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`). API keys are the
|
|
20710
|
+
* recommended credential; kit keys remain accepted as the legacy path.
|
|
20711
|
+
*
|
|
20712
|
+
* @remarks
|
|
20713
|
+
* This is a local pre-flight check, not the authority — the Stablecoin Service
|
|
20714
|
+
* validates the credential and answers 401 when it rejects one. The prefix list
|
|
20715
|
+
* is therefore deliberately permissive: a valid key carrying a prefix this SDK
|
|
20716
|
+
* has not been taught about should reach the service and be judged there rather
|
|
20717
|
+
* than be refused locally, since refusing locally is indistinguishable from an
|
|
20718
|
+
* outage to the caller. Kept as the single source of truth so the pattern is
|
|
20719
|
+
* not restated per call site.
|
|
20720
|
+
*
|
|
20721
|
+
* @internal
|
|
20722
|
+
*/ const API_KEY_PATTERN = new RegExp(`^(?:KIT_KEY|(?:${API_KEY_ENV_PREFIXES.join('|')})_API_KEY)` + ':[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$');
|
|
17889
20723
|
|
|
17890
20724
|
/**
|
|
17891
20725
|
* Zod schema for validating stop limits.
|
|
@@ -17954,13 +20788,14 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17954
20788
|
/**
|
|
17955
20789
|
* Zod schema for validating API keys.
|
|
17956
20790
|
*
|
|
17957
|
-
*
|
|
20791
|
+
* Accepts a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
20792
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`).
|
|
17958
20793
|
*
|
|
17959
20794
|
* @example
|
|
17960
20795
|
* ```typescript
|
|
17961
20796
|
* import { apiKeySchema } from '@core/service-client'
|
|
17962
20797
|
*
|
|
17963
|
-
* const result = apiKeySchema.safeParse('
|
|
20798
|
+
* const result = apiKeySchema.safeParse('TEST_API_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
17964
20799
|
* if (!result.success) {
|
|
17965
20800
|
* console.error('Invalid API key format:', result.error.issues)
|
|
17966
20801
|
* }
|
|
@@ -17968,7 +20803,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17968
20803
|
*/ const apiKeySchema = z.string({
|
|
17969
20804
|
required_error: 'API key is required',
|
|
17970
20805
|
invalid_type_error: 'Invalid API key format'
|
|
17971
|
-
}).regex(
|
|
20806
|
+
}).regex(API_KEY_PATTERN, 'Invalid API key format');
|
|
17972
20807
|
/**
|
|
17973
20808
|
* Zod schema for platform fees configuration.
|
|
17974
20809
|
*
|
|
@@ -18407,6 +21242,40 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
18407
21242
|
transaction: createSwapTransactionSchema
|
|
18408
21243
|
});
|
|
18409
21244
|
|
|
21245
|
+
/**
|
|
21246
|
+
* Resolve the credential to authenticate a Stablecoin Service request with.
|
|
21247
|
+
*
|
|
21248
|
+
* `apiKey` is the supported field; `kitKey` is the deprecated alias kept for
|
|
21249
|
+
* existing integrations. When both are supplied `apiKey` wins, so a caller
|
|
21250
|
+
* migrating field-by-field cannot be silently pinned to a stale credential.
|
|
21251
|
+
*
|
|
21252
|
+
* An empty-string value is treated as absent on either field. Without this, the
|
|
21253
|
+
* common `process.env.CIRCLE_API_KEY ?? ''` idiom (which yields `''` when the
|
|
21254
|
+
* variable is unset) would either shadow a working `kitKey` or, on a bare
|
|
21255
|
+
* `kitKey: ''`, reach downstream validation as an invalid credential instead of
|
|
21256
|
+
* falling through to the permissionless path.
|
|
21257
|
+
*
|
|
21258
|
+
* @param source - Object carrying either credential field, or neither.
|
|
21259
|
+
* @returns The credential to use, or `undefined` for the permissionless
|
|
21260
|
+
* (keyless) path.
|
|
21261
|
+
*
|
|
21262
|
+
* @example
|
|
21263
|
+
* ```typescript
|
|
21264
|
+
* import { resolveApiKey } from '@core/service-client'
|
|
21265
|
+
*
|
|
21266
|
+
* resolveApiKey({ apiKey: 'TEST_API_KEY:id:secret' }) // 'TEST_API_KEY:id:secret'
|
|
21267
|
+
* resolveApiKey({ kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
21268
|
+
* resolveApiKey({ apiKey: '', kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
21269
|
+
* resolveApiKey({ kitKey: '' }) // undefined
|
|
21270
|
+
* resolveApiKey({}) // undefined
|
|
21271
|
+
* ```
|
|
21272
|
+
*/ const resolveApiKey = (source)=>{
|
|
21273
|
+
// Treat an empty-string value as absent on either field so the
|
|
21274
|
+
// `env ?? ''` idiom falls through to the next credential (or permissionless).
|
|
21275
|
+
const normalize = (value)=>value !== undefined && value !== '' ? value : undefined;
|
|
21276
|
+
return normalize(source.apiKey) ?? normalize(source.kitKey);
|
|
21277
|
+
};
|
|
21278
|
+
|
|
18410
21279
|
/**
|
|
18411
21280
|
* Zod schema for validating EVM adapter capabilities.
|
|
18412
21281
|
*
|
|
@@ -18836,7 +21705,7 @@ const abiParameterSchema = z.object({
|
|
|
18836
21705
|
*/ z.object({
|
|
18837
21706
|
type: z.literal('evm'),
|
|
18838
21707
|
abi: abiSchema,
|
|
18839
|
-
address: evmAddressSchema,
|
|
21708
|
+
address: evmAddressSchema$1,
|
|
18840
21709
|
functionName: z.string({
|
|
18841
21710
|
required_error: 'Function name is required',
|
|
18842
21711
|
invalid_type_error: 'Function name must be a string'
|
|
@@ -18873,7 +21742,7 @@ const abiParameterSchema = z.object({
|
|
|
18873
21742
|
* }
|
|
18874
21743
|
* ```
|
|
18875
21744
|
*/ z.object({
|
|
18876
|
-
address: evmAddressSchema,
|
|
21745
|
+
address: evmAddressSchema$1,
|
|
18877
21746
|
value: z.bigint({
|
|
18878
21747
|
required_error: 'Value is required for native transfers',
|
|
18879
21748
|
invalid_type_error: 'Value must be a bigint'
|
|
@@ -18959,7 +21828,7 @@ z.object({
|
|
|
18959
21828
|
signature: evmSignatureSchema,
|
|
18960
21829
|
tokenInputs: z.array(z.object({
|
|
18961
21830
|
permitType: z.nativeEnum(PermitType),
|
|
18962
|
-
token: evmAddressSchema,
|
|
21831
|
+
token: evmAddressSchema$1,
|
|
18963
21832
|
amount: z.bigint().refine((value)=>value >= 0n, {
|
|
18964
21833
|
message: 'amount must be a non-negative bigint'
|
|
18965
21834
|
}),
|
|
@@ -19379,7 +22248,7 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
19379
22248
|
/**
|
|
19380
22249
|
* Fee recipient address (required).
|
|
19381
22250
|
* Must be a valid EVM address or Solana address.
|
|
19382
|
-
*/ recipientAddress: z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
22251
|
+
*/ recipientAddress: z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
19383
22252
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
19384
22253
|
})
|
|
19385
22254
|
}).strict();
|
|
@@ -19391,7 +22260,8 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
19391
22260
|
* - slippageBps: Optional positive number for slippage tolerance
|
|
19392
22261
|
* - stopLimit: Optional decimal string for minimum output
|
|
19393
22262
|
* - customFee: Optional fee configuration
|
|
19394
|
-
* -
|
|
22263
|
+
* - apiKey: Optional credential string
|
|
22264
|
+
* - kitKey: Optional credential string (deprecated alias for apiKey)
|
|
19395
22265
|
*/ const swapConfigSchema = z.object({
|
|
19396
22266
|
allowanceStrategy: allowanceStrategySchema.optional(),
|
|
19397
22267
|
slippageBps: z.number().int().min(0).optional(),
|
|
@@ -19401,11 +22271,12 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
19401
22271
|
attributeName: 'stopLimit'
|
|
19402
22272
|
})(z.string())).optional(),
|
|
19403
22273
|
customFee: swapCustomFeeSchema.optional(),
|
|
22274
|
+
apiKey: z.string().optional(),
|
|
19404
22275
|
kitKey: z.string().optional()
|
|
19405
22276
|
});
|
|
19406
22277
|
const swapDestinationSchema = z.object({
|
|
19407
22278
|
chain: optionalSwapChainIdentifierField,
|
|
19408
|
-
recipientAddress: z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
22279
|
+
recipientAddress: z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
19409
22280
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
19410
22281
|
}).optional()
|
|
19411
22282
|
}).strict();
|
|
@@ -19594,7 +22465,7 @@ new Set(Object.values(Blockchain));
|
|
|
19594
22465
|
registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
19595
22466
|
|
|
19596
22467
|
var name = "@circle-fin/earn-kit";
|
|
19597
|
-
var version = "1.
|
|
22468
|
+
var version = "1.6.1";
|
|
19598
22469
|
var pkg = {
|
|
19599
22470
|
name: name,
|
|
19600
22471
|
version: version};
|
|
@@ -19666,7 +22537,7 @@ function isNonNegativeBigIntLike(value) {
|
|
|
19666
22537
|
}
|
|
19667
22538
|
}
|
|
19668
22539
|
const hexSignatureSchema = evmSignatureSchema;
|
|
19669
|
-
const hexAddressSchema = evmAddressSchema;
|
|
22540
|
+
const hexAddressSchema = evmAddressSchema$1;
|
|
19670
22541
|
// '0x' prefix + 32 bytes * 2 hex chars.
|
|
19671
22542
|
const BYTES32_HEX_LENGTH = 66;
|
|
19672
22543
|
const bridgeFeeTokenSchema = hexAddressSchema;
|
|
@@ -20571,7 +23442,7 @@ createTokenRegistry();
|
|
|
20571
23442
|
* fast instead of round-tripping to the service.
|
|
20572
23443
|
*
|
|
20573
23444
|
* @internal
|
|
20574
|
-
*/ const recipientEvmAddressSchema = evmAddressSchema.refine((address)=>!ZERO_EVM_ADDRESS_REGEX.test(address), 'address must not be the zero address').refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
23445
|
+
*/ const recipientEvmAddressSchema = evmAddressSchema$1.refine((address)=>!ZERO_EVM_ADDRESS_REGEX.test(address), 'address must not be the zero address').refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
20575
23446
|
/**
|
|
20576
23447
|
* Schema for the adapter context within earn operations.
|
|
20577
23448
|
*
|
|
@@ -20603,19 +23474,39 @@ const sourceAdapterContextSchema = z.object({
|
|
|
20603
23474
|
/**
|
|
20604
23475
|
* Schema for the EarnConfig options.
|
|
20605
23476
|
*
|
|
20606
|
-
* Validate the
|
|
20607
|
-
*
|
|
20608
|
-
*
|
|
20609
|
-
*
|
|
20610
|
-
*
|
|
20611
|
-
*
|
|
23477
|
+
* Validate the *resolved* credential using the standard `apiKeySchema` format
|
|
23478
|
+
* (`<ENV>_API_KEY:<keyId>:<keySecret>`, or a legacy
|
|
23479
|
+
* `KIT_KEY:<keyId>:<keySecret>`). `apiKey` takes precedence over the deprecated
|
|
23480
|
+
* `kitKey`, so a malformed `kitKey` that is being ignored must not fail a config
|
|
23481
|
+
* that supplies a valid `apiKey` (and vice versa) — only the credential that
|
|
23482
|
+
* would actually be sent is format-checked. When neither is supplied the SDK
|
|
23483
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service endpoint
|
|
23484
|
+
* (e.g. staging); `batchTransactions: false` opts out of atomic batched
|
|
23485
|
+
* execution. All are forwarded to the provider, so this `.strict()` schema must
|
|
23486
|
+
* accept them or a valid config object is rejected.
|
|
20612
23487
|
*
|
|
20613
23488
|
* @internal
|
|
20614
23489
|
*/ const earnConfigSchema = z.object({
|
|
20615
|
-
|
|
23490
|
+
apiKey: z.string().optional(),
|
|
23491
|
+
kitKey: z.string().optional(),
|
|
20616
23492
|
baseUrl: z.string().optional(),
|
|
20617
23493
|
batchTransactions: z.boolean().optional()
|
|
20618
|
-
}).strict()
|
|
23494
|
+
}).strict().superRefine((config, ctx)=>{
|
|
23495
|
+
const credential = resolveApiKey(config);
|
|
23496
|
+
if (credential === undefined) {
|
|
23497
|
+
return;
|
|
23498
|
+
}
|
|
23499
|
+
const result = apiKeySchema.safeParse(credential);
|
|
23500
|
+
if (!result.success) {
|
|
23501
|
+
ctx.addIssue({
|
|
23502
|
+
code: z.ZodIssueCode.custom,
|
|
23503
|
+
path: [
|
|
23504
|
+
credential === config.apiKey ? 'apiKey' : 'kitKey'
|
|
23505
|
+
],
|
|
23506
|
+
message: result.error.issues[0]?.message ?? 'Invalid API key format'
|
|
23507
|
+
});
|
|
23508
|
+
}
|
|
23509
|
+
});
|
|
20619
23510
|
/**
|
|
20620
23511
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
20621
23512
|
* '0' is only allowed immediately before the decimal point). Rejects the
|
|
@@ -20687,7 +23578,7 @@ const sourceAdapterContextSchema = z.object({
|
|
|
20687
23578
|
* currently supports EVM vault addresses on Arc Testnet.
|
|
20688
23579
|
*
|
|
20689
23580
|
* @internal
|
|
20690
|
-
*/ const vaultAddressSchema = evmAddressSchema.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
23581
|
+
*/ const vaultAddressSchema = evmAddressSchema$1.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
20691
23582
|
/**
|
|
20692
23583
|
* Validation schema for VaultQuery.
|
|
20693
23584
|
*
|
|
@@ -20962,7 +23853,7 @@ const sameChainGetDepositQuoteParamsSchema = z.object({
|
|
|
20962
23853
|
const crossChainGetDepositQuoteParamsSchema = z.object({
|
|
20963
23854
|
from: sourceAdapterContextSchema,
|
|
20964
23855
|
chain: earnBridgeDestinationChainIdentifierSchema,
|
|
20965
|
-
address: evmAddressSchema,
|
|
23856
|
+
address: evmAddressSchema$1,
|
|
20966
23857
|
vaultAddress: vaultAddressSchema,
|
|
20967
23858
|
amount: amountSchema,
|
|
20968
23859
|
transferSpeed: z.enum([
|