@circle-fin/adapter-ethers-v6 0.0.2-alpha.7 → 1.0.1

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.
Files changed (5) hide show
  1. package/README.md +39 -13
  2. package/index.cjs.js +457 -252
  3. package/index.d.ts +276 -97
  4. package/index.mjs +396 -194
  5. package/package.json +6 -4
package/index.mjs CHANGED
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { JsonRpcProvider, Wallet, BrowserProvider, Interface, Contract } from 'ethers';
20
+ export { JsonRpcProvider } from 'ethers';
20
21
  import { z } from 'zod';
21
22
  import { hexlify, hexZeroPad } from '@ethersproject/bytes';
22
23
  import { getAddress } from '@ethersproject/address';
@@ -262,6 +263,7 @@ var Blockchain;
262
263
  Blockchain["Algorand_Testnet"] = "Algorand_Testnet";
263
264
  Blockchain["Aptos"] = "Aptos";
264
265
  Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
266
+ Blockchain["Arc_Testnet"] = "Arc_Testnet";
265
267
  Blockchain["Arbitrum"] = "Arbitrum";
266
268
  Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
267
269
  Blockchain["Avalanche"] = "Avalanche";
@@ -482,6 +484,48 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
482
484
  */
483
485
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
484
486
 
487
+ /**
488
+ * Arc Testnet chain definition
489
+ * @remarks
490
+ * This represents the test network for the Arc blockchain,
491
+ * Circle's EVM-compatible Layer-1 designed for stablecoin finance
492
+ * and asset tokenization. Arc uses USDC as the native gas token and
493
+ * features the Malachite Byzantine Fault Tolerant (BFT) consensus
494
+ * engine for sub-second finality.
495
+ */
496
+ const ArcTestnet = defineChain({
497
+ type: 'evm',
498
+ chain: Blockchain.Arc_Testnet,
499
+ name: 'Arc Testnet',
500
+ title: 'ArcTestnet',
501
+ nativeCurrency: {
502
+ name: 'Arc',
503
+ symbol: 'Arc',
504
+ decimals: 18,
505
+ },
506
+ chainId: 5042002,
507
+ isTestnet: true,
508
+ explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
509
+ rpcEndpoints: ['https://rpc.testnet.arc.network/'],
510
+ eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
511
+ usdcAddress: '0x3600000000000000000000000000000000000000',
512
+ cctp: {
513
+ domain: 26,
514
+ contracts: {
515
+ v2: {
516
+ type: 'split',
517
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
518
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
519
+ confirmations: 1,
520
+ fastConfirmations: 1,
521
+ },
522
+ },
523
+ },
524
+ kitContracts: {
525
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
526
+ },
527
+ });
528
+
485
529
  /**
486
530
  * Arbitrum Mainnet chain definition
487
531
  * @remarks
@@ -1773,26 +1817,26 @@ const Sonic = defineChain({
1773
1817
  });
1774
1818
 
1775
1819
  /**
1776
- * Sonic Blaze Testnet chain definition
1820
+ * Sonic Testnet chain definition
1777
1821
  * @remarks
1778
1822
  * This represents the official test network for the Sonic blockchain.
1779
1823
  */
1780
1824
  const SonicTestnet = defineChain({
1781
1825
  type: 'evm',
1782
1826
  chain: Blockchain.Sonic_Testnet,
1783
- name: 'Sonic Blaze Testnet',
1784
- title: 'Sonic Blaze Testnet',
1827
+ name: 'Sonic Testnet',
1828
+ title: 'Sonic Testnet',
1785
1829
  nativeCurrency: {
1786
1830
  name: 'Sonic',
1787
1831
  symbol: 'S',
1788
1832
  decimals: 18,
1789
1833
  },
1790
- chainId: 57054,
1834
+ chainId: 14601,
1791
1835
  isTestnet: true,
1792
1836
  explorerUrl: 'https://testnet.sonicscan.org/tx/{hash}',
1793
- rpcEndpoints: ['https://rpc.blaze.soniclabs.com'],
1837
+ rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
1794
1838
  eurcAddress: null,
1795
- usdcAddress: '0xA4879Fed32Ecbef99399e5cbC247E533421C4eC6',
1839
+ usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
1796
1840
  cctp: {
1797
1841
  domain: 13,
1798
1842
  contracts: {
@@ -2309,6 +2353,7 @@ var Blockchains = /*#__PURE__*/Object.freeze({
2309
2353
  AptosTestnet: AptosTestnet,
2310
2354
  Arbitrum: Arbitrum,
2311
2355
  ArbitrumSepolia: ArbitrumSepolia,
2356
+ ArcTestnet: ArcTestnet,
2312
2357
  Avalanche: Avalanche,
2313
2358
  AvalancheFuji: AvalancheFuji,
2314
2359
  Base: Base,
@@ -2441,6 +2486,11 @@ const baseChainDefinitionSchema = z.object({
2441
2486
  eurcAddress: z.string().nullable(),
2442
2487
  usdcAddress: z.string().nullable(),
2443
2488
  cctp: z.any().nullable(), // We'll accept any CCTP config structure
2489
+ kitContracts: z
2490
+ .object({
2491
+ bridge: z.string().optional(),
2492
+ })
2493
+ .optional(),
2444
2494
  });
2445
2495
  /**
2446
2496
  * Zod schema for validating EVM chain definitions specifically.
@@ -2472,13 +2522,15 @@ const baseChainDefinitionSchema = z.object({
2472
2522
  * }
2473
2523
  * ```
2474
2524
  */
2475
- const evmChainDefinitionSchema = baseChainDefinitionSchema.extend({
2525
+ const evmChainDefinitionSchema = baseChainDefinitionSchema
2526
+ .extend({
2476
2527
  type: z.literal('evm'),
2477
2528
  chainId: z.number({
2478
2529
  required_error: 'EVM chains must have a chainId. Please provide a valid EVM chain ID.',
2479
2530
  invalid_type_error: 'EVM chain ID must be a number.',
2480
2531
  }),
2481
- });
2532
+ })
2533
+ .strict(); //// Reject any additional properties not defined in the schema
2482
2534
  /**
2483
2535
  * Zod schema for validating non-EVM chain definitions.
2484
2536
  * This schema extends the base schema with non-EVM specific properties.
@@ -2498,7 +2550,7 @@ const nonEvmChainDefinitionSchema = baseChainDefinitionSchema
2498
2550
  'polkadot',
2499
2551
  ]),
2500
2552
  })
2501
- .strict(); // Reject additional properties like chainId
2553
+ .strict(); // Reject any additional properties not defined in the schema
2502
2554
  /**
2503
2555
  * Discriminated union schema for all chain definitions.
2504
2556
  * This schema validates different chain types based on their 'type' field.
@@ -2980,67 +3032,41 @@ class KitError extends Error {
2980
3032
  }
2981
3033
 
2982
3034
  /**
2983
- * Standardized error codes for INPUT type errors.
3035
+ * Minimum error code for INPUT type errors.
3036
+ * INPUT errors represent validation failures and invalid parameters.
3037
+ */
3038
+ /**
3039
+ * Standardized error definitions for INPUT type errors.
3040
+ *
3041
+ * Each entry combines the numeric error code with its corresponding
3042
+ * string name to ensure consistency when creating error instances.
2984
3043
  *
2985
3044
  * Error codes follow a hierarchical numbering scheme where the first digit
2986
3045
  * indicates the error category (1 = INPUT) and subsequent digits provide
2987
3046
  * specific error identification within that category.
2988
3047
  *
3048
+ *
2989
3049
  * @example
2990
3050
  * ```typescript
2991
- * import { InputErrorCode, InputErrorName } from '@core/errors'
3051
+ * import { InputError } from '@core/errors'
2992
3052
  *
2993
3053
  * const error = new KitError({
2994
- * code: InputErrorCode.NETWORK_MISMATCH,
2995
- * name: InputErrorName.NETWORK_MISMATCH,
3054
+ * ...InputError.NETWORK_MISMATCH,
2996
3055
  * recoverability: 'FATAL',
2997
3056
  * message: 'Source and destination networks must be different'
2998
3057
  * })
2999
- * ```
3000
- */
3001
- var InputErrorCode;
3002
- (function (InputErrorCode) {
3003
- /** Network type mismatch between chains (mainnet vs testnet) */
3004
- InputErrorCode[InputErrorCode["NETWORK_MISMATCH"] = 1001] = "NETWORK_MISMATCH";
3005
- /** Invalid amount format or value (negative, zero, or malformed) */
3006
- InputErrorCode[InputErrorCode["INVALID_AMOUNT"] = 1002] = "INVALID_AMOUNT";
3007
- /** Unsupported or invalid bridge route configuration */
3008
- InputErrorCode[InputErrorCode["UNSUPPORTED_ROUTE"] = 1003] = "UNSUPPORTED_ROUTE";
3009
- /** Invalid wallet or contract address format */
3010
- InputErrorCode[InputErrorCode["INVALID_ADDRESS"] = 1004] = "INVALID_ADDRESS";
3011
- /** Invalid or unsupported chain identifier */
3012
- InputErrorCode[InputErrorCode["INVALID_CHAIN"] = 1005] = "INVALID_CHAIN";
3013
- /** General validation failure for complex validation rules */
3014
- InputErrorCode[InputErrorCode["VALIDATION_FAILED"] = 1098] = "VALIDATION_FAILED";
3015
- })(InputErrorCode || (InputErrorCode = {}));
3016
- /**
3017
- * Standardized error names for INPUT type errors.
3018
- *
3019
- * These names correspond 1:1 with InputErrorCode and should always
3020
- * be used together to ensure consistency across error instances.
3021
3058
  *
3022
- * @example
3023
- * ```typescript
3024
- * import { InputErrorCode, InputErrorName } from '@core/errors'
3025
- *
3026
- * // Use matching code and name enums
3027
- * const error = new KitError({
3028
- * code: InputErrorCode.NETWORK_MISMATCH,
3029
- * name: InputErrorName.NETWORK_MISMATCH,
3030
- * recoverability: 'FATAL',
3031
- * message: 'Network mismatch detected'
3032
- * })
3059
+ * // Access code and name individually if needed
3060
+ * console.log(InputError.NETWORK_MISMATCH.code) // 1001
3061
+ * console.log(InputError.NETWORK_MISMATCH.name) // 'INPUT_NETWORK_MISMATCH'
3033
3062
  * ```
3034
3063
  */
3035
- var InputErrorName;
3036
- (function (InputErrorName) {
3037
- InputErrorName["NETWORK_MISMATCH"] = "INPUT_NETWORK_MISMATCH";
3038
- InputErrorName["INVALID_AMOUNT"] = "INPUT_INVALID_AMOUNT";
3039
- InputErrorName["UNSUPPORTED_ROUTE"] = "INPUT_UNSUPPORTED_ROUTE";
3040
- InputErrorName["INVALID_ADDRESS"] = "INPUT_INVALID_ADDRESS";
3041
- InputErrorName["INVALID_CHAIN"] = "INPUT_INVALID_CHAIN";
3042
- InputErrorName["VALIDATION_FAILED"] = "INPUT_VALIDATION_FAILED";
3043
- })(InputErrorName || (InputErrorName = {}));
3064
+ const InputError = {
3065
+ /** Unsupported or invalid bridge route configuration */
3066
+ UNSUPPORTED_ROUTE: {
3067
+ code: 1003,
3068
+ name: 'INPUT_UNSUPPORTED_ROUTE',
3069
+ }};
3044
3070
 
3045
3071
  /**
3046
3072
  * Creates error for unsupported bridge route.
@@ -3062,8 +3088,7 @@ var InputErrorName;
3062
3088
  */
3063
3089
  function createUnsupportedRouteError(source, destination) {
3064
3090
  const errorDetails = {
3065
- code: InputErrorCode.UNSUPPORTED_ROUTE,
3066
- name: InputErrorName.UNSUPPORTED_ROUTE,
3091
+ ...InputError.UNSUPPORTED_ROUTE,
3067
3092
  recoverability: 'FATAL',
3068
3093
  message: `Route from ${source} to ${destination} is not supported.`,
3069
3094
  cause: {
@@ -3112,26 +3137,6 @@ class Adapter {
3112
3137
  * ```
3113
3138
  */
3114
3139
  capabilities;
3115
- /**
3116
- * Default chain for operations when none is explicitly provided.
3117
- *
3118
- * This allows adapters to have sensible defaults for chain operations,
3119
- * reducing the need for explicit chain specification in every call.
3120
- *
3121
- * @remarks
3122
- * This is optional for backward compatibility and because some adapter types
3123
- * (like developer-controlled adapters) may not have meaningful defaults.
3124
- *
3125
- * @example
3126
- * ```typescript
3127
- * // User-controlled adapter with default chain
3128
- * defaultChain = Ethereum
3129
- *
3130
- * // Developer-controlled adapter (no default)
3131
- * defaultChain = undefined
3132
- * ```
3133
- */
3134
- defaultChain;
3135
3140
  /**
3136
3141
  * Registry of available actions for this adapter.
3137
3142
  *
@@ -3203,17 +3208,14 @@ class Adapter {
3203
3208
  * - **Browser wallet adapters**: Request chain switch via EIP-1193 or equivalent
3204
3209
  * - **Multi-entity adapters**: Validate chain support (operations are contextual)
3205
3210
  *
3206
- * @param chain - The target chain for operations. If not provided, uses the adapter's defaultChain.
3211
+ * @param chain - The target chain for operations.
3207
3212
  * @returns A promise that resolves when the adapter is operating on the specified chain.
3208
3213
  * @throws When the target chain is not supported or chain switching fails.
3209
3214
  *
3210
3215
  * @remarks
3211
- * This method replaces the pattern of calling `getChain()` to check current chain and then
3212
- * manually switching. It provides a declarative "ensure this chain" interface for operations.
3213
- *
3214
- * **Backward Compatibility**: The default implementation provides basic validation but
3215
- * doesn't perform actual chain switching. Concrete adapter implementations should override
3216
- * this method to provide proper chain switching logic.
3216
+ * This method always calls `switchToChain()` to ensure consistency across all adapter types.
3217
+ * The underlying implementations handle idempotent switching efficiently (e.g., browser wallets
3218
+ * gracefully handle switching to the current chain, private key adapters recreate lightweight clients).
3217
3219
  *
3218
3220
  * @example
3219
3221
  * ```typescript
@@ -3227,25 +3229,9 @@ class Adapter {
3227
3229
  * await circleWalletsAdapter.ensureChain(Ethereum)
3228
3230
  * ```
3229
3231
  */
3230
- async ensureChain(chain) {
3231
- const targetChain = chain ?? this.defaultChain;
3232
- if (!targetChain) {
3233
- return;
3234
- }
3232
+ async ensureChain(targetChain) {
3235
3233
  this.validateChainSupport(targetChain);
3236
- // Check if already on the target chain (skip for developer-controlled adapters)
3237
- if (this.capabilities?.addressContext !== 'developer-controlled') {
3238
- try {
3239
- const currentChain = await this.getChain();
3240
- if (currentChain.chain === targetChain.chain) {
3241
- return; // Already on the correct chain
3242
- }
3243
- }
3244
- catch {
3245
- // If getChain() fails, we'll proceed with switching anyway
3246
- }
3247
- }
3248
- // Delegate actual switching to the concrete implementation
3234
+ // Always delegate to switchToChain - implementations handle idempotent switching
3249
3235
  try {
3250
3236
  await this.switchToChain(targetChain);
3251
3237
  }
@@ -3808,7 +3794,6 @@ base58StringSchema.refine((value) => value.length >= 86 && value.length <= 88, '
3808
3794
  const adapterSchema = z.object({
3809
3795
  prepare: z.function(),
3810
3796
  waitForTransaction: z.function(),
3811
- getChain: z.function(),
3812
3797
  getAddress: z.function(),
3813
3798
  });
3814
3799
 
@@ -8070,6 +8055,246 @@ class EvmAdapter extends Adapter {
8070
8055
  }
8071
8056
  }
8072
8057
 
8058
+ // --- Signature Parsing Constants ---
8059
+ const SIGNATURE_BYTE_LENGTH = 65; // Total bytes in an Ethereum signature
8060
+ const HEX_CHARS_PER_BYTE = 2;
8061
+ const SIGNATURE_HEX_LENGTH = SIGNATURE_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
8062
+ const R_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE; // 32 bytes for 'r'
8063
+ const S_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE; // 32 bytes for 's'
8064
+ // --- Recovery ID Validation Constants ---
8065
+ /**
8066
+ * Legacy recovery IDs (pre-EIP-155 format):
8067
+ * - 0 or 1 (older clients) or 27, 28 (Ethereum before chain-id inclusion)
8068
+ */
8069
+ const LEGACY_V_VALUES = new Set([0, 1, 27, 28]);
8070
+ /**
8071
+ * Minimum v value indicating an EIP-155 style signature.
8072
+ * Calculated as 35 + (2 * chainId) + recoveryId (0 or 1).
8073
+ */
8074
+ const MIN_EIP155_V = 35;
8075
+ /**
8076
+ * parseSignature
8077
+ *
8078
+ * Parse a 65-byte ECDSA signature into its r, s, and v components, expressed in hex.
8079
+ *
8080
+ * Ethereum signatures are structured as:
8081
+ * signature = r (32 bytes) || s (32 bytes) || v (1 byte)
8082
+ *
8083
+ * - r, s: Big-endian hex values (32 bytes each)
8084
+ * - v: Recovery identifier, used by secp256k1 to reconstruct the signer’s public key
8085
+ *
8086
+ * @param signatureHex - Signature as a hex string, optionally prefixed with "0x".
8087
+ * @returns { r, s, v }
8088
+ * - r: Hex string of the R component.
8089
+ * - s: Hex string of the S component.
8090
+ * - v: Numeric recovery ID.
8091
+ *
8092
+ * @throws Error if:
8093
+ * - Input is not valid hex.
8094
+ * - Incorrect length (must be exactly 65 bytes / 130 hex chars).
8095
+ * - v is outside the supported range (Legacy or EIP-155 formula).
8096
+ *
8097
+ * Notes on EIP-155 overload:
8098
+ * EIP-155 repurposes the v field so that:
8099
+ * v = 35 + (2 * chainId) + recoveryId(0 or 1)
8100
+ * Any v value >= 35 indicates the chain-id is encoded, preventing cross-chain replay.
8101
+ * r and s values remain unchanged.
8102
+ *
8103
+ * Example:
8104
+ * ```typescript
8105
+ * const rawSig = '0x6c1b...f02b'
8106
+ * try {
8107
+ * const { r, s, v } = parseSignature(rawSig)
8108
+ * console.log('R:', r)
8109
+ * console.log('S:', s)
8110
+ * console.log('Recovery ID (v):', v)
8111
+ * } catch (err) {
8112
+ * console.error('Signature parse error:', err.message)
8113
+ * }
8114
+ * ```
8115
+ */
8116
+ function parseSignature(signatureHex) {
8117
+ // 1) Remove optional '0x' prefix
8118
+ const hex = signatureHex.startsWith('0x')
8119
+ ? signatureHex.slice(2)
8120
+ : signatureHex;
8121
+ // 2) Validate hex format and length
8122
+ const VALID_HEX_REGEX = /^[0-9a-fA-F]+$/;
8123
+ if (!VALID_HEX_REGEX.test(hex)) {
8124
+ throw new Error('Signature must be a valid hex string (0-9, a-f, A-F).');
8125
+ }
8126
+ if (hex.length !== SIGNATURE_HEX_LENGTH) {
8127
+ throw new Error(`Invalid length: expected ${SIGNATURE_HEX_LENGTH.toString()} hex chars (65 bytes), got ${hex.length.toString()}`);
8128
+ }
8129
+ // 3) Extract components
8130
+ const r = `0x${hex.slice(0, R_HEX_LENGTH)}`;
8131
+ const s = `0x${hex.slice(R_HEX_LENGTH, R_HEX_LENGTH + S_HEX_LENGTH)}`;
8132
+ const vValue = parseInt(hex.slice(R_HEX_LENGTH + S_HEX_LENGTH), 16);
8133
+ // 4) Ensure v is within supported values
8134
+ const isLegacy = LEGACY_V_VALUES.has(vValue);
8135
+ const isEIP155 = vValue >= MIN_EIP155_V;
8136
+ if (!isLegacy && !isEIP155) {
8137
+ throw new Error(`Unsupported v value: ${vValue.toString()}. Must be one of [${[
8138
+ ...LEGACY_V_VALUES,
8139
+ ].join(', ')}] or >=${MIN_EIP155_V.toString()}`);
8140
+ }
8141
+ return { r: r, s: s, v: vValue };
8142
+ }
8143
+ /**
8144
+ * buildTypedData
8145
+ *
8146
+ * Create an EIP-712 compliant "TypedData" object, encapsulating:
8147
+ * - Domain separator (application/contract identity, chain ID)
8148
+ * - Explicit type definitions (field names and types)
8149
+ * - Primary type name identifying the root object
8150
+ * - Message payload matching the defined schema
8151
+ *
8152
+ * This enforces structured, non-ambiguous signing, and guards against signature replay.
8153
+ *
8154
+ * @typeParam Types - Map of type names to arrays of { name, type } definitions.
8155
+ * @typeParam Msg - Object whose shape matches the primaryType fields.
8156
+ *
8157
+ * @param domain - EIP712Domain, e.g. { name, version, chainId, verifyingContract }.
8158
+ * @param types - Type definitions, e.g.:
8159
+ * {
8160
+ * Mail: [ { name: 'from', type: 'string' }, { name: 'contents', type: 'string' } ],
8161
+ * }
8162
+ * @param primaryType - Root key in types, e.g. 'Mail'.
8163
+ * @param message - The actual values, e.g. { from: 'Alice', contents: 'Hello' }.
8164
+ *
8165
+ * @returns TypedData object ready for use with signing libraries.
8166
+ *
8167
+ * Example:
8168
+ * ```typescript
8169
+ * const typedData = buildTypedData(
8170
+ * { name: 'MyDApp', version: '1', chainId: 1, verifyingContract: '0xabc...' },
8171
+ * { Message: [{ name: 'text', type: 'string' }] },
8172
+ * 'Message',
8173
+ * { text: 'Hello, EIP-712!' }
8174
+ * )
8175
+ * // Pass typedData to ethers.js signer._signTypedData(domain, types, message)
8176
+ * ```
8177
+ */
8178
+ function buildTypedData(domain, types, primaryType, message) {
8179
+ return { domain, types, primaryType, message };
8180
+ }
8181
+
8182
+ /**
8183
+ * Compute default deadline for permit signatures.
8184
+ *
8185
+ * Returns a timestamp 1 hour (3600 seconds) from the current time,
8186
+ * converted to Unix timestamp format as a bigint. This is commonly
8187
+ * used as the default expiration time for EIP-2612 permit signatures.
8188
+ *
8189
+ * @returns Unix timestamp (in seconds) 1 hour from now as a bigint
8190
+ *
8191
+ * @example
8192
+ * ```typescript
8193
+ * import { computeDefaultDeadline } from '@core/adapter-evm'
8194
+ *
8195
+ * const deadline = computeDefaultDeadline()
8196
+ * console.log(`Permit expires at: ${deadline}`)
8197
+ * // Output: Permit expires at: 1640998800
8198
+ * ```
8199
+ */
8200
+ function computeDefaultDeadline() {
8201
+ return BigInt(Math.floor(Date.now() / 1000) + 3600);
8202
+ }
8203
+
8204
+ /**
8205
+ * EIP-2612 permit type definition.
8206
+ * Defines the structure for permit signatures according to EIP-2612 specification.
8207
+ *
8208
+ * @see {@link https://eips.ethereum.org/EIPS/eip-2612 | EIP-2612 Specification}
8209
+ */
8210
+ const EIP2612_TYPES = {
8211
+ Permit: [
8212
+ { name: 'owner', type: 'address' },
8213
+ { name: 'spender', type: 'address' },
8214
+ { name: 'value', type: 'uint256' },
8215
+ { name: 'nonce', type: 'uint256' },
8216
+ { name: 'deadline', type: 'uint256' },
8217
+ ],
8218
+ };
8219
+
8220
+ /**
8221
+ * Build EIP-2612 typed data for permit signing.
8222
+ *
8223
+ * This function creates the complete EIP-712 typed data structure required
8224
+ * for EIP-2612 permit signatures, including automatic nonce fetching using
8225
+ * the adapter's built-in nonce fetching capability.
8226
+ *
8227
+ * **Address Formatting**: All addresses are automatically formatted with proper
8228
+ * EIP-55 checksumming using the `convertAddress` utility, ensuring compatibility
8229
+ * with strict validation libraries like viem.
8230
+ *
8231
+ * **Nonce Handling**: The nonce can be provided explicitly or will be fetched
8232
+ * automatically using the adapter's `fetchEIP2612Nonce` method, which queries
8233
+ * the token contract's `nonces(owner)` function.
8234
+ *
8235
+ * **Deadline Calculation**: If no deadline is provided, it defaults to 24 hours
8236
+ * from the current time (computed using `computeDefaultDeadline`).
8237
+ *
8238
+ * @param meta - Domain metadata for the token contract
8239
+ * @param adapter - Adapter instance with nonce-fetching capability
8240
+ * @param opts - EIP-2612 permit options including owner, spender, value
8241
+ * @returns Complete EIP-712 typed data ready for signing
8242
+ *
8243
+ * @example
8244
+ * ```typescript
8245
+ * import { buildEIP2612TypedData } from '@core/adapter-evm'
8246
+ *
8247
+ * const typedData = await buildEIP2612TypedData(
8248
+ * {
8249
+ * name: 'USD Coin',
8250
+ * version: '2',
8251
+ * chainId: 1,
8252
+ * verifyingContract: '0xa0b86a33e6441e4d178bb0c14ce0e9ce9c83bdd8'
8253
+ * },
8254
+ * adapter,
8255
+ * {
8256
+ * owner: '0x742d35cc6639c0532fbe9002b3a2265ca4c878f8e',
8257
+ * spender: '0x1234567890123456789012345678901234567890',
8258
+ * value: 1000000n
8259
+ * }
8260
+ * )
8261
+ *
8262
+ * const signature = await adapter.signTypedData(typedData)
8263
+ * ```
8264
+ */
8265
+ async function buildEIP2612TypedData(meta, adapter, opts, ctx) {
8266
+ // Format addresses to ensure proper EIP-55 checksumming
8267
+ const formattedOwner = convertAddress(opts.owner, 'evm');
8268
+ const formattedSpender = convertAddress(opts.spender, 'evm');
8269
+ const formattedContract = convertAddress(meta.verifyingContract, 'evm');
8270
+ // Fetch nonce if not provided - adapter handles the contract interaction
8271
+ const nonce = opts.nonce ??
8272
+ (await adapter.fetchEIP2612Nonce(formattedContract, formattedOwner, ctx));
8273
+ /*
8274
+ * Calculate deadline if not provided (24 hours from now)
8275
+ * EIP-2612 deadline is a uint256 in the permit struct, so we use bigint for full compatibility
8276
+ * with on-chain values and to avoid overflow/precision issues with large timestamps.
8277
+ * Most real-world deadlines are within JS safe integer range, but using bigint is safest.
8278
+ */
8279
+ const now = BigInt(Math.floor(Date.now() / 1000));
8280
+ const deadline = opts.deadline !== undefined
8281
+ ? BigInt(opts.deadline)
8282
+ : computeDefaultDeadline();
8283
+ if (deadline <= now) {
8284
+ throw new Error(`EIP-2612 deadline must be in the future (got ${deadline.toString()}, now ${now.toString()})`);
8285
+ }
8286
+ // Build the message with properly formatted addresses
8287
+ const message = {
8288
+ owner: formattedOwner,
8289
+ spender: formattedSpender,
8290
+ value: opts.value,
8291
+ nonce,
8292
+ deadline,
8293
+ };
8294
+ // Return complete typed data structure with formatted contract address
8295
+ return buildTypedData({ ...meta, verifyingContract: formattedContract }, EIP2612_TYPES, 'Permit', message);
8296
+ }
8297
+
8073
8298
  /**
8074
8299
  * Checks if a function in an ABI is read-only (view or pure).
8075
8300
  *
@@ -8444,7 +8669,6 @@ const abiSchema = z
8444
8669
  * - A chainType property set to 'evm'
8445
8670
  * - An actionRegistry with registerHandlers method and actionHandlers record
8446
8671
  * - A prepare method for creating chain requests
8447
- * - A getChain method for retrieving chain configuration
8448
8672
  *
8449
8673
  * @throws \{ValidationError\} If validation fails, with details about which properties failed
8450
8674
  *
@@ -8461,7 +8685,6 @@ const abiSchema = z
8461
8685
  * }
8462
8686
  * },
8463
8687
  * prepare: async (request: any) => ({}),
8464
- * getChain: async () => ({ name: 'Ethereum', type: 'evm' as const })
8465
8688
  * }
8466
8689
  *
8467
8690
  * const result = evmAdapterSchema.safeParse(validAdapter)
@@ -8563,6 +8786,44 @@ const evmTransactionHashSchema = z
8563
8786
  .refine((hash) => /^0x[0-9a-fA-F]{64}$/.test(hash), (hash) => ({
8564
8787
  message: `Transaction hash "${hash}" contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after '0x'.`,
8565
8788
  }));
8789
+ /**
8790
+ * Zod schema for validating EVM private keys.
8791
+ *
8792
+ * This schema validates private key strings to ensure they are properly formatted:
8793
+ * - Accepts private keys with or without '0x' prefix
8794
+ * - Automatically normalizes by adding '0x' prefix if missing
8795
+ * - Validates that the key is exactly 64 hexadecimal characters (32 bytes)
8796
+ * - Ensures only valid hexadecimal characters (0-9, a-f, A-F) are used
8797
+ *
8798
+ * @remarks
8799
+ * This is a shared schema used by both ethers.v6 and viem.v2 adapters to ensure
8800
+ * consistent private key validation across all EVM adapters.
8801
+ *
8802
+ * @throws \{ValidationError\} If validation fails, with details about the issue
8803
+ *
8804
+ * @example
8805
+ * ```typescript
8806
+ * import { evmPrivateKeySchema } from '@core/adapter-evm/validation'
8807
+ *
8808
+ * // Both formats are accepted and normalized
8809
+ * const keyWithPrefix = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
8810
+ * const keyWithoutPrefix = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
8811
+ *
8812
+ * const result1 = evmPrivateKeySchema.safeParse(keyWithPrefix)
8813
+ * const result2 = evmPrivateKeySchema.safeParse(keyWithoutPrefix)
8814
+ *
8815
+ * // Both succeed and produce the same normalized result with '0x' prefix
8816
+ * ```
8817
+ */
8818
+ const evmPrivateKeySchema = z
8819
+ .string({
8820
+ required_error: 'Private key is required. Please provide a valid private key.',
8821
+ invalid_type_error: 'Private key must be a string. Please provide a valid private key.',
8822
+ })
8823
+ .transform((val) => (val.startsWith('0x') ? val : `0x${val}`))
8824
+ .pipe(z.string().regex(/^0x[0-9a-fA-F]{64}$/, {
8825
+ message: 'Private key must be a valid 64-character hex string. Please provide a valid private key with or without "0x" prefix.',
8826
+ }));
8566
8827
 
8567
8828
  const assertEvmPreparedChainRequestParamsSymbol = Symbol('assertEvmPreparedChainRequestParams');
8568
8829
  /**
@@ -9331,7 +9592,7 @@ const ethersAdapterOptionsSchema = z.object({
9331
9592
  * Zod schema for validating parameters to create an EthersAdapter from a private key.
9332
9593
  *
9333
9594
  * @remarks
9334
- * - `privateKey` must be a string starting with "0x" and 64 hexadecimal characters.
9595
+ * - `privateKey` can be provided with or without the "0x" prefix. The schema automatically normalizes keys by adding the "0x" prefix if missing before validation.
9335
9596
  * - `getProvider` is optional and, if provided, must be a function accepting an object with a `chain` parameter and returning any value.
9336
9597
  * - `capabilities` is optional and allows partial overrides with smart defaults.
9337
9598
  *
@@ -9339,27 +9600,22 @@ const ethersAdapterOptionsSchema = z.object({
9339
9600
  * ```typescript
9340
9601
  * import { createAdapterFromPrivateKeyParamsSchema } from '@circle-fin/adapter-ethers-v6/validation'
9341
9602
  *
9342
- * const params = {
9603
+ * // Both formats are supported:
9604
+ * const paramsWithPrefix = {
9343
9605
  * privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
9344
9606
  * }
9345
9607
  *
9346
- * const result = createAdapterFromPrivateKeyParamsSchema.safeParse(params)
9347
- * if (result.success) {
9348
- * console.log('Parameters are valid')
9349
- * } else {
9350
- * console.error('Validation failed:', result.error)
9608
+ * const paramsWithoutPrefix = {
9609
+ * privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
9351
9610
  * }
9611
+ *
9612
+ * const result1 = createAdapterFromPrivateKeyParamsSchema.safeParse(paramsWithPrefix)
9613
+ * const result2 = createAdapterFromPrivateKeyParamsSchema.safeParse(paramsWithoutPrefix)
9614
+ * // Both succeed and produce the same normalized result
9352
9615
  * ```
9353
9616
  */
9354
9617
  const createAdapterFromPrivateKeyParamsSchema = z.object({
9355
- privateKey: z
9356
- .string({
9357
- required_error: 'Private key is required. Please provide a valid private key.',
9358
- invalid_type_error: 'Private key must be a string. Please provide a valid private key.',
9359
- })
9360
- .regex(/^0x[0-9a-fA-F]{64}$/, {
9361
- message: 'Private key must be a 64-character hexadecimal string prefixed with "0x".',
9362
- }),
9618
+ privateKey: evmPrivateKeySchema,
9363
9619
  getProvider: z
9364
9620
  .function()
9365
9621
  .args(z.object({ chain: z.unknown() }))
@@ -9468,28 +9724,6 @@ z.object({
9468
9724
  function validateEthersAdapterOptions(options) {
9469
9725
  validate(options, ethersAdapterOptionsSchema, 'EthersAdapterOptions');
9470
9726
  }
9471
- /**
9472
- * Validates parameters for creating an Ethers adapter from a private key.
9473
- *
9474
- * This function validates the parameters used by the createAdapterFromPrivateKey
9475
- * factory function for the Ethers adapter.
9476
- *
9477
- * @param params - The parameters to validate. Must include a `privateKey` string and optionally a `getProvider` function and `capabilities`.
9478
- * @throws \{ValidationError\} If validation fails.
9479
- * @example
9480
- * ```typescript
9481
- * import { validateCreateAdapterFromPrivateKeyParams } from '@circle-fin/adapter-ethers-v6/validation'
9482
- *
9483
- * const params = {
9484
- * privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
9485
- * }
9486
- *
9487
- * validateCreateAdapterFromPrivateKeyParams(params) // throws if invalid
9488
- * ```
9489
- */
9490
- function validateCreateAdapterFromPrivateKeyParams(params) {
9491
- validate(params, createAdapterFromPrivateKeyParamsSchema, 'CreateAdapterFromPrivateKeyParams');
9492
- }
9493
9727
  /**
9494
9728
  * Validates parameters for creating an Ethers adapter from an EIP-1193 provider.
9495
9729
  *
@@ -10035,6 +10269,11 @@ class EthersAdapter extends EvmAdapter {
10035
10269
  * ```
10036
10270
  */
10037
10271
  async getAddress(chain) {
10272
+ // Prevent calling getAddress on developer-controlled adapters
10273
+ if (this.capabilities?.addressContext === 'developer-controlled') {
10274
+ throw new Error('Cannot call getAddress() on developer-controlled adapters. ' +
10275
+ 'Address must be provided explicitly in the operation context.');
10276
+ }
10038
10277
  // Chain parameter should now be provided by resolveOperationContext
10039
10278
  if (!chain) {
10040
10279
  throw new Error('Chain parameter is required for address resolution. ' +
@@ -10046,45 +10285,6 @@ class EthersAdapter extends EvmAdapter {
10046
10285
  const address = await signer.getAddress();
10047
10286
  return address;
10048
10287
  }
10049
- /**
10050
- * Gets the current chain definition.
10051
- *
10052
- * TEMP This method is temporary and will be removed once all adapters migrate to the OperationContext pattern.
10053
- *
10054
- * **Migration Guide:**
10055
- *
10056
- * With the OperationContext pattern, chain information is provided explicitly in each operation
10057
- * rather than queried from the adapter. This eliminates ambiguity and enables seamless multi-chain
10058
- * operations with a single adapter instance.
10059
- *
10060
- * **Before (Deprecated):**
10061
- * ```typescript
10062
- * const chain = await adapter.getChain()
10063
- * const prepared = await adapter.prepare(params) // Uses cached chain
10064
- * ```
10065
- *
10066
- * **After (OperationContext):**
10067
- * ```typescript
10068
- * // Chain specified explicitly per operation
10069
- * const prepared = await adapter.prepare(params, { chain: 'Ethereum' })
10070
- *
10071
- * // Multi-chain operations with same adapter
10072
- * const ethPrepared = await adapter.prepare(params, { chain: 'Ethereum' })
10073
- * const basePrepared = await adapter.prepare(params, { chain: 'Base' })
10074
- * ```
10075
- *
10076
- * @returns A promise that resolves to the first supported chain from capabilities
10077
- * @throws Error when no supported chains are configured
10078
- */
10079
- async getChain() {
10080
- // Fallback: return first supported chain (deprecated - only for backward compatibility)
10081
- const firstChain = this.capabilities?.supportedChains?.[0];
10082
- if (!firstChain) {
10083
- throw new Error('No supported chains configured. ' +
10084
- 'This method is deprecated - use OperationContext pattern instead.');
10085
- }
10086
- return await Promise.resolve(firstChain);
10087
- }
10088
10288
  /**
10089
10289
  * Waits for a transaction to be mined and confirmed on the blockchain.
10090
10290
  *
@@ -10359,16 +10559,17 @@ class EthersAdapter extends EvmAdapter {
10359
10559
  * ```typescript
10360
10560
  * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-ethers-v6'
10361
10561
  *
10362
- * // Minimal configuration with lazy initialization
10363
- * const adapter = createAdapterFromPrivateKey({
10364
- * privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
10365
- * // Defaults applied:
10366
- * // - addressContext: 'user-controlled'
10367
- * // - supportedChains: all EVM chains (~29 networks)
10562
+ * // Both private key formats are supported (with or without '0x' prefix):
10563
+ * const adapter1 = createAdapterFromPrivateKey({
10564
+ * privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' // With prefix
10565
+ * })
10566
+ *
10567
+ * const adapter2 = createAdapterFromPrivateKey({
10568
+ * privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' // Without prefix (automatically normalized)
10368
10569
  * })
10369
10570
  *
10370
10571
  * // Chain specified per-operation via OperationContext
10371
- * const prepared = await adapter.prepare({
10572
+ * const prepared = await adapter1.prepare({
10372
10573
  * address: '0x...',
10373
10574
  * abi: contractAbi,
10374
10575
  * functionName: 'transfer',
@@ -10427,9 +10628,9 @@ class EthersAdapter extends EvmAdapter {
10427
10628
  * ```
10428
10629
  */
10429
10630
  function createAdapterFromPrivateKey(params) {
10430
- // Validate input parameters at runtime
10431
- validateCreateAdapterFromPrivateKeyParams(params);
10432
- const { privateKey, getProvider, capabilities } = params;
10631
+ // Parse and validate input parameters at runtime (normalizes the private key by adding '0x' prefix if missing)
10632
+ const { privateKey } = createAdapterFromPrivateKeyParamsSchema.parse(params);
10633
+ const { getProvider, capabilities } = params;
10433
10634
  // Resolve capabilities with default configuration
10434
10635
  const resolvedCapabilities = createAdapterCapabilities('evm', capabilities);
10435
10636
  // Validate that capabilities are appropriate for private key adapters
@@ -10444,6 +10645,7 @@ function createAdapterFromPrivateKey(params) {
10444
10645
  const getProviderFn = getProvider ?? getDefaultProviderUtil(new Map());
10445
10646
  // Create wallet WITHOUT provider (lazy initialization)
10446
10647
  // The wallet will be connected to a provider when ensureChain is called
10648
+ // Type assertion safe because we normalized the private key in the schema
10447
10649
  const wallet = new Wallet(privateKey);
10448
10650
  // Create and return the adapter
10449
10651
  return new EthersAdapter({
@@ -10552,13 +10754,13 @@ function createAdapterFromPrivateKey(params) {
10552
10754
  * ```typescript
10553
10755
  * // Cross-chain transfer using a single adapter
10554
10756
  * import { createAdapterFromProvider } from '@circle-fin/adapter-ethers-v6'
10555
- * import { BridgingKit } from '@circle-fin/bridging-kit'
10757
+ * import { BridgeKit } from '@circle-fin/bridge-kit'
10556
10758
  *
10557
10759
  * const adapter = await createAdapterFromProvider({
10558
10760
  * provider: window.ethereum
10559
10761
  * })
10560
10762
  *
10561
- * const kit = new BridgingKit()
10763
+ * const kit = new BridgeKit()
10562
10764
  *
10563
10765
  * // Use the same adapter for both source and destination
10564
10766
  * const result = await kit.bridge({
@@ -10621,5 +10823,5 @@ const createAdapterFromProvider = async (params) => {
10621
10823
  }, resolvedCapabilities);
10622
10824
  };
10623
10825
 
10624
- export { EthersAdapter, createAdapterFromPrivateKey, createAdapterFromProvider, validateAdapterCapabilities };
10826
+ export { Blockchain, EthersAdapter, buildEIP2612TypedData, computeDefaultDeadline, createAdapterFromPrivateKey, createAdapterFromProvider, parseSignature, validateAdapterCapabilities };
10625
10827
  //# sourceMappingURL=index.mjs.map