mirror of
https://github.com/status-im/nim-ethers.git
synced 2025-01-13 17:15:26 +00:00
241ce6e8f3
Allows it to contain error data. It is not the signing that fails, so it makes sense to use an error type that indicates that the provider failed.
337 lines
9.7 KiB
Nim
337 lines
9.7 KiB
Nim
import std/tables
|
|
import std/uri
|
|
import pkg/chronicles
|
|
import pkg/eth/common/eth_types_json_serialization
|
|
import pkg/json_rpc/rpcclient
|
|
import pkg/json_rpc/errors
|
|
import pkg/serde
|
|
import ../basics
|
|
import ../provider
|
|
import ../signer
|
|
import ./jsonrpc/rpccalls
|
|
import ./jsonrpc/conversions
|
|
import ./jsonrpc/subscriptions
|
|
import ./jsonrpc/errors
|
|
|
|
export basics
|
|
export provider
|
|
export chronicles
|
|
export errors.JsonRpcProviderError
|
|
|
|
{.push raises: [].}
|
|
|
|
logScope:
|
|
topics = "ethers jsonrpc"
|
|
|
|
type
|
|
JsonRpcProvider* = ref object of Provider
|
|
client: Future[RpcClient]
|
|
subscriptions: Future[JsonRpcSubscriptions]
|
|
|
|
JsonRpcSubscription* = ref object of Subscription
|
|
subscriptions: JsonRpcSubscriptions
|
|
id: JsonNode
|
|
|
|
# Signer
|
|
JsonRpcSigner* = ref object of Signer
|
|
provider: JsonRpcProvider
|
|
address: ?Address
|
|
JsonRpcSignerError* = object of SignerError
|
|
|
|
# Provider
|
|
|
|
const defaultUrl = "http://localhost:8545"
|
|
const defaultPollingInterval = 4.seconds
|
|
|
|
proc jsonHeaders: seq[(string, string)] =
|
|
@[("Content-Type", "application/json")]
|
|
|
|
proc new*(
|
|
_: type JsonRpcProvider,
|
|
url=defaultUrl,
|
|
pollingInterval=defaultPollingInterval): JsonRpcProvider {.raises: [JsonRpcProviderError].} =
|
|
|
|
var initialized: Future[void]
|
|
var client: RpcClient
|
|
var subscriptions: JsonRpcSubscriptions
|
|
|
|
proc initialize {.async: (raises:[JsonRpcProviderError]).} =
|
|
convertError:
|
|
case parseUri(url).scheme
|
|
of "ws", "wss":
|
|
let websocket = newRpcWebSocketClient(getHeaders = jsonHeaders)
|
|
await websocket.connect(url)
|
|
client = websocket
|
|
subscriptions = JsonRpcSubscriptions.new(websocket)
|
|
else:
|
|
let http = newRpcHttpClient(getHeaders = jsonHeaders)
|
|
await http.connect(url)
|
|
client = http
|
|
subscriptions = JsonRpcSubscriptions.new(http,
|
|
pollingInterval = pollingInterval)
|
|
subscriptions.start()
|
|
|
|
proc awaitClient: Future[RpcClient] {.async:(raises:[JsonRpcProviderError]).} =
|
|
convertError:
|
|
await initialized
|
|
return client
|
|
|
|
proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async:(raises:[JsonRpcProviderError]).} =
|
|
convertError:
|
|
await initialized
|
|
return subscriptions
|
|
|
|
initialized = initialize()
|
|
return JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
|
|
|
|
proc callImpl(
|
|
client: RpcClient,
|
|
call: string,
|
|
args: JsonNode): Future[JsonNode] {.async: (raises: [JsonRpcProviderError]).} =
|
|
|
|
without response =? (await client.call(call, %args)).catch, error:
|
|
raiseJsonRpcProviderError error.msg
|
|
without json =? JsonNode.fromJson(response.string), error:
|
|
raiseJsonRpcProviderError "Failed to parse response: " & error.msg
|
|
json
|
|
|
|
proc send*(
|
|
provider: JsonRpcProvider,
|
|
call: string,
|
|
arguments: seq[JsonNode] = @[]): Future[JsonNode]
|
|
{.async: (raises: [JsonRpcProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.callImpl(call, %arguments)
|
|
|
|
proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]]
|
|
{.async: (raises: [JsonRpcProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_accounts()
|
|
|
|
proc getSigner*(provider: JsonRpcProvider): JsonRpcSigner =
|
|
JsonRpcSigner(provider: provider)
|
|
|
|
proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
|
JsonRpcSigner(provider: provider, address: some address)
|
|
|
|
method getBlockNumber*(
|
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_blockNumber()
|
|
|
|
method getBlock*(
|
|
provider: JsonRpcProvider,
|
|
tag: BlockTag): Future[?Block] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_getBlockByNumber(tag, false)
|
|
|
|
method call*(
|
|
provider: JsonRpcProvider,
|
|
tx: Transaction,
|
|
blockTag = BlockTag.latest): Future[seq[byte]] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_call(tx, blockTag)
|
|
|
|
method getGasPrice*(
|
|
provider: JsonRpcProvider): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_gasPrice()
|
|
|
|
method getTransactionCount*(
|
|
provider: JsonRpcProvider,
|
|
address: Address,
|
|
blockTag = BlockTag.latest): Future[UInt256] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_getTransactionCount(address, blockTag)
|
|
|
|
method getTransaction*(
|
|
provider: JsonRpcProvider,
|
|
txHash: TransactionHash): Future[?PastTransaction] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_getTransactionByHash(txHash)
|
|
|
|
method getTransactionReceipt*(
|
|
provider: JsonRpcProvider,
|
|
txHash: TransactionHash): Future[?TransactionReceipt] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
return await client.eth_getTransactionReceipt(txHash)
|
|
|
|
method getLogs*(
|
|
provider: JsonRpcProvider,
|
|
filter: EventFilter): Future[seq[Log]] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
let logsJson = if filter of Filter:
|
|
await client.eth_getLogs(Filter(filter))
|
|
elif filter of FilterByBlockHash:
|
|
await client.eth_getLogs(FilterByBlockHash(filter))
|
|
else:
|
|
await 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]).} =
|
|
|
|
try:
|
|
convertError:
|
|
let client = await provider.client
|
|
return await 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]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
try:
|
|
return await client.eth_chainId()
|
|
except CatchableError:
|
|
return parse(await client.net_version(), UInt256)
|
|
|
|
method sendTransaction*(
|
|
provider: JsonRpcProvider,
|
|
rawTransaction: seq[byte]): Future[TransactionResponse]
|
|
{.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let
|
|
client = await provider.client
|
|
hash = await client.eth_sendRawTransaction(rawTransaction)
|
|
|
|
return TransactionResponse(hash: hash, provider: provider)
|
|
|
|
method subscribe*(
|
|
provider: JsonRpcProvider,
|
|
filter: EventFilter,
|
|
onLog: LogHandler): Future[Subscription] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let subscriptions = await provider.subscriptions
|
|
let id = await subscriptions.subscribeLogs(filter, onLog)
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
method subscribe*(
|
|
provider: JsonRpcProvider,
|
|
onBlock: BlockHandler): Future[Subscription] {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let subscriptions = await provider.subscriptions
|
|
let id = await subscriptions.subscribeBlocks(onBlock)
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
method unsubscribe*(
|
|
subscription: JsonRpcSubscription) {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let subscriptions = subscription.subscriptions
|
|
let id = subscription.id
|
|
await subscriptions.unsubscribe(id)
|
|
|
|
method isSyncing*(provider: JsonRpcProvider): Future[bool] {.async.} =
|
|
let response = await provider.send("eth_syncing")
|
|
if response.kind == JsonNodeKind.JObject:
|
|
return true
|
|
return response.getBool()
|
|
|
|
method close*(
|
|
provider: JsonRpcProvider) {.async: (raises:[ProviderError]).} =
|
|
|
|
convertError:
|
|
let client = await provider.client
|
|
let subscriptions = await provider.subscriptions
|
|
await subscriptions.close()
|
|
await client.close()
|
|
|
|
# Signer
|
|
|
|
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 JsonRpcError as error:
|
|
raiseJsonRpcSignerError(error.msg)
|
|
except CatchableError as error:
|
|
raise newException(JsonRpcSignerError, error.msg)
|
|
|
|
method provider*(signer: JsonRpcSigner): Provider
|
|
{.gcsafe, raises: [SignerError].} =
|
|
|
|
signer.provider
|
|
|
|
method getAddress*(
|
|
signer: JsonRpcSigner): Future[Address]
|
|
{.async: (raises:[ProviderError, SignerError]).} =
|
|
|
|
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]).} =
|
|
|
|
convertSignerError:
|
|
let client = await signer.provider.client
|
|
let address = await signer.getAddress()
|
|
return await client.personal_sign(message, address)
|
|
|
|
method sendTransaction*(
|
|
signer: JsonRpcSigner,
|
|
transaction: Transaction): Future[TransactionResponse]
|
|
{.async: (raises:[SignerError, ProviderError]).} =
|
|
|
|
convertError:
|
|
if nonce =? transaction.nonce:
|
|
signer.updateNonce(nonce)
|
|
let
|
|
client = await signer.provider.client
|
|
hash = await client.eth_sendTransaction(transaction)
|
|
|
|
return TransactionResponse(hash: hash, provider: signer.provider)
|