From d6937ac5b9d0ee04b6e3d28aef2519f438976399 Mon Sep 17 00:00:00 2001 From: qvvg Date: Tue, 12 Jan 2021 14:10:54 -0800 Subject: [PATCH] buy stuff --- src/flow/buy-market-item.tx.js | 63 +++++++++++++ src/flow/cancel-market-listing.tx.js | 33 +++++++ src/flow/fetch-account-item.script.js | 6 +- src/flow/fetch-account-items.script.js | 14 +-- src/flow/fetch-market-item.script.js | 119 +++++++++++++++++++++++++ src/flow/fetch-market-items.script.js | 2 +- src/global/config.comp.js | 2 +- src/hooks/use-market-item.hook.js | 96 ++++++++++++++++++++ src/pages/wip/qvvg.page.js | 2 + src/parts/account-item-cluster.comp.js | 5 +- src/parts/market-item-cluster.comp.js | 44 +++++++++ src/parts/market-items-cluster.comp.js | 3 +- 12 files changed, 376 insertions(+), 13 deletions(-) create mode 100644 src/flow/buy-market-item.tx.js create mode 100644 src/flow/cancel-market-listing.tx.js create mode 100644 src/flow/fetch-market-item.script.js create mode 100644 src/hooks/use-market-item.hook.js create mode 100644 src/parts/market-item-cluster.comp.js diff --git a/src/flow/buy-market-item.tx.js b/src/flow/buy-market-item.tx.js new file mode 100644 index 0000000..781762d --- /dev/null +++ b/src/flow/buy-market-item.tx.js @@ -0,0 +1,63 @@ +import * as fcl from "@onflow/fcl" +import * as t from "@onflow/types" +import {tx} from "./util/tx" +import {invariant} from "@onflow/util-invariant" + +const CODE = fcl.cdc` + import FungibleToken from 0xFungibleToken + import NonFungibleToken from 0xNonFungibleToken + import Kibble from 0xKibble + import KittyItems from 0xKittyItems + import KittyItemsMarket from 0xKittyItemsMarket + + transaction(saleItemID: UInt64, marketCollectionAddress: Address) { + let paymentVault: @FungibleToken.Vault + let kittyItemsCollection: &KittyItems.Collection{NonFungibleToken.Receiver} + let marketCollection: &KittyItemsMarket.Collection{KittyItemsMarket.CollectionPublic} + + prepare(acct: AuthAccount) { + self.marketCollection = getAccount(marketCollectionAddress) + .getCapability<&KittyItemsMarket.Collection{KittyItemsMarket.CollectionPublic}>( + KittyItemsMarket.CollectionPublicPath + )! + .borrow() + ?? panic("Could not borrow market collection from market address") + + let price = self.marketCollection.borrowSaleItem(saleItemID: saleItemID).salePrice + + let mainKibbleVault = acct.borrow<&Kibble.Vault>(from: Kibble.VaultStoragePath) + ?? panic("Cannot borrow Kibble vault from acct storage") + self.paymentVault <- mainKibbleVault.withdraw(amount: price) + + self.kittyItemsCollection = acct.borrow<&KittyItems.Collection{NonFungibleToken.Receiver}>( + from: KittyItems.CollectionStoragePath + ) ?? panic("Cannot borrow KittyItems collection receiver from acct") + } + + execute { + self.marketCollection.purchase( + saleItemID: saleItemID, + buyerCollection: self.kittyItemsCollection, + buyerPayment: <- self.paymentVault + ) + } + } +` + +// prettier-ignore +export function buyMarketItem({itemId, ownerAddress}, opts = {}) { + invariant(itemId != null, "buyMarketItem({itemId, ownerAddress}) -- itemId required") + invariant(ownerAddress != null, "buyMarketItem({itemId, ownerAddress}) -- ownerAddress required") + + return tx([ + fcl.transaction(CODE), + fcl.args([ + fcl.arg(Number(itemId), t.UInt64), + fcl.arg(String(ownerAddress), t.Address), + ]), + fcl.proposer(fcl.authz), + fcl.payer(fcl.authz), + fcl.authorizations([fcl.authz]), + fcl.limit(1000), + ], opts) +} diff --git a/src/flow/cancel-market-listing.tx.js b/src/flow/cancel-market-listing.tx.js new file mode 100644 index 0000000..bf2da92 --- /dev/null +++ b/src/flow/cancel-market-listing.tx.js @@ -0,0 +1,33 @@ +import * as fcl from "@onflow/fcl" +import * as t from "@onflow/types" +import {tx} from "./util/tx" +import {invariant} from "@onflow/util-invariant" + +const CODE = fcl.cdc` + import KittyItemsMarket from 0xKittyItemsMarket + + transaction(saleItemID: UInt64) { + prepare(account: AuthAccount) { + let listing <- account + .borrow<&KittyItemsMarket.Collection>(from: KittyItemsMarket.CollectionStoragePath)! + .remove(saleItemID: saleItemID) + destroy listing + } + } +` + +// prettier-ignore +export function cancelMarketListing({ itemId }, opts = {}) { + invariant(itemId != null, "cancelMarketListing({itemId}) -- itemId required") + + return tx([ + fcl.transaction(CODE), + fcl.args([ + fcl.arg(Number(itemId), t.UInt64), + ]), + fcl.proposer(fcl.authz), + fcl.payer(fcl.authz), + fcl.authorizations([fcl.authz]), + fcl.limit(1000), + ], opts) +} diff --git a/src/flow/fetch-account-item.script.js b/src/flow/fetch-account-item.script.js index 92800a8..eed1e38 100644 --- a/src/flow/fetch-account-item.script.js +++ b/src/flow/fetch-account-item.script.js @@ -9,10 +9,12 @@ import KittyItems from 0xKittyItems pub struct Item { pub let id: UInt64 pub let type: UInt64 + pub let owner: Address - init(id: UInt64, type: UInt64) { + init(id: UInt64, type: UInt64, owner: Address) { self.id = id self.type = type + self.owner = owner } } @@ -22,7 +24,7 @@ pub fun fetch(address: Address, id: UInt64): Item? { if let collection = cap.borrow() { if let item = collection.borrowKittyItem(id: id) { - return Item(id: id, type: item.typeID) + return Item(id: id, type: item.typeID, owner: address) } else { return nil } diff --git a/src/flow/fetch-account-items.script.js b/src/flow/fetch-account-items.script.js index 13cbb96..51add57 100644 --- a/src/flow/fetch-account-items.script.js +++ b/src/flow/fetch-account-items.script.js @@ -1,7 +1,7 @@ -import {send, decode, script, args, arg, cdc} from "@onflow/fcl" +import * as fcl from "@onflow/fcl" import {Address} from "@onflow/types" -const CODE = cdc` +const CODE = fcl.cdc` import NonFungibleToken from 0xNonFungibleToken import KittyItems from 0xKittyItems @@ -21,10 +21,10 @@ export function fetchAccountItems(address) { if (address == null) return Promise.resolve([]) // prettier-ignore - return send([ - script(CODE), - args([ - arg(address, Address) + return fcl.send([ + fcl.script(CODE), + fcl.args([ + fcl.arg(address, Address) ]), - ]).then(decode) + ]).then(fcl.decode).then(d => d.sort((a, b) => a - b)) } diff --git a/src/flow/fetch-market-item.script.js b/src/flow/fetch-market-item.script.js new file mode 100644 index 0000000..af1ab48 --- /dev/null +++ b/src/flow/fetch-market-item.script.js @@ -0,0 +1,119 @@ +import * as fcl from "@onflow/fcl" +import * as t from "@onflow/types" +// import {batch} from "./util/batch" + +// const CODE = fcl.cdc` +// import KittyItemsMarket from 0xfcceff21d9532b58 + +// pub struct Item { +// pub let id: UInt64 +// pub let isCompleted: Bool +// pub let price: UFix64 +// pub let owner: Address + +// init(id: UInt64, isCompleted: Bool, price: UFix64, owner: Address) { +// self.id = id +// self.isCompleted = isCompleted +// self.price = price +// self.owner: owner +// } +// } + +// pub fun fetch(address: Address, id: UInt64): Item? { +// let cap = getAccount(address) +// .getCapability<&KittyItemsMarket.Collection{KittyItemsMarket.CollectionPublic}>(KittyItemsMarket.CollectionPublicPath)! + +// if let collection = cap.borrow() { +// // this currently throws as the collection.borrowSaleItem returns a non-optional resource +// if let item = collection.borrowSaleItem(saleItemID: id) { +// return Item(id: id, isCompleted: item.saleCompleted, price: item.salePrice, owner: address) +// } else { +// return nil +// } +// } else { +// return nil +// } +// } + +// pub fun main(keys: [String], addresses: [Address], ids: [UInt64]): {String: Item?} { +// let r: {String: Item?} = {} +// var i = 0 +// while i < keys.length { +// let key = keys[i] +// let address = addresses[i] +// let id = ids[i] +// r[key] = fetch(address: address, id: id) +// i = i + i +// } +// return r +// } +// ` + +// const collate = px => { +// return Object.keys(px).reduce( +// (acc, key) => { +// acc.keys.push(key) +// acc.addresses.push(px[key][0]) +// acc.ids.push(px[key][1]) +// return acc +// }, +// {keys: [], addresses: [], ids: []} +// ) +// } + +// const {enqueue} = batch("FETCH_MARKET_ITEM", async px => { +// const {keys, addresses, ids} = collate(px) + +// // prettier-ignore +// return fcl.send([ +// fcl.script(CODE), +// fcl.args([ +// fcl.arg(keys, t.Array(t.String)), +// fcl.arg(addresses, t.Array(t.Address)), +// fcl.arg(ids.map(Number), t.Array(t.UInt64)), +// ]), +// ]).then(fcl.decode) +// }) + +// export async function fetchMarketItem(address, id) { +// if (address == null) return Promise.resolve(null) +// if (id == null) return Promise.resolve(null) +// return enqueue(address, id) +// } + +export async function fetchMarketItem(address, id) { + return fcl + .send([ + fcl.script` + import KittyItemsMarket from 0xfcceff21d9532b58 + + pub struct Item { + pub let id: UInt64 + pub let isCompleted: Bool + pub let price: UFix64 + pub let owner: Address + + init(id: UInt64, isCompleted: Bool, price: UFix64, owner: Address) { + self.id = id + self.isCompleted = isCompleted + self.price = price + self.owner = owner + } + } + + pub fun main(address: Address, id: UInt64): Item? { + let cap = getAccount(address) + .getCapability<&KittyItemsMarket.Collection{KittyItemsMarket.CollectionPublic}>(KittyItemsMarket.CollectionPublicPath)! + + if let collection = cap.borrow() { + let item = collection.borrowSaleItem(saleItemID: id) + return Item(id: id, isCompleted: item.saleCompleted, price: item.salePrice, owner: address) + } else { + return nil + } + } + `, + fcl.args([fcl.arg(address, t.Address), fcl.arg(Number(id), t.UInt64)]), + ]) + .then(fcl.decode) +} diff --git a/src/flow/fetch-market-items.script.js b/src/flow/fetch-market-items.script.js index 0ba48e0..e4a67c0 100644 --- a/src/flow/fetch-market-items.script.js +++ b/src/flow/fetch-market-items.script.js @@ -25,5 +25,5 @@ export function fetchMarketItems(address) { fcl.args([ fcl.arg(address, t.Address) ]) - ]).then(fcl.decode) + ]).then(fcl.decode).then(d => d.sort((a, b) => a - b)) } diff --git a/src/global/config.comp.js b/src/global/config.comp.js index 5f2ec8b..b350c38 100644 --- a/src/global/config.comp.js +++ b/src/global/config.comp.js @@ -13,8 +13,8 @@ export function Config() { .put("0xFungibleToken", "0x9a0766d93b6608b7") .put("0xNonFungibleToken", "0x631e88ae7f1d7c20") .put("0xKibble", CONTRACTS) - .put("0xKittyItems", CONTRACTS) .put("0xKittyItemsMarket", CONTRACTS) + .put("0xKittyItems", CONTRACTS) }, []) return null } diff --git a/src/hooks/use-market-item.hook.js b/src/hooks/use-market-item.hook.js new file mode 100644 index 0000000..cf45da9 --- /dev/null +++ b/src/hooks/use-market-item.hook.js @@ -0,0 +1,96 @@ +import {atomFamily, selectorFamily, useRecoilState} from "recoil" +import {sansPrefix} from "@onflow/fcl" +import {IDLE, PROCESSING} from "../global/constants" +import {useCurrentUser} from "../hooks/use-current-user.hook" +import {useAccountItems} from "../hooks/use-account-items.hook" +import {useMarketItems} from "../hooks/use-market-items.hook" +import {useKibblesBalance} from "../hooks/use-kibbles-balance.hook" +import {fetchMarketItem} from "../flow/fetch-market-item.script" +import {buyMarketItem} from "../flow/buy-market-item.tx" +import {cancelMarketListing} from "../flow/cancel-market-listing.tx" + +function expand(key) { + return key.split("|") +} + +function comp(address, id) { + return [address, id].join("|") +} + +export const $state = atomFamily({ + key: "market-item::state", + default: selectorFamily({ + key: "market-item::default", + get: key => async () => fetchMarketItem(...expand(key)), + }), +}) + +export const $status = atomFamily({ + key: "market-item::status", + default: IDLE, +}) + +export function useMarketItem(address, id) { + const [cu] = useCurrentUser() + const ownerItems = useAccountItems(address) + const cuItems = useAccountItems(cu.addr) + const ownerMarket = useMarketItems(address) + const cuMarket = useMarketItems(cu.addr) + const kibble = useKibblesBalance(cu.addr) + const key = comp(address, id) + const [item, setItem] = useRecoilState($state(key)) + const [status, setStatus] = useRecoilState($status(key)) + + const owned = sansPrefix(cu.addr) === sansPrefix(address) + + return { + ...item, + status, + owned, + async buy() { + await buyMarketItem( + {itemId: id, ownerAddress: address}, + { + onStart() { + setStatus(PROCESSING) + }, + async onSuccess() { + if (address !== cu.addr) { + ownerItems.refresh() + ownerMarket.refresh() + } + cuItems.refresh() + cuMarket.refresh() + kibble.refresh() + }, + async onComplete() { + setStatus(IDLE) + }, + } + ) + }, + async cancelListing() { + await cancelMarketListing( + {itemId: id}, + { + onStart() { + setStatus(PROCESSING) + }, + async onSuccess() { + cuItems.refresh() + cuMarket.refresh() + kibble.refresh() + }, + async onComplete() { + setStatus(IDLE) + }, + } + ) + }, + async refresh() { + setStatus(PROCESSING) + await fetchMarketItem(...expand(key)).then(setItem) + setStatus(IDLE) + }, + } +} diff --git a/src/pages/wip/qvvg.page.js b/src/pages/wip/qvvg.page.js index c5180c0..87041ef 100644 --- a/src/pages/wip/qvvg.page.js +++ b/src/pages/wip/qvvg.page.js @@ -21,6 +21,8 @@ export function Page() { +

Store

+ ) diff --git a/src/parts/account-item-cluster.comp.js b/src/parts/account-item-cluster.comp.js index 49fc5ca..fd32fb3 100644 --- a/src/parts/account-item-cluster.comp.js +++ b/src/parts/account-item-cluster.comp.js @@ -28,7 +28,10 @@ export function AccountItemCluster({address, id}) { Refetch {item.forSale || ( - )} diff --git a/src/parts/market-item-cluster.comp.js b/src/parts/market-item-cluster.comp.js new file mode 100644 index 0000000..ccc9b6f --- /dev/null +++ b/src/parts/market-item-cluster.comp.js @@ -0,0 +1,44 @@ +import {Suspense} from "react" +import {Loading} from "../parts/loading.comp" +import {useMarketItem} from "../hooks/use-market-item.hook" +import {useAccountItem} from "../hooks/use-account-item.hook" +import {Bar, Label, Button} from "../display/bar.comp" +import {IDLE} from "../global/constants" + +export function MarketItemCluster({address, id}) { + const item = useAccountItem(address, id) + const list = useMarketItem(address, id) + + const BUSY = item.status !== IDLE || list.status !== IDLE + + if (address == null) return null + if (id == null) return null + + return ( +
  • + + + + + + + + + + {BUSY && } + +
  • + ) +} + +export default function WrappedMarketItemCluster(props) { + return ( + }> + + + ) +} diff --git a/src/parts/market-items-cluster.comp.js b/src/parts/market-items-cluster.comp.js index 704c359..4d6beae 100644 --- a/src/parts/market-items-cluster.comp.js +++ b/src/parts/market-items-cluster.comp.js @@ -3,6 +3,7 @@ import {Bar, Label, Button} from "../display/bar.comp" import {Loading} from "../parts/loading.comp" import {useMarketItems} from "../hooks/use-market-items.hook" import {IDLE} from "../global/constants" +import Item from "./market-item-cluster.comp" export function MarketItemsCluster({address}) { const items = useMarketItems(address) @@ -24,7 +25,7 @@ export function MarketItemsCluster({address}) { {items.ids.length > 0 && (
      {items.ids.map(id => ( -
    • {id}
    • + ))}
    )}