@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 +89 -0
- package/{index.cjs.js → index.cjs} +316 -13
- package/index.cjs.map +1 -0
- package/index.d.ts +269 -9
- package/index.mjs +313 -13
- package/package.json +7 -3
- package/index.cjs.js.map +0 -1
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
|
|
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
|
|
1790
|
-
title: 'Sonic
|
|
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:
|
|
1839
|
+
chainId: 14601,
|
|
1797
1840
|
isTestnet: true,
|
|
1798
1841
|
explorerUrl: 'https://testnet.sonicscan.org/tx/{hash}',
|
|
1799
|
-
rpcEndpoints: ['https://rpc.
|
|
1842
|
+
rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
|
|
1800
1843
|
eurcAddress: null,
|
|
1801
|
-
usdcAddress: '
|
|
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
1784
|
-
title: 'Sonic
|
|
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:
|
|
1834
|
+
chainId: 14601,
|
|
1791
1835
|
isTestnet: true,
|
|
1792
1836
|
explorerUrl: 'https://testnet.sonicscan.org/tx/{hash}',
|
|
1793
|
-
rpcEndpoints: ['https://rpc.
|
|
1837
|
+
rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
|
|
1794
1838
|
eurcAddress: null,
|
|
1795
|
-
usdcAddress: '
|
|
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
|
-
|
|
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.
|
|
4
|
-
"
|
|
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
|
-
"
|
|
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":""}
|