@circle-fin/adapter-ethers-v6 1.0.0 → 1.1.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 ADDED
@@ -0,0 +1,89 @@
1
+ # @circle-fin/adapter-ethers-v6
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Improves static gas estimate values for contract calls. Updates adapters' `prepare()` method so that transaction simulation is now performed only during `execute()`, not during `estimate()`. Adds an optional fallback parameter to `estimate()` for cases where gas estimation fails.
8
+
9
+ ### Patch Changes
10
+
11
+ - Fix CommonJS compatibility by correcting file extensions and package exports. This resolves a "ReferenceError: require is not defined" error that occurred when using packages in CommonJS projects with ts-node.
12
+
13
+ ## 1.0.1
14
+
15
+ ### Patch Changes
16
+
17
+ - Add support for Arc Testnet chain definition. Arc is Circle's EVM-compatible Layer-1 blockchain designed for stablecoin finance and asset
18
+ tokenization, featuring USDC as the native gas token and sub-second finality via the Malachite BFT consensus engine.
19
+ - Improve error messages for developer-controlled address contexts. Calling `getAddress()` on an adapter configured with `addressContext: 'developer-controlled'` now throws a clear error explaining that addresses must be provided explicitly in the operation context.
20
+ - Update Sonic Testnet chain definition to canonical network. The `SonicTestnet` chain definition now points to the official Sonic Testnet (chainId: 14601) instead of the deprecated Sonic Blaze Testnet (chainId: 57054). The RPC endpoint has been updated to `https://rpc.testnet.soniclabs.com`, the display name simplified to "Sonic Testnet", and the USDC contract address updated to the new deployment.
21
+
22
+ **Breaking Changes:**
23
+ - **Chain ID:** 57054 → 14601
24
+ - **RPC Endpoint:** `https://rpc.blaze.soniclabs.com` → `https://rpc.testnet.soniclabs.com`
25
+ - **USDC Address:** `0xA4879Fed32Ecbef99399e5cbC247E533421C4eC6` → `0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51`
26
+
27
+ **Migration:** If you were using `SonicTestnet`, your application will automatically connect to the new network upon upgrading. Any accounts, contracts, or transactions on the old Blaze testnet (chainId: 57054) will need to be recreated on the new testnet.
28
+
29
+ ## 1.0.0
30
+
31
+ ### Major Changes
32
+
33
+ - # Ethers v6 Adapter 1.0.0 Release 🎉
34
+
35
+ Full-featured EVM blockchain adapter built on ethers.js v6 - providing comprehensive support for Ethereum and EVM-compatible chains with familiar ethers.js APIs.
36
+
37
+ ## 🚀 Core EVM Features
38
+ - **Complete EVM Support**: Full compatibility with Ethereum and EVM-compatible chains
39
+ - **Ethers.js v6 Integration**: Built on the latest ethers.js with improved performance
40
+ - **Multi-chain Support**: Seamless switching between different EVM networks
41
+ - **Wallet Integration**: Support for various wallet types and signing methods
42
+
43
+ ## 🔗 Supported Networks
44
+ - **Ethereum Mainnet & Testnets**: Full support for Ethereum ecosystem
45
+ - **Layer 2 Solutions**: Optimism, Arbitrum, Polygon, and other L2s
46
+ - **EVM Sidechains**: Base, Avalanche, and other EVM-compatible networks
47
+ - **Custom Networks**: Easy configuration for private or custom EVM chains
48
+
49
+ ## 💼 Address Management
50
+
51
+ **User-Controlled Adapters**
52
+ - Automatic address resolution from connected wallets
53
+ - Support for MetaMask, WalletConnect, and other wallet providers
54
+ - Real-time account switching and network detection
55
+
56
+ **Developer-Controlled Adapters**
57
+ - Private key-based signing for server-side applications
58
+ - Deterministic address management for automated systems
59
+ - Secure key handling with ethers.js security practices
60
+
61
+ ```typescript
62
+ // User-controlled adapter (wallet-based)
63
+ const adapter = createAdapterFromWallet(wallet)
64
+
65
+ // Developer-controlled adapter (private key)
66
+ const adapter = createAdapterFromPrivateKey({
67
+ privateKey: '0x...',
68
+ })
69
+ ```
70
+
71
+ ## ⛽ Gas Management
72
+ - **Intelligent Gas Estimation**: Automatic gas limit calculation with safety buffers
73
+ - **Dynamic Fee Calculation**: Real-time gas price fetching with configurable buffers
74
+ - **EIP-1559 Support**: Type 2 transaction support for modern fee markets
75
+ - **Gas Optimization**: Efficient transaction batching and optimization
76
+
77
+ ## 🔐 Security Features
78
+ - **Type Safety**: Full TypeScript support with strict type checking
79
+ - **Parameter Validation**: Runtime validation of all transaction parameters
80
+ - **Secure Signing**: Leverages ethers.js battle-tested signing infrastructure
81
+ - **Error Handling**: Comprehensive error handling with detailed error messages
82
+
83
+ ## 🛠️ Developer Experience
84
+ - **Rich JSDoc Documentation**: Complete API documentation with examples
85
+ - **TypeScript First**: Built with TypeScript for superior developer experience
86
+ - **Familiar APIs**: Consistent with ethers.js patterns and conventions
87
+ - **Comprehensive Testing**: Extensive test coverage for reliability
88
+
89
+ This adapter provides a robust foundation for EVM-based cross-chain USDC transfers, leveraging the mature ethers.js ecosystem while providing the specialized functionality needed for bridge operations.
@@ -20,10 +20,10 @@
20
20
 
21
21
  var ethers = require('ethers');
22
22
  var zod = require('zod');
23
+ require('@ethersproject/units');
23
24
  var bytes = require('@ethersproject/bytes');
24
25
  var address = require('@ethersproject/address');
25
26
  var bs58 = require('bs58');
26
- require('@ethersproject/units');
27
27
 
28
28
  function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
29
29
 
@@ -268,6 +268,7 @@ exports.Blockchain = void 0;
268
268
  Blockchain["Algorand_Testnet"] = "Algorand_Testnet";
269
269
  Blockchain["Aptos"] = "Aptos";
270
270
  Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
271
+ Blockchain["Arc_Testnet"] = "Arc_Testnet";
271
272
  Blockchain["Arbitrum"] = "Arbitrum";
272
273
  Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
273
274
  Blockchain["Avalanche"] = "Avalanche";
@@ -488,6 +489,48 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
488
489
  */
489
490
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
490
491
 
492
+ /**
493
+ * Arc Testnet chain definition
494
+ * @remarks
495
+ * This represents the test network for the Arc blockchain,
496
+ * Circle's EVM-compatible Layer-1 designed for stablecoin finance
497
+ * and asset tokenization. Arc uses USDC as the native gas token and
498
+ * features the Malachite Byzantine Fault Tolerant (BFT) consensus
499
+ * engine for sub-second finality.
500
+ */
501
+ const ArcTestnet = defineChain({
502
+ type: 'evm',
503
+ chain: exports.Blockchain.Arc_Testnet,
504
+ name: 'Arc Testnet',
505
+ title: 'ArcTestnet',
506
+ nativeCurrency: {
507
+ name: 'Arc',
508
+ symbol: 'Arc',
509
+ decimals: 18,
510
+ },
511
+ chainId: 5042002,
512
+ isTestnet: true,
513
+ explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
514
+ rpcEndpoints: ['https://rpc.testnet.arc.network/'],
515
+ eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
516
+ usdcAddress: '0x3600000000000000000000000000000000000000',
517
+ cctp: {
518
+ domain: 26,
519
+ contracts: {
520
+ v2: {
521
+ type: 'split',
522
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
523
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
524
+ confirmations: 1,
525
+ fastConfirmations: 1,
526
+ },
527
+ },
528
+ },
529
+ kitContracts: {
530
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
531
+ },
532
+ });
533
+
491
534
  /**
492
535
  * Arbitrum Mainnet chain definition
493
536
  * @remarks
@@ -1779,26 +1822,26 @@ const Sonic = defineChain({
1779
1822
  });
1780
1823
 
1781
1824
  /**
1782
- * Sonic Blaze Testnet chain definition
1825
+ * Sonic Testnet chain definition
1783
1826
  * @remarks
1784
1827
  * This represents the official test network for the Sonic blockchain.
1785
1828
  */
1786
1829
  const SonicTestnet = defineChain({
1787
1830
  type: 'evm',
1788
1831
  chain: exports.Blockchain.Sonic_Testnet,
1789
- name: 'Sonic Blaze Testnet',
1790
- title: 'Sonic Blaze Testnet',
1832
+ name: 'Sonic Testnet',
1833
+ title: 'Sonic Testnet',
1791
1834
  nativeCurrency: {
1792
1835
  name: 'Sonic',
1793
1836
  symbol: 'S',
1794
1837
  decimals: 18,
1795
1838
  },
1796
- chainId: 57054,
1839
+ chainId: 14601,
1797
1840
  isTestnet: true,
1798
1841
  explorerUrl: 'https://testnet.sonicscan.org/tx/{hash}',
1799
- rpcEndpoints: ['https://rpc.blaze.soniclabs.com'],
1842
+ rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
1800
1843
  eurcAddress: null,
1801
- usdcAddress: '0xA4879Fed32Ecbef99399e5cbC247E533421C4eC6',
1844
+ usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
1802
1845
  cctp: {
1803
1846
  domain: 13,
1804
1847
  contracts: {
@@ -2315,6 +2358,7 @@ var Blockchains = {
2315
2358
  AptosTestnet: AptosTestnet,
2316
2359
  Arbitrum: Arbitrum,
2317
2360
  ArbitrumSepolia: ArbitrumSepolia,
2361
+ ArcTestnet: ArcTestnet,
2318
2362
  Avalanche: Avalanche,
2319
2363
  AvalancheFuji: AvalancheFuji,
2320
2364
  Base: Base,
@@ -2992,6 +3036,10 @@ class KitError extends Error {
2992
3036
  }
2993
3037
  }
2994
3038
 
3039
+ /**
3040
+ * Minimum error code for INPUT type errors.
3041
+ * INPUT errors represent validation failures and invalid parameters.
3042
+ */
2995
3043
  /**
2996
3044
  * Standardized error definitions for INPUT type errors.
2997
3045
  *
@@ -8012,6 +8060,246 @@ class EvmAdapter extends Adapter {
8012
8060
  }
8013
8061
  }
8014
8062
 
8063
+ // --- Signature Parsing Constants ---
8064
+ const SIGNATURE_BYTE_LENGTH = 65; // Total bytes in an Ethereum signature
8065
+ const HEX_CHARS_PER_BYTE = 2;
8066
+ const SIGNATURE_HEX_LENGTH = SIGNATURE_BYTE_LENGTH * HEX_CHARS_PER_BYTE;
8067
+ const R_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE; // 32 bytes for 'r'
8068
+ const S_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE; // 32 bytes for 's'
8069
+ // --- Recovery ID Validation Constants ---
8070
+ /**
8071
+ * Legacy recovery IDs (pre-EIP-155 format):
8072
+ * - 0 or 1 (older clients) or 27, 28 (Ethereum before chain-id inclusion)
8073
+ */
8074
+ const LEGACY_V_VALUES = new Set([0, 1, 27, 28]);
8075
+ /**
8076
+ * Minimum v value indicating an EIP-155 style signature.
8077
+ * Calculated as 35 + (2 * chainId) + recoveryId (0 or 1).
8078
+ */
8079
+ const MIN_EIP155_V = 35;
8080
+ /**
8081
+ * parseSignature
8082
+ *
8083
+ * Parse a 65-byte ECDSA signature into its r, s, and v components, expressed in hex.
8084
+ *
8085
+ * Ethereum signatures are structured as:
8086
+ * signature = r (32 bytes) || s (32 bytes) || v (1 byte)
8087
+ *
8088
+ * - r, s: Big-endian hex values (32 bytes each)
8089
+ * - v: Recovery identifier, used by secp256k1 to reconstruct the signer’s public key
8090
+ *
8091
+ * @param signatureHex - Signature as a hex string, optionally prefixed with "0x".
8092
+ * @returns { r, s, v }
8093
+ * - r: Hex string of the R component.
8094
+ * - s: Hex string of the S component.
8095
+ * - v: Numeric recovery ID.
8096
+ *
8097
+ * @throws Error if:
8098
+ * - Input is not valid hex.
8099
+ * - Incorrect length (must be exactly 65 bytes / 130 hex chars).
8100
+ * - v is outside the supported range (Legacy or EIP-155 formula).
8101
+ *
8102
+ * Notes on EIP-155 overload:
8103
+ * EIP-155 repurposes the v field so that:
8104
+ * v = 35 + (2 * chainId) + recoveryId(0 or 1)
8105
+ * Any v value >= 35 indicates the chain-id is encoded, preventing cross-chain replay.
8106
+ * r and s values remain unchanged.
8107
+ *
8108
+ * Example:
8109
+ * ```typescript
8110
+ * const rawSig = '0x6c1b...f02b'
8111
+ * try {
8112
+ * const { r, s, v } = parseSignature(rawSig)
8113
+ * console.log('R:', r)
8114
+ * console.log('S:', s)
8115
+ * console.log('Recovery ID (v):', v)
8116
+ * } catch (err) {
8117
+ * console.error('Signature parse error:', err.message)
8118
+ * }
8119
+ * ```
8120
+ */
8121
+ function parseSignature(signatureHex) {
8122
+ // 1) Remove optional '0x' prefix
8123
+ const hex = signatureHex.startsWith('0x')
8124
+ ? signatureHex.slice(2)
8125
+ : signatureHex;
8126
+ // 2) Validate hex format and length
8127
+ const VALID_HEX_REGEX = /^[0-9a-fA-F]+$/;
8128
+ if (!VALID_HEX_REGEX.test(hex)) {
8129
+ throw new Error('Signature must be a valid hex string (0-9, a-f, A-F).');
8130
+ }
8131
+ if (hex.length !== SIGNATURE_HEX_LENGTH) {
8132
+ throw new Error(`Invalid length: expected ${SIGNATURE_HEX_LENGTH.toString()} hex chars (65 bytes), got ${hex.length.toString()}`);
8133
+ }
8134
+ // 3) Extract components
8135
+ const r = `0x${hex.slice(0, R_HEX_LENGTH)}`;
8136
+ const s = `0x${hex.slice(R_HEX_LENGTH, R_HEX_LENGTH + S_HEX_LENGTH)}`;
8137
+ const vValue = parseInt(hex.slice(R_HEX_LENGTH + S_HEX_LENGTH), 16);
8138
+ // 4) Ensure v is within supported values
8139
+ const isLegacy = LEGACY_V_VALUES.has(vValue);
8140
+ const isEIP155 = vValue >= MIN_EIP155_V;
8141
+ if (!isLegacy && !isEIP155) {
8142
+ throw new Error(`Unsupported v value: ${vValue.toString()}. Must be one of [${[
8143
+ ...LEGACY_V_VALUES,
8144
+ ].join(', ')}] or >=${MIN_EIP155_V.toString()}`);
8145
+ }
8146
+ return { r: r, s: s, v: vValue };
8147
+ }
8148
+ /**
8149
+ * buildTypedData
8150
+ *
8151
+ * Create an EIP-712 compliant "TypedData" object, encapsulating:
8152
+ * - Domain separator (application/contract identity, chain ID)
8153
+ * - Explicit type definitions (field names and types)
8154
+ * - Primary type name identifying the root object
8155
+ * - Message payload matching the defined schema
8156
+ *
8157
+ * This enforces structured, non-ambiguous signing, and guards against signature replay.
8158
+ *
8159
+ * @typeParam Types - Map of type names to arrays of { name, type } definitions.
8160
+ * @typeParam Msg - Object whose shape matches the primaryType fields.
8161
+ *
8162
+ * @param domain - EIP712Domain, e.g. { name, version, chainId, verifyingContract }.
8163
+ * @param types - Type definitions, e.g.:
8164
+ * {
8165
+ * Mail: [ { name: 'from', type: 'string' }, { name: 'contents', type: 'string' } ],
8166
+ * }
8167
+ * @param primaryType - Root key in types, e.g. 'Mail'.
8168
+ * @param message - The actual values, e.g. { from: 'Alice', contents: 'Hello' }.
8169
+ *
8170
+ * @returns TypedData object ready for use with signing libraries.
8171
+ *
8172
+ * Example:
8173
+ * ```typescript
8174
+ * const typedData = buildTypedData(
8175
+ * { name: 'MyDApp', version: '1', chainId: 1, verifyingContract: '0xabc...' },
8176
+ * { Message: [{ name: 'text', type: 'string' }] },
8177
+ * 'Message',
8178
+ * { text: 'Hello, EIP-712!' }
8179
+ * )
8180
+ * // Pass typedData to ethers.js signer._signTypedData(domain, types, message)
8181
+ * ```
8182
+ */
8183
+ function buildTypedData(domain, types, primaryType, message) {
8184
+ return { domain, types, primaryType, message };
8185
+ }
8186
+
8187
+ /**
8188
+ * Compute default deadline for permit signatures.
8189
+ *
8190
+ * Returns a timestamp 1 hour (3600 seconds) from the current time,
8191
+ * converted to Unix timestamp format as a bigint. This is commonly
8192
+ * used as the default expiration time for EIP-2612 permit signatures.
8193
+ *
8194
+ * @returns Unix timestamp (in seconds) 1 hour from now as a bigint
8195
+ *
8196
+ * @example
8197
+ * ```typescript
8198
+ * import { computeDefaultDeadline } from '@core/adapter-evm'
8199
+ *
8200
+ * const deadline = computeDefaultDeadline()
8201
+ * console.log(`Permit expires at: ${deadline}`)
8202
+ * // Output: Permit expires at: 1640998800
8203
+ * ```
8204
+ */
8205
+ function computeDefaultDeadline() {
8206
+ return BigInt(Math.floor(Date.now() / 1000) + 3600);
8207
+ }
8208
+
8209
+ /**
8210
+ * EIP-2612 permit type definition.
8211
+ * Defines the structure for permit signatures according to EIP-2612 specification.
8212
+ *
8213
+ * @see {@link https://eips.ethereum.org/EIPS/eip-2612 | EIP-2612 Specification}
8214
+ */
8215
+ const EIP2612_TYPES = {
8216
+ Permit: [
8217
+ { name: 'owner', type: 'address' },
8218
+ { name: 'spender', type: 'address' },
8219
+ { name: 'value', type: 'uint256' },
8220
+ { name: 'nonce', type: 'uint256' },
8221
+ { name: 'deadline', type: 'uint256' },
8222
+ ],
8223
+ };
8224
+
8225
+ /**
8226
+ * Build EIP-2612 typed data for permit signing.
8227
+ *
8228
+ * This function creates the complete EIP-712 typed data structure required
8229
+ * for EIP-2612 permit signatures, including automatic nonce fetching using
8230
+ * the adapter's built-in nonce fetching capability.
8231
+ *
8232
+ * **Address Formatting**: All addresses are automatically formatted with proper
8233
+ * EIP-55 checksumming using the `convertAddress` utility, ensuring compatibility
8234
+ * with strict validation libraries like viem.
8235
+ *
8236
+ * **Nonce Handling**: The nonce can be provided explicitly or will be fetched
8237
+ * automatically using the adapter's `fetchEIP2612Nonce` method, which queries
8238
+ * the token contract's `nonces(owner)` function.
8239
+ *
8240
+ * **Deadline Calculation**: If no deadline is provided, it defaults to 24 hours
8241
+ * from the current time (computed using `computeDefaultDeadline`).
8242
+ *
8243
+ * @param meta - Domain metadata for the token contract
8244
+ * @param adapter - Adapter instance with nonce-fetching capability
8245
+ * @param opts - EIP-2612 permit options including owner, spender, value
8246
+ * @returns Complete EIP-712 typed data ready for signing
8247
+ *
8248
+ * @example
8249
+ * ```typescript
8250
+ * import { buildEIP2612TypedData } from '@core/adapter-evm'
8251
+ *
8252
+ * const typedData = await buildEIP2612TypedData(
8253
+ * {
8254
+ * name: 'USD Coin',
8255
+ * version: '2',
8256
+ * chainId: 1,
8257
+ * verifyingContract: '0xa0b86a33e6441e4d178bb0c14ce0e9ce9c83bdd8'
8258
+ * },
8259
+ * adapter,
8260
+ * {
8261
+ * owner: '0x742d35cc6639c0532fbe9002b3a2265ca4c878f8e',
8262
+ * spender: '0x1234567890123456789012345678901234567890',
8263
+ * value: 1000000n
8264
+ * }
8265
+ * )
8266
+ *
8267
+ * const signature = await adapter.signTypedData(typedData)
8268
+ * ```
8269
+ */
8270
+ async function buildEIP2612TypedData(meta, adapter, opts, ctx) {
8271
+ // Format addresses to ensure proper EIP-55 checksumming
8272
+ const formattedOwner = convertAddress(opts.owner, 'evm');
8273
+ const formattedSpender = convertAddress(opts.spender, 'evm');
8274
+ const formattedContract = convertAddress(meta.verifyingContract, 'evm');
8275
+ // Fetch nonce if not provided - adapter handles the contract interaction
8276
+ const nonce = opts.nonce ??
8277
+ (await adapter.fetchEIP2612Nonce(formattedContract, formattedOwner, ctx));
8278
+ /*
8279
+ * Calculate deadline if not provided (24 hours from now)
8280
+ * EIP-2612 deadline is a uint256 in the permit struct, so we use bigint for full compatibility
8281
+ * with on-chain values and to avoid overflow/precision issues with large timestamps.
8282
+ * Most real-world deadlines are within JS safe integer range, but using bigint is safest.
8283
+ */
8284
+ const now = BigInt(Math.floor(Date.now() / 1000));
8285
+ const deadline = opts.deadline !== undefined
8286
+ ? BigInt(opts.deadline)
8287
+ : computeDefaultDeadline();
8288
+ if (deadline <= now) {
8289
+ throw new Error(`EIP-2612 deadline must be in the future (got ${deadline.toString()}, now ${now.toString()})`);
8290
+ }
8291
+ // Build the message with properly formatted addresses
8292
+ const message = {
8293
+ owner: formattedOwner,
8294
+ spender: formattedSpender,
8295
+ value: opts.value,
8296
+ nonce,
8297
+ deadline,
8298
+ };
8299
+ // Return complete typed data structure with formatted contract address
8300
+ return buildTypedData({ ...meta, verifyingContract: formattedContract }, EIP2612_TYPES, 'Permit', message);
8301
+ }
8302
+
8015
8303
  /**
8016
8304
  * Checks if a function in an ABI is read-only (view or pure).
8017
8305
  *
@@ -9689,7 +9977,7 @@ class EthersAdapter extends EvmAdapter {
9689
9977
  * @param overrides - Optional estimate overrides.
9690
9978
  * @returns Promise resolving to the estimated gas, gas price, and total fee.
9691
9979
  */
9692
- async estimateGasForFunction(contract, functionName, args, chain, overrides) {
9980
+ async estimateGasForFunction(contract, functionName, args, chain, overrides, fallback) {
9693
9981
  let gas;
9694
9982
  try {
9695
9983
  const contractFunction = contract.getFunction(functionName);
@@ -9698,7 +9986,12 @@ class EthersAdapter extends EvmAdapter {
9698
9986
  : await contractFunction.estimateGas(...args);
9699
9987
  }
9700
9988
  catch (error) {
9701
- throw new Error(`Gas estimation failed: ${error.message}`);
9989
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
9990
+ if (fallback &&
9991
+ errorMessage.toLocaleLowerCase().includes('execution reverted')) {
9992
+ return fallback;
9993
+ }
9994
+ throw new Error(`Gas estimation failed: ${errorMessage}`);
9702
9995
  }
9703
9996
  let gasPrice;
9704
9997
  try {
@@ -9923,18 +10216,19 @@ class EthersAdapter extends EvmAdapter {
9923
10216
  }
9924
10217
  // For state-changing functions, use the original logic
9925
10218
  const contract = this.createContractInstance(params, signer, resolvedContext.address, provider);
9926
- await this.simulateFunctionCall(contract, functionName, args);
9927
10219
  return {
9928
10220
  type: 'evm',
9929
- estimate: async (overrides) => {
10221
+ estimate: async (overrides, fallback) => {
9930
10222
  await this.ensureChain(targetChain);
9931
10223
  // Reconnect the contract with the correct provider for the target chain to ensure
9932
10224
  // gas estimation and fee data retrieval use the correct network.
9933
10225
  const estimationProvider = await this.getProvider(targetChain);
9934
10226
  const contractForEstimation = contract.connect(estimationProvider);
9935
- return this.estimateGasForFunction(contractForEstimation, functionName, args, targetChain, overrides);
10227
+ return this.estimateGasForFunction(contractForEstimation, functionName, args, targetChain, overrides, fallback);
9936
10228
  },
9937
10229
  execute: async (overrides) => {
10230
+ // Simulate the function call to catch errors before submission
10231
+ await this.simulateFunctionCall(contract, functionName, args);
9938
10232
  await this.ensureChain(targetChain);
9939
10233
  // Reconnect the contract with the current signer, which is on the correct
9940
10234
  // chain after `ensureChain`, to ensure the transaction is populated and
@@ -9986,6 +10280,11 @@ class EthersAdapter extends EvmAdapter {
9986
10280
  * ```
9987
10281
  */
9988
10282
  async getAddress(chain) {
10283
+ // Prevent calling getAddress on developer-controlled adapters
10284
+ if (this.capabilities?.addressContext === 'developer-controlled') {
10285
+ throw new Error('Cannot call getAddress() on developer-controlled adapters. ' +
10286
+ 'Address must be provided explicitly in the operation context.');
10287
+ }
9989
10288
  // Chain parameter should now be provided by resolveOperationContext
9990
10289
  if (!chain) {
9991
10290
  throw new Error('Chain parameter is required for address resolution. ' +
@@ -10535,8 +10834,12 @@ const createAdapterFromProvider = async (params) => {
10535
10834
  }, resolvedCapabilities);
10536
10835
  };
10537
10836
 
10837
+ exports.JsonRpcProvider = ethers.JsonRpcProvider;
10538
10838
  exports.EthersAdapter = EthersAdapter;
10839
+ exports.buildEIP2612TypedData = buildEIP2612TypedData;
10840
+ exports.computeDefaultDeadline = computeDefaultDeadline;
10539
10841
  exports.createAdapterFromPrivateKey = createAdapterFromPrivateKey;
10540
10842
  exports.createAdapterFromProvider = createAdapterFromProvider;
10843
+ exports.parseSignature = parseSignature;
10541
10844
  exports.validateAdapterCapabilities = validateAdapterCapabilities;
10542
- //# sourceMappingURL=index.cjs.js.map
10845
+ //# sourceMappingURL=index.cjs.map
package/index.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/index.d.ts CHANGED
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import { Provider, Signer as Signer$1, Eip1193Provider } from 'ethers';
20
+ export { JsonRpcProvider } from 'ethers';
20
21
  import { Abi } from 'abitype';
21
22
  import { TransactionInstruction, Signer } from '@solana/web3.js';
22
23
 
@@ -409,6 +410,7 @@ declare enum Blockchain {
409
410
  Algorand_Testnet = "Algorand_Testnet",
410
411
  Aptos = "Aptos",
411
412
  Aptos_Testnet = "Aptos_Testnet",
413
+ Arc_Testnet = "Arc_Testnet",
412
414
  Arbitrum = "Arbitrum",
413
415
  Arbitrum_Sepolia = "Arbitrum_Sepolia",
414
416
  Avalanche = "Avalanche",
@@ -594,10 +596,11 @@ interface EvmPreparedChainRequest {
594
596
  * Estimate the gas cost for the contract execution.
595
597
  *
596
598
  * @param overrides - Optional parameters to override the default estimation behavior
599
+ * @param fallback - Optional fallback gas information to use if the estimation fails
597
600
  * @returns A promise that resolves to the estimated gas information
598
601
  * @throws If the estimation fails
599
602
  */
600
- estimate(overrides?: EvmEstimateOverrides): Promise<EstimatedGas>;
603
+ estimate(overrides?: EvmEstimateOverrides, fallback?: EstimatedGas): Promise<EstimatedGas>;
601
604
  /**
602
605
  * Execute the prepared contract call.
603
606
  *
@@ -675,7 +678,7 @@ interface SolanaPreparedChainRequest {
675
678
  /** The type of the chain request. */
676
679
  type: 'solana';
677
680
  /** Estimate the compute units and fee for the transaction. */
678
- estimate(overrides?: SolanaEstimateOverrides): Promise<EstimatedGas>;
681
+ estimate(overrides?: SolanaEstimateOverrides, fallback?: EstimatedGas): Promise<EstimatedGas>;
679
682
  /** Execute the prepared transaction. */
680
683
  execute(overrides?: SolanaExecuteOverrides): Promise<string>;
681
684
  }
@@ -707,7 +710,7 @@ interface NoopPreparedChainRequest {
707
710
  * Placeholder for the estimate method.
708
711
  * @returns The estimated gas cost.
709
712
  */
710
- estimate: () => Promise<EstimatedGas>;
713
+ estimate: (overrides?: EvmEstimateOverrides | SolanaEstimateOverrides, fallback?: EstimatedGas) => Promise<EstimatedGas>;
711
714
  /**
712
715
  * Placeholder for the execute method.
713
716
  * @returns The transaction hash.
@@ -1965,10 +1968,23 @@ interface AdapterCapabilities {
1965
1968
  */
1966
1969
  declare abstract class Adapter<TAdapterCapabilities extends AdapterCapabilities = AdapterCapabilities> {
1967
1970
  /**
1968
- * The type of the chain.
1969
- * @example 'evm', 'solana'
1971
+ * The type of the chain for this adapter.
1972
+ *
1973
+ * - For concrete adapters, this should be a real chain type (e.g., `'evm'`, `'solana'`, etc.) from the ChainType union.
1974
+ * - For hybrid adapters (adapters that route to concrete adapters supporting multiple ecosystems),
1975
+ * set this property to the string literal `'hybrid'`.
1976
+ *
1977
+ * Note: `'hybrid'` is not a legal ChainType and should only be used as a marker for multi-ecosystem adapters.
1978
+ * Hybrid adapters do not interact directly with any chain, but instead route requests to a concrete underlying adapter.
1979
+ *
1980
+ * @example
1981
+ * // For an EVM-only adapter:
1982
+ * chainType = 'evm'
1983
+ *
1984
+ * // For a hybrid adapter:
1985
+ * chainType = 'hybrid'
1970
1986
  */
1971
- abstract chainType: ChainType;
1987
+ abstract chainType: ChainType | 'hybrid';
1972
1988
  /**
1973
1989
  * Capabilities of this adapter, defining address control model and supported chains.
1974
1990
  *
@@ -2050,7 +2066,7 @@ declare abstract class Adapter<TAdapterCapabilities extends AdapterCapabilities
2050
2066
  * })
2051
2067
  * ```
2052
2068
  */
2053
- prepareAction<TActionKey extends ActionKeys>(action: TActionKey, params: ActionPayload<TActionKey>, ctx?: OperationContext<TAdapterCapabilities>): Promise<PreparedChainRequest>;
2069
+ prepareAction<TActionKey extends ActionKeys>(action: TActionKey, params: ActionPayload<TActionKey>, ctx: OperationContext<TAdapterCapabilities>): Promise<PreparedChainRequest>;
2054
2070
  /**
2055
2071
  * Prepares a transaction for future gas estimation and execution.
2056
2072
  *
@@ -2295,6 +2311,127 @@ interface TypedData<Types extends Record<string, TypedDataField[]>, Message exte
2295
2311
  /** The message payload to be signed */
2296
2312
  message: Message;
2297
2313
  }
2314
+ /**
2315
+ * Standard ECDSA signature format (r, s, v components).
2316
+ *
2317
+ * Used for all EIP-712 and permit/authorization signatures.
2318
+ *
2319
+ * @example
2320
+ * ```typescript
2321
+ * const sig: Signature = { v: 28, r: "0x...", s: "0x..." }
2322
+ * ```
2323
+ */
2324
+ interface Signature {
2325
+ /** Recovery identifier (27 or 28) */
2326
+ v: number;
2327
+ /** ECDSA signature r value (32-byte hex string) */
2328
+ r: `0x${string}`;
2329
+ /** ECDSA signature s value (32-byte hex string) */
2330
+ s: `0x${string}`;
2331
+ }
2332
+ declare const PERMIT_STANDARDS: readonly ["EIP-2612"];
2333
+ type KnownPermitStandard = (typeof PERMIT_STANDARDS)[number];
2334
+ /**
2335
+ * Supported permit/authorization standards for cross-chain USDC.
2336
+ *
2337
+ * Extend this union as new standards are supported.
2338
+ */
2339
+ type PermitStandardName = KnownPermitStandard
2340
+ /**
2341
+ * Branded string type to keep this union from collapsing into plain `string`.
2342
+ * This lets TS/your IDE still offer autocomplete for the known literals
2343
+ * while allowing any other string without casting.
2344
+ */
2345
+ | (string & {
2346
+ readonly __brand?: 'PermitStandardName';
2347
+ });
2348
+ /**
2349
+ * Metadata required to construct an EIP-712 domain.
2350
+ *
2351
+ * Used as input to typed data builders for various standards.
2352
+ *
2353
+ * @example
2354
+ * ```typescript
2355
+ * const meta: DomainMeta = {
2356
+ * name: "USD Coin",
2357
+ * version: "2",
2358
+ * chainId: 1,
2359
+ * verifyingContract: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
2360
+ * }
2361
+ * ```
2362
+ */
2363
+ interface DomainMeta {
2364
+ /** Human-readable name of the signing domain */
2365
+ name: string;
2366
+ /** Current major version of the signing domain */
2367
+ version: string;
2368
+ /** EVM chain ID */
2369
+ chainId: number | bigint;
2370
+ /** Address of the verifying contract */
2371
+ verifyingContract: `0x${string}`;
2372
+ }
2373
+
2374
+ /**
2375
+ * Utility module for handling ECDSA signatures and EIP-712 typed data.
2376
+ *
2377
+ * This file provides:
2378
+ * 1. parseSignature: Take a raw hexadecimal signature and split it into its three
2379
+ * ECDSA components: r, s, and v (recovery identifier). Used when verifying
2380
+ * Ethereum transactions and signed messages.
2381
+ *
2382
+ * 2. buildTypedData: Assemble a data structure compliant with EIP-712, which standardizes
2383
+ * how structured data is formatted and hashed for secure off-chain signing.
2384
+ *
2385
+ * Key concepts:
2386
+ * - Cryptographic signatures (r, s, v) ensure that only the holder of a private key can
2387
+ * authorize actions or sign messages. The 'v' component enables recovering the public key
2388
+ * from the signature, confirming the signer’s identity.
2389
+ * - EIP-712 typed data enforces a clear schema for signing, preventing ambiguous or replayable
2390
+ * signatures and simplifying integration with common wallet libraries.
2391
+ */
2392
+
2393
+ /**
2394
+ * parseSignature
2395
+ *
2396
+ * Parse a 65-byte ECDSA signature into its r, s, and v components, expressed in hex.
2397
+ *
2398
+ * Ethereum signatures are structured as:
2399
+ * signature = r (32 bytes) || s (32 bytes) || v (1 byte)
2400
+ *
2401
+ * - r, s: Big-endian hex values (32 bytes each)
2402
+ * - v: Recovery identifier, used by secp256k1 to reconstruct the signer’s public key
2403
+ *
2404
+ * @param signatureHex - Signature as a hex string, optionally prefixed with "0x".
2405
+ * @returns { r, s, v }
2406
+ * - r: Hex string of the R component.
2407
+ * - s: Hex string of the S component.
2408
+ * - v: Numeric recovery ID.
2409
+ *
2410
+ * @throws Error if:
2411
+ * - Input is not valid hex.
2412
+ * - Incorrect length (must be exactly 65 bytes / 130 hex chars).
2413
+ * - v is outside the supported range (Legacy or EIP-155 formula).
2414
+ *
2415
+ * Notes on EIP-155 overload:
2416
+ * EIP-155 repurposes the v field so that:
2417
+ * v = 35 + (2 * chainId) + recoveryId(0 or 1)
2418
+ * Any v value >= 35 indicates the chain-id is encoded, preventing cross-chain replay.
2419
+ * r and s values remain unchanged.
2420
+ *
2421
+ * Example:
2422
+ * ```typescript
2423
+ * const rawSig = '0x6c1b...f02b'
2424
+ * try {
2425
+ * const { r, s, v } = parseSignature(rawSig)
2426
+ * console.log('R:', r)
2427
+ * console.log('S:', s)
2428
+ * console.log('Recovery ID (v):', v)
2429
+ * } catch (err) {
2430
+ * console.error('Signature parse error:', err.message)
2431
+ * }
2432
+ * ```
2433
+ */
2434
+ declare function parseSignature(signatureHex: string): Signature;
2298
2435
 
2299
2436
  /**
2300
2437
  * Core type definitions for EVM-compatible blockchain transaction execution
@@ -2475,6 +2612,129 @@ declare abstract class EvmAdapter<TAdapterCapabilities extends AdapterCapabiliti
2475
2612
  calculateTransactionFee(baseComputeUnits: bigint, bufferBasisPoints: bigint | undefined, chain: EVMChainDefinition): Promise<EstimatedGas>;
2476
2613
  }
2477
2614
 
2615
+ /**
2616
+ * EIP-2612 permit type definition.
2617
+ * Defines the structure for permit signatures according to EIP-2612 specification.
2618
+ *
2619
+ * @see {@link https://eips.ethereum.org/EIPS/eip-2612 | EIP-2612 Specification}
2620
+ */
2621
+ declare const EIP2612_TYPES: {
2622
+ readonly Permit: TypedDataField[];
2623
+ };
2624
+ /**
2625
+ * EIP-2612 permit message structure.
2626
+ * This is the exact data that gets signed according to EIP-2612.
2627
+ */
2628
+ interface EIP2612Message extends Record<string, unknown> {
2629
+ /** Token owner address */
2630
+ owner: `0x${string}`;
2631
+ /** Address permitted to spend tokens */
2632
+ spender: `0x${string}`;
2633
+ /** Amount of tokens permitted */
2634
+ value: bigint;
2635
+ /** Current nonce for the owner */
2636
+ nonce: bigint;
2637
+ /** Deadline timestamp (Unix timestamp in seconds) */
2638
+ deadline: bigint;
2639
+ }
2640
+ /**
2641
+ * Input options for building an EIP-2612 permit.
2642
+ * Nonce and deadline can be omitted and will be fetched/computed automatically.
2643
+ *
2644
+ * **Address Formatting**: Addresses are automatically formatted with proper EIP-55
2645
+ * checksumming, so you can provide addresses in any case format.
2646
+ */
2647
+ interface EIP2612Options extends Record<string, unknown> {
2648
+ /** Token owner address (automatically formatted with EIP-55 checksumming) */
2649
+ owner: `0x${string}`;
2650
+ /** Address that will be permitted to spend tokens (automatically formatted with EIP-55 checksumming) */
2651
+ spender: `0x${string}`;
2652
+ /** Amount of tokens to permit */
2653
+ value: bigint;
2654
+ /** Optional nonce - will be fetched from token contract if not provided */
2655
+ nonce?: bigint;
2656
+ /** Optional deadline - will default to 1 hour from now if not provided */
2657
+ deadline?: bigint;
2658
+ }
2659
+ /**
2660
+ * Function type for fetching nonces from EIP-2612 compatible tokens.
2661
+ *
2662
+ * @param owner - Token owner address
2663
+ * @param token - Token contract address
2664
+ * @returns Promise resolving to current nonce
2665
+ */
2666
+ type EIP2612NonceFetcher = (token: `0x${string}`, owner: `0x${string}`) => Promise<bigint>;
2667
+
2668
+ interface EIP2612Adapter {
2669
+ fetchEIP2612Nonce(tokenAddress: `0x${string}`, ownerAddress: `0x${string}`, ctx: OperationContext): Promise<bigint>;
2670
+ }
2671
+ /**
2672
+ * Build EIP-2612 typed data for permit signing.
2673
+ *
2674
+ * This function creates the complete EIP-712 typed data structure required
2675
+ * for EIP-2612 permit signatures, including automatic nonce fetching using
2676
+ * the adapter's built-in nonce fetching capability.
2677
+ *
2678
+ * **Address Formatting**: All addresses are automatically formatted with proper
2679
+ * EIP-55 checksumming using the `convertAddress` utility, ensuring compatibility
2680
+ * with strict validation libraries like viem.
2681
+ *
2682
+ * **Nonce Handling**: The nonce can be provided explicitly or will be fetched
2683
+ * automatically using the adapter's `fetchEIP2612Nonce` method, which queries
2684
+ * the token contract's `nonces(owner)` function.
2685
+ *
2686
+ * **Deadline Calculation**: If no deadline is provided, it defaults to 24 hours
2687
+ * from the current time (computed using `computeDefaultDeadline`).
2688
+ *
2689
+ * @param meta - Domain metadata for the token contract
2690
+ * @param adapter - Adapter instance with nonce-fetching capability
2691
+ * @param opts - EIP-2612 permit options including owner, spender, value
2692
+ * @returns Complete EIP-712 typed data ready for signing
2693
+ *
2694
+ * @example
2695
+ * ```typescript
2696
+ * import { buildEIP2612TypedData } from '@core/adapter-evm'
2697
+ *
2698
+ * const typedData = await buildEIP2612TypedData(
2699
+ * {
2700
+ * name: 'USD Coin',
2701
+ * version: '2',
2702
+ * chainId: 1,
2703
+ * verifyingContract: '0xa0b86a33e6441e4d178bb0c14ce0e9ce9c83bdd8'
2704
+ * },
2705
+ * adapter,
2706
+ * {
2707
+ * owner: '0x742d35cc6639c0532fbe9002b3a2265ca4c878f8e',
2708
+ * spender: '0x1234567890123456789012345678901234567890',
2709
+ * value: 1000000n
2710
+ * }
2711
+ * )
2712
+ *
2713
+ * const signature = await adapter.signTypedData(typedData)
2714
+ * ```
2715
+ */
2716
+ declare function buildEIP2612TypedData(meta: DomainMeta, adapter: EIP2612Adapter, opts: EIP2612Options, ctx: OperationContext): Promise<TypedData<typeof EIP2612_TYPES, EIP2612Message>>;
2717
+
2718
+ /**
2719
+ * Compute default deadline for permit signatures.
2720
+ *
2721
+ * Returns a timestamp 1 hour (3600 seconds) from the current time,
2722
+ * converted to Unix timestamp format as a bigint. This is commonly
2723
+ * used as the default expiration time for EIP-2612 permit signatures.
2724
+ *
2725
+ * @returns Unix timestamp (in seconds) 1 hour from now as a bigint
2726
+ *
2727
+ * @example
2728
+ * ```typescript
2729
+ * import { computeDefaultDeadline } from '@core/adapter-evm'
2730
+ *
2731
+ * const deadline = computeDefaultDeadline()
2732
+ * console.log(`Permit expires at: ${deadline}`)
2733
+ * // Output: Permit expires at: 1640998800
2734
+ * ```
2735
+ */
2736
+ declare function computeDefaultDeadline(): bigint;
2737
+
2478
2738
  /**
2479
2739
  * @packageDocumentation
2480
2740
  * @module EthersAdapter
@@ -3404,5 +3664,5 @@ declare const createAdapterFromProvider: (params: CreateAdapterFromProviderParam
3404
3664
  */
3405
3665
  declare function validateAdapterCapabilities(capabilities: AdapterCapabilities): void;
3406
3666
 
3407
- export { Blockchain, EthersAdapter, createAdapterFromPrivateKey, createAdapterFromProvider, validateAdapterCapabilities };
3408
- export type { ChainIdentifier, CreateAdapterFromPrivateKeyParams, CreateAdapterFromProviderParams, EthersAdapterOptions };
3667
+ export { Blockchain, EthersAdapter, buildEIP2612TypedData, computeDefaultDeadline, createAdapterFromPrivateKey, createAdapterFromProvider, parseSignature, validateAdapterCapabilities };
3668
+ export type { ChainIdentifier, CreateAdapterFromPrivateKeyParams, CreateAdapterFromProviderParams, EIP2612Message, EIP2612NonceFetcher, EIP2612Options, EthersAdapterOptions, PermitStandardName, Signature, TypedData, TypedDataField };
package/index.mjs CHANGED
@@ -17,11 +17,12 @@
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';
22
+ import '@ethersproject/units';
21
23
  import { hexlify, hexZeroPad } from '@ethersproject/bytes';
22
24
  import { getAddress } from '@ethersproject/address';
23
25
  import bs58 from 'bs58';
24
- import '@ethersproject/units';
25
26
 
26
27
  /**
27
28
  * Type-safe registry for managing and executing blockchain action handlers.
@@ -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,
@@ -2986,6 +3031,10 @@ class KitError extends Error {
2986
3031
  }
2987
3032
  }
2988
3033
 
3034
+ /**
3035
+ * Minimum error code for INPUT type errors.
3036
+ * INPUT errors represent validation failures and invalid parameters.
3037
+ */
2989
3038
  /**
2990
3039
  * Standardized error definitions for INPUT type errors.
2991
3040
  *
@@ -8006,6 +8055,246 @@ class EvmAdapter extends Adapter {
8006
8055
  }
8007
8056
  }
8008
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
+
8009
8298
  /**
8010
8299
  * Checks if a function in an ABI is read-only (view or pure).
8011
8300
  *
@@ -9683,7 +9972,7 @@ class EthersAdapter extends EvmAdapter {
9683
9972
  * @param overrides - Optional estimate overrides.
9684
9973
  * @returns Promise resolving to the estimated gas, gas price, and total fee.
9685
9974
  */
9686
- async estimateGasForFunction(contract, functionName, args, chain, overrides) {
9975
+ async estimateGasForFunction(contract, functionName, args, chain, overrides, fallback) {
9687
9976
  let gas;
9688
9977
  try {
9689
9978
  const contractFunction = contract.getFunction(functionName);
@@ -9692,7 +9981,12 @@ class EthersAdapter extends EvmAdapter {
9692
9981
  : await contractFunction.estimateGas(...args);
9693
9982
  }
9694
9983
  catch (error) {
9695
- throw new Error(`Gas estimation failed: ${error.message}`);
9984
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
9985
+ if (fallback &&
9986
+ errorMessage.toLocaleLowerCase().includes('execution reverted')) {
9987
+ return fallback;
9988
+ }
9989
+ throw new Error(`Gas estimation failed: ${errorMessage}`);
9696
9990
  }
9697
9991
  let gasPrice;
9698
9992
  try {
@@ -9917,18 +10211,19 @@ class EthersAdapter extends EvmAdapter {
9917
10211
  }
9918
10212
  // For state-changing functions, use the original logic
9919
10213
  const contract = this.createContractInstance(params, signer, resolvedContext.address, provider);
9920
- await this.simulateFunctionCall(contract, functionName, args);
9921
10214
  return {
9922
10215
  type: 'evm',
9923
- estimate: async (overrides) => {
10216
+ estimate: async (overrides, fallback) => {
9924
10217
  await this.ensureChain(targetChain);
9925
10218
  // Reconnect the contract with the correct provider for the target chain to ensure
9926
10219
  // gas estimation and fee data retrieval use the correct network.
9927
10220
  const estimationProvider = await this.getProvider(targetChain);
9928
10221
  const contractForEstimation = contract.connect(estimationProvider);
9929
- return this.estimateGasForFunction(contractForEstimation, functionName, args, targetChain, overrides);
10222
+ return this.estimateGasForFunction(contractForEstimation, functionName, args, targetChain, overrides, fallback);
9930
10223
  },
9931
10224
  execute: async (overrides) => {
10225
+ // Simulate the function call to catch errors before submission
10226
+ await this.simulateFunctionCall(contract, functionName, args);
9932
10227
  await this.ensureChain(targetChain);
9933
10228
  // Reconnect the contract with the current signer, which is on the correct
9934
10229
  // chain after `ensureChain`, to ensure the transaction is populated and
@@ -9980,6 +10275,11 @@ class EthersAdapter extends EvmAdapter {
9980
10275
  * ```
9981
10276
  */
9982
10277
  async getAddress(chain) {
10278
+ // Prevent calling getAddress on developer-controlled adapters
10279
+ if (this.capabilities?.addressContext === 'developer-controlled') {
10280
+ throw new Error('Cannot call getAddress() on developer-controlled adapters. ' +
10281
+ 'Address must be provided explicitly in the operation context.');
10282
+ }
9983
10283
  // Chain parameter should now be provided by resolveOperationContext
9984
10284
  if (!chain) {
9985
10285
  throw new Error('Chain parameter is required for address resolution. ' +
@@ -10529,5 +10829,5 @@ const createAdapterFromProvider = async (params) => {
10529
10829
  }, resolvedCapabilities);
10530
10830
  };
10531
10831
 
10532
- export { Blockchain, EthersAdapter, createAdapterFromPrivateKey, createAdapterFromProvider, validateAdapterCapabilities };
10832
+ export { Blockchain, EthersAdapter, buildEIP2612TypedData, computeDefaultDeadline, createAdapterFromPrivateKey, createAdapterFromProvider, parseSignature, validateAdapterCapabilities };
10533
10833
  //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@circle-fin/adapter-ethers-v6",
3
- "version": "1.0.0",
4
- "main": "./index.cjs.js",
3
+ "version": "1.1.0",
4
+ "description": "EVM blockchain adapter powered by Ethers v6",
5
+ "main": "./index.cjs",
5
6
  "module": "./index.mjs",
6
7
  "types": "./index.d.ts",
7
8
  "dependencies": {
@@ -21,16 +22,19 @@
21
22
  ".": {
22
23
  "types": "./index.d.ts",
23
24
  "import": "./index.mjs",
24
- "default": "./index.cjs.js"
25
+ "require": "./index.cjs",
26
+ "default": "./index.cjs"
25
27
  }
26
28
  },
27
29
  "files": [
28
30
  "index.*",
29
31
  "README.md",
30
32
  "LICENSE",
33
+ "CHANGELOG.md",
31
34
  "package.json"
32
35
  ],
33
36
  "publishConfig": {
37
+ "access": "public",
34
38
  "directory": "../../dist/adapters/ethers.v6"
35
39
  }
36
40
  }
package/index.cjs.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}