@w3lib/evm-batch-call 0.6.0 → 0.8.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 +54 -0
- package/README.md +367 -3
- package/contracts/TokenProbe.sol +103 -0
- package/dist/aggregate.d.ts.map +1 -1
- package/dist/aggregate.js +114 -3
- package/dist/aggregate.js.map +1 -1
- package/dist/approvals.d.ts +169 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +310 -0
- package/dist/approvals.js.map +1 -0
- package/dist/chains.d.ts.map +1 -1
- package/dist/chains.js +4 -0
- package/dist/chains.js.map +1 -1
- package/dist/dividends.d.ts +135 -0
- package/dist/dividends.d.ts.map +1 -0
- package/dist/dividends.js +253 -0
- package/dist/dividends.js.map +1 -0
- package/dist/fees.d.ts +97 -0
- package/dist/fees.d.ts.map +1 -0
- package/dist/fees.js +115 -0
- package/dist/fees.js.map +1 -0
- package/dist/history.d.ts +154 -0
- package/dist/history.d.ts.map +1 -0
- package/dist/history.js +247 -0
- package/dist/history.js.map +1 -0
- package/dist/historySources.d.ts +32 -0
- package/dist/historySources.d.ts.map +1 -0
- package/dist/historySources.js +319 -0
- package/dist/historySources.js.map +1 -0
- package/dist/http.d.ts +10 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +30 -0
- package/dist/http.js.map +1 -0
- package/dist/liquidity/v3.d.ts +2 -0
- package/dist/liquidity/v3.d.ts.map +1 -1
- package/dist/liquidity/v3.js +1 -1
- package/dist/liquidity/v3.js.map +1 -1
- package/dist/liquidity/v4.d.ts +21 -0
- package/dist/liquidity/v4.d.ts.map +1 -1
- package/dist/liquidity/v4.js +4 -3
- package/dist/liquidity/v4.js.map +1 -1
- package/dist/lp.d.ts +10 -1
- package/dist/lp.d.ts.map +1 -1
- package/dist/lp.js +19 -2
- package/dist/lp.js.map +1 -1
- package/dist/lpV4.d.ts +4 -0
- package/dist/lpV4.d.ts.map +1 -1
- package/dist/lpV4.js +27 -3
- package/dist/lpV4.js.map +1 -1
- package/dist/multicall.d.ts.map +1 -1
- package/dist/multicall.js +3 -0
- package/dist/multicall.js.map +1 -1
- package/dist/names.d.ts +55 -0
- package/dist/names.d.ts.map +1 -0
- package/dist/names.js +93 -0
- package/dist/names.js.map +1 -0
- package/dist/owner.d.ts +4 -26
- package/dist/owner.d.ts.map +1 -1
- package/dist/owner.js +7 -112
- package/dist/owner.js.map +1 -1
- package/dist/permit.d.ts +137 -0
- package/dist/permit.d.ts.map +1 -0
- package/dist/permit.js +355 -0
- package/dist/permit.js.map +1 -0
- package/dist/portfolio.d.ts +56 -0
- package/dist/portfolio.d.ts.map +1 -0
- package/dist/portfolio.js +54 -0
- package/dist/portfolio.js.map +1 -0
- package/dist/price.d.ts +95 -0
- package/dist/price.d.ts.map +1 -0
- package/dist/price.js +329 -0
- package/dist/price.js.map +1 -0
- package/dist/rewards/farms.d.ts +26 -0
- package/dist/rewards/farms.d.ts.map +1 -0
- package/dist/rewards/farms.js +210 -0
- package/dist/rewards/farms.js.map +1 -0
- package/dist/rewards/index.d.ts +42 -0
- package/dist/rewards/index.d.ts.map +1 -0
- package/dist/rewards/index.js +382 -0
- package/dist/rewards/index.js.map +1 -0
- package/dist/rewards/types.d.ts +170 -0
- package/dist/rewards/types.d.ts.map +1 -0
- package/dist/rewards/types.js +2 -0
- package/dist/rewards/types.js.map +1 -0
- package/dist/risk.d.ts +42 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +134 -0
- package/dist/risk.js.map +1 -0
- package/dist/rpcNodes.d.ts.map +1 -1
- package/dist/rpcNodes.js +18 -0
- package/dist/rpcNodes.js.map +1 -1
- package/dist/simulate.d.ts +186 -0
- package/dist/simulate.d.ts.map +1 -0
- package/dist/simulate.js +559 -0
- package/dist/simulate.js.map +1 -0
- package/dist/simulateDecode.d.ts +33 -0
- package/dist/simulateDecode.d.ts.map +1 -0
- package/dist/simulateDecode.js +321 -0
- package/dist/simulateDecode.js.map +1 -0
- package/dist/smartAccount/account.d.ts +31 -0
- package/dist/smartAccount/account.d.ts.map +1 -0
- package/dist/smartAccount/account.js +33 -0
- package/dist/smartAccount/account.js.map +1 -0
- package/dist/smartAccount/clients.d.ts +77 -0
- package/dist/smartAccount/clients.d.ts.map +1 -0
- package/dist/smartAccount/clients.js +122 -0
- package/dist/smartAccount/clients.js.map +1 -0
- package/dist/smartAccount/eip5792.d.ts +77 -0
- package/dist/smartAccount/eip5792.d.ts.map +1 -0
- package/dist/smartAccount/eip5792.js +152 -0
- package/dist/smartAccount/eip5792.js.map +1 -0
- package/dist/smartAccount/send.d.ts +40 -0
- package/dist/smartAccount/send.d.ts.map +1 -0
- package/dist/smartAccount/send.js +271 -0
- package/dist/smartAccount/send.js.map +1 -0
- package/dist/smartAccount/userOp.d.ts +91 -0
- package/dist/smartAccount/userOp.d.ts.map +1 -0
- package/dist/smartAccount/userOp.js +124 -0
- package/dist/smartAccount/userOp.js.map +1 -0
- package/dist/subpaths/approvals.d.ts +26 -0
- package/dist/subpaths/approvals.d.ts.map +1 -0
- package/dist/subpaths/approvals.js +27 -0
- package/dist/subpaths/approvals.js.map +1 -0
- package/dist/subpaths/dividends.d.ts +28 -0
- package/dist/subpaths/dividends.d.ts.map +1 -0
- package/dist/subpaths/dividends.js +29 -0
- package/dist/subpaths/dividends.js.map +1 -0
- package/dist/subpaths/fees.d.ts +23 -0
- package/dist/subpaths/fees.d.ts.map +1 -0
- package/dist/subpaths/fees.js +25 -0
- package/dist/subpaths/fees.js.map +1 -0
- package/dist/subpaths/history.d.ts +22 -0
- package/dist/subpaths/history.d.ts.map +1 -0
- package/dist/subpaths/history.js +23 -0
- package/dist/subpaths/history.js.map +1 -0
- package/dist/subpaths/names.d.ts +35 -0
- package/dist/subpaths/names.d.ts.map +1 -0
- package/dist/subpaths/names.js +39 -0
- package/dist/subpaths/names.js.map +1 -0
- package/dist/subpaths/permit.d.ts +14 -0
- package/dist/subpaths/permit.d.ts.map +1 -0
- package/dist/subpaths/permit.js +14 -0
- package/dist/subpaths/permit.js.map +1 -0
- package/dist/subpaths/price.d.ts +38 -0
- package/dist/subpaths/price.d.ts.map +1 -0
- package/dist/subpaths/price.js +40 -0
- package/dist/subpaths/price.js.map +1 -0
- package/dist/subpaths/rewards.d.ts +27 -0
- package/dist/subpaths/rewards.d.ts.map +1 -0
- package/dist/subpaths/rewards.js +25 -0
- package/dist/subpaths/rewards.js.map +1 -0
- package/dist/subpaths/risk.d.ts +19 -0
- package/dist/subpaths/risk.d.ts.map +1 -0
- package/dist/subpaths/risk.js +20 -0
- package/dist/subpaths/risk.js.map +1 -0
- package/dist/subpaths/simulate.d.ts +25 -0
- package/dist/subpaths/simulate.d.ts.map +1 -0
- package/dist/subpaths/simulate.js +28 -0
- package/dist/subpaths/simulate.js.map +1 -0
- package/dist/subpaths/smart-account.d.ts +24 -0
- package/dist/subpaths/smart-account.d.ts.map +1 -0
- package/dist/subpaths/smart-account.js +24 -0
- package/dist/subpaths/smart-account.js.map +1 -0
- package/dist/subpaths/swap.d.ts +19 -0
- package/dist/subpaths/swap.d.ts.map +1 -0
- package/dist/subpaths/swap.js +20 -0
- package/dist/subpaths/swap.js.map +1 -0
- package/dist/swap.d.ts +168 -0
- package/dist/swap.d.ts.map +1 -0
- package/dist/swap.js +387 -0
- package/dist/swap.js.map +1 -0
- package/dist/tokenProbe.d.ts +3 -0
- package/dist/tokenProbe.d.ts.map +1 -0
- package/dist/tokenProbe.js +4 -0
- package/dist/tokenProbe.js.map +1 -0
- package/dist/write/batch.d.ts +34 -2
- package/dist/write/batch.d.ts.map +1 -1
- package/dist/write/batch.js +22 -3
- package/dist/write/batch.js.map +1 -1
- package/dist/write/collect.d.ts +9 -0
- package/dist/write/collect.d.ts.map +1 -1
- package/dist/write/collect.js +41 -1
- package/dist/write/collect.js.map +1 -1
- package/dist/write/send.d.ts +4 -0
- package/dist/write/send.d.ts.map +1 -1
- package/dist/write/send.js.map +1 -1
- package/dist/write/signer.d.ts +5 -1
- package/dist/write/signer.d.ts.map +1 -1
- package/dist/write/signer.js.map +1 -1
- package/dist/write/transfer.d.ts +5 -3
- package/dist/write/transfer.d.ts.map +1 -1
- package/dist/write/transfer.js +12 -6
- package/dist/write/transfer.js.map +1 -1
- package/dist/write/tron.d.ts +25 -0
- package/dist/write/tron.d.ts.map +1 -1
- package/dist/write/tron.js +26 -0
- package/dist/write/tron.js.map +1 -1
- package/package.json +73 -16
- package/src/aggregate.ts +110 -3
- package/src/approvals.ts +462 -0
- package/src/chains.ts +4 -0
- package/src/dividends.ts +360 -0
- package/src/fees.ts +201 -0
- package/src/history.ts +367 -0
- package/src/historySources.ts +360 -0
- package/src/http.ts +34 -0
- package/src/liquidity/v3.ts +1 -1
- package/src/liquidity/v4.ts +4 -3
- package/src/lp.ts +21 -2
- package/src/lpV4.ts +32 -2
- package/src/multicall.ts +3 -0
- package/src/names.ts +141 -0
- package/src/owner.ts +7 -138
- package/src/permit.ts +457 -0
- package/src/portfolio.ts +98 -0
- package/src/price.ts +415 -0
- package/src/rewards/farms.ts +229 -0
- package/src/rewards/index.ts +431 -0
- package/src/rewards/types.ts +179 -0
- package/src/risk.ts +166 -0
- package/src/rpcNodes.ts +18 -0
- package/src/simulate.ts +727 -0
- package/src/simulateDecode.ts +352 -0
- package/src/smartAccount/account.ts +63 -0
- package/src/smartAccount/clients.ts +180 -0
- package/src/smartAccount/eip5792.ts +187 -0
- package/src/smartAccount/send.ts +341 -0
- package/src/smartAccount/userOp.ts +180 -0
- package/src/subpaths/approvals.ts +48 -0
- package/src/subpaths/dividends.ts +47 -0
- package/src/subpaths/fees.ts +43 -0
- package/src/subpaths/history.ts +41 -0
- package/src/subpaths/names.ts +43 -0
- package/src/subpaths/permit.ts +39 -0
- package/src/subpaths/price.ts +60 -0
- package/src/subpaths/rewards.ts +46 -0
- package/src/subpaths/risk.ts +23 -0
- package/src/subpaths/simulate.ts +51 -0
- package/src/subpaths/smart-account.ts +40 -0
- package/src/subpaths/swap.ts +36 -0
- package/src/swap.ts +534 -0
- package/src/tokenProbe.ts +5 -0
- package/src/write/batch.ts +44 -5
- package/src/write/collect.ts +60 -1
- package/src/write/send.ts +4 -0
- package/src/write/signer.ts +5 -1
- package/src/write/transfer.ts +17 -10
- package/src/write/tron.ts +43 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import type { CallOverrides } from '../aggregate.js'
|
|
2
|
+
import { MAX_UINT128, createCtx, evm, fmt, out, type Ctx } from '../liquidity/common.js'
|
|
3
|
+
import { npmInterface } from '../liquidity/v3.js'
|
|
4
|
+
import { Actions, Planner, pmInterface } from '../liquidity/v4.js'
|
|
5
|
+
import { LP_PROTOCOLS, lpPositions, type LpConcentratedPosition, type LpProtocol } from '../lp.js'
|
|
6
|
+
import { tokenPrices } from '../price.js'
|
|
7
|
+
import type { Provider } from '../provider.js'
|
|
8
|
+
import { withSignal } from '../util.js'
|
|
9
|
+
import { batchCall, tronModule } from '../write/batch.js'
|
|
10
|
+
import { resolveSigner } from '../write/signer.js'
|
|
11
|
+
import { mapPool } from '../write/send.js'
|
|
12
|
+
import { FARM_PROTOCOLS, gaugeInterface, masterChefInterface, readGauges, readMasterChef } from './farms.js'
|
|
13
|
+
import type {
|
|
14
|
+
ClaimAllOptions,
|
|
15
|
+
ClaimAllSendOptions,
|
|
16
|
+
ClaimAmount,
|
|
17
|
+
ClaimItem,
|
|
18
|
+
ClaimPlan,
|
|
19
|
+
ClaimResult,
|
|
20
|
+
ClaimSkipped,
|
|
21
|
+
ClaimTx,
|
|
22
|
+
ClaimTxResult,
|
|
23
|
+
Claimable,
|
|
24
|
+
ClaimableOptions,
|
|
25
|
+
FarmProtocol,
|
|
26
|
+
} from './types.js'
|
|
27
|
+
import type { SignerInput } from '../write/signer.js'
|
|
28
|
+
|
|
29
|
+
export { FARM_PROTOCOLS } from './farms.js'
|
|
30
|
+
|
|
31
|
+
const lower = (s: string) => s.toLowerCase()
|
|
32
|
+
|
|
33
|
+
/** 同一个代币的数量合并 */
|
|
34
|
+
function sumAmounts(items: readonly ClaimItem[]): ClaimAmount[] {
|
|
35
|
+
const sum = new Map<string, { amount: ClaimAmount; raw: bigint; usd: number | null | undefined }>()
|
|
36
|
+
for (const { amounts } of items) {
|
|
37
|
+
for (const a of amounts) {
|
|
38
|
+
const key = lower(a.token.address)
|
|
39
|
+
const entry = sum.get(key) ?? { amount: a, raw: 0n, usd: undefined }
|
|
40
|
+
entry.raw += BigInt(a.amount)
|
|
41
|
+
if (a.usd !== undefined) {
|
|
42
|
+
entry.usd = a.usd === null ? (entry.usd ?? null) : (entry.usd ?? 0) + a.usd
|
|
43
|
+
}
|
|
44
|
+
sum.set(key, entry)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [...sum.values()]
|
|
48
|
+
.filter((e) => e.raw > 0n)
|
|
49
|
+
.map(({ amount, raw, usd }): ClaimAmount => ({ token: amount.token, amount: raw.toString(), formatted: fmt(raw, amount.token), ...(usd === undefined ? {} : { usd }) }))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const usdOf = (amounts: readonly ClaimAmount[]): number | null | undefined => {
|
|
53
|
+
if (amounts.every((a) => a.usd === undefined)) {
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
const known = amounts.filter((a) => a.usd != null && BigInt(a.amount) > 0n)
|
|
57
|
+
return known.length ? known.reduce((s, a) => s + (a.usd as number), 0) : null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 给每一项的数量加美元价值(价格源出错时全部为 null) */
|
|
61
|
+
async function applyPrices(provider: Provider, items: ClaimItem[], options: ClaimableOptions): Promise<void> {
|
|
62
|
+
const tokens = [...new Set(items.flatMap((i) => i.amounts).filter((a) => BigInt(a.amount) > 0n).map((a) => a.token.address))]
|
|
63
|
+
if (!tokens.length) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
let prices: Array<number | null> = tokens.map(() => null)
|
|
67
|
+
try {
|
|
68
|
+
const found = await tokenPrices(provider, tokens, {
|
|
69
|
+
...(options.prices === true ? {} : { sources: options.prices as never }),
|
|
70
|
+
...(options.fetch ? { fetch: options.fetch } : {}),
|
|
71
|
+
})
|
|
72
|
+
prices = found.map((p) => p.price)
|
|
73
|
+
} catch (err) {
|
|
74
|
+
try {
|
|
75
|
+
options.onPriceError?.(err)
|
|
76
|
+
} catch {
|
|
77
|
+
// 回调出错不影响查询结果
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const priceOf = new Map(tokens.map((t, i) => [lower(t), prices[i] as number | null]))
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
for (const a of item.amounts) {
|
|
83
|
+
const price = priceOf.get(lower(a.token.address)) ?? null
|
|
84
|
+
a.usd = a.formatted !== null && price !== null ? Number(a.formatted) * price : BigInt(a.amount) === 0n ? 0 : null
|
|
85
|
+
}
|
|
86
|
+
item.usd = usdOf(item.amounts) ?? null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 选出要查的 LP 协议 / 挖矿合约 */
|
|
91
|
+
function selectProtocols(chainId: number, options: ClaimableOptions): { lp: Array<string | LpProtocol> | undefined; farms: FarmProtocol[]; lpAll: LpProtocol[] } {
|
|
92
|
+
const farmTable = [...(options.farms ?? []), ...FARM_PROTOCOLS].filter((f) => f.chainId === chainId)
|
|
93
|
+
const custom = (options.protocols ?? []).filter((p): p is LpProtocol => typeof p !== 'string' && p.chainId === chainId)
|
|
94
|
+
const lpAll = [...custom, ...LP_PROTOCOLS.filter((p) => p.chainId === chainId)]
|
|
95
|
+
if (!options.protocols) {
|
|
96
|
+
return { lp: undefined, farms: farmTable, lpAll }
|
|
97
|
+
}
|
|
98
|
+
const lp: Array<string | LpProtocol> = []
|
|
99
|
+
const farms: FarmProtocol[] = []
|
|
100
|
+
for (const p of options.protocols) {
|
|
101
|
+
if (typeof p !== 'string') {
|
|
102
|
+
if (p.chainId === chainId && p.version !== 'v2') lp.push(p)
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
const inLp = lpAll.filter((x) => x.id === p)
|
|
106
|
+
const inFarm = farmTable.filter((f) => f.id === p)
|
|
107
|
+
if (!inLp.length && !inFarm.length) {
|
|
108
|
+
throw new Error(`Unknown protocol ${p} on chain ${chainId}; pass an LpProtocol / FarmProtocol object for protocols that are not built in`)
|
|
109
|
+
}
|
|
110
|
+
// V2 的手续费计入 LP 份额,没有单独领取的
|
|
111
|
+
if (inLp.some((x) => x.version !== 'v2')) lp.push(p)
|
|
112
|
+
farms.push(...inFarm)
|
|
113
|
+
}
|
|
114
|
+
return { lp, farms, lpAll }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 查一个地址能领取的全部内容(只读):V3 / V4 仓位未领取的手续费、质押在 MasterChef / gauge 里的仓位的挖矿奖励,
|
|
119
|
+
* 数量按精度换算,可选美元价值。每个来源单独读取,某个来源出错只记进 `errors`
|
|
120
|
+
*
|
|
121
|
+
* ```ts
|
|
122
|
+
* const c = await claimable(multi, user, { prices: true })
|
|
123
|
+
* c.items // [{ kind: 'fees', protocol: 'uniswap-v3', tokenId: '123', amounts: [{ token: USDC, formatted: '12.3', usd: 12.3 }, …], usd: 20.1 }, { kind: 'farm', amounts: [CAKE …] }, …]
|
|
124
|
+
* c.totals // 按代币汇总
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function claimable(provider: Provider, owner: string, options: ClaimableOptions = {}): Promise<Claimable> {
|
|
128
|
+
return withSignal(options.signal, () => queryClaimable(provider, owner, options))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function queryClaimable(provider: Provider, owner: string, options: ClaimableOptions): Promise<Claimable> {
|
|
132
|
+
const ctx = await createCtx(provider, owner, {})
|
|
133
|
+
const { chainId } = ctx
|
|
134
|
+
const wantFees = options.fees ?? true
|
|
135
|
+
const wantRewards = options.rewards ?? true
|
|
136
|
+
const maxPositions = Math.max(1, Math.floor(options.maxPositions ?? 500))
|
|
137
|
+
const overrides: CallOverrides = {
|
|
138
|
+
...(options.blockTag !== undefined ? { blockTag: options.blockTag } : {}),
|
|
139
|
+
...(options.minBlock !== undefined ? { minBlock: options.minBlock } : {}),
|
|
140
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
141
|
+
}
|
|
142
|
+
const { lp, farms, lpAll } = selectProtocols(chainId, options)
|
|
143
|
+
const errors: Claimable['errors'] = []
|
|
144
|
+
const items: ClaimItem[] = []
|
|
145
|
+
const task = async (source: string, run: () => Promise<ClaimItem[]>) => {
|
|
146
|
+
try {
|
|
147
|
+
items.push(...(await run()))
|
|
148
|
+
} catch (err) {
|
|
149
|
+
errors.push({ source, error: err instanceof Error ? err.message : String(err) })
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const source = { ctx, owner: ctx.owner, overrides, maxPositions, lpProtocols: lpAll }
|
|
153
|
+
await Promise.all([
|
|
154
|
+
wantFees && (lp === undefined || lp.length)
|
|
155
|
+
? task('positions', async () => {
|
|
156
|
+
const positions = await lpPositions(provider, owner, { ...overrides, ...(lp ? { protocols: lp } : {}), versions: ['v3', 'v4'], v4: options.v4, maxPositions })
|
|
157
|
+
return (positions as LpConcentratedPosition[])
|
|
158
|
+
.filter((p) => BigInt(p.fees0) + BigInt(p.fees1) > 0n)
|
|
159
|
+
.map(
|
|
160
|
+
(p): ClaimItem => ({
|
|
161
|
+
id: `fees:${p.protocol}:${p.tokenId}`,
|
|
162
|
+
kind: 'fees',
|
|
163
|
+
protocol: p.protocol,
|
|
164
|
+
protocolName: p.protocolName,
|
|
165
|
+
version: p.version,
|
|
166
|
+
tokenId: p.tokenId,
|
|
167
|
+
pool: p.pool,
|
|
168
|
+
staked: false,
|
|
169
|
+
target: p.positionManager,
|
|
170
|
+
amounts: [
|
|
171
|
+
{ token: p.token0, amount: p.fees0, formatted: p.fees0Formatted },
|
|
172
|
+
{ token: p.token1, amount: p.fees1, formatted: p.fees1Formatted },
|
|
173
|
+
],
|
|
174
|
+
}),
|
|
175
|
+
)
|
|
176
|
+
})
|
|
177
|
+
: null,
|
|
178
|
+
...farms.map((farm) =>
|
|
179
|
+
farm.kind === 'masterchef-v3'
|
|
180
|
+
? wantFees || wantRewards
|
|
181
|
+
? task(farm.id, () => readMasterChef(source, farm, wantFees, wantRewards))
|
|
182
|
+
: null
|
|
183
|
+
: wantRewards
|
|
184
|
+
? task(farm.id, () => readGauges(source, farm, options.gauges))
|
|
185
|
+
: null,
|
|
186
|
+
),
|
|
187
|
+
])
|
|
188
|
+
if (options.prices) {
|
|
189
|
+
await applyPrices(provider, items, options)
|
|
190
|
+
}
|
|
191
|
+
if (options.prices) {
|
|
192
|
+
items.sort((a, b) => (b.usd ?? -1) - (a.usd ?? -1))
|
|
193
|
+
}
|
|
194
|
+
const totals = sumAmounts(items)
|
|
195
|
+
const usd = usdOf(totals)
|
|
196
|
+
return { chainId, owner: out(ctx, ctx.owner), items, totals, ...(usd === undefined ? {} : { usd }), errors }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// 构建交易
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
interface Group {
|
|
204
|
+
kind: ClaimTx['kind']
|
|
205
|
+
type: 'v3' | 'v4' | 'masterchef' | 'gauge'
|
|
206
|
+
target: string
|
|
207
|
+
items: ClaimItem[]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function groupOf(items: readonly ClaimItem[]): Group[] {
|
|
211
|
+
const groups = new Map<string, Group>()
|
|
212
|
+
for (const item of items) {
|
|
213
|
+
const [type, kind, key]: [Group['type'], Group['kind'], string] =
|
|
214
|
+
item.kind === 'gauge'
|
|
215
|
+
? ['gauge', 'gauge', item.id]
|
|
216
|
+
: item.staked
|
|
217
|
+
? ['masterchef', 'farm', item.target]
|
|
218
|
+
: item.version === 'v4'
|
|
219
|
+
? ['v4', 'fees', item.target]
|
|
220
|
+
: ['v3', 'fees', item.target]
|
|
221
|
+
const k = `${type}:${key}`
|
|
222
|
+
const group = groups.get(k) ?? { kind, type, target: item.target, items: [] }
|
|
223
|
+
group.items.push(item)
|
|
224
|
+
groups.set(k, group)
|
|
225
|
+
}
|
|
226
|
+
return [...groups.values()]
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const wrap = (iface: typeof npmInterface, calls: string[]) => (calls.length === 1 ? (calls[0] as string) : iface.encodeFunctionData('multicall', [calls]))
|
|
230
|
+
|
|
231
|
+
/** 一组(同一个合约)里的若干项 → 一笔交易的 calldata */
|
|
232
|
+
function encodeGroup(ctx: Ctx, type: Group['type'], items: readonly ClaimItem[]): string {
|
|
233
|
+
const recipient = ctx.recipient
|
|
234
|
+
if (type === 'v3') {
|
|
235
|
+
return wrap(
|
|
236
|
+
npmInterface,
|
|
237
|
+
items.map((i) => npmInterface.encodeFunctionData('collect', [{ tokenId: i.tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }])),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
if (type === 'masterchef') {
|
|
241
|
+
return wrap(
|
|
242
|
+
masterChefInterface,
|
|
243
|
+
items.map((i) =>
|
|
244
|
+
i.kind === 'farm'
|
|
245
|
+
? masterChefInterface.encodeFunctionData('harvest', [i.tokenId, recipient])
|
|
246
|
+
: masterChefInterface.encodeFunctionData('collectTo', [{ tokenId: i.tokenId, recipient, amount0Max: MAX_UINT128, amount1Max: MAX_UINT128 }, recipient]),
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
if (type === 'gauge') {
|
|
251
|
+
return gaugeInterface.encodeFunctionData('getReward', [(items[0] as ClaimItem).tokenId])
|
|
252
|
+
}
|
|
253
|
+
// V4:每个仓位 DECREASE_LIQUIDITY(0) 结算手续费,再把涉及的每个币整个取出(TAKE 的数量 0 = 全部)
|
|
254
|
+
const planner = new Planner()
|
|
255
|
+
const hookData = ctx.options.hookData ?? '0x'
|
|
256
|
+
for (const i of items) {
|
|
257
|
+
planner.add(Actions.DECREASE_LIQUIDITY, ['uint256', 'uint256', 'uint128', 'uint128', 'bytes'], [i.tokenId, 0n, 0n, 0n, hookData])
|
|
258
|
+
}
|
|
259
|
+
const currencies = new Set(items.flatMap((i) => i.amounts.map((a) => evm(a.token.address))))
|
|
260
|
+
for (const currency of currencies) {
|
|
261
|
+
planner.add(Actions.TAKE, ['address', 'address', 'uint256'], [currency, recipient, 0n])
|
|
262
|
+
}
|
|
263
|
+
return pmInterface.encodeFunctionData('modifyLiquidities', [planner.encode(), ctx.deadline])
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function buildTxs(ctx: Ctx, items: readonly ClaimItem[], batchSize: number): ClaimTx[] {
|
|
267
|
+
return groupOf(items).flatMap((group) => {
|
|
268
|
+
// gauge 的 getReward 一次只能领一个仓位
|
|
269
|
+
const size = group.type === 'gauge' ? 1 : batchSize
|
|
270
|
+
const txs: ClaimTx[] = []
|
|
271
|
+
for (let i = 0; i < group.items.length; i += size) {
|
|
272
|
+
const part = group.items.slice(i, i + size)
|
|
273
|
+
txs.push({ kind: group.kind, to: out(ctx, group.target), data: encodeGroup(ctx, group.type, part), value: '0', itemIds: part.map((x) => x.id) })
|
|
274
|
+
}
|
|
275
|
+
return txs
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 领取计划(不签名、不发送):把要领取的项合并成尽量少的交易——同一个仓位合约的 V3 仓位用它的 `multicall`、
|
|
281
|
+
* V4 仓位合并成一次 `modifyLiquidities`、MasterChef 的挖矿奖励和质押仓位手续费用它的 `multicall`、每个 gauge 的仓位各一笔。
|
|
282
|
+
* 交易之间互不依赖,可以交给 `batchCall` 的 '7702' / '4337' / '5792' 模式合成一笔原子交易,或者逐笔发送。
|
|
283
|
+
*
|
|
284
|
+
* 默认先模拟(/simulate):某一笔失败时逐项再试,会失败的项放进 `skipped`,不拖累其他仓位。
|
|
285
|
+
*
|
|
286
|
+
* ```ts
|
|
287
|
+
* const plan = await buildClaimAll(multi, user, { prices: true, minUsd: 1 })
|
|
288
|
+
* plan.transactions // [{ kind: 'fees', to: npm, data, itemIds: [...] }, { kind: 'farm', … }]
|
|
289
|
+
* plan.skipped // [{ item, reason: 'below minUsd' }]
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
export async function buildClaimAll(provider: Provider, owner: string, options: ClaimAllOptions = {}): Promise<ClaimPlan> {
|
|
293
|
+
const ctx = await createCtx(provider, owner, { recipient: options.recipient, hookData: options.hookData })
|
|
294
|
+
const batchSize = Math.floor(options.batchSize ?? 20)
|
|
295
|
+
if (!(batchSize >= 1)) {
|
|
296
|
+
throw new RangeError(`batchSize must be at least 1: ${options.batchSize}`)
|
|
297
|
+
}
|
|
298
|
+
if (options.minUsd !== undefined && !(options.minUsd >= 0)) {
|
|
299
|
+
throw new RangeError(`minUsd must not be negative: ${options.minUsd}`)
|
|
300
|
+
}
|
|
301
|
+
const found = options.items ? undefined : await claimable(provider, owner, options)
|
|
302
|
+
const candidates = options.items ? [...options.items] : (found as Claimable).items
|
|
303
|
+
const skipped: ClaimSkipped[] = []
|
|
304
|
+
const seen = new Set<string>()
|
|
305
|
+
let included = candidates.filter((item) => {
|
|
306
|
+
const skip = (reason: string) => {
|
|
307
|
+
skipped.push({ item, reason })
|
|
308
|
+
return false
|
|
309
|
+
}
|
|
310
|
+
if (seen.has(item.id)) return false
|
|
311
|
+
seen.add(item.id)
|
|
312
|
+
if (item.amounts.every((a) => BigInt(a.amount) === 0n)) return skip('nothing to claim')
|
|
313
|
+
// 有一个要领的币没有价格就当价值未知(usd 只是已知部分的合计),不跳过
|
|
314
|
+
const unknownValue = item.amounts.some((a) => BigInt(a.amount) > 0n && a.usd === null)
|
|
315
|
+
if (options.minUsd !== undefined && item.usd != null && !unknownValue && item.usd < options.minUsd) return skip(`below minUsd (${item.usd})`)
|
|
316
|
+
if (item.kind === 'gauge' && ctx.recipient !== ctx.owner) return skip('gauge rewards are always paid to the owner, recipient is not supported')
|
|
317
|
+
return true
|
|
318
|
+
})
|
|
319
|
+
let transactions = buildTxs(ctx, included, batchSize)
|
|
320
|
+
if (options.simulate !== false && transactions.length) {
|
|
321
|
+
const checked = await preflight(provider, ctx, included, transactions, skipped, options.signal)
|
|
322
|
+
included = checked.items
|
|
323
|
+
transactions = checked.transactions
|
|
324
|
+
}
|
|
325
|
+
const totals = sumAmounts(included)
|
|
326
|
+
const usd = usdOf(totals)
|
|
327
|
+
return { chainId: ctx.chainId, owner: out(ctx, ctx.owner), recipient: out(ctx, ctx.recipient), items: included, skipped, totals, ...(usd === undefined ? {} : { usd }), transactions }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 模拟每一笔;失败的逐项再试,去掉会失败的项后重新构建。逐项都通过、只有合并后的那一笔失败时(gas / multicall 的相互影响)
|
|
332
|
+
* 对半拆开再模拟,直到通过或拆成单项。返回的交易模拟都没有失败;模拟本身出错(节点不支持等)时不拦,照常发送;取消(signal)时抛出
|
|
333
|
+
*/
|
|
334
|
+
async function preflight(provider: Provider, ctx: Ctx, items: ClaimItem[], transactions: ClaimTx[], skipped: ClaimSkipped[], signal?: AbortSignal): Promise<{ items: ClaimItem[]; transactions: ClaimTx[] }> {
|
|
335
|
+
const { simulate } = await import('../simulate.js')
|
|
336
|
+
const run = async (tx: ClaimTx) => {
|
|
337
|
+
signal?.throwIfAborted()
|
|
338
|
+
try {
|
|
339
|
+
tx.simulation = await simulate(provider, { from: ctx.owner, to: tx.to, data: tx.data, value: 0n }, { from: ctx.owner, fee: false })
|
|
340
|
+
return tx.simulation.success ? null : (tx.simulation.error ?? 'execution reverted')
|
|
341
|
+
} catch (err) {
|
|
342
|
+
if (signal?.aborted || (err instanceof Error && err.name === 'AbortError')) throw err
|
|
343
|
+
return null
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const byId = new Map(items.map((i) => [i.id, i]))
|
|
347
|
+
const failed = new Map<string, string>()
|
|
348
|
+
const settle = async (tx: ClaimTx): Promise<ClaimTx[]> => {
|
|
349
|
+
const error = await run(tx)
|
|
350
|
+
if (error === null) return [tx]
|
|
351
|
+
const list = tx.itemIds.map((id) => byId.get(id) as ClaimItem)
|
|
352
|
+
if (list.length === 1) {
|
|
353
|
+
failed.set(tx.itemIds[0] as string, error)
|
|
354
|
+
return []
|
|
355
|
+
}
|
|
356
|
+
// 逐项再试,找出是哪几个仓位失败
|
|
357
|
+
const singles = await mapPool(list, 4, async (item) => {
|
|
358
|
+
const single = buildTxs(ctx, [item], 1)[0] as ClaimTx
|
|
359
|
+
return { item, single, error: await run(single) }
|
|
360
|
+
})
|
|
361
|
+
const good = singles.filter((s) => s.error === null)
|
|
362
|
+
for (const s of singles) {
|
|
363
|
+
if (s.error !== null) failed.set(s.item.id, s.error)
|
|
364
|
+
}
|
|
365
|
+
if (good.length === singles.length) {
|
|
366
|
+
// 单项都能过、合并的失败:对半拆开
|
|
367
|
+
const half = Math.ceil(list.length / 2)
|
|
368
|
+
const parts = [list.slice(0, half), list.slice(half)].flatMap((part) => buildTxs(ctx, part, part.length))
|
|
369
|
+
return (await Promise.all(parts.map(settle))).flat()
|
|
370
|
+
}
|
|
371
|
+
if (good.length === 1) return [(good[0] as (typeof good)[number]).single]
|
|
372
|
+
return (await Promise.all(buildTxs(ctx, good.map((s) => s.item), list.length).map(settle))).flat()
|
|
373
|
+
}
|
|
374
|
+
const settled: ClaimTx[][] = []
|
|
375
|
+
await mapPool(transactions, 4, async (tx, i) => {
|
|
376
|
+
settled[i] = await settle(tx)
|
|
377
|
+
})
|
|
378
|
+
if (!failed.size) {
|
|
379
|
+
return { items, transactions: settled.flat() }
|
|
380
|
+
}
|
|
381
|
+
for (const item of items) {
|
|
382
|
+
if (failed.has(item.id)) skipped.push({ item, reason: `simulation failed: ${failed.get(item.id)}` })
|
|
383
|
+
}
|
|
384
|
+
return { items: items.filter((i) => !failed.has(i.id)), transactions: settled.flat() }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 领取全部手续费和挖矿奖励:构建计划(同 buildClaimAll)后用 from 签名发送,每笔独立,某一笔失败不影响后面的。
|
|
389
|
+
* from 与 /write 的 batchCall 相同;`mode: '7702' | '4337' | '5792'` 时合成一笔原子交易(一个仓位失败整笔回滚,所以先模拟、剔除会失败的项)。
|
|
390
|
+
*
|
|
391
|
+
* ```ts
|
|
392
|
+
* const { status, plan, claimed } = await claimAll(multi, privateKey, { minUsd: 1 })
|
|
393
|
+
* await claimAll(multi, privateKey, { mode: '7702' }) // 一笔交易领完
|
|
394
|
+
* await claimAll(multi, privateKey, { items: picked }) // 只领 getClaimable / claimable 结果里挑出来的
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
export async function claimAll(provider: Provider, from: SignerInput, options: ClaimAllSendOptions = {}): Promise<ClaimResult> {
|
|
398
|
+
const tron = await tronModule(provider)
|
|
399
|
+
const owner = tron ? await tron.tronSignerAddress(from) : (await resolveSigner(from)).address
|
|
400
|
+
const plan = await buildClaimAll(provider, owner, options)
|
|
401
|
+
if (!plan.transactions.length) {
|
|
402
|
+
return { plan, results: [], status: 'skipped', claimed: [], failed: [] }
|
|
403
|
+
}
|
|
404
|
+
const {
|
|
405
|
+
items: _items, protocols: _protocols, fees: _fees, rewards: _rewards, v4: _v4, farms: _farms, gauges: _gauges, maxPositions: _max, prices: _prices, onPriceError: _onPriceError,
|
|
406
|
+
fetch: _fetch, recipient: _recipient, hookData: _hookData, minUsd: _minUsd, batchSize: _batchSize, simulate: _simulate, blockTag: _blockTag, from: _from, minBlock: _minBlock, ...send
|
|
407
|
+
} = options
|
|
408
|
+
const written = await batchCall(
|
|
409
|
+
provider,
|
|
410
|
+
plan.transactions.map((tx) => ({ from, to: tx.to, data: tx.data, value: 0n })),
|
|
411
|
+
{ ...send, continueOnError: options.continueOnError ?? true },
|
|
412
|
+
)
|
|
413
|
+
const results = written.map((r, i): ClaimTxResult => ({ ...r, kind: (plan.transactions[i] as ClaimTx).kind, itemIds: (plan.transactions[i] as ClaimTx).itemIds }))
|
|
414
|
+
if (options.dryRun) {
|
|
415
|
+
// 只预估:没有领取任何东西。预估通过的项放进 simulated,预估失败的才算 failed
|
|
416
|
+
const ok = new Set(results.filter((r) => r.status === 'simulated').flatMap((r) => r.itemIds))
|
|
417
|
+
const badRun = results.find((r) => r.status !== 'simulated')
|
|
418
|
+
return { plan, results, status: badRun ? badRun.status : 'simulated', claimed: [], failed: plan.items.filter((i) => !ok.has(i.id)), simulated: plan.items.filter((i) => ok.has(i.id)) }
|
|
419
|
+
}
|
|
420
|
+
const confirmed = new Set(results.filter((r) => r.status === 'confirmed').flatMap((r) => r.itemIds))
|
|
421
|
+
const bad = results.find((r) => r.status !== 'confirmed')
|
|
422
|
+
return {
|
|
423
|
+
plan,
|
|
424
|
+
results,
|
|
425
|
+
status: bad ? bad.status : 'confirmed',
|
|
426
|
+
claimed: plan.items.filter((i) => confirmed.has(i.id)),
|
|
427
|
+
failed: plan.items.filter((i) => !confirmed.has(i.id)),
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export type { ClaimAllOptions, ClaimAllSendOptions, ClaimAmount, ClaimItem, ClaimKind, ClaimPlan, ClaimResult, ClaimSkipped, ClaimTx, ClaimTxResult, Claimable, ClaimableOptions, FarmProtocol } from './types.js'
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { CallOverrides } from '../aggregate.js'
|
|
2
|
+
import type { LpProtocol, LpToken } from '../lp.js'
|
|
3
|
+
import type { LpV4Options } from '../lpV4.js'
|
|
4
|
+
import type { TokenPricesOptions } from '../price.js'
|
|
5
|
+
import type { SimulationResult } from '../simulate.js'
|
|
6
|
+
import type { BatchCallOptions } from '../write/batch.js'
|
|
7
|
+
import type { WriteResult, WriteStatus } from '../write/send.js'
|
|
8
|
+
|
|
9
|
+
/** 内置表之外的挖矿合约(自己的 FarmProtocol) */
|
|
10
|
+
export interface FarmProtocol {
|
|
11
|
+
/** 协议 id:与 LP 协议同名时(如 'pancakeswap-v3')选这个协议会同时查它的挖矿奖励 */
|
|
12
|
+
id: string
|
|
13
|
+
name: string
|
|
14
|
+
/**
|
|
15
|
+
* - masterchef-v3:PancakeSwap V3 的 MasterChefV3,仓位 NFT 存在合约里,按 `balanceOf` / `tokenOfOwnerByIndex` 列出用户质押的仓位,
|
|
16
|
+
* `pendingCake` 是待领取的 CAKE,质押中的仓位手续费也由它代为领取
|
|
17
|
+
* - gauge:Aerodrome / Velodrome Slipstream 的 CLGauge(经 Voter 找到每个池子的 gauge),`stakedValues` / `earned` / `getReward`
|
|
18
|
+
*/
|
|
19
|
+
kind: 'masterchef-v3' | 'gauge'
|
|
20
|
+
chainId: number
|
|
21
|
+
/** 仓位 NFT 合约(质押的仓位属于它) */
|
|
22
|
+
positionManager: string
|
|
23
|
+
/** masterchef-v3:MasterChefV3 合约 */
|
|
24
|
+
masterChef?: string
|
|
25
|
+
/** gauge:Voter 合约(pools / gauges) */
|
|
26
|
+
voter?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 领取的内容:
|
|
31
|
+
* - fees:V3 / V4 仓位未领取的手续费(token0 / token1),质押在 MasterChef 里的仓位也有
|
|
32
|
+
* - farm:MasterChef 的挖矿奖励(CAKE)
|
|
33
|
+
* - gauge:Slipstream gauge 的奖励(AERO / VELO)
|
|
34
|
+
*/
|
|
35
|
+
export type ClaimKind = 'fees' | 'farm' | 'gauge'
|
|
36
|
+
|
|
37
|
+
export interface ClaimAmount {
|
|
38
|
+
token: LpToken
|
|
39
|
+
/** 最小单位 */
|
|
40
|
+
amount: string
|
|
41
|
+
/** 按精度换算;读不到精度时为 null */
|
|
42
|
+
formatted: string | null
|
|
43
|
+
/** 开启 prices 时:美元价值(没有价格或读不到精度时为 null) */
|
|
44
|
+
usd?: number | null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 一项可领取的内容:一个仓位的手续费,或一个质押仓位的挖矿奖励 */
|
|
48
|
+
export interface ClaimItem {
|
|
49
|
+
/** `<kind>:<协议 id>:<tokenId>`,在一次查询里唯一 */
|
|
50
|
+
id: string
|
|
51
|
+
kind: ClaimKind
|
|
52
|
+
protocol: string
|
|
53
|
+
protocolName: string
|
|
54
|
+
/** fees:仓位的版本 */
|
|
55
|
+
version?: 'v3' | 'v4'
|
|
56
|
+
tokenId: string
|
|
57
|
+
/** 池子地址(V4 为 poolId);gauge 为 null */
|
|
58
|
+
pool: string | null
|
|
59
|
+
/** 仓位 NFT 质押在挖矿合约里 */
|
|
60
|
+
staked: boolean
|
|
61
|
+
/** 领取时调用的合约:仓位合约 / MasterChef / gauge */
|
|
62
|
+
target: string
|
|
63
|
+
/** fees 固定是 [token0, token1](可能有一边为 0);farm / gauge 是奖励代币 */
|
|
64
|
+
amounts: ClaimAmount[]
|
|
65
|
+
/** 开启 prices 时:这一项的美元价值(已知部分的合计;全部未知时为 null) */
|
|
66
|
+
usd?: number | null
|
|
67
|
+
/** 数量是估计值(MasterChef 的 harvest 预执行失败,退回 pendingCake,实际领到的可能更多) */
|
|
68
|
+
approximate?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Claimable {
|
|
72
|
+
chainId: number
|
|
73
|
+
owner: string
|
|
74
|
+
/** 有东西可领的项,开启 prices 时按美元价值从大到小排 */
|
|
75
|
+
items: ClaimItem[]
|
|
76
|
+
/** 按代币汇总 */
|
|
77
|
+
totals: ClaimAmount[]
|
|
78
|
+
usd?: number | null
|
|
79
|
+
/** 某个来源读取失败(节点出错等)时的说明,其他来源的结果照常返回 */
|
|
80
|
+
errors: Array<{ source: string; error: string }>
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ClaimableOptions extends CallOverrides {
|
|
84
|
+
/**
|
|
85
|
+
* 查哪些协议:LP 协议 id('uniswap-v3'、'pancakeswap-v3'、'uniswap-v4' …)或自己的 LpProtocol、挖矿协议 id('aerodrome-slipstream' …)。
|
|
86
|
+
* 'pancakeswap-v3' 同时包含它的 MasterChef 挖矿。默认这条链上内置的全部
|
|
87
|
+
*/
|
|
88
|
+
protocols?: ReadonlyArray<string | LpProtocol>
|
|
89
|
+
/** 查手续费(V3 / V4 仓位,含质押在 MasterChef 里的),默认 true */
|
|
90
|
+
fees?: boolean
|
|
91
|
+
/** 查挖矿奖励(MasterChef / gauge),默认 true */
|
|
92
|
+
rewards?: boolean
|
|
93
|
+
/** V4:怎么找仓位(已知 tokenIds 或扫描 Transfer 事件,同 getLpPositions),两个都没有时不查 V4 */
|
|
94
|
+
v4?: LpV4Options
|
|
95
|
+
/** 内置表里没有的挖矿合约 */
|
|
96
|
+
farms?: readonly FarmProtocol[]
|
|
97
|
+
/** gauge:直接指定要查的 gauge 地址,不再通过 Voter 枚举全部池子(Aerodrome 有两千多个,枚举要几千次读取) */
|
|
98
|
+
gauges?: readonly string[]
|
|
99
|
+
/** 每个协议最多核对多少个仓位,默认 500 */
|
|
100
|
+
maxPositions?: number
|
|
101
|
+
/** 美元价值(可选):true 用 DefiLlama + 链上池子,也可以传 tokenPrices 的 sources。默认不查 */
|
|
102
|
+
prices?: boolean | TokenPricesOptions['sources']
|
|
103
|
+
/** 价格源出错时调用:照样返回结果,usd 为 null */
|
|
104
|
+
onPriceError?: (error: unknown) => void
|
|
105
|
+
/** 自定义 fetch(价格用) */
|
|
106
|
+
fetch?: typeof fetch
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ClaimAllOptions extends ClaimableOptions {
|
|
110
|
+
/** 要领取的项(getClaimable 的结果里挑),默认领取查到的全部 */
|
|
111
|
+
items?: readonly ClaimItem[]
|
|
112
|
+
/** 领取的代币收到哪个地址,默认 owner。gauge 的奖励只能发给 owner,传了别的地址时这些项会被跳过 */
|
|
113
|
+
recipient?: string
|
|
114
|
+
/** V4:传给 hooks 的 hookData,默认 '0x' */
|
|
115
|
+
hookData?: string
|
|
116
|
+
/** 美元价值低于这个值的项跳过(不够付 gas 的零头);价值未知的不跳过。需要 prices */
|
|
117
|
+
minUsd?: number
|
|
118
|
+
/** 一笔交易最多合并多少个仓位,默认 20 */
|
|
119
|
+
batchSize?: number
|
|
120
|
+
/**
|
|
121
|
+
* 构建后先模拟(用 /simulate,按需加载):某一笔失败时逐项再试,把会失败的项放进 skipped,其余照常领取。默认 true;
|
|
122
|
+
* 设为 false 时不模拟(某一项失败会让它所在的那一笔整体失败)
|
|
123
|
+
*/
|
|
124
|
+
simulate?: boolean
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ClaimTx {
|
|
128
|
+
kind: ClaimKind
|
|
129
|
+
to: string
|
|
130
|
+
data: string
|
|
131
|
+
value: '0'
|
|
132
|
+
/** 这一笔里包含的项(ClaimItem.id) */
|
|
133
|
+
itemIds: string[]
|
|
134
|
+
/** 模拟的结果(simulate 不为 false 时):资产变化就是实际会收到的数量 */
|
|
135
|
+
simulation?: SimulationResult
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface ClaimSkipped {
|
|
139
|
+
item: ClaimItem
|
|
140
|
+
reason: string
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 领取计划:要按顺序发送的交易(每笔独立,互不依赖)。只读取、不签名 */
|
|
144
|
+
export interface ClaimPlan {
|
|
145
|
+
chainId: number
|
|
146
|
+
owner: string
|
|
147
|
+
recipient: string
|
|
148
|
+
/** 会领取的项 */
|
|
149
|
+
items: ClaimItem[]
|
|
150
|
+
/** 跳过的项和原因(没东西可领、低于 minUsd、模拟失败、地址不允许 …) */
|
|
151
|
+
skipped: ClaimSkipped[]
|
|
152
|
+
/** 按代币汇总的预计数量(用的是查询时的数量,挖矿奖励随时间还会增加一点) */
|
|
153
|
+
totals: ClaimAmount[]
|
|
154
|
+
usd?: number | null
|
|
155
|
+
transactions: ClaimTx[]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ClaimAllSendOptions extends ClaimAllOptions, Omit<BatchCallOptions, 'continueOnError'> {
|
|
159
|
+
/** 某一笔发送前失败后,后面的是否继续,默认 true(每一笔都是独立的领取) */
|
|
160
|
+
continueOnError?: boolean
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface ClaimTxResult extends WriteResult {
|
|
164
|
+
kind: ClaimKind
|
|
165
|
+
itemIds: string[]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ClaimResult {
|
|
169
|
+
plan: ClaimPlan
|
|
170
|
+
/** 与 plan.transactions 一一对应 */
|
|
171
|
+
results: ClaimTxResult[]
|
|
172
|
+
/** 全部确认为 'confirmed';dryRun 全部预估通过为 'simulated';没有东西可领为 'skipped';否则是第一笔没成功的状态 */
|
|
173
|
+
status: WriteStatus
|
|
174
|
+
/** 已确认的交易里的项 / 其他交易里的项(dryRun 时 claimed 为空,failed 只含预估失败的项) */
|
|
175
|
+
claimed: ClaimItem[]
|
|
176
|
+
failed: ClaimItem[]
|
|
177
|
+
/** dryRun:预估通过的项(没有真正发送) */
|
|
178
|
+
simulated?: ClaimItem[]
|
|
179
|
+
}
|