j"19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAutMî'<html>
<head>
<style>
body {
font-family: monospace;
font-size: 15px;
margin: 10% auto;
max-width: 600px;
position: relative;
}
#qrcode {
float: right;
}
hr {
clear: both;
}
a {
cursor: pointer;
text-decoration: none;
}
</style>
<meta charset="UTF-8" />
<script src="https://bico.media/3412b9beb4234acea4d3406c38af5e283cc91486abeddfa80fcb45842c1f99c2/"></script>
<script src="https://bico.media/3bd388b0097b3bde7c8c8f5d760e1c772f5e9a002a33a42bb3440a176df21415/"></script>
<!-- <script src="b://3412b9beb4234acea4d3406c38af5e283cc91486abeddfa80fcb45842c1f99c2/"></script>
<script src="b://bico.media/3bd388b0097b3bde7c8c8f5d760e1c772f5e9a002a33a42bb3440a176df21415/"></script> -->
<script>
let privateKey;
let address;
let utxos = [];
let readyUtxos = [];
let uncomfirmedUtxos = [];
let spendUtxos = [];
let chunkSize = 90e3;
let splitValue = 90.5e3;
let splitCount;
let chunks;
let txns = {};
let requiredTxns;
function initialize() {
let wif = localStorage.getItem('privateKey');
if (wif) {
privateKey = bsv.PrivateKey.fromWIF(wif);
}
if (!privateKey) {
privateKey = bsv.PrivateKey.fromRandom();
localStorage.setItem('privateKey', privateKey.toWIF());
}
address = privateKey.toAddress();
document.getElementById('address').innerText = address;
const qr = qrcode(0, 'L');
qr.addData(`bitcoin:${address}?sv`);
qr.make();
document.getElementById('qrcode').innerHTML = qr.createImgTag();
return fetch('https://api.bitindex.network/api/v3/addr/utxos?address=' + address, {
headers: {
api_key: '5eTuVfKYpWiaRWaEBN5NF1VPKf9Tvm2HBXh9mmigjNG2iC94ZCnut1SMb3sNV4hwV4',
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then(({ data }) => {
utxos = data;
readyUtxos = utxos.filter((utxo) => {
return utxo.satoshis == splitValue && utxo.height > 0
});
uncomfirmedUtxos = utxos.filter((utxo) => {
return utxo.satoshis == splitValue && !(utxo.height > 0)
});
spendUtxos = utxos.filter((utxo) => utxo.satoshis != splitValue);
balance = spendUtxos.reduce((balance, utxo) => {
return balance + utxo.satoshis;
}, 0);
splitCount = Math.min(Math.floor(balance / splitValue), 500);
document.getElementById('confirmed').innerText = readyUtxos.length;
document.getElementById('unconfirmed').innerText = uncomfirmedUtxos.length;
document.getElementById('unsplit').innerText = spendUtxos.length;
document.getElementById('balance').innerText =
(
utxos.reduce((balance, utxo) => {
return balance + utxo.satoshis;
}, 0) / 100000000
).toFixed(4) + 'ð';
document.getElementById('splitCount').innerText = splitCount;
analyzeFile();
});
}
function split() {
let transaction = new bsv.Transaction().from(spendUtxos);
for (let i = 0; i < splitCount; i++) {
transaction.to(address, splitValue);
}
transaction.change(address);
transaction.sign(privateKey);
fetch('https://api.bitindex.network/api/v3/main/tx/send', {
method: 'POST',
body: JSON.stringify({ rawtx: transaction.toString() }),
headers: {
api_key: '22qtEpsphEv2ZtP8JkBiKD65bLQ26PxyJ66obK42uCGeb3b8MetH1bK5n4xEF3yxQ4',
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then((res) => console.log(res))
.then(() => initialize());
}
function analyzeFile() {
const file = document.getElementById('file').files[0];
chunks = Math.ceil(file.size / chunkSize);
requiredTxns = chunks + (chunks > 1 ? 1 : 0);
document.getElementById('requiredTxns').innerText = requiredTxns;
document.getElementById('cost').innerText = (requiredTxns * splitValue) / 100000000;
document.getElementById('uploadButton').disabled = requiredTxns > readyUtxos.length;
}
async function upload() {
const file = document.getElementById('file').files[0];
const reader = new FileReader();
txns = {};
reader.onload = async (e) => {
console.log(file);
let buffer = reader.result;
console.log(buffer);
console.log(`${chunks} chunks`);
await initialize();
let i = 0;
let bTxn;
if (chunks > 1) {
let chunkTxns = [];
for (i = 0; i < chunks; i++) {
let chunk = bsv.deps.Buffer.from(buffer.slice(i * chunkSize, (i + 1) * chunkSize));
let txn = new bsv.Transaction()
.from(readyUtxos[i])
.addData(['1ChDHzdd1H4wSjgGMHyndZm6qxEDGjqpJL', chunk]);
if (chunk.byteLength < chunkSize - 546) {
txn.change(address);
}
txn.sign(privateKey);
chunkTxns.push(txn.hash);
txns[txn.hash] = {
hex: txn.toString(),
status: 0
};
}
bTxn = new bsv.Transaction()
.from(readyUtxos[i++])
.addData([
'15DHFxWZJT58f9nhyGnsRBqrgwK4W6h4Up',
'Dynamic upload',
file.type,
bsv.deps.Buffer.from('20', 'hex'),
file.name,
bsv.deps.Buffer.from('20', 'hex'),
...chunkTxns.map((txnId) => bsv.deps.Buffer.from(txnId, 'hex')),
])
.change(address)
.sign(privateKey);
txns[bTxn.hash] = {
hex: bTxn.toString(),
status: 0
};
}
else {
bTxn = new bsv.Transaction()
.from(readyUtxos[i++])
.addData([
'19HxigV4QyBv3tHpQVcUEQyq1pzZVdoAut',
bsv.deps.Buffer.from(buffer),
file.type,
bsv.deps.Buffer.from('20', 'hex'),
file.name
]);
if (buffer.byteLength < chunkSize - 546) {
bTxn.change(address);
}
bTxn.sign(privateKey);
txns[bTxn.hash] = {
hex: bTxn.toString(),
status: 0
};
}
document.getElementById('btxn').innerHTML =
'<a href="https://bico.media/' +
bTxn.hash +
'" target="_blank"> B://' +
bTxn.hash +
'</a>';
renderQueue();
await processQueue();
initialize();
};
reader.readAsArrayBuffer(file);
}
async function broadcast(txnId) {
let txn = txns[txnId];
if(!txn) {
console.error(`Missing Txn: ${txnId}`);
return;
}
let res = await fetch('https://api.bitindex.network/api/v3/main/tx/send', {
method: 'POST',
body: JSON.stringify({ rawtx: txn.hex }),
headers: {
api_key: '22qtEpsphEv2ZtP8JkBiKD65bLQ26PxyJ66obK42uCGeb3b8MetH1bK5n4xEF3yxQ4',
'Content-Type': 'application/json',
},
});
if (res.ok) {
txn.status = 1;
document.getElementById(txnId).innerHTML = `<a href="https://whatsonchain.com/tx/${txnId}" target="_blank">Success</a>`;
}
else {
let error = await res.json();
txn.status = error.message.message;
document.getElementById(txnId).innerHTML = `Error: ${txn.status} - <a href="javascript:void()" onclick="broadcast('${txnId}')">Retry</a>`;
}
}
function renderQueue() {
let txnList = [];
for (let [hash, txn] of Object.entries(txns)) {
let status;
if (txn.status == 0) {
status = 'Pending';
}
else if (txn.status == 1) {
status = `<a href="https://whatsonchain.com/tx/${hash}">Success</a>`;
}
else {
status = `Error: ${txn.status} - <a href="javascript:void()" onclick="broadcast('${hash}')">Retry</a>`;
}
txnList.push(`<li><strong>${hash}</strong> - <span id="${hash}">${status}</span></li>`);
}
document.getElementById('queue').innerHTML = txnList.join('\n');
}
async function processQueue() {
let i = 0;
for (let [hash, txn] of Object.entries(txns)) {
if (txn.status == 1) continue;
console.log(`Broadcasting: ${hash}`);
await broadcast(hash);
}
}
function refund() {
let refundAddress = bsv.Address.fromString(document.getElementById('refundAddress').value);
let total = utxos.reduce((acc, utxo) => {
return acc + utxo.satoshis;
}, 0);
let txn = new bsv.Transaction()
.from(utxos)
.change(refundAddress)
.sign(privateKey);
return fetch('https://api.bitindex.network/api/v3/main/tx/send', {
method: 'POST',
body: JSON.stringify({ hex: txn.toString() }),
headers: {
api_key: '22qtEpsphEv2ZtP8JkBiKD65bLQ26PxyJ66obK42uCGeb3b8MetH1bK5n4xEF3yxQ4',
'Content-Type': 'application/json',
},
})
.then((res) => res.json())
.then((res) => {
console.log(res);
document.getElementById('refund').innerText = txn.hash;
initialize();
});
}
function toggle() {
if (document.getElementById('wif').style.display == 'none') {
document.getElementById('wif').style.display = 'inline';
document.getElementById('wif').innerHTML = privateKey.toWIF();
} else {
document.getElementById('wif').style.display = 'none';
document.getElementById('wif').innerHTML = '';
}
}
</script>
</head>
<body onload="initialize()">
<h1>Dynamic files on the blockchain</h1>
<hr />
<div id="qrcode"></div>
<h2>1. Fund Wallet</h2>
<h4>Current address</h4>
<p><span id="address"></span></p>
<p>
Total Balance: <span id="balance"></span>
<a href="#" onclick="initialize()">ð</a>
</p>
<hr />
<h2>2. Prepare UTXOs</h2>
In order to bypass a limitation in the number of chained transactions allowed, split transactions and let them
confirm before you upload.
<a href="#" onclick="initialize()">ð</a>
<p>
Ready: <span id="confirmed">?</span><br />
Unconfirmed: <span id="unconfirmed">?</span><br />
Unsplit: <span id="unsplit">?</span>
<button onclick="split()">Split <span id="splitCount">0</span> Txns</button>
</p>
<hr />
<h2>3. Upload</h2>
<p>
<input type="file" id="file" onchange="analyzeFile()" /><br />
Required Txns: <span id="requiredTxns">0</span><br />
Cost: ~<span id="cost">0</span>
</p>
<button onclick="upload()" id="uploadButton" disabled="true">Upload</button>
<p>
B Txn: <span id="btxn"></span>
</p>
<p>
Upload Queue: <span id="status"></span><br>
<ul id="queue"></ul>
</p>
<hr />
<h4>Cash Out</h4>
<p>
<input type="text" id="refundAddress" placeholder="Refund Address" />
</p>
<button onclick="refund()">Refund</button><br>
<span id="refund"></span>
<h4>Private Key</h4>
<button onclick="toggle()">Show/Hide WIF</button>
<p><span id="wif" style="display:none"></span></p>
</body>
</html>
text/html
https://whatsonchain.com/tx/2eb7b0932cf938f10e550ac99671e7846b97e898efe52ea0b5eff85df6f584dc