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
-
Golden Egg
-
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)
+}