Skip to content

Instantly share code, notes, and snippets.

@cellog
Created February 20, 2019 19:20
Show Gist options
  • Save cellog/57de482c8a481b617c5ba5d061f0f6a0 to your computer and use it in GitHub Desktop.
Save cellog/57de482c8a481b617c5ba5d061f0f6a0 to your computer and use it in GitHub Desktop.
Version 2 - key purchase
export function makeGetTransactionInfo({
web3,
transactionHash,
mineTransaction,
failTransaction,
}) {
const getTransactionInfo = async transaction => {
if (!transactionHash) return
const [blockNumber, blockTransaction] = await Promise.all([
web3.eth.getBlockNumber(),
web3.eth.getTransaction(transactionHash),
])
// If the block transaction is missing the transaction has been submitted but not
// received by all nodes
if (!blockTransaction) {
return
}
if (blockTransaction.transactionIndex === null) {
// This means the transaction is not in a block yet (ie. not mined)
return
}
mineTransaction(blockNumber, blockTransaction.blockNumber)
// The transaction was mined, so we should have a receipt for it
const transactionReceipt = await web3.eth.getTransactionReceipt(
transactionHash
)
if (transactionReceipt) {
// NOTE: old version of web3.js (pre 1.0.0-beta.34) are not parsing 0x0 into a falsy value
if (!transactionReceipt.status || transactionReceipt.status === '0x0') {
failTransaction()
}
}
}
return getTransactionInfo
}
export function makeTransactionPoll({
transaction,
requiredConfirmations,
getTransactionInfo,
}) {
const transactionPoll = async () => {
if (!['pending', 'confirming', 'mined'].includes(transaction.status)) return
if (transaction.confirmations >= requiredConfirmations) return
getTransactionInfo(transaction)
}
return transactionPoll
}
export function sendNewKeyPurchaseTransaction({
to,
from,
data,
value,
gas,
wallet,
newTransaction,
failTransaction,
setHash,
setError,
}) {
const transaction = wallet.eth.sendTransaction({
to,
from,
value,
data,
gas,
})
transaction
.once('transactionHash', hash => {
setHash(hash)
newTransaction()
})
.on('error', error => {
failTransaction()
setError(error)
})
.once('transactionHash', hash => {
setHash(hash)
})
}
import { useState, useReducer } from 'react'
import Web3Utils from 'web3-utils'
import LockContract from '../artifacts/contracts/PublicLock.json'
import useAccount from './web3/useAccount'
import { TRANSACTION_TYPES } from '../constants'
import useWallet from './web3/useWallet'
import usePoll from './utils/usePoll'
import useWeb3 from './web3/useWeb3'
import useConfig from './utils/useConfig'
import {
makeGetTransactionInfo,
makeTransactionPoll,
sendNewKeyPurchaseTransaction,
} from './asyncActions/keyPurchaseTransactions'
export function handleTransactionUpdates(transaction, update) {
const { type, info } = update
// triggered on key purchase, prior to sending the transaction, after retrieving the hash
if (type === 'new') {
const { lock, account } = info
return {
...transaction,
lock,
account,
status: 'pending',
type: TRANSACTION_TYPES.KEY_PURCHASE,
confirmations: 0,
asOf: Number.MAX_SAFE_INTEGER, // Assign the largest block number for sorting purposes
}
}
// triggered when we get the transaction hash before the transaction is sent to the miners
if (type === 'hash') {
const { hash } = info
return { ...transaction, hash }
}
// transaction has been mined, is on the chain, and a new block has been mined
if (type === 'mined') {
const { blockNumber, asOf, requiredConfirmations } = info
const confirmations = blockNumber - asOf
return {
...transaction,
asOf,
status: confirmations < requiredConfirmations ? 'confirming' : 'mined',
confirmations,
}
}
// transaction receipt showed the transaction was not propagated for some error
if (type === 'failed') {
return {
...transaction,
status: 'failed',
}
}
return transaction
}
export default function useKeyPurchaseTransaction(window, lock) {
const web3 = useWeb3()
const wallet = useWallet()
const [error, setError] = useState()
const { requiredConfirmations, blockTime } = useConfig()
const [transaction, updateTransaction] = useReducer(
handleTransactionUpdates,
{ status: 'inactive', confirmations: 0 }
)
const { account } = useAccount(window)
// transaction reducer action creators
const setHash = hash => updateTransaction({ type: 'hash', info: { hash } })
const newTransaction = () =>
updateTransaction({ type: 'new', info: { lock: lock.address, account } })
const mineTransaction = (blockNumber, asOf) =>
updateTransaction({
type: 'mined',
info: { blockNumber, asOf, requiredConfirmations },
})
const failTransaction = () => updateTransaction({ type: 'fail' })
const getTransactionInfo = makeGetTransactionInfo({
web3,
transactionHash: transaction.hash,
mineTransaction,
failTransaction,
})
const transactionPoll = makeTransactionPoll({
transaction,
requiredConfirmations,
getTransactionInfo,
})
usePoll(transactionPoll, blockTime / 2)
const purchaseKey = () => {
if (!lock || !account || transaction.status !== 'inactive') return
// when we enable transfer of existing keys, data will be the existing key's data
const data = ''
const lockContract = new wallet.eth.Contract(LockContract.abi, lock.address)
const abi = lockContract.methods
// when we enable transfer of existing keys, the account will be the existing key's owner
.purchaseFor(account, Web3Utils.utf8ToHex(data || ''))
.encodeABI()
sendNewKeyPurchaseTransaction({
wallet,
to: lock.address,
from: account,
data: abi,
gas: 1000000,
value: Web3Utils.toWei(lock.keyPrice, 'ether'),
contract: LockContract,
newTransaction,
mineTransaction,
failTransaction,
setHash,
setError,
})
}
if (error) throw error
// updateTransaction is returned strictly for testing purposes
return { purchaseKey, transaction, updateTransaction }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment