2023-12-06 12:59:24 +00:00
|
|
|
# nim-web3
|
|
|
|
# Copyright (c) 2019-2023 Status Research & Development GmbH
|
|
|
|
# Licensed under either of
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
# at your option.
|
|
|
|
# This file may not be copied, modified, or distributed except according to
|
|
|
|
# those terms.
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
import
|
2024-01-03 14:54:30 +00:00
|
|
|
std/[options, json, tables, uri],
|
2023-12-13 23:56:54 +00:00
|
|
|
stint, httputils, chronos,
|
2024-01-03 14:54:30 +00:00
|
|
|
json_rpc/[rpcclient, jsonmarshal],
|
|
|
|
eth/keys,
|
2023-06-04 05:39:28 +00:00
|
|
|
chronos/apps/http/httpclient,
|
2024-01-03 14:54:30 +00:00
|
|
|
web3/[eth_api_types, conversions, transaction_signing, encoding, contract_dsl],
|
|
|
|
web3/eth_api
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
from eth/common/eth_types import ChainId
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2023-12-13 06:23:31 +00:00
|
|
|
export UInt256, Int256, Uint128, Int128, ChainId
|
2024-01-03 14:54:30 +00:00
|
|
|
export
|
|
|
|
eth_api_types,
|
|
|
|
conversions,
|
|
|
|
encoding,
|
|
|
|
contract_dsl,
|
|
|
|
HttpClientFlag,
|
|
|
|
HttpClientFlags,
|
|
|
|
eth_api
|
2018-12-21 17:27:09 +00:00
|
|
|
|
|
|
|
type
|
2019-06-12 16:46:29 +00:00
|
|
|
Web3* = ref object
|
|
|
|
provider*: RpcClient
|
|
|
|
subscriptions*: Table[string, Subscription]
|
2019-08-05 20:08:18 +00:00
|
|
|
defaultAccount*: Address
|
2020-06-21 19:57:43 +00:00
|
|
|
privateKey*: Option[PrivateKey]
|
2023-12-06 12:59:24 +00:00
|
|
|
lastKnownNonce*: Option[Quantity]
|
2023-10-05 10:37:04 +00:00
|
|
|
onDisconnect*: proc() {.gcsafe, raises: [].}
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
Web3SenderImpl = ref object
|
2019-06-12 16:46:29 +00:00
|
|
|
web3*: Web3
|
2019-08-05 20:08:18 +00:00
|
|
|
contractAddress*: Address
|
2019-01-10 11:50:51 +00:00
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
Sender*[T] = ContractInstance[T, Web3SenderImpl]
|
2019-01-22 15:08:24 +00:00
|
|
|
|
2023-10-05 10:37:04 +00:00
|
|
|
SubscriptionEventHandler* = proc (j: JsonNode) {.gcsafe, raises: [].}
|
|
|
|
SubscriptionErrorHandler* = proc (err: CatchableError) {.gcsafe, raises: [].}
|
2020-06-26 15:30:55 +00:00
|
|
|
|
2023-10-05 10:37:04 +00:00
|
|
|
BlockHeaderHandler* = proc (b: BlockHeader) {.gcsafe, raises: [].}
|
2020-06-26 15:30:55 +00:00
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
Subscription* = ref object
|
|
|
|
id*: string
|
|
|
|
web3*: Web3
|
2020-06-26 15:30:55 +00:00
|
|
|
eventHandler*: SubscriptionEventHandler
|
|
|
|
errorHandler*: SubscriptionErrorHandler
|
2019-07-02 13:55:39 +00:00
|
|
|
pendingEvents: seq[JsonNode]
|
|
|
|
historicalEventsProcessed: bool
|
|
|
|
removed: bool
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
proc handleSubscriptionNotification(w: Web3, params: JsonNode) =
|
|
|
|
let s = w.subscriptions.getOrDefault(params{"subscription"}.getStr())
|
2019-07-02 13:55:39 +00:00
|
|
|
if not s.isNil and not s.removed:
|
|
|
|
if s.historicalEventsProcessed:
|
2024-01-03 14:54:30 +00:00
|
|
|
s.eventHandler(params{"result"})
|
2019-07-02 13:55:39 +00:00
|
|
|
else:
|
2024-01-03 14:54:30 +00:00
|
|
|
s.pendingEvents.add(params)
|
|
|
|
|
|
|
|
template `or`(a: JsonNode, b: typed): JsonNode =
|
|
|
|
if a.isNil: b else: a
|
2019-06-12 16:46:29 +00:00
|
|
|
|
|
|
|
proc newWeb3*(provider: RpcClient): Web3 =
|
|
|
|
result = Web3(provider: provider)
|
|
|
|
result.subscriptions = initTable[string, Subscription]()
|
2024-01-03 14:54:30 +00:00
|
|
|
let w3 = result
|
|
|
|
|
|
|
|
provider.onProcessMessage = proc(client: RpcClient, line: string):
|
|
|
|
Result[bool, string] {.gcsafe, raises: [].} =
|
|
|
|
try:
|
|
|
|
let node = JrpcConv.decode(line, JsonNode)
|
|
|
|
if "method" notin node:
|
|
|
|
# fallback to regular onProcessMessage
|
|
|
|
return ok(true)
|
|
|
|
|
|
|
|
# This could be subscription notification
|
|
|
|
let name = node["method"].getStr()
|
|
|
|
if name == "eth_subscription":
|
|
|
|
let params = node{"params"} or newJArray()
|
|
|
|
w3.handleSubscriptionNotification(params)
|
|
|
|
|
|
|
|
# don't fallback, just quit onProcessMessage
|
|
|
|
return ok(false)
|
|
|
|
except CatchableError as exc:
|
|
|
|
return err(exc.msg)
|
2019-06-12 16:46:29 +00:00
|
|
|
|
2022-03-04 19:14:42 +00:00
|
|
|
proc newWeb3*(
|
2023-05-04 23:26:27 +00:00
|
|
|
uri: string,
|
|
|
|
getHeaders: GetJsonRpcRequestHeaders = nil,
|
|
|
|
httpFlags: HttpClientFlags = {}):
|
2022-03-04 19:14:42 +00:00
|
|
|
Future[Web3] {.async.} =
|
2019-07-31 09:16:35 +00:00
|
|
|
let u = parseUri(uri)
|
|
|
|
var provider: RpcClient
|
|
|
|
case u.scheme
|
|
|
|
of "http", "https":
|
2023-05-04 23:26:27 +00:00
|
|
|
let p = newRpcHttpClient(getHeaders = getHeaders,
|
|
|
|
flags = httpFlags)
|
2019-07-31 09:16:35 +00:00
|
|
|
await p.connect(uri)
|
|
|
|
provider = p
|
|
|
|
of "ws", "wss":
|
2022-03-04 19:14:42 +00:00
|
|
|
let p = newRpcWebSocketClient(getHeaders = getHeaders)
|
2019-07-31 09:16:35 +00:00
|
|
|
await p.connect(uri)
|
|
|
|
provider = p
|
|
|
|
else:
|
|
|
|
raise newException(CatchableError, "Unknown web3 url scheme")
|
|
|
|
result = newWeb3(provider)
|
2019-11-22 12:41:12 +00:00
|
|
|
let r = result
|
|
|
|
provider.onDisconnect = proc() =
|
|
|
|
r.subscriptions.clear()
|
|
|
|
if not r.onDisconnect.isNil:
|
|
|
|
r.onDisconnect()
|
2019-07-31 09:16:35 +00:00
|
|
|
|
2019-10-22 15:57:59 +00:00
|
|
|
proc close*(web3: Web3): Future[void] = web3.provider.close()
|
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
proc getHistoricalEvents(s: Subscription, options: FilterOptions) {.async.} =
|
2019-07-02 13:55:39 +00:00
|
|
|
try:
|
2024-01-03 14:54:30 +00:00
|
|
|
let logs = await s.web3.provider.eth_getJsonLogs(options)
|
2019-07-02 13:55:39 +00:00
|
|
|
for l in logs:
|
|
|
|
if s.removed: break
|
2020-06-26 15:30:55 +00:00
|
|
|
s.eventHandler(l)
|
2019-07-02 13:55:39 +00:00
|
|
|
s.historicalEventsProcessed = true
|
|
|
|
var i = 0
|
|
|
|
while i < s.pendingEvents.len: # Mind reentrancy
|
|
|
|
if s.removed: break
|
2020-06-26 15:30:55 +00:00
|
|
|
s.eventHandler(s.pendingEvents[i])
|
2019-07-02 13:55:39 +00:00
|
|
|
inc i
|
|
|
|
s.pendingEvents = @[]
|
2020-03-11 09:03:09 +00:00
|
|
|
except CatchableError as e:
|
2019-07-02 13:55:39 +00:00
|
|
|
echo "Caught exception in getHistoricalEvents: ", e.msg
|
|
|
|
echo e.getStackTrace()
|
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
proc subscribe*(w: Web3, name: string, options: Option[FilterOptions],
|
2020-06-26 15:30:55 +00:00
|
|
|
eventHandler: SubscriptionEventHandler,
|
|
|
|
errorHandler: SubscriptionErrorHandler): Future[Subscription]
|
|
|
|
{.async.} =
|
|
|
|
## Sets up a new subsciption using the `eth_subscribe` RPC call.
|
|
|
|
##
|
|
|
|
## May raise a `CatchableError` if the subscription is not established.
|
|
|
|
##
|
|
|
|
## Once the subscription is established, the `eventHandler` callback
|
|
|
|
## will be executed for each event of interest.
|
|
|
|
##
|
|
|
|
## In case of any errors or illegal behavior of the remote RPC node,
|
|
|
|
## the `errorHandler` will be executed with relevant information about
|
|
|
|
## the error.
|
2020-09-24 12:56:26 +00:00
|
|
|
|
|
|
|
# Don't send an empty `{}` object as an extra argument if there are no options
|
2024-01-03 14:54:30 +00:00
|
|
|
let id = if options.isNone:
|
2020-09-24 12:56:26 +00:00
|
|
|
await w.provider.eth_subscribe(name)
|
|
|
|
else:
|
2024-01-03 14:54:30 +00:00
|
|
|
await w.provider.eth_subscribe(name, options.get)
|
2020-09-24 12:56:26 +00:00
|
|
|
|
2020-06-26 15:30:55 +00:00
|
|
|
result = Subscription(id: id,
|
|
|
|
web3: w,
|
|
|
|
eventHandler: eventHandler,
|
|
|
|
errorHandler: errorHandler)
|
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
w.subscriptions[id] = result
|
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
proc subscribeForLogs*(w: Web3, options: FilterOptions,
|
2020-06-26 15:30:55 +00:00
|
|
|
logsHandler: SubscriptionEventHandler,
|
2020-06-27 11:50:53 +00:00
|
|
|
errorHandler: SubscriptionErrorHandler,
|
|
|
|
withHistoricEvents = true): Future[Subscription]
|
2020-06-26 15:30:55 +00:00
|
|
|
{.async.} =
|
2024-01-03 14:54:30 +00:00
|
|
|
result = await subscribe(w, "logs", some(options), logsHandler, errorHandler)
|
2020-06-27 11:50:53 +00:00
|
|
|
if withHistoricEvents:
|
|
|
|
discard getHistoricalEvents(result, options)
|
|
|
|
else:
|
|
|
|
result.historicalEventsProcessed = true
|
2019-07-02 13:55:39 +00:00
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
proc addAddressAndSignatureToOptions(options: FilterOptions, address: Address, topic: Topic): FilterOptions =
|
|
|
|
result = options
|
|
|
|
if result.address.kind == slkNull:
|
|
|
|
result.address = AddressOrList(kind: slkSingle, single: address)
|
|
|
|
result.topics.insert(TopicOrList(kind: slkSingle, single: topic), 0)
|
|
|
|
|
|
|
|
proc subscribeForLogs*(s: Web3SenderImpl, options: FilterOptions,
|
|
|
|
topic: Topic,
|
2023-12-13 05:26:04 +00:00
|
|
|
logsHandler: SubscriptionEventHandler,
|
|
|
|
errorHandler: SubscriptionErrorHandler,
|
|
|
|
withHistoricEvents = true): Future[Subscription] =
|
|
|
|
let options = addAddressAndSignatureToOptions(options, s.contractAddress, topic)
|
|
|
|
s.web3.subscribeForLogs(options, logsHandler, errorHandler, withHistoricEvents)
|
|
|
|
|
2020-09-24 12:56:26 +00:00
|
|
|
proc subscribeForBlockHeaders*(w: Web3,
|
2023-10-05 10:37:04 +00:00
|
|
|
blockHeadersCallback: proc(b: BlockHeader) {.gcsafe, raises: [].},
|
2020-06-26 15:30:55 +00:00
|
|
|
errorHandler: SubscriptionErrorHandler): Future[Subscription]
|
|
|
|
{.async.} =
|
2023-10-05 10:37:04 +00:00
|
|
|
proc eventHandler(json: JsonNode) {.gcsafe, raises: [].} =
|
2024-01-03 14:54:30 +00:00
|
|
|
|
2021-10-04 15:41:19 +00:00
|
|
|
try:
|
2024-01-03 14:54:30 +00:00
|
|
|
let blk = JrpcConv.decode($json, BlockHeader)
|
2021-10-04 15:41:19 +00:00
|
|
|
blockHeadersCallback(blk)
|
|
|
|
except CatchableError as err:
|
|
|
|
errorHandler(err[])
|
2020-06-26 15:30:55 +00:00
|
|
|
|
2020-09-24 12:56:26 +00:00
|
|
|
# `nil` options so that we skip sending an empty `{}` object as an extra argument
|
|
|
|
# to geth for `newHeads`: https://github.com/ethereum/go-ethereum/issues/21588
|
2024-01-03 14:54:30 +00:00
|
|
|
result = await subscribe(w, "newHeads", none(FilterOptions), eventHandler, errorHandler)
|
2020-06-26 15:30:55 +00:00
|
|
|
result.historicalEventsProcessed = true
|
|
|
|
|
2019-06-12 16:46:29 +00:00
|
|
|
proc unsubscribe*(s: Subscription): Future[void] {.async.} =
|
2019-07-02 13:55:39 +00:00
|
|
|
s.web3.subscriptions.del(s.id)
|
|
|
|
s.removed = true
|
2019-06-12 16:46:29 +00:00
|
|
|
discard await s.web3.provider.eth_unsubscribe(s.id)
|
2018-12-21 17:27:09 +00:00
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
proc getJsonLogs(s: Web3SenderImpl, topic: Topic,
|
|
|
|
fromBlock = none(RtBlockIdentifier),
|
|
|
|
toBlock = none(RtBlockIdentifier),
|
2020-03-07 21:46:10 +00:00
|
|
|
blockHash = none(BlockHash)): Future[JsonNode] =
|
2024-01-03 14:54:30 +00:00
|
|
|
|
|
|
|
var options = FilterOptions(
|
|
|
|
address: AddressOrList(kind: slkSingle, single: s.contractAddress),
|
|
|
|
topics: @[TopicOrList(kind: slkSingle, single: topic)],
|
|
|
|
)
|
|
|
|
|
2020-03-07 21:46:10 +00:00
|
|
|
if blockHash.isSome:
|
|
|
|
doAssert fromBlock.isNone and toBlock.isNone
|
2024-01-03 14:54:30 +00:00
|
|
|
options.blockHash = blockHash
|
2020-03-07 21:46:10 +00:00
|
|
|
else:
|
2024-01-03 14:54:30 +00:00
|
|
|
options.fromBlock = fromBlock
|
|
|
|
options.toBlock = toBlock
|
2020-03-07 21:46:10 +00:00
|
|
|
|
2024-01-03 14:54:30 +00:00
|
|
|
# TODO: optimize it instead of double conversion
|
|
|
|
s.web3.provider.eth_getJsonLogs(options)
|
2020-03-07 21:46:10 +00:00
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
proc getJsonLogs*[TContract](s: Sender[TContract],
|
|
|
|
EventName: type,
|
2024-01-03 14:54:30 +00:00
|
|
|
fromBlock= none(RtBlockIdentifier),
|
|
|
|
toBlock = none(RtBlockIdentifier),
|
2023-12-13 05:26:04 +00:00
|
|
|
blockHash = none(BlockHash)): Future[JsonNode] {.inline.} =
|
|
|
|
mixin eventTopic
|
|
|
|
getJsonLogs(s.sender, eventTopic(EventName))
|
|
|
|
|
2023-12-06 12:59:24 +00:00
|
|
|
proc nextNonce*(web3: Web3): Future[Quantity] {.async.} =
|
2020-07-28 19:54:37 +00:00
|
|
|
if web3.lastKnownNonce.isSome:
|
|
|
|
inc web3.lastKnownNonce.get
|
|
|
|
return web3.lastKnownNonce.get
|
|
|
|
else:
|
|
|
|
let fromAddress = web3.privateKey.get().toPublicKey().toCanonicalAddress.Address
|
2023-12-06 12:59:24 +00:00
|
|
|
result = await web3.provider.eth_getTransactionCount(fromAddress, "latest")
|
2020-07-28 19:54:37 +00:00
|
|
|
web3.lastKnownNonce = some result
|
|
|
|
|
2019-08-05 20:08:18 +00:00
|
|
|
proc send*(web3: Web3, c: EthSend): Future[TxHash] {.async.} =
|
2020-06-21 19:57:43 +00:00
|
|
|
if web3.privateKey.isSome():
|
2019-08-05 20:08:18 +00:00
|
|
|
var cc = c
|
2020-07-28 19:54:37 +00:00
|
|
|
if cc.nonce.isNone:
|
|
|
|
cc.nonce = some(await web3.nextNonce())
|
2023-12-06 12:59:24 +00:00
|
|
|
let t = encodeTransaction(cc, web3.privateKey.get())
|
2019-08-05 20:08:18 +00:00
|
|
|
return await web3.provider.eth_sendRawTransaction(t)
|
|
|
|
else:
|
|
|
|
return await web3.provider.eth_sendTransaction(c)
|
|
|
|
|
2023-12-13 06:23:31 +00:00
|
|
|
proc send*(web3: Web3, c: EthSend, chainId: ChainId): Future[TxHash] {.async.} =
|
|
|
|
doAssert(web3.privateKey.isSome())
|
|
|
|
var cc = c
|
|
|
|
if cc.nonce.isNone:
|
|
|
|
cc.nonce = some(await web3.nextNonce())
|
|
|
|
let t = encodeTransaction(cc, web3.privateKey.get(), chainId)
|
|
|
|
return await web3.provider.eth_sendRawTransaction(t)
|
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
proc sendData(sender: Web3SenderImpl,
|
|
|
|
data: seq[byte],
|
|
|
|
value: UInt256,
|
|
|
|
gas: uint64,
|
2023-12-13 06:23:31 +00:00
|
|
|
gasPrice: int,
|
|
|
|
chainId = none(ChainId)): Future[TxHash] {.async.} =
|
2020-07-28 19:54:37 +00:00
|
|
|
let
|
2023-12-13 05:26:04 +00:00
|
|
|
web3 = sender.web3
|
2023-12-06 12:59:24 +00:00
|
|
|
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice.Quantity)
|
|
|
|
else: none(Quantity)
|
2020-07-28 19:54:37 +00:00
|
|
|
nonce = if web3.privateKey.isSome(): some(await web3.nextNonce())
|
2023-12-06 12:59:24 +00:00
|
|
|
else: none(Quantity)
|
2020-07-28 19:54:37 +00:00
|
|
|
|
|
|
|
cc = EthSend(
|
2023-12-13 05:26:04 +00:00
|
|
|
data: data,
|
2023-12-06 12:59:24 +00:00
|
|
|
`from`: web3.defaultAccount,
|
2023-12-13 05:26:04 +00:00
|
|
|
to: some(sender.contractAddress),
|
2020-07-28 19:54:37 +00:00
|
|
|
gas: some(Quantity(gas)),
|
|
|
|
value: some(value),
|
|
|
|
nonce: nonce,
|
2023-12-06 12:59:24 +00:00
|
|
|
gasPrice: gasPrice,
|
|
|
|
)
|
2020-07-28 19:54:37 +00:00
|
|
|
|
2023-12-13 06:23:31 +00:00
|
|
|
if chainId.isNone:
|
|
|
|
return await web3.send(cc)
|
|
|
|
else:
|
|
|
|
return await web3.send(cc, chainId.get)
|
2019-08-05 20:08:18 +00:00
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
|
|
|
|
value = 0.u256,
|
|
|
|
gas = 3000000'u64,
|
|
|
|
gasPrice = 0): Future[TxHash] =
|
|
|
|
sendData(c.sender, c.data, value, gas, gasPrice)
|
|
|
|
|
2023-12-13 06:23:31 +00:00
|
|
|
proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
|
|
|
|
chainId: ChainId,
|
|
|
|
value = 0.u256,
|
|
|
|
gas = 3000000'u64,
|
|
|
|
gasPrice = 0): Future[TxHash] =
|
|
|
|
sendData(c.sender, c.data, value, gas, gasPrice, some(chainId))
|
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
proc call*[T](c: ContractInvocation[T, Web3SenderImpl],
|
2020-03-05 23:56:23 +00:00
|
|
|
value = 0.u256,
|
|
|
|
gas = 3000000'u64,
|
|
|
|
blockNumber = high(uint64)): Future[T] {.async.} =
|
2023-12-13 05:26:04 +00:00
|
|
|
let web3 = c.sender.web3
|
2019-08-05 20:08:18 +00:00
|
|
|
var cc: EthCall
|
2023-12-06 12:59:24 +00:00
|
|
|
cc.data = some(c.data)
|
2023-12-13 05:26:04 +00:00
|
|
|
cc.source = some(web3.defaultAccount)
|
|
|
|
cc.to = some(c.sender.contractAddress)
|
2019-08-05 20:08:18 +00:00
|
|
|
cc.gas = some(Quantity(gas))
|
|
|
|
cc.value = some(value)
|
2023-12-06 12:59:24 +00:00
|
|
|
let response =
|
2020-03-05 23:56:23 +00:00
|
|
|
if blockNumber != high(uint64):
|
2024-01-03 14:54:30 +00:00
|
|
|
await web3.provider.eth_call(cc, blockId(blockNumber))
|
2020-03-05 23:56:23 +00:00
|
|
|
else:
|
2023-12-13 05:26:04 +00:00
|
|
|
await web3.provider.eth_call(cc, "latest")
|
2020-03-05 23:56:23 +00:00
|
|
|
|
|
|
|
if response.len > 0:
|
|
|
|
var res: T
|
2023-12-13 05:26:04 +00:00
|
|
|
discard decode(response, 0, 0, res)
|
2020-03-05 23:56:23 +00:00
|
|
|
return res
|
|
|
|
else:
|
|
|
|
raise newException(CatchableError, "No response from the Web3 provider")
|
2019-08-05 20:08:18 +00:00
|
|
|
|
2019-10-30 13:05:44 +00:00
|
|
|
proc getMinedTransactionReceipt*(web3: Web3, tx: TxHash): Future[ReceiptObject] {.async.} =
|
|
|
|
## Returns the receipt for the transaction. Waits for it to be mined if necessary.
|
|
|
|
# TODO: Potentially more optimal solution is to subscribe and wait for appropriate
|
|
|
|
# notification. Now we're just polling every 500ms which should be ok for most cases.
|
2023-12-06 12:59:24 +00:00
|
|
|
var r: ReceiptObject
|
|
|
|
while r.isNil:
|
2019-10-30 13:05:44 +00:00
|
|
|
r = await web3.provider.eth_getTransactionReceipt(tx)
|
2023-12-06 12:59:24 +00:00
|
|
|
if r.isNil:
|
2019-10-30 13:05:44 +00:00
|
|
|
await sleepAsync(500.milliseconds)
|
2023-12-06 12:59:24 +00:00
|
|
|
result = r
|
2019-10-30 13:05:44 +00:00
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
proc exec*[T](c: ContractInvocation[T, Web3SenderImpl], value = 0.u256, gas = 3000000'u64): Future[T] {.async.} =
|
2019-08-05 20:08:18 +00:00
|
|
|
let h = await c.send(value, gas)
|
2019-10-30 13:05:44 +00:00
|
|
|
let receipt = await c.web3.getMinedTransactionReceipt(h)
|
2019-08-05 20:08:18 +00:00
|
|
|
|
|
|
|
# TODO: decode result from receipt
|
|
|
|
|
|
|
|
|
2018-12-21 17:27:09 +00:00
|
|
|
# This call will generate the `cc.data` part to call that contract method in the code below
|
2019-01-04 17:28:43 +00:00
|
|
|
#sendCoin(fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"), 600.to(Stuint[256]))
|
2018-12-21 17:27:09 +00:00
|
|
|
|
|
|
|
# Set up a JsonRPC call to send a transaction
|
|
|
|
# The idea here is to let the Web3 object contain the RPC calls, then allow the
|
|
|
|
# above DSL to create helpers to create the EthSend object and perform the
|
|
|
|
# transaction. The current idea is to make all this reduce to something like:
|
|
|
|
# var
|
|
|
|
# w3 = initWeb3("127.0.0.1", 8545)
|
|
|
|
# myContract = contract:
|
|
|
|
# <DSL>
|
|
|
|
# myContract.sender("0x780bc7b4055941c2cb0ee10510e3fc837eb093c1").sendCoin(
|
|
|
|
# fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"),
|
|
|
|
# 600.to(Stuint[256])
|
|
|
|
# )
|
|
|
|
# If the address of the contract on the chain should be part of the DSL or
|
|
|
|
# dynamically registered is still not decided.
|
2019-01-04 17:28:43 +00:00
|
|
|
#var cc: EthSend
|
|
|
|
#cc.source = [0x78.byte, 0x0b, 0xc7, 0xb4, 0x05, 0x59, 0x41, 0xc2, 0xcb, 0x0e, 0xe1, 0x05, 0x10, 0xe3, 0xfc, 0x83, 0x7e, 0xb0, 0x93, 0xc1]
|
|
|
|
#cc.to = some([0x0a.byte, 0x78, 0xc0, 0x8F, 0x31, 0x4E, 0xB2, 0x5A, 0x35, 0x1B, 0xfB, 0xA9, 0x03,0x21, 0xa6, 0x96, 0x04, 0x74, 0xbD, 0x79])
|
|
|
|
#cc.data = "0x90b98a11000000000000000000000000e375b6fb6d0bf0d86707884f3952fee3977251FE0000000000000000000000000000000000000000000000000000000000000258"
|
|
|
|
|
|
|
|
#var w3 = initWeb3("127.0.0.1", 8545)
|
|
|
|
#let response = waitFor w3.eth.eth_sendTransaction(cc)
|
|
|
|
#echo response
|
|
|
|
|
2019-08-05 20:08:18 +00:00
|
|
|
proc contractSender*(web3: Web3, T: typedesc, toAddress: Address): Sender[T] =
|
2023-12-13 05:26:04 +00:00
|
|
|
Sender[T](sender: Web3SenderImpl(web3: web3, contractAddress: toAddress))
|
2019-01-10 11:50:51 +00:00
|
|
|
|
2020-06-19 16:42:55 +00:00
|
|
|
proc isDeployed*(s: Sender, atBlock: RtBlockIdentifier): Future[bool] {.async.} =
|
|
|
|
let
|
|
|
|
codeFut = case atBlock.kind
|
2020-06-22 12:27:47 +00:00
|
|
|
of bidNumber:
|
2023-12-13 05:26:04 +00:00
|
|
|
s.sender.web3.provider.eth_getCode(s.contractAddress, atBlock.number)
|
2020-06-22 12:27:47 +00:00
|
|
|
of bidAlias:
|
2023-12-13 05:26:04 +00:00
|
|
|
s.sender.web3.provider.eth_getCode(s.contractAddress, atBlock.alias)
|
2020-06-19 16:42:55 +00:00
|
|
|
code = await codeFut
|
|
|
|
|
|
|
|
# TODO: Check that all methods of the contract are present by
|
|
|
|
# looking for their ABI signatures within the code:
|
|
|
|
# https://ethereum.stackexchange.com/questions/11856/how-to-detect-from-web3-if-method-exists-on-a-deployed-contract
|
|
|
|
return code.len > 0
|
|
|
|
|
2023-12-13 05:26:04 +00:00
|
|
|
proc subscribe*[TContract](s: Sender[TContract], t: typedesc, cb: proc): Future[Subscription] {.inline.} =
|
2024-01-03 14:54:30 +00:00
|
|
|
subscribe(s, t, FilterOptions(), cb, SubscriptionErrorHandler nil)
|