Marketplace provider
The MarketProvider
follows the same principles as the TzombiesProvider
. This page will cover only the specifics for this provider. If you would like to view the full code, it is available on the repository.
Provider base
The props:
interface ListingParameters {
tokenId: number
amount: number
price: number
expiry: Date
}
interface Listing {
saleId: number
seller: Address
parameters: ListingParameters
}
interface MarketProviderContextProps {
market?: Market
isApproved: boolean
listings: Listing[]
approve: () => Promise<void>
revoke: () => Promise<void>
list_for_sale: (params: ListingParameters) => Promise<CallResult | undefined>
remove_listing: (listing: Listing) => Promise<CallResult | undefined>
buy: (listing: Listing, amount: number) => Promise<CallResult | undefined>
fetchMarketplaceApproval: () => Promise<void>
fetchListings: () => Promise<void>
}
market
is the marketplace contract instanceisApproved
and sales is the provider's stateapprove
,revoke
control the marketplace operator status for the connected walletlist_for_sale
, remove_listing andbuy
are the contract entry pointsfetchMarketplaceApproval
and fetchListings update the state
As for the FA2 contract provider, we initialize it at load:
useEffect(() => {
if (!Tezos) {
return
}
setMarket(new Market(process.env.NEXT_PUBLIC_MARKET_ADDRESS))
}, [Tezos])
Fetch approval
This one is a simple contract read.
const fetchMarketplaceApproval = useCallback(async () => {
if (!account || !fa2) {
setIsApproved(false)
return
}
const arg = new operator_for_all_key(
new Address(process.env.NEXT_PUBLIC_MARKET_ADDRESS!),
new Address(account.address)
)
const approved = await fa2.get_operator_for_all_value(arg)
setIsApproved(!!approved)
}, [account, fa2])
Fetch sales
This one is a bit more tricky. If you remember, the marketplace contract does not (and cannot) check all aspects of valid orders, as it is loosely coupled with the token contract. We need to filter out empty or expired orders.
We have a counter: next_order_id.
We'll use it to iterate over the orders.
const fetchListings = useCallback(async () => {
if (!market || !fa2 || !fa2.address) return
const nOrders = await market.get_next_order_id()
const listings: Listing[] = []
const inventories: Map<Address, UserInventory> = new Map()
for (let i = 1; i < nOrders.to_number(); i++) {
const order = await market.get_order_value(new Nat(i))
if (!order) continue
// filter out expired orders
if (new Date(order.expiry) < new Date()) continue
// check how many tokens the seller still has
if (!inventories.has(order.seller)) {
inventories.set(order.seller, await fetchFa2Balance(order.seller))
}
const amount = inventories
.get(order.seller)
?.get(order.token_id.to_number())
const qty = Math.min(order.amount.to_number(), amount ?? 0)
if (qty < 1) continue
listings.push({
saleId: i,
seller: order.seller,
parameters: {
tokenId: order.token_id.to_number(),
amount: qty,
price: order.price.to_big_number().toNumber(),
expiry: order.expiry,
},
})
}
setListings(listings)
}, [fa2, fetchFa2Balance, market])
Fetch on load
As usual, we fetch the state on component load:
useEffect(() => {
fetchListings()
}, [fetchListings])
useEffect(() => {
fetchMarketplaceApproval()
}, [fetchMarketplaceApproval])
Entry points
Each entrypoint is called using the generated bindings.
approve / revoke
const approve = useCallback(async () => {
if (!fa2 || !market) return
const arg = new add_for_all(new Address(market.address!))
await fa2.update_operators_for_all([arg], {})
}, [fa2, market])
const revoke = useCallback(async () => {
if (!fa2 || !market) return
const arg = new remove_for_all(new Address(market.address!))
await fa2.update_operators_for_all([arg], {})
}, [fa2, market])
list_for_sale / remove_listing / buy
const list_for_sale = useCallback<
(params: ListingParameters) => Promise<CallResult | undefined>
>(
async ({ tokenId, amount, price, expiry }: ListingParameters) => {
if (!market || !fa2 || !fa2.address) return
return await market.list_for_sale(
new Address(fa2.address),
new Nat(tokenId),
new Nat(amount),
new Tez(price),
expiry,
{}
)
},
[market, fa2]
)
const remove_listing = useCallback(
async (listing: Listing) => {
if (!market || !fa2 || !fa2.address) return
return await market.remove_listing(new Nat(listing.saleId), {})
},
[market, fa2]
)
const buy = useCallback(
async (listing: Listing, amount: number) => {
if (!market || !fa2 || !fa2.address) return
return await market.buy(new Nat(listing.saleId), new Nat(amount), {
amount: new Tez(listing.parameters.price * amount, 'mutez'),
})
},
[market, fa2]
)
Wrap up
Memoise the value and pass it to the children:
const value = useMemo(
() => ({
market,
isApproved,
listings,
approve,
revoke,
list_for_sale,
remove_listing,
buy,
fetchMarketplaceApproval,
fetchListings,
}),
[
market,
isApproved,
listings,
approve,
revoke,
list_for_sale,
remove_listing,
buy,
fetchMarketplaceApproval,
fetchListings,
]
)
return (
<MarketProviderContext.Provider value={value}>
{children}
</MarketProviderContext.Provider>
)
Export
Export the component.
export { MarketProvider, useMarketProviderContext }
export type { Listing }
Include the provider in the App hierarchy, with access to the wallet and Tzombies contexts.
Last updated