Skip to content

Commit

Permalink
Dispenser
Browse files Browse the repository at this point in the history
Added new form for creating dispenser
  • Loading branch information
Jpja committed Jan 3, 2024
1 parent 970aa57 commit 9e49e00
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 4 deletions.
269 changes: 269 additions & 0 deletions dispenser.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Create a Counterparty Dispenser w/ Electrum</title>
<link rel="stylesheet" href="css/main.css">

<script src="db/asset_longnames.js"></script>
<script src="db/assets_div.js"></script>
<script src="db/assets_indiv.js"></script>

<script src="js/xcp/hex.js"></script>
<script src="js/xcp/assets.js"></script>
<script src="js/xcp/build_tx_msg.js"></script>
<script src="js/xcp/decode_tx_msg.js"></script>
<script src="js/xcp/display.js"></script>
<script src="js/xcp/rc4.js"></script>
<script src="js/xcp/validate_address.js"></script>

<script src="js/lib/bitcoinjs-lib.js"></script>
<script src="js/lib/bitcoinjs-message.js"></script>
<script src="js/lib/jquery-3.4.1.min.js"></script>
<script src="js/lib/lozad.min.js"></script>
<script src="js/lib/sha256.js"></script>
<script src="js/lib/btc.js"></script>

<script>
let addr = '';
let asset = '';
let longname = '';
let utxo = '';
let div = '';
let max = '';
let network = 'bitcoin';
function get_url_parameters() {
//Get url parameter 'address'
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
if (urlParams.has('address')) {
addr = urlParams.get('address');
}
if (urlParams.has('asset')) {
asset = urlParams.get('asset');
document.getElementById('input_asset').value = asset;
}
if (urlParams.has('longname')) {
longname = urlParams.get('longname');
}
if (urlParams.has('utxo')) {
utxo = urlParams.get('utxo');
document.getElementById('input_utxo').value = utxo;
document.getElementById('div_utxo').style.display = 'none';
}
if (urlParams.has('div')) {
div = urlParams.get('div');
if (div == 'div') {
document.getElementById('input_divisibility').selectedIndex = 1;
document.getElementById('div_divisibility').style.display = 'none';
}
if (div == 'indiv') {
document.getElementById('input_divisibility').selectedIndex = 2;
document.getElementById('div_divisibility').style.display = 'none';
}
}
if (urlParams.has('max') && urlParams.has('div')) {
max = urlParams.get('max');
}
if (urlParams.has('network')) {
network = urlParams.get('network');
}
}

function display() {
let header = wallet_header;
header = header.replace('{address}', addr_short(addr));
if (utxo != '') {
header = header.replace('{utxo}', '<span onclick="document.getElementById(\'div_utxo\').style.display=\'block\';">UTXO '+utxo.slice(0,4)+'</span>');
} else{
header =header.replace('{utxo}', '');
}
document.getElementById('html_top').innerHTML = header;
show_alt_name();

}

function show_alt_name() {
document.getElementById('alt_name').innerHTML = alt_name(document.getElementById('input_asset').value);
show_max_amount();
}

function show_divisibility() {
let current_asset = document.getElementById('input_asset').value;
let div = get_divisibility(current_asset);
if (div == 'unknown') {
document.getElementById('input_divisibility').value = 'select';
document.getElementById('div_divisibility').style.display = 'block';
}
if (div == 'div') {
document.getElementById('input_divisibility').value = 'div';
document.getElementById('div_divisibility').style.display = 'none';
}
if (div == 'indiv') {
document.getElementById('input_divisibility').value = 'indiv';
document.getElementById('div_divisibility').style.display = 'none';
}
}

function show_max_amount() {
let max_message = '';
let show_max = true;
if (max == '') show_max = false;
if (asset != document.getElementById('input_asset').value) show_max = false;
if (div != 'div' && div != 'indiv') show_max = false;
if (show_max) max_message = 'Max ' + sat_to_displayx(max, div, '');
document.getElementById('max_amount').innerHTML = max_message;
}


function prepare_inputs() {
let inp_utxo = document.getElementById('input_utxo').value; //eg a1870614c4cd1b3e20f98316f062c8622313eca4bbeb3f7bb695a61c164a45e7
let inp_asset = document.getElementById('input_asset').value; //eg FLDC
let inp_escrow = document.getElementById('input_escrow').value; //eg 5843.57
let inp_give = document.getElementById('input_give').value;
let inp_btc = document.getElementById('input_btc').value;
let inp_divisibility = document.getElementById('input_divisibility').value; //'div' or 'indiv'
let inp_address = document.getElementById('input_address').value; //eg bc1qqqj54caf4cusvycr4dmzv24pgh875xeg8d5wrq
let inp_tip_btc = document.getElementById('input_tip').value;

//Inputs formatted ok?
inp_utxo = inp_utxo.substring(0,64);
inp_utxo = inp_utxo.toLowerCase();
if (inp_utxo.length != 64) {
print_result("UTXO is not valid"); return;
}
if (valid_name(inp_asset) == false) {
print_result("Asset name is not valid"); return;
}
if (isNaN(inp_escrow)) {
print_result("Escrow amount is not a number"); return;
}
if (isNaN(inp_give)) {
print_result("Give amount is not a number"); return;
}
if (isNaN(inp_btc)) {
print_result("BTC amount is not a number"); return;
}
if (inp_address != '' && valid_btc_addr(inp_address, network) == false) {
print_result("Dispenser is not a valid Bitcoin address"); return;
}
if (inp_address != '' && valid_xcp_addr(inp_address, network) == false) {
print_result("Dispenser's address is not supported by Counterparty"); return;
}
if (isNaN(inp_tip_btc)) {
print_result("Tip is not a number"); return;
}

let out = ' ';

//Check divisibility
let div = get_divisibility(inp_asset);
if (div == 'unknown') {
out += 'Warning: Confirm that divisibility is correct on {xchain} and {xcpdev}.'
} else if (div != inp_divisibility) {
print_result("Wrong divisibility"); return;
}

//Convert longname. If not possible return error.
if (inp_asset.includes('.')) {
inp_asset = longname_to_asset(inp_asset);
if (inp_asset == '') {
out += "Please input subasset's numeric name. See {xchain} and {xcpdev}.";
}
}
out += 'Make sure the first input coin is ' + addr_short(inp_utxo, 3, 2);

let amount_escrow = display_to_sat(inp_escrow, inp_divisibility);
let amount_give = display_to_sat(inp_give, inp_divisibility);
let amount_btc = display_to_sat(inp_btc, 'div');
let opreturn_unencoded = msg_create_dispenser(inp_asset, amount_escrow, amount_give, amount_btc, inp_address);
let opreturn = rc4_hex(inp_utxo, opreturn_unencoded);
console.log(opreturn);

//Sanity checks. Does decoded tx match inputs?
let info = decode_tx(opreturn, inp_utxo);
if (info.prefix != 'CNTRPRTY') {
print_result("Op_return encoding error. Wrong prefix."); return;
}
if (info.msg_id != 12) {
print_result("Op_return encoding error. Wrong messge type."); return;
}
if (info.prefix != 'CNTRPRTY') {
print_result("Op_return encoding error. Wrong prefix."); return;
}
if (info.asset_id != asset_id(inp_asset)) {
print_result("Op_return encoding error. Wrong asset id."); return;
}
if (Number(info.escrow_amount) != Number(amount_escrow)) {
print_result("Op_return encoding error. Wrong amount."); return;
}
if (Number(info.give_amount) != Number(amount_give)) {
print_result("Op_return encoding error. Wrong amount."); return;
}
if (Number(info.btc_amount) != Number(amount_btc)) {
print_result("Op_return encoding error. Wrong amount."); return;
}
if (info.address != inp_address) {
print_result("Op_return encoding error. Wrong recipient address."); return;
}


//Output messages and code
let code = 'OP_RETURN ' + opreturn + ',0' + tip_line(inp_tip_btc);

let url_xchain = 'https://xchain.io/asset/' + inp_asset
let url_xcpdev = 'https://www.xcp.dev/asset/' + inp_asset
out = out.replaceAll('{xchain}', '<a href="' + url_xchain + '">Xchain</a>');
out = out.replaceAll('{xcpdev}', '<a href="' + url_xcpdev + '">Xcp.dev</a>');

print_result(out, code)

}


function print_result(message = '', code = '') {
document.getElementById('text_output').innerHTML = '<br>' + message;
document.getElementById('code_output').innerHTML = code;
}

</script>

</head>

<body onload="get_url_parameters();display();">
<div id="content">
<div id="html_top"></div>
<h1>Create Dispenser</h1>
<div id="div_utxo">UTXO (Input Coin)<br>
<input id="input_utxo"><br><br></div>
<div id="div_asset">Asset<span id="alt_name" style="float:right"></span><br>
<input id="input_asset" oninput="show_alt_name();" onchange="show_divisibility();"><br><br></div>
<div id="div_escrow">Amount to Escrow<span id="max_amount" style="float:right">Max</span><br>
<input id="input_escrow"><br><br></div>
<div id="div_give">Amount to Give per Dispense<br>
<input id="input_give"><br><br></div>
<div id="div_divisibility">Divisible<br>
<select name="select_divisibility" id="input_divisibility">
<option value="select">Select</option>
<option value="div">True (Fractional tokens)</option>
<option value="indiv">False (Whole tokens)</option>
</select><br><br></div>
<div id="div_btc">BTC to Charge per Dispense<br>
<input id="input_btc"><br><br></div>
<div id="div_address">Optional Dispenser Address<br>
<input id="input_address"><br><br></div>
<div id="div_tip">Optional Tip to Developer (BTC)<span id="suggested_tip" style="float:right" onclick="document.getElementById('input_tip').value='0.0001';">Suggested: 0.0001</span><br>
<input id="input_tip"><br></div>
<button type="button" id="generate" onclick="prepare_inputs();" >Generate OP_RETURN</button><br><br>
<i><span style="font-size:75%">This is an experimental script. Peer review pending. Your tokens may get lost. Use at own risk!</span></i><br>
<span id="text_output">&nbsp;</span>
<textarea id="code_output" rows="2"></textarea>
<button type="button" id="copy" onclick="document.getElementById('code_output').select();document.execCommand('copy');">Copy</button><br><br>

</div>
</body>
<script>
document.getElementById('input_tip').value = tip_btc;
</script>

</html>
7 changes: 4 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
const TO_RECEIVER = 0.00005420; //BTC dust sent along asset
const forms = [
['Send', 'send.html'],
['Sell (Dispenser)', 'dispenser.html'],
['Sweep', 'sweep.html'],
['Broadcast Text', 'broadcast_text.html'],
['Broadcast File (CIP33)', 'cip33_broadcast.html'],
Expand Down Expand Up @@ -202,7 +203,7 @@

function displayBalances() {
let html = '';
let url_send = 'send.html?asset={asset}&longname={longname}&max={max}&div={div}&address={addr}&utxo={utxo}';
let url_send = 'asset={asset}&longname={longname}&max={max}&div={div}&address={addr}&utxo={utxo}';

//UTXOs
let utxo = '';
Expand Down Expand Up @@ -238,10 +239,10 @@
send_link = send_link.replace('{div}', balances[i].divisibility);
send_link = send_link.replace('{addr}', address);
send_link = send_link.replace('{utxo}', utxo);
send_links.push('<a href="'+send_link+'">Send</a>');
send_links.push('<a href="send.html?'+send_link+'">Send</a> | <a href="dispenser.html?'+send_link+'">Sell</a>');
if (balances[i].asset == 'XCP') {
xcp_balance = balances[i].quantity;
send_link_xcp = '<a href="'+send_link+'">Send</a>';
send_link_xcp = '<a href="send.html?'+send_link+'">Send</a> | <a href="dispenser.html?'+send_link+'">Sell</a>';
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions js/xcp/build_tx_msg.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,20 @@ function msg_broadcast(text, ts = '', value = 0, fee = 0) {
let text_len_hex = text_len.toString(16).padStart(2, '0');
return prefix_hex + msg_id_hex + ts_hex + value_hex + fee_hex + text_len_hex + text_hex;
//return prefix_hex + msg_id_hex + ts_hex + value_hex + fee_hex + text_len_hex + text_hex;
}

function msg_create_dispenser(asset, amount_escrow, amount_give, amount_btc, dispenser_address = '') {
const msg_id = 12;
let status_hex = '00';
let msg_id_hex = msg_id.toString(16).padStart(2, '0');
let asset_hex = asset_id_hex(asset).padStart(16, '0');
let amount_escrow_hex = BigInt(amount_escrow).toString(16).padStart(16, '0');
let amount_give_hex = BigInt(amount_give).toString(16).padStart(16, '0');
let amount_btc_hex = BigInt(amount_btc).toString(16).padStart(16, '0');
let dispenser_address_hex = '';
if (dispenser_address != '') {
status_hex = '01';
dispenser_address_hex = address_to_hex(dispenser_address);
}
return prefix_hex + msg_id_hex + asset_hex + amount_give_hex + amount_escrow_hex + amount_btc_hex + status_hex + dispenser_address_hex;
}
38 changes: 38 additions & 0 deletions js/xcp/decode_tx_msg.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,22 @@ function decode_tx(msg, utxo = '') {
info['sweep_type'] = sweep_types[flag];
}

if (msg_id == 12) { //Dispenser
asset_id_hex = msg.slice(0,16);
msg = msg.slice(16);
give_hex = msg.slice(0,16);
msg = msg.slice(16);
escrow_hex = msg.slice(0,16);
msg = msg.slice(16);
btc_hex = msg.slice(0,16);
msg = msg.slice(16);
dispenser_status = msg.slice(0,2);
msg = msg.slice(2);
if (dispenser_status == '01') {
addr_hex = msg;
}
}

if (msg_id == 30) { //Broadcast
let ts_hex = msg.slice(0,8);
msg = msg.slice(8);
Expand Down Expand Up @@ -89,6 +105,27 @@ function decode_tx(msg, utxo = '') {
info['amount_display'] = sat_to_display(amount, div);
}

//for dispensers there are three kinds of 'amount'; give, escrow, btc
if (msg_id == 12) {
if (give_hex != '') {
let amount = BigInt('0x'+give_hex).toString(10);
let div = get_divisibility(asset);
info['give_amount'] = amount;
info['give_amount_display'] = sat_to_display(amount, div);
}
if (escrow_hex != '') {
let amount = BigInt('0x'+escrow_hex).toString(10);
let div = get_divisibility(asset);
info['escrow_amount'] = amount;
info['escrow_amount_display'] = sat_to_display(amount, div);
}
if (btc_hex != '') {
let amount = BigInt('0x'+btc_hex).toString(10);
info['btc_amount'] = amount;
info['btc_amount_display'] = sat_to_display(amount, 'div');
}
}

if (addr_hex != '') {
info['address'] = hex_to_address(addr_hex);
}
Expand All @@ -103,6 +140,7 @@ function decode_tx(msg, utxo = '') {




//** ADDRESS FUNCTIONS **//
function hex_to_address(hex) { //21 byte hex encoded in cntrprty message
let version_byte = hex.substring(0,2);
Expand Down
2 changes: 1 addition & 1 deletion js/xcp/display.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const tip_btc = '0';
const wallet_header = `<b>XCP Opreturn Builder for Electrum</b><div style="float:right;text-align: right;width:50%;">{address}<br>{utxo}</div>
<div><i>Alpha release. For testing only!</i></div>`;

const wallet_footer = `<p>Footer goes here</p>`;
const wallet_footer = `<p>&nbsp;</p>`;

function addr_short(address, start = 4, end = 2) {
if (address == '') return '';
Expand Down

0 comments on commit 9e49e00

Please sign in to comment.