Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(): add error list #11

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion packages/snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/airgap-it/tezos-metamask-snap.git"
},
"source": {
"shasum": "gZ8KD6mLrT9pHe/Lw3RBxFGg5d3ilAUFmEgAenZ5F/s=",
"shasum": "SfXyr9LaibY8HwzSGrBgpfSxZ2lsTin01dLKk4tefLQ=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
3 changes: 2 additions & 1 deletion packages/snap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { tezosSignPayload } from './rpc-methods/sign-payload';
import { tezosGetRpc } from './rpc-methods/get-rpc';
import { tezosSetRpc } from './rpc-methods/set-rpc';
import { tezosClearRpc } from './rpc-methods/clear-rpc';
import { METHOD_NOT_FOUND_ERROR } from './utils/errors';

// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
globalThis.Buffer = require('buffer/').Buffer;
Expand Down Expand Up @@ -55,6 +56,6 @@ export const onRpcRequest: OnRpcRequestHandler = async ({
return tezosClearRpc();

default:
throw new Error('Method not found.');
throw METHOD_NOT_FOUND_ERROR();
}
};
3 changes: 2 additions & 1 deletion packages/snap/src/rpc-methods/clear-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { panel, heading, text } from '@metamask/snaps-ui';
import { DEFAULT_NODE_URL } from '../constants';
import { USER_REJECTED_ERROR } from '../utils/errors';

export const tezosClearRpc = async () => {
const approved = await snap.request({
Expand All @@ -16,7 +17,7 @@ export const tezosClearRpc = async () => {
});

if (!approved) {
throw new Error('User rejected');
throw USER_REJECTED_ERROR();
}

await snap.request({
Expand Down
3 changes: 2 additions & 1 deletion packages/snap/src/rpc-methods/get-account.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { panel, heading, text, copyable, divider } from '@metamask/snaps-ui';
import { getSigner } from '../utils/get-signer';
import { getWallet } from '../utils/get-wallet';
import { USER_REJECTED_ERROR } from '../utils/errors';

export const tezosGetAccount = async (origin: string) => {
const wallet = await getWallet();
Expand All @@ -26,7 +27,7 @@ export const tezosGetAccount = async (origin: string) => {
});

if (!approved) {
throw new Error('User rejected');
throw USER_REJECTED_ERROR();
}

return {
Expand Down
3 changes: 2 additions & 1 deletion packages/snap/src/rpc-methods/get-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { panel, heading, text, copyable, divider } from '@metamask/snaps-ui';
import { getRpc } from '../utils/get-rpc';
import { USER_REJECTED_ERROR } from '../utils/errors';

export const tezosGetRpc = async () => {
const rpc = await getRpc();
Expand All @@ -21,7 +22,7 @@ export const tezosGetRpc = async () => {
});

if (!approved) {
throw new Error('User rejected');
throw USER_REJECTED_ERROR();
}

return {
Expand Down
14 changes: 10 additions & 4 deletions packages/snap/src/rpc-methods/set-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { panel, heading, text, copyable, divider } from '@metamask/snaps-ui';
import { SnapStorage } from '../types';
import {
RPC_NO_HTTPS_ERROR,
RPC_INVALID_URL_ERROR,
RPC_INVALID_RESPONSE_ERROR,
USER_REJECTED_ERROR,
} from '../utils/errors';

export const tezosSetRpc = async (params: any) => {
const { network, nodeUrl }: { network: string; nodeUrl: string } = params;

if (!nodeUrl.startsWith('https://')) {
throw new Error('RPC URL needs to start with https://');
throw RPC_NO_HTTPS_ERROR();
}

const normalisedNodeUrl = `${nodeUrl}${nodeUrl.endsWith('/') ? '' : '/'}`;
Expand All @@ -15,11 +21,11 @@ export const tezosSetRpc = async (params: any) => {
)
.then((res) => res.json())
.catch(() => {
throw new Error('Invalid RPC URL');
throw RPC_INVALID_URL_ERROR();
});

if (!header.hash || !header.chain_id) {
throw new Error('Invalid RPC response');
throw RPC_INVALID_RESPONSE_ERROR();
}

const approved = await snap.request({
Expand All @@ -37,7 +43,7 @@ export const tezosSetRpc = async (params: any) => {
});

if (!approved) {
throw new Error('User rejected');
throw USER_REJECTED_ERROR();
}

const newState: SnapStorage = {
Expand Down
3 changes: 2 additions & 1 deletion packages/snap/src/rpc-methods/sign-payload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { copyable, heading, panel, text } from '@metamask/snaps-ui';
import { getWallet } from '../utils/get-wallet';
import { sign } from '../utils/sign';
import { USER_REJECTED_ERROR } from '../utils/errors';

export const tezosSignPayload = async (params: any) => {
const { payload } = params;
Expand All @@ -19,7 +20,7 @@ export const tezosSignPayload = async (params: any) => {
});

if (!approved) {
throw new Error('User rejected');
throw USER_REJECTED_ERROR();
}

return sign(payload, undefined, wallet);
Expand Down
20 changes: 10 additions & 10 deletions packages/snap/src/tezos/estimate-fee.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { localForger } from '@taquito/local-forging';
import BigNumber from 'bignumber.js';
import {
TEZOS_INTERNAL_ERROR,
TEZOS_INTERNAL_OP_COUNT_MISMATCH_ERROR,
TEZOS_OPERATION_ERROR,
} from '../utils/errors';
import {
TezosWrappedOperation,
TezosOperationType,
Expand Down Expand Up @@ -44,11 +49,7 @@ export const sumUpInternalFees = (metadata: RunOperationMetadata) => {
(internalOperation: RunOperationInternalOperationResult) => {
if (internalOperation?.result) {
if (internalOperation.result.errors) {
throw new Error(
`An internal operation produced an error ${JSON.stringify(
internalOperation.result.errors,
)}`,
);
throw TEZOS_INTERNAL_ERROR(internalOperation.result.errors);
}

gasLimit += Math.ceil(
Expand Down Expand Up @@ -100,9 +101,7 @@ const sumUpFees = async (
const result: RunOperationOperationResult = metadata.operation_result;

if (result.errors) {
throw new Error(
`The operation produced an error ${JSON.stringify(result.errors)}`,
);
throw TEZOS_OPERATION_ERROR(result.errors);
}

let { gasLimit, storageLimit } = sumUpInternalFees(metadata);
Expand Down Expand Up @@ -211,8 +210,9 @@ export const estimateAndReplaceLimitsAndFee = async (
});

if (tezosWrappedOperation.contents.length !== response.contents.length) {
throw new Error(
`Run Operation did not return same number of operations. Locally we have ${tezosWrappedOperation.contents.length}, but got back ${response.contents.length}`,
throw TEZOS_INTERNAL_OP_COUNT_MISMATCH_ERROR(
tezosWrappedOperation.contents.length,
response.contents.length,
);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/snap/src/tezos/get-balance-of-address.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import BigNumber from 'bignumber.js';
import { FETCH_BALANCE_ERROR } from '../utils/errors';

export const getBalanceOfAddress = async (
address: string,
Expand All @@ -15,7 +16,7 @@ export const getBalanceOfAddress = async (
} catch (error: any) {
// if node returns 404 (which means 'no account found'), go with 0 balance
if (error.response && error.response.status !== 404) {
throw new Error('Error fetching balance');
throw FETCH_BALANCE_ERROR();
}
}

Expand Down
28 changes: 14 additions & 14 deletions packages/snap/src/tezos/prepare-operations.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { localForger } from '@taquito/local-forging';
import BigNumber from 'bignumber.js';
import {
NETWORK_ERROR,
PROPERTY_NOT_DEFINED_ERROR,
PROPERTY_NOT_DEFINED_WITH_DATA_ERROR,
UNSUPPORTED_OPERATION_KIND_ERROR,
} from '../utils/errors';
import { createRevealOperation } from './create-reveal-operation';
import {
TezosOperationType,
Expand Down Expand Up @@ -31,7 +37,7 @@ export const handleRevealOperation = async (
operationRequest as TezosRevealOperation;

if (!revealOperation.public_key) {
throw new Error('property "public_key" was not defined');
throw PROPERTY_NOT_DEFINED_ERROR('public_key');
}

revealOperation.source = revealOperation.source ?? address;
Expand Down Expand Up @@ -81,11 +87,11 @@ export const handleTransactionOperation = async (
operationRequest as TezosTransactionOperation;

if (!transactionOperation.amount) {
throw new Error('property "amount" was not defined');
throw PROPERTY_NOT_DEFINED_ERROR('amount');
}

if (!transactionOperation.destination) {
throw new Error('property "destination" was not defined');
throw PROPERTY_NOT_DEFINED_ERROR('destination');
}

transactionOperation.source = transactionOperation.source ?? address;
Expand All @@ -110,11 +116,11 @@ export const handleOriginationOperation = async (
operationRequest as TezosOriginationOperation;

if (!originationOperation.balance) {
throw new Error('property "balance" was not defined');
throw PROPERTY_NOT_DEFINED_ERROR('balance');
}

if (!originationOperation.script) {
throw new Error('property "script" was not defined');
throw PROPERTY_NOT_DEFINED_ERROR('script');
}

originationOperation.source = originationOperation.source ?? address;
Expand Down Expand Up @@ -150,7 +156,7 @@ export const prepareOperations = async (
`${nodeUrl}chains/main/blocks/head/context/contracts/${address}/manager_key`,
),
]).catch((error) => {
throw new Error(error);
throw NETWORK_ERROR(error);
})
).map((res) => res.json()),
);
Expand All @@ -173,9 +179,7 @@ export const prepareOperations = async (
const operationPromises: Promise<TezosOperation>[] = operationRequests.map(
async (operationRequest: TezosOperation, index: number) => {
if (!operationRequest.kind) {
throw new Error(
`property "kind" was not defined ${JSON.stringify(operationRequest)}`,
);
throw PROPERTY_NOT_DEFINED_WITH_DATA_ERROR('kind', operationRequest);
}

const recipient: string | undefined = (
Expand Down Expand Up @@ -245,11 +249,7 @@ export const prepareOperations = async (
// Do not change anything
return operationRequest;
default:
throw new Error(
`unsupported operation type "${JSON.stringify(
operationRequest.kind,
)}"`,
);
throw UNSUPPORTED_OPERATION_KIND_ERROR(operationRequest.kind);
}
},
);
Expand Down
47 changes: 47 additions & 0 deletions packages/snap/src/utils/errors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import chai, { expect } from 'chai';
import sinonChai from 'sinon-chai';
import chaiAsPromised from 'chai-as-promised';
import chaiBytes from 'chai-bytes';

import {
NETWORK_ERROR,
PROPERTY_NOT_DEFINED_WITH_DATA_ERROR,
TEZOS_INTERNAL_OP_COUNT_MISMATCH_ERROR,
TEZOS_OPERATION_ERROR,
UNSUPPORTED_OPERATION_KIND_ERROR,
} from './errors';

chai.use(chaiBytes);
chai.use(sinonChai);
chai.use(chaiAsPromised);

describe('Test file: errors', function () {
it('should return errors', async function () {
{
const error = TEZOS_OPERATION_ERROR({});
expect(error.message).to.contain('The operation produced an error');
}

{
const error = TEZOS_INTERNAL_OP_COUNT_MISMATCH_ERROR(1, 2);
expect(error.message).to.contain(
'Run Operation did not return same number of operations. Locally we have',
);
}

{
const error = PROPERTY_NOT_DEFINED_WITH_DATA_ERROR('prop', {});
expect(error.message).to.contain('was not defined');
}

{
const error = UNSUPPORTED_OPERATION_KIND_ERROR('prop');
expect(error.message).to.contain('unsupported operation type');
}

{
const error = NETWORK_ERROR('err');
expect(error.message).to.contain('Network error: ');
}
});
});
34 changes: 34 additions & 0 deletions packages/snap/src/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const USER_REJECTED_ERROR = () => new Error('User rejected');
export const METHOD_NOT_FOUND_ERROR = () => new Error('Method not found.');
export const RPC_NO_HTTPS_ERROR = () =>
new Error('RPC URL needs to start with https://');
export const RPC_INVALID_URL_ERROR = () => new Error('Invalid RPC URL');
export const RPC_INVALID_RESPONSE_ERROR = () =>
new Error('Invalid RPC response');
export const TEZOS_INTERNAL_ERROR = (json: any) =>
new Error(`An internal operation produced an error ${JSON.stringify(json)}`);
export const TEZOS_OPERATION_ERROR = (json: any) =>
new Error(`The operation produced an error ${JSON.stringify(json)}`);
export const TEZOS_INTERNAL_OP_COUNT_MISMATCH_ERROR = (
l1: number,
l2: number,
) =>
new Error(
`Run Operation did not return same number of operations. Locally we have ${l1}, but got back ${l2}`,
);
export const FETCH_BALANCE_ERROR = () => new Error('Error fetching balance');
export const PROPERTY_NOT_DEFINED_ERROR = (property: string) =>
new Error(`property "${property}" was not defined`);
export const PROPERTY_NOT_DEFINED_WITH_DATA_ERROR = (
property: string,
data: any,
) =>
new Error(`property "${property}" was not defined ${JSON.stringify(data)}`);
export const UNSUPPORTED_OPERATION_KIND_ERROR = (kind: string) =>
new Error(`unsupported operation type "${kind}"`);
export const NETWORK_ERROR = (error: any) =>
new Error(`Network error: ${JSON.stringify(error)}`);
export const HEX_LENGTH_INVALID_ERROR = () =>
new Error('Hex String has invalid length');
export const HEX_CHARACTER_INVALID_ERROR = () =>
new Error('Hex String has invalid character');
9 changes: 7 additions & 2 deletions packages/snap/src/utils/hex-string-to-uint8array.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {
HEX_CHARACTER_INVALID_ERROR,
HEX_LENGTH_INVALID_ERROR,
} from './errors';

export const hexStringToUInt8Array = (hexString: string): Uint8Array => {
if (hexString.length % 2 !== 0) {
throw new Error('Hex String has invalid length');
throw HEX_LENGTH_INVALID_ERROR();
}

const arrayBuffer = new Uint8Array(hexString.length / 2);

for (let i = 0; i < hexString.length; i += 2) {
const byteValue = parseInt(hexString.substring(i, i + 2), 16);
if (isNaN(byteValue)) {
throw new Error('Hex String has invalid character');
throw HEX_CHARACTER_INVALID_ERROR();
}
arrayBuffer[i / 2] = byteValue;
}
Expand Down
Loading
Loading