From f0715d4b8c4b3505698bc90d75ac039ba4bffa7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20S=C3=A1ez?= Date: Fri, 20 Oct 2023 23:08:25 -0400 Subject: [PATCH] sam example finished --- examples/nextjs/src/app/v1/page.tsx | 7 +- examples/nextjs/src/app/v1/sam/page.tsx | 196 +++++++++++++++--- examples/nextjs/src/app/v2/page.tsx | 7 +- .../nextjs/src/components/editionInfo.tsx | 3 +- .../src/contract/edition-v1/read/actions.ts | 20 +- .../sdk/src/contract/edition-v1/read/sam.ts | 46 ++-- .../src/contract/edition-v1/write/actions.ts | 16 +- .../sdk/src/contract/edition-v1/write/sam.ts | 16 ++ 8 files changed, 251 insertions(+), 60 deletions(-) create mode 100644 packages/sdk/src/contract/edition-v1/write/sam.ts diff --git a/examples/nextjs/src/app/v1/page.tsx b/examples/nextjs/src/app/v1/page.tsx index 4d6c17c0..20f6a27d 100644 --- a/examples/nextjs/src/app/v1/page.tsx +++ b/examples/nextjs/src/app/v1/page.tsx @@ -101,7 +101,12 @@ function EditionSchedule({ schedule }: { schedule: MintSchedule }) { Quantity - setQuantity(ev.target.valueAsNumber)} /> + setQuantity(ev.target.valueAsNumber)} + /> {mintParameters?.mint.type === 'mint' && Price {formatEther(mintParameters.mint.input.value)} ETH} diff --git a/examples/nextjs/src/app/v1/sam/page.tsx b/examples/nextjs/src/app/v1/sam/page.tsx index d5a1d86c..a27ea718 100644 --- a/examples/nextjs/src/app/v1/sam/page.tsx +++ b/examples/nextjs/src/app/v1/sam/page.tsx @@ -5,12 +5,15 @@ import { Embed } from '@/components/iframe' import { Spinner } from '@/components/spinner' import { WalletPrivateKeyInput } from '@/components/walletInput' +import { queryClient } from '@/context/reactQuery' import { soundApi } from '@/context/sound' import { publicClient } from '@/context/wagmi' import { useWallet } from '@/context/wallet' import { useEditionVersion } from '@/hooks/edition' -import { Box, Link, Text, TextFieldInput } from '@radix-ui/themes' -import { useQuery } from '@tanstack/react-query' +import { Box, Button, Link, Text, TextFieldInput } from '@radix-ui/themes' +import { retryAsync } from '@soundxyz/sdk/utils/helpers' +import { useMutation, useQuery } from '@tanstack/react-query' +import assert from 'assert' import { useState } from 'react' import { formatEther } from 'viem' @@ -66,9 +69,9 @@ export default function EditionV1SAM() { const { wallet } = useWallet() - const [quantity, setQuantity] = useState(1) + const [quantityInput, setQuantity] = useState(1) - const quantityNumber = Number.isSafeInteger(quantity) && quantity > 0 ? quantity : null + const quantity = Number.isSafeInteger(quantityInput) && quantityInput > 0 ? quantityInput : null const { data: ownedTokens } = useQuery({ queryKey: [EDITION_V1, 'owned-tokens', contractAddress, wallet?.account.address], @@ -82,43 +85,163 @@ export default function EditionV1SAM() { sort: { serialNumber: 'DESC', }, + filter: { + includeGoldenEgg: false, + }, }) .then((v) => v.data) }, + refetchInterval: 1000, }) + const samInfoSupply = samInfo?.supply + const ownedTokensLength = ownedTokens?.length + const { data: sellPrice } = useQuery({ - queryKey: [EDITION_V1, 'sam-sell-price', contractAddress, samAddress, quantityNumber], + queryKey: [EDITION_V1, 'sam-sell-price', contractAddress, samAddress, quantity, ownedTokensLength, samInfoSupply], queryFn() { - if (!wallet || !samAddress || !quantityNumber) return null - - return publicClient.editionV1.sam.sell.sellPrice({ + if ( + !wallet || + !samAddress || + !ownedTokensLength || + !quantity || + ownedTokensLength < quantity || + !samInfoSupply || + samInfoSupply < quantity + ) { + return null + } + + return publicClient.editionV1.sam.sellPrice({ editionAddress: contractAddress, samAddress, - })({ - offset: 1000, - quantity: quantityNumber, + offset: 0, + quantity, }) }, }) const { data: buyPrice } = useQuery({ - queryKey: [EDITION_V1, 'sam-buy-price', contractAddress, samAddress, quantityNumber], + queryKey: [EDITION_V1, 'sam-buy-price', contractAddress, samAddress, quantity], queryFn() { - if (!wallet || !samAddress || !quantityNumber) return null + if (!wallet || !samAddress || !quantity) return null - return publicClient.editionV1.sam.buy + return publicClient.editionV1.sam .buyPrice({ editionAddress: contractAddress, samAddress, - })({ - offset: 1000, - quantity: quantityNumber, + offset: 0, + quantity, }) .then((v) => v.total) }, }) + const [message, setMessage] = useState('') + + const { data: samBuyParams } = useQuery({ + queryKey: [EDITION_V1, 'sam-buy', contractAddress, samAddress, buyPrice?.toString()], + queryFn() { + if (!wallet || !samAddress || !buyPrice || !quantity) return null + + return publicClient.editionV1.sam.buyParameters({ + editionAddress: contractAddress, + samAddress, + + account: wallet.account, + chain: wallet.walletClient.chain, + maxTotalValue: buyPrice, + mintTo: wallet.account.address, + quantity, + }) + }, + }) + + const { mutate: samBuy, isPending: samBuyIsPending } = useMutation({ + async mutationFn() { + assert(samBuyParams?.type === 'mint' && wallet && quantity) + + setMessage(`Buying ${quantity}...`) + + const hash = await wallet.walletClient.editionV1.sam.buy(samBuyParams) + + setMessage('Waiting for transaction...') + + const receipt = await retryAsync( + () => + publicClient.getTransactionReceipt({ + hash, + }), + { + attempts: 10, + interval: 500, + }, + ) + + setMessage(`- Successfully minted ${quantity} -`) + + if (receipt.status !== 'success') throw Error('Transaction failed') + }, + onSuccess() { + queryClient.invalidateQueries({ + queryKey: [EDITION_V1], + }) + }, + }) + + const tokenIdsToSell = quantity ? ownedTokens?.slice(0, quantity) : null + + const { data: samSellParams } = useQuery({ + queryKey: [EDITION_V1, 'sam-sell', contractAddress, samAddress, quantity, sellPrice?.toString(), tokenIdsToSell], + queryFn() { + if (!wallet || !samAddress || !sellPrice || !quantity || !tokenIdsToSell || tokenIdsToSell.length !== quantity) + return null + + return publicClient.editionV1.sam.sellParameters({ + editionAddress: contractAddress, + samAddress, + + account: wallet.account, + chain: wallet.walletClient.chain, + + minimumPayout: sellPrice, + tokenIds: tokenIdsToSell, + }) + }, + }) + + const { mutate: samSell, isPending: samSellIsPending } = useMutation({ + async mutationFn() { + assert(samSellParams?.type === 'available' && wallet && quantity) + + setMessage(`Selling ${quantity}...`) + + const hash = await wallet.walletClient.editionV1.sam.sell(samSellParams) + + setMessage('Waiting for transaction...') + + const receipt = await retryAsync( + () => + publicClient.getTransactionReceipt({ + hash, + }), + { + attempts: 10, + interval: 500, + }, + ) + + setMessage(`- Successfully sold ${quantity} -`) + + if (receipt.status !== 'success') throw Error('Transaction failed') + }, + onSuccess() { + queryClient.invalidateQueries({ + queryKey: [EDITION_V1], + }) + }, + }) + return (
@@ -130,36 +253,49 @@ export default function EditionV1SAM() { ) : ( <>

{soundEditionInfo.name}

- {soundEditionApi.data.webappUri} -
- Cover Image Cover Image - Golden Egg Egg Game Image - Sam Address {samAddress} - - Supply: {samInfo.supply} - + SAM Supply: {samInfo.supply} {ownedTokens && Owned Tokens: {ownedTokens.join()}} + {message ? {message} : null} + Quantity - setQuantity(ev.target.valueAsNumber)} /> + setQuantity(ev.target.valueAsNumber)} + /> - - {buyPrice != null ? Buy Price: {Number(formatEther(buyPrice)).toPrecision(5)} ETH : null} - - {sellPrice != null ? Sell Price: {Number(formatEther(sellPrice)).toPrecision(5)} ETH : null} + Buy Price: {buyPrice ? Number(formatEther(buyPrice)).toPrecision(5) : '...'} ETH + + Sell Price: {sellPrice ? Number(formatEther(sellPrice)).toPrecision(5) : '...'} ETH + + )}
diff --git a/examples/nextjs/src/app/v2/page.tsx b/examples/nextjs/src/app/v2/page.tsx index 032a3054..af71638e 100644 --- a/examples/nextjs/src/app/v2/page.tsx +++ b/examples/nextjs/src/app/v2/page.tsx @@ -114,7 +114,12 @@ function EditionSchedule({ schedule }: { schedule: SuperMinterSchedule }) { Quantity - setQuantity(ev.target.valueAsNumber)} /> + setQuantity(ev.target.valueAsNumber)} + /> {mintParameters?.mint.type === 'mint' && Price: {formatEther(mintParameters.mint.input.value)} ETH} diff --git a/examples/nextjs/src/components/editionInfo.tsx b/examples/nextjs/src/components/editionInfo.tsx index 527485db..bbf348d2 100644 --- a/examples/nextjs/src/components/editionInfo.tsx +++ b/examples/nextjs/src/components/editionInfo.tsx @@ -105,10 +105,9 @@ export function EditionInfo() { async queryFn() { if (!samAddress || !contractAddress) return null - return publicClient.editionV1.sam.buy.buyPrice({ + return publicClient.editionV1.sam.buyPrice({ editionAddress: contractAddress, samAddress, - })({ offset: 0, quantity: 1, }) diff --git a/packages/sdk/src/contract/edition-v1/read/actions.ts b/packages/sdk/src/contract/edition-v1/read/actions.ts index fd554e0f..c6b19f3e 100644 --- a/packages/sdk/src/contract/edition-v1/read/actions.ts +++ b/packages/sdk/src/contract/edition-v1/read/actions.ts @@ -24,7 +24,12 @@ export function editionV1PublicActions< Client extends Pick< PublicClient, 'readContract' | 'multicall' | 'estimateContractGas' | 'createEventFilter' | 'getFilterLogs' - > & { editionV1?: {}; merkleProvider: MerkleProvider }, + > & { + editionV1?: { + sam?: {} + } + merkleProvider: MerkleProvider + }, >(client: Client) { return { editionV1: { @@ -48,19 +53,16 @@ export function editionV1PublicActions< mintSchedulesFromIds: curry(editionMintSchedulesFromIds)(client), sam: { + ...client.editionV1?.sam, samAddress: curry(SamContractAddress)(client), info: curry(SamEditionInfo)(client), - sell: { - sellParameters: curry(SamSellParameters)(client), - sellPrice: curry(SamTotalSellPrice)(client), - }, + sellParameters: curry(SamSellParameters)(client), + sellPrice: curry(SamTotalSellPrice)(client), - buy: { - buyParameters: curry(SamBuyParameters)(client), - buyPrice: curry(SamTotalBuyPrice)(client), - }, + buyParameters: curry(SamBuyParameters)(client), + buyPrice: curry(SamTotalBuyPrice)(client), }, }, } diff --git a/packages/sdk/src/contract/edition-v1/read/sam.ts b/packages/sdk/src/contract/edition-v1/read/sam.ts index a55d880f..110f8842 100644 --- a/packages/sdk/src/contract/edition-v1/read/sam.ts +++ b/packages/sdk/src/contract/edition-v1/read/sam.ts @@ -2,7 +2,7 @@ import type { Account, Address, Chain, Hex, PublicClient } from 'viem' import { isSoundV1_2 } from './interface' import { soundEditionV1_2Abi } from '../abi/sound-edition-v1_2' import { MINT_FALLBACK_GAS_LIMIT, MINT_GAS_LIMIT_MULTIPLIER, NULL_ADDRESS, scaleAmount } from '../../../utils/helpers' -import type { TransactionGasOptions } from '../../../utils/types' +import type { TransactionGasOptions, TypeFromUnion } from '../../../utils/types' import { samv1Abi } from '../abi/sam-v1' import { InvalidOffsetError, InvalidQuantityError, UnsupportedMinterError } from '../../../utils/errors' import { interfaceIds } from '../interfaceIds' @@ -13,7 +13,7 @@ export interface SamEditionAddress { samAddress: Address } -export interface SamBuyOptions extends TransactionGasOptions { +export interface SamBuyOptions extends TransactionGasOptions, SamEditionAddress { account: Address | Account mintTo: Address @@ -33,8 +33,8 @@ export interface SamBuyOptions extends TransactionGasOptions { chain: Chain } -export interface SamSellOptions extends TransactionGasOptions { - userAddress: Address +export interface SamSellOptions extends TransactionGasOptions, SamEditionAddress { + account: Address | Account /** * Chain of expected edition to be minted @@ -67,10 +67,11 @@ export async function SamContractAddress>( client: Client, - { editionAddress, samAddress }: SamEditionAddress, { - userAddress, + editionAddress, + samAddress, + account, tokenIds, minimumPayout, @@ -100,11 +101,17 @@ export async function SamSellParameters>( client: Client, - { editionAddress, samAddress }: SamEditionAddress, { + editionAddress, + samAddress, quantity, account, @@ -197,10 +205,14 @@ export async function SamBuyParameters>( client: Client, - { editionAddress, samAddress }: SamEditionAddress, - { offset, quantity }: { offset: number; quantity: number }, + { editionAddress, samAddress, offset, quantity }: SamSellPriceInput, ) { if (typeof quantity !== 'number' || !Number.isInteger(quantity) || quantity <= 0) throw new InvalidQuantityError({ quantity }) @@ -215,10 +227,14 @@ export async function SamTotalSellPrice>( client: Client, - { editionAddress, samAddress }: SamEditionAddress, - { offset, quantity }: { offset: number; quantity: number }, + { editionAddress, samAddress, offset, quantity }: SamBuyPriceInput, ) { if (typeof quantity !== 'number' || !Number.isInteger(quantity) || quantity <= 0) throw new InvalidQuantityError({ quantity }) @@ -261,7 +277,6 @@ export interface SAM { export async function SamEditionInfo>( client: Client, - { editionAddress, samAddress }: SamEditionAddress, ): Promise { const interfaceId = await client.readContract({ @@ -305,3 +320,6 @@ export async function SamEditionInfo>, 'mint'> +export type SamSellContractInput = TypeFromUnion>, 'available'> diff --git a/packages/sdk/src/contract/edition-v1/write/actions.ts b/packages/sdk/src/contract/edition-v1/write/actions.ts index 78c68a9b..20a5edb6 100644 --- a/packages/sdk/src/contract/edition-v1/write/actions.ts +++ b/packages/sdk/src/contract/edition-v1/write/actions.ts @@ -1,15 +1,25 @@ import type { WalletClient } from 'viem' import { curry } from '../../../utils/helpers' import { editionMint, editionMintTo } from './mint' +import { SamBuy, SamSell } from './sam' -export function editionV1WalletActions & { editionV1?: {} }>( - client: Client, -) { +export function editionV1WalletActions< + Client extends Pick & { + editionV1?: { + sam?: {} + } + }, +>(client: Client) { return { editionV1: { ...client.editionV1, mint: curry(editionMint)(client), mintTo: curry(editionMintTo)(client), + sam: { + ...client.editionV1?.sam, + sell: curry(SamSell)(client), + buy: curry(SamBuy)(client), + }, }, } } diff --git a/packages/sdk/src/contract/edition-v1/write/sam.ts b/packages/sdk/src/contract/edition-v1/write/sam.ts new file mode 100644 index 00000000..2859a822 --- /dev/null +++ b/packages/sdk/src/contract/edition-v1/write/sam.ts @@ -0,0 +1,16 @@ +import type { WalletClient } from 'viem' +import type { SamBuyContractInput, SamSellContractInput } from '../read/sam' + +export function SamBuy>( + client: Client, + { input }: SamBuyContractInput, +) { + return client.writeContract(input) +} + +export function SamSell>( + client: Client, + { input }: SamSellContractInput, +) { + return client.writeContract(input) +}