• Duel is open op Robinhood Chain
  • Lanceer een duel twee tokens, drie pools, één transactie
  • Ronde sluit over --:--:--
  • Rondes van vier uur de terugkoop van de winnaar heeft een prijslimiet
  • Liquiditeit vergrendeld voor altijd, geen teamtokens

Bouwen op Duel

De pools van Duel zijn standaard Uniswap V4-pools met een hook, en elk getal dat de app toont, komt uit openbare views en events. Bots, terminals, dashboards en aggregators kunnen erop bouwen zonder toestemming te vragen.

Een duel lezen

DuelLens verzamelt alles over een duel in één aanroep:

function getPair(uint256 pairId) external view returns (PairView memory);
function getPairs(uint256 offset, uint256 limit) external view returns (PairView[] memory);

Een PairView bevat het ID van het duel, zijn creator en zijn metadata-URI, beide kanten (token, naam, symbool, supply, poolprijs en tick, ETH per token, echte ETH in de pool), de drie poolsleutels, de live verdeling (gaugeBps, het aandeel van kant A in basispunten), het gemiddelde van de ronde tot nu toe (roundGaugeBps), de pot, de creatorvergoedingen, het totale volume en de terugkopen, de huidige anti-snipebelasting, de index, het begin en het einde van de ronde en of erin is gehandeld (roundTraded), het tijdstip en de tick van de lancering (launchTime, startTick), of het een supportersduel is, zijn categorie (category, volgens de nummering van de app, 0 voor geen), zijn vergoedingsvoorwaarden (fees) en het gedeelte van zijn deel dat het protocol nu neemt (protocolRateBps, op 10.000): het protocol krijgt fees.protocolShareBps × protocolRateBps / 10.000 van de swapvergoeding, en de pot de rest van dat deel. In de contracten en hun ABI's heet kant B D: d, tokenD, keyD.

De hook heeft kleinere views: pairCount(), getPair(pairId), poolKeys(pairId), gaugeBps(pairId), snipeTaxBps(pairId), protocolRateBps(), protocolFees() en firstRoundEnd(launchTime).

Handelen

Elke Uniswap V4-router kan in de pools van een duel handelen. Bouw de poolsleutels uit poolKeys(pairId):

Poolcurrency0currency1feetickSpacing
ETH / AETH (address(0))token A0200
ETH / BETH (address(0))token B0200
A / Bhet lagere tokenadreshet hogere060

De hook heft zijn vergoedingen via return deltas, dus vraag koersen op via de V4 Quoter: zijn antwoorden bevatten elke vergoeding en belasting. Een wissel via ETH is zijn quoteExactInput langs het pad token → ETH → andere token.

Een swap waarvan de hook de vergoeding vóór de uitvoering heft (een aankoop of wissel met exacte input, een verkoop met exacte output), moet volledig worden uitgevoerd: een swap die door zijn prijslimiet, of door een ETH-pool met te weinig ETH, voortijdig zou stoppen, draait terug met PartialFill. De PoolManager verpakt de fout van een hook: PartialFill en ArbitrageStarved komen binnen in WrappedError(address target, bytes4 selector, bytes reason, bytes details), en in UnexpectedRevertBytes(bytes revertData) wanneer de V4 Quoter ze meldt. Decodeer reason om de fout van de hook te lezen.

Elke trade eindigt met de herschikking van de hook, die eigen gas nodig heeft: een swap die haar te weinig laat, draait terug met ArbitrageStarved in plaats van haar over te slaan. Verstuur swaps met het gas van een schatting, nooit minder, en met marge: de eerste swap na de sluiting van een ronde rekent ook de ronde af (haar terugkoop, daarna een herschikking, zo'n 170.000 gas extra), wat een schatting van vóór de sluiting niet meetelt. De app telt bij elke schatting 500.000 gas op.

DuelRouter biedt één aanroep voor een swap in één pool:

function swapExactIn(
    PoolKey calldata key,
    bool zeroForOne,
    uint256 amountIn,
    uint256 minAmountOut,
    address recipient,
    uint256 deadline
) external payable returns (uint256 amountOut);

en één voor een wissel via ETH, waarbij de kant die je verlaat in zijn ETH-pool wordt verkocht en alle verkregen ETH in die van de andere wordt besteed:

function swapThroughEth(
    PoolKey calldata keyIn,
    PoolKey calldata keyOut,
    uint256 amountIn,
    uint256 minAmountOut,
    address recipient,
    uint256 deadline
) external returns (uint256 amountOut);

Een verkoop in een ETH-pool van Duel die stopt bij de lanceringsprijs van de pool, omdat zijn ETH op is, wordt in dezelfde aanroep opnieuw verstuurd voor de rest, zodra de herschikking van de hook van de pool heeft teruggekocht wat de pool tussen de kanten opneemt: tot SALE_PASSES() (8) swaps, zolang de prijs van de pool onder de limiet van de swaps ligt. Elke swap betaalt de vergoeding van de hook over zijn eigen ETH, en minAmountOut telt ze allemaal. De V4 Quoter van Uniswap offreert één swap: voorbij wat die uitvoert, antwoordt hij NotEnoughLiquidity. De app offreert zo'n verkoop door de router als de trader uit te voeren (een eth_call met een state override, DuelSimulator).

Wat de herschikking niet meer kan terugkopen, neemt sell via de pool tussen de kanten mee naar de ETH-pool van de andere kant, waar de herschikkingen de ETH die ervoor betaalt naartoe hebben verplaatst:

function sell(
    PoolKey calldata keyIn,
    PoolKey calldata keyCross,
    PoolKey calldata keyOther,
    uint256 amountIn,
    uint256 minAmountOut,
    address recipient,
    uint256 deadline
) external returns (uint256 amountOut);

sell verkoopt in keyIn zoals swapExactIn dat doet, daarna wat overblijft via keyCross, die alles opneemt (waarbij zijn wisselvergoeding wordt verbrand, zoals bij elke wissel), en in keyOther, ook daar in meerdere stappen. minAmountOut telt de ETH van beide pools. Wat ook keyOther niet kan terugkopen, wordt aan recipient betaald in de token van die pool, buiten de minimale opbrengst. De app verkoopt via sell wanneer de V4 Quoter een verkoop weigert en DuelSimulator.simulateSale laat zien dat alles zo voor ETH wordt verkocht.

Een andere V4-router doet één swap. De hook weigert geen verkoop met exacte input die verder gaat dan wat de ETH van de pool betaalt (zijn vergoeding wordt na de swap geheven, op de uitbetaalde ETH): de verkoop wordt deels uitgevoerd, tot aan de lanceringsprijs van de pool, en de rest blijft bij de verkoper. Stel de minimale opbrengst in op wat kan worden uitgevoerd, en verkoop de rest in een latere transactie, tegen wat de herschikking heeft teruggekocht.

Stuur ETH mee met swapExactIn om te kopen. Om te verkopen of te wisselen, keur je de router goed voor de token, of laat je een permit (EIP-2612) dat in dezelfde transactie doen: swapExactInWithPermit, swapThroughEthWithPermit en sellWithPermit nemen dezelfde argumenten en de permit van de aanroeper voor precies amountIn, ondertekend voor de router:

struct Permit {
    uint256 deadline;
    uint8 v;
    bytes32 r;
    bytes32 s;
}

De wallet van de gebruiker ondertekent permits voor elke Duel-token in een EIP-712-domein met de naam Duel, versie 1, op het adres van de token (zijn eip712Domain(), ERC-5267, zegt dat). De router gebruikt eerst de permit en negeert het als die mislukt: een permit die al door iemand is verstuurd, heeft de allowance ingesteld, en een die mislukt verandert niets; de swap neemt dan wat de bestaande allowance dekt en draait terug als dat niet genoeg is. Na een gedeeltelijk uitgevoerde swap blijft het ongebruikte deel van de via de permit toegekende allowance geldig. De deadline begrenst de geldigheid van de permit-handtekening; een al toegekende allowance verloopt daardoor niet. De router haalt alleen tokens bij de aanroeper weg. De ETH van een wissel blijft in de PoolManager zodat de tweede pool die kan besteden; een Duel-pool besteedt alles, of de hook draait de swap terug (PartialFill), en bij elke andere pool wordt wat die niet kan besteden aan de aanroeper terugbetaald.

Een duel lanceren

DuelFactory.createPair(CreateParams) lanceert een duel; stuur de lanceringsvergoeding en de lanceringsaankopen mee, een eventueel teveel wordt terugbetaald. CreateParams bestaat uit nameA, symbolA, nameD, symbolD (kant B), metadataURI, buyA, buyD (de ETH die bij de lancering aan elke kant wordt besteed, zonder anti-snipebelasting), supporter, category (volgens de nummering van de categorieën in de app, 0 voor geen) en config, de configHash() van de factory zoals je die hebt gelezen: de lancering draait terug met ConfigChanged als de voorwaarden sindsdien zijn veranderd. Een naam telt 1 tot 128 bytes, een ticker 1 tot 40 en de metadata-URI hooguit 4096 (InvalidMetadata); een lanceringsaankoop boven 5% van de supply van een kant draait terug (CreatorBuyTooLarge).

De factory begint met gepauzeerde lanceringen. Zolang paused() true is, kan alleen owner() een duel lanceren; aanroepen van anderen worden teruggedraaid met CreationPaused. De eigenaar opent de lanceringen met setPaused(false). Bestaande duels blijven verhandelbaar.

Events

ContractEventWanneer
DuelFactoryPairCreated(pairId, creator, tokenA, tokenD)Er werd een duel gelanceerd
DuelHookPairRegistered(pairId, tokenA, tokenD, creator, category)Zijn pools werden bij de hook geregistreerd, in dezelfde transactie; category is waar het over gaat, volgens de nummering van de categorieën in de app (0 voor geen)
DuelHookFeeTaken(pairId, fee, snipeTax)Een trade tegen ETH betaalde zijn vergoeding
DuelHookSwitchFeeBurned(pairId, token, amount)Een wissel verbrandde zijn vergoeding
DuelHookArbitrageCaptured(pairId, profit)De herschikking vulde de pot
DuelHookRoundSettled(pairId, roundIndex, averageTick, winner, ethSpent, tokensBurned)Een ronde werd gesloten
DuelHookCreatorFeesClaimed(pairId, to, amount)Een creator werd betaald
DuelFactoryCreatorTransferStarted(pairId, creator, newCreator)Een creator bood zijn rol aan (aan het nuladres: aanbod ingetrokken)
DuelHookCreatorChanged(pairId, from, to)De creatorrol van een duel ging over in andere handen
DuelHookCreatorFeesGivenUp(pairId, creator)De maker heeft zijn aandeel van toekomstige kosten permanent naar de potten gestuurd
DuelHookProtocolFeesClaimed(to, amount)De treasury werd betaald
DuelHookProtocolRateSet(rateBps)Het deel van het protocol in de swapvergoeding van alle duels is meteen veranderd: het neemt er rateBps van (10.000 voor alles), de rest gaat naar de pots
DuelFactoryConfigProposed(config, eta), ConfigApplied(config), ConfigCancelled()Lanceringsvoorwaarden voorgesteld voor over 48 uur, toegepast, ingetrokken
DuelFactoryPausedSet(paused)Lanceringen gepauzeerd of hervat
PoolManagerSwap(id, sender, amount0, amount1, sqrtPriceX96, liquidity, tick, fee)Elke trade, inclusief de eigen herschikking van de hook (zijn sender is de hook)

Tradelijsten lezen swaps uit alle drie de pools; de prijs- en verdelingsgrafieken lezen alleen de twee ETH-pools. Holderlijsten gebruiken de Transfer-events van de tokens. De subgraph indexeert swaps, saldi, holders en afgerekende rondes, zodat de app de geschiedenis kan laden zonder de chain vanaf de lancering opnieuw af te spelen.

Afrekenen en opeisen

  • settle(pairId) rekent een afgelopen ronde af. Iedereen kan die aanroepen.
  • claimCreatorFees(pairId) betaalt alleen de creator van het duel, de wallet die het lanceerde of het adres waaraan die zijn rol overdroeg; claimProtocolFees() betaalt de treasury die is ingesteld op het moment van opeisen. Iedereen kan beide opeisingen starten. De treasury kan veranderen na de wachttijd van 48 uur van de factory; een aanroeper kan geen andere ontvanger kiezen.
  • transferCreator(pairId, newCreator) op de factory biedt de creatorrol van een duel aan, die acceptCreator(pairId) vanuit newCreator overneemt; pendingCreator(pairId) leest het aanbod. Alleen de creator biedt hem aan, en alleen het aangeboden adres neemt hem over.
  • giveUpCreatorFees(pairId) op de hook stuurt het makersaandeel van toekomstige kosten permanent naar de rondepotten. Alleen de huidige maker kan de functie aanroepen. De maker kan al opgebouwde kosten nog steeds opeisen en de makersrol kan nog steeds worden overgedragen.