Skip to content

Commit

Permalink
add MetaMask Snap services and types for account management and netwo…
Browse files Browse the repository at this point in the history
…rk interactions
  • Loading branch information
Ben-Rey committed Nov 14, 2024
1 parent b2651e7 commit d3b6ade
Show file tree
Hide file tree
Showing 22 changed files with 588 additions and 85 deletions.
95 changes: 46 additions & 49 deletions src/metamaskSnap/MetamaskAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
DeploySCParams,
EventFilter,
formatNodeStatusObject,
JsonRPCClient,
MAX_GAS_CALL,
Network,
NodeStatusInfo,
Expand All @@ -22,13 +21,18 @@ import {
SmartContract,
strToBytes,
} from '@massalabs/massa-web3';

import { WalletName } from '../wallet';
import { getClient, networkInfos } from '../massaStation/utils/network';
import { MetaMaskInpageProvider } from '@metamask/providers';
import { MASSA_SNAP_ID } from './config';
import { web3 } from '@hicaru/bearby.js';
import { AccountBalanceResponse } from './types/snap';
import {
buyRolls,
callSC,
getBalance,
readSC,
sellRolls,
transfer,
} from './services';

export class MetamaskAccount implements Provider {
public constructor(
Expand All @@ -49,16 +53,7 @@ export class MetamaskAccount implements Provider {
address: this.address,
};

const res = await this.provider.request<AccountBalanceResponse>({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.balance',
params,
},
},
});
const res = await getBalance(this.provider, params);

return final ? BigInt(res.finalBalance) : BigInt(res.candidateBalance);
}
Expand Down Expand Up @@ -95,10 +90,11 @@ export class MetamaskAccount implements Provider {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_opts?: OperationOptions,
): Promise<Operation> {
// TODO: update to use snap

try {
const operationId = await web3.massa.buyRolls(amount.toString());
const { operationId } = await buyRolls(this.provider, {
amount: amount.toString(),
fee: '0',
});
return new Operation(this, operationId);
} catch (error) {
throw errorHandler(operationType.BuyRolls, error);
Expand All @@ -110,10 +106,11 @@ export class MetamaskAccount implements Provider {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_opts?: OperationOptions,
): Promise<Operation> {
// TODO: update to use snap

try {
const operationId = await web3.massa.sellRolls(amount.toString());
const { operationId } = await sellRolls(this.provider, {
amount: amount.toString(),
fee: '0',
});
return new Operation(this, operationId);
} catch (error) {
throw errorHandler(operationType.SellRolls, error);
Expand All @@ -126,13 +123,12 @@ export class MetamaskAccount implements Provider {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_opts?: OperationOptions,
): Promise<Operation> {
// TODO: update to use snap

try {
const operationId = await web3.massa.payment(
amount.toString(),
to.toString(),
);
const { operationId } = await transfer(this.provider, {
amount: amount.toString(),
fee: '0',
recipientAddress: to.toString(),
});

return new Operation(this, operationId);
} catch (error) {
Expand All @@ -141,39 +137,34 @@ export class MetamaskAccount implements Provider {
}

private async minimalFee(): Promise<bigint> {
// TODO: update to use snap
const { minimalFee } = await this.networkInfos();
return minimalFee;
}

public async callSC(params: CallSCParams): Promise<Operation> {
// TODO: update to use snap

const args = params.parameter ?? new Uint8Array();
const unsafeParameters =
args instanceof Uint8Array ? args : Uint8Array.from(args.serialize());

const fee = params?.fee ?? (await this.minimalFee());

try {
const operationId = await web3.contract.call({
// TODO: add bigint support in bearby.js
// TODO add gas estimation here
maxGas: Number(params.maxGas || MAX_GAS_CALL),
coins: Number(params.coins || 0),
fee: Number(fee),
targetAddress: params.target,
const { operationId } = await callSC(this.provider, {
functionName: params.func,
unsafeParameters,
at: params.target,
args: Array.from(unsafeParameters),
coins: params.coins.toString(),
fee: fee.toString(),
maxGas: params.maxGas.toString(),
});

return new Operation(this, operationId);
} catch (error) {
throw errorHandler(operationType.CallSC, error);
}
}

public async readSC(params: ReadSCParams): Promise<ReadSCData> {
// TODO: update to use snap
if (params?.maxGas > MAX_GAS_CALL) {
throw new Error(
`Gas amount ${params.maxGas} exceeds the maximum allowed ${MAX_GAS_CALL}.`,
Expand All @@ -188,18 +179,24 @@ export class MetamaskAccount implements Provider {
const caller = params.caller ?? this.address;

try {
const network = await this.networkInfos();
const client =
network.name === 'mainnet'
? JsonRPCClient.mainnet()
: JsonRPCClient.buildnet();
const readOnlyParams = {
...params,
caller,
fee,
parameter: unsafeParameters,
const res = await readSC(this.provider, {
fee: fee.toString(),
functionName: params.func,
at: params.target,
args: Array.from(unsafeParameters),
coins: params.coins.toString(),
maxGas: params.maxGas.toString(),
caller: caller,
});
return {
value: new Uint8Array(res.data),
info: {
gasCost: res.infos.gasCost,
// TODO: update snap to return those values
events: [],
error: '',
},
};
return client.executeReadOnlyCall(readOnlyParams);
} catch (error) {
throw new Error(
`An error occurred while reading the smart contract: ${error.message}`,
Expand Down
24 changes: 3 additions & 21 deletions src/metamaskSnap/MetamaskWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import {
import { WalletName } from '../wallet';
import { getMetamaskProvider } from './metamask';
import { connectSnap, getMassaSnapInfo } from './snap';
import { MASSA_SNAP_ID } from './config';
import { MetamaskAccount } from './MetamaskAccount';
import { MetaMaskInpageProvider } from '@metamask/providers';
import { ActiveAccountResponse, NetworkResponse } from './types/snap';
import { getActiveAccount, getNetwork } from './services';

export class MetamaskWallet implements Wallet {
private walletName = WalletName.Metamask;
Expand Down Expand Up @@ -42,16 +41,7 @@ export class MetamaskWallet implements Wallet {
}

public async accounts(): Promise<MetamaskAccount[]> {
const res = await this.metamaskProvider.request<ActiveAccountResponse>({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.getActive',
},
},
});

const res = await getActiveAccount(this.metamaskProvider);
return [new MetamaskAccount(res.address, this.metamaskProvider)];
}

Expand All @@ -70,15 +60,7 @@ export class MetamaskWallet implements Wallet {
}

public async networkInfos(): Promise<Network> {
const res = await this.metamaskProvider.request<NetworkResponse>({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'Provider.getNetwork',
},
},
});
const res = await getNetwork(this.metamaskProvider);

return {
name: getNetworkNameByChainId(BigInt(res.chainId)),
Expand Down
31 changes: 31 additions & 0 deletions src/metamaskSnap/services/addToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { MetaMaskInpageProvider } from '@metamask/providers';
import { MASSA_SNAP_ID } from '../config';
import type { AccountToken } from '../types/account-token';

export type AddTokenParams = {
address: string;
};

export type AddTokenResponse = AccountToken;

/**
* Function that calls the MetaMask provider to add a token
* @param provider - The MetaMask provider
* @param params - The add token parameters (address of the token)
* @returns The response of the operation
*/
export const addToken = async (
provider: MetaMaskInpageProvider,
params: AddTokenParams,
): Promise<AddTokenResponse | undefined> => {
return provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.addToken',
params,
},
},
}) as Promise<AddTokenResponse>;
};
33 changes: 33 additions & 0 deletions src/metamaskSnap/services/buyRolls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MetaMaskInpageProvider } from '@metamask/providers';
import { MASSA_SNAP_ID } from '../config';

export type BuyRollsParams = {
fee: string;
amount: string;
};

export type BuyRollsResponse = {
operationId: string;
};

/**
* Function that calls the MetaMask provider to buy rolls
* @param provider - The MetaMask provider
* @param params - The buy rolls parameters (fee and amount)
* @returns The operationId of the operation
*/
export const buyRolls = async (
provider: MetaMaskInpageProvider,
params: BuyRollsParams,
): Promise<BuyRollsResponse | undefined> => {
return provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.buyRolls',
params,
},
},
}) as Promise<BuyRollsResponse>;
};
37 changes: 37 additions & 0 deletions src/metamaskSnap/services/callSC.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MetaMaskInpageProvider } from '@metamask/providers';
import { MASSA_SNAP_ID } from '../config';

export type CallSCParams = {
fee: string;
functionName: string;
at: string;
args: number[];
coins: string;
maxGas?: string;
};

export type CallSCResponse = {
operationId: string;
};

/**
* Function that calls the MetaMask provider to call a smart contract
* @param provider - The MetaMask provider
* @param params - The call smart contract parameters (see massa standard)
* @returns The response of the operation
*/
export const callSC = async (
provider: MetaMaskInpageProvider,
params: CallSCParams,
): Promise<CallSCResponse | undefined> => {
return provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.callSC',
params,
},
},
}) as Promise<CallSCResponse>;
};
26 changes: 26 additions & 0 deletions src/metamaskSnap/services/clearOperations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { MetaMaskInpageProvider } from '@metamask/providers';
import { MASSA_SNAP_ID } from '../config';

export type ClearOperationsResponse = {
response: 'OK' | 'ERROR' | 'REFUSED';
message?: string;
};

/**
* Function that calls the MetaMask provider to clear the operations of an account
* @param provider - The MetaMask provider
* @returns The response of the operation
*/
export const clearOperations = async (
provider: MetaMaskInpageProvider,
): Promise<ClearOperationsResponse | undefined> => {
return provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.clearOperations',
},
},
}) as Promise<ClearOperationsResponse>;
};
33 changes: 33 additions & 0 deletions src/metamaskSnap/services/deleteToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MetaMaskInpageProvider } from '@metamask/providers';
import { MASSA_SNAP_ID } from '../config';

export type DeleteTokenParams = {
address: string;
};

export type DeleteTokenResponse = {
response: 'OK' | 'ERROR';
message?: string;
};

/**
* Function that calls the MetaMask provider to delete a token
* @param provider - The MetaMask provider
* @param params - The delete token parameters (address of the token)
* @returns The response of the operation
*/
export const deleteToken = async (
provider: MetaMaskInpageProvider,
params: DeleteTokenParams,
): Promise<DeleteTokenResponse | undefined> => {
return provider.request({
method: 'wallet_invokeSnap',
params: {
snapId: MASSA_SNAP_ID,
request: {
method: 'account.deleteToken',
params,
},
},
}) as Promise<DeleteTokenResponse>;
};
Loading

0 comments on commit d3b6ade

Please sign in to comment.