@portal-hq/provider 0.2.0-beta10
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/lib/commonjs/index.js +8 -0
- package/lib/commonjs/providers/index.js +383 -0
- package/lib/commonjs/requesters/http.js +52 -0
- package/lib/commonjs/requesters/index.js +8 -0
- package/lib/commonjs/signers/abstract.js +19 -0
- package/lib/commonjs/signers/http.js +58 -0
- package/lib/commonjs/signers/index.js +12 -0
- package/lib/commonjs/signers/mpc.js +75 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/providers/index.js +381 -0
- package/lib/esm/requesters/http.js +50 -0
- package/lib/esm/requesters/index.js +1 -0
- package/lib/esm/signers/abstract.js +17 -0
- package/lib/esm/signers/http.js +56 -0
- package/lib/esm/signers/index.js +3 -0
- package/lib/esm/signers/mpc.js +73 -0
- package/package.json +25 -0
- package/src/index.ts +3 -0
- package/src/providers/index.ts +494 -0
- package/src/requesters/http.ts +61 -0
- package/src/requesters/index.ts +1 -0
- package/src/signers/abstract.ts +14 -0
- package/src/signers/http.ts +79 -0
- package/src/signers/index.ts +3 -0
- package/src/signers/mpc.ts +114 -0
- package/types.d.ts +98 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InvalidApiKeyError,
|
|
3
|
+
InvalidChainIdError,
|
|
4
|
+
InvalidGatewayConfigError,
|
|
5
|
+
KeychainAdapter,
|
|
6
|
+
ProviderRpcError,
|
|
7
|
+
RpcErrorCodes,
|
|
8
|
+
} from '@portal-hq/utils'
|
|
9
|
+
|
|
10
|
+
import { HttpRequester } from '../requesters'
|
|
11
|
+
import { HttpSigner, MpcSigner, Signer } from '../signers'
|
|
12
|
+
import {
|
|
13
|
+
type EventHandler,
|
|
14
|
+
type GatewayLike,
|
|
15
|
+
type ProviderOptions,
|
|
16
|
+
type RegisteredEventHandler,
|
|
17
|
+
type RequestArguments,
|
|
18
|
+
type SwitchEthereumChainParameter,
|
|
19
|
+
} from '../../types'
|
|
20
|
+
|
|
21
|
+
const passiveSignerMethods = [
|
|
22
|
+
'eth_accounts',
|
|
23
|
+
'eth_chainId',
|
|
24
|
+
'eth_requestAccounts',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const signerMethods = [
|
|
28
|
+
'eth_accounts',
|
|
29
|
+
'eth_chainId',
|
|
30
|
+
'eth_requestAccounts',
|
|
31
|
+
'eth_sendTransaction',
|
|
32
|
+
'eth_sign',
|
|
33
|
+
'eth_signTransaction',
|
|
34
|
+
'eth_signTypedData',
|
|
35
|
+
'personal_sign',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
class Provider {
|
|
39
|
+
public readonly mpcUrl: string
|
|
40
|
+
public readonly portal: HttpRequester
|
|
41
|
+
public apiKey: string
|
|
42
|
+
|
|
43
|
+
public chainId: number
|
|
44
|
+
public isMPC?: boolean
|
|
45
|
+
|
|
46
|
+
private _address: string
|
|
47
|
+
private apiUrl: string
|
|
48
|
+
private autoApprove?: boolean
|
|
49
|
+
private events: Record<string, RegisteredEventHandler[]>
|
|
50
|
+
private keychain: KeychainAdapter
|
|
51
|
+
private log: Console
|
|
52
|
+
private gatewayConfig: GatewayLike
|
|
53
|
+
private rpc: HttpRequester
|
|
54
|
+
|
|
55
|
+
public signer?: Signer
|
|
56
|
+
|
|
57
|
+
constructor({
|
|
58
|
+
// Required options
|
|
59
|
+
apiKey,
|
|
60
|
+
chainId,
|
|
61
|
+
keychain,
|
|
62
|
+
|
|
63
|
+
// Optional options
|
|
64
|
+
apiUrl = 'api.portalhq.io',
|
|
65
|
+
autoApprove = false,
|
|
66
|
+
enableMpc = true,
|
|
67
|
+
mpcUrl = 'mpc.portalhq.io',
|
|
68
|
+
gatewayConfig = {},
|
|
69
|
+
}: ProviderOptions) {
|
|
70
|
+
// Handle required fields
|
|
71
|
+
if (!apiKey || apiKey.length === 0) {
|
|
72
|
+
throw new InvalidApiKeyError()
|
|
73
|
+
}
|
|
74
|
+
if (!chainId) {
|
|
75
|
+
throw new InvalidChainIdError()
|
|
76
|
+
}
|
|
77
|
+
if (!gatewayConfig) {
|
|
78
|
+
throw new InvalidGatewayConfigError()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Handle the stuff we can auto-set
|
|
82
|
+
this.apiKey = apiKey
|
|
83
|
+
this.apiUrl = apiUrl
|
|
84
|
+
this.autoApprove = autoApprove
|
|
85
|
+
this.chainId = chainId
|
|
86
|
+
this.events = {}
|
|
87
|
+
this.isMPC = enableMpc
|
|
88
|
+
this.keychain = keychain
|
|
89
|
+
this.log = console
|
|
90
|
+
this.mpcUrl = mpcUrl
|
|
91
|
+
this.portal = new HttpRequester({
|
|
92
|
+
baseUrl: this.apiUrl,
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Handle RPC Initialization
|
|
96
|
+
this.gatewayConfig = gatewayConfig
|
|
97
|
+
|
|
98
|
+
this.rpc = new HttpRequester({
|
|
99
|
+
baseUrl: this.getRpcUrl(),
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
if (this.isMPC) {
|
|
103
|
+
// If MPC is enabled, initialize an MpcSigner
|
|
104
|
+
this.signer = new MpcSigner({
|
|
105
|
+
mpcUrl: this.mpcUrl,
|
|
106
|
+
keychain: this.keychain,
|
|
107
|
+
})
|
|
108
|
+
} else {
|
|
109
|
+
// If MPC is disabled, initialize an HttpSigner, talking to whatever httpHost was provided
|
|
110
|
+
this.signer = new HttpSigner({
|
|
111
|
+
portal: this.portal,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.dispatchConnect()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
get address(): string {
|
|
119
|
+
return this._address
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
set address(value: string) {
|
|
123
|
+
this._address = value
|
|
124
|
+
|
|
125
|
+
if (this.signer && this.isMPC) {
|
|
126
|
+
;(this.signer as MpcSigner)._address = value
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get rpcUrl(): string {
|
|
131
|
+
return this.getRpcUrl()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Invokes all registered event handlers with the data provided
|
|
136
|
+
* - If any `once` handlers exist, they are removed after all handlers are invoked
|
|
137
|
+
*
|
|
138
|
+
* @param event The name of the event to be handled
|
|
139
|
+
* @param data The data to be passed to registered event handlers
|
|
140
|
+
* @returns BaseProvider
|
|
141
|
+
*/
|
|
142
|
+
public emit(event: string, data: any): Provider {
|
|
143
|
+
// Grab the registered event handlers if any are available
|
|
144
|
+
const handlers = this.events[event] || []
|
|
145
|
+
|
|
146
|
+
// Execute every event handler
|
|
147
|
+
for (const registeredEventHandler of handlers) {
|
|
148
|
+
registeredEventHandler.handler(data)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Remove any registered event handlers with the `once` flag
|
|
152
|
+
this.events[event] = handlers.filter((handler) => !handler.once)
|
|
153
|
+
|
|
154
|
+
return this
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Registers an event handler for the provided event
|
|
159
|
+
*
|
|
160
|
+
* @param event The event name to add a handler to
|
|
161
|
+
* @param callback The callback to be invoked when the event is emitted
|
|
162
|
+
* @returns BaseProvider
|
|
163
|
+
*/
|
|
164
|
+
public on(event: string, callback: EventHandler): Provider {
|
|
165
|
+
// If no handlers are registered for this event, create an entry for the event
|
|
166
|
+
if (!this.events[event]) {
|
|
167
|
+
this.events[event] = []
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Register event handler with the rudimentary event bus
|
|
171
|
+
if (typeof callback !== 'undefined') {
|
|
172
|
+
this.events[event].push({
|
|
173
|
+
handler: callback,
|
|
174
|
+
once: false,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return this
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Registers a single-execution event handler for the provided event
|
|
183
|
+
*
|
|
184
|
+
* @param event The event name to add a handler to
|
|
185
|
+
* @param callback The callback to be invoked the next time the event is emitted
|
|
186
|
+
* @returns BaseProvider
|
|
187
|
+
*/
|
|
188
|
+
public once(event: string, callback: EventHandler): Provider {
|
|
189
|
+
// If no handlers are registered for this event, create an entry for the event
|
|
190
|
+
if (!this.events[event]) {
|
|
191
|
+
this.events[event] = []
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Register event handler with the rudimentary event bus
|
|
195
|
+
if (typeof callback !== 'undefined') {
|
|
196
|
+
this.events[event].push({
|
|
197
|
+
handler: callback,
|
|
198
|
+
once: true,
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return this
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public removeEventListener(
|
|
206
|
+
event: string,
|
|
207
|
+
listenerToRemove?: EventHandler,
|
|
208
|
+
): void {
|
|
209
|
+
if (!this.events[event]) {
|
|
210
|
+
this.log.info(
|
|
211
|
+
`[PortalProvider] Attempted to remove a listener from unregistered event '${event}'. Ignoring.`,
|
|
212
|
+
)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!listenerToRemove) {
|
|
217
|
+
this.events[event] = []
|
|
218
|
+
} else {
|
|
219
|
+
const filterEventHandlers = (
|
|
220
|
+
registeredEventHandler: RegisteredEventHandler,
|
|
221
|
+
) => {
|
|
222
|
+
return registeredEventHandler.handler !== listenerToRemove
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this.events[event] = this.events[event].filter(filterEventHandlers)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Handles request routing in compliance with the EIP-1193 Ethereum Javascript Provider API
|
|
231
|
+
* - See here for more info: https://eips.ethereum.org/EIPS/eip-1193
|
|
232
|
+
*
|
|
233
|
+
* @param args The arguments of the request being made
|
|
234
|
+
* @returns Promise<any>
|
|
235
|
+
*/
|
|
236
|
+
public async request({ method, params }: RequestArguments): Promise<any> {
|
|
237
|
+
if (method === 'eth_chainId') {
|
|
238
|
+
return this.chainId
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const isSignerMethod = signerMethods.includes(method)
|
|
242
|
+
|
|
243
|
+
let result: any
|
|
244
|
+
if (!isSignerMethod && !method.startsWith('wallet_')) {
|
|
245
|
+
// Send to Gateway for RPC calls
|
|
246
|
+
const response = await this.handleGatewayRequests({ method, params })
|
|
247
|
+
|
|
248
|
+
this.emit('portal_signatureReceived', {
|
|
249
|
+
method,
|
|
250
|
+
params,
|
|
251
|
+
signature: response,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
if (response.error) {
|
|
255
|
+
throw new ProviderRpcError(response.error)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
result = response.result
|
|
259
|
+
} else if (isSignerMethod) {
|
|
260
|
+
// Handle signing
|
|
261
|
+
const transactionHash = await this.handleSigningRequests({
|
|
262
|
+
method,
|
|
263
|
+
params,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
if (transactionHash) {
|
|
267
|
+
console.log(`Received transaction hash: `, transactionHash)
|
|
268
|
+
|
|
269
|
+
this.emit('portal_signatureReceived', {
|
|
270
|
+
method,
|
|
271
|
+
params,
|
|
272
|
+
signature: transactionHash,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
result = transactionHash
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Unsupported method
|
|
279
|
+
throw new ProviderRpcError({
|
|
280
|
+
code: RpcErrorCodes.UnsupportedMethod,
|
|
281
|
+
data: {
|
|
282
|
+
method,
|
|
283
|
+
params,
|
|
284
|
+
},
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return result
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Updates the chainId of this instance and builds a new RPC HttpRequester for
|
|
293
|
+
* the gateway used for the new chain
|
|
294
|
+
*
|
|
295
|
+
* @param chainId A hex string of the chainId to use for this connection
|
|
296
|
+
* @returns BaseProvider
|
|
297
|
+
*/
|
|
298
|
+
public async updateChainId(chainId: string): Promise<Provider> {
|
|
299
|
+
this.chainId = Number(`${chainId}`)
|
|
300
|
+
|
|
301
|
+
this.rpc = new HttpRequester({
|
|
302
|
+
baseUrl: this.getRpcUrl(),
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
this.emit('chainChanged', { chainId } as SwitchEthereumChainParameter)
|
|
306
|
+
|
|
307
|
+
return this
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Kicks off the approval flow for a given request
|
|
312
|
+
*
|
|
313
|
+
* @param args The arguments of the request being made
|
|
314
|
+
*/
|
|
315
|
+
protected async getApproval({
|
|
316
|
+
method,
|
|
317
|
+
params,
|
|
318
|
+
}: RequestArguments): Promise<boolean> {
|
|
319
|
+
// If autoApprove is enabled, just resolve to true
|
|
320
|
+
if (this.autoApprove) {
|
|
321
|
+
return true
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (!this.events['portal_signingRequested']) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`[PortalProvider] Auto-approve is disabled. Cannot perform signing requests without an event handler for the 'portal_signingRequested' event.`,
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return new Promise((resolve) => {
|
|
331
|
+
// Remove already used listeners
|
|
332
|
+
this.removeEventListener('portal_signingApproved')
|
|
333
|
+
this.removeEventListener('portal_signingRejected')
|
|
334
|
+
|
|
335
|
+
// If the signing has been approved, resolve to true
|
|
336
|
+
this.once(
|
|
337
|
+
'portal_signingApproved',
|
|
338
|
+
({ method: approvedMethod, params: approvedParams }) => {
|
|
339
|
+
console.log(`[PortalProvider] Signing Approved`, method, params)
|
|
340
|
+
// Remove already used listeners
|
|
341
|
+
this.removeEventListener('portal_signingApproved')
|
|
342
|
+
this.removeEventListener('portal_signingRejected')
|
|
343
|
+
|
|
344
|
+
// First verify that this is the same signing request
|
|
345
|
+
if (
|
|
346
|
+
method === approvedMethod &&
|
|
347
|
+
JSON.stringify(params) === JSON.stringify(approvedParams)
|
|
348
|
+
) {
|
|
349
|
+
resolve(true)
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
// If the signing request has been rejected, resolve to false
|
|
355
|
+
this.once(
|
|
356
|
+
'portal_signingRejected',
|
|
357
|
+
({ method: rejectedMethod, params: rejectedParams }) => {
|
|
358
|
+
console.log(`[PortalProvider] Signing Approved`, method, params)
|
|
359
|
+
// Remove already used listeners
|
|
360
|
+
this.removeEventListener('portal_signingApproved')
|
|
361
|
+
this.removeEventListener('portal_signingRejected')
|
|
362
|
+
|
|
363
|
+
// First verify that this is the same signing request
|
|
364
|
+
if (
|
|
365
|
+
method === rejectedMethod &&
|
|
366
|
+
JSON.stringify(params) === JSON.stringify(rejectedParams)
|
|
367
|
+
) {
|
|
368
|
+
resolve(false)
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
// Tell any listening clients that signing has been requested
|
|
374
|
+
this.emit('portal_signingRequested', {
|
|
375
|
+
method,
|
|
376
|
+
params,
|
|
377
|
+
})
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private async dispatchConnect(): Promise<void> {
|
|
382
|
+
console.log(
|
|
383
|
+
`[PortalProvider] Connected on chainId: 0x${this.chainId.toString(16)}`,
|
|
384
|
+
)
|
|
385
|
+
this.emit('connect', {
|
|
386
|
+
chainId: `0x${this.chainId.toString(16)}`,
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private async dispatchDisconnect(): Promise<void> {
|
|
391
|
+
this.emit('disconnect', {
|
|
392
|
+
error: new ProviderRpcError({
|
|
393
|
+
code: RpcErrorCodes.Disconnected,
|
|
394
|
+
}),
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Determines the RPC URL to be used for the current chain
|
|
400
|
+
*
|
|
401
|
+
* @returns string
|
|
402
|
+
*/
|
|
403
|
+
private getRpcUrl(): string {
|
|
404
|
+
if (typeof this.gatewayConfig === 'string') {
|
|
405
|
+
// If the gatewayConfig is just a static URL, return that
|
|
406
|
+
return this.gatewayConfig
|
|
407
|
+
} else if (
|
|
408
|
+
typeof this.gatewayConfig === 'object' &&
|
|
409
|
+
!this.gatewayConfig.hasOwnProperty(this.chainId)
|
|
410
|
+
) {
|
|
411
|
+
// If there's no explicit mapping for the current chainId, error out
|
|
412
|
+
throw new Error(
|
|
413
|
+
`[PortalProvider] No RPC endpoint configured for chainId: ${this.chainId}`,
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Get the entry for the current chainId from the gatewayConfig
|
|
418
|
+
const config = this.gatewayConfig[this.chainId]
|
|
419
|
+
|
|
420
|
+
if (typeof config === 'string') {
|
|
421
|
+
return config
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// If we got this far, there's no way to support the chain with the current config
|
|
425
|
+
throw new Error(
|
|
426
|
+
`[PortalProvider] Could not find a valid gatewayConfig entry for chainId: ${this.chainId}`,
|
|
427
|
+
)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Sends the provided request payload along to the RPC HttpRequester
|
|
432
|
+
*
|
|
433
|
+
* @param args The arguments of the request being made
|
|
434
|
+
* @returns Promise<any>
|
|
435
|
+
*/
|
|
436
|
+
private async handleGatewayRequests({
|
|
437
|
+
method,
|
|
438
|
+
params,
|
|
439
|
+
}: RequestArguments): Promise<any> {
|
|
440
|
+
// Pass request off to the gateway
|
|
441
|
+
return await this.rpc.post<any>('', {
|
|
442
|
+
body: {
|
|
443
|
+
jsonrpc: '2.0',
|
|
444
|
+
id: this.chainId,
|
|
445
|
+
method,
|
|
446
|
+
params,
|
|
447
|
+
},
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Sends the provided request payload along to the Signer
|
|
453
|
+
*
|
|
454
|
+
* @param args The arguments of the request being made
|
|
455
|
+
* @returns Promise<any>
|
|
456
|
+
*/
|
|
457
|
+
private async handleSigningRequests({
|
|
458
|
+
method,
|
|
459
|
+
params,
|
|
460
|
+
}: RequestArguments): Promise<any> {
|
|
461
|
+
const isApproved = passiveSignerMethods.includes(method)
|
|
462
|
+
? true
|
|
463
|
+
: await this.getApproval({ method, params })
|
|
464
|
+
|
|
465
|
+
if (!isApproved) {
|
|
466
|
+
this.log.info(
|
|
467
|
+
`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`,
|
|
468
|
+
)
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
switch (method) {
|
|
473
|
+
case 'eth_chainId':
|
|
474
|
+
return `0x${this.chainId.toString(16)}`
|
|
475
|
+
case 'eth_accounts':
|
|
476
|
+
case 'eth_requestAccounts':
|
|
477
|
+
case 'eth_sendTransaction':
|
|
478
|
+
case 'eth_sign':
|
|
479
|
+
case 'eth_signTransaction':
|
|
480
|
+
case 'eth_signTypedData':
|
|
481
|
+
case 'personal_sign':
|
|
482
|
+
return await this.signer?.sign(
|
|
483
|
+
{ chainId: this.chainId, method, params },
|
|
484
|
+
this,
|
|
485
|
+
)
|
|
486
|
+
default:
|
|
487
|
+
throw new Error(
|
|
488
|
+
'[PortalProvider] Method "' + method + '" not supported',
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export default Provider
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { HttpRequest } from '@portal-hq/utils'
|
|
2
|
+
import {
|
|
3
|
+
type HttpRequestOptions,
|
|
4
|
+
type HttpOptions,
|
|
5
|
+
type HttpRequesterOptions,
|
|
6
|
+
} from '@portal-hq/utils/types'
|
|
7
|
+
|
|
8
|
+
class HttpRequester {
|
|
9
|
+
private baseUrl: string
|
|
10
|
+
|
|
11
|
+
constructor({ baseUrl }: HttpRequesterOptions) {
|
|
12
|
+
this.baseUrl = baseUrl.startsWith('https://')
|
|
13
|
+
? baseUrl
|
|
14
|
+
: `https://${baseUrl}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async get<T>(path: string, options?: HttpOptions): Promise<T> {
|
|
18
|
+
const requestOptions = {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
url: `${this.baseUrl}${path}`,
|
|
21
|
+
} as HttpRequestOptions
|
|
22
|
+
|
|
23
|
+
if (options && options.headers) {
|
|
24
|
+
requestOptions.headers = this.buildHeaders(options.headers)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const request = new HttpRequest(requestOptions)
|
|
28
|
+
const response = (await request.send()) as T
|
|
29
|
+
|
|
30
|
+
return response
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async post<T>(path: string, options?: HttpOptions): Promise<T> {
|
|
34
|
+
const requestOptions = {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
url: `${this.baseUrl}${path}`,
|
|
37
|
+
} as HttpRequestOptions
|
|
38
|
+
|
|
39
|
+
requestOptions.headers = this.buildHeaders(
|
|
40
|
+
options && options.headers ? options.headers : {},
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if (options && options.body) {
|
|
44
|
+
requestOptions.body = options.body
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const request = new HttpRequest(requestOptions)
|
|
48
|
+
const response = (await request.send()) as T
|
|
49
|
+
|
|
50
|
+
return response
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private buildHeaders(headers: Record<string, any>): Record<string, any> {
|
|
54
|
+
return {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
...headers,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default HttpRequester
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as HttpRequester } from './http'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type SigningRequestArguments, type SignResult } from '../../types'
|
|
2
|
+
|
|
3
|
+
abstract class Signer {
|
|
4
|
+
public async sign(
|
|
5
|
+
message: SigningRequestArguments,
|
|
6
|
+
provider?: any,
|
|
7
|
+
): Promise<SignResult> {
|
|
8
|
+
throw new Error(
|
|
9
|
+
'[Portal] sign() method must be implemented in a child of BaseSigner',
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default Signer
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Keychain } from '@portal-hq/keychain'
|
|
2
|
+
import { MissingOptionError } from '@portal-hq/utils'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
type HttpSignerOptions,
|
|
6
|
+
type SigningRequestArguments,
|
|
7
|
+
type SignResult,
|
|
8
|
+
} from '../../types'
|
|
9
|
+
|
|
10
|
+
import Signer from './abstract'
|
|
11
|
+
import { Provider } from '../index'
|
|
12
|
+
import HttpRequester from '../requesters/http'
|
|
13
|
+
|
|
14
|
+
class HttpSigner implements Signer {
|
|
15
|
+
private portal: HttpRequester
|
|
16
|
+
private keychain: Keychain
|
|
17
|
+
|
|
18
|
+
constructor(opts: HttpSignerOptions) {
|
|
19
|
+
if (!opts.portal) {
|
|
20
|
+
throw new MissingOptionError({
|
|
21
|
+
className: 'HttpSigner',
|
|
22
|
+
option: 'portal',
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.keychain = new Keychain()
|
|
27
|
+
this.portal = opts.portal
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async sign(
|
|
31
|
+
message: SigningRequestArguments,
|
|
32
|
+
provider: Provider,
|
|
33
|
+
): Promise<any> {
|
|
34
|
+
const address = await this.keychain.getAddress()
|
|
35
|
+
const arrayMethods = ['personal_sign']
|
|
36
|
+
const { chainId, method, params } = message
|
|
37
|
+
|
|
38
|
+
switch (method) {
|
|
39
|
+
case 'eth_requestAccounts':
|
|
40
|
+
return [address]
|
|
41
|
+
case 'eth_accounts':
|
|
42
|
+
return [address]
|
|
43
|
+
default:
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(
|
|
48
|
+
`[Portal:HttpSigner] Requesting signature from exchange for:`,
|
|
49
|
+
JSON.stringify(
|
|
50
|
+
{
|
|
51
|
+
chainId,
|
|
52
|
+
method,
|
|
53
|
+
params: JSON.stringify([params]),
|
|
54
|
+
},
|
|
55
|
+
null,
|
|
56
|
+
2,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const signatureResponse = await this.portal.post<SignResult>(
|
|
61
|
+
'/api/v1/clients/transactions/sign',
|
|
62
|
+
{
|
|
63
|
+
body: {
|
|
64
|
+
chainId,
|
|
65
|
+
method,
|
|
66
|
+
params: JSON.stringify([params]),
|
|
67
|
+
},
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Bearer ${provider.apiKey}`,
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return signatureResponse
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default HttpSigner
|