@w3lib/solana-batch-call 0.0.0-stage → 0.2.0
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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +270 -2
- package/dist/address.d.ts +20 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +120 -0
- package/dist/address.js.map +1 -0
- package/dist/client.d.ts +190 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +971 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +35 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +84 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +237 -0
- package/dist/layout.js.map +1 -0
- package/dist/rpc.d.ts +115 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +458 -0
- package/dist/rpc.js.map +1 -0
- package/dist/shortcuts.d.ts +93 -0
- package/dist/shortcuts.d.ts.map +1 -0
- package/dist/shortcuts.js +281 -0
- package/dist/shortcuts.js.map +1 -0
- package/dist/source.d.ts +35 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +124 -0
- package/dist/source.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -4
- package/src/address.ts +134 -0
- package/src/client.ts +1298 -0
- package/src/constants.ts +41 -0
- package/src/index.ts +98 -0
- package/src/layout.ts +320 -0
- package/src/rpc.ts +549 -0
- package/src/shortcuts.ts +367 -0
- package/src/source.ts +149 -0
- package/src/types.ts +164 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SolanaClient, type BalanceQuery, type BalancesOptions, type ClientConfig, type OwnerTokensOptions, type RequestOptions, type SolBalancesOptions, type TokensOptions } from './client.js';
|
|
2
|
+
import type { RpcSource } from './source.js';
|
|
3
|
+
import type { DefaultTokenField, NftDetails, NftOwner, OwnedToken, SolBalance, TokenBalance, TokenDetails, TokenField } from './types.js';
|
|
4
|
+
/** 各函数共用的节点参数 */
|
|
5
|
+
export interface ShortcutOptions extends ClientConfig {
|
|
6
|
+
/**
|
|
7
|
+
* 节点:RPC URL、URL 数组(主节点 + 备用节点)、web3.js Connection、自定义传输(request(method, params))。
|
|
8
|
+
* 不传则使用 cluster(默认 mainnet)的内置公共节点
|
|
9
|
+
*/
|
|
10
|
+
provider?: RpcSource | readonly RpcSource[];
|
|
11
|
+
}
|
|
12
|
+
export interface GetBalancesOptions extends BalancesOptions, ShortcutOptions {
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 批量查余额(SOL + SPL Token + Token-2022),返回原始余额、decimals 和换算后的数值。
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { NATIVE_MINT, getBalances } from '@w3lib/solana-batch-call'
|
|
19
|
+
*
|
|
20
|
+
* await getBalances(owner, [NATIVE_MINT, USDC]) // 指定代币:免费公共节点也能用
|
|
21
|
+
* await getBalances(owner, [NATIVE_MINT, USDC], { symbol: true })
|
|
22
|
+
* await getBalances(owner, undefined, { tokenPrograms: ['spl'] }) // 不含 Token-2022 代币
|
|
23
|
+
* await getBalances(owner, [USDC], { accounts: 'all' }) // 算上非 ATA 账户:需要支持 getTokenAccountsByOwner 的节点
|
|
24
|
+
* await getBalances(owner, undefined, { provider: 'https://my-rpc…' }) // 全部持仓:需要支持 getTokenAccountsByOwner 的节点
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function getBalances(owner: string, mints?: readonly string[], options?: GetBalancesOptions): Promise<TokenBalance[]>;
|
|
28
|
+
/**
|
|
29
|
+
* 多个钱包一次查,结果与 queries 一一对应;ATA 模式下所有钱包合并成一次 getMultipleAccounts。参数同 getBalances。
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* await getMultiBalances([{ owner: a, mints: [NATIVE_MINT, USDC] }, { owner: b, mints: [USDC] }])
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function getMultiBalances(queries: readonly BalanceQuery[], options?: GetBalancesOptions): Promise<TokenBalance[][]>;
|
|
36
|
+
export interface GetSolBalancesOptions extends SolBalancesOptions, ShortcutOptions {
|
|
37
|
+
}
|
|
38
|
+
/** 批量查多个地址的 SOL 余额(一次 getMultipleAccounts) */
|
|
39
|
+
export declare function getSolBalances(addresses: readonly string[], options?: GetSolBalancesOptions): Promise<SolBalance[]>;
|
|
40
|
+
export interface GetTokensOptions<F extends TokenField = DefaultTokenField> extends TokensOptions<F>, ShortcutOptions {
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 批量查代币详情,字段可选(默认 name / symbol / decimals)。
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* await getTokens([USDC, PYUSD], { fields: ['name', 'symbol', 'decimals', 'supply', 'tokenProgram'] })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function getTokens<const F extends TokenField = DefaultTokenField>(mints: readonly string[], options?: GetTokensOptions<F>): Promise<TokenDetails<F>[]>;
|
|
50
|
+
export interface GetNftsOptions extends RequestOptions, ShortcutOptions {
|
|
51
|
+
}
|
|
52
|
+
/** 批量查 NFT 元数据(名称、uri、所属集合、版税、创作者等) */
|
|
53
|
+
export declare function getNfts(mints: readonly string[], options?: GetNftsOptions): Promise<NftDetails[]>;
|
|
54
|
+
/** 批量查 NFT 持有人 */
|
|
55
|
+
export declare function getNftOwners(mints: readonly string[], options?: GetNftsOptions): Promise<NftOwner[]>;
|
|
56
|
+
/** 查某地址持有的全部 NFT(需要支持 getTokenAccountsByOwner 的节点) */
|
|
57
|
+
export declare function getOwnerNfts(owner: string, options?: GetNftsOptions): Promise<NftDetails[]>;
|
|
58
|
+
export interface GetOwnerTokensOptions extends OwnerTokensOptions, ShortcutOptions {
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 列出持有人拥有的全部代币(SOL 在第一位),带 name / symbol,适合做资产列表。
|
|
62
|
+
* 需要支持 getTokenAccountsByOwner 的节点。
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* await getOwnerTokens(owner, { provider: 'https://my-rpc…' })
|
|
66
|
+
* // [{ token: NATIVE_MINT, native: true, name: 'Solana', symbol: 'SOL', formatted: '1.5', … }, { token: USDC, name: 'USD Coin', … }]
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export declare function getOwnerTokens(owner: string, options?: GetOwnerTokensOptions): Promise<OwnedToken[]>;
|
|
70
|
+
export interface WatchBalancesOptions extends Omit<GetBalancesOptions, 'signal' | 'minContextSlot'> {
|
|
71
|
+
/** 轮询间隔(毫秒),默认 15000;多处订阅同一份时取最小的 */
|
|
72
|
+
interval?: number;
|
|
73
|
+
/** 第一次拿到结果、以及之后余额(balance)变化时调用;previous 是上一次通知的结果(第一次为 null) */
|
|
74
|
+
onChange: (balances: TokenBalance[], previous: TokenBalance[] | null) => void;
|
|
75
|
+
/** 某次轮询失败时调用(之后照常继续轮询) */
|
|
76
|
+
onError?: (err: unknown) => void;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 轮询余额,多处订阅同一个钱包时合并成一份轮询,只在余额变化时通知。返回取消订阅的函数;最后一个订阅者取消时停止轮询。
|
|
80
|
+
* 参数同 getBalances(不支持 signal / minContextSlot);上一次请求没完成时不会发起下一次。
|
|
81
|
+
* 结果顺序按第一个订阅者传的 mints;新订阅者加入时如果已有结果,立即收到一次(previous 为 null)
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* const stop = watchBalances(owner, [NATIVE_MINT, USDC], { interval: 10_000, onChange: (list) => render(list) })
|
|
85
|
+
* stop()
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function watchBalances(owner: string, mints: readonly string[] | undefined, options: WatchBalancesOptions): () => void;
|
|
89
|
+
/** 测试用:按参数取(或创建)客户端,用于验证缓存复用(不在包的公开导出里) */
|
|
90
|
+
export declare function resolveClientForTest(options: ShortcutOptions): SolanaClient;
|
|
91
|
+
/** 测试用:清空客户端缓存 */
|
|
92
|
+
export declare function resetClientCache(): void;
|
|
93
|
+
//# sourceMappingURL=shortcuts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcuts.d.ts","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { SolanaClient, } from './client.js';
|
|
2
|
+
// 复用客户端:多次调用共享连接、批量合并与故障切换状态。按 “配置内容” 分组,组内 URL 按字符串、对象按引用(WeakMap)。
|
|
3
|
+
// 配置里有函数(如自定义 fetch)或节点数组含对象时不缓存。
|
|
4
|
+
const MAX_CACHED = 32;
|
|
5
|
+
const clientCache = new Map();
|
|
6
|
+
function setBounded(map, key, value) {
|
|
7
|
+
map.set(key, value);
|
|
8
|
+
if (map.size > MAX_CACHED) {
|
|
9
|
+
map.delete(map.keys().next().value);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function configKey(config) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(config, (_key, value) => {
|
|
15
|
+
if (typeof value === 'function') {
|
|
16
|
+
throw new Error('not comparable');
|
|
17
|
+
}
|
|
18
|
+
return value;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function sourceKey(source) {
|
|
26
|
+
if (source === undefined) {
|
|
27
|
+
return 'default';
|
|
28
|
+
}
|
|
29
|
+
if (typeof source === 'string') {
|
|
30
|
+
return `url:${source}`;
|
|
31
|
+
}
|
|
32
|
+
if (Array.isArray(source)) {
|
|
33
|
+
return source.every((item) => typeof item === 'string') ? `urls:${source.join('\n')}` : null;
|
|
34
|
+
}
|
|
35
|
+
return source;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 拆出节点参数和函数自己的参数(ownKeys),其余作为客户端配置;值为 undefined 的参数忽略。
|
|
39
|
+
* signal 总是作为函数自己的参数(放进客户端配置会让每次调用都新建客户端)
|
|
40
|
+
*/
|
|
41
|
+
function resolve(options, ownKeys = []) {
|
|
42
|
+
const { provider, ...others } = options;
|
|
43
|
+
const own = {};
|
|
44
|
+
const config = {};
|
|
45
|
+
for (const [key, value] of Object.entries(others)) {
|
|
46
|
+
if (value !== undefined) {
|
|
47
|
+
;
|
|
48
|
+
(ownKeys.includes(key) ? own : key === 'signal' ? {} : config)[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { client: getClient(provider, config), own: own };
|
|
52
|
+
}
|
|
53
|
+
function getClient(source, config) {
|
|
54
|
+
const cfg = configKey(config);
|
|
55
|
+
const key = sourceKey(source);
|
|
56
|
+
if (cfg === null || key === null) {
|
|
57
|
+
return new SolanaClient(source, config);
|
|
58
|
+
}
|
|
59
|
+
let group = clientCache.get(cfg);
|
|
60
|
+
if (!group) {
|
|
61
|
+
group = { byString: new Map(), byObject: new WeakMap() };
|
|
62
|
+
setBounded(clientCache, cfg, group);
|
|
63
|
+
}
|
|
64
|
+
let client = typeof key === 'string' ? group.byString.get(key) : group.byObject.get(key);
|
|
65
|
+
if (!client) {
|
|
66
|
+
client = new SolanaClient(source, config);
|
|
67
|
+
if (typeof key === 'string') {
|
|
68
|
+
setBounded(group.byString, key, client);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
group.byObject.set(key, client);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return client;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 批量查余额(SOL + SPL Token + Token-2022),返回原始余额、decimals 和换算后的数值。
|
|
78
|
+
*
|
|
79
|
+
* ```ts
|
|
80
|
+
* import { NATIVE_MINT, getBalances } from '@w3lib/solana-batch-call'
|
|
81
|
+
*
|
|
82
|
+
* await getBalances(owner, [NATIVE_MINT, USDC]) // 指定代币:免费公共节点也能用
|
|
83
|
+
* await getBalances(owner, [NATIVE_MINT, USDC], { symbol: true })
|
|
84
|
+
* await getBalances(owner, undefined, { tokenPrograms: ['spl'] }) // 不含 Token-2022 代币
|
|
85
|
+
* await getBalances(owner, [USDC], { accounts: 'all' }) // 算上非 ATA 账户:需要支持 getTokenAccountsByOwner 的节点
|
|
86
|
+
* await getBalances(owner, undefined, { provider: 'https://my-rpc…' }) // 全部持仓:需要支持 getTokenAccountsByOwner 的节点
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function getBalances(owner, mints, options = {}) {
|
|
90
|
+
const { client, own } = resolve(options, BALANCE_KEYS);
|
|
91
|
+
return client.balances(owner, mints, own);
|
|
92
|
+
}
|
|
93
|
+
const BALANCE_KEYS = ['symbol', 'scan', 'accounts', 'tokenPrograms', 'signal', 'minContextSlot', 'withSlot'];
|
|
94
|
+
/**
|
|
95
|
+
* 多个钱包一次查,结果与 queries 一一对应;ATA 模式下所有钱包合并成一次 getMultipleAccounts。参数同 getBalances。
|
|
96
|
+
*
|
|
97
|
+
* ```ts
|
|
98
|
+
* await getMultiBalances([{ owner: a, mints: [NATIVE_MINT, USDC] }, { owner: b, mints: [USDC] }])
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function getMultiBalances(queries, options = {}) {
|
|
102
|
+
const { client, own } = resolve(options, BALANCE_KEYS);
|
|
103
|
+
return client.multiBalances(queries, own);
|
|
104
|
+
}
|
|
105
|
+
/** 批量查多个地址的 SOL 余额(一次 getMultipleAccounts) */
|
|
106
|
+
export function getSolBalances(addresses, options = {}) {
|
|
107
|
+
const { client, own } = resolve(options, ['signal', 'minContextSlot', 'withSlot']);
|
|
108
|
+
return client.solBalances(addresses, own);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 批量查代币详情,字段可选(默认 name / symbol / decimals)。
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* await getTokens([USDC, PYUSD], { fields: ['name', 'symbol', 'decimals', 'supply', 'tokenProgram'] })
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export function getTokens(mints, options = {}) {
|
|
118
|
+
const { client, own } = resolve(options, ['fields', 'signal']);
|
|
119
|
+
return client.tokens(mints, own);
|
|
120
|
+
}
|
|
121
|
+
/** 批量查 NFT 元数据(名称、uri、所属集合、版税、创作者等) */
|
|
122
|
+
export function getNfts(mints, options = {}) {
|
|
123
|
+
const { client, own } = resolve(options, ['signal']);
|
|
124
|
+
return client.nfts(mints, own);
|
|
125
|
+
}
|
|
126
|
+
/** 批量查 NFT 持有人 */
|
|
127
|
+
export function getNftOwners(mints, options = {}) {
|
|
128
|
+
const { client, own } = resolve(options, ['signal']);
|
|
129
|
+
return client.nftOwners(mints, own);
|
|
130
|
+
}
|
|
131
|
+
/** 查某地址持有的全部 NFT(需要支持 getTokenAccountsByOwner 的节点) */
|
|
132
|
+
export function getOwnerNfts(owner, options = {}) {
|
|
133
|
+
const { client, own } = resolve(options, ['signal']);
|
|
134
|
+
return client.ownerNfts(owner, own);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 列出持有人拥有的全部代币(SOL 在第一位),带 name / symbol,适合做资产列表。
|
|
138
|
+
* 需要支持 getTokenAccountsByOwner 的节点。
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* await getOwnerTokens(owner, { provider: 'https://my-rpc…' })
|
|
142
|
+
* // [{ token: NATIVE_MINT, native: true, name: 'Solana', symbol: 'SOL', formatted: '1.5', … }, { token: USDC, name: 'USD Coin', … }]
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function getOwnerTokens(owner, options = {}) {
|
|
146
|
+
const { client, own } = resolve(options, ['metadata', 'includeNative', 'includeZero', 'includeNfts', 'tokenPrograms', 'signal', 'minContextSlot', 'withSlot']);
|
|
147
|
+
return client.ownerTokens(owner, own);
|
|
148
|
+
}
|
|
149
|
+
// 共享轮询:按客户端(同样的节点配置会复用同一个客户端)+ owner + mints(顺序无关)+ 影响结果的选项
|
|
150
|
+
const watches = new WeakMap();
|
|
151
|
+
/**
|
|
152
|
+
* 轮询余额,多处订阅同一个钱包时合并成一份轮询,只在余额变化时通知。返回取消订阅的函数;最后一个订阅者取消时停止轮询。
|
|
153
|
+
* 参数同 getBalances(不支持 signal / minContextSlot);上一次请求没完成时不会发起下一次。
|
|
154
|
+
* 结果顺序按第一个订阅者传的 mints;新订阅者加入时如果已有结果,立即收到一次(previous 为 null)
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* const stop = watchBalances(owner, [NATIVE_MINT, USDC], { interval: 10_000, onChange: (list) => render(list) })
|
|
158
|
+
* stop()
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function watchBalances(owner, mints, options) {
|
|
162
|
+
const { onChange, onError, interval = 15_000, ...rest } = options;
|
|
163
|
+
if (!(interval > 0)) {
|
|
164
|
+
throw new Error(`Invalid interval: ${interval}`);
|
|
165
|
+
}
|
|
166
|
+
const { client, own } = resolve(rest, ['symbol', 'scan', 'accounts', 'tokenPrograms', 'withSlot']);
|
|
167
|
+
const key = JSON.stringify([
|
|
168
|
+
owner,
|
|
169
|
+
mints === undefined ? null : [...new Set(mints)].sort(),
|
|
170
|
+
own.symbol ?? false,
|
|
171
|
+
own.scan ?? false,
|
|
172
|
+
own.accounts ?? 'ata',
|
|
173
|
+
own.tokenPrograms === undefined ? null : [...own.tokenPrograms].sort(),
|
|
174
|
+
own.withSlot ?? false,
|
|
175
|
+
]);
|
|
176
|
+
let group = watches.get(client);
|
|
177
|
+
if (!group) {
|
|
178
|
+
group = new Map();
|
|
179
|
+
watches.set(client, group);
|
|
180
|
+
}
|
|
181
|
+
let watch = group.get(key);
|
|
182
|
+
const watcher = { interval, onChange, onError };
|
|
183
|
+
if (!watch) {
|
|
184
|
+
watch = { client, owner, mints, options: own, watchers: new Set([watcher]), timer: null, running: false, stopped: false, last: null, lastKey: null };
|
|
185
|
+
group.set(key, watch);
|
|
186
|
+
poll(watch);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
watch.watchers.add(watcher);
|
|
190
|
+
const current = watch;
|
|
191
|
+
if (current.last) {
|
|
192
|
+
const last = current.last;
|
|
193
|
+
queueMicrotask(() => current.watchers.has(watcher) && safely(() => watcher.onChange(last, null)));
|
|
194
|
+
}
|
|
195
|
+
// 间隔变小:按新的间隔重新安排下一次
|
|
196
|
+
if (current.timer && interval < nextInterval(current, watcher)) {
|
|
197
|
+
clearTimeout(current.timer);
|
|
198
|
+
current.timer = null;
|
|
199
|
+
schedule(current);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const owned = watch;
|
|
203
|
+
const ownedGroup = group;
|
|
204
|
+
return () => {
|
|
205
|
+
if (!owned.watchers.delete(watcher) || owned.watchers.size) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
owned.stopped = true;
|
|
209
|
+
if (owned.timer) {
|
|
210
|
+
clearTimeout(owned.timer);
|
|
211
|
+
owned.timer = null;
|
|
212
|
+
}
|
|
213
|
+
if (ownedGroup.get(key) === owned) {
|
|
214
|
+
ownedGroup.delete(key);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/** 除了 except 以外订阅者的最小间隔(用于判断新订阅者是否让间隔变小) */
|
|
219
|
+
function nextInterval(watch, except) {
|
|
220
|
+
let min = Number.POSITIVE_INFINITY;
|
|
221
|
+
for (const w of watch.watchers) {
|
|
222
|
+
if (w !== except)
|
|
223
|
+
min = Math.min(min, w.interval);
|
|
224
|
+
}
|
|
225
|
+
return min;
|
|
226
|
+
}
|
|
227
|
+
function schedule(watch) {
|
|
228
|
+
if (watch.stopped || watch.timer || watch.running) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
watch.timer = setTimeout(() => poll(watch), nextInterval(watch));
|
|
232
|
+
}
|
|
233
|
+
function poll(watch) {
|
|
234
|
+
watch.timer = null;
|
|
235
|
+
if (watch.running || watch.stopped) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
watch.running = true;
|
|
239
|
+
watch.client
|
|
240
|
+
.balances(watch.owner, watch.mints, watch.options)
|
|
241
|
+
.then((balances) => {
|
|
242
|
+
// 只比较余额(slot 每次都会变)
|
|
243
|
+
const key = JSON.stringify(balances.map((b) => [b.token, b.balance, b.success]));
|
|
244
|
+
if (watch.stopped || key === watch.lastKey) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const previous = watch.last;
|
|
248
|
+
watch.last = balances;
|
|
249
|
+
watch.lastKey = key;
|
|
250
|
+
for (const w of [...watch.watchers]) {
|
|
251
|
+
safely(() => w.onChange(balances, previous));
|
|
252
|
+
}
|
|
253
|
+
}, (err) => {
|
|
254
|
+
if (!watch.stopped) {
|
|
255
|
+
for (const w of [...watch.watchers]) {
|
|
256
|
+
safely(() => w.onError?.(err));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
.finally(() => {
|
|
261
|
+
watch.running = false;
|
|
262
|
+
schedule(watch);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function safely(fn) {
|
|
266
|
+
try {
|
|
267
|
+
fn();
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
// 订阅者的回调出错不影响轮询
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** 测试用:按参数取(或创建)客户端,用于验证缓存复用(不在包的公开导出里) */
|
|
274
|
+
export function resolveClientForTest(options) {
|
|
275
|
+
return resolve(options).client;
|
|
276
|
+
}
|
|
277
|
+
/** 测试用:清空客户端缓存 */
|
|
278
|
+
export function resetClientCache() {
|
|
279
|
+
clientCache.clear();
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=shortcuts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcuts.js","sourceRoot":"","sources":["../src/shortcuts.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAQb,MAAM,aAAa,CAAA;AAapB,qEAAqE;AACrE,kCAAkC;AAClC,MAAM,UAAU,GAAG,EAAE,CAAA;AAOrB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAuB,CAAA;AAElD,SAAS,UAAU,CAAO,GAAc,EAAE,GAAM,EAAE,KAAQ;IACxD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACnB,IAAI,GAAG,CAAC,IAAI,GAAG,UAAU,EAAE,CAAC;QAC1B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAU,CAAC,CAAA;IAC1C,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,MAAoB;IACrC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,KAAc,EAAE,EAAE;YACrD,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAA;YACnC,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,MAAmC;IACpD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,OAAO,MAAM,EAAE,CAAA;IACxB,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC9F,CAAC;IACD,OAAO,MAAgB,CAAA;AACzB,CAAC;AAED;;;GAGG;AACH,SAAS,OAAO,CACd,OAAU,EACV,UAAwB,EAAE;IAE1B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,CAAA;IACvC,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,MAAM,MAAM,GAA4B,EAAE,CAAA;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,CAAC;YAAA,CAAE,OAA6B,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QACrG,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,QAAQ,EAAE,MAAsB,CAAC,EAAE,GAAG,EAAE,GAAiB,EAAE,CAAA;AACxF,CAAC;AAED,SAAS,SAAS,CAAC,MAAmC,EAAE,MAAoB;IAC1E,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IAC7B,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjC,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,CAAC;IACD,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,EAAE,QAAQ,EAAE,IAAI,GAAG,EAAE,EAAE,QAAQ,EAAE,IAAI,OAAO,EAAE,EAAE,CAAA;QACxD,UAAU,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;IACrC,CAAC;IACD,IAAI,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACxF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QACzC,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC5B,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,CAAA;QACzC,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QACjC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAID;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,KAAyB,EAAE,UAA8B,EAAE;IACpG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACtD,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,CAAU,CAAA;AAErH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgC,EAAE,UAA8B,EAAE;IACjG,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IACtD,OAAO,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;AAC3C,CAAC;AAID,8CAA8C;AAC9C,MAAM,UAAU,cAAc,CAAC,SAA4B,EAAE,UAAiC,EAAE;IAC9F,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,gBAAgB,EAAE,UAAU,CAAC,CAAC,CAAA;IAClF,OAAO,MAAM,CAAC,WAAW,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;AAC3C,CAAC;AAID;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAiD,KAAwB,EAAE,UAA+B,EAAE;IACnI,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;IAC9D,OAAO,MAAM,CAAC,MAAM,CAAI,KAAK,EAAE,GAAG,CAAC,CAAA;AACrC,CAAC;AAID,uCAAuC;AACvC,MAAM,UAAU,OAAO,CAAC,KAAwB,EAAE,UAA0B,EAAE;IAC5E,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACpD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;AAChC,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,YAAY,CAAC,KAAwB,EAAE,UAA0B,EAAE;IACjF,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACpD,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;AACrC,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,UAA0B,EAAE;IACtE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IACpD,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;AACrC,CAAC;AAID;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,UAAiC,EAAE;IAC/E,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,QAAQ,EAAE,gBAAgB,EAAE,UAAU,CAAC,CAAC,CAAA;IAC9J,OAAO,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;AACvC,CAAC;AA8BD,4DAA4D;AAC5D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAoC,CAAA;AAE/D;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,KAAoC,EAAE,OAA6B;IAC9G,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,GAAG,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAA;IACjE,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,EAAE,CAAC,CAAA;IAClD,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC,CAAA;IAClG,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QACzB,KAAK;QACL,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;QACvD,GAAG,CAAC,MAAM,IAAI,KAAK;QACnB,GAAG,CAAC,IAAI,IAAI,KAAK;QACjB,GAAG,CAAC,QAAQ,IAAI,KAAK;QACrB,GAAG,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE;QACtE,GAAG,CAAC,QAAQ,IAAI,KAAK;KACtB,CAAC,CAAA;IACF,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,IAAI,GAAG,EAAE,CAAA;QACjB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC5B,CAAC;IACD,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC1B,MAAM,OAAO,GAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;IACxD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;QACpJ,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACrB,IAAI,CAAC,KAAK,CAAC,CAAA;IACb,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAA;QACrB,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;YACzB,cAAc,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QACnG,CAAC;QACD,oBAAoB;QACpB,IAAI,OAAO,CAAC,KAAK,IAAI,QAAQ,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;YAC/D,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YAC3B,OAAO,CAAC,KAAK,GAAG,IAAI,CAAA;YACpB,QAAQ,CAAC,OAAO,CAAC,CAAA;QACnB,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAA;IACnB,MAAM,UAAU,GAAG,KAAK,CAAA;IACxB,OAAO,GAAG,EAAE;QACV,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC3D,OAAM;QACR,CAAC;QACD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;QACpB,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YACzB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAA;QACpB,CAAC;QACD,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC;YAClC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACxB,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,4CAA4C;AAC5C,SAAS,YAAY,CAAC,KAAY,EAAE,MAAgB;IAClD,IAAI,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAA;IAClC,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,MAAM;YAAE,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,QAAQ,CAAC,KAAY;IAC5B,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClD,OAAM;IACR,CAAC;IACD,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAA;AAClE,CAAC;AAED,SAAS,IAAI,CAAC,KAAY;IACxB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAA;IAClB,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,OAAM;IACR,CAAC;IACD,KAAK,CAAC,OAAO,GAAG,IAAI,CAAA;IACpB,KAAK,CAAC,MAAM;SACT,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;SACjD,IAAI,CACH,CAAC,QAAQ,EAAE,EAAE;QACX,oBAAoB;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QAChF,IAAI,KAAK,CAAC,OAAO,IAAI,GAAG,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;YAC3C,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAA;QAC3B,KAAK,CAAC,IAAI,GAAG,QAAQ,CAAA;QACrB,KAAK,CAAC,OAAO,GAAG,GAAG,CAAA;QACnB,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC,EACD,CAAC,GAAY,EAAE,EAAE;QACf,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;YAChC,CAAC;QACH,CAAC;IACH,CAAC,CACF;SACA,OAAO,CAAC,GAAG,EAAE;QACZ,KAAK,CAAC,OAAO,GAAG,KAAK,CAAA;QACrB,QAAQ,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;AACN,CAAC;AAED,SAAS,MAAM,CAAC,EAAc;IAC5B,IAAI,CAAC;QACH,EAAE,EAAE,CAAA;IACN,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;AACH,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,oBAAoB,CAAC,OAAwB;IAC3D,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CAAA;AAChC,CAAC;AAED,kBAAkB;AAClB,MAAM,UAAU,gBAAgB;IAC9B,WAAW,CAAC,KAAK,EAAE,CAAA;AACrB,CAAC"}
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type Cluster } from './constants.js';
|
|
2
|
+
import { type FallbackOptions, type HttpRpcOptions, type RpcTransport } from './rpc.js';
|
|
3
|
+
/**
|
|
4
|
+
* 节点来源(也可以传数组:主节点 + 备用节点):
|
|
5
|
+
* - RPC URL 字符串
|
|
6
|
+
* - @solana/web3.js 的 Connection(使用其 rpcEndpoint)
|
|
7
|
+
* - 自定义传输:实现 `request(method, params)` 的对象(如包一层 @solana/kit 的 rpc)
|
|
8
|
+
*/
|
|
9
|
+
export type RpcSource = string | RpcTransport | {
|
|
10
|
+
rpcEndpoint: string;
|
|
11
|
+
};
|
|
12
|
+
export interface SourceOptions extends HttpRpcOptions, FallbackOptions {
|
|
13
|
+
}
|
|
14
|
+
export declare function genesisOf(transport: RpcTransport): Promise<string>;
|
|
15
|
+
export declare class NetworkMismatchError extends Error {
|
|
16
|
+
constructor(expected: string, actual: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 校验节点所在的网络(创世区块哈希),避免把 devnet 的数据当成 mainnet 的返回。
|
|
20
|
+
* 校验与请求并行发出,不增加延迟;结果缓存,每个节点只多一次请求;识别失败时不拦截,并在一段时间内不再重试。
|
|
21
|
+
*/
|
|
22
|
+
export declare class NetworkCheckedRpc implements RpcTransport {
|
|
23
|
+
#private;
|
|
24
|
+
readonly inner: RpcTransport;
|
|
25
|
+
readonly expected: () => Promise<string>;
|
|
26
|
+
constructor(inner: RpcTransport, expected: () => Promise<string>);
|
|
27
|
+
request<T = unknown>(method: string, params?: readonly unknown[]): Promise<T>;
|
|
28
|
+
}
|
|
29
|
+
export interface ResolvedSource {
|
|
30
|
+
transport: RpcTransport;
|
|
31
|
+
/** 期望的创世区块哈希:指定了 cluster 时取对应值,否则取第一个响应节点的 */
|
|
32
|
+
genesis: () => Promise<string>;
|
|
33
|
+
}
|
|
34
|
+
export declare function resolveSource(source: RpcSource | readonly RpcSource[] | undefined, cluster: Cluster | undefined, options?: SourceOptions): ResolvedSource;
|
|
35
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAC/E,OAAO,EAAwB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAA;AAE7G;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvE,MAAM,WAAW,aAAc,SAAQ,cAAc,EAAE,eAAe;CAAG;AAoBzE,wBAAgB,SAAS,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAalE;AAKD,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI7C;AAED;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,YAAY;;IAIlD,QAAQ,CAAC,KAAK,EAAE,YAAY;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;gBAD/B,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;IAqBpC,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;CAUpF;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,YAAY,CAAA;IACvB,8CAA8C;IAC9C,OAAO,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/B;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE,GAAG,SAAS,EACpD,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,OAAO,GAAE,aAAkB,GAC1B,cAAc,CAwChB"}
|
package/dist/source.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { DEFAULT_RPC_URLS, GENESIS_HASHES } from './constants.js';
|
|
2
|
+
import { FallbackRpc, HttpRpc } from './rpc.js';
|
|
3
|
+
/** @param failFast 多个自定义节点时为 true:不在单节点内重试 429,直接切到下一个节点 */
|
|
4
|
+
function toTransport(source, options, failFast) {
|
|
5
|
+
if (typeof source === 'string') {
|
|
6
|
+
return new HttpRpc(source, { ...options, retries: failFast ? 0 : options.retries });
|
|
7
|
+
}
|
|
8
|
+
if (typeof source.request === 'function') {
|
|
9
|
+
return source;
|
|
10
|
+
}
|
|
11
|
+
const endpoint = source.rpcEndpoint;
|
|
12
|
+
if (typeof endpoint === 'string') {
|
|
13
|
+
return new HttpRpc(endpoint, { ...options, retries: failFast ? 0 : options.retries });
|
|
14
|
+
}
|
|
15
|
+
throw new Error('Unsupported RPC source: expected a URL, a web3.js Connection or an object with request(method, params)');
|
|
16
|
+
}
|
|
17
|
+
// 各节点的创世区块哈希缓存:失败(含超时)时删除,下次重试
|
|
18
|
+
const genesisCache = new WeakMap();
|
|
19
|
+
export function genesisOf(transport) {
|
|
20
|
+
let cached = genesisCache.get(transport);
|
|
21
|
+
if (!cached) {
|
|
22
|
+
const promise = transport.request('getGenesisHash');
|
|
23
|
+
genesisCache.set(transport, promise);
|
|
24
|
+
promise.catch(() => {
|
|
25
|
+
if (genesisCache.get(transport) === promise) {
|
|
26
|
+
genesisCache.delete(transport);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
cached = promise;
|
|
30
|
+
}
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
/** 识别失败后多久内不再重试(期间请求照常发出,不做校验) */
|
|
34
|
+
const CHECK_RETRY_INTERVAL = 60_000;
|
|
35
|
+
export class NetworkMismatchError extends Error {
|
|
36
|
+
constructor(expected, actual) {
|
|
37
|
+
super(`Network mismatch: expected genesis ${expected}, but the node is on ${actual}`);
|
|
38
|
+
this.name = 'NetworkMismatchError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 校验节点所在的网络(创世区块哈希),避免把 devnet 的数据当成 mainnet 的返回。
|
|
43
|
+
* 校验与请求并行发出,不增加延迟;结果缓存,每个节点只多一次请求;识别失败时不拦截,并在一段时间内不再重试。
|
|
44
|
+
*/
|
|
45
|
+
export class NetworkCheckedRpc {
|
|
46
|
+
inner;
|
|
47
|
+
expected;
|
|
48
|
+
#checkFailedAt = 0;
|
|
49
|
+
constructor(inner, expected) {
|
|
50
|
+
this.inner = inner;
|
|
51
|
+
this.expected = expected;
|
|
52
|
+
}
|
|
53
|
+
async #check() {
|
|
54
|
+
if (this.#checkFailedAt && Date.now() - this.#checkFailedAt < CHECK_RETRY_INTERVAL) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
let expected;
|
|
58
|
+
let actual;
|
|
59
|
+
try {
|
|
60
|
+
;
|
|
61
|
+
[expected, actual] = await Promise.all([this.expected(), genesisOf(this.inner)]);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
this.#checkFailedAt = Date.now();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.#checkFailedAt = 0;
|
|
68
|
+
if (expected !== actual) {
|
|
69
|
+
throw new NetworkMismatchError(expected, actual);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async request(method, params) {
|
|
73
|
+
const [checked, result] = await Promise.allSettled([this.#check(), this.inner.request(method, params)]);
|
|
74
|
+
if (checked.status === 'rejected') {
|
|
75
|
+
throw checked.reason;
|
|
76
|
+
}
|
|
77
|
+
if (result.status === 'rejected') {
|
|
78
|
+
throw result.reason;
|
|
79
|
+
}
|
|
80
|
+
return result.value;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function resolveSource(source, cluster, options = {}) {
|
|
84
|
+
const isDefault = source === undefined;
|
|
85
|
+
const list = isDefault ? DEFAULT_RPC_URLS[cluster ?? 'mainnet'] : Array.isArray(source) ? source : [source];
|
|
86
|
+
if (!list.length) {
|
|
87
|
+
throw new Error('RPC source list is empty');
|
|
88
|
+
}
|
|
89
|
+
const multiple = list.length > 1;
|
|
90
|
+
// 内置节点保留 429 重试:浏览器里官方节点总是 403,实际只有 publicnode 一个可用节点
|
|
91
|
+
const raw = list.map((item) => toTransport(item, options, multiple && !isDefault));
|
|
92
|
+
let detecting = null;
|
|
93
|
+
const genesis = () => {
|
|
94
|
+
if (cluster) {
|
|
95
|
+
return Promise.resolve(GENESIS_HASHES[cluster]);
|
|
96
|
+
}
|
|
97
|
+
if (!detecting) {
|
|
98
|
+
const promise = (async () => {
|
|
99
|
+
let lastError;
|
|
100
|
+
for (const node of raw) {
|
|
101
|
+
try {
|
|
102
|
+
return await genesisOf(node);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
lastError = err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw lastError;
|
|
109
|
+
})();
|
|
110
|
+
detecting = promise;
|
|
111
|
+
promise.catch(() => {
|
|
112
|
+
if (detecting === promise)
|
|
113
|
+
detecting = null;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return detecting;
|
|
117
|
+
};
|
|
118
|
+
// 内置节点本来就是按 cluster 选的,不校验;单个节点且没指定 cluster 时,期望值就是它自己,也不需要校验
|
|
119
|
+
const check = !isDefault && (multiple || cluster !== undefined);
|
|
120
|
+
const nodes = check ? raw.map((node) => new NetworkCheckedRpc(node, genesis)) : raw;
|
|
121
|
+
// 单个节点也包一层 FallbackRpc:统一 onRequest 事件和健康状态(单节点失败时抛原始错误)
|
|
122
|
+
return { transport: new FallbackRpc(nodes, options), genesis };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"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"}
|