251 lines
9.9 KiB
HTML
251 lines
9.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>BSC Testnet Imageboard</title>
|
|
<link rel="icon" type="image/png" href="favicon.png">
|
|
<link rel="shortcut icon" href="https://chainboard.neocities.org/favicon.png" />
|
|
<style>
|
|
html {
|
|
min-height: 100%;
|
|
}
|
|
body {
|
|
background: rgb(24,24,24);
|
|
background: linear-gradient(180deg, rgba(24,24,24,1) 60%, rgba(0,0,0,1) 100%);
|
|
color: white;
|
|
}
|
|
a:link {
|
|
color: #a859d9;
|
|
}
|
|
a:visited {
|
|
color: #a045d9;
|
|
}
|
|
a:hover {
|
|
color: #ab40ed;
|
|
}
|
|
a:active {
|
|
color: #752fa1;
|
|
}
|
|
h1 {
|
|
color: #4287f5;
|
|
}
|
|
div.info {
|
|
display: flex;
|
|
font-size: .8em;
|
|
}
|
|
div.address {
|
|
margin-right: .8em;
|
|
}
|
|
div.post {
|
|
background-color: #202020;
|
|
border: 1px solid black;
|
|
margin-bottom: 0.5em;
|
|
margin-top: 0.5em;
|
|
max-width: max-content;
|
|
padding: 0.5em;
|
|
}
|
|
div.tx {
|
|
}
|
|
div.timestamp {
|
|
color: #cfcfcf;
|
|
font-size: 0.9em;
|
|
}
|
|
div#guide {
|
|
background-color: #222222;
|
|
border: 1px solid black;
|
|
margin: 1em;
|
|
max-width: max-content;
|
|
padding-right: 1em;
|
|
}
|
|
div#footer {
|
|
margin-top: 1em;
|
|
}
|
|
img.postImg {
|
|
height: auto;
|
|
max-height: 256px;
|
|
max-width: 100%;
|
|
}
|
|
img.postImgFull {
|
|
height: auto;
|
|
max-width: 100%;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>BSC Testnet Imageboard</h1>
|
|
<div id="menu"><button onclick="guide()">Guide</button><button onclick="getPosts();">Get Posts</button></div>
|
|
<script src="web3.min.js"></script>
|
|
<script src="validator.min.js"></script>
|
|
<form id="postForm" onsubmit="submitPost();return false;">
|
|
<div><textarea id="submitPostTextArea" rows="5" cols="60"></textarea></div>
|
|
<input id="postUpload" type="file">
|
|
<button>Submit</button>
|
|
</form>
|
|
<div id="posts">
|
|
</div>
|
|
<div id="footer">
|
|
🌐 <a href="https://git.kiwifarms.net/CrunkLord420/chainboard">Powered by ChainBoard under AGPL-3.0-only+NIGGER</a>
|
|
</div>
|
|
<script>
|
|
const configChainID = '0x61';
|
|
const configContract = '0x4682e630031B6d9219d15E84D921152C2E65f9F4';
|
|
//const configChainID = '0x539';
|
|
//const configRPC = 'http://localhost:7545/';
|
|
const configRPC = 'https://data-seed-prebsc-1-s2.binance.org:8545/';
|
|
function guide() {
|
|
let guideDiv = document.getElementById('guide');
|
|
if (guideDiv) {
|
|
guideDiv.remove();
|
|
} else {
|
|
guideDiv = document.createElement('div');
|
|
guideDiv.id = 'guide';
|
|
guideDiv.innerHTML = '<ul><li>Install <a href="https://metamask.io/">MetaMask</a></li><li><a href="https://metamask.zendesk.com/hc/en-us/articles/360043227612-How-to-add-custom-Network-RPC-and-or-Block-Explorer">Add BSC Testnet to MetaMask</a></li><ul><li>RPC: ' + configRPC + '</li><li>ChainID: 97</li><li>Symbol: BNB</li><li>Block Explorer: https://testnet.bscscan.com/</li><li><a href="https://docs.binance.org/smart-chain/developer/rpc.html">Additional RPC URLs</a></li></ul><li>Get free testnet BNB from the <a href="https://testnet.binance.org/faucet-smart">faucet</a></li><li>Extra Information</li><ul><li>Contract: <a href="https://testnet.bscscan.com/address/' + configContract + '">' + configContract + '</a></li><li><a href="abi.json">Contract ABI</a></li></ul>';
|
|
document.body.insertBefore(guideDiv, document.getElementById('postForm'));
|
|
}
|
|
}
|
|
if (typeof window.ethereum === 'undefined') {
|
|
guide();
|
|
}
|
|
const textArea = document.getElementById("submitPostTextArea");
|
|
const postDiv = document.getElementById("posts");
|
|
const postUpload = document.getElementById("postUpload");
|
|
const web3 = new Web3(configRPC);
|
|
let abi, contract;
|
|
async function addMeme(memeText, mimeType, data) {
|
|
const accounts = (await ethereum.request({ method: 'eth_requestAccounts' }))[0];
|
|
const tx = contract.methods.createPost(memeText, mimeType, data).encodeABI();
|
|
if (tx.length >= 262144) {
|
|
alert('Error: transaction too large\nCurrent: ' + tx.length + '\nMaximum: 262144');
|
|
return;
|
|
}
|
|
const estimate = await contract.methods.createPost(memeText, mimeType, data).estimateGas({from: ethereum.selectedAddress, gas: 10000000, value: 0}).catch((err) => {
|
|
console.error(err);
|
|
});
|
|
if (estimate === undefined) {
|
|
alert('Error: couldn\'t estimate gas price');
|
|
return;
|
|
}
|
|
const transactionParameters = {
|
|
nonce: '0x00', // ignored by MetaMask
|
|
gasPrice: '0x2540be400', // customizable by user during MetaMask confirmation.
|
|
gas: estimate.toString(16), // customizable by user during MetaMask confirmation.
|
|
to: contract._address, // Required except during contract publications.
|
|
from: ethereum.selectedAddress, // must match user's active address.
|
|
value: '0x00', // Only required to send ether to the recipient from the initiating external account.
|
|
data: tx, // Optional, but used for defining smart contract creation and interaction.
|
|
chainId: configChainID, // Used to prevent transaction reuse across blockchains. Auto-filled by MetaMask.
|
|
};
|
|
// txHash is a hex string
|
|
// As with any RPC call, it may throw an error
|
|
const txHash = await ethereum.request({
|
|
method: 'eth_sendTransaction',
|
|
params: [transactionParameters],
|
|
});
|
|
textArea.value = '';
|
|
postUpload.value = '';
|
|
}
|
|
async function submitPost() {
|
|
if (typeof window.ethereum === 'undefined') {
|
|
alert('Error: no "ethereum provider" found, install MetaMask (or alternative)');
|
|
return;
|
|
}
|
|
const chainId = await ethereum.request({ method: 'eth_chainId' });
|
|
if (chainId !== configChainID) {
|
|
alert('Wrong Chain ID\nCurrent: ' + chainId + '\nRequired: ' + configChainID);
|
|
return;
|
|
}
|
|
let mimeType = '';
|
|
let data = '';
|
|
if (postUpload.files.length > 0) {
|
|
const file = postUpload.files.item(0);
|
|
mimeType = file.type;
|
|
//data = btoa(String.fromCharCode.apply(null, new Uint8Array(await file.arrayBuffer())));
|
|
data = btoa(new Uint8Array(await file.arrayBuffer()).reduce((data, byte) => data + String.fromCharCode(byte), ''));
|
|
}
|
|
addMeme(textArea.value, mimeType, data);
|
|
}
|
|
function resizeImg(el) {
|
|
if (el.className === 'postImg') {
|
|
el.className = 'postImgFull';
|
|
} else {
|
|
el.className = 'postImg';
|
|
}
|
|
}
|
|
async function getPosts() {
|
|
const posts = await contract.getPastEvents("Post", {fromBlock: 0}).catch((err) => {
|
|
console.error(err);
|
|
return;
|
|
});
|
|
postDiv.innerHTML = '';
|
|
for (let i=posts.length-1; i>=0; i--) {
|
|
const div = document.createElement('div');
|
|
div.className = 'post';
|
|
const timestampDiv = document.createElement('div');
|
|
timestampDiv.className = 'timestamp';
|
|
timestampDiv.innerText = (new Date(parseInt(posts[i].returnValues.timestamp)*1000)).toUTCString();
|
|
div.appendChild(timestampDiv);
|
|
const infoDiv = document.createElement('div');
|
|
infoDiv.className = 'info';
|
|
const addressDiv = document.createElement('div');
|
|
addressDiv.className = 'address';
|
|
const txDiv = document.createElement('div');
|
|
txDiv.className = 'tx';
|
|
txDiv.innerHTML = '<a href="https://testnet.bscscan.com/tx/' + posts[i].transactionHash + '">Tx</a>';
|
|
const textDiv = document.createElement('div');
|
|
textDiv.className = 'postText';
|
|
addressDiv.innerHTML = '<a href="https://testnet.bscscan.com/address/' + posts[i].returnValues.poster + '">' + posts[i].returnValues.poster + '</a>';
|
|
textDiv.innerText = posts[i].returnValues.text;
|
|
infoDiv.appendChild(addressDiv);
|
|
infoDiv.appendChild(txDiv);
|
|
div.appendChild(infoDiv);
|
|
if (posts[i].returnValues.mime) {
|
|
if (validator.isBase64(posts[i].returnValues.data)) {
|
|
if (validator.isMimeType(posts[i].returnValues.mime)) {
|
|
if (posts[i].returnValues.mime === 'image/png' || posts[i].returnValues.mime === 'image/jpeg' || posts[i].returnValues.mime === 'image/gif') {
|
|
const imgDiv = document.createElement('div');
|
|
imgDiv.className = 'postImg';
|
|
imgDiv.innerHTML = '<a href="javascript:resizeImg(postImg' + i +')"><img id="postImg' + i + '" class="postImg" src="data:' + posts[i].returnValues.mime + ';base64,' + posts[i].returnValues.data + '"></a>';
|
|
div.appendChild(imgDiv);
|
|
} else if (posts[i].returnValues.mime === 'video/mp4' || posts[i].returnValues.mime === 'video/webm') {
|
|
const imgDiv = document.createElement('div');
|
|
imgDiv.className = 'postImg';
|
|
imgDiv.innerHTML = '<video id="postImg' + i + '" class="postImg" controls><source src="data:' + posts[i].returnValues.mime + ';base64,' + posts[i].returnValues.data + '" type="' + posts[i].returnValues.mime + '"></video>';
|
|
div.appendChild(imgDiv);
|
|
} else if (posts[i].returnValues.mime === 'audio/mpeg' || posts[i].returnValues.mime === 'audio/ogg' || posts[i].returnValues.mime === 'audio/opus') {
|
|
const imgDiv = document.createElement('div');
|
|
imgDiv.className = 'postImg';
|
|
imgDiv.innerHTML = '<audio id="postImg' + i + '" class="postImg" controls><source src="data:' + posts[i].returnValues.mime + ';base64,' + posts[i].returnValues.data + '" type="' + posts[i].returnValues.mime + '"></audio>';
|
|
div.appendChild(imgDiv);
|
|
} else {
|
|
const downloadDiv = document.createElement('div');
|
|
downloadDiv.className = 'postDownload';
|
|
downloadDiv.innerHTML = '<a href="data:' + posts[i].returnValues.mime + ';base64,' + posts[i].returnValues.data + '">Download (' + posts[i].returnValues.mime + ')</a>';
|
|
div.appendChild(downloadDiv);
|
|
}
|
|
} else {
|
|
const postErrorDiv = document.createElement('div');
|
|
postErrorDiv.className = 'postError';
|
|
postErrorDiv.innerHTML = 'ERROR: invalid mime type';
|
|
div.appendChild(postErrorDiv);
|
|
}
|
|
} else {
|
|
const postErrorDiv = document.createElement('div');
|
|
postErrorDiv.className = 'postError';
|
|
postErrorDiv.innerHTML = 'ERROR: data is not valid base64';
|
|
div.appendChild(postErrorDiv);
|
|
}
|
|
}
|
|
div.appendChild(textDiv);
|
|
postDiv.appendChild(div);
|
|
}
|
|
}
|
|
async function initContract() {
|
|
abi = await (await fetch('/abi.json')).json();
|
|
contract = new web3.eth.Contract(abi, configContract);
|
|
getPosts();
|
|
}
|
|
initContract();
|
|
</script>
|
|
</body>
|
|
</html>
|