@swimmingkiim/pay-sdk 0.1.21 → 0.1.22
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 +10 -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 +60 -0
- package/dist/account/smart-account.js.map +1 -1
- package/package.json +1 -1
- package/src/account/smart-account.ts +75 -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,19 @@ 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.
|
|
108
|
+
1. **Check**: Before sending, it checks if the Smart Account has enough USDC for the Paymaster fee (default 0.1 USDC).
|
|
109
|
+
2. **Deposit**: If funds are insufficient, it automatically triggers a deposit transaction from the signer's EOA (Externally Owned Account) to the Smart Account.
|
|
110
|
+
3. **Execute**: Once the deposit is confirmed, it proceeds with the Paymaster transaction.
|
|
111
|
+
|
|
112
|
+
**Note**: This requires your EOA to have sufficient USDC and ETH (for the deposit gas) if a top-up is needed.
|
|
113
|
+
|
|
104
114
|
## 🛡️ Error Handling
|
|
105
115
|
|
|
106
116
|
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;IA4B9C,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;
|
|
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;IA4B9C,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"}
|
|
@@ -46,11 +46,71 @@ export class SmartAccountManager {
|
|
|
46
46
|
async executeBatch(calls) {
|
|
47
47
|
if (!this.client)
|
|
48
48
|
throw new Error("Account not initialized");
|
|
49
|
+
// [Fee Logic] Ensure Smart Account has enough USDC for the fee
|
|
50
|
+
// We assume 0.1 USDC is required per transaction for now (as per Paymaster config)
|
|
51
|
+
await this.ensureGasFunds(100000n);
|
|
49
52
|
const txHash = await this.client.sendTransaction({
|
|
50
53
|
calls: calls,
|
|
51
54
|
account: this.account
|
|
52
55
|
});
|
|
53
56
|
return txHash;
|
|
54
57
|
}
|
|
58
|
+
// New Helper: Check and Deposit Funds if needed
|
|
59
|
+
async ensureGasFunds(requiredAmount) {
|
|
60
|
+
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
61
|
+
const ERC20_ABI = [{
|
|
62
|
+
name: 'balanceOf',
|
|
63
|
+
type: 'function',
|
|
64
|
+
stateMutability: 'view',
|
|
65
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
66
|
+
outputs: [{ name: '', type: 'uint256' }]
|
|
67
|
+
}, {
|
|
68
|
+
name: 'transfer',
|
|
69
|
+
type: 'function',
|
|
70
|
+
stateMutability: 'nonpayable',
|
|
71
|
+
inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
|
|
72
|
+
outputs: [{ name: '', type: 'bool' }]
|
|
73
|
+
}];
|
|
74
|
+
console.log(`[SmartAccount] Checking Gas Funds (Required: ${requiredAmount})...`);
|
|
75
|
+
// 1. Check Smart Account Balance
|
|
76
|
+
const saBalance = await this.publicClient.readContract({
|
|
77
|
+
address: USDC_ADDRESS,
|
|
78
|
+
abi: ERC20_ABI,
|
|
79
|
+
functionName: 'balanceOf',
|
|
80
|
+
args: [this.account.address]
|
|
81
|
+
});
|
|
82
|
+
console.log(`[SmartAccount] Current Balance: ${saBalance}`);
|
|
83
|
+
if (saBalance >= requiredAmount) {
|
|
84
|
+
console.log(`[SmartAccount] ✅ Sufficient funds.`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const shortage = requiredAmount - saBalance;
|
|
88
|
+
console.log(`[SmartAccount] ⚠️ Insufficient funds. Shortage: ${shortage}`);
|
|
89
|
+
// 2. Check EOA Balance
|
|
90
|
+
const eoaAddress = this.signer.account.address;
|
|
91
|
+
const eoaBalance = await this.publicClient.readContract({
|
|
92
|
+
address: USDC_ADDRESS,
|
|
93
|
+
abi: ERC20_ABI,
|
|
94
|
+
functionName: 'balanceOf',
|
|
95
|
+
args: [eoaAddress]
|
|
96
|
+
});
|
|
97
|
+
console.log(`[SmartAccount] EOA Balance: ${eoaBalance}`);
|
|
98
|
+
if (eoaBalance < shortage) {
|
|
99
|
+
throw new Error(`Insufficient funds in both Smart Account (${saBalance}) and EOA (${eoaBalance}). Required: ${requiredAmount}`);
|
|
100
|
+
}
|
|
101
|
+
// 3. Deposit from EOA
|
|
102
|
+
console.log(`[SmartAccount] 🔄 Auto-depositing ${shortage} USDC from EOA...`);
|
|
103
|
+
const hash = await this.signer.writeContract({
|
|
104
|
+
address: USDC_ADDRESS,
|
|
105
|
+
abi: ERC20_ABI,
|
|
106
|
+
functionName: 'transfer',
|
|
107
|
+
args: [this.account.address, shortage],
|
|
108
|
+
chain: this.signer.chain,
|
|
109
|
+
account: this.signer.account
|
|
110
|
+
});
|
|
111
|
+
console.log(`[SmartAccount] Deposit Tx Sent: ${hash}. Waiting for confirmation...`);
|
|
112
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
113
|
+
console.log(`[SmartAccount] ✅ Deposit confirmed. Proceeding with UserOp.`);
|
|
114
|
+
}
|
|
55
115
|
}
|
|
56
116
|
//# 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;YACnC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS;YAClE,aAAa,EAAE;gBACX,kBAAkB,EAAE,KAAK,IAAI,EAAE;oBAC3B,OAAO,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,CAAQ,CAAA;gBAChE,CAAC;aACJ;SACJ,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,CAAA;QAE3B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAA;IAC/B,CAAC;IAED,UAAU;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAA;IAC/B,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAA4D;QAC3E,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;QAE5D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC;YAC7C,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,IAAI,CAAC,OAAO;SACxB,CAAC,CAAA;QAEF,OAAO,MAAM,CAAA;IACjB,CAAC;CACJ"}
|
|
1
|
+
{"version":3,"file":"smart-account.js","sourceRoot":"","sources":["../../src/account/smart-account.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -53,6 +53,10 @@ export class SmartAccountManager {
|
|
|
53
53
|
async executeBatch(calls: { to: Address, value: bigint, data: `0x${string}` }[]) {
|
|
54
54
|
if (!this.client) throw new Error("Account not initialized")
|
|
55
55
|
|
|
56
|
+
// [Fee Logic] Ensure Smart Account has enough USDC for the fee
|
|
57
|
+
// We assume 0.1 USDC is required per transaction for now (as per Paymaster config)
|
|
58
|
+
await this.ensureGasFunds(100000n);
|
|
59
|
+
|
|
56
60
|
const txHash = await this.client.sendTransaction({
|
|
57
61
|
calls: calls,
|
|
58
62
|
account: this.account
|
|
@@ -60,4 +64,75 @@ export class SmartAccountManager {
|
|
|
60
64
|
|
|
61
65
|
return txHash
|
|
62
66
|
}
|
|
67
|
+
|
|
68
|
+
// New Helper: Check and Deposit Funds if needed
|
|
69
|
+
async ensureGasFunds(requiredAmount: bigint) {
|
|
70
|
+
const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
|
|
71
|
+
const ERC20_ABI = [{
|
|
72
|
+
name: 'balanceOf',
|
|
73
|
+
type: 'function',
|
|
74
|
+
stateMutability: 'view',
|
|
75
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
76
|
+
outputs: [{ name: '', type: 'uint256' }]
|
|
77
|
+
}, {
|
|
78
|
+
name: 'transfer',
|
|
79
|
+
type: 'function',
|
|
80
|
+
stateMutability: 'nonpayable',
|
|
81
|
+
inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }],
|
|
82
|
+
outputs: [{ name: '', type: 'bool' }]
|
|
83
|
+
}] as const;
|
|
84
|
+
|
|
85
|
+
console.log(`[SmartAccount] Checking Gas Funds (Required: ${requiredAmount})...`);
|
|
86
|
+
|
|
87
|
+
// 1. Check Smart Account Balance
|
|
88
|
+
const saBalance = await this.publicClient.readContract({
|
|
89
|
+
address: USDC_ADDRESS,
|
|
90
|
+
abi: ERC20_ABI,
|
|
91
|
+
functionName: 'balanceOf',
|
|
92
|
+
args: [this.account.address]
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
console.log(`[SmartAccount] Current Balance: ${saBalance}`);
|
|
96
|
+
|
|
97
|
+
if (saBalance >= requiredAmount) {
|
|
98
|
+
console.log(`[SmartAccount] ✅ Sufficient funds.`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const shortage = requiredAmount - saBalance;
|
|
103
|
+
console.log(`[SmartAccount] ⚠️ Insufficient funds. Shortage: ${shortage}`);
|
|
104
|
+
|
|
105
|
+
// 2. Check EOA Balance
|
|
106
|
+
const eoaAddress = this.signer.account.address;
|
|
107
|
+
const eoaBalance = await this.publicClient.readContract({
|
|
108
|
+
address: USDC_ADDRESS,
|
|
109
|
+
abi: ERC20_ABI,
|
|
110
|
+
functionName: 'balanceOf',
|
|
111
|
+
args: [eoaAddress]
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(`[SmartAccount] EOA Balance: ${eoaBalance}`);
|
|
115
|
+
|
|
116
|
+
if (eoaBalance < shortage) {
|
|
117
|
+
throw new Error(`Insufficient funds in both Smart Account (${saBalance}) and EOA (${eoaBalance}). Required: ${requiredAmount}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 3. Deposit from EOA
|
|
121
|
+
console.log(`[SmartAccount] 🔄 Auto-depositing ${shortage} USDC from EOA...`);
|
|
122
|
+
|
|
123
|
+
const hash = await this.signer.writeContract({
|
|
124
|
+
address: USDC_ADDRESS,
|
|
125
|
+
abi: ERC20_ABI,
|
|
126
|
+
functionName: 'transfer',
|
|
127
|
+
args: [this.account.address, shortage],
|
|
128
|
+
chain: this.signer.chain,
|
|
129
|
+
account: this.signer.account
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
console.log(`[SmartAccount] Deposit Tx Sent: ${hash}. Waiting for confirmation...`);
|
|
133
|
+
|
|
134
|
+
await this.publicClient.waitForTransactionReceipt({ hash });
|
|
135
|
+
|
|
136
|
+
console.log(`[SmartAccount] ✅ Deposit confirmed. Proceeding with UserOp.`);
|
|
137
|
+
}
|
|
63
138
|
}
|
|
@@ -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
|
)
|