@picon-finance/dlmm-sdk 1.6.0 → 1.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.
Files changed (36) hide show
  1. package/README.md +33 -6
  2. package/dist/entities/pool.d.ts +5 -1
  3. package/dist/entities/pool.d.ts.map +1 -1
  4. package/dist/entities/pool.js +10 -5
  5. package/dist/entities/position.d.ts +4 -5
  6. package/dist/entities/position.d.ts.map +1 -1
  7. package/dist/entities/position.js +105 -28
  8. package/dist/entities/types.d.ts +26 -0
  9. package/dist/entities/types.d.ts.map +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/quote/index.d.ts +1 -0
  13. package/dist/quote/index.d.ts.map +1 -1
  14. package/dist/quote/index.js +4 -0
  15. package/dist/quote/wasm-node/picon_dlmm_quote.d.ts +6 -0
  16. package/dist/quote/wasm-node/picon_dlmm_quote.js +15 -0
  17. package/dist/quote/wasm-node/picon_dlmm_quote_bg.wasm +0 -0
  18. package/dist/quote/wasm-node/picon_dlmm_quote_bg.wasm.d.ts +1 -0
  19. package/dist/quote/wasm-web/picon_dlmm_quote.d.ts +7 -0
  20. package/dist/quote/wasm-web/picon_dlmm_quote.js +15 -0
  21. package/dist/quote/wasm-web/picon_dlmm_quote_bg.wasm +0 -0
  22. package/dist/quote/wasm-web/picon_dlmm_quote_bg.wasm.d.ts +1 -0
  23. package/package.json +1 -1
  24. package/src/entities/pool.ts +15 -6
  25. package/src/entities/position.ts +144 -38
  26. package/src/entities/types.ts +37 -1
  27. package/src/index.ts +1 -1
  28. package/src/quote/index.ts +5 -0
  29. package/src/quote/wasm-node/picon_dlmm_quote.d.ts +6 -0
  30. package/src/quote/wasm-node/picon_dlmm_quote.js +15 -0
  31. package/src/quote/wasm-node/picon_dlmm_quote_bg.wasm +0 -0
  32. package/src/quote/wasm-node/picon_dlmm_quote_bg.wasm.d.ts +1 -0
  33. package/src/quote/wasm-web/picon_dlmm_quote.d.ts +7 -0
  34. package/src/quote/wasm-web/picon_dlmm_quote.js +15 -0
  35. package/src/quote/wasm-web/picon_dlmm_quote_bg.wasm +0 -0
  36. package/src/quote/wasm-web/picon_dlmm_quote_bg.wasm.d.ts +1 -0
package/README.md CHANGED
@@ -67,8 +67,8 @@ const openPositionIxs = await pool.openPosition(ownerSigner, positionMintSigner,
67
67
 
68
68
  const position = await pool.getPositionByPositionMint(positionMintSigner.address);
69
69
 
70
- // depositByWeight checks the deposit against the pool's cached activeBinId — if the pool was
71
- // fetched a while ago (or opened before other activity could've moved the price), refresh it
70
+ // depositByWeight checks the deposit against the pool's cached activeBinId — if time has
71
+ // passed since pool was fetched (other activity could've moved the price since), refresh it
72
72
  // first so the deposit isn't built/checked against a stale active bin.
73
73
  await pool.refresh();
74
74
 
@@ -93,12 +93,28 @@ const closeIx = await position.close(ownerSigner);
93
93
  console.log(position.data.lowerBinId, position.data.upperBinId);
94
94
  ```
95
95
 
96
- `position.data.feeStates[i].feeOwedX`/`feeOwedY` is the fee already swept into the position (materialized by the last deposit/withdraw/claim_fee call), not the full claimable amount fees earned since then aren't reflected until the next such call. `position.getPendingFee()` computes that remaining delta from the same cached position + bin array data (no RPC):
96
+ `position.getInfo()` returns a full summary: deposited amounts and the total fee a `claimFee` call would sweep right now, each in both `gross*` (before any Token-2022 transfer fee) and `net*` (what you'd actually receive) forms, plus the per-bin data they're summed from:
97
97
 
98
98
  ```ts
99
- const { amountX: pendingFeeX, amountY: pendingFeeY } = position.getPendingFee();
100
- const owedFeeX = position.data.feeStates.reduce((sum, feeState) => sum + feeState.feeOwedX, 0n);
101
- // owedFeeX + pendingFeeX is the total a claimFee call would sweep right now.
99
+ const info = await position.getInfo();
100
+ console.log(`Deposited: ${info.netAmountX} X / ${info.netAmountY} Y`);
101
+ console.log(`Claimable fee: ${info.netFeeX} X / ${info.netFeeY} Y`);
102
+ ```
103
+
104
+ `info.bins` (same as calling `position.getBinInfos()` directly) is the per-bin breakdown `getInfo()` sums to produce those totals — always raw, since `gross*`/`net*` only apply once, to the aggregate:
105
+
106
+ ```ts
107
+ for (const { bin, amountX, amountY, feeX, feeY } of position.getBinInfos()) {
108
+ console.log(`bin ${bin.id}: deposited ${amountX} X / ${amountY} Y, fee ${feeX} X / ${feeY} Y`);
109
+ }
110
+ ```
111
+
112
+ Both are computed entirely from cached position + bin array data — `getBinInfos()` makes no RPC calls at all; `getInfo()` makes one round trip (via `Pool.getTransferFees()`) to resolve each mint's live transfer-fee rate.
113
+
114
+ `Pool.getTransferFees()` resolves each mint's active [Token-2022 transfer-fee](https://spl.solana.com/token-2022/extensions#transfer-fees) config from cached mint data — `undefined` per side means that mint has no `TransferFeeConfig` extension, not that the fee is zero:
115
+
116
+ ```ts
117
+ const { transferFeeX, transferFeeY } = await pool.getTransferFees();
102
118
  ```
103
119
 
104
120
  Refresh a position's cached state after it changes on-chain (e.g. after a deposit lands):
@@ -126,6 +142,17 @@ Or fetch a single position directly if you already know its address:
126
142
  const positionByAddress = await pool.getPosition(positionAddress);
127
143
  ```
128
144
 
145
+ Claim fees across many positions at once with `Position.claimAllFees()` — it skips positions with nothing pending (checked locally, no extra RPC) and batches the rest into `MAX_POSITIONS_PER_CLAIM`-sized (overridable via a third argument) instruction groups, one per transaction, sharing a single set of ATA setup/teardown instructions across the whole batch rather than repeating them per position. All positions must belong to the same pool:
146
+
147
+ ```ts
148
+ const positions = await pool.getAllPositionsByPositionMints(positionMints);
149
+ const claimTxs = await Position.claimAllFees(ownerSigner, positions);
150
+
151
+ for (const claimIxs of claimTxs) {
152
+ // build + send one transaction per entry
153
+ }
154
+ ```
155
+
129
156
  ### Events
130
157
 
131
158
  Event decoding and subscriptions are a separate, opt-in feature under the `events` namespace — independent of `DlmmProgram`/`Pool`/`Position`/`BinArray`:
@@ -1,6 +1,6 @@
1
1
  import { createSolanaRpc, type Address, type Account, type Instruction, type TransactionSigner, type FetchAccountConfig, type FetchAccountsConfig } from "@solana/kit";
2
2
  import { generated } from "..";
3
- import { type QuoteOutput } from "../quote";
3
+ import { type QuoteOutput, type TransferFee } from "../quote";
4
4
  import { Position } from "./position";
5
5
  import { type ProgramConfig } from "./types";
6
6
  export declare class Pool {
@@ -19,6 +19,10 @@ export declare class Pool {
19
19
  get tokenVaultY(): Address;
20
20
  get tokenProgramX(): Address;
21
21
  get tokenProgramY(): Address;
22
+ getTransferFees(): Promise<{
23
+ transferFeeX?: TransferFee;
24
+ transferFeeY?: TransferFee;
25
+ }>;
22
26
  refresh(data: Account<generated.Pool>): Promise<void>;
23
27
  refresh(config?: FetchAccountConfig): Promise<void>;
24
28
  getPosition(address: Address, config?: FetchAccountsConfig): Promise<Position>;
@@ -1 +1 @@
1
- {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/entities/pool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/entities/pool.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { AccountRole, } from "@solana/kit";
2
2
  import { fetchAllMint, } from "@solana-program/token-2022";
3
3
  import { generated } from "..";
4
- import { quote } from "../quote";
4
+ import { quote, } from "../quote";
5
5
  import { isAccount } from "../utils/type_guards";
6
6
  import { resolveAdminConfigAddress } from "./admin_config";
7
7
  import { processAssociatedTokenAccountPair } from "../utils/ata";
@@ -36,6 +36,13 @@ export class Pool {
36
36
  get tokenVaultY() { return this.data.tokenVaultY; }
37
37
  get tokenProgramX() { return this.#mintX.programAddress; }
38
38
  get tokenProgramY() { return this.#mintY.programAddress; }
39
+ async getTransferFees() {
40
+ const { epoch } = await this.#rpc.getEpochInfo().send();
41
+ return {
42
+ transferFeeX: resolveTransferFee(this.#mintX.data, epoch),
43
+ transferFeeY: resolveTransferFee(this.#mintY.data, epoch),
44
+ };
45
+ }
39
46
  async refresh(arg) {
40
47
  if (!isAccount(arg)) {
41
48
  arg = await generated.fetchPool(this.#rpc, this.address, arg);
@@ -95,13 +102,11 @@ export class Pool {
95
102
  async quote({ xToY, amountIn, slippageToleranceBps, }) {
96
103
  const indices = generateBinArrayIndicesForSwap(this.activeBinId, xToY, MAX_BIN_ARRAYS_PER_SWAP);
97
104
  const binArrayAddresses = await findAllBinArrayAddresses({ pool: this.address, indices }, { programAddress: this.#programAddress });
98
- const [maybeBinArrays, { epoch }] = await Promise.all([
105
+ const [maybeBinArrays, { transferFeeX, transferFeeY }] = await Promise.all([
99
106
  generated.fetchAllMaybeBinArray(this.#rpc, binArrayAddresses),
100
- this.#rpc.getEpochInfo().send(),
107
+ this.getTransferFees(),
101
108
  ]);
102
109
  const binArrays = maybeBinArrays.map(maybeBinArray => maybeBinArray.exists ? maybeBinArray.data : undefined);
103
- const transferFeeX = resolveTransferFee(this.#mintX.data, epoch);
104
- const transferFeeY = resolveTransferFee(this.#mintY.data, epoch);
105
110
  return await quote({
106
111
  amountIn,
107
112
  xToY,
@@ -3,7 +3,7 @@ import { generated } from "..";
3
3
  import { type BinIdRange } from "../utils/bin_location";
4
4
  import { Pool } from "./pool";
5
5
  import { BinArray } from "./bin_array";
6
- import { type ProgramConfig } from "./types";
6
+ import { type PositionInfo, type ProgramConfig, type PositionBinInfo } from "./types";
7
7
  export declare class Position {
8
8
  #private;
9
9
  private constructor();
@@ -15,10 +15,8 @@ export declare class Position {
15
15
  lowerBinArray: Address;
16
16
  upperBinArray?: Address | undefined;
17
17
  };
18
- getPendingFee(): {
19
- amountX: bigint;
20
- amountY: bigint;
21
- };
18
+ getBinInfos(): PositionBinInfo[];
19
+ getInfo(): Promise<PositionInfo>;
22
20
  refresh(data: [Account<generated.Position>, Account<generated.BinArray>[]]): Promise<void>;
23
21
  refresh(config?: FetchAccountsConfig): Promise<void>;
24
22
  depositByWeight(owner: TransactionSigner, { amountX, amountY, lowerBinId, binWeights, maxActiveBinSlippage, distributionMode, }: {
@@ -35,6 +33,7 @@ export declare class Position {
35
33
  static load(pool: Pool, address: Address, config?: FetchAccountsConfig): Promise<Position>;
36
34
  static loadAll(pool: Pool, addresses: Address[], config?: FetchAccountsConfig): Promise<Position[]>;
37
35
  static refreshAll(rpc: ReturnType<typeof createSolanaRpc>, positions: Position[], config?: FetchAccountsConfig): Promise<void>;
36
+ static claimAllFees(owner: TransactionSigner, positions: Position[], positionsPerClaim?: number): Promise<Instruction[][]>;
38
37
  }
39
38
  export declare function findPositionAddress(seeds: {
40
39
  positionMint: Address;
@@ -1 +1 @@
1
- {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../src/entities/position.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../src/entities/position.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,13 @@
1
1
  import { decodeAccount, assertAccountExists, fetchEncodedAccounts, } from "@solana/kit";
2
2
  import { generated } from "..";
3
+ import { applyTransferFee } from "../quote";
3
4
  import { findPositionTokenAccount, processAssociatedTokenAccountPair, } from "../utils/ata";
4
5
  import { getBinIndex, getBinArrayIndex, getPositionBinArrayIndices, } from "../utils/bin_location";
5
6
  import { assertRangeWithinRange, assertPositionBelongsToPool, } from "../utils/assert";
6
7
  import { isUndefined } from "../utils/type_guards";
7
- import { fetchInChunks } from "../utils/chunk";
8
+ import { chunk, fetchInChunks } from "../utils/chunk";
8
9
  import { resolveAdminConfigAddress } from "./admin_config";
9
- import { MAX_ACCOUNTS_PER_RPC_BATCH } from "../utils/constants";
10
+ import { MAX_ACCOUNTS_PER_RPC_BATCH, MAX_POSITIONS_PER_CLAIM } from "../utils/constants";
10
11
  import { BinArray, decodeAllBinArrays, createAllBinArrays, fetchAllBinArraysChunked, } from "./bin_array";
11
12
  export class Position {
12
13
  #pool;
@@ -26,25 +27,68 @@ export class Position {
26
27
  const upperBinArray = this.#binArrays.length > 1 ? this.#binArrays[1].address : undefined;
27
28
  return { lowerBinArray, upperBinArray };
28
29
  }
29
- getPendingFee() {
30
+ getBinInfos() {
30
31
  const { lowerBinId, upperBinId, feeStates } = this.data;
31
32
  const lowerBinArrayIndex = getBinArrayIndex(lowerBinId);
32
- return Array.from({ length: upperBinId - lowerBinId + 1 }, (_, i) => i)
33
- .flatMap(i => {
34
- const feeState = feeStates[i];
35
- if (feeState.shares === 0n)
36
- return [];
33
+ const binCount = upperBinId - lowerBinId + 1;
34
+ return feeStates
35
+ .slice(0, binCount)
36
+ .map((feeState, i) => {
37
37
  const binId = lowerBinId + i;
38
38
  const binIndex = getBinIndex(binId);
39
- const binArrayIndex = getBinArrayIndex(binId) - lowerBinArrayIndex;
40
- const binArray = this.#binArrays[binArrayIndex];
39
+ const localBinArrayIndex = getBinArrayIndex(binId) - lowerBinArrayIndex;
40
+ const binArray = this.#binArrays[localBinArrayIndex];
41
41
  const bin = binArray.bins[binIndex];
42
- return bin.totalShares > 0n ? [{ feeState, bin }] : [];
43
- })
44
- .reduce((total, { feeState, bin }) => ({
45
- amountX: total.amountX + earnedAmount(bin.feePerShareX, feeState.feePerShareCheckpointX, feeState.shares),
46
- amountY: total.amountY + earnedAmount(bin.feePerShareY, feeState.feePerShareCheckpointY, feeState.shares),
47
- }), { amountX: 0n, amountY: 0n });
42
+ const amountX = bin.totalShares > 0n ? (feeState.shares * bin.amountX) / bin.totalShares : 0n;
43
+ const amountY = bin.totalShares > 0n ? (feeState.shares * bin.amountY) / bin.totalShares : 0n;
44
+ let feeX = feeState.feeOwedX;
45
+ let feeY = feeState.feeOwedY;
46
+ if (feeState.shares > 0n && bin.totalShares > 0n) {
47
+ feeX += earnedAmount(bin.feePerShareX, feeState.feePerShareCheckpointX, feeState.shares);
48
+ feeY += earnedAmount(bin.feePerShareY, feeState.feePerShareCheckpointY, feeState.shares);
49
+ }
50
+ return {
51
+ bin: {
52
+ id: binId,
53
+ price: bin.price,
54
+ amountX: bin.amountX,
55
+ amountY: bin.amountY,
56
+ shares: bin.totalShares
57
+ },
58
+ amountX,
59
+ amountY,
60
+ shares: feeState.shares,
61
+ feeX,
62
+ feeY,
63
+ };
64
+ });
65
+ }
66
+ async getInfo() {
67
+ const bins = this.getBinInfos();
68
+ const gross = bins.reduce((acc, { amountX, amountY, feeX, feeY }) => ({
69
+ amountX: acc.amountX + amountX,
70
+ amountY: acc.amountY + amountY,
71
+ feeX: acc.feeX + feeX,
72
+ feeY: acc.feeY + feeY,
73
+ }), { amountX: 0n, amountY: 0n, feeX: 0n, feeY: 0n });
74
+ const { transferFeeX, transferFeeY } = await this.#pool.getTransferFees();
75
+ const [netAmountX, netAmountY, netFeeX, netFeeY] = await Promise.all([
76
+ applyTransferFee(gross.amountX, transferFeeX),
77
+ applyTransferFee(gross.amountY, transferFeeY),
78
+ applyTransferFee(gross.feeX, transferFeeX),
79
+ applyTransferFee(gross.feeY, transferFeeY),
80
+ ]);
81
+ return {
82
+ bins,
83
+ grossAmountX: gross.amountX,
84
+ grossAmountY: gross.amountY,
85
+ netAmountX,
86
+ netAmountY,
87
+ grossFeeX: gross.feeX,
88
+ grossFeeY: gross.feeY,
89
+ netFeeX,
90
+ netFeeY,
91
+ };
48
92
  }
49
93
  async refresh(arg) {
50
94
  if (!Array.isArray(arg)) {
@@ -97,21 +141,11 @@ export class Position {
97
141
  });
98
142
  return [...preInstructions, depositByWeightIx, ...postInstructions];
99
143
  }
100
- async claimFee(owner, range) {
144
+ async #buildClaimFeeInstruction(owner, positionTokenAccount, tokenOwnerAccountX, tokenOwnerAccountY, range) {
101
145
  const { lowerBinId, upperBinId } = range ?? this.data;
102
146
  assertRangeWithinRange({ lowerBinId, upperBinId }, this.data);
103
- const [positionTokenAccount, { ataX: tokenOwnerAccountX, ataY: tokenOwnerAccountY, preInstructions, postInstructions }] = await Promise.all([
104
- findPositionTokenAccount({ owner: owner.address, positionMint: this.data.positionMint }),
105
- processAssociatedTokenAccountPair({
106
- owner,
107
- tokenMintX: this.#pool.tokenMintX,
108
- tokenMintY: this.#pool.tokenMintY,
109
- tokenProgramX: this.#pool.tokenProgramX,
110
- tokenProgramY: this.#pool.tokenProgramY,
111
- }),
112
- ]);
113
147
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
114
- const claimFeeIx = generated.getClaimFeeInstruction({
148
+ return generated.getClaimFeeInstruction({
115
149
  owner,
116
150
  pool: this.#pool.address,
117
151
  position: this.address,
@@ -131,6 +165,19 @@ export class Position {
131
165
  }, {
132
166
  programAddress: this.#pool.programAddress
133
167
  });
168
+ }
169
+ async claimFee(owner, range) {
170
+ const [positionTokenAccount, { ataX: tokenOwnerAccountX, ataY: tokenOwnerAccountY, preInstructions, postInstructions }] = await Promise.all([
171
+ findPositionTokenAccount({ owner: owner.address, positionMint: this.data.positionMint }),
172
+ processAssociatedTokenAccountPair({
173
+ owner,
174
+ tokenMintX: this.#pool.tokenMintX,
175
+ tokenMintY: this.#pool.tokenMintY,
176
+ tokenProgramX: this.#pool.tokenProgramX,
177
+ tokenProgramY: this.#pool.tokenProgramY,
178
+ }),
179
+ ]);
180
+ const claimFeeIx = await this.#buildClaimFeeInstruction(owner, positionTokenAccount, tokenOwnerAccountX, tokenOwnerAccountY, range);
134
181
  return [...preInstructions, claimFeeIx, ...postInstructions];
135
182
  }
136
183
  async withdraw(owner, bpsToRemove, range) {
@@ -209,6 +256,36 @@ export class Position {
209
256
  position.#binArrays.map(binArray => binArrayByAddress.get(binArray.address)),
210
257
  ])));
211
258
  }
259
+ static async claimAllFees(owner, positions, positionsPerClaim = MAX_POSITIONS_PER_CLAIM) {
260
+ if (positions.length === 0) {
261
+ return [];
262
+ }
263
+ const pool = positions[0].#pool;
264
+ for (const position of positions) {
265
+ assertPositionBelongsToPool(position.data, pool);
266
+ }
267
+ const claimablePositions = positions.filter(position => position.getBinInfos().some(({ feeX, feeY }) => feeX > 0n || feeY > 0n));
268
+ if (claimablePositions.length === 0) {
269
+ return [];
270
+ }
271
+ const [positionTokenAccounts, { ataX, ataY, preInstructions, postInstructions }] = await Promise.all([
272
+ Promise.all(claimablePositions.map(position => findPositionTokenAccount({ owner: owner.address, positionMint: position.data.positionMint }))),
273
+ processAssociatedTokenAccountPair({
274
+ owner,
275
+ tokenMintX: pool.tokenMintX,
276
+ tokenMintY: pool.tokenMintY,
277
+ tokenProgramX: pool.tokenProgramX,
278
+ tokenProgramY: pool.tokenProgramY,
279
+ }),
280
+ ]);
281
+ const claimFeeIxs = await Promise.all(claimablePositions.map((position, i) => position.#buildClaimFeeInstruction(owner, positionTokenAccounts[i], ataX, ataY)));
282
+ const chunks = chunk(claimFeeIxs, positionsPerClaim);
283
+ return chunks.map((claimFeeIxsChunk, i) => [
284
+ ...(i === 0 ? preInstructions : []),
285
+ ...claimFeeIxsChunk,
286
+ ...(i === chunks.length - 1 ? postInstructions : []),
287
+ ]);
288
+ }
212
289
  }
213
290
  export async function findPositionAddress(seeds, config) {
214
291
  const [address] = await generated.findPositionPda(seeds, config);
@@ -2,4 +2,30 @@ import { type Address } from "@solana/kit";
2
2
  export type ProgramConfig = {
3
3
  programAddress: Address;
4
4
  };
5
+ export type BinInfo = {
6
+ id: number;
7
+ price: bigint;
8
+ amountX: bigint;
9
+ amountY: bigint;
10
+ shares: bigint;
11
+ };
12
+ export type PositionBinInfo = {
13
+ bin: BinInfo;
14
+ amountX: bigint;
15
+ amountY: bigint;
16
+ shares: bigint;
17
+ feeX: bigint;
18
+ feeY: bigint;
19
+ };
20
+ export type PositionInfo = {
21
+ bins: PositionBinInfo[];
22
+ grossAmountX: bigint;
23
+ grossAmountY: bigint;
24
+ netAmountX: bigint;
25
+ netAmountY: bigint;
26
+ grossFeeX: bigint;
27
+ grossFeeY: bigint;
28
+ netFeeX: bigint;
29
+ netFeeY: bigint;
30
+ };
5
31
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entities/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,aAAa,GAAG;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/entities/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,aAAa,GAAG;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IAEd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,OAAO,CAAC;IAEb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IAEf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,eAAe,EAAE,CAAC;IAExB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IAErB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IAEnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,6 @@ export * as events from "./events";
4
4
  export * from "./entities";
5
5
  export * from "./utils/constants";
6
6
  export { NATIVE_MINT_ADDRESS } from "./utils/ata";
7
- export type { QuoteInput, QuoteOutput, PoolState, TransferFee } from "./quote";
7
+ export type { QuoteOutput, TransferFee } from "./quote";
8
8
  export * from "./program_addresses";
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC/E,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACxD,cAAc,qBAAqB,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { type QuoteInput, type QuoteOutput, type PoolState, type TransferFee } from "#wasm";
2
2
  export type { QuoteInput, QuoteOutput, PoolState, TransferFee };
3
3
  export declare function quote(input: QuoteInput): Promise<QuoteOutput>;
4
+ export declare function applyTransferFee(amount: bigint, fee?: TransferFee): Promise<bigint>;
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/quote/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,OAAO,CAAC;AAEf,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAEhE,wBAAsB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAGnE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/quote/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,WAAW,EACjB,MAAM,OAAO,CAAC;AAEf,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC;AAEhE,wBAAsB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAGnE;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAGzF"}
@@ -3,3 +3,7 @@ export async function quote(input) {
3
3
  const wasm = await loadWasm();
4
4
  return wasm.quote(input);
5
5
  }
6
+ export async function applyTransferFee(amount, fee) {
7
+ const wasm = await loadWasm();
8
+ return wasm.applyTransferFee(amount, fee);
9
+ }
@@ -61,4 +61,10 @@ export interface TransferFee {
61
61
  }
62
62
 
63
63
 
64
+ /**
65
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
66
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
67
+ */
68
+ export function applyTransferFee(amount: bigint, fee?: TransferFee | null): bigint;
69
+
64
70
  export function quote(input: QuoteInput): QuoteOutput;
@@ -2,6 +2,21 @@
2
2
  import { readFileSync } from 'node:fs';
3
3
 
4
4
 
5
+ /**
6
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
7
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
8
+ * @param {bigint} amount
9
+ * @param {TransferFee | null} [fee]
10
+ * @returns {bigint}
11
+ */
12
+ export function applyTransferFee(amount, fee) {
13
+ const ret = wasm.applyTransferFee(amount, isLikeNone(fee) ? 0 : addToExternrefTable0(fee));
14
+ if (ret[2]) {
15
+ throw takeFromExternrefTable0(ret[1]);
16
+ }
17
+ return BigInt.asUintN(64, ret[0]);
18
+ }
19
+
5
20
  /**
6
21
  * @param {QuoteInput} input
7
22
  * @returns {QuoteOutput}
@@ -1,6 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const applyTransferFee: (a: bigint, b: number) => [bigint, number, number];
4
5
  export const quote: (a: any) => [number, number, number];
5
6
  export const __wbindgen_malloc: (a: number, b: number) => number;
6
7
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
@@ -61,12 +61,19 @@ export interface TransferFee {
61
61
  }
62
62
 
63
63
 
64
+ /**
65
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
66
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
67
+ */
68
+ export function applyTransferFee(amount: bigint, fee?: TransferFee | null): bigint;
69
+
64
70
  export function quote(input: QuoteInput): QuoteOutput;
65
71
 
66
72
  export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
67
73
 
68
74
  export interface InitOutput {
69
75
  readonly memory: WebAssembly.Memory;
76
+ readonly applyTransferFee: (a: bigint, b: number) => [bigint, number, number];
70
77
  readonly quote: (a: any) => [number, number, number];
71
78
  readonly __wbindgen_malloc: (a: number, b: number) => number;
72
79
  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
@@ -1,5 +1,20 @@
1
1
  /* @ts-self-types="./picon_dlmm_quote.d.ts" */
2
2
 
3
+ /**
4
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
5
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
6
+ * @param {bigint} amount
7
+ * @param {TransferFee | null} [fee]
8
+ * @returns {bigint}
9
+ */
10
+ export function applyTransferFee(amount, fee) {
11
+ const ret = wasm.applyTransferFee(amount, isLikeNone(fee) ? 0 : addToExternrefTable0(fee));
12
+ if (ret[2]) {
13
+ throw takeFromExternrefTable0(ret[1]);
14
+ }
15
+ return BigInt.asUintN(64, ret[0]);
16
+ }
17
+
3
18
  /**
4
19
  * @param {QuoteInput} input
5
20
  * @returns {QuoteOutput}
@@ -1,6 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const applyTransferFee: (a: bigint, b: number) => [bigint, number, number];
4
5
  export const quote: (a: any) => [number, number, number];
5
6
  export const __wbindgen_malloc: (a: number, b: number) => number;
6
7
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picon-finance/dlmm-sdk",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Typescript SDK for interacting with the Picon DLMM protocol",
5
5
  "author": "Picon Finance",
6
6
  "license": "MIT",
@@ -14,7 +14,11 @@ import {
14
14
  } from "@solana-program/token-2022";
15
15
 
16
16
  import { generated } from "..";
17
- import { quote, type QuoteOutput } from "../quote";
17
+ import {
18
+ quote,
19
+ type QuoteOutput,
20
+ type TransferFee,
21
+ } from "../quote";
18
22
  import { isAccount } from "../utils/type_guards";
19
23
  import { resolveAdminConfigAddress } from "./admin_config";
20
24
  import { processAssociatedTokenAccountPair } from "../utils/ata";
@@ -78,6 +82,14 @@ export class Pool {
78
82
  public get tokenProgramX() { return this.#mintX.programAddress; }
79
83
  public get tokenProgramY() { return this.#mintY.programAddress; }
80
84
 
85
+ public async getTransferFees(): Promise<{ transferFeeX?: TransferFee, transferFeeY?: TransferFee }> {
86
+ const { epoch } = await this.#rpc.getEpochInfo().send();
87
+ return {
88
+ transferFeeX: resolveTransferFee(this.#mintX.data, epoch),
89
+ transferFeeY: resolveTransferFee(this.#mintY.data, epoch),
90
+ };
91
+ }
92
+
81
93
  public async refresh(data: Account<generated.Pool>): Promise<void>;
82
94
  public async refresh(config?: FetchAccountConfig): Promise<void>;
83
95
  public async refresh(arg?: Account<generated.Pool> | FetchAccountConfig) {
@@ -211,15 +223,12 @@ export class Pool {
211
223
  { programAddress: this.#programAddress },
212
224
  );
213
225
 
214
- const [maybeBinArrays, { epoch }] = await Promise.all([
226
+ const [maybeBinArrays, { transferFeeX, transferFeeY }] = await Promise.all([
215
227
  generated.fetchAllMaybeBinArray(this.#rpc, binArrayAddresses),
216
- this.#rpc.getEpochInfo().send(),
228
+ this.getTransferFees(),
217
229
  ]);
218
230
  const binArrays = maybeBinArrays.map(maybeBinArray => maybeBinArray.exists ? maybeBinArray.data : undefined);
219
231
 
220
- const transferFeeX = resolveTransferFee(this.#mintX.data, epoch);
221
- const transferFeeY = resolveTransferFee(this.#mintY.data, epoch);
222
-
223
232
  return await quote({
224
233
  amountIn,
225
234
  xToY,
@@ -12,6 +12,7 @@ import {
12
12
  } from "@solana/kit";
13
13
 
14
14
  import { generated } from "..";
15
+ import { applyTransferFee } from "../quote";
15
16
  import {
16
17
  findPositionTokenAccount,
17
18
  processAssociatedTokenAccountPair,
@@ -27,9 +28,9 @@ import {
27
28
  assertPositionBelongsToPool,
28
29
  } from "../utils/assert";
29
30
  import { isUndefined } from "../utils/type_guards";
30
- import { fetchInChunks } from "../utils/chunk";
31
+ import { chunk, fetchInChunks } from "../utils/chunk";
31
32
  import { resolveAdminConfigAddress } from "./admin_config";
32
- import { MAX_ACCOUNTS_PER_RPC_BATCH } from "../utils/constants";
33
+ import { MAX_ACCOUNTS_PER_RPC_BATCH, MAX_POSITIONS_PER_CLAIM } from "../utils/constants";
33
34
 
34
35
  import { Pool } from "./pool";
35
36
  import {
@@ -38,7 +39,11 @@ import {
38
39
  createAllBinArrays,
39
40
  fetchAllBinArraysChunked,
40
41
  } from "./bin_array";
41
- import { type ProgramConfig } from "./types";
42
+ import {
43
+ type PositionInfo,
44
+ type ProgramConfig,
45
+ type PositionBinInfo,
46
+ } from "./types";
42
47
 
43
48
  export class Position {
44
49
  #pool: Pool;
@@ -66,29 +71,75 @@ export class Position {
66
71
  return { lowerBinArray, upperBinArray };
67
72
  }
68
73
 
69
- public getPendingFee(): { amountX: bigint, amountY: bigint } {
74
+ public getBinInfos(): PositionBinInfo[] {
70
75
  const { lowerBinId, upperBinId, feeStates } = this.data;
71
76
  const lowerBinArrayIndex = getBinArrayIndex(lowerBinId);
77
+ const binCount = upperBinId - lowerBinId + 1;
72
78
 
73
- return Array.from({ length: upperBinId - lowerBinId + 1 }, (_, i) => i)
74
- .flatMap(i => {
75
- const feeState = feeStates[i]!;
76
- if (feeState.shares === 0n) return [];
77
-
79
+ return feeStates
80
+ .slice(0, binCount)
81
+ .map((feeState, i) => {
78
82
  const binId = lowerBinId + i;
79
-
80
83
  const binIndex = getBinIndex(binId);
81
- const binArrayIndex = getBinArrayIndex(binId) - lowerBinArrayIndex;
82
-
83
- const binArray = this.#binArrays[binArrayIndex];
84
+ const localBinArrayIndex = getBinArrayIndex(binId) - lowerBinArrayIndex;
85
+ const binArray = this.#binArrays[localBinArrayIndex];
84
86
  const bin = binArray.bins[binIndex];
85
87
 
86
- return bin.totalShares > 0n ? [{ feeState, bin }] : [];
87
- })
88
- .reduce((total, { feeState, bin }) => ({
89
- amountX: total.amountX + earnedAmount(bin.feePerShareX, feeState.feePerShareCheckpointX, feeState.shares),
90
- amountY: total.amountY + earnedAmount(bin.feePerShareY, feeState.feePerShareCheckpointY, feeState.shares),
91
- }), { amountX: 0n, amountY: 0n });
88
+ const amountX = bin.totalShares > 0n ? (feeState.shares * bin.amountX) / bin.totalShares : 0n;
89
+ const amountY = bin.totalShares > 0n ? (feeState.shares * bin.amountY) / bin.totalShares : 0n;
90
+
91
+ let feeX = feeState.feeOwedX;
92
+ let feeY = feeState.feeOwedY;
93
+ if (feeState.shares > 0n && bin.totalShares > 0n) {
94
+ feeX += earnedAmount(bin.feePerShareX, feeState.feePerShareCheckpointX, feeState.shares);
95
+ feeY += earnedAmount(bin.feePerShareY, feeState.feePerShareCheckpointY, feeState.shares);
96
+ }
97
+
98
+ return {
99
+ bin: {
100
+ id: binId,
101
+ price: bin.price,
102
+ amountX: bin.amountX,
103
+ amountY: bin.amountY,
104
+ shares: bin.totalShares
105
+ },
106
+ amountX,
107
+ amountY,
108
+ shares: feeState.shares,
109
+ feeX,
110
+ feeY,
111
+ };
112
+ });
113
+ }
114
+
115
+ public async getInfo(): Promise<PositionInfo> {
116
+ const bins = this.getBinInfos();
117
+ const gross = bins.reduce((acc, { amountX, amountY, feeX, feeY }) => ({
118
+ amountX: acc.amountX + amountX,
119
+ amountY: acc.amountY + amountY,
120
+ feeX: acc.feeX + feeX,
121
+ feeY: acc.feeY + feeY,
122
+ }), { amountX: 0n, amountY: 0n, feeX: 0n, feeY: 0n });
123
+
124
+ const { transferFeeX, transferFeeY } = await this.#pool.getTransferFees();
125
+ const [netAmountX, netAmountY, netFeeX, netFeeY] = await Promise.all([
126
+ applyTransferFee(gross.amountX, transferFeeX),
127
+ applyTransferFee(gross.amountY, transferFeeY),
128
+ applyTransferFee(gross.feeX, transferFeeX),
129
+ applyTransferFee(gross.feeY, transferFeeY),
130
+ ]);
131
+
132
+ return {
133
+ bins,
134
+ grossAmountX: gross.amountX,
135
+ grossAmountY: gross.amountY,
136
+ netAmountX,
137
+ netAmountY,
138
+ grossFeeX: gross.feeX,
139
+ grossFeeY: gross.feeY,
140
+ netFeeX,
141
+ netFeeY,
142
+ };
92
143
  }
93
144
 
94
145
  public async refresh(data: [Account<generated.Position>, Account<generated.BinArray>[]]): Promise<void>;
@@ -173,31 +224,18 @@ export class Position {
173
224
  return [...preInstructions, depositByWeightIx, ...postInstructions];
174
225
  }
175
226
 
176
- public async claimFee(
227
+ async #buildClaimFeeInstruction(
177
228
  owner: TransactionSigner,
229
+ positionTokenAccount: Address,
230
+ tokenOwnerAccountX: Address,
231
+ tokenOwnerAccountY: Address,
178
232
  range?: BinIdRange,
179
- ): Promise<Instruction[]> {
233
+ ): Promise<Instruction> {
180
234
  const { lowerBinId, upperBinId } = range ?? this.data;
181
235
  assertRangeWithinRange({ lowerBinId, upperBinId }, this.data);
182
236
 
183
- const [positionTokenAccount, {
184
- ataX: tokenOwnerAccountX,
185
- ataY: tokenOwnerAccountY,
186
- preInstructions,
187
- postInstructions
188
- }] = await Promise.all([
189
- findPositionTokenAccount({ owner: owner.address, positionMint: this.data.positionMint }),
190
- processAssociatedTokenAccountPair({
191
- owner,
192
- tokenMintX: this.#pool.tokenMintX,
193
- tokenMintY: this.#pool.tokenMintY,
194
- tokenProgramX: this.#pool.tokenProgramX,
195
- tokenProgramY: this.#pool.tokenProgramY,
196
- }),
197
- ]);
198
-
199
237
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
200
- const claimFeeIx = generated.getClaimFeeInstruction({
238
+ return generated.getClaimFeeInstruction({
201
239
  owner,
202
240
  pool: this.#pool.address,
203
241
  position: this.address,
@@ -217,6 +255,31 @@ export class Position {
217
255
  }, {
218
256
  programAddress: this.#pool.programAddress
219
257
  });
258
+ }
259
+
260
+ public async claimFee(
261
+ owner: TransactionSigner,
262
+ range?: BinIdRange,
263
+ ): Promise<Instruction[]> {
264
+ const [positionTokenAccount, {
265
+ ataX: tokenOwnerAccountX,
266
+ ataY: tokenOwnerAccountY,
267
+ preInstructions,
268
+ postInstructions
269
+ }] = await Promise.all([
270
+ findPositionTokenAccount({ owner: owner.address, positionMint: this.data.positionMint }),
271
+ processAssociatedTokenAccountPair({
272
+ owner,
273
+ tokenMintX: this.#pool.tokenMintX,
274
+ tokenMintY: this.#pool.tokenMintY,
275
+ tokenProgramX: this.#pool.tokenProgramX,
276
+ tokenProgramY: this.#pool.tokenProgramY,
277
+ }),
278
+ ]);
279
+
280
+ const claimFeeIx = await this.#buildClaimFeeInstruction(
281
+ owner, positionTokenAccount, tokenOwnerAccountX, tokenOwnerAccountY, range,
282
+ );
220
283
 
221
284
  return [...preInstructions, claimFeeIx, ...postInstructions];
222
285
  }
@@ -348,6 +411,49 @@ export class Position {
348
411
  position.#binArrays.map(binArray => binArrayByAddress.get(binArray.address)!),
349
412
  ])));
350
413
  }
414
+
415
+ public static async claimAllFees(
416
+ owner: TransactionSigner,
417
+ positions: Position[],
418
+ positionsPerClaim: number = MAX_POSITIONS_PER_CLAIM,
419
+ ): Promise<Instruction[][]> {
420
+ if (positions.length === 0) {
421
+ return [];
422
+ }
423
+
424
+ const pool = positions[0].#pool;
425
+ for (const position of positions) {
426
+ assertPositionBelongsToPool(position.data, pool);
427
+ }
428
+
429
+ const claimablePositions = positions.filter(position =>
430
+ position.getBinInfos().some(({ feeX, feeY }) => feeX > 0n || feeY > 0n));
431
+ if (claimablePositions.length === 0) {
432
+ return [];
433
+ }
434
+
435
+ const [positionTokenAccounts, { ataX, ataY, preInstructions, postInstructions }] = await Promise.all([
436
+ Promise.all(claimablePositions.map(position =>
437
+ findPositionTokenAccount({ owner: owner.address, positionMint: position.data.positionMint }))),
438
+ processAssociatedTokenAccountPair({
439
+ owner,
440
+ tokenMintX: pool.tokenMintX,
441
+ tokenMintY: pool.tokenMintY,
442
+ tokenProgramX: pool.tokenProgramX,
443
+ tokenProgramY: pool.tokenProgramY,
444
+ }),
445
+ ]);
446
+
447
+ const claimFeeIxs = await Promise.all(claimablePositions.map((position, i) =>
448
+ position.#buildClaimFeeInstruction(owner, positionTokenAccounts[i], ataX, ataY)));
449
+
450
+ const chunks = chunk(claimFeeIxs, positionsPerClaim);
451
+ return chunks.map((claimFeeIxsChunk, i) => [
452
+ ...(i === 0 ? preInstructions : []),
453
+ ...claimFeeIxsChunk,
454
+ ...(i === chunks.length - 1 ? postInstructions : []),
455
+ ]);
456
+ }
351
457
  }
352
458
 
353
459
  export async function findPositionAddress(
@@ -1,3 +1,39 @@
1
1
  import { type Address } from "@solana/kit";
2
2
 
3
- export type ProgramConfig = { programAddress: Address };
3
+ export type ProgramConfig = { programAddress: Address };
4
+
5
+ export type BinInfo = {
6
+ id: number;
7
+ price: bigint;
8
+
9
+ amountX: bigint;
10
+ amountY: bigint;
11
+ shares: bigint;
12
+ }
13
+
14
+ export type PositionBinInfo = {
15
+ bin: BinInfo;
16
+
17
+ amountX: bigint;
18
+ amountY: bigint;
19
+ shares: bigint;
20
+
21
+ feeX: bigint;
22
+ feeY: bigint;
23
+ }
24
+
25
+ export type PositionInfo = {
26
+ bins: PositionBinInfo[];
27
+
28
+ grossAmountX: bigint;
29
+ grossAmountY: bigint;
30
+
31
+ netAmountX: bigint;
32
+ netAmountY: bigint;
33
+
34
+ grossFeeX: bigint;
35
+ grossFeeY: bigint;
36
+
37
+ netFeeX: bigint;
38
+ netFeeY: bigint;
39
+ };
package/src/index.ts CHANGED
@@ -4,5 +4,5 @@ export * as events from "./events";
4
4
  export * from "./entities";
5
5
  export * from "./utils/constants";
6
6
  export { NATIVE_MINT_ADDRESS } from "./utils/ata";
7
- export type { QuoteInput, QuoteOutput, PoolState, TransferFee } from "./quote";
7
+ export type { QuoteOutput, TransferFee } from "./quote";
8
8
  export * from "./program_addresses";
@@ -12,3 +12,8 @@ export async function quote(input: QuoteInput): Promise<QuoteOutput> {
12
12
  const wasm = await loadWasm();
13
13
  return wasm.quote(input);
14
14
  }
15
+
16
+ export async function applyTransferFee(amount: bigint, fee?: TransferFee): Promise<bigint> {
17
+ const wasm = await loadWasm();
18
+ return wasm.applyTransferFee(amount, fee);
19
+ }
@@ -61,4 +61,10 @@ export interface TransferFee {
61
61
  }
62
62
 
63
63
 
64
+ /**
65
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
66
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
67
+ */
68
+ export function applyTransferFee(amount: bigint, fee?: TransferFee | null): bigint;
69
+
64
70
  export function quote(input: QuoteInput): QuoteOutput;
@@ -2,6 +2,21 @@
2
2
  import { readFileSync } from 'node:fs';
3
3
 
4
4
 
5
+ /**
6
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
7
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
8
+ * @param {bigint} amount
9
+ * @param {TransferFee | null} [fee]
10
+ * @returns {bigint}
11
+ */
12
+ export function applyTransferFee(amount, fee) {
13
+ const ret = wasm.applyTransferFee(amount, isLikeNone(fee) ? 0 : addToExternrefTable0(fee));
14
+ if (ret[2]) {
15
+ throw takeFromExternrefTable0(ret[1]);
16
+ }
17
+ return BigInt.asUintN(64, ret[0]);
18
+ }
19
+
5
20
  /**
6
21
  * @param {QuoteInput} input
7
22
  * @returns {QuoteOutput}
@@ -1,6 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const applyTransferFee: (a: bigint, b: number) => [bigint, number, number];
4
5
  export const quote: (a: any) => [number, number, number];
5
6
  export const __wbindgen_malloc: (a: number, b: number) => number;
6
7
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
@@ -61,12 +61,19 @@ export interface TransferFee {
61
61
  }
62
62
 
63
63
 
64
+ /**
65
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
66
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
67
+ */
68
+ export function applyTransferFee(amount: bigint, fee?: TransferFee | null): bigint;
69
+
64
70
  export function quote(input: QuoteInput): QuoteOutput;
65
71
 
66
72
  export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
67
73
 
68
74
  export interface InitOutput {
69
75
  readonly memory: WebAssembly.Memory;
76
+ readonly applyTransferFee: (a: bigint, b: number) => [bigint, number, number];
70
77
  readonly quote: (a: any) => [number, number, number];
71
78
  readonly __wbindgen_malloc: (a: number, b: number) => number;
72
79
  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
@@ -1,5 +1,20 @@
1
1
  /* @ts-self-types="./picon_dlmm_quote.d.ts" */
2
2
 
3
+ /**
4
+ * Applies `fee` to a gross `amount`, returning the net amount — or `amount` unchanged when
5
+ * `fee` is `None` (the mint has no `TransferFeeConfig` extension).
6
+ * @param {bigint} amount
7
+ * @param {TransferFee | null} [fee]
8
+ * @returns {bigint}
9
+ */
10
+ export function applyTransferFee(amount, fee) {
11
+ const ret = wasm.applyTransferFee(amount, isLikeNone(fee) ? 0 : addToExternrefTable0(fee));
12
+ if (ret[2]) {
13
+ throw takeFromExternrefTable0(ret[1]);
14
+ }
15
+ return BigInt.asUintN(64, ret[0]);
16
+ }
17
+
3
18
  /**
4
19
  * @param {QuoteInput} input
5
20
  * @returns {QuoteOutput}
@@ -1,6 +1,7 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const applyTransferFee: (a: bigint, b: number) => [bigint, number, number];
4
5
  export const quote: (a: any) => [number, number, number];
5
6
  export const __wbindgen_malloc: (a: number, b: number) => number;
6
7
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;