mirror of
https://github.com/status-im/status-desktop.git
synced 2025-02-22 11:38:57 +00:00
feat: add support for purchased sticker packs
This commit is contained in:
parent
0a2ea90117
commit
69ba3c4468
@ -40,15 +40,18 @@ proc init*(self: ChatController) =
|
||||
let currAcct = status_wallet.getWalletAccounts()[0] # TODO: make generic
|
||||
let currAddr = parseAddress(currAcct.address)
|
||||
|
||||
let installedStickerPacks = self.status.chat.getInstalledStickerPacks(currAddr)
|
||||
let installedStickerPacks = self.status.chat.getInstalledStickerPacks()
|
||||
|
||||
let purchasedStickerPacks = self.status.chat.getPurchasedStickerPacks(currAddr)
|
||||
|
||||
# TODO: getting available stickers should be done in a separate thread as there
|
||||
# a long wait for contract response, decoded, downloading from IPFS, EDN decoding,
|
||||
# etc
|
||||
let availableStickerPacks = self.status.chat.getAvailableStickerPacks(currAddr)
|
||||
let availableStickerPacks = self.status.chat.getAvailableStickerPacks()
|
||||
for packId, stickerPack in availableStickerPacks.pairs:
|
||||
let isInstalled = installedStickerPacks.hasKey(packId)
|
||||
self.view.addStickerPackToList(stickerPack, isInstalled)
|
||||
let isBought = purchasedStickerPacks.contains(packId)
|
||||
self.view.addStickerPackToList(stickerPack, isInstalled, isBought)
|
||||
|
||||
let recentStickers = self.status.chat.getRecentStickers()
|
||||
for sticker in recentStickers:
|
||||
|
@ -48,8 +48,8 @@ QtObject:
|
||||
result.recentStickers = newStickerList()
|
||||
result.setup()
|
||||
|
||||
proc addStickerPackToList*(self: ChatsView, stickerPack: StickerPack, isInstalled: bool) =
|
||||
self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled)
|
||||
proc addStickerPackToList*(self: ChatsView, stickerPack: StickerPack, isInstalled, isBought: bool) =
|
||||
self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought)
|
||||
|
||||
proc getStickerPackList(self: ChatsView): QVariant {.slot.} =
|
||||
newQVariant(self.stickerPacks)
|
||||
|
@ -12,9 +12,10 @@ type
|
||||
Stickers = UserRole + 6
|
||||
Thumbnail = UserRole + 7
|
||||
Installed = UserRole + 8
|
||||
Bought = UserRole + 9
|
||||
|
||||
type
|
||||
StickerPackView* = tuple[pack: StickerPack, stickers: StickerList, installed: bool]
|
||||
StickerPackView* = tuple[pack: StickerPack, stickers: StickerList, installed, bought: bool]
|
||||
|
||||
QtObject:
|
||||
type
|
||||
@ -50,6 +51,7 @@ QtObject:
|
||||
of StickerPackRoles.Stickers: result = newQVariant(packInfo.stickers)
|
||||
of StickerPackRoles.Thumbnail: result = newQVariant(decodeContentHash(stickerPack.thumbnail))
|
||||
of StickerPackRoles.Installed: result = newQVariant(packInfo.installed)
|
||||
of StickerPackRoles.Bought: result = newQVariant(packInfo.bought)
|
||||
|
||||
method roleNames(self: StickerPackList): Table[int, string] =
|
||||
{
|
||||
@ -60,7 +62,8 @@ QtObject:
|
||||
StickerPackRoles.Preview.int: "preview",
|
||||
StickerPackRoles.Stickers.int: "stickers",
|
||||
StickerPackRoles.Thumbnail.int: "thumbnail",
|
||||
StickerPackRoles.Installed.int: "installed"
|
||||
StickerPackRoles.Installed.int: "installed",
|
||||
StickerPackRoles.Bought.int: "bought"
|
||||
}.toTable
|
||||
|
||||
|
||||
@ -82,9 +85,9 @@ QtObject:
|
||||
raise newException(ValueError, "Sticker pack list does not have a pack with id " & $packId)
|
||||
result = self.packs.filterIt(it.pack.id == packId)[0].pack
|
||||
|
||||
proc addStickerPackToList*(self: StickerPackList, pack: StickerPack, stickers: StickerList, installed: bool) =
|
||||
proc addStickerPackToList*(self: StickerPackList, pack: StickerPack, stickers: StickerList, installed, bought: bool) =
|
||||
self.beginInsertRows(newQModelIndex(), 0, 0)
|
||||
self.packs.insert((pack: pack, stickers: stickers, installed: installed), 0)
|
||||
self.packs.insert((pack: pack, stickers: stickers, installed: installed, bought: bought), 0)
|
||||
self.endInsertRows()
|
||||
|
||||
proc removeStickerPackFromList*(self: StickerPackList, packId: int) =
|
||||
|
@ -1,4 +1,4 @@
|
||||
import eventemitter, json, strutils, sequtils, tables, chronicles
|
||||
import eventemitter, json, strutils, sequtils, tables, chronicles, sugar
|
||||
import libstatus/chat as status_chat
|
||||
import libstatus/stickers as status_stickers
|
||||
import libstatus/types
|
||||
@ -38,6 +38,7 @@ type
|
||||
recentStickers*: seq[Sticker]
|
||||
availableStickerPacks*: Table[int, StickerPack]
|
||||
installedStickerPacks*: Table[int, StickerPack]
|
||||
purchasedStickerPacks*: seq[int]
|
||||
|
||||
MessageArgs* = ref object of Args
|
||||
id*: string
|
||||
@ -54,6 +55,7 @@ proc newChatModel*(events: EventEmitter): ChatModel =
|
||||
result.recentStickers = @[]
|
||||
result.availableStickerPacks = initTable[int, StickerPack]()
|
||||
result.installedStickerPacks = initTable[int, StickerPack]()
|
||||
result.purchasedStickerPacks = @[]
|
||||
|
||||
|
||||
proc delete*(self: ChatModel) =
|
||||
@ -93,29 +95,35 @@ proc join*(self: ChatModel, chatId: string, chatType: ChatType) =
|
||||
|
||||
self.events.emit("channelJoined", ChannelArgs(chat: chat))
|
||||
|
||||
proc getInstalledStickerPacks*(self: ChatModel, address: EthAddress): Table[int, StickerPack] =
|
||||
proc getPurchasedStickerPacks*(self: ChatModel, address: EthAddress): seq[int] =
|
||||
if self.purchasedStickerPacks != @[]:
|
||||
return self.purchasedStickerPacks
|
||||
|
||||
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))
|
||||
result = self.purchasedStickerPacks
|
||||
|
||||
proc getInstalledStickerPacks*(self: ChatModel): Table[int, StickerPack] =
|
||||
if self.installedStickerPacks != initTable[int, StickerPack]():
|
||||
return self.installedStickerPacks
|
||||
|
||||
# TODO: needs more fleshing out to determine which sticker packs
|
||||
# we own -- owned sticker packs will simply allowed them to be installed
|
||||
discard status_stickers.getBalance(address)
|
||||
|
||||
self.installedStickerPacks = status_stickers.getInstalledStickerPacks()
|
||||
result = self.installedStickerPacks
|
||||
|
||||
proc getAvailableStickerPacks*(self: ChatModel, address: EthAddress): Table[int, StickerPack] =
|
||||
proc getAvailableStickerPacks*(self: ChatModel): Table[int, StickerPack] =
|
||||
if self.availableStickerPacks != initTable[int, StickerPack]():
|
||||
return self.availableStickerPacks
|
||||
|
||||
# TODO: needs more fleshing out to determine which sticker packs
|
||||
# we own -- owned sticker packs will simply allowed them to be installed
|
||||
discard status_stickers.getBalance(address)
|
||||
|
||||
let numPacks = status_stickers.getPackCount()
|
||||
for i in 0..<numPacks:
|
||||
let stickerPack = status_stickers.getPackData(i)
|
||||
self.availableStickerPacks[stickerPack.id] = stickerPack
|
||||
try:
|
||||
let stickerPack = status_stickers.getPackData(i)
|
||||
self.availableStickerPacks[stickerPack.id] = stickerPack
|
||||
except:
|
||||
continue
|
||||
result = self.availableStickerPacks
|
||||
|
||||
proc getRecentStickers*(self: ChatModel): seq[Sticker] =
|
||||
|
@ -23,6 +23,7 @@ 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
|
||||
@ -40,7 +41,12 @@ let CONTRACTS: seq[Contract] = @[
|
||||
("transfer", Method(signature: "transfer(address,uint256)"))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "snt", network: Network.Testnet, address: parseAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162")),
|
||||
Contract(name: "snt", network: Network.Testnet, address: parseAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162"),
|
||||
methods: [
|
||||
("approveAndCall", Method(signature: "approveAndCall(address,uint256,bytes)")),
|
||||
("transfer", Method(signature: "transfer(address,uint256)"))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "tribute-to-talk", network: Network.Testnet, address: parseAddress("0xC61aa0287247a0398589a66fCD6146EC0F295432")),
|
||||
Contract(name: "stickers", network: Network.Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"),
|
||||
methods: [
|
||||
@ -48,19 +54,35 @@ let CONTRACTS: seq[Contract] = @[
|
||||
("getPackData", Method(signature: "getPackData(uint256)", noPadding: true))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d")),
|
||||
Contract(name: "stickers", network: Network.Testnet, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"),
|
||||
methods: [
|
||||
("packCount", Method(signature: "packCount()")),
|
||||
("getPackData", Method(signature: "getPackData(uint256)", noPadding: true))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "sticker-market", network: Network.Mainnet, address: parseAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"),
|
||||
methods: [
|
||||
("buyToken", Method(signature: "buyToken(uint256,address,uint256)"))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "sticker-market", network: Network.Testnet, address: parseAddress("0x6CC7274aF9cE9572d22DFD8545Fb8c9C9Bcb48AD")),
|
||||
Contract(name: "sticker-pack", network: Network.Mainnet, address: parseAddress("0x110101156e8F0743948B2A61aFcf3994A8Fb172e"),
|
||||
Contract(name: "sticker-market", network: Network.Testnet, address: parseAddress("0x6CC7274aF9cE9572d22DFD8545Fb8c9C9Bcb48AD"),
|
||||
methods: [
|
||||
("balanceOf", Method(signature: "balanceOf(address)"))
|
||||
("buyToken", Method(signature: "buyToken(uint256,address,uint256)"))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "sticker-pack", network: Network.Testnet, address: parseAddress("0xf852198d0385c4b871e0b91804ecd47c6ba97351")),
|
||||
Contract(name: "sticker-pack", network: Network.Mainnet, address: parseAddress("0x110101156e8F0743948B2A61aFcf3994A8Fb172e"),
|
||||
methods: [
|
||||
("balanceOf", Method(signature: "balanceOf(address)")),
|
||||
("tokenOfOwnerByIndex", Method(signature: "tokenOfOwnerByIndex(address,uint256)")),
|
||||
("tokenPackId", Method(signature: "tokenPackId(uint256)"))
|
||||
].toTable
|
||||
),
|
||||
Contract(name: "sticker-pack", network: Network.Testnet, address: parseAddress("0xf852198d0385c4b871e0b91804ecd47c6ba97351"),
|
||||
methods: [
|
||||
("balanceOf", Method(signature: "balanceOf(address)")),
|
||||
("tokenOfOwnerByIndex", Method(signature: "tokenOfOwnerByIndex(address,uint256)")),
|
||||
("tokenPackId", Method(signature: "tokenPackId(uint256)"))
|
||||
].toTable),
|
||||
# Strikers seems dead. Their website doesn't work anymore
|
||||
Contract(name: "strikers", network: Network.Mainnet, address: parseAddress("0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"),
|
||||
methods: [
|
||||
@ -85,6 +107,11 @@ proc getContract*(network: Network, name: string): Contract =
|
||||
let found = CONTRACTS.filter(contract => contract.name == name and contract.network == network)
|
||||
result = if found.len > 0: found[0] else: nil
|
||||
|
||||
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)]
|
||||
@ -98,6 +125,8 @@ proc encodeParam[T](value: T): string =
|
||||
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')
|
||||
|
||||
@ -108,9 +137,6 @@ macro encodeAbi*(self: Method, params: varargs[untyped]): untyped =
|
||||
result = quote do:
|
||||
`result` & encodeParam(`param`)
|
||||
|
||||
proc `$`*(a: EthAddress): string =
|
||||
"0x" & a.toHex()
|
||||
|
||||
proc skip0xPrefix*(s: string): int =
|
||||
if s.len > 1 and s[0] == '0' and s[1] in {'x', 'X'}: 2
|
||||
else: 0
|
||||
|
@ -67,6 +67,8 @@ proc getBalance*(address: EthAddress): int =
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
if response.error != "":
|
||||
raise newException(RpcException, "Error getting stickers balance: " & response.error)
|
||||
if response.result == "0x":
|
||||
return 0
|
||||
result = fromHex[int](response.result)
|
||||
|
||||
# Gets number of sticker packs
|
||||
@ -81,6 +83,8 @@ proc getPackCount*(): int =
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
if response.error != "":
|
||||
raise newException(RpcException, "Error getting stickers balance: " & response.error)
|
||||
if response.result == "0x":
|
||||
return 0
|
||||
result = fromHex[int](response.result)
|
||||
|
||||
# Gets sticker pack data
|
||||
@ -118,17 +122,17 @@ proc getPackData*(id: int): StickerPack =
|
||||
# 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: int, password: string): string =
|
||||
proc buyPack*(packId: int, address: EthAddress, price: Stuint[256], password: string): string =
|
||||
let stickerMktContract = contracts.getContract(Network.Mainnet, "sticker-market")
|
||||
let sntContract = contracts.getContract(Network.Mainnet, "sticker-market")
|
||||
let sntContract = contracts.getContract(Network.Mainnet, "snt")
|
||||
let buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(packId, address, price)
|
||||
let approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi($stickerMktContract.address, price, buyTxAbiEncoded)
|
||||
let payload = %* [{
|
||||
let approveAndCallAbiEncoded = sntContract.methods["approveAndCall"].encodeAbi(stickerMktContract.address, price, buyTxAbiEncoded.strip0xPrefix)
|
||||
let payload = %* {
|
||||
"from": $address,
|
||||
"to": $sntContract.address,
|
||||
"gas": 200000,
|
||||
# "gas": 200000, # leave out for now
|
||||
"data": approveAndCallAbiEncoded
|
||||
}, "latest"]
|
||||
}
|
||||
|
||||
let responseStr = status.sendTransaction($payload, password)
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
@ -136,6 +140,36 @@ proc buyPack*(packId: int, address: EthAddress, price: int, password: string): s
|
||||
raise newException(RpcException, "Error getting stickers balance: " & response.error)
|
||||
result = response.result # should be a tx receipt
|
||||
|
||||
proc tokenOfOwnerByIndex*(address: EthAddress, idx: int): int =
|
||||
let contract = contracts.getContract(Network.Testnet, "sticker-pack")
|
||||
let payload = %* [{
|
||||
"to": $contract.address,
|
||||
"data": contract.methods["tokenOfOwnerByIndex"].encodeAbi(address, idx)
|
||||
}, "latest"]
|
||||
|
||||
let responseStr = status.callPrivateRPC("eth_call", payload)
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
if response.error != "":
|
||||
raise newException(RpcException, "Error getting owned tokens: " & response.error)
|
||||
if response.result == "0x":
|
||||
return 0
|
||||
result = fromHex[int](response.result)
|
||||
|
||||
proc getPackIdFromTokenId*(tokenId: int): int =
|
||||
let contract = contracts.getContract(Network.Testnet, "sticker-pack")
|
||||
let payload = %* [{
|
||||
"to": $contract.address,
|
||||
"data": contract.methods["tokenPackId"].encodeAbi(tokenId)
|
||||
}, "latest"]
|
||||
|
||||
let responseStr = status.callPrivateRPC("eth_call", payload)
|
||||
let response = Json.decode(responseStr, RpcResponse)
|
||||
if response.error != "":
|
||||
raise newException(RpcException, "Error getting pack id from token id: " & response.error)
|
||||
if response.result == "0x":
|
||||
return 0
|
||||
result = fromHex[int](response.result)
|
||||
|
||||
proc saveInstalledStickerPacks*(installedStickerPacks: Table[int, StickerPack]) =
|
||||
let json = %* {}
|
||||
for packId, pack in installedStickerPacks.pairs:
|
||||
|
@ -1,4 +1,5 @@
|
||||
import eventemitter, json_serialization, stint, json
|
||||
import eventemitter, json
|
||||
import eth/common/eth_types, stew/byteutils, json_serialization, stint
|
||||
import accounts/constants
|
||||
|
||||
type SignalCallback* = proc(eventMessage: cstring): void {.cdecl.}
|
||||
@ -118,6 +119,9 @@ type StickerPack* = object
|
||||
proc `%`*(stuint256: Stuint[256]): JsonNode =
|
||||
newJString($stuint256)
|
||||
|
||||
proc `$`*(a: EthAddress): string =
|
||||
"0x" & a.toHex()
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var Stuint[256])
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
|
@ -68,6 +68,7 @@ Item {
|
||||
style: StickerButton.StyleType.LargeNoIcon
|
||||
packPrice: price
|
||||
isInstalled: installed
|
||||
isBought: bought
|
||||
onInstallClicked: root.installClicked(stickers, packId, index)
|
||||
onUninstallClicked: root.uninstallClicked(packId)
|
||||
onCancelClicked: root.cancelClicked(packId)
|
||||
@ -100,6 +101,7 @@ Item {
|
||||
packPrice: price
|
||||
width: 75 // only needed for Qt Creator
|
||||
isInstalled: installed
|
||||
isBought: bought
|
||||
onInstallClicked: root.installClicked(stickers, packId, index)
|
||||
onUninstallClicked: root.uninstallClicked(packId)
|
||||
onCancelClicked: root.cancelClicked(packId)
|
||||
|
@ -185,7 +185,7 @@ Popup {
|
||||
|
||||
Repeater {
|
||||
id: stickerPackListView
|
||||
property int selectedPackId
|
||||
property int selectedPackId: -1
|
||||
model: stickerPackList
|
||||
|
||||
delegate: StickerPackIconWithIndicator {
|
||||
|
Loading…
x
Reference in New Issue
Block a user