Skip to content

Commit

Permalink
Add address resolving logic
Browse files Browse the repository at this point in the history
  • Loading branch information
FrederikBolding committed Nov 7, 2024
1 parent b85b44d commit bb9122e
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 21 deletions.
123 changes: 103 additions & 20 deletions packages/snaps-controllers/src/multichain/MultichainRoutingController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import type {
JsonRpcRequest,
SnapId,
} from '@metamask/snaps-sdk';
import { HandlerType, type Caip2ChainId } from '@metamask/snaps-utils';
import { HandlerType } from '@metamask/snaps-utils';
import type { CaipChainId } from '@metamask/utils';
import { hasProperty } from '@metamask/utils';

import { getRunnableSnaps } from '../snaps';
Expand All @@ -45,11 +46,15 @@ type InternalAccount = {
address: string;
options: Record<string, Json>;
methods: string[];
metadata: {
name: string;
snap?: { id: SnapId; enabled: boolean; name: string };
};
};

export type AccountsControllerListMultichainAccountsAction = {
type: `AccountsController:listMultichainAccounts`;
handler: (chainId?: Caip2ChainId) => InternalAccount[];
handler: (chainId?: CaipChainId) => InternalAccount[];
};

export type MultichainRoutingControllerActions =
Expand Down Expand Up @@ -101,16 +106,75 @@ export class MultichainRoutingController extends BaseController<
});
}

#getAccountSnapMethods(chainId: Caip2ChainId) {
const accounts = this.messagingSystem.call(
'AccountsController:listMultichainAccounts',
async #resolveRequestAddress(
snapId: SnapId,
chainId: CaipChainId,
request: JsonRpcRequest,
) {
const result = (await this.messagingSystem.call(
'SnapController:handleRequest',
{
snapId,
origin: 'metamask',
request: {
method: '',
params: {
chainId,
request,
},
},
handler: HandlerType.OnProtocolRequest, // TODO: Export and request format
},
)) as { address: string } | null;
return result?.address;
}

async #getAccountSnap(
protocolSnapId: SnapId,
chainId: CaipChainId,
request: JsonRpcRequest,
) {
const accounts = this.messagingSystem
.call('AccountsController:listMultichainAccounts', chainId)
.filter(
(account) =>
account.metadata.snap?.enabled &&
account.methods.includes(request.method),
);

// If no accounts can service the request, return null.
if (accounts.length === 0) {
return null;
}

// Attempt to resolve the address that should be used for signing.
const address = await this.#resolveRequestAddress(
protocolSnapId,
chainId,
request,
);

return accounts.flatMap((account) => account.methods);
if (!address) {
throw rpcErrors.invalidParams();
}

// TODO: Decide what happens if we have more than one possible account.
const selectedAccount = accounts.find(
(account) => account.address.toLowerCase() === address.toLowerCase(),
);

if (!selectedAccount) {
throw rpcErrors.invalidParams();
}

return {
address: selectedAccount.address,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
snapId: selectedAccount.metadata.snap!.id,
};
}

#getProtocolSnaps(chainId: Caip2ChainId, method: string) {
#getProtocolSnaps(chainId: CaipChainId) {
const allSnaps = this.messagingSystem.call('SnapController:getAll');
const filteredSnaps = getRunnableSnaps(allSnaps);

Expand All @@ -122,9 +186,7 @@ export class MultichainRoutingController extends BaseController<
if (permissions && hasProperty(permissions, SnapEndowments.Protocol)) {
const permission = permissions[SnapEndowments.Protocol];
const chains = getProtocolCaveatChainIds(permission);
const methods = getProtocolCaveatRpcMethods(permission);
// TODO: This may need to be more complicated depending on the decided format.
if (chains?.includes(chainId) && methods?.includes(method)) {
if (chains?.includes(chainId)) {
accumulator.push({
snapId: snap.id,
permission,
Expand All @@ -141,28 +203,49 @@ export class MultichainRoutingController extends BaseController<
request,
}: {
origin: string;
chainId: Caip2ChainId;
chainId: CaipChainId;
request: JsonRpcRequest;
}) {
// TODO: Determine if the request is already validated here?
const { method } = request;

const protocolSnaps = this.#getProtocolSnaps(chainId);
if (protocolSnaps.length === 0) {
throw rpcErrors.methodNotFound();
}

// If the RPC request can be serviced by an account Snap, route it there.
const accountMethods = this.#getAccountSnapMethods(chainId);
if (accountMethods.includes(method)) {
// TODO: Determine how to call the AccountsRouter
return null;
const accountSnap = await this.#getAccountSnap(
protocolSnaps[0].snapId,
chainId,
request,
);
if (accountSnap) {
return this.messagingSystem.call('SnapController:handleRequest', {
snapId: accountSnap.snapId,
origin: 'metamask', // TODO: Determine origin of these requests?
request,
handler: HandlerType.OnKeyringRequest,
});
}

// If the RPC request cannot be serviced by an account Snap,
// but has a protocol Snap available, route it there.
const protocolSnaps = this.#getProtocolSnaps(chainId, method);
const snapId = protocolSnaps[0]?.snapId;
if (snapId) {
// TODO: This may need to be more complicated depending on the decided format.
const protocolSnap = protocolSnaps.find((snap) =>
getProtocolCaveatRpcMethods(snap.permission)?.includes(method),
);
if (protocolSnap) {
return this.messagingSystem.call('SnapController:handleRequest', {
snapId,
snapId: protocolSnap.snapId,
origin: 'metamask', // TODO: Determine origin of these requests?
request,
request: {
method: '',
params: {
request,
chainId,
},
},
handler: HandlerType.OnProtocolRequest,
});
}
Expand Down
1 change: 1 addition & 0 deletions packages/snaps-controllers/src/multichain/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MultichainRoutingController';
9 changes: 8 additions & 1 deletion packages/snaps-sdk/src/types/handlers/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';
import type {
CaipChainId,
Json,
JsonRpcParams,
JsonRpcRequest,
} from '@metamask/utils';

/**
* The `onProtocolRequest` handler, which is called when a Snap receives a
Expand All @@ -9,6 +14,7 @@ import type { Json, JsonRpcParams, JsonRpcRequest } from '@metamask/utils';
* @param args - The request arguments.
* @param args.origin - The origin of the request. This can be the ID of another
* Snap, or the URL of a website.
* @param args.chainId - The chain ID of the request.
* @param args.request - The protocol request sent to the Snap. This includes
* the method name and parameters.
* @returns The response to the protocol request. This must be a
Expand All @@ -19,5 +25,6 @@ export type OnProtocolRequestHandler<
Params extends JsonRpcParams = JsonRpcParams,
> = (args: {
origin: string;
chainId: CaipChainId;
request: JsonRpcRequest<Params>;
}) => Promise<Json>;

0 comments on commit bb9122e

Please sign in to comment.