Duel üzerinde geliştirme
Duel'in havuzları, hook'lu standart Uniswap V4 havuzlarıdır ve uygulamanın gösterdiği her sayı herkese açık görünümlerden ve olaylardan gelir. Botlar, terminaller, panolar ve toplayıcılar izin istemeden bunun üzerine geliştirme yapabilir.
Bir düelloyu okumak
DuelLens, bir düello hakkındaki her şeyi tek bir çağrıda toplar:
function getPair(uint256 pairId) external view returns (PairView memory);
function getPairs(uint256 offset, uint256 limit) external view returns (PairView[] memory);
Bir PairView, düellonun kimliğini, yaratıcısını ve meta veri URI'sini, iki tarafı (token, ad, sembol, arz, havuz fiyatı ve tick'i, token başına ETH, havuzdaki gerçek ETH), üç havuz anahtarını, canlı oranı (gaugeBps, A tarafının baz puan cinsinden payı), turun şimdiye kadarki ortalamasını (roundGaugeBps), kasayı, yaratıcı ücretlerini, tüm zamanların hacmini ve geri alımlarını, güncel anti-snipe vergisini, turun numarasını, başlangıcını, bitişini ve turda işlem yapılıp yapılmadığını (roundTraded), lansman zamanını ve tick'ini (launchTime, startTick), bir destek düellosu olup olmadığını, kategorisini (category, uygulamanın numaralandırmasına göre, hiçbiri için 0), ücret koşullarını (fees) ve protokolün kendi payından şu anda aldığı kısmı (protocolRateBps, 10.000 üzerinden) içerir: protokol swap ücretinin fees.protocolShareBps × protocolRateBps / 10.000 kadarını alır, o payın kalanı kasaya gider. Sözleşmelerde ve ABI'lerinde B tarafı D'dir: d, tokenD, keyD.
Hook'un daha küçük görünümleri de vardır: pairCount(), getPair(pairId), poolKeys(pairId), gaugeBps(pairId), snipeTaxBps(pairId), protocolRateBps(), protocolFees() ve firstRoundEnd(launchTime).
İşlem yapmak
Herhangi bir Uniswap V4 router'ı bir düellonun havuzlarında işlem yapabilir. Havuz anahtarlarını poolKeys(pairId) üzerinden oluşturun:
| Havuz | currency0 | currency1 | fee | tickSpacing |
|---|---|---|---|---|
| ETH / A | ETH (address(0)) | token A | 0 | 200 |
| ETH / B | ETH (address(0)) | token B | 0 | 200 |
| A / B | daha küçük token adresi | daha büyük olanı | 0 | 60 |
Hook ücretlerini return delta'lar üzerinden alır; bu yüzden fiyat teklifini V4 Quoter ile alın: yanıtları tüm ücretleri ve vergileri içerir. ETH üzerinden bir geçiş, token → ETH → diğer token yolu boyunca onun quoteExactInput çağrısıdır.
Ücretini hook'un çalışmadan önce aldığı bir swap (tam girdili bir alım ya da geçiş, tam çıktılı bir satış) tamamen gerçekleşmelidir: fiyat limitinin ya da ETH'si yetmeyen bir ETH havuzunun erken durduracağı bir swap, PartialFill hatasıyla geri alınır. PoolManager bir hook'un hatasını sarmalar: PartialFill ve ArbitrageStarved, WrappedError(address target, bytes4 selector, bytes reason, bytes details) içinde, onları V4 Quoter bildirdiğinde ise UnexpectedRevertBytes(bytes revertData) içinde gelir. Hook'un hatasını okumak için reason alanını çözün.
Her işlem hook'un yeniden dengelemesiyle biter ve bunun kendi gas'ı gerekir: ona çok az gas bırakan bir swap, onu atlamak yerine ArbitrageStarved hatasıyla geri alınır. Swap'ları bir tahminin verdiği gas'la, asla daha azıyla değil, üstüne pay bırakarak gönderin: bir turun kapanışından sonraki ilk swap o turu da sonuçlandırır (geri alımı, ardından bir yeniden dengeleme; yaklaşık 170.000 gas daha), kapanıştan önce yapılmış bir tahmin ise bunu saymaz. Uygulama her tahmine 500.000 gas ekler.
DuelRouter, tek bir havuzdaki swap için bir çağrı sunar:
function swapExactIn(
PoolKey calldata key,
bool zeroForOne,
uint256 amountIn,
uint256 minAmountOut,
address recipient,
uint256 deadline
) external payable returns (uint256 amountOut);
ve ETH üzerinden geçiş için bir çağrı daha; bırakılan taraf kendi ETH havuzunda satılır ve elde edilen ETH'nin tamamı diğerinin havuzunda harcanır:
function swapThroughEth(
PoolKey calldata keyIn,
PoolKey calldata keyOut,
uint256 amountIn,
uint256 minAmountOut,
address recipient,
uint256 deadline
) external returns (uint256 amountOut);
Bir Duel ETH havuzuna yapılan ve ETH'si tükendiği için havuzun lansman fiyatında duran bir satış, hook'un yeniden dengelemesi taraflar arasındaki havuzun aldığını havuzdan geri aldıktan sonra, kalanı için aynı çağrıda yeniden gönderilir: havuzun fiyatı swap'ların sınırının altında kaldığı sürece en fazla SALE_PASSES() (8) swap. Her biri hook'un ücretini kendi ETH'si üzerinden öder ve minAmountOut hepsini sayar. Uniswap'ın V4 Quoter'ı tek bir swap'ı fiyatlar: onun gerçekleştirdiğinin ötesinde NotEnoughLiquidity yanıtını verir. Uygulama böyle bir satışı, yönlendiriciyi trader olarak çalıştırarak fiyatlar (state override ile bir eth_call, DuelSimulator).
Yeniden dengelemenin artık geri alamadığını sell, taraflar arasındaki havuz üzerinden, yeniden dengelemelerin bunu ödeyen ETH'yi taşıdığı diğer tarafın ETH havuzuna götürür:
function sell(
PoolKey calldata keyIn,
PoolKey calldata keyCross,
PoolKey calldata keyOther,
uint256 amountIn,
uint256 minAmountOut,
address recipient,
uint256 deadline
) external returns (uint256 amountOut);
sell, swapExactIn gibi keyIn havuzuna satar, ardından kalanı hepsini alan keyCross üzerinden (geçiş ücreti, her taraf değiştirmede olduğu gibi yakılır) keyOther havuzuna, yine birkaç geçişte satar. minAmountOut iki havuzun ETH'sini birlikte sayar. keyOther havuzunun da geri alamadığı kısım, asgari çıktıya sayılmadan, o havuzun token'ı cinsinden recipient adresine ödenir. Uygulama, V4 Quoter bir satışı reddettiğinde ve DuelSimulator.simulateSale her şeyin ETH karşılığında satıldığını gösterdiğinde sell üzerinden satar.
Başka bir V4 router'ı tek bir swap yapar. Hook, havuzdaki ETH'nin ödeyebileceğini aşan tam girdili bir satışı reddetmez (ücreti swap'tan sonra, ödenen ETH üzerinden alınır): satış havuzun lansman fiyatına kadar kısmen gerçekleşir ve kalanı satıcıda kalır. Asgari çıktıyı gerçekleşebilecek kısma göre ayarlayın; kalanı ise sonraki bir işlemde, yeniden dengelemenin geri aldığı kısma satabilirsiniz.
Almak için swapExactIn ile ETH gönderin. Satmak ya da geçiş yapmak için router'a token onayı verin ya da bir permit'in (EIP-2612) bunu aynı işlemde onaylamasına izin verin: swapExactInWithPermit, swapThroughEthWithPermit ve sellWithPermit aynı argümanları ve çağıranın, router için imzalanmış, tam olarak amountIn tutarındaki permit'ini alır:
struct Permit {
uint256 deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
Kullanıcının cüzdanı, her Duel token'ı için permit'leri token'ın kendi adresinde, Duel adlı, 1 sürümlü bir EIP-712 alanında imzalar (bunu token'ın eip712Domain() işlevi, ERC-5267, söyler). Router önce permit'i kullanır ve başarısızlığını yok sayar: herhangi biri tarafından zaten gönderilmiş bir permit izni ayarlamıştır, başarısız olan ise hiçbir şeyi değiştirmez; swap bu durumda zaten var olan iznin karşıladığını kullanır ve yetmezse geri alınır. Kısmen gerçekleşen bir swap'tan sonra, permit ile verilen harcama izninin kullanılmayan kısmı geçerli kalır. Permit'in son geçerlilik zamanı imzanın geçerliliğini sınırlar; daha önce verilmiş harcama iznini sona erdirmez. Router token'ları yalnızca çağırandan çeker. Bir geçişin ETH'si, ikinci havuzun harcaması için PoolManager'da kalır; bir Duel havuzu onun tamamını harcar, aksi hâlde hook swap'ı geri çevirir (PartialFill); başka herhangi bir havuzda ise harcanamayan kısım çağırana geri ödenir.
Bir düello başlatmak
DuelFactory.createPair(CreateParams) bir düello başlatır; lansman ücretini ve lansman alımlarını çağrıyla birlikte gönderin, fazlası iade edilir. CreateParams şunlardan oluşur: nameA, symbolA, nameD, symbolD (B tarafı), metadataURI, buyA, buyD (lansmanda her tarafa harcanan ETH, anti-snipe vergisi olmadan), supporter, category (uygulamanın kategori numaralandırmasına göre, hiçbiri için 0) ve config, yani fabrikanın okuduğunuz hâliyle configHash() değeri: koşullar o zamandan beri değiştiyse lansman ConfigChanged hatasıyla geri alınır. Bir ad 1 ile 128 bayt, bir ticker 1 ile 40 bayt, meta veri URI'si ise en fazla 4.096 bayt tutar (InvalidMetadata); bir tarafın arzının %5'ini aşan bir lansman alımı geri alınır (CreatorBuyTooLarge).
Fabrika, başlatmalar duraklatılmış olarak açılır. paused() true olduğu sürece yalnızca owner() düello başlatabilir; diğer çağrılar CreationPaused ile geri alınır. Sahip, setPaused(false) ile başlatmaları açar. Mevcut düellolarda alım satım sürer.
Olaylar
| Sözleşme | Olay | Ne zaman |
|---|---|---|
| DuelFactory | PairCreated(pairId, creator, tokenA, tokenD) | Bir düello başlatıldı |
| DuelHook | PairRegistered(pairId, tokenA, tokenD, creator, category) | Havuzları aynı işlemde hook'a kaydedildi; category, uygulamanın kategori numaralandırmasına göre konusudur (hiçbiri için 0) |
| DuelHook | FeeTaken(pairId, fee, snipeTax) | ETH karşılığı bir işlem ücretini ödedi |
| DuelHook | SwitchFeeBurned(pairId, token, amount) | Bir geçiş ücretini yaktı |
| DuelHook | ArbitrageCaptured(pairId, profit) | Yeniden dengeleme kasayı doldurdu |
| DuelHook | RoundSettled(pairId, roundIndex, averageTick, winner, ethSpent, tokensBurned) | Bir tur kapandı |
| DuelHook | CreatorFeesClaimed(pairId, to, amount) | Bir yaratıcıya ödeme yapıldı |
| DuelFactory | CreatorTransferStarted(pairId, creator, newCreator) | Bir yaratıcı rolünü teklif etti (sıfır adresine: teklif geri çekildi) |
| DuelHook | CreatorChanged(pairId, from, to) | Bir düellonun yaratıcı rolü el değiştirdi |
| DuelHook | CreatorFeesGivenUp(pairId, creator) | Oluşturucu, gelecekteki ücretlerden kendi payını kalıcı olarak havuzlara yönlendirdi |
| DuelHook | ProtocolFeesClaimed(to, amount) | Hazineye ödeme yapıldı |
| DuelHook | ProtocolRateSet(rateBps) | Tüm düellolarda protokolün swap ücretindeki payı anında değişti: bunun rateBps kadarını alır (tamamı için 10.000), kalanı kasalara gider |
| DuelFactory | ConfigProposed(config, eta), ConfigApplied(config), ConfigCancelled() | Lansman koşulları 48 saat sonrası için önerildi, uygulandı, geri çekildi |
| DuelFactory | PausedSet(paused) | Lansmanlar durduruldu ya da yeniden başlatıldı |
| PoolManager | Swap(id, sender, amount0, amount1, sqrtPriceX96, liquidity, tick, fee) | Hook'un kendi yeniden dengelemesi dahil her işlem (onun sender alanı hook'tur) |
İşlem listeleri üç havuzun hepsinden swap'ları okur; fiyat ve oran grafikleri yalnızca iki ETH havuzunu okur. Sahip listeleri token'ların Transfer olaylarını kullanır. Subgraph swap'ları, bakiyeleri, sahipleri ve sonuçlanmış turları indeksler; böylece uygulama, zinciri lansmandan itibaren yeniden oynatmadan geçmişi yükleyebilir.
Sonuçlandırma ve talep
settle(pairId)biten bir turu sonuçlandırır. Herkes çağırabilir.claimCreatorFees(pairId)yalnızca düellonun yaratıcısına, yani onu başlatan cüzdana ya da rolünü devrettiği adrese ödeme yapar;claimProtocolFees()ise talep anında ayarlı olan hazineye öder. Her iki talebi de herkes tetikleyebilir. Hazine, fabrikanın 48 saatlik bekleme süresinden sonra değişebilir; çağıran farklı bir alıcı seçemez.- Fabrikadaki
transferCreator(pairId, newCreator)bir düellonun yaratıcı rolünü teklif eder;newCreatortarafından çağrılanacceptCreator(pairId)rolü devralır;pendingCreator(pairId)teklifi okur. Rolü yalnızca yaratıcı teklif eder ve yalnızca teklif edilen adres devralır. - Hook üzerindeki
giveUpCreatorFees(pairId), gelecekteki ücretlerden oluşturucuya düşen payı kalıcı olarak tur havuzlarına gönderir. Yalnızca mevcut oluşturucu çağırabilir. Birikmiş ücretler oluşturucu tarafından alınmaya devam edebilir ve oluşturucu rolü hâlâ devredilebilir.