diff --git a/src/frame/types.ts b/src/frame/types.ts index 4b0af76e2a..e7f48912f8 100644 --- a/src/frame/types.ts +++ b/src/frame/types.ts @@ -95,6 +95,7 @@ export type FrameButtonMetadata = | { action?: 'post' | 'post_redirect'; label: string; + postUrl?: string; target?: string; } | { diff --git a/src/frame/utils/getFrameMetadata.test.ts b/src/frame/utils/getFrameMetadata.test.ts index 81b3bd7a06..915f297ebd 100644 --- a/src/frame/utils/getFrameMetadata.test.ts +++ b/src/frame/utils/getFrameMetadata.test.ts @@ -217,6 +217,39 @@ describe('getFrameMetadata', () => { }); }); + it('should return the correct metadata with action post and post_url', () => { + expect( + getFrameMetadata({ + buttons: [ + { + label: 'Button1', + action: 'post', + postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX', + }, + { + label: 'Button2', + action: 'post', + postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY', + }, + ], + image: 'https://zizzamia.xyz/park-1.png', + postUrl: 'https://zizzamia.xyz/api/frame', + }), + ).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'Button1', + 'fc:frame:button:1:action': 'post', + 'fc:frame:button:1:post_url': + 'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX', + 'fc:frame:button:2': 'Button2', + 'fc:frame:button:2:action': 'post', + 'fc:frame:button:2:post_url': + 'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY', + 'fc:frame:image': 'https://zizzamia.xyz/park-1.png', + 'fc:frame:post_url': 'https://zizzamia.xyz/api/frame', + }); + }); + it('should not render action target if action is not link, mint or tx', () => { expect( getFrameMetadata({ diff --git a/src/frame/utils/getFrameMetadata.ts b/src/frame/utils/getFrameMetadata.ts index 23e75b4045..b20e103370 100644 --- a/src/frame/utils/getFrameMetadata.ts +++ b/src/frame/utils/getFrameMetadata.ts @@ -1,4 +1,5 @@ import type { FrameMetadataResponse, FrameMetadataType } from '../types'; +import { setFrameMetadataButtons } from './setFrameMetadataButtons'; /** * This function generates the metadata for a Farcaster Frame. @@ -36,18 +37,7 @@ export const getFrameMetadata = ({ metadata['fc:frame:input:text'] = input.text; } if (buttons) { - buttons.forEach((button, index) => { - metadata[`fc:frame:button:${index + 1}`] = button.label; - if (button.action) { - metadata[`fc:frame:button:${index + 1}:action`] = button.action; - } - if (button.target) { - metadata[`fc:frame:button:${index + 1}:target`] = button.target; - } - if (button.action && button.action === 'tx' && button.postUrl) { - metadata[`fc:frame:button:${index + 1}:post_url`] = button.postUrl; - } - }); + setFrameMetadataButtons(metadata, buttons); } if (postUrlToUse) { metadata['fc:frame:post_url'] = postUrlToUse; diff --git a/src/frame/utils/setFrameMetadataButtons.test.ts b/src/frame/utils/setFrameMetadataButtons.test.ts new file mode 100644 index 0000000000..a77ac62b47 --- /dev/null +++ b/src/frame/utils/setFrameMetadataButtons.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from 'vitest'; +import { setFrameMetadataButtons } from './setFrameMetadataButtons'; + +describe('setFrameMetadataButtons', () => { + it('should return no button metadata', () => { + const testMetadata = { + 'fc:frame': 'vNext', + }; + setFrameMetadataButtons(testMetadata, undefined); + + expect(testMetadata).toEqual({ + 'fc:frame': 'vNext', + }); + }); + + it('should return the correct metadata', () => { + const testMetadata = { + 'fc:frame': 'vNext', + }; + setFrameMetadataButtons(testMetadata, [ + { label: 'button1', action: 'post' }, + { label: 'button2', action: 'post_redirect' }, + { label: 'button3' }, + ]); + + expect(testMetadata).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'button1', + 'fc:frame:button:1:action': 'post', + 'fc:frame:button:2': 'button2', + 'fc:frame:button:2:action': 'post_redirect', + 'fc:frame:button:3': 'button3', + }); + }); + + it('should return the correct metadata for button with action tx and target', () => { + const testMetadata = { 'fc:frame': 'vNext' }; + setFrameMetadataButtons(testMetadata, [ + { + label: 'Button1', + action: 'tx', + target: 'https://zizzamia.xyz/api/frame/tx', + }, + ]); + + expect(testMetadata).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'Button1', + 'fc:frame:button:1:action': 'tx', + 'fc:frame:button:1:target': 'https://zizzamia.xyz/api/frame/tx', + }); + }); + + it('should return the correct metadata for button with action post and post_url', () => { + const testMetadata = { 'fc:frame': 'vNext' }; + setFrameMetadataButtons(testMetadata, [ + { + label: 'Button1', + action: 'post', + postUrl: 'https://zizzamia.xyz/api/frame/post_url', + }, + ]); + + expect(testMetadata).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'Button1', + 'fc:frame:button:1:action': 'post', + 'fc:frame:button:1:post_url': 'https://zizzamia.xyz/api/frame/post_url', + }); + }); + + it('should return the correct metadata for buttons with action tx and custom targets', () => { + const testMetadata = { 'fc:frame': 'vNext' }; + setFrameMetadataButtons(testMetadata, [ + { + label: 'Button1', + action: 'tx', + target: 'https://zizzamia.xyz/api/frame/tx?queryParam=XXX', + }, + { + label: 'Button2', + action: 'tx', + target: 'https://zizzamia.xyz/api/frame/tx?queryParam=YYY', + }, + ]); + + expect(testMetadata).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'Button1', + 'fc:frame:button:1:action': 'tx', + 'fc:frame:button:1:target': + 'https://zizzamia.xyz/api/frame/tx?queryParam=XXX', + 'fc:frame:button:2': 'Button2', + 'fc:frame:button:2:action': 'tx', + 'fc:frame:button:2:target': + 'https://zizzamia.xyz/api/frame/tx?queryParam=YYY', + }); + }); + + it('should return the correct metadata for buttons with action post and custom post_urls', () => { + const testMetadata = { 'fc:frame': 'vNext' }; + setFrameMetadataButtons(testMetadata, [ + { + label: 'Button1', + action: 'post', + postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX', + }, + { + label: 'Button2', + action: 'post', + postUrl: 'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY', + }, + ]); + expect(testMetadata).toEqual({ + 'fc:frame': 'vNext', + 'fc:frame:button:1': 'Button1', + 'fc:frame:button:1:action': 'post', + 'fc:frame:button:1:post_url': + 'https://zizzamia.xyz/api/frame/post-url?queryParam=XXX', + 'fc:frame:button:2': 'Button2', + 'fc:frame:button:2:action': 'post', + 'fc:frame:button:2:post_url': + 'https://zizzamia.xyz/api/frame/post-url?queryParam=YYY', + }); + }); +}); diff --git a/src/frame/utils/setFrameMetadataButtons.ts b/src/frame/utils/setFrameMetadataButtons.ts new file mode 100644 index 0000000000..d5a375931b --- /dev/null +++ b/src/frame/utils/setFrameMetadataButtons.ts @@ -0,0 +1,27 @@ +import type { FrameMetadataType } from '../types'; + +export function setFrameMetadataButtons( + metadata: Record, + buttons: FrameMetadataType['buttons'], +) { + if (!buttons) { + return; + } + + buttons.forEach((button, index) => { + metadata[`fc:frame:button:${index + 1}`] = button.label; + if (button.action) { + metadata[`fc:frame:button:${index + 1}:action`] = button.action; + } + if (button.target) { + metadata[`fc:frame:button:${index + 1}:target`] = button.target; + } + if ( + button.action && + (button.action === 'tx' || button.action === 'post') && + button.postUrl + ) { + metadata[`fc:frame:button:${index + 1}:post_url`] = button.postUrl; + } + }); +}