@circle-fin/usdckit 0.22.0 → 0.23.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.
Files changed (59) hide show
  1. package/dist/cjs/abis/EIP2612.d.ts +197 -0
  2. package/dist/cjs/abis/EIP2612.js +78 -0
  3. package/dist/cjs/actions/index.d.ts +66 -0
  4. package/dist/cjs/actions/index.js +7 -0
  5. package/dist/cjs/actions/index.test.js +4 -0
  6. package/dist/cjs/actions/permit.d.ts +19 -0
  7. package/dist/cjs/actions/permit.js +50 -0
  8. package/dist/cjs/actions/permit.test.d.ts +1 -0
  9. package/dist/cjs/actions/permit.test.js +652 -0
  10. package/dist/cjs/actions/signEIP2612Permit.d.ts +130 -0
  11. package/dist/cjs/actions/signEIP2612Permit.js +128 -0
  12. package/dist/cjs/actions/signEIP2612Permit.test.d.ts +1 -0
  13. package/dist/cjs/actions/signEIP2612Permit.test.js +434 -0
  14. package/dist/cjs/chains/ARC_TESTNET.d.ts +180 -0
  15. package/dist/cjs/chains/ARC_TESTNET.js +57 -0
  16. package/dist/cjs/chains/index.d.ts +1 -0
  17. package/dist/cjs/chains/index.js +3 -1
  18. package/dist/cjs/chains/index.test.js +1 -0
  19. package/dist/cjs/extractChain.d.ts +179 -0
  20. package/dist/cjs/extractChain.test.js +1 -0
  21. package/dist/cjs/metadata.js +1 -1
  22. package/dist/cjs/providers/circle-wallets/actions/createAccount.d.ts +179 -0
  23. package/dist/cjs/providers/circle-wallets/actions/createAccount.js +3 -0
  24. package/dist/cjs/providers/circle-wallets/actions/estimateContractExecutionFee.d.ts +6 -0
  25. package/dist/cjs/providers/circle-wallets/actions/estimateTransferFee.d.ts +6 -0
  26. package/dist/cjs/providers/circle-wallets/actions/getAccounts.d.ts +179 -0
  27. package/dist/cjs/providers/circle-wallets/index.d.ts +358 -0
  28. package/dist/cjs/providers/circle-wallets/index.js +2 -0
  29. package/dist/cjs/providers/circle-wallets/transports/index.d.ts +12 -0
  30. package/dist/esm/abis/EIP2612.d.ts +197 -0
  31. package/dist/esm/abis/EIP2612.js +76 -0
  32. package/dist/esm/actions/index.d.ts +66 -0
  33. package/dist/esm/actions/index.js +7 -0
  34. package/dist/esm/actions/index.test.js +4 -0
  35. package/dist/esm/actions/permit.d.ts +19 -0
  36. package/dist/esm/actions/permit.js +44 -0
  37. package/dist/esm/actions/permit.test.d.ts +1 -0
  38. package/dist/esm/actions/permit.test.js +650 -0
  39. package/dist/esm/actions/signEIP2612Permit.d.ts +130 -0
  40. package/dist/esm/actions/signEIP2612Permit.js +122 -0
  41. package/dist/esm/actions/signEIP2612Permit.test.d.ts +1 -0
  42. package/dist/esm/actions/signEIP2612Permit.test.js +432 -0
  43. package/dist/esm/chains/ARC_TESTNET.d.ts +180 -0
  44. package/dist/esm/chains/ARC_TESTNET.js +54 -0
  45. package/dist/esm/chains/index.d.ts +1 -0
  46. package/dist/esm/chains/index.js +1 -0
  47. package/dist/esm/chains/index.test.js +1 -0
  48. package/dist/esm/extractChain.d.ts +179 -0
  49. package/dist/esm/extractChain.test.js +2 -1
  50. package/dist/esm/metadata.js +1 -1
  51. package/dist/esm/providers/circle-wallets/actions/createAccount.d.ts +179 -0
  52. package/dist/esm/providers/circle-wallets/actions/createAccount.js +3 -0
  53. package/dist/esm/providers/circle-wallets/actions/estimateContractExecutionFee.d.ts +6 -0
  54. package/dist/esm/providers/circle-wallets/actions/estimateTransferFee.d.ts +6 -0
  55. package/dist/esm/providers/circle-wallets/actions/getAccounts.d.ts +179 -0
  56. package/dist/esm/providers/circle-wallets/index.d.ts +358 -0
  57. package/dist/esm/providers/circle-wallets/index.js +3 -1
  58. package/dist/esm/providers/circle-wallets/transports/index.d.ts +12 -0
  59. package/package.json +5 -4
@@ -0,0 +1,130 @@
1
+ import { signTypedData as _signTypedData } from 'viem/actions';
2
+ import type { Account, Address, BaseUnits, NativeUnits } from '../types.js';
3
+ import type { Chain, Client } from 'viem';
4
+ /**
5
+ * Parameters for the {@link signEIP2612Permit} function.
6
+ */
7
+ export type SignEIP2612PermitParams = {
8
+ /**
9
+ * The token contract address or contract object that supports EIP-2612 permit functionality.
10
+ */
11
+ token: Address | {
12
+ address: Address;
13
+ };
14
+ /**
15
+ * The account that owns the tokens and will sign the permit.
16
+ */
17
+ owner: Address | Account;
18
+ /**
19
+ * The account that will be granted permission to spend the tokens.
20
+ */
21
+ spender: Address | Account;
22
+ /**
23
+ * The amount of tokens to permit for spending.
24
+ */
25
+ value: NativeUnits | BaseUnits;
26
+ /**
27
+ * The deadline timestamp for the permit. Defaults to maximum uint256 value.
28
+ */
29
+ deadline?: bigint;
30
+ /**
31
+ * The blockchain network on which the permit will be created.
32
+ */
33
+ chain?: Chain;
34
+ /**
35
+ * The name of the token. Defaults to the name of the token contract.
36
+ */
37
+ name?: string;
38
+ /**
39
+ * The version of the token. Defaults to the version of the token contract. If the token contract does not have a version, defaults to '1'.
40
+ */
41
+ version?: string;
42
+ };
43
+ /**
44
+ * Return type for the signEIP2612Permit function.
45
+ */
46
+ export type SignEIP2612PermitReturnType = ReturnType<typeof _signTypedData>;
47
+ /**
48
+ * Creates a signed EIP-2612 permit
49
+ *
50
+ * @param client - The client instance.
51
+ * @param params - The permit parameters.
52
+ * @returns An object containing the typed data structure and signature.
53
+ *
54
+ * @example
55
+ * Creating a permit for USDC spending
56
+ * ```typescript
57
+ * const permit = await client.signEIP2612Permit({
58
+ * token: client.chain.contracts.USDC,
59
+ * owner: '0x0000000000000000000000000000000000000000',
60
+ * spender: '0x1111111111111111111111111111111111111111',
61
+ * value: 1000000n, // 1 USDC (6 decimals)
62
+ * })
63
+ * ```
64
+ *
65
+ * @example
66
+ * Creating a permit with custom deadline
67
+ * ```typescript
68
+ * const permit = await client.signEIP2612Permit({
69
+ * token: '0x...',
70
+ * owner: account1,
71
+ * spender: account2,
72
+ * value: '100',
73
+ * deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
74
+ * })
75
+ * ```
76
+ */
77
+ export declare function signEIP2612Permit(client: Client, params: SignEIP2612PermitParams): Promise<{
78
+ typedData: {
79
+ readonly types: {
80
+ readonly EIP712Domain: readonly [{
81
+ readonly name: "name";
82
+ readonly type: "string";
83
+ }, {
84
+ readonly name: "version";
85
+ readonly type: "string";
86
+ }, {
87
+ readonly name: "chainId";
88
+ readonly type: "uint256";
89
+ }, {
90
+ readonly name: "verifyingContract";
91
+ readonly type: "address";
92
+ }];
93
+ readonly Permit: readonly [{
94
+ readonly name: "owner";
95
+ readonly type: "address";
96
+ }, {
97
+ readonly name: "spender";
98
+ readonly type: "address";
99
+ }, {
100
+ readonly name: "value";
101
+ readonly type: "uint256";
102
+ }, {
103
+ readonly name: "nonce";
104
+ readonly type: "uint256";
105
+ }, {
106
+ readonly name: "deadline";
107
+ readonly type: "uint256";
108
+ }];
109
+ };
110
+ readonly primaryType: "Permit";
111
+ readonly domain: {
112
+ readonly name: string;
113
+ readonly version: string;
114
+ readonly chainId: bigint;
115
+ readonly verifyingContract: `0x${string}`;
116
+ };
117
+ readonly message: {
118
+ readonly owner: `0x${string}`;
119
+ readonly spender: `0x${string}`;
120
+ readonly value: bigint;
121
+ readonly nonce: bigint;
122
+ readonly deadline: bigint;
123
+ };
124
+ };
125
+ signature: `0x${string}`;
126
+ r: `0x${string}`;
127
+ s: `0x${string}`;
128
+ yParity: number;
129
+ v: number;
130
+ }>;
@@ -0,0 +1,122 @@
1
+ // Copyright (c) 2025, Circle Internet Group, Inc.
2
+ // All rights reserved.
3
+ //
4
+ // Circle Internet Group, Inc. CONFIDENTIAL
5
+ //
6
+ // This file includes unpublished proprietary source code of Circle Internet
7
+ // Group, Inc. The copyright notice above does not
8
+ // evidence any actual or intended publication of such source code. Disclosure
9
+ // of this source code or any related proprietary information is strictly
10
+ // prohibited without the express written permission of Circle Internet Group, Inc.
11
+ import { maxUint256, getContract, parseSignature } from 'viem';
12
+ import { signTypedData as _signTypedData } from 'viem/actions';
13
+ import { getAction } from 'viem/utils';
14
+ import EIP2612 from '../abis/EIP2612.js';
15
+ import { getClient } from '../utils/getClient.js';
16
+ import { parseAccount } from '../utils/parseAccount.js';
17
+ import { parseAddress } from '../utils/parseAddress.js';
18
+ import { parseUnits } from '../utils/parseUnits.js';
19
+ import { raise } from '../utils/raise.js';
20
+ /**
21
+ * Creates a signed EIP-2612 permit
22
+ *
23
+ * @param client - The client instance.
24
+ * @param params - The permit parameters.
25
+ * @returns An object containing the typed data structure and signature.
26
+ *
27
+ * @example
28
+ * Creating a permit for USDC spending
29
+ * ```typescript
30
+ * const permit = await client.signEIP2612Permit({
31
+ * token: client.chain.contracts.USDC,
32
+ * owner: '0x0000000000000000000000000000000000000000',
33
+ * spender: '0x1111111111111111111111111111111111111111',
34
+ * value: 1000000n, // 1 USDC (6 decimals)
35
+ * })
36
+ * ```
37
+ *
38
+ * @example
39
+ * Creating a permit with custom deadline
40
+ * ```typescript
41
+ * const permit = await client.signEIP2612Permit({
42
+ * token: '0x...',
43
+ * owner: account1,
44
+ * spender: account2,
45
+ * value: '100',
46
+ * deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour from now
47
+ * })
48
+ * ```
49
+ */
50
+ export async function signEIP2612Permit(client, params) {
51
+ const { token: _token, owner: _owner, spender: _spender, value: _value, deadline = maxUint256, chain = client.chain, name: _name, version: _version, } = params;
52
+ const _client = getClient(client, chain);
53
+ const signTypedData = getAction(_client, _signTypedData, 'signTypedData');
54
+ // Parse addresses and accounts
55
+ const { address: verifyingContractAddress } = parseAddress(_token);
56
+ const owner = parseAccount(_owner, { chain });
57
+ const spender = parseAccount(_spender, { chain });
58
+ // Create contract instance with EIP-2612 ABI
59
+ const verifyingContract = getContract({
60
+ address: verifyingContractAddress,
61
+ abi: EIP2612,
62
+ client,
63
+ });
64
+ // Fetch token metadata and owner's nonce
65
+ const [name, version, nonce, decimals] = await Promise.all([
66
+ _name ?? verifyingContract.read.name(),
67
+ // Default version to '1' if `version()` fails
68
+ _version ?? verifyingContract.read.version().catch(() => '1'),
69
+ verifyingContract.read.nonces([owner.address]),
70
+ verifyingContract.read.decimals(),
71
+ ]);
72
+ const value = parseUnits(_value, decimals);
73
+ const typedData = {
74
+ types: {
75
+ EIP712Domain: [
76
+ { name: 'name', type: 'string' },
77
+ { name: 'version', type: 'string' },
78
+ { name: 'chainId', type: 'uint256' },
79
+ { name: 'verifyingContract', type: 'address' },
80
+ ],
81
+ Permit: [
82
+ { name: 'owner', type: 'address' },
83
+ { name: 'spender', type: 'address' },
84
+ { name: 'value', type: 'uint256' },
85
+ { name: 'nonce', type: 'uint256' },
86
+ { name: 'deadline', type: 'uint256' },
87
+ ],
88
+ },
89
+ primaryType: 'Permit',
90
+ domain: {
91
+ name,
92
+ version,
93
+ chainId: BigInt(chain.id),
94
+ verifyingContract: verifyingContract.address,
95
+ },
96
+ message: {
97
+ owner: owner.address,
98
+ spender: spender.address,
99
+ value,
100
+ nonce,
101
+ deadline,
102
+ },
103
+ };
104
+ const signature = await signTypedData({
105
+ ...typedData,
106
+ account: owner,
107
+ });
108
+ const { r, s, v, yParity } = parseSignature(signature);
109
+ return {
110
+ typedData,
111
+ signature,
112
+ r,
113
+ s,
114
+ yParity,
115
+ // Backfill `v` from `yParity` if not present
116
+ // REF: https://github.com/ethereum/go-ethereum/blob/1487a8577d1566497e161a04f8cee3204d4b3d36/core/types/transaction_marshalling.go#L63-L79
117
+ v: Number(v ??
118
+ (yParity === 0 ? 27n : undefined) ??
119
+ (yParity === 1 ? 28n : undefined) ??
120
+ raise(new Error('v is missing and yParity must be 0 or 1'))),
121
+ };
122
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,432 @@
1
+ // Copyright (c) 2025, Circle Internet Group, Inc.
2
+ // All rights reserved.
3
+ //
4
+ // Circle Internet Group, Inc. CONFIDENTIAL
5
+ //
6
+ // This file includes unpublished proprietary source code of Circle Internet
7
+ // Group, Inc. The copyright notice above does not
8
+ // evidence any actual or intended publication of such source code. Disclosure
9
+ // of this source code or any related proprietary information is strictly
10
+ // prohibited without the express written permission of Circle Internet Group, Inc.
11
+ import { maxUint256 } from 'viem';
12
+ import { describe, test, expect, vi, beforeEach } from 'vitest';
13
+ // Create mock functions using vi.hoisted() to handle hoisting properly
14
+ const mockGetContract = vi.hoisted(() => vi.fn());
15
+ const mockSignTypedData = vi.hoisted(() => vi.fn());
16
+ const mockGetAction = vi.hoisted(() => vi.fn());
17
+ import { ETH_SEPOLIA } from '../chains/index.js';
18
+ import { signEIP2612Permit } from './signEIP2612Permit.js';
19
+ // Mock viem with factory function
20
+ vi.mock('viem', async (importOriginal) => {
21
+ return {
22
+ ...(await importOriginal()),
23
+ getContract: mockGetContract,
24
+ };
25
+ });
26
+ // Mock viem/actions
27
+ vi.mock('viem/actions', async (importOriginal) => {
28
+ return {
29
+ ...(await importOriginal()),
30
+ signTypedData: mockSignTypedData,
31
+ };
32
+ });
33
+ // Mock viem/utils
34
+ vi.mock('viem/utils', async (importOriginal) => {
35
+ return {
36
+ ...(await importOriginal()),
37
+ getAction: mockGetAction,
38
+ };
39
+ });
40
+ describe.concurrent('signEIP2612Permit', () => {
41
+ const mockClient = {
42
+ chain: ETH_SEPOLIA,
43
+ };
44
+ const mockContract = {
45
+ address: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
46
+ read: {
47
+ name: vi.fn(),
48
+ version: vi.fn(),
49
+ nonces: vi.fn(),
50
+ decimals: vi.fn(),
51
+ },
52
+ };
53
+ beforeEach(() => {
54
+ vi.clearAllMocks();
55
+ // Setup default mocks
56
+ mockGetContract.mockReturnValue(mockContract);
57
+ mockGetAction.mockReturnValue(mockSignTypedData);
58
+ // Mock contract calls
59
+ mockContract.read.name.mockResolvedValue('USD Coin');
60
+ mockContract.read.version.mockResolvedValue('2');
61
+ mockContract.read.nonces.mockResolvedValue(0n);
62
+ mockContract.read.decimals.mockResolvedValue(6);
63
+ mockSignTypedData.mockResolvedValue('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b');
64
+ });
65
+ test('should be defined and be a function', () => {
66
+ expect(typeof signEIP2612Permit).toBe('function');
67
+ });
68
+ test('should create permit with basic parameters', async () => {
69
+ const params = {
70
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
71
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
72
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
73
+ value: '1.0',
74
+ };
75
+ const result = await signEIP2612Permit(mockClient, params);
76
+ expect(result.typedData).toEqual({
77
+ types: {
78
+ EIP712Domain: [
79
+ { name: 'name', type: 'string' },
80
+ { name: 'version', type: 'string' },
81
+ { name: 'chainId', type: 'uint256' },
82
+ { name: 'verifyingContract', type: 'address' },
83
+ ],
84
+ Permit: [
85
+ { name: 'owner', type: 'address' },
86
+ { name: 'spender', type: 'address' },
87
+ { name: 'value', type: 'uint256' },
88
+ { name: 'nonce', type: 'uint256' },
89
+ { name: 'deadline', type: 'uint256' },
90
+ ],
91
+ },
92
+ primaryType: 'Permit',
93
+ domain: {
94
+ name: 'USD Coin',
95
+ version: '2',
96
+ chainId: BigInt(ETH_SEPOLIA.id),
97
+ verifyingContract: mockContract.address,
98
+ },
99
+ message: {
100
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
101
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
102
+ value: 1000000n,
103
+ nonce: 0n,
104
+ deadline: maxUint256,
105
+ },
106
+ });
107
+ expect(result.signature).toBe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b');
108
+ expect(result.v).toBe(27);
109
+ expect(result.yParity).toBe(0);
110
+ expect(result.r).toBe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef');
111
+ expect(result.s).toBe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef');
112
+ });
113
+ test('should handle token as contract object', async () => {
114
+ const params = {
115
+ token: { address: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D' },
116
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
117
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
118
+ value: '1.0',
119
+ };
120
+ const result = await signEIP2612Permit(mockClient, params);
121
+ expect(result.typedData.domain.verifyingContract).toBe('0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D');
122
+ });
123
+ test('should handle BaseUnits (bigint) value', async () => {
124
+ const params = {
125
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
126
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
127
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
128
+ value: 1000000n,
129
+ };
130
+ const result = await signEIP2612Permit(mockClient, params);
131
+ expect(result.typedData.message.value).toBe(1000000n); // Should pass through unchanged for bigint
132
+ });
133
+ test('should handle address strings for owner and spender', async () => {
134
+ const ownerAccount = '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e';
135
+ const spenderAccount = '0x8ba1f109551bD432803012645Hac136c22C57B9';
136
+ const params = {
137
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
138
+ owner: ownerAccount,
139
+ spender: spenderAccount,
140
+ value: '1.0',
141
+ };
142
+ const result = await signEIP2612Permit(mockClient, params);
143
+ expect(result.typedData.message.owner).toBe(ownerAccount);
144
+ expect(result.typedData.message.spender).toBe(spenderAccount);
145
+ });
146
+ test('should use custom deadline when provided', async () => {
147
+ const customDeadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
148
+ const params = {
149
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
150
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
151
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
152
+ value: '1.0',
153
+ deadline: customDeadline,
154
+ };
155
+ const result = await signEIP2612Permit(mockClient, params);
156
+ expect(result.typedData.message.deadline).toBe(customDeadline);
157
+ });
158
+ test('should use maxUint256 as default deadline', async () => {
159
+ const params = {
160
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
161
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
162
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
163
+ value: '1.0',
164
+ };
165
+ const result = await signEIP2612Permit(mockClient, params);
166
+ expect(result.typedData.message.deadline).toBe(maxUint256);
167
+ });
168
+ test('should use custom chain when provided', async () => {
169
+ const customChain = ETH_SEPOLIA;
170
+ const params = {
171
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
172
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
173
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
174
+ value: '1.0',
175
+ chain: customChain,
176
+ };
177
+ const result = await signEIP2612Permit(mockClient, params);
178
+ expect(result.typedData.domain.chainId).toBe(BigInt(customChain.id));
179
+ });
180
+ test('should construct correct EIP-712 typed data', async () => {
181
+ const params = {
182
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
183
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
184
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
185
+ value: '1.0',
186
+ };
187
+ const result = await signEIP2612Permit(mockClient, params);
188
+ expect(result.typedData.primaryType).toBe('Permit');
189
+ expect(result.typedData.domain).toEqual({
190
+ name: 'USD Coin',
191
+ version: '2',
192
+ chainId: BigInt(ETH_SEPOLIA.id),
193
+ verifyingContract: mockContract.address,
194
+ });
195
+ expect(result.typedData.message).toEqual({
196
+ owner: params.owner,
197
+ spender: params.spender,
198
+ value: 1000000n,
199
+ nonce: 0n,
200
+ deadline: maxUint256,
201
+ });
202
+ });
203
+ test('should fetch contract metadata', async () => {
204
+ const params = {
205
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
206
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
207
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
208
+ value: '1.0',
209
+ };
210
+ await signEIP2612Permit(mockClient, params);
211
+ expect(mockContract.read.name).toHaveBeenCalled();
212
+ expect(mockContract.read.version).toHaveBeenCalled();
213
+ expect(mockContract.read.nonces).toHaveBeenCalledWith([params.owner]);
214
+ expect(mockContract.read.decimals).toHaveBeenCalled();
215
+ });
216
+ test('should call signTypedData with correct params', async () => {
217
+ const params = {
218
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
219
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
220
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
221
+ value: '1.0',
222
+ };
223
+ await signEIP2612Permit(mockClient, params);
224
+ expect(mockSignTypedData).toHaveBeenCalledTimes(1);
225
+ const callArgs = mockSignTypedData.mock.calls[0];
226
+ const signedData = callArgs[0];
227
+ expect(signedData.account.address).toBe(params.owner);
228
+ expect(signedData.primaryType).toBe('Permit');
229
+ expect(signedData.message).toEqual({
230
+ owner: params.owner,
231
+ spender: params.spender,
232
+ value: 1000000n, // 1.0 with 6 decimals
233
+ nonce: 0n,
234
+ deadline: maxUint256,
235
+ });
236
+ });
237
+ test('should handle different token decimals', async () => {
238
+ const params = {
239
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
240
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
241
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
242
+ value: '1.0',
243
+ };
244
+ // Override contract decimals for this test
245
+ mockContract.read.decimals.mockResolvedValue(18);
246
+ const result = await signEIP2612Permit(mockClient, params);
247
+ // With 18 decimals, '1.0' should be converted to 1000000000000000000n
248
+ expect(result.typedData.message.value).toBe(1000000000000000000n);
249
+ });
250
+ test('should fetch and use nonce from contract', async () => {
251
+ const params = {
252
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
253
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
254
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
255
+ value: '1.0',
256
+ };
257
+ const result = await signEIP2612Permit(mockClient, params);
258
+ expect(mockContract.read.nonces).toHaveBeenCalledWith([params.owner]);
259
+ expect(result.typedData.message.nonce).toBe(0n); // Default mock value
260
+ });
261
+ test('should handle different contract metadata', async () => {
262
+ const params = {
263
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
264
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
265
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
266
+ value: '1.0',
267
+ };
268
+ const result = await signEIP2612Permit(mockClient, params);
269
+ // Verify the result contains the expected domain values from our mocks
270
+ expect(result.typedData.domain.name).toBe('USD Coin'); // Default mock value
271
+ expect(result.typedData.domain.version).toBe('2'); // Default mock value
272
+ });
273
+ test('should handle zero value permits', async () => {
274
+ const zeroParams = {
275
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
276
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
277
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
278
+ value: '0',
279
+ };
280
+ const result = await signEIP2612Permit(mockClient, zeroParams);
281
+ // With 6 decimals, '0' should be converted to 0n
282
+ expect(result.typedData.message.value).toBe(0n);
283
+ });
284
+ describe('yParity and v conversion logic', () => {
285
+ test('should parse signature and return v/yParity values', async () => {
286
+ mockSignTypedData.mockResolvedValue('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1c');
287
+ const params = {
288
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
289
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
290
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
291
+ value: '1.0',
292
+ };
293
+ const result = await signEIP2612Permit(mockClient, params);
294
+ expect(result.v).toBe(27);
295
+ expect(result.yParity).toBe(0);
296
+ expect(result.r).toBe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef');
297
+ expect(result.s).toBe('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef');
298
+ });
299
+ });
300
+ describe('edge cases and error scenarios', () => {
301
+ test('should handle custom name parameter', async () => {
302
+ vi.clearAllMocks();
303
+ mockGetContract.mockReturnValue(mockContract);
304
+ mockGetAction.mockReturnValue(mockSignTypedData);
305
+ mockContract.read.version.mockResolvedValue('2');
306
+ mockContract.read.nonces.mockResolvedValue(0n);
307
+ mockContract.read.decimals.mockResolvedValue(6);
308
+ mockSignTypedData.mockResolvedValue('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b');
309
+ const params = {
310
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
311
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
312
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
313
+ value: '1.0',
314
+ name: 'Custom Token Name',
315
+ };
316
+ const result = await signEIP2612Permit(mockClient, params);
317
+ // Should use custom name provided in params
318
+ expect(result.typedData.domain.name).toBe('Custom Token Name');
319
+ });
320
+ test('should handle custom version parameter', async () => {
321
+ vi.clearAllMocks();
322
+ mockGetContract.mockReturnValue(mockContract);
323
+ mockGetAction.mockReturnValue(mockSignTypedData);
324
+ mockContract.read.name.mockResolvedValue('USD Coin');
325
+ mockContract.read.nonces.mockResolvedValue(0n);
326
+ mockContract.read.decimals.mockResolvedValue(6);
327
+ mockSignTypedData.mockResolvedValue('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b');
328
+ const params = {
329
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
330
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
331
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
332
+ value: '1.0',
333
+ version: '3',
334
+ };
335
+ const result = await signEIP2612Permit(mockClient, params);
336
+ // Should use custom version provided in params
337
+ expect(result.typedData.domain.version).toBe('3');
338
+ });
339
+ test('should handle both custom name and version', async () => {
340
+ vi.restoreAllMocks();
341
+ mockGetContract.mockReturnValue(mockContract);
342
+ mockGetAction.mockReturnValue(mockSignTypedData);
343
+ mockContract.read.nonces.mockResolvedValue(0n);
344
+ mockContract.read.decimals.mockResolvedValue(6);
345
+ mockSignTypedData.mockResolvedValue('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1b');
346
+ const params = {
347
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
348
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
349
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
350
+ value: '1.0',
351
+ name: 'Custom Token',
352
+ version: '4',
353
+ };
354
+ const result = await signEIP2612Permit(mockClient, params);
355
+ // Should use both custom values provided in params
356
+ expect(result.typedData.domain.name).toBe('Custom Token');
357
+ expect(result.typedData.domain.version).toBe('4');
358
+ });
359
+ test('should fallback to version "1" when contract.version() throws', async () => {
360
+ // Mock version call to throw an error
361
+ mockContract.read.version.mockRejectedValue(new Error('Version not supported'));
362
+ const params = {
363
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
364
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
365
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
366
+ value: '1.0',
367
+ };
368
+ const result = await signEIP2612Permit(mockClient, params);
369
+ expect(result.typedData.domain.version).toBe('1');
370
+ });
371
+ test('should handle maximum safe integer values', async () => {
372
+ const params = {
373
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
374
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
375
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
376
+ value: Number.MAX_SAFE_INTEGER.toString(),
377
+ };
378
+ const result = await signEIP2612Permit(mockClient, params);
379
+ // Should handle large numbers correctly
380
+ expect(result.typedData.message.value).toBe(BigInt(Number.MAX_SAFE_INTEGER) * BigInt(1000000));
381
+ });
382
+ test('should handle custom deadline values', async () => {
383
+ const customDeadline = 1n;
384
+ const params = {
385
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
386
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
387
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
388
+ value: '1.0',
389
+ deadline: customDeadline,
390
+ };
391
+ const result = await signEIP2612Permit(mockClient, params);
392
+ expect(result.typedData.message.deadline).toBe(customDeadline);
393
+ });
394
+ test('should handle tokens with very high decimals', async () => {
395
+ const highDecimals = 30;
396
+ mockContract.read.decimals.mockResolvedValue(highDecimals);
397
+ const params = {
398
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
399
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
400
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
401
+ value: '1.0',
402
+ };
403
+ const result = await signEIP2612Permit(mockClient, params);
404
+ // 1.0 with 30 decimals should be 10^30
405
+ const expectedValue = BigInt('1000000000000000000000000000000');
406
+ expect(result.typedData.message.value).toBe(expectedValue);
407
+ });
408
+ test('should handle tokens with zero decimals', async () => {
409
+ mockContract.read.decimals.mockResolvedValue(0);
410
+ const params = {
411
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
412
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
413
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
414
+ value: '123',
415
+ };
416
+ const result = await signEIP2612Permit(mockClient, params);
417
+ // With 0 decimals, '123' should remain 123n
418
+ expect(result.typedData.message.value).toBe(123n);
419
+ });
420
+ test('should handle fractional values with high precision', async () => {
421
+ mockContract.read.decimals.mockResolvedValue(18);
422
+ const params = {
423
+ token: '0xA0b86a33E6411E4C3c04E5b72c5F3E6b2A6E1C3D',
424
+ owner: '0x742d35Cc6634C0532925a3b8D40745c5F5d78F6e',
425
+ spender: '0x8ba1f109551bD432803012645Hac136c22C57B9',
426
+ value: '0.000000000000000001', // 1 wei
427
+ };
428
+ const result = await signEIP2612Permit(mockClient, params);
429
+ expect(result.typedData.message.value).toBe(1n);
430
+ });
431
+ });
432
+ });