Multi-wallet support: EIP-6963 discovery + picker

Detects all injected wallets via EIP-6963 (Trust, MetaMask, SafePal, Phantom,
OKX, TokenPocket, and any 6963 wallet), with named fallbacks for ones that don't
announce and window.ethereum(.providers). One wallet connects directly; multiple
show a picker. connect()/sendTx() resolve a provider before use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 08:24:28 -05:00
parent 4a9e4c789e
commit 9fd85e0d6c
3 changed files with 62 additions and 4 deletions
+60 -2
View File
@@ -5,9 +5,65 @@ window.IAPWallet = (function () {
const SEL_ACTIVATE = '0x1a93ec95'; // activate(uint32)
const pad = v => BigInt(v).toString(16).padStart(64, '0');
// ── multi-wallet: EIP-6963 discovery + a picker; falls back to window.ethereum.
// Supports Trust, MetaMask, SafePal, Phantom, OKX, TokenPocket and any 6963 wallet.
const discovered = [];
try {
window.addEventListener('eip6963:announceProvider', e => {
const d = e.detail;
if (d && d.provider && !discovered.some(x => x.info && d.info && x.info.uuid === d.info.uuid)) discovered.push(d);
});
window.dispatchEvent(new Event('eip6963:requestProvider'));
} catch (e) {}
let CHOSEN = null;
function providerList() {
const seen = new Set(), out = [];
for (const d of discovered) { const k = (d.info && (d.info.rdns || d.info.uuid)) || Math.random(); if (!seen.has(k)) { seen.add(k); out.push(d); } }
const add = (p, name) => { if (p && !out.some(x => x.provider === p)) out.push({ info: { name }, provider: p }); };
if (window.ethereum) {
if (Array.isArray(window.ethereum.providers)) window.ethereum.providers.forEach(p => add(p, p.isMetaMask ? 'MetaMask' : p.isTrust || p.isTrustWallet ? 'Trust Wallet' : 'Injected wallet'));
else add(window.ethereum, window.ethereum.isMetaMask ? 'MetaMask' : window.ethereum.isTrust || window.ethereum.isTrustWallet ? 'Trust Wallet' : 'Injected wallet');
}
try { add(window.phantom && window.phantom.ethereum, 'Phantom'); } catch (e) {}
add(window.okxwallet, 'OKX Wallet');
try { add(window.tokenpocket && window.tokenpocket.ethereum, 'TokenPocket'); } catch (e) {}
add(window.safepal, 'SafePal');
return out;
}
function pickModal(list) {
return new Promise(resolve => {
const back = document.createElement('div');
back.style.cssText = 'position:fixed;inset:0;z-index:200;display:grid;place-items:center;background:rgba(2,10,8,.72)';
const card = document.createElement('div');
card.style.cssText = 'background:var(--panel-solid,#0b1512);border:1px solid var(--line-strong,#2c5044);border-radius:16px;padding:20px;max-width:360px;width:92%';
card.innerHTML = '<h3 style="margin:0 0 12px">Choose your wallet</h3>';
const close = v => { if (back.parentNode) document.body.removeChild(back); resolve(v); };
list.forEach(d => {
const b = document.createElement('button');
b.className = 'btn sec'; b.type = 'button';
b.style.cssText = 'display:flex;align-items:center;gap:10px;width:100%;justify-content:flex-start;margin:6px 0';
b.innerHTML = (d.info && d.info.icon ? '<img src="' + d.info.icon + '" alt="" width="22" height="22" style="border-radius:5px">' : '') + '<span>' + ((d.info && d.info.name) || 'Wallet') + '</span>';
b.addEventListener('click', () => close(d));
card.appendChild(b);
});
const cancel = document.createElement('button');
cancel.className = 'btn sec small'; cancel.type = 'button'; cancel.textContent = 'Cancel'; cancel.style.marginTop = '10px';
cancel.addEventListener('click', () => close(null));
card.appendChild(cancel); back.appendChild(card); document.body.appendChild(back);
});
}
async function ensureProvider() {
if (CHOSEN && CHOSEN.provider) return CHOSEN.provider;
const list = providerList();
if (!list.length) throw new Error('No wallet found. Open this page in your wallet app (Trust, MetaMask, SafePal, Phantom, OKX, TokenPocket) or install a wallet extension.');
CHOSEN = list.length === 1 ? list[0] : await pickModal(list);
if (!CHOSEN) throw new Error('No wallet selected.');
return CHOSEN.provider;
}
function eth() {
if (!window.ethereum) throw new Error('No wallet found. Open this page in a browser with MetaMask (or a wallet browser).');
return window.ethereum;
if (CHOSEN && CHOSEN.provider) return CHOSEN.provider;
if (window.ethereum) return window.ethereum;
throw new Error('No wallet found. Open this page in your wallet app or install a wallet extension.');
}
async function ensureChain(c) {
const want = '0x' + Number(c.chainId).toString(16);
@@ -28,6 +84,7 @@ window.IAPWallet = (function () {
}
async function connect() {
const c = await IAP.getConfig();
await ensureProvider();
const [addr] = await eth().request({ method: 'eth_requestAccounts' });
await ensureChain(c);
return addr;
@@ -50,6 +107,7 @@ window.IAPWallet = (function () {
}
async function sendTx(data, valueWei) {
const c = await IAP.getConfig();
await ensureProvider();
const [addr] = await eth().request({ method: 'eth_requestAccounts' });
await ensureChain(c);
const tx = { from: addr, to: c.contract, data };
+1 -1
View File
@@ -438,7 +438,7 @@
</section>
<script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/wallet.js?v=20260907f"></script>
<script src="/assets/wallet.js?v=20260907q"></script>
<script src="/assets/home.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script>
</body>
+1 -1
View File
@@ -643,7 +643,7 @@
</div>
</div>
<script src="/assets/common.js?v=20260907n"></script>
<script src="/assets/wallet.js?v=20260907f"></script>
<script src="/assets/wallet.js?v=20260907q"></script>
<script src="/assets/my.js?v=20260907p"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>