feat: encode contract calls

This commit is contained in:
emizzle 2020-07-21 16:57:57 +10:00 committed by Iuri Matias
parent e484c41291
commit b6884a5170
6 changed files with 296 additions and 194 deletions

View File

@ -1,11 +1,11 @@
import eventemitter, json, strutils, sequtils, tables, chronicles, sugar
import libstatus/settings as status_settings
import libstatus/chat as status_chat
import libstatus/stickers as status_stickers
import libstatus/types
import profile/profile
import chat/[chat, message]
import ../signals/messages
import ../signals/types as signal_types
import ens
import eth/common/eth_types
@ -103,14 +103,20 @@ proc getPurchasedStickerPacks*(self: ChatModel, address: EthAddress): seq[int] =
return self.purchasedStickerPacks
try:
var balance = status_stickers.getBalance(address)
# balance = 2 # hardcode to test support of purchased sticker packs, because buying sticker packs is proving very difficult on testnet
var tokenIds = toSeq[0..<balance].map(idx => status_stickers.tokenOfOwnerByIndex(address, idx))
# tokenIds = @[1, 2] # hardcode to test support of purchased sticker packs
self.purchasedStickerPacks = tokenIds.map(tokenId => status_stickers.getPackIdFromTokenId(tokenId))
# Buy the "Toozeman" sticker pack on testnet
# Ensure there is enough STT and ETHro in the account first before uncommenting.
# STT faucet: simpledapp.eth
# NOTE: don't forget to update your account password!
# if status_settings.getCurrentNetwork() == Network.Testnet:
# debugEcho ">>> [getPurchasedStickerPacks] buy Toozeman sticker pack, response/txid: ", status_stickers.buyPack(1.u256, address, "20000000000000000000".u256, "<your password here>")
var
balance = status_stickers.getBalance(address)
tokenIds = toSeq[0..<balance].map(idx => status_stickers.tokenOfOwnerByIndex(address, idx.u256))
self.purchasedStickerPacks = tokenIds.map(tokenId => status_stickers.getPackIdFromTokenId(tokenId.u256))
result = self.purchasedStickerPacks
except RpcException:
error "Error in getPurchasedStickerPacks", message = getCurrentExceptionMsg()
error "Error getting purchased sticker packs", message = getCurrentExceptionMsg()
result = @[]
proc getInstalledStickerPacks*(self: ChatModel): Table[int, StickerPack] =
@ -128,7 +134,7 @@ proc getAvailableStickerPacks*(self: ChatModel): Table[int, StickerPack] =
let numPacks = status_stickers.getPackCount()
for i in 0..<numPacks:
try:
let stickerPack = status_stickers.getPackData(i)
let stickerPack = status_stickers.getPackData(i.u256)
self.availableStickerPacks[stickerPack.id] = stickerPack
except:
continue

View File

@ -0,0 +1,185 @@
import sequtils, strformat, sugar, macros, tables, strutils
import eth/common/eth_types, stew/byteutils
type
FixedBytes* [N: static[int]] = distinct array[N, byte]
DynamicBytes* [N: static[int]] = distinct array[N, byte]
EncodeResult* = tuple[dynamic: bool, data: string]
Bool* = distinct Int256 # TODO: implement Bool as Stint[256]?
Encodable* = concept x
encode(x) is EncodeResult
type
GetPackData* = object
packId*: Stuint[256]
PackData* = object
category*: DynamicBytes[32] # bytes4[]
owner*: EthAddress # address
mintable*: bool # bool
timestamp*: Stuint[256] # uint256
price*: Stuint[256] # uint256
contentHash*: DynamicBytes[64] # bytes
BuyToken* = object
packId*: Stuint[256]
address*: EthAddress
price*: Stuint[256]
ApproveAndCall* = object
to*: EthAddress
value*: Stuint[256]
data*: DynamicBytes[100]
Transfer* = object
to*: EthAddress
value*: Stuint[256]
BalanceOf* = object
address*: EthAddress
TokenOfOwnerByIndex* = object
address*: EthAddress
index*: Stuint[256]
TokenPackId* = object
tokenId*: Stuint[256]
TokenUri* = object
tokenId*: Stuint[256]
proc skip0xPrefix*(s: string): int =
if s.len > 1 and s[0] == '0' and s[1] in {'x', 'X'}: 2
else: 0
proc strip0xPrefix*(s: string): string =
let prefixLen = skip0xPrefix(s)
if prefixLen != 0:
s[prefixLen .. ^1]
else:
s
proc fromHexAux*(s: string, result: var openarray[byte]) =
let prefixLen = skip0xPrefix(s)
let meaningfulLen = s.len - prefixLen
let requiredChars = result.len * 2
if meaningfulLen > requiredChars:
let start = s.len - requiredChars
hexToByteArray(s[start .. s.len - 1], result)
elif meaningfulLen == requiredChars:
hexToByteArray(s, result)
else:
raise newException(ValueError, "Short hex string (" & $meaningfulLen & ") for Bytes[" & $result.len & "]")
func fromHex*[N](x: type FixedBytes[N], s: string): FixedBytes[N] {.inline.} =
fromHexAux(s, array[N, byte](result))
func fromHex*[N](x: type DynamicBytes[N], s: string): DynamicBytes[N] {.inline.} =
fromHexAux(s, array[N, byte](result))
func fromHex*(x: type EthAddress, s: string): EthAddress {.inline.} =
fromHexAux(s, array[20, byte](result))
template toHex*[N](x: FixedBytes[N]): string =
toHex(array[N, byte](x))
template toHex*[N](x: DynamicBytes[N]): string =
toHex(array[N, byte](x))
template toHex*(x: EthAddress): string =
toHex(array[20, byte](x))
func encode*[bits: static[int]](x: Stuint[bits]): EncodeResult =
## Encodes a `Stuint` to a textual representation for use in the JsonRPC
## `sendTransaction` call.
(dynamic: false, data: ('0'.repeat((256 - bits) div 4) & x.dumpHex.map(c => c).join("")))
func encode*[bits: static[int]](x: Stint[bits]): EncodeResult =
## Encodes a `Stint` to a textual representation for use in the JsonRPC
## `sendTransaction` call.
(dynamic: false,
data:
if x.isNegative:
'f'.repeat((256 - bits) div 4) & x.dumpHex
else:
'0'.repeat((256 - bits) div 4) & x.dumpHex
)
func fixedEncode(a: openarray[byte]): EncodeResult =
var padding = a.len mod 32
if padding != 0: padding = 32 - padding
result = (dynamic: false, data: cast[string]("00".repeat(padding) & byteutils.toHex(a)))
func encode*[N](b: FixedBytes[N]): EncodeResult = fixedEncode(array[N, byte](b))
func encode*(b: EthAddress): EncodeResult = fixedEncode(array[20, byte](b))
func encodeDynamic(v: openarray[byte]): EncodeResult =
result.dynamic = true
result.data = toHex(v.len, 64).toLower
for y in v:
result.data &= y.toHex.toLower
result.data &= "00".repeat(v.len mod 32)
func encode*[N](x: DynamicBytes[N]): EncodeResult {.inline.} =
encodeDynamic(array[N, byte](x))
func encode*(x: Bool): EncodeResult = encode(Int256(x))
func decode*(input: string, offset: int, to: var Stuint): int =
let meaningfulLen = to.bits div 8 * 2
to = type(to).fromHex(input[offset .. offset + meaningfulLen - 1])
meaningfulLen
func decode*[N](input: string, offset: int, to: var Stint[N]): int =
let meaningfulLen = N div 8 * 2
fromHex(input[offset .. offset + meaningfulLen], to)
meaningfulLen
func decodeFixed(input: string, offset: int, to: var openarray[byte]): int =
let meaningfulLen = to.len * 2
var padding = to.len mod 32
if padding != 0: padding = (32 - padding) * 2
let offset = offset + padding
fromHexAux(input[offset .. offset + meaningfulLen - 1], to)
meaningfulLen + padding
func decode*[N](input: string, offset: int, to: var FixedBytes[N]): int {.inline.} =
decodeFixed(input, offset, array[N, byte](to))
func decode*(input: string, offset: int, to: var EthAddress): int {.inline.} =
decodeFixed(input, offset, array[20, byte](to))
func decodeDynamic(input: string, offset: int, to: var openarray[byte]): int =
var dataOffset, dataLen: UInt256
result = decode(input, offset, dataOffset)
discard decode(input, dataOffset.truncate(int) * 2, dataLen)
# TODO: Check data len, and raise?
let meaningfulLen = to.len * 2
let actualDataOffset = (dataOffset.truncate(int) + 32) * 2
fromHexAux(input[actualDataOffset .. actualDataOffset + meaningfulLen - 1], to)
func decode*[N](input: string, offset: int, to: var DynamicBytes[N]): int {.inline.} =
decodeDynamic(input, offset, array[N, byte](to))
# TODO: Figure out a way to parse a bool as a FixedBytes[N], so that we can allow
# variance in the number of bytes. The current implementation is a very forceful
# way of parsing a bool because it assumes the bool is 32 bytes (64 chars).
func decode*(input: string, offset: int, to: var bool): int {.inline.} =
let val = input[offset..offset+63].parse(Int256)
to = val.truncate(int) == 1
64
func decode*(input: string, offset: int, obj: var object): int =
var offset = offset
for field in fields(obj):
offset += decode(input, offset, field)
func decode*[T](input: string, to: seq[T]): seq[T] =
var count = input[0..64].decode(Stuint)
result = newSeq[T](count)
for i in 0..count:
result[i] = input[i*64 .. (i+1)*64].decode(T)
func decode*[T; I: static int](input: string, to: array[0..I, T]): array[0..I, T] =
for i in 0..I:
result[i] = input[i*64 .. (i+1)*64].decode(T)

View File

@ -1,12 +1,15 @@
import sequtils, strformat, sugar, macros, tables
import sequtils, strformat, sugar, macros, tables, strutils
import eth/common/eth_types, stew/byteutils, nimcrypto
from eth/common/utils import parseAddress
import ./types, ./settings
import ./types, ./settings, ./coder
export
GetPackData, PackData, BuyToken, ApproveAndCall, Transfer, BalanceOf,
TokenOfOwnerByIndex, TokenPackId, TokenUri, DynamicBytes, toHex, fromHex
type Method* = object
name*: string
signature*: string
noPadding*: bool
type Contract* = ref object
name*: string
@ -14,22 +17,6 @@ type Contract* = ref object
address*: EthAddress
methods*: Table[string, Method]
type
FixedBytes* [N: static[int]] = distinct array[N, byte]
DynamicBytes* [N: static[int]] = distinct array[N, byte]
Address* = distinct EthAddress
EncodeResult* = tuple[dynamic: bool, data: string]
# Bool* = distinct Int256 # TODO: implement Bool as FixedBytes[N]?
type PackData* = object
category*: DynamicBytes[32] # bytes4[]
owner*: Address # address
mintable*: bool # bool
timestamp*: Stuint[256] # uint256
price*: Stuint[256] # uint256
contentHash*: DynamicBytes[64] # bytes
proc allContracts(): seq[Contract] = @[
Contract(name: "snt", network: Network.Mainnet, address: parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"),
methods: [
@ -47,13 +34,13 @@ proc allContracts(): seq[Contract] = @[
Contract(name: "stickers", network: Network.Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"),
methods: [
("packCount", Method(signature: "packCount()")),
("getPackData", Method(signature: "getPackData(uint256)", noPadding: true))
("getPackData", Method(signature: "getPackData(uint256)"))
].toTable
),
Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"),
methods: [
("packCount", Method(signature: "packCount()")),
("getPackData", Method(signature: "getPackData(uint256)", noPadding: true))
("getPackData", Method(signature: "getPackData(uint256)"))
].toTable
),
Contract(name: "sticker-market", network: Network.Mainnet, address: parseAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"),
@ -93,7 +80,13 @@ proc allContracts(): seq[Contract] = @[
Contract(name: "kudos", network: Network.Mainnet, address: parseAddress("0x2aea4add166ebf38b63d09a75de1a7b94aa24163"),
methods: [
("tokenOfOwnerByIndex", Method(signature: "tokenOfOwnerByIndex(address,uint256)")),
("tokenURI", Method(signature: "tokenURI(uint256)", noPadding: true))
("tokenURI", Method(signature: "tokenURI(uint256)"))
].toTable
),
Contract(name: "kudos", network: Network.Testnet, address: parseAddress("0xcd520707fc68d153283d518b29ada466f9091ea8"),
methods: [
("tokenOfOwnerByIndex", Method(signature: "tokenOfOwnerByIndex(address,uint256)")),
("tokenURI", Method(signature: "tokenURI(uint256)"))
].toTable
),
Contract(name: "crypto-kitties", network: Network.Mainnet, address: parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d")),
@ -107,132 +100,30 @@ proc getContract*(name: string): Contract =
let network = settings.getCurrentNetwork()
getContract(network, name)
func encode*[bits: static[int]](x: Stuint[bits]): EncodeResult =
## Encodes a `Stuint` to a textual representation for use in the JsonRPC
## `sendTransaction` call.
(dynamic: false, data: ('0'.repeat((256 - bits) div 4) & x.dumpHex.map(c => c)).join(""))
proc encodeMethod(self: Method): string =
let hash = $nimcrypto.keccak256.digest(self.signature)
result = hash[0 .. ^(hash.high - 6)]
if (not self.noPadding):
result = &"{result:0<32}"
($nimcrypto.keccak256.digest(self.signature))[0..<8].toLower
proc encodeParam[T](value: T): string =
# Could possibly simplify this by passing a string value, like so:
# https://github.com/status-im/nimbus/blob/4ade5797ee04dc778641372177e4b3e1851cdb6c/nimbus/config.nim#L304-L324
when T is int:
result = toHex(value, 64)
elif T is EthAddress:
result = value.toHex()
elif T is Stuint:
result = value.encode().data
else:
result = align(value, 64, '0')
proc encodeAbi*(self: Method, obj: object = RootObj()): string =
result = "0x" & self.encodeMethod()
macro encodeAbi*(self: Method, params: varargs[untyped]): untyped =
result = quote do:
"0x" & encodeMethod(`self`)
for param in params:
result = quote do:
`result` & encodeParam(`param`)
# .fields is an iterator, and there's no way to get a count of an iterator
# in nim, so we have to loop and increment a counter
var fieldCount = 0
for i in obj.fields:
fieldCount += 1
var
offset = 32*fieldCount
data = ""
proc skip0xPrefix*(s: string): int =
if s.len > 1 and s[0] == '0' and s[1] in {'x', 'X'}: 2
else: 0
proc strip0xPrefix*(s: string): string =
let prefixLen = skip0xPrefix(s)
if prefixLen != 0:
s[prefixLen .. ^1]
else:
s
proc fromHexAux*(s: string, result: var openarray[byte]) =
let prefixLen = skip0xPrefix(s)
let meaningfulLen = s.len - prefixLen
let requiredChars = result.len * 2
if meaningfulLen > requiredChars:
let start = s.len - requiredChars
hexToByteArray(s[start .. s.len - 1], result)
elif meaningfulLen == requiredChars:
hexToByteArray(s, result)
else:
raise newException(ValueError, "Short hex string (" & $meaningfulLen & ") for Bytes[" & $result.len & "]")
func fromHex*[N](x: type FixedBytes[N], s: string): FixedBytes[N] {.inline.} =
fromHexAux(s, array[N, byte](result))
func fromHex*(x: type Address, s: string): Address {.inline.} =
fromHexAux(s, array[20, byte](result))
template toHex*[N](x: FixedBytes[N]): string =
toHex(array[N, byte](x))
template toHex*[N](x: DynamicBytes[N]): string =
toHex(array[N, byte](x))
template toHex*(x: Address): string =
toHex(array[20, byte](x))
func decode*(input: string, offset: int, to: var Stuint): int =
let meaningfulLen = to.bits div 8 * 2
to = type(to).fromHex(input[offset .. offset + meaningfulLen - 1])
meaningfulLen
func decode*[N](input: string, offset: int, to: var Stint[N]): int =
let meaningfulLen = N div 8 * 2
fromHex(input[offset .. offset + meaningfulLen], to)
meaningfulLen
func decodeFixed(input: string, offset: int, to: var openarray[byte]): int =
let meaningfulLen = to.len * 2
var padding = to.len mod 32
if padding != 0: padding = (32 - padding) * 2
let offset = offset + padding
fromHexAux(input[offset .. offset + meaningfulLen - 1], to)
meaningfulLen + padding
func decode*[N](input: string, offset: int, to: var FixedBytes[N]): int {.inline.} =
decodeFixed(input, offset, array[N, byte](to))
func decode*(input: string, offset: int, to: var Address): int {.inline.} =
decodeFixed(input, offset, array[20, byte](to))
func decodeDynamic(input: string, offset: int, to: var openarray[byte]): int =
var dataOffset, dataLen: UInt256
result = decode(input, offset, dataOffset)
discard decode(input, dataOffset.truncate(int) * 2, dataLen)
# TODO: Check data len, and raise?
let meaningfulLen = to.len * 2
let actualDataOffset = (dataOffset.truncate(int) + 32) * 2
fromHexAux(input[actualDataOffset .. actualDataOffset + meaningfulLen - 1], to)
func decode*[N](input: string, offset: int, to: var DynamicBytes[N]): int {.inline.} =
decodeDynamic(input, offset, array[N, byte](to))
# TODO: Figure out a way to parse a bool as a FixedBytes[N], so that we can allow
# variance in the number of bytes. The current implementation is a very forceful
# way of parsing a bool because it assumes the bool is 32 bytes (64 chars).
func decode*(input: string, offset: int, to: var bool): int {.inline.} =
let val = input[offset..offset+63].parse(Int256)
to = val.truncate(int) == 1
64
func decode*(input: string, offset: int, obj: var object): int =
var offset = offset
for field in fields(obj):
offset += decode(input, offset, field)
func decode*[T](input: string, to: seq[T]): seq[T] =
var count = input[0..64].decode(Stuint)
result = newSeq[T](count)
for i in 0..count:
result[i] = input[i*64 .. (i+1)*64].decode(T)
func decode*[T; I: static int](input: string, to: array[0..I, T]): array[0..I, T] =
for i in 0..I:
result[i] = input[i*64 .. (i+1)*64].decode(T)
for field in obj.fields:
let encoded = encode(field)
if encoded.dynamic:
result &= offset.toHex(64).toLower
data &= encoded.data
offset += encoded.data.len
else:
result &= encoded.data
result &= data
func decodeContractResponse*[T](input: string): T =
result = T()

View File

@ -57,10 +57,12 @@ proc decodeContentHash*(value: string): string =
# See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Read-Sticker-Packs-owned-by-a-user
# for more details
proc getBalance*(address: EthAddress): int =
let contract = contracts.getContract("sticker-pack")
let payload = %* [{
let
contract = contracts.getContract("sticker-pack")
balanceOf = BalanceOf(address: address)
payload = %* [{
"to": $contract.address,
"data": contract.methods["balanceOf"].encodeAbi(address)
"data": contract.methods["balanceOf"].encodeAbi(balanceOf)
}, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload)
@ -88,15 +90,17 @@ proc getPackCount*(): int =
result = fromHex[int](response.result)
# Gets sticker pack data
proc getPackData*(id: int): StickerPack =
let contract = contracts.getContract("stickers")
let contractMethod = contract.methods["getPackData"]
let payload = %* [{
proc getPackData*(id: Stuint[256]): StickerPack =
let
contract = contracts.getContract("stickers")
contractMethod = contract.methods["getPackData"]
getPackData = GetPackData(packId: id)
payload = %* [{
"to": $contract.address,
"data": contractMethod.encodeAbi(id)
"data": contractMethod.encodeAbi(getPackData)
}, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload)
let response = Json.decode(responseStr, RpcResponse)
responseStr = status.callPrivateRPC("eth_call", payload)
response = Json.decode(responseStr, RpcResponse)
if not response.error.isNil:
raise newException(RpcException, "Error getting sticker pack data: " & response.error.message)
@ -115,18 +119,22 @@ proc getPackData*(id: int): StickerPack =
result = edn_helpers.decode[StickerPack](ednMeta)
# EDN doesn't include a packId for each sticker, so add it here
result.stickers.apply(proc(sticker: var Sticker) =
sticker.packId = id)
result.id = id
sticker.packId = truncate(id, int))
result.id = truncate(id, int)
result.price = packData.price
# Buys a sticker pack for user
# See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Buy-a-Sticker-Pack for more
# details
proc buyPack*(packId: int, address: EthAddress, price: Stuint[256], password: string): string =
let stickerMktContract = contracts.getContract("sticker-market")
let sntContract = contracts.getContract("snt")
let buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(packId, address, price)
let approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(stickerMktContract.address, price, buyTxAbiEncoded.strip0xPrefix)
proc buyPack*(packId: Stuint[256], address: EthAddress, price: Stuint[256], password: string): string =
let
stickerMktContract = contracts.getContract("sticker-market")
sntContract = contracts.getContract("snt")
buyToken = BuyToken(packId: packId, address: address, price: price)
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
let
approveAndCallObj = ApproveAndCall(to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(approveAndCallObj)
let payload = %* {
"from": $address,
"to": $sntContract.address,
@ -140,11 +148,13 @@ proc buyPack*(packId: int, address: EthAddress, price: Stuint[256], password: st
raise newException(RpcException, "Error getting stickers balance: " & response.error.message)
result = response.result # should be a tx receipt
proc tokenOfOwnerByIndex*(address: EthAddress, idx: int): int =
let contract = contracts.getContract("sticker-pack")
let payload = %* [{
proc tokenOfOwnerByIndex*(address: EthAddress, idx: Stuint[256]): int =
let
contract = contracts.getContract("sticker-pack")
tokenOfOwnerByIndex = TokenOfOwnerByIndex(address: address, index: idx)
payload = %* [{
"to": $contract.address,
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(address, idx)
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex)
}, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload)
@ -155,11 +165,13 @@ proc tokenOfOwnerByIndex*(address: EthAddress, idx: int): int =
return 0
result = fromHex[int](response.result)
proc getPackIdFromTokenId*(tokenId: int): int =
let contract = contracts.getContract("sticker-pack")
let payload = %* [{
proc getPackIdFromTokenId*(tokenId: Stuint[256]): int =
let
contract = contracts.getContract("sticker-pack")
tokenPackId = TokenPackId(tokenId: tokenId)
payload = %* [{
"to": $contract.address,
"data": contract.methods["tokenPackId"].encodeAbi(tokenId)
"data": contract.methods["tokenPackId"].encodeAbi(tokenPackId)
}, "latest"]
let responseStr = status.callPrivateRPC("eth_call", payload)

View File

@ -80,8 +80,10 @@ proc sendTransaction*(from_address: string, to: string, assetAddress: string, va
# TODO get decimals from the token
bigIntValue = bigIntValue * u256(1000000000000000000)
# Just using mainnet SNT to get the method ABI
let contract = getContract("snt")
let transferEncoded = contract.methods["transfer"].encodeAbi(parseAddress(to), bigIntValue.toHex())
let
contract = getContract("snt")
transfer = Transfer(to: parseAddress(to), value: bigIntValue)
transferEncoded = contract.methods["transfer"].encodeAbi(transfer)
args = %* {
"from": from_address,

View File

@ -2,17 +2,20 @@ import strformat, httpclient, json, chronicles, sequtils, strutils, tables
from eth/common/utils import parseAddress
import ../libstatus/core as status
import ../libstatus/contracts as contracts
import ../libstatus/types
import eth/common/eth_types
import ../libstatus/types
import account
proc getTokenUri(contract: Contract, tokenId: int): string =
proc getTokenUri(contract: Contract, tokenId: Stuint[256]): string =
try:
let payload = %* [{
"to": $contract.address,
"data": contract.methods["tokenURI"].encodeAbi(tokenId)
}, "latest"]
let response = callPrivateRPC("eth_call", payload)
let
tokenUri = TokenUri(tokenId: tokenId)
payload = %* [{
"to": $contract.address,
"data": contract.methods["tokenURI"].encodeAbi(tokenUri)
}, "latest"]
response = callPrivateRPC("eth_call", payload)
var postfixedResult: string = parseJson($response)["result"].str
postfixedResult.removeSuffix('0')
postfixedResult.removePrefix("0x")
@ -25,13 +28,15 @@ proc getTokenUri(contract: Contract, tokenId: int): string =
error "Error getting the token URI", mes = e.msg
result = ""
proc tokenOfOwnerByIndex(contract: Contract, address: EthAddress, index: int): int =
let payload = %* [{
"to": $contract.address,
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(address, index)
}, "latest"]
let response = callPrivateRPC("eth_call", payload)
let res = parseJson($response)["result"].str
proc tokenOfOwnerByIndex(contract: Contract, address: EthAddress, index: Stuint[256]): int =
let
tokenOfOwnerByIndex = TokenOfOwnerByIndex(address: address, index: index)
payload = %* [{
"to": $contract.address,
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex)
}, "latest"]
response = callPrivateRPC("eth_call", payload)
res = parseJson($response)["result"].str
if (res == "0x"):
return -1
result = fromHex[int](res)
@ -41,7 +46,7 @@ proc tokensOfOwnerByIndex(contract: Contract, address: EthAddress): seq[int] =
var token: int
result = @[]
while (true):
token = tokenOfOwnerByIndex(contract, address, index)
token = tokenOfOwnerByIndex(contract, address, index.u256)
if (token == -1 or token == 0):
return result
result.add(token)
@ -50,6 +55,7 @@ proc tokensOfOwnerByIndex(contract: Contract, address: EthAddress): seq[int] =
proc getCryptoKitties*(address: EthAddress): seq[Collectible] =
result = @[]
try:
# TODO handle testnet -- does this API exist in testnet??
# TODO handle offset (recursive method?)
# Crypto kitties has a limit of 20
let url: string = fmt"https://api.cryptokitties.co/kitties?limit=20&offset=0&owner_wallet_address={$address}&parents=false"
@ -105,7 +111,7 @@ proc getKudos*(address: EthAddress): seq[Collectible] =
return result
for token in tokens:
let url = getTokenUri(contract, token)
let url = getTokenUri(contract, token.u256)
if (url == ""):
return result