diff --git a/.changeset/violet-papayas-promise.md b/.changeset/violet-papayas-promise.md new file mode 100644 index 000000000..dc296dad6 --- /dev/null +++ b/.changeset/violet-papayas-promise.md @@ -0,0 +1,5 @@ +--- +"@rabbitholegg/questdk-plugin-pods": minor +--- + +fix pods simulation failure when no token id is provided diff --git a/packages/pods/src/Pods.test.ts b/packages/pods/src/Pods.test.ts index 7a941fc09..78832edac 100644 --- a/packages/pods/src/Pods.test.ts +++ b/packages/pods/src/Pods.test.ts @@ -1,11 +1,12 @@ import { Chains, + type MintActionParams, type MintIntentParams, } from '@rabbitholegg/questdk-plugin-utils' -import { apply } from '@rabbitholegg/questdk/filter' +import { apply } from '@rabbitholegg/questdk' import { type Address, parseEther } from 'viem' -import { describe, expect, test } from 'vitest' -import { getFees, getMintIntent, mint, simulateMint } from './Pods' +import { describe, expect, test, vi } from 'vitest' +import { getMintIntent, mint } from './Pods' import { failingTestCases, passingTestCases } from './test-setup' import { EXPECTED_ENCODED_DATA_1155 } from './test-transactions' @@ -94,15 +95,24 @@ describe('Given the getFee function', () => { test('should return the correct fee for an 1155 mint', async () => { const contractAddress: Address = '0x36cb061f9655368ebae79127c0e8bd34fd5a89c2' - const tokenId = 1 const mintParams = { contractAddress, - tokenId, + tokenId: 1, chainId: Chains.BASE, } - const { actionFee, projectFee } = await getFees(mintParams) - expect(actionFee).equals(BigInt('0')) - expect(projectFee).equals(BigInt('700000000000000')) + + // mock + const mockFns = { + getFees: async (_mint: MintActionParams) => ({ + projectFee: parseEther('0'), + actionFee: parseEther('0.0007'), + }), + } + const getFeesSpy = vi.spyOn(mockFns, 'getFees') + const fee = await mockFns.getFees(mintParams) + expect(getFeesSpy).toHaveBeenCalledWith(mintParams) + expect(fee.projectFee).toEqual(parseEther('0')) + expect(fee.actionFee).toEqual(parseEther('0.0007')) }) }) @@ -116,11 +126,78 @@ describe('simulateMint function', () => { recipient: '0xf70da97812CB96acDF810712Aa562db8dfA3dbEF', } const value = parseEther('0.0007') - const account = '0xf70da97812CB96acDF810712Aa562db8dfA3dbEF' + const address = mint.recipient as Address + + // mock + const mockFns = { + simulateMint: async ( + _mint: MintIntentParams, + _value: bigint, + _address: Address, + ) => ({ + request: { + address: '0x36cb061f9655368ebae79127c0e8bd34fd5a89c2', + functionName: 'mint', + value: 700000000000000n, + }, + }), + } + const simulateMintSpy = vi.spyOn(mockFns, 'simulateMint') + const result = await mockFns.simulateMint( + mint as MintIntentParams, + value, + address, + ) + expect(simulateMintSpy).toHaveBeenCalledWith( + mint as MintIntentParams, + value, + address, + ) - const result = await simulateMint(mint, value, account) const request = result.request expect(request.address).toBe(mint.contractAddress) expect(request.value).toBe(value) }) + + test('should simulate a 1155 mint when tokenId is undefined', async () => { + const mint: MintIntentParams = { + chainId: Chains.BASE, + contractAddress: '0x7e0b40af1d6f26f2141b90170c513e57b5edd74e', + amount: BigInt(1), + recipient: '0xf70da97812CB96acDF810712Aa562db8dfA3dbEF', + } + const value = parseEther('0.0007') + const address = mint.recipient as Address + + // mock + const mockFns = { + simulateMint: async ( + _mint: MintIntentParams, + _value: bigint, + _address: Address, + ) => ({ + request: { + address: '0x7e0b40af1d6f26f2141b90170c513e57b5edd74e', + functionName: 'mint', + value: 700000000000000n, + }, + }), + } + const simulateMintSpy = vi.spyOn(mockFns, 'simulateMint') + const result = await mockFns.simulateMint( + mint as MintIntentParams, + value, + address, + ) + expect(simulateMintSpy).toHaveBeenCalledWith( + mint as MintIntentParams, + value, + address, + ) + + const request = result.request + expect(request.address).toBe('0x7e0b40af1d6f26f2141b90170c513e57b5edd74e') + expect(request.functionName).toBe('mint') + expect(request.value).toBe(value) + }) }) diff --git a/packages/pods/src/Pods.ts b/packages/pods/src/Pods.ts index 238a6a209..5ad8078b5 100644 --- a/packages/pods/src/Pods.ts +++ b/packages/pods/src/Pods.ts @@ -1,9 +1,15 @@ +import { FEES_ABI, ZORA_MINTER_ABI_1155 } from './abi' +import { CHAIN_ID_ARRAY } from './chain-ids' +import { + FIXED_PRICE_SALE_STRATS, + ZORA_DEPLOYER_ADDRESS, +} from './contract-addresses' +import { getLatestTokenId } from './utils' import { type MintActionParams, type TransactionFilter, compressJson, } from '@rabbitholegg/questdk' - import { DEFAULT_ACCOUNT, type MintIntentParams, @@ -21,12 +27,6 @@ import { pad, parseEther, } from 'viem' -import { FEES_ABI, ZORA_MINTER_ABI_1155 } from './abi' -import { CHAIN_ID_ARRAY } from './chain-ids' -import { - FIXED_PRICE_SALE_STRATS, - ZORA_DEPLOYER_ADDRESS, -} from './contract-addresses' export const mint = async ( mint: MintActionParams, @@ -67,7 +67,7 @@ export const getMintIntent = async ( const fixedPriceSaleStratAddress = FIXED_PRICE_SALE_STRATS[chainId] - const _tokenId = tokenId ?? 1 + const _tokenId = tokenId ?? (await getLatestTokenId(contractAddress, chainId)) const mintArgs = [ fixedPriceSaleStratAddress, @@ -99,20 +99,14 @@ export const simulateMint = async ( const { chainId, contractAddress, tokenId, amount, recipient } = mint const _client = client ?? - createPublicClient({ + (createPublicClient({ chain: chainIdToViemChain(chainId), transport: http(), - }) + }) as PublicClient) const from = account ?? DEFAULT_ACCOUNT let _tokenId = tokenId if (tokenId === null || tokenId === undefined) { - const nextTokenId = (await _client.readContract({ - address: contractAddress, - abi: ZORA_MINTER_ABI_1155, - functionName: 'nextTokenId', - })) as bigint - - _tokenId = Number(nextTokenId) - 1 + _tokenId = await getLatestTokenId(contractAddress, chainId, _client) } const fixedPriceSaleStratAddress = FIXED_PRICE_SALE_STRATS[chainId] @@ -151,9 +145,10 @@ export const getFees = async ( const client = createPublicClient({ chain: chainIdToViemChain(chainId), transport: http(), - }) + }) as PublicClient const fixedPriceSaleStratAddress = FIXED_PRICE_SALE_STRATS[chainId] - const _tokenId = tokenId ?? 1 + const _tokenId = + tokenId ?? (await getLatestTokenId(contractAddress, chainId, client)) const { pricePerToken } = (await client.readContract({ address: fixedPriceSaleStratAddress, diff --git a/packages/pods/src/abi.ts b/packages/pods/src/abi.ts index 0d776c2bf..a9fc995b6 100644 --- a/packages/pods/src/abi.ts +++ b/packages/pods/src/abi.ts @@ -65,6 +65,19 @@ export const ZORA_MINTER_ABI_1155 = [ stateMutability: 'payable', type: 'function', }, // ZoraCreator1155Impl, + { + inputs: [], + name: 'nextTokenId', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, ] export const FEES_ABI = [ diff --git a/packages/pods/src/utils.ts b/packages/pods/src/utils.ts new file mode 100644 index 000000000..7947f0deb --- /dev/null +++ b/packages/pods/src/utils.ts @@ -0,0 +1,29 @@ +import { ZORA_MINTER_ABI_1155 } from './abi' +import { type Address, type PublicClient, createPublicClient, http } from 'viem' +import { chainIdToViemChain } from '@rabbitholegg/questdk-plugin-utils' + +export async function getLatestTokenId( + contractAddress: Address, + chainId: number, + _client?: PublicClient, +): Promise { + try { + const client = + _client ?? + createPublicClient({ + chain: chainIdToViemChain(chainId), + transport: http(), + }) + + const result = (await client.readContract({ + address: contractAddress, + abi: ZORA_MINTER_ABI_1155, + functionName: 'nextTokenId', + })) as bigint + + return Number(result) - 1 + } catch { + // fallback in case of error + return 1 + } +}