@w3lib/solana-batch-call 0.6.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 (165) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +16 -8
  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 +2 -2
  20. package/dist/batchPlan.d.ts.map +1 -1
  21. package/dist/batchPlan.js +5 -26
  22. package/dist/batchPlan.js.map +1 -1
  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 +5 -3
  28. package/dist/claim.d.ts.map +1 -1
  29. package/dist/claim.js +10 -10
  30. package/dist/claim.js.map +1 -1
  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.map +1 -1
  40. package/dist/decode.js +2 -29
  41. package/dist/decode.js.map +1 -1
  42. package/dist/fees.d.ts.map +1 -1
  43. package/dist/fees.js +2 -12
  44. package/dist/fees.js.map +1 -1
  45. package/dist/history.d.ts.map +1 -1
  46. package/dist/history.js +4 -15
  47. package/dist/history.js.map +1 -1
  48. package/dist/index.d.ts +3 -3
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +1 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/layout.d.ts +21 -0
  53. package/dist/layout.d.ts.map +1 -1
  54. package/dist/layout.js +50 -69
  55. package/dist/layout.js.map +1 -1
  56. package/dist/liquidity/common.js +2 -2
  57. package/dist/liquidity/common.js.map +1 -1
  58. package/dist/lp.d.ts +9 -13
  59. package/dist/lp.d.ts.map +1 -1
  60. package/dist/lp.js +67 -55
  61. package/dist/lp.js.map +1 -1
  62. package/dist/portfolio.d.ts.map +1 -1
  63. package/dist/portfolio.js +3 -8
  64. package/dist/portfolio.js.map +1 -1
  65. package/dist/reclaim.d.ts +4 -2
  66. package/dist/reclaim.d.ts.map +1 -1
  67. package/dist/reclaim.js +16 -16
  68. package/dist/reclaim.js.map +1 -1
  69. package/dist/risk.js +2 -2
  70. package/dist/risk.js.map +1 -1
  71. package/dist/rpc.d.ts +30 -7
  72. package/dist/rpc.d.ts.map +1 -1
  73. package/dist/rpc.js +400 -109
  74. package/dist/rpc.js.map +1 -1
  75. package/dist/simulate.d.ts.map +1 -1
  76. package/dist/simulate.js +3 -26
  77. package/dist/simulate.js.map +1 -1
  78. package/dist/source.d.ts +8 -4
  79. package/dist/source.d.ts.map +1 -1
  80. package/dist/source.js +11 -4
  81. package/dist/source.js.map +1 -1
  82. package/dist/subpaths/approvals.d.ts +3 -3
  83. package/dist/subpaths/approvals.d.ts.map +1 -1
  84. package/dist/subpaths/approvals.js.map +1 -1
  85. package/dist/subpaths/claim.d.ts +3 -3
  86. package/dist/subpaths/claim.d.ts.map +1 -1
  87. package/dist/subpaths/claim.js.map +1 -1
  88. package/dist/subpaths/reclaim.d.ts +3 -3
  89. package/dist/subpaths/reclaim.d.ts.map +1 -1
  90. package/dist/subpaths/reclaim.js.map +1 -1
  91. package/dist/swap/index.js +3 -3
  92. package/dist/swap/index.js.map +1 -1
  93. package/dist/tokenDetails.d.ts +12 -0
  94. package/dist/tokenDetails.d.ts.map +1 -0
  95. package/dist/tokenDetails.js +196 -0
  96. package/dist/tokenDetails.js.map +1 -0
  97. package/dist/tokenMeta.d.ts +63 -0
  98. package/dist/tokenMeta.d.ts.map +1 -0
  99. package/dist/tokenMeta.js +226 -0
  100. package/dist/tokenMeta.js.map +1 -0
  101. package/dist/tradeCheck.js +3 -3
  102. package/dist/tradeCheck.js.map +1 -1
  103. package/dist/util.d.ts +22 -0
  104. package/dist/util.d.ts.map +1 -0
  105. package/dist/util.js +107 -0
  106. package/dist/util.js.map +1 -0
  107. package/dist/write/chain.d.ts +51 -0
  108. package/dist/write/chain.d.ts.map +1 -0
  109. package/dist/write/chain.js +184 -0
  110. package/dist/write/chain.js.map +1 -0
  111. package/dist/write/collect.js +2 -2
  112. package/dist/write/collect.js.map +1 -1
  113. package/dist/write/errors.d.ts +10 -0
  114. package/dist/write/errors.d.ts.map +1 -0
  115. package/dist/write/errors.js +77 -0
  116. package/dist/write/errors.js.map +1 -0
  117. package/dist/write/pack.d.ts +28 -0
  118. package/dist/write/pack.d.ts.map +1 -0
  119. package/dist/write/pack.js +125 -0
  120. package/dist/write/pack.js.map +1 -0
  121. package/dist/write/send.d.ts +23 -9
  122. package/dist/write/send.d.ts.map +1 -1
  123. package/dist/write/send.js +53 -351
  124. package/dist/write/send.js.map +1 -1
  125. package/dist/write/tracker.d.ts +24 -0
  126. package/dist/write/tracker.d.ts.map +1 -0
  127. package/dist/write/tracker.js +129 -0
  128. package/dist/write/tracker.js.map +1 -0
  129. package/package.json +1 -1
  130. package/src/accountReader.ts +187 -0
  131. package/src/address.ts +41 -6
  132. package/src/approvals.ts +36 -44
  133. package/src/balances.ts +678 -0
  134. package/src/batchPlan.ts +6 -26
  135. package/src/bytes.ts +89 -0
  136. package/src/claim.ts +15 -11
  137. package/src/client.ts +59 -1239
  138. package/src/clientConfig.ts +12 -0
  139. package/src/decode.ts +4 -30
  140. package/src/fees.ts +2 -9
  141. package/src/history.ts +4 -15
  142. package/src/index.ts +13 -1
  143. package/src/layout.ts +63 -82
  144. package/src/liquidity/common.ts +2 -2
  145. package/src/lp.ts +86 -60
  146. package/src/portfolio.ts +8 -11
  147. package/src/reclaim.ts +20 -17
  148. package/src/risk.ts +2 -2
  149. package/src/rpc.ts +472 -116
  150. package/src/simulate.ts +3 -21
  151. package/src/source.ts +14 -5
  152. package/src/subpaths/approvals.ts +3 -2
  153. package/src/subpaths/claim.ts +3 -2
  154. package/src/subpaths/reclaim.ts +3 -2
  155. package/src/swap/index.ts +3 -3
  156. package/src/tokenDetails.ts +240 -0
  157. package/src/tokenMeta.ts +270 -0
  158. package/src/tradeCheck.ts +3 -3
  159. package/src/util.ts +110 -0
  160. package/src/write/chain.ts +218 -0
  161. package/src/write/collect.ts +2 -2
  162. package/src/write/errors.ts +82 -0
  163. package/src/write/pack.ts +142 -0
  164. package/src/write/send.ts +74 -407
  165. package/src/write/tracker.ts +152 -0
package/src/batchPlan.ts CHANGED
@@ -3,13 +3,13 @@
3
3
  * 打包规则同 batchCall(相邻的调用放进同一笔交易,不超过 1232 字节和 64 个账户)
4
4
  */
5
5
  import type { SolanaClient } from './client.js'
6
- import { isAddress } from './address.js'
6
+ import { InvalidAddressError, isAddress } from './address.js'
7
7
  import type { SimulationResult } from './simulate.js'
8
8
  import type { BatchCallOptions } from './write/batch.js'
9
- import { MAX_COMPUTE_UNITS, setComputeUnitLimit, setComputeUnitPrice } from './write/instructions.js'
9
+ import { packUnits } from './write/pack.js'
10
10
  import type { WriteResult } from './write/send.js'
11
11
  import type { SignerInput } from './write/signer.js'
12
- import { MAX_ACCOUNT_LOCKS, PACKET_DATA_SIZE, compileMessage, transactionSize, type Instruction } from './write/transaction.js'
12
+ import type { Instruction } from './write/transaction.js'
13
13
 
14
14
  /** 一个调用:要签名的地址(代币账户 / 仓位的 owner)和原子执行的指令 */
15
15
  export interface PlannedCall {
@@ -17,29 +17,9 @@ export interface PlannedCall {
17
17
  instructions: Instruction[]
18
18
  }
19
19
 
20
- /** 把调用按顺序打包成交易,返回每笔交易包含的调用下标。单个调用放不进一笔交易时自己成一笔(发送时会报“transaction too large”) */
20
+ /** 把调用按顺序打包成交易,返回每笔交易包含的调用下标。单个调用放不进一笔交易时自己成一笔(发送时会报“transaction too large”);规则同 batchCall(packUnits) */
21
21
  export function packCalls(payer: string, calls: readonly PlannedCall[], batchSize = Number.POSITIVE_INFINITY): number[][] {
22
- const packs: number[][] = []
23
- let current: number[] = []
24
- let instructions: Instruction[] = []
25
- const fits = (list: readonly Instruction[]) => {
26
- const probe = compileMessage(payer, [setComputeUnitLimit(MAX_COMPUTE_UNITS), setComputeUnitPrice(1n), ...list], '11111111111111111111111111111111')
27
- return transactionSize(probe) <= PACKET_DATA_SIZE && probe.accountKeys.length <= MAX_ACCOUNT_LOCKS
28
- }
29
- calls.forEach((call, i) => {
30
- const next = [...instructions, ...call.instructions]
31
- if (current.length && (current.length >= batchSize || !fits(next))) {
32
- packs.push(current)
33
- current = []
34
- instructions = []
35
- }
36
- current.push(i)
37
- instructions.push(...call.instructions)
38
- })
39
- if (current.length) {
40
- packs.push(current)
41
- }
42
- return packs
22
+ return packUnits(payer, calls, { batchSize }).map((pack) => pack.indices)
43
23
  }
44
24
 
45
25
  export interface PlanSimulationOptions {
@@ -76,7 +56,7 @@ export async function simulatePlan(client: SolanaClient, calls: readonly Planned
76
56
  }
77
57
  const payer = options.feePayer ?? first.owner
78
58
  if (!isAddress(payer)) {
79
- throw new Error(`Invalid fee payer: ${payer}`)
59
+ throw new InvalidAddressError(`Invalid fee payer: ${payer}`, payer)
80
60
  }
81
61
  const { simulate } = await import('./simulate.js')
82
62
  const run = (list: readonly PlannedCall[]) =>
package/src/bytes.ts ADDED
@@ -0,0 +1,89 @@
1
+ import { encodeAddress } from './address.js'
2
+
3
+ /**
4
+ * 小端读取二进制数据(账户数据、指令数据共用)。两种用法:
5
+ * - 传偏移量:按绝对位置读,不移动游标(如 `b.u64(333)`)
6
+ * - 不传:从游标处顺序读并前进(如 `r.u32(); r.pubkey()`)
7
+ * 越界时抛 RangeError
8
+ */
9
+ export class Bytes {
10
+ readonly view: DataView
11
+ /** 顺序读取的游标 */
12
+ offset: number
13
+
14
+ constructor(
15
+ readonly data: Uint8Array,
16
+ offset = 0,
17
+ ) {
18
+ this.view = new DataView(data.buffer, data.byteOffset, data.byteLength)
19
+ this.offset = offset
20
+ }
21
+
22
+ get bytes(): Uint8Array {
23
+ return this.data
24
+ }
25
+
26
+ /** 要读的位置:传了 at 用它,否则用游标并前进 n 字节;检查越界 */
27
+ #pos(at: number | undefined, n: number): number {
28
+ const pos = at ?? this.offset
29
+ if (pos < 0 || pos + n > this.data.length) {
30
+ throw new RangeError('Account data is shorter than expected')
31
+ }
32
+ if (at === undefined) {
33
+ this.offset += n
34
+ }
35
+ return pos
36
+ }
37
+
38
+ skip(n: number): void {
39
+ this.#pos(undefined, n)
40
+ }
41
+
42
+ u8(at?: number): number {
43
+ return this.data[this.#pos(at, 1)] as number
44
+ }
45
+
46
+ u16(at?: number): number {
47
+ return this.view.getUint16(this.#pos(at, 2), true)
48
+ }
49
+
50
+ u32(at?: number): number {
51
+ return this.view.getUint32(this.#pos(at, 4), true)
52
+ }
53
+
54
+ i32(at?: number): number {
55
+ return this.view.getInt32(this.#pos(at, 4), true)
56
+ }
57
+
58
+ u64(at?: number): bigint {
59
+ return this.view.getBigUint64(this.#pos(at, 8), true)
60
+ }
61
+
62
+ u128(at?: number): bigint {
63
+ const pos = this.#pos(at, 16)
64
+ return (this.view.getBigUint64(pos + 8, true) << 64n) | this.view.getBigUint64(pos, true)
65
+ }
66
+
67
+ /** 32 字节地址(base58) */
68
+ key(at?: number): string {
69
+ const pos = this.#pos(at, 32)
70
+ return encodeAddress(this.data.subarray(pos, pos + 32))
71
+ }
72
+
73
+ /** 同 key(顺序读取时的习惯叫法) */
74
+ pubkey(at?: number): string {
75
+ return this.key(at)
76
+ }
77
+
78
+ /** borsh string:u32 长度 + UTF-8;去掉 Metaplex 用来补齐长度的 \0 */
79
+ string(): string {
80
+ const len = this.u32()
81
+ const pos = this.#pos(undefined, len)
82
+ return new TextDecoder().decode(this.data.subarray(pos, pos + len)).replace(/\0+$/, '').trim()
83
+ }
84
+
85
+ /** borsh Option:u8 标记(1 有)+ 值 */
86
+ option<T>(read: () => T): T | null {
87
+ return this.u8() === 1 ? read() : null
88
+ }
89
+ }
package/src/claim.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * 领取的指令就是 /liquidity 的 collectFees(手续费 + 全部奖励,SOL 自动解包、缺少的代币账户自动创建),这里只做批量和打包
5
5
  */
6
6
  import type { RequestOptions, SolanaClient } from './client.js'
7
- import { isAddress } from './address.js'
7
+ import { InvalidAddressError, isAddress } from './address.js'
8
8
  import { buildCollectFees } from './liquidity/index.js'
9
9
  import type { SolanaLiquidityPlan } from './liquidity/types.js'
10
10
  import { lpPositions, type SolanaLpClmmPosition, type SolanaLpProtocol, type SolanaLpToken } from './lp.js'
@@ -13,7 +13,8 @@ import { simulatePlan, sendCalls, type PlanSimulation, type PlanSimulationOption
13
13
  import { formatUnits } from './layout.js'
14
14
  import type { SimulatedBalanceChange } from './simulate.js'
15
15
  import type { BatchCallOptions } from './write/batch.js'
16
- import { mapPool, type WriteResult } from './write/send.js'
16
+ import { mapPool } from './util.js'
17
+ import type { WriteResult } from './write/send.js'
17
18
  import type { SignerInput } from './write/signer.js'
18
19
 
19
20
  export type ClaimProtocol = Extract<SolanaLpProtocol, 'orca-whirlpool' | 'raydium-clmm'>
@@ -67,7 +68,7 @@ export interface Claimable {
67
68
  warnings: string[]
68
69
  }
69
70
 
70
- export interface ClaimQuery extends RequestOptions {
71
+ export interface ClaimableOptions extends RequestOptions {
71
72
  /** 只看这些仓位 NFT 的 mint(读 ATA,免费公共节点也能用);不传时列出全部代币账户(getTokenAccountsByOwner,需要支持它的节点) */
72
73
  mints?: readonly string[]
73
74
  /** 协议,默认两个都查 */
@@ -79,6 +80,9 @@ export interface ClaimQuery extends RequestOptions {
79
80
  fetch?: typeof fetch
80
81
  }
81
82
 
83
+ /** @deprecated 改名为 ClaimableOptions(与其他查询的 options 命名统一),旧名保留 */
84
+ export type ClaimQuery = ClaimableOptions
85
+
82
86
  const MAX_ERROR = 200
83
87
 
84
88
  /**
@@ -91,23 +95,23 @@ const MAX_ERROR = 200
91
95
  * c.positions[0].rewards // [{ index: 0, symbol: 'RAY', formatted: '0.042698', usd: 0.083 }]
92
96
  * ```
93
97
  */
94
- export async function claimable(client: SolanaClient, owner: string, query: ClaimQuery = {}): Promise<Claimable> {
98
+ export async function claimable(client: SolanaClient, owner: string, options: ClaimableOptions = {}): Promise<Claimable> {
95
99
  if (!isAddress(owner)) {
96
- throw new Error(`Invalid owner address: ${owner}`)
100
+ throw new InvalidAddressError(`Invalid owner address: ${owner}`, owner)
97
101
  }
98
- const { signal } = query
99
- const protocols: ClaimProtocol[] = query.protocols ? [...query.protocols] : ['orca-whirlpool', 'raydium-clmm']
102
+ const { signal } = options
103
+ const protocols: ClaimProtocol[] = options.protocols ? [...options.protocols] : ['orca-whirlpool', 'raydium-clmm']
100
104
  const list = (
101
- await lpPositions(client, owner, { protocols, mints: query.mints, rewards: true, now: query.now, signal })
105
+ await lpPositions(client, owner, { protocols, mints: options.mints, rewards: true, now: options.now, signal })
102
106
  ).filter((p): p is SolanaLpClmmPosition => p.type === 'clmm')
103
107
 
104
108
  const warnings: string[] = []
105
109
  const mints = [...new Set(list.flatMap((p) => [p.token0.mint, p.token1.mint, ...(p.rewards ?? []).map((r) => r.mint)]))]
106
- const source = query.prices === false ? null : query.prices && query.prices !== true ? query.prices : defillamaPrices()
110
+ const source = options.prices === false ? null : options.prices && options.prices !== true ? options.prices : defillamaPrices()
107
111
  let priceMap = new Map<string, number>()
108
112
  if (source && mints.length) {
109
113
  try {
110
- priceMap = await source.prices({ mints, fetch: query.fetch ?? ((...args: Parameters<typeof fetch>) => fetch(...args)), signal, client })
114
+ priceMap = await source.prices({ mints, fetch: options.fetch ?? ((...args: Parameters<typeof fetch>) => fetch(...args)), signal, client })
111
115
  } catch (err) {
112
116
  if (signal?.aborted) throw err
113
117
  warnings.push(`Price lookup failed (${(err instanceof Error ? err.message : String(err)).slice(0, MAX_ERROR)}): usd values are null`)
@@ -215,7 +219,7 @@ export interface ClaimPlan {
215
219
  */
216
220
  export async function buildClaimAll(client: SolanaClient, owner: string, positions: readonly ClaimTarget[], options: BuildClaimOptions = {}): Promise<ClaimPlan> {
217
221
  if (!isAddress(owner)) {
218
- throw new Error(`Invalid owner address: ${owner}`)
222
+ throw new InvalidAddressError(`Invalid owner address: ${owner}`, owner)
219
223
  }
220
224
  const unique = [...new Map(positions.map((p) => [p.positionMint, p])).values()]
221
225
  const calls: ClaimCall[] = []