@portal-hq/web 0.0.1-beta-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const utils_1 = require("@portal-hq/utils");
13
+ const WEB_SDK_VERSION = '0.0.1-beta-1';
14
+ class Mpc {
15
+ constructor({ apiKey, chainId, portal, rpcUrl,
16
+ // Optional
17
+ apiHost = 'api.portalhq.io', autoApprove = false, mpcHost = 'mpc.portalhq.io', webHost = 'web.portalhq.io', }) {
18
+ this.apiHost = apiHost;
19
+ this.apiKey = apiKey;
20
+ this.autoApprove = autoApprove;
21
+ this.chainId = chainId;
22
+ this.mpcHost = mpcHost;
23
+ this.portal = portal;
24
+ this.rpcUrl = rpcUrl;
25
+ this.webHost = webHost;
26
+ // Handle scoping of certain functions
27
+ this.configureIframe = this.configureIframe.bind(this);
28
+ // Create the iFrame for MPC operations
29
+ this.appendIframe();
30
+ }
31
+ // In Progress
32
+ backup(data) {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ const message = {
35
+ type: 'portal:wasm:backup',
36
+ data,
37
+ };
38
+ return new Promise((resolve, reject) => {
39
+ // Create a function to be bound when backup is triggered
40
+ const handleBackup = (event) => {
41
+ const { type, data: result } = event;
42
+ // Check that the message is the one we're looking for
43
+ if (type === 'portal:wasm:backupResult') {
44
+ // Check if the result is an error
45
+ if (result.error && result.error.code > 0) {
46
+ // Remove the event listener
47
+ window.removeEventListener('message', handleBackup);
48
+ // Reject the promise with the error
49
+ return reject(new utils_1.PortalMpcError(result.error));
50
+ }
51
+ // Remove the event listener
52
+ window.removeEventListener('message', handleBackup);
53
+ // Resolve the promise with the result
54
+ resolve(result);
55
+ }
56
+ };
57
+ // Bind the function to the message event
58
+ window.addEventListener('message', handleBackup);
59
+ // Send the request to the iframe
60
+ this.postMessage(message);
61
+ });
62
+ });
63
+ }
64
+ // In Progress
65
+ decrypt(data) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ const message = {
68
+ type: 'portal:wasm:decrypt',
69
+ data,
70
+ };
71
+ return new Promise((resolve, reject) => {
72
+ const handleDecrypt = (event) => {
73
+ const { type, data: result } = event;
74
+ // Check that the message is the one we're looking for
75
+ if (type === 'portal:wasm:decryptResult') {
76
+ // Check if the result is an error
77
+ if (result.error && result.error.code > 0) {
78
+ // Remove the event listener
79
+ window.removeEventListener('message', handleDecrypt);
80
+ // Reject the promise with the error
81
+ return reject(new utils_1.PortalMpcError(result.error));
82
+ }
83
+ // Remove the event listener
84
+ window.removeEventListener('message', handleDecrypt);
85
+ // Resolve the promise with the result
86
+ resolve(result);
87
+ }
88
+ };
89
+ // Bind the function to the message event
90
+ window.addEventListener('message', handleDecrypt);
91
+ // Send the request to the iframe
92
+ this.postMessage(message);
93
+ });
94
+ });
95
+ }
96
+ // In Progress
97
+ encrypt(data) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ const message = {
100
+ type: 'portal:wasm:encrypt',
101
+ data,
102
+ };
103
+ return new Promise((resolve, reject) => {
104
+ const handleEncrypt = (event) => {
105
+ const { type, data: result } = event;
106
+ // Check that the message is the one we're looking for
107
+ if (type === 'portal:wasm:encryptResult') {
108
+ // Check if the result is an error
109
+ if (result.error && result.error.code > 0) {
110
+ // Remove the event listener
111
+ window.removeEventListener('message', handleEncrypt);
112
+ // Reject the promise with the error
113
+ return reject(new utils_1.PortalMpcError(result.error));
114
+ }
115
+ // Remove the event listener
116
+ window.removeEventListener('message', handleEncrypt);
117
+ // Resolve the promise with the result
118
+ resolve(result);
119
+ }
120
+ };
121
+ // Bind the function to the message event
122
+ window.addEventListener('message', handleEncrypt);
123
+ // Send the request to the iframe
124
+ this.postMessage(message);
125
+ });
126
+ });
127
+ }
128
+ generate(data) {
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ const message = {
131
+ type: 'portal:wasm:generate',
132
+ data,
133
+ };
134
+ return new Promise((resolve, reject) => {
135
+ // Create a function to be bound when generate is triggered
136
+ const handleGenerate = (event) => {
137
+ console.log(`[MPC] Temporary listener received event: `, event);
138
+ const { type, data: result } = event.data;
139
+ // Handle errors
140
+ if (type === 'portal:wasm:generateError') {
141
+ // Remove the event listener
142
+ window.removeEventListener('message', handleGenerate);
143
+ // Reject the promise with the error
144
+ return reject(new utils_1.PortalMpcError(result));
145
+ }
146
+ else if (type === 'portal:wasm:generateResult') {
147
+ // Remove the event listener
148
+ window.removeEventListener('message', handleGenerate);
149
+ // Resolve the promise with the result
150
+ resolve(result);
151
+ }
152
+ };
153
+ // Bind the function to the message event
154
+ window.addEventListener('message', handleGenerate);
155
+ // Send the request to the iframe
156
+ this.postMessage(message);
157
+ });
158
+ });
159
+ }
160
+ getAddress() {
161
+ return __awaiter(this, void 0, void 0, function* () {
162
+ const message = {
163
+ type: 'portal:address',
164
+ data: {},
165
+ };
166
+ return new Promise((resolve) => {
167
+ // Create a function to be bound when getAddress is triggered
168
+ const handleGetAddress = (event) => {
169
+ const { type, data: result } = event.data;
170
+ // Check that the message is the one we're looking for
171
+ if (type === 'portal:addressResult') {
172
+ // Remove the event listener
173
+ window.removeEventListener('message', handleGetAddress);
174
+ // Resolve the promise with the result
175
+ resolve(result);
176
+ }
177
+ };
178
+ // Bind the function to the message event
179
+ window.addEventListener('message', handleGetAddress);
180
+ // Send the request to the iframe
181
+ this.postMessage(message);
182
+ });
183
+ });
184
+ }
185
+ // In Progress
186
+ recover(data) {
187
+ return __awaiter(this, void 0, void 0, function* () {
188
+ yield this.recoverBackup(data);
189
+ // TODO: Write the DKG Result to local storage
190
+ const cipherText = yield this.recoverSigning(data);
191
+ return JSON.stringify(cipherText);
192
+ });
193
+ }
194
+ // In Progress
195
+ recoverBackup(data) {
196
+ return __awaiter(this, void 0, void 0, function* () {
197
+ const message = {
198
+ type: 'portal:wasm:recoverBackup',
199
+ data,
200
+ };
201
+ return new Promise((resolve, reject) => {
202
+ // Create a function to be bound when recoverSigning is triggered
203
+ const handleRecovery = (event) => {
204
+ const { type, data: result } = event;
205
+ // Check that the message is the one we're looking for
206
+ if (type === 'portal:wasm:recoverBackupResult') {
207
+ // Check if the result is an error
208
+ if (result.error && result.error.code > 0) {
209
+ // Remove the event listener
210
+ window.removeEventListener('message', handleRecovery);
211
+ // Reject the promise with the error
212
+ return reject(new utils_1.PortalMpcError(result.error));
213
+ }
214
+ // Remove the event listener
215
+ window.removeEventListener('message', handleRecovery);
216
+ // Resolve the promise with the result
217
+ resolve(result);
218
+ }
219
+ };
220
+ // Bind the function to the message event
221
+ window.addEventListener('message', handleRecovery);
222
+ // Send the request to the iframe
223
+ this.postMessage(message);
224
+ });
225
+ });
226
+ }
227
+ // In Progress
228
+ recoverSigning(data) {
229
+ return __awaiter(this, void 0, void 0, function* () {
230
+ const message = {
231
+ type: 'portal:wasm:recoverSigning',
232
+ data,
233
+ };
234
+ return new Promise((resolve, reject) => {
235
+ // Create a function to be bound when recoverSigning is triggered
236
+ const handleRecovery = (event) => {
237
+ const { type, data: result } = event;
238
+ // Check that the message is the one we're looking for
239
+ if (type === 'portal:wasm:recoverSigningResult') {
240
+ // Check if the result is an error
241
+ if (result.error && result.error.code > 0) {
242
+ // Remove the event listener
243
+ window.removeEventListener('message', handleRecovery);
244
+ // Reject the promise with the error
245
+ return reject(new utils_1.PortalMpcError(result.error));
246
+ }
247
+ // Remove the event listener
248
+ window.removeEventListener('message', handleRecovery);
249
+ // Resolve the promise with the result
250
+ resolve(result);
251
+ }
252
+ };
253
+ // Bind the function to the message event
254
+ window.addEventListener('message', handleRecovery);
255
+ // Send the request to the iframe
256
+ this.postMessage(message);
257
+ });
258
+ });
259
+ }
260
+ sign(data) {
261
+ return __awaiter(this, void 0, void 0, function* () {
262
+ const message = {
263
+ type: 'portal:wasm:sign',
264
+ data,
265
+ };
266
+ return new Promise((resolve, reject) => {
267
+ // Create a function to be bound when sign is triggered
268
+ const handleSign = (event) => {
269
+ const { type, data: result } = event.data;
270
+ if (type === 'portal:wasm:signError') {
271
+ // Remove the event listener
272
+ window.removeEventListener('message', handleSign);
273
+ // Reject the promise with the error
274
+ return reject(new utils_1.PortalMpcError(result));
275
+ }
276
+ else if (type === 'portal:wasm:signResult') {
277
+ // Remove the event listener
278
+ window.removeEventListener('message', handleSign);
279
+ // Resolve the promise with the result
280
+ resolve(result);
281
+ }
282
+ };
283
+ // Bind the function to the message event
284
+ window.addEventListener('message', handleSign);
285
+ // Send the request to the iframe
286
+ this.postMessage(message);
287
+ });
288
+ });
289
+ }
290
+ /***************************
291
+ * Private Methods
292
+ ***************************/
293
+ /**
294
+ * Appends the iframe to the document body
295
+ */
296
+ appendIframe() {
297
+ const source = this.webHost.startsWith('locahost:')
298
+ ? `http://${this.webHost}/${WEB_SDK_VERSION}/iframe/index.html?t=${Date.now()}`
299
+ : `https://${this.webHost}/${WEB_SDK_VERSION}/iframe/index.html?t=${Date.now()}`;
300
+ const iframe = document.createElement('iframe');
301
+ iframe.height = '0';
302
+ iframe.width = '0';
303
+ iframe.src = source;
304
+ iframe.addEventListener('load', this.configureIframe);
305
+ document.body.appendChild(iframe);
306
+ this.iframe = iframe;
307
+ }
308
+ configureIframe() {
309
+ const config = {
310
+ apiHost: this.apiHost,
311
+ apiKey: this.apiKey,
312
+ autoApprove: this.autoApprove,
313
+ chainId: this.chainId,
314
+ mpcHost: this.mpcHost,
315
+ rpcUrl: this.rpcUrl,
316
+ };
317
+ const message = {
318
+ type: 'portal:configure',
319
+ data: config
320
+ };
321
+ this.postMessage(message);
322
+ this.waitForReadyMessage();
323
+ }
324
+ getOrigin() {
325
+ const origin = this.webHost.startsWith('localhost:')
326
+ ? `http://${this.webHost}`
327
+ : `https://${this.webHost}`;
328
+ return origin;
329
+ }
330
+ postMessage(event) {
331
+ var _a, _b, _c;
332
+ console.log(`[MpcProxy] Sending new message: `, event, (_a = this.iframe) === null || _a === void 0 ? void 0 : _a.contentWindow);
333
+ (_c = (_b = this.iframe) === null || _b === void 0 ? void 0 : _b.contentWindow) === null || _c === void 0 ? void 0 : _c.postMessage(event, this.getOrigin());
334
+ }
335
+ waitForReadyMessage() {
336
+ const handleReady = (message) => __awaiter(this, void 0, void 0, function* () {
337
+ const { type, data } = message.data;
338
+ if (type === 'portal:wasm:ready' && data === true) {
339
+ // Unbind the event listener
340
+ window.removeEventListener('message', handleReady);
341
+ // Update ready state
342
+ this.portal.ready = true;
343
+ // Update the address
344
+ const address = yield this.getAddress();
345
+ this.portal.address = address;
346
+ // Trigger the ready callback
347
+ this.portal.triggerReady();
348
+ }
349
+ });
350
+ window.addEventListener('message', handleReady);
351
+ }
352
+ }
353
+ exports.default = Mpc;
@@ -0,0 +1,259 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const utils_1 = require("@portal-hq/utils");
13
+ const passiveSignerMethods = [
14
+ 'eth_accounts',
15
+ 'eth_chainId',
16
+ 'eth_requestAccounts',
17
+ ];
18
+ const signerMethods = [
19
+ 'eth_accounts',
20
+ 'eth_chainId',
21
+ 'eth_requestAccounts',
22
+ 'eth_sendTransaction',
23
+ 'eth_sign',
24
+ 'eth_signTransaction',
25
+ 'eth_signTypedData_v3',
26
+ 'eth_signTypedData_v4',
27
+ 'personal_sign',
28
+ ];
29
+ class Provider {
30
+ constructor({ autoApprove = false, portal, }) {
31
+ this.autoApprove = autoApprove;
32
+ this.events = {};
33
+ this.portal = portal;
34
+ }
35
+ /**
36
+ * Invokes all registered event handlers with the data provided
37
+ * - If any `once` handlers exist, they are removed after all handlers are invoked
38
+ *
39
+ * @param event The name of the event to be handled
40
+ * @param data The data to be passed to registered event handlers
41
+ * @returns BaseProvider
42
+ */
43
+ emit(event, data) {
44
+ // Grab the registered event handlers if any are available
45
+ const handlers = this.events[event] || [];
46
+ // Execute every event handler
47
+ for (const registeredEventHandler of handlers) {
48
+ registeredEventHandler.handler(data);
49
+ }
50
+ // Remove any registered event handlers with the `once` flag
51
+ this.events[event] = handlers.filter((handler) => !handler.once);
52
+ return this;
53
+ }
54
+ /**
55
+ * Registers an event handler for the provided event
56
+ *
57
+ * @param event The event name to add a handler to
58
+ * @param callback The callback to be invoked when the event is emitted
59
+ * @returns BaseProvider
60
+ */
61
+ on(event, callback) {
62
+ // If no handlers are registered for this event, create an entry for the event
63
+ if (!this.events[event]) {
64
+ this.events[event] = [];
65
+ }
66
+ // Register event handler with the rudimentary event bus
67
+ if (typeof callback !== 'undefined') {
68
+ this.events[event].push({
69
+ handler: callback,
70
+ once: false,
71
+ });
72
+ }
73
+ return this;
74
+ }
75
+ removeEventListener(event, listenerToRemove) {
76
+ if (!this.events[event]) {
77
+ return;
78
+ }
79
+ if (!listenerToRemove) {
80
+ this.events[event] = [];
81
+ }
82
+ else {
83
+ const filterEventHandlers = (registeredEventHandler) => {
84
+ return registeredEventHandler.handler !== listenerToRemove;
85
+ };
86
+ this.events[event] = this.events[event].filter(filterEventHandlers);
87
+ }
88
+ }
89
+ /**
90
+ * Handles request routing in compliance with the EIP-1193 Ethereum Javascript Provider API
91
+ * - See here for more info: https://eips.ethereum.org/EIPS/eip-1193
92
+ *
93
+ * @param args The arguments of the request being made
94
+ * @returns Promise<any>
95
+ */
96
+ request({ method, params }) {
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ if (method === 'eth_chainId') {
99
+ return this.portal.chainId;
100
+ }
101
+ const isSignerMethod = signerMethods.includes(method);
102
+ let result;
103
+ if (!isSignerMethod && !method.startsWith('wallet_')) {
104
+ // Send to Gateway for RPC calls
105
+ const response = yield this.handleGatewayRequest({ method, params });
106
+ this.emit('portal_signatureReceived', {
107
+ method,
108
+ params,
109
+ signature: response,
110
+ });
111
+ if (response.error) {
112
+ throw new utils_1.ProviderRpcError(response.error);
113
+ }
114
+ result = response.result;
115
+ }
116
+ else if (isSignerMethod) {
117
+ // Handle signing
118
+ const transactionHash = yield this.handleSigningRequest({
119
+ method,
120
+ params,
121
+ });
122
+ if (transactionHash) {
123
+ this.emit('portal_signatureReceived', {
124
+ method,
125
+ params,
126
+ signature: transactionHash,
127
+ });
128
+ result = transactionHash;
129
+ }
130
+ }
131
+ else {
132
+ // Unsupported method
133
+ throw new utils_1.ProviderRpcError({
134
+ code: utils_1.RpcErrorCodes.UnsupportedMethod,
135
+ data: {
136
+ method,
137
+ params,
138
+ },
139
+ });
140
+ }
141
+ return result;
142
+ });
143
+ }
144
+ /************************
145
+ * Private Methods
146
+ ************************/
147
+ /**
148
+ * Kicks off the approval flow for a given request
149
+ *
150
+ * @param args The arguments of the request being made
151
+ */
152
+ getApproval({ method, params, }) {
153
+ return __awaiter(this, void 0, void 0, function* () {
154
+ // If autoApprove is enabled, just resolve to true
155
+ if (this.autoApprove) {
156
+ return true;
157
+ }
158
+ const signingHandlers = this.events['portal_signingRequested'];
159
+ if (!signingHandlers || signingHandlers.length === 0) {
160
+ throw new Error(`[PortalProvider] Auto-approve is disabled. Cannot perform signing requests without an event handler for the 'portal_signingRequested' event.`);
161
+ }
162
+ return new Promise((resolve) => {
163
+ // Remove already used listeners
164
+ this.removeEventListener('portal_signingApproved');
165
+ this.removeEventListener('portal_signingRejected');
166
+ const handleApproval = ({ method: approvedMethod, params: approvedParams }) => {
167
+ // Remove already used listeners
168
+ this.removeEventListener('portal_signingApproved');
169
+ this.removeEventListener('portal_signingRejected');
170
+ // First verify that this is the same signing request
171
+ if (method === approvedMethod &&
172
+ JSON.stringify(params) === JSON.stringify(approvedParams)) {
173
+ resolve(true);
174
+ }
175
+ };
176
+ const handleRejection = ({ method: rejectedMethod, params: rejectedParams }) => {
177
+ // Remove already used listeners
178
+ this.removeEventListener('portal_signingApproved');
179
+ this.removeEventListener('portal_signingRejected');
180
+ // First verify that this is the same signing request
181
+ if (method === rejectedMethod &&
182
+ JSON.stringify(params) === JSON.stringify(rejectedParams)) {
183
+ resolve(false);
184
+ }
185
+ };
186
+ // If the signing has been approved, resolve to true
187
+ this.on('portal_signingApproved', handleApproval);
188
+ // If the signing request has been rejected, resolve to false
189
+ this.on('portal_signingRejected', handleRejection);
190
+ // Tell any listening clients that signing has been requested
191
+ this.emit('portal_signingRequested', {
192
+ method,
193
+ params,
194
+ });
195
+ });
196
+ });
197
+ }
198
+ /**
199
+ * Sends the provided request payload along to the RPC HttpRequester
200
+ *
201
+ * @param args The arguments of the request being made
202
+ * @returns Promise<any>
203
+ */
204
+ handleGatewayRequest({ method, params, }) {
205
+ return __awaiter(this, void 0, void 0, function* () {
206
+ // Pass request off to the gateway
207
+ const result = yield fetch(this.portal.getRpcUrl(), {
208
+ body: JSON.stringify({
209
+ jsonrpc: '2.0',
210
+ id: this.portal.chainId,
211
+ method,
212
+ params,
213
+ }),
214
+ method: 'POST',
215
+ });
216
+ return result.json();
217
+ });
218
+ }
219
+ /**
220
+ * Sends the provided request payload along to the Signer
221
+ *
222
+ * @param args The arguments of the request being made
223
+ * @returns Promise<any>
224
+ */
225
+ handleSigningRequest({ method, params, }) {
226
+ return __awaiter(this, void 0, void 0, function* () {
227
+ const isApproved = passiveSignerMethods.includes(method)
228
+ ? true
229
+ : yield this.getApproval({ method, params });
230
+ if (!isApproved) {
231
+ console.info(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
232
+ return;
233
+ }
234
+ switch (method) {
235
+ case 'eth_chainId':
236
+ return `0x${this.portal.chainId.toString(16)}`;
237
+ case 'eth_accounts':
238
+ case 'eth_requestAccounts':
239
+ return [this.portal.address];
240
+ case 'eth_sendTransaction':
241
+ case 'eth_sign':
242
+ case 'eth_signTransaction':
243
+ case 'eth_signTypedData_v3':
244
+ case 'eth_signTypedData_v4':
245
+ case 'personal_sign':
246
+ const result = yield this.portal.mpc.sign({
247
+ host: this.portal.mpcHost,
248
+ method,
249
+ mpcVersion: this.portal.mpcVersion,
250
+ params,
251
+ });
252
+ return result;
253
+ default:
254
+ throw new Error('[PortalProvider] Method "' + method + '" not supported');
255
+ }
256
+ });
257
+ }
258
+ }
259
+ exports.default = Provider;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class DefaultBackupStorage {
4
+ delete() {
5
+ throw new Error('[Portal] Storage method delete cannot be called directly. Please extend Storage.');
6
+ }
7
+ read() {
8
+ throw new Error('[Portal] Storage method read cannot be called directly. Please extend Storage.');
9
+ }
10
+ write(privateKey) {
11
+ throw new Error(`[Portal] Storage method write(${privateKey}) cannot be called directly. Please extend Storage.`);
12
+ }
13
+ validateOperations() {
14
+ throw new Error('[Portal] Storage method validateOperations cannot be called directly. Please extend Storage.');
15
+ }
16
+ }
17
+ exports.default = DefaultBackupStorage;