518 lines
12 KiB
JavaScript
518 lines
12 KiB
JavaScript
(function (EXPORTS) {
|
|
//bscOperator v1.0.2
|
|
/* ETH Crypto and API Operator */
|
|
if (!window.ethers) return console.error("ethers.js not found");
|
|
const bscOperator = EXPORTS;
|
|
const isValidAddress = (bscOperator.isValidAddress = (address) => {
|
|
try {
|
|
// Check if the address is a valid checksum address
|
|
const isValidChecksum = ethers.utils.isAddress(address);
|
|
// Check if the address is a valid non-checksum address
|
|
const isValidNonChecksum =
|
|
ethers.utils.getAddress(address) === address.toLowerCase();
|
|
return isValidChecksum || isValidNonChecksum;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
});
|
|
const BEP20ABI = [
|
|
{
|
|
constant: true,
|
|
inputs: [],
|
|
name: "name",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "string",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: false,
|
|
inputs: [
|
|
{
|
|
name: "_spender",
|
|
type: "address",
|
|
},
|
|
{
|
|
name: "_value",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
name: "approve",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "bool",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: true,
|
|
inputs: [],
|
|
name: "totalSupply",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: false,
|
|
inputs: [
|
|
{
|
|
name: "_from",
|
|
type: "address",
|
|
},
|
|
{
|
|
name: "_to",
|
|
type: "address",
|
|
},
|
|
{
|
|
name: "_value",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
name: "transferFrom",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "bool",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: true,
|
|
inputs: [],
|
|
name: "decimals",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "uint8",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: true,
|
|
inputs: [
|
|
{
|
|
name: "_owner",
|
|
type: "address",
|
|
},
|
|
],
|
|
name: "balanceOf",
|
|
outputs: [
|
|
{
|
|
name: "balance",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: true,
|
|
inputs: [],
|
|
name: "symbol",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "string",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: false,
|
|
inputs: [
|
|
{
|
|
name: "_to",
|
|
type: "address",
|
|
},
|
|
{
|
|
name: "_value",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
name: "transfer",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "bool",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "nonpayable",
|
|
type: "function",
|
|
},
|
|
{
|
|
constant: true,
|
|
inputs: [
|
|
{
|
|
name: "_owner",
|
|
type: "address",
|
|
},
|
|
{
|
|
name: "_spender",
|
|
type: "address",
|
|
},
|
|
],
|
|
name: "allowance",
|
|
outputs: [
|
|
{
|
|
name: "",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
payable: false,
|
|
stateMutability: "view",
|
|
type: "function",
|
|
},
|
|
{
|
|
payable: true,
|
|
stateMutability: "payable",
|
|
type: "fallback",
|
|
},
|
|
{
|
|
anonymous: false,
|
|
inputs: [
|
|
{
|
|
indexed: true,
|
|
name: "owner",
|
|
type: "address",
|
|
},
|
|
{
|
|
indexed: true,
|
|
name: "spender",
|
|
type: "address",
|
|
},
|
|
{
|
|
indexed: false,
|
|
name: "value",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
name: "Approval",
|
|
type: "event",
|
|
},
|
|
{
|
|
anonymous: false,
|
|
inputs: [
|
|
{
|
|
indexed: true,
|
|
name: "from",
|
|
type: "address",
|
|
},
|
|
{
|
|
indexed: true,
|
|
name: "to",
|
|
type: "address",
|
|
},
|
|
{
|
|
indexed: false,
|
|
name: "value",
|
|
type: "uint256",
|
|
},
|
|
],
|
|
name: "Transfer",
|
|
type: "event",
|
|
},
|
|
];
|
|
const CONTRACT_ADDRESSES = {
|
|
usdc: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
|
|
usdt: "0x55d398326f99059ff775485246999027b3197955",
|
|
};
|
|
function getProvider() {
|
|
// switches provider based on whether the user is using MetaMask or not
|
|
const bscMainnet = {
|
|
chainId: 56,
|
|
name: "binance",
|
|
rpc: "https://bsc-dataseed.binance.org/",
|
|
explorer: "https://bscscan.com",
|
|
};
|
|
|
|
if (window.ethereum) {
|
|
return new ethers.providers.Web3Provider(window.ethereum);
|
|
} else {
|
|
return new ethers.providers.JsonRpcProvider(bscMainnet.rpc, bscMainnet);
|
|
}
|
|
}
|
|
function connectToMetaMask() {
|
|
return new Promise((resolve, reject) => {
|
|
// if (typeof window.ethereum === "undefined")
|
|
// return reject("MetaMask not installed");
|
|
return resolve(true);
|
|
ethereum
|
|
.request({ method: "eth_requestAccounts" })
|
|
.then((accounts) => {
|
|
console.log("Connected to MetaMask");
|
|
return resolve(accounts);
|
|
})
|
|
.catch((err) => {
|
|
console.log(err);
|
|
return reject(err);
|
|
});
|
|
});
|
|
}
|
|
const getTransactionHistory = (bscOperator.getTransactionHistory = async (
|
|
address,
|
|
cursor = null
|
|
) => {
|
|
try {
|
|
if (!address || !isValidAddress(address))
|
|
return new Error("Invalid address");
|
|
|
|
// Moralis API endpoint for BSC transactions
|
|
const MORALIS_API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjQyZWNiMjk0LTBiMGItNDg4Yy1hNjUwLTE4NmJhMjFjNjNhYyIsIm9yZ0lkIjoiNDg4NzAzIiwidXNlcklkIjoiNTAyODExIiwidHlwZUlkIjoiZjE5ZmZjYTYtNDllMS00NTdlLTllNjgtMGI1MDIyODU2N2Q4IiwidHlwZSI6IlBST0pFQ1QiLCJpYXQiOjE3Njc1NDkxNDQsImV4cCI6NDkyMzMwOTE0NH0.yr_jtBCrrid4Y5d48iTwJ4PwgMOZn8mwWyiQ7dAmNvw";
|
|
|
|
// Fetch 10 transactions per page
|
|
const url = `https://deep-index.moralis.io/api/v2/${address}?chain=bsc&limit=10${cursor ? `&cursor=${cursor}` : ''}`;
|
|
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"X-API-Key": MORALIS_API_KEY
|
|
}
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.result && Array.isArray(data.result)) {
|
|
// Get current block number to calculate confirmations
|
|
const provider = getProvider();
|
|
const currentBlockNumber = await provider.getBlockNumber();
|
|
|
|
const transactions = data.result.map((tx) => ({
|
|
hash: tx.hash,
|
|
from: tx.from_address,
|
|
to: tx.to_address,
|
|
value: tx.value,
|
|
timeStamp: Math.floor(new Date(tx.block_timestamp).getTime() / 1000),
|
|
blockNumber: tx.block_number,
|
|
confirmations: currentBlockNumber - parseInt(tx.block_number),
|
|
gasPrice: tx.gas_price,
|
|
gasUsed: tx.receipt_gas_used,
|
|
}));
|
|
|
|
// Return transactions along with cursor for next page
|
|
return {
|
|
transactions: transactions,
|
|
nextCursor: data.cursor || null,
|
|
hasMore: !!data.cursor
|
|
};
|
|
} else {
|
|
console.error("Error fetching transaction history:", data.message || "No results");
|
|
return {
|
|
transactions: [],
|
|
nextCursor: null,
|
|
hasMore: false
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error("Error:", error.message);
|
|
return error;
|
|
}
|
|
});
|
|
|
|
const getTransactionDetails = (bscOperator.getTransactionDetails = async (
|
|
txHash
|
|
) => {
|
|
try {
|
|
if (!txHash || !/^0x([A-Fa-f0-9]{64})$/.test(txHash)) return null;
|
|
const provider = getProvider();
|
|
const tx = await provider.getTransaction(txHash);
|
|
|
|
if (!tx) return null;
|
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
|
|
let timestamp = null;
|
|
let confirmations = 0;
|
|
if (tx.blockNumber) {
|
|
const block = await provider.getBlock(tx.blockNumber);
|
|
timestamp = block.timestamp;
|
|
const currentBlockNumber = await provider.getBlockNumber();
|
|
confirmations = currentBlockNumber - tx.blockNumber;
|
|
}
|
|
|
|
return {
|
|
hash: tx.hash,
|
|
from: tx.from,
|
|
to: tx.to,
|
|
value: tx.value,
|
|
gasPrice: tx.gasPrice,
|
|
gasUsed: receipt ? receipt.gasUsed : null,
|
|
blockNumber: tx.blockNumber,
|
|
timeStamp: timestamp,
|
|
confirmations: confirmations,
|
|
status: receipt ? (receipt.status ? "success" : "failed") : "pending",
|
|
};
|
|
} catch (error) {
|
|
console.error("Error fetching transaction details:", error);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
const getBalance = (bscOperator.getBalance = async (address) => {
|
|
try {
|
|
if (!address || !isValidAddress(address)) {
|
|
return new Error("Invalid address");
|
|
}
|
|
|
|
const provider = getProvider();
|
|
const balanceWei = await provider.getBalance(address);
|
|
const balanceEth = parseFloat(ethers.utils.formatEther(balanceWei));
|
|
return balanceEth;
|
|
} catch (error) {
|
|
console.error("Error in getBalance:", error);
|
|
return error;
|
|
}
|
|
});
|
|
|
|
const getTokenBalance = (bscOperator.getTokenBalance = async (
|
|
address,
|
|
token,
|
|
{ contractAddress } = {}
|
|
) => {
|
|
try {
|
|
if (!address) {
|
|
|
|
throw new Error("Address not specified");
|
|
}
|
|
if (!token) {
|
|
|
|
throw new Error("Token not specified");
|
|
}
|
|
if (!CONTRACT_ADDRESSES[token] && !contractAddress) {
|
|
|
|
throw new Error("Contract address of token not available");
|
|
}
|
|
|
|
const provider = getProvider();
|
|
const contract = new ethers.Contract(
|
|
CONTRACT_ADDRESSES[token] || contractAddress,
|
|
BEP20ABI,
|
|
provider
|
|
);
|
|
|
|
let balance = await contract.balanceOf(address);
|
|
|
|
const decimals = 18;
|
|
balance = parseFloat(ethers.utils.formatUnits(balance, decimals));
|
|
|
|
return balance;
|
|
} catch (e) {
|
|
console.error("Error in getTokenBalance:", e);
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
|
|
const estimateGas = (bscOperator.estimateGas = async ({
|
|
privateKey,
|
|
receiver,
|
|
amount,
|
|
}) => {
|
|
try {
|
|
const provider = getProvider();
|
|
const signer = new ethers.Wallet(privateKey, provider);
|
|
return provider.estimateGas({
|
|
from: signer.address,
|
|
to: receiver,
|
|
value: ethers.utils.parseUnits(amount, "ether"),
|
|
});
|
|
} catch (e) {
|
|
throw new Error(e);
|
|
}
|
|
});
|
|
|
|
const sendTransaction = (bscOperator.sendTransaction = async ({
|
|
privateKey,
|
|
receiver,
|
|
amount,
|
|
}) => {
|
|
try {
|
|
const provider = getProvider();
|
|
const signer = new ethers.Wallet(privateKey, provider);
|
|
|
|
const limit = await estimateGas({ privateKey, receiver, amount });
|
|
|
|
const tx = await signer.sendTransaction({
|
|
to: receiver,
|
|
value: ethers.utils.parseUnits(amount, "ether"),
|
|
gasLimit: limit,
|
|
nonce: await signer.getTransactionCount(),
|
|
maxPriorityFeePerGas: ethers.utils.parseUnits("2", "gwei"),
|
|
});
|
|
|
|
return tx;
|
|
} catch (e) {
|
|
console.error("Error in sendTransaction:", e);
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
const sendToken = (bscOperator.sendToken = async ({
|
|
token,
|
|
privateKey,
|
|
amount,
|
|
receiver,
|
|
contractAddress,
|
|
}) => {
|
|
try {
|
|
const wallet = new ethers.Wallet(privateKey, getProvider());
|
|
|
|
const tokenContract = new ethers.Contract(
|
|
CONTRACT_ADDRESSES[token] || contractAddress,
|
|
BEP20ABI,
|
|
wallet
|
|
);
|
|
|
|
const decimals = await tokenContract.decimals();
|
|
|
|
const amountWei = ethers.utils.parseUnits(amount.toString(), decimals);
|
|
|
|
const gasLimit = await tokenContract.estimateGas.transfer(
|
|
receiver,
|
|
amountWei
|
|
);
|
|
|
|
const gasPrice = await wallet.provider.getGasPrice();
|
|
|
|
const gasCost = gasPrice.mul(gasLimit);
|
|
|
|
const balance = await wallet.getBalance();
|
|
|
|
if (balance.lt(gasCost)) {
|
|
throw new Error("Insufficient funds for gas fee");
|
|
}
|
|
|
|
const tx = await tokenContract.transfer(receiver, amountWei, {
|
|
gasLimit,
|
|
gasPrice,
|
|
});
|
|
return tx;
|
|
} catch (e) {
|
|
console.error("Error in sendToken:", e);
|
|
throw e;
|
|
}
|
|
});
|
|
})("object" === typeof module ? module.exports : (window.bscOperator = {}));
|