four-flap-meme-sdk 1.4.91 → 1.4.92

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 (162) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.js +24 -0
  3. package/dist/sol/constants.d.ts +126 -0
  4. package/dist/sol/constants.js +145 -0
  5. package/dist/sol/dex/blockrazor/client.d.ts +51 -0
  6. package/dist/sol/dex/blockrazor/client.js +96 -0
  7. package/dist/sol/dex/blockrazor/constants.d.ts +34 -0
  8. package/dist/sol/dex/blockrazor/constants.js +55 -0
  9. package/dist/sol/dex/blockrazor/geyser.d.ts +128 -0
  10. package/dist/sol/dex/blockrazor/geyser.js +530 -0
  11. package/dist/sol/dex/blockrazor/index.d.ts +18 -0
  12. package/dist/sol/dex/blockrazor/index.js +23 -0
  13. package/dist/sol/dex/blockrazor/send.d.ts +135 -0
  14. package/dist/sol/dex/blockrazor/send.js +254 -0
  15. package/dist/sol/dex/blockrazor/types.d.ts +191 -0
  16. package/dist/sol/dex/blockrazor/types.js +5 -0
  17. package/dist/sol/dex/index.d.ts +10 -0
  18. package/dist/sol/dex/index.js +16 -0
  19. package/dist/sol/dex/jup/client.d.ts +33 -0
  20. package/dist/sol/dex/jup/client.js +110 -0
  21. package/dist/sol/dex/jup/index.d.ts +16 -0
  22. package/dist/sol/dex/jup/index.js +148 -0
  23. package/dist/sol/dex/jup/legacy.d.ts +623 -0
  24. package/dist/sol/dex/jup/legacy.js +416 -0
  25. package/dist/sol/dex/jup/lend.d.ts +640 -0
  26. package/dist/sol/dex/jup/lend.js +603 -0
  27. package/dist/sol/dex/jup/portfolio.d.ts +362 -0
  28. package/dist/sol/dex/jup/portfolio.js +367 -0
  29. package/dist/sol/dex/jup/price.d.ts +173 -0
  30. package/dist/sol/dex/jup/price.js +220 -0
  31. package/dist/sol/dex/jup/recurring.d.ts +437 -0
  32. package/dist/sol/dex/jup/recurring.js +320 -0
  33. package/dist/sol/dex/jup/send.d.ts +282 -0
  34. package/dist/sol/dex/jup/send.js +295 -0
  35. package/dist/sol/dex/jup/studio.d.ts +457 -0
  36. package/dist/sol/dex/jup/studio.js +488 -0
  37. package/dist/sol/dex/jup/tokens.d.ts +767 -0
  38. package/dist/sol/dex/jup/tokens.js +697 -0
  39. package/dist/sol/dex/jup/trigger.d.ts +511 -0
  40. package/dist/sol/dex/jup/trigger.js +397 -0
  41. package/dist/sol/dex/jup/types.d.ts +433 -0
  42. package/dist/sol/dex/jup/types.js +5 -0
  43. package/dist/sol/dex/jup/ultra.d.ts +646 -0
  44. package/dist/sol/dex/jup/ultra.js +853 -0
  45. package/dist/sol/dex/meteora/client.d.ts +76 -0
  46. package/dist/sol/dex/meteora/client.js +219 -0
  47. package/dist/sol/dex/meteora/damm-v1-bundle.d.ts +61 -0
  48. package/dist/sol/dex/meteora/damm-v1-bundle.js +112 -0
  49. package/dist/sol/dex/meteora/damm-v1.d.ts +118 -0
  50. package/dist/sol/dex/meteora/damm-v1.js +315 -0
  51. package/dist/sol/dex/meteora/damm-v2-bundle.d.ts +82 -0
  52. package/dist/sol/dex/meteora/damm-v2-bundle.js +242 -0
  53. package/dist/sol/dex/meteora/damm-v2.d.ts +172 -0
  54. package/dist/sol/dex/meteora/damm-v2.js +632 -0
  55. package/dist/sol/dex/meteora/dbc-bundle.d.ts +123 -0
  56. package/dist/sol/dex/meteora/dbc-bundle.js +304 -0
  57. package/dist/sol/dex/meteora/dbc.d.ts +192 -0
  58. package/dist/sol/dex/meteora/dbc.js +619 -0
  59. package/dist/sol/dex/meteora/dlmm-bundle.d.ts +39 -0
  60. package/dist/sol/dex/meteora/dlmm-bundle.js +189 -0
  61. package/dist/sol/dex/meteora/dlmm.d.ts +157 -0
  62. package/dist/sol/dex/meteora/dlmm.js +671 -0
  63. package/dist/sol/dex/meteora/index.d.ts +25 -0
  64. package/dist/sol/dex/meteora/index.js +65 -0
  65. package/dist/sol/dex/meteora/types.d.ts +787 -0
  66. package/dist/sol/dex/meteora/types.js +110 -0
  67. package/dist/sol/dex/orca/index.d.ts +10 -0
  68. package/dist/sol/dex/orca/index.js +16 -0
  69. package/dist/sol/dex/orca/orca-bundle.d.ts +41 -0
  70. package/dist/sol/dex/orca/orca-bundle.js +173 -0
  71. package/dist/sol/dex/orca/orca.d.ts +65 -0
  72. package/dist/sol/dex/orca/orca.js +474 -0
  73. package/dist/sol/dex/orca/types.d.ts +263 -0
  74. package/dist/sol/dex/orca/types.js +38 -0
  75. package/dist/sol/dex/orca/wavebreak-bundle.d.ts +34 -0
  76. package/dist/sol/dex/orca/wavebreak-bundle.js +198 -0
  77. package/dist/sol/dex/orca/wavebreak-types.d.ts +227 -0
  78. package/dist/sol/dex/orca/wavebreak-types.js +23 -0
  79. package/dist/sol/dex/orca/wavebreak.d.ts +78 -0
  80. package/dist/sol/dex/orca/wavebreak.js +497 -0
  81. package/dist/sol/dex/pump/index.d.ts +9 -0
  82. package/dist/sol/dex/pump/index.js +14 -0
  83. package/dist/sol/dex/pump/pump-bundle.d.ts +92 -0
  84. package/dist/sol/dex/pump/pump-bundle.js +383 -0
  85. package/dist/sol/dex/pump/pump-swap-bundle.d.ts +103 -0
  86. package/dist/sol/dex/pump/pump-swap-bundle.js +380 -0
  87. package/dist/sol/dex/pump/pump-swap.d.ts +46 -0
  88. package/dist/sol/dex/pump/pump-swap.js +199 -0
  89. package/dist/sol/dex/pump/pump.d.ts +35 -0
  90. package/dist/sol/dex/pump/pump.js +352 -0
  91. package/dist/sol/dex/pump/types.d.ts +215 -0
  92. package/dist/sol/dex/pump/types.js +5 -0
  93. package/dist/sol/dex/raydium/index.d.ts +8 -0
  94. package/dist/sol/dex/raydium/index.js +12 -0
  95. package/dist/sol/dex/raydium/launchlab.d.ts +68 -0
  96. package/dist/sol/dex/raydium/launchlab.js +210 -0
  97. package/dist/sol/dex/raydium/raydium-bundle.d.ts +64 -0
  98. package/dist/sol/dex/raydium/raydium-bundle.js +324 -0
  99. package/dist/sol/dex/raydium/raydium.d.ts +40 -0
  100. package/dist/sol/dex/raydium/raydium.js +366 -0
  101. package/dist/sol/dex/raydium/types.d.ts +240 -0
  102. package/dist/sol/dex/raydium/types.js +5 -0
  103. package/dist/sol/index.d.ts +10 -0
  104. package/dist/sol/index.js +16 -0
  105. package/dist/sol/jito/bundle.d.ts +90 -0
  106. package/dist/sol/jito/bundle.js +263 -0
  107. package/dist/sol/jito/index.d.ts +7 -0
  108. package/dist/sol/jito/index.js +7 -0
  109. package/dist/sol/jito/tip.d.ts +51 -0
  110. package/dist/sol/jito/tip.js +83 -0
  111. package/dist/sol/jito/types.d.ts +100 -0
  112. package/dist/sol/jito/types.js +5 -0
  113. package/dist/sol/token/create-complete.d.ts +115 -0
  114. package/dist/sol/token/create-complete.js +235 -0
  115. package/dist/sol/token/create-token.d.ts +57 -0
  116. package/dist/sol/token/create-token.js +230 -0
  117. package/dist/sol/token/index.d.ts +9 -0
  118. package/dist/sol/token/index.js +14 -0
  119. package/dist/sol/token/metadata-upload.d.ts +86 -0
  120. package/dist/sol/token/metadata-upload.js +173 -0
  121. package/dist/sol/token/metadata.d.ts +92 -0
  122. package/dist/sol/token/metadata.js +274 -0
  123. package/dist/sol/token/types.d.ts +153 -0
  124. package/dist/sol/token/types.js +5 -0
  125. package/dist/sol/types.d.ts +176 -0
  126. package/dist/sol/types.js +7 -0
  127. package/dist/sol/utils/balance.d.ts +160 -0
  128. package/dist/sol/utils/balance.js +638 -0
  129. package/dist/sol/utils/connection.d.ts +78 -0
  130. package/dist/sol/utils/connection.js +168 -0
  131. package/dist/sol/utils/index.d.ts +9 -0
  132. package/dist/sol/utils/index.js +9 -0
  133. package/dist/sol/utils/lp-inspect.d.ts +129 -0
  134. package/dist/sol/utils/lp-inspect.js +900 -0
  135. package/dist/sol/utils/transfer.d.ts +196 -0
  136. package/dist/sol/utils/transfer.js +307 -0
  137. package/dist/sol/utils/wallet.d.ts +107 -0
  138. package/dist/sol/utils/wallet.js +210 -0
  139. package/dist/xlayer/aa-account.d.ts +240 -0
  140. package/dist/xlayer/aa-account.js +510 -0
  141. package/dist/xlayer/bundle.d.ts +93 -0
  142. package/dist/xlayer/bundle.js +472 -0
  143. package/dist/xlayer/bundler.d.ts +111 -0
  144. package/dist/xlayer/bundler.js +162 -0
  145. package/dist/xlayer/constants.d.ts +75 -0
  146. package/dist/xlayer/constants.js +145 -0
  147. package/dist/xlayer/dex.d.ts +132 -0
  148. package/dist/xlayer/dex.js +335 -0
  149. package/dist/xlayer/examples/bundle-buy-sell.d.ts +11 -0
  150. package/dist/xlayer/examples/bundle-buy-sell.js +194 -0
  151. package/dist/xlayer/index.d.ts +75 -0
  152. package/dist/xlayer/index.js +130 -0
  153. package/dist/xlayer/portal-ops.d.ts +152 -0
  154. package/dist/xlayer/portal-ops.js +239 -0
  155. package/dist/xlayer/types.d.ts +298 -0
  156. package/dist/xlayer/types.js +6 -0
  157. package/dist/xlayer/volume.d.ts +65 -0
  158. package/dist/xlayer/volume.js +235 -0
  159. package/package.json +38 -3
  160. package/README.zh-CN.pdf +0 -0
  161. package/dist/flap/portal-bundle-merkle/encryption.d.ts +0 -16
  162. package/dist/flap/portal-bundle-merkle/encryption.js +0 -146
@@ -0,0 +1,472 @@
1
+ /**
2
+ * XLayer 捆绑交易 SDK
3
+ *
4
+ * 通过 ERC-4337 handleOps 实现多个地址的原子化交易:
5
+ * - 捆绑买入:多个 sender 同一笔 handleOps 买入
6
+ * - 捆绑卖出:多个 sender 同一笔 handleOps 卖出
7
+ * - 买卖一体化:买入 -> 授权 -> 卖出 -> 归集
8
+ * - OKB 归集:将 sender 的 OKB 转回 owner
9
+ */
10
+ import { Interface, Contract } from 'ethers';
11
+ import { FLAP_PORTAL, ENTRYPOINT_ABI, DEFAULT_CALL_GAS_LIMIT_SELL, DEFAULT_WITHDRAW_RESERVE, } from './constants.js';
12
+ import { AAAccountManager, encodeExecute, createWallet } from './aa-account.js';
13
+ import { encodeBuyCall, encodeSellCall, encodeApproveCall, encodeTransferCall, PortalQuery, parseOkb, formatOkb, } from './portal-ops.js';
14
+ // ============================================================================
15
+ // 捆绑交易执行器
16
+ // ============================================================================
17
+ /**
18
+ * XLayer 捆绑交易执行器
19
+ *
20
+ * 核心设计原则:
21
+ * 1. 用户只需操控 AA 地址(Sender)
22
+ * 2. 如果指定了 Paymaster,不需要往 AA 转手续费
23
+ * 3. 多个 UserOp 放入同一笔 handleOps 保证原子性
24
+ */
25
+ export class BundleExecutor {
26
+ constructor(config = {}) {
27
+ this.config = config;
28
+ this.aaManager = new AAAccountManager(config);
29
+ this.portalQuery = new PortalQuery({
30
+ rpcUrl: config.rpcUrl,
31
+ chainId: config.chainId,
32
+ });
33
+ this.portalAddress = FLAP_PORTAL;
34
+ }
35
+ /**
36
+ * 获取 AA 管理器
37
+ */
38
+ getAAManager() {
39
+ return this.aaManager;
40
+ }
41
+ /**
42
+ * 获取 Portal 查询器
43
+ */
44
+ getPortalQuery() {
45
+ return this.portalQuery;
46
+ }
47
+ /**
48
+ * 执行 handleOps 并解析结果
49
+ */
50
+ async runHandleOps(label, ops, bundlerSigner, beneficiary) {
51
+ if (ops.length === 0) {
52
+ console.log(`\n[${label}] 没有 ops,跳过`);
53
+ return null;
54
+ }
55
+ const provider = this.aaManager.getProvider();
56
+ const entryPointAddress = this.aaManager.getEntryPointAddress();
57
+ const feeData = await provider.getFeeData();
58
+ console.log(`\n[${label}] 发送 handleOps,ops=${ops.length} ...`);
59
+ // 使用 bundlerSigner 创建新的合约实例以调用 handleOps
60
+ const entryPointWithSigner = new Contract(entryPointAddress, ENTRYPOINT_ABI, bundlerSigner);
61
+ const tx = await entryPointWithSigner.handleOps(ops, beneficiary, { gasPrice: feeData.gasPrice ?? 100000000n });
62
+ console.log(`[${label}] txHash:`, tx.hash);
63
+ const receipt = await tx.wait();
64
+ console.log(`[${label}] mined block=${receipt.blockNumber} status=${receipt.status}`);
65
+ // 解析 UserOperationEvent
66
+ const epIface = new Interface(ENTRYPOINT_ABI);
67
+ const userOpEvents = [];
68
+ for (const log of receipt.logs) {
69
+ try {
70
+ const parsed = epIface.parseLog(log);
71
+ if (parsed?.name === 'UserOperationEvent') {
72
+ const e = parsed.args;
73
+ userOpEvents.push({
74
+ userOpHash: e.userOpHash,
75
+ sender: e.sender,
76
+ paymaster: e.paymaster,
77
+ success: e.success,
78
+ actualGasCost: e.actualGasCost,
79
+ actualGasUsed: e.actualGasUsed,
80
+ });
81
+ console.log(` - userOpHash=${e.userOpHash} sender=${e.sender} success=${e.success} gasUsed=${e.actualGasUsed}`);
82
+ }
83
+ }
84
+ catch { }
85
+ }
86
+ // 检查是否有失败的 UserOp
87
+ const failed = userOpEvents.filter((e) => !e.success);
88
+ if (failed.length > 0) {
89
+ console.warn(`[${label}] 有 ${failed.length} 个 UserOp 执行失败`);
90
+ }
91
+ return {
92
+ txHash: tx.hash,
93
+ blockNumber: receipt.blockNumber,
94
+ status: receipt.status,
95
+ userOpEvents,
96
+ };
97
+ }
98
+ /**
99
+ * 构建买入 UserOp
100
+ */
101
+ async buildBuyUserOp(ownerWallet, tokenAddress, buyAmountWei, ownerName) {
102
+ const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
103
+ const initCode = accountInfo.deployed
104
+ ? '0x'
105
+ : this.aaManager.generateInitCode(ownerWallet.address);
106
+ // 构建 callData: execute(portal, value=buyAmountWei, swapExactInput)
107
+ const swapData = encodeBuyCall(tokenAddress, buyAmountWei, 0n);
108
+ const callData = encodeExecute(this.portalAddress, buyAmountWei, swapData);
109
+ // 估算前确保 sender 有足够余额(用于模拟)
110
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, buyAmountWei + parseOkb('0.0003'), `${ownerName ?? 'owner'}/buy-prefund-before-estimate`);
111
+ // 使用 Bundler 估算
112
+ const { userOp, prefundWei } = await this.aaManager.buildUserOpWithBundlerEstimate({
113
+ ownerWallet,
114
+ sender: accountInfo.sender,
115
+ callData,
116
+ nonce: accountInfo.nonce,
117
+ initCode,
118
+ });
119
+ // 补足 prefund + 买入金额
120
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, buyAmountWei + prefundWei + parseOkb('0.0002'), `${ownerName ?? 'owner'}/buy-fund`);
121
+ // 签名
122
+ const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
123
+ return {
124
+ ...signed,
125
+ prefundWei,
126
+ ownerName,
127
+ };
128
+ }
129
+ /**
130
+ * 构建授权 UserOp
131
+ */
132
+ async buildApproveUserOp(ownerWallet, tokenAddress, spender, nonce, ownerName) {
133
+ const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
134
+ const approveData = encodeApproveCall(spender);
135
+ const callData = encodeExecute(tokenAddress, 0n, approveData);
136
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/approve-prefund`);
137
+ const { userOp, prefundWei } = await this.aaManager.buildUserOpWithLocalEstimate({
138
+ ownerWallet,
139
+ sender: accountInfo.sender,
140
+ callData,
141
+ nonce,
142
+ });
143
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/approve-fund`);
144
+ const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
145
+ return { ...signed, prefundWei, ownerName };
146
+ }
147
+ /**
148
+ * 构建卖出 UserOp
149
+ */
150
+ async buildSellUserOp(ownerWallet, tokenAddress, sellAmount, nonce, needApprove, ownerName) {
151
+ const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
152
+ const sellData = encodeSellCall(tokenAddress, sellAmount, 0n);
153
+ const callData = encodeExecute(this.portalAddress, 0n, sellData);
154
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, parseOkb('0.0003'), `${ownerName ?? 'owner'}/sell-prefund`);
155
+ // 如果需要 approve(还未执行),estimateGas 会 revert,使用固定值
156
+ const { userOp, prefundWei } = needApprove
157
+ ? await this.aaManager.buildUserOpWithLocalEstimate({
158
+ ownerWallet,
159
+ sender: accountInfo.sender,
160
+ callData,
161
+ nonce,
162
+ callGasLimit: DEFAULT_CALL_GAS_LIMIT_SELL,
163
+ })
164
+ : await this.aaManager.buildUserOpWithLocalEstimate({
165
+ ownerWallet,
166
+ sender: accountInfo.sender,
167
+ callData,
168
+ nonce,
169
+ });
170
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/sell-fund`);
171
+ const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
172
+ return { ...signed, prefundWei, ownerName };
173
+ }
174
+ /**
175
+ * 构建归集 UserOp(将 OKB 从 sender 转回 owner)
176
+ */
177
+ async buildWithdrawUserOp(ownerWallet, reserveWei, ownerName) {
178
+ const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
179
+ const senderBalance = await this.portalQuery.getOkbBalance(accountInfo.sender);
180
+ if (senderBalance <= reserveWei) {
181
+ console.log(`\n[${ownerName ?? 'owner'}] sender OKB 太少,跳过归集:${formatOkb(senderBalance)} OKB`);
182
+ return null;
183
+ }
184
+ // 先估算 prefund
185
+ const tempCallData = encodeExecute(ownerWallet.address, 0n, '0x');
186
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/withdraw-prefund`);
187
+ const { prefundWei } = await this.aaManager.buildUserOpWithLocalEstimate({
188
+ ownerWallet,
189
+ sender: accountInfo.sender,
190
+ callData: tempCallData,
191
+ nonce: accountInfo.nonce,
192
+ });
193
+ // 计算可归集金额
194
+ const currentBalance = await this.portalQuery.getOkbBalance(accountInfo.sender);
195
+ const withdrawAmount = currentBalance > prefundWei + reserveWei
196
+ ? currentBalance - prefundWei - reserveWei
197
+ : 0n;
198
+ if (withdrawAmount <= 0n) {
199
+ console.log(`\n[${ownerName ?? 'owner'}] 归集后可转出=0(余额不足以覆盖 prefund+reserve)`);
200
+ return null;
201
+ }
202
+ // 真正的 callData
203
+ const callData = encodeExecute(ownerWallet.address, withdrawAmount, '0x');
204
+ const { userOp } = await this.aaManager.buildUserOpWithLocalEstimate({
205
+ ownerWallet,
206
+ sender: accountInfo.sender,
207
+ callData,
208
+ nonce: accountInfo.nonce,
209
+ });
210
+ console.log(`\n[${ownerName ?? 'owner'}] withdraw: ${formatOkb(withdrawAmount)} OKB`);
211
+ const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
212
+ return { ...signed, prefundWei, ownerName };
213
+ }
214
+ /**
215
+ * 构建代币转账 UserOp(将代币从 sender 转回 owner)
216
+ */
217
+ async buildTransferTokenUserOp(ownerWallet, tokenAddress, ownerName) {
218
+ const accountInfo = await this.aaManager.getAccountInfo(ownerWallet.address);
219
+ const tokenBalance = await this.portalQuery.getTokenBalance(tokenAddress, accountInfo.sender);
220
+ if (tokenBalance === 0n) {
221
+ console.log(`\n[${ownerName ?? 'owner'}] sender 没有代币,跳过转账`);
222
+ return null;
223
+ }
224
+ const transferData = encodeTransferCall(ownerWallet.address, tokenBalance);
225
+ const callData = encodeExecute(tokenAddress, 0n, transferData);
226
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, parseOkb('0.0002'), `${ownerName ?? 'owner'}/transfer-prefund`);
227
+ const { userOp, prefundWei } = await this.aaManager.buildUserOpWithBundlerEstimate({
228
+ ownerWallet,
229
+ sender: accountInfo.sender,
230
+ callData,
231
+ nonce: accountInfo.nonce,
232
+ });
233
+ await this.aaManager.ensureSenderBalance(ownerWallet, accountInfo.sender, prefundWei + parseOkb('0.00005'), `${ownerName ?? 'owner'}/transfer-fund`);
234
+ console.log(`\n[${ownerName ?? 'owner'}] transfer token: ${tokenBalance.toString()}`);
235
+ const signed = await this.aaManager.signUserOp(userOp, ownerWallet);
236
+ return { ...signed, prefundWei, ownerName };
237
+ }
238
+ // ============================================================================
239
+ // 公开 API
240
+ // ============================================================================
241
+ /**
242
+ * 捆绑买入
243
+ *
244
+ * 多个地址同一笔 handleOps 买入代币
245
+ */
246
+ async bundleBuy(params) {
247
+ const { tokenAddress, privateKeys, buyAmounts, transferBackToOwner, config } = params;
248
+ if (privateKeys.length !== buyAmounts.length) {
249
+ throw new Error('私钥数量和购买金额数量必须一致');
250
+ }
251
+ // 使用第一个 owner 作为 bundler signer
252
+ const wallets = privateKeys.map((pk) => createWallet(pk, this.config));
253
+ const bundlerSigner = wallets[0];
254
+ const beneficiary = bundlerSigner.address;
255
+ console.log('=== XLayer Bundle Buy ===');
256
+ console.log('token:', tokenAddress);
257
+ console.log('owners:', wallets.length);
258
+ // 1. 构建买入 UserOps
259
+ const buyOps = [];
260
+ for (let i = 0; i < wallets.length; i++) {
261
+ const wallet = wallets[i];
262
+ const buyWei = parseOkb(buyAmounts[i]);
263
+ const signed = await this.buildBuyUserOp(wallet, tokenAddress, buyWei, `owner${i + 1}`);
264
+ buyOps.push(signed.userOp);
265
+ }
266
+ // 2. 执行买入
267
+ const buyResult = await this.runHandleOps('buyBundle', buyOps, bundlerSigner, beneficiary);
268
+ if (!buyResult) {
269
+ throw new Error('买入交易失败');
270
+ }
271
+ // 3. 获取代币余额
272
+ const senders = await Promise.all(wallets.map((w) => this.aaManager.predictSenderAddress(w.address)));
273
+ const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
274
+ // 4. 可选:转账代币回 owner
275
+ let transferResult;
276
+ if (transferBackToOwner) {
277
+ const transferOps = [];
278
+ for (let i = 0; i < wallets.length; i++) {
279
+ const signed = await this.buildTransferTokenUserOp(wallets[i], tokenAddress, `owner${i + 1}`);
280
+ if (signed) {
281
+ transferOps.push(signed.userOp);
282
+ }
283
+ }
284
+ if (transferOps.length > 0) {
285
+ transferResult = await this.runHandleOps('transferBundle', transferOps, bundlerSigner, beneficiary) ?? undefined;
286
+ }
287
+ }
288
+ return { buyResult, transferResult, tokenBalances };
289
+ }
290
+ /**
291
+ * 捆绑卖出
292
+ *
293
+ * 多个地址同一笔 handleOps 卖出代币
294
+ */
295
+ async bundleSell(params) {
296
+ const { tokenAddress, privateKeys, sellPercent = 100, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, } = params;
297
+ const wallets = privateKeys.map((pk) => createWallet(pk, this.config));
298
+ const bundlerSigner = wallets[0];
299
+ const beneficiary = bundlerSigner.address;
300
+ const reserveWei = parseOkb(withdrawReserve);
301
+ console.log('=== XLayer Bundle Sell ===');
302
+ console.log('token:', tokenAddress);
303
+ console.log('owners:', wallets.length);
304
+ console.log('sellPercent:', sellPercent);
305
+ // 获取 sender 列表和余额
306
+ const senders = await Promise.all(wallets.map((w) => this.aaManager.predictSenderAddress(w.address)));
307
+ const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
308
+ const allowances = await this.portalQuery.getMultipleAllowances(tokenAddress, senders);
309
+ // 1. 检查授权,必要时先 approve
310
+ const approveOps = [];
311
+ for (let i = 0; i < wallets.length; i++) {
312
+ const sender = senders[i];
313
+ const balance = tokenBalances.get(sender) ?? 0n;
314
+ const allowance = allowances.get(sender) ?? 0n;
315
+ if (balance === 0n)
316
+ continue;
317
+ if (allowance >= balance)
318
+ continue;
319
+ const accountInfo = await this.aaManager.getAccountInfo(wallets[i].address);
320
+ const signed = await this.buildApproveUserOp(wallets[i], tokenAddress, this.portalAddress, accountInfo.nonce, `owner${i + 1}`);
321
+ approveOps.push(signed.userOp);
322
+ }
323
+ let approveResult;
324
+ if (approveOps.length > 0) {
325
+ approveResult = await this.runHandleOps('approveBundle', approveOps, bundlerSigner, beneficiary) ?? undefined;
326
+ }
327
+ // 2. 卖出
328
+ const sellOps = [];
329
+ for (let i = 0; i < wallets.length; i++) {
330
+ const sender = senders[i];
331
+ const balance = tokenBalances.get(sender) ?? 0n;
332
+ if (balance === 0n)
333
+ continue;
334
+ const sellAmount = (balance * BigInt(sellPercent)) / 100n;
335
+ if (sellAmount === 0n)
336
+ continue;
337
+ const accountInfo = await this.aaManager.getAccountInfo(wallets[i].address);
338
+ const allowance = allowances.get(sender) ?? 0n;
339
+ const needApprove = allowance < sellAmount && approveOps.length > 0;
340
+ const signed = await this.buildSellUserOp(wallets[i], tokenAddress, sellAmount, accountInfo.nonce, needApprove, `owner${i + 1}`);
341
+ sellOps.push(signed.userOp);
342
+ }
343
+ const sellResult = await this.runHandleOps('sellBundle', sellOps, bundlerSigner, beneficiary);
344
+ if (!sellResult) {
345
+ throw new Error('卖出交易失败');
346
+ }
347
+ // 3. 可选:归集 OKB
348
+ let withdrawResult;
349
+ if (withdrawToOwner) {
350
+ const withdrawOps = [];
351
+ for (let i = 0; i < wallets.length; i++) {
352
+ const signed = await this.buildWithdrawUserOp(wallets[i], reserveWei, `owner${i + 1}`);
353
+ if (signed) {
354
+ withdrawOps.push(signed.userOp);
355
+ }
356
+ }
357
+ if (withdrawOps.length > 0) {
358
+ withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
359
+ }
360
+ }
361
+ return { approveResult, sellResult, withdrawResult };
362
+ }
363
+ /**
364
+ * 捆绑买卖(先买后卖)
365
+ *
366
+ * 完整流程:买入 -> 授权 -> 卖出 -> 归集
367
+ */
368
+ async bundleBuySell(params) {
369
+ const { tokenAddress, privateKeys, buyAmounts, sellPercent = 100, withdrawToOwner = true, withdrawReserve = DEFAULT_WITHDRAW_RESERVE, } = params;
370
+ if (privateKeys.length !== buyAmounts.length) {
371
+ throw new Error('私钥数量和购买金额数量必须一致');
372
+ }
373
+ const wallets = privateKeys.map((pk) => createWallet(pk, this.config));
374
+ const bundlerSigner = wallets[0];
375
+ const beneficiary = bundlerSigner.address;
376
+ const reserveWei = parseOkb(withdrawReserve);
377
+ console.log('=== XLayer Bundle Buy -> Sell ===');
378
+ console.log('token:', tokenAddress);
379
+ console.log('owners:', wallets.length);
380
+ console.log('sellPercent:', sellPercent);
381
+ // 获取 sender 列表
382
+ const senders = await Promise.all(wallets.map((w) => this.aaManager.predictSenderAddress(w.address)));
383
+ // 1. 买入
384
+ const buyOps = [];
385
+ for (let i = 0; i < wallets.length; i++) {
386
+ const buyWei = parseOkb(buyAmounts[i]);
387
+ const signed = await this.buildBuyUserOp(wallets[i], tokenAddress, buyWei, `owner${i + 1}`);
388
+ buyOps.push(signed.userOp);
389
+ }
390
+ const buyResult = await this.runHandleOps('buyBundle', buyOps, bundlerSigner, beneficiary);
391
+ if (!buyResult) {
392
+ throw new Error('买入交易失败');
393
+ }
394
+ // 获取买入后的代币余额
395
+ const tokenBalances = await this.portalQuery.getMultipleTokenBalances(tokenAddress, senders);
396
+ const allowances = await this.portalQuery.getMultipleAllowances(tokenAddress, senders);
397
+ // 2. 授权 + 卖出(可以合并到同一笔 handleOps)
398
+ const sellOps = [];
399
+ for (let i = 0; i < wallets.length; i++) {
400
+ const sender = senders[i];
401
+ const balance = tokenBalances.get(sender) ?? 0n;
402
+ if (balance === 0n) {
403
+ console.log(`[owner${i + 1}] 没买到代币,跳过卖出`);
404
+ continue;
405
+ }
406
+ const allowance = allowances.get(sender) ?? 0n;
407
+ let baseNonce = (await this.aaManager.getAccountInfo(wallets[i].address)).nonce;
408
+ // 如果需要授权,先添加 approve op
409
+ const needApprove = allowance < balance;
410
+ if (needApprove) {
411
+ const approveOp = await this.buildApproveUserOp(wallets[i], tokenAddress, this.portalAddress, baseNonce, `owner${i + 1}`);
412
+ sellOps.push(approveOp.userOp);
413
+ baseNonce = baseNonce + 1n;
414
+ }
415
+ // 添加 sell op
416
+ const sellAmount = (balance * BigInt(sellPercent)) / 100n;
417
+ const sellOp = await this.buildSellUserOp(wallets[i], tokenAddress, sellAmount, baseNonce, needApprove, `owner${i + 1}`);
418
+ sellOps.push(sellOp.userOp);
419
+ }
420
+ const sellResult = await this.runHandleOps('sellBundle', sellOps, bundlerSigner, beneficiary);
421
+ if (!sellResult) {
422
+ throw new Error('卖出交易失败');
423
+ }
424
+ // 3. 可选:归集 OKB
425
+ let withdrawResult;
426
+ if (withdrawToOwner) {
427
+ const withdrawOps = [];
428
+ for (let i = 0; i < wallets.length; i++) {
429
+ const signed = await this.buildWithdrawUserOp(wallets[i], reserveWei, `owner${i + 1}`);
430
+ if (signed) {
431
+ withdrawOps.push(signed.userOp);
432
+ }
433
+ }
434
+ if (withdrawOps.length > 0) {
435
+ withdrawResult = await this.runHandleOps('withdrawBundle', withdrawOps, bundlerSigner, beneficiary) ?? undefined;
436
+ }
437
+ }
438
+ // 最终余额
439
+ const finalBalances = await this.portalQuery.getMultipleOkbBalances(senders);
440
+ return { buyResult, sellResult, withdrawResult, finalBalances };
441
+ }
442
+ }
443
+ // ============================================================================
444
+ // 便捷函数
445
+ // ============================================================================
446
+ /**
447
+ * 创建捆绑交易执行器
448
+ */
449
+ export function createBundleExecutor(config) {
450
+ return new BundleExecutor(config);
451
+ }
452
+ /**
453
+ * 快速捆绑买入
454
+ */
455
+ export async function bundleBuy(params) {
456
+ const executor = createBundleExecutor(params.config);
457
+ return executor.bundleBuy(params);
458
+ }
459
+ /**
460
+ * 快速捆绑卖出
461
+ */
462
+ export async function bundleSell(params) {
463
+ const executor = createBundleExecutor(params.config);
464
+ return executor.bundleSell(params);
465
+ }
466
+ /**
467
+ * 快速捆绑买卖
468
+ */
469
+ export async function bundleBuySell(params) {
470
+ const executor = createBundleExecutor(params.config);
471
+ return executor.bundleBuySell(params);
472
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * XLayer Bundler 客户端
3
+ *
4
+ * 与 Particle Bundler 交互,提供 ERC-4337 相关 RPC 方法
5
+ */
6
+ import type { UserOperation, GasEstimate } from './types.js';
7
+ export interface BundlerConfig {
8
+ /** Bundler URL */
9
+ url?: string;
10
+ /** Chain ID */
11
+ chainId?: number;
12
+ /** EntryPoint 地址 */
13
+ entryPoint?: string;
14
+ /** 请求超时时间(毫秒) */
15
+ timeoutMs?: number;
16
+ /** 额外请求头 */
17
+ headers?: Record<string, string>;
18
+ }
19
+ export interface BundlerReceipt {
20
+ userOpHash: string;
21
+ sender: string;
22
+ nonce: string;
23
+ actualGasCost: string;
24
+ actualGasUsed: string;
25
+ success: boolean;
26
+ logs: any[];
27
+ receipt: {
28
+ transactionHash: string;
29
+ transactionIndex: number;
30
+ blockHash: string;
31
+ blockNumber: number;
32
+ from: string;
33
+ to: string;
34
+ gasUsed: string;
35
+ status: string;
36
+ };
37
+ }
38
+ /**
39
+ * Particle Bundler 客户端
40
+ *
41
+ * 提供与 ERC-4337 Bundler 交互的方法:
42
+ * - eth_supportedEntryPoints
43
+ * - eth_estimateUserOperationGas
44
+ * - eth_sendUserOperation
45
+ * - eth_getUserOperationReceipt
46
+ * - eth_getUserOperationByHash
47
+ */
48
+ export declare class BundlerClient {
49
+ private url;
50
+ private chainId;
51
+ private entryPoint;
52
+ private timeoutMs;
53
+ private headers;
54
+ constructor(config?: BundlerConfig);
55
+ /**
56
+ * 发送 JSON-RPC 请求到 Bundler
57
+ */
58
+ private rpc;
59
+ /**
60
+ * 获取支持的 EntryPoint 列表
61
+ */
62
+ getSupportedEntryPoints(): Promise<string[]>;
63
+ /**
64
+ * 估算 UserOperation Gas
65
+ *
66
+ * @param userOp 未签名的 UserOperation(signature 可为空)
67
+ * @returns Gas 估算结果
68
+ */
69
+ estimateUserOperationGas(userOp: UserOperation): Promise<GasEstimate>;
70
+ /**
71
+ * 发送 UserOperation
72
+ *
73
+ * @param userOp 已签名的 UserOperation
74
+ * @returns userOpHash
75
+ */
76
+ sendUserOperation(userOp: UserOperation): Promise<string>;
77
+ /**
78
+ * 获取 UserOperation 回执
79
+ *
80
+ * @param userOpHash UserOperation 哈希
81
+ * @returns 回执(如果还未被打包则返回 null)
82
+ */
83
+ getUserOperationReceipt(userOpHash: string): Promise<BundlerReceipt | null>;
84
+ /**
85
+ * 根据 hash 获取 UserOperation
86
+ *
87
+ * @param userOpHash UserOperation 哈希
88
+ */
89
+ getUserOperationByHash(userOpHash: string): Promise<any>;
90
+ /**
91
+ * 等待 UserOperation 被打包
92
+ *
93
+ * @param userOpHash UserOperation 哈希
94
+ * @param maxTries 最大轮询次数(默认 80)
95
+ * @param intervalMs 轮询间隔(默认 1500ms)
96
+ * @returns 回执
97
+ */
98
+ waitForUserOperationReceipt(userOpHash: string, maxTries?: number, intervalMs?: number): Promise<BundlerReceipt | null>;
99
+ /**
100
+ * 获取当前使用的 EntryPoint 地址
101
+ */
102
+ getEntryPoint(): string;
103
+ /**
104
+ * 获取当前链 ID
105
+ */
106
+ getChainId(): number;
107
+ }
108
+ /**
109
+ * 创建默认的 Bundler 客户端
110
+ */
111
+ export declare function createBundlerClient(config?: BundlerConfig): BundlerClient;