Workflow updating files of polkadotwallet
This commit is contained in:
parent
d7070a9dea
commit
bf6d0e50e6
2
polkadotwallet/README.md
Normal file
2
polkadotwallet/README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# polkadotwallet
|
||||
FLO / BTC linked Polkadot web wallet from RanchiMall
|
||||
2451
polkadotwallet/index.html
Normal file
2451
polkadotwallet/index.html
Normal file
File diff suppressed because it is too large
Load Diff
11473
polkadotwallet/lib.polkadot.js
Normal file
11473
polkadotwallet/lib.polkadot.js
Normal file
File diff suppressed because it is too large
Load Diff
2
polkadotwallet/polkadot-api-bundle.js
Normal file
2
polkadotwallet/polkadot-api-bundle.js
Normal file
File diff suppressed because one or more lines are too long
536
polkadotwallet/polkadotBlockchainAPI.js
Normal file
536
polkadotwallet/polkadotBlockchainAPI.js
Normal 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
polkadotwallet/polkadotCrypto.js
Normal file
322
polkadotwallet/polkadotCrypto.js
Normal 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
polkadotwallet/polkadotSearchDB.js
Normal file
136
polkadotwallet/polkadotSearchDB.js
Normal 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
polkadotwallet/polkadot_favicon.svg
Normal file
14
polkadotwallet/polkadot_favicon.svg
Normal 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
polkadotwallet/style.css
Normal file
3688
polkadotwallet/style.css
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user