feat(@desktop/wallet2): Enable multi chain for collectible (#5)

This commit is contained in:
Anthony Laibe 2021-09-20 18:24:36 +02:00 committed by GitHub
parent c716a89544
commit ea02568fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 11 additions and 317 deletions

View File

@ -129,12 +129,12 @@ proc checkRecentHistory*(addresses: seq[string]): string =
let payload = %* [addresses]
result = callPrivateRPC("wallet_checkRecentHistory", payload)
proc getOpenseaCollections*(address: string): string =
let payload = %* [address]
proc getOpenseaCollections*(chainId: int, address: string): string =
let payload = %* [chainId, address]
result = callPrivateRPC("wallet_getOpenseaCollectionsByOwner", payload)
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
let payload = %* [address, collectionSlug, limit]
proc getOpenseaAssets*(chainId: int, address: string, collectionSlug: string, limit: int): string =
let payload = %* [chainId, address, collectionSlug, limit]
result = callPrivateRPC("wallet_getOpenseaAssetsByOwnerAndCollection", payload)
proc fetchCryptoServices*(success: var bool): string =

View File

@ -422,12 +422,6 @@ proc watchTransaction*(transactionHash: string): string =
proc hex2Token*(self: WalletModel, input: string, decimals: int): string =
result = status_wallet.hex2Token(input, decimals)
proc getOpenseaCollections*(address: string): string =
result = status_wallet.getOpenseaCollections(address)
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
result = status_wallet.getOpenseaAssets(address, collectionSlug, limit)
proc getGasPrice*(self: WalletModel): string =
let response = status_wallet.getGasPrice().parseJson
if response.hasKey("result"):
@ -436,7 +430,6 @@ proc getGasPrice*(self: WalletModel): string =
error "Error obtaining max priority fee per gas", error=response
raise newException(StatusGoException, "Error obtaining gas price")
proc setLatestBaseFee*(self: WalletModel, latestBaseFee: string) =
self.latestBaseFee = latestBaseFee

View File

@ -10,14 +10,6 @@ type CollectibleList* = ref object
type Collectible* = ref object
name*, image*, id*, collectibleType*, description*, externalUrl*: string
type OpenseaCollection* = ref object
name*, slug*, imageUrl*: string
ownedAssetCount*: int
type OpenseaAsset* = ref object
id*: int
name*, description*, permalink*, imageThumbnailUrl*, imageUrl*, address*: string
type CurrencyArgs* = ref object of Args
currency*: string
@ -34,29 +26,4 @@ type WalletAccount* = ref object
transactions*: tuple[hasMore: bool, data: seq[Transaction]]
type AccountArgs* = ref object of Args
account*: WalletAccount
proc `$`*(self: OpenseaCollection): string =
return fmt"OpenseaCollection(name:{self.name}, slug:{self.slug}, owned asset count:{self.ownedAssetCount})"
proc `$`*(self: OpenseaAsset): string =
return fmt"OpenseaAsset(id:{self.id}, name:{self.name}, address:{self.address}, imageUrl: {self.imageUrl}, imageThumbnailUrl: {self.imageThumbnailUrl})"
proc toOpenseaCollection*(jsonCollection: JsonNode): OpenseaCollection =
return OpenseaCollection(
name: jsonCollection{"name"}.getStr,
slug: jsonCollection{"slug"}.getStr,
imageUrl: jsonCollection{"image_url"}.getStr,
ownedAssetCount: jsonCollection{"owned_asset_count"}.getInt
)
proc toOpenseaAsset*(jsonAsset: JsonNode): OpenseaAsset =
return OpenseaAsset(
id: jsonAsset{"id"}.getInt,
name: jsonAsset{"name"}.getStr,
description: jsonAsset{"description"}.getStr,
permalink: jsonAsset{"permalink"}.getStr,
imageThumbnailUrl: jsonAsset{"image_thumbnail_url"}.getStr,
imageUrl: jsonAsset{"image_url"}.getStr,
address: jsonAsset{"asset_contract"}{"address"}.getStr
)
account*: WalletAccount

View File

@ -7,14 +7,14 @@ import statusgo_backend/wallet as status_wallet
import statusgo_backend/network as status_network
import statusgo_backend/settings as status_settings
import eth/[contracts]
import wallet2/[balance_manager, collectibles, network]
import wallet2/[balance_manager, network]
import wallet2/account as wallet_account
import ./types/[account, transaction, network_type, setting, gas_prediction, rpc_response]
import ../eventemitter
from web3/ethtypes import Address
from web3/conversions import `$`
export wallet_account, collectibles
export wallet_account
logScope:
topics = "status-wallet2"
@ -205,10 +205,12 @@ proc deleteAccount*(self: StatusWalletController, address: string): string =
self.accounts = self.accounts.filter(acc => acc.address.toLowerAscii != address.toLowerAscii)
proc getOpenseaCollections*(address: string): string =
result = status_wallet.getOpenseaCollections(address)
let networkId = status_settings.getCurrentNetworkDetails().config.networkId
result = status_wallet.getOpenseaCollections(networkId, address)
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
result = status_wallet.getOpenseaAssets(address, collectionSlug, limit)
let networkId = status_settings.getCurrentNetworkDetails().config.networkId
result = status_wallet.getOpenseaAssets(networkId, address, collectionSlug, limit)
proc onAsyncFetchCryptoServices*(self: StatusWalletController, response: string) =
let responseArray = response.parseJson

View File

@ -3,13 +3,6 @@ import Tables, options, json, strformat, strutils
from ../../eventemitter import Args
import ../types/[transaction]
type CollectibleList* = ref object
collectibleType*, collectiblesJSON*, error*: string
loading*: int
type Collectible* = ref object
name*, image*, id*, collectibleType*, description*, externalUrl*: string
type OpenseaCollectionTrait* = ref object
min*, max*: float
@ -38,7 +31,6 @@ type WalletAccount* = ref object
realFiatBalance*: Option[float]
assetList*: seq[Asset]
wallet*, chat*: bool
collectiblesLists*: seq[CollectibleList]
transactions*: tuple[hasMore: bool, data: seq[Transaction]]
type TraitType* {.pure.} = enum

View File

@ -1,260 +0,0 @@
import # std libs
atomics, strformat, httpclient, json, chronicles, sequtils, strutils, tables,
sugar, net
import # vendor libs
stint
import # status-desktop libs
../statusgo_backend/core as status, ../eth/contracts as contracts,
../stickers as status_stickers,
../utils as status_utils,
web3/[conversions, ethtypes], ../utils, account
const CRYPTOKITTY* = "cryptokitty"
const KUDO* = "kudo"
const ETHERMON* = "ethermon"
const STICKER* = "stickers"
const COLLECTIBLE_TYPES* = [CRYPTOKITTY, KUDO, ETHERMON, STICKER]
const MAX_TOKENS = 200
proc getTokenUri(contract: Erc721Contract, tokenId: Stuint[256]): string =
try:
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")
postfixedResult = parseHexStr(postfixedResult)
let index = postfixedResult.find("http")
if (index < -1):
return ""
result = postfixedResult[index .. postfixedResult.high]
except Exception as e:
error "Error getting the token URI", mes = e.msg
result = ""
proc tokenOfOwnerByIndex(contract: Erc721Contract, address: Address, 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)
jsonResponse = parseJson($response)
if (not jsonResponse.hasKey("result")):
return -1
let res = jsonResponse["result"].getStr
if (res == "0x"):
return -1
result = fromHex[int](res)
proc balanceOf(contract: Erc721Contract, address: Address): int =
let
balanceOf = BalanceOf(address: address)
payload = %* [{
"to": $contract.address,
"data": contract.methods["balanceOf"].encodeAbi(balanceOf)
}, "latest"]
response = callPrivateRPC("eth_call", payload)
jsonResponse = parseJson($response)
if (not jsonResponse.hasKey("result")):
return 0
let res = jsonResponse["result"].getStr
if (res == "0x"):
return 0
result = fromHex[int](res)
proc tokensOfOwnerByIndex(contract: Erc721Contract, address: Address): seq[int] =
var index = 0
var token: int
var maxIndex: int = balanceOf(contract, address)
result = @[]
while index < maxIndex and result.len <= MAX_TOKENS:
token = tokenOfOwnerByIndex(contract, address, index.u256)
result.add(token)
index = index + 1
return result
proc getCryptoKittiesBatch*(address: Address, offset: int = 0): seq[Collectible] =
var cryptokitties: seq[Collectible]
cryptokitties = @[]
# TODO handle testnet -- does this API exist in testnet??
let url: string = fmt"https://api.cryptokitties.co/kitties?limit=20&offset={$offset}&owner_wallet_address={$address}&parents=false"
let secureSSLContext = newContext()
let client = newHttpClient(sslContext = secureSSLContext)
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
let response = client.request(url)
let responseBody = parseJson(response.body)
let kitties = responseBody["kitties"]
for kitty in kitties:
try:
var id = kitty["id"]
var name = kitty["name"]
var finalId = ""
var finalName = ""
if id.kind != JNull:
finalId = $id
if name.kind != JNull:
finalName = $name
cryptokitties.add(Collectible(id: finalId,
name: finalName,
image: kitty["image_url_png"].str,
collectibleType: CRYPTOKITTY,
description: "",
externalUrl: ""))
except Exception as e2:
error "Error with this individual cat", msg = e2.msg, cat = kitty
let limit = responseBody["limit"].getInt
let total = responseBody["total"].getInt
let currentCount = limit * (offset + 1)
if (currentCount < total and currentCount < MAX_TOKENS):
# Call the API again with offset + 1
let nextBatch = getCryptoKittiesBatch(address, offset + 1)
return concat(cryptokitties, nextBatch)
return cryptokitties
proc getCryptoKitties*(address: Address): string =
try:
let cryptokitties = getCryptoKittiesBatch(address, 0)
return $(%*cryptokitties)
except Exception as e:
error "Error getting Cryptokitties", msg = e.msg
return e.msg
proc getCryptoKitties*(address: string): string =
let eth_address = parseAddress(address)
result = getCryptoKitties(eth_address)
proc getEthermons*(address: Address): string =
try:
var ethermons: seq[Collectible]
ethermons = @[]
let contract = getErc721Contract("ethermon")
if contract == nil: return $(%*ethermons)
let tokens = tokensOfOwnerByIndex(contract, address)
if (tokens.len == 0):
return $(%*ethermons)
let tokensJoined = strutils.join(tokens, ",")
let url = fmt"https://www.ethermon.io/api/monster/get_data?monster_ids={tokensJoined}"
let secureSSLContext = newContext()
let client = newHttpClient(sslContext = secureSSLContext)
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
let response = client.request(url)
let monsters = parseJson(response.body)["data"]
var i = 0
for monsterKey in json.keys(monsters):
let monster = monsters[monsterKey]
ethermons.add(Collectible(id: $tokens[i],
name: monster["class_name"].str,
image: monster["image"].str,
collectibleType: ETHERMON,
description: "",
externalUrl: ""))
i = i + 1
return $(%*ethermons)
except Exception as e:
error "Error getting Ethermons", msg = e.msg
result = e.msg
proc getEthermons*(address: string): string =
let eth_address = parseAddress(address)
result = getEthermons(eth_address)
proc getKudos*(address: Address): string =
try:
var kudos: seq[Collectible]
kudos = @[]
let contract = getErc721Contract("kudos")
if contract == nil: return $(%*kudos)
let tokens = tokensOfOwnerByIndex(contract, address)
if (tokens.len == 0):
return $(%*kudos)
for token in tokens:
let url = getTokenUri(contract, token.u256)
if (url == ""):
return $(%*kudos)
let secureSSLContext = newContext()
let client = newHttpClient(sslContext = secureSSLContext)
client.headers = newHttpHeaders({ "Content-Type": "application/json" })
let response = client.request(url)
let kudo = parseJson(response.body)
kudos.add(Collectible(id: $token,
name: kudo["name"].str,
image: kudo["image"].str,
collectibleType: KUDO,
description: kudo["description"].str,
externalUrl: kudo["external_url"].str))
return $(%*kudos)
except Exception as e:
error "Error getting Kudos", msg = e.msg
result = e.msg
proc getKudos*(address: string): string =
let eth_address = parseAddress(address)
result = getKudos(eth_address)
proc getStickers*(address: Address, running: var Atomic[bool]): string =
try:
var stickers: seq[Collectible]
stickers = @[]
let contract = getErc721Contract("sticker-pack")
if contract == nil: return
let tokensIds = tokensOfOwnerByIndex(contract, address)
if (tokensIds.len == 0):
return $(%*stickers)
let purchasedStickerPacks = tokensIds.map(tokenId => status_stickers.getPackIdFromTokenId(tokenId.u256))
if (purchasedStickerPacks.len == 0):
return $(%*stickers)
# TODO find a way to keep those in memory so as not to reload it each time
let availableStickerPacks = getAvailableStickerPacks(running)
var index = 0
for stickerId in purchasedStickerPacks:
let sticker = availableStickerPacks[stickerId]
stickers.add(Collectible(id: $tokensIds[index],
name: sticker.name,
image: fmt"https://ipfs.infura.io/ipfs/{status_utils.decodeContentHash(sticker.preview)}",
collectibleType: STICKER,
description: sticker.author,
externalUrl: "")
)
index = index + 1
return $(%*stickers)
except Exception as e:
error "Error getting Stickers", msg = e.msg
result = e.msg
proc getStickers*(address: string, running: var Atomic[bool]): string =
let eth_address = parseAddress(address)
result = getStickers(eth_address, running)