@stabbleorg/mclmm-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 +267 -0
- package/idl/stabble_clmm.json +5407 -0
- package/lib/api/index.d.ts +15 -0
- package/lib/api/index.d.ts.map +1 -0
- package/lib/api/pools.d.ts +45 -0
- package/lib/api/pools.d.ts.map +1 -0
- package/lib/client.d.ts +43 -0
- package/lib/client.d.ts.map +1 -0
- package/lib/clmm.d.ts +91 -0
- package/lib/clmm.d.ts.map +1 -0
- package/lib/constants.d.ts +122 -0
- package/lib/constants.d.ts.map +1 -0
- package/lib/generated/accounts/ammConfig.d.ts +58 -0
- package/lib/generated/accounts/ammConfig.d.ts.map +1 -0
- package/lib/generated/accounts/index.d.ts +17 -0
- package/lib/generated/accounts/index.d.ts.map +1 -0
- package/lib/generated/accounts/observationState.d.ts +51 -0
- package/lib/generated/accounts/observationState.d.ts.map +1 -0
- package/lib/generated/accounts/operationState.d.ts +38 -0
- package/lib/generated/accounts/operationState.d.ts.map +1 -0
- package/lib/generated/accounts/personalPositionState.d.ts +73 -0
- package/lib/generated/accounts/personalPositionState.d.ts.map +1 -0
- package/lib/generated/accounts/poolState.d.ts +157 -0
- package/lib/generated/accounts/poolState.d.ts.map +1 -0
- package/lib/generated/accounts/protocolPositionState.d.ts +70 -0
- package/lib/generated/accounts/protocolPositionState.d.ts.map +1 -0
- package/lib/generated/accounts/supportMintAssociated.d.ts +36 -0
- package/lib/generated/accounts/supportMintAssociated.d.ts.map +1 -0
- package/lib/generated/accounts/tickArrayBitmapExtension.d.ts +36 -0
- package/lib/generated/accounts/tickArrayBitmapExtension.d.ts.map +1 -0
- package/lib/generated/accounts/tickArrayState.d.ts +39 -0
- package/lib/generated/accounts/tickArrayState.d.ts.map +1 -0
- package/lib/generated/errors/ammV3.d.ts +110 -0
- package/lib/generated/errors/ammV3.d.ts.map +1 -0
- package/lib/generated/errors/index.d.ts +9 -0
- package/lib/generated/errors/index.d.ts.map +1 -0
- package/lib/generated/index.d.ts +13 -0
- package/lib/generated/index.d.ts.map +1 -0
- package/lib/generated/instructions/closePosition.d.ts +78 -0
- package/lib/generated/instructions/closePosition.d.ts.map +1 -0
- package/lib/generated/instructions/closeProtocolPosition.d.ts +40 -0
- package/lib/generated/instructions/closeProtocolPosition.d.ts.map +1 -0
- package/lib/generated/instructions/collectFundFee.d.ts +96 -0
- package/lib/generated/instructions/collectFundFee.d.ts.map +1 -0
- package/lib/generated/instructions/collectProtocolFee.d.ts +96 -0
- package/lib/generated/instructions/collectProtocolFee.d.ts.map +1 -0
- package/lib/generated/instructions/collectRemainingRewards.d.ts +76 -0
- package/lib/generated/instructions/collectRemainingRewards.d.ts.map +1 -0
- package/lib/generated/instructions/createAmmConfig.d.ts +78 -0
- package/lib/generated/instructions/createAmmConfig.d.ts.map +1 -0
- package/lib/generated/instructions/createOperationAccount.d.ts +57 -0
- package/lib/generated/instructions/createOperationAccount.d.ts.map +1 -0
- package/lib/generated/instructions/createPool.d.ts +133 -0
- package/lib/generated/instructions/createPool.d.ts.map +1 -0
- package/lib/generated/instructions/createSupportMintAssociated.d.ts +64 -0
- package/lib/generated/instructions/createSupportMintAssociated.d.ts.map +1 -0
- package/lib/generated/instructions/decreaseLiquidity.d.ts +100 -0
- package/lib/generated/instructions/decreaseLiquidity.d.ts.map +1 -0
- package/lib/generated/instructions/decreaseLiquidityV2.d.ts +120 -0
- package/lib/generated/instructions/decreaseLiquidityV2.d.ts.map +1 -0
- package/lib/generated/instructions/increaseLiquidity.d.ts +100 -0
- package/lib/generated/instructions/increaseLiquidity.d.ts.map +1 -0
- package/lib/generated/instructions/increaseLiquidityV2.d.ts +118 -0
- package/lib/generated/instructions/increaseLiquidityV2.d.ts.map +1 -0
- package/lib/generated/instructions/index.d.ts +34 -0
- package/lib/generated/instructions/index.d.ts.map +1 -0
- package/lib/generated/instructions/initializeReward.d.ts +113 -0
- package/lib/generated/instructions/initializeReward.d.ts.map +1 -0
- package/lib/generated/instructions/openPosition.d.ts +198 -0
- package/lib/generated/instructions/openPosition.d.ts.map +1 -0
- package/lib/generated/instructions/openPositionV2.d.ts +218 -0
- package/lib/generated/instructions/openPositionV2.d.ts.map +1 -0
- package/lib/generated/instructions/openPositionWithToken22Nft.d.ts +201 -0
- package/lib/generated/instructions/openPositionWithToken22Nft.d.ts.map +1 -0
- package/lib/generated/instructions/setRewardParams.d.ts +92 -0
- package/lib/generated/instructions/setRewardParams.d.ts.map +1 -0
- package/lib/generated/instructions/swap.d.ts +95 -0
- package/lib/generated/instructions/swap.d.ts.map +1 -0
- package/lib/generated/instructions/swapRouterBaseIn.d.ts +71 -0
- package/lib/generated/instructions/swapRouterBaseIn.d.ts.map +1 -0
- package/lib/generated/instructions/swapV2.d.ts +112 -0
- package/lib/generated/instructions/swapV2.d.ts.map +1 -0
- package/lib/generated/instructions/transferRewardOwner.d.ts +46 -0
- package/lib/generated/instructions/transferRewardOwner.d.ts.map +1 -0
- package/lib/generated/instructions/updateAmmConfig.d.ts +51 -0
- package/lib/generated/instructions/updateAmmConfig.d.ts.map +1 -0
- package/lib/generated/instructions/updateOperationAccount.d.ts +66 -0
- package/lib/generated/instructions/updateOperationAccount.d.ts.map +1 -0
- package/lib/generated/instructions/updatePoolStatus.d.ts +44 -0
- package/lib/generated/instructions/updatePoolStatus.d.ts.map +1 -0
- package/lib/generated/instructions/updateRewardInfos.d.ts +39 -0
- package/lib/generated/instructions/updateRewardInfos.d.ts.map +1 -0
- package/lib/generated/programs/ammV3.d.ts +109 -0
- package/lib/generated/programs/ammV3.d.ts.map +1 -0
- package/lib/generated/programs/index.d.ts +9 -0
- package/lib/generated/programs/index.d.ts.map +1 -0
- package/lib/generated/shared/index.d.ts +50 -0
- package/lib/generated/shared/index.d.ts.map +1 -0
- package/lib/generated/types/collectPersonalFeeEvent.d.ts +37 -0
- package/lib/generated/types/collectPersonalFeeEvent.d.ts.map +1 -0
- package/lib/generated/types/collectProtocolFeeEvent.d.ts +37 -0
- package/lib/generated/types/collectProtocolFeeEvent.d.ts.map +1 -0
- package/lib/generated/types/configChangeEvent.d.ts +23 -0
- package/lib/generated/types/configChangeEvent.d.ts.map +1 -0
- package/lib/generated/types/createPersonalPositionEvent.d.ts +57 -0
- package/lib/generated/types/createPersonalPositionEvent.d.ts.map +1 -0
- package/lib/generated/types/decreaseLiquidityEvent.d.ts +51 -0
- package/lib/generated/types/decreaseLiquidityEvent.d.ts.map +1 -0
- package/lib/generated/types/increaseLiquidityEvent.d.ts +41 -0
- package/lib/generated/types/increaseLiquidityEvent.d.ts.map +1 -0
- package/lib/generated/types/index.d.ts +23 -0
- package/lib/generated/types/index.d.ts.map +1 -0
- package/lib/generated/types/liquidityCalculateEvent.d.ts +51 -0
- package/lib/generated/types/liquidityCalculateEvent.d.ts.map +1 -0
- package/lib/generated/types/liquidityChangeEvent.d.ts +41 -0
- package/lib/generated/types/liquidityChangeEvent.d.ts.map +1 -0
- package/lib/generated/types/observation.d.ts +29 -0
- package/lib/generated/types/observation.d.ts.map +1 -0
- package/lib/generated/types/poolCreatedEvent.d.ts +52 -0
- package/lib/generated/types/poolCreatedEvent.d.ts.map +1 -0
- package/lib/generated/types/positionRewardInfo.d.ts +20 -0
- package/lib/generated/types/positionRewardInfo.d.ts.map +1 -0
- package/lib/generated/types/rewardInfo.d.ts +66 -0
- package/lib/generated/types/rewardInfo.d.ts.map +1 -0
- package/lib/generated/types/swapEvent.d.ts +77 -0
- package/lib/generated/types/swapEvent.d.ts.map +1 -0
- package/lib/generated/types/tickState.d.ts +42 -0
- package/lib/generated/types/tickState.d.ts.map +1 -0
- package/lib/generated/types/updateRewardInfosEvent.d.ts +21 -0
- package/lib/generated/types/updateRewardInfosEvent.d.ts.map +1 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +9404 -0
- package/lib/index.mjs +10038 -0
- package/lib/pool-manager.d.ts +84 -0
- package/lib/pool-manager.d.ts.map +1 -0
- package/lib/position-manager.d.ts +127 -0
- package/lib/position-manager.d.ts.map +1 -0
- package/lib/rewards.d.ts +7 -0
- package/lib/rewards.d.ts.map +1 -0
- package/lib/swap.d.ts +6 -0
- package/lib/swap.d.ts.map +1 -0
- package/lib/types.d.ts +302 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/utils/index.d.ts +79 -0
- package/lib/utils/index.d.ts.map +1 -0
- package/lib/utils/math.d.ts +51 -0
- package/lib/utils/math.d.ts.map +1 -0
- package/lib/utils/pda.d.ts +80 -0
- package/lib/utils/pda.d.ts.map +1 -0
- package/lib/utils/pool.d.ts +171 -0
- package/lib/utils/pool.d.ts.map +1 -0
- package/lib/utils/tick.d.ts +172 -0
- package/lib/utils/tick.d.ts.map +1 -0
- package/lib/utils/tickQuery.d.ts +13 -0
- package/lib/utils/tickQuery.d.ts.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# @stabbleorg/clmm-sdk
|
|
2
|
+
|
|
3
|
+
A comprehensive TypeScript SDK for interacting with the Stabble Concentrated Liquidity Market Maker (CLMM) protocol on Solana.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🏗️ **Built on @solana/kit** - Modern, type-safe Solana interactions
|
|
8
|
+
- 🎯 **Concentrated Liquidity** - Full support for CLMM functionality
|
|
9
|
+
- 💧 **Pool Management** - Create, configure, and query liquidity pools
|
|
10
|
+
- 📍 **Position Management** - NFT-based position lifecycle management
|
|
11
|
+
- 💱 **Swap Operations** - Execute swaps with slippage protection
|
|
12
|
+
- 🎁 **Rewards System** - Manage liquidity mining rewards
|
|
13
|
+
- 🧮 **Mathematical Utilities** - CLMM calculations and price conversions
|
|
14
|
+
- 📝 **Type Safety** - Full TypeScript support with comprehensive types
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @stabbleorg/clmm-sdk @solana/kit
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { createRpc } from '@solana/kit';
|
|
26
|
+
import { ClmmSdk } from '@stabbleorg/clmm-sdk';
|
|
27
|
+
|
|
28
|
+
// Initialize the SDK
|
|
29
|
+
const rpc = createRpc('https://api.mainnet-beta.solana.com');
|
|
30
|
+
const sdk = new ClmmSdk({ rpc });
|
|
31
|
+
|
|
32
|
+
// Get pool information
|
|
33
|
+
const poolAddress = 'YOUR_POOL_ADDRESS';
|
|
34
|
+
const pool = await sdk.pools.getPool(poolAddress);
|
|
35
|
+
console.log(`Current price: ${pool?.currentPrice}`);
|
|
36
|
+
|
|
37
|
+
// Execute a swap
|
|
38
|
+
const quote = await sdk.swap.getSwapQuote(poolAddress, {
|
|
39
|
+
tokenIn: 'So11111111111111111111111111111111111111112', // SOL
|
|
40
|
+
tokenOut: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
|
|
41
|
+
amountIn: 1000000000n, // 1 SOL
|
|
42
|
+
slippageTolerance: 0.01, // 1%
|
|
43
|
+
wallet: userWallet
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(`Expected output: ${quote.amountOut}`);
|
|
47
|
+
console.log(`Price impact: ${quote.priceImpact * 100}%`);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Core Concepts
|
|
51
|
+
|
|
52
|
+
### Pools
|
|
53
|
+
|
|
54
|
+
Liquidity pools are the foundation of the CLMM protocol. Each pool represents a trading pair with concentrated liquidity.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Find pools for a token pair
|
|
58
|
+
const pools = await sdk.pools.getPoolsForTokenPair(tokenA, tokenB);
|
|
59
|
+
|
|
60
|
+
// Create a new pool
|
|
61
|
+
const createPoolTx = await sdk.pools.createPool({
|
|
62
|
+
tokenA: 'TOKEN_A_MINT',
|
|
63
|
+
tokenB: 'TOKEN_B_MINT',
|
|
64
|
+
fee: 3000, // 0.3%
|
|
65
|
+
initialPrice: sqrtPriceX64,
|
|
66
|
+
creator: creatorWallet
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Positions
|
|
71
|
+
|
|
72
|
+
Positions are NFT-based liquidity provisions within specific price ranges.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Open a new position
|
|
76
|
+
const { instruction: openTx, positionMint } = await sdk.positions.openPosition({
|
|
77
|
+
poolAddress,
|
|
78
|
+
tickLower: -1000,
|
|
79
|
+
tickUpper: 1000,
|
|
80
|
+
amountA: 1000000n,
|
|
81
|
+
amountB: 1000000n,
|
|
82
|
+
minAmountA: 950000n,
|
|
83
|
+
minAmountB: 950000n,
|
|
84
|
+
wallet: userWallet
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Increase liquidity
|
|
88
|
+
const increaseTx = await sdk.positions.increaseLiquidity({
|
|
89
|
+
poolAddress,
|
|
90
|
+
positionMint,
|
|
91
|
+
tickLower: -1000,
|
|
92
|
+
tickUpper: 1000,
|
|
93
|
+
amountA: 500000n,
|
|
94
|
+
amountB: 500000n,
|
|
95
|
+
minAmountA: 475000n,
|
|
96
|
+
minAmountB: 475000n,
|
|
97
|
+
wallet: userWallet
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Swaps
|
|
102
|
+
|
|
103
|
+
Execute token swaps with slippage protection and price impact calculation.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Get swap quote
|
|
107
|
+
const quote = await sdk.swap.getSwapQuote(poolAddress, {
|
|
108
|
+
tokenIn: 'INPUT_TOKEN_MINT',
|
|
109
|
+
tokenOut: 'OUTPUT_TOKEN_MINT',
|
|
110
|
+
amountIn: 1000000n,
|
|
111
|
+
slippageTolerance: 0.01,
|
|
112
|
+
wallet: userWallet
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Execute swap
|
|
116
|
+
const swapTx = await sdk.swap.executeSwap(poolAddress, {
|
|
117
|
+
tokenIn: 'INPUT_TOKEN_MINT',
|
|
118
|
+
tokenOut: 'OUTPUT_TOKEN_MINT',
|
|
119
|
+
amountIn: 1000000n,
|
|
120
|
+
slippageTolerance: 0.01,
|
|
121
|
+
wallet: userWallet
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Rewards
|
|
126
|
+
|
|
127
|
+
Manage liquidity mining rewards and fee collection.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Initialize rewards for a pool
|
|
131
|
+
const initRewardTx = await sdk.rewards.initializeReward(poolAddress, {
|
|
132
|
+
rewardMint: 'REWARD_TOKEN_MINT',
|
|
133
|
+
rewardVault: 'REWARD_VAULT_ADDRESS',
|
|
134
|
+
authority: authorityWallet,
|
|
135
|
+
emissionsPerSecondX64: emissionsRate,
|
|
136
|
+
openTime: BigInt(Date.now() / 1000),
|
|
137
|
+
endTime: BigInt(Date.now() / 1000 + 86400 * 30) // 30 days
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Collect rewards from position
|
|
141
|
+
const collectTx = await sdk.rewards.collectRewards({
|
|
142
|
+
positionMint: 'POSITION_NFT_MINT',
|
|
143
|
+
owner: userWallet,
|
|
144
|
+
rewardIndex: 0,
|
|
145
|
+
recipientTokenAccount: 'USER_REWARD_TOKEN_ACCOUNT'
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Mathematical Utilities
|
|
150
|
+
|
|
151
|
+
The SDK provides comprehensive mathematical utilities for CLMM calculations:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { MathUtils } from '@stabbleorg/clmm-sdk';
|
|
155
|
+
|
|
156
|
+
// Convert between ticks and prices
|
|
157
|
+
const tick = MathUtils.sqrtPriceX64ToTick(sqrtPriceX64);
|
|
158
|
+
const sqrtPrice = MathUtils.tickToSqrtPriceX64(tick);
|
|
159
|
+
|
|
160
|
+
// Calculate human-readable price
|
|
161
|
+
const price = MathUtils.sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB);
|
|
162
|
+
|
|
163
|
+
// Calculate liquidity from token amounts
|
|
164
|
+
const liquidity = MathUtils.getLiquidityFromAmounts(
|
|
165
|
+
amount0, amount1, tickLower, tickUpper, tickCurrent
|
|
166
|
+
);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Error Handling
|
|
170
|
+
|
|
171
|
+
The SDK provides comprehensive error handling with specific error codes:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { ClmmError, ClmmErrorCode } from '@stabbleorg/clmm-sdk';
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const pool = await sdk.pools.getPool(poolAddress);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof ClmmError) {
|
|
180
|
+
switch (error.code) {
|
|
181
|
+
case ClmmErrorCode.POOL_NOT_FOUND:
|
|
182
|
+
console.log('Pool does not exist');
|
|
183
|
+
break;
|
|
184
|
+
case ClmmErrorCode.INSUFFICIENT_LIQUIDITY:
|
|
185
|
+
console.log('Not enough liquidity for this trade');
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
console.log(`CLMM Error: ${error.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Configuration
|
|
195
|
+
|
|
196
|
+
Configure the SDK with custom RPC endpoints and options:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const sdk = new ClmmSdk({
|
|
200
|
+
rpc: createRpc('https://your-rpc-endpoint.com'),
|
|
201
|
+
commitment: 'confirmed',
|
|
202
|
+
programAddress: 'CUSTOM_PROGRAM_ADDRESS' // Optional override
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Advanced Usage
|
|
207
|
+
|
|
208
|
+
### Direct Access to Managers
|
|
209
|
+
|
|
210
|
+
For more control, you can use the individual managers directly:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import { PoolManager, PositionManager } from '@stabbleorg/clmm-sdk';
|
|
214
|
+
|
|
215
|
+
const poolManager = new PoolManager({ rpc });
|
|
216
|
+
const positionManager = new PositionManager({ rpc });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Generated Code Access
|
|
220
|
+
|
|
221
|
+
Access the underlying generated code for advanced use cases:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { generated } from '@stabbleorg/clmm-sdk';
|
|
225
|
+
|
|
226
|
+
// Direct access to instruction builders
|
|
227
|
+
const swapIx = generated.swap({
|
|
228
|
+
// ... accounts and parameters
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Development
|
|
233
|
+
|
|
234
|
+
### Building from Source
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
git clone https://github.com/stabbleorg/clmm-sdk
|
|
238
|
+
cd clmm-sdk
|
|
239
|
+
npm install
|
|
240
|
+
npm run build
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Running Tests
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npm test
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Generating Documentation
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm run docs
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
|
|
262
|
+
|
|
263
|
+
## Support
|
|
264
|
+
|
|
265
|
+
- Documentation: [https://docs.stabble.org](https://docs.stabble.org)
|
|
266
|
+
- GitHub Issues: [https://github.com/stabbleorg/clmm-sdk/issues](https://github.com/stabbleorg/clmm-sdk/issues)
|
|
267
|
+
- Discord: [https://discord.gg/stabble](https://discord.gg/stabble)
|