Skip to content

Commit

Permalink
refactor(SNAP): revamp RPC starkNet_addErc20Token (#388)
Browse files Browse the repository at this point in the history
* chore: revamp starkNet_addErc20Token

* chore: update watch asset

* fix: pr comment

* chore: update superstruct
  • Loading branch information
stanleyyconsensys authored Oct 18, 2024
1 parent a034bcf commit 157b5ad
Show file tree
Hide file tree
Showing 19 changed files with 703 additions and 617 deletions.
79 changes: 0 additions & 79 deletions packages/starknet-snap/src/addErc20Token.ts

This file was deleted.

22 changes: 21 additions & 1 deletion packages/starknet-snap/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import type { Network } from './types/snapState';
import type { Erc20Token, Network } from './types/snapState';
import {
SnapEnv,
STARKNET_MAINNET_NETWORK,
STARKNET_SEPOLIA_TESTNET_NETWORK,
ETHER_MAINNET,
ETHER_SEPOLIA_TESTNET,
USDC_MAINNET,
USDC_SEPOLIA_TESTNET,
USDT_MAINNET,
USDT_SEPOLIA_TESTNET,
STRK_MAINNET,
STRK_SEPOLIA_TESTNET,
} from './utils/constants';
import { LogLevel } from './utils/logger';

Expand All @@ -11,6 +19,7 @@ export type SnapConfig = {
snapEnv: SnapEnv;
defaultNetwork: Network;
availableNetworks: Network[];
preloadTokens: Erc20Token[];
};

export const Config: SnapConfig = {
Expand All @@ -25,4 +34,15 @@ export const Config: SnapConfig = {
STARKNET_MAINNET_NETWORK,
STARKNET_SEPOLIA_TESTNET_NETWORK,
],

preloadTokens: [
ETHER_MAINNET,
ETHER_SEPOLIA_TESTNET,
USDC_MAINNET,
USDC_SEPOLIA_TESTNET,
USDT_MAINNET,
USDT_SEPOLIA_TESTNET,
STRK_MAINNET,
STRK_SEPOLIA_TESTNET,
],
};
7 changes: 5 additions & 2 deletions packages/starknet-snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
} from '@metamask/snaps-sdk';
import { panel, text, MethodNotFoundError } from '@metamask/snaps-sdk';

import { addErc20Token } from './addErc20Token';
import { addNetwork } from './addNetwork';
import { Config } from './config';
import { createAccount } from './createAccount';
Expand Down Expand Up @@ -36,6 +35,7 @@ import type {
VerifySignatureParams,
SwitchNetworkParams,
GetDeploymentDataParams,
WatchAssetParams,
} from './rpcs';
import {
displayPrivateKey,
Expand All @@ -47,6 +47,7 @@ import {
verifySignature,
switchNetwork,
getDeploymentData,
watchAsset,
} from './rpcs';
import { sendTransaction } from './sendTransaction';
import { signDeployAccountTransaction } from './signDeployAccountTransaction';
Expand Down Expand Up @@ -224,7 +225,9 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ request }) => {
);

case 'starkNet_addErc20Token':
return await addErc20Token(apiParams);
return await watchAsset.execute(
apiParams.requestParams as unknown as WatchAssetParams,
);

case 'starkNet_getStoredErc20Tokens':
return await getStoredErc20Tokens(apiParams);
Expand Down
16 changes: 16 additions & 0 deletions packages/starknet-snap/src/rpcs/__tests__/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,19 @@ export function prepareAlertDialog() {
alertDialogSpy,
};
}

export const buildRowComponent = (label: string, value: string) => ({
type: 'row',
label,
value: {
value,
markdown: false,
type: 'text',
},
});

export const buildDividerComponent = () => {
return {
type: 'divider',
};
};
1 change: 1 addition & 0 deletions packages/starknet-snap/src/rpcs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './sign-declare-transaction';
export * from './verify-signature';
export * from './switch-network';
export * from './get-deployment-data';
export * from './watch-asset';
170 changes: 170 additions & 0 deletions packages/starknet-snap/src/rpcs/watch-asset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { constants } from 'starknet';

import { Config } from '../config';
import { NetworkStateManager } from '../state/network-state-manager';
import { TokenStateManager } from '../state/token-state-manager';
import type { Network } from '../types/snapState';
import { STARKNET_SEPOLIA_TESTNET_NETWORK } from '../utils/constants';
import {
InvalidRequestParamsError,
TokenIsPreloadedError,
InvalidNetworkError,
UserRejectedOpError,
} from '../utils/exceptions';
import {
buildDividerComponent,
buildRowComponent,
prepareConfirmDialog,
} from './__tests__/helper';
import type { WatchAssetParams } from './watch-asset';
import { watchAsset } from './watch-asset';

jest.mock('../utils/snap');
jest.mock('../utils/logger');

describe('WatchAssetRpc', () => {
const createRequest = ({
chainId = constants.StarknetChainId.SN_SEPOLIA,
tokenName = 'Valid Token',
tokenSymbol = 'VT',
tokenAddress = '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004d99',
tokenDecimals = 18,
}: {
chainId?: constants.StarknetChainId;
tokenName?: string;
tokenSymbol?: string;
tokenDecimals?: number;
tokenAddress?: string;
}) => ({
tokenAddress,
tokenName,
tokenSymbol,
tokenDecimals,
chainId,
});

const mockNetworkStateManager = ({
network,
}: {
network: Network | null;
}) => {
const getNetworkSpy = jest.spyOn(
NetworkStateManager.prototype,
'getNetwork',
);

getNetworkSpy.mockResolvedValue(network);

return { getNetworkSpy };
};

const mockTokenStateManager = () => {
const upsertTokenSpy = jest.spyOn(
TokenStateManager.prototype,
'upsertToken',
);

return { upsertTokenSpy };
};

const prepareWatchAssetTest = async ({
network = STARKNET_SEPOLIA_TESTNET_NETWORK,
}: {
network?: Network;
}) => {
const request = createRequest({
chainId: network.chainId as unknown as constants.StarknetChainId,
});
const { confirmDialogSpy } = prepareConfirmDialog();
const { getNetworkSpy } = mockNetworkStateManager({
network,
});
const { upsertTokenSpy } = mockTokenStateManager();

return {
getNetworkSpy,
confirmDialogSpy,
upsertTokenSpy,
request,
};
};

it('returns true if the token is added', async () => {
const { request } = await prepareWatchAssetTest({});

const expectedResult = true;

const result = await watchAsset.execute(request);

expect(result).toStrictEqual(expectedResult);
});

it('renders confirmation dialog', async () => {
const network = STARKNET_SEPOLIA_TESTNET_NETWORK;
const { request, confirmDialogSpy } = await prepareWatchAssetTest({
network,
});

await watchAsset.execute(request);

expect(confirmDialogSpy).toHaveBeenCalledWith([
{ type: 'heading', value: 'Do you want to add this token?' },
buildRowComponent('Network', network.name),
buildDividerComponent(),
buildRowComponent('Token Address', request.tokenAddress),
buildDividerComponent(),
buildRowComponent('Token Name', request.tokenName),
buildDividerComponent(),
buildRowComponent('Token Symbol', request.tokenSymbol),
buildDividerComponent(),
buildRowComponent('Token Decimals', request.tokenDecimals.toString()),
]);
});

it('throws `InvalidNetworkError` if the network can not be found', async () => {
const { request, getNetworkSpy } = await prepareWatchAssetTest({});
getNetworkSpy.mockResolvedValue(null);

await expect(watchAsset.execute(request)).rejects.toThrow(
InvalidNetworkError,
);
});

it('throws `TokenIsPreloadedError` if the given token is one of the preloaded tokens', async () => {
const preloadedToken = Config.preloadTokens[0];
const { address, symbol, decimals, name, chainId } = preloadedToken;
// Ensure the network is matching the preloaded token
const network = Config.availableNetworks.find(
(net) => net.chainId === chainId,
);
await prepareWatchAssetTest({
network,
});
const request = createRequest({
tokenAddress: address,
tokenName: name,
tokenSymbol: symbol,
tokenDecimals: decimals,
chainId: chainId as unknown as constants.StarknetChainId,
});

await expect(watchAsset.execute(request)).rejects.toThrow(
TokenIsPreloadedError,
);
});

it('throws `UserRejectedOpError` if user denied the operation', async () => {
const { request, confirmDialogSpy } = await prepareWatchAssetTest({});
confirmDialogSpy.mockResolvedValue(false);

await expect(watchAsset.execute(request)).rejects.toThrow(
UserRejectedOpError,
);
});

it('throws `InvalidRequestParamsError` when request parameter is not correct', async () => {
await expect(
watchAsset.execute({} as unknown as WatchAssetParams),
).rejects.toThrow(InvalidRequestParamsError);
});
});
Loading

0 comments on commit 157b5ad

Please sign in to comment.