PeakeCoin AtomPEK Swaps - JavaScript Updates

On the route to greatness you often use a lot of swear words. I had a lot of issues trying to get this whole Atomic Swap function. I have it working on the sell of each token, but the buys don't seem to be working.
I have created a he_cors_proxy.py
to the CORS work around and have launched it on a free service. I'll write that up in the next few days.
Have I eliminated the CORS problems, yes. Does it work right now? Not exactly sure.
script.js - Main entry point. Wires up UI, handles form input, and triggers the swap logic.
import { fetchWithBackups, getHiveBlockNumberForTxId } from './api.js';
import { getSwapHivePayoutForTx, performSwap } from './swapLogic.js';
import { updateRateDisplay, setSwapResult } from './ui.js';
import { logDebug } from './utils.js';
logDebug('App loaded.');
async function handleRateDisplay() {
const token = document.getElementById('hiveToken').value;
const tokenAmount = parseFloat(document.getElementById('hiveAmount').value);
if (tokenAmount > 0) {
const swapHiveRate = await fetchSwapHiveRate(token);
updateRateDisplay(swapHiveRate, tokenAmount, token);
} else {
updateRateDisplay(null, 0, token);
}
}
document.getElementById('hiveAmount').addEventListener('input', handleRateDisplay);
document.getElementById('hiveToken').addEventListener('change', handleRateDisplay);
window.addEventListener('DOMContentLoaded', handleRateDisplay);
document.getElementById('swapKeychain').addEventListener('click', function(e) {
e.preventDefault();
logDebug('swapKeychain button clicked');
performSwap(true);
});
document.getElementById('swapHivesigner').addEventListener('click', function(e) {
e.preventDefault();
logDebug('swapHivesigner button clicked');
performSwap(false);
});
swapLogic.js - Orchestrates the swap. Validates input, kicks off sell, polls for payout, and triggers buy PEK.
import { fetchWithBackups, getHiveBlockNumberForTxId } from './api.js';
import { logDebug } from './utils.js';
import { performKeychainSell, performKeychainBuy } from './keychain.js';
export async function performSwap(useKeychain) {
const account = document.getElementById('hiveSender').value.trim();
const symbol = document.getElementById('hiveToken').value;
const quantity = parseFloat(document.getElementById('hiveAmount').value);
const swapResult = document.getElementById('swapResult');
logDebug(`Swap requested: account=${account}, symbol=${symbol}, quantity=${quantity}, useKeychain=${useKeychain}`);
swapResult.innerHTML = '';
if (!account || !symbol || !quantity || quantity <= 0) {
swapResult.innerHTML = "Please fill in all fields.";
logDebug('Swap aborted: missing fields.');
return;
}
if (symbol === 'SCALA') {
logDebug('Scala swap selected.');
swapResult.innerHTML = 'Scala swap not implemented in this module.';
return;
}
if (useKeychain) {
performKeychainSell(account, symbol, quantity, swapResult, getSwapHivePayoutForTx, getLastSwapHivePayout, performBuyPEK);
} else {
logDebug('Opening Hivesigner for marketSell.');
swapResult.innerHTML = "Hivesigner flow not implemented in this module.";
}
}
keychain.js
Handles signing and broadcasting Hive Keychain transactions, both for sell and buy. Polls for payout and triggers buy automatically.
import { logDebug } from './utils.js';
export function performKeychainSell(account, symbol, quantity, swapResult, getSwapHivePayoutForTx, getLastSwapHivePayout, performBuyPEK) {
if (!window.hive_keychain) {
swapResult.innerHTML = "Hive Keychain extension not detected.";
logDebug('Hive Keychain not detected.');
return;
}
const sellJson = {
contractName: "market",
contractAction: "marketSell",
contractPayload: {
symbol: symbol,
quantity: String(quantity)
}
};
logDebug('Requesting Keychain signature for marketSell...');
window.hive_keychain.requestCustomJson(
account,
'ssc-mainnet-hive',
'Active',
JSON.stringify(sellJson),
`Sell ${quantity} ${symbol} for SWAP.HIVE`,
function(response) {
logDebug('Keychain response: ' + JSON.stringify(response));
if (response.success) {
swapResult.innerHTML = "Sell order broadcasted! Waiting for your SWAP.HIVE payout...";
let payout = 0;
let pollCount = 0;
let lastPayout = 0;
const txId = response.result && response.result.tx_id ? response.result.tx_id : null;
const pollPayout = async function() {
payout = txId ? await getSwapHivePayoutForTx(account, symbol, txId) : 0;
if (!payout || payout <= 0) {
payout = await getLastSwapHivePayout(account, symbol);
}
logDebug(`Polling payout (txId=${txId}): ${payout}`);
if (payout > lastPayout + 0.0000001) {
lastPayout = payout;
swapResult.innerHTML += '<br>SWAP.HIVE payout detected! Waiting 10 seconds before buying PEK...';
logDebug('SWAP.HIVE payout detected. Waiting 10 seconds before auto-buying PEK.');
setTimeout(function() {
logDebug('Auto-buying PEK after 10s delay.');
performBuyPEK(account, payout, true);
}, 10000);
} else if (++pollCount < 30) {
setTimeout(pollPayout, 2000);
} else {
swapResult.innerHTML = "No new SWAP.HIVE payout detected from your sale after 60 seconds. Please check your wallet and try again.";
logDebug('Payout polling timed out.');
}
};
setTimeout(pollPayout, 2000);
} else {
swapResult.innerHTML = "Keychain error: " + (response.message || "Unknown error");
logDebug('Keychain error: ' + (response.message || 'Unknown error'));
}
}
);
}
export function performKeychainBuy(account, swapHiveAmount, swapResult) {
const MULTI_TX_FEE = 0.001;
let buyAmount = swapHiveAmount - MULTI_TX_FEE;
logDebug(`Preparing to buy PEK: swapHiveAmount=${swapHiveAmount}, buyAmount=${buyAmount}, useKeychain=true`);
if (buyAmount <= 0) {
swapResult.innerHTML = "Insufficient SWAP.HIVE amount after fee deduction.";
logDebug('Buy aborted: insufficient SWAP.HIVE after fee.');
return;
}
const buyJson = {
contractName: "market",
contractAction: "marketBuy",
contractPayload: {
symbol: 'PEK',
quantity: String(buyAmount)
}
};
logDebug('Requesting Keychain signature for marketBuy...');
window.hive_keychain.requestCustomJson(
account,
'ssc-mainnet-hive',
'Active',
JSON.stringify(buyJson),
`Buy PEK with ${buyAmount} SWAP.HIVE`,
function(response) {
logDebug('Keychain response (buy): ' + JSON.stringify(response));
if (response.success) {
swapResult.innerHTML = "Buy order broadcasted!";
} else {
swapResult.innerHTML = "Keychain error: " + (response.message || "Unknown error");
logDebug('Keychain error (buy): ' + (response.message || 'Unknown error'));
}
}
);
}
api.js - Handles network logic for Hive Engine and Hive, with endpoint failover.
export const HIVE_ENGINE_APIS = [
'https://peake-swap.onrender.com/he-proxy'
];
export const CORS_PROXY = 'https://corsproxy.io/?';
export async function fetchWithBackups(options) {
for (let i = 0; i < HIVE_ENGINE_APIS.length; i++) {
try {
const res = await fetch(HIVE_ENGINE_APIS[i], options);
if (res.ok) return await res.json();
} catch (e) {}
}
for (let i = 0; i < HIVE_ENGINE_APIS.length; i++) {
try {
const res = await fetch(CORS_PROXY + HIVE_ENGINE_APIS[i], options);
if (res.ok) return await res.json();
} catch (e) {}
}
return null;
}
ui.js- Updates the DOM for rates and swap results.
import { logDebug } from './utils.js';
export function updateRateDisplay(rate, tokenAmount, token) {
const rateDisplay = document.getElementById('rateDisplay');
if (rate && tokenAmount > 0) {
const pekAmount = tokenAmount * rate;
rateDisplay.innerHTML = `Estimated: <b>${pekAmount.toFixed(6)} PEK</b> for <b>${tokenAmount} ${token}</b><br><span style='font-size:0.95em;color:#fff;'>Final swap rate is determined by the market at the time of each transaction.</span>`;
} else {
rateDisplay.textContent = 'Unable to fetch live SWAP.HIVE rate.';
}
}
export function setSwapResult(msg) {
document.getElementById('swapResult').innerHTML = msg;
}
utils.js - General helpers and debug logging.
js
export function logDebug(msg) {
const el = document.getElementById('debugLogContent');
if (el) {
const now = new Date().toLocaleTimeString();
const entry = `<span style="font-size:0.82em;line-height:1.5;display:block;margin-bottom:2px;">[${now}] ${msg}</span>`;
el.innerHTML += entry;
el.scrollTop = el.scrollHeight;
}
}