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 tx info #15

Draft
wants to merge 1 commit into
base: feat/add-origin
Choose a base branch
from
Draft
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": "ntvGpQQ6EK6wxpx6eXq4lEsYqrocwuPqR9sCK2RixY0=",
"shasum": "QSzx20eIc9oYUmnwcuhNl3z8pT9fTpCsfaCqsWaqit0=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
6 changes: 3 additions & 3 deletions packages/snap/src/rpc-methods/send-operation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import chaiBytes from 'chai-bytes';
import * as sinon from 'sinon';
import * as getWalletMethods from '../utils/get-wallet';
import * as getRpcMethods from '../utils/get-rpc';
import * as prepareAndSignMethods from '../utils/prepare-and-sign';
import * as prepareMethods from '../utils/prepare';
import { SnapMock } from '../../test/snap.mock.test';
import { bip32Entropy } from '../../test/constants.test';
import { tezosSendOperation } from './send-operation';
Expand Down Expand Up @@ -38,7 +38,7 @@ describe('Test function: sendOperation', function () {
);

const prepareStub = sinon
.stub(prepareAndSignMethods, 'prepareAndSign')
.stub(prepareMethods, 'prepare')
.returns(Promise.resolve('op...'));

const walletMethodStub = sinon
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('Test function: sendOperation', function () {
);

const prepareStub = sinon
.stub(prepareAndSignMethods, 'prepareAndSign')
.stub(prepareMethods, 'prepare')
.returns(Promise.resolve('op...'));

const walletMethodStub = sinon
Expand Down
83 changes: 76 additions & 7 deletions packages/snap/src/rpc-methods/send-operation.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,90 @@
import { panel, heading, text, copyable, divider } from '@metamask/snaps-ui';
import BigNumber from 'bignumber.js';
import { getWallet } from '../utils/get-wallet';
import { prepareAndSign } from '../utils/prepare-and-sign';
import { prepare } from '../utils/prepare';
import { getRpc } from '../utils/get-rpc';
import { to } from '../utils/to';
import { USER_REJECTED_ERROR } from '../utils/errors';
import { NO_OPERATION_ERROR, USER_REJECTED_ERROR } from '../utils/errors';
import { TezosTransactionOperation } from '../tezos/types';
import { createOriginElement } from '../ui/origin-element';
import { sign } from '../utils/sign';
import { injectTransaction } from '../tezos/inject-transaction';

const mutezToTez = (mutez: string): string => {
return BigNumber(mutez).shiftedBy(-6).toString(10);
};

const aggregate = (array: any[], field: string) => {
return array
.reduce((pv, cv): BigNumber => {
return pv.plus(cv[field]);
}, new BigNumber(0))
.toString();
};

export const tezosSendOperation = async (origin: string, params: any) => {
const { payload } = params;
const wallet = await getWallet();
const rpc = await getRpc();

const typedPayload: TezosTransactionOperation[] = payload;
const prepareResult = await prepare(payload, wallet, rpc.nodeUrl);
const typedPayload: TezosTransactionOperation[] = prepareResult.estimated
.contents as any;

if (typedPayload.length === 0) {
throw NO_OPERATION_ERROR();
}

const humanReadable = [];

if (typedPayload[0] && typedPayload[0].kind === 'transaction') {
if (typedPayload.length > 1) {
if (typedPayload.every((el) => el.kind === 'transaction')) {
// If we have more than one operation and all of them are "transaction" operations, we aggregate
humanReadable.push(
text(`This operation group contains multiple the aggregated fees.`),
text(
`**Amount:** ${mutezToTez(aggregate(typedPayload, 'amount'))} tez`,
),
text(`**Fee:** ${mutezToTez(aggregate(typedPayload, 'fee'))} tez`),
text(
`**Gas Limit:** ${mutezToTez(
aggregate(typedPayload, 'gas_limit'),
)} tez`,
),
text(
`**Storage Limit:** ${mutezToTez(
aggregate(typedPayload, 'storage_limit'),
)} tez`,
),
);
} else if (!typedPayload.every((el) => el.kind === 'transaction')) {
// If we have more than one operation and some are not of kind "transaction", we show a note
humanReadable.push(
text(`**This operation group contains multiple operations.**`),
);
}
} else if (typedPayload[0].kind === 'transaction') {
// If transaction operation, we show additional information
humanReadable.push(
text(`**To:** ${typedPayload[0].destination}`),
text(`**Amount:** ${mutezToTez(typedPayload[0].amount)} tez`),
text(`**Fee:** ${mutezToTez(typedPayload[0].fee)} tez`),
text(`**Gas Limit:** ${mutezToTez(typedPayload[0].fee)} tez`),
text(`**Storage Limit:** ${mutezToTez(typedPayload[0].fee)} tez`),
text(`**Gas Limit:** ${mutezToTez(typedPayload[0].gas_limit)} tez`),
text(
`**Storage Limit:** ${mutezToTez(typedPayload[0].storage_limit)} tez`,
),
);
} else {
// If one operation, we show kind
humanReadable.push(text(`**Kind:** ${typedPayload[0].kind}`));
}

// If "parameter" is present (contract call), we show a note
if (typedPayload.some((el) => el.kind === 'transaction' && el.parameters)) {
humanReadable.push(
text(
`**Note:** This is a contract call. Please only sign this operation if you trust the origin.`,
),
);
}

Expand Down Expand Up @@ -59,5 +116,17 @@ export const tezosSendOperation = async (origin: string, params: any) => {
throw USER_REJECTED_ERROR();
}

return { opHash: await prepareAndSign(payload, wallet, rpc.nodeUrl) };
const operationWatermark = new Uint8Array([3]);
const signResult = await sign(
prepareResult.forged,
operationWatermark,
wallet,
);

const opHash = await injectTransaction(
signResult.signature.sbytes,
rpc.nodeUrl,
);

return { opHash };
};
5 changes: 5 additions & 0 deletions packages/snap/src/rpc-methods/set-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import {
RPC_INVALID_URL_ERROR,
RPC_INVALID_RESPONSE_ERROR,
USER_REJECTED_ERROR,
RPC_NO_URL_ERROR,
} from '../utils/errors';
import { createOriginElement } from '../ui/origin-element';

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

if (!nodeUrl) {
throw RPC_NO_URL_ERROR();
}

if (!nodeUrl.startsWith('https://')) {
throw RPC_NO_HTTPS_ERROR();
}
Expand Down
5 changes: 3 additions & 2 deletions packages/snap/src/tezos/prepare-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
TezosDelegationOperation,
TezosOriginationOperation,
TezosOperation,
TezosWrappedOperation,
} from './types';
import {
ALLOCATION_STORAGE_LIMIT,
Expand Down Expand Up @@ -141,7 +142,7 @@ export const prepareOperations = async (
operationRequests: TezosOperation[],
nodeUrl: string,
overrideParameters = true,
): Promise<string> => {
): Promise<{ estimated: TezosWrappedOperation; forged: string }> => {
let counter: BigNumber = new BigNumber(1);
const operations: TezosOperation[] = [];

Expand Down Expand Up @@ -267,5 +268,5 @@ export const prepareOperations = async (
overrideParameters,
);

return await localForger.forge(estimated as any);
return { estimated, forged: await localForger.forge(estimated as any) };
};
4 changes: 3 additions & 1 deletion packages/snap/src/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const USER_REJECTED_ERROR = () => new Error('User rejected');
export const METHOD_NOT_FOUND_ERROR = () => new Error('Method not found.');
export const METHOD_NOT_FOUND_ERROR = () => new Error('Method not found');
export const RPC_NO_URL_ERROR = () => new Error('RPC URL not set');
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');
Expand Down Expand Up @@ -32,3 +33,4 @@ 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');
export const NO_OPERATION_ERROR = () => new Error('Empty operations array');
25 changes: 0 additions & 25 deletions packages/snap/src/utils/prepare-and-sign.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { bip32Entropy } from '../../test/constants.test';
import { DEFAULT_NODE_URL } from '../constants';
import * as prepareOperationMethods from '../tezos/prepare-operations';
import * as injectTransactionMethods from '../tezos/inject-transaction';
import { prepareAndSign } from './prepare-and-sign';
import { prepare } from './prepare';

chai.use(chaiBytes);
chai.use(sinonChai);
Expand All @@ -26,11 +26,7 @@ describe('Test function: prepareAndSign', function () {
.stub(injectTransactionMethods, 'injectTransaction')
.returns(Promise.resolve('op...'));

const hash = await prepareAndSign(
[],
{ ed25519: bip32Entropy },
DEFAULT_NODE_URL,
);
const hash = await prepare([], { ed25519: bip32Entropy }, DEFAULT_NODE_URL);

expect(hash).to.equal('op...');

Expand Down
17 changes: 17 additions & 0 deletions packages/snap/src/utils/prepare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { prepareOperations } from '../tezos/prepare-operations';
import { getSigner } from './get-signer';

export const prepare = async (
operations: any[],
node: { ed25519: any },
nodeUrl: string,
) => {
const signer = await getSigner(node);

return prepareOperations(
await signer.publicKeyHash(),
await signer.publicKey(),
operations,
nodeUrl,
);
};
Loading