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