diff --git a/chrome-extension/public/injected.js b/chrome-extension/public/injected.js index d4fed39..3dce14a 100644 --- a/chrome-extension/public/injected.js +++ b/chrome-extension/public/injected.js @@ -1,15 +1,15 @@ -"use strict";(()=>{var N="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function M(l){let a=[0];for(let i of l){let d=N.indexOf(i);if(d===-1)throw new Error("Invalid base58 character");let n=d;for(let h=0;h>=8;for(;n>0;)a.push(n&255),n>>=8}for(let i of l){if(i!=="1")break;a.push(0)}return new Uint8Array(a.reverse())}var U=class l{#o;#e=[];#t=null;#s=new Set;version="1.0.0";name="KeepKey";icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAADUklEQVRYCb1XTUgUYRie3bXEWhVLQaUsgwVLoUtEQjUJiZX0A0GX7BIZXurkOTSvdo2kvETHAsOshFgqOqhlRD9C7SGS1JTCsj1krU7PM+w7zMzOzuzMqi88+73v9z7vz3zzzTeziuIgmqbFgG5gBPguFOgq4CXLIMwCo0AXEJN4zxHkEuA6kAIMkUBMqMZk7so/UG8AUcnjOIKwFXgHZIgEwKFmOHOfYO4aySVjmAoc7O4R0EB7lYS5h9K1jBJ6A7CuAfXG7OopbKLXkh4dccNZ7jlsi0gAJlWLI5jBPWFsTK5AGxCRImswFqDGWanDBo6IsYbjUanFbmrFWIHxD3IsmfJsgB4y2aJuF4UrUC5GnuNtxJeEQqEoAb3LJV+F4ctlHwkZXDULv8fEKQCHB4+rCJ9ngKcIGUTVRubT027y8yR9bOM4mhKTTwNJZD4miaDXAG8dqzlMShw3YRCZRVAr7vU4g5F/D4ZBoJK2H+Em9CsfEdBoKn4K9jPAd3G9sMPqZEzpRPzAwRfWJpN9EfZSRkAOE5LD7wrw8dkpwRh55VMm27fqt4FiVBjGBTaxEm4Db8d+4BPtIOK3AdbYCPC1qh/haGIS9gHgDeBbgjTAIkXAfTRxkgaamMNwCHgB+BMk4Decq0hGkFQbka/WMyZ/EeyHNo6TuSwx3Nn8gHQVIYOkOhB5Gp4zcdbBHiDvZ2pRuzozru2euKuDOucg/KliTAjKKMa9ksBpxBLrbzRwVfifOnB4RR2g3QSH3Cfx5FRdc2KoGstroUeQKh47vnAwWvUKjsPcA/wWdBUkjRAgZdsznO8D5xLGC/Opxc3NiQeV9uIsgkNDaUoMFpNDLleAn0cTQNBjGaFW6fn2Wrky/dI6abPOl9eN9deoWhjLloCv3+bPy7w3/9kzfvjX120g1cuSdsJ47xm1CgS9AaxCErlbV6qJ02W1nq22lG75AtIHWQEeJpOYaAT6gBQQWC5XNCjc7dkkHFKWe6v3FcLfbzRAMlcC6IC6C+gGxgCectZnCRMuopVG1v+Nx04sYINlxLH4wI6W52UFhT+Q41b2Nl0qeLnwZPGQucNHrXN6ZDG94RQuO688XbwNFzvjlSuwH03wEW8H+Bf/dxrUOWdc+H8mKXtEpGpY3AAAAABJRU5ErkJggg==";chains=["solana:mainnet"];static ACCOUNT_FEATURES=["solana:signTransaction","solana:signAndSendTransaction","solana:signMessage"];get accounts(){return this.#e}features={"standard:connect":{version:"1.0.0",connect:async()=>{if(this.#e.length>0)return{accounts:this.#e};let a=this.#t||await this.#n("solana_connect",[]);return a&&this.#a(a),{accounts:this.#e}}},"standard:disconnect":{version:"1.0.0",disconnect:async()=>{await this.#n("solana_disconnect",[]).catch(()=>{}),this.#e=[];try{localStorage.removeItem("keepkey-solana")}catch{}this.#r()}},"standard:events":{version:"1.0.0",on:(a,i)=>(a==="change"&&this.#s.add(i),()=>{this.#s.delete(i)})},"solana:signMessage":{version:"1.0.0",signMessage:async(...a)=>{let i=[];for(let{message:d}of a){let n=await this.#n("solana_signMessage",[Array.from(d)]);i.push({signedMessage:d,signature:new Uint8Array(n)})}return i}},"solana:signTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signTransaction:async(...a)=>{let i=[];for(let{transaction:d}of a){let n=await this.#n("solana_signTransaction",[Array.from(d)]);i.push({signedTransaction:new Uint8Array(n)})}return i}},"solana:signAndSendTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signAndSendTransaction:async(...a)=>{let i=[];for(let{transaction:d}of a){let n=await this.#n("solana_signAndSendTransaction",[Array.from(d)]);i.push({signature:M(n)})}return i}},"solana:signIn":{version:"1.0.0",signIn:async(...a)=>{var d;let i=[];for(let n of a){if(this.#e.length===0){let m=this.#t||await this.#n("solana_connect",[]);m&&this.#a(m)}let h=this.#e[0];if(!h)throw new Error("Not connected");let y=(n==null?void 0:n.domain)||location.host,p=(n==null?void 0:n.address)||h.address,C=(n==null?void 0:n.uri)||location.href,b=(n==null?void 0:n.version)||"1",E=(n==null?void 0:n.chainId)||"mainnet",v=(n==null?void 0:n.nonce)||Math.random().toString(36).substring(2),I=(n==null?void 0:n.issuedAt)||new Date().toISOString(),R=(n==null?void 0:n.statement)||"",f=`${y} wants you to sign in with your Solana account: -${p}`;if(R&&(f+=` +"use strict";(()=>{var N="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function M(f){let o=[0];for(let r of f){let c=N.indexOf(r);if(c===-1)throw new Error("Invalid base58 character");let e=c;for(let l=0;l>=8;for(;e>0;)o.push(e&255),e>>=8}for(let r of f){if(r!=="1")break;o.push(0)}return new Uint8Array(o.reverse())}var R=class f{#a;#e=[];#t=null;#s=new Set;version="1.0.0";name="KeepKey";icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAADUklEQVRYCb1XTUgUYRie3bXEWhVLQaUsgwVLoUtEQjUJiZX0A0GX7BIZXurkOTSvdo2kvETHAsOshFgqOqhlRD9C7SGS1JTCsj1krU7PM+w7zMzOzuzMqi88+73v9z7vz3zzzTeziuIgmqbFgG5gBPguFOgq4CXLIMwCo0AXEJN4zxHkEuA6kAIMkUBMqMZk7so/UG8AUcnjOIKwFXgHZIgEwKFmOHOfYO4aySVjmAoc7O4R0EB7lYS5h9K1jBJ6A7CuAfXG7OopbKLXkh4dccNZ7jlsi0gAJlWLI5jBPWFsTK5AGxCRImswFqDGWanDBo6IsYbjUanFbmrFWIHxD3IsmfJsgB4y2aJuF4UrUC5GnuNtxJeEQqEoAb3LJV+F4ctlHwkZXDULv8fEKQCHB4+rCJ9ngKcIGUTVRubT027y8yR9bOM4mhKTTwNJZD4miaDXAG8dqzlMShw3YRCZRVAr7vU4g5F/D4ZBoJK2H+Em9CsfEdBoKn4K9jPAd3G9sMPqZEzpRPzAwRfWJpN9EfZSRkAOE5LD7wrw8dkpwRh55VMm27fqt4FiVBjGBTaxEm4Db8d+4BPtIOK3AdbYCPC1qh/haGIS9gHgDeBbgjTAIkXAfTRxkgaamMNwCHgB+BMk4Decq0hGkFQbka/WMyZ/EeyHNo6TuSwx3Nn8gHQVIYOkOhB5Gp4zcdbBHiDvZ2pRuzozru2euKuDOucg/KliTAjKKMa9ksBpxBLrbzRwVfifOnB4RR2g3QSH3Cfx5FRdc2KoGstroUeQKh47vnAwWvUKjsPcA/wWdBUkjRAgZdsznO8D5xLGC/Opxc3NiQeV9uIsgkNDaUoMFpNDLleAn0cTQNBjGaFW6fn2Wrky/dI6abPOl9eN9deoWhjLloCv3+bPy7w3/9kzfvjX120g1cuSdsJ47xm1CgS9AaxCErlbV6qJ02W1nq22lG75AtIHWQEeJpOYaAT6gBQQWC5XNCjc7dkkHFKWe6v3FcLfbzRAMlcC6IC6C+gGxgCectZnCRMuopVG1v+Nx04sYINlxLH4wI6W52UFhT+Q41b2Nl0qeLnwZPGQucNHrXN6ZDG94RQuO688XbwNFzvjlSuwH03wEW8H+Bf/dxrUOWdc+H8mKXtEpGpY3AAAAABJRU5ErkJggg==";chains=["solana:mainnet"];static ACCOUNT_FEATURES=["solana:signTransaction","solana:signAndSendTransaction","solana:signMessage"];get accounts(){return this.#e}features={"standard:connect":{version:"1.0.0",connect:async()=>{if(this.#e.length>0)return{accounts:this.#e};let o=this.#t||await this.#n("solana_connect",[]);return o&&this.#o(o),{accounts:this.#e}}},"standard:disconnect":{version:"1.0.0",disconnect:async()=>{await this.#n("solana_disconnect",[]).catch(()=>{}),this.#e=[];try{localStorage.removeItem("keepkey-solana")}catch{}this.#i()}},"standard:events":{version:"1.0.0",on:(o,r)=>(o==="change"&&this.#s.add(r),()=>{this.#s.delete(r)})},"solana:signMessage":{version:"1.0.0",signMessage:async(...o)=>{let r=[];for(let{message:c}of o){let e=await this.#n("solana_signMessage",[Array.from(c)]);r.push({signedMessage:c,signature:new Uint8Array(e)})}return r}},"solana:signTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signTransaction:async(...o)=>{let r=[];for(let{transaction:c}of o){let e=await this.#n("solana_signTransaction",[Array.from(c)]);r.push({signedTransaction:new Uint8Array(e)})}return r}},"solana:signAndSendTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signAndSendTransaction:async(...o)=>{let r=[];for(let{transaction:c}of o){let e=await this.#n("solana_signAndSendTransaction",[Array.from(c)]);r.push({signature:M(e)})}return r}},"solana:signIn":{version:"1.0.0",signIn:async(...o)=>{var c;let r=[];for(let e of o){if(this.#e.length===0){let v=this.#t||await this.#n("solana_connect",[]);v&&this.#o(v)}let l=this.#e[0];if(!l)throw new Error("Not connected");let y=(e==null?void 0:e.domain)||location.host,I=(e==null?void 0:e.address)||l.address,T=(e==null?void 0:e.uri)||location.href,w=(e==null?void 0:e.version)||"1",m=(e==null?void 0:e.chainId)||"mainnet",E=(e==null?void 0:e.nonce)||Math.random().toString(36).substring(2),S=(e==null?void 0:e.issuedAt)||new Date().toISOString(),b=(e==null?void 0:e.statement)||"",u=`${y} wants you to sign in with your Solana account: +${I}`;if(b&&(u+=` -${R}`),f+=` +${b}`),u+=` -URI: ${C}`,f+=` -Version: ${b}`,f+=` -Chain ID: ${E}`,f+=` -Nonce: ${v}`,f+=` -Issued At: ${I}`,n!=null&&n.expirationTime&&(f+=` -Expiration Time: ${n.expirationTime}`),n!=null&&n.notBefore&&(f+=` -Not Before: ${n.notBefore}`),n!=null&&n.requestId&&(f+=` -Request ID: ${n.requestId}`),(d=n==null?void 0:n.resources)!=null&&d.length){f+=` -Resources:`;for(let m of n.resources)f+=` -- ${m}`}let k=new TextEncoder().encode(f),T=await this.#n("solana_signMessage",[Array.from(k)]);i.push({account:h,signedMessage:k,signature:new Uint8Array(T)})}return i}}};constructor(a){this.#o=a;try{let i=localStorage.getItem("keepkey-solana");if(i){let{address:d}=JSON.parse(i);d&&typeof d=="string"&&(this.#t=d)}}catch{}this.#c()}#i(a){return{address:a,publicKey:M(a),chains:["solana:mainnet"],features:[...l.ACCOUNT_FEATURES]}}#a(a){this.#e=[this.#i(a)];try{localStorage.setItem("keepkey-solana",JSON.stringify({address:a}))}catch{}this.#r()}async#c(){try{let a=await this.#n("solana_connect",[]);if(a&&typeof a=="string"){this.#t=a;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:a}))}catch{}}}catch{}}#r(){let a=this.#e,i=this.features;this.#s.forEach(d=>{try{d({accounts:a,features:i})}catch{}})}#n(a,i){return new Promise((d,n)=>{this.#o(a,i,"solana",(h,y)=>{h?n(h):d(y)})})}};function O(l){let a=({register:i})=>{i(l)};try{let i=window.navigator;i.wallets||(i.wallets=[]),Array.isArray(i.wallets)?i.wallets.push(a):typeof i.wallets.register=="function"&&i.wallets.register(l)}catch{}try{window.dispatchEvent(new CustomEvent("wallet-standard:register-wallet",{detail:a}))}catch{}window.addEventListener("wallet-standard:app-ready",i=>{let d=i;try{typeof d.detail=="function"&&d.detail(a)}catch{}})}(function(){let l=" | KeepKeyInjected | ",a="2.1.0",y=window,p={isInjected:!1,version:a,injectedAt:Date.now(),retryCount:0};if(y.keepkeyInjectionState){let o=y.keepkeyInjectionState;if(console.warn(l,`Existing injection detected v${o.version}, current v${a}`),o.version>=a){console.log(l,"Skipping injection, newer or same version already present");return}console.log(l,"Upgrading injection to newer version")}y.keepkeyInjectionState=p,console.log(l,`Initializing KeepKey Injection v${a}`);let C={siteUrl:window.location.href,scriptSource:"KeepKey Extension",version:a,injectedTime:new Date().toISOString(),origin:window.location.origin,protocol:window.location.protocol},b=0,E=new Map,v=[],I=!1;setInterval(()=>{let o=Date.now();E.forEach((t,s)=>{o-t.timestamp>3e5&&(console.warn(l,`Callback timeout for request ${s} (${t.method})`),t.callback(new Error("Request timeout")),E.delete(s))})},5e3);let f=o=>{v.length>=100&&(console.warn(l,"Message queue full, removing oldest message"),v.shift()),v.push(o)},k=()=>{if(I)for(;v.length>0;){let o=v.shift();o&&window.postMessage(o,window.location.origin)}},T=(o=0)=>new Promise(t=>{let s=++b,e=setTimeout(()=>{o<3?(console.log(l,`Verification attempt ${o+1} failed, retrying...`),setTimeout(()=>{T(o+1).then(t)},100*Math.pow(2,o))):(console.error(l,"Failed to verify injection after max retries"),p.lastError="Failed to verify injection",t(!1))},1e3),c=r=>{var g,u,w;r.source===window&&((g=r.data)==null?void 0:g.source)==="keepkey-content"&&((u=r.data)==null?void 0:u.type)==="INJECTION_CONFIRMED"&&((w=r.data)==null?void 0:w.requestId)===s&&(clearTimeout(e),window.removeEventListener("message",c),I=!0,p.isInjected=!0,console.log(l,"Injection verified successfully"),k(),t(!0))};window.addEventListener("message",c),window.postMessage({source:"keepkey-injected",type:"INJECTION_VERIFY",requestId:s,version:a,timestamp:Date.now()},window.location.origin)});function m(o,t=[],s,e){let c=l+" | walletRequest | ";if(!o||typeof o!="string"){console.error(c,"Invalid method:",o),e(new Error("Invalid method"));return}Array.isArray(t)||(console.warn(c,"Params not an array, wrapping:",t),t=[t]);try{let r=++b,g={id:r,method:o,params:t,chain:s,siteUrl:C.siteUrl,scriptSource:C.scriptSource,version:C.version,requestTime:new Date().toISOString(),referrer:document.referrer,href:window.location.href,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language};E.set(r,{callback:e,timestamp:Date.now(),method:o});let u={source:"keepkey-injected",type:"WALLET_REQUEST",requestId:r,requestInfo:g,timestamp:Date.now()};I?window.postMessage(u,window.location.origin):(console.log(c,"Content script not ready, queueing request"),f(u))}catch(r){console.error(c,"Error in walletRequest:",r),e(r)}}window.addEventListener("message",o=>{let t=l+" | message | ";if(o.source!==window)return;let s=o.data;if(!(!s||typeof s!="object")){if(s.source==="keepkey-content"&&s.type==="INJECTION_CONFIRMED"){I=!0,k();return}if(s.source==="keepkey-content"&&s.type==="WALLET_RESPONSE"&&s.requestId){let e=E.get(s.requestId);e?(s.error?e.callback(s.error):e.callback(null,s.result),E.delete(s.requestId)):console.warn(t,"No callback found for requestId:",s.requestId)}}});class B{events=new Map;on(t,s){this.events.has(t)||this.events.set(t,new Set),this.events.get(t).add(s)}off(t,s){var e;(e=this.events.get(t))==null||e.delete(s)}removeListener(t,s){this.off(t,s)}removeAllListeners(t){t?this.events.delete(t):this.events.clear()}emit(t,...s){var e;(e=this.events.get(t))==null||e.forEach(c=>{try{c(...s)}catch(r){console.error(l,`Error in event handler for ${t}:`,r)}})}once(t,s){let e=(...c)=>{s(...c),this.off(t,e)};this.on(t,e)}}function A(o){console.log(l,"Creating wallet object for chain:",o);let t=new B,s={network:"mainnet",isKeepKey:!0,isMetaMask:!0,isConnected:()=>I,request:({method:e,params:c=[]})=>new Promise((r,g)=>{m(e,c,o,(u,w)=>{u?g(u):r(w)})}),send:(e,c,r)=>{if(e.chain||(e.chain=o),typeof r=="function"){m(e.method,e.params||c,o,(g,u)=>{g?r(g):r(null,{id:e.id,jsonrpc:"2.0",result:u})});return}else return console.warn(l,"Synchronous send is deprecated and may not work properly"),{id:e.id,jsonrpc:"2.0",result:null}},sendAsync:(e,c,r)=>{e.chain||(e.chain=o);let g=r||c;if(typeof g!="function"){console.error(l,"sendAsync requires a callback function");return}m(e.method,e.params||c,o,(u,w)=>{u?g(u):g(null,{id:e.id,jsonrpc:"2.0",result:w})})},on:(e,c)=>(t.on(e,c),s),off:(e,c)=>(t.off(e,c),s),removeListener:(e,c)=>(t.removeListener(e,c),s),removeAllListeners:e=>(t.removeAllListeners(e),s),emit:(e,...c)=>(t.emit(e,...c),s),once:(e,c)=>(t.once(e,c),s),enable:()=>s.request({method:"eth_requestAccounts"}),_metamask:{isUnlocked:()=>Promise.resolve(!0)}};return o==="ethereum"&&(s.chainId="0x1",s.networkVersion="1",s.selectedAddress=null,s._handleAccountsChanged=e=>{s.selectedAddress=e[0]||null,t.emit("accountsChanged",e)},s._handleChainChanged=e=>{s.chainId=e,t.emit("chainChanged",e)},s._handleConnect=e=>{t.emit("connect",e)},s._handleDisconnect=e=>{s.selectedAddress=null,t.emit("disconnect",e)}),s}function S(o){let t={uuid:"350670db-19fa-4704-a166-e52e178b59d4",name:"KeepKey",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAADUklEQVRYCb1XTUgUYRie3bXEWhVLQaUsgwVLoUtEQjUJiZX0A0GX7BIZXurkOTSvdo2kvETHAsOshFgqOqhlRD9C7SGS1JTCsj1krU7PM+w7zMzOzuzMqi88+73v9z7vz3zzzTeziuIgmqbFgG5gBPguFOgq4CXLIMwCo0AXEJN4zxHkEuA6kAIMkUBMqMZk7so/UG8AUcnjOIKwFXgHZIgEwKFmOHOfYO4aySVjmAoc7O4R0EB7lYS5h9K1jBJ6A7CuAfXG7OopbKLXkh4dccNZ7jlsi0gAJlWLI5jBPWFsTK5AGxCRImswFqDGWanDBo6IsYbjUanFbmrFWIHxD3IsmfJsgB4y2aJuF4UrUC5GnuNtxJeEQqEoAb3LJV+F4ctlHwkZXDULv8fEKQCHB4+rCJ9ngKcIGUTVRubT027y8yR9bOM4mhKTTwNJZD4miaDXAG8dqzlMShw3YRCZRVAr7vU4g5F/D4ZBoJK2H+Em9CsfEdBoKn4K9jPAd3G9sMPqZEzpRPzAwRfWJpN9EfZSRkAOE5LD7wrw8dkpwRh55VMm27fqt4FiVBjGBTaxEm4Db8d+4BPtIOK3AdbYCPC1qh/haGIS9gHgDeBbgjTAIkXAfTRxkgaamMNwCHgB+BMk4Decq0hGkFQbka/WMyZ/EeyHNo6TuSwx3Nn8gHQVIYOkOhB5Gp4zcdbBHiDvZ2pRuzozru2euKuDOucg/KliTAjKKMa9ksBpxBLrbzRwVfifOnB4RR2g3QSH3Cfx5FRdc2KoGstroUeQKh47vnAwWvUKjsPcA/wWdBUkjRAgZdsznO8D5xLGC/Opxc3NiQeV9uIsgkNDaUoMFpNDLleAn0cTQNBjGaFW6fn2Wrky/dI6abPOl9eN9deoWhjLloCv3+bPy7w3/9kzfvjX120g1cuSdsJ47xm1CgS9AaxCErlbV6qJ02W1nq22lG75AtIHWQEeJpOYaAT6gBQQWC5XNCjc7dkkHFKWe6v3FcLfbzRAMlcC6IC6C+gGxgCectZnCRMuopVG1v+Nx04sYINlxLH4wI6W52UFhT+Q41b2Nl0qeLnwZPGQucNHrXN6ZDG94RQuO688XbwNFzvjlSuwH03wEW8H+Bf/dxrUOWdc+H8mKXtEpGpY3AAAAABJRU5ErkJggg==",rdns:"com.keepkey.client"},s=new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:t,provider:o})});console.log(l,"Announcing EIP-6963 provider"),window.dispatchEvent(s)}async function j(){let o=l+" | mountWallet | ";console.log(o,"Starting wallet mount process");let t=A("ethereum"),s={binance:A("binance"),bitcoin:A("bitcoin"),bitcoincash:A("bitcoincash"),dogecoin:A("dogecoin"),dash:A("dash"),ethereum:t,keplr:A("keplr"),litecoin:A("litecoin"),thorchain:A("thorchain"),mayachain:A("mayachain")},e={binance:A("binance"),bitcoin:A("bitcoin"),bitcoincash:A("bitcoincash"),dogecoin:A("dogecoin"),dash:A("dash"),ethereum:t,osmosis:A("osmosis"),cosmos:A("cosmos"),litecoin:A("litecoin"),thorchain:A("thorchain"),mayachain:A("mayachain"),ripple:A("ripple")},c=(r,g,{force:u=!1}={})=>{if(y[r]&&!u){console.warn(o,`window.${r} already present \u2014 yielding to it (EIP-6963 still announced for discovery)`);return}try{Object.defineProperty(y,r,{value:g,writable:!1,configurable:!0}),console.log(o,`Successfully mounted window.${r}`)}catch(K){console.error(o,`Failed to mount window.${r}:`,K),p.lastError=`Failed to mount ${r}`}};c("ethereum",t),c("xfi",s),c("keepkey",e,{force:!0}),window.addEventListener("eip6963:requestProvider",()=>{console.log(o,"Re-announcing provider on request"),S(t)}),S(t),setTimeout(()=>{console.log(o,"Delayed EIP-6963 announcement for late-loading dApps"),S(t)},100);try{let r=new U(m);O(r),console.log(o,"Solana wallet registered via Wallet Standard")}catch(r){console.error(o,"Failed to register Solana wallet:",r)}window.addEventListener("message",r=>{var g,u,w;((g=r.data)==null?void 0:g.type)==="CHAIN_CHANGED"&&(console.log(o,"Chain changed:",r.data),t.emit("chainChanged",(u=r.data.provider)==null?void 0:u.chainId)),((w=r.data)==null?void 0:w.type)==="ACCOUNTS_CHANGED"&&(console.log(o,"Accounts changed:",r.data),t._handleAccountsChanged&&t._handleAccountsChanged(r.data.accounts||[]))}),T().then(r=>{r?console.log(o,"Injection verified successfully"):(console.error(o,"Failed to verify injection, wallet features may not work"),p.lastError="Injection not verified")}),console.log(o,"Wallet mount complete")}j(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{if(console.log(l,"DOM loaded, re-announcing provider for late-loading dApps"),y.ethereum&&typeof y.dispatchEvent=="function"){let o=y.ethereum;S(o)}}),console.log(l,"Injection script loaded and initialized")})();})(); +URI: ${T}`,u+=` +Version: ${w}`,u+=` +Chain ID: ${m}`,u+=` +Nonce: ${E}`,u+=` +Issued At: ${S}`,e!=null&&e.expirationTime&&(u+=` +Expiration Time: ${e.expirationTime}`),e!=null&&e.notBefore&&(u+=` +Not Before: ${e.notBefore}`),e!=null&&e.requestId&&(u+=` +Request ID: ${e.requestId}`),(c=e==null?void 0:e.resources)!=null&&c.length){u+=` +Resources:`;for(let v of e.resources)u+=` +- ${v}`}let k=new TextEncoder().encode(u),C=await this.#n("solana_signMessage",[Array.from(k)]);r.push({account:l,signedMessage:k,signature:new Uint8Array(C)})}return r}}};constructor(o){this.#a=o;try{let r=localStorage.getItem("keepkey-solana");if(r){let{address:c}=JSON.parse(r);c&&typeof c=="string"&&(this.#t=c)}}catch{}this.#c()}#r(o){return{address:o,publicKey:M(o),chains:["solana:mainnet"],features:[...f.ACCOUNT_FEATURES]}}#o(o){this.#e=[this.#r(o)];try{localStorage.setItem("keepkey-solana",JSON.stringify({address:o}))}catch{}this.#i()}async#c(){try{let o=await this.#n("solana_connect",[]);if(o&&typeof o=="string"){this.#t=o;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:o}))}catch{}}}catch{}}#i(){let o=this.#e,r=this.features;this.#s.forEach(c=>{try{c({accounts:o,features:r})}catch{}})}#n(o,r){return new Promise((c,e)=>{this.#a(o,r,"solana",(l,y)=>{l?e(l):c(y)})})}};function O(f){let o=({register:r})=>{r(f)};try{let r=window.navigator;r.wallets||(r.wallets=[]),Array.isArray(r.wallets)?r.wallets.push(o):typeof r.wallets.register=="function"&&r.wallets.register(f)}catch{}try{window.dispatchEvent(new CustomEvent("wallet-standard:register-wallet",{detail:o}))}catch{}window.addEventListener("wallet-standard:app-ready",r=>{let c=r;try{typeof c.detail=="function"&&c.detail(o)}catch{}})}(function(){let f="2.1.0",l=window,y={isInjected:!1,version:f,injectedAt:Date.now(),retryCount:0};if(l.keepkeyInjectionState&&l.keepkeyInjectionState.version>=f)return;l.keepkeyInjectionState=y;let I={siteUrl:window.location.href,scriptSource:"KeepKey Extension",version:f,injectedTime:new Date().toISOString(),origin:window.location.origin,protocol:window.location.protocol},T=0,w=new Map,m=[],E=!1;setInterval(()=>{let i=Date.now();w.forEach((n,s)=>{i-n.timestamp>3e5&&(n.callback(new Error("Request timeout")),w.delete(s))})},5e3);let b=i=>{m.length>=100&&m.shift(),m.push(i)},u=()=>{if(E)for(;m.length>0;){let i=m.shift();i&&window.postMessage(i,window.location.origin)}},k=(i=0)=>new Promise(n=>{let s=++T,t=setTimeout(()=>{i<3?setTimeout(()=>{k(i+1).then(n)},100*Math.pow(2,i)):(y.lastError="Failed to verify injection",n(!1))},1e3),a=d=>{var g,h,p;d.source===window&&((g=d.data)==null?void 0:g.source)==="keepkey-content"&&((h=d.data)==null?void 0:h.type)==="INJECTION_CONFIRMED"&&((p=d.data)==null?void 0:p.requestId)===s&&(clearTimeout(t),window.removeEventListener("message",a),E=!0,y.isInjected=!0,u(),n(!0))};window.addEventListener("message",a),window.postMessage({source:"keepkey-injected",type:"INJECTION_VERIFY",requestId:s,version:f,timestamp:Date.now()},window.location.origin)});function C(i,n=[],s,t){if(!i||typeof i!="string"){t(new Error("Invalid method"));return}Array.isArray(n)||(n=[n]);try{let a=++T,d={id:a,method:i,params:n,chain:s,siteUrl:I.siteUrl,scriptSource:I.scriptSource,version:I.version,requestTime:new Date().toISOString(),referrer:document.referrer,href:window.location.href,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language};w.set(a,{callback:t,timestamp:Date.now(),method:i});let g={source:"keepkey-injected",type:"WALLET_REQUEST",requestId:a,requestInfo:d,timestamp:Date.now()};E?window.postMessage(g,window.location.origin):b(g)}catch(a){t(a)}}window.addEventListener("message",i=>{if(i.source!==window)return;let n=i.data;if(!(!n||typeof n!="object")){if(n.source==="keepkey-content"&&n.type==="INJECTION_CONFIRMED"){E=!0,u();return}if(n.source==="keepkey-content"&&n.type==="WALLET_RESPONSE"&&n.requestId){let s=w.get(n.requestId);s&&(n.error?s.callback(n.error):s.callback(null,n.result),w.delete(n.requestId))}}});class v{events=new Map;on(n,s){this.events.has(n)||this.events.set(n,new Set),this.events.get(n).add(s)}off(n,s){var t;(t=this.events.get(n))==null||t.delete(s)}removeListener(n,s){this.off(n,s)}removeAllListeners(n){n?this.events.delete(n):this.events.clear()}emit(n,...s){var t;(t=this.events.get(n))==null||t.forEach(a=>{try{a(...s)}catch{}})}once(n,s){let t=(...a)=>{s(...a),this.off(n,t)};this.on(n,t)}}function A(i){let n=new v,s={network:"mainnet",isKeepKey:!0,isMetaMask:!0,isConnected:()=>E,request:({method:t,params:a=[]})=>new Promise((d,g)=>{C(t,a,i,(h,p)=>{h?g(h):d(p)})}),send:(t,a,d)=>{if(t.chain||(t.chain=i),typeof d=="function"){C(t.method,t.params||a,i,(g,h)=>{g?d(g):d(null,{id:t.id,jsonrpc:"2.0",result:h})});return}else return{id:t.id,jsonrpc:"2.0",result:null}},sendAsync:(t,a,d)=>{t.chain||(t.chain=i);let g=d||a;typeof g=="function"&&C(t.method,t.params||a,i,(h,p)=>{h?g(h):g(null,{id:t.id,jsonrpc:"2.0",result:p})})},on:(t,a)=>(n.on(t,a),s),off:(t,a)=>(n.off(t,a),s),removeListener:(t,a)=>(n.removeListener(t,a),s),removeAllListeners:t=>(n.removeAllListeners(t),s),emit:(t,...a)=>(n.emit(t,...a),s),once:(t,a)=>(n.once(t,a),s),enable:()=>s.request({method:"eth_requestAccounts"}),_metamask:{isUnlocked:()=>Promise.resolve(!0)}};return i==="ethereum"&&(s.chainId="0x1",s.networkVersion="1",s.selectedAddress=null,s._handleAccountsChanged=t=>{s.selectedAddress=t[0]||null,n.emit("accountsChanged",t)},s._handleChainChanged=t=>{s.chainId=t,n.emit("chainChanged",t)},s._handleConnect=t=>{n.emit("connect",t)},s._handleDisconnect=t=>{s.selectedAddress=null,n.emit("disconnect",t)}),s}function U(i){let n={uuid:"350670db-19fa-4704-a166-e52e178b59d4",name:"KeepKey",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAADUklEQVRYCb1XTUgUYRie3bXEWhVLQaUsgwVLoUtEQjUJiZX0A0GX7BIZXurkOTSvdo2kvETHAsOshFgqOqhlRD9C7SGS1JTCsj1krU7PM+w7zMzOzuzMqi88+73v9z7vz3zzzTeziuIgmqbFgG5gBPguFOgq4CXLIMwCo0AXEJN4zxHkEuA6kAIMkUBMqMZk7so/UG8AUcnjOIKwFXgHZIgEwKFmOHOfYO4aySVjmAoc7O4R0EB7lYS5h9K1jBJ6A7CuAfXG7OopbKLXkh4dccNZ7jlsi0gAJlWLI5jBPWFsTK5AGxCRImswFqDGWanDBo6IsYbjUanFbmrFWIHxD3IsmfJsgB4y2aJuF4UrUC5GnuNtxJeEQqEoAb3LJV+F4ctlHwkZXDULv8fEKQCHB4+rCJ9ngKcIGUTVRubT027y8yR9bOM4mhKTTwNJZD4miaDXAG8dqzlMShw3YRCZRVAr7vU4g5F/D4ZBoJK2H+Em9CsfEdBoKn4K9jPAd3G9sMPqZEzpRPzAwRfWJpN9EfZSRkAOE5LD7wrw8dkpwRh55VMm27fqt4FiVBjGBTaxEm4Db8d+4BPtIOK3AdbYCPC1qh/haGIS9gHgDeBbgjTAIkXAfTRxkgaamMNwCHgB+BMk4Decq0hGkFQbka/WMyZ/EeyHNo6TuSwx3Nn8gHQVIYOkOhB5Gp4zcdbBHiDvZ2pRuzozru2euKuDOucg/KliTAjKKMa9ksBpxBLrbzRwVfifOnB4RR2g3QSH3Cfx5FRdc2KoGstroUeQKh47vnAwWvUKjsPcA/wWdBUkjRAgZdsznO8D5xLGC/Opxc3NiQeV9uIsgkNDaUoMFpNDLleAn0cTQNBjGaFW6fn2Wrky/dI6abPOl9eN9deoWhjLloCv3+bPy7w3/9kzfvjX120g1cuSdsJ47xm1CgS9AaxCErlbV6qJ02W1nq22lG75AtIHWQEeJpOYaAT6gBQQWC5XNCjc7dkkHFKWe6v3FcLfbzRAMlcC6IC6C+gGxgCectZnCRMuopVG1v+Nx04sYINlxLH4wI6W52UFhT+Q41b2Nl0qeLnwZPGQucNHrXN6ZDG94RQuO688XbwNFzvjlSuwH03wEW8H+Bf/dxrUOWdc+H8mKXtEpGpY3AAAAABJRU5ErkJggg==",rdns:"com.keepkey.client"},s=new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:n,provider:i})});window.dispatchEvent(s)}async function W(){let i=A("ethereum"),n={binance:A("binance"),bitcoin:A("bitcoin"),bitcoincash:A("bitcoincash"),dogecoin:A("dogecoin"),dash:A("dash"),ethereum:i,keplr:A("keplr"),litecoin:A("litecoin"),thorchain:A("thorchain"),mayachain:A("mayachain")},s={binance:A("binance"),bitcoin:A("bitcoin"),bitcoincash:A("bitcoincash"),dogecoin:A("dogecoin"),dash:A("dash"),ethereum:i,osmosis:A("osmosis"),cosmos:A("cosmos"),litecoin:A("litecoin"),thorchain:A("thorchain"),mayachain:A("mayachain"),ripple:A("ripple")},t=(a,d,{force:g=!1}={})=>{if(!(l[a]&&!g))try{Object.defineProperty(l,a,{value:d,writable:!1,configurable:!0})}catch{y.lastError=`Failed to mount ${a}`}};t("ethereum",i),t("xfi",n),t("keepkey",s,{force:!0}),window.addEventListener("eip6963:requestProvider",()=>{U(i)}),U(i),setTimeout(()=>{U(i)},100);try{let a=new R(C);O(a)}catch{}window.addEventListener("message",a=>{var d,g,h;((d=a.data)==null?void 0:d.type)==="CHAIN_CHANGED"&&i.emit("chainChanged",(g=a.data.provider)==null?void 0:g.chainId),((h=a.data)==null?void 0:h.type)==="ACCOUNTS_CHANGED"&&i._handleAccountsChanged&&i._handleAccountsChanged(a.data.accounts||[])}),k().then(a=>{a||(y.lastError="Injection not verified")})}W(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{if(l.ethereum&&typeof l.dispatchEvent=="function"){let i=l.ethereum;U(i)}})})();})(); diff --git a/chrome-extension/src/background/index.ts b/chrome-extension/src/background/index.ts index f034a8d..92937fd 100644 --- a/chrome-extension/src/background/index.ts +++ b/chrome-extension/src/background/index.ts @@ -53,15 +53,45 @@ const KEEPKEY_STATES = { }; let KEEPKEY_STATE = 0; -function updateIcon() { - let iconPath = './icon-128.png'; +// MV3 service workers sometimes fail `chrome.action.setIcon({path})` with +// "Failed to fetch" when the worker has just (re-)started — the extension's +// file-map isn't always ready to serve its own packaged assets immediately. +// Guards: +// 1. Deduplicate: don't re-invoke the API when the path hasn't changed +// (checkKeepKey fires updateIcon every 5s; 99% of those calls are +// redundant and each one is a chance to hit the transient error). +// 2. Retry by re-running updateIcon() — NOT by replaying the captured +// path. If KEEPKEY_STATE flipped during the 500 ms gap, re-running +// reads the current state and applies whatever is correct now, +// preventing a stale "online" icon from painting over a subsequent +// "errored" transition. +let lastIconPath: string | null = null; +let iconRetryPending = false; + +function currentIconPath(): string { // Show green/online icon when connected (state 2) or paired (state 5) - if (KEEPKEY_STATE === 2 || KEEPKEY_STATE === 5) iconPath = './icon-128-online.png'; + return KEEPKEY_STATE === 2 || KEEPKEY_STATE === 5 ? './icon-128-online.png' : './icon-128.png'; +} + +function updateIcon() { + const iconPath = currentIconPath(); + if (iconPath === lastIconPath) return; + lastIconPath = iconPath; chrome.action.setIcon({ path: iconPath }, () => { - if (chrome.runtime.lastError) { - console.error('Error setting icon:', chrome.runtime.lastError); - } + const err = chrome.runtime.lastError; + if (!err) return; + // Clear the dedupe so the retry path can actually re-apply an icon + // (even if it's the same string) and then call updateIcon() again. + // Re-running reads CURRENT state, so a state flip during the 500ms + // backoff doesn't leave us painting a stale icon. + lastIconPath = null; + if (iconRetryPending) return; // one retry in flight is enough + iconRetryPending = true; + setTimeout(() => { + iconRetryPending = false; + updateIcon(); + }, 500); }); } @@ -208,20 +238,24 @@ async function fetchBalancesFromPioneer(forceRefresh = false): Promise { console.log(`[fetchBalances] Sample pubkeys:`, pioneerPubkeys.slice(0, 3)); // Two Pioneer endpoints, chosen per chain: - // /charts/portfolio — EVM + UTXO + Cosmos (unauthenticated, returns tokens via Zapper/Unchained). - // Returns EMPTY for Solana, so Solana pubkeys must NOT go here. - // /portfolio — Solana (authenticated). Returns natives + SPL tokens in one flat array. - const chartsPortfolioUrl = `${PIONEER_API}/api/v1/charts/portfolio`; + // /charts/portfolio — EVM + UTXO + Cosmos + TON + TRON (when authenticated + // via ?key=key:public-*). Returns natives + tokens + + // priceUsd + valueUsd in one call. Without the + // queryKey, Pioneer silently drops TON / TRON + // entries — which is what made dashboard balances + // appear with $0.00 USD. Returns EMPTY for Solana, + // so Solana pubkeys must go through /portfolio. + // /portfolio — Solana (same key-based auth). Returns natives + + // SPL tokens in one flat array. + // + // The queryKey just has to be a unique "key:public-*" string — Pioneer + // rate-limits by it but doesn't gate reads. Bumping on every fetch is + // fine; cached responses still serve fast. + const queryKey = `key:public-${Date.now()}`; + const chartsPortfolioUrl = `${PIONEER_API}/api/v1/charts/portfolio?key=${encodeURIComponent(queryKey)}`; const solanaPubkeys = pioneerPubkeys.filter(p => p.caip.toLowerCase().startsWith('solana:')); - const tronPubkeys = pioneerPubkeys.filter(p => p.caip.toLowerCase().startsWith('tron:')); - const tonPubkeys = pioneerPubkeys.filter(p => p.caip.toLowerCase().startsWith('ton:')); - const generic = pioneerPubkeys.filter( - p => - !p.caip.toLowerCase().startsWith('solana:') && - !p.caip.toLowerCase().startsWith('tron:') && - !p.caip.toLowerCase().startsWith('ton:'), - ); + const generic = pioneerPubkeys.filter(p => !p.caip.toLowerCase().startsWith('solana:')); const addressPubkeys = generic.filter( p => !p.pubkey.startsWith('xpub') && !p.pubkey.startsWith('zpub') && !p.pubkey.startsWith('ypub'), ); @@ -246,7 +280,19 @@ async function fetchBalancesFromPioneer(forceRefresh = false): Promise { console.log( `[fetchBalances] ${label} batch: ${data.balances?.length || 0} balances, ${data.tokens?.length || 0} tokens`, ); - return { balances: data.balances || [], tokens: data.tokens || [] }; + // Surface TON / Tron entries so we can verify priceUsd + valueUsd + // are arriving from Pioneer (this was the reason they showed $0 + // on the dashboard before the ?key query param was added). + const bals = Array.isArray(data.balances) ? data.balances : []; + for (const b of bals) { + const nid = (b?.networkId || '').toLowerCase(); + if (nid === 'ton:-239' || nid.startsWith('tron:')) { + console.log( + `[fetchBalances][${label}] ${b.symbol} balance=${b.balance} price=${b.priceUsd} value=${b.valueUsd} caip=${b.caip}`, + ); + } + } + return { balances: bals, tokens: data.tokens || [] }; } catch (e: any) { console.warn(`[fetchBalances] ${label} batch error:`, e.message); return { balances: [] as any[], tokens: [] as any[] }; @@ -306,109 +352,85 @@ async function fetchBalancesFromPioneer(forceRefresh = false): Promise { } }; - // Tron lives outside /charts/portfolio coverage and outside the - // Solana-shaped /portfolio endpoint too. Use the dedicated Pioneer - // accountInfo route, one request per address. Price USD comes back as - // 0 (Pioneer doesn't return a TRX market entry) — balance quantity is - // still accurate so Send math works; USD value lights up once a market - // feed is wired in. - const fetchTronBatch = async (batch: typeof pioneerPubkeys) => { - if (batch.length === 0) return { balances: [] as any[], tokens: [] as any[] }; - const out: any[] = []; - await Promise.all( - batch.map(async p => { - try { - const url = `${PIONEER_API}/api/v1/tron/accountInfo/${encodeURIComponent(p.pubkey)}`; - const resp = await fetch(url, { signal: AbortSignal.timeout(10000) }); - if (!resp.ok) return; - const json = await resp.json(); - // Shape: { success, data: { balance: "26.739864", ... } } - // Pioneer already returns TRX as a decimal string here, unlike - // Solana which returns lamports — no conversion needed. - const bal = json?.data?.balance; - if (bal === undefined || bal === null) return; - out.push({ - networkId: 'tron:27Lqcw', - caip: p.caip, - symbol: 'TRX', - name: 'Tron', - balance: String(bal), - valueUsd: '0', - priceUsd: '0', - icon: 'https://api.keepkey.info/coins/' + btoa(p.caip).replace(/=+$/, '') + '.png', - isNative: true, - address: p.pubkey, - }); - } catch (e: any) { - console.warn('[fetchBalances] tron accountInfo failed for', p.pubkey, e.message); - } - }), - ); - console.log(`[fetchBalances] tron batch: ${out.length} natives`); - return { balances: out, tokens: [] as any[] }; - }; - - const fetchTonBatch = async (batch: typeof pioneerPubkeys) => { - if (batch.length === 0) return { balances: [] as any[], tokens: [] as any[] }; - const out: any[] = []; - await Promise.all( - batch.map(async p => { - try { - const url = `${PIONEER_API}/api/v1/ton/accountInfo/${encodeURIComponent(p.pubkey)}`; - const resp = await fetch(url, { signal: AbortSignal.timeout(10000) }); - if (!resp.ok) return; - const json = await resp.json(); - // Shape: { success, data: { seqno, balance: "", wallet_version } } - // Pioneer returns the raw nanoTON integer — convert to TON (9 decimals) - // for UI display, matching the decimal-balance convention Solana/TRON use. - // Pioneer's /api/v1/ton/accountInfo returns `balance` as a - // decimal TON string already (e.g. "15.701798194"), NOT - // nanoTON. The old comment claimed nanoTON and divided by - // 1e9 — that turned a real 15.7 TON balance into - // 1.5701798194e-8 and made the asset page / send page - // show 0.0000 TON. Shape verified against Pioneer's live - // response for UQDzK5… on 2026-04-21. - const bal = json?.data?.balance; - if (bal === undefined || bal === null) return; - const ton = String(bal); - out.push({ - networkId: 'ton:-239', - caip: p.caip, - symbol: 'TON', - name: 'Ton', - balance: ton, - valueUsd: '0', - priceUsd: '0', - icon: 'https://api.keepkey.info/coins/' + btoa(p.caip).replace(/=+$/, '') + '.png', - isNative: true, - address: p.pubkey, - }); - } catch (e: any) { - console.warn('[fetchBalances] ton accountInfo failed for', p.pubkey, e.message); - } - }), - ); - console.log(`[fetchBalances] ton batch: ${out.length} natives`); - return { balances: out, tokens: [] as any[] }; + // TON + Tron happy path: authenticated /charts/portfolio returns + // them alongside everything else with full priceUsd + valueUsd. + // Partial-response safety net: Pioneer has been observed to + // silently omit TON / TRON rows (rate-limit edge cases, + // 0-balance pubkeys, upstream provider hiccups) even when the + // rest of the portfolio comes back fine. Without a fallback, a + // partial response would overwrite cachedBalances without those + // rows and the dashboard would flicker a chain off entirely. We + // call /api/v1/{ton,tron}/accountInfo targeted only at any + // TON/TRON pubkey absent from the portfolio response; price stays + // 0 on that fallback row — acceptable degradation vs losing the + // row completely. + const fetchAccountInfoFallback = async ( + pk: { caip: string; pubkey: string }, + chain: 'ton' | 'tron', + ): Promise => { + try { + const url = `${PIONEER_API}/api/v1/${chain}/accountInfo/${encodeURIComponent(pk.pubkey)}`; + const resp = await fetch(url, { signal: AbortSignal.timeout(10000) }); + if (!resp.ok) return null; + const json = await resp.json(); + const bal = json?.data?.balance; + if (bal === undefined || bal === null) return null; + return { + networkId: chain === 'ton' ? 'ton:-239' : 'tron:27Lqcw', + caip: pk.caip, + symbol: chain === 'ton' ? 'TON' : 'TRX', + name: chain === 'ton' ? 'Ton' : 'Tron', + balance: String(bal), + valueUsd: '0', + priceUsd: '0', + icon: 'https://api.keepkey.info/coins/' + btoa(pk.caip).replace(/=+$/, '') + '.png', + isNative: true, + address: pk.pubkey, + _fallback: true, + }; + } catch (e: any) { + console.warn(`[fetchBalances] ${chain} accountInfo fallback failed for ${pk.pubkey}:`, e.message); + return null; + } }; - const [addressResult, xpubResult, solanaResult, tronResult, tonResult] = await Promise.all([ + const [addressResult, xpubResult, solanaResult] = await Promise.all([ fetchBatch(addressPubkeys, 'address'), fetchBatch(xpubPubkeys, 'xpub'), fetchSolanaBatch(solanaPubkeys), - fetchTronBatch(tronPubkeys), - fetchTonBatch(tonPubkeys), ]); - const rawBalances: any[] = [ - ...addressResult.balances, - ...xpubResult.balances, - ...solanaResult.balances, - ...tronResult.balances, - ...tonResult.balances, - ]; + const rawBalances: any[] = [...addressResult.balances, ...xpubResult.balances, ...solanaResult.balances]; const rawTokens: any[] = [...addressResult.tokens, ...xpubResult.tokens, ...solanaResult.tokens]; + // Gap-patch any TON/TRON pubkey that the portfolio call silently + // dropped. Match by (networkId, pubkey/address) — lowercased both + // sides since Pioneer echoes lowercased networkIds. + const tonPubkeysForPatch = pioneerPubkeys.filter(p => p.caip.toLowerCase().startsWith('ton:')); + const tronPubkeysForPatch = pioneerPubkeys.filter(p => p.caip.toLowerCase().startsWith('tron:')); + if (tonPubkeysForPatch.length > 0 || tronPubkeysForPatch.length > 0) { + const covered = new Set(); + for (const b of rawBalances) { + const nid = (b?.networkId || '').toLowerCase(); + if (nid === 'ton:-239' || nid.startsWith('tron:')) { + const addr = String(b.pubkey || b.address || '').toLowerCase(); + if (addr) covered.add(`${nid}:${addr}`); + } + } + const missingTon = tonPubkeysForPatch.filter(p => !covered.has(`ton:-239:${p.pubkey.toLowerCase()}`)); + const missingTron = tronPubkeysForPatch.filter(p => !covered.has(`tron:27lqcw:${p.pubkey.toLowerCase()}`)); + if (missingTon.length > 0 || missingTron.length > 0) { + console.warn( + `[fetchBalances] /charts/portfolio partial: ${missingTon.length} TON + ${missingTron.length} TRON missing; patching from /accountInfo`, + ); + const patches = await Promise.all([ + ...missingTon.map(p => fetchAccountInfoFallback(p, 'ton')), + ...missingTron.map(p => fetchAccountInfoFallback(p, 'tron')), + ]); + for (const row of patches) if (row) rawBalances.push(row); + } + } + if (rawBalances.length === 0 && rawTokens.length === 0) { console.warn('[fetchBalances] Pioneer returned 0 balances for', pioneerPubkeys.length, 'pubkeys'); return cachedBalances; diff --git a/chrome-extension/src/injected/injected.ts b/chrome-extension/src/injected/injected.ts index 05caa2c..9e9882a 100644 --- a/chrome-extension/src/injected/injected.ts +++ b/chrome-extension/src/injected/injected.ts @@ -12,7 +12,6 @@ import { KeepKeySolanaWallet } from './solana-wallet-standard'; import { registerSolanaWallet } from './solana-wallet-register'; (function () { - const TAG = ' | KeepKeyInjected | '; const VERSION = '2.1.0'; const MAX_RETRY_COUNT = 3; const RETRY_DELAY = 100; // ms @@ -32,21 +31,16 @@ import { registerSolanaWallet } from './solana-wallet-register'; // Check for existing injection with version comparison if (kWindow.keepkeyInjectionState) { const existing = kWindow.keepkeyInjectionState; - console.warn(TAG, `Existing injection detected v${existing.version}, current v${VERSION}`); // Only skip if same or newer version if (existing.version >= VERSION) { - console.log(TAG, 'Skipping injection, newer or same version already present'); return; } - console.log(TAG, 'Upgrading injection to newer version'); } // Set injection state kWindow.keepkeyInjectionState = injectionState; - console.log(TAG, `Initializing KeepKey Injection v${VERSION}`); - // Enhanced source information const SOURCE_INFO = { siteUrl: window.location.href, @@ -67,7 +61,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; const now = Date.now(); callbacks.forEach((callback, id) => { if (now - callback.timestamp > CALLBACK_TIMEOUT) { - console.warn(TAG, `Callback timeout for request ${id} (${callback.method})`); callback.callback(new Error('Request timeout')); callbacks.delete(id); } @@ -79,7 +72,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; // Manage message queue size const addToQueue = (message: WalletMessage) => { if (messageQueue.length >= MESSAGE_QUEUE_MAX) { - console.warn(TAG, 'Message queue full, removing oldest message'); messageQueue.shift(); } messageQueue.push(message); @@ -103,7 +95,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; const verifyId = ++messageId; const timeout = setTimeout(() => { if (retryCount < MAX_RETRY_COUNT) { - console.log(TAG, `Verification attempt ${retryCount + 1} failed, retrying...`); setTimeout( () => { verifyInjection(retryCount + 1).then(resolve); @@ -111,7 +102,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; RETRY_DELAY * Math.pow(2, retryCount), ); // Exponential backoff } else { - console.error(TAG, 'Failed to verify injection after max retries'); injectionState.lastError = 'Failed to verify injection'; resolve(false); } @@ -128,7 +118,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; window.removeEventListener('message', handleVerification); isContentScriptReady = true; injectionState.isInjected = true; - console.log(TAG, 'Injection verified successfully'); processQueue(); resolve(true); } @@ -157,17 +146,13 @@ import { registerSolanaWallet } from './solana-wallet-register'; chain: ChainType, callback: (error: any, result?: any) => void, ) { - const tag = TAG + ' | walletRequest | '; - // Validate inputs if (!method || typeof method !== 'string') { - console.error(tag, 'Invalid method:', method); callback(new Error('Invalid method')); return; } if (!Array.isArray(params)) { - console.warn(tag, 'Params not an array, wrapping:', params); params = [params]; } @@ -207,19 +192,15 @@ import { registerSolanaWallet } from './solana-wallet-register'; if (isContentScriptReady) { window.postMessage(message, window.location.origin); } else { - console.log(tag, 'Content script not ready, queueing request'); addToQueue(message); } } catch (error) { - console.error(tag, 'Error in walletRequest:', error); callback(error); } } // Listen for responses with enhanced validation window.addEventListener('message', (event: MessageEvent) => { - const tag = TAG + ' | message | '; - // Security: Validate origin if (event.source !== window) return; @@ -243,8 +224,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; callback.callback(null, data.result); } callbacks.delete(data.requestId); - } else { - console.warn(tag, 'No callback found for requestId:', data.requestId); } } }); @@ -280,8 +259,8 @@ import { registerSolanaWallet } from './solana-wallet-register'; this.events.get(event)?.forEach(handler => { try { handler(...args); - } catch (error) { - console.error(TAG, `Error in event handler for ${event}:`, error); + } catch (_error) { + // swallow handler errors to avoid breaking other listeners } }); } @@ -297,8 +276,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; // Create wallet provider with proper typing function createWalletObject(chain: ChainType): WalletProvider { - console.log(TAG, 'Creating wallet object for chain:', chain); - const eventEmitter = new EventEmitter(); const wallet: WalletProvider = { @@ -336,7 +313,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; return undefined; } else { // Sync send (deprecated, but required for compatibility) - console.warn(TAG, 'Synchronous send is deprecated and may not work properly'); return { id: payload.id, jsonrpc: '2.0', result: null }; } }, @@ -348,7 +324,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; const cb = callback || param1; if (typeof cb !== 'function') { - console.error(TAG, 'sendAsync requires a callback function'); return; } @@ -445,15 +420,11 @@ import { registerSolanaWallet } from './solana-wallet-register'; detail: Object.freeze({ info, provider: ethereumProvider }), }); - console.log(TAG, 'Announcing EIP-6963 provider'); window.dispatchEvent(announceEvent); } // Mount wallet with proper state management async function mountWallet() { - const tag = TAG + ' | mountWallet | '; - console.log(tag, 'Starting wallet mount process'); - // Create wallet objects immediately - don't wait for verification const ethereum = createWalletObject('ethereum'); const xfi: Record = { @@ -499,7 +470,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; const mountProvider = (name: string, provider: any, { force = false } = {}) => { const existing = (kWindow as any)[name]; if (existing && !force) { - console.warn(tag, `window.${name} already present — yielding to it (EIP-6963 still announced for discovery)`); return; } @@ -509,9 +479,7 @@ import { registerSolanaWallet } from './solana-wallet-register'; writable: false, configurable: true, // Allow reconfiguration for updates }); - console.log(tag, `Successfully mounted window.${name}`); - } catch (e) { - console.error(tag, `Failed to mount window.${name}:`, e); + } catch (_e) { injectionState.lastError = `Failed to mount ${name}`; } }; @@ -526,7 +494,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; // CRITICAL: Set up EIP-6963 listener BEFORE announcing // This ensures we catch any immediate requests window.addEventListener('eip6963:requestProvider', () => { - console.log(tag, 'Re-announcing provider on request'); announceProvider(ethereum); }); @@ -535,7 +502,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; // Also announce with a slight delay to catch late-loading dApps setTimeout(() => { - console.log(tag, 'Delayed EIP-6963 announcement for late-loading dApps'); announceProvider(ethereum); }, 100); @@ -544,19 +510,16 @@ import { registerSolanaWallet } from './solana-wallet-register'; try { const solanaWallet = new KeepKeySolanaWallet(walletRequest); registerSolanaWallet(solanaWallet); - console.log(tag, 'Solana wallet registered via Wallet Standard'); - } catch (e) { - console.error(tag, 'Failed to register Solana wallet:', e); + } catch (_e) { + // swallow; Solana registration is best-effort } // Handle chain changes and other events window.addEventListener('message', (event: MessageEvent) => { if (event.data?.type === 'CHAIN_CHANGED') { - console.log(tag, 'Chain changed:', event.data); ethereum.emit('chainChanged', event.data.provider?.chainId); } if (event.data?.type === 'ACCOUNTS_CHANGED') { - console.log(tag, 'Accounts changed:', event.data); if (ethereum._handleAccountsChanged) { ethereum._handleAccountsChanged(event.data.accounts || []); } @@ -567,14 +530,9 @@ import { registerSolanaWallet } from './solana-wallet-register'; // This is non-blocking for EIP-6963 verifyInjection().then(verified => { if (!verified) { - console.error(tag, 'Failed to verify injection, wallet features may not work'); injectionState.lastError = 'Injection not verified'; - } else { - console.log(tag, 'Injection verified successfully'); } }); - - console.log(tag, 'Wallet mount complete'); } // Initialize immediately for EIP-6963 compliance @@ -584,7 +542,6 @@ import { registerSolanaWallet } from './solana-wallet-register'; // Also re-run when DOM is ready in case dApp loads later if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { - console.log(TAG, 'DOM loaded, re-announcing provider for late-loading dApps'); // Re-announce when DOM is ready if (kWindow.ethereum && typeof kWindow.dispatchEvent === 'function') { const ethereum = kWindow.ethereum as WalletProvider; @@ -592,6 +549,4 @@ import { registerSolanaWallet } from './solana-wallet-register'; } }); } - - console.log(TAG, 'Injection script loaded and initialized'); })(); diff --git a/pages/content/src/index.ts b/pages/content/src/index.ts index edd4cb5..9e5c8db 100644 --- a/pages/content/src/index.ts +++ b/pages/content/src/index.ts @@ -2,13 +2,9 @@ import type { WalletMessage } from '../../../chrome-extension/src/injected/types'; -const TAG = ' | KeepKeyContent | '; -const ALLOWED_ORIGINS = ['http://localhost', 'https://localhost']; // Add allowed origins const INJECTION_TIMEOUT = 5000; // 5 seconds const MAX_INJECTION_RETRIES = 3; -console.log(TAG, 'Content script initializing'); - // Track injection state let injectionAttempts = 0; let isInjected = false; @@ -41,7 +37,6 @@ window.addEventListener('message', (event: MessageEvent) => { // Security: Check origin if (event.source !== window) return; if (!isAllowedOrigin(event.origin)) { - console.warn(TAG, 'Rejected message from untrusted origin:', event.origin); return; } @@ -49,7 +44,6 @@ window.addEventListener('message', (event: MessageEvent) => { // Handle injection verification if (data?.source === 'keepkey-injected' && data.type === 'INJECTION_VERIFY') { - console.log(TAG, 'Received injection verification request'); window.postMessage( { source: 'keepkey-content', @@ -66,9 +60,6 @@ window.addEventListener('message', (event: MessageEvent) => { // Validate wallet request if (!isValidWalletMessage(data)) { - if (data?.source === 'keepkey-injected') { - console.warn(TAG, 'Invalid message structure:', data); - } return; } @@ -76,7 +67,6 @@ window.addEventListener('message', (event: MessageEvent) => { if (data.type === 'WALLET_REQUEST' && data.requestInfo) { const { requestId, requestInfo } = data; - console.log(TAG, `Processing ${requestInfo.method} request from ${requestInfo.siteUrl}`); // Add request timestamp for tracking const requestWithMetadata = { @@ -87,7 +77,6 @@ window.addEventListener('message', (event: MessageEvent) => { // Forward to background script with timeout const timeout = setTimeout(() => { - console.error(TAG, 'Background script timeout for request:', requestId); window.postMessage( { source: 'keepkey-content', @@ -101,7 +90,6 @@ window.addEventListener('message', (event: MessageEvent) => { // Check if extension context is still valid before sending message if (!chrome.runtime?.id) { - console.error(TAG, 'Extension context invalidated, reloading page...'); window.postMessage( { source: 'keepkey-content', @@ -124,8 +112,6 @@ window.addEventListener('message', (event: MessageEvent) => { clearTimeout(timeout); if (chrome.runtime.lastError) { - console.error(TAG, 'Background communication error:', chrome.runtime.lastError); - // Check if it's a context invalidation error if (chrome.runtime.lastError.message?.includes('context invalidated')) { window.postMessage( @@ -159,8 +145,6 @@ window.addEventListener('message', (event: MessageEvent) => { return; } - console.log(TAG, 'Received response from background:', response); - // Send response back to injected script. `|| null` would collapse // legitimate `false` / `0` / `''` results into null — wrong for any // JSON-RPC method with a falsy success value (e.g. a boolean @@ -176,8 +160,7 @@ window.addEventListener('message', (event: MessageEvent) => { '*', ); }); - } catch (error) { - console.error(TAG, 'Failed to send message to background:', error); + } catch (_error) { window.postMessage( { source: 'keepkey-content', @@ -196,18 +179,14 @@ window.addEventListener('message', (event: MessageEvent) => { // Enhanced injection function with verification async function injectProviderScript(): Promise { - const tag = TAG + ' | inject | '; - return new Promise(resolve => { try { // Check if already injected if (isInjected) { - console.log(tag, 'Script already injected'); resolve(true); return; } - console.log(tag, `Injection attempt ${injectionAttempts + 1}`); injectionAttempts++; const script = document.createElement('script'); @@ -219,15 +198,12 @@ async function injectProviderScript(): Promise { script.setAttribute('data-timestamp', Date.now().toString()); const timeout = setTimeout(() => { - console.warn(tag, 'Injection verification timeout'); script.remove(); resolve(false); }, INJECTION_TIMEOUT); // Wait for script to load and verify injection script.onload = () => { - console.log(tag, 'Script loaded, waiting for verification...'); - // Listen for verification const verifyHandler = (event: MessageEvent) => { if ( @@ -237,7 +213,6 @@ async function injectProviderScript(): Promise { ) { clearTimeout(timeout); window.removeEventListener('message', verifyHandler); - console.log(tag, 'Injection verified successfully'); isInjected = true; // Send confirmation @@ -259,8 +234,7 @@ async function injectProviderScript(): Promise { window.addEventListener('message', verifyHandler); }; - script.onerror = error => { - console.error(tag, 'Script load error:', error); + script.onerror = () => { clearTimeout(timeout); resolve(false); }; @@ -268,7 +242,6 @@ async function injectProviderScript(): Promise { // Inject the script const target = document.head || document.documentElement; if (!target) { - console.error(tag, 'No suitable injection target found'); resolve(false); return; } @@ -281,8 +254,7 @@ async function injectProviderScript(): Promise { script.remove(); } }, 100); - } catch (error) { - console.error(tag, 'Injection error:', error); + } catch (_error) { resolve(false); } }); @@ -293,18 +265,15 @@ async function injectWithRetry(): Promise { for (let i = 0; i < MAX_INJECTION_RETRIES; i++) { const success = await injectProviderScript(); if (success) { - console.log(TAG, 'Injection successful'); return true; } if (i < MAX_INJECTION_RETRIES - 1) { const delay = Math.pow(2, i) * 100; // Exponential backoff: 100ms, 200ms, 400ms - console.log(TAG, `Retrying injection in ${delay}ms...`); await new Promise(resolve => setTimeout(resolve, delay)); } } - console.error(TAG, 'Failed to inject after maximum retries'); return false; } @@ -340,8 +309,6 @@ function waitForInjectionTarget(): Promise { // Initialize injection based on document state async function initialize() { - console.log(TAG, 'Initializing content script'); - // Wait for injection target to be available await waitForInjectionTarget(); @@ -349,7 +316,6 @@ async function initialize() { const injected = await injectWithRetry(); if (!injected) { - console.error(TAG, 'Failed to inject provider script'); // Notify background script of failure chrome.runtime.sendMessage({ type: 'INJECTION_FAILED', @@ -367,14 +333,13 @@ async function initialize() { } // Start initialization -initialize().catch(error => { - console.error(TAG, 'Initialization error:', error); +initialize().catch(() => { + // swallow initialization errors silently }); // Handle page visibility changes (for single-page apps) document.addEventListener('visibilitychange', () => { if (!document.hidden && !isInjected) { - console.log(TAG, 'Page became visible, checking injection status'); injectWithRetry(); } }); @@ -385,7 +350,6 @@ const checkForUrlChange = () => { const currentUrl = window.location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; - console.log(TAG, 'URL changed, checking injection status'); if (!isInjected) { injectWithRetry(); } @@ -399,13 +363,9 @@ setInterval(checkForUrlChange, 1000); // This enables EIP-1193 accountsChanged / chainChanged events for dApps chrome.runtime.onMessage.addListener((message: any) => { if (message.type === 'ACCOUNTS_CHANGED') { - console.log(TAG, 'Relaying ACCOUNTS_CHANGED to page:', message.accounts); window.postMessage({ type: 'ACCOUNTS_CHANGED', accounts: message.accounts }, '*'); } if (message.type === 'CHAIN_CHANGED') { - console.log(TAG, 'Relaying CHAIN_CHANGED to page:', message.provider?.chainId); window.postMessage({ type: 'CHAIN_CHANGED', provider: message.provider }, '*'); } }); - -console.log(TAG, 'Content script loaded');