|
1 | | -///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps) |
2 | | -/* eslint-disable import/no-commonjs */ |
3 | | -/* eslint-disable @typescript-eslint/no-require-imports */ |
4 | | -/* eslint-disable @typescript-eslint/no-var-requires */ |
5 | | -// eslint-disable-next-line @typescript-eslint/ban-ts-comment |
6 | | -// @ts-nocheck |
7 | 1 | // eslint-disable-next-line import/no-nodejs-modules |
8 | 2 | import { Duplex } from 'stream'; |
| 3 | +// @ts-expect-error - No types declarations |
| 4 | +import pump from 'pump'; |
| 5 | + |
| 6 | +import { JsonRpcEngine, JsonRpcMiddleware } from '@metamask/json-rpc-engine'; |
| 7 | +// @ts-expect-error - No types declarations |
| 8 | +import createFilterMiddleware from '@metamask/eth-json-rpc-filters'; |
| 9 | +// @ts-expect-error - No types declarations |
| 10 | +import createSubscriptionManager from '@metamask/eth-json-rpc-filters/subscriptionManager'; |
| 11 | +import { JsonRpcParams, Json } from '@metamask/utils'; |
9 | 12 | import { |
10 | | - createSwappableProxy, |
11 | | - createEventEmitterProxy, |
12 | | -} from '@metamask/swappable-obj-proxy'; |
13 | | -import { JsonRpcEngine } from '@metamask/json-rpc-engine'; |
| 13 | + createSelectedNetworkMiddleware, |
| 14 | + SelectedNetworkControllerMessenger, |
| 15 | +} from '@metamask/selected-network-controller'; |
| 16 | +import { createPreinstalledSnapsMiddleware } from '@metamask/snaps-rpc-methods'; |
| 17 | +import { SubjectType } from '@metamask/permission-controller'; |
| 18 | +import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware'; |
14 | 19 | import { createEngineStream } from '@metamask/json-rpc-middleware-stream'; |
15 | | -import EthQuery from '@metamask/eth-query'; |
| 20 | +import { SnapId } from '@metamask/snaps-sdk'; |
| 21 | +import { InternalAccount } from '@metamask/keyring-internal-api'; |
16 | 22 |
|
17 | 23 | import Engine from '../Engine'; |
18 | 24 | import { setupMultiplex } from '../../util/streams'; |
19 | 25 | import Logger from '../../util/Logger'; |
| 26 | +import { createOriginMiddleware } from '../../util/middlewares'; |
| 27 | +import { RPCMethodsMiddleParameters } from '../RPCMethods/RPCMethodMiddleware'; |
20 | 28 | import snapMethodMiddlewareBuilder from './SnapsMethodMiddleware'; |
21 | | -import { SubjectType } from '@metamask/permission-controller'; |
22 | | -import { createPreinstalledSnapsMiddleware } from '@metamask/snaps-rpc-methods'; |
23 | 29 | import { isSnapPreinstalled } from '../SnapKeyring/utils/snaps'; |
24 | 30 |
|
25 | | -import ObjectMultiplex from '@metamask/object-multiplex'; |
26 | | -import createFilterMiddleware from '@metamask/eth-json-rpc-filters'; |
27 | | -import createSubscriptionManager from '@metamask/eth-json-rpc-filters/subscriptionManager'; |
28 | | -import { providerAsMiddleware } from '@metamask/eth-json-rpc-middleware'; |
29 | | -import { createOriginMiddleware } from '../../util/middlewares'; |
30 | | -import { createSelectedNetworkMiddleware } from '@metamask/selected-network-controller'; |
31 | | -const pump = require('pump'); |
32 | | - |
33 | | -interface ISnapBridgeProps { |
34 | | - snapId: string; |
35 | | - connectionStream: Duplex; |
36 | | - // TODO: Replace "any" with type |
37 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
38 | | - getRPCMethodMiddleware: (args: any) => any; |
39 | | -} |
40 | | - |
| 31 | +/** |
| 32 | + * Type definition for the GetRPCMethodMiddleware function. |
| 33 | + */ |
| 34 | +type GetRPCMethodMiddleware = ({ |
| 35 | + hostname, |
| 36 | + getProviderState, |
| 37 | +}: { |
| 38 | + hostname: RPCMethodsMiddleParameters['hostname']; |
| 39 | + getProviderState: RPCMethodsMiddleParameters['getProviderState']; |
| 40 | +}) => JsonRpcMiddleware<JsonRpcParams, Json>; |
| 41 | + |
| 42 | +/** |
| 43 | + * A bridge for connecting the client Ethereum provider to a Snap's execution environment. |
| 44 | + * |
| 45 | + * @param params - The parameters for the SnapBridge. |
| 46 | + * @param params.snapId - The ID of the Snap. |
| 47 | + * @param params.connectionStream - The stream to connect to the Snap. |
| 48 | + * @param params.getRPCMethodMiddleware - A function to get the RPC method middleware. |
| 49 | + */ |
41 | 50 | export default class SnapBridge { |
42 | | - snapId: string; |
43 | | - stream: Duplex; |
44 | | - // TODO: Replace "any" with type |
45 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
46 | | - getRPCMethodMiddleware: (args: any) => any; |
47 | | - // TODO: Replace "any" with type |
48 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
49 | | - provider: any; |
50 | | - // TODO: Replace "any" with type |
51 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
52 | | - blockTracker: any; |
53 | | - |
54 | | - #mux: typeof ObjectMultiplex; |
55 | | - // TODO: Replace "any" with type |
56 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
57 | | - #providerProxy: any; |
58 | | - // TODO: Replace "any" with type |
59 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
60 | | - #blockTrackerProxy: any; |
| 51 | + #snapId: SnapId; |
| 52 | + #stream: Duplex; |
| 53 | + #getRPCMethodMiddleware: GetRPCMethodMiddleware; |
61 | 54 |
|
62 | 55 | constructor({ |
63 | 56 | snapId, |
64 | 57 | connectionStream, |
65 | 58 | getRPCMethodMiddleware, |
66 | | - }: ISnapBridgeProps) { |
67 | | - Logger.log( |
68 | | - '[SNAP BRIDGE LOG] Engine+setupSnapProvider: Setup bridge for Snap', |
69 | | - snapId, |
70 | | - ); |
71 | | - |
72 | | - this.snapId = snapId; |
73 | | - this.stream = connectionStream; |
74 | | - this.getRPCMethodMiddleware = getRPCMethodMiddleware; |
75 | | - this.deprecatedNetworkVersions = {}; |
76 | | - |
77 | | - // TODO: Replace "any" with type |
78 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
79 | | - const { NetworkController } = Engine.context as any; |
80 | | - |
81 | | - const { provider, blockTracker } = |
82 | | - NetworkController.getProviderAndBlockTracker(); |
83 | | - |
84 | | - this.#providerProxy = null; |
85 | | - this.#blockTrackerProxy = null; |
86 | | - |
87 | | - this.#setProvider(provider); |
88 | | - this.#setBlockTracker(blockTracker); |
89 | | - |
90 | | - this.#mux = setupMultiplex(this.stream); |
| 59 | + }: { |
| 60 | + snapId: SnapId; |
| 61 | + connectionStream: Duplex; |
| 62 | + getRPCMethodMiddleware: GetRPCMethodMiddleware; |
| 63 | + }) { |
| 64 | + Logger.log('[SNAP BRIDGE] Initializing SnapBridge for Snap:', snapId); |
| 65 | + |
| 66 | + this.#snapId = snapId; |
| 67 | + this.#stream = connectionStream; |
| 68 | + this.#getRPCMethodMiddleware = getRPCMethodMiddleware; |
91 | 69 | } |
92 | 70 |
|
93 | | - // TODO: Replace "any" with type |
94 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
95 | | - #setProvider = (provider: any): void => { |
96 | | - if (this.#providerProxy) { |
97 | | - this.#providerProxy.setTarget(provider); |
98 | | - } else { |
99 | | - this.#providerProxy = createSwappableProxy(provider); |
100 | | - } |
101 | | - this.provider = provider; |
102 | | - }; |
103 | | - |
104 | | - // TODO: Replace "any" with type |
105 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
106 | | - #setBlockTracker = (blockTracker: any): void => { |
107 | | - if (this.#blockTrackerProxy) { |
108 | | - this.#blockTrackerProxy.setTarget(blockTracker); |
109 | | - } else { |
110 | | - this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, { |
111 | | - eventFilter: 'skipInternal', |
112 | | - }); |
113 | | - } |
114 | | - this.blockTracker = blockTracker; |
115 | | - }; |
116 | | - |
117 | | - async getProviderState() { |
| 71 | + /** |
| 72 | + * Gets the provider state. |
| 73 | + * @returns An object containing the provider state. |
| 74 | + */ |
| 75 | + #getProviderState() { |
118 | 76 | return { |
119 | | - isUnlocked: this.isUnlocked(), |
120 | | - ...(await this.getProviderNetworkState(this.snapId)), |
| 77 | + isUnlocked: Engine.context.KeyringController.isUnlocked(), |
121 | 78 | }; |
122 | 79 | } |
123 | 80 |
|
124 | | - setupProviderConnection = () => { |
125 | | - Logger.log('[SNAP BRIDGE LOG] Engine+setupProviderConnection'); |
126 | | - const outStream = this.#mux.createStream('metamask-provider'); |
127 | | - const engine = this.setupProviderEngine(); |
| 81 | + /** |
| 82 | + * Sets up the provider engine for the Snap. |
| 83 | + * @returns The configured JSON-RPC engine. |
| 84 | + */ |
| 85 | + #setupProviderEngine() { |
| 86 | + Logger.log('[SNAP BRIDGE] Setting up provider engine'); |
128 | 87 |
|
129 | | - const providerStream = createEngineStream({ engine }); |
130 | | - // TODO: Replace "any" with type |
131 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
132 | | - pump(outStream, providerStream, outStream, (err: any) => { |
133 | | - engine.destroy(); |
134 | | - if (err) Logger.log('Error with provider stream conn', err); |
135 | | - }); |
136 | | - }; |
137 | | - |
138 | | - setupProviderEngine = () => { |
| 88 | + const { context, controllerMessenger } = Engine; |
| 89 | + const { SelectedNetworkController, PermissionController } = context; |
139 | 90 | const engine = new JsonRpcEngine(); |
140 | 91 |
|
141 | | - // create filter polyfill middleware |
142 | | - const filterMiddleware = createFilterMiddleware({ |
143 | | - provider: this.#providerProxy, |
144 | | - blockTracker: this.#blockTrackerProxy, |
145 | | - }); |
| 92 | + const proxy = SelectedNetworkController.getProviderAndBlockTracker( |
| 93 | + this.#snapId, |
| 94 | + ); |
146 | 95 |
|
147 | | - // create subscription polyfill middleware |
148 | | - const subscriptionManager = createSubscriptionManager({ |
149 | | - provider: this.#providerProxy, |
150 | | - blockTracker: this.#blockTrackerProxy, |
151 | | - }); |
| 96 | + const filterMiddleware = createFilterMiddleware(proxy); |
152 | 97 |
|
153 | | - // TODO: Replace "any" with type |
154 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
155 | | - subscriptionManager.events.on('notification', (message: any) => |
| 98 | + const subscriptionManager = createSubscriptionManager(proxy); |
| 99 | + subscriptionManager.events.on('notification', (message: Json) => |
156 | 100 | engine.emit('notification', message), |
157 | 101 | ); |
158 | 102 |
|
159 | | - engine.push(createOriginMiddleware({ origin: this.snapId })); |
160 | | - engine.push(createSelectedNetworkMiddleware(Engine.controllerMessenger)); |
| 103 | + engine.push( |
| 104 | + createOriginMiddleware({ origin: this.#snapId }) as JsonRpcMiddleware< |
| 105 | + JsonRpcParams, |
| 106 | + Json |
| 107 | + >, |
| 108 | + ); |
| 109 | + |
| 110 | + engine.push( |
| 111 | + createSelectedNetworkMiddleware( |
| 112 | + controllerMessenger as unknown as SelectedNetworkControllerMessenger, |
| 113 | + ), |
| 114 | + ); |
161 | 115 |
|
162 | 116 | // Filter and subscription polyfills |
163 | 117 | engine.push(filterMiddleware); |
164 | 118 | engine.push(subscriptionManager.middleware); |
165 | 119 |
|
166 | | - const { context, controllerMessenger } = Engine; |
167 | | - const { PermissionController } = context; |
168 | | - |
169 | | - if (isSnapPreinstalled(this.snapId)) { |
| 120 | + if (isSnapPreinstalled(this.#snapId)) { |
170 | 121 | engine.push( |
171 | 122 | createPreinstalledSnapsMiddleware({ |
172 | 123 | getPermissions: PermissionController.getPermissions.bind( |
173 | 124 | PermissionController, |
174 | | - this.snapId, |
| 125 | + this.#snapId, |
175 | 126 | ), |
176 | 127 | getAllEvmAccounts: () => |
177 | 128 | controllerMessenger |
178 | 129 | .call('AccountsController:listAccounts') |
179 | | - .map((account) => account.address), |
| 130 | + .map((account: InternalAccount) => account.address), |
180 | 131 | grantPermissions: (approvedPermissions) => |
181 | 132 | controllerMessenger.call('PermissionController:grantPermissions', { |
182 | 133 | approvedPermissions, |
183 | | - subject: { origin: this.snapId }, |
| 134 | + subject: { origin: this.#snapId }, |
184 | 135 | }), |
185 | 136 | }), |
186 | 137 | ); |
187 | 138 | } |
188 | 139 |
|
189 | 140 | engine.push( |
190 | 141 | PermissionController.createPermissionMiddleware({ |
191 | | - origin: this.snapId, |
| 142 | + origin: this.#snapId, |
192 | 143 | }), |
193 | 144 | ); |
194 | 145 |
|
195 | 146 | engine.push( |
196 | 147 | snapMethodMiddlewareBuilder( |
197 | 148 | context, |
198 | 149 | controllerMessenger, |
199 | | - this.snapId, |
| 150 | + this.#snapId, |
200 | 151 | SubjectType.Snap, |
201 | 152 | ), |
202 | 153 | ); |
203 | 154 |
|
204 | 155 | // User-Facing RPC methods |
205 | 156 | engine.push( |
206 | | - this.getRPCMethodMiddleware({ |
207 | | - hostname: this.snapId, |
208 | | - getProviderState: this.getProviderState.bind(this), |
| 157 | + this.#getRPCMethodMiddleware({ |
| 158 | + hostname: this.#snapId, |
| 159 | + getProviderState: this.#getProviderState.bind(this), |
209 | 160 | }), |
210 | 161 | ); |
211 | 162 |
|
212 | 163 | // Forward to metamask primary provider |
213 | | - engine.push(providerAsMiddleware(this.#providerProxy)); |
| 164 | + engine.push(providerAsMiddleware(proxy.provider)); |
| 165 | + |
214 | 166 | return engine; |
215 | | - }; |
| 167 | + } |
216 | 168 |
|
217 | | - isUnlocked = (): boolean => { |
218 | | - // TODO: Replace "any" with type |
219 | | - // eslint-disable-next-line @typescript-eslint/no-explicit-any |
220 | | - const { KeyringController } = Engine.context as any; |
221 | | - return KeyringController.isUnlocked(); |
222 | | - }; |
| 169 | + /** |
| 170 | + * Sets up the provider connection for the Snap. |
| 171 | + */ |
| 172 | + setupProviderConnection() { |
| 173 | + Logger.log('[SNAP BRIDGE] Setting up provider connection'); |
223 | 174 |
|
224 | | - async getProviderNetworkState(origin: string) { |
225 | | - const networkClientId = Engine.controllerMessenger.call( |
226 | | - 'SelectedNetworkController:getNetworkClientIdForDomain', |
227 | | - origin, |
228 | | - ); |
| 175 | + const mux = setupMultiplex(this.#stream); |
| 176 | + const stream = mux.createStream('metamask-provider'); |
229 | 177 |
|
230 | | - const networkClient = Engine.controllerMessenger.call( |
231 | | - 'NetworkController:getNetworkClientById', |
232 | | - networkClientId, |
233 | | - ); |
| 178 | + const engine = this.#setupProviderEngine(); |
234 | 179 |
|
235 | | - const { chainId } = networkClient.configuration; |
| 180 | + const providerStream = createEngineStream({ engine }); |
236 | 181 |
|
237 | | - let networkVersion = this.deprecatedNetworkVersions[networkClientId]; |
238 | | - if (!networkVersion) { |
239 | | - const ethQuery = new EthQuery(networkClient.provider); |
240 | | - networkVersion = await new Promise((resolve) => { |
241 | | - ethQuery.sendAsync({ method: 'net_version' }, (error, result) => { |
242 | | - if (error) { |
243 | | - console.error(error); |
244 | | - resolve(null); |
245 | | - } else { |
246 | | - resolve(result); |
247 | | - } |
248 | | - }); |
249 | | - }); |
250 | | - this.deprecatedNetworkVersions[networkClientId] = networkVersion; |
251 | | - } |
| 182 | + pump(stream, providerStream, stream, (error: Error | null) => { |
| 183 | + engine.destroy(); |
252 | 184 |
|
253 | | - return { |
254 | | - chainId, |
255 | | - networkVersion: networkVersion ?? 'loading', |
256 | | - }; |
| 185 | + if (error) { |
| 186 | + Logger.log('[SNAP BRIDGE] Error with provider stream:', error); |
| 187 | + } |
| 188 | + }); |
257 | 189 | } |
258 | 190 | } |
259 | | -///: END:ONLY_INCLUDE_IF |
0 commit comments