@circle-fin/bridge-kit 1.1.1 → 1.2.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 +37 -0
- package/QUICKSTART.md +144 -28
- package/README.md +127 -11
- package/chains.cjs +135 -2
- package/chains.d.ts +92 -3
- package/chains.mjs +136 -3
- package/index.cjs +2121 -1481
- package/index.d.ts +407 -108
- package/index.mjs +2123 -1484
- package/package.json +2 -2
package/index.mjs
CHANGED
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { z } from 'zod';
|
|
20
|
-
import { parseUnits as parseUnits$1 } from '@ethersproject/units';
|
|
21
20
|
import '@ethersproject/bytes';
|
|
22
21
|
import '@ethersproject/address';
|
|
23
22
|
import 'bs58';
|
|
23
|
+
import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ethersproject/units';
|
|
24
24
|
import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2';
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -64,202 +64,963 @@ const registerKit = (kitIdentifier) => {
|
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
// -----------------------------------------------------------------------------
|
|
68
|
-
// Blockchain Enum
|
|
69
|
-
// -----------------------------------------------------------------------------
|
|
70
67
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
68
|
+
* Valid recoverability values for error handling strategies.
|
|
69
|
+
*
|
|
70
|
+
* - FATAL errors are thrown immediately (invalid inputs, insufficient funds)
|
|
71
|
+
* - RETRYABLE errors are returned when a flow fails to start but could work later
|
|
72
|
+
* - RESUMABLE errors are returned when a flow fails mid-execution but can be continued
|
|
75
73
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
|
|
82
|
-
Blockchain["Arc_Testnet"] = "Arc_Testnet";
|
|
83
|
-
Blockchain["Arbitrum"] = "Arbitrum";
|
|
84
|
-
Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
85
|
-
Blockchain["Avalanche"] = "Avalanche";
|
|
86
|
-
Blockchain["Avalanche_Fuji"] = "Avalanche_Fuji";
|
|
87
|
-
Blockchain["Base"] = "Base";
|
|
88
|
-
Blockchain["Base_Sepolia"] = "Base_Sepolia";
|
|
89
|
-
Blockchain["Celo"] = "Celo";
|
|
90
|
-
Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
|
|
91
|
-
Blockchain["Codex"] = "Codex";
|
|
92
|
-
Blockchain["Codex_Testnet"] = "Codex_Testnet";
|
|
93
|
-
Blockchain["Ethereum"] = "Ethereum";
|
|
94
|
-
Blockchain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
|
|
95
|
-
Blockchain["Hedera"] = "Hedera";
|
|
96
|
-
Blockchain["Hedera_Testnet"] = "Hedera_Testnet";
|
|
97
|
-
Blockchain["HyperEVM"] = "HyperEVM";
|
|
98
|
-
Blockchain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
99
|
-
Blockchain["Ink"] = "Ink";
|
|
100
|
-
Blockchain["Ink_Testnet"] = "Ink_Testnet";
|
|
101
|
-
Blockchain["Linea"] = "Linea";
|
|
102
|
-
Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
103
|
-
Blockchain["NEAR"] = "NEAR";
|
|
104
|
-
Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
|
|
105
|
-
Blockchain["Noble"] = "Noble";
|
|
106
|
-
Blockchain["Noble_Testnet"] = "Noble_Testnet";
|
|
107
|
-
Blockchain["Optimism"] = "Optimism";
|
|
108
|
-
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
109
|
-
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
110
|
-
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
111
|
-
Blockchain["Plume"] = "Plume";
|
|
112
|
-
Blockchain["Plume_Testnet"] = "Plume_Testnet";
|
|
113
|
-
Blockchain["Polygon"] = "Polygon";
|
|
114
|
-
Blockchain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
115
|
-
Blockchain["Sei"] = "Sei";
|
|
116
|
-
Blockchain["Sei_Testnet"] = "Sei_Testnet";
|
|
117
|
-
Blockchain["Solana"] = "Solana";
|
|
118
|
-
Blockchain["Solana_Devnet"] = "Solana_Devnet";
|
|
119
|
-
Blockchain["Sonic"] = "Sonic";
|
|
120
|
-
Blockchain["Sonic_Testnet"] = "Sonic_Testnet";
|
|
121
|
-
Blockchain["Stellar"] = "Stellar";
|
|
122
|
-
Blockchain["Stellar_Testnet"] = "Stellar_Testnet";
|
|
123
|
-
Blockchain["Sui"] = "Sui";
|
|
124
|
-
Blockchain["Sui_Testnet"] = "Sui_Testnet";
|
|
125
|
-
Blockchain["Unichain"] = "Unichain";
|
|
126
|
-
Blockchain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
127
|
-
Blockchain["World_Chain"] = "World_Chain";
|
|
128
|
-
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
129
|
-
Blockchain["XDC"] = "XDC";
|
|
130
|
-
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
131
|
-
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
132
|
-
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
133
|
-
})(Blockchain || (Blockchain = {}));
|
|
134
|
-
|
|
74
|
+
const RECOVERABILITY_VALUES = [
|
|
75
|
+
'RETRYABLE',
|
|
76
|
+
'RESUMABLE',
|
|
77
|
+
'FATAL',
|
|
78
|
+
];
|
|
135
79
|
/**
|
|
136
|
-
*
|
|
80
|
+
* Error type constants for categorizing errors by origin.
|
|
137
81
|
*
|
|
138
|
-
* This
|
|
139
|
-
*
|
|
140
|
-
*
|
|
82
|
+
* This const object provides a reference for error types, enabling
|
|
83
|
+
* IDE autocomplete and preventing typos when creating custom errors.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* While internal error definitions use string literals with type annotations
|
|
87
|
+
* for strict type safety, this constant is useful for developers creating
|
|
88
|
+
* custom error instances or checking error types programmatically.
|
|
141
89
|
*
|
|
142
|
-
* When used with `as const`, it allows TypeScript to infer the most specific
|
|
143
|
-
* possible types for all properties, including string literals and numeric literals,
|
|
144
|
-
* rather than widening them to general types like string or number.
|
|
145
|
-
* @typeParam T - The specific chain definition type (must extend ChainDefinition)
|
|
146
|
-
* @param chain - The chain definition object, typically with an `as const` assertion
|
|
147
|
-
* @returns The same chain definition with preserved literal types
|
|
148
90
|
* @example
|
|
149
91
|
* ```typescript
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* }
|
|
92
|
+
* import { ERROR_TYPES, KitError } from '@core/errors'
|
|
93
|
+
*
|
|
94
|
+
* // Use for type checking
|
|
95
|
+
* if (error.type === ERROR_TYPES.BALANCE) {
|
|
96
|
+
* console.log('This is a balance error')
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // Use as reference when creating custom errors
|
|
103
|
+
* const error = new KitError({
|
|
104
|
+
* code: 9999,
|
|
105
|
+
* name: 'CUSTOM_ERROR',
|
|
106
|
+
* type: ERROR_TYPES.BALANCE, // IDE autocomplete works here
|
|
107
|
+
* recoverability: 'FATAL',
|
|
108
|
+
* message: 'Custom balance error'
|
|
109
|
+
* })
|
|
168
110
|
* ```
|
|
169
111
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
112
|
+
const ERROR_TYPES = {
|
|
113
|
+
/** User input validation and parameter checking */
|
|
114
|
+
INPUT: 'INPUT',
|
|
115
|
+
/** Insufficient token balances and amount validation */
|
|
116
|
+
BALANCE: 'BALANCE',
|
|
117
|
+
/** On-chain execution: reverts, gas issues, transaction failures */
|
|
118
|
+
ONCHAIN: 'ONCHAIN',
|
|
119
|
+
/** Blockchain RPC provider issues and endpoint problems */
|
|
120
|
+
RPC: 'RPC',
|
|
121
|
+
/** Internet connectivity, DNS resolution, connection issues */
|
|
122
|
+
NETWORK: 'NETWORK',
|
|
123
|
+
};
|
|
174
124
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* This represents the official production network for the Algorand blockchain.
|
|
125
|
+
* Array of valid error type values for validation.
|
|
126
|
+
* Derived from ERROR_TYPES const object.
|
|
178
127
|
*/
|
|
179
|
-
const
|
|
180
|
-
type: 'algorand',
|
|
181
|
-
chain: Blockchain.Algorand,
|
|
182
|
-
name: 'Algorand',
|
|
183
|
-
title: 'Algorand Mainnet',
|
|
184
|
-
nativeCurrency: {
|
|
185
|
-
name: 'Algo',
|
|
186
|
-
symbol: 'ALGO',
|
|
187
|
-
decimals: 6,
|
|
188
|
-
},
|
|
189
|
-
isTestnet: false,
|
|
190
|
-
explorerUrl: 'https://explorer.perawallet.app/tx/{hash}',
|
|
191
|
-
rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
|
|
192
|
-
eurcAddress: null,
|
|
193
|
-
usdcAddress: '31566704',
|
|
194
|
-
cctp: null,
|
|
195
|
-
});
|
|
128
|
+
const ERROR_TYPE_VALUES = Object.values(ERROR_TYPES);
|
|
196
129
|
|
|
130
|
+
// Create mutable arrays for Zod enum validation
|
|
131
|
+
const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
|
|
132
|
+
const ERROR_TYPE_ARRAY = [...ERROR_TYPE_VALUES];
|
|
197
133
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* This represents the official testnet for the Algorand blockchain.
|
|
134
|
+
* Error code ranges for validation.
|
|
135
|
+
* Single source of truth for valid error code ranges.
|
|
201
136
|
*/
|
|
202
|
-
const
|
|
203
|
-
type: '
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
symbol: 'ALGO',
|
|
210
|
-
decimals: 6,
|
|
211
|
-
},
|
|
212
|
-
isTestnet: true,
|
|
213
|
-
explorerUrl: 'https://testnet.explorer.perawallet.app/tx/{hash}',
|
|
214
|
-
rpcEndpoints: ['https://testnet-api.algonode.cloud'],
|
|
215
|
-
eurcAddress: null,
|
|
216
|
-
usdcAddress: '10458941',
|
|
217
|
-
cctp: null,
|
|
218
|
-
});
|
|
219
|
-
|
|
137
|
+
const ERROR_CODE_RANGES = [
|
|
138
|
+
{ min: 1000, max: 1999, type: 'INPUT' },
|
|
139
|
+
{ min: 3000, max: 3999, type: 'NETWORK' },
|
|
140
|
+
{ min: 4000, max: 4999, type: 'RPC' },
|
|
141
|
+
{ min: 5000, max: 5999, type: 'ONCHAIN' },
|
|
142
|
+
{ min: 9000, max: 9999, type: 'BALANCE' },
|
|
143
|
+
];
|
|
220
144
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* This
|
|
145
|
+
* Zod schema for validating ErrorDetails objects.
|
|
146
|
+
*
|
|
147
|
+
* This schema provides runtime validation for all ErrorDetails properties,
|
|
148
|
+
* ensuring type safety and proper error handling for JavaScript consumers.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* import { errorDetailsSchema } from '@core/errors'
|
|
153
|
+
*
|
|
154
|
+
* const result = errorDetailsSchema.safeParse({
|
|
155
|
+
* code: 1001,
|
|
156
|
+
* name: 'INPUT_NETWORK_MISMATCH',
|
|
157
|
+
* type: 'INPUT',
|
|
158
|
+
* recoverability: 'FATAL',
|
|
159
|
+
* message: 'Source and destination networks must be different'
|
|
160
|
+
* })
|
|
161
|
+
*
|
|
162
|
+
* if (!result.success) {
|
|
163
|
+
* console.error('Validation failed:', result.error.issues)
|
|
164
|
+
* }
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* // Runtime error
|
|
170
|
+
* const result = errorDetailsSchema.safeParse({
|
|
171
|
+
* code: 9001,
|
|
172
|
+
* name: 'BALANCE_INSUFFICIENT_TOKEN',
|
|
173
|
+
* type: 'BALANCE',
|
|
174
|
+
* recoverability: 'FATAL',
|
|
175
|
+
* message: 'Insufficient USDC balance'
|
|
176
|
+
* })
|
|
177
|
+
* ```
|
|
224
178
|
*/
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
},
|
|
250
|
-
},
|
|
179
|
+
const errorDetailsSchema = z.object({
|
|
180
|
+
/**
|
|
181
|
+
* Numeric identifier following standardized ranges:
|
|
182
|
+
* - 1000-1999: INPUT errors - Parameter validation
|
|
183
|
+
* - 3000-3999: NETWORK errors - Connectivity issues
|
|
184
|
+
* - 4000-4999: RPC errors - Provider issues, gas estimation
|
|
185
|
+
* - 5000-5999: ONCHAIN errors - Transaction/simulation failures
|
|
186
|
+
* - 9000-9999: BALANCE errors - Insufficient funds
|
|
187
|
+
*/
|
|
188
|
+
code: z
|
|
189
|
+
.number()
|
|
190
|
+
.int('Error code must be an integer')
|
|
191
|
+
.refine((code) => ERROR_CODE_RANGES.some((range) => code >= range.min && code <= range.max), {
|
|
192
|
+
message: 'Error code must be in valid ranges: 1000-1999 (INPUT), 3000-3999 (NETWORK), 4000-4999 (RPC), 5000-5999 (ONCHAIN), 9000-9999 (BALANCE)',
|
|
193
|
+
}),
|
|
194
|
+
/** Human-readable ID (e.g., "INPUT_NETWORK_MISMATCH", "BALANCE_INSUFFICIENT_TOKEN") */
|
|
195
|
+
name: z
|
|
196
|
+
.string()
|
|
197
|
+
.min(1, 'Error name must be a non-empty string')
|
|
198
|
+
.regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
|
|
199
|
+
/** Error category indicating where the error originated */
|
|
200
|
+
type: z.enum(ERROR_TYPE_ARRAY, {
|
|
201
|
+
errorMap: () => ({
|
|
202
|
+
message: 'Error type must be one of: INPUT, BALANCE, ONCHAIN, RPC, NETWORK',
|
|
203
|
+
}),
|
|
204
|
+
}),
|
|
205
|
+
/** Error handling strategy */
|
|
206
|
+
recoverability: z.enum(RECOVERABILITY_ARRAY, {
|
|
207
|
+
errorMap: () => ({
|
|
208
|
+
message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
|
|
209
|
+
}),
|
|
210
|
+
}),
|
|
211
|
+
/** User-friendly explanation with context */
|
|
212
|
+
message: z
|
|
213
|
+
.string()
|
|
214
|
+
.min(1, 'Error message must be a non-empty string')
|
|
215
|
+
.max(1000, 'Error message must be 1000 characters or less'),
|
|
216
|
+
/** Raw error details, context, or the original error that caused this one. */
|
|
217
|
+
cause: z
|
|
218
|
+
.object({
|
|
219
|
+
/** Free-form error payload from underlying system */
|
|
220
|
+
trace: z.unknown().optional(),
|
|
221
|
+
})
|
|
222
|
+
.optional(),
|
|
251
223
|
});
|
|
252
224
|
|
|
253
225
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
226
|
+
* Validates an ErrorDetails object using Zod schema.
|
|
227
|
+
*
|
|
228
|
+
* @param details - The object to validate
|
|
229
|
+
* @returns The validated ErrorDetails object
|
|
230
|
+
* @throws TypeError When validation fails
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* import { validateErrorDetails } from '@core/errors'
|
|
235
|
+
*
|
|
236
|
+
* try {
|
|
237
|
+
* const validDetails = validateErrorDetails({
|
|
238
|
+
* code: 1001,
|
|
239
|
+
* name: 'NETWORK_MISMATCH',
|
|
240
|
+
* recoverability: 'FATAL',
|
|
241
|
+
* message: 'Source and destination networks must be different'
|
|
242
|
+
* })
|
|
243
|
+
* } catch (error) {
|
|
244
|
+
* console.error('Validation failed:', error.message)
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
function validateErrorDetails(details) {
|
|
249
|
+
const result = errorDetailsSchema.safeParse(details);
|
|
250
|
+
if (!result.success) {
|
|
251
|
+
const issues = result.error.issues
|
|
252
|
+
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
253
|
+
.join(', ');
|
|
254
|
+
throw new TypeError(`Invalid ErrorDetails: ${issues}`);
|
|
255
|
+
}
|
|
256
|
+
return result.data;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Maximum length for error messages in fallback validation errors.
|
|
261
|
+
*
|
|
262
|
+
* KitError enforces a 1000-character limit on error messages. When creating
|
|
263
|
+
* fallback validation errors that combine multiple Zod issues, we use 950
|
|
264
|
+
* characters to leave a 50-character buffer for:
|
|
265
|
+
* - The error message prefix ("Invalid bridge parameters: ")
|
|
266
|
+
* - Potential encoding differences or formatting overhead
|
|
267
|
+
* - Safety margin to prevent KitError constructor failures
|
|
268
|
+
*
|
|
269
|
+
* This ensures that even with concatenated issue summaries, the final message
|
|
270
|
+
* stays within KitError's constraints.
|
|
271
|
+
*/
|
|
272
|
+
const MAX_MESSAGE_LENGTH = 950;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Structured error class for Stablecoin Kit operations.
|
|
276
|
+
*
|
|
277
|
+
* This class extends the native Error class while implementing the ErrorDetails
|
|
278
|
+
* interface, providing a consistent error format for programmatic handling
|
|
279
|
+
* across the Stablecoin Kits ecosystem. All properties are immutable to ensure
|
|
280
|
+
* error objects cannot be modified after creation.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```typescript
|
|
284
|
+
* import { KitError } from '@core/errors'
|
|
285
|
+
*
|
|
286
|
+
* const error = new KitError({
|
|
287
|
+
* code: 1001,
|
|
288
|
+
* name: 'INPUT_NETWORK_MISMATCH',
|
|
289
|
+
* recoverability: 'FATAL',
|
|
290
|
+
* message: 'Cannot bridge between mainnet and testnet'
|
|
291
|
+
* })
|
|
292
|
+
*
|
|
293
|
+
* if (error instanceof KitError) {
|
|
294
|
+
* console.log(`Error ${error.code}: ${error.name}`)
|
|
295
|
+
* // → "Error 1001: INPUT_NETWORK_MISMATCH"
|
|
296
|
+
* }
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* import { KitError } from '@core/errors'
|
|
302
|
+
*
|
|
303
|
+
* // Error with cause information
|
|
304
|
+
* const error = new KitError({
|
|
305
|
+
* code: 1002,
|
|
306
|
+
* name: 'INVALID_AMOUNT',
|
|
307
|
+
* recoverability: 'FATAL',
|
|
308
|
+
* message: 'Amount must be greater than zero',
|
|
309
|
+
* cause: {
|
|
310
|
+
* trace: { providedAmount: -100, minimumAmount: 0 }
|
|
311
|
+
* }
|
|
312
|
+
* })
|
|
313
|
+
*
|
|
314
|
+
* throw error
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
class KitError extends Error {
|
|
318
|
+
/** Numeric identifier following standardized ranges (1000+ for INPUT errors) */
|
|
319
|
+
code;
|
|
320
|
+
/** Human-readable ID (e.g., "NETWORK_MISMATCH") */
|
|
321
|
+
name;
|
|
322
|
+
/** Error category indicating where the error originated */
|
|
323
|
+
type;
|
|
324
|
+
/** Error handling strategy */
|
|
325
|
+
recoverability;
|
|
326
|
+
/** Raw error details, context, or the original error that caused this one. */
|
|
327
|
+
cause;
|
|
328
|
+
/**
|
|
329
|
+
* Create a new KitError instance.
|
|
330
|
+
*
|
|
331
|
+
* @param details - The error details object containing all required properties.
|
|
332
|
+
* @throws \{TypeError\} When details parameter is missing or invalid.
|
|
333
|
+
*/
|
|
334
|
+
constructor(details) {
|
|
335
|
+
// Truncate message if it exceeds maximum length to prevent validation errors
|
|
336
|
+
let message = details.message;
|
|
337
|
+
if (message.length > MAX_MESSAGE_LENGTH) {
|
|
338
|
+
message = `${message.slice(0, MAX_MESSAGE_LENGTH - 3)}...`;
|
|
339
|
+
}
|
|
340
|
+
const truncatedDetails = { ...details, message };
|
|
341
|
+
// Validate input at runtime for JavaScript consumers using Zod
|
|
342
|
+
const validatedDetails = validateErrorDetails(truncatedDetails);
|
|
343
|
+
super(validatedDetails.message);
|
|
344
|
+
// Set properties as readonly at runtime
|
|
345
|
+
Object.defineProperties(this, {
|
|
346
|
+
name: {
|
|
347
|
+
value: validatedDetails.name,
|
|
348
|
+
writable: false,
|
|
349
|
+
enumerable: true,
|
|
350
|
+
configurable: false,
|
|
351
|
+
},
|
|
352
|
+
code: {
|
|
353
|
+
value: validatedDetails.code,
|
|
354
|
+
writable: false,
|
|
355
|
+
enumerable: true,
|
|
356
|
+
configurable: false,
|
|
357
|
+
},
|
|
358
|
+
type: {
|
|
359
|
+
value: validatedDetails.type,
|
|
360
|
+
writable: false,
|
|
361
|
+
enumerable: true,
|
|
362
|
+
configurable: false,
|
|
363
|
+
},
|
|
364
|
+
recoverability: {
|
|
365
|
+
value: validatedDetails.recoverability,
|
|
366
|
+
writable: false,
|
|
367
|
+
enumerable: true,
|
|
368
|
+
configurable: false,
|
|
369
|
+
},
|
|
370
|
+
...(validatedDetails.cause && {
|
|
371
|
+
cause: {
|
|
372
|
+
value: validatedDetails.cause,
|
|
373
|
+
writable: false,
|
|
374
|
+
enumerable: true,
|
|
375
|
+
configurable: false,
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Standardized error code ranges for consistent categorization:
|
|
384
|
+
*
|
|
385
|
+
* - 1000-1999: INPUT errors - Parameter validation, input format errors
|
|
386
|
+
* - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
|
|
387
|
+
* - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
|
|
388
|
+
* - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
|
|
389
|
+
* - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
|
|
390
|
+
*/
|
|
391
|
+
/**
|
|
392
|
+
* Standardized error definitions for INPUT type errors.
|
|
393
|
+
*
|
|
394
|
+
* Each entry combines the numeric error code, string name, and type
|
|
395
|
+
* to ensure consistency when creating error instances.
|
|
396
|
+
*
|
|
397
|
+
* Error codes follow a hierarchical numbering scheme where the first digit
|
|
398
|
+
* indicates the error category (1 = INPUT) and subsequent digits provide
|
|
399
|
+
* specific error identification within that category.
|
|
400
|
+
*
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```typescript
|
|
404
|
+
* import { InputError } from '@core/errors'
|
|
405
|
+
*
|
|
406
|
+
* const error = new KitError({
|
|
407
|
+
* ...InputError.NETWORK_MISMATCH,
|
|
408
|
+
* recoverability: 'FATAL',
|
|
409
|
+
* message: 'Source and destination networks must be different'
|
|
410
|
+
* })
|
|
411
|
+
*
|
|
412
|
+
* // Access code, name, and type individually if needed
|
|
413
|
+
* console.log(InputError.NETWORK_MISMATCH.code) // 1001
|
|
414
|
+
* console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
|
|
415
|
+
* console.log(InputError.NETWORK_MISMATCH.type) // 'INPUT'
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
const InputError = {
|
|
419
|
+
/** Network type mismatch between chains (mainnet vs testnet) */
|
|
420
|
+
NETWORK_MISMATCH: {
|
|
421
|
+
code: 1001,
|
|
422
|
+
name: 'INPUT_NETWORK_MISMATCH',
|
|
423
|
+
type: 'INPUT',
|
|
424
|
+
},
|
|
425
|
+
/** Invalid amount format or value (negative, zero, or malformed) */
|
|
426
|
+
INVALID_AMOUNT: {
|
|
427
|
+
code: 1002,
|
|
428
|
+
name: 'INPUT_INVALID_AMOUNT',
|
|
429
|
+
type: 'INPUT',
|
|
430
|
+
},
|
|
431
|
+
/** Unsupported or invalid bridge route configuration */
|
|
432
|
+
UNSUPPORTED_ROUTE: {
|
|
433
|
+
code: 1003,
|
|
434
|
+
name: 'INPUT_UNSUPPORTED_ROUTE',
|
|
435
|
+
type: 'INPUT',
|
|
436
|
+
},
|
|
437
|
+
/** Invalid wallet or contract address format */
|
|
438
|
+
INVALID_ADDRESS: {
|
|
439
|
+
code: 1004,
|
|
440
|
+
name: 'INPUT_INVALID_ADDRESS',
|
|
441
|
+
type: 'INPUT',
|
|
442
|
+
},
|
|
443
|
+
/** Invalid or unsupported chain identifier */
|
|
444
|
+
INVALID_CHAIN: {
|
|
445
|
+
code: 1005,
|
|
446
|
+
name: 'INPUT_INVALID_CHAIN',
|
|
447
|
+
type: 'INPUT',
|
|
448
|
+
},
|
|
449
|
+
/** General validation failure for complex validation rules */
|
|
450
|
+
VALIDATION_FAILED: {
|
|
451
|
+
code: 1098,
|
|
452
|
+
name: 'INPUT_VALIDATION_FAILED',
|
|
453
|
+
type: 'INPUT',
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Creates error for network type mismatch between source and destination.
|
|
459
|
+
*
|
|
460
|
+
* This error is thrown when attempting to bridge between chains that have
|
|
461
|
+
* different network types (e.g., mainnet to testnet), which is not supported
|
|
462
|
+
* for security reasons.
|
|
463
|
+
*
|
|
464
|
+
* @param sourceChain - The source chain definition
|
|
465
|
+
* @param destChain - The destination chain definition
|
|
466
|
+
* @returns KitError with specific network mismatch details
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```typescript
|
|
470
|
+
* import { createNetworkMismatchError } from '@core/errors'
|
|
471
|
+
* import { Ethereum, BaseSepolia } from '@core/chains'
|
|
472
|
+
*
|
|
473
|
+
* // This will throw a detailed error
|
|
474
|
+
* throw createNetworkMismatchError(Ethereum, BaseSepolia)
|
|
475
|
+
* // Message: "Cannot bridge between Ethereum (mainnet) and Base Sepolia (testnet). Source and destination networks must both be testnet or both be mainnet."
|
|
476
|
+
* ```
|
|
477
|
+
*/
|
|
478
|
+
function createNetworkMismatchError(sourceChain, destChain) {
|
|
479
|
+
const sourceNetworkType = sourceChain.isTestnet ? 'testnet' : 'mainnet';
|
|
480
|
+
const destNetworkType = destChain.isTestnet ? 'testnet' : 'mainnet';
|
|
481
|
+
const errorDetails = {
|
|
482
|
+
...InputError.NETWORK_MISMATCH,
|
|
483
|
+
recoverability: 'FATAL',
|
|
484
|
+
message: `Cannot bridge between ${sourceChain.name} (${sourceNetworkType}) and ${destChain.name} (${destNetworkType}). Source and destination networks must both be testnet or both be mainnet.`,
|
|
485
|
+
cause: {
|
|
486
|
+
trace: { sourceChain: sourceChain.name, destChain: destChain.name },
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
return new KitError(errorDetails);
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Creates error for unsupported bridge route.
|
|
493
|
+
*
|
|
494
|
+
* This error is thrown when attempting to bridge between chains that don't
|
|
495
|
+
* have a supported bridge route configured.
|
|
496
|
+
*
|
|
497
|
+
* @param source - Source chain name
|
|
498
|
+
* @param destination - Destination chain name
|
|
499
|
+
* @returns KitError with specific route details
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```typescript
|
|
503
|
+
* import { createUnsupportedRouteError } from '@core/errors'
|
|
504
|
+
*
|
|
505
|
+
* throw createUnsupportedRouteError('Ethereum', 'Solana')
|
|
506
|
+
* // Message: "Route from Ethereum to Solana is not supported"
|
|
507
|
+
* ```
|
|
508
|
+
*/
|
|
509
|
+
function createUnsupportedRouteError(source, destination) {
|
|
510
|
+
const errorDetails = {
|
|
511
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
512
|
+
recoverability: 'FATAL',
|
|
513
|
+
message: `Route from ${source} to ${destination} is not supported.`,
|
|
514
|
+
cause: {
|
|
515
|
+
trace: { source, destination },
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
return new KitError(errorDetails);
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Creates error for invalid amount format or precision.
|
|
522
|
+
*
|
|
523
|
+
* This error is thrown when the provided amount doesn't meet validation
|
|
524
|
+
* requirements such as precision, range, or format.
|
|
525
|
+
*
|
|
526
|
+
* @param amount - The invalid amount string
|
|
527
|
+
* @param reason - Specific reason why amount is invalid
|
|
528
|
+
* @returns KitError with amount details and validation rule
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```typescript
|
|
532
|
+
* import { createInvalidAmountError } from '@core/errors'
|
|
533
|
+
*
|
|
534
|
+
* throw createInvalidAmountError('0.000001', 'Amount must be at least 0.01 USDC')
|
|
535
|
+
* // Message: "Invalid amount '0.000001': Amount must be at least 0.01 USDC"
|
|
536
|
+
*
|
|
537
|
+
* throw createInvalidAmountError('1,000.50', 'Amount must be a numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals')
|
|
538
|
+
* // Message: "Invalid amount '1,000.50': Amount must be a numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals."
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
function createInvalidAmountError(amount, reason) {
|
|
542
|
+
const errorDetails = {
|
|
543
|
+
...InputError.INVALID_AMOUNT,
|
|
544
|
+
recoverability: 'FATAL',
|
|
545
|
+
message: `Invalid amount '${amount}': ${reason}.`,
|
|
546
|
+
cause: {
|
|
547
|
+
trace: { amount, reason },
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
return new KitError(errorDetails);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Creates error for invalid wallet address format.
|
|
554
|
+
*
|
|
555
|
+
* This error is thrown when the provided address doesn't match the expected
|
|
556
|
+
* format for the specified chain.
|
|
557
|
+
*
|
|
558
|
+
* @param address - The invalid address string
|
|
559
|
+
* @param chain - Chain name where address is invalid
|
|
560
|
+
* @param expectedFormat - Description of expected address format
|
|
561
|
+
* @returns KitError with address details and format requirements
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```typescript
|
|
565
|
+
* import { createInvalidAddressError } from '@core/errors'
|
|
566
|
+
*
|
|
567
|
+
* throw createInvalidAddressError('0x123', 'Ethereum', '42-character hex string starting with 0x')
|
|
568
|
+
* // Message: "Invalid address '0x123' for Ethereum. Expected 42-character hex string starting with 0x."
|
|
569
|
+
*
|
|
570
|
+
* throw createInvalidAddressError('invalid', 'Solana', 'base58-encoded string')
|
|
571
|
+
* // Message: "Invalid address 'invalid' for Solana. Expected base58-encoded string."
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
function createInvalidAddressError(address, chain, expectedFormat) {
|
|
575
|
+
const errorDetails = {
|
|
576
|
+
...InputError.INVALID_ADDRESS,
|
|
577
|
+
recoverability: 'FATAL',
|
|
578
|
+
message: `Invalid address '${address}' for ${chain}. Expected ${expectedFormat}.`,
|
|
579
|
+
cause: {
|
|
580
|
+
trace: { address, chain, expectedFormat },
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
return new KitError(errorDetails);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Creates error for invalid chain configuration.
|
|
587
|
+
*
|
|
588
|
+
* This error is thrown when the provided chain doesn't meet the required
|
|
589
|
+
* configuration or is not supported for the operation.
|
|
590
|
+
*
|
|
591
|
+
* @param chain - The invalid chain name or identifier
|
|
592
|
+
* @param reason - Specific reason why chain is invalid
|
|
593
|
+
* @returns KitError with chain details and validation rule
|
|
594
|
+
*
|
|
595
|
+
* @example
|
|
596
|
+
* ```typescript
|
|
597
|
+
* import { createInvalidChainError } from '@core/errors'
|
|
598
|
+
*
|
|
599
|
+
* throw createInvalidChainError('UnknownChain', 'Chain is not supported by this bridge')
|
|
600
|
+
* // Message: "Invalid chain 'UnknownChain': Chain is not supported by this bridge"
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
function createInvalidChainError(chain, reason) {
|
|
604
|
+
const errorDetails = {
|
|
605
|
+
...InputError.INVALID_CHAIN,
|
|
606
|
+
recoverability: 'FATAL',
|
|
607
|
+
message: `Invalid chain '${chain}': ${reason}`,
|
|
608
|
+
cause: {
|
|
609
|
+
trace: { chain, reason },
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
return new KitError(errorDetails);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Creates error for general validation failure.
|
|
616
|
+
*
|
|
617
|
+
* This error is thrown when input validation fails for reasons not covered
|
|
618
|
+
* by more specific error types.
|
|
619
|
+
*
|
|
620
|
+
* @param field - The field that failed validation
|
|
621
|
+
* @param value - The invalid value (can be any type)
|
|
622
|
+
* @param reason - Specific reason why validation failed
|
|
623
|
+
* @returns KitError with validation details
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```typescript
|
|
627
|
+
* import { createValidationFailedError } from '@core/errors'
|
|
628
|
+
*
|
|
629
|
+
* throw createValidationFailedError('recipient', 'invalid@email', 'Must be a valid wallet address')
|
|
630
|
+
* // Message: "Validation failed for 'recipient': 'invalid@email' - Must be a valid wallet address"
|
|
631
|
+
*
|
|
632
|
+
* throw createValidationFailedError('chainId', 999, 'Unsupported chain ID')
|
|
633
|
+
* // Message: "Validation failed for 'chainId': 999 - Unsupported chain ID"
|
|
634
|
+
*
|
|
635
|
+
* throw createValidationFailedError('config', { invalid: true }, 'Missing required properties')
|
|
636
|
+
* // Message: "Validation failed for 'config': [object Object] - Missing required properties"
|
|
637
|
+
* ```
|
|
638
|
+
*/
|
|
639
|
+
function createValidationFailedError$1(field, value, reason) {
|
|
640
|
+
// Convert value to string for display, handling different types appropriately
|
|
641
|
+
let valueString;
|
|
642
|
+
if (typeof value === 'string') {
|
|
643
|
+
valueString = `'${value}'`;
|
|
644
|
+
}
|
|
645
|
+
else if (typeof value === 'object' && value !== null) {
|
|
646
|
+
valueString = JSON.stringify(value);
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
valueString = String(value);
|
|
650
|
+
}
|
|
651
|
+
const errorDetails = {
|
|
652
|
+
...InputError.VALIDATION_FAILED,
|
|
653
|
+
recoverability: 'FATAL',
|
|
654
|
+
message: `Validation failed for '${field}': ${valueString} - ${reason}.`,
|
|
655
|
+
cause: {
|
|
656
|
+
trace: { field, value, reason },
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
return new KitError(errorDetails);
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Creates a KitError from a Zod validation error with detailed error information.
|
|
663
|
+
*
|
|
664
|
+
* This factory function converts Zod validation failures into standardized KitError
|
|
665
|
+
* instances with INPUT_VALIDATION_FAILED code (1098). It extracts all Zod issues
|
|
666
|
+
* and includes them in both the error message and trace for debugging, providing
|
|
667
|
+
* developers with comprehensive validation feedback.
|
|
668
|
+
*
|
|
669
|
+
* The error message includes all validation errors concatenated with semicolons.
|
|
670
|
+
*
|
|
671
|
+
* @param zodError - The Zod validation error containing one or more validation issues
|
|
672
|
+
* @param context - Context string describing what was being validated (e.g., 'bridge parameters', 'user input')
|
|
673
|
+
* @returns KitError with INPUT_VALIDATION_FAILED code and structured validation details
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* ```typescript
|
|
677
|
+
* import { createValidationErrorFromZod } from '@core/errors'
|
|
678
|
+
* import { z } from 'zod'
|
|
679
|
+
*
|
|
680
|
+
* const schema = z.object({
|
|
681
|
+
* name: z.string().min(3),
|
|
682
|
+
* age: z.number().positive()
|
|
683
|
+
* })
|
|
684
|
+
*
|
|
685
|
+
* const result = schema.safeParse({ name: 'ab', age: -1 })
|
|
686
|
+
* if (!result.success) {
|
|
687
|
+
* throw createValidationErrorFromZod(result.error, 'user data')
|
|
688
|
+
* }
|
|
689
|
+
* // Throws: KitError with message:
|
|
690
|
+
* // "Invalid user data: name: String must contain at least 3 character(s); age: Number must be greater than 0"
|
|
691
|
+
* // And cause.trace.validationErrors containing all validation errors as an array
|
|
692
|
+
* ```
|
|
693
|
+
*
|
|
694
|
+
* @example
|
|
695
|
+
* ```typescript
|
|
696
|
+
* // Usage in validation functions
|
|
697
|
+
* import { createValidationErrorFromZod } from '@core/errors'
|
|
698
|
+
*
|
|
699
|
+
* function validateBridgeParams(params: unknown): asserts params is BridgeParams {
|
|
700
|
+
* const result = bridgeParamsSchema.safeParse(params)
|
|
701
|
+
* if (!result.success) {
|
|
702
|
+
* throw createValidationErrorFromZod(result.error, 'bridge parameters')
|
|
703
|
+
* }
|
|
704
|
+
* }
|
|
705
|
+
* ```
|
|
706
|
+
*/
|
|
707
|
+
function createValidationErrorFromZod(zodError, context) {
|
|
708
|
+
// Format each Zod issue as "path: message"
|
|
709
|
+
const validationErrors = zodError.issues.map((issue) => {
|
|
710
|
+
const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : '';
|
|
711
|
+
return `${path}${issue.message}`;
|
|
712
|
+
});
|
|
713
|
+
// Join all errors with semicolons to show complete validation feedback
|
|
714
|
+
const issueSummary = validationErrors.join('; ');
|
|
715
|
+
const allErrors = issueSummary || 'Invalid Input';
|
|
716
|
+
// Build full message from context and validation errors
|
|
717
|
+
const fullMessage = `Invalid ${context}: ${allErrors}`;
|
|
718
|
+
const errorDetails = {
|
|
719
|
+
...InputError.VALIDATION_FAILED,
|
|
720
|
+
recoverability: 'FATAL',
|
|
721
|
+
message: fullMessage,
|
|
722
|
+
cause: {
|
|
723
|
+
trace: {
|
|
724
|
+
validationErrors, // Array of formatted error strings for display
|
|
725
|
+
zodError: zodError.message, // Original Zod error message
|
|
726
|
+
zodIssues: zodError.issues, // Full Zod issues array for debugging
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
return new KitError(errorDetails);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// -----------------------------------------------------------------------------
|
|
734
|
+
// Blockchain Enum
|
|
735
|
+
// -----------------------------------------------------------------------------
|
|
736
|
+
/**
|
|
737
|
+
* Enumeration of all blockchains known to this library.
|
|
738
|
+
*
|
|
739
|
+
* This enum contains every blockchain that has a chain definition, regardless
|
|
740
|
+
* of whether bridging is currently supported. For chains that support bridging
|
|
741
|
+
* via CCTPv2, see {@link BridgeChain}.
|
|
742
|
+
*
|
|
743
|
+
* @enum
|
|
744
|
+
* @category Enums
|
|
745
|
+
* @description Provides string identifiers for each blockchain with a definition.
|
|
746
|
+
* @see {@link BridgeChain} for the subset of chains that support CCTPv2 bridging.
|
|
747
|
+
*/
|
|
748
|
+
var Blockchain;
|
|
749
|
+
(function (Blockchain) {
|
|
750
|
+
Blockchain["Algorand"] = "Algorand";
|
|
751
|
+
Blockchain["Algorand_Testnet"] = "Algorand_Testnet";
|
|
752
|
+
Blockchain["Aptos"] = "Aptos";
|
|
753
|
+
Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
|
|
754
|
+
Blockchain["Arc_Testnet"] = "Arc_Testnet";
|
|
755
|
+
Blockchain["Arbitrum"] = "Arbitrum";
|
|
756
|
+
Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
757
|
+
Blockchain["Avalanche"] = "Avalanche";
|
|
758
|
+
Blockchain["Avalanche_Fuji"] = "Avalanche_Fuji";
|
|
759
|
+
Blockchain["Base"] = "Base";
|
|
760
|
+
Blockchain["Base_Sepolia"] = "Base_Sepolia";
|
|
761
|
+
Blockchain["Celo"] = "Celo";
|
|
762
|
+
Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
|
|
763
|
+
Blockchain["Codex"] = "Codex";
|
|
764
|
+
Blockchain["Codex_Testnet"] = "Codex_Testnet";
|
|
765
|
+
Blockchain["Ethereum"] = "Ethereum";
|
|
766
|
+
Blockchain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
|
|
767
|
+
Blockchain["Hedera"] = "Hedera";
|
|
768
|
+
Blockchain["Hedera_Testnet"] = "Hedera_Testnet";
|
|
769
|
+
Blockchain["HyperEVM"] = "HyperEVM";
|
|
770
|
+
Blockchain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
771
|
+
Blockchain["Ink"] = "Ink";
|
|
772
|
+
Blockchain["Ink_Testnet"] = "Ink_Testnet";
|
|
773
|
+
Blockchain["Linea"] = "Linea";
|
|
774
|
+
Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
775
|
+
Blockchain["NEAR"] = "NEAR";
|
|
776
|
+
Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
|
|
777
|
+
Blockchain["Noble"] = "Noble";
|
|
778
|
+
Blockchain["Noble_Testnet"] = "Noble_Testnet";
|
|
779
|
+
Blockchain["Optimism"] = "Optimism";
|
|
780
|
+
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
781
|
+
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
782
|
+
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
783
|
+
Blockchain["Plume"] = "Plume";
|
|
784
|
+
Blockchain["Plume_Testnet"] = "Plume_Testnet";
|
|
785
|
+
Blockchain["Polygon"] = "Polygon";
|
|
786
|
+
Blockchain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
787
|
+
Blockchain["Sei"] = "Sei";
|
|
788
|
+
Blockchain["Sei_Testnet"] = "Sei_Testnet";
|
|
789
|
+
Blockchain["Solana"] = "Solana";
|
|
790
|
+
Blockchain["Solana_Devnet"] = "Solana_Devnet";
|
|
791
|
+
Blockchain["Sonic"] = "Sonic";
|
|
792
|
+
Blockchain["Sonic_Testnet"] = "Sonic_Testnet";
|
|
793
|
+
Blockchain["Stellar"] = "Stellar";
|
|
794
|
+
Blockchain["Stellar_Testnet"] = "Stellar_Testnet";
|
|
795
|
+
Blockchain["Sui"] = "Sui";
|
|
796
|
+
Blockchain["Sui_Testnet"] = "Sui_Testnet";
|
|
797
|
+
Blockchain["Unichain"] = "Unichain";
|
|
798
|
+
Blockchain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
799
|
+
Blockchain["World_Chain"] = "World_Chain";
|
|
800
|
+
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
801
|
+
Blockchain["XDC"] = "XDC";
|
|
802
|
+
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
803
|
+
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
804
|
+
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
805
|
+
})(Blockchain || (Blockchain = {}));
|
|
806
|
+
// -----------------------------------------------------------------------------
|
|
807
|
+
// Bridge Chain Enum (CCTPv2 Supported Chains)
|
|
808
|
+
// -----------------------------------------------------------------------------
|
|
809
|
+
/**
|
|
810
|
+
* Enumeration of blockchains that support cross-chain bridging via CCTPv2.
|
|
811
|
+
*
|
|
812
|
+
* The enum is derived from the full {@link Blockchain} enum but filtered to only
|
|
813
|
+
* include chains with active CCTPv2 support. When new chains gain CCTPv2 support,
|
|
814
|
+
* they are added to this enum.
|
|
815
|
+
*
|
|
816
|
+
* @enum
|
|
817
|
+
* @category Enums
|
|
818
|
+
*
|
|
819
|
+
* @remarks
|
|
820
|
+
* - This enum is the **canonical source** of bridging-supported chains.
|
|
821
|
+
* - Use this enum (or its string literals) in `kit.bridge()` calls for type safety.
|
|
822
|
+
* - Attempting to use a chain not in this enum will produce a TypeScript compile error.
|
|
823
|
+
*
|
|
824
|
+
* @example
|
|
825
|
+
* ```typescript
|
|
826
|
+
* import { BridgeKit, BridgeChain } from '@circle-fin/bridge-kit'
|
|
827
|
+
*
|
|
828
|
+
* const kit = new BridgeKit()
|
|
829
|
+
*
|
|
830
|
+
* // ✅ Valid - autocomplete suggests only supported chains
|
|
831
|
+
* await kit.bridge({
|
|
832
|
+
* from: { adapter, chain: BridgeChain.Ethereum },
|
|
833
|
+
* to: { adapter, chain: BridgeChain.Base },
|
|
834
|
+
* amount: '100'
|
|
835
|
+
* })
|
|
836
|
+
*
|
|
837
|
+
* // ✅ Also valid - string literals work with autocomplete
|
|
838
|
+
* await kit.bridge({
|
|
839
|
+
* from: { adapter, chain: 'Ethereum_Sepolia' },
|
|
840
|
+
* to: { adapter, chain: 'Base_Sepolia' },
|
|
841
|
+
* amount: '100'
|
|
842
|
+
* })
|
|
843
|
+
*
|
|
844
|
+
* // ❌ Compile error - Algorand is not in BridgeChain
|
|
845
|
+
* await kit.bridge({
|
|
846
|
+
* from: { adapter, chain: 'Algorand' }, // TypeScript error!
|
|
847
|
+
* to: { adapter, chain: 'Base' },
|
|
848
|
+
* amount: '100'
|
|
849
|
+
* })
|
|
850
|
+
* ```
|
|
851
|
+
*
|
|
852
|
+
* @see {@link Blockchain} for the complete list of all known blockchains.
|
|
853
|
+
* @see {@link BridgeChainIdentifier} for the type that accepts these values.
|
|
854
|
+
*/
|
|
855
|
+
var BridgeChain;
|
|
856
|
+
(function (BridgeChain) {
|
|
857
|
+
// Mainnet chains with CCTPv2 support
|
|
858
|
+
BridgeChain["Arbitrum"] = "Arbitrum";
|
|
859
|
+
BridgeChain["Avalanche"] = "Avalanche";
|
|
860
|
+
BridgeChain["Base"] = "Base";
|
|
861
|
+
BridgeChain["Codex"] = "Codex";
|
|
862
|
+
BridgeChain["Ethereum"] = "Ethereum";
|
|
863
|
+
BridgeChain["HyperEVM"] = "HyperEVM";
|
|
864
|
+
BridgeChain["Ink"] = "Ink";
|
|
865
|
+
BridgeChain["Linea"] = "Linea";
|
|
866
|
+
BridgeChain["Optimism"] = "Optimism";
|
|
867
|
+
BridgeChain["Plume"] = "Plume";
|
|
868
|
+
BridgeChain["Polygon"] = "Polygon";
|
|
869
|
+
BridgeChain["Sei"] = "Sei";
|
|
870
|
+
BridgeChain["Solana"] = "Solana";
|
|
871
|
+
BridgeChain["Sonic"] = "Sonic";
|
|
872
|
+
BridgeChain["Unichain"] = "Unichain";
|
|
873
|
+
BridgeChain["World_Chain"] = "World_Chain";
|
|
874
|
+
BridgeChain["XDC"] = "XDC";
|
|
875
|
+
// Testnet chains with CCTPv2 support
|
|
876
|
+
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
877
|
+
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
878
|
+
BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
|
|
879
|
+
BridgeChain["Base_Sepolia"] = "Base_Sepolia";
|
|
880
|
+
BridgeChain["Codex_Testnet"] = "Codex_Testnet";
|
|
881
|
+
BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
|
|
882
|
+
BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
|
|
883
|
+
BridgeChain["Ink_Testnet"] = "Ink_Testnet";
|
|
884
|
+
BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
|
|
885
|
+
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
886
|
+
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
887
|
+
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
888
|
+
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
889
|
+
BridgeChain["Solana_Devnet"] = "Solana_Devnet";
|
|
890
|
+
BridgeChain["Sonic_Testnet"] = "Sonic_Testnet";
|
|
891
|
+
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
892
|
+
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
893
|
+
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
894
|
+
})(BridgeChain || (BridgeChain = {}));
|
|
895
|
+
|
|
896
|
+
/**
|
|
897
|
+
* Helper function to define a chain with proper TypeScript typing.
|
|
898
|
+
*
|
|
899
|
+
* This utility function works with TypeScript's `as const` assertion to create
|
|
900
|
+
* strongly-typed, immutable chain definition objects. It preserves literal types
|
|
901
|
+
* from the input and ensures the resulting object maintains all type information.
|
|
902
|
+
*
|
|
903
|
+
* When used with `as const`, it allows TypeScript to infer the most specific
|
|
904
|
+
* possible types for all properties, including string literals and numeric literals,
|
|
905
|
+
* rather than widening them to general types like string or number.
|
|
906
|
+
* @typeParam T - The specific chain definition type (must extend ChainDefinition)
|
|
907
|
+
* @param chain - The chain definition object, typically with an `as const` assertion
|
|
908
|
+
* @returns The same chain definition with preserved literal types
|
|
909
|
+
* @example
|
|
910
|
+
* ```typescript
|
|
911
|
+
* // Define an EVM chain with literal types preserved
|
|
912
|
+
* const Ethereum = defineChain({
|
|
913
|
+
* type: 'evm',
|
|
914
|
+
* chain: Blockchain.Ethereum,
|
|
915
|
+
* chainId: 1,
|
|
916
|
+
* name: 'Ethereum',
|
|
917
|
+
* nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
|
918
|
+
* isTestnet: false,
|
|
919
|
+
* usdcAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
920
|
+
* eurcAddress: null,
|
|
921
|
+
* cctp: {
|
|
922
|
+
* domain: 0,
|
|
923
|
+
* contracts: {
|
|
924
|
+
* TokenMessengerV1: '0xbd3fa81b58ba92a82136038b25adec7066af3155',
|
|
925
|
+
* MessageTransmitterV1: '0x0a992d191deec32afe36203ad87d7d289a738f81'
|
|
926
|
+
* }
|
|
927
|
+
* }
|
|
928
|
+
* } as const);
|
|
929
|
+
* ```
|
|
930
|
+
*/
|
|
931
|
+
function defineChain(chain) {
|
|
932
|
+
return chain;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Algorand Mainnet chain definition
|
|
937
|
+
* @remarks
|
|
938
|
+
* This represents the official production network for the Algorand blockchain.
|
|
939
|
+
*/
|
|
940
|
+
const Algorand = defineChain({
|
|
941
|
+
type: 'algorand',
|
|
942
|
+
chain: Blockchain.Algorand,
|
|
943
|
+
name: 'Algorand',
|
|
944
|
+
title: 'Algorand Mainnet',
|
|
945
|
+
nativeCurrency: {
|
|
946
|
+
name: 'Algo',
|
|
947
|
+
symbol: 'ALGO',
|
|
948
|
+
decimals: 6,
|
|
949
|
+
},
|
|
950
|
+
isTestnet: false,
|
|
951
|
+
explorerUrl: 'https://explorer.perawallet.app/tx/{hash}',
|
|
952
|
+
rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
|
|
953
|
+
eurcAddress: null,
|
|
954
|
+
usdcAddress: '31566704',
|
|
955
|
+
cctp: null,
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Algorand Testnet chain definition
|
|
960
|
+
* @remarks
|
|
961
|
+
* This represents the official testnet for the Algorand blockchain.
|
|
962
|
+
*/
|
|
963
|
+
const AlgorandTestnet = defineChain({
|
|
964
|
+
type: 'algorand',
|
|
965
|
+
chain: Blockchain.Algorand_Testnet,
|
|
966
|
+
name: 'Algorand Testnet',
|
|
967
|
+
title: 'Algorand Test Network',
|
|
968
|
+
nativeCurrency: {
|
|
969
|
+
name: 'Algo',
|
|
970
|
+
symbol: 'ALGO',
|
|
971
|
+
decimals: 6,
|
|
972
|
+
},
|
|
973
|
+
isTestnet: true,
|
|
974
|
+
explorerUrl: 'https://testnet.explorer.perawallet.app/tx/{hash}',
|
|
975
|
+
rpcEndpoints: ['https://testnet-api.algonode.cloud'],
|
|
976
|
+
eurcAddress: null,
|
|
977
|
+
usdcAddress: '10458941',
|
|
978
|
+
cctp: null,
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Aptos Mainnet chain definition
|
|
983
|
+
* @remarks
|
|
984
|
+
* This represents the official production network for the Aptos blockchain.
|
|
985
|
+
*/
|
|
986
|
+
const Aptos = defineChain({
|
|
987
|
+
type: 'aptos',
|
|
988
|
+
chain: Blockchain.Aptos,
|
|
989
|
+
name: 'Aptos',
|
|
990
|
+
title: 'Aptos Mainnet',
|
|
991
|
+
nativeCurrency: {
|
|
992
|
+
name: 'Aptos',
|
|
993
|
+
symbol: 'APT',
|
|
994
|
+
decimals: 8,
|
|
995
|
+
},
|
|
996
|
+
isTestnet: false,
|
|
997
|
+
explorerUrl: 'https://explorer.aptoslabs.com/txn/{hash}?network=mainnet',
|
|
998
|
+
rpcEndpoints: ['https://fullnode.mainnet.aptoslabs.com/v1'],
|
|
999
|
+
eurcAddress: null,
|
|
1000
|
+
usdcAddress: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',
|
|
1001
|
+
cctp: {
|
|
1002
|
+
domain: 9,
|
|
1003
|
+
contracts: {
|
|
1004
|
+
v1: {
|
|
1005
|
+
type: 'split',
|
|
1006
|
+
tokenMessenger: '0x9bce6734f7b63e835108e3bd8c36743d4709fe435f44791918801d0989640a9d',
|
|
1007
|
+
messageTransmitter: '0x177e17751820e4b4371873ca8c30279be63bdea63b88ed0f2239c2eea10f1772',
|
|
1008
|
+
confirmations: 1,
|
|
1009
|
+
},
|
|
1010
|
+
},
|
|
1011
|
+
},
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Aptos Testnet chain definition
|
|
1016
|
+
* @remarks
|
|
1017
|
+
* This represents the official test network for the Aptos blockchain.
|
|
1018
|
+
*/
|
|
1019
|
+
const AptosTestnet = defineChain({
|
|
1020
|
+
type: 'aptos',
|
|
1021
|
+
chain: Blockchain.Aptos_Testnet,
|
|
1022
|
+
name: 'Aptos Testnet',
|
|
1023
|
+
title: 'Aptos Test Network',
|
|
263
1024
|
nativeCurrency: {
|
|
264
1025
|
name: 'Aptos',
|
|
265
1026
|
symbol: 'APT',
|
|
@@ -2361,13 +3122,51 @@ const chainDefinitionSchema$1 = z.discriminatedUnion('type', [
|
|
|
2361
3122
|
* chainIdentifierSchema.parse(Ethereum)
|
|
2362
3123
|
* ```
|
|
2363
3124
|
*/
|
|
2364
|
-
|
|
3125
|
+
z.union([
|
|
2365
3126
|
z
|
|
2366
3127
|
.string()
|
|
2367
3128
|
.refine((val) => val in Blockchain, 'Must be a valid Blockchain enum value as string'),
|
|
2368
3129
|
z.nativeEnum(Blockchain),
|
|
2369
3130
|
chainDefinitionSchema$1,
|
|
2370
3131
|
]);
|
|
3132
|
+
/**
|
|
3133
|
+
* Zod schema for validating bridge chain identifiers.
|
|
3134
|
+
*
|
|
3135
|
+
* This schema validates that the provided chain is supported for CCTPv2 bridging.
|
|
3136
|
+
* It accepts either a BridgeChain enum value, a string matching a BridgeChain value,
|
|
3137
|
+
* or a ChainDefinition for a supported chain.
|
|
3138
|
+
*
|
|
3139
|
+
* Use this schema when validating chain parameters for bridge operations to ensure
|
|
3140
|
+
* only CCTPv2-supported chains are accepted at runtime.
|
|
3141
|
+
*
|
|
3142
|
+
* @example
|
|
3143
|
+
* ```typescript
|
|
3144
|
+
* import { bridgeChainIdentifierSchema } from '@core/chains/validation'
|
|
3145
|
+
* import { BridgeChain, Chains } from '@core/chains'
|
|
3146
|
+
*
|
|
3147
|
+
* // Valid - BridgeChain enum value
|
|
3148
|
+
* bridgeChainIdentifierSchema.parse(BridgeChain.Ethereum)
|
|
3149
|
+
*
|
|
3150
|
+
* // Valid - string literal
|
|
3151
|
+
* bridgeChainIdentifierSchema.parse('Base_Sepolia')
|
|
3152
|
+
*
|
|
3153
|
+
* // Valid - ChainDefinition (validated by CCTP support)
|
|
3154
|
+
* bridgeChainIdentifierSchema.parse(Chains.Solana)
|
|
3155
|
+
*
|
|
3156
|
+
* // Invalid - Algorand is not in BridgeChain (throws ZodError)
|
|
3157
|
+
* bridgeChainIdentifierSchema.parse('Algorand')
|
|
3158
|
+
* ```
|
|
3159
|
+
*
|
|
3160
|
+
* @see {@link BridgeChain} for the enum of supported chains.
|
|
3161
|
+
*/
|
|
3162
|
+
const bridgeChainIdentifierSchema = z.union([
|
|
3163
|
+
z.string().refine((val) => val in BridgeChain, (val) => ({
|
|
3164
|
+
message: `Chain "${val}" is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
|
|
3165
|
+
})),
|
|
3166
|
+
chainDefinitionSchema$1.refine((chainDef) => chainDef.chain in BridgeChain, (chainDef) => ({
|
|
3167
|
+
message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
|
|
3168
|
+
})),
|
|
3169
|
+
]);
|
|
2371
3170
|
|
|
2372
3171
|
/**
|
|
2373
3172
|
* Retrieve a chain definition by its blockchain enum value.
|
|
@@ -2396,1464 +3195,1150 @@ const getChainByEnum = (blockchain) => {
|
|
|
2396
3195
|
if (!chain) {
|
|
2397
3196
|
throw new Error(`No chain definition found for blockchain: ${blockchain}`);
|
|
2398
3197
|
}
|
|
2399
|
-
return chain;
|
|
2400
|
-
};
|
|
2401
|
-
|
|
2402
|
-
/**
|
|
2403
|
-
* Resolves a flexible chain identifier to a ChainDefinition.
|
|
2404
|
-
*
|
|
2405
|
-
* This function handles all three supported formats:
|
|
2406
|
-
* - ChainDefinition objects (passed through unchanged)
|
|
2407
|
-
* - Blockchain enum values (resolved via getChainByEnum)
|
|
2408
|
-
* - String literals of blockchain values (resolved via getChainByEnum)
|
|
2409
|
-
*
|
|
2410
|
-
* @param chainIdentifier - The chain identifier to resolve
|
|
2411
|
-
* @returns The resolved ChainDefinition object
|
|
2412
|
-
* @throws Error if the chain identifier cannot be resolved
|
|
2413
|
-
*
|
|
2414
|
-
* @example
|
|
2415
|
-
* ```typescript
|
|
2416
|
-
* import { resolveChainIdentifier } from '@core/chains'
|
|
2417
|
-
* import { Blockchain, Ethereum } from '@core/chains'
|
|
2418
|
-
*
|
|
2419
|
-
* // All of these resolve to the same ChainDefinition:
|
|
2420
|
-
* const chain1 = resolveChainIdentifier(Ethereum)
|
|
2421
|
-
* const chain2 = resolveChainIdentifier(Blockchain.Ethereum)
|
|
2422
|
-
* const chain3 = resolveChainIdentifier('Ethereum')
|
|
2423
|
-
* ```
|
|
2424
|
-
*/
|
|
2425
|
-
function resolveChainIdentifier(chainIdentifier) {
|
|
2426
|
-
// If it's already a ChainDefinition object, return it unchanged
|
|
2427
|
-
if (typeof chainIdentifier === 'object') {
|
|
2428
|
-
return chainIdentifier;
|
|
2429
|
-
}
|
|
2430
|
-
// If it's a string or enum value, resolve it via getChainByEnum
|
|
2431
|
-
if (typeof chainIdentifier === 'string') {
|
|
2432
|
-
return getChainByEnum(chainIdentifier);
|
|
2433
|
-
}
|
|
2434
|
-
// This should never happen with proper typing, but provide a fallback
|
|
2435
|
-
throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
/**
|
|
2439
|
-
* Convert a human-readable decimal string to its smallest unit representation.
|
|
2440
|
-
*
|
|
2441
|
-
* This function converts user-friendly decimal values into the integer representation
|
|
2442
|
-
* required by blockchain operations, where all values are stored in the smallest
|
|
2443
|
-
* denomination. Uses the battle-tested implementation from @ethersproject/units.
|
|
2444
|
-
*
|
|
2445
|
-
* @param value - The decimal string to convert (e.g., "1.0")
|
|
2446
|
-
* @param decimals - The number of decimal places for the unit conversion
|
|
2447
|
-
* @returns The value in smallest units as a bigint (e.g., 1000000n for 1 USDC with 6 decimals)
|
|
2448
|
-
* @throws Error if the value is not a valid decimal string
|
|
2449
|
-
*
|
|
2450
|
-
* @example
|
|
2451
|
-
* ```typescript
|
|
2452
|
-
* import { parseUnits } from '@core/utils'
|
|
2453
|
-
*
|
|
2454
|
-
* // Parse USDC (6 decimals)
|
|
2455
|
-
* const usdcParsed = parseUnits('1.0', 6)
|
|
2456
|
-
* console.log(usdcParsed) // 1000000n
|
|
2457
|
-
*
|
|
2458
|
-
* // Parse ETH (18 decimals)
|
|
2459
|
-
* const ethParsed = parseUnits('1.0', 18)
|
|
2460
|
-
* console.log(ethParsed) // 1000000000000000000n
|
|
2461
|
-
*
|
|
2462
|
-
* // Parse fractional amount
|
|
2463
|
-
* const fractionalParsed = parseUnits('1.5', 6)
|
|
2464
|
-
* console.log(fractionalParsed) // 1500000n
|
|
2465
|
-
*
|
|
2466
|
-
* // Parse integer (no decimal point)
|
|
2467
|
-
* const integerParsed = parseUnits('42', 6)
|
|
2468
|
-
* console.log(integerParsed) // 42000000n
|
|
2469
|
-
* ```
|
|
2470
|
-
*/
|
|
2471
|
-
const parseUnits = (value, decimals) => {
|
|
2472
|
-
return parseUnits$1(value, decimals).toBigInt();
|
|
2473
|
-
};
|
|
2474
|
-
|
|
2475
|
-
/**
|
|
2476
|
-
* Custom error class for validation errors.
|
|
2477
|
-
* Provides structured error information while hiding implementation details.
|
|
2478
|
-
*/
|
|
2479
|
-
class ValidationError extends Error {
|
|
2480
|
-
errors;
|
|
2481
|
-
constructor(message, errors) {
|
|
2482
|
-
super(message);
|
|
2483
|
-
this.errors = errors;
|
|
2484
|
-
this.name = 'ValidationError';
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
/**
|
|
2489
|
-
* Formats a Zod error for display.
|
|
2490
|
-
* @internal
|
|
2491
|
-
*/
|
|
2492
|
-
const formatZodError = (error) => {
|
|
2493
|
-
const path = error.path.length > 0 ? `${error.path.join('.')}: ` : '';
|
|
2494
|
-
return `${path}${error.message}`;
|
|
2495
|
-
};
|
|
2496
|
-
/**
|
|
2497
|
-
* Validates data against a Zod schema with enhanced error reporting.
|
|
2498
|
-
*
|
|
2499
|
-
* This function performs validation using Zod schemas and provides detailed error
|
|
2500
|
-
* messages that include the validation context. It's designed to give developers
|
|
2501
|
-
* clear feedback about what went wrong during validation.
|
|
2502
|
-
*
|
|
2503
|
-
* @param schema - The Zod schema to validate against
|
|
2504
|
-
* @param data - The data to validate
|
|
2505
|
-
* @param context - Context string to include in error messages (e.g., 'bridge parameters')
|
|
2506
|
-
* @returns The validated and parsed data
|
|
2507
|
-
* @throws {ValidationError} If validation fails
|
|
2508
|
-
*
|
|
2509
|
-
* @example
|
|
2510
|
-
* ```typescript
|
|
2511
|
-
* const result = validate(BridgeParamsSchema, params, 'bridge parameters')
|
|
2512
|
-
* ```
|
|
2513
|
-
*/
|
|
2514
|
-
function validate(value, schema, context) {
|
|
2515
|
-
const result = schema.safeParse(value);
|
|
2516
|
-
if (!result.success) {
|
|
2517
|
-
const errors = result.error.errors.map(formatZodError);
|
|
2518
|
-
const firstError = errors[0] ?? 'Invalid value';
|
|
2519
|
-
throw new ValidationError(`Invalid ${context}: ${firstError}`, errors);
|
|
2520
|
-
}
|
|
2521
|
-
}
|
|
3198
|
+
return chain;
|
|
3199
|
+
};
|
|
2522
3200
|
|
|
2523
3201
|
/**
|
|
2524
|
-
*
|
|
2525
|
-
* This allows us to attach metadata to objects without interfering with their structure,
|
|
2526
|
-
* enabling optimized validation by skipping already validated objects.
|
|
2527
|
-
* @internal
|
|
2528
|
-
*/
|
|
2529
|
-
const VALIDATION_STATE = Symbol('validationState');
|
|
2530
|
-
/**
|
|
2531
|
-
* Validates data against a Zod schema with state tracking and enhanced error reporting.
|
|
3202
|
+
* Resolves a flexible chain identifier to a ChainDefinition.
|
|
2532
3203
|
*
|
|
2533
|
-
* This function
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
3204
|
+
* This function handles all three supported formats:
|
|
3205
|
+
* - ChainDefinition objects (passed through unchanged)
|
|
3206
|
+
* - Blockchain enum values (resolved via getChainByEnum)
|
|
3207
|
+
* - String literals of blockchain values (resolved via getChainByEnum)
|
|
2536
3208
|
*
|
|
2537
|
-
* @param
|
|
2538
|
-
* @
|
|
2539
|
-
* @
|
|
2540
|
-
* @returns Object containing validation result and state information
|
|
2541
|
-
* @throws {ValidationError} If validation fails
|
|
3209
|
+
* @param chainIdentifier - The chain identifier to resolve
|
|
3210
|
+
* @returns The resolved ChainDefinition object
|
|
3211
|
+
* @throws Error if the chain identifier cannot be resolved
|
|
2542
3212
|
*
|
|
2543
3213
|
* @example
|
|
2544
3214
|
* ```typescript
|
|
2545
|
-
*
|
|
3215
|
+
* import { resolveChainIdentifier } from '@core/chains'
|
|
3216
|
+
* import { Blockchain, Ethereum } from '@core/chains'
|
|
3217
|
+
*
|
|
3218
|
+
* // All of these resolve to the same ChainDefinition:
|
|
3219
|
+
* const chain1 = resolveChainIdentifier(Ethereum)
|
|
3220
|
+
* const chain2 = resolveChainIdentifier(Blockchain.Ethereum)
|
|
3221
|
+
* const chain3 = resolveChainIdentifier('Ethereum')
|
|
2546
3222
|
* ```
|
|
2547
3223
|
*/
|
|
2548
|
-
function
|
|
2549
|
-
//
|
|
2550
|
-
if (
|
|
2551
|
-
|
|
2552
|
-
`Value is null`,
|
|
2553
|
-
]);
|
|
2554
|
-
}
|
|
2555
|
-
if (value === undefined) {
|
|
2556
|
-
throw new ValidationError(`Invalid ${context}: Value is undefined`, [
|
|
2557
|
-
`Value is undefined`,
|
|
2558
|
-
]);
|
|
2559
|
-
}
|
|
2560
|
-
// Ensure value is an object that can hold validation state
|
|
2561
|
-
if (typeof value !== 'object') {
|
|
2562
|
-
throw new ValidationError(`Invalid ${context}: Value must be an object`, [
|
|
2563
|
-
`Value must be an object, got ${typeof value}`,
|
|
2564
|
-
]);
|
|
3224
|
+
function resolveChainIdentifier(chainIdentifier) {
|
|
3225
|
+
// If it's already a ChainDefinition object, return it unchanged
|
|
3226
|
+
if (typeof chainIdentifier === 'object') {
|
|
3227
|
+
return chainIdentifier;
|
|
2565
3228
|
}
|
|
2566
|
-
//
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
// Skip validation if already validated by this validator
|
|
2570
|
-
if (state.validatedBy.includes(validatorName)) {
|
|
2571
|
-
return;
|
|
3229
|
+
// If it's a string or enum value, resolve it via getChainByEnum
|
|
3230
|
+
if (typeof chainIdentifier === 'string') {
|
|
3231
|
+
return getChainByEnum(chainIdentifier);
|
|
2572
3232
|
}
|
|
2573
|
-
//
|
|
2574
|
-
|
|
2575
|
-
// Update validation state
|
|
2576
|
-
state.validatedBy.push(validatorName);
|
|
2577
|
-
valueWithState[VALIDATION_STATE] = state;
|
|
3233
|
+
// This should never happen with proper typing, but provide a fallback
|
|
3234
|
+
throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
|
|
2578
3235
|
}
|
|
2579
3236
|
|
|
2580
3237
|
/**
|
|
2581
|
-
*
|
|
2582
|
-
* This schema ensures the chain definition has the required properties for URL generation.
|
|
2583
|
-
*/
|
|
2584
|
-
const chainDefinitionSchema = z.object({
|
|
2585
|
-
name: z
|
|
2586
|
-
.string({
|
|
2587
|
-
required_error: 'Chain name is required',
|
|
2588
|
-
invalid_type_error: 'Chain name must be a string',
|
|
2589
|
-
})
|
|
2590
|
-
.min(1, 'Chain name cannot be empty'),
|
|
2591
|
-
explorerUrl: z
|
|
2592
|
-
.string({
|
|
2593
|
-
required_error: 'Explorer URL template is required',
|
|
2594
|
-
invalid_type_error: 'Explorer URL template must be a string',
|
|
2595
|
-
})
|
|
2596
|
-
.min(1, 'Explorer URL template cannot be empty')
|
|
2597
|
-
.refine((url) => url.includes('{hash}'), 'Explorer URL template must contain a {hash} placeholder'),
|
|
2598
|
-
});
|
|
2599
|
-
/**
|
|
2600
|
-
* Zod schema for validating transaction hash strings used in buildExplorerUrl.
|
|
2601
|
-
* This schema ensures the transaction hash is a non-empty string.
|
|
2602
|
-
*/
|
|
2603
|
-
const transactionHashSchema = z
|
|
2604
|
-
.string({
|
|
2605
|
-
required_error: 'Transaction hash is required',
|
|
2606
|
-
invalid_type_error: 'Transaction hash must be a string',
|
|
2607
|
-
})
|
|
2608
|
-
.min(1, 'Transaction hash cannot be empty')
|
|
2609
|
-
.transform((hash) => hash.trim()) // Automatically trim whitespace
|
|
2610
|
-
.refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
|
|
2611
|
-
/**
|
|
2612
|
-
* Zod schema for validating buildExplorerUrl function parameters.
|
|
2613
|
-
* This schema validates both the chain definition and transaction hash together.
|
|
2614
|
-
*/
|
|
2615
|
-
z.object({
|
|
2616
|
-
chainDef: chainDefinitionSchema,
|
|
2617
|
-
txHash: transactionHashSchema,
|
|
2618
|
-
});
|
|
2619
|
-
/**
|
|
2620
|
-
* Zod schema for validating the generated explorer URL.
|
|
2621
|
-
* This schema ensures the generated URL is valid.
|
|
2622
|
-
*/
|
|
2623
|
-
z
|
|
2624
|
-
.string()
|
|
2625
|
-
.url('Generated explorer URL is invalid');
|
|
2626
|
-
|
|
2627
|
-
/**
|
|
2628
|
-
* A type-safe event emitter for managing action-based event subscriptions.
|
|
3238
|
+
* Extracts chain information including name, display name, and expected address format.
|
|
2629
3239
|
*
|
|
2630
|
-
*
|
|
2631
|
-
*
|
|
2632
|
-
*
|
|
3240
|
+
* This function determines the chain type by checking the explicit `type` property first,
|
|
3241
|
+
* then falls back to name-based matching for Solana chains. The expected address format
|
|
3242
|
+
* is determined based on the chain type:
|
|
3243
|
+
* - EVM chains: 42-character hex address starting with 0x
|
|
3244
|
+
* - Solana chains: 44-character base58 encoded string
|
|
3245
|
+
* - Other chains: Generic format message based on chain type
|
|
2633
3246
|
*
|
|
2634
|
-
* @
|
|
3247
|
+
* @param chain - The chain identifier (ChainDefinition object, string name, or undefined/null)
|
|
3248
|
+
* @returns Chain information with name, display name, and expected address format
|
|
2635
3249
|
*
|
|
2636
3250
|
* @example
|
|
2637
3251
|
* ```typescript
|
|
2638
|
-
* import {
|
|
2639
|
-
*
|
|
2640
|
-
* // Define your action types
|
|
2641
|
-
* type TransferActions = {
|
|
2642
|
-
* started: { txHash: string; amount: string };
|
|
2643
|
-
* completed: { txHash: string; destinationTxHash: string };
|
|
2644
|
-
* failed: { error: Error };
|
|
2645
|
-
* };
|
|
3252
|
+
* import { extractChainInfo } from '@core/chains'
|
|
2646
3253
|
*
|
|
2647
|
-
* //
|
|
2648
|
-
* const
|
|
3254
|
+
* // EVM chain with explicit type
|
|
3255
|
+
* const info1 = extractChainInfo({ name: 'Ethereum', type: 'evm' })
|
|
3256
|
+
* console.log(info1.name) // → "Ethereum"
|
|
3257
|
+
* console.log(info1.displayName) // → "Ethereum"
|
|
3258
|
+
* console.log(info1.expectedAddressFormat)
|
|
3259
|
+
* // → "42-character hex address starting with 0x"
|
|
2649
3260
|
*
|
|
2650
|
-
* //
|
|
2651
|
-
*
|
|
2652
|
-
*
|
|
2653
|
-
*
|
|
3261
|
+
* // Solana chain (inferred from name)
|
|
3262
|
+
* const info2 = extractChainInfo('Solana')
|
|
3263
|
+
* console.log(info2.expectedAddressFormat)
|
|
3264
|
+
* // → "44-character base58 encoded string"
|
|
2654
3265
|
*
|
|
2655
|
-
* //
|
|
2656
|
-
*
|
|
2657
|
-
*
|
|
2658
|
-
*
|
|
3266
|
+
* // Non-EVM chain with explicit type
|
|
3267
|
+
* const info3 = extractChainInfo({ name: 'Algorand', type: 'algorand' })
|
|
3268
|
+
* console.log(info3.expectedAddressFormat)
|
|
3269
|
+
* // → "valid algorand address"
|
|
2659
3270
|
*
|
|
2660
|
-
* //
|
|
2661
|
-
*
|
|
2662
|
-
*
|
|
2663
|
-
*
|
|
2664
|
-
* });
|
|
3271
|
+
* // Unknown chain
|
|
3272
|
+
* const info4 = extractChainInfo(undefined)
|
|
3273
|
+
* console.log(info4.name) // → "unknown"
|
|
3274
|
+
* console.log(info4.expectedAddressFormat) // → "valid blockchain address"
|
|
2665
3275
|
* ```
|
|
2666
3276
|
*/
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
//
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
}
|
|
2683
|
-
// Add the handler to the specific action's array
|
|
2684
|
-
this.handlers[action].push(handler);
|
|
2685
|
-
}
|
|
3277
|
+
function extractChainInfo(chain) {
|
|
3278
|
+
const name = typeof chain === 'string' ? chain : (chain?.name ?? 'unknown');
|
|
3279
|
+
const chainType = typeof chain === 'object' && chain !== null ? chain.type : undefined;
|
|
3280
|
+
// Use explicit chain type if available, fallback to name matching
|
|
3281
|
+
const isSolana = chainType === undefined
|
|
3282
|
+
? name.toLowerCase().includes('solana')
|
|
3283
|
+
: chainType === 'solana';
|
|
3284
|
+
// Default to EVM if not Solana and no explicit non-EVM type
|
|
3285
|
+
const isEVM = chainType === undefined
|
|
3286
|
+
? !isSolana // Default to EVM for unknown chains (unless they're Solana)
|
|
3287
|
+
: chainType === 'evm';
|
|
3288
|
+
// Determine expected address format based on chain type
|
|
3289
|
+
let expectedAddressFormat;
|
|
3290
|
+
if (isSolana) {
|
|
3291
|
+
expectedAddressFormat = '44-character base58 encoded string';
|
|
2686
3292
|
}
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
if (action === '*') {
|
|
2690
|
-
// Find and remove the handler from wildcard array
|
|
2691
|
-
const index = this.wildcard.indexOf(handler);
|
|
2692
|
-
if (index !== -1) {
|
|
2693
|
-
this.wildcard.splice(index, 1);
|
|
2694
|
-
}
|
|
2695
|
-
}
|
|
2696
|
-
else if (this.handlers[action]) {
|
|
2697
|
-
// Check if there are handlers for this action
|
|
2698
|
-
// Find and remove the specific handler
|
|
2699
|
-
const index = this.handlers[action].indexOf(handler);
|
|
2700
|
-
if (index !== -1) {
|
|
2701
|
-
this.handlers[action].splice(index, 1);
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
3293
|
+
else if (isEVM) {
|
|
3294
|
+
expectedAddressFormat = '42-character hex address starting with 0x';
|
|
2704
3295
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
*
|
|
2708
|
-
* This method notifies both:
|
|
2709
|
-
* - Handlers registered specifically for this action
|
|
2710
|
-
* - Wildcard handlers registered for all actions
|
|
2711
|
-
*
|
|
2712
|
-
* @param action - The action key identifying the event type.
|
|
2713
|
-
* @param payload - The data associated with the action.
|
|
2714
|
-
*
|
|
2715
|
-
* @example
|
|
2716
|
-
* ```typescript
|
|
2717
|
-
* type Actions = {
|
|
2718
|
-
* transferStarted: { amount: string; destination: string };
|
|
2719
|
-
* transferComplete: { txHash: string };
|
|
2720
|
-
* };
|
|
2721
|
-
*
|
|
2722
|
-
* const events = new Actionable<Actions>();
|
|
2723
|
-
*
|
|
2724
|
-
* // Dispatch an event
|
|
2725
|
-
* events.dispatch('transferStarted', {
|
|
2726
|
-
* amount: '100',
|
|
2727
|
-
* destination: '0xABC123'
|
|
2728
|
-
* });
|
|
2729
|
-
* ```
|
|
2730
|
-
*/
|
|
2731
|
-
dispatch(action, payload) {
|
|
2732
|
-
// Execute all handlers registered for this specific action
|
|
2733
|
-
for (const h of this.handlers[action] ?? [])
|
|
2734
|
-
h(payload);
|
|
2735
|
-
// Execute all wildcard handlers
|
|
2736
|
-
for (const h of this.wildcard)
|
|
2737
|
-
h(payload);
|
|
3296
|
+
else {
|
|
3297
|
+
expectedAddressFormat = `valid ${chainType ?? 'blockchain'} address`;
|
|
2738
3298
|
}
|
|
3299
|
+
return {
|
|
3300
|
+
name,
|
|
3301
|
+
displayName: name.replaceAll('_', ' '),
|
|
3302
|
+
expectedAddressFormat,
|
|
3303
|
+
};
|
|
2739
3304
|
}
|
|
2740
3305
|
|
|
2741
|
-
var name = "@circle-fin/bridge-kit";
|
|
2742
|
-
var version = "1.1.1";
|
|
2743
|
-
var pkg = {
|
|
2744
|
-
name: name,
|
|
2745
|
-
version: version};
|
|
2746
|
-
|
|
2747
3306
|
/**
|
|
2748
|
-
*
|
|
3307
|
+
* Type guard to check if an error is a KitError instance.
|
|
2749
3308
|
*
|
|
2750
|
-
*
|
|
2751
|
-
*
|
|
2752
|
-
*
|
|
2753
|
-
*/
|
|
2754
|
-
const RECOVERABILITY_VALUES = [
|
|
2755
|
-
'RETRYABLE',
|
|
2756
|
-
'RESUMABLE',
|
|
2757
|
-
'FATAL',
|
|
2758
|
-
];
|
|
2759
|
-
|
|
2760
|
-
// Create a mutable array for Zod enum validation
|
|
2761
|
-
const RECOVERABILITY_ARRAY = [...RECOVERABILITY_VALUES];
|
|
2762
|
-
/**
|
|
2763
|
-
* Zod schema for validating ErrorDetails objects.
|
|
3309
|
+
* This guard enables TypeScript to narrow the type from `unknown` to
|
|
3310
|
+
* `KitError`, providing access to structured error properties like
|
|
3311
|
+
* code, name, and recoverability.
|
|
2764
3312
|
*
|
|
2765
|
-
*
|
|
2766
|
-
*
|
|
3313
|
+
* @param error - Unknown error to check
|
|
3314
|
+
* @returns True if error is KitError with proper type narrowing
|
|
2767
3315
|
*
|
|
2768
3316
|
* @example
|
|
2769
3317
|
* ```typescript
|
|
2770
|
-
* import {
|
|
2771
|
-
*
|
|
2772
|
-
* const result = errorDetailsSchema.safeParse({
|
|
2773
|
-
* code: 1001,
|
|
2774
|
-
* name: 'NETWORK_MISMATCH',
|
|
2775
|
-
* recoverability: 'FATAL',
|
|
2776
|
-
* message: 'Source and destination networks must be different'
|
|
2777
|
-
* })
|
|
3318
|
+
* import { isKitError } from '@core/errors'
|
|
2778
3319
|
*
|
|
2779
|
-
*
|
|
2780
|
-
*
|
|
3320
|
+
* try {
|
|
3321
|
+
* await kit.bridge(params)
|
|
3322
|
+
* } catch (error) {
|
|
3323
|
+
* if (isKitError(error)) {
|
|
3324
|
+
* // TypeScript knows this is KitError
|
|
3325
|
+
* console.log(`Structured error: ${error.name} (${error.code})`)
|
|
3326
|
+
* } else {
|
|
3327
|
+
* console.log('Regular error:', error)
|
|
3328
|
+
* }
|
|
2781
3329
|
* }
|
|
2782
3330
|
* ```
|
|
2783
3331
|
*/
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
.number()
|
|
2788
|
-
.int('Error code must be an integer')
|
|
2789
|
-
.min(1000, 'Error code must be within valid range (1000+)')
|
|
2790
|
-
.max(1099, 'Error code must be within valid range (1099 max)'),
|
|
2791
|
-
/** Human-readable ID (e.g., "NETWORK_MISMATCH") */
|
|
2792
|
-
name: z
|
|
2793
|
-
.string()
|
|
2794
|
-
.min(1, 'Error name must be a non-empty string')
|
|
2795
|
-
.regex(/^[A-Z_][A-Z0-9_]*$/, 'Error name must match pattern: ^[A-Z_][A-Z0-9_]*$'),
|
|
2796
|
-
/** Error handling strategy */
|
|
2797
|
-
recoverability: z.enum(RECOVERABILITY_ARRAY, {
|
|
2798
|
-
errorMap: () => ({
|
|
2799
|
-
message: 'Recoverability must be one of: RETRYABLE, RESUMABLE, FATAL',
|
|
2800
|
-
}),
|
|
2801
|
-
}),
|
|
2802
|
-
/** User-friendly explanation with network context */
|
|
2803
|
-
message: z
|
|
2804
|
-
.string()
|
|
2805
|
-
.min(1, 'Error message must be a non-empty string')
|
|
2806
|
-
.max(500, 'Error message must be 500 characters or less'),
|
|
2807
|
-
/** Raw error details, context, or the original error that caused this one. */
|
|
2808
|
-
cause: z
|
|
2809
|
-
.object({
|
|
2810
|
-
/** Free-form error payload from underlying system */
|
|
2811
|
-
trace: z.unknown().optional(),
|
|
2812
|
-
})
|
|
2813
|
-
.optional(),
|
|
2814
|
-
});
|
|
2815
|
-
|
|
3332
|
+
function isKitError(error) {
|
|
3333
|
+
return error instanceof KitError;
|
|
3334
|
+
}
|
|
2816
3335
|
/**
|
|
2817
|
-
*
|
|
3336
|
+
* Checks if an error is a KitError with FATAL recoverability.
|
|
2818
3337
|
*
|
|
2819
|
-
*
|
|
2820
|
-
*
|
|
2821
|
-
*
|
|
3338
|
+
* FATAL errors indicate issues that cannot be resolved through retries,
|
|
3339
|
+
* such as invalid inputs, configuration problems, or business rule
|
|
3340
|
+
* violations. These errors require user intervention to fix.
|
|
3341
|
+
*
|
|
3342
|
+
* @param error - Unknown error to check
|
|
3343
|
+
* @returns True if error is a KitError with FATAL recoverability
|
|
2822
3344
|
*
|
|
2823
3345
|
* @example
|
|
2824
3346
|
* ```typescript
|
|
2825
|
-
* import {
|
|
3347
|
+
* import { isFatalError } from '@core/errors'
|
|
2826
3348
|
*
|
|
2827
3349
|
* try {
|
|
2828
|
-
*
|
|
2829
|
-
* code: 1001,
|
|
2830
|
-
* name: 'NETWORK_MISMATCH',
|
|
2831
|
-
* recoverability: 'FATAL',
|
|
2832
|
-
* message: 'Source and destination networks must be different'
|
|
2833
|
-
* })
|
|
3350
|
+
* await kit.bridge(params)
|
|
2834
3351
|
* } catch (error) {
|
|
2835
|
-
*
|
|
3352
|
+
* if (isFatalError(error)) {
|
|
3353
|
+
* // Show user-friendly error message - don't retry
|
|
3354
|
+
* showUserError(error.message)
|
|
3355
|
+
* }
|
|
2836
3356
|
* }
|
|
2837
3357
|
* ```
|
|
2838
3358
|
*/
|
|
2839
|
-
function
|
|
2840
|
-
|
|
2841
|
-
if (!result.success) {
|
|
2842
|
-
const issues = result.error.issues
|
|
2843
|
-
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
2844
|
-
.join(', ');
|
|
2845
|
-
throw new TypeError(`Invalid ErrorDetails: ${issues}`);
|
|
2846
|
-
}
|
|
2847
|
-
return result.data;
|
|
3359
|
+
function isFatalError(error) {
|
|
3360
|
+
return isKitError(error) && error.recoverability === 'FATAL';
|
|
2848
3361
|
}
|
|
2849
|
-
|
|
2850
3362
|
/**
|
|
2851
|
-
*
|
|
3363
|
+
* Checks if an error is a KitError with RETRYABLE recoverability.
|
|
2852
3364
|
*
|
|
2853
|
-
*
|
|
2854
|
-
*
|
|
2855
|
-
*
|
|
2856
|
-
*
|
|
3365
|
+
* RETRYABLE errors indicate transient failures that may succeed on
|
|
3366
|
+
* subsequent attempts, such as network timeouts or temporary service
|
|
3367
|
+
* unavailability. These errors are safe to retry after a delay.
|
|
3368
|
+
*
|
|
3369
|
+
* @param error - Unknown error to check
|
|
3370
|
+
* @returns True if error is a KitError with RETRYABLE recoverability
|
|
2857
3371
|
*
|
|
2858
3372
|
* @example
|
|
2859
3373
|
* ```typescript
|
|
2860
|
-
* import {
|
|
2861
|
-
*
|
|
2862
|
-
* const error = new KitError({
|
|
2863
|
-
* code: 1001,
|
|
2864
|
-
* name: 'INPUT_NETWORK_MISMATCH',
|
|
2865
|
-
* recoverability: 'FATAL',
|
|
2866
|
-
* message: 'Cannot bridge between mainnet and testnet'
|
|
2867
|
-
* })
|
|
3374
|
+
* import { isRetryableError } from '@core/errors'
|
|
2868
3375
|
*
|
|
2869
|
-
*
|
|
2870
|
-
*
|
|
2871
|
-
*
|
|
3376
|
+
* try {
|
|
3377
|
+
* await kit.bridge(params)
|
|
3378
|
+
* } catch (error) {
|
|
3379
|
+
* if (isRetryableError(error)) {
|
|
3380
|
+
* // Implement retry logic with exponential backoff
|
|
3381
|
+
* setTimeout(() => retryOperation(), 5000)
|
|
3382
|
+
* }
|
|
2872
3383
|
* }
|
|
2873
3384
|
* ```
|
|
3385
|
+
*/
|
|
3386
|
+
function isRetryableError(error) {
|
|
3387
|
+
return isKitError(error) && error.recoverability === 'RETRYABLE';
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Type guard to check if error is KitError with INPUT type.
|
|
3391
|
+
*
|
|
3392
|
+
* INPUT errors represent validation failures, invalid parameters,
|
|
3393
|
+
* or user input problems. These errors are always FATAL and require
|
|
3394
|
+
* the user to correct their input before retrying.
|
|
3395
|
+
*
|
|
3396
|
+
* @param error - Unknown error to check
|
|
3397
|
+
* @returns True if error is KitError with INPUT type
|
|
2874
3398
|
*
|
|
2875
3399
|
* @example
|
|
2876
3400
|
* ```typescript
|
|
2877
|
-
* import {
|
|
3401
|
+
* import { isInputError } from '@core/errors'
|
|
2878
3402
|
*
|
|
2879
|
-
*
|
|
2880
|
-
*
|
|
2881
|
-
*
|
|
2882
|
-
*
|
|
2883
|
-
*
|
|
2884
|
-
*
|
|
2885
|
-
* cause: {
|
|
2886
|
-
* trace: { providedAmount: -100, minimumAmount: 0 }
|
|
3403
|
+
* try {
|
|
3404
|
+
* await kit.bridge(params)
|
|
3405
|
+
* } catch (error) {
|
|
3406
|
+
* if (isInputError(error)) {
|
|
3407
|
+
* console.log('Validation error:', error.message)
|
|
3408
|
+
* showValidationUI()
|
|
2887
3409
|
* }
|
|
2888
|
-
* }
|
|
2889
|
-
*
|
|
2890
|
-
* throw error
|
|
3410
|
+
* }
|
|
2891
3411
|
* ```
|
|
2892
3412
|
*/
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
code;
|
|
2896
|
-
/** Human-readable ID (e.g., "NETWORK_MISMATCH") */
|
|
2897
|
-
name;
|
|
2898
|
-
/** Error handling strategy */
|
|
2899
|
-
recoverability;
|
|
2900
|
-
/** Raw error details, context, or the original error that caused this one. */
|
|
2901
|
-
cause;
|
|
2902
|
-
/**
|
|
2903
|
-
* Create a new KitError instance.
|
|
2904
|
-
*
|
|
2905
|
-
* @param details - The error details object containing all required properties.
|
|
2906
|
-
* @throws \{TypeError\} When details parameter is missing or invalid.
|
|
2907
|
-
*/
|
|
2908
|
-
constructor(details) {
|
|
2909
|
-
// Validate input at runtime for JavaScript consumers using Zod
|
|
2910
|
-
const validatedDetails = validateErrorDetails(details);
|
|
2911
|
-
super(validatedDetails.message);
|
|
2912
|
-
// Set properties as readonly at runtime
|
|
2913
|
-
Object.defineProperties(this, {
|
|
2914
|
-
name: {
|
|
2915
|
-
value: validatedDetails.name,
|
|
2916
|
-
writable: false,
|
|
2917
|
-
enumerable: true,
|
|
2918
|
-
configurable: false,
|
|
2919
|
-
},
|
|
2920
|
-
code: {
|
|
2921
|
-
value: validatedDetails.code,
|
|
2922
|
-
writable: false,
|
|
2923
|
-
enumerable: true,
|
|
2924
|
-
configurable: false,
|
|
2925
|
-
},
|
|
2926
|
-
recoverability: {
|
|
2927
|
-
value: validatedDetails.recoverability,
|
|
2928
|
-
writable: false,
|
|
2929
|
-
enumerable: true,
|
|
2930
|
-
configurable: false,
|
|
2931
|
-
},
|
|
2932
|
-
...(validatedDetails.cause && {
|
|
2933
|
-
cause: {
|
|
2934
|
-
value: validatedDetails.cause,
|
|
2935
|
-
writable: false,
|
|
2936
|
-
enumerable: true,
|
|
2937
|
-
configurable: false,
|
|
2938
|
-
},
|
|
2939
|
-
}),
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
3413
|
+
function isInputError(error) {
|
|
3414
|
+
return isKitError(error) && error.type === ERROR_TYPES.INPUT;
|
|
2942
3415
|
}
|
|
2943
|
-
|
|
2944
3416
|
/**
|
|
2945
|
-
*
|
|
2946
|
-
* INPUT errors represent validation failures and invalid parameters.
|
|
2947
|
-
*/
|
|
2948
|
-
const INPUT_ERROR_CODE_MIN = 1000;
|
|
2949
|
-
/**
|
|
2950
|
-
* Maximum error code for INPUT type errors (exclusive).
|
|
2951
|
-
* INPUT error codes range from 1000 to 1099 inclusive.
|
|
2952
|
-
*/
|
|
2953
|
-
const INPUT_ERROR_CODE_MAX = 1100;
|
|
2954
|
-
/**
|
|
2955
|
-
* Standardized error definitions for INPUT type errors.
|
|
2956
|
-
*
|
|
2957
|
-
* Each entry combines the numeric error code with its corresponding
|
|
2958
|
-
* string name to ensure consistency when creating error instances.
|
|
3417
|
+
* Safely extracts error message from any error type.
|
|
2959
3418
|
*
|
|
2960
|
-
*
|
|
2961
|
-
*
|
|
2962
|
-
*
|
|
3419
|
+
* This utility handles different error types gracefully, extracting
|
|
3420
|
+
* meaningful messages from Error instances, string errors, or providing
|
|
3421
|
+
* a fallback for unknown error types. Never throws.
|
|
2963
3422
|
*
|
|
3423
|
+
* @param error - Unknown error to extract message from
|
|
3424
|
+
* @returns Error message string, or fallback message
|
|
2964
3425
|
*
|
|
2965
3426
|
* @example
|
|
2966
3427
|
* ```typescript
|
|
2967
|
-
* import {
|
|
2968
|
-
*
|
|
2969
|
-
* const error = new KitError({
|
|
2970
|
-
* ...InputError.NETWORK_MISMATCH,
|
|
2971
|
-
* recoverability: 'FATAL',
|
|
2972
|
-
* message: 'Source and destination networks must be different'
|
|
2973
|
-
* })
|
|
3428
|
+
* import { getErrorMessage } from '@core/errors'
|
|
2974
3429
|
*
|
|
2975
|
-
*
|
|
2976
|
-
*
|
|
2977
|
-
*
|
|
3430
|
+
* try {
|
|
3431
|
+
* await riskyOperation()
|
|
3432
|
+
* } catch (error) {
|
|
3433
|
+
* const message = getErrorMessage(error)
|
|
3434
|
+
* console.log('Error occurred:', message)
|
|
3435
|
+
* // Works with Error, KitError, string, or any other type
|
|
3436
|
+
* }
|
|
2978
3437
|
* ```
|
|
2979
3438
|
*/
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
name: 'INPUT_INVALID_AMOUNT',
|
|
2990
|
-
},
|
|
2991
|
-
/** Unsupported or invalid bridge route configuration */
|
|
2992
|
-
UNSUPPORTED_ROUTE: {
|
|
2993
|
-
code: 1003,
|
|
2994
|
-
name: 'INPUT_UNSUPPORTED_ROUTE',
|
|
2995
|
-
},
|
|
2996
|
-
/** Invalid wallet or contract address format */
|
|
2997
|
-
INVALID_ADDRESS: {
|
|
2998
|
-
code: 1004,
|
|
2999
|
-
name: 'INPUT_INVALID_ADDRESS',
|
|
3000
|
-
},
|
|
3001
|
-
/** Invalid or unsupported chain identifier */
|
|
3002
|
-
INVALID_CHAIN: {
|
|
3003
|
-
code: 1005,
|
|
3004
|
-
name: 'INPUT_INVALID_CHAIN',
|
|
3005
|
-
},
|
|
3006
|
-
/** General validation failure for complex validation rules */
|
|
3007
|
-
VALIDATION_FAILED: {
|
|
3008
|
-
code: 1098,
|
|
3009
|
-
name: 'INPUT_VALIDATION_FAILED',
|
|
3010
|
-
},
|
|
3011
|
-
};
|
|
3012
|
-
|
|
3439
|
+
function getErrorMessage(error) {
|
|
3440
|
+
if (error instanceof Error) {
|
|
3441
|
+
return error.message;
|
|
3442
|
+
}
|
|
3443
|
+
if (typeof error === 'string') {
|
|
3444
|
+
return error;
|
|
3445
|
+
}
|
|
3446
|
+
return 'An unknown error occurred';
|
|
3447
|
+
}
|
|
3013
3448
|
/**
|
|
3014
|
-
*
|
|
3449
|
+
* Gets the error code from a KitError, or null if not applicable.
|
|
3015
3450
|
*
|
|
3016
|
-
* This
|
|
3017
|
-
*
|
|
3018
|
-
*
|
|
3451
|
+
* This utility safely extracts the numeric error code from KitError
|
|
3452
|
+
* instances, returning null for non-KitError types. Useful for
|
|
3453
|
+
* programmatic error handling based on specific error codes.
|
|
3019
3454
|
*
|
|
3020
|
-
* @param
|
|
3021
|
-
* @
|
|
3022
|
-
* @returns KitError with specific network mismatch details
|
|
3455
|
+
* @param error - Unknown error to extract code from
|
|
3456
|
+
* @returns Error code number, or null if not a KitError
|
|
3023
3457
|
*
|
|
3024
3458
|
* @example
|
|
3025
3459
|
* ```typescript
|
|
3026
|
-
* import {
|
|
3027
|
-
* import { Ethereum, BaseSepolia } from '@core/chains'
|
|
3460
|
+
* import { getErrorCode, InputError } from '@core/errors'
|
|
3028
3461
|
*
|
|
3029
|
-
*
|
|
3030
|
-
*
|
|
3031
|
-
*
|
|
3462
|
+
* try {
|
|
3463
|
+
* await kit.bridge(params)
|
|
3464
|
+
* } catch (error) {
|
|
3465
|
+
* const code = getErrorCode(error)
|
|
3466
|
+
* if (code === InputError.NETWORK_MISMATCH.code) {
|
|
3467
|
+
* // Handle network mismatch specifically
|
|
3468
|
+
* showNetworkMismatchHelp()
|
|
3469
|
+
* }
|
|
3470
|
+
* }
|
|
3032
3471
|
* ```
|
|
3033
3472
|
*/
|
|
3034
|
-
function
|
|
3035
|
-
|
|
3036
|
-
const destNetworkType = destChain.isTestnet ? 'testnet' : 'mainnet';
|
|
3037
|
-
const errorDetails = {
|
|
3038
|
-
...InputError.NETWORK_MISMATCH,
|
|
3039
|
-
recoverability: 'FATAL',
|
|
3040
|
-
message: `Cannot bridge between ${sourceChain.name} (${sourceNetworkType}) and ${destChain.name} (${destNetworkType}). Source and destination networks must both be testnet or both be mainnet.`,
|
|
3041
|
-
cause: {
|
|
3042
|
-
trace: { sourceChain: sourceChain.name, destChain: destChain.name },
|
|
3043
|
-
},
|
|
3044
|
-
};
|
|
3045
|
-
return new KitError(errorDetails);
|
|
3473
|
+
function getErrorCode(error) {
|
|
3474
|
+
return isKitError(error) ? error.code : null;
|
|
3046
3475
|
}
|
|
3476
|
+
|
|
3047
3477
|
/**
|
|
3048
|
-
*
|
|
3478
|
+
* Validates if an address format is correct for the specified chain.
|
|
3049
3479
|
*
|
|
3050
|
-
* This
|
|
3051
|
-
*
|
|
3480
|
+
* This function checks the explicit chain `type` property first, then falls back
|
|
3481
|
+
* to name-based matching to determine whether to validate as a Solana or EVM address.
|
|
3052
3482
|
*
|
|
3053
|
-
* @param
|
|
3054
|
-
* @param
|
|
3055
|
-
* @returns
|
|
3483
|
+
* @param address - The address to validate
|
|
3484
|
+
* @param chain - The chain identifier or chain definition (cannot be null)
|
|
3485
|
+
* @returns True if the address format is valid for the chain, false otherwise
|
|
3056
3486
|
*
|
|
3057
3487
|
* @example
|
|
3058
3488
|
* ```typescript
|
|
3059
|
-
* import {
|
|
3489
|
+
* import { isValidAddressForChain } from '@core/errors'
|
|
3060
3490
|
*
|
|
3061
|
-
*
|
|
3062
|
-
*
|
|
3491
|
+
* // EVM address validation
|
|
3492
|
+
* isValidAddressForChain('0x742d35Cc6634C0532925a3b8D0C0C1C4C5C6C7C8', 'Ethereum')
|
|
3493
|
+
* // → true
|
|
3494
|
+
*
|
|
3495
|
+
* // Solana address validation
|
|
3496
|
+
* isValidAddressForChain('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', { name: 'Solana', type: 'solana' })
|
|
3497
|
+
* // → true
|
|
3498
|
+
*
|
|
3499
|
+
* // Invalid format
|
|
3500
|
+
* isValidAddressForChain('invalid', 'Ethereum')
|
|
3501
|
+
* // → false
|
|
3063
3502
|
* ```
|
|
3064
3503
|
*/
|
|
3065
|
-
function
|
|
3066
|
-
const
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3504
|
+
function isValidAddressForChain(address, chain) {
|
|
3505
|
+
const chainType = typeof chain === 'object' ? chain.type : undefined;
|
|
3506
|
+
const name = typeof chain === 'string' ? chain : chain.name;
|
|
3507
|
+
// Use explicit chain type if available, fallback to name matching
|
|
3508
|
+
const isSolana = chainType !== undefined
|
|
3509
|
+
? chainType === 'solana'
|
|
3510
|
+
: name.toLowerCase().includes('solana');
|
|
3511
|
+
if (isSolana) {
|
|
3512
|
+
// Solana base58 address: 32-44 characters from base58 alphabet
|
|
3513
|
+
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
|
|
3514
|
+
}
|
|
3515
|
+
// EVM hex address: 0x followed by 40 hex characters
|
|
3516
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
3075
3517
|
}
|
|
3076
3518
|
/**
|
|
3077
|
-
*
|
|
3519
|
+
* Type guard to check if a value is an object.
|
|
3078
3520
|
*
|
|
3079
|
-
*
|
|
3080
|
-
*
|
|
3521
|
+
* @param val - The value to check
|
|
3522
|
+
* @returns True if the value is a non-null object
|
|
3523
|
+
*/
|
|
3524
|
+
function isObject(val) {
|
|
3525
|
+
return typeof val === 'object' && val !== null;
|
|
3526
|
+
}
|
|
3527
|
+
/**
|
|
3528
|
+
* Type guard to check if a value is a chain with isTestnet property.
|
|
3081
3529
|
*
|
|
3082
|
-
* @param
|
|
3083
|
-
* @
|
|
3084
|
-
* @returns KitError with amount details and validation rule
|
|
3530
|
+
* @param chain - The value to check
|
|
3531
|
+
* @returns True if the value is a chain object with name and isTestnet properties
|
|
3085
3532
|
*
|
|
3086
3533
|
* @example
|
|
3087
3534
|
* ```typescript
|
|
3088
|
-
* import {
|
|
3535
|
+
* import { isChainWithTestnet } from '@core/errors'
|
|
3089
3536
|
*
|
|
3090
|
-
*
|
|
3091
|
-
* //
|
|
3537
|
+
* const chain1 = { name: 'Ethereum', isTestnet: false }
|
|
3538
|
+
* isChainWithTestnet(chain1) // → true
|
|
3539
|
+
*
|
|
3540
|
+
* const chain2 = { name: 'Ethereum' }
|
|
3541
|
+
* isChainWithTestnet(chain2) // → false
|
|
3092
3542
|
*
|
|
3093
|
-
*
|
|
3094
|
-
* //
|
|
3543
|
+
* const chain3 = 'Ethereum'
|
|
3544
|
+
* isChainWithTestnet(chain3) // → false
|
|
3095
3545
|
* ```
|
|
3096
3546
|
*/
|
|
3097
|
-
function
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
trace: { amount, reason },
|
|
3104
|
-
},
|
|
3105
|
-
};
|
|
3106
|
-
return new KitError(errorDetails);
|
|
3547
|
+
function isChainWithTestnet(chain) {
|
|
3548
|
+
return (isObject(chain) &&
|
|
3549
|
+
'isTestnet' in chain &&
|
|
3550
|
+
typeof chain['isTestnet'] === 'boolean' &&
|
|
3551
|
+
'name' in chain &&
|
|
3552
|
+
typeof chain['name'] === 'string');
|
|
3107
3553
|
}
|
|
3108
3554
|
/**
|
|
3109
|
-
*
|
|
3555
|
+
* Gets chain identifier from toData object.
|
|
3110
3556
|
*
|
|
3111
|
-
*
|
|
3112
|
-
* format for the specified chain.
|
|
3557
|
+
* Looks for chain in context.chain first, then falls back to direct chain property.
|
|
3113
3558
|
*
|
|
3114
|
-
* @param
|
|
3115
|
-
* @
|
|
3116
|
-
|
|
3117
|
-
|
|
3559
|
+
* @param toData - The destination data object
|
|
3560
|
+
* @returns The chain identifier or null
|
|
3561
|
+
*/
|
|
3562
|
+
function getChainFromToData(toData) {
|
|
3563
|
+
const context = toData['context'];
|
|
3564
|
+
const chain = context?.['chain'] ?? toData['chain'];
|
|
3565
|
+
return chain;
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* Gets chain name from params object.
|
|
3118
3569
|
*
|
|
3119
|
-
* @
|
|
3120
|
-
*
|
|
3121
|
-
|
|
3570
|
+
* @param params - The parameters object
|
|
3571
|
+
* @returns The chain name as a string, or null if not found
|
|
3572
|
+
*/
|
|
3573
|
+
function getChainName(params) {
|
|
3574
|
+
if (isObject(params)) {
|
|
3575
|
+
const chain = params['chain'];
|
|
3576
|
+
if (typeof chain === 'string') {
|
|
3577
|
+
return chain;
|
|
3578
|
+
}
|
|
3579
|
+
if (isObject(chain) && 'name' in chain) {
|
|
3580
|
+
return chain.name;
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
return null;
|
|
3584
|
+
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Extracts from data from params object.
|
|
3122
3587
|
*
|
|
3123
|
-
*
|
|
3124
|
-
* // Message: "Invalid address '0x123' for Ethereum. Expected 42-character hex string starting with 0x."
|
|
3588
|
+
* Supports both 'from' and 'source' property names.
|
|
3125
3589
|
*
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3128
|
-
* ```
|
|
3590
|
+
* @param params - The parameters object
|
|
3591
|
+
* @returns The from/source data or undefined
|
|
3129
3592
|
*/
|
|
3130
|
-
function
|
|
3131
|
-
|
|
3132
|
-
...InputError.INVALID_ADDRESS,
|
|
3133
|
-
recoverability: 'FATAL',
|
|
3134
|
-
message: `Invalid address '${address}' for ${chain}. Expected ${expectedFormat}.`,
|
|
3135
|
-
cause: {
|
|
3136
|
-
trace: { address, chain, expectedFormat },
|
|
3137
|
-
},
|
|
3138
|
-
};
|
|
3139
|
-
return new KitError(errorDetails);
|
|
3593
|
+
function extractFromData(params) {
|
|
3594
|
+
return (params['from'] ?? params['source']);
|
|
3140
3595
|
}
|
|
3141
3596
|
/**
|
|
3142
|
-
*
|
|
3597
|
+
* Extracts to data from params object.
|
|
3143
3598
|
*
|
|
3144
|
-
*
|
|
3145
|
-
* configuration or is not supported for the operation.
|
|
3599
|
+
* Supports both 'to' and 'destination' property names.
|
|
3146
3600
|
*
|
|
3147
|
-
* @param
|
|
3148
|
-
* @
|
|
3149
|
-
|
|
3601
|
+
* @param params - The parameters object
|
|
3602
|
+
* @returns The to/destination data or undefined
|
|
3603
|
+
*/
|
|
3604
|
+
function extractToData(params) {
|
|
3605
|
+
return (params['to'] ?? params['destination']);
|
|
3606
|
+
}
|
|
3607
|
+
/**
|
|
3608
|
+
* Gets address from params object using a dot-separated path.
|
|
3609
|
+
*
|
|
3610
|
+
* Traverses nested objects to extract the address value at the specified path.
|
|
3611
|
+
*
|
|
3612
|
+
* @param params - The parameters object
|
|
3613
|
+
* @param path - Dot-separated path to extract address from (e.g., 'to.recipientAddress')
|
|
3614
|
+
* @returns The address as a string, or 'unknown' if not found
|
|
3150
3615
|
*
|
|
3151
3616
|
* @example
|
|
3152
3617
|
* ```typescript
|
|
3153
|
-
*
|
|
3154
|
-
*
|
|
3155
|
-
*
|
|
3156
|
-
* // Message: "Invalid chain 'UnknownChain': Chain is not supported by this bridge"
|
|
3618
|
+
* // Extract from specific path
|
|
3619
|
+
* getAddressFromParams(params, 'to.recipientAddress')
|
|
3620
|
+
* // Returns the value at params.to.recipientAddress
|
|
3157
3621
|
* ```
|
|
3158
3622
|
*/
|
|
3159
|
-
function
|
|
3160
|
-
const
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3623
|
+
function getAddressFromParams(params, path) {
|
|
3624
|
+
const parts = path.split('.');
|
|
3625
|
+
let current = params;
|
|
3626
|
+
for (const part of parts) {
|
|
3627
|
+
if (current !== null &&
|
|
3628
|
+
current !== undefined &&
|
|
3629
|
+
typeof current === 'object' &&
|
|
3630
|
+
part in current) {
|
|
3631
|
+
current = current[part];
|
|
3632
|
+
}
|
|
3633
|
+
else {
|
|
3634
|
+
return 'unknown';
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
return typeof current === 'string' ? current : 'unknown';
|
|
3638
|
+
}
|
|
3639
|
+
/**
|
|
3640
|
+
* Gets chain identifier from params object.
|
|
3641
|
+
*
|
|
3642
|
+
* Looks for chain in to.context.chain, to.chain, or direct chain property.
|
|
3643
|
+
*
|
|
3644
|
+
* @param params - The parameters object
|
|
3645
|
+
* @returns The chain identifier or null
|
|
3646
|
+
*/
|
|
3647
|
+
function getChainFromParams(params) {
|
|
3648
|
+
const to = extractToData(params);
|
|
3649
|
+
const chain = to?.['chain'] ?? params['chain'];
|
|
3650
|
+
return chain;
|
|
3169
3651
|
}
|
|
3170
3652
|
|
|
3171
3653
|
/**
|
|
3172
|
-
*
|
|
3173
|
-
*
|
|
3174
|
-
* This guard enables TypeScript to narrow the type from `unknown` to
|
|
3175
|
-
* `KitError`, providing access to structured error properties like
|
|
3176
|
-
* code, name, and recoverability.
|
|
3177
|
-
*
|
|
3178
|
-
* @param error - Unknown error to check
|
|
3179
|
-
* @returns True if error is KitError with proper type narrowing
|
|
3654
|
+
* Converts a Zod validation error into a specific KitError instance using structured pattern matching.
|
|
3180
3655
|
*
|
|
3181
|
-
*
|
|
3182
|
-
*
|
|
3183
|
-
*
|
|
3656
|
+
* This function inspects Zod's error details (path, code, message) and delegates each issue
|
|
3657
|
+
* to specialized handlers that generate domain-specific KitError objects. It leverages
|
|
3658
|
+
* Zod's error codes and path information for robust matching, avoiding fragile string checks.
|
|
3184
3659
|
*
|
|
3185
|
-
*
|
|
3186
|
-
*
|
|
3187
|
-
*
|
|
3188
|
-
* if (isKitError(error)) {
|
|
3189
|
-
* // TypeScript knows this is KitError
|
|
3190
|
-
* console.log(`Structured error: ${error.name} (${error.code})`)
|
|
3191
|
-
* } else {
|
|
3192
|
-
* console.log('Regular error:', error)
|
|
3193
|
-
* }
|
|
3194
|
-
* }
|
|
3195
|
-
* ```
|
|
3660
|
+
* @param zodError - The Zod validation error containing one or more issues
|
|
3661
|
+
* @param params - The original parameters that failed validation (used to extract invalid values)
|
|
3662
|
+
* @returns A specific KitError instance with actionable error details
|
|
3196
3663
|
*/
|
|
3197
|
-
function
|
|
3198
|
-
|
|
3664
|
+
function convertZodErrorToStructured(zodError, params) {
|
|
3665
|
+
// Handle null/undefined params gracefully
|
|
3666
|
+
if (params === null || params === undefined) {
|
|
3667
|
+
return createValidationFailedError(zodError);
|
|
3668
|
+
}
|
|
3669
|
+
const paramsObj = params;
|
|
3670
|
+
const toData = extractToData(paramsObj);
|
|
3671
|
+
const fromData = extractFromData(paramsObj);
|
|
3672
|
+
for (const issue of zodError.issues) {
|
|
3673
|
+
const path = issue.path.join('.');
|
|
3674
|
+
const code = issue.code;
|
|
3675
|
+
// Try to handle specific error types
|
|
3676
|
+
const amountError = handleAmountError(path, code, issue.message, paramsObj);
|
|
3677
|
+
if (amountError)
|
|
3678
|
+
return amountError;
|
|
3679
|
+
const chainError = handleChainError(path, code, issue.message, fromData, toData);
|
|
3680
|
+
if (chainError)
|
|
3681
|
+
return chainError;
|
|
3682
|
+
const addressError = handleAddressError(path, code, issue.message, paramsObj);
|
|
3683
|
+
if (addressError)
|
|
3684
|
+
return addressError;
|
|
3685
|
+
}
|
|
3686
|
+
// Fallback
|
|
3687
|
+
return createValidationFailedError(zodError);
|
|
3199
3688
|
}
|
|
3200
3689
|
/**
|
|
3201
|
-
*
|
|
3202
|
-
*
|
|
3203
|
-
* FATAL errors indicate issues that cannot be resolved through retries,
|
|
3204
|
-
* such as invalid inputs, configuration problems, or business rule
|
|
3205
|
-
* violations. These errors require user intervention to fix.
|
|
3690
|
+
* Creates a generic validation failed error for null/undefined params or unmapped error cases.
|
|
3206
3691
|
*
|
|
3207
|
-
*
|
|
3208
|
-
*
|
|
3692
|
+
* This is a fallback handler used when:
|
|
3693
|
+
* - Parameters are null or undefined
|
|
3694
|
+
* - No specific error handler matches the Zod error
|
|
3695
|
+
* - The error doesn't fit into amount/chain/address categories
|
|
3209
3696
|
*
|
|
3210
|
-
*
|
|
3211
|
-
* ```typescript
|
|
3212
|
-
* import { isFatalError } from '@core/errors'
|
|
3697
|
+
* This function delegates to createValidationErrorFromZod with a generic "parameters" context.
|
|
3213
3698
|
*
|
|
3214
|
-
*
|
|
3215
|
-
*
|
|
3216
|
-
* } catch (error) {
|
|
3217
|
-
* if (isFatalError(error)) {
|
|
3218
|
-
* // Show user-friendly error message - don't retry
|
|
3219
|
-
* showUserError(error.message)
|
|
3220
|
-
* }
|
|
3221
|
-
* }
|
|
3222
|
-
* ```
|
|
3699
|
+
* @param zodError - The Zod validation error with all issues
|
|
3700
|
+
* @returns A generic KitError with INPUT_VALIDATION_FAILED code
|
|
3223
3701
|
*/
|
|
3224
|
-
function
|
|
3225
|
-
return
|
|
3702
|
+
function createValidationFailedError(zodError) {
|
|
3703
|
+
return createValidationErrorFromZod(zodError, 'parameters');
|
|
3226
3704
|
}
|
|
3705
|
+
const AMOUNT_FORMAT_ERROR_MESSAGE = 'Amount must be a numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals';
|
|
3227
3706
|
/**
|
|
3228
|
-
*
|
|
3229
|
-
*
|
|
3230
|
-
* RETRYABLE errors indicate transient failures that may succeed on
|
|
3231
|
-
* subsequent attempts, such as network timeouts or temporary service
|
|
3232
|
-
* unavailability. These errors are safe to retry after a delay.
|
|
3233
|
-
*
|
|
3234
|
-
* @param error - Unknown error to check
|
|
3235
|
-
* @returns True if error is a KitError with RETRYABLE recoverability
|
|
3707
|
+
* Handles amount-related validation errors from Zod.
|
|
3236
3708
|
*
|
|
3237
|
-
*
|
|
3238
|
-
*
|
|
3239
|
-
*
|
|
3709
|
+
* Checks if the validation error path includes 'amount' and attempts
|
|
3710
|
+
* to convert generic Zod errors into specific KitError instances with
|
|
3711
|
+
* actionable messages. Delegates to specialized handlers in order of specificity:
|
|
3712
|
+
* 1. Negative amount errors (too_small)
|
|
3713
|
+
* 2. Custom validation errors (decimal places, numeric string)
|
|
3714
|
+
* 3. Invalid string format errors
|
|
3715
|
+
* 4. Invalid type errors
|
|
3240
3716
|
*
|
|
3241
|
-
*
|
|
3242
|
-
*
|
|
3243
|
-
*
|
|
3244
|
-
*
|
|
3245
|
-
*
|
|
3246
|
-
* setTimeout(() => retryOperation(), 5000)
|
|
3247
|
-
* }
|
|
3248
|
-
* }
|
|
3249
|
-
* ```
|
|
3717
|
+
* @param path - The Zod error path (e.g., 'amount' or 'config.amount')
|
|
3718
|
+
* @param code - The Zod error code (e.g., 'too_small', 'invalid_string', 'custom')
|
|
3719
|
+
* @param message - The original Zod error message
|
|
3720
|
+
* @param paramsObj - The original params object for extracting the invalid amount value
|
|
3721
|
+
* @returns KitError with INPUT_INVALID_AMOUNT code if this is an amount error, null otherwise
|
|
3250
3722
|
*/
|
|
3251
|
-
function
|
|
3252
|
-
|
|
3723
|
+
function handleAmountError(path, code, message, paramsObj) {
|
|
3724
|
+
if (!path.includes('amount'))
|
|
3725
|
+
return null;
|
|
3726
|
+
const amount = typeof paramsObj['amount'] === 'string' ? paramsObj['amount'] : 'unknown';
|
|
3727
|
+
// Try different error handlers in order of specificity
|
|
3728
|
+
const negativeError = handleNegativeAmountError(code, message, amount);
|
|
3729
|
+
if (negativeError)
|
|
3730
|
+
return negativeError;
|
|
3731
|
+
const customError = handleCustomAmountError(code, message, amount);
|
|
3732
|
+
if (customError)
|
|
3733
|
+
return customError;
|
|
3734
|
+
const stringFormatError = handleInvalidStringAmountError(code, message, amount);
|
|
3735
|
+
if (stringFormatError)
|
|
3736
|
+
return stringFormatError;
|
|
3737
|
+
const typeError = handleInvalidTypeAmountError(code, amount);
|
|
3738
|
+
if (typeError)
|
|
3739
|
+
return typeError;
|
|
3740
|
+
return null;
|
|
3253
3741
|
}
|
|
3254
3742
|
/**
|
|
3255
|
-
*
|
|
3256
|
-
*
|
|
3257
|
-
* INPUT errors have error codes in the 1000-1099 range and represent
|
|
3258
|
-
* validation failures, invalid parameters, or user input problems.
|
|
3259
|
-
* These errors are always FATAL and require the user to correct their
|
|
3260
|
-
* input before retrying.
|
|
3743
|
+
* Handles negative or too-small amount validation errors.
|
|
3261
3744
|
*
|
|
3262
|
-
*
|
|
3263
|
-
*
|
|
3745
|
+
* Detects Zod 'too_small' error codes or messages containing 'greater than'
|
|
3746
|
+
* and creates a specific error indicating the amount must be positive.
|
|
3264
3747
|
*
|
|
3265
|
-
* @
|
|
3266
|
-
*
|
|
3267
|
-
*
|
|
3268
|
-
*
|
|
3748
|
+
* @param code - The Zod error code
|
|
3749
|
+
* @param message - The Zod error message
|
|
3750
|
+
* @param amount - The invalid amount value as a string
|
|
3751
|
+
* @returns KitError if this is a negative/too-small amount error, null otherwise
|
|
3752
|
+
*/
|
|
3753
|
+
function handleNegativeAmountError(code, message, amount) {
|
|
3754
|
+
if (code === 'too_small' || message.includes('greater than')) {
|
|
3755
|
+
return createInvalidAmountError(amount, 'Amount must be greater than 0');
|
|
3756
|
+
}
|
|
3757
|
+
return null;
|
|
3758
|
+
}
|
|
3759
|
+
/**
|
|
3760
|
+
* Handles custom Zod refinement validation errors for amounts.
|
|
3269
3761
|
*
|
|
3270
|
-
*
|
|
3271
|
-
*
|
|
3272
|
-
*
|
|
3762
|
+
* Processes Zod 'custom' error codes from .refine() validators and matches
|
|
3763
|
+
* against known patterns:
|
|
3764
|
+
* - 'non-negative' - amount must be \>= 0
|
|
3765
|
+
* - 'decimal places' - too many decimal places
|
|
3766
|
+
* - 'numeric string' - value is not a valid numeric string
|
|
3273
3767
|
*
|
|
3274
|
-
*
|
|
3275
|
-
*
|
|
3276
|
-
*
|
|
3277
|
-
*
|
|
3278
|
-
* console.log(`Input error: ${error.message} (code: ${error.code})`)
|
|
3279
|
-
* }
|
|
3280
|
-
* }
|
|
3281
|
-
* }
|
|
3282
|
-
* ```
|
|
3768
|
+
* @param code - The Zod error code (must be 'custom')
|
|
3769
|
+
* @param message - The custom error message from the refinement
|
|
3770
|
+
* @param amount - The invalid amount value as a string
|
|
3771
|
+
* @returns KitError with specific message if pattern matches, null otherwise
|
|
3283
3772
|
*/
|
|
3284
|
-
function
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3773
|
+
function handleCustomAmountError(code, message, amount) {
|
|
3774
|
+
if (code !== 'custom')
|
|
3775
|
+
return null;
|
|
3776
|
+
if (message.includes('non-negative')) {
|
|
3777
|
+
return createInvalidAmountError(amount, 'Amount must be non-negative');
|
|
3778
|
+
}
|
|
3779
|
+
if (message.includes('greater than 0')) {
|
|
3780
|
+
return createInvalidAmountError(amount, 'Amount must be greater than 0');
|
|
3781
|
+
}
|
|
3782
|
+
if (message.includes('decimal places')) {
|
|
3783
|
+
return createInvalidAmountError(amount, message);
|
|
3784
|
+
}
|
|
3785
|
+
if (message.includes('numeric string')) {
|
|
3786
|
+
return createInvalidAmountError(amount, AMOUNT_FORMAT_ERROR_MESSAGE);
|
|
3787
|
+
}
|
|
3788
|
+
return null;
|
|
3288
3789
|
}
|
|
3289
3790
|
/**
|
|
3290
|
-
*
|
|
3291
|
-
*
|
|
3292
|
-
* This utility handles different error types gracefully, extracting
|
|
3293
|
-
* meaningful messages from Error instances, string errors, or providing
|
|
3294
|
-
* a fallback for unknown error types. Never throws.
|
|
3791
|
+
* Handles Zod 'invalid_string' errors for amount values.
|
|
3295
3792
|
*
|
|
3296
|
-
*
|
|
3297
|
-
*
|
|
3793
|
+
* Processes string validation failures (e.g., regex mismatches) and categorizes them:
|
|
3794
|
+
* 1. Negative numbers that pass Number() but fail other validations
|
|
3795
|
+
* 2. Decimal places validation failures (too many decimal places)
|
|
3796
|
+
* 3. Numeric format validation failures (invalid characters, comma decimals, thousand separators)
|
|
3797
|
+
* 4. Generic invalid number strings (must use dot decimal notation)
|
|
3298
3798
|
*
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
3301
|
-
*
|
|
3799
|
+
* Note: The SDK enforces strict dot-decimal notation. Comma decimals (e.g., "1,5") and
|
|
3800
|
+
* thousand separators (e.g., "1,000.50") are not allowed. UI layers should normalize
|
|
3801
|
+
* locale-specific formats before passing values to the SDK.
|
|
3302
3802
|
*
|
|
3303
|
-
*
|
|
3304
|
-
*
|
|
3305
|
-
*
|
|
3306
|
-
*
|
|
3307
|
-
* console.log('Error occurred:', message)
|
|
3308
|
-
* // Works with Error, KitError, string, or any other type
|
|
3309
|
-
* }
|
|
3310
|
-
* ```
|
|
3803
|
+
* @param code - The Zod error code (must be 'invalid_string')
|
|
3804
|
+
* @param message - The Zod error message from string validation
|
|
3805
|
+
* @param amount - The invalid amount value as a string
|
|
3806
|
+
* @returns KitError with context-specific message if pattern matches, null otherwise
|
|
3311
3807
|
*/
|
|
3312
|
-
function
|
|
3313
|
-
if (
|
|
3314
|
-
return
|
|
3808
|
+
function handleInvalidStringAmountError(code, message, amount) {
|
|
3809
|
+
if (code !== 'invalid_string')
|
|
3810
|
+
return null;
|
|
3811
|
+
// Check for decimal places validation
|
|
3812
|
+
if (isDecimalPlacesError(message)) {
|
|
3813
|
+
return createInvalidAmountError(amount, 'Maximum supported decimal places: 6');
|
|
3315
3814
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3815
|
+
// Check for numeric format validation
|
|
3816
|
+
if (isNumericFormatError(message)) {
|
|
3817
|
+
return createInvalidAmountError(amount, AMOUNT_FORMAT_ERROR_MESSAGE);
|
|
3318
3818
|
}
|
|
3319
|
-
|
|
3819
|
+
// For other cases like 'abc', return specific error
|
|
3820
|
+
if (!message.includes('valid number format')) {
|
|
3821
|
+
return createInvalidAmountError(amount, AMOUNT_FORMAT_ERROR_MESSAGE);
|
|
3822
|
+
}
|
|
3823
|
+
return null;
|
|
3320
3824
|
}
|
|
3321
3825
|
/**
|
|
3322
|
-
*
|
|
3826
|
+
* Handles Zod 'invalid_type' errors for amount values.
|
|
3323
3827
|
*
|
|
3324
|
-
*
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
3828
|
+
* Triggered when the amount is not a string type (e.g., number, boolean, object).
|
|
3829
|
+
* Creates an error indicating the amount must be a string representation of a number
|
|
3830
|
+
* using strict dot-decimal notation (no comma decimals or thousand separators).
|
|
3327
3831
|
*
|
|
3328
|
-
* @param
|
|
3329
|
-
* @
|
|
3832
|
+
* @param code - The Zod error code (must be 'invalid_type')
|
|
3833
|
+
* @param amount - The invalid amount value (will be converted to string for error message)
|
|
3834
|
+
* @returns KitError if this is a type mismatch, null otherwise
|
|
3835
|
+
*/
|
|
3836
|
+
function handleInvalidTypeAmountError(code, amount) {
|
|
3837
|
+
if (code === 'invalid_type') {
|
|
3838
|
+
return createInvalidAmountError(amount, AMOUNT_FORMAT_ERROR_MESSAGE);
|
|
3839
|
+
}
|
|
3840
|
+
return null;
|
|
3841
|
+
}
|
|
3842
|
+
/**
|
|
3843
|
+
* Checks if an error message indicates a decimal places validation failure.
|
|
3330
3844
|
*
|
|
3331
|
-
*
|
|
3332
|
-
*
|
|
3333
|
-
* import { getErrorCode, InputError } from '@core/errors'
|
|
3845
|
+
* Looks for keywords like 'maximum', 'at most', and 'decimal places' to identify
|
|
3846
|
+
* errors related to too many decimal digits in an amount value.
|
|
3334
3847
|
*
|
|
3335
|
-
*
|
|
3336
|
-
*
|
|
3337
|
-
* } catch (error) {
|
|
3338
|
-
* const code = getErrorCode(error)
|
|
3339
|
-
* if (code === InputError.NETWORK_MISMATCH.code) {
|
|
3340
|
-
* // Handle network mismatch specifically
|
|
3341
|
-
* showNetworkMismatchHelp()
|
|
3342
|
-
* }
|
|
3343
|
-
* }
|
|
3344
|
-
* ```
|
|
3848
|
+
* @param message - The error message to analyze
|
|
3849
|
+
* @returns True if the message indicates a decimal places error, false otherwise
|
|
3345
3850
|
*/
|
|
3346
|
-
function
|
|
3347
|
-
return
|
|
3851
|
+
function isDecimalPlacesError(message) {
|
|
3852
|
+
return ((message.includes('maximum') || message.includes('at most')) &&
|
|
3853
|
+
message.includes('decimal places'));
|
|
3348
3854
|
}
|
|
3349
|
-
|
|
3350
3855
|
/**
|
|
3351
|
-
*
|
|
3856
|
+
* Checks if an error message indicates a numeric format validation failure.
|
|
3352
3857
|
*
|
|
3353
|
-
*
|
|
3354
|
-
*
|
|
3355
|
-
* on string matching and future-proofs the code for new chain ecosystems.
|
|
3858
|
+
* Identifies errors where a value contains 'numeric' but is not specifically
|
|
3859
|
+
* about 'valid number format' or 'numeric string' (to avoid false positives).
|
|
3356
3860
|
*
|
|
3357
|
-
* @param
|
|
3358
|
-
* @returns
|
|
3861
|
+
* @param message - The error message to analyze
|
|
3862
|
+
* @returns True if the message indicates a numeric format error, false otherwise
|
|
3863
|
+
*/
|
|
3864
|
+
function isNumericFormatError(message) {
|
|
3865
|
+
return (message.includes('numeric') &&
|
|
3866
|
+
!message.includes('valid number format') &&
|
|
3867
|
+
!message.includes('numeric string'));
|
|
3868
|
+
}
|
|
3869
|
+
/**
|
|
3870
|
+
* Handles chain-related validation errors from Zod.
|
|
3359
3871
|
*
|
|
3360
|
-
*
|
|
3361
|
-
*
|
|
3362
|
-
*
|
|
3872
|
+
* Checks if the validation error path includes 'chain' and extracts the
|
|
3873
|
+
* chain name from either the source (from) or destination (to) data.
|
|
3874
|
+
* Creates a KitError with the invalid chain name and original error message.
|
|
3363
3875
|
*
|
|
3364
|
-
*
|
|
3365
|
-
*
|
|
3366
|
-
*
|
|
3367
|
-
*
|
|
3876
|
+
* @param path - The Zod error path (e.g., 'from.chain' or 'to.chain')
|
|
3877
|
+
* @param _code - The Zod error code (unused, prefixed with _ to indicate intentionally ignored)
|
|
3878
|
+
* @param message - The original Zod error message
|
|
3879
|
+
* @param fromData - The source/from data object (may contain chain info)
|
|
3880
|
+
* @param toData - The destination/to data object (may contain chain info)
|
|
3881
|
+
* @returns KitError with INPUT_INVALID_CHAIN code if this is a chain error, null otherwise
|
|
3882
|
+
*/
|
|
3883
|
+
function handleChainError(path, _code, message, fromData, toData) {
|
|
3884
|
+
if (!path.includes('chain'))
|
|
3885
|
+
return null;
|
|
3886
|
+
const chain = getChainName(fromData) ?? getChainName(toData);
|
|
3887
|
+
return createInvalidChainError(chain ?? 'unknown', message);
|
|
3888
|
+
}
|
|
3889
|
+
/**
|
|
3890
|
+
* Handles address-related validation errors from Zod.
|
|
3368
3891
|
*
|
|
3369
|
-
*
|
|
3370
|
-
*
|
|
3371
|
-
*
|
|
3372
|
-
*
|
|
3892
|
+
* Checks if the validation error path includes 'address' and extracts both
|
|
3893
|
+
* the invalid address from the path and the target chain from the params.
|
|
3894
|
+
* Uses chain utilities to determine the expected address format (EVM or Solana)
|
|
3895
|
+
* and creates a context-specific error message.
|
|
3373
3896
|
*
|
|
3374
|
-
*
|
|
3375
|
-
*
|
|
3376
|
-
*
|
|
3377
|
-
*
|
|
3897
|
+
* @param path - The Zod error path (e.g., 'to.recipientAddress')
|
|
3898
|
+
* @param _code - The Zod error code (unused, prefixed with _ to indicate intentionally ignored)
|
|
3899
|
+
* @param _message - The original Zod error message (unused, we create a more specific message)
|
|
3900
|
+
* @param paramsObj - The original params object for extracting address and chain info
|
|
3901
|
+
* @returns KitError with INPUT_INVALID_ADDRESS code if this is an address error, null otherwise
|
|
3378
3902
|
*/
|
|
3379
|
-
function
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
const
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
return {
|
|
3387
|
-
name,
|
|
3388
|
-
displayName: name.replace(/_/g, ' '),
|
|
3389
|
-
expectedAddressFormat: isSolana
|
|
3390
|
-
? '44-character base58 encoded string'
|
|
3391
|
-
: '42-character hex address starting with 0x',
|
|
3392
|
-
};
|
|
3903
|
+
function handleAddressError(path, _code, _message, paramsObj) {
|
|
3904
|
+
if (!path.toLowerCase().includes('address'))
|
|
3905
|
+
return null;
|
|
3906
|
+
const address = getAddressFromParams(paramsObj, path);
|
|
3907
|
+
const chain = getChainFromParams(paramsObj);
|
|
3908
|
+
const chainInfo = extractChainInfo(chain);
|
|
3909
|
+
return createInvalidAddressError(address, chainInfo.displayName, chainInfo.expectedAddressFormat);
|
|
3393
3910
|
}
|
|
3911
|
+
|
|
3394
3912
|
/**
|
|
3395
|
-
* Validates
|
|
3913
|
+
* Validates data against a Zod schema with enhanced error reporting.
|
|
3396
3914
|
*
|
|
3397
|
-
* This function
|
|
3398
|
-
*
|
|
3915
|
+
* This function performs validation using Zod schemas and provides detailed error
|
|
3916
|
+
* messages that include the validation context. It's designed to give developers
|
|
3917
|
+
* clear feedback about what went wrong during validation.
|
|
3399
3918
|
*
|
|
3400
|
-
* @param
|
|
3401
|
-
* @param
|
|
3402
|
-
* @
|
|
3919
|
+
* @param value - The value to validate
|
|
3920
|
+
* @param schema - The Zod schema to validate against
|
|
3921
|
+
* @param context - Context string to include in error messages (e.g., 'bridge parameters')
|
|
3922
|
+
* @returns Asserts that value is of type T (type narrowing)
|
|
3923
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098)
|
|
3403
3924
|
*
|
|
3404
3925
|
* @example
|
|
3405
3926
|
* ```typescript
|
|
3406
|
-
*
|
|
3407
|
-
*
|
|
3408
|
-
* // EVM address validation
|
|
3409
|
-
* isValidAddressForChain('0x742d35Cc6634C0532925a3b8D0C0C1C4C5C6C7C8', 'Ethereum')
|
|
3410
|
-
* // → true
|
|
3411
|
-
*
|
|
3412
|
-
* // Solana address validation
|
|
3413
|
-
* isValidAddressForChain('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', { name: 'Solana', type: 'solana' })
|
|
3414
|
-
* // → true
|
|
3415
|
-
*
|
|
3416
|
-
* // Invalid format
|
|
3417
|
-
* isValidAddressForChain('invalid', 'Ethereum')
|
|
3418
|
-
* // → false
|
|
3927
|
+
* validate(params, BridgeParamsSchema, 'bridge parameters')
|
|
3928
|
+
* // After this call, TypeScript knows params is of type BridgeParams
|
|
3419
3929
|
* ```
|
|
3420
3930
|
*/
|
|
3421
|
-
function
|
|
3422
|
-
const
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
const isSolana = chainType !== undefined
|
|
3426
|
-
? chainType === 'solana'
|
|
3427
|
-
: name.toLowerCase().includes('solana');
|
|
3428
|
-
if (isSolana) {
|
|
3429
|
-
// Solana base58 address: 32-44 characters from base58 alphabet
|
|
3430
|
-
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
|
|
3931
|
+
function validate(value, schema, context) {
|
|
3932
|
+
const result = schema.safeParse(value);
|
|
3933
|
+
if (!result.success) {
|
|
3934
|
+
throw createValidationErrorFromZod(result.error, context);
|
|
3431
3935
|
}
|
|
3432
|
-
// EVM hex address: 0x followed by 40 hex characters
|
|
3433
|
-
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
3434
3936
|
}
|
|
3937
|
+
|
|
3435
3938
|
/**
|
|
3436
|
-
*
|
|
3437
|
-
*
|
|
3438
|
-
*
|
|
3439
|
-
* @
|
|
3939
|
+
* Symbol used to track validation state on objects.
|
|
3940
|
+
* This allows us to attach metadata to objects without interfering with their structure,
|
|
3941
|
+
* enabling optimized validation by skipping already validated objects.
|
|
3942
|
+
* @internal
|
|
3440
3943
|
*/
|
|
3441
|
-
|
|
3442
|
-
return typeof val === 'object' && val !== null;
|
|
3443
|
-
}
|
|
3944
|
+
const VALIDATION_STATE = Symbol('validationState');
|
|
3444
3945
|
/**
|
|
3445
|
-
*
|
|
3946
|
+
* Validates data against a Zod schema with state tracking and enhanced error reporting.
|
|
3446
3947
|
*
|
|
3447
|
-
*
|
|
3448
|
-
*
|
|
3948
|
+
* This function performs validation using Zod schemas while tracking validation state
|
|
3949
|
+
* and providing detailed error messages. It's designed for use in scenarios where
|
|
3950
|
+
* validation state needs to be monitored and reported.
|
|
3951
|
+
*
|
|
3952
|
+
* @param value - The value to validate
|
|
3953
|
+
* @param schema - The Zod schema to validate against
|
|
3954
|
+
* @param context - Context string to include in error messages (e.g., 'bridge parameters')
|
|
3955
|
+
* @param validatorName - Symbol identifying the validator for state tracking
|
|
3956
|
+
* @returns Asserts that value is of type T (type narrowing)
|
|
3957
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098)
|
|
3449
3958
|
*
|
|
3450
3959
|
* @example
|
|
3451
3960
|
* ```typescript
|
|
3452
|
-
*
|
|
3453
|
-
*
|
|
3454
|
-
* const chain1 = { name: 'Ethereum', isTestnet: false }
|
|
3455
|
-
* isChainWithTestnet(chain1) // → true
|
|
3456
|
-
*
|
|
3457
|
-
* const chain2 = { name: 'Ethereum' }
|
|
3458
|
-
* isChainWithTestnet(chain2) // → false
|
|
3459
|
-
*
|
|
3460
|
-
* const chain3 = 'Ethereum'
|
|
3461
|
-
* isChainWithTestnet(chain3) // → false
|
|
3961
|
+
* const result = validateWithStateTracking(BridgeParamsSchema, params, 'bridge parameters')
|
|
3462
3962
|
* ```
|
|
3463
3963
|
*/
|
|
3464
|
-
function
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3964
|
+
function validateWithStateTracking(value, schema, context, validatorName) {
|
|
3965
|
+
// Skip validation for null or undefined values
|
|
3966
|
+
if (value === null) {
|
|
3967
|
+
throw new KitError({
|
|
3968
|
+
...InputError.VALIDATION_FAILED,
|
|
3969
|
+
recoverability: 'FATAL',
|
|
3970
|
+
message: `Invalid ${context}: Value is null`,
|
|
3971
|
+
cause: {
|
|
3972
|
+
trace: {
|
|
3973
|
+
validationErrors: ['Value is null'],
|
|
3974
|
+
},
|
|
3975
|
+
},
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
if (value === undefined) {
|
|
3979
|
+
throw new KitError({
|
|
3980
|
+
...InputError.VALIDATION_FAILED,
|
|
3981
|
+
recoverability: 'FATAL',
|
|
3982
|
+
message: `Invalid ${context}: Value is undefined`,
|
|
3983
|
+
cause: {
|
|
3984
|
+
trace: {
|
|
3985
|
+
validationErrors: ['Value is undefined'],
|
|
3986
|
+
},
|
|
3987
|
+
},
|
|
3988
|
+
});
|
|
3989
|
+
}
|
|
3990
|
+
// Ensure value is an object that can hold validation state
|
|
3991
|
+
if (typeof value !== 'object') {
|
|
3992
|
+
throw new KitError({
|
|
3993
|
+
...InputError.VALIDATION_FAILED,
|
|
3994
|
+
recoverability: 'FATAL',
|
|
3995
|
+
message: `Invalid ${context}: Value must be an object`,
|
|
3996
|
+
cause: {
|
|
3997
|
+
trace: {
|
|
3998
|
+
validationErrors: [`Value must be an object, got ${typeof value}`],
|
|
3999
|
+
},
|
|
4000
|
+
},
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
// Get or initialize validation state
|
|
4004
|
+
const valueWithState = value;
|
|
4005
|
+
const state = valueWithState[VALIDATION_STATE] ?? { validatedBy: [] };
|
|
4006
|
+
// Skip validation if already validated by this validator
|
|
4007
|
+
if (state.validatedBy.includes(validatorName)) {
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
// Delegate to the validate function for actual validation (now throws KitError)
|
|
4011
|
+
validate(value, schema, context);
|
|
4012
|
+
// Update validation state
|
|
4013
|
+
state.validatedBy.push(validatorName);
|
|
4014
|
+
valueWithState[VALIDATION_STATE] = state;
|
|
3470
4015
|
}
|
|
4016
|
+
|
|
3471
4017
|
/**
|
|
3472
|
-
*
|
|
3473
|
-
*
|
|
3474
|
-
* Looks for chain in context.chain first, then falls back to direct chain property.
|
|
3475
|
-
*
|
|
3476
|
-
* @param toData - The destination data object
|
|
3477
|
-
* @returns The chain identifier or null
|
|
4018
|
+
* Zod schema for validating chain definition objects used in buildExplorerUrl.
|
|
4019
|
+
* This schema ensures the chain definition has the required properties for URL generation.
|
|
3478
4020
|
*/
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
4021
|
+
const chainDefinitionSchema = z.object({
|
|
4022
|
+
name: z
|
|
4023
|
+
.string({
|
|
4024
|
+
required_error: 'Chain name is required',
|
|
4025
|
+
invalid_type_error: 'Chain name must be a string',
|
|
4026
|
+
})
|
|
4027
|
+
.min(1, 'Chain name cannot be empty'),
|
|
4028
|
+
explorerUrl: z
|
|
4029
|
+
.string({
|
|
4030
|
+
required_error: 'Explorer URL template is required',
|
|
4031
|
+
invalid_type_error: 'Explorer URL template must be a string',
|
|
4032
|
+
})
|
|
4033
|
+
.min(1, 'Explorer URL template cannot be empty')
|
|
4034
|
+
.refine((url) => url.includes('{hash}'), 'Explorer URL template must contain a {hash} placeholder'),
|
|
4035
|
+
});
|
|
3484
4036
|
/**
|
|
3485
|
-
*
|
|
3486
|
-
*
|
|
3487
|
-
* @param params - The parameters object
|
|
3488
|
-
* @returns The chain name as a string, or null if not found
|
|
4037
|
+
* Zod schema for validating transaction hash strings used in buildExplorerUrl.
|
|
4038
|
+
* This schema ensures the transaction hash is a non-empty string.
|
|
3489
4039
|
*/
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
}
|
|
3499
|
-
}
|
|
3500
|
-
return null;
|
|
3501
|
-
}
|
|
4040
|
+
const transactionHashSchema = z
|
|
4041
|
+
.string({
|
|
4042
|
+
required_error: 'Transaction hash is required',
|
|
4043
|
+
invalid_type_error: 'Transaction hash must be a string',
|
|
4044
|
+
})
|
|
4045
|
+
.min(1, 'Transaction hash cannot be empty')
|
|
4046
|
+
.transform((hash) => hash.trim()) // Automatically trim whitespace
|
|
4047
|
+
.refine((hash) => hash.length > 0, 'Transaction hash must not be empty or whitespace-only');
|
|
3502
4048
|
/**
|
|
3503
|
-
*
|
|
3504
|
-
*
|
|
3505
|
-
* Supports both 'from' and 'source' property names.
|
|
3506
|
-
*
|
|
3507
|
-
* @param params - The parameters object
|
|
3508
|
-
* @returns The from/source data or undefined
|
|
4049
|
+
* Zod schema for validating buildExplorerUrl function parameters.
|
|
4050
|
+
* This schema validates both the chain definition and transaction hash together.
|
|
3509
4051
|
*/
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
4052
|
+
z.object({
|
|
4053
|
+
chainDef: chainDefinitionSchema,
|
|
4054
|
+
txHash: transactionHashSchema,
|
|
4055
|
+
});
|
|
3513
4056
|
/**
|
|
3514
|
-
*
|
|
3515
|
-
*
|
|
3516
|
-
* Supports both 'to' and 'destination' property names.
|
|
3517
|
-
*
|
|
3518
|
-
* @param params - The parameters object
|
|
3519
|
-
* @returns The to/destination data or undefined
|
|
4057
|
+
* Zod schema for validating the generated explorer URL.
|
|
4058
|
+
* This schema ensures the generated URL is valid.
|
|
3520
4059
|
*/
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
4060
|
+
z
|
|
4061
|
+
.string()
|
|
4062
|
+
.url('Generated explorer URL is invalid');
|
|
4063
|
+
|
|
3524
4064
|
/**
|
|
3525
|
-
*
|
|
4065
|
+
* A type-safe event emitter for managing action-based event subscriptions.
|
|
3526
4066
|
*
|
|
3527
|
-
*
|
|
4067
|
+
* Actionable provides a strongly-typed publish/subscribe pattern for events,
|
|
4068
|
+
* where each event (action) has its own specific payload type. Handlers can
|
|
4069
|
+
* subscribe to specific events or use a wildcard to receive all events.
|
|
3528
4070
|
*
|
|
3529
|
-
* @
|
|
3530
|
-
* @param path - Dot-separated path to extract address from (e.g., 'to.recipientAddress')
|
|
3531
|
-
* @returns The address as a string, or 'unknown' if not found
|
|
4071
|
+
* @typeParam AllActions - A record mapping action names to their payload types.
|
|
3532
4072
|
*
|
|
3533
4073
|
* @example
|
|
3534
4074
|
* ```typescript
|
|
3535
|
-
*
|
|
3536
|
-
* getAddressFromParams(params, 'to.recipientAddress')
|
|
3537
|
-
* // Returns the value at params.to.recipientAddress
|
|
3538
|
-
* ```
|
|
3539
|
-
*/
|
|
3540
|
-
function getAddressFromParams(params, path) {
|
|
3541
|
-
const parts = path.split('.');
|
|
3542
|
-
let current = params;
|
|
3543
|
-
for (const part of parts) {
|
|
3544
|
-
if (current !== null &&
|
|
3545
|
-
current !== undefined &&
|
|
3546
|
-
typeof current === 'object' &&
|
|
3547
|
-
part in current) {
|
|
3548
|
-
current = current[part];
|
|
3549
|
-
}
|
|
3550
|
-
else {
|
|
3551
|
-
return 'unknown';
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
return typeof current === 'string' ? current : 'unknown';
|
|
3555
|
-
}
|
|
3556
|
-
/**
|
|
3557
|
-
* Gets chain identifier from params object.
|
|
3558
|
-
*
|
|
3559
|
-
* Looks for chain in to.context.chain, to.chain, or direct chain property.
|
|
4075
|
+
* import { Actionable } from '@circle-fin/bridge-kit/utils';
|
|
3560
4076
|
*
|
|
3561
|
-
*
|
|
3562
|
-
*
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
return chain;
|
|
3568
|
-
}
|
|
3569
|
-
|
|
3570
|
-
/**
|
|
3571
|
-
* Maximum length for error messages in fallback validation errors.
|
|
4077
|
+
* // Define your action types
|
|
4078
|
+
* type TransferActions = {
|
|
4079
|
+
* started: { txHash: string; amount: string };
|
|
4080
|
+
* completed: { txHash: string; destinationTxHash: string };
|
|
4081
|
+
* failed: { error: Error };
|
|
4082
|
+
* };
|
|
3572
4083
|
*
|
|
3573
|
-
*
|
|
3574
|
-
*
|
|
3575
|
-
* characters to leave a 50-character buffer for:
|
|
3576
|
-
* - The error message prefix ("Invalid bridge parameters: ")
|
|
3577
|
-
* - Potential encoding differences or formatting overhead
|
|
3578
|
-
* - Safety margin to prevent KitError constructor failures
|
|
4084
|
+
* // Create an actionable instance
|
|
4085
|
+
* const transferEvents = new Actionable<TransferActions>();
|
|
3579
4086
|
*
|
|
3580
|
-
*
|
|
3581
|
-
*
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
/**
|
|
3586
|
-
* Converts a Zod validation error into a specific KitError instance using structured pattern matching.
|
|
4087
|
+
* // Subscribe to a specific event
|
|
4088
|
+
* transferEvents.on('completed', (payload) => {
|
|
4089
|
+
* console.log(`Transfer completed with hash: ${payload.destinationTxHash}`);
|
|
4090
|
+
* });
|
|
3587
4091
|
*
|
|
3588
|
-
*
|
|
3589
|
-
*
|
|
3590
|
-
*
|
|
4092
|
+
* // Subscribe to all events
|
|
4093
|
+
* transferEvents.on('*', (payload) => {
|
|
4094
|
+
* console.log('Event received:', payload);
|
|
4095
|
+
* });
|
|
3591
4096
|
*
|
|
3592
|
-
*
|
|
3593
|
-
*
|
|
3594
|
-
*
|
|
4097
|
+
* // Dispatch an event
|
|
4098
|
+
* transferEvents.dispatch('completed', {
|
|
4099
|
+
* txHash: '0x123',
|
|
4100
|
+
* destinationTxHash: '0xabc'
|
|
4101
|
+
* });
|
|
4102
|
+
* ```
|
|
3595
4103
|
*/
|
|
3596
|
-
|
|
3597
|
-
//
|
|
3598
|
-
|
|
3599
|
-
|
|
4104
|
+
class Actionable {
|
|
4105
|
+
// Store event handlers by action key
|
|
4106
|
+
handlers = {};
|
|
4107
|
+
// Store wildcard handlers that receive all events
|
|
4108
|
+
wildcard = [];
|
|
4109
|
+
// Implementation that handles both overloads
|
|
4110
|
+
on(action, handler) {
|
|
4111
|
+
if (action === '*') {
|
|
4112
|
+
// Add to wildcard handlers array
|
|
4113
|
+
this.wildcard.push(handler);
|
|
4114
|
+
}
|
|
4115
|
+
else {
|
|
4116
|
+
// Initialize the action's handler array if it doesn't exist
|
|
4117
|
+
if (!this.handlers[action]) {
|
|
4118
|
+
this.handlers[action] = [];
|
|
4119
|
+
}
|
|
4120
|
+
// Add the handler to the specific action's array
|
|
4121
|
+
this.handlers[action].push(handler);
|
|
4122
|
+
}
|
|
3600
4123
|
}
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
4124
|
+
// Implementation that handles both overloads
|
|
4125
|
+
off(action, handler) {
|
|
4126
|
+
if (action === '*') {
|
|
4127
|
+
// Find and remove the handler from wildcard array
|
|
4128
|
+
const index = this.wildcard.indexOf(handler);
|
|
4129
|
+
if (index !== -1) {
|
|
4130
|
+
this.wildcard.splice(index, 1);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
else if (this.handlers[action]) {
|
|
4134
|
+
// Check if there are handlers for this action
|
|
4135
|
+
// Find and remove the specific handler
|
|
4136
|
+
const index = this.handlers[action].indexOf(handler);
|
|
4137
|
+
if (index !== -1) {
|
|
4138
|
+
this.handlers[action].splice(index, 1);
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
/**
|
|
4143
|
+
* Dispatch an action with its payload to all registered handlers.
|
|
4144
|
+
*
|
|
4145
|
+
* This method notifies both:
|
|
4146
|
+
* - Handlers registered specifically for this action
|
|
4147
|
+
* - Wildcard handlers registered for all actions
|
|
4148
|
+
*
|
|
4149
|
+
* @param action - The action key identifying the event type.
|
|
4150
|
+
* @param payload - The data associated with the action.
|
|
4151
|
+
*
|
|
4152
|
+
* @example
|
|
4153
|
+
* ```typescript
|
|
4154
|
+
* type Actions = {
|
|
4155
|
+
* transferStarted: { amount: string; destination: string };
|
|
4156
|
+
* transferComplete: { txHash: string };
|
|
4157
|
+
* };
|
|
4158
|
+
*
|
|
4159
|
+
* const events = new Actionable<Actions>();
|
|
4160
|
+
*
|
|
4161
|
+
* // Dispatch an event
|
|
4162
|
+
* events.dispatch('transferStarted', {
|
|
4163
|
+
* amount: '100',
|
|
4164
|
+
* destination: '0xABC123'
|
|
4165
|
+
* });
|
|
4166
|
+
* ```
|
|
4167
|
+
*/
|
|
4168
|
+
dispatch(action, payload) {
|
|
4169
|
+
// Execute all handlers registered for this specific action
|
|
4170
|
+
for (const h of this.handlers[action] ?? [])
|
|
4171
|
+
h(payload);
|
|
4172
|
+
// Execute all wildcard handlers
|
|
4173
|
+
for (const h of this.wildcard)
|
|
4174
|
+
h(payload);
|
|
3617
4175
|
}
|
|
3618
|
-
// Fallback
|
|
3619
|
-
return createValidationFailedError(zodError);
|
|
3620
|
-
}
|
|
3621
|
-
/**
|
|
3622
|
-
* Creates a generic validation failed error for null/undefined params or unmapped error cases.
|
|
3623
|
-
*
|
|
3624
|
-
* This is a fallback handler used when:
|
|
3625
|
-
* - Parameters are null or undefined
|
|
3626
|
-
* - No specific error handler matches the Zod error
|
|
3627
|
-
* - The error doesn't fit into amount/chain/address categories
|
|
3628
|
-
*
|
|
3629
|
-
* The function truncates the error message to stay within KitError's 500-character limit.
|
|
3630
|
-
*
|
|
3631
|
-
* @param zodError - The Zod validation error with all issues
|
|
3632
|
-
* @param params - The original parameters (may be null/undefined)
|
|
3633
|
-
* @returns A generic KitError with INPUT_VALIDATION_FAILED code
|
|
3634
|
-
*/
|
|
3635
|
-
function createValidationFailedError(zodError) {
|
|
3636
|
-
const issueSummary = zodError.issues
|
|
3637
|
-
.map((issue) => `${issue.path.join('.')}: ${issue.message}`)
|
|
3638
|
-
.join('; ');
|
|
3639
|
-
// Truncate message to avoid KitError constructor failure.
|
|
3640
|
-
const fullMessage = `Invalid parameters: ${issueSummary}`;
|
|
3641
|
-
const truncatedMessage = fullMessage.length > MAX_MESSAGE_LENGTH
|
|
3642
|
-
? `${fullMessage.substring(0, MAX_MESSAGE_LENGTH)}...`
|
|
3643
|
-
: fullMessage;
|
|
3644
|
-
return new KitError({
|
|
3645
|
-
...InputError.VALIDATION_FAILED,
|
|
3646
|
-
recoverability: 'FATAL',
|
|
3647
|
-
message: truncatedMessage,
|
|
3648
|
-
cause: {
|
|
3649
|
-
trace: {
|
|
3650
|
-
originalError: zodError.message,
|
|
3651
|
-
zodIssues: zodError.issues,
|
|
3652
|
-
},
|
|
3653
|
-
},
|
|
3654
|
-
});
|
|
3655
|
-
}
|
|
3656
|
-
/**
|
|
3657
|
-
* Handles amount-related validation errors from Zod.
|
|
3658
|
-
*
|
|
3659
|
-
* Checks if the validation error path includes 'amount' and attempts
|
|
3660
|
-
* to convert generic Zod errors into specific KitError instances with
|
|
3661
|
-
* actionable messages. Delegates to specialized handlers in order of specificity:
|
|
3662
|
-
* 1. Negative amount errors (too_small)
|
|
3663
|
-
* 2. Custom validation errors (decimal places, numeric string)
|
|
3664
|
-
* 3. Invalid string format errors
|
|
3665
|
-
* 4. Invalid type errors
|
|
3666
|
-
*
|
|
3667
|
-
* @param path - The Zod error path (e.g., 'amount' or 'config.amount')
|
|
3668
|
-
* @param code - The Zod error code (e.g., 'too_small', 'invalid_string', 'custom')
|
|
3669
|
-
* @param message - The original Zod error message
|
|
3670
|
-
* @param paramsObj - The original params object for extracting the invalid amount value
|
|
3671
|
-
* @returns KitError with INPUT_INVALID_AMOUNT code if this is an amount error, null otherwise
|
|
3672
|
-
*/
|
|
3673
|
-
function handleAmountError(path, code, message, paramsObj) {
|
|
3674
|
-
if (!path.includes('amount'))
|
|
3675
|
-
return null;
|
|
3676
|
-
const amount = typeof paramsObj['amount'] === 'string' ? paramsObj['amount'] : 'unknown';
|
|
3677
|
-
// Try different error handlers in order of specificity
|
|
3678
|
-
const negativeError = handleNegativeAmountError(code, message, amount);
|
|
3679
|
-
if (negativeError)
|
|
3680
|
-
return negativeError;
|
|
3681
|
-
const customError = handleCustomAmountError(code, message, amount);
|
|
3682
|
-
if (customError)
|
|
3683
|
-
return customError;
|
|
3684
|
-
const stringFormatError = handleInvalidStringAmountError(code, message, amount);
|
|
3685
|
-
if (stringFormatError)
|
|
3686
|
-
return stringFormatError;
|
|
3687
|
-
const typeError = handleInvalidTypeAmountError(code, amount);
|
|
3688
|
-
if (typeError)
|
|
3689
|
-
return typeError;
|
|
3690
|
-
return null;
|
|
3691
4176
|
}
|
|
4177
|
+
|
|
3692
4178
|
/**
|
|
3693
|
-
*
|
|
4179
|
+
* Convert a value from its smallest unit representation to a human-readable decimal string.
|
|
3694
4180
|
*
|
|
3695
|
-
*
|
|
3696
|
-
*
|
|
4181
|
+
* This function normalizes token values from their blockchain representation (where
|
|
4182
|
+
* everything is stored as integers in the smallest denomination) to human-readable
|
|
4183
|
+
* decimal format. Uses the battle-tested implementation from @ethersproject/units.
|
|
3697
4184
|
*
|
|
3698
|
-
* @param
|
|
3699
|
-
* @param
|
|
3700
|
-
* @
|
|
3701
|
-
* @
|
|
3702
|
-
*/
|
|
3703
|
-
function handleNegativeAmountError(code, message, amount) {
|
|
3704
|
-
if (code === 'too_small' || message.includes('greater than')) {
|
|
3705
|
-
return createInvalidAmountError(amount, 'Amount must be greater than 0');
|
|
3706
|
-
}
|
|
3707
|
-
return null;
|
|
3708
|
-
}
|
|
3709
|
-
/**
|
|
3710
|
-
* Handles custom Zod refinement validation errors for amounts.
|
|
4185
|
+
* @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
|
|
4186
|
+
* @param decimals - The number of decimal places for the unit conversion
|
|
4187
|
+
* @returns A human-readable decimal string (e.g., "1.0")
|
|
4188
|
+
* @throws Error if the value is not a valid numeric string
|
|
3711
4189
|
*
|
|
3712
|
-
*
|
|
3713
|
-
*
|
|
3714
|
-
*
|
|
3715
|
-
* - 'decimal places' - too many decimal places
|
|
3716
|
-
* - 'numeric string' - value is not a valid numeric string
|
|
4190
|
+
* @example
|
|
4191
|
+
* ```typescript
|
|
4192
|
+
* import { formatUnits } from '@core/utils'
|
|
3717
4193
|
*
|
|
3718
|
-
*
|
|
3719
|
-
*
|
|
3720
|
-
*
|
|
3721
|
-
* @returns KitError with specific message if pattern matches, null otherwise
|
|
3722
|
-
*/
|
|
3723
|
-
function handleCustomAmountError(code, message, amount) {
|
|
3724
|
-
if (code !== 'custom')
|
|
3725
|
-
return null;
|
|
3726
|
-
if (message.includes('non-negative')) {
|
|
3727
|
-
return createInvalidAmountError(amount, 'Amount must be non-negative');
|
|
3728
|
-
}
|
|
3729
|
-
if (message.includes('decimal places')) {
|
|
3730
|
-
return createInvalidAmountError(amount, message);
|
|
3731
|
-
}
|
|
3732
|
-
if (message.includes('numeric string')) {
|
|
3733
|
-
return createInvalidAmountError(amount, 'Amount must be a numeric string');
|
|
3734
|
-
}
|
|
3735
|
-
return null;
|
|
3736
|
-
}
|
|
3737
|
-
/**
|
|
3738
|
-
* Handles Zod 'invalid_string' errors for amount values.
|
|
4194
|
+
* // Format USDC (6 decimals)
|
|
4195
|
+
* const usdcFormatted = formatUnits('1000000', 6)
|
|
4196
|
+
* console.log(usdcFormatted) // "1.0"
|
|
3739
4197
|
*
|
|
3740
|
-
*
|
|
3741
|
-
*
|
|
3742
|
-
*
|
|
3743
|
-
* 3. Numeric format validation failures (contains non-numeric characters)
|
|
3744
|
-
* 4. Generic invalid number strings
|
|
4198
|
+
* // Format ETH (18 decimals)
|
|
4199
|
+
* const ethFormatted = formatUnits('1000000000000000000', 18)
|
|
4200
|
+
* console.log(ethFormatted) // "1.0"
|
|
3745
4201
|
*
|
|
3746
|
-
*
|
|
3747
|
-
*
|
|
3748
|
-
*
|
|
3749
|
-
*
|
|
4202
|
+
* // Format with fractional part
|
|
4203
|
+
* const fractionalFormatted = formatUnits('1500000', 6)
|
|
4204
|
+
* console.log(fractionalFormatted) // "1.5"
|
|
4205
|
+
* ```
|
|
3750
4206
|
*/
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
// Check if it's a negative number first
|
|
3755
|
-
if (amount.startsWith('-') && !isNaN(Number(amount))) {
|
|
3756
|
-
return createInvalidAmountError(amount, 'Amount must be greater than 0');
|
|
3757
|
-
}
|
|
3758
|
-
// Check for decimal places validation
|
|
3759
|
-
if (isDecimalPlacesError(message)) {
|
|
3760
|
-
return createInvalidAmountError(amount, 'Maximum supported decimal places: 6');
|
|
3761
|
-
}
|
|
3762
|
-
// Check for numeric format validation
|
|
3763
|
-
if (isNumericFormatError(message)) {
|
|
3764
|
-
return createInvalidAmountError(amount, 'Amount must be a valid number format');
|
|
3765
|
-
}
|
|
3766
|
-
// For other cases like 'abc', return specific error
|
|
3767
|
-
if (!message.includes('valid number format')) {
|
|
3768
|
-
return createInvalidAmountError(amount, 'Amount must be a valid number string');
|
|
3769
|
-
}
|
|
3770
|
-
return null;
|
|
3771
|
-
}
|
|
4207
|
+
const formatUnits = (value, decimals) => {
|
|
4208
|
+
return formatUnits$1(value, decimals);
|
|
4209
|
+
};
|
|
3772
4210
|
/**
|
|
3773
|
-
*
|
|
4211
|
+
* Convert a human-readable decimal string to its smallest unit representation.
|
|
3774
4212
|
*
|
|
3775
|
-
*
|
|
3776
|
-
*
|
|
4213
|
+
* This function converts user-friendly decimal values into the integer representation
|
|
4214
|
+
* required by blockchain operations, where all values are stored in the smallest
|
|
4215
|
+
* denomination. Uses the battle-tested implementation from @ethersproject/units.
|
|
3777
4216
|
*
|
|
3778
|
-
* @param
|
|
3779
|
-
* @param
|
|
3780
|
-
* @returns
|
|
3781
|
-
|
|
3782
|
-
function handleInvalidTypeAmountError(code, amount) {
|
|
3783
|
-
if (code === 'invalid_type') {
|
|
3784
|
-
return createInvalidAmountError(amount, 'Amount must be a valid number string');
|
|
3785
|
-
}
|
|
3786
|
-
return null;
|
|
3787
|
-
}
|
|
3788
|
-
/**
|
|
3789
|
-
* Checks if an error message indicates a decimal places validation failure.
|
|
4217
|
+
* @param value - The decimal string to convert (e.g., "1.0")
|
|
4218
|
+
* @param decimals - The number of decimal places for the unit conversion
|
|
4219
|
+
* @returns The value in smallest units as a bigint (e.g., 1000000n for 1 USDC with 6 decimals)
|
|
4220
|
+
* @throws Error if the value is not a valid decimal string
|
|
3790
4221
|
*
|
|
3791
|
-
*
|
|
3792
|
-
*
|
|
4222
|
+
* @example
|
|
4223
|
+
* ```typescript
|
|
4224
|
+
* import { parseUnits } from '@core/utils'
|
|
3793
4225
|
*
|
|
3794
|
-
*
|
|
3795
|
-
*
|
|
3796
|
-
|
|
3797
|
-
function isDecimalPlacesError(message) {
|
|
3798
|
-
return ((message.includes('maximum') || message.includes('at most')) &&
|
|
3799
|
-
message.includes('decimal places'));
|
|
3800
|
-
}
|
|
3801
|
-
/**
|
|
3802
|
-
* Checks if an error message indicates a numeric format validation failure.
|
|
4226
|
+
* // Parse USDC (6 decimals)
|
|
4227
|
+
* const usdcParsed = parseUnits('1.0', 6)
|
|
4228
|
+
* console.log(usdcParsed) // 1000000n
|
|
3803
4229
|
*
|
|
3804
|
-
*
|
|
3805
|
-
*
|
|
4230
|
+
* // Parse ETH (18 decimals)
|
|
4231
|
+
* const ethParsed = parseUnits('1.0', 18)
|
|
4232
|
+
* console.log(ethParsed) // 1000000000000000000n
|
|
3806
4233
|
*
|
|
3807
|
-
*
|
|
3808
|
-
*
|
|
4234
|
+
* // Parse fractional amount
|
|
4235
|
+
* const fractionalParsed = parseUnits('1.5', 6)
|
|
4236
|
+
* console.log(fractionalParsed) // 1500000n
|
|
4237
|
+
*
|
|
4238
|
+
* // Parse integer (no decimal point)
|
|
4239
|
+
* const integerParsed = parseUnits('42', 6)
|
|
4240
|
+
* console.log(integerParsed) // 42000000n
|
|
4241
|
+
* ```
|
|
3809
4242
|
*/
|
|
3810
|
-
|
|
3811
|
-
return (
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
}
|
|
4243
|
+
const parseUnits = (value, decimals) => {
|
|
4244
|
+
return parseUnits$1(value, decimals).toBigInt();
|
|
4245
|
+
};
|
|
4246
|
+
|
|
3815
4247
|
/**
|
|
3816
|
-
*
|
|
4248
|
+
* Format a token amount into a human-readable decimal string.
|
|
3817
4249
|
*
|
|
3818
|
-
*
|
|
3819
|
-
*
|
|
3820
|
-
*
|
|
4250
|
+
* Accepts a smallest-unit string and either assumes USDC's 6 decimals or derives the
|
|
4251
|
+
* native decimals from the provided chain definition. Delegates to {@link formatUnits}
|
|
4252
|
+
* to preserve consistent rounding and formatting behaviour across the SDK.
|
|
3821
4253
|
*
|
|
3822
|
-
* @
|
|
3823
|
-
*
|
|
3824
|
-
*
|
|
3825
|
-
*
|
|
3826
|
-
* @param
|
|
3827
|
-
* @returns
|
|
4254
|
+
* @remarks
|
|
4255
|
+
* When `token` is `'native'`, supply a chain identifier that {@link resolveChainIdentifier}
|
|
4256
|
+
* can resolve so the native currency decimals can be determined.
|
|
4257
|
+
*
|
|
4258
|
+
* @param params - The formatting input including the raw value and token selector.
|
|
4259
|
+
* @returns The decimal string representation of the amount.
|
|
4260
|
+
* @throws Error if the value cannot be parsed or if the chain identifier is unknown.
|
|
4261
|
+
*
|
|
4262
|
+
* @example
|
|
4263
|
+
* ```typescript
|
|
4264
|
+
* import { formatAmount } from '@core/utils'
|
|
4265
|
+
* import { Ethereum } from '@core/chains'
|
|
4266
|
+
*
|
|
4267
|
+
* const usdcAmount = formatAmount({ value: '1000000', token: 'USDC' })
|
|
4268
|
+
* console.log(usdcAmount) // "1"
|
|
4269
|
+
*
|
|
4270
|
+
* const ethAmount = formatAmount({
|
|
4271
|
+
* value: '3141592000000000000',
|
|
4272
|
+
* token: 'native',
|
|
4273
|
+
* chain: Ethereum,
|
|
4274
|
+
* })
|
|
4275
|
+
* console.log(ethAmount) // "3.141592"
|
|
4276
|
+
* ```
|
|
3828
4277
|
*/
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
4278
|
+
const formatAmount = (params) => {
|
|
4279
|
+
const { value, token } = params;
|
|
4280
|
+
switch (token) {
|
|
4281
|
+
case 'USDC':
|
|
4282
|
+
return formatUnits(value, 6);
|
|
4283
|
+
case 'native':
|
|
4284
|
+
return formatUnits(value, resolveChainIdentifier(params.chain).nativeCurrency.decimals);
|
|
4285
|
+
default:
|
|
4286
|
+
// This will cause a compile-time error if a new token type is added to
|
|
4287
|
+
// `FormatAmountParams` but not handled in this switch statement, ensuring exhaustiveness.
|
|
4288
|
+
throw new Error(`formatAmount: Unhandled token type: ${token}`);
|
|
4289
|
+
}
|
|
4290
|
+
};
|
|
4291
|
+
|
|
3835
4292
|
/**
|
|
3836
|
-
*
|
|
4293
|
+
* Parse a human-readable token amount into its smallest unit representation.
|
|
3837
4294
|
*
|
|
3838
|
-
*
|
|
3839
|
-
*
|
|
3840
|
-
*
|
|
3841
|
-
* and creates a context-specific error message.
|
|
4295
|
+
* Accepts a decimal string and either assumes USDC's 6 decimals or derives the
|
|
4296
|
+
* native decimals from the provided chain definition. Delegates to {@link parseUnits}
|
|
4297
|
+
* to preserve deterministic rounding and bigint conversions across the SDK.
|
|
3842
4298
|
*
|
|
3843
|
-
* @
|
|
3844
|
-
*
|
|
3845
|
-
*
|
|
3846
|
-
*
|
|
3847
|
-
* @
|
|
4299
|
+
* @remarks
|
|
4300
|
+
* When `token` is `'native'`, supply a chain identifier that {@link resolveChainIdentifier}
|
|
4301
|
+
* can resolve so the native currency decimals can be determined.
|
|
4302
|
+
*
|
|
4303
|
+
* @param params - The parsing input including the amount value, token, and optional chain identifier.
|
|
4304
|
+
* @returns The bigint representation of the amount in smallest units.
|
|
4305
|
+
* @throws Error if the value cannot be parsed or if the chain identifier is unknown.
|
|
4306
|
+
*
|
|
4307
|
+
* @example
|
|
4308
|
+
* ```typescript
|
|
4309
|
+
* import { parseAmount } from '@core/utils'
|
|
4310
|
+
* import { Ethereum } from '@core/chains'
|
|
4311
|
+
*
|
|
4312
|
+
* const usdcAmount = parseAmount({ value: '1', token: 'USDC' })
|
|
4313
|
+
* console.log(usdcAmount) // 1000000n
|
|
4314
|
+
*
|
|
4315
|
+
* const ethAmount = parseAmount({
|
|
4316
|
+
* value: '3.141592',
|
|
4317
|
+
* token: 'native',
|
|
4318
|
+
* chain: Ethereum,
|
|
4319
|
+
* })
|
|
4320
|
+
* console.log(ethAmount) // 3141592000000000000n
|
|
4321
|
+
* ```
|
|
3848
4322
|
*/
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
4323
|
+
const parseAmount = (params) => {
|
|
4324
|
+
const { value, token } = params;
|
|
4325
|
+
switch (token) {
|
|
4326
|
+
case 'USDC':
|
|
4327
|
+
return parseUnits(value, 6);
|
|
4328
|
+
case 'native':
|
|
4329
|
+
return parseUnits(value, resolveChainIdentifier(params.chain).nativeCurrency.decimals);
|
|
4330
|
+
default:
|
|
4331
|
+
// This will cause a compile-time error if a new token type is added to
|
|
4332
|
+
// `FormatAmountParams` but not handled in this switch statement, ensuring exhaustiveness.
|
|
4333
|
+
throw new Error(`parseAmount: Unhandled token type: ${token}`);
|
|
4334
|
+
}
|
|
4335
|
+
};
|
|
4336
|
+
|
|
4337
|
+
var name = "@circle-fin/bridge-kit";
|
|
4338
|
+
var version = "1.2.0";
|
|
4339
|
+
var pkg = {
|
|
4340
|
+
name: name,
|
|
4341
|
+
version: version};
|
|
3857
4342
|
|
|
3858
4343
|
const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
3859
4344
|
/**
|
|
@@ -3862,17 +4347,22 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
3862
4347
|
* Validates the shape of {@link CustomFeePolicy}, which lets SDK consumers
|
|
3863
4348
|
* provide custom fee calculation and fee-recipient resolution logic.
|
|
3864
4349
|
*
|
|
3865
|
-
* -
|
|
4350
|
+
* - computeFee: optional function (recommended) that receives human-readable amounts
|
|
4351
|
+
* and returns a fee as a string (or Promise<string>).
|
|
4352
|
+
* - calculateFee: optional function (deprecated) that receives smallest-unit amounts
|
|
4353
|
+
* and returns a fee as a string (or Promise<string>).
|
|
3866
4354
|
* - resolveFeeRecipientAddress: required function that returns a recipient address as a
|
|
3867
4355
|
* string (or Promise<string>).
|
|
3868
4356
|
*
|
|
4357
|
+
* Exactly one of `computeFee` or `calculateFee` must be provided (not both).
|
|
4358
|
+
*
|
|
3869
4359
|
* This schema only ensures the presence and return types of the functions; it
|
|
3870
4360
|
* does not validate their argument types.
|
|
3871
4361
|
*
|
|
3872
4362
|
* @example
|
|
3873
4363
|
* ```ts
|
|
3874
4364
|
* const config = {
|
|
3875
|
-
*
|
|
4365
|
+
* computeFee: async () => '1', // 1 USDC (human-readable)
|
|
3876
4366
|
* resolveFeeRecipientAddress: () => '0x1234567890123456789012345678901234567890',
|
|
3877
4367
|
* }
|
|
3878
4368
|
* const result = customFeePolicySchema.safeParse(config)
|
|
@@ -3881,12 +4371,27 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
3881
4371
|
*/
|
|
3882
4372
|
const customFeePolicySchema = z
|
|
3883
4373
|
.object({
|
|
3884
|
-
|
|
4374
|
+
computeFee: z
|
|
4375
|
+
.function()
|
|
4376
|
+
.returns(z.string().or(z.promise(z.string())))
|
|
4377
|
+
.optional(),
|
|
4378
|
+
calculateFee: z
|
|
4379
|
+
.function()
|
|
4380
|
+
.returns(z.string().or(z.promise(z.string())))
|
|
4381
|
+
.optional(),
|
|
3885
4382
|
resolveFeeRecipientAddress: z
|
|
3886
4383
|
.function()
|
|
3887
4384
|
.returns(z.string().or(z.promise(z.string()))),
|
|
3888
4385
|
})
|
|
3889
|
-
.strict()
|
|
4386
|
+
.strict()
|
|
4387
|
+
.refine((data) => {
|
|
4388
|
+
const hasComputeFee = data.computeFee !== undefined;
|
|
4389
|
+
const hasCalculateFee = data.calculateFee !== undefined;
|
|
4390
|
+
// XOR: exactly one must be provided
|
|
4391
|
+
return hasComputeFee !== hasCalculateFee;
|
|
4392
|
+
}, {
|
|
4393
|
+
message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.',
|
|
4394
|
+
});
|
|
3890
4395
|
/**
|
|
3891
4396
|
* Assert that the provided value conforms to {@link CustomFeePolicy}.
|
|
3892
4397
|
*
|
|
@@ -3898,7 +4403,7 @@ const customFeePolicySchema = z
|
|
|
3898
4403
|
* @example
|
|
3899
4404
|
* ```ts
|
|
3900
4405
|
* const config = {
|
|
3901
|
-
*
|
|
4406
|
+
* computeFee: () => '1', // 1 USDC (human-readable)
|
|
3902
4407
|
* resolveFeeRecipientAddress: () => '0x1234567890123456789012345678901234567890',
|
|
3903
4408
|
* }
|
|
3904
4409
|
* assertCustomFeePolicy(config)
|
|
@@ -3965,16 +4470,16 @@ function assertBridgeParams(params, schema) {
|
|
|
3965
4470
|
validateWithStateTracking(params, schema, 'bridge parameters', ASSERT_BRIDGE_PARAMS_SYMBOL);
|
|
3966
4471
|
}
|
|
3967
4472
|
catch (error) {
|
|
3968
|
-
// Convert
|
|
3969
|
-
if (error instanceof
|
|
3970
|
-
|
|
3971
|
-
//
|
|
4473
|
+
// Convert generic KitError validation failure to structured KitError with specific codes
|
|
4474
|
+
if (error instanceof KitError &&
|
|
4475
|
+
error.code === InputError.VALIDATION_FAILED.code) {
|
|
4476
|
+
// Re-parse to get the underlying Zod error for enhanced error mapping
|
|
3972
4477
|
const result = schema.safeParse(params);
|
|
3973
4478
|
if (!result.success) {
|
|
3974
4479
|
throw convertZodErrorToStructured(result.error, params);
|
|
3975
4480
|
}
|
|
3976
4481
|
}
|
|
3977
|
-
// Re-throw if it's not a
|
|
4482
|
+
// Re-throw if it's not a validation error or couldn't be parsed
|
|
3978
4483
|
throw error;
|
|
3979
4484
|
}
|
|
3980
4485
|
// Additional business logic checks that Zod cannot handle
|
|
@@ -4004,7 +4509,7 @@ function assertBridgeParams(params, schema) {
|
|
|
4004
4509
|
* This schema does not validate length, making it suitable for various hex string types
|
|
4005
4510
|
* like addresses, transaction hashes, and other hex-encoded data.
|
|
4006
4511
|
*
|
|
4007
|
-
* @throws {
|
|
4512
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
4008
4513
|
*
|
|
4009
4514
|
* @example
|
|
4010
4515
|
* ```typescript
|
|
@@ -4035,7 +4540,7 @@ const hexStringSchema = z
|
|
|
4035
4540
|
* - Must be a valid hex string with '0x' prefix
|
|
4036
4541
|
* - Must be exactly 42 characters long (0x + 40 hex characters)
|
|
4037
4542
|
*
|
|
4038
|
-
* @throws {
|
|
4543
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
4039
4544
|
*
|
|
4040
4545
|
* @example
|
|
4041
4546
|
* ```typescript
|
|
@@ -4055,7 +4560,7 @@ hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exac
|
|
|
4055
4560
|
* - Must be a valid hex string with '0x' prefix
|
|
4056
4561
|
* - Must be exactly 66 characters long (0x + 64 hex characters)
|
|
4057
4562
|
*
|
|
4058
|
-
* @throws {
|
|
4563
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
4059
4564
|
*
|
|
4060
4565
|
* @example
|
|
4061
4566
|
* ```typescript
|
|
@@ -4081,7 +4586,7 @@ hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be
|
|
|
4081
4586
|
* This schema does not validate length, making it suitable for various base58-encoded data
|
|
4082
4587
|
* like Solana addresses, transaction signatures, and other base58-encoded data.
|
|
4083
4588
|
*
|
|
4084
|
-
* @throws {
|
|
4589
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
4085
4590
|
*
|
|
4086
4591
|
* @example
|
|
4087
4592
|
* ```typescript
|
|
@@ -4113,7 +4618,7 @@ const base58StringSchema = z
|
|
|
4113
4618
|
* - Must be a valid base58-encoded string
|
|
4114
4619
|
* - Must be between 32-44 characters long (typical length for base58-encoded 32-byte addresses)
|
|
4115
4620
|
*
|
|
4116
|
-
* @throws {
|
|
4621
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
4117
4622
|
*
|
|
4118
4623
|
* @example
|
|
4119
4624
|
* ```typescript
|
|
@@ -4133,7 +4638,7 @@ base58StringSchema.refine((value) => value.length >= 32 && value.length <= 44, '
|
|
|
4133
4638
|
* - Must be a valid base58-encoded string
|
|
4134
4639
|
* - Must be between 86-88 characters long (typical length for base58-encoded 64-byte signatures)
|
|
4135
4640
|
*
|
|
4136
|
-
* @throws {
|
|
4641
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
4137
4642
|
*
|
|
4138
4643
|
* @example
|
|
4139
4644
|
* ```typescript
|
|
@@ -4171,23 +4676,35 @@ var TransferSpeed;
|
|
|
4171
4676
|
})(TransferSpeed || (TransferSpeed = {}));
|
|
4172
4677
|
|
|
4173
4678
|
/**
|
|
4174
|
-
* Factory to validate a numeric string
|
|
4175
|
-
*
|
|
4679
|
+
* Factory to validate a numeric string with strict dot-decimal notation.
|
|
4680
|
+
* Only accepts dot (.) as the decimal separator. Thousand separators are not allowed.
|
|
4681
|
+
*
|
|
4682
|
+
* This enforces an unambiguous format for SDK inputs. Internationalization concerns
|
|
4683
|
+
* (comma vs dot decimal separators) should be handled in the UI layer before passing
|
|
4684
|
+
* values to the SDK.
|
|
4685
|
+
*
|
|
4686
|
+
* Accepts the following formats:
|
|
4687
|
+
* - Whole numbers: "1", "100", "1000"
|
|
4688
|
+
* - Leading zero decimals: "0.1", "0.5", "0.001"
|
|
4689
|
+
* - Shorthand decimals: ".1", ".5", ".001"
|
|
4690
|
+
* - Standard decimals: "1.23", "100.50"
|
|
4691
|
+
*
|
|
4692
|
+
* Does NOT accept:
|
|
4693
|
+
* - Comma decimal separator: "1,5" (use "1.5" instead)
|
|
4694
|
+
* - Thousand separators: "1,000.50" or "1.000,50" (use "1000.50" instead)
|
|
4695
|
+
* - Multiple decimal points: "1.2.3"
|
|
4696
|
+
* - Negative numbers: "-100"
|
|
4697
|
+
* - Non-numeric characters: "abc", "100a"
|
|
4176
4698
|
*
|
|
4177
4699
|
* Behavior differences controlled by options:
|
|
4178
4700
|
* - allowZero: when false, value must be strictly greater than 0; when true, non-negative.
|
|
4179
4701
|
* - regexMessage: error message when the basic numeric format fails.
|
|
4702
|
+
* - maxDecimals: maximum number of decimal places allowed (e.g., 6 for USDC).
|
|
4180
4703
|
*/
|
|
4181
4704
|
const createDecimalStringValidator = (options) => (schema) => schema
|
|
4182
|
-
.regex(
|
|
4705
|
+
.regex(/^-?(?:\d+(?:\.\d+)?|\.\d+)$/, options.regexMessage)
|
|
4183
4706
|
.superRefine((val, ctx) => {
|
|
4184
|
-
const
|
|
4185
|
-
const lastDot = val.lastIndexOf('.');
|
|
4186
|
-
const isCommaSeparated = lastSeparator > lastDot;
|
|
4187
|
-
const normalizedValue = val
|
|
4188
|
-
.replace(isCommaSeparated ? /\./g : /,/g, '')
|
|
4189
|
-
.replace(isCommaSeparated ? ',' : '.', '.');
|
|
4190
|
-
const amount = parseFloat(normalizedValue);
|
|
4707
|
+
const amount = Number.parseFloat(val);
|
|
4191
4708
|
if (Number.isNaN(amount)) {
|
|
4192
4709
|
ctx.addIssue({
|
|
4193
4710
|
code: z.ZodIssueCode.custom,
|
|
@@ -4197,7 +4714,7 @@ const createDecimalStringValidator = (options) => (schema) => schema
|
|
|
4197
4714
|
}
|
|
4198
4715
|
// Check decimal precision if maxDecimals is specified
|
|
4199
4716
|
if (options.maxDecimals !== undefined) {
|
|
4200
|
-
const decimalPart =
|
|
4717
|
+
const decimalPart = val.split('.')[1];
|
|
4201
4718
|
if (decimalPart && decimalPart.length > options.maxDecimals) {
|
|
4202
4719
|
ctx.addIssue({
|
|
4203
4720
|
code: z.ZodIssueCode.custom,
|
|
@@ -4224,7 +4741,7 @@ const createDecimalStringValidator = (options) => (schema) => schema
|
|
|
4224
4741
|
* This ensures the basic structure of a chain definition is valid.
|
|
4225
4742
|
* A chain definition must include at minimum a name and type.
|
|
4226
4743
|
*
|
|
4227
|
-
* @throws
|
|
4744
|
+
* @throws KitError if validation fails
|
|
4228
4745
|
*
|
|
4229
4746
|
* @example
|
|
4230
4747
|
* ```typescript
|
|
@@ -4251,7 +4768,7 @@ z.object({
|
|
|
4251
4768
|
* - A valid Ethereum address
|
|
4252
4769
|
* - A valid chain definition with required properties
|
|
4253
4770
|
*
|
|
4254
|
-
* @throws
|
|
4771
|
+
* @throws KitError if validation fails
|
|
4255
4772
|
*
|
|
4256
4773
|
* @example
|
|
4257
4774
|
* ```typescript
|
|
@@ -4292,7 +4809,7 @@ const walletContextSchema = z.object({
|
|
|
4292
4809
|
* - An optional fee amount as string
|
|
4293
4810
|
* - An optional fee recipient as string address
|
|
4294
4811
|
*
|
|
4295
|
-
* @throws
|
|
4812
|
+
* @throws KitError if validation fails
|
|
4296
4813
|
*
|
|
4297
4814
|
* @example
|
|
4298
4815
|
* ```typescript
|
|
@@ -4311,11 +4828,12 @@ const customFeeSchema = z
|
|
|
4311
4828
|
.object({
|
|
4312
4829
|
/**
|
|
4313
4830
|
* The fee to charge for the transfer as string.
|
|
4314
|
-
* Must be a non-negative value.
|
|
4831
|
+
* Must be a non-negative value using dot (.) as decimal separator,
|
|
4832
|
+
* with no thousand separators or comma decimals.
|
|
4315
4833
|
*/
|
|
4316
4834
|
value: createDecimalStringValidator({
|
|
4317
4835
|
allowZero: true,
|
|
4318
|
-
regexMessage: 'Value must be non-negative',
|
|
4836
|
+
regexMessage: 'Value must be a non-negative numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals.',
|
|
4319
4837
|
attributeName: 'value',
|
|
4320
4838
|
})(z.string()).optional(),
|
|
4321
4839
|
/**
|
|
@@ -4338,7 +4856,7 @@ const customFeeSchema = z
|
|
|
4338
4856
|
* - USDC as the token
|
|
4339
4857
|
* - Optional config with transfer speed and max fee settings
|
|
4340
4858
|
*
|
|
4341
|
-
* @throws
|
|
4859
|
+
* @throws KitError if validation fails
|
|
4342
4860
|
*
|
|
4343
4861
|
* @example
|
|
4344
4862
|
* ```typescript
|
|
@@ -4359,9 +4877,9 @@ const customFeeSchema = z
|
|
|
4359
4877
|
* token: 'USDC',
|
|
4360
4878
|
* config: {
|
|
4361
4879
|
* transferSpeed: 'FAST',
|
|
4362
|
-
* maxFee: '1.5', //
|
|
4880
|
+
* maxFee: '1.5', // Must use dot as decimal separator
|
|
4363
4881
|
* customFee: {
|
|
4364
|
-
* value: '0.5', //
|
|
4882
|
+
* value: '0.5', // Must use dot as decimal separator
|
|
4365
4883
|
* recipientAddress: '0x1234567890123456789012345678901234567890'
|
|
4366
4884
|
* }
|
|
4367
4885
|
* }
|
|
@@ -4377,7 +4895,7 @@ z.object({
|
|
|
4377
4895
|
.min(1, 'Required')
|
|
4378
4896
|
.pipe(createDecimalStringValidator({
|
|
4379
4897
|
allowZero: false,
|
|
4380
|
-
regexMessage: 'Amount must be a numeric string with
|
|
4898
|
+
regexMessage: 'Amount must be a numeric string with dot (.) as decimal separator (e.g., "0.1", ".1", "10.5", "1000.50"), with no thousand separators or comma decimals.',
|
|
4381
4899
|
attributeName: 'amount',
|
|
4382
4900
|
maxDecimals: 6,
|
|
4383
4901
|
})(z.string())),
|
|
@@ -4390,7 +4908,7 @@ z.object({
|
|
|
4390
4908
|
.string()
|
|
4391
4909
|
.pipe(createDecimalStringValidator({
|
|
4392
4910
|
allowZero: true,
|
|
4393
|
-
regexMessage: 'maxFee must be a numeric string with
|
|
4911
|
+
regexMessage: 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals.',
|
|
4394
4912
|
attributeName: 'maxFee',
|
|
4395
4913
|
maxDecimals: 6,
|
|
4396
4914
|
})(z.string()))
|
|
@@ -4400,13 +4918,19 @@ z.object({
|
|
|
4400
4918
|
});
|
|
4401
4919
|
|
|
4402
4920
|
/**
|
|
4403
|
-
*
|
|
4921
|
+
* Error message constants for validation
|
|
4922
|
+
*/
|
|
4923
|
+
const AMOUNT_VALIDATION_MESSAGE = 'Amount must be a numeric string with dot (.) as decimal separator (e.g., "0.1", ".1", "10.5", "1000.50"), with no thousand separators or comma decimals.';
|
|
4924
|
+
const MAX_FEE_VALIDATION_MESSAGE = 'maxFee must be a numeric string with dot (.) as decimal separator (e.g., "1", "0.5", ".5", "1.5"), with no thousand separators or comma decimals.';
|
|
4925
|
+
/**
|
|
4926
|
+
* Schema for validating AdapterContext for bridge operations.
|
|
4404
4927
|
* Must always contain both adapter and chain explicitly.
|
|
4928
|
+
*
|
|
4405
4929
|
* Optionally includes address for developer-controlled adapters.
|
|
4406
4930
|
*/
|
|
4407
4931
|
const adapterContextSchema = z.object({
|
|
4408
4932
|
adapter: adapterSchema,
|
|
4409
|
-
chain:
|
|
4933
|
+
chain: bridgeChainIdentifierSchema,
|
|
4410
4934
|
address: z.string().optional(),
|
|
4411
4935
|
});
|
|
4412
4936
|
/**
|
|
@@ -4492,7 +5016,7 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
|
|
|
4492
5016
|
.min(1, 'Required')
|
|
4493
5017
|
.pipe(createDecimalStringValidator({
|
|
4494
5018
|
allowZero: false,
|
|
4495
|
-
regexMessage:
|
|
5019
|
+
regexMessage: AMOUNT_VALIDATION_MESSAGE,
|
|
4496
5020
|
attributeName: 'amount',
|
|
4497
5021
|
maxDecimals: 6,
|
|
4498
5022
|
})(z.string())),
|
|
@@ -4505,7 +5029,7 @@ const bridgeParamsWithChainIdentifierSchema = z.object({
|
|
|
4505
5029
|
.min(1, 'Required')
|
|
4506
5030
|
.pipe(createDecimalStringValidator({
|
|
4507
5031
|
allowZero: true,
|
|
4508
|
-
regexMessage:
|
|
5032
|
+
regexMessage: MAX_FEE_VALIDATION_MESSAGE,
|
|
4509
5033
|
attributeName: 'maxFee',
|
|
4510
5034
|
maxDecimals: 6,
|
|
4511
5035
|
})(z.string()))
|
|
@@ -4714,6 +5238,10 @@ function resolveConfig(params) {
|
|
|
4714
5238
|
async function resolveBridgeParams(params) {
|
|
4715
5239
|
const fromChain = resolveChainDefinition(params.from);
|
|
4716
5240
|
const toChain = resolveChainDefinition(params.to);
|
|
5241
|
+
// Validate adapter chain support after resolution
|
|
5242
|
+
// This ensures adapters support the resolved chains before proceeding
|
|
5243
|
+
params.from.adapter.validateChainSupport(fromChain);
|
|
5244
|
+
params.to.adapter.validateChainSupport(toChain);
|
|
4717
5245
|
const [fromAddress, toAddress] = await Promise.all([
|
|
4718
5246
|
resolveAddress(params.from),
|
|
4719
5247
|
resolveAddress(params.to),
|
|
@@ -4752,6 +5280,70 @@ async function resolveBridgeParams(params) {
|
|
|
4752
5280
|
*/
|
|
4753
5281
|
const getDefaultProviders = () => [new CCTPV2BridgingProvider()];
|
|
4754
5282
|
|
|
5283
|
+
/**
|
|
5284
|
+
* A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
|
|
5285
|
+
* @param formatDirection - The direction to format the amount in.
|
|
5286
|
+
* @returns A function that transforms an amount into a human-readable string or a bigint string.
|
|
5287
|
+
*/
|
|
5288
|
+
const getAmountTransformer = (formatDirection) => formatDirection === 'to-human-readable'
|
|
5289
|
+
? (params) => formatAmount(params)
|
|
5290
|
+
: (params) => parseAmount(params).toString();
|
|
5291
|
+
/**
|
|
5292
|
+
* Format the bridge result into human-readable string values for the user or bigint string values for internal use.
|
|
5293
|
+
* @param result - The bridge result to format.
|
|
5294
|
+
* @param formatDirection - The direction to format the result in.
|
|
5295
|
+
* - If 'to-human-readable', the result will be converted to human-readable string values.
|
|
5296
|
+
* - If 'to-internal', the result will be converted to bigint string values (usually for internal use).
|
|
5297
|
+
* @returns The formatted bridge result.
|
|
5298
|
+
*
|
|
5299
|
+
* @example
|
|
5300
|
+
* ```typescript
|
|
5301
|
+
* const result = await kit.bridge({
|
|
5302
|
+
* amount: '1000000',
|
|
5303
|
+
* token: 'USDC',
|
|
5304
|
+
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
5305
|
+
* to: { adapter: adapter, chain: 'Base' },
|
|
5306
|
+
* })
|
|
5307
|
+
*
|
|
5308
|
+
* // Format the bridge result into human-readable string values for the user
|
|
5309
|
+
* const formattedResultHumanReadable = formatBridgeResult(result, 'to-human-readable')
|
|
5310
|
+
* console.log(formattedResultHumanReadable)
|
|
5311
|
+
*
|
|
5312
|
+
* // Format the bridge result into bigint string values for internal use
|
|
5313
|
+
* const formattedResultInternal = formatBridgeResult(result, 'to-internal')
|
|
5314
|
+
* console.log(formattedResultInternal)
|
|
5315
|
+
* ```
|
|
5316
|
+
*/
|
|
5317
|
+
const formatBridgeResult = (result, formatDirection) => {
|
|
5318
|
+
const transform = getAmountTransformer(formatDirection);
|
|
5319
|
+
return {
|
|
5320
|
+
...result,
|
|
5321
|
+
amount: transform({ value: result.amount, token: result.token }),
|
|
5322
|
+
...(result.config && {
|
|
5323
|
+
config: {
|
|
5324
|
+
...result.config,
|
|
5325
|
+
...(result.config.maxFee && {
|
|
5326
|
+
maxFee: transform({
|
|
5327
|
+
value: result.config.maxFee,
|
|
5328
|
+
token: result.token,
|
|
5329
|
+
}),
|
|
5330
|
+
}),
|
|
5331
|
+
...(result.config.customFee && {
|
|
5332
|
+
customFee: {
|
|
5333
|
+
...result.config.customFee,
|
|
5334
|
+
value: result.config.customFee.value
|
|
5335
|
+
? transform({
|
|
5336
|
+
value: result.config.customFee.value,
|
|
5337
|
+
token: result.token,
|
|
5338
|
+
})
|
|
5339
|
+
: undefined,
|
|
5340
|
+
},
|
|
5341
|
+
}),
|
|
5342
|
+
},
|
|
5343
|
+
}),
|
|
5344
|
+
};
|
|
5345
|
+
};
|
|
5346
|
+
|
|
4755
5347
|
/**
|
|
4756
5348
|
* Route cross-chain USDC bridging through Circle's Cross-Chain Transfer Protocol v2 (CCTPv2).
|
|
4757
5349
|
*
|
|
@@ -4766,18 +5358,18 @@ const getDefaultProviders = () => [new CCTPV2BridgingProvider()];
|
|
|
4766
5358
|
*
|
|
4767
5359
|
* @param params - The bridge parameters containing source, destination, amount, and token
|
|
4768
5360
|
* @returns Promise resolving to the bridge result with transaction details and steps
|
|
4769
|
-
* @throws {
|
|
5361
|
+
* @throws {KitError} If the parameters are invalid
|
|
4770
5362
|
* @throws {BridgeError} If the bridging process fails
|
|
4771
5363
|
* @throws {UnsupportedRouteError} If the route is not supported
|
|
4772
5364
|
*
|
|
4773
5365
|
* @example
|
|
4774
5366
|
* ```typescript
|
|
4775
5367
|
* import { BridgeKit } from '@circle-fin/bridge-kit'
|
|
4776
|
-
* import {
|
|
5368
|
+
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
4777
5369
|
*
|
|
4778
5370
|
* // Create kit with default CCTPv2 provider
|
|
4779
5371
|
* const kit = new BridgeKit()
|
|
4780
|
-
* const adapter =
|
|
5372
|
+
* const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
|
|
4781
5373
|
*
|
|
4782
5374
|
* // Execute cross-chain transfer
|
|
4783
5375
|
* const result = await kit.bridge({
|
|
@@ -4850,18 +5442,18 @@ class BridgeKit {
|
|
|
4850
5442
|
*
|
|
4851
5443
|
* @param params - The transfer parameters containing source, destination, amount, and token
|
|
4852
5444
|
* @returns Promise resolving to the transfer result with transaction details and steps
|
|
4853
|
-
* @throws {
|
|
5445
|
+
* @throws {KitError} When any parameter validation fails.
|
|
4854
5446
|
* @throws {Error} When CCTPv2 does not support the specified route.
|
|
4855
5447
|
*
|
|
4856
5448
|
* @example
|
|
4857
5449
|
* ```typescript
|
|
4858
5450
|
* import { BridgeKit } from '@circle-fin/bridge-kit'
|
|
4859
|
-
* import {
|
|
5451
|
+
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
4860
5452
|
*
|
|
4861
5453
|
* const kit = new BridgeKit()
|
|
4862
5454
|
*
|
|
4863
5455
|
* // Create a single adapter that can work across chains
|
|
4864
|
-
* const adapter =
|
|
5456
|
+
* const adapter = createViemAdapterFromPrivateKey({
|
|
4865
5457
|
* privateKey: process.env.PRIVATE_KEY,
|
|
4866
5458
|
* })
|
|
4867
5459
|
*
|
|
@@ -4891,7 +5483,7 @@ class BridgeKit {
|
|
|
4891
5483
|
async bridge(params) {
|
|
4892
5484
|
// First validate the parameters
|
|
4893
5485
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
4894
|
-
// Then resolve chain definitions
|
|
5486
|
+
// Then resolve chain definitions (includes adapter chain support validation)
|
|
4895
5487
|
const resolvedParams = await resolveBridgeParams(params);
|
|
4896
5488
|
// Validate network compatibility
|
|
4897
5489
|
this.validateNetworkCompatibility(resolvedParams);
|
|
@@ -4900,7 +5492,8 @@ class BridgeKit {
|
|
|
4900
5492
|
// Find a provider that supports this route
|
|
4901
5493
|
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
4902
5494
|
// Execute the transfer using the provider
|
|
4903
|
-
|
|
5495
|
+
// Format the bridge result into human-readable string values for the user
|
|
5496
|
+
return formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
|
|
4904
5497
|
}
|
|
4905
5498
|
/**
|
|
4906
5499
|
* Retry a failed or incomplete cross-chain USDC bridge operation.
|
|
@@ -4933,10 +5526,14 @@ class BridgeKit {
|
|
|
4933
5526
|
* @example
|
|
4934
5527
|
* ```typescript
|
|
4935
5528
|
* import { BridgeKit } from '@circle-fin/bridge-kit'
|
|
4936
|
-
* import {
|
|
5529
|
+
* import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
4937
5530
|
*
|
|
4938
5531
|
* const kit = new BridgeKit()
|
|
4939
5532
|
*
|
|
5533
|
+
* // Create adapters for source and destination chains
|
|
5534
|
+
* const sourceAdapter = createViemAdapterFromPrivateKey({ privateKey: '...' })
|
|
5535
|
+
* const destAdapter = createViemAdapterFromPrivateKey({ privateKey: '...' })
|
|
5536
|
+
*
|
|
4940
5537
|
* // Assume we have a failed bridge result from a previous operation
|
|
4941
5538
|
* const failedResult: BridgeResult = {
|
|
4942
5539
|
* state: 'error',
|
|
@@ -4968,7 +5565,11 @@ class BridgeKit {
|
|
|
4968
5565
|
if (!provider) {
|
|
4969
5566
|
throw new Error(`Provider ${result.provider} not found`);
|
|
4970
5567
|
}
|
|
4971
|
-
|
|
5568
|
+
// Format the bridge result into bigint string values for internal use
|
|
5569
|
+
const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
|
|
5570
|
+
// Execute the retry using the provider
|
|
5571
|
+
// Format the bridge result into human-readable string values for the user
|
|
5572
|
+
return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context), 'to-human-readable');
|
|
4972
5573
|
}
|
|
4973
5574
|
/**
|
|
4974
5575
|
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
@@ -4978,7 +5579,7 @@ class BridgeKit {
|
|
|
4978
5579
|
* as the bridge method but stops before execution.
|
|
4979
5580
|
* @param params - The bridge parameters for cost estimation
|
|
4980
5581
|
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
4981
|
-
* @throws {
|
|
5582
|
+
* @throws {KitError} When the parameters are invalid.
|
|
4982
5583
|
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
4983
5584
|
*
|
|
4984
5585
|
* @example
|
|
@@ -4995,7 +5596,7 @@ class BridgeKit {
|
|
|
4995
5596
|
async estimate(params) {
|
|
4996
5597
|
// First validate the parameters
|
|
4997
5598
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
4998
|
-
// Then resolve chain definitions
|
|
5599
|
+
// Then resolve chain definitions (includes adapter chain support validation)
|
|
4999
5600
|
const resolvedParams = await resolveBridgeParams(params);
|
|
5000
5601
|
// Validate network compatibility
|
|
5001
5602
|
this.validateNetworkCompatibility(resolvedParams);
|
|
@@ -5095,7 +5696,7 @@ class BridgeKit {
|
|
|
5095
5696
|
const existingFee = providerParams.config?.customFee?.value;
|
|
5096
5697
|
const existingFeeRecipient = providerParams.config?.customFee?.recipientAddress;
|
|
5097
5698
|
// Fill missing values using kit-level configuration (if available)
|
|
5098
|
-
const fee = existingFee ?? (await this.
|
|
5699
|
+
const fee = existingFee ?? (await this.resolveFee(providerParams));
|
|
5099
5700
|
const feeRecipient = existingFeeRecipient ??
|
|
5100
5701
|
(await this.customFeePolicy?.resolveFeeRecipientAddress(providerParams.source.chain, providerParams));
|
|
5101
5702
|
// Only attach customFee if at least one value is defined
|
|
@@ -5111,56 +5712,94 @@ class BridgeKit {
|
|
|
5111
5712
|
return providerParams;
|
|
5112
5713
|
}
|
|
5113
5714
|
/**
|
|
5114
|
-
*
|
|
5715
|
+
* Resolve the custom fee for a bridge transfer.
|
|
5115
5716
|
*
|
|
5116
|
-
*
|
|
5117
|
-
* -
|
|
5118
|
-
* -
|
|
5717
|
+
* Checks which fee function the user provided and executes accordingly:
|
|
5718
|
+
* - `computeFee`: receives human-readable amounts, returns human-readable fee
|
|
5719
|
+
* - `calculateFee` (deprecated): receives smallest units, returns smallest units
|
|
5119
5720
|
*
|
|
5120
|
-
*
|
|
5121
|
-
*
|
|
5721
|
+
* @param providerParams - The resolved bridge parameters (amounts in smallest units).
|
|
5722
|
+
* @returns The resolved fee in smallest units, or undefined if no fee policy is set.
|
|
5723
|
+
*/
|
|
5724
|
+
async resolveFee(providerParams) {
|
|
5725
|
+
if (!this.customFeePolicy) {
|
|
5726
|
+
return undefined;
|
|
5727
|
+
}
|
|
5728
|
+
const token = providerParams.token ?? 'USDC';
|
|
5729
|
+
if (token !== 'USDC') {
|
|
5730
|
+
throw createValidationFailedError$1('token', token, 'Custom fee policy only supports USDC');
|
|
5731
|
+
}
|
|
5732
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- intentionally support deprecated calculateFee
|
|
5733
|
+
const { computeFee, calculateFee } = this.customFeePolicy;
|
|
5734
|
+
let fee;
|
|
5735
|
+
if (computeFee) {
|
|
5736
|
+
// Convert amount to human-readable for the user's computeFee
|
|
5737
|
+
const humanReadableParams = {
|
|
5738
|
+
...providerParams,
|
|
5739
|
+
amount: formatUnits(providerParams.amount, 6),
|
|
5740
|
+
};
|
|
5741
|
+
fee = await computeFee(humanReadableParams);
|
|
5742
|
+
}
|
|
5743
|
+
// Fall back to deprecated calculateFee (receives smallest units)
|
|
5744
|
+
if (calculateFee) {
|
|
5745
|
+
fee = await calculateFee(providerParams);
|
|
5746
|
+
}
|
|
5747
|
+
if (fee) {
|
|
5748
|
+
return parseUnits(fee, 6).toString();
|
|
5749
|
+
}
|
|
5750
|
+
return undefined;
|
|
5751
|
+
}
|
|
5752
|
+
/**
|
|
5753
|
+
* Set the custom fee policy for the kit.
|
|
5754
|
+
*
|
|
5755
|
+
* Use `computeFee` (recommended) for human-readable amounts, or `calculateFee`
|
|
5756
|
+
* (deprecated) for smallest-unit amounts. Only one should be provided.
|
|
5757
|
+
*
|
|
5758
|
+
* ```text
|
|
5759
|
+
* Transfer amount (user input, e.g., 1,000 USDC)
|
|
5760
|
+
* ↓ Wallet signs for transfer + custom fee (e.g., 1,000 + 10 = 1,010 USDC)
|
|
5761
|
+
* ↓ Custom fee split (10% Circle, 90% your recipientAddress wallet)
|
|
5762
|
+
* ↓ Full transfer amount (1,000 USDC) forwarded to CCTPv2
|
|
5763
|
+
* ↓ CCTPv2 protocol fee (e.g., 0.1 USDC) deducted from transfer amount
|
|
5764
|
+
* ↓ User receives funds on destination chain (e.g., 999.9 USDC)
|
|
5765
|
+
* ```
|
|
5122
5766
|
*
|
|
5123
5767
|
* @param customFeePolicy - The custom fee policy to set.
|
|
5124
|
-
* @throws {
|
|
5768
|
+
* @throws {KitError} If the custom fee policy is invalid or missing required functions
|
|
5125
5769
|
*
|
|
5126
5770
|
* @example
|
|
5127
5771
|
* ```typescript
|
|
5128
5772
|
* import { BridgeKit } from '@circle-fin/bridge-kit'
|
|
5129
|
-
* import { Blockchain } from '@core/chains'
|
|
5130
5773
|
*
|
|
5131
5774
|
* const kit = new BridgeKit()
|
|
5132
5775
|
*
|
|
5133
5776
|
* kit.setCustomFeePolicy({
|
|
5134
|
-
*
|
|
5135
|
-
*
|
|
5136
|
-
*
|
|
5137
|
-
*
|
|
5138
|
-
*
|
|
5139
|
-
*
|
|
5777
|
+
* // computeFee receives human-readable amounts (e.g., '100' for 100 USDC)
|
|
5778
|
+
* computeFee: (params) => {
|
|
5779
|
+
* const amount = parseFloat(params.amount)
|
|
5780
|
+
*
|
|
5781
|
+
* // 1% fee, bounded to 5-50 USDC
|
|
5782
|
+
* const fee = Math.min(Math.max(amount * 0.01, 5), 50)
|
|
5783
|
+
* return fee.toFixed(6)
|
|
5140
5784
|
* },
|
|
5141
|
-
* resolveFeeRecipientAddress: (feePayoutChain
|
|
5142
|
-
*
|
|
5143
|
-
*
|
|
5144
|
-
*
|
|
5145
|
-
* : '0x1E1A18B7bD95bcFcFb4d6E245D289C1e95547b35';
|
|
5785
|
+
* resolveFeeRecipientAddress: (feePayoutChain) => {
|
|
5786
|
+
* return feePayoutChain.type === 'solana'
|
|
5787
|
+
* ? '9xQeWvG816bUx9EP9MnZ4buHh3A6E2dFQa4Xz6V7C7Gn'
|
|
5788
|
+
* : '0x23f9a5BEA7B92a0638520607407BC7f0310aEeD4'
|
|
5146
5789
|
* },
|
|
5147
|
-
* })
|
|
5790
|
+
* })
|
|
5791
|
+
*
|
|
5792
|
+
* // 100 USDC transfer + 5 USDC custom fee results:
|
|
5793
|
+
* // - Wallet signs for 105 USDC total.
|
|
5794
|
+
* // - Circle receives 0.5 USDC (10% share of the custom fee).
|
|
5795
|
+
* // - Your recipientAddress wallet receives 4.5 USDC.
|
|
5796
|
+
* // - CCTPv2 processes 100 USDC and later deducts its own protocol fee.
|
|
5148
5797
|
* ```
|
|
5149
5798
|
*/
|
|
5150
5799
|
setCustomFeePolicy(customFeePolicy) {
|
|
5151
5800
|
assertCustomFeePolicy(customFeePolicy);
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
const formattedCalculateFee = async (params) => {
|
|
5155
|
-
const fee = await calculateFee(params);
|
|
5156
|
-
const token = params.token ?? 'USDC';
|
|
5157
|
-
return token === 'USDC' ? parseUnits(fee, 6).toString() : fee;
|
|
5158
|
-
};
|
|
5159
|
-
// Return a new custom fee policy with the formatted calculateFee function
|
|
5160
|
-
this.customFeePolicy = {
|
|
5161
|
-
calculateFee: formattedCalculateFee,
|
|
5162
|
-
resolveFeeRecipientAddress,
|
|
5163
|
-
};
|
|
5801
|
+
// Store the policy as-is; resolveFee handles the branching logic
|
|
5802
|
+
this.customFeePolicy = customFeePolicy;
|
|
5164
5803
|
}
|
|
5165
5804
|
/**
|
|
5166
5805
|
* Remove the custom fee policy for the kit.
|
|
@@ -5185,5 +5824,5 @@ class BridgeKit {
|
|
|
5185
5824
|
// Auto-register this kit for user agent tracking
|
|
5186
5825
|
registerKit(`${pkg.name}/${pkg.version}`);
|
|
5187
5826
|
|
|
5188
|
-
export { Blockchain, BridgeKit, KitError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isFatalError, isInputError, isKitError, isRetryableError, setExternalPrefix };
|
|
5827
|
+
export { Blockchain, BridgeChain, BridgeKit, KitError, TransferSpeed, bridgeParamsWithChainIdentifierSchema, getErrorCode, getErrorMessage, isFatalError, isInputError, isKitError, isRetryableError, resolveChainIdentifier, setExternalPrefix };
|
|
5189
5828
|
//# sourceMappingURL=index.mjs.map
|