Compare commits
10 Commits
139dba4cd9
...
87220ae53e
| Author | SHA1 | Date | |
|---|---|---|---|
| 87220ae53e | |||
| 7198c9ad48 | |||
| 259fb0c7cf | |||
| 1ca70add07 | |||
| 11c9aed730 | |||
| 3102dfe898 | |||
| eb692be158 | |||
| f20f4ac5ca | |||
| e67385440b | |||
| 2278af46ed |
2
arbitrumwallet/README.md
Normal file
2
arbitrumwallet/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# arbitrum
|
||||
Arbitrum blockchain wallet linked with FLO and BTC network
|
||||
1291
arbitrumwallet/css/main.css
Normal file
1291
arbitrumwallet/css/main.css
Normal file
File diff suppressed because it is too large
Load Diff
1908
arbitrumwallet/index.html
Normal file
1908
arbitrumwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
1499
arbitrumwallet/scripts/btcOperator.js
Normal file
1499
arbitrumwallet/scripts/btcOperator.js
Normal file
File diff suppressed because it is too large
Load Diff
1
arbitrumwallet/scripts/btcOperator.min.js
vendored
Normal file
1
arbitrumwallet/scripts/btcOperator.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10430
arbitrumwallet/scripts/btcwallet_scripts_lib.js
Normal file
10430
arbitrumwallet/scripts/btcwallet_scripts_lib.js
Normal file
File diff suppressed because it is too large
Load Diff
57
arbitrumwallet/scripts/btcwallet_scripts_lib.min.js
vendored
Normal file
57
arbitrumwallet/scripts/btcwallet_scripts_lib.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
257
arbitrumwallet/scripts/compactIDB.js
Normal file
257
arbitrumwallet/scripts/compactIDB.js
Normal file
@ -0,0 +1,257 @@
|
||||
(function (EXPORTS) { //compactIDB v2.1.2
|
||||
/* Compact IndexedDB operations */
|
||||
'use strict';
|
||||
const compactIDB = EXPORTS;
|
||||
|
||||
var defaultDB;
|
||||
|
||||
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
const IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
|
||||
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
|
||||
|
||||
if (!indexedDB) {
|
||||
console.error("Your browser doesn't support a stable version of IndexedDB.");
|
||||
return;
|
||||
}
|
||||
|
||||
compactIDB.setDefaultDB = dbName => defaultDB = dbName;
|
||||
|
||||
Object.defineProperty(compactIDB, 'default', {
|
||||
get: () => defaultDB,
|
||||
set: dbName => defaultDB = dbName
|
||||
});
|
||||
|
||||
function getDBversion(dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
resolve(db.version)
|
||||
db.close()
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function upgradeDB(dbName, createList = null, deleteList = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDBversion(dbName).then(version => {
|
||||
var idb = indexedDB.open(dbName, version + 1);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
let db = event.target.result;
|
||||
if (createList instanceof Object) {
|
||||
if (Array.isArray(createList)) {
|
||||
let tmp = {}
|
||||
createList.forEach(o => tmp[o] = {})
|
||||
createList = tmp
|
||||
}
|
||||
for (let o in createList) {
|
||||
let obs = db.createObjectStore(o, createList[o].options || {});
|
||||
if (createList[o].indexes instanceof Object)
|
||||
for (let i in createList[o].indexes)
|
||||
obs.createIndex(i, i, createList[o].indexes || {});
|
||||
}
|
||||
}
|
||||
if (Array.isArray(deleteList))
|
||||
deleteList.forEach(o => db.deleteObjectStore(o));
|
||||
resolve('Database upgraded')
|
||||
}
|
||||
idb.onsuccess = (event) => event.target.result.close();
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
compactIDB.initDB = function (dbName, objectStores = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(objectStores instanceof Object))
|
||||
return reject('ObjectStores must be an object or array')
|
||||
defaultDB = defaultDB || dbName;
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onsuccess = (event) => {
|
||||
var db = event.target.result;
|
||||
let cList = Object.values(db.objectStoreNames);
|
||||
var obs = {},
|
||||
a_obs = {},
|
||||
d_obs = [];
|
||||
if (!Array.isArray(objectStores))
|
||||
var obs = objectStores
|
||||
else
|
||||
objectStores.forEach(o => obs[o] = {})
|
||||
let nList = Object.keys(obs)
|
||||
for (let o of nList)
|
||||
if (!cList.includes(o))
|
||||
a_obs[o] = obs[o]
|
||||
for (let o of cList)
|
||||
if (!nList.includes(o))
|
||||
d_obs.push(o)
|
||||
if (!Object.keys(a_obs).length && !d_obs.length)
|
||||
resolve("Initiated IndexedDB");
|
||||
else
|
||||
upgradeDB(dbName, a_obs, d_obs)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const openDB = compactIDB.openDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
event.target.result.close();
|
||||
deleteDB(dbName).then(_ => null).catch(_ => null).finally(_ => reject("Datebase not found"))
|
||||
}
|
||||
idb.onsuccess = (event) => resolve(event.target.result);
|
||||
});
|
||||
}
|
||||
|
||||
const deleteDB = compactIDB.deleteDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var deleteReq = indexedDB.deleteDatabase(dbName);;
|
||||
deleteReq.onerror = (event) => reject("Error deleting database!");
|
||||
deleteReq.onsuccess = (event) => resolve("Database deleted successfully");
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.writeData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let writeReq = (key ? obs.put(data, key) : obs.put(data));
|
||||
writeReq.onsuccess = (evt) => resolve(`Write data Successful`);
|
||||
writeReq.onerror = (evt) => reject(
|
||||
`Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.addData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let addReq = (key ? obs.add(data, key) : obs.add(data));
|
||||
addReq.onsuccess = (evt) => resolve(`Add data successful`);
|
||||
addReq.onerror = (evt) => reject(
|
||||
`Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.removeData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let delReq = obs.delete(key);
|
||||
delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`);
|
||||
delReq.onerror = (evt) => reject(
|
||||
`Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.clearData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let clearReq = obs.clear();
|
||||
clearReq.onsuccess = (evt) => resolve(`Clear data Successful`);
|
||||
clearReq.onerror = (evt) => reject(`Clear data Unsuccessful`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
let getReq = obs.get(key);
|
||||
getReq.onsuccess = (evt) => resolve(evt.target.result);
|
||||
getReq.onerror = (evt) => reject(
|
||||
`Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readAllData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var tmpResult = {}
|
||||
let curReq = obs.openCursor();
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (cursor) {
|
||||
tmpResult[cursor.primaryKey] = cursor.value;
|
||||
cursor.continue();
|
||||
} else
|
||||
resolve(tmpResult);
|
||||
}
|
||||
curReq.onerror = (evt) => reject(
|
||||
`Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/* compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let keyRange;
|
||||
if(options.lowerKey!==null && options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.bound(options.lowerKey, options.upperKey);
|
||||
else if(options.lowerKey!==null)
|
||||
keyRange = IDBKeyRange.lowerBound(options.lowerKey);
|
||||
else if (options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.upperBound(options.upperBound);
|
||||
else if (options.atKey)
|
||||
let curReq = obs.openCursor(keyRange, )
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}*/
|
||||
|
||||
compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
options.lowerKey = options.atKey || options.lowerKey || 0
|
||||
options.upperKey = options.atKey || options.upperKey || false
|
||||
options.patternEval = options.patternEval || ((k, v) => true);
|
||||
options.limit = options.limit || false;
|
||||
options.reverse = options.reverse || false;
|
||||
options.lastOnly = options.lastOnly || false
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let curReq = obs.openCursor(
|
||||
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey),
|
||||
options.lastOnly || options.reverse ? "prev" : "next");
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length))
|
||||
return resolve(filteredResult); //reached end of key list or limit reached
|
||||
else if (options.patternEval(cursor.primaryKey, cursor.value)) {
|
||||
filteredResult[cursor.primaryKey] = cursor.value;
|
||||
options.lastOnly ? resolve(filteredResult) : cursor.continue();
|
||||
} else
|
||||
cursor.continue();
|
||||
}
|
||||
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
})(window.compactIDB = {});
|
||||
9
arbitrumwallet/scripts/components.js
Normal file
9
arbitrumwallet/scripts/components.js
Normal file
File diff suppressed because one or more lines are too long
1
arbitrumwallet/scripts/components.min.js
vendored
Normal file
1
arbitrumwallet/scripts/components.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
556
arbitrumwallet/scripts/ethOperator.js
Normal file
556
arbitrumwallet/scripts/ethOperator.js
Normal file
@ -0,0 +1,556 @@
|
||||
(function (EXPORTS) { // ethOperator v1.0.2
|
||||
/* ETH Crypto and API Operator */
|
||||
if (!window.ethers)
|
||||
return console.error('ethers.js not found')
|
||||
const ethOperator = EXPORTS;
|
||||
const isValidAddress = ethOperator.isValidAddress = (address) => {
|
||||
try {
|
||||
// Check if the address is a valid checksum address
|
||||
const isValidChecksum = ethers.utils.isAddress(address);
|
||||
// Check if the address is a valid non-checksum address
|
||||
const isValidNonChecksum = ethers.utils.getAddress(address) === address.toLowerCase();
|
||||
return isValidChecksum || isValidNonChecksum;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const ERC20ABI = [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
const CONTRACT_ADDRESSES = {
|
||||
// Arbitrum One network token addresses
|
||||
usdc: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC on Arbitrum
|
||||
usdt: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT on Arbitrum
|
||||
weth: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" // Wrapped ETH on Arbitrum
|
||||
}
|
||||
/**
|
||||
* Get Arbitrum provider (MetaMask or public RPC)
|
||||
* @param {boolean} readOnly - If true, use public RPC; if false, use MetaMask when available
|
||||
* @returns {ethers.providers.Provider} Arbitrum provider instance
|
||||
*/
|
||||
const getProvider = ethOperator.getProvider = (readOnly = false) => {
|
||||
if (!readOnly && window.ethereum) {
|
||||
return new ethers.providers.Web3Provider(window.ethereum);
|
||||
} else {
|
||||
return new ethers.providers.JsonRpcProvider(`https://arb1.arbitrum.io/rpc`)
|
||||
}
|
||||
}
|
||||
// Note: MetaMask connection is handled in the UI layer, not here
|
||||
const getBalance = ethOperator.getBalance = async (address) => {
|
||||
try {
|
||||
if (!address || !isValidAddress(address))
|
||||
return new Error('Invalid address');
|
||||
|
||||
// Use read-only provider (public RPC) for balance checks
|
||||
const provider = getProvider(true);
|
||||
const balanceWei = await provider.getBalance(address);
|
||||
const balanceEth = parseFloat(ethers.utils.formatEther(balanceWei));
|
||||
return balanceEth;
|
||||
} catch (error) {
|
||||
console.error('Balance error:', error.message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
const getTokenBalance = ethOperator.getTokenBalance = async (address, token, { contractAddress } = {}) => {
|
||||
try {
|
||||
if (!token)
|
||||
return new Error("Token not specified");
|
||||
if (!CONTRACT_ADDRESSES[token] && !contractAddress)
|
||||
return new Error('Contract address of token not available')
|
||||
|
||||
// Use read-only provider (public RPC) for token balance checks
|
||||
const provider = getProvider(true);
|
||||
const tokenAddress = CONTRACT_ADDRESSES[token] || contractAddress;
|
||||
const tokenContract = new ethers.Contract(tokenAddress, ERC20ABI, provider);
|
||||
let balance = await tokenContract.balanceOf(address);
|
||||
|
||||
// WETH uses 18 decimals (like native ETH), USDC and USDT use 6 decimals
|
||||
const decimals = token === 'weth' ? 18 : 6;
|
||||
balance = parseFloat(ethers.utils.formatUnits(balance, decimals));
|
||||
return balance;
|
||||
} catch (e) {
|
||||
console.error('Token balance error:', e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const estimateGas = ethOperator.estimateGas = async ({ privateKey, receiver, amount }) => {
|
||||
try {
|
||||
const provider = getProvider();
|
||||
const signer = new ethers.Wallet(privateKey, provider);
|
||||
return provider.estimateGas({
|
||||
from: signer.address,
|
||||
to: receiver,
|
||||
value: ethers.utils.parseUnits(amount, "ether"),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const sendTransaction = ethOperator.sendTransaction = async ({ privateKey, receiver, amount }) => {
|
||||
try {
|
||||
const provider = getProvider();
|
||||
const signer = new ethers.Wallet(privateKey, provider);
|
||||
const limit = await estimateGas({ privateKey, receiver, amount })
|
||||
|
||||
// Get current fee data from the network
|
||||
const feeData = await provider.getFeeData();
|
||||
|
||||
// Calculate priority fee (tip to miners) - use 1.5 gwei or the network's suggested priority fee, whichever is higher
|
||||
const priorityFee = feeData.maxPriorityFeePerGas || ethers.utils.parseUnits("1.5", "gwei");
|
||||
|
||||
// Calculate max fee per gas (base fee + priority fee)
|
||||
// Use the network's suggested maxFeePerGas or calculate it manually
|
||||
let maxFee = feeData.maxFeePerGas;
|
||||
|
||||
// If maxFeePerGas is not available or is less than priority fee, calculate it
|
||||
if (!maxFee || maxFee.lt(priorityFee)) {
|
||||
// Get the base fee from the latest block and add our priority fee
|
||||
const block = await provider.getBlock("latest");
|
||||
const baseFee = block.baseFeePerGas || ethers.utils.parseUnits("1", "gwei");
|
||||
// maxFee = (baseFee * 2) + priorityFee to account for potential base fee increases
|
||||
maxFee = baseFee.mul(2).add(priorityFee);
|
||||
}
|
||||
|
||||
// Ensure maxFee is at least 1.5x the priority fee for safety
|
||||
const minMaxFee = priorityFee.mul(15).div(10); // 1.5x priority fee
|
||||
if (maxFee.lt(minMaxFee)) {
|
||||
maxFee = minMaxFee;
|
||||
}
|
||||
|
||||
// Creating and sending the transaction object
|
||||
return signer.sendTransaction({
|
||||
to: receiver,
|
||||
value: ethers.utils.parseUnits(amount, "ether"),
|
||||
gasLimit: limit,
|
||||
nonce: await signer.getTransactionCount(),
|
||||
maxPriorityFeePerGas: priorityFee,
|
||||
maxFeePerGas: maxFee,
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send ERC20 tokens (USDC, USDT, or WETH)
|
||||
* @param {object} params - Transaction parameters
|
||||
* @param {string} params.token - Token symbol ('usdc', 'usdt', or 'weth')
|
||||
* @param {string} params.privateKey - Sender's private key
|
||||
* @param {string} params.amount - Amount to send
|
||||
* @param {string} params.receiver - Recipient's Arbitrum address
|
||||
* @param {string} params.contractAddress - Optional custom contract address
|
||||
* @returns {Promise} Transaction promise
|
||||
*/
|
||||
const sendToken = ethOperator.sendToken = async ({ token, privateKey, amount, receiver, contractAddress }) => {
|
||||
const wallet = new ethers.Wallet(privateKey, getProvider());
|
||||
const tokenContract = new ethers.Contract(CONTRACT_ADDRESSES[token] || contractAddress, ERC20ABI, wallet);
|
||||
// Convert amount to smallest unit: WETH uses 18 decimals, USDC and USDT use 6 decimals
|
||||
const decimals = token === 'weth' ? 18 : 6;
|
||||
const amountWei = ethers.utils.parseUnits(amount.toString(), decimals);
|
||||
return tokenContract.transfer(receiver, amountWei)
|
||||
}
|
||||
|
||||
|
||||
const MORALIS_API_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjNmMjE5NjM5LTQwYmYtNDhkMC1hNDMxLTI5YjA4YzhlYzE5MiIsIm9yZ0lkIjoiNDkwNTU1IiwidXNlcklkIjoiNTA0NzE5IiwidHlwZUlkIjoiYWNiMjQzOWUtMDEzYy00YjhjLWI2N2MtNjRlNGNhMjA4YTlkIiwidHlwZSI6IlBST0pFQ1QiLCJpYXQiOjE3Njg1MDcyNTIsImV4cCI6NDkyNDI2NzI1Mn0.X4Hn3VxLVRJL6HlAGPFQdWvQAdTXO20_Z8CpWhNt5CE';
|
||||
|
||||
/**
|
||||
* Get transaction history for an Arbitrum address using Moralis API
|
||||
* @param {string} address - Arbitrum address
|
||||
* @param {object} options - Optional parameters
|
||||
* @returns {Promise<Array>} Array of transactions
|
||||
*/
|
||||
const getTransactionHistory = ethOperator.getTransactionHistory = async (address, options = {}) => {
|
||||
try {
|
||||
if (!address || !isValidAddress(address)) {
|
||||
throw new Error('Invalid Arbitrum address');
|
||||
}
|
||||
|
||||
const {
|
||||
page = 1,
|
||||
offset = 100,
|
||||
} = options;
|
||||
|
||||
// Moralis API endpoint for Arbitrum
|
||||
const chain = '0xa4b1'; // Arbitrum One chain ID in hex
|
||||
|
||||
// Fetch transactions using Moralis API
|
||||
const moralisUrl = `https://deep-index.moralis.io/api/v2.2/${address}?chain=${chain}&limit=${offset}`;
|
||||
|
||||
const response = await fetch(moralisUrl, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-API-Key': MORALIS_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(`Moralis API Error: ${errorData.message || response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.result || data.result.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Parse and format transactions from Moralis response
|
||||
return data.result.map(tx => {
|
||||
const isReceived = tx.to_address && tx.to_address.toLowerCase() === address.toLowerCase();
|
||||
const value = parseFloat(ethers.utils.formatEther(tx.value || '0'));
|
||||
|
||||
return {
|
||||
hash: tx.hash,
|
||||
from: tx.from_address,
|
||||
to: tx.to_address,
|
||||
value: value,
|
||||
symbol: 'ETH',
|
||||
timestamp: new Date(tx.block_timestamp).getTime() / 1000,
|
||||
blockNumber: parseInt(tx.block_number),
|
||||
isReceived: isReceived,
|
||||
isSent: !isReceived,
|
||||
gasUsed: tx.receipt_gas_used ? parseInt(tx.receipt_gas_used) : 0,
|
||||
gasPrice: tx.gas_price ? parseFloat(ethers.utils.formatUnits(tx.gas_price, 'gwei')) : 0,
|
||||
isError: tx.receipt_status === '0',
|
||||
contractAddress: tx.to_address && tx.input !== '0x' ? tx.to_address : null,
|
||||
tokenName: null,
|
||||
confirmations: 0,
|
||||
nonce: tx.nonce ? parseInt(tx.nonce) : 0,
|
||||
input: tx.input || '0x',
|
||||
isTokenTransfer: false
|
||||
};
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching transaction history:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get detailed information about a specific transaction
|
||||
* @param {string} txHash - Transaction hash
|
||||
* @returns {Promise<Object>} Transaction details
|
||||
*/
|
||||
const getTransactionDetails = ethOperator.getTransactionDetails = async (txHash) => {
|
||||
try {
|
||||
if (!txHash || !/^0x([A-Fa-f0-9]{64})$/.test(txHash)) {
|
||||
throw new Error('Invalid transaction hash');
|
||||
}
|
||||
|
||||
// Use read-only provider for fetching transaction details
|
||||
const provider = getProvider(true);
|
||||
|
||||
// Get transaction details
|
||||
const tx = await provider.getTransaction(txHash);
|
||||
|
||||
if (!tx) {
|
||||
throw new Error('Transaction not found');
|
||||
}
|
||||
|
||||
// Get transaction receipt for status and gas used
|
||||
const receipt = await provider.getTransactionReceipt(txHash);
|
||||
|
||||
// Get current block number for confirmations
|
||||
const currentBlock = await provider.getBlockNumber();
|
||||
|
||||
// Get block details for timestamp
|
||||
const block = await provider.getBlock(tx.blockNumber);
|
||||
|
||||
// Calculate gas fee
|
||||
const gasUsed = receipt ? receipt.gasUsed : null;
|
||||
const effectiveGasPrice = receipt ? receipt.effectiveGasPrice : tx.gasPrice;
|
||||
const gasFee = gasUsed && effectiveGasPrice ?
|
||||
parseFloat(ethers.utils.formatEther(gasUsed.mul(effectiveGasPrice))) : null;
|
||||
|
||||
// Check if it's a token transfer by examining logs
|
||||
let tokenTransfer = null;
|
||||
if (receipt && receipt.logs.length > 0) {
|
||||
// Try to decode ERC20 Transfer event
|
||||
const transferEventSignature = ethers.utils.id('Transfer(address,address,uint256)');
|
||||
const transferLog = receipt.logs.find(log => log.topics[0] === transferEventSignature);
|
||||
|
||||
if (transferLog) {
|
||||
try {
|
||||
const tokenContract = new ethers.Contract(transferLog.address, ERC20ABI, provider);
|
||||
const [symbol, decimals] = await Promise.all([
|
||||
tokenContract.symbol().catch(() => 'TOKEN'),
|
||||
tokenContract.decimals().catch(() => 18)
|
||||
]);
|
||||
|
||||
const from = ethers.utils.getAddress('0x' + transferLog.topics[1].slice(26));
|
||||
const to = ethers.utils.getAddress('0x' + transferLog.topics[2].slice(26));
|
||||
const value = parseFloat(ethers.utils.formatUnits(transferLog.data, decimals));
|
||||
|
||||
tokenTransfer = {
|
||||
from,
|
||||
to,
|
||||
value,
|
||||
symbol,
|
||||
contractAddress: transferLog.address
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn('Could not decode token transfer:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hash: tx.hash,
|
||||
from: tx.from,
|
||||
to: tx.to,
|
||||
value: parseFloat(ethers.utils.formatEther(tx.value)),
|
||||
symbol: 'ETH',
|
||||
blockNumber: tx.blockNumber,
|
||||
timestamp: block ? block.timestamp : null,
|
||||
confirmations: currentBlock - tx.blockNumber,
|
||||
gasLimit: tx.gasLimit.toString(),
|
||||
gasUsed: gasUsed ? gasUsed.toString() : null,
|
||||
gasPrice: parseFloat(ethers.utils.formatUnits(tx.gasPrice, 'gwei')),
|
||||
gasFee: gasFee,
|
||||
nonce: tx.nonce,
|
||||
input: tx.data,
|
||||
status: receipt ? (receipt.status === 1 ? 'success' : 'failed') : 'pending',
|
||||
isError: receipt ? receipt.status !== 1 : false,
|
||||
tokenTransfer: tokenTransfer,
|
||||
logs: receipt ? receipt.logs : [],
|
||||
type: tx.type
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching transaction details:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a string is a valid transaction hash
|
||||
* @param {string} hash - Potential transaction hash
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isValidTxHash = ethOperator.isValidTxHash = (hash) => {
|
||||
return /^0x([A-Fa-f0-9]{64})$/.test(hash);
|
||||
};
|
||||
|
||||
})('object' === typeof module ? module.exports : window.ethOperator = {});
|
||||
1
arbitrumwallet/scripts/ethOperator.min.js
vendored
Normal file
1
arbitrumwallet/scripts/ethOperator.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(EXPORTS){if(!window.ethers)return console.error("ethers.js not found");const ethOperator=EXPORTS,isValidAddress=ethOperator.isValidAddress=address=>{try{const isValidChecksum=ethers.utils.isAddress(address),isValidNonChecksum=ethers.utils.getAddress(address)===address.toLowerCase();return isValidChecksum||isValidNonChecksum}catch(error){return!1}},ERC20ABI=[{constant:!0,inputs:[],name:"name",outputs:[{name:"",type:"string"}],payable:!1,stateMutability:"view",type:"function"},{constant:!1,inputs:[{name:"_spender",type:"address"},{name:"_value",type:"uint256"}],name:"approve",outputs:[{name:"",type:"bool"}],payable:!1,stateMutability:"nonpayable",type:"function"},{constant:!0,inputs:[],name:"totalSupply",outputs:[{name:"",type:"uint256"}],payable:!1,stateMutability:"view",type:"function"},{constant:!1,inputs:[{name:"_from",type:"address"},{name:"_to",type:"address"},{name:"_value",type:"uint256"}],name:"transferFrom",outputs:[{name:"",type:"bool"}],payable:!1,stateMutability:"nonpayable",type:"function"},{constant:!0,inputs:[],name:"decimals",outputs:[{name:"",type:"uint8"}],payable:!1,stateMutability:"view",type:"function"},{constant:!0,inputs:[{name:"_owner",type:"address"}],name:"balanceOf",outputs:[{name:"balance",type:"uint256"}],payable:!1,stateMutability:"view",type:"function"},{constant:!0,inputs:[],name:"symbol",outputs:[{name:"",type:"string"}],payable:!1,stateMutability:"view",type:"function"},{constant:!1,inputs:[{name:"_to",type:"address"},{name:"_value",type:"uint256"}],name:"transfer",outputs:[{name:"",type:"bool"}],payable:!1,stateMutability:"nonpayable",type:"function"},{constant:!0,inputs:[{name:"_owner",type:"address"},{name:"_spender",type:"address"}],name:"allowance",outputs:[{name:"",type:"uint256"}],payable:!1,stateMutability:"view",type:"function"},{payable:!0,stateMutability:"payable",type:"fallback"},{anonymous:!1,inputs:[{indexed:!0,name:"owner",type:"address"},{indexed:!0,name:"spender",type:"address"},{indexed:!1,name:"value",type:"uint256"}],name:"Approval",type:"event"},{anonymous:!1,inputs:[{indexed:!0,name:"from",type:"address"},{indexed:!0,name:"to",type:"address"},{indexed:!1,name:"value",type:"uint256"}],name:"Transfer",type:"event"}],CONTRACT_ADDRESSES={usdc:"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",usdt:"0xdac17f958d2ee523a2206206994597c13d831ec7"};function getProvider(){return window.ethereum?new ethers.providers.Web3Provider(window.ethereum):new ethers.providers.JsonRpcProvider("https://mainnet.infura.io/v3/6e12fee52bdd48208f0d82fb345bcb3c")}ethOperator.getBalance=async address=>{try{if(!address||!isValidAddress(address))return new Error("Invalid address");const provider=getProvider(),balanceWei=await provider.getBalance(address);return parseFloat(ethers.utils.formatEther(balanceWei))}catch(error){return console.error("Error:",error.message),error}},ethOperator.getTokenBalance=async(address,token,{contractAddress:contractAddress}={})=>{try{if(!token)return new Error("Token not specified");if(!CONTRACT_ADDRESSES[token]&&contractAddress)return new Error("Contract address of token not available");const usdcContract=new ethers.Contract(CONTRACT_ADDRESSES[token]||contractAddress,ERC20ABI,getProvider());let balance=await usdcContract.balanceOf(address);return balance=parseFloat(ethers.utils.formatUnits(balance,6)),balance}catch(e){console.error(e)}};const estimateGas=ethOperator.estimateGas=async({privateKey:privateKey,receiver:receiver,amount:amount})=>{try{const provider=getProvider(),signer=new ethers.Wallet(privateKey,provider);return provider.estimateGas({from:signer.address,to:receiver,value:ethers.utils.parseUnits(amount,"ether")})}catch(e){throw new Error(e)}};ethOperator.sendTransaction=async({privateKey:privateKey,receiver:receiver,amount:amount})=>{try{const provider=getProvider(),signer=new ethers.Wallet(privateKey,provider),limit=await estimateGas({privateKey:privateKey,receiver:receiver,amount:amount});return signer.sendTransaction({to:receiver,value:ethers.utils.parseUnits(amount,"ether"),gasLimit:limit,nonce:signer.getTransactionCount(),maxPriorityFeePerGas:ethers.utils.parseUnits("2","gwei")})}catch(e){throw new Error(e)}},ethOperator.sendToken=async({token:token,privateKey:privateKey,amount:amount,receiver:receiver,contractAddress:contractAddress})=>{const wallet=new ethers.Wallet(privateKey,getProvider()),tokenContract=new ethers.Contract(CONTRACT_ADDRESSES[token]||contractAddress,ERC20ABI,wallet),amountWei=ethers.utils.parseUnits(amount.toString(),6);return tokenContract.transfer(receiver,amountWei)}}("object"==typeof module?module.exports:window.ethOperator={});
|
||||
1
arbitrumwallet/scripts/ether.umd.min.js
vendored
Normal file
1
arbitrumwallet/scripts/ether.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
530
arbitrumwallet/scripts/floCrypto.js
Normal file
530
arbitrumwallet/scripts/floCrypto.js
Normal file
@ -0,0 +1,530 @@
|
||||
(function (EXPORTS) { //floCrypto v2.3.6a
|
||||
/* FLO Crypto Operators */
|
||||
'use strict';
|
||||
const floCrypto = EXPORTS;
|
||||
|
||||
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||
|
||||
function calculateY(x) {
|
||||
let exp = exponent1();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||
}
|
||||
|
||||
function getUncompressedPublicKey(compressedPublicKey) {
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift() // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0) // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes)
|
||||
let xDecimalValue = x.toString()
|
||||
// Fetch y
|
||||
let y = calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check)
|
||||
yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue
|
||||
};
|
||||
}
|
||||
|
||||
function getSenderPublicKeyString() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString
|
||||
}
|
||||
}
|
||||
|
||||
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||
return senderDerivedKey;
|
||||
}
|
||||
|
||||
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
}
|
||||
|
||||
function getReceiverPublicKeyString(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
}
|
||||
|
||||
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||
pk.shift()
|
||||
pk.splice(-4, 4)
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop()
|
||||
pk.unshift(0)
|
||||
let privateKeyDecimal = BigInteger(pk).toString()
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex
|
||||
}
|
||||
}
|
||||
|
||||
//generate a random Interger within range
|
||||
floCrypto.randInt = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
floCrypto.randString = function (length, alphaNumeric = true) {
|
||||
var result = '';
|
||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
//Encrypt Data using public-key
|
||||
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||
var senderECKeyData = getSenderPublicKeyString();
|
||||
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||
};
|
||||
}
|
||||
|
||||
//Decrypt Data using private-key
|
||||
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
}
|
||||
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function (data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
}
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
}
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
const generateNewID = floCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat()
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(floCrypto, {
|
||||
newID: {
|
||||
get: () => generateNewID()
|
||||
},
|
||||
hashID: {
|
||||
value: (str) => {
|
||||
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
},
|
||||
tmpID: {
|
||||
get: () => {
|
||||
let bytes = Crypto.util.randomBytes(20);
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Returns public-key from private-key
|
||||
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||
if (!privateKeyHex)
|
||||
return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
}
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
floCrypto.getFloID = function (keyHex) {
|
||||
if (!keyHex)
|
||||
return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null)
|
||||
key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getAddress = function (privateKeyHex, strict = false) {
|
||||
if (!privateKeyHex)
|
||||
return;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
let pubKey = key.getPubKeyHex(),
|
||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
||||
switch (version) {
|
||||
case coinjs.priv: //BTC
|
||||
return coinjs.bech32Address(pubKey).address;
|
||||
case bitjs.priv: //FLO
|
||||
return bitjs.pubkey2address(pubKey);
|
||||
default:
|
||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
||||
}
|
||||
}
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
|
||||
if (!privateKeyHex || !pubKey_floID)
|
||||
return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||
return true;
|
||||
else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
||||
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
|
||||
return null;
|
||||
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
|
||||
return null;
|
||||
try {
|
||||
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
||||
return multisig;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.decodeRedeemScript = function (redeemScript) {
|
||||
try {
|
||||
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
||||
return decoded;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
||||
if (!floID)
|
||||
return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
||||
return false;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given address (any blockchain) is valid or not
|
||||
floCrypto.validateAddr = function (address, std = true, bech = true) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return false;
|
||||
if (typeof raw.version !== 'undefined') { //legacy or segwit
|
||||
if (std == false)
|
||||
return false;
|
||||
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else if (typeof raw.bech_version !== 'undefined') { //bech32
|
||||
if (bech === false)
|
||||
return false;
|
||||
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else //unknown
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check the public-key (or redeem-script) for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
|
||||
if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
|
||||
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
||||
return pub_hash === raw.hex;
|
||||
}
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
raw.bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Convert raw address bytes to floID
|
||||
floCrypto.rawToFloID = function (raw_bytes) {
|
||||
if (typeof raw_bytes === 'string')
|
||||
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
||||
if (raw_bytes.length != 20)
|
||||
return null;
|
||||
raw_bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
||||
floCrypto.toMultisigFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
if (typeof raw.bech_version !== 'undefined') {
|
||||
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
||||
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
||||
raw.bytes = ripemd160(raw.bytes, {
|
||||
asBytes: true
|
||||
});
|
||||
}
|
||||
raw.bytes.unshift(bitjs.multisig);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
||||
if (!addr1 || !addr2)
|
||||
return;
|
||||
let raw1 = decodeAddress(addr1),
|
||||
raw2 = decodeAddress(addr2);
|
||||
if (!raw1 || !raw2)
|
||||
return false;
|
||||
else {
|
||||
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
|
||||
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
|
||||
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
|
||||
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
|
||||
return raw1.hex === raw2.hex;
|
||||
}
|
||||
}
|
||||
|
||||
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
||||
if (!address)
|
||||
return;
|
||||
else if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
let bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
|
||||
version: bytes.shift(),
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else if (address.length == 42 || address.length == 62) { //bech encoding
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
let bytes = decode.data;
|
||||
let bech_version = bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
return {
|
||||
bech_version,
|
||||
hrp: decode.hrp,
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Verifies the shares and str
|
||||
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||
if (!str)
|
||||
return null;
|
||||
else if (retrieveShamirSecret(sharesArray) === str)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
|
||||
if (typeof string !== "string")
|
||||
return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x, invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids)
|
||||
invalids[string[i]].push(i)
|
||||
else
|
||||
invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length)
|
||||
return invalids;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
|
||||
let chars = validateASCII(string, false);
|
||||
if (chars === true)
|
||||
return string;
|
||||
else if (chars === null)
|
||||
return null;
|
||||
let convertor, result = string,
|
||||
refAlt = {};
|
||||
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove")
|
||||
convertor = c => "";
|
||||
else if (mode === "soft-remove")
|
||||
convertor = c => refAlt[c] || "";
|
||||
else
|
||||
return null;
|
||||
for (let c in chars)
|
||||
result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
}
|
||||
|
||||
floCrypto.revertUnicode = function (string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floCrypto = {});
|
||||
57
arbitrumwallet/scripts/floEthereum.js
Normal file
57
arbitrumwallet/scripts/floEthereum.js
Normal file
@ -0,0 +1,57 @@
|
||||
(function (EXPORTS) { //floEthereum v1.0.1a
|
||||
/* FLO Ethereum Operators */
|
||||
/* Make sure you added Taproot, Keccak, FLO and BTC Libraries before */
|
||||
'use strict';
|
||||
const floEthereum = EXPORTS;
|
||||
|
||||
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);
|
||||
t1_x = t1.slice(2, 66); t1_y = t1.slice(-64);
|
||||
if (onlyEvenY) {
|
||||
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) {
|
||||
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 ethPrivateKeyFromUntweakedPrivateKey = floEthereum.ethPrivateKeyFromUntweakedPrivateKey = function (untweakedPrivateKey) {
|
||||
var t1;
|
||||
t1 = hex.encode(taproot.taprootTweakPrivKey(hex.decode(untweakedPrivateKey)));
|
||||
return t1;
|
||||
}
|
||||
|
||||
const ethAddressFromUntweakedPrivateKey = floEthereum.ethAddressFromUntweakedPrivateKey = function (untweakedPrivateKey) {
|
||||
var t1, t2;
|
||||
t1 = hex.encode(taproot.taprootTweakPrivKey(hex.decode(untweakedPrivateKey)));
|
||||
t2 = ethAddressFromPrivateKey(t1);
|
||||
return t2;
|
||||
}
|
||||
|
||||
const ethAddressFromTaprootAddress = floEthereum.ethAddressFromTaprootAddress = function (taprootAddress) {
|
||||
var t1, t2, t3, t4;
|
||||
t1 = coinjs.addressDecode(taprootAddress);
|
||||
t2 = t1.outstring.slice(4);
|
||||
t3 = "02" + t2;
|
||||
t4 = ethAddressFromCompressedPublicKey(t3);
|
||||
return t4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floEthereum = {});
|
||||
1
arbitrumwallet/scripts/floEthereum.min.js
vendored
Normal file
1
arbitrumwallet/scripts/floEthereum.min.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
!function(EXPORTS){"use strict";const floEthereum="object"===typeof module?module.exports:window.floEthereum={},ethAddressFromPrivateKey=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)},ethAddressFromCompressedPublicKey=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.ethPrivateKeyFromUntweakedPrivateKey=function(untweakedPrivateKey){return hex.encode(taproot.taprootTweakPrivKey(hex.decode(untweakedPrivateKey)))},floEthereum.ethAddressFromUntweakedPrivateKey=function(untweakedPrivateKey){var t1;return t1=hex.encode(taproot.taprootTweakPrivKey(hex.decode(untweakedPrivateKey))),ethAddressFromPrivateKey(t1)},floEthereum.ethAddressFromTaprootAddress=function(taprootAddress){var t2;return t2=coinjs.addressDecode(taprootAddress).outstring.slice(4),ethAddressFromCompressedPublicKey("02"+t2)}}();
|
||||
674
arbitrumwallet/scripts/keccak.js
Normal file
674
arbitrumwallet/scripts/keccak.js
Normal file
@ -0,0 +1,674 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var INPUT_ERROR = 'input is invalid type';
|
||||
var FINALIZE_ERROR = 'finalize already called';
|
||||
var WINDOW = typeof window === 'object';
|
||||
var root = WINDOW ? (window.keccak = window.keccak || {}) : {};
|
||||
if (root.JS_SHA3_NO_WINDOW) {
|
||||
WINDOW = false;
|
||||
}
|
||||
var WEB_WORKER = !WINDOW && typeof self === 'object';
|
||||
var NODE_JS = !root.JS_SHA3_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
|
||||
if (NODE_JS) {
|
||||
root = global;
|
||||
} else if (WEB_WORKER) {
|
||||
root = self;
|
||||
}
|
||||
var COMMON_JS = !root.JS_SHA3_NO_COMMON_JS && typeof module === 'object' && module.exports;
|
||||
var AMD = typeof define === 'function' && define.amd;
|
||||
var ARRAY_BUFFER = !root.JS_SHA3_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
|
||||
var HEX_CHARS = '0123456789abcdef'.split('');
|
||||
var SHAKE_PADDING = [31, 7936, 2031616, 520093696];
|
||||
var CSHAKE_PADDING = [4, 1024, 262144, 67108864];
|
||||
var KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
var PADDING = [6, 1536, 393216, 100663296];
|
||||
var SHIFT = [0, 8, 16, 24];
|
||||
var RC = [1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
||||
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
||||
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
||||
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
||||
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
var BITS = [224, 256, 384, 512];
|
||||
var SHAKE_BITS = [128, 256];
|
||||
var OUTPUT_TYPES = ['hex', 'buffer', 'arrayBuffer', 'array', 'digest'];
|
||||
var CSHAKE_BYTEPAD = {
|
||||
'128': 168,
|
||||
'256': 136
|
||||
};
|
||||
|
||||
|
||||
var isArray = root.JS_SHA3_NO_NODE_JS || !Array.isArray
|
||||
? function (obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
}
|
||||
: Array.isArray;
|
||||
|
||||
var isView = (ARRAY_BUFFER && (root.JS_SHA3_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView))
|
||||
? function (obj) {
|
||||
return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
|
||||
}
|
||||
: ArrayBuffer.isView;
|
||||
|
||||
// [message: string, isString: bool]
|
||||
var formatMessage = function (message) {
|
||||
var type = typeof message;
|
||||
if (type === 'string') {
|
||||
return [message, true];
|
||||
}
|
||||
if (type !== 'object' || message === null) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
|
||||
return [new Uint8Array(message), false];
|
||||
}
|
||||
if (!isArray(message) && !isView(message)) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
return [message, false];
|
||||
}
|
||||
|
||||
var empty = function (message) {
|
||||
return formatMessage(message)[0].length === 0;
|
||||
};
|
||||
|
||||
var createOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message) {
|
||||
return new Keccak(bits, padding, bits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createShakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits) {
|
||||
return new Keccak(bits, padding, outputBits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createCshakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits, n, s) {
|
||||
return methods['cshake' + bits].update(message, outputBits, n, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createKmacOutputMethod = function (bits, padding, outputType) {
|
||||
return function (key, message, outputBits, s) {
|
||||
return methods['kmac' + bits].update(key, message, outputBits, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createOutputMethods = function (method, createMethod, bits, padding) {
|
||||
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
|
||||
var type = OUTPUT_TYPES[i];
|
||||
method[type] = createMethod(bits, padding, type);
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
var createMethod = function (bits, padding) {
|
||||
var method = createOutputMethod(bits, padding, 'hex');
|
||||
method.create = function () {
|
||||
return new Keccak(bits, padding, bits);
|
||||
};
|
||||
method.update = function (message) {
|
||||
return method.create().update(message);
|
||||
};
|
||||
return createOutputMethods(method, createOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createShakeMethod = function (bits, padding) {
|
||||
var method = createShakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits) {
|
||||
return new Keccak(bits, padding, outputBits);
|
||||
};
|
||||
method.update = function (message, outputBits) {
|
||||
return method.create(outputBits).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createShakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createCshakeMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createCshakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits, n, s) {
|
||||
if (empty(n) && empty(s)) {
|
||||
return methods['shake' + bits].create(outputBits);
|
||||
} else {
|
||||
return new Keccak(bits, padding, outputBits).bytepad([n, s], w);
|
||||
}
|
||||
};
|
||||
method.update = function (message, outputBits, n, s) {
|
||||
return method.create(outputBits, n, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createCshakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createKmacMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createKmacOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (key, outputBits, s) {
|
||||
return new Kmac(bits, padding, outputBits).bytepad(['KMAC', s], w).bytepad([key], w);
|
||||
};
|
||||
method.update = function (key, message, outputBits, s) {
|
||||
return method.create(key, outputBits, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createKmacOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var algorithms = [
|
||||
{ name: 'keccak', padding: KECCAK_PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'sha3', padding: PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'shake', padding: SHAKE_PADDING, bits: SHAKE_BITS, createMethod: createShakeMethod },
|
||||
{ name: 'cshake', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createCshakeMethod },
|
||||
{ name: 'kmac', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createKmacMethod }
|
||||
];
|
||||
|
||||
var methods = {}, methodNames = [];
|
||||
|
||||
for (var i = 0; i < algorithms.length; ++i) {
|
||||
var algorithm = algorithms[i];
|
||||
var bits = algorithm.bits;
|
||||
for (var j = 0; j < bits.length; ++j) {
|
||||
var methodName = algorithm.name + '_' + bits[j];
|
||||
methodNames.push(methodName);
|
||||
methods[methodName] = algorithm.createMethod(bits[j], algorithm.padding);
|
||||
if (algorithm.name !== 'sha3') {
|
||||
var newMethodName = algorithm.name + bits[j];
|
||||
methodNames.push(newMethodName);
|
||||
methods[newMethodName] = methods[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.push("extractLast20Bytes");
|
||||
methods["extractLast20Bytes"] = extractLast20Bytes;
|
||||
|
||||
|
||||
function Keccak(bits, padding, outputBits) {
|
||||
this.blocks = [];
|
||||
this.s = [];
|
||||
this.padding = padding;
|
||||
this.outputBits = outputBits;
|
||||
this.reset = true;
|
||||
this.finalized = false;
|
||||
this.block = 0;
|
||||
this.start = 0;
|
||||
this.blockCount = (1600 - (bits << 1)) >> 5;
|
||||
this.byteCount = this.blockCount << 2;
|
||||
this.outputBlocks = outputBits >> 5;
|
||||
this.extraBytes = (outputBits & 31) >> 3;
|
||||
|
||||
for (var i = 0; i < 50; ++i) {
|
||||
this.s[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Keccak.prototype.update = function (message) {
|
||||
if (this.finalized) {
|
||||
throw new Error(FINALIZE_ERROR);
|
||||
}
|
||||
var result = formatMessage(message);
|
||||
message = result[0];
|
||||
var isString = result[1];
|
||||
var blocks = this.blocks, byteCount = this.byteCount, length = message.length,
|
||||
blockCount = this.blockCount, index = 0, s = this.s, i, code;
|
||||
|
||||
while (index < length) {
|
||||
if (this.reset) {
|
||||
this.reset = false;
|
||||
blocks[0] = this.block;
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if (isString) {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
this.lastByteIndex = i;
|
||||
if (i >= byteCount) {
|
||||
this.start = i - byteCount;
|
||||
this.block = blocks[blockCount];
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
this.reset = true;
|
||||
} else {
|
||||
this.start = i;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.encode = function (x, right) {
|
||||
var o = x & 255, n = 1;
|
||||
var bytes = [o];
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
while (o > 0) {
|
||||
bytes.unshift(o);
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
++n;
|
||||
}
|
||||
if (right) {
|
||||
bytes.push(n);
|
||||
} else {
|
||||
bytes.unshift(n);
|
||||
}
|
||||
this.update(bytes);
|
||||
return bytes.length;
|
||||
};
|
||||
|
||||
Keccak.prototype.encodeString = function (str) {
|
||||
var result = formatMessage(str);
|
||||
str = result[0];
|
||||
var isString = result[1];
|
||||
var bytes = 0, length = str.length;
|
||||
if (isString) {
|
||||
for (var i = 0; i < str.length; ++i) {
|
||||
var code = str.charCodeAt(i);
|
||||
if (code < 0x80) {
|
||||
bytes += 1;
|
||||
} else if (code < 0x800) {
|
||||
bytes += 2;
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
bytes += 3;
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (str.charCodeAt(++i) & 0x3ff));
|
||||
bytes += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bytes = length;
|
||||
}
|
||||
bytes += this.encode(bytes * 8);
|
||||
this.update(str);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
Keccak.prototype.bytepad = function (strs, w) {
|
||||
var bytes = this.encode(w);
|
||||
for (var i = 0; i < strs.length; ++i) {
|
||||
bytes += this.encodeString(strs[i]);
|
||||
}
|
||||
var paddingBytes = (w - bytes % w) % w;
|
||||
var zeros = [];
|
||||
zeros.length = paddingBytes;
|
||||
this.update(zeros);
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.finalize = function () {
|
||||
if (this.finalized) {
|
||||
return;
|
||||
}
|
||||
this.finalized = true;
|
||||
var blocks = this.blocks, i = this.lastByteIndex, blockCount = this.blockCount, s = this.s;
|
||||
blocks[i >> 2] |= this.padding[i & 3];
|
||||
if (this.lastByteIndex === this.byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
};
|
||||
|
||||
Keccak.prototype.toString = Keccak.prototype.hex = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var hex = '', block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
||||
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
||||
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
||||
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F];
|
||||
if (extraBytes > 1) {
|
||||
hex += HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F];
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
hex += HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F];
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
Keccak.prototype.arrayBuffer = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var bytes = this.outputBits >> 3;
|
||||
var buffer;
|
||||
if (extraBytes) {
|
||||
buffer = new ArrayBuffer((outputBlocks + 1) << 2);
|
||||
} else {
|
||||
buffer = new ArrayBuffer(bytes);
|
||||
}
|
||||
var array = new Uint32Array(buffer);
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
array[j] = s[i];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
array[i] = s[i];
|
||||
buffer = buffer.slice(0, bytes);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Keccak.prototype.buffer = Keccak.prototype.arrayBuffer;
|
||||
|
||||
Keccak.prototype.digest = Keccak.prototype.array = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var array = [], offset, block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
array[offset + 3] = (block >> 24) & 0xFF;
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
if (extraBytes > 1) {
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
function Kmac(bits, padding, outputBits) {
|
||||
Keccak.call(this, bits, padding, outputBits);
|
||||
}
|
||||
|
||||
Kmac.prototype = new Keccak();
|
||||
|
||||
Kmac.prototype.finalize = function () {
|
||||
this.encode(this.outputBits, true);
|
||||
return Keccak.prototype.finalize.call(this);
|
||||
};
|
||||
|
||||
var f = function (s) {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9,
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17,
|
||||
b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33,
|
||||
b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
for (n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ ((c2 << 1) | (c3 >>> 31));
|
||||
l = c9 ^ ((c3 << 1) | (c2 >>> 31));
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ ((c4 << 1) | (c5 >>> 31));
|
||||
l = c1 ^ ((c5 << 1) | (c4 >>> 31));
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ ((c6 << 1) | (c7 >>> 31));
|
||||
l = c3 ^ ((c7 << 1) | (c6 >>> 31));
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ ((c8 << 1) | (c9 >>> 31));
|
||||
l = c5 ^ ((c9 << 1) | (c8 >>> 31));
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ ((c0 << 1) | (c1 >>> 31));
|
||||
l = c7 ^ ((c1 << 1) | (c0 >>> 31));
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = (s[11] << 4) | (s[10] >>> 28);
|
||||
b33 = (s[10] << 4) | (s[11] >>> 28);
|
||||
b14 = (s[20] << 3) | (s[21] >>> 29);
|
||||
b15 = (s[21] << 3) | (s[20] >>> 29);
|
||||
b46 = (s[31] << 9) | (s[30] >>> 23);
|
||||
b47 = (s[30] << 9) | (s[31] >>> 23);
|
||||
b28 = (s[40] << 18) | (s[41] >>> 14);
|
||||
b29 = (s[41] << 18) | (s[40] >>> 14);
|
||||
b20 = (s[2] << 1) | (s[3] >>> 31);
|
||||
b21 = (s[3] << 1) | (s[2] >>> 31);
|
||||
b2 = (s[13] << 12) | (s[12] >>> 20);
|
||||
b3 = (s[12] << 12) | (s[13] >>> 20);
|
||||
b34 = (s[22] << 10) | (s[23] >>> 22);
|
||||
b35 = (s[23] << 10) | (s[22] >>> 22);
|
||||
b16 = (s[33] << 13) | (s[32] >>> 19);
|
||||
b17 = (s[32] << 13) | (s[33] >>> 19);
|
||||
b48 = (s[42] << 2) | (s[43] >>> 30);
|
||||
b49 = (s[43] << 2) | (s[42] >>> 30);
|
||||
b40 = (s[5] << 30) | (s[4] >>> 2);
|
||||
b41 = (s[4] << 30) | (s[5] >>> 2);
|
||||
b22 = (s[14] << 6) | (s[15] >>> 26);
|
||||
b23 = (s[15] << 6) | (s[14] >>> 26);
|
||||
b4 = (s[25] << 11) | (s[24] >>> 21);
|
||||
b5 = (s[24] << 11) | (s[25] >>> 21);
|
||||
b36 = (s[34] << 15) | (s[35] >>> 17);
|
||||
b37 = (s[35] << 15) | (s[34] >>> 17);
|
||||
b18 = (s[45] << 29) | (s[44] >>> 3);
|
||||
b19 = (s[44] << 29) | (s[45] >>> 3);
|
||||
b10 = (s[6] << 28) | (s[7] >>> 4);
|
||||
b11 = (s[7] << 28) | (s[6] >>> 4);
|
||||
b42 = (s[17] << 23) | (s[16] >>> 9);
|
||||
b43 = (s[16] << 23) | (s[17] >>> 9);
|
||||
b24 = (s[26] << 25) | (s[27] >>> 7);
|
||||
b25 = (s[27] << 25) | (s[26] >>> 7);
|
||||
b6 = (s[36] << 21) | (s[37] >>> 11);
|
||||
b7 = (s[37] << 21) | (s[36] >>> 11);
|
||||
b38 = (s[47] << 24) | (s[46] >>> 8);
|
||||
b39 = (s[46] << 24) | (s[47] >>> 8);
|
||||
b30 = (s[8] << 27) | (s[9] >>> 5);
|
||||
b31 = (s[9] << 27) | (s[8] >>> 5);
|
||||
b12 = (s[18] << 20) | (s[19] >>> 12);
|
||||
b13 = (s[19] << 20) | (s[18] >>> 12);
|
||||
b44 = (s[29] << 7) | (s[28] >>> 25);
|
||||
b45 = (s[28] << 7) | (s[29] >>> 25);
|
||||
b26 = (s[38] << 8) | (s[39] >>> 24);
|
||||
b27 = (s[39] << 8) | (s[38] >>> 24);
|
||||
b8 = (s[48] << 14) | (s[49] >>> 18);
|
||||
b9 = (s[49] << 14) | (s[48] >>> 18);
|
||||
|
||||
s[0] = b0 ^ (~b2 & b4);
|
||||
s[1] = b1 ^ (~b3 & b5);
|
||||
s[10] = b10 ^ (~b12 & b14);
|
||||
s[11] = b11 ^ (~b13 & b15);
|
||||
s[20] = b20 ^ (~b22 & b24);
|
||||
s[21] = b21 ^ (~b23 & b25);
|
||||
s[30] = b30 ^ (~b32 & b34);
|
||||
s[31] = b31 ^ (~b33 & b35);
|
||||
s[40] = b40 ^ (~b42 & b44);
|
||||
s[41] = b41 ^ (~b43 & b45);
|
||||
s[2] = b2 ^ (~b4 & b6);
|
||||
s[3] = b3 ^ (~b5 & b7);
|
||||
s[12] = b12 ^ (~b14 & b16);
|
||||
s[13] = b13 ^ (~b15 & b17);
|
||||
s[22] = b22 ^ (~b24 & b26);
|
||||
s[23] = b23 ^ (~b25 & b27);
|
||||
s[32] = b32 ^ (~b34 & b36);
|
||||
s[33] = b33 ^ (~b35 & b37);
|
||||
s[42] = b42 ^ (~b44 & b46);
|
||||
s[43] = b43 ^ (~b45 & b47);
|
||||
s[4] = b4 ^ (~b6 & b8);
|
||||
s[5] = b5 ^ (~b7 & b9);
|
||||
s[14] = b14 ^ (~b16 & b18);
|
||||
s[15] = b15 ^ (~b17 & b19);
|
||||
s[24] = b24 ^ (~b26 & b28);
|
||||
s[25] = b25 ^ (~b27 & b29);
|
||||
s[34] = b34 ^ (~b36 & b38);
|
||||
s[35] = b35 ^ (~b37 & b39);
|
||||
s[44] = b44 ^ (~b46 & b48);
|
||||
s[45] = b45 ^ (~b47 & b49);
|
||||
s[6] = b6 ^ (~b8 & b0);
|
||||
s[7] = b7 ^ (~b9 & b1);
|
||||
s[16] = b16 ^ (~b18 & b10);
|
||||
s[17] = b17 ^ (~b19 & b11);
|
||||
s[26] = b26 ^ (~b28 & b20);
|
||||
s[27] = b27 ^ (~b29 & b21);
|
||||
s[36] = b36 ^ (~b38 & b30);
|
||||
s[37] = b37 ^ (~b39 & b31);
|
||||
s[46] = b46 ^ (~b48 & b40);
|
||||
s[47] = b47 ^ (~b49 & b41);
|
||||
s[8] = b8 ^ (~b0 & b2);
|
||||
s[9] = b9 ^ (~b1 & b3);
|
||||
s[18] = b18 ^ (~b10 & b12);
|
||||
s[19] = b19 ^ (~b11 & b13);
|
||||
s[28] = b28 ^ (~b20 & b22);
|
||||
s[29] = b29 ^ (~b21 & b23);
|
||||
s[38] = b38 ^ (~b30 & b32);
|
||||
s[39] = b39 ^ (~b31 & b33);
|
||||
s[48] = b48 ^ (~b40 & b42);
|
||||
s[49] = b49 ^ (~b41 & b43);
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
};
|
||||
|
||||
function extractLast20Bytes(hexString, addPrefix) {
|
||||
// Ensure the input hexString has '0x' prefix
|
||||
if (!hexString.startsWith('0x')) {
|
||||
hexString = '0x' + hexString;
|
||||
}
|
||||
|
||||
// Remove '0x' prefix and parse the hex string to a BigInt
|
||||
var bigIntValue = BigInt(hexString);
|
||||
|
||||
// Extract the last 20 bytes (160 bits) from the BigInt
|
||||
var last20Bytes = bigIntValue & BigInt('0x' + 'f'.repeat(40)); // 0xf is 4 bits in hexadecimal, repeated 40 times for 160 bits
|
||||
|
||||
// Convert the result back to a hexadecimal string
|
||||
var result = last20Bytes.toString(16).padStart(40, '0'); // 40 characters for 160 bits
|
||||
|
||||
// Add '0x' prefix if addPrefix is truthy
|
||||
if (addPrefix) {
|
||||
result = '0x' + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof root.keccak === 'object') {
|
||||
Object.assign(root.keccak, methods);
|
||||
}
|
||||
|
||||
if (COMMON_JS) {
|
||||
module.exports = methods;
|
||||
} else {
|
||||
for (i = 0; i < methodNames.length; ++i) {
|
||||
root[methodNames[i]] = methods[methodNames[i]];
|
||||
}
|
||||
if (AMD) {
|
||||
define(function () {
|
||||
return methods;
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
8428
arbitrumwallet/scripts/tap_combined.js
Normal file
8428
arbitrumwallet/scripts/tap_combined.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -469,14 +469,14 @@
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
// Desktop: offset from left navbar
|
||||
Object.assign(panel.style, {
|
||||
left: 'calc(10rem + 1rem)',
|
||||
bottom: '1rem',
|
||||
top: 'auto',
|
||||
left: 'auto',
|
||||
bottom: 'auto',
|
||||
top: '1rem',
|
||||
right: '1rem', // optional, lets long toasts wrap before content edge
|
||||
zIndex: '1000'
|
||||
});
|
||||
} else {
|
||||
// Mobile: keep top-right or top-left as you prefer
|
||||
// Mobile: keep top-right or top-center as you prefer
|
||||
Object.assign(panel.style, {
|
||||
left: '0.5rem',
|
||||
top: '0.5rem',
|
||||
@ -497,6 +497,7 @@
|
||||
cro: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <g clip-path="url(#clip0_201_2)"> <path d="M12 0L19.6368 12.4368L12.1633 16.8L4.36325 12.4368L12 0Z"/> <path d="M12 24L4.36325 13.6099L11.8367 18L19.6368 13.6099L12 24Z"/> </g> <defs> <clipPath id="clip0_201_2"> <rect width="24" height="24" fill="white"/> </clipPath> </defs> </svg>`,
|
||||
usdc: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" data-name="86977684-12db-4850-8f30-233a7c267d11" viewBox="0 0 2000 2000"> <path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/> <path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/> <path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`,
|
||||
usdt: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 339.43 295.27"><title>tether-usdt-logo</title><path d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z" style="fill:#50af95;fill-rule:evenodd"/><path d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z" style="fill:#fff;fill-rule:evenodd"/><script xmlns=""/></svg>`,
|
||||
wcro: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <g clip-path="url(#clip0_201_2)"> <path d="M12 0L19.6368 12.4368L12.1633 16.8L4.36325 12.4368L12 0Z"/> <path d="M12 24L4.36325 13.6099L11.8367 18L19.6368 13.6099L12 24Z"/> </g> <defs> <clipPath id="clip0_201_2"> <rect width="24" height="24" fill="white"/> </clipPath> </defs> </svg>`,
|
||||
}
|
||||
window.smCompConfig = {
|
||||
'sm-input': [
|
||||
@ -664,7 +665,7 @@
|
||||
</h2>
|
||||
<sm-form oninvalid="handleInvalidSearch()">
|
||||
<div id="input_wrapper">
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="Address, private key, or tx hash"
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="CRO address, private key, or tx hash"
|
||||
type="password" animate>
|
||||
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
|
||||
<label slot="right" class="interact">
|
||||
@ -735,12 +736,13 @@
|
||||
}
|
||||
const renderedContacts = []
|
||||
for (const floAddress in contacts) {
|
||||
const { ethAddress } = contacts[floAddress]
|
||||
const { ethAddress, btcAddress } = contacts[floAddress]
|
||||
renderedContacts.push(html`
|
||||
<li class="contact" .dataset=${{ floAddress, ethAddress }}>
|
||||
${floAddress === ethAddress ? html`
|
||||
`: html`
|
||||
<sm-chips onchange=${e => e.target.closest('.contact').querySelector('sm-copy').value = e.target.value}>
|
||||
${btcAddress ? html`<sm-chip value=${btcAddress}>BTC</sm-chip>` : ''}
|
||||
<sm-chip value=${floAddress} selected>FLO</sm-chip>
|
||||
<sm-chip value=${ethAddress}>CRO</sm-chip>
|
||||
</sm-chips>
|
||||
@ -766,7 +768,7 @@
|
||||
let allTransactions = [];
|
||||
const TRANSACTIONS_PER_PAGE = 10;
|
||||
|
||||
function checkBalance(ethAddress, floAddress) {
|
||||
function checkBalance(ethAddress, floAddress, btcAddress) {
|
||||
if (ethAddress) {
|
||||
// If address is provided sync the input box
|
||||
const searchInput = document.querySelector('#check_balance_input');
|
||||
@ -782,7 +784,19 @@
|
||||
|
||||
// Check if input is empty
|
||||
if (!keyToConvert) {
|
||||
notify('Please enter a Cronos address, private key, or transaction hash', 'error');
|
||||
notify('Please enter a CRO address, private key, or transaction hash', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject FLO addresses (start with F and len 34)
|
||||
if (/^[F][a-km-zA-HJ-NP-Z1-9]{26,34}$/.test(keyToConvert)) {
|
||||
notify('FLO addresses are not supported. Please use a CRO address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject BTC addresses (legacy 1/3, segwit bc1)
|
||||
if (/^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(keyToConvert) || /^bc1[a-z0-9]{39,59}$/i.test(keyToConvert)) {
|
||||
notify('BTC addresses are not supported. Please use a CRO address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -804,8 +818,9 @@
|
||||
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
||||
ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
floAddress = floCrypto.getFloID(keyToConvert)
|
||||
btcAddress = btcOperator.bech32Address(keyToConvert)
|
||||
} catch (error) {
|
||||
notify('Invalid input. Please enter a valid Ethereum address or private key.', 'error');
|
||||
notify('Invalid input. Please enter a valid CRO address, private key, or tx hash.', 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -817,10 +832,10 @@
|
||||
currentAddress = ethAddress;
|
||||
currentFloAddress = floAddress;
|
||||
|
||||
loadTransactionsPage(ethAddress, floAddress, currentPage);
|
||||
loadTransactionsPage(ethAddress, floAddress, currentPage, btcAddress);
|
||||
}
|
||||
|
||||
async function loadTransactionsPage(ethAddress, floAddress, page) {
|
||||
async function loadTransactionsPage(ethAddress, floAddress, page, btcAddress) {
|
||||
buttonLoader('check_balance_button', true);
|
||||
|
||||
try {
|
||||
@ -867,6 +882,7 @@
|
||||
if (result) return
|
||||
compactIDB.addData('contacts', {
|
||||
ethAddress,
|
||||
btcAddress
|
||||
}, floAddress || ethAddress).then(() => {
|
||||
renderSearchedAddressList()
|
||||
}).catch((error) => {
|
||||
@ -1768,7 +1784,7 @@
|
||||
// BTC bech32 from WIF
|
||||
const btcBech32 = btcOperator.bech32Address(wif);
|
||||
//const btcPriv = btcOperator.convert.wif(wif);
|
||||
//const floPriv = btcOperator.convert.wif(wif, 0xa3);
|
||||
//const floPriv = btcOperator.convert.wif(wif, 0xa3);
|
||||
|
||||
// FLO from WIF
|
||||
const floAddress = floCrypto.getFloID(wif);
|
||||
@ -1778,14 +1794,12 @@
|
||||
renderElem(outEl, html`
|
||||
<div class="card grid gap-1">
|
||||
|
||||
|
||||
<div class="grid gap-0-5">
|
||||
<div><strong>BTC Address</strong>: <sm-copy value="${btcBech32}"></sm-copy></div>
|
||||
<div><strong>FLO Address</strong>: <sm-copy value="${floAddress}"></sm-copy></div>
|
||||
<div><strong>CRO Address</strong>: <sm-copy value="${ethAddress}"></sm-copy></div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
`);
|
||||
|
||||
@ -1846,24 +1860,31 @@
|
||||
const { floID, privKey } = floCrypto.generateNewID();
|
||||
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
const btcAddress = btcOperator.bech32Address(privKey);
|
||||
const btcPrivKey = btcOperator.convert.wif(privKey, 0x80); // 0x80 for BTC Mainnet
|
||||
|
||||
renderElem(getRef('created_address_wrapper'), html`
|
||||
<ul id="generated_addresses" class="grid gap-1-5">
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<div class="card grid gap-0-5">
|
||||
<h5>FLO Address</h5>
|
||||
<sm-copy value="${floID}"></sm-copy>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Private Key</h5>
|
||||
<sm-copy value="${privKey}"></sm-copy>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>Equivalent Cronos Address</h5>
|
||||
<sm-copy value="${ethAddress}"></sm-copy>
|
||||
<li class="grid gap-0-5">
|
||||
<div class="card grid gap-0-5">
|
||||
<h5>Bitcoin Address</h5>
|
||||
<sm-copy value="${btcAddress}"></sm-copy>
|
||||
<h5>Private Key</h5>
|
||||
<sm-copy value="${btcPrivKey}"></sm-copy>
|
||||
</div>
|
||||
<div>
|
||||
</li>
|
||||
<li class="grid gap-0-5">
|
||||
<div class="card grid gap-0-5">
|
||||
<h5>Cronos Address</h5>
|
||||
<sm-copy value="${ethAddress}"></sm-copy>
|
||||
<h5>Private Key</h5>
|
||||
<sm-copy value="${ethPrivateKey}"></sm-copy>
|
||||
</div>
|
||||
|
||||
@ -467,12 +467,12 @@
|
||||
|
||||
const apply = () => {
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
// Desktop: offset from left navbar
|
||||
// Desktop: top-right to avoid overlapping "Searched addresses" (which is left-side)
|
||||
Object.assign(panel.style, {
|
||||
left: 'calc(10rem + 1rem)',
|
||||
bottom: '1rem',
|
||||
top: 'auto',
|
||||
right: '1rem', // optional, lets long toasts wrap before content edge
|
||||
left: 'auto',
|
||||
bottom: 'auto',
|
||||
top: '1rem',
|
||||
right: '1rem',
|
||||
zIndex: '1000'
|
||||
});
|
||||
} else {
|
||||
@ -494,6 +494,8 @@
|
||||
</script>
|
||||
<script>
|
||||
const assetIcons = {
|
||||
|
||||
wetc: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <g clip-path="url(#clip0_201_2)"> <path d="M12 0L19.6368 12.4368L12.1633 16.8L4.36325 12.4368L12 0Z"/> <path d="M12 24L4.36325 13.6099L11.8367 18L19.6368 13.6099L12 24Z"/> </g> <defs> <clipPath id="clip0_201_2"> <rect width="24" height="24" fill="white"/> </clipPath> </defs> </svg>`,
|
||||
ETC: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <g clip-path="url(#clip0_201_2)"> <path d="M12 0L19.6368 12.4368L12.1633 16.8L4.36325 12.4368L12 0Z"/> <path d="M12 24L4.36325 13.6099L11.8367 18L19.6368 13.6099L12 24Z"/> </g> <defs> <clipPath id="clip0_201_2"> <rect width="24" height="24" fill="white"/> </clipPath> </defs> </svg>`,
|
||||
usdc: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" data-name="86977684-12db-4850-8f30-233a7c267d11" viewBox="0 0 2000 2000"> <path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/> <path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/> <path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`,
|
||||
usdt: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 339.43 295.27"><title>tether-usdt-logo</title><path d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z" style="fill:#50af95;fill-rule:evenodd"/><path d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z" style="fill:#fff;fill-rule:evenodd"/><script xmlns=""/></svg>`,
|
||||
@ -664,7 +666,7 @@
|
||||
</h2>
|
||||
<sm-form oninvalid="handleInvalidSearch()">
|
||||
<div id="input_wrapper">
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="Address, private key, or tx hash"
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="ETC Address, private key, or tx hash"
|
||||
type="password" animate>
|
||||
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
|
||||
<label slot="right" class="interact">
|
||||
@ -735,13 +737,14 @@
|
||||
}
|
||||
const renderedContacts = []
|
||||
for (const floAddress in contacts) {
|
||||
const { ethAddress } = contacts[floAddress]
|
||||
const { ethAddress, btcAddress } = contacts[floAddress]
|
||||
renderedContacts.push(html`
|
||||
<li class="contact" .dataset=${{ floAddress, ethAddress }}>
|
||||
<li class="contact" .dataset=${{ floAddress, ethAddress, btcAddress }}>
|
||||
${floAddress === ethAddress ? html`
|
||||
`: html`
|
||||
<sm-chips onchange=${e => e.target.closest('.contact').querySelector('sm-copy').value = e.target.value}>
|
||||
<sm-chip value=${floAddress} selected>FLO</sm-chip>
|
||||
${btcAddress ? html`<sm-chip value=${btcAddress}>BTC</sm-chip>` : ''}
|
||||
<sm-chip value=${ethAddress}>ETC</sm-chip>
|
||||
</sm-chips>
|
||||
`}
|
||||
@ -778,6 +781,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject FLO addresses (start with F)
|
||||
if (/^F[a-km-zA-HJ-NP-Z1-9]{26,34}$/.test(keyToConvert)) {
|
||||
notify('FLO addresses are not supported. Please use an ETC address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject BTC addresses (legacy: 1/3, segwit: bc1)
|
||||
if (/^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(keyToConvert) || /^bc1[a-z0-9]{39,59}$/i.test(keyToConvert)) {
|
||||
notify('BTC addresses are not supported. Please use an ETC address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
// Check if it's a valid Ethereum address
|
||||
if (ethOperator.isValidAddress(keyToConvert)) {
|
||||
ethAddress = keyToConvert
|
||||
@ -790,12 +804,36 @@
|
||||
// Otherwise, try to convert as private key
|
||||
else {
|
||||
try {
|
||||
let isHex = false;
|
||||
let btcAddress;
|
||||
|
||||
if (/^[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
||||
keyToConvert = coinjs.privkey2wif(keyToConvert)
|
||||
isHex = true;
|
||||
}
|
||||
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
||||
ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
floAddress = floCrypto.getFloID(keyToConvert)
|
||||
|
||||
// Only derive FLO and BTC addresses if input was NOT a raw hex key
|
||||
if (!isHex) {
|
||||
floAddress = floCrypto.getFloID(keyToConvert)
|
||||
try {
|
||||
btcAddress = btcOperator.bech32Address(keyToConvert)
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
// Save to indexed DB
|
||||
compactIDB.readData('contacts', floAddress || ethAddress).then(result => {
|
||||
if (result) return
|
||||
compactIDB.addData('contacts', {
|
||||
ethAddress,
|
||||
btcAddress
|
||||
}, floAddress || ethAddress).then(() => {
|
||||
renderSearchedAddressList()
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
notify('Invalid input. Please enter a valid Ethereum address or private key.', 'error');
|
||||
return;
|
||||
@ -1936,8 +1974,24 @@
|
||||
const { floID, privKey } = floCrypto.generateNewID();
|
||||
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
|
||||
// Bitcoin support
|
||||
const btcBech32 = btcOperator.bech32Address(privKey);
|
||||
// Convert to Bitcoin WIF format
|
||||
const btcPrivKey = btcOperator.convert.wif(privKey);
|
||||
|
||||
renderElem(getRef('created_address_wrapper'), html`
|
||||
<ul id="generated_addresses" class="grid gap-1-5">
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>Bitcoin Address</h5>
|
||||
<sm-copy value="${btcBech32}"></sm-copy>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Private Key</h5>
|
||||
<sm-copy value="${btcPrivKey}"></sm-copy>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>FLO Address</h5>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap"
|
||||
rel="stylesheet">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="hidden">
|
||||
@ -92,66 +92,65 @@
|
||||
</button>
|
||||
<theme-toggle></theme-toggle>
|
||||
</header>
|
||||
<nav id="main_navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="nav-item nav-item--active interactive" href="#/balance">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z" />
|
||||
<circle cx="16" cy="12" r="1.5" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Balance
|
||||
</span>
|
||||
<div class="nav-item__indicator"></div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/send">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||
<path
|
||||
d="M4.01 6.03l7.51 3.22-7.52-1 .01-2.22m7.5 8.72L4 17.97v-2.22l7.51-1M2.01 3L2 10l15 2-15 2 .01 7L23 12 2.01 3z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Send
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/create">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||
<path
|
||||
d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Create
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/retrieve">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Retrieve
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav id="main_navbar">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="nav-item nav-item--active interactive" href="#/balance">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path
|
||||
d="M21 7.28V5c0-1.1-.9-2-2-2H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-2.28c.59-.35 1-.98 1-1.72V9c0-.74-.41-1.37-1-1.72zM20 9v6h-7V9h7zM5 19V5h14v2h-6c-1.1 0-2 .9-2 2v6c0 1.1.9 2 2 2h6v2H5z" />
|
||||
<circle cx="16" cy="12" r="1.5" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Balance
|
||||
</span>
|
||||
<div class="nav-item__indicator"></div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/send">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||
<path
|
||||
d="M4.01 6.03l7.51 3.22-7.52-1 .01-2.22m7.5 8.72L4 17.97v-2.22l7.51-1M2.01 3L2 10l15 2-15 2 .01 7L23 12 2.01 3z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Send
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/create">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"></path>
|
||||
<path
|
||||
d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z">
|
||||
</path>
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Create
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item interactive" href="#/retrieve">
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24"
|
||||
width="24px" fill="#000000">
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" />
|
||||
</svg>
|
||||
<span class="nav-item__title">
|
||||
Retrieve
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div id="page_container"></div>
|
||||
</main>
|
||||
@ -178,7 +177,7 @@
|
||||
expirationDays: 60,
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<!-- ethers.js version 5.6 -->
|
||||
|
||||
<script src="https://unpkg.com/uhtml@3.0.1/es.js"></script>
|
||||
@ -259,7 +258,7 @@
|
||||
if (mode === 'error') {
|
||||
console.error(message)
|
||||
}
|
||||
|
||||
|
||||
// Ensure notification drawer element exists and is fully initialized
|
||||
const notificationDrawer = getRef("notification_drawer");
|
||||
if (!notificationDrawer || typeof notificationDrawer.push !== 'function') {
|
||||
@ -271,7 +270,7 @@
|
||||
window._pendingNotifications.push({ message, mode, options, icon });
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return notificationDrawer.push(message, { icon, ...options });
|
||||
}
|
||||
// displays a popup for asking permission. Use this instead of JS confirm
|
||||
@ -440,8 +439,8 @@
|
||||
else
|
||||
page = path
|
||||
this.state = { page, wildcards, lastPage: this.lastPage, params }
|
||||
|
||||
|
||||
|
||||
|
||||
if (queryString) {
|
||||
params = new URLSearchParams(queryString)
|
||||
this.state.params = Object.fromEntries(params)
|
||||
@ -460,37 +459,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Moving notification draw so that it does not overlap the menu item
|
||||
(function placeToasts() {
|
||||
const drawer = document.getElementById('notification_drawer');
|
||||
const panel = drawer?.shadowRoot?.querySelector('.notification-panel');
|
||||
if (!panel) return;
|
||||
//Moving notification draw so that it does not overlap the menu item
|
||||
(function placeToasts() {
|
||||
const drawer = document.getElementById('notification_drawer');
|
||||
const panel = drawer?.shadowRoot?.querySelector('.notification-panel');
|
||||
if (!panel) return;
|
||||
|
||||
const apply = () => {
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
// Desktop: offset from left navbar
|
||||
Object.assign(panel.style, {
|
||||
left: 'calc(10rem + 1rem)',
|
||||
bottom: '1rem',
|
||||
top: 'auto',
|
||||
right: '1rem', // optional, lets long toasts wrap before content edge
|
||||
zIndex: '1000'
|
||||
});
|
||||
} else {
|
||||
// Mobile: keep top-right or top-left as you prefer
|
||||
Object.assign(panel.style, {
|
||||
left: '0.5rem',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
bottom: 'auto',
|
||||
zIndex: '1000'
|
||||
});
|
||||
}
|
||||
};
|
||||
const apply = () => {
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
// Desktop: top-right corner
|
||||
Object.assign(panel.style, {
|
||||
left: 'auto',
|
||||
top: '1rem',
|
||||
right: '1rem',
|
||||
bottom: 'auto',
|
||||
zIndex: '1000'
|
||||
});
|
||||
} else {
|
||||
// Mobile: top-center
|
||||
Object.assign(panel.style, {
|
||||
left: '0.5rem',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
bottom: 'auto',
|
||||
zIndex: '1000'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
apply();
|
||||
window.addEventListener('resize', apply);
|
||||
})();
|
||||
apply();
|
||||
window.addEventListener('resize', apply);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script>
|
||||
@ -562,87 +561,87 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Initialize IndexedDB for storing contact addresses
|
||||
let idbReady;
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
// Initialize the database before any routing or data reads
|
||||
idbReady = compactIDB.initDB('floEthereum', { contacts: {} })
|
||||
.then((res) => { console.log(res); })
|
||||
.catch((err) => { console.error(err); });
|
||||
// Initialize the database before any routing or data reads
|
||||
idbReady = compactIDB.initDB('floEthereum', { contacts: {} })
|
||||
.then((res) => { console.log(res); })
|
||||
.catch((err) => { console.error(err); });
|
||||
|
||||
// Set up event listeners and routing after database is ready
|
||||
idbReady.then(() => {
|
||||
const routeNow = () => router.routeTo(location.hash);
|
||||
// Set up event listeners and routing after database is ready
|
||||
idbReady.then(() => {
|
||||
const routeNow = () => router.routeTo(location.hash);
|
||||
|
||||
// Set up UI event listeners for copy notifications and ripple effects
|
||||
document.addEventListener('copy', () => notify('copied', 'success'));
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
const target = e.target.closest('button:not(:disabled), .interactive:not(:disabled)');
|
||||
if (target) createRipple(e, target);
|
||||
});
|
||||
|
||||
// Handle Ethereum provider and MetaMask connection
|
||||
if (window.ethereum) {
|
||||
window.ethereum.on('chainChanged', (chainId) => {
|
||||
window.currentChainId = chainId;
|
||||
if (chainId !== '0x1') {
|
||||
renderError('Please switch MetaMask to Ethereum Mainnet');
|
||||
} else {
|
||||
routeNow();
|
||||
}
|
||||
});
|
||||
|
||||
window.ethereum.request({ method: 'eth_chainId' })
|
||||
.then((chainId) => {
|
||||
window.currentChainId = chainId;
|
||||
if (chainId !== '0x1') {
|
||||
renderError('Please switch MetaMask to Ethereum Mainnet');
|
||||
} else {
|
||||
routeNow();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// If reading chain id fails, still render the app
|
||||
routeNow();
|
||||
// Set up UI event listeners for copy notifications and ripple effects
|
||||
document.addEventListener('copy', () => notify('copied', 'success'));
|
||||
document.addEventListener('pointerdown', (e) => {
|
||||
const target = e.target.closest('button:not(:disabled), .interactive:not(:disabled)');
|
||||
if (target) createRipple(e, target);
|
||||
});
|
||||
|
||||
// Listen for MetaMask account changes
|
||||
ethereum.on('accountsChanged', (accounts) => {
|
||||
getRef('eth_balance_wrapper').classList.add('hidden');
|
||||
setMetaMaskStatus(accounts.length > 0);
|
||||
});
|
||||
ethereum.on('connect', (accounts) => {
|
||||
setMetaMaskStatus(accounts.length > 0);
|
||||
});
|
||||
ethereum.on('disconnect', (accounts) => {
|
||||
setMetaMaskStatus(false);
|
||||
});
|
||||
} else {
|
||||
// No MetaMask detected, proceed with normal routing
|
||||
routeNow();
|
||||
}
|
||||
// Handle Ethereum provider and MetaMask connection
|
||||
if (window.ethereum) {
|
||||
window.ethereum.on('chainChanged', (chainId) => {
|
||||
window.currentChainId = chainId;
|
||||
if (chainId !== '0x1') {
|
||||
renderError('Please switch MetaMask to Ethereum Mainnet');
|
||||
} else {
|
||||
routeNow();
|
||||
}
|
||||
});
|
||||
|
||||
// 3) Reveal UI only after we’re safe to render
|
||||
document.body.classList.remove('hidden');
|
||||
});
|
||||
window.ethereum.request({ method: 'eth_chainId' })
|
||||
.then((chainId) => {
|
||||
window.currentChainId = chainId;
|
||||
if (chainId !== '0x1') {
|
||||
renderError('Please switch MetaMask to Ethereum Mainnet');
|
||||
} else {
|
||||
routeNow();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// If reading chain id fails, still render the app
|
||||
routeNow();
|
||||
});
|
||||
|
||||
// Listen for MetaMask account changes
|
||||
ethereum.on('accountsChanged', (accounts) => {
|
||||
getRef('eth_balance_wrapper').classList.add('hidden');
|
||||
setMetaMaskStatus(accounts.length > 0);
|
||||
});
|
||||
ethereum.on('connect', (accounts) => {
|
||||
setMetaMaskStatus(accounts.length > 0);
|
||||
});
|
||||
ethereum.on('disconnect', (accounts) => {
|
||||
setMetaMaskStatus(false);
|
||||
});
|
||||
} else {
|
||||
// No MetaMask detected, proceed with normal routing
|
||||
routeNow();
|
||||
}
|
||||
|
||||
// 3) Reveal UI only after we’re safe to render
|
||||
document.body.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Process pending notifications after a delay to ensure custom elements are ready
|
||||
setTimeout(() => {
|
||||
if (window._pendingNotifications && window._pendingNotifications.length > 0) {
|
||||
const notificationDrawer = getRef("notification_drawer");
|
||||
if (notificationDrawer && typeof notificationDrawer.push === 'function') {
|
||||
window._pendingNotifications.forEach(({ message, icon, options }) => {
|
||||
notificationDrawer.push(message, { icon, ...options });
|
||||
});
|
||||
window._pendingNotifications = [];
|
||||
if (window._pendingNotifications && window._pendingNotifications.length > 0) {
|
||||
const notificationDrawer = getRef("notification_drawer");
|
||||
if (notificationDrawer && typeof notificationDrawer.push === 'function') {
|
||||
window._pendingNotifications.forEach(({ message, icon, options }) => {
|
||||
notificationDrawer.push(message, { icon, ...options });
|
||||
});
|
||||
window._pendingNotifications = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000); // Give custom elements time to fully initialize
|
||||
|
||||
|
||||
|
||||
router.addRoute('404', () => {
|
||||
renderElem(getRef('page_container'), html`
|
||||
<h1>Page not found</h1>
|
||||
@ -665,7 +664,7 @@
|
||||
</h2>
|
||||
<sm-form oninvalid="handleInvalidSearch()">
|
||||
<div id="input_wrapper">
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="Address, private key, or tx hash"
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="ETH address, private key, or tx hash"
|
||||
type="password" animate>
|
||||
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
|
||||
<label slot="right" class="interact">
|
||||
@ -689,7 +688,7 @@
|
||||
renderError('Please switch MetaMask to Ethereum Mainnet')
|
||||
}
|
||||
renderSearchedAddressList()
|
||||
|
||||
|
||||
// Handle URL parameters after page is rendered
|
||||
// Use setTimeout to ensure DOM is fully ready
|
||||
setTimeout(() => {
|
||||
@ -770,17 +769,29 @@
|
||||
function checkBalance(ethAddress, floAddress) {
|
||||
if (!ethAddress) {
|
||||
let keyToConvert = document.querySelector('#check_balance_input').value.trim()
|
||||
|
||||
|
||||
// Check if input is empty
|
||||
if (!keyToConvert) {
|
||||
notify('Please enter an Ethereum address, private key, or transaction hash', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Reject FLO addresses (start with F)
|
||||
if (/^F[a-km-zA-HJ-NP-Z1-9]{26,34}$/.test(keyToConvert)) {
|
||||
notify('FLO addresses are not supported. Please use an ETH address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject BTC addresses (legacy: 1/3, segwit: bc1)
|
||||
if (/^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(keyToConvert) || /^bc1[a-z0-9]{39,59}$/i.test(keyToConvert)) {
|
||||
notify('BTC addresses are not supported. Please use an ETH address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's a valid Ethereum address
|
||||
if (ethOperator.isValidAddress(keyToConvert)) {
|
||||
ethAddress = keyToConvert
|
||||
}
|
||||
}
|
||||
// Check if it's a transaction hash (0x followed by 64 hex characters)
|
||||
else if (/^0x[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
||||
viewTransactionDetails(keyToConvert);
|
||||
@ -802,24 +813,24 @@
|
||||
}
|
||||
}
|
||||
if (!ethAddress) return
|
||||
|
||||
|
||||
// Reset pagination when checking new address
|
||||
currentPage = 1;
|
||||
currentAddress = ethAddress;
|
||||
currentFloAddress = floAddress;
|
||||
|
||||
|
||||
loadTransactionsPage(ethAddress, floAddress, currentPage);
|
||||
}
|
||||
|
||||
async function loadTransactionsPage(ethAddress, floAddress, page) {
|
||||
buttonLoader('check_balance_button', true);
|
||||
|
||||
|
||||
try {
|
||||
const results = await Promise.allSettled([
|
||||
ethOperator.getBalance(ethAddress),
|
||||
ethOperator.getTokenBalance(ethAddress, 'usdc'),
|
||||
ethOperator.getTokenBalance(ethAddress, 'usdt'),
|
||||
ethOperator.getTransactionHistory(ethAddress, {
|
||||
ethOperator.getTransactionHistory(ethAddress, {
|
||||
page: page,
|
||||
offset: TRANSACTIONS_PER_PAGE,
|
||||
sort: 'desc'
|
||||
@ -859,9 +870,9 @@
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, transactions, page);
|
||||
|
||||
|
||||
} catch (error) {
|
||||
notify(error.message || error, 'error');
|
||||
} finally {
|
||||
@ -877,7 +888,7 @@
|
||||
url.searchParams.delete('page');
|
||||
// Update browser URL without reloading the page
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
|
||||
// Determine if pagination buttons should be enabled
|
||||
const hasNextPage = transactions.length >= TRANSACTIONS_PER_PAGE;
|
||||
const hasPrevPage = page > 1;
|
||||
@ -914,9 +925,9 @@
|
||||
<div class="flex align-center space-between">
|
||||
<h4>Transactions</h4>
|
||||
<sm-chips id="tx_filter_chips" onchange=${(e) => {
|
||||
const selectedValue = e.detail?.value || e.target.value || 'all';
|
||||
filterTransactions(selectedValue, transactions, ethAddress);
|
||||
}}>
|
||||
const selectedValue = e.detail?.value || e.target.value || 'all';
|
||||
filterTransactions(selectedValue, transactions, ethAddress);
|
||||
}}>
|
||||
<sm-chip value="all" selected>All</sm-chip>
|
||||
<sm-chip value="sent">Sent</sm-chip>
|
||||
<sm-chip value="received">Received</sm-chip>
|
||||
@ -944,7 +955,7 @@
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
||||
getRef('eth_balance_wrapper').classList.remove('hidden');
|
||||
getRef('eth_balance_wrapper').animate([
|
||||
{
|
||||
@ -1006,9 +1017,9 @@
|
||||
const amountPrefix = isReceived ? '+' : '-';
|
||||
const displayAddress = isReceived ? tx.from : tx.to;
|
||||
const directionText = isReceived ? 'Received from' : 'Sent to';
|
||||
|
||||
|
||||
// Arrow icons matching BTC wallet
|
||||
const icon = isReceived
|
||||
const icon = isReceived
|
||||
? svg`<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/></svg>`
|
||||
: svg`<svg class="icon sent" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z"/></svg>`;
|
||||
|
||||
@ -1048,22 +1059,22 @@
|
||||
async function viewTransactionDetails(txHash, preserveAddress = false) {
|
||||
try {
|
||||
buttonLoader('check_balance_button', true);
|
||||
|
||||
|
||||
// Update URL to show the transaction hash
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('tx', txHash);
|
||||
url.searchParams.delete('page');
|
||||
|
||||
|
||||
// Remove address from URL unless viewing from transaction history
|
||||
if (!preserveAddress) {
|
||||
url.searchParams.delete('address');
|
||||
}
|
||||
|
||||
|
||||
// Update browser URL without reloading the page
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
|
||||
const txDetails = await ethOperator.getTransactionDetails(txHash);
|
||||
|
||||
|
||||
let formattedDate = 'Pending';
|
||||
if (txDetails.timestamp) {
|
||||
const date = new Date(txDetails.timestamp * 1000);
|
||||
@ -1086,13 +1097,13 @@
|
||||
<div class="flex align-center space-between">
|
||||
<h3>Transaction Details</h3>
|
||||
<button class="button button--small" onclick=${() => {
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.delete('tx');
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
// Go back to address view
|
||||
handleUrlParams();
|
||||
}}>
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.delete('tx');
|
||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||
|
||||
// Go back to address view
|
||||
handleUrlParams();
|
||||
}}>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
@ -1167,7 +1178,7 @@
|
||||
// Function to handle URL parameters and load appropriate data
|
||||
function handleUrlParams() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
||||
// Check for transaction hash parameter
|
||||
const txHash = urlParams.get('tx');
|
||||
if (txHash && /^0x[0-9a-fA-F]{64}$/.test(txHash)) {
|
||||
@ -1184,14 +1195,14 @@
|
||||
viewTransactionDetails(txHash);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check for address parameter
|
||||
const address = urlParams.get('address');
|
||||
if (address && ethOperator.isValidAddress(address)) {
|
||||
// Populate the input field - target both custom element and native input
|
||||
const searchInput = document.querySelector('#check_balance_input');
|
||||
const nativeInput = searchInput?.querySelector('input');
|
||||
|
||||
|
||||
if (nativeInput) {
|
||||
nativeInput.value = address;
|
||||
nativeInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
@ -1200,7 +1211,7 @@
|
||||
searchInput.value = address;
|
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
}
|
||||
|
||||
|
||||
// Load the address (checkBalance handles its own loading state)
|
||||
checkBalance(address);
|
||||
} else {
|
||||
@ -1352,38 +1363,38 @@
|
||||
const receiver = getRef('send_tx_form').querySelector('.receiver-address').value.trim();
|
||||
const amount = getRef('send_tx_form').querySelector('.receiver-amount').value.trim();
|
||||
const asset = getRef('asset_selector').value;
|
||||
|
||||
|
||||
try {
|
||||
// First, get basic confirmation
|
||||
const initialConfirmation = await getConfirmation('Confirm transaction', {
|
||||
message: `Calculating gas fees for sending ${amount} ${asset.toUpperCase()} to ${receiver}...`,
|
||||
confirmText: 'Continue',
|
||||
});
|
||||
|
||||
|
||||
if (!initialConfirmation) return;
|
||||
|
||||
|
||||
// Show loading state while calculating gas
|
||||
buttonLoader('send_tx_button', true);
|
||||
|
||||
|
||||
// Get private key
|
||||
let privateKey = getRef('private_key_input').value.trim();
|
||||
if (/^[0-9a-fA-F]{64}$/.test(privateKey)) {
|
||||
privateKey = coinjs.privkey2wif(privateKey);
|
||||
}
|
||||
privateKey = coinjs.wif2privkey(privateKey).privkey;
|
||||
|
||||
|
||||
// Calculate gas fees
|
||||
let gasEstimate, feeData, estimatedGasFee, maxGasFee, totalCostETH;
|
||||
|
||||
|
||||
try {
|
||||
// Get provider for gas estimation
|
||||
const provider = ethOperator.getProvider(true);
|
||||
|
||||
|
||||
if (!provider) throw new Error('Provider not available');
|
||||
|
||||
|
||||
// Get fee data
|
||||
feeData = await provider.getFeeData();
|
||||
|
||||
|
||||
// Estimate gas limit
|
||||
if (asset === 'ether') {
|
||||
gasEstimate = await ethOperator.estimateGas({
|
||||
@ -1395,43 +1406,43 @@
|
||||
// For token transfers, estimate is typically higher
|
||||
gasEstimate = ethers.BigNumber.from('65000'); // Typical ERC20 transfer gas
|
||||
}
|
||||
|
||||
|
||||
// Calculate priority fee and max fee
|
||||
const priorityFee = feeData.maxPriorityFeePerGas || ethers.utils.parseUnits("1.5", "gwei");
|
||||
let maxFee = feeData.maxFeePerGas;
|
||||
|
||||
|
||||
if (!maxFee || maxFee.lt(priorityFee)) {
|
||||
const block = await provider.getBlock("latest");
|
||||
const baseFee = block.baseFeePerGas || ethers.utils.parseUnits("1", "gwei");
|
||||
maxFee = baseFee.mul(2).add(priorityFee);
|
||||
}
|
||||
|
||||
|
||||
const minMaxFee = priorityFee.mul(15).div(10);
|
||||
if (maxFee.lt(minMaxFee)) {
|
||||
maxFee = minMaxFee;
|
||||
}
|
||||
|
||||
|
||||
// Calculate estimated gas fee (using base fee + priority fee for estimation)
|
||||
const block = await provider.getBlock("latest");
|
||||
const baseFee = block.baseFeePerGas || ethers.utils.parseUnits("1", "gwei");
|
||||
const estimatedGasPrice = baseFee.add(priorityFee);
|
||||
estimatedGasFee = parseFloat(ethers.utils.formatEther(gasEstimate.mul(estimatedGasPrice)));
|
||||
|
||||
|
||||
// Calculate max possible gas fee
|
||||
maxGasFee = parseFloat(ethers.utils.formatEther(gasEstimate.mul(maxFee)));
|
||||
|
||||
|
||||
// Calculate total cost in ETH
|
||||
totalCostETH = asset === 'ether' ? (parseFloat(amount) + estimatedGasFee) : estimatedGasFee;
|
||||
|
||||
|
||||
} catch (gasError) {
|
||||
console.error('Gas estimation error:', gasError);
|
||||
buttonLoader('send_tx_button', false);
|
||||
notify('Failed to estimate gas fees. Please try again.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
buttonLoader('send_tx_button', false);
|
||||
|
||||
|
||||
// Show detailed confirmation with gas fees
|
||||
const gasConfirmationPopup = html.node`
|
||||
<sm-popup id="gas_confirmation_popup">
|
||||
@ -1487,9 +1498,9 @@
|
||||
</div>
|
||||
</sm-popup>
|
||||
`;
|
||||
|
||||
|
||||
document.body.appendChild(gasConfirmationPopup);
|
||||
|
||||
|
||||
// Store transaction data for confirmation
|
||||
window.pendingTxData = {
|
||||
receiver,
|
||||
@ -1497,7 +1508,7 @@
|
||||
asset,
|
||||
privateKey
|
||||
};
|
||||
|
||||
|
||||
// Define close function
|
||||
window.closeGasConfirmation = () => {
|
||||
closePopup();
|
||||
@ -1508,19 +1519,19 @@
|
||||
delete window.confirmAndSend;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
|
||||
// Define confirm and send function
|
||||
window.confirmAndSend = async () => {
|
||||
closePopup();
|
||||
gasConfirmationPopup.remove();
|
||||
|
||||
|
||||
const { receiver, amount, asset, privateKey } = window.pendingTxData;
|
||||
delete window.pendingTxData;
|
||||
delete window.closeGasConfirmation;
|
||||
delete window.confirmAndSend;
|
||||
|
||||
|
||||
buttonLoader('send_tx_button', true);
|
||||
|
||||
|
||||
try {
|
||||
switch (asset) {
|
||||
case 'ether': {
|
||||
@ -1568,9 +1579,9 @@
|
||||
buttonLoader('send_tx_button', false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
openPopup('gas_confirmation_popup');
|
||||
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
notify(e.message || 'Transaction failed', 'error');
|
||||
@ -1625,13 +1636,13 @@
|
||||
}
|
||||
openPopup('transaction_result_popup')
|
||||
}
|
||||
|
||||
|
||||
// ROUTE: Retrieve
|
||||
router.addRoute('retrieve', (state) => {
|
||||
const container = getRef('page_container');
|
||||
container.dataset.page = 'retrieve';
|
||||
const container = getRef('page_container');
|
||||
container.dataset.page = 'retrieve';
|
||||
|
||||
renderElem(container, html`
|
||||
renderElem(container, html`
|
||||
<sm-form id="retrieve_form" style="width: min(32rem, 100%)">
|
||||
<!-- Block 1 (like Sender block) -->
|
||||
<fieldset class="flex flex-direction-column gap-0-5">
|
||||
@ -1678,70 +1689,70 @@
|
||||
}
|
||||
|
||||
function retrieveAddress() {
|
||||
const outEl = getRef('retrieve_result');
|
||||
const inpEl = getRef('retrieve_key_input');
|
||||
let input = getRef('retrieve_key_input').value?.trim();
|
||||
let usedSecret = false;
|
||||
const outEl = getRef('retrieve_result');
|
||||
const inpEl = getRef('retrieve_key_input');
|
||||
let input = getRef('retrieve_key_input').value?.trim();
|
||||
let usedSecret = false;
|
||||
let wif = '';
|
||||
let ethPriv = '';
|
||||
|
||||
if (!input) {
|
||||
notify('Please enter an ETH address, WIF, or 64-hex private key', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
if (!input) {
|
||||
notify('Please enter an ETH address, WIF, or 64-hex private key', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Loading hint
|
||||
renderElem(outEl, html`<div class="muted">Resolving…</div>`);
|
||||
outEl.classList.remove('hidden');
|
||||
try {
|
||||
// Loading hint
|
||||
renderElem(outEl, html`<div class="muted">Resolving…</div>`);
|
||||
outEl.classList.remove('hidden');
|
||||
|
||||
// Detect ETH address (without private key)
|
||||
const isEthAddrRegex = /^0x[0-9a-fA-F]{40}$/;
|
||||
const isEthAddress = (typeof ethOperator !== 'undefined' && ethOperator.isValidAddress?.(input))
|
||||
|| isEthAddrRegex.test(input);
|
||||
// Detect ETH address (without private key)
|
||||
const isEthAddrRegex = /^0x[0-9a-fA-F]{40}$/;
|
||||
const isEthAddress = (typeof ethOperator !== 'undefined' && ethOperator.isValidAddress?.(input))
|
||||
|| isEthAddrRegex.test(input);
|
||||
|
||||
if (isEthAddress) {
|
||||
// We cannot derive BTC/FLO from an address alone (no private key)
|
||||
const normalizedEth = input.toLowerCase();
|
||||
renderElem(outEl, html`
|
||||
if (isEthAddress) {
|
||||
// We cannot derive BTC/FLO from an address alone (no private key)
|
||||
const normalizedEth = input.toLowerCase();
|
||||
renderElem(outEl, html`
|
||||
<div class="card grid gap-0-5">
|
||||
<span class="label">Resolved</span>
|
||||
<div><strong>ETH address</strong>: <sm-copy value="${normalizedEth}"></sm-copy></div>
|
||||
<div class="muted">BTC/FLO cannot be derived from an address without the private key.</div>
|
||||
</div>
|
||||
`);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize to WIF if the input is a raw 64-hex private key
|
||||
usedSecret = true;
|
||||
// Normalize to WIF if the input is a raw 64-hex private key
|
||||
usedSecret = true;
|
||||
|
||||
let wif = input;
|
||||
const is64Hex = /^[0-9a-fA-F]{64}$/.test(input);
|
||||
if (is64Hex) {
|
||||
wif = coinjs.privkey2wif(input); // hex → WIF
|
||||
}
|
||||
let wif = input;
|
||||
const is64Hex = /^[0-9a-fA-F]{64}$/.test(input);
|
||||
if (is64Hex) {
|
||||
wif = coinjs.privkey2wif(input); // hex → WIF
|
||||
}
|
||||
|
||||
// From WIF, derive ETH private key (hex)
|
||||
const ethPriv = coinjs.wif2privkey(wif).privkey;
|
||||
// From WIF, derive ETH private key (hex)
|
||||
const ethPriv = coinjs.wif2privkey(wif).privkey;
|
||||
|
||||
|
||||
// ETH address from private key
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPriv);
|
||||
// ETH address from private key
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPriv);
|
||||
|
||||
// BTC bech32 from WIF
|
||||
const btcBech32 = btcOperator.bech32Address(wif);
|
||||
//const btcPriv = btcOperator.convert.wif(wif);
|
||||
//const floPriv = btcOperator.convert.wif(wif, 0xa3);
|
||||
// BTC bech32 from WIF
|
||||
const btcBech32 = btcOperator.bech32Address(wif);
|
||||
//const btcPriv = btcOperator.convert.wif(wif);
|
||||
//const floPriv = btcOperator.convert.wif(wif, 0xa3);
|
||||
|
||||
// FLO from WIF
|
||||
const floAddress = floCrypto.getFloID(wif);
|
||||
// FLO from WIF
|
||||
const floAddress = floCrypto.getFloID(wif);
|
||||
|
||||
|
||||
// Render results
|
||||
renderElem(outEl, html`
|
||||
// Render results
|
||||
renderElem(outEl, html`
|
||||
<div class="card grid gap-1">
|
||||
|
||||
|
||||
@ -1755,29 +1766,29 @@
|
||||
</div>
|
||||
`);
|
||||
|
||||
} catch (e) {
|
||||
notify('Could not retrieve from the provided value', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
}
|
||||
} catch (e) {
|
||||
notify('Could not retrieve from the provided value', 'error');
|
||||
renderElem(outEl, html``);
|
||||
outEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
finally {
|
||||
if (usedSecret && inpEl) {
|
||||
const wipe = (s) => (typeof s === 'string' ? s.replace(/./g, '\0') : s);
|
||||
input = typeof input === 'string' && input.length ? (wipe(input), null) : input;
|
||||
wif = typeof wif === 'string' && wif.length ? (wipe(wif), null) : wif;
|
||||
ethPriv = typeof ethPriv === 'string' && ethPriv.length ? (wipe(ethPriv), null) : ethPriv;
|
||||
finally {
|
||||
if (usedSecret && inpEl) {
|
||||
const wipe = (s) => (typeof s === 'string' ? s.replace(/./g, '\0') : s);
|
||||
input = typeof input === 'string' && input.length ? (wipe(input), null) : input;
|
||||
wif = typeof wif === 'string' && wif.length ? (wipe(wif), null) : wif;
|
||||
ethPriv = typeof ethPriv === 'string' && ethPriv.length ? (wipe(ethPriv), null) : ethPriv;
|
||||
|
||||
const inner = inpEl.shadowRoot?.querySelector('input[part="input"]');
|
||||
if (inner) {
|
||||
inner.value = '';
|
||||
inner.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||
if (typeof handleRetrieveInput === 'function') {
|
||||
const inner = inpEl.shadowRoot?.querySelector('input[part="input"]');
|
||||
if (inner) {
|
||||
inner.value = '';
|
||||
inner.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
|
||||
if (typeof handleRetrieveInput === 'function') {
|
||||
handleRetrieveInput({ target: inpEl });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1812,6 +1823,9 @@
|
||||
const { floID, privKey } = floCrypto.generateNewID();
|
||||
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
const btcBech32 = btcOperator.bech32Address(privKey);
|
||||
// Convert FLO WIF to Bitcoin WIF format (L/K prefix)
|
||||
const btcPrivKey = btcOperator.convert.wif(privKey);
|
||||
renderElem(getRef('created_address_wrapper'), html`
|
||||
<ul id="generated_addresses" class="grid gap-1-5">
|
||||
<li class="grid gap-0-5">
|
||||
@ -1824,6 +1838,16 @@
|
||||
<sm-copy value="${privKey}"></sm-copy>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>Bitcoin Address</h5>
|
||||
<sm-copy value="${btcBech32}"></sm-copy>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Private Key</h5>
|
||||
<sm-copy value="${btcPrivKey}"></sm-copy>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>Equivalent Ethereum Address</h5>
|
||||
@ -1842,4 +1866,4 @@
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,9 +3,32 @@
|
||||
const ltcBlockchainAPI = EXPORTS;
|
||||
|
||||
const DEFAULT = {
|
||||
fee: 0.001,
|
||||
// Fee rate in satoshis per byte (10 sat/byte is safe for Litecoin)
|
||||
feeRateSatPerByte: 10,
|
||||
// Fallback fixed fee in LTC (only used if calculation fails)
|
||||
fallbackFee: 0.00001,
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate transaction fee based on number of inputs and outputs
|
||||
* Formula: (10 + inputs*148 + outputs*34) * satPerByte
|
||||
* @param {number} numInputs - Number of transaction inputs
|
||||
* @param {number} numOutputs - Number of transaction outputs
|
||||
* @param {number} satPerByte - Fee rate in satoshis per byte (default 10)
|
||||
* @returns {number} Fee in LTC
|
||||
*/
|
||||
function calculateFee(numInputs, numOutputs, satPerByte = DEFAULT.feeRateSatPerByte) {
|
||||
// P2PKH transaction size estimation:
|
||||
// - Overhead: ~10 bytes
|
||||
// - Per input: ~148 bytes (for compressed pubkey signatures)
|
||||
// - Per output: ~34 bytes
|
||||
const estimatedSize = 10 + (numInputs * 148) + (numOutputs * 34);
|
||||
const feeInSatoshis = estimatedSize * satPerByte;
|
||||
const feeInLTC = feeInSatoshis / 100000000;
|
||||
console.log(`Estimated tx size: ${estimatedSize} bytes, Fee: ${feeInLTC.toFixed(8)} LTC (${feeInSatoshis} satoshis)`);
|
||||
return feeInLTC;
|
||||
}
|
||||
|
||||
//Get balance for the given Address
|
||||
ltcBlockchainAPI.getBalance = function (addr) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -282,12 +305,12 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Send Litecoin transaction using direct RPC calls to GetBlock.io
|
||||
* This method implements the full RPC workflow: createrawtransaction -> signrawtransaction -> sendrawtransaction
|
||||
* Send Litecoin transaction using client-side signing with bitjs library
|
||||
* Transaction is constructed and signed locally, then broadcast via RPC
|
||||
* @param {string} senderAddr - Sender's Litecoin address
|
||||
* @param {string} receiverAddr - Receiver's Litecoin address
|
||||
* @param {number} sendAmt - Amount to send in LTC
|
||||
* @param {string} privKey - Private key of the sender
|
||||
* @param {string} privKey - Private key of the sender (WIF format)
|
||||
* @returns {Promise} Promise that resolves with the transaction ID
|
||||
*/
|
||||
ltcBlockchainAPI.sendLitecoinRPC = function (
|
||||
@ -303,10 +326,14 @@
|
||||
return reject(`Invalid receiver address: ${receiverAddr}`);
|
||||
if (typeof sendAmt !== "number" || sendAmt <= 0)
|
||||
return reject(`Invalid send amount: ${sendAmt}`);
|
||||
|
||||
// Minimum amount to avoid dust errors (GetBlock requires ~10000 satoshis minimum)
|
||||
const MIN_SEND_AMOUNT = 0.0001; // 10000 satoshis
|
||||
if (sendAmt < MIN_SEND_AMOUNT)
|
||||
return reject(`Amount too small. Minimum is ${MIN_SEND_AMOUNT} LTC to avoid dust rejection.`);
|
||||
if (privKey.length < 1 || !ltcCrypto.verifyPrivKey(privKey, senderAddr))
|
||||
return reject("Invalid Private key!");
|
||||
|
||||
const fee = DEFAULT.fee;
|
||||
const apiToken = "31ea37c3a0c44b368e879007af7a64c8";
|
||||
const rpcEndpoint = `https://go.getblock.io/${apiToken}/`;
|
||||
|
||||
@ -319,9 +346,15 @@
|
||||
const text = await res.text();
|
||||
try {
|
||||
const data = JSON.parse(text);
|
||||
if (data.error) throw new Error(JSON.stringify(data.error));
|
||||
if (data.error) {
|
||||
// Extract meaningful error message from RPC response
|
||||
const errMsg = data.error.message || JSON.stringify(data.error);
|
||||
throw new Error(`RPC Error: ${errMsg}`);
|
||||
}
|
||||
return data.result;
|
||||
} catch (err) {
|
||||
// Re-throw if it's already our formatted error
|
||||
if (err.message.startsWith("RPC Error:")) throw err;
|
||||
console.error("Raw RPC response:\n", text);
|
||||
throw new Error("Failed to parse JSON-RPC response");
|
||||
}
|
||||
@ -336,61 +369,69 @@
|
||||
const utxoTotal = utxos.reduce((sum, utxo) => sum + utxo.value, 0);
|
||||
console.log("Total UTXO value:", utxoTotal);
|
||||
|
||||
// Calculate fee based on transaction size
|
||||
// Inputs = number of UTXOs, Outputs = 2 (receiver + change)
|
||||
const numInputs = utxos.length;
|
||||
const numOutputs = 2; // receiver + change output
|
||||
const fee = calculateFee(numInputs, numOutputs);
|
||||
console.log(`Dynamic fee calculated: ${fee.toFixed(8)} LTC for ${numInputs} inputs, ${numOutputs} outputs`);
|
||||
|
||||
if (utxoTotal < sendAmt + fee)
|
||||
return reject(
|
||||
`Insufficient funds: ${utxoTotal} < ${sendAmt + fee}`
|
||||
`Insufficient funds: ${utxoTotal.toFixed(8)} LTC < ${(sendAmt + fee).toFixed(8)} LTC (${sendAmt} + ${fee.toFixed(8)} fee)`
|
||||
);
|
||||
|
||||
const inputs = utxos.map((utxo) => ({
|
||||
txid: utxo.txid,
|
||||
vout: utxo.vout,
|
||||
}));
|
||||
|
||||
console.log("inputs:", inputs);
|
||||
|
||||
// Calculate change amount
|
||||
const change = utxoTotal - sendAmt - fee;
|
||||
|
||||
const outputs = {
|
||||
[senderAddr]: Number(change.toFixed(8)),
|
||||
[receiverAddr]: Number(sendAmt.toFixed(8)),
|
||||
};
|
||||
console.log("outputs:", outputs);
|
||||
|
||||
try {
|
||||
// Create raw transaction
|
||||
console.log("Creating raw transaction...");
|
||||
const rawTx = await rpc("createrawtransaction", [inputs, outputs]);
|
||||
console.log("Raw transaction hex:", rawTx);
|
||||
// Sign raw transaction
|
||||
console.log("Signing transaction...");
|
||||
const signedTx = await rpc("signrawtransaction", [
|
||||
rawTx,
|
||||
[
|
||||
{
|
||||
txid: utxos[0].txid,
|
||||
vout: utxos[0].vout,
|
||||
scriptPubKey: utxos[0].scriptPubKey,
|
||||
amount: utxos[0].value.toFixed(8),
|
||||
},
|
||||
],
|
||||
[privKey],
|
||||
]);
|
||||
// Save original bitjs settings and set Litecoin version bytes
|
||||
const origPub = bitjs.pub;
|
||||
const origPriv = bitjs.priv;
|
||||
const origCompressed = bitjs.compressed;
|
||||
|
||||
if (!signedTx.complete) {
|
||||
return reject(
|
||||
`Failed to sign transaction: ${JSON.stringify(signedTx.errors)}`
|
||||
);
|
||||
// Litecoin mainnet version bytes
|
||||
bitjs.pub = 0x30; // Litecoin P2PKH address prefix
|
||||
bitjs.priv = 0xb0; // Litecoin WIF prefix
|
||||
bitjs.compressed = true;
|
||||
|
||||
// Create transaction using bitjs
|
||||
console.log("Creating transaction with bitjs...");
|
||||
const tx = bitjs.transaction();
|
||||
|
||||
// Add all UTXOs as inputs
|
||||
for (const utxo of utxos) {
|
||||
tx.addinput(utxo.txid, utxo.vout, utxo.scriptPubKey);
|
||||
console.log(`Added input: ${utxo.txid}:${utxo.vout}`);
|
||||
}
|
||||
console.log("Signed transaction hex:", signedTx.hex);
|
||||
|
||||
// Send raw transaction
|
||||
// Add outputs: receiver first, then change back to sender
|
||||
tx.addoutput(receiverAddr, sendAmt);
|
||||
console.log(`Added output to receiver: ${receiverAddr} = ${sendAmt} LTC`);
|
||||
|
||||
if (change > 0.00000546) { // Only add change if above dust threshold
|
||||
tx.addoutput(senderAddr, change);
|
||||
console.log(`Added change output: ${senderAddr} = ${change} LTC`);
|
||||
}
|
||||
|
||||
// Sign the transaction with private key
|
||||
console.log("Signing transaction locally...");
|
||||
const signedTxHex = tx.sign(privKey);
|
||||
console.log("Signed transaction hex:", signedTxHex);
|
||||
|
||||
// Restore original bitjs settings
|
||||
bitjs.pub = origPub;
|
||||
bitjs.priv = origPriv;
|
||||
bitjs.compressed = origCompressed;
|
||||
|
||||
// Broadcast the signed transaction
|
||||
console.log("Broadcasting transaction...");
|
||||
const txid = await rpc("sendrawtransaction", [signedTx.hex]);
|
||||
const txid = await rpc("sendrawtransaction", [signedTxHex]);
|
||||
console.log("Transaction broadcast successful! TXID:", txid);
|
||||
|
||||
resolve(txid);
|
||||
} catch (error) {
|
||||
console.error("RPC error:", error);
|
||||
console.error("Transaction error:", error);
|
||||
reject(error);
|
||||
}
|
||||
})
|
||||
|
||||
@ -468,9 +468,10 @@
|
||||
const apply = () => {
|
||||
if (window.matchMedia('(min-width: 640px)').matches) {
|
||||
Object.assign(panel.style, {
|
||||
left: 'calc(10rem + 1rem)',
|
||||
bottom: '1rem',
|
||||
top: 'auto',
|
||||
// Desktop: top-right to avoid overlapping "Searched addresses" (which is left-side)
|
||||
left: 'auto',
|
||||
bottom: 'auto',
|
||||
top: '1rem',
|
||||
right: '1rem',
|
||||
zIndex: '1000'
|
||||
});
|
||||
@ -663,7 +664,7 @@
|
||||
</h2>
|
||||
<sm-form oninvalid="handleInvalidSearch()">
|
||||
<div id="input_wrapper">
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="Address, private key, or tx hash"
|
||||
<sm-input id="check_balance_input" class="password-field flex-1" placeholder="MNT Address, private key, or tx hash"
|
||||
type="password" animate>
|
||||
<svg class="icon" slot="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M21,10h-8.35C11.83,7.67,9.61,6,7,6c-3.31,0-6,2.69-6,6s2.69,6,6,6c2.61,0,4.83-1.67,5.65-4H13l2,2l2-2l2,2l4-4.04L21,10z M7,15c-1.65,0-3-1.35-3-3c0-1.65,1.35-3,3-3s3,1.35,3,3C10,13.65,8.65,15,7,15z"> </path> </g> </svg>
|
||||
<label slot="right" class="interact">
|
||||
@ -734,13 +735,14 @@
|
||||
}
|
||||
const renderedContacts = []
|
||||
for (const floAddress in contacts) {
|
||||
const { ethAddress } = contacts[floAddress]
|
||||
const { ethAddress, btcAddress } = contacts[floAddress]
|
||||
renderedContacts.push(html`
|
||||
<li class="contact" .dataset=${{ floAddress, ethAddress }}>
|
||||
<li class="contact" .dataset=${{ floAddress, ethAddress, btcAddress }}>
|
||||
${floAddress === ethAddress ? html`
|
||||
`: html`
|
||||
<sm-chips onchange=${e => e.target.closest('.contact').querySelector('sm-copy').value = e.target.value}>
|
||||
<sm-chip value=${floAddress} selected>FLO</sm-chip>
|
||||
${btcAddress ? html`<sm-chip value=${btcAddress}>BTC</sm-chip>` : ''}
|
||||
<sm-chip value=${ethAddress}>MNT</sm-chip>
|
||||
</sm-chips>
|
||||
`}
|
||||
@ -777,6 +779,19 @@
|
||||
}
|
||||
|
||||
// The search bar is versatile: it accepts addresses, private keys, or transaction hashes.
|
||||
|
||||
// Reject FLO addresses (start with F)
|
||||
if (/^F[a-km-zA-HJ-NP-Z1-9]{26,34}$/.test(keyToConvert)) {
|
||||
notify('FLO addresses are not supported. Please use a MNT address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Reject BTC addresses (legacy: 1/3, segwit: bc1)
|
||||
if (/^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(keyToConvert) || /^bc1[a-z0-9]{39,59}$/i.test(keyToConvert)) {
|
||||
notify('BTC addresses are not supported. Please use a MNT address or private key.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (ethOperator.isValidAddress(keyToConvert)) {
|
||||
ethAddress = keyToConvert
|
||||
}
|
||||
@ -787,12 +802,37 @@
|
||||
// Otherwise, try to convert as private key
|
||||
else {
|
||||
try {
|
||||
let isHex = false;
|
||||
let btcAddress;
|
||||
|
||||
if (/^[0-9a-fA-F]{64}$/.test(keyToConvert)) {
|
||||
keyToConvert = coinjs.privkey2wif(keyToConvert)
|
||||
isHex = true;
|
||||
}
|
||||
const ethPrivateKey = coinjs.wif2privkey(keyToConvert).privkey;
|
||||
ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
floAddress = floCrypto.getFloID(keyToConvert)
|
||||
|
||||
|
||||
|
||||
if (!isHex) {
|
||||
floAddress = floCrypto.getFloID(keyToConvert)
|
||||
try {
|
||||
btcAddress = btcOperator.bech32Address(keyToConvert)
|
||||
} catch (e) { console.error(e) }
|
||||
}
|
||||
|
||||
// Save to indexed DB
|
||||
compactIDB.readData('contacts', floAddress || ethAddress).then(result => {
|
||||
if (result) return
|
||||
compactIDB.addData('contacts', {
|
||||
ethAddress,
|
||||
btcAddress
|
||||
}, floAddress || ethAddress).then(() => {
|
||||
renderSearchedAddressList()
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
notify('Invalid input. Please enter a valid Ethereum address or private key.', 'error');
|
||||
return;
|
||||
@ -1962,8 +2002,24 @@
|
||||
const { floID, privKey } = floCrypto.generateNewID();
|
||||
const ethPrivateKey = coinjs.wif2privkey(privKey).privkey;
|
||||
const ethAddress = floEthereum.ethAddressFromPrivateKey(ethPrivateKey)
|
||||
|
||||
// Bitcoin support
|
||||
const btcBech32 = btcOperator.bech32Address(privKey);
|
||||
// Convert to Bitcoin WIF format
|
||||
const btcPrivKey = btcOperator.convert.wif(privKey);
|
||||
|
||||
renderElem(getRef('created_address_wrapper'), html`
|
||||
<ul id="generated_addresses" class="grid gap-1-5">
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>Bitcoin Address</h5>
|
||||
<sm-copy value="${btcBech32}"></sm-copy>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Private Key</h5>
|
||||
<sm-copy value="${btcPrivKey}"></sm-copy>
|
||||
</div>
|
||||
</li>
|
||||
<li class="grid gap-0-5">
|
||||
<div>
|
||||
<h5>FLO Address</h5>
|
||||
|
||||
2
optimismwallet/README.md
Normal file
2
optimismwallet/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# optimismwallet
|
||||
Optimism blockchain wallet linked with FLO and BTC network
|
||||
1291
optimismwallet/css/main.css
Normal file
1291
optimismwallet/css/main.css
Normal file
File diff suppressed because it is too large
Load Diff
1886
optimismwallet/index.html
Normal file
1886
optimismwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
1499
optimismwallet/scripts/btcOperator.js
Normal file
1499
optimismwallet/scripts/btcOperator.js
Normal file
File diff suppressed because it is too large
Load Diff
10430
optimismwallet/scripts/btcwallet_scripts_lib.js
Normal file
10430
optimismwallet/scripts/btcwallet_scripts_lib.js
Normal file
File diff suppressed because it is too large
Load Diff
257
optimismwallet/scripts/compactIDB.js
Normal file
257
optimismwallet/scripts/compactIDB.js
Normal file
@ -0,0 +1,257 @@
|
||||
(function (EXPORTS) { //compactIDB v2.1.2
|
||||
/* Compact IndexedDB operations */
|
||||
'use strict';
|
||||
const compactIDB = EXPORTS;
|
||||
|
||||
var defaultDB;
|
||||
|
||||
const indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
|
||||
const IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
|
||||
const IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
|
||||
|
||||
if (!indexedDB) {
|
||||
console.error("Your browser doesn't support a stable version of IndexedDB.");
|
||||
return;
|
||||
}
|
||||
|
||||
compactIDB.setDefaultDB = dbName => defaultDB = dbName;
|
||||
|
||||
Object.defineProperty(compactIDB, 'default', {
|
||||
get: () => defaultDB,
|
||||
set: dbName => defaultDB = dbName
|
||||
});
|
||||
|
||||
function getDBversion(dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
resolve(db.version)
|
||||
db.close()
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
function upgradeDB(dbName, createList = null, deleteList = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getDBversion(dbName).then(version => {
|
||||
var idb = indexedDB.open(dbName, version + 1);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
let db = event.target.result;
|
||||
if (createList instanceof Object) {
|
||||
if (Array.isArray(createList)) {
|
||||
let tmp = {}
|
||||
createList.forEach(o => tmp[o] = {})
|
||||
createList = tmp
|
||||
}
|
||||
for (let o in createList) {
|
||||
let obs = db.createObjectStore(o, createList[o].options || {});
|
||||
if (createList[o].indexes instanceof Object)
|
||||
for (let i in createList[o].indexes)
|
||||
obs.createIndex(i, i, createList[o].indexes || {});
|
||||
}
|
||||
}
|
||||
if (Array.isArray(deleteList))
|
||||
deleteList.forEach(o => db.deleteObjectStore(o));
|
||||
resolve('Database upgraded')
|
||||
}
|
||||
idb.onsuccess = (event) => event.target.result.close();
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}
|
||||
|
||||
compactIDB.initDB = function (dbName, objectStores = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(objectStores instanceof Object))
|
||||
return reject('ObjectStores must be an object or array')
|
||||
defaultDB = defaultDB || dbName;
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onsuccess = (event) => {
|
||||
var db = event.target.result;
|
||||
let cList = Object.values(db.objectStoreNames);
|
||||
var obs = {},
|
||||
a_obs = {},
|
||||
d_obs = [];
|
||||
if (!Array.isArray(objectStores))
|
||||
var obs = objectStores
|
||||
else
|
||||
objectStores.forEach(o => obs[o] = {})
|
||||
let nList = Object.keys(obs)
|
||||
for (let o of nList)
|
||||
if (!cList.includes(o))
|
||||
a_obs[o] = obs[o]
|
||||
for (let o of cList)
|
||||
if (!nList.includes(o))
|
||||
d_obs.push(o)
|
||||
if (!Object.keys(a_obs).length && !d_obs.length)
|
||||
resolve("Initiated IndexedDB");
|
||||
else
|
||||
upgradeDB(dbName, a_obs, d_obs)
|
||||
.then(result => resolve(result))
|
||||
.catch(error => reject(error))
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const openDB = compactIDB.openDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var idb = indexedDB.open(dbName);
|
||||
idb.onerror = (event) => reject("Error in opening IndexedDB");
|
||||
idb.onupgradeneeded = (event) => {
|
||||
event.target.result.close();
|
||||
deleteDB(dbName).then(_ => null).catch(_ => null).finally(_ => reject("Datebase not found"))
|
||||
}
|
||||
idb.onsuccess = (event) => resolve(event.target.result);
|
||||
});
|
||||
}
|
||||
|
||||
const deleteDB = compactIDB.deleteDB = function (dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var deleteReq = indexedDB.deleteDatabase(dbName);;
|
||||
deleteReq.onerror = (event) => reject("Error deleting database!");
|
||||
deleteReq.onsuccess = (event) => resolve("Database deleted successfully");
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.writeData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let writeReq = (key ? obs.put(data, key) : obs.put(data));
|
||||
writeReq.onsuccess = (evt) => resolve(`Write data Successful`);
|
||||
writeReq.onerror = (evt) => reject(
|
||||
`Write data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.addData = function (obsName, data, key = false, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let addReq = (key ? obs.add(data, key) : obs.add(data));
|
||||
addReq.onsuccess = (evt) => resolve(`Add data successful`);
|
||||
addReq.onerror = (evt) => reject(
|
||||
`Add data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.removeData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let delReq = obs.delete(key);
|
||||
delReq.onsuccess = (evt) => resolve(`Removed Data ${key}`);
|
||||
delReq.onerror = (evt) => reject(
|
||||
`Remove data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.clearData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readwrite").objectStore(obsName);
|
||||
let clearReq = obs.clear();
|
||||
clearReq.onsuccess = (evt) => resolve(`Clear data Successful`);
|
||||
clearReq.onerror = (evt) => reject(`Clear data Unsuccessful`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readData = function (obsName, key, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
let getReq = obs.get(key);
|
||||
getReq.onsuccess = (evt) => resolve(evt.target.result);
|
||||
getReq.onerror = (evt) => reject(
|
||||
`Read data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
compactIDB.readAllData = function (obsName, dbName = defaultDB) {
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var tmpResult = {}
|
||||
let curReq = obs.openCursor();
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (cursor) {
|
||||
tmpResult[cursor.primaryKey] = cursor.value;
|
||||
cursor.continue();
|
||||
} else
|
||||
resolve(tmpResult);
|
||||
}
|
||||
curReq.onerror = (evt) => reject(
|
||||
`Read-All data unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`
|
||||
);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/* compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let keyRange;
|
||||
if(options.lowerKey!==null && options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.bound(options.lowerKey, options.upperKey);
|
||||
else if(options.lowerKey!==null)
|
||||
keyRange = IDBKeyRange.lowerBound(options.lowerKey);
|
||||
else if (options.upperKey!==null)
|
||||
keyRange = IDBKeyRange.upperBound(options.upperBound);
|
||||
else if (options.atKey)
|
||||
let curReq = obs.openCursor(keyRange, )
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
}*/
|
||||
|
||||
compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) {
|
||||
options.lowerKey = options.atKey || options.lowerKey || 0
|
||||
options.upperKey = options.atKey || options.upperKey || false
|
||||
options.patternEval = options.patternEval || ((k, v) => true);
|
||||
options.limit = options.limit || false;
|
||||
options.reverse = options.reverse || false;
|
||||
options.lastOnly = options.lastOnly || false
|
||||
return new Promise((resolve, reject) => {
|
||||
openDB(dbName).then(db => {
|
||||
var obs = db.transaction(obsName, "readonly").objectStore(obsName);
|
||||
var filteredResult = {}
|
||||
let curReq = obs.openCursor(
|
||||
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey),
|
||||
options.lastOnly || options.reverse ? "prev" : "next");
|
||||
curReq.onsuccess = (evt) => {
|
||||
var cursor = evt.target.result;
|
||||
if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length))
|
||||
return resolve(filteredResult); //reached end of key list or limit reached
|
||||
else if (options.patternEval(cursor.primaryKey, cursor.value)) {
|
||||
filteredResult[cursor.primaryKey] = cursor.value;
|
||||
options.lastOnly ? resolve(filteredResult) : cursor.continue();
|
||||
} else
|
||||
cursor.continue();
|
||||
}
|
||||
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
|
||||
db.close();
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
})(window.compactIDB = {});
|
||||
9
optimismwallet/scripts/components.js
Normal file
9
optimismwallet/scripts/components.js
Normal file
File diff suppressed because one or more lines are too long
1
optimismwallet/scripts/components.min.js
vendored
Normal file
1
optimismwallet/scripts/components.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
543
optimismwallet/scripts/ethOperator.js
Normal file
543
optimismwallet/scripts/ethOperator.js
Normal file
@ -0,0 +1,543 @@
|
||||
(function (EXPORTS) { // ethOperator v1.0.2
|
||||
/* ETH Crypto and API Operator */
|
||||
if (!window.ethers)
|
||||
return console.error('ethers.js not found')
|
||||
const ethOperator = EXPORTS;
|
||||
const isValidAddress = ethOperator.isValidAddress = (address) => {
|
||||
try {
|
||||
// Check if the address is a valid checksum address
|
||||
const isValidChecksum = ethers.utils.isAddress(address);
|
||||
// Check if the address is a valid non-checksum address
|
||||
const isValidNonChecksum = ethers.utils.getAddress(address) === address.toLowerCase();
|
||||
return isValidChecksum || isValidNonChecksum;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const ERC20ABI = [
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
const CONTRACT_ADDRESSES = {
|
||||
// Optimism network token addresses
|
||||
usdc: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", // USDC on Optimism
|
||||
usdt: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", // USDT on Optimism
|
||||
weth: "0x4200000000000000000000000000000000000006" // Wrapped ETH on Optimism
|
||||
}
|
||||
/**
|
||||
* Get Optimism provider (MetaMask or public RPC)
|
||||
* @param {boolean} readOnly - If true, use public RPC; if false, use MetaMask when available
|
||||
* @returns {ethers.providers.Provider} Optimism provider instance
|
||||
*/
|
||||
const getProvider = ethOperator.getProvider = (readOnly = false) => {
|
||||
if (!readOnly && window.ethereum) {
|
||||
return new ethers.providers.Web3Provider(window.ethereum);
|
||||
} else {
|
||||
return new ethers.providers.JsonRpcProvider(`https://mainnet.optimism.io`)
|
||||
}
|
||||
}
|
||||
// Note: MetaMask connection is handled in the UI layer, not here
|
||||
const getBalance = ethOperator.getBalance = async (address) => {
|
||||
try {
|
||||
if (!address || !isValidAddress(address))
|
||||
return new Error('Invalid address');
|
||||
|
||||
// Use read-only provider (public RPC) for balance checks
|
||||
const provider = getProvider(true);
|
||||
const balanceWei = await provider.getBalance(address);
|
||||
const balanceEth = parseFloat(ethers.utils.formatEther(balanceWei));
|
||||
return balanceEth;
|
||||
} catch (error) {
|
||||
console.error('Balance error:', error.message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
const getTokenBalance = ethOperator.getTokenBalance = async (address, token, { contractAddress } = {}) => {
|
||||
try {
|
||||
if (!token)
|
||||
return new Error("Token not specified");
|
||||
if (!CONTRACT_ADDRESSES[token] && !contractAddress)
|
||||
return new Error('Contract address of token not available')
|
||||
|
||||
// Use read-only provider (public RPC) for token balance checks
|
||||
const provider = getProvider(true);
|
||||
const tokenAddress = CONTRACT_ADDRESSES[token] || contractAddress;
|
||||
const tokenContract = new ethers.Contract(tokenAddress, ERC20ABI, provider);
|
||||
let balance = await tokenContract.balanceOf(address);
|
||||
|
||||
// WETH uses 18 decimals (like native ETH), USDC and USDT use 6 decimals
|
||||
const decimals = token === 'weth' ? 18 : 6;
|
||||
balance = parseFloat(ethers.utils.formatUnits(balance, decimals));
|
||||
return balance;
|
||||
} catch (e) {
|
||||
console.error('Token balance error:', e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const estimateGas = ethOperator.estimateGas = async ({ privateKey, receiver, amount }) => {
|
||||
try {
|
||||
const provider = getProvider();
|
||||
const signer = new ethers.Wallet(privateKey, provider);
|
||||
return provider.estimateGas({
|
||||
from: signer.address,
|
||||
to: receiver,
|
||||
value: ethers.utils.parseUnits(amount, "ether"),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
const sendTransaction = ethOperator.sendTransaction = async ({ privateKey, receiver, amount }) => {
|
||||
try {
|
||||
const provider = getProvider();
|
||||
const signer = new ethers.Wallet(privateKey, provider);
|
||||
const limit = await estimateGas({ privateKey, receiver, amount })
|
||||
|
||||
// Get current fee data from the network
|
||||
const block = await provider.getBlock("latest");
|
||||
|
||||
// Use actual base fee from block
|
||||
const baseFee = block.baseFeePerGas || ethers.utils.parseUnits("0.00001", "gwei");
|
||||
|
||||
// On Optimism, priority fee is typically very small
|
||||
const priorityFee = ethers.utils.parseUnits("0.00001", "gwei");
|
||||
|
||||
// Calculate max fee (2x base fee + priority for safety margin)
|
||||
const maxFee = baseFee.mul(2).add(priorityFee);
|
||||
|
||||
// Creating and sending the transaction object
|
||||
return signer.sendTransaction({
|
||||
to: receiver,
|
||||
value: ethers.utils.parseUnits(amount, "ether"),
|
||||
gasLimit: limit,
|
||||
nonce: await signer.getTransactionCount(),
|
||||
maxPriorityFeePerGas: priorityFee,
|
||||
maxFeePerGas: maxFee,
|
||||
})
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send ERC20 tokens (USDC, USDT, or WETH)
|
||||
* @param {object} params - Transaction parameters
|
||||
* @param {string} params.token - Token symbol ('usdc', 'usdt', or 'weth')
|
||||
* @param {string} params.privateKey - Sender's private key
|
||||
* @param {string} params.amount - Amount to send
|
||||
* @param {string} params.receiver - Recipient's Optimism address
|
||||
* @param {string} params.contractAddress - Optional custom contract address
|
||||
* @returns {Promise} Transaction promise
|
||||
*/
|
||||
const sendToken = ethOperator.sendToken = async ({ token, privateKey, amount, receiver, contractAddress }) => {
|
||||
const wallet = new ethers.Wallet(privateKey, getProvider());
|
||||
const tokenContract = new ethers.Contract(CONTRACT_ADDRESSES[token] || contractAddress, ERC20ABI, wallet);
|
||||
// Convert amount to smallest unit: WETH uses 18 decimals, USDC and USDT use 6 decimals
|
||||
const decimals = token === 'weth' ? 18 : 6;
|
||||
const amountWei = ethers.utils.parseUnits(amount.toString(), decimals);
|
||||
return tokenContract.transfer(receiver, amountWei)
|
||||
}
|
||||
|
||||
|
||||
const MORALIS_API_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub25jZSI6IjNmMjE5NjM5LTQwYmYtNDhkMC1hNDMxLTI5YjA4YzhlYzE5MiIsIm9yZ0lkIjoiNDkwNTU1IiwidXNlcklkIjoiNTA0NzE5IiwidHlwZUlkIjoiYWNiMjQzOWUtMDEzYy00YjhjLWI2N2MtNjRlNGNhMjA4YTlkIiwidHlwZSI6IlBST0pFQ1QiLCJpYXQiOjE3Njg1MDcyNTIsImV4cCI6NDkyNDI2NzI1Mn0.X4Hn3VxLVRJL6HlAGPFQdWvQAdTXO20_Z8CpWhNt5CE';
|
||||
|
||||
/**
|
||||
* Get transaction history for an Optimism address using Moralis API
|
||||
* @param {string} address - Optimism address
|
||||
* @param {object} options - Optional parameters
|
||||
* @returns {Promise<Array>} Array of transactions
|
||||
*/
|
||||
const getTransactionHistory = ethOperator.getTransactionHistory = async (address, options = {}) => {
|
||||
try {
|
||||
if (!address || !isValidAddress(address)) {
|
||||
throw new Error('Invalid Optimism address');
|
||||
}
|
||||
|
||||
const {
|
||||
page = 1,
|
||||
offset = 100,
|
||||
} = options;
|
||||
|
||||
// Moralis API endpoint for Optimism
|
||||
const chain = '0xa'; // Optimism chain ID in hex
|
||||
|
||||
// Fetch transactions using Moralis API
|
||||
const moralisUrl = `https://deep-index.moralis.io/api/v2.2/${address}?chain=${chain}&limit=${offset}`;
|
||||
|
||||
const response = await fetch(moralisUrl, {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-API-Key': MORALIS_API_KEY
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(`Moralis API Error: ${errorData.message || response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.result || data.result.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Parse and format transactions from Moralis response
|
||||
return data.result.map(tx => {
|
||||
const isReceived = tx.to_address && tx.to_address.toLowerCase() === address.toLowerCase();
|
||||
const value = parseFloat(ethers.utils.formatEther(tx.value || '0'));
|
||||
|
||||
return {
|
||||
hash: tx.hash,
|
||||
from: tx.from_address,
|
||||
to: tx.to_address,
|
||||
value: value,
|
||||
symbol: 'ETH',
|
||||
timestamp: new Date(tx.block_timestamp).getTime() / 1000,
|
||||
blockNumber: parseInt(tx.block_number),
|
||||
isReceived: isReceived,
|
||||
isSent: !isReceived,
|
||||
gasUsed: tx.receipt_gas_used ? parseInt(tx.receipt_gas_used) : 0,
|
||||
gasPrice: tx.gas_price ? parseFloat(ethers.utils.formatUnits(tx.gas_price, 'gwei')) : 0,
|
||||
isError: tx.receipt_status === '0',
|
||||
contractAddress: tx.to_address && tx.input !== '0x' ? tx.to_address : null,
|
||||
tokenName: null,
|
||||
confirmations: 0,
|
||||
nonce: tx.nonce ? parseInt(tx.nonce) : 0,
|
||||
input: tx.input || '0x',
|
||||
isTokenTransfer: false
|
||||
};
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching transaction history:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get detailed information about a specific transaction
|
||||
* @param {string} txHash - Transaction hash
|
||||
* @returns {Promise<Object>} Transaction details
|
||||
*/
|
||||
const getTransactionDetails = ethOperator.getTransactionDetails = async (txHash) => {
|
||||
try {
|
||||
if (!txHash || !/^0x([A-Fa-f0-9]{64})$/.test(txHash)) {
|
||||
throw new Error('Invalid transaction hash');
|
||||
}
|
||||
|
||||
// Use read-only provider for fetching transaction details
|
||||
const provider = getProvider(true);
|
||||
|
||||
// Get transaction details
|
||||
const tx = await provider.getTransaction(txHash);
|
||||
|
||||
if (!tx) {
|
||||
throw new Error('Transaction not found');
|
||||
}
|
||||
|
||||
// Get transaction receipt for status and gas used
|
||||
const receipt = await provider.getTransactionReceipt(txHash);
|
||||
|
||||
// Get current block number for confirmations
|
||||
const currentBlock = await provider.getBlockNumber();
|
||||
|
||||
// Get block details for timestamp
|
||||
const block = await provider.getBlock(tx.blockNumber);
|
||||
|
||||
// Calculate gas fee
|
||||
const gasUsed = receipt ? receipt.gasUsed : null;
|
||||
const effectiveGasPrice = receipt ? receipt.effectiveGasPrice : tx.gasPrice;
|
||||
const gasFee = gasUsed && effectiveGasPrice ?
|
||||
parseFloat(ethers.utils.formatEther(gasUsed.mul(effectiveGasPrice))) : null;
|
||||
|
||||
// Check if it's a token transfer by examining logs
|
||||
let tokenTransfer = null;
|
||||
if (receipt && receipt.logs.length > 0) {
|
||||
// Try to decode ERC20 Transfer event
|
||||
const transferEventSignature = ethers.utils.id('Transfer(address,address,uint256)');
|
||||
const transferLog = receipt.logs.find(log => log.topics[0] === transferEventSignature);
|
||||
|
||||
if (transferLog) {
|
||||
try {
|
||||
const tokenContract = new ethers.Contract(transferLog.address, ERC20ABI, provider);
|
||||
const [symbol, decimals] = await Promise.all([
|
||||
tokenContract.symbol().catch(() => 'TOKEN'),
|
||||
tokenContract.decimals().catch(() => 18)
|
||||
]);
|
||||
|
||||
const from = ethers.utils.getAddress('0x' + transferLog.topics[1].slice(26));
|
||||
const to = ethers.utils.getAddress('0x' + transferLog.topics[2].slice(26));
|
||||
const value = parseFloat(ethers.utils.formatUnits(transferLog.data, decimals));
|
||||
|
||||
tokenTransfer = {
|
||||
from,
|
||||
to,
|
||||
value,
|
||||
symbol,
|
||||
contractAddress: transferLog.address
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn('Could not decode token transfer:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hash: tx.hash,
|
||||
from: tx.from,
|
||||
to: tx.to,
|
||||
value: parseFloat(ethers.utils.formatEther(tx.value)),
|
||||
symbol: 'ETH',
|
||||
blockNumber: tx.blockNumber,
|
||||
timestamp: block ? block.timestamp : null,
|
||||
confirmations: currentBlock - tx.blockNumber,
|
||||
gasLimit: tx.gasLimit.toString(),
|
||||
gasUsed: gasUsed ? gasUsed.toString() : null,
|
||||
gasPrice: parseFloat(ethers.utils.formatUnits(tx.gasPrice, 'gwei')),
|
||||
gasFee: gasFee,
|
||||
nonce: tx.nonce,
|
||||
input: tx.data,
|
||||
status: receipt ? (receipt.status === 1 ? 'success' : 'failed') : 'pending',
|
||||
isError: receipt ? receipt.status !== 1 : false,
|
||||
tokenTransfer: tokenTransfer,
|
||||
logs: receipt ? receipt.logs : [],
|
||||
type: tx.type
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching transaction details:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a string is a valid transaction hash
|
||||
* @param {string} hash - Potential transaction hash
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isValidTxHash = ethOperator.isValidTxHash = (hash) => {
|
||||
return /^0x([A-Fa-f0-9]{64})$/.test(hash);
|
||||
};
|
||||
|
||||
})('object' === typeof module ? module.exports : window.ethOperator = {});
|
||||
1
optimismwallet/scripts/ether.umd.min.js
vendored
Normal file
1
optimismwallet/scripts/ether.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
530
optimismwallet/scripts/floCrypto.js
Normal file
530
optimismwallet/scripts/floCrypto.js
Normal file
@ -0,0 +1,530 @@
|
||||
(function (EXPORTS) { //floCrypto v2.3.6a
|
||||
/* FLO Crypto Operators */
|
||||
'use strict';
|
||||
const floCrypto = EXPORTS;
|
||||
|
||||
const p = BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
|
||||
const ecparams = EllipticCurve.getSECCurveByName("secp256k1");
|
||||
const ascii_alternatives = `‘ '\n’ '\n“ "\n” "\n– --\n— ---\n≥ >=\n≤ <=\n≠ !=\n× *\n÷ /\n← <-\n→ ->\n↔ <->\n⇒ =>\n⇐ <=\n⇔ <=>`;
|
||||
const exponent1 = () => p.add(BigInteger.ONE).divide(BigInteger("4"));
|
||||
coinjs.compressed = true; //defaulting coinjs compressed to true;
|
||||
|
||||
function calculateY(x) {
|
||||
let exp = exponent1();
|
||||
// x is x value of public key in BigInteger format without 02 or 03 or 04 prefix
|
||||
return x.modPow(BigInteger("3"), p).add(BigInteger("7")).mod(p).modPow(exp, p)
|
||||
}
|
||||
|
||||
function getUncompressedPublicKey(compressedPublicKey) {
|
||||
// Fetch x from compressedPublicKey
|
||||
let pubKeyBytes = Crypto.util.hexToBytes(compressedPublicKey);
|
||||
const prefix = pubKeyBytes.shift() // remove prefix
|
||||
let prefix_modulus = prefix % 2;
|
||||
pubKeyBytes.unshift(0) // add prefix 0
|
||||
let x = new BigInteger(pubKeyBytes)
|
||||
let xDecimalValue = x.toString()
|
||||
// Fetch y
|
||||
let y = calculateY(x);
|
||||
let yDecimalValue = y.toString();
|
||||
// verify y value
|
||||
let resultBigInt = y.mod(BigInteger("2"));
|
||||
let check = resultBigInt.toString() % 2;
|
||||
if (prefix_modulus !== check)
|
||||
yDecimalValue = y.negate().mod(p).toString();
|
||||
return {
|
||||
x: xDecimalValue,
|
||||
y: yDecimalValue
|
||||
};
|
||||
}
|
||||
|
||||
function getSenderPublicKeyString() {
|
||||
let privateKey = ellipticCurveEncryption.senderRandom();
|
||||
var senderPublicKeyString = ellipticCurveEncryption.senderPublicString(privateKey);
|
||||
return {
|
||||
privateKey: privateKey,
|
||||
senderPublicKeyString: senderPublicKeyString
|
||||
}
|
||||
}
|
||||
|
||||
function deriveSharedKeySender(receiverPublicKeyHex, senderPrivateKey) {
|
||||
let receiverPublicKeyString = getUncompressedPublicKey(receiverPublicKeyHex);
|
||||
var senderDerivedKey = ellipticCurveEncryption.senderSharedKeyDerivation(
|
||||
receiverPublicKeyString.x, receiverPublicKeyString.y, senderPrivateKey);
|
||||
return senderDerivedKey;
|
||||
}
|
||||
|
||||
function deriveSharedKeyReceiver(senderPublicKeyString, receiverPrivateKey) {
|
||||
return ellipticCurveEncryption.receiverSharedKeyDerivation(
|
||||
senderPublicKeyString.XValuePublicString, senderPublicKeyString.YValuePublicString, receiverPrivateKey);
|
||||
}
|
||||
|
||||
function getReceiverPublicKeyString(privateKey) {
|
||||
return ellipticCurveEncryption.receiverPublicString(privateKey);
|
||||
}
|
||||
|
||||
function wifToDecimal(pk_wif, isPubKeyCompressed = false) {
|
||||
let pk = Bitcoin.Base58.decode(pk_wif)
|
||||
pk.shift()
|
||||
pk.splice(-4, 4)
|
||||
//If the private key corresponded to a compressed public key, also drop the last byte (it should be 0x01).
|
||||
if (isPubKeyCompressed == true) pk.pop()
|
||||
pk.unshift(0)
|
||||
let privateKeyDecimal = BigInteger(pk).toString()
|
||||
let privateKeyHex = Crypto.util.bytesToHex(pk)
|
||||
return {
|
||||
privateKeyDecimal: privateKeyDecimal,
|
||||
privateKeyHex: privateKeyHex
|
||||
}
|
||||
}
|
||||
|
||||
//generate a random Interger within range
|
||||
floCrypto.randInt = function (min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(securedMathRandom() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
//generate a random String within length (options : alphaNumeric chars only)
|
||||
floCrypto.randString = function (length, alphaNumeric = true) {
|
||||
var result = '';
|
||||
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
|
||||
for (var i = 0; i < length; i++)
|
||||
result += characters.charAt(Math.floor(securedMathRandom() * characters.length));
|
||||
return result;
|
||||
}
|
||||
|
||||
//Encrypt Data using public-key
|
||||
floCrypto.encryptData = function (data, receiverPublicKeyHex) {
|
||||
var senderECKeyData = getSenderPublicKeyString();
|
||||
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
|
||||
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
|
||||
let secret = Crypto.AES.encrypt(data, senderKey);
|
||||
return {
|
||||
secret: secret,
|
||||
senderPublicKeyString: senderECKeyData.senderPublicKeyString
|
||||
};
|
||||
}
|
||||
|
||||
//Decrypt Data using private-key
|
||||
floCrypto.decryptData = function (data, privateKeyHex) {
|
||||
var receiverECKeyData = {};
|
||||
if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
|
||||
let privateKey = wifToDecimal(privateKeyHex, true);
|
||||
if (typeof privateKey.privateKeyDecimal !== "string") throw new Error("Failed to detremine your private key.");
|
||||
receiverECKeyData.privateKey = privateKey.privateKeyDecimal;
|
||||
var receiverDerivedKey = deriveSharedKeyReceiver(data.senderPublicKeyString, receiverECKeyData.privateKey);
|
||||
let receiverKey = receiverDerivedKey.XValue + receiverDerivedKey.YValue;
|
||||
let decryptMsg = Crypto.AES.decrypt(data.secret, receiverKey);
|
||||
return decryptMsg;
|
||||
}
|
||||
|
||||
//Sign data using private-key
|
||||
floCrypto.signData = function (data, privateKeyHex) {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
var messageHash = Crypto.SHA256(data);
|
||||
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
|
||||
var sighex = Crypto.util.bytesToHex(messageSign);
|
||||
return sighex;
|
||||
}
|
||||
|
||||
//Verify signatue of the data using public-key
|
||||
floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
|
||||
var msgHash = Crypto.SHA256(data);
|
||||
var sigBytes = Crypto.util.hexToBytes(signatureHex);
|
||||
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
|
||||
var verify = Bitcoin.ECDSA.verify(msgHash, sigBytes, publicKeyPoint);
|
||||
return verify;
|
||||
}
|
||||
|
||||
//Generates a new flo ID and returns private-key, public-key and floID
|
||||
const generateNewID = floCrypto.generateNewID = function () {
|
||||
var key = new Bitcoin.ECKey(false);
|
||||
key.setCompressed(true);
|
||||
return {
|
||||
floID: key.getBitcoinAddress(),
|
||||
pubKey: key.getPubKeyHex(),
|
||||
privKey: key.getBitcoinWalletImportFormat()
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(floCrypto, {
|
||||
newID: {
|
||||
get: () => generateNewID()
|
||||
},
|
||||
hashID: {
|
||||
value: (str) => {
|
||||
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
},
|
||||
tmpID: {
|
||||
get: () => {
|
||||
let bytes = Crypto.util.randomBytes(20);
|
||||
bytes.unshift(bitjs.pub);
|
||||
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
var checksum = hash.slice(0, 4);
|
||||
return bitjs.Base58.encode(bytes.concat(checksum));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Returns public-key from private-key
|
||||
floCrypto.getPubKeyHex = function (privateKeyHex) {
|
||||
if (!privateKeyHex)
|
||||
return null;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
return key.getPubKeyHex();
|
||||
}
|
||||
|
||||
//Returns flo-ID from public-key or private-key
|
||||
floCrypto.getFloID = function (keyHex) {
|
||||
if (!keyHex)
|
||||
return null;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(keyHex);
|
||||
if (key.priv == null)
|
||||
key.setPub(keyHex);
|
||||
return key.getBitcoinAddress();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getAddress = function (privateKeyHex, strict = false) {
|
||||
if (!privateKeyHex)
|
||||
return;
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return null;
|
||||
key.setCompressed(true);
|
||||
let pubKey = key.getPubKeyHex(),
|
||||
version = bitjs.Base58.decode(privateKeyHex)[0];
|
||||
switch (version) {
|
||||
case coinjs.priv: //BTC
|
||||
return coinjs.bech32Address(pubKey).address;
|
||||
case bitjs.priv: //FLO
|
||||
return bitjs.pubkey2address(pubKey);
|
||||
default:
|
||||
return strict ? false : bitjs.pubkey2address(pubKey); //default to FLO address (if strict=false)
|
||||
}
|
||||
}
|
||||
|
||||
//Verify the private-key for the given public-key or flo-ID
|
||||
floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
|
||||
if (!privateKeyHex || !pubKey_floID)
|
||||
return false;
|
||||
try {
|
||||
var key = new Bitcoin.ECKey(privateKeyHex);
|
||||
if (key.priv == null)
|
||||
return false;
|
||||
key.setCompressed(true);
|
||||
if (isfloID && pubKey_floID == key.getBitcoinAddress())
|
||||
return true;
|
||||
else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
|
||||
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
|
||||
return null;
|
||||
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
|
||||
return null;
|
||||
try {
|
||||
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
|
||||
return multisig;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.decodeRedeemScript = function (redeemScript) {
|
||||
try {
|
||||
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
|
||||
return decoded;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given flo-id is valid or not
|
||||
floCrypto.validateFloID = function (floID, regularOnly = false) {
|
||||
if (!floID)
|
||||
return false;
|
||||
try {
|
||||
let addr = new Bitcoin.Address(floID);
|
||||
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
|
||||
return false;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Check if the given address (any blockchain) is valid or not
|
||||
floCrypto.validateAddr = function (address, std = true, bech = true) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return false;
|
||||
if (typeof raw.version !== 'undefined') { //legacy or segwit
|
||||
if (std == false)
|
||||
return false;
|
||||
else if (std === true || (!Array.isArray(std) && std === raw.version) || (Array.isArray(std) && std.includes(raw.version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else if (typeof raw.bech_version !== 'undefined') { //bech32
|
||||
if (bech === false)
|
||||
return false;
|
||||
else if (bech === true || (!Array.isArray(bech) && bech === raw.bech_version) || (Array.isArray(bech) && bech.includes(raw.bech_version)))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
} else //unknown
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check the public-key (or redeem-script) for the address (any blockchain)
|
||||
floCrypto.verifyPubKey = function (pubKeyHex, address) {
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
|
||||
if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
|
||||
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
|
||||
return pub_hash === raw.hex;
|
||||
}
|
||||
|
||||
//Convert the given address (any blockchain) to equivalent floID
|
||||
floCrypto.toFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
raw.bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Convert raw address bytes to floID
|
||||
floCrypto.rawToFloID = function (raw_bytes) {
|
||||
if (typeof raw_bytes === 'string')
|
||||
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
|
||||
if (raw_bytes.length != 20)
|
||||
return null;
|
||||
raw_bytes.unshift(bitjs.pub);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Convert the given multisig address (any blockchain) to equivalent multisig floID
|
||||
floCrypto.toMultisigFloID = function (address, options = null) {
|
||||
if (!address)
|
||||
return;
|
||||
let raw = decodeAddress(address);
|
||||
if (!raw)
|
||||
return;
|
||||
else if (options) { //if (optional) version check is passed
|
||||
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
|
||||
return;
|
||||
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
|
||||
return;
|
||||
}
|
||||
if (typeof raw.bech_version !== 'undefined') {
|
||||
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
|
||||
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
|
||||
raw.bytes = ripemd160(raw.bytes, {
|
||||
asBytes: true
|
||||
});
|
||||
}
|
||||
raw.bytes.unshift(bitjs.multisig);
|
||||
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
|
||||
}
|
||||
|
||||
//Checks if the given addresses (any blockchain) are same (w.r.t keys)
|
||||
floCrypto.isSameAddr = function (addr1, addr2) {
|
||||
if (!addr1 || !addr2)
|
||||
return;
|
||||
let raw1 = decodeAddress(addr1),
|
||||
raw2 = decodeAddress(addr2);
|
||||
if (!raw1 || !raw2)
|
||||
return false;
|
||||
else {
|
||||
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
|
||||
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
|
||||
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
|
||||
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
|
||||
return raw1.hex === raw2.hex;
|
||||
}
|
||||
}
|
||||
|
||||
const decodeAddress = floCrypto.decodeAddr = function (address) {
|
||||
if (!address)
|
||||
return;
|
||||
else if (address.length == 33 || address.length == 34) { //legacy encoding
|
||||
let decode = bitjs.Base58.decode(address);
|
||||
let bytes = decode.slice(0, decode.length - 4);
|
||||
let checksum = decode.slice(decode.length - 4),
|
||||
hash = Crypto.SHA256(Crypto.SHA256(bytes, {
|
||||
asBytes: true
|
||||
}), {
|
||||
asBytes: true
|
||||
});
|
||||
return (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) ? null : {
|
||||
version: bytes.shift(),
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else if (address.length == 42 || address.length == 62) { //bech encoding
|
||||
let decode = coinjs.bech32_decode(address);
|
||||
if (decode) {
|
||||
let bytes = decode.data;
|
||||
let bech_version = bytes.shift();
|
||||
bytes = coinjs.bech32_convert(bytes, 5, 8, false);
|
||||
return {
|
||||
bech_version,
|
||||
hrp: decode.hrp,
|
||||
hex: Crypto.util.bytesToHex(bytes),
|
||||
bytes
|
||||
}
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//Split the str using shamir's Secret and Returns the shares
|
||||
floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
|
||||
try {
|
||||
if (str.length > 0) {
|
||||
var strHex = shamirSecretShare.str2hex(str);
|
||||
var shares = shamirSecretShare.share(strHex, total_shares, threshold_limit);
|
||||
return shares;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//Returns the retrived secret by combining the shamirs shares
|
||||
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
|
||||
try {
|
||||
if (sharesArray.length > 0) {
|
||||
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
|
||||
comb = shamirSecretShare.hex2str(comb);
|
||||
return comb;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Verifies the shares and str
|
||||
floCrypto.verifyShamirsSecret = function (sharesArray, str) {
|
||||
if (!str)
|
||||
return null;
|
||||
else if (retrieveShamirSecret(sharesArray) === str)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
|
||||
if (typeof string !== "string")
|
||||
return null;
|
||||
if (bool) {
|
||||
let x;
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
let x, invalids = {};
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
x = string.charCodeAt(i);
|
||||
if (x < 32 || x > 127)
|
||||
if (x in invalids)
|
||||
invalids[string[i]].push(i)
|
||||
else
|
||||
invalids[string[i]] = [i];
|
||||
}
|
||||
if (Object.keys(invalids).length)
|
||||
return invalids;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
|
||||
let chars = validateASCII(string, false);
|
||||
if (chars === true)
|
||||
return string;
|
||||
else if (chars === null)
|
||||
return null;
|
||||
let convertor, result = string,
|
||||
refAlt = {};
|
||||
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
|
||||
mode = mode.toLowerCase();
|
||||
if (mode === "hard-unicode")
|
||||
convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "soft-unicode")
|
||||
convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
|
||||
else if (mode === "hard-remove")
|
||||
convertor = c => "";
|
||||
else if (mode === "soft-remove")
|
||||
convertor = c => refAlt[c] || "";
|
||||
else
|
||||
return null;
|
||||
for (let c in chars)
|
||||
result = result.replaceAll(c, convertor(c));
|
||||
return result;
|
||||
}
|
||||
|
||||
floCrypto.revertUnicode = function (string) {
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
|
||||
}
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floCrypto = {});
|
||||
57
optimismwallet/scripts/floEthereum.js
Normal file
57
optimismwallet/scripts/floEthereum.js
Normal file
@ -0,0 +1,57 @@
|
||||
(function (EXPORTS) { //floEthereum v1.0.1a
|
||||
/* FLO Ethereum Operators */
|
||||
/* Make sure you added Taproot, Keccak, FLO and BTC Libraries before */
|
||||
'use strict';
|
||||
const floEthereum = EXPORTS;
|
||||
|
||||
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);
|
||||
t1_x = t1.slice(2, 66); t1_y = t1.slice(-64);
|
||||
if (onlyEvenY) {
|
||||
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) {
|
||||
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 ethPrivateKeyFromUntweakedPrivateKey = floEthereum.ethPrivateKeyFromUntweakedPrivateKey = function (untweakedPrivateKey) {
|
||||
var t1;
|
||||
t1 = hex.encode(taproot.taprootTweakPrivKey(hex.decode(untweakedPrivateKey)));
|
||||
return t1;
|
||||
}
|
||||
|
||||
const ethAddressFromUntweakedPrivateKey = floEthereum.ethAddressFromUntweakedPrivateKey = function (untweakedPrivateKey) {
|
||||
var t1, t2;
|
||||
t1 = hex.encode(taproot.taprootTweakPrivKey(hex.decode(untweakedPrivateKey)));
|
||||
t2 = ethAddressFromPrivateKey(t1);
|
||||
return t2;
|
||||
}
|
||||
|
||||
const ethAddressFromTaprootAddress = floEthereum.ethAddressFromTaprootAddress = function (taprootAddress) {
|
||||
var t1, t2, t3, t4;
|
||||
t1 = coinjs.addressDecode(taprootAddress);
|
||||
t2 = t1.outstring.slice(4);
|
||||
t3 = "02" + t2;
|
||||
t4 = ethAddressFromCompressedPublicKey(t3);
|
||||
return t4;
|
||||
}
|
||||
|
||||
|
||||
|
||||
})('object' === typeof module ? module.exports : window.floEthereum = {});
|
||||
674
optimismwallet/scripts/keccak.js
Normal file
674
optimismwallet/scripts/keccak.js
Normal file
@ -0,0 +1,674 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var INPUT_ERROR = 'input is invalid type';
|
||||
var FINALIZE_ERROR = 'finalize already called';
|
||||
var WINDOW = typeof window === 'object';
|
||||
var root = WINDOW ? (window.keccak = window.keccak || {}) : {};
|
||||
if (root.JS_SHA3_NO_WINDOW) {
|
||||
WINDOW = false;
|
||||
}
|
||||
var WEB_WORKER = !WINDOW && typeof self === 'object';
|
||||
var NODE_JS = !root.JS_SHA3_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;
|
||||
if (NODE_JS) {
|
||||
root = global;
|
||||
} else if (WEB_WORKER) {
|
||||
root = self;
|
||||
}
|
||||
var COMMON_JS = !root.JS_SHA3_NO_COMMON_JS && typeof module === 'object' && module.exports;
|
||||
var AMD = typeof define === 'function' && define.amd;
|
||||
var ARRAY_BUFFER = !root.JS_SHA3_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
|
||||
var HEX_CHARS = '0123456789abcdef'.split('');
|
||||
var SHAKE_PADDING = [31, 7936, 2031616, 520093696];
|
||||
var CSHAKE_PADDING = [4, 1024, 262144, 67108864];
|
||||
var KECCAK_PADDING = [1, 256, 65536, 16777216];
|
||||
var PADDING = [6, 1536, 393216, 100663296];
|
||||
var SHIFT = [0, 8, 16, 24];
|
||||
var RC = [1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
||||
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
||||
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
||||
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
||||
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648];
|
||||
var BITS = [224, 256, 384, 512];
|
||||
var SHAKE_BITS = [128, 256];
|
||||
var OUTPUT_TYPES = ['hex', 'buffer', 'arrayBuffer', 'array', 'digest'];
|
||||
var CSHAKE_BYTEPAD = {
|
||||
'128': 168,
|
||||
'256': 136
|
||||
};
|
||||
|
||||
|
||||
var isArray = root.JS_SHA3_NO_NODE_JS || !Array.isArray
|
||||
? function (obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Array]';
|
||||
}
|
||||
: Array.isArray;
|
||||
|
||||
var isView = (ARRAY_BUFFER && (root.JS_SHA3_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView))
|
||||
? function (obj) {
|
||||
return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
|
||||
}
|
||||
: ArrayBuffer.isView;
|
||||
|
||||
// [message: string, isString: bool]
|
||||
var formatMessage = function (message) {
|
||||
var type = typeof message;
|
||||
if (type === 'string') {
|
||||
return [message, true];
|
||||
}
|
||||
if (type !== 'object' || message === null) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
|
||||
return [new Uint8Array(message), false];
|
||||
}
|
||||
if (!isArray(message) && !isView(message)) {
|
||||
throw new Error(INPUT_ERROR);
|
||||
}
|
||||
return [message, false];
|
||||
}
|
||||
|
||||
var empty = function (message) {
|
||||
return formatMessage(message)[0].length === 0;
|
||||
};
|
||||
|
||||
var createOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message) {
|
||||
return new Keccak(bits, padding, bits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createShakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits) {
|
||||
return new Keccak(bits, padding, outputBits).update(message)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createCshakeOutputMethod = function (bits, padding, outputType) {
|
||||
return function (message, outputBits, n, s) {
|
||||
return methods['cshake' + bits].update(message, outputBits, n, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createKmacOutputMethod = function (bits, padding, outputType) {
|
||||
return function (key, message, outputBits, s) {
|
||||
return methods['kmac' + bits].update(key, message, outputBits, s)[outputType]();
|
||||
};
|
||||
};
|
||||
|
||||
var createOutputMethods = function (method, createMethod, bits, padding) {
|
||||
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
|
||||
var type = OUTPUT_TYPES[i];
|
||||
method[type] = createMethod(bits, padding, type);
|
||||
}
|
||||
return method;
|
||||
};
|
||||
|
||||
var createMethod = function (bits, padding) {
|
||||
var method = createOutputMethod(bits, padding, 'hex');
|
||||
method.create = function () {
|
||||
return new Keccak(bits, padding, bits);
|
||||
};
|
||||
method.update = function (message) {
|
||||
return method.create().update(message);
|
||||
};
|
||||
return createOutputMethods(method, createOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createShakeMethod = function (bits, padding) {
|
||||
var method = createShakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits) {
|
||||
return new Keccak(bits, padding, outputBits);
|
||||
};
|
||||
method.update = function (message, outputBits) {
|
||||
return method.create(outputBits).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createShakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createCshakeMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createCshakeOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (outputBits, n, s) {
|
||||
if (empty(n) && empty(s)) {
|
||||
return methods['shake' + bits].create(outputBits);
|
||||
} else {
|
||||
return new Keccak(bits, padding, outputBits).bytepad([n, s], w);
|
||||
}
|
||||
};
|
||||
method.update = function (message, outputBits, n, s) {
|
||||
return method.create(outputBits, n, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createCshakeOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var createKmacMethod = function (bits, padding) {
|
||||
var w = CSHAKE_BYTEPAD[bits];
|
||||
var method = createKmacOutputMethod(bits, padding, 'hex');
|
||||
method.create = function (key, outputBits, s) {
|
||||
return new Kmac(bits, padding, outputBits).bytepad(['KMAC', s], w).bytepad([key], w);
|
||||
};
|
||||
method.update = function (key, message, outputBits, s) {
|
||||
return method.create(key, outputBits, s).update(message);
|
||||
};
|
||||
return createOutputMethods(method, createKmacOutputMethod, bits, padding);
|
||||
};
|
||||
|
||||
var algorithms = [
|
||||
{ name: 'keccak', padding: KECCAK_PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'sha3', padding: PADDING, bits: BITS, createMethod: createMethod },
|
||||
{ name: 'shake', padding: SHAKE_PADDING, bits: SHAKE_BITS, createMethod: createShakeMethod },
|
||||
{ name: 'cshake', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createCshakeMethod },
|
||||
{ name: 'kmac', padding: CSHAKE_PADDING, bits: SHAKE_BITS, createMethod: createKmacMethod }
|
||||
];
|
||||
|
||||
var methods = {}, methodNames = [];
|
||||
|
||||
for (var i = 0; i < algorithms.length; ++i) {
|
||||
var algorithm = algorithms[i];
|
||||
var bits = algorithm.bits;
|
||||
for (var j = 0; j < bits.length; ++j) {
|
||||
var methodName = algorithm.name + '_' + bits[j];
|
||||
methodNames.push(methodName);
|
||||
methods[methodName] = algorithm.createMethod(bits[j], algorithm.padding);
|
||||
if (algorithm.name !== 'sha3') {
|
||||
var newMethodName = algorithm.name + bits[j];
|
||||
methodNames.push(newMethodName);
|
||||
methods[newMethodName] = methods[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
methodNames.push("extractLast20Bytes");
|
||||
methods["extractLast20Bytes"] = extractLast20Bytes;
|
||||
|
||||
|
||||
function Keccak(bits, padding, outputBits) {
|
||||
this.blocks = [];
|
||||
this.s = [];
|
||||
this.padding = padding;
|
||||
this.outputBits = outputBits;
|
||||
this.reset = true;
|
||||
this.finalized = false;
|
||||
this.block = 0;
|
||||
this.start = 0;
|
||||
this.blockCount = (1600 - (bits << 1)) >> 5;
|
||||
this.byteCount = this.blockCount << 2;
|
||||
this.outputBlocks = outputBits >> 5;
|
||||
this.extraBytes = (outputBits & 31) >> 3;
|
||||
|
||||
for (var i = 0; i < 50; ++i) {
|
||||
this.s[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Keccak.prototype.update = function (message) {
|
||||
if (this.finalized) {
|
||||
throw new Error(FINALIZE_ERROR);
|
||||
}
|
||||
var result = formatMessage(message);
|
||||
message = result[0];
|
||||
var isString = result[1];
|
||||
var blocks = this.blocks, byteCount = this.byteCount, length = message.length,
|
||||
blockCount = this.blockCount, index = 0, s = this.s, i, code;
|
||||
|
||||
while (index < length) {
|
||||
if (this.reset) {
|
||||
this.reset = false;
|
||||
blocks[0] = this.block;
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
if (isString) {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
code = message.charCodeAt(index);
|
||||
if (code < 0x80) {
|
||||
blocks[i >> 2] |= code << SHIFT[i++ & 3];
|
||||
} else if (code < 0x800) {
|
||||
blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
|
||||
blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i++ & 3];
|
||||
blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = this.start; index < length && i < byteCount; ++index) {
|
||||
blocks[i >> 2] |= message[index] << SHIFT[i++ & 3];
|
||||
}
|
||||
}
|
||||
this.lastByteIndex = i;
|
||||
if (i >= byteCount) {
|
||||
this.start = i - byteCount;
|
||||
this.block = blocks[blockCount];
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
this.reset = true;
|
||||
} else {
|
||||
this.start = i;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.encode = function (x, right) {
|
||||
var o = x & 255, n = 1;
|
||||
var bytes = [o];
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
while (o > 0) {
|
||||
bytes.unshift(o);
|
||||
x = x >> 8;
|
||||
o = x & 255;
|
||||
++n;
|
||||
}
|
||||
if (right) {
|
||||
bytes.push(n);
|
||||
} else {
|
||||
bytes.unshift(n);
|
||||
}
|
||||
this.update(bytes);
|
||||
return bytes.length;
|
||||
};
|
||||
|
||||
Keccak.prototype.encodeString = function (str) {
|
||||
var result = formatMessage(str);
|
||||
str = result[0];
|
||||
var isString = result[1];
|
||||
var bytes = 0, length = str.length;
|
||||
if (isString) {
|
||||
for (var i = 0; i < str.length; ++i) {
|
||||
var code = str.charCodeAt(i);
|
||||
if (code < 0x80) {
|
||||
bytes += 1;
|
||||
} else if (code < 0x800) {
|
||||
bytes += 2;
|
||||
} else if (code < 0xd800 || code >= 0xe000) {
|
||||
bytes += 3;
|
||||
} else {
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (str.charCodeAt(++i) & 0x3ff));
|
||||
bytes += 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bytes = length;
|
||||
}
|
||||
bytes += this.encode(bytes * 8);
|
||||
this.update(str);
|
||||
return bytes;
|
||||
};
|
||||
|
||||
Keccak.prototype.bytepad = function (strs, w) {
|
||||
var bytes = this.encode(w);
|
||||
for (var i = 0; i < strs.length; ++i) {
|
||||
bytes += this.encodeString(strs[i]);
|
||||
}
|
||||
var paddingBytes = (w - bytes % w) % w;
|
||||
var zeros = [];
|
||||
zeros.length = paddingBytes;
|
||||
this.update(zeros);
|
||||
return this;
|
||||
};
|
||||
|
||||
Keccak.prototype.finalize = function () {
|
||||
if (this.finalized) {
|
||||
return;
|
||||
}
|
||||
this.finalized = true;
|
||||
var blocks = this.blocks, i = this.lastByteIndex, blockCount = this.blockCount, s = this.s;
|
||||
blocks[i >> 2] |= this.padding[i & 3];
|
||||
if (this.lastByteIndex === this.byteCount) {
|
||||
blocks[0] = blocks[blockCount];
|
||||
for (i = 1; i < blockCount + 1; ++i) {
|
||||
blocks[i] = 0;
|
||||
}
|
||||
}
|
||||
blocks[blockCount - 1] |= 0x80000000;
|
||||
for (i = 0; i < blockCount; ++i) {
|
||||
s[i] ^= blocks[i];
|
||||
}
|
||||
f(s);
|
||||
};
|
||||
|
||||
Keccak.prototype.toString = Keccak.prototype.hex = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var hex = '', block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
||||
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
||||
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
||||
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
block = s[i];
|
||||
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F];
|
||||
if (extraBytes > 1) {
|
||||
hex += HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F];
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
hex += HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F];
|
||||
}
|
||||
}
|
||||
return hex;
|
||||
};
|
||||
|
||||
Keccak.prototype.arrayBuffer = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var bytes = this.outputBits >> 3;
|
||||
var buffer;
|
||||
if (extraBytes) {
|
||||
buffer = new ArrayBuffer((outputBlocks + 1) << 2);
|
||||
} else {
|
||||
buffer = new ArrayBuffer(bytes);
|
||||
}
|
||||
var array = new Uint32Array(buffer);
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
array[j] = s[i];
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
array[i] = s[i];
|
||||
buffer = buffer.slice(0, bytes);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
Keccak.prototype.buffer = Keccak.prototype.arrayBuffer;
|
||||
|
||||
Keccak.prototype.digest = Keccak.prototype.array = function () {
|
||||
this.finalize();
|
||||
|
||||
var blockCount = this.blockCount, s = this.s, outputBlocks = this.outputBlocks,
|
||||
extraBytes = this.extraBytes, i = 0, j = 0;
|
||||
var array = [], offset, block;
|
||||
while (j < outputBlocks) {
|
||||
for (i = 0; i < blockCount && j < outputBlocks; ++i, ++j) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
array[offset + 3] = (block >> 24) & 0xFF;
|
||||
}
|
||||
if (j % blockCount === 0) {
|
||||
f(s);
|
||||
}
|
||||
}
|
||||
if (extraBytes) {
|
||||
offset = j << 2;
|
||||
block = s[i];
|
||||
array[offset] = block & 0xFF;
|
||||
if (extraBytes > 1) {
|
||||
array[offset + 1] = (block >> 8) & 0xFF;
|
||||
}
|
||||
if (extraBytes > 2) {
|
||||
array[offset + 2] = (block >> 16) & 0xFF;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
function Kmac(bits, padding, outputBits) {
|
||||
Keccak.call(this, bits, padding, outputBits);
|
||||
}
|
||||
|
||||
Kmac.prototype = new Keccak();
|
||||
|
||||
Kmac.prototype.finalize = function () {
|
||||
this.encode(this.outputBits, true);
|
||||
return Keccak.prototype.finalize.call(this);
|
||||
};
|
||||
|
||||
var f = function (s) {
|
||||
var h, l, n, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9,
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15, b16, b17,
|
||||
b18, b19, b20, b21, b22, b23, b24, b25, b26, b27, b28, b29, b30, b31, b32, b33,
|
||||
b34, b35, b36, b37, b38, b39, b40, b41, b42, b43, b44, b45, b46, b47, b48, b49;
|
||||
for (n = 0; n < 48; n += 2) {
|
||||
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40];
|
||||
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41];
|
||||
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42];
|
||||
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43];
|
||||
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44];
|
||||
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45];
|
||||
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46];
|
||||
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47];
|
||||
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48];
|
||||
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49];
|
||||
|
||||
h = c8 ^ ((c2 << 1) | (c3 >>> 31));
|
||||
l = c9 ^ ((c3 << 1) | (c2 >>> 31));
|
||||
s[0] ^= h;
|
||||
s[1] ^= l;
|
||||
s[10] ^= h;
|
||||
s[11] ^= l;
|
||||
s[20] ^= h;
|
||||
s[21] ^= l;
|
||||
s[30] ^= h;
|
||||
s[31] ^= l;
|
||||
s[40] ^= h;
|
||||
s[41] ^= l;
|
||||
h = c0 ^ ((c4 << 1) | (c5 >>> 31));
|
||||
l = c1 ^ ((c5 << 1) | (c4 >>> 31));
|
||||
s[2] ^= h;
|
||||
s[3] ^= l;
|
||||
s[12] ^= h;
|
||||
s[13] ^= l;
|
||||
s[22] ^= h;
|
||||
s[23] ^= l;
|
||||
s[32] ^= h;
|
||||
s[33] ^= l;
|
||||
s[42] ^= h;
|
||||
s[43] ^= l;
|
||||
h = c2 ^ ((c6 << 1) | (c7 >>> 31));
|
||||
l = c3 ^ ((c7 << 1) | (c6 >>> 31));
|
||||
s[4] ^= h;
|
||||
s[5] ^= l;
|
||||
s[14] ^= h;
|
||||
s[15] ^= l;
|
||||
s[24] ^= h;
|
||||
s[25] ^= l;
|
||||
s[34] ^= h;
|
||||
s[35] ^= l;
|
||||
s[44] ^= h;
|
||||
s[45] ^= l;
|
||||
h = c4 ^ ((c8 << 1) | (c9 >>> 31));
|
||||
l = c5 ^ ((c9 << 1) | (c8 >>> 31));
|
||||
s[6] ^= h;
|
||||
s[7] ^= l;
|
||||
s[16] ^= h;
|
||||
s[17] ^= l;
|
||||
s[26] ^= h;
|
||||
s[27] ^= l;
|
||||
s[36] ^= h;
|
||||
s[37] ^= l;
|
||||
s[46] ^= h;
|
||||
s[47] ^= l;
|
||||
h = c6 ^ ((c0 << 1) | (c1 >>> 31));
|
||||
l = c7 ^ ((c1 << 1) | (c0 >>> 31));
|
||||
s[8] ^= h;
|
||||
s[9] ^= l;
|
||||
s[18] ^= h;
|
||||
s[19] ^= l;
|
||||
s[28] ^= h;
|
||||
s[29] ^= l;
|
||||
s[38] ^= h;
|
||||
s[39] ^= l;
|
||||
s[48] ^= h;
|
||||
s[49] ^= l;
|
||||
|
||||
b0 = s[0];
|
||||
b1 = s[1];
|
||||
b32 = (s[11] << 4) | (s[10] >>> 28);
|
||||
b33 = (s[10] << 4) | (s[11] >>> 28);
|
||||
b14 = (s[20] << 3) | (s[21] >>> 29);
|
||||
b15 = (s[21] << 3) | (s[20] >>> 29);
|
||||
b46 = (s[31] << 9) | (s[30] >>> 23);
|
||||
b47 = (s[30] << 9) | (s[31] >>> 23);
|
||||
b28 = (s[40] << 18) | (s[41] >>> 14);
|
||||
b29 = (s[41] << 18) | (s[40] >>> 14);
|
||||
b20 = (s[2] << 1) | (s[3] >>> 31);
|
||||
b21 = (s[3] << 1) | (s[2] >>> 31);
|
||||
b2 = (s[13] << 12) | (s[12] >>> 20);
|
||||
b3 = (s[12] << 12) | (s[13] >>> 20);
|
||||
b34 = (s[22] << 10) | (s[23] >>> 22);
|
||||
b35 = (s[23] << 10) | (s[22] >>> 22);
|
||||
b16 = (s[33] << 13) | (s[32] >>> 19);
|
||||
b17 = (s[32] << 13) | (s[33] >>> 19);
|
||||
b48 = (s[42] << 2) | (s[43] >>> 30);
|
||||
b49 = (s[43] << 2) | (s[42] >>> 30);
|
||||
b40 = (s[5] << 30) | (s[4] >>> 2);
|
||||
b41 = (s[4] << 30) | (s[5] >>> 2);
|
||||
b22 = (s[14] << 6) | (s[15] >>> 26);
|
||||
b23 = (s[15] << 6) | (s[14] >>> 26);
|
||||
b4 = (s[25] << 11) | (s[24] >>> 21);
|
||||
b5 = (s[24] << 11) | (s[25] >>> 21);
|
||||
b36 = (s[34] << 15) | (s[35] >>> 17);
|
||||
b37 = (s[35] << 15) | (s[34] >>> 17);
|
||||
b18 = (s[45] << 29) | (s[44] >>> 3);
|
||||
b19 = (s[44] << 29) | (s[45] >>> 3);
|
||||
b10 = (s[6] << 28) | (s[7] >>> 4);
|
||||
b11 = (s[7] << 28) | (s[6] >>> 4);
|
||||
b42 = (s[17] << 23) | (s[16] >>> 9);
|
||||
b43 = (s[16] << 23) | (s[17] >>> 9);
|
||||
b24 = (s[26] << 25) | (s[27] >>> 7);
|
||||
b25 = (s[27] << 25) | (s[26] >>> 7);
|
||||
b6 = (s[36] << 21) | (s[37] >>> 11);
|
||||
b7 = (s[37] << 21) | (s[36] >>> 11);
|
||||
b38 = (s[47] << 24) | (s[46] >>> 8);
|
||||
b39 = (s[46] << 24) | (s[47] >>> 8);
|
||||
b30 = (s[8] << 27) | (s[9] >>> 5);
|
||||
b31 = (s[9] << 27) | (s[8] >>> 5);
|
||||
b12 = (s[18] << 20) | (s[19] >>> 12);
|
||||
b13 = (s[19] << 20) | (s[18] >>> 12);
|
||||
b44 = (s[29] << 7) | (s[28] >>> 25);
|
||||
b45 = (s[28] << 7) | (s[29] >>> 25);
|
||||
b26 = (s[38] << 8) | (s[39] >>> 24);
|
||||
b27 = (s[39] << 8) | (s[38] >>> 24);
|
||||
b8 = (s[48] << 14) | (s[49] >>> 18);
|
||||
b9 = (s[49] << 14) | (s[48] >>> 18);
|
||||
|
||||
s[0] = b0 ^ (~b2 & b4);
|
||||
s[1] = b1 ^ (~b3 & b5);
|
||||
s[10] = b10 ^ (~b12 & b14);
|
||||
s[11] = b11 ^ (~b13 & b15);
|
||||
s[20] = b20 ^ (~b22 & b24);
|
||||
s[21] = b21 ^ (~b23 & b25);
|
||||
s[30] = b30 ^ (~b32 & b34);
|
||||
s[31] = b31 ^ (~b33 & b35);
|
||||
s[40] = b40 ^ (~b42 & b44);
|
||||
s[41] = b41 ^ (~b43 & b45);
|
||||
s[2] = b2 ^ (~b4 & b6);
|
||||
s[3] = b3 ^ (~b5 & b7);
|
||||
s[12] = b12 ^ (~b14 & b16);
|
||||
s[13] = b13 ^ (~b15 & b17);
|
||||
s[22] = b22 ^ (~b24 & b26);
|
||||
s[23] = b23 ^ (~b25 & b27);
|
||||
s[32] = b32 ^ (~b34 & b36);
|
||||
s[33] = b33 ^ (~b35 & b37);
|
||||
s[42] = b42 ^ (~b44 & b46);
|
||||
s[43] = b43 ^ (~b45 & b47);
|
||||
s[4] = b4 ^ (~b6 & b8);
|
||||
s[5] = b5 ^ (~b7 & b9);
|
||||
s[14] = b14 ^ (~b16 & b18);
|
||||
s[15] = b15 ^ (~b17 & b19);
|
||||
s[24] = b24 ^ (~b26 & b28);
|
||||
s[25] = b25 ^ (~b27 & b29);
|
||||
s[34] = b34 ^ (~b36 & b38);
|
||||
s[35] = b35 ^ (~b37 & b39);
|
||||
s[44] = b44 ^ (~b46 & b48);
|
||||
s[45] = b45 ^ (~b47 & b49);
|
||||
s[6] = b6 ^ (~b8 & b0);
|
||||
s[7] = b7 ^ (~b9 & b1);
|
||||
s[16] = b16 ^ (~b18 & b10);
|
||||
s[17] = b17 ^ (~b19 & b11);
|
||||
s[26] = b26 ^ (~b28 & b20);
|
||||
s[27] = b27 ^ (~b29 & b21);
|
||||
s[36] = b36 ^ (~b38 & b30);
|
||||
s[37] = b37 ^ (~b39 & b31);
|
||||
s[46] = b46 ^ (~b48 & b40);
|
||||
s[47] = b47 ^ (~b49 & b41);
|
||||
s[8] = b8 ^ (~b0 & b2);
|
||||
s[9] = b9 ^ (~b1 & b3);
|
||||
s[18] = b18 ^ (~b10 & b12);
|
||||
s[19] = b19 ^ (~b11 & b13);
|
||||
s[28] = b28 ^ (~b20 & b22);
|
||||
s[29] = b29 ^ (~b21 & b23);
|
||||
s[38] = b38 ^ (~b30 & b32);
|
||||
s[39] = b39 ^ (~b31 & b33);
|
||||
s[48] = b48 ^ (~b40 & b42);
|
||||
s[49] = b49 ^ (~b41 & b43);
|
||||
|
||||
s[0] ^= RC[n];
|
||||
s[1] ^= RC[n + 1];
|
||||
}
|
||||
};
|
||||
|
||||
function extractLast20Bytes(hexString, addPrefix) {
|
||||
// Ensure the input hexString has '0x' prefix
|
||||
if (!hexString.startsWith('0x')) {
|
||||
hexString = '0x' + hexString;
|
||||
}
|
||||
|
||||
// Remove '0x' prefix and parse the hex string to a BigInt
|
||||
var bigIntValue = BigInt(hexString);
|
||||
|
||||
// Extract the last 20 bytes (160 bits) from the BigInt
|
||||
var last20Bytes = bigIntValue & BigInt('0x' + 'f'.repeat(40)); // 0xf is 4 bits in hexadecimal, repeated 40 times for 160 bits
|
||||
|
||||
// Convert the result back to a hexadecimal string
|
||||
var result = last20Bytes.toString(16).padStart(40, '0'); // 40 characters for 160 bits
|
||||
|
||||
// Add '0x' prefix if addPrefix is truthy
|
||||
if (addPrefix) {
|
||||
result = '0x' + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (typeof root.keccak === 'object') {
|
||||
Object.assign(root.keccak, methods);
|
||||
}
|
||||
|
||||
if (COMMON_JS) {
|
||||
module.exports = methods;
|
||||
} else {
|
||||
for (i = 0; i < methodNames.length; ++i) {
|
||||
root[methodNames[i]] = methods[methodNames[i]];
|
||||
}
|
||||
if (AMD) {
|
||||
define(function () {
|
||||
return methods;
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
8428
optimismwallet/scripts/tap_combined.js
Normal file
8428
optimismwallet/scripts/tap_combined.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user