@gasfree-kit/evm-4337 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # @gasfree-kit/evm-4337
2
+
3
+ ERC-4337 USDT transfers for EVM chains using WDK-backed Safe smart accounts.
4
+
5
+ This package gives you:
6
+
7
+ - standard seed-phrase driven transfers
8
+ - sponsored mode, where a paymaster covers gas
9
+ - token-paid mode, where gas is charged in USDT
10
+ - optional passkey linking and passkey-signed transfers
11
+
12
+ ## How It Works
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ App["Your app"] --> SDK["@gasfree-kit/evm-4337"]
17
+ SDK --> WDK["WDK wallet manager"]
18
+ WDK --> Safe["Safe smart account"]
19
+ Safe --> UserOp["ERC-4337 UserOperation"]
20
+ UserOp --> Bundler["Bundler"]
21
+ Paymaster["Paymaster"] --> Bundler
22
+ Bundler --> Chain["Supported EVM chain"]
23
+ ```
24
+
25
+ ## Supported Chains
26
+
27
+ | Chain | Chain ID | Typical fallback fee estimate |
28
+ | -------- | -------- | ----------------------------- |
29
+ | Ethereum | 1 | `1.40` USDT |
30
+ | Base | 8453 | `0.15` USDT |
31
+ | Arbitrum | 42161 | `0.10` USDT |
32
+ | Optimism | 10 | `0.10` USDT |
33
+ | Polygon | 137 | `0.10` USDT |
34
+ | Celo | 42220 | `0.05` USDT |
35
+ | Plasma | 9745 | `0.05` USDT |
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ npm install @gasfree-kit/evm-4337
41
+ ```
42
+
43
+ Required peer dependency:
44
+
45
+ ```bash
46
+ npm install @tetherto/wdk-wallet-evm-erc-4337
47
+ ```
48
+
49
+ Optional passkey peer dependencies:
50
+
51
+ ```bash
52
+ npm install @safe-global/protocol-kit @safe-global/relay-kit
53
+ ```
54
+
55
+ ## Choose Your Gas Mode
56
+
57
+ | Mode | When to use it | Required config |
58
+ | ---------- | --------------------------------- | ------------------------------------------------ |
59
+ | Sponsored | Your paymaster fully covers gas | `isSponsored: true` and `sponsorshipPolicyId` |
60
+ | Token-paid | Gas is deducted from USDT balance | `isSponsored: false` and a paymaster/token setup |
61
+
62
+ ## Quick Start
63
+
64
+ ### 1. Create a config
65
+
66
+ Sponsored mode:
67
+
68
+ ```ts
69
+ import type { EVM4337ClientConfig } from '@gasfree-kit/evm-4337';
70
+
71
+ const sponsoredConfig: EVM4337ClientConfig = {
72
+ chain: 'base',
73
+ rpcUrl: 'https://mainnet.base.org',
74
+ bundlerUrl: 'https://your-bundler.example.com',
75
+ paymasterUrl: 'https://your-paymaster.example.com',
76
+ isSponsored: true,
77
+ sponsorshipPolicyId: 'your-policy-id',
78
+ };
79
+ ```
80
+
81
+ Token-paid mode:
82
+
83
+ ```ts
84
+ const tokenPaidConfig: EVM4337ClientConfig = {
85
+ chain: 'base',
86
+ rpcUrl: 'https://mainnet.base.org',
87
+ bundlerUrl: 'https://your-bundler.example.com',
88
+ paymasterUrl: 'https://your-paymaster.example.com',
89
+ isSponsored: false,
90
+ paymasterAddress: '0xYourPaymasterAddress',
91
+ // Optional:
92
+ // paymasterTokenAddress: '0xYourUSDTLikeToken'
93
+ };
94
+ ```
95
+
96
+ ### 2. Set up the Safe smart account
97
+
98
+ ```ts
99
+ import { setupErc4337Wallet } from '@gasfree-kit/evm-4337';
100
+
101
+ const seedPhrase = 'your twelve word seed phrase here ...';
102
+ const { wallet, account, address } = await setupErc4337Wallet(seedPhrase, sponsoredConfig);
103
+
104
+ console.log(address); // Safe address
105
+ ```
106
+
107
+ ### 3. Check balance
108
+
109
+ ```ts
110
+ import { EvmTransfer } from '@gasfree-kit/evm-4337';
111
+ import { EVM_CHAINS } from '@gasfree-kit/core';
112
+
113
+ const balance = await EvmTransfer.checkTokenBalance(
114
+ seedPhrase,
115
+ sponsoredConfig,
116
+ EVM_CHAINS.base.usdtAddress,
117
+ );
118
+
119
+ console.log(balance.data.usdBalance);
120
+ ```
121
+
122
+ ### 4. Estimate fees
123
+
124
+ ```ts
125
+ const estimate = await EvmTransfer.getTransactionEstimateFee(
126
+ seedPhrase,
127
+ sponsoredConfig,
128
+ '0x1111111111111111111111111111111111111111',
129
+ );
130
+
131
+ console.log(estimate.data.fee);
132
+ ```
133
+
134
+ ### 5. Send a transfer
135
+
136
+ ```ts
137
+ const result = await EvmTransfer.sendToken(
138
+ seedPhrase,
139
+ sponsoredConfig,
140
+ EVM_CHAINS.base.usdtAddress,
141
+ '50.00',
142
+ '0x1111111111111111111111111111111111111111',
143
+ );
144
+
145
+ console.log(result.transactionHash);
146
+ ```
147
+
148
+ ### 6. Send a batch transfer
149
+
150
+ ```ts
151
+ const batch = await EvmTransfer.sendBatchToken(
152
+ seedPhrase,
153
+ sponsoredConfig,
154
+ EVM_CHAINS.base.usdtAddress,
155
+ [
156
+ { address: '0x1111111111111111111111111111111111111111', amount: '25.00' },
157
+ { address: '0x2222222222222222222222222222222222222222', amount: '10.00' },
158
+ ],
159
+ );
160
+ ```
161
+
162
+ ## Passkey Flow
163
+
164
+ Passkeys are optional. They let you add a WebAuthn signer to an existing Safe so transfers can be approved with biometrics instead of a seed phrase.
165
+
166
+ Important:
167
+
168
+ - the Safe must already be deployed on-chain before you link a passkey
169
+ - `storage: 'persist'` is convenience storage only, not hardened secret storage
170
+
171
+ ### Passkey diagram
172
+
173
+ ```mermaid
174
+ flowchart TD
175
+ Seed["Seed phrase owner"] --> Safe["Safe smart account"]
176
+ Passkey["Passkey owner"] --> Safe
177
+ Safe --> Transfer["Passkey-signed transfer"]
178
+ Transfer --> Bundler["Bundler + paymaster"]
179
+ Bundler --> Chain["EVM chain"]
180
+ ```
181
+
182
+ ### Link a passkey to an existing Safe
183
+
184
+ ```ts
185
+ import { linkPasskeyToSafe } from '@gasfree-kit/evm-4337';
186
+
187
+ const passkey = await linkPasskeyToSafe(seedPhrase, sponsoredConfig, {
188
+ rpName: 'My App',
189
+ userName: 'user@example.com',
190
+ storage: 'not_persist',
191
+ });
192
+
193
+ console.log(passkey.safeAddress);
194
+ console.log(passkey.credential.signerAddress);
195
+ ```
196
+
197
+ ### Check whether a passkey is linked
198
+
199
+ ```ts
200
+ import { isPasskeyLinked } from '@gasfree-kit/evm-4337';
201
+
202
+ const status = await isPasskeyLinked(seedPhrase, sponsoredConfig);
203
+
204
+ if (status.linked) {
205
+ console.log(status.signerAddress);
206
+ }
207
+ ```
208
+
209
+ ### Send a transfer with a passkey
210
+
211
+ ```ts
212
+ import { PasskeyTransfer } from '@gasfree-kit/evm-4337';
213
+ import { EVM_CHAINS } from '@gasfree-kit/core';
214
+
215
+ const tx = await PasskeyTransfer.sendToken(
216
+ passkey.credential,
217
+ sponsoredConfig,
218
+ EVM_CHAINS.base.usdtAddress,
219
+ '10.00',
220
+ '0x1111111111111111111111111111111111111111',
221
+ );
222
+ ```
223
+
224
+ ### Remove a passkey owner
225
+
226
+ ```ts
227
+ import { unlinkPasskeyFromSafe } from '@gasfree-kit/evm-4337';
228
+
229
+ await unlinkPasskeyFromSafe(seedPhrase, sponsoredConfig, passkey.credential);
230
+ ```
231
+
232
+ ## Main Exports
233
+
234
+ | Export | What it does |
235
+ | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
236
+ | `setupErc4337Wallet` | Creates a WDK-backed ERC-4337 wallet and resolves the Safe address |
237
+ | `EvmTransfer` | Estimates, checks balance, sends single transfers, and sends batch transfers |
238
+ | `PasskeyTransfer` | Sends passkey-signed transfers after a passkey has been linked |
239
+ | `linkPasskeyToSafe` | Registers a passkey and adds its signer to the Safe owner set |
240
+ | `isPasskeyLinked` | Checks whether a stored passkey is still an owner on-chain |
241
+ | `unlinkPasskeyFromSafe` | Removes a passkey signer from the Safe |
242
+ | `getErc4337ConfigForChain` | Normalizes the public client config into runtime config |
243
+ | `GAS_FEE_FALLBACKS` and `GAS_FEE_ESTIMATES` | Exposes fallback fee heuristics per chain |
244
+ | `toUsdtBaseUnitsEvm`, `formatTokenBalance`, `feeToUsdt`, `encodeErc20Transfer` | Utility helpers for amounts and calldata |
245
+
246
+ ## Export Example
247
+
248
+ ```ts
249
+ import {
250
+ setupErc4337Wallet,
251
+ EvmTransfer,
252
+ PasskeyTransfer,
253
+ linkPasskeyToSafe,
254
+ isPasskeyLinked,
255
+ unlinkPasskeyFromSafe,
256
+ getErc4337ConfigForChain,
257
+ GAS_FEE_FALLBACKS,
258
+ GAS_FEE_ESTIMATES,
259
+ } from '@gasfree-kit/evm-4337';
260
+ ```
261
+
262
+ ## Notes
263
+
264
+ - `sponsorshipPolicyId` is required when `isSponsored` is `true`
265
+ - self-transfers are blocked
266
+ - token-paid mode checks that the USDT balance can cover the transfer and gas reserve
267
+ - passkey linking verifies the signer deployment before adding it as an owner
268
+
269
+ ## License
270
+
271
+ MIT
package/dist/index.d.mts CHANGED
@@ -40,6 +40,54 @@ declare const GAS_FEE_FALLBACKS: Record<string, bigint>;
40
40
  /** Fallback fee estimates as human-readable strings. */
41
41
  declare const GAS_FEE_ESTIMATES: Record<string, string>;
42
42
 
43
+ /** Passkey credential data — app developer must persist this (or use 'persist' storage mode). */
44
+ interface PasskeyCredential {
45
+ /** WebAuthn credential ID (base64url encoded) */
46
+ id: string;
47
+ /** P256 public key coordinates from WebAuthn attestation */
48
+ publicKey: {
49
+ x: bigint;
50
+ y: bigint;
51
+ };
52
+ /** On-chain SafeWebAuthnSigner contract address */
53
+ signerAddress: string;
54
+ /** The Safe smart account this passkey is linked to */
55
+ safeAddress: string;
56
+ }
57
+ /** Storage mode for passkey credentials. */
58
+ type PasskeyStorageMode = 'persist' | 'not_persist';
59
+ /** Result from linking a passkey to a Safe. */
60
+ interface PasskeyLinkResult {
61
+ /** Passkey credential — always returned regardless of storage mode */
62
+ credential: PasskeyCredential;
63
+ /** Safe smart account address */
64
+ safeAddress: string;
65
+ /** Transaction hash of the addOwner operation */
66
+ transactionHash: string;
67
+ /** Whether the SDK persisted the credential internally */
68
+ persisted: boolean;
69
+ }
70
+ /** Options for passkey registration and linking. */
71
+ interface PasskeyOptions {
72
+ /** Relying party ID (defaults to window.location.hostname) */
73
+ rpId?: string;
74
+ /** Relying party display name */
75
+ rpName?: string;
76
+ /** User display name for the passkey credential */
77
+ userName?: string;
78
+ /** Credential storage mode. Default: 'not_persist'. Convenience only; not secure storage. */
79
+ storage?: PasskeyStorageMode;
80
+ }
81
+ /** Config extending EVM4337ClientConfig with passkey-specific options. */
82
+ interface PasskeyConfig extends EVM4337ClientConfig {
83
+ /** WebAuthn and passkey registration options */
84
+ passkeyOptions?: PasskeyOptions;
85
+ }
86
+ /** Pre-deployed SafeWebAuthnSignerFactory addresses per chain. */
87
+ declare const SAFE_WEBAUTHN_SIGNER_FACTORY: Record<string, string>;
88
+ /** Pre-deployed FCLP256Verifier addresses per chain. */
89
+ declare const FCL_P256_VERIFIER: Record<string, string>;
90
+
43
91
  /**
44
92
  * Set up an ERC-4337 wallet via WDK.
45
93
  *
@@ -47,21 +95,17 @@ declare const GAS_FEE_ESTIMATES: Record<string, string>;
47
95
  * correct discriminated-union config:
48
96
  * - Sponsored mode → EvmErc4337WalletSponsorshipPolicyConfig
49
97
  * - Non-sponsored → EvmErc4337WalletPaymasterTokenConfig
98
+ *
99
+ * Optionally links a passkey to the Safe account when passkeyOptions is provided.
50
100
  */
51
- declare function setupErc4337Wallet(seedPhrase: string, config: EVM4337ClientConfig, accountIndex?: number): Promise<{
101
+ declare function setupErc4337Wallet(seedPhrase: string, config: EVM4337ClientConfig, accountIndex?: number, passkeyOptions?: PasskeyOptions): Promise<{
52
102
  wallet: _tetherto_wdk_wallet_evm_erc_4337.default;
53
103
  account: _tetherto_wdk_wallet_evm_erc_4337.EvmAccount;
54
104
  address: string;
105
+ passkey: PasskeyLinkResult | undefined;
55
106
  }>;
56
107
 
57
- declare class TetherEVMERC4337Transfer {
58
- /** Default timeout for UserOp confirmation: 120 seconds. */
59
- private static readonly USER_OP_TIMEOUT_MS;
60
- /**
61
- * Wait for a UserOperation to be included on-chain via the Candide bundler.
62
- * Times out after USER_OP_TIMEOUT_MS to prevent indefinite hangs.
63
- */
64
- private static waitForUserOpConfirmation;
108
+ declare class EvmTransfer {
65
109
  /**
66
110
  * Get transaction fee estimate for a USDT transfer.
67
111
  *
@@ -117,10 +161,6 @@ declare class TetherEVMERC4337Transfer {
117
161
  amount: string;
118
162
  }[];
119
163
  }>;
120
- /**
121
- * Map raw errors into user-friendly messages.
122
- */
123
- static handleTransferError(error: unknown, chain: string): Error;
124
164
  }
125
165
 
126
166
  /**
@@ -143,4 +183,111 @@ declare function encodeErc20Transfer(to: string, amount: bigint): string;
143
183
  */
144
184
  declare function feeToUsdt(feeInWei: bigint, nativeTokenPriceUsdt: number): string;
145
185
 
146
- export { type EVM4337ClientConfig, type EvmErc4337NetworkConfig, GAS_FEE_ESTIMATES, GAS_FEE_FALLBACKS, TetherEVMERC4337Transfer, encodeErc20Transfer, feeToUsdt, formatTokenBalance, getErc4337ConfigForChain, setupErc4337Wallet, toUsdtBaseUnitsEvm };
186
+ /**
187
+ * Wait for a UserOperation to be included on-chain via the Candide bundler.
188
+ * Times out after USER_OP_TIMEOUT_MS to prevent indefinite hangs.
189
+ */
190
+ declare function waitForUserOpConfirmation(userOpHash: string, bundlerUrl: string, entryPointAddress: string): Promise<string>;
191
+ /**
192
+ * Map raw errors into user-friendly messages.
193
+ */
194
+ declare function handleTransferError(error: unknown, chain: string): Error;
195
+
196
+ /**
197
+ * Register a new passkey via WebAuthn and link it to an existing Safe account.
198
+ *
199
+ * 1. Validates the Safe is deployed on-chain
200
+ * 2. Registers a new WebAuthn passkey (triggers biometric prompt)
201
+ * 3. Checks the signer is not already a Safe owner
202
+ * 4. Deploys the SafeWebAuthnSigner contract on-chain via the factory
203
+ * 5. Adds the signer as a Safe owner with threshold 1 (1-of-2)
204
+ *
205
+ * The seed phrase signs both the deploy and addOwner transactions.
206
+ */
207
+ declare function linkPasskeyToSafe(seedPhrase: string, config: EVM4337ClientConfig, passkeyOptions?: PasskeyOptions, accountIndex?: number): Promise<PasskeyLinkResult>;
208
+ /**
209
+ * Check if a Safe account already has a passkey signer linked.
210
+ *
211
+ * Fix #5: properly disposes wallet/account resources.
212
+ */
213
+ declare function isPasskeyLinked(seedPhrase: string, config: EVM4337ClientConfig, accountIndex?: number): Promise<{
214
+ linked: boolean;
215
+ signerAddress?: string;
216
+ }>;
217
+ /**
218
+ * Remove a passkey signer from the Safe.
219
+ * Signs the removeOwner tx with the seed phrase.
220
+ *
221
+ * Fix #1: Queries the actual Safe owner list to determine the correct
222
+ * previous owner in the linked list, instead of hardcoding SENTINEL.
223
+ */
224
+ declare function unlinkPasskeyFromSafe(seedPhrase: string, config: EVM4337ClientConfig, credential: PasskeyCredential, accountIndex?: number): Promise<{
225
+ transactionHash: string;
226
+ }>;
227
+
228
+ /**
229
+ * Passkey-signed ERC-4337 transfers via Safe account.
230
+ *
231
+ * Uses Safe4337Pack from @safe-global/relay-kit to handle the full
232
+ * UserOperation lifecycle: construction, passkey signing (WebAuthn biometric),
233
+ * paymaster integration, and bundler submission.
234
+ *
235
+ * The passkey must first be linked to the Safe via `linkPasskeyToSafe()`.
236
+ */
237
+ declare class PasskeyTransfer {
238
+ /**
239
+ * Send a single token transfer signed by passkey.
240
+ * Triggers biometric authentication via WebAuthn.
241
+ */
242
+ static sendToken(credential: PasskeyCredential, config: EVM4337ClientConfig, tokenAddress: string, amount: string, recipientAddress: string): Promise<{
243
+ success: boolean;
244
+ transactionHash: string;
245
+ chain: _gasfree_kit_core.EvmChain;
246
+ amount: string;
247
+ }>;
248
+ /**
249
+ * Send a batch token transfer to multiple recipients, signed by passkey.
250
+ */
251
+ static sendBatchToken(credential: PasskeyCredential, config: EVM4337ClientConfig, tokenAddress: string, recipients: Array<{
252
+ address: string;
253
+ amount: string;
254
+ }>): Promise<{
255
+ success: boolean;
256
+ transactionHash: string;
257
+ chain: _gasfree_kit_core.EvmChain;
258
+ recipients: {
259
+ address: string;
260
+ amount: string;
261
+ }[];
262
+ }>;
263
+ /**
264
+ * Check token balance for a passkey-linked Safe account.
265
+ * Does NOT require biometric — balance is publicly readable via RPC.
266
+ */
267
+ static checkTokenBalance(credential: PasskeyCredential, config: EVM4337ClientConfig, tokenAddress: string): Promise<{
268
+ message: string;
269
+ success: boolean;
270
+ data: {
271
+ tokenBalance: string;
272
+ decimals: number;
273
+ usdBalance: string;
274
+ };
275
+ }>;
276
+ }
277
+
278
+ /** Interface for passkey credential persistence. */
279
+ interface CredentialStore {
280
+ save(credential: PasskeyCredential): Promise<void>;
281
+ load(safeAddress: string): Promise<PasskeyCredential | null>;
282
+ remove(safeAddress: string): Promise<void>;
283
+ listAll(): Promise<PasskeyCredential[]>;
284
+ }
285
+ /**
286
+ * Auto-detect environment and return the appropriate credential store.
287
+ *
288
+ * Fix #10: Stricter browser detection — checks for navigator.credentials
289
+ * to avoid false positives in SSR/jsdom environments.
290
+ */
291
+ declare function createCredentialStore(): CredentialStore;
292
+
293
+ export { type CredentialStore, type EVM4337ClientConfig, type EvmErc4337NetworkConfig, EvmTransfer, FCL_P256_VERIFIER, GAS_FEE_ESTIMATES, GAS_FEE_FALLBACKS, type PasskeyConfig, type PasskeyCredential, type PasskeyLinkResult, type PasskeyOptions, type PasskeyStorageMode, PasskeyTransfer, SAFE_WEBAUTHN_SIGNER_FACTORY, createCredentialStore, encodeErc20Transfer, feeToUsdt, formatTokenBalance, getErc4337ConfigForChain, handleTransferError, isPasskeyLinked, linkPasskeyToSafe, setupErc4337Wallet, toUsdtBaseUnitsEvm, unlinkPasskeyFromSafe, waitForUserOpConfirmation };
package/dist/index.d.ts CHANGED
@@ -40,6 +40,54 @@ declare const GAS_FEE_FALLBACKS: Record<string, bigint>;
40
40
  /** Fallback fee estimates as human-readable strings. */
41
41
  declare const GAS_FEE_ESTIMATES: Record<string, string>;
42
42
 
43
+ /** Passkey credential data — app developer must persist this (or use 'persist' storage mode). */
44
+ interface PasskeyCredential {
45
+ /** WebAuthn credential ID (base64url encoded) */
46
+ id: string;
47
+ /** P256 public key coordinates from WebAuthn attestation */
48
+ publicKey: {
49
+ x: bigint;
50
+ y: bigint;
51
+ };
52
+ /** On-chain SafeWebAuthnSigner contract address */
53
+ signerAddress: string;
54
+ /** The Safe smart account this passkey is linked to */
55
+ safeAddress: string;
56
+ }
57
+ /** Storage mode for passkey credentials. */
58
+ type PasskeyStorageMode = 'persist' | 'not_persist';
59
+ /** Result from linking a passkey to a Safe. */
60
+ interface PasskeyLinkResult {
61
+ /** Passkey credential — always returned regardless of storage mode */
62
+ credential: PasskeyCredential;
63
+ /** Safe smart account address */
64
+ safeAddress: string;
65
+ /** Transaction hash of the addOwner operation */
66
+ transactionHash: string;
67
+ /** Whether the SDK persisted the credential internally */
68
+ persisted: boolean;
69
+ }
70
+ /** Options for passkey registration and linking. */
71
+ interface PasskeyOptions {
72
+ /** Relying party ID (defaults to window.location.hostname) */
73
+ rpId?: string;
74
+ /** Relying party display name */
75
+ rpName?: string;
76
+ /** User display name for the passkey credential */
77
+ userName?: string;
78
+ /** Credential storage mode. Default: 'not_persist'. Convenience only; not secure storage. */
79
+ storage?: PasskeyStorageMode;
80
+ }
81
+ /** Config extending EVM4337ClientConfig with passkey-specific options. */
82
+ interface PasskeyConfig extends EVM4337ClientConfig {
83
+ /** WebAuthn and passkey registration options */
84
+ passkeyOptions?: PasskeyOptions;
85
+ }
86
+ /** Pre-deployed SafeWebAuthnSignerFactory addresses per chain. */
87
+ declare const SAFE_WEBAUTHN_SIGNER_FACTORY: Record<string, string>;
88
+ /** Pre-deployed FCLP256Verifier addresses per chain. */
89
+ declare const FCL_P256_VERIFIER: Record<string, string>;
90
+
43
91
  /**
44
92
  * Set up an ERC-4337 wallet via WDK.
45
93
  *
@@ -47,21 +95,17 @@ declare const GAS_FEE_ESTIMATES: Record<string, string>;
47
95
  * correct discriminated-union config:
48
96
  * - Sponsored mode → EvmErc4337WalletSponsorshipPolicyConfig
49
97
  * - Non-sponsored → EvmErc4337WalletPaymasterTokenConfig
98
+ *
99
+ * Optionally links a passkey to the Safe account when passkeyOptions is provided.
50
100
  */
51
- declare function setupErc4337Wallet(seedPhrase: string, config: EVM4337ClientConfig, accountIndex?: number): Promise<{
101
+ declare function setupErc4337Wallet(seedPhrase: string, config: EVM4337ClientConfig, accountIndex?: number, passkeyOptions?: PasskeyOptions): Promise<{
52
102
  wallet: _tetherto_wdk_wallet_evm_erc_4337.default;
53
103
  account: _tetherto_wdk_wallet_evm_erc_4337.EvmAccount;
54
104
  address: string;
105
+ passkey: PasskeyLinkResult | undefined;
55
106
  }>;
56
107
 
57
- declare class TetherEVMERC4337Transfer {
58
- /** Default timeout for UserOp confirmation: 120 seconds. */
59
- private static readonly USER_OP_TIMEOUT_MS;
60
- /**
61
- * Wait for a UserOperation to be included on-chain via the Candide bundler.
62
- * Times out after USER_OP_TIMEOUT_MS to prevent indefinite hangs.
63
- */
64
- private static waitForUserOpConfirmation;
108
+ declare class EvmTransfer {
65
109
  /**
66
110
  * Get transaction fee estimate for a USDT transfer.
67
111
  *
@@ -117,10 +161,6 @@ declare class TetherEVMERC4337Transfer {
117
161
  amount: string;
118
162
  }[];
119
163
  }>;
120
- /**
121
- * Map raw errors into user-friendly messages.
122
- */
123
- static handleTransferError(error: unknown, chain: string): Error;
124
164
  }
125
165
 
126
166
  /**
@@ -143,4 +183,111 @@ declare function encodeErc20Transfer(to: string, amount: bigint): string;
143
183
  */
144
184
  declare function feeToUsdt(feeInWei: bigint, nativeTokenPriceUsdt: number): string;
145
185
 
146
- export { type EVM4337ClientConfig, type EvmErc4337NetworkConfig, GAS_FEE_ESTIMATES, GAS_FEE_FALLBACKS, TetherEVMERC4337Transfer, encodeErc20Transfer, feeToUsdt, formatTokenBalance, getErc4337ConfigForChain, setupErc4337Wallet, toUsdtBaseUnitsEvm };
186
+ /**
187
+ * Wait for a UserOperation to be included on-chain via the Candide bundler.
188
+ * Times out after USER_OP_TIMEOUT_MS to prevent indefinite hangs.
189
+ */
190
+ declare function waitForUserOpConfirmation(userOpHash: string, bundlerUrl: string, entryPointAddress: string): Promise<string>;
191
+ /**
192
+ * Map raw errors into user-friendly messages.
193
+ */
194
+ declare function handleTransferError(error: unknown, chain: string): Error;
195
+
196
+ /**
197
+ * Register a new passkey via WebAuthn and link it to an existing Safe account.
198
+ *
199
+ * 1. Validates the Safe is deployed on-chain
200
+ * 2. Registers a new WebAuthn passkey (triggers biometric prompt)
201
+ * 3. Checks the signer is not already a Safe owner
202
+ * 4. Deploys the SafeWebAuthnSigner contract on-chain via the factory
203
+ * 5. Adds the signer as a Safe owner with threshold 1 (1-of-2)
204
+ *
205
+ * The seed phrase signs both the deploy and addOwner transactions.
206
+ */
207
+ declare function linkPasskeyToSafe(seedPhrase: string, config: EVM4337ClientConfig, passkeyOptions?: PasskeyOptions, accountIndex?: number): Promise<PasskeyLinkResult>;
208
+ /**
209
+ * Check if a Safe account already has a passkey signer linked.
210
+ *
211
+ * Fix #5: properly disposes wallet/account resources.
212
+ */
213
+ declare function isPasskeyLinked(seedPhrase: string, config: EVM4337ClientConfig, accountIndex?: number): Promise<{
214
+ linked: boolean;
215
+ signerAddress?: string;
216
+ }>;
217
+ /**
218
+ * Remove a passkey signer from the Safe.
219
+ * Signs the removeOwner tx with the seed phrase.
220
+ *
221
+ * Fix #1: Queries the actual Safe owner list to determine the correct
222
+ * previous owner in the linked list, instead of hardcoding SENTINEL.
223
+ */
224
+ declare function unlinkPasskeyFromSafe(seedPhrase: string, config: EVM4337ClientConfig, credential: PasskeyCredential, accountIndex?: number): Promise<{
225
+ transactionHash: string;
226
+ }>;
227
+
228
+ /**
229
+ * Passkey-signed ERC-4337 transfers via Safe account.
230
+ *
231
+ * Uses Safe4337Pack from @safe-global/relay-kit to handle the full
232
+ * UserOperation lifecycle: construction, passkey signing (WebAuthn biometric),
233
+ * paymaster integration, and bundler submission.
234
+ *
235
+ * The passkey must first be linked to the Safe via `linkPasskeyToSafe()`.
236
+ */
237
+ declare class PasskeyTransfer {
238
+ /**
239
+ * Send a single token transfer signed by passkey.
240
+ * Triggers biometric authentication via WebAuthn.
241
+ */
242
+ static sendToken(credential: PasskeyCredential, config: EVM4337ClientConfig, tokenAddress: string, amount: string, recipientAddress: string): Promise<{
243
+ success: boolean;
244
+ transactionHash: string;
245
+ chain: _gasfree_kit_core.EvmChain;
246
+ amount: string;
247
+ }>;
248
+ /**
249
+ * Send a batch token transfer to multiple recipients, signed by passkey.
250
+ */
251
+ static sendBatchToken(credential: PasskeyCredential, config: EVM4337ClientConfig, tokenAddress: string, recipients: Array<{
252
+ address: string;
253
+ amount: string;
254
+ }>): Promise<{
255
+ success: boolean;
256
+ transactionHash: string;
257
+ chain: _gasfree_kit_core.EvmChain;
258
+ recipients: {
259
+ address: string;
260
+ amount: string;
261
+ }[];
262
+ }>;
263
+ /**
264
+ * Check token balance for a passkey-linked Safe account.
265
+ * Does NOT require biometric — balance is publicly readable via RPC.
266
+ */
267
+ static checkTokenBalance(credential: PasskeyCredential, config: EVM4337ClientConfig, tokenAddress: string): Promise<{
268
+ message: string;
269
+ success: boolean;
270
+ data: {
271
+ tokenBalance: string;
272
+ decimals: number;
273
+ usdBalance: string;
274
+ };
275
+ }>;
276
+ }
277
+
278
+ /** Interface for passkey credential persistence. */
279
+ interface CredentialStore {
280
+ save(credential: PasskeyCredential): Promise<void>;
281
+ load(safeAddress: string): Promise<PasskeyCredential | null>;
282
+ remove(safeAddress: string): Promise<void>;
283
+ listAll(): Promise<PasskeyCredential[]>;
284
+ }
285
+ /**
286
+ * Auto-detect environment and return the appropriate credential store.
287
+ *
288
+ * Fix #10: Stricter browser detection — checks for navigator.credentials
289
+ * to avoid false positives in SSR/jsdom environments.
290
+ */
291
+ declare function createCredentialStore(): CredentialStore;
292
+
293
+ export { type CredentialStore, type EVM4337ClientConfig, type EvmErc4337NetworkConfig, EvmTransfer, FCL_P256_VERIFIER, GAS_FEE_ESTIMATES, GAS_FEE_FALLBACKS, type PasskeyConfig, type PasskeyCredential, type PasskeyLinkResult, type PasskeyOptions, type PasskeyStorageMode, PasskeyTransfer, SAFE_WEBAUTHN_SIGNER_FACTORY, createCredentialStore, encodeErc20Transfer, feeToUsdt, formatTokenBalance, getErc4337ConfigForChain, handleTransferError, isPasskeyLinked, linkPasskeyToSafe, setupErc4337Wallet, toUsdtBaseUnitsEvm, unlinkPasskeyFromSafe, waitForUserOpConfirmation };