What the heck is a Blink?
Understand everything there is to Solana Blinks and create a blink of your own while at it!
Whenever you are doing a transaction or interacting with web3 apps, you often end up navigating away from the content or app that you were engaging with 😕. But, what if you could perform blockchain transactions without ever leaving the page you're on?💫 That's where Solana Blinks come in!
So, you can simply click a button while scrolling through X and do a transaction, without ever leaving your feed!! (Yes, blinks are only supported on Twitter for now.)
In this blog, we'll see what Blinks are, how they work, and how you can create your own blink. By the end, you'll have everything you need to blink on solana😎.
But before we start blinking, we first need to know about Solana actions.
What are Actions?🤌
Actions are a protocol for creating & delivering Solana transactions through URLs, making Solana sharable everywhere.
Imagine you want to send someone 1 SOL. Instead of going to a wallet app, you could just click a link that says "Send 1 SOL", and it would prepare the transaction for you.
We will get into technical stuff later in this blog, but they work very similar to typical REST APIs.
Now, let's see how Blinks fit into this picture and take Actions a step further.
What are blinks?✨
Blinks, or Blockchain Links, are clients that detect Action URLs & unfurl them into full experiences.
In easy words, Blink are actions packaged in a nice ui.
For example, you might see a Blink unfurl like this.
All we had to do is click the button, and boom💥! You've initiated a blockchain transaction without going anywhere else!!
And mind you, you don't have you write code or anything for the client side, the ui unfurls automatically, once the platform detects its an action URL. So yeah, none of that hassle anymore.😊
(DISCLAIMER: Only verified Blinks unfurl and only on X. So, you can apply to be verified by Dialect at dial.to/register)
Now that we have a basic understanding, let's get technical and dive deeper into how Actions and Blinks actually work.
Let's get Technical🤓
Don't worry if some of this sounds complex – we'll break everything down into steps.
How Actions Work:
Solana actions work very similar to our typical REST APIs. Because at the core, all we're doing is sending GET
and POST
request to a server, and that server is returning some data.
Actions use a special URL format
solana-action:<link>
to tell compatible clients that this URL is an Action.Example:
solana-action:
https://example.com/send-sol
Actions have two main API endpoints:
GET
: This returns metadata about the Action (like its title, description, and what it does).POST
: This returns the actual transaction to be signed.
Action Lifecycle:
A client (like a wallet app) makes a GET request to the Action URL.
The Action returns metadata, which the client uses to display information to the user.
When the user decides to perform the Action, the client makes a POST request.
The Action returns a transaction, which the wallet then asks the user to sign.
More About Blinks:
As we know, Blinks are like Link Previews with interactive capabilities.
It's supported in Phantom, Backpack, and Solflare for now. So, for it to work, you have to enable them in your wallet: ⚙️ → Enable Actions/Blinks.
Or you can try Dialect's chrome extension for Blinks.
Okay, enough talking! Let's roll up our sleeves and actually create our very own Blink.⚒️
Let's create a Blink!
In this blog, we'll make a simple Blink that allows users to transfer SOL.
You can find the source code here.📦
Here are the steps:
Setting up 🛠️
Create Your Action API 🚀
Configure CORS
Test Locally 🧪
Deploy to Vercel 🚀
Test Your Live Blink ✨
Step 1: Setting up 🛠️
First things first, make sure you have Node.js installed. Then, let's create a new Next.js project:
npx create-next-app@latest solana-blink
cd solana-blink
Install dependencies
npm install @solana/actions @solana/web3.js
Step 2: Create Action API 🚀
Create a new file app/api/actions/transfer-sol/route.ts
:
You can either copy paste the file code from here, or just follow along👇
import {
ActionPostResponse,
ACTIONS_CORS_HEADERS,
createPostResponse,
ActionGetResponse,
ActionPostRequest,
} from "@solana/actions";
import {
clusterApiUrl,
Connection,
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
Transaction,
} from "@solana/web3.js";
// Define default values
const DEFAULT_SOL_ADDRESS = new PublicKey("11111111111111111111111111111111");
const DEFAULT_SOL_AMOUNT = 1;
First, Let's handle GET
requests
This function is responsible for providing metadata about our Action. This metadata is what allows Blink-compatible clients to display a nice UI for our Action.
export async function GET(req: Request) {
try {
const requestUrl = new URL(req.url);
const { toPubkey } = validatedQueryParams(requestUrl);
// Construct the base URL for our action
const baseHref = new URL(
`/api/actions/transfer-sol?to=${toPubkey.toBase58()}`,
requestUrl.origin
).toString();
// Define the payload for our GET response
const payload: ActionGetResponse = {
title: "Transfer SOL Action",
icon: new URL("/logo.png", requestUrl.origin).toString(),
description: "Transfer SOL to another Solana wallet",
label: "Transfer",
links: {
actions: [
// Define different action options
{
label: "Send 1 SOL",
href: `${baseHref}&amount=1`,
},
{
label: "Send 5 SOL",
href: `${baseHref}&amount=5`,
},
{
label: "Custom Amount",
href: `${baseHref}&amount={amount}`,
parameters: [
{
name: "amount",
label: "Enter SOL amount",
required: true,
},
],
},
],
},
};
// Return the payload with CORS headers
return new Response(JSON.stringify(payload), {
headers: ACTIONS_CORS_HEADERS,
});
} catch (err) {
console.error(err);
return new Response("An error occurred", {
status: 400,
headers: ACTIONS_CORS_HEADERS,
});
}
}
Make sure, you have logo.png
in your public folder. In links
you can list all the actions a user can perform. You can read more about it here.
Next, Let' handle POST
requests.
This is what this function is going to do:
Validate the provided account.
Connect to the Solana network.
Create a transfer instruction for the specified amount of SOL.
Build a transaction with this instruction.
Create a response payload with the transaction and a message describing the action.
Return this payload with CORS headers.
export async function POST(req: Request) {
try {
const requestUrl = new URL(req.url);
const { amount, toPubkey } = validatedQueryParams(requestUrl);
const body: ActionPostRequest = await req.json();
// Validate the provided account
let account: PublicKey;
try {
account = new PublicKey(body.account);
} catch (err) {
return new Response('Invalid "account" provided', {
status: 400,
headers: ACTIONS_CORS_HEADERS,
});
}
// Connect to Solana
const connection = new Connection(
process.env.SOLANA_RPC || clusterApiUrl("devnet")
);
// Create the transfer instruction
const transferSolInstruction = SystemProgram.transfer({
fromPubkey: account,
toPubkey: toPubkey,
lamports: amount * LAMPORTS_PER_SOL,
});
// Get the latest blockhash
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
// Create the transaction
const transaction = new Transaction({
feePayer: account,
blockhash,
lastValidBlockHeight,
}).add(transferSolInstruction);
// Create the response payload
const payload: ActionPostResponse = await createPostResponse({
fields: {
transaction,
message: `Send ${amount} SOL to ${toPubkey.toBase58()}`,
},
});
// Return the payload with CORS headers
return new Response(JSON.stringify(payload), {
headers: ACTIONS_CORS_HEADERS,
});
} catch (err) {
console.error(err);
return new Response("An error occurred", {
status: 400,
headers: ACTIONS_CORS_HEADERS,
});
}
}
Read more about it here.
Finally, we need a helper
function to validate our query parameters.
This function parses and validates the to
and amount
parameters from the request URL. It's used by both the GET
and POST
functions to ensure we're working with valid data.
function validatedQueryParams(requestUrl: URL) {
let toPubkey: PublicKey = DEFAULT_SOL_ADDRESS;
let amount: number = DEFAULT_SOL_AMOUNT;
try {
if (requestUrl.searchParams.get("to")) {
toPubkey = new PublicKey(requestUrl.searchParams.get("to")!);
}
} catch (err) {
throw "Invalid input query parameter: to";
}
try {
if (requestUrl.searchParams.get("amount")) {
amount = parseFloat(requestUrl.searchParams.get("amount")!);
}
if (amount <= 0) throw "amount is too small";
} catch (err) {
throw "Invalid input query parameter: amount";
}
return {
amount,
toPubkey,
};
}
The OPTIONS
export is necessary for CORS to work correctly, allowing our Action to be called from different origins. Read more about it here.
export const OPTIONS = GET;
Step 3: Configure CORS ⚙️
Update your next.config.mjs
:
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: "/api/actions/transfer-sol",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "true" },
{ key: "Access-Control-Allow-Origin", value: "*" },
{ key: "Access-Control-Allow-Methods", value: "GET,POST,OPTIONS" },
{ key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
]
}
]
}
}
export default nextConfig;
Step 4: Test Locally 🧪
Fire up your development server:
npm run dev
Use VS Code's Thunder Client extension on VS code or Postman to test your endpoints:
Send a
GET
andpost
request tohttp://localhost:3000/api/actions/transfer-sol
Check the response. You should see your Blink's metadata!
Here's a video guide for the same😉
Step 5: Deploy to Vercel 🚀
You can deploy your website anywhere, but I am doing it on vercel.
Push your code to GitHub.
Go to Vercel and create a new project from your repo.
Deploy! Now you have a link for your website!
Step 6: Test Your Live Blink🎁
Almost there, this is the last and the most rewarding step👇
Copy your deployed URL (e.g.,
https://yourproject.vercel.app/api/actions/transfer-sol
)Go to dial.to and paste your URL.
And See your Blink in action!🚀
Here's a quick video for the same😉
And there you have it! You've just created your very first Blink. Pretty cool, right? 😎
Click here to get the full source code.
Troubleshooting🔧
Don't worry if your facing errors, happens to the best of us!🙂
Here are some issues I faced, and how to solve them:
1. CORS errors: Double-check your next.config.mjs
. Make sure the CORS headers are set correctly. You might want to refer this part of the docs.
2. Blink doesn't appear on dial.to: Verify your API endpoint is correct and returning the expected response. Make sure the URL is to the blink (eg: https://blink-kappa.vercel.app/api/actions/transfer-sol
) and not the website (eg: https://blink-kappa.vercel.app
)
Remember, you can always use the Blink Inspector to debug your Blinks!
We just created a simple blink, but the use cases are far more vast. Let's look at some other amazing things you can do with Blinks!✨
Awesome Blink Ideas 💡
Here are some cool blink ideas for you.
Token Gating: Create a Blink that checks if a user holds a specific NFT before granting access to exclusive content.
DAO Voting: Make a Blink that allows users to vote on DAO proposals directly from social media.
NFT Minting: Design a Blink that lets users mint NFTs without leaving their favorite platform.
DeFi Interactions: Build Blinks for swapping tokens or providing liquidity to DeFi protocols.
Tipping: Create a Blink that allows users to tip content creators with SOL or SPL tokens.
The possibilities are endless!
I found this really cool thread of blink ideas for you, do check out :)
What cool Blink will you create? 🤔 Post on twitter and tag me @duckwhocodes, would love to see what you came up with❤️
Conclusion👋
And there you have it! You're now officially a Blink builder. 🎓
Remember, Blinks are all about making blockchain interactions seamless and user-friendly. They're bringing Web3 right to where users already are, making adoption easier than ever.
Here are some resources for you: