UNPKG

@walletconnect/universal-provider

Version:
3 lines (2 loc) 21.3 kB
import{SignClient as V}from"@walletconnect/sign-client";import{isCaipNamespace as N,isValidObject as m,parseNamespaceKey as g,mergeArrays as C,parseChainId as f,isExpired as O,calcExpiry as W,createLogger as K,getSdkError as q,isValidArray as P}from"@walletconnect/utils";import{merge as Y}from"es-toolkit/compat";import{formatJsonRpcRequest as A,formatJsonRpcResult as X}from"@walletconnect/jsonrpc-utils";import{JsonRpcProvider as _}from"@walletconnect/jsonrpc-provider";import Q,{HttpConnection as Z}from"@walletconnect/jsonrpc-http-connection";import ee from"events";const R="error",te="wss://relay.walletconnect.org",se="wc",H="universal_provider",v=`${se}@2:${H}:`,D="https://rpc.walletconnect.org/v1/",j="generic",ie=`${D}bundler`,d="call_status",ne=86400,I={DEFAULT_CHAIN_CHANGED:"default_chain_changed"};function F(i,e,t){const s=f(i);return e.rpcMap?.[s.reference]||`${D}?chainId=${s.namespace}:${s.reference}&projectId=${t}`}function ae(i){return i.includes(":")?i.split(":")[1]:i}function U(i){return i.map(e=>`${e.split(":")[0]}:${e.split(":")[1]}`)}function re(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 T(i){return Object.fromEntries(Object.entries(i).filter(([e,t])=>t?.chains?.length&&t?.chains?.length>0))}function w(i={},e={}){const t=T(x(i)),s=T(x(e));return Y(t,s)}function x(i){const e={};if(!m(i))return e;for(const[t,s]of Object.entries(i)){const n=N(t)?[t]:s.chains,a=s.methods||[],r=s.events||[],h=s.rpcMap||{},o=g(t);e[o]={...e[o],...s,chains:C(n,e[o]?.chains),methods:C(a,e[o]?.methods),events:C(r,e[o]?.events)},(m(h)||m(e[o]?.rpcMap||{}))&&(e[o].rpcMap={...h,...e[o]?.rpcMap})}return e}function L(i){return i.includes(":")?i.split(":")[2]:i}function k(i){const e={};for(const[t,s]of Object.entries(i)){const n=s.methods||[],a=s.events||[],r=s.accounts||[],h=N(t)?[t]:s.chains?s.chains:U(s.accounts);e[t]={chains:h,methods:n,events:a,accounts:r}}return e}function y(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 oe(i){try{const e=JSON.parse(i);return typeof e=="object"&&e!==null&&!Array.isArray(e)}catch{return!1}}const z={},u=i=>z[i],S=(i,e)=>{z[i]=e},G="eip155",ce=["atomic","flow-control","paymasterService","sessionKeys","auxiliaryFunds"],he=i=>i&&i.startsWith("0x")?BigInt(i).toString(10):i,b=i=>i&&i.startsWith("0x")?i:`0x${BigInt(i).toString(16)}`,M=i=>Object.keys(i).filter(e=>ce.includes(e)).reduce((e,t)=>(e[t]=pe(i[t]),e),{}),pe=i=>typeof i=="string"&&oe(i)?JSON.parse(i):i,le=(i,e,t)=>{const{sessionProperties:s={},scopedProperties:n={}}=i,a={};if(!m(n)&&!m(s))return;const r=M(s);for(const h of t){const o=he(h);if(!o)continue;a[b(o)]=r;const l=n?.[`${G}:${o}`];if(l){const p=l?.[`${G}:${o}:${e}`];a[b(o)]={...a[b(o)],...M(p||l)}}}for(const[h,o]of Object.entries(a))Object.keys(o).length===0&&delete a[h];return Object.keys(a).length>0?a:void 0};let E;class ${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 E||(E=new $(e)),E}}async function de(i,e){const t=f(i.result.capabilities.caip345.caip2),s=i.result.capabilities.caip345.transactionHashes,n=await Promise.allSettled(s.map(c=>ue(t.reference,c,e))),a=n.filter(c=>c.status==="fulfilled").map(c=>c.value).filter(c=>c);n.filter(c=>c.status==="rejected").forEach(c=>console.warn("Failed to fetch transaction receipt:",c.reason));const r=!a.length||a.some(c=>!c),h=a.every(c=>c?.status==="0x1"),o=a.every(c=>c?.status==="0x0"),l=a.some(c=>c?.status==="0x0");let p;return r?p=100:h?p=200:o?p=500:l&&(p=600),{id:i.result.id,version:i.request.version,atomic:i.request.atomicRequired,chainId:i.request.chainId,capabilities:i.result.capabilities,receipts:a,status:p}}async function ue(i,e,t){return await t(parseInt(i)).request(A("eth_getTransactionReceipt",[e]))}async function me({sendCalls:i,storage:e}){const t=await e.getItem(d);await e.setItem(d,{...t,[i.result.id]:{request:i.request,result:i.result,expiry:W(ne)}})}async function ge({resultId:i,storage:e}){const t=await e.getItem(d);if(t){delete t[i],await e.setItem(d,t);for(const s in t)O(t[s].expiry)&&delete t[s];await e.setItem(d,t)}}async function fe({resultId:i,storage:e}){const t=(await e.getItem(d))?.[i];if(t&&!O(t.expiry))return t;await ge({resultId:i,storage:e})}class ve{constructor(e){this.name="eip155",this.namespace=e.namespace,this.events=u("events"),this.client=u("client"),this.httpProviders=this.createHttpProviders(),this.chainId=parseInt(this.getDefaultChain()),this.storage=$.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(I.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||F(`${this.name}:${e}`,this.namespace,this.client.core.projectId);if(!s)throw new Error(`No RPC url provided for chainId: ${e}`);return new _(new Z(s,u("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(ae(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)}`,h=`${t}${r}`,o=a?.[h];if(o)return o;let l;try{l=le(n,t,s)}catch(c){console.warn("Failed to extract capabilities from session",c)}if(l)return l;const p=await this.client.request(e);try{await this.client.session.update(e.topic,{sessionProperties:{...n.sessionProperties||{},capabilities:{...a||{},[h]:p}}})}catch(c){console.warn("Failed to update session with capabilities",c)}return p}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(h){console.warn("Failed to fetch call status from bundler",h,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 fe({resultId:e.request.params?.[0],storage:this.storage});if(a)try{return await de(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(A("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`${ie}?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,h=a?.caip345?.transactionHashes;return!n||!r||!h?.length||await me({sendCalls:{request:s,result:t},storage:this.storage}),t}}class we{constructor(e){this.name=j,this.namespace=e.namespace,this.events=u("events"),this.client=u("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(I.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 f(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=f(t),n=this.namespace?.rpcMap?.[`${s.namespace}:${s.reference}`];e[s.reference]=this.createHttpProvider(t,n)}),e}getHttpProvider(e){const t=f(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||F(e,this.namespace,this.client.core.projectId);if(!s)throw new Error(`No RPC url provided for chainId: ${e}`);return new _(new Q(s,u("disableProviderPing")))}}let J=class B{constructor(e){this.events=new ee,this.rpcProviders={},this.disableProviderPing=!1,this.providerOpts=e,this.logger=K({logger:e.logger??R,name:this.providerOpts.name??H}),this.disableProviderPing=e?.disableProviderPing||!1}static async init(e){const t=new B(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,X(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:q("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=k(this.session.namespaces);this.namespaces=w(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=k(n.namespaces);return this.namespaces=w(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(!P(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.init({core:this.providerOpts.core,logger:this.providerOpts.logger||R,relayUrl:this.providerOpts.relayUrl||te,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=>g(t)))];S("client",this.client),S("events",this.events),S("disableProviderPing",this.disableProviderPing),e.forEach(t=>{if(!this.session)return;const s=re(t,this.session);if(s?.length===0)return;const n=U(s),a={...w(this.namespaces,this.optionalNamespaces)[t],accounts:s,chains:n};switch(t){case"eip155":this.rpcProviders[t]=new ve({namespace:a});break;default:this.rpcProviders[t]=new we({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&&P(a)&&this.events.emit("accountsChanged",a.map(L))}else if(n.name==="chainChanged"){const a=t.chainId,r=t.event.data,h=g(a),o=y(a)!==y(r)?`${h}:${y(r)}`:a;this.onChainChanged({currentCaipChainId:o})}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",{...q("USER_DISCONNECTED"),data:e.topic}))}),this.on(I.DEFAULT_CHAIN_CHANGED,e=>{this.onChainChanged({...e,internal:!0})})}getProvider(e){return this.rpcProviders[e]||this.rpcProviders[j]}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=w(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=>g(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=g(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(L);if(!P(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(`${v}/${e}${s}`,t)}async getFromStore(e){const t=this.session?.topic||"";return await this.client.core.storage.getItem(`${v}/${e}${t}`)}async deleteFromStore(e){const t=this.session?.topic||"";await this.client.core.storage.removeItem(`${v}/${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(v)&&await this.client.core.storage.removeItem(t)}catch(e){this.logger.warn(e,"Failed to cleanup storage")}}};const Ce=J;export{Ce as UniversalProvider,J as default}; //# sourceMappingURL=index.js.map