feat: add initial Polkadot wallet app with multi-blockchain address generation, recovery, sending, and transaction history for DOT, BTC, and FLO

This commit is contained in:
void-57 2026-01-02 01:21:43 +05:30
parent 67fb3f43c3
commit 6619f3eb2d
8 changed files with 18622 additions and 0 deletions

2451
index.html Normal file

File diff suppressed because it is too large Load Diff

11473
lib.polkadot.js Normal file

File diff suppressed because it is too large Load Diff

2
polkadot-api-bundle.js Normal file

File diff suppressed because one or more lines are too long

536
polkadotBlockchainAPI.js Normal file
View File

@ -0,0 +1,536 @@
// API for Polkadot AssetHub (Subscan)
const polkadotAPI = (function () {
"use strict";
const SUBSCAN_API = "https://assethub-polkadot.api.subscan.io";
const NETWORK = "assethub-polkadot";
function normalizeAddress(address) {
if (!address) return address;
if (typeof polkadotCrypto !== "undefined" && polkadotCrypto.hexToSS58) {
return polkadotCrypto.hexToSS58(address, 0);
}
// Fallback: return as-is
return address;
}
async function getBalance(address) {
try {
const response = await fetch(`${SUBSCAN_API}/api/scan/account/tokens`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "239a9db0f7174ad6a07ee6006dbb29a7", // Add API key if needed
},
body: JSON.stringify({
address: address,
}),
});
const data = await response.json();
if (data.code === 0 && data.data) {
// Find DOT balance
const dotBalance = data.data.native?.find(
(token) => token.symbol === "DOT"
);
return {
balance: dotBalance
? parseFloat(dotBalance.balance) / Math.pow(10, dotBalance.decimals)
: 0,
address: address,
decimals: dotBalance?.decimals || 10,
};
}
throw new Error(data.message || "Failed to fetch balance");
} catch (error) {
console.error("Error fetching balance:", error);
throw error;
}
}
async function getTransactions(address, page = 0, limit = 20) {
try {
const response = await fetch(`${SUBSCAN_API}/api/v2/scan/transfers`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "239a9db0f7174ad6a07ee6006dbb29a7",
},
body: JSON.stringify({
address: address,
row: limit,
page: page,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.code === 0 && data.data) {
const transactions = data.data.transfers || [];
return transactions.map((tx) => ({
id: tx.hash,
hash: tx.hash,
from: normalizeAddress(tx.from),
to: normalizeAddress(tx.to),
amount: parseFloat(tx.amount || 0),
amountDot: parseFloat(tx.amount || 0),
fee: parseFloat(tx.fee || 0) / Math.pow(10, 10),
feeDot: parseFloat(tx.fee || 0) / Math.pow(10, 10),
block: tx.block_num,
timestamp: tx.block_timestamp,
success: tx.success,
type: normalizeAddress(tx.from) === address ? "sent" : "received",
module: tx.module,
asset_symbol: tx.asset_symbol || "DOT",
extrinsicIndex: tx.extrinsic_index,
}));
}
throw new Error(data.message || "Failed to fetch transactions");
} catch (error) {
console.error("Error fetching transactions:", error);
throw error;
}
}
async function getTransaction(hash) {
try {
const response = await fetch(`${SUBSCAN_API}/api/scan/extrinsic`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "239a9db0f7174ad6a07ee6006dbb29a7", // Add API key if needed
},
body: JSON.stringify({
hash: hash,
}),
});
const data = await response.json();
if (data.code === 0 && data.data) {
const tx = data.data;
// Extract sender address
let from = tx.account_id || tx.account?.address || "";
// Extract destination and amount based on transaction type
let to = "";
let amount = 0;
// Check if it's a regular transfer with transfer object
if (tx.transfer) {
to = tx.transfer.to || tx.transfer.destination || "";
// The transfer.amount is already in DOT (not planck), so use it directly
amount = parseFloat(tx.transfer.amount) || 0;
}
// Check if it's a balance transfer without transfer object (data in params)
else if (
tx.params &&
Array.isArray(tx.params) &&
(tx.call_module === "balances" || tx.module === "balances")
) {
// Find dest parameter
const destParam = tx.params.find((p) => p.name === "dest");
if (destParam && destParam.value) {
// Try to get SS58 address first, fallback to hex Id
to = destParam.value.address || destParam.value.Id || "";
}
// Find value parameter
const valueParam = tx.params.find((p) => p.name === "value");
if (valueParam && valueParam.value) {
// Convert from planck to DOT (1 DOT = 10^10 planck)
amount = parseFloat(valueParam.value) / Math.pow(10, 10);
}
}
// Check if it's an XCM transfer
else if (tx.params && Array.isArray(tx.params)) {
// Find beneficiary parameter
const beneficiaryParam = tx.params.find(
(p) => p.name === "beneficiary"
);
if (beneficiaryParam && beneficiaryParam.value) {
try {
// Extract AccountId32 from nested structure
const v3 = beneficiaryParam.value.V3 || beneficiaryParam.value.V4;
if (v3 && v3.interior) {
// Handle X1 as object or array
let accountId32Data = null;
if (v3.interior.X1) {
if (v3.interior.X1.AccountId32) {
accountId32Data = v3.interior.X1.AccountId32;
} else if (
Array.isArray(v3.interior.X1) &&
v3.interior.X1[0]?.AccountId32
) {
accountId32Data = v3.interior.X1[0].AccountId32;
}
}
if (accountId32Data && accountId32Data.id) {
to = accountId32Data.id;
}
}
} catch (e) {
console.error("Failed to extract beneficiary:", e);
}
}
// Find assets/amount parameter
const assetsParam = tx.params.find(
(p) => p.name === "assets" || p.name === "value"
);
if (assetsParam && assetsParam.value) {
try {
const v3 = assetsParam.value.V3 || assetsParam.value.V4;
if (v3 && Array.isArray(v3) && v3.length > 0) {
const asset = v3[0];
const fungible = asset.fun?.Fungible || asset.amount?.Fungible;
if (fungible) {
amount = parseFloat(fungible) / Math.pow(10, 10);
}
}
} catch (e) {
console.error("Failed to extract amount:", e);
}
}
}
// Fallback: If still no data, check events for Transfer
if (!to && !amount && tx.event && Array.isArray(tx.event)) {
const transferEvent = tx.event.find(
(e) => e.module_id === "balances" && e.event_id === "Transfer"
);
if (transferEvent && transferEvent.params) {
try {
const eventParams = JSON.parse(transferEvent.params);
// Find to and amount from event params
const toParam = eventParams.find((p) => p.name === "to");
const amountParam = eventParams.find((p) => p.name === "amount");
if (toParam && toParam.value) {
to = toParam.value;
}
if (amountParam && amountParam.value) {
amount = parseFloat(amountParam.value) / Math.pow(10, 10);
}
} catch (e) {
console.error("Failed to parse event params:", e);
}
}
}
// Normalize addresses (keep SS58 as-is, hex addresses remain valid for AssetHub)
from = normalizeAddress(from);
to = normalizeAddress(to);
return {
id: tx.extrinsic_hash,
hash: tx.extrinsic_hash,
from: from,
to: to,
amount: amount,
amountDot: amount,
fee: parseFloat(tx.fee || 0) / Math.pow(10, 10),
feeDot: parseFloat(tx.fee || 0) / Math.pow(10, 10),
block: tx.block_num,
blockNum: tx.block_num,
timestamp: tx.block_timestamp,
success: tx.success,
module: tx.call_module,
method: tx.call_module_function,
signature: tx.signature,
};
}
throw new Error(data.message || "Transaction not found");
} catch (error) {
console.error("Error fetching transaction:", error);
throw error;
}
}
// Build and sign transaction using direct crypto (no full API needed)
async function buildAndSignTransaction(txParams) {
const { sourceAddress, destinationAddress, amount, privateKeyHex, memo } =
txParams;
try {
// Wait for crypto to be ready
const { cryptoWaitReady, sr25519PairFromSeed, sr25519Sign } =
window.polkadotUtilCrypto || {};
if (!cryptoWaitReady) {
throw new Error(
"Polkadot crypto utilities not loaded. Please refresh the page."
);
}
await cryptoWaitReady();
// Convert hex private key to seed (first 32 bytes)
const privKeyOnly = privateKeyHex.substring(0, 64);
const { hexToU8a, u8aToHex } = window.polkadotUtil || {};
if (!hexToU8a) {
throw new Error(
"Polkadot utilities not loaded. Please refresh the page."
);
}
const seed = hexToU8a("0x" + privKeyOnly).slice(0, 32);
// Create keypair from seed using Sr25519
const keypair = sr25519PairFromSeed(seed);
// Get address from public key
const { encodeAddress } = window.polkadotUtilCrypto;
const address = encodeAddress(keypair.publicKey, 0); // 0 = Polkadot prefix
// Convert amount to planck (1 DOT = 10^10 planck)
const amountInPlanck = Math.floor(parseFloat(amount) * Math.pow(10, 10));
// Estimated fee
const estimatedFee = 0.0165;
// Return transaction data for RPC submission
return {
keypair: keypair,
destinationAddress: destinationAddress,
amountInPlanck: amountInPlanck,
fee: estimatedFee,
feeDot: estimatedFee,
sourceAddress: sourceAddress,
};
} catch (error) {
console.error("Error building transaction:", error);
throw error;
}
}
// Submit transaction using Polkadot.js API
async function submitTransaction(txData) {
try {
const { keypair, destinationAddress, amountInPlanck, sourceAddress } =
txData;
// Check if API is available
if (
!window.polkadotApi ||
!window.polkadotApi.ApiPromise ||
!window.polkadotApi.WsProvider
) {
throw new Error("Polkadot API not loaded! Please refresh the page.");
}
const { ApiPromise, WsProvider } = window.polkadotApi;
const rpcUrl = "wss://polkadot-asset-hub-rpc.polkadot.io";
// Create API instance
const provider = new WsProvider(rpcUrl);
const api = await ApiPromise.create({ provider });
// Get account nonce
const nonce = await api.rpc.system.accountNextIndex(sourceAddress);
const transfer = api.tx.balances.transferAllowDeath(
destinationAddress,
amountInPlanck
);
// Create a Keyring and add our keypair
// The Polkadot API needs a proper KeyringPair interface
// Our keypair from sr25519PairFromSeed has the right structure, but we need to add the sign method
const { u8aToHex } = window.polkadotUtil;
const signerPair = {
address: keypair.address,
addressRaw: keypair.publicKey,
publicKey: keypair.publicKey,
sign: (data) => {
const { sr25519Sign } = window.polkadotUtilCrypto;
const signature = sr25519Sign(data, keypair);
// Return signature with proper format for Sr25519: { sr25519: Uint8Array }
return { sr25519: signature };
},
type: "sr25519",
unlock: () => {}, // Required but unused for our case
lock: () => {}, // Required but unused for our case
isLocked: false,
};
return new Promise((resolve, reject) => {
let unsub;
const timeout = setTimeout(() => {
if (unsub) unsub();
api.disconnect();
reject(new Error("Transaction timeout"));
}, 60000);
transfer
.signAndSend(signerPair, { nonce }, (result) => {
if (result.status.isFinalized) {
clearTimeout(timeout);
// Check for errors
const failed = result.events.find(({ event }) =>
api.events.system.ExtrinsicFailed.is(event)
);
if (failed) {
const [dispatchError] = failed.event.data;
let errorMessage = "Transaction failed";
if (dispatchError.isModule) {
try {
const decoded = api.registry.findMetaError(
dispatchError.asModule
);
errorMessage = `${decoded.section}.${
decoded.name
}: ${decoded.docs.join(" ")}`;
} catch (e) {
errorMessage = `Module error: ${dispatchError.asModule.toHuman()}`;
}
} else if (dispatchError.isToken) {
errorMessage = `Token error: ${dispatchError.asToken.toString()}`;
} else if (dispatchError.isArithmetic) {
errorMessage = `Arithmetic error: ${dispatchError.asArithmetic.toString()}`;
}
console.error(`❌ Transaction failed: ${errorMessage}`);
if (unsub) unsub();
api.disconnect();
reject(new Error(errorMessage));
} else {
const txHash = transfer.hash.toHex();
console.log(`✅ Transaction successful! Hash: ${txHash}`);
if (unsub) unsub();
api.disconnect();
resolve({
hash: txHash,
success: true,
block: result.status.asFinalized.toHex(),
});
}
}
})
.then((unsubscribe) => {
unsub = unsubscribe;
})
.catch((error) => {
clearTimeout(timeout);
console.error(`❌ Signing/sending error: ${error.message}`);
api.disconnect();
reject(error);
});
});
} catch (error) {
console.error("❌ Error submitting transaction:", error);
throw error;
}
}
// Estimate transaction fee using Polkadot API paymentInfo
async function estimateFee(sourceAddress, destinationAddress, amount) {
try {
// Check if API is available
if (
!window.polkadotApi ||
!window.polkadotApi.ApiPromise ||
!window.polkadotApi.WsProvider
) {
console.warn("Polkadot API not loaded, using default fee estimate");
return {
fee: 0.0165,
feeDot: 0.0165,
};
}
const { ApiPromise, WsProvider } = window.polkadotApi;
const rpcUrl = "wss://polkadot-asset-hub-rpc.polkadot.io";
// Create API instance
const provider = new WsProvider(rpcUrl);
const api = await ApiPromise.create({ provider });
// Convert amount to planck
const amountInPlanck = Math.floor(parseFloat(amount) * Math.pow(10, 10));
// Create the transfer transaction
const transfer = api.tx.balances.transferAllowDeath(
destinationAddress,
amountInPlanck
);
// Get payment info (accurate fee estimation)
const paymentInfo = await transfer.paymentInfo(sourceAddress);
// Disconnect after getting fee
await api.disconnect();
// Convert fee from planck to DOT
const fee =
parseFloat(paymentInfo.partialFee.toString()) / Math.pow(10, 10);
return {
fee: fee,
feeDot: fee,
};
} catch (error) {
console.error("Error estimating fee:", error);
// Return typical DOT transfer fee if estimation fails
return {
fee: 0.0165,
feeDot: 0.0165,
};
}
}
// Check if account is active (has balance)
async function checkAccountActive(address) {
try {
const balanceData = await getBalance(address);
return {
isActive: balanceData.balance > 0,
balance: balanceData.balance,
minimumRequired: 0.01, // Minimum to activate new account
};
} catch (error) {
console.error("Error checking account status:", error);
// If we can't check, assume account needs activation
return {
isActive: false,
balance: 0,
minimumRequired: 0.01,
};
}
}
// Public API
return {
getBalance,
getTransactions,
getTransaction,
buildAndSignTransaction,
submitTransaction,
estimateFee,
checkAccountActive,
SUBSCAN_API,
NETWORK,
};
})();

322
polkadotCrypto.js Normal file
View File

@ -0,0 +1,322 @@
(function (EXPORTS) {
"use strict";
const polkadotCrypto = EXPORTS;
function hexToBytes(hex) {
const bytes = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substr(i, 2), 16));
}
return bytes;
}
function bytesToHex(bytes) {
return Array.from(bytes)
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
}
function generateNewID() {
var key = new Bitcoin.ECKey(false);
key.setCompressed(true);
return {
floID: key.getBitcoinAddress(),
pubKey: key.getPubKeyHex(),
privKey: key.getBitcoinWalletImportFormat(),
};
}
const BASE58_ALPHABET =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
function base58Encode(bytes) {
const digits = [0];
for (let i = 0; i < bytes.length; i++) {
let carry = bytes[i];
for (let j = 0; j < digits.length; j++) {
carry += digits[j] << 8;
digits[j] = carry % 58;
carry = (carry / 58) | 0;
}
while (carry > 0) {
digits.push(carry % 58);
carry = (carry / 58) | 0;
}
}
// Add leading zeros
for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
digits.push(0);
}
// Convert to string
return digits
.reverse()
.map((d) => BASE58_ALPHABET[d])
.join("");
}
function base58Decode(str) {
const bytes = [0];
for (let i = 0; i < str.length; i++) {
const value = BASE58_ALPHABET.indexOf(str[i]);
if (value === -1) {
throw new Error(`Invalid Base58 character: ${str[i]}`);
}
let carry = value;
for (let j = 0; j < bytes.length; j++) {
carry += bytes[j] * 58;
bytes[j] = carry & 0xff;
carry >>= 8;
}
while (carry > 0) {
bytes.push(carry & 0xff);
carry >>= 8;
}
}
// Add leading zeros
for (let i = 0; i < str.length && str[i] === "1"; i++) {
bytes.push(0);
}
return new Uint8Array(bytes.reverse());
}
function blake2bHash(data, outlen = 64) {
if (typeof blakejs !== "undefined" && blakejs.blake2b) {
return blakejs.blake2b(data, null, outlen);
}
throw new Error("Blake2b library not available");
}
function createPolkadotAddress(publicKey, ss58Prefix = 0) {
// SS58 format: [prefix] + [public_key] + [checksum]
const prefix = new Uint8Array([ss58Prefix]);
const payload = new Uint8Array([...prefix, ...publicKey]);
const checksumInput = new Uint8Array([
...new TextEncoder().encode("SS58PRE"),
...payload,
]);
const hash = blake2bHash(checksumInput, 64);
const checksum = hash.slice(0, 2);
// Combine all parts
const addressBytes = new Uint8Array([...payload, ...checksum]);
return base58Encode(addressBytes);
}
// --- Multi-chain Generator (BTC, FLO, DOT) ---
polkadotCrypto.generateMultiChain = async function (inputWif) {
const versions = {
BTC: { pub: 0x00, priv: 0x80 },
FLO: { pub: 0x23, priv: 0xa3 },
};
const origBitjsPub = bitjs.pub;
const origBitjsPriv = bitjs.priv;
const origBitjsCompressed = bitjs.compressed;
const origCoinJsCompressed = coinjs.compressed;
bitjs.compressed = true;
coinjs.compressed = true;
let privKeyHex;
let compressed = true;
if (typeof inputWif === "string" && inputWif.trim().length > 0) {
const trimmedInput = inputWif.trim();
const hexOnly = /^[0-9a-fA-F]+$/.test(trimmedInput);
// Check if it's a Polkadot seed phrase or private key
if (
hexOnly &&
(trimmedInput.length === 64 || trimmedInput.length === 128)
) {
privKeyHex =
trimmedInput.length === 128
? trimmedInput.substring(0, 64)
: trimmedInput;
} else {
try {
const decode = Bitcoin.Base58.decode(trimmedInput);
// Validate WIF checksum
if (decode.length < 37) {
throw new Error("Invalid WIF key: too short");
}
// WIF format: [version(1)] + [private_key(32)] + [compression_flag(0-1)] + [checksum(4)]
const payload = decode.slice(0, decode.length - 4);
const providedChecksum = decode.slice(decode.length - 4);
// Calculate expected checksum using double SHA256
const hash1 = Crypto.SHA256(payload, { asBytes: true });
const hash2 = Crypto.SHA256(hash1, { asBytes: true });
const expectedChecksum = hash2.slice(0, 4);
// Verify checksum matches
let checksumMatch = true;
for (let i = 0; i < 4; i++) {
if (providedChecksum[i] !== expectedChecksum[i]) {
checksumMatch = false;
break;
}
}
if (!checksumMatch) {
const providedHex = providedChecksum
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
const expectedHex = expectedChecksum
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
throw new Error(
`Invalid WIF key: checksum mismatch (expected ${expectedHex}, got ${providedHex})`
);
}
const keyWithVersion = decode.slice(0, decode.length - 4);
let key = keyWithVersion.slice(1);
if (key.length >= 33 && key[key.length - 1] === 0x01) {
key = key.slice(0, key.length - 1);
compressed = true;
}
privKeyHex = bytesToHex(key);
} catch (e) {
console.error("Invalid WIF key:", e.message);
throw new Error(`Failed to recover from WIF key: ${e.message}`);
}
}
} else {
// Generate new key if no input
const newKey = generateNewID();
const decode = Bitcoin.Base58.decode(newKey.privKey);
const keyWithVersion = decode.slice(0, decode.length - 4);
let key = keyWithVersion.slice(1);
if (key.length >= 33 && key[key.length - 1] === 0x01)
key = key.slice(0, key.length - 1);
privKeyHex = bytesToHex(key);
}
// --- Derive addresses for each chain ---
const result = { BTC: {}, FLO: {}, DOT: {} };
// BTC
bitjs.pub = versions.BTC.pub;
bitjs.priv = versions.BTC.priv;
const pubKeyBTC = bitjs.newPubkey(privKeyHex);
result.BTC.address = coinjs.bech32Address(pubKeyBTC).address;
result.BTC.privateKey = bitjs.privkey2wif(privKeyHex);
// FLO
bitjs.pub = versions.FLO.pub;
bitjs.priv = versions.FLO.priv;
const pubKeyFLO = bitjs.newPubkey(privKeyHex);
result.FLO.address = bitjs.pubkey2address(pubKeyFLO);
result.FLO.privateKey = bitjs.privkey2wif(privKeyHex);
// DOT (Polkadot) - Using Sr25519 with Polkadot.js
try {
const privBytes = hexToBytes(privKeyHex.substring(0, 64));
const seed = new Uint8Array(privBytes.slice(0, 32));
// Wait for Polkadot crypto to be ready
await polkadotUtilCrypto.cryptoWaitReady();
// Create keypair from seed using Sr25519 (Schnorrkel)
const keyPair = polkadotUtilCrypto.sr25519PairFromSeed(seed);
// Encode address in SS58 format with Polkadot prefix (0)
const dotAddress = polkadotUtilCrypto.encodeAddress(keyPair.publicKey, 0);
// Store private key as hex
const dotPrivateKey = bytesToHex(seed);
result.DOT.address = dotAddress;
result.DOT.privateKey = dotPrivateKey;
} catch (error) {
console.error("Error generating DOT address:", error);
result.DOT.address = "Error generating address";
result.DOT.privateKey = privKeyHex;
}
bitjs.pub = origBitjsPub;
bitjs.priv = origBitjsPriv;
bitjs.compressed = origBitjsCompressed;
coinjs.compressed = origCoinJsCompressed;
return result;
};
// Sign Polkadot Transaction using Sr25519
polkadotCrypto.signDot = async function (txBytes, dotPrivateKey) {
const privKeyOnly = dotPrivateKey.substring(0, 64);
const privBytes = hexToBytes(privKeyOnly);
const seed = new Uint8Array(privBytes.slice(0, 32));
// Wait for Polkadot crypto to be ready
await polkadotUtilCrypto.cryptoWaitReady();
// Create keypair from seed using Sr25519
const keypair = polkadotUtilCrypto.sr25519PairFromSeed(seed);
let txData;
if (typeof txBytes === "string") {
txData = new Uint8Array(
atob(txBytes)
.split("")
.map((c) => c.charCodeAt(0))
);
} else {
txData = new Uint8Array(txBytes);
}
// Sign using Sr25519
const signature = polkadotUtilCrypto.sr25519Sign(txData, keypair);
return signature;
};
// Export helper function for converting hex addresses to SS58
polkadotCrypto.hexToSS58 = function (hexAddress, prefix = 0) {
try {
if (!hexAddress) return hexAddress;
// Remove 0x prefix if present
const cleanHex = hexAddress.startsWith("0x")
? hexAddress.slice(2)
: hexAddress;
// If it's already SS58 format (not hex), return as-is
if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
return hexAddress;
}
// Convert hex to bytes
const bytes = [];
for (let i = 0; i < cleanHex.length; i += 2) {
bytes.push(parseInt(cleanHex.substr(i, 2), 16));
}
const publicKey = new Uint8Array(bytes);
// Only convert if it's exactly 32 bytes (valid public key)
if (publicKey.length === 32) {
return createPolkadotAddress(publicKey, prefix);
}
// Return original if not a valid 32-byte key
return hexAddress;
} catch (error) {
console.warn("Failed to convert hex to SS58:", error);
return hexAddress;
}
};
})("object" === typeof module ? module.exports : (window.polkadotCrypto = {}));

136
polkadotSearchDB.js Normal file
View File

@ -0,0 +1,136 @@
// Search database using local storage
class PolkadotSearchDB {
constructor() {
this.dbName = "PolkadotWalletDB";
this.storeName = "recentSearches";
this.maxSearches = 10;
}
saveSearch(address, balance, sourceInfo = null) {
try {
const searches = this.getSearches();
// Check if address already exists
const existingIndex = searches.findIndex((s) => s.address === address);
const existing = existingIndex !== -1 ? searches[existingIndex] : null;
const searchData = {
address: address,
balance: balance || 0,
timestamp: Date.now(),
date: new Date().toISOString(),
btcAddress: sourceInfo?.btcAddress || existing?.btcAddress || null,
floAddress: sourceInfo?.floAddress || existing?.floAddress || null,
isFromPrivateKey: !!(
sourceInfo?.btcAddress ||
sourceInfo?.floAddress ||
existing?.btcAddress ||
existing?.floAddress
),
};
if (existingIndex !== -1) {
searches[existingIndex] = searchData;
} else {
// Add new search at the beginning
searches.unshift(searchData);
// Keep only the most recent searches
if (searches.length > this.maxSearches) {
searches.pop();
}
}
localStorage.setItem(this.storeName, JSON.stringify(searches));
return true;
} catch (error) {
console.error("Error saving search:", error);
return false;
}
}
getSearches() {
try {
const data = localStorage.getItem(this.storeName);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error("Error getting searches:", error);
return [];
}
}
getSearch(address) {
try {
const searches = this.getSearches();
return searches.find((s) => s.address === address) || null;
} catch (error) {
console.error("Error getting search:", error);
return null;
}
}
deleteSearch(address) {
try {
const searches = this.getSearches();
const filtered = searches.filter((s) => s.address !== address);
localStorage.setItem(this.storeName, JSON.stringify(filtered));
return true;
} catch (error) {
console.error("Error deleting search:", error);
return false;
}
}
clearAll() {
try {
localStorage.removeItem(this.storeName);
return true;
} catch (error) {
console.error("Error clearing searches:", error);
return false;
}
}
getRecentSearches(limit = null) {
try {
let searches = this.getSearches();
// Sort by timestamp descending (newest first)
searches.sort((a, b) => b.timestamp - a.timestamp);
// Apply limit if specified
if (limit && limit > 0) {
searches = searches.slice(0, limit);
}
return searches;
} catch (error) {
console.error("Error getting recent searches:", error);
return [];
}
}
updateBalance(address, newBalance) {
try {
const searches = this.getSearches();
const index = searches.findIndex((s) => s.address === address);
if (index !== -1) {
searches[index].balance = newBalance;
searches[index].timestamp = Date.now();
searches[index].date = new Date().toISOString();
localStorage.setItem(this.storeName, JSON.stringify(searches));
return true;
}
return false;
} catch (error) {
console.error("Error updating balance:", error);
return false;
}
}
}
// Create a global instance if SearchedAddressDB is referenced anywhere
const SearchedAddressDB = PolkadotSearchDB;

14
polkadot_favicon.svg Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1326.1 1410.3" style="enable-background:new 0 0 1326.1 1410.3;" xml:space="preserve">
<style type="text/css">
.st0{fill:#E6007A;}
</style>
<ellipse class="st0" cx="663" cy="147.9" rx="254.3" ry="147.9"/>
<ellipse class="st0" cx="663" cy="1262.3" rx="254.3" ry="147.9"/>
<ellipse transform="matrix(0.5 -0.866 0.866 0.5 -279.1512 369.5916)" class="st0" cx="180.5" cy="426.5" rx="254.3" ry="148"/>
<ellipse transform="matrix(0.5 -0.866 0.866 0.5 -279.1552 1483.9517)" class="st0" cx="1145.6" cy="983.7" rx="254.3" ry="147.9"/>
<ellipse transform="matrix(0.866 -0.5 0.5 0.866 -467.6798 222.044)" class="st0" cx="180.5" cy="983.7" rx="148" ry="254.3"/>
<ellipse transform="matrix(0.866 -0.5 0.5 0.866 -59.8007 629.9254)" class="st0" cx="1145.6" cy="426.6" rx="147.9" ry="254.3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

3688
style.css Normal file

File diff suppressed because it is too large Load Diff