@liquid-af/sdk 0.10.5 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/client.d.ts +8 -5
  2. package/dist/client.d.ts.map +1 -1
  3. package/dist/client.js +8 -4
  4. package/dist/client.js.map +1 -1
  5. package/dist/idl/liquid_events.d.ts +4 -11
  6. package/dist/idl/liquid_events.d.ts.map +1 -1
  7. package/dist/idl/liquid_events.json +4 -11
  8. package/dist/idl/liquid_fees.d.ts +0 -22
  9. package/dist/idl/liquid_fees.d.ts.map +1 -1
  10. package/dist/idl/liquid_fees.json +0 -22
  11. package/dist/instructions/index.d.ts +2 -2
  12. package/dist/instructions/index.d.ts.map +1 -1
  13. package/dist/instructions/index.js +1 -1
  14. package/dist/instructions/index.js.map +1 -1
  15. package/dist/instructions/liquid-fees.d.ts +18 -7
  16. package/dist/instructions/liquid-fees.d.ts.map +1 -1
  17. package/dist/instructions/liquid-fees.js +20 -11
  18. package/dist/instructions/liquid-fees.js.map +1 -1
  19. package/dist/math/bonding-curve.d.ts.map +1 -1
  20. package/dist/math/bonding-curve.js +6 -4
  21. package/dist/math/bonding-curve.js.map +1 -1
  22. package/dist/math/fees.d.ts +5 -2
  23. package/dist/math/fees.d.ts.map +1 -1
  24. package/dist/math/fees.js +32 -13
  25. package/dist/math/fees.js.map +1 -1
  26. package/dist/pda/liquid-fees.d.ts +3 -3
  27. package/dist/pda/liquid-fees.d.ts.map +1 -1
  28. package/dist/pda/liquid-fees.js +4 -4
  29. package/dist/pda/liquid-fees.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/client.ts +12 -9
  32. package/src/idl/liquid_events.json +4 -11
  33. package/src/idl/liquid_events.ts +4 -11
  34. package/src/idl/liquid_fees.json +0 -22
  35. package/src/idl/liquid_fees.ts +0 -22
  36. package/src/instructions/index.ts +2 -0
  37. package/src/instructions/liquid-fees.ts +28 -28
  38. package/src/math/bonding-curve.ts +6 -4
  39. package/src/math/fees.ts +33 -15
  40. package/src/pda/liquid-fees.ts +3 -4
@@ -34,14 +34,14 @@ export const getFeeVaultPDA = (feeConfig, programId) => {
34
34
  return PublicKey.findProgramAddressSync([SEED_FEE_VAULT, feeConfig.toBuffer()], programId);
35
35
  };
36
36
  /**
37
- * Derives the recipient vault PDA for a specific recipient within a fee config.
37
+ * Derives the recipient vault PDA for a specific recipient.
38
+ * Vaults are shared across all fee configs — one vault per recipient.
38
39
  *
39
- * @param feeConfig - Fee config PDA address
40
40
  * @param recipient - Recipient wallet address
41
41
  * @param programId - Liquid Fees program ID
42
42
  * @returns Tuple of [PDA address, bump seed]
43
43
  */
44
- export const getRecipientVaultPDA = (feeConfig, recipient, programId) => {
45
- return PublicKey.findProgramAddressSync([SEED_RECIPIENT_VAULT, feeConfig.toBuffer(), recipient.toBuffer()], programId);
44
+ export const getRecipientVaultPDA = (recipient, programId) => {
45
+ return PublicKey.findProgramAddressSync([SEED_RECIPIENT_VAULT, recipient.toBuffer()], programId);
46
46
  };
47
47
  //# sourceMappingURL=liquid-fees.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"liquid-fees.js","sourceRoot":"","sources":["../../src/pda/liquid-fees.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAClD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAChD,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AACxD,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,SAAoB,EACpB,SAAoB,EACpB,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CACtC,CAAC,eAAe,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,EAC7D,SAAS,CACT,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACpC,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CAAC,CAAC,kBAAkB,CAAC,EAAE,SAAS,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC7B,SAAoB,EACpB,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CACtC,CAAC,cAAc,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,EACtC,SAAS,CACT,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CACnC,SAAoB,EACpB,SAAoB,EACpB,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CACtC,CAAC,oBAAoB,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,EAClE,SAAS,CACT,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"liquid-fees.js","sourceRoot":"","sources":["../../src/pda/liquid-fees.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAClD,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;AAChD,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;AACxD,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,SAAoB,EACpB,SAAoB,EACpB,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CACtC,CAAC,eAAe,EAAE,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,EAC7D,SAAS,CACT,CAAC;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACpC,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CAAC,CAAC,kBAAkB,CAAC,EAAE,SAAS,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC7B,SAAoB,EACpB,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CACtC,CAAC,cAAc,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,EACtC,SAAS,CACT,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CACnC,SAAoB,EACpB,SAAoB,EACE,EAAE;IACxB,OAAO,SAAS,CAAC,sBAAsB,CACtC,CAAC,oBAAoB,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,EAC5C,SAAS,CACT,CAAC;AACH,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liquid-af/sdk",
3
- "version": "0.10.5",
3
+ "version": "0.11.1",
4
4
  "description": "TypeScript SDK for the Liquid DeFi protocol on Solana",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/client.ts CHANGED
@@ -50,6 +50,7 @@ import {
50
50
  import {
51
51
  buildUpdateFeeConfig,
52
52
  buildRevokeFeeConfig,
53
+ buildRevokeUpdateAuthority,
53
54
  buildDistributeFees,
54
55
  buildDistributeTokenFees,
55
56
  buildClaimFees,
@@ -686,6 +687,15 @@ export class LiquidClient {
686
687
  return buildRevokeFeeConfig({ ...params, config: this.config });
687
688
  }
688
689
 
690
+ /** Revokes the update authority by setting it to the default pubkey. Irreversible. */
691
+ buildRevokeUpdateAuthority(params: {
692
+ authority: PublicKey;
693
+ tokenMint: PublicKey;
694
+ quoteMint: PublicKey;
695
+ }): Promise<TransactionInstruction> {
696
+ return buildRevokeUpdateAuthority({ ...params, config: this.config });
697
+ }
698
+
689
699
  /** Builds a permissionless distributeFees instruction (SOL fees). */
690
700
  buildDistributeFees(params: {
691
701
  payer: PublicKey;
@@ -709,8 +719,6 @@ export class LiquidClient {
709
719
  /** Builds a claimFees instruction. Recipient claims accumulated SOL from their vault PDA. */
710
720
  buildClaimFees(params: {
711
721
  recipient: PublicKey;
712
- tokenMint: PublicKey;
713
- quoteMint: PublicKey;
714
722
  }): Promise<TransactionInstruction> {
715
723
  return buildClaimFees({ ...params, config: this.config });
716
724
  }
@@ -718,7 +726,6 @@ export class LiquidClient {
718
726
  /** Builds a claimTokenFees instruction. Recipient claims accumulated tokens from their vault ATA. */
719
727
  buildClaimTokenFees(params: {
720
728
  recipient: PublicKey;
721
- tokenMint: PublicKey;
722
729
  quoteMint: PublicKey;
723
730
  }): Promise<TransactionInstruction> {
724
731
  return buildClaimTokenFees({ ...params, config: this.config });
@@ -1027,13 +1034,9 @@ export class LiquidClient {
1027
1034
  return getFeeVaultPDA(feeConfig, this.config.liquidFeesProgramId);
1028
1035
  }
1029
1036
 
1030
- /** Derives the recipient vault PDA for a specific recipient within a fee config. */
1031
- getRecipientVaultPDA(
1032
- feeConfig: PublicKey,
1033
- recipient: PublicKey,
1034
- ): [PublicKey, number] {
1037
+ /** Derives the recipient vault PDA for a specific recipient. */
1038
+ getRecipientVaultPDA(recipient: PublicKey): [PublicKey, number] {
1035
1039
  return getRecipientVaultPDA(
1036
- feeConfig,
1037
1040
  recipient,
1038
1041
  this.config.liquidFeesProgramId,
1039
1042
  );
@@ -1740,13 +1740,6 @@
1740
1740
  "type": {
1741
1741
  "kind": "struct",
1742
1742
  "fields": [
1743
- {
1744
- "name": "fee_config",
1745
- "docs": [
1746
- "The fee configuration PDA."
1747
- ],
1748
- "type": "pubkey"
1749
- },
1750
1743
  {
1751
1744
  "name": "mint",
1752
1745
  "docs": [
@@ -2395,9 +2388,9 @@
2395
2388
  {
2396
2389
  "name": "cashback",
2397
2390
  "docs": [
2398
- "Cashback amount deducted from protocol fee."
2391
+ "Cashback earned (positive) or spent (negative) by the user."
2399
2392
  ],
2400
- "type": "u64"
2393
+ "type": "i64"
2401
2394
  },
2402
2395
  {
2403
2396
  "name": "cashback_bps",
@@ -2688,9 +2681,9 @@
2688
2681
  {
2689
2682
  "name": "cashback",
2690
2683
  "docs": [
2691
- "Cashback received by the user."
2684
+ "Cashback earned (positive) or spent (negative) by the user."
2692
2685
  ],
2693
- "type": "u64"
2686
+ "type": "i64"
2694
2687
  },
2695
2688
  {
2696
2689
  "name": "cashback_bps",
@@ -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"
@@ -78,6 +78,7 @@ export {
78
78
  buildInitializeGlobalConfig,
79
79
  buildUpdateFeeConfig,
80
80
  buildRevokeFeeConfig,
81
+ buildRevokeUpdateAuthority,
81
82
  buildDistributeFees,
82
83
  buildDistributeTokenFees,
83
84
  buildClaimFees,
@@ -88,6 +89,7 @@ export type {
88
89
  BuildInitializeGlobalConfigParams,
89
90
  BuildUpdateFeeConfigParams,
90
91
  BuildRevokeFeeConfigParams,
92
+ BuildRevokeUpdateAuthorityParams,
91
93
  BuildDistributeFeesParams,
92
94
  BuildDistributeTokenFeesParams,
93
95
  BuildClaimFeesParams,
@@ -1,4 +1,4 @@
1
- import type { PublicKey, TransactionInstruction } from "@solana/web3.js";
1
+ import { PublicKey, type TransactionInstruction } from "@solana/web3.js";
2
2
  import { getAssociatedTokenAddressSync } from "@solana/spl-token";
3
3
  import type { LiquidConfig } from "../config.js";
4
4
  import type { FeeRecipient } from "../types.js";
@@ -122,6 +122,29 @@ export function buildRevokeFeeConfig(
122
122
  .instruction();
123
123
  }
124
124
 
125
+ export interface BuildRevokeUpdateAuthorityParams {
126
+ authority: PublicKey;
127
+ tokenMint: PublicKey;
128
+ quoteMint: PublicKey;
129
+ config: LiquidConfig;
130
+ }
131
+
132
+ /**
133
+ * Builds an updateFeeConfig instruction that permanently revokes the update authority
134
+ * by setting it to the default public key (all zeros). Irreversible.
135
+ *
136
+ * @param params - {@link BuildRevokeUpdateAuthorityParams}
137
+ * @returns Transaction instruction
138
+ */
139
+ export function buildRevokeUpdateAuthority(
140
+ params: BuildRevokeUpdateAuthorityParams,
141
+ ): Promise<TransactionInstruction> {
142
+ return buildUpdateFeeConfig({
143
+ ...params,
144
+ newUpdateAuthority: PublicKey.default,
145
+ });
146
+ }
147
+
125
148
  export interface BuildDistributeFeesParams {
126
149
  payer: PublicKey;
127
150
  tokenMint: PublicKey;
@@ -209,14 +232,13 @@ export function buildDistributeTokenFees(
209
232
 
210
233
  export interface BuildClaimFeesParams {
211
234
  recipient: PublicKey;
212
- tokenMint: PublicKey;
213
- quoteMint: PublicKey;
214
235
  config: LiquidConfig;
215
236
  }
216
237
 
217
238
  /**
218
239
  * Builds a claimFees instruction.
219
240
  * Allows a recipient to claim accumulated SOL fees from their vault PDA.
241
+ * Vaults are shared across all fee configs — one claim drains all SOL fees.
220
242
  *
221
243
  * @param params - {@link BuildClaimFeesParams}
222
244
  * @returns Transaction instruction
@@ -224,26 +246,19 @@ export interface BuildClaimFeesParams {
224
246
  export function buildClaimFees(
225
247
  params: BuildClaimFeesParams,
226
248
  ): Promise<TransactionInstruction> {
227
- const { recipient, tokenMint, quoteMint, config } = params;
249
+ const { recipient, config } = params;
228
250
  const program = getCachedFeesProgram(config);
229
- const [feeConfig] = getFeeConfigPDA(
230
- tokenMint,
231
- quoteMint,
232
- config.liquidFeesProgramId,
233
- );
234
251
 
235
252
  return program.methods
236
253
  .claimFees()
237
254
  .accounts({
238
255
  recipient,
239
- feeConfig,
240
256
  })
241
257
  .instruction();
242
258
  }
243
259
 
244
260
  export interface BuildClaimTokenFeesParams {
245
261
  recipient: PublicKey;
246
- tokenMint: PublicKey;
247
262
  quoteMint: PublicKey;
248
263
  recipientTokenAccount?: PublicKey;
249
264
  config: LiquidConfig;
@@ -252,7 +267,7 @@ export interface BuildClaimTokenFeesParams {
252
267
  /**
253
268
  * Builds a claimTokenFees instruction.
254
269
  * Allows a recipient to claim accumulated token fees from their vault ATA.
255
- * Closes the vault ATA after claiming to reclaim rent.
270
+ * Vaults are shared across all fee configs one claim drains all token fees for the quote mint.
256
271
  *
257
272
  * @param params - {@link BuildClaimTokenFeesParams}
258
273
  * @returns Transaction instruction
@@ -260,14 +275,8 @@ export interface BuildClaimTokenFeesParams {
260
275
  export function buildClaimTokenFees(
261
276
  params: BuildClaimTokenFeesParams,
262
277
  ): Promise<TransactionInstruction> {
263
- const { recipient, tokenMint, quoteMint, recipientTokenAccount, config } =
264
- params;
278
+ const { recipient, quoteMint, recipientTokenAccount, config } = params;
265
279
  const program = getCachedFeesProgram(config);
266
- const [feeConfig] = getFeeConfigPDA(
267
- tokenMint,
268
- quoteMint,
269
- config.liquidFeesProgramId,
270
- );
271
280
 
272
281
  const resolvedRecipientTokenAccount =
273
282
  recipientTokenAccount ??
@@ -277,7 +286,6 @@ export function buildClaimTokenFees(
277
286
  .claimTokenFees()
278
287
  .accounts({
279
288
  recipient,
280
- feeConfig,
281
289
  quoteMint,
282
290
  recipientTokenAccount: resolvedRecipientTokenAccount,
283
291
  })
@@ -313,26 +321,18 @@ export function buildInitializeGlobalConfig(
313
321
  /**
314
322
  * Helper: derives recipient vault PDA pairs for use with buildDistributeTokenFees.
315
323
  *
316
- * @param tokenMint - Token mint address
317
324
  * @param recipients - Array of recipient public keys in fee_config order
318
325
  * @param quoteMint - Quote mint for ATA derivation
319
326
  * @param config - Liquid protocol config
320
327
  * @returns Array of RecipientVaultPair for remaining accounts
321
328
  */
322
329
  export function deriveRecipientVaultPairs(
323
- tokenMint: PublicKey,
324
330
  recipients: PublicKey[],
325
331
  quoteMint: PublicKey,
326
332
  config: LiquidConfig,
327
333
  ): RecipientVaultPair[] {
328
- const [feeConfig] = getFeeConfigPDA(
329
- tokenMint,
330
- quoteMint,
331
- config.liquidFeesProgramId,
332
- );
333
334
  return recipients.map((recipient) => {
334
335
  const [vaultPda] = getRecipientVaultPDA(
335
- feeConfig,
336
336
  recipient,
337
337
  config.liquidFeesProgramId,
338
338
  );
@@ -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
  };