UNPKG

@walletconnect/universal-provider

Version:
3 lines (2 loc) 21.7 kB
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var V=require("@walletconnect/sign-client"),o=require("@walletconnect/utils"),z=require("es-toolkit/compat"),v=require("@walletconnect/jsonrpc-utils"),b=require("@walletconnect/jsonrpc-provider"),E=require("@walletconnect/jsonrpc-http-connection"),G=require("events");function N(i){return i&&i.__esModule?i:{default:i}}var M=N(E),J=N(G);const $="error",K="wss://relay.walletconnect.org",B="wc",O="universal_provider",g=`${B}@2:${O}:`,_="https://rpc.walletconnect.org/v1/",q="generic",W=`${_}bundler`,u="call_status",Y=86400,w={DEFAULT_CHAIN_CHANGED:"default_chain_changed"};function A(i,e,t){const s=o.parseChainId(i);return e.rpcMap?.[s.reference]||`${_}?chainId=${s.namespace}:${s.reference}&projectId=${t}`}function X(i){return i.includes(":")?i.split(":")[1]:i}function R(i){return i.map(e=>`${e.split(":")[0]}:${e.split(":")[1]}`)}function Q(i,e){const t=Object.keys(e.namespaces).filter(n=>n.includes(i));if(!t.length)return[];const s=[];return t.forEach(n=>{const a=e.namespaces[n].accounts;s.push(...a)}),s}function j(i){return Object.fromEntries(Object.entries(i).filter(([e,t])=>t?.chains?.length&&t?.chains?.length>0))}function f(i={},e={}){const t=j(H(i)),s=j(H(e));return z.merge(t,s)}function H(i){const e={};if(!o.isValidObject(i))return e;for(const[t,s]of Object.entries(i)){const n=o.isCaipNamespace(t)?[t]:s.chains,a=s.methods||[],r=s.events||[],p=s.rpcMap||{},c=o.parseNamespaceKey(t);e[c]={...e[c],...s,chains:o.mergeArrays(n,e[c]?.chains),methods:o.mergeArrays(a,e[c]?.methods),events:o.mergeArrays(r,e[c]?.events)},(o.isValidObject(p)||o.isValidObject(e[c]?.rpcMap||{}))&&(e[c].rpcMap={...p,...e[c]?.rpcMap})}return e}function D(i){return i.includes(":")?i.split(":")[2]:i}function U(i){const e={};for(const[t,s]of Object.entries(i)){const n=s.methods||[],a=s.events||[],r=s.accounts||[],p=o.isCaipNamespace(t)?[t]:s.chains?s.chains:R(s.accounts);e[t]={chains:p,methods:n,events:a,accounts:r}}return e}function C(i){return typeof i=="number"?i:i.includes("0x")?parseInt(i,16):(i=i.includes(":")?i.split(":")[1]:i,isNaN(Number(i))?i:Number(i))}function Z(i){try{const e=JSON.parse(i);return typeof e=="object"&&e!==null&&!Array.isArray(e)}catch{return!1}}const F={},m=i=>F[i],P=(i,e)=>{F[i]=e},T="eip155",ee=["atomic","flow-control","paymasterService","sessionKeys","auxiliaryFunds"],te=i=>i&&i.startsWith("0x")?BigInt(i).toString(10):i,I=i=>i&&i.startsWith("0x")?i:`0x${BigInt(i).toString(16)}`,x=i=>Object.keys(i).filter(e=>ee.includes(e)).reduce((e,t)=>(e[t]=se(i[t]),e),{}),se=i=>typeof i=="string"&&Z(i)?JSON.parse(i):i,ie=(i,e,t)=>{const{sessionProperties:s={},scopedProperties:n={}}=i,a={};if(!o.isValidObject(n)&&!o.isValidObject(s))return;const r=x(s);for(const p of t){const c=te(p);if(!c)continue;a[I(c)]=r;const d=n?.[`${T}:${c}`];if(d){const l=d?.[`${T}:${c}:${e}`];a[I(c)]={...a[I(c)],...x(l||d)}}}for(const[p,c]of Object.entries(a))Object.keys(c).length===0&&delete a[p];return Object.keys(a).length>0?a:void 0};let y;class S{constructor(e){this.storage=e}async getItem(e){return await this.storage.getItem(e)}async setItem(e,t){return await this.storage.setItem(e,t)}async removeItem(e){return await this.storage.removeItem(e)}static getStorage(e){return y||(y=new S(e)),y}}async function ne(i,e){const t=o.parseChainId(i.result.capabilities.caip345.caip2),s=i.result.capabilities.caip345.transactionHashes,n=await Promise.allSettled(s.map(h=>ae(t.reference,h,e))),a=n.filter(h=>h.status==="fulfilled").map(h=>h.value).filter(h=>h);n.filter(h=>h.status==="rejected").forEach(h=>console.warn("Failed to fetch transaction receipt:",h.reason));const r=!a.length||a.some(h=>!h),p=a.every(h=>h?.status==="0x1"),c=a.every(h=>h?.status==="0x0"),d=a.some(h=>h?.status==="0x0");let l;return r?l=100:p?l=200:c?l=500:d&&(l=600),{id:i.result.id,version:i.request.version,atomic:i.request.atomicRequired,chainId:i.request.chainId,capabilities:i.result.capabilities,receipts:a,status:l}}async function ae(i,e,t){return await t(parseInt(i)).request(v.formatJsonRpcRequest("eth_getTransactionReceipt",[e]))}async function re({sendCalls:i,storage:e}){const t=await e.getItem(u);await e.setItem(u,{...t,[i.result.id]:{request:i.request,result:i.result,expiry:o.calcExpiry(Y)}})}async function oe({resultId:i,storage:e}){const t=await e.getItem(u);if(t){delete t[i],await e.setItem(u,t);for(const s in t)o.isExpired(t[s].expiry)&&delete t[s];await e.setItem(u,t)}}async function ce({resultId:i,storage:e}){const t=(await e.getItem(u))?.[i];if(t&&!o.isExpired(t.expiry))return t;await oe({resultId:i,storage:e})}class he{constructor(e){this.name="eip155",this.namespace=e.namespace,this.events=m("events"),this.client=m("client"),this.httpProviders=this.createHttpProviders(),this.chainId=parseInt(this.getDefaultChain()),this.storage=S.getStorage(this.client.core.storage)}async request(e){switch(e.request.method){case"eth_requestAccounts":return this.getAccounts();case"eth_accounts":return this.getAccounts();case"wallet_switchEthereumChain":return await this.handleSwitchChain(e);case"eth_chainId":return parseInt(this.getDefaultChain());case"wallet_getCapabilities":return await this.getCapabilities(e);case"wallet_getCallsStatus":return await this.getCallStatus(e);case"wallet_sendCalls":return await this.sendCalls(e)}return this.namespace.methods.includes(e.request.method)?await this.client.request(e):this.getHttpProvider().request(e.request)}updateNamespace(e){this.namespace=Object.assign(this.namespace,e)}setDefaultChain(e,t){this.httpProviders[e]||this.setHttpProvider(parseInt(e),t);const s=this.chainId;this.chainId=parseInt(e),this.events.emit(w.DEFAULT_CHAIN_CHANGED,{currentCaipChainId:`${this.name}:${e}`,previousCaipChainId:`${this.name}:${s}`})}requestAccounts(){return this.getAccounts()}getDefaultChain(){if(this.chainId)return this.chainId.toString();if(this.namespace.defaultChain)return this.namespace.defaultChain;const e=this.namespace.chains[0];if(!e)throw new Error("ChainId not found");return e.split(":")[1]}createHttpProvider(e,t){const s=t||A(`${this.name}:${e}`,this.namespace,this.client.core.projectId);if(!s)throw new Error(`No RPC url provided for chainId: ${e}`);return new b.JsonRpcProvider(new E.HttpConnection(s,m("disableProviderPing")))}setHttpProvider(e,t){const s=this.createHttpProvider(e,t);s&&(this.httpProviders[e]=s)}createHttpProviders(){const e={};return this.namespace.chains.forEach(t=>{const s=parseInt(X(t));e[s]=this.createHttpProvider(s,this.namespace.rpcMap?.[t])}),e}getAccounts(){const e=this.namespace.accounts;return e?[...new Set(e.filter(t=>t.split(":")[1]===this.chainId.toString()).map(t=>t.split(":")[2]))]:[]}getHttpProvider(e){const t=e||this.chainId;return this.httpProviders[t]||(this.httpProviders={...this.httpProviders,[t]:this.createHttpProvider(t)},this.httpProviders[t])}async handleSwitchChain(e){let t=e.request.params?e.request.params[0]?.chainId:"0x0";t=t.startsWith("0x")?t:`0x${t}`;const s=parseInt(t,16);if(this.isChainApproved(s))this.setDefaultChain(`${s}`);else if(this.namespace.methods.includes("wallet_switchEthereumChain"))await this.client.request({topic:e.topic,request:{method:e.request.method,params:[{chainId:t}]},chainId:this.namespace.chains?.[0]}),this.setDefaultChain(`${s}`);else throw new Error(`Failed to switch to chain 'eip155:${s}'. The chain is not approved or the wallet does not support 'wallet_switchEthereumChain' method.`);return null}isChainApproved(e){return this.namespace.chains.includes(`${this.name}:${e}`)}async getCapabilities(e){const t=e.request?.params?.[0],s=e.request?.params?.[1]||[];if(!t)throw new Error("Missing address parameter in `wallet_getCapabilities` request");const n=this.client.session.get(e.topic),a=n?.sessionProperties?.capabilities||{},r=s.length>0?s.join(","):`0x${this.chainId.toString(16)}`,p=`${t}${r}`,c=a?.[p];if(c)return c;let d;try{d=ie(n,t,s)}catch(h){console.warn("Failed to extract capabilities from session",h)}if(d)return d;const l=await this.client.request(e);try{await this.client.session.update(e.topic,{sessionProperties:{...n.sessionProperties||{},capabilities:{...a||{},[p]:l}}})}catch(h){console.warn("Failed to update session with capabilities",h)}return l}async getCallStatus(e){const t=this.client.session.get(e.topic),s=t.sessionProperties?.bundler_name;if(s){const r=this.getBundlerUrl(e.chainId,s);try{return await this.getUserOperationReceipt(r,e)}catch(p){console.warn("Failed to fetch call status from bundler",p,r)}}const n=t.sessionProperties?.bundler_url;if(n)try{return await this.getUserOperationReceipt(n,e)}catch(r){console.warn("Failed to fetch call status from custom bundler",r,n)}const a=await ce({resultId:e.request.params?.[0],storage:this.storage});if(a)try{return await ne(a,this.getHttpProvider.bind(this))}catch(r){console.warn("Failed to fetch call status from stored send calls",r,a)}if(this.namespace.methods.includes(e.request.method))return await this.client.request(e);throw new Error("Fetching call status not approved by the wallet.")}async getUserOperationReceipt(e,t){const s=new URL(e),n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(v.formatJsonRpcRequest("eth_getUserOperationReceipt",[t.request.params?.[0]]))});if(!n.ok)throw new Error(`Failed to fetch user operation receipt - ${n.status}`);return await n.json()}getBundlerUrl(e,t){return`${W}?projectId=${this.client.core.projectId}&chainId=${e}&bundler=${t}`}async sendCalls(e){const t=await this.client.request(e),s=e.request.params?.[0],n=t?.id,a=t?.capabilities||{},r=a?.caip345?.caip2,p=a?.caip345?.transactionHashes;return!n||!r||!p?.length||await re({sendCalls:{request:s,result:t},storage:this.storage}),t}}class pe{constructor(e){this.name=q,this.namespace=e.namespace,this.events=m("events"),this.client=m("client"),this.chainId=this.getDefaultChain(),this.name=this.getNamespaceName(),this.httpProviders=this.createHttpProviders()}updateNamespace(e){this.namespace.chains=[...new Set((this.namespace.chains||[]).concat(e.chains||[]))],this.namespace.accounts=[...new Set((this.namespace.accounts||[]).concat(e.accounts||[]))],this.namespace.methods=[...new Set((this.namespace.methods||[]).concat(e.methods||[]))],this.namespace.events=[...new Set((this.namespace.events||[]).concat(e.events||[]))],this.httpProviders=this.createHttpProviders()}requestAccounts(){return this.getAccounts()}request(e){return this.namespace.methods.includes(e.request.method)?this.client.request(e):this.getHttpProvider(e.chainId).request(e.request)}setDefaultChain(e,t){this.httpProviders[e]||this.setHttpProvider(e,t);const s=this.chainId;this.chainId=e,this.events.emit(w.DEFAULT_CHAIN_CHANGED,{currentCaipChainId:`${this.name}:${e}`,previousCaipChainId:`${this.name}:${s}`})}getDefaultChain(){if(this.chainId)return this.chainId;if(this.namespace.defaultChain)return this.namespace.defaultChain;const e=this.namespace.chains[0];if(!e)throw new Error("ChainId not found");return e.split(":")[1]}getNamespaceName(){const e=this.namespace.chains[0];if(!e)throw new Error("ChainId not found");return o.parseChainId(e).namespace}getAccounts(){const e=this.namespace.accounts;return e?[...new Set(e.filter(t=>t.split(":")[1]===this.chainId.toString()).map(t=>t.split(":")[2]))]:[]}createHttpProviders(){const e={};return this.namespace?.accounts?.forEach(t=>{const s=o.parseChainId(t),n=this.namespace?.rpcMap?.[`${s.namespace}:${s.reference}`];e[s.reference]=this.createHttpProvider(t,n)}),e}getHttpProvider(e){const t=o.parseChainId(e).reference,s=this.httpProviders[t];if(typeof s>"u")throw new Error(`JSON-RPC provider for ${e} not found`);return s}setHttpProvider(e,t){const s=this.createHttpProvider(e,t);s&&(this.httpProviders[e]=s)}createHttpProvider(e,t){const s=t||A(e,this.namespace,this.client.core.projectId);if(!s)throw new Error(`No RPC url provided for chainId: ${e}`);return new b.JsonRpcProvider(new M.default(s,m("disableProviderPing")))}}let L=class k{constructor(e){this.events=new J.default,this.rpcProviders={},this.disableProviderPing=!1,this.providerOpts=e,this.logger=o.createLogger({logger:e.logger??$,name:this.providerOpts.name??O}),this.disableProviderPing=e?.disableProviderPing||!1}static async init(e){const t=new k(e);return await t.initialize(),t}async request(e,t,s){const[n,a]=this.validateChain(t);if(!this.session)throw new Error("Please call connect() before request()");return await this.getProvider(n).request({request:{...e},chainId:`${n}:${a}`,topic:this.session.topic,expiry:s})}sendAsync(e,t,s,n){const a=new Date().getTime();this.request(e,s,n).then(r=>t(null,v.formatJsonRpcResult(a,r))).catch(r=>t(r,void 0))}async enable(){if(!this.client)throw new Error("Sign Client not initialized");return this.session||await this.connect({namespaces:this.namespaces,optionalNamespaces:this.optionalNamespaces,sessionProperties:this.sessionProperties,scopedProperties:this.scopedProperties}),await this.requestAccounts()}async disconnect(){if(!this.session)throw new Error("Please call connect() before enable()");await this.client.disconnect({topic:this.session?.topic,reason:o.getSdkError("USER_DISCONNECTED")}),await this.cleanup()}async connect(e){if(!this.client)throw new Error("Sign Client not initialized");if(this.connectParams=e,this.setNamespaces(e),this.cleanupPendingPairings(),!e.skipPairing)return await this.pair(e.pairingTopic)}async authenticate(e,t){if(!this.client)throw new Error("Sign Client not initialized");this.setNamespaces(e),await this.cleanupPendingPairings();const{uri:s,response:n}=await this.client.authenticate(e,t);s&&(this.uri=s,this.events.emit("display_uri",s));const a=await n();if(this.session=a.session,this.session){const r=U(this.session.namespaces);this.namespaces=f(this.namespaces,r),await this.persist("namespaces",this.namespaces),this.onConnect()}return a}on(e,t){this.events.on(e,t)}once(e,t){this.events.once(e,t)}removeListener(e,t){this.events.removeListener(e,t)}off(e,t){this.events.off(e,t)}get isWalletConnect(){return!0}async pair(e){const{uri:t,approval:s}=await this.client.connect({pairingTopic:e,requiredNamespaces:this.namespaces,optionalNamespaces:this.optionalNamespaces,sessionProperties:this.sessionProperties,scopedProperties:this.scopedProperties,authentication:this.connectParams?.authentication,walletPay:this.connectParams?.walletPay});t&&(this.uri=t,this.events.emit("display_uri",t));const n=await s();this.session=n;const a=U(n.namespaces);return this.namespaces=f(this.namespaces,a),await this.persist("namespaces",this.namespaces),await this.persist("optionalNamespaces",this.optionalNamespaces),this.onConnect(),this.session}setDefaultChain(e,t){try{if(!this.session)return;const[s,n]=this.validateChain(e),a=this.getProvider(s);a?a.setDefaultChain(n,t):this.session&&this.logger.warn(`Provider for namespace '${s}' not found in setDefaultChain`)}catch(s){if(!/Please call connect/.test(s.message))throw s}}async cleanupPendingPairings(e={}){try{this.logger.info("Cleaning up inactive pairings...");const t=this.client.pairing.getAll();if(!o.isValidArray(t))return;for(const s of t)e.deletePairings?this.client.core.expirer.set(s.topic,0):await this.client.core.relayer.subscriber.unsubscribe(s.topic);this.logger.info(`Inactive pairings cleared: ${t.length}`)}catch(t){this.logger.warn(t,"Failed to cleanup pending pairings")}}abortPairingAttempt(){this.logger.warn("abortPairingAttempt is deprecated. This is now a no-op.")}async checkStorage(){this.namespaces=await this.getFromStore("namespaces")||{},this.optionalNamespaces=await this.getFromStore("optionalNamespaces")||{},this.session&&this.createProviders()}async initialize(){this.logger.trace("Initialized"),await this.createClient(),await this.checkStorage(),this.registerEventListeners()}async createClient(){if(this.client=this.providerOpts.client||await V.SignClient.init({core:this.providerOpts.core,logger:this.providerOpts.logger||$,relayUrl:this.providerOpts.relayUrl||K,projectId:this.providerOpts.projectId,metadata:this.providerOpts.metadata,storageOptions:this.providerOpts.storageOptions,storage:this.providerOpts.storage,name:this.providerOpts.name,customStoragePrefix:this.providerOpts.customStoragePrefix,telemetryEnabled:this.providerOpts.telemetryEnabled}),this.providerOpts.session)try{this.session=this.client.session.get(this.providerOpts.session.topic)}catch(e){throw this.logger.error(e,"Failed to get session"),new Error(`The provided session: ${this.providerOpts?.session?.topic} doesn't exist in the Sign client`)}else{const e=this.client.session.getAll();this.session=e[0]}this.logger.trace("SignClient Initialized")}createProviders(){if(!this.client)throw new Error("Sign Client not initialized");if(!this.session)throw new Error("Session not initialized. Please call connect() before enable()");const e=[...new Set(Object.keys(this.session.namespaces).map(t=>o.parseNamespaceKey(t)))];P("client",this.client),P("events",this.events),P("disableProviderPing",this.disableProviderPing),e.forEach(t=>{if(!this.session)return;const s=Q(t,this.session);if(s?.length===0)return;const n=R(s),a={...f(this.namespaces,this.optionalNamespaces)[t],accounts:s,chains:n};switch(t){case"eip155":this.rpcProviders[t]=new he({namespace:a});break;default:this.rpcProviders[t]=new pe({namespace:a})}})}registerEventListeners(){if(typeof this.client>"u")throw new Error("Sign Client is not initialized");this.client.on("session_ping",e=>{const{topic:t}=e;t===this.session?.topic&&this.events.emit("session_ping",e)}),this.client.on("session_event",e=>{const{params:t,topic:s}=e;if(s!==this.session?.topic)return;const{event:n}=t;if(n.name==="accountsChanged"){const a=n.data;a&&o.isValidArray(a)&&this.events.emit("accountsChanged",a.map(D))}else if(n.name==="chainChanged"){const a=t.chainId,r=t.event.data,p=o.parseNamespaceKey(a),c=C(a)!==C(r)?`${p}:${C(r)}`:a;this.onChainChanged({currentCaipChainId:c})}else this.events.emit(n.name,n.data);this.events.emit("session_event",e)}),this.client.on("session_update",({topic:e,params:t})=>{if(e!==this.session?.topic)return;const{namespaces:s}=t,n=this.client?.session.get(e);this.session={...n,namespaces:s},this.onSessionUpdate(),this.events.emit("session_update",{topic:e,params:t})}),this.client.on("session_delete",async e=>{e.topic===this.session?.topic&&(await this.cleanup(),this.events.emit("session_delete",e),this.events.emit("disconnect",{...o.getSdkError("USER_DISCONNECTED"),data:e.topic}))}),this.on(w.DEFAULT_CHAIN_CHANGED,e=>{this.onChainChanged({...e,internal:!0})})}getProvider(e){return this.rpcProviders[e]||this.rpcProviders[q]}onSessionUpdate(){Object.keys(this.rpcProviders).forEach(e=>{this.getProvider(e).updateNamespace(this.session?.namespaces[e])})}setNamespaces(e){const{namespaces:t={},optionalNamespaces:s={},sessionProperties:n,scopedProperties:a}=e;this.optionalNamespaces=f(t,s),this.sessionProperties=n,this.scopedProperties=a}validateChain(e){const[t,s]=e?.split(":")||["",""];if(!this.namespaces||!Object.keys(this.namespaces).length)return[t,s];if(t&&!Object.keys(this.namespaces||{}).map(r=>o.parseNamespaceKey(r)).includes(t))throw new Error(`Namespace '${t}' is not configured. Please call connect() first with namespace config.`);if(t&&s)return[t,s];const n=o.parseNamespaceKey(Object.keys(this.namespaces)[0]),a=this.rpcProviders[n].getDefaultChain();return[n,a]}async requestAccounts(){const[e]=this.validateChain();return await this.getProvider(e).requestAccounts()}async onChainChanged({currentCaipChainId:e,previousCaipChainId:t,internal:s=!1}){if(!this.namespaces)return;const[n,a]=this.validateChain(e);if(a){if(this.updateNamespaceChain(n,a),s)this.events.emit("chainChanged",a),this.emitAccountsChangedOnChainChange({namespace:n,currentCaipChainId:e,previousCaipChainId:t});else{const r=this.getProvider(n);r?r.setDefaultChain(a):this.session&&this.logger.warn(`Provider for namespace '${n}' not found during chain change`)}await this.persist("namespaces",this.namespaces)}}emitAccountsChangedOnChainChange({namespace:e,currentCaipChainId:t,previousCaipChainId:s}){try{if(s===t)return;const n=this.session?.namespaces[e]?.accounts;if(!n)return;const a=n.filter(r=>r.includes(`${t}:`)).map(D);if(!o.isValidArray(a))return;this.events.emit("accountsChanged",a)}catch(n){this.logger.warn(n,"Failed to emit accountsChanged on chain change")}}updateNamespaceChain(e,t){if(!this.namespaces)return;const s=this.namespaces[e]?e:`${e}:${t}`,n={chains:[],methods:[],events:[],defaultChain:t};this.namespaces[s]?this.namespaces[s]&&(this.namespaces[s].defaultChain=t):this.namespaces[s]=n}onConnect(){this.createProviders(),this.events.emit("connect",{session:this.session})}async cleanup(){this.connectParams=void 0,this.namespaces=void 0,this.optionalNamespaces=void 0,this.sessionProperties=void 0,await this.deleteFromStore("namespaces"),await this.deleteFromStore("optionalNamespaces"),await this.deleteFromStore("sessionProperties"),this.session=void 0,this.cleanupPendingPairings({deletePairings:!0}),await this.cleanupStorage()}async persist(e,t){const s=this.session?.topic||"";await this.client.core.storage.setItem(`${g}/${e}${s}`,t)}async getFromStore(e){const t=this.session?.topic||"";return await this.client.core.storage.getItem(`${g}/${e}${t}`)}async deleteFromStore(e){const t=this.session?.topic||"";await this.client.core.storage.removeItem(`${g}/${e}${t}`)}async cleanupStorage(){try{if(this.client?.session.length>0)return;const e=await this.client.core.storage.getKeys();for(const t of e)t.startsWith(g)&&await this.client.core.storage.removeItem(t)}catch(e){this.logger.warn(e,"Failed to cleanup storage")}}};const le=L;exports.UniversalProvider=le,exports.default=L; //# sourceMappingURL=index.cjs.map