94 lines
6.5 KiB
JavaScript
94 lines
6.5 KiB
JavaScript
/* The Igneum Wallet's page provider (8 October 2026): an EIP-1193 provider for a page when the Igneum Wallet runs on
|
|
* the same machine. The wallet's engine listens on 127.0.0.1:26811 (its page bridge, app/igneum-wallet/src/bridge.rs);
|
|
* this file POSTs JSON-RPC there with the X-Igneum-Bridge header (so the browser sends a CORS preflight the bridge
|
|
* answers for the page's origin) and polls a request the wallet window has to answer (connect, send, sign) until the
|
|
* person decides there. Nothing is signed on the page; the wallet shows the facts and asks in its own window.
|
|
*
|
|
* <script src="/wallet-provider.js"></script> before the page's own script
|
|
*
|
|
* Rules: a page that already has window.ethereum (MetaMask) keeps it; the Igneum provider is then window.igneum and is
|
|
* announced through EIP-6963 (rdns network.igneum.wallet) so a wallet picker can list it. Without an injected wallet,
|
|
* the Igneum provider is window.ethereum. A request while the wallet is not open rejects with code 4900. Errors come
|
|
* back as {code, message} (EIP-1193 codes: 4001 declined, 4100 not connected, 4200 not offered, 4902 another chain).
|
|
* Tests: site/wallet-provider.test.mjs. */
|
|
(function (win) {
|
|
var BASE = 'http://127.0.0.1:26811', POLL_MS = 800, TICK_MS = 4000;
|
|
var seq = 0, hello = null, helloAt = 0, listeners = {}, lastAccounts = '', lastChain = '';
|
|
function err(code, message) { var e = new Error(message); e.code = code; return e; }
|
|
function emit(ev, arg) { (listeners[ev] || []).slice().forEach(function (f) { try { f(arg); } catch (e) { } }); }
|
|
function probe() {
|
|
var now = Date.now();
|
|
if (hello && now - helloAt < 10000) return Promise.resolve(hello);
|
|
return fetch(BASE + '/bridge/hello', { method: 'GET', cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (j) {
|
|
if (!j || !j.ok || j.wallet !== 'igneum') throw err(4900, 'The Igneum Wallet is not open on this computer');
|
|
hello = j; helloAt = Date.now(); return j;
|
|
}).catch(function (e) { hello = null; throw e.code ? e : err(4900, 'The Igneum Wallet is not open on this computer. Open it, then try again.'); });
|
|
}
|
|
function rpc(method, params) {
|
|
var id = ++seq;
|
|
return fetch(BASE + '/bridge/rpc', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Igneum-Bridge': '1' }, body: JSON.stringify({ jsonrpc: '2.0', id: id, method: method, params: params || [] }), cache: 'no-store' })
|
|
.then(function (r) { return r.json(); }, function () { throw err(4900, 'The Igneum Wallet stopped answering. Open it, then try again.'); });
|
|
}
|
|
function settle(j) {
|
|
if (j && j.error) throw err(typeof j.error.code === 'number' ? j.error.code : -32603, j.error.message || 'the wallet refused');
|
|
return j ? j.result : null;
|
|
}
|
|
function wait(id) {
|
|
return new Promise(function (resolve, reject) {
|
|
function again() {
|
|
rpc('igneum_poll', [id]).then(function (j) {
|
|
if (j && j.pending) { win.setTimeout(again, POLL_MS); return; }
|
|
try { resolve(settle(j)); } catch (e) { reject(e); }
|
|
}, reject);
|
|
}
|
|
win.setTimeout(again, POLL_MS);
|
|
});
|
|
}
|
|
var provider = {
|
|
isIgneum: true,
|
|
isConnected: function () { return !!hello; },
|
|
selectedAddress: null,
|
|
chainId: null,
|
|
request: function (args) {
|
|
if (!args || typeof args.method !== 'string') return Promise.reject(err(-32602, 'request({method, params})'));
|
|
var method = args.method, params = args.params || [];
|
|
return probe().then(function () { return rpc(method, params); }).then(function (j) {
|
|
if (j && j.pending) return wait(j.pending);
|
|
return settle(j);
|
|
}).then(function (result) {
|
|
if (method === 'eth_requestAccounts' || method === 'eth_accounts') { provider.selectedAddress = Array.isArray(result) && result.length ? result[0] : null; }
|
|
if (method === 'eth_chainId' && typeof result === 'string') provider.chainId = result;
|
|
return result;
|
|
});
|
|
},
|
|
on: function (ev, f) { (listeners[ev] = listeners[ev] || []).push(f); return provider; },
|
|
removeListener: function (ev, f) { listeners[ev] = (listeners[ev] || []).filter(function (g) { return g !== f; }); return provider; },
|
|
// the poll behind accountsChanged and chainChanged (tests call it directly)
|
|
_tick: function () {
|
|
return (hello ? Promise.resolve() : probe()).then(function () { return rpc('eth_accounts', []); }).then(function (j) {
|
|
var acc = (j && !j.error && Array.isArray(j.result)) ? j.result : [];
|
|
var key = acc.join(',');
|
|
if (lastAccounts !== '' || key !== '') { if (key !== lastAccounts) { lastAccounts = key; provider.selectedAddress = acc[0] || null; emit('accountsChanged', acc); } } else lastAccounts = key;
|
|
return rpc('eth_chainId', []);
|
|
}).then(function (j) {
|
|
var c = j && !j.error && typeof j.result === 'string' ? j.result : '';
|
|
if (c && lastChain && c !== lastChain) emit('chainChanged', c);
|
|
if (c) { lastChain = c; provider.chainId = c; }
|
|
}).catch(function () { });
|
|
},
|
|
// legacy shapes some libraries still call
|
|
enable: function () { return provider.request({ method: 'eth_requestAccounts' }); },
|
|
send: function (method, params) { return provider.request(typeof method === 'string' ? { method: method, params: params } : method); },
|
|
sendAsync: function (payload, cb) { provider.request(payload).then(function (r) { cb(null, { id: payload.id, jsonrpc: '2.0', result: r }); }, function (e) { cb(e); }); }
|
|
};
|
|
var ICON = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="22" fill="#0C0C0E"/><path d="M50 10 L78 50 L50 90 L22 50 Z M50 36 L61 50 L50 64 L39 50 Z" fill="#F2541B" fill-rule="evenodd"/></svg>');
|
|
var info = { uuid: 'c0b3a5a6-6b2e-4a0a-9d0e-igneumwallet1', name: 'Igneum Wallet', icon: ICON, rdns: 'network.igneum.wallet' };
|
|
function announce() { try { win.dispatchEvent(new CustomEvent('eip6963:announceProvider', { detail: Object.freeze({ info: info, provider: provider }) })); } catch (e) { } }
|
|
win.igneum = provider;
|
|
if (!win.ethereum) win.ethereum = provider;
|
|
announce();
|
|
win.addEventListener('eip6963:requestProvider', announce);
|
|
// the first probe tells the page whether the wallet is here; the tick keeps accountsChanged honest
|
|
probe().then(function () { provider._tick(); }, function () { });
|
|
win.setInterval(function () { if (hello) provider._tick(); else probe().catch(function () { }); }, TICK_MS);
|
|
})(typeof window !== 'undefined' ? window : this);
|