From 1fbf26ac1f70af6a8f8976c48eb360eb69dd1361 Mon Sep 17 00:00:00 2001 From: Andreas Gassmann Date: Fri, 15 Sep 2023 01:50:19 +0200 Subject: [PATCH 1/4] feat(): add origin --- packages/snap/src/index.test.ts | 16 +++++++++++----- packages/snap/src/index.ts | 10 +++++----- .../snap/src/rpc-methods/clear-rpc.test.ts | 6 ++++-- packages/snap/src/rpc-methods/clear-rpc.ts | 7 +++++-- packages/snap/src/rpc-methods/get-rpc.test.ts | 6 ++++-- packages/snap/src/rpc-methods/get-rpc.ts | 5 ++++- .../src/rpc-methods/send-operation.test.ts | 6 ++++-- packages/snap/src/rpc-methods/set-rpc.test.ts | 18 ++++++++++++------ packages/snap/src/rpc-methods/set-rpc.ts | 5 ++++- .../snap/src/rpc-methods/sign-payload.test.ts | 6 ++++-- packages/snap/src/rpc-methods/sign-payload.ts | 7 +++++-- packages/snap/src/ui/origin-element.ts | 5 +++++ 12 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 packages/snap/src/ui/origin-element.ts diff --git a/packages/snap/src/index.test.ts b/packages/snap/src/index.test.ts index f63c0fc..eb80399 100644 --- a/packages/snap/src/index.test.ts +++ b/packages/snap/src/index.test.ts @@ -103,7 +103,10 @@ describe('Test function: onRpcRequest', function () { ); expect(response).to.deep.equal(returnValue); - expect(sendOperationStub).to.have.been.calledWithExactly(exampleParams); + expect(sendOperationStub).to.have.been.calledWithExactly( + ORIGIN, + exampleParams, + ); expect(getAccountStub.callCount).to.equal(0, 'getAccountStub'); expect(sendOperationStub.callCount).to.equal(1, 'sendOperationsStub'); @@ -133,7 +136,10 @@ describe('Test function: onRpcRequest', function () { ); expect(response).to.deep.equal(returnValue); - expect(signPayloadStub).to.have.been.calledWithExactly(exampleParams); + expect(signPayloadStub).to.have.been.calledWithExactly( + ORIGIN, + exampleParams, + ); expect(getAccountStub.callCount).to.equal(0, 'getAccountStub'); expect(sendOperationStub.callCount).to.equal(0, 'sendOperationsStub'); @@ -159,7 +165,7 @@ describe('Test function: onRpcRequest', function () { ); expect(response).to.deep.equal(returnValue); - expect(getRpcStub).to.have.been.calledWithExactly(); + expect(getRpcStub).to.have.been.calledWithExactly(ORIGIN); expect(getAccountStub.callCount).to.equal(0, 'getAccountStub'); expect(sendOperationStub.callCount).to.equal(0, 'sendOperationsStub'); @@ -185,7 +191,7 @@ describe('Test function: onRpcRequest', function () { ); expect(response).to.deep.equal(returnValue); - expect(setRpcStub).to.have.been.calledWithExactly(exampleParams); + expect(setRpcStub).to.have.been.calledWithExactly(ORIGIN, exampleParams); expect(getAccountStub.callCount).to.equal(0, 'getAccountStub'); expect(sendOperationStub.callCount).to.equal(0, 'sendOperationsStub'); @@ -211,7 +217,7 @@ describe('Test function: onRpcRequest', function () { ); expect(response).to.deep.equal(returnValue); - expect(clearRpcStub).to.have.been.calledWithExactly(); + expect(clearRpcStub).to.have.been.calledWithExactly(ORIGIN); expect(getAccountStub.callCount).to.equal(0, 'getAccountStub'); expect(sendOperationStub.callCount).to.equal(0, 'sendOperationsStub'); diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 834301a..bdd759f 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -41,19 +41,19 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ return tezosGetAccount(origin); case 'tezos_sendOperation': - return tezosSendOperation(params); + return tezosSendOperation(origin, params); case 'tezos_signPayload': - return tezosSignPayload(params); + return tezosSignPayload(origin, params); case 'tezos_getRpc': - return tezosGetRpc(); + return tezosGetRpc(origin); case 'tezos_setRpc': - return tezosSetRpc(params); + return tezosSetRpc(origin, params); case 'tezos_clearRpc': - return tezosClearRpc(); + return tezosClearRpc(origin); default: throw METHOD_NOT_FOUND_ERROR(); diff --git a/packages/snap/src/rpc-methods/clear-rpc.test.ts b/packages/snap/src/rpc-methods/clear-rpc.test.ts index 2230dfe..a8d940b 100644 --- a/packages/snap/src/rpc-methods/clear-rpc.test.ts +++ b/packages/snap/src/rpc-methods/clear-rpc.test.ts @@ -28,7 +28,7 @@ describe('Test function: clearRpc', function () { snapStub.rpcStubs.snap_dialog.resolves(true); snapStub.rpcStubs.snap_manageState.resolves(); - const response = await tezosClearRpc(); + const response = await tezosClearRpc('http://localhost:1234'); expect(response.network).to.equal(data.network); expect(response.nodeUrl).to.equal(data.nodeUrl); @@ -41,6 +41,8 @@ describe('Test function: clearRpc', function () { it('should not clear the RPC if the user rejects the dialog', async function () { snapStub.rpcStubs.snap_dialog.resolves(false); - await expect(tezosClearRpc()).to.be.rejectedWith('User rejected'); + await expect(tezosClearRpc('http://localhost:1234')).to.be.rejectedWith( + 'User rejected', + ); }); }); diff --git a/packages/snap/src/rpc-methods/clear-rpc.ts b/packages/snap/src/rpc-methods/clear-rpc.ts index d9e12d9..5cab3e9 100644 --- a/packages/snap/src/rpc-methods/clear-rpc.ts +++ b/packages/snap/src/rpc-methods/clear-rpc.ts @@ -1,8 +1,9 @@ -import { panel, heading, text } from '@metamask/snaps-ui'; +import { panel, heading, text, divider } from '@metamask/snaps-ui'; import { DEFAULT_NODE_URL } from '../constants'; import { USER_REJECTED_ERROR } from '../utils/errors'; +import { createOriginElement } from '../ui/origin-element'; -export const tezosClearRpc = async () => { +export const tezosClearRpc = async (origin: string) => { const approved = await snap.request({ method: 'snap_dialog', params: { @@ -12,6 +13,8 @@ export const tezosClearRpc = async () => { text( `Do you want to clear the RPC settings and use the default RPC node again?`, ), + divider(), + ...createOriginElement(origin), ]), }, }); diff --git a/packages/snap/src/rpc-methods/get-rpc.test.ts b/packages/snap/src/rpc-methods/get-rpc.test.ts index 23e324c..244964f 100644 --- a/packages/snap/src/rpc-methods/get-rpc.test.ts +++ b/packages/snap/src/rpc-methods/get-rpc.test.ts @@ -28,7 +28,7 @@ describe('Test function: getRpc', function () { snapStub.rpcStubs.snap_dialog.resolves(true); - const response = await tezosGetRpc(); + const response = await tezosGetRpc('http://localhost:1234'); expect(response.network).to.equal(data.network); expect(response.nodeUrl).to.equal(data.nodeUrl); @@ -43,6 +43,8 @@ describe('Test function: getRpc', function () { snapStub.rpcStubs.snap_dialog.resolves(false); - await expect(tezosGetRpc()).to.be.rejectedWith('User rejected'); + await expect(tezosGetRpc('http://localhost:1234')).to.be.rejectedWith( + 'User rejected', + ); }); }); diff --git a/packages/snap/src/rpc-methods/get-rpc.ts b/packages/snap/src/rpc-methods/get-rpc.ts index a9aa6fa..f082848 100644 --- a/packages/snap/src/rpc-methods/get-rpc.ts +++ b/packages/snap/src/rpc-methods/get-rpc.ts @@ -1,8 +1,9 @@ import { panel, heading, text, copyable, divider } from '@metamask/snaps-ui'; import { getRpc } from '../utils/get-rpc'; import { USER_REJECTED_ERROR } from '../utils/errors'; +import { createOriginElement } from '../ui/origin-element'; -export const tezosGetRpc = async () => { +export const tezosGetRpc = async (origin: string) => { const rpc = await getRpc(); const approved = await snap.request({ @@ -17,6 +18,8 @@ export const tezosGetRpc = async () => { divider(), text(rpc.network), copyable(rpc.nodeUrl), + divider(), + ...createOriginElement(origin), ]), }, }); diff --git a/packages/snap/src/rpc-methods/send-operation.test.ts b/packages/snap/src/rpc-methods/send-operation.test.ts index f3d59a6..8336027 100644 --- a/packages/snap/src/rpc-methods/send-operation.test.ts +++ b/packages/snap/src/rpc-methods/send-operation.test.ts @@ -47,7 +47,9 @@ describe('Test function: sendOperation', function () { snapStub.rpcStubs.snap_dialog.resolves(true); - const response = await tezosSendOperation({ payload: { test: 123 } }); + const response = await tezosSendOperation('http://localhost:1234', { + payload: { test: 123 }, + }); expect(response.opHash).to.equal(data.opHash); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(1); @@ -74,7 +76,7 @@ describe('Test function: sendOperation', function () { .returns(Promise.resolve({ ed25519: bip32Entropy } as any)); await expect( - tezosSendOperation({ payload: { test: 123 } }), + tezosSendOperation('http://localhost:1234', { payload: { test: 123 } }), ).to.be.rejectedWith('User rejected'); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(1); expect(walletMethodStub.callCount).to.be.equal(1); diff --git a/packages/snap/src/rpc-methods/set-rpc.test.ts b/packages/snap/src/rpc-methods/set-rpc.test.ts index 354aa7e..64e05cc 100644 --- a/packages/snap/src/rpc-methods/set-rpc.test.ts +++ b/packages/snap/src/rpc-methods/set-rpc.test.ts @@ -56,7 +56,7 @@ describe('Test function: setRpc', function () { const data = { network: 'mainnet', nodeUrl: 'https://test.com/' }; const { fetchStub } = setupStubs(snapStub); - const response = await tezosSetRpc(data); + const response = await tezosSetRpc('http://localhost:1234', data); checkStubs(response, data, fetchStub, snapStub); @@ -74,7 +74,7 @@ describe('Test function: setRpc', function () { const data = { network: 'mainnet', nodeUrl: 'https://test.com' }; const { fetchStub } = setupStubs(snapStub); - const response = await tezosSetRpc(data); + const response = await tezosSetRpc('http://localhost:1234', data); checkStubs(response, data, fetchStub, snapStub); @@ -96,7 +96,7 @@ describe('Test function: setRpc', function () { .stub(global, 'fetch') .returns(jsonOk({ hash: 'op...', chain_id: 'testchain' })); - await expect(tezosSetRpc(data)).to.be.rejectedWith( + await expect(tezosSetRpc('http://localhost:1234', data)).to.be.rejectedWith( 'RPC URL needs to start with https://', ); @@ -114,7 +114,9 @@ describe('Test function: setRpc', function () { .stub(global, 'fetch') .returns(jsonOk({ hash: 'op...', chain_id: 'testchain' })); - expect(tezosSetRpc(data)).to.be.rejectedWith('User rejected'); + expect(tezosSetRpc('http://localhost:1234', data)).to.be.rejectedWith( + 'User rejected', + ); expect(fetchStub.callCount).to.be.equal(1, 'fetchStub'); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(0); @@ -130,7 +132,9 @@ describe('Test function: setRpc', function () { .stub(global, 'fetch') .returns(jsonOk({ test: '123' })); - expect(tezosSetRpc(data)).to.be.rejectedWith('Invalid RPC URL'); + expect(tezosSetRpc('http://localhost:1234', data)).to.be.rejectedWith( + 'Invalid RPC URL', + ); expect(fetchStub.callCount).to.be.equal(1, 'fetchStub'); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(0); @@ -146,7 +150,9 @@ describe('Test function: setRpc', function () { .stub(global, 'fetch') .rejects(new Error('Invalid RPC URL')); - expect(tezosSetRpc(data)).to.be.rejectedWith('Invalid RPC URL'); + expect(tezosSetRpc('http://localhost:1234', data)).to.be.rejectedWith( + 'Invalid RPC URL', + ); expect(fetchStub.callCount).to.be.equal(1, 'fetchStub'); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(0); diff --git a/packages/snap/src/rpc-methods/set-rpc.ts b/packages/snap/src/rpc-methods/set-rpc.ts index a1559db..2873095 100644 --- a/packages/snap/src/rpc-methods/set-rpc.ts +++ b/packages/snap/src/rpc-methods/set-rpc.ts @@ -6,8 +6,9 @@ import { RPC_INVALID_RESPONSE_ERROR, USER_REJECTED_ERROR, } from '../utils/errors'; +import { createOriginElement } from '../ui/origin-element'; -export const tezosSetRpc = async (params: any) => { +export const tezosSetRpc = async (origin: string, params: any) => { const { network, nodeUrl }: { network: string; nodeUrl: string } = params; if (!nodeUrl.startsWith('https://')) { @@ -38,6 +39,8 @@ export const tezosSetRpc = async (params: any) => { divider(), text(`${network}`), copyable(nodeUrl), + divider(), + ...createOriginElement(origin), ]), }, }); diff --git a/packages/snap/src/rpc-methods/sign-payload.test.ts b/packages/snap/src/rpc-methods/sign-payload.test.ts index ed4c232..0cad770 100644 --- a/packages/snap/src/rpc-methods/sign-payload.test.ts +++ b/packages/snap/src/rpc-methods/sign-payload.test.ts @@ -45,7 +45,9 @@ describe('Test function: signPayload', function () { snapStub.rpcStubs.snap_dialog.resolves(true); - const response = await tezosSignPayload({ payload: { test: 123 } }); + const response = await tezosSignPayload('http://localhost:1234', { + payload: { test: 123 }, + }); expect(response).to.deep.equal(data); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(1); @@ -65,7 +67,7 @@ describe('Test function: signPayload', function () { .returns(Promise.resolve({ ed25519: bip32Entropy } as any)); await expect( - tezosSignPayload({ payload: { test: 123 } }), + tezosSignPayload('http://localhost:1234', { payload: { test: 123 } }), ).to.be.rejectedWith('User rejected'); expect(snapStub.rpcStubs.snap_dialog.callCount).to.be.equal(1); expect(walletMethodStub.callCount).to.be.equal(1); diff --git a/packages/snap/src/rpc-methods/sign-payload.ts b/packages/snap/src/rpc-methods/sign-payload.ts index 515af4b..9a00930 100644 --- a/packages/snap/src/rpc-methods/sign-payload.ts +++ b/packages/snap/src/rpc-methods/sign-payload.ts @@ -1,9 +1,10 @@ -import { copyable, heading, panel, text } from '@metamask/snaps-ui'; +import { copyable, divider, 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'; +import { createOriginElement } from '../ui/origin-element'; -export const tezosSignPayload = async (params: any) => { +export const tezosSignPayload = async (origin: string, params: any) => { const { payload } = params; const wallet = await getWallet(); @@ -15,6 +16,8 @@ export const tezosSignPayload = async (params: any) => { heading('Sign Payload'), text('Do you want to sign the following payload?'), copyable(JSON.stringify(payload)), + divider(), + ...createOriginElement(origin), ]), }, }); diff --git a/packages/snap/src/ui/origin-element.ts b/packages/snap/src/ui/origin-element.ts new file mode 100644 index 0000000..c1496bc --- /dev/null +++ b/packages/snap/src/ui/origin-element.ts @@ -0,0 +1,5 @@ +import { copyable, text } from '@metamask/snaps-ui'; + +export const createOriginElement = (origin: string) => { + return [text(`Requested from:`), copyable(origin)]; +}; From 62945bcbc9ceb2c04bc06be0c4a75a4e6c6164b5 Mon Sep 17 00:00:00 2001 From: Andreas Gassmann Date: Fri, 15 Sep 2023 01:59:57 +0200 Subject: [PATCH 2/4] feat(): update checksum --- packages/snap/snap.manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 50831fb..3c22434 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/airgap-it/tezos-metamask-snap.git" }, "source": { - "shasum": "SfXyr9LaibY8HwzSGrBgpfSxZ2lsTin01dLKk4tefLQ=", + "shasum": "uUQoKqQ+SIvmVvRLfTLHO0uELyeSXLxcngMvyO4uF+U=", "location": { "npm": { "filePath": "dist/bundle.js", From ac795e65137dada07aad4a0b93e44ac6c8e61b67 Mon Sep 17 00:00:00 2001 From: Andreas Gassmann Date: Fri, 15 Sep 2023 02:04:49 +0200 Subject: [PATCH 3/4] feat(): more op details --- packages/snap/snap.manifest.json | 2 +- .../snap/src/rpc-methods/send-operation.ts | 64 ++++++++++++++----- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index 3c22434..c302cdd 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/airgap-it/tezos-metamask-snap.git" }, "source": { - "shasum": "uUQoKqQ+SIvmVvRLfTLHO0uELyeSXLxcngMvyO4uF+U=", + "shasum": "ntvGpQQ6EK6wxpx6eXq4lEsYqrocwuPqR9sCK2RixY0=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/rpc-methods/send-operation.ts b/packages/snap/src/rpc-methods/send-operation.ts index b108555..4122814 100644 --- a/packages/snap/src/rpc-methods/send-operation.ts +++ b/packages/snap/src/rpc-methods/send-operation.ts @@ -1,30 +1,62 @@ 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 { getRpc } from '../utils/get-rpc'; +import { to } from '../utils/to'; +import { USER_REJECTED_ERROR } from '../utils/errors'; +import { TezosTransactionOperation } from '../tezos/types'; +import { createOriginElement } from '../ui/origin-element'; -export const tezosSendOperation = async (params: any) => { +const mutezToTez = (mutez: string): string => { + return BigNumber(mutez).shiftedBy(-6).toString(10); +}; + +export const tezosSendOperation = async (origin: string, params: any) => { const { payload } = params; const wallet = await getWallet(); const rpc = await getRpc(); - const approved = await snap.request({ - method: 'snap_dialog', - params: { - type: 'confirmation', - content: panel([ - heading('Sign Operation'), - text('Do you want to sign the following payload?'), - copyable(JSON.stringify(payload, null, 2)), - divider(), - text(`The operation will be submit to the following node:`), - copyable(rpc.nodeUrl), - ]), - }, - }); + const typedPayload: TezosTransactionOperation[] = payload; + + const humanReadable = []; + + if (typedPayload[0] && typedPayload[0].kind === 'transaction') { + 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`), + ); + } + + const [approveError, approved] = await to( + snap.request({ + method: 'snap_dialog', + params: { + type: 'confirmation', + content: panel([ + heading('Sign Operation'), + text('Do you want to sign the following payload?'), + ...humanReadable, + copyable(JSON.stringify(payload, null, 2)), + divider(), + text(`The operation will be submit to the following node:`), + copyable(rpc.nodeUrl), + divider(), + ...createOriginElement(origin), + ]), + }, + }), + ); + + if (approveError) { + throw new Error(`APPROVE ERROR ${approveError.message}`); + } if (!approved) { - throw new Error('User rejected'); + throw USER_REJECTED_ERROR(); } return { opHash: await prepareAndSign(payload, wallet, rpc.nodeUrl) }; From 1ff1841f37a923e9397ca8b6966c30c6dcb20c93 Mon Sep 17 00:00:00 2001 From: Andreas Gassmann Date: Fri, 15 Sep 2023 02:09:15 +0200 Subject: [PATCH 4/4] feat(): format --- packages/snap/snap.manifest.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index c302cdd..cd22ac2 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -22,13 +22,7 @@ "snap_manageState": {}, "snap_getBip32Entropy": [ { - "path": [ - "m", - "44'", - "1729'", - "0'", - "0'" - ], + "path": ["m", "44'", "1729'", "0'", "0'"], "curve": "ed25519" } ],