four-flap-meme-sdk 1.5.18 → 1.5.20
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.
|
@@ -16,7 +16,9 @@ import { type GeneratedWallet } from './wallet.js';
|
|
|
16
16
|
export declare class NonceManager {
|
|
17
17
|
private provider;
|
|
18
18
|
private tempNonceCache;
|
|
19
|
+
private chainIdPromise?;
|
|
19
20
|
constructor(provider: JsonRpcProvider);
|
|
21
|
+
private getChainId;
|
|
20
22
|
/**
|
|
21
23
|
* 获取下一个可用的 nonce
|
|
22
24
|
* @param wallet 钱包对象或地址
|
|
@@ -4,6 +4,47 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { ethers, Wallet } from 'ethers';
|
|
6
6
|
import { generateWallets } from './wallet.js';
|
|
7
|
+
const GLOBAL_NONCE_TTL_MS = 60 * 1000; // 1 分钟:只用于短时间“pending 不同步”兜底,避免长时间缓存造成 nonce 过高
|
|
8
|
+
const globalNonceCursorByChain = new Map();
|
|
9
|
+
function isFlakyNonceChain(chainId) {
|
|
10
|
+
// ✅ XLayer: 196(OKX X Layer)
|
|
11
|
+
return Number(chainId) === 196;
|
|
12
|
+
}
|
|
13
|
+
function getCursorMap(chainId) {
|
|
14
|
+
let m = globalNonceCursorByChain.get(chainId);
|
|
15
|
+
if (!m) {
|
|
16
|
+
m = new Map();
|
|
17
|
+
globalNonceCursorByChain.set(chainId, m);
|
|
18
|
+
}
|
|
19
|
+
return m;
|
|
20
|
+
}
|
|
21
|
+
function getGlobalNextNonce(chainId, addrLower) {
|
|
22
|
+
const m = globalNonceCursorByChain.get(chainId);
|
|
23
|
+
if (!m)
|
|
24
|
+
return undefined;
|
|
25
|
+
const e = m.get(addrLower);
|
|
26
|
+
if (!e)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (Date.now() - e.updatedAt > GLOBAL_NONCE_TTL_MS) {
|
|
29
|
+
m.delete(addrLower);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return e.nextNonce;
|
|
33
|
+
}
|
|
34
|
+
function setGlobalNextNonce(chainId, addrLower, nextNonce) {
|
|
35
|
+
const m = getCursorMap(chainId);
|
|
36
|
+
const cur = m.get(addrLower);
|
|
37
|
+
const next = Number(nextNonce);
|
|
38
|
+
if (!Number.isFinite(next) || next < 0)
|
|
39
|
+
return;
|
|
40
|
+
if (!cur || next > cur.nextNonce || (Date.now() - cur.updatedAt > GLOBAL_NONCE_TTL_MS)) {
|
|
41
|
+
m.set(addrLower, { nextNonce: next, updatedAt: Date.now() });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// 刷新时间戳,避免短时间内频繁淘汰
|
|
45
|
+
cur.updatedAt = Date.now();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
7
48
|
/**
|
|
8
49
|
* Nonce 管理器
|
|
9
50
|
* 用于在 bundle 交易中管理多个钱包的 nonce
|
|
@@ -19,6 +60,12 @@ export class NonceManager {
|
|
|
19
60
|
this.tempNonceCache = new Map();
|
|
20
61
|
this.provider = provider;
|
|
21
62
|
}
|
|
63
|
+
async getChainId() {
|
|
64
|
+
if (!this.chainIdPromise) {
|
|
65
|
+
this.chainIdPromise = this.provider.getNetwork().then(n => Number(n.chainId)).catch(() => 0);
|
|
66
|
+
}
|
|
67
|
+
return this.chainIdPromise;
|
|
68
|
+
}
|
|
22
69
|
/**
|
|
23
70
|
* 获取下一个可用的 nonce
|
|
24
71
|
* @param wallet 钱包对象或地址
|
|
@@ -27,18 +74,34 @@ export class NonceManager {
|
|
|
27
74
|
async getNextNonce(wallet) {
|
|
28
75
|
const address = typeof wallet === 'string' ? wallet : wallet.address;
|
|
29
76
|
const key = address.toLowerCase();
|
|
77
|
+
const chainId = await this.getChainId();
|
|
78
|
+
const flaky = isFlakyNonceChain(chainId);
|
|
30
79
|
// 检查临时缓存
|
|
31
80
|
if (this.tempNonceCache.has(key)) {
|
|
32
81
|
const cachedNonce = this.tempNonceCache.get(key);
|
|
33
82
|
this.tempNonceCache.set(key, cachedNonce + 1);
|
|
83
|
+
if (flaky)
|
|
84
|
+
setGlobalNextNonce(chainId, key, cachedNonce + 1);
|
|
34
85
|
return cachedNonce;
|
|
35
86
|
}
|
|
36
87
|
// ✅ 使用 'pending' 获取 nonce(包含待处理交易)
|
|
37
88
|
// 由于前端已移除 nonce 缓存,SDK 每次都从链上获取最新状态
|
|
38
89
|
const onchainNonce = await this.provider.getTransactionCount(address, 'pending');
|
|
90
|
+
// ✅ XLayer:如果链上 pending nonce 暂时没跟上,短时间内允许使用“全局游标”兜底(只允许小幅领先)
|
|
91
|
+
let effectiveNonce = onchainNonce;
|
|
92
|
+
if (flaky) {
|
|
93
|
+
const globalNext = getGlobalNextNonce(chainId, key);
|
|
94
|
+
if (globalNext !== undefined) {
|
|
95
|
+
const delta = globalNext - onchainNonce;
|
|
96
|
+
if (delta > 0 && delta <= 10) {
|
|
97
|
+
effectiveNonce = globalNext;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
setGlobalNextNonce(chainId, key, effectiveNonce + 1);
|
|
101
|
+
}
|
|
39
102
|
// 缓存下一个值(仅在当前批次内有效)
|
|
40
|
-
this.tempNonceCache.set(key,
|
|
41
|
-
return
|
|
103
|
+
this.tempNonceCache.set(key, effectiveNonce + 1);
|
|
104
|
+
return effectiveNonce;
|
|
42
105
|
}
|
|
43
106
|
/**
|
|
44
107
|
* 批量获取连续 nonce(推荐用于同一地址的批量交易)
|
|
@@ -49,6 +112,8 @@ export class NonceManager {
|
|
|
49
112
|
async getNextNonceBatch(wallet, count) {
|
|
50
113
|
const address = typeof wallet === 'string' ? wallet : wallet.address;
|
|
51
114
|
const key = address.toLowerCase();
|
|
115
|
+
const chainId = await this.getChainId();
|
|
116
|
+
const flaky = isFlakyNonceChain(chainId);
|
|
52
117
|
let startNonce;
|
|
53
118
|
if (this.tempNonceCache.has(key)) {
|
|
54
119
|
startNonce = this.tempNonceCache.get(key);
|
|
@@ -56,8 +121,18 @@ export class NonceManager {
|
|
|
56
121
|
else {
|
|
57
122
|
startNonce = await this.provider.getTransactionCount(address, 'pending');
|
|
58
123
|
}
|
|
124
|
+
if (flaky) {
|
|
125
|
+
const globalNext = getGlobalNextNonce(chainId, key);
|
|
126
|
+
if (globalNext !== undefined) {
|
|
127
|
+
const delta = globalNext - startNonce;
|
|
128
|
+
if (delta > 0 && delta <= 10)
|
|
129
|
+
startNonce = globalNext;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
59
132
|
// 更新缓存
|
|
60
133
|
this.tempNonceCache.set(key, startNonce + count);
|
|
134
|
+
if (flaky)
|
|
135
|
+
setGlobalNextNonce(chainId, key, startNonce + count);
|
|
61
136
|
// 返回连续 nonce 数组
|
|
62
137
|
return Array.from({ length: count }, (_, i) => startNonce + i);
|
|
63
138
|
}
|
|
@@ -82,6 +157,8 @@ export class NonceManager {
|
|
|
82
157
|
async getNextNoncesForWallets(wallets) {
|
|
83
158
|
const addresses = wallets.map(w => typeof w === 'string' ? w : w.address);
|
|
84
159
|
const keys = addresses.map(a => a.toLowerCase());
|
|
160
|
+
const chainId = await this.getChainId();
|
|
161
|
+
const flaky = isFlakyNonceChain(chainId);
|
|
85
162
|
// 分离:已缓存的 vs 需要查询的
|
|
86
163
|
const needQuery = [];
|
|
87
164
|
const results = new Array(wallets.length);
|
|
@@ -91,6 +168,8 @@ export class NonceManager {
|
|
|
91
168
|
const cachedNonce = this.tempNonceCache.get(key);
|
|
92
169
|
results[i] = cachedNonce;
|
|
93
170
|
this.tempNonceCache.set(key, cachedNonce + 1);
|
|
171
|
+
if (flaky)
|
|
172
|
+
setGlobalNextNonce(chainId, key, cachedNonce + 1);
|
|
94
173
|
}
|
|
95
174
|
else {
|
|
96
175
|
needQuery.push({ index: i, address: addresses[i] });
|
|
@@ -127,9 +206,20 @@ export class NonceManager {
|
|
|
127
206
|
// 填充结果并更新缓存
|
|
128
207
|
for (let i = 0; i < needQuery.length; i++) {
|
|
129
208
|
const { index, address } = needQuery[i];
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
209
|
+
const key = address.toLowerCase();
|
|
210
|
+
const onchainNonce = queryResults[i];
|
|
211
|
+
let effectiveNonce = onchainNonce;
|
|
212
|
+
if (flaky) {
|
|
213
|
+
const globalNext = getGlobalNextNonce(chainId, key);
|
|
214
|
+
if (globalNext !== undefined) {
|
|
215
|
+
const delta = globalNext - onchainNonce;
|
|
216
|
+
if (delta > 0 && delta <= 10)
|
|
217
|
+
effectiveNonce = globalNext;
|
|
218
|
+
}
|
|
219
|
+
setGlobalNextNonce(chainId, key, effectiveNonce + 1);
|
|
220
|
+
}
|
|
221
|
+
results[index] = effectiveNonce;
|
|
222
|
+
this.tempNonceCache.set(key, effectiveNonce + 1);
|
|
133
223
|
}
|
|
134
224
|
}
|
|
135
225
|
return results;
|