dimesyboy
dimesyboy

Reputation: 37

Erc20 Programatic Token Swap Parameters for broadcasting Pulsechain (python)

I am 1. trying to send from wallet a to wallet b, and 2. buy a token ("mars", in this case) with wallet a. I'm trying these 2 tasks programatically, and have a simple front end to test the functionality.

everything appears to be working ok; the calls to the rpc node to fetch token metadata and gas price work good, and when i send the transaction, it says success, and even gives me a tx hash. However, that's where it stops. It's been 2 days, i've sent a handful or transactions from a few wallets, but none of them have shown up on pulsescan, shown up in either wallet, or completed. they are just stalling wherever they are. and that's what i need help with.

where is this limbo the transactions are stuck in? why are they stuck? What am i doing wrong? here is my code (minus personal keys etc. )

import requests, threading, time, math
from web3 import Web3
from web3.exceptions import TransactionNotFound

app = Flask(__name__)

# ------------------
# Configuration
# ------------------
moralis_rpc_node_url = "https://pulsechain-rpc.publicnode.com"
private_key_bot = "*********"
from_address = "0x679FC7C20C0106D9F55047503994CeAb1D78c7Eb"
to_address = "0x835c93fb0DfF2e310c9bc6324818C4F5755bD4d4"
CHAIN_ID = 369

# Swap Router configuration (using provided PULSEXROUTER02)
swap_router_address = Web3.to_checksum_address("0x165C3410fC91EF562C50559f7d2289fEbed552d9")
swap_router_abi = [
    {
        "inputs": [
            {"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": "swapExactETHForTokens",
        "outputs": [
            {"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}
        ],
        "stateMutability": "payable",
        "type": "function"
    }
]

erc20_abi = [
    {
        "constant": True,
        "inputs": [],
        "name": "name",
        "outputs": [{"name": "", "type": "string"}],
        "type": "function"
    },
    {
        "constant": True,
        "inputs": [],
        "name": "symbol",
        "outputs": [{"name": "", "type": "string"}],
        "type": "function"
    },
    {
        "constant": True,
        "inputs": [],
        "name": "decimals",
        "outputs": [{"name": "", "type": "uint8"}],
        "type": "function"
    }
]

wpls_address = Web3.to_checksum_address("0xA1077a294dDE1B09bB078844df40758a5D0f9a27")

# ------------------
# Web3 Setup
# ------------------
w3 = Web3(Web3.HTTPProvider(moralis_rpc_node_url))
if not w3.is_connected():
    raise Exception("Could not connect to the RPC node.")

swap_router = w3.eth.contract(address=swap_router_address, abi=swap_router_abi)

# ------------------
# Helper Function to Send Tx with Nonce Adjustment and Debug Info
# ------------------
def send_tx_with_nonce_adjustment(txn, from_addr, max_attempts=3, gas_bump_factor=1.1):
    attempts = 0
    debug_info = {}
    while attempts < max_attempts:
        try:
            signed_txn = w3.eth.account.sign_transaction(txn, private_key_bot)
            debug_info["signed_tx"] = signed_txn.raw_transaction.hex()
            tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
            debug_info["node_response"] = tx_hash.hex()
            return tx_hash.hex(), debug_info
        except Exception as e:
            err_str = str(e)
            if "could not replace existing tx" in err_str or "nonce too low" in err_str:
                attempts += 1
                new_nonce = w3.eth.get_transaction_count(from_addr, "pending")
                txn['nonce'] = new_nonce
                txn['gasPrice'] = int(txn['gasPrice'] * gas_bump_factor)
                debug_info["adjustment_attempt"] = attempts
                debug_info["updated_nonce"] = new_nonce
                debug_info["updated_gasPrice"] = txn['gasPrice']
                print(f"Attempt {attempts}: Updated nonce to {new_nonce} and gasPrice to {txn['gasPrice']}")
            else:
                debug_info["error"] = err_str
                raise e
    raise Exception("Failed to send transaction after nonce adjustments")

# ------------------
# New Endpoint: Transaction Estimated Time
# ------------------
@app.route('/tx_estimate_time', methods=['GET'])
def tx_estimate_time():
    tx_hash = request.args.get("tx_hash")
    if not tx_hash:
        return jsonify({"error": "tx_hash not provided"}), 400
    try:
        receipt = w3.eth.get_transaction_receipt(tx_hash)
    except TransactionNotFound:
        receipt = None
    if receipt is not None:
        return jsonify({"tx_hash": tx_hash, "status": "Confirmed"})
    avg_block_time = 2  
    return jsonify({"tx_hash": tx_hash, "status": "Pending", "estimated_time_left": f"Approximately {avg_block_time} seconds for next block"})

# ------------------
# HTML Template
# ------------------
HTML_TEMPLATE = """
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Pulsechain Transaction & Swap</title>
  </head>
  <body>
    <h1>Pulsechain Native Transfer</h1>
    <form id="txForm">
      <label for="amount">Amount to Send (in PLS):</label>
      <input type="text" id="amount" name="amount" required>
      <br><br>
      <button type="button" onclick="estimate()">Estimate Gas Fee</button>
      <button type="button" onclick="previewTx()">Preview Transaction</button>
      <button type="button" onclick="sendTx()">Send Transaction</button>
    </form>
    <hr>
    <h3>Estimate</h3>
    <p id="estimateResult">Gas Estimate: N/A</p>
    <hr>
    <h3>Transaction Preview</h3>
    <pre id="previewResult">N/A</pre>
    <hr>
    <h3>Native Transfer Result</h3>
    <p id="result">Result: N/A</p>
    <h4>Transaction Status:</h4>
    <p id="txStatus">N/A</p>
    <button type="button" onclick="updateStatus('txStatus', nativeTxHash)">Update Status</button>
    <h4>RPC Debug Info:</h4>
    <pre id="rpcDebug">N/A</pre>
    <hr>
    <h1>Swap Tokens</h1>
    <form id="swapForm">
      <label for="token_to_buy">Token to Buy (address):</label>
      <input type="text" id="token_to_buy" name="token_to_buy" required>
      <br><br>
      <label for="pls_amount">PLS to Spend:</label>
      <input type="text" id="pls_amount" name="pls_amount" required>
      <br><br>
      <label for="gas_bump">Gas Bump Factor (optional):</label>
      <input type="hidden" id="gas_bump" name="gas_bump" value="1.0">
      <button type="button" onclick="setGasBump(1.05)">+5%</button>
      <button type="button" onclick="setGasBump(1.10)">+10%</button>
      <button type="button" onclick="setGasBump(1.20)">+20%</button>
      <br><br>
      <label for="swap_from">Swap From Address (optional):</label>
      <input type="text" id="swap_from" name="swap_from">
      <br><br>
      <button type="button" onclick="previewSwap()">Preview Swap</button>
      <button type="button" onclick="confirmSwap()">Confirm Swap</button>
    </form>
    <hr>
    <h3>Swap Preview</h3>
    <pre id="swapPreview">N/A</pre>
    <hr>
    <h3>Swap Result</h3>
    <p id="swapResult">Result: N/A</p>
    <h4>Swap Transaction Status:</h4>
    <p id="swapTxStatus">N/A</p>
    <button type="button" onclick="updateStatus('swapTxStatus', swapTxHash)">Update Swap Status</button>
    <script>
      var nativeTxHash = "";
      var swapTxHash = "";
      
      function setGasBump(value) {
        document.getElementById("gas_bump").value = value;
        alert("Gas bump factor set to " + value);
      }
      function estimate() {
        const formData = new FormData(document.getElementById("txForm"));
        fetch('/estimate', { method: 'POST', body: formData })
          .then(response => response.json())
          .then(data => {
            if(data.error){
              document.getElementById("estimateResult").innerText = "Error: " + data.error;
            } else {
              document.getElementById("estimateResult").innerText =
                "Estimated Gas Limit: " + data.gas_limit +
                ", Gas Price (wei): " + data.gas_price +
                ", Fee (USD): " + data.gas_fee_usd;
            }
          })
          .catch(err => {
            document.getElementById("estimateResult").innerText = "Error estimating gas fee.";
            console.error(err);
          });
      }
      function previewTx() {
        const formData = new FormData(document.getElementById("txForm"));
        fetch('/preview', { method: 'POST', body: formData })
          .then(response => response.json())
          .then(data => {
            if(data.error){
              document.getElementById("previewResult").innerText = "Error: " + data.error;
            } else {
              document.getElementById("previewResult").innerText = JSON.stringify(data, null, 2);
            }
          })
          .catch(err => {
            document.getElementById("previewResult").innerText = "Error previewing transaction.";
            console.error(err);
          });
      }
      function sendTx() {
        const formData = new FormData(document.getElementById("txForm"));
        fetch('/send_tx', { method: 'POST', body: formData })
          .then(response => response.json())
          .then(data => {
            if(data.error){
              document.getElementById("result").innerText = "Error: " + data.error;
            } else {
              nativeTxHash = data.tx_hash;
              document.getElementById("result").innerText = "Transaction sent! Tx Hash: " + nativeTxHash +
                "\\nDebug Info: " + JSON.stringify(data.debug_info);
            }
          })
          .catch(err => {
            document.getElementById("result").innerText = "Error sending transaction.";
            console.error(err);
          });
      }
      function previewSwap() {
        const formData = new FormData(document.getElementById("swapForm"));
        fetch('/preview_swap', { method: 'POST', body: formData })
          .then(response => response.json())
          .then(data => {
            if(data.error){
              document.getElementById("swapPreview").innerText = "Error: " + data.error;
            } else {
              document.getElementById("swapPreview").innerText = JSON.stringify(data, null, 2);
            }
          })
          .catch(err => {
            document.getElementById("swapPreview").innerText = "Error previewing swap.";
            console.error(err);
          });
      }
      function confirmSwap() {
        const formData = new FormData(document.getElementById("swapForm"));
        fetch('/swap', { method: 'POST', body: formData })
          .then(response => response.json())
          .then(data => {
            if(data.error){
              document.getElementById("swapResult").innerText = "Error: " + data.error;
            } else {
              swapTxHash = data.tx_hash;
              document.getElementById("swapResult").innerText = "Swap Transaction sent! Tx Hash: " + swapTxHash;
            }
          })
          .catch(err => {
            document.getElementById("swapResult").innerText = "Error sending swap transaction.";
            console.error(err);
          });
      }
      function updateStatus(elementId, tx_hash) {
        if(!tx_hash){
          alert("No transaction hash available.");
          return;
        }
        fetch(`/tx_estimate_time?tx_hash=${tx_hash}`)
          .then(response => response.json())
          .then(data => {
            document.getElementById(elementId).innerText = data.status + (data.estimated_time_left ? " (" + data.estimated_time_left + ")" : "");
          })
          .catch(err => {
            console.error(err);
            alert("Error updating status.");
          });
      }
    </script>
  </body>
</html>
"""

# ------------------
# Helper Functions (Backend)
# ------------------
def fetch_current_gas_price_wei():
    payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_gasPrice"}
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    response = requests.post(moralis_rpc_node_url, json=payload, headers=headers)
    data = response.json()
    gas_price_hex = data.get("result")
    if not gas_price_hex:
        raise Exception("Failed to retrieve gas price from the node.")
    return int(gas_price_hex, 16)

def fetch_native_price_usd():
    cg_url = "https://api.coingecko.com/api/v3/simple/price?ids=pulsechain&vs_currencies=usd"
    cg_response = requests.get(cg_url)
    cg_data = cg_response.json()
    pulse_price = cg_data.get("pulsechain", {}).get("usd")
    if pulse_price is None:
        raise Exception("Failed to fetch Pulsechain price in USD.")
    return pulse_price

def cancel_tx_after_deadline(nonce, original_tx_hash):
    time.sleep(600)
    try:
        receipt = w3.eth.get_transaction_receipt(original_tx_hash)
    except TransactionNotFound:
        receipt = None
    if receipt is None:
        try:
            current_gas_price = fetch_current_gas_price_wei()
            cancel_gas_price = int(current_gas_price * 1.2)
            cancel_tx = {
                'from': from_address,
                'to': from_address,
                'value': 0,
                'nonce': nonce,
                'chainId': CHAIN_ID,
                'gas': 21000,
                'gasPrice': cancel_gas_price
            }
            signed_cancel_tx = w3.eth.account.sign_transaction(cancel_tx, private_key_bot)
            cancel_tx_hash = w3.eth.send_raw_transaction(signed_cancel_tx.raw_transaction)
            print("Cancellation transaction sent! Tx Hash:", cancel_tx_hash.hex())
        except Exception as e:
            print("Error sending cancellation transaction:", e)
    else:
        print("Transaction was mined within the deadline.")

# ------------------
# Native Transfer Endpoints
# ------------------
@app.route('/')
def index():
    return render_template_string(HTML_TEMPLATE)

@app.route('/estimate', methods=['POST'])
def estimate():
    try:
        amount_pls = request.form.get("amount")
        if not amount_pls:
            return jsonify({"error": "Amount not provided."}), 400
        try:
            amount_value = float(amount_pls.strip())
            amount_wei = int(amount_value * 10**18)
        except ValueError:
            return jsonify({"error": "Invalid amount provided."}), 400

        nonce = w3.eth.get_transaction_count(from_address, "pending")
        base_tx = {'from': from_address, 'to': to_address, 'value': amount_wei, 'nonce': nonce, 'chainId': CHAIN_ID}
        estimated_gas = w3.eth.estimate_gas(base_tx)
        gas_limit = int(estimated_gas * 1.2)
        gas_price = fetch_current_gas_price_wei()
        total_fee_wei = gas_limit * gas_price
        pulse_price = fetch_native_price_usd()
        fee_usd = (total_fee_wei * pulse_price) / 1e18

        return jsonify({
            "gas_limit": gas_limit,
            "gas_price": gas_price,
            "gas_fee_usd": f"${fee_usd:.10f}"
        })
    except Exception as ex:
        return jsonify({"error": str(ex)}), 500

@app.route('/preview', methods=['POST'])
def preview():
    try:
        amount_pls = request.form.get("amount")
        if not amount_pls:
            return jsonify({"error": "Amount not provided."}), 400
        try:
            amount_value = float(amount_pls.strip())
            amount_wei = int(amount_value * 10**18)
        except ValueError:
            return jsonify({"error": "Invalid amount provided."}), 400

        nonce = w3.eth.get_transaction_count(from_address, "pending")
        base_tx = {'from': from_address, 'to': to_address, 'value': amount_wei, 'nonce': nonce, 'chainId': CHAIN_ID}
        estimated_gas = w3.eth.estimate_gas(base_tx)
        gas_limit = int(estimated_gas * 1.2)
        gas_price = fetch_current_gas_price_wei()
        total_fee_wei = gas_limit * gas_price
        pulse_price = fetch_native_price_usd()
        fee_usd = (total_fee_wei * pulse_price) / 1e18

        preview_data = {
            "from": from_address,
            "to": to_address,
            "amount_pls": amount_value,
            "nonce": nonce,
            "chainId": CHAIN_ID,
            "estimated_gas_limit": gas_limit,
            "gas_price_wei": gas_price,
            "total_fee_wei": total_fee_wei,
            "fee_usd": f"${fee_usd:.10f}"
        }
        return jsonify(preview_data)
    except Exception as ex:
        return jsonify({"error": str(ex)}), 500

@app.route('/send_tx', methods=['POST'])
def send_tx():
    try:
        amount_pls = request.form.get("amount")
        if not amount_pls:
            return jsonify({"error": "Amount not provided."}), 400
        try:
            amount_value = float(amount_pls.strip())
            amount_wei = int(amount_value * 10**18)
        except ValueError:
            return jsonify({"error": "Invalid amount provided."}), 400

        nonce = w3.eth.get_transaction_count(from_address, "pending")
        base_tx = {'from': from_address, 'to': to_address, 'value': amount_wei, 'nonce': nonce, 'chainId': CHAIN_ID}
        estimated_gas = w3.eth.estimate_gas(base_tx)
        gas_limit = int(estimated_gas * 1.2)
        gas_price = fetch_current_gas_price_wei()

        tx = {**base_tx, 'gas': gas_limit, 'gasPrice': gas_price}
        signed_tx = w3.eth.account.sign_transaction(tx, private_key_bot)
        tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
        tx_hash_hex = tx_hash.hex()

        threading.Thread(target=cancel_tx_after_deadline, args=(nonce, tx_hash_hex), daemon=True).start()
        return jsonify({"tx_hash": tx_hash_hex, "debug_info": {"signed_tx": signed_tx.raw_transaction.hex()}})
    except Exception as ex:
        return jsonify({"error": str(ex)}), 500

@app.route('/tx_status', methods=['GET'])
def tx_status():
    tx_hash = request.args.get("tx_hash")
    if not tx_hash:
        return jsonify({"error": "tx_hash not provided"}), 400
    try:
        receipt = w3.eth.get_transaction_receipt(tx_hash)
    except Exception:
        receipt = None
    if receipt is None:
        status = "Pending"
    else:
        current_block = w3.eth.block_number
        confirmations = current_block - receipt["blockNumber"] + 1
        status = f"Confirmed in block {receipt['blockNumber']} with {confirmations} confirmations"
    return jsonify({"tx_hash": tx_hash, "status": status})

@app.route('/preview_swap', methods=['POST'])
def preview_swap():
    try:
        token_to_buy = request.form.get("token_to_buy")
        pls_amount = request.form.get("pls_amount")
        swap_from = request.form.get("swap_from")
        gas_bump = request.form.get("gas_bump", "1.0")
        try:
            gas_bump = float(gas_bump.strip())
        except:
            gas_bump = 1.0
        if not token_to_buy or not pls_amount:
            return jsonify({"error": "Token to buy or PLS amount not provided."}), 400
        try:
            pls_value = float(pls_amount.strip())
            pls_value_wei = int(pls_value * 10**18)
        except ValueError:
            return jsonify({"error": "Invalid PLS amount provided."}), 400

        if swap_from and swap_from.strip() != "":
            swap_from = Web3.to_checksum_address(swap_from.strip())
        else:
            swap_from = from_address

        token_to_buy = Web3.to_checksum_address(token_to_buy.strip())
        token_contract = w3.eth.contract(address=token_to_buy, abi=erc20_abi)
        try:
            token_name = token_contract.functions.name().call()
        except Exception:
            token_name = "Unknown"
        try:
            token_symbol = token_contract.functions.symbol().call()
        except Exception:
            token_symbol = "Unknown"
        try:
            token_decimals = token_contract.functions.decimals().call()
        except Exception:
            token_decimals = "Unknown"
        
        nonce = w3.eth.get_transaction_count(swap_from, "pending")
        base_gas_price = fetch_current_gas_price_wei()
        gas_price = int(base_gas_price * gas_bump)
        path = [wpls_address, token_to_buy]
        deadline = math.floor(time.time() + 300)
        estimated_gas = swap_router.functions.swapExactETHForTokens(
            0, path, swap_from, deadline
        ).estimate_gas({
            'from': swap_from,
            'value': pls_value_wei
        })
        gas_limit = int(estimated_gas * 1.2)
        total_fee_wei = gas_limit * gas_price
        pulse_price = fetch_native_price_usd()
        fee_usd = (total_fee_wei * pulse_price) / 1e18

        preview_data = {
            "swap_router": swap_router_address,
            "function": "swapExactETHForTokens",
            "path": path,
            "to": swap_from,
            "deadline": deadline,
            "token_to_buy": token_to_buy,
            "token_metadata": {
                "name": token_name,
                "symbol": token_symbol,
                "decimals": token_decimals
            },
            "pls_amount": pls_value,
            "from": swap_from,
            "gas_bump": gas_bump,
            "nonce": nonce,
            "estimated_gas_limit": gas_limit,
            "gas_price_wei": gas_price,
            "total_fee_wei": total_fee_wei,
            "fee_usd": f"${fee_usd:.10f}"
        }
        return jsonify(preview_data)
    except Exception as ex:
        return jsonify({"error": str(ex)}), 500

@app.route('/swap', methods=['POST'])
def swap():
    try:
        token_to_buy = request.form.get("token_to_buy")
        pls_amount = request.form.get("pls_amount")
        swap_from = request.form.get("swap_from")
        gas_bump = request.form.get("gas_bump", "1.0")
        try:
            gas_bump = float(gas_bump.strip())
        except:
            gas_bump = 1.0
        if not token_to_buy or not pls_amount:
            return jsonify({"error": "Token to buy or PLS amount not provided."}), 400
        try:
            pls_value = float(pls_amount.strip())
            pls_value_wei = int(pls_value * 10**18)
        except ValueError:
            return jsonify({"error": "Invalid PLS amount provided."}), 400

        if swap_from and swap_from.strip() != "":
            swap_from = Web3.to_checksum_address(swap_from.strip())
        else:
            swap_from = from_address

        token_to_buy = Web3.to_checksum_address(token_to_buy.strip())
        nonce = w3.eth.get_transaction_count(swap_from, "pending")
        base_gas_price = fetch_current_gas_price_wei()
        gas_price = int(base_gas_price * gas_bump)
        path = [wpls_address, token_to_buy]
        deadline = math.floor(time.time() + 300)
        txn = swap_router.functions.swapExactETHForTokens(
            0, path, swap_from, deadline
        ).build_transaction({
            'from': swap_from,
            'value': pls_value_wei,
            'nonce': nonce,
            'chainId': CHAIN_ID,
            'gas': int(swap_router.functions.swapExactETHForTokens(
                0, path, swap_from, deadline
            ).estimate_gas({
                'from': swap_from,
                'value': pls_value_wei
            }) * 1.2),
            'gasPrice': gas_price
        })
        tx_hash_hex = send_tx_with_nonce_adjustment(txn, swap_from)[0]
        return jsonify({"tx_hash": tx_hash_hex})
    except Exception as ex:
        return jsonify({"error": str(ex)}), 500

if __name__ == '__main__':
    app.run(debug=True)

also, i'm curious- on pulsechain, wei are actually beats.. does that make a difference?

any help would be appreciated, thank you for your time.

Upvotes: 1

Views: 20

Answers (0)

Related Questions