on master help sign in
txn: Typescriptify signing and sending of Fake txns
01a1446tomholford
src/lib/WalletConnect.ts+9−7
@@ -1,13 +1,14 @@
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 { FakeSignResult } from './txn';
4+import { FakeSignableTx, FakeSignResult } from './txn';
5+import Web3 from 'web3';
5
6
6type SignWalletConnectTransactionArgs = {
7type SignWalletConnectTransactionArgs = {
7 from: string;
8 from: string;
8 txn: Transaction;
9 txn: Transaction;
9− txnSigner: (args: ITxData) => Promise<string>;
10+ txnSigner: (txn: ITxData) => Promise<string>;
10− txnSender: (args: ITxData) => Promise<string>;
11+ txnSender: (txn: FakeSignableTx) => Promise<string>;
11};
12};
12
13
13const walletConnectSignTransaction = async ({
14const walletConnectSignTransaction = async ({
@@ -16,7 +17,8 @@ const walletConnectSignTransaction = async ({
16 txnSigner,
17 txnSigner,
17 txnSender,
18 txnSender,
18}: SignWalletConnectTransactionArgs) => {
19}: SignWalletConnectTransactionArgs) => {
20+ // TS compiler complains next line about missing from and gas, which we set on the following lines
19− let wcFormattedTx = txn.toJSON();
21+ let wcFormattedTx: FakeSignableTx = txn.toJSON();
20 wcFormattedTx.from = from;
22 wcFormattedTx.from = from;
21 wcFormattedTx.gas = wcFormattedTx.gasLimit;
23 wcFormattedTx.gas = wcFormattedTx.gasLimit;
22
24
@@ -26,7 +28,7 @@ const walletConnectSignTransaction = async ({
26 } catch (e) {
28 } catch (e) {
27 if (e.message === 'METHOD_NOT_SUPPORTED') {
29 if (e.message === 'METHOD_NOT_SUPPORTED') {
28 console.log('connected wc wallet does not support tx signing.');
30 console.log('connected wc wallet does not support tx signing.');
29− return new FakeSignResult(
31+ return FakeSignResult(
30 wcFormattedTx,
32 wcFormattedTx,
31 walletConnectSendTransaction(txnSender)
33 walletConnectSendTransaction(txnSender)
32 );
34 );
@@ -41,7 +43,7 @@ const walletConnectSignTransaction = async ({
41};
43};
42
44
43const walletConnectSendTransaction = (
45const walletConnectSendTransaction = (
44− txnSender: (tx: any) => Promise<string>
46+ txnSender: (tx: FakeSignableTx) => Promise<string>
45−) => (_web3, txn): Promise<string> => txnSender(txn);
47+) => (txn: FakeSignableTx, _web3: Web3): Promise<string> => txnSender(txn);
46
48
47export { walletConnectSignTransaction };
49export { walletConnectSignTransaction };
src/lib/metamask.js → src/lib/metamask.tsrenamed+9−5
@@ -1,5 +1,6 @@
1+import Web3 from 'web3';
1import { toHex } from 'web3-utils';
2import { toHex } from 'web3-utils';
2−import { FakeSignResult } from './txn';
3+import { FakeSignableTx, FakeSignResult } from './txn';
3
4
4export function MetamaskWallet(address) {
5export function MetamaskWallet(address) {
5 this.address = address;
6 this.address = address;
@@ -7,12 +8,15 @@ export function MetamaskWallet(address) {
7
8
8// Cheat a bit and pretend to sign transaction
9// Cheat a bit and pretend to sign transaction
9// Sign it actually when sending
10// Sign it actually when sending
10−export const metamaskSignTransaction = async (txn, from) => {
11+export const metamaskSignTransaction = async (
12+ txn: FakeSignableTx,
13+ from: string
14+) => {
11 txn.from = from;
15 txn.from = from;
12− return new FakeSignResult(txn, metamaskSendTransaction);
16+ return FakeSignResult(txn, metamaskSendTransaction);
13};
17};
14
18
15−const metamaskSendTransaction = async (web3, txn) => {
19+const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
16 const metamaskFormattedTxn = {
20 const metamaskFormattedTxn = {
17 data: txn.data,
21 data: txn.data,
18 gasLimit: txn.gasLimit,
22 gasLimit: txn.gasLimit,
@@ -29,4 +33,4 @@ const metamaskSendTransaction = async (web3, txn) => {
29 //TODO can we do better?
33 //TODO can we do better?
30 const receipt = await web3.eth.sendTransaction(metamaskFormattedTxn);
34 const receipt = await web3.eth.sendTransaction(metamaskFormattedTxn);
31 return receipt.transactionHash;
35 return receipt.transactionHash;
32−}
36+};
src/lib/tank.js−1
@@ -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';
src/lib/txn.ts+25−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';
@@ -12,6 +13,7 @@ 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,21 +22,31 @@ 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+
23type SignedTx = {
32type SignedTx = {
24 serialize: () => string;
33 serialize: () => string;
25};
34};
26
35
27−//TODO merge with FakeSignResult, get it type-checking properly
28type FakeSignedTx = SignedTx & {
36type FakeSignedTx = SignedTx & {
29− txn: any;
37+ txn: FakeSignableTx;
30− send: (web3: any, txn: any) => Promise<string>; // txhash
38+ send: TxSender;
31};
39};
32
40
33−function FakeSignResult(txn, send) {
41+const FakeSignResult = (txn: FakeSignableTx, send: TxSender): FakeSignedTx => {
42+ return {
34− this.txn = txn;
43+ txn,
44+ serialize: () => {
35− this.serialize = () => '???'; //NOTE update tank.js when changing this!
45+ return '???'; //NOTE update tank.js when changing this!
46+ },
36− this.send = send;
47+ send,
48+ };
37−}
49+};
38
50
39const signTransaction = async ({
51const signTransaction = async ({
40 wallet,
52 wallet,
@@ -142,19 +154,19 @@ const signTransaction = async ({
142};
154};
143
155
144const sendSignedTransaction = (
156const sendSignedTransaction = (
145− web3,
157+ web3: Web3,
146− stx: SignedTx,
158+ stx: Transaction | FakeSignedTx,
147 doubtNonceError: boolean
159 doubtNonceError: boolean
148): Promise<string> => {
160): Promise<string> => {
149 // if we couldn't sign it, we depend on the given sender function
161 // if we couldn't sign it, we depend on the given sender function
150− if (stx instanceof FakeSignResult) { //TODO set up for proper typecheck
162+ if (!(stx instanceof Transaction)) {
151 if (doubtNonceError) {
163 if (doubtNonceError) {
152 console.log('why doubting nonce error? tank unavailable without rawtx.');
164 console.log('why doubting nonce error? tank unavailable without rawtx.');
153 }
165 }
154 if (!stx.send) {
166 if (!stx.send) {
155 throw new Error('no sign+sending function available');
167 throw new Error('no sign+sending function available');
156 }
168 }
157− return stx.send(web3, stx.txn);
169+ return stx.send(stx.txn, web3);
158 }
170 }
159
171
160 let rawTx: string = hexify(stx.serialize());
172 let rawTx: string = hexify(stx.serialize());
src/lib/useEthereumTransaction.js+5−2
@@ -17,7 +17,6 @@ import {
17 sendSignedTransaction,
17 sendSignedTransaction,
18 waitForTransactionConfirm,
18 waitForTransactionConfirm,
19 hexify,
19 hexify,
20− FakeSignResult,
21} from './txn';
20} from './txn';
22import * as need from 'lib/need';
21import * as need from 'lib/need';
23import { ensureFundsFor } from 'lib/tank';
22import { ensureFundsFor } from 'lib/tank';
@@ -26,6 +25,7 @@ import useGasPrice from 'lib/useGasPrice';
26import { timeout } from 'lib/timeout';
25import { timeout } from 'lib/timeout';
27import { safeToWei, safeFromWei } from 'lib/lib';
26import { safeToWei, safeFromWei } from 'lib/lib';
28import { useWalletConnect } from './useWalletConnect';
27import { useWalletConnect } from './useWalletConnect';
28+import { Transaction } from '@ethereumjs/tx';
29
29
30const STATE = {
30const STATE = {
31 NONE: 'NONE',
31 NONE: 'NONE',
@@ -144,7 +144,10 @@ export default function useEthereumTransaction(
144
144
145 setSignedTransactions(txns);
145 setSignedTransactions(txns);
146
146
147+ // Per return type of `signTransaction`, tx is either a Transaction or FakeSignedTx.
148+ // We check the negation in this case, since TS cannot perform run-time check of type
149+ // FakeSignedTx, but it can check for an instance of the ethereumjs/tx class.
147− if (txns.some(tx => tx instanceof FakeSignResult)) {
150+ if (txns.some(tx => !(tx instanceof Transaction))) {
148 setState(STATE.FAKE_SIGNED);
151 setState(STATE.FAKE_SIGNED);
149 } else {
152 } else {
150 setState(STATE.SIGNED);
153 setState(STATE.SIGNED);