Skip to content

Commit 7373909

Browse files
committed
Feat: Gamma support for hummingbot with oracle based swaps (#1)
* Add connector and routes for gfx gamma normal swaps * Swap with oracle * Update goosefx-amm-sdk
1 parent eef2ede commit 7373909

16 files changed

Lines changed: 1419 additions & 1 deletion

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"fastify": "^4.29.0",
7474
"fastify-type-provider-zod": "^2.1.0",
7575
"fs-extra": "^10.1.0",
76+
"goosefx-amm-sdk": "^2.0.0",
7677
"handle": "link:@oclif/errors/handle",
7778
"js-yaml": "^4.1.0",
7879
"level": "^8.0.1",

pnpm-lock.yaml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { ethereumRoutes } from './chains/ethereum/ethereum.routes';
1717
import { solanaRoutes } from './chains/solana/solana.routes';
1818
import { configRoutes } from './config/config.routes';
1919
import { connectorsRoutes } from './connectors/connector.routes';
20+
import { gammaRoutes } from './connectors/gamma/gamma.routes';
2021
import { jupiterRoutes } from './connectors/jupiter/jupiter.routes';
2122
import { meteoraRoutes } from './connectors/meteora/meteora.routes';
2223
import { raydiumRoutes } from './connectors/raydium/raydium.routes';
@@ -90,6 +91,9 @@ const swaggerOptions = {
9091
name: 'uniswap/clmm',
9192
description: 'Uniswap V3 pool connector (Ethereum)',
9293
},
94+
{ name: 'gamma/amm',
95+
description: 'Gamma AMM connector endpoints',
96+
}
9397
],
9498
components: {
9599
parameters: {
@@ -206,6 +210,7 @@ const configureGatewayServer = () => {
206210
// Raydium routes
207211
app.register(raydiumRoutes.clmm, { prefix: '/connectors/raydium/clmm' });
208212
app.register(raydiumRoutes.amm, { prefix: '/connectors/raydium/amm' });
213+
app.register(gammaRoutes.amm, { prefix: '/connectors/gamma/amm' });
209214

210215
app.register(uniswapRoutes, { prefix: '/connectors/uniswap' });
211216

src/connectors/connector.routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FastifyPluginAsync } from 'fastify';
33

44
import { logger } from '../services/logger';
55

6+
import { GammaConfig } from './gamma/gamma.config';
67
import { JupiterConfig } from './jupiter/jupiter.config';
78
import { MeteoraConfig } from './meteora/meteora.config';
89
import { RaydiumConfig } from './raydium/raydium.config';
@@ -46,6 +47,12 @@ export const connectorsRoutes: FastifyPluginAsync = async (fastify) => {
4647
logger.info('Getting available DEX connectors and networks');
4748

4849
const connectors = [
50+
{
51+
name: 'gamma/amm',
52+
trading_types: ['amm'],
53+
chain: GammaConfig.chain,
54+
networks: GammaConfig.networks
55+
},
4956
{
5057
name: 'jupiter',
5158
trading_types: ['swap'],
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { FastifyPluginAsync, FastifyInstance } from 'fastify'
2+
import { Gamma } from '../gamma'
3+
import { Solana, BASE_FEE } from '../../../chains/solana/solana'
4+
import { logger } from '../../../services/logger'
5+
import {
6+
AddLiquidityRequest,
7+
AddLiquidityResponse,
8+
AddLiquidityRequestType,
9+
AddLiquidityResponseType,
10+
QuoteLiquidityResponseType,
11+
} from '../../../schemas/amm-schema'
12+
import { Percent, PoolInfo, PoolKeys, TxVersion } from 'goosefx-amm-sdk'
13+
import { quoteLiquidity } from './quoteLiquidity'
14+
import Decimal from 'decimal.js'
15+
import BN from 'bn.js'
16+
import { VersionedTransaction, Transaction } from '@solana/web3.js'
17+
18+
async function createAddLiquidityTransaction(
19+
gamma: Gamma,
20+
poolInfo: PoolInfo,
21+
poolKeys: PoolKeys,
22+
baseTokenAmountAdded: number,
23+
quoteTokenAmountAdded: number,
24+
baseLimited: boolean,
25+
slippage: Percent,
26+
computeBudgetConfig: { units: number; microLamports: number }
27+
): Promise<VersionedTransaction | Transaction> {
28+
const inputAmount = new BN(
29+
new Decimal(baseLimited ? baseTokenAmountAdded : quoteTokenAmountAdded)
30+
.mul(10 ** (baseLimited ? poolInfo.mintA.decimals : poolInfo.mintB.decimals))
31+
.toFixed(0)
32+
);
33+
const response = await gamma.client.cpmm.addLiquidity({
34+
poolInfo: poolInfo,
35+
poolKeys: poolKeys,
36+
inputAmount,
37+
slippage,
38+
baseSpecified: baseLimited,
39+
txVersion: TxVersion.V0,
40+
computeBudgetConfig,
41+
})
42+
return response.transaction
43+
}
44+
45+
async function addLiquidity(
46+
_fastify: FastifyInstance,
47+
network: string,
48+
walletAddress: string,
49+
poolAddress: string,
50+
baseTokenAmount: number,
51+
quoteTokenAmount: number,
52+
slippagePct?: number
53+
): Promise<AddLiquidityResponseType> {
54+
const solana = await Solana.getInstance(network)
55+
const gamma = await Gamma.getInstance(network)
56+
const wallet = await solana.getWallet(walletAddress);
57+
58+
const { poolInfo, poolKeys } = await gamma.client.cpmm.getPoolInfoFromRpc(poolAddress)
59+
60+
const { baseLimited, baseTokenAmountMax, quoteTokenAmountMax } = await quoteLiquidity(
61+
_fastify,
62+
network,
63+
poolAddress,
64+
baseTokenAmount,
65+
quoteTokenAmount,
66+
slippagePct
67+
) as QuoteLiquidityResponseType;
68+
69+
const baseTokenAmountAdded = baseLimited ? baseTokenAmount : baseTokenAmountMax;
70+
const quoteTokenAmountAdded = baseLimited ? quoteTokenAmount : quoteTokenAmountMax;
71+
72+
logger.info(`Adding liquidity to Gamma...`);
73+
const COMPUTE_UNITS = 600000
74+
const slippage = new Percent(
75+
Math.floor(((slippagePct === 0 ? 0 : slippagePct || gamma.getSlippagePct('amm')) * 100) / 10000)
76+
);
77+
78+
let currentPriorityFee = (await solana.estimateGas() * 1e9) - BASE_FEE
79+
while (currentPriorityFee <= solana.config.maxPriorityFee * 1e9) {
80+
const priorityFeePerCU = Math.floor(currentPriorityFee * 1e6 / COMPUTE_UNITS)
81+
82+
const transaction = await createAddLiquidityTransaction(
83+
gamma,
84+
poolInfo,
85+
poolKeys,
86+
baseTokenAmountAdded,
87+
quoteTokenAmountAdded,
88+
baseLimited,
89+
slippage,
90+
{
91+
units: COMPUTE_UNITS,
92+
microLamports: priorityFeePerCU,
93+
}
94+
)
95+
console.log('transaction', transaction);
96+
97+
if (transaction instanceof VersionedTransaction) {
98+
(transaction as VersionedTransaction).sign([wallet]);
99+
} else {
100+
const txAsTransaction = transaction as Transaction;
101+
const { blockhash, lastValidBlockHeight } = await solana.connection.getLatestBlockhash();
102+
txAsTransaction.recentBlockhash = blockhash;
103+
txAsTransaction.lastValidBlockHeight = lastValidBlockHeight;
104+
txAsTransaction.feePayer = wallet.publicKey;
105+
txAsTransaction.sign(wallet);
106+
}
107+
108+
await solana.simulateTransaction(transaction);
109+
110+
console.log('signed transaction', transaction);
111+
112+
const { confirmed, signature, txData } = await solana.sendAndConfirmRawTransaction(transaction);
113+
if (confirmed && txData) {
114+
const { baseTokenBalanceChange, quoteTokenBalanceChange } =
115+
await solana.extractPairBalanceChangesAndFee(
116+
signature,
117+
await solana.getToken(poolInfo.mintA.address),
118+
await solana.getToken(poolInfo.mintB.address),
119+
wallet.publicKey.toBase58()
120+
);
121+
return {
122+
signature,
123+
fee: txData.meta.fee / 1e9,
124+
baseTokenAmountAdded: baseTokenBalanceChange,
125+
quoteTokenAmountAdded: quoteTokenBalanceChange,
126+
}
127+
}
128+
currentPriorityFee = currentPriorityFee * solana.config.priorityFeeMultiplier
129+
logger.info(`Increasing max priority fee to ${(currentPriorityFee / 1e9).toFixed(6)} SOL`);
130+
}
131+
throw new Error(`Add liquidity failed after reaching max priority fee of ${(solana.config.maxPriorityFee / 1e9).toFixed(6)} SOL`);
132+
}
133+
134+
export const addLiquidityRoute: FastifyPluginAsync = async (fastify) => {
135+
// Get first wallet address for example
136+
const solana = await Solana.getInstance('mainnet-beta');
137+
let firstWalletAddress = '<solana-wallet-address>';
138+
139+
const foundWallet = await solana.getFirstWalletAddress();
140+
if (foundWallet) {
141+
firstWalletAddress = foundWallet;
142+
} else {
143+
logger.debug('No wallets found for examples in schema');
144+
}
145+
146+
// Update schema example
147+
AddLiquidityRequest.properties.walletAddress.examples = [firstWalletAddress];
148+
149+
fastify.post<{
150+
Body: AddLiquidityRequestType
151+
Reply: AddLiquidityResponseType
152+
}>(
153+
'/add-liquidity',
154+
{
155+
schema: {
156+
description: 'Add liquidity to a Gamma AMM/CPMM pool',
157+
tags: ['gamma/amm'],
158+
body: {
159+
...AddLiquidityRequest,
160+
properties: {
161+
...AddLiquidityRequest.properties,
162+
network: { type: 'string', default: 'mainnet-beta' },
163+
poolAddress: { type: 'string', examples: ['Hjm1F98vgVdN7Y9L46KLqcZZWyTKS9tj9ybYKJcXnSng'] }, // SOL-USDC
164+
slippagePct: { type: 'number', examples: [1] },
165+
baseTokenAmount: { type: 'number', examples: [1] },
166+
quoteTokenAmount: { type: 'number', examples: [1] },
167+
}
168+
},
169+
response: {
170+
200: AddLiquidityResponse
171+
},
172+
}
173+
},
174+
async (request) => {
175+
try {
176+
const {
177+
network,
178+
walletAddress,
179+
poolAddress,
180+
baseTokenAmount,
181+
quoteTokenAmount,
182+
slippagePct
183+
} = request.body
184+
185+
return await addLiquidity(
186+
fastify,
187+
network || 'mainnet-beta',
188+
walletAddress,
189+
poolAddress,
190+
baseTokenAmount,
191+
quoteTokenAmount,
192+
slippagePct
193+
)
194+
} catch (e) {
195+
logger.error(e)
196+
throw fastify.httpErrors.internalServerError('Internal server error')
197+
}
198+
}
199+
)
200+
}
201+
202+
export default addLiquidityRoute
203+

0 commit comments

Comments
 (0)