@picon-finance/dlmm-sdk 1.12.0 → 1.14.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.
@@ -1,6 +1,7 @@
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
3
  import { type FeeRates, type QuoteOutput, type TransferFee } from "../quote";
4
+ import { type ExtraAccount, type TransferAccounts as TransferHookAccounts } from "../utils/transfer_hook";
4
5
  import { Position } from "./position";
5
6
  import { type ProgramConfig } from "./types";
6
7
  export declare class Pool {
@@ -42,6 +43,8 @@ export declare class Pool {
42
43
  quoteOutput: QuoteOutput;
43
44
  instructions: Instruction[];
44
45
  }>;
46
+ resolveTransferHookAccountsX(transferAccounts: TransferHookAccounts): Promise<ExtraAccount[]>;
47
+ resolveTransferHookAccountsY(transferAccounts: TransferHookAccounts): Promise<ExtraAccount[]>;
45
48
  quote(params: {
46
49
  xToY: boolean;
47
50
  amountIn: bigint;
@@ -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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"}
@@ -7,6 +7,7 @@ import { resolveAdminConfigAddress } from "./admin_config";
7
7
  import { processAssociatedTokenAccountPair } from "../utils/ata";
8
8
  import { getBinArrayIndex, generateBinArrayIndicesForSwap, generateBinArrayIndicesForSync, } from "../utils/bin_location";
9
9
  import { resolveTransferFee } from "../utils/transfer_fee";
10
+ import { resolveTransferHookProgram, resolveTransferHookExtraAccounts, toRemainingAccountMeta, } from "../utils/transfer_hook";
10
11
  import { MAX_BIN_ARRAYS_PER_TRAVERSAL } from "../utils/constants";
11
12
  import { Position, openPosition, closePosition, findPositionAddress, findAllPositionAddresses, } from "./position";
12
13
  import { findAllBinArrayAddresses } from "./bin_array";
@@ -80,9 +81,31 @@ export class Pool {
80
81
  amountX: xToY ? amountIn : undefined,
81
82
  amountY: xToY ? undefined : amountIn,
82
83
  });
83
- const remainingAccountsScheme = {
84
- groups: [{ kind: generated.AccountKind.BinArray, count: binArrayAddresses.length }],
85
- };
84
+ // Each mint's hook accounts are resolved against whichever role it plays in this swap —
85
+ // input (user -> vault, owner = user) or output (vault -> user, owner = pool PDA).
86
+ const xTransferAccounts = xToY
87
+ ? { source: tokenUserAccountX, mint: this.tokenMintX, destination: this.tokenVaultX, owner: user.address }
88
+ : { source: this.tokenVaultX, mint: this.tokenMintX, destination: tokenUserAccountX, owner: this.address };
89
+ const yTransferAccounts = xToY
90
+ ? { source: this.tokenVaultY, mint: this.tokenMintY, destination: tokenUserAccountY, owner: this.address }
91
+ : { source: tokenUserAccountY, mint: this.tokenMintY, destination: this.tokenVaultY, owner: user.address };
92
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
93
+ this.resolveTransferHookAccountsX(xTransferAccounts),
94
+ this.resolveTransferHookAccountsY(yTransferAccounts),
95
+ ]);
96
+ const groups = [
97
+ { kind: generated.AccountKind.BinArray, count: binArrayAddresses.length },
98
+ ];
99
+ const remainingAccounts = binArrayAddresses.map(address => ({ address, role: AccountRole.WRITABLE }));
100
+ if (transferHookXAccounts.length > 0) {
101
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
102
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
103
+ }
104
+ if (transferHookYAccounts.length > 0) {
105
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
106
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
107
+ }
108
+ const remainingAccountsScheme = { groups };
86
109
  const swapIx = generated.getSwapInstruction({
87
110
  user,
88
111
  pool: this.address,
@@ -99,11 +122,26 @@ export class Pool {
99
122
  xToY,
100
123
  remainingAccountsScheme,
101
124
  }, { programAddress: this.#programAddress });
102
- const remainingAccounts = binArrayAddresses.map(address => ({ address, role: AccountRole.WRITABLE }));
103
125
  const swapIxWithBinArrays = { ...swapIx, accounts: [...swapIx.accounts, ...remainingAccounts] };
104
126
  const instructions = [...preInstructions, swapIxWithBinArrays, ...postInstructions];
105
127
  return { quoteOutput, instructions };
106
128
  }
129
+ // Public so Position (which only holds a reference to this Pool, not its private mint data)
130
+ // can resolve hook accounts for its own deposit/withdraw/claimFee instructions without ever
131
+ // needing a Mint account itself — empty when that mint has no active transfer hook, in which
132
+ // case the caller should add no group to the scheme for it.
133
+ async resolveTransferHookAccountsX(transferAccounts) {
134
+ return await this.#resolveTransferHookAccounts(this.#mintX, transferAccounts);
135
+ }
136
+ async resolveTransferHookAccountsY(transferAccounts) {
137
+ return await this.#resolveTransferHookAccounts(this.#mintY, transferAccounts);
138
+ }
139
+ async #resolveTransferHookAccounts(mint, transferAccounts) {
140
+ const hookProgram = resolveTransferHookProgram(mint.data);
141
+ if (!hookProgram)
142
+ return [];
143
+ return await resolveTransferHookExtraAccounts(this.#rpc, hookProgram, transferAccounts);
144
+ }
107
145
  async quote(params) {
108
146
  const { quoteOutput } = await this.#quote(params);
109
147
  return quoteOutput;
@@ -163,6 +201,26 @@ export class Pool {
163
201
  tokenProgramY: this.tokenProgramY,
164
202
  }),
165
203
  ]);
204
+ // Both legs move vault -> admin, signed by the pool PDA (not the authority signer, which
205
+ // only gates the on-chain permission check).
206
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
207
+ this.resolveTransferHookAccountsX({
208
+ source: this.tokenVaultX, mint: this.tokenMintX, destination: tokenAdminAccountX, owner: this.address,
209
+ }),
210
+ this.resolveTransferHookAccountsY({
211
+ source: this.tokenVaultY, mint: this.tokenMintY, destination: tokenAdminAccountY, owner: this.address,
212
+ }),
213
+ ]);
214
+ const groups = [];
215
+ const remainingAccounts = [];
216
+ if (transferHookXAccounts.length > 0) {
217
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
218
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
219
+ }
220
+ if (transferHookYAccounts.length > 0) {
221
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
222
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
223
+ }
166
224
  const claimProtocolFeeIx = await generated.getClaimProtocolFeeInstructionAsync({
167
225
  authority,
168
226
  pool: this.address,
@@ -173,11 +231,15 @@ export class Pool {
173
231
  tokenVaultY: this.tokenVaultY,
174
232
  tokenAdminAccountX,
175
233
  tokenAdminAccountY,
176
- remainingAccountsScheme: null,
234
+ remainingAccountsScheme: { groups },
177
235
  tokenProgramX: this.tokenProgramX,
178
236
  tokenProgramY: this.tokenProgramY,
179
237
  }, { programAddress: this.#programAddress });
180
- return [...preInstructions, claimProtocolFeeIx, ...postInstructions];
238
+ const claimProtocolFeeIxWithHookAccounts = {
239
+ ...claimProtocolFeeIx,
240
+ accounts: [...claimProtocolFeeIx.accounts, ...remainingAccounts],
241
+ };
242
+ return [...preInstructions, claimProtocolFeeIxWithHookAccounts, ...postInstructions];
181
243
  }
182
244
  static async load(rpc, address, programAddress, config) {
183
245
  const pool = await generated.fetchPool(rpc, address, config);
@@ -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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"}
@@ -2,6 +2,7 @@ import { decodeAccount, assertAccountExists, fetchEncodedAccounts, } from "@sola
2
2
  import { generated } from "..";
3
3
  import { applyTransferFee } from "../quote";
4
4
  import { findPositionTokenAccount, processAssociatedTokenAccountPair, } from "../utils/ata";
5
+ import { toRemainingAccountMeta } from "../utils/transfer_hook";
5
6
  import { getBinIndex, getBinArrayIndex, getPositionBinArrayIndices, } from "../utils/bin_location";
6
7
  import { assertRangeWithinRange, assertPositionBelongsToPool, } from "../utils/assert";
7
8
  import { isUndefined } from "../utils/type_guards";
@@ -113,6 +114,25 @@ export class Position {
113
114
  amountY,
114
115
  }),
115
116
  ]);
117
+ // Both legs move owner -> vault (deposit), signed by owner itself.
118
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
119
+ this.#pool.resolveTransferHookAccountsX({
120
+ source: tokenOwnerAccountX, mint: this.#pool.tokenMintX, destination: this.#pool.tokenVaultX, owner: owner.address,
121
+ }),
122
+ this.#pool.resolveTransferHookAccountsY({
123
+ source: tokenOwnerAccountY, mint: this.#pool.tokenMintY, destination: this.#pool.tokenVaultY, owner: owner.address,
124
+ }),
125
+ ]);
126
+ const groups = [];
127
+ const remainingAccounts = [];
128
+ if (transferHookXAccounts.length > 0) {
129
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
130
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
131
+ }
132
+ if (transferHookYAccounts.length > 0) {
133
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
134
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
135
+ }
116
136
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
117
137
  const depositByWeightIx = generated.getDepositByWeightInstruction({
118
138
  owner,
@@ -136,17 +156,40 @@ export class Position {
136
156
  distributionMode,
137
157
  lowerBinId,
138
158
  binWeights,
139
- remainingAccountsScheme: null,
159
+ remainingAccountsScheme: { groups },
140
160
  }, {
141
161
  programAddress: this.#pool.programAddress
142
162
  });
143
- return [...preInstructions, depositByWeightIx, ...postInstructions];
163
+ const depositByWeightIxWithHookAccounts = {
164
+ ...depositByWeightIx,
165
+ accounts: [...depositByWeightIx.accounts, ...remainingAccounts],
166
+ };
167
+ return [...preInstructions, depositByWeightIxWithHookAccounts, ...postInstructions];
144
168
  }
145
169
  async #buildClaimFeeInstruction(owner, positionTokenAccount, tokenOwnerAccountX, tokenOwnerAccountY, range) {
146
170
  const { lowerBinId, upperBinId } = range ?? this.data;
147
171
  assertRangeWithinRange({ lowerBinId, upperBinId }, this.data);
172
+ // Both legs move vault -> owner (fee claim), signed by the pool PDA.
173
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
174
+ this.#pool.resolveTransferHookAccountsX({
175
+ source: this.#pool.tokenVaultX, mint: this.#pool.tokenMintX, destination: tokenOwnerAccountX, owner: this.#pool.address,
176
+ }),
177
+ this.#pool.resolveTransferHookAccountsY({
178
+ source: this.#pool.tokenVaultY, mint: this.#pool.tokenMintY, destination: tokenOwnerAccountY, owner: this.#pool.address,
179
+ }),
180
+ ]);
181
+ const groups = [];
182
+ const remainingAccounts = [];
183
+ if (transferHookXAccounts.length > 0) {
184
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
185
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
186
+ }
187
+ if (transferHookYAccounts.length > 0) {
188
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
189
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
190
+ }
148
191
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
149
- return generated.getClaimFeeInstruction({
192
+ const claimFeeIx = generated.getClaimFeeInstruction({
150
193
  owner,
151
194
  pool: this.#pool.address,
152
195
  position: this.address,
@@ -163,10 +206,11 @@ export class Position {
163
206
  upperBinArray,
164
207
  lowerBinId,
165
208
  upperBinId,
166
- remainingAccountsScheme: null,
209
+ remainingAccountsScheme: { groups },
167
210
  }, {
168
211
  programAddress: this.#pool.programAddress
169
212
  });
213
+ return { ...claimFeeIx, accounts: [...claimFeeIx.accounts, ...remainingAccounts] };
170
214
  }
171
215
  async claimFee(owner, range) {
172
216
  const [positionTokenAccount, { ataX: tokenOwnerAccountX, ataY: tokenOwnerAccountY, preInstructions, postInstructions }] = await Promise.all([
@@ -195,6 +239,25 @@ export class Position {
195
239
  tokenProgramY: this.#pool.tokenProgramY,
196
240
  }),
197
241
  ]);
242
+ // Both legs move vault -> owner (withdraw), signed by the pool PDA.
243
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
244
+ this.#pool.resolveTransferHookAccountsX({
245
+ source: this.#pool.tokenVaultX, mint: this.#pool.tokenMintX, destination: tokenOwnerAccountX, owner: this.#pool.address,
246
+ }),
247
+ this.#pool.resolveTransferHookAccountsY({
248
+ source: this.#pool.tokenVaultY, mint: this.#pool.tokenMintY, destination: tokenOwnerAccountY, owner: this.#pool.address,
249
+ }),
250
+ ]);
251
+ const groups = [];
252
+ const remainingAccounts = [];
253
+ if (transferHookXAccounts.length > 0) {
254
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
255
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
256
+ }
257
+ if (transferHookYAccounts.length > 0) {
258
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
259
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
260
+ }
198
261
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
199
262
  const withdrawIx = generated.getWithdrawInstruction({
200
263
  owner,
@@ -214,11 +277,15 @@ export class Position {
214
277
  lowerBinId,
215
278
  upperBinId,
216
279
  bpsToRemove,
217
- remainingAccountsScheme: null,
280
+ remainingAccountsScheme: { groups },
218
281
  }, {
219
282
  programAddress: this.#pool.programAddress
220
283
  });
221
- return [...preInstructions, withdrawIx, ...postInstructions];
284
+ const withdrawIxWithHookAccounts = {
285
+ ...withdrawIx,
286
+ accounts: [...withdrawIx.accounts, ...remainingAccounts],
287
+ };
288
+ return [...preInstructions, withdrawIxWithHookAccounts, ...postInstructions];
222
289
  }
223
290
  async close(owner) {
224
291
  return await closePosition({ owner, positionMint: this.data.positionMint, position: this.address }, { programAddress: this.#pool.programAddress });
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { DistributionMode } from "./generated/types/distributionMode";
3
3
  export * as events from "./events";
4
4
  export * from "./entities";
5
5
  export * from "./utils/constants";
6
+ export * from "./utils/price";
6
7
  export { NATIVE_MINT_ADDRESS } from "./utils/ata";
7
8
  export type { QuoteOutput, TransferFee, FeeRates } from "./quote";
8
9
  export * from "./program_addresses";
@@ -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,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAClE,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,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAClE,cAAc,qBAAqB,CAAC"}
package/dist/index.js CHANGED
@@ -3,5 +3,6 @@ export { DistributionMode } from "./generated/types/distributionMode";
3
3
  export * as events from "./events";
4
4
  export * from "./entities";
5
5
  export * from "./utils/constants";
6
+ export * from "./utils/price";
6
7
  export { NATIVE_MINT_ADDRESS } from "./utils/ata";
7
8
  export * from "./program_addresses";
@@ -0,0 +1,4 @@
1
+ export declare function getBaseBinPrice(binStep: number): bigint;
2
+ export declare function getBinPrice(baseBinPrice: bigint, binId: number): bigint | undefined;
3
+ export declare function getBinPriceFromStep(binStep: number, binId: number): bigint | undefined;
4
+ //# sourceMappingURL=price.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price.d.ts","sourceRoot":"","sources":["../../src/utils/price.ts"],"names":[],"mappings":"AAGA,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBnF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEtF"}
@@ -0,0 +1,27 @@
1
+ import { MAX_BASIS_POINTS } from "./constants";
2
+ import { expBySquaring, MAX, MAX_EXPONENTIAL, ONE, SCALE_OFFSET } from "./u64x64";
3
+ export function getBaseBinPrice(binStep) {
4
+ const priceStep = (BigInt(binStep) << SCALE_OFFSET) / BigInt(MAX_BASIS_POINTS);
5
+ return ONE + priceStep;
6
+ }
7
+ export function getBinPrice(baseBinPrice, binId) {
8
+ if (baseBinPrice < 0n || baseBinPrice > MAX) {
9
+ throw new Error(`getBinPrice: baseBinPrice out of u128 range (${baseBinPrice})`);
10
+ }
11
+ if (baseBinPrice <= ONE)
12
+ return;
13
+ if (binId === 0)
14
+ return ONE;
15
+ const invert = binId > 0;
16
+ const absBinId = Math.abs(binId);
17
+ if (absBinId >= MAX_EXPONENTIAL)
18
+ return;
19
+ const invertedBase = MAX / baseBinPrice;
20
+ const price = expBySquaring(invertedBase, absBinId);
21
+ if (price === 0n)
22
+ return;
23
+ return invert ? MAX / price : price;
24
+ }
25
+ export function getBinPriceFromStep(binStep, binId) {
26
+ return getBinPrice(getBaseBinPrice(binStep), binId);
27
+ }
@@ -1,4 +1,4 @@
1
- import { createSolanaRpc, type Address } from "@solana/kit";
1
+ import { AccountRole, createSolanaRpc, type Address } from "@solana/kit";
2
2
  import type { Mint } from "@solana-program/token-2022";
3
3
  export declare function resolveTransferHookProgram(mint: Mint): Address | undefined;
4
4
  export declare function getExtraAccountMetaListAddress(mint: Address, hookProgram: Address): Promise<Address>;
@@ -14,4 +14,8 @@ export type TransferAccounts = {
14
14
  owner: Address;
15
15
  };
16
16
  export declare function resolveTransferHookExtraAccounts(rpc: ReturnType<typeof createSolanaRpc>, hookProgram: Address, transferAccounts: TransferAccounts, instructionData?: Uint8Array): Promise<ExtraAccount[]>;
17
+ export declare function toRemainingAccountMeta({ address, isSigner, isWritable }: ExtraAccount): {
18
+ address: Address;
19
+ role: AccountRole;
20
+ };
17
21
  //# sourceMappingURL=transfer_hook.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transfer_hook.d.ts","sourceRoot":"","sources":["../../src/utils/transfer_hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,eAAe,EAEf,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,IAAI,EAAa,MAAM,4BAA4B,CAAC;AA8BlE,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAQ1E;AAKD,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,OAAO,CAAC,CAQlB;AA4ED,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AA+CF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AASF,wBAAsB,gCAAgC,CACpD,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACvC,WAAW,EAAE,OAAO,EACpB,gBAAgB,EAAE,gBAAgB,EAClC,eAAe,GAAE,UAA6B,GAC7C,OAAO,CAAC,YAAY,EAAE,CAAC,CA2DzB"}
1
+ {"version":3,"file":"transfer_hook.d.ts","sourceRoot":"","sources":["../../src/utils/transfer_hook.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAIX,eAAe,EAEf,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,IAAI,EAAa,MAAM,4BAA4B,CAAC;AA8BlE,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAQ1E;AAKD,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,OAAO,EACb,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,OAAO,CAAC,CAQlB;AA4ED,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AA+CF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAWF,wBAAsB,gCAAgC,CACpD,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACvC,WAAW,EAAE,OAAO,EACpB,gBAAgB,EAAE,gBAAgB,EAClC,eAAe,GAAE,UAA6B,GAC7C,OAAO,CAAC,YAAY,EAAE,CAAC,CAqEzB;AAGD,wBAAgB,sBAAsB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,YAAY;;;EAKrF"}
@@ -1,4 +1,4 @@
1
- import { getAddressEncoder, getAddressDecoder, getProgramDerivedAddress, unwrapOption, } from "@solana/kit";
1
+ import { AccountRole, getAddressEncoder, getAddressDecoder, getProgramDerivedAddress, unwrapOption, } from "@solana/kit";
2
2
  import { SYSTEM_PROGRAM_ADDRESS } from "@solana-program/system";
3
3
  const EXTRA_ACCOUNT_METAS_SEED = "extra-account-metas";
4
4
  // spl-tlv-account-resolution's on-chain byte layout (verified against the actual Rust crate
@@ -127,13 +127,15 @@ async function resolveSeed(rpc, seed, instructionData, knownAccounts, addressEnc
127
127
  }
128
128
  }
129
129
  }
130
- // Resolves every extra account a mint's active transfer hook requires for one specific transfer,
131
- // in the exact order they need to be appended after {source, mint, destination, owner} on the
132
- // transfer_checked instruction. Faithful port of spl-tlv-account-resolution's resolution
133
- // algorithm (account.rs's ExtraAccountMeta::resolve + seeds.rs's Seed::unpack_address_config)
134
- // a hook can require more than one extra account (each can itself be a PDA seeded off any
135
- // account already in the list, including earlier-resolved extra accounts), so this always
136
- // returns an array, not a single address.
130
+ // Resolves every account a mint's active transfer hook requires for one specific transfer, in
131
+ // the exact order they need to be appended after {source, mint, destination, owner} on the
132
+ // transfer_checked instruction: each seed-resolved extra account (in declaration order — a hook
133
+ // can require more than one, each itself possibly a PDA seeded off any account already in the
134
+ // list, including earlier-resolved extras), then the ExtraAccountMetaList PDA, then the hook
135
+ // program itself. Faithful port of spl-transfer-hook-interface's onchain.rs
136
+ // (add_extra_accounts_for_execute_cpi) and spl-tlv-account-resolution's resolution algorithm
137
+ // (account.rs's ExtraAccountMeta::resolve + seeds.rs's Seed::unpack_address_config) — verified
138
+ // against both the real source and its own unit test's expected account list, not guessed.
137
139
  export async function resolveTransferHookExtraAccounts(rpc, hookProgram, transferAccounts, instructionData = new Uint8Array()) {
138
140
  const addressEncoder = getAddressEncoder();
139
141
  const addressDecoder = getAddressDecoder();
@@ -149,11 +151,16 @@ export async function resolveTransferHookExtraAccounts(rpc, hookProgram, transfe
149
151
  throw new Error("ExtraAccountMetaList account does not carry the Execute instruction's discriminator");
150
152
  }
151
153
  const rawMetas = parseExtraAccountMetaList(data);
154
+ // Matches spl-transfer-hook-interface onchain.rs's add_extra_accounts_for_execute_cpi exactly:
155
+ // execute_account_infos starts as [source, mint, destination, authority, validate_state_info]
156
+ // (5 entries) *before* any seed resolution begins — so a Seed::AccountKey{index: 4} means the
157
+ // ExtraAccountMetaList PDA itself, not the first resolved extra account (which starts at 5).
152
158
  const knownAccounts = [
153
159
  { address: transferAccounts.source, data: null },
154
160
  { address: transferAccounts.mint, data: null },
155
161
  { address: transferAccounts.destination, data: null },
156
162
  { address: transferAccounts.owner, data: null },
163
+ { address: extraAccountMetaListAddress, data: null },
157
164
  ];
158
165
  const resolved = [];
159
166
  for (const meta of rawMetas) {
@@ -178,5 +185,16 @@ export async function resolveTransferHookExtraAccounts(rpc, hookProgram, transfe
178
185
  resolved.push({ address, isSigner: meta.isSigner, isWritable: meta.isWritable });
179
186
  knownAccounts.push({ address, data: null });
180
187
  }
188
+ // add_extra_accounts_for_execute_cpi appends these two last, after every resolved extra
189
+ // meta — the ExtraAccountMetaList PDA, then the hook program itself, both readonly/non-signer.
190
+ resolved.push({ address: extraAccountMetaListAddress, isSigner: false, isWritable: false });
191
+ resolved.push({ address: hookProgram, isSigner: false, isWritable: false });
181
192
  return resolved;
182
193
  }
194
+ // Converts a resolved hook account into the {address, role} shape instruction builders expect.
195
+ export function toRemainingAccountMeta({ address, isSigner, isWritable }) {
196
+ const role = isSigner
197
+ ? (isWritable ? AccountRole.WRITABLE_SIGNER : AccountRole.READONLY_SIGNER)
198
+ : (isWritable ? AccountRole.WRITABLE : AccountRole.READONLY);
199
+ return { address, role };
200
+ }
@@ -0,0 +1,6 @@
1
+ export declare const SCALE_OFFSET = 64n;
2
+ export declare const ONE: bigint;
3
+ export declare const MAX: bigint;
4
+ export declare const MAX_EXPONENTIAL = 524288;
5
+ export declare function expBySquaring(base: bigint, exp: number): bigint;
6
+ //# sourceMappingURL=u64x64.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"u64x64.d.ts","sourceRoot":"","sources":["../../src/utils/u64x64.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,GAAG,QAAqB,CAAC;AACtC,eAAO,MAAM,GAAG,QAAoB,CAAC;AACrC,eAAO,MAAM,eAAe,SAAU,CAAC;AAQvC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAgB/D"}
@@ -0,0 +1,25 @@
1
+ export const SCALE_OFFSET = 64n;
2
+ export const ONE = 1n << SCALE_OFFSET;
3
+ export const MAX = (1n << 128n) - 1n; // u128::MAX
4
+ export const MAX_EXPONENTIAL = 0x80000; // 2^19
5
+ function mulU128(a, b) {
6
+ const product = a * b;
7
+ if (product > MAX)
8
+ throw new Error("u64x64: u128 multiplication overflow");
9
+ return product;
10
+ }
11
+ export function expBySquaring(base, exp) {
12
+ let result = ONE;
13
+ let remainingBits = 32 - Math.clz32(exp);
14
+ for (let bit = 0x1; bit <= 0x40000; bit <<= 1) {
15
+ if (exp & bit) {
16
+ result = mulU128(result, base) >> SCALE_OFFSET;
17
+ }
18
+ remainingBits -= 1;
19
+ if (remainingBits === 0) {
20
+ return result;
21
+ }
22
+ base = mulU128(base, base) >> SCALE_OFFSET;
23
+ }
24
+ throw new Error(`u64x64.expBySquaring: exp out of range (${exp})`);
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@picon-finance/dlmm-sdk",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Typescript SDK for interacting with the Picon DLMM protocol",
5
5
  "author": "Picon Finance",
6
6
  "license": "MIT",
@@ -30,6 +30,13 @@ import {
30
30
  generateBinArrayIndicesForSync,
31
31
  } from "../utils/bin_location";
32
32
  import { resolveTransferFee } from "../utils/transfer_fee";
33
+ import {
34
+ resolveTransferHookProgram,
35
+ resolveTransferHookExtraAccounts,
36
+ toRemainingAccountMeta,
37
+ type ExtraAccount,
38
+ type TransferAccounts as TransferHookAccounts,
39
+ } from "../utils/transfer_hook";
33
40
  import { MAX_BIN_ARRAYS_PER_TRAVERSAL } from "../utils/constants";
34
41
  import {
35
42
  Position,
@@ -184,9 +191,35 @@ export class Pool {
184
191
  amountY: xToY ? undefined : amountIn,
185
192
  });
186
193
 
187
- const remainingAccountsScheme: generated.RemainingAccountsScheme = {
188
- groups: [{ kind: generated.AccountKind.BinArray, count: binArrayAddresses.length }],
189
- };
194
+ // Each mint's hook accounts are resolved against whichever role it plays in this swap —
195
+ // input (user -> vault, owner = user) or output (vault -> user, owner = pool PDA).
196
+ const xTransferAccounts: TransferHookAccounts = xToY
197
+ ? { source: tokenUserAccountX, mint: this.tokenMintX, destination: this.tokenVaultX, owner: user.address }
198
+ : { source: this.tokenVaultX, mint: this.tokenMintX, destination: tokenUserAccountX, owner: this.address };
199
+ const yTransferAccounts: TransferHookAccounts = xToY
200
+ ? { source: this.tokenVaultY, mint: this.tokenMintY, destination: tokenUserAccountY, owner: this.address }
201
+ : { source: tokenUserAccountY, mint: this.tokenMintY, destination: this.tokenVaultY, owner: user.address };
202
+
203
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
204
+ this.resolveTransferHookAccountsX(xTransferAccounts),
205
+ this.resolveTransferHookAccountsY(yTransferAccounts),
206
+ ]);
207
+
208
+ const groups: generated.AccountGroup[] = [
209
+ { kind: generated.AccountKind.BinArray, count: binArrayAddresses.length },
210
+ ];
211
+ const remainingAccounts = binArrayAddresses.map(address => ({ address, role: AccountRole.WRITABLE }));
212
+
213
+ if (transferHookXAccounts.length > 0) {
214
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
215
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
216
+ }
217
+ if (transferHookYAccounts.length > 0) {
218
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
219
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
220
+ }
221
+
222
+ const remainingAccountsScheme: generated.RemainingAccountsScheme = { groups };
190
223
  const swapIx = generated.getSwapInstruction({
191
224
  user,
192
225
  pool: this.address,
@@ -204,13 +237,33 @@ export class Pool {
204
237
  remainingAccountsScheme,
205
238
  }, { programAddress: this.#programAddress });
206
239
 
207
- const remainingAccounts = binArrayAddresses.map(address => ({ address, role: AccountRole.WRITABLE }));
208
240
  const swapIxWithBinArrays = { ...swapIx, accounts: [...swapIx.accounts, ...remainingAccounts] };
209
241
 
210
242
  const instructions = [...preInstructions, swapIxWithBinArrays, ...postInstructions];
211
243
  return { quoteOutput, instructions };
212
244
  }
213
245
 
246
+ // Public so Position (which only holds a reference to this Pool, not its private mint data)
247
+ // can resolve hook accounts for its own deposit/withdraw/claimFee instructions without ever
248
+ // needing a Mint account itself — empty when that mint has no active transfer hook, in which
249
+ // case the caller should add no group to the scheme for it.
250
+ public async resolveTransferHookAccountsX(transferAccounts: TransferHookAccounts): Promise<ExtraAccount[]> {
251
+ return await this.#resolveTransferHookAccounts(this.#mintX, transferAccounts);
252
+ }
253
+
254
+ public async resolveTransferHookAccountsY(transferAccounts: TransferHookAccounts): Promise<ExtraAccount[]> {
255
+ return await this.#resolveTransferHookAccounts(this.#mintY, transferAccounts);
256
+ }
257
+
258
+ async #resolveTransferHookAccounts(
259
+ mint: Account<Mint>,
260
+ transferAccounts: TransferHookAccounts,
261
+ ): Promise<ExtraAccount[]> {
262
+ const hookProgram = resolveTransferHookProgram(mint.data);
263
+ if (!hookProgram) return [];
264
+ return await resolveTransferHookExtraAccounts(this.#rpc, hookProgram, transferAccounts);
265
+ }
266
+
214
267
  public async quote(
215
268
  params: { xToY: boolean, amountIn: bigint, slippageToleranceBps: number },
216
269
  ): Promise<QuoteOutput> {
@@ -332,6 +385,29 @@ export class Pool {
332
385
  }),
333
386
  ]);
334
387
 
388
+ // Both legs move vault -> admin, signed by the pool PDA (not the authority signer, which
389
+ // only gates the on-chain permission check).
390
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
391
+ this.resolveTransferHookAccountsX({
392
+ source: this.tokenVaultX, mint: this.tokenMintX, destination: tokenAdminAccountX, owner: this.address,
393
+ }),
394
+ this.resolveTransferHookAccountsY({
395
+ source: this.tokenVaultY, mint: this.tokenMintY, destination: tokenAdminAccountY, owner: this.address,
396
+ }),
397
+ ]);
398
+
399
+ const groups: generated.AccountGroup[] = [];
400
+ const remainingAccounts: { address: Address, role: AccountRole }[] = [];
401
+
402
+ if (transferHookXAccounts.length > 0) {
403
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
404
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
405
+ }
406
+ if (transferHookYAccounts.length > 0) {
407
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
408
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
409
+ }
410
+
335
411
  const claimProtocolFeeIx = await generated.getClaimProtocolFeeInstructionAsync({
336
412
  authority,
337
413
  pool: this.address,
@@ -342,12 +418,17 @@ export class Pool {
342
418
  tokenVaultY: this.tokenVaultY,
343
419
  tokenAdminAccountX,
344
420
  tokenAdminAccountY,
345
- remainingAccountsScheme: null,
421
+ remainingAccountsScheme: { groups },
346
422
  tokenProgramX: this.tokenProgramX,
347
423
  tokenProgramY: this.tokenProgramY,
348
424
  }, { programAddress: this.#programAddress });
349
425
 
350
- return [...preInstructions, claimProtocolFeeIx, ...postInstructions];
426
+ const claimProtocolFeeIxWithHookAccounts = {
427
+ ...claimProtocolFeeIx,
428
+ accounts: [...claimProtocolFeeIx.accounts, ...remainingAccounts],
429
+ };
430
+
431
+ return [...preInstructions, claimProtocolFeeIxWithHookAccounts, ...postInstructions];
351
432
  }
352
433
 
353
434
  public static async load(
@@ -3,6 +3,7 @@ import {
3
3
  createSolanaRpc,
4
4
  assertAccountExists,
5
5
  fetchEncodedAccounts,
6
+ type AccountRole,
6
7
  type Address,
7
8
  type Account,
8
9
  type Instruction,
@@ -17,6 +18,7 @@ import {
17
18
  findPositionTokenAccount,
18
19
  processAssociatedTokenAccountPair,
19
20
  } from "../utils/ata";
21
+ import { toRemainingAccountMeta } from "../utils/transfer_hook";
20
22
  import {
21
23
  getBinIndex,
22
24
  getBinArrayIndex,
@@ -200,6 +202,28 @@ export class Position {
200
202
  }),
201
203
  ]);
202
204
 
205
+ // Both legs move owner -> vault (deposit), signed by owner itself.
206
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
207
+ this.#pool.resolveTransferHookAccountsX({
208
+ source: tokenOwnerAccountX, mint: this.#pool.tokenMintX, destination: this.#pool.tokenVaultX, owner: owner.address,
209
+ }),
210
+ this.#pool.resolveTransferHookAccountsY({
211
+ source: tokenOwnerAccountY, mint: this.#pool.tokenMintY, destination: this.#pool.tokenVaultY, owner: owner.address,
212
+ }),
213
+ ]);
214
+
215
+ const groups: generated.AccountGroup[] = [];
216
+ const remainingAccounts: { address: Address, role: AccountRole }[] = [];
217
+
218
+ if (transferHookXAccounts.length > 0) {
219
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
220
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
221
+ }
222
+ if (transferHookYAccounts.length > 0) {
223
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
224
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
225
+ }
226
+
203
227
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
204
228
  const depositByWeightIx = generated.getDepositByWeightInstruction({
205
229
  owner,
@@ -223,12 +247,17 @@ export class Position {
223
247
  distributionMode,
224
248
  lowerBinId,
225
249
  binWeights,
226
- remainingAccountsScheme: null,
250
+ remainingAccountsScheme: { groups },
227
251
  }, {
228
252
  programAddress: this.#pool.programAddress
229
253
  });
230
254
 
231
- return [...preInstructions, depositByWeightIx, ...postInstructions];
255
+ const depositByWeightIxWithHookAccounts = {
256
+ ...depositByWeightIx,
257
+ accounts: [...depositByWeightIx.accounts, ...remainingAccounts],
258
+ };
259
+
260
+ return [...preInstructions, depositByWeightIxWithHookAccounts, ...postInstructions];
232
261
  }
233
262
 
234
263
  async #buildClaimFeeInstruction(
@@ -241,8 +270,30 @@ export class Position {
241
270
  const { lowerBinId, upperBinId } = range ?? this.data;
242
271
  assertRangeWithinRange({ lowerBinId, upperBinId }, this.data);
243
272
 
273
+ // Both legs move vault -> owner (fee claim), signed by the pool PDA.
274
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
275
+ this.#pool.resolveTransferHookAccountsX({
276
+ source: this.#pool.tokenVaultX, mint: this.#pool.tokenMintX, destination: tokenOwnerAccountX, owner: this.#pool.address,
277
+ }),
278
+ this.#pool.resolveTransferHookAccountsY({
279
+ source: this.#pool.tokenVaultY, mint: this.#pool.tokenMintY, destination: tokenOwnerAccountY, owner: this.#pool.address,
280
+ }),
281
+ ]);
282
+
283
+ const groups: generated.AccountGroup[] = [];
284
+ const remainingAccounts: { address: Address, role: AccountRole }[] = [];
285
+
286
+ if (transferHookXAccounts.length > 0) {
287
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
288
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
289
+ }
290
+ if (transferHookYAccounts.length > 0) {
291
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
292
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
293
+ }
294
+
244
295
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
245
- return generated.getClaimFeeInstruction({
296
+ const claimFeeIx = generated.getClaimFeeInstruction({
246
297
  owner,
247
298
  pool: this.#pool.address,
248
299
  position: this.address,
@@ -259,10 +310,12 @@ export class Position {
259
310
  upperBinArray,
260
311
  lowerBinId,
261
312
  upperBinId,
262
- remainingAccountsScheme: null,
313
+ remainingAccountsScheme: { groups },
263
314
  }, {
264
315
  programAddress: this.#pool.programAddress
265
316
  });
317
+
318
+ return { ...claimFeeIx, accounts: [...claimFeeIx.accounts, ...remainingAccounts] };
266
319
  }
267
320
 
268
321
  public async claimFee(
@@ -316,6 +369,28 @@ export class Position {
316
369
  }),
317
370
  ]);
318
371
 
372
+ // Both legs move vault -> owner (withdraw), signed by the pool PDA.
373
+ const [transferHookXAccounts, transferHookYAccounts] = await Promise.all([
374
+ this.#pool.resolveTransferHookAccountsX({
375
+ source: this.#pool.tokenVaultX, mint: this.#pool.tokenMintX, destination: tokenOwnerAccountX, owner: this.#pool.address,
376
+ }),
377
+ this.#pool.resolveTransferHookAccountsY({
378
+ source: this.#pool.tokenVaultY, mint: this.#pool.tokenMintY, destination: tokenOwnerAccountY, owner: this.#pool.address,
379
+ }),
380
+ ]);
381
+
382
+ const groups: generated.AccountGroup[] = [];
383
+ const remainingAccounts: { address: Address, role: AccountRole }[] = [];
384
+
385
+ if (transferHookXAccounts.length > 0) {
386
+ groups.push({ kind: generated.AccountKind.TransferHookX, count: transferHookXAccounts.length });
387
+ remainingAccounts.push(...transferHookXAccounts.map(toRemainingAccountMeta));
388
+ }
389
+ if (transferHookYAccounts.length > 0) {
390
+ groups.push({ kind: generated.AccountKind.TransferHookY, count: transferHookYAccounts.length });
391
+ remainingAccounts.push(...transferHookYAccounts.map(toRemainingAccountMeta));
392
+ }
393
+
319
394
  const { lowerBinArray, upperBinArray } = this.binArrayAddresses;
320
395
  const withdrawIx = generated.getWithdrawInstruction({
321
396
  owner,
@@ -335,12 +410,17 @@ export class Position {
335
410
  lowerBinId,
336
411
  upperBinId,
337
412
  bpsToRemove,
338
- remainingAccountsScheme: null,
413
+ remainingAccountsScheme: { groups },
339
414
  }, {
340
415
  programAddress: this.#pool.programAddress
341
416
  });
342
417
 
343
- return [...preInstructions, withdrawIx, ...postInstructions];
418
+ const withdrawIxWithHookAccounts = {
419
+ ...withdrawIx,
420
+ accounts: [...withdrawIx.accounts, ...remainingAccounts],
421
+ };
422
+
423
+ return [...preInstructions, withdrawIxWithHookAccounts, ...postInstructions];
344
424
  }
345
425
 
346
426
  public async close(owner: TransactionSigner): Promise<Instruction> {
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export { DistributionMode } from "./generated/types/distributionMode";
3
3
  export * as events from "./events";
4
4
  export * from "./entities";
5
5
  export * from "./utils/constants";
6
+ export * from "./utils/price";
6
7
  export { NATIVE_MINT_ADDRESS } from "./utils/ata";
7
8
  export type { QuoteOutput, TransferFee, FeeRates } from "./quote";
8
9
  export * from "./program_addresses";
@@ -0,0 +1,32 @@
1
+ import { MAX_BASIS_POINTS } from "./constants";
2
+ import { expBySquaring, MAX, MAX_EXPONENTIAL, ONE, SCALE_OFFSET } from "./u64x64";
3
+
4
+ export function getBaseBinPrice(binStep: number): bigint {
5
+ const priceStep = (BigInt(binStep) << SCALE_OFFSET) / BigInt(MAX_BASIS_POINTS);
6
+ return ONE + priceStep;
7
+ }
8
+
9
+ export function getBinPrice(baseBinPrice: bigint, binId: number): bigint | undefined {
10
+ if (baseBinPrice < 0n || baseBinPrice > MAX) {
11
+ throw new Error(`getBinPrice: baseBinPrice out of u128 range (${baseBinPrice})`);
12
+ }
13
+
14
+ if (baseBinPrice <= ONE) return;
15
+ if (binId === 0) return ONE;
16
+
17
+ const invert = binId > 0;
18
+ const absBinId = Math.abs(binId);
19
+
20
+ if (absBinId >= MAX_EXPONENTIAL) return;
21
+
22
+ const invertedBase = MAX / baseBinPrice;
23
+ const price = expBySquaring(invertedBase, absBinId);
24
+
25
+ if (price === 0n) return;
26
+
27
+ return invert ? MAX / price : price;
28
+ }
29
+
30
+ export function getBinPriceFromStep(binStep: number, binId: number): bigint | undefined {
31
+ return getBinPrice(getBaseBinPrice(binStep), binId);
32
+ }
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AccountRole,
2
3
  getAddressEncoder,
3
4
  getAddressDecoder,
4
5
  getProgramDerivedAddress,
@@ -195,13 +196,15 @@ export type TransferAccounts = {
195
196
  owner: Address;
196
197
  };
197
198
 
198
- // Resolves every extra account a mint's active transfer hook requires for one specific transfer,
199
- // in the exact order they need to be appended after {source, mint, destination, owner} on the
200
- // transfer_checked instruction. Faithful port of spl-tlv-account-resolution's resolution
201
- // algorithm (account.rs's ExtraAccountMeta::resolve + seeds.rs's Seed::unpack_address_config)
202
- // a hook can require more than one extra account (each can itself be a PDA seeded off any
203
- // account already in the list, including earlier-resolved extra accounts), so this always
204
- // returns an array, not a single address.
199
+ // Resolves every account a mint's active transfer hook requires for one specific transfer, in
200
+ // the exact order they need to be appended after {source, mint, destination, owner} on the
201
+ // transfer_checked instruction: each seed-resolved extra account (in declaration order — a hook
202
+ // can require more than one, each itself possibly a PDA seeded off any account already in the
203
+ // list, including earlier-resolved extras), then the ExtraAccountMetaList PDA, then the hook
204
+ // program itself. Faithful port of spl-transfer-hook-interface's onchain.rs
205
+ // (add_extra_accounts_for_execute_cpi) and spl-tlv-account-resolution's resolution algorithm
206
+ // (account.rs's ExtraAccountMeta::resolve + seeds.rs's Seed::unpack_address_config) — verified
207
+ // against both the real source and its own unit test's expected account list, not guessed.
205
208
  export async function resolveTransferHookExtraAccounts(
206
209
  rpc: ReturnType<typeof createSolanaRpc>,
207
210
  hookProgram: Address,
@@ -230,11 +233,16 @@ export async function resolveTransferHookExtraAccounts(
230
233
 
231
234
  const rawMetas = parseExtraAccountMetaList(data);
232
235
 
236
+ // Matches spl-transfer-hook-interface onchain.rs's add_extra_accounts_for_execute_cpi exactly:
237
+ // execute_account_infos starts as [source, mint, destination, authority, validate_state_info]
238
+ // (5 entries) *before* any seed resolution begins — so a Seed::AccountKey{index: 4} means the
239
+ // ExtraAccountMetaList PDA itself, not the first resolved extra account (which starts at 5).
233
240
  const knownAccounts: KnownAccount[] = [
234
241
  { address: transferAccounts.source, data: null },
235
242
  { address: transferAccounts.mint, data: null },
236
243
  { address: transferAccounts.destination, data: null },
237
244
  { address: transferAccounts.owner, data: null },
245
+ { address: extraAccountMetaListAddress, data: null },
238
246
  ];
239
247
 
240
248
  const resolved: ExtraAccount[] = [];
@@ -265,5 +273,18 @@ export async function resolveTransferHookExtraAccounts(
265
273
  knownAccounts.push({ address, data: null });
266
274
  }
267
275
 
276
+ // add_extra_accounts_for_execute_cpi appends these two last, after every resolved extra
277
+ // meta — the ExtraAccountMetaList PDA, then the hook program itself, both readonly/non-signer.
278
+ resolved.push({ address: extraAccountMetaListAddress, isSigner: false, isWritable: false });
279
+ resolved.push({ address: hookProgram, isSigner: false, isWritable: false });
280
+
268
281
  return resolved;
269
282
  }
283
+
284
+ // Converts a resolved hook account into the {address, role} shape instruction builders expect.
285
+ export function toRemainingAccountMeta({ address, isSigner, isWritable }: ExtraAccount) {
286
+ const role = isSigner
287
+ ? (isWritable ? AccountRole.WRITABLE_SIGNER : AccountRole.READONLY_SIGNER)
288
+ : (isWritable ? AccountRole.WRITABLE : AccountRole.READONLY);
289
+ return { address, role };
290
+ }
@@ -0,0 +1,28 @@
1
+ export const SCALE_OFFSET = 64n;
2
+ export const ONE = 1n << SCALE_OFFSET;
3
+ export const MAX = (1n << 128n) - 1n; // u128::MAX
4
+ export const MAX_EXPONENTIAL = 0x80000; // 2^19
5
+
6
+ function mulU128(a: bigint, b: bigint): bigint {
7
+ const product = a * b;
8
+ if (product > MAX) throw new Error("u64x64: u128 multiplication overflow");
9
+ return product;
10
+ }
11
+
12
+ export function expBySquaring(base: bigint, exp: number): bigint {
13
+ let result = ONE;
14
+ let remainingBits = 32 - Math.clz32(exp);
15
+
16
+ for (let bit = 0x1; bit <= 0x40000; bit <<= 1) {
17
+ if (exp & bit) {
18
+ result = mulU128(result, base) >> SCALE_OFFSET;
19
+ }
20
+ remainingBits -= 1;
21
+ if (remainingBits === 0) {
22
+ return result;
23
+ }
24
+ base = mulU128(base, base) >> SCALE_OFFSET;
25
+ }
26
+
27
+ throw new Error(`u64x64.expBySquaring: exp out of range (${exp})`);
28
+ }