From ec607ba65c839e9e1e845f007443abb57bfffe71 Mon Sep 17 00:00:00 2001 From: Tomek Marciniak Date: Mon, 5 Aug 2024 08:51:31 +0200 Subject: [PATCH 1/2] feat(web connector): add events emitting --- .deepsource.toml | 11 + apps/extension/manifest.ts | 2 +- apps/extension/package.json | 11 +- apps/extension/public/pallad_rpc.js | 107 ------ apps/extension/public/rpc.js | 140 ++++++++ .../src/background/handlers/index.ts | 10 + .../src/background/handlers/wallet.ts | 48 +++ .../src/background/handlers/web-provider.ts | 180 ++++++++++ apps/extension/src/background/index.ts | 263 ++++---------- apps/extension/src/inject/index.ts | 12 +- apps/extension/src/inject/rpc.ts | 19 + apps/extension/src/inject/rpc/provider.ts | 34 ++ apps/extension/src/inject/rpc/utils.ts | 37 ++ apps/extension/tsup.config.ts | 10 + biome.json | 1 + packages/features/package.json | 2 +- .../features/src/common/hooks/use-account.ts | 3 +- packages/features/src/common/store/app.ts | 11 +- .../src/components/address-dropdown.tsx | 20 +- .../features/src/components/fee-picker.tsx | 6 +- .../features/src/components/hash-dropdown.tsx | 11 +- .../routes/seed-backup-confirmation.tsx | 2 +- .../src/onboarding/routes/seed-import.tsx | 2 +- .../features/src/receive/routes/receive.tsx | 3 +- .../features/src/receive/views/receive.tsx | 4 +- packages/features/src/router.tsx | 2 +- .../features/src/settings/routes/settings.tsx | 2 +- .../src/staking/routes/staking-overview.tsx | 13 +- .../src/staking/views/staking-overview.tsx | 6 +- .../src/transactions/routes/transactions.tsx | 11 +- .../src/transactions/views/transactions.tsx | 7 +- .../features/src/wallet/routes/networks.tsx | 8 +- .../web-connector/routes/web-connector.tsx | 7 + packages/vault/package.json | 2 +- .../src/vault/utils/get-current-wallet.ts | 9 +- packages/web-provider/package.json | 3 +- .../src/mina-network/mina-provider.ts | 328 +++++------------- .../web-provider/src/mina-network/types.ts | 81 ----- .../src/universal-provider/types.ts | 4 +- packages/web-provider/src/utils/prompts.ts | 17 +- .../src/web-provider-types/data-model.ts | 1 - .../test/mina/mina-provider.test.ts | 21 +- pnpm-lock.yaml | 37 +- 43 files changed, 795 insertions(+), 713 deletions(-) create mode 100644 .deepsource.toml delete mode 100644 apps/extension/public/pallad_rpc.js create mode 100644 apps/extension/public/rpc.js create mode 100644 apps/extension/src/background/handlers/index.ts create mode 100644 apps/extension/src/background/handlers/wallet.ts create mode 100644 apps/extension/src/background/handlers/web-provider.ts create mode 100644 apps/extension/src/inject/rpc.ts create mode 100644 apps/extension/src/inject/rpc/provider.ts create mode 100644 apps/extension/src/inject/rpc/utils.ts create mode 100644 apps/extension/tsup.config.ts diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 00000000..ae676efb --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,11 @@ +version = 1 + +[[analyzers]] +name = "javascript" + + [analyzers.meta] + plugins = ["react"] + environment = [ + "browser", + "vitest" + ] diff --git a/apps/extension/manifest.ts b/apps/extension/manifest.ts index b6daf8a1..3a246584 100644 --- a/apps/extension/manifest.ts +++ b/apps/extension/manifest.ts @@ -29,7 +29,7 @@ export const manifest: chrome.runtime.ManifestV3 = { ], web_accessible_resources: [ { - resources: ["pallad_rpc.js"], + resources: ["rpc.js"], matches: ["https://*/*"], }, ], diff --git a/apps/extension/package.json b/apps/extension/package.json index 2fefc8fe..57eaaf09 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -4,7 +4,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "tsc && pnpm build:rpc && vite build", + "build:rpc": "tsup", "build:firefox": "web-ext build --source-dir=dist", "build:safari": "xcrun safari-web-extension-converter dist --app-name Pallad --bundle-identifier co.pallad.app --swift --no-prompt --force --macos-only --no-open", "preview": "vite preview", @@ -18,9 +19,11 @@ "@palladxyz/features": "workspace:*", "@palladxyz/key-management": "workspace:*", "@palladxyz/web-provider": "workspace:*", + "@palladxyz/vault": "workspace:*", "@plasmohq/messaging": "0.6.2", "buffer": "6.0.3", - "debounce": "^2.1.0", + "p-debounce": "4.0.0", + "mitt": "3.0.1", "next-themes": "0.3.0", "react": "18.3.1", "react-dom": "18.3.1", @@ -58,8 +61,8 @@ "vite-plugin-svgr": "4.2.0", "vite-plugin-top-level-await": "1.4.2", "vite-plugin-wasm": "3.3.0", - "vite-plugin-web-extension": "^4.1.6", + "vite-plugin-web-extension": "4.1.6", "web-ext": "8.2.0", - "write-json-file": "^6.0.0" + "write-json-file": "6.0.0" } } diff --git a/apps/extension/public/pallad_rpc.js b/apps/extension/public/pallad_rpc.js deleted file mode 100644 index e402ab7a..00000000 --- a/apps/extension/public/pallad_rpc.js +++ /dev/null @@ -1,107 +0,0 @@ -function debounce(func, wait, immediate) { - let timeout - return function (...args) { - return new Promise((resolve) => { - clearTimeout(timeout) - timeout = setTimeout(() => { - timeout = null - if (!immediate) { - Promise.resolve(func.apply(this, [...args])).then(resolve) - } - }, wait) - if (immediate && !timeout) { - Promise.resolve(func.apply(this, [...args])).then(resolve) - } - }) - } -} -const BROADCAST_CHANNEL_ID = "pallad" -const callPalladAsync = ({ method, payload }) => { - return new Promise((resolve, reject) => { - const privateChannelId = `private-${Math.random()}` - const channel = new BroadcastChannel(BROADCAST_CHANNEL_ID) - const responseChannel = new BroadcastChannel(privateChannelId) - responseChannel.addEventListener("message", ({ data }) => { - channel.close() - const error = data.response?.error - if (error) { - try { - console.table(JSON.parse(error.message)) - } catch { - console.info(error.message) - } - return reject(error) - } - return resolve(data.response) - }) - channel.postMessage({ - method, - payload, - isPallad: true, - respondAt: privateChannelId, - }) - return channel.close() - }) -} -const debouncedCall = debounce(callPalladAsync, 300, false) -const init = () => { - const info = { - slug: "pallad", - name: "Pallad", - icon: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzAwIDMwMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE5OF8yNDY5NSkiPgo8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgcng9IjEyIiBmaWxsPSIjRjZDMTc3Ii8+CjxwYXRoIGQ9Ik05MCAyMTRDOTAgMjExLjc5MSA5MS43OTA5IDIxMCA5NCAyMTBIMTM2QzE0My43MzIgMjEwIDE1MCAyMTYuMjY4IDE1MCAyMjRWMjI2QzE1MCAyMzMuNzMyIDE0My43MzIgMjQwIDEzNiAyNDBIMTA0Qzk2LjI2OCAyNDAgOTAgMjMzLjczMiA5MCAyMjZWMjE0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgNjRDOTAgNjEuNzkwOSA5MS43OTA5IDYwIDk0IDYwSDE5NkMyMDMuNzMyIDYwIDIxMCA2Ni4yNjggMjEwIDc0Vjc2QzIxMCA4My43MzIgMjAzLjczMiA5MCAxOTYgOTBIMTA0Qzk2LjI2OCA5MCA5MCA4My43MzIgOTAgNzZWNjRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTRDMjEwIDkxLjc5MDkgMjExLjc5MSA5MCAyMTQgOTBIMjI2QzIzMy43MzIgOTAgMjQwIDk2LjI2OCAyNDAgMTA0VjEzNkMyNDAgMTQzLjczMiAyMzMuNzMyIDE1MCAyMjYgMTUwSDIyNEMyMTYuMjY4IDE1MCAyMTAgMTQzLjczMiAyMTAgMTM2Vjk0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNNjAgOTRDNjAgOTEuNzkwOSA2MS43OTA5IDkwIDY0IDkwSDc2QzgzLjczMiA5MCA5MCA5Ni4yNjggOTAgMTA0VjE5NkM5MCAyMDMuNzMyIDgzLjczMiAyMTAgNzYgMjEwSDc0QzY2LjI2OCAyMTAgNjAgMjAzLjczMiA2MCAxOTZWOTRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0xNTAgMTUwSDE5NkMyMDMuNzMyIDE1MCAyMTAgMTU2LjI2OCAyMTAgMTY0VjE2NkMyMTAgMTczLjczMiAyMDMuNzMyIDE4MCAxOTYgMTgwSDE2NEMxNTYuMjY4IDE4MCAxNTAgMTczLjczMiAxNTAgMTY2VjE1MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCAxNTBIMTk1QzE5NSAxNTAgMjAxIDE1MCAyMDUuNSAxNTQuNUMyMTAgMTU5IDIxMCAxNjUgMjEwIDE2NVYxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIyNSAxNTBDMjI1IDE1MCAyMTkgMTUwIDIxNC41IDE0NS41QzIxMCAxNDEgMjEwIDEzNSAyMTAgMTM1TDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIxMCAxMzVDMjEwIDEzNSAyMTAgMTQxIDIwNS41IDE0NS41QzIwMSAxNTAgMTk1IDE1MCAxOTUgMTUwTDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik05MCA5MEg3NUM3NSA5MCA4MSA5MCA4NS41IDk0LjVDOTAgOTkgOTAgMTA1IDkwIDEwNVY5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDkwTDEwNSA5MEMxMDUgOTAgOTkgOTAgOTQuNSA4NS41QzkwIDgxIDkwIDc1IDkwIDc1TDkwIDkwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgOTBMOTAgMTA1QzkwIDEwNSA5MCA5OSA5NC41IDk0LjVDOTkgOTAgMTA1IDkwIDEwNSA5MEw5MCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCA5MEgxOTVDMTk1IDkwIDIwMSA5MCAyMDUuNSA5NC41QzIxMCA5OSAyMTAgMTA1IDIxMCAxMDVWOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDc1QzIxMCA3NSAyMTAgODEgMjA1LjUgODUuNUMyMDEgOTAgMTk1IDkwIDE5NSA5MEwyMTAgOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDEwNUMyMTAgMTA1IDIxMCA5OSAyMTQuNSA5NC41QzIxOSA5MCAyMjUgOTAgMjI1IDkwTDIxMCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDIxMEwxMDUgMjEwQzEwNSAyMTAgOTkgMjEwIDk0LjUgMjA1LjVDOTAgMjAxIDkwIDE5NSA5MCAxOTVMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDE5NUM5MCAxOTUgOTAgMjAxIDg1LjUgMjA1LjVDODEgMjEwIDc1IDIxMCA3NSAyMTBMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDIyNUM5MCAyMjUgOTAgMjE5IDk0LjUgMjE0LjVDOTkgMjEwIDEwNSAyMTAgMTA1IDIxMEw5MCAyMTBaIiBmaWxsPSIjMjUyMzNBIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTk4XzI0Njk1Ij4KPHJlY3Qgd2lkdGg9IjMwMCIgaGVpZ2h0PSIzMDAiIHJ4PSI0MCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K", - rdns: "co.pallad.wallet", - } - const provider = { - request: async ({ method, params }) => - debouncedCall({ - method, - payload: { ...params, origin: window.location.origin }, - }), - enable: async () => { - return debouncedCall({ - method: "enable", - payload: { origin: window.location.origin }, - }) - }, - isPallad: true, - isConnected: async () => { - return debouncedCall({ - method: "isConnected", - payload: { origin: window.location.origin }, - }) - }, - /* - Note: `listenerId` is used as a placeholder to identify listener functions. - Since functions can't be serialized over postMessage, you need to implement - a system in your background script to manage listeners and associate them with - IDs. When an event occurs, you can then send a message back to the content - script to invoke the appropriate listener by ID. - */ - // I don't think we need listenerId for `on`, `once` - on: async (event, listenerId) => { - return debouncedCall({ - method: "on", - payload: { event, listenerId }, - }) - }, - off: async (event, listenerId) => { - return debouncedCall({ - method: "off", - payload: { event, listenerId }, - }) - }, - } - window.mina = provider - const announceProvider = () => { - window.dispatchEvent( - new CustomEvent("mina:announceProvider", { - detail: Object.freeze({ info, provider }), - }), - ) - } - window.addEventListener("mina:requestProvider", (event) => { - announceProvider() - }) - announceProvider() -} -init() diff --git a/apps/extension/public/rpc.js b/apps/extension/public/rpc.js new file mode 100644 index 00000000..5c105f12 --- /dev/null +++ b/apps/extension/public/rpc.js @@ -0,0 +1,140 @@ +// ../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs +function mitt_default(n) { + return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) { + var i = n.get(t); + i ? i.push(e) : n.set(t, [e]); + }, off: function(t, e) { + var i = n.get(t); + i && (e ? i.splice(i.indexOf(e) >>> 0, 1) : n.set(t, [])); + }, emit: function(t, e) { + var i = n.get(t); + i && i.slice().map(function(n2) { + n2(e); + }), (i = n.get("*")) && i.slice().map(function(n2) { + n2(t, e); + }); + } }; +} + +// ../../node_modules/.pnpm/p-debounce@4.0.0/node_modules/p-debounce/index.js +var pDebounce = (fn, wait, options = {}) => { + if (!Number.isFinite(wait)) { + throw new TypeError("Expected `wait` to be a finite number"); + } + let leadingValue; + let timeout; + let resolveList = []; + return function(...arguments_) { + return new Promise((resolve) => { + const shouldCallNow = options.before && !timeout; + clearTimeout(timeout); + timeout = setTimeout(() => { + timeout = null; + const result = options.before ? leadingValue : fn.apply(this, arguments_); + for (resolve of resolveList) { + resolve(result); + } + resolveList = []; + }, wait); + if (shouldCallNow) { + leadingValue = fn.apply(this, arguments_); + resolve(leadingValue); + } else { + resolveList.push(resolve); + } + }); + }; +}; +pDebounce.promise = (function_) => { + let currentPromise; + return async function(...arguments_) { + if (currentPromise) { + return currentPromise; + } + try { + currentPromise = function_.apply(this, arguments_); + return await currentPromise; + } finally { + currentPromise = void 0; + } + }; +}; +var p_debounce_default = pDebounce; + +// src/inject/rpc/utils.ts +var BROADCAST_CHANNEL_ID = "pallad"; +var callPalladAsync = ({ + method, + payload +}) => { + return new Promise((resolve, reject) => { + const privateChannelId = `private-${Math.random()}`; + const channel = new BroadcastChannel(BROADCAST_CHANNEL_ID); + const responseChannel = new BroadcastChannel(privateChannelId); + const messageListener = ({ data }) => { + channel.close(); + const error = data.response?.error; + if (error) { + try { + console.table(JSON.parse(error.message)); + } catch { + console.info(error.message); + } + return reject(error); + } + return resolve(data.response); + }; + responseChannel.addEventListener("message", messageListener); + channel.postMessage({ + method, + payload, + isPallad: true, + respondAt: privateChannelId + }); + return channel.close(); + }); +}; +var debouncedCall = p_debounce_default(callPalladAsync, 300); + +// src/inject/rpc/provider.ts +var _events = mitt_default(); +window.addEventListener("pallad_event", (event) => { + event.stopImmediatePropagation(); + const { detail } = event; + _events.emit(detail.type, detail.data); +}); +var info = { + slug: "pallad", + name: "Pallad", + icon: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzAwIDMwMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE5OF8yNDY5NSkiPgo8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgcng9IjEyIiBmaWxsPSIjRjZDMTc3Ii8+CjxwYXRoIGQ9Ik05MCAyMTRDOTAgMjExLjc5MSA5MS43OTA5IDIxMCA5NCAyMTBIMTM2QzE0My43MzIgMjEwIDE1MCAyMTYuMjY4IDE1MCAyMjRWMjI2QzE1MCAyMzMuNzMyIDE0My43MzIgMjQwIDEzNiAyNDBIMTA0Qzk2LjI2OCAyNDAgOTAgMjMzLjczMiA5MCAyMjZWMjE0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgNjRDOTAgNjEuNzkwOSA5MS43OTA5IDYwIDk0IDYwSDE5NkMyMDMuNzMyIDYwIDIxMCA2Ni4yNjggMjEwIDc0Vjc2QzIxMCA4My43MzIgMjAzLjczMiA5MCAxOTYgOTBIMTA0Qzk2LjI2OCA5MCA5MCA4My43MzIgOTAgNzZWNjRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTRDMjEwIDkxLjc5MDkgMjExLjc5MSA5MCAyMTQgOTBIMjI2QzIzMy43MzIgOTAgMjQwIDk2LjI2OCAyNDAgMTA0VjEzNkMyNDAgMTQzLjczMiAyMzMuNzMyIDE1MCAyMjYgMTUwSDIyNEMyMTYuMjY4IDE1MCAyMTAgMTQzLjczMiAyMTAgMTM2Vjk0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNNjAgOTRDNjAgOTEuNzkwOSA2MS43OTA5IDkwIDY0IDkwSDc2QzgzLjczMiA5MCA5MCA5Ni4yNjggOTAgMTA0VjE5NkM5MCAyMDMuNzMyIDgzLjczMiAyMTAgNzYgMjEwSDc0QzY2LjI2OCAyMTAgNjAgMjAzLjczMiA2MCAxOTZWOTRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0xNTAgMTUwSDE5NkMyMDMuNzMyIDE1MCAyMTAgMTU2LjI2OCAyMTAgMTY0VjE2NkMyMTAgMTczLjczMiAyMDMuNzMyIDE4MCAxOTYgMTgwSDE2NEMxNTYuMjY4IDE4MCAxNTAgMTczLjczMiAxNTAgMTY2VjE1MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCAxNTBIMTk1QzE5NSAxNTAgMjAxIDE1MCAyMDUuNSAxNTQuNUMyMTAgMTU5IDIxMCAxNjUgMjEwIDE2NVYxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIyNSAxNTBDMjI1IDE1MCAyMTkgMTUwIDIxNC41IDE0NS41QzIxMCAxNDEgMjEwIDEzNSAyMTAgMTM1TDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIxMCAxMzVDMjEwIDEzNSAyMTAgMTQxIDIwNS41IDE0NS41QzIwMSAxNTAgMTk1IDE1MCAxOTUgMTUwTDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik05MCA5MEg3NUM3NSA5MCA4MSA5MCA4NS41IDk0LjVDOTAgOTkgOTAgMTA1IDkwIDEwNVY5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDkwTDEwNSA5MEMxMDUgOTAgOTkgOTAgOTQuNSA4NS41QzkwIDgxIDkwIDc1IDkwIDc1TDkwIDkwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgOTBMOTAgMTA1QzkwIDEwNSA5MCA5OSA5NC41IDk0LjVDOTkgOTAgMTA1IDkwIDEwNSA5MEw5MCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCA5MEgxOTVDMTk1IDkwIDIwMSA5MCAyMDUuNSA5NC41QzIxMCA5OSAyMTAgMTA1IDIxMCAxMDVWOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDc1QzIxMCA3NSAyMTAgODEgMjA1LjUgODUuNUMyMDEgOTAgMTk1IDkwIDE5NSA5MEwyMTAgOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDEwNUMyMTAgMTA1IDIxMCA5OSAyMTQuNSA5NC41QzIxOSA5MCAyMjUgOTAgMjI1IDkwTDIxMCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDIxMEwxMDUgMjEwQzEwNSAyMTAgOTkgMjEwIDk0LjUgMjA1LjVDOTAgMjAxIDkwIDE5NSA5MCAxOTVMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDE5NUM5MCAxOTUgOTAgMjAxIDg1LjUgMjA1LjVDODEgMjEwIDc1IDIxMCA3NSAyMTBMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDIyNUM5MCAyMjUgOTAgMjE5IDk0LjUgMjE0LjVDOTkgMjEwIDEwNSAyMTAgMTA1IDIxMEw5MCAyMTBaIiBmaWxsPSIjMjUyMzNBIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTk4XzI0Njk1Ij4KPHJlY3Qgd2lkdGg9IjMwMCIgaGVpZ2h0PSIzMDAiIHJ4PSI0MCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K", + rdns: "co.pallad" +}; +var provider = { + _events, + request: async ({ method, params }) => await debouncedCall({ + method, + payload: { ...params, origin: window.location.origin } + }), + isPallad: true, + on: _events.on, + off: _events.off +}; + +// src/inject/rpc.ts +var init = () => { + ; + window.mina = provider; + const announceProvider = () => { + window.dispatchEvent( + new CustomEvent("mina:announceProvider", { + detail: Object.freeze({ info, provider }) + }) + ); + }; + window.addEventListener("mina:requestProvider", () => { + announceProvider(); + }); + announceProvider(); + console.info("[Pallad] RPC has been initialized."); +}; +init(); diff --git a/apps/extension/src/background/handlers/index.ts b/apps/extension/src/background/handlers/index.ts new file mode 100644 index 00000000..93918b67 --- /dev/null +++ b/apps/extension/src/background/handlers/index.ts @@ -0,0 +1,10 @@ +import type { OnMessageCallback } from "webext-bridge" + +export type Handler = OnMessageCallback +export * from "./web-provider" +export * from "./wallet" + +export const opts = { + projectId: "test", + chains: ["Mainnet"], +} diff --git a/apps/extension/src/background/handlers/wallet.ts b/apps/extension/src/background/handlers/wallet.ts new file mode 100644 index 00000000..503eb9f7 --- /dev/null +++ b/apps/extension/src/background/handlers/wallet.ts @@ -0,0 +1,48 @@ +import { useVault } from "@palladxyz/vault" +import type { NetworkName } from "@palladxyz/vault" +import { MinaProvider } from "@palladxyz/web-provider" +import { serializeError } from "serialize-error" +import type { Handler } from "./" + +export const palladSidePanel: Handler = async ({ sender }) => { + await chrome.sidePanel.open({ + tabId: sender.tabId, + }) +} + +export const palladSwitchNetwork: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + await useVault.persist.rehydrate() + const { switchNetwork, getChainId } = useVault.getState() + const network = data.network as NetworkName + await switchNetwork(network) + await useVault.persist.rehydrate() + const chainId = await getChainId() + provider.emit("pallad_event", { + data: { + chainId: chainId, + }, + type: "chainChanged", + }) + } catch (error) { + return { error: serializeError(error) } + } +} + +export const palladConnected: Handler = async () => { + try { + const provider = await MinaProvider.getInstance() + await useVault.persist.rehydrate() + const { getChainId } = useVault.getState() + const chainId = await getChainId() + provider.emit("pallad_event", { + data: { + chainId: chainId, + }, + type: "connect", + }) + } catch (error) { + return { error: serializeError(error) } + } +} diff --git a/apps/extension/src/background/handlers/web-provider.ts b/apps/extension/src/background/handlers/web-provider.ts new file mode 100644 index 00000000..73fae151 --- /dev/null +++ b/apps/extension/src/background/handlers/web-provider.ts @@ -0,0 +1,180 @@ +import { serializeError } from "serialize-error" +import type { Handler } from "." +import { + MinaProvider, + Validation, +} from "../../../../../packages/web-provider/src" + +export const minaSetState: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.setStateRequestSchema.parse(data) + return await provider.request({ + method: "mina_setState", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaAddChain = async () => { + return { error: serializeError(new Error("4200 - Unsupported Method")) } +} + +export const minaRequestNetwork: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.requestSchema.parse(data) + return await provider.request({ + method: "mina_requestNetwork", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaSwitchChain = async () => { + return { error: serializeError(new Error("4200 - Unsupported Method")) } +} + +export const minaGetState: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.getStateRequestSchema.parse(data) + return await provider.request({ + method: "mina_getState", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaChainId: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.requestSchema.parse(data) + return await provider.request({ + method: "mina_chainId", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaAccounts: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.requestSchema.parse(data) + return await provider.request({ + method: "mina_accounts", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaRequestAccounts: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.requestSchema.parse(data) + return await provider.request({ + method: "mina_accounts", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaSign: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.signMessageRequestSchema.parse(data) + return await provider.request({ + method: "mina_sign", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaSignFields: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.signFieldsRequestSchema.parse(data) + return await provider.request({ + method: "mina_signFields", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaSignTransaction: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.signTransactionRequestSchema.parse(data) + return await provider.request({ + method: "mina_signTransaction", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaGetBalance: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.requestSchema.parse(data) + return await provider.request({ + method: "mina_getBalance", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaCreateNullifier: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.createNullifierRequestSchema.parse(data) + return await provider.request({ + method: "mina_createNullifier", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const minaSendTransaction: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const params = Validation.sendTransactionRequestSchema.parse(data) + return await provider.request({ + method: "mina_sendTransaction", + params, + }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} + +export const palladIsConnected: Handler = async ({ data }) => { + try { + const provider = await MinaProvider.getInstance() + const { origin } = Validation.requestSchema.parse(data) + return await provider.isConnected({ origin }) + } catch (error: unknown) { + return { error: serializeError(error) } + } +} diff --git a/apps/extension/src/background/index.ts b/apps/extension/src/background/index.ts index 39c1c46c..876a11c7 100644 --- a/apps/extension/src/background/index.ts +++ b/apps/extension/src/background/index.ts @@ -1,207 +1,60 @@ -import { MinaProvider, Validation } from "@palladxyz/web-provider" -import { serializeError } from "serialize-error" -import { onMessage } from "webext-bridge/background" +import { MinaProvider } from "@palladxyz/web-provider" +import { onMessage, sendMessage } from "webext-bridge/background" import { runtime, tabs } from "webextension-polyfill" +import { + minaAccounts, + minaAddChain, + minaChainId, + minaCreateNullifier, + minaGetBalance, + minaGetState, + minaRequestAccounts, + minaRequestNetwork, + minaSendTransaction, + minaSetState, + minaSign, + minaSignFields, + minaSignTransaction, + minaSwitchChain, + palladConnected, + palladIsConnected, + palladSidePanel, + palladSwitchNetwork, +} from "./handlers" const E2E_TESTING = import.meta.env.VITE_APP_E2E === "true" -const opts = { - projectId: "test", - chains: ["Mina - Mainnet"], -} - -onMessage("enable", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const { origin } = Validation.requestSchema.parse(data) - return await provider.enable({ origin }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_setState", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.setStateRequestSchema.parse(data) - return await provider.request({ - method: "mina_setState", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_addChain", async () => { - return { error: serializeError(new Error("4200 - Unsupported Method")) } -}) - -onMessage("mina_requestNetwork", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.requestSchema.parse(data) - return await provider.request({ - method: "mina_requestNetwork", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_switchChain", async () => { - return { error: serializeError(new Error("4200 - Unsupported Method")) } -}) - -onMessage("mina_getState", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.getStateRequestSchema.parse(data) - return await provider.request({ - method: "mina_getState", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("isConnected", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const { origin } = Validation.requestSchema.parse(data) - return await provider.isConnected({ origin }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_chainId", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.requestSchema.parse(data) - return await provider.request({ - method: "mina_chainId", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_accounts", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.requestSchema.parse(data) - return await provider.request({ - method: "mina_accounts", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - +/** + * Web Connector handlers + */ +onMessage("mina_setState", minaSetState) +onMessage("mina_addChain", minaAddChain) +onMessage("mina_requestNetwork", minaRequestNetwork) +onMessage("mina_switchChain", minaSwitchChain) +onMessage("mina_getState", minaGetState) +onMessage("mina_chainId", minaChainId) +onMessage("mina_accounts", minaAccounts) // TODO: It should be removed, but let's keep it for now for Auro compatibility. -onMessage("mina_requestAccounts", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.requestSchema.parse(data) - return await provider.request({ - method: "mina_accounts", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_sign", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.signMessageRequestSchema.parse(data) - return await provider.request({ - method: "mina_sign", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_signFields", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.signFieldsRequestSchema.parse(data) - return await provider.request({ - method: "mina_signFields", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_signTransaction", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.signTransactionRequestSchema.parse(data) - return await provider.request({ - method: "mina_signTransaction", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_getBalance", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.requestSchema.parse(data) - return await provider.request({ - method: "mina_getBalance", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_createNullifier", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.createNullifierRequestSchema.parse(data) - return await provider.request({ - method: "mina_createNullifier", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("mina_sendTransaction", async ({ data }) => { - try { - const provider = await MinaProvider.init(opts, []) - const params = Validation.sendTransactionRequestSchema.parse(data) - return await provider.request({ - method: "mina_sendTransaction", - params, - }) - } catch (error: unknown) { - return { error: serializeError(error) } - } -}) - -onMessage("pallad_sidePanel", async ({ sender }) => { - await chrome.sidePanel.open({ - tabId: sender.tabId, - }) -}) - -runtime.onConnect.addListener((port) => { +onMessage("mina_requestAccounts", minaRequestAccounts) +onMessage("mina_sign", minaSign) +onMessage("mina_signFields", minaSignFields) +onMessage("mina_signTransaction", minaSignTransaction) +onMessage("mina_getBalance", minaGetBalance) +onMessage("mina_createNullifier", minaCreateNullifier) +onMessage("mina_sendTransaction", minaSendTransaction) + +/** + * Wallet handlers + */ +onMessage("pallad_isConnected", palladIsConnected) +onMessage("pallad_switchNetwork", palladSwitchNetwork) +onMessage("pallad_connected", palladConnected) +onMessage("pallad_sidePanel", palladSidePanel) + +/** + * Runtime + */ +runtime.onConnect.addListener(async (port) => { if (port.name === "prompt") { port.onDisconnect.addListener(async () => { await chrome.sidePanel.setOptions({ @@ -211,11 +64,21 @@ runtime.onConnect.addListener((port) => { }) } }) - runtime.onInstalled.addListener(async ({ reason }) => { + const provider = await MinaProvider.getInstance() await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }) if (reason === "install") { if (!E2E_TESTING) await tabs.create({ url: "https://get.pallad.co/welcome" }) } + provider.on("pallad_event", async (data) => { + const { permissions } = await chrome.storage.local.get("permissions") + const urls = Object.entries(permissions) + .filter(([_, allowed]) => allowed === "ALLOWED") + .map(([url]) => `${url}/*`) + const allowedTabs = await tabs.query({ url: urls }) + for (const tab of allowedTabs) { + await sendMessage("pallad_event", data, `content-script@${tab.id}`) + } + }) }) diff --git a/apps/extension/src/inject/index.ts b/apps/extension/src/inject/index.ts index f87d8ee7..341b36e0 100644 --- a/apps/extension/src/inject/index.ts +++ b/apps/extension/src/inject/index.ts @@ -1,14 +1,20 @@ import { deserializeError } from "serialize-error" -import { sendMessage } from "webext-bridge/content-script" +import { onMessage, sendMessage } from "webext-bridge/content-script" import { runtime } from "webextension-polyfill" +onMessage("pallad_event", async ({ data }) => { + const palladEvent = new CustomEvent("pallad_event", { + detail: data, + }) + window.dispatchEvent(palladEvent) +}) + const inject = () => { if (typeof document === "undefined") return const script = document.createElement("script") - script.src = runtime.getURL("/pallad_rpc.js") + script.src = runtime.getURL("/rpc.js") script.type = "module" document.documentElement.appendChild(script) - console.info("[Pallad] RPC has been initialized.") const channel = new BroadcastChannel("pallad") channel.addEventListener("message", async ({ data }) => { const origin = window.location.origin diff --git a/apps/extension/src/inject/rpc.ts b/apps/extension/src/inject/rpc.ts new file mode 100644 index 00000000..2679352d --- /dev/null +++ b/apps/extension/src/inject/rpc.ts @@ -0,0 +1,19 @@ +import { info, provider } from "./rpc/provider" + +const init = () => { + ;(window as any).mina = provider + const announceProvider = () => { + window.dispatchEvent( + new CustomEvent("mina:announceProvider", { + detail: Object.freeze({ info, provider }), + }), + ) + } + window.addEventListener("mina:requestProvider", () => { + announceProvider() + }) + announceProvider() + console.info("[Pallad] RPC has been initialized.") +} + +init() diff --git a/apps/extension/src/inject/rpc/provider.ts b/apps/extension/src/inject/rpc/provider.ts new file mode 100644 index 00000000..bd67f27c --- /dev/null +++ b/apps/extension/src/inject/rpc/provider.ts @@ -0,0 +1,34 @@ +import mitt from "mitt" +import { debouncedCall } from "./utils" + +type EmitterEvents = { + connect: { chainId: string } + chainChanged: { chainId: string } +} + +const _events = mitt() + +window.addEventListener("pallad_event", (event) => { + event.stopImmediatePropagation() + const { detail } = event as CustomEvent + _events.emit(detail.type, detail.data) +}) + +export const info = { + slug: "pallad", + name: "Pallad", + icon: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzAwIDMwMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE5OF8yNDY5NSkiPgo8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgcng9IjEyIiBmaWxsPSIjRjZDMTc3Ii8+CjxwYXRoIGQ9Ik05MCAyMTRDOTAgMjExLjc5MSA5MS43OTA5IDIxMCA5NCAyMTBIMTM2QzE0My43MzIgMjEwIDE1MCAyMTYuMjY4IDE1MCAyMjRWMjI2QzE1MCAyMzMuNzMyIDE0My43MzIgMjQwIDEzNiAyNDBIMTA0Qzk2LjI2OCAyNDAgOTAgMjMzLjczMiA5MCAyMjZWMjE0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgNjRDOTAgNjEuNzkwOSA5MS43OTA5IDYwIDk0IDYwSDE5NkMyMDMuNzMyIDYwIDIxMCA2Ni4yNjggMjEwIDc0Vjc2QzIxMCA4My43MzIgMjAzLjczMiA5MCAxOTYgOTBIMTA0Qzk2LjI2OCA5MCA5MCA4My43MzIgOTAgNzZWNjRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTRDMjEwIDkxLjc5MDkgMjExLjc5MSA5MCAyMTQgOTBIMjI2QzIzMy43MzIgOTAgMjQwIDk2LjI2OCAyNDAgMTA0VjEzNkMyNDAgMTQzLjczMiAyMzMuNzMyIDE1MCAyMjYgMTUwSDIyNEMyMTYuMjY4IDE1MCAyMTAgMTQzLjczMiAyMTAgMTM2Vjk0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNNjAgOTRDNjAgOTEuNzkwOSA2MS43OTA5IDkwIDY0IDkwSDc2QzgzLjczMiA5MCA5MCA5Ni4yNjggOTAgMTA0VjE5NkM5MCAyMDMuNzMyIDgzLjczMiAyMTAgNzYgMjEwSDc0QzY2LjI2OCAyMTAgNjAgMjAzLjczMiA2MCAxOTZWOTRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0xNTAgMTUwSDE5NkMyMDMuNzMyIDE1MCAyMTAgMTU2LjI2OCAyMTAgMTY0VjE2NkMyMTAgMTczLjczMiAyMDMuNzMyIDE4MCAxOTYgMTgwSDE2NEMxNTYuMjY4IDE4MCAxNTAgMTczLjczMiAxNTAgMTY2VjE1MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCAxNTBIMTk1QzE5NSAxNTAgMjAxIDE1MCAyMDUuNSAxNTQuNUMyMTAgMTU5IDIxMCAxNjUgMjEwIDE2NVYxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIyNSAxNTBDMjI1IDE1MCAyMTkgMTUwIDIxNC41IDE0NS41QzIxMCAxNDEgMjEwIDEzNSAyMTAgMTM1TDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIxMCAxMzVDMjEwIDEzNSAyMTAgMTQxIDIwNS41IDE0NS41QzIwMSAxNTAgMTk1IDE1MCAxOTUgMTUwTDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik05MCA5MEg3NUM3NSA5MCA4MSA5MCA4NS41IDk0LjVDOTAgOTkgOTAgMTA1IDkwIDEwNVY5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDkwTDEwNSA5MEMxMDUgOTAgOTkgOTAgOTQuNSA4NS41QzkwIDgxIDkwIDc1IDkwIDc1TDkwIDkwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgOTBMOTAgMTA1QzkwIDEwNSA5MCA5OSA5NC41IDk0LjVDOTkgOTAgMTA1IDkwIDEwNSA5MEw5MCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCA5MEgxOTVDMTk1IDkwIDIwMSA5MCAyMDUuNSA5NC41QzIxMCA5OSAyMTAgMTA1IDIxMCAxMDVWOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDc1QzIxMCA3NSAyMTAgODEgMjA1LjUgODUuNUMyMDEgOTAgMTk1IDkwIDE5NSA5MEwyMTAgOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDEwNUMyMTAgMTA1IDIxMCA5OSAyMTQuNSA5NC41QzIxOSA5MCAyMjUgOTAgMjI1IDkwTDIxMCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDIxMEwxMDUgMjEwQzEwNSAyMTAgOTkgMjEwIDk0LjUgMjA1LjVDOTAgMjAxIDkwIDE5NSA5MCAxOTVMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDE5NUM5MCAxOTUgOTAgMjAxIDg1LjUgMjA1LjVDODEgMjEwIDc1IDIxMCA3NSAyMTBMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDIyNUM5MCAyMjUgOTAgMjE5IDk0LjUgMjE0LjVDOTkgMjEwIDEwNSAyMTAgMTA1IDIxMEw5MCAyMTBaIiBmaWxsPSIjMjUyMzNBIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTk4XzI0Njk1Ij4KPHJlY3Qgd2lkdGg9IjMwMCIgaGVpZ2h0PSIzMDAiIHJ4PSI0MCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K", + rdns: "co.pallad", +} + +export const provider = { + _events, + request: async ({ method, params }: { method: string; params: any }) => + await debouncedCall({ + method, + payload: { ...params, origin: window.location.origin }, + }), + isPallad: true, + on: _events.on, + off: _events.off, +} diff --git a/apps/extension/src/inject/rpc/utils.ts b/apps/extension/src/inject/rpc/utils.ts new file mode 100644 index 00000000..5a5f1f5e --- /dev/null +++ b/apps/extension/src/inject/rpc/utils.ts @@ -0,0 +1,37 @@ +import debounce from "p-debounce" + +const BROADCAST_CHANNEL_ID = "pallad" + +const callPalladAsync = ({ + method, + payload, +}: { method: string; payload: any }) => { + return new Promise((resolve, reject) => { + const privateChannelId = `private-${Math.random()}` + const channel = new BroadcastChannel(BROADCAST_CHANNEL_ID) + const responseChannel = new BroadcastChannel(privateChannelId) + const messageListener = ({ data }: any) => { + channel.close() + const error = data.response?.error + if (error) { + try { + console.table(JSON.parse(error.message)) + } catch { + console.info(error.message) + } + return reject(error) + } + return resolve(data.response) + } + responseChannel.addEventListener("message", messageListener) + channel.postMessage({ + method, + payload, + isPallad: true, + respondAt: privateChannelId, + }) + return channel.close() + }) +} + +export const debouncedCall = debounce(callPalladAsync, 300) diff --git a/apps/extension/tsup.config.ts b/apps/extension/tsup.config.ts new file mode 100644 index 00000000..11563ded --- /dev/null +++ b/apps/extension/tsup.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsup" + +export default defineConfig({ + entry: ["./src/inject/rpc.ts"], + outDir: "public", + format: "esm", + noExternal: [/(.*)/], + splitting: false, + bundle: true, +}) diff --git a/biome.json b/biome.json index 786fdbcb..9484c218 100644 --- a/biome.json +++ b/biome.json @@ -7,6 +7,7 @@ "ignore": [ "apps/**/playwright-report/**/*", "apps/**/dist/*.js", + "apps/**/public/rpc.js", "apps/**/dist/*.ts", "apps/**/dist/*.json", "packages/**/build/**/*", diff --git a/packages/features/package.json b/packages/features/package.json index e9721f38..892d798e 100644 --- a/packages/features/package.json +++ b/packages/features/package.json @@ -74,7 +74,7 @@ "tailwindcss-animate": "1.0.7", "webext-bridge": "6.0.1", "webextension-polyfill": "0.12.0", - "xss": "^1.0.15", + "xss": "1.0.15", "yaml": "2.5.0", "zod": "3.23.8", "zustand": "4.5.4" diff --git a/packages/features/src/common/hooks/use-account.ts b/packages/features/src/common/hooks/use-account.ts index eadb5a0f..523a0ac4 100644 --- a/packages/features/src/common/hooks/use-account.ts +++ b/packages/features/src/common/hooks/use-account.ts @@ -65,10 +65,11 @@ export const useAccount = () => { } return { ...swr, + fetchWallet, minaBalance, gradientBackground, copyWalletAddress, - accountInfo: currentWallet.accountInfo, + currentWallet, publicKey, lockWallet, restartCurrentWallet, diff --git a/packages/features/src/common/store/app.ts b/packages/features/src/common/store/app.ts index 819021eb..84874172 100644 --- a/packages/features/src/common/store/app.ts +++ b/packages/features/src/common/store/app.ts @@ -41,8 +41,16 @@ export const useAppStore = create()( setVaultState(vaultState) { return set({ vaultState }) }, - setVaultStateInitialized: () => { + setVaultStateInitialized: async () => { const { setVaultState } = get() + const { id } = chrome.runtime + const { permissions } = await chrome.storage.local.get("permissions") + await chrome.storage.local.set({ + permissions: { + ...permissions, + [`chrome-extension://${id}`]: "ALLOWED", + }, + }) return setVaultState(VaultState.INITIALIZED) }, setVaultStateUninitialized: () => { @@ -54,6 +62,7 @@ export const useAppStore = create()( }, }), { + // Do not change this, may break Web Connector if not updated in Mina Provider. name: "PalladApp", storage: createJSONStorage(() => localPersistence), }, diff --git a/packages/features/src/components/address-dropdown.tsx b/packages/features/src/components/address-dropdown.tsx index 89983282..51d2074d 100644 --- a/packages/features/src/components/address-dropdown.tsx +++ b/packages/features/src/components/address-dropdown.tsx @@ -1,6 +1,7 @@ import { truncateString } from "@/common/lib/string" import { useVault } from "@palladxyz/vault" import clsx from "clsx" +import { CopyIcon, ExternalLinkIcon, UserPlusIcon } from "lucide-react" import { Link } from "react-router-dom" import { toast } from "sonner" @@ -52,18 +53,25 @@ export const AddressDropdown = ({
  • -
  • -
  • - - Create Contact + + + Create Contact
diff --git a/packages/features/src/components/fee-picker.tsx b/packages/features/src/components/fee-picker.tsx index f95150ee..bb6cede2 100644 --- a/packages/features/src/components/fee-picker.tsx +++ b/packages/features/src/components/fee-picker.tsx @@ -33,7 +33,7 @@ export const FeePicker = ({ feeValue, setValue }: FeePickerProps) => {
  • -
  • diff --git a/packages/features/src/onboarding/routes/seed-backup-confirmation.tsx b/packages/features/src/onboarding/routes/seed-backup-confirmation.tsx index 420d5809..a49f75e5 100644 --- a/packages/features/src/onboarding/routes/seed-backup-confirmation.tsx +++ b/packages/features/src/onboarding/routes/seed-backup-confirmation.tsx @@ -69,7 +69,7 @@ export const SeedBackupConfirmationRoute = () => { "Test", // TODO: make this a configurable credential name or random if not provided ) mixpanel.track("WalletCreated") - setVaultStateInitialized() + await setVaultStateInitialized() return navigate("/onboarding/finish") } finally { setRestoring(false) diff --git a/packages/features/src/onboarding/routes/seed-import.tsx b/packages/features/src/onboarding/routes/seed-import.tsx index 0ffd6d88..46d17aa6 100644 --- a/packages/features/src/onboarding/routes/seed-import.tsx +++ b/packages/features/src/onboarding/routes/seed-import.tsx @@ -61,7 +61,7 @@ export const SeedImportRoute = () => { "Test", // TODO: make this a configurable credential name or random if not provided ) mixpanel.track("WalletRestored") - setVaultStateInitialized() + await setVaultStateInitialized() return navigate("/onboarding/finish") } finally { setRestoring(false) diff --git a/packages/features/src/receive/routes/receive.tsx b/packages/features/src/receive/routes/receive.tsx index d223b0fe..bbd79bd7 100644 --- a/packages/features/src/receive/routes/receive.tsx +++ b/packages/features/src/receive/routes/receive.tsx @@ -6,10 +6,11 @@ import { ReceiveView } from "../views/receive" export const ReceiveRoute = () => { const navigate = useNavigate() - const { copyWalletAddress, publicKey } = useAccount() + const { copyWalletAddress, publicKey, currentWallet } = useAccount() return ( navigate(-1)} /> diff --git a/packages/features/src/receive/views/receive.tsx b/packages/features/src/receive/views/receive.tsx index e540941d..663735bb 100644 --- a/packages/features/src/receive/views/receive.tsx +++ b/packages/features/src/receive/views/receive.tsx @@ -5,12 +5,14 @@ import { MenuBar } from "@/components/menu-bar" type ReceiveViewProps = { publicKey: string + walletName: string onGoBack: () => void onCopyWalletAddress: () => void } export const ReceiveView = ({ publicKey, + walletName, onGoBack, onCopyWalletAddress, }: ReceiveViewProps) => ( @@ -27,7 +29,7 @@ export const ReceiveView = ({ className="relative w-[140px] h-[140px]" />
    -

    Personal

    +

    {walletName}

    {publicKey}

    )} {Object.values(txsGrouped).map((txs) => ( diff --git a/packages/features/src/wallet/routes/networks.tsx b/packages/features/src/wallet/routes/networks.tsx index 213aeb8b..166cc96e 100644 --- a/packages/features/src/wallet/routes/networks.tsx +++ b/packages/features/src/wallet/routes/networks.tsx @@ -1,13 +1,17 @@ +import { useAccount } from "@/common/hooks/use-account" import { useVault } from "@palladxyz/vault" import { useNavigate } from "react-router-dom" +import { sendMessage } from "webext-bridge/popup" import { NetworksView } from "../views/networks" export const NetworksRoute = () => { const navigate = useNavigate() - const switchNetwork = useVault((state) => state.switchNetwork) const currentNetworkName = useVault((state) => state.currentNetworkName) + const { fetchWallet } = useAccount() const onNetworkSwitch = async (network: string) => { - await switchNetwork(network) + await sendMessage("pallad_switchNetwork", { network }) + await useVault.persist.rehydrate() + await fetchWallet() navigate("/dashboard") } return ( diff --git a/packages/features/src/web-connector/routes/web-connector.tsx b/packages/features/src/web-connector/routes/web-connector.tsx index c3bffd55..4dd2e6b7 100644 --- a/packages/features/src/web-connector/routes/web-connector.tsx +++ b/packages/features/src/web-connector/routes/web-connector.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react" import type { SubmitHandler } from "react-hook-form" import { MemoryRouter } from "react-router-dom" +import { sendMessage } from "webext-bridge/popup" import { runtime, windows } from "webextension-polyfill" import xss from "xss" import yaml from "yaml" @@ -18,6 +19,7 @@ type ActionRequest = { payload: string inputType: "text" | "password" | "confirmation" loading: boolean + emitConnected: boolean } export const WebConnectorRoute = () => { @@ -26,6 +28,7 @@ export const WebConnectorRoute = () => { payload: "", inputType: "confirmation", loading: true, + emitConnected: false, }) const onSubmit: SubmitHandler = async ({ userInput }) => { const { id } = await windows.getCurrent() @@ -41,6 +44,9 @@ export const WebConnectorRoute = () => { userConfirmed: true, windowId: id, }) + if (request.emitConnected) { + await sendMessage("pallad_connected", {}, "background") + } window.close() } const decline = async () => { @@ -67,6 +73,7 @@ export const WebConnectorRoute = () => { payload: await sanitizePayload(message.params.payload), inputType: message.params.inputType, loading: false, + emitConnected: message.params.emitConnected, }) } }) diff --git a/packages/vault/package.json b/packages/vault/package.json index 4a507fab..c6df3d23 100644 --- a/packages/vault/package.json +++ b/packages/vault/package.json @@ -24,7 +24,7 @@ "@palladxyz/pallad-core": "workspace:*", "@palladxyz/providers": "workspace:*", "@palladxyz/util": "workspace:*", - "@plasmohq/storage": "^1.11.0", + "@plasmohq/storage": "1.11.0", "bs58check": "4.0.0", "buffer": "6.0.3", "dayjs": "1.11.12", diff --git a/packages/vault/src/vault/utils/get-current-wallet.ts b/packages/vault/src/vault/utils/get-current-wallet.ts index 86837e32..abf5e816 100644 --- a/packages/vault/src/vault/utils/get-current-wallet.ts +++ b/packages/vault/src/vault/utils/get-current-wallet.ts @@ -33,12 +33,15 @@ export function getCurrentWalletHelper(get: any) { const credential = getCredential(credentialName) const publicKey = getPublicKey(credential) const providerConfig = getCurrentNetworkInfo() - const accountsInfo = getAccountsInfo(providerConfig.networkName, publicKey) + const { accountInfo, transactions } = getAccountsInfo( + providerConfig.networkName, + publicKey, + ) return { singleKeyAgentState, credential, - accountInfo: accountsInfo.accountInfo, - transactions: accountsInfo.transactions, + accountInfo, + transactions, } } diff --git a/packages/web-provider/package.json b/packages/web-provider/package.json index 72d55976..22482d57 100644 --- a/packages/web-provider/package.json +++ b/packages/web-provider/package.json @@ -24,8 +24,9 @@ "@palladxyz/providers": "workspace:*", "@palladxyz/vault": "workspace:*", "dayjs": "1.11.12", - "eventemitter3": "5.0.1", + "eventemitter3": "^5.0.1", "mina-signer": "3.0.7", + "mitt": "3.0.1", "superjson": "2.2.1", "webext-bridge": "6.0.1", "webextension-polyfill": "0.12.0", diff --git a/packages/web-provider/src/mina-network/mina-provider.ts b/packages/web-provider/src/mina-network/mina-provider.ts index f08566f5..70e44431 100644 --- a/packages/web-provider/src/mina-network/mina-provider.ts +++ b/packages/web-provider/src/mina-network/mina-provider.ts @@ -13,24 +13,7 @@ import EventEmitter from "eventemitter3" import type { Validation } from "." import { showUserPrompt } from "../utils/prompts" import { type VaultService, vaultService } from "../vault-service" -import type { - ChainProviderOptions, - ChainRpcConfig, - ConnectOps, - ProviderConnectInfo, - RequestArguments, -} from "../web-provider-types" -import { - OPTIONAL_EVENTS, - OPTIONAL_METHODS, - REQUIRED_EVENTS, - REQUIRED_METHODS, -} from "./constants/rpc" -import type { - IMinaProviderBase, - IMinaProviderEvents, - MinaRpcProviderMap, -} from "./types" +import type { ConnectOps, RequestArguments } from "../web-provider-types" import { serializeField, serializeGroup, serializeTransaction } from "./utils" export type RpcMethod = @@ -44,90 +27,61 @@ export type RpcMethod = | "mina_getState" | "mina_setState" -export interface IMinaProvider extends IMinaProviderBase { - connect(opts?: ConnectOps | undefined): void -} - -export function getRpcUrl(chainId: string, rpc: ChainRpcConfig) { - let rpcUrl: string | undefined - if (rpc.rpcMap) { - rpcUrl = rpc.rpcMap[getMinaChainId([chainId])] - } - return rpcUrl -} - export function getMinaChainId(chains: string[]) { return Number(chains[0]?.split(":")[1]) } -export class MinaProvider implements IMinaProvider { - public events = new EventEmitter() +export class MinaProvider extends EventEmitter { + private static instance: MinaProvider public accounts: string[] = [] public chainId: string | undefined = undefined - public rpcProviders: MinaRpcProviderMap = {} - private vault: VaultService + private _vault: VaultService private userPrompt: typeof showUserPrompt - protected rpc: ChainRpcConfig - - constructor( - opts: ChainProviderOptions, - private externalEmitter?: EventEmitter, - ) { - // Initialization logic - this.rpc = {} as ChainRpcConfig - this.vault = vaultService - - // Use provided userPrompt function or default to the actual implementation - if (opts.showUserPrompt) { - this.userPrompt = opts.showUserPrompt - } else { - // Default to the actual implementation - this.userPrompt = showUserPrompt - } + constructor() { + super() + this._vault = vaultService + this.userPrompt = showUserPrompt } - static async init( - opts: ChainProviderOptions, - authorizedMethods: string[] = REQUIRED_METHODS, - externalEmitter: EventEmitter = new EventEmitter(), - ) { - const provider = new MinaProvider(opts, externalEmitter) - await provider.initialize(opts, authorizedMethods) + static async init() { + const provider = new MinaProvider() + await provider.initialize() return provider } - private async initialize( - opts: ChainProviderOptions, - unauthorizedMethods: string[] = [], - ) { - this.rpc = { - chains: opts.chains || [], - optionalChains: opts.optionalChains || [], - rpcMap: opts.rpcMap || {}, - projectId: opts.projectId, - methods: REQUIRED_METHODS, // Use required methods from constants - optionalMethods: OPTIONAL_METHODS, // Use optional methods from constants - unauthorizedMethods: unauthorizedMethods, - events: REQUIRED_EVENTS, // Use required events from constants - optionalEvents: OPTIONAL_EVENTS, // Use optional events from constants + public static async getInstance() { + if (!MinaProvider.instance) { + MinaProvider.instance = await MinaProvider.init() } + return MinaProvider.instance + } - this.chainId = await this.vault.getChainId() + private async initialize() { + this.chainId = await this._vault.getChainId() } async unlockWallet() { - const passphrase = await this.userPrompt("password", { - title: "Unlock your wallet", + const passphrase = await this.userPrompt({ + inputType: "password", + metadata: { + title: "Unlock your wallet", + }, }) if (passphrase === null) throw this.createProviderRpcError(4100, "Unauthorized") - await this.vault.unlockWallet(passphrase as string) + await this._vault.unlockWallet(passphrase as string) + } + + async verifyInitialized() { + const { PalladApp } = await chrome.storage.local.get("PalladApp") + if (!PalladApp) return false + return !PalladApp.includes("UNINITIALIZED") } async checkAndUnlock() { - const locked = await this.vault.isLocked() + const locked = await this._vault.isLocked() if (locked === true) { await this.unlockWallet() } @@ -142,9 +96,13 @@ export class MinaProvider implements IMinaProvider { public async enable({ origin }: { origin: string }) { // check if wallet is locked first await this.checkAndUnlock() - const userConfirmed = await this.userPrompt("confirmation", { - title: "Connection request.", - payload: JSON.stringify({ origin }), + const userConfirmed = await this.userPrompt({ + inputType: "confirmation", + metadata: { + title: "Connection request.", + payload: JSON.stringify({ origin }), + }, + emitConnected: true, }) if (!userConfirmed) { // should this emit an error event? @@ -152,57 +110,17 @@ export class MinaProvider implements IMinaProvider { } await this.connect({ origin }) // TODO: perform 'mina_requestAccounts' method - return await this.vault.getAccounts() - } - // these are the methods that are called by the dapp to interact/listen to the wallet - public on: IMinaProviderEvents["on"] = (event, listener) => { - if (this.externalEmitter) { - this.externalEmitter.on(event, listener) - } else { - this.events.on(event, listener) - } - return this + return await this._vault.getAccounts() } - - public once: IMinaProviderEvents["once"] = (event, listener) => { - if (this.externalEmitter) { - this.externalEmitter.once(event, listener) - } else { - this.events.once(event, listener) - } - return this - } - - public removeListener: IMinaProviderEvents["removeListener"] = ( - event, - listener, - ) => { - if (this.externalEmitter) { - this.externalEmitter.removeListener(event, listener) - } else { - this.events.removeListener(event, listener) - } - return this - } - - public off: IMinaProviderEvents["off"] = (event, listener) => { - if (this.externalEmitter) { - this.externalEmitter.off(event, listener) - } else { - this.events.off(event, listener) - } - return this - } - public async connect(opts: ConnectOps) { try { // Step 1: Check if already connected. - if (await this.vault.getEnabled({ origin: opts.origin })) + if (await this._vault.getEnabled({ origin: opts.origin })) throw this.createProviderRpcError(4100, "Already enabled.") // Step 2: Attempt to connect to a chain. if (!opts.chains) { // Try to connect to the default chain -- this is actually the current chain the wallet is connected to not the default chain - const defaultChainId = await this.vault.getChainId() + const defaultChainId = await this._vault.getChainId() if (!defaultChainId) { throw this.createProviderRpcError(4100, "Chain ID is undefined.") } @@ -212,70 +130,19 @@ export class MinaProvider implements IMinaProvider { } else { throw this.createProviderRpcError(4901, "Chain Disconnected") } - // Step 4: Set the connected flag. - //this.connected = true // this is redundant because we're setting the connected flag in the next step - await this.vault.setEnabled({ origin: opts.origin }) - // Step 5: Emit a 'connect' event. - const connectInfo: ProviderConnectInfo = { chainId: this.chainId ?? "" } - //this.events.emit('connect', connectInfo) - if (this.externalEmitter) { - this.externalEmitter.emit("connect", connectInfo) - } else { - this.events.emit("connect", connectInfo) - } + await this._vault.setEnabled({ origin: opts.origin }) } catch (error) { // Handle any errors that occurred during connection. console.error("Error during connection:", error) // Additional error handling as needed } } - public requestAccounts() { return this.accounts } - public async isConnected({ origin }: { origin: string }) { - return await this.vault.getEnabled({ origin }) + return await this._vault.getEnabled({ origin }) } - - public disconnect({ origin }: { origin: string }) { - // Check if it's connected in the first place - if (!this.isConnected({ origin })) { - // Emit a 'disconnect' event with an error only if disconnected - if (this.externalEmitter) { - this.externalEmitter.emit( - "disconnect", - this.createProviderRpcError(4900, "Disconnected"), - ) - } else { - this.events.emit( - "disconnect", - this.createProviderRpcError(4900, "Disconnected"), - ) - } - } else { - // If it's connected, then handle the disconnection logic - // For example, disconnect from the Mina client or other cleanup - // ... - - // Update the connected status - // this.connected = false - - // Reset accounts - this.accounts = [] - - // Reset chainId - this.chainId = undefined - - // Emit a 'disconnect' event without an error - if (this.externalEmitter) { - this.externalEmitter.emit("disconnect") - } else { - this.events.emit("disconnect") - } - } - } - private async sign({ signable, operationArgs, @@ -286,48 +153,35 @@ export class MinaProvider implements IMinaProvider { passphrase: string }) { try { - return await this.vault.sign(signable, operationArgs, () => + return await this._vault.sign(signable, operationArgs, () => utf8ToBytes(passphrase), ) } catch (error) { throw this.createProviderRpcError(4100, "Unauthorized") } } - public async request(args: RequestArguments): Promise { // Step 1: Check if request instantiator is in blocked list. const { params } = args const requestOrigin = params.origin if (!requestOrigin) { - throw this.createProviderRpcError( - 4100, - "Unauthorized - Request lacks origin.", - ) + throw this.createProviderRpcError(4100, "Unauthorized") } - if (await this.vault.isBlocked({ origin: requestOrigin })) { - throw this.createProviderRpcError( - 4100, - "Unauthorized - The requested method and/or account has not been authorized for the requests origin by the user.", - ) + if (await this._vault.isBlocked({ origin: requestOrigin })) { + throw this.createProviderRpcError(4100, "Unauthorized") + } + const initialized = await this.verifyInitialized() + if (!initialized) { + throw this.createProviderRpcError(4100, "Unauthorized") } // check if wallet is locked first await this.checkAndUnlock() - if ( - // when the provider is initialized, the rpc methods are set to the required methods - // this can be the set of authorized methods the user has given permission for the zkApp to use - this.rpc.unauthorizedMethods?.includes(args.method) - ) { - throw this.createProviderRpcError( - 4100, - "Unauthorized - The requested method and/or account has not been authorized by the user.", - ) - } - const enabled = await this.vault.getEnabled({ origin: requestOrigin }) + const enabled = await this._vault.getEnabled({ origin: requestOrigin }) if (!enabled) await this.enable({ origin: requestOrigin }) switch (args.method) { case "mina_accounts": - return (await this.vault.getAccounts()) as unknown as T + return (await this._vault.getAccounts()) as unknown as T case "mina_addChain": { throw this.createProviderRpcError(4200, "Unsupported Method") } @@ -336,14 +190,17 @@ export class MinaProvider implements IMinaProvider { } case "mina_requestNetwork": { { - const userConfirmed = await this.userPrompt("confirmation", { - title: "Request to current Mina network information.", - payload: JSON.stringify(params), + const userConfirmed = await this.userPrompt({ + inputType: "confirmation", + metadata: { + title: "Request to current Mina network information.", + payload: JSON.stringify(params), + }, }) if (!userConfirmed) { throw this.createProviderRpcError(4001, "User Rejected Request") } - const requestNetworkResponse = await this.vault.requestNetwork() + const requestNetworkResponse = await this._vault.requestNetwork() return requestNetworkResponse as unknown as T } } @@ -351,9 +208,12 @@ export class MinaProvider implements IMinaProvider { case "mina_createNullifier": case "mina_signFields": case "mina_signTransaction": { - const passphrase = await this.userPrompt("password", { - title: "Signature request", - payload: JSON.stringify(params), + const passphrase = await this.userPrompt({ + inputType: "password", + metadata: { + title: "Signature request", + payload: JSON.stringify(params), + }, }) if (passphrase === null) throw this.createProviderRpcError(4100, "Unauthorized.") @@ -450,14 +310,17 @@ export class MinaProvider implements IMinaProvider { })) as unknown as T } case "mina_getBalance": - return (await this.vault.getBalance()) as unknown as T + return (await this._vault.getBalance()) as unknown as T case "mina_getState": { const { query, props } = params as Validation.GetStateData - const credentials = await this.vault.getState(query as any, props) - const confirmation = await this.userPrompt("confirmation", { - title: "Credential read request", - payload: JSON.stringify({ ...params, credentials }), + const credentials = await this._vault.getState(query as any, props) + const confirmation = await this.userPrompt({ + inputType: "confirmation", + metadata: { + title: "Credential read request", + payload: JSON.stringify({ ...params, credentials }), + }, }) if (!confirmation) { throw this.createProviderRpcError(4001, "User Rejected Request") @@ -466,54 +329,41 @@ export class MinaProvider implements IMinaProvider { } case "mina_setState": { - const confirmation = await this.userPrompt("confirmation", { - title: "Credential write request", - payload: JSON.stringify(params), + const confirmation = await this.userPrompt({ + inputType: "confirmation", + metadata: { + title: "Credential write request", + payload: JSON.stringify(params), + }, }) if (!confirmation) { throw this.createProviderRpcError(4001, "User Rejected Request") } const requestData = params as Validation.SetStateData - await this.vault.setState(requestData as any) + await this._vault.setState(requestData as any) return { success: true } as unknown as T } case "mina_chainId": { - return (await this.vault.getChainId()) as unknown as T + return (await this._vault.getChainId()) as unknown as T } case "mina_sendTransaction": { const requestData = params as Validation.SendTransactionData - const passphrase = await this.userPrompt("password", { - title: "Send transaction request", - payload: JSON.stringify(params), + const passphrase = await this.userPrompt({ + inputType: "password", + metadata: { + title: "Send transaction request", + payload: JSON.stringify(params), + }, }) if (passphrase === null) throw this.createProviderRpcError(4100, "Unauthorized.") - return this.vault.submitTransaction(requestData) as unknown as T + return this._vault.submitTransaction(requestData) as unknown as T } default: throw this.createProviderRpcError(4200, "Unsupported Method") } } - - protected getRpcConfig(ops: ChainProviderOptions) { - return { - chains: ops.chains || [], - optionalChains: ops.optionalChains || [], - rpcMap: ops.rpcMap || {}, - projectId: ops.projectId, - methods: REQUIRED_METHODS, // Use required methods from constants - optionalMethods: OPTIONAL_METHODS, // Use optional methods from constants - unauthorizedMethods: this.rpc.unauthorizedMethods!, - events: REQUIRED_EVENTS, // Use required events from constants - optionalEvents: OPTIONAL_EVENTS, // Use optional events from constants - } - } - - protected getRpcUrl(chainId: string /*, projectId?: string*/) { - const providedRpc = this.rpc.rpcMap?.[chainId] - return providedRpc - } } diff --git a/packages/web-provider/src/mina-network/types.ts b/packages/web-provider/src/mina-network/types.ts index c189f172..697dc4cc 100644 --- a/packages/web-provider/src/mina-network/types.ts +++ b/packages/web-provider/src/mina-network/types.ts @@ -1,96 +1,17 @@ -import type { MinaSignablePayload } from "@palladxyz/key-management" -import type { ProviderConfig } from "@palladxyz/providers" - import type { ProviderAccounts, ProviderChainId, ProviderConnectInfo, - ProviderEvent, - ProviderEventArguments, ProviderMessage, ProviderRpcError, RequestArguments, } from "../web-provider-types" import type { MinaProvider } from "./mina-provider" -export type RequestSignableData = { - data: MinaSignablePayload -} - -export interface OffchainSessionAllowedMethods { - contractAddress: string - method: string -} - -export type RequestOffchainSession = { - data: { - sessionKey: string - expirationTime: string - allowedMethods: OffchainSessionAllowedMethods[] - sessionMerkleRoot: MinaSignablePayload - } -} - -export type RequestSignableTransaction = { - data: { transaction: MinaSignablePayload } -} - -export type RequestDataFields = { - data: { - fields: number[] | bigint[] - } -} - -export type RequestDataNullifier = { - data: { - message: number[] | bigint[] - } -} - -export type RequestAddChain = { - data: ProviderConfig -} - -export type RequestSwitchChain = { - data: { chainId: string } -} - export interface MinaRpcProviderMap { [chainId: string]: IMinaProviderBase } -export interface EIP1102Request extends RequestArguments { - method: "mina_requestAccounts" -} - -// IMinaProviderEvents interface -export interface IMinaProviderEvents { - on: ( - event: E, - listener: (args: ProviderEventArguments[E]) => void, - ) => MinaProvider - - once: ( - event: E, - listener: (args: ProviderEventArguments[E]) => void, - ) => MinaProvider - - off: ( - event: E, - listener: (args: ProviderEventArguments[E]) => void, - ) => MinaProvider - - removeListener: ( - event: E, - listener: (args: ProviderEventArguments[E]) => void, - ) => MinaProvider - - //emit: ( - // event: E, - // payload: ProviderEventArguments[E] - //) => boolean -} - // originally the EIP1193Provider interface export interface IMinaProviderBase { // connection event @@ -120,6 +41,4 @@ export interface IMinaProviderBase { ): MinaProvider // make an Ethereum RPC method call. request(args: RequestArguments): Promise - // legacy alias for EIP-1102 - enable({ origin }: { origin: string }): Promise } diff --git a/packages/web-provider/src/universal-provider/types.ts b/packages/web-provider/src/universal-provider/types.ts index 5e505b65..834949df 100644 --- a/packages/web-provider/src/universal-provider/types.ts +++ b/packages/web-provider/src/universal-provider/types.ts @@ -1,4 +1,4 @@ -import type { EventEmitter } from "eventemitter3" +import type { Emitter } from "mitt" import type { RequestArguments } from "../web-provider-types" @@ -23,7 +23,7 @@ export interface IProvider { } export abstract class IEvents { - public abstract events: EventEmitter + public abstract events: Emitter // events public abstract on(event: string, listener: any): void diff --git a/packages/web-provider/src/utils/prompts.ts b/packages/web-provider/src/utils/prompts.ts index 7e788419..e8a6a408 100644 --- a/packages/web-provider/src/utils/prompts.ts +++ b/packages/web-provider/src/utils/prompts.ts @@ -1,7 +1,15 @@ -export const showUserPrompt = async ( - inputType: "text" | "password" | "confirmation", - metadata: { title: string; payload?: string }, -) => { +type InputType = "text" | "password" | "confirmation" +type Metadata = { title: string; payload?: string } + +export const showUserPrompt = async ({ + inputType, + metadata, + emitConnected = false, +}: { + inputType: InputType + metadata: Metadata + emitConnected?: boolean +}) => { return new Promise((resolve, reject) => { chrome.windows .create({ @@ -38,6 +46,7 @@ export const showUserPrompt = async ( title: metadata.title, payload: metadata.payload ?? "{}", inputType, + emitConnected, }, }) }, 1000) diff --git a/packages/web-provider/src/web-provider-types/data-model.ts b/packages/web-provider/src/web-provider-types/data-model.ts index 57005576..e50461ad 100644 --- a/packages/web-provider/src/web-provider-types/data-model.ts +++ b/packages/web-provider/src/web-provider-types/data-model.ts @@ -41,7 +41,6 @@ export interface ChainRpcConfig { optionalChains: string[] methods: string[] optionalMethods?: string[] - unauthorizedMethods?: string[] /** * @description Events that the wallet MUST support or the connection will be rejected */ diff --git a/packages/web-provider/test/mina/mina-provider.test.ts b/packages/web-provider/test/mina/mina-provider.test.ts index 04f97feb..66ee1823 100644 --- a/packages/web-provider/test/mina/mina-provider.test.ts +++ b/packages/web-provider/test/mina/mina-provider.test.ts @@ -138,7 +138,7 @@ describe.skip("Wallet Provider Test", () => { it("should emit connect event on successful connection when using `enable` method", async () => { // Listen to the connect event const connectListener = vi.fn() - provider.on("connect", connectListener) + provider.emitter.on("connect", connectListener) // Trigger connection const result = await provider.enable({ origin: TEST_ORIGIN }) @@ -308,7 +308,7 @@ describe.skip("Wallet Provider Test", () => { it.skip("should switch the network successfully when requesting the balance of another chain id", async () => { // Listen to the chains changed event const connectListener = vi.fn() - provider.on("chainChanged", connectListener) + provider.emitter.on("chainChanged", connectListener) const switchNetworkRequestArgs: RequestArguments = { method: "mina_getBalance", @@ -363,22 +363,5 @@ describe.skip("Wallet Provider Test", () => { }), ) }) - - it("should emit disconnect event with ProviderRpcError code 4900 when not connected", () => { - // Listen to the disconnect event - const disconnectListener = vi.fn() - provider.on("disconnect", disconnectListener) - - // Call the disconnect method - provider.disconnect({ origin: TEST_ORIGIN }) - - // Assert that the disconnect event was emitted with the correct error - expect(disconnectListener).toHaveBeenCalledWith( - expect.objectContaining({ - code: 4900, - message: "Disconnected", - }), - ) - }) }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 063992ee..9469ee5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: '@palladxyz/key-management': specifier: workspace:* version: link:../../packages/key-management + '@palladxyz/vault': + specifier: workspace:* + version: link:../../packages/vault '@palladxyz/web-provider': specifier: workspace:* version: link:../../packages/web-provider @@ -83,12 +86,15 @@ importers: buffer: specifier: 6.0.3 version: 6.0.3 - debounce: - specifier: ^2.1.0 - version: 2.1.0 + mitt: + specifier: 3.0.1 + version: 3.0.1 next-themes: specifier: 0.3.0 version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + p-debounce: + specifier: 4.0.0 + version: 4.0.0 react: specifier: 18.3.1 version: 18.3.1 @@ -193,13 +199,13 @@ importers: specifier: 3.3.0 version: 3.3.0(vite@5.3.5(@types/node@22.0.0)) vite-plugin-web-extension: - specifier: ^4.1.6 + specifier: 4.1.6 version: 4.1.6(@types/node@22.0.0) web-ext: specifier: 8.2.0 version: 8.2.0 write-json-file: - specifier: ^6.0.0 + specifier: 6.0.0 version: 6.0.0 packages/_template: @@ -336,7 +342,7 @@ importers: specifier: 0.12.0 version: 0.12.0 xss: - specifier: ^1.0.15 + specifier: 1.0.15 version: 1.0.15 yaml: specifier: 2.5.0 @@ -658,7 +664,7 @@ importers: specifier: workspace:* version: link:../util '@plasmohq/storage': - specifier: ^1.11.0 + specifier: 1.11.0 version: 1.11.0(react@18.3.1) bs58check: specifier: 4.0.0 @@ -731,11 +737,14 @@ importers: specifier: 1.11.12 version: 1.11.12 eventemitter3: - specifier: 5.0.1 + specifier: ^5.0.1 version: 5.0.1 mina-signer: specifier: 3.0.7 version: 3.0.7 + mitt: + specifier: 3.0.1 + version: 3.0.1 superjson: specifier: 2.2.1 version: 2.2.1 @@ -3706,10 +3715,6 @@ packages: debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} - debounce@2.1.0: - resolution: {integrity: sha512-OkL3+0pPWCqoBc/nhO9u6TIQNTK44fnBnzuVtJAbp13Naxw9R6u21x+8tVTka87AhDZ3htqZ2pSSsZl9fqL2Wg==} - engines: {node: '>=18'} - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -5856,6 +5861,10 @@ packages: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} + p-debounce@4.0.0: + resolution: {integrity: sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==} + engines: {node: '>=12'} + p-defer@1.0.0: resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} engines: {node: '>=4'} @@ -10900,8 +10909,6 @@ snapshots: debounce@1.2.1: {} - debounce@2.1.0: {} - debug@2.6.9: dependencies: ms: 2.0.0 @@ -13548,6 +13555,8 @@ snapshots: p-cancelable@3.0.0: {} + p-debounce@4.0.0: {} + p-defer@1.0.0: {} p-is-promise@2.1.0: {} From 0bf5e98cd19686e400fda17a0419209979923a1f Mon Sep 17 00:00:00 2001 From: Tomek Marciniak Date: Mon, 5 Aug 2024 09:03:29 +0200 Subject: [PATCH 2/2] chore(web connector): remove rpc dist --- apps/extension/.gitignore | 2 + apps/extension/public/rpc.js | 140 ----------------------------------- 2 files changed, 2 insertions(+), 140 deletions(-) delete mode 100644 apps/extension/public/rpc.js diff --git a/apps/extension/.gitignore b/apps/extension/.gitignore index 4e525c0a..01e02d80 100644 --- a/apps/extension/.gitignore +++ b/apps/extension/.gitignore @@ -1,3 +1,5 @@ +/public/rpc.js + # Logs logs *.log diff --git a/apps/extension/public/rpc.js b/apps/extension/public/rpc.js deleted file mode 100644 index 5c105f12..00000000 --- a/apps/extension/public/rpc.js +++ /dev/null @@ -1,140 +0,0 @@ -// ../../node_modules/.pnpm/mitt@3.0.1/node_modules/mitt/dist/mitt.mjs -function mitt_default(n) { - return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) { - var i = n.get(t); - i ? i.push(e) : n.set(t, [e]); - }, off: function(t, e) { - var i = n.get(t); - i && (e ? i.splice(i.indexOf(e) >>> 0, 1) : n.set(t, [])); - }, emit: function(t, e) { - var i = n.get(t); - i && i.slice().map(function(n2) { - n2(e); - }), (i = n.get("*")) && i.slice().map(function(n2) { - n2(t, e); - }); - } }; -} - -// ../../node_modules/.pnpm/p-debounce@4.0.0/node_modules/p-debounce/index.js -var pDebounce = (fn, wait, options = {}) => { - if (!Number.isFinite(wait)) { - throw new TypeError("Expected `wait` to be a finite number"); - } - let leadingValue; - let timeout; - let resolveList = []; - return function(...arguments_) { - return new Promise((resolve) => { - const shouldCallNow = options.before && !timeout; - clearTimeout(timeout); - timeout = setTimeout(() => { - timeout = null; - const result = options.before ? leadingValue : fn.apply(this, arguments_); - for (resolve of resolveList) { - resolve(result); - } - resolveList = []; - }, wait); - if (shouldCallNow) { - leadingValue = fn.apply(this, arguments_); - resolve(leadingValue); - } else { - resolveList.push(resolve); - } - }); - }; -}; -pDebounce.promise = (function_) => { - let currentPromise; - return async function(...arguments_) { - if (currentPromise) { - return currentPromise; - } - try { - currentPromise = function_.apply(this, arguments_); - return await currentPromise; - } finally { - currentPromise = void 0; - } - }; -}; -var p_debounce_default = pDebounce; - -// src/inject/rpc/utils.ts -var BROADCAST_CHANNEL_ID = "pallad"; -var callPalladAsync = ({ - method, - payload -}) => { - return new Promise((resolve, reject) => { - const privateChannelId = `private-${Math.random()}`; - const channel = new BroadcastChannel(BROADCAST_CHANNEL_ID); - const responseChannel = new BroadcastChannel(privateChannelId); - const messageListener = ({ data }) => { - channel.close(); - const error = data.response?.error; - if (error) { - try { - console.table(JSON.parse(error.message)); - } catch { - console.info(error.message); - } - return reject(error); - } - return resolve(data.response); - }; - responseChannel.addEventListener("message", messageListener); - channel.postMessage({ - method, - payload, - isPallad: true, - respondAt: privateChannelId - }); - return channel.close(); - }); -}; -var debouncedCall = p_debounce_default(callPalladAsync, 300); - -// src/inject/rpc/provider.ts -var _events = mitt_default(); -window.addEventListener("pallad_event", (event) => { - event.stopImmediatePropagation(); - const { detail } = event; - _events.emit(detail.type, detail.data); -}); -var info = { - slug: "pallad", - name: "Pallad", - icon: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzAwIDMwMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzE5OF8yNDY5NSkiPgo8cmVjdCB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgcng9IjEyIiBmaWxsPSIjRjZDMTc3Ii8+CjxwYXRoIGQ9Ik05MCAyMTRDOTAgMjExLjc5MSA5MS43OTA5IDIxMCA5NCAyMTBIMTM2QzE0My43MzIgMjEwIDE1MCAyMTYuMjY4IDE1MCAyMjRWMjI2QzE1MCAyMzMuNzMyIDE0My43MzIgMjQwIDEzNiAyNDBIMTA0Qzk2LjI2OCAyNDAgOTAgMjMzLjczMiA5MCAyMjZWMjE0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgNjRDOTAgNjEuNzkwOSA5MS43OTA5IDYwIDk0IDYwSDE5NkMyMDMuNzMyIDYwIDIxMCA2Ni4yNjggMjEwIDc0Vjc2QzIxMCA4My43MzIgMjAzLjczMiA5MCAxOTYgOTBIMTA0Qzk2LjI2OCA5MCA5MCA4My43MzIgOTAgNzZWNjRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTRDMjEwIDkxLjc5MDkgMjExLjc5MSA5MCAyMTQgOTBIMjI2QzIzMy43MzIgOTAgMjQwIDk2LjI2OCAyNDAgMTA0VjEzNkMyNDAgMTQzLjczMiAyMzMuNzMyIDE1MCAyMjYgMTUwSDIyNEMyMTYuMjY4IDE1MCAyMTAgMTQzLjczMiAyMTAgMTM2Vjk0WiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNNjAgOTRDNjAgOTEuNzkwOSA2MS43OTA5IDkwIDY0IDkwSDc2QzgzLjczMiA5MCA5MCA5Ni4yNjggOTAgMTA0VjE5NkM5MCAyMDMuNzMyIDgzLjczMiAyMTAgNzYgMjEwSDc0QzY2LjI2OCAyMTAgNjAgMjAzLjczMiA2MCAxOTZWOTRaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0xNTAgMTUwSDE5NkMyMDMuNzMyIDE1MCAyMTAgMTU2LjI2OCAyMTAgMTY0VjE2NkMyMTAgMTczLjczMiAyMDMuNzMyIDE4MCAxOTYgMTgwSDE2NEMxNTYuMjY4IDE4MCAxNTAgMTczLjczMiAxNTAgMTY2VjE1MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCAxNTBIMTk1QzE5NSAxNTAgMjAxIDE1MCAyMDUuNSAxNTQuNUMyMTAgMTU5IDIxMCAxNjUgMjEwIDE2NVYxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIyNSAxNTBDMjI1IDE1MCAyMTkgMTUwIDIxNC41IDE0NS41QzIxMCAxNDEgMjEwIDEzNSAyMTAgMTM1TDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgMTUwTDIxMCAxMzVDMjEwIDEzNSAyMTAgMTQxIDIwNS41IDE0NS41QzIwMSAxNTAgMTk1IDE1MCAxOTUgMTUwTDIxMCAxNTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik05MCA5MEg3NUM3NSA5MCA4MSA5MCA4NS41IDk0LjVDOTAgOTkgOTAgMTA1IDkwIDEwNVY5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDkwTDEwNSA5MEMxMDUgOTAgOTkgOTAgOTQuNSA4NS41QzkwIDgxIDkwIDc1IDkwIDc1TDkwIDkwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgOTBMOTAgMTA1QzkwIDEwNSA5MCA5OSA5NC41IDk0LjVDOTkgOTAgMTA1IDkwIDEwNSA5MEw5MCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTIxMCA5MEgxOTVDMTk1IDkwIDIwMSA5MCAyMDUuNSA5NC41QzIxMCA5OSAyMTAgMTA1IDIxMCAxMDVWOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDc1QzIxMCA3NSAyMTAgODEgMjA1LjUgODUuNUMyMDEgOTAgMTk1IDkwIDE5NSA5MEwyMTAgOTBaIiBmaWxsPSIjMjUyMzNBIi8+CjxwYXRoIGQ9Ik0yMTAgOTBMMjEwIDEwNUMyMTAgMTA1IDIxMCA5OSAyMTQuNSA5NC41QzIxOSA5MCAyMjUgOTAgMjI1IDkwTDIxMCA5MFoiIGZpbGw9IiMyNTIzM0EiLz4KPHBhdGggZD0iTTkwIDIxMEwxMDUgMjEwQzEwNSAyMTAgOTkgMjEwIDk0LjUgMjA1LjVDOTAgMjAxIDkwIDE5NSA5MCAxOTVMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDE5NUM5MCAxOTUgOTAgMjAxIDg1LjUgMjA1LjVDODEgMjEwIDc1IDIxMCA3NSAyMTBMOTAgMjEwWiIgZmlsbD0iIzI1MjMzQSIvPgo8cGF0aCBkPSJNOTAgMjEwTDkwIDIyNUM5MCAyMjUgOTAgMjE5IDk0LjUgMjE0LjVDOTkgMjEwIDEwNSAyMTAgMTA1IDIxMEw5MCAyMTBaIiBmaWxsPSIjMjUyMzNBIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTk4XzI0Njk1Ij4KPHJlY3Qgd2lkdGg9IjMwMCIgaGVpZ2h0PSIzMDAiIHJ4PSI0MCIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K", - rdns: "co.pallad" -}; -var provider = { - _events, - request: async ({ method, params }) => await debouncedCall({ - method, - payload: { ...params, origin: window.location.origin } - }), - isPallad: true, - on: _events.on, - off: _events.off -}; - -// src/inject/rpc.ts -var init = () => { - ; - window.mina = provider; - const announceProvider = () => { - window.dispatchEvent( - new CustomEvent("mina:announceProvider", { - detail: Object.freeze({ info, provider }) - }) - ); - }; - window.addEventListener("mina:requestProvider", () => { - announceProvider(); - }); - announceProvider(); - console.info("[Pallad] RPC has been initialized."); -}; -init();