@w3lib/solana-batch-call 0.5.0 → 0.7.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.
Files changed (220) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +129 -9
  3. package/dist/accountReader.d.ts +57 -0
  4. package/dist/accountReader.d.ts.map +1 -0
  5. package/dist/accountReader.js +149 -0
  6. package/dist/accountReader.js.map +1 -0
  7. package/dist/address.d.ts +9 -0
  8. package/dist/address.d.ts.map +1 -1
  9. package/dist/address.js +36 -6
  10. package/dist/address.js.map +1 -1
  11. package/dist/approvals.d.ts +5 -3
  12. package/dist/approvals.d.ts.map +1 -1
  13. package/dist/approvals.js +25 -36
  14. package/dist/approvals.js.map +1 -1
  15. package/dist/balances.d.ts +178 -0
  16. package/dist/balances.d.ts.map +1 -0
  17. package/dist/balances.js +524 -0
  18. package/dist/balances.js.map +1 -0
  19. package/dist/batchPlan.d.ts +52 -0
  20. package/dist/batchPlan.d.ts.map +1 -0
  21. package/dist/batchPlan.js +64 -0
  22. package/dist/batchPlan.js.map +1 -0
  23. package/dist/bytes.d.ts +31 -0
  24. package/dist/bytes.d.ts.map +1 -0
  25. package/dist/bytes.js +74 -0
  26. package/dist/bytes.js.map +1 -0
  27. package/dist/claim.d.ts +163 -0
  28. package/dist/claim.d.ts.map +1 -0
  29. package/dist/claim.js +197 -0
  30. package/dist/claim.js.map +1 -0
  31. package/dist/client.d.ts +26 -41
  32. package/dist/client.d.ts.map +1 -1
  33. package/dist/client.js +41 -986
  34. package/dist/client.js.map +1 -1
  35. package/dist/clientConfig.d.ts +11 -0
  36. package/dist/clientConfig.d.ts.map +1 -0
  37. package/dist/clientConfig.js +3 -0
  38. package/dist/clientConfig.js.map +1 -0
  39. package/dist/decode.d.ts +22 -0
  40. package/dist/decode.d.ts.map +1 -0
  41. package/dist/decode.js +160 -0
  42. package/dist/decode.js.map +1 -0
  43. package/dist/describe.d.ts +26 -0
  44. package/dist/describe.d.ts.map +1 -0
  45. package/dist/describe.js +58 -0
  46. package/dist/describe.js.map +1 -0
  47. package/dist/fees.d.ts.map +1 -1
  48. package/dist/fees.js +2 -12
  49. package/dist/fees.js.map +1 -1
  50. package/dist/history.d.ts +125 -0
  51. package/dist/history.d.ts.map +1 -0
  52. package/dist/history.js +220 -0
  53. package/dist/history.js.map +1 -0
  54. package/dist/historyHelius.d.ts +52 -0
  55. package/dist/historyHelius.d.ts.map +1 -0
  56. package/dist/historyHelius.js +125 -0
  57. package/dist/historyHelius.js.map +1 -0
  58. package/dist/index.d.ts +3 -3
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +1 -1
  61. package/dist/index.js.map +1 -1
  62. package/dist/layout.d.ts +21 -0
  63. package/dist/layout.d.ts.map +1 -1
  64. package/dist/layout.js +50 -69
  65. package/dist/layout.js.map +1 -1
  66. package/dist/liquidity/common.js +2 -2
  67. package/dist/liquidity/common.js.map +1 -1
  68. package/dist/liquidity/orca.d.ts +6 -0
  69. package/dist/liquidity/orca.d.ts.map +1 -1
  70. package/dist/liquidity/orca.js +14 -3
  71. package/dist/liquidity/orca.js.map +1 -1
  72. package/dist/liquidity/raydiumClmm.d.ts +6 -0
  73. package/dist/liquidity/raydiumClmm.d.ts.map +1 -1
  74. package/dist/liquidity/raydiumClmm.js +12 -5
  75. package/dist/liquidity/raydiumClmm.js.map +1 -1
  76. package/dist/lp.d.ts +55 -12
  77. package/dist/lp.d.ts.map +1 -1
  78. package/dist/lp.js +158 -70
  79. package/dist/lp.js.map +1 -1
  80. package/dist/lpMath.d.ts +22 -0
  81. package/dist/lpMath.d.ts.map +1 -1
  82. package/dist/lpMath.js +21 -0
  83. package/dist/lpMath.js.map +1 -1
  84. package/dist/portfolio.d.ts.map +1 -1
  85. package/dist/portfolio.js +3 -8
  86. package/dist/portfolio.js.map +1 -1
  87. package/dist/programs.d.ts +19 -0
  88. package/dist/programs.d.ts.map +1 -0
  89. package/dist/programs.js +175 -0
  90. package/dist/programs.js.map +1 -0
  91. package/dist/reclaim.d.ts +192 -0
  92. package/dist/reclaim.d.ts.map +1 -0
  93. package/dist/reclaim.js +399 -0
  94. package/dist/reclaim.js.map +1 -0
  95. package/dist/risk.js +2 -2
  96. package/dist/risk.js.map +1 -1
  97. package/dist/rpc.d.ts +30 -7
  98. package/dist/rpc.d.ts.map +1 -1
  99. package/dist/rpc.js +404 -111
  100. package/dist/rpc.js.map +1 -1
  101. package/dist/simulate.d.ts +106 -0
  102. package/dist/simulate.d.ts.map +1 -0
  103. package/dist/simulate.js +395 -0
  104. package/dist/simulate.js.map +1 -0
  105. package/dist/source.d.ts +8 -4
  106. package/dist/source.d.ts.map +1 -1
  107. package/dist/source.js +11 -4
  108. package/dist/source.js.map +1 -1
  109. package/dist/subpaths/approvals.d.ts +3 -3
  110. package/dist/subpaths/approvals.d.ts.map +1 -1
  111. package/dist/subpaths/approvals.js.map +1 -1
  112. package/dist/subpaths/claim.d.ts +31 -0
  113. package/dist/subpaths/claim.d.ts.map +1 -0
  114. package/dist/subpaths/claim.js +31 -0
  115. package/dist/subpaths/claim.js.map +1 -0
  116. package/dist/subpaths/history.d.ts +21 -0
  117. package/dist/subpaths/history.d.ts.map +1 -0
  118. package/dist/subpaths/history.js +22 -0
  119. package/dist/subpaths/history.js.map +1 -0
  120. package/dist/subpaths/lp.d.ts +2 -2
  121. package/dist/subpaths/lp.d.ts.map +1 -1
  122. package/dist/subpaths/lp.js +2 -2
  123. package/dist/subpaths/lp.js.map +1 -1
  124. package/dist/subpaths/reclaim.d.ts +34 -0
  125. package/dist/subpaths/reclaim.d.ts.map +1 -0
  126. package/dist/subpaths/reclaim.js +34 -0
  127. package/dist/subpaths/reclaim.js.map +1 -0
  128. package/dist/subpaths/simulate.d.ts +22 -0
  129. package/dist/subpaths/simulate.d.ts.map +1 -0
  130. package/dist/subpaths/simulate.js +23 -0
  131. package/dist/subpaths/simulate.js.map +1 -0
  132. package/dist/swap/index.js +3 -3
  133. package/dist/swap/index.js.map +1 -1
  134. package/dist/tokenDetails.d.ts +12 -0
  135. package/dist/tokenDetails.d.ts.map +1 -0
  136. package/dist/tokenDetails.js +196 -0
  137. package/dist/tokenDetails.js.map +1 -0
  138. package/dist/tokenMeta.d.ts +63 -0
  139. package/dist/tokenMeta.d.ts.map +1 -0
  140. package/dist/tokenMeta.js +226 -0
  141. package/dist/tokenMeta.js.map +1 -0
  142. package/dist/tradeCheck.js +3 -3
  143. package/dist/tradeCheck.js.map +1 -1
  144. package/dist/util.d.ts +22 -0
  145. package/dist/util.d.ts.map +1 -0
  146. package/dist/util.js +107 -0
  147. package/dist/util.js.map +1 -0
  148. package/dist/write/chain.d.ts +51 -0
  149. package/dist/write/chain.d.ts.map +1 -0
  150. package/dist/write/chain.js +184 -0
  151. package/dist/write/chain.js.map +1 -0
  152. package/dist/write/collect.js +2 -2
  153. package/dist/write/collect.js.map +1 -1
  154. package/dist/write/errors.d.ts +10 -0
  155. package/dist/write/errors.d.ts.map +1 -0
  156. package/dist/write/errors.js +77 -0
  157. package/dist/write/errors.js.map +1 -0
  158. package/dist/write/instructions.d.ts +2 -0
  159. package/dist/write/instructions.d.ts.map +1 -1
  160. package/dist/write/instructions.js +16 -0
  161. package/dist/write/instructions.js.map +1 -1
  162. package/dist/write/pack.d.ts +28 -0
  163. package/dist/write/pack.d.ts.map +1 -0
  164. package/dist/write/pack.js +125 -0
  165. package/dist/write/pack.js.map +1 -0
  166. package/dist/write/send.d.ts +23 -9
  167. package/dist/write/send.d.ts.map +1 -1
  168. package/dist/write/send.js +53 -351
  169. package/dist/write/send.js.map +1 -1
  170. package/dist/write/tracker.d.ts +24 -0
  171. package/dist/write/tracker.d.ts.map +1 -0
  172. package/dist/write/tracker.js +129 -0
  173. package/dist/write/tracker.js.map +1 -0
  174. package/package.json +21 -1
  175. package/src/accountReader.ts +187 -0
  176. package/src/address.ts +41 -6
  177. package/src/approvals.ts +36 -44
  178. package/src/balances.ts +678 -0
  179. package/src/batchPlan.ts +117 -0
  180. package/src/bytes.ts +89 -0
  181. package/src/claim.ts +327 -0
  182. package/src/client.ts +59 -1239
  183. package/src/clientConfig.ts +12 -0
  184. package/src/decode.ts +176 -0
  185. package/src/describe.ts +77 -0
  186. package/src/fees.ts +2 -9
  187. package/src/history.ts +325 -0
  188. package/src/historyHelius.ts +136 -0
  189. package/src/index.ts +13 -1
  190. package/src/layout.ts +63 -82
  191. package/src/liquidity/common.ts +2 -2
  192. package/src/liquidity/orca.ts +15 -5
  193. package/src/liquidity/raydiumClmm.ts +13 -5
  194. package/src/lp.ts +226 -75
  195. package/src/lpMath.ts +38 -0
  196. package/src/portfolio.ts +8 -11
  197. package/src/programs.ts +216 -0
  198. package/src/reclaim.ts +550 -0
  199. package/src/risk.ts +2 -2
  200. package/src/rpc.ts +476 -118
  201. package/src/simulate.ts +517 -0
  202. package/src/source.ts +14 -5
  203. package/src/subpaths/approvals.ts +3 -2
  204. package/src/subpaths/claim.ts +54 -0
  205. package/src/subpaths/history.ts +38 -0
  206. package/src/subpaths/lp.ts +5 -1
  207. package/src/subpaths/reclaim.ts +58 -0
  208. package/src/subpaths/simulate.ts +37 -0
  209. package/src/swap/index.ts +3 -3
  210. package/src/tokenDetails.ts +240 -0
  211. package/src/tokenMeta.ts +270 -0
  212. package/src/tradeCheck.ts +3 -3
  213. package/src/util.ts +110 -0
  214. package/src/write/chain.ts +218 -0
  215. package/src/write/collect.ts +2 -2
  216. package/src/write/errors.ts +82 -0
  217. package/src/write/instructions.ts +17 -0
  218. package/src/write/pack.ts +142 -0
  219. package/src/write/send.ts +74 -407
  220. package/src/write/tracker.ts +152 -0
package/dist/lp.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { RequestOptions, SolanaClient } from './client.js';
2
2
  import { type AccountInfo } from './layout.js';
3
+ import { Bytes } from './bytes.js';
4
+ import { type TickFeeData } from './lpMath.js';
3
5
  import { type SolanaPriceSource } from './price.js';
4
6
  export declare const RAYDIUM_CLMM_PROGRAM_ID = "CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK";
5
7
  export declare const ORCA_WHIRLPOOL_PROGRAM_ID = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";
@@ -77,6 +79,23 @@ export interface SolanaLpClmmPosition extends SolanaLpPositionBase {
77
79
  fees1Formatted: string | null;
78
80
  /** 开启 prices 时:未领取手续费的美元价值(手续费为 null 或缺价格时为 null) */
79
81
  feesUsd?: number | null;
82
+ /** 开启 rewards 时:未领取的挖矿奖励(池子里已初始化的每个奖励一项,数量为 0 的也在) */
83
+ rewards?: SolanaLpReward[];
84
+ /** 开启 rewards 和 prices 时:未领取挖矿奖励的美元价值(有数量的奖励缺价格或读不到时为 null) */
85
+ rewardsUsd?: number | null;
86
+ }
87
+ /** 集中流动性仓位未领取的挖矿奖励(Raydium CLMM reward_infos / Orca Whirlpool reward_infos) */
88
+ export interface SolanaLpReward {
89
+ /** 奖励在池子里的位置(0~2) */
90
+ index: number;
91
+ mint: string;
92
+ symbol: string | null;
93
+ decimals: number | null;
94
+ /** 未领取的数量(最小单位):已记账的 + 按池子最新状态结算的新增;读不到两端的 tick 数据时为 null */
95
+ amount: string | null;
96
+ formatted: string | null;
97
+ /** 开启 prices 时:美元价值(没有价格时为 null) */
98
+ usd?: number | null;
80
99
  }
81
100
  export type SolanaLpPosition = SolanaLpAmmPosition | SolanaLpClmmPosition;
82
101
  export interface LpPositionsOptions extends RequestOptions {
@@ -94,6 +113,13 @@ export interface LpPositionsOptions extends RequestOptions {
94
113
  pools?: readonly string[];
95
114
  /** 返回已清空的仓位(流动性为 0、手续费也领完,NFT 还在)。默认 false */
96
115
  includeClosed?: boolean;
116
+ /**
117
+ * 集中流动性仓位同时算未领取的挖矿奖励(Raydium CLMM 农场奖励、Orca 的奖励 slot),放在 rewards 字段,照搬程序的结算:
118
+ * 池子的奖励增长按时间摊给当前流动性,再按仓位区间内的增长计算。默认 false。有奖励没领的仓位不算“已清空”
119
+ */
120
+ rewards?: boolean;
121
+ /** rewards 结算到哪个时刻(unix 秒),默认当前时间;链上用的是区块时间,差几秒影响很小 */
122
+ now?: number;
97
123
  /** 查美元价值:true 用 DefiLlama,也可以传自己的 SolanaPriceSource(如 /price 的 combinePrices([defillamaPrices(), onchainPrices()]))。默认不查 */
98
124
  prices?: boolean | SolanaPriceSource;
99
125
  /**
@@ -104,19 +130,27 @@ export interface LpPositionsOptions extends RequestOptions {
104
130
  /** 查价格用的 fetch,默认全局 fetch */
105
131
  fetch?: typeof fetch;
106
132
  }
107
- /** 小端读取账户数据(添加 / 移除流动性也用) */
108
- export declare class Bytes {
109
- #private;
110
- readonly data: Uint8Array;
111
- constructor(data: Uint8Array);
112
- u8(o: number): number;
113
- u16(o: number): number;
114
- u32(o: number): number;
115
- i32(o: number): number;
116
- u64(o: number): bigint;
117
- u128(o: number): bigint;
118
- key(o: number): string;
133
+ export { Bytes };
134
+ export interface ClmmPositionState {
135
+ protocol: 'raydium-clmm' | 'orca-whirlpool';
136
+ address: string;
137
+ mint: string;
138
+ pool: string;
139
+ tickLower: number;
140
+ tickUpper: number;
141
+ liquidity: bigint;
142
+ growthLast0: bigint;
143
+ growthLast1: bigint;
144
+ owed0: bigint;
145
+ owed1: bigint;
146
+ /** 三个挖矿奖励:上次记录的区间内增长、已记账未领取的数量 */
147
+ rewards: Array<{
148
+ growthLast: bigint;
149
+ owed: bigint;
150
+ }>;
119
151
  }
152
+ /** 按程序和账户大小解析仓位账户(Raydium PersonalPositionState 281 字节、Orca Position 216 字节);不是仓位账户时为 null */
153
+ export declare function parseClmmPosition(protocol: 'raydium-clmm' | 'orca-whirlpool', address: string, account: AccountInfo): ClmmPositionState | null;
120
154
  export interface AmmPoolState {
121
155
  protocol: 'raydium-amm' | 'raydium-cpmm';
122
156
  address: string;
@@ -144,6 +178,8 @@ export declare function orcaTickArray(pool: string, tick: number, spacing: numbe
144
178
  address: string;
145
179
  start: number;
146
180
  };
181
+ export declare function raydiumTick(account: AccountInfo, start: number, tick: number, spacing: number): TickFeeData | null;
182
+ export declare function orcaTick(account: AccountInfo, start: number, tick: number, spacing: number): TickFeeData | null;
147
183
  /**
148
184
  * 查一个地址的 LP 仓位:Raydium CLMM / Orca Whirlpool 的仓位 NFT,Raydium AMM v4 / CPMM 的 LP 代币。
149
185
  * 返回两种代币的数量、未领取的手续费(集中流动性),可选美元价值。
@@ -153,9 +189,16 @@ export declare function orcaTickArray(pool: string, tick: number, spacing: numbe
153
189
  * - 质押在农场里的 LP、Orca 的仓位组合(Position Bundle)、锁仓的 Raydium 仓位不在用户名下,查不到
154
190
  */
155
191
  export declare function lpPositions(client: SolanaClient, owner: string, options?: LpPositionsOptions): Promise<SolanaLpPosition[]>;
192
+ /**
193
+ * 方法不可用(见 methodUnavailable)时把错误换成说明怎么绕过的 Error(cause 为原错误,hint 拿到原错误的 message);
194
+ * 取消、限频、超时等原样抛出(保留错误类型)。/lp、/approvals、资产总值共用
195
+ */
196
+ export declare function withMethodHint<T>(promise: Promise<T>, signal: AbortSignal | undefined, hint: (detail: string) => string): Promise<T>;
156
197
  /**
157
198
  * 节点不提供这个方法(方法不存在、被拦截、需要付费 / Token、403 等):这时报错里说明怎么绕过。
158
199
  * 多节点时所有节点都是这个原因才算;限频、超时、网络不对等原样抛出(资产总值 portfolio 也用)
159
200
  */
160
201
  export declare function methodUnavailable(err: unknown): boolean;
202
+ /** 测试用:清空 LP mint → 池子地址的缓存 */
203
+ export declare function resetLpPoolCache(): void;
161
204
  //# sourceMappingURL=lp.d.ts.map
package/dist/lp.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lp.d.ts","sourceRoot":"","sources":["../src/lp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lp.d.ts","sourceRoot":"","sources":["../src/lp.ts"],"names":[],"mappings":"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"}
package/dist/lp.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { sha256 } from '@noble/hashes/sha2.js';
2
2
  import { base64 } from '@scure/base';
3
- import { decodeAddress, encodeAddress, findProgramAddress, isAddress } from './address.js';
3
+ import { decodeAddress, findProgramAddress, InvalidAddressError, isAddress } from './address.js';
4
4
  import { formatUnits, parseMint, parseTokenAccount } from './layout.js';
5
5
  import { AllNodesFailedError, HttpError, RpcError } from './rpc.js';
6
- import { getAmountsForLiquidity, getFeeGrowthInside, getFeesOwed, orcaSqrtPriceAtTick, raydiumSqrtPriceAtTick, sqrtPriceToPrice, tickToPrice } from './lpMath.js';
6
+ import { boundedSet, mapPool } from './util.js';
7
+ import { Bytes } from './bytes.js';
8
+ import { getAmountsForLiquidity, getFeeGrowthInside, getFeesOwed, getRewardGrowthInside, orcaSqrtPriceAtTick, raydiumSqrtPriceAtTick, rewardGrowthGlobal, sqrtPriceToPrice, tickToPrice, } from './lpMath.js';
7
9
  import { defillamaPrices } from './price.js';
8
10
  export const RAYDIUM_CLMM_PROGRAM_ID = 'CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK';
9
11
  export const ORCA_WHIRLPOOL_PROGRAM_ID = 'whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc';
@@ -24,36 +26,9 @@ export { defillamaPrices } from './price.js';
24
26
  // ---------------------------------------------------------------------------
25
27
  // 账户布局(各程序的 Anchor / 原生账户,偏移量含 8 字节判别符)
26
28
  // ---------------------------------------------------------------------------
27
- /** 小端读取账户数据(添加 / 移除流动性也用) */
28
- export class Bytes {
29
- data;
30
- #view;
31
- constructor(data) {
32
- this.data = data;
33
- this.#view = new DataView(data.buffer, data.byteOffset, data.byteLength);
34
- }
35
- u8(o) {
36
- return this.#view.getUint8(o);
37
- }
38
- u16(o) {
39
- return this.#view.getUint16(o, true);
40
- }
41
- u32(o) {
42
- return this.#view.getUint32(o, true);
43
- }
44
- i32(o) {
45
- return this.#view.getInt32(o, true);
46
- }
47
- u64(o) {
48
- return this.#view.getBigUint64(o, true);
49
- }
50
- u128(o) {
51
- return (this.#view.getBigUint64(o + 8, true) << 64n) | this.#view.getBigUint64(o, true);
52
- }
53
- key(o) {
54
- return encodeAddress(this.data.subarray(o, o + 32));
55
- }
56
- }
29
+ // 小端读取账户数据:公共的 Bytes(添加 / 移除流动性也从这里引用)
30
+ export { Bytes };
31
+ const ZERO_ADDRESS = '11111111111111111111111111111111';
57
32
  const RAYDIUM_POSITION_SIZE = 281;
58
33
  const ORCA_POSITION_SIZE = 216;
59
34
  /** 池子账户至少要读到的长度(Raydium PoolState 实际 1544 字节,Orca Whirlpool 653 字节) */
@@ -65,6 +40,7 @@ const RAYDIUM_TICK_ARRAY_SIZE = 60;
65
40
  const RAYDIUM_TICK_SIZE = 168;
66
41
  const ORCA_TICK_ARRAY_SIZE = 88;
67
42
  const ORCA_TICK_SIZE = 113;
43
+ const positionRewards = (b, offset) => [0, 1, 2].map((i) => ({ growthLast: b.u128(offset + i * 24), owed: b.u64(offset + i * 24 + 16) }));
68
44
  function raydiumPosition(address, b) {
69
45
  return {
70
46
  protocol: 'raydium-clmm',
@@ -78,6 +54,7 @@ function raydiumPosition(address, b) {
78
54
  growthLast1: b.u128(113),
79
55
  owed0: b.u64(129),
80
56
  owed1: b.u64(137),
57
+ rewards: positionRewards(b, 145),
81
58
  };
82
59
  }
83
60
  function orcaPosition(address, b) {
@@ -93,8 +70,47 @@ function orcaPosition(address, b) {
93
70
  owed0: b.u64(112),
94
71
  growthLast1: b.u128(120),
95
72
  owed1: b.u64(136),
73
+ rewards: positionRewards(b, 144),
96
74
  };
97
75
  }
76
+ /** 按程序和账户大小解析仓位账户(Raydium PersonalPositionState 281 字节、Orca Position 216 字节);不是仓位账户时为 null */
77
+ export function parseClmmPosition(protocol, address, account) {
78
+ if (protocol === 'raydium-clmm' && account.owner === RAYDIUM_CLMM_PROGRAM_ID && account.data.length === RAYDIUM_POSITION_SIZE) {
79
+ return raydiumPosition(address, new Bytes(account.data));
80
+ }
81
+ if (protocol === 'orca-whirlpool' && account.owner === ORCA_WHIRLPOOL_PROGRAM_ID && account.data.length === ORCA_POSITION_SIZE) {
82
+ return orcaPosition(address, new Bytes(account.data));
83
+ }
84
+ return null;
85
+ }
86
+ const U64_MAX_VALUE = (1n << 64n) - 1n;
87
+ /** Raydium reward_infos(每个 169 字节,从 397 开始):open_time +1、end_time +9、last_update_time +17、emissions_per_second_x64 +25、mint +57、growth_global +153 */
88
+ function raydiumRewards(b) {
89
+ if (b.data.length < 397 + 3 * 169) {
90
+ return [];
91
+ }
92
+ return [0, 1, 2].flatMap((index) => {
93
+ const o = 397 + index * 169;
94
+ const mint = b.key(o + 57);
95
+ return mint === ZERO_ADDRESS
96
+ ? []
97
+ : [{ index, mint, emission: { initialized: true, openTime: b.u64(o + 1), endTime: b.u64(o + 9), lastUpdated: b.u64(o + 17), emissionsPerSecondX64: b.u128(o + 25), growthGlobalX64: b.u128(o + 153) } }];
98
+ });
99
+ }
100
+ /** Orca reward_last_updated_timestamp(261)和 reward_infos(每个 128 字节,从 269 开始):mint +0、emissions_per_second_x64 +96、growth_global +112 */
101
+ function orcaRewards(b) {
102
+ if (b.data.length < 269 + 3 * 128) {
103
+ return [];
104
+ }
105
+ const lastUpdated = b.u64(261);
106
+ return [0, 1, 2].flatMap((index) => {
107
+ const o = 269 + index * 128;
108
+ const mint = b.key(o);
109
+ return mint === ZERO_ADDRESS
110
+ ? []
111
+ : [{ index, mint, emission: { initialized: true, openTime: 0n, endTime: U64_MAX_VALUE, lastUpdated, emissionsPerSecondX64: b.u128(o + 96), growthGlobalX64: b.u128(o + 112) } }];
112
+ });
113
+ }
98
114
  function raydiumPool(b) {
99
115
  return {
100
116
  ammConfig: b.key(9),
@@ -108,6 +124,8 @@ function raydiumPool(b) {
108
124
  global0: b.u128(277),
109
125
  global1: b.u128(293),
110
126
  fee: null,
127
+ liquidity: b.u128(237),
128
+ rewards: raydiumRewards(b),
111
129
  };
112
130
  }
113
131
  function orcaPool(b) {
@@ -123,6 +141,8 @@ function orcaPool(b) {
123
141
  global1: b.u128(245),
124
142
  decimals0: null,
125
143
  decimals1: null,
144
+ liquidity: b.u128(49),
145
+ rewards: orcaRewards(b),
126
146
  };
127
147
  }
128
148
  function ammPool(address, b) {
@@ -183,7 +203,8 @@ export function orcaTickArray(pool, tick, spacing) {
183
203
  const start = floorDiv(tick, spacing * ORCA_TICK_ARRAY_SIZE);
184
204
  return { address: findProgramAddress([utf8('tick_array'), decodeAddress(pool), utf8(String(start))], ORCA_WHIRLPOOL_PROGRAM_ID), start };
185
205
  }
186
- function raydiumTick(account, start, tick, spacing) {
206
+ const rewardOutside = (b, offset) => [b.u128(offset), b.u128(offset + 16), b.u128(offset + 32)];
207
+ export function raydiumTick(account, start, tick, spacing) {
187
208
  raydiumDiscriminator ??= discriminator('TickArrayState');
188
209
  const offset = 44 + ((tick - start) / spacing) * RAYDIUM_TICK_SIZE;
189
210
  // 先认判别符、查长度再读字段(PDA 上可能是别的账户,如被转过 SOL 的系统账户)
@@ -195,14 +216,14 @@ function raydiumTick(account, start, tick, spacing) {
195
216
  return null;
196
217
  }
197
218
  // Raydium 不区分是否初始化:没用过的 tick 各项都是 0,按程序的公式算出来就是对的
198
- return { initialized: true, feeGrowthOutside0: b.u128(offset + 36), feeGrowthOutside1: b.u128(offset + 52) };
219
+ return { initialized: true, feeGrowthOutside0: b.u128(offset + 36), feeGrowthOutside1: b.u128(offset + 52), rewardGrowthsOutside: rewardOutside(b, offset + 68) };
199
220
  }
200
221
  /** Anchor 账户判别符:sha256("account:<名称>") 的前 8 字节 */
201
222
  const discriminator = (name) => sha256(utf8(`account:${name}`)).subarray(0, 8);
202
223
  let orcaDiscriminators;
203
224
  let raydiumDiscriminator;
204
225
  const startsWith = (data, prefix) => data.length >= prefix.length && prefix.every((byte, i) => data[i] === byte);
205
- function orcaTick(account, start, tick, spacing) {
226
+ export function orcaTick(account, start, tick, spacing) {
206
227
  orcaDiscriminators ??= { fixed: discriminator('TickArray'), dynamic: discriminator('DynamicTickArray') };
207
228
  if (account.data.length < 60) {
208
229
  return null; // 两种格式的头部(判别符 + start + …)都放不下
@@ -215,7 +236,7 @@ function orcaTick(account, start, tick, spacing) {
215
236
  if (b.i32(8) !== start || offset + ORCA_TICK_SIZE > account.data.length) {
216
237
  return null;
217
238
  }
218
- return { initialized: b.u8(offset) === 1, feeGrowthOutside0: b.u128(offset + 33), feeGrowthOutside1: b.u128(offset + 49) };
239
+ return { initialized: b.u8(offset) === 1, feeGrowthOutside0: b.u128(offset + 33), feeGrowthOutside1: b.u128(offset + 49), rewardGrowthsOutside: rewardOutside(b, offset + 65) };
219
240
  }
220
241
  if (startsWith(account.data, orcaDiscriminators.dynamic)) {
221
242
  // 可变大小:start i32 | whirlpool | tick_bitmap u128 | ticks(未初始化 1 字节 0;初始化 1 字节 1 + 112 字节数据)
@@ -238,7 +259,7 @@ function orcaTick(account, start, tick, spacing) {
238
259
  if (offset + 113 > account.data.length) {
239
260
  return null;
240
261
  }
241
- return { initialized: true, feeGrowthOutside0: b.u128(offset + 33), feeGrowthOutside1: b.u128(offset + 49) };
262
+ return { initialized: true, feeGrowthOutside0: b.u128(offset + 33), feeGrowthOutside1: b.u128(offset + 49), rewardGrowthsOutside: rewardOutside(b, offset + 65) };
242
263
  }
243
264
  return null;
244
265
  }
@@ -252,7 +273,7 @@ function orcaTick(account, start, tick, spacing) {
252
273
  */
253
274
  export async function lpPositions(client, owner, options = {}) {
254
275
  if (!isAddress(owner)) {
255
- throw new Error(`Invalid owner address: ${owner}`);
276
+ throw new InvalidAddressError(`Invalid owner address: ${owner}`, owner);
256
277
  }
257
278
  const protocols = new Set(options.protocols ?? Object.keys(SOLANA_LP_PROTOCOLS));
258
279
  for (const p of protocols) {
@@ -262,7 +283,7 @@ export async function lpPositions(client, owner, options = {}) {
262
283
  }
263
284
  for (const address of [...(options.mints ?? []), ...(options.pools ?? [])]) {
264
285
  if (!isAddress(address)) {
265
- throw new Error(`Invalid address: ${address}`);
286
+ throw new InvalidAddressError(`Invalid address: ${address}`, address);
266
287
  }
267
288
  }
268
289
  const { signal } = options;
@@ -301,12 +322,9 @@ export async function lpPositions(client, owner, options = {}) {
301
322
  if (!account) {
302
323
  return;
303
324
  }
304
- const program = SOLANA_LP_PROTOCOLS[c.protocol].programId;
305
- if (c.protocol === 'raydium-clmm' && account.owner === program && account.data.length === RAYDIUM_POSITION_SIZE) {
306
- positions.push(raydiumPosition(c.address, new Bytes(account.data)));
307
- }
308
- else if (c.protocol === 'orca-whirlpool' && account.owner === program && account.data.length === ORCA_POSITION_SIZE) {
309
- positions.push(orcaPosition(c.address, new Bytes(account.data)));
325
+ const parsed = parseClmmPosition(c.protocol, c.address, account);
326
+ if (parsed) {
327
+ positions.push(parsed);
310
328
  }
311
329
  });
312
330
  // LP mint 的权限是 AMM v4 / CPMM 的固定权限地址时才去找池子
@@ -330,13 +348,31 @@ export async function lpPositions(client, owner, options = {}) {
330
348
  lookups.push({ protocol: 'raydium-cpmm', lpMint: h.mint });
331
349
  }
332
350
  });
333
- // 3. 池子:集中流动性的池子账户;LP 代币按 LP mint 找池子
351
+ // 3. 池子:集中流动性的池子账户;LP 代币按 LP mint 找池子(之前找到过的直接读池子账户,和集中流动性的池子一起读)
334
352
  const poolAddresses = [...new Set(positions.map((p) => p.pool))];
353
+ const cached = lookups.flatMap((l) => {
354
+ const address = ammPoolByLpMint.get(lpPoolKey(l.protocol, l.lpMint));
355
+ return address ? [{ ...l, address }] : [];
356
+ });
357
+ const uncached = lookups.filter((l) => !ammPoolByLpMint.has(lpPoolKey(l.protocol, l.lpMint)));
335
358
  const [poolAccounts, found] = await Promise.all([
336
- client.accounts(poolAddresses, { signal }),
337
- Promise.all(lookups.map((l) => findAmmPool(client, l.protocol, l.lpMint, signal))),
359
+ client.accounts([...poolAddresses, ...cached.map((c) => c.address)], { signal }),
360
+ findAmmPools(client, uncached, signal),
338
361
  ]);
339
- for (const pool of found) {
362
+ // 缓存的池子核对一下(地址不对、账户读不到时重新找)
363
+ const stale = [];
364
+ cached.forEach((c, i) => {
365
+ const account = poolAccounts[poolAddresses.length + i];
366
+ const pool = account ? parseAmmPool(account) : null;
367
+ if (pool && pool.protocol === c.protocol && pool.lpMint === c.lpMint) {
368
+ poolByLp.set(pool.lpMint, pool);
369
+ }
370
+ else {
371
+ ammPoolByLpMint.delete(lpPoolKey(c.protocol, c.lpMint));
372
+ stale.push({ protocol: c.protocol, lpMint: c.lpMint });
373
+ }
374
+ });
375
+ for (const pool of [...found, ...(stale.length ? await findAmmPools(client, stale, signal) : [])]) {
340
376
  if (pool) {
341
377
  poolByLp.set(pool.lpMint, pool);
342
378
  }
@@ -410,10 +446,35 @@ export async function lpPositions(client, owner, options = {}) {
410
446
  fees1 = null;
411
447
  }
412
448
  }
413
- if (!options.includeClosed && position.liquidity === 0n && fees0 === 0n && fees1 === 0n) {
449
+ // 未领取的挖矿奖励:没有流动性时只有已记账的;有流动性时先把池子的奖励增长结算到 now,再按区间内的增长算新增
450
+ let rewards;
451
+ if (options.rewards) {
452
+ const now = BigInt(Math.floor(options.now ?? Date.now() / 1000));
453
+ const ticks = (() => {
454
+ if (!arrays)
455
+ return null;
456
+ const read = position.protocol === 'raydium-clmm' ? raydiumTick : orcaTick;
457
+ const lowerAccount = byAddress.get(arrays.lower.address);
458
+ const upperAccount = byAddress.get(arrays.upper.address);
459
+ const lower = lowerAccount ? read(lowerAccount, arrays.lower.start, position.tickLower, pool.tickSpacing) : null;
460
+ const upper = upperAccount ? read(upperAccount, arrays.upper.start, position.tickUpper, pool.tickSpacing) : null;
461
+ return lower && upper ? { lower, upper } : null;
462
+ })();
463
+ rewards = pool.rewards.map(({ index, mint, emission }) => {
464
+ const state = position.rewards[index];
465
+ if (!arrays)
466
+ return { index, mint, amount: state.owed };
467
+ if (!ticks)
468
+ return { index, mint, amount: null };
469
+ const global = rewardGrowthGlobal(pool.liquidity, emission, now);
470
+ const inside = getRewardGrowthInside(pool.tick, position.tickLower, ticks.lower, position.tickUpper, ticks.upper, index, global);
471
+ return { index, mint, amount: getFeesOwed(state.owed, position.liquidity, inside, state.growthLast) };
472
+ });
473
+ }
474
+ if (!options.includeClosed && position.liquidity === 0n && fees0 === 0n && fees1 === 0n && !rewards?.some((x) => x.amount !== 0n)) {
414
475
  continue;
415
476
  }
416
- raw.push({ type: 'clmm', position, pool: { ...pool, fee: pool.fee ?? (pool.ammConfig ? (feeRates.get(pool.ammConfig) ?? null) : null) }, amount0, amount1, fees0, fees1 });
477
+ raw.push({ type: 'clmm', position, pool: { ...pool, fee: pool.fee ?? (pool.ammConfig ? (feeRates.get(pool.ammConfig) ?? null) : null) }, amount0, amount1, fees0, fees1, rewards });
417
478
  }
418
479
  for (const pool of ammHeld) {
419
480
  const balance = held.get(pool.lpMint)?.amount ?? 0n;
@@ -438,6 +499,18 @@ export async function lpPositions(client, owner, options = {}) {
438
499
  }
439
500
  return finalize(client, raw, options);
440
501
  }
502
+ /**
503
+ * 方法不可用(见 methodUnavailable)时把错误换成说明怎么绕过的 Error(cause 为原错误,hint 拿到原错误的 message);
504
+ * 取消、限频、超时等原样抛出(保留错误类型)。/lp、/approvals、资产总值共用
505
+ */
506
+ export function withMethodHint(promise, signal, hint) {
507
+ return promise.catch((err) => {
508
+ if (signal?.aborted || !methodUnavailable(err)) {
509
+ throw err;
510
+ }
511
+ throw new Error(hint(err instanceof Error ? err.message : String(err)), { cause: err });
512
+ });
513
+ }
441
514
  /**
442
515
  * 节点不提供这个方法(方法不存在、被拦截、需要付费 / Token、403 等):这时报错里说明怎么绕过。
443
516
  * 多节点时所有节点都是这个原因才算;限频、超时、网络不对等原样抛出(资产总值 portfolio 也用)
@@ -465,32 +538,40 @@ async function listHoldings(client, owner, mints, extra, signal) {
465
538
  const balances = list.length ? await client.balances(owner, list, { signal }) : [];
466
539
  return balances.filter((b) => b.success && !b.native && b.balance !== '0').map((b) => ({ mint: b.token, amount: BigInt(b.balance), decimals: b.decimals }));
467
540
  }
468
- const tokens = await client.ownerTokens(owner, { signal, metadata: false, includeNative: false, includeNfts: true }).catch((err) => {
469
- if (signal?.aborted || !methodUnavailable(err)) {
470
- throw err; // 取消、限频、超时等原样抛出(保留错误类型)
471
- }
472
- throw new Error(`Scanning the token accounts of ${owner} failed (${err instanceof Error ? err.message : String(err)}). ` +
473
- 'Scanning needs getTokenAccountsByOwner (most free public nodes do not support it): pass the position NFT / LP mints in `mints`, or use a node that supports it', { cause: err });
474
- });
541
+ const tokens = await withMethodHint(client.ownerTokens(owner, { signal, metadata: false, includeNative: false, includeNfts: true }), signal, (detail) => `Scanning the token accounts of ${owner} failed (${detail}). ` +
542
+ 'Scanning needs getTokenAccountsByOwner (most free public nodes do not support it): pass the position NFT / LP mints in `mints`, or use a node that supports it');
475
543
  return tokens.map((t) => ({ mint: t.token, amount: BigInt(t.balance), decimals: t.decimals }));
476
544
  }
477
- /** 按 LP mint 找 AMM v4 / CPMM 的池子(getProgramAccounts + memcmp),找不到时为 null */
545
+ /**
546
+ * LP mint → 池子地址(AMM v4 / CPMM):池子地址不会变,找到后永久缓存(条目数有上限,超出淘汰最早写入的),
547
+ * 之后不再 getProgramAccounts,用时读池子账户核对
548
+ */
549
+ const ammPoolByLpMint = new Map();
550
+ const MAX_CACHED_LP_POOLS = 10_000;
551
+ /** 同时进行的 getProgramAccounts 数(每个都要节点扫描整个程序的账户,很重) */
552
+ const MAX_PARALLEL_POOL_LOOKUPS = 4;
553
+ function lpPoolKey(protocol, lpMint) {
554
+ return `${protocol}:${lpMint}`;
555
+ }
556
+ /** 测试用:清空 LP mint → 池子地址的缓存 */
557
+ export function resetLpPoolCache() {
558
+ ammPoolByLpMint.clear();
559
+ }
560
+ /** 按 LP mint 找池子,限制并发;结果与 lookups 一一对应 */
561
+ function findAmmPools(client, lookups, signal) {
562
+ return mapPool(lookups, MAX_PARALLEL_POOL_LOOKUPS, (l) => findAmmPool(client, l.protocol, l.lpMint, signal));
563
+ }
564
+ /** 按 LP mint 找 AMM v4 / CPMM 的池子(getProgramAccounts + memcmp),找不到时为 null;找到的池子地址写入缓存 */
478
565
  async function findAmmPool(client, protocol, lpMint, signal) {
479
566
  const [programId, size, offset] = protocol === 'raydium-amm' ? [RAYDIUM_AMM_PROGRAM_ID, AMM_POOL_SIZE, 464] : [RAYDIUM_CPMM_PROGRAM_ID, CPMM_POOL_SIZE, 136];
480
- const res = await client
481
- .request('getProgramAccounts', [programId, { encoding: 'base64', commitment: client.commitment, filters: [{ dataSize: size }, { memcmp: { offset, bytes: lpMint } }] }], { signal })
482
- .catch((err) => {
483
- if (signal?.aborted || !methodUnavailable(err)) {
484
- throw err;
485
- }
486
- // 免费公共节点大多不支持 getProgramAccounts:说明怎么绕过
487
- throw new Error(`Finding the ${SOLANA_LP_PROTOCOLS[protocol].name} pool of LP token ${lpMint} uses getProgramAccounts, which failed (${err instanceof Error ? err.message : String(err)}; most free public nodes do not support it). ` +
488
- "Pass the pool address in `pools`, use a node that supports getProgramAccounts, or skip LP tokens with `protocols: ['raydium-clmm', 'orca-whirlpool']`", { cause: err });
489
- });
567
+ // 免费公共节点大多不支持 getProgramAccounts:说明怎么绕过
568
+ const res = await withMethodHint(client.request('getProgramAccounts', [programId, { encoding: 'base64', commitment: client.commitment, filters: [{ dataSize: size }, { memcmp: { offset, bytes: lpMint } }] }], { signal }), signal, (detail) => `Finding the ${SOLANA_LP_PROTOCOLS[protocol].name} pool of LP token ${lpMint} uses getProgramAccounts, which failed (${detail}; most free public nodes do not support it). ` +
569
+ "Pass the pool address in `pools`, use a node that supports getProgramAccounts, or skip LP tokens with `protocols: ['raydium-clmm', 'orca-whirlpool']`");
490
570
  for (const item of res ?? []) {
491
571
  const data = base64.decode(item.account.data[0]);
492
572
  const pool = parseAmmPool({ address: item.pubkey, owner: item.account.owner, data, lamports: 0n, executable: false });
493
573
  if (pool?.lpMint === lpMint) {
574
+ boundedSet(ammPoolByLpMint, lpPoolKey(protocol, lpMint), pool.address, MAX_CACHED_LP_POOLS);
494
575
  return pool;
495
576
  }
496
577
  }
@@ -498,7 +579,7 @@ async function findAmmPool(client, protocol, lpMint, signal) {
498
579
  }
499
580
  async function finalize(client, raw, options) {
500
581
  const { signal } = options;
501
- const mints = [...new Set(raw.flatMap((r) => [r.pool.mint0, r.pool.mint1]))];
582
+ const mints = [...new Set(raw.flatMap((r) => [r.pool.mint0, r.pool.mint1, ...(r.type === 'clmm' ? (r.rewards ?? []).map((x) => x.mint) : [])]))];
502
583
  const priceSource = options.prices === true ? defillamaPrices() : options.prices || null;
503
584
  const fetchFn = options.fetch ?? ((...args) => fetch(...args));
504
585
  // 价格源出错(服务不可用、被拦截、同步抛错等)时照样返回仓位,只是不带价格;取消查询时照常 reject
@@ -577,6 +658,11 @@ async function finalize(client, raw, options) {
577
658
  const d1 = token1.decimals;
578
659
  const fees0Formatted = r.fees0 === null ? null : fmt(r.fees0, token0);
579
660
  const fees1Formatted = r.fees1 === null ? null : fmt(r.fees1, token1);
661
+ const rewards = r.rewards?.map((x) => {
662
+ const t = token(x.mint, null);
663
+ const formatted = x.amount === null ? null : fmt(x.amount, t);
664
+ return { index: x.index, mint: x.mint, symbol: t.symbol, decimals: t.decimals, amount: x.amount === null ? null : x.amount.toString(), formatted, ...(priceSource ? { usd: x.amount === 0n ? 0 : usd(t, formatted) } : {}) };
665
+ });
580
666
  return {
581
667
  ...base,
582
668
  type: 'clmm',
@@ -597,6 +683,8 @@ async function finalize(client, raw, options) {
597
683
  fees0Formatted,
598
684
  fees1Formatted,
599
685
  ...(priceSource ? { feesUsd: r.fees0 === null || r.fees1 === null ? null : sum(usd(token0, fees0Formatted), usd(token1, fees1Formatted), r.fees0, r.fees1) } : {}),
686
+ ...(rewards ? { rewards } : {}),
687
+ ...(rewards && priceSource ? { rewardsUsd: rewards.some((x) => x.usd === null || x.usd === undefined) ? null : rewards.reduce((acc, x) => acc + (x.usd ?? 0), 0) } : {}),
600
688
  };
601
689
  });
602
690
  if (priceSource) {