feat: implement auto-pump mechanism to prevent pool drainage

This commit is contained in:
Face 2025-05-31 20:04:25 +03:00
parent d9f2836fb9
commit b8de80d8a9
8 changed files with 1727 additions and 29 deletions

View file

@ -0,0 +1,4 @@
ALTER TABLE "coin" ADD COLUMN "pump_fee_rate" numeric(10, 8) DEFAULT '0.00500000' NOT NULL;--> statement-breakpoint
ALTER TABLE "coin" ADD COLUMN "burn_rate" numeric(10, 8) DEFAULT '0.00100000' NOT NULL;--> statement-breakpoint
ALTER TABLE "transaction" ADD COLUMN "pump_fee_applied" numeric(30, 8) DEFAULT '0.00000000';--> statement-breakpoint
ALTER TABLE "transaction" ADD COLUMN "tokens_burned" numeric(30, 8) DEFAULT '0.00000000';

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,13 @@
"when": 1748700252762, "when": 1748700252762,
"tag": "0002_lush_guardian", "tag": "0002_lush_guardian",
"breakpoints": true "breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1748710560443,
"tag": "0003_adorable_leper_queen",
"breakpoints": true
} }
] ]
} }

View file

@ -34,6 +34,7 @@
: userHolding : userHolding
); );
let estimatedResult = $derived(calculateEstimate(numericAmount, type, currentPrice)); let estimatedResult = $derived(calculateEstimate(numericAmount, type, currentPrice));
let estimatedAutoPump = $derived(calculateAutoPumpEffects(numericAmount, type, coin));
let hasValidAmount = $derived(numericAmount > 0); let hasValidAmount = $derived(numericAmount > 0);
let userBalance = $derived($PORTFOLIO_DATA ? $PORTFOLIO_DATA.baseCurrencyBalance : 0); let userBalance = $derived($PORTFOLIO_DATA ? $PORTFOLIO_DATA.baseCurrencyBalance : 0);
let hasEnoughFunds = $derived( let hasEnoughFunds = $derived(
@ -64,6 +65,25 @@
} }
} }
function calculateAutoPumpEffects(amount: number, tradeType: 'BUY' | 'SELL', coinData: any) {
if (!amount) return { fee: 0, burn: 0 };
const pumpFeeRate = Number(coinData.pumpFeeRate || 0.005);
const burnRate = Number(coinData.burnRate || 0.001);
if (tradeType === 'BUY') {
const fee = amount * pumpFeeRate;
const estimatedTokens = calculateEstimate(amount, tradeType, currentPrice).result;
const burn = estimatedTokens * burnRate;
return { fee, burn };
} else {
const estimatedValue = calculateEstimate(amount, tradeType, currentPrice).result;
const fee = estimatedValue * pumpFeeRate;
const burn = amount * burnRate;
return { fee, burn };
}
}
function handleClose() { function handleClose() {
open = false; open = false;
amount = ''; amount = '';
@ -183,6 +203,20 @@
: `~$${estimatedResult.result.toFixed(6)}`} : `~$${estimatedResult.result.toFixed(6)}`}
</span> </span>
</div> </div>
{#if estimatedAutoPump.fee > 0 || estimatedAutoPump.burn > 0}
<div class="border-muted mt-2 border-t pt-2">
<div class="text-muted-foreground text-xs">
<div class="mb-1 flex items-center justify-between">
<span>🔥 Auto-pump fee (0.5%):</span>
<span>+${estimatedAutoPump.fee.toFixed(6)} to pool</span>
</div>
<div class="flex items-center justify-between">
<span>🔥 Token burn (0.1%):</span>
<span>-{estimatedAutoPump.burn.toFixed(6)} {coin.symbol}</span>
</div>
</div>
</div>
{/if}
<p class="text-muted-foreground mt-1 text-xs"> <p class="text-muted-foreground mt-1 text-xs">
AMM estimation - includes slippage from pool impact AMM estimation - includes slippage from pool impact
</p> </p>

View file

@ -83,6 +83,8 @@ export const coin = pgTable("coin", {
change24h: decimal("change_24h", { precision: 30, scale: 4 }).default("0.0000"), // Percentage change24h: decimal("change_24h", { precision: 30, scale: 4 }).default("0.0000"), // Percentage
poolCoinAmount: decimal("pool_coin_amount", { precision: 30, scale: 8 }).notNull().default("0.00000000"), poolCoinAmount: decimal("pool_coin_amount", { precision: 30, scale: 8 }).notNull().default("0.00000000"),
poolBaseCurrencyAmount: decimal("pool_base_currency_amount", { precision: 30, scale: 8, }).notNull().default("0.00000000"), poolBaseCurrencyAmount: decimal("pool_base_currency_amount", { precision: 30, scale: 8, }).notNull().default("0.00000000"),
pumpFeeRate: decimal("pump_fee_rate", { precision: 10, scale: 8 }).notNull().default("0.00500000"), // 0.5%
burnRate: decimal("burn_rate", { precision: 10, scale: 8 }).notNull().default("0.00100000"), // 0.1%
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
isListed: boolean("is_listed").default(true).notNull(), isListed: boolean("is_listed").default(true).notNull(),
@ -109,6 +111,8 @@ export const transaction = pgTable("transaction", {
quantity: decimal("quantity", { precision: 30, scale: 8 }).notNull(), quantity: decimal("quantity", { precision: 30, scale: 8 }).notNull(),
pricePerCoin: decimal("price_per_coin", { precision: 20, scale: 8 }).notNull(), pricePerCoin: decimal("price_per_coin", { precision: 20, scale: 8 }).notNull(),
totalBaseCurrencyAmount: decimal("total_base_currency_amount", { precision: 30, scale: 8 }).notNull(), totalBaseCurrencyAmount: decimal("total_base_currency_amount", { precision: 30, scale: 8 }).notNull(),
pumpFeeApplied: decimal("pump_fee_applied", { precision: 30, scale: 8 }).default("0.00000000"),
tokensBurned: decimal("tokens_burned", { precision: 30, scale: 8 }).default("0.00000000"),
timestamp: timestamp("timestamp", { withTimezone: true }).notNull().defaultNow(), timestamp: timestamp("timestamp", { withTimezone: true }).notNull().defaultNow(),
recipientUserId: integer('recipient_user_id').references(() => user.id, { onDelete: 'set null' }), recipientUserId: integer('recipient_user_id').references(() => user.id, { onDelete: 'set null' }),
senderUserId: integer('sender_user_id').references(() => user.id, { onDelete: 'set null' }), senderUserId: integer('sender_user_id').references(() => user.id, { onDelete: 'set null' }),

View file

@ -8,6 +8,8 @@ export interface CoinData {
change24h: number; change24h: number;
createdAt: string; createdAt: string;
creatorName: string | null; creatorName: string | null;
pumpFeeRate?: number;
burnRate?: number;
} }
export interface MarketFilters { export interface MarketFilters {

View file

@ -130,6 +130,8 @@ export async function GET({ params, url }) {
isListed: coin.isListed, isListed: coin.isListed,
createdAt: coin.createdAt, createdAt: coin.createdAt,
creatorId: coin.creatorId, creatorId: coin.creatorId,
pumpFeeRate: coin.pumpFeeRate,
burnRate: coin.burnRate,
creatorName: user.name, creatorName: user.name,
creatorUsername: user.username, creatorUsername: user.username,
creatorBio: user.bio, creatorBio: user.bio,
@ -184,7 +186,9 @@ export async function GET({ params, url }) {
poolCoinAmount: Number(coinData.poolCoinAmount), poolCoinAmount: Number(coinData.poolCoinAmount),
poolBaseCurrencyAmount: Number(coinData.poolBaseCurrencyAmount), poolBaseCurrencyAmount: Number(coinData.poolBaseCurrencyAmount),
circulatingSupply: Number(coinData.circulatingSupply), circulatingSupply: Number(coinData.circulatingSupply),
initialSupply: Number(coinData.initialSupply) initialSupply: Number(coinData.initialSupply),
pumpFeeRate: Number(coinData.pumpFeeRate),
burnRate: Number(coinData.burnRate)
}, },
candlestickData, candlestickData,
volumeData, volumeData,

View file

@ -42,6 +42,34 @@ async function calculate24hMetrics(coinId: number, currentPrice: number) {
return { change24h: Number(change24h.toFixed(4)), volume24h: Number(volume24h.toFixed(4)) }; return { change24h: Number(change24h.toFixed(4)), volume24h: Number(volume24h.toFixed(4)) };
} }
function calculateAutoPumpEffects(
coinData: any,
transactionValue: number,
tokensTraded: number
) {
const pumpFeeRate = Number(coinData.pumpFeeRate);
const burnRate = Number(coinData.burnRate);
const pumpFee = transactionValue * pumpFeeRate;
const tokensBurned = tokensTraded * burnRate;
// Ensure we don't burn more tokens than available in pool
const maxBurnableTokens = Number(coinData.poolCoinAmount) * 0.99; // Keep 1% minimum
const actualTokensBurned = Math.min(tokensBurned, maxBurnableTokens);
const newPoolBase = Number(coinData.poolBaseCurrencyAmount) + pumpFee;
const newPoolCoin = Number(coinData.poolCoinAmount) - actualTokensBurned;
const newCirculatingSupply = Number(coinData.circulatingSupply) - actualTokensBurned;
return {
pumpFee,
tokensBurned: actualTokensBurned,
newPoolBase,
newPoolCoin,
newCirculatingSupply
};
}
export async function POST({ params, request }) { export async function POST({ params, request }) {
const session = await auth.api.getSession({ const session = await auth.api.getSession({
headers: request.headers headers: request.headers
@ -127,6 +155,13 @@ export async function POST({ params, request }) {
throw error(400, 'Trade amount too small - would result in zero tokens'); throw error(400, 'Trade amount too small - would result in zero tokens');
} }
// Apply auto-pump effects
const autoPumpEffects = calculateAutoPumpEffects(coinData, amount, coinsBought);
const finalPoolBase = autoPumpEffects.newPoolBase;
const finalPoolCoin = autoPumpEffects.newPoolCoin;
const finalPrice = finalPoolBase / finalPoolCoin;
await tx.update(user) await tx.update(user)
.set({ .set({
baseCurrencyBalance: (userBalance - totalCost).toString(), baseCurrencyBalance: (userBalance - totalCost).toString(),
@ -168,22 +203,25 @@ export async function POST({ params, request }) {
type: 'BUY', type: 'BUY',
quantity: coinsBought.toString(), quantity: coinsBought.toString(),
pricePerCoin: (totalCost / coinsBought).toString(), pricePerCoin: (totalCost / coinsBought).toString(),
totalBaseCurrencyAmount: totalCost.toString() totalBaseCurrencyAmount: totalCost.toString(),
pumpFeeApplied: autoPumpEffects.pumpFee.toString(),
tokensBurned: autoPumpEffects.tokensBurned.toString()
}); });
await tx.insert(priceHistory).values({ await tx.insert(priceHistory).values({
coinId: coinData.id, coinId: coinData.id,
price: newPrice.toString() price: finalPrice.toString()
}); });
const metrics = await calculate24hMetrics(coinData.id, newPrice); const metrics = await calculate24hMetrics(coinData.id, finalPrice);
await tx.update(coin) await tx.update(coin)
.set({ .set({
currentPrice: newPrice.toString(), currentPrice: finalPrice.toString(),
marketCap: (Number(coinData.circulatingSupply) * newPrice).toString(), marketCap: (autoPumpEffects.newCirculatingSupply * finalPrice).toString(),
poolCoinAmount: newPoolCoin.toString(), poolCoinAmount: finalPoolCoin.toString(),
poolBaseCurrencyAmount: newPoolBaseCurrency.toString(), poolBaseCurrencyAmount: finalPoolBase.toString(),
circulatingSupply: autoPumpEffects.newCirculatingSupply.toString(),
change24h: metrics.change24h.toString(), change24h: metrics.change24h.toString(),
volume24h: metrics.volume24h.toString(), volume24h: metrics.volume24h.toString(),
updatedAt: new Date() updatedAt: new Date()
@ -191,12 +229,12 @@ export async function POST({ params, request }) {
.where(eq(coin.id, coinData.id)); .where(eq(coin.id, coinData.id));
const priceUpdateData = { const priceUpdateData = {
currentPrice: newPrice, currentPrice: finalPrice,
marketCap: Number(coinData.circulatingSupply) * newPrice, marketCap: autoPumpEffects.newCirculatingSupply * finalPrice,
change24h: metrics.change24h, change24h: metrics.change24h,
volume24h: metrics.volume24h, volume24h: metrics.volume24h,
poolCoinAmount: newPoolCoin, poolCoinAmount: finalPoolCoin,
poolBaseCurrencyAmount: newPoolBaseCurrency poolBaseCurrencyAmount: finalPoolBase
}; };
const tradeData = { const tradeData = {
@ -232,9 +270,13 @@ export async function POST({ params, request }) {
type: 'BUY', type: 'BUY',
coinsBought, coinsBought,
totalCost, totalCost,
newPrice, newPrice: finalPrice,
priceImpact, priceImpact: ((finalPrice - currentPrice) / currentPrice) * 100,
newBalance: userBalance - totalCost newBalance: userBalance - totalCost,
autoPumpEffects: {
feeApplied: autoPumpEffects.pumpFee,
tokensBurned: autoPumpEffects.tokensBurned,
}
}); });
} else { } else {
@ -275,6 +317,13 @@ export async function POST({ params, request }) {
throw error(400, 'Trade amount results in zero base currency received'); throw error(400, 'Trade amount results in zero base currency received');
} }
// Apply auto-pump effects
const autoPumpEffects = calculateAutoPumpEffects(coinData, totalCost, amount);
const finalPoolBase = autoPumpEffects.newPoolBase;
const finalPoolCoin = autoPumpEffects.newPoolCoin;
const finalPrice = finalPoolBase / finalPoolCoin;
await tx.update(user) await tx.update(user)
.set({ .set({
baseCurrencyBalance: (userBalance + totalCost).toString(), baseCurrencyBalance: (userBalance + totalCost).toString(),
@ -307,22 +356,25 @@ export async function POST({ params, request }) {
type: 'SELL', type: 'SELL',
quantity: amount.toString(), quantity: amount.toString(),
pricePerCoin: (totalCost / amount).toString(), pricePerCoin: (totalCost / amount).toString(),
totalBaseCurrencyAmount: totalCost.toString() totalBaseCurrencyAmount: totalCost.toString(),
pumpFeeApplied: autoPumpEffects.pumpFee.toString(),
tokensBurned: autoPumpEffects.tokensBurned.toString()
}); });
await tx.insert(priceHistory).values({ await tx.insert(priceHistory).values({
coinId: coinData.id, coinId: coinData.id,
price: newPrice.toString() price: finalPrice.toString()
}); });
const metrics = await calculate24hMetrics(coinData.id, newPrice); const metrics = await calculate24hMetrics(coinData.id, finalPrice);
await tx.update(coin) await tx.update(coin)
.set({ .set({
currentPrice: newPrice.toString(), currentPrice: finalPrice.toString(),
marketCap: (Number(coinData.circulatingSupply) * newPrice).toString(), marketCap: (autoPumpEffects.newCirculatingSupply * finalPrice).toString(),
poolCoinAmount: newPoolCoin.toString(), poolCoinAmount: finalPoolCoin.toString(),
poolBaseCurrencyAmount: newPoolBaseCurrency.toString(), poolBaseCurrencyAmount: finalPoolBase.toString(),
circulatingSupply: autoPumpEffects.newCirculatingSupply.toString(),
change24h: metrics.change24h.toString(), change24h: metrics.change24h.toString(),
volume24h: metrics.volume24h.toString(), volume24h: metrics.volume24h.toString(),
updatedAt: new Date() updatedAt: new Date()
@ -330,12 +382,12 @@ export async function POST({ params, request }) {
.where(eq(coin.id, coinData.id)); .where(eq(coin.id, coinData.id));
const priceUpdateData = { const priceUpdateData = {
currentPrice: newPrice, currentPrice: finalPrice,
marketCap: Number(coinData.circulatingSupply) * newPrice, marketCap: autoPumpEffects.newCirculatingSupply * finalPrice,
change24h: metrics.change24h, change24h: metrics.change24h,
volume24h: metrics.volume24h, volume24h: metrics.volume24h,
poolCoinAmount: newPoolCoin, poolCoinAmount: finalPoolCoin,
poolBaseCurrencyAmount: newPoolBaseCurrency poolBaseCurrencyAmount: finalPoolBase
}; };
const tradeData = { const tradeData = {
@ -371,9 +423,13 @@ export async function POST({ params, request }) {
type: 'SELL', type: 'SELL',
coinsSold: amount, coinsSold: amount,
totalReceived: totalCost, totalReceived: totalCost,
newPrice, newPrice: finalPrice,
priceImpact, priceImpact: ((finalPrice - currentPrice) / currentPrice) * 100,
newBalance: userBalance + totalCost newBalance: userBalance + totalCost,
autoPumpEffects: {
feeApplied: autoPumpEffects.pumpFee,
tokensBurned: autoPumpEffects.tokensBurned
}
}); });
} }
}); });