UNPKG

four-flap-meme-sdk

Version:

SDK for Flap bonding curve and four.meme TokenManager

351 lines (350 loc) 16.4 kB
import { Contract, Wallet, JsonRpcProvider, formatEther, FixedNumber } from 'ethers'; import { CDPV2 } from './curve.js'; import { FLAP_PORTAL_ADDRESSES, FLAP_DEFAULT_FEE_RATES } from './constants.js'; export var TokenStatus; (function (TokenStatus) { TokenStatus[TokenStatus["Invalid"] = 0] = "Invalid"; TokenStatus[TokenStatus["Tradable"] = 1] = "Tradable"; TokenStatus[TokenStatus["InDuel"] = 2] = "InDuel"; TokenStatus[TokenStatus["Killed"] = 3] = "Killed"; TokenStatus[TokenStatus["DEX"] = 4] = "DEX"; })(TokenStatus || (TokenStatus = {})); export var TokenVersion; (function (TokenVersion) { TokenVersion[TokenVersion["TOKEN_LEGACY_MINT_NO_PERMIT"] = 0] = "TOKEN_LEGACY_MINT_NO_PERMIT"; TokenVersion[TokenVersion["TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE"] = 1] = "TOKEN_LEGACY_MINT_NO_PERMIT_DUPLICATE"; TokenVersion[TokenVersion["TOKEN_V2_PERMIT"] = 2] = "TOKEN_V2_PERMIT"; TokenVersion[TokenVersion["TOKEN_GOPLUS"] = 3] = "TOKEN_GOPLUS"; TokenVersion[TokenVersion["TOKEN_TAXED"] = 4] = "TOKEN_TAXED"; })(TokenVersion || (TokenVersion = {})); export var DexThreshType; (function (DexThreshType) { DexThreshType[DexThreshType["TWO_THIRDS"] = 0] = "TWO_THIRDS"; DexThreshType[DexThreshType["FOUR_FIFTHS"] = 1] = "FOUR_FIFTHS"; DexThreshType[DexThreshType["HALF"] = 2] = "HALF"; DexThreshType[DexThreshType["_95_PERCENT"] = 3] = "_95_PERCENT"; DexThreshType[DexThreshType["_81_PERCENT"] = 4] = "_81_PERCENT"; DexThreshType[DexThreshType["_1_PERCENT"] = 5] = "_1_PERCENT"; })(DexThreshType || (DexThreshType = {})); export var MigratorType; (function (MigratorType) { MigratorType[MigratorType["V3_MIGRATOR"] = 0] = "V3_MIGRATOR"; MigratorType[MigratorType["V2_MIGRATOR"] = 1] = "V2_MIGRATOR"; })(MigratorType || (MigratorType = {})); // Portal ABI (使用 ethers 标准格式) const PORTAL_ABI = [ // Token 状态查询 'function getTokenV2(address token) external view returns (uint8,uint256,uint256,uint256,uint8,uint256,uint256)', 'function getTokenV3(address token) external view returns (uint8,uint256,uint256,uint256,uint8,uint256,uint256,address,bool)', 'function getTokenV4(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,address,bool,bytes32))', 'function getTokenV5(address token) external view returns ((uint8,uint256,uint256,uint256,uint8,uint256,uint256,uint256,uint256,address,bool,bytes32))', // 报价方法 'function previewBuy(address token, uint256 ethAmount) external view returns (uint256)', 'function previewSell(address token, uint256 tokenAmount) external view returns (uint256)', 'function quoteExactInput((address inputToken,address outputToken,uint256 inputAmount)) external returns (uint256)', // 交易方法 'function buy(address token, address to, uint256 minAmount) external payable returns (uint256)', 'function sell(address token, uint256 amount, uint256 minEth) external returns (uint256)', 'function swapExactInput((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData)) external payable returns (uint256)', 'function swapExactInputBuy((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData)) external payable returns (uint256)', 'function swapExactInputSell((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData)) external returns (uint256)', 'function swapExactInputV3((address inputToken,address outputToken,uint256 inputAmount,uint256 minOutputAmount,bytes permitData,bytes extensionData)) external payable returns (uint256)', // 创建代币 'function newTokenV2((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData)) external payable returns (address)', 'function newTokenV3((string name,string symbol,string meta,uint8 dexThresh,bytes32 salt,uint16 taxRate,uint8 migratorType,address quoteToken,uint256 quoteAmt,address beneficiary,bytes permitData,bytes32 extensionID,bytes extensionData)) external payable returns (address)', // 受益人领取 'function claim(address token) external returns (uint256,uint256)', 'function delegateClaim(address token) external returns (uint256,uint256)', // 合约信息 'function getFeeRate() external view returns (uint256,uint256)', 'function nonce() external view returns (uint256)', 'function version() external pure returns (string)', 'function getLocks(address token) external view returns (uint256[])' ]; export class FlapPortal { constructor(cfg) { this.rpcUrl = cfg.rpcUrl; this.portal = cfg.addressOverride ?? FLAP_PORTAL_ADDRESSES[cfg.chain]; // 使用链对应的默认费率,如果用户指定了费率则覆盖 const defaultFees = FLAP_DEFAULT_FEE_RATES[cfg.chain]; this.buyFeeRate = cfg.buyFeeRate ?? defaultFees.buy; this.sellFeeRate = cfg.sellFeeRate ?? defaultFees.sell; // 使用 ethers v6 创建只读合约实例 const provider = new JsonRpcProvider(this.rpcUrl); this.contract = new Contract(this.portal, PORTAL_ABI, provider); } async getTokenV2(token) { const r = await this.contract.getTokenV2(token); return { status: r[0], reserve: r[1], circulatingSupply: r[2], price: r[3], tokenVersion: r[4], r: r[5], dexSupplyThresh: r[6], }; } async getTokenV3(token) { const r = await this.contract.getTokenV3(token); return { status: r[0], reserve: r[1], circulatingSupply: r[2], price: r[3], tokenVersion: r[4], r: r[5], dexSupplyThresh: r[6], quoteTokenAddress: r[7], nativeToQuoteSwapEnabled: r[8], }; } async getTokenV4(token) { const raw = await this.contract.getTokenV4(token); return { status: raw[0], reserve: raw[1], circulatingSupply: raw[2], price: raw[3], tokenVersion: raw[4], r: raw[5], dexSupplyThresh: raw[6], quoteTokenAddress: raw[7], nativeToQuoteSwapEnabled: raw[8], extensionID: raw[9], }; } async getTokenV5(token) { const raw = await this.contract.getTokenV5(token); return { status: raw[0], reserve: raw[1], circulatingSupply: raw[2], price: raw[3], tokenVersion: raw[4], r: raw[5], h: raw[6], k: raw[7], dexSupplyThresh: raw[8], quoteTokenAddress: raw[9], nativeToQuoteSwapEnabled: raw[10], extensionID: raw[11], }; } // 价格/进度 computePriceAndProgress(state) { // 使用合约返回的实时价格,而不是重新计算 // 合约的 price 字段会在每次买卖后更新,是最准确的实时价格 const price = formatEther(state.price); // 计算进度(保留4位小数,格式如 "0.5432" 或 "1.0000") let progress; const supply = formatEther(state.circulatingSupply); const dexThresh = formatEther(state.dexSupplyThresh); const supplyNum = FixedNumber.fromString(supply); const dexThreshNum = FixedNumber.fromString(dexThresh); if (supplyNum.gte(dexThreshNum)) { // 如果已达到或超过 DEX 阈值,进度为 100% progress = '1.0000'; } else { // 使用曲线计算当前和预期的储备量 const curve = new CDPV2(Number(formatEther(state.r)), Number(formatEther(state.h)), Number(formatEther(state.k))); const currReserve = curve.estimateReserve(supply); const expectReserve = curve.estimateReserve(dexThresh); if (expectReserve.isZero() || parseFloat(expectReserve.toString()) <= 0) { // 如果预期储备量为 0 或负数,进度为 0 progress = '0.0000'; } else { // 使用 FixedNumber 进行精度计算,保留 4 位小数 const progressNum = currReserve.div(expectReserve); progress = parseFloat(progressNum.toString()).toFixed(4); } } return { price, progress }; } // 示例报价(离线) quoteBuy(state, inputEth) { const curve = new CDPV2(Number(formatEther(state.r)), Number(formatEther(state.h)), Number(formatEther(state.k))); const supply = formatEther(state.circulatingSupply); const dexThresh = formatEther(state.dexSupplyThresh); const currReserve = curve.estimateReserve(supply); const maxReserve = curve.estimateReserve(dexThresh); // 使用 FixedNumber 进行精度计算 const inputAfterFee = FixedNumber.fromString(inputEth).mul(FixedNumber.fromValue(1 - this.buyFeeRate)); let newReserve; if (curve.h === 0) { newReserve = currReserve.add(inputAfterFee); } else { const availableReserve = maxReserve.sub(currReserve); const actualInput = parseFloat(inputAfterFee.toString()) <= parseFloat(availableReserve.toString()) ? inputAfterFee : availableReserve; newReserve = currReserve.add(actualInput); } const newSupply = curve.estimateSupply(newReserve.toString()); const outputTokens = newSupply.sub(FixedNumber.fromString(supply)); return outputTokens.toString(); } quoteSell(state, inputToken) { const curve = new CDPV2(Number(formatEther(state.r)), Number(formatEther(state.h)), Number(formatEther(state.k))); const currSupply = FixedNumber.fromString(formatEther(state.circulatingSupply)); const inputAmt = FixedNumber.fromString(inputToken); // 使用 FixedNumber 进行精度计算 const currReserve = curve.estimateReserve(currSupply.toString()); const newSupply = currSupply.sub(inputAmt); const newReserve = curve.estimateReserve(newSupply.toString()); const beforeFee = currReserve.sub(newReserve); const afterFee = beforeFee.mul(FixedNumber.fromValue(1 - this.sellFeeRate)); return afterFee.toString(); } // 合约链上报价(买入) async previewBuy(token, ethAmount) { return await this.contract.previewBuy(token, ethAmount); } // 合约链上报价(卖出) async previewSell(token, tokenAmount) { return await this.contract.previewSell(token, tokenAmount); } // 合约 quote(使用 staticCall 模拟) async quoteExactInput(params) { return await this.contract.quoteExactInput.staticCall(params); } // 获取平台手续费率 async getFeeRate() { const result = await this.contract.getFeeRate(); return { buyFeeRate: result[0], sellFeeRate: result[1] }; } // 获取合约 nonce async nonce() { return await this.contract.nonce(); } // 获取合约版本 async version() { return await this.contract.version(); } // 获取代币的锁仓信息 async getLocks(token) { return await this.contract.getLocks(token); } } export class FlapPortalWriter extends FlapPortal { constructor(cfg, privateKey) { super(cfg); // 使用 ethers v6 创建钱包 const provider = new JsonRpcProvider(cfg.rpcUrl); this.wallet = new Wallet(privateKey, provider); // 重新创建可写合约实例 this.contract = new Contract(this.portal, PORTAL_ABI, this.wallet); } // 买入代币 async buy(token, to, minAmount, msgValue) { const tx = await this.contract.buy(token, to, minAmount, { value: msgValue }); return await tx.wait(); } // 创建代币时同时买入 // buyOnCreation 已从合约端移除,SDK 同步移除 // 卖出代币 async sell(token, amount, minEth) { const tx = await this.contract.sell(token, amount, minEth); return await tx.wait(); } // 发送 swap 交易 async swapExactInput(params, msgValue) { const tx = await this.contract.swapExactInput({ inputToken: params.inputToken, outputToken: params.outputToken, inputAmount: params.inputAmount, minOutputAmount: params.minOutputAmount, permitData: params.permitData ?? '0x', }, { value: msgValue }); return await tx.wait(); } // 发送显式买入交易 async swapExactInputBuy(params, msgValue) { const tx = await this.contract.swapExactInputBuy({ inputToken: params.inputToken, outputToken: params.outputToken, inputAmount: params.inputAmount, minOutputAmount: params.minOutputAmount, permitData: params.permitData ?? '0x', }, { value: msgValue }); return await tx.wait(); } // 发送显式卖出交易 async swapExactInputSell(params) { const tx = await this.contract.swapExactInputSell({ inputToken: params.inputToken, outputToken: params.outputToken, inputAmount: params.inputAmount, minOutputAmount: params.minOutputAmount, permitData: params.permitData ?? '0x', }); return await tx.wait(); } // 发送 swap 交易(V3,支持扩展数据) async swapExactInputV3(params, msgValue) { const tx = await this.contract.swapExactInputV3({ inputToken: params.inputToken, outputToken: params.outputToken, inputAmount: params.inputAmount, minOutputAmount: params.minOutputAmount, permitData: params.permitData ?? '0x', extensionData: params.extensionData ?? '0x', }, { value: msgValue }); return await tx.wait(); } async newTokenV2(args) { const tx = await this.contract.newTokenV2({ name: args.name, symbol: args.symbol, meta: args.meta, dexThresh: args.dexThresh, salt: args.salt, taxRate: args.taxRate, migratorType: args.migratorType, quoteToken: args.quoteToken, quoteAmt: args.quoteAmt, beneficiary: args.beneficiary, permitData: args.permitData ?? '0x', }, { value: args.msgValue }); return await tx.wait(); } async newTokenV3(args) { const tx = await this.contract.newTokenV3({ name: args.name, symbol: args.symbol, meta: args.meta, dexThresh: args.dexThresh, salt: args.salt, taxRate: args.taxRate, migratorType: args.migratorType, quoteToken: args.quoteToken, quoteAmt: args.quoteAmt, beneficiary: args.beneficiary, permitData: args.permitData ?? '0x', extensionID: args.extensionID, extensionData: args.extensionData ?? '0x', }, { value: args.msgValue }); return await tx.wait(); } // 受益人领取收益 async claim(token) { const tx = await this.contract.claim(token); const receipt = await tx.wait(); // 这里返回 hash,具体的 tokenAmount 和 ethAmount 需要从事件中解析 return { txHash: receipt.hash, tokenAmount: 0n, ethAmount: 0n }; } // 受益人委托领取收益 async delegateClaim(token) { const tx = await this.contract.delegateClaim(token); const receipt = await tx.wait(); return { txHash: receipt.hash, tokenAmount: 0n, ethAmount: 0n }; } /** * 获取当前使用的 Portal 合约地址 * 用于事件监听等场景 */ getContractAddress() { return this.portal; } } /** * 获取 Flap Portal 合约地址(内部使用) * 注意:此方法不对外导出,用户应使用 FlapPortal 实例而非直接获取地址 * @internal */ function getFlapPortalAddress(chain) { const address = FLAP_PORTAL_ADDRESSES[chain]; if (!address) { throw new Error(`Flap Portal not deployed on ${chain}`); } return address; }