@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.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var zod = require('zod');
|
|
|
33
33
|
var pino = require('pino');
|
|
34
34
|
var units = require('@ethersproject/units');
|
|
35
35
|
var bytes = require('@ethersproject/bytes');
|
|
36
|
-
require('@ethersproject/abi');
|
|
36
|
+
var abi = require('@ethersproject/abi');
|
|
37
37
|
var address = require('@ethersproject/address');
|
|
38
38
|
var bs58 = require('bs58');
|
|
39
39
|
var web3_js = require('@solana/web3.js');
|
|
@@ -792,6 +792,32 @@ class KitError extends Error {
|
|
|
792
792
|
type: 'ONCHAIN'
|
|
793
793
|
}
|
|
794
794
|
};
|
|
795
|
+
/**
|
|
796
|
+
* Standardized error definitions for LIQUIDITY type errors.
|
|
797
|
+
*
|
|
798
|
+
* LIQUIDITY errors indicate that an upstream provider or AMM cannot fulfill
|
|
799
|
+
* the requested swap size due to insufficient liquidity at the moment.
|
|
800
|
+
* These are typically transient — retrying later or reducing the amount
|
|
801
|
+
* may succeed once liquidity replenishes.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```typescript
|
|
805
|
+
* import { LiquidityError } from '@core/errors'
|
|
806
|
+
*
|
|
807
|
+
* const error = new KitError({
|
|
808
|
+
* ...LiquidityError.INSUFFICIENT_LIQUIDITY,
|
|
809
|
+
* recoverability: 'RETRYABLE',
|
|
810
|
+
* message: 'Insufficient liquidity for the requested swap',
|
|
811
|
+
* cause: { trace: { token: '0xA0b86991...' } }
|
|
812
|
+
* })
|
|
813
|
+
* ```
|
|
814
|
+
*/ const LiquidityError = {
|
|
815
|
+
/** Upstream provider has a route but cannot fulfill the requested size right now */ INSUFFICIENT_LIQUIDITY: {
|
|
816
|
+
code: 6001,
|
|
817
|
+
name: 'LIQUIDITY_INSUFFICIENT',
|
|
818
|
+
type: 'LIQUIDITY'
|
|
819
|
+
}
|
|
820
|
+
};
|
|
795
821
|
/**
|
|
796
822
|
* Standardized error definitions for RPC type errors.
|
|
797
823
|
*
|
|
@@ -839,7 +865,10 @@ class KitError extends Error {
|
|
|
839
865
|
type: 'NETWORK'
|
|
840
866
|
},
|
|
841
867
|
/** Network request timeout */ TIMEOUT: {
|
|
842
|
-
code: 3002
|
|
868
|
+
code: 3002,
|
|
869
|
+
name: 'NETWORK_TIMEOUT',
|
|
870
|
+
type: 'NETWORK'
|
|
871
|
+
},
|
|
843
872
|
/** Circle relayer failed to process the forwarding/mint transaction */ RELAYER_FORWARD_FAILED: {
|
|
844
873
|
code: 3003,
|
|
845
874
|
name: 'NETWORK_RELAYER_FORWARD_FAILED',
|
|
@@ -850,6 +879,58 @@ class KitError extends Error {
|
|
|
850
879
|
name: 'NETWORK_RELAYER_PENDING',
|
|
851
880
|
type: 'NETWORK'
|
|
852
881
|
}};
|
|
882
|
+
/**
|
|
883
|
+
* Standardized error definitions for RATE_LIMIT type errors.
|
|
884
|
+
*
|
|
885
|
+
* RATE_LIMIT errors indicate API throttling, request frequency limits errors.
|
|
886
|
+
*
|
|
887
|
+
* @example
|
|
888
|
+
* ```typescript
|
|
889
|
+
* import { RateLimitError } from '@core/errors'
|
|
890
|
+
*
|
|
891
|
+
* const error = new KitError({
|
|
892
|
+
* ...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
893
|
+
* recoverability: 'RETRYABLE',
|
|
894
|
+
* message: 'Rate limit exceeded, please retry later',
|
|
895
|
+
* cause: { trace: { error: '429 Too Many Requests' } }
|
|
896
|
+
* })
|
|
897
|
+
* ```
|
|
898
|
+
*/ const RateLimitError = {
|
|
899
|
+
/** Rate limit exceeded */ RATE_LIMIT_EXCEEDED: {
|
|
900
|
+
code: 7001,
|
|
901
|
+
name: 'RATE_LIMIT_EXCEEDED',
|
|
902
|
+
type: 'RATE_LIMIT'
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
/**
|
|
906
|
+
* Standardized error definitions for SERVICE type errors.
|
|
907
|
+
*
|
|
908
|
+
* SERVICE errors indicate internal service failures, HTTP 5xx errors,
|
|
909
|
+
* or backend processing issues that are retryable.
|
|
910
|
+
*
|
|
911
|
+
* @example
|
|
912
|
+
* ```typescript
|
|
913
|
+
* import { ServiceError } from '@core/errors'
|
|
914
|
+
*
|
|
915
|
+
* const error = new KitError({
|
|
916
|
+
* ...ServiceError.INTERNAL_ERROR,
|
|
917
|
+
* recoverability: 'RETRYABLE',
|
|
918
|
+
* message: 'Service encountered an internal error (500)',
|
|
919
|
+
* cause: { trace: { statusCode: 500 } }
|
|
920
|
+
* })
|
|
921
|
+
* ```
|
|
922
|
+
*/ const ServiceError = {
|
|
923
|
+
/** Internal server error (HTTP 5xx) */ INTERNAL_ERROR: {
|
|
924
|
+
code: 8001,
|
|
925
|
+
name: 'SERVICE_INTERNAL_ERROR',
|
|
926
|
+
type: 'SERVICE'
|
|
927
|
+
},
|
|
928
|
+
/** Unknown or unclassified error that cannot be categorized */ UNKNOWN_ERROR: {
|
|
929
|
+
code: 8002,
|
|
930
|
+
name: 'SERVICE_UNKNOWN_ERROR',
|
|
931
|
+
type: 'SERVICE'
|
|
932
|
+
}
|
|
933
|
+
};
|
|
853
934
|
|
|
854
935
|
/**
|
|
855
936
|
* Creates error for network type mismatch between source and destination.
|
|
@@ -2221,6 +2302,32 @@ class KitError extends Error {
|
|
|
2221
2302
|
}
|
|
2222
2303
|
return false;
|
|
2223
2304
|
}
|
|
2305
|
+
/**
|
|
2306
|
+
* Type guard to check if error is KitError with RATE_LIMIT type.
|
|
2307
|
+
*
|
|
2308
|
+
* RATE_LIMIT errors indicate API throttling or request frequency limits.
|
|
2309
|
+
* These errors are typically RETRYABLE after a delay.
|
|
2310
|
+
*
|
|
2311
|
+
* @param error - Unknown error to check
|
|
2312
|
+
* @returns True if error is KitError with RATE_LIMIT type
|
|
2313
|
+
*
|
|
2314
|
+
* @example
|
|
2315
|
+
* ```typescript
|
|
2316
|
+
* import { isRateLimitError } from '@core/errors'
|
|
2317
|
+
*
|
|
2318
|
+
* try {
|
|
2319
|
+
* await kit.bridge(params)
|
|
2320
|
+
* } catch (error) {
|
|
2321
|
+
* if (isRateLimitError(error)) {
|
|
2322
|
+
* console.log('Rate limited, retrying in 60s')
|
|
2323
|
+
* await sleep(60000)
|
|
2324
|
+
* retry()
|
|
2325
|
+
* }
|
|
2326
|
+
* }
|
|
2327
|
+
* ```
|
|
2328
|
+
*/ function isRateLimitError(error) {
|
|
2329
|
+
return isKitError(error) && error.type === ERROR_TYPES.RATE_LIMIT;
|
|
2330
|
+
}
|
|
2224
2331
|
/**
|
|
2225
2332
|
* Safely extracts error message from any error type.
|
|
2226
2333
|
*
|
|
@@ -2470,6 +2577,478 @@ class KitError extends Error {
|
|
|
2470
2577
|
return chain;
|
|
2471
2578
|
}
|
|
2472
2579
|
|
|
2580
|
+
/**
|
|
2581
|
+
* Proxy-specific structured error codes carried in `responseBody.code`.
|
|
2582
|
+
*
|
|
2583
|
+
* These are NOT HTTP status codes — they are application-level identifiers
|
|
2584
|
+
* the stablecoin-kits-proxy emits inside JSON error bodies so the kit can
|
|
2585
|
+
* distinguish conditions that share the same HTTP status (e.g. a 400 caused
|
|
2586
|
+
* by an out-of-range swap amount vs. a generic validation failure).
|
|
2587
|
+
*
|
|
2588
|
+
* @internal
|
|
2589
|
+
*/ const ProxyErrorCode = {
|
|
2590
|
+
/** Swap amount outside the upstream provider's accepted bounds (HTTP 400) */ INVALID_SWAP_AMOUNT: 331017,
|
|
2591
|
+
/** Upstream liquidity insufficient for the requested size (HTTP 503) */ LOW_LIQUIDITY: 331018
|
|
2592
|
+
};
|
|
2593
|
+
/**
|
|
2594
|
+
* Parses raw HTTP API errors into structured KitError instances.
|
|
2595
|
+
*
|
|
2596
|
+
* This function uses pattern matching to identify common HTTP error types
|
|
2597
|
+
* and converts them into standardized KitError format. It handles errors
|
|
2598
|
+
* from fetch, HTTP status codes, timeouts, and network failures.
|
|
2599
|
+
*
|
|
2600
|
+
* The parser recognizes the following error patterns:
|
|
2601
|
+
* - Client errors (4xx) - validation, authentication, not found
|
|
2602
|
+
* - Server errors (5xx) - service unavailability
|
|
2603
|
+
* - Timeout errors
|
|
2604
|
+
* - Network connectivity errors
|
|
2605
|
+
* - Rate limiting
|
|
2606
|
+
*
|
|
2607
|
+
* Unrecognized errors are treated as fatal `SERVICE` errors, as their
|
|
2608
|
+
* cause and recoverability are unknown.
|
|
2609
|
+
*
|
|
2610
|
+
* @param error - The raw error from the API call
|
|
2611
|
+
* @param context - Context information including operation name
|
|
2612
|
+
* @returns A structured KitError instance
|
|
2613
|
+
*
|
|
2614
|
+
* @example
|
|
2615
|
+
* ```typescript
|
|
2616
|
+
* try {
|
|
2617
|
+
* const response = await fetch(url)
|
|
2618
|
+
* } catch (error) {
|
|
2619
|
+
* throw parseApiError(error, { operation: 'getQuote' })
|
|
2620
|
+
* }
|
|
2621
|
+
* ```
|
|
2622
|
+
*/ function parseApiError(error, context) {
|
|
2623
|
+
// If it's already a KitError, return it as-is
|
|
2624
|
+
if (error instanceof KitError) {
|
|
2625
|
+
return error;
|
|
2626
|
+
}
|
|
2627
|
+
const msg = getErrorMessage(error);
|
|
2628
|
+
const statusCode = extractHttpStatusCode(msg);
|
|
2629
|
+
const serviceName = context.service ?? 'Stablecoin Service';
|
|
2630
|
+
const operation = context.operation ?? 'API';
|
|
2631
|
+
const responseBody = extractResponseBody(error);
|
|
2632
|
+
// Rate limit errors (429)
|
|
2633
|
+
if (statusCode === 429 || /too many requests|rate limit exceeded/i.test(msg)) {
|
|
2634
|
+
return handleRateLimitError(serviceName, operation, error);
|
|
2635
|
+
}
|
|
2636
|
+
// HTTP 4xx Client Errors
|
|
2637
|
+
if (statusCode !== null && statusCode >= 400 && statusCode < 500) {
|
|
2638
|
+
return handleClientError(statusCode, serviceName, operation, error, msg, responseBody);
|
|
2639
|
+
}
|
|
2640
|
+
// HTTP 5xx Server Errors
|
|
2641
|
+
if (statusCode !== null && statusCode >= 500 && statusCode < 600) {
|
|
2642
|
+
return handleServerError(statusCode, serviceName, operation, error, responseBody);
|
|
2643
|
+
}
|
|
2644
|
+
// Timeout errors
|
|
2645
|
+
if (/timeout|timed out/i.test(msg)) {
|
|
2646
|
+
return handleTimeoutError(serviceName, operation, error);
|
|
2647
|
+
}
|
|
2648
|
+
// Network connectivity errors
|
|
2649
|
+
if (/connection (refused|failed)|network|ENOTFOUND|ECONNREFUSED|ETIMEDOUT/i.test(msg)) {
|
|
2650
|
+
return handleConnectionError(serviceName, operation, error);
|
|
2651
|
+
}
|
|
2652
|
+
// Fallback: Unknown error - use UNKNOWN_ERROR with fatal recoverability since we don't know what the error is
|
|
2653
|
+
return new KitError({
|
|
2654
|
+
...ServiceError.UNKNOWN_ERROR,
|
|
2655
|
+
recoverability: 'FATAL',
|
|
2656
|
+
message: `${serviceName} ${operation} failed: ${msg.length > 0 ? msg : 'Unknown error'}`,
|
|
2657
|
+
cause: {
|
|
2658
|
+
trace: error
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
/**
|
|
2663
|
+
* Handles HTTP 4xx client errors and maps them to appropriate KitError instances.
|
|
2664
|
+
*
|
|
2665
|
+
* @param statusCode - The HTTP status code
|
|
2666
|
+
* @param serviceName - The name of the service
|
|
2667
|
+
* @param operation - The operation name
|
|
2668
|
+
* @param error - The raw error object
|
|
2669
|
+
* @param msg - The message extracted from the error
|
|
2670
|
+
* @param responseBody - The parsed JSON response body from the server, if available
|
|
2671
|
+
* @returns A KitError instance
|
|
2672
|
+
*/ function handleClientError(statusCode, serviceName, operation, error, msg, responseBody) {
|
|
2673
|
+
const detail = extractDetailFromBody(responseBody) ?? msg;
|
|
2674
|
+
switch(statusCode){
|
|
2675
|
+
// 401/403 - Authentication/Authorization
|
|
2676
|
+
case 401:
|
|
2677
|
+
case 403:
|
|
2678
|
+
return new KitError({
|
|
2679
|
+
...InputError.VALIDATION_FAILED,
|
|
2680
|
+
recoverability: 'FATAL',
|
|
2681
|
+
message: `${serviceName} ${operation} failed: Invalid or missing API key or authorization`,
|
|
2682
|
+
cause: {
|
|
2683
|
+
trace: error
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
// 404 - Not found - unsupported route OR stop-limit / slippage constraint not met
|
|
2687
|
+
case 404:
|
|
2688
|
+
if (isSlippageConstraintFailure(responseBody)) {
|
|
2689
|
+
return new KitError({
|
|
2690
|
+
...InputError.SLIPPAGE_CONSTRAINT_NOT_MET,
|
|
2691
|
+
recoverability: 'RETRYABLE',
|
|
2692
|
+
message: `${serviceName} ${operation} failed: ${detail}. ` + 'Try increasing slippageBps or adjusting stopLimit.',
|
|
2693
|
+
cause: {
|
|
2694
|
+
trace: error
|
|
2695
|
+
}
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
return new KitError({
|
|
2699
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
2700
|
+
recoverability: 'FATAL',
|
|
2701
|
+
message: `${serviceName} ${operation} failed: Route or resource not found. Details: ${detail}`,
|
|
2702
|
+
cause: {
|
|
2703
|
+
trace: error
|
|
2704
|
+
}
|
|
2705
|
+
});
|
|
2706
|
+
// 422 Unprocessable Entity
|
|
2707
|
+
// Proxy service is mapping 422 to INSUFFICIENT_SWAP_AMOUNT
|
|
2708
|
+
case 422:
|
|
2709
|
+
return new KitError({
|
|
2710
|
+
...InputError.INSUFFICIENT_SWAP_AMOUNT,
|
|
2711
|
+
recoverability: 'FATAL',
|
|
2712
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2713
|
+
cause: {
|
|
2714
|
+
trace: error
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
// 400 Bad Request - Invalid token, amount-out-of-range, or validation failed
|
|
2718
|
+
// Proxy maps 400 to UNSUPPORTED_TOKEN | AMOUNT_OUT_OF_RANGE | VALIDATION_FAILED
|
|
2719
|
+
case 400:
|
|
2720
|
+
if (responseBody?.code === ProxyErrorCode.INVALID_SWAP_AMOUNT) {
|
|
2721
|
+
const amountErr = extractAmountError(responseBody);
|
|
2722
|
+
return new KitError({
|
|
2723
|
+
...InputError.AMOUNT_OUT_OF_RANGE,
|
|
2724
|
+
recoverability: 'FATAL',
|
|
2725
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2726
|
+
cause: {
|
|
2727
|
+
trace: {
|
|
2728
|
+
rawError: error,
|
|
2729
|
+
minAmount: amountErr?.minAmount,
|
|
2730
|
+
maxAmount: amountErr?.maxAmount,
|
|
2731
|
+
token: amountErr?.token
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
return new KitError({
|
|
2737
|
+
...InputError.VALIDATION_FAILED,
|
|
2738
|
+
recoverability: 'FATAL',
|
|
2739
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2740
|
+
cause: {
|
|
2741
|
+
trace: error
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
default:
|
|
2745
|
+
// Other 4xx errors - treat as validation failures
|
|
2746
|
+
return new KitError({
|
|
2747
|
+
...InputError.VALIDATION_FAILED,
|
|
2748
|
+
recoverability: 'FATAL',
|
|
2749
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2750
|
+
cause: {
|
|
2751
|
+
trace: error
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
/**
|
|
2757
|
+
* Pattern that matches proxy response body text indicating the 404 was
|
|
2758
|
+
* caused by a slippage / price constraint rather than a truly unsupported
|
|
2759
|
+
* route. Kept case-insensitive so future proxy wording changes are tolerated.
|
|
2760
|
+
*
|
|
2761
|
+
* @internal
|
|
2762
|
+
*/ const SLIPPAGE_BODY_PATTERN = /slippage|stop.?limit|price.?impact|minimum.?output|SLIPPAGE_CONSTRAINT_NOT_MET/i;
|
|
2763
|
+
/**
|
|
2764
|
+
* Determine whether a 404 was caused by an unmet slippage or price
|
|
2765
|
+
* constraint rather than a genuinely unsupported route.
|
|
2766
|
+
*
|
|
2767
|
+
* Detection relies on the proxy response body containing slippage-related
|
|
2768
|
+
* language or a structured reason code. This avoids false positives that
|
|
2769
|
+
* would occur if we guessed based on request parameters alone (a user
|
|
2770
|
+
* can set `slippageBps` and still hit a truly unsupported route).
|
|
2771
|
+
*
|
|
2772
|
+
* @param responseBody - The parsed JSON body returned by the proxy
|
|
2773
|
+
* @returns `true` when the 404 should be treated as a slippage constraint failure
|
|
2774
|
+
* @internal
|
|
2775
|
+
*/ function isSlippageConstraintFailure(responseBody) {
|
|
2776
|
+
if (responseBody === undefined) {
|
|
2777
|
+
return false;
|
|
2778
|
+
}
|
|
2779
|
+
const textsToCheck = [
|
|
2780
|
+
responseBody.externalMessage,
|
|
2781
|
+
responseBody.message,
|
|
2782
|
+
extractDetailFromBody(responseBody)
|
|
2783
|
+
];
|
|
2784
|
+
return textsToCheck.some((t)=>typeof t === 'string' && SLIPPAGE_BODY_PATTERN.test(t));
|
|
2785
|
+
}
|
|
2786
|
+
/**
|
|
2787
|
+
* Handles HTTP 5xx server errors and maps them to appropriate KitError instances.
|
|
2788
|
+
*
|
|
2789
|
+
* Recognizes proxy-specific structured codes in `responseBody.code` and routes
|
|
2790
|
+
* known conditions (e.g. {@link ProxyErrorCode.LOW_LIQUIDITY} on 503) to their
|
|
2791
|
+
* dedicated KitError. Falls back to a generic retryable `SERVICE_INTERNAL_ERROR`
|
|
2792
|
+
* when no specific code is present.
|
|
2793
|
+
*
|
|
2794
|
+
* @param statusCode - The HTTP status code
|
|
2795
|
+
* @param serviceName - The name of the service
|
|
2796
|
+
* @param operation - The operation name
|
|
2797
|
+
* @param error - The raw error object
|
|
2798
|
+
* @param responseBody - The parsed JSON response body from the server, if available
|
|
2799
|
+
* @returns A KitError instance
|
|
2800
|
+
*/ function handleServerError(statusCode, serviceName, operation, error, responseBody) {
|
|
2801
|
+
// 503 + 331018 = upstream liquidity insufficient (proxy)
|
|
2802
|
+
if (statusCode === 503 && responseBody?.code === ProxyErrorCode.LOW_LIQUIDITY) {
|
|
2803
|
+
const amountErr = extractAmountError(responseBody);
|
|
2804
|
+
const detail = extractDetailFromBody(responseBody) ?? getErrorMessage(error);
|
|
2805
|
+
return new KitError({
|
|
2806
|
+
...LiquidityError.INSUFFICIENT_LIQUIDITY,
|
|
2807
|
+
recoverability: 'RETRYABLE',
|
|
2808
|
+
message: `${serviceName} ${operation} failed: ${detail}`,
|
|
2809
|
+
cause: {
|
|
2810
|
+
trace: {
|
|
2811
|
+
rawError: error,
|
|
2812
|
+
minAmount: amountErr?.minAmount,
|
|
2813
|
+
maxAmount: amountErr?.maxAmount,
|
|
2814
|
+
token: amountErr?.token
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
return new KitError({
|
|
2820
|
+
...ServiceError.INTERNAL_ERROR,
|
|
2821
|
+
recoverability: 'RETRYABLE',
|
|
2822
|
+
message: `${serviceName} ${operation} failed: Server error (${statusCode.toString()})`,
|
|
2823
|
+
cause: {
|
|
2824
|
+
trace: error
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Handles network connection errors and maps them to appropriate KitError instances.
|
|
2830
|
+
*
|
|
2831
|
+
* @param serviceName - The name of the service
|
|
2832
|
+
* @param operation - The operation name
|
|
2833
|
+
* @param error - The raw error object
|
|
2834
|
+
* @returns A KitError instance
|
|
2835
|
+
*/ function handleConnectionError(serviceName, operation, error) {
|
|
2836
|
+
return new KitError({
|
|
2837
|
+
...NetworkError.CONNECTION_FAILED,
|
|
2838
|
+
recoverability: 'RETRYABLE',
|
|
2839
|
+
message: `${serviceName} ${operation} failed: Network connection error`,
|
|
2840
|
+
cause: {
|
|
2841
|
+
trace: error
|
|
2842
|
+
}
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2845
|
+
/**
|
|
2846
|
+
* Handles rate limit errors and maps them to appropriate KitError instances.
|
|
2847
|
+
*
|
|
2848
|
+
* @param serviceName - The name of the service
|
|
2849
|
+
* @param operation - The operation name
|
|
2850
|
+
* @param error - The raw error object
|
|
2851
|
+
* @returns A KitError instance
|
|
2852
|
+
*/ function handleRateLimitError(serviceName, operation, error) {
|
|
2853
|
+
return new KitError({
|
|
2854
|
+
...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
2855
|
+
recoverability: 'RETRYABLE',
|
|
2856
|
+
message: `${serviceName} ${operation} failed: Too many requests, please retry later`,
|
|
2857
|
+
cause: {
|
|
2858
|
+
trace: error
|
|
2859
|
+
}
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
/**
|
|
2863
|
+
* Handles timeout errors and maps them to appropriate KitError instances.
|
|
2864
|
+
*
|
|
2865
|
+
* @param serviceName - The name of the service
|
|
2866
|
+
* @param operation - The operation name
|
|
2867
|
+
* @param error - The raw error object
|
|
2868
|
+
* @returns A KitError instance
|
|
2869
|
+
*/ function handleTimeoutError(serviceName, operation, error) {
|
|
2870
|
+
return new KitError({
|
|
2871
|
+
...NetworkError.TIMEOUT,
|
|
2872
|
+
recoverability: 'RETRYABLE',
|
|
2873
|
+
message: `${serviceName} ${operation} failed: Request timeout`,
|
|
2874
|
+
cause: {
|
|
2875
|
+
trace: error
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Type guard that narrows an unknown error to one carrying a non-null
|
|
2881
|
+
* object `responseBody` property (attached by `makeApiRequest`).
|
|
2882
|
+
*
|
|
2883
|
+
* @param error - The raw error from the HTTP layer
|
|
2884
|
+
* @returns `true` when `error.responseBody` is a non-null object
|
|
2885
|
+
* @internal
|
|
2886
|
+
*/ function hasResponseBody(error) {
|
|
2887
|
+
return typeof error === 'object' && error !== null && 'responseBody' in error && typeof error['responseBody'] === 'object' && error['responseBody'] !== null;
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Extract the `responseBody` property that `makeApiRequest` attaches to
|
|
2891
|
+
* HTTP error instances when the server returns a JSON body.
|
|
2892
|
+
*
|
|
2893
|
+
* @param error - The raw error from the HTTP layer
|
|
2894
|
+
* @returns The parsed body cast to {@link ApiErrorResponseBody}, or undefined
|
|
2895
|
+
* @throws Never. Returns `undefined` when the error does not carry a valid
|
|
2896
|
+
* `responseBody`.
|
|
2897
|
+
* @internal
|
|
2898
|
+
*/ function extractResponseBody(error) {
|
|
2899
|
+
if (!hasResponseBody(error)) {
|
|
2900
|
+
return undefined;
|
|
2901
|
+
}
|
|
2902
|
+
return error.responseBody;
|
|
2903
|
+
}
|
|
2904
|
+
/**
|
|
2905
|
+
* Extract a field name from an {@link ApiFieldError}.
|
|
2906
|
+
*
|
|
2907
|
+
* The proxy service may provide the field name as a plain `field` string
|
|
2908
|
+
* or as a `path` array (e.g. `["tokenInChain"]`). This helper resolves
|
|
2909
|
+
* whichever is available, preferring `field` when both exist.
|
|
2910
|
+
*
|
|
2911
|
+
* @param entry - A single error entry from the `errors` array
|
|
2912
|
+
* @returns The field name, or `undefined` when neither is available
|
|
2913
|
+
* @internal
|
|
2914
|
+
*/ function extractFieldName(entry) {
|
|
2915
|
+
if (typeof entry.field === 'string' && entry.field.length > 0) {
|
|
2916
|
+
return entry.field;
|
|
2917
|
+
}
|
|
2918
|
+
if (Array.isArray(entry.path) && entry.path.length > 0) {
|
|
2919
|
+
const first = entry.path[0];
|
|
2920
|
+
if (typeof first === 'string' && first.length > 0) {
|
|
2921
|
+
return first;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return undefined;
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Join field-level error entries into a single human-readable string.
|
|
2928
|
+
*
|
|
2929
|
+
* Each entry is formatted as `"field: message"` when a field name is
|
|
2930
|
+
* available (via `field` or `path`), or just the message otherwise.
|
|
2931
|
+
* Entries without a usable message (including amount-bound entries that
|
|
2932
|
+
* only carry `minAmount`/`maxAmount`/`token`) are skipped.
|
|
2933
|
+
*
|
|
2934
|
+
* @param errors - The `errors` array from the response body
|
|
2935
|
+
* @returns A joined string, or `undefined` when no usable entries exist
|
|
2936
|
+
* @internal
|
|
2937
|
+
*/ function joinFieldErrors(errors) {
|
|
2938
|
+
const parts = errors.map((entry)=>{
|
|
2939
|
+
if (!isFieldEntry(entry)) {
|
|
2940
|
+
return undefined;
|
|
2941
|
+
}
|
|
2942
|
+
const fieldMsg = typeof entry.message === 'string' && entry.message.length > 0 ? entry.message : undefined;
|
|
2943
|
+
if (fieldMsg === undefined) {
|
|
2944
|
+
return undefined;
|
|
2945
|
+
}
|
|
2946
|
+
const fieldName = extractFieldName(entry);
|
|
2947
|
+
return fieldName === undefined ? fieldMsg : `${fieldName}: ${fieldMsg}`;
|
|
2948
|
+
}).filter((s)=>s !== undefined);
|
|
2949
|
+
return parts.length > 0 ? parts.join('; ') : undefined;
|
|
2950
|
+
}
|
|
2951
|
+
/**
|
|
2952
|
+
* Narrow an {@link ApiErrorItem} to the field-error shape used for
|
|
2953
|
+
* validation messages. Amount-bound entries (which carry no `message`)
|
|
2954
|
+
* are excluded.
|
|
2955
|
+
*
|
|
2956
|
+
* @internal
|
|
2957
|
+
*/ function isFieldEntry(entry) {
|
|
2958
|
+
return 'message' in entry || 'field' in entry || 'path' in entry;
|
|
2959
|
+
}
|
|
2960
|
+
/**
|
|
2961
|
+
* Narrow an {@link ApiErrorItem} to the amount-bound shape emitted by the
|
|
2962
|
+
* proxy for {@link ProxyErrorCode.INVALID_SWAP_AMOUNT}.
|
|
2963
|
+
*
|
|
2964
|
+
* @internal
|
|
2965
|
+
*/ function isAmountEntry(entry) {
|
|
2966
|
+
return 'minAmount' in entry || 'maxAmount' in entry || 'token' in entry;
|
|
2967
|
+
}
|
|
2968
|
+
/**
|
|
2969
|
+
* Extract the first amount-bound entry from a response body, if any.
|
|
2970
|
+
*
|
|
2971
|
+
* @internal
|
|
2972
|
+
*/ function extractAmountError(body) {
|
|
2973
|
+
if (body === undefined || !Array.isArray(body.errors)) {
|
|
2974
|
+
return undefined;
|
|
2975
|
+
}
|
|
2976
|
+
return body.errors.find(isAmountEntry);
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Derive a human-readable detail string from an {@link ApiErrorResponseBody}.
|
|
2980
|
+
*
|
|
2981
|
+
* Resolution order:
|
|
2982
|
+
* 1. `body.externalMessage` -- the user-facing string the proxy intends
|
|
2983
|
+
* consumers to display (e.g. "No route found that satisfies the
|
|
2984
|
+
* requested stop limit"). Preferred when available.
|
|
2985
|
+
* 2. `body.message` **and** `body.errors` -- when both are present the
|
|
2986
|
+
* top-level message is combined with the field-level detail so
|
|
2987
|
+
* developers see the full picture
|
|
2988
|
+
* (e.g. `"Validation error: tokenInChain: Invalid input; amount: …"`).
|
|
2989
|
+
* 3. `body.message` alone -- used as-is.
|
|
2990
|
+
* 4. `body.errors` alone -- field-level entries joined with "; ".
|
|
2991
|
+
* 5. `undefined` -- caller should fall back to the raw HTTP status text.
|
|
2992
|
+
*
|
|
2993
|
+
* @param body - The parsed response body, may be undefined
|
|
2994
|
+
* @returns A detail string, or undefined when no useful info is available
|
|
2995
|
+
* @internal
|
|
2996
|
+
*/ function extractDetailFromBody(body) {
|
|
2997
|
+
if (body === undefined) {
|
|
2998
|
+
return undefined;
|
|
2999
|
+
}
|
|
3000
|
+
const externalMessage = typeof body.externalMessage === 'string' && body.externalMessage.length > 0 ? body.externalMessage : undefined;
|
|
3001
|
+
if (externalMessage !== undefined) {
|
|
3002
|
+
return externalMessage;
|
|
3003
|
+
}
|
|
3004
|
+
const topMessage = typeof body.message === 'string' && body.message.length > 0 ? body.message : undefined;
|
|
3005
|
+
const fieldDetail = Array.isArray(body.errors) && body.errors.length > 0 ? joinFieldErrors(body.errors) : undefined;
|
|
3006
|
+
if (topMessage !== undefined && fieldDetail !== undefined) {
|
|
3007
|
+
return `${topMessage}: ${fieldDetail}`;
|
|
3008
|
+
}
|
|
3009
|
+
return topMessage ?? fieldDetail;
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* Extracts the HTTP status code from an error message.
|
|
3013
|
+
*
|
|
3014
|
+
* Attempts to parse HTTP status codes from common error message formats,
|
|
3015
|
+
* such as "HTTP 404" or "Status: 500".
|
|
3016
|
+
*
|
|
3017
|
+
* @param msg - The error message to extract from
|
|
3018
|
+
* @returns The extracted HTTP status code, or null if not found
|
|
3019
|
+
*
|
|
3020
|
+
* @example
|
|
3021
|
+
* ```typescript
|
|
3022
|
+
* const code = extractHttpStatusCode('HTTP 404')
|
|
3023
|
+
* // Returns: 404
|
|
3024
|
+
* ```
|
|
3025
|
+
*
|
|
3026
|
+
* @example
|
|
3027
|
+
* ```typescript
|
|
3028
|
+
* const code = extractHttpStatusCode('Status: 500 Internal Server Error')
|
|
3029
|
+
* // Returns: 500
|
|
3030
|
+
* ```
|
|
3031
|
+
*/ function extractHttpStatusCode(msg) {
|
|
3032
|
+
// Pattern: "HTTP 404" or "HTTP 404 - some message" or "Status: 404"
|
|
3033
|
+
const patterns = [
|
|
3034
|
+
/HTTP (\d{3})/i,
|
|
3035
|
+
/Status:\s*(\d{3})/i,
|
|
3036
|
+
/^(\d{3}) -/
|
|
3037
|
+
];
|
|
3038
|
+
for (const pattern of patterns){
|
|
3039
|
+
const match = pattern.exec(msg);
|
|
3040
|
+
const codeStr = match?.at(1);
|
|
3041
|
+
if (codeStr !== undefined) {
|
|
3042
|
+
const code = Number.parseInt(codeStr, 10);
|
|
3043
|
+
// Validate it's a valid HTTP status code
|
|
3044
|
+
if (code >= 100 && code < 600) {
|
|
3045
|
+
return code;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
return null;
|
|
3050
|
+
}
|
|
3051
|
+
|
|
2473
3052
|
/**
|
|
2474
3053
|
* @packageDocumentation
|
|
2475
3054
|
* @module ChainDefinitions
|
|
@@ -2541,6 +3120,8 @@ class KitError extends Error {
|
|
|
2541
3120
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
2542
3121
|
Blockchain["Pharos"] = "Pharos";
|
|
2543
3122
|
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
3123
|
+
Blockchain["Plasma"] = "Plasma";
|
|
3124
|
+
Blockchain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
2544
3125
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
2545
3126
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
2546
3127
|
Blockchain["Plume"] = "Plume";
|
|
@@ -2610,6 +3191,7 @@ var BridgeChain;
|
|
|
2610
3191
|
BridgeChain["Morph"] = "Morph";
|
|
2611
3192
|
BridgeChain["Optimism"] = "Optimism";
|
|
2612
3193
|
BridgeChain["Pharos"] = "Pharos";
|
|
3194
|
+
BridgeChain["Plasma"] = "Plasma";
|
|
2613
3195
|
BridgeChain["Plume"] = "Plume";
|
|
2614
3196
|
BridgeChain["Polygon"] = "Polygon";
|
|
2615
3197
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -2636,6 +3218,7 @@ var BridgeChain;
|
|
|
2636
3218
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
2637
3219
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
2638
3220
|
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
3221
|
+
BridgeChain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
2639
3222
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
2640
3223
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
2641
3224
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -3082,17 +3665,56 @@ var EarnChain;
|
|
|
3082
3665
|
* This program handles minting operations for Gateway transactions
|
|
3083
3666
|
* on Solana devnet.
|
|
3084
3667
|
*/ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
|
|
3085
|
-
|
|
3086
3668
|
/**
|
|
3087
|
-
*
|
|
3088
|
-
*
|
|
3089
|
-
*
|
|
3090
|
-
*
|
|
3091
|
-
*
|
|
3092
|
-
*
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3669
|
+
* The `TokenMessengerWithFees` proxy contract address for EVM mainnet networks
|
|
3670
|
+
* (all chains except Edge).
|
|
3671
|
+
*
|
|
3672
|
+
* Deployed at a CREATE3-derived address; identical across all mainnet EVM
|
|
3673
|
+
* source chains. Present on any chain that supports the prepaid FORWARD path
|
|
3674
|
+
* via `depositForBurnWithHookAndFees`.
|
|
3675
|
+
*/ const TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET = '0x71f54F818671cD0D7ea140Da213e5C8b5C92a408';
|
|
3676
|
+
/**
|
|
3677
|
+
* The `TokenMessengerWithFees` proxy contract address for EVM testnet networks.
|
|
3678
|
+
*
|
|
3679
|
+
* Identical across all testnet EVM source chains. Present on any testnet chain
|
|
3680
|
+
* that supports the prepaid FORWARD path via `depositForBurnWithHookAndFees`.
|
|
3681
|
+
*/ const TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET = '0x8745D906D67C346E5eb1aEEED38Eb87F34DF0C0A';
|
|
3682
|
+
/**
|
|
3683
|
+
* The `DepositForHandler` proxy contract address for EVM mainnet networks.
|
|
3684
|
+
*
|
|
3685
|
+
* The handler the GenericExecutor calls on a fast-deposit destination chain to
|
|
3686
|
+
* run a cross-chain deposit into the GatewayWallet. Deployed at the same
|
|
3687
|
+
* address across all mainnet EVM destination chains.
|
|
3688
|
+
*/ const DEPOSIT_FOR_HANDLER_EVM_MAINNET = '0x16529813203f77E036576666336554a1210dce4D';
|
|
3689
|
+
/**
|
|
3690
|
+
* The `DepositForHandler` proxy contract address for EVM testnet networks.
|
|
3691
|
+
*
|
|
3692
|
+
* Identical across all testnet EVM destination chains.
|
|
3693
|
+
*/ const DEPOSIT_FOR_HANDLER_EVM_TESTNET = '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48';
|
|
3694
|
+
/**
|
|
3695
|
+
* The `GenericExecutor` proxy contract address for EVM mainnet networks.
|
|
3696
|
+
*
|
|
3697
|
+
* The GenericExecutor is the `mintRecipient` and `destinationCaller` on the
|
|
3698
|
+
* destination chain for the CCTP v2 prepaid FORWARD path. It receives the CCTP
|
|
3699
|
+
* mint and calls the `DepositForHandler` to complete the fast deposit.
|
|
3700
|
+
* Deployed at the same address across all mainnet EVM destination chains.
|
|
3701
|
+
*/ const GENERIC_EXECUTOR_EVM_MAINNET = '0xFa7be2f04F3Ad4ca969260729c6d45B5625984A7';
|
|
3702
|
+
/**
|
|
3703
|
+
* The `GenericExecutor` proxy contract address for EVM testnet networks.
|
|
3704
|
+
*
|
|
3705
|
+
* Identical across all testnet EVM destination chains.
|
|
3706
|
+
*/ const GENERIC_EXECUTOR_EVM_TESTNET = '0xEdC81040756AcCfF070c21D37b265b9D0b5Ba45e';
|
|
3707
|
+
|
|
3708
|
+
/**
|
|
3709
|
+
* Arc Testnet chain definition
|
|
3710
|
+
* @remarks
|
|
3711
|
+
* This represents the test network for the Arc blockchain,
|
|
3712
|
+
* Circle's EVM-compatible Layer-1 designed for stablecoin finance
|
|
3713
|
+
* and asset tokenization. Arc uses USDC as the native gas token and
|
|
3714
|
+
* features the Malachite Byzantine Fault Tolerant (BFT) consensus
|
|
3715
|
+
* engine for sub-second finality.
|
|
3716
|
+
*/ const ArcTestnet = defineChain({
|
|
3717
|
+
type: 'evm',
|
|
3096
3718
|
chain: Blockchain.Arc_Testnet,
|
|
3097
3719
|
name: 'Arc Testnet',
|
|
3098
3720
|
title: 'ArcTestnet',
|
|
@@ -3119,6 +3741,7 @@ var EarnChain;
|
|
|
3119
3741
|
v2: {
|
|
3120
3742
|
type: 'split',
|
|
3121
3743
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
3744
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3122
3745
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3123
3746
|
confirmations: 1,
|
|
3124
3747
|
fastConfirmations: 1
|
|
@@ -3139,9 +3762,8 @@ var EarnChain;
|
|
|
3139
3762
|
v1: {
|
|
3140
3763
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3141
3764
|
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
3765
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
|
|
3766
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
|
|
3145
3767
|
}
|
|
3146
3768
|
},
|
|
3147
3769
|
forwarderSupported: {
|
|
@@ -3186,6 +3808,7 @@ var EarnChain;
|
|
|
3186
3808
|
v2: {
|
|
3187
3809
|
type: 'split',
|
|
3188
3810
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
3811
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3189
3812
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3190
3813
|
confirmations: 65,
|
|
3191
3814
|
fastConfirmations: 1
|
|
@@ -3250,6 +3873,7 @@ var EarnChain;
|
|
|
3250
3873
|
v2: {
|
|
3251
3874
|
type: 'split',
|
|
3252
3875
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
3876
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3253
3877
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3254
3878
|
confirmations: 65,
|
|
3255
3879
|
fastConfirmations: 1
|
|
@@ -3314,6 +3938,7 @@ var EarnChain;
|
|
|
3314
3938
|
v2: {
|
|
3315
3939
|
type: 'split',
|
|
3316
3940
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
3941
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3317
3942
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3318
3943
|
confirmations: 1,
|
|
3319
3944
|
fastConfirmations: 1
|
|
@@ -3333,7 +3958,9 @@ var EarnChain;
|
|
|
3333
3958
|
contracts: {
|
|
3334
3959
|
v1: {
|
|
3335
3960
|
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
3336
|
-
minter: GATEWAY_MINTER_EVM_MAINNET
|
|
3961
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
3962
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
|
|
3963
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
|
|
3337
3964
|
}
|
|
3338
3965
|
},
|
|
3339
3966
|
forwarderSupported: {
|
|
@@ -3375,6 +4002,7 @@ var EarnChain;
|
|
|
3375
4002
|
v2: {
|
|
3376
4003
|
type: 'split',
|
|
3377
4004
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4005
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3378
4006
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3379
4007
|
confirmations: 1,
|
|
3380
4008
|
fastConfirmations: 1
|
|
@@ -3396,7 +4024,9 @@ var EarnChain;
|
|
|
3396
4024
|
contracts: {
|
|
3397
4025
|
v1: {
|
|
3398
4026
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3399
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
4027
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
4028
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
|
|
4029
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
|
|
3400
4030
|
}
|
|
3401
4031
|
},
|
|
3402
4032
|
forwarderSupported: {
|
|
@@ -3442,6 +4072,7 @@ var EarnChain;
|
|
|
3442
4072
|
v2: {
|
|
3443
4073
|
type: 'split',
|
|
3444
4074
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4075
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3445
4076
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3446
4077
|
confirmations: 65,
|
|
3447
4078
|
fastConfirmations: 1
|
|
@@ -3506,6 +4137,7 @@ var EarnChain;
|
|
|
3506
4137
|
v2: {
|
|
3507
4138
|
type: 'split',
|
|
3508
4139
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4140
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3509
4141
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3510
4142
|
confirmations: 65,
|
|
3511
4143
|
fastConfirmations: 1
|
|
@@ -3616,6 +4248,7 @@ var EarnChain;
|
|
|
3616
4248
|
v2: {
|
|
3617
4249
|
type: 'split',
|
|
3618
4250
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4251
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3619
4252
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3620
4253
|
confirmations: 65,
|
|
3621
4254
|
fastConfirmations: 1
|
|
@@ -3660,6 +4293,7 @@ var EarnChain;
|
|
|
3660
4293
|
v2: {
|
|
3661
4294
|
type: 'split',
|
|
3662
4295
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4296
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3663
4297
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3664
4298
|
confirmations: 65,
|
|
3665
4299
|
fastConfirmations: 1
|
|
@@ -3705,6 +4339,7 @@ var EarnChain;
|
|
|
3705
4339
|
v2: {
|
|
3706
4340
|
type: 'split',
|
|
3707
4341
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4342
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3708
4343
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3709
4344
|
confirmations: 1,
|
|
3710
4345
|
fastConfirmations: 1
|
|
@@ -3750,6 +4385,7 @@ var EarnChain;
|
|
|
3750
4385
|
v2: {
|
|
3751
4386
|
type: 'split',
|
|
3752
4387
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4388
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3753
4389
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3754
4390
|
confirmations: 1,
|
|
3755
4391
|
fastConfirmations: 1
|
|
@@ -3795,6 +4431,7 @@ var EarnChain;
|
|
|
3795
4431
|
v2: {
|
|
3796
4432
|
type: 'split',
|
|
3797
4433
|
tokenMessenger: '0x98706A006bc632Df31CAdFCBD43F38887ce2ca5c',
|
|
4434
|
+
tokenMessengerWithFees: '0x3Ac96675F9a3E6922713e041645D82f3561d3686',
|
|
3798
4435
|
messageTransmitter: '0x5b61381Fc9e58E70EfC13a4A97516997019198ee',
|
|
3799
4436
|
confirmations: 65,
|
|
3800
4437
|
fastConfirmations: 1
|
|
@@ -3840,6 +4477,7 @@ var EarnChain;
|
|
|
3840
4477
|
v2: {
|
|
3841
4478
|
type: 'split',
|
|
3842
4479
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4480
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3843
4481
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
3844
4482
|
confirmations: 65,
|
|
3845
4483
|
fastConfirmations: 1
|
|
@@ -3891,6 +4529,7 @@ var EarnChain;
|
|
|
3891
4529
|
v2: {
|
|
3892
4530
|
type: 'split',
|
|
3893
4531
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4532
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
3894
4533
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
3895
4534
|
confirmations: 65,
|
|
3896
4535
|
fastConfirmations: 2
|
|
@@ -3955,6 +4594,7 @@ var EarnChain;
|
|
|
3955
4594
|
v2: {
|
|
3956
4595
|
type: 'split',
|
|
3957
4596
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4597
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
3958
4598
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
3959
4599
|
confirmations: 65,
|
|
3960
4600
|
fastConfirmations: 2
|
|
@@ -4065,6 +4705,7 @@ var EarnChain;
|
|
|
4065
4705
|
v2: {
|
|
4066
4706
|
type: 'split',
|
|
4067
4707
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4708
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4068
4709
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4069
4710
|
confirmations: 1,
|
|
4070
4711
|
fastConfirmations: 1
|
|
@@ -4124,6 +4765,7 @@ var EarnChain;
|
|
|
4124
4765
|
v2: {
|
|
4125
4766
|
type: 'split',
|
|
4126
4767
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4768
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4127
4769
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4128
4770
|
confirmations: 1,
|
|
4129
4771
|
fastConfirmations: 1
|
|
@@ -4184,6 +4826,7 @@ var EarnChain;
|
|
|
4184
4826
|
v2: {
|
|
4185
4827
|
type: 'split',
|
|
4186
4828
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4829
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4187
4830
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4188
4831
|
confirmations: 1,
|
|
4189
4832
|
fastConfirmations: 1
|
|
@@ -4231,6 +4874,7 @@ var EarnChain;
|
|
|
4231
4874
|
v2: {
|
|
4232
4875
|
type: 'split',
|
|
4233
4876
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4877
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4234
4878
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4235
4879
|
confirmations: 1,
|
|
4236
4880
|
fastConfirmations: 1
|
|
@@ -4278,6 +4922,7 @@ var EarnChain;
|
|
|
4278
4922
|
v2: {
|
|
4279
4923
|
type: 'split',
|
|
4280
4924
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4925
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4281
4926
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4282
4927
|
confirmations: 65,
|
|
4283
4928
|
fastConfirmations: 1
|
|
@@ -4325,6 +4970,7 @@ var EarnChain;
|
|
|
4325
4970
|
v2: {
|
|
4326
4971
|
type: 'split',
|
|
4327
4972
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4973
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4328
4974
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4329
4975
|
confirmations: 65,
|
|
4330
4976
|
fastConfirmations: 1
|
|
@@ -4369,6 +5015,7 @@ var EarnChain;
|
|
|
4369
5015
|
v2: {
|
|
4370
5016
|
type: 'split',
|
|
4371
5017
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5018
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4372
5019
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4373
5020
|
confirmations: 1,
|
|
4374
5021
|
fastConfirmations: 1
|
|
@@ -4414,6 +5061,7 @@ var EarnChain;
|
|
|
4414
5061
|
v2: {
|
|
4415
5062
|
type: 'split',
|
|
4416
5063
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5064
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4417
5065
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
4418
5066
|
confirmations: 1,
|
|
4419
5067
|
fastConfirmations: 1
|
|
@@ -4460,6 +5108,7 @@ var EarnChain;
|
|
|
4460
5108
|
v2: {
|
|
4461
5109
|
type: 'split',
|
|
4462
5110
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5111
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4463
5112
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4464
5113
|
confirmations: 1,
|
|
4465
5114
|
fastConfirmations: 1
|
|
@@ -4507,6 +5156,7 @@ var EarnChain;
|
|
|
4507
5156
|
v2: {
|
|
4508
5157
|
type: 'split',
|
|
4509
5158
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5159
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4510
5160
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4511
5161
|
confirmations: 1,
|
|
4512
5162
|
fastConfirmations: 1
|
|
@@ -4552,6 +5202,7 @@ var EarnChain;
|
|
|
4552
5202
|
v2: {
|
|
4553
5203
|
type: 'split',
|
|
4554
5204
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5205
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4555
5206
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4556
5207
|
confirmations: 64,
|
|
4557
5208
|
fastConfirmations: 1
|
|
@@ -4597,6 +5248,7 @@ var EarnChain;
|
|
|
4597
5248
|
v2: {
|
|
4598
5249
|
type: 'split',
|
|
4599
5250
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5251
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4600
5252
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4601
5253
|
confirmations: 64,
|
|
4602
5254
|
fastConfirmations: 1
|
|
@@ -4773,6 +5425,7 @@ var EarnChain;
|
|
|
4773
5425
|
v2: {
|
|
4774
5426
|
type: 'split',
|
|
4775
5427
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5428
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4776
5429
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4777
5430
|
confirmations: 65,
|
|
4778
5431
|
fastConfirmations: 1
|
|
@@ -4837,6 +5490,7 @@ var EarnChain;
|
|
|
4837
5490
|
v2: {
|
|
4838
5491
|
type: 'split',
|
|
4839
5492
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5493
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4840
5494
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
4841
5495
|
confirmations: 65,
|
|
4842
5496
|
fastConfirmations: 1
|
|
@@ -4896,6 +5550,7 @@ var EarnChain;
|
|
|
4896
5550
|
v2: {
|
|
4897
5551
|
type: 'split',
|
|
4898
5552
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5553
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4899
5554
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4900
5555
|
confirmations: 1,
|
|
4901
5556
|
fastConfirmations: 1
|
|
@@ -4942,6 +5597,7 @@ var EarnChain;
|
|
|
4942
5597
|
v2: {
|
|
4943
5598
|
type: 'split',
|
|
4944
5599
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5600
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4945
5601
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4946
5602
|
confirmations: 1,
|
|
4947
5603
|
fastConfirmations: 1
|
|
@@ -4957,6 +5613,98 @@ var EarnChain;
|
|
|
4957
5613
|
}
|
|
4958
5614
|
});
|
|
4959
5615
|
|
|
5616
|
+
/**
|
|
5617
|
+
* Plasma Mainnet chain definition
|
|
5618
|
+
* @remarks
|
|
5619
|
+
* This represents the official production network for the Plasma blockchain.
|
|
5620
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
5621
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
5622
|
+
*/ const Plasma = defineChain({
|
|
5623
|
+
type: 'evm',
|
|
5624
|
+
chain: Blockchain.Plasma,
|
|
5625
|
+
name: 'Plasma',
|
|
5626
|
+
title: 'Plasma Mainnet',
|
|
5627
|
+
nativeCurrency: {
|
|
5628
|
+
name: 'Plasma',
|
|
5629
|
+
symbol: 'XPL',
|
|
5630
|
+
decimals: 18
|
|
5631
|
+
},
|
|
5632
|
+
chainId: 9745,
|
|
5633
|
+
isTestnet: false,
|
|
5634
|
+
explorerUrl: 'https://plasmascan.to/tx/{hash}',
|
|
5635
|
+
rpcEndpoints: [
|
|
5636
|
+
'https://rpc.plasma.to'
|
|
5637
|
+
],
|
|
5638
|
+
eurcAddress: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
5639
|
+
usdcAddress: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
5640
|
+
usdtAddress: null,
|
|
5641
|
+
cctp: {
|
|
5642
|
+
domain: 33,
|
|
5643
|
+
contracts: {
|
|
5644
|
+
v2: {
|
|
5645
|
+
type: 'split',
|
|
5646
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5647
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5648
|
+
confirmations: 3,
|
|
5649
|
+
fastConfirmations: 1
|
|
5650
|
+
}
|
|
5651
|
+
},
|
|
5652
|
+
forwarderSupported: {
|
|
5653
|
+
source: false,
|
|
5654
|
+
destination: false
|
|
5655
|
+
}
|
|
5656
|
+
},
|
|
5657
|
+
kitContracts: {
|
|
5658
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
5659
|
+
}
|
|
5660
|
+
});
|
|
5661
|
+
|
|
5662
|
+
/**
|
|
5663
|
+
* Plasma Testnet chain definition
|
|
5664
|
+
* @remarks
|
|
5665
|
+
* This represents the official test network for the Plasma blockchain.
|
|
5666
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
5667
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
5668
|
+
*/ const PlasmaTestnet = defineChain({
|
|
5669
|
+
type: 'evm',
|
|
5670
|
+
chain: Blockchain.Plasma_Testnet,
|
|
5671
|
+
name: 'Plasma Testnet',
|
|
5672
|
+
title: 'Plasma Testnet',
|
|
5673
|
+
nativeCurrency: {
|
|
5674
|
+
name: 'Plasma',
|
|
5675
|
+
symbol: 'XPL',
|
|
5676
|
+
decimals: 18
|
|
5677
|
+
},
|
|
5678
|
+
chainId: 9746,
|
|
5679
|
+
isTestnet: true,
|
|
5680
|
+
explorerUrl: 'https://testnet.plasmascan.to/tx/{hash}',
|
|
5681
|
+
rpcEndpoints: [
|
|
5682
|
+
'https://testnet-rpc.plasma.to'
|
|
5683
|
+
],
|
|
5684
|
+
eurcAddress: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330',
|
|
5685
|
+
usdcAddress: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
5686
|
+
usdtAddress: null,
|
|
5687
|
+
cctp: {
|
|
5688
|
+
domain: 33,
|
|
5689
|
+
contracts: {
|
|
5690
|
+
v2: {
|
|
5691
|
+
type: 'split',
|
|
5692
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5693
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5694
|
+
confirmations: 3,
|
|
5695
|
+
fastConfirmations: 1
|
|
5696
|
+
}
|
|
5697
|
+
},
|
|
5698
|
+
forwarderSupported: {
|
|
5699
|
+
source: false,
|
|
5700
|
+
destination: false
|
|
5701
|
+
}
|
|
5702
|
+
},
|
|
5703
|
+
kitContracts: {
|
|
5704
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
5705
|
+
}
|
|
5706
|
+
});
|
|
5707
|
+
|
|
4960
5708
|
/**
|
|
4961
5709
|
* Plume Mainnet chain definition
|
|
4962
5710
|
* @remarks
|
|
@@ -4988,6 +5736,7 @@ var EarnChain;
|
|
|
4988
5736
|
v2: {
|
|
4989
5737
|
type: 'split',
|
|
4990
5738
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5739
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4991
5740
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4992
5741
|
confirmations: 65,
|
|
4993
5742
|
fastConfirmations: 1
|
|
@@ -5034,6 +5783,7 @@ var EarnChain;
|
|
|
5034
5783
|
v2: {
|
|
5035
5784
|
type: 'split',
|
|
5036
5785
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5786
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5037
5787
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5038
5788
|
confirmations: 65,
|
|
5039
5789
|
fastConfirmations: 1
|
|
@@ -5135,6 +5885,7 @@ var EarnChain;
|
|
|
5135
5885
|
v2: {
|
|
5136
5886
|
type: 'split',
|
|
5137
5887
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5888
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5138
5889
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5139
5890
|
confirmations: 33,
|
|
5140
5891
|
fastConfirmations: 13
|
|
@@ -5154,7 +5905,9 @@ var EarnChain;
|
|
|
5154
5905
|
contracts: {
|
|
5155
5906
|
v1: {
|
|
5156
5907
|
wallet: GATEWAY_WALLET_EVM_MAINNET,
|
|
5157
|
-
minter: GATEWAY_MINTER_EVM_MAINNET
|
|
5908
|
+
minter: GATEWAY_MINTER_EVM_MAINNET,
|
|
5909
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
|
|
5910
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
|
|
5158
5911
|
}
|
|
5159
5912
|
},
|
|
5160
5913
|
forwarderSupported: {
|
|
@@ -5200,6 +5953,7 @@ var EarnChain;
|
|
|
5200
5953
|
v2: {
|
|
5201
5954
|
type: 'split',
|
|
5202
5955
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5956
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5203
5957
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5204
5958
|
confirmations: 33,
|
|
5205
5959
|
fastConfirmations: 13
|
|
@@ -5218,7 +5972,9 @@ var EarnChain;
|
|
|
5218
5972
|
contracts: {
|
|
5219
5973
|
v1: {
|
|
5220
5974
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
5221
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
5975
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
5976
|
+
depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
|
|
5977
|
+
genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
|
|
5222
5978
|
}
|
|
5223
5979
|
},
|
|
5224
5980
|
forwarderSupported: {
|
|
@@ -5259,6 +6015,7 @@ var EarnChain;
|
|
|
5259
6015
|
v2: {
|
|
5260
6016
|
type: 'split',
|
|
5261
6017
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6018
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5262
6019
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5263
6020
|
confirmations: 1,
|
|
5264
6021
|
fastConfirmations: 1
|
|
@@ -5318,6 +6075,7 @@ var EarnChain;
|
|
|
5318
6075
|
v2: {
|
|
5319
6076
|
type: 'split',
|
|
5320
6077
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6078
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5321
6079
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5322
6080
|
confirmations: 1,
|
|
5323
6081
|
fastConfirmations: 1
|
|
@@ -5375,6 +6133,7 @@ var EarnChain;
|
|
|
5375
6133
|
v2: {
|
|
5376
6134
|
type: 'split',
|
|
5377
6135
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6136
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5378
6137
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5379
6138
|
confirmations: 1,
|
|
5380
6139
|
fastConfirmations: 1
|
|
@@ -5433,6 +6192,7 @@ var EarnChain;
|
|
|
5433
6192
|
v2: {
|
|
5434
6193
|
type: 'split',
|
|
5435
6194
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6195
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5436
6196
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5437
6197
|
confirmations: 1,
|
|
5438
6198
|
fastConfirmations: 1
|
|
@@ -5748,6 +6508,7 @@ var EarnChain;
|
|
|
5748
6508
|
v2: {
|
|
5749
6509
|
type: 'split',
|
|
5750
6510
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6511
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5751
6512
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5752
6513
|
confirmations: 65,
|
|
5753
6514
|
fastConfirmations: 1
|
|
@@ -5812,6 +6573,7 @@ var EarnChain;
|
|
|
5812
6573
|
v2: {
|
|
5813
6574
|
type: 'split',
|
|
5814
6575
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6576
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5815
6577
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5816
6578
|
confirmations: 65,
|
|
5817
6579
|
fastConfirmations: 1
|
|
@@ -5869,6 +6631,7 @@ var EarnChain;
|
|
|
5869
6631
|
v2: {
|
|
5870
6632
|
type: 'split',
|
|
5871
6633
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cF5d',
|
|
6634
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5872
6635
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5873
6636
|
confirmations: 65,
|
|
5874
6637
|
fastConfirmations: 1
|
|
@@ -5928,6 +6691,7 @@ var EarnChain;
|
|
|
5928
6691
|
v2: {
|
|
5929
6692
|
type: 'split',
|
|
5930
6693
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
6694
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5931
6695
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5932
6696
|
confirmations: 65,
|
|
5933
6697
|
fastConfirmations: 1
|
|
@@ -5988,6 +6752,7 @@ var EarnChain;
|
|
|
5988
6752
|
v2: {
|
|
5989
6753
|
type: 'split',
|
|
5990
6754
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6755
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5991
6756
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5992
6757
|
confirmations: 3,
|
|
5993
6758
|
fastConfirmations: 3
|
|
@@ -6033,6 +6798,7 @@ var EarnChain;
|
|
|
6033
6798
|
v2: {
|
|
6034
6799
|
type: 'split',
|
|
6035
6800
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6801
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6036
6802
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6037
6803
|
confirmations: 3,
|
|
6038
6804
|
fastConfirmations: 1
|
|
@@ -6243,6 +7009,8 @@ var Chains = {
|
|
|
6243
7009
|
OptimismSepolia: OptimismSepolia,
|
|
6244
7010
|
Pharos: Pharos,
|
|
6245
7011
|
PharosTestnet: PharosTestnet,
|
|
7012
|
+
Plasma: Plasma,
|
|
7013
|
+
PlasmaTestnet: PlasmaTestnet,
|
|
6246
7014
|
Plume: Plume,
|
|
6247
7015
|
PlumeTestnet: PlumeTestnet,
|
|
6248
7016
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -6292,6 +7060,71 @@ var Chains = {
|
|
|
6292
7060
|
return chain.cctp?.contracts.v2 !== undefined;
|
|
6293
7061
|
}
|
|
6294
7062
|
|
|
7063
|
+
/**
|
|
7064
|
+
* Chains the Fee Service accepts as a SOURCE for source-paid ("receive-exact")
|
|
7065
|
+
* CCTP v2 fees. An explicit allowlist is required because the
|
|
7066
|
+
* `TokenMessengerWithFees` wrapper address is now shared with the fast-deposit
|
|
7067
|
+
* forwarder path, so wrapper presence no longer implies source-fee support.
|
|
7068
|
+
* Keep in sync with backend coverage.
|
|
7069
|
+
*/ const SOURCE_FEE_SUPPORTED_ALLOWLIST = new Set([
|
|
7070
|
+
// Mainnet
|
|
7071
|
+
Blockchain.Ethereum,
|
|
7072
|
+
Blockchain.Base,
|
|
7073
|
+
Blockchain.Arbitrum,
|
|
7074
|
+
Blockchain.Unichain,
|
|
7075
|
+
Blockchain.Optimism,
|
|
7076
|
+
Blockchain.Codex,
|
|
7077
|
+
Blockchain.Ink,
|
|
7078
|
+
Blockchain.Plume,
|
|
7079
|
+
Blockchain.Linea,
|
|
7080
|
+
Blockchain.World_Chain,
|
|
7081
|
+
// Testnet counterparts
|
|
7082
|
+
Blockchain.Ethereum_Sepolia,
|
|
7083
|
+
Blockchain.Base_Sepolia,
|
|
7084
|
+
Blockchain.Arbitrum_Sepolia,
|
|
7085
|
+
Blockchain.Unichain_Sepolia,
|
|
7086
|
+
Blockchain.Optimism_Sepolia,
|
|
7087
|
+
Blockchain.Codex_Testnet,
|
|
7088
|
+
Blockchain.Ink_Testnet,
|
|
7089
|
+
Blockchain.Plume_Testnet,
|
|
7090
|
+
Blockchain.Linea_Sepolia,
|
|
7091
|
+
Blockchain.World_Chain_Sepolia
|
|
7092
|
+
]);
|
|
7093
|
+
/**
|
|
7094
|
+
* Check whether a chain supports source-paid ("receive-exact") CCTP v2 fees.
|
|
7095
|
+
*
|
|
7096
|
+
* A chain qualifies when it supports CCTP v2, carries a `TokenMessengerWithFees`
|
|
7097
|
+
* wrapper, and is in {@link SOURCE_FEE_SUPPORTED_ALLOWLIST}.
|
|
7098
|
+
*
|
|
7099
|
+
* @param chain - The chain definition to check. A nullish or non-object value
|
|
7100
|
+
* returns `false` rather than throwing, since consumers may call from plain
|
|
7101
|
+
* JavaScript.
|
|
7102
|
+
* @returns `true` when the chain supports CCTP v2, carries a
|
|
7103
|
+
* `TokenMessengerWithFees` wrapper, and is on the source-fee allowlist;
|
|
7104
|
+
* `false` otherwise.
|
|
7105
|
+
*
|
|
7106
|
+
* @example
|
|
7107
|
+
* ```typescript
|
|
7108
|
+
* import { Chains, hasSourceFeeSupport } from '@core/chains'
|
|
7109
|
+
*
|
|
7110
|
+
* hasSourceFeeSupport(Chains.Optimism) // true
|
|
7111
|
+
* hasSourceFeeSupport(Chains.Solana) // false
|
|
7112
|
+
* ```
|
|
7113
|
+
*/ function hasSourceFeeSupport(chain) {
|
|
7114
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for nullish/non-object input from plain JS
|
|
7115
|
+
if (chain === null || typeof chain !== 'object') {
|
|
7116
|
+
return false;
|
|
7117
|
+
}
|
|
7118
|
+
if (!isCCTPV2Supported(chain)) {
|
|
7119
|
+
return false;
|
|
7120
|
+
}
|
|
7121
|
+
const wrapper = chain.cctp.contracts.v2.tokenMessengerWithFees;
|
|
7122
|
+
if (typeof wrapper !== 'string' || wrapper.length === 0) {
|
|
7123
|
+
return false;
|
|
7124
|
+
}
|
|
7125
|
+
return SOURCE_FEE_SUPPORTED_ALLOWLIST.has(chain.chain);
|
|
7126
|
+
}
|
|
7127
|
+
|
|
6295
7128
|
/**
|
|
6296
7129
|
* Check if a chain supports a specific type of custom smart contract logic.
|
|
6297
7130
|
*
|
|
@@ -6342,6 +7175,73 @@ var Chains = {
|
|
|
6342
7175
|
return typeof contractAddress === 'string' && contractAddress.trim().length > 0;
|
|
6343
7176
|
}
|
|
6344
7177
|
|
|
7178
|
+
/**
|
|
7179
|
+
* Check whether a given chain supports Gateway protocol version 1.
|
|
7180
|
+
*
|
|
7181
|
+
* This type guard function examines a chain definition to determine if it has Gateway v1
|
|
7182
|
+
* contract configurations. It checks that the chain has a gateway object with a
|
|
7183
|
+
* `contracts.v1` entry present.
|
|
7184
|
+
*
|
|
7185
|
+
* @param chain - The chain definition to check for Gateway v1 support
|
|
7186
|
+
* @returns `true` if `chain.gateway?.contracts?.v1` is defined, `false` otherwise
|
|
7187
|
+
*
|
|
7188
|
+
* @example
|
|
7189
|
+
* ```typescript
|
|
7190
|
+
* import { isGatewayV1Supported, Base } from '@core/chains'
|
|
7191
|
+
*
|
|
7192
|
+
* if (isGatewayV1Supported(Base)) {
|
|
7193
|
+
* // TypeScript knows Base.gateway is defined here
|
|
7194
|
+
* console.log('Gateway domain:', Base.gateway.domain)
|
|
7195
|
+
* console.log('Wallet address:', Base.gateway.contracts.v1.wallet)
|
|
7196
|
+
* console.log('Minter address:', Base.gateway.contracts.v1.minter)
|
|
7197
|
+
* }
|
|
7198
|
+
* ```
|
|
7199
|
+
*
|
|
7200
|
+
* @example
|
|
7201
|
+
* ```typescript
|
|
7202
|
+
* // Usage in conditional flow
|
|
7203
|
+
* function getGatewayWalletAddress(chain: ChainDefinition): string | null {
|
|
7204
|
+
* if (isGatewayV1Supported(chain)) {
|
|
7205
|
+
* return chain.gateway.contracts.v1.wallet
|
|
7206
|
+
* }
|
|
7207
|
+
* return null
|
|
7208
|
+
* }
|
|
7209
|
+
* ```
|
|
7210
|
+
*/ function isGatewayV1Supported(chain) {
|
|
7211
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass a gateway object without contracts
|
|
7212
|
+
return chain.gateway?.contracts?.v1 !== undefined;
|
|
7213
|
+
}
|
|
7214
|
+
|
|
7215
|
+
/**
|
|
7216
|
+
* Temporary allowlist of chains permitted to initiate Gateway fast deposits.
|
|
7217
|
+
* Only chains keyed here are eligible; all others are rejected. Using the
|
|
7218
|
+
* {@link Blockchain} enum keeps entries type-safe and catches typos at compile
|
|
7219
|
+
* time. Remove this allowlist once roll-out is complete.
|
|
7220
|
+
*/ new Set([
|
|
7221
|
+
// Mainnet
|
|
7222
|
+
Blockchain.Ethereum,
|
|
7223
|
+
Blockchain.Base,
|
|
7224
|
+
Blockchain.Arbitrum,
|
|
7225
|
+
Blockchain.Unichain,
|
|
7226
|
+
Blockchain.Optimism,
|
|
7227
|
+
Blockchain.Codex,
|
|
7228
|
+
Blockchain.Ink,
|
|
7229
|
+
Blockchain.Plume,
|
|
7230
|
+
Blockchain.Linea,
|
|
7231
|
+
Blockchain.World_Chain,
|
|
7232
|
+
// Testnet counterparts
|
|
7233
|
+
Blockchain.Ethereum_Sepolia,
|
|
7234
|
+
Blockchain.Base_Sepolia,
|
|
7235
|
+
Blockchain.Arbitrum_Sepolia,
|
|
7236
|
+
Blockchain.Unichain_Sepolia,
|
|
7237
|
+
Blockchain.Optimism_Sepolia,
|
|
7238
|
+
Blockchain.Codex_Testnet,
|
|
7239
|
+
Blockchain.Ink_Testnet,
|
|
7240
|
+
Blockchain.Plume_Testnet,
|
|
7241
|
+
Blockchain.Linea_Sepolia,
|
|
7242
|
+
Blockchain.World_Chain_Sepolia
|
|
7243
|
+
]);
|
|
7244
|
+
|
|
6345
7245
|
/**
|
|
6346
7246
|
* Zod schema for validating Gateway v1 contract addresses.
|
|
6347
7247
|
*
|
|
@@ -6363,7 +7263,10 @@ var Chains = {
|
|
|
6363
7263
|
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
6364
7264
|
depositForHandler: zod.z.string({
|
|
6365
7265
|
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
6366
|
-
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
7266
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional(),
|
|
7267
|
+
genericExecutor: zod.z.string({
|
|
7268
|
+
invalid_type_error: 'Gateway genericExecutor address must be a string.'
|
|
7269
|
+
}).min(1, 'Gateway genericExecutor address cannot be empty.').optional()
|
|
6367
7270
|
}).strict() // Reject any additional properties not defined in the schema
|
|
6368
7271
|
;
|
|
6369
7272
|
/**
|
|
@@ -8310,6 +9213,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8310
9213
|
[Blockchain.Noble]: 'uusdc',
|
|
8311
9214
|
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
8312
9215
|
[Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
9216
|
+
[Blockchain.Plasma]: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
8313
9217
|
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
8314
9218
|
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
8315
9219
|
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -8346,6 +9250,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8346
9250
|
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
8347
9251
|
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
8348
9252
|
[Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
9253
|
+
[Blockchain.Plasma_Testnet]: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
8349
9254
|
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
8350
9255
|
[Blockchain.Polkadot_Westmint]: '31337',
|
|
8351
9256
|
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -8408,6 +9313,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8408
9313
|
[Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
|
|
8409
9314
|
[Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
|
|
8410
9315
|
[Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
|
|
9316
|
+
[Blockchain.Plasma]: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
8411
9317
|
[Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
|
|
8412
9318
|
[Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
|
|
8413
9319
|
// =========================================================================
|
|
@@ -8416,7 +9322,8 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8416
9322
|
[Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
|
|
8417
9323
|
[Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
|
|
8418
9324
|
[Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
|
|
8419
|
-
[Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
|
|
9325
|
+
[Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
|
|
9326
|
+
[Blockchain.Plasma_Testnet]: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330'
|
|
8420
9327
|
}
|
|
8421
9328
|
};
|
|
8422
9329
|
|
|
@@ -9230,6 +10137,9 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9230
10137
|
* The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
|
|
9231
10138
|
* This prefix is right-padded to 24 bytes in the final hookData.
|
|
9232
10139
|
*/ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
|
|
10140
|
+
/**
|
|
10141
|
+
* Maximum value of the 32-bit `version` field in a `cctp-forward` frame.
|
|
10142
|
+
*/ const MAX_UINT32 = 0xffffffff;
|
|
9233
10143
|
/**
|
|
9234
10144
|
* CCTP forwarding version number.
|
|
9235
10145
|
*
|
|
@@ -9240,6 +10150,16 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9240
10150
|
*
|
|
9241
10151
|
* Set to 0 when no additional Circle-reserved data is needed.
|
|
9242
10152
|
*/ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
|
|
10153
|
+
/**
|
|
10154
|
+
* Length in bytes of a Solana owner (ed25519 / PDA) public key.
|
|
10155
|
+
*/ const SOLANA_PUBKEY_LENGTH = 32;
|
|
10156
|
+
/**
|
|
10157
|
+
* Byte length of the Solana ATA-creation forwarding payload appended after the
|
|
10158
|
+
* `cctp-forward` frame: `createAta` (1 byte) + `ataOwner` (32 bytes).
|
|
10159
|
+
*
|
|
10160
|
+
* Circle's Orbit relayer decodes exactly this many bytes; see
|
|
10161
|
+
* {@link buildSolanaAtaForwardingHookData}.
|
|
10162
|
+
*/ const SOLANA_ATA_FORWARD_PAYLOAD_LENGTH = 1 + SOLANA_PUBKEY_LENGTH;
|
|
9243
10163
|
/**
|
|
9244
10164
|
* Build the hookData bytes for CCTP forwarding.
|
|
9245
10165
|
*
|
|
@@ -9296,6 +10216,518 @@ function buildForwardingHookData() {
|
|
|
9296
10216
|
cachedHookDataHex = '0x' + Array.from(buffer).map((b)=>b.toString(16).padStart(2, '0')).join('');
|
|
9297
10217
|
return cachedHookDataHex;
|
|
9298
10218
|
}
|
|
10219
|
+
/**
|
|
10220
|
+
* Build a `cctp-forward` hookData frame with a versioned header and an appended
|
|
10221
|
+
* opaque payload.
|
|
10222
|
+
*
|
|
10223
|
+
* Produces the 32-byte `cctp-forward` header (24-byte ASCII magic + `uint32`
|
|
10224
|
+
* version + `uint32` `dataLength = 0`) followed by `payload` appended verbatim.
|
|
10225
|
+
* Unlike {@link buildForwardingHookData} — which emits only the fixed,
|
|
10226
|
+
* version-0 empty frame — this lets the caller set the frame `version` and
|
|
10227
|
+
* carry an inner payload such as a GenericExecutor blob.
|
|
10228
|
+
*
|
|
10229
|
+
* @remarks
|
|
10230
|
+
* The forwarder reads only the 32-byte header to decide that a hook is
|
|
10231
|
+
* forwardable, then strips it before the inner payload is consumed downstream
|
|
10232
|
+
* (e.g. the GenericExecutor `abi.decode`s the appended blob, never the frame).
|
|
10233
|
+
* `dataLength` stays `0` because the appended bytes are opaque to the forwarder
|
|
10234
|
+
* — it is not the payload's length.
|
|
10235
|
+
*
|
|
10236
|
+
* @param version - The `uint32` frame version (e.g. `1` for the GenericExecutor
|
|
10237
|
+
* FORWARD path). Must be an integer in `[0, 0xFFFFFFFF]`.
|
|
10238
|
+
* @param payload - A 0x-prefixed hex string appended after the header (e.g. the
|
|
10239
|
+
* bare GenericExecutor blob from `buildDepositForGenericExecutorPayload`).
|
|
10240
|
+
* @returns A 0x-prefixed hex string: the 32-byte frame followed by `payload`.
|
|
10241
|
+
* @throws {KitError} If `version` is out of `uint32` range or `payload` is not
|
|
10242
|
+
* a 0x-prefixed hex string (INPUT_VALIDATION_FAILED).
|
|
10243
|
+
*
|
|
10244
|
+
* @example
|
|
10245
|
+
* ```typescript
|
|
10246
|
+
* import {
|
|
10247
|
+
* buildDepositForGenericExecutorPayload,
|
|
10248
|
+
* buildForwardingHookDataWithPayload,
|
|
10249
|
+
* padAddressToBytes32,
|
|
10250
|
+
* } from '@core/utils'
|
|
10251
|
+
*
|
|
10252
|
+
* const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
|
|
10253
|
+
* dappId: 'gateway_deposit',
|
|
10254
|
+
* domainId: 26,
|
|
10255
|
+
* handler: '0xHandlerAddressOnDestinationChain',
|
|
10256
|
+
* params: [USDC_ARC, user, 0],
|
|
10257
|
+
* recoveryAddress: padAddressToBytes32(user),
|
|
10258
|
+
* })
|
|
10259
|
+
*
|
|
10260
|
+
* // Wrap for the prepaid Quote-API FORWARD path (frame version 1).
|
|
10261
|
+
* const hookData = buildForwardingHookDataWithPayload(1, geBlob)
|
|
10262
|
+
* ```
|
|
10263
|
+
*/ function buildForwardingHookDataWithPayload(version, payload) {
|
|
10264
|
+
if (!Number.isInteger(version) || version < 0 || version > MAX_UINT32) {
|
|
10265
|
+
throw createValidationFailedError$1('version', version, 'Expected an integer in the uint32 range [0, 4294967295]');
|
|
10266
|
+
}
|
|
10267
|
+
if (!bytes.isHexString(payload)) {
|
|
10268
|
+
throw createValidationFailedError$1('payload', payload, 'Expected a 0x-prefixed hex string');
|
|
10269
|
+
}
|
|
10270
|
+
// `isHexString` accepts odd-length hex (e.g. '0xabc'); catch it here so it
|
|
10271
|
+
// surfaces as a KitError rather than ethers' raw "hex data is odd-length"
|
|
10272
|
+
// from `concat` below.
|
|
10273
|
+
if (payload.length % 2 !== 0) {
|
|
10274
|
+
throw createValidationFailedError$1('payload', payload, 'Expected an even-length (whole-byte) hex string');
|
|
10275
|
+
}
|
|
10276
|
+
// 32-byte header: 24-byte magic + uint32 version + uint32 dataLength (0).
|
|
10277
|
+
const frame = new Uint8Array(32);
|
|
10278
|
+
frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
|
|
10279
|
+
const view = new DataView(frame.buffer);
|
|
10280
|
+
view.setUint32(24, version, false) // big-endian
|
|
10281
|
+
;
|
|
10282
|
+
view.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 0
|
|
10283
|
+
;
|
|
10284
|
+
return bytes.hexlify(bytes.concat([
|
|
10285
|
+
frame,
|
|
10286
|
+
payload
|
|
10287
|
+
]));
|
|
10288
|
+
}
|
|
10289
|
+
/**
|
|
10290
|
+
* Build a `cctp-forward` hookData frame that instructs Circle's Orbit relayer to
|
|
10291
|
+
* create the recipient's Associated Token Account (ATA) before minting on Solana.
|
|
10292
|
+
*
|
|
10293
|
+
* When an EVM→Solana bridge is forwarded, the destination mint targets the
|
|
10294
|
+
* recipient's USDC ATA — which does not exist for a fresh wallet. This frame
|
|
10295
|
+
* tells the relayer to prepend an idempotent `createAssociatedTokenAccount`
|
|
10296
|
+
* instruction (the relayer pays the rent) so the mint always succeeds.
|
|
10297
|
+
*
|
|
10298
|
+
* Unlike {@link buildForwardingHookData} (an empty version-0 frame), this emits
|
|
10299
|
+
* a version-0 frame whose 32-bit `dataLength` is set to
|
|
10300
|
+
* {@link SOLANA_ATA_FORWARD_PAYLOAD_LENGTH} (33), followed by the payload the
|
|
10301
|
+
* relayer decodes:
|
|
10302
|
+
* - Byte 0: `createAta` flag, always `1`
|
|
10303
|
+
* - Bytes 1-32: the recipient's 32-byte Solana owner public key (`ataOwner`)
|
|
10304
|
+
*
|
|
10305
|
+
* @remarks
|
|
10306
|
+
* `ataOwner` is the recipient's *wallet* public key, not the derived ATA. The
|
|
10307
|
+
* relayer re-derives the ATA from `ataOwner` and the USDC mint and requires it
|
|
10308
|
+
* to equal the burn's `mintRecipient`, so callers must pass the same owner used
|
|
10309
|
+
* to derive `mintRecipient`. The all-zero key is reserved as "absent owner" and
|
|
10310
|
+
* is rejected.
|
|
10311
|
+
*
|
|
10312
|
+
* @param ataOwner - The recipient's 32-byte Solana owner public key.
|
|
10313
|
+
* @returns A 0x-prefixed hex string: the 32-byte frame followed by the 33-byte
|
|
10314
|
+
* Solana ATA payload.
|
|
10315
|
+
* @throws {KitError} If `ataOwner` is not exactly 32 bytes, or is the all-zero
|
|
10316
|
+
* key (INPUT_VALIDATION_FAILED).
|
|
10317
|
+
*
|
|
10318
|
+
* @example
|
|
10319
|
+
* ```typescript
|
|
10320
|
+
* import { PublicKey } from '@solana/web3.js'
|
|
10321
|
+
* import { buildSolanaAtaForwardingHookData } from '@core/utils'
|
|
10322
|
+
*
|
|
10323
|
+
* const owner = new PublicKey('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM')
|
|
10324
|
+
* const hookData = buildSolanaAtaForwardingHookData(owner.toBytes())
|
|
10325
|
+
*
|
|
10326
|
+
* // Use with the forwarded depositForBurnWithHook action so the relayer
|
|
10327
|
+
* // creates the recipient ATA before minting.
|
|
10328
|
+
* await adapter.prepareAction('cctp.v2.depositForBurnWithHook', {
|
|
10329
|
+
* amount: BigInt('1000000'),
|
|
10330
|
+
* mintRecipient: '0x...',
|
|
10331
|
+
* maxFee: BigInt('50000'),
|
|
10332
|
+
* minFinalityThreshold: 1000,
|
|
10333
|
+
* fromChain: ethereum,
|
|
10334
|
+
* toChain: solana,
|
|
10335
|
+
* hookData,
|
|
10336
|
+
* })
|
|
10337
|
+
* ```
|
|
10338
|
+
*/ function buildSolanaAtaForwardingHookData(ataOwner) {
|
|
10339
|
+
if (!(ataOwner instanceof Uint8Array) || ataOwner.length !== SOLANA_PUBKEY_LENGTH) {
|
|
10340
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, `Expected a ${String(SOLANA_PUBKEY_LENGTH)}-byte Solana owner public key`);
|
|
10341
|
+
}
|
|
10342
|
+
if (ataOwner.every((byte)=>byte === 0)) {
|
|
10343
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, 'Expected a non-zero Solana owner public key; the all-zero key is reserved as "absent owner"');
|
|
10344
|
+
}
|
|
10345
|
+
// Inner payload: createAta(1) + ataOwner(32).
|
|
10346
|
+
const payload = new Uint8Array(SOLANA_ATA_FORWARD_PAYLOAD_LENGTH);
|
|
10347
|
+
payload[0] = 1 // createAta = true
|
|
10348
|
+
;
|
|
10349
|
+
payload.set(ataOwner, 1);
|
|
10350
|
+
// 32-byte header: 24-byte magic + uint32 version(0) + uint32 dataLength(33).
|
|
10351
|
+
// The relayer reads dataLength from the v0 frame to slice the inner payload,
|
|
10352
|
+
// so it MUST reflect the appended byte count (unlike the GenericExecutor path).
|
|
10353
|
+
const frame = new Uint8Array(32);
|
|
10354
|
+
frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
|
|
10355
|
+
const view = new DataView(frame.buffer);
|
|
10356
|
+
view.setUint32(24, CCTP_FORWARD_VERSION, false) // big-endian, 0
|
|
10357
|
+
;
|
|
10358
|
+
view.setUint32(28, SOLANA_ATA_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 33
|
|
10359
|
+
;
|
|
10360
|
+
return bytes.hexlify(bytes.concat([
|
|
10361
|
+
frame,
|
|
10362
|
+
payload
|
|
10363
|
+
]));
|
|
10364
|
+
}
|
|
10365
|
+
|
|
10366
|
+
/**
|
|
10367
|
+
* `version` field of the GenericExecutor hookData, in both the
|
|
10368
|
+
* `circle-generic-executor` header (`uint32`) and the ABI tuple (`uint8`). The
|
|
10369
|
+
* executor reverts if it is not `1`.
|
|
10370
|
+
*
|
|
10371
|
+
* @see https://circlepay.atlassian.net/wiki/spaces/~712020cd79585b52ea4353b4720c277fbfcca6/pages/3049291839
|
|
10372
|
+
*/ const GENERIC_EXECUTOR_HOOK_DATA_VERSION = 1;
|
|
10373
|
+
/**
|
|
10374
|
+
* ASCII magic that prefixes a GenericExecutor hookData blob.
|
|
10375
|
+
*
|
|
10376
|
+
* The executor auto-detects its payload by this string. It is left-aligned and
|
|
10377
|
+
* zero-padded to 24 bytes in the header, mirroring the `cctp-forward` frame
|
|
10378
|
+
* layout (magic + `uint32` version + `uint32` dataLength).
|
|
10379
|
+
*/ const GENERIC_EXECUTOR_MAGIC_PREFIX = 'circle-generic-executor';
|
|
10380
|
+
/**
|
|
10381
|
+
* Prepend the 32-byte `circle-generic-executor` header to the ABI tuple.
|
|
10382
|
+
*
|
|
10383
|
+
* Header layout (mirrors the `cctp-forward` frame): 24-byte zero-padded ASCII
|
|
10384
|
+
* magic + `uint32` version + `uint32` dataLength. Unlike the `cctp-forward`
|
|
10385
|
+
* frame (which the forwarder strips and so carries `dataLength = 0`), this
|
|
10386
|
+
* header's dataLength is the byte length of the ABI tuple that follows, since
|
|
10387
|
+
* the executor consumes both.
|
|
10388
|
+
*/ function prependGenericExecutorHeader(abiTuple) {
|
|
10389
|
+
const header = new Uint8Array(32);
|
|
10390
|
+
header.set(new TextEncoder().encode(GENERIC_EXECUTOR_MAGIC_PREFIX), 0);
|
|
10391
|
+
// Byte length of the ABI tuple that the header announces.
|
|
10392
|
+
const tupleByteLength = (abiTuple.length - 2) / 2;
|
|
10393
|
+
const view = new DataView(header.buffer);
|
|
10394
|
+
view.setUint32(24, GENERIC_EXECUTOR_HOOK_DATA_VERSION, false) // big-endian
|
|
10395
|
+
;
|
|
10396
|
+
view.setUint32(28, tupleByteLength, false) // big-endian
|
|
10397
|
+
;
|
|
10398
|
+
return bytes.hexlify(bytes.concat([
|
|
10399
|
+
header,
|
|
10400
|
+
abiTuple
|
|
10401
|
+
]));
|
|
10402
|
+
}
|
|
10403
|
+
/**
|
|
10404
|
+
* Left-pad a 20-byte EVM address to a 32-byte (`bytes32`) hex string.
|
|
10405
|
+
*
|
|
10406
|
+
* Mirrors viem's `pad(address, size 32)` and CCTP's `mintRecipient`
|
|
10407
|
+
* convention. Solana addresses are already 32 bytes and need no padding.
|
|
10408
|
+
*
|
|
10409
|
+
* @param address - A 0x-prefixed 20-byte EVM address.
|
|
10410
|
+
* @returns The address left-zero-padded to a 0x-prefixed 32-byte hex string.
|
|
10411
|
+
* @throws {KitError} If `address` is not a valid EVM address (INPUT_VALIDATION_FAILED).
|
|
10412
|
+
*
|
|
10413
|
+
* @example
|
|
10414
|
+
* ```typescript
|
|
10415
|
+
* import { padAddressToBytes32 } from '@core/utils'
|
|
10416
|
+
*
|
|
10417
|
+
* padAddressToBytes32('0x75275Aff2D01699D922f045b69ed291311209738')
|
|
10418
|
+
* // '0x00000000000000000000000075275aff2d01699d922f045b69ed291311209738'
|
|
10419
|
+
* ```
|
|
10420
|
+
*/ function padAddressToBytes32(address$1) {
|
|
10421
|
+
if (!address.isAddress(address$1)) {
|
|
10422
|
+
throw createValidationFailedError$1('address', address$1, 'Expected a valid 20-byte EVM address');
|
|
10423
|
+
}
|
|
10424
|
+
// bytes32 is raw bytes, not a checksummed address — emit lowercase so it
|
|
10425
|
+
// matches ABI-decoded output.
|
|
10426
|
+
return bytes.hexZeroPad(address.getAddress(address$1), 32).toLowerCase();
|
|
10427
|
+
}
|
|
10428
|
+
/**
|
|
10429
|
+
* Encode the bare GenericExecutor + DepositForHandler payload for a CCTP v2
|
|
10430
|
+
* fast-transfer deposit into a dApp.
|
|
10431
|
+
*
|
|
10432
|
+
* Builds the layers inner→outer:
|
|
10433
|
+
* 1. dApp calldata — the dApp function selector + ABI params, with each amount
|
|
10434
|
+
* slot left as the caller-supplied placeholder.
|
|
10435
|
+
* 2. handler calldata — `(depositContract, approvalTarget, depositCalldata, amountIndices)`
|
|
10436
|
+
* for `DepositForHandler`.
|
|
10437
|
+
* 3. ABI tuple — `(uint8 version, bytes32 recoveryAddress, address handler, bytes handlerCalldata)`.
|
|
10438
|
+
* No handler selector travels on the wire; the executor applies a fixed one.
|
|
10439
|
+
* 4. header — the 32-byte `circle-generic-executor` magic frame prepended to the
|
|
10440
|
+
* tuple, by which the executor auto-detects the payload.
|
|
10441
|
+
*
|
|
10442
|
+
* The returned `hookData` is the bare GenericExecutor blob (header ‖ tuple) — the
|
|
10443
|
+
* exact bytes the executor consumes. It carries no `cctp-forward` envelope. For
|
|
10444
|
+
* the prepaid Quote-API FORWARD path, wrap it with
|
|
10445
|
+
* {@link buildForwardingHookDataWithPayload}; the forwarder strips that envelope
|
|
10446
|
+
* before the executor reads the blob.
|
|
10447
|
+
*
|
|
10448
|
+
* @param options - See {@link BuildDepositForGenericExecutorPayloadParams}.
|
|
10449
|
+
* @returns The encoded {@link DepositForGenericExecutorPayload} layers.
|
|
10450
|
+
* @throws {KitError} If `options` is not an object, `dappId` is unknown,
|
|
10451
|
+
* `config.deployments` is not an array, neither `domainId` nor
|
|
10452
|
+
* `destinationChain` resolves a domain (or the two disagree), no deployment
|
|
10453
|
+
* exists for the resolved domain, the deposit contract cannot be resolved (a
|
|
10454
|
+
* built-in deployment supplied without a `destinationChain`), no `handler` is
|
|
10455
|
+
* supplied and it cannot be resolved from `destinationChain`,
|
|
10456
|
+
* `recoveryAddress`/`handler`/contract addresses are malformed,
|
|
10457
|
+
* `config.function` is not a valid Solidity function signature, `params`
|
|
10458
|
+
* length does not match the dApp signature, `params` values fail ABI encoding
|
|
10459
|
+
* (type mismatch), `config.dynamicAmountIndices` is not an array, or an amount
|
|
10460
|
+
* index is out of range (all INPUT_VALIDATION_FAILED).
|
|
10461
|
+
*
|
|
10462
|
+
* @example Encode a 1-click cross-chain Circle Gateway deposit
|
|
10463
|
+
* ```typescript
|
|
10464
|
+
* import { buildDepositForGenericExecutorPayload, padAddressToBytes32 } from '@core/utils'
|
|
10465
|
+
* import { ArcTestnet } from '@core/chains'
|
|
10466
|
+
*
|
|
10467
|
+
* const user = '0x75275Aff2D01699D922f045b69ed291311209738'
|
|
10468
|
+
* const usdcArc = '0x3600000000000000000000000000000000000000'
|
|
10469
|
+
* const { hookData } = buildDepositForGenericExecutorPayload({
|
|
10470
|
+
* dappId: 'gateway_deposit',
|
|
10471
|
+
* destinationChain: ArcTestnet, // resolves the GatewayWallet + DepositForHandler
|
|
10472
|
+
* // depositFor(address token, address depositor, uint256 value), amount idx [2]
|
|
10473
|
+
* params: [usdcArc, user, 0],
|
|
10474
|
+
* recoveryAddress: padAddressToBytes32(user),
|
|
10475
|
+
* })
|
|
10476
|
+
*
|
|
10477
|
+
* // Pass hookData straight into the CCTP v2 fast transfer.
|
|
10478
|
+
* console.log(hookData)
|
|
10479
|
+
* ```
|
|
10480
|
+
*/ function buildDepositForGenericExecutorPayload(options) {
|
|
10481
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for plain-JS callers
|
|
10482
|
+
if (options === null || typeof options !== 'object') {
|
|
10483
|
+
throw createValidationFailedError$1('options', options, 'Expected an options object');
|
|
10484
|
+
}
|
|
10485
|
+
const { dappId, params: dappParams } = options;
|
|
10486
|
+
const registry = options.config ?? DAPP_CONFIG;
|
|
10487
|
+
// Look up the dApp before resolving the deposit contract or handler, so an
|
|
10488
|
+
// unknown dApp reports the actionable "Unknown dApp" error rather than an
|
|
10489
|
+
// unresolved-handler error.
|
|
10490
|
+
const config = registry[dappId];
|
|
10491
|
+
if (config === undefined) {
|
|
10492
|
+
throw createValidationFailedError$1('dappId', dappId, `Unknown dApp. Known dApps: ${Object.keys(registry).join(', ')}`);
|
|
10493
|
+
}
|
|
10494
|
+
if (!Array.isArray(config.deployments)) {
|
|
10495
|
+
throw createValidationFailedError$1('deployments', config.deployments, `Expected an array of deployments for dApp '${dappId}'`);
|
|
10496
|
+
}
|
|
10497
|
+
// Array.isArray narrows to `any[]`; re-assert the concrete type.
|
|
10498
|
+
const deployments = config.deployments;
|
|
10499
|
+
// Destination CCTP domain: taken from `destinationChain` when supplied (the
|
|
10500
|
+
// chain is the (network, domain) key), else the explicit `domainId`.
|
|
10501
|
+
const domainId = resolveDomainId(options);
|
|
10502
|
+
const deployment = deployments.find((d)=>d.domainId === domainId);
|
|
10503
|
+
if (deployment === undefined) {
|
|
10504
|
+
throw createValidationFailedError$1('domainId', domainId, `No '${dappId}' deployment for domain ${String(domainId)}`);
|
|
10505
|
+
}
|
|
10506
|
+
if (!Array.isArray(dappParams)) {
|
|
10507
|
+
throw createValidationFailedError$1('params', dappParams, 'Expected an array of ABI-ordered parameters');
|
|
10508
|
+
}
|
|
10509
|
+
if (!bytes.isHexString(options.recoveryAddress, 32)) {
|
|
10510
|
+
throw createValidationFailedError$1('recoveryAddress', options.recoveryAddress, 'Expected a 0x-prefixed 32-byte (bytes32) hex string');
|
|
10511
|
+
}
|
|
10512
|
+
// Deposit contract: the deployment's own address, or — for the built-in
|
|
10513
|
+
// gateway_deposit deployment, which carries none — the destination chain's
|
|
10514
|
+
// GatewayWallet, so the address lives only in @core/chains.
|
|
10515
|
+
const resolvedDepositContract = resolveDepositContract(deployment, options, dappId);
|
|
10516
|
+
// Handler: an explicit `handler` always wins; otherwise resolve the
|
|
10517
|
+
// DepositForHandler from `destinationChain`. The chain is the (network, domain)
|
|
10518
|
+
// key, so a shared CCTP domain (Arc is 26 on both testnet and mainnet) can
|
|
10519
|
+
// never resolve the wrong network's handler and strand funds.
|
|
10520
|
+
const handler = resolveDepositForHandler(options, domainId);
|
|
10521
|
+
if (!address.isAddress(handler)) {
|
|
10522
|
+
throw createValidationFailedError$1('handler', handler, 'Expected a valid EVM address');
|
|
10523
|
+
}
|
|
10524
|
+
const depositContract = assertAddress(resolvedDepositContract, 'depositContract');
|
|
10525
|
+
const approvalTarget = assertAddress(deployment.approvalTarget ?? resolvedDepositContract, 'approvalTarget');
|
|
10526
|
+
// 1. dApp calldata: selector + ABI-encoded params (amount slots stay as placeholders).
|
|
10527
|
+
let dappInterface;
|
|
10528
|
+
try {
|
|
10529
|
+
dappInterface = new abi.Interface([
|
|
10530
|
+
`function ${config.function}`
|
|
10531
|
+
]);
|
|
10532
|
+
} catch {
|
|
10533
|
+
throw createValidationFailedError$1('function', config.function, 'Expected a valid Solidity function signature');
|
|
10534
|
+
}
|
|
10535
|
+
const rawFragment = dappInterface.fragments[0];
|
|
10536
|
+
/* v8 ignore start -- defensive: guards against unexpected library behavior */ if (rawFragment === undefined || rawFragment.type !== 'function') {
|
|
10537
|
+
throw createValidationFailedError$1('function', config.function, 'Expected a valid Solidity function signature');
|
|
10538
|
+
}
|
|
10539
|
+
/* v8 ignore stop */ const fragment = rawFragment;
|
|
10540
|
+
if (dappParams.length !== fragment.inputs.length) {
|
|
10541
|
+
throw createValidationFailedError$1('params', dappParams, `'${config.function}' expects ${String(fragment.inputs.length)} params, got ${String(dappParams.length)}`);
|
|
10542
|
+
}
|
|
10543
|
+
let depositCalldata;
|
|
10544
|
+
try {
|
|
10545
|
+
depositCalldata = dappInterface.encodeFunctionData(fragment, dappParams);
|
|
10546
|
+
} catch {
|
|
10547
|
+
throw createValidationFailedError$1('params', dappParams, 'ABI encoding failed — check that each param matches the expected Solidity type');
|
|
10548
|
+
}
|
|
10549
|
+
if (!Array.isArray(config.dynamicAmountIndices)) {
|
|
10550
|
+
throw createValidationFailedError$1('dynamicAmountIndices', config.dynamicAmountIndices, `Expected an array of amount indices for dApp '${dappId}'`);
|
|
10551
|
+
}
|
|
10552
|
+
// Array.isArray narrows to `any[]`; re-assert the concrete type.
|
|
10553
|
+
const dynamicAmountIndices = config.dynamicAmountIndices;
|
|
10554
|
+
// Amount byte offsets in depositCalldata: 4 (selector) + paramIndex * 32.
|
|
10555
|
+
const amountIndices = dynamicAmountIndices.map((paramIndex)=>{
|
|
10556
|
+
if (!Number.isInteger(paramIndex) || paramIndex < 0 || paramIndex >= fragment.inputs.length) {
|
|
10557
|
+
throw createValidationFailedError$1('dynamicAmountIndices', paramIndex, `Index out of range for '${config.function}' (${String(fragment.inputs.length)} params)`);
|
|
10558
|
+
}
|
|
10559
|
+
return BigInt(4 + paramIndex * 32);
|
|
10560
|
+
});
|
|
10561
|
+
// 2. Handler layer.
|
|
10562
|
+
const handlerCalldata = abi.defaultAbiCoder.encode([
|
|
10563
|
+
'address',
|
|
10564
|
+
'address',
|
|
10565
|
+
'bytes',
|
|
10566
|
+
'uint256[]'
|
|
10567
|
+
], [
|
|
10568
|
+
depositContract,
|
|
10569
|
+
approvalTarget,
|
|
10570
|
+
depositCalldata,
|
|
10571
|
+
amountIndices
|
|
10572
|
+
]);
|
|
10573
|
+
// 3. Executor ABI tuple. No handler selector travels on the wire — the
|
|
10574
|
+
// executor applies a fixed selector internally.
|
|
10575
|
+
const executorTuple = abi.defaultAbiCoder.encode([
|
|
10576
|
+
'uint8',
|
|
10577
|
+
'bytes32',
|
|
10578
|
+
'address',
|
|
10579
|
+
'bytes'
|
|
10580
|
+
], [
|
|
10581
|
+
GENERIC_EXECUTOR_HOOK_DATA_VERSION,
|
|
10582
|
+
options.recoveryAddress,
|
|
10583
|
+
address.getAddress(handler),
|
|
10584
|
+
handlerCalldata
|
|
10585
|
+
]);
|
|
10586
|
+
// 4. Prepend the circle-generic-executor magic header; this is the final
|
|
10587
|
+
// bare GE blob the executor consumes.
|
|
10588
|
+
const hookData = prependGenericExecutorHeader(executorTuple);
|
|
10589
|
+
return {
|
|
10590
|
+
hookData,
|
|
10591
|
+
handlerCalldata,
|
|
10592
|
+
depositCalldata,
|
|
10593
|
+
amountIndices,
|
|
10594
|
+
depositContract,
|
|
10595
|
+
approvalTarget
|
|
10596
|
+
};
|
|
10597
|
+
}
|
|
10598
|
+
/**
|
|
10599
|
+
* Config key of the built-in Circle Gateway deposit dApp. Its deposit contract
|
|
10600
|
+
* is the destination chain's GatewayWallet, resolved from `destinationChain`
|
|
10601
|
+
* (not a hardcoded address), so this is the only dApp whose deployment may omit
|
|
10602
|
+
* `depositContract`.
|
|
10603
|
+
*/ const GATEWAY_DEPOSIT_DAPP_ID = 'gateway_deposit';
|
|
10604
|
+
/**
|
|
10605
|
+
* Built-in dApp registry. Adding a new `depositFor`-style dApp is a config entry
|
|
10606
|
+
* here (or via {@link BuildDepositForGenericExecutorPayloadParams.config}).
|
|
10607
|
+
*
|
|
10608
|
+
* @remarks
|
|
10609
|
+
* Only dApps with confirmed deployment addresses and active callers are included.
|
|
10610
|
+
* The built-in `gateway_deposit` entry omits `depositContract` — it is resolved
|
|
10611
|
+
* from the destination chain's GatewayWallet (`@core/chains`) rather than
|
|
10612
|
+
* duplicated here. Pass a custom registry via `config` for unlisted dApps.
|
|
10613
|
+
*/ const DAPP_CONFIG = {
|
|
10614
|
+
// Circle Gateway: depositFor(address token, address depositor, uint256 value)
|
|
10615
|
+
[GATEWAY_DEPOSIT_DAPP_ID]: {
|
|
10616
|
+
function: 'depositFor(address,address,uint256)',
|
|
10617
|
+
dynamicAmountIndices: [
|
|
10618
|
+
2
|
|
10619
|
+
],
|
|
10620
|
+
deployments: [
|
|
10621
|
+
// Arc Testnet (CCTP domain 26). The deposit contract is the chain's
|
|
10622
|
+
// GatewayWallet, resolved from `destinationChain` (@core/chains) rather
|
|
10623
|
+
// than duplicated here.
|
|
10624
|
+
{
|
|
10625
|
+
domainId: 26
|
|
10626
|
+
}
|
|
10627
|
+
]
|
|
10628
|
+
}
|
|
10629
|
+
};
|
|
10630
|
+
/**
|
|
10631
|
+
* Resolve the destination CCTP domain for an encode request.
|
|
10632
|
+
*
|
|
10633
|
+
* Prefers {@link BuildDepositForGenericExecutorPayloadParams.destinationChain}
|
|
10634
|
+
* (`chain.cctp.domain`) — the chain is the (network, domain) key. Falls back to
|
|
10635
|
+
* an explicit `domainId`. When both are supplied they must agree.
|
|
10636
|
+
*
|
|
10637
|
+
* @param options - The encode request.
|
|
10638
|
+
* @returns The destination CCTP domain.
|
|
10639
|
+
* @throws {KitError} If no domain is available, or `domainId` disagrees with
|
|
10640
|
+
* `destinationChain` (INPUT_VALIDATION_FAILED).
|
|
10641
|
+
* @internal
|
|
10642
|
+
*/ function resolveDomainId(options) {
|
|
10643
|
+
const chain = options.destinationChain;
|
|
10644
|
+
if (chain !== undefined) {
|
|
10645
|
+
const chainDomain = chain.cctp?.domain;
|
|
10646
|
+
if (chainDomain !== undefined) {
|
|
10647
|
+
if (options.domainId !== undefined && options.domainId !== chainDomain) {
|
|
10648
|
+
throw createValidationFailedError$1('domainId', options.domainId, `does not match destinationChain '${chain.name}' CCTP domain ` + String(chainDomain));
|
|
10649
|
+
}
|
|
10650
|
+
return chainDomain;
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10653
|
+
if (options.domainId !== undefined) {
|
|
10654
|
+
return options.domainId;
|
|
10655
|
+
}
|
|
10656
|
+
throw createValidationFailedError$1('domainId', options.domainId, "Provide 'domainId', or a 'destinationChain' with a CCTP domain");
|
|
10657
|
+
}
|
|
10658
|
+
/**
|
|
10659
|
+
* Resolve the deposit contract the handler calls.
|
|
10660
|
+
*
|
|
10661
|
+
* Uses the deployment's own `depositContract` when present. Only the built-in
|
|
10662
|
+
* {@link GATEWAY_DEPOSIT_DAPP_ID} may omit it: its deposit contract is the
|
|
10663
|
+
* destination chain's Gateway v1 wallet, resolved from
|
|
10664
|
+
* {@link BuildDepositForGenericExecutorPayloadParams.destinationChain} so the
|
|
10665
|
+
* address is owned once in `@core/chains`. Any other dApp that omits
|
|
10666
|
+
* `depositContract` is a config error and fails here rather than silently
|
|
10667
|
+
* targeting the GatewayWallet.
|
|
10668
|
+
*
|
|
10669
|
+
* @param deployment - The resolved dApp deployment.
|
|
10670
|
+
* @param options - The encode request.
|
|
10671
|
+
* @param dappId - The dApp key, checked against {@link GATEWAY_DEPOSIT_DAPP_ID}.
|
|
10672
|
+
* @returns The deposit contract address (unvalidated; the caller checks it).
|
|
10673
|
+
* @throws {KitError} If a non-`gateway_deposit` deployment omits
|
|
10674
|
+
* `depositContract`, or if `gateway_deposit` has no Gateway v1
|
|
10675
|
+
* `destinationChain` to resolve one (INPUT_VALIDATION_FAILED).
|
|
10676
|
+
* @internal
|
|
10677
|
+
*/ function resolveDepositContract(deployment, options, dappId) {
|
|
10678
|
+
if (deployment.depositContract !== undefined) {
|
|
10679
|
+
return deployment.depositContract;
|
|
10680
|
+
}
|
|
10681
|
+
// Only gateway_deposit may omit its address (it targets the chain's
|
|
10682
|
+
// GatewayWallet). Any other addressless deployment is a config mistake and
|
|
10683
|
+
// must fail rather than silently resolve to the GatewayWallet.
|
|
10684
|
+
if (dappId !== GATEWAY_DEPOSIT_DAPP_ID) {
|
|
10685
|
+
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");
|
|
10686
|
+
}
|
|
10687
|
+
const chain = options.destinationChain;
|
|
10688
|
+
if (chain === undefined || !isGatewayV1Supported(chain)) {
|
|
10689
|
+
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');
|
|
10690
|
+
}
|
|
10691
|
+
return chain.gateway.contracts.v1.wallet;
|
|
10692
|
+
}
|
|
10693
|
+
/**
|
|
10694
|
+
* Resolve the `DepositForHandler` address for an encode request.
|
|
10695
|
+
*
|
|
10696
|
+
* An explicit `options.handler` always wins. Otherwise the handler is resolved
|
|
10697
|
+
* from {@link BuildDepositForGenericExecutorPayloadParams.destinationChain}
|
|
10698
|
+
* (`chain.gateway.contracts.v1.depositForHandler`). The chain is the (network,
|
|
10699
|
+
* domain) key, so a CCTP domain shared across a chain's testnet and mainnet
|
|
10700
|
+
* cannot resolve the wrong network's handler; a missing chain or an unregistered
|
|
10701
|
+
* handler throws rather than guessing.
|
|
10702
|
+
*
|
|
10703
|
+
* @param options - The encode request.
|
|
10704
|
+
* @param domainId - The resolved destination domain, reported in the error.
|
|
10705
|
+
* @returns The resolved handler address (unvalidated; the caller checks it).
|
|
10706
|
+
* @throws {KitError} If `handler` is omitted and cannot be resolved
|
|
10707
|
+
* (INPUT_VALIDATION_FAILED).
|
|
10708
|
+
* @internal
|
|
10709
|
+
*/ function resolveDepositForHandler(options, domainId) {
|
|
10710
|
+
if (options.handler !== undefined) {
|
|
10711
|
+
return options.handler;
|
|
10712
|
+
}
|
|
10713
|
+
const chain = options.destinationChain;
|
|
10714
|
+
if (chain === undefined) {
|
|
10715
|
+
throw createValidationFailedError$1('handler', domainId, "No 'handler' supplied; pass 'handler' explicitly, or a " + "'destinationChain' whose Gateway config registers a DepositForHandler");
|
|
10716
|
+
}
|
|
10717
|
+
const registered = isGatewayV1Supported(chain) ? chain.gateway.contracts.v1.depositForHandler : undefined;
|
|
10718
|
+
if (registered === undefined) {
|
|
10719
|
+
throw createValidationFailedError$1('handler', domainId, `No DepositForHandler registered for domain ${String(domainId)} on ` + `chain '${chain.name}'; pass 'handler' explicitly`);
|
|
10720
|
+
}
|
|
10721
|
+
return registered;
|
|
10722
|
+
}
|
|
10723
|
+
/**
|
|
10724
|
+
* Validate and checksum an EVM address, throwing a consistent validation error.
|
|
10725
|
+
*/ function assertAddress(address$1, field) {
|
|
10726
|
+
if (!address.isAddress(address$1)) {
|
|
10727
|
+
throw createValidationFailedError$1(field, address$1, 'Expected a valid EVM address');
|
|
10728
|
+
}
|
|
10729
|
+
return address.getAddress(address$1);
|
|
10730
|
+
}
|
|
9299
10731
|
|
|
9300
10732
|
/**
|
|
9301
10733
|
* Configuration for {@link retryAsync}.
|
|
@@ -9385,7 +10817,7 @@ function resolveOptions(options) {
|
|
|
9385
10817
|
* allowlisted {@link ClientLogPayload} fields (and the allowlisted
|
|
9386
10818
|
* sub-fields of `errorDetails` / `clientContext`) are copied across.
|
|
9387
10819
|
* A regressing upstream mapper — or a plain-JS caller that bypasses the
|
|
9388
|
-
* type — therefore cannot exfiltrate stray properties (secrets,
|
|
10820
|
+
* type — therefore cannot exfiltrate stray properties (secrets,
|
|
9389
10821
|
* raw error stacks) through the analytics channel. Optional fields are
|
|
9390
10822
|
* only included when present so the serialised shape matches the
|
|
9391
10823
|
* server's strict schema.
|
|
@@ -9408,6 +10840,9 @@ function resolveOptions(options) {
|
|
|
9408
10840
|
if (payload.destinationChain !== undefined) safe['destinationChain'] = payload.destinationChain;
|
|
9409
10841
|
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
9410
10842
|
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
10843
|
+
if (payload.amountIn !== undefined) safe['amountIn'] = payload.amountIn;
|
|
10844
|
+
if (payload.durationMs !== undefined) safe['durationMs'] = payload.durationMs;
|
|
10845
|
+
if (payload.sourceAddress !== undefined) safe['sourceAddress'] = payload.sourceAddress;
|
|
9411
10846
|
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
9412
10847
|
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
9413
10848
|
if (payload.errorDetails !== undefined) {
|
|
@@ -9604,22 +11039,36 @@ function resolveOptions(options) {
|
|
|
9604
11039
|
}
|
|
9605
11040
|
|
|
9606
11041
|
/**
|
|
9607
|
-
*
|
|
9608
|
-
*
|
|
11042
|
+
* Emit a stable console warning when building or emitting a telemetry payload
|
|
11043
|
+
* throws — for example, a buggy `TelemetryContextResolver`, a regression in
|
|
9609
11044
|
* `extractErrorDetails`, or a synchronous failure inside `emitAnalyticsLog`
|
|
9610
|
-
* before it could swallow the error itself.
|
|
9611
|
-
*
|
|
9612
|
-
*
|
|
11045
|
+
* before it could swallow the error itself. Uses a stable prefix so the
|
|
11046
|
+
* drop is discoverable via grep. Never re-throws: the caller's original
|
|
11047
|
+
* operation error must always win.
|
|
9613
11048
|
*
|
|
9614
11049
|
* @internal
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
11050
|
+
*
|
|
11051
|
+
* @param eventType - The telemetry event type that was being emitted.
|
|
11052
|
+
* @param cause - The error or value that caused the drop.
|
|
11053
|
+
*
|
|
11054
|
+
* @example
|
|
11055
|
+
* ```typescript
|
|
11056
|
+
* import { warnTelemetryDrop } from '@core/utils'
|
|
11057
|
+
*
|
|
11058
|
+
* try {
|
|
11059
|
+
* void emitAnalyticsLog(payload)
|
|
11060
|
+
* } catch (err) {
|
|
11061
|
+
* warnTelemetryDrop('my_event', err)
|
|
11062
|
+
* }
|
|
11063
|
+
* ```
|
|
11064
|
+
*/ function warnTelemetryDrop(eventType, cause) {
|
|
11065
|
+
try {
|
|
11066
|
+
// Pass `cause` as the second console.warn argument rather than
|
|
11067
|
+
// string-coercing it. `String(err)` (and `err.message` alone)
|
|
11068
|
+
// discards the stack trace, nested `cause`, and any custom Error
|
|
11069
|
+
// properties — exactly the context an on-call needs when a
|
|
11070
|
+
// resolver-closure regression triggers this path.
|
|
11071
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
9623
11072
|
} catch {
|
|
9624
11073
|
// console.warn itself throwing is the user's environment; nothing more we
|
|
9625
11074
|
// can do without risking the original operation error.
|
|
@@ -9651,6 +11100,9 @@ function resolveOptions(options) {
|
|
|
9651
11100
|
...context?.tokenOut != null && {
|
|
9652
11101
|
tokenOut: context.tokenOut
|
|
9653
11102
|
},
|
|
11103
|
+
...context?.amountIn != null && {
|
|
11104
|
+
amountIn: context.amountIn
|
|
11105
|
+
},
|
|
9654
11106
|
...context?.txHash != null && {
|
|
9655
11107
|
txHash: context.txHash
|
|
9656
11108
|
},
|
|
@@ -9752,7 +11204,7 @@ function resolveOptions(options) {
|
|
|
9752
11204
|
const stepEntry = stepEventMap.find(([name])=>name === failedStep?.name);
|
|
9753
11205
|
// `failedStep.errorMessage` is intentionally **not** copied into the payload.
|
|
9754
11206
|
// Provider messages are unbounded and frequently contain operator data
|
|
9755
|
-
// (
|
|
11207
|
+
// (signatures, partial intent payloads, raw RPC responses). The
|
|
9756
11208
|
// step name plus the surrounding context fields already identify *which*
|
|
9757
11209
|
// phase failed; the *why* is left to the corresponding step-level logs that
|
|
9758
11210
|
// the provider emits separately. The thrown-error path (`extractErrorDetails`
|
|
@@ -9769,7 +11221,7 @@ function resolveOptions(options) {
|
|
|
9769
11221
|
}
|
|
9770
11222
|
|
|
9771
11223
|
var name$2 = "@circle-fin/bridge-kit";
|
|
9772
|
-
var version$3 = "1.
|
|
11224
|
+
var version$3 = "1.14.1";
|
|
9773
11225
|
var pkg$3 = {
|
|
9774
11226
|
name: name$2,
|
|
9775
11227
|
version: version$3};
|
|
@@ -10150,7 +11602,7 @@ const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
|
|
|
10150
11602
|
* const result = evmAddressSchema.safeParse(validAddress)
|
|
10151
11603
|
* console.log(result.success) // true
|
|
10152
11604
|
* ```
|
|
10153
|
-
*/ const evmAddressSchema = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
11605
|
+
*/ const evmAddressSchema$1 = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
10154
11606
|
/**
|
|
10155
11607
|
* Schema for validating transaction hashes.
|
|
10156
11608
|
*
|
|
@@ -10839,6 +12291,10 @@ var TransferSpeed;
|
|
|
10839
12291
|
token: zod.z.literal('USDC').optional(),
|
|
10840
12292
|
config: zod.z.object({
|
|
10841
12293
|
transferSpeed: zod.z.nativeEnum(TransferSpeed).optional(),
|
|
12294
|
+
feePayment: zod.z.enum([
|
|
12295
|
+
'source',
|
|
12296
|
+
'destination'
|
|
12297
|
+
]).optional(),
|
|
10842
12298
|
maxFee: zod.z.string().min(1, 'Required').pipe(createDecimalStringValidator({
|
|
10843
12299
|
allowZero: true,
|
|
10844
12300
|
regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
|
|
@@ -10846,7 +12302,8 @@ var TransferSpeed;
|
|
|
10846
12302
|
maxDecimals: 6
|
|
10847
12303
|
})(zod.z.string())).optional(),
|
|
10848
12304
|
customFee: customFeeSchema.optional()
|
|
10849
|
-
}).optional()
|
|
12305
|
+
}).optional(),
|
|
12306
|
+
quote: zod.z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex').optional()
|
|
10850
12307
|
});
|
|
10851
12308
|
|
|
10852
12309
|
/**
|
|
@@ -11458,7 +12915,7 @@ var TransferSpeed;
|
|
|
11458
12915
|
* })
|
|
11459
12916
|
* ```
|
|
11460
12917
|
*/ function createLogger(options, stream) {
|
|
11461
|
-
const { redact, ...pinoOptions } = {};
|
|
12918
|
+
const { redact, ...pinoOptions } = options ?? {};
|
|
11462
12919
|
// Build redaction config
|
|
11463
12920
|
const redactConfig = buildRedactConfig(redact);
|
|
11464
12921
|
// Build final pino options, only include redact if defined
|
|
@@ -12021,16 +13478,73 @@ var TransferSpeed;
|
|
|
12021
13478
|
};
|
|
12022
13479
|
}
|
|
12023
13480
|
|
|
13481
|
+
/**
|
|
13482
|
+
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
13483
|
+
*
|
|
13484
|
+
* Constructs the appropriate action payload and dispatches it to any registered
|
|
13485
|
+
* event listeners. Handles type-safe dispatching for different step types.
|
|
13486
|
+
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
13487
|
+
*
|
|
13488
|
+
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
13489
|
+
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
13490
|
+
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
13491
|
+
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
13492
|
+
*
|
|
13493
|
+
* @example
|
|
13494
|
+
* ```typescript
|
|
13495
|
+
* const step: BridgeStep = {
|
|
13496
|
+
* name: 'burn',
|
|
13497
|
+
* state: 'success',
|
|
13498
|
+
* txHash: '0xabc...',
|
|
13499
|
+
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
13500
|
+
* data: { ... }
|
|
13501
|
+
* }
|
|
13502
|
+
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
13503
|
+
* ```
|
|
13504
|
+
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
13505
|
+
if (!provider.actionDispatcher) {
|
|
13506
|
+
return;
|
|
13507
|
+
}
|
|
13508
|
+
// Extract traceId from invocation context if provided
|
|
13509
|
+
const traceId = invocation?.traceId;
|
|
13510
|
+
const actionValues = {
|
|
13511
|
+
protocol: 'cctp',
|
|
13512
|
+
version: 'v2',
|
|
13513
|
+
...traceId !== undefined && {
|
|
13514
|
+
traceId
|
|
13515
|
+
},
|
|
13516
|
+
values: step
|
|
13517
|
+
};
|
|
13518
|
+
switch(name){
|
|
13519
|
+
case 'approve':
|
|
13520
|
+
case 'burn':
|
|
13521
|
+
case 'mint':
|
|
13522
|
+
provider.actionDispatcher.dispatch(name, {
|
|
13523
|
+
...actionValues,
|
|
13524
|
+
method: name
|
|
13525
|
+
});
|
|
13526
|
+
break;
|
|
13527
|
+
case 'fetchAttestation':
|
|
13528
|
+
case 'reAttest':
|
|
13529
|
+
provider.actionDispatcher.dispatch(name, {
|
|
13530
|
+
...actionValues,
|
|
13531
|
+
method: name,
|
|
13532
|
+
values: step
|
|
13533
|
+
});
|
|
13534
|
+
break;
|
|
13535
|
+
}
|
|
13536
|
+
}
|
|
13537
|
+
|
|
12024
13538
|
/**
|
|
12025
13539
|
* Base URL for Circle's IRIS API (mainnet/production).
|
|
12026
13540
|
*
|
|
12027
13541
|
* The IRIS API provides attestation services for CCTP cross-chain transfers.
|
|
12028
|
-
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
13542
|
+
*/ const IRIS_API_BASE_URL$1 = 'https://iris-api.circle.com';
|
|
12029
13543
|
/**
|
|
12030
13544
|
* Base URL for Circle's IRIS API (testnet/sandbox).
|
|
12031
13545
|
*
|
|
12032
13546
|
* Used for development and testing on testnet chains.
|
|
12033
|
-
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
13547
|
+
*/ const IRIS_API_SANDBOX_BASE_URL$1 = 'https://iris-api-sandbox.circle.com';
|
|
12034
13548
|
|
|
12035
13549
|
/**
|
|
12036
13550
|
* Type guard to validate the API response structure.
|
|
@@ -12073,7 +13587,7 @@ const isFastBurnFeeResponse = (data)=>{
|
|
|
12073
13587
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
12074
13588
|
* @returns The complete API URL
|
|
12075
13589
|
*/ function buildFastBurnFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
12076
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
13590
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
12077
13591
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}`;
|
|
12078
13592
|
}
|
|
12079
13593
|
const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
@@ -12284,6 +13798,77 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12284
13798
|
}
|
|
12285
13799
|
};
|
|
12286
13800
|
|
|
13801
|
+
/**
|
|
13802
|
+
* Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
|
|
13803
|
+
* tailored to the destination chain.
|
|
13804
|
+
*
|
|
13805
|
+
* For EVM destinations the recipient already holds ERC-20 USDC directly, so the
|
|
13806
|
+
* empty version-0 `cctp-forward` frame is sufficient. For Solana destinations
|
|
13807
|
+
* USDC is held in an Associated Token Account (ATA) that may not exist for a
|
|
13808
|
+
* fresh wallet, so this emits a frame carrying `createAta` + `ataOwner` that
|
|
13809
|
+
* instructs the relayer to create the recipient ATA (idempotently, at the
|
|
13810
|
+
* relayer's expense) before minting.
|
|
13811
|
+
*
|
|
13812
|
+
* `@solana/web3.js` is imported lazily so EVM-only consumers never load Solana
|
|
13813
|
+
* code, mirroring {@link getMintRecipientAccount}.
|
|
13814
|
+
*
|
|
13815
|
+
* @param chainType - The destination blockchain type ('evm' or 'solana').
|
|
13816
|
+
* @param ownerAddress - The recipient's wallet address on the destination chain
|
|
13817
|
+
* (base58 for Solana). Must be the same owner used to derive `mintRecipient`.
|
|
13818
|
+
* @returns A 0x-prefixed hookData hex string for the forwarded burn.
|
|
13819
|
+
* @throws {KitError} If `chainType` is neither 'evm' nor 'solana', if
|
|
13820
|
+
* `@solana/web3.js` cannot be loaded, or if `ownerAddress` is not a valid
|
|
13821
|
+
* Solana public key (all FATAL).
|
|
13822
|
+
*
|
|
13823
|
+
* @example
|
|
13824
|
+
* ```typescript
|
|
13825
|
+
* import { getForwarderHookData } from './getForwarderHookData'
|
|
13826
|
+
*
|
|
13827
|
+
* // EVM: empty forwarding frame
|
|
13828
|
+
* const evmHook = await getForwarderHookData('evm', '0x742d35Cc...')
|
|
13829
|
+
*
|
|
13830
|
+
* // Solana: frame instructing the relayer to create the recipient ATA
|
|
13831
|
+
* const solanaHook = await getForwarderHookData(
|
|
13832
|
+
* 'solana',
|
|
13833
|
+
* '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
13834
|
+
* )
|
|
13835
|
+
* ```
|
|
13836
|
+
*/ const getForwarderHookData = async (/** The destination blockchain type - determines the hookData shape */ chainType, /** The recipient's wallet address (hex for EVM, base58 for Solana) */ ownerAddress)=>{
|
|
13837
|
+
if (chainType === 'evm') {
|
|
13838
|
+
// EVM: the recipient holds USDC directly; no ATA setup is needed.
|
|
13839
|
+
return buildForwardingHookData();
|
|
13840
|
+
}
|
|
13841
|
+
// Fail closed: only EVM and Solana forwarding destinations are supported.
|
|
13842
|
+
// Without this guard any future non-EVM chain type would silently fall
|
|
13843
|
+
// through to the Solana path and mis-encode hookData on a money-movement path.
|
|
13844
|
+
if (chainType !== 'solana') {
|
|
13845
|
+
throw new KitError({
|
|
13846
|
+
...InputError.VALIDATION_FAILED,
|
|
13847
|
+
recoverability: 'FATAL',
|
|
13848
|
+
message: `Forwarded burns are not supported for destination chain type "${chainType}"`
|
|
13849
|
+
});
|
|
13850
|
+
}
|
|
13851
|
+
// Solana: encode the owner so the relayer creates the recipient ATA.
|
|
13852
|
+
// Resolve @solana/web3.js lazily so EVM-only consumers never load Solana code.
|
|
13853
|
+
const { PublicKey } = await import('@solana/web3.js').catch(()=>{
|
|
13854
|
+
throw new KitError({
|
|
13855
|
+
...InputError.VALIDATION_FAILED,
|
|
13856
|
+
recoverability: 'FATAL',
|
|
13857
|
+
message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js'
|
|
13858
|
+
});
|
|
13859
|
+
});
|
|
13860
|
+
try {
|
|
13861
|
+
const owner = new PublicKey(ownerAddress);
|
|
13862
|
+
return buildSolanaAtaForwardingHookData(owner.toBytes());
|
|
13863
|
+
} catch (error) {
|
|
13864
|
+
throw new KitError({
|
|
13865
|
+
...InputError.INVALID_ADDRESS,
|
|
13866
|
+
recoverability: 'FATAL',
|
|
13867
|
+
message: `Failed to build Solana forwarder hookData for recipient "${ownerAddress}": ${error instanceof Error ? error.message : String(error)}`
|
|
13868
|
+
});
|
|
13869
|
+
}
|
|
13870
|
+
};
|
|
13871
|
+
|
|
12287
13872
|
/**
|
|
12288
13873
|
* Validates and converts a fee value to bigint.
|
|
12289
13874
|
*
|
|
@@ -12386,7 +13971,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12386
13971
|
|
|
12387
13972
|
/**
|
|
12388
13973
|
* The zero address, denoting a native-currency fee in a signed quote.
|
|
12389
|
-
*/ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
13974
|
+
*/ const ZERO_ADDRESS$1 = '0x0000000000000000000000000000000000000000';
|
|
12390
13975
|
/**
|
|
12391
13976
|
* Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
|
|
12392
13977
|
*
|
|
@@ -12427,7 +14012,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12427
14012
|
if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
|
|
12428
14013
|
throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
|
|
12429
14014
|
}
|
|
12430
|
-
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS;
|
|
14015
|
+
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$1;
|
|
12431
14016
|
const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
|
|
12432
14017
|
if (isNativeFee) {
|
|
12433
14018
|
return {
|
|
@@ -12565,7 +14150,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
12565
14150
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
12566
14151
|
* @returns The complete API URL with forward=true query parameter
|
|
12567
14152
|
*/ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
12568
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
14153
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
12569
14154
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true`;
|
|
12570
14155
|
}
|
|
12571
14156
|
/**
|
|
@@ -12684,6 +14269,8 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
|
|
|
12684
14269
|
;
|
|
12685
14270
|
const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
|
|
12686
14271
|
;
|
|
14272
|
+
/** 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
|
|
14273
|
+
;
|
|
12687
14274
|
// Gas FLOORS, not ceilings — kept separate from the fee-estimate averages
|
|
12688
14275
|
// above. `executePreparedChainRequest` submits
|
|
12689
14276
|
// max(estimate * buffer, floor), so a chain whose real cost exceeds the floor
|
|
@@ -13626,7 +15213,7 @@ function hasPendingState(analysis, result) {
|
|
|
13626
15213
|
* // => 'https://iris-api-sandbox.circle.com/v2/messages/1?transactionHash=0xdef...'
|
|
13627
15214
|
* ```
|
|
13628
15215
|
*/ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet)=>{
|
|
13629
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
15216
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
13630
15217
|
const url = new URL(`${baseUrl}/v2/messages/${String(sourceDomainId)}`);
|
|
13631
15218
|
url.searchParams.set('transactionHash', transactionHash);
|
|
13632
15219
|
return url.toString();
|
|
@@ -13794,7 +15381,7 @@ function hasPendingState(analysis, result) {
|
|
|
13794
15381
|
* // => 'https://iris-api-sandbox.circle.com/v2/reattest/0xabc'
|
|
13795
15382
|
* ```
|
|
13796
15383
|
*/ const buildReAttestUrl = (nonce, isTestnet)=>{
|
|
13797
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
15384
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
13798
15385
|
const url = new URL(`${baseUrl}/v2/reattest/${nonce}`);
|
|
13799
15386
|
return url.toString();
|
|
13800
15387
|
};
|
|
@@ -14249,7 +15836,8 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14249
15836
|
* - `destinationChain` — present and supports CCTP v2
|
|
14250
15837
|
* - source and destination chains must both be testnet or both mainnet
|
|
14251
15838
|
* - source and destination chains must differ
|
|
14252
|
-
* - `executor
|
|
15839
|
+
* - destination — either `executor`, or both `mintRecipient` and
|
|
15840
|
+
* `destinationCaller`; not both
|
|
14253
15841
|
* - `amount` — bigint or non-empty string coercible to bigint
|
|
14254
15842
|
* - `feeTotalAmount` — bigint or non-empty string coercible to bigint
|
|
14255
15843
|
* - `feeToken` — valid EVM address (`0x` + 40 hex chars)
|
|
@@ -14291,10 +15879,17 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14291
15879
|
if (source.chain.name === dest.name) {
|
|
14292
15880
|
throw createUnsupportedRouteError(source.chain.name, dest.name);
|
|
14293
15881
|
}
|
|
14294
|
-
//
|
|
15882
|
+
// Destination: GenericExecutor shorthand or explicit recipient + caller.
|
|
14295
15883
|
const executor = p['executor'];
|
|
14296
|
-
|
|
14297
|
-
|
|
15884
|
+
const mintRecipient = p['mintRecipient'];
|
|
15885
|
+
const destinationCaller = p['destinationCaller'];
|
|
15886
|
+
const hasExecutor = typeof executor === 'string' && executor !== '';
|
|
15887
|
+
const hasDirectDestination = typeof mintRecipient === 'string' && mintRecipient !== '' && typeof destinationCaller === 'string' && destinationCaller !== '';
|
|
15888
|
+
if (!hasExecutor && !hasDirectDestination) {
|
|
15889
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor, or both mintRecipient and destinationCaller');
|
|
15890
|
+
}
|
|
15891
|
+
if (hasExecutor && hasDirectDestination) {
|
|
15892
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor or direct destination fields, not both');
|
|
14298
15893
|
}
|
|
14299
15894
|
// amount
|
|
14300
15895
|
const rawAmount = p['amount'];
|
|
@@ -14317,7 +15912,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14317
15912
|
throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
|
|
14318
15913
|
}
|
|
14319
15914
|
// feeToken
|
|
14320
|
-
if (!evmAddressSchema.safeParse(p['feeToken']).success) {
|
|
15915
|
+
if (!evmAddressSchema$1.safeParse(p['feeToken']).success) {
|
|
14321
15916
|
throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
|
|
14322
15917
|
}
|
|
14323
15918
|
// claim
|
|
@@ -14329,7 +15924,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
14329
15924
|
if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
|
|
14330
15925
|
throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
|
|
14331
15926
|
}
|
|
14332
|
-
if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
|
|
15927
|
+
if (!evmAddressSchema$1.safeParse(claim['refundAddress']).success) {
|
|
14333
15928
|
throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
|
|
14334
15929
|
}
|
|
14335
15930
|
// hookData (optional)
|
|
@@ -14711,63 +16306,6 @@ const mockAttestationMessage = {
|
|
|
14711
16306
|
});
|
|
14712
16307
|
}
|
|
14713
16308
|
|
|
14714
|
-
/**
|
|
14715
|
-
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
14716
|
-
*
|
|
14717
|
-
* Constructs the appropriate action payload and dispatches it to any registered
|
|
14718
|
-
* event listeners. Handles type-safe dispatching for different step types.
|
|
14719
|
-
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
14720
|
-
*
|
|
14721
|
-
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
14722
|
-
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
14723
|
-
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
14724
|
-
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
14725
|
-
*
|
|
14726
|
-
* @example
|
|
14727
|
-
* ```typescript
|
|
14728
|
-
* const step: BridgeStep = {
|
|
14729
|
-
* name: 'burn',
|
|
14730
|
-
* state: 'success',
|
|
14731
|
-
* txHash: '0xabc...',
|
|
14732
|
-
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
14733
|
-
* data: { ... }
|
|
14734
|
-
* }
|
|
14735
|
-
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
14736
|
-
* ```
|
|
14737
|
-
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
14738
|
-
if (!provider.actionDispatcher) {
|
|
14739
|
-
return;
|
|
14740
|
-
}
|
|
14741
|
-
// Extract traceId from invocation context if provided
|
|
14742
|
-
const traceId = invocation?.traceId;
|
|
14743
|
-
const actionValues = {
|
|
14744
|
-
protocol: 'cctp',
|
|
14745
|
-
version: 'v2',
|
|
14746
|
-
...traceId !== undefined && {
|
|
14747
|
-
traceId
|
|
14748
|
-
},
|
|
14749
|
-
values: step
|
|
14750
|
-
};
|
|
14751
|
-
switch(name){
|
|
14752
|
-
case 'approve':
|
|
14753
|
-
case 'burn':
|
|
14754
|
-
case 'mint':
|
|
14755
|
-
provider.actionDispatcher.dispatch(name, {
|
|
14756
|
-
...actionValues,
|
|
14757
|
-
method: name
|
|
14758
|
-
});
|
|
14759
|
-
break;
|
|
14760
|
-
case 'fetchAttestation':
|
|
14761
|
-
case 'reAttest':
|
|
14762
|
-
provider.actionDispatcher.dispatch(name, {
|
|
14763
|
-
...actionValues,
|
|
14764
|
-
method: name,
|
|
14765
|
-
values: step
|
|
14766
|
-
});
|
|
14767
|
-
break;
|
|
14768
|
-
}
|
|
14769
|
-
}
|
|
14770
|
-
|
|
14771
16309
|
/**
|
|
14772
16310
|
* Check whether the source adapter supports EIP-5792 atomic batching and
|
|
14773
16311
|
* the consumer has not explicitly opted out via `config.batchTransactions`.
|
|
@@ -15014,7 +16552,7 @@ const mockAttestationMessage = {
|
|
|
15014
16552
|
return step;
|
|
15015
16553
|
}
|
|
15016
16554
|
|
|
15017
|
-
var version$2 = "1.
|
|
16555
|
+
var version$2 = "1.13.0";
|
|
15018
16556
|
var pkg$2 = {
|
|
15019
16557
|
version: version$2};
|
|
15020
16558
|
|
|
@@ -15720,6 +17258,9 @@ var pkg$2 = {
|
|
|
15720
17258
|
}
|
|
15721
17259
|
}
|
|
15722
17260
|
|
|
17261
|
+
const logger = createLogger({
|
|
17262
|
+
name: 'provider-cctp-v2'
|
|
17263
|
+
});
|
|
15723
17264
|
function isPlainObject(value) {
|
|
15724
17265
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
15725
17266
|
return false;
|
|
@@ -15825,6 +17366,34 @@ function assertCCTPV2Config(config) {
|
|
|
15825
17366
|
this.config = config;
|
|
15826
17367
|
}
|
|
15827
17368
|
/**
|
|
17369
|
+
* Emit a bridge step event through the provider's registered action
|
|
17370
|
+
* dispatcher.
|
|
17371
|
+
*
|
|
17372
|
+
* Kit-level orchestration that drives the burn primitives directly instead
|
|
17373
|
+
* of {@link CCTPV2BridgingProvider.bridge} (for example the receive-exact
|
|
17374
|
+
* source-fee flow) uses this to surface the same `approve`/`burn`/`mint`
|
|
17375
|
+
* events as the standard bridge path. It is a no-op when no dispatcher is
|
|
17376
|
+
* registered.
|
|
17377
|
+
*
|
|
17378
|
+
* @param name - The step name (`approve`, `burn`, `mint`, ...).
|
|
17379
|
+
* @param step - The completed bridge step to broadcast.
|
|
17380
|
+
* @param invocation - Optional invocation context carrying a `traceId` for
|
|
17381
|
+
* end-to-end correlation.
|
|
17382
|
+
* @returns Nothing.
|
|
17383
|
+
*
|
|
17384
|
+
* @example
|
|
17385
|
+
* ```typescript
|
|
17386
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
17387
|
+
* provider.emitBridgeStep('burn', {
|
|
17388
|
+
* name: 'burn',
|
|
17389
|
+
* state: 'success',
|
|
17390
|
+
* txHash: '0xabc...',
|
|
17391
|
+
* })
|
|
17392
|
+
* ```
|
|
17393
|
+
*/ emitBridgeStep(name, step, invocation) {
|
|
17394
|
+
dispatchStepEvent(name, step, this, invocation);
|
|
17395
|
+
}
|
|
17396
|
+
/**
|
|
15828
17397
|
* Resolves the effective polling configuration for an attestation request.
|
|
15829
17398
|
*
|
|
15830
17399
|
* Precedence (lowest to highest): provider `config.attestation`, then the
|
|
@@ -16054,6 +17623,116 @@ function assertCCTPV2Config(config) {
|
|
|
16054
17623
|
return estimateResult;
|
|
16055
17624
|
}
|
|
16056
17625
|
/**
|
|
17626
|
+
* Estimate source-chain gas for a prepaid-FORWARD deposit burn via
|
|
17627
|
+
* `TokenMessengerWithFees.depositForBurnWithHookAndFees`.
|
|
17628
|
+
*
|
|
17629
|
+
* Builds a size-correct GenericExecutor hookData placeholder — using the
|
|
17630
|
+
* pre-validated `contracts.executor` and `contracts.depositForHandler` — so
|
|
17631
|
+
* the EVM calldata length matches production. Attempts a live
|
|
17632
|
+
* `eth_estimateGas` via the adapter's `cctp.v2.depositForBurnWithFees`
|
|
17633
|
+
* action and falls back to the static
|
|
17634
|
+
* {@link DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM} constant when the live
|
|
17635
|
+
* RPC call fails.
|
|
17636
|
+
*
|
|
17637
|
+
* Gateway eligibility is the caller's responsibility: validate the
|
|
17638
|
+
* destination chain with `resolveGatewayExecutorContracts` (UBK) before
|
|
17639
|
+
* calling this method.
|
|
17640
|
+
*
|
|
17641
|
+
* @param params - Estimation parameters including adapter, chains, amount,
|
|
17642
|
+
* the signed fee quote returned by the Quote API, and the pre-validated
|
|
17643
|
+
* Gateway executor contracts resolved by the caller.
|
|
17644
|
+
* @returns Promise resolving to the estimated (or fallback) gas cost.
|
|
17645
|
+
* @throws KitError `SERVICE_INTERNAL_ERROR` (FATAL) if `dstChain`'s
|
|
17646
|
+
* `usdcAddress` is `null`.
|
|
17647
|
+
*
|
|
17648
|
+
* @example
|
|
17649
|
+
* ```typescript
|
|
17650
|
+
* const contracts = resolveGatewayExecutorContracts(srcChain, dstChain)
|
|
17651
|
+
* const gasEstimate = await CCTPV2BridgingProvider.estimateDepositBurn({
|
|
17652
|
+
* adapter: evmAdapter,
|
|
17653
|
+
* srcChain: Ethereum,
|
|
17654
|
+
* dstChain: ArcTestnet,
|
|
17655
|
+
* amountMinorUnits: 100_000_000n,
|
|
17656
|
+
* refundAddress: '0xUserWallet',
|
|
17657
|
+
* signedQuote: quote.signedQuote,
|
|
17658
|
+
* feeToken: quote.feeToken,
|
|
17659
|
+
* feeTotalAmount: BigInt(quote.feeTotalAmount),
|
|
17660
|
+
* resolvedContext,
|
|
17661
|
+
* contracts,
|
|
17662
|
+
* })
|
|
17663
|
+
* ```
|
|
17664
|
+
*/ static async estimateDepositBurn(params) {
|
|
17665
|
+
const { adapter, srcChain, dstChain, amountMinorUnits, refundAddress, signedQuote, feeToken, feeTotalAmount, resolvedContext, contracts } = params;
|
|
17666
|
+
const { executor, depositForHandler } = contracts;
|
|
17667
|
+
if (dstChain.usdcAddress === null) {
|
|
17668
|
+
throw new KitError({
|
|
17669
|
+
...ServiceError.INTERNAL_ERROR,
|
|
17670
|
+
recoverability: 'FATAL',
|
|
17671
|
+
message: `Destination chain ${dstChain.name} has no USDC address configured`
|
|
17672
|
+
});
|
|
17673
|
+
}
|
|
17674
|
+
// Build a size-correct hookData for eth_estimateGas. The EVM uses calldata
|
|
17675
|
+
// length to compute gas, so the byte layout must match production even
|
|
17676
|
+
// though field values are not final.
|
|
17677
|
+
const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
|
|
17678
|
+
dappId: 'gateway_deposit',
|
|
17679
|
+
destinationChain: dstChain,
|
|
17680
|
+
handler: depositForHandler,
|
|
17681
|
+
params: [
|
|
17682
|
+
dstChain.usdcAddress,
|
|
17683
|
+
refundAddress,
|
|
17684
|
+
0n
|
|
17685
|
+
],
|
|
17686
|
+
recoveryAddress: padAddressToBytes32(refundAddress),
|
|
17687
|
+
// Override deployments only — spread the canonical function signature
|
|
17688
|
+
// and amount indices from DAPP_CONFIG so they stay in sync.
|
|
17689
|
+
config: {
|
|
17690
|
+
gateway_deposit: {
|
|
17691
|
+
...DAPP_CONFIG.gateway_deposit,
|
|
17692
|
+
deployments: [
|
|
17693
|
+
{
|
|
17694
|
+
domainId: dstChain.cctp.domain
|
|
17695
|
+
}
|
|
17696
|
+
]
|
|
17697
|
+
}
|
|
17698
|
+
}
|
|
17699
|
+
});
|
|
17700
|
+
const hookData = buildForwardingHookDataWithPayload(GENERIC_EXECUTOR_HOOK_DATA_VERSION, geBlob);
|
|
17701
|
+
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee
|
|
17702
|
+
// item, so the hookData must carry a `cctp-forward` frame — matches the
|
|
17703
|
+
// assertion in `prepareDepositForBurn`.
|
|
17704
|
+
assertForwardHookData(hookData);
|
|
17705
|
+
try {
|
|
17706
|
+
const prepared = await adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
17707
|
+
fromChain: srcChain,
|
|
17708
|
+
toChain: dstChain,
|
|
17709
|
+
amount: amountMinorUnits,
|
|
17710
|
+
mintRecipient: executor,
|
|
17711
|
+
destinationCaller: executor,
|
|
17712
|
+
claim: {
|
|
17713
|
+
signedQuote,
|
|
17714
|
+
refundAddress
|
|
17715
|
+
},
|
|
17716
|
+
feeToken,
|
|
17717
|
+
feeTotalAmount,
|
|
17718
|
+
hookData
|
|
17719
|
+
}, resolvedContext);
|
|
17720
|
+
return await prepared.estimate(undefined);
|
|
17721
|
+
} catch (err) {
|
|
17722
|
+
logger.debug('estimateDepositBurn: live eth_estimateGas failed, using static fallback', {
|
|
17723
|
+
err,
|
|
17724
|
+
chain: srcChain.name
|
|
17725
|
+
});
|
|
17726
|
+
try {
|
|
17727
|
+
return await adapter.calculateTransactionFee(DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM, undefined, srcChain);
|
|
17728
|
+
} catch (feeErr) {
|
|
17729
|
+
throw createRpcEndpointError(srcChain.name, {
|
|
17730
|
+
rawError: feeErr
|
|
17731
|
+
});
|
|
17732
|
+
}
|
|
17733
|
+
}
|
|
17734
|
+
}
|
|
17735
|
+
/**
|
|
16057
17736
|
* Extracts OperationContext from bridge parameters for a given wallet context.
|
|
16058
17737
|
*
|
|
16059
17738
|
* This method extracts the chain and address information from the wallet context
|
|
@@ -16650,8 +18329,11 @@ function assertCCTPV2Config(config) {
|
|
|
16650
18329
|
// 2. Forwarder: Does the user want Circle's relayer to handle attestation/mint?
|
|
16651
18330
|
const useCustomBurn = hasCustomContractSupport(source.chain, 'bridge');
|
|
16652
18331
|
const useForwarder = destination.useForwarder === true;
|
|
16653
|
-
// Build hookData once if forwarder is enabled
|
|
16654
|
-
|
|
18332
|
+
// Build hookData once if forwarder is enabled. EVM destinations get the
|
|
18333
|
+
// empty forwarding frame; Solana destinations get a frame instructing the
|
|
18334
|
+
// relayer to create the recipient's ATA (using the same owner that derived
|
|
18335
|
+
// `mintRecipient`) so the mint succeeds even for a fresh wallet.
|
|
18336
|
+
const hookData = useForwarder ? await getForwarderHookData(destination.chain.type, destinationAddressForMint) : undefined;
|
|
16655
18337
|
if (useCustomBurn) {
|
|
16656
18338
|
// Custom burn path: use bridge contract (with or without hook)
|
|
16657
18339
|
const customBurnParams = {
|
|
@@ -16677,15 +18359,109 @@ function assertCCTPV2Config(config) {
|
|
|
16677
18359
|
return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
|
|
16678
18360
|
}
|
|
16679
18361
|
/**
|
|
18362
|
+
* Prepare the source-chain `depositForBurnWithHookAndFees` call for the
|
|
18363
|
+
* GenericExecutor FORWARD path.
|
|
18364
|
+
*
|
|
18365
|
+
* Exposed as a public static method so the UBK fast-deposit flow can invoke
|
|
18366
|
+
* it directly without holding a provider instance. The byte layout mirrors
|
|
18367
|
+
* {@link CCTPV2BridgingProvider.estimateDepositBurn} so gas estimates and the
|
|
18368
|
+
* executed call agree.
|
|
18369
|
+
*
|
|
18370
|
+
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
18371
|
+
* @param params - Burn parameters including adapter, chains, deposit action,
|
|
18372
|
+
* amount, signer address, signed fee quote, and pre-resolved adapter context.
|
|
18373
|
+
* @returns The prepared `depositForBurnWithHookAndFees` burn transaction.
|
|
18374
|
+
* @throws {KitError} `UNSUPPORTED_ROUTE` when `dstChain` lacks a
|
|
18375
|
+
* GenericExecutor or DepositForHandler, or the `deposit.dappId` is unknown.
|
|
18376
|
+
*
|
|
18377
|
+
* @example
|
|
18378
|
+
* ```typescript
|
|
18379
|
+
* import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
|
|
18380
|
+
* import { Ethereum, ArcTestnet } from '@core/chains'
|
|
18381
|
+
*
|
|
18382
|
+
* const prepared = await CCTPV2BridgingProvider.prepareDepositForBurn({
|
|
18383
|
+
* adapter,
|
|
18384
|
+
* srcChain: Ethereum,
|
|
18385
|
+
* dstChain: ArcTestnet,
|
|
18386
|
+
* deposit: { dappId: 'gateway_deposit', params: [usdcAddress, recipient, 0n] },
|
|
18387
|
+
* amountMinorUnits: 100_000_000n,
|
|
18388
|
+
* refundAddress: '0xSender...',
|
|
18389
|
+
* signedQuote: quote.signedQuote,
|
|
18390
|
+
* feeToken: quote.feeToken,
|
|
18391
|
+
* feeTotalAmount: BigInt(quote.feeTotalAmount),
|
|
18392
|
+
* resolvedContext,
|
|
18393
|
+
* })
|
|
18394
|
+
* const txHash = await prepared.execute()
|
|
18395
|
+
* ```
|
|
18396
|
+
*/ static async prepareDepositForBurn(params) {
|
|
18397
|
+
const { adapter, srcChain, dstChain, deposit, amountMinorUnits, refundAddress, signedQuote, feeToken, feeTotalAmount, resolvedContext } = params;
|
|
18398
|
+
// Resolve executor and depositForHandler from the destination chain's
|
|
18399
|
+
// gateway config. Throw an unsupported-route error if either is absent.
|
|
18400
|
+
const executor = dstChain.gateway?.contracts?.v1?.genericExecutor;
|
|
18401
|
+
const depositForHandler = dstChain.gateway?.contracts?.v1?.depositForHandler;
|
|
18402
|
+
if (!executor || !depositForHandler) {
|
|
18403
|
+
throw createUnsupportedRouteError(srcChain.name, dstChain.name);
|
|
18404
|
+
}
|
|
18405
|
+
// Resolve the canonical dApp config (function signature + amount indices)
|
|
18406
|
+
// for THIS deposit's `dappId`. Reject an unknown `dappId` rather than
|
|
18407
|
+
// encoding the wrong ABI selector, which would burn on the source but
|
|
18408
|
+
// revert in the executor call on the destination.
|
|
18409
|
+
const dappConfig = DAPP_CONFIG[deposit.dappId];
|
|
18410
|
+
if (dappConfig === undefined) {
|
|
18411
|
+
throw createUnsupportedRouteError(srcChain.name, dstChain.name);
|
|
18412
|
+
}
|
|
18413
|
+
// Build the bare GenericExecutor payload, then wrap it in the `cctp-forward`
|
|
18414
|
+
// frame required by the prepaid-FORWARD wrapper.
|
|
18415
|
+
const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
|
|
18416
|
+
dappId: deposit.dappId,
|
|
18417
|
+
destinationChain: dstChain,
|
|
18418
|
+
handler: depositForHandler,
|
|
18419
|
+
params: deposit.params,
|
|
18420
|
+
recoveryAddress: padAddressToBytes32(refundAddress),
|
|
18421
|
+
config: {
|
|
18422
|
+
[deposit.dappId]: {
|
|
18423
|
+
...dappConfig,
|
|
18424
|
+
deployments: [
|
|
18425
|
+
{
|
|
18426
|
+
domainId: dstChain.cctp.domain
|
|
18427
|
+
}
|
|
18428
|
+
]
|
|
18429
|
+
}
|
|
18430
|
+
}
|
|
18431
|
+
});
|
|
18432
|
+
const hookData = buildForwardingHookDataWithPayload(GENERIC_EXECUTOR_HOOK_DATA_VERSION, geBlob);
|
|
18433
|
+
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee
|
|
18434
|
+
// item, so the hookData must carry a `cctp-forward` frame; otherwise the
|
|
18435
|
+
// wrapper reverts `ForwardFeeWithoutHook`.
|
|
18436
|
+
assertForwardHookData(hookData);
|
|
18437
|
+
// Source-chain burn: `mintRecipient` AND `destinationCaller` are both the
|
|
18438
|
+
// executor; fees are prepaid against the signed quote.
|
|
18439
|
+
return adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
18440
|
+
fromChain: srcChain,
|
|
18441
|
+
toChain: dstChain,
|
|
18442
|
+
amount: amountMinorUnits,
|
|
18443
|
+
mintRecipient: executor,
|
|
18444
|
+
destinationCaller: executor,
|
|
18445
|
+
hookData,
|
|
18446
|
+
claim: {
|
|
18447
|
+
signedQuote,
|
|
18448
|
+
refundAddress
|
|
18449
|
+
},
|
|
18450
|
+
feeToken,
|
|
18451
|
+
feeTotalAmount
|
|
18452
|
+
}, resolvedContext);
|
|
18453
|
+
}
|
|
18454
|
+
/**
|
|
16680
18455
|
* Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
|
|
16681
18456
|
*
|
|
16682
|
-
*
|
|
16683
|
-
*
|
|
16684
|
-
*
|
|
16685
|
-
*
|
|
18457
|
+
* Build the source-chain `depositForBurnWithHookAndFees` call. Fees are
|
|
18458
|
+
* collected up front on the source chain against a signed quote. The
|
|
18459
|
+
* destination may use the GenericExecutor shorthand, or an explicit mint
|
|
18460
|
+
* recipient and destination caller for direct forwarding.
|
|
16686
18461
|
*
|
|
16687
|
-
* This is the low-level on-chain primitive behind the
|
|
16688
|
-
*
|
|
18462
|
+
* This is the low-level on-chain primitive behind the Unified Balance Kit
|
|
18463
|
+
* `fastCrossChainDeposit` and the Bridge Kit source-fee
|
|
18464
|
+
* (`feePayment: 'source'`) flow. The `hookData` and signed-quote
|
|
16689
18465
|
* `claim` are produced elsewhere and passed in here:
|
|
16690
18466
|
* - `hookData`: `buildForwardingHookDataWithPayload(version,
|
|
16691
18467
|
* buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
|
|
@@ -16702,32 +18478,48 @@ function assertCCTPV2Config(config) {
|
|
|
16702
18478
|
* approval covers both; the redundant second approval is skipped.
|
|
16703
18479
|
*
|
|
16704
18480
|
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
16705
|
-
* @param params - The burn amount,
|
|
18481
|
+
* @param params - The burn amount, destination, hook data, signed quote, and fee.
|
|
16706
18482
|
* @returns The prepared approvals, the prepared burn, and the resolved fee plan.
|
|
16707
18483
|
* @throws {KitError} If the wallet context is invalid, `destinationChain` does not
|
|
16708
|
-
* support CCTP v2, the
|
|
16709
|
-
* a bigint or a numeric string coercible to bigint,
|
|
16710
|
-
* `cctp-forward` frame (guaranteed
|
|
16711
|
-
* context cannot be resolved.
|
|
18484
|
+
* support CCTP v2, the destination fields are missing, `amount` or
|
|
18485
|
+
* `feeTotalAmount` is not a bigint or a numeric string coercible to bigint,
|
|
18486
|
+
* the hook data lacks a `cctp-forward` frame (guaranteed
|
|
18487
|
+
* `ForwardFeeWithoutHook`), or the operation context cannot be resolved.
|
|
16712
18488
|
*
|
|
16713
18489
|
* @example
|
|
16714
18490
|
* ```typescript
|
|
18491
|
+
* import {
|
|
18492
|
+
* CCTPV2BridgingProvider,
|
|
18493
|
+
* type BurnWithFeesParams,
|
|
18494
|
+
* } from '@circle-fin/provider-cctp-v2'
|
|
18495
|
+
*
|
|
18496
|
+
* declare const source: BurnWithFeesParams['source']
|
|
18497
|
+
* declare const destinationChain: BurnWithFeesParams['destinationChain']
|
|
18498
|
+
* declare const recipient: string
|
|
18499
|
+
* declare const hookData: string
|
|
18500
|
+
* declare const claim: BurnWithFeesParams['claim']
|
|
18501
|
+
*
|
|
18502
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
16715
18503
|
* const { approvals, burn } = await provider.burnWithFees({
|
|
16716
18504
|
* source,
|
|
16717
|
-
* destinationChain
|
|
18505
|
+
* destinationChain,
|
|
16718
18506
|
* amount: 1_000_000n,
|
|
16719
|
-
*
|
|
16720
|
-
*
|
|
16721
|
-
*
|
|
16722
|
-
*
|
|
16723
|
-
*
|
|
18507
|
+
* mintRecipient: recipient,
|
|
18508
|
+
* destinationCaller: '0x0000000000000000000000000000000000000000',
|
|
18509
|
+
* hookData,
|
|
18510
|
+
* claim,
|
|
18511
|
+
* feeToken: source.chain.usdcAddress,
|
|
18512
|
+
* feeTotalAmount: 10_000n,
|
|
16724
18513
|
* })
|
|
16725
18514
|
* for (const approval of approvals) await approval.execute()
|
|
16726
18515
|
* const txHash = await burn.execute()
|
|
16727
18516
|
* ```
|
|
16728
18517
|
*/ async burnWithFees(params) {
|
|
16729
18518
|
assertBurnWithFeesParams(params);
|
|
16730
|
-
const { source, destinationChain,
|
|
18519
|
+
const { source, destinationChain, hookData, claim, feeToken } = params;
|
|
18520
|
+
const hasExecutor = 'executor' in params && params.executor !== undefined;
|
|
18521
|
+
const mintRecipient = hasExecutor ? params.executor : params.mintRecipient;
|
|
18522
|
+
const destinationCaller = hasExecutor ? params.executor : params.destinationCaller;
|
|
16731
18523
|
const amount = BigInt(params.amount);
|
|
16732
18524
|
const feeTotalAmount = BigInt(params.feeTotalAmount);
|
|
16733
18525
|
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
|
|
@@ -16758,168 +18550,1171 @@ function assertCCTPV2Config(config) {
|
|
|
16758
18550
|
delegate: wrapperAddress,
|
|
16759
18551
|
amount: approval.amount
|
|
16760
18552
|
}, context)));
|
|
16761
|
-
// Build the burn
|
|
18553
|
+
// Build the burn with either the GenericExecutor shorthand or the explicit
|
|
18554
|
+
// direct-forwarding recipient and caller.
|
|
16762
18555
|
const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
16763
18556
|
fromChain: source.chain,
|
|
16764
18557
|
toChain: destinationChain,
|
|
16765
18558
|
amount,
|
|
16766
|
-
mintRecipient
|
|
16767
|
-
destinationCaller
|
|
18559
|
+
mintRecipient,
|
|
18560
|
+
destinationCaller,
|
|
16768
18561
|
hookData,
|
|
16769
18562
|
claim,
|
|
16770
18563
|
feeToken,
|
|
16771
18564
|
feeTotalAmount
|
|
16772
18565
|
}, context);
|
|
16773
18566
|
return {
|
|
16774
|
-
approvals,
|
|
16775
|
-
burn,
|
|
16776
|
-
feePayment
|
|
18567
|
+
approvals,
|
|
18568
|
+
burn,
|
|
18569
|
+
feePayment
|
|
18570
|
+
};
|
|
18571
|
+
}
|
|
18572
|
+
/**
|
|
18573
|
+
* Waits for a transaction to be mined and confirmed on the blockchain.
|
|
18574
|
+
*
|
|
18575
|
+
* This method should block until the transaction is confirmed on the blockchain.
|
|
18576
|
+
*
|
|
18577
|
+
* @param adapter - The adapter to use for transaction waiting
|
|
18578
|
+
* @param txHash - The hash of the transaction to wait for
|
|
18579
|
+
* @param chain - The chain definition where the transaction was executed
|
|
18580
|
+
* @param config - Optional configuration for transaction waiting (confirmations, timeout)
|
|
18581
|
+
* @returns The hash of the confirmed transaction
|
|
18582
|
+
* @example
|
|
18583
|
+
* ```typescript
|
|
18584
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
18585
|
+
* const txHash = await provider.waitForTransaction(
|
|
18586
|
+
* adapter,
|
|
18587
|
+
* '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
18588
|
+
* Ethereum,
|
|
18589
|
+
* )
|
|
18590
|
+
* console.log('Transaction confirmed:', txHash)
|
|
18591
|
+
* ```
|
|
18592
|
+
*/ async waitForTransaction(adapter, txHash, chain, config) {
|
|
18593
|
+
return adapter.waitForTransaction(txHash, config, chain);
|
|
18594
|
+
}
|
|
18595
|
+
}
|
|
18596
|
+
|
|
18597
|
+
/**
|
|
18598
|
+
* The default providers that will be used in addition to the providers provided
|
|
18599
|
+
* to the BridgeKit constructor.
|
|
18600
|
+
*
|
|
18601
|
+
* @param config - Optional configuration forwarded to the default providers
|
|
18602
|
+
* @returns The default bridging providers
|
|
18603
|
+
*/ const getDefaultProviders = (config = {})=>[
|
|
18604
|
+
new CCTPV2BridgingProvider(config.headers ? {
|
|
18605
|
+
headers: config.headers
|
|
18606
|
+
} : {})
|
|
18607
|
+
];
|
|
18608
|
+
|
|
18609
|
+
/**
|
|
18610
|
+
* A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
|
|
18611
|
+
* @param formatDirection - The direction to format the amount in.
|
|
18612
|
+
* @returns A function that transforms an amount into a human-readable string or a bigint string.
|
|
18613
|
+
*/ const getAmountTransformer = (formatDirection)=>formatDirection === 'to-human-readable' ? (params)=>formatAmount(params) : (params)=>parseAmount(params).toString();
|
|
18614
|
+
/**
|
|
18615
|
+
* Format the bridge result into human-readable string values for the user or bigint string values for internal use.
|
|
18616
|
+
*
|
|
18617
|
+
* @typeParam T - The specific result type (must extend BridgeResult or EstimateResult). Preserves the exact type passed in.
|
|
18618
|
+
* @param result - The bridge result to format.
|
|
18619
|
+
* @param formatDirection - The direction to format the result in.
|
|
18620
|
+
* - If 'to-human-readable', the result will be converted to human-readable string values.
|
|
18621
|
+
* - If 'to-internal', the result will be converted to bigint string values (usually for internal use).
|
|
18622
|
+
* @returns The formatted bridge result.
|
|
18623
|
+
*
|
|
18624
|
+
* @example
|
|
18625
|
+
* ```typescript
|
|
18626
|
+
* const result = await kit.bridge({
|
|
18627
|
+
* amount: '1000000',
|
|
18628
|
+
* token: 'USDC',
|
|
18629
|
+
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
18630
|
+
* to: { adapter: adapter, chain: 'Base' },
|
|
18631
|
+
* })
|
|
18632
|
+
*
|
|
18633
|
+
* // Format the bridge result into human-readable string values for the user
|
|
18634
|
+
* const formattedResultHumanReadable = formatBridgeResult(result, 'to-human-readable')
|
|
18635
|
+
* console.log(formattedResultHumanReadable)
|
|
18636
|
+
*
|
|
18637
|
+
* // Format the bridge result into bigint string values for internal use
|
|
18638
|
+
* const formattedResultInternal = formatBridgeResult(result, 'to-internal')
|
|
18639
|
+
* console.log(formattedResultInternal)
|
|
18640
|
+
* ```
|
|
18641
|
+
*/ const formatBridgeResult = (result, formatDirection)=>{
|
|
18642
|
+
const transform = getAmountTransformer(formatDirection);
|
|
18643
|
+
return {
|
|
18644
|
+
...result,
|
|
18645
|
+
amount: transform({
|
|
18646
|
+
value: result.amount,
|
|
18647
|
+
token: result.token
|
|
18648
|
+
}),
|
|
18649
|
+
...'config' in result && result.config && Object.keys(result.config).length > 0 && {
|
|
18650
|
+
config: {
|
|
18651
|
+
...result.config,
|
|
18652
|
+
...result.config.maxFee && {
|
|
18653
|
+
maxFee: transform({
|
|
18654
|
+
value: result.config.maxFee,
|
|
18655
|
+
token: result.token
|
|
18656
|
+
})
|
|
18657
|
+
},
|
|
18658
|
+
...result.config.customFee && {
|
|
18659
|
+
customFee: {
|
|
18660
|
+
...result.config.customFee,
|
|
18661
|
+
...result.config.customFee.value && {
|
|
18662
|
+
value: transform({
|
|
18663
|
+
value: result.config.customFee.value,
|
|
18664
|
+
token: result.token
|
|
18665
|
+
})
|
|
18666
|
+
}
|
|
18667
|
+
}
|
|
18668
|
+
}
|
|
18669
|
+
}
|
|
18670
|
+
}
|
|
18671
|
+
};
|
|
18672
|
+
};
|
|
18673
|
+
|
|
18674
|
+
/**
|
|
18675
|
+
* Register all bridge-kit event type strings with the shared registry so
|
|
18676
|
+
* callers of `withErrorTelemetry` / `emitResultStepErrorTelemetry` are
|
|
18677
|
+
* compile-time checked.
|
|
18678
|
+
*
|
|
18679
|
+
* @internal
|
|
18680
|
+
*/ /**
|
|
18681
|
+
* Telemetry event type identifiers for bridge-kit operations.
|
|
18682
|
+
*
|
|
18683
|
+
* @internal
|
|
18684
|
+
*/ const BRIDGE_EVENT_TYPES = {
|
|
18685
|
+
BRIDGE: 'bridge_bridge',
|
|
18686
|
+
RETRY: 'bridge_retry',
|
|
18687
|
+
ESTIMATE: 'bridge_estimate'
|
|
18688
|
+
};
|
|
18689
|
+
/**
|
|
18690
|
+
* Ordered mapping from provider step event names to telemetry event types.
|
|
18691
|
+
*
|
|
18692
|
+
* @remarks
|
|
18693
|
+
* The order matches the CCTP v2 bridge execution sequence. During
|
|
18694
|
+
* `bridge()`, completed step events are counted so the failing step
|
|
18695
|
+
* can be identified by its index.
|
|
18696
|
+
*
|
|
18697
|
+
* @internal
|
|
18698
|
+
*/ const BRIDGE_STEP_EVENT_MAP = [
|
|
18699
|
+
[
|
|
18700
|
+
'approve',
|
|
18701
|
+
'bridge_approve'
|
|
18702
|
+
],
|
|
18703
|
+
[
|
|
18704
|
+
'burn',
|
|
18705
|
+
'bridge_burn'
|
|
18706
|
+
],
|
|
18707
|
+
[
|
|
18708
|
+
'fetchAttestation',
|
|
18709
|
+
'bridge_fetch_attestation'
|
|
18710
|
+
],
|
|
18711
|
+
[
|
|
18712
|
+
'mint',
|
|
18713
|
+
'bridge_mint'
|
|
18714
|
+
]
|
|
18715
|
+
];
|
|
18716
|
+
|
|
18717
|
+
/**
|
|
18718
|
+
* Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
|
|
18719
|
+
*
|
|
18720
|
+
* @internal
|
|
18721
|
+
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
18722
|
+
/**
|
|
18723
|
+
* Base URL for Circle's Quote API (hosted in Iris) on testnet/sandbox.
|
|
18724
|
+
*
|
|
18725
|
+
* @internal
|
|
18726
|
+
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
18727
|
+
/**
|
|
18728
|
+
* Native fee-token sentinel (the zero address).
|
|
18729
|
+
*
|
|
18730
|
+
* When `feeToken` is the zero address the quote prices fees in the source
|
|
18731
|
+
* chain's native gas token (paid as `msg.value` on-chain). Pass a USDC token
|
|
18732
|
+
* address instead to denominate fees in USDC.
|
|
18733
|
+
*
|
|
18734
|
+
* @internal
|
|
18735
|
+
*/ const NATIVE_FEE_TOKEN = '0x0000000000000000000000000000000000000000';
|
|
18736
|
+
/**
|
|
18737
|
+
* API path prefix for the CCTP v2 USDC burn quote endpoint.
|
|
18738
|
+
*
|
|
18739
|
+
* The full path is `${QUOTE_BURN_USDC_PATH}/{sourceDomain}/{destinationDomain}`;
|
|
18740
|
+
* `usdc` is a fixed literal, not a token parameter.
|
|
18741
|
+
*
|
|
18742
|
+
* @internal
|
|
18743
|
+
*/ const QUOTE_BURN_USDC_PATH = '/v2/quote/burn/usdc';
|
|
18744
|
+
/**
|
|
18745
|
+
* API path prefix for the CCTP v2 USDC quote validate endpoint.
|
|
18746
|
+
*
|
|
18747
|
+
* The full path is `${QUOTE_VALIDATE_USDC_PATH}/{sourceDomain}`; accepts a
|
|
18748
|
+
* `POST { abiSignature, args }` body and returns `claimable`, `failedChecks`,
|
|
18749
|
+
* and the decoded `expiry`, `feeToken`, and `feeTotalAmount`.
|
|
18750
|
+
*
|
|
18751
|
+
* @internal
|
|
18752
|
+
*/ const QUOTE_VALIDATE_USDC_PATH = '/v2/quote/validate/usdc';
|
|
18753
|
+
/**
|
|
18754
|
+
* Default polling configuration for Quote API calls.
|
|
18755
|
+
*
|
|
18756
|
+
* A signed quote is short-lived (typically ~2 minutes, varying per chain) and
|
|
18757
|
+
* a feature-flag-disabled source chain returns a
|
|
18758
|
+
* permanent `503 SERVICE_NOT_ENABLED`, so retrying buys little and risks
|
|
18759
|
+
* outliving the quote. The client therefore makes a single attempt
|
|
18760
|
+
* (`maxRetries: 1`) with a 15s timeout, mirroring the reference
|
|
18761
|
+
* implementation; callers refresh by requesting a new quote rather than
|
|
18762
|
+
* relying on transport retries.
|
|
18763
|
+
*
|
|
18764
|
+
* No `headers` are set here: `pollApiWithValidation` always injects
|
|
18765
|
+
* `Content-Type: application/json` and adds `User-Agent` in Node. Browser
|
|
18766
|
+
* requests omit a user-agent header to avoid a CORS preflight, so duplicating
|
|
18767
|
+
* either header here would be dead configuration.
|
|
18768
|
+
*
|
|
18769
|
+
* @internal
|
|
18770
|
+
*/ const FEE_QUOTE_DEFAULT_CONFIG = {
|
|
18771
|
+
timeout: 15_000,
|
|
18772
|
+
maxRetries: 1,
|
|
18773
|
+
retryDelay: 200
|
|
18774
|
+
};
|
|
18775
|
+
|
|
18776
|
+
/**
|
|
18777
|
+
* Decimal string in token minor units, constrained to be strictly positive.
|
|
18778
|
+
*
|
|
18779
|
+
* @internal
|
|
18780
|
+
*/ const positiveAmountSchema = zod.z.string().regex(/^\d+$/, 'must be a non-negative integer string')// Re-check the digit shape here: zod still runs this refinement when the
|
|
18781
|
+
// regex check above fails ("dirty"), so guard BigInt() against throwing on a
|
|
18782
|
+
// non-numeric value before comparing.
|
|
18783
|
+
.refine((value)=>/^\d+$/.test(value) && BigInt(value) > 0n, 'must be greater than zero');
|
|
18784
|
+
/**
|
|
18785
|
+
* A 20-byte EVM address in `0x` hex.
|
|
18786
|
+
*
|
|
18787
|
+
* The MVP prepaid-`FORWARD` `burn/usdc` path targets EVM contracts
|
|
18788
|
+
* (`TokenMessengerWithFees` / `GenericExecutor`), so `feeToken` and
|
|
18789
|
+
* `destinationCaller` are constrained to EVM addresses by design. This is an
|
|
18790
|
+
* intentional scope limit, not a permanent one: it can be widened to other
|
|
18791
|
+
* address formats as the fee service expands to more chains.
|
|
18792
|
+
*/ const evmAddressSchema = zod.z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'must be a 20-byte 0x address');
|
|
18793
|
+
/** Even-length `0x` hex (the empty `0x` is allowed). */ const hexSchema = zod.z.string().regex(/^0x([a-fA-F0-9]{2})*$/, 'must be even-length 0x hex');
|
|
18794
|
+
/** Non-empty, even-length `0x` hex. */ const nonEmptyHexSchema = zod.z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex');
|
|
18795
|
+
/** A 32-byte `0x` hex hash. */ const bytes32Schema = zod.z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'must be a 32-byte 0x hash');
|
|
18796
|
+
/** Decimal string in minor units, allowing zero. */ const numericStringSchema = zod.z.string().regex(/^\d+$/, 'must be a non-negative integer string');
|
|
18797
|
+
/** An `https:` URL, used for the optional base-URL override. */ const httpsUrlSchema = zod.z.string().refine((value)=>{
|
|
18798
|
+
try {
|
|
18799
|
+
return new URL(value).protocol === 'https:';
|
|
18800
|
+
} catch {
|
|
18801
|
+
return false;
|
|
18802
|
+
}
|
|
18803
|
+
}, 'must be an https URL');
|
|
18804
|
+
const forwardParamsSchema = zod.z.object({
|
|
18805
|
+
hookData: hexSchema.optional(),
|
|
18806
|
+
destinationCaller: evmAddressSchema.optional()
|
|
18807
|
+
}).strict();
|
|
18808
|
+
const forwardRequestSchema = zod.z.object({
|
|
18809
|
+
type: zod.z.literal('FORWARD'),
|
|
18810
|
+
params: forwardParamsSchema.optional()
|
|
18811
|
+
}).strict();
|
|
18812
|
+
const preFinalityRequestSchema = zod.z.object({
|
|
18813
|
+
type: zod.z.literal('PRE_FINALITY')
|
|
18814
|
+
}).strict();
|
|
18815
|
+
/**
|
|
18816
|
+
* A single quote request item (`FORWARD` or `PRE_FINALITY`).
|
|
18817
|
+
*
|
|
18818
|
+
* @internal
|
|
18819
|
+
*/ const feeQuoteRequestSchema = zod.z.discriminatedUnion('type', [
|
|
18820
|
+
forwardRequestSchema,
|
|
18821
|
+
preFinalityRequestSchema
|
|
18822
|
+
]);
|
|
18823
|
+
/**
|
|
18824
|
+
* A non-empty list of quote request items with unique types.
|
|
18825
|
+
*
|
|
18826
|
+
* @internal
|
|
18827
|
+
*/ const feeQuoteRequestsSchema = zod.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');
|
|
18828
|
+
/**
|
|
18829
|
+
* A structured `Partial<ApiPollingConfig>` polling override.
|
|
18830
|
+
*
|
|
18831
|
+
* Validates the field types callers actually set, so a plain-JS caller passing
|
|
18832
|
+
* `{ timeout: 'soon' }` is rejected at the boundary rather than failing opaquely
|
|
18833
|
+
* inside the transport. Unknown keys pass through so a future `ApiPollingConfig`
|
|
18834
|
+
* field is forwarded rather than silently dropped.
|
|
18835
|
+
*/ const apiPollingConfigSchema = zod.z.object({
|
|
18836
|
+
timeout: zod.z.number().int().positive().optional(),
|
|
18837
|
+
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
18838
|
+
retryDelay: zod.z.number().int().nonnegative().optional(),
|
|
18839
|
+
backoff: zod.z.enum([
|
|
18840
|
+
'fixed',
|
|
18841
|
+
'exponential'
|
|
18842
|
+
]).optional(),
|
|
18843
|
+
maxRetryDelayMs: zod.z.number().int().positive().optional(),
|
|
18844
|
+
headers: zod.z.record(zod.z.string()).optional()
|
|
18845
|
+
}).passthrough();
|
|
18846
|
+
/**
|
|
18847
|
+
* The validatable input for {@link fetchFeeQuote}.
|
|
18848
|
+
*
|
|
18849
|
+
* This is the single source of truth for input validation, including the CCTP
|
|
18850
|
+
* domains and the `isTestnet` environment flag. Validating `isTestnet` at
|
|
18851
|
+
* runtime matters because a plain-JS caller who omits it would otherwise leave
|
|
18852
|
+
* it `undefined`, which is falsy and silently selects the production base URL.
|
|
18853
|
+
* (`buildFeeQuoteUrl` independently re-validates the domains for standalone
|
|
18854
|
+
* callers.)
|
|
18855
|
+
*
|
|
18856
|
+
* @internal
|
|
18857
|
+
*/ const fetchFeeQuoteInputSchema = zod.z.object({
|
|
18858
|
+
sourceDomain: zod.z.number().int().nonnegative(),
|
|
18859
|
+
destinationDomain: zod.z.number().int().nonnegative(),
|
|
18860
|
+
amount: positiveAmountSchema,
|
|
18861
|
+
feeToken: evmAddressSchema.optional(),
|
|
18862
|
+
requests: feeQuoteRequestsSchema,
|
|
18863
|
+
isTestnet: zod.z.boolean(),
|
|
18864
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
18865
|
+
config: apiPollingConfigSchema.optional()
|
|
18866
|
+
}).strict();
|
|
18867
|
+
const feeQuoteItemSchema = zod.z.object({
|
|
18868
|
+
type: zod.z.string().min(1),
|
|
18869
|
+
amount: numericStringSchema,
|
|
18870
|
+
args: zod.z.array(zod.z.string()),
|
|
18871
|
+
argsHash: bytes32Schema
|
|
18872
|
+
}).passthrough();
|
|
18873
|
+
const exchangeRatesSchema = zod.z.object({
|
|
18874
|
+
feeTokenUsd: zod.z.string(),
|
|
18875
|
+
destinationTokenUsd: zod.z.string()
|
|
18876
|
+
}).passthrough();
|
|
18877
|
+
const metadataSchema = zod.z.object({
|
|
18878
|
+
destinationGasPrice: zod.z.string().optional(),
|
|
18879
|
+
exchangeRates: exchangeRatesSchema.optional()
|
|
18880
|
+
}).passthrough();
|
|
18881
|
+
/** The `expiry` object the Quote API nests the quote TTL under. */ const expirySchema = zod.z.discriminatedUnion('mode', [
|
|
18882
|
+
zod.z.object({
|
|
18883
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
18884
|
+
expiresAt: zod.z.number().int().nonnegative()
|
|
18885
|
+
}).passthrough(),
|
|
18886
|
+
zod.z.object({
|
|
18887
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
18888
|
+
expiresAtBlock: zod.z.number().int().nonnegative(),
|
|
18889
|
+
blockEstimatedAt: zod.z.number().int().nonnegative().optional()
|
|
18890
|
+
}).passthrough()
|
|
18891
|
+
]);
|
|
18892
|
+
/**
|
|
18893
|
+
* Schema for a signed fee quote returned by the Quote API.
|
|
18894
|
+
*
|
|
18895
|
+
* @internal
|
|
18896
|
+
*/ const signedFeeQuoteSchema = zod.z.object({
|
|
18897
|
+
// The runtime YAML spec maps signedQuote to a looser `hex` (which allows
|
|
18898
|
+
// an empty `0x`); we keep the stricter non-empty form. Do not relax
|
|
18899
|
+
// without a reason.
|
|
18900
|
+
signedQuote: nonEmptyHexSchema,
|
|
18901
|
+
issuedAt: zod.z.number().int().nonnegative(),
|
|
18902
|
+
// The API returns a mode-specific timestamp or source-block deadline.
|
|
18903
|
+
expiry: expirySchema,
|
|
18904
|
+
feeTotalAmount: numericStringSchema,
|
|
18905
|
+
feeToken: evmAddressSchema,
|
|
18906
|
+
nonce: numericStringSchema,
|
|
18907
|
+
items: zod.z.array(feeQuoteItemSchema),
|
|
18908
|
+
metadata: metadataSchema.optional()
|
|
18909
|
+
}).passthrough();
|
|
18910
|
+
/**
|
|
18911
|
+
* Validate that an unknown value is a signed fee quote.
|
|
18912
|
+
*
|
|
18913
|
+
* @param value - The unknown value to validate.
|
|
18914
|
+
* @returns `true` when the value matches the signed-quote response shape.
|
|
18915
|
+
*
|
|
18916
|
+
* @example
|
|
18917
|
+
* ```typescript
|
|
18918
|
+
* import { isSignedFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
18919
|
+
*
|
|
18920
|
+
* declare const payload: unknown
|
|
18921
|
+
* if (isSignedFeeQuote(payload)) {
|
|
18922
|
+
* console.log(payload.feeTotalAmount)
|
|
18923
|
+
* }
|
|
18924
|
+
* ```
|
|
18925
|
+
*
|
|
18926
|
+
* @internal
|
|
18927
|
+
*/ function isSignedFeeQuote(value) {
|
|
18928
|
+
return signedFeeQuoteSchema.safeParse(value).success;
|
|
18929
|
+
}
|
|
18930
|
+
/**
|
|
18931
|
+
* Validates input to {@link validateQuote}.
|
|
18932
|
+
*
|
|
18933
|
+
* @internal
|
|
18934
|
+
*/ const validateQuoteInputSchema = zod.z.object({
|
|
18935
|
+
sourceDomain: zod.z.number().int().nonnegative(),
|
|
18936
|
+
abiSignature: zod.z.string().min(1),
|
|
18937
|
+
args: zod.z.array(zod.z.union([
|
|
18938
|
+
zod.z.string(),
|
|
18939
|
+
zod.z.array(zod.z.string())
|
|
18940
|
+
])),
|
|
18941
|
+
isTestnet: zod.z.boolean(),
|
|
18942
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
18943
|
+
config: apiPollingConfigSchema.optional()
|
|
18944
|
+
}).strict();
|
|
18945
|
+
const quoteExpiryStatusSchema = zod.z.discriminatedUnion('mode', [
|
|
18946
|
+
zod.z.object({
|
|
18947
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
18948
|
+
expired: zod.z.boolean(),
|
|
18949
|
+
secondsRemaining: zod.z.number().int().nonnegative(),
|
|
18950
|
+
expiresAt: zod.z.number().int().nonnegative()
|
|
18951
|
+
}).passthrough(),
|
|
18952
|
+
zod.z.object({
|
|
18953
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
18954
|
+
expired: zod.z.boolean(),
|
|
18955
|
+
secondsRemaining: zod.z.number().int().nonnegative(),
|
|
18956
|
+
expiresAtBlock: zod.z.number().int().nonnegative(),
|
|
18957
|
+
blockEstimatedAt: zod.z.number().int().nonnegative().optional()
|
|
18958
|
+
}).passthrough()
|
|
18959
|
+
]);
|
|
18960
|
+
const validateQuoteItemSchema = zod.z.object({
|
|
18961
|
+
type: zod.z.string().min(1),
|
|
18962
|
+
argsMatch: zod.z.boolean(),
|
|
18963
|
+
amount: numericStringSchema.optional(),
|
|
18964
|
+
args: zod.z.array(zod.z.string()).optional(),
|
|
18965
|
+
argsHash: bytes32Schema.optional(),
|
|
18966
|
+
computedArgsHash: bytes32Schema.optional()
|
|
18967
|
+
}).passthrough();
|
|
18968
|
+
/**
|
|
18969
|
+
* Schema for a validate-quote result returned by the Iris `/validate/usdc/:sourceDomain` endpoint.
|
|
18970
|
+
*
|
|
18971
|
+
* The endpoint takes the source domain as a URL path parameter and does not
|
|
18972
|
+
* return it in the response body, so `sourceDomain` is intentionally not part
|
|
18973
|
+
* of this schema.
|
|
18974
|
+
*
|
|
18975
|
+
* @internal
|
|
18976
|
+
*/ const validateQuoteResultSchema = zod.z.object({
|
|
18977
|
+
signedQuote: nonEmptyHexSchema,
|
|
18978
|
+
expiry: quoteExpiryStatusSchema,
|
|
18979
|
+
feeTotalAmount: numericStringSchema,
|
|
18980
|
+
feeToken: evmAddressSchema,
|
|
18981
|
+
nonce: numericStringSchema,
|
|
18982
|
+
claimable: zod.z.boolean(),
|
|
18983
|
+
// Preserve newly introduced server-side reasons as opaque strings rather
|
|
18984
|
+
// than rejecting the entire safety response before the SDK is updated.
|
|
18985
|
+
failedChecks: zod.z.array(zod.z.string().min(1)),
|
|
18986
|
+
items: zod.z.array(validateQuoteItemSchema)
|
|
18987
|
+
}).passthrough();
|
|
18988
|
+
/**
|
|
18989
|
+
* Validate that an unknown value is a validate-quote result.
|
|
18990
|
+
*
|
|
18991
|
+
* @param value - The unknown value to validate.
|
|
18992
|
+
* @returns `true` when the value matches the validate-quote response shape.
|
|
18993
|
+
*
|
|
18994
|
+
* @example
|
|
18995
|
+
* ```typescript
|
|
18996
|
+
* import { isValidateQuoteResult } from '@circle-fin/provider-fee-v1'
|
|
18997
|
+
*
|
|
18998
|
+
* declare const payload: unknown
|
|
18999
|
+
* if (isValidateQuoteResult(payload)) {
|
|
19000
|
+
* console.log(payload.claimable, payload.failedChecks)
|
|
19001
|
+
* }
|
|
19002
|
+
* ```
|
|
19003
|
+
*
|
|
19004
|
+
* @internal
|
|
19005
|
+
*/ function isValidateQuoteResult(value) {
|
|
19006
|
+
return validateQuoteResultSchema.safeParse(value).success;
|
|
19007
|
+
}
|
|
19008
|
+
|
|
19009
|
+
/**
|
|
19010
|
+
* Validate that a CCTP domain id is a non-negative integer.
|
|
19011
|
+
*
|
|
19012
|
+
* @param value - The domain id to validate.
|
|
19013
|
+
* @param label - The parameter name, used in the error message.
|
|
19014
|
+
* @returns Nothing.
|
|
19015
|
+
* @throws {@link KitError} When the value is not a non-negative integer.
|
|
19016
|
+
* @internal
|
|
19017
|
+
*/ function assertDomain(value, label) {
|
|
19018
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
19019
|
+
throw new KitError({
|
|
19020
|
+
...InputError.VALIDATION_FAILED,
|
|
19021
|
+
recoverability: 'FATAL',
|
|
19022
|
+
message: `Quote API getFeeQuote failed: ${label} must be a ` + `non-negative integer, received ${String(value)}`,
|
|
19023
|
+
cause: {
|
|
19024
|
+
trace: {
|
|
19025
|
+
[label]: value
|
|
19026
|
+
}
|
|
19027
|
+
}
|
|
19028
|
+
});
|
|
19029
|
+
}
|
|
19030
|
+
}
|
|
19031
|
+
/**
|
|
19032
|
+
* Build the Quote API URL for a CCTP v2 USDC burn quote.
|
|
19033
|
+
*
|
|
19034
|
+
* Resolves the environment base URL (or an explicit `baseUrl` override) and
|
|
19035
|
+
* appends the burn/usdc path with the source and destination CCTP domains.
|
|
19036
|
+
* `usdc` is a fixed path literal, not a token parameter.
|
|
19037
|
+
*
|
|
19038
|
+
* @param params - The domains and environment selector.
|
|
19039
|
+
* @returns The fully-qualified Quote API URL.
|
|
19040
|
+
* @throws {@link KitError} When either domain is not a non-negative integer.
|
|
19041
|
+
*
|
|
19042
|
+
* @example
|
|
19043
|
+
* ```typescript
|
|
19044
|
+
* import { buildFeeQuoteUrl } from '@circle-fin/provider-fee-v1'
|
|
19045
|
+
*
|
|
19046
|
+
* const url = buildFeeQuoteUrl({
|
|
19047
|
+
* sourceDomain: 3,
|
|
19048
|
+
* destinationDomain: 26,
|
|
19049
|
+
* isTestnet: false,
|
|
19050
|
+
* })
|
|
19051
|
+
* // => 'https://iris-api.circle.com/v2/quote/burn/usdc/3/26'
|
|
19052
|
+
* ```
|
|
19053
|
+
*
|
|
19054
|
+
* @internal
|
|
19055
|
+
*/ function buildFeeQuoteUrl(params) {
|
|
19056
|
+
const { sourceDomain, destinationDomain, isTestnet, baseUrl } = params;
|
|
19057
|
+
assertDomain(sourceDomain, 'sourceDomain');
|
|
19058
|
+
assertDomain(destinationDomain, 'destinationDomain');
|
|
19059
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
19060
|
+
return new URL(`${QUOTE_BURN_USDC_PATH}/${String(sourceDomain)}/${String(destinationDomain)}`, resolvedBaseUrl).toString();
|
|
19061
|
+
}
|
|
19062
|
+
|
|
19063
|
+
/**
|
|
19064
|
+
* Assert that a quote's per-item fee amounts sum to its `feeTotalAmount`.
|
|
19065
|
+
*
|
|
19066
|
+
* A defensive integrity check on the Quote API response, enforced internally
|
|
19067
|
+
* by `fetchFeeQuote`. It does not compare against the destination-side
|
|
19068
|
+
* `feeExecuted`, which is expected to be zero for prepaid forward-only burns.
|
|
19069
|
+
*
|
|
19070
|
+
* @param quote - The signed fee quote to check.
|
|
19071
|
+
* @returns Nothing.
|
|
19072
|
+
* @throws {@link KitError} When the item amounts do not sum to `feeTotalAmount`.
|
|
19073
|
+
* @internal
|
|
19074
|
+
*/ function assertFeeItemsSumToTotal(quote) {
|
|
19075
|
+
const itemsTotal = quote.items.reduce((sum, item)=>sum + BigInt(item.amount), 0n);
|
|
19076
|
+
const declaredTotal = BigInt(quote.feeTotalAmount);
|
|
19077
|
+
if (itemsTotal !== declaredTotal) {
|
|
19078
|
+
throw new KitError({
|
|
19079
|
+
...InputError.VALIDATION_FAILED,
|
|
19080
|
+
recoverability: 'FATAL',
|
|
19081
|
+
message: `Quote API getFeeQuote failed: fee items sum ` + `(${itemsTotal.toString()}) does not equal feeTotalAmount ` + `(${declaredTotal.toString()})`,
|
|
19082
|
+
cause: {
|
|
19083
|
+
trace: {
|
|
19084
|
+
itemsTotal: itemsTotal.toString(),
|
|
19085
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19086
|
+
}
|
|
19087
|
+
}
|
|
19088
|
+
});
|
|
19089
|
+
}
|
|
19090
|
+
}
|
|
19091
|
+
|
|
19092
|
+
/**
|
|
19093
|
+
* Determine whether a Quote API error represents a disabled source chain.
|
|
19094
|
+
*
|
|
19095
|
+
* @param error - The error thrown by the HTTP layer.
|
|
19096
|
+
* @returns `true` when the error contains the `SERVICE_NOT_ENABLED` marker.
|
|
19097
|
+
* @internal
|
|
19098
|
+
*/ function isServiceNotEnabled(error) {
|
|
19099
|
+
const body = typeof error === 'object' && error !== null && 'responseBody' in error ? error.responseBody : undefined;
|
|
19100
|
+
if (typeof body === 'object' && body !== null) {
|
|
19101
|
+
const fields = body;
|
|
19102
|
+
const candidates = [
|
|
19103
|
+
fields['errorCode'],
|
|
19104
|
+
fields['code'],
|
|
19105
|
+
fields['message'],
|
|
19106
|
+
fields['externalMessage'],
|
|
19107
|
+
fields['error']
|
|
19108
|
+
];
|
|
19109
|
+
if (candidates.some((value)=>typeof value === 'string' && value.toUpperCase().includes('SERVICE_NOT_ENABLED'))) {
|
|
19110
|
+
return true;
|
|
19111
|
+
}
|
|
19112
|
+
}
|
|
19113
|
+
return getErrorMessage(error).toUpperCase().includes('SERVICE_NOT_ENABLED');
|
|
19114
|
+
}
|
|
19115
|
+
|
|
19116
|
+
const SERVICE$1 = 'Quote API';
|
|
19117
|
+
const OPERATION$1 = 'getFeeQuote';
|
|
19118
|
+
/**
|
|
19119
|
+
* Serialize request items for the wire body.
|
|
19120
|
+
*
|
|
19121
|
+
* `PRE_FINALITY` is emitted with no `params` key, and a `FORWARD` item only
|
|
19122
|
+
* carries the binding fields that are present.
|
|
19123
|
+
*
|
|
19124
|
+
* @param requests - The request items to serialize.
|
|
19125
|
+
* @returns The serialized request items.
|
|
19126
|
+
* @internal
|
|
19127
|
+
*/ function serializeRequests(requests) {
|
|
19128
|
+
return requests.map((request)=>{
|
|
19129
|
+
if (request.type === 'PRE_FINALITY') {
|
|
19130
|
+
return {
|
|
19131
|
+
type: 'PRE_FINALITY'
|
|
19132
|
+
};
|
|
19133
|
+
}
|
|
19134
|
+
const params = request.params;
|
|
19135
|
+
if (params === undefined) {
|
|
19136
|
+
return {
|
|
19137
|
+
type: 'FORWARD'
|
|
19138
|
+
};
|
|
19139
|
+
}
|
|
19140
|
+
const forwardParams = {};
|
|
19141
|
+
if (params.hookData !== undefined) {
|
|
19142
|
+
forwardParams.hookData = params.hookData;
|
|
19143
|
+
}
|
|
19144
|
+
if (params.destinationCaller !== undefined) {
|
|
19145
|
+
forwardParams.destinationCaller = params.destinationCaller;
|
|
19146
|
+
}
|
|
19147
|
+
return {
|
|
19148
|
+
type: 'FORWARD',
|
|
19149
|
+
params: forwardParams
|
|
19150
|
+
};
|
|
19151
|
+
});
|
|
19152
|
+
}
|
|
19153
|
+
/**
|
|
19154
|
+
* Fetch a signed fee quote from Circle's Quote API for a CCTP v2 USDC burn.
|
|
19155
|
+
*
|
|
19156
|
+
* Validates inputs, POSTs to
|
|
19157
|
+
* `/v2/quote/burn/usdc/{sourceDomain}/{destinationDomain}` with a single
|
|
19158
|
+
* attempt (the signed quote is short-lived), and returns the typed quote. A
|
|
19159
|
+
* disabled source chain (`503 SERVICE_NOT_ENABLED`) surfaces as a fatal,
|
|
19160
|
+
* non-retryable error; other failures are mapped to a {@link KitError} via the
|
|
19161
|
+
* shared API error parser.
|
|
19162
|
+
*
|
|
19163
|
+
* @param params - The domains, amount, request items, and environment.
|
|
19164
|
+
* @returns The signed fee quote.
|
|
19165
|
+
* @throws {@link KitError} On invalid input, a disabled source chain, an HTTP
|
|
19166
|
+
* error, or an invalid response shape.
|
|
19167
|
+
*
|
|
19168
|
+
* @example
|
|
19169
|
+
* ```typescript
|
|
19170
|
+
* import { fetchFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
19171
|
+
*
|
|
19172
|
+
* const quote = await fetchFeeQuote({
|
|
19173
|
+
* sourceDomain: 3,
|
|
19174
|
+
* destinationDomain: 26,
|
|
19175
|
+
* amount: '1000000',
|
|
19176
|
+
* requests: [{ type: 'FORWARD' }, { type: 'PRE_FINALITY' }],
|
|
19177
|
+
* isTestnet: false,
|
|
19178
|
+
* })
|
|
19179
|
+
* console.log(quote.feeTotalAmount, quote.expiry)
|
|
19180
|
+
* ```
|
|
19181
|
+
*
|
|
19182
|
+
* @internal
|
|
19183
|
+
*/ async function fetchFeeQuote(params) {
|
|
19184
|
+
const { sourceDomain, destinationDomain, amount, requests, feeToken, isTestnet, baseUrl, config } = params;
|
|
19185
|
+
const parsed = fetchFeeQuoteInputSchema.safeParse({
|
|
19186
|
+
sourceDomain,
|
|
19187
|
+
destinationDomain,
|
|
19188
|
+
amount,
|
|
19189
|
+
feeToken,
|
|
19190
|
+
requests,
|
|
19191
|
+
isTestnet,
|
|
19192
|
+
baseUrl,
|
|
19193
|
+
config
|
|
19194
|
+
});
|
|
19195
|
+
if (!parsed.success) {
|
|
19196
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
19197
|
+
throw new KitError({
|
|
19198
|
+
...InputError.VALIDATION_FAILED,
|
|
19199
|
+
recoverability: 'FATAL',
|
|
19200
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: ${detail}`,
|
|
19201
|
+
cause: {
|
|
19202
|
+
trace: parsed.error.issues
|
|
19203
|
+
}
|
|
19204
|
+
});
|
|
19205
|
+
}
|
|
19206
|
+
const url = baseUrl === undefined ? buildFeeQuoteUrl({
|
|
19207
|
+
sourceDomain,
|
|
19208
|
+
destinationDomain,
|
|
19209
|
+
isTestnet
|
|
19210
|
+
}) : buildFeeQuoteUrl({
|
|
19211
|
+
sourceDomain,
|
|
19212
|
+
destinationDomain,
|
|
19213
|
+
isTestnet,
|
|
19214
|
+
baseUrl
|
|
19215
|
+
});
|
|
19216
|
+
const body = {
|
|
19217
|
+
amount,
|
|
19218
|
+
feeToken: feeToken ?? NATIVE_FEE_TOKEN,
|
|
19219
|
+
requests: serializeRequests(requests)
|
|
19220
|
+
};
|
|
19221
|
+
const pollingConfig = {
|
|
19222
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
19223
|
+
...config
|
|
19224
|
+
};
|
|
19225
|
+
let quote;
|
|
19226
|
+
try {
|
|
19227
|
+
quote = await pollApiPost(url, body, isSignedFeeQuote, pollingConfig);
|
|
19228
|
+
} catch (error) {
|
|
19229
|
+
// Only one service-specific code (SERVICE_NOT_ENABLED) needs bespoke
|
|
19230
|
+
// mapping, so it is detected inline rather than via a dedicated
|
|
19231
|
+
// `parseFeeQuoteApiError` parser; everything else flows through the shared
|
|
19232
|
+
// `parseApiError`. Promote to a parser if more coded errors appear.
|
|
19233
|
+
if (isServiceNotEnabled(error)) {
|
|
19234
|
+
throw new KitError({
|
|
19235
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
19236
|
+
recoverability: 'FATAL',
|
|
19237
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: source chain not enabled for fee ` + `quotes (SERVICE_NOT_ENABLED)`,
|
|
19238
|
+
cause: {
|
|
19239
|
+
trace: error
|
|
19240
|
+
}
|
|
19241
|
+
});
|
|
19242
|
+
}
|
|
19243
|
+
throw parseApiError(error, {
|
|
19244
|
+
service: SERVICE$1,
|
|
19245
|
+
operation: OPERATION$1
|
|
19246
|
+
});
|
|
19247
|
+
}
|
|
19248
|
+
// Defense-in-depth: a self-consistent quote's per-item fees sum to the
|
|
19249
|
+
// declared total. Enforced here so callers cannot forget the check.
|
|
19250
|
+
assertFeeItemsSumToTotal(quote);
|
|
19251
|
+
return quote;
|
|
19252
|
+
}
|
|
19253
|
+
|
|
19254
|
+
const SERVICE = 'Quote API';
|
|
19255
|
+
const OPERATION = 'validateQuote';
|
|
19256
|
+
/**
|
|
19257
|
+
* Validate a signed fee quote against a full on-chain call via Circle's Iris
|
|
19258
|
+
* `/v2/quote/validate/usdc/:sourceDomain` endpoint.
|
|
19259
|
+
*
|
|
19260
|
+
* POSTs the ABI function signature and the encoded call arguments to Iris,
|
|
19261
|
+
* which verifies the signature, checks expiry, confirms the argsHash committed
|
|
19262
|
+
* in the quote matches the submitted args, and returns `claimable` plus the
|
|
19263
|
+
* decoded `expiry`, `feeToken`, and `feeTotalAmount`.
|
|
19264
|
+
*
|
|
19265
|
+
* @param params - The source domain, ABI signature, call arguments, and environment.
|
|
19266
|
+
* @returns The claimability, binding checks, and authoritative expiry status.
|
|
19267
|
+
* @throws {@link KitError} When input, transport, or response validation fails.
|
|
19268
|
+
*
|
|
19269
|
+
* @example
|
|
19270
|
+
* ```typescript
|
|
19271
|
+
* import { validateQuote } from '@circle-fin/provider-fee-v1'
|
|
19272
|
+
*
|
|
19273
|
+
* const result = await validateQuote({
|
|
19274
|
+
* sourceDomain: 3,
|
|
19275
|
+
* // The exact function + arguments, in ABI order, that will be burned on-chain.
|
|
19276
|
+
* abiSignature:
|
|
19277
|
+
* 'depositForBurnWithHookAndFees(uint256,uint32,bytes32,address,bytes32,bytes,(bytes,address))',
|
|
19278
|
+
* args: [
|
|
19279
|
+
* '1000000',
|
|
19280
|
+
* '26',
|
|
19281
|
+
* '0x0000000000000000000000001111111111111111111111111111111111111111',
|
|
19282
|
+
* '0x2222222222222222222222222222222222222222',
|
|
19283
|
+
* '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
19284
|
+
* '0x636374702d666f72776172640000000000000000000000000000000000000000',
|
|
19285
|
+
* ['0x01abcd', '0x3333333333333333333333333333333333333333'],
|
|
19286
|
+
* ],
|
|
19287
|
+
* isTestnet: false,
|
|
19288
|
+
* })
|
|
19289
|
+
* console.log(result.claimable, result.expiry.secondsRemaining)
|
|
19290
|
+
* if (!result.claimable) {
|
|
19291
|
+
* console.error('Quote rejected:', result.failedChecks)
|
|
19292
|
+
* }
|
|
19293
|
+
* ```
|
|
19294
|
+
*
|
|
19295
|
+
* @internal
|
|
19296
|
+
*/ async function validateQuote(params) {
|
|
19297
|
+
const parsed = validateQuoteInputSchema.safeParse(params);
|
|
19298
|
+
if (!parsed.success) {
|
|
19299
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
19300
|
+
throw new KitError({
|
|
19301
|
+
...InputError.VALIDATION_FAILED,
|
|
19302
|
+
recoverability: 'FATAL',
|
|
19303
|
+
message: `${SERVICE} ${OPERATION} failed: ${detail}`,
|
|
19304
|
+
cause: {
|
|
19305
|
+
trace: parsed.error.issues
|
|
19306
|
+
}
|
|
19307
|
+
});
|
|
19308
|
+
}
|
|
19309
|
+
const { sourceDomain, abiSignature, args, isTestnet, baseUrl } = parsed.data;
|
|
19310
|
+
// `config` was validated by the schema above; spread the caller's original,
|
|
19311
|
+
// precisely typed `Partial<ApiPollingConfig>` so the merged polling config
|
|
19312
|
+
// stays assignable under `exactOptionalPropertyTypes`.
|
|
19313
|
+
const pollingConfig = {
|
|
19314
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
19315
|
+
...params.config
|
|
19316
|
+
};
|
|
19317
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
19318
|
+
const url = new URL(`${QUOTE_VALIDATE_USDC_PATH}/${String(sourceDomain)}`, resolvedBaseUrl).toString();
|
|
19319
|
+
try {
|
|
19320
|
+
return await pollApiPost(url, {
|
|
19321
|
+
abiSignature,
|
|
19322
|
+
args
|
|
19323
|
+
}, isValidateQuoteResult, pollingConfig);
|
|
19324
|
+
} catch (error) {
|
|
19325
|
+
if (isServiceNotEnabled(error)) {
|
|
19326
|
+
throw new KitError({
|
|
19327
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
19328
|
+
recoverability: 'FATAL',
|
|
19329
|
+
message: `${SERVICE} ${OPERATION} failed: source chain not enabled for ` + `quote validation (SERVICE_NOT_ENABLED)`,
|
|
19330
|
+
cause: {
|
|
19331
|
+
trace: error
|
|
19332
|
+
}
|
|
19333
|
+
});
|
|
19334
|
+
}
|
|
19335
|
+
throw parseApiError(error, {
|
|
19336
|
+
service: SERVICE,
|
|
19337
|
+
operation: OPERATION
|
|
19338
|
+
});
|
|
19339
|
+
}
|
|
19340
|
+
}
|
|
19341
|
+
|
|
19342
|
+
/** Refresh timestamp quotes this many seconds before submission. */ const QUOTE_EXPIRY_SAFETY_SECONDS = 30;
|
|
19343
|
+
/** 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))';
|
|
19344
|
+
/** Unrestricted CCTP destination caller used by the forwarding relayer. */ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
19345
|
+
function isQuoteNearEstimatedExpiry(quote) {
|
|
19346
|
+
const currentSeconds = Math.floor(Date.now() / 1_000);
|
|
19347
|
+
const expiresAt = quote.expiry.mode === 'TIMESTAMP' ? quote.expiry.expiresAt : quote.expiry.blockEstimatedAt;
|
|
19348
|
+
// A BLOCK_NUMBER quote may omit the advisory `blockEstimatedAt` estimate; when
|
|
19349
|
+
// it is absent, skip this wall-clock pre-check and defer to the authoritative
|
|
19350
|
+
// source-chain-tip validation performed downstream.
|
|
19351
|
+
if (expiresAt === undefined) {
|
|
19352
|
+
return false;
|
|
19353
|
+
}
|
|
19354
|
+
return expiresAt <= currentSeconds + QUOTE_EXPIRY_SAFETY_SECONDS;
|
|
19355
|
+
}
|
|
19356
|
+
function assertSourceFeeRoute(params) {
|
|
19357
|
+
const { source, destination, config } = params;
|
|
19358
|
+
// Keep these checks explicit so a successful assertion guarantees the CCTP
|
|
19359
|
+
// v2 narrowing; hasSourceFeeSupport returns a plain boolean.
|
|
19360
|
+
if (source.chain.type !== 'evm' || destination.chain.type !== 'evm' || !isCCTPV2Supported(source.chain) || !isCCTPV2Supported(destination.chain)) {
|
|
19361
|
+
throw createUnsupportedRouteError(source.chain.name, destination.chain.name);
|
|
19362
|
+
}
|
|
19363
|
+
if (!hasSourceFeeSupport(source.chain)) {
|
|
19364
|
+
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');
|
|
19365
|
+
}
|
|
19366
|
+
const useForwarder = destination.useForwarder;
|
|
19367
|
+
if (useForwarder !== true) {
|
|
19368
|
+
throw createValidationFailedError$1('to.useForwarder', useForwarder, "feePayment: 'source' requires useForwarder: true");
|
|
19369
|
+
}
|
|
19370
|
+
if (config.customFee !== undefined) {
|
|
19371
|
+
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.');
|
|
19372
|
+
}
|
|
19373
|
+
}
|
|
19374
|
+
function buildQuoteBinding(params) {
|
|
19375
|
+
assertSourceFeeRoute(params);
|
|
19376
|
+
const mintRecipient = params.destination.recipientAddress ?? params.destination.address;
|
|
19377
|
+
const hookData = buildForwardingHookData();
|
|
19378
|
+
const requests = [
|
|
19379
|
+
{
|
|
19380
|
+
type: 'FORWARD',
|
|
19381
|
+
params: {
|
|
19382
|
+
hookData,
|
|
19383
|
+
destinationCaller: ZERO_ADDRESS
|
|
19384
|
+
}
|
|
19385
|
+
}
|
|
19386
|
+
];
|
|
19387
|
+
if ((params.config.transferSpeed ?? TransferSpeed.FAST) === TransferSpeed.FAST) {
|
|
19388
|
+
requests.push({
|
|
19389
|
+
type: 'PRE_FINALITY'
|
|
19390
|
+
});
|
|
19391
|
+
}
|
|
19392
|
+
return {
|
|
19393
|
+
sourceDomain: params.source.chain.cctp.domain,
|
|
19394
|
+
destinationDomain: params.destination.chain.cctp.domain,
|
|
19395
|
+
isTestnet: params.source.chain.isTestnet,
|
|
19396
|
+
amount: params.amount,
|
|
19397
|
+
mintRecipient,
|
|
19398
|
+
hookData,
|
|
19399
|
+
destinationCaller: ZERO_ADDRESS,
|
|
19400
|
+
feeToken: params.source.chain.usdcAddress,
|
|
19401
|
+
requests
|
|
19402
|
+
};
|
|
19403
|
+
}
|
|
19404
|
+
async function fetchBoundQuote(binding) {
|
|
19405
|
+
try {
|
|
19406
|
+
const quote = await fetchFeeQuote({
|
|
19407
|
+
sourceDomain: binding.sourceDomain,
|
|
19408
|
+
destinationDomain: binding.destinationDomain,
|
|
19409
|
+
amount: binding.amount,
|
|
19410
|
+
feeToken: binding.feeToken,
|
|
19411
|
+
requests: binding.requests,
|
|
19412
|
+
isTestnet: binding.isTestnet
|
|
19413
|
+
});
|
|
19414
|
+
if (quote.feeToken.toLowerCase() !== binding.feeToken.toLowerCase()) {
|
|
19415
|
+
throw createValidationFailedError$1('feeToken', quote.feeToken, 'Fee Service must return source-chain USDC for source-fee bridging');
|
|
19416
|
+
}
|
|
19417
|
+
return quote;
|
|
19418
|
+
} catch (error) {
|
|
19419
|
+
if (isRateLimitError(error)) {
|
|
19420
|
+
throw new KitError({
|
|
19421
|
+
...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
19422
|
+
recoverability: 'RETRYABLE',
|
|
19423
|
+
message: 'Fee Service rate limit exceeded. Retry with caller-managed exponential backoff; Bridge Kit does not retry signed quote requests automatically.',
|
|
19424
|
+
cause: {
|
|
19425
|
+
trace: error
|
|
19426
|
+
}
|
|
19427
|
+
});
|
|
19428
|
+
}
|
|
19429
|
+
throw error;
|
|
19430
|
+
}
|
|
19431
|
+
}
|
|
19432
|
+
async function fetchSubmissionQuote(binding) {
|
|
19433
|
+
let quote = await fetchBoundQuote(binding);
|
|
19434
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
19435
|
+
quote = await fetchBoundQuote(binding);
|
|
19436
|
+
}
|
|
19437
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
19438
|
+
throw createValidationFailedError$1('quote', undefined, 'Fee Service returned a quote too close to expiry for safe submission');
|
|
19439
|
+
}
|
|
19440
|
+
return quote;
|
|
19441
|
+
}
|
|
19442
|
+
async function validateBoundQuote(binding, signedQuote, refundAddress) {
|
|
19443
|
+
return validateQuote({
|
|
19444
|
+
sourceDomain: binding.sourceDomain,
|
|
19445
|
+
abiSignature: BURN_WITH_FEES_ABI_SIGNATURE,
|
|
19446
|
+
args: [
|
|
19447
|
+
binding.amount,
|
|
19448
|
+
String(binding.destinationDomain),
|
|
19449
|
+
padAddressToBytes32(binding.mintRecipient),
|
|
19450
|
+
binding.feeToken,
|
|
19451
|
+
padAddressToBytes32(binding.destinationCaller),
|
|
19452
|
+
binding.hookData,
|
|
19453
|
+
[
|
|
19454
|
+
signedQuote,
|
|
19455
|
+
refundAddress
|
|
19456
|
+
]
|
|
19457
|
+
],
|
|
19458
|
+
isTestnet: binding.isTestnet
|
|
19459
|
+
});
|
|
19460
|
+
}
|
|
19461
|
+
function isValidationSafe(binding, signedQuote, validation, expectedQuote) {
|
|
19462
|
+
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);
|
|
19463
|
+
}
|
|
19464
|
+
function unsafeQuoteError(validation) {
|
|
19465
|
+
const detail = validation.failedChecks.length > 0 ? ` (${validation.failedChecks.join(', ')})` : '';
|
|
19466
|
+
return createValidationFailedError$1('quote', undefined, `The fee quote is not safe for submission${detail}; ` + 'call estimate again to obtain a valid quote');
|
|
19467
|
+
}
|
|
19468
|
+
function toExecutionFeeQuote(quote) {
|
|
19469
|
+
return {
|
|
19470
|
+
signedQuote: quote.signedQuote,
|
|
19471
|
+
feeToken: quote.feeToken,
|
|
19472
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19473
|
+
};
|
|
19474
|
+
}
|
|
19475
|
+
function toFeeItems(quote) {
|
|
19476
|
+
return quote.items.map((item)=>({
|
|
19477
|
+
type: item.type,
|
|
19478
|
+
amount: formatUnits(item.amount, 6),
|
|
19479
|
+
args: item.args,
|
|
19480
|
+
argsHash: item.argsHash
|
|
19481
|
+
}));
|
|
19482
|
+
}
|
|
19483
|
+
/**
|
|
19484
|
+
* Estimate a source-fee bridge using a source-denominated signed fee quote.
|
|
19485
|
+
*
|
|
19486
|
+
* @internal
|
|
19487
|
+
*/ async function estimateSourceFeeBridge(params) {
|
|
19488
|
+
const binding = buildQuoteBinding(params);
|
|
19489
|
+
const quote = await fetchSubmissionQuote(binding);
|
|
19490
|
+
const feeTotal = formatUnits(quote.feeTotalAmount, 6);
|
|
19491
|
+
return {
|
|
19492
|
+
token: 'USDC',
|
|
19493
|
+
amount: formatUnits(params.amount, 6),
|
|
19494
|
+
source: {
|
|
19495
|
+
address: params.source.address,
|
|
19496
|
+
chain: params.source.chain.chain
|
|
19497
|
+
},
|
|
19498
|
+
destination: {
|
|
19499
|
+
address: params.destination.address,
|
|
19500
|
+
chain: params.destination.chain.chain,
|
|
19501
|
+
...params.destination.recipientAddress !== undefined && {
|
|
19502
|
+
recipientAddress: params.destination.recipientAddress
|
|
19503
|
+
}
|
|
19504
|
+
},
|
|
19505
|
+
gasFees: [],
|
|
19506
|
+
fees: quote.items.map((item)=>({
|
|
19507
|
+
type: item.type === 'FORWARD' ? 'forwarder' : 'provider',
|
|
19508
|
+
token: 'USDC',
|
|
19509
|
+
amount: formatUnits(item.amount, 6)
|
|
19510
|
+
})),
|
|
19511
|
+
amountReceived: formatUnits(params.amount, 6),
|
|
19512
|
+
feeTotal,
|
|
19513
|
+
feeItems: toFeeItems(quote),
|
|
19514
|
+
totalDebit: formatUnits((BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString(), 6),
|
|
19515
|
+
quoteExpiry: quote.expiry,
|
|
19516
|
+
quote: quote.signedQuote
|
|
19517
|
+
};
|
|
19518
|
+
}
|
|
19519
|
+
async function readAllowance(params, delegate) {
|
|
19520
|
+
const operationContext = {
|
|
19521
|
+
chain: params.source.chain,
|
|
19522
|
+
address: params.source.address
|
|
19523
|
+
};
|
|
19524
|
+
const prepared = await params.source.adapter.prepareAction('usdc.allowance', {
|
|
19525
|
+
walletAddress: params.source.address,
|
|
19526
|
+
delegate
|
|
19527
|
+
}, operationContext);
|
|
19528
|
+
return BigInt(String(await prepared.execute()));
|
|
19529
|
+
}
|
|
19530
|
+
async function executeAndConfirm(request, params, provider) {
|
|
19531
|
+
const txHash = await request.execute();
|
|
19532
|
+
const data = await provider.waitForTransaction(params.source.adapter, txHash, params.source.chain);
|
|
19533
|
+
return {
|
|
19534
|
+
txHash,
|
|
19535
|
+
data
|
|
19536
|
+
};
|
|
19537
|
+
}
|
|
19538
|
+
async function prepareAndPreflight(params, binding, quote, provider) {
|
|
19539
|
+
assertSourceFeeRoute(params);
|
|
19540
|
+
const totalDebit = (BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString();
|
|
19541
|
+
const operationContext = {
|
|
19542
|
+
chain: params.source.chain,
|
|
19543
|
+
address: params.source.address
|
|
19544
|
+
};
|
|
19545
|
+
await validateBalanceForTransaction({
|
|
19546
|
+
adapter: params.source.adapter,
|
|
19547
|
+
amount: totalDebit,
|
|
19548
|
+
token: 'USDC',
|
|
19549
|
+
tokenAddress: params.source.chain.usdcAddress,
|
|
19550
|
+
operationContext
|
|
19551
|
+
});
|
|
19552
|
+
const prepared = await provider.burnWithFees({
|
|
19553
|
+
source: params.source,
|
|
19554
|
+
destinationChain: params.destination.chain,
|
|
19555
|
+
amount: params.amount,
|
|
19556
|
+
mintRecipient: binding.mintRecipient,
|
|
19557
|
+
destinationCaller: binding.destinationCaller,
|
|
19558
|
+
hookData: binding.hookData,
|
|
19559
|
+
claim: {
|
|
19560
|
+
signedQuote: quote.signedQuote,
|
|
19561
|
+
refundAddress: params.source.address
|
|
19562
|
+
},
|
|
19563
|
+
feeToken: quote.feeToken,
|
|
19564
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19565
|
+
});
|
|
19566
|
+
const wrapper = resolveCCTPV2ContractAddress(params.source.chain, 'tokenMessengerWithFees');
|
|
19567
|
+
const allowance = await readAllowance(params, wrapper);
|
|
19568
|
+
if (allowance >= BigInt(totalDebit)) {
|
|
19569
|
+
return {
|
|
19570
|
+
prepared,
|
|
19571
|
+
approveStep: {
|
|
19572
|
+
name: 'approve',
|
|
19573
|
+
state: 'noop'
|
|
19574
|
+
}
|
|
16777
19575
|
};
|
|
16778
19576
|
}
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
* This method should block until the transaction is confirmed on the blockchain.
|
|
16783
|
-
*
|
|
16784
|
-
* @param adapter - The adapter to use for transaction waiting
|
|
16785
|
-
* @param txHash - The hash of the transaction to wait for
|
|
16786
|
-
* @param chain - The chain definition where the transaction was executed
|
|
16787
|
-
* @param config - Optional configuration for transaction waiting (confirmations, timeout)
|
|
16788
|
-
* @returns The hash of the confirmed transaction
|
|
16789
|
-
* @example
|
|
16790
|
-
* ```typescript
|
|
16791
|
-
* const provider = new CCTPV2BridgingProvider()
|
|
16792
|
-
* const txHash = await provider.waitForTransaction(
|
|
16793
|
-
* adapter,
|
|
16794
|
-
* '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
|
16795
|
-
* Ethereum,
|
|
16796
|
-
* )
|
|
16797
|
-
* console.log('Transaction confirmed:', txHash)
|
|
16798
|
-
* ```
|
|
16799
|
-
*/ async waitForTransaction(adapter, txHash, chain, config) {
|
|
16800
|
-
return adapter.waitForTransaction(txHash, config, chain);
|
|
19577
|
+
let lastApproval;
|
|
19578
|
+
for (const approval of prepared.approvals){
|
|
19579
|
+
lastApproval = await executeAndConfirm(approval, params, provider);
|
|
16801
19580
|
}
|
|
16802
|
-
}
|
|
16803
|
-
|
|
16804
|
-
/**
|
|
16805
|
-
* The default providers that will be used in addition to the providers provided
|
|
16806
|
-
* to the BridgeKit constructor.
|
|
16807
|
-
*
|
|
16808
|
-
* @param config - Optional configuration forwarded to the default providers
|
|
16809
|
-
* @returns The default bridging providers
|
|
16810
|
-
*/ const getDefaultProviders = (config = {})=>[
|
|
16811
|
-
new CCTPV2BridgingProvider(config.headers ? {
|
|
16812
|
-
headers: config.headers
|
|
16813
|
-
} : {})
|
|
16814
|
-
];
|
|
16815
|
-
|
|
16816
|
-
/**
|
|
16817
|
-
* A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
|
|
16818
|
-
* @param formatDirection - The direction to format the amount in.
|
|
16819
|
-
* @returns A function that transforms an amount into a human-readable string or a bigint string.
|
|
16820
|
-
*/ const getAmountTransformer = (formatDirection)=>formatDirection === 'to-human-readable' ? (params)=>formatAmount(params) : (params)=>parseAmount(params).toString();
|
|
16821
|
-
/**
|
|
16822
|
-
* Format the bridge result into human-readable string values for the user or bigint string values for internal use.
|
|
16823
|
-
*
|
|
16824
|
-
* @typeParam T - The specific result type (must extend BridgeResult or EstimateResult). Preserves the exact type passed in.
|
|
16825
|
-
* @param result - The bridge result to format.
|
|
16826
|
-
* @param formatDirection - The direction to format the result in.
|
|
16827
|
-
* - If 'to-human-readable', the result will be converted to human-readable string values.
|
|
16828
|
-
* - If 'to-internal', the result will be converted to bigint string values (usually for internal use).
|
|
16829
|
-
* @returns The formatted bridge result.
|
|
16830
|
-
*
|
|
16831
|
-
* @example
|
|
16832
|
-
* ```typescript
|
|
16833
|
-
* const result = await kit.bridge({
|
|
16834
|
-
* amount: '1000000',
|
|
16835
|
-
* token: 'USDC',
|
|
16836
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
16837
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
16838
|
-
* })
|
|
16839
|
-
*
|
|
16840
|
-
* // Format the bridge result into human-readable string values for the user
|
|
16841
|
-
* const formattedResultHumanReadable = formatBridgeResult(result, 'to-human-readable')
|
|
16842
|
-
* console.log(formattedResultHumanReadable)
|
|
16843
|
-
*
|
|
16844
|
-
* // Format the bridge result into bigint string values for internal use
|
|
16845
|
-
* const formattedResultInternal = formatBridgeResult(result, 'to-internal')
|
|
16846
|
-
* console.log(formattedResultInternal)
|
|
16847
|
-
* ```
|
|
16848
|
-
*/ const formatBridgeResult = (result, formatDirection)=>{
|
|
16849
|
-
const transform = getAmountTransformer(formatDirection);
|
|
16850
19581
|
return {
|
|
16851
|
-
|
|
16852
|
-
|
|
16853
|
-
|
|
16854
|
-
|
|
16855
|
-
|
|
16856
|
-
|
|
16857
|
-
|
|
16858
|
-
|
|
16859
|
-
...result.config.maxFee && {
|
|
16860
|
-
maxFee: transform({
|
|
16861
|
-
value: result.config.maxFee,
|
|
16862
|
-
token: result.token
|
|
16863
|
-
})
|
|
16864
|
-
},
|
|
16865
|
-
...result.config.customFee && {
|
|
16866
|
-
customFee: {
|
|
16867
|
-
...result.config.customFee,
|
|
16868
|
-
...result.config.customFee.value && {
|
|
16869
|
-
value: transform({
|
|
16870
|
-
value: result.config.customFee.value,
|
|
16871
|
-
token: result.token
|
|
16872
|
-
})
|
|
16873
|
-
}
|
|
16874
|
-
}
|
|
16875
|
-
}
|
|
19582
|
+
prepared,
|
|
19583
|
+
approveStep: {
|
|
19584
|
+
name: 'approve',
|
|
19585
|
+
state: 'success',
|
|
19586
|
+
data: lastApproval?.data,
|
|
19587
|
+
...lastApproval?.txHash !== undefined && {
|
|
19588
|
+
txHash: lastApproval.txHash,
|
|
19589
|
+
explorerUrl: buildExplorerUrl(params.source.chain, lastApproval.txHash)
|
|
16876
19590
|
}
|
|
16877
19591
|
}
|
|
16878
19592
|
};
|
|
16879
|
-
}
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
|
|
16894
|
-
|
|
16895
|
-
};
|
|
19593
|
+
}
|
|
19594
|
+
async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
19595
|
+
if (suppliedQuote === undefined) {
|
|
19596
|
+
const fetchedQuote = await fetchSubmissionQuote(binding);
|
|
19597
|
+
return {
|
|
19598
|
+
quote: toExecutionFeeQuote(fetchedQuote),
|
|
19599
|
+
fetchedQuote
|
|
19600
|
+
};
|
|
19601
|
+
}
|
|
19602
|
+
const validation = await validateBoundQuote(binding, suppliedQuote, refundAddress);
|
|
19603
|
+
if (!isValidationSafe(binding, suppliedQuote, validation)) {
|
|
19604
|
+
throw unsafeQuoteError(validation);
|
|
19605
|
+
}
|
|
19606
|
+
return {
|
|
19607
|
+
quote: toExecutionFeeQuote(validation),
|
|
19608
|
+
fetchedQuote: undefined
|
|
19609
|
+
};
|
|
19610
|
+
}
|
|
16896
19611
|
/**
|
|
16897
|
-
*
|
|
16898
|
-
*
|
|
16899
|
-
* @remarks
|
|
16900
|
-
* The order matches the CCTP v2 bridge execution sequence. During
|
|
16901
|
-
* `bridge()`, completed step events are counted so the failing step
|
|
16902
|
-
* can be identified by its index.
|
|
19612
|
+
* Execute a source-fee bridge while preserving receive-exact semantics.
|
|
16903
19613
|
*
|
|
16904
19614
|
* @internal
|
|
16905
|
-
*/
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
19615
|
+
*/ async function executeSourceFeeBridge(rawParams, params, provider) {
|
|
19616
|
+
// Narrows `params` to the CCTP v2 route type for the rest of this function.
|
|
19617
|
+
// buildQuoteBinding asserts too, but that narrows its own scope, not this one.
|
|
19618
|
+
assertSourceFeeRoute(params);
|
|
19619
|
+
const binding = buildQuoteBinding(params);
|
|
19620
|
+
const suppliedQuote = rawParams.quote;
|
|
19621
|
+
let { quote, fetchedQuote } = await resolveExecutionQuote(binding, suppliedQuote, params.source.address);
|
|
19622
|
+
let { prepared, approveStep } = await prepareAndPreflight(params, binding, quote, provider);
|
|
19623
|
+
// Validate against the current source-chain tip after approval confirmation.
|
|
19624
|
+
// BLOCK_NUMBER expiries cannot be checked safely with wall-clock time alone.
|
|
19625
|
+
let validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
19626
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
19627
|
+
if (suppliedQuote === undefined) {
|
|
19628
|
+
fetchedQuote = await fetchSubmissionQuote(binding);
|
|
19629
|
+
quote = toExecutionFeeQuote(fetchedQuote);
|
|
19630
|
+
const refreshedPreparation = await prepareAndPreflight(params, binding, quote, provider);
|
|
19631
|
+
prepared = refreshedPreparation.prepared;
|
|
19632
|
+
if (refreshedPreparation.approveStep.state !== 'noop') {
|
|
19633
|
+
approveStep = refreshedPreparation.approveStep;
|
|
19634
|
+
}
|
|
19635
|
+
validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
19636
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
19637
|
+
throw unsafeQuoteError(validation);
|
|
19638
|
+
}
|
|
19639
|
+
} else {
|
|
19640
|
+
throw unsafeQuoteError(validation);
|
|
19641
|
+
}
|
|
19642
|
+
}
|
|
19643
|
+
// Surface the same step events as the standard bridge() path so
|
|
19644
|
+
// kit.on('approve'|'burn'|'mint', ...) handlers fire for source-fee bridges.
|
|
19645
|
+
if (approveStep.state !== 'noop') {
|
|
19646
|
+
provider.emitBridgeStep('approve', approveStep);
|
|
19647
|
+
}
|
|
19648
|
+
const burn = await executeAndConfirm(prepared.burn, params, provider);
|
|
19649
|
+
const burnStep = {
|
|
19650
|
+
name: 'burn',
|
|
19651
|
+
state: 'success',
|
|
19652
|
+
txHash: burn.txHash,
|
|
19653
|
+
data: burn.data,
|
|
19654
|
+
explorerUrl: buildExplorerUrl(params.source.chain, burn.txHash)
|
|
19655
|
+
};
|
|
19656
|
+
provider.emitBridgeStep('burn', burnStep);
|
|
19657
|
+
const resultBase = {
|
|
19658
|
+
amount: params.amount,
|
|
19659
|
+
token: 'USDC',
|
|
19660
|
+
config: params.config,
|
|
19661
|
+
provider: provider.name,
|
|
19662
|
+
source: {
|
|
19663
|
+
address: params.source.address,
|
|
19664
|
+
chain: params.source.chain
|
|
19665
|
+
},
|
|
19666
|
+
destination: {
|
|
19667
|
+
address: params.destination.address,
|
|
19668
|
+
chain: params.destination.chain,
|
|
19669
|
+
...params.destination.recipientAddress !== undefined && {
|
|
19670
|
+
recipientAddress: params.destination.recipientAddress
|
|
19671
|
+
},
|
|
19672
|
+
useForwarder: true
|
|
19673
|
+
}
|
|
19674
|
+
};
|
|
19675
|
+
const attestation = await provider.fetchRelayerMint(params.source, burn.txHash);
|
|
19676
|
+
const forwardTxHash = attestation.forwardTxHash;
|
|
19677
|
+
// The burn already moved funds. If the relayer confirms without a
|
|
19678
|
+
// destination hash, surface an error-state result that preserves the burn
|
|
19679
|
+
// step (so `retry()` can resume the mint) instead of throwing and discarding
|
|
19680
|
+
// the completed burn.
|
|
19681
|
+
if (typeof forwardTxHash !== 'string' || forwardTxHash.trim() === '') {
|
|
19682
|
+
const mintStep = {
|
|
19683
|
+
name: 'mint',
|
|
19684
|
+
state: 'error',
|
|
19685
|
+
forwarded: true,
|
|
19686
|
+
errorCategory: 'failed_offchain',
|
|
19687
|
+
errorMessage: 'Relayer confirmation did not include a destination transaction hash'
|
|
19688
|
+
};
|
|
19689
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
19690
|
+
return {
|
|
19691
|
+
...resultBase,
|
|
19692
|
+
state: 'error',
|
|
19693
|
+
steps: [
|
|
19694
|
+
approveStep,
|
|
19695
|
+
burnStep,
|
|
19696
|
+
mintStep
|
|
19697
|
+
]
|
|
19698
|
+
};
|
|
19699
|
+
}
|
|
19700
|
+
const mintStep = {
|
|
19701
|
+
name: 'mint',
|
|
19702
|
+
state: 'success',
|
|
19703
|
+
forwarded: true,
|
|
19704
|
+
txHash: forwardTxHash,
|
|
19705
|
+
explorerUrl: buildExplorerUrl(params.destination.chain, forwardTxHash)
|
|
19706
|
+
};
|
|
19707
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
19708
|
+
return {
|
|
19709
|
+
...resultBase,
|
|
19710
|
+
state: 'success',
|
|
19711
|
+
steps: [
|
|
19712
|
+
approveStep,
|
|
19713
|
+
burnStep,
|
|
19714
|
+
mintStep
|
|
19715
|
+
]
|
|
19716
|
+
};
|
|
19717
|
+
}
|
|
16923
19718
|
|
|
16924
19719
|
/** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg$3.name);
|
|
16925
19720
|
/**
|
|
@@ -17148,11 +19943,18 @@ function assertCCTPV2Config(config) {
|
|
|
17148
19943
|
this.validateNetworkCompatibility(resolvedParams);
|
|
17149
19944
|
// Merge the custom fee config into the resolved params
|
|
17150
19945
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
//
|
|
17154
|
-
|
|
17155
|
-
|
|
19946
|
+
let result;
|
|
19947
|
+
// Execute the explicit source-fee path without changing legacy
|
|
19948
|
+
// useForwarder behavior for callers that did not opt in.
|
|
19949
|
+
if (params.config?.feePayment === 'source') {
|
|
19950
|
+
const sourceFeeProvider = this.findSourceFeeProvider(finalResolvedParams);
|
|
19951
|
+
result = formatBridgeResult(await executeSourceFeeBridge(params, finalResolvedParams, sourceFeeProvider), 'to-human-readable');
|
|
19952
|
+
} else {
|
|
19953
|
+
// Find a provider that supports this route
|
|
19954
|
+
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
19955
|
+
// Execute the transfer using the provider and format the result.
|
|
19956
|
+
result = formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
|
|
19957
|
+
}
|
|
17156
19958
|
// Emit error telemetry when the provider returns an error state
|
|
17157
19959
|
// (provider records step failures in the result instead of throwing).
|
|
17158
19960
|
if (result.state === 'error') {
|
|
@@ -17275,42 +20077,7 @@ function assertCCTPV2Config(config) {
|
|
|
17275
20077
|
tokenIn: result.token
|
|
17276
20078
|
});
|
|
17277
20079
|
}
|
|
17278
|
-
|
|
17279
|
-
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
17280
|
-
*
|
|
17281
|
-
* This method calculates the expected gas fees and protocol costs for bridging
|
|
17282
|
-
* without actually executing the transaction. It performs the same validation
|
|
17283
|
-
* as the bridge method but stops before execution.
|
|
17284
|
-
*
|
|
17285
|
-
* @param params - The bridge parameters for cost estimation, including optional invocation metadata
|
|
17286
|
-
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
17287
|
-
* @throws {KitError} When the parameters are invalid.
|
|
17288
|
-
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
17289
|
-
*
|
|
17290
|
-
* @example
|
|
17291
|
-
* ```typescript
|
|
17292
|
-
* // Basic usage
|
|
17293
|
-
* const estimate = await kit.estimate({
|
|
17294
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
17295
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
17296
|
-
* amount: '10.50',
|
|
17297
|
-
* token: 'USDC'
|
|
17298
|
-
* })
|
|
17299
|
-
* console.log('Estimated cost:', estimate.totalCost)
|
|
17300
|
-
*
|
|
17301
|
-
* // With custom invocation metadata
|
|
17302
|
-
* const estimate = await kit.estimate({
|
|
17303
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
17304
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
17305
|
-
* amount: '10.50',
|
|
17306
|
-
* token: 'USDC',
|
|
17307
|
-
* invocationMeta: {
|
|
17308
|
-
* traceId: 'custom-trace-id',
|
|
17309
|
-
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
17310
|
-
* },
|
|
17311
|
-
* })
|
|
17312
|
-
* ```
|
|
17313
|
-
*/ async estimate(params) {
|
|
20080
|
+
async estimate(params) {
|
|
17314
20081
|
return withErrorTelemetry(async ()=>{
|
|
17315
20082
|
// First validate the parameters
|
|
17316
20083
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
@@ -17320,6 +20087,10 @@ function assertCCTPV2Config(config) {
|
|
|
17320
20087
|
this.validateNetworkCompatibility(resolvedParams);
|
|
17321
20088
|
// Merge the custom fee config into the resolved params
|
|
17322
20089
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
20090
|
+
if (params.config?.feePayment === 'source') {
|
|
20091
|
+
this.findSourceFeeProvider(finalResolvedParams);
|
|
20092
|
+
return estimateSourceFeeBridge(finalResolvedParams);
|
|
20093
|
+
}
|
|
17323
20094
|
// Find a provider that supports this route
|
|
17324
20095
|
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
17325
20096
|
// Estimate the transfer using the provider and format amounts to human-readable strings
|
|
@@ -17368,6 +20139,9 @@ function assertCCTPV2Config(config) {
|
|
|
17368
20139
|
* // Get only chains that support forwarding
|
|
17369
20140
|
* const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
|
|
17370
20141
|
*
|
|
20142
|
+
* // Get only chains that can pay fees on the source chain (receive-exact)
|
|
20143
|
+
* const sourceFeeChains = kit.getSupportedChains({ sourceFeeSupported: true })
|
|
20144
|
+
*
|
|
17371
20145
|
* console.log('Supported chains:')
|
|
17372
20146
|
* allChains.forEach(chain => {
|
|
17373
20147
|
* console.log(`- ${chain.name} (${chain.type})`)
|
|
@@ -17416,6 +20190,10 @@ function assertCCTPV2Config(config) {
|
|
|
17416
20190
|
return options.forwarderSupported ? fs.source || fs.destination : !fs.source && !fs.destination;
|
|
17417
20191
|
});
|
|
17418
20192
|
}
|
|
20193
|
+
// Apply source-paid ("receive-exact") fee support filter if provided
|
|
20194
|
+
if (options?.sourceFeeSupported !== undefined) {
|
|
20195
|
+
chains = chains.filter((chain)=>hasSourceFeeSupport(chain) === options.sourceFeeSupported);
|
|
20196
|
+
}
|
|
17419
20197
|
return chains;
|
|
17420
20198
|
}
|
|
17421
20199
|
/**
|
|
@@ -17450,6 +20228,20 @@ function assertCCTPV2Config(config) {
|
|
|
17450
20228
|
return provider;
|
|
17451
20229
|
}
|
|
17452
20230
|
/**
|
|
20231
|
+
* Find the default CCTP v2 provider for a source-fee forwarding route.
|
|
20232
|
+
*
|
|
20233
|
+
* @param params - The resolved provider parameters.
|
|
20234
|
+
* @returns The CCTP v2 provider that supports the forwarded route.
|
|
20235
|
+
* @throws {UnsupportedRouteError} When no source-fee provider supports the route.
|
|
20236
|
+
* @internal
|
|
20237
|
+
*/ findSourceFeeProvider(params) {
|
|
20238
|
+
const provider = this.providers.find((candidate)=>candidate instanceof CCTPV2BridgingProvider && candidate.supportsRoute(params.source.chain, params.destination.chain, params.token, true));
|
|
20239
|
+
if (!(provider instanceof CCTPV2BridgingProvider)) {
|
|
20240
|
+
throw createUnsupportedRouteError(params.source.chain.name, params.destination.chain.name);
|
|
20241
|
+
}
|
|
20242
|
+
return provider;
|
|
20243
|
+
}
|
|
20244
|
+
/**
|
|
17453
20245
|
* Merge custom fee configuration into provider parameters.
|
|
17454
20246
|
*
|
|
17455
20247
|
* Prioritizes any custom fee configuration already present on the
|
|
@@ -17658,7 +20450,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
17658
20450
|
};
|
|
17659
20451
|
|
|
17660
20452
|
var name$1 = "@circle-fin/swap-kit";
|
|
17661
|
-
var version$1 = "1.
|
|
20453
|
+
var version$1 = "1.6.1";
|
|
17662
20454
|
var pkg$1 = {
|
|
17663
20455
|
name: name$1,
|
|
17664
20456
|
version: version$1};
|
|
@@ -17672,7 +20464,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17672
20464
|
* Catches obviously malformed addresses at parse time; chain-specific validation
|
|
17673
20465
|
* is performed in buildServiceParams.
|
|
17674
20466
|
*/ const destinationAddressSchema = zod.z.union([
|
|
17675
|
-
evmAddressSchema,
|
|
20467
|
+
evmAddressSchema$1,
|
|
17676
20468
|
solanaAddressSchema
|
|
17677
20469
|
]);
|
|
17678
20470
|
/**
|
|
@@ -17718,9 +20510,16 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17718
20510
|
message: 'stopLimit must be greater than 0'
|
|
17719
20511
|
}).optional(),
|
|
17720
20512
|
customFee: serviceSwapCustomFeeSchema.optional(),
|
|
20513
|
+
apiKey: zod.z.string({
|
|
20514
|
+
invalid_type_error: 'apiKey must be a string'
|
|
20515
|
+
})// Tolerate '' so the `process.env.CIRCLE_API_KEY ?? ''` idiom falls back to
|
|
20516
|
+
// kitKey via resolveApiKey instead of being rejected here.
|
|
20517
|
+
.optional(),
|
|
17721
20518
|
kitKey: zod.z.string({
|
|
17722
20519
|
invalid_type_error: 'kitKey must be a string'
|
|
17723
|
-
})
|
|
20520
|
+
})// Tolerate '' so an unset kit-key env var yields the permissionless path,
|
|
20521
|
+
// matching buildServiceParams (which already omits an empty credential).
|
|
20522
|
+
.optional(),
|
|
17724
20523
|
provider: zod.z.string({
|
|
17725
20524
|
invalid_type_error: 'provider must be a string'
|
|
17726
20525
|
}).min(1, 'provider must be a non-empty string').optional(),
|
|
@@ -17893,6 +20692,41 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17893
20692
|
*
|
|
17894
20693
|
* @internal
|
|
17895
20694
|
*/ const MAX_RATE_ADDRESSES_PER_REQUEST = 100;
|
|
20695
|
+
/**
|
|
20696
|
+
* Environment prefixes carried by Circle platform API keys.
|
|
20697
|
+
*
|
|
20698
|
+
* A Circle API key is `<ENV>_API_KEY:<keyId>:<keySecret>`, where `<ENV>` is one
|
|
20699
|
+
* of these prefixes.
|
|
20700
|
+
*
|
|
20701
|
+
* @internal
|
|
20702
|
+
*/ const API_KEY_ENV_PREFIXES = [
|
|
20703
|
+
'TEST',
|
|
20704
|
+
'LIVE',
|
|
20705
|
+
'SAND',
|
|
20706
|
+
'SANDBOX',
|
|
20707
|
+
'SMOK',
|
|
20708
|
+
'PROD',
|
|
20709
|
+
'STAG',
|
|
20710
|
+
'DEV'
|
|
20711
|
+
];
|
|
20712
|
+
/**
|
|
20713
|
+
* Accepted credential formats for Stablecoin Service authentication.
|
|
20714
|
+
*
|
|
20715
|
+
* Matches a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
20716
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`). API keys are the
|
|
20717
|
+
* recommended credential; kit keys remain accepted as the legacy path.
|
|
20718
|
+
*
|
|
20719
|
+
* @remarks
|
|
20720
|
+
* This is a local pre-flight check, not the authority — the Stablecoin Service
|
|
20721
|
+
* validates the credential and answers 401 when it rejects one. The prefix list
|
|
20722
|
+
* is therefore deliberately permissive: a valid key carrying a prefix this SDK
|
|
20723
|
+
* has not been taught about should reach the service and be judged there rather
|
|
20724
|
+
* than be refused locally, since refusing locally is indistinguishable from an
|
|
20725
|
+
* outage to the caller. Kept as the single source of truth so the pattern is
|
|
20726
|
+
* not restated per call site.
|
|
20727
|
+
*
|
|
20728
|
+
* @internal
|
|
20729
|
+
*/ const API_KEY_PATTERN = new RegExp(`^(?:KIT_KEY|(?:${API_KEY_ENV_PREFIXES.join('|')})_API_KEY)` + ':[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$');
|
|
17896
20730
|
|
|
17897
20731
|
/**
|
|
17898
20732
|
* Zod schema for validating stop limits.
|
|
@@ -17961,13 +20795,14 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17961
20795
|
/**
|
|
17962
20796
|
* Zod schema for validating API keys.
|
|
17963
20797
|
*
|
|
17964
|
-
*
|
|
20798
|
+
* Accepts a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
20799
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`).
|
|
17965
20800
|
*
|
|
17966
20801
|
* @example
|
|
17967
20802
|
* ```typescript
|
|
17968
20803
|
* import { apiKeySchema } from '@core/service-client'
|
|
17969
20804
|
*
|
|
17970
|
-
* const result = apiKeySchema.safeParse('
|
|
20805
|
+
* const result = apiKeySchema.safeParse('TEST_API_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
17971
20806
|
* if (!result.success) {
|
|
17972
20807
|
* console.error('Invalid API key format:', result.error.issues)
|
|
17973
20808
|
* }
|
|
@@ -17975,7 +20810,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
17975
20810
|
*/ const apiKeySchema = zod.z.string({
|
|
17976
20811
|
required_error: 'API key is required',
|
|
17977
20812
|
invalid_type_error: 'Invalid API key format'
|
|
17978
|
-
}).regex(
|
|
20813
|
+
}).regex(API_KEY_PATTERN, 'Invalid API key format');
|
|
17979
20814
|
/**
|
|
17980
20815
|
* Zod schema for platform fees configuration.
|
|
17981
20816
|
*
|
|
@@ -18414,6 +21249,40 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
18414
21249
|
transaction: createSwapTransactionSchema
|
|
18415
21250
|
});
|
|
18416
21251
|
|
|
21252
|
+
/**
|
|
21253
|
+
* Resolve the credential to authenticate a Stablecoin Service request with.
|
|
21254
|
+
*
|
|
21255
|
+
* `apiKey` is the supported field; `kitKey` is the deprecated alias kept for
|
|
21256
|
+
* existing integrations. When both are supplied `apiKey` wins, so a caller
|
|
21257
|
+
* migrating field-by-field cannot be silently pinned to a stale credential.
|
|
21258
|
+
*
|
|
21259
|
+
* An empty-string value is treated as absent on either field. Without this, the
|
|
21260
|
+
* common `process.env.CIRCLE_API_KEY ?? ''` idiom (which yields `''` when the
|
|
21261
|
+
* variable is unset) would either shadow a working `kitKey` or, on a bare
|
|
21262
|
+
* `kitKey: ''`, reach downstream validation as an invalid credential instead of
|
|
21263
|
+
* falling through to the permissionless path.
|
|
21264
|
+
*
|
|
21265
|
+
* @param source - Object carrying either credential field, or neither.
|
|
21266
|
+
* @returns The credential to use, or `undefined` for the permissionless
|
|
21267
|
+
* (keyless) path.
|
|
21268
|
+
*
|
|
21269
|
+
* @example
|
|
21270
|
+
* ```typescript
|
|
21271
|
+
* import { resolveApiKey } from '@core/service-client'
|
|
21272
|
+
*
|
|
21273
|
+
* resolveApiKey({ apiKey: 'TEST_API_KEY:id:secret' }) // 'TEST_API_KEY:id:secret'
|
|
21274
|
+
* resolveApiKey({ kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
21275
|
+
* resolveApiKey({ apiKey: '', kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
21276
|
+
* resolveApiKey({ kitKey: '' }) // undefined
|
|
21277
|
+
* resolveApiKey({}) // undefined
|
|
21278
|
+
* ```
|
|
21279
|
+
*/ const resolveApiKey = (source)=>{
|
|
21280
|
+
// Treat an empty-string value as absent on either field so the
|
|
21281
|
+
// `env ?? ''` idiom falls through to the next credential (or permissionless).
|
|
21282
|
+
const normalize = (value)=>value !== undefined && value !== '' ? value : undefined;
|
|
21283
|
+
return normalize(source.apiKey) ?? normalize(source.kitKey);
|
|
21284
|
+
};
|
|
21285
|
+
|
|
18417
21286
|
/**
|
|
18418
21287
|
* Zod schema for validating EVM adapter capabilities.
|
|
18419
21288
|
*
|
|
@@ -18843,7 +21712,7 @@ const abiParameterSchema = zod.z.object({
|
|
|
18843
21712
|
*/ zod.z.object({
|
|
18844
21713
|
type: zod.z.literal('evm'),
|
|
18845
21714
|
abi: abiSchema,
|
|
18846
|
-
address: evmAddressSchema,
|
|
21715
|
+
address: evmAddressSchema$1,
|
|
18847
21716
|
functionName: zod.z.string({
|
|
18848
21717
|
required_error: 'Function name is required',
|
|
18849
21718
|
invalid_type_error: 'Function name must be a string'
|
|
@@ -18880,7 +21749,7 @@ const abiParameterSchema = zod.z.object({
|
|
|
18880
21749
|
* }
|
|
18881
21750
|
* ```
|
|
18882
21751
|
*/ zod.z.object({
|
|
18883
|
-
address: evmAddressSchema,
|
|
21752
|
+
address: evmAddressSchema$1,
|
|
18884
21753
|
value: zod.z.bigint({
|
|
18885
21754
|
required_error: 'Value is required for native transfers',
|
|
18886
21755
|
invalid_type_error: 'Value must be a bigint'
|
|
@@ -18966,7 +21835,7 @@ zod.z.object({
|
|
|
18966
21835
|
signature: evmSignatureSchema,
|
|
18967
21836
|
tokenInputs: zod.z.array(zod.z.object({
|
|
18968
21837
|
permitType: zod.z.nativeEnum(PermitType),
|
|
18969
|
-
token: evmAddressSchema,
|
|
21838
|
+
token: evmAddressSchema$1,
|
|
18970
21839
|
amount: zod.z.bigint().refine((value)=>value >= 0n, {
|
|
18971
21840
|
message: 'amount must be a non-negative bigint'
|
|
18972
21841
|
}),
|
|
@@ -19386,7 +22255,7 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
19386
22255
|
/**
|
|
19387
22256
|
* Fee recipient address (required).
|
|
19388
22257
|
* Must be a valid EVM address or Solana address.
|
|
19389
|
-
*/ recipientAddress: zod.z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
22258
|
+
*/ recipientAddress: zod.z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
19390
22259
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
19391
22260
|
})
|
|
19392
22261
|
}).strict();
|
|
@@ -19398,7 +22267,8 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
19398
22267
|
* - slippageBps: Optional positive number for slippage tolerance
|
|
19399
22268
|
* - stopLimit: Optional decimal string for minimum output
|
|
19400
22269
|
* - customFee: Optional fee configuration
|
|
19401
|
-
* -
|
|
22270
|
+
* - apiKey: Optional credential string
|
|
22271
|
+
* - kitKey: Optional credential string (deprecated alias for apiKey)
|
|
19402
22272
|
*/ const swapConfigSchema = zod.z.object({
|
|
19403
22273
|
allowanceStrategy: allowanceStrategySchema.optional(),
|
|
19404
22274
|
slippageBps: zod.z.number().int().min(0).optional(),
|
|
@@ -19408,11 +22278,12 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
19408
22278
|
attributeName: 'stopLimit'
|
|
19409
22279
|
})(zod.z.string())).optional(),
|
|
19410
22280
|
customFee: swapCustomFeeSchema.optional(),
|
|
22281
|
+
apiKey: zod.z.string().optional(),
|
|
19411
22282
|
kitKey: zod.z.string().optional()
|
|
19412
22283
|
});
|
|
19413
22284
|
const swapDestinationSchema = zod.z.object({
|
|
19414
22285
|
chain: optionalSwapChainIdentifierField,
|
|
19415
|
-
recipientAddress: zod.z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
22286
|
+
recipientAddress: zod.z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
19416
22287
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
19417
22288
|
}).optional()
|
|
19418
22289
|
}).strict();
|
|
@@ -19601,7 +22472,7 @@ new Set(Object.values(Blockchain));
|
|
|
19601
22472
|
registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
19602
22473
|
|
|
19603
22474
|
var name = "@circle-fin/earn-kit";
|
|
19604
|
-
var version = "1.
|
|
22475
|
+
var version = "1.6.1";
|
|
19605
22476
|
var pkg = {
|
|
19606
22477
|
name: name,
|
|
19607
22478
|
version: version};
|
|
@@ -19673,7 +22544,7 @@ function isNonNegativeBigIntLike(value) {
|
|
|
19673
22544
|
}
|
|
19674
22545
|
}
|
|
19675
22546
|
const hexSignatureSchema = evmSignatureSchema;
|
|
19676
|
-
const hexAddressSchema = evmAddressSchema;
|
|
22547
|
+
const hexAddressSchema = evmAddressSchema$1;
|
|
19677
22548
|
// '0x' prefix + 32 bytes * 2 hex chars.
|
|
19678
22549
|
const BYTES32_HEX_LENGTH = 66;
|
|
19679
22550
|
const bridgeFeeTokenSchema = hexAddressSchema;
|
|
@@ -20578,7 +23449,7 @@ createTokenRegistry();
|
|
|
20578
23449
|
* fast instead of round-tripping to the service.
|
|
20579
23450
|
*
|
|
20580
23451
|
* @internal
|
|
20581
|
-
*/ 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');
|
|
23452
|
+
*/ 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');
|
|
20582
23453
|
/**
|
|
20583
23454
|
* Schema for the adapter context within earn operations.
|
|
20584
23455
|
*
|
|
@@ -20610,19 +23481,39 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
20610
23481
|
/**
|
|
20611
23482
|
* Schema for the EarnConfig options.
|
|
20612
23483
|
*
|
|
20613
|
-
* Validate the
|
|
20614
|
-
*
|
|
20615
|
-
*
|
|
20616
|
-
*
|
|
20617
|
-
*
|
|
20618
|
-
*
|
|
23484
|
+
* Validate the *resolved* credential using the standard `apiKeySchema` format
|
|
23485
|
+
* (`<ENV>_API_KEY:<keyId>:<keySecret>`, or a legacy
|
|
23486
|
+
* `KIT_KEY:<keyId>:<keySecret>`). `apiKey` takes precedence over the deprecated
|
|
23487
|
+
* `kitKey`, so a malformed `kitKey` that is being ignored must not fail a config
|
|
23488
|
+
* that supplies a valid `apiKey` (and vice versa) — only the credential that
|
|
23489
|
+
* would actually be sent is format-checked. When neither is supplied the SDK
|
|
23490
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service endpoint
|
|
23491
|
+
* (e.g. staging); `batchTransactions: false` opts out of atomic batched
|
|
23492
|
+
* execution. All are forwarded to the provider, so this `.strict()` schema must
|
|
23493
|
+
* accept them or a valid config object is rejected.
|
|
20619
23494
|
*
|
|
20620
23495
|
* @internal
|
|
20621
23496
|
*/ const earnConfigSchema = zod.z.object({
|
|
20622
|
-
|
|
23497
|
+
apiKey: zod.z.string().optional(),
|
|
23498
|
+
kitKey: zod.z.string().optional(),
|
|
20623
23499
|
baseUrl: zod.z.string().optional(),
|
|
20624
23500
|
batchTransactions: zod.z.boolean().optional()
|
|
20625
|
-
}).strict()
|
|
23501
|
+
}).strict().superRefine((config, ctx)=>{
|
|
23502
|
+
const credential = resolveApiKey(config);
|
|
23503
|
+
if (credential === undefined) {
|
|
23504
|
+
return;
|
|
23505
|
+
}
|
|
23506
|
+
const result = apiKeySchema.safeParse(credential);
|
|
23507
|
+
if (!result.success) {
|
|
23508
|
+
ctx.addIssue({
|
|
23509
|
+
code: zod.z.ZodIssueCode.custom,
|
|
23510
|
+
path: [
|
|
23511
|
+
credential === config.apiKey ? 'apiKey' : 'kitKey'
|
|
23512
|
+
],
|
|
23513
|
+
message: result.error.issues[0]?.message ?? 'Invalid API key format'
|
|
23514
|
+
});
|
|
23515
|
+
}
|
|
23516
|
+
});
|
|
20626
23517
|
/**
|
|
20627
23518
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
20628
23519
|
* '0' is only allowed immediately before the decimal point). Rejects the
|
|
@@ -20694,7 +23585,7 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
20694
23585
|
* currently supports EVM vault addresses on Arc Testnet.
|
|
20695
23586
|
*
|
|
20696
23587
|
* @internal
|
|
20697
|
-
*/ const vaultAddressSchema = evmAddressSchema.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
23588
|
+
*/ const vaultAddressSchema = evmAddressSchema$1.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
20698
23589
|
/**
|
|
20699
23590
|
* Validation schema for VaultQuery.
|
|
20700
23591
|
*
|
|
@@ -20969,7 +23860,7 @@ const sameChainGetDepositQuoteParamsSchema = zod.z.object({
|
|
|
20969
23860
|
const crossChainGetDepositQuoteParamsSchema = zod.z.object({
|
|
20970
23861
|
from: sourceAdapterContextSchema,
|
|
20971
23862
|
chain: earnBridgeDestinationChainIdentifierSchema,
|
|
20972
|
-
address: evmAddressSchema,
|
|
23863
|
+
address: evmAddressSchema$1,
|
|
20973
23864
|
vaultAddress: vaultAddressSchema,
|
|
20974
23865
|
amount: amountSchema,
|
|
20975
23866
|
transferSpeed: zod.z.enum([
|