
Solana Frames: Farcaster에서 cNFT 민팅하기
이 글에서 다루는 내용
탈중앙화 소셜 미디어는 사용자에게 온라인 데이터에 대한 더 강력한 개인정보 보호와 통제권을 제공하는 중요한 변화입니다. 기존 소셜 미디어 플랫폼과 달리 탈중앙화 네트워크에서는 개인이 소셜 그래프와 디지털 신원을 직접 관리할 수 있습니다. Farcaster 같은 플랫폼은 검열 저항성을 갖춘 네트워크를 구축하며 이러한 변화를 이끌고 있습니다. 여러 플랫폼에서 개방적이고 상호 운용 가능한 커뮤니케이션을 지원합니다. Farcaster의 핵심 혁신은 일반 소셜 미디어 게시물을 완전한 애플리케이션으로 전환하는 프레임입니다. 이를 통해 온라인 소셜 경험이 한층 풍부해집니다.
이 글에서는 Farcaster와 최근 추가된 Solana 지원을 살펴봅니다. Farcaster와 프레임이 무엇인지, Solana 주소를 인증하는 방법은 무엇인지 알아봅니다. 그런 다음 Helius Mint API를 사용해 사용자의 인증된 Solana 주소로 cNFT를 민팅하는 프레임을 만드는 짧은 튜토리얼을 진행합니다. 이 튜토리얼은 TypeScript와 Next.js 사용 경험이 있다고 가정합니다.
Farcaster란?
Farcaster는 Ethereum 네트워크를 활용하는 충분히 탈중앙화된 소셜 네트워크입니다. Twitter나 Reddit 같은 인기 소셜 네트워크와 유사한 공개 플랫폼으로, 사용자는 프로필을 만들고 *캐스트(cast)*라고 부르는 게시물을 공유하며 다른 사용자를 팔로우할 수 있습니다. Farcaster의 차별점은 사용자의 소유권과 이동성을 중시한다는 데 있습니다. 사용자는 자신의 계정과 다른 사람과의 관계를 직접 소유합니다. 디지털 신원과 소셜 그래프를 유지하면서 여러 애플리케이션 사이를 자유롭게 이동할 수 있습니다.
프레임이란?
프레임을 사용하면 개발자는 모든 캐스트를 대화형 애플리케이션으로 바꿀 수 있습니다. OpenGraph 표준을 확장해 정적인 임베드를 동적인 대화형 경험으로 전환합니다. 사용자는 Warpcaster를 비롯한 모든 Farcaster 클라이언트에서 설문 조사, 라이브 피드, 대화형 갤러리 같은 기능을 만들 수 있습니다. 기본적으로 프레임은 HTML 페이지의 <head>와 함께 반환되는 <meta> 태그 집합입니다. 페이지에 필요한 프레임 속성이 모두 포함되어 있으면 Farcaster 앱은 해당 페이지를 프레임으로 렌더링합니다. 프레임 사양에는 이러한 필수 속성이 설명되어 있습니다. 예를 들어 초기 프레임은 다음 조건을 충족해야 합니다.
- HTML
<head>섹션에서 유효한 프레임을 반환해야 합니다 - 사용자가 브라우저에서 프레임을 클릭해 이동하는 경우를 대비해 유효한 HTML
<body>을 반환해야 합니다 - Farcaster 클라이언트가 초기 프레임을 캐시하므로 동적 콘텐츠를 포함해서는 안 됩니다
fc:frame:state태그를 포함해서는 안 됩니다
Farcaster에서 Solana 주소 인증하기
Farcaster는 최근 Solana 주소 지원을 추가했습니다. 이제 개발자는 사용자의 Solana 지갑과 상호작용하는 프레임을 구축할 수 있습니다. 지원 기능 출시 후 24시간도 지나지 않아 9,328개가 넘는 고유 Farcaster FID가 Solana 주소를 인증했습니다.
사용자는 다음 단계에 따라 Solana 주소를 인증할 수 있습니다.
- Settings로 이동합니다
- Verified Addresses를 클릭한 다음 “주소 인증” 버튼을 클릭합니다
- Solana 지갑을 연결하고 메시지에 서명해 소유권을 증명합니다
현재 지원되는 지갑은 Phantom뿐입니다.
튜토리얼: cNFT를 민팅하는 프레임 만들기
이 글에서는 인증된 Solana 주소가 있는 사용자가 압축 NFT(cNFT)를 민팅할 수 있는 프레임을 만듭니다. 프레임 구축과 디버깅을 위한 Next.js 템플릿인 frames.js로 프로젝트를 시작합니다. 프레임과 상호작용하는 Farcaster 사용자의 정보를 가져오기 위해 Coinbase의 OnchainKit, 그중에서도 Frame Kit을 사용합니다. 그런 다음 이 데이터를 Helius Mint API에 전달해 사용자의 Solana 지갑으로 다음 cNFT를 민팅합니다.
프레임 설정하기
frame.js로 프레임을 만드는 방법은 시작 템플릿을 복제하는 것만큼 간단합니다. 다음 명령어를 실행하세요. <name>은 템플릿을 복제할 새 폴더 이름으로 바꾸면 됩니다.
npx degit github:framesjs/frames.js/examples/framesjs-starter#main <name>여기서는 디렉터리 이름을 helius-frame로 정했으므로 <name>을 <helius-frame>로 바꿨습니다. 다음으로 새 디렉터리(예: cd helius-frame)로 이동하고 yarn install 명령어로 필요한 종속성을 설치합니다. 다운로드가 완료되면 yarn dev를 실행해 개발 서버를 시작합니다. 브라우저에서 http://localhost:3000을 열면 다음 화면이 표시됩니다.
Debug 하이퍼링크를 클릭하면 http://localhost:3000/debug로 이동합니다.
배포하지 않고도 프레임을 로컬에서 테스트할 수 있어 유용합니다. 예를 들어 로그인해 버튼을 테스트하고, 다른 사용자를 가장하고, 허브 상태를 모의로 설정할 수 있습니다. Warpcaster에는 프레임을 검증하는 훌륭한 Frame Validator 도구도 있습니다. 이후에는 개인적인 선호에 따라 Frame Validator 도구로 테스트하겠습니다.
다음으로 yarn install을 실행해 템플릿에 필요한 종속성을 설치합니다. yarn install @coinbase/onchainkit도 실행해 OnchainKit을 설치합니다.
프로젝트 폴더를 다음과 같이 재구성합니다.
- examples 폴더를 삭제합니다. 예제는 프레임 구축 방식을 이해하는 데 유용하지만 지금 만들려는 기능에는 필요하지 않습니다. 이 폴더를 제거하면 프로젝트의 불필요한 용량을 줄일 수 있습니다. 다만 일반적인 프레임을 만드는 다른 방법을 알아보려면 예제를 살펴보시기를 적극 권장합니다
- app 디렉터리에 api 폴더를 만듭니다. 이 단계는 애플리케이션의 프런트엔드와 백엔드를 명확히 구분하며 Next.js의 API 경로 규칙을 따릅니다. 그런 다음 frames 폴더와 그 안의 route.ts를 새 api 폴더로 이동합니다.
- app 디렉터리에 utils 폴더를 만듭니다. 이 새 폴더에 mint.ts 파일을 만듭니다. cNFT 민팅 로직은 이 파일에 작성합니다
이제 프레임 구축을 시작할 준비가 끝났습니다!
프레임 수정하기
layout.tsx
먼저 layout.tsx 파일을 수정합니다. 기존 내용을 모두 삭제하고 다음 코드로 바꾸세요.
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}RootLayout 컴포넌트는 일관된 레이아웃 구조로 전체 애플리케이션을 감싸는 최소한의 접근 방식을 사용합니다. 유연하고 재사용할 수 있도록 설계되었습니다. 개발자는 이 최소 레이아웃을 템플릿으로 활용해 필요에 따라 자체 프레임을 구축하고 맞춤 설정할 수 있습니다.
layout.tsx 파일을 변경하면 로컬 디버거 사용 시 문제가 발생할 수 있습니다. layout.tsx 수정은 필수가 아닙니다. 여기서는 애플리케이션 메타데이터를 page.tsx에 추가하기 위해 수정합니다. 대신 이 파일에 메타데이터를 추가하는 방법도 있습니다.
page.tsx
page.tsx의 내용을 삭제하고 다음 코드로 바꾸세요.
import type { Metadata } from "next";
import { getFrameMetadata } from "@coinbase/onchainkit";
const baseURL = "https://helius-frame.vercel.app/";
const frameMetadata = getFrameMetadata({
image: {
src: `${baseURL}/default.jpg`,
aspectRatio: "1:1",
},
buttons: [
{
label: "Click to mint a cNFT on Solana!",
action: "post",
},
],
postUrl: `${baseURL}api/frames`,
});
export const metadata: Metadata = {
title: "Mint a cNFT on Solana",
description: "Mint a cNFT to your verified Solana address using the Helius Mint API",
openGraph: {
title: "Mint a cNFT on Solana",
description: "Mint a cNFT to your verified Solana address using the Helius Mint API",
images: ["https://helius-frame.vercel.app/default.jpg"],
},
other: {
...frameMetadata,
},
};
export default function Page() {
return (
Mint a cNFT on Farcaster using the Helius Mint API
Built by 0xIchigo
GitHub Repo
);
}위 코드는 웹 서버의 URL에서 제공될 초기 프레임을 설정합니다. 이 예제의 초기 프레임은 Vercel의 https://helius-frame.vercel.app/에 호스팅됩니다. 이 튜토리얼의 뒷부분에서 Vercel로 배포하는 방법을 알아봅니다.
먼저 이 파일의 metadata 내보내기에 타입을 지정하기 위해 Next.js에서 Metadata 타입을 가져옵니다. @coinbase/onchainkit에서 getFrameMetadata도 가져옵니다. 그런 다음 반복되는 코드를 줄이기 위해 애플리케이션의 기본 URL을 정의합니다.
이어서 getFrameMetadata을 호출해 초기 프레임의 메타데이터를 구성합니다. 메타데이터에는 다음 항목이 포함됩니다.
- 프레임의 시각적 요소로 사용되는 1:1 화면비의 이미지(즉, src)
- “Solana에서 cNFT를 민팅하려면 클릭하세요!”라는 버튼. 클릭하면 지정된
postUrl(즉,${baseURL}api/frames)로 POST 요청을 보내 민팅 프로세스를 시작합니다
그런 다음 페이지의 제목과 설명, OpenGraph 메타데이터(즉, openGraph), 앞에서 정의해 other 속성에 펼친 frameMetadata를 포함하는 metadata 객체를 내보냅니다.
마지막으로 page은 기본 페이지 컴포넌트를 정의하고 내보냅니다. cNFT를 민팅하라는 기본 h1을 반환합니다. Farcaster에서 일부 HTML을 반환해야 하기 때문입니다. 주요 상호작용은 사이트에 직접 접속하는 대신 프레임을 통해 이루어집니다. 하지만 사용자가 클릭해 사이트로 이동할 경우 표시할 콘텐츠는 있어야 합니다.
mint.ts
mint.ts에 다음 코드를 붙여 넣으세요.
const url = `Get a URL at dev.helius.xyz`;
export const mintCompressedNFT = async (address: string) => {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: "frames-sol-mint",
method: "mintCompressedNft",
params: {
name: "Solana Soldier",
symbol: "HELIUS",
owner: address,
description: "A decentralized warrior backed by Solana's most loved RPC Nodes, APIs, Webhooks, and Developer Tooling",
attributes: [
{
trait_type: "RPCs",
value: "Fast",
},
{
trait_type: "Company",
value: "Helius"
},
],
imageUrl: "https://shdw-drive.genesysgo.net/HmvVwYAy7cxWECLbu2cjWBSHysVoriTqgdugyePVg3rY/cnftmint.jpg",
externalUrl: "https://www.helius.dev/",
sellerFeeBasisPoints: 6900,
},
}),
});
const data = await response.json();
console.log(`data: ${JSON.stringify(data, null, 2)}`);
if (!response.ok) {
throw new Error(`The response was not okay - ${response.status}`);
}
if (!data.result || !data.result.assetId) {
throw new Error(`The response did not include the expected data.result.assetId field`);
}
return data.result;
};mintCompressedNFT 유틸리티 함수는 미리 정의된 cNFT를 지정한 주소로 민팅하므로 애플리케이션에서 매우 중요합니다. Helius Mint API를 사용해 cNFT 민팅 프로세스를 캡슐화하는 단순하고 재사용 가능한 비동기 함수를 만듭니다. url로 정의된 Helius General RPC URL을 요청해야 합니다. 아직 URL이 없다면 Helius 대시보드에서 url에 사용할 무료 RPC URL을 생성하세요.
이 예제의 저장소에서는 SecureRPC URL을 사용합니다. API 키가 노출되지 않도록 주의하고 프록시 실행도 고려하세요. 요청 스키마에 관한 전체 설명은 여기에서 확인할 수 있습니다.
또한 작성 시점에는 Farcaster가 아직 완전한 트랜잭션 지원을 추가하지 않았으므로 cNFT 이미지를 이미 ShdwDrive에 업로드했습니다. 서명자가 제공되면 Helius Mint API가 오프체인 메타데이터를 Arweave에 업로드합니다.
이 함수에는 민팅 과정에서 발생하는 모든 문제를 포착하고 보고하는 강력한 오류 처리 기능도 포함되어 있습니다. 디버깅을 위해 응답 데이터도 로그에 기록합니다.
route.ts
api/frames/route.ts에 다음 코드를 붙여 넣으세요.
import { NextRequest, NextResponse} from "next/server";
import { getFrameMessage, getFrameHtmlResponse } from "@coinbase/onchainkit/frame";
import { mintCompressedNFT } from "../../utils/mint";
const baseURL = "https://helius-frame.vercel.app/";
export async function POST(req: NextRequest): Promise {
const body = await req.json();
// Validate the frame message and extract the user ID
const { isValid, message } = await getFrameMessage(body, {
neynarApiKey: "NEYNAR_ONCHAIN_KIT",
});
console.log(JSON.stringify(message, null, 2));
// Make sure they're following us before they can mint the cNFT
if (isValid && !message.following) {
return new NextResponse(getFrameHtmlResponse({
image: {
src: `${baseURL}/error.jpg`,
aspectRatio: "1:1",
},
buttons: [
{
label: "You need to be following to mint! Click to retry",
action: "post",
},
],
postUrl: `${baseURL}api/frames`,
}));
// Make sure they have a verified Solana address
} else if (isValid && message.interactor.verified_addresses.sol_addresses) {
const solanaAddresses = message.interactor.verified_addresses.sol_addresses;
console.log(`SOLANA ADDRESSES: ${solanaAddresses}`);
if (solanaAddresses.length !== 0) {
try {
console.log(`MINTING to ${solanaAddresses[0]}`);
// We take the first address address, in case there's more than one
const mintResult = await mintCompressedNFT(solanaAddresses[0]!);
return new NextResponse(getFrameHtmlResponse({
image: {
src: `${baseURL}/success.jpg`,
aspectRatio: "1:1",
},
buttons: [
{
label: "Success! View your cNFT on XRAY",
action: "link",
target: `https://xray.helius.xyz/token/${mintResult.assetId}?network=mainnet`
},
],
}));
} catch (e: any) {
console.log(`Minting failed: ${e}`);
return new NextResponse(getFrameHtmlResponse({
image: {
src: `${baseURL}/error.jpg`,
aspectRatio: "1:1",
},
buttons: [
{
label: "Minting failed. Click to retry",
action: "post",
},
],
postUrl: `${baseURL}api/frames`,
}));
}
} else {
console.log(`No Solana address found`);
return new NextResponse(getFrameHtmlResponse({
image: {
src: `${baseURL}/error.jpg`,
aspectRatio: "1:1",
},
buttons: [
{
label: "No Solana address found. Click to retry",
action: "post",
},
],
postUrl: `${baseURL}api/frames`
}));
}
} else {
return new NextResponse(getFrameHtmlResponse({
image: {
src: `${baseURL}/error.jpg`,
aspectRatio: "1:1",
},
buttons: [
{
label: "Invalid frame message. Click to retry",
action: "post",
},
],
postUrl: `${baseURL}api/frames`,
}));
}
}이 파일은 프레임에 대한 모든 POST 요청을 처리하는 Next.js API 경로입니다. 먼저 수신 요청을 처리하고 응답을 보내기 위해 next/server에서 NextRequest와 NextResponse을 가져옵니다. 프레임 메시지를 검증하고 프레임 상호작용용 HTML 응답을 생성하기 위해 @coinbase/onchainkit/frame에서 getFrameMessage과 getFrameHtmlResponse도 가져옵니다. 앞서 만든 mintCompressedNFT 유틸리티 함수도 가져옵니다.
baseURL은 코드 반복을 줄이기 위해 정의합니다. 리소스와 API 엔드포인트의 경로를 구성할 때 사용합니다.
이 파일은 수신 요청 본문을 파싱해 프레임 메시지를 추출하는 POST 함수를 내보냅니다. getFrameMessage을 사용해 수신된 프레임 상호작용과 메시지 페이로드가 유효한지 확인합니다. 이를 위해 요청 본문을 JSON으로 전달하고 기본 Neynar API 키(즉, “NEYNAR_ONCHAIN_KIT”)를 전달합니다. Neynar는 Farcaster 개발을 간소화하는 개발자 플랫폼입니다. getFrameMessage은 FrameValidationResponse로 이행되는 Promise을 반환합니다. 정의는 다음과 같습니다.
export type FrameValidationResponse =
| { isValid: true; message: FrameValidationData }
| { isValid: false; message: undefined };따라서 프레임 상호작용이 유효하면 FrameValidationData 타입의 메시지를 반환합니다. 정의는 다음과 같습니다.
export interface FrameValidationData {
button: number; // Number of the button clicked
following: boolean; // Indicates if the viewer clicking the frame follows the cast author
input: string; // Text input from the viewer typing in the frame
interactor: {
fid: number; // Viewer Farcaster ID
custody_address: string; // Viewer custody address
verified_accounts: string[]; // Viewer account addresses
verified_addresses: {
eth_addresses: string[] | null;
sol_addresses: string[] | null;
};
};
liked: boolean; // Indicates if the viewer clicking the frame liked the cast
raw: NeynarFrameValidationInternalModel;
recasted: boolean; // Indicates if the viewer clicking the frame recasted the cast
valid: boolean; // Indicates if the frame is valid
}여기에는 사용자의 Farcaster ID, 인증된 Ethereum 또는 Solana 주소 보유 여부, 캐스트에 좋아요를 표시했는지 여부 등 관련 데이터가 포함되어 있어 매우 유용합니다. 이 튜토리얼에서는 민팅을 시도하는 사용자가 작성자의 프로필을 팔로우하는지 확인합니다. 팔로우 중이라면 cNFT 민팅을 허용합니다. cNFT를 민팅할 수 있는 인증된 Solana 주소가 있는지도 확인해야 합니다.
먼저 상호작용이 유효하고 사용자가 작성자의 계정을 팔로우하는지 확인합니다. 그렇지 않으면 오류 메시지와 다시 시도하라는 버튼을 표시합니다. 이어서 사용자에게 인증된 Solana 주소가 있는지 확인합니다. 하나 이상의 주소가 있으면 민팅 프로세스를 진행합니다. 인증된 첫 번째 Solana 주소를 가져와 mintCompressedNFT에 전달합니다. 성공하면 성공 이미지를 반환하고 Orb에서 cNFT를 볼 수 있는 링크를 제공합니다. 실패하면 오류를 로그에 기록하고 다시 시도하라는 메시지로 응답합니다. 인증된 Solana 주소를 찾지 못하면 인증된 Solana 주소가 없다는 오류 메시지와 함께 다시 시도하라는 안내를 보냅니다. 마지막으로 프레임 메시지가 유효하지 않으면 잘못된 프레임이라는 오류 메시지와 다시 시도하라는 안내를 보냅니다.
코드는 getFrameHtmlResponse을 사용해 민팅 프로세스나 유효성 검사 결과에 맞춘 HTML 응답을 생성합니다. 여기에는 특정 사용자 상호작용에 맞는 응답 이미지, 화면비, 버튼 설정이 포함됩니다.
프로덕션에 배포하기
코드 작성이 끝났으면 새 GitHub 저장소를 만들고 코드를 푸시합니다. 명령줄에서 다음 명령어를 실행해 코드를 GitHub에 푸시하세요.
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/<username>/<new repo name>.git
git branch -M main
git push -u origin main코드를 GitHub에 푸시했다면 다음 단계는 애플리케이션 배포입니다. Vercel은 GitHub와 원활하게 통합되어 전체 배포 프로세스를 간소화하므로 Vercel을 사용하겠습니다. 다음 단계를 진행하세요.
- Vercel 웹사이트에서 계정에 로그인합니다. 계정이 없다면 새로 만들어야 합니다. Vercel은 이 프로젝트에 충분한 무료 Hobby 등급을 제공합니다
- 로그인한 후 대시보드로 이동해 New Project 버튼을 클릭합니다. Vercel에서 GitHub 프로젝트를 가져오라는 메시지가 표시됩니다. 이전 단계에서 만든 저장소를 선택합니다
- Vercel은 사용 중인 프레임워크를 자동으로 감지하고 빌드 설정을 제안합니다. Vercel은 Next.js 제작자가 만든 플랫폼으로 최고 수준의 지원을 제공하므로 복잡한 구성 설정을 크게 걱정할 필요가 없습니다
- 프로젝트 설정을 구성한 후 Deploy 버튼을 클릭합니다. Vercel이 자동으로 배포 프로세스를 시작하고 프로젝트를 빌드해 고유한 Vercel URL에 배포합니다.
- 배포가 끝나면 Vercel에서 라이브 애플리케이션에 접속할 수 있는 고유 URL을 제공합니다. 이 프로젝트는 https://helius-frame.vercel.app/에 빌드되었습니다. GitHub 저장소 이름 뒤에 .vercel.app이 붙은 형태입니다. Vercel 프로젝트 설정에서 사용자 지정 도메인을 구성할 수도 있습니다
완료되었습니다! 이 단계에 따라 Farcaster 프레임을 GitHub에 푸시하고 Vercel에 호스팅했습니다. 앞서 소개한 Frames validator 같은 테스트 도구를 사용해 프레임 기능을 테스트할 수 있습니다. 이제 프레임 URL이 포함된 캐스트를 만들기만 하면 됩니다!
마치며
블록체인과 소셜 미디어의 결합은 디지털 공간을 바라보고 그 안에서 상호작용하는 방식을 크게 발전시킵니다. Farcaster와 프레임의 인기는 기존 소셜 미디어 플랫폼의 정적 게시물을 대화형 경험으로 전환하고 있습니다. 이 글에서는 Farcaster와 프레임을 살펴봤습니다. 튜토리얼에서는 최근 추가된 Farcaster의 Solana 지원을 활용해 버튼 한 번으로 사용자에게 cNFT를 민팅하는 방법을 소개했습니다. Farcaster에서 서비스를 구축하려는 개발자든, 새롭고 흥미로운 방식으로 소셜 미디어 피드를 둘러보고 싶은 사용자든 가능성은 무궁무진합니다.
여기까지 읽어주셔서 감사합니다, 익명의 독자님! Farcaster에 가입하려면 다음 초대 링크를 사용하세요!
아래에 이메일 주소를 입력하고 Solana의 새로운 소식을 빠짐없이 받아보세요. 더 깊이 알아볼 준비가 되셨나요? Helius 블로그에서 최신 글을 살펴보고 지금 바로 Solana 여정을 이어가세요.
추가 자료
관련 아티클
Helius 구독하기
최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요


