1import { Nothing, Just } from 'folktale/maybe';
2import { Ok } from 'folktale/result';
3import React from 'react';
4import * as need from '../../lib/need';
6import { Code, H3 } from './Base';
7import { Button } from './Base';
8import { CheckboxButton, Input, InnerLabel, InnerLabelDropdown } from './Base';
9import { Warning } from './Base';
11import { BRIDGE_ERROR } from '../../lib/error';
12import { ROUTE_NAMES } from '../../lib/routeNames';
13import { compose } from '../../lib/lib';
14import * as tank from '../../lib/tank';
16 sendSignedTransaction,
22} from '../../lib/txn';
24import { withTxnConfirmations } from '../../store/txnConfirmations';
25import { withNetwork } from '../../store/network';
26import { withHistory } from '../../store/history';
27import { withWallet } from '../../store/wallet';
28import { withTxnCursor } from '../../store/txnCursor';
30const SUBMISSION_STATES = {
31 PROMPT: 'Send transaction',
32 PREPARING: 'Preparing...',
33 FUNDING: 'Finding transaction funding...',
34 SENDING: 'Sending transaction...',
37class StatelessTransaction extends React.Component {
44 showGasDetails: false,
51 txStatus: SUBMISSION_STATES.PROMPT,
55 this.createUnsignedTxn = this.createUnsignedTxn.bind(this);
56 this.setUserApproval = this.setUserApproval.bind(this);
57 this.setTxn = this.setTxn.bind(this);
58 this.setStx = this.setStx.bind(this);
59 this.sendTxn = this.sendTxn.bind(this);
60 this.setNonce = this.setNonce.bind(this);
61 this.setChainId = this.setChainId.bind(this);
62 this.setGasPrice = this.setGasPrice.bind(this);
63 this.setGasLimit = this.setGasLimit.bind(this);
64 this.rangeChange = this.rangeChange.bind(this);
65 this.toggleGasDetails = this.toggleGasDetails.bind(this);
66 this.handleChainUpdate = this.handleChainUpdate.bind(this);
70 const { props } = this;
72 const addr = need.address(props);
74 props.web3.matchWith({
77 const validWeb3 = w3.value;
79 const getTxMetadata = [
80 validWeb3.eth.getTransactionCount(addr),
81 validWeb3.eth.net.getId(),
82 validWeb3.eth.getGasPrice(),
85 Promise.all(getTxMetadata).then(r => {
89 gasPrice: fromWei(r[2], 'gwei'),
92 this.setState({ ...txMetadata });
98 handleChainUpdate(chainId) {
99 if (chainId === 'custom') {
114 const { state } = this;
115 this.setState({ userApproval: !state.userApproval });
120 showGasDetails: !this.state.showGasDetails,
132 this.setState({ txn });
135 createUnsignedTxn() {
136 const txn = this.props.createUnsignedTxn();
138 this.setState({ txn });
150 this.setState({ nonce });
154 setChainId(chainId) {
155 this.setState({ chainId });
159 setGasPrice(gasPrice) {
160 this.setState({ gasPrice });
165 this.setGasPrice(e.target.value);
168 setGasLimit(gasLimit) {
169 this.setState({ gasLimit });
181 const { props, state } = this;
182 const web3 = props.web3.value;
184 const stx = state.stx.matchWith({
185 Just: tx => tx.value,
187 throw BRIDGE_ERROR.MISSING_TXN;
191 if (props.beforeSend) {
193 this.setState({ txStatus: SUBMISSION_STATES.PREPARING });
194 const res = await props.beforeSend(stx);
195 if (res === false) throw new Error('beforeSend disallowed sending');
197 console.log('beforeSend error', e);
199 txStatus: SUBMISSION_STATES.PROMPT,
200 txError: Just('Something went wrong!'),
206 this.setState({ txStatus: SUBMISSION_STATES.FUNDING });
208 const rawTx = hexify(stx.serialize());
209 const cost = state.gasLimit * toWei(state.gasPrice, 'gwei');
211 const address = need.address(props);
212 let balance = await web3.eth.getBalance(address);
213 let hasBalance = balance >= cost;
215 let usedTank = false;
216 // if we need to, try and fund the transaction
218 usedTank = await this.ensureFundsFor(web3, address, cost, [rawTx]);
219 hasBalance = usedTank;
222 // if we still don't have sufficient balance, fail and tell the user
225 txStatus: SUBMISSION_STATES.PROMPT,
226 txError: Just(`Insufficient funds.
227 Address ${address} needs at least ${fromWei(cost.toString())} ETH,
228 currently has ${fromWei(balance.toString())} ETH.`),
231 // if we have the balance, proceed with submission
233 this.setState({ txStatus: SUBMISSION_STATES.SENDING });
235 sendSignedTransaction(
239 props.setTxnConfirmations
242 props.setTxnCursor(Just(Ok(txHash)));
245 if (props.newNetworkSeed) {
246 props.setNetworkSeed(Just(props.newNetworkSeed));
247 routeData.promptKeyfile = true;
250 if (props.newRevision) {
251 props.setNetworkRevision(Just(props.newRevision));
254 props.history.popAndPush(ROUTE_NAMES.SENT_TRANSACTION, routeData);
258 txStatus: SUBMISSION_STATES.PROMPT,
265 // uses the gas tank to ensure funds, but if we have to ask the user
266 // for funding, just resolve instead of waiting
267 // returns true if the gas tank was used, false otherwise
268 ensureFundsFor(web3, address, cost, signedTxs) {
269 return new Promise((resolve, reject) => {
271 .ensureFundsFor(web3, null, address, cost, signedTxs, () => {
280 getChainTitle(chainId) {
287 '1337': 'Geth private chains - 1337',
297 title: this.getChainTitle('1'),
301 title: this.getChainTitle('2'),
305 title: this.getChainTitle('3'),
309 title: this.getChainTitle('4'),
313 title: this.getChainTitle('42'),
317 title: this.getChainTitle('1337'),
321 title: this.getChainTitle('custom'),
328 const { web3, canGenerate } = this.props;
354 const { state } = this;
356 const canSign = Just.hasInstance(txn);
357 const canApprove = Just.hasInstance(stx);
358 const canSend = Just.hasInstance(stx) && userApproval === true;
360 const chainOptions = this.getChainOptions();
362 const generateButtonColor = Nothing.hasInstance(txn) ? 'blue' : 'green';
364 const signerButtonColor = Nothing.hasInstance(stx) ? 'blue' : 'green';
366 const generateTxnButton = (
369 disabled={!canGenerate}
370 prop-color={generateButtonColor}
371 prop-size={'lg wide'}
372 onClick={createUnsignedTxn}>
373 {'Generate Transaction'}
377 const unsignedTxnDisplay = txn.matchWith({
381 <H3 className={'mt-8'}>{'Unsigned Transaction'}</H3>
382 <Code>{JSON.stringify(tx.value, null, 2)}</Code>
387 const gasPriceRangeDialogue = (
389 <div className="mt-12 flex space-between align-baseline">
391 <span>Gas Price:</span>
392 <span className="ml-4 text-700 text-sm">{gasPrice} gwei</span>
394 <div className="text-sm">
395 <span>Max transaction fee: </span>
396 <span className="text-700">
397 {(gasPrice * gasLimit) / 1000000000} eth
409 onChange={this.rangeChange}
412 <div className="flex space-between text-sm mb-8">
419 const toggleGasDetailsDialogue = (
420 <span className="text-link" onClick={toggleGasDetails}>
425 const gasPriceDialogue = (
427 className={'mono mt-4'}
429 prop-format={'innerLabel'}
431 onChange={setGasPrice}>
432 <InnerLabel>{'Gas Price (gwei)'}</InnerLabel>
436 const gasLimitDialogue = (
438 className={'mono mt-4'}
440 prop-format={'innerLabel'}
442 onChange={setGasLimit}>
443 <InnerLabel>{'Gas Limit'}</InnerLabel>
447 const nonceDialogue = (
449 className={'mono mt-4 mb-4'}
451 prop-format={'innerLabel'}
454 <InnerLabel>{'Nonce'}</InnerLabel>
458 const chainDialogueTitle = customChain
460 : this.getChainTitle(chainId);
461 const chainDialogue = (
466 options={chainOptions}
467 handleUpdate={handleChainUpdate}
468 currentSelectionTitle={chainDialogueTitle}
472 const customChainDialogue = !customChain ? null : (
474 className={'mono mt-4 mb-8'}
476 prop-format={'innerLabel'}
478 onChange={setChainId}>
479 <InnerLabel>{'Chain ID'}</InnerLabel>
483 const onlineParamsDialogue = web3.matchWith({
489 {customChainDialogue}
494 const signTxnButton = (
498 prop-size={'lg wide'}
499 prop-color={signerButtonColor}
501 signTransaction({ ...this.props, ...this.state, setStx: this.setStx })
507 const signedTxnDisplay = stx.matchWith({
511 <H3 className={'mt-8'}>{'Signed Transaction'}</H3>
512 <Code>{JSON.stringify(renderSignedTx(tx.value), null, 2)}</Code>
517 const confirmButton = (
520 disabled={!canApprove}
521 onToggle={setUserApproval}
523 label="I approve this transaction and wish to send."
527 const sending = txStatus !== SUBMISSION_STATES.PROMPT;
528 const sendTxnSpinner = sending ? '' : 'hide';
529 const sendTxnButton = (
531 prop-size={'xl wide'}
533 disabled={!canSend || sending}
535 <span className="relative">
536 <span className={`btn-spinner ${sendTxnSpinner}`} />
542 const sendDialogue = web3.matchWith({
552 const txnErrorDialogue = Nothing.hasInstance(state.txError) ? (
555 <Warning className={'mt-8'}>
556 <H3 style={{ marginTop: 0, paddingTop: 0 }}>
557 {'There was an error sending your transaction.'}
559 {state.txError.value}
568 {gasPriceRangeDialogue}
569 {toggleGasDetailsDialogue}
577 {onlineParamsDialogue}
590export default compose(
592 withTxnConfirmations,
596)(StatelessTransaction);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list