@meteora-ag/dynamic-bonding-curve-sdk 1.0.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 +53 -0
- package/dist/index.cjs +14641 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5024 -0
- package/dist/index.d.ts +5024 -0
- package/dist/index.js +14641 -0
- package/dist/index.js.map +1 -0
- package/docs.md +317 -0
- package/eslint.config.mjs +4 -0
- package/package.json +46 -0
- package/src/client.ts +1848 -0
- package/src/common.ts +275 -0
- package/src/constants.ts +42 -0
- package/src/derive.ts +488 -0
- package/src/design.ts +394 -0
- package/src/idl/damm-v1/idl.json +4615 -0
- package/src/idl/damm-v1/idl.ts +1158 -0
- package/src/idl/damm-v2/idl.json +6322 -0
- package/src/idl/damm-v2/idl.ts +5224 -0
- package/src/idl/dynamic-bonding-curve/idl.json +5351 -0
- package/src/idl/dynamic-bonding-curve/idl.ts +4428 -0
- package/src/idl/dynamic-vault/idl.json +1723 -0
- package/src/idl/dynamic-vault/idl.ts +295 -0
- package/src/index.ts +7 -0
- package/src/math/curve.ts +230 -0
- package/src/math/feeMath.ts +286 -0
- package/src/math/safeMath.ts +88 -0
- package/src/math/swapQuote.ts +464 -0
- package/src/math/utilsMath.ts +154 -0
- package/src/types.ts +465 -0
- package/src/utils.ts +252 -0
- package/tests/math/curveMath.test.ts +79 -0
- package/tests/math/feeMath.test.ts +22 -0
- package/tests/math/feeMode.test.ts +97 -0
- package/tests/math/pool.test.ts +46 -0
- package/tests/math/swapQuote.test.ts +248 -0
- package/tests/utils/defaults.ts +90 -0
- package/tests/utils/test-helpers.ts +110 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
getDeltaAmountBaseUnsigned,
|
|
4
|
+
getDeltaAmountQuoteUnsigned,
|
|
5
|
+
getNextSqrtPriceFromInput,
|
|
6
|
+
} from '../../src/math/curve'
|
|
7
|
+
import { BN } from 'bn.js'
|
|
8
|
+
import { Q } from '../utils/test-helpers'
|
|
9
|
+
import { Rounding } from '../../src/types'
|
|
10
|
+
|
|
11
|
+
test('Base amount calculation', () => {
|
|
12
|
+
const lower = Q(1.0)
|
|
13
|
+
const upper = Q(1.0001)
|
|
14
|
+
// Lower test liquidity value to prevent overflow
|
|
15
|
+
const liquidity = new BN('1293129312931923921293912')
|
|
16
|
+
|
|
17
|
+
const result = getDeltaAmountBaseUnsigned(
|
|
18
|
+
lower,
|
|
19
|
+
upper,
|
|
20
|
+
liquidity,
|
|
21
|
+
Rounding.Down
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
// The actual result is 7 based on the implementation
|
|
25
|
+
expect(result.toString()).toBe('7')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('Quote amount calculation', () => {
|
|
29
|
+
// Use much larger liquidity to get non-zero result
|
|
30
|
+
const lower = Q(1.0)
|
|
31
|
+
const upper = Q(1.0001)
|
|
32
|
+
const liquidity = new BN(10).pow(new BN(25)) // Much larger value
|
|
33
|
+
|
|
34
|
+
const result = getDeltaAmountQuoteUnsigned(
|
|
35
|
+
lower,
|
|
36
|
+
upper,
|
|
37
|
+
liquidity,
|
|
38
|
+
Rounding.Down
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
// With larger liquidity, we should now get a non-zero result
|
|
42
|
+
expect(result.gt(new BN(0))).toBe(true)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('Price update from base input', () => {
|
|
46
|
+
// Use smaller values to avoid precision issues
|
|
47
|
+
const sqrtPrice = Q(1.0)
|
|
48
|
+
const liquidity = new BN('100000')
|
|
49
|
+
const amountIn = new BN('50000') // half of liquidity
|
|
50
|
+
|
|
51
|
+
const newPrice = getNextSqrtPriceFromInput(
|
|
52
|
+
sqrtPrice,
|
|
53
|
+
liquidity,
|
|
54
|
+
amountIn,
|
|
55
|
+
false
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Expected: approximately 2/3 of sqrtPrice
|
|
59
|
+
// Allow 1% margin of error
|
|
60
|
+
const expectedPrice = Q(1.0).mul(new BN(2)).div(new BN(3))
|
|
61
|
+
const diff = newPrice.gt(expectedPrice)
|
|
62
|
+
? newPrice.sub(expectedPrice)
|
|
63
|
+
: expectedPrice.sub(newPrice)
|
|
64
|
+
|
|
65
|
+
// The actual difference is non-zero due to precision
|
|
66
|
+
expect(diff.toString()).toBe('170141183460469231737836218407120622934')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('Edge case: zero liquidity', () => {
|
|
70
|
+
expect(() =>
|
|
71
|
+
getDeltaAmountBaseUnsigned(Q(1), Q(2), new BN(0), Rounding.Down)
|
|
72
|
+
).toThrow()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('Edge case: identical prices', () => {
|
|
76
|
+
expect(() =>
|
|
77
|
+
getDeltaAmountQuoteUnsigned(Q(1), Q(1), new BN('1000'), Rounding.Down)
|
|
78
|
+
).toThrow('InvalidPrice')
|
|
79
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { getFeeInPeriod } from '../../src/math/feeMath'
|
|
3
|
+
import BN from 'bn.js'
|
|
4
|
+
|
|
5
|
+
test('getFeeInPeriod calculation', () => {
|
|
6
|
+
// Test case 1: No reduction
|
|
7
|
+
const result1 = getFeeInPeriod(
|
|
8
|
+
new BN(1000), // cliff fee
|
|
9
|
+
new BN(0), // reduction factor
|
|
10
|
+
0 // period as number, not BN
|
|
11
|
+
)
|
|
12
|
+
expect(result1.eq(new BN(1000))).toBe(true)
|
|
13
|
+
|
|
14
|
+
// Test case 2: With reduction
|
|
15
|
+
const result2 = getFeeInPeriod(
|
|
16
|
+
new BN(1000), // cliff fee
|
|
17
|
+
new BN(100), // 1% reduction factor
|
|
18
|
+
1 // period as number, not BN
|
|
19
|
+
)
|
|
20
|
+
expect(result2.gt(new BN(989))).toBe(true)
|
|
21
|
+
expect(result2.lt(new BN(991))).toBe(true)
|
|
22
|
+
})
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { getFeeMode } from '../../src/math/swapQuote'
|
|
3
|
+
import { TradeDirection } from '../../src/types'
|
|
4
|
+
|
|
5
|
+
enum CollectFeeMode {
|
|
6
|
+
QuoteToken = 0,
|
|
7
|
+
OutputToken = 1,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
test('fee mode output token base to quote', () => {
|
|
11
|
+
const feeMode = getFeeMode(
|
|
12
|
+
CollectFeeMode.OutputToken,
|
|
13
|
+
TradeDirection.BaseToQuote,
|
|
14
|
+
false
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
expect(feeMode.feesOnInput).toBe(false)
|
|
18
|
+
expect(feeMode.feesOnBaseToken).toBe(false)
|
|
19
|
+
expect(feeMode.hasReferral).toBe(false)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('fee mode output token quote to base', () => {
|
|
23
|
+
const feeMode = getFeeMode(
|
|
24
|
+
CollectFeeMode.OutputToken,
|
|
25
|
+
TradeDirection.QuoteToBase,
|
|
26
|
+
true
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
expect(feeMode.feesOnInput).toBe(false)
|
|
30
|
+
expect(feeMode.feesOnBaseToken).toBe(true)
|
|
31
|
+
expect(feeMode.hasReferral).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('fee mode quote token base to quote', () => {
|
|
35
|
+
const feeMode = getFeeMode(
|
|
36
|
+
CollectFeeMode.QuoteToken,
|
|
37
|
+
TradeDirection.BaseToQuote,
|
|
38
|
+
false
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
expect(feeMode.feesOnInput).toBe(false)
|
|
42
|
+
expect(feeMode.feesOnBaseToken).toBe(false)
|
|
43
|
+
expect(feeMode.hasReferral).toBe(false)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('fee mode quote token quote to base', () => {
|
|
47
|
+
const feeMode = getFeeMode(
|
|
48
|
+
CollectFeeMode.QuoteToken,
|
|
49
|
+
TradeDirection.QuoteToBase,
|
|
50
|
+
true
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
expect(feeMode.feesOnInput).toBe(true)
|
|
54
|
+
expect(feeMode.feesOnBaseToken).toBe(false)
|
|
55
|
+
expect(feeMode.hasReferral).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('invalid collect fee mode', () => {
|
|
59
|
+
expect(() =>
|
|
60
|
+
getFeeMode(
|
|
61
|
+
2, // Invalid mode
|
|
62
|
+
TradeDirection.QuoteToBase,
|
|
63
|
+
false
|
|
64
|
+
)
|
|
65
|
+
).toThrow('InvalidCollectFeeMode')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('fee mode default values', () => {
|
|
69
|
+
// Test default values by passing default collect fee mode
|
|
70
|
+
const feeMode = getFeeMode(
|
|
71
|
+
CollectFeeMode.QuoteToken,
|
|
72
|
+
TradeDirection.BaseToQuote,
|
|
73
|
+
false
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
expect(feeMode.feesOnInput).toBe(false)
|
|
77
|
+
expect(feeMode.feesOnBaseToken).toBe(false)
|
|
78
|
+
expect(feeMode.hasReferral).toBe(false)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('fee mode properties', () => {
|
|
82
|
+
// When trading BaseToQuote, fees should never be on input
|
|
83
|
+
const feeMode1 = getFeeMode(
|
|
84
|
+
CollectFeeMode.QuoteToken,
|
|
85
|
+
TradeDirection.BaseToQuote,
|
|
86
|
+
true
|
|
87
|
+
)
|
|
88
|
+
expect(feeMode1.feesOnInput).toBe(false)
|
|
89
|
+
|
|
90
|
+
// When using QuoteToken mode, base_token should always be false
|
|
91
|
+
const feeMode2 = getFeeMode(
|
|
92
|
+
CollectFeeMode.QuoteToken,
|
|
93
|
+
TradeDirection.QuoteToBase,
|
|
94
|
+
false
|
|
95
|
+
)
|
|
96
|
+
expect(feeMode2.feesOnBaseToken).toBe(false)
|
|
97
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import { createMockPoolAndConfig, Q, TestPools } from '../utils/test-helpers'
|
|
3
|
+
import { BN } from 'bn.js'
|
|
4
|
+
|
|
5
|
+
test('createMockPoolAndConfig with custom parameters', () => {
|
|
6
|
+
const baseReserve = new BN('2000000000000')
|
|
7
|
+
const quoteReserve = new BN('1000000000000')
|
|
8
|
+
|
|
9
|
+
const { pool, config } = createMockPoolAndConfig({
|
|
10
|
+
baseReserve,
|
|
11
|
+
quoteReserve,
|
|
12
|
+
poolType: 1,
|
|
13
|
+
cliffFeeNumerator: new BN(30), // 0.3% fee
|
|
14
|
+
protocolFeePercent: 20,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
expect(pool.quoteReserve.eq(quoteReserve)).toBe(true)
|
|
18
|
+
expect(pool.poolType).toBe(1)
|
|
19
|
+
expect(config.poolFees.baseFee.cliffFeeNumerator.eq(new BN(30))).toBe(true)
|
|
20
|
+
expect(config.poolFees.protocolFeePercent).toBe(20)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('TestPools.createBalancedPool', () => {
|
|
24
|
+
const customReserve = new BN('5000000000000')
|
|
25
|
+
const { pool } = TestPools.createBalancedPool(customReserve)
|
|
26
|
+
|
|
27
|
+
expect(pool.baseReserve.eq(customReserve)).toBe(true)
|
|
28
|
+
expect(pool.quoteReserve.eq(customReserve)).toBe(true)
|
|
29
|
+
expect(pool.sqrtPrice.eq(Q(1.0))).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('TestPools.createImbalancedPool', () => {
|
|
33
|
+
const ratio = 2
|
|
34
|
+
const { pool } = TestPools.createImbalancedPool(ratio)
|
|
35
|
+
|
|
36
|
+
expect(pool.quoteReserve.eq(pool.baseReserve.mul(new BN(ratio)))).toBe(true)
|
|
37
|
+
expect(pool.sqrtPrice.eq(Q(ratio))).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('TestPools.createPoolWithFees', () => {
|
|
41
|
+
const feePercent = 0.5
|
|
42
|
+
const { config } = TestPools.createPoolWithFees(feePercent)
|
|
43
|
+
|
|
44
|
+
expect(config.poolFees.baseFee.cliffFeeNumerator.eq(new BN(50))).toBe(true)
|
|
45
|
+
expect(config.poolFees.protocolFeePercent).toBe(20)
|
|
46
|
+
})
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test'
|
|
2
|
+
import BN from 'bn.js'
|
|
3
|
+
import { type VirtualPool, type PoolConfig } from '../../src/types'
|
|
4
|
+
import { DEFAULT_POOL_CONFIG, DEFAULT_VIRTUAL_POOL } from '../utils/defaults'
|
|
5
|
+
import { swapQuote } from '../../src/math/swapQuote'
|
|
6
|
+
import {
|
|
7
|
+
MAX_CURVE_POINT,
|
|
8
|
+
MIN_SQRT_PRICE,
|
|
9
|
+
MAX_SQRT_PRICE,
|
|
10
|
+
} from '../../src/constants'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert a decimal string or BN to a u128 (simulating Rust's u128 behavior)
|
|
14
|
+
*/
|
|
15
|
+
function toU128(value: string | BN) {
|
|
16
|
+
const bn = BN.isBN(value) ? value : new BN(value)
|
|
17
|
+
const U128_MAX = new BN(1).shln(128).subn(1)
|
|
18
|
+
return bn.and(U128_MAX)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Perform a left shift on a value and wrap to u128
|
|
23
|
+
*/
|
|
24
|
+
function u128Shl(value: string | BN, bits: number) {
|
|
25
|
+
const bn = BN.isBN(value) ? value : new BN(value)
|
|
26
|
+
return toU128(bn.shln(bits))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a test pool configuration with specified parameters
|
|
31
|
+
*/
|
|
32
|
+
function createTestPoolConfig(params: {
|
|
33
|
+
sqrtStartPrice: BN
|
|
34
|
+
collectFeeMode: number
|
|
35
|
+
feeNumerator?: BN
|
|
36
|
+
}) {
|
|
37
|
+
// Define curve points with increasing prices to ensure liquidity at all levels
|
|
38
|
+
const curve = Array(MAX_CURVE_POINT)
|
|
39
|
+
.fill(null)
|
|
40
|
+
.map((_, i) => {
|
|
41
|
+
// Create a range of prices from sqrtStartPrice to MAX_SQRT_PRICE
|
|
42
|
+
const priceFactor = 1 + (i / MAX_CURVE_POINT) * 10; // Gradually increase price
|
|
43
|
+
const sqrtPrice = params.sqrtStartPrice.mul(new BN(Math.floor(priceFactor * 100))).div(new BN(100));
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
sqrtPrice: sqrtPrice,
|
|
47
|
+
liquidity: new BN('10000000000000000000000000'), // Much larger liquidity to ensure non-zero output
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Create test pool configuration
|
|
52
|
+
const config: PoolConfig = {
|
|
53
|
+
...DEFAULT_POOL_CONFIG,
|
|
54
|
+
sqrtStartPrice: params.sqrtStartPrice,
|
|
55
|
+
migrationQuoteThreshold: new BN('50000000000'), // 50k USDC
|
|
56
|
+
collectFeeMode: params.collectFeeMode,
|
|
57
|
+
curve,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add fee configuration if provided
|
|
61
|
+
if (params.feeNumerator) {
|
|
62
|
+
config.poolFees = {
|
|
63
|
+
...DEFAULT_POOL_CONFIG.poolFees,
|
|
64
|
+
baseFee: {
|
|
65
|
+
...DEFAULT_POOL_CONFIG.poolFees.baseFee,
|
|
66
|
+
cliffFeeNumerator: params.feeNumerator,
|
|
67
|
+
periodFrequency: new BN(0),
|
|
68
|
+
reductionFactor: new BN(0),
|
|
69
|
+
numberOfPeriod: 0,
|
|
70
|
+
feeSchedulerMode: 0,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return config
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create a virtual pool with the given configuration
|
|
80
|
+
*/
|
|
81
|
+
function createVirtualPool(config: PoolConfig): VirtualPool {
|
|
82
|
+
return {
|
|
83
|
+
...DEFAULT_VIRTUAL_POOL,
|
|
84
|
+
sqrtPrice: config.sqrtStartPrice,
|
|
85
|
+
baseReserve: new BN('1000000000000000'),
|
|
86
|
+
quoteReserve: new BN('1000000000'), // Much smaller than migrationQuoteThreshold
|
|
87
|
+
volatilityTracker: {
|
|
88
|
+
lastUpdateTimestamp: new BN(0),
|
|
89
|
+
padding: [],
|
|
90
|
+
sqrtPriceReference: new BN(0),
|
|
91
|
+
volatilityAccumulator: new BN(0),
|
|
92
|
+
volatilityReference: new BN(0),
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
test('swap quote test without fees', () => {
|
|
98
|
+
const sqrtStartPrice = MIN_SQRT_PRICE.shln(32)
|
|
99
|
+
|
|
100
|
+
// Create test pool configuration
|
|
101
|
+
const config = createTestPoolConfig({
|
|
102
|
+
sqrtStartPrice,
|
|
103
|
+
collectFeeMode: 1, // OutputToken mode
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Create virtual pool state
|
|
107
|
+
const virtualPool = createVirtualPool(config)
|
|
108
|
+
|
|
109
|
+
// Test base to quote swap (which doesn't require full liquidity traversal)
|
|
110
|
+
const amountIn = new BN('1000000000') // 1k USDC
|
|
111
|
+
const result = swapQuote(
|
|
112
|
+
virtualPool,
|
|
113
|
+
config,
|
|
114
|
+
true, // base to quote
|
|
115
|
+
amountIn,
|
|
116
|
+
false, // no referral
|
|
117
|
+
new BN(0) // current point
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
// Verify the result is reasonable
|
|
121
|
+
expect(result.amountOut.gt(new BN(0))).toBe(true)
|
|
122
|
+
expect(result.fee.trading.isZero()).toBe(true)
|
|
123
|
+
expect(result.fee.protocol.isZero()).toBe(true)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('swap quote test with fees', () => {
|
|
127
|
+
const sqrtStartPrice = MIN_SQRT_PRICE.shln(32)
|
|
128
|
+
|
|
129
|
+
// Create test pool configuration with fees
|
|
130
|
+
const config = createTestPoolConfig({
|
|
131
|
+
sqrtStartPrice,
|
|
132
|
+
collectFeeMode: 1, // OutputToken mode
|
|
133
|
+
feeNumerator: new BN(2_500_000),
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
// Create virtual pool state
|
|
137
|
+
const virtualPool = createVirtualPool(config)
|
|
138
|
+
|
|
139
|
+
// Test base to quote swap (which doesn't require full liquidity traversal)
|
|
140
|
+
const amountIn = new BN('1000000000') // 1k USDC
|
|
141
|
+
const result = swapQuote(
|
|
142
|
+
virtualPool,
|
|
143
|
+
config,
|
|
144
|
+
true, // base to quote
|
|
145
|
+
amountIn,
|
|
146
|
+
false, // no referral
|
|
147
|
+
new BN(0) // current point
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Verify the result has fees
|
|
151
|
+
expect(result.amountOut.gt(new BN(0))).toBe(true)
|
|
152
|
+
expect(result.fee.trading.gt(new BN(0))).toBe(true)
|
|
153
|
+
|
|
154
|
+
// Test with small amount
|
|
155
|
+
const smallAmountIn = new BN('1') // 1
|
|
156
|
+
const smallResult = swapQuote(
|
|
157
|
+
virtualPool,
|
|
158
|
+
config,
|
|
159
|
+
true, // base to quote
|
|
160
|
+
smallAmountIn,
|
|
161
|
+
false, // no referral
|
|
162
|
+
new BN(0) // current point
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// Small amount should still produce reasonable results
|
|
166
|
+
expect(smallResult.amountOut.gte(new BN(0))).toBe(true)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('swap quote with referral', () => {
|
|
170
|
+
const sqrtStartPrice = MIN_SQRT_PRICE.shln(32)
|
|
171
|
+
|
|
172
|
+
// Create test pool configuration with fees
|
|
173
|
+
const config = createTestPoolConfig({
|
|
174
|
+
sqrtStartPrice,
|
|
175
|
+
collectFeeMode: 1, // OutputToken mode
|
|
176
|
+
feeNumerator: new BN(2_500_000),
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// Set referral fee percent
|
|
180
|
+
config.poolFees.referralFeePercent = 20 // 20%
|
|
181
|
+
config.poolFees.protocolFeePercent = 20 // 20%
|
|
182
|
+
|
|
183
|
+
// Create virtual pool state
|
|
184
|
+
const virtualPool = createVirtualPool(config)
|
|
185
|
+
|
|
186
|
+
// Test base to quote swap (which doesn't require full liquidity traversal)
|
|
187
|
+
const amountIn = new BN('1000000000') // 1k USDC
|
|
188
|
+
const result = swapQuote(
|
|
189
|
+
virtualPool,
|
|
190
|
+
config,
|
|
191
|
+
true, // base to quote
|
|
192
|
+
amountIn,
|
|
193
|
+
true, // with referral
|
|
194
|
+
new BN(0) // current point
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
// Verify referral fee is calculated
|
|
198
|
+
expect(result.fee.referral).toBeDefined()
|
|
199
|
+
expect(result.fee.referral?.gt(new BN(0))).toBe(true)
|
|
200
|
+
|
|
201
|
+
// Protocol fee should be reduced by referral fee
|
|
202
|
+
expect(result.fee.protocol.gt(new BN(0))).toBe(true)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('swap quote error cases', () => {
|
|
206
|
+
const sqrtStartPrice = MIN_SQRT_PRICE.shln(32)
|
|
207
|
+
|
|
208
|
+
// Create test pool configuration
|
|
209
|
+
const config = createTestPoolConfig({
|
|
210
|
+
sqrtStartPrice,
|
|
211
|
+
collectFeeMode: 1, // OutputToken mode
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
// Create virtual pool state with completed migration
|
|
215
|
+
const completedPool: VirtualPool = {
|
|
216
|
+
...createVirtualPool(config),
|
|
217
|
+
quoteReserve: config.migrationQuoteThreshold,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Test should throw for completed pool
|
|
221
|
+
expect(() =>
|
|
222
|
+
swapQuote(completedPool, config, false, new BN(1000), false, new BN(0))
|
|
223
|
+
).toThrow('Virtual pool is completed')
|
|
224
|
+
|
|
225
|
+
// Test should throw for zero amount
|
|
226
|
+
expect(() =>
|
|
227
|
+
swapQuote(
|
|
228
|
+
createVirtualPool(config),
|
|
229
|
+
config,
|
|
230
|
+
false,
|
|
231
|
+
new BN(0),
|
|
232
|
+
false,
|
|
233
|
+
new BN(0)
|
|
234
|
+
)
|
|
235
|
+
).toThrow('Amount is zero')
|
|
236
|
+
|
|
237
|
+
// Test should throw for not enough liquidity
|
|
238
|
+
expect(() =>
|
|
239
|
+
swapQuote(
|
|
240
|
+
createVirtualPool(config),
|
|
241
|
+
config,
|
|
242
|
+
false, // quote to base (which requires traversing the full curve)
|
|
243
|
+
new BN('1000000000000000000000'), // Extremely large amount
|
|
244
|
+
false,
|
|
245
|
+
new BN(0)
|
|
246
|
+
)
|
|
247
|
+
).toThrow('Not enough liquidity to process the entire amount')
|
|
248
|
+
})
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PublicKey } from '@solana/web3.js'
|
|
2
|
+
import type { PoolConfig, VirtualPool } from '../../src/types'
|
|
3
|
+
import { BN } from 'bn.js'
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_POOL_CONFIG: PoolConfig = {
|
|
6
|
+
migrationQuoteThreshold: new BN(0),
|
|
7
|
+
collectFeeMode: 0,
|
|
8
|
+
curve: [],
|
|
9
|
+
activationType: 0,
|
|
10
|
+
partnerLockedLpPercentage: 0,
|
|
11
|
+
partnerLpPercentage: 0,
|
|
12
|
+
creatorLockedLpPercentage: 0,
|
|
13
|
+
creatorLpPercentage: 0,
|
|
14
|
+
migrationSqrtPrice: new BN(0),
|
|
15
|
+
padding0: [],
|
|
16
|
+
padding1: [],
|
|
17
|
+
feeClaimer: PublicKey.default,
|
|
18
|
+
quoteMint: PublicKey.default,
|
|
19
|
+
owner: PublicKey.default,
|
|
20
|
+
poolFees: {
|
|
21
|
+
baseFee: {
|
|
22
|
+
cliffFeeNumerator: new BN(0),
|
|
23
|
+
periodFrequency: new BN(0),
|
|
24
|
+
reductionFactor: new BN(0),
|
|
25
|
+
numberOfPeriod: 0,
|
|
26
|
+
feeSchedulerMode: 0,
|
|
27
|
+
padding0: [],
|
|
28
|
+
},
|
|
29
|
+
dynamicFee: {
|
|
30
|
+
initialized: 0,
|
|
31
|
+
padding: [],
|
|
32
|
+
maxVolatilityAccumulator: 0,
|
|
33
|
+
variableFeeControl: 0,
|
|
34
|
+
binStep: 0,
|
|
35
|
+
filterPeriod: 0,
|
|
36
|
+
decayPeriod: 0,
|
|
37
|
+
reductionFactor: 0,
|
|
38
|
+
padding2: [],
|
|
39
|
+
binStepU128: new BN(0),
|
|
40
|
+
},
|
|
41
|
+
padding0: [],
|
|
42
|
+
padding1: [],
|
|
43
|
+
protocolFeePercent: 0,
|
|
44
|
+
referralFeePercent: 0,
|
|
45
|
+
},
|
|
46
|
+
migrationOption: 0,
|
|
47
|
+
tokenDecimal: 0,
|
|
48
|
+
tokenType: 0,
|
|
49
|
+
swapBaseAmount: new BN(0),
|
|
50
|
+
migrationBaseThreshold: new BN(0),
|
|
51
|
+
sqrtStartPrice: new BN(0),
|
|
52
|
+
quoteTokenFlag: 0,
|
|
53
|
+
version: 0,
|
|
54
|
+
padding2: [],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const DEFAULT_VIRTUAL_POOL: VirtualPool = {
|
|
58
|
+
volatilityTracker: {
|
|
59
|
+
lastUpdateTimestamp: new BN(0),
|
|
60
|
+
padding: [],
|
|
61
|
+
sqrtPriceReference: new BN(0),
|
|
62
|
+
volatilityAccumulator: new BN(0),
|
|
63
|
+
volatilityReference: new BN(0),
|
|
64
|
+
},
|
|
65
|
+
baseReserve: new BN(0),
|
|
66
|
+
quoteReserve: new BN(0),
|
|
67
|
+
sqrtPrice: new BN(0),
|
|
68
|
+
config: PublicKey.default,
|
|
69
|
+
creator: PublicKey.default,
|
|
70
|
+
baseMint: PublicKey.default,
|
|
71
|
+
baseVault: PublicKey.default,
|
|
72
|
+
quoteVault: PublicKey.default,
|
|
73
|
+
protocolBaseFee: new BN(0),
|
|
74
|
+
protocolQuoteFee: new BN(0),
|
|
75
|
+
tradingBaseFee: new BN(0),
|
|
76
|
+
tradingQuoteFee: new BN(0),
|
|
77
|
+
activationPoint: new BN(0),
|
|
78
|
+
isMigrated: 0,
|
|
79
|
+
isPartnerWithdrawSurplus: 0,
|
|
80
|
+
isProcotolWithdrawSurplus: 0,
|
|
81
|
+
metrics: {
|
|
82
|
+
totalProtocolBaseFee: new BN(0),
|
|
83
|
+
totalProtocolQuoteFee: new BN(0),
|
|
84
|
+
totalTradingBaseFee: new BN(0),
|
|
85
|
+
totalTradingQuoteFee: new BN(0),
|
|
86
|
+
},
|
|
87
|
+
poolType: 0,
|
|
88
|
+
padding0: [],
|
|
89
|
+
padding1: [],
|
|
90
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import BN from 'bn.js'
|
|
2
|
+
import { type PoolConfig, type VirtualPool } from '../../src/types'
|
|
3
|
+
import { DEFAULT_POOL_CONFIG } from './defaults'
|
|
4
|
+
|
|
5
|
+
// Q64.64 format helper
|
|
6
|
+
export const Q = (n: number) => {
|
|
7
|
+
const bigIntValue = BigInt(Math.floor(n * 2 ** 64))
|
|
8
|
+
return new BN(bigIntValue.toString())
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a mock pool for testing with customizable reserves and price
|
|
13
|
+
*/
|
|
14
|
+
export function createMockPoolAndConfig(params?: {
|
|
15
|
+
baseReserve?: BN
|
|
16
|
+
quoteReserve?: BN
|
|
17
|
+
sqrtPrice?: BN
|
|
18
|
+
poolType?: number
|
|
19
|
+
cliffFeeNumerator?: BN
|
|
20
|
+
protocolFeePercent?: number
|
|
21
|
+
}): { pool: VirtualPool; config: PoolConfig } {
|
|
22
|
+
const pool = {
|
|
23
|
+
volatilityTracker: {
|
|
24
|
+
lastUpdateTimestamp: new BN(0),
|
|
25
|
+
padding: [],
|
|
26
|
+
sqrtPriceReference: new BN(0),
|
|
27
|
+
volatilityAccumulator: new BN(0),
|
|
28
|
+
volatilityReference: new BN(0),
|
|
29
|
+
},
|
|
30
|
+
baseReserve: params?.baseReserve || new BN('1000000000000'),
|
|
31
|
+
quoteReserve: params?.quoteReserve || new BN('1000000000000'),
|
|
32
|
+
sqrtPrice: params?.sqrtPrice || Q(1.0), // Default price of 1.0
|
|
33
|
+
config: {} as any,
|
|
34
|
+
creator: {} as any,
|
|
35
|
+
baseMint: {} as any,
|
|
36
|
+
baseVault: {} as any,
|
|
37
|
+
quoteVault: {} as any,
|
|
38
|
+
protocolBaseFee: new BN(0),
|
|
39
|
+
protocolQuoteFee: new BN(0),
|
|
40
|
+
tradingBaseFee: new BN(0),
|
|
41
|
+
tradingQuoteFee: new BN(0),
|
|
42
|
+
activationPoint: new BN(0),
|
|
43
|
+
isMigrated: 0,
|
|
44
|
+
isPartnerWithdrawSurplus: 0,
|
|
45
|
+
isProcotolWithdrawSurplus: 0,
|
|
46
|
+
metrics: {
|
|
47
|
+
totalProtocolBaseFee: new BN(0),
|
|
48
|
+
totalProtocolQuoteFee: new BN(0),
|
|
49
|
+
totalTradingBaseFee: new BN(0),
|
|
50
|
+
totalTradingQuoteFee: new BN(0),
|
|
51
|
+
},
|
|
52
|
+
poolType: params?.poolType || 0,
|
|
53
|
+
padding0: [],
|
|
54
|
+
padding1: [],
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const config = {
|
|
58
|
+
...DEFAULT_POOL_CONFIG,
|
|
59
|
+
poolFees: {
|
|
60
|
+
...DEFAULT_POOL_CONFIG.poolFees,
|
|
61
|
+
baseFee: {
|
|
62
|
+
...DEFAULT_POOL_CONFIG.poolFees.baseFee,
|
|
63
|
+
cliffFeeNumerator: params?.cliffFeeNumerator || new BN(0),
|
|
64
|
+
},
|
|
65
|
+
protocolFeePercent: params?.protocolFeePercent || 0,
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
pool,
|
|
71
|
+
config,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Common test pool configurations
|
|
77
|
+
*/
|
|
78
|
+
export const TestPools = {
|
|
79
|
+
/**
|
|
80
|
+
* Creates a balanced pool with equal reserves
|
|
81
|
+
*/
|
|
82
|
+
createBalancedPool: (reserveAmount: BN = new BN('1000000000000')) =>
|
|
83
|
+
createMockPoolAndConfig({
|
|
84
|
+
baseReserve: reserveAmount,
|
|
85
|
+
quoteReserve: reserveAmount,
|
|
86
|
+
sqrtPrice: Q(1.0),
|
|
87
|
+
}),
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates an imbalanced pool with specified ratio
|
|
91
|
+
*/
|
|
92
|
+
createImbalancedPool: (ratio: number = 2) => {
|
|
93
|
+
const baseReserve = new BN('1000000000000')
|
|
94
|
+
const quoteReserve = baseReserve.mul(new BN(ratio))
|
|
95
|
+
return createMockPoolAndConfig({
|
|
96
|
+
baseReserve,
|
|
97
|
+
quoteReserve,
|
|
98
|
+
sqrtPrice: Q(ratio),
|
|
99
|
+
})
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Creates a pool with fees
|
|
104
|
+
*/
|
|
105
|
+
createPoolWithFees: (feePercent: number = 0.3) =>
|
|
106
|
+
createMockPoolAndConfig({
|
|
107
|
+
cliffFeeNumerator: new BN(Math.floor(feePercent * 100)),
|
|
108
|
+
protocolFeePercent: 20, // 20% of trading fees go to protocol
|
|
109
|
+
}),
|
|
110
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@meteora-ag/ts-sdk-config/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"resolveJsonModule": true
|
|
7
|
+
},
|
|
8
|
+
"include": ["src/**/*.ts", "tests/utils/defaults.ts"],
|
|
9
|
+
"exclude": ["node_modules", "dist"]
|
|
10
|
+
}
|