@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.
- package/dist/entities/pool.d.ts +3 -0
- package/dist/entities/pool.d.ts.map +1 -1
- package/dist/entities/pool.js +68 -6
- package/dist/entities/position.d.ts.map +1 -1
- package/dist/entities/position.js +73 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/utils/price.d.ts +4 -0
- package/dist/utils/price.d.ts.map +1 -0
- package/dist/utils/price.js +27 -0
- package/dist/utils/transfer_hook.d.ts +5 -1
- package/dist/utils/transfer_hook.d.ts.map +1 -1
- package/dist/utils/transfer_hook.js +26 -8
- package/dist/utils/u64x64.d.ts +6 -0
- package/dist/utils/u64x64.d.ts.map +1 -0
- package/dist/utils/u64x64.js +25 -0
- package/package.json +1 -1
- package/src/entities/pool.ts +87 -6
- package/src/entities/position.ts +86 -6
- package/src/index.ts +1 -0
- package/src/utils/price.ts +32 -0
- package/src/utils/transfer_hook.ts +28 -7
- package/src/utils/u64x64.ts +28 -0
package/dist/entities/pool.d.ts
CHANGED
|
@@ -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":"AAAA,OAAO,EACL,eAAe,EAEf,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACzB,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/entities/pool.ts"],"names":[],"mappings":"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"}
|
package/dist/entities/pool.js
CHANGED
|
@@ -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
|
-
|
|
84
|
-
|
|
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:
|
|
234
|
+
remainingAccountsScheme: { groups },
|
|
177
235
|
tokenProgramX: this.tokenProgramX,
|
|
178
236
|
tokenProgramY: this.tokenProgramY,
|
|
179
237
|
}, { programAddress: this.#programAddress });
|
|
180
|
-
|
|
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":"AAAA,OAAO,EAEL,eAAe,
|
|
1
|
+
{"version":3,"file":"position.d.ts","sourceRoot":"","sources":["../../src/entities/position.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,eAAe,EAIf,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAEzB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAO/B,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAC;AAgB/B,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EACL,QAAQ,EAIT,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAEjB,qBAAa,QAAQ;;IAKnB,OAAO,eAQN;IAED,IAAW,OAAO,oBAAoC;IACtD,IAAW,IAAI,uBAAiC;IAChD,IAAW,YAAY,YAAqC;IAC5D,IAAW,SAAS,IAAI,SAAS,QAAQ,EAAE,CAA4B;IAEvE,IAAW,iBAAiB,IAAI;QAAE,aAAa,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,CAI9F;IAEM,WAAW,IAAI,eAAe,EAAE,CAuCtC;IAEY,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,CA4B5C;IAEY,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,OAAO,CAAC,MAAM,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAerD,eAAe,CAAC,KAAK,EAAE,iBAAiB,EAAE,EACrD,OAAO,EACP,OAAO,EACP,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,gBAAgB,GACjB,EAAE;QACD,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,gBAAgB,EAAE,SAAS,CAAC,gBAAgB,CAAC;KAC9C,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA8EzB;IA4DY,QAAQ,CACnB,KAAK,EAAE,iBAAiB,EACxB,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC,CAsBxB;IAEY,QAAQ,CACnB,KAAK,EAAE,iBAAiB,EACxB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC,WAAW,EAAE,CAAC,CAwExB;IAEY,KAAK,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,CAKjE;IAED,OAAoB,IAAI,CACtB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAkBnB;IAED,OAAoB,OAAO,CACzB,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,OAAO,EAAE,EACpB,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,EAAE,CAAC,CAkBrB;IAED,OAAoB,UAAU,CAC5B,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACvC,SAAS,EAAE,QAAQ,EAAE,EACrB,MAAM,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAiBf;IAED,OAAoB,YAAY,CAC9B,KAAK,EAAE,iBAAiB,EACxB,SAAS,EAAE,QAAQ,EAAE,EACrB,iBAAiB,GAAE,MAAoC,GACtD,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAqC1B;CACF;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE;IAAE,YAAY,EAAE,OAAO,CAAA;CAAE,EAChC,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,OAAO,CAAC,CAGlB;AAED,wBAAsB,wBAAwB,CAC5C,EAAE,aAAa,EAAE,EAAE;IAAE,aAAa,EAAE,OAAO,EAAE,CAAA;CAAE,EAC/C,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,OAAO,EAAE,CAAC,CAIpB;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACvC,EACE,KAAK,EACL,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,UAAU,GACX,EAAE;IACD,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,EAAE,iBAAiB,CAAC;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,EACD,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBxB;AAED,wBAAsB,aAAa,CACjC,EACE,KAAK,EACL,YAAY,EACZ,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,iBAAiB,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,EACD,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,WAAW,CAAC,CAWtB"}
|
|
@@ -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:
|
|
159
|
+
remainingAccountsScheme: { groups },
|
|
140
160
|
}, {
|
|
141
161
|
programAddress: this.#pool.programAddress
|
|
142
162
|
});
|
|
143
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
280
|
+
remainingAccountsScheme: { groups },
|
|
218
281
|
}, {
|
|
219
282
|
programAddress: this.#pool.programAddress
|
|
220
283
|
});
|
|
221
|
-
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
131
|
-
//
|
|
132
|
-
// transfer_checked instruction
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
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
package/src/entities/pool.ts
CHANGED
|
@@ -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
|
-
|
|
188
|
-
|
|
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:
|
|
421
|
+
remainingAccountsScheme: { groups },
|
|
346
422
|
tokenProgramX: this.tokenProgramX,
|
|
347
423
|
tokenProgramY: this.tokenProgramY,
|
|
348
424
|
}, { programAddress: this.#programAddress });
|
|
349
425
|
|
|
350
|
-
|
|
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(
|
package/src/entities/position.ts
CHANGED
|
@@ -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:
|
|
250
|
+
remainingAccountsScheme: { groups },
|
|
227
251
|
}, {
|
|
228
252
|
programAddress: this.#pool.programAddress
|
|
229
253
|
});
|
|
230
254
|
|
|
231
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
413
|
+
remainingAccountsScheme: { groups },
|
|
339
414
|
}, {
|
|
340
415
|
programAddress: this.#pool.programAddress
|
|
341
416
|
});
|
|
342
417
|
|
|
343
|
-
|
|
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
|
|
199
|
-
//
|
|
200
|
-
// transfer_checked instruction
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
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
|
+
}
|