diff --git a/chrome-extension/public/injected.js b/chrome-extension/public/injected.js index 3dce14a..8f1d8b7 100644 --- a/chrome-extension/public/injected.js +++ b/chrome-extension/public/injected.js @@ -1,15 +1,15 @@ -"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+=` +"use strict";(()=>{var P="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function L(d){let e=[0];for(let t of d){let i=P.indexOf(t);if(i===-1)throw new Error("Invalid base58 character");let n=i;for(let g=0;g>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of d){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}var R=class d{#r;#e=[];#n=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 e=this.#n||await this.#t("solana_connect",[]);return e&&this.#a(e),{accounts:this.#e}}},"standard:disconnect":{version:"1.0.0",disconnect:async()=>{await this.#t("solana_disconnect",[]).catch(()=>{}),this.#e=[];try{localStorage.removeItem("keepkey-solana")}catch{}this.#i()}},"standard:events":{version:"1.0.0",on:(e,t)=>(e==="change"&&this.#s.add(t),()=>{this.#s.delete(t)})},"solana:signMessage":{version:"1.0.0",signMessage:async(...e)=>{let t=[];for(let{message:i}of e){let n=await this.#t("solana_signMessage",[Array.from(i)]);t.push({signedMessage:i,signature:new Uint8Array(n)})}return t}},"solana:signTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signTransaction:async(...e)=>{let t=[];for(let{transaction:i}of e){let n=await this.#t("solana_signTransaction",[Array.from(i)]);t.push({signedTransaction:new Uint8Array(n)})}return t}},"solana:signAndSendTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signAndSendTransaction:async(...e)=>{let t=[];for(let{transaction:i}of e){let n=await this.#t("solana_signAndSendTransaction",[Array.from(i)]);t.push({signature:L(n)})}return t}},"solana:signIn":{version:"1.0.0",signIn:async(...e)=>{var i;let t=[];for(let n of e){if(this.#e.length===0){let v=this.#n||await this.#t("solana_connect",[]);v&&this.#a(v)}let g=this.#e[0];if(!g)throw new Error("Not connected");let c=(n==null?void 0:n.domain)||location.host,u=(n==null?void 0:n.address)||g.address,y=(n==null?void 0:n.uri)||location.href,p=(n==null?void 0:n.version)||"1",b=(n==null?void 0:n.chainId)||"mainnet",k=(n==null?void 0:n.nonce)||Math.random().toString(36).substring(2),E=(n==null?void 0:n.issuedAt)||new Date().toISOString(),B=(n==null?void 0:n.statement)||"",w=`${c} wants you to sign in with your Solana account: +${u}`;if(B&&(w+=` -${b}`),u+=` +${B}`),w+=` -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)}})})();})(); +URI: ${y}`,w+=` +Version: ${p}`,w+=` +Chain ID: ${b}`,w+=` +Nonce: ${k}`,w+=` +Issued At: ${E}`,n!=null&&n.expirationTime&&(w+=` +Expiration Time: ${n.expirationTime}`),n!=null&&n.notBefore&&(w+=` +Not Before: ${n.notBefore}`),n!=null&&n.requestId&&(w+=` +Request ID: ${n.requestId}`),(i=n==null?void 0:n.resources)!=null&&i.length){w+=` +Resources:`;for(let v of n.resources)w+=` +- ${v}`}let I=new TextEncoder().encode(w),T=await this.#t("solana_signMessage",[Array.from(I)]);t.push({account:g,signedMessage:I,signature:new Uint8Array(T)})}return t}}};constructor(e){this.#r=e;try{let t=localStorage.getItem("keepkey-solana");if(t){let{address:i}=JSON.parse(t);i&&typeof i=="string"&&(this.#n=i)}}catch{}this.#c()}#o(e){return{address:e,publicKey:L(e),chains:["solana:mainnet"],features:[...d.ACCOUNT_FEATURES]}}#a(e){this.#e=[this.#o(e)];try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}this.#i()}async#c(){try{let e=await this.#t("solana_connect",[]);if(e&&typeof e=="string"){this.#n=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}}}catch{}}#i(){let e=this.#e,t=this.features;this.#s.forEach(i=>{try{i({accounts:e,features:t})}catch{}})}#t(e,t){return new Promise((i,n)=>{this.#r(e,t,"solana",(g,c)=>{g?n(g):i(c)})})}};function _(d){let e=({register:t})=>{t(d)};try{let t=window.navigator;t.wallets||(t.wallets=[]),Array.isArray(t.wallets)?t.wallets.push(e):typeof t.wallets.register=="function"&&t.wallets.register(d)}catch{}try{window.dispatchEvent(new CustomEvent("wallet-standard:register-wallet",{detail:e}))}catch{}window.addEventListener("wallet-standard:app-ready",t=>{let i=t;try{typeof i.detail=="function"&&i.detail(e)}catch{}})}var W="https://api.trongrid.io",H="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function z(d){let e=[0];for(let t of d){let i=H.indexOf(t);if(i===-1)throw new Error("Invalid base58 character");let n=i;for(let g=0;g>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of d){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}function Q(d){let e="";for(let t of d)e+=t.toString(16).padStart(2,"0");return e}function U(d){let e=z(d);if(e.length!==25||e[0]!==65)throw new Error(`Invalid Tron address: ${d}`);return Q(e.slice(0,21))}function q(d){if(typeof d!="string"||d.length!==34||!d.startsWith("T"))return!1;try{return U(d),!0}catch{return!1}}var N=class{events=new Map;on(e,t){this.events.has(e)||this.events.set(e,new Set),this.events.get(e).add(t)}off(e,t){var i;(i=this.events.get(e))==null||i.delete(t)}emit(e,...t){var i;(i=this.events.get(e))==null||i.forEach(n=>{try{n(...t)}catch{}})}};function O(d,e,t){return new Promise((i,n)=>{d(e,t,"tron",(g,c)=>{g?n(g):i(c)})})}async function C(d,e){let t=await fetch(`${W}${d}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){let i=await t.text().catch(()=>"");throw new Error(`TronGrid ${d} failed (${t.status}): ${i}`)}return t.json()}var x=class{tronWeb;tronLink;address=null;hexAddress=null;emitter=new N;walletRequest;constructor(e){this.walletRequest=e,this.tronWeb=this.buildTronWeb(),this.tronLink=this.buildTronLink()}setAddress(e){!e||!q(e)||(this.address=e,this.hexAddress=U(e),this.tronWeb.ready=!0,this.tronWeb.defaultAddress={base58:e,hex:this.hexAddress,name:"KeepKey",type:1},this.tronLink.ready=!0,this.fireMessage("setAccount",{address:e,name:"KeepKey",type:1}),this.fireMessage("accountsChanged",{address:e}))}fireMessage(e,t){try{window.postMessage({message:{action:e,data:t},isTronLink:!0},window.location.origin)}catch{}this.emitter.emit(e,t)}buildTronLink(){return{isTronLink:!0,ready:!1,tronWeb:null,request:async({method:e,params:t})=>{switch(e){case"tron_requestAccounts":case"tron_accounts":{let i=await O(this.walletRequest,"tron_requestAccounts",[]);return!i||typeof i!="string"?{code:4001,message:"User denied account access"}:(this.setAddress(i),{code:200,message:"ok"})}default:return O(this.walletRequest,e,Array.isArray(t)?t:[t])}},on:(e,t)=>this.emitter.on(e,t),off:(e,t)=>this.emitter.off(e,t)}}buildTronWeb(){let e=this,t={sign:async(c,u,y,p)=>{if(typeof c=="string")throw new Error("tronWeb.trx.sign(message) not supported \u2014 use tronWeb.trx.signMessage()");if(!c||!c.raw_data_hex)throw new Error("tronWeb.trx.sign expects a built transaction with raw_data_hex");return await O(e.walletRequest,"tron_sign",[c])},signMessage:async(c,u)=>{throw new Error("tronWeb.trx.signMessage is not yet supported by KeepKey")},signMessageV2:async(c,u)=>{throw new Error("tronWeb.trx.signMessageV2 is not yet supported by KeepKey")},sendRawTransaction:async c=>C("/wallet/broadcasttransaction",c),broadcast:async c=>t.sendRawTransaction(c),getBalance:async c=>{let u=c||e.address;if(!u)throw new Error("No address \u2014 call tron_requestAccounts first");let y=await C("/wallet/getaccount",{address:u,visible:!0});return typeof(y==null?void 0:y.balance)=="number"?y.balance:0},getAccount:async c=>{let u=c||e.address;if(!u)throw new Error("No address \u2014 call tron_requestAccounts first");return C("/wallet/getaccount",{address:u,visible:!0})},getUnconfirmedAccount:async c=>{let u=c||e.address;if(!u)throw new Error("No address \u2014 call tron_requestAccounts first");return C("/wallet/getaccount",{address:u,visible:!0})},getTransaction:async c=>C("/wallet/gettransactionbyid",{value:c})},g={isTronLink:!0,ready:!1,defaultAddress:{base58:!1,hex:!1,name:!1,type:-1},fullNode:{host:W},solidityNode:{host:W},eventServer:{host:W},trx:t,transactionBuilder:{sendTrx:async(c,u,y)=>{let p=y||e.address;if(!p)throw new Error("No address \u2014 call tron_requestAccounts first");return C("/wallet/createtransaction",{owner_address:p,to_address:c,amount:u,visible:!0})},triggerSmartContract:async(c,u,y={},p=[],b)=>{let k=b||e.address;if(!k)throw new Error("No address \u2014 call tron_requestAccounts first");return C("/wallet/triggersmartcontract",{contract_address:c,function_selector:u,parameter:X(p),fee_limit:y.feeLimit??1e8,call_value:y.callValue??0,owner_address:k,visible:!0})}},utils:{isAddress:c=>q(c),fromSun:c=>String(Number(c)/1e6),toSun:c=>String(Math.round(Number(c)*1e6)),toHex:c=>U(c)},on:(c,u)=>this.emitter.on(c,u),off:(c,u)=>this.emitter.off(c,u),setAddress:c=>{},isConnected:()=>this.address!==null};return queueMicrotask(()=>{this.tronLink&&(this.tronLink.tronWeb=g)}),g}};function X(d){if(!Array.isArray(d)||d.length===0)return"";let e="";for(let t of d)if(t.type==="address"){let i=String(t.value),n=i.startsWith("T")?U(i).slice(2):i.replace(/^0x/,"").replace(/^41/,"");e+=n.padStart(64,"0")}else if(t.type==="uint256"||t.type==="uint"){let i=BigInt(t.value);e+=i.toString(16).padStart(64,"0")}else{let i=String(t.value).replace(/^0x/,"");e+=i.padStart(64,"0")}return e}(function(){let d="2.1.0",g=window,c={isInjected:!1,version:d,injectedAt:Date.now(),retryCount:0};if(g.keepkeyInjectionState&&g.keepkeyInjectionState.version>=d)return;g.keepkeyInjectionState=c;let u=(()=>{var s;let l={enableMetaMaskMasking:!1,enableXfiMasking:!1,enableKeplrMasking:!1};try{let o=document.currentScript,r=document.getElementById("keepkey-injected-script"),a=(s=o==null?void 0:o.dataset)!=null&&s.masking?o:r,A=a==null?void 0:a.dataset.masking;if(!A)return l;let f=JSON.parse(A);return{enableMetaMaskMasking:f.enableMetaMaskMasking===!0,enableXfiMasking:f.enableXfiMasking===!0,enableKeplrMasking:f.enableKeplrMasking===!0}}catch{return l}})();console.log(`[KeepKey] masking: metamask=${u.enableMetaMaskMasking?"on":"off"} xfi=${u.enableXfiMasking?"on":"off"} keplr=${u.enableKeplrMasking?"on":"off"}`);let y={siteUrl:window.location.href,scriptSource:"KeepKey Extension",version:d,injectedTime:new Date().toISOString(),origin:window.location.origin,protocol:window.location.protocol},p=0,b=new Map,k=[],E=!1;setInterval(()=>{let l=Date.now();b.forEach((s,o)=>{l-s.timestamp>3e5&&(s.callback(new Error("Request timeout")),b.delete(o))})},5e3);let w=l=>{k.length>=100&&k.shift(),k.push(l)},I=()=>{if(E)for(;k.length>0;){let l=k.shift();l&&window.postMessage(l,window.location.origin)}},T=(l=0)=>new Promise(s=>{let o=++p,r=setTimeout(()=>{l<3?setTimeout(()=>{T(l+1).then(s)},100*Math.pow(2,l)):(c.lastError="Failed to verify injection",s(!1))},1e3),a=A=>{var f,m,M;A.source===window&&((f=A.data)==null?void 0:f.source)==="keepkey-content"&&((m=A.data)==null?void 0:m.type)==="INJECTION_CONFIRMED"&&((M=A.data)==null?void 0:M.requestId)===o&&(clearTimeout(r),window.removeEventListener("message",a),E=!0,c.isInjected=!0,I(),s(!0))};window.addEventListener("message",a),window.postMessage({source:"keepkey-injected",type:"INJECTION_VERIFY",requestId:o,version:d,timestamp:Date.now()},window.location.origin)});function v(l,s=[],o,r){if(!l||typeof l!="string"){r(new Error("Invalid method"));return}Array.isArray(s)||(s=[s]);try{let a=++p,A={id:a,method:l,params:s,chain:o,siteUrl:y.siteUrl,scriptSource:y.scriptSource,version:y.version,requestTime:new Date().toISOString(),referrer:document.referrer,href:window.location.href,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language};b.set(a,{callback:r,timestamp:Date.now(),method:l});let f={source:"keepkey-injected",type:"WALLET_REQUEST",requestId:a,requestInfo:A,timestamp:Date.now()};E?window.postMessage(f,window.location.origin):w(f)}catch(a){r(a)}}window.addEventListener("message",l=>{if(l.source!==window)return;let s=l.data;if(!(!s||typeof s!="object")){if(s.source==="keepkey-content"&&s.type==="INJECTION_CONFIRMED"){E=!0,I();return}if(s.source==="keepkey-content"&&s.type==="WALLET_RESPONSE"&&s.requestId){let o=b.get(s.requestId);o&&(s.error?o.callback(s.error):o.callback(null,s.result),b.delete(s.requestId))}}});class F{events=new Map;on(s,o){this.events.has(s)||this.events.set(s,new Set),this.events.get(s).add(o)}off(s,o){var r;(r=this.events.get(s))==null||r.delete(o)}removeListener(s,o){this.off(s,o)}removeAllListeners(s){s?this.events.delete(s):this.events.clear()}emit(s,...o){var r;(r=this.events.get(s))==null||r.forEach(a=>{try{a(...o)}catch{}})}once(s,o){let r=(...a)=>{o(...a),this.off(s,r)};this.on(s,r)}}function h(l){let s=new F,o={network:"mainnet",isKeepKey:!0,isMetaMask:u.enableMetaMaskMasking,isConnected:()=>E,request:({method:r,params:a=[]})=>new Promise((A,f)=>{v(r,a,l,(m,M)=>{m?f(m):A(M)})}),send:(r,a,A)=>{if(r.chain||(r.chain=l),typeof A=="function"){v(r.method,r.params||a,l,(f,m)=>{f?A(f):A(null,{id:r.id,jsonrpc:"2.0",result:m})});return}else return{id:r.id,jsonrpc:"2.0",result:null}},sendAsync:(r,a,A)=>{r.chain||(r.chain=l);let f=A||a;typeof f=="function"&&v(r.method,r.params||a,l,(m,M)=>{m?f(m):f(null,{id:r.id,jsonrpc:"2.0",result:M})})},on:(r,a)=>(s.on(r,a),o),off:(r,a)=>(s.off(r,a),o),removeListener:(r,a)=>(s.removeListener(r,a),o),removeAllListeners:r=>(s.removeAllListeners(r),o),emit:(r,...a)=>(s.emit(r,...a),o),once:(r,a)=>(s.once(r,a),o),enable:()=>o.request({method:"eth_requestAccounts"}),_metamask:{isUnlocked:()=>Promise.resolve(!0)}};return l==="ethereum"&&(o.chainId="0x1",o.networkVersion="1",o.selectedAddress=null,o._handleAccountsChanged=r=>{o.selectedAddress=r[0]||null,s.emit("accountsChanged",r)},o._handleChainChanged=r=>{o.chainId=r,s.emit("chainChanged",r)},o._handleConnect=r=>{s.emit("connect",r)},o._handleDisconnect=r=>{o.selectedAddress=null,s.emit("disconnect",r)}),o}let j="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAADUklEQVRYCb1XTUgUYRie3bXEWhVLQaUsgwVLoUtEQjUJiZX0A0GX7BIZXurkOTSvdo2kvETHAsOshFgqOqhlRD9C7SGS1JTCsj1krU7PM+w7zMzOzuzMqi88+73v9z7vz3zzzTeziuIgmqbFgG5gBPguFOgq4CXLIMwCo0AXEJN4zxHkEuA6kAIMkUBMqMZk7so/UG8AUcnjOIKwFXgHZIgEwKFmOHOfYO4aySVjmAoc7O4R0EB7lYS5h9K1jBJ6A7CuAfXG7OopbKLXkh4dccNZ7jlsi0gAJlWLI5jBPWFsTK5AGxCRImswFqDGWanDBo6IsYbjUanFbmrFWIHxD3IsmfJsgB4y2aJuF4UrUC5GnuNtxJeEQqEoAb3LJV+F4ctlHwkZXDULv8fEKQCHB4+rCJ9ngKcIGUTVRubT027y8yR9bOM4mhKTTwNJZD4miaDXAG8dqzlMShw3YRCZRVAr7vU4g5F/D4ZBoJK2H+Em9CsfEdBoKn4K9jPAd3G9sMPqZEzpRPzAwRfWJpN9EfZSRkAOE5LD7wrw8dkpwRh55VMm27fqt4FiVBjGBTaxEm4Db8d+4BPtIOK3AdbYCPC1qh/haGIS9gHgDeBbgjTAIkXAfTRxkgaamMNwCHgB+BMk4Decq0hGkFQbka/WMyZ/EeyHNo6TuSwx3Nn8gHQVIYOkOhB5Gp4zcdbBHiDvZ2pRuzozru2euKuDOucg/KliTAjKKMa9ksBpxBLrbzRwVfifOnB4RR2g3QSH3Cfx5FRdc2KoGstroUeQKh47vnAwWvUKjsPcA/wWdBUkjRAgZdsznO8D5xLGC/Opxc3NiQeV9uIsgkNDaUoMFpNDLleAn0cTQNBjGaFW6fn2Wrky/dI6abPOl9eN9deoWhjLloCv3+bPy7w3/9kzfvjX120g1cuSdsJ47xm1CgS9AaxCErlbV6qJ22W1nq22lG75AtIHWQEeJpOYaAT6gBQQWC5XNCjc7dkkHFKWe6v3FcLfbzRAMlcC6IC6C+gGxgCectZnCRMuopVG1v+Nx04sYINlxLH4wI6W52UFhT+Q41b2Nl0qeLnwZPGQucNHrXN6ZDG94RQuO688XbwNFzvjlSuwH03wEW8H+Bf/dxrUOWdc+H8mKXtEpGpY3AAAAABJRU5ErkJggg==",D="data:image/svg+xml;utf8,"+encodeURIComponent('MM');function S(l){let s={uuid:"350670db-19fa-4704-a166-e52e178b59d4",name:"KeepKey",icon:j,rdns:"com.keepkey.client"};if(window.dispatchEvent(new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:s,provider:l})})),u.enableMetaMaskMasking){let o={uuid:"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",name:"MetaMask",icon:D,rdns:"io.metamask"};window.dispatchEvent(new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:o,provider:l})}))}}async function G(){let l=h("ethereum"),s={binance:h("binance"),bitcoin:h("bitcoin"),bitcoincash:h("bitcoincash"),dogecoin:h("dogecoin"),dash:h("dash"),ethereum:l,keplr:h("keplr"),litecoin:h("litecoin"),thorchain:h("thorchain"),mayachain:h("mayachain")},o={binance:h("binance"),bitcoin:h("bitcoin"),bitcoincash:h("bitcoincash"),dogecoin:h("dogecoin"),dash:h("dash"),ethereum:l,osmosis:h("osmosis"),cosmos:h("cosmos"),litecoin:h("litecoin"),thorchain:h("thorchain"),mayachain:h("mayachain"),ripple:h("ripple")},r=(a,A,{force:f=!1}={})=>{if(!(g[a]&&!f))try{Object.defineProperty(g,a,{value:A,writable:!1,configurable:!0})}catch{c.lastError=`Failed to mount ${a}`}};u.enableMetaMaskMasking&&r("ethereum",l),u.enableXfiMasking&&r("xfi",s),r("keepkey",o,{force:!0}),window.addEventListener("eip6963:requestProvider",()=>{S(l)}),S(l),setTimeout(()=>{S(l)},100);try{let a=new R(v);_(a)}catch{}try{let a=new x(v);g.tronLink||Object.defineProperty(g,"tronLink",{value:a.tronLink,writable:!1,configurable:!0}),g.tronWeb||Object.defineProperty(g,"tronWeb",{value:a.tronWeb,writable:!1,configurable:!0})}catch{}window.addEventListener("message",a=>{var A,f,m;((A=a.data)==null?void 0:A.type)==="CHAIN_CHANGED"&&l.emit("chainChanged",(f=a.data.provider)==null?void 0:f.chainId),((m=a.data)==null?void 0:m.type)==="ACCOUNTS_CHANGED"&&l._handleAccountsChanged&&l._handleAccountsChanged(a.data.accounts||[])}),T().then(a=>{a||(c.lastError="Injection not verified")})}G(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{if(g.ethereum&&typeof g.dispatchEvent=="function"){let l=g.ethereum;S(l)}})})();})(); diff --git a/chrome-extension/src/background/chains/tronHandler.ts b/chrome-extension/src/background/chains/tronHandler.ts index 36dd7ed..74f3f07 100644 --- a/chrome-extension/src/background/chains/tronHandler.ts +++ b/chrome-extension/src/background/chains/tronHandler.ts @@ -317,7 +317,11 @@ async function buildTrc20Transfer( * The vault's handler emulates emuWrap for the device, so this call may block * until the user confirms on the KeepKey. */ -async function signTronViaRest(rawDataHex: string, toAddress: string, amountRaw: string | number): Promise { +async function signTronViaRest( + rawDataHex: string, + toAddress: string, + amountRaw: string | number | bigint, +): Promise { const apiKey = getApiKey(); let resp: Response; try { @@ -331,10 +335,12 @@ async function signTronViaRest(rawDataHex: string, toAddress: string, amountRaw: addressNList: TRON_ADDRESS_N, raw_tx: rawDataHex, to_address: toAddress, - // Keep as-is — TRC-20 amounts can exceed Number.MAX_SAFE_INTEGER - // for 18-decimal tokens. Caller passes a decimal string; passing - // a number would overflow silently at ~2^53 base units. - amount: String(amountRaw), + // Stringify without going through Number — 18-decimal TRC-20 + // amounts exceed Number.MAX_SAFE_INTEGER in base units and + // would silently round before hitting the vault. Handle bigint + // explicitly since String(bigint) works but the intent is + // clearer and future-proofed. + amount: typeof amountRaw === 'bigint' ? amountRaw.toString() : String(amountRaw), }), signal: AbortSignal.timeout(60000), }); @@ -358,6 +364,152 @@ async function signTronViaRest(rawDataHex: string, toAddress: string, amountRaw: return signature; } +/** + * Convert a Tron hex address (e.g. `41xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`) + * to its base58 form (T...). Uses SHA-256 double-hash for the checksum, + * via WebCrypto (available in service workers). + * + * Only used from the tron_sign path when the tx was built without + * `visible: true` and comes through with hex addresses — we normalise to + * base58 for the approval UI and vault signing hint. + */ +async function hexAddressToBase58(hexAddr: string): Promise { + const hex = hexAddr.toLowerCase().replace(/^0x/, ''); + if (hex.length !== 42 || !hex.startsWith('41')) { + throw new Error(`Invalid Tron hex address: ${hexAddr}`); + } + const payload = Uint8Array.from(hex.match(/.{2}/g)!.map(b => parseInt(b, 16))); + const h1 = new Uint8Array(await crypto.subtle.digest('SHA-256', payload)); + const h2 = new Uint8Array(await crypto.subtle.digest('SHA-256', h1)); + const checksum = h2.slice(0, 4); + const full = new Uint8Array(payload.length + 4); + full.set(payload, 0); + full.set(checksum, payload.length); + + const ALPHA = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + const digits: number[] = [0]; + for (const byte of full) { + let carry = byte; + for (let j = 0; j < digits.length; j++) { + carry += digits[j] << 8; + digits[j] = carry % 58; + carry = (carry / 58) | 0; + } + while (carry > 0) { + digits.push(carry % 58); + carry = (carry / 58) | 0; + } + } + // Leading zero bytes → leading '1's. + let out = ''; + for (const b of full) { + if (b !== 0) break; + out += '1'; + } + for (let i = digits.length - 1; i >= 0; i--) out += ALPHA[digits[i]]; + return out; +} + +interface DecodedTronTx { + kind: 'trx-transfer' | 'trc20-transfer' | 'contract-call'; + ownerAddress: string; // base58 + toAddress: string; // base58 — recipient for transfers, contract address for generic calls + // Raw base-units as a DECIMAL STRING so 18-decimal TRC-20 amounts + // survive the trip to the vault. Going through Number would truncate + // at ~2^53 base units (9e15 — fine for 6-decimal TRX/USDT, broken + // for 18-decimal tokens). + amountRaw: string; + displayAmount: string; // human-readable + contractAddress?: string; // base58, non-native + functionSelector?: string; // hex, contract-call only +} + +/** + * Decode the first contract from a tronweb-built transaction into the + * fields we need for approval UX and the vault signing hint. + * + * Supports: + * - TransferContract (native TRX) + * - TriggerSmartContract with `transfer(address,uint256)` (TRC20) + * + * Throws on any other contract type so dApps fail fast with a clear + * message instead of the device rejecting an un-parseable tx. + */ +async function decodeTronTx(tx: any): Promise { + const contract = tx?.raw_data?.contract?.[0]; + if (!contract) throw createProviderRpcError(4000, 'Transaction has no contract'); + + const value = contract.parameter?.value; + if (!value) throw createProviderRpcError(4000, 'Contract has no parameter value'); + + const visible = tx.visible === true; + const normalizeAddr = async (addr: string): Promise => { + if (!addr) throw createProviderRpcError(4000, 'Missing address'); + if (addr.startsWith('T') && addr.length === 34) return addr; + if (visible) return addr; + return hexAddressToBase58(addr); + }; + + if (contract.type === 'TransferContract') { + const v = value as { amount: number; owner_address: string; to_address: string }; + return { + kind: 'trx-transfer', + ownerAddress: await normalizeAddr(v.owner_address), + toAddress: await normalizeAddr(v.to_address), + amountRaw: String(v.amount), + displayAmount: String(v.amount / 1_000_000), + }; + } + + if (contract.type === 'TriggerSmartContract') { + const v = value as { contract_address: string; owner_address: string; data: string; call_value?: number }; + const data = String(v.data || '') + .toLowerCase() + .replace(/^0x/, ''); + const selector = data.slice(0, 8); + const contractAddress = await normalizeAddr(v.contract_address); + const ownerAddress = await normalizeAddr(v.owner_address); + + // transfer(address,uint256) — the canonical TRC20 transfer path. + // Decode to show the real recipient + token amount in the approval + // UI; this is by far the most common smart-contract call on Tron. + if (selector === 'a9059cbb' && data.length >= 8 + 64 * 2) { + const recipientHex = data.slice(8 + 24, 8 + 64); + const amountHex = data.slice(8 + 64, 8 + 64 * 2); + const amount = BigInt('0x' + amountHex); + // Tron hex addresses are prefixed with 0x41. ABI `address` pads to + // 20 bytes (no prefix), so re-prepend before base58-encoding. + const recipientBase58 = await hexAddressToBase58('41' + recipientHex); + return { + kind: 'trc20-transfer', + ownerAddress, + toAddress: recipientBase58, + amountRaw: amount.toString(), + displayAmount: amount.toString(), + contractAddress, + }; + } + + // Generic contract call (swaps, stake, approve, etc.). The firmware + // parses the raw_data itself and signs based on what it finds; our + // hints here are purely for the side-panel approval UI. Route + // `call_value` (TRX attached to the call) to amountRaw so swaps + // that spend native TRX display the right outgoing amount. + const callValue = typeof v.call_value === 'number' ? v.call_value : 0; + return { + kind: 'contract-call', + ownerAddress, + toAddress: contractAddress, + amountRaw: String(callValue), + displayAmount: String(callValue / 1_000_000), + contractAddress, + functionSelector: selector, + }; + } + + throw createProviderRpcError(4200, `Tron contract type "${contract.type}" is not supported by KeepKey yet.`); +} + /** Broadcast a signed Tron tx via TronGrid. Returns the txid. */ async function broadcastTron(signedTx: any): Promise { let resp: Response; @@ -400,6 +552,7 @@ export const handleTronRequest = async ( switch (method) { case 'request_accounts': + case 'tron_requestAccounts': case 'tron_connect': case 'tron_getAccount': { return await getTronAddress(); @@ -409,6 +562,147 @@ export const handleTronRequest = async ( return wallet.getPubkeys(TRON_NETWORK_ID); } + // dApp flow (window.tronWeb.trx.sign). Payload: params[0] is a full + // transaction built by tronweb — { txID, raw_data, raw_data_hex, ... }. + // We parse the first contract to extract display hints (to_address, + // amount) the vault / firmware need, run the approval loop, then + // return the signed tx for the dApp to broadcast itself. + case 'tron_sign': + case 'signTransaction': { + const tx = params?.[0]; + if (!tx || typeof tx !== 'object' || !tx.raw_data_hex) { + throw createProviderRpcError(4000, 'tron_sign expects a built transaction with raw_data_hex'); + } + + const decoded = await decodeTronTx(tx); + const sender = await getTronAddress(); + + if (decoded.ownerAddress && decoded.ownerAddress !== sender) { + // dApps occasionally hard-code an owner address from a cached + // session; surface the mismatch so the dApp can recover rather + // than letting the device reject it silently. + throw createProviderRpcError( + 4001, + `Transaction owner (${decoded.ownerAddress}) does not match connected KeepKey address (${sender}).`, + ); + } + + if (!requestInfo.id) requestInfo.id = uuidv4(); + // Always emit type='transfer'. The shared "other" approval + // renderer (pages/side-panel/src/approval/other/*) only has a + // cased handler for 'transfer' + reads `unsignedTx.payment.*` — + // anything else shows "Unknown Method" / N/A. Downstream code + // that cares about kind can still branch on `contractAddress` / + // `functionSelector` on the event. + // + // decimals: native TRX is 6 (sun); TRC-20 transfer()s use the + // displayAmount's decimals which we don't know from raw_data + // alone — default to 0 so the UI shows raw base units, which is + // at least correct, not misleading. Contract-call rows attach + // call_value (TRX, 6 decimals). Callers that want prettier + // token display should teach decodeTronTx to look up decimals + // from assetData or an on-chain call — not a fix for this PR. + const decimals = decoded.kind === 'trc20-transfer' ? 0 : 6; + // For TRC-20 events, scope the caip to the specific token + // contract so the UI's ctx.caip-match gate only fires when the + // side-panel had THIS token selected. Otherwise a dApp USDT + // transfer with the user on the TRX asset page would match + // (both sides = tron:27Lqcw/slip44:195) and render with the TRX + // symbol/icon — the exact leak #48's gate was meant to block. + // + // Native TRX and generic contract-call stay on TRON_CAIP: + // trx-transfer is native TRX so matching the TRX context is + // correct; contract-call renders its own Contract/Function UI + // with a hardcoded 'TRX' fallback on the call_value row, so a + // partial caip-match can't leak the wrong symbol into anything + // user-facing. + const eventCaip = + decoded.kind === 'trc20-transfer' && decoded.contractAddress + ? `${TRON_NETWORK_ID}/token:${decoded.contractAddress}` + : TRON_CAIP; + const event = buildEvent(requestInfo, 'transfer', params, { + caip: eventCaip, + from: sender, + to: decoded.toAddress, + amount: decoded.displayAmount, + // String, not number — preserves precision for 18-decimal TRC-20. + amountRaw: decoded.amountRaw, + decimals, + kind: decoded.kind, // 'trx-transfer' | 'trc20-transfer' | 'contract-call' for downstream branches + contractAddress: decoded.contractAddress, + functionSelector: decoded.functionSelector, + tronGridTx: tx, + rawDataHex: tx.raw_data_hex, + // Shape the "other" approval UI reads. `amount` stays as a raw + // base-units decimal string — formatAmount in the UI does the + // BigInt-safe division by `decimals`. + // + // symbol: + // trx-transfer / contract-call → 'TRX' (call_value on a + // contract call is always native TRX; stating it + // explicitly in the handler lets the UI render the amount + // row without falling through to asset-context guesswork) + // trc20-transfer → undefined (we don't know + // the token's symbol without an on-chain `symbol()` call + // or an assetData lookup — both are out of scope here; + // the UI's caip-match gate on asset context will decline + // to show a wrong symbol) + payment: { + destination: decoded.toAddress, + amount: decoded.amountRaw, + decimals, + symbol: decoded.kind === 'trc20-transfer' ? undefined : 'TRX', + }, + }); + // @ts-expect-error addEvent is untyped on the storage wrapper + const saved = await requestStorage.addEvent(event); + if (!saved) throw createProviderRpcError(-32603, 'Failed to create approval event'); + chrome.runtime.sendMessage({ action: 'TRANSACTION_CONTEXT_UPDATED', id: event.id }).catch(() => {}); + + const approval = await requireApproval(TRON_NETWORK_ID, requestInfo, 'tron', method, params); + if (!approval?.success) { + throw createProviderRpcError(4001, 'User denied transaction'); + } + + const signatureHex = await signTronViaRest(tx.raw_data_hex, decoded.toAddress, decoded.amountRaw); + + // Preserve any existing `signature` array from the dApp (multi-sig + // case) and append ours; most dApps pass in an unsigned tx so this + // ends up [ours]. + const existing: string[] = Array.isArray(tx.signature) ? tx.signature : []; + const signedTx = { ...tx, signature: [...existing, signatureHex] }; + + // Persist signedTx on the event for debugging/history. Broadcast is + // the dApp's job — they'll call tronWeb.trx.sendRawTransaction next. + try { + const stored = await requestStorage.getEventById(requestInfo.id); + if (stored) { + stored.signedTx = signedTx; + stored.status = 'completed'; + await requestStorage.updateEventById(requestInfo.id, stored); + } + } catch (e) { + console.warn(tag, 'Failed to persist signedTx:', e); + } + + // Dismiss the side-panel approval overlay. We don't emit + // `transaction_complete` (that's for flows where we also broadcast + // and have a txHash) — for dApp signing the broadcast happens + // client-side and the dApp's own UI confirms success. + chrome.runtime.sendMessage({ action: 'signature_complete', eventId: requestInfo.id }).catch(() => {}); + + return signedTx; + } + + case 'tron_signMessage': + case 'signMessage': + case 'signMessageV2': { + throw createProviderRpcError( + 4200, + 'Tron message signing is not yet supported by KeepKey. Use transaction signing instead.', + ); + } + // Side-panel Send flow. Payload: // params[0] = { caip, amount: { amount, denom }, recipient, memo, isMax } // diff --git a/chrome-extension/src/background/index.ts b/chrome-extension/src/background/index.ts index 7c4d5c4..16ef7e2 100644 --- a/chrome-extension/src/background/index.ts +++ b/chrome-extension/src/background/index.ts @@ -1749,12 +1749,6 @@ exampleSidebarStorage console.error('Error fetching sidebar storage:', error); }); -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === 'getMaskingSettings') { - chrome.storage.local.get(['enableMetaMaskMasking', 'enableXfiMasking', 'enableKeplrMasking'], result => { - console.log('getMaskingSettings result: ', result); - sendResponse(result); - }); - return true; - } -}); +// Masking settings are read directly by the content script from +// chrome.storage.local before injection; there's no background handler +// for them. diff --git a/chrome-extension/src/injected/injected.ts b/chrome-extension/src/injected/injected.ts index 9e9882a..38727b7 100644 --- a/chrome-extension/src/injected/injected.ts +++ b/chrome-extension/src/injected/injected.ts @@ -10,6 +10,7 @@ import type { } from './types'; import { KeepKeySolanaWallet } from './solana-wallet-standard'; import { registerSolanaWallet } from './solana-wallet-register'; +import { KeepKeyTronProvider } from './tron-provider'; (function () { const VERSION = '2.1.0'; @@ -41,6 +42,51 @@ import { registerSolanaWallet } from './solana-wallet-register'; // Set injection state kWindow.keepkeyInjectionState = injectionState; + // Read masking settings from the