@portal-hq/provider 0.2.11 → 0.2.12

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,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Provider = void 0;
7
+ var providers_1 = require("./providers");
8
+ Object.defineProperty(exports, "Provider", { enumerable: true, get: function () { return __importDefault(providers_1).default; } });
@@ -0,0 +1,376 @@
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 requesters_1 = require("../requesters");
14
+ const signers_1 = require("../signers");
15
+ const passiveSignerMethods = [
16
+ 'eth_accounts',
17
+ 'eth_chainId',
18
+ 'eth_requestAccounts',
19
+ ];
20
+ const signerMethods = [
21
+ 'eth_accounts',
22
+ 'eth_chainId',
23
+ 'eth_requestAccounts',
24
+ 'eth_sendTransaction',
25
+ 'eth_sign',
26
+ 'eth_signTransaction',
27
+ 'eth_signTypedData',
28
+ 'personal_sign',
29
+ ];
30
+ class Provider {
31
+ constructor({
32
+ // Required options
33
+ apiKey, chainId, keychain,
34
+ // Optional options
35
+ apiUrl = 'api.portalhq.io', autoApprove = false, enableMpc = true, mpcUrl = 'mpc.portalhq.io', gatewayConfig = {}, }) {
36
+ this._address = '';
37
+ // Handle required fields
38
+ if (!apiKey || apiKey.length === 0) {
39
+ throw new utils_1.InvalidApiKeyError();
40
+ }
41
+ if (!chainId) {
42
+ throw new utils_1.InvalidChainIdError();
43
+ }
44
+ if (!gatewayConfig) {
45
+ throw new utils_1.InvalidGatewayConfigError();
46
+ }
47
+ // Handle the stuff we can auto-set
48
+ this.apiKey = apiKey;
49
+ this.apiUrl = apiUrl;
50
+ this.autoApprove = autoApprove;
51
+ this.chainId = chainId;
52
+ this.events = {};
53
+ this.isMPC = enableMpc;
54
+ this.keychain = keychain;
55
+ this.log = console;
56
+ this.mpcUrl = mpcUrl;
57
+ this.portal = new requesters_1.HttpRequester({
58
+ baseUrl: this.apiUrl,
59
+ });
60
+ // Handle RPC Initialization
61
+ this.gatewayConfig = gatewayConfig;
62
+ this.rpc = new requesters_1.HttpRequester({
63
+ baseUrl: this.getRpcUrl(),
64
+ });
65
+ if (this.isMPC) {
66
+ // If MPC is enabled, initialize an MpcSigner
67
+ this.signer = new signers_1.MpcSigner({
68
+ mpcUrl: this.mpcUrl,
69
+ keychain: this.keychain,
70
+ });
71
+ }
72
+ else {
73
+ // If MPC is disabled, initialize an HttpSigner, talking to whatever httpHost was provided
74
+ this.signer = new signers_1.HttpSigner({
75
+ keychain: this.keychain,
76
+ portal: this.portal,
77
+ });
78
+ }
79
+ this.dispatchConnect();
80
+ }
81
+ get address() {
82
+ return this._address;
83
+ }
84
+ set address(value) {
85
+ this._address = value;
86
+ if (this.signer && this.isMPC) {
87
+ ;
88
+ this.signer._address = value;
89
+ }
90
+ }
91
+ get rpcUrl() {
92
+ return this.getRpcUrl();
93
+ }
94
+ /**
95
+ * Invokes all registered event handlers with the data provided
96
+ * - If any `once` handlers exist, they are removed after all handlers are invoked
97
+ *
98
+ * @param event The name of the event to be handled
99
+ * @param data The data to be passed to registered event handlers
100
+ * @returns BaseProvider
101
+ */
102
+ emit(event, data) {
103
+ // Grab the registered event handlers if any are available
104
+ const handlers = this.events[event] || [];
105
+ // Execute every event handler
106
+ for (const registeredEventHandler of handlers) {
107
+ registeredEventHandler.handler(data);
108
+ }
109
+ // Remove any registered event handlers with the `once` flag
110
+ this.events[event] = handlers.filter((handler) => !handler.once);
111
+ return this;
112
+ }
113
+ /**
114
+ * Registers an event handler for the provided event
115
+ *
116
+ * @param event The event name to add a handler to
117
+ * @param callback The callback to be invoked when the event is emitted
118
+ * @returns BaseProvider
119
+ */
120
+ on(event, callback) {
121
+ // If no handlers are registered for this event, create an entry for the event
122
+ if (!this.events[event]) {
123
+ this.events[event] = [];
124
+ }
125
+ // Register event handler with the rudimentary event bus
126
+ if (typeof callback !== 'undefined') {
127
+ this.events[event].push({
128
+ handler: callback,
129
+ once: false,
130
+ });
131
+ }
132
+ return this;
133
+ }
134
+ /**
135
+ * Registers a single-execution event handler for the provided event
136
+ *
137
+ * @param event The event name to add a handler to
138
+ * @param callback The callback to be invoked the next time the event is emitted
139
+ * @returns BaseProvider
140
+ */
141
+ once(event, callback) {
142
+ // If no handlers are registered for this event, create an entry for the event
143
+ if (!this.events[event]) {
144
+ this.events[event] = [];
145
+ }
146
+ // Register event handler with the rudimentary event bus
147
+ if (typeof callback !== 'undefined') {
148
+ this.events[event].push({
149
+ handler: callback,
150
+ once: true,
151
+ });
152
+ }
153
+ return this;
154
+ }
155
+ removeEventListener(event, listenerToRemove) {
156
+ if (!this.events[event]) {
157
+ this.log.info(`[PortalProvider] Attempted to remove a listener from unregistered event '${event}'. Ignoring.`);
158
+ return;
159
+ }
160
+ if (!listenerToRemove) {
161
+ this.events[event] = [];
162
+ }
163
+ else {
164
+ const filterEventHandlers = (registeredEventHandler) => {
165
+ return registeredEventHandler.handler !== listenerToRemove;
166
+ };
167
+ this.events[event] = this.events[event].filter(filterEventHandlers);
168
+ }
169
+ }
170
+ /**
171
+ * Handles request routing in compliance with the EIP-1193 Ethereum Javascript Provider API
172
+ * - See here for more info: https://eips.ethereum.org/EIPS/eip-1193
173
+ *
174
+ * @param args The arguments of the request being made
175
+ * @returns Promise<any>
176
+ */
177
+ request({ method, params }) {
178
+ return __awaiter(this, void 0, void 0, function* () {
179
+ if (method === 'eth_chainId') {
180
+ return this.chainId;
181
+ }
182
+ const isSignerMethod = signerMethods.includes(method);
183
+ let result;
184
+ if (!isSignerMethod && !method.startsWith('wallet_')) {
185
+ // Send to Gateway for RPC calls
186
+ const response = yield this.handleGatewayRequests({ method, params });
187
+ this.emit('portal_signatureReceived', {
188
+ method,
189
+ params,
190
+ signature: response,
191
+ });
192
+ if (response.error) {
193
+ throw new utils_1.ProviderRpcError(response.error);
194
+ }
195
+ result = response.result;
196
+ }
197
+ else if (isSignerMethod) {
198
+ // Handle signing
199
+ const transactionHash = yield this.handleSigningRequests({
200
+ method,
201
+ params,
202
+ });
203
+ if (transactionHash) {
204
+ console.log(`Received transaction hash: `, transactionHash);
205
+ this.emit('portal_signatureReceived', {
206
+ method,
207
+ params,
208
+ signature: transactionHash,
209
+ });
210
+ result = transactionHash;
211
+ }
212
+ }
213
+ else {
214
+ // Unsupported method
215
+ throw new utils_1.ProviderRpcError({
216
+ code: utils_1.RpcErrorCodes.UnsupportedMethod,
217
+ data: {
218
+ method,
219
+ params,
220
+ },
221
+ });
222
+ }
223
+ return result;
224
+ });
225
+ }
226
+ /**
227
+ * Updates the chainId of this instance and builds a new RPC HttpRequester for
228
+ * the gateway used for the new chain
229
+ *
230
+ * @param chainId A hex string of the chainId to use for this connection
231
+ * @returns BaseProvider
232
+ */
233
+ updateChainId(chainId) {
234
+ return __awaiter(this, void 0, void 0, function* () {
235
+ this.chainId = Number(`${chainId}`);
236
+ this.rpc = new requesters_1.HttpRequester({
237
+ baseUrl: this.getRpcUrl(),
238
+ });
239
+ this.emit('chainChanged', { chainId });
240
+ return this;
241
+ });
242
+ }
243
+ /**
244
+ * Kicks off the approval flow for a given request
245
+ *
246
+ * @param args The arguments of the request being made
247
+ */
248
+ getApproval({ method, params, }) {
249
+ return __awaiter(this, void 0, void 0, function* () {
250
+ // If autoApprove is enabled, just resolve to true
251
+ if (this.autoApprove) {
252
+ return true;
253
+ }
254
+ if (!this.events['portal_signingRequested']) {
255
+ throw new Error(`[PortalProvider] Auto-approve is disabled. Cannot perform signing requests without an event handler for the 'portal_signingRequested' event.`);
256
+ }
257
+ return new Promise((resolve) => {
258
+ // Remove already used listeners
259
+ this.removeEventListener('portal_signingApproved');
260
+ this.removeEventListener('portal_signingRejected');
261
+ // If the signing has been approved, resolve to true
262
+ this.once('portal_signingApproved', ({ method: approvedMethod, params: approvedParams }) => {
263
+ console.log(`[PortalProvider] Signing Approved`, method, params);
264
+ // Remove already used listeners
265
+ this.removeEventListener('portal_signingApproved');
266
+ this.removeEventListener('portal_signingRejected');
267
+ // First verify that this is the same signing request
268
+ if (method === approvedMethod &&
269
+ JSON.stringify(params) === JSON.stringify(approvedParams)) {
270
+ resolve(true);
271
+ }
272
+ });
273
+ // If the signing request has been rejected, resolve to false
274
+ this.once('portal_signingRejected', ({ method: rejectedMethod, params: rejectedParams }) => {
275
+ console.log(`[PortalProvider] Signing Approved`, method, params);
276
+ // Remove already used listeners
277
+ this.removeEventListener('portal_signingApproved');
278
+ this.removeEventListener('portal_signingRejected');
279
+ // First verify that this is the same signing request
280
+ if (method === rejectedMethod &&
281
+ JSON.stringify(params) === JSON.stringify(rejectedParams)) {
282
+ resolve(false);
283
+ }
284
+ });
285
+ // Tell any listening clients that signing has been requested
286
+ this.emit('portal_signingRequested', {
287
+ method,
288
+ params,
289
+ });
290
+ });
291
+ });
292
+ }
293
+ dispatchConnect() {
294
+ return __awaiter(this, void 0, void 0, function* () {
295
+ console.log(`[PortalProvider] Connected on chainId: 0x${this.chainId.toString(16)}`);
296
+ this.emit('connect', {
297
+ chainId: `0x${this.chainId.toString(16)}`,
298
+ });
299
+ });
300
+ }
301
+ /**
302
+ * Determines the RPC URL to be used for the current chain
303
+ *
304
+ * @returns string
305
+ */
306
+ getRpcUrl() {
307
+ if (typeof this.gatewayConfig === 'string') {
308
+ // If the gatewayConfig is just a static URL, return that
309
+ return this.gatewayConfig;
310
+ }
311
+ else if (typeof this.gatewayConfig === 'object' &&
312
+ !this.gatewayConfig.hasOwnProperty(this.chainId)) {
313
+ // If there's no explicit mapping for the current chainId, error out
314
+ throw new Error(`[PortalProvider] No RPC endpoint configured for chainId: ${this.chainId}`);
315
+ }
316
+ // Get the entry for the current chainId from the gatewayConfig
317
+ const config = this.gatewayConfig[this.chainId];
318
+ if (typeof config === 'string') {
319
+ return config;
320
+ }
321
+ // If we got this far, there's no way to support the chain with the current config
322
+ throw new Error(`[PortalProvider] Could not find a valid gatewayConfig entry for chainId: ${this.chainId}`);
323
+ }
324
+ /**
325
+ * Sends the provided request payload along to the RPC HttpRequester
326
+ *
327
+ * @param args The arguments of the request being made
328
+ * @returns Promise<any>
329
+ */
330
+ handleGatewayRequests({ method, params, }) {
331
+ return __awaiter(this, void 0, void 0, function* () {
332
+ // Pass request off to the gateway
333
+ return yield this.rpc.post('', {
334
+ body: {
335
+ jsonrpc: '2.0',
336
+ id: this.chainId,
337
+ method,
338
+ params,
339
+ },
340
+ });
341
+ });
342
+ }
343
+ /**
344
+ * Sends the provided request payload along to the Signer
345
+ *
346
+ * @param args The arguments of the request being made
347
+ * @returns Promise<any>
348
+ */
349
+ handleSigningRequests({ method, params, }) {
350
+ var _a;
351
+ return __awaiter(this, void 0, void 0, function* () {
352
+ const isApproved = passiveSignerMethods.includes(method)
353
+ ? true
354
+ : yield this.getApproval({ method, params });
355
+ if (!isApproved) {
356
+ this.log.info(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
357
+ return;
358
+ }
359
+ switch (method) {
360
+ case 'eth_chainId':
361
+ return `0x${this.chainId.toString(16)}`;
362
+ case 'eth_accounts':
363
+ case 'eth_requestAccounts':
364
+ case 'eth_sendTransaction':
365
+ case 'eth_sign':
366
+ case 'eth_signTransaction':
367
+ case 'eth_signTypedData':
368
+ case 'personal_sign':
369
+ return yield ((_a = this.signer) === null || _a === void 0 ? void 0 : _a.sign({ chainId: this.chainId, method, params }, this));
370
+ default:
371
+ throw new Error('[PortalProvider] Method "' + method + '" not supported');
372
+ }
373
+ });
374
+ }
375
+ }
376
+ exports.default = Provider;
@@ -0,0 +1,52 @@
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
+ class HttpRequester {
14
+ constructor({ baseUrl }) {
15
+ this.baseUrl = baseUrl.startsWith('https://')
16
+ ? baseUrl
17
+ : `https://${baseUrl}`;
18
+ }
19
+ get(path, options) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ const requestOptions = {
22
+ method: 'GET',
23
+ url: `${this.baseUrl}${path}`,
24
+ };
25
+ if (options && options.headers) {
26
+ requestOptions.headers = this.buildHeaders(options.headers);
27
+ }
28
+ const request = new utils_1.HttpRequest(requestOptions);
29
+ const response = (yield request.send());
30
+ return response;
31
+ });
32
+ }
33
+ post(path, options) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ const requestOptions = {
36
+ method: 'POST',
37
+ url: `${this.baseUrl}${path}`,
38
+ };
39
+ requestOptions.headers = this.buildHeaders(options && options.headers ? options.headers : {});
40
+ if (options && options.body) {
41
+ requestOptions.body = options.body;
42
+ }
43
+ const request = new utils_1.HttpRequest(requestOptions);
44
+ const response = (yield request.send());
45
+ return response;
46
+ });
47
+ }
48
+ buildHeaders(headers) {
49
+ return Object.assign({ 'Content-Type': 'application/json' }, headers);
50
+ }
51
+ }
52
+ exports.default = HttpRequester;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HttpRequester = void 0;
7
+ var http_1 = require("./http");
8
+ Object.defineProperty(exports, "HttpRequester", { enumerable: true, get: function () { return __importDefault(http_1).default; } });
@@ -0,0 +1,19 @@
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
+ class Signer {
13
+ sign(_, __) {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ throw new Error('[Portal] sign() method must be implemented in a child of BaseSigner');
16
+ });
17
+ }
18
+ }
19
+ exports.default = Signer;
@@ -0,0 +1,56 @@
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
+ class HttpSigner {
14
+ constructor(opts) {
15
+ if (!opts.portal) {
16
+ throw new utils_1.MissingOptionError({
17
+ className: 'HttpSigner',
18
+ option: 'portal',
19
+ });
20
+ }
21
+ this.keychain = opts.keychain;
22
+ this.portal = opts.portal;
23
+ }
24
+ sign(message, provider) {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ const address = yield this.keychain.getAddress();
27
+ const { chainId, method, params } = message;
28
+ switch (method) {
29
+ case 'eth_requestAccounts':
30
+ return [address];
31
+ case 'eth_accounts':
32
+ return [address];
33
+ default:
34
+ break;
35
+ }
36
+ console.log(`[Portal:HttpSigner] Requesting signature from exchange for:`, JSON.stringify({
37
+ chainId,
38
+ method,
39
+ params: JSON.stringify([params]),
40
+ }, null, 2));
41
+ const signatureResponse = yield this.portal.post('/api/v1/clients/transactions/sign', {
42
+ body: {
43
+ chainId,
44
+ method,
45
+ params: JSON.stringify([params]),
46
+ },
47
+ headers: {
48
+ Authorization: `Bearer ${provider.apiKey}`,
49
+ 'Content-Type': 'application/json',
50
+ },
51
+ });
52
+ return signatureResponse;
53
+ });
54
+ }
55
+ }
56
+ exports.default = HttpSigner;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MpcSigner = exports.HttpSigner = exports.Signer = void 0;
7
+ var abstract_1 = require("./abstract");
8
+ Object.defineProperty(exports, "Signer", { enumerable: true, get: function () { return __importDefault(abstract_1).default; } });
9
+ var http_1 = require("./http");
10
+ Object.defineProperty(exports, "HttpSigner", { enumerable: true, get: function () { return __importDefault(http_1).default; } });
11
+ var mpc_1 = require("./mpc");
12
+ Object.defineProperty(exports, "MpcSigner", { enumerable: true, get: function () { return __importDefault(mpc_1).default; } });
@@ -0,0 +1,76 @@
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 react_native_1 = require("react-native");
13
+ const utils_1 = require("@portal-hq/utils");
14
+ class MpcSigner {
15
+ constructor(opts) {
16
+ this._address = '';
17
+ this.buildParams = (method, txParams) => {
18
+ let params = txParams;
19
+ switch (method) {
20
+ case 'eth_sign':
21
+ case 'personal_sign':
22
+ if (!Array.isArray(txParams)) {
23
+ params = [txParams];
24
+ }
25
+ break;
26
+ default:
27
+ if (Array.isArray(txParams)) {
28
+ params = txParams[0];
29
+ }
30
+ }
31
+ return params;
32
+ };
33
+ this.keychain = opts.keychain;
34
+ this.mpc = react_native_1.NativeModules.PortalMobileMpc;
35
+ this.mpcUrl = opts.mpcUrl;
36
+ if (!this.mpc) {
37
+ throw new Error(`[Portal.Provider.MpcSigner] The MPC module could not be found by the signer. This is usually an issue with React Native linking. Please verify that the 'PortalReactNative' module is properly linked to this project.`);
38
+ }
39
+ }
40
+ get address() {
41
+ return (() => __awaiter(this, void 0, void 0, function* () {
42
+ if (this._address) {
43
+ return this._address;
44
+ }
45
+ const address = yield this.keychain.getAddress();
46
+ this._address = address;
47
+ return address;
48
+ }))();
49
+ }
50
+ sign(message, provider) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ const address = yield this.keychain.getAddress();
53
+ const apiKey = provider.apiKey;
54
+ const { method, params } = message;
55
+ switch (method) {
56
+ case 'eth_requestAccounts':
57
+ return [address];
58
+ case 'eth_accounts':
59
+ return [address];
60
+ default:
61
+ break;
62
+ }
63
+ console.log(`[Portal:MpcSigner] Requesting signature from PortalMobileMpc for:`, JSON.stringify(message, null, 2));
64
+ const dkg = yield this.keychain.getDkgResult();
65
+ console.log(`[PortalMpcSigner] RPC URL: ${provider.rpcUrl}`);
66
+ const result = yield this.mpc.sign(apiKey, this.mpcUrl, dkg, message.method, JSON.stringify(this.buildParams(method, params)), provider.rpcUrl, provider.chainId.toString());
67
+ console.log(`[PortalMpcSigner] Result: `, result);
68
+ const { data, error } = JSON.parse(String(result));
69
+ if (error && error.length) {
70
+ throw new utils_1.MpcSigningError(error);
71
+ }
72
+ return data;
73
+ });
74
+ }
75
+ }
76
+ exports.default = MpcSigner;
@@ -0,0 +1 @@
1
+ export { default as Provider } from './providers';
@@ -0,0 +1,374 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { InvalidApiKeyError, InvalidChainIdError, InvalidGatewayConfigError, ProviderRpcError, RpcErrorCodes, } from '@portal-hq/utils';
11
+ import { HttpRequester } from '../requesters';
12
+ import { HttpSigner, MpcSigner } from '../signers';
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',
26
+ 'personal_sign',
27
+ ];
28
+ class Provider {
29
+ constructor({
30
+ // Required options
31
+ apiKey, chainId, keychain,
32
+ // Optional options
33
+ apiUrl = 'api.portalhq.io', autoApprove = false, enableMpc = true, mpcUrl = 'mpc.portalhq.io', gatewayConfig = {}, }) {
34
+ this._address = '';
35
+ // Handle required fields
36
+ if (!apiKey || apiKey.length === 0) {
37
+ throw new InvalidApiKeyError();
38
+ }
39
+ if (!chainId) {
40
+ throw new InvalidChainIdError();
41
+ }
42
+ if (!gatewayConfig) {
43
+ throw new InvalidGatewayConfigError();
44
+ }
45
+ // Handle the stuff we can auto-set
46
+ this.apiKey = apiKey;
47
+ this.apiUrl = apiUrl;
48
+ this.autoApprove = autoApprove;
49
+ this.chainId = chainId;
50
+ this.events = {};
51
+ this.isMPC = enableMpc;
52
+ this.keychain = keychain;
53
+ this.log = console;
54
+ this.mpcUrl = mpcUrl;
55
+ this.portal = new HttpRequester({
56
+ baseUrl: this.apiUrl,
57
+ });
58
+ // Handle RPC Initialization
59
+ this.gatewayConfig = gatewayConfig;
60
+ this.rpc = new HttpRequester({
61
+ baseUrl: this.getRpcUrl(),
62
+ });
63
+ if (this.isMPC) {
64
+ // If MPC is enabled, initialize an MpcSigner
65
+ this.signer = new MpcSigner({
66
+ mpcUrl: this.mpcUrl,
67
+ keychain: this.keychain,
68
+ });
69
+ }
70
+ else {
71
+ // If MPC is disabled, initialize an HttpSigner, talking to whatever httpHost was provided
72
+ this.signer = new HttpSigner({
73
+ keychain: this.keychain,
74
+ portal: this.portal,
75
+ });
76
+ }
77
+ this.dispatchConnect();
78
+ }
79
+ get address() {
80
+ return this._address;
81
+ }
82
+ set address(value) {
83
+ this._address = value;
84
+ if (this.signer && this.isMPC) {
85
+ ;
86
+ this.signer._address = value;
87
+ }
88
+ }
89
+ get rpcUrl() {
90
+ return this.getRpcUrl();
91
+ }
92
+ /**
93
+ * Invokes all registered event handlers with the data provided
94
+ * - If any `once` handlers exist, they are removed after all handlers are invoked
95
+ *
96
+ * @param event The name of the event to be handled
97
+ * @param data The data to be passed to registered event handlers
98
+ * @returns BaseProvider
99
+ */
100
+ emit(event, data) {
101
+ // Grab the registered event handlers if any are available
102
+ const handlers = this.events[event] || [];
103
+ // Execute every event handler
104
+ for (const registeredEventHandler of handlers) {
105
+ registeredEventHandler.handler(data);
106
+ }
107
+ // Remove any registered event handlers with the `once` flag
108
+ this.events[event] = handlers.filter((handler) => !handler.once);
109
+ return this;
110
+ }
111
+ /**
112
+ * Registers an event handler for the provided event
113
+ *
114
+ * @param event The event name to add a handler to
115
+ * @param callback The callback to be invoked when the event is emitted
116
+ * @returns BaseProvider
117
+ */
118
+ on(event, callback) {
119
+ // If no handlers are registered for this event, create an entry for the event
120
+ if (!this.events[event]) {
121
+ this.events[event] = [];
122
+ }
123
+ // Register event handler with the rudimentary event bus
124
+ if (typeof callback !== 'undefined') {
125
+ this.events[event].push({
126
+ handler: callback,
127
+ once: false,
128
+ });
129
+ }
130
+ return this;
131
+ }
132
+ /**
133
+ * Registers a single-execution event handler for the provided event
134
+ *
135
+ * @param event The event name to add a handler to
136
+ * @param callback The callback to be invoked the next time the event is emitted
137
+ * @returns BaseProvider
138
+ */
139
+ once(event, callback) {
140
+ // If no handlers are registered for this event, create an entry for the event
141
+ if (!this.events[event]) {
142
+ this.events[event] = [];
143
+ }
144
+ // Register event handler with the rudimentary event bus
145
+ if (typeof callback !== 'undefined') {
146
+ this.events[event].push({
147
+ handler: callback,
148
+ once: true,
149
+ });
150
+ }
151
+ return this;
152
+ }
153
+ removeEventListener(event, listenerToRemove) {
154
+ if (!this.events[event]) {
155
+ this.log.info(`[PortalProvider] Attempted to remove a listener from unregistered event '${event}'. Ignoring.`);
156
+ return;
157
+ }
158
+ if (!listenerToRemove) {
159
+ this.events[event] = [];
160
+ }
161
+ else {
162
+ const filterEventHandlers = (registeredEventHandler) => {
163
+ return registeredEventHandler.handler !== listenerToRemove;
164
+ };
165
+ this.events[event] = this.events[event].filter(filterEventHandlers);
166
+ }
167
+ }
168
+ /**
169
+ * Handles request routing in compliance with the EIP-1193 Ethereum Javascript Provider API
170
+ * - See here for more info: https://eips.ethereum.org/EIPS/eip-1193
171
+ *
172
+ * @param args The arguments of the request being made
173
+ * @returns Promise<any>
174
+ */
175
+ request({ method, params }) {
176
+ return __awaiter(this, void 0, void 0, function* () {
177
+ if (method === 'eth_chainId') {
178
+ return this.chainId;
179
+ }
180
+ const isSignerMethod = signerMethods.includes(method);
181
+ let result;
182
+ if (!isSignerMethod && !method.startsWith('wallet_')) {
183
+ // Send to Gateway for RPC calls
184
+ const response = yield this.handleGatewayRequests({ method, params });
185
+ this.emit('portal_signatureReceived', {
186
+ method,
187
+ params,
188
+ signature: response,
189
+ });
190
+ if (response.error) {
191
+ throw new ProviderRpcError(response.error);
192
+ }
193
+ result = response.result;
194
+ }
195
+ else if (isSignerMethod) {
196
+ // Handle signing
197
+ const transactionHash = yield this.handleSigningRequests({
198
+ method,
199
+ params,
200
+ });
201
+ if (transactionHash) {
202
+ console.log(`Received transaction hash: `, transactionHash);
203
+ this.emit('portal_signatureReceived', {
204
+ method,
205
+ params,
206
+ signature: transactionHash,
207
+ });
208
+ result = transactionHash;
209
+ }
210
+ }
211
+ else {
212
+ // Unsupported method
213
+ throw new ProviderRpcError({
214
+ code: RpcErrorCodes.UnsupportedMethod,
215
+ data: {
216
+ method,
217
+ params,
218
+ },
219
+ });
220
+ }
221
+ return result;
222
+ });
223
+ }
224
+ /**
225
+ * Updates the chainId of this instance and builds a new RPC HttpRequester for
226
+ * the gateway used for the new chain
227
+ *
228
+ * @param chainId A hex string of the chainId to use for this connection
229
+ * @returns BaseProvider
230
+ */
231
+ updateChainId(chainId) {
232
+ return __awaiter(this, void 0, void 0, function* () {
233
+ this.chainId = Number(`${chainId}`);
234
+ this.rpc = new HttpRequester({
235
+ baseUrl: this.getRpcUrl(),
236
+ });
237
+ this.emit('chainChanged', { chainId });
238
+ return this;
239
+ });
240
+ }
241
+ /**
242
+ * Kicks off the approval flow for a given request
243
+ *
244
+ * @param args The arguments of the request being made
245
+ */
246
+ getApproval({ method, params, }) {
247
+ return __awaiter(this, void 0, void 0, function* () {
248
+ // If autoApprove is enabled, just resolve to true
249
+ if (this.autoApprove) {
250
+ return true;
251
+ }
252
+ if (!this.events['portal_signingRequested']) {
253
+ throw new Error(`[PortalProvider] Auto-approve is disabled. Cannot perform signing requests without an event handler for the 'portal_signingRequested' event.`);
254
+ }
255
+ return new Promise((resolve) => {
256
+ // Remove already used listeners
257
+ this.removeEventListener('portal_signingApproved');
258
+ this.removeEventListener('portal_signingRejected');
259
+ // If the signing has been approved, resolve to true
260
+ this.once('portal_signingApproved', ({ method: approvedMethod, params: approvedParams }) => {
261
+ console.log(`[PortalProvider] Signing Approved`, method, params);
262
+ // Remove already used listeners
263
+ this.removeEventListener('portal_signingApproved');
264
+ this.removeEventListener('portal_signingRejected');
265
+ // First verify that this is the same signing request
266
+ if (method === approvedMethod &&
267
+ JSON.stringify(params) === JSON.stringify(approvedParams)) {
268
+ resolve(true);
269
+ }
270
+ });
271
+ // If the signing request has been rejected, resolve to false
272
+ this.once('portal_signingRejected', ({ method: rejectedMethod, params: rejectedParams }) => {
273
+ console.log(`[PortalProvider] Signing Approved`, method, params);
274
+ // Remove already used listeners
275
+ this.removeEventListener('portal_signingApproved');
276
+ this.removeEventListener('portal_signingRejected');
277
+ // First verify that this is the same signing request
278
+ if (method === rejectedMethod &&
279
+ JSON.stringify(params) === JSON.stringify(rejectedParams)) {
280
+ resolve(false);
281
+ }
282
+ });
283
+ // Tell any listening clients that signing has been requested
284
+ this.emit('portal_signingRequested', {
285
+ method,
286
+ params,
287
+ });
288
+ });
289
+ });
290
+ }
291
+ dispatchConnect() {
292
+ return __awaiter(this, void 0, void 0, function* () {
293
+ console.log(`[PortalProvider] Connected on chainId: 0x${this.chainId.toString(16)}`);
294
+ this.emit('connect', {
295
+ chainId: `0x${this.chainId.toString(16)}`,
296
+ });
297
+ });
298
+ }
299
+ /**
300
+ * Determines the RPC URL to be used for the current chain
301
+ *
302
+ * @returns string
303
+ */
304
+ getRpcUrl() {
305
+ if (typeof this.gatewayConfig === 'string') {
306
+ // If the gatewayConfig is just a static URL, return that
307
+ return this.gatewayConfig;
308
+ }
309
+ else if (typeof this.gatewayConfig === 'object' &&
310
+ !this.gatewayConfig.hasOwnProperty(this.chainId)) {
311
+ // If there's no explicit mapping for the current chainId, error out
312
+ throw new Error(`[PortalProvider] No RPC endpoint configured for chainId: ${this.chainId}`);
313
+ }
314
+ // Get the entry for the current chainId from the gatewayConfig
315
+ const config = this.gatewayConfig[this.chainId];
316
+ if (typeof config === 'string') {
317
+ return config;
318
+ }
319
+ // If we got this far, there's no way to support the chain with the current config
320
+ throw new Error(`[PortalProvider] Could not find a valid gatewayConfig entry for chainId: ${this.chainId}`);
321
+ }
322
+ /**
323
+ * Sends the provided request payload along to the RPC HttpRequester
324
+ *
325
+ * @param args The arguments of the request being made
326
+ * @returns Promise<any>
327
+ */
328
+ handleGatewayRequests({ method, params, }) {
329
+ return __awaiter(this, void 0, void 0, function* () {
330
+ // Pass request off to the gateway
331
+ return yield this.rpc.post('', {
332
+ body: {
333
+ jsonrpc: '2.0',
334
+ id: this.chainId,
335
+ method,
336
+ params,
337
+ },
338
+ });
339
+ });
340
+ }
341
+ /**
342
+ * Sends the provided request payload along to the Signer
343
+ *
344
+ * @param args The arguments of the request being made
345
+ * @returns Promise<any>
346
+ */
347
+ handleSigningRequests({ method, params, }) {
348
+ var _a;
349
+ return __awaiter(this, void 0, void 0, function* () {
350
+ const isApproved = passiveSignerMethods.includes(method)
351
+ ? true
352
+ : yield this.getApproval({ method, params });
353
+ if (!isApproved) {
354
+ this.log.info(`[PortalProvider] Request for signing method '${method}' could not be completed because it was not approved by the user.`);
355
+ return;
356
+ }
357
+ switch (method) {
358
+ case 'eth_chainId':
359
+ return `0x${this.chainId.toString(16)}`;
360
+ case 'eth_accounts':
361
+ case 'eth_requestAccounts':
362
+ case 'eth_sendTransaction':
363
+ case 'eth_sign':
364
+ case 'eth_signTransaction':
365
+ case 'eth_signTypedData':
366
+ case 'personal_sign':
367
+ return yield ((_a = this.signer) === null || _a === void 0 ? void 0 : _a.sign({ chainId: this.chainId, method, params }, this));
368
+ default:
369
+ throw new Error('[PortalProvider] Method "' + method + '" not supported');
370
+ }
371
+ });
372
+ }
373
+ }
374
+ export default Provider;
@@ -0,0 +1,50 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { HttpRequest, } from '@portal-hq/utils';
11
+ class HttpRequester {
12
+ constructor({ baseUrl }) {
13
+ this.baseUrl = baseUrl.startsWith('https://')
14
+ ? baseUrl
15
+ : `https://${baseUrl}`;
16
+ }
17
+ get(path, options) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ const requestOptions = {
20
+ method: 'GET',
21
+ url: `${this.baseUrl}${path}`,
22
+ };
23
+ if (options && options.headers) {
24
+ requestOptions.headers = this.buildHeaders(options.headers);
25
+ }
26
+ const request = new HttpRequest(requestOptions);
27
+ const response = (yield request.send());
28
+ return response;
29
+ });
30
+ }
31
+ post(path, options) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const requestOptions = {
34
+ method: 'POST',
35
+ url: `${this.baseUrl}${path}`,
36
+ };
37
+ requestOptions.headers = this.buildHeaders(options && options.headers ? options.headers : {});
38
+ if (options && options.body) {
39
+ requestOptions.body = options.body;
40
+ }
41
+ const request = new HttpRequest(requestOptions);
42
+ const response = (yield request.send());
43
+ return response;
44
+ });
45
+ }
46
+ buildHeaders(headers) {
47
+ return Object.assign({ 'Content-Type': 'application/json' }, headers);
48
+ }
49
+ }
50
+ export default HttpRequester;
@@ -0,0 +1 @@
1
+ export { default as HttpRequester } from './http';
@@ -0,0 +1,17 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ class Signer {
11
+ sign(_, __) {
12
+ return __awaiter(this, void 0, void 0, function* () {
13
+ throw new Error('[Portal] sign() method must be implemented in a child of BaseSigner');
14
+ });
15
+ }
16
+ }
17
+ export default Signer;
@@ -0,0 +1,54 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { MissingOptionError, } from '@portal-hq/utils';
11
+ class HttpSigner {
12
+ constructor(opts) {
13
+ if (!opts.portal) {
14
+ throw new MissingOptionError({
15
+ className: 'HttpSigner',
16
+ option: 'portal',
17
+ });
18
+ }
19
+ this.keychain = opts.keychain;
20
+ this.portal = opts.portal;
21
+ }
22
+ sign(message, provider) {
23
+ return __awaiter(this, void 0, void 0, function* () {
24
+ const address = yield this.keychain.getAddress();
25
+ const { chainId, method, params } = message;
26
+ switch (method) {
27
+ case 'eth_requestAccounts':
28
+ return [address];
29
+ case 'eth_accounts':
30
+ return [address];
31
+ default:
32
+ break;
33
+ }
34
+ console.log(`[Portal:HttpSigner] Requesting signature from exchange for:`, JSON.stringify({
35
+ chainId,
36
+ method,
37
+ params: JSON.stringify([params]),
38
+ }, null, 2));
39
+ const signatureResponse = yield this.portal.post('/api/v1/clients/transactions/sign', {
40
+ body: {
41
+ chainId,
42
+ method,
43
+ params: JSON.stringify([params]),
44
+ },
45
+ headers: {
46
+ Authorization: `Bearer ${provider.apiKey}`,
47
+ 'Content-Type': 'application/json',
48
+ },
49
+ });
50
+ return signatureResponse;
51
+ });
52
+ }
53
+ }
54
+ export default HttpSigner;
@@ -0,0 +1,3 @@
1
+ export { default as Signer } from './abstract';
2
+ export { default as HttpSigner } from './http';
3
+ export { default as MpcSigner } from './mpc';
@@ -0,0 +1,74 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { NativeModules } from 'react-native';
11
+ import { MpcSigningError, } from '@portal-hq/utils';
12
+ class MpcSigner {
13
+ constructor(opts) {
14
+ this._address = '';
15
+ this.buildParams = (method, txParams) => {
16
+ let params = txParams;
17
+ switch (method) {
18
+ case 'eth_sign':
19
+ case 'personal_sign':
20
+ if (!Array.isArray(txParams)) {
21
+ params = [txParams];
22
+ }
23
+ break;
24
+ default:
25
+ if (Array.isArray(txParams)) {
26
+ params = txParams[0];
27
+ }
28
+ }
29
+ return params;
30
+ };
31
+ this.keychain = opts.keychain;
32
+ this.mpc = NativeModules.PortalMobileMpc;
33
+ this.mpcUrl = opts.mpcUrl;
34
+ if (!this.mpc) {
35
+ throw new Error(`[Portal.Provider.MpcSigner] The MPC module could not be found by the signer. This is usually an issue with React Native linking. Please verify that the 'PortalReactNative' module is properly linked to this project.`);
36
+ }
37
+ }
38
+ get address() {
39
+ return (() => __awaiter(this, void 0, void 0, function* () {
40
+ if (this._address) {
41
+ return this._address;
42
+ }
43
+ const address = yield this.keychain.getAddress();
44
+ this._address = address;
45
+ return address;
46
+ }))();
47
+ }
48
+ sign(message, provider) {
49
+ return __awaiter(this, void 0, void 0, function* () {
50
+ const address = yield this.keychain.getAddress();
51
+ const apiKey = provider.apiKey;
52
+ const { method, params } = message;
53
+ switch (method) {
54
+ case 'eth_requestAccounts':
55
+ return [address];
56
+ case 'eth_accounts':
57
+ return [address];
58
+ default:
59
+ break;
60
+ }
61
+ console.log(`[Portal:MpcSigner] Requesting signature from PortalMobileMpc for:`, JSON.stringify(message, null, 2));
62
+ const dkg = yield this.keychain.getDkgResult();
63
+ console.log(`[PortalMpcSigner] RPC URL: ${provider.rpcUrl}`);
64
+ const result = yield this.mpc.sign(apiKey, this.mpcUrl, dkg, message.method, JSON.stringify(this.buildParams(method, params)), provider.rpcUrl, provider.chainId.toString());
65
+ console.log(`[PortalMpcSigner] Result: `, result);
66
+ const { data, error } = JSON.parse(String(result));
67
+ if (error && error.length) {
68
+ throw new MpcSigningError(error);
69
+ }
70
+ return data;
71
+ });
72
+ }
73
+ }
74
+ export default MpcSigner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@portal-hq/provider",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "license": "MIT",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/esm/index",
@@ -17,7 +17,7 @@
17
17
  "prepare:esm": "tsc --outDir lib/esm --module es2015 --target es2015"
18
18
  },
19
19
  "dependencies": {
20
- "@portal-hq/utils": "^0.2.11"
20
+ "@portal-hq/utils": "^0.2.12"
21
21
  },
22
22
  "devDependencies": {
23
23
  "typescript": "^4.8.4"