feat: encode contract calls
This commit is contained in:
parent
e484c41291
commit
b6884a5170
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue