2022-02-02 15:56:37 +00:00
|
|
|
import std/json
|
|
|
|
import std/tables
|
2022-01-18 11:10:20 +00:00
|
|
|
import std/uri
|
|
|
|
import pkg/json_rpc/rpcclient
|
2022-06-29 13:13:25 +00:00
|
|
|
import pkg/json_rpc/errors
|
2022-01-18 11:10:20 +00:00
|
|
|
import ../basics
|
|
|
|
import ../provider
|
2022-01-20 13:39:37 +00:00
|
|
|
import ../signer
|
2022-02-02 16:00:12 +00:00
|
|
|
import ./jsonrpc/rpccalls
|
|
|
|
import ./jsonrpc/conversions
|
2023-06-27 09:47:59 +00:00
|
|
|
import ./jsonrpc/subscriptions
|
2022-01-18 11:10:20 +00:00
|
|
|
|
2023-06-19 12:13:44 +00:00
|
|
|
export json
|
2022-01-18 11:10:20 +00:00
|
|
|
export basics
|
|
|
|
export provider
|
|
|
|
|
2022-01-18 13:51:53 +00:00
|
|
|
push: {.upraises: [].}
|
|
|
|
|
2022-01-20 13:39:37 +00:00
|
|
|
type
|
|
|
|
JsonRpcProvider* = ref object of Provider
|
|
|
|
client: Future[RpcClient]
|
2023-06-27 09:47:59 +00:00
|
|
|
subscriptions: Future[JsonRpcSubscriptions]
|
2022-01-20 13:39:37 +00:00
|
|
|
JsonRpcSigner* = ref object of Signer
|
|
|
|
provider: JsonRpcProvider
|
|
|
|
address: ?Address
|
2022-09-20 11:24:47 +00:00
|
|
|
JsonRpcProviderError* = object of ProviderError
|
2023-06-28 09:02:21 +00:00
|
|
|
JsonRpcSubscription* = ref object of Subscription
|
|
|
|
subscriptions: JsonRpcSubscriptions
|
|
|
|
id: JsonNode
|
2022-01-20 13:39:37 +00:00
|
|
|
|
2022-09-20 10:18:01 +00:00
|
|
|
proc raiseProviderError(message: string) {.upraises: [JsonRpcProviderError].} =
|
|
|
|
var message = message
|
|
|
|
try:
|
|
|
|
message = parseJson(message){"message"}.getStr
|
|
|
|
except Exception:
|
|
|
|
discard
|
2022-01-20 13:39:37 +00:00
|
|
|
raise newException(JsonRpcProviderError, message)
|
|
|
|
|
2022-06-29 13:13:25 +00:00
|
|
|
template convertError(body) =
|
|
|
|
try:
|
|
|
|
body
|
|
|
|
except JsonRpcError as error:
|
|
|
|
raiseProviderError(error.msg)
|
2022-09-15 10:38:52 +00:00
|
|
|
# Catch all ValueErrors for now, at least until JsonRpcError is actually
|
|
|
|
# raised. PR created: https://github.com/status-im/nim-json-rpc/pull/151
|
|
|
|
except ValueError as error:
|
|
|
|
raiseProviderError(error.msg)
|
2022-06-29 13:13:25 +00:00
|
|
|
|
2022-01-20 13:39:37 +00:00
|
|
|
# Provider
|
2022-01-18 11:10:20 +00:00
|
|
|
|
|
|
|
const defaultUrl = "http://localhost:8545"
|
2023-06-27 13:08:37 +00:00
|
|
|
const defaultPollingInterval = 4.seconds
|
2022-01-18 11:10:20 +00:00
|
|
|
|
2023-06-20 11:32:14 +00:00
|
|
|
proc jsonHeaders: seq[(string, string)] =
|
|
|
|
@[("Content-Type", "application/json")]
|
|
|
|
|
2023-06-27 13:08:37 +00:00
|
|
|
proc new*(_: type JsonRpcProvider,
|
|
|
|
url=defaultUrl,
|
|
|
|
pollingInterval=defaultPollingInterval): JsonRpcProvider =
|
2023-06-27 09:47:59 +00:00
|
|
|
var initialized: Future[void]
|
|
|
|
var client: RpcClient
|
|
|
|
var subscriptions: JsonRpcSubscriptions
|
|
|
|
|
|
|
|
proc initialize {.async.} =
|
|
|
|
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
|
2023-06-27 13:08:37 +00:00
|
|
|
subscriptions = JsonRpcSubscriptions.new(http,
|
|
|
|
pollingInterval = pollingInterval)
|
2022-04-19 15:48:27 +00:00
|
|
|
|
2023-06-27 09:47:59 +00:00
|
|
|
proc awaitClient: Future[RpcClient] {.async.} =
|
2023-06-28 09:02:21 +00:00
|
|
|
convertError:
|
|
|
|
await initialized
|
|
|
|
return client
|
2022-04-19 15:48:27 +00:00
|
|
|
|
2023-06-27 09:47:59 +00:00
|
|
|
proc awaitSubscriptions: Future[JsonRpcSubscriptions] {.async.} =
|
2023-06-28 09:02:21 +00:00
|
|
|
convertError:
|
|
|
|
await initialized
|
|
|
|
return subscriptions
|
2022-02-02 15:56:37 +00:00
|
|
|
|
2023-06-27 09:47:59 +00:00
|
|
|
initialized = initialize()
|
|
|
|
JsonRpcProvider(client: awaitClient(), subscriptions: awaitSubscriptions())
|
2022-01-18 11:10:20 +00:00
|
|
|
|
2022-01-18 13:24:46 +00:00
|
|
|
proc send*(provider: JsonRpcProvider,
|
|
|
|
call: string,
|
2022-01-25 14:05:54 +00:00
|
|
|
arguments: seq[JsonNode] = @[]): Future[JsonNode] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.call(call, %arguments)
|
2022-01-18 13:24:46 +00:00
|
|
|
|
2022-01-18 11:10:20 +00:00
|
|
|
proc listAccounts*(provider: JsonRpcProvider): Future[seq[Address]] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_accounts()
|
2022-01-18 13:26:41 +00:00
|
|
|
|
2022-01-20 13:39:37 +00:00
|
|
|
proc getSigner*(provider: JsonRpcProvider): JsonRpcSigner =
|
|
|
|
JsonRpcSigner(provider: provider)
|
|
|
|
|
|
|
|
proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
|
|
|
JsonRpcSigner(provider: provider, address: some address)
|
|
|
|
|
2022-01-18 13:26:41 +00:00
|
|
|
method getBlockNumber*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_blockNumber()
|
2022-01-20 11:56:18 +00:00
|
|
|
|
2022-03-16 13:02:44 +00:00
|
|
|
method getBlock*(provider: JsonRpcProvider,
|
|
|
|
tag: BlockTag): Future[?Block] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_getBlockByNumber(tag, false)
|
2022-03-16 13:02:44 +00:00
|
|
|
|
2022-01-20 11:56:18 +00:00
|
|
|
method call*(provider: JsonRpcProvider,
|
2022-04-10 20:21:59 +00:00
|
|
|
tx: Transaction,
|
|
|
|
blockTag = BlockTag.latest): Future[seq[byte]] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_call(tx, blockTag)
|
2022-01-20 13:39:37 +00:00
|
|
|
|
2022-01-24 11:12:52 +00:00
|
|
|
method getGasPrice*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
2023-03-09 09:58:54 +00:00
|
|
|
return await client.eth_gasPrice()
|
2022-01-24 11:12:52 +00:00
|
|
|
|
2022-01-24 11:14:31 +00:00
|
|
|
method getTransactionCount*(provider: JsonRpcProvider,
|
|
|
|
address: Address,
|
|
|
|
blockTag = BlockTag.latest):
|
|
|
|
Future[UInt256] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_getTransactionCount(address, blockTag)
|
2022-05-18 13:14:39 +00:00
|
|
|
|
2022-05-17 02:34:22 +00:00
|
|
|
method getTransactionReceipt*(provider: JsonRpcProvider,
|
2023-07-20 05:51:28 +00:00
|
|
|
txHash: TransactionHash):
|
|
|
|
Future[?TransactionReceipt] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_getTransactionReceipt(txHash)
|
2022-01-24 11:14:31 +00:00
|
|
|
|
2023-07-20 05:51:28 +00:00
|
|
|
method getLogs*(provider: JsonRpcProvider,
|
|
|
|
filter: EventFilter):
|
|
|
|
Future[seq[Log]] {.async.} =
|
|
|
|
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).catch:
|
|
|
|
logs.add log
|
|
|
|
|
|
|
|
return logs
|
|
|
|
|
2022-01-24 13:40:47 +00:00
|
|
|
method estimateGas*(provider: JsonRpcProvider,
|
|
|
|
transaction: Transaction): Future[UInt256] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
return await client.eth_estimateGas(transaction)
|
2022-01-24 13:40:47 +00:00
|
|
|
|
2022-01-24 16:29:25 +00:00
|
|
|
method getChainId*(provider: JsonRpcProvider): Future[UInt256] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
try:
|
|
|
|
return await client.eth_chainId()
|
|
|
|
except CatchableError:
|
|
|
|
return parse(await client.net_version(), UInt256)
|
2022-01-24 16:29:25 +00:00
|
|
|
|
2022-07-14 09:45:37 +00:00
|
|
|
method sendTransaction*(provider: JsonRpcProvider, rawTransaction: seq[byte]): Future[TransactionResponse] {.async.} =
|
2022-07-12 18:24:52 +00:00
|
|
|
convertError:
|
|
|
|
let
|
|
|
|
client = await provider.client
|
|
|
|
hash = await client.eth_sendRawTransaction(rawTransaction)
|
2022-07-14 08:22:54 +00:00
|
|
|
|
2022-07-12 18:24:52 +00:00
|
|
|
return TransactionResponse(hash: hash, provider: provider)
|
|
|
|
|
2022-02-02 15:56:37 +00:00
|
|
|
method subscribe*(provider: JsonRpcProvider,
|
2023-07-20 05:51:28 +00:00
|
|
|
filter: EventFilter,
|
2023-06-27 09:47:59 +00:00
|
|
|
onLog: LogHandler):
|
2022-02-02 15:56:37 +00:00
|
|
|
Future[Subscription] {.async.} =
|
2023-06-27 09:47:59 +00:00
|
|
|
convertError:
|
|
|
|
let subscriptions = await provider.subscriptions
|
2023-06-28 09:02:21 +00:00
|
|
|
let id = await subscriptions.subscribeLogs(filter, onLog)
|
|
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
2022-05-16 12:40:30 +00:00
|
|
|
|
2022-05-16 12:51:39 +00:00
|
|
|
method subscribe*(provider: JsonRpcProvider,
|
2023-06-27 09:47:59 +00:00
|
|
|
onBlock: BlockHandler):
|
2022-05-16 12:51:39 +00:00
|
|
|
Future[Subscription] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
2023-06-27 09:47:59 +00:00
|
|
|
let subscriptions = await provider.subscriptions
|
2023-06-28 09:02:21 +00:00
|
|
|
let id = await subscriptions.subscribeBlocks(onBlock)
|
|
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
|
|
|
|
method unsubscribe(subscription: JsonRpcSubscription) {.async.} =
|
|
|
|
convertError:
|
|
|
|
let subscriptions = subscription.subscriptions
|
|
|
|
let id = subscription.id
|
|
|
|
await subscriptions.unsubscribe(id)
|
2022-02-02 15:56:37 +00:00
|
|
|
|
2023-06-27 14:40:29 +00:00
|
|
|
method close*(provider: JsonRpcProvider) {.async.} =
|
|
|
|
convertError:
|
|
|
|
let client = await provider.client
|
|
|
|
let subscriptions = await provider.subscriptions
|
|
|
|
await subscriptions.close()
|
|
|
|
await client.close()
|
|
|
|
|
2022-01-20 13:39:37 +00:00
|
|
|
# Signer
|
|
|
|
|
2022-01-24 11:12:52 +00:00
|
|
|
method provider*(signer: JsonRpcSigner): Provider =
|
|
|
|
signer.provider
|
|
|
|
|
2022-01-20 13:39:37 +00:00
|
|
|
method getAddress*(signer: JsonRpcSigner): Future[Address] {.async.} =
|
|
|
|
if address =? signer.address:
|
|
|
|
return address
|
|
|
|
|
|
|
|
let accounts = await signer.provider.listAccounts()
|
|
|
|
if accounts.len > 0:
|
|
|
|
return accounts[0]
|
|
|
|
|
|
|
|
raiseProviderError "no address found"
|
2022-01-25 16:17:43 +00:00
|
|
|
|
2022-01-26 10:21:28 +00:00
|
|
|
method signMessage*(signer: JsonRpcSigner,
|
|
|
|
message: seq[byte]): Future[seq[byte]] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
|
|
|
let client = await signer.provider.client
|
|
|
|
let address = await signer.getAddress()
|
|
|
|
return await client.eth_sign(address, message)
|
2022-01-26 10:21:28 +00:00
|
|
|
|
2022-01-25 16:17:43 +00:00
|
|
|
method sendTransaction*(signer: JsonRpcSigner,
|
2022-05-17 02:34:22 +00:00
|
|
|
transaction: Transaction): Future[TransactionResponse] {.async.} =
|
2022-06-29 13:13:25 +00:00
|
|
|
convertError:
|
2023-09-14 23:54:08 +00:00
|
|
|
signer.updateNonce(transaction.nonce)
|
2022-06-29 13:13:25 +00:00
|
|
|
let
|
|
|
|
client = await signer.provider.client
|
|
|
|
hash = await client.eth_sendTransaction(transaction)
|
2022-05-17 02:34:22 +00:00
|
|
|
|
2022-06-29 13:13:25 +00:00
|
|
|
return TransactionResponse(hash: hash, provider: signer.provider)
|