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 eventemitter, json, strutils, sequtils, tables, chronicles, sugar
|
||||||
|
import libstatus/settings as status_settings
|
||||||
import libstatus/chat as status_chat
|
import libstatus/chat as status_chat
|
||||||
import libstatus/stickers as status_stickers
|
import libstatus/stickers as status_stickers
|
||||||
import libstatus/types
|
import libstatus/types
|
||||||
import profile/profile
|
import profile/profile
|
||||||
import chat/[chat, message]
|
import chat/[chat, message]
|
||||||
import ../signals/messages
|
import ../signals/messages
|
||||||
import ../signals/types as signal_types
|
|
||||||
import ens
|
import ens
|
||||||
import eth/common/eth_types
|
import eth/common/eth_types
|
||||||
|
|
||||||
|
@ -103,14 +103,20 @@ proc getPurchasedStickerPacks*(self: ChatModel, address: EthAddress): seq[int] =
|
||||||
return self.purchasedStickerPacks
|
return self.purchasedStickerPacks
|
||||||
|
|
||||||
try:
|
try:
|
||||||
var balance = status_stickers.getBalance(address)
|
# Buy the "Toozeman" sticker pack on testnet
|
||||||
# balance = 2 # hardcode to test support of purchased sticker packs, because buying sticker packs is proving very difficult on testnet
|
# Ensure there is enough STT and ETHro in the account first before uncommenting.
|
||||||
var tokenIds = toSeq[0..<balance].map(idx => status_stickers.tokenOfOwnerByIndex(address, idx))
|
# STT faucet: simpledapp.eth
|
||||||
# tokenIds = @[1, 2] # hardcode to test support of purchased sticker packs
|
# NOTE: don't forget to update your account password!
|
||||||
self.purchasedStickerPacks = tokenIds.map(tokenId => status_stickers.getPackIdFromTokenId(tokenId))
|
# 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
|
result = self.purchasedStickerPacks
|
||||||
except RpcException:
|
except RpcException:
|
||||||
error "Error in getPurchasedStickerPacks", message = getCurrentExceptionMsg()
|
error "Error getting purchased sticker packs", message = getCurrentExceptionMsg()
|
||||||
result = @[]
|
result = @[]
|
||||||
|
|
||||||
proc getInstalledStickerPacks*(self: ChatModel): Table[int, StickerPack] =
|
proc getInstalledStickerPacks*(self: ChatModel): Table[int, StickerPack] =
|
||||||
|
@ -128,7 +134,7 @@ proc getAvailableStickerPacks*(self: ChatModel): Table[int, StickerPack] =
|
||||||
let numPacks = status_stickers.getPackCount()
|
let numPacks = status_stickers.getPackCount()
|
||||||
for i in 0..<numPacks:
|
for i in 0..<numPacks:
|
||||||
try:
|
try:
|
||||||
let stickerPack = status_stickers.getPackData(i)
|
let stickerPack = status_stickers.getPackData(i.u256)
|
||||||
self.availableStickerPacks[stickerPack.id] = stickerPack
|
self.availableStickerPacks[stickerPack.id] = stickerPack
|
||||||
except:
|
except:
|
||||||
continue
|
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
|
import eth/common/eth_types, stew/byteutils, nimcrypto
|
||||||
from eth/common/utils import parseAddress
|
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
|
type Method* = object
|
||||||
name*: string
|
name*: string
|
||||||
signature*: string
|
signature*: string
|
||||||
noPadding*: bool
|
|
||||||
|
|
||||||
type Contract* = ref object
|
type Contract* = ref object
|
||||||
name*: string
|
name*: string
|
||||||
|
@ -14,22 +17,6 @@ type Contract* = ref object
|
||||||
address*: EthAddress
|
address*: EthAddress
|
||||||
methods*: Table[string, Method]
|
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] = @[
|
proc allContracts(): seq[Contract] = @[
|
||||||
Contract(name: "snt", network: Network.Mainnet, address: parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"),
|
Contract(name: "snt", network: Network.Mainnet, address: parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"),
|
||||||
methods: [
|
methods: [
|
||||||
|
@ -47,13 +34,13 @@ proc allContracts(): seq[Contract] = @[
|
||||||
Contract(name: "stickers", network: Network.Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"),
|
Contract(name: "stickers", network: Network.Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"),
|
||||||
methods: [
|
methods: [
|
||||||
("packCount", Method(signature: "packCount()")),
|
("packCount", Method(signature: "packCount()")),
|
||||||
("getPackData", Method(signature: "getPackData(uint256)", noPadding: true))
|
("getPackData", Method(signature: "getPackData(uint256)"))
|
||||||
].toTable
|
].toTable
|
||||||
),
|
),
|
||||||
Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"),
|
Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"),
|
||||||
methods: [
|
methods: [
|
||||||
("packCount", Method(signature: "packCount()")),
|
("packCount", Method(signature: "packCount()")),
|
||||||
("getPackData", Method(signature: "getPackData(uint256)", noPadding: true))
|
("getPackData", Method(signature: "getPackData(uint256)"))
|
||||||
].toTable
|
].toTable
|
||||||
),
|
),
|
||||||
Contract(name: "sticker-market", network: Network.Mainnet, address: parseAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"),
|
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"),
|
Contract(name: "kudos", network: Network.Mainnet, address: parseAddress("0x2aea4add166ebf38b63d09a75de1a7b94aa24163"),
|
||||||
methods: [
|
methods: [
|
||||||
("tokenOfOwnerByIndex", Method(signature: "tokenOfOwnerByIndex(address,uint256)")),
|
("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
|
].toTable
|
||||||
),
|
),
|
||||||
Contract(name: "crypto-kitties", network: Network.Mainnet, address: parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d")),
|
Contract(name: "crypto-kitties", network: Network.Mainnet, address: parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d")),
|
||||||
|
@ -107,132 +100,30 @@ proc getContract*(name: string): Contract =
|
||||||
let network = settings.getCurrentNetwork()
|
let network = settings.getCurrentNetwork()
|
||||||
getContract(network, name)
|
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 =
|
proc encodeMethod(self: Method): string =
|
||||||
let hash = $nimcrypto.keccak256.digest(self.signature)
|
($nimcrypto.keccak256.digest(self.signature))[0..<8].toLower
|
||||||
result = hash[0 .. ^(hash.high - 6)]
|
|
||||||
if (not self.noPadding):
|
|
||||||
result = &"{result:0<32}"
|
|
||||||
|
|
||||||
proc encodeParam[T](value: T): string =
|
proc encodeAbi*(self: Method, obj: object = RootObj()): string =
|
||||||
# Could possibly simplify this by passing a string value, like so:
|
result = "0x" & self.encodeMethod()
|
||||||
# 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')
|
|
||||||
|
|
||||||
macro encodeAbi*(self: Method, params: varargs[untyped]): untyped =
|
# .fields is an iterator, and there's no way to get a count of an iterator
|
||||||
result = quote do:
|
# in nim, so we have to loop and increment a counter
|
||||||
"0x" & encodeMethod(`self`)
|
var fieldCount = 0
|
||||||
for param in params:
|
for i in obj.fields:
|
||||||
result = quote do:
|
fieldCount += 1
|
||||||
`result` & encodeParam(`param`)
|
var
|
||||||
|
offset = 32*fieldCount
|
||||||
|
data = ""
|
||||||
|
|
||||||
proc skip0xPrefix*(s: string): int =
|
for field in obj.fields:
|
||||||
if s.len > 1 and s[0] == '0' and s[1] in {'x', 'X'}: 2
|
let encoded = encode(field)
|
||||||
else: 0
|
if encoded.dynamic:
|
||||||
|
result &= offset.toHex(64).toLower
|
||||||
proc strip0xPrefix*(s: string): string =
|
data &= encoded.data
|
||||||
let prefixLen = skip0xPrefix(s)
|
offset += encoded.data.len
|
||||||
if prefixLen != 0:
|
else:
|
||||||
s[prefixLen .. ^1]
|
result &= encoded.data
|
||||||
else:
|
result &= data
|
||||||
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)
|
|
||||||
|
|
||||||
func decodeContractResponse*[T](input: string): T =
|
func decodeContractResponse*[T](input: string): T =
|
||||||
result = 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
|
# See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Read-Sticker-Packs-owned-by-a-user
|
||||||
# for more details
|
# for more details
|
||||||
proc getBalance*(address: EthAddress): int =
|
proc getBalance*(address: EthAddress): int =
|
||||||
let contract = contracts.getContract("sticker-pack")
|
let
|
||||||
let payload = %* [{
|
contract = contracts.getContract("sticker-pack")
|
||||||
|
balanceOf = BalanceOf(address: address)
|
||||||
|
payload = %* [{
|
||||||
"to": $contract.address,
|
"to": $contract.address,
|
||||||
"data": contract.methods["balanceOf"].encodeAbi(address)
|
"data": contract.methods["balanceOf"].encodeAbi(balanceOf)
|
||||||
}, "latest"]
|
}, "latest"]
|
||||||
|
|
||||||
let responseStr = status.callPrivateRPC("eth_call", payload)
|
let responseStr = status.callPrivateRPC("eth_call", payload)
|
||||||
|
@ -88,15 +90,17 @@ proc getPackCount*(): int =
|
||||||
result = fromHex[int](response.result)
|
result = fromHex[int](response.result)
|
||||||
|
|
||||||
# Gets sticker pack data
|
# Gets sticker pack data
|
||||||
proc getPackData*(id: int): StickerPack =
|
proc getPackData*(id: Stuint[256]): StickerPack =
|
||||||
let contract = contracts.getContract("stickers")
|
let
|
||||||
let contractMethod = contract.methods["getPackData"]
|
contract = contracts.getContract("stickers")
|
||||||
let payload = %* [{
|
contractMethod = contract.methods["getPackData"]
|
||||||
|
getPackData = GetPackData(packId: id)
|
||||||
|
payload = %* [{
|
||||||
"to": $contract.address,
|
"to": $contract.address,
|
||||||
"data": contractMethod.encodeAbi(id)
|
"data": contractMethod.encodeAbi(getPackData)
|
||||||
}, "latest"]
|
}, "latest"]
|
||||||
let responseStr = status.callPrivateRPC("eth_call", payload)
|
responseStr = status.callPrivateRPC("eth_call", payload)
|
||||||
let response = Json.decode(responseStr, RpcResponse)
|
response = Json.decode(responseStr, RpcResponse)
|
||||||
if not response.error.isNil:
|
if not response.error.isNil:
|
||||||
raise newException(RpcException, "Error getting sticker pack data: " & response.error.message)
|
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)
|
result = edn_helpers.decode[StickerPack](ednMeta)
|
||||||
# EDN doesn't include a packId for each sticker, so add it here
|
# EDN doesn't include a packId for each sticker, so add it here
|
||||||
result.stickers.apply(proc(sticker: var Sticker) =
|
result.stickers.apply(proc(sticker: var Sticker) =
|
||||||
sticker.packId = id)
|
sticker.packId = truncate(id, int))
|
||||||
result.id = id
|
result.id = truncate(id, int)
|
||||||
result.price = packData.price
|
result.price = packData.price
|
||||||
|
|
||||||
# Buys a sticker pack for user
|
# Buys a sticker pack for user
|
||||||
# See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Buy-a-Sticker-Pack for more
|
# See https://notes.status.im/Q-sQmQbpTOOWCQcYiXtf5g#Buy-a-Sticker-Pack for more
|
||||||
# details
|
# details
|
||||||
proc buyPack*(packId: int, address: EthAddress, price: Stuint[256], password: string): string =
|
proc buyPack*(packId: Stuint[256], address: EthAddress, price: Stuint[256], password: string): string =
|
||||||
let stickerMktContract = contracts.getContract("sticker-market")
|
let
|
||||||
let sntContract = contracts.getContract("snt")
|
stickerMktContract = contracts.getContract("sticker-market")
|
||||||
let buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(packId, address, price)
|
sntContract = contracts.getContract("snt")
|
||||||
let approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(stickerMktContract.address, price, buyTxAbiEncoded.strip0xPrefix)
|
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 = %* {
|
let payload = %* {
|
||||||
"from": $address,
|
"from": $address,
|
||||||
"to": $sntContract.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)
|
raise newException(RpcException, "Error getting stickers balance: " & response.error.message)
|
||||||
result = response.result # should be a tx receipt
|
result = response.result # should be a tx receipt
|
||||||
|
|
||||||
proc tokenOfOwnerByIndex*(address: EthAddress, idx: int): int =
|
proc tokenOfOwnerByIndex*(address: EthAddress, idx: Stuint[256]): int =
|
||||||
let contract = contracts.getContract("sticker-pack")
|
let
|
||||||
let payload = %* [{
|
contract = contracts.getContract("sticker-pack")
|
||||||
|
tokenOfOwnerByIndex = TokenOfOwnerByIndex(address: address, index: idx)
|
||||||
|
payload = %* [{
|
||||||
"to": $contract.address,
|
"to": $contract.address,
|
||||||
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(address, idx)
|
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex)
|
||||||
}, "latest"]
|
}, "latest"]
|
||||||
|
|
||||||
let responseStr = status.callPrivateRPC("eth_call", payload)
|
let responseStr = status.callPrivateRPC("eth_call", payload)
|
||||||
|
@ -155,11 +165,13 @@ proc tokenOfOwnerByIndex*(address: EthAddress, idx: int): int =
|
||||||
return 0
|
return 0
|
||||||
result = fromHex[int](response.result)
|
result = fromHex[int](response.result)
|
||||||
|
|
||||||
proc getPackIdFromTokenId*(tokenId: int): int =
|
proc getPackIdFromTokenId*(tokenId: Stuint[256]): int =
|
||||||
let contract = contracts.getContract("sticker-pack")
|
let
|
||||||
let payload = %* [{
|
contract = contracts.getContract("sticker-pack")
|
||||||
|
tokenPackId = TokenPackId(tokenId: tokenId)
|
||||||
|
payload = %* [{
|
||||||
"to": $contract.address,
|
"to": $contract.address,
|
||||||
"data": contract.methods["tokenPackId"].encodeAbi(tokenId)
|
"data": contract.methods["tokenPackId"].encodeAbi(tokenPackId)
|
||||||
}, "latest"]
|
}, "latest"]
|
||||||
|
|
||||||
let responseStr = status.callPrivateRPC("eth_call", payload)
|
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
|
# TODO get decimals from the token
|
||||||
bigIntValue = bigIntValue * u256(1000000000000000000)
|
bigIntValue = bigIntValue * u256(1000000000000000000)
|
||||||
# Just using mainnet SNT to get the method ABI
|
# Just using mainnet SNT to get the method ABI
|
||||||
let contract = getContract("snt")
|
let
|
||||||
let transferEncoded = contract.methods["transfer"].encodeAbi(parseAddress(to), bigIntValue.toHex())
|
contract = getContract("snt")
|
||||||
|
transfer = Transfer(to: parseAddress(to), value: bigIntValue)
|
||||||
|
transferEncoded = contract.methods["transfer"].encodeAbi(transfer)
|
||||||
|
|
||||||
args = %* {
|
args = %* {
|
||||||
"from": from_address,
|
"from": from_address,
|
||||||
|
|
|
@ -2,17 +2,20 @@ import strformat, httpclient, json, chronicles, sequtils, strutils, tables
|
||||||
from eth/common/utils import parseAddress
|
from eth/common/utils import parseAddress
|
||||||
import ../libstatus/core as status
|
import ../libstatus/core as status
|
||||||
import ../libstatus/contracts as contracts
|
import ../libstatus/contracts as contracts
|
||||||
|
import ../libstatus/types
|
||||||
import eth/common/eth_types
|
import eth/common/eth_types
|
||||||
import ../libstatus/types
|
import ../libstatus/types
|
||||||
import account
|
import account
|
||||||
|
|
||||||
proc getTokenUri(contract: Contract, tokenId: int): string =
|
proc getTokenUri(contract: Contract, tokenId: Stuint[256]): string =
|
||||||
try:
|
try:
|
||||||
let payload = %* [{
|
let
|
||||||
"to": $contract.address,
|
tokenUri = TokenUri(tokenId: tokenId)
|
||||||
"data": contract.methods["tokenURI"].encodeAbi(tokenId)
|
payload = %* [{
|
||||||
}, "latest"]
|
"to": $contract.address,
|
||||||
let response = callPrivateRPC("eth_call", payload)
|
"data": contract.methods["tokenURI"].encodeAbi(tokenUri)
|
||||||
|
}, "latest"]
|
||||||
|
response = callPrivateRPC("eth_call", payload)
|
||||||
var postfixedResult: string = parseJson($response)["result"].str
|
var postfixedResult: string = parseJson($response)["result"].str
|
||||||
postfixedResult.removeSuffix('0')
|
postfixedResult.removeSuffix('0')
|
||||||
postfixedResult.removePrefix("0x")
|
postfixedResult.removePrefix("0x")
|
||||||
|
@ -25,13 +28,15 @@ proc getTokenUri(contract: Contract, tokenId: int): string =
|
||||||
error "Error getting the token URI", mes = e.msg
|
error "Error getting the token URI", mes = e.msg
|
||||||
result = ""
|
result = ""
|
||||||
|
|
||||||
proc tokenOfOwnerByIndex(contract: Contract, address: EthAddress, index: int): int =
|
proc tokenOfOwnerByIndex(contract: Contract, address: EthAddress, index: Stuint[256]): int =
|
||||||
let payload = %* [{
|
let
|
||||||
"to": $contract.address,
|
tokenOfOwnerByIndex = TokenOfOwnerByIndex(address: address, index: index)
|
||||||
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(address, index)
|
payload = %* [{
|
||||||
}, "latest"]
|
"to": $contract.address,
|
||||||
let response = callPrivateRPC("eth_call", payload)
|
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex)
|
||||||
let res = parseJson($response)["result"].str
|
}, "latest"]
|
||||||
|
response = callPrivateRPC("eth_call", payload)
|
||||||
|
res = parseJson($response)["result"].str
|
||||||
if (res == "0x"):
|
if (res == "0x"):
|
||||||
return -1
|
return -1
|
||||||
result = fromHex[int](res)
|
result = fromHex[int](res)
|
||||||
|
@ -41,7 +46,7 @@ proc tokensOfOwnerByIndex(contract: Contract, address: EthAddress): seq[int] =
|
||||||
var token: int
|
var token: int
|
||||||
result = @[]
|
result = @[]
|
||||||
while (true):
|
while (true):
|
||||||
token = tokenOfOwnerByIndex(contract, address, index)
|
token = tokenOfOwnerByIndex(contract, address, index.u256)
|
||||||
if (token == -1 or token == 0):
|
if (token == -1 or token == 0):
|
||||||
return result
|
return result
|
||||||
result.add(token)
|
result.add(token)
|
||||||
|
@ -50,6 +55,7 @@ proc tokensOfOwnerByIndex(contract: Contract, address: EthAddress): seq[int] =
|
||||||
proc getCryptoKitties*(address: EthAddress): seq[Collectible] =
|
proc getCryptoKitties*(address: EthAddress): seq[Collectible] =
|
||||||
result = @[]
|
result = @[]
|
||||||
try:
|
try:
|
||||||
|
# TODO handle testnet -- does this API exist in testnet??
|
||||||
# TODO handle offset (recursive method?)
|
# TODO handle offset (recursive method?)
|
||||||
# Crypto kitties has a limit of 20
|
# 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"
|
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
|
return result
|
||||||
|
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
let url = getTokenUri(contract, token)
|
let url = getTokenUri(contract, token.u256)
|
||||||
|
|
||||||
if (url == ""):
|
if (url == ""):
|
||||||
return result
|
return result
|
||||||
|
|
Loading…
Reference in New Issue