Setup 5d15508bba929f1f074c052ac933863eadf6eb8e56984ebd5a1af75e80626643 Portable a5d346b97f5a13d85fa3bd301a72075ddb82fe636d7b1a51840ffd5a16d879f4 Bundled since 0.3.27: 32d4b75 - Aegis (bchwallet) gains its own update card in Settings > General beside Ariadne. Check for updates hits the same signed OTA endpoint the boot timer uses; Restart to apply appears when a signed newer version is staged. Uses the existing addons-check-updates + a new app-restart IPC. New Aegis versions ship without a Theseus release. 32d4b75 (same commit) - DevTools (F12 / Ctrl+Shift+I) opens docked to the right of the tab (mode: 'right') instead of a detached window. Matches stock Chrome. Users who prefer detached can drag out via the DevTools own toolbar. b71c925 - Search-engine favicons in Settings > Search now use Google's /s2/favicons service — DuckDuckGo's ip3 source returned 404 for enough hosts (Brave, Bing, Yandex, etc.) that half the list was falling through to the emoji placeholder. Deployed. Verified LIVE 0.3.28.
506 lines
23 KiB
JavaScript
506 lines
23 KiB
JavaScript
// Aegis dapp bridges. Runs in the isolated world of every matching tab and
|
|
// puts up to two dapp APIs on the page's main world:
|
|
//
|
|
// window.bitcoincash Silent Mode's own BCH API (only on .x pages —
|
|
// matches the pre-multi-wallet gate).
|
|
// window.tronWeb TronLink-shaped Tron dapp bridge (on any page).
|
|
// window.tronLink TronLink shim; `tron_requestAccounts` triggers
|
|
// an approval overlay via activate().
|
|
//
|
|
// Everything crosses into the wallet's activate() context in main, which
|
|
// shows the approval overlay and enforces per-origin permissions. Nothing
|
|
// here can sign, read a balance, or read an address on its own.
|
|
//
|
|
// `theseus` is provided by the host: { id, origin, contextBridge, invoke }.
|
|
|
|
const call = (msg, payload) =>
|
|
theseus.invoke(msg, payload).catch((e) => {
|
|
const text = String(e && e.message || e).replace(/^Error invoking remote method '[^']+': (Error: )?/, "");
|
|
throw new Error(text);
|
|
});
|
|
|
|
// -------- BCH bridge (window.bitcoincash), only on .x pages ------------------
|
|
const isBchOrigin = (() => { try { return /\.x$/i.test(location.hostname); } catch { return false; } })();
|
|
if (isBchOrigin) {
|
|
theseus.contextBridge.exposeInMainWorld("bitcoincash", {
|
|
isTheseus: true,
|
|
version: "0.2.0",
|
|
network: "mainnet",
|
|
getAddress: () => call("getAddress"),
|
|
signAndSend: (txSpec) => call("signAndSend", txSpec && typeof txSpec === "object" ? txSpec : {}),
|
|
signMessage: (message) => call("signMessage", { message: String(message ?? "") }),
|
|
});
|
|
}
|
|
|
|
// -------- Tron bridge (window.tronWeb, window.tronLink), everywhere ---------
|
|
//
|
|
// Matches enough of the TronLink surface for read + send flows on dapps like
|
|
// tronscan, sunswap, and justlend (mainnet or Nile). Not implemented:
|
|
// TRC20/TRC721 abstractions, address utilities beyond base58<->hex, event
|
|
// emitters beyond the "message" events TronLink documents.
|
|
|
|
let CACHED = { address: null, network: null };
|
|
const listeners = new Set();
|
|
|
|
// Public copy of tronWeb.trx.
|
|
const trx = {
|
|
// Returns the account object TronGrid gives (may be {} for never-funded).
|
|
// Deliberately mirrors tronWeb.trx.getAccount.
|
|
getAccount: async () => call("trx.getAccount"),
|
|
// Sign an assembled transaction (dapp built it via its own tronWeb).
|
|
// Returns the same object with a `signature` field populated.
|
|
sign: async (transaction) => {
|
|
if (!transaction || typeof transaction !== "object" || !transaction.raw_data_hex) {
|
|
throw new Error("tronWeb.trx.sign: expected an assembled transaction with raw_data_hex");
|
|
}
|
|
return call("trx.signTransaction", { transaction });
|
|
},
|
|
// Broadcast a signed transaction. Returns { result: true, txid } on success.
|
|
sendRawTransaction: async (signedTx) => {
|
|
const r = await call("trx.sendRawTransaction", { transaction: signedTx });
|
|
return { result: true, txid: r.txid };
|
|
},
|
|
// Convenience wrappers matching the tronWeb surface — dapps call these
|
|
// when the wallet is supposed to build the tx too. Left unimplemented
|
|
// in this build so a dapp that hits them gets a clear error instead of
|
|
// silently unassembled behavior; assemble on the dapp side.
|
|
sendTrx: async () => { throw new Error("tronWeb.trx.sendTrx not implemented — build the transaction with tronWeb.transactionBuilder and call sign+sendRawTransaction"); },
|
|
// TronLink documents signMessage (legacy, keccak of just the message) and
|
|
// signMessageV2 (adds the "\x19TRON Signed Message:\n<len>" prefix). We
|
|
// only implement V2 because that is what modern dapps use.
|
|
signMessageV2: async (message) => {
|
|
const r = await call("trx.signMessageV2", { message: String(message ?? "") });
|
|
return r.signature;
|
|
},
|
|
};
|
|
|
|
// Shallow tronWeb — enough for `tronWeb.defaultAddress.base58` / `.hex` reads
|
|
// and the trx methods. Populated once the user connects.
|
|
const tronWeb = {
|
|
ready: false,
|
|
defaultAddress: { base58: false, hex: false, name: false },
|
|
fullNode: { host: "" },
|
|
solidityNode: { host: "" },
|
|
eventServer: { host: "" },
|
|
trx,
|
|
// Utility helpers a few dapps poke at.
|
|
isConnected: () => tronWeb.ready === true && !!tronWeb.defaultAddress.base58,
|
|
address: {
|
|
fromHex: (hex) => hex, // no-op stub; dapps that need a real
|
|
toHex: (base58) => base58, // conversion should use their own copy.
|
|
},
|
|
// Some dapps read this to decide "Tron mainnet vs testnet".
|
|
get currentNetwork() { return CACHED.network || null; },
|
|
};
|
|
|
|
function nodeHost(network) {
|
|
return network === "nile" ? "https://nile.trongrid.io" : "https://api.trongrid.io";
|
|
}
|
|
function applyConnection({ address, network }) {
|
|
CACHED = { address, network };
|
|
tronWeb.defaultAddress = { base58: address || false, hex: false, name: false };
|
|
tronWeb.ready = !!address;
|
|
tronLink.ready = !!address;
|
|
const host = nodeHost(network);
|
|
tronWeb.fullNode = { host };
|
|
tronWeb.solidityNode = { host };
|
|
tronWeb.eventServer = { host };
|
|
// Broadcast on the window in the way TronLink documents.
|
|
try {
|
|
window.postMessage({ message: { action: "accountsChanged", data: { address } }, isTronLink: true }, location.origin);
|
|
window.postMessage({ message: { action: "setAccount", data: { address } }, isTronLink: true }, location.origin);
|
|
window.postMessage({ message: { action: "setNode", data: { node: { fullNode: host, solidityNode: host, eventServer: host, chain: network === "nile" ? "0xcd8690dc" : "0x2b6653dc" } } }, isTronLink: true }, location.origin);
|
|
} catch {}
|
|
for (const fn of listeners) { try { fn({ address, network }); } catch {} }
|
|
}
|
|
|
|
const tronLink = {
|
|
ready: false,
|
|
tronWeb: tronWeb,
|
|
// TronLink's canonical request router. Handles at minimum tron_requestAccounts.
|
|
request: async (opts) => {
|
|
const method = String(opts?.method || "");
|
|
if (method === "tron_requestAccounts" || method === "requestAccounts") {
|
|
const r = await call("trx.requestAccounts");
|
|
applyConnection({ address: r.address, network: r.network });
|
|
return { code: 200, message: "ok" };
|
|
}
|
|
if (method === "tron_accounts") {
|
|
const r = await call("trx.getAccount").catch(() => null);
|
|
return r ? [r.address] : [];
|
|
}
|
|
throw new Error("Aegis: unsupported tronLink method " + method);
|
|
},
|
|
// Dapps sometimes attach here to react to wallet changes.
|
|
on: (event, fn) => {
|
|
if (event === "message" || event === "accountsChanged" || event === "networkChanged") listeners.add(fn);
|
|
},
|
|
removeListener: (_event, fn) => listeners.delete(fn),
|
|
};
|
|
|
|
theseus.contextBridge.exposeInMainWorld("tronWeb", tronWeb);
|
|
theseus.contextBridge.exposeInMainWorld("tronLink", tronLink);
|
|
|
|
// -------- Main-world bridges (window.ethereum, window.solana) ---------------
|
|
//
|
|
// EIP-1193 (Ethereum) and the Solana wallet-adapter both expect the wallet
|
|
// object to touch the dapp's own JS values — Solana in particular passes
|
|
// `Transaction` instances whose `.serializeMessage()` method the wallet has
|
|
// to call. Electron's contextBridge shallow-copies function arguments
|
|
// between worlds and strips methods, so bridges that need to call methods
|
|
// on dapp-side objects have to live in the main world. We inject a `<script>`
|
|
// with the bridge source; it runs synchronously in the main world and talks
|
|
// to us via `window.postMessage` on a namespaced envelope. This mirrors how
|
|
// MetaMask and Phantom bridge extension code back to page code.
|
|
|
|
// Namespace used on the postMessage envelope. Includes the addon id so a
|
|
// page that runs multiple dapp-wallet extensions doesn't misroute messages.
|
|
const AEGIS_TAG = "aegis-" + theseus.id;
|
|
const pendingCalls = new Map();
|
|
window.addEventListener("message", async (e) => {
|
|
const d = e && e.data;
|
|
if (!d || d.aegisTag !== AEGIS_TAG) return;
|
|
if (d.kind === "request") {
|
|
// Forward main-world → isolated-world → addon.
|
|
try {
|
|
const result = await call(d.msg, d.payload);
|
|
window.postMessage({ aegisTag: AEGIS_TAG, kind: "response", id: d.id, ok: true, result }, location.origin);
|
|
} catch (err) {
|
|
window.postMessage({ aegisTag: AEGIS_TAG, kind: "response", id: d.id, ok: false, error: String(err && err.message || err) }, location.origin);
|
|
}
|
|
}
|
|
});
|
|
function emitToMainWorld(event, data) {
|
|
try { window.postMessage({ aegisTag: AEGIS_TAG, kind: "event", event, data }, location.origin); } catch {}
|
|
}
|
|
|
|
// The main-world bridge — installed as a page-level `<script>` so it can
|
|
// call methods on Transaction objects the dapp hands it, and so the globals
|
|
// it defines look like ordinary page code to the dapp.
|
|
const mainWorldSource = `(function () {
|
|
if (window.__aegisBridge) return;
|
|
window.__aegisBridge = true;
|
|
const TAG = ${JSON.stringify(AEGIS_TAG)};
|
|
const pending = new Map();
|
|
let seq = 1;
|
|
function invoke(msg, payload) {
|
|
const id = "r" + (seq++);
|
|
return new Promise((resolve, reject) => {
|
|
pending.set(id, { resolve, reject });
|
|
window.postMessage({ aegisTag: TAG, kind: "request", id, msg, payload }, location.origin);
|
|
});
|
|
}
|
|
window.addEventListener("message", (e) => {
|
|
const d = e && e.data;
|
|
if (!d || d.aegisTag !== TAG) return;
|
|
if (d.kind === "response") {
|
|
const p = pending.get(d.id);
|
|
if (!p) return;
|
|
pending.delete(d.id);
|
|
d.ok ? p.resolve(d.result) : p.reject(new Error(d.error));
|
|
} else if (d.kind === "event") {
|
|
dispatchEvent(d.event, d.data);
|
|
}
|
|
});
|
|
|
|
// ---- Ethereum (EIP-1193) ------------------------------------------------
|
|
const ethListeners = { connect: [], disconnect: [], accountsChanged: [], chainChanged: [], message: [] };
|
|
let ethState = { address: null, chainIdHex: "0x1", networkVersion: "1" };
|
|
function ethEmit(event, data) {
|
|
for (const fn of ethListeners[event] || []) { try { fn(data); } catch {} }
|
|
}
|
|
// After a chain switch or add, refresh the local chainId/address state
|
|
// and fire the two events dapps expect (accountsChanged +
|
|
// chainChanged). MetaMask does the same round-trip after switchChain.
|
|
async function pullEthStateAndEmit() {
|
|
try {
|
|
const s = await invoke("eth.state");
|
|
const oldChain = ethState.chainIdHex, oldAddr = ethState.address;
|
|
ethState.address = s.address || null;
|
|
ethState.chainIdHex = s.chainIdHex;
|
|
ethState.networkVersion = s.networkVersion;
|
|
window.ethereum.selectedAddress = ethState.address;
|
|
window.ethereum.chainId = ethState.chainIdHex;
|
|
window.ethereum.networkVersion = ethState.networkVersion;
|
|
if (s.chainIdHex !== oldChain) ethEmit("chainChanged", s.chainIdHex);
|
|
if ((s.address || null) !== oldAddr) ethEmit("accountsChanged", s.address ? [s.address] : []);
|
|
} catch {}
|
|
}
|
|
async function ethHandle(method, params) {
|
|
params = Array.isArray(params) ? params : (params ? [params] : []);
|
|
switch (method) {
|
|
case "eth_requestAccounts": {
|
|
const r = await invoke("eth.requestAccounts");
|
|
ethState.address = r.address;
|
|
ethState.chainIdHex = r.chainIdHex;
|
|
ethState.networkVersion = r.networkVersion;
|
|
window.ethereum.selectedAddress = r.address;
|
|
window.ethereum.chainId = r.chainIdHex;
|
|
window.ethereum.networkVersion = r.networkVersion;
|
|
ethEmit("accountsChanged", [r.address]);
|
|
return [r.address];
|
|
}
|
|
case "eth_accounts":
|
|
return ethState.address ? [ethState.address] : [];
|
|
case "eth_chainId":
|
|
return ethState.chainIdHex;
|
|
case "net_version":
|
|
return ethState.networkVersion;
|
|
case "personal_sign": {
|
|
// Both param orders are seen in the wild: [message, from] and [from, message].
|
|
const [a, b] = params;
|
|
const looksLikeAddr = (s) => typeof s === "string" && /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
const message = looksLikeAddr(a) ? b : a;
|
|
return (await invoke("eth.personalSign", { message: String(message) })).signature;
|
|
}
|
|
case "eth_sign": {
|
|
// Legacy method. Same shape as personal_sign for our purposes.
|
|
const [_from, msg] = params;
|
|
return (await invoke("eth.personalSign", { message: String(msg) })).signature;
|
|
}
|
|
case "eth_sendTransaction": {
|
|
const tx = params[0] || {};
|
|
return (await invoke("eth.sendTransaction", { tx })).txid;
|
|
}
|
|
case "eth_signTypedData_v4":
|
|
case "eth_signTypedData":
|
|
case "eth_signTypedData_v3": {
|
|
// v3/v4 differ mostly in nested-struct support; the encoder handles
|
|
// both. v1 is the flat "type[]" schema that Metamask deprecated —
|
|
// reject it, dapps that still use v1 should upgrade.
|
|
const [a, b] = params;
|
|
const looksLikeAddr = (s) => typeof s === "string" && /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
const typedData = looksLikeAddr(a) ? b : a;
|
|
return (await invoke("eth.signTypedData", { typedData })).signature;
|
|
}
|
|
case "wallet_switchEthereumChain": {
|
|
const target = String((params[0] && params[0].chainId) || "").toLowerCase();
|
|
try {
|
|
const r = await invoke("eth.switchChain", { chainId: target });
|
|
await pullEthStateAndEmit();
|
|
return r;
|
|
}
|
|
catch (e) {
|
|
// EIP-3326: preserve the 4902 signal the isolated-world handler
|
|
// stamps on the Error so dapps fall through to addChain.
|
|
if (/is not added/i.test(e.message || "")) { const err = new Error(e.message); err.code = 4902; throw err; }
|
|
throw e;
|
|
}
|
|
}
|
|
case "wallet_addEthereumChain": {
|
|
// EIP-3085. Approval overlay + persistence live in the addon.
|
|
const r = await invoke("eth.addChain", { params: params[0] });
|
|
await pullEthStateAndEmit();
|
|
return r;
|
|
}
|
|
case "wallet_getPermissions":
|
|
case "wallet_requestPermissions":
|
|
// Minimum shape most dapps accept.
|
|
return [{ parentCapability: "eth_accounts" }];
|
|
default:
|
|
// Read passthrough: eth_getBalance, eth_call, eth_blockNumber, etc.
|
|
return invoke("eth.rpc", { method, params });
|
|
}
|
|
}
|
|
const ethereum = {
|
|
isAegis: true,
|
|
isMetaMask: false,
|
|
chainId: ethState.chainIdHex,
|
|
networkVersion: ethState.networkVersion,
|
|
selectedAddress: null,
|
|
request: (opts) => ethHandle(String(opts && opts.method || ""), (opts && opts.params) || []),
|
|
// EIP-1193 event surface.
|
|
on: (event, fn) => { if (ethListeners[event]) ethListeners[event].push(fn); },
|
|
removeListener: (event, fn) => {
|
|
const arr = ethListeners[event]; if (!arr) return;
|
|
const i = arr.indexOf(fn); if (i >= 0) arr.splice(i, 1);
|
|
},
|
|
// Legacy compat some old dapps still call.
|
|
enable: () => ethereum.request({ method: "eth_requestAccounts" }),
|
|
sendAsync: (payload, cb) => ethereum.request(payload).then((result) => cb(null, { id: payload.id, jsonrpc: "2.0", result }), (err) => cb(err)),
|
|
send: (methodOrPayload, params) => ethereum.request(typeof methodOrPayload === "string" ? { method: methodOrPayload, params } : methodOrPayload),
|
|
};
|
|
|
|
// ---- Solana (wallet-adapter shape) --------------------------------------
|
|
const solListeners = { connect: [], disconnect: [], accountChanged: [] };
|
|
let solState = { publicKey: null };
|
|
function makePubkey(base58) {
|
|
return {
|
|
toString: () => base58,
|
|
toBase58: () => base58,
|
|
toBytes: () => base58ToBytes(base58),
|
|
equals: (other) => other && other.toString && other.toString() === base58,
|
|
_bn: null,
|
|
};
|
|
}
|
|
// Local base58 decoder — needed to expose PublicKey.toBytes(). Alphabet
|
|
// matches Bitcoin's (the only base58 flavor in real use).
|
|
const B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
function base58ToBytes(s) {
|
|
let zeros = 0;
|
|
while (zeros < s.length && s[zeros] === B58[0]) zeros++;
|
|
const out = new Uint8Array(Math.ceil(s.length * 733 / 1000 + 1));
|
|
let length = 0;
|
|
for (let i = zeros; i < s.length; i++) {
|
|
const v = B58.indexOf(s[i]);
|
|
if (v < 0) throw new Error("bad base58 char " + s[i]);
|
|
let carry = v, j = 0;
|
|
for (let k = out.length - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
carry += 58 * out[k];
|
|
out[k] = carry & 0xff;
|
|
carry >>>= 8;
|
|
}
|
|
length = j;
|
|
}
|
|
let it = out.length - length;
|
|
while (it < out.length && out[it] === 0) it++;
|
|
const total = zeros + (out.length - it);
|
|
const dec = new Uint8Array(total);
|
|
let p = zeros;
|
|
while (it < out.length) dec[p++] = out[it++];
|
|
return dec;
|
|
}
|
|
function bytesToBase58(b) {
|
|
let zeros = 0;
|
|
while (zeros < b.length && b[zeros] === 0) zeros++;
|
|
const buf = new Uint8Array(Math.ceil(b.length * 138 / 100 + 1));
|
|
let length = 0;
|
|
for (let i = zeros; i < b.length; i++) {
|
|
let carry = b[i], j = 0;
|
|
for (let k = buf.length - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
carry += (buf[k] << 8) >>> 0;
|
|
buf[k] = carry % 58;
|
|
carry = (carry / 58) | 0;
|
|
}
|
|
length = j;
|
|
}
|
|
let it = buf.length - length;
|
|
while (it < buf.length && buf[it] === 0) it++;
|
|
let out = "";
|
|
for (let i = 0; i < zeros; i++) out += B58[0];
|
|
for (; it < buf.length; it++) out += B58[buf[it]];
|
|
return out;
|
|
}
|
|
function u8ToBase64(u8) {
|
|
let s = "";
|
|
for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]);
|
|
return btoa(s);
|
|
}
|
|
function base64ToU8(s) {
|
|
const bin = atob(s);
|
|
const u8 = new Uint8Array(bin.length);
|
|
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
|
|
return u8;
|
|
}
|
|
async function solConnect() {
|
|
const r = await invoke("sol.connect");
|
|
solState.publicKey = makePubkey(r.address);
|
|
window.solana.publicKey = solState.publicKey;
|
|
window.solana.isConnected = true;
|
|
for (const fn of solListeners.connect) { try { fn(solState.publicKey); } catch {} }
|
|
return { publicKey: solState.publicKey };
|
|
}
|
|
async function solDisconnect() {
|
|
solState.publicKey = null;
|
|
window.solana.publicKey = null;
|
|
window.solana.isConnected = false;
|
|
for (const fn of solListeners.disconnect) { try { fn(); } catch {} }
|
|
}
|
|
async function solSignMessage(u8) {
|
|
if (!(u8 instanceof Uint8Array)) u8 = new Uint8Array(u8 || []);
|
|
const r = await invoke("sol.signMessage", { messageB64: u8ToBase64(u8) });
|
|
return { publicKey: solState.publicKey, signature: base58ToBytes(r.signature) };
|
|
}
|
|
// Solana Transaction objects have serializeMessage() → Uint8Array and
|
|
// addSignature(publicKey, sig) — we live in the main world, so we can
|
|
// call both. Wallets that live in an isolated content-script can't.
|
|
async function solSignAndSendTransaction(tx, opts) {
|
|
if (!tx || typeof tx.serialize !== "function") {
|
|
throw new Error("Aegis: pass a @solana/web3.js Transaction (needs .serialize)");
|
|
}
|
|
// Serialize the FULL wire including any partial signatures the dapp
|
|
// has already collected (multi-signer flows: session keys, escrows,
|
|
// ephemeral co-signers). Aegis fills the wallet's own signature slot
|
|
// in the addon and leaves the other slots untouched.
|
|
const wire = tx.serialize({ requireAllSignatures: false, verifySignatures: false });
|
|
const r = await invoke("sol.signAndSend", { wireB64: u8ToBase64(new Uint8Array(wire)) });
|
|
return { signature: r.txid, publicKey: solState.publicKey };
|
|
}
|
|
async function solSignTransaction(tx) {
|
|
if (!tx || typeof tx.serializeMessage !== "function" || typeof tx.addSignature !== "function") {
|
|
throw new Error("Aegis: pass a @solana/web3.js Transaction");
|
|
}
|
|
const messageBytes = tx.serializeMessage();
|
|
const r = await invoke("sol.signMessage", { messageB64: u8ToBase64(new Uint8Array(messageBytes)) });
|
|
// r.signature is base58 of the 64-byte ed25519 sig.
|
|
tx.addSignature(solState.publicKey, base58ToBytes(r.signature));
|
|
return tx;
|
|
}
|
|
const solana = {
|
|
isAegis: true,
|
|
isPhantom: true, // set so dapps that gate on isPhantom pick us
|
|
isConnected: false,
|
|
publicKey: null,
|
|
connect: async (opts) => solConnect(opts),
|
|
disconnect: solDisconnect,
|
|
signMessage: (u8) => solSignMessage(u8),
|
|
signTransaction: (tx) => solSignTransaction(tx),
|
|
signAllTransactions: async (txs) => {
|
|
const out = [];
|
|
for (const tx of txs) out.push(await solSignTransaction(tx));
|
|
return out;
|
|
},
|
|
signAndSendTransaction: (tx, opts) => solSignAndSendTransaction(tx, opts),
|
|
request: async (opts) => {
|
|
const method = String(opts && opts.method || "");
|
|
const params = opts && opts.params || {};
|
|
if (method === "connect") return solConnect();
|
|
if (method === "disconnect") return solDisconnect();
|
|
if (method === "signMessage") return solSignMessage(params.message);
|
|
if (method === "signTransaction") return solSignTransaction(params.transaction);
|
|
if (method === "signAndSendTransaction") return solSignAndSendTransaction(params.transaction, params.options);
|
|
throw new Error("Aegis: unsupported solana method " + method);
|
|
},
|
|
on: (event, fn) => { if (solListeners[event]) solListeners[event].push(fn); },
|
|
off: (event, fn) => {
|
|
const arr = solListeners[event]; if (!arr) return;
|
|
const i = arr.indexOf(fn); if (i >= 0) arr.splice(i, 1);
|
|
},
|
|
removeAllListeners: () => { Object.keys(solListeners).forEach((k) => solListeners[k].length = 0); },
|
|
};
|
|
|
|
function dispatchEvent(event, data) {
|
|
if (event === "eth.accountsChanged") { ethState.address = data.address || null; ethereum.selectedAddress = ethState.address; ethEmit("accountsChanged", data.address ? [data.address] : []); }
|
|
else if (event === "eth.chainChanged") { ethState.chainIdHex = data.chainIdHex; ethState.networkVersion = data.networkVersion; ethereum.chainId = data.chainIdHex; ethereum.networkVersion = data.networkVersion; ethEmit("chainChanged", data.chainIdHex); }
|
|
else if (event === "sol.accountChanged") {
|
|
const pk = data.address ? makePubkey(data.address) : null;
|
|
solState.publicKey = pk; solana.publicKey = pk;
|
|
for (const fn of solListeners.accountChanged || []) { try { fn(pk); } catch {} }
|
|
}
|
|
}
|
|
|
|
// Install the globals. Defined lazily via Object.defineProperty so we
|
|
// survive dapps that check hasOwnProperty(window, "ethereum") after page
|
|
// load — MetaMask does the same trick.
|
|
try { Object.defineProperty(window, "ethereum", { value: ethereum, writable: false, configurable: false }); } catch { window.ethereum = ethereum; }
|
|
try { Object.defineProperty(window, "solana", { value: solana, writable: false, configurable: false }); } catch { window.solana = solana; }
|
|
// EIP-6963 provider announcement so wagmi / RainbowKit discover Aegis.
|
|
try {
|
|
const info = { uuid: crypto.randomUUID(), name: "Aegis", icon: "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpolygon points='16,2 29,9 29,23 16,30 3,23 3,9' fill='none' stroke='%23d6ff3d' stroke-width='2.5'/%3E%3Ccircle cx='16' cy='16' r='4.3' fill='none' stroke='%23d6ff3d' stroke-width='1.6'/%3E%3Ccircle cx='16' cy='16' r='1.6' fill='%23d6ff3d'/%3E%3C/svg%3E", rdns: "st.silentmode.aegis" };
|
|
const announce = () => window.dispatchEvent(new CustomEvent("eip6963:announceProvider", { detail: Object.freeze({ info, provider: ethereum }) }));
|
|
announce();
|
|
window.addEventListener("eip6963:requestProvider", announce);
|
|
} catch {}
|
|
})();`;
|
|
|
|
// Actually push the script into the main world. Doing this at
|
|
// document_start (which is when this preload runs) means the bridge is in
|
|
// place before the dapp's own scripts execute.
|
|
try {
|
|
const s = document.createElement("script");
|
|
s.textContent = mainWorldSource;
|
|
(document.head || document.documentElement).appendChild(s);
|
|
s.remove();
|
|
} catch (e) {
|
|
console.warn("[aegis] main-world bridge install failed:", e && e.message || e);
|
|
}
|