@picon-finance/dlmm-sdk 1.18.2 → 1.18.3
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/README.md +83 -21
- package/dist/picon_dlmm.json +1 -1
- package/package.json +4 -4
- package/src/picon_dlmm.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @picon-finance/dlmm-sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the [Picon DLMM](https://github.com/picon-finance) protocol on Solana,
|
|
3
|
+
TypeScript SDK for the [Picon DLMM](https://github.com/picon-finance) protocol on Solana,
|
|
4
|
+
built on [`@solana/kit`](https://github.com/anza-xyz/kit).
|
|
5
|
+
|
|
6
|
+
Full protocol and integration docs: **[docs.picon.finance](https://docs.picon.finance)**.
|
|
4
7
|
|
|
5
8
|
## Install
|
|
6
9
|
|
|
@@ -23,9 +26,13 @@ supported consumption splits into two scenarios:
|
|
|
23
26
|
|
|
24
27
|
## Usage
|
|
25
28
|
|
|
26
|
-
The SDK is built around stateful classes
|
|
29
|
+
The SDK is built around stateful classes — `DlmmProgram`, `Pool`, `Position`, `BinArray` — that
|
|
30
|
+
cache on-chain state to avoid redundant RPC round trips. Every method returns unsigned
|
|
31
|
+
`Instruction`s and never signs or sends; you build, sign, and send the transaction yourself with
|
|
32
|
+
`@solana/kit`.
|
|
27
33
|
|
|
28
|
-
For low-level access — raw instruction builders, account decoders, PDA derivation — the
|
|
34
|
+
For low-level access — raw instruction builders, account decoders, PDA derivation — the
|
|
35
|
+
Codama-generated client is exposed as `generated`, and used internally by the SDK itself.
|
|
29
36
|
|
|
30
37
|
```ts
|
|
31
38
|
import {
|
|
@@ -39,12 +46,13 @@ import {
|
|
|
39
46
|
sendAndConfirmTransactionFactory,
|
|
40
47
|
createSolanaRpcSubscriptions,
|
|
41
48
|
} from "@solana/kit";
|
|
42
|
-
import { DlmmProgram, events, DistributionMode
|
|
49
|
+
import { DlmmProgram, events, DistributionMode } from "@picon-finance/dlmm-sdk";
|
|
43
50
|
|
|
44
51
|
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
|
|
45
52
|
|
|
46
|
-
// Omit programAddress to target mainnet
|
|
47
|
-
|
|
53
|
+
// Omit programAddress to target mainnet. Testing against devnet instead? Import
|
|
54
|
+
// DEVNET_PROGRAM_ADDRESS and pass it as the second argument here.
|
|
55
|
+
const dlmmProgram = new DlmmProgram(rpc);
|
|
48
56
|
|
|
49
57
|
// Fetch and cache a pool's account data, mints, and token programs in one call.
|
|
50
58
|
const pool = await dlmmProgram.getPool(poolAddress);
|
|
@@ -73,6 +81,8 @@ const signedTransaction = await signTransactionMessageWithSigners(transactionMes
|
|
|
73
81
|
|
|
74
82
|
### Positions
|
|
75
83
|
|
|
84
|
+
#### Open, deposit, claim, withdraw, close
|
|
85
|
+
|
|
76
86
|
```ts
|
|
77
87
|
// Open a position and deposit in one flow.
|
|
78
88
|
const openPositionIxs = await pool.openPosition(ownerSigner, positionMintSigner, {
|
|
@@ -102,13 +112,19 @@ const withdrawIxs = await position.withdraw(ownerSigner, { bpsToRemove: 5_000 })
|
|
|
102
112
|
const closeIx = await position.close(ownerSigner);
|
|
103
113
|
```
|
|
104
114
|
|
|
105
|
-
|
|
115
|
+
#### Reading position state
|
|
116
|
+
|
|
117
|
+
`position.data` exposes the decoded on-chain `Position` account (`pool`, `positionMint`,
|
|
118
|
+
`lowerBinId`, `upperBinId`, `feeStates`, ...) directly from the cached account — no extra fetch
|
|
119
|
+
needed:
|
|
106
120
|
|
|
107
121
|
```ts
|
|
108
122
|
console.log(position.data.lowerBinId, position.data.upperBinId);
|
|
109
123
|
```
|
|
110
124
|
|
|
111
|
-
`position.getInfo()` returns a full summary: deposited amounts and the total fee a `claimFee`
|
|
125
|
+
`position.getInfo()` returns a full summary: deposited amounts and the total fee a `claimFee`
|
|
126
|
+
call would sweep right now, each in both `gross*` (before any Token-2022 transfer fee) and
|
|
127
|
+
`net*` (what you'd actually receive) forms, plus the per-bin data they're summed from:
|
|
112
128
|
|
|
113
129
|
```ts
|
|
114
130
|
const info = await position.getInfo();
|
|
@@ -116,7 +132,9 @@ console.log(`Deposited: ${info.netAmountX} X / ${info.netAmountY} Y`);
|
|
|
116
132
|
console.log(`Claimable fee: ${info.netFeeX} X / ${info.netFeeY} Y`);
|
|
117
133
|
```
|
|
118
134
|
|
|
119
|
-
`info.bins` (same as calling `position.getBinInfos()` directly) is the per-bin breakdown
|
|
135
|
+
`info.bins` (same as calling `position.getBinInfos()` directly) is the per-bin breakdown
|
|
136
|
+
`getInfo()` sums to produce those totals. It's always raw — `gross*`/`net*` only apply once,
|
|
137
|
+
to the aggregate:
|
|
120
138
|
|
|
121
139
|
```ts
|
|
122
140
|
for (const { bin, amountX, amountY, feeX, feeY } of position.getBinInfos()) {
|
|
@@ -124,21 +142,36 @@ for (const { bin, amountX, amountY, feeX, feeY } of position.getBinInfos()) {
|
|
|
124
142
|
}
|
|
125
143
|
```
|
|
126
144
|
|
|
127
|
-
Both are computed entirely from cached position + bin array data
|
|
145
|
+
Both are computed entirely from cached position + bin array data. `getBinInfos()` makes no RPC
|
|
146
|
+
calls at all; `getInfo()` makes one round trip (via `Pool.getTransferFees()`) to resolve each
|
|
147
|
+
mint's live transfer-fee rate.
|
|
128
148
|
|
|
129
|
-
`Pool.getTransferFees()` resolves each mint's active
|
|
149
|
+
`Pool.getTransferFees()` resolves each mint's active
|
|
150
|
+
[Token-2022 transfer-fee](https://spl.solana.com/token-2022/extensions#transfer-fees) config
|
|
151
|
+
from cached mint data. `undefined` per side means that mint has no `TransferFeeConfig`
|
|
152
|
+
extension — not that the fee is zero:
|
|
130
153
|
|
|
131
154
|
```ts
|
|
132
155
|
const { transferFeeX, transferFeeY } = await pool.getTransferFees();
|
|
133
156
|
```
|
|
134
157
|
|
|
135
|
-
`Pool.getFeeRates(now?)` returns the base/dynamic/total fee rate the pool would charge for a
|
|
158
|
+
`Pool.getFeeRates(now?)` returns the base/dynamic/total fee rate the pool would charge for a
|
|
159
|
+
swap at `now` (defaults to the current time). It decays the dynamic fee state first — the same
|
|
160
|
+
"clock sim" the on-chain program runs right before pricing a real swap — so this reflects what
|
|
161
|
+
a swap would actually pay, rather than the raw, potentially stale, stored volatility
|
|
162
|
+
accumulator:
|
|
136
163
|
|
|
137
164
|
```ts
|
|
138
165
|
const { baseFeeRate, dynamicFeeRate, totalFeeRate } = await pool.getFeeRates();
|
|
139
166
|
```
|
|
140
167
|
|
|
141
|
-
|
|
168
|
+
#### Swaps and quotes
|
|
169
|
+
|
|
170
|
+
`swapExactIn`/`swapExactOut`/`quoteExactIn`/`quoteExactOut`/`sync` don't take a
|
|
171
|
+
bin-array-count parameter. The quote methods always quote against the protocol max traversal
|
|
172
|
+
window (`MAX_BIN_ARRAYS_PER_TRAVERSAL`) internally, then the matching swap method trims the
|
|
173
|
+
bin-array set down to only what the quote says it actually needs (plus a one-array drift
|
|
174
|
+
margin) — so you never pay for more accounts than the trade is likely to touch:
|
|
142
175
|
|
|
143
176
|
```ts
|
|
144
177
|
// quoteExactIn() is a thin wrapper — same internal fetch/simulate/trim as swapExactIn(), just
|
|
@@ -161,9 +194,18 @@ const { quoteOutput: exactOutQuote, instructions: exactOutInstructions } = await
|
|
|
161
194
|
});
|
|
162
195
|
```
|
|
163
196
|
|
|
164
|
-
`Pool.sync(user, desiredBinId)` repositions the pool's active bin toward `desiredBinId` across
|
|
197
|
+
`Pool.sync(user, desiredBinId)` repositions the pool's active bin toward `desiredBinId` across
|
|
198
|
+
empty bins only — also always uses the max traversal window, no count parameter.
|
|
199
|
+
|
|
200
|
+
#### Transfer hooks
|
|
165
201
|
|
|
166
|
-
If either mint carries an active Token-2022 `TransferHook` extension, `swapExactIn()
|
|
202
|
+
If either mint carries an active Token-2022 `TransferHook` extension, `swapExactIn()`/
|
|
203
|
+
`swapExactOut()` (and every position method above) resolve and append its extra accounts
|
|
204
|
+
automatically — nothing to configure. `Pool.resolveTransferHookAccountsX(transferAccounts)`/
|
|
205
|
+
`resolveTransferHookAccountsY(transferAccounts)` are exposed publicly if you need to resolve
|
|
206
|
+
them yourself for a custom instruction.
|
|
207
|
+
|
|
208
|
+
#### Looking up positions
|
|
167
209
|
|
|
168
210
|
Refresh a position's cached state after it changes on-chain (e.g. after a deposit lands):
|
|
169
211
|
|
|
@@ -171,7 +213,8 @@ Refresh a position's cached state after it changes on-chain (e.g. after a deposi
|
|
|
171
213
|
await position.refresh();
|
|
172
214
|
```
|
|
173
215
|
|
|
174
|
-
Look up several positions at once, e.g. every position minted to a given owner across all
|
|
216
|
+
Look up several positions at once, e.g. every position minted to a given owner across all
|
|
217
|
+
their pools:
|
|
175
218
|
|
|
176
219
|
```ts
|
|
177
220
|
// Map<poolAddress, positionMintAddress[]>
|
|
@@ -190,7 +233,16 @@ Or fetch a single position directly if you already know its address:
|
|
|
190
233
|
const positionByAddress = await pool.getPosition(positionAddress);
|
|
191
234
|
```
|
|
192
235
|
|
|
193
|
-
|
|
236
|
+
#### Claiming across positions
|
|
237
|
+
|
|
238
|
+
Claim fees across many positions at once with `Position.claimAllFees()`:
|
|
239
|
+
|
|
240
|
+
- Skips positions with nothing pending (checked locally, no extra RPC).
|
|
241
|
+
- Batches the rest into `DEFAULT_POSITIONS_PER_CLAIM`-sized (overridable via a third argument)
|
|
242
|
+
instruction groups, one per transaction.
|
|
243
|
+
- Shares a single set of ATA setup/teardown instructions across the whole batch, rather than
|
|
244
|
+
repeating them per position.
|
|
245
|
+
- Requires all positions to belong to the same pool.
|
|
194
246
|
|
|
195
247
|
```ts
|
|
196
248
|
const positions = await pool.getAllPositionsByPositionMints(positionMints);
|
|
@@ -203,7 +255,12 @@ for (const claimIxs of claimTxs) {
|
|
|
203
255
|
|
|
204
256
|
### Price math
|
|
205
257
|
|
|
206
|
-
`getBaseBinPrice(binStep)`, `getBinPrice(baseBinPrice, binId)`, and
|
|
258
|
+
`getBaseBinPrice(binStep)`, `getBinPrice(baseBinPrice, binId)`, and
|
|
259
|
+
`getBinPriceFromStep(binStep, binId)` are direct `bigint` ports of the on-chain Q64.64 price
|
|
260
|
+
math. They're useful for computing a bin's price without an RPC round trip — e.g. when picking
|
|
261
|
+
`lowerBinId`/`upperBinId` for a new position before it exists on-chain. `getBinPrice`/
|
|
262
|
+
`getBinPriceFromStep` return `undefined` if `binId` is outside the representable range for that
|
|
263
|
+
`binStep`:
|
|
207
264
|
|
|
208
265
|
```ts
|
|
209
266
|
import { getBinPriceFromStep } from "@picon-finance/dlmm-sdk";
|
|
@@ -213,19 +270,24 @@ const rawPrice = getBinPriceFromStep(pool.data.binStep, pool.activeBinId); // Q6
|
|
|
213
270
|
|
|
214
271
|
### Events
|
|
215
272
|
|
|
216
|
-
Event decoding and subscriptions are a separate, opt-in feature under the `events` namespace —
|
|
273
|
+
Event decoding and subscriptions are a separate, opt-in feature under the `events` namespace —
|
|
274
|
+
independent of `DlmmProgram`/`Pool`/`Position`/`BinArray`:
|
|
217
275
|
|
|
218
276
|
```ts
|
|
219
277
|
const rpcSubscriptions = createSolanaRpcSubscriptions("wss://api.mainnet-beta.solana.com");
|
|
220
278
|
|
|
279
|
+
// Omit programAddress to target mainnet, same as DlmmProgram above. Testing against devnet
|
|
280
|
+
// instead? Import DEVNET_PROGRAM_ADDRESS and pass it as { programAddress: DEVNET_PROGRAM_ADDRESS }.
|
|
221
281
|
const unsubscribe = await events.onEvent(rpcSubscriptions, (event) => {
|
|
222
282
|
console.log(event.name, event.data);
|
|
223
|
-
}
|
|
283
|
+
});
|
|
224
284
|
|
|
225
285
|
// unsubscribe() when done.
|
|
226
286
|
```
|
|
227
287
|
|
|
228
|
-
`MAINNET_PROGRAM_ADDRESS` and `DEVNET_PROGRAM_ADDRESS` are both exported for convenience
|
|
288
|
+
`MAINNET_PROGRAM_ADDRESS` and `DEVNET_PROGRAM_ADDRESS` are both exported for convenience.
|
|
289
|
+
Everywhere above that takes a `programAddress`, omitting it defaults to mainnet — pass
|
|
290
|
+
`DEVNET_PROGRAM_ADDRESS` explicitly to point the same code at devnet instead.
|
|
229
291
|
|
|
230
292
|
## License
|
|
231
293
|
|
package/dist/picon_dlmm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@picon-finance/dlmm-sdk",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.3",
|
|
4
4
|
"description": "Typescript SDK for interacting with the Picon DLMM protocol",
|
|
5
5
|
"author": "Picon Finance",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,9 +46,9 @@
|
|
|
46
46
|
"@solana-program/system": "^0.13.0",
|
|
47
47
|
"@solana-program/token": "^0.15.0",
|
|
48
48
|
"@solana-program/token-2022": "^0.15.0",
|
|
49
|
-
"@solana/kit": "^7.1.
|
|
50
|
-
"@solana/program-client-core": "^7.1.
|
|
51
|
-
"@solana/transaction-messages": "^7.1.
|
|
49
|
+
"@solana/kit": "^7.1.1",
|
|
50
|
+
"@solana/program-client-core": "^7.1.1",
|
|
51
|
+
"@solana/transaction-messages": "^7.1.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/bun": "latest",
|