From b6884a5170c56ce03c79e65d6493e9833c50cc31 Mon Sep 17 00:00:00 2001 From: emizzle Date: Tue, 21 Jul 2020 16:57:57 +1000 Subject: [PATCH] feat: encode contract calls --- src/status/chat.nim | 22 ++-- src/status/libstatus/coder.nim | 185 +++++++++++++++++++++++++++++ src/status/libstatus/contracts.nim | 179 ++++++---------------------- src/status/libstatus/stickers.nim | 62 ++++++---- src/status/libstatus/wallet.nim | 6 +- src/status/wallet/collectibles.nim | 36 +++--- 6 files changed, 296 insertions(+), 194 deletions(-) create mode 100644 src/status/libstatus/coder.nim diff --git a/src/status/chat.nim b/src/status/chat.nim index 7853b80e0a..0e32986265 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -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.. 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, "") + var + balance = status_stickers.getBalance(address) + tokenIds = toSeq[0.. 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.. 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) \ No newline at end of file diff --git a/src/status/libstatus/contracts.nim b/src/status/libstatus/contracts.nim index 4a647ef42d..0321d54dd6 100644 --- a/src/status/libstatus/contracts.nim +++ b/src/status/libstatus/contracts.nim @@ -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() diff --git a/src/status/libstatus/stickers.nim b/src/status/libstatus/stickers.nim index 7b7c89087c..f6f5d9524a 100644 --- a/src/status/libstatus/stickers.nim +++ b/src/status/libstatus/stickers.nim @@ -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) diff --git a/src/status/libstatus/wallet.nim b/src/status/libstatus/wallet.nim index 80aad15ae3..ef3c472bfc 100644 --- a/src/status/libstatus/wallet.nim +++ b/src/status/libstatus/wallet.nim @@ -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, diff --git a/src/status/wallet/collectibles.nim b/src/status/wallet/collectibles.nim index 0bdc65229b..86c096ccf2 100644 --- a/src/status/wallet/collectibles.nim +++ b/src/status/wallet/collectibles.nim @@ -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