@swimmingkiim/pay-sdk 0.1.21 → 0.1.23
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/PAYMASTER_USAGE.md +16 -0
- package/dist/account/smart-account.d.ts +1 -0
- package/dist/account/smart-account.d.ts.map +1 -1
- package/dist/account/smart-account.js +68 -2
- package/dist/account/smart-account.js.map +1 -1
- package/dist/paymaster/paymaster.d.ts +2 -0
- package/dist/paymaster/paymaster.d.ts.map +1 -1
- package/dist/paymaster/paymaster.js +5 -0
- package/dist/paymaster/paymaster.js.map +1 -1
- package/package.json +1 -1
- package/src/account/smart-account.ts +84 -2
- package/src/paymaster/paymaster.ts +6 -0
- package/test/auto-deposit.test.ts +96 -0
- package/test/smart-account.test.ts +7 -1
- package/tsconfig.tsbuildinfo +1 -1
package/PAYMASTER_USAGE.md
CHANGED
|
@@ -98,9 +98,25 @@ const txHash = await smartAccount.executeBatch([
|
|
|
98
98
|
}
|
|
99
99
|
]);
|
|
100
100
|
|
|
101
|
+
|
|
101
102
|
console.log("Transaction Hash:", txHash);
|
|
102
103
|
```
|
|
103
104
|
|
|
105
|
+
### 5. Auto-Deposit Feature
|
|
106
|
+
|
|
107
|
+
The SDK includes a built-in **Auto-Deposit** mechanism to ensure smooth execution even for new accounts.
|
|
108
|
+
|
|
109
|
+
#### How it works:
|
|
110
|
+
1. **Check**: Before sending a UserOperation, the SDK checks if the Smart Account has enough USDC to cover the Paymaster fee (default fee or custom amount).
|
|
111
|
+
2. **Deposit**: If funds are insufficient, it automatically triggers a standard ETH transaction from the signer's EOA (Externally Owned Account) to the Smart Account to transfer the missing USDC.
|
|
112
|
+
3. **Execute**: Once the deposit transaction is confirmed on-chain, it proceeds with the Paymaster sponsored transaction.
|
|
113
|
+
|
|
114
|
+
#### ⚠️ Important Requirements:
|
|
115
|
+
* **Signer EOA Funds**: Your private key's wallet (EOA) MUST have:
|
|
116
|
+
* **ETH**: To pay gas for the standard deposit transaction (this step is NOT sponsored).
|
|
117
|
+
* **USDC**: Sufficient balance to transfer to the Smart Account.
|
|
118
|
+
* **Permissions**: The EOA must be an owner of the Smart Account (handled automatically during creation).
|
|
119
|
+
|
|
104
120
|
## 🛡️ Error Handling
|
|
105
121
|
|
|
106
122
|
If the Paymaster service is unavailable or rejects the request (e.g., due to rate limiting or insufficient funds), the SDK may throw an error. It is good practice to wrap your execution logic in a try-catch block.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smart-account.d.ts","sourceRoot":"","sources":["../../src/account/smart-account.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAQ,MAAM,MAAM,CAAA;AAIzH,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAE5D,qBAAa,mBAAmB;IAKxB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,SAAS,CAAC;IAPf,MAAM,EAAE,GAAG,CAAA;IACX,OAAO,EAAE,GAAG,CAAA;gBAGP,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,EAC/C,YAAY,EAAE,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,EAC5C,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,gBAAgB,YAAA;IAGlC,iBAAiB,CAAC,SAAS,GAAE,MAAW;
|
|
1
|
+
{"version":3,"file":"smart-account.d.ts","sourceRoot":"","sources":["../../src/account/smart-account.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAQ,MAAM,MAAM,CAAA;AAIzH,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAE5D,qBAAa,mBAAmB;IAKxB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,SAAS,CAAC;IAPf,MAAM,EAAE,GAAG,CAAA;IACX,OAAO,EAAE,GAAG,CAAA;gBAGP,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,EAC/C,YAAY,EAAE,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,EAC5C,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,gBAAgB,YAAA;IAGlC,iBAAiB,CAAC,SAAS,GAAE,MAAW;IAmC9C,UAAU,IAAI,OAAO;IAIf,YAAY,CAAC,KAAK,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,KAAK,MAAM,EAAE,CAAA;KAAE,EAAE;IAgBzE,cAAc,CAAC,cAAc,EAAE,MAAM;CAqE9C"}
|
|
@@ -27,14 +27,20 @@ export class SmartAccountManager {
|
|
|
27
27
|
},
|
|
28
28
|
saltNonce,
|
|
29
29
|
});
|
|
30
|
+
const apiKey = this.paymaster?.getApiKey();
|
|
31
|
+
const fetchOptions = apiKey ? { headers: { 'x-api-key': apiKey } } : undefined;
|
|
30
32
|
this.client = createSmartAccountClient({
|
|
31
33
|
account: this.account,
|
|
32
34
|
chain: this.signer.chain,
|
|
33
|
-
bundlerTransport: http(this.rpcUrl),
|
|
35
|
+
bundlerTransport: http(this.rpcUrl, { fetchOptions }),
|
|
34
36
|
paymaster: this.paymaster ? this.paymaster.getClient() : undefined,
|
|
35
37
|
userOperation: {
|
|
36
38
|
estimateFeesPerGas: async () => {
|
|
37
|
-
|
|
39
|
+
const fees = await this.publicClient.estimateFeesPerGas();
|
|
40
|
+
return {
|
|
41
|
+
maxFeePerGas: (fees.maxFeePerGas * 20n) / 10n, // 2x (safety margin)
|
|
42
|
+
maxPriorityFeePerGas: (fees.maxPriorityFeePerGas * 20n) / 10n
|
|
43
|
+
};
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
}).extend(erc7579Actions());
|
|
@@ -46,11 +52,71 @@ export class SmartAccountManager {
|
|
|
46
52
|
async executeBatch(calls) {
|
|
47
53
|
if (!this.client)
|
|
48
54
|
throw new Error("Account not initialized");
|
|
55
|
+
// [Fee Logic] Ensure Smart Account has enough USDC for the fee
|
|
56
|
+
// We assume 0.6 USDC is required per transaction (matching Paymaster Policy)
|
|
57
|
+
await this.ensureGasFunds(600000n);
|
|
49
58
|
const txHash = await this.client.sendTransaction({
|
|
50
59
|
calls: calls,
|
|
51
60
|
account: this.account
|
|
52
61
|
});
|
|
53
62
|
return txHash;
|
|
54
63
|
}
|
|
64
|
+
// New Helper: Check and Deposit Funds if needed
|
|
65
|
+
async ensureGasFunds(requiredAmount) {
|
|
66
|
+
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
67
|
+
const ERC20_ABI = [{
|
|
68
|
+
name: 'balanceOf',
|
|
69
|
+
type: 'function',
|
|
70
|
+
stateMutability: 'view',
|
|
71
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
72
|
+
outputs: [{ name: '', type: 'uint256' }]
|
|
73
|
+
}, {
|
|
74
|
+
name: 'transfer',
|
|
75
|
+
type: 'function',
|
|
76
|
+
stateMutability: 'nonpayable',
|
|
77
|
+
inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
|
|
78
|
+
outputs: [{ name: '', type: 'bool' }]
|
|
79
|
+
}];
|
|
80
|
+
console.log(`[SmartAccount] Checking Gas Funds (Required: ${requiredAmount})...`);
|
|
81
|
+
// 1. Check Smart Account Balance
|
|
82
|
+
const saBalance = await this.publicClient.readContract({
|
|
83
|
+
address: USDC_ADDRESS,
|
|
84
|
+
abi: ERC20_ABI,
|
|
85
|
+
functionName: 'balanceOf',
|
|
86
|
+
args: [this.account.address]
|
|
87
|
+
});
|
|
88
|
+
console.log(`[SmartAccount] Current Balance: ${saBalance}`);
|
|
89
|
+
if (saBalance >= requiredAmount) {
|
|
90
|
+
console.log(`[SmartAccount] ✅ Sufficient funds.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const shortage = requiredAmount - saBalance;
|
|
94
|
+
console.log(`[SmartAccount] ⚠️ Insufficient funds. Shortage: ${shortage}`);
|
|
95
|
+
// 2. Check EOA Balance
|
|
96
|
+
const eoaAddress = this.signer.account.address;
|
|
97
|
+
const eoaBalance = await this.publicClient.readContract({
|
|
98
|
+
address: USDC_ADDRESS,
|
|
99
|
+
abi: ERC20_ABI,
|
|
100
|
+
functionName: 'balanceOf',
|
|
101
|
+
args: [eoaAddress]
|
|
102
|
+
});
|
|
103
|
+
console.log(`[SmartAccount] EOA Balance: ${eoaBalance}`);
|
|
104
|
+
if (eoaBalance < shortage) {
|
|
105
|
+
throw new Error(`Insufficient funds in both Smart Account (${saBalance}) and EOA (${eoaBalance}). Required: ${requiredAmount}`);
|
|
106
|
+
}
|
|
107
|
+
// 3. Deposit from EOA
|
|
108
|
+
console.log(`[SmartAccount] 🔄 Auto-depositing ${shortage} USDC from EOA...`);
|
|
109
|
+
const hash = await this.signer.writeContract({
|
|
110
|
+
address: USDC_ADDRESS,
|
|
111
|
+
abi: ERC20_ABI,
|
|
112
|
+
functionName: 'transfer',
|
|
113
|
+
args: [this.account.address, shortage],
|
|
114
|
+
chain: this.signer.chain,
|
|
115
|
+
account: this.signer.account
|
|
116
|
+
});
|
|
117
|
+
console.log(`[SmartAccount] Deposit Tx Sent: ${hash}. Waiting for confirmation...`);
|
|
118
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
119
|
+
console.log(`[SmartAccount] ✅ Deposit confirmed. Proceeding with UserOp.`);
|
|
120
|
+
}
|
|
55
121
|
}
|
|
56
122
|
//# sourceMappingURL=smart-account.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smart-account.js","sourceRoot":"","sources":["../../src/account/smart-account.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAEzD,OAAO,EAAgG,IAAI,EAAE,MAAM,MAAM,CAAA;AACzH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAI/D,MAAM,OAAO,mBAAmB;IAKhB;IACA;IACA;IACA;IAPL,MAAM,CAAK;IACX,OAAO,CAAK;IAEnB,YACY,MAA+C,EAC/C,YAA4C,EAC5C,MAAc,EACd,SAA4B;QAH5B,WAAM,GAAN,MAAM,CAAyC;QAC/C,iBAAY,GAAZ,YAAY,CAAgC;QAC5C,WAAM,GAAN,MAAM,CAAQ;QACd,cAAS,GAAT,SAAS,CAAmB;IACpC,CAAC;IAEL,KAAK,CAAC,iBAAiB,CAAC,YAAoB,EAAE;QAC1C,sCAAsC;QACtC,IAAI,CAAC,OAAO,GAAG,MAAM,kBAAkB,CAAC;YACpC,MAAM,EAAE,IAAI,CAAC,YAAY;YACzB,MAAM,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAC7B,OAAO,EAAE,OAAO;YAChB,UAAU,EAAE;gBACR,OAAO,EAAE,4CAA4C,EAAE,iBAAiB;gBACxE,OAAO,EAAE,KAAK;aACjB;YACD,SAAS;SACZ,CAAC,CAAA;QAEF,IAAI,CAAC,MAAM,GAAG,wBAAwB,CAAC;YACnC,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;YACxB,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"smart-account.js","sourceRoot":"","sources":["../../src/account/smart-account.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export declare class PaymasterManager {
|
|
2
2
|
private client;
|
|
3
|
+
private apiKey?;
|
|
3
4
|
constructor(rpcUrl?: string, apiKey?: string);
|
|
4
5
|
getClient(): any;
|
|
6
|
+
getApiKey(): string | undefined;
|
|
5
7
|
getStubPaymasterData(userOp: any): Promise<any>;
|
|
6
8
|
/**
|
|
7
9
|
* Appends a fee transfer transaction to the list of calls.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paymaster.d.ts","sourceRoot":"","sources":["../../src/paymaster/paymaster.ts"],"names":[],"mappings":"AAMA,qBAAa,gBAAgB;IACzB,OAAO,CAAC,MAAM,CAAK;
|
|
1
|
+
{"version":3,"file":"paymaster.d.ts","sourceRoot":"","sources":["../../src/paymaster/paymaster.ts"],"names":[],"mappings":"AAMA,qBAAa,gBAAgB;IACzB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAC,CAAQ;gBAEX,MAAM,GAAE,MAA6C,EAAE,MAAM,CAAC,EAAE,MAAM;IAalF,SAAS;IAIT,SAAS;IAIH,oBAAoB,CAAC,MAAM,EAAE,GAAG;IAMtC;;;;;OAKG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;CAwBxG"}
|
|
@@ -3,7 +3,9 @@ import { http, encodeFunctionData, parseAbi } from "viem";
|
|
|
3
3
|
const ERC20_ABI = parseAbi(['function transfer(address to, uint256 amount) returns (bool)']);
|
|
4
4
|
export class PaymasterManager {
|
|
5
5
|
client;
|
|
6
|
+
apiKey;
|
|
6
7
|
constructor(rpcUrl = "http://localhost:8080/v1/paymaster", apiKey) {
|
|
8
|
+
this.apiKey = apiKey;
|
|
7
9
|
const fetchOptions = apiKey ? { headers: { 'x-api-key': apiKey } } : undefined;
|
|
8
10
|
this.client = createPimlicoClient({
|
|
9
11
|
transport: http(rpcUrl, { fetchOptions }),
|
|
@@ -16,6 +18,9 @@ export class PaymasterManager {
|
|
|
16
18
|
getClient() {
|
|
17
19
|
return this.client;
|
|
18
20
|
}
|
|
21
|
+
getApiKey() {
|
|
22
|
+
return this.apiKey;
|
|
23
|
+
}
|
|
19
24
|
async getStubPaymasterData(userOp) {
|
|
20
25
|
return this.client.sponsorUserOperation({
|
|
21
26
|
userOperation: userOp
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paymaster.js","sourceRoot":"","sources":["../../src/paymaster/paymaster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAO,MAAM,MAAM,CAAA;AAE9D,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,8DAA8D,CAAC,CAAC,CAAC;AAG7F,MAAM,OAAO,gBAAgB;IACjB,MAAM,CAAK;
|
|
1
|
+
{"version":3,"file":"paymaster.js","sourceRoot":"","sources":["../../src/paymaster/paymaster.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,QAAQ,EAAO,MAAM,MAAM,CAAA;AAE9D,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,8DAA8D,CAAC,CAAC,CAAC;AAG7F,MAAM,OAAO,gBAAgB;IACjB,MAAM,CAAK;IACX,MAAM,CAAS;IAEvB,YAAY,SAAiB,oCAAoC,EAAE,MAAe;QAC9E,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAE/E,IAAI,CAAC,MAAM,GAAG,mBAAmB,CAAC;YAC9B,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC;YACzC,UAAU,EAAE;gBACR,OAAO,EAAE,4CAA4C;gBACrD,OAAO,EAAE,KAAK;aACjB;SACJ,CAAC,CAAA;IACN,CAAC;IAED,SAAS;QACL,OAAO,IAAI,CAAC,MAAM,CAAA;IACtB,CAAC;IAED,SAAS;QACL,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,MAAW;QAClC,OAAO,IAAI,CAAC,MAAM,CAAC,oBAAoB,CAAC;YACpC,aAAa,EAAE,MAAM;SACxB,CAAC,CAAA;IACN,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,gBAAgB,CAAC,KAAY,EAAE,SAA+D;QACjG,yCAAyC;QACzC,MAAM,MAAM,GAAG;YACX,QAAQ,EAAE,SAAS,EAAE,QAAQ,IAAI,4CAA4C,EAAE,sBAAsB;YACrG,MAAM,EAAE,SAAS,EAAE,MAAM,IAAI,OAAO,EAAE,WAAW;YACjD,KAAK,EAAE,SAAS,EAAE,KAAK,IAAI,4CAA4C,CAAC,eAAe;SAC1F,CAAC;QAEF,IAAI,MAAM,CAAC,QAAQ,KAAK,4CAA4C,EAAE,CAAC;YACnE,OAAO,CAAC,IAAI,CAAC,uFAAuF,CAAC,CAAC;QAC1G,CAAC;QAED,MAAM,OAAO,GAAG;YACZ,EAAE,EAAE,MAAM,CAAC,KAAY;YACvB,KAAK,EAAE,EAAE;YACT,IAAI,EAAE,kBAAkB,CAAC;gBACrB,GAAG,EAAE,SAAS;gBACd,YAAY,EAAE,UAAU;gBACxB,IAAI,EAAE,CAAC,MAAM,CAAC,QAAe,EAAE,MAAM,CAAC,MAAM,CAAC;aAChD,CAAC;SACL,CAAC;QAEF,OAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC;IAC/B,CAAC;CACJ"}
|
package/package.json
CHANGED
|
@@ -31,14 +31,21 @@ export class SmartAccountManager {
|
|
|
31
31
|
saltNonce,
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
+
const apiKey = this.paymaster?.getApiKey();
|
|
35
|
+
const fetchOptions = apiKey ? { headers: { 'x-api-key': apiKey } } : undefined;
|
|
36
|
+
|
|
34
37
|
this.client = createSmartAccountClient({
|
|
35
38
|
account: this.account,
|
|
36
39
|
chain: this.signer.chain,
|
|
37
|
-
bundlerTransport: http(this.rpcUrl),
|
|
40
|
+
bundlerTransport: http(this.rpcUrl, { fetchOptions }),
|
|
38
41
|
paymaster: this.paymaster ? this.paymaster.getClient() : undefined,
|
|
39
42
|
userOperation: {
|
|
40
43
|
estimateFeesPerGas: async () => {
|
|
41
|
-
|
|
44
|
+
const fees = await this.publicClient.estimateFeesPerGas();
|
|
45
|
+
return {
|
|
46
|
+
maxFeePerGas: (fees.maxFeePerGas * 20n) / 10n, // 2x (safety margin)
|
|
47
|
+
maxPriorityFeePerGas: (fees.maxPriorityFeePerGas * 20n) / 10n
|
|
48
|
+
} as any;
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
}).extend(erc7579Actions())
|
|
@@ -53,6 +60,10 @@ export class SmartAccountManager {
|
|
|
53
60
|
async executeBatch(calls: { to: Address, value: bigint, data: `0x${string}` }[]) {
|
|
54
61
|
if (!this.client) throw new Error("Account not initialized")
|
|
55
62
|
|
|
63
|
+
// [Fee Logic] Ensure Smart Account has enough USDC for the fee
|
|
64
|
+
// We assume 0.6 USDC is required per transaction (matching Paymaster Policy)
|
|
65
|
+
await this.ensureGasFunds(600000n);
|
|
66
|
+
|
|
56
67
|
const txHash = await this.client.sendTransaction({
|
|
57
68
|
calls: calls,
|
|
58
69
|
account: this.account
|
|
@@ -60,4 +71,75 @@ export class SmartAccountManager {
|
|
|
60
71
|
|
|
61
72
|
return txHash
|
|
62
73
|
}
|
|
74
|
+
|
|
75
|
+
// New Helper: Check and Deposit Funds if needed
|
|
76
|
+
async ensureGasFunds(requiredAmount: bigint) {
|
|
77
|
+
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
78
|
+
const ERC20_ABI = [{
|
|
79
|
+
name: 'balanceOf',
|
|
80
|
+
type: 'function',
|
|
81
|
+
stateMutability: 'view',
|
|
82
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
83
|
+
outputs: [{ name: '', type: 'uint256' }]
|
|
84
|
+
}, {
|
|
85
|
+
name: 'transfer',
|
|
86
|
+
type: 'function',
|
|
87
|
+
stateMutability: 'nonpayable',
|
|
88
|
+
inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
|
|
89
|
+
outputs: [{ name: '', type: 'bool' }]
|
|
90
|
+
}] as const;
|
|
91
|
+
|
|
92
|
+
console.log(`[SmartAccount] Checking Gas Funds (Required: ${requiredAmount})...`);
|
|
93
|
+
|
|
94
|
+
// 1. Check Smart Account Balance
|
|
95
|
+
const saBalance = await this.publicClient.readContract({
|
|
96
|
+
address: USDC_ADDRESS,
|
|
97
|
+
abi: ERC20_ABI,
|
|
98
|
+
functionName: 'balanceOf',
|
|
99
|
+
args: [this.account.address]
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log(`[SmartAccount] Current Balance: ${saBalance}`);
|
|
103
|
+
|
|
104
|
+
if (saBalance >= requiredAmount) {
|
|
105
|
+
console.log(`[SmartAccount] ✅ Sufficient funds.`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const shortage = requiredAmount - saBalance;
|
|
110
|
+
console.log(`[SmartAccount] ⚠️ Insufficient funds. Shortage: ${shortage}`);
|
|
111
|
+
|
|
112
|
+
// 2. Check EOA Balance
|
|
113
|
+
const eoaAddress = this.signer.account.address;
|
|
114
|
+
const eoaBalance = await this.publicClient.readContract({
|
|
115
|
+
address: USDC_ADDRESS,
|
|
116
|
+
abi: ERC20_ABI,
|
|
117
|
+
functionName: 'balanceOf',
|
|
118
|
+
args: [eoaAddress]
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log(`[SmartAccount] EOA Balance: ${eoaBalance}`);
|
|
122
|
+
|
|
123
|
+
if (eoaBalance < shortage) {
|
|
124
|
+
throw new Error(`Insufficient funds in both Smart Account (${saBalance}) and EOA (${eoaBalance}). Required: ${requiredAmount}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 3. Deposit from EOA
|
|
128
|
+
console.log(`[SmartAccount] 🔄 Auto-depositing ${shortage} USDC from EOA...`);
|
|
129
|
+
|
|
130
|
+
const hash = await this.signer.writeContract({
|
|
131
|
+
address: USDC_ADDRESS,
|
|
132
|
+
abi: ERC20_ABI,
|
|
133
|
+
functionName: 'transfer',
|
|
134
|
+
args: [this.account.address, shortage],
|
|
135
|
+
chain: this.signer.chain,
|
|
136
|
+
account: this.signer.account
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
console.log(`[SmartAccount] Deposit Tx Sent: ${hash}. Waiting for confirmation...`);
|
|
140
|
+
|
|
141
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
142
|
+
|
|
143
|
+
console.log(`[SmartAccount] ✅ Deposit confirmed. Proceeding with UserOp.`);
|
|
144
|
+
}
|
|
63
145
|
}
|
|
@@ -6,8 +6,10 @@ const ERC20_ABI = parseAbi(['function transfer(address to, uint256 amount) retur
|
|
|
6
6
|
|
|
7
7
|
export class PaymasterManager {
|
|
8
8
|
private client: any
|
|
9
|
+
private apiKey?: string
|
|
9
10
|
|
|
10
11
|
constructor(rpcUrl: string = "http://localhost:8080/v1/paymaster", apiKey?: string) {
|
|
12
|
+
this.apiKey = apiKey;
|
|
11
13
|
const fetchOptions = apiKey ? { headers: { 'x-api-key': apiKey } } : undefined;
|
|
12
14
|
|
|
13
15
|
this.client = createPimlicoClient({
|
|
@@ -23,6 +25,10 @@ export class PaymasterManager {
|
|
|
23
25
|
return this.client
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
getApiKey() {
|
|
29
|
+
return this.apiKey;
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
async getStubPaymasterData(userOp: any) {
|
|
27
33
|
return this.client.sponsorUserOperation({
|
|
28
34
|
userOperation: userOp
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { SmartAccountManager } from '../src/account/smart-account.js'
|
|
3
|
+
import { privateKeyToAccount } from 'viem/accounts'
|
|
4
|
+
import { baseSepolia } from 'viem/chains'
|
|
5
|
+
|
|
6
|
+
describe('SmartAccountManager: Auto Deposit', () => {
|
|
7
|
+
let sa: SmartAccountManager;
|
|
8
|
+
let mockPublicClient: any;
|
|
9
|
+
let mockWalletClient: any;
|
|
10
|
+
let mockSigner: any;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mockSigner = privateKeyToAccount('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80');
|
|
14
|
+
|
|
15
|
+
mockWalletClient = {
|
|
16
|
+
account: mockSigner,
|
|
17
|
+
chain: baseSepolia,
|
|
18
|
+
writeContract: vi.fn().mockResolvedValue('0xdepositHash')
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
mockPublicClient = {
|
|
22
|
+
readContract: vi.fn(),
|
|
23
|
+
waitForTransactionReceipt: vi.fn().mockResolvedValue({ status: 'success' }),
|
|
24
|
+
estimateFeesPerGas: vi.fn().mockResolvedValue({ maxFeePerGas: 10n, maxPriorityFeePerGas: 1n })
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
sa = new SmartAccountManager(
|
|
28
|
+
mockWalletClient,
|
|
29
|
+
mockPublicClient,
|
|
30
|
+
'http://localhost:8545'
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Mock initialized account manually since we are not calling createSafeAccount
|
|
34
|
+
sa.account = { address: '0xSmartAccountAddress' };
|
|
35
|
+
// Mock the permissionless client
|
|
36
|
+
sa.client = {
|
|
37
|
+
sendTransaction: vi.fn().mockResolvedValue('0xuserOpHash')
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should proceed if Smart Account has sufficient funds', async () => {
|
|
42
|
+
// Mock Balance: SA = 1 USDC (1000000), Required = 0.1 USDC (100000)
|
|
43
|
+
// First call is checking SA balance
|
|
44
|
+
mockPublicClient.readContract.mockResolvedValueOnce(1000000n);
|
|
45
|
+
|
|
46
|
+
await sa.executeBatch([]);
|
|
47
|
+
|
|
48
|
+
expect(mockPublicClient.readContract).toHaveBeenCalledTimes(1); // Only checked SA balance
|
|
49
|
+
expect(mockWalletClient.writeContract).not.toHaveBeenCalled(); // No deposit
|
|
50
|
+
expect(sa.client.sendTransaction).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should deposit from EOA if Smart Account has insufficient funds', async () => {
|
|
54
|
+
// First call: SA Balance = 0
|
|
55
|
+
mockPublicClient.readContract.mockResolvedValueOnce(0n);
|
|
56
|
+
// Second call: EOA Balance = 1 USDC
|
|
57
|
+
mockPublicClient.readContract.mockResolvedValueOnce(1000000n);
|
|
58
|
+
|
|
59
|
+
await sa.executeBatch([]);
|
|
60
|
+
|
|
61
|
+
expect(mockPublicClient.readContract).toHaveBeenCalledTimes(2); // Checked both
|
|
62
|
+
expect(mockWalletClient.writeContract).toHaveBeenCalledWith(expect.objectContaining({
|
|
63
|
+
functionName: 'transfer',
|
|
64
|
+
args: ['0xSmartAccountAddress', 100000n] // Shortage is full amount (100000 - 0)
|
|
65
|
+
}));
|
|
66
|
+
expect(mockPublicClient.waitForTransactionReceipt).toHaveBeenCalled();
|
|
67
|
+
expect(sa.client.sendTransaction).toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should throw if both have insufficient funds', async () => {
|
|
71
|
+
// First call: SA Balance = 0
|
|
72
|
+
mockPublicClient.readContract.mockResolvedValueOnce(0n);
|
|
73
|
+
// Second call: EOA Balance = 0
|
|
74
|
+
mockPublicClient.readContract.mockResolvedValueOnce(0n);
|
|
75
|
+
|
|
76
|
+
await expect(sa.executeBatch([])).rejects.toThrow(/Insufficient funds/);
|
|
77
|
+
|
|
78
|
+
expect(mockWalletClient.writeContract).not.toHaveBeenCalled();
|
|
79
|
+
expect(sa.client.sendTransaction).not.toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should deposit partial shortage if partial funds exist', async () => {
|
|
83
|
+
// Required: 100000
|
|
84
|
+
// SA Balance: 40000
|
|
85
|
+
// Shortage: 60000
|
|
86
|
+
|
|
87
|
+
mockPublicClient.readContract.mockResolvedValueOnce(40000n); // SA
|
|
88
|
+
mockPublicClient.readContract.mockResolvedValueOnce(1000000n); // EOA
|
|
89
|
+
|
|
90
|
+
await sa.executeBatch([]);
|
|
91
|
+
|
|
92
|
+
expect(mockWalletClient.writeContract).toHaveBeenCalledWith(expect.objectContaining({
|
|
93
|
+
args: ['0xSmartAccountAddress', 60000n]
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -58,9 +58,15 @@ describe('a2pay: Smart Accounts & Sessions', () => {
|
|
|
58
58
|
getClient: () => ({ sponsorUserOperation: vi.fn() })
|
|
59
59
|
} as any
|
|
60
60
|
|
|
61
|
+
// Mock Public Client to handle ensureGasFunds check
|
|
62
|
+
const mockPublicClientForTest = {
|
|
63
|
+
readContract: vi.fn().mockResolvedValue(1000000n), // Sufficient balance
|
|
64
|
+
estimateFeesPerGas: vi.fn().mockResolvedValue({ maxFeePerGas: 10n, maxPriorityFeePerGas: 1n })
|
|
65
|
+
} as any
|
|
66
|
+
|
|
61
67
|
const sa = new SmartAccountManager(
|
|
62
68
|
walletClient,
|
|
63
|
-
|
|
69
|
+
mockPublicClientForTest,
|
|
64
70
|
'https://rpc.url',
|
|
65
71
|
mockPaymaster
|
|
66
72
|
)
|