Compare commits

...

33 Commits

Author SHA1 Message Date
0158c875e5
Fixing FLO Private key in ETH converter
Some checks failed
Workflow push to Dappbundle / Build (push) Has been cancelled
BTC Private key and FLO private key were the same in ETH converter. This has been fixed
2024-07-11 20:22:06 +05:30
SaketAnand
dbc5cfda95 Update index.html 2024-07-01 03:30:16 +05:30
sairaj mote
b61565398c Update index.html 2024-05-22 17:15:04 +05:30
sairaj mote
d6d3da01e0 "No API available" issue fix 2024-05-07 00:36:14 +05:30
sairaj mote
bb8a5a4dd1 bug fix: stopped unnecessary address API calls when no address is present on first load 2024-04-14 23:14:46 +05:30
sairaj mote
712515a990 bug fixes 2024-04-04 13:15:27 +05:30
sairaj mote
6aa9fa0255 code refactoring 2024-02-13 18:08:21 +05:30
sairaj mote
fca68debf1 adding price history toggle to transaction details page 2024-01-25 01:56:49 +05:30
sairaj mote
c5b61f3c05 valuation switches to historic prices if current value not present 2024-01-19 01:10:33 +05:30
sairaj mote
2e3b0209b5 error handling if historic api takes too long to respond 2024-01-16 15:28:49 +05:30
sairaj mote
33a972afc6 added error handling for when historic price api fails 2024-01-16 15:12:02 +05:30
sairaj mote
a52b27a4d5 adding different tor detection 2024-01-16 05:42:43 +05:30
sairaj mote
74bb2608dc adding historic price for transactions 2024-01-16 03:29:36 +05:30
sairaj mote
c857c04b85 code refactoring 2024-01-09 03:34:57 +05:30
sairaj mote
cd89411365 uncommenting other APIs 2024-01-05 20:33:10 +05:30
sairaj mote
4403cee668 testing broadcast APIs 2024-01-05 14:16:43 +05:30
sairaj mote
32cc6e166c Added better confirmations counting 2024-01-05 02:54:42 +05:30
sairaj mote
779af53fcb UX improvements and code refactoring
-- added address and private key matching for increase fee option
2024-01-05 00:21:18 +05:30
sairaj mote
f09870bf3a fixed issue with broadcasting tx 2024-01-02 09:58:07 +05:30
sairaj mote
74def25d14 fixed pending transaction tip text 2024-01-02 08:59:51 +05:30
sairaj mote
83e3905408 better handling for insufficient balance case 2023-12-31 03:21:45 +05:30
sairaj mote
5203782935 updated version number 2023-12-29 04:54:12 +05:30
sairaj mote
9412a707d8 Bug fixes 2023-12-29 04:52:43 +05:30
sairaj mote
f28ecf5f3f Update index.html 2023-12-28 15:53:28 +05:30
sairaj mote
09a4943298 Update index.html 2023-12-28 14:51:03 +05:30
sairaj mote
22672d0172 Fixed Tx time bug 2023-12-26 23:38:44 +05:30
sairaj mote
0b02732f48 UI bug fix 2023-12-26 23:31:10 +05:30
sairaj mote
52de5ff1cd moving most of the API calls to multi-api mode 2023-12-26 23:23:39 +05:30
sairaj mote
1f11a8f286 Adding multi API support 2023-12-24 02:03:19 +05:30
sairaj mote
1fd6dbbf9c Update push-dappbundle.yml 2023-12-18 22:04:45 +05:30
Vivek Teega
e0c8f9ee7a
Cleaning all git data in workflow 2023-12-18 20:19:38 +05:30
Vivek Teega
129daf7a5b
Update README.md 2023-12-18 20:13:58 +05:30
Vivek Teega
80c74cd9d9 Push dappbundle 2023-12-18 20:12:33 +05:30
13 changed files with 1703 additions and 888 deletions

32
.github/workflows/push-dappbundle.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Workflow push to Dappbundle
on: [push]
jobs:
build:
name: Build
runs-on: self-hosted
steps:
- name: Executing remote command
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.R_HOST }}
username: ${{ secrets.P_USERNAME }}
password: ${{ secrets.P_PASSWORD }}
port: ${{ secrets.SSH_PORT }}
script: |
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle" ]; then
echo "Folder exists. Skipping Git clone."
else
echo "Folder does not exist. Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/ && git clone https://github.com/ranchimall/dappbundle.git
fi
if [ -d "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" ]; then
echo "Repository exists. Remove folder "
rm -r "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}"
fi
echo "Cloning repository..."
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle && git clone https://github.com/ranchimall/${{ github.event.repository.name }}
cd "${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/${{ github.event.repository.name }}" && rm -rf .gitattributes .git .github .gitignore
cd ${{ secrets.DEPLOYMENT_LOCATION}}/dappbundle/ && git add . && git commit -m "Workflow updating files of ${{ github.event.repository.name }}" && git push "https://ranchimalldev:${{ secrets.RM_ACCESS_TOKEN }}@github.com/ranchimall/dappbundle.git"

View File

@ -1,18 +0,0 @@
# push contents of this repo to specified repo on github on commit
name: push this repo to bundle repo
on: [push]
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: push to bundle repo
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.SM_ACCESS_TOKEN }}
branch: master
force: true
directory: .
repository: ranchimall/dappbundle

View File

@ -1,4 +1,5 @@
# BTC Wallet # BTC Wallet
[![Workflow push to Dappbundle](https://github.com/ranchimall/btcwallet/actions/workflows/push-dappbundle.yml/badge.svg)](https://github.com/ranchimall/btcwallet/actions/workflows/push-dappbundle.yml)
BTC Wallet BTC Wallet
It is a web-based Bitcoin wallet and Bitcoin explorer that promotes self-custody by generating Bitcoin addresses and private keys locally on the user's device. If you are suspicious about internet monitoring, then open the URL, disconnect your computer from the internet, and then generate the Bitcoin address and private key. It is a web-based Bitcoin wallet and Bitcoin explorer that promotes self-custody by generating Bitcoin addresses and private keys locally on the user's device. If you are suspicious about internet monitoring, then open the URL, disconnect your computer from the internet, and then generate the Bitcoin address and private key.

View File

@ -27,7 +27,6 @@ body {
scrollbar-gutter: stable; scrollbar-gutter: stable;
color: rgba(var(--text-color), 1); color: rgba(var(--text-color), 1);
background-color: rgba(var(--background-color), 1); background-color: rgba(var(--background-color), 1);
transition: background-color 0.3s;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -394,6 +393,10 @@ ol li::before {
align-items: flex-start; align-items: flex-start;
} }
.align-self-start {
align-self: flex-start;
}
.align-center { .align-center {
align-items: center; align-items: center;
} }
@ -658,7 +661,7 @@ ol li::before {
} }
#main_header { #main_header {
padding: 1.5rem max(1rem, 4vw); padding: 1rem max(1rem, 4vw);
} }
.app-brand { .app-brand {
@ -683,7 +686,6 @@ ol li::before {
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
height: 100%; height: 100%;
width: 100%; width: 100%;
transition: background-color 0.3s;
background-color: rgba(var(--foreground-color), 1); background-color: rgba(var(--foreground-color), 1);
} }
@ -823,6 +825,10 @@ ol li::before {
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
} }
#search_query_input {
font-weight: 500;
}
#address_balance_card { #address_balance_card {
padding: 1.5rem; padding: 1.5rem;
border-radius: 0.5rem; border-radius: 0.5rem;
@ -837,6 +843,13 @@ ol li::before {
color: var(--accent-color); color: var(--accent-color);
} }
#filter_selector {
--padding: 0.3rem 0.5rem;
}
#filter_selector sm-chip {
font-weight: 500;
}
.card { .card {
padding: 0.5rem 0; padding: 0.5rem 0;
border: none; border: none;
@ -871,21 +884,55 @@ ol li::before {
border-bottom: thin solid rgba(var(--text-color), 0.3); border-bottom: thin solid rgba(var(--text-color), 0.3);
} }
#tx_details__header:has(#tx_status) time {
text-align: left;
margin-right: auto;
}
#tx_details__header:has(:not(#tx_status)) time {
text-align: right;
margin-left: auto;
}
#transactions_list { #transactions_list {
display: grid; display: grid;
gap: 2rem; gap: 2rem;
padding-bottom: 4rem; padding-bottom: 4rem;
padding-top: 2rem;
}
transaction-card {
position: relative;
}
transaction-card:not(:last-of-type) {
padding-bottom: 2rem;
}
transaction-card:not(:last-of-type)::after {
content: "";
position: absolute;
bottom: 0;
right: 0;
height: 1px;
width: calc(100% - 3.5rem);
background-color: rgba(var(--text-color), 0.2);
} }
.transaction { .transaction {
position: relative;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
gap: 0.5rem 1rem; gap: 0.5rem 1rem;
align-items: center; align-items: center;
content-visibility: auto;
contain-intrinsic-height: 8rem;
} }
.transaction:not(:last-of-type) { .transaction.in .transaction__amount {
padding-bottom: 2rem; color: var(--green);
}
.transaction.in .transaction__amount::before {
content: "+";
}
.transaction.out .transaction__amount {
color: var(--danger-color);
}
.transaction.out .transaction__amount::before {
content: "-";
} }
.transaction__amount { .transaction__amount {
white-space: nowrap; white-space: nowrap;
@ -893,21 +940,9 @@ ol li::before {
.transaction.out .transaction__icon .icon { .transaction.out .transaction__icon .icon {
fill: var(--danger-color); fill: var(--danger-color);
} }
.transaction.out .transaction__amount {
color: var(--danger-color);
}
.transaction.out .transaction__amount::before {
content: "- ";
}
.transaction.in .transaction__icon .icon { .transaction.in .transaction__icon .icon {
fill: var(--green); fill: var(--green);
} }
.transaction.in .transaction__amount {
color: var(--green);
}
.transaction.in .transaction__amount::before {
content: "+ ";
}
.transaction.unconfirmed-tx .transaction__icon .icon { .transaction.unconfirmed-tx .transaction__icon .icon {
fill: var(--yellow); fill: var(--yellow);
} }
@ -928,6 +963,7 @@ ol li::before {
.transaction__time { .transaction__time {
font-size: 0.9rem; font-size: 0.9rem;
color: rgba(var(--text-color), 0.8); color: rgba(var(--text-color), 0.8);
text-wrap: balance;
} }
.transaction__amount { .transaction__amount {
font-size: 1rem; font-size: 1rem;
@ -954,6 +990,15 @@ ol li::before {
content: ","; content: ",";
} }
#tx_details time {
padding: 0.3rem 0.8rem;
border-radius: 5rem;
background-color: rgba(var(--text-color), 0.06);
font-weight: 500;
font-size: 0.9rem;
text-wrap: balance;
}
#tx_status { #tx_status {
display: flex; display: flex;
align-items: center; align-items: center;
@ -962,11 +1007,23 @@ ol li::before {
border-radius: 0.5rem; border-radius: 0.5rem;
background-color: rgba(var(--text-color), 0.03); background-color: rgba(var(--text-color), 0.03);
color: var(--danger-color); color: var(--danger-color);
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
} }
#tx_status .icon { #tx_status .icon {
fill: var(--danger-color); fill: var(--danger-color);
} }
#tx_amount {
font-size: max(2rem, 4vw);
font-weight: 700;
text-wrap: balance;
}
#tx_amount:has(+ *) {
margin-bottom: 0.5rem;
}
#tx_technicals .tx-detail:first-of-type { #tx_technicals .tx-detail:first-of-type {
position: relative; position: relative;
} }
@ -1197,6 +1254,7 @@ ol li::before {
} }
#main_header { #main_header {
grid-area: header; grid-area: header;
padding: 1.5rem max(1rem, 4vw);
} }
#main_navbar { #main_navbar {
grid-area: nav; grid-area: nav;

2
css/main.min.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1370
index.html

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,437 @@
(function (EXPORTS) { //btcOperator v1.1.4 (function (EXPORTS) { //btcOperator v1.2.10
/* BTC Crypto and API Operator */ /* BTC Crypto and API Operator */
const btcOperator = EXPORTS; const btcOperator = EXPORTS;
const SATOSHI_IN_BTC = 1e8;
const util = btcOperator.util = {};
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
const checkIfTor = btcOperator.checkIfTor = () => {
return fetch('https://check.torproject.org/api/ip')
.then(res => res.json())
.then(res => {
return res.IsTor
}).catch(e => {
console.error(e)
return false
})
}
let isTor = false;
checkIfTor().then(result => isTor = result);
async function post(url, data, { asText = false } = {}) {
try {
const response = await fetch(url, {
method: 'POST',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
if (response.ok) {
return asText ? await response.text() : await response.json()
} else {
throw response
}
} catch (e) {
throw e
}
}
// NOTE: some APIs may not support all functions properly hence they are omitted
const APIs = btcOperator.APIs = [
{
url: 'https://api.blockcypher.com/v1/btc/main/',
name: 'Blockcypher',
balance({ addr }) {
return fetch_api(`addrs/${addr}/balance`, { url: this.url })
.then(result => util.Sat_to_BTC(result.balance))
},
async block({ id }) {
try {
let block = await fetch_api(`blocks/${id}`, { url: this.url })
return formatBlock(block)
} catch (e) {
console.log(e)
}
},
async broadcast({ rawTxHex, url }) {
try {
const result = await post(`${url || this.url}pushtx`, { tx: rawTxHex })
return result.hash
} catch (e) {
throw e
}
}
},
{
url: 'https://blockstream.info/api/',
name: 'Blockstream',
hasOnion: true,
onionUrl: `http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/api/`,
balance({ addr, url }) {
return fetch_api(`address/${addr}/utxo`, { url: url || this.url })
.then(result => {
const balance = result.reduce((t, u) => t + u.value, 0)
return util.Sat_to_BTC(balance)
})
},
latestBlock() {
return fetch_api(`blocks/tip/height`, { url: this.url })
},
tx({ txid, url }) {
return fetch_api(`tx/${txid}`, { url: url || this.url })
.then(result => formatTx(result))
},
txHex({ txid, url }) {
return fetch_api(`tx/${txid}/hex`, { url: url || this.url, asText: true })
},
txs({ addr, url, ...args }) {
let queryParams = Object.entries(args).map(([key, value]) => `${key}=${value}`).join('&')
if (queryParams)
queryParams = '?' + queryParams
return fetch_api(`address/${addr}/txs${queryParams}`, { url: url || this.url })
},
async block({ id, url }) {
// if id is hex string then it is block hash
try {
let blockHash = id
if (!/^[0-9a-f]{64}$/i.test(id))
blockHash = await fetch_api(`block-height/${id}`, { url: url || this.url, asText: true })
const block = await fetch_api(`block/${blockHash}`, { url: url || this.url })
return formatBlock(block)
} catch (e) {
throw e
}
},
async broadcast({ rawTxHex, url }) {
return post(`${url || this.url}tx`, { tx: rawTxHex }, { asText: true })
}
},
{
url: 'https://mempool.space/api/',
name: 'Mempool',
balance({ addr }) {
return fetch_api(`address/${addr}`, { url: this.url })
.then(result => util.Sat_to_BTC(result.chain_stats.funded_txo_sum - result.chain_stats.spent_txo_sum))
},
latestBlock() {
return fetch_api(`blocks/tip/height`, { url: this.url })
},
tx({ txid }) {
return fetch_api(`tx/${txid}`, { url: this.url })
.then(result => formatTx(result))
},
txHex({ txid }) {
return fetch_api(`tx/${txid}/hex`, { url: this.url, asText: true })
},
txs({ addr, ...args }) {
let queryParams = Object.entries(args).map(([key, value]) => `${key}=${value}`).join('&')
if (queryParams)
queryParams = '?' + queryParams
return fetch_api(`address/${addr}/txs${queryParams}`, { url: this.url })
},
async block({ id }) {
// if id is hex string then it is block hash
try {
let blockHash = id
if (!/^[0-9a-f]{64}$/i.test(id))
blockHash = await fetch_api(`block-height/${id}`, { url: this.url, asText: true })
const block = await fetch_api(`block/${blockHash}`, { url: this.url })
return formatBlock(block)
} catch (e) {
throw e
}
},
async broadcast({ rawTxHex, url }) {
return post(`${url || this.url}tx`, { tx: rawTxHex }, { asText: true })
}
},
{
url: 'https://blockchain.info/',
name: 'Blockchain',
balance({ addr }) {
return fetch_api(`q/addressbalance/${addr}`, { url: this.url })
.then(result => util.Sat_to_BTC(result))
},
unspent({ addr, allowUnconfirmedUtxos = false }) {
return fetch_api(`unspent?active=${addr}`, { url: this.url })
.then(result => formatUtxos(result.unspent_outputs, allowUnconfirmedUtxos))
},
tx({ txid }) {
return fetch_api(`rawtx/${txid}`, { url: this.url })
.then(result => formatTx(result))
},
txHex({ txid }) {
return fetch_api(`rawtx/${txid}?format=hex`, { url: this.url, asText: true })
},
txs({ addr, ...args }) {
let queryParams = Object.entries(args).map(([key, value]) => `${key}=${value}`).join('&')
if (queryParams)
queryParams = '?' + queryParams
return fetch_api(`rawaddr/${addr}${queryParams}`, { url: this.url })
.then(result => result.txs)
},
latestBlock() {
return fetch_api(`q/getblockcount`, { url: this.url })
},
async block({ id }) {
try {
let block
// if id is hex string then it is block hash
if (/^[0-9a-f]{64}$/i.test(id))
block = await fetch_api(`rawblock/${id}`, { url: this.url })
else {
const result = await fetch_api(`block-height/${id}?format=json`, { url: this.url })
block = result.blocks[0]
}
return formatBlock(block)
} catch (e) {
throw e
}
},
async blockTxs({ id }) {
try {
let block
// if id is hex string then it is block hash
if (/^[0-9a-f]{64}$/i.test(id))
block = await fetch_api(`rawblock/${id}`, { url: this.url })
else {
const result = await fetch_api(`block-height/${id}?format=json`, { url: this.url })
block = result.blocks[0]
}
return block.tx
} catch (e) {
}
}
},
{
url: 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction',
name: 'Coinb.in',
broadcast({ rawTxHex }) {
return new Promise((resolve, reject) => {
fetch(this.url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: "rawtx=" + rawTxHex
}).then(response => {
console.log(response)
response.text().then(resultText => {
let r = resultText.match(/<result>.*<\/result>/);
if (!r)
reject(resultText);
else {
r = r.pop().replace('<result>', '').replace('</result>', '');
if (r == '1') {
let txid = resultText.match(/<txid>.*<\/txid>/).pop().replace('<txid>', '').replace('</txid>', '');
resolve(txid);
} else if (r == '0') {
let error
if (resultText.includes('<message>')) {
error = resultText.match(/<message>.*<\/message>/).pop().replace('<message>', '').replace('</message>', '');
} else {
error = resultText.match(/<response>.*<\/response>/).pop().replace('<response>', '').replace('</response>', '');
}
reject(decodeURIComponent(error.replace(/\+/g, " ")));
} else reject(resultText);
}
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
}
]
btcOperator.util.format = {} // functions to homogenize API results
const formatBlock = btcOperator.util.format.block = async (block) => {
try {
const { height, hash, id, time, timestamp, mrkl_root, merkle_root, prev_block, next_block, size } = block;
const details = {
height,
hash: hash || id,
time: (time || timestamp) * 1000,
merkle_root: merkle_root || mrkl_root,
size,
}
if (prev_block)
details.prev_block = prev_block
if (next_block)
details.next_block = next_block[0]
return details
} catch (e) {
throw e
}
}
const formatUtxos = btcOperator.util.format.utxos = async (utxos, allowUnconfirmedUtxos = false) => {
try {
if (!allowUnconfirmedUtxos && !utxos || !Array.isArray(utxos))
throw {
message: "No utxos found",
code: 1000 //error code for when issue is not from API but situational (like no utxos found)
}
return utxos.map(utxo => {
const { tx_hash, tx_hash_big_endian, txid, tx_output_n, vout, value, script, confirmations, status: { confirmed } = {} } = utxo;
return {
confirmations: confirmations || confirmed,
tx_hash_big_endian: tx_hash_big_endian || tx_hash || txid,
tx_output_n: tx_output_n || vout,
value,
script
}
})
} catch (e) {
throw e
}
}
const formatTx = btcOperator.util.format.tx = async (tx) => {
try {
let { txid, hash, time, block_height, fee, fees, received,
confirmed, size, double_spend, block_hash, confirmations,
status: { block_height: statusBlockHeight, block_hash: statusBlockHash, block_time } = {}
} = tx;
if ((block_height || statusBlockHeight) && confirmations === undefined || confirmations === null) {
const latestBlock = await multiApi('latestBlock');
confirmations = latestBlock - (block_height || statusBlockHeight);
}
const inputs = tx.vin || tx.inputs;
const outputs = tx.vout || tx.outputs || tx.out;
return {
hash: hash || txid,
size: size,
fee: fee || fees,
double_spend,
time: (time * 1000) || new Date(confirmed || received).getTime() || block_time * 1000 || Date.now(),
block_height: block_height || statusBlockHeight,
block_hash: block_hash || statusBlockHash,
confirmations,
inputs: inputs.map(input => {
return {
index: input.n || input.output_index || input.vout,
prev_out: {
addr: input.prev_out?.addr || input.addresses?.[0] || input.prev_out?.address || input.addr || input.prevout.scriptpubkey_address,
value: input.prev_out?.value || input.output_value || input.prevout.value,
},
}
}),
out: outputs.map(output => {
return {
addr: output.scriptpubkey_address || output.addresses?.[0] || output.scriptpubkey_address || output.addr,
value: output.value || output.scriptpubkey_value,
}
})
}
} catch (e) {
throw e
}
}
const multiApi = btcOperator.multiApi = async (fnName, { index = 0, ...args } = {}) => {
try {
let triedOnion = false;
while (index < APIs.length) {
if (!APIs[index][fnName] || (APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime())) {
index += 1;
continue;
}
return await APIs[index][fnName](args);
}
if (isTor && !triedOnion) {
triedOnion = true;
index = 0;
while (index < APIs.length) {
if (!APIs[index].hasOnion || (APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime())) {
index += 1;
continue;
}
return await multiApi(fnName, { index: index + 1, ...args, url: APIs[index].onionUrl });
}
}
throw "No API available"
} catch (error) {
console.error(error)
if (!APIs[index])
throw "No API available"
APIs[index].coolDownTime = new Date().getTime() + 1000 * 60 * 10; // 10 minutes
return multiApi(fnName, { index: index + 1, ...args });
}
};
function parseTx(tx, addressOfTx) {
const { txid, hash, time, block_height, inputs, outputs, out, vin, vout, fee, fees, received, confirmed, status: { block_height: statusBlockHeight, block_time } = {} } = tx;
let parsedTx = {
txid: hash || txid,
time: (time * 1000) || new Date(confirmed || received).getTime() || block_time * 1000 || Date.now(),
block: block_height || statusBlockHeight,
}
//sender list
parsedTx.tx_senders = {};
(inputs || vin).forEach(i => {
const address = i.prev_out?.addr || i.addresses?.[0] || i.prev_out?.address || i.addr || i.prevout.scriptpubkey_address;
const value = i.prev_out?.value || i.output_value || i.value || i.prevout.value;
if (address in parsedTx.tx_senders)
parsedTx.tx_senders[address] += value;
else parsedTx.tx_senders[address] = value;
});
parsedTx.tx_input_value = 0;
for (let senderAddr in parsedTx.tx_senders) {
let val = parsedTx.tx_senders[senderAddr];
parsedTx.tx_senders[senderAddr] = util.Sat_to_BTC(val);
parsedTx.tx_input_value += val;
}
parsedTx.tx_input_value = util.Sat_to_BTC(parsedTx.tx_input_value);
//receiver list
parsedTx.tx_receivers = {};
(outputs || out || vout).forEach(o => {
const address = o.scriptpubkey_address || o.addresses?.[0] || o.scriptpubkey_address || o.addr;
const value = o.value || o.scriptpubkey_value;
if (address in parsedTx.tx_receivers)
parsedTx.tx_receivers[address] += value;
else parsedTx.tx_receivers[address] = value;
});
parsedTx.tx_output_value = 0;
for (let receiverAddr in parsedTx.tx_receivers) {
let val = parsedTx.tx_receivers[receiverAddr];
parsedTx.tx_receivers[receiverAddr] = util.Sat_to_BTC(val);
parsedTx.tx_output_value += val;
}
parsedTx.tx_output_value = util.Sat_to_BTC(parsedTx.tx_output_value);
// tx fee
parsedTx.tx_fee = util.Sat_to_BTC(fee || fees || (parsedTx.tx_input_value - parsedTx.tx_output_value));
//detect tx type (in, out, self)
if (Object.keys(parsedTx.tx_receivers).length === 1 && Object.keys(parsedTx.tx_senders).length === 1 && Object.keys(parsedTx.tx_senders)[0] === Object.keys(parsedTx.tx_receivers)[0]) {
parsedTx.type = 'self';
parsedTx.amount = parsedTx.tx_receivers[addressOfTx];
parsedTx.address = addressOfTx;
} else if (addressOfTx in parsedTx.tx_senders && Object.keys(parsedTx.tx_receivers).some(addr => addr !== addressOfTx)) {
parsedTx.type = 'out';
parsedTx.receiver = Object.keys(parsedTx.tx_receivers).filter(addr => addr != addressOfTx);
parsedTx.amount = parsedTx.receiver.reduce((t, addr) => t + parsedTx.tx_receivers[addr], 0) + parsedTx.tx_fee;
} else {
parsedTx.type = 'in';
parsedTx.sender = Object.keys(parsedTx.tx_senders).filter(addr => addr != addressOfTx);
parsedTx.amount = parsedTx.tx_receivers[addressOfTx];
}
return parsedTx;
}
//This library uses API provided by chain.so (https://chain.so/)
const URL = "https://blockchain.info/";
const DUST_AMT = 546, const DUST_AMT = 546,
MIN_FEE_UPDATE = 219; MIN_FEE_UPDATE = 219;
const fetch_api = btcOperator.fetch = function (api, json_res = true) { const fetch_api = btcOperator.fetch = function (api, { asText = false, url = 'https://blockchain.info/' } = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.debug(URL + api); console.debug(url + api);
fetch(URL + api).then(response => { fetch(url + api).then(response => {
if (response.ok) { if (response.ok) {
(json_res ? response.json() : response.text()) (asText ? response.text() : response.json())
.then(result => resolve(result)) .then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
} else { } else {
@ -25,13 +443,6 @@
}) })
}; };
const SATOSHI_IN_BTC = 1e8;
const util = btcOperator.util = {};
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
function get_fee_rate() { function get_fee_rate() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch('https://api.blockchain.info/mempool/fees').then(response => { fetch('https://api.blockchain.info/mempool/fees').then(response => {
@ -47,6 +458,13 @@
const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => { const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => {
console.log('txHex:', rawTxHex) console.log('txHex:', rawTxHex)
// return multiApi('broadcast', { rawTxHex })
// .then(result => {
// resolve(result)
// })
// .catch(error => {
// reject(error)
// })
let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction'; let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction';
fetch(url, { fetch(url, {
method: 'POST', method: 'POST',
@ -55,6 +473,7 @@
}, },
body: "rawtx=" + rawTxHex body: "rawtx=" + rawTxHex
}).then(response => { }).then(response => {
console.log(response)
response.text().then(resultText => { response.text().then(resultText => {
let r = resultText.match(/<result>.*<\/result>/); let r = resultText.match(/<result>.*<\/result>/);
if (!r) if (!r)
@ -273,10 +692,11 @@
} }
//BTC blockchain APIs //BTC blockchain APIs
btcOperator.getBalance = addr => new Promise((resolve, reject) => { btcOperator.getBalance = addr => new Promise((resolve, reject) => {
fetch_api(`q/addressbalance/${addr}`) if (!validateAddress(addr))
.then(result => resolve(util.Sat_to_BTC(result))) return reject("Invalid address");
multiApi('balance', { addr })
.then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
}); });
@ -400,7 +820,11 @@
} }
btcOperator.validateTxParameters = validateTxParameters; btcOperator.validateTxParameters = validateTxParameters;
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver, allowUnconfirmedUtxos = false) { const createTransaction = btcOperator.createTransaction = ({
senders, redeemScripts, receivers, amounts, fee, change_address,
fee_from_receiver, allowUnconfirmedUtxos = false, sendingTx = false,
hasInsufficientBalance = false
}) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8)); let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
const tx = coinjs.transaction(); const tx = coinjs.transaction();
@ -433,11 +857,16 @@
result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0); result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0);
result.total_size = BASE_TX_SIZE + output_size + result.input_size; result.total_size = BASE_TX_SIZE + output_size + result.input_size;
result.transaction = tx; result.transaction = tx;
resolve(result); if (sendingTx && result.hasOwnProperty('hasInsufficientBalance') && result.hasInsufficientBalance)
reject({
message: "Insufficient balance",
...result
});
else
resolve(result);
}).catch(error => reject(error)) }).catch(error => reject(error))
}) })
} }
btcOperator.createTransaction = createTransaction;
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) { function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -479,14 +908,18 @@
change_amount: required_amount * -1 //required_amount will be -ve of change_amount change_amount: required_amount * -1 //required_amount will be -ve of change_amount
}); });
else if (rec_args.n >= senders.length) { else if (rec_args.n >= senders.length) {
return reject("Insufficient Balance"); return resolve({
hasInsufficientBalance: true,
input_size: rec_args.input_size,
input_amount: rec_args.input_amount,
change_amount: required_amount * -1
});
} }
let addr = senders[rec_args.n], let addr = senders[rec_args.n],
rs = redeemScripts[rec_args.n]; rs = redeemScripts[rec_args.n];
let addr_type = coinjs.addressDecode(addr).type; let addr_type = coinjs.addressDecode(addr).type;
let size_per_input = _sizePerInput(addr, rs); let size_per_input = _sizePerInput(addr, rs);
fetch_api(`unspent?active=${addr}`).then(result => { multiApi('unspent', { addr, allowUnconfirmedUtxos: rec_args.allowUnconfirmedUtxos }).then(utxos => {
let utxos = result.unspent_outputs;
//console.debug("add-utxo", addr, rs, required_amount, utxos); //console.debug("add-utxo", addr, rs, required_amount, utxos);
for (let i = 0; i < utxos.length && required_amount > 0; i++) { for (let i = 0; i < utxos.length && required_amount > 0; i++) {
if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) { if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) {
@ -534,43 +967,6 @@
} }
btcOperator.addOutputs = addOutputs; btcOperator.addOutputs = addOutputs;
/*
function autoFeeCalc(tx) {
return new Promise((resolve, reject) => {
get_fee_rate().then(fee_rate => {
let tx_size = tx.size();
for (var i = 0; i < this.ins.length; i++)
switch (tx.extractScriptKey(i).type) {
case 'scriptpubkey':
tx_size += SIGN_SIZE;
break;
case 'segwit':
case 'multisig':
tx_size += SIGN_SIZE * 0.25;
break;
default:
console.warn('Unknown script-type');
tx_size += SIGN_SIZE;
}
resolve(tx_size * fee_rate);
}).catch(error => reject(error))
})
}
function editFee(tx, current_fee, target_fee, index = -1) {
//values are in satoshi
index = parseInt(index >= 0 ? index : tx.outs.length - index);
if (index < 0 || index >= tx.outs.length)
throw "Invalid index";
let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places
current_value = tx.outs[index].value; //could be BigInterger
if (edit_value < 0 && edit_value > current_value)
throw "Insufficient value at vout";
tx.outs[index].value = current_value instanceof BigInteger ?
current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value);
}
*/
function tx_fetch_for_editing(tx) { function tx_fetch_for_editing(tx) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof tx == 'string' && /^[0-9a-f]{64}$/i.test(tx)) { //tx is txid if (typeof tx == 'string' && /^[0-9a-f]{64}$/i.test(tx)) { //tx is txid
@ -793,6 +1189,7 @@
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
options.sendingTx = true;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => { createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => {
broadcastTx(result.transaction.serialize()) broadcastTx(result.transaction.serialize())
@ -816,7 +1213,7 @@
receivers, receivers,
amounts, amounts,
fee, fee,
change_address: options.change_address ...options
})); }));
} catch (e) { } catch (e) {
return reject(e) return reject(e)
@ -831,7 +1228,11 @@
if (redeemScripts.includes(null)) //TODO: segwit if (redeemScripts.includes(null)) //TODO: segwit
return reject("Unable to get redeem-script"); return reject("Unable to get redeem-script");
//create transaction //create transaction
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { createTransaction({
senders, redeemScripts, receivers, amounts, fee,
change_address: options.change_address || senders[0],
...options
}).then(result => {
let tx = result.transaction; let tx = result.transaction;
console.debug("Unsigned:", tx.serialize()); console.debug("Unsigned:", tx.serialize());
new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF
@ -864,7 +1265,11 @@
if (redeemScripts.includes(null)) //TODO: segwit if (redeemScripts.includes(null)) //TODO: segwit
return reject("Unable to get redeem-script"); return reject("Unable to get redeem-script");
//create transaction //create transaction
createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver, options.allowUnconfirmedUtxos).then(result => { createTransaction({
senders, redeemScripts, receivers, amounts, fee,
change_address: options.change_address || senders[0],
...options
}).then(result => {
result.tx_hex = result.transaction.serialize(); result.tx_hex = result.transaction.serialize();
delete result.transaction; delete result.transaction;
resolve(result); resolve(result);
@ -900,7 +1305,12 @@
return reject(e) return reject(e)
} }
//create transaction //create transaction
createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => { createTransaction({
senders: [sender], redeemScripts: [redeemScript],
receivers, amounts, fee,
change_address: options.change_address || sender,
...options
}).then(result => {
result.tx_hex = result.transaction.serialize(); result.tx_hex = result.transaction.serialize();
delete result.transaction; delete result.transaction;
resolve(result); resolve(result);
@ -975,7 +1385,7 @@
} }
const getTxOutput = (txid, i) => new Promise((resolve, reject) => { const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
fetch_api(`rawtx/${txid}`) multiApi('tx', { txid })
.then(result => resolve(result.out[i])) .then(result => resolve(result.out[i]))
.catch(error => reject(error)) .catch(error => reject(error))
}); });
@ -1031,94 +1441,41 @@
return Crypto.util.bytesToHex(txid); return Crypto.util.bytesToHex(txid);
} }
const getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => { const getTx = btcOperator.getTx = txid => new Promise(async (resolve, reject) => {
fetch_api(`q/getblockcount`) try {
.then(result => resolve(result)) const result = await multiApi('tx', { txid });
.catch(error => reject(error)) resolve({
}) confirmations: result.confirmations,
const getTx = btcOperator.getTx = txid => new Promise((resolve, reject) => {
fetch_api(`rawtx/${txid}`).then(result => {
console.debug("Tx:", result);
getLatestBlock().then(latest_block => resolve({
block: result.block_height, block: result.block_height,
txid: result.hash, txid: result.hash,
time: result.time * 1000, time: result.time,
confirmations: result.block_height === null ? 0 : latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it
size: result.size, size: result.size,
fee: util.Sat_to_BTC(result.fee), fee: util.Sat_to_BTC(result.fee),
inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })), inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)), total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })), outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)), total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)),
})) })
}).catch(error => reject(error)) } catch (error) {
}); reject(error)
}
getTx.hex = btcOperator.getTx.hex = txid => new Promise((resolve, reject) => {
fetch_api(`rawtx/${txid}?format=hex`, false)
.then(result => resolve(result))
.catch(error => reject(error))
}) })
getTx.hex = btcOperator.getTx.hex = txid => multiApi('txHex', { txid });
btcOperator.getAddressData = address => new Promise((resolve, reject) => { btcOperator.getAddressData = address => new Promise((resolve, reject) => {
fetch_api(`rawaddr/${address}`).then(data => { if (!validateAddress(address))
let details = {}; return reject("Invalid address");
details.balance = util.Sat_to_BTC(data.final_balance); Promise.all([
details.address = data.address; multiApi('balance', { addr: address }),
details.txs = data.txs.map(tx => { multiApi('txs', { addr: address })
let d = { ]).then(([balance, txs]) => {
txid: tx.hash, const parsedTxs = txs.map(tx => parseTx(tx, address));
time: tx.time * 1000, //s to ms resolve({
block: tx.block_height, address,
} balance,
//sender list txs: parsedTxs
d.tx_senders = {}; });
tx.inputs.forEach(i => {
if (i.prev_out.addr in d.tx_senders)
d.tx_senders[i.prev_out.addr] += i.prev_out.value;
else d.tx_senders[i.prev_out.addr] = i.prev_out.value;
});
d.tx_input_value = 0;
for (let s in d.tx_senders) {
let val = d.tx_senders[s];
d.tx_senders[s] = util.Sat_to_BTC(val);
d.tx_input_value += val;
}
d.tx_input_value = util.Sat_to_BTC(d.tx_input_value);
//receiver list
d.tx_receivers = {};
tx.out.forEach(o => {
if (o.addr in d.tx_receivers)
d.tx_receivers[o.addr] += o.value;
else d.tx_receivers[o.addr] = o.value;
});
d.tx_output_value = 0;
for (let r in d.tx_receivers) {
let val = d.tx_receivers[r];
d.tx_receivers[r] = util.Sat_to_BTC(val);
d.tx_output_value += val;
}
d.tx_output_value = util.Sat_to_BTC(d.tx_output_value);
d.tx_fee = util.Sat_to_BTC(tx.fee);
//tx type
if (tx.result > 0) { //net > 0, balance inc => type=in
d.type = "in";
d.amount = util.Sat_to_BTC(tx.result);
d.sender = Object.keys(d.tx_senders).filter(s => s !== address);
} else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out
d.type = "out";
d.amount = util.Sat_to_BTC(tx.result * -1);
d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address);
d.fee = d.tx_fee;
} else { //net < 0 (fee) & no other id in receiver list => type=self
d.type = "self";
d.amount = d.tx_receivers[address];
d.address = address
}
return d;
})
resolve(details);
}).catch(error => reject(error)) }).catch(error => reject(error))
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,45 +2,45 @@
/* FLO Ethereum Operators */ /* FLO Ethereum Operators */
/* Make sure you added Taproot, Keccak, FLO and BTC Libraries before */ /* Make sure you added Taproot, Keccak, FLO and BTC Libraries before */
'use strict'; 'use strict';
const floEthereum = EXPORTS; const floEthereum = EXPORTS;
const ethPrivateKeyFromWif = floEthereum.ethPrivateKeyFromWif = function(privateKey,){ const ethPrivateKeyFromWif = floEthereum.ethPrivateKeyFromWif = function (privateKey,) {
return coinjs.wif2privkey(privateKey).privkey; return coinjs.wif2privkey(privateKey).privkey;
} }
const ethAddressFromPrivateKey = floEthereum.ethAddressFromPrivateKey = function(privateKey, onlyEvenY = false){
var t1,t1_x,t1_y,t1_y_BigInt,t2,t3,t4;
var groupOrder = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
t1 = bitjs.newPubkey(privateKey); const ethAddressFromPrivateKey = floEthereum.ethAddressFromPrivateKey = function (privateKey, onlyEvenY = false) {
t1_x = t1.slice(2, 66); t1_y = t1.slice(-64); var t1, t1_x, t1_y, t1_y_BigInt, t2, t3, t4;
if (onlyEvenY) { var groupOrder = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");
t1_y_BigInt = BigInt("0x"+t1_y);
if (t1_y_BigInt % 2n !== 0n) { t1_y_BigInt = (groupOrder-t1_y_BigInt)%groupOrder; t1_y=t1_y_BigInt.toString(16)}
};
t2 = t1_x.toString(16) + t1_y.toString(16);
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
t4 = keccak.extractLast20Bytes(t3);
return "0x" + t4;
}
const ethAddressFromCompressedPublicKey = floEthereum.ethAddressFromCompressedPublicKey = function(compressedPublicKey){ t1 = bitjs.newPubkey(privateKey);
var t1,t2,t3,t4; t1_x = t1.slice(2, 66); t1_y = t1.slice(-64);
t1 = coinjs.compressedToUncompressed(compressedPublicKey); if (onlyEvenY) {
t2 = t1.slice(2); t1_y_BigInt = BigInt("0x" + t1_y);
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2)); if (t1_y_BigInt % 2n !== 0n) { t1_y_BigInt = (groupOrder - t1_y_BigInt) % groupOrder; t1_y = t1_y_BigInt.toString(16) }
t4 = keccak.extractLast20Bytes(t3); };
return "0x" + t4;
}
const ethAddressFromUncompressedPublicKey = floEthereum.ethAddressFromUncompressedPublicKey = function(unCompressedPublicKey){ t2 = t1_x.toString(16) + t1_y.toString(16);
var t1,t2,t3,t4; t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
t1 = unCompressedPublicKey; t4 = keccak.extractLast20Bytes(t3);
t2 = t1.slice(2); return "0x" + t4;
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2)); }
t4 = keccak.extractLast20Bytes(t3);
return "0x" + t4; const ethAddressFromCompressedPublicKey = floEthereum.ethAddressFromCompressedPublicKey = function (compressedPublicKey) {
} var t1, t2, t3, t4;
t1 = coinjs.compressedToUncompressed(compressedPublicKey);
t2 = t1.slice(2);
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
t4 = keccak.extractLast20Bytes(t3);
return "0x" + t4;
}
const ethAddressFromUncompressedPublicKey = floEthereum.ethAddressFromUncompressedPublicKey = function (unCompressedPublicKey) {
var t1, t2, t3, t4;
t1 = unCompressedPublicKey;
t2 = t1.slice(2);
t3 = keccak.keccak_256(Crypto.util.hexToBytes(t2));
t4 = keccak.extractLast20Bytes(t3);
return "0x" + t4;
}
})('object' === typeof module ? module.exports : window.floEthereum = {}); })('object' === typeof module ? module.exports : window.floEthereum = {});

1
scripts/floEthereum.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(EXPORTS){"use strict";const floEthereum="object"===typeof module?module.exports:window.floEthereum={};floEthereum.ethPrivateKeyFromWif=function(privateKey){return coinjs.wif2privkey(privateKey).privkey},floEthereum.ethAddressFromPrivateKey=function(privateKey,onlyEvenY=!1){var t1,t1_x,t1_y,t1_y_BigInt,t2,t3,groupOrder=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");return t1_x=(t1=bitjs.newPubkey(privateKey)).slice(2,66),t1_y=t1.slice(-64),onlyEvenY&&(t1_y_BigInt=BigInt("0x"+t1_y))%2n!==0n&&(t1_y=(t1_y_BigInt=(groupOrder-t1_y_BigInt)%groupOrder).toString(16)),t2=t1_x.toString(16)+t1_y.toString(16),t3=keccak.keccak_256(Crypto.util.hexToBytes(t2)),"0x"+keccak.extractLast20Bytes(t3)},floEthereum.ethAddressFromCompressedPublicKey=function(compressedPublicKey){var t2,t3;return t2=coinjs.compressedToUncompressed(compressedPublicKey).slice(2),t3=keccak.keccak_256(Crypto.util.hexToBytes(t2)),"0x"+keccak.extractLast20Bytes(t3)},floEthereum.ethAddressFromUncompressedPublicKey=function(unCompressedPublicKey){var t2,t3;return t2=unCompressedPublicKey.slice(2),t3=keccak.keccak_256(Crypto.util.hexToBytes(t2)),"0x"+keccak.extractLast20Bytes(t3)}}();