Skip to content

Commit e88f671

Browse files
authored
fix: multichain cold start issue with RPCClient (#1355)
* fix: attempt to fix cold start issue * fix: improve deeplink by using transport directly in Client, to not trigger getSession on provider
1 parent ac8f8c3 commit e88f671

File tree

3 files changed

+53
-28
lines changed

3 files changed

+53
-28
lines changed

packages/sdk-multichain/src/multichain/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,17 +433,23 @@ export class MultichainSDK extends MultichainCore {
433433

434434
this.__provider ??= getMultichainClient({ transport });
435435

436-
const client = new RPCClient(this.provider, this.options.api, sdkInfo);
436+
const client = new RPCClient(this.transport, this.options.api, sdkInfo);
437437
const secure = isSecure();
438438

439-
if (secure && !preferDesktop) {
439+
const shouldOpenDeeplink = secure && !preferDesktop;
440+
441+
// Call the client invoke method first
442+
const invokePromise = client.invokeMethod(request);
443+
444+
// Schedule the deeplink to open 100ms after the invoke method is called
445+
if (shouldOpenDeeplink) {
440446
if (this.options.mobile?.preferredOpenLink) {
441447
this.options.mobile.preferredOpenLink(METAMASK_DEEPLINK_BASE, '_self');
442448
} else {
443449
this.openDeeplink(METAMASK_DEEPLINK_BASE, METAMASK_CONNECT_BASE_URL);
444450
}
445451
}
446452

447-
return client.invokeMethod(request);
453+
return invokePromise as Promise<Json>;
448454
}
449455
}

packages/sdk-multichain/src/multichain/rpc/client.test.ts

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ vi.mock('./client', async () => {
3232
});
3333

3434
t.describe('RPCClient', () => {
35-
let mockProvider: any;
35+
let mockTransport: any;
3636
let mockConfig: MultichainOptions['api'];
3737
let sdkInfo: string;
3838
let rpcClient: RPCClient;
3939
let rpcClientModule: typeof RPCClient;
4040
let defaultHeaders: Record<string, string>;
4141
let headers: Record<string, string>;
4242
let mockFetch: any;
43-
let baseOptions: InvokeMethodOptions;
43+
let baseOptions: any;
4444

4545
t.beforeEach(async () => {
4646
const clientModule = await import('./client');
@@ -51,8 +51,8 @@ t.describe('RPCClient', () => {
5151
params: { address: '0x123', blockNumber: 'latest' },
5252
},
5353
};
54-
mockProvider = {
55-
invokeMethod: t.vi.fn(),
54+
mockTransport = {
55+
request: t.vi.fn(),
5656
};
5757
mockConfig = {
5858
infuraAPIKey: 'test-infura-key',
@@ -61,13 +61,13 @@ t.describe('RPCClient', () => {
6161
},
6262
};
6363
sdkInfo = 'Sdk/Javascript SdkVersion/1.0.0 Platform/web';
64-
rpcClient = new clientModule.RPCClient(mockProvider, mockConfig, sdkInfo);
64+
rpcClient = new clientModule.RPCClient(mockTransport, mockConfig, sdkInfo);
6565
rpcClientModule = clientModule.RPCClient;
6666
// Get mock fetch from the module mock
6767
const fetchModule = await import('cross-fetch');
6868
mockFetch = (fetchModule as any).__mockFetch;
6969
// Reset mocks
70-
mockProvider.invokeMethod.mockClear();
70+
mockTransport.request.mockClear();
7171
mockFetch.mockClear();
7272
defaultHeaders = {
7373
Accept: 'application/json',
@@ -119,7 +119,7 @@ t.describe('RPCClient', () => {
119119
const result = await rpcClient.invokeMethod(baseOptions);
120120

121121
t.expect(result).toBe('0x1234567890abcdef');
122-
t.expect(mockProvider.invokeMethod).not.toHaveBeenCalled();
122+
t.expect(mockTransport.request).not.toHaveBeenCalled();
123123
t.expect(mockFetch).toHaveBeenCalledWith('https://mainnet.infura.io/v3/test-infura-key', {
124124
method: 'POST',
125125
headers: headers,
@@ -164,7 +164,7 @@ t.describe('RPCClient', () => {
164164
infuraAPIKey: 'test-infura-key',
165165
// No readonlyRPCMap provided
166166
};
167-
const clientWithoutRPCMap = new rpcClientModule(mockProvider, configWithoutRPCMap, sdkInfo);
167+
const clientWithoutRPCMap = new rpcClientModule(mockTransport, configWithoutRPCMap, sdkInfo);
168168

169169
const mockJsonResponse = {
170170
jsonrpc: '2.0',
@@ -182,7 +182,7 @@ t.describe('RPCClient', () => {
182182
const result = await clientWithoutRPCMap.invokeMethod(baseOptions);
183183

184184
t.expect(result).toBe('0xabcdef123456');
185-
t.expect(mockProvider.invokeMethod).not.toHaveBeenCalled();
185+
t.expect(mockTransport.request).not.toHaveBeenCalled();
186186

187187
// Should still use Infura URL since infuraAPIKey is provided
188188
t.expect(mockFetch).toHaveBeenCalledWith('https://mainnet.infura.io/v3/test-infura-key', {
@@ -198,7 +198,7 @@ t.describe('RPCClient', () => {
198198
'eip155:1': 'https://custom-ethereum-node.com/rpc',
199199
},
200200
};
201-
const clientWithCustomRPC = new rpcClientModule(mockProvider, configWithCustomRPC, sdkInfo);
201+
const clientWithCustomRPC = new rpcClientModule(mockTransport, configWithCustomRPC, sdkInfo);
202202
const mockJsonResponse = {
203203
jsonrpc: '2.0',
204204
result: '0x123456account12345',
@@ -217,7 +217,7 @@ t.describe('RPCClient', () => {
217217

218218
const result = await clientWithCustomRPC.invokeMethod(baseOptions);
219219
t.expect(result).toBe('0x123456account12345');
220-
t.expect(mockProvider.invokeMethod).not.toHaveBeenCalled();
220+
t.expect(mockTransport.request).not.toHaveBeenCalled();
221221
t.expect(mockFetch).toHaveBeenCalledWith('https://custom-ethereum-node.com/rpc', {
222222
method: 'POST',
223223
headers: defaultHeaders,
@@ -233,10 +233,13 @@ t.describe('RPCClient', () => {
233233
params: { to: '0x123', value: '0x100' },
234234
},
235235
};
236-
mockProvider.invokeMethod.mockResolvedValue('0xhash');
236+
mockTransport.request.mockResolvedValue({ result: '0xhash' });
237237
const result = await rpcClient.invokeMethod(redirectOptions);
238238
t.expect(result).toBe('0xhash');
239-
t.expect(mockProvider.invokeMethod).toHaveBeenCalledWith(redirectOptions);
239+
t.expect(mockTransport.request).toHaveBeenCalledWith({
240+
method: 'wallet_invokeMethod',
241+
params: redirectOptions,
242+
});
240243
t.expect(mockFetch).not.toHaveBeenCalled();
241244
});
242245

@@ -248,10 +251,13 @@ t.describe('RPCClient', () => {
248251
params: { message: 'hello world' },
249252
},
250253
};
251-
mockProvider.invokeMethod.mockResolvedValue('0xsignature');
254+
mockTransport.request.mockResolvedValue({ result: '0xsignature' });
252255
const result = await rpcClient.invokeMethod(signOptions);
253256
t.expect(result).toBe('0xsignature');
254-
t.expect(mockProvider.invokeMethod).toHaveBeenCalledWith(signOptions);
257+
t.expect(mockTransport.request).toHaveBeenCalledWith({
258+
method: 'wallet_invokeMethod',
259+
params: signOptions,
260+
});
255261
t.expect(mockFetch).not.toHaveBeenCalled();
256262
});
257263

@@ -263,19 +269,25 @@ t.describe('RPCClient', () => {
263269
params: { address: '0x123', blockNumber: 'latest' },
264270
},
265271
};
266-
mockProvider.invokeMethod.mockResolvedValue('0xbalance');
272+
mockTransport.request.mockResolvedValue({ result: '0xbalance' });
267273
const result = await rpcClient.invokeMethod(noRpcOptions);
268274
t.expect(result).toBe('0xbalance');
269-
t.expect(mockProvider.invokeMethod).toHaveBeenCalledWith(noRpcOptions);
275+
t.expect(mockTransport.request).toHaveBeenCalledWith({
276+
method: 'wallet_invokeMethod',
277+
params: noRpcOptions,
278+
});
270279
t.expect(mockFetch).not.toHaveBeenCalled();
271280
});
272281

273282
t.it('should redirect to provider when no infura API key and no custom RPC map', async () => {
274-
const noConfigClient = new rpcClientModule(mockProvider, {}, sdkInfo);
275-
mockProvider.invokeMethod.mockResolvedValue('0xfallback');
283+
const noConfigClient = new rpcClientModule(mockTransport, {}, sdkInfo);
284+
mockTransport.request.mockResolvedValue({ result: '0xfallback' });
276285
const result = await noConfigClient.invokeMethod(baseOptions);
277286
t.expect(result).toBe('0xfallback');
278-
t.expect(mockProvider.invokeMethod).toHaveBeenCalledWith(baseOptions);
287+
t.expect(mockTransport.request).toHaveBeenCalledWith({
288+
method: 'wallet_invokeMethod',
289+
params: baseOptions,
290+
});
279291
t.expect(mockFetch).not.toHaveBeenCalled();
280292
});
281293

@@ -287,7 +299,7 @@ t.describe('RPCClient', () => {
287299
params: { to: '0x123', value: '0x100' },
288300
},
289301
};
290-
mockProvider.invokeMethod.mockRejectedValue(new Error('Provider error'));
302+
mockTransport.request.mockRejectedValue(new Error('Provider error'));
291303
await t.expect(rpcClient.invokeMethod(redirectOptions)).rejects.toBeInstanceOf(RPCInvokeMethodErr);
292304
t.expect(mockFetch).not.toHaveBeenCalled();
293305
});

packages/sdk-multichain/src/multichain/rpc/client.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import type { InvokeMethodParams, MultichainApiClient } from '@metamask/multichain-api-client';
1+
import type { InvokeMethodParams } from '@metamask/multichain-api-client';
22
import type { Json } from '@metamask/utils';
33
import fetch from 'cross-fetch';
44

55
import {
6+
type ExtendedTransport,
67
getInfuraRpcUrls,
78
type InvokeMethodOptions,
89
METHODS_TO_REDIRECT,
@@ -26,7 +27,7 @@ export function getNextRpcId() {
2627

2728
export class RPCClient {
2829
constructor(
29-
private readonly provider: MultichainApiClient<RPCAPI>,
30+
private readonly transport: ExtendedTransport,
3031
private readonly config: MultichainOptions['api'],
3132
private readonly sdkInfo: string,
3233
) {}
@@ -109,8 +110,14 @@ export class RPCClient {
109110
return response;
110111
}
111112
try {
112-
const response = await this.provider.invokeMethod(options as InvokeMethodParams<RPCAPI, Scope, never>);
113-
return response;
113+
const response = await this.transport.request({
114+
method: 'wallet_invokeMethod',
115+
params: options,
116+
});
117+
if (response.error) {
118+
throw new RPCInvokeMethodErr(`RPC Request failed with code ${response.error.code}: ${response.error.message}`);
119+
}
120+
return response.result;
114121
} catch (error) {
115122
throw new RPCInvokeMethodErr(error.message);
116123
}

0 commit comments

Comments
 (0)