@liquid-af/sdk 0.10.5 → 0.11.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.
@@ -1746,13 +1746,6 @@ export type LiquidEvents = {
1746
1746
  "type": {
1747
1747
  "kind": "struct",
1748
1748
  "fields": [
1749
- {
1750
- "name": "feeConfig",
1751
- "docs": [
1752
- "The fee configuration PDA."
1753
- ],
1754
- "type": "pubkey"
1755
- },
1756
1749
  {
1757
1750
  "name": "mint",
1758
1751
  "docs": [
@@ -2401,9 +2394,9 @@ export type LiquidEvents = {
2401
2394
  {
2402
2395
  "name": "cashback",
2403
2396
  "docs": [
2404
- "Cashback amount deducted from protocol fee."
2397
+ "Cashback earned (positive) or spent (negative) by the user."
2405
2398
  ],
2406
- "type": "u64"
2399
+ "type": "i64"
2407
2400
  },
2408
2401
  {
2409
2402
  "name": "cashbackBps",
@@ -2694,9 +2687,9 @@ export type LiquidEvents = {
2694
2687
  {
2695
2688
  "name": "cashback",
2696
2689
  "docs": [
2697
- "Cashback received by the user."
2690
+ "Cashback earned (positive) or spent (negative) by the user."
2698
2691
  ],
2699
- "type": "u64"
2692
+ "type": "i64"
2700
2693
  },
2701
2694
  {
2702
2695
  "name": "cashbackBps",
@@ -115,10 +115,6 @@
115
115
  116
116
116
  ]
117
117
  },
118
- {
119
- "kind": "account",
120
- "path": "fee_config"
121
- },
122
118
  {
123
119
  "kind": "account",
124
120
  "path": "recipient"
@@ -126,13 +122,6 @@
126
122
  ]
127
123
  }
128
124
  },
129
- {
130
- "name": "fee_config",
131
- "docs": [
132
- "The fee configuration this vault belongs to.",
133
- "only the correct fee_config will produce a vault PDA that has funds."
134
- ]
135
- },
136
125
  {
137
126
  "name": "system_program",
138
127
  "address": "11111111111111111111111111111111"
@@ -299,13 +288,6 @@
299
288
  ],
300
289
  "writable": true
301
290
  },
302
- {
303
- "name": "fee_config",
304
- "docs": [
305
- "The fee configuration this vault belongs to.",
306
- "only the correct fee_config will produce a vault PDA that has funds."
307
- ]
308
- },
309
291
  {
310
292
  "name": "quote_mint",
311
293
  "docs": [
@@ -339,10 +321,6 @@
339
321
  116
340
322
  ]
341
323
  },
342
- {
343
- "kind": "account",
344
- "path": "fee_config"
345
- },
346
324
  {
347
325
  "kind": "account",
348
326
  "path": "recipient"
@@ -121,10 +121,6 @@ export type LiquidFees = {
121
121
  116
122
122
  ]
123
123
  },
124
- {
125
- "kind": "account",
126
- "path": "feeConfig"
127
- },
128
124
  {
129
125
  "kind": "account",
130
126
  "path": "recipient"
@@ -132,13 +128,6 @@ export type LiquidFees = {
132
128
  ]
133
129
  }
134
130
  },
135
- {
136
- "name": "feeConfig",
137
- "docs": [
138
- "The fee configuration this vault belongs to.",
139
- "only the correct fee_config will produce a vault PDA that has funds."
140
- ]
141
- },
142
131
  {
143
132
  "name": "systemProgram",
144
133
  "address": "11111111111111111111111111111111"
@@ -305,13 +294,6 @@ export type LiquidFees = {
305
294
  ],
306
295
  "writable": true
307
296
  },
308
- {
309
- "name": "feeConfig",
310
- "docs": [
311
- "The fee configuration this vault belongs to.",
312
- "only the correct fee_config will produce a vault PDA that has funds."
313
- ]
314
- },
315
297
  {
316
298
  "name": "quoteMint",
317
299
  "docs": [
@@ -345,10 +327,6 @@ export type LiquidFees = {
345
327
  116
346
328
  ]
347
329
  },
348
- {
349
- "kind": "account",
350
- "path": "feeConfig"
351
- },
352
330
  {
353
331
  "kind": "account",
354
332
  "path": "recipient"
@@ -209,14 +209,13 @@ export function buildDistributeTokenFees(
209
209
 
210
210
  export interface BuildClaimFeesParams {
211
211
  recipient: PublicKey;
212
- tokenMint: PublicKey;
213
- quoteMint: PublicKey;
214
212
  config: LiquidConfig;
215
213
  }
216
214
 
217
215
  /**
218
216
  * Builds a claimFees instruction.
219
217
  * Allows a recipient to claim accumulated SOL fees from their vault PDA.
218
+ * Vaults are shared across all fee configs — one claim drains all SOL fees.
220
219
  *
221
220
  * @param params - {@link BuildClaimFeesParams}
222
221
  * @returns Transaction instruction
@@ -224,26 +223,19 @@ export interface BuildClaimFeesParams {
224
223
  export function buildClaimFees(
225
224
  params: BuildClaimFeesParams,
226
225
  ): Promise<TransactionInstruction> {
227
- const { recipient, tokenMint, quoteMint, config } = params;
226
+ const { recipient, config } = params;
228
227
  const program = getCachedFeesProgram(config);
229
- const [feeConfig] = getFeeConfigPDA(
230
- tokenMint,
231
- quoteMint,
232
- config.liquidFeesProgramId,
233
- );
234
228
 
235
229
  return program.methods
236
230
  .claimFees()
237
231
  .accounts({
238
232
  recipient,
239
- feeConfig,
240
233
  })
241
234
  .instruction();
242
235
  }
243
236
 
244
237
  export interface BuildClaimTokenFeesParams {
245
238
  recipient: PublicKey;
246
- tokenMint: PublicKey;
247
239
  quoteMint: PublicKey;
248
240
  recipientTokenAccount?: PublicKey;
249
241
  config: LiquidConfig;
@@ -252,7 +244,7 @@ export interface BuildClaimTokenFeesParams {
252
244
  /**
253
245
  * Builds a claimTokenFees instruction.
254
246
  * Allows a recipient to claim accumulated token fees from their vault ATA.
255
- * Closes the vault ATA after claiming to reclaim rent.
247
+ * Vaults are shared across all fee configs one claim drains all token fees for the quote mint.
256
248
  *
257
249
  * @param params - {@link BuildClaimTokenFeesParams}
258
250
  * @returns Transaction instruction
@@ -260,14 +252,8 @@ export interface BuildClaimTokenFeesParams {
260
252
  export function buildClaimTokenFees(
261
253
  params: BuildClaimTokenFeesParams,
262
254
  ): Promise<TransactionInstruction> {
263
- const { recipient, tokenMint, quoteMint, recipientTokenAccount, config } =
264
- params;
255
+ const { recipient, quoteMint, recipientTokenAccount, config } = params;
265
256
  const program = getCachedFeesProgram(config);
266
- const [feeConfig] = getFeeConfigPDA(
267
- tokenMint,
268
- quoteMint,
269
- config.liquidFeesProgramId,
270
- );
271
257
 
272
258
  const resolvedRecipientTokenAccount =
273
259
  recipientTokenAccount ??
@@ -277,7 +263,6 @@ export function buildClaimTokenFees(
277
263
  .claimTokenFees()
278
264
  .accounts({
279
265
  recipient,
280
- feeConfig,
281
266
  quoteMint,
282
267
  recipientTokenAccount: resolvedRecipientTokenAccount,
283
268
  })
@@ -313,26 +298,18 @@ export function buildInitializeGlobalConfig(
313
298
  /**
314
299
  * Helper: derives recipient vault PDA pairs for use with buildDistributeTokenFees.
315
300
  *
316
- * @param tokenMint - Token mint address
317
301
  * @param recipients - Array of recipient public keys in fee_config order
318
302
  * @param quoteMint - Quote mint for ATA derivation
319
303
  * @param config - Liquid protocol config
320
304
  * @returns Array of RecipientVaultPair for remaining accounts
321
305
  */
322
306
  export function deriveRecipientVaultPairs(
323
- tokenMint: PublicKey,
324
307
  recipients: PublicKey[],
325
308
  quoteMint: PublicKey,
326
309
  config: LiquidConfig,
327
310
  ): RecipientVaultPair[] {
328
- const [feeConfig] = getFeeConfigPDA(
329
- tokenMint,
330
- quoteMint,
331
- config.liquidFeesProgramId,
332
- );
333
311
  return recipients.map((recipient) => {
334
312
  const [vaultPda] = getRecipientVaultPDA(
335
- feeConfig,
336
313
  recipient,
337
314
  config.liquidFeesProgramId,
338
315
  );
@@ -41,8 +41,9 @@ export const calculateBuyExpectation = (
41
41
  // New quote reserves after adding net amount
42
42
  const newVirtualQuote = curveState.virtualQuoteReserves.add(amountInNet);
43
43
 
44
- // Solve for new token reserves: newToken = k / newQuote + 1 (ceiling division)
45
- const newVirtualToken = k.div(newVirtualQuote).add(new BN(1));
44
+ // Solve for new token reserves: newToken = k / newQuote
45
+ const { div: q1, mod: r1 } = k.divmod(newVirtualQuote);
46
+ const newVirtualToken = r1.isZero() ? q1 : q1.add(new BN(1));
46
47
 
47
48
  // Tokens out = difference in token reserves
48
49
  const tokensOut = curveState.virtualTokenReserves.sub(newVirtualToken);
@@ -90,8 +91,9 @@ export const calculateSellExpectation = (
90
91
  // New token reserves after adding sold tokens
91
92
  const newVirtualToken = curveState.virtualTokenReserves.add(amountInTokens);
92
93
 
93
- // Solve for new quote reserves: newQuote = k / newToken + 1 (ceiling division)
94
- const newVirtualQuote = k.div(newVirtualToken).add(new BN(1));
94
+ // Solve for new quote reserves: newQuote = k / newToken
95
+ const { div: q2, mod: r2 } = k.divmod(newVirtualToken);
96
+ const newVirtualQuote = r2.isZero() ? q2 : q2.add(new BN(1));
95
97
 
96
98
  // Gross quote out (before fees)
97
99
  const quoteOutGross = curveState.virtualQuoteReserves.sub(newVirtualQuote);
package/src/math/fees.ts CHANGED
@@ -9,14 +9,18 @@ import type {
9
9
 
10
10
  /**
11
11
  * Calculates basis points of an amount.
12
- * result = (amount * bps) / 10000
12
+ * result = ceil(amount * bps / 10000)
13
13
  *
14
14
  * @param amount - The amount to calculate basis points of
15
15
  * @param bps - Basis points (1 bps = 0.01%)
16
16
  * @returns The calculated portion
17
17
  */
18
- export const calcBps = (amount: BN, bps: number): BN =>
19
- amount.mul(new BN(bps)).div(new BN(BPS_DENOMINATOR));
18
+ export const calcBps = (amount: BN, bps: number): BN => {
19
+ if (bps === 0) return new BN(0);
20
+ const numerator = amount.mul(new BN(bps));
21
+ const denom = new BN(BPS_DENOMINATOR);
22
+ return numerator.add(denom).sub(new BN(1)).div(denom);
23
+ };
20
24
 
21
25
  /**
22
26
  * Fee configuration for bonding curve trades.
@@ -30,7 +34,10 @@ export interface FeeConfig {
30
34
 
31
35
  /**
32
36
  * Calculates fee distribution for a given trade amount.
33
- * Referral fees are carved from the protocol fee, not the trade amount.
37
+ * Matches the on-chain algorithm in `trade_utils::fees::calculate_fees`:
38
+ * - Single ceiling division for total fees
39
+ * - Proportional floor distribution for creator; protocol gets remainder (dust)
40
+ * - Referral fees use ceiling (carved from protocol fee, not trade amount)
34
41
  *
35
42
  * @param amount - Gross trade amount (before fees)
36
43
  * @param config - Fee configuration in basis points
@@ -44,23 +51,34 @@ export const calculateFees = (
44
51
  hasCreatorRef: boolean,
45
52
  hasTraderRef: boolean,
46
53
  ): FeeDistribution => {
47
- const baseProtocolFee = calcBps(amount, config.protocolFeeBps);
48
- const creatorFee = calcBps(amount, config.creatorFeeBps);
54
+ const totalBps = config.protocolFeeBps + config.creatorFeeBps;
55
+ if (totalBps === 0) {
56
+ return {
57
+ protocolFee: new BN(0),
58
+ creatorFee: new BN(0),
59
+ creatorReferralFee: new BN(0),
60
+ traderReferralFee: new BN(0),
61
+ totalFees: new BN(0),
62
+ };
63
+ }
64
+
65
+ // Single ceiling division for total fees (matches on-chain)
66
+ const totalFees = calcBps(amount, totalBps);
49
67
 
50
- // Referral amounts are BPS of the protocol fee (not trade amount)
68
+ // Proportional floor distribution: creator gets floor, protocol gets remainder
69
+ const creatorFee = totalFees
70
+ .mul(new BN(config.creatorFeeBps))
71
+ .div(new BN(totalBps));
72
+ const protocolFee = totalFees.sub(creatorFee);
73
+
74
+ // Referral amounts are ceiling BPS of the protocol fee (not trade amount)
51
75
  const creatorReferralFee = hasCreatorRef
52
- ? calcBps(baseProtocolFee, config.creatorReferralBps)
76
+ ? calcBps(protocolFee, config.creatorReferralBps)
53
77
  : new BN(0);
54
78
  const traderReferralFee = hasTraderRef
55
- ? calcBps(baseProtocolFee, config.traderReferralBps)
79
+ ? calcBps(protocolFee, config.traderReferralBps)
56
80
  : new BN(0);
57
81
 
58
- // Protocol fee starts at base amount. On-chain, it is only reduced
59
- // when referral transfers actually succeed.
60
- const protocolFee = baseProtocolFee;
61
- // Referrals are carved from protocol fee (not additive), so total = protocol + creator.
62
- const totalFees = protocolFee.add(creatorFee);
63
-
64
82
  return {
65
83
  protocolFee,
66
84
  creatorFee,
@@ -54,20 +54,19 @@ export const getFeeVaultPDA = (
54
54
  };
55
55
 
56
56
  /**
57
- * Derives the recipient vault PDA for a specific recipient within a fee config.
57
+ * Derives the recipient vault PDA for a specific recipient.
58
+ * Vaults are shared across all fee configs — one vault per recipient.
58
59
  *
59
- * @param feeConfig - Fee config PDA address
60
60
  * @param recipient - Recipient wallet address
61
61
  * @param programId - Liquid Fees program ID
62
62
  * @returns Tuple of [PDA address, bump seed]
63
63
  */
64
64
  export const getRecipientVaultPDA = (
65
- feeConfig: PublicKey,
66
65
  recipient: PublicKey,
67
66
  programId: PublicKey,
68
67
  ): [PublicKey, number] => {
69
68
  return PublicKey.findProgramAddressSync(
70
- [SEED_RECIPIENT_VAULT, feeConfig.toBuffer(), recipient.toBuffer()],
69
+ [SEED_RECIPIENT_VAULT, recipient.toBuffer()],
71
70
  programId,
72
71
  );
73
72
  };