Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
655b24b
feat(solana): implement Phase 2 - real-time wallet balance monitoring…
fengtality Nov 2, 2025
71a686e
feat(meteora): implement Phase 3 - real-time pool monitoring via WebS…
fengtality Nov 2, 2025
b4af80d
Merge branch 'development' into fix/dynamic-wrap-token-lookup
rapcmia Nov 3, 2025
433c86f
feat(solana): auto-subscribe to all wallets on Gateway startup
fengtality Nov 5, 2025
6fc9148
fix(solana): trigger wallet auto-subscription on Gateway startup
fengtality Nov 5, 2025
ccc6fb5
fix(solana): correct getBalances() return type in auto-subscription
fengtality Nov 5, 2025
6e1fb17
Merge branch 'development' into feat/helius-websocket-monitoring
fengtality Nov 5, 2025
51fec1e
feat(solana): implement provider-agnostic balance caching with WebSoc…
fengtality Nov 6, 2025
955bfe4
feat(solana): add position and pool tracking to cache system
fengtality Nov 6, 2025
3e142c6
feat(solana): add pool and position tracking at startup
fengtality Nov 6, 2025
7ecc069
style(solana): use distinct emojis for tracking types
fengtality Nov 6, 2025
c714eb9
feat(pancakeswap-sol): add cache-first pool-info endpoint
fengtality Nov 6, 2025
6ae7697
feat(pancakeswap-sol): add cache-first position-info endpoint
fengtality Nov 6, 2025
4ce29ba
fix(solana): prevent pre-loading incomplete pool data into cache
fengtality Nov 6, 2025
fd7f76f
feat(solana): add rate limiting for pool cache pre-loading at startup
fengtality Nov 6, 2025
e7a2e1c
fix(solana): disable pool pre-loading to prevent recursive init loop
fengtality Nov 6, 2025
4136af5
fix(solana): re-enable pool pre-loading with recursive loop prevention
fengtality Nov 6, 2025
116cc0e
fix(solana): fix singleton pattern to prevent duplicate instances
fengtality Nov 6, 2025
87a8d9a
feat(pancakeswap-sol): add cache-first logic for positions-owned endp…
fengtality Nov 6, 2025
cfec534
feat(solana): add periodic refresh for position and pool caches
fengtality Nov 6, 2025
31ec50f
refactor(solana): simplify pool refresh by storing poolType in cache
fengtality Nov 6, 2025
822865c
feat(cache): implement unified cache-first logic across all Solana CL…
fengtality Nov 6, 2025
d91de14
feat(tokens): add CoinGecko/GeckoTerminal integration for top pools
fengtality Nov 6, 2025
06a3ff7
fix(solana): reduce log noise from pool refresh try-each-connector pa…
fengtality Nov 6, 2025
5ad2bfa
fix(solana): improve pool refresh robustness for multi-connector support
fengtality Nov 6, 2025
5dfeb2d
feat(solana): implement background position refresh across all connec…
fengtality Nov 6, 2025
83b0e2b
refactor(positions): consolidate into single trackPositions method
fengtality Nov 6, 2025
4d91903
refactor(positions): use connector:clmm:address cache key format
fengtality Nov 6, 2025
76d0478
refactor(positions): simplify tracking with structured cache keys
fengtality Nov 6, 2025
1ae6e63
refactor(position-cache): use connector:clmm:address cache key format
fengtality Nov 6, 2025
d304efd
feat(tokens): add GET /tokens/find/{address} endpoint
fengtality Nov 6, 2025
f495eed
feat(tokens): add connector and type filtering to top-pools endpoint
fengtality Nov 6, 2025
28df856
refactor(tokens): default type to clmm in top-pools endpoint
fengtality Nov 6, 2025
18e6282
fix(tokens): correct PancakeSwap Solana DEX ID mapping
fengtality Nov 6, 2025
495fff4
refactor(tokens): remove orca mappings from DEX connector dictionary
fengtality Nov 6, 2025
d671793
fix(tokens): verify and clean up DEX connector mapping
fengtality Nov 6, 2025
2663d05
feat(tokens): fetch up to 10 pages for top-pools endpoint
fengtality Nov 6, 2025
84a7c54
feat(pools,tokens): standardize chainNetwork format and improve consi…
fengtality Nov 6, 2025
a1c5b01
feat(cache): integrate pool, token, and position cache with find-save…
fengtality Nov 6, 2025
fa7fe83
fix(security): add token address validation to prevent SSRF in CoinGe…
fengtality Nov 6, 2025
98e7b3e
refactor(security): simplify token address validation to only support…
fengtality Nov 6, 2025
02570b1
feat(pools): add connector registry system and APR calculation
fengtality Nov 7, 2025
f5cabd9
feat(pools): add APR calculation, market data, and connector registry…
fengtality Nov 7, 2025
094fb78
fix: removed extra docs
fengtality Nov 7, 2025
ca65a19
fix: fix all failing tests and configuration validation
fengtality Nov 7, 2025
a13156e
fix(pancakeswap-sol): correctly read sqrtPriceX64 as u128 (16 bytes)
fengtality Nov 7, 2025
266235e
fix(tokens): correct MET token name and decimals
fengtality Nov 7, 2025
205060a
fix(helius): prevent duplicate WebSocket account subscriptions
fengtality Nov 7, 2025
08966c8
fix(pancakeswap-sol): prevent double slippage application in open-pos…
fengtality Nov 7, 2025
21ff3a1
feat(jupiter): enforce ExactOut for BUY swaps and pass through API er…
fengtality Nov 7, 2025
81c66e2
fix(meteora): apply slippage correctly to respect user max amounts
fengtality Nov 8, 2025
5633ec1
refactor(pancakeswap-sol): move slippage logic to quote-swap
fengtality Nov 8, 2025
32392ce
feat(pancakeswap-sol): add slippagePct parameter to quote-position
fengtality Nov 8, 2025
40da157
fix(pancakeswap-sol): use quote max amounts directly in openPosition
fengtality Nov 8, 2025
cc0b30c
feat(clmm): parse pool and position state for fee/reward data
fengtality Nov 9, 2025
aa90a2d
fix(tests): update tests for renamed routes
fengtality Nov 9, 2025
6e10d78
fix(pancakeswap-sol): use configured slippagePct for all operations
fengtality Nov 9, 2025
a28b7c4
Merge branch 'development' into feat/helius-websocket-monitoring
rapcmia Nov 10, 2025
9be3fca
Merge development into feat/helius-websocket-monitoring
fengtality Nov 12, 2025
16f6b0d
feat(config): refactor chain configuration to dynamic file-based loading
fengtality Nov 12, 2025
afe003e
feat(pools): persist geckoData in pool files and improve error handling
fengtality Nov 12, 2025
70c8b3e
fix(solana): improve base64 token account error handling
fengtality Nov 12, 2025
50141ea
feat: add wrap/unwrap endpoints for Solana and hide subscription rout…
fengtality Nov 12, 2025
abf48af
fix: improve wrap/unwrap test mocking to match other route tests
fengtality Nov 12, 2025
b1291b3
test: remove failing wrap/unwrap route tests
fengtality Nov 12, 2025
9d5a011
fix(solana): use base64 encoding for token accounts to support Helius
fengtality Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
592 changes: 592 additions & 0 deletions docs/websocket-monitoring-guide.md

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions scripts/test-auto-subscription.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash

# Test auto-subscription to Solana wallets on Gateway startup
# This script verifies that wallets in conf/wallets/solana are automatically monitored

echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Testing Auto-Subscription to Solana Wallets ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""

# Check if conf/wallets/solana exists and has wallets
if [ ! -d "conf/wallets/solana" ]; then
echo "❌ Directory conf/wallets/solana does not exist"
echo " No wallets to auto-subscribe"
exit 1
fi

WALLET_COUNT=$(find conf/wallets/solana -name "*.json" -not -name "hardware-wallets.json" 2>/dev/null | wc -l | tr -d ' ')

if [ "$WALLET_COUNT" -eq 0 ]; then
echo "⚠️ No Solana wallets found in conf/wallets/solana"
echo " Auto-subscription will be skipped on startup"
echo ""
echo "To add a wallet, use:"
echo " curl -X POST http://localhost:15888/wallet/add \\"
echo " -H \"Content-Type: application/json\" \\"
echo " -d '{\"chain\":\"solana\",\"privateKey\":\"YOUR_PRIVATE_KEY\"}'"
exit 0
fi

echo "✅ Found $WALLET_COUNT Solana wallet(s):"
echo ""

# List wallet addresses
for wallet_file in conf/wallets/solana/*.json; do
if [[ "$wallet_file" != *"hardware-wallets.json" ]]; then
wallet_address=$(basename "$wallet_file" .json)
echo " 📔 $wallet_address"
fi
done

echo ""
echo "─────────────────────────────────────────────────────────────"
echo ""
echo "Starting Gateway with auto-subscription enabled..."
echo ""
echo "Expected log output:"
echo " 1. ✅ Helius WebSocket monitor successfully initialized"
echo " 2. Auto-subscribing to N Solana wallet(s)..."
echo " 3. [ADDRESS...] Initial balance: X.XXXX SOL, Y token(s)"
echo " 4. Subscribed to wallet balance updates for ADDRESS..."
echo " 5. ✅ Auto-subscribed to N/N Solana wallet(s)"
echo ""
echo "When a transaction occurs on any wallet, you'll see:"
echo " [ADDRESS...] Balance update at slot XXXXXX:"
echo " SOL: X.XXXX, Tokens: Y"
echo ""
echo "─────────────────────────────────────────────────────────────"
echo ""
echo "Press Ctrl+C to stop Gateway when ready..."
echo ""

# Start Gateway in dev mode
GATEWAY_PASSPHRASE=a START_SERVER=true DEV=true pnpm start
49 changes: 49 additions & 0 deletions scripts/test-sse-stream.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash

# Test Server-Sent Events streaming for WebSocket monitoring
# This script demonstrates how to consume real-time pool updates

echo "╔════════════════════════════════════════════════════════════╗"
echo "║ Testing Phase 3: Pool Monitoring via SSE ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""

# Check if server is running
if ! curl -s http://localhost:15888/chains 2>&1 | grep -q "solana"; then
echo "❌ Gateway server is not running on port 15888"
echo " Start it with: GATEWAY_PASSPHRASE=a START_SERVER=true DEV=true pnpm start"
exit 1
fi

echo "✅ Gateway server detected"
echo ""

# Pool address - SOL-USDC Meteora DLMM pool
POOL_ADDRESS="5E4sYT75xoHs41wWv7cUKzbe8kUE6wZVB3QjhKBp3jAH"

echo "Connecting to pool monitoring stream..."
echo "Pool: $POOL_ADDRESS"
echo ""
echo "Stream will show real-time pool updates as they occur."
echo "Press Ctrl+C to stop."
echo ""
echo "─────────────────────────────────────────────────────────────"
echo ""

# Stream pool updates
curl -N "http://localhost:15888/connectors/meteora/clmm/pool-info-stream?network=mainnet-beta&poolAddress=$POOL_ADDRESS" 2>/dev/null | while IFS= read -r line; do
# Skip empty lines and keepalive comments
if [[ "$line" =~ ^data:\ (.*)$ ]]; then
data="${BASH_REMATCH[1]}"

# Pretty print JSON with color if jq is available
if command -v jq &> /dev/null; then
echo "$data" | jq -C '.'
else
echo "$data" | python3 -m json.tool
fi

echo "─────────────────────────────────────────────────────────────"
echo ""
fi
done
162 changes: 162 additions & 0 deletions scripts/test-websocket-monitoring.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* Test script for WebSocket monitoring features
* Tests Phase 2 (wallet balance monitoring) and Phase 3 (pool monitoring)
*
* Usage:
* GATEWAY_PASSPHRASE=a START_SERVER=true DEV=true node dist/scripts/test-websocket-monitoring.js
*/

import { Solana } from '../chains/solana/solana';
import { Meteora } from '../connectors/meteora/meteora';
import { logger } from '../services/logger';

async function testWalletBalanceMonitoring() {
console.log('\n=== Testing Phase 2: Wallet Balance Monitoring ===\n');

try {
const solana = await Solana.getInstance('mainnet-beta');
const heliusService = solana.getHeliusService();

if (!heliusService?.isWebSocketConnected()) {
console.log('❌ WebSocket not available');
console.log(' Enable useWebSocketRPC in conf/rpc/helius.yml and configure Helius API key');
return;
}

// Example wallet - replace with your wallet address
const walletAddress = 'vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg'; // Random example wallet

console.log(`Subscribing to wallet: ${walletAddress}`);
console.log('Waiting for balance updates...\n');

const subscriptionId = await solana.subscribeToWalletBalance(walletAddress, (balances) => {
console.log(`[Wallet Update] Slot: ${balances.slot}`);
console.log(` SOL Balance: ${balances.sol.toFixed(4)} SOL`);
console.log(` Token Count: ${balances.tokens.length}`);

if (balances.tokens.length > 0) {
console.log(' Tokens:');
balances.tokens.slice(0, 5).forEach(token => {
console.log(` - ${token.symbol}: ${token.balance.toFixed(4)} (${token.address.slice(0, 8)}...)`);
});
if (balances.tokens.length > 5) {
console.log(` ... and ${balances.tokens.length - 5} more tokens`);
}
}
console.log('');
});

console.log(`✅ Subscribed (ID: ${subscriptionId})`);
console.log(' Balance updates will be logged in real-time');
console.log(' Press Ctrl+C to stop\n');

// Keep running for 2 minutes
await new Promise(resolve => setTimeout(resolve, 120000));

// Cleanup
const helius = solana.getHeliusService();
if (helius) {
await helius.unsubscribeFromAccount(subscriptionId);
console.log('Unsubscribed from wallet monitoring');
}

} catch (error: any) {
console.error('Error testing wallet monitoring:', error.message);
}
}

async function testPoolMonitoring() {
console.log('\n=== Testing Phase 3: Pool Monitoring ===\n');

try {
const meteora = await Meteora.getInstance('mainnet-beta');
const solana = await Solana.getInstance('mainnet-beta');
const heliusService = solana.getHeliusService();

if (!heliusService?.isWebSocketConnected()) {
console.log('❌ WebSocket not available');
console.log(' Enable useWebSocketRPC in conf/rpc/helius.yml and configure Helius API key');
return;
}

// Example SOL-USDC pool - replace with any Meteora pool address
const poolAddress = '5E4sYT75xoHs41wWv7cUKzbe8kUE6wZVB3QjhKBp3jAH'; // SOL-USDC pool

console.log(`Subscribing to pool: ${poolAddress}`);
console.log('Waiting for pool updates...\n');

const subscriptionId = await meteora.subscribeToPoolUpdates(poolAddress, (poolInfo) => {
console.log(`[Pool Update] Slot: ${poolInfo.slot}`);
console.log(` Active Bin ID: ${poolInfo.activeBinId}`);
console.log(` Price: ${poolInfo.price.toFixed(6)}`);
console.log(` Base Reserve: ${poolInfo.baseTokenAmount.toFixed(4)}`);
console.log(` Quote Reserve: ${poolInfo.quoteTokenAmount.toFixed(4)}`);
console.log(` Fee: ${poolInfo.feePct.toFixed(4)}%`);
console.log(` Bin Step: ${poolInfo.binStep}`);
console.log('');
});

console.log(`✅ Subscribed (ID: ${subscriptionId})`);
console.log(' Pool updates will be logged in real-time');
console.log(' Press Ctrl+C to stop\n');

// Keep running for 2 minutes
await new Promise(resolve => setTimeout(resolve, 120000));

// Cleanup
await meteora.unsubscribeFromPool(subscriptionId);
console.log('Unsubscribed from pool monitoring');

} catch (error: any) {
console.error('Error testing pool monitoring:', error.message);
}
}

async function testServerSentEvents() {
console.log('\n=== Testing Server-Sent Events (SSE) ===\n');
console.log('To test SSE streaming, use curl in another terminal:\n');
console.log('Example wallet balance streaming:');
console.log(' curl -X POST http://localhost:15888/chains/solana/subscribe-balances \\');
console.log(' -H "Content-Type: application/json" \\');
console.log(' -d \'{"network": "mainnet-beta", "address": "YOUR_WALLET_ADDRESS"}\'\n');

console.log('Example pool info streaming:');
console.log(' curl "http://localhost:15888/connectors/meteora/clmm/pool-info-stream?network=mainnet-beta&poolAddress=5E4sYT75xoHs41wWv7cUKzbe8kUE6wZVB3QjhKBp3jAH"\n');

console.log('The stream will continuously output JSON events as they occur.\n');
}

async function main() {
console.log('╔════════════════════════════════════════════════════════════╗');
console.log('║ WebSocket Monitoring Test Suite ║');
console.log('║ Phase 2: Wallet Balance Monitoring ║');
console.log('║ Phase 3: Pool State Monitoring ║');
console.log('╚════════════════════════════════════════════════════════════╝');

const args = process.argv.slice(2);
const testType = args[0] || 'all';

if (testType === 'wallet' || testType === 'all') {
await testWalletBalanceMonitoring();
}

if (testType === 'pool' || testType === 'all') {
await testPoolMonitoring();
}

if (testType === 'sse') {
testServerSentEvents();
// Keep running to allow SSE testing
console.log('Server running... Press Ctrl+C to stop');
await new Promise(() => {}); // Run forever
}

console.log('\n✅ Tests complete');
process.exit(0);
}

// Run tests
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});
Loading