#' Get transactions for an address
#'
#' Get normal, internal, ERC-20, or ERC-721 transactions for an Ethereum address.
#'
#' @param address Character. A single ethereum address as a character string (40
#' hexadecimal characters prepended by '0x').
#' @param api_key An Etherscan API key (see Details).
#' @param internal Logical. Should normal (`FALSE`, default) or internal
#' (`TRUE`) transactions be queried? _Deprecated. Please use `type` instead._
#' @param type The type of transaction to query. One of `'normal'`,
#' `'internal'`, `'ERC20'`, or `'ERC721'`.
#' @param startblock Starting block for transaction query. Default is 0.
#' @param endblock Ending block for transaction query. Default is 999999999.
#' @param startdate Optional `Date` or `POSIXct` object defining the start date
#' or datetime for the resulting list of transactions to be filtered to. If
#' `startdate` is provided, `startblock` is ignored.If a `Date` object is
#' provided, timezone is assumed to be UTC, and all transactions up to
#' midnight of that date will be included in the filtered result. For
#' `POSIXct` objects, timezone is taken from the object. Note that,
#' `startdate` is applied as a filter _after_ downloading transactions, and so
#' settings a value that is later than the earliest transaction sent/received
#' by the address does not speed up the download process.
#' @param enddate Optional `Date` or `POSIXct` object defining the end date
#' or datetime for the resulting list of transactions to be filtered to.
#' Starting block for transaction query. If `enddate` is provided, `endblock`
#' is ignored. If a `Date` object is provided, timezone is assumed to be UTC,
#' and all transactions up to midnight of that date will be included in the
#' filtered result. For `POSIXct` objects, timezone is taken from the object.
#' @param network Ethereum network to use. One of `'mainnet'` (default),
#' `'ropsten'`, `'rinkeby'`, `'kovan'`, or `'goerli'`.
#' @param first_page Logical. Should only the first page of results (up to
#' 10,000 transactions) be returned.
#' @param no_errors Logical. Should unsuccessful transactions be omitted
#' (`FALSE`, default)?
#' @param quiet Logical. Suppress messages? Default is `FALSE`.
#' @return A `tbl_df` with transaction details. Columns vary depending on
#' `type`.
#' @details `get_txs` uses the Etherscan API to source information about
#' transactions to and from an Ethereum address. Register for an API key at
#' the [Etherscan Developer APIs page](https://etherscan.io/apis).
#' @section Warning:
#' As per the Etherscan documentation, _the Etherscan Ethereum Developer APIs
#' are provided as a community service and without warranty, so please just use
#' what you need and no more. They support both GET/POST requests and a rate
#' limit of 5 requests/sec._
#' @keywords Ethereum, transaction, blockchain, cryptocurrency, crypto, ETH
#' @importFrom jsonlite fromJSON
#' @importFrom dplyr filter as_tibble mutate select matches %>%
#' @importFrom lubridate with_tz now
#' @importFrom gmp as.bigz
#' @export
get_txs <- function(address, api_key, internal=FALSE,
type=c('normal', 'internal', 'ERC20', 'ERC721'),
startblock=0, endblock=999999999,
startdate, enddate, network='mainnet',
first_page=FALSE, no_errors=TRUE,
quiet=FALSE) {
if(as.numeric(startblock) > as.numeric(endblock))
stop('endblock cannot be less than startblock')
if(!missing(startdate)) {
if(!any(c('Date', 'POSIXt') %in% is(startdate)))
stop('startdate must be a Date, POSIXct or POSIXlt object.')
message('Ignoring startblock (startdate provided)')
startblock <- 0
}
if(!missing(enddate)) {
if(!any(c('Date', 'POSIXt') %in% is(enddate)))
stop('enddate must be a Date, POSIXct or POSIXlt object.')
if(!missing(startdate) && startdate > enddate)
stop('startdate must be before enddate')
message('Ignoring endblock (enddate provided)')
endblock <- 999999999
if(is(enddate, 'Date'))
enddate <- as.POSIXct(enddate) + 86400 # enddate is midnight of enddate
}
if (isTRUE(internal)) {
warning("argument `internal` is deprecated; please use `type` instead.",
call. = FALSE)
type <- 'internal'
} else {
type <- match.arg(type)
}
address <- tolower(address)
if(missing(api_key)) stop('API Key required. See https://etherscan.io/apis')
network <- match.arg(network, c('mainnet', 'ropsten', 'rinkeby', 'kovan', 'goerli'))
config <- list(address=address, type=type, network=network, no_errors=no_errors)
network <- ifelse(network=='mainnet', '', paste0('-', network))
txtype <- switch(
type, normal='txlist', internal='txlistinternal',
ERC20='tokentx', ERC721='tokennfttx')
.get_txs <- function(network, txtype, address, sort, api_key, startblock,
endblock) {
j <- jsonlite::fromJSON(sprintf(
'http://api%s.etherscan.io/api?module=account&action=%s&address=%s&startblock=%s&endblock=%s&sort=%s&apikey=%s',
network, txtype, address, startblock, endblock, sort, api_key))
if(j$status != '1') {
if(j$message == 'No transactions found') {
warning('No transactions found for address: ', address, call.=FALSE)
return(NULL)
} else {
stop('Invalid address', call. = FALSE)
}
}
j <- j$result
out <- switch(type,
normal={
j %>%
dplyr::mutate(
timeStamp=as.numeric(timeStamp),
timeStamp=as.POSIXct(timeStamp, origin='1970-01-01'),
blockNumber=as.numeric(blockNumber),
nonce=as.numeric(nonce),
value=as.numeric(value),
transactionIndex=as.numeric(transactionIndex),
value_eth=value/1e18,
gas=as.numeric(gas),
gasPrice=as.numeric(gasPrice),
gasPrice_gwei=gasPrice/1e9,
isError=as.numeric(isError),
cumulativeGasUsed=as.numeric(cumulativeGasUsed),
gasUsed=as.numeric(gasUsed),
confirmations=as.numeric(confirmations)
)
},
internal={
j %>%
dplyr::mutate(
timeStamp=as.numeric(timeStamp),
timeStamp=as.POSIXct(timeStamp, origin='1970-01-01'),
blockNumber=as.numeric(blockNumber),
value=as.numeric(value),
value_eth=value/1e18,
gas=as.numeric(gas),
isError=as.numeric(isError),
gasUsed=as.numeric(gasUsed)
)
},
ERC20={
d <- j %>%
dplyr::mutate(
timeStamp=as.numeric(timeStamp),
timeStamp=as.POSIXct(timeStamp, origin='1970-01-01'),
blockNumber=as.numeric(blockNumber),
nonce=as.numeric(nonce),
tokenDecimal=as.numeric(tokenDecimal),
transactionIndex=as.numeric(transactionIndex),
gas=as.numeric(gas),
gasPrice=as.numeric(gasPrice),
gasPrice_gwei=gasPrice/1e9,
gasUsed=as.numeric(gasUsed),
cumulativeGasUsed=as.numeric(cumulativeGasUsed),
confirmations=as.numeric(confirmations)
)
d$value <- gmp::as.bigz(d$value)
d
},
ERC721={
j %>%
dplyr::mutate(
timeStamp=as.numeric(timeStamp),
timeStamp=as.POSIXct(timeStamp, origin='1970-01-01'),
blockNumber=as.numeric(blockNumber),
nonce=as.numeric(nonce),
tokenID=as.integer(tokenID),
tokenDecimal=as.numeric(tokenDecimal),
transactionIndex=as.numeric(transactionIndex),
gas=as.numeric(gas),
gasPrice=as.numeric(gasPrice),
gasPrice_gwei=gasPrice/1e9,
gasUsed=as.numeric(gasUsed),
cumulativeGasUsed=as.numeric(cumulativeGasUsed),
confirmations=as.numeric(confirmations)
)
}
)
out$timeStamp <- lubridate::with_tz(out$timeStamp, 'UTC')
out
}
if(isTRUE(first_page)) {
if(!quiet) message(sprintf('Getting %s transactions...', type))
txs <- .get_txs(network=network, txtype=txtype, address=address,
startblock=startblock, endblock=endblock, sort='desc',
api_key=api_key)
if(is.null(txs)) return(NULL)
txs <- dplyr::select(txs, dplyr::matches('^((?!confirmations).)*$', perl=T))
} else {
if(!quiet) message(sprintf('Getting %s transactions...', type))
txs <- .get_txs(network, txtype, address, 'asc', api_key,
startblock=startblock, endblock=endblock)
if(is.null(txs)) return(NULL)
txs <- dplyr::select(txs, dplyr::matches('^((?!confirmations).)*$', perl=T))
n <- nrow(txs)
while(n == 10000) {
if(!missing(enddate) && max(txs$timeStamp) > enddate)
break
if(!quiet) message(sprintf('Getting more %s transactions...', type))
.txs <- .get_txs(network, txtype, address, 'asc', api_key,
startblock=max(txs$blockNumber), endblock=endblock) %>%
dplyr::select(dplyr::matches('^((?!confirmations).)*$', perl=T))
n <- nrow(.txs)
txs <- rbind(txs, .txs)
}
}
now <- lubridate::now(tzone='UTC')
txs <- unique(txs)
if(!missing(startdate))
txs <- dplyr::filter(txs, timeStamp >= startdate)
if(!missing(enddate))
txs <- dplyr::filter(txs, timeStamp <= enddate)
if(isTRUE(no_errors) && type %in% c('normal', 'internal'))
txs <- dplyr::filter(txs, isError=='0')
attr(txs, 'address') <- config$address
attr(txs, 'type') <- config$type
attr(txs, 'network') <- config$network
attr(txs, 'no_errors') <- config$no_errors
attr(txs, 'last_updated') <- now
txs
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.