diff --git a/.changeset/light-rabbits-develop.md b/.changeset/light-rabbits-develop.md new file mode 100644 index 000000000..e7eeec645 --- /dev/null +++ b/.changeset/light-rabbits-develop.md @@ -0,0 +1,5 @@ +--- +"@rabbitholegg/questdk-plugin-pods": minor +--- + +implement getExternalUrl for pods diff --git a/packages/pods/src/Pods.test.ts b/packages/pods/src/Pods.test.ts index 78832edac..03509a891 100644 --- a/packages/pods/src/Pods.test.ts +++ b/packages/pods/src/Pods.test.ts @@ -4,11 +4,12 @@ import { type MintIntentParams, } from '@rabbitholegg/questdk-plugin-utils' import { apply } from '@rabbitholegg/questdk' -import { type Address, parseEther } from 'viem' +import { type Address, parseEther, getAddress, zeroAddress } from 'viem' import { describe, expect, test, vi } from 'vitest' -import { getMintIntent, mint } from './Pods' +import { getExternalUrl, getMintIntent, mint } from './Pods' import { failingTestCases, passingTestCases } from './test-setup' import { EXPECTED_ENCODED_DATA_1155 } from './test-transactions' +import { ZORA_DEPLOYER_ADDRESS } from './contract-addresses' describe('Given the pods plugin', () => { describe('When handling the mint', () => { @@ -201,3 +202,52 @@ describe('simulateMint function', () => { expect(request.value).toBe(value) }) }) + +describe('getExternalUrl function', () => { + test('should return correct url for mint w/tokenId and referral', async () => { + const params = { + chainId: Chains.BASE, + contractAddress: getAddress('0x7e0b40af1d6f26f2141b90170c513e57b5edd74e'), + tokenId: 21, + referral: getAddress('0x1234567890123456789012345678901234567890'), + } + const result = await getExternalUrl(params) + expect(result).toBe( + 'https://pods.media/mint-podcast/why-social-needs-a-layer-2-ft-ryan-li-of-cyber?referrer=0x1234567890123456789012345678901234567890', + ) + }) + + test('should return correct url for mint w/tokenId and w/o referral', async () => { + const params = { + chainId: Chains.BASE, + contractAddress: getAddress('0x7e0b40af1d6f26f2141b90170c513e57b5edd74e'), + tokenId: 21, + } + const result = await getExternalUrl(params) + expect(result).toBe( + `https://pods.media/mint-podcast/why-social-needs-a-layer-2-ft-ryan-li-of-cyber?referrer=${ZORA_DEPLOYER_ADDRESS}`, + ) + }) + + test('should return correct url for mint w/out tokenId', async () => { + const params = { + chainId: Chains.BASE, + contractAddress: getAddress('0x7e0b40af1d6f26f2141b90170c513e57b5edd74e'), + referral: getAddress('0x1234567890123456789012345678901234567890'), + } + const result = await getExternalUrl(params) + expect(result).toBe( + 'https://pods.media/mint-podcast?referrer=0x1234567890123456789012345678901234567890', + ) + }) + + test('should return fallback url if error occurs', async () => { + const params = { + chainId: Chains.BASE, + contractAddress: zeroAddress, + referral: getAddress('0x1234567890123456789012345678901234567890'), + } + const result = await getExternalUrl(params) + expect(result).toBe('https://pods.media') + }) +}) diff --git a/packages/pods/src/Pods.ts b/packages/pods/src/Pods.ts index aba53407d..19240a662 100644 --- a/packages/pods/src/Pods.ts +++ b/packages/pods/src/Pods.ts @@ -1,10 +1,11 @@ +import axios from 'axios' 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 { type AndArrayItem, getLatestTokenId } from './utils' +import { type AndArrayItem, getLatestTokenId, getUri } from './utils' import { type MintActionParams, type TransactionFilter, @@ -180,6 +181,38 @@ export const getFees = async ( } } +export const getExternalUrl = async ( + params: MintActionParams, +): Promise => { + const { chainId, contractAddress, tokenId, referral } = params + + try { + const client = createPublicClient({ + chain: chainIdToViemChain(chainId), + transport: http(), + }) as PublicClient + + const uri = await getUri(client, contractAddress, tokenId) + const cid = uri.split('/').slice(2).join('/') + + const { data } = await axios.get(`https://arweave.net/${cid}`) + + // different properties depending on uri function. One of these will be defined + const baseUrl = data.external_link ?? data.external_url + + return `${baseUrl}?referrer=${referral ?? ZORA_DEPLOYER_ADDRESS}` + } catch (error) { + console.error('an error occurred fetching data from the contract') + if (error instanceof Error) { + console.error(error.message) + } else { + console.error(error) + } + // fallback to default pods url + return 'https://pods.media' + } +} + export const getSupportedTokenAddresses = async ( _chainId: number, ): Promise => { diff --git a/packages/pods/src/index.ts b/packages/pods/src/index.ts index 167165fab..f1ba85a0f 100644 --- a/packages/pods/src/index.ts +++ b/packages/pods/src/index.ts @@ -6,6 +6,7 @@ import { } from '@rabbitholegg/questdk-plugin-utils' import { + getExternalUrl, getFees, getMintIntent, getProjectFees, @@ -13,7 +14,7 @@ import { getSupportedTokenAddresses, mint, simulateMint, -} from './Pods.js' +} from './Pods' export const Pods: IActionPlugin = { pluginId: 'pods', @@ -24,6 +25,8 @@ export const Pods: IActionPlugin = { mint, getProjectFees: async (params: ActionParams) => getProjectFees(params as unknown as MintActionParams), + getExternalUrl: async (params: ActionParams) => + getExternalUrl(params as unknown as MintActionParams), getFees: async (params: ActionParams) => getFees(params as unknown as MintActionParams), getMintIntent, diff --git a/packages/pods/src/utils.ts b/packages/pods/src/utils.ts index 9c1631c8c..d12e6b079 100644 --- a/packages/pods/src/utils.ts +++ b/packages/pods/src/utils.ts @@ -35,3 +35,40 @@ export async function getLatestTokenId( return 1 } } + +export async function getUri( + client: PublicClient, + contractAddress: Address, + tokenId?: number, +): Promise { + if (tokenId == null) { + return (await client.readContract({ + address: contractAddress, + abi: [ + { + inputs: [], + name: 'contractURI', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'contractURI', + })) as string + } + + return (await client.readContract({ + address: contractAddress, + abi: [ + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'uri', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + ], + functionName: 'uri', + args: [BigInt(tokenId)], + })) as string +}