mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-02 13:43:06 +00:00
282 lines
9.1 KiB
Nim
282 lines
9.1 KiB
Nim
import std/tables
|
|
import std/uri
|
|
import pkg/chronicles
|
|
import pkg/eth/common/eth_types except Block, Log, Address, Transaction
|
|
import pkg/json_rpc/rpcclient
|
|
import pkg/json_rpc/errors
|
|
import pkg/serde
|
|
import ../basics
|
|
import ../provider
|
|
import ../subscriptions
|
|
import ../signer
|
|
import ./jsonrpc/rpccalls
|
|
import ./jsonrpc/conversions
|
|
import ./jsonrpc/errors
|
|
import ./jsonrpc/websocket
|
|
|
|
export basics
|
|
export provider
|
|
export chronicles
|
|
export errors.JsonRpcProviderError
|
|
|
|
{.push raises: [].}
|
|
|
|
logScope:
|
|
topics = "ethers jsonrpc"
|
|
|
|
type JsonRpcProvider* = ref object of Provider
|
|
client: RpcClient
|
|
subscriptions: Subscriptions
|
|
maxPriorityFeePerGas: UInt256
|
|
|
|
const defaultUrl = "http://localhost:8545"
|
|
const defaultPollingInterval = 4.seconds
|
|
const defaultMaxPriorityFeePerGas = 1_000_000_000.u256
|
|
|
|
proc jsonHeaders: seq[(string, string)] =
|
|
@[("Content-Type", "application/json")]
|
|
|
|
proc connect*(
|
|
_: type JsonRpcProvider,
|
|
url=defaultUrl,
|
|
pollingInterval=defaultPollingInterval,
|
|
maxPriorityFeePerGas=defaultMaxPriorityFeePerGas
|
|
): Future[JsonRpcProvider] {.async:(raises: [JsonRpcProviderError, CancelledError]).} =
|
|
convertError:
|
|
let provider = JsonRpcProvider(maxPriorityFeePerGas: maxPriorityFeePerGas)
|
|
case parseUri(url).scheme
|
|
of "ws", "wss":
|
|
let websocket = newRpcWebSocketClient(getHeaders = jsonHeaders)
|
|
await websocket.connect(url)
|
|
provider.client = websocket
|
|
provider.subscriptions = Subscriptions.new(provider, pollingInterval)
|
|
await provider.subscriptions.useWebsocketUpdates(websocket)
|
|
else:
|
|
let http = newRpcHttpClient(getHeaders = jsonHeaders)
|
|
await http.connect(url)
|
|
provider.client = http
|
|
provider.subscriptions = Subscriptions.new(provider, pollingInterval)
|
|
return provider
|
|
|
|
proc callImpl(
|
|
client: RpcClient, call: string, args: JsonNode
|
|
): Future[JsonNode] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
|
try:
|
|
let response = await client.call(call, %args)
|
|
without json =? JsonNode.fromJson(response.string), error:
|
|
raiseJsonRpcProviderError(
|
|
"Failed to parse response '" & response.string & "': " & error.msg
|
|
)
|
|
return json
|
|
except CancelledError as error:
|
|
raise error
|
|
except CatchableError as error:
|
|
raiseJsonRpcProviderError error.msg
|
|
|
|
proc send*(
|
|
provider: JsonRpcProvider, call: string, arguments: seq[JsonNode] = @[]
|
|
): Future[JsonNode] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.callImpl(call, %arguments)
|
|
|
|
proc listAccounts*(
|
|
provider: JsonRpcProvider
|
|
): Future[seq[Address]] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_accounts()
|
|
|
|
method getBlockNumber*(
|
|
provider: JsonRpcProvider
|
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_blockNumber()
|
|
|
|
method getBlock*(
|
|
provider: JsonRpcProvider, tag: BlockTag
|
|
): Future[?Block] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_getBlockByNumber(tag, false)
|
|
|
|
method call*(
|
|
provider: JsonRpcProvider, tx: Transaction, blockTag = BlockTag.latest
|
|
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_call(tx, blockTag)
|
|
|
|
method getGasPrice*(
|
|
provider: JsonRpcProvider
|
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_gasPrice()
|
|
|
|
method getMaxPriorityFeePerGas*(
|
|
provider: JsonRpcProvider
|
|
): Future[UInt256] {.async: (raises: [CancelledError]).} =
|
|
try:
|
|
convertError:
|
|
return await provider.client.eth_maxPriorityFeePerGas()
|
|
except JsonRpcProviderError:
|
|
# If the provider does not provide the implementation
|
|
# let's just remove the manual value
|
|
return provider.maxPriorityFeePerGas
|
|
|
|
method getTransactionCount*(
|
|
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_getTransactionCount(address, blockTag)
|
|
|
|
method getTransaction*(
|
|
provider: JsonRpcProvider, txHash: TransactionHash
|
|
): Future[?PastTransaction] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_getTransactionByHash(txHash)
|
|
|
|
method getTransactionReceipt*(
|
|
provider: JsonRpcProvider, txHash: TransactionHash
|
|
): Future[?TransactionReceipt] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
return await provider.client.eth_getTransactionReceipt(txHash)
|
|
|
|
method getLogs*(
|
|
provider: JsonRpcProvider, filter: EventFilter
|
|
): Future[seq[Log]] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
let logsJson =
|
|
if filter of Filter:
|
|
await provider.client.eth_getLogs(Filter(filter))
|
|
elif filter of FilterByBlockHash:
|
|
await provider.client.eth_getLogs(FilterByBlockHash(filter))
|
|
else:
|
|
await provider.client.eth_getLogs(filter)
|
|
|
|
var logs: seq[Log] = @[]
|
|
for logJson in logsJson.getElems:
|
|
if log =? Log.fromJson(logJson):
|
|
logs.add log
|
|
|
|
return logs
|
|
|
|
method estimateGas*(
|
|
provider: JsonRpcProvider,
|
|
transaction: Transaction,
|
|
blockTag = BlockTag.latest,
|
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
try:
|
|
convertError:
|
|
return await provider.client.eth_estimateGas(transaction, blockTag)
|
|
except ProviderError as error:
|
|
raise (ref EstimateGasError)(
|
|
msg: "Estimate gas failed: " & error.msg,
|
|
data: error.data,
|
|
transaction: transaction,
|
|
parent: error,
|
|
)
|
|
|
|
method getChainId*(
|
|
provider: JsonRpcProvider
|
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
try:
|
|
return await provider.client.eth_chainId()
|
|
except CancelledError as error:
|
|
raise error
|
|
except CatchableError:
|
|
return parse(await provider.client.net_version(), UInt256)
|
|
|
|
method sendTransaction*(
|
|
provider: JsonRpcProvider, rawTransaction: seq[byte]
|
|
): Future[TransactionResponse] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
let hash = await provider.client.eth_sendRawTransaction(rawTransaction)
|
|
return TransactionResponse(hash: hash, provider: provider)
|
|
|
|
method subscribe*(
|
|
provider: JsonRpcProvider, filter: EventFilter, onLog: LogHandler
|
|
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
await provider.subscriptions.subscribe(filter, onLog)
|
|
|
|
method subscribe*(
|
|
provider: JsonRpcProvider, onBlock: BlockHandler
|
|
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
await provider.subscriptions.subscribe(onBlock)
|
|
|
|
method isSyncing*(
|
|
provider: JsonRpcProvider
|
|
): Future[bool] {.async: (raises: [ProviderError, CancelledError]).} =
|
|
let response = await provider.send("eth_syncing")
|
|
if response.kind == JsonNodeKind.JObject:
|
|
return true
|
|
return response.getBool()
|
|
|
|
method close*(
|
|
provider: JsonRpcProvider
|
|
) {.async: (raises: [ProviderError, CancelledError]).} =
|
|
convertError:
|
|
await provider.subscriptions.close()
|
|
await provider.client.close()
|
|
|
|
type
|
|
JsonRpcSigner* = ref object of Signer
|
|
provider: JsonRpcProvider
|
|
address: ?Address
|
|
JsonRpcSignerError* = object of SignerError
|
|
|
|
proc raiseJsonRpcSignerError(
|
|
message: string) {.raises: [JsonRpcSignerError].} =
|
|
|
|
var message = message
|
|
if json =? JsonNode.fromJson(message):
|
|
if "message" in json:
|
|
message = json{"message"}.getStr
|
|
raise newException(JsonRpcSignerError, message)
|
|
|
|
template convertSignerError(body) =
|
|
try:
|
|
body
|
|
except CancelledError as error:
|
|
raise error
|
|
except JsonRpcError as error:
|
|
raiseJsonRpcSignerError(error.msg)
|
|
except CatchableError as error:
|
|
raise newException(JsonRpcSignerError, error.msg)
|
|
|
|
proc getSigner*(provider: JsonRpcProvider): JsonRpcSigner =
|
|
JsonRpcSigner(provider: provider)
|
|
|
|
proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
|
JsonRpcSigner(provider: provider, address: some address)
|
|
|
|
method provider*(signer: JsonRpcSigner): Provider
|
|
{.gcsafe, raises: [SignerError].} =
|
|
|
|
signer.provider
|
|
|
|
method getAddress*(
|
|
signer: JsonRpcSigner
|
|
): Future[Address] {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
|
if address =? signer.address:
|
|
return address
|
|
|
|
let accounts = await signer.provider.listAccounts()
|
|
if accounts.len > 0:
|
|
return accounts[0]
|
|
|
|
raiseJsonRpcSignerError "no address found"
|
|
|
|
method signMessage*(
|
|
signer: JsonRpcSigner, message: seq[byte]
|
|
): Future[seq[byte]] {.async: (raises: [SignerError, CancelledError]).} =
|
|
convertSignerError:
|
|
let address = await signer.getAddress()
|
|
return await signer.provider.client.personal_sign(message, address)
|
|
|
|
method sendTransaction*(
|
|
signer: JsonRpcSigner, transaction: Transaction
|
|
): Future[TransactionResponse] {.
|
|
async: (raises: [SignerError, ProviderError, CancelledError])
|
|
.} =
|
|
convertError:
|
|
let hash = await signer.provider.client.eth_sendTransaction(transaction)
|
|
return TransactionResponse(hash: hash, provider: signer.provider)
|