on master help sign in
Merge pull request #615 from urbit/m/unsigned-sender
23e4f98fang
package-lock.json+40−40
@@ -8866,36 +8866,36 @@
8866 }
8866 }
8867 },
8867 },
8868 "@walletconnect/browser-utils": {
8868 "@walletconnect/browser-utils": {
8869− "version": "1.6.1",
8869+ "version": "1.6.2",
8870− "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.6.1.tgz",
8870+ "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.6.2.tgz",
8871− "integrity": "sha512-73RoyCuOqqK6hrRspkLRPFAsp5/ir9omyBbQygaFmFaCyxFKDKNMmrq/9UZvicqlhNCSutuuOrZizbzZsEVhtA==",
8871+ "integrity": "sha512-8+Yc9iXe2VD7C5I2iZjXKBvzTqDBse7yC9PrCWFJdUYW2rInk7BSqw/9NFhBk41sPonm2dklzwaMJApoW7V5bw==",
8872 "requires": {
8872 "requires": {
8873 "@walletconnect/safe-json": "1.0.0",
8873 "@walletconnect/safe-json": "1.0.0",
8874− "@walletconnect/types": "^1.6.1",
8874+ "@walletconnect/types": "^1.6.2",
8875 "@walletconnect/window-getters": "1.0.0",
8875 "@walletconnect/window-getters": "1.0.0",
8876 "@walletconnect/window-metadata": "1.0.0",
8876 "@walletconnect/window-metadata": "1.0.0",
8877 "detect-browser": "5.2.0"
8877 "detect-browser": "5.2.0"
8878 }
8878 }
8879 },
8879 },
8880 "@walletconnect/client": {
8880 "@walletconnect/client": {
8881− "version": "1.6.1",
8881+ "version": "1.6.2",
8882− "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.6.1.tgz",
8882+ "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.6.2.tgz",
8883− "integrity": "sha512-W80gELPzPD2x07OjBGK6LApCCi0uI3YSWAf2c/el5LA3OGLGXG29zVC5TEsK4y1uJwbwoifl1hn6rMhXocrA4Q==",
8883+ "integrity": "sha512-ETeYAczgES2jlo5FuZyAfJmrRX38S7uy48zy89C7Rr4P0ZUy4fHXpnHGy0k7vI1tgikQXXQ0dv7DoLOST3V3Fg==",
8884 "requires": {
8884 "requires": {
8885− "@walletconnect/core": "^1.6.1",
8885+ "@walletconnect/core": "^1.6.2",
8886− "@walletconnect/iso-crypto": "^1.6.1",
8886+ "@walletconnect/iso-crypto": "^1.6.2",
8887− "@walletconnect/types": "^1.6.1",
8887+ "@walletconnect/types": "^1.6.2",
8888− "@walletconnect/utils": "^1.6.1"
8888+ "@walletconnect/utils": "^1.6.2"
8889 }
8889 }
8890 },
8890 },
8891 "@walletconnect/core": {
8891 "@walletconnect/core": {
8892− "version": "1.6.1",
8892+ "version": "1.6.2",
8893− "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.6.1.tgz",
8893+ "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.6.2.tgz",
8894− "integrity": "sha512-rYYsBywbJsULk40szBlh2Xh9z01D/FmkJlfkUeqq6zRLr7HaUm9zSoI9+WaA2yxfVGzw+HMnuukT4UINKZGgPg==",
8894+ "integrity": "sha512-XQLcHXVA2SWFnwQz6y8AAhRV2P2q7qoWfhz9vV1ZJgKJ3i8mi9Dp9zZsYRKDl+T5bRpGpdxv/PZ3JpQvr1IDgQ==",
8895 "requires": {
8895 "requires": {
8896− "@walletconnect/socket-transport": "^1.6.1",
8896+ "@walletconnect/socket-transport": "^1.6.2",
8897− "@walletconnect/types": "^1.6.1",
8897+ "@walletconnect/types": "^1.6.2",
8898− "@walletconnect/utils": "^1.6.1"
8898+ "@walletconnect/utils": "^1.6.2"
8899 }
8899 }
8900 },
8900 },
8901 "@walletconnect/crypto": {
8901 "@walletconnect/crypto": {
@@ -8932,13 +8932,13 @@
8932 "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ=="
8932 "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ=="
8933 },
8933 },
8934 "@walletconnect/iso-crypto": {
8934 "@walletconnect/iso-crypto": {
8935− "version": "1.6.1",
8935+ "version": "1.6.2",
8936− "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.6.1.tgz",
8936+ "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.6.2.tgz",
8937− "integrity": "sha512-BM+5R7HYB4r0xojw+FeSg7DPdxY3/i6Z1tLCD83wRaK+Vs2QUvI+SdmU0OLm9AJmodos/Qrhw3IgZSEtExT9yw==",
8937+ "integrity": "sha512-hznvNn3FojyRNrA5QWfkK5kyzlVbeE8gFZ7hETtSPpt24xjPi0WYo9yPxMJuMBXw2hbwSRVPkxjR354D4miZmQ==",
8938 "requires": {
8938 "requires": {
8939 "@walletconnect/crypto": "^1.0.1",
8939 "@walletconnect/crypto": "^1.0.1",
8940− "@walletconnect/types": "^1.6.1",
8940+ "@walletconnect/types": "^1.6.2",
8941− "@walletconnect/utils": "^1.6.1"
8941+ "@walletconnect/utils": "^1.6.2"
8942 }
8942 }
8943 },
8943 },
8944 "@walletconnect/jsonrpc-types": {
8944 "@walletconnect/jsonrpc-types": {
@@ -8964,13 +8964,13 @@
8964 "integrity": "sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw=="
8964 "integrity": "sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw=="
8965 },
8965 },
8966 "@walletconnect/qrcode-modal": {
8966 "@walletconnect/qrcode-modal": {
8967− "version": "1.6.1",
8967+ "version": "1.6.2",
8968− "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.6.1.tgz",
8968+ "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.6.2.tgz",
8969− "integrity": "sha512-CCw7fdnkqMK/NX1J2ji+PS8z0VNKC9xFCvG4rp+rZENyA+LHBgeviteD60dZW9nl/kEKvSwMQLSI82L8To8ZGg==",
8969+ "integrity": "sha512-+d1SsUvLzFUWY60X+OKumejzQj1BYkZGPdIHNlm2sjsDhsGTMrID7fVL+FlHq/I63bT/9Bsv08dt7FeG7y+p/Q==",
8970 "requires": {
8970 "requires": {
8971− "@walletconnect/browser-utils": "^1.6.1",
8971+ "@walletconnect/browser-utils": "^1.6.2",
8972 "@walletconnect/mobile-registry": "^1.4.0",
8972 "@walletconnect/mobile-registry": "^1.4.0",
8973− "@walletconnect/types": "^1.6.1",
8973+ "@walletconnect/types": "^1.6.2",
8974 "copy-to-clipboard": "^3.3.1",
8974 "copy-to-clipboard": "^3.3.1",
8975 "preact": "10.4.1",
8975 "preact": "10.4.1",
8976 "qrcode": "1.4.4"
8976 "qrcode": "1.4.4"
@@ -8992,12 +8992,12 @@
8992 "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg=="
8992 "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg=="
8993 },
8993 },
8994 "@walletconnect/socket-transport": {
8994 "@walletconnect/socket-transport": {
8995− "version": "1.6.1",
8995+ "version": "1.6.2",
8996− "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.6.1.tgz",
8996+ "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.6.2.tgz",
8997− "integrity": "sha512-dn1eBO3SrgIUpBo8geKCvAUe7pPg/3qO6KRibj7cF+kY+u2hNId0W79UaZmHFbX9TUVZHg+oYBKonydv8ozUAw==",
8997+ "integrity": "sha512-G07Iqt2WkkqS8Q5M+ERNjeUV1A2MEBpiYOn6H9xmv7dkoXWQbTxd4qJbIgP3jAcVX0yYyLaf/gSFWFg7tpp8dw==",
8998 "requires": {
8998 "requires": {
8999− "@walletconnect/types": "^1.6.1",
8999+ "@walletconnect/types": "^1.6.2",
9000− "@walletconnect/utils": "^1.6.1",
9000+ "@walletconnect/utils": "^1.6.2",
9001 "ws": "7.5.3"
9001 "ws": "7.5.3"
9002 },
9002 },
9003 "dependencies": {
9003 "dependencies": {
@@ -9009,19 +9009,19 @@
9009 }
9009 }
9010 },
9010 },
9011 "@walletconnect/types": {
9011 "@walletconnect/types": {
9012− "version": "1.6.1",
9012+ "version": "1.6.2",
9013− "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.6.1.tgz",
9013+ "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.6.2.tgz",
9014− "integrity": "sha512-seqL/ONP0iRNVEPbbl0NVCSWTPym4NiU2R/MPx9zK63dTH6lgR5Kp7QRfz+EWCoP6SCPr9vtyhQ0CMl3/qdCmw=="
9014+ "integrity": "sha512-21jvvnUcbdcH6cSe/8B/KoeU+foNbvNsBOYY3AWW0LMuD/N5CnGZHGYcbEq0R0z7Gj4lp9IFvDIBLM7WaG1IiQ=="
9015 },
9015 },
9016 "@walletconnect/utils": {
9016 "@walletconnect/utils": {
9017− "version": "1.6.1",
9017+ "version": "1.6.2",
9018− "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.6.1.tgz",
9018+ "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.6.2.tgz",
9019− "integrity": "sha512-gi/7YKOEmA2hbirzj5MVVkgpIq2TI8iI4qqGUMtSZYHKoU170UzmML3IYPRL6mEBz6Jv77mDOp60JsRMzWdsxA==",
9019+ "integrity": "sha512-/J5xO0qzGYw/gwpIYZ72ml0TASGClRJ/siFG+5QDghXvRgQKYiQKPq7nBDbtcUd3pjbuGKTOeTLm01S4urGvTQ==",
9020 "requires": {
9020 "requires": {
9021− "@walletconnect/browser-utils": "^1.6.1",
9021+ "@walletconnect/browser-utils": "^1.6.2",
9022 "@walletconnect/encoding": "^1.0.0",
9022 "@walletconnect/encoding": "^1.0.0",
9023 "@walletconnect/jsonrpc-utils": "^1.0.0",
9023 "@walletconnect/jsonrpc-utils": "^1.0.0",
9024− "@walletconnect/types": "^1.6.1",
9024+ "@walletconnect/types": "^1.6.2",
9025 "bn.js": "4.11.8",
9025 "bn.js": "4.11.8",
9026 "js-sha3": "0.8.0",
9026 "js-sha3": "0.8.0",
9027 "query-string": "6.13.5"
9027 "query-string": "6.13.5"
package.json+2−2
@@ -32,8 +32,8 @@
32 "@types/styled-components": "^5.1.10",
32 "@types/styled-components": "^5.1.10",
33 "@types/styled-system": "^5.1.11",
33 "@types/styled-system": "^5.1.11",
34 "@types/styled-system__css": "^5.0.15",
34 "@types/styled-system__css": "^5.0.15",
35− "@walletconnect/client": "^1.6.1",
35+ "@walletconnect/client": "^1.6.2",
36− "@walletconnect/qrcode-modal": "^1.6.1",
36+ "@walletconnect/qrcode-modal": "^1.6.2",
37 "@welldone-software/why-did-you-render": "^3.2.3",
37 "@welldone-software/why-did-you-render": "^3.2.3",
38 "async-retry": "^1.2.3",
38 "async-retry": "^1.2.3",
39 "azimuth-js": "^0.22.1",
39 "azimuth-js": "^0.22.1",
src/components/InlineEthereumTransaction.js+11−1
@@ -26,6 +26,7 @@ import CopyButton from './CopyButton';
26import ProgressButton from './ProgressButton';
26import ProgressButton from './ProgressButton';
27import { convertToInt } from 'lib/convertToInt';
27import { convertToInt } from 'lib/convertToInt';
28import NeedFundsNotice from './NeedFundsNotice';
28import NeedFundsNotice from './NeedFundsNotice';
29+import NoticeBox from './NoticeBox';
29
30
30export default function InlineEthereumTransaction({
31export default function InlineEthereumTransaction({
31 // from useEthereumTransaction.bind
32 // from useEthereumTransaction.bind
@@ -33,6 +34,7 @@ export default function InlineEthereumTransaction({
33 canSign,
34 canSign,
34 generateAndSign,
35 generateAndSign,
35 signed,
36 signed,
37+ fakeSigned,
36 broadcast,
38 broadcast,
37 broadcasted,
39 broadcasted,
38 confirmed,
40 confirmed,
@@ -277,7 +279,15 @@ export default function InlineEthereumTransaction({
277 </>
279 </>
278 )}
280 )}
279
281
280− {showSignedTx && (
282+ {showSignedTx && fakeSigned && (
283+ <>
284+ <Grid.Item full as={NoticeBox} className="mt2">
285+ Your wallet will sign the transaction upon sending it.
286+ </Grid.Item>
287+ </>
288+ )}
289+
290+ {showSignedTx && !fakeSigned && (
281 <>
291 <>
282 <Grid.Item
292 <Grid.Item
283 full
293 full
src/lib/WalletConnect.ts+25−3
@@ -1,27 +1,49 @@
1import { ITxData } from '@walletconnect/types';
1import { ITxData } from '@walletconnect/types';
2import { Transaction } from '@ethereumjs/tx';
2import { Transaction } from '@ethereumjs/tx';
3import { stripHexPrefix } from './utils/address';
3import { stripHexPrefix } from './utils/address';
4+import { FakeSignableTx, FakeSignResult } from './txn';
5+import Web3 from 'web3';
4
6
5type SignWalletConnectTransactionArgs = {
7type SignWalletConnectTransactionArgs = {
6 from: string;
8 from: string;
7 txn: Transaction;
9 txn: Transaction;
8− txnSigner: (args: ITxData) => Promise<string>;
10+ txnSigner: (txn: ITxData) => Promise<string>;
11+ txnSender: (txn: FakeSignableTx) => Promise<string>;
9};
12};
10
13
11const walletConnectSignTransaction = async ({
14const walletConnectSignTransaction = async ({
12 from,
15 from,
13 txn,
16 txn,
14 txnSigner,
17 txnSigner,
18+ txnSender,
15}: SignWalletConnectTransactionArgs) => {
19}: SignWalletConnectTransactionArgs) => {
20+ // TS compiler complains next line about missing from and gas, which we set on the following lines
16− let wcFormattedTx = txn.toJSON();
21+ let wcFormattedTx: FakeSignableTx = txn.toJSON();
17 wcFormattedTx.from = from;
22 wcFormattedTx.from = from;
18 wcFormattedTx.gas = wcFormattedTx.gasLimit;
23 wcFormattedTx.gas = wcFormattedTx.gasLimit;
19
24
25+ let signature;
26+ try {
20− const signature = await txnSigner(wcFormattedTx);
27+ signature = await txnSigner(wcFormattedTx);
28+ } catch (e) {
29+ if (e.message === 'METHOD_NOT_SUPPORTED') {
30+ console.log('connected wc wallet does not support tx signing.');
31+ return FakeSignResult(
32+ wcFormattedTx,
33+ walletConnectSendTransaction(txnSender)
34+ );
35+ } else {
36+ throw e;
37+ }
38+ }
21 const serializedTx = Buffer.from(stripHexPrefix(signature), 'hex');
39 const serializedTx = Buffer.from(stripHexPrefix(signature), 'hex');
22 const signedTx = Transaction.fromSerializedTx(serializedTx);
40 const signedTx = Transaction.fromSerializedTx(serializedTx);
23
41
24 return signedTx;
42 return signedTx;
25};
43};
26
44
45+const walletConnectSendTransaction = (
46+ txnSender: (tx: FakeSignableTx) => Promise<string>
47+) => (txn: FakeSignableTx, _web3: Web3): Promise<string> => txnSender(txn);
48+
27export { walletConnectSignTransaction };
49export { walletConnectSignTransaction };
src/lib/metamask.jsdeleted−28
@@ -1,28 +0,0 @@
1−import { toHex } from 'web3-utils';
2−
3−export function MetamaskWallet(address) {
4− this.address = address;
5−}
6−
7−export function FakeMetamaskTransaction(txnData) {
8− this.txnData = txnData;
9−
10− this.serialize = function() {
11− return '0';
12− };
13−}
14−
15−// Cheat a bit and pretend to sign transaction
16−// Sign it actually when sending
17−export const metamaskSignTransaction = async (txn, from) => {
18− const metamaskFormattedTxn = {
19− data: txn.data,
20− gasLimit: txn.gasLimit,
21− gasPrice: txn.gasPrice,
22− nonce: txn.nonce,
23− to: toHex(txn.to),
24− from: toHex(from),
25− };
26−
27− return new FakeMetamaskTransaction(metamaskFormattedTxn);
28−};
src/lib/metamask.tsnew file+36
@@ -0,0 +1,36 @@
1+import Web3 from 'web3';
2+import { toHex } from 'web3-utils';
3+import { FakeSignableTx, FakeSignResult } from './txn';
4+
5+export function MetamaskWallet(address) {
6+ this.address = address;
7+}
8+
9+// Cheat a bit and pretend to sign transaction
10+// Sign it actually when sending
11+export const metamaskSignTransaction = async (
12+ txn: FakeSignableTx,
13+ from: string
14+) => {
15+ txn.from = from;
16+ return FakeSignResult(txn, metamaskSendTransaction);
17+};
18+
19+const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
20+ const metamaskFormattedTxn = {
21+ data: txn.data,
22+ gasLimit: txn.gasLimit,
23+ gasPrice: txn.gasPrice,
24+ nonce: txn.nonce,
25+ to: toHex(txn.to),
26+ from: toHex(txn.from),
27+ };
28+
29+ //NOTE since this gives us a receipt instead of just the hash of the signed
30+ // tx, it makes us wait for confirmation outside of our own
31+ // waitForTransactionConfirm. because of this the progress bar loses
32+ // its progressiveness for metamask users.
33+ //TODO can we do better?
34+ const receipt = await web3.eth.sendTransaction(metamaskFormattedTxn);
35+ return receipt.transactionHash;
36+};
src/lib/reticket.js+4
@@ -42,6 +42,8 @@ export async function reticketPointBetweenWallets({
42 onUpdate,
42 onUpdate,
43 transferEth = false,
43 transferEth = false,
44 nextRevision = INITIAL_NETWORK_KEY_REVISION,
44 nextRevision = INITIAL_NETWORK_KEY_REVISION,
45+ txnSigner,
46+ txnSender,
45}) {
47}) {
46 const askForFunding = (address, minBalance, balance) =>
48 const askForFunding = (address, minBalance, balance) =>
47 onUpdate({
49 onUpdate({
@@ -171,6 +173,8 @@ export async function reticketPointBetweenWallets({
171 chainId,
173 chainId,
172 gasPrice: gasPriceGwei,
174 gasPrice: gasPriceGwei,
173 gasLimit: txs[i].gas,
175 gasLimit: txs[i].gas,
176+ txnSigner,
177+ txnSender,
174 });
178 });
175 txPairs.push({
179 txPairs.push({
176 raw: hexify(stx.serialize()),
180 raw: hexify(stx.serialize()),
src/lib/tank.js+6−6
@@ -3,7 +3,6 @@
3import retry from 'async-retry';
3import retry from 'async-retry';
4import { toBN } from 'web3-utils';
4import { toBN } from 'web3-utils';
5import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
5import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
6−import { WALLET_TYPES } from 'lib/constants';
7import { isRopsten } from './flags';
6import { isRopsten } from './flags';
8
7
9const TANK_ADDRESS = '0x40f0A6db85f8D7A54fF3eA915b040dE8Cd4A0Eb5';
8const TANK_ADDRESS = '0x40f0A6db85f8D7A54fF3eA915b040dE8Cd4A0Eb5';
@@ -64,6 +63,12 @@ const ensureFundsFor = async (
64 throw new Error('tank: no transactions provided!');
63 throw new Error('tank: no transactions provided!');
65 }
64 }
66
65
66+ //TODO this depends on FakeSignResult serialization in a somewhat ugly way
67+ if (signedTxs.some(tx => tx === '0x???')) {
68+ console.log('tank: disabling for unsigned sender wallet');
69+ return false;
70+ }
71+
67 const balance = toBN(await web3.eth.getBalance(address));
72 const balance = toBN(await web3.eth.getBalance(address));
68 cost = toBN(cost);
73 cost = toBN(cost);
69
74
@@ -76,11 +81,6 @@ const ensureFundsFor = async (
76 return false;
81 return false;
77 }
82 }
78
83
79− if (walletType === WALLET_TYPES.METAMASK) {
80− console.log('tank: disabling for metamask login');
81−
82− return false;
83− }
84 try {
84 try {
85 // TODO: if we can't always (easily) provide a point, and the
85 // TODO: if we can't always (easily) provide a point, and the
86 // fundTransactions call is gonna fail anyway, should we maybe
86 // fundTransactions call is gonna fail anyway, should we maybe
src/lib/txn.ts+52−13
@@ -1,5 +1,6 @@
1import Common, { Chain, Hardfork } from '@ethereumjs/common';
1import Common, { Chain, Hardfork } from '@ethereumjs/common';
2−import { Transaction } from '@ethereumjs/tx';
2+import { JsonTx, Transaction } from '@ethereumjs/tx';
3+import { ITxData } from '@walletconnect/types';
3import { toHex } from 'web3-utils';
4import { toHex } from 'web3-utils';
4import { safeFromWei, safeToWei } from './lib';
5import { safeFromWei, safeToWei } from './lib';
5import retry from 'async-retry';
6import retry from 'async-retry';
@@ -8,10 +9,11 @@ import { NETWORK_TYPES } from './network';
8import { ledgerSignTransaction } from './ledger';
9import { ledgerSignTransaction } from './ledger';
9import { trezorSignTransaction } from './trezor';
10import { trezorSignTransaction } from './trezor';
10import { walletConnectSignTransaction } from './WalletConnect';
11import { walletConnectSignTransaction } from './WalletConnect';
11−import { metamaskSignTransaction, FakeMetamaskTransaction } from './metamask';
12+import { metamaskSignTransaction } from './metamask';
12import { addHexPrefix } from './utils/address';
13import { addHexPrefix } from './utils/address';
13import { CHECK_BLOCK_EVERY_MS, WALLET_TYPES } from './constants';
14import { CHECK_BLOCK_EVERY_MS, WALLET_TYPES } from './constants';
14import { patp2dec } from './patp2dec';
15import { patp2dec } from './patp2dec';
16+import Web3 from 'web3';
15
17
16const RETRY_OPTIONS = {
18const RETRY_OPTIONS = {
17 retries: 99999,
19 retries: 99999,
@@ -20,6 +22,32 @@ const RETRY_OPTIONS = {
20 randomize: false,
22 randomize: false,
21};
23};
22
24
25+// catch-all type: Metamask passes an obj shaped like ITxData, WC passes JsonTx + from
26+export type FakeSignableTx =
27+ | ITxData
28+ | (JsonTx & { from: string; to: string; gas: string });
29+
30+type TxSender = (txn: FakeSignableTx, web3: Web3) => Promise<string>; // Metamask requires Web3 (txhash)
31+
32+type SignedTx = {
33+ serialize: () => string;
34+};
35+
36+type FakeSignedTx = SignedTx & {
37+ txn: FakeSignableTx;
38+ send: TxSender;
39+};
40+
41+const FakeSignResult = (txn: FakeSignableTx, send: TxSender): FakeSignedTx => {
42+ return {
43+ txn,
44+ serialize: () => {
45+ return '???'; //NOTE update tank.js when changing this!
46+ },
47+ send,
48+ };
49+};
50+
23const signTransaction = async ({
51const signTransaction = async ({
24 wallet,
52 wallet,
25 walletType,
53 walletType,
@@ -30,7 +58,8 @@ const signTransaction = async ({
30 chainId, // number
58 chainId, // number
31 gasPrice, // string, in gwei
59 gasPrice, // string, in gwei
32 gasLimit, // string | number
60 gasLimit, // string | number
33− txnSigner, // optionally inject a transaction signing function
61+ txnSigner, // optionally inject a transaction signing function,
62+ txnSender, // and a sending function, for wallets that need these passed in.
34}) => {
63}) => {
35 // TODO: require these in txn object
64 // TODO: require these in txn object
36 nonce = toHex(nonce);
65 nonce = toHex(nonce);
@@ -115,6 +144,7 @@ const signTransaction = async ({
115 from,
144 from,
116 txn: stx,
145 txn: stx,
117 txnSigner,
146 txnSigner,
147+ txnSender,
118 });
148 });
119 } else {
149 } else {
120 stx = stx.sign(wallet.privateKey);
150 stx = stx.sign(wallet.privateKey);
@@ -123,19 +153,27 @@ const signTransaction = async ({
123 return stx;
153 return stx;
124};
154};
125
155
126−const sendSignedTransaction = (web3, stx, doubtNonceError) => {
156+const sendSignedTransaction = (
127− // Handle fake metamask transaction
157+ web3: Web3,
128− let eventEmitter;
158+ stx: Transaction | FakeSignedTx,
129− let rawTx;
159+ doubtNonceError: boolean
160+): Promise<string> => {
161+ // if we couldn't sign it, we depend on the given sender function
130− if (stx instanceof FakeMetamaskTransaction) {
162+ if (!(stx instanceof Transaction)) {
131− eventEmitter = web3.eth.sendTransaction(stx.txnData);
163+ if (doubtNonceError) {
132− } else {
164+ console.log('why doubting nonce error? tank unavailable without rawtx.');
133− rawTx = hexify(stx.serialize());
165+ }
134− eventEmitter = web3.eth.sendSignedTransaction(rawTx);
166+ if (!stx.send) {
167+ throw new Error('no sign+sending function available');
168+ }
169+ return stx.send(stx.txn, web3);
135 }
170 }
171+
172+ let rawTx: string = hexify(stx.serialize());
173+ let eventEmitter = web3.eth.sendSignedTransaction(rawTx);
136 return new Promise(async (resolve, reject) => {
174 return new Promise(async (resolve, reject) => {
137 eventEmitter
175 eventEmitter
138− .on('transactionHash', hash => {
176+ .on('transactionHash', (hash: string) => {
139 resolve(hash);
177 resolve(hash);
140 })
178 })
141 .on('error', err => {
179 .on('error', err => {
@@ -240,6 +278,7 @@ const canDecodePatp = p => {
240
278
241export {
279export {
242 RETRY_OPTIONS,
280 RETRY_OPTIONS,
281+ FakeSignResult,
243 signTransaction,
282 signTransaction,
244 sendSignedTransaction,
283 sendSignedTransaction,
245 waitForTransactionConfirm,
284 waitForTransactionConfirm,
src/lib/useEthereumTransaction.js+23−3
@@ -25,10 +25,12 @@ import useGasPrice from 'lib/useGasPrice';
25import { timeout } from 'lib/timeout';
25import { timeout } from 'lib/timeout';
26import { safeToWei, safeFromWei } from 'lib/lib';
26import { safeToWei, safeFromWei } from 'lib/lib';
27import { useWalletConnect } from './useWalletConnect';
27import { useWalletConnect } from './useWalletConnect';
28+import { Transaction } from '@ethereumjs/tx';
28
29
29const STATE = {
30const STATE = {
30 NONE: 'NONE',
31 NONE: 'NONE',
31 SIGNED: 'SIGNED',
32 SIGNED: 'SIGNED',
33+ FAKE_SIGNED: 'FAKE_SIGNED',
32 BROADCASTED: 'BROADCASTED',
34 BROADCASTED: 'BROADCASTED',
33 CONFIRMED: 'CONFIRMED',
35 CONFIRMED: 'CONFIRMED',
34 COMPLETED: 'COMPLETED',
36 COMPLETED: 'COMPLETED',
@@ -50,7 +52,10 @@ export default function useEthereumTransaction(
50 const { wallet, walletType, walletHdPath } = useWallet();
52 const { wallet, walletType, walletHdPath } = useWallet();
51 const { web3, networkType } = useNetwork();
53 const { web3, networkType } = useNetwork();
52 const { pointCursor } = usePointCursor();
54 const { pointCursor } = usePointCursor();
55+ const {
53− const { signTransaction: wcSign } = useWalletConnect();
56+ signTransaction: wcSign,
57+ sendTransaction: wcSend,
58+ } = useWalletConnect();
54
59
55 const _web3 = need.web3(web3);
60 const _web3 = need.web3(web3);
56 const _wallet = need.wallet(wallet);
61 const _wallet = need.wallet(wallet);
@@ -81,7 +86,8 @@ export default function useEthereumTransaction(
81 const initializing = nonce === undefined || chainId === undefined;
86 const initializing = nonce === undefined || chainId === undefined;
82 const constructed = !!unsignedTransactions;
87 const constructed = !!unsignedTransactions;
83 const isDefaultState = state === STATE.NONE;
88 const isDefaultState = state === STATE.NONE;
84− const signed = state === STATE.SIGNED;
89+ const signed = state === STATE.SIGNED || state === STATE.FAKE_SIGNED;
90+ const fakeSigned = state === STATE.FAKE_SIGNED;
85 const broadcasted = state === STATE.BROADCASTED;
91 const broadcasted = state === STATE.BROADCASTED;
86 const confirmed = state === STATE.CONFIRMED;
92 const confirmed = state === STATE.CONFIRMED;
87 const completed = state === STATE.COMPLETED;
93 const completed = state === STATE.COMPLETED;
@@ -113,8 +119,11 @@ export default function useEthereumTransaction(
113 // Due to React's Rules of Hooks, optionally inject the
119 // Due to React's Rules of Hooks, optionally inject the
114 // WC transaction signing function
120 // WC transaction signing function
115 // https://reactjs.org/docs/hooks-rules.html
121 // https://reactjs.org/docs/hooks-rules.html
122+ //TODO this is duplicated across relevant callsites, should refactor
116 const txnSigner =
123 const txnSigner =
117 walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
124 walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
125+ const txnSender =
126+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
118
127
119 const txns = await Promise.all(
128 const txns = await Promise.all(
120 utxs.map((utx, i) =>
129 utxs.map((utx, i) =>
@@ -129,12 +138,21 @@ export default function useEthereumTransaction(
129 gasPrice: gasPrice.toFixed(0),
138 gasPrice: gasPrice.toFixed(0),
130 gasLimit,
139 gasLimit,
131 txnSigner,
140 txnSigner,
141+ txnSender,
132 })
142 })
133 )
143 )
134 );
144 );
135
145
136 setSignedTransactions(txns);
146 setSignedTransactions(txns);
147+
148+ // Per return type of `signTransaction`, tx is either a Transaction or FakeSignedTx.
149+ // We check the negation in this case, since TS cannot perform run-time check of type
150+ // FakeSignedTx, but it can check for an instance of the ethereumjs/tx class.
151+ if (txns.some(tx => !(tx instanceof Transaction))) {
152+ setState(STATE.FAKE_SIGNED);
153+ } else {
137− setState(STATE.SIGNED);
154+ setState(STATE.SIGNED);
155+ }
138 } catch (error) {
156 } catch (error) {
139 console.error('error signing txs', error);
157 console.error('error signing txs', error);
140 setError(error);
158 setError(error);
@@ -151,6 +169,7 @@ export default function useEthereumTransaction(
151 walletHdPath,
169 walletHdPath,
152 walletType,
170 walletType,
153 wcSign,
171 wcSign,
172+ wcSend,
154 ]);
173 ]);
155
174
156 const broadcast = useCallback(async () => {
175 const broadcast = useCallback(async () => {
@@ -327,6 +346,7 @@ export default function useEthereumTransaction(
327 canSign,
346 canSign,
328 generateAndSign,
347 generateAndSign,
329 signed,
348 signed,
349+ fakeSigned,
330 broadcast,
350 broadcast,
331 broadcasted,
351 broadcasted,
332 confirmed,
352 confirmed,
src/lib/useWalletConnect.ts+52−7
@@ -3,7 +3,7 @@ import QRCodeModal from '@walletconnect/qrcode-modal';
3import { ITxData } from '@walletconnect/types';
3import { ITxData } from '@walletconnect/types';
4
4
5import { useEffect, useMemo, useState } from 'react';
5import { useEffect, useMemo, useState } from 'react';
6−import { Just } from 'folktale/maybe';
6+import { Just, Nothing } from 'folktale/maybe';
7
7
8import { useWallet } from 'store/wallet';
8import { useWallet } from 'store/wallet';
9import { getAuthToken } from './authToken';
9import { getAuthToken } from './authToken';
@@ -81,17 +81,30 @@ export const useWalletConnect = () => {
81 return;
81 return;
82 }
82 }
83
83
84+ let authToken = Nothing();
85+ try {
84− const authToken = await getAuthToken({
86+ const token = await getAuthToken({
85− address,
87+ address,
86− connector,
88+ connector,
87− walletType: WALLET_TYPES.WALLET_CONNECT,
89+ walletType: WALLET_TYPES.WALLET_CONNECT,
90+ });
91+ authToken = Just(token);
92+ } catch (e) {
93+ if (e.message === 'METHOD_NOT_SUPPORTED') {
94+ console.warn(
95+ 'wallet does not support message signing. proceeding without auth token.'
88− });
96+ );
97+ } else {
98+ throw e;
99+ //TODO should errors with this *really* prevent login?
100+ }
101+ }
89
102
90 const wallet: WalletConnectWallet = {
103 const wallet: WalletConnectWallet = {
91 address,
104 address,
92 };
105 };
93
106
94− setAuthToken(Just(authToken));
107+ setAuthToken(authToken);
95 setWallet(Just(wallet));
108 setWallet(Just(wallet));
96 };
109 };
97
110
@@ -139,6 +152,37 @@ export const useWalletConnect = () => {
139 data,
152 data,
140 nonce,
153 nonce,
141 })
154 })
155+ .then((signature: string) => resolve(signature))
156+ .catch((error: Error) => reject(error));
157+ });
158+ };
159+
160+ const sendTransaction = async ({
161+ from,
162+ to,
163+ gas,
164+ gasPrice,
165+ value,
166+ data,
167+ nonce,
168+ }: ITxData): Promise<string> => {
169+ return new Promise((resolve, reject) => {
170+ if (!connector || !isConnected()) {
171+ reject(new Error('No connected wallet available for sending'));
172+ return;
173+ }
174+
175+ //REVIEW .then path untested
176+ return connector
177+ .sendTransaction({
178+ from,
179+ to,
180+ gas,
181+ gasPrice,
182+ value,
183+ data,
184+ nonce,
185+ })
142 .then((txHash: string) => resolve(txHash))
186 .then((txHash: string) => resolve(txHash))
143 .catch((error: Error) => reject(error));
187 .catch((error: Error) => reject(error));
144 });
188 });
@@ -234,5 +278,6 @@ export const useWalletConnect = () => {
234 peerMeta,
278 peerMeta,
235 resetConnector,
279 resetConnector,
236 signTransaction,
280 signTransaction,
281+ sendTransaction,
237 };
282 };
238};
283};
src/views/Activate/PassportTransfer.js+1
@@ -112,6 +112,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
112 networkType,
112 networkType,
113 onUpdate: handleUpdate,
113 onUpdate: handleUpdate,
114 transferEth: true,
114 transferEth: true,
115+ //NOTE not passing wc function, we always have invite ticket wallet
115 });
116 });
116
117
117 // set the global wallet
118 // set the global wallet
src/views/UrbitID/ResetKeys/ResetExecute.js+34−5
@@ -1,4 +1,4 @@
1−import React, { useState, useCallback } from 'react';
1+import React, { useState, useCallback, useEffect } from 'react';
2import { Just, Nothing } from 'folktale/maybe';
2import { Just, Nothing } from 'folktale/maybe';
3import { Grid, H4, Text, ErrorText } from 'indigo-react';
3import { Grid, H4, Text, ErrorText } from 'indigo-react';
4
4
@@ -7,7 +7,6 @@ import {
7 TRANSACTION_PROGRESS,
7 TRANSACTION_PROGRESS,
8} from 'lib/reticket';
8} from 'lib/reticket';
9import * as need from 'lib/need';
9import * as need from 'lib/need';
10−import useLifecycle from 'lib/useLifecycle';
11import { WALLET_TYPES } from 'lib/constants';
10import { WALLET_TYPES } from 'lib/constants';
12import { convertToInt } from 'lib/convertToInt';
11import { convertToInt } from 'lib/convertToInt';
13import useBlockWindowClose from 'lib/useBlockWindowClose';
12import useBlockWindowClose from 'lib/useBlockWindowClose';
@@ -18,6 +17,8 @@ import { usePointCursor } from 'store/pointCursor';
18import { usePointCache } from 'store/pointCache';
17import { usePointCache } from 'store/pointCache';
19import { useHistory } from 'store/history';
18import { useHistory } from 'store/history';
20
19
20+import { useWalletConnect } from 'lib/useWalletConnect';
21+
21import { RestartButton, ForwardButton } from 'components/Buttons';
22import { RestartButton, ForwardButton } from 'components/Buttons';
22import WarningBox from 'components/WarningBox';
23import WarningBox from 'components/WarningBox';
23import LoadingBar from 'components/LoadingBar';
24import LoadingBar from 'components/LoadingBar';
@@ -54,6 +55,11 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
54 } = useWallet();
55 } = useWallet();
55 const { pointCursor } = usePointCursor();
56 const { pointCursor } = usePointCursor();
56 const { getDetails } = usePointCache();
57 const { getDetails } = usePointCache();
58+ const {
59+ signTransaction: wcSign,
60+ sendTransaction: wcSend,
61+ connector,
62+ } = useWalletConnect();
57
63
58 const [generalError, setGeneralError] = useState();
64 const [generalError, setGeneralError] = useState();
59 const [progress, setProgress] = useState(0);
65 const [progress, setProgress] = useState(0);
@@ -63,11 +69,28 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
63 useBlockWindowClose();
69 useBlockWindowClose();
64
70
65 // start reticketing transactions on mount
71 // start reticketing transactions on mount
66− useLifecycle(() => {
72+ useEffect(() => {
67 (async () => {
73 (async () => {
74+ // due to react shenanigans we may need to wait for the connector
75+ if (
76+ walletType === WALLET_TYPES.WALLET_CONNECT &&
77+ (!connector || !connector.connected)
78+ ) {
79+ setGeneralError(new Error('Awaiting WalletConnect connection...'));
80+ return;
81+ }
82+ setGeneralError();
83+
68 const point = need.point(pointCursor);
84 const point = need.point(pointCursor);
69 const details = need.details(getDetails(point));
85 const details = need.details(getDetails(point));
70 const networkRevision = convertToInt(details.keyRevisionNumber, 10);
86 const networkRevision = convertToInt(details.keyRevisionNumber, 10);
87+
88+ // see also comment in useEthereumTransaction
89+ const txnSigner =
90+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
91+ const txnSender =
92+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
93+
71 try {
94 try {
72 await reticketPointBetweenWallets({
95 await reticketPointBetweenWallets({
73 fromWallet: need.wallet(wallet),
96 fromWallet: need.wallet(wallet),
@@ -80,13 +103,19 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
80 networkType,
103 networkType,
81 onUpdate: handleUpdate,
104 onUpdate: handleUpdate,
82 nextRevision: networkRevision + 1,
105 nextRevision: networkRevision + 1,
106+ txnSigner,
107+ txnSender,
83 });
108 });
84 } catch (err) {
109 } catch (err) {
85 console.error(err);
110 console.error(err);
86 setGeneralError(err);
111 setGeneralError(err);
87 }
112 }
88 })();
113 })();
89− });
114+ //NOTE this was using useLifecycle previously (which is useEffect without
115+ // dependencies) and worked fine, but in the wc case we might need to
116+ // wait for the connector to get set due to react state shenanigans.
117+ // eslint-disable-next-line react-hooks/exhaustive-deps
118+ }, [connector]);
90
119
91 const goToRestart = useCallback(() => {
120 const goToRestart = useCallback(() => {
92 reset();
121 reset();
@@ -99,7 +128,7 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
99 setWalletType(WALLET_TYPES.TICKET);
128 setWalletType(WALLET_TYPES.TICKET);
100 setUrbitWallet(Just(newWallet.value.wallet));
129 setUrbitWallet(Just(newWallet.value.wallet));
101 // (implicit) pointCursor stays the same
130 // (implicit) pointCursor stays the same
102− // rediect to point
131+ // redirect to point
103 popTo(names.POINT);
132 popTo(names.POINT);
104 }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
133 }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
105
134