@beluswap/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 +292 -0
- package/package.json +57 -0
- package/src/config.ts +82 -0
- package/src/converters.ts +153 -0
- package/src/factory.ts +151 -0
- package/src/index.ts +13 -0
- package/src/pool.ts +265 -0
- package/src/sdk.ts +110 -0
- package/src/types.ts +202 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# BeluSwap SDK
|
|
2
|
+
|
|
3
|
+
Human-friendly TypeScript SDK for BeluSwap concentrated liquidity AMM on Stellar.
|
|
4
|
+
|
|
5
|
+
## Why BeluSwap SDK?
|
|
6
|
+
|
|
7
|
+
**No more manual conversions!** Use normal numbers instead of:
|
|
8
|
+
- ❌ Converting amounts to stroops (7 decimals)
|
|
9
|
+
- ❌ Calculating sqrt_price_x64 in Q64.64 format
|
|
10
|
+
- ❌ Converting prices to ticks
|
|
11
|
+
- ❌ Aligning ticks to spacing
|
|
12
|
+
- ❌ Converting days to ledgers
|
|
13
|
+
- ❌ Converting percentages to basis points
|
|
14
|
+
|
|
15
|
+
✅ **Just use human-readable inputs!**
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @beluswap/sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import BeluSwapSDK from '@beluswap/sdk';
|
|
27
|
+
|
|
28
|
+
// Initialize SDK
|
|
29
|
+
const sdk = new BeluSwapSDK({
|
|
30
|
+
factoryAddress: 'CFACTORY...',
|
|
31
|
+
network: 'testnet', // or 'mainnet'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Create pool with NORMAL numbers!
|
|
35
|
+
const pool = await sdk.factory.createPool({
|
|
36
|
+
creator: 'GCREATOR...',
|
|
37
|
+
tokenA: 'CDUSDC...',
|
|
38
|
+
tokenB: 'CDXLM...',
|
|
39
|
+
feeTier: 'VOLATILE', // Not 30 bps!
|
|
40
|
+
creatorFeePercent: 1, // 1%, not 100 bps!
|
|
41
|
+
initialPrice: 1.0, // Normal price!
|
|
42
|
+
amount0: 100, // 100 tokens, not stroops!
|
|
43
|
+
amount1: 100,
|
|
44
|
+
priceRangeLower: 0.95, // Normal price range!
|
|
45
|
+
priceRangeUpper: 1.05,
|
|
46
|
+
lockDurationDays: 7, // Days, not ledgers!
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(pool.summary);
|
|
50
|
+
// {
|
|
51
|
+
// pair: "CDUSDC.../CDXLM...",
|
|
52
|
+
// fee: "0.3%",
|
|
53
|
+
// initialPrice: 1,
|
|
54
|
+
// priceRange: "0.95 - 1.05",
|
|
55
|
+
// amounts: "100 + 100",
|
|
56
|
+
// lockDuration: "7 days"
|
|
57
|
+
// }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
### ✅ Human-Friendly Inputs
|
|
63
|
+
- Normal prices (1.0, 2.5) instead of sqrt_price_x64
|
|
64
|
+
- Normal amounts (100, 500) instead of stroops
|
|
65
|
+
- Percentages (1% slippage) instead of basis points
|
|
66
|
+
- Days for lock duration instead of ledgers
|
|
67
|
+
- Fee tier names ('VOLATILE') instead of numbers
|
|
68
|
+
|
|
69
|
+
### ✅ Complete Integration
|
|
70
|
+
- **Factory SDK**: Create pools
|
|
71
|
+
- **Pool SDK**: Add/remove liquidity, swap, collect fees
|
|
72
|
+
- **Automatic Conversions**: All technical formats handled
|
|
73
|
+
|
|
74
|
+
### ✅ Type-Safe
|
|
75
|
+
- Full TypeScript support
|
|
76
|
+
- Autocomplete for all parameters
|
|
77
|
+
- Compile-time error checking
|
|
78
|
+
|
|
79
|
+
## Usage Examples
|
|
80
|
+
|
|
81
|
+
### 1. Initialize SDK
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import BeluSwapSDK from '@beluswap/sdk';
|
|
85
|
+
|
|
86
|
+
// Option A: Use predefined network
|
|
87
|
+
const sdk = new BeluSwapSDK({
|
|
88
|
+
factoryAddress: 'CFACTORY...',
|
|
89
|
+
network: 'testnet', // or 'mainnet'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Option B: Custom network
|
|
93
|
+
const sdk = new BeluSwapSDK({
|
|
94
|
+
factoryAddress: 'CFACTORY...',
|
|
95
|
+
rpcUrl: 'https://your-rpc-url',
|
|
96
|
+
networkPassphrase: 'Your Network ; Passphrase',
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 2. Add Liquidity
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// Connect to pool
|
|
104
|
+
const pool = await sdk.getAndConnectPool({
|
|
105
|
+
tokenA: 'CDUSDC...',
|
|
106
|
+
tokenB: 'CDXLM...',
|
|
107
|
+
feeTier: 'VOLATILE',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Add liquidity with normal numbers!
|
|
111
|
+
const addLiq = await pool.addLiquidity({
|
|
112
|
+
owner: 'GOWNER...',
|
|
113
|
+
amount0: 50, // 50 tokens!
|
|
114
|
+
amount1: 50,
|
|
115
|
+
priceRangeLower: 0.95, // Normal prices!
|
|
116
|
+
priceRangeUpper: 1.05,
|
|
117
|
+
feeTier: 'VOLATILE',
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
console.log(addLiq.summary);
|
|
121
|
+
// { priceRange: "0.9500 - 1.0500", amounts: "50 + 50" }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 3. Execute Swap
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const swap = await pool.swap({
|
|
128
|
+
sender: 'GTRADER...',
|
|
129
|
+
tokenIn: 'USDC',
|
|
130
|
+
amountIn: 10, // Normal amount!
|
|
131
|
+
slippagePercent: 1, // 1%, not 100 bps!
|
|
132
|
+
priceLimit: 0.95, // Optional
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
console.log(swap.summary);
|
|
136
|
+
// {
|
|
137
|
+
// swapping: "10 USDC",
|
|
138
|
+
// estimatedOutput: "~9.9700",
|
|
139
|
+
// minimumOutput: "9.9003",
|
|
140
|
+
// slippage: "1%"
|
|
141
|
+
// }
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 4. Check Position
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
const position = await pool.getPosition({
|
|
148
|
+
owner: 'GOWNER...',
|
|
149
|
+
priceRangeLower: 0.95,
|
|
150
|
+
priceRangeUpper: 1.05,
|
|
151
|
+
feeTier: 'VOLATILE',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
console.log(position);
|
|
155
|
+
// {
|
|
156
|
+
// amount0: 100, // Human-readable!
|
|
157
|
+
// amount1: 100,
|
|
158
|
+
// feesOwed0: 0.5,
|
|
159
|
+
// feesOwed1: 0.5
|
|
160
|
+
// }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## API Reference
|
|
164
|
+
|
|
165
|
+
### BeluSwapSDK
|
|
166
|
+
|
|
167
|
+
Main SDK class.
|
|
168
|
+
|
|
169
|
+
#### Constructor
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
new BeluSwapSDK(config: {
|
|
173
|
+
factoryAddress: string;
|
|
174
|
+
poolAddress?: string;
|
|
175
|
+
network?: 'testnet' | 'mainnet';
|
|
176
|
+
rpcUrl?: string;
|
|
177
|
+
networkPassphrase?: string;
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Properties
|
|
182
|
+
|
|
183
|
+
- `factory: BelugaFactorySDK` - Factory operations
|
|
184
|
+
- `pool?: BelugaPoolSDK` - Pool operations (if connected)
|
|
185
|
+
|
|
186
|
+
#### Methods
|
|
187
|
+
|
|
188
|
+
- `connectPool(address: string): BelugaPoolSDK` - Connect to specific pool
|
|
189
|
+
- `getAndConnectPool(params): Promise<BelugaPoolSDK>` - Get and connect pool
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### BelugaFactorySDK
|
|
194
|
+
|
|
195
|
+
Factory operations for pool creation.
|
|
196
|
+
|
|
197
|
+
#### Methods
|
|
198
|
+
|
|
199
|
+
##### `createPool(params): Promise<CreatePoolResult>`
|
|
200
|
+
|
|
201
|
+
Create a new pool.
|
|
202
|
+
|
|
203
|
+
**Parameters:**
|
|
204
|
+
- `creator: string` - Creator address
|
|
205
|
+
- `tokenA: string` - Token A address
|
|
206
|
+
- `tokenB: string` - Token B address
|
|
207
|
+
- `feeTier: 'STABLE' | 'VOLATILE' | 'EXOTIC'` - Fee tier
|
|
208
|
+
- `creatorFeePercent: number` - Creator fee (0.1-10%)
|
|
209
|
+
- `initialPrice: number` - Initial price
|
|
210
|
+
- `amount0: number` - Initial token0 amount
|
|
211
|
+
- `amount1: number` - Initial token1 amount
|
|
212
|
+
- `priceRangeLower: number` - Lower price bound
|
|
213
|
+
- `priceRangeUpper: number` - Upper price bound
|
|
214
|
+
- `lockDurationDays?: number` - Lock duration in days
|
|
215
|
+
- `permanent?: boolean` - Permanent lock?
|
|
216
|
+
|
|
217
|
+
**Returns:** Object with `summary`, `contractParams`, and `technical` details.
|
|
218
|
+
|
|
219
|
+
##### `getPool(params): Promise<string | null>`
|
|
220
|
+
|
|
221
|
+
Get pool address.
|
|
222
|
+
|
|
223
|
+
##### `poolExists(params): Promise<boolean>`
|
|
224
|
+
|
|
225
|
+
Check if pool exists.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### BelugaPoolSDK
|
|
230
|
+
|
|
231
|
+
Pool operations for liquidity and swaps.
|
|
232
|
+
|
|
233
|
+
#### Methods
|
|
234
|
+
|
|
235
|
+
##### `addLiquidity(params): Promise<AddLiquidityResult>`
|
|
236
|
+
|
|
237
|
+
Add liquidity to pool.
|
|
238
|
+
|
|
239
|
+
##### `removeLiquidity(params)`
|
|
240
|
+
|
|
241
|
+
Remove liquidity from pool.
|
|
242
|
+
|
|
243
|
+
##### `swap(params): Promise<SwapResult>`
|
|
244
|
+
|
|
245
|
+
Execute a swap.
|
|
246
|
+
|
|
247
|
+
##### `previewSwap(params): Promise<PreviewSwapResult>`
|
|
248
|
+
|
|
249
|
+
Preview swap output (read-only, no gas).
|
|
250
|
+
|
|
251
|
+
##### `getPosition(params): Promise<PositionInfo>`
|
|
252
|
+
|
|
253
|
+
Get position information.
|
|
254
|
+
|
|
255
|
+
##### `collectFees(params)`
|
|
256
|
+
|
|
257
|
+
Collect accumulated fees.
|
|
258
|
+
|
|
259
|
+
##### `getPoolState(): Promise<PoolState>`
|
|
260
|
+
|
|
261
|
+
Get current pool state.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Fee Tiers
|
|
266
|
+
|
|
267
|
+
Three predefined fee tiers:
|
|
268
|
+
|
|
269
|
+
| Tier | Fee | Tick Spacing | Use Case |
|
|
270
|
+
|------|-----|--------------|----------|
|
|
271
|
+
| STABLE | 0.05% | 10 | Stablecoin pairs |
|
|
272
|
+
| VOLATILE | 0.30% | 60 | Normal volatile pairs |
|
|
273
|
+
| EXOTIC | 1.00% | 200 | Exotic/meme tokens |
|
|
274
|
+
|
|
275
|
+
## Examples
|
|
276
|
+
|
|
277
|
+
See [examples/](examples/) directory for complete examples:
|
|
278
|
+
- `create-pool.ts` - Create a new pool
|
|
279
|
+
- `add-liquidity.ts` - Add liquidity to pool
|
|
280
|
+
- `swap.ts` - Execute swaps
|
|
281
|
+
- `monitor-position.ts` - Monitor position value
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
Apache 2.0
|
|
287
|
+
|
|
288
|
+
## Support
|
|
289
|
+
|
|
290
|
+
- GitHub Issues: [github.com/Beluga-Swap/sdk/issues](https://github.com/Beluga-Swap/sdk/issues)
|
|
291
|
+
- Discord: [discord.gg/belugaswap](https://discord.gg/belugaswap)
|
|
292
|
+
- Email: support@beluswap.io
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beluswap/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "BelugaSwap TypeScript SDK with human-friendly inputs",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"build:watch": "tsc --watch",
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"prepublish": "npm run clean && npm run build",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"lint": "eslint src --ext .ts",
|
|
20
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"belugaswap",
|
|
24
|
+
"stellar",
|
|
25
|
+
"soroban",
|
|
26
|
+
"dex",
|
|
27
|
+
"amm",
|
|
28
|
+
"concentrated-liquidity",
|
|
29
|
+
"sdk",
|
|
30
|
+
"typescript"
|
|
31
|
+
],
|
|
32
|
+
"author": "BelugaSwap Team",
|
|
33
|
+
"license": "Apache-2.0",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/Beluga-Swap/sdk.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/Beluga-Swap/sdk/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/Beluga-Swap/sdk#readme",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@stellar/stellar-sdk": "^12.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
48
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
49
|
+
"eslint": "^8.0.0",
|
|
50
|
+
"jest": "^29.0.0",
|
|
51
|
+
"prettier": "^3.0.0",
|
|
52
|
+
"typescript": "^5.0.0"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
// BelugaSwap SDK Configuration
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SDK Configuration Constants
|
|
6
|
+
*/
|
|
7
|
+
export const BELUGA_CONFIG = {
|
|
8
|
+
// Stellar stroops (7 decimals)
|
|
9
|
+
STROOPS_DECIMALS: 7,
|
|
10
|
+
STROOPS_MULTIPLIER: 10_000_000,
|
|
11
|
+
|
|
12
|
+
// Q64.64 format
|
|
13
|
+
Q64: BigInt(2) ** BigInt(64),
|
|
14
|
+
ONE_X64: BigInt(2) ** BigInt(64), // 18446744073709551616
|
|
15
|
+
|
|
16
|
+
// Tick constants
|
|
17
|
+
MIN_TICK: -887272,
|
|
18
|
+
MAX_TICK: 887272,
|
|
19
|
+
TICK_BASE: 1.0001,
|
|
20
|
+
|
|
21
|
+
// Fee tiers
|
|
22
|
+
FEE_TIERS: {
|
|
23
|
+
STABLE: {
|
|
24
|
+
name: 'STABLE' as const,
|
|
25
|
+
bps: 5,
|
|
26
|
+
spacing: 10,
|
|
27
|
+
percent: 0.05,
|
|
28
|
+
description: 'For stablecoin pairs (0.05% fee)',
|
|
29
|
+
},
|
|
30
|
+
VOLATILE: {
|
|
31
|
+
name: 'VOLATILE' as const,
|
|
32
|
+
bps: 30,
|
|
33
|
+
spacing: 60,
|
|
34
|
+
percent: 0.30,
|
|
35
|
+
description: 'For volatile pairs (0.30% fee)',
|
|
36
|
+
},
|
|
37
|
+
EXOTIC: {
|
|
38
|
+
name: 'EXOTIC' as const,
|
|
39
|
+
bps: 100,
|
|
40
|
+
spacing: 200,
|
|
41
|
+
percent: 1.00,
|
|
42
|
+
description: 'For exotic/meme pairs (1.00% fee)',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Validation limits
|
|
47
|
+
MIN_LOCK_DURATION_LEDGERS: 120_960, // ~7 days
|
|
48
|
+
MIN_INITIAL_LIQUIDITY: 1_000_000, // 0.1 tokens
|
|
49
|
+
MIN_CREATOR_FEE_BPS: 10, // 0.1%
|
|
50
|
+
MAX_CREATOR_FEE_BPS: 1000, // 10%
|
|
51
|
+
MAX_SLIPPAGE_BPS: 5000, // 50%
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fee tier type
|
|
56
|
+
*/
|
|
57
|
+
export type FeeTier = keyof typeof BELUGA_CONFIG.FEE_TIERS;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Network configuration
|
|
61
|
+
*/
|
|
62
|
+
export interface NetworkConfig {
|
|
63
|
+
rpcUrl: string;
|
|
64
|
+
networkPassphrase: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Predefined networks
|
|
70
|
+
*/
|
|
71
|
+
export const NETWORKS = {
|
|
72
|
+
TESTNET: {
|
|
73
|
+
rpcUrl: 'https://soroban-testnet.stellar.org',
|
|
74
|
+
networkPassphrase: 'Test SDF Network ; September 2015',
|
|
75
|
+
name: 'Testnet',
|
|
76
|
+
},
|
|
77
|
+
MAINNET: {
|
|
78
|
+
rpcUrl: 'https://soroban-mainnet.stellar.org',
|
|
79
|
+
networkPassphrase: 'Public Global Stellar Network ; September 2015',
|
|
80
|
+
name: 'Mainnet',
|
|
81
|
+
},
|
|
82
|
+
} as const;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/converters.ts
|
|
2
|
+
// Core conversion utilities
|
|
3
|
+
|
|
4
|
+
import { BELUGA_CONFIG } from './config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Price Converters
|
|
8
|
+
*/
|
|
9
|
+
export class PriceConverter {
|
|
10
|
+
/**
|
|
11
|
+
* Convert human price to sqrt_price_x64
|
|
12
|
+
*/
|
|
13
|
+
static toSqrtPrice(price: number): bigint {
|
|
14
|
+
if (price <= 0) throw new Error("Price must be positive");
|
|
15
|
+
const sqrtPrice = Math.sqrt(price);
|
|
16
|
+
return BigInt(Math.floor(sqrtPrice * Number(BELUGA_CONFIG.Q64)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Convert sqrt_price_x64 to human price
|
|
21
|
+
*/
|
|
22
|
+
static fromSqrtPrice(sqrtPriceX64: bigint): number {
|
|
23
|
+
const sqrtPrice = Number(sqrtPriceX64) / Number(BELUGA_CONFIG.Q64);
|
|
24
|
+
return sqrtPrice * sqrtPrice;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format price for display
|
|
29
|
+
*/
|
|
30
|
+
static format(sqrtPriceX64: bigint, decimals: number = 6): string {
|
|
31
|
+
const price = this.fromSqrtPrice(sqrtPriceX64);
|
|
32
|
+
return price.toFixed(decimals);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Tick Converters
|
|
38
|
+
*/
|
|
39
|
+
export class TickConverter {
|
|
40
|
+
/**
|
|
41
|
+
* Convert human price to tick
|
|
42
|
+
*/
|
|
43
|
+
static priceToTick(price: number): number {
|
|
44
|
+
if (price <= 0) throw new Error("Price must be positive");
|
|
45
|
+
return Math.floor(Math.log(price) / Math.log(BELUGA_CONFIG.TICK_BASE));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert tick to human price
|
|
50
|
+
*/
|
|
51
|
+
static tickToPrice(tick: number): number {
|
|
52
|
+
return Math.pow(BELUGA_CONFIG.TICK_BASE, tick);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Align tick to spacing
|
|
57
|
+
*/
|
|
58
|
+
static align(tick: number, spacing: number): number {
|
|
59
|
+
return Math.floor(tick / spacing) * spacing;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create tick range from price range
|
|
64
|
+
*/
|
|
65
|
+
static createRange(
|
|
66
|
+
lowerPrice: number,
|
|
67
|
+
upperPrice: number,
|
|
68
|
+
spacing: number
|
|
69
|
+
): { lowerTick: number; upperTick: number } {
|
|
70
|
+
return {
|
|
71
|
+
lowerTick: this.align(this.priceToTick(lowerPrice), spacing),
|
|
72
|
+
upperTick: this.align(this.priceToTick(upperPrice), spacing),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Amount Converters (Stroops ↔ Human)
|
|
79
|
+
*/
|
|
80
|
+
export class AmountConverter {
|
|
81
|
+
/**
|
|
82
|
+
* Convert human amount to stroops
|
|
83
|
+
*/
|
|
84
|
+
static toStroops(amount: number): bigint {
|
|
85
|
+
return BigInt(Math.floor(amount * BELUGA_CONFIG.STROOPS_MULTIPLIER));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Convert stroops to human amount
|
|
90
|
+
*/
|
|
91
|
+
static fromStroops(stroops: bigint | number): number {
|
|
92
|
+
const stroopsNum = typeof stroops === 'bigint' ? Number(stroops) : stroops;
|
|
93
|
+
return stroopsNum / BELUGA_CONFIG.STROOPS_MULTIPLIER;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Format amount for display
|
|
98
|
+
*/
|
|
99
|
+
static format(stroops: bigint | number, decimals: number = 7): string {
|
|
100
|
+
const amount = this.fromStroops(stroops);
|
|
101
|
+
return amount.toFixed(decimals);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Format with token symbol
|
|
106
|
+
*/
|
|
107
|
+
static formatWithSymbol(
|
|
108
|
+
stroops: bigint | number,
|
|
109
|
+
symbol: string,
|
|
110
|
+
decimals: number = 7
|
|
111
|
+
): string {
|
|
112
|
+
return `${this.format(stroops, decimals)} ${symbol}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Fee Converters
|
|
118
|
+
*/
|
|
119
|
+
export class FeeConverter {
|
|
120
|
+
/**
|
|
121
|
+
* Convert percent to basis points
|
|
122
|
+
*/
|
|
123
|
+
static percentToBps(percent: number): number {
|
|
124
|
+
return Math.floor(percent * 100);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert basis points to percent
|
|
129
|
+
*/
|
|
130
|
+
static bpsToPercent(bps: number): number {
|
|
131
|
+
return bps / 100;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Time Converters
|
|
137
|
+
*/
|
|
138
|
+
export class TimeConverter {
|
|
139
|
+
/**
|
|
140
|
+
* Convert days to ledgers
|
|
141
|
+
*/
|
|
142
|
+
static daysToLedgers(days: number): number {
|
|
143
|
+
// 1 day ≈ 17,280 ledgers (5s per ledger)
|
|
144
|
+
return Math.floor(days * 17_280);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Convert ledgers to days
|
|
149
|
+
*/
|
|
150
|
+
static ledgersToDays(ledgers: number): number {
|
|
151
|
+
return ledgers / 17_280;
|
|
152
|
+
}
|
|
153
|
+
}
|
package/src/factory.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// src/factory.ts
|
|
2
|
+
// Factory SDK for pool creation
|
|
3
|
+
|
|
4
|
+
import { Contract, SorobanRpc } from '@stellar/stellar-sdk';
|
|
5
|
+
import { BELUGA_CONFIG } from './config';
|
|
6
|
+
import { PriceConverter, TickConverter, AmountConverter, FeeConverter, TimeConverter } from './converters';
|
|
7
|
+
import type { CreatePoolParams, CreatePoolResult, GetPoolParams } from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* BelugaSwap Factory SDK
|
|
11
|
+
* Create pools with human-friendly inputs
|
|
12
|
+
*/
|
|
13
|
+
export class BelugaFactorySDK {
|
|
14
|
+
private contract: Contract;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private contractId: string,
|
|
18
|
+
private rpc: SorobanRpc.Server,
|
|
19
|
+
private networkPassphrase: string
|
|
20
|
+
) {
|
|
21
|
+
this.contract = new Contract(contractId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a new pool with human-friendly inputs
|
|
26
|
+
*/
|
|
27
|
+
async createPool(params: CreatePoolParams): Promise<CreatePoolResult> {
|
|
28
|
+
// 1. Get fee tier info
|
|
29
|
+
const feeTier = BELUGA_CONFIG.FEE_TIERS[params.feeTier];
|
|
30
|
+
const feeBps = feeTier.bps;
|
|
31
|
+
const tickSpacing = feeTier.spacing;
|
|
32
|
+
|
|
33
|
+
// 2. Convert creator fee
|
|
34
|
+
const creatorFeeBps = FeeConverter.percentToBps(params.creatorFeePercent);
|
|
35
|
+
if (creatorFeeBps < BELUGA_CONFIG.MIN_CREATOR_FEE_BPS ||
|
|
36
|
+
creatorFeeBps > BELUGA_CONFIG.MAX_CREATOR_FEE_BPS) {
|
|
37
|
+
throw new Error("Creator fee must be between 0.1% and 10%");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Convert initial price
|
|
41
|
+
const initialSqrtPrice = PriceConverter.toSqrtPrice(params.initialPrice);
|
|
42
|
+
const currentTick = TickConverter.priceToTick(params.initialPrice);
|
|
43
|
+
|
|
44
|
+
// 4. Convert amounts to stroops
|
|
45
|
+
const amount0Stroops = AmountConverter.toStroops(params.amount0);
|
|
46
|
+
const amount1Stroops = AmountConverter.toStroops(params.amount1);
|
|
47
|
+
|
|
48
|
+
// Validate minimum
|
|
49
|
+
if (amount0Stroops < BELUGA_CONFIG.MIN_INITIAL_LIQUIDITY ||
|
|
50
|
+
amount1Stroops < BELUGA_CONFIG.MIN_INITIAL_LIQUIDITY) {
|
|
51
|
+
throw new Error("Minimum amount is 0.1 tokens each");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 5. Convert price range to ticks
|
|
55
|
+
const range = TickConverter.createRange(
|
|
56
|
+
params.priceRangeLower,
|
|
57
|
+
params.priceRangeUpper,
|
|
58
|
+
tickSpacing
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (range.lowerTick >= range.upperTick) {
|
|
62
|
+
throw new Error("Lower price must be less than upper price");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 6. Convert lock duration
|
|
66
|
+
let lockDuration: number;
|
|
67
|
+
if (params.permanent) {
|
|
68
|
+
lockDuration = 0;
|
|
69
|
+
} else if (params.lockDurationDays) {
|
|
70
|
+
lockDuration = TimeConverter.daysToLedgers(params.lockDurationDays);
|
|
71
|
+
if (lockDuration < BELUGA_CONFIG.MIN_LOCK_DURATION_LEDGERS) {
|
|
72
|
+
throw new Error("Minimum lock duration is 7 days");
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
lockDuration = BELUGA_CONFIG.MIN_LOCK_DURATION_LEDGERS;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 7. Prepare contract params
|
|
79
|
+
const contractParams = {
|
|
80
|
+
creator: params.creator,
|
|
81
|
+
params: {
|
|
82
|
+
token_a: params.tokenA,
|
|
83
|
+
token_b: params.tokenB,
|
|
84
|
+
fee_bps: feeBps,
|
|
85
|
+
creator_fee_bps: creatorFeeBps,
|
|
86
|
+
initial_sqrt_price_x64: initialSqrtPrice.toString(),
|
|
87
|
+
amount0_desired: amount0Stroops.toString(),
|
|
88
|
+
amount1_desired: amount1Stroops.toString(),
|
|
89
|
+
lower_tick: range.lowerTick,
|
|
90
|
+
upper_tick: range.upperTick,
|
|
91
|
+
lock_duration: lockDuration,
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// 8. Return result
|
|
96
|
+
return {
|
|
97
|
+
summary: {
|
|
98
|
+
pair: `${params.tokenA}/${params.tokenB}`,
|
|
99
|
+
fee: `${feeTier.percent}%`,
|
|
100
|
+
creatorFee: `${params.creatorFeePercent}%`,
|
|
101
|
+
initialPrice: params.initialPrice,
|
|
102
|
+
priceRange: `${params.priceRangeLower} - ${params.priceRangeUpper}`,
|
|
103
|
+
amounts: `${params.amount0} + ${params.amount1}`,
|
|
104
|
+
lockDuration: params.permanent
|
|
105
|
+
? 'Permanent'
|
|
106
|
+
: `${params.lockDurationDays} days`,
|
|
107
|
+
},
|
|
108
|
+
contractParams,
|
|
109
|
+
technical: {
|
|
110
|
+
feeBps,
|
|
111
|
+
creatorFeeBps,
|
|
112
|
+
sqrtPriceX64: initialSqrtPrice.toString(),
|
|
113
|
+
currentTick,
|
|
114
|
+
tickSpacing,
|
|
115
|
+
lowerTick: range.lowerTick,
|
|
116
|
+
upperTick: range.upperTick,
|
|
117
|
+
amount0Stroops: amount0Stroops.toString(),
|
|
118
|
+
amount1Stroops: amount1Stroops.toString(),
|
|
119
|
+
lockDurationLedgers: lockDuration,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get pool address
|
|
126
|
+
*/
|
|
127
|
+
async getPool(params: GetPoolParams): Promise<string | null> {
|
|
128
|
+
const feeBps = BELUGA_CONFIG.FEE_TIERS[params.feeTier].bps;
|
|
129
|
+
|
|
130
|
+
// TODO: Implement contract call
|
|
131
|
+
// const result = await this.contract.call('get_pool_address', ...);
|
|
132
|
+
|
|
133
|
+
throw new Error("Not implemented - add contract integration");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if pool exists
|
|
138
|
+
*/
|
|
139
|
+
async poolExists(params: GetPoolParams): Promise<boolean> {
|
|
140
|
+
const pool = await this.getPool(params);
|
|
141
|
+
return pool !== null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get total number of pools
|
|
146
|
+
*/
|
|
147
|
+
async getTotalPools(): Promise<number> {
|
|
148
|
+
// TODO: Implement contract call
|
|
149
|
+
throw new Error("Not implemented - add contract integration");
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// BelugaSwap SDK - Main Export
|
|
2
|
+
// Version: 0.1.0
|
|
3
|
+
|
|
4
|
+
export { BelugaSwapSDK as default, BelugaSwapSDK } from './sdk';
|
|
5
|
+
export { BelugaFactorySDK } from './factory';
|
|
6
|
+
export { BelugaPoolSDK } from './pool';
|
|
7
|
+
export { BELUGA_CONFIG } from './config';
|
|
8
|
+
export * from './types';
|
|
9
|
+
export * from './converters';
|
|
10
|
+
|
|
11
|
+
// Re-export for convenience
|
|
12
|
+
import { BelugaSwapSDK } from './sdk';
|
|
13
|
+
export default BelugaSwapSDK;
|
package/src/pool.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// src/pool.ts
|
|
2
|
+
// Pool SDK for liquidity and swap operations
|
|
3
|
+
|
|
4
|
+
import { Contract, SorobanRpc } from '@stellar/stellar-sdk';
|
|
5
|
+
import { BELUGA_CONFIG } from './config';
|
|
6
|
+
import { PriceConverter, TickConverter, AmountConverter } from './converters';
|
|
7
|
+
import type {
|
|
8
|
+
AddLiquidityParams,
|
|
9
|
+
AddLiquidityResult,
|
|
10
|
+
RemoveLiquidityParams,
|
|
11
|
+
SwapParams,
|
|
12
|
+
SwapResult,
|
|
13
|
+
PreviewSwapResult,
|
|
14
|
+
GetPositionParams,
|
|
15
|
+
PositionInfo,
|
|
16
|
+
CollectFeesParams,
|
|
17
|
+
PoolState,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* BelugaSwap Pool SDK
|
|
22
|
+
* Interact with pools using human-friendly inputs
|
|
23
|
+
*/
|
|
24
|
+
export class BelugaPoolSDK {
|
|
25
|
+
private contract: Contract;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private contractId: string,
|
|
29
|
+
private rpc: SorobanRpc.Server,
|
|
30
|
+
private networkPassphrase: string
|
|
31
|
+
) {
|
|
32
|
+
this.contract = new Contract(contractId);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Add liquidity with human-friendly inputs
|
|
37
|
+
*/
|
|
38
|
+
async addLiquidity(params: AddLiquidityParams): Promise<AddLiquidityResult> {
|
|
39
|
+
// 1. Get tick spacing
|
|
40
|
+
const tickSpacing = BELUGA_CONFIG.FEE_TIERS[params.feeTier].spacing;
|
|
41
|
+
|
|
42
|
+
// 2. Convert price range to ticks
|
|
43
|
+
const range = TickConverter.createRange(
|
|
44
|
+
params.priceRangeLower,
|
|
45
|
+
params.priceRangeUpper,
|
|
46
|
+
tickSpacing
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// 3. Convert amounts to stroops
|
|
50
|
+
const amount0Stroops = AmountConverter.toStroops(params.amount0);
|
|
51
|
+
const amount1Stroops = AmountConverter.toStroops(params.amount1);
|
|
52
|
+
|
|
53
|
+
// 4. Prepare contract params
|
|
54
|
+
const contractParams = {
|
|
55
|
+
owner: params.owner,
|
|
56
|
+
lower_tick: range.lowerTick,
|
|
57
|
+
upper_tick: range.upperTick,
|
|
58
|
+
amount0_desired: amount0Stroops.toString(),
|
|
59
|
+
amount1_desired: amount1Stroops.toString(),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
summary: {
|
|
64
|
+
priceRange: `${params.priceRangeLower.toFixed(4)} - ${params.priceRangeUpper.toFixed(4)}`,
|
|
65
|
+
amounts: `${params.amount0} + ${params.amount1}`,
|
|
66
|
+
},
|
|
67
|
+
contractParams,
|
|
68
|
+
technical: {
|
|
69
|
+
lowerTick: range.lowerTick,
|
|
70
|
+
upperTick: range.upperTick,
|
|
71
|
+
tickSpacing,
|
|
72
|
+
amount0Stroops: amount0Stroops.toString(),
|
|
73
|
+
amount1Stroops: amount1Stroops.toString(),
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Remove liquidity with human-friendly inputs
|
|
80
|
+
*/
|
|
81
|
+
async removeLiquidity(params: RemoveLiquidityParams) {
|
|
82
|
+
if (params.liquidityPercent <= 0 || params.liquidityPercent > 100) {
|
|
83
|
+
throw new Error("Liquidity percent must be between 0 and 100");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const tickSpacing = BELUGA_CONFIG.FEE_TIERS[params.feeTier].spacing;
|
|
87
|
+
const range = TickConverter.createRange(
|
|
88
|
+
params.priceRangeLower,
|
|
89
|
+
params.priceRangeUpper,
|
|
90
|
+
tickSpacing
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
summary: {
|
|
95
|
+
removing: `${params.liquidityPercent}% of liquidity`,
|
|
96
|
+
priceRange: `${params.priceRangeLower} - ${params.priceRangeUpper}`,
|
|
97
|
+
},
|
|
98
|
+
contractParams: {
|
|
99
|
+
owner: params.owner,
|
|
100
|
+
lower_tick: range.lowerTick,
|
|
101
|
+
upper_tick: range.upperTick,
|
|
102
|
+
},
|
|
103
|
+
technical: {
|
|
104
|
+
lowerTick: range.lowerTick,
|
|
105
|
+
upperTick: range.upperTick,
|
|
106
|
+
percentToRemove: params.liquidityPercent,
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Execute swap with human-friendly inputs
|
|
113
|
+
*/
|
|
114
|
+
async swap(params: SwapParams): Promise<SwapResult> {
|
|
115
|
+
// 1. Convert amount to stroops
|
|
116
|
+
const amountInStroops = AmountConverter.toStroops(params.amountIn);
|
|
117
|
+
|
|
118
|
+
// 2. Calculate slippage in bps
|
|
119
|
+
const slippageBps = Math.floor(params.slippagePercent * 100);
|
|
120
|
+
if (slippageBps > BELUGA_CONFIG.MAX_SLIPPAGE_BPS) {
|
|
121
|
+
throw new Error("Slippage cannot exceed 50%");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Estimate output (placeholder - should call preview_swap)
|
|
125
|
+
const currentPrice = 1.0; // TODO: Get from pool state
|
|
126
|
+
const estimatedOut = params.amountIn * currentPrice;
|
|
127
|
+
const minAmountOut = estimatedOut * (1 - params.slippagePercent / 100);
|
|
128
|
+
const minAmountOutStroops = AmountConverter.toStroops(minAmountOut);
|
|
129
|
+
|
|
130
|
+
// 4. Convert price limit
|
|
131
|
+
const sqrtPriceLimitX64 = params.priceLimit
|
|
132
|
+
? PriceConverter.toSqrtPrice(params.priceLimit)
|
|
133
|
+
: BigInt(0);
|
|
134
|
+
|
|
135
|
+
// 5. Prepare contract params
|
|
136
|
+
const contractParams = {
|
|
137
|
+
sender: params.sender,
|
|
138
|
+
token_in: params.tokenIn,
|
|
139
|
+
amount_in: amountInStroops.toString(),
|
|
140
|
+
amount_out_min: minAmountOutStroops.toString(),
|
|
141
|
+
sqrt_price_limit_x64: sqrtPriceLimitX64.toString(),
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
summary: {
|
|
146
|
+
swapping: `${params.amountIn} ${params.tokenIn}`,
|
|
147
|
+
estimatedOutput: `~${estimatedOut.toFixed(4)}`,
|
|
148
|
+
minimumOutput: `${minAmountOut.toFixed(4)}`,
|
|
149
|
+
slippage: `${params.slippagePercent}%`,
|
|
150
|
+
priceLimit: params.priceLimit ? `${params.priceLimit}` : 'None',
|
|
151
|
+
},
|
|
152
|
+
contractParams,
|
|
153
|
+
technical: {
|
|
154
|
+
amountInStroops: amountInStroops.toString(),
|
|
155
|
+
minAmountOutStroops: minAmountOutStroops.toString(),
|
|
156
|
+
slippageBps,
|
|
157
|
+
sqrtPriceLimitX64: sqrtPriceLimitX64.toString(),
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Preview swap output (read-only)
|
|
164
|
+
*/
|
|
165
|
+
async previewSwap(params: {
|
|
166
|
+
tokenIn: string;
|
|
167
|
+
amountIn: number;
|
|
168
|
+
}): Promise<PreviewSwapResult> {
|
|
169
|
+
const amountInStroops = AmountConverter.toStroops(params.amountIn);
|
|
170
|
+
|
|
171
|
+
// TODO: Call contract preview_swap
|
|
172
|
+
|
|
173
|
+
// Placeholder
|
|
174
|
+
return {
|
|
175
|
+
amountOut: params.amountIn * 0.997,
|
|
176
|
+
priceImpact: 0.3,
|
|
177
|
+
newPrice: 1.003,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get position info (human-readable)
|
|
183
|
+
*/
|
|
184
|
+
async getPosition(params: GetPositionParams): Promise<PositionInfo> {
|
|
185
|
+
const tickSpacing = BELUGA_CONFIG.FEE_TIERS[params.feeTier].spacing;
|
|
186
|
+
const range = TickConverter.createRange(
|
|
187
|
+
params.priceRangeLower,
|
|
188
|
+
params.priceRangeUpper,
|
|
189
|
+
tickSpacing
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// TODO: Call contract get_position
|
|
193
|
+
|
|
194
|
+
// Mock result
|
|
195
|
+
const result = {
|
|
196
|
+
liquidity: 10000000,
|
|
197
|
+
amount0: BigInt(50_000_000),
|
|
198
|
+
amount1: BigInt(50_000_000),
|
|
199
|
+
fees_owed_0: BigInt(500_000),
|
|
200
|
+
fees_owed_1: BigInt(500_000),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
liquidity: result.liquidity,
|
|
205
|
+
amount0: AmountConverter.fromStroops(result.amount0),
|
|
206
|
+
amount1: AmountConverter.fromStroops(result.amount1),
|
|
207
|
+
feesOwed0: AmountConverter.fromStroops(result.fees_owed_0),
|
|
208
|
+
feesOwed1: AmountConverter.fromStroops(result.fees_owed_1),
|
|
209
|
+
formatted: {
|
|
210
|
+
liquidity: result.liquidity.toLocaleString(),
|
|
211
|
+
amounts: `${AmountConverter.fromStroops(result.amount0)} + ${AmountConverter.fromStroops(result.amount1)}`,
|
|
212
|
+
fees: `${AmountConverter.fromStroops(result.fees_owed_0)} + ${AmountConverter.fromStroops(result.fees_owed_1)}`,
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Collect fees
|
|
219
|
+
*/
|
|
220
|
+
async collectFees(params: CollectFeesParams) {
|
|
221
|
+
const tickSpacing = BELUGA_CONFIG.FEE_TIERS[params.feeTier].spacing;
|
|
222
|
+
const range = TickConverter.createRange(
|
|
223
|
+
params.priceRangeLower,
|
|
224
|
+
params.priceRangeUpper,
|
|
225
|
+
tickSpacing
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
contractParams: {
|
|
230
|
+
owner: params.owner,
|
|
231
|
+
lower_tick: range.lowerTick,
|
|
232
|
+
upper_tick: range.upperTick,
|
|
233
|
+
},
|
|
234
|
+
technical: {
|
|
235
|
+
lowerTick: range.lowerTick,
|
|
236
|
+
upperTick: range.upperTick,
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get current pool state (human-readable)
|
|
243
|
+
*/
|
|
244
|
+
async getPoolState(): Promise<PoolState> {
|
|
245
|
+
// TODO: Call contract get_pool_state
|
|
246
|
+
|
|
247
|
+
// Mock state
|
|
248
|
+
const state = {
|
|
249
|
+
sqrt_price_x64: (BigInt(1) << BigInt(64)).toString(),
|
|
250
|
+
current_tick: 0,
|
|
251
|
+
liquidity: 100_000_000,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
currentPrice: PriceConverter.fromSqrtPrice(BigInt(state.sqrt_price_x64)),
|
|
256
|
+
currentTick: state.current_tick,
|
|
257
|
+
liquidity: state.liquidity,
|
|
258
|
+
technical: {
|
|
259
|
+
sqrtPriceX64: state.sqrt_price_x64,
|
|
260
|
+
currentTick: state.current_tick,
|
|
261
|
+
liquidity: state.liquidity,
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/sdk.ts
|
|
2
|
+
// Main SDK class combining Factory and Pool
|
|
3
|
+
|
|
4
|
+
import { SorobanRpc } from '@stellar/stellar-sdk';
|
|
5
|
+
import { BelugaFactorySDK } from './factory';
|
|
6
|
+
import { BelugaPoolSDK } from './pool';
|
|
7
|
+
import { NETWORKS } from './config';
|
|
8
|
+
import type { NetworkConfig } from './config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* SDK Configuration
|
|
12
|
+
*/
|
|
13
|
+
export interface BelugaSDKConfig {
|
|
14
|
+
factoryAddress: string;
|
|
15
|
+
poolAddress?: string;
|
|
16
|
+
rpcUrl?: string;
|
|
17
|
+
networkPassphrase?: string;
|
|
18
|
+
network?: 'testnet' | 'mainnet';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Main BelugaSwap SDK
|
|
23
|
+
* Complete integration from Factory to Pool operations
|
|
24
|
+
*/
|
|
25
|
+
export class BelugaSwapSDK {
|
|
26
|
+
public factory: BelugaFactorySDK;
|
|
27
|
+
public pool?: BelugaPoolSDK;
|
|
28
|
+
private rpc: SorobanRpc.Server;
|
|
29
|
+
private networkPassphrase: string;
|
|
30
|
+
|
|
31
|
+
constructor(config: BelugaSDKConfig) {
|
|
32
|
+
// Determine network configuration
|
|
33
|
+
let networkConfig: NetworkConfig;
|
|
34
|
+
|
|
35
|
+
if (config.network) {
|
|
36
|
+
// Use predefined network
|
|
37
|
+
networkConfig = NETWORKS[config.network.toUpperCase() as keyof typeof NETWORKS];
|
|
38
|
+
} else if (config.rpcUrl && config.networkPassphrase) {
|
|
39
|
+
// Use custom network
|
|
40
|
+
networkConfig = {
|
|
41
|
+
rpcUrl: config.rpcUrl,
|
|
42
|
+
networkPassphrase: config.networkPassphrase,
|
|
43
|
+
};
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'Must provide either "network" (testnet/mainnet) or both "rpcUrl" and "networkPassphrase"'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.rpc = new SorobanRpc.Server(networkConfig.rpcUrl);
|
|
51
|
+
this.networkPassphrase = networkConfig.networkPassphrase;
|
|
52
|
+
|
|
53
|
+
// Initialize factory
|
|
54
|
+
this.factory = new BelugaFactorySDK(
|
|
55
|
+
config.factoryAddress,
|
|
56
|
+
this.rpc,
|
|
57
|
+
this.networkPassphrase
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Initialize pool if address provided
|
|
61
|
+
if (config.poolAddress) {
|
|
62
|
+
this.pool = new BelugaPoolSDK(
|
|
63
|
+
config.poolAddress,
|
|
64
|
+
this.rpc,
|
|
65
|
+
this.networkPassphrase
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Connect to a specific pool
|
|
72
|
+
*/
|
|
73
|
+
connectPool(poolAddress: string): BelugaPoolSDK {
|
|
74
|
+
this.pool = new BelugaPoolSDK(
|
|
75
|
+
poolAddress,
|
|
76
|
+
this.rpc,
|
|
77
|
+
this.networkPassphrase
|
|
78
|
+
);
|
|
79
|
+
return this.pool;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get pool address and connect in one step
|
|
84
|
+
*/
|
|
85
|
+
async getAndConnectPool(params: {
|
|
86
|
+
tokenA: string;
|
|
87
|
+
tokenB: string;
|
|
88
|
+
feeTier: 'STABLE' | 'VOLATILE' | 'EXOTIC';
|
|
89
|
+
}): Promise<BelugaPoolSDK> {
|
|
90
|
+
const poolAddress = await this.factory.getPool(params);
|
|
91
|
+
if (!poolAddress) {
|
|
92
|
+
throw new Error('Pool does not exist');
|
|
93
|
+
}
|
|
94
|
+
return this.connectPool(poolAddress);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get RPC server instance
|
|
99
|
+
*/
|
|
100
|
+
getRpc(): SorobanRpc.Server {
|
|
101
|
+
return this.rpc;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get network passphrase
|
|
106
|
+
*/
|
|
107
|
+
getNetworkPassphrase(): string {
|
|
108
|
+
return this.networkPassphrase;
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
// TypeScript type definitions
|
|
3
|
+
|
|
4
|
+
import { FeeTier } from './config';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pool Creation Parameters
|
|
8
|
+
*/
|
|
9
|
+
export interface CreatePoolParams {
|
|
10
|
+
creator: string;
|
|
11
|
+
tokenA: string;
|
|
12
|
+
tokenB: string;
|
|
13
|
+
feeTier: FeeTier;
|
|
14
|
+
creatorFeePercent: number;
|
|
15
|
+
initialPrice: number;
|
|
16
|
+
amount0: number;
|
|
17
|
+
amount1: number;
|
|
18
|
+
priceRangeLower: number;
|
|
19
|
+
priceRangeUpper: number;
|
|
20
|
+
lockDurationDays?: number;
|
|
21
|
+
permanent?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Pool Creation Result
|
|
26
|
+
*/
|
|
27
|
+
export interface CreatePoolResult {
|
|
28
|
+
summary: {
|
|
29
|
+
pair: string;
|
|
30
|
+
fee: string;
|
|
31
|
+
creatorFee: string;
|
|
32
|
+
initialPrice: number;
|
|
33
|
+
priceRange: string;
|
|
34
|
+
amounts: string;
|
|
35
|
+
lockDuration: string;
|
|
36
|
+
};
|
|
37
|
+
contractParams: any;
|
|
38
|
+
technical: {
|
|
39
|
+
feeBps: number;
|
|
40
|
+
creatorFeeBps: number;
|
|
41
|
+
sqrtPriceX64: string;
|
|
42
|
+
currentTick: number;
|
|
43
|
+
tickSpacing: number;
|
|
44
|
+
lowerTick: number;
|
|
45
|
+
upperTick: number;
|
|
46
|
+
amount0Stroops: string;
|
|
47
|
+
amount1Stroops: string;
|
|
48
|
+
lockDurationLedgers: number;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Add Liquidity Parameters
|
|
54
|
+
*/
|
|
55
|
+
export interface AddLiquidityParams {
|
|
56
|
+
owner: string;
|
|
57
|
+
amount0: number;
|
|
58
|
+
amount1: number;
|
|
59
|
+
priceRangeLower: number;
|
|
60
|
+
priceRangeUpper: number;
|
|
61
|
+
feeTier: FeeTier;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Add Liquidity Result
|
|
66
|
+
*/
|
|
67
|
+
export interface AddLiquidityResult {
|
|
68
|
+
summary: {
|
|
69
|
+
priceRange: string;
|
|
70
|
+
amounts: string;
|
|
71
|
+
};
|
|
72
|
+
contractParams: {
|
|
73
|
+
owner: string;
|
|
74
|
+
lower_tick: number;
|
|
75
|
+
upper_tick: number;
|
|
76
|
+
amount0_desired: string;
|
|
77
|
+
amount1_desired: string;
|
|
78
|
+
};
|
|
79
|
+
technical: {
|
|
80
|
+
lowerTick: number;
|
|
81
|
+
upperTick: number;
|
|
82
|
+
tickSpacing: number;
|
|
83
|
+
amount0Stroops: string;
|
|
84
|
+
amount1Stroops: string;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Remove Liquidity Parameters
|
|
90
|
+
*/
|
|
91
|
+
export interface RemoveLiquidityParams {
|
|
92
|
+
owner: string;
|
|
93
|
+
liquidityPercent: number;
|
|
94
|
+
priceRangeLower: number;
|
|
95
|
+
priceRangeUpper: number;
|
|
96
|
+
feeTier: FeeTier;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Swap Parameters
|
|
101
|
+
*/
|
|
102
|
+
export interface SwapParams {
|
|
103
|
+
sender: string;
|
|
104
|
+
tokenIn: string;
|
|
105
|
+
amountIn: number;
|
|
106
|
+
slippagePercent: number;
|
|
107
|
+
priceLimit?: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Swap Result
|
|
112
|
+
*/
|
|
113
|
+
export interface SwapResult {
|
|
114
|
+
summary: {
|
|
115
|
+
swapping: string;
|
|
116
|
+
estimatedOutput: string;
|
|
117
|
+
minimumOutput: string;
|
|
118
|
+
slippage: string;
|
|
119
|
+
priceLimit: string;
|
|
120
|
+
};
|
|
121
|
+
contractParams: {
|
|
122
|
+
sender: string;
|
|
123
|
+
token_in: string;
|
|
124
|
+
amount_in: string;
|
|
125
|
+
amount_out_min: string;
|
|
126
|
+
sqrt_price_limit_x64: string;
|
|
127
|
+
};
|
|
128
|
+
technical: {
|
|
129
|
+
amountInStroops: string;
|
|
130
|
+
minAmountOutStroops: string;
|
|
131
|
+
slippageBps: number;
|
|
132
|
+
sqrtPriceLimitX64: string;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Preview Swap Result
|
|
138
|
+
*/
|
|
139
|
+
export interface PreviewSwapResult {
|
|
140
|
+
amountOut: number;
|
|
141
|
+
priceImpact: number;
|
|
142
|
+
newPrice: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Position Info
|
|
147
|
+
*/
|
|
148
|
+
export interface PositionInfo {
|
|
149
|
+
liquidity: number;
|
|
150
|
+
amount0: number;
|
|
151
|
+
amount1: number;
|
|
152
|
+
feesOwed0: number;
|
|
153
|
+
feesOwed1: number;
|
|
154
|
+
formatted: {
|
|
155
|
+
liquidity: string;
|
|
156
|
+
amounts: string;
|
|
157
|
+
fees: string;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pool State
|
|
163
|
+
*/
|
|
164
|
+
export interface PoolState {
|
|
165
|
+
currentPrice: number;
|
|
166
|
+
currentTick: number;
|
|
167
|
+
liquidity: number;
|
|
168
|
+
technical: {
|
|
169
|
+
sqrtPriceX64: string;
|
|
170
|
+
currentTick: number;
|
|
171
|
+
liquidity: number;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get Position Parameters
|
|
177
|
+
*/
|
|
178
|
+
export interface GetPositionParams {
|
|
179
|
+
owner: string;
|
|
180
|
+
priceRangeLower: number;
|
|
181
|
+
priceRangeUpper: number;
|
|
182
|
+
feeTier: FeeTier;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Collect Fees Parameters
|
|
187
|
+
*/
|
|
188
|
+
export interface CollectFeesParams {
|
|
189
|
+
owner: string;
|
|
190
|
+
priceRangeLower: number;
|
|
191
|
+
priceRangeUpper: number;
|
|
192
|
+
feeTier: FeeTier;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get Pool Parameters
|
|
197
|
+
*/
|
|
198
|
+
export interface GetPoolParams {
|
|
199
|
+
tokenA: string;
|
|
200
|
+
tokenB: string;
|
|
201
|
+
feeTier: FeeTier;
|
|
202
|
+
}
|