@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.
- package/README.md +297 -0
- package/lib/commonjs/index.js +201 -0
- package/lib/commonjs/mpc/index.js +353 -0
- package/lib/commonjs/provider/index.js +259 -0
- package/lib/commonjs/storage/index.js +17 -0
- package/lib/esm/index.js +196 -0
- package/lib/esm/mpc/index.js +351 -0
- package/lib/esm/provider/index.js +257 -0
- package/lib/esm/storage/index.js +15 -0
- package/package.json +46 -0
- package/src/index.ts +232 -0
- package/src/mpc/index.ts +443 -0
- package/src/provider/index.ts +314 -0
- package/src/storage/index.ts +29 -0
- package/types.d.ts +341 -0
package/src/mpc/index.ts
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { PortalError, PortalMpcError } from '@portal-hq/utils'
|
|
2
|
+
|
|
3
|
+
import Portal from '../index'
|
|
4
|
+
import {
|
|
5
|
+
BackupArgs,
|
|
6
|
+
DecryptArgs,
|
|
7
|
+
DecryptResult,
|
|
8
|
+
EncryptArgs,
|
|
9
|
+
EncryptResult,
|
|
10
|
+
GenerateArgs,
|
|
11
|
+
GenerateResult,
|
|
12
|
+
IframeConfigurationOptions,
|
|
13
|
+
MpcOptions,
|
|
14
|
+
RecoverArgs,
|
|
15
|
+
SignArgs,
|
|
16
|
+
WorkerResult,
|
|
17
|
+
} from '../../types'
|
|
18
|
+
|
|
19
|
+
const WEB_SDK_VERSION = '0.0.1-beta-1'
|
|
20
|
+
|
|
21
|
+
class Mpc {
|
|
22
|
+
private apiHost: string
|
|
23
|
+
private apiKey: string
|
|
24
|
+
private autoApprove: boolean
|
|
25
|
+
private chainId: number
|
|
26
|
+
private iframe?: HTMLIFrameElement
|
|
27
|
+
private mpcHost: string
|
|
28
|
+
private portal: Portal
|
|
29
|
+
private rpcUrl: string
|
|
30
|
+
private webHost: string
|
|
31
|
+
|
|
32
|
+
constructor({
|
|
33
|
+
apiKey,
|
|
34
|
+
chainId,
|
|
35
|
+
portal,
|
|
36
|
+
rpcUrl,
|
|
37
|
+
|
|
38
|
+
// Optional
|
|
39
|
+
apiHost = 'api.portalhq.io',
|
|
40
|
+
autoApprove = false,
|
|
41
|
+
mpcHost = 'mpc.portalhq.io',
|
|
42
|
+
webHost = 'web.portalhq.io',
|
|
43
|
+
}: MpcOptions) {
|
|
44
|
+
this.apiHost = apiHost
|
|
45
|
+
this.apiKey = apiKey
|
|
46
|
+
this.autoApprove = autoApprove
|
|
47
|
+
this.chainId = chainId
|
|
48
|
+
this.mpcHost = mpcHost
|
|
49
|
+
this.portal = portal
|
|
50
|
+
this.rpcUrl = rpcUrl
|
|
51
|
+
this.webHost = webHost
|
|
52
|
+
|
|
53
|
+
// Handle scoping of certain functions
|
|
54
|
+
this.configureIframe = this.configureIframe.bind(this)
|
|
55
|
+
|
|
56
|
+
// Create the iFrame for MPC operations
|
|
57
|
+
this.appendIframe()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// In Progress
|
|
61
|
+
public async backup(data: BackupArgs): Promise<GenerateResult> {
|
|
62
|
+
const message = {
|
|
63
|
+
type: 'portal:wasm:backup',
|
|
64
|
+
data,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
// Create a function to be bound when backup is triggered
|
|
69
|
+
const handleBackup = (event: MessageEvent<GenerateResult>) => {
|
|
70
|
+
const { type, data: result } = event
|
|
71
|
+
|
|
72
|
+
// Check that the message is the one we're looking for
|
|
73
|
+
if (type === 'portal:wasm:backupResult') {
|
|
74
|
+
// Check if the result is an error
|
|
75
|
+
if (result.error && result.error.code > 0) {
|
|
76
|
+
// Remove the event listener
|
|
77
|
+
window.removeEventListener('message', handleBackup)
|
|
78
|
+
|
|
79
|
+
// Reject the promise with the error
|
|
80
|
+
return reject(new PortalMpcError(result.error))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Remove the event listener
|
|
84
|
+
window.removeEventListener('message', handleBackup)
|
|
85
|
+
|
|
86
|
+
// Resolve the promise with the result
|
|
87
|
+
resolve(result)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Bind the function to the message event
|
|
92
|
+
window.addEventListener('message', handleBackup)
|
|
93
|
+
|
|
94
|
+
// Send the request to the iframe
|
|
95
|
+
this.postMessage(message)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// In Progress
|
|
100
|
+
public async decrypt(data: DecryptArgs): Promise<DecryptResult> {
|
|
101
|
+
const message = {
|
|
102
|
+
type: 'portal:wasm:decrypt',
|
|
103
|
+
data,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const handleDecrypt = (event: MessageEvent<DecryptResult>) => {
|
|
108
|
+
const { type, data: result } = event
|
|
109
|
+
|
|
110
|
+
// Check that the message is the one we're looking for
|
|
111
|
+
if (type === 'portal:wasm:decryptResult') {
|
|
112
|
+
// Check if the result is an error
|
|
113
|
+
if (result.error && result.error.code > 0) {
|
|
114
|
+
// Remove the event listener
|
|
115
|
+
window.removeEventListener('message', handleDecrypt)
|
|
116
|
+
|
|
117
|
+
// Reject the promise with the error
|
|
118
|
+
return reject(new PortalMpcError(result.error))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Remove the event listener
|
|
122
|
+
window.removeEventListener('message', handleDecrypt)
|
|
123
|
+
|
|
124
|
+
// Resolve the promise with the result
|
|
125
|
+
resolve(result)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Bind the function to the message event
|
|
130
|
+
window.addEventListener('message', handleDecrypt)
|
|
131
|
+
|
|
132
|
+
// Send the request to the iframe
|
|
133
|
+
this.postMessage(message)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// In Progress
|
|
138
|
+
public async encrypt(data: EncryptArgs): Promise<EncryptResult> {
|
|
139
|
+
const message = {
|
|
140
|
+
type: 'portal:wasm:encrypt',
|
|
141
|
+
data,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const handleEncrypt = (event: MessageEvent<EncryptResult>) => {
|
|
146
|
+
const { type, data: result } = event
|
|
147
|
+
|
|
148
|
+
// Check that the message is the one we're looking for
|
|
149
|
+
if (type === 'portal:wasm:encryptResult') {
|
|
150
|
+
// Check if the result is an error
|
|
151
|
+
if (result.error && result.error.code > 0) {
|
|
152
|
+
// Remove the event listener
|
|
153
|
+
window.removeEventListener('message', handleEncrypt)
|
|
154
|
+
|
|
155
|
+
// Reject the promise with the error
|
|
156
|
+
return reject(new PortalMpcError(result.error))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Remove the event listener
|
|
160
|
+
window.removeEventListener('message', handleEncrypt)
|
|
161
|
+
|
|
162
|
+
// Resolve the promise with the result
|
|
163
|
+
resolve(result)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Bind the function to the message event
|
|
168
|
+
window.addEventListener('message', handleEncrypt)
|
|
169
|
+
|
|
170
|
+
// Send the request to the iframe
|
|
171
|
+
this.postMessage(message)
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public async generate(data: GenerateArgs): Promise<string> {
|
|
176
|
+
const message = {
|
|
177
|
+
type: 'portal:wasm:generate',
|
|
178
|
+
data,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
// Create a function to be bound when generate is triggered
|
|
183
|
+
const handleGenerate = (event: MessageEvent<WorkerResult>) => {
|
|
184
|
+
console.log(`[MPC] Temporary listener received event: `, event)
|
|
185
|
+
|
|
186
|
+
const { type, data: result } = event.data
|
|
187
|
+
|
|
188
|
+
// Handle errors
|
|
189
|
+
if (type === 'portal:wasm:generateError') {
|
|
190
|
+
// Remove the event listener
|
|
191
|
+
window.removeEventListener('message', handleGenerate)
|
|
192
|
+
|
|
193
|
+
// Reject the promise with the error
|
|
194
|
+
return reject(new PortalMpcError(result))
|
|
195
|
+
} else if (type === 'portal:wasm:generateResult') {
|
|
196
|
+
// Remove the event listener
|
|
197
|
+
window.removeEventListener('message', handleGenerate)
|
|
198
|
+
|
|
199
|
+
// Resolve the promise with the result
|
|
200
|
+
resolve(result)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Bind the function to the message event
|
|
205
|
+
window.addEventListener('message', handleGenerate)
|
|
206
|
+
|
|
207
|
+
// Send the request to the iframe
|
|
208
|
+
this.postMessage(message)
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public async getAddress(): Promise<string> {
|
|
213
|
+
const message = {
|
|
214
|
+
type: 'portal:address',
|
|
215
|
+
data: {},
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
// Create a function to be bound when getAddress is triggered
|
|
220
|
+
const handleGetAddress = (event: MessageEvent<WorkerResult>) => {
|
|
221
|
+
const { type, data: result } = event.data
|
|
222
|
+
|
|
223
|
+
// Check that the message is the one we're looking for
|
|
224
|
+
if (type === 'portal:addressResult') {
|
|
225
|
+
// Remove the event listener
|
|
226
|
+
window.removeEventListener('message', handleGetAddress)
|
|
227
|
+
|
|
228
|
+
// Resolve the promise with the result
|
|
229
|
+
resolve(result)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Bind the function to the message event
|
|
234
|
+
window.addEventListener('message', handleGetAddress)
|
|
235
|
+
|
|
236
|
+
// Send the request to the iframe
|
|
237
|
+
this.postMessage(message)
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// In Progress
|
|
242
|
+
public async recover(data: RecoverArgs): Promise<string> {
|
|
243
|
+
await this.recoverBackup(data)
|
|
244
|
+
// TODO: Write the DKG Result to local storage
|
|
245
|
+
const cipherText = await this.recoverSigning(data)
|
|
246
|
+
|
|
247
|
+
return JSON.stringify(cipherText)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// In Progress
|
|
251
|
+
private async recoverBackup(data: RecoverArgs): Promise<GenerateResult> {
|
|
252
|
+
const message = {
|
|
253
|
+
type: 'portal:wasm:recoverBackup',
|
|
254
|
+
data,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
// Create a function to be bound when recoverSigning is triggered
|
|
259
|
+
const handleRecovery = (event: MessageEvent<GenerateResult>) => {
|
|
260
|
+
const { type, data: result } = event
|
|
261
|
+
|
|
262
|
+
// Check that the message is the one we're looking for
|
|
263
|
+
if (type === 'portal:wasm:recoverBackupResult') {
|
|
264
|
+
// Check if the result is an error
|
|
265
|
+
if (result.error && result.error.code > 0) {
|
|
266
|
+
// Remove the event listener
|
|
267
|
+
window.removeEventListener('message', handleRecovery)
|
|
268
|
+
|
|
269
|
+
// Reject the promise with the error
|
|
270
|
+
return reject(new PortalMpcError(result.error))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Remove the event listener
|
|
274
|
+
window.removeEventListener('message', handleRecovery)
|
|
275
|
+
|
|
276
|
+
// Resolve the promise with the result
|
|
277
|
+
resolve(result)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Bind the function to the message event
|
|
282
|
+
window.addEventListener('message', handleRecovery)
|
|
283
|
+
|
|
284
|
+
// Send the request to the iframe
|
|
285
|
+
this.postMessage(message)
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// In Progress
|
|
290
|
+
private async recoverSigning(data: RecoverArgs): Promise<GenerateResult> {
|
|
291
|
+
const message = {
|
|
292
|
+
type: 'portal:wasm:recoverSigning',
|
|
293
|
+
data,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return new Promise((resolve, reject) => {
|
|
297
|
+
// Create a function to be bound when recoverSigning is triggered
|
|
298
|
+
const handleRecovery = (event: MessageEvent<GenerateResult>) => {
|
|
299
|
+
const { type, data: result } = event
|
|
300
|
+
|
|
301
|
+
// Check that the message is the one we're looking for
|
|
302
|
+
if (type === 'portal:wasm:recoverSigningResult') {
|
|
303
|
+
// Check if the result is an error
|
|
304
|
+
if (result.error && result.error.code > 0) {
|
|
305
|
+
// Remove the event listener
|
|
306
|
+
window.removeEventListener('message', handleRecovery)
|
|
307
|
+
|
|
308
|
+
// Reject the promise with the error
|
|
309
|
+
return reject(new PortalMpcError(result.error))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Remove the event listener
|
|
313
|
+
window.removeEventListener('message', handleRecovery)
|
|
314
|
+
|
|
315
|
+
// Resolve the promise with the result
|
|
316
|
+
resolve(result)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Bind the function to the message event
|
|
321
|
+
window.addEventListener('message', handleRecovery)
|
|
322
|
+
|
|
323
|
+
// Send the request to the iframe
|
|
324
|
+
this.postMessage(message)
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public async sign(data: SignArgs): Promise<string> {
|
|
329
|
+
const message = {
|
|
330
|
+
type: 'portal:wasm:sign',
|
|
331
|
+
data,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return new Promise((resolve, reject) => {
|
|
335
|
+
// Create a function to be bound when sign is triggered
|
|
336
|
+
const handleSign = (event: MessageEvent<WorkerResult>) => {
|
|
337
|
+
const { type, data: result } = event.data
|
|
338
|
+
|
|
339
|
+
if (type === 'portal:wasm:signError') {
|
|
340
|
+
// Remove the event listener
|
|
341
|
+
window.removeEventListener('message', handleSign)
|
|
342
|
+
|
|
343
|
+
// Reject the promise with the error
|
|
344
|
+
return reject(new PortalMpcError(result as PortalError))
|
|
345
|
+
} else if (type === 'portal:wasm:signResult') {
|
|
346
|
+
// Remove the event listener
|
|
347
|
+
window.removeEventListener('message', handleSign)
|
|
348
|
+
|
|
349
|
+
// Resolve the promise with the result
|
|
350
|
+
resolve(result as string)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Bind the function to the message event
|
|
355
|
+
window.addEventListener('message', handleSign)
|
|
356
|
+
|
|
357
|
+
// Send the request to the iframe
|
|
358
|
+
this.postMessage(message)
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/***************************
|
|
363
|
+
* Private Methods
|
|
364
|
+
***************************/
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Appends the iframe to the document body
|
|
368
|
+
*/
|
|
369
|
+
private appendIframe() {
|
|
370
|
+
const source = this.webHost.startsWith('locahost:')
|
|
371
|
+
? `http://${this.webHost}/${WEB_SDK_VERSION}/iframe/index.html?t=${Date.now()}`
|
|
372
|
+
: `https://${this.webHost}/${WEB_SDK_VERSION}/iframe/index.html?t=${Date.now()}`
|
|
373
|
+
|
|
374
|
+
const iframe = document.createElement('iframe')
|
|
375
|
+
iframe.height = '0'
|
|
376
|
+
iframe.width = '0'
|
|
377
|
+
iframe.src = source
|
|
378
|
+
iframe.addEventListener('load', this.configureIframe)
|
|
379
|
+
|
|
380
|
+
document.body.appendChild(iframe)
|
|
381
|
+
|
|
382
|
+
this.iframe = iframe
|
|
383
|
+
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private configureIframe() {
|
|
387
|
+
const config: IframeConfigurationOptions = {
|
|
388
|
+
apiHost: this.apiHost,
|
|
389
|
+
apiKey: this.apiKey,
|
|
390
|
+
autoApprove: this.autoApprove,
|
|
391
|
+
chainId: this.chainId,
|
|
392
|
+
mpcHost: this.mpcHost,
|
|
393
|
+
rpcUrl: this.rpcUrl,
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const message = {
|
|
397
|
+
type: 'portal:configure',
|
|
398
|
+
data: config
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.postMessage(message)
|
|
402
|
+
|
|
403
|
+
this.waitForReadyMessage()
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private getOrigin(): string {
|
|
407
|
+
const origin = this.webHost.startsWith('localhost:')
|
|
408
|
+
? `http://${this.webHost}`
|
|
409
|
+
: `https://${this.webHost}`
|
|
410
|
+
|
|
411
|
+
return origin
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private postMessage(event: { type: string, data: any }) {
|
|
415
|
+
console.log(`[MpcProxy] Sending new message: `, event, this.iframe?.contentWindow)
|
|
416
|
+
this.iframe?.contentWindow?.postMessage(event, this.getOrigin())
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private waitForReadyMessage() {
|
|
420
|
+
const handleReady = async (message: MessageEvent<WorkerResult>) => {
|
|
421
|
+
const { type, data } = message.data
|
|
422
|
+
|
|
423
|
+
if (type === 'portal:wasm:ready' && data === true) {
|
|
424
|
+
// Unbind the event listener
|
|
425
|
+
window.removeEventListener('message', handleReady)
|
|
426
|
+
|
|
427
|
+
// Update ready state
|
|
428
|
+
this.portal.ready = true
|
|
429
|
+
|
|
430
|
+
// Update the address
|
|
431
|
+
const address = await this.getAddress()
|
|
432
|
+
this.portal.address = address
|
|
433
|
+
|
|
434
|
+
// Trigger the ready callback
|
|
435
|
+
this.portal.triggerReady()
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
window.addEventListener('message', handleReady)
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export default Mpc
|