feat: implement auto-pump mechanism to prevent pool drainage
This commit is contained in:
parent
d9f2836fb9
commit
b8de80d8a9
8 changed files with 1727 additions and 29 deletions
4
website/drizzle/0003_adorable_leper_queen.sql
Normal file
4
website/drizzle/0003_adorable_leper_queen.sql
Normal 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';
|
||||||
1587
website/drizzle/meta/0003_snapshot.json
Normal file
1587
website/drizzle/meta/0003_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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' }),
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Reference in a new issue