@liquid-af/sdk 0.1.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/README.md +776 -0
- package/dist/accounts/index.d.ts +5 -0
- package/dist/accounts/index.d.ts.map +1 -0
- package/dist/accounts/index.js +5 -0
- package/dist/accounts/index.js.map +1 -0
- package/dist/accounts/liquid-fees.d.ts +63 -0
- package/dist/accounts/liquid-fees.d.ts.map +1 -0
- package/dist/accounts/liquid-fees.js +27 -0
- package/dist/accounts/liquid-fees.js.map +1 -0
- package/dist/accounts/liquid-state.d.ts +112 -0
- package/dist/accounts/liquid-state.d.ts.map +1 -0
- package/dist/accounts/liquid-state.js +92 -0
- package/dist/accounts/liquid-state.js.map +1 -0
- package/dist/accounts/liquid-swap.d.ts +97 -0
- package/dist/accounts/liquid-swap.d.ts.map +1 -0
- package/dist/accounts/liquid-swap.js +54 -0
- package/dist/accounts/liquid-swap.js.map +1 -0
- package/dist/accounts/liquid.d.ts +175 -0
- package/dist/accounts/liquid.d.ts.map +1 -0
- package/dist/accounts/liquid.js +66 -0
- package/dist/accounts/liquid.js.map +1 -0
- package/dist/client.d.ts +621 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +511 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +56 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +63 -0
- package/dist/errors.js.map +1 -0
- package/dist/events/index.d.ts +4 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +2 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/parser.d.ts +40 -0
- package/dist/events/parser.d.ts.map +1 -0
- package/dist/events/parser.js +67 -0
- package/dist/events/parser.js.map +1 -0
- package/dist/events/types.d.ts +286 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +2 -0
- package/dist/events/types.js.map +1 -0
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +3 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/preview.d.ts +259 -0
- package/dist/helpers/preview.d.ts.map +1 -0
- package/dist/helpers/preview.js +458 -0
- package/dist/helpers/preview.js.map +1 -0
- package/dist/helpers/user.d.ts +11 -0
- package/dist/helpers/user.d.ts.map +1 -0
- package/dist/helpers/user.js +20 -0
- package/dist/helpers/user.js.map +1 -0
- package/dist/idl/index.d.ts +53 -0
- package/dist/idl/index.d.ts.map +1 -0
- package/dist/idl/index.js +64 -0
- package/dist/idl/index.js.map +1 -0
- package/dist/idl/liquid.d.ts +10523 -0
- package/dist/idl/liquid.d.ts.map +1 -0
- package/dist/idl/liquid.js +2 -0
- package/dist/idl/liquid.js.map +1 -0
- package/dist/idl/liquid.json +10516 -0
- package/dist/idl/liquid_fees.d.ts +1520 -0
- package/dist/idl/liquid_fees.d.ts.map +1 -0
- package/dist/idl/liquid_fees.js +2 -0
- package/dist/idl/liquid_fees.js.map +1 -0
- package/dist/idl/liquid_fees.json +1513 -0
- package/dist/idl/liquid_state.d.ts +2936 -0
- package/dist/idl/liquid_state.d.ts.map +1 -0
- package/dist/idl/liquid_state.js +2 -0
- package/dist/idl/liquid_state.js.map +1 -0
- package/dist/idl/liquid_state.json +2929 -0
- package/dist/idl/liquid_swap.d.ts +5849 -0
- package/dist/idl/liquid_swap.d.ts.map +1 -0
- package/dist/idl/liquid_swap.js +2 -0
- package/dist/idl/liquid_swap.js.map +1 -0
- package/dist/idl/liquid_swap.json +5842 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/instructions/index.d.ts +9 -0
- package/dist/instructions/index.d.ts.map +1 -0
- package/dist/instructions/index.js +9 -0
- package/dist/instructions/index.js.map +1 -0
- package/dist/instructions/liquid-fees.d.ts +111 -0
- package/dist/instructions/liquid-fees.d.ts.map +1 -0
- package/dist/instructions/liquid-fees.js +169 -0
- package/dist/instructions/liquid-fees.js.map +1 -0
- package/dist/instructions/liquid-state.d.ts +173 -0
- package/dist/instructions/liquid-state.d.ts.map +1 -0
- package/dist/instructions/liquid-state.js +194 -0
- package/dist/instructions/liquid-state.js.map +1 -0
- package/dist/instructions/liquid-swap.d.ts +122 -0
- package/dist/instructions/liquid-swap.d.ts.map +1 -0
- package/dist/instructions/liquid-swap.js +167 -0
- package/dist/instructions/liquid-swap.js.map +1 -0
- package/dist/instructions/liquid.d.ts +297 -0
- package/dist/instructions/liquid.d.ts.map +1 -0
- package/dist/instructions/liquid.js +483 -0
- package/dist/instructions/liquid.js.map +1 -0
- package/dist/instructions/program-cache.d.ts +35 -0
- package/dist/instructions/program-cache.d.ts.map +1 -0
- package/dist/instructions/program-cache.js +68 -0
- package/dist/instructions/program-cache.js.map +1 -0
- package/dist/math/amm.d.ts +42 -0
- package/dist/math/amm.d.ts.map +1 -0
- package/dist/math/amm.js +109 -0
- package/dist/math/amm.js.map +1 -0
- package/dist/math/bonding-curve.d.ts +34 -0
- package/dist/math/bonding-curve.d.ts.map +1 -0
- package/dist/math/bonding-curve.js +80 -0
- package/dist/math/bonding-curve.js.map +1 -0
- package/dist/math/constants.d.ts +14 -0
- package/dist/math/constants.d.ts.map +1 -0
- package/dist/math/constants.js +14 -0
- package/dist/math/constants.js.map +1 -0
- package/dist/math/fees.d.ts +88 -0
- package/dist/math/fees.d.ts.map +1 -0
- package/dist/math/fees.js +135 -0
- package/dist/math/fees.js.map +1 -0
- package/dist/math/index.d.ts +8 -0
- package/dist/math/index.d.ts.map +1 -0
- package/dist/math/index.js +6 -0
- package/dist/math/index.js.map +1 -0
- package/dist/math/tiered-fees.d.ts +80 -0
- package/dist/math/tiered-fees.d.ts.map +1 -0
- package/dist/math/tiered-fees.js +129 -0
- package/dist/math/tiered-fees.js.map +1 -0
- package/dist/oracle.d.ts +53 -0
- package/dist/oracle.d.ts.map +1 -0
- package/dist/oracle.js +70 -0
- package/dist/oracle.js.map +1 -0
- package/dist/pda/index.d.ts +89 -0
- package/dist/pda/index.d.ts.map +1 -0
- package/dist/pda/index.js +127 -0
- package/dist/pda/index.js.map +1 -0
- package/dist/pda/liquid-fees.d.ts +27 -0
- package/dist/pda/liquid-fees.d.ts.map +1 -0
- package/dist/pda/liquid-fees.js +36 -0
- package/dist/pda/liquid-fees.js.map +1 -0
- package/dist/pda/liquid-state.d.ts +56 -0
- package/dist/pda/liquid-state.d.ts.map +1 -0
- package/dist/pda/liquid-state.js +79 -0
- package/dist/pda/liquid-state.js.map +1 -0
- package/dist/pda/liquid-swap.d.ts +76 -0
- package/dist/pda/liquid-swap.d.ts.map +1 -0
- package/dist/pda/liquid-swap.js +103 -0
- package/dist/pda/liquid-swap.js.map +1 -0
- package/dist/pda/liquid.d.ts +67 -0
- package/dist/pda/liquid.d.ts.map +1 -0
- package/dist/pda/liquid.js +91 -0
- package/dist/pda/liquid.js.map +1 -0
- package/dist/provider.d.ts +26 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +47 -0
- package/dist/provider.js.map +1 -0
- package/dist/transaction/builder.d.ts +30 -0
- package/dist/transaction/builder.d.ts.map +1 -0
- package/dist/transaction/builder.js +48 -0
- package/dist/transaction/builder.js.map +1 -0
- package/dist/transaction/index.d.ts +3 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/send.d.ts +25 -0
- package/dist/transaction/send.d.ts.map +1 -0
- package/dist/transaction/send.js +52 -0
- package/dist/transaction/send.js.map +1 -0
- package/dist/types.d.ts +311 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +46 -0
- package/dist/types.js.map +1 -0
- package/package.json +112 -0
- package/src/accounts/index.ts +26 -0
- package/src/accounts/liquid-fees.ts +38 -0
- package/src/accounts/liquid-state.ts +134 -0
- package/src/accounts/liquid-swap.ts +79 -0
- package/src/accounts/liquid.ts +100 -0
- package/src/client.ts +1001 -0
- package/src/config.ts +91 -0
- package/src/errors.ts +94 -0
- package/src/events/index.ts +42 -0
- package/src/events/parser.ts +90 -0
- package/src/events/types.ts +310 -0
- package/src/helpers/index.ts +23 -0
- package/src/helpers/preview.ts +798 -0
- package/src/helpers/user.ts +24 -0
- package/src/idl/index.ts +94 -0
- package/src/idl/liquid.json +10516 -0
- package/src/idl/liquid.ts +10522 -0
- package/src/idl/liquid_fees.json +1513 -0
- package/src/idl/liquid_fees.ts +1519 -0
- package/src/idl/liquid_state.json +2929 -0
- package/src/idl/liquid_state.ts +2935 -0
- package/src/idl/liquid_swap.json +5842 -0
- package/src/idl/liquid_swap.ts +5848 -0
- package/src/index.ts +98 -0
- package/src/instructions/index.ts +109 -0
- package/src/instructions/liquid-fees.ts +289 -0
- package/src/instructions/liquid-state.ts +336 -0
- package/src/instructions/liquid-swap.ts +414 -0
- package/src/instructions/liquid.ts +884 -0
- package/src/instructions/program-cache.ts +106 -0
- package/src/math/amm.ts +146 -0
- package/src/math/bonding-curve.ts +122 -0
- package/src/math/constants.ts +19 -0
- package/src/math/fees.ts +191 -0
- package/src/math/index.ts +40 -0
- package/src/math/tiered-fees.ts +165 -0
- package/src/oracle.ts +97 -0
- package/src/pda/index.ts +331 -0
- package/src/pda/liquid-fees.ts +58 -0
- package/src/pda/liquid-state.ts +123 -0
- package/src/pda/liquid-swap.ts +162 -0
- package/src/pda/liquid.ts +152 -0
- package/src/provider.ts +60 -0
- package/src/transaction/builder.ts +80 -0
- package/src/transaction/index.ts +6 -0
- package/src/transaction/send.ts +72 -0
- package/src/types.ts +354 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Program } from "@coral-xyz/anchor";
|
|
2
|
+
import type { LiquidConfig } from "../config.js";
|
|
3
|
+
import { createStubProvider } from "../provider.js";
|
|
4
|
+
|
|
5
|
+
import liquidIdl from "../idl/liquid.json" with { type: "json" };
|
|
6
|
+
import liquidSwapIdl from "../idl/liquid_swap.json" with { type: "json" };
|
|
7
|
+
import liquidFeesIdl from "../idl/liquid_fees.json" with { type: "json" };
|
|
8
|
+
import liquidStateIdl from "../idl/liquid_state.json" with { type: "json" };
|
|
9
|
+
|
|
10
|
+
import type { Liquid } from "../idl/liquid.js";
|
|
11
|
+
import type { LiquidSwap } from "../idl/liquid_swap.js";
|
|
12
|
+
import type { LiquidFees } from "../idl/liquid_fees.js";
|
|
13
|
+
import type { LiquidState } from "../idl/liquid_state.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cached stub Program instances for instruction building.
|
|
17
|
+
* Instruction building via `.instruction()` never hits the network,
|
|
18
|
+
* so these programs use a shared stub provider with no real Connection.
|
|
19
|
+
*/
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
const cache = new Map<string, Program<any>>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns a cached Program instance or creates one if it doesn't exist.
|
|
25
|
+
*
|
|
26
|
+
* @param key - Cache key prefix (e.g., "liquid", "swap")
|
|
27
|
+
* @param idl - Raw IDL JSON object
|
|
28
|
+
* @param address - Program address as a base58 string
|
|
29
|
+
* @returns Cached or newly created Program instance
|
|
30
|
+
*/
|
|
31
|
+
function getOrCreate<T extends Liquid | LiquidSwap | LiquidFees | LiquidState>(
|
|
32
|
+
key: string,
|
|
33
|
+
idl: Record<string, unknown>,
|
|
34
|
+
address: string
|
|
35
|
+
): Program<T> {
|
|
36
|
+
const cacheKey = `${key}:${address}`;
|
|
37
|
+
let prog = cache.get(cacheKey) as Program<T> | undefined;
|
|
38
|
+
if (!prog) {
|
|
39
|
+
const patchedIdl = { ...idl, address };
|
|
40
|
+
prog = new Program(patchedIdl as T, createStubProvider());
|
|
41
|
+
cache.set(cacheKey, prog);
|
|
42
|
+
}
|
|
43
|
+
return prog;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns a cached Liquid bonding curve program instance for instruction building.
|
|
48
|
+
*
|
|
49
|
+
* @param config - Liquid protocol configuration
|
|
50
|
+
* @returns Cached Program instance typed as Liquid
|
|
51
|
+
*/
|
|
52
|
+
export function getCachedLiquidProgram(config: LiquidConfig): Program<Liquid> {
|
|
53
|
+
return getOrCreate<Liquid>(
|
|
54
|
+
"liquid",
|
|
55
|
+
liquidIdl,
|
|
56
|
+
config.liquidProgramId.toBase58()
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Returns a cached Liquid Swap AMM program instance for instruction building.
|
|
62
|
+
*
|
|
63
|
+
* @param config - Liquid protocol configuration
|
|
64
|
+
* @returns Cached Program instance typed as LiquidSwap
|
|
65
|
+
*/
|
|
66
|
+
export function getCachedSwapProgram(
|
|
67
|
+
config: LiquidConfig
|
|
68
|
+
): Program<LiquidSwap> {
|
|
69
|
+
return getOrCreate<LiquidSwap>(
|
|
70
|
+
"swap",
|
|
71
|
+
liquidSwapIdl,
|
|
72
|
+
config.liquidSwapProgramId.toBase58()
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns a cached Liquid Fees program instance for instruction building.
|
|
78
|
+
*
|
|
79
|
+
* @param config - Liquid protocol configuration
|
|
80
|
+
* @returns Cached Program instance typed as LiquidFees
|
|
81
|
+
*/
|
|
82
|
+
export function getCachedFeesProgram(
|
|
83
|
+
config: LiquidConfig
|
|
84
|
+
): Program<LiquidFees> {
|
|
85
|
+
return getOrCreate<LiquidFees>(
|
|
86
|
+
"fees",
|
|
87
|
+
liquidFeesIdl,
|
|
88
|
+
config.liquidFeesProgramId.toBase58()
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns a cached Liquid State program instance for instruction building.
|
|
94
|
+
*
|
|
95
|
+
* @param config - Liquid protocol configuration
|
|
96
|
+
* @returns Cached Program instance typed as LiquidState
|
|
97
|
+
*/
|
|
98
|
+
export function getCachedStateProgram(
|
|
99
|
+
config: LiquidConfig
|
|
100
|
+
): Program<LiquidState> {
|
|
101
|
+
return getOrCreate<LiquidState>(
|
|
102
|
+
"state",
|
|
103
|
+
liquidStateIdl,
|
|
104
|
+
config.liquidStateProgramId.toBase58()
|
|
105
|
+
);
|
|
106
|
+
}
|
package/src/math/amm.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import BN from "bn.js";
|
|
2
|
+
import { FEE_RATE_DENOMINATOR } from "./constants.js";
|
|
3
|
+
import type { AmmSellOutput, AmmBuyInput, LpTokenAmounts } from "../types.js";
|
|
4
|
+
|
|
5
|
+
/** AMM fee rates in basis points */
|
|
6
|
+
export interface AmmFeeRates {
|
|
7
|
+
lpFeeRate: number;
|
|
8
|
+
creatorFeeRate: number;
|
|
9
|
+
protocolFeeRate: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Calculates expected output for selling base tokens on the AMM.
|
|
14
|
+
* Uses constant product formula: quoteOut = (amountIn * quoteVault) / (baseVault + amountIn)
|
|
15
|
+
*
|
|
16
|
+
* @param amountIn - Base tokens to sell
|
|
17
|
+
* @param baseVault - Current base vault balance
|
|
18
|
+
* @param quoteVault - Current quote vault balance
|
|
19
|
+
* @param fees - Fee rates in basis points
|
|
20
|
+
* @returns Sell output with gross/net quote amounts and fee breakdown
|
|
21
|
+
*/
|
|
22
|
+
export const calculateAmmSellOutput = (
|
|
23
|
+
amountIn: BN,
|
|
24
|
+
baseVault: BN,
|
|
25
|
+
quoteVault: BN,
|
|
26
|
+
fees: AmmFeeRates
|
|
27
|
+
): AmmSellOutput => {
|
|
28
|
+
// Gross output: (amountIn * quoteVault) / (baseVault + amountIn)
|
|
29
|
+
const grossQuoteOut = amountIn.mul(quoteVault).div(baseVault.add(amountIn));
|
|
30
|
+
|
|
31
|
+
const denom = new BN(FEE_RATE_DENOMINATOR);
|
|
32
|
+
const lpFee = grossQuoteOut.mul(new BN(fees.lpFeeRate)).div(denom);
|
|
33
|
+
const creatorFee = grossQuoteOut
|
|
34
|
+
.mul(new BN(fees.creatorFeeRate))
|
|
35
|
+
.div(denom);
|
|
36
|
+
const protocolFee = grossQuoteOut
|
|
37
|
+
.mul(new BN(fees.protocolFeeRate))
|
|
38
|
+
.div(denom);
|
|
39
|
+
const totalFees = lpFee.add(creatorFee).add(protocolFee);
|
|
40
|
+
|
|
41
|
+
const quoteOutNet = grossQuoteOut.sub(totalFees);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
quoteOut: grossQuoteOut,
|
|
45
|
+
quoteOutNet,
|
|
46
|
+
lpFee,
|
|
47
|
+
creatorFee,
|
|
48
|
+
protocolFee,
|
|
49
|
+
totalFees,
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Calculates required quote input for buying base tokens on the AMM.
|
|
55
|
+
* Reverse calculation: how much quote is needed for the desired base output.
|
|
56
|
+
*
|
|
57
|
+
* @param amountOut - Base tokens to buy
|
|
58
|
+
* @param baseVault - Current base vault balance
|
|
59
|
+
* @param quoteVault - Current quote vault balance
|
|
60
|
+
* @param fees - Fee rates in basis points
|
|
61
|
+
* @returns Buy input with gross/net quote amounts and fee breakdown
|
|
62
|
+
*/
|
|
63
|
+
export const calculateAmmBuyInput = (
|
|
64
|
+
amountOut: BN,
|
|
65
|
+
baseVault: BN,
|
|
66
|
+
quoteVault: BN,
|
|
67
|
+
fees: AmmFeeRates
|
|
68
|
+
): AmmBuyInput => {
|
|
69
|
+
const denom = new BN(FEE_RATE_DENOMINATOR);
|
|
70
|
+
|
|
71
|
+
// Quote needed for swap (without fees), ceiling division
|
|
72
|
+
const quoteForSwap = quoteVault
|
|
73
|
+
.mul(amountOut)
|
|
74
|
+
.div(baseVault.sub(amountOut))
|
|
75
|
+
.add(new BN(1));
|
|
76
|
+
|
|
77
|
+
// Total fee rate
|
|
78
|
+
const totalFeeRate =
|
|
79
|
+
fees.lpFeeRate + fees.creatorFeeRate + fees.protocolFeeRate;
|
|
80
|
+
|
|
81
|
+
// Gross quote needed (including fees), ceiling division
|
|
82
|
+
const quoteInGross = quoteForSwap
|
|
83
|
+
.mul(denom)
|
|
84
|
+
.div(new BN(FEE_RATE_DENOMINATOR - totalFeeRate))
|
|
85
|
+
.add(new BN(1));
|
|
86
|
+
|
|
87
|
+
// Calculate individual fees
|
|
88
|
+
const lpFee = quoteInGross.mul(new BN(fees.lpFeeRate)).div(denom);
|
|
89
|
+
const creatorFee = quoteInGross.mul(new BN(fees.creatorFeeRate)).div(denom);
|
|
90
|
+
const protocolFee = quoteInGross
|
|
91
|
+
.mul(new BN(fees.protocolFeeRate))
|
|
92
|
+
.div(denom);
|
|
93
|
+
const totalFees = lpFee.add(creatorFee).add(protocolFee);
|
|
94
|
+
|
|
95
|
+
const quoteIn = quoteInGross.sub(totalFees);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
quoteIn,
|
|
99
|
+
quoteInGross,
|
|
100
|
+
lpFee,
|
|
101
|
+
creatorFee,
|
|
102
|
+
protocolFee,
|
|
103
|
+
totalFees,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Converts LP tokens to the equivalent base and quote token amounts.
|
|
109
|
+
*
|
|
110
|
+
* @param lpAmount - LP tokens to convert
|
|
111
|
+
* @param lpSupply - Total LP supply
|
|
112
|
+
* @param baseVault - Base vault balance
|
|
113
|
+
* @param quoteVault - Quote vault balance
|
|
114
|
+
* @param roundUp - Use ceiling division (for deposits) vs floor division (for withdrawals)
|
|
115
|
+
* @returns Equivalent base and quote token amounts for the given LP tokens
|
|
116
|
+
*/
|
|
117
|
+
export const calculateLpToTokens = (
|
|
118
|
+
lpAmount: BN,
|
|
119
|
+
lpSupply: BN,
|
|
120
|
+
baseVault: BN,
|
|
121
|
+
quoteVault: BN,
|
|
122
|
+
roundUp: boolean = false
|
|
123
|
+
): LpTokenAmounts => {
|
|
124
|
+
let baseAmount: BN;
|
|
125
|
+
let quoteAmount: BN;
|
|
126
|
+
|
|
127
|
+
if (roundUp) {
|
|
128
|
+
// Ceiling division for deposits (require slightly more from user)
|
|
129
|
+
baseAmount = lpAmount
|
|
130
|
+
.mul(baseVault)
|
|
131
|
+
.add(lpSupply)
|
|
132
|
+
.sub(new BN(1))
|
|
133
|
+
.div(lpSupply);
|
|
134
|
+
quoteAmount = lpAmount
|
|
135
|
+
.mul(quoteVault)
|
|
136
|
+
.add(lpSupply)
|
|
137
|
+
.sub(new BN(1))
|
|
138
|
+
.div(lpSupply);
|
|
139
|
+
} else {
|
|
140
|
+
// Floor division for withdrawals (give slightly less to user)
|
|
141
|
+
baseAmount = lpAmount.mul(baseVault).div(lpSupply);
|
|
142
|
+
quoteAmount = lpAmount.mul(quoteVault).div(lpSupply);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { baseAmount, quoteAmount };
|
|
146
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import BN from "bn.js";
|
|
2
|
+
import type { BondingCurveBuyResult, BondingCurveSellResult } from "../types.js";
|
|
3
|
+
import { calculateFees, type FeeConfig } from "./fees.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Calculates expected output for a buy transaction on the bonding curve.
|
|
7
|
+
* Uses constant product formula: k = virtualQuote * virtualToken
|
|
8
|
+
*
|
|
9
|
+
* @param amountInGross - Total quote amount the user is spending (before fees)
|
|
10
|
+
* @param curveState - Current curve reserves
|
|
11
|
+
* @param config - Fee configuration
|
|
12
|
+
* @param hasCreatorRef - Whether the creator has a referrer
|
|
13
|
+
* @param hasTraderRef - Whether the trader has a referrer
|
|
14
|
+
* @returns Buy result with tokens out, new reserves, net amount, and fee breakdown
|
|
15
|
+
*/
|
|
16
|
+
export const calculateBuyExpectation = (
|
|
17
|
+
amountInGross: BN,
|
|
18
|
+
curveState: { virtualQuoteReserves: BN; virtualTokenReserves: BN },
|
|
19
|
+
config: FeeConfig,
|
|
20
|
+
hasCreatorRef: boolean,
|
|
21
|
+
hasTraderRef: boolean
|
|
22
|
+
): BondingCurveBuyResult => {
|
|
23
|
+
const fees = calculateFees(
|
|
24
|
+
amountInGross,
|
|
25
|
+
config,
|
|
26
|
+
hasCreatorRef,
|
|
27
|
+
hasTraderRef
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Net quote amount that goes into the curve after fees
|
|
31
|
+
const amountInNet = amountInGross.sub(fees.totalFees);
|
|
32
|
+
|
|
33
|
+
// Constant product: k = x * y
|
|
34
|
+
const k = curveState.virtualQuoteReserves.mul(
|
|
35
|
+
curveState.virtualTokenReserves
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// New quote reserves after adding net amount
|
|
39
|
+
const newVirtualQuote = curveState.virtualQuoteReserves.add(amountInNet);
|
|
40
|
+
|
|
41
|
+
// Solve for new token reserves: newToken = k / newQuote + 1 (ceiling division)
|
|
42
|
+
const newVirtualToken = k.div(newVirtualQuote).add(new BN(1));
|
|
43
|
+
|
|
44
|
+
// Tokens out = difference in token reserves
|
|
45
|
+
const tokensOut = curveState.virtualTokenReserves.sub(newVirtualToken);
|
|
46
|
+
|
|
47
|
+
// On-chain, protocol fee is only reduced by referral amounts that are
|
|
48
|
+
// actually transferred. These helpers assume referrals succeed, so
|
|
49
|
+
// subtract them to match the expected on-chain protocol fee.
|
|
50
|
+
const effectiveProtocolFee = fees.protocolFee
|
|
51
|
+
.sub(fees.creatorReferralFee)
|
|
52
|
+
.sub(fees.traderReferralFee);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...fees,
|
|
56
|
+
protocolFee: effectiveProtocolFee,
|
|
57
|
+
tokensOut,
|
|
58
|
+
newVirtualQuote,
|
|
59
|
+
newVirtualToken,
|
|
60
|
+
amountInNet,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Calculates expected output for a sell transaction on the bonding curve.
|
|
66
|
+
* Uses constant product formula: k = virtualQuote * virtualToken
|
|
67
|
+
*
|
|
68
|
+
* @param amountInTokens - Amount of tokens to sell
|
|
69
|
+
* @param curveState - Current curve reserves
|
|
70
|
+
* @param config - Fee configuration
|
|
71
|
+
* @param hasCreatorRef - Whether the creator has a referrer
|
|
72
|
+
* @param hasTraderRef - Whether the trader has a referrer
|
|
73
|
+
* @returns Sell result with quote out (gross/net), new reserves, and fee breakdown
|
|
74
|
+
*/
|
|
75
|
+
export const calculateSellExpectation = (
|
|
76
|
+
amountInTokens: BN,
|
|
77
|
+
curveState: { virtualQuoteReserves: BN; virtualTokenReserves: BN },
|
|
78
|
+
config: FeeConfig,
|
|
79
|
+
hasCreatorRef: boolean,
|
|
80
|
+
hasTraderRef: boolean
|
|
81
|
+
): BondingCurveSellResult => {
|
|
82
|
+
// Constant product: k = x * y
|
|
83
|
+
const k = curveState.virtualQuoteReserves.mul(
|
|
84
|
+
curveState.virtualTokenReserves
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// New token reserves after adding sold tokens
|
|
88
|
+
const newVirtualToken = curveState.virtualTokenReserves.add(amountInTokens);
|
|
89
|
+
|
|
90
|
+
// Solve for new quote reserves: newQuote = k / newToken + 1 (ceiling division)
|
|
91
|
+
const newVirtualQuote = k.div(newVirtualToken).add(new BN(1));
|
|
92
|
+
|
|
93
|
+
// Gross quote out (before fees)
|
|
94
|
+
const quoteOutGross = curveState.virtualQuoteReserves.sub(newVirtualQuote);
|
|
95
|
+
|
|
96
|
+
// Calculate fees on gross amount
|
|
97
|
+
const fees = calculateFees(
|
|
98
|
+
quoteOutGross,
|
|
99
|
+
config,
|
|
100
|
+
hasCreatorRef,
|
|
101
|
+
hasTraderRef
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Net quote amount user receives
|
|
105
|
+
const quoteOutNet = quoteOutGross.sub(fees.totalFees);
|
|
106
|
+
|
|
107
|
+
// On-chain, protocol fee is only reduced by referral amounts that are
|
|
108
|
+
// actually transferred. These helpers assume referrals succeed, so
|
|
109
|
+
// subtract them to match the expected on-chain protocol fee.
|
|
110
|
+
const effectiveProtocolFee = fees.protocolFee
|
|
111
|
+
.sub(fees.creatorReferralFee)
|
|
112
|
+
.sub(fees.traderReferralFee);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
...fees,
|
|
116
|
+
protocolFee: effectiveProtocolFee,
|
|
117
|
+
quoteOutGross,
|
|
118
|
+
quoteOutNet,
|
|
119
|
+
newVirtualQuote,
|
|
120
|
+
newVirtualToken,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import BN from "bn.js";
|
|
2
|
+
|
|
3
|
+
/** Basis points denominator (10,000 = 100%) */
|
|
4
|
+
export const BPS_DENOMINATOR = 10_000;
|
|
5
|
+
|
|
6
|
+
/** Fee rate denominator for AMM (same as BPS) */
|
|
7
|
+
export const FEE_RATE_DENOMINATOR = 10_000;
|
|
8
|
+
|
|
9
|
+
/** Q32 constant for fixed-point oracle math (2^32) */
|
|
10
|
+
export const Q32 = new BN(1).shln(32);
|
|
11
|
+
|
|
12
|
+
/** Number of oracle observations stored */
|
|
13
|
+
export const OBSERVATION_NUM = 100;
|
|
14
|
+
|
|
15
|
+
/** Default oracle update duration in seconds */
|
|
16
|
+
export const OBSERVATION_UPDATE_DURATION_DEFAULT = 15;
|
|
17
|
+
|
|
18
|
+
/** Minimum rent-exempt balance for a 0-byte account */
|
|
19
|
+
export const MIN_VAULT_BALANCE = 890_880;
|
package/src/math/fees.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import BN from "bn.js";
|
|
2
|
+
import type { PublicKey } from "@solana/web3.js";
|
|
3
|
+
import { BPS_DENOMINATOR, MIN_VAULT_BALANCE } from "./constants.js";
|
|
4
|
+
import type {
|
|
5
|
+
FeeDistribution,
|
|
6
|
+
FeeRecipient,
|
|
7
|
+
DistributionResult,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calculates basis points of an amount.
|
|
12
|
+
* result = (amount * bps) / 10000
|
|
13
|
+
*
|
|
14
|
+
* @param amount - The amount to calculate basis points of
|
|
15
|
+
* @param bps - Basis points (1 bps = 0.01%)
|
|
16
|
+
* @returns The calculated portion
|
|
17
|
+
*/
|
|
18
|
+
export const calcBps = (amount: BN, bps: number): BN =>
|
|
19
|
+
amount.mul(new BN(bps)).div(new BN(BPS_DENOMINATOR));
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fee configuration for bonding curve trades.
|
|
23
|
+
*/
|
|
24
|
+
export interface FeeConfig {
|
|
25
|
+
protocolFeeBps: number;
|
|
26
|
+
creatorFeeBps: number;
|
|
27
|
+
creatorReferralBps: number;
|
|
28
|
+
traderReferralBps: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Calculates fee distribution for a given trade amount.
|
|
33
|
+
* Referral fees are carved from the protocol fee, not the trade amount.
|
|
34
|
+
*
|
|
35
|
+
* @param amount - Gross trade amount (before fees)
|
|
36
|
+
* @param config - Fee configuration in basis points
|
|
37
|
+
* @param hasCreatorRef - Whether the creator has a referrer
|
|
38
|
+
* @param hasTraderRef - Whether the trader has a referrer
|
|
39
|
+
* @returns Fee distribution breakdown
|
|
40
|
+
*/
|
|
41
|
+
export const calculateFees = (
|
|
42
|
+
amount: BN,
|
|
43
|
+
config: FeeConfig,
|
|
44
|
+
hasCreatorRef: boolean,
|
|
45
|
+
hasTraderRef: boolean
|
|
46
|
+
): FeeDistribution => {
|
|
47
|
+
const baseProtocolFee = calcBps(amount, config.protocolFeeBps);
|
|
48
|
+
const creatorFee = calcBps(amount, config.creatorFeeBps);
|
|
49
|
+
|
|
50
|
+
// Referral amounts are BPS of the protocol fee (not trade amount)
|
|
51
|
+
const creatorReferralFee = hasCreatorRef
|
|
52
|
+
? calcBps(baseProtocolFee, config.creatorReferralBps)
|
|
53
|
+
: new BN(0);
|
|
54
|
+
const traderReferralFee = hasTraderRef
|
|
55
|
+
? calcBps(baseProtocolFee, config.traderReferralBps)
|
|
56
|
+
: new BN(0);
|
|
57
|
+
|
|
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
|
+
return {
|
|
65
|
+
protocolFee,
|
|
66
|
+
creatorFee,
|
|
67
|
+
creatorReferralFee,
|
|
68
|
+
traderReferralFee,
|
|
69
|
+
totalFees,
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Calculates expected distribution amounts for each fee recipient.
|
|
75
|
+
* Distributable = vaultBalance - MIN_VAULT_BALANCE (rent).
|
|
76
|
+
*
|
|
77
|
+
* @param vaultBalance - Current vault balance in lamports
|
|
78
|
+
* @param recipients - Fee recipients with basis point allocations
|
|
79
|
+
* @returns Distribution amounts per recipient
|
|
80
|
+
*/
|
|
81
|
+
export const calculateDistribution = (
|
|
82
|
+
vaultBalance: number,
|
|
83
|
+
recipients: FeeRecipient[]
|
|
84
|
+
): DistributionResult[] => {
|
|
85
|
+
const distributable = Math.max(0, vaultBalance - MIN_VAULT_BALANCE);
|
|
86
|
+
return recipients.map((r) => ({
|
|
87
|
+
pubkey: r.pubkey,
|
|
88
|
+
amount: Math.floor((distributable * r.basisPoints) / BPS_DENOMINATOR),
|
|
89
|
+
}));
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Calculates total distribution (sum of all recipient amounts).
|
|
94
|
+
*
|
|
95
|
+
* @param vaultBalance - Current vault balance in lamports
|
|
96
|
+
* @param recipients - Fee recipients with basis point allocations
|
|
97
|
+
* @returns Total amount distributed across all recipients
|
|
98
|
+
*/
|
|
99
|
+
export const calculateTotalDistribution = (
|
|
100
|
+
vaultBalance: number,
|
|
101
|
+
recipients: FeeRecipient[]
|
|
102
|
+
): number => {
|
|
103
|
+
return calculateDistribution(vaultBalance, recipients).reduce(
|
|
104
|
+
(sum, r) => sum + r.amount,
|
|
105
|
+
0
|
|
106
|
+
);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculates dust remaining after distribution (due to integer rounding).
|
|
111
|
+
*
|
|
112
|
+
* @param vaultBalance - Current vault balance in lamports
|
|
113
|
+
* @param recipients - Fee recipients with basis point allocations
|
|
114
|
+
* @returns Undistributed lamports remaining in the vault
|
|
115
|
+
*/
|
|
116
|
+
export const calculateDistributionDust = (
|
|
117
|
+
vaultBalance: number,
|
|
118
|
+
recipients: FeeRecipient[]
|
|
119
|
+
): number => {
|
|
120
|
+
const distributable = Math.max(0, vaultBalance - MIN_VAULT_BALANCE);
|
|
121
|
+
const totalDistributed = calculateTotalDistribution(
|
|
122
|
+
vaultBalance,
|
|
123
|
+
recipients
|
|
124
|
+
);
|
|
125
|
+
return distributable - totalDistributed;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a recipients array from pubkeys and basis points.
|
|
130
|
+
*
|
|
131
|
+
* @param pubkeys - Recipient public keys
|
|
132
|
+
* @param bpsArray - Basis point allocations (must match pubkeys length)
|
|
133
|
+
* @returns Array of fee recipients
|
|
134
|
+
*/
|
|
135
|
+
export const createRecipients = (
|
|
136
|
+
pubkeys: PublicKey[],
|
|
137
|
+
bpsArray: number[]
|
|
138
|
+
): FeeRecipient[] => {
|
|
139
|
+
if (pubkeys.length !== bpsArray.length) {
|
|
140
|
+
throw new Error("pubkeys and bpsArray must have the same length");
|
|
141
|
+
}
|
|
142
|
+
return pubkeys.map((pubkey, i) => ({
|
|
143
|
+
pubkey,
|
|
144
|
+
basisPoints: bpsArray[i],
|
|
145
|
+
}));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Creates equal-share recipients (auto-calculates BPS to sum to 10,000).
|
|
150
|
+
*
|
|
151
|
+
* @param count - Number of recipients
|
|
152
|
+
* @param pubkeys - Recipient public keys (must have at least `count` entries)
|
|
153
|
+
* @returns Array of fee recipients with equal allocations
|
|
154
|
+
*/
|
|
155
|
+
export const createEqualRecipients = (
|
|
156
|
+
count: number,
|
|
157
|
+
pubkeys: PublicKey[]
|
|
158
|
+
): FeeRecipient[] => {
|
|
159
|
+
if (pubkeys.length < count) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Need at least ${count} pubkeys, got ${pubkeys.length}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const bpsPerRecipient = Math.floor(BPS_DENOMINATOR / count);
|
|
165
|
+
const remainder = BPS_DENOMINATOR % count;
|
|
166
|
+
return pubkeys.slice(0, count).map((pubkey, i) => ({
|
|
167
|
+
pubkey,
|
|
168
|
+
basisPoints: i === 0 ? bpsPerRecipient + remainder : bpsPerRecipient,
|
|
169
|
+
}));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Creates a single recipient with 100% allocation.
|
|
174
|
+
*
|
|
175
|
+
* @param pubkey - Recipient public key
|
|
176
|
+
* @returns Array with a single fee recipient at 10,000 BPS
|
|
177
|
+
*/
|
|
178
|
+
export const createSingleRecipient = (pubkey: PublicKey): FeeRecipient[] => {
|
|
179
|
+
return [{ pubkey, basisPoints: BPS_DENOMINATOR }];
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Validates that recipients sum to exactly 10,000 BPS.
|
|
184
|
+
*
|
|
185
|
+
* @param recipients - Fee recipients to validate
|
|
186
|
+
* @returns True if basis points sum to exactly 10,000
|
|
187
|
+
*/
|
|
188
|
+
export const validateRecipientsSum = (recipients: FeeRecipient[]): boolean => {
|
|
189
|
+
const sum = recipients.reduce((acc, r) => acc + r.basisPoints, 0);
|
|
190
|
+
return sum === BPS_DENOMINATOR;
|
|
191
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
BPS_DENOMINATOR,
|
|
3
|
+
FEE_RATE_DENOMINATOR,
|
|
4
|
+
Q32,
|
|
5
|
+
OBSERVATION_NUM,
|
|
6
|
+
OBSERVATION_UPDATE_DURATION_DEFAULT,
|
|
7
|
+
MIN_VAULT_BALANCE,
|
|
8
|
+
} from "./constants.js";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
calcBps,
|
|
12
|
+
calculateFees,
|
|
13
|
+
calculateDistribution,
|
|
14
|
+
calculateTotalDistribution,
|
|
15
|
+
calculateDistributionDust,
|
|
16
|
+
createRecipients,
|
|
17
|
+
createEqualRecipients,
|
|
18
|
+
createSingleRecipient,
|
|
19
|
+
validateRecipientsSum,
|
|
20
|
+
} from "./fees.js";
|
|
21
|
+
export type { FeeConfig } from "./fees.js";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
calculateBuyExpectation,
|
|
25
|
+
calculateSellExpectation,
|
|
26
|
+
} from "./bonding-curve.js";
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
calculateAmmSellOutput,
|
|
30
|
+
calculateAmmBuyInput,
|
|
31
|
+
calculateLpToTokens,
|
|
32
|
+
} from "./amm.js";
|
|
33
|
+
export type { AmmFeeRates } from "./amm.js";
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
calculatePoolMarketCap,
|
|
37
|
+
lookupFeeTier,
|
|
38
|
+
calculateFeesForPool,
|
|
39
|
+
WSOL_MINT,
|
|
40
|
+
} from "./tiered-fees.js";
|