Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
replace provider proxy with MultichainApiClientWrapperTransport
  • Loading branch information
jiexi committed Nov 21, 2025
commit cf8c4d84119622ae818effa79a30c282f110eff5
16 changes: 13 additions & 3 deletions packages/connect-multichain/src/multichain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,17 @@ import { DefaultTransport } from './transports/default';
import { MWPTransport } from './transports/mwp';
import { keymanager } from './transports/mwp/KeyManager';
import { getDappId, openDeeplink, setupDappMetadata } from './utils';
import { createProviderProxy } from './proxy/createProviderProxy';
import { MultichainApiClientWrapperTransport } from './transports/multichainApiClientWrapper';

export { getInfuraRpcUrls } from '../domain/multichain/api/infura';

// ENFORCE NAMESPACE THAT CAN BE DISABLED
const logger = createLogger('metamask-sdk:core');

export class MultichainSDK extends MultichainCore {
private __providerProxy = createProviderProxy(this);
private __provider: MultichainApiClient<RPCAPI>;

private __providerTransportWrapper: MultichainApiClientWrapperTransport;

private __transport: ExtendedTransport | undefined = undefined;

Expand All @@ -103,7 +105,7 @@ export class MultichainSDK extends MultichainCore {
}

get provider(): MultichainApiClient<RPCAPI> {
return this.__providerProxy;
return this.__provider;
}

get transport(): ExtendedTransport {
Expand Down Expand Up @@ -146,6 +148,9 @@ export class MultichainSDK extends MultichainCore {
};

super(allOptions);

this.__providerTransportWrapper = new MultichainApiClientWrapperTransport(this);
this.__provider = getMultichainClient({ transport: this.__providerTransportWrapper });
}

static async create(options: MultichainOptions): Promise<MultichainSDK> {
Expand Down Expand Up @@ -213,6 +218,7 @@ export class MultichainSDK extends MultichainCore {
if (hasExtensionInstalled) {
const apiTransport = new DefaultTransport();
this.__transport = apiTransport;
this.__providerTransportWrapper.setupNotifcationListener();
this.listener = apiTransport.onNotification(
this.onTransportNotification.bind(this),
);
Expand All @@ -224,6 +230,7 @@ export class MultichainSDK extends MultichainCore {
const apiTransport = new MWPTransport(dappClient, kvstore);
this.__dappClient = dappClient;
this.__transport = apiTransport;
this.__providerTransportWrapper.setupNotifcationListener();
this.listener = apiTransport.onNotification(
this.onTransportNotification.bind(this),
);
Expand Down Expand Up @@ -302,6 +309,7 @@ export class MultichainSDK extends MultichainCore {
this.__dappClient = dappClient;
const apiTransport = new MWPTransport(dappClient, kvstore);
this.__transport = apiTransport;
this.__providerTransportWrapper.setupNotifcationListener();
this.listener = this.transport.onNotification(
this.onTransportNotification.bind(this),
);
Expand Down Expand Up @@ -415,6 +423,7 @@ export class MultichainSDK extends MultichainCore {
this.onTransportNotification.bind(this),
);
this.__transport = transport;
this.__providerTransportWrapper.setupNotifcationListener();
return transport;
}

Expand Down Expand Up @@ -589,6 +598,7 @@ export class MultichainSDK extends MultichainCore {
this.listener = undefined;
this.__beforeUnloadListener = undefined;
this.__transport = undefined;
this.__providerTransportWrapper.clearNotificationCallbacks();
this.__dappClient = undefined;
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { CreateSessionParams, Transport, TransportRequest, TransportResponse } from "@metamask/multichain-api-client";
import { providerErrors } from "@metamask/rpc-errors";
import { CaipAccountId } from "@metamask/utils";
import { InvokeMethodOptions, RPCAPI, Scope } from "src/domain";
import { MultichainSDK } from "src/multichain";

// uint32 (two's complement) max
// more conservative than Number.MAX_SAFE_INTEGER
const MAX = 4_294_967_295;
let idCounter = Math.floor(Math.random() * MAX);

const getUniqueId = (): number => {
idCounter = (idCounter + 1) % MAX;
return idCounter;
};

type TransportRequestWithId = TransportRequest & { id: number };

export class MultichainApiClientWrapperTransport implements Transport {
private requestId = getUniqueId();
private notificationCallbacks = new Set<(data: unknown) => void>();
constructor(private multichainSDK: MultichainSDK) {
}

clearNotificationCallbacks() {
this.notificationCallbacks.clear();
}

notifyCallbacks(data: unknown) {
this.notificationCallbacks.forEach((callback) => {
callback(data);
});
}

setupNotifcationListener() {
this.multichainSDK.transport.onNotification(this.notifyCallbacks.bind(this));
}

connect(): Promise<void> {
// noop
return Promise.resolve();
}

disconnect(): Promise<void> {
return Promise.resolve();
}

isConnected(): boolean {
return true
}

async request<ParamsType extends TransportRequest, ReturnType extends TransportResponse>(
params: ParamsType,
options: { timeout?: number } = {},
): Promise<ReturnType> {
const id = this.requestId++;
const requestPayload = {
id,
jsonrpc: '2.0',
...params,
};

if (requestPayload.method === 'wallet_createSession') {
return this.#walletCreateSession(requestPayload) as Promise<ReturnType>;
} else if (requestPayload.method === 'wallet_getSession') {
return this.#walletGetSession(requestPayload) as Promise<ReturnType>;
} else if (requestPayload.method === 'wallet_revokeSession') {
return this.#walletRevokeSession(requestPayload) as Promise<ReturnType>;
} else if (requestPayload.method === 'wallet_invokeMethod') {
return this.#walletInvokeMethod(requestPayload) as Promise<ReturnType>;
}

throw new Error(`Unknown method: ${requestPayload.method}`);
}

onNotification(callback: (data: unknown) => void) {
if (!this.multichainSDK.transport) {
this.notificationCallbacks.add(callback);
return () => {
this.notificationCallbacks.delete(callback);
};
}

return this.multichainSDK.transport.onNotification(callback);
}

async #walletCreateSession(request: TransportRequestWithId) {
const createSessionParams = request.params as CreateSessionParams<RPCAPI>;
const scopes = Object.keys({...createSessionParams.optionalScopes, ...createSessionParams.requiredScopes}) as Scope[]
const scopeAccounts: CaipAccountId[] = [];

scopes.forEach((scope) => {
const requiredScope = createSessionParams.requiredScopes?.[scope];
const optionalScope = createSessionParams.optionalScopes?.[scope];
if (requiredScope) {
scopeAccounts.push(...(requiredScope.accounts ?? []));
}

if (optionalScope) {
scopeAccounts.push(...(optionalScope.accounts ?? []));
}
});
const accounts = [...new Set(scopeAccounts)];


await this.multichainSDK.connect(scopes, accounts, createSessionParams.sessionProperties)
return this.multichainSDK.transport.request({ method: 'wallet_getSession' });
}

async #walletGetSession(request: TransportRequestWithId) {
if (!this.multichainSDK.transport) {
return {
jsonrpc: '2.0',
id: request.id,
result: {
"sessionScopes": {}
}
}
}
return this.multichainSDK.transport.request({ method: 'wallet_getSession' });
}

async #walletRevokeSession(request: TransportRequestWithId) {
if (!this.multichainSDK.transport) {
return { jsonrpc: '2.0', id: request.id, result: true };
}

try {
this.multichainSDK.disconnect()
return { jsonrpc: '2.0', id: request.id, result: true }
} catch (error) {
return { jsonrpc: '2.0', id: request.id, result: false }
}
}

async #walletInvokeMethod(request: TransportRequestWithId) {
if (!this.multichainSDK.transport) {
return { error: providerErrors.unauthorized() }
}
return this.multichainSDK.invokeMethod(request.params as InvokeMethodOptions)
}
}