on master help sign in
Merge pull request #615 from urbit/m/unsigned-sender

txn: generalize fake signed transactions

commit23e4f98a6bc5bdd60e984fe55b2dde4ef7e000f7 authorfang <github@fang.io> dateThu 26 Aug 2021, 22:51 (5 years ago) parentf2ef03b2228876
13 files changed+286−108
package-lock.json+40−40 package.json+2−2 src/components/InlineEthereumTransaction.js+11−1 src/lib/WalletConnect.ts+25−3 src/lib/metamask.js−28 src/lib/metamask.ts+36 src/lib/reticket.js+4 src/lib/tank.js+6−6 src/lib/txn.ts+52−13 src/lib/useEthereumTransaction.js+23−3 src/lib/useWalletConnect.ts+52−7 src/views/Activate/PassportTransfer.js+1 src/views/UrbitID/ResetKeys/ResetExecute.js+34−5
package-lock.json+40−40
@@ -8866,36 +8866,36 @@
88668866 }
88678867 },
88688868 "@walletconnect/browser-utils": {
8869− "version": "1.6.1",
8870− "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.6.1.tgz",
8871− "integrity": "sha512-73RoyCuOqqK6hrRspkLRPFAsp5/ir9omyBbQygaFmFaCyxFKDKNMmrq/9UZvicqlhNCSutuuOrZizbzZsEVhtA==",
8869+ "version": "1.6.2",
8870+ "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.6.2.tgz",
8871+ "integrity": "sha512-8+Yc9iXe2VD7C5I2iZjXKBvzTqDBse7yC9PrCWFJdUYW2rInk7BSqw/9NFhBk41sPonm2dklzwaMJApoW7V5bw==",
88728872 "requires": {
88738873 "@walletconnect/safe-json": "1.0.0",
8874− "@walletconnect/types": "^1.6.1",
8874+ "@walletconnect/types": "^1.6.2",
88758875 "@walletconnect/window-getters": "1.0.0",
88768876 "@walletconnect/window-metadata": "1.0.0",
88778877 "detect-browser": "5.2.0"
88788878 }
88798879 },
88808880 "@walletconnect/client": {
8881− "version": "1.6.1",
8882− "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.6.1.tgz",
8883− "integrity": "sha512-W80gELPzPD2x07OjBGK6LApCCi0uI3YSWAf2c/el5LA3OGLGXG29zVC5TEsK4y1uJwbwoifl1hn6rMhXocrA4Q==",
8881+ "version": "1.6.2",
8882+ "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.6.2.tgz",
8883+ "integrity": "sha512-ETeYAczgES2jlo5FuZyAfJmrRX38S7uy48zy89C7Rr4P0ZUy4fHXpnHGy0k7vI1tgikQXXQ0dv7DoLOST3V3Fg==",
88848884 "requires": {
8885− "@walletconnect/core": "^1.6.1",
8886− "@walletconnect/iso-crypto": "^1.6.1",
8887− "@walletconnect/types": "^1.6.1",
8888− "@walletconnect/utils": "^1.6.1"
8885+ "@walletconnect/core": "^1.6.2",
8886+ "@walletconnect/iso-crypto": "^1.6.2",
8887+ "@walletconnect/types": "^1.6.2",
8888+ "@walletconnect/utils": "^1.6.2"
88898889 }
88908890 },
88918891 "@walletconnect/core": {
8892− "version": "1.6.1",
8893− "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.6.1.tgz",
8894− "integrity": "sha512-rYYsBywbJsULk40szBlh2Xh9z01D/FmkJlfkUeqq6zRLr7HaUm9zSoI9+WaA2yxfVGzw+HMnuukT4UINKZGgPg==",
8892+ "version": "1.6.2",
8893+ "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.6.2.tgz",
8894+ "integrity": "sha512-XQLcHXVA2SWFnwQz6y8AAhRV2P2q7qoWfhz9vV1ZJgKJ3i8mi9Dp9zZsYRKDl+T5bRpGpdxv/PZ3JpQvr1IDgQ==",
88958895 "requires": {
8896− "@walletconnect/socket-transport": "^1.6.1",
8897− "@walletconnect/types": "^1.6.1",
8898− "@walletconnect/utils": "^1.6.1"
8896+ "@walletconnect/socket-transport": "^1.6.2",
8897+ "@walletconnect/types": "^1.6.2",
8898+ "@walletconnect/utils": "^1.6.2"
88998899 }
89008900 },
89018901 "@walletconnect/crypto": {
@@ -8932,13 +8932,13 @@
89328932 "integrity": "sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ=="
89338933 },
89348934 "@walletconnect/iso-crypto": {
8935− "version": "1.6.1",
8936− "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.6.1.tgz",
8937− "integrity": "sha512-BM+5R7HYB4r0xojw+FeSg7DPdxY3/i6Z1tLCD83wRaK+Vs2QUvI+SdmU0OLm9AJmodos/Qrhw3IgZSEtExT9yw==",
8935+ "version": "1.6.2",
8936+ "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.6.2.tgz",
8937+ "integrity": "sha512-hznvNn3FojyRNrA5QWfkK5kyzlVbeE8gFZ7hETtSPpt24xjPi0WYo9yPxMJuMBXw2hbwSRVPkxjR354D4miZmQ==",
89388938 "requires": {
89398939 "@walletconnect/crypto": "^1.0.1",
8940− "@walletconnect/types": "^1.6.1",
8941− "@walletconnect/utils": "^1.6.1"
8940+ "@walletconnect/types": "^1.6.2",
8941+ "@walletconnect/utils": "^1.6.2"
89428942 }
89438943 },
89448944 "@walletconnect/jsonrpc-types": {
@@ -8964,13 +8964,13 @@
89648964 "integrity": "sha512-ZtKRio4uCZ1JUF7LIdecmZt7FOLnX72RPSY7aUVu7mj7CSfxDwUn6gBuK6WGtH+NZCldBqDl5DenI5fFSvkKYw=="
89658965 },
89668966 "@walletconnect/qrcode-modal": {
8967− "version": "1.6.1",
8968− "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.6.1.tgz",
8969− "integrity": "sha512-CCw7fdnkqMK/NX1J2ji+PS8z0VNKC9xFCvG4rp+rZENyA+LHBgeviteD60dZW9nl/kEKvSwMQLSI82L8To8ZGg==",
8967+ "version": "1.6.2",
8968+ "resolved": "https://registry.npmjs.org/@walletconnect/qrcode-modal/-/qrcode-modal-1.6.2.tgz",
8969+ "integrity": "sha512-+d1SsUvLzFUWY60X+OKumejzQj1BYkZGPdIHNlm2sjsDhsGTMrID7fVL+FlHq/I63bT/9Bsv08dt7FeG7y+p/Q==",
89708970 "requires": {
8971− "@walletconnect/browser-utils": "^1.6.1",
8971+ "@walletconnect/browser-utils": "^1.6.2",
89728972 "@walletconnect/mobile-registry": "^1.4.0",
8973− "@walletconnect/types": "^1.6.1",
8973+ "@walletconnect/types": "^1.6.2",
89748974 "copy-to-clipboard": "^3.3.1",
89758975 "preact": "10.4.1",
89768976 "qrcode": "1.4.4"
@@ -8992,12 +8992,12 @@
89928992 "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg=="
89938993 },
89948994 "@walletconnect/socket-transport": {
8995− "version": "1.6.1",
8996− "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.6.1.tgz",
8997− "integrity": "sha512-dn1eBO3SrgIUpBo8geKCvAUe7pPg/3qO6KRibj7cF+kY+u2hNId0W79UaZmHFbX9TUVZHg+oYBKonydv8ozUAw==",
8995+ "version": "1.6.2",
8996+ "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.6.2.tgz",
8997+ "integrity": "sha512-G07Iqt2WkkqS8Q5M+ERNjeUV1A2MEBpiYOn6H9xmv7dkoXWQbTxd4qJbIgP3jAcVX0yYyLaf/gSFWFg7tpp8dw==",
89988998 "requires": {
8999− "@walletconnect/types": "^1.6.1",
9000− "@walletconnect/utils": "^1.6.1",
8999+ "@walletconnect/types": "^1.6.2",
9000+ "@walletconnect/utils": "^1.6.2",
90019001 "ws": "7.5.3"
90029002 },
90039003 "dependencies": {
@@ -9009,19 +9009,19 @@
90099009 }
90109010 },
90119011 "@walletconnect/types": {
9012− "version": "1.6.1",
9013− "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.6.1.tgz",
9014− "integrity": "sha512-seqL/ONP0iRNVEPbbl0NVCSWTPym4NiU2R/MPx9zK63dTH6lgR5Kp7QRfz+EWCoP6SCPr9vtyhQ0CMl3/qdCmw=="
9012+ "version": "1.6.2",
9013+ "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.6.2.tgz",
9014+ "integrity": "sha512-21jvvnUcbdcH6cSe/8B/KoeU+foNbvNsBOYY3AWW0LMuD/N5CnGZHGYcbEq0R0z7Gj4lp9IFvDIBLM7WaG1IiQ=="
90159015 },
90169016 "@walletconnect/utils": {
9017− "version": "1.6.1",
9018− "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.6.1.tgz",
9019− "integrity": "sha512-gi/7YKOEmA2hbirzj5MVVkgpIq2TI8iI4qqGUMtSZYHKoU170UzmML3IYPRL6mEBz6Jv77mDOp60JsRMzWdsxA==",
9017+ "version": "1.6.2",
9018+ "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.6.2.tgz",
9019+ "integrity": "sha512-/J5xO0qzGYw/gwpIYZ72ml0TASGClRJ/siFG+5QDghXvRgQKYiQKPq7nBDbtcUd3pjbuGKTOeTLm01S4urGvTQ==",
90209020 "requires": {
9021− "@walletconnect/browser-utils": "^1.6.1",
9021+ "@walletconnect/browser-utils": "^1.6.2",
90229022 "@walletconnect/encoding": "^1.0.0",
90239023 "@walletconnect/jsonrpc-utils": "^1.0.0",
9024− "@walletconnect/types": "^1.6.1",
9024+ "@walletconnect/types": "^1.6.2",
90259025 "bn.js": "4.11.8",
90269026 "js-sha3": "0.8.0",
90279027 "query-string": "6.13.5"
package.json+2−2
@@ -32,8 +32,8 @@
3232 "@types/styled-components": "^5.1.10",
3333 "@types/styled-system": "^5.1.11",
3434 "@types/styled-system__css": "^5.0.15",
35− "@walletconnect/client": "^1.6.1",
36− "@walletconnect/qrcode-modal": "^1.6.1",
35+ "@walletconnect/client": "^1.6.2",
36+ "@walletconnect/qrcode-modal": "^1.6.2",
3737 "@welldone-software/why-did-you-render": "^3.2.3",
3838 "async-retry": "^1.2.3",
3939 "azimuth-js": "^0.22.1",
src/components/InlineEthereumTransaction.js+11−1
@@ -26,6 +26,7 @@ import CopyButton from './CopyButton';
2626import ProgressButton from './ProgressButton';
2727import { convertToInt } from 'lib/convertToInt';
2828import NeedFundsNotice from './NeedFundsNotice';
29+import NoticeBox from './NoticeBox';
2930
3031export default function InlineEthereumTransaction({
3132 // from useEthereumTransaction.bind
@@ -33,6 +34,7 @@ export default function InlineEthereumTransaction({
3334 canSign,
3435 generateAndSign,
3536 signed,
37+ fakeSigned,
3638 broadcast,
3739 broadcasted,
3840 confirmed,
@@ -277,7 +279,15 @@ export default function InlineEthereumTransaction({
277279 </>
278280 )}
279281
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 && (
281291 <>
282292 <Grid.Item
283293 full
src/lib/WalletConnect.ts+25−3
@@ -1,27 +1,49 @@
11import { ITxData } from '@walletconnect/types';
22import { Transaction } from '@ethereumjs/tx';
33import { stripHexPrefix } from './utils/address';
4+import { FakeSignableTx, FakeSignResult } from './txn';
5+import Web3 from 'web3';
46
57type SignWalletConnectTransactionArgs = {
68 from: string;
79 txn: Transaction;
8− txnSigner: (args: ITxData) => Promise<string>;
10+ txnSigner: (txn: ITxData) => Promise<string>;
11+ txnSender: (txn: FakeSignableTx) => Promise<string>;
912};
1013
1114const walletConnectSignTransaction = async ({
1215 from,
1316 txn,
1417 txnSigner,
18+ txnSender,
1519}: SignWalletConnectTransactionArgs) => {
16− let wcFormattedTx = txn.toJSON();
20+ // TS compiler complains next line about missing from and gas, which we set on the following lines
21+ let wcFormattedTx: FakeSignableTx = txn.toJSON();
1722 wcFormattedTx.from = from;
1823 wcFormattedTx.gas = wcFormattedTx.gasLimit;
1924
20− const signature = await txnSigner(wcFormattedTx);
25+ let signature;
26+ try {
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+ }
2139 const serializedTx = Buffer.from(stripHexPrefix(signature), 'hex');
2240 const signedTx = Transaction.fromSerializedTx(serializedTx);
2341
2442 return signedTx;
2543};
2644
45+const walletConnectSendTransaction = (
46+ txnSender: (tx: FakeSignableTx) => Promise<string>
47+) => (txn: FakeSignableTx, _web3: Web3): Promise<string> => txnSender(txn);
48+
2749export { 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({
4242 onUpdate,
4343 transferEth = false,
4444 nextRevision = INITIAL_NETWORK_KEY_REVISION,
45+ txnSigner,
46+ txnSender,
4547}) {
4648 const askForFunding = (address, minBalance, balance) =>
4749 onUpdate({
@@ -171,6 +173,8 @@ export async function reticketPointBetweenWallets({
171173 chainId,
172174 gasPrice: gasPriceGwei,
173175 gasLimit: txs[i].gas,
176+ txnSigner,
177+ txnSender,
174178 });
175179 txPairs.push({
176180 raw: hexify(stx.serialize()),
src/lib/tank.js+6−6
@@ -3,7 +3,6 @@
33import retry from 'async-retry';
44import { toBN } from 'web3-utils';
55import { RETRY_OPTIONS, waitForTransactionConfirm } from './txn';
6−import { WALLET_TYPES } from 'lib/constants';
76import { isRopsten } from './flags';
87
98const TANK_ADDRESS = '0x40f0A6db85f8D7A54fF3eA915b040dE8Cd4A0Eb5';
@@ -64,6 +63,12 @@ const ensureFundsFor = async (
6463 throw new Error('tank: no transactions provided!');
6564 }
6665
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+
6772 const balance = toBN(await web3.eth.getBalance(address));
6873 cost = toBN(cost);
6974
@@ -76,11 +81,6 @@ const ensureFundsFor = async (
7681 return false;
7782 }
7883
79− if (walletType === WALLET_TYPES.METAMASK) {
80− console.log('tank: disabling for metamask login');
81−
82− return false;
83− }
8484 try {
8585 // TODO: if we can't always (easily) provide a point, and the
8686 // fundTransactions call is gonna fail anyway, should we maybe
src/lib/txn.ts+52−13
@@ -1,5 +1,6 @@
11import 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';
34import { toHex } from 'web3-utils';
45import { safeFromWei, safeToWei } from './lib';
56import retry from 'async-retry';
@@ -8,10 +9,11 @@ import { NETWORK_TYPES } from './network';
89import { ledgerSignTransaction } from './ledger';
910import { trezorSignTransaction } from './trezor';
1011import { walletConnectSignTransaction } from './WalletConnect';
11−import { metamaskSignTransaction, FakeMetamaskTransaction } from './metamask';
12+import { metamaskSignTransaction } from './metamask';
1213import { addHexPrefix } from './utils/address';
1314import { CHECK_BLOCK_EVERY_MS, WALLET_TYPES } from './constants';
1415import { patp2dec } from './patp2dec';
16+import Web3 from 'web3';
1517
1618const RETRY_OPTIONS = {
1719 retries: 99999,
@@ -20,6 +22,32 @@ const RETRY_OPTIONS = {
2022 randomize: false,
2123};
2224
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+
2351const signTransaction = async ({
2452 wallet,
2553 walletType,
@@ -30,7 +58,8 @@ const signTransaction = async ({
3058 chainId, // number
3159 gasPrice, // string, in gwei
3260 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.
3463}) => {
3564 // TODO: require these in txn object
3665 nonce = toHex(nonce);
@@ -115,6 +144,7 @@ const signTransaction = async ({
115144 from,
116145 txn: stx,
117146 txnSigner,
147+ txnSender,
118148 });
119149 } else {
120150 stx = stx.sign(wallet.privateKey);
@@ -123,19 +153,27 @@ const signTransaction = async ({
123153 return stx;
124154};
125155
126−const sendSignedTransaction = (web3, stx, doubtNonceError) => {
127− // Handle fake metamask transaction
128− let eventEmitter;
129− let rawTx;
130− if (stx instanceof FakeMetamaskTransaction) {
131− eventEmitter = web3.eth.sendTransaction(stx.txnData);
132− } else {
133− rawTx = hexify(stx.serialize());
134− eventEmitter = web3.eth.sendSignedTransaction(rawTx);
156+const sendSignedTransaction = (
157+ web3: Web3,
158+ stx: Transaction | FakeSignedTx,
159+ doubtNonceError: boolean
160+): Promise<string> => {
161+ // if we couldn't sign it, we depend on the given sender function
162+ if (!(stx instanceof Transaction)) {
163+ if (doubtNonceError) {
164+ console.log('why doubting nonce error? tank unavailable without rawtx.');
165+ }
166+ if (!stx.send) {
167+ throw new Error('no sign+sending function available');
168+ }
169+ return stx.send(stx.txn, web3);
135170 }
171+
172+ let rawTx: string = hexify(stx.serialize());
173+ let eventEmitter = web3.eth.sendSignedTransaction(rawTx);
136174 return new Promise(async (resolve, reject) => {
137175 eventEmitter
138− .on('transactionHash', hash => {
176+ .on('transactionHash', (hash: string) => {
139177 resolve(hash);
140178 })
141179 .on('error', err => {
@@ -240,6 +278,7 @@ const canDecodePatp = p => {
240278
241279export {
242280 RETRY_OPTIONS,
281+ FakeSignResult,
243282 signTransaction,
244283 sendSignedTransaction,
245284 waitForTransactionConfirm,
src/lib/useEthereumTransaction.js+23−3
@@ -25,10 +25,12 @@ import useGasPrice from 'lib/useGasPrice';
2525import { timeout } from 'lib/timeout';
2626import { safeToWei, safeFromWei } from 'lib/lib';
2727import { useWalletConnect } from './useWalletConnect';
28+import { Transaction } from '@ethereumjs/tx';
2829
2930const STATE = {
3031 NONE: 'NONE',
3132 SIGNED: 'SIGNED',
33+ FAKE_SIGNED: 'FAKE_SIGNED',
3234 BROADCASTED: 'BROADCASTED',
3335 CONFIRMED: 'CONFIRMED',
3436 COMPLETED: 'COMPLETED',
@@ -50,7 +52,10 @@ export default function useEthereumTransaction(
5052 const { wallet, walletType, walletHdPath } = useWallet();
5153 const { web3, networkType } = useNetwork();
5254 const { pointCursor } = usePointCursor();
53− const { signTransaction: wcSign } = useWalletConnect();
55+ const {
56+ signTransaction: wcSign,
57+ sendTransaction: wcSend,
58+ } = useWalletConnect();
5459
5560 const _web3 = need.web3(web3);
5661 const _wallet = need.wallet(wallet);
@@ -81,7 +86,8 @@ export default function useEthereumTransaction(
8186 const initializing = nonce === undefined || chainId === undefined;
8287 const constructed = !!unsignedTransactions;
8388 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;
8591 const broadcasted = state === STATE.BROADCASTED;
8692 const confirmed = state === STATE.CONFIRMED;
8793 const completed = state === STATE.COMPLETED;
@@ -113,8 +119,11 @@ export default function useEthereumTransaction(
113119 // Due to React's Rules of Hooks, optionally inject the
114120 // WC transaction signing function
115121 // https://reactjs.org/docs/hooks-rules.html
122+ //TODO this is duplicated across relevant callsites, should refactor
116123 const txnSigner =
117124 walletType === WALLET_TYPES.WALLET_CONNECT ? wcSign : undefined;
125+ const txnSender =
126+ walletType === WALLET_TYPES.WALLET_CONNECT ? wcSend : undefined;
118127
119128 const txns = await Promise.all(
120129 utxs.map((utx, i) =>
@@ -129,12 +138,21 @@ export default function useEthereumTransaction(
129138 gasPrice: gasPrice.toFixed(0),
130139 gasLimit,
131140 txnSigner,
141+ txnSender,
132142 })
133143 )
134144 );
135145
136146 setSignedTransactions(txns);
137− setState(STATE.SIGNED);
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 {
154+ setState(STATE.SIGNED);
155+ }
138156 } catch (error) {
139157 console.error('error signing txs', error);
140158 setError(error);
@@ -151,6 +169,7 @@ export default function useEthereumTransaction(
151169 walletHdPath,
152170 walletType,
153171 wcSign,
172+ wcSend,
154173 ]);
155174
156175 const broadcast = useCallback(async () => {
@@ -327,6 +346,7 @@ export default function useEthereumTransaction(
327346 canSign,
328347 generateAndSign,
329348 signed,
349+ fakeSigned,
330350 broadcast,
331351 broadcasted,
332352 confirmed,
src/lib/useWalletConnect.ts+52−7
@@ -3,7 +3,7 @@ import QRCodeModal from '@walletconnect/qrcode-modal';
33import { ITxData } from '@walletconnect/types';
44
55import { useEffect, useMemo, useState } from 'react';
6−import { Just } from 'folktale/maybe';
6+import { Just, Nothing } from 'folktale/maybe';
77
88import { useWallet } from 'store/wallet';
99import { getAuthToken } from './authToken';
@@ -81,17 +81,30 @@ export const useWalletConnect = () => {
8181 return;
8282 }
8383
84− const authToken = await getAuthToken({
85− address,
86− connector,
87− walletType: WALLET_TYPES.WALLET_CONNECT,
88− });
84+ let authToken = Nothing();
85+ try {
86+ const token = await getAuthToken({
87+ address,
88+ connector,
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.'
96+ );
97+ } else {
98+ throw e;
99+ //TODO should errors with this *really* prevent login?
100+ }
101+ }
89102
90103 const wallet: WalletConnectWallet = {
91104 address,
92105 };
93106
94− setAuthToken(Just(authToken));
107+ setAuthToken(authToken);
95108 setWallet(Just(wallet));
96109 };
97110
@@ -139,6 +152,37 @@ export const useWalletConnect = () => {
139152 data,
140153 nonce,
141154 })
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+ })
142186 .then((txHash: string) => resolve(txHash))
143187 .catch((error: Error) => reject(error));
144188 });
@@ -234,5 +278,6 @@ export const useWalletConnect = () => {
234278 peerMeta,
235279 resetConnector,
236280 signTransaction,
281+ sendTransaction,
237282 };
238283};
src/views/Activate/PassportTransfer.js+1
@@ -112,6 +112,7 @@ export default function PassportTransfer({ className, resetActivateRouter }) {
112112 networkType,
113113 onUpdate: handleUpdate,
114114 transferEth: true,
115+ //NOTE not passing wc function, we always have invite ticket wallet
115116 });
116117
117118 // 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';
22import { Just, Nothing } from 'folktale/maybe';
33import { Grid, H4, Text, ErrorText } from 'indigo-react';
44
@@ -7,7 +7,6 @@ import {
77 TRANSACTION_PROGRESS,
88} from 'lib/reticket';
99import * as need from 'lib/need';
10−import useLifecycle from 'lib/useLifecycle';
1110import { WALLET_TYPES } from 'lib/constants';
1211import { convertToInt } from 'lib/convertToInt';
1312import useBlockWindowClose from 'lib/useBlockWindowClose';
@@ -18,6 +17,8 @@ import { usePointCursor } from 'store/pointCursor';
1817import { usePointCache } from 'store/pointCache';
1918import { useHistory } from 'store/history';
2019
20+import { useWalletConnect } from 'lib/useWalletConnect';
21+
2122import { RestartButton, ForwardButton } from 'components/Buttons';
2223import WarningBox from 'components/WarningBox';
2324import LoadingBar from 'components/LoadingBar';
@@ -54,6 +55,11 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
5455 } = useWallet();
5556 const { pointCursor } = usePointCursor();
5657 const { getDetails } = usePointCache();
58+ const {
59+ signTransaction: wcSign,
60+ sendTransaction: wcSend,
61+ connector,
62+ } = useWalletConnect();
5763
5864 const [generalError, setGeneralError] = useState();
5965 const [progress, setProgress] = useState(0);
@@ -63,11 +69,28 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
6369 useBlockWindowClose();
6470
6571 // start reticketing transactions on mount
66− useLifecycle(() => {
72+ useEffect(() => {
6773 (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+
6884 const point = need.point(pointCursor);
6985 const details = need.details(getDetails(point));
7086 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+
7194 try {
7295 await reticketPointBetweenWallets({
7396 fromWallet: need.wallet(wallet),
@@ -80,13 +103,19 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
80103 networkType,
81104 onUpdate: handleUpdate,
82105 nextRevision: networkRevision + 1,
106+ txnSigner,
107+ txnSender,
83108 });
84109 } catch (err) {
85110 console.error(err);
86111 setGeneralError(err);
87112 }
88113 })();
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]);
90119
91120 const goToRestart = useCallback(() => {
92121 reset();
@@ -99,7 +128,7 @@ export default function ResetExecute({ newWallet, setNewWallet }) {
99128 setWalletType(WALLET_TYPES.TICKET);
100129 setUrbitWallet(Just(newWallet.value.wallet));
101130 // (implicit) pointCursor stays the same
102− // rediect to point
131+ // redirect to point
103132 popTo(names.POINT);
104133 }, [resetWallet, setWalletType, setUrbitWallet, newWallet, popTo, names]);
105134