Router कॉन्ट्रैक्ट्स इसके लिए एक यूजर-फेसिंग स्मार्ट कॉन्ट्रैक्ट (user-facing smart contract) प्रदान करते हैं:
- सुरक्षित रूप से LP tokens को मिंट और बर्न करने (लिक्विडिटी जोड़ने और हटाने)
- सुरक्षित रूप से pair tokens को स्वैप करने
- वे रैप्ड ईथर (wrapped Ether) (WETH) ERC20 कॉन्ट्रैक्ट के साथ इंटीग्रेट करके Ether को स्वैप करने की क्षमता जोड़ते हैं।
- वे कोर कॉन्ट्रैक्ट से छोड़े गए slippage से संबंधित सेफ्टी चेक (safety checks) जोड़ते हैं।
- वे फी ऑन ट्रांसफर टोकन (fee on transfer tokens) के लिए सपोर्ट जोड़ते हैं।
Router02 वह सब कुछ है जो Router01 करता है, साथ ही फी ऑन ट्रांसफर टोकन के लिए सपोर्ट भी जुड़ा है
जब हम पहली बार पेरीफेरी रिपॉजिटरी (periphery repository) में कॉन्ट्रैक्ट्स फोल्डर खोलते हैं, तो हमें तीन कॉन्ट्रैक्ट दिखाई देते हैं

Router02 असल में Router01 ही है, जिसमें फी ऑन ट्रांसफर टोकन (fee on transfer tokens) के लिए अतिरिक्त फ़ंक्शंस हैं। जब हम Router02 का इंटरफ़ेस देखते हैं, तो हम देख सकते हैं कि यह Router01 (लाल बॉक्स) को इनहेरिट (inherit) करता है (जिसका अर्थ है कि यह इसके सभी फ़ंक्शंस को लागू करता है), और इसमें निम्नलिखित अतिरिक्त फ़ंक्शंस हैं, जो सभी फी ऑन ट्रांसफर टोकन के सपोर्ट के साथ ऑपरेशन्स करने के लिए हैं (पीला हाइलाइट)।

swapExactTokensForTokens और swapTokensForExactTokens
आइए टोकन स्वैप करने के लिए Router फ़ंक्शंस से शुरुआत करें। दो ऐसे फ़ंक्शन हैं जो इस काम को पूरा करते हैं (हरे रंग में हाइलाइट किए गए)।

इन फ़ंक्शन नामों में अंतर इस प्रकार है:
swapExactTokensForTokensमें “पहला टोकन सटीक (exact) है” का मतलब है कि जिस इनपुट टोकन को आप स्वैप कर रहे हैं उसकी मात्रा (amount) एक निश्चित (fixed) मात्रा है।swapTokensForExactTokensमें, “दूसरा टोकन सटीक (exact) है” यह दर्शाता है कि आउटपुट टोकन की वह मात्रा जो आप प्राप्त करना चाहते हैं, एक निश्चित मात्रा है।
यदि कोई यूजर केवल दो टोकन स्वैप कर रहा है, तो वे इन फ़ंक्शंस को एक address[] calldata path ऐरे (array) (नीले रंग में हाइलाइट किया गया) [address(tokenIn), address(tokenOut)] प्रदान करेंगे। यदि वे पूल्स (pools) के बीच हॉप (hop) कर रहे हैं, तो वे [address(tokenIn), address(intermediateToken), …, address(tokenOut)] निर्दिष्ट करेंगे।
swapExactTokensForTokens
swapExactTokensForTokens के मामले में, यूजर बिल्कुल सटीक रूप से बताता है कि वे पहले टोकन का कितना हिस्सा जमा (deposit) करने जा रहे हैं और आउटपुट टोकन की वह न्यूनतम मात्रा (minimum amount) कितनी है जिसे वे स्वीकार करेंगे।
उदाहरण के लिए, मान लें कि हम 50 token1 के लिए 25 token0 का ट्रेड करना चाहते हैं। यदि यह वर्तमान स्थिति (current state) में बिल्कुल सटीक कीमत है, तो यह हमारे ट्रांजैक्शन के कन्फर्म होने से पहले कीमत में बदलाव के लिए कोई टॉलरेंस (tolerance) नहीं छोड़ता है, जिससे ट्रांजैक्शन रिवर्ट (revert) हो सकता है। इसलिए हम इसके बजाय न्यूनतम आउटपुट 49.5 token1 निर्दिष्ट करते हैं, जो परोक्ष रूप से (implicitly) 1% टॉलरेंस छोड़ता है।
swapTokensForExactTokens
इस मामले में हम यह निर्दिष्ट करते हैं कि हमें ठीक 50 token1 चाहिए, लेकिन इसे प्राप्त करने के लिए हम अधिकतम 25.5 token0 तक ट्रेड करने के इच्छुक हैं।
कौन सा swap फंक्शन इस्तेमाल करें?
EOA का उपयोग करने वाले अधिकांश यूजर संभवतः एग्जेक्ट इनपुट फ़ंक्शन (exact input function) का उपयोग करना चुनेंगे, क्योंकि उन्हें अप्रूवल स्टेप (approval step) की आवश्यकता होती है, और यदि उन्हें स्वीकृत (approved) मात्रा से अधिक इनपुट करने की आवश्यकता पड़ी तो ट्रेड फेल हो जाएगा। एक सटीक इनपुट होने से, वे सटीक मात्रा को अप्रूव कर सकते हैं। हालाँकि, Uniswap के साथ इंटीग्रेट होने वाले स्मार्ट कॉन्ट्रैक्ट्स की आवश्यकताएं अधिक जटिल हो सकती हैं, इसलिए राउटर उन्हें दोनों के लिए विकल्प देता है।
swap कैसे काम करता है
जब इनपुट सटीक (swapExactTokensForTokens) होता है, तो फ़ंक्शन सिंगल स्वैप या स्वैप की एक चेन के आधार पर अपेक्षित (expected) आउटपुट की भविष्यवाणी करता है। यदि परिणामी आउटपुट (resulting output) यूजर द्वारा निर्दिष्ट मात्रा से कम है, तो फ़ंक्शन रिवर्ट हो जाता है। एग्जेक्ट आउटपुट के लिए इसके विपरीत होता है: यह आवश्यक इनपुट की गणना करता है और यदि यह यूजर द्वारा निर्दिष्ट सीमा (threshold) से ऊपर है तो रिवर्ट हो जाता है।
फिर दोनों फ़ंक्शन यूजर के टोकन को पेयर (pair) में ट्रांसफर कर देंगे (याद रखें, Uniswap V2 Pair में पेयर कॉन्ट्रैक्ट फ़ंक्शन swap() को कॉल करने से पहले टोकन को कॉन्ट्रैक्ट में भेजे जाने की आवश्यकता होती है)। अंत में, वे दोनों इंटरनल _swap() फ़ंक्शन को कॉल करते हैं जिस पर आगे चर्चा की गई है।

_swap() फ़ंक्शन
आंतरिक रूप से (Under the hood), swap() नाम वाले सभी पब्लिक-फेसिंग फ़ंक्शंस नीचे दिखाए गए _swap() इंटरनल फ़ंक्शन को कॉल करते हैं।
याद करें कि कोर swap फ़ंक्शन के लिए फ़ंक्शन सिग्नेचर (function signature) दोनों टोकन के लिए amountOut निर्दिष्ट करता है और amountIn उस मात्रा से निहित (implied) होता है जिसे फ़ंक्शन कॉल किए जाने से पहले ट्रांसफर किया गया था।

_addLiquidity
लिक्विडिटी जोड़ने के लिए सेफ्टी चेक (safety checks) याद हैं? विशेष रूप से, हम यह सुनिश्चित करना चाहते हैं कि हम दोनों टोकन को बिल्कुल उसी अनुपात (ratio) में जमा करें जो वर्तमान में पेयर (pair) का है, अन्यथा हम जितने LP टोकन मिंट करते हैं, वह हमारे द्वारा प्रदान की गई मात्रा और पेयर बैलेंस के बीच के दोनों अनुपातों में से जो सबसे खराब (worse) होगा, उसके बराबर होता है। हालांकि, लिक्विडिटी प्रोवाइडर द्वारा लिक्विडिटी जोड़ने का प्रयास करने और ट्रांजैक्शन कन्फर्म होने के बीच अनुपात बदल सकता है।
इससे बचने के लिए, एक लिक्विडिटी प्रोवाइडर को (एक पैरामीटर के रूप में) वह न्यूनतम बैलेंस (minimum balance) प्रदान करना होगा जिसे वे token0 और token1 के लिए जमा करना चाहते हैं (UniswapV2 उन्हें amountAMin और amountBMin कहता है)। फिर वे उन न्यूनतम मात्राओं से अधिक मात्रा ट्रांसफर करते हैं (UniswapV2 उन्हें amountADesired और amountBDesired कहता है)। यदि पेयर अनुपात इस तरह से बदल गया है कि न्यूनतम मात्राओं का अब सम्मान नहीं किया जा सकता (minimums are no longer respected), तो ट्रांजैक्शन रिवर्ट हो जाता है।
_addLiquidity amountADesired लेगा और tokenB की सही मात्रा की गणना करेगा जो अनुपात का सम्मान (respect) करेगा। यदि यह मात्रा amountBDesired (लिक्विडिटी प्रोवाइडर द्वारा भेजी गई B की मात्रा) से अधिक है, तो यह amountBDesired से शुरू होगा और B की इष्टतम मात्रा (optimal amount) की गणना करेगा। लॉजिक नीचे दिखाया गया है। ध्यान दें कि यदि कोई पेयर कॉन्ट्रैक्ट पहले से मौजूद नहीं है, तो लिक्विडिटी जोड़ने से एक नया पेयर कॉन्ट्रैक्ट बन सकता है।

उदाहरण के लिए, मान लें कि वर्तमान पेयर बैलेंस 100 token0 और 300 token1 है। हम क्रमशः 20 और 60 token0 और token1 जोड़ना चाहते हैं, लेकिन पेयर अनुपात बदल सकता है। इसलिए हम इसके बजाय 21 token0 और 63 token1 के लिए राउटर को अप्रूव करते हैं, यह कहते हुए कि हम न्यूनतम 20 और 60 जमा करना चाहते हैं। यदि अनुपात इस तरह बदलता है कि जमा करने के लिए token0 की इष्टतम मात्रा (optimal amount) 19.9 हो जाती है, तो ट्रांजैक्शन रिवर्ट हो जाता है।
याद करें कि हमने कहा था quote को ऑरेकल (oracle) के रूप में उपयोग नहीं किया जाना चाहिए, और वह अब भी सच है। हालांकि लिक्विडिटी जोड़ने के उद्देश्यों के लिए हमें पिछली कीमतों के औसत में कोई दिलचस्पी नहीं है, बल्कि अभी वर्तमान कीमत (पूल अनुपात) में दिलचस्पी है क्योंकि लिक्विडिटी प्रोवाइडर को इसका सम्मान करना चाहिए।
addLiquidity(), और addLiquidityEth()
ये फ़ंक्शंस स्वतः स्पष्ट (self-explanatory) होने चाहिए। वे सबसे पहले ऊपर दिए गए _addLiquidity का उपयोग करके इष्टतम अनुपात (optimal ratio) की गणना करते हैं, फिर एसेट्स को पेयर में ट्रांसफर करते हैं, उसके बाद पेयर पर mint कॉल करते हैं। एकमात्र अंतर यह है कि addLiquidityEth फ़ंक्शन पहले Ether को WETH में रैप (wrap) करेगा।

लिक्विडिटी हटाना
रिमूव लिक्विडिटी (Remove liquidity) burn को कॉल करता है लेकिन सेफ्टी चेक (safety checks) के रूप में amountAMin और amountBMin (लाल हाइलाइट्स) पैरामीटर्स का उपयोग करता है ताकि यह सुनिश्चित हो सके कि लिक्विडिटी प्रोवाइडर को टोकन की उतनी मात्रा वापस मिले जिसकी वे उम्मीद कर रहे हैं। यदि लिक्विडिटी टोकन के बर्न होने से पहले टोकन का अनुपात (ratio) नाटकीय रूप से बदल जाता है, तो टोकन बर्न करने वाले यूजर को टोकन A या B की वह मात्रा वापस नहीं मिलेगी जिसकी वे उम्मीद कर रहे हैं।
फ़ंक्शन removeLiquidityEth, removeLiquidity (हरा हाइलाइट) को कॉल करता है लेकिन राउटर को टोकन के प्राप्तकर्ता (recipient) के रूप में सेट करता है। इसके बाद रेगुलर ERC20 टोकन लिक्विडिटी प्रोवाइडर को ट्रांसफर कर दिया जाता है, और WETH को अनरैप (unwrap) करके ETH में बदल दिया जाता है, फिर लिक्विडिटी प्रोवाइडर को वापस भेज दिया जाता है।

removeLiquidityWithPermit() और removeLiquidityETHWithPermit()
ऊपर दी गई फ़ाइल में लाइन 109 पर जहाँ ग्रे रंग का कमेंट send liquidity to pair है, यह स्टेप मान कर चलता है कि पेयर कॉन्ट्रैक्ट के पास LP टोकन्स को बर्न करने के लिए उन्हें लिक्विडिटी प्रोवाइडर से ट्रांसफर करने का अप्रूवल है। इसका मतलब है कि LP टोकन्स को बर्न करने के लिए पहले पेयर को अप्रूव करने की आवश्यकता होती है। इस स्टेप को permit() के साथ छोड़ा (skip) जा सकता है, क्योंकि Uniswap V2 के LP टोकन एक ERC20 Permit Token हैं। फ़ंक्शन removeLiquidityWithPermit() को एक ही ट्रांजैक्शन में अप्रूव और बर्न करने के लिए एक सिग्नेचर (signature) प्राप्त होता है। यदि टोकन में से एक WETH है, तो लिक्विडिटी प्रोवाइडर removeLiquidityETHWithPermit() का उपयोग करेगा।
Router02: फी ऑन ट्रांसफर टोकन (fee on transfer tokens) का सपोर्ट करना
फी ऑन ट्रांसफर टोकन (fee on transfer tokens) को हैंडल करने के लिए, राउटर सीधे amountIn() (स्वैप के लिए) या liquidity() (लिक्विडिटी हटाने के लिए) जैसे आर्ग्यूमेंट्स (arguments) पर अपनी गणना नहीं कर सकता है। लिक्विडिटी जोड़ने पर फी ऑन ट्रांसफर टोकन का कोई प्रभाव नहीं पड़ता है क्योंकि यूजर को केवल उसी चीज़ का क्रेडिट दिया जाता है जो वे वास्तव में पेयर में ट्रांसफर करते हैं।


UniswapV2Library के चारों ओर रैपर्स (Wrappers)
Router लाइब्रेरी के बाकी फ़ंक्शंस UniswapV2Library फ़ंक्शंस के चारों ओर रैपर्स (wrappers) हैं जैसा कि नीचे दिखाया गया है।
function quote(uint amountA, uint reserveA, uint reserveB) public pure override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure override returns (uint amountOut) {
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) public pure override returns (uint amountIn) {
return UniswapV2Library.getAmountOut(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path) public view override returns (uint[] memory amounts) {
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path) public view override returns (uint[] memory amounts) {
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
deadline पैरामीटर
Uniswap V2 राउटर्स में, सभी पब्लिक फ़ंक्शंस में एक deadline पैरामीटर होता है। जब आप Uniswap पर अभी (right now) ट्रेड करते हैं, तो इसका मतलब है कि आप वर्तमान कीमतों (current prices) पर ट्रेड करना चाहते हैं।
Uniswap के साथ इंटीग्रेट होने वाला स्मार्ट कॉन्ट्रैक्ट लिखते समय, deadline को block.timestamp या block.timestamp प्लस एक कांस्टेंट (constant) पर सेट न करें।
आपके स्मार्ट कॉन्ट्रैक्ट को अलग से (separately) यह सुनिश्चित करने की आवश्यकता है कि यूजर द्वारा सबमिट किया गया ट्रांजैक्शन बहुत पुराना न हो। इसका मतलब है कि आपके अपने कॉन्ट्रैक्ट को यूजर से deadline पैरामीटर स्वीकार करने और उसे Uniswap को फॉरवर्ड करने की आवश्यकता है, या यदि block.timestamp > deadline है तो इसे रिवर्ट करना होगा।
पुराने ट्रांजैक्शन्स का शोषण (exploit) कैसे करें
एक दुर्भावनापूर्ण (malicious) ब्लॉक बिल्डर स्वैप ट्रांजैक्शन्स को “होल्ड (hold on)” करके रख सकता है और उन्हें बहुत बाद में तब एग्जीक्यूट कर सकता है जब ऐसे ट्रांजैक्शन्स कीमत में हेरफेर करने के लिए उपयोगी हों, या यूजर पर एक प्रतिकूल (unfavorable) कीमत पर टोकन डंप (dump) करने के लिए उपयोगी हों। एक deadline पैरामीटर उस टाइम विंडो को सीमित कर देता है जिसमें कोई हमलावर (attacker) इस तरह का एक्सप्लॉइट (exploit) कर सकता है। एक deadline भविष्य में इतनी दूर होनी चाहिए ताकि नेटवर्क कंजेशन (congestion) के दौरान भी ट्रांजैक्शन को एग्जीक्यूट करने का समय हो, लेकिन इससे अधिक नहीं। इसका आम तौर पर मतलब है कि deadline ट्रांजैक्शन के साइन किए जाने के कुछ मिनटों के क्रम (order of minutes) में होनी चाहिए।
हालाँकि, यदि कोई स्मार्ट कॉन्ट्रैक्ट deadline को शामिल नहीं करता है या deadline को अनदेखा करके और वर्तमान block.timestamp को Uniswap पर फॉरवर्ड करके पैरामीटर को बेकार (useless) बना देता है, तो यूजर सुरक्षित नहीं है।
कभी भी amountMin को शून्य (zero) या amountMax को type(uint).max पर सेट न करें
एक और बहुत आम गलती amountMin को शून्य या amountMax को बहुत अधिक मान (value) पर सेट करना है। यह प्राइस स्लिपेज (price slippage) और सैंडविच अटैक (sandwich attacks) के खिलाफ सुरक्षा को नष्ट कर देता है।
निष्कर्ष
Router कॉन्ट्रैक्ट्स स्लिपेज प्रोटेक्शन (slippage protection) के साथ टोकन स्वैप करने के लिए एक यूजर-फेसिंग मैकेनिज्म (user-facing mechanism) प्रदान करते हैं, संभवतः कई पूल्स (pools) के पार, और ETH तथा फी-ऑन-ट्रांसफर टोकन (Router02 में) को ट्रेड करने के लिए सपोर्ट जोड़ते हैं। लिक्विडिटी जमा करने (Depositing liquidity) में फी-ऑन-ट्रांसफर टोकन के लिए हिसाब रखने (account for) की आवश्यकता नहीं होती है क्योंकि Uniswap केवल उसी का क्रेडिट देता है जो वास्तव में पूल में ट्रांसफर किया गया था।
लिक्विडिटी जमा करने वाले (depositing liquidity) फ़ंक्शंस यह सुनिश्चित करते हैं कि यूजर केवल पूल के सटीक अनुपात में ही जमा करे। लिक्विडिटी हटाना (Removing liquidity) LP टोकन को राउटर में ट्रांसफर करके फिर उन्हें बर्न करने जितना आसान हो सकता है, या इसमें WETH को अनरैप करना और फी ऑन ट्रांसफर टोकन को विथड्रॉ करना (withdrawing) शामिल हो सकता है।
इसके अतिरिक्त, ERC20 Permit के माध्यम से गैस फ्री अप्रूवल्स (gas free approvals) के लिए सपोर्ट शामिल किया गया है।
एक स्मार्ट कॉन्ट्रैक्ट जो Uniswap के साथ इंटीग्रेट होता है, उसे डिलेड स्वैप (delayed swaps) और प्राइस स्लिपेज (price slippage) के खिलाफ सुरक्षा को अक्षम (disable) नहीं करना चाहिए।
मूल रूप से 10 नवंबर, 2023 को प्रकाशित