• Duel đã mở trên Robinhood Chain
  • Ra mắt trận đấu hai token, ba pool, một giao dịch
  • Vòng kết thúc sau --:--:--
  • Vòng bốn giờ việc mua lại phe thắng có giới hạn giá
  • Thanh khoản bị khóa vĩnh viễn, không có token của đội ngũ

Xây dựng trên Duel

Các pool của Duel là pool Uniswap V4 tiêu chuẩn có hook, và mọi con số ứng dụng hiển thị đều đến từ các view và sự kiện công khai. Bot, terminal, bảng điều khiển và trình tổng hợp có thể xây dựng trên đó mà không cần xin phép.

Đọc một trận đấu

DuelLens gom mọi thông tin về một trận đấu trong một lần gọi:

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

Một PairView chứa id của trận đấu, người tạo và URI siêu dữ liệu của nó, cả hai phe (token, tên, ký hiệu, nguồn cung, giá và tick của pool, ETH trên mỗi token, ETH thật trong pool), ba khóa pool, tỷ lệ theo thời gian thực (gaugeBps, phần của phe A tính bằng điểm cơ bản), mức trung bình của vòng tính đến hiện tại (roundGaugeBps), quỹ, phí người tạo, tổng khối lượng và các lần mua lại từ trước đến nay, thuế chống snipe hiện tại, số thứ tự, thời điểm bắt đầu và kết thúc của vòng cùng việc vòng đó đã có giao dịch hay chưa (roundTraded), thời điểm và tick ra mắt (launchTime, startTick), việc nó có phải trận đấu ủng hộ hay không, thể loại của nó (category, theo cách đánh số của ứng dụng, 0 nếu không có), các điều khoản phí của nó (fees) và phần mà giao thức hiện lấy trong phần của mình (protocolRateBps, trên 10.000): giao thức nhận fees.protocolShareBps × protocolRateBps / 10.000 của phí swap, và quỹ nhận phần còn lại của phần đó. Trong các hợp đồng và ABI của chúng, phe B là D: d, tokenD, keyD.

Hook có các view nhỏ hơn: pairCount(), getPair(pairId), poolKeys(pairId), gaugeBps(pairId), snipeTaxBps(pairId), protocolRateBps(), protocolFees() và firstRoundEnd(launchTime).

Giao dịch

Bất kỳ router Uniswap V4 nào cũng có thể giao dịch trên các pool của một trận đấu. Hãy dựng các khóa pool từ poolKeys(pairId):

Poolcurrency0currency1feetickSpacing
ETH / AETH (address(0))token A0200
ETH / BETH (address(0))token B0200
A / Bđịa chỉ token nhỏ hơnđịa chỉ lớn hơn060

Hook thu phí qua các return delta, nên hãy lấy báo giá bằng V4 Quoter: câu trả lời của nó đã gồm mọi khoản phí và thuế. Một lần chuyển qua ETH là quoteExactInput của nó dọc theo đường token → ETH → token kia.

Một swap mà hook thu phí trước khi chạy (lệnh mua hoặc chuyển phe với đầu vào chính xác, lệnh bán với đầu ra chính xác) phải được khớp hoàn toàn: swap nào bị giới hạn giá của nó, hoặc một pool ETH thiếu ETH, chặn lại giữa chừng sẽ bị hoàn tác với PartialFill. PoolManager bọc lỗi của hook: PartialFill và ArbitrageStarved đến bên trong WrappedError(address target, bytes4 selector, bytes reason, bytes details), và bên trong UnexpectedRevertBytes(bytes revertData) khi V4 Quoter báo về chúng. Hãy giải mã reason để đọc lỗi của hook.

Mỗi giao dịch kết thúc bằng việc tái cân bằng của hook, vốn cần gas riêng: swap để lại quá ít gas cho việc này sẽ bị hoàn tác với ArbitrageStarved thay vì bỏ qua nó. Hãy gửi swap với lượng gas mà ước tính đưa ra, không bao giờ ít hơn, và có dư: swap đầu tiên sau khi một vòng kết thúc cũng chốt vòng đó (lần mua lại, rồi một lần tái cân bằng, thêm khoảng 170.000 gas), điều mà một ước tính thực hiện trước khi vòng kết thúc không tính đến. Ứng dụng cộng thêm 500.000 gas vào mỗi ước tính.

DuelRouter cung cấp một lệnh gọi cho swap trong một pool:

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

và một lệnh gọi cho việc chuyển phe qua ETH, trong đó phe bạn rời đi được bán trong pool ETH của nó và toàn bộ ETH thu được được chi trong pool của phe kia:

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

Một lệnh bán vào pool ETH của Duel dừng lại ở giá ra mắt của pool, khi ETH của nó đã cạn, sẽ được gửi lại cho phần còn lại trong cùng lời gọi, sau khi lần tái cân bằng của hook đã mua lại từ pool phần mà pool giữa hai phe nhận: tối đa SALE_PASSES() (8) swap, chừng nào giá của pool còn dưới giới hạn của các swap. Mỗi swap trả phí của hook trên ETH của chính nó, và minAmountOut tính tất cả. V4 Quoter của Uniswap báo giá một swap: vượt quá phần nó khớp, nó trả về NotEnoughLiquidity. Ứng dụng báo giá một lệnh bán như vậy bằng cách chạy router với tư cách trader (một eth_call có state override, DuelSimulator).

Phần mà tái cân bằng không còn mua lại được, sell đưa tiếp qua pool giữa hai phe vào pool ETH của phe kia, nơi các lần tái cân bằng đã chuyển số ETH dùng để trả cho phần đó:

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

sell bán vào keyIn như swapExactIn, rồi bán phần còn lại qua keyCross, pool nhận toàn bộ phần đó (phí chuyển phe của pool này bị đốt, như với mọi lần chuyển phe), và vào keyOther, cũng qua nhiều lượt. minAmountOut tính ETH của cả hai pool. Phần mà keyOther cũng không mua lại được sẽ được trả cho recipient bằng token của pool đó và không được tính vào đầu ra tối thiểu. Ứng dụng bán qua sell khi V4 Quoter từ chối một lệnh bán và DuelSimulator.simulateSale cho thấy sell bán được toàn bộ lấy ETH.

Một router V4 khác chỉ thực hiện một swap. Hook không từ chối một lệnh bán đầu vào chính xác vượt quá mức ETH của pool có thể trả (phí của nó được thu sau swap, trên số ETH trả ra): lệnh bán được khớp một phần, xuống đến giá ra mắt của pool, và phần còn lại ở lại với người bán. Hãy đặt đầu ra tối thiểu theo phần có thể khớp, rồi bán phần còn lại trong một giao dịch sau, vào phần mà tái cân bằng đã mua lại.

Gửi ETH kèm swapExactIn để mua. Để bán hoặc chuyển phe, hãy cấp quyền cho router với token, hoặc để một permit (EIP-2612) cấp quyền ngay trong cùng giao dịch: swapExactInWithPermit, swapThroughEthWithPermit và sellWithPermit nhận cùng các tham số và permit của bên gọi cho đúng amountIn, được ký cho router:

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

Với mỗi token Duel, chủ ví ký permit trong một domain EIP-712 tên là Duel, phiên bản 1, gắn với địa chỉ của token đó (hàm eip712Domain() của token, ERC-5267, cho biết điều đó). Router dùng permit trước và bỏ qua nếu nó thất bại: một permit đã được ai đó gửi trước đã đặt allowance, còn một permit thất bại không thay đổi gì; khi đó swap dùng phần mà allowance hiện có đáp ứng và bị hoàn tác nếu không đủ. Sau một swap chỉ được khớp một phần, phần allowance chưa dùng vẫn có hiệu lực: thời hạn permit giới hạn thời điểm có thể dùng chữ ký, nhưng không làm hết hạn allowance đã cấp. Router chỉ kéo token từ bên gọi. ETH của một lần chuyển phe ở lại trong PoolManager để pool thứ hai chi; một pool của Duel chi hết số đó, nếu không hook sẽ hoàn tác swap (PartialFill), còn trên bất kỳ pool nào khác, phần không thể chi sẽ được trả lại cho bên gọi.

Ra mắt một trận đấu

DuelFactory.createPair(CreateParams) ra mắt một trận đấu; hãy gửi kèm phí ra mắt và các lệnh mua khi ra mắt, phần dư được hoàn lại. CreateParams gồm nameA, symbolA, nameD, symbolD (phe B), metadataURI, buyA, buyD (số ETH chi cho mỗi phe khi ra mắt, không chịu thuế chống snipe), supporter, category (theo cách đánh số thể loại của ứng dụng, 0 nếu không có) và config, tức configHash() của factory đúng như bạn đã đọc: lần ra mắt bị hoàn tác với ConfigChanged nếu điều khoản đã thay đổi từ lúc đó. Một cái tên dài từ 1 đến 128 byte, một ticker từ 1 đến 40 và URI siêu dữ liệu tối đa 4.096 (InvalidMetadata); một lệnh mua khi ra mắt vượt quá 5% nguồn cung của một phe sẽ bị hoàn tác (CreatorBuyTooLarge).

Factory bắt đầu với các lần ra mắt bị tạm dừng. Khi paused() là true, chỉ owner() mới có thể ra mắt trận đấu; các lệnh gọi khác bị hoàn tác với CreationPaused. Chủ sở hữu mở các lần ra mắt bằng setPaused(false). Các trận đấu hiện có vẫn được giao dịch.

Sự kiện

Hợp đồngSự kiệnKhi nào
DuelFactoryPairCreated(pairId, creator, tokenA, tokenD)Một trận đấu được khởi chạy
DuelHookPairRegistered(pairId, tokenA, tokenD, creator, category)Các pool của nó được đăng ký với hook, trong cùng giao dịch; category là chủ đề của nó, theo cách đánh số thể loại của ứng dụng (0 nếu không có)
DuelHookFeeTaken(pairId, fee, snipeTax)Một giao dịch với ETH đã trả phí
DuelHookSwitchFeeBurned(pairId, token, amount)Một lần chuyển phe đã đốt phí của nó
DuelHookArbitrageCaptured(pairId, profit)Việc tái cân bằng đã nạp thêm vào quỹ
DuelHookRoundSettled(pairId, roundIndex, averageTick, winner, ethSpent, tokensBurned)Một vòng đã kết thúc
DuelHookCreatorFeesClaimed(pairId, to, amount)Một người tạo đã được trả
DuelFactoryCreatorTransferStarted(pairId, creator, newCreator)Một người tạo đã đề nghị vai trò của mình (tới địa chỉ không: rút lại đề nghị)
DuelHookCreatorChanged(pairId, from, to)Vai trò người tạo của một trận đấu đã đổi chủ
DuelHookCreatorFeesGivenUp(pairId, creator)Người tạo đã chuyển vĩnh viễn phần phí tương lai của mình vào các quỹ
DuelHookProtocolFeesClaimed(to, amount)Kho bạc đã được trả
DuelHookProtocolRateSet(rateBps)Phần của giao thức trong phí swap của mọi trận đấu đã thay đổi ngay: giao thức lấy rateBps của phần đó (10.000 là toàn bộ), phần còn lại vào các quỹ
DuelFactoryConfigProposed(config, eta), ConfigApplied(config), ConfigCancelled()Điều khoản ra mắt được đề xuất cho 48 giờ sau, được áp dụng, bị rút lại
DuelFactoryPausedSet(paused)Các lần ra mắt bị tạm dừng hoặc được tiếp tục
PoolManagerSwap(id, sender, amount0, amount1, sqrtPriceX96, liquidity, tick, fee)Mọi giao dịch, kể cả việc tái cân bằng của chính hook (sender của nó là hook)

Danh sách giao dịch đọc các swap từ cả ba pool; biểu đồ giá và tỷ lệ chỉ đọc hai pool ETH. Danh sách người nắm giữ dùng các sự kiện Transfer của token. Subgraph lập chỉ mục swap, số dư, người nắm giữ và các vòng đã chốt để ứng dụng có thể tải lịch sử mà không phải phát lại chuỗi từ lúc ra mắt.

Chốt vòng và nhận phí

  • settle(pairId) chốt một vòng đã kết thúc. Bất kỳ ai cũng có thể gọi.
  • claimCreatorFees(pairId) chỉ trả cho người tạo trận đấu, tức ví đã khởi chạy nó hoặc địa chỉ được chuyển giao vai trò; claimProtocolFees() trả cho kho bạc được cấu hình vào lúc nhận. Bất kỳ ai cũng có thể kích hoạt cả hai. Kho bạc có thể thay đổi sau thời gian chờ 48 giờ của factory; bên gọi không thể chọn bên nhận khác.
  • transferCreator(pairId, newCreator) trên factory đề nghị vai trò người tạo của một trận đấu, và acceptCreator(pairId) do newCreator gọi sẽ nhận vai trò đó; pendingCreator(pairId) đọc lời đề nghị. Chỉ người tạo mới đề nghị được, và chỉ địa chỉ được đề nghị mới nhận được.
  • giveUpCreatorFees(pairId) trên hook chuyển vĩnh viễn phần phí tương lai của người tạo vào các quỹ. Chỉ người tạo hiện tại mới có thể gọi hàm này. Người tạo vẫn có thể nhận phí đã tích lũy, và vai trò người tạo vẫn có thể được chuyển giao.