diff --git a/chrome-extension/public/injected.js b/chrome-extension/public/injected.js index f26da71..3792b27 100644 --- a/chrome-extension/public/injected.js +++ b/chrome-extension/public/injected.js @@ -1,19 +1,19 @@ -"use strict";(()=>{var V="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function L(r){let e=[0];for(let t of r){let s=V.indexOf(t);if(s===-1)throw new Error("Invalid base58 character");let n=s;for(let a=0;a>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of r){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}var U=class r{#a;#e=[];#t=null;#r=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=await this.#s("solana_getAccounts",[]).catch(()=>null);if(!Array.isArray(e)||e.length===0){let t=this.#t||await this.#s("solana_connect",[]);e=t?[{address:t}]:[]}return this.#o(e),{accounts:this.#e}}},"standard:disconnect":{version:"1.0.0",disconnect:async()=>{await this.#s("solana_disconnect",[]).catch(()=>{}),this.#e=[];try{localStorage.removeItem("keepkey-solana")}catch{}this.#n()}},"standard:events":{version:"1.0.0",on:(e,t)=>(e==="change"&&this.#r.add(t),()=>{this.#r.delete(t)})},"solana:signMessage":{version:"1.0.0",signMessage:async(...e)=>{let t=[];for(let{message:s,account:n}of e){let a=await this.#s("solana_signMessage",[Array.from(s),{accountAddress:n==null?void 0:n.address}]);t.push({signedMessage:s,signature:new Uint8Array(a)})}return t}},"solana:signTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signTransaction:async(...e)=>{let t=[];for(let{transaction:s,account:n}of e){let a=await this.#s("solana_signTransaction",[Array.from(s),{accountAddress:n==null?void 0:n.address}]);t.push({signedTransaction:new Uint8Array(a)})}return t}},"solana:signAndSendTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signAndSendTransaction:async(...e)=>{let t=[];for(let{transaction:s,account:n}of e){let a=await this.#s("solana_signAndSendTransaction",[Array.from(s),{accountAddress:n==null?void 0:n.address}]);t.push({signature:L(a)})}return t}},"keepkey:signOffchainMessage":{version:"1.0.0",signOffchainMessage:async e=>{let t=typeof e.message=="string"?Array.from(new TextEncoder().encode(e.message)):Array.from(e.message);return await this.#s("solana_signOffchainMessage",[{message:t,version:e.version,messageFormat:e.messageFormat}])}},"solana:signIn":{version:"1.0.0",signIn:async(...e)=>{var s;let t=[];for(let n of e){if(this.#e.length===0){let E=this.#t||await this.#s("solana_connect",[]);E&&this.#c(E)}let a=(n==null?void 0:n.address)&&this.#e.find(E=>E.address===n.address)||this.#e[0];if(!a)throw new Error("Not connected");let i=(n==null?void 0:n.domain)||location.host,o=(n==null?void 0:n.address)||a.address,f=(n==null?void 0:n.uri)||location.href,A=(n==null?void 0:n.version)||"1",y=(n==null?void 0:n.chainId)||"mainnet",w=(n==null?void 0:n.nonce)||Math.random().toString(36).substring(2),v=(n==null?void 0:n.issuedAt)||new Date().toISOString(),C=(n==null?void 0:n.statement)||"",b=`${i} wants you to sign in with your Solana account: -${o}`;if(C&&(b+=` +"use strict";(()=>{var V="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function L(a){let e=[0];for(let t of a){let s=V.indexOf(t);if(s===-1)throw new Error("Invalid base58 character");let n=s;for(let r=0;r>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of a){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}var U=class a{#r;#e=[];#t=null;#a=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=await this.#s("solana_getAccounts",[]).catch(()=>null);if(!Array.isArray(e)||e.length===0){let t=this.#t||await this.#s("solana_connect",[]);e=t?[{address:t}]:[]}return this.#o(e),{accounts:this.#e}}},"standard:disconnect":{version:"1.0.0",disconnect:async()=>{await this.#s("solana_disconnect",[]).catch(()=>{}),this.#e=[];try{localStorage.removeItem("keepkey-solana")}catch{}this.#n()}},"standard:events":{version:"1.0.0",on:(e,t)=>(e==="change"&&this.#a.add(t),()=>{this.#a.delete(t)})},"solana:signMessage":{version:"1.0.0",signMessage:async(...e)=>{let t=[];for(let{message:s,account:n}of e){let r=await this.#s("solana_signMessage",[Array.from(s),{accountAddress:n==null?void 0:n.address}]);t.push({signedMessage:s,signature:new Uint8Array(r)})}return t}},"solana:signTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signTransaction:async(...e)=>{let t=[];for(let{transaction:s,account:n}of e){let r=await this.#s("solana_signTransaction",[Array.from(s),{accountAddress:n==null?void 0:n.address}]);t.push({signedTransaction:new Uint8Array(r)})}return t}},"solana:signAndSendTransaction":{version:"1.0.0",supportedTransactionVersions:new Set(["legacy",0]),signAndSendTransaction:async(...e)=>{let t=[];for(let{transaction:s,account:n}of e){let r=await this.#s("solana_signAndSendTransaction",[Array.from(s),{accountAddress:n==null?void 0:n.address}]);t.push({signature:L(r)})}return t}},"keepkey:signOffchainMessage":{version:"1.0.0",signOffchainMessage:async e=>{let t=typeof e.message=="string"?Array.from(new TextEncoder().encode(e.message)):Array.from(e.message);return await this.#s("solana_signOffchainMessage",[{message:t,version:e.version,messageFormat:e.messageFormat}])}},"solana:signIn":{version:"1.0.0",signIn:async(...e)=>{var s;let t=[];for(let n of e){if(this.#e.length===0){let E=this.#t||await this.#s("solana_connect",[]);E&&this.#c(E)}let r=(n==null?void 0:n.address)&&this.#e.find(E=>E.address===n.address)||this.#e[0];if(!r)throw new Error("Not connected");let o=(n==null?void 0:n.domain)||location.host,i=(n==null?void 0:n.address)||r.address,f=(n==null?void 0:n.uri)||location.href,h=(n==null?void 0:n.version)||"1",y=(n==null?void 0:n.chainId)||"mainnet",p=(n==null?void 0:n.nonce)||Math.random().toString(36).substring(2),b=(n==null?void 0:n.issuedAt)||new Date().toISOString(),M=(n==null?void 0:n.statement)||"",k=`${o} wants you to sign in with your Solana account: +${i}`;if(M&&(k+=` -${C}`),b+=` +${M}`),k+=` -URI: ${f}`,b+=` -Version: ${A}`,b+=` -Chain ID: ${y}`,b+=` -Nonce: ${w}`,b+=` -Issued At: ${v}`,n!=null&&n.expirationTime&&(b+=` -Expiration Time: ${n.expirationTime}`),n!=null&&n.notBefore&&(b+=` -Not Before: ${n.notBefore}`),n!=null&&n.requestId&&(b+=` -Request ID: ${n.requestId}`),(s=n==null?void 0:n.resources)!=null&&s.length){b+=` -Resources:`;for(let E of n.resources)b+=` -- ${E}`}let S=new TextEncoder().encode(b),R=await this.#s("solana_signMessage",[Array.from(S),{accountAddress:a.address}]);t.push({account:a,signedMessage:S,signature:new Uint8Array(R)})}return t}}};constructor(e){this.#a=e;try{let t=localStorage.getItem("keepkey-solana");if(t){let{address:s}=JSON.parse(t);s&&typeof s=="string"&&(this.#t=s)}}catch{}this.#l()}#i(e){return{address:e,publicKey:L(e),chains:["solana:mainnet"],features:[...r.ACCOUNT_FEATURES]}}#c(e){this.#e=[this.#i(e)];try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}this.#n()}#o(e){var t;this.#e=e.filter(s=>s==null?void 0:s.address).map(s=>this.#i(s.address));try{let s=(t=this.#e[0])==null?void 0:t.address;s&&localStorage.setItem("keepkey-solana",JSON.stringify({address:s}))}catch{}this.#n()}async#l(){try{let e=await this.#s("solana_connect",[]);if(e&&typeof e=="string"){this.#t=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}}}catch{}}#n(){let e=this.#e,t=this.features;this.#r.forEach(s=>{try{s({accounts:e,features:t})}catch{}})}#s(e,t){return new Promise((s,n)=>{this.#a(e,t,"solana",(a,i)=>{a?n(a):s(i)})})}};function x(r){let e=({register:s})=>{s(r)};try{let s=window.navigator;s.wallets||(s.wallets=[]),Array.isArray(s.wallets)?s.wallets.push(e):typeof s.wallets.register=="function"&&s.wallets.register(r)}catch{}let t=()=>{try{window.dispatchEvent(new CustomEvent("wallet-standard:register-wallet",{detail:e}))}catch{}};t(),setTimeout(t,100),setTimeout(t,1e3),window.addEventListener("wallet-standard:app-ready",s=>{let n=s.detail;try{n&&typeof n.register=="function"?e(n):typeof n=="function"&&n(e)}catch{}})}var Y="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function Z(r){let e=[0];for(let t of r){let s=Y.indexOf(t);if(s===-1)throw new Error("Invalid base58 character");let n=s;for(let a=0;a>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of r){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}function $(r){let e=Z(r);return{toString:()=>r,toBase58:()=>r,toBytes:()=>e,toBuffer:()=>e,equals:t=>{var s,n;try{return((s=t==null?void 0:t.toBase58)==null?void 0:s.call(t))===r||((n=t==null?void 0:t.toString)==null?void 0:n.call(t))===r}catch{return!1}}}}function j(r){return r!=null&&typeof r=="object"&&"version"in r&&r.message!=null}function F(r){if(!r||typeof r.serialize!="function")throw new Error("Unsupported transaction: missing serialize()");return j(r)?new Uint8Array(r.serialize()):new Uint8Array(r.serialize({requireAllSignatures:!1,verifySignatures:!1}))}function ee(r){let e=r[0];if(!e)throw new Error("Signed transaction has no signatures");if(e&128)throw new Error("Unexpected multi-byte signature count");return r.slice(1,65)}function te(r){var t,s;if(j(r)){let n=(t=r.message)==null?void 0:t.staticAccountKeys;if(n&&n.length)return n[0];throw new Error("Versioned transaction missing account keys")}if(r.feePayer)return r.feePayer;let e=(s=r.signatures)==null?void 0:s[0];if(e!=null&&e.publicKey)return e.publicKey;throw new Error("Legacy transaction missing fee payer")}var O=class{#a;#e=new Map;#t=null;#r=null;isPhantom=!0;isKeepKey=!0;constructor(e){this.#a=e;try{let t=localStorage.getItem("keepkey-solana");if(t){let{address:s}=JSON.parse(t);s&&typeof s=="string"&&(this.#r=s)}}catch{}this.#l()}get publicKey(){return this.#t}get isConnected(){return this.#t!==null}async connect(e){if(this.#t)return{publicKey:this.#t};let t=this.#r||await this.#n("solana_connect",[]);if(!t)throw new Error("Failed to connect to KeepKey");return this.#c(t),{publicKey:this.#t}}async disconnect(){await this.#n("solana_disconnect",[]).catch(()=>{}),this.#t=null;try{localStorage.removeItem("keepkey-solana")}catch{}this.#i("disconnect")}async signMessage(e,t){await this.#o();let s=await this.#n("solana_signMessage",[Array.from(e)]);return{signature:new Uint8Array(s),publicKey:this.#t}}async signTransaction(e){await this.#o();let t=F(e),s=await this.#n("solana_signTransaction",[Array.from(t)]),n=new Uint8Array(s),a=ee(n);return e.addSignature(te(e),a),e}async signAllTransactions(e){let t=[];for(let s of e)t.push(await this.signTransaction(s));return t}async signAndSendTransaction(e,t){await this.#o();let s=F(e);return{signature:await this.#n("solana_signAndSendTransaction",[Array.from(s)]),publicKey:this.#t}}async request({method:e,params:t}){var n,a,i,o,f,A;let s=Array.isArray(t)?t:t!=null?[t]:[];switch(e){case"connect":return this.connect(s[0]);case"disconnect":return this.disconnect();case"signTransaction":return this.signTransaction(((n=s[0])==null?void 0:n.transaction)??s[0]);case"signAllTransactions":return this.signAllTransactions(((a=s[0])==null?void 0:a.transactions)??s[0]);case"signAndSendTransaction":return this.signAndSendTransaction(((i=s[0])==null?void 0:i.transaction)??s[0],((o=s[0])==null?void 0:o.options)??s[1]);case"signMessage":return this.signMessage(((f=s[0])==null?void 0:f.message)??s[0],(A=s[0])==null?void 0:A.display);default:throw new Error(`KeepKey (Solana): unsupported method "${e}"`)}}on(e,t){return this.#e.has(e)||this.#e.set(e,new Set),this.#e.get(e).add(t),this}off(e,t){var s;return(s=this.#e.get(e))==null||s.delete(t),this}removeListener(e,t){return this.off(e,t)}removeAllListeners(e){return e?this.#e.delete(e):this.#e.clear(),this}#i(e,...t){var s;(s=this.#e.get(e))==null||s.forEach(n=>{try{n(...t)}catch{}})}#c(e){this.#t=$(e),this.#r=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}this.#i("connect",this.#t)}async#o(){this.#t||await this.connect()}async#l(){try{let e=await this.#n("solana_connect",[]);if(e&&typeof e=="string"){this.#r=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}}}catch{}}#n(e,t){return new Promise((s,n)=>{this.#a(e,t,"solana",(a,i)=>{a?n(a):s(i)})})}};var P="https://api.trongrid.io",ne="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function se(r){let e=[0];for(let t of r){let s=ne.indexOf(t);if(s===-1)throw new Error("Invalid base58 character");let n=s;for(let a=0;a>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of r){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}function re(r){let e="";for(let t of r)e+=t.toString(16).padStart(2,"0");return e}function W(r){let e=se(r);if(e.length!==25||e[0]!==65)throw new Error(`Invalid Tron address: ${r}`);return re(e.slice(0,21))}function D(r){if(typeof r!="string"||r.length!==34||!r.startsWith("T"))return!1;try{return W(r),!0}catch{return!1}}var B=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 s;(s=this.events.get(e))==null||s.delete(t)}emit(e,...t){var s;(s=this.events.get(e))==null||s.forEach(n=>{try{n(...t)}catch{}})}};function K(r,e,t){return new Promise((s,n)=>{r(e,t,"tron",(a,i)=>{a?n(a):s(i)})})}var ae=8e3,ie=12e3,oe=r=>r>=500&&r<600;async function M(r,e){let s=r.includes("broadcasttransaction")?ie:ae,n=2,a;for(let i=1;i<=n;i++)try{let o=await fetch(`${P}${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(s)});if(!o.ok){if(oe(o.status)&&isetTimeout(A,200*i));continue}let f=await o.text().catch(()=>"");throw new Error(`TronGrid ${r} failed (${o.status}): ${f}`)}return await o.json()}catch(o){if(a=o,isetTimeout(f,200*i));continue}}throw a}var N=class{tronWeb;tronLink;address=null;hexAddress=null;emitter=new B;walletRequest;constructor(e){this.walletRequest=e,this.tronWeb=this.buildTronWeb(),this.tronLink=this.buildTronLink()}setAddress(e){!e||!D(e)||(this.address=e,this.hexAddress=W(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 s=await K(this.walletRequest,"tron_requestAccounts",[]);return!s||typeof s!="string"?{code:4001,message:"User denied account access"}:(this.setAddress(s),{code:200,message:"ok"})}default:return K(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(i,o,f,A)=>{if(typeof i=="string")throw new Error("tronWeb.trx.sign(message) not supported \u2014 use tronWeb.trx.signMessage()");if(!i||!i.raw_data_hex)throw new Error("tronWeb.trx.sign expects a built transaction with raw_data_hex");return await K(e.walletRequest,"tron_sign",[i])},signMessage:async(i,o)=>await K(e.walletRequest,"tron_signMessage",[i]),signMessageV2:async(i,o)=>await K(e.walletRequest,"signMessageV2",[i]),sendRawTransaction:async i=>M("/wallet/broadcasttransaction",i),broadcast:async i=>t.sendRawTransaction(i),getBalance:async i=>{let o=i||e.address;if(!o)throw new Error("No address \u2014 call tron_requestAccounts first");let f=await M("/wallet/getaccount",{address:o,visible:!0});return typeof(f==null?void 0:f.balance)=="number"?f.balance:0},getAccount:async i=>{let o=i||e.address;if(!o)throw new Error("No address \u2014 call tron_requestAccounts first");return M("/wallet/getaccount",{address:o,visible:!0})},getUnconfirmedAccount:async i=>{let o=i||e.address;if(!o)throw new Error("No address \u2014 call tron_requestAccounts first");return M("/wallet/getaccount",{address:o,visible:!0})},getTransaction:async i=>M("/wallet/gettransactionbyid",{value:i})},a={isTronLink:!0,ready:!1,defaultAddress:{base58:!1,hex:!1,name:!1,type:-1},fullNode:{host:P},solidityNode:{host:P},eventServer:{host:P},trx:t,transactionBuilder:{sendTrx:async(i,o,f)=>{let A=f||e.address;if(!A)throw new Error("No address \u2014 call tron_requestAccounts first");return M("/wallet/createtransaction",{owner_address:A,to_address:i,amount:o,visible:!0})},triggerSmartContract:async(i,o,f={},A=[],y)=>{let w=y||e.address;if(!w)throw new Error("No address \u2014 call tron_requestAccounts first");return M("/wallet/triggersmartcontract",{contract_address:i,function_selector:o,parameter:ce(A),fee_limit:f.feeLimit??1e8,call_value:f.callValue??0,owner_address:w,visible:!0})}},utils:{isAddress:i=>D(i),fromSun:i=>String(Number(i)/1e6),toSun:i=>String(Math.round(Number(i)*1e6)),toHex:i=>W(i)},on:(i,o)=>this.emitter.on(i,o),off:(i,o)=>this.emitter.off(i,o),setAddress:i=>{},isConnected:()=>this.address!==null};return queueMicrotask(()=>{this.tronLink&&(this.tronLink.tronWeb=a)}),a}};function ce(r){if(!Array.isArray(r)||r.length===0)return"";let e="";for(let t of r)if(t.type==="address"){let s=String(t.value),n=s.startsWith("T")?W(s).slice(2):s.replace(/^0x/,"").replace(/^41/,"");e+=n.padStart(64,"0")}else if(t.type==="uint256"||t.type==="uint"){let s=BigInt(t.value);e+=s.toString(16).padStart(64,"0")}else{let s=String(t.value).replace(/^0x/,"");e+=s.padStart(64,"0")}return e}function G(r){let e=1;function t(o,f,A,y,w){if(typeof o!="function")return;let v=y?(y==null?void 0:y.message)||String(y):null;o({success:!y,error:y?(y==null?void 0:y.code)===4001?"user_cancel":v:null,message:v,request_id:f,result:y?null:w??null,data:A})}function s(o,f,A){let y=e++;r(o,[f],"hive",(w,v)=>{t(A,y,f,w,v)})}function n(o,f,A){t(A,e++,f,{message:`KeepKey does not support Keychain request "${o}" yet`})}let a={current_id:1,requestHandshake:function(o){s("hive_handshake",{},()=>{typeof o=="function"&&o()})},requestTransfer:function(o,f,A,y,w,v,C=!1,b){s("hive_transfer",{type:"transfer",username:o,to:f,amount:A,memo:y,enforce:C,currency:w,rpc:b},v)}},i=["requestEncodeMessage","requestEncodeWithKeys","requestVerifyKey","requestSignBuffer","requestAddAccountAuthority","requestRemoveAccountAuthority","requestAddKeyAuthority","requestRemoveKeyAuthority","requestBroadcast","requestSignTx","requestSignedCall","requestPost","requestVote","requestCustomJson","requestSendToken","requestDelegation","requestWitnessVote","requestProxy","requestPowerUp","requestPowerDown","requestCreateClaimedAccount","requestCreateProposal","requestRemoveProposal","requestUpdateProposalVote","requestAddAccount","requestConversion","requestRecurrentTransfer","requestSavingsOperation","requestSwap"];for(let o of i)a[o]=(...f)=>{let A=f.find(y=>typeof y=="function");n(o.replace(/^request/,""),{type:o},A)};return a}(function(){let r="2.1.0",a=window,i={isInjected:!1,version:r,injectedAt:Date.now(),retryCount:0};if(a.keepkeyInjectionState&&a.keepkeyInjectionState.version>=r)return;a.keepkeyInjectionState=i;let o=(()=>{var l;let g={enableMetaMaskMasking:!1,enableXfiMasking:!1,enableKeplrMasking:!1,enablePhantomMasking:!1};try{let d=document.currentScript,c=document.getElementById("keepkey-injected-script"),u=(l=d==null?void 0:d.dataset)!=null&&l.masking?d:c,m=u==null?void 0:u.dataset.masking;if(!m)return g;let h=JSON.parse(m);return{enableMetaMaskMasking:h.enableMetaMaskMasking===!0,enableXfiMasking:h.enableXfiMasking===!0,enableKeplrMasking:h.enableKeplrMasking===!0,enablePhantomMasking:h.enablePhantomMasking===!0}}catch{return g}})();console.log(`[KeepKey] masking: metamask=${o.enableMetaMaskMasking?"on":"off"} xfi=${o.enableXfiMasking?"on":"off"} keplr=${o.enableKeplrMasking?"on":"off"} phantom=${o.enablePhantomMasking?"on":"off"}`);let f={siteUrl:window.location.href,scriptSource:"KeepKey Extension",version:r,injectedTime:new Date().toISOString(),origin:window.location.origin,protocol:window.location.protocol},A=0,y=new Map,w=[],v=!1;setInterval(()=>{let g=Date.now();y.forEach((l,d)=>{g-l.timestamp>3e5&&(l.callback(new Error("Request timeout")),y.delete(d))})},5e3);let b=g=>{w.length>=100&&w.shift(),w.push(g)},S=()=>{if(v)for(;w.length>0;){let g=w.shift();g&&window.postMessage(g,window.location.origin)}},R=(g=0)=>new Promise(l=>{let d=++A,c=setTimeout(()=>{g<3?setTimeout(()=>{R(g+1).then(l)},100*Math.pow(2,g)):(i.lastError="Failed to verify injection",l(!1))},1e3),u=m=>{var h,k,T;m.source===window&&((h=m.data)==null?void 0:h.source)==="keepkey-content"&&((k=m.data)==null?void 0:k.type)==="INJECTION_CONFIRMED"&&((T=m.data)==null?void 0:T.requestId)===d&&(clearTimeout(c),window.removeEventListener("message",u),v=!0,i.isInjected=!0,S(),l(!0))};window.addEventListener("message",u),window.postMessage({source:"keepkey-injected",type:"INJECTION_VERIFY",requestId:d,version:r,timestamp:Date.now()},window.location.origin)});function E(g,l=[],d,c){if(!g||typeof g!="string"){c(new Error("Invalid method"));return}Array.isArray(l)||(l=[l]);try{let u=++A,m={id:u,method:g,params:l,chain:d,siteUrl:f.siteUrl,scriptSource:f.scriptSource,version:f.version,requestTime:new Date().toISOString(),referrer:document.referrer,href:window.location.href,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language};y.set(u,{callback:c,timestamp:Date.now(),method:g});let h={source:"keepkey-injected",type:"WALLET_REQUEST",requestId:u,requestInfo:m,timestamp:Date.now()};v?window.postMessage(h,window.location.origin):b(h)}catch(u){c(u)}}window.addEventListener("message",g=>{if(g.source!==window)return;let l=g.data;if(!(!l||typeof l!="object")){if(l.source==="keepkey-content"&&l.type==="INJECTION_CONFIRMED"){v=!0,S();return}if(l.source==="keepkey-content"&&l.type==="WALLET_RESPONSE"&&l.requestId){let d=y.get(l.requestId);d&&(l.error?d.callback(l.error):d.callback(null,l.result),y.delete(l.requestId))}}});class H{events=new Map;on(l,d){this.events.has(l)||this.events.set(l,new Set),this.events.get(l).add(d)}off(l,d){var c;(c=this.events.get(l))==null||c.delete(d)}removeListener(l,d){this.off(l,d)}removeAllListeners(l){l?this.events.delete(l):this.events.clear()}emit(l,...d){var c;(c=this.events.get(l))==null||c.forEach(u=>{try{u(...d)}catch{}})}once(l,d){let c=(...u)=>{d(...u),this.off(l,c)};this.on(l,c)}}function p(g){let l=new H,d={network:"mainnet",isKeepKey:!0,isMetaMask:o.enableMetaMaskMasking,isConnected:()=>v,request:({method:c,params:u=[]})=>new Promise((m,h)=>{E(c,u,g,(k,T)=>{if(k)console.log(`[HANDOFF] dApp \u2190 KeepKey (${g}/${c}) REJECT +URI: ${f}`,k+=` +Version: ${h}`,k+=` +Chain ID: ${y}`,k+=` +Nonce: ${p}`,k+=` +Issued At: ${b}`,n!=null&&n.expirationTime&&(k+=` +Expiration Time: ${n.expirationTime}`),n!=null&&n.notBefore&&(k+=` +Not Before: ${n.notBefore}`),n!=null&&n.requestId&&(k+=` +Request ID: ${n.requestId}`),(s=n==null?void 0:n.resources)!=null&&s.length){k+=` +Resources:`;for(let E of n.resources)k+=` +- ${E}`}let C=new TextEncoder().encode(k),R=await this.#s("solana_signMessage",[Array.from(C),{accountAddress:r.address}]);t.push({account:r,signedMessage:C,signature:new Uint8Array(R)})}return t}}};constructor(e){this.#r=e;try{let t=localStorage.getItem("keepkey-solana");if(t){let{address:s}=JSON.parse(t);s&&typeof s=="string"&&(this.#t=s)}}catch{}this.#l()}#i(e){return{address:e,publicKey:L(e),chains:["solana:mainnet"],features:[...a.ACCOUNT_FEATURES]}}#c(e){this.#e=[this.#i(e)];try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}this.#n()}#o(e){var t;this.#e=e.filter(s=>s==null?void 0:s.address).map(s=>this.#i(s.address));try{let s=(t=this.#e[0])==null?void 0:t.address;s&&localStorage.setItem("keepkey-solana",JSON.stringify({address:s}))}catch{}this.#n()}async#l(){try{let e=await this.#s("solana_connect",[]);if(e&&typeof e=="string"){this.#t=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}}}catch{}}#n(){let e=this.#e,t=this.features;this.#a.forEach(s=>{try{s({accounts:e,features:t})}catch{}})}#s(e,t){return new Promise((s,n)=>{this.#r(e,t,"solana",(r,o)=>{r?n(r):s(o)})})}};function x(a){let e=({register:s})=>{s(a)};try{let s=window.navigator;s.wallets||(s.wallets=[]),Array.isArray(s.wallets)?s.wallets.push(e):typeof s.wallets.register=="function"&&s.wallets.register(a)}catch{}let t=()=>{try{window.dispatchEvent(new CustomEvent("wallet-standard:register-wallet",{detail:e}))}catch{}};t(),setTimeout(t,100),setTimeout(t,1e3),window.addEventListener("wallet-standard:app-ready",s=>{let n=s.detail;try{n&&typeof n.register=="function"?e(n):typeof n=="function"&&n(e)}catch{}})}var Y="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function Z(a){let e=[0];for(let t of a){let s=Y.indexOf(t);if(s===-1)throw new Error("Invalid base58 character");let n=s;for(let r=0;r>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of a){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}function $(a){let e=Z(a);return{toString:()=>a,toBase58:()=>a,toBytes:()=>e,toBuffer:()=>e,equals:t=>{var s,n;try{return((s=t==null?void 0:t.toBase58)==null?void 0:s.call(t))===a||((n=t==null?void 0:t.toString)==null?void 0:n.call(t))===a}catch{return!1}}}}function j(a){return a!=null&&typeof a=="object"&&"version"in a&&a.message!=null}function F(a){if(!a||typeof a.serialize!="function")throw new Error("Unsupported transaction: missing serialize()");return j(a)?new Uint8Array(a.serialize()):new Uint8Array(a.serialize({requireAllSignatures:!1,verifySignatures:!1}))}function ee(a){let e=a[0];if(!e)throw new Error("Signed transaction has no signatures");if(e&128)throw new Error("Unexpected multi-byte signature count");return a.slice(1,65)}function te(a){var t,s;if(j(a)){let n=(t=a.message)==null?void 0:t.staticAccountKeys;if(n&&n.length)return n[0];throw new Error("Versioned transaction missing account keys")}if(a.feePayer)return a.feePayer;let e=(s=a.signatures)==null?void 0:s[0];if(e!=null&&e.publicKey)return e.publicKey;throw new Error("Legacy transaction missing fee payer")}var O=class{#r;#e=new Map;#t=null;#a=null;isPhantom=!0;isKeepKey=!0;constructor(e){this.#r=e;try{let t=localStorage.getItem("keepkey-solana");if(t){let{address:s}=JSON.parse(t);s&&typeof s=="string"&&(this.#a=s)}}catch{}this.#l()}get publicKey(){return this.#t}get isConnected(){return this.#t!==null}async connect(e){if(this.#t)return{publicKey:this.#t};let t=this.#a||await this.#n("solana_connect",[]);if(!t)throw new Error("Failed to connect to KeepKey");return this.#c(t),{publicKey:this.#t}}async disconnect(){await this.#n("solana_disconnect",[]).catch(()=>{}),this.#t=null;try{localStorage.removeItem("keepkey-solana")}catch{}this.#i("disconnect")}async signMessage(e,t){await this.#o();let s=await this.#n("solana_signMessage",[Array.from(e)]);return{signature:new Uint8Array(s),publicKey:this.#t}}async signTransaction(e){await this.#o();let t=F(e),s=await this.#n("solana_signTransaction",[Array.from(t)]),n=new Uint8Array(s),r=ee(n);return e.addSignature(te(e),r),e}async signAllTransactions(e){let t=[];for(let s of e)t.push(await this.signTransaction(s));return t}async signAndSendTransaction(e,t){await this.#o();let s=F(e);return{signature:await this.#n("solana_signAndSendTransaction",[Array.from(s)]),publicKey:this.#t}}async request({method:e,params:t}){var n,r,o,i,f,h;let s=Array.isArray(t)?t:t!=null?[t]:[];switch(e){case"connect":return this.connect(s[0]);case"disconnect":return this.disconnect();case"signTransaction":return this.signTransaction(((n=s[0])==null?void 0:n.transaction)??s[0]);case"signAllTransactions":return this.signAllTransactions(((r=s[0])==null?void 0:r.transactions)??s[0]);case"signAndSendTransaction":return this.signAndSendTransaction(((o=s[0])==null?void 0:o.transaction)??s[0],((i=s[0])==null?void 0:i.options)??s[1]);case"signMessage":return this.signMessage(((f=s[0])==null?void 0:f.message)??s[0],(h=s[0])==null?void 0:h.display);default:throw new Error(`KeepKey (Solana): unsupported method "${e}"`)}}on(e,t){return this.#e.has(e)||this.#e.set(e,new Set),this.#e.get(e).add(t),this}off(e,t){var s;return(s=this.#e.get(e))==null||s.delete(t),this}removeListener(e,t){return this.off(e,t)}removeAllListeners(e){return e?this.#e.delete(e):this.#e.clear(),this}#i(e,...t){var s;(s=this.#e.get(e))==null||s.forEach(n=>{try{n(...t)}catch{}})}#c(e){this.#t=$(e),this.#a=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}this.#i("connect",this.#t)}async#o(){this.#t||await this.connect()}async#l(){try{let e=await this.#n("solana_connect",[]);if(e&&typeof e=="string"){this.#a=e;try{localStorage.setItem("keepkey-solana",JSON.stringify({address:e}))}catch{}}}catch{}}#n(e,t){return new Promise((s,n)=>{this.#r(e,t,"solana",(r,o)=>{r?n(r):s(o)})})}};var P="https://api.trongrid.io",ne="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";function se(a){let e=[0];for(let t of a){let s=ne.indexOf(t);if(s===-1)throw new Error("Invalid base58 character");let n=s;for(let r=0;r>=8;for(;n>0;)e.push(n&255),n>>=8}for(let t of a){if(t!=="1")break;e.push(0)}return new Uint8Array(e.reverse())}function ae(a){let e="";for(let t of a)e+=t.toString(16).padStart(2,"0");return e}function W(a){let e=se(a);if(e.length!==25||e[0]!==65)throw new Error(`Invalid Tron address: ${a}`);return ae(e.slice(0,21))}function D(a){if(typeof a!="string"||a.length!==34||!a.startsWith("T"))return!1;try{return W(a),!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 s;(s=this.events.get(e))==null||s.delete(t)}emit(e,...t){var s;(s=this.events.get(e))==null||s.forEach(n=>{try{n(...t)}catch{}})}};function K(a,e,t){return new Promise((s,n)=>{a(e,t,"tron",(r,o)=>{r?n(r):s(o)})})}var re=8e3,ie=12e3,oe=a=>a>=500&&a<600;async function S(a,e){let s=a.includes("broadcasttransaction")?ie:re,n=2,r;for(let o=1;o<=n;o++)try{let i=await fetch(`${P}${a}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),signal:AbortSignal.timeout(s)});if(!i.ok){if(oe(i.status)&&osetTimeout(h,200*o));continue}let f=await i.text().catch(()=>"");throw new Error(`TronGrid ${a} failed (${i.status}): ${f}`)}return await i.json()}catch(i){if(r=i,osetTimeout(f,200*o));continue}}throw r}var B=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||!D(e)||(this.address=e,this.hexAddress=W(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 s=await K(this.walletRequest,"tron_requestAccounts",[]);return!s||typeof s!="string"?{code:4001,message:"User denied account access"}:(this.setAddress(s),{code:200,message:"ok"})}default:return K(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(o,i,f,h)=>{if(typeof o=="string")throw new Error("tronWeb.trx.sign(message) not supported \u2014 use tronWeb.trx.signMessage()");if(!o||!o.raw_data_hex)throw new Error("tronWeb.trx.sign expects a built transaction with raw_data_hex");return await K(e.walletRequest,"tron_sign",[o])},signMessage:async(o,i)=>await K(e.walletRequest,"tron_signMessage",[o]),signMessageV2:async(o,i)=>await K(e.walletRequest,"signMessageV2",[o]),sendRawTransaction:async o=>S("/wallet/broadcasttransaction",o),broadcast:async o=>t.sendRawTransaction(o),getBalance:async o=>{let i=o||e.address;if(!i)throw new Error("No address \u2014 call tron_requestAccounts first");let f=await S("/wallet/getaccount",{address:i,visible:!0});return typeof(f==null?void 0:f.balance)=="number"?f.balance:0},getAccount:async o=>{let i=o||e.address;if(!i)throw new Error("No address \u2014 call tron_requestAccounts first");return S("/wallet/getaccount",{address:i,visible:!0})},getUnconfirmedAccount:async o=>{let i=o||e.address;if(!i)throw new Error("No address \u2014 call tron_requestAccounts first");return S("/wallet/getaccount",{address:i,visible:!0})},getTransaction:async o=>S("/wallet/gettransactionbyid",{value:o})},r={isTronLink:!0,ready:!1,defaultAddress:{base58:!1,hex:!1,name:!1,type:-1},fullNode:{host:P},solidityNode:{host:P},eventServer:{host:P},trx:t,transactionBuilder:{sendTrx:async(o,i,f)=>{let h=f||e.address;if(!h)throw new Error("No address \u2014 call tron_requestAccounts first");return S("/wallet/createtransaction",{owner_address:h,to_address:o,amount:i,visible:!0})},triggerSmartContract:async(o,i,f={},h=[],y)=>{let p=y||e.address;if(!p)throw new Error("No address \u2014 call tron_requestAccounts first");return S("/wallet/triggersmartcontract",{contract_address:o,function_selector:i,parameter:ce(h),fee_limit:f.feeLimit??1e8,call_value:f.callValue??0,owner_address:p,visible:!0})}},utils:{isAddress:o=>D(o),fromSun:o=>String(Number(o)/1e6),toSun:o=>String(Math.round(Number(o)*1e6)),toHex:o=>W(o)},on:(o,i)=>this.emitter.on(o,i),off:(o,i)=>this.emitter.off(o,i),setAddress:o=>{},isConnected:()=>this.address!==null};return queueMicrotask(()=>{this.tronLink&&(this.tronLink.tronWeb=r)}),r}};function ce(a){if(!Array.isArray(a)||a.length===0)return"";let e="";for(let t of a)if(t.type==="address"){let s=String(t.value),n=s.startsWith("T")?W(s).slice(2):s.replace(/^0x/,"").replace(/^41/,"");e+=n.padStart(64,"0")}else if(t.type==="uint256"||t.type==="uint"){let s=BigInt(t.value);e+=s.toString(16).padStart(64,"0")}else{let s=String(t.value).replace(/^0x/,"");e+=s.padStart(64,"0")}return e}function G(a){let e=1;function t(i,f,h,y,p){if(typeof i!="function")return;let b=y?(y==null?void 0:y.message)||String(y):null,M={success:!y,error:y?(y==null?void 0:y.code)===4001?"user_cancel":b:null,message:b,request_id:f,result:y?null:p??null,data:h};if(!y&&p&&typeof p=="object"&&"result"in p){i({...M,...p});return}i(M)}function s(i,f,h){let y=e++;a(i,[f],"hive",(p,b)=>{t(h,y,f,p,b)})}function n(i,f,h){t(h,e++,f,{message:`KeepKey does not support Keychain request "${i}" yet`})}let r={current_id:1,requestHandshake:function(i){s("hive_handshake",{},()=>{typeof i=="function"&&i()})},requestTransfer:function(i,f,h,y,p,b,M=!1,k){s("hive_transfer",{type:"transfer",username:i,to:f,amount:h,memo:y,enforce:M,currency:p,rpc:k},b)},requestSignBuffer:function(i,f,h,y,p,b){s("hive_signBuffer",{type:"signBuffer",username:i,message:f,method:h,rpc:p,title:b},y)}},o=["requestEncodeMessage","requestEncodeWithKeys","requestVerifyKey","requestAddAccountAuthority","requestRemoveAccountAuthority","requestAddKeyAuthority","requestRemoveKeyAuthority","requestBroadcast","requestSignTx","requestSignedCall","requestPost","requestVote","requestCustomJson","requestSendToken","requestDelegation","requestWitnessVote","requestProxy","requestPowerUp","requestPowerDown","requestCreateClaimedAccount","requestCreateProposal","requestRemoveProposal","requestUpdateProposalVote","requestAddAccount","requestConversion","requestRecurrentTransfer","requestSavingsOperation","requestSwap"];for(let i of o)r[i]=(...f)=>{let h=f.find(y=>typeof y=="function");n(i.replace(/^request/,""),{type:i},h)};return r}(function(){let a="2.1.0",r=window,o={isInjected:!1,version:a,injectedAt:Date.now(),retryCount:0};if(r.keepkeyInjectionState&&r.keepkeyInjectionState.version>=a)return;r.keepkeyInjectionState=o;let i=(()=>{var l;let g={enableMetaMaskMasking:!1,enableXfiMasking:!1,enableKeplrMasking:!1,enablePhantomMasking:!1};try{let d=document.currentScript,c=document.getElementById("keepkey-injected-script"),u=(l=d==null?void 0:d.dataset)!=null&&l.masking?d:c,m=u==null?void 0:u.dataset.masking;if(!m)return g;let A=JSON.parse(m);return{enableMetaMaskMasking:A.enableMetaMaskMasking===!0,enableXfiMasking:A.enableXfiMasking===!0,enableKeplrMasking:A.enableKeplrMasking===!0,enablePhantomMasking:A.enablePhantomMasking===!0}}catch{return g}})();console.log(`[KeepKey] masking: metamask=${i.enableMetaMaskMasking?"on":"off"} xfi=${i.enableXfiMasking?"on":"off"} keplr=${i.enableKeplrMasking?"on":"off"} phantom=${i.enablePhantomMasking?"on":"off"}`);let f={siteUrl:window.location.href,scriptSource:"KeepKey Extension",version:a,injectedTime:new Date().toISOString(),origin:window.location.origin,protocol:window.location.protocol},h=0,y=new Map,p=[],b=!1;setInterval(()=>{let g=Date.now();y.forEach((l,d)=>{g-l.timestamp>3e5&&(l.callback(new Error("Request timeout")),y.delete(d))})},5e3);let k=g=>{p.length>=100&&p.shift(),p.push(g)},C=()=>{if(b)for(;p.length>0;){let g=p.shift();g&&window.postMessage(g,window.location.origin)}},R=(g=0)=>new Promise(l=>{let d=++h,c=setTimeout(()=>{g<3?setTimeout(()=>{R(g+1).then(l)},100*Math.pow(2,g)):(o.lastError="Failed to verify injection",l(!1))},1e3),u=m=>{var A,v,T;m.source===window&&((A=m.data)==null?void 0:A.source)==="keepkey-content"&&((v=m.data)==null?void 0:v.type)==="INJECTION_CONFIRMED"&&((T=m.data)==null?void 0:T.requestId)===d&&(clearTimeout(c),window.removeEventListener("message",u),b=!0,o.isInjected=!0,C(),l(!0))};window.addEventListener("message",u),window.postMessage({source:"keepkey-injected",type:"INJECTION_VERIFY",requestId:d,version:a,timestamp:Date.now()},window.location.origin)});function E(g,l=[],d,c){if(!g||typeof g!="string"){c(new Error("Invalid method"));return}Array.isArray(l)||(l=[l]);try{let u=++h,m={id:u,method:g,params:l,chain:d,siteUrl:f.siteUrl,scriptSource:f.scriptSource,version:f.version,requestTime:new Date().toISOString(),referrer:document.referrer,href:window.location.href,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language};y.set(u,{callback:c,timestamp:Date.now(),method:g});let A={source:"keepkey-injected",type:"WALLET_REQUEST",requestId:u,requestInfo:m,timestamp:Date.now()};b?window.postMessage(A,window.location.origin):k(A)}catch(u){c(u)}}window.addEventListener("message",g=>{if(g.source!==window)return;let l=g.data;if(!(!l||typeof l!="object")){if(l.source==="keepkey-content"&&l.type==="INJECTION_CONFIRMED"){b=!0,C();return}if(l.source==="keepkey-content"&&l.type==="WALLET_RESPONSE"&&l.requestId){let d=y.get(l.requestId);d&&(l.error?d.callback(l.error):d.callback(null,l.result),y.delete(l.requestId))}}});class H{events=new Map;on(l,d){this.events.has(l)||this.events.set(l,new Set),this.events.get(l).add(d)}off(l,d){var c;(c=this.events.get(l))==null||c.delete(d)}removeListener(l,d){this.off(l,d)}removeAllListeners(l){l?this.events.delete(l):this.events.clear()}emit(l,...d){var c;(c=this.events.get(l))==null||c.forEach(u=>{try{u(...d)}catch{}})}once(l,d){let c=(...u)=>{d(...u),this.off(l,c)};this.on(l,c)}}function w(g){let l=new H,d={network:"mainnet",isKeepKey:!0,isMetaMask:i.enableMetaMaskMasking,isConnected:()=>b,request:({method:c,params:u=[]})=>new Promise((m,A)=>{E(c,u,g,(v,T)=>{if(v)console.log(`[HANDOFF] dApp \u2190 KeepKey (${g}/${c}) REJECT params=${JSON.stringify(u)} - error=`,k),h(k);else{let _=typeof T,X=_==="string"?`len=${T.length} value=${T}`:`value=${JSON.stringify(T)}`;console.log(`[HANDOFF] dApp \u2190 KeepKey (${g}/${c}) RESOLVE + error=`,v),A(v);else{let _=typeof T,X=_==="string"?`len=${T.length} value=${T}`:`value=${JSON.stringify(T)}`;console.log(`[HANDOFF] dApp \u2190 KeepKey (${g}/${c}) RESOLVE params=${JSON.stringify(u)} - type=${_} ${X}`),m(T)}})}),send:(c,u,m)=>{if(c.chain||(c.chain=g),typeof m=="function"){E(c.method,c.params||u,g,(h,k)=>{h?m(h):m(null,{id:c.id,jsonrpc:"2.0",result:k})});return}else return{id:c.id,jsonrpc:"2.0",result:null}},sendAsync:(c,u,m)=>{c.chain||(c.chain=g);let h=m||u;typeof h=="function"&&E(c.method,c.params||u,g,(k,T)=>{k?h(k):h(null,{id:c.id,jsonrpc:"2.0",result:T})})},on:(c,u)=>(l.on(c,u),d),off:(c,u)=>(l.off(c,u),d),removeListener:(c,u)=>(l.removeListener(c,u),d),removeAllListeners:c=>(l.removeAllListeners(c),d),emit:(c,...u)=>(l.emit(c,...u),d),once:(c,u)=>(l.once(c,u),d),enable:()=>d.request({method:"eth_requestAccounts"}),_metamask:{isUnlocked:()=>Promise.resolve(!0)}};return g==="ethereum"&&(d.chainId="0x1",d.networkVersion="1",d.selectedAddress=null,d._handleAccountsChanged=c=>{d.selectedAddress=c[0]||null,l.emit("accountsChanged",c)},d._handleChainChanged=c=>{d.chainId=c,l.emit("chainChanged",c)},d._handleConnect=c=>{l.emit("connect",c)},d._handleDisconnect=c=>{d.selectedAddress=null,l.emit("disconnect",c)}),d}let z="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==",J="data:image/svg+xml;utf8,"+encodeURIComponent('MM');function q(g){let l={uuid:"350670db-19fa-4704-a166-e52e178b59d4",name:"KeepKey",icon:z,rdns:"com.keepkey.client"};if(window.dispatchEvent(new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:l,provider:g})})),o.enableMetaMaskMasking){let d={uuid:"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",name:"MetaMask",icon:J,rdns:"io.metamask"};window.dispatchEvent(new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:d,provider:g})}))}}async function Q(){let g=p("ethereum"),l={binance:p("binance"),bitcoin:p("bitcoin"),bitcoincash:p("bitcoincash"),dogecoin:p("dogecoin"),dash:p("dash"),ethereum:g,keplr:p("keplr"),litecoin:p("litecoin"),thorchain:p("thorchain"),mayachain:p("mayachain")},d={binance:p("binance"),bitcoin:p("bitcoin"),bitcoincash:p("bitcoincash"),dogecoin:p("dogecoin"),dash:p("dash"),ethereum:g,osmosis:p("osmosis"),cosmos:p("cosmos"),litecoin:p("litecoin"),thorchain:p("thorchain"),mayachain:p("mayachain"),ripple:p("ripple")},c=(u,m,{force:h=!1}={})=>{if(!(a[u]&&!h))try{Object.defineProperty(a,u,{value:m,writable:!1,configurable:!0})}catch{i.lastError=`Failed to mount ${u}`}};o.enableMetaMaskMasking&&c("ethereum",g,{force:!0}),o.enableXfiMasking&&c("xfi",l,{force:!0}),c("keepkey",d,{force:!0}),window.addEventListener("eip6963:requestProvider",()=>{q(g)}),q(g),setTimeout(()=>{q(g)},100);try{let u=new U(E);x(u)}catch{}if(o.enablePhantomMasking)try{let u=new O(E);Object.defineProperty(a,"solana",{value:u,writable:!1,configurable:!0})}catch{}try{let u=new N(E);a.tronLink||Object.defineProperty(a,"tronLink",{value:u.tronLink,writable:!1,configurable:!0}),a.tronWeb||Object.defineProperty(a,"tronWeb",{value:u.tronWeb,writable:!1,configurable:!0})}catch{}try{a.hive_keychain||(a.hive_keychain=G(E))}catch{}window.addEventListener("message",u=>{var m,h,k;((m=u.data)==null?void 0:m.type)==="CHAIN_CHANGED"&&g.emit("chainChanged",(h=u.data.provider)==null?void 0:h.chainId),((k=u.data)==null?void 0:k.type)==="ACCOUNTS_CHANGED"&&g._handleAccountsChanged&&g._handleAccountsChanged(u.data.accounts||[])}),R().then(u=>{u||(i.lastError="Injection not verified")})}Q(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{if(a.ethereum&&typeof a.dispatchEvent=="function"){let g=a.ethereum;q(g)}})})();})(); + type=${_} ${X}`),m(T)}})}),send:(c,u,m)=>{if(c.chain||(c.chain=g),typeof m=="function"){E(c.method,c.params||u,g,(A,v)=>{A?m(A):m(null,{id:c.id,jsonrpc:"2.0",result:v})});return}else return{id:c.id,jsonrpc:"2.0",result:null}},sendAsync:(c,u,m)=>{c.chain||(c.chain=g);let A=m||u;typeof A=="function"&&E(c.method,c.params||u,g,(v,T)=>{v?A(v):A(null,{id:c.id,jsonrpc:"2.0",result:T})})},on:(c,u)=>(l.on(c,u),d),off:(c,u)=>(l.off(c,u),d),removeListener:(c,u)=>(l.removeListener(c,u),d),removeAllListeners:c=>(l.removeAllListeners(c),d),emit:(c,...u)=>(l.emit(c,...u),d),once:(c,u)=>(l.once(c,u),d),enable:()=>d.request({method:"eth_requestAccounts"}),_metamask:{isUnlocked:()=>Promise.resolve(!0)}};return g==="ethereum"&&(d.chainId="0x1",d.networkVersion="1",d.selectedAddress=null,d._handleAccountsChanged=c=>{d.selectedAddress=c[0]||null,l.emit("accountsChanged",c)},d._handleChainChanged=c=>{d.chainId=c,l.emit("chainChanged",c)},d._handleConnect=c=>{l.emit("connect",c)},d._handleDisconnect=c=>{d.selectedAddress=null,l.emit("disconnect",c)}),d}let z="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==",J="data:image/svg+xml;utf8,"+encodeURIComponent('MM');function q(g){let l={uuid:"350670db-19fa-4704-a166-e52e178b59d4",name:"KeepKey",icon:z,rdns:"com.keepkey.client"};if(window.dispatchEvent(new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:l,provider:g})})),i.enableMetaMaskMasking){let d={uuid:"9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",name:"MetaMask",icon:J,rdns:"io.metamask"};window.dispatchEvent(new CustomEvent("eip6963:announceProvider",{detail:Object.freeze({info:d,provider:g})}))}}async function Q(){let g=w("ethereum"),l={binance:w("binance"),bitcoin:w("bitcoin"),bitcoincash:w("bitcoincash"),dogecoin:w("dogecoin"),dash:w("dash"),ethereum:g,keplr:w("keplr"),litecoin:w("litecoin"),thorchain:w("thorchain"),mayachain:w("mayachain")},d={binance:w("binance"),bitcoin:w("bitcoin"),bitcoincash:w("bitcoincash"),dogecoin:w("dogecoin"),dash:w("dash"),ethereum:g,osmosis:w("osmosis"),cosmos:w("cosmos"),litecoin:w("litecoin"),thorchain:w("thorchain"),mayachain:w("mayachain"),ripple:w("ripple")},c=(u,m,{force:A=!1}={})=>{if(!(r[u]&&!A))try{Object.defineProperty(r,u,{value:m,writable:!1,configurable:!0})}catch{o.lastError=`Failed to mount ${u}`}};i.enableMetaMaskMasking&&c("ethereum",g,{force:!0}),i.enableXfiMasking&&c("xfi",l,{force:!0}),c("keepkey",d,{force:!0}),window.addEventListener("eip6963:requestProvider",()=>{q(g)}),q(g),setTimeout(()=>{q(g)},100);try{let u=new U(E);x(u)}catch{}if(i.enablePhantomMasking)try{let u=new O(E);Object.defineProperty(r,"solana",{value:u,writable:!1,configurable:!0})}catch{}try{let u=new B(E);r.tronLink||Object.defineProperty(r,"tronLink",{value:u.tronLink,writable:!1,configurable:!0}),r.tronWeb||Object.defineProperty(r,"tronWeb",{value:u.tronWeb,writable:!1,configurable:!0})}catch{}try{r.hive_keychain||(r.hive_keychain=G(E))}catch{}window.addEventListener("message",u=>{var m,A,v;((m=u.data)==null?void 0:m.type)==="CHAIN_CHANGED"&&g.emit("chainChanged",(A=u.data.provider)==null?void 0:A.chainId),((v=u.data)==null?void 0:v.type)==="ACCOUNTS_CHANGED"&&g._handleAccountsChanged&&g._handleAccountsChanged(u.data.accounts||[])}),R().then(u=>{u||(o.lastError="Injection not verified")})}Q(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{if(r.ethereum&&typeof r.dispatchEvent=="function"){let g=r.ethereum;q(g)}})})();})(); diff --git a/chrome-extension/src/background/chains/hiveHandler.ts b/chrome-extension/src/background/chains/hiveHandler.ts index 8b4b1fe..45f3e63 100644 --- a/chrome-extension/src/background/chains/hiveHandler.ts +++ b/chrome-extension/src/background/chains/hiveHandler.ts @@ -281,6 +281,281 @@ async function hiveTransfer( }; } +/** + * Keychain requestSignBuffer — dApp login. Firmware signs SHA256(message) + * with the requested role's SLIP-48 key and we return the signature hex as + * `result` with the STM pubkey beside it, exactly like Hive Keychain. + */ +async function hiveSignBuffer( + params: any[], + requestInfo: any, + requireApproval: (networkId: string, requestInfo: any, chain: any, method: string, params: any) => Promise, +) { + const { username, message, method: keyRole, title } = params[0] || {}; + if (!message || typeof message !== 'string') { + throw createProviderRpcError(-32602, 'signBuffer requires a message'); + } + const role = String(keyRole || '').toLowerCase(); + // Owner is deliberately excluded (matches firmware — owner' is not a + // signBuffer role; it exists for recovery/authority changes only). + if (!['posting', 'active', 'memo'].includes(role)) { + throw createProviderRpcError(-32602, `signBuffer key must be Posting, Active or Memo (got ${keyRole})`); + } + + // Keychain contract: a message that JSON-parses to a serialized Node + // Buffer ({type:'Buffer',data:[...]}) is signed as those raw bytes; + // anything else is signed as the UTF-8 string. + let payload = message; + let isText = true; + try { + const o = JSON.parse(message, (_k, v) => + v !== null && typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data) ? Uint8Array.from(v.data) : v, + ); + if (o instanceof Uint8Array) { + payload = Array.from(o) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); + isText = false; + } + } catch { + /* not JSON — sign the raw string */ + } + + await requireHiveFirmware('Hive message signing'); + const from = await getHiveAccount(); + if (username && username !== from.name) { + throw createProviderRpcError(4100, `This KeepKey controls @${from.name}, not @${username}`); + } + + const event = buildEvent(requestInfo, 'signBuffer', params); + (event as any).unsignedTx = { + from: from.name, + messageUtf8: isText ? payload : undefined, + message: isText ? undefined : payload, + role, + title: title || '', + }; + await requestUserApproval(event, requestInfo, 'signBuffer', params, requireApproval); + + let resp: Response; + try { + resp = await fetch(`${VAULT_URL}/hive/sign-message`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getApiKey()}` }, + body: JSON.stringify({ + address_n: hiveAddressN(role as keyof typeof HIVE_ROLES, 0), + message: payload, + is_text: isText, + }), + signal: AbortSignal.timeout(300_000), + }); + } catch (e: any) { + if (e.name === 'TimeoutError' || e.name === 'AbortError') throw createTimeoutError('Vault signing timed out'); + throw createProviderRpcError(-32603, `Vault connection failed: ${e.message}`); + } + if (!resp.ok) { + const text = await resp.text().catch(() => ''); + throw createProviderRpcError(-32603, `Vault Hive sign-message failed (${resp.status}): ${text}`); + } + const { signature, public_key } = await resp.json(); + if (!signature) throw createProviderRpcError(-32603, 'Vault returned no Hive message signature'); + + chrome.runtime.sendMessage({ action: 'signature_complete', eventId: requestInfo.id }).catch(() => {}); + + // Keychain shape: result = signature hex, publicKey beside it. + return { result: signature, publicKey: public_key }; +} + +// Firmware clear-sign op table — phase 1 + phase 2 +// (handoff-hive-sign-operations-phase2.md). The vault serializer and the +// firmware both re-enforce this; the check here just fails fast with a +// clear dApp-facing error. +const SUPPORTED_OPS = new Set([ + 'vote', + 'comment', + 'custom_json', + 'transfer_to_vesting', + 'withdraw_vesting', + 'convert', + 'comment_options', + 'transfer_to_savings', + 'transfer_from_savings', + 'claim_reward_balance', + 'delegate_vesting_shares', + 'account_update2', +]); + +/** Strict "x.xxx" normalization — same no-parseFloat rule as hiveTransfer. */ +function normalizeAmount3(amount: any, what: string): string { + if (typeof amount !== 'string' || !/^\d+(\.\d{1,3})?$/.test(amount)) { + throw createProviderRpcError( + -32602, + `Invalid ${what} "${amount}" — expected a decimal string with up to 3 decimals`, + ); + } + const [whole, frac = ''] = amount.split('.'); + const base = Number(whole) * 10 ** HIVE_DECIMALS + Number(frac.padEnd(HIVE_DECIMALS, '0')); + if (!Number.isSafeInteger(base) || base <= 0) { + throw createProviderRpcError(-32602, `Invalid ${what} "${amount}" — must be greater than zero`); + } + return (base / 10 ** HIVE_DECIMALS).toFixed(HIVE_DECIMALS); +} + +/** + * HP → VESTS via the vesting pool on Pioneer /hive/tx-params (Pioneer is the + * RPC source of truth — no direct Hive-node calls, same policy as transfers). + */ +async function hpToVests(hp3: string): Promise { + const resp = await fetch(`${PIONEER_URL}/api/v1/hive/tx-params`, { + signal: AbortSignal.timeout(20_000), + }).then(r => r.json()); + if (!resp.success) throw createProviderRpcError(-32603, `Hive tx-params failed: ${resp.error || 'unknown'}`); + const totalFund = parseFloat(resp.totalVestingFundHive); + const totalShares = parseFloat(resp.totalVestingShares); + if (!isFinite(totalFund) || !isFinite(totalShares) || totalFund <= 0 || totalShares <= 0) { + throw createProviderRpcError( + -32603, + 'Pioneer /hive/tx-params has no vesting pool — HP conversion needs an updated api.keepkey.info deploy', + ); + } + return ((parseFloat(hp3) * totalShares) / totalFund).toFixed(6); +} + +// ponytail: epoch-seconds request id — unique enough for one-off +// conversions/withdrawals; per-account id tracking if a dApp ever collides. +const epochRequestId = () => Math.floor(Date.now() / 1000); + +/** One-line device-preview summary per op for the side-panel approval. */ +function opSummary(name: string, p: Record): string { + switch (name) { + case 'vote': + return `@${p.voter} → @${p.author}/${p.permlink} (${(Number(p.weight) / 100).toFixed(0)}%)`; + case 'comment': + return `@${p.author}: ${p.title || p.permlink}`; + case 'custom_json': + return `${p.id}: ${String(p.json).slice(0, 120)}`; + case 'transfer_to_vesting': + return `Power up ${p.amount} → @${p.to}`; + case 'withdraw_vesting': + return String(p.vesting_shares).startsWith('0.000000') + ? `Stop power down (@${p.account})` + : `Power down ${p.vesting_shares} from @${p.account}`; + case 'convert': + return `Convert ${p.amount} → HIVE (request ${p.requestid})`; + case 'comment_options': + return `Payout options for @${p.author}/${p.permlink}${ + (p.extensions?.[0]?.[1]?.beneficiaries ?? []) + .map((b: any) => ` · ${(Number(b.weight) / 100).toFixed(1)}% → @${b.account}`) + .join('') || '' + }`; + case 'transfer_to_savings': + return `Savings deposit ${p.amount} → @${p.to}`; + case 'transfer_from_savings': + return `Savings withdraw ${p.amount} → @${p.to}`; + case 'claim_reward_balance': + return `Claim ${p.reward_hive}, ${p.reward_hbd}, ${p.reward_vests}`; + case 'delegate_vesting_shares': + return String(p.vesting_shares).startsWith('0.000000') + ? `Remove delegation from @${p.delegatee}` + : `Delegate ${p.vesting_shares} → @${p.delegatee}`; + case 'account_update2': + return `Update profile @${p.account}`; + default: + return name; + } +} + +/** + * Shared path for vote/post/custom_json/broadcast: validate ops against the + * firmware's phase-1 clear-sign table, approve, sign via the vault + * (device parses + displays the Graphene bytes), broadcast via Pioneer + * /hive/broadcast-ops. Returns Keychain's { id, tx_id, confirmed } shape. + */ +async function hiveSignAndBroadcastOps( + displayType: string, + operations: [string, Record][], + username: string | undefined, + params: any[], + requestInfo: any, + requireApproval: (networkId: string, requestInfo: any, chain: any, method: string, params: any) => Promise, +) { + for (const op of operations) { + if (!Array.isArray(op) || op.length !== 2 || !SUPPORTED_OPS.has(op[0])) { + throw createProviderRpcError( + 4200, + `Operation not in the KeepKey clear-sign table (got ${Array.isArray(op) ? op[0] : typeof op})`, + ); + } + } + + await requireHiveFirmware('Hive operations'); + const from = await getHiveAccount(); + if (username && username !== from.name) { + throw createProviderRpcError(4100, `This KeepKey controls @${from.name}, not @${username}`); + } + + const event = buildEvent(requestInfo, displayType, params); + (event as any).unsignedTx = { + from: from.name, + operations: operations.map(([name, p]) => ({ op: name, summary: opSummary(name, p) })), + }; + await requestUserApproval(event, requestInfo, displayType, params, requireApproval); + + // Sign — the vault serializes, the device parses + clear-signs + let signResp: Response; + try { + signResp = await fetch(`${VAULT_URL}/hive/sign-operations`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getApiKey()}` }, + body: JSON.stringify({ operations }), + signal: AbortSignal.timeout(300_000), + }); + } catch (e: any) { + if (e.name === 'TimeoutError' || e.name === 'AbortError') throw createTimeoutError('Vault signing timed out'); + throw createProviderRpcError(-32603, `Vault connection failed: ${e.message}`); + } + if (!signResp.ok) { + const text = await signResp.text().catch(() => ''); + throw createProviderRpcError(-32603, `Vault Hive sign-operations failed (${signResp.status}): ${text}`); + } + const signed = await signResp.json(); + if (!signed.signature) throw createProviderRpcError(-32603, 'Vault returned no Hive signature'); + + // Broadcast the EXACT tx the device signed (header echoed by the vault) + const bResp = await fetch(`${PIONEER_URL}/api/v1/hive/broadcast-ops`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + ref_block_num: signed.ref_block_num, + ref_block_prefix: signed.ref_block_prefix, + expiration: signed.expiration, + operations: signed.operations, + signature: signed.signature.replace(/^0x/, ''), + }), + signal: AbortSignal.timeout(30_000), + }); + const bData = await bResp.json().catch(() => ({})); + if (!bData.success || !bData.txid) { + throw createProviderRpcError( + -32603, + `Hive broadcast failed: ${bData.error || `HTTP ${bResp.status} — is /hive/broadcast-ops deployed?`}`, + ); + } + + await requestStorage.updateEventById(event.id, { ...event, txid: bData.txid, status: 'broadcasted' } as any); + chrome.runtime + .sendMessage({ + action: 'transaction_complete', + eventId: requestInfo.id, + txHash: bData.txid, + explorerTxLink: 'https://hiveblocks.com/tx/', + networkId: HIVE_NETWORK_ID, + }) + .catch(() => {}); + + return { id: bData.txid, tx_id: bData.txid, confirmed: Boolean(bData.blockNum ?? bData.block_num) }; +} + export const handleHiveRequest = async ( method: string, params: any[], @@ -302,6 +577,205 @@ export const handleHiveRequest = async ( case 'hive_transfer': { return await hiveTransfer(params, requestInfo, requireApproval); } + case 'hive_signBuffer': { + return await hiveSignBuffer(params, requestInfo, requireApproval); + } + case 'hive_vote': { + const { username, permlink, author, weight } = params[0] || {}; + const from = await getHiveAccount(); + const voter = username || from.name; + return await hiveSignAndBroadcastOps( + 'vote', + [['vote', { voter, author, permlink, weight: Number(weight) }]], + username, + params, + requestInfo, + requireApproval, + ); + } + case 'hive_post': { + const p = params[0] || {}; + const from = await getHiveAccount(); + const author = p.username || from.name; + const permlink = p.permlink || `keepkey-${Date.now().toString(36)}`; + const ops: [string, Record][] = [ + [ + 'comment', + { + parent_author: p.parent_username || '', + parent_permlink: p.parent_perm, + author, + permlink, + title: p.title || '', + body: p.body, + json_metadata: + typeof p.json_metadata === 'string' ? p.json_metadata : JSON.stringify(p.json_metadata ?? {}), + }, + ], + ]; + if (p.comment_options) { + // Keychain sends comment_options as a JSON string or object. Author + + // permlink are forced to the comment's — the vault serializer rejects + // any mismatch (beneficiary-redirect protection). + let opts: any; + try { + opts = typeof p.comment_options === 'string' ? JSON.parse(p.comment_options) : p.comment_options; + } catch { + throw createProviderRpcError(-32602, 'comment_options is not valid JSON'); + } + ops.push([ + 'comment_options', + { + author, + permlink, + max_accepted_payout: opts.max_accepted_payout ?? '1000000.000 HBD', + percent_hbd: opts.percent_hbd ?? opts.percent_steem_dollars ?? 10000, + allow_votes: opts.allow_votes ?? true, + allow_curation_rewards: opts.allow_curation_rewards ?? true, + extensions: opts.extensions ?? [], + }, + ]); + } + return await hiveSignAndBroadcastOps('post', ops, p.username, params, requestInfo, requireApproval); + } + case 'hive_customJson': { + const p = params[0] || {}; + const from = await getHiveAccount(); + const account = p.username || from.name; + const active = String(p.method || 'Posting').toLowerCase() === 'active'; + const op: [string, Record] = [ + 'custom_json', + { + required_auths: active ? [account] : [], + required_posting_auths: active ? [] : [account], + id: p.id, + json: typeof p.json === 'string' ? p.json : JSON.stringify(p.json ?? {}), + }, + ]; + return await hiveSignAndBroadcastOps('custom_json', [op], p.username, params, requestInfo, requireApproval); + } + case 'hive_broadcast': { + const p = params[0] || {}; + if (!Array.isArray(p.operations) || p.operations.length === 0) { + throw createProviderRpcError(-32602, 'requestBroadcast requires an operations array'); + } + return await hiveSignAndBroadcastOps('broadcast', p.operations, p.username, params, requestInfo, requireApproval); + } + case 'hive_sendToken': { + // Hive Engine token transfer = custom_json on ssc-mainnet-hive (active key) + const p = params[0] || {}; + if (!p.to || !p.amount || !p.currency) { + throw createProviderRpcError(-32602, 'sendToken requires { to, amount, currency }'); + } + if (typeof p.amount !== 'string' || !/^\d+(\.\d+)?$/.test(p.amount)) { + throw createProviderRpcError(-32602, `Invalid token amount "${p.amount}"`); + } + const from = await getHiveAccount(); + const account = p.username || from.name; + const op: [string, Record] = [ + 'custom_json', + { + required_auths: [account], + required_posting_auths: [], + id: 'ssc-mainnet-hive', + json: JSON.stringify({ + contractName: 'tokens', + contractAction: 'transfer', + contractPayload: { symbol: p.currency, to: p.to, quantity: p.amount, memo: p.memo || '' }, + }), + }, + ]; + return await hiveSignAndBroadcastOps('sendToken', [op], p.username, params, requestInfo, requireApproval); + } + case 'hive_powerUp': { + const p = params[0] || {}; + const from = await getHiveAccount(); + const account = p.username || from.name; + const amount = normalizeAmount3(p.hive, 'power-up amount'); + const op: [string, Record] = [ + 'transfer_to_vesting', + { from: account, to: p.recipient || account, amount: `${amount} HIVE` }, + ]; + return await hiveSignAndBroadcastOps('powerUp', [op], p.username, params, requestInfo, requireApproval); + } + case 'hive_powerDown': { + const p = params[0] || {}; + const from = await getHiveAccount(); + const account = p.username || from.name; + // Keychain contract: hive_power is HP; '0.000' stops an active power-down + const stop = typeof p.hive_power === 'string' && /^0+(\.0{1,3})?$/.test(p.hive_power); + const vests = stop ? '0.000000' : await hpToVests(normalizeAmount3(p.hive_power, 'power-down amount')); + const op: [string, Record] = ['withdraw_vesting', { account, vesting_shares: `${vests} VESTS` }]; + return await hiveSignAndBroadcastOps('powerDown', [op], p.username, params, requestInfo, requireApproval); + } + case 'hive_delegation': { + const p = params[0] || {}; + if (!p.delegatee) throw createProviderRpcError(-32602, 'delegation requires a delegatee'); + const from = await getHiveAccount(); + const account = p.username || from.name; + let vests: string; + if (p.unit === 'VESTS') { + if (typeof p.amount !== 'string' || !/^\d+\.\d{6}$/.test(p.amount)) { + throw createProviderRpcError(-32602, `Invalid VESTS amount "${p.amount}" — requires exactly 6 decimals`); + } + vests = p.amount; + } else if (p.unit === 'HP') { + // '0.000' HP removes the delegation — skip the pool conversion + vests = /^0+(\.0{1,3})?$/.test(String(p.amount)) + ? '0.000000' + : await hpToVests(normalizeAmount3(p.amount, 'delegation amount')); + } else { + throw createProviderRpcError(-32602, `delegation unit must be HP or VESTS (got ${p.unit})`); + } + const op: [string, Record] = [ + 'delegate_vesting_shares', + { delegator: account, delegatee: p.delegatee, vesting_shares: `${vests} VESTS` }, + ]; + return await hiveSignAndBroadcastOps('delegation', [op], p.username, params, requestInfo, requireApproval); + } + case 'hive_savings': { + const p = params[0] || {}; + if (!['HIVE', 'HBD'].includes(p.currency)) { + throw createProviderRpcError(-32602, `savings currency must be HIVE or HBD (got ${p.currency})`); + } + if (!['deposit', 'withdraw'].includes(p.operation)) { + throw createProviderRpcError(-32602, `savings operation must be deposit or withdraw (got ${p.operation})`); + } + if (typeof p.memo === 'string' && p.memo.startsWith('#')) { + throw createProviderRpcError( + 4200, + 'Encrypted memos (starting with "#") are not supported — the memo would be broadcast as public plaintext', + ); + } + const from = await getHiveAccount(); + const account = p.username || from.name; + const to = p.to || account; + const amount = `${normalizeAmount3(p.amount, 'savings amount')} ${p.currency}`; + const op: [string, Record] = + p.operation === 'deposit' + ? ['transfer_to_savings', { from: account, to, amount, memo: p.memo || '' }] + : ['transfer_from_savings', { from: account, request_id: epochRequestId(), to, amount, memo: p.memo || '' }]; + return await hiveSignAndBroadcastOps('savings', [op], p.username, params, requestInfo, requireApproval); + } + case 'hive_conversion': { + const p = params[0] || {}; + // collaterized=true is HIVE→HBD via collateralized_convert (op 48) — + // not in the device clear-sign table. + if (p.collaterized) { + throw createProviderRpcError(4200, 'HIVE → HBD (collateralized) conversion is not supported yet'); + } + const from = await getHiveAccount(); + const account = p.username || from.name; + const op: [string, Record] = [ + 'convert', + { + owner: account, + requestid: epochRequestId(), + amount: `${normalizeAmount3(p.amount, 'conversion amount')} HBD`, + }, + ]; + return await hiveSignAndBroadcastOps('conversion', [op], p.username, params, requestInfo, requireApproval); + } default: throw createProviderRpcError(4200, `Hive method ${method} not supported`); } diff --git a/chrome-extension/src/injected/hive-provider.ts b/chrome-extension/src/injected/hive-provider.ts index 0d44e13..01f9288 100644 --- a/chrome-extension/src/injected/hive-provider.ts +++ b/chrome-extension/src/injected/hive-provider.ts @@ -4,15 +4,16 @@ * routing signing to the extension background (and ultimately the KeepKey * device via the vault REST API). Reads/broadcasts go through Pioneer. * - * Scope of this MVP (firmware 7.15.0+): - * - requestHandshake — presence check - * - requestTransfer — native HIVE transfer (device-serialized + signed) + * Scope (firmware 7.15.0+): + * - requestHandshake, requestTransfer, requestSignBuffer + * - clear-signed ops via HiveSignOperations: requestVote, requestPost + * (incl. comment_options/beneficiaries), requestCustomJson, + * requestBroadcast, requestSendToken (Hive Engine), requestDelegation, + * requestPowerUp/Down, requestSavingsOperation, requestConversion * * Out of scope (returned as keychain-style failures so dApps degrade - * gracefully): posting-key ops (vote/post/custom_json), requestSignBuffer - * (firmware has no Hive message-sign message type), requestBroadcast - * (generic ops need per-op firmware serialization), encode/decode (memo-key - * crypto is on-device only). + * gracefully): authority management, witness/proposal ops, recurrent + * transfers, swaps, encode/decode (memo-key crypto is on-device only). */ import type { ChainType } from './types'; @@ -41,14 +42,21 @@ export function createHiveKeychainShim(walletRequest: WalletRequestFn) { function respond(callback: KeychainCallback | undefined, requestId: number, data: any, error: any, result?: any) { if (typeof callback !== 'function') return; const message = error ? error?.message || String(error) : null; - callback({ + const base = { success: !error, error: error ? (error?.code === 4001 ? 'user_cancel' : message) : null, message, request_id: requestId, result: error ? null : (result ?? null), data, - }); + }; + // A handler can return { result, ...extras } to control the top-level + // response shape — Keychain puts e.g. publicKey beside result, not in it. + if (!error && result && typeof result === 'object' && 'result' in result) { + callback({ ...base, ...result }); + return; + } + callback(base); } function dispatch(method: string, data: any, callback?: KeychainCallback) { @@ -88,6 +96,164 @@ export function createHiveKeychainShim(walletRequest: WalletRequestFn) { const data = { type: 'transfer', username: account, to, amount, memo, enforce, currency, rpc }; dispatch('hive_transfer', data, callback); }, + + /** Vote on a post/comment (posting authority). */ + requestVote: function ( + account: string, + permlink: string, + author: string, + weight: number, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'vote', username: account, permlink, author, weight, rpc }; + dispatch('hive_vote', data, callback); + }, + + /** Blog post / comment (posting authority). comment_options unsupported in phase 1. */ + requestPost: function ( + account: string, + title: string, + body: string, + parent_perm: string, + parent_account: string | null, + json_metadata: any, + permlink: string, + comment_options: any, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { + type: 'post', + username: account, + title, + body, + parent_perm, + parent_username: parent_account, + json_metadata, + permlink, + comment_options, + rpc, + }; + dispatch('hive_post', data, callback); + }, + + /** custom_json broadcast (posting by default; active when key='Active'). */ + requestCustomJson: function ( + account: string | null, + id: string, + key: string, + json: string, + display_msg: string, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'custom', username: account, id, method: key || 'Posting', json, display_msg, rpc }; + dispatch('hive_customJson', data, callback); + }, + + /** Generic operations broadcast — device clear-sign op table only. */ + requestBroadcast: function ( + account: string, + operations: any[], + key: string, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'broadcast', username: account, operations, method: key, rpc }; + dispatch('hive_broadcast', data, callback); + }, + + /** Message signing (dApp login), mirrors Keychain's signature exactly. */ + requestSignBuffer: function ( + account: string, + message: string, + key: string, + callback: KeychainCallback, + rpc?: string, + title?: string, + ) { + const data = { type: 'signBuffer', username: account, message, method: key, rpc, title }; + dispatch('hive_signBuffer', data, callback); + }, + + /** Hive Engine token transfer (custom_json on ssc-mainnet-hive, active key). */ + requestSendToken: function ( + account: string, + to: string, + amount: string, + memo: string, + currency: string, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'sendToken', username: account, to, amount, memo, currency, rpc }; + dispatch('hive_sendToken', data, callback); + }, + + /** Delegate HP/VESTS (delegate_vesting_shares, active key). */ + requestDelegation: function ( + username: string | null, + delegatee: string, + amount: string, + unit: string, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'delegation', username, delegatee, amount, unit, rpc }; + dispatch('hive_delegation', data, callback); + }, + + /** Power up — stake HIVE as HP (transfer_to_vesting, active key). */ + requestPowerUp: function ( + username: string, + recipient: string, + hive: string, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'powerUp', username, recipient, hive, rpc }; + dispatch('hive_powerUp', data, callback); + }, + + /** Power down HP ('0.000' stops an active power-down) (withdraw_vesting, active key). */ + requestPowerDown: function (username: string, hive_power: string, callback: KeychainCallback, rpc?: string) { + const data = { type: 'powerDown', username, hive_power, rpc }; + dispatch('hive_powerDown', data, callback); + }, + + /** Savings deposit/withdraw (transfer_to/from_savings, active key). */ + requestSavingsOperation: function ( + username: string, + to: string, + amount: string, + currency: string, + operation: string, + memo: string | KeychainCallback, + callback?: KeychainCallback | string, + rpc?: string, + ) { + // Keychain quirk: memo is optional and position-shifted when omitted + if (typeof memo === 'function') { + rpc = callback as string | undefined; + callback = memo; + memo = ''; + } + const data = { type: 'savings', username, to, amount, currency, operation, memo: memo || '', rpc }; + dispatch('hive_savings', data, callback as KeychainCallback); + }, + + /** HBD → HIVE conversion (convert op; collateralized HIVE → HBD unsupported). */ + requestConversion: function ( + username: string, + amount: string, + collaterized: boolean, + callback: KeychainCallback, + rpc?: string, + ) { + const data = { type: 'convert', username, amount, collaterized, rpc }; + dispatch('hive_conversion', data, callback); + }, }; // The rest of Keychain's public surface (per public/hive_keychain.js). @@ -99,31 +265,20 @@ export function createHiveKeychainShim(walletRequest: WalletRequestFn) { 'requestEncodeMessage', 'requestEncodeWithKeys', 'requestVerifyKey', - 'requestSignBuffer', 'requestAddAccountAuthority', 'requestRemoveAccountAuthority', 'requestAddKeyAuthority', 'requestRemoveKeyAuthority', - 'requestBroadcast', 'requestSignTx', 'requestSignedCall', - 'requestPost', - 'requestVote', - 'requestCustomJson', - 'requestSendToken', - 'requestDelegation', 'requestWitnessVote', 'requestProxy', - 'requestPowerUp', - 'requestPowerDown', 'requestCreateClaimedAccount', 'requestCreateProposal', 'requestRemoveProposal', 'requestUpdateProposalVote', 'requestAddAccount', - 'requestConversion', 'requestRecurrentTransfer', - 'requestSavingsOperation', 'requestSwap', ]; for (const name of UNSUPPORTED) {