on cf73cee help sign in
bridge/src/lib/txn.js
211 lines 5.3 kB history raw
1import * as ob from 'urbit-ob'
2import { Just } from 'folktale/maybe'
3import { Ok, Error } from 'folktale/result'
4import Tx from 'ethereumjs-tx'
5import Web3 from 'web3'
7import { BRIDGE_ERROR } from '../lib/error'
8import { NETWORK_NAMES } from '../lib/network'
9import { ledgerSignTransaction } from '../lib/ledger'
10import { trezorSignTransaction } from '../lib/trezor'
11import {
12 WALLET_NAMES,
13 addHexPrefix
14 } from '../lib/wallet'
16const TXN_PURPOSE = {
17 SET_MANAGEMENT_PROXY: Symbol('SET_MANAGEMENT_PROXY'),
18 SET_TRANSFER_PROXY: Symbol('SET_TRANSFER_PROXY'),
19 SET_SPAWN_PROXY: Symbol('SET_SPAWN_PROXY'),
20 CREATE_GALAXY: Symbol('CREATE_GALAXY'),
21 ISSUE_CHILD: Symbol('ISSUE_CHILD'),
22 SET_KEYS: Symbol('SET_KEYS'),
23 TRANSFER: Symbol('TRANSFER'),
24 CANCEL_TRANSFER: Symbol('CANCEL_TRANSFER')
27const renderTxnPurpose = (purpose) =>
28 purpose === TXN_PURPOSE.SET_MANAGEMENT_PROXY
29 ? 'set this management proxy'
30 : purpose === TXN_PURPOSE.SET_SPAWN_PROXY
31 ? 'set this spawn proxy'
32 : purpose === TXN_PURPOSE.SET_TRANSFER_PROXY
33 ? 'set this transfer proxy'
34 : purpose === TXN_PURPOSE.CREATE_GALAXY
35 ? 'create this galaxy'
36 : purpose === TXN_PURPOSE.ISSUE_CHILD
37 ? 'issue this point'
38 : purpose === TXN_PURPOSE.SET_KEYS
39 ? 'set these network keys'
40 : purpose === TXN_PURPOSE.TRANSFER
41 ? 'transfer this point'
42 : purpose === TXN_PURPOSE.CANCEL_TRANSFER
43 ? 'cancel this transfer'
44 : 'perform this transaction'
46const signTransaction = async config => {
48 let {
49 wallet,
50 walletType,
51 walletHdPath,
52 networkType,
53 txn,
54 setStx,
55 nonce,
56 chainId,
57 gasPrice,
58 gasLimit
59 } = config
61 nonce = toHex(nonce)
62 chainId = toHex(chainId)
63 gasPrice = toHex(toWei(gasPrice, 'gwei'))
64 gasLimit = toHex(gasLimit)
66 const txParams = { nonce, chainId, gasPrice, gasLimit }
68 // NB (jtobin)
69 //
70 // Ledger does not seem to handle EIP-155 automatically. When using a Ledger,
71 // if the block number is at least FORK_BLKNUM = 2675000, one needs to
72 // pre-set the ECDSA signature parameters with r = 0, s = 0, and v = chainId
73 // prior to signing.
74 //
75 // The easiest way to handle this is to just branch on the network, since
76 // mainnet and Ropsten have obviously passed FORK_BLKNUM. This is somewhat
77 // awkward when dealing with offline transactions, since we might want to
78 // test them on a local network as well.
79 //
80 // The best thing to do is probably to add an 'advanced' tab to offline
81 // transaction generation where one can disable the defaulted-on EIP-155
82 // settings in this case. This is pretty low-priority, but is a
83 // comprehensive solution.
84 //
85 // See:
86 //
87 // See https://github.com/LedgerHQ/ledgerjs/issues/43#issuecomment-366984725
88 //
89 // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
91 const eip155Params = {
92 r: '0x00',
93 s: '0x00',
94 v: chainId
95 }
97 const defaultEip155Networks = [
98 NETWORK_NAMES.MAINNET,
99 NETWORK_NAMES.ROPSTEN,
100 NETWORK_NAMES.OFFLINE
101 ]
103 const needEip155Params =
104 walletType === WALLET_NAMES.LEDGER &&
105 defaultEip155Networks.includes(networkType)
107 const signingParams =
108 needEip155Params
109 ? Object.assign(txParams, eip155Params)
110 : txParams
112 const wal = wallet.matchWith({
113 Just: (w) => w.value,
114 Nothing: () => { throw BRIDGE_ERROR.MISSING_WALLET }
115 })
117 const sec = wal.privateKey
119 const utx = txn.matchWith({
120 Just: (tx) =>
121 Object.assign(tx.value, signingParams),
122 Nothing: () => {
123 throw BRIDGE_ERROR.MISSING_TXN
124 }
125 })
127 const stx = new Tx(utx)
129 if (walletType === WALLET_NAMES.LEDGER) {
130 await ledgerSignTransaction(stx, walletHdPath)
131 } else if (walletType === WALLET_NAMES.TREZOR) {
132 await trezorSignTransaction(stx, walletHdPath)
133 } else {
134 stx.sign(sec)
135 }
137 setStx(Just(stx))
140const sendSignedTransaction = (web3, stx) => {
141 const txn = stx.matchWith({
142 Just: (tx) => tx.value,
143 Nothing: () => {
144 throw BRIDGE_ERROR.MISSING_TXN
145 }
146 })
148 const serializedTx = addHexPrefix(txn.serialize().toString('hex'))
150 return new Promise((resolve, reject) => {
151 web3.eth.sendSignedTransaction(serializedTx)
152 .on('transactionHash', hash =>
153 resolve(Just(Ok(hash)))
154 )
155 .on('receipt', txn => {
156 resolve(Just(Ok(txn.transactionHash)))
157 })
158 .on('error', err => {
159 reject(Just(Error(err.message)))
160 })
161 })
164const hexify = val => addHexPrefix(val.toString('hex'))
166const renderSignedTx = stx => ({
167 messageHash: hexify(stx.hash()),
168 v: hexify(stx.v),
169 s: hexify(stx.s),
170 r: hexify(stx.r),
171 rawTransaction: hexify(stx.serialize())
172})
174const getTxnInfo = async (web3, addr) => {
175 let nonce = await web3.eth.getTransactionCount(addr)
176 let chainId = await web3.eth.net.getId()
177 let gasPrice = await web3.eth.getGasPrice()
178 return {
179 nonce: nonce,
180 chainId: chainId,
181 gasPrice: fromWei(gasPrice, 'gwei')
182 }
185const dummy = new Web3()
186const toHex = dummy.utils.toHex
187const toWei = dummy.utils.toWei
188const fromWei = dummy.utils.fromWei
190const canDecodePatp = p => {
191 try {
192 ob.patp2dec(p)
193 return true
194 } catch (_) {
195 return false
196 }
199export {
200 signTransaction,
201 sendSignedTransaction,
202 TXN_PURPOSE,
203 getTxnInfo,
204 renderTxnPurpose,
205 hexify,
206 renderSignedTx,
207 toHex,
208 toWei,
209 fromWei,
210 canDecodePatp,