Reputation: 37
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