Solana在2024年6月25日推出的新功能:Solana Actions和Blinks,这些工具将大大简化了用户与Solana区块链的交互体验。本教程讲解Solana action和Blink基本原理,和简易的开发过程,并且通过教程可以实现无代码生成捐款的Blink
作者 崔棉大师 X:@MasterCui Youtube: 崔棉大师 原文链接:https://www.solana-cn.com/SolanaAction/001.html
Solana Actions
是符合规范的 API,返回 Solana 区块链上的交易,供预 览、签名和在各种上下文中发送,包括二维码、按钮 + 小部件和互联网上的网站。 Actions 使开发人员能够轻松地将 Solana 生态系统中的操作集成到他们的环境中,允许你 执行区块链交易而无需导航到不同的应用程序或网页。
区块链链接
——或称 blinks——将任何 Solana Action 转化为可共享的、富含元 数据的链接。 Blinks 允许支持 Action 的客户端(浏览器扩展钱包、机器人)为用户显示 额外的功能。 在网站上,blink 可能会立即在钱包中触发交易预览,而无需访问去中心化 应用程序;在 Discord 中,机器人可能会将 blink 扩展为一组交互式按钮。 这将链上交 互的能力推向任何能够显示 URL 的网页表面。
通过 npx 创建一个 Next 运行环境:
npx create-next-app solana-action
输出:
➜ ~ npx create-next-app solana-action
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
Creating a new Next.js app in ~/solana-action.
Using npm.
Initializing project with template: app-tw
Installing dependencies:
- react
- react-dom
- next
Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- postcss
- tailwindcss
- eslint
- eslint-config-next
npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm WARN deprecated @humanwhocodes/config-array@0.11.14: Use @eslint/config-array instead
npm WARN deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
added 362 packages, and audited 363 packages in 2m
137 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Initialized a git repository.
Success! Created solana-action at ~/solana-action
安装 Solana web3 依赖和 Solana action 依赖
cd solana-action
npm i @solana/web3.js @solana/actions
输出:
➜ ~ cd solana-action
➜ solana-action git:(main) npm i @solana/web3.js @solana/actions
added 76 packages, and audited 439 packages in 4m
146 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
创建目录
mkdir -p src/app/api/actions/memo/
创建 route 程序文件
vim src/app/api/actions/memo/route.ts
首先我们创建一个链上 memo 的 action,通过一个简单的按钮实现向 Solana 链上存储一段 message:this is a simple memo message,用这个示例代码讲解 Solana action 主体结构,即在一个 URL 中兼容 GET 和 POST 两种请求格式,并且可以按照规范的格式响应 GET 请求和 POST 请求
import {
ACTIONS_CORS_HEADERS,
ActionGetResponse,
ActionPostRequest,
ActionPostResponse,
MEMO_PROGRAM_ID,
createPostResponse,
} from "@solana/actions";
import {
ComputeBudgetProgram,
Connection,
PublicKey,
Transaction,
TransactionInstruction,
clusterApiUrl,
} from "@solana/web3.js";
在 Solana action 标准中,链接地址默认要通过 GET 请求返回一个元数据格式,这个返回要满足以下接口规范:
export interface ActionGetResponse {
/** 图片的完整链接URL */
icon: string;
/** action的标题 */
title: string;
/** action的简介 */
description: string;
/** 按钮渲染后的标签文字 */
label: string;
/** 可选项:action按钮是否有效 */
disabled?: boolean;
/** 可选项:action的按钮数组 */
links?: {
actions: LinkedAction[],
};
/** 可选项:报错信息 */
error?: ActionError;
}
icon
- 必须是描述操作的图像的绝对 HTTP 或 HTTPS URL。支持的图像格式为 SVG、PNG 或 WebP 图像。如果以上都不是,客户端必须将其拒绝为格式错误。
title
- 操作标题的 UTF-8 字符串。
description
- 一个 UTF-8 字符串,提供操作的简要描述。
label
- UTF-8 字符串将显示在用于执行操作的按钮上。标签不应超过 5 个词组,并且应以动词开头,以强调要采取的操作。例如,"铸造 NFT"、"投票赞成"或"质押 1 SOL"。
disabled
- 可选 boolean 值。如果存在,则应禁用与操作相关的所有按钮。此可选值为 false,因为不存在该值相当于 enabled=true。disabled 使用中的示例可能包括已完成铸造的 NFT 集合,或投票期已结束的治理提案。
error
- 非致命错误的可选错误指示,旨在便于用户阅读并呈现给最终用户。如果设置,它不应阻止客户端解释操作或将其显示给用户。例如,错误可以与 disabled 显示原因(如业务限制、授权、状态或外部资源错误)一起使用。
export interface ActionError {
/** 向用户展示的非致命错误信息字符串 */
message: string;
}
links.actions
- 与此操作相关的附加操作的可选数组。每个链接操作仅被赋予一个标签(将在按钮上呈现)和一个关联的操作 URL(位于)href。不提供其他图标、标题或描述。例如,治理投票操作端点可能会为用户返回三个选项:"投票赞成"、"投票反对"和"弃权"。
links.actions
提供,客户端应该使用根字符串呈现单个按钮 label,并向与初始 GET 请求相同的操作 URL 端点发出 POST 请求。links.actions
提供了任何内容,客户端应仅根据字段中列出的项目呈现按钮和输入字段 links.actions。客户端不应为根的内容呈现按钮 label。export interface LinkedAction {
/** 次action的URL */
href: string;
/** 按钮显示的文字标签 */
label: string;
/** 允许用户输入的参数 */
parameters?: [ActionParameter];
}
/** 允许用户输入的参数 */
export interface ActionParameter {
/** 参数名称 */
name: string;
/** 在输入框显示的替换文本 */
label?: string;
/** 是否必填(默认:否) */
required?: boolean;
}
export const GET = (req: Request) => {
const payload: ActionGetResponse = {
icon: new URL("/solana_devs.jpg", new URL(req.url).origin).toString(),
title: "Memo Demo",
description: "This is a super simple Action",
label: "Memo Demo",
};
return Response.json(payload, {
headers: ACTIONS_CORS_HEADERS,
});
};
export const OPTIONS = GET;
Solana action 要支持 POST 请求,并且 Blink 客户端需要按照以下格式将用户的公钥以 HTTP JSON 格式发送给 action 的 URL
{
"account": "<account>"
}
其中,account 是发出请求的用户的公钥的 base58 编码表示形式。
客户端应该使用 Accept-Encoding 标头,而 Action 服务应该使用 Content-Encoding 标头进行响应以进行 HTTP 压缩。
返回格式:
action 服务应使用 HTTP OK JSON 响应和以下格式来响应 POST 请求。
export interface ActionPostResponse {
/** base64编码的交易数据 */
transaction: string;
/** 可选项:返回消息,例如描述交易细节 */
message?: string;
}
export const POST = async (req: Request) => {
try {
const body: ActionPostRequest = await req.json();
let account: PublicKey;
try {
account = new PublicKey(body.account);
} catch (err) {
return new Response("Invalid 'account' provided", {
status: 400,
headers: ACTIONS_CORS_HEADERS,
});
}
const transaction = new Transaction();
transaction.add(
// note: createPostResponse 至少需要 1 条非备忘录指令
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: 1000,
}),
new TransactionInstruction({
programId: new PublicKey(MEMO_PROGRAM_ID),
data: Buffer.from("this is a simple memo message", "utf8"),
keys: [],
})
);
transaction.feePayer = account;
const connection = new Connection(
process.env.RPC_URL_MAINNET ?? clusterApiUrl("mainnet-beta")
);
transaction.recentBlockhash = (
await connection.getLatestBlockhash()
).blockhash;
const payload: ActionPostResponse = await createPostResponse({
fields: {
transaction,
},
});
return Response.json(payload, { headers: ACTIONS_CORS_HEADERS });
} catch (err) {
return Response.json("An unknow error occurred", { status: 400 });
}
};
将代码保存后就可以提交到 GitHub 了
git remote add origin https://github.com/whaler-academy/solana-action.git
git branch -M main
git push -u origin main
输出:
Enumerating objects: 20, done.
Counting objects: 100% (20/20), done.
Delta compression using up to 8 threads
Compressing objects: 100% (18/18), done.
Writing objects: 100% (20/20), 56.17 KiB | 2.16 MiB/s, done.
Total 20 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/whaler-academy/solana-action.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
注册并登录https://vercel.com/
部署项目时输入自定义的二级域名,例如我的项目是 solana-action,部署之后可以通过 solana-action.vercel.com 访问
接下来你就可以用 URL 访问你的 Solana action 了
<https://solana-action.vercel.app/api/actions/memo>
返回结果应该是 JSON 格式:
{
"icon": "https://solana-action.vercel.app/solana_devs.jpg",
"title": "Memo Demo",
"description": "This is a super simple Action",
"label": "Memo Demo"
}
编写和部署好 Solana action 之后我们可以通过<https://dial.to/>创建自己的 Blink 链接,这个方法的方便之处是无需代码即可创建一个 Blink 链接收取 SOL,缺点是金额和文字都无法自定义
打开<https://dial.to/>输入刚才创建的 memo action 的 URL
提交之后就可以看到创建好的 Blink 前端界面了,如果我们想要分享这个链接,只需要拷贝地址栏的链接,粘贴到 X 或者其他社交平台
Solana action 的官方示例代码中提供了一个 action 链接,可以将链接中的收款地址替换成自己的收款地址完成一个允许其他人向自己发送 SOL 的 Blink 链接
action 链接地址:
<https://solana-actions.vercel.app/api/actions/transfer-sol?to=你的SOL收款地址>
例如:
链接中的CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG
为我的收款地址
有了 action 链接我们就可以将 action 链接提交到<https://dial.to/>创建自己的 Blink 了
创建好的 Blink 链接为:
这个由官方提供的 action 链接目前是在 dial.to 白名单中的,所以在 X 平台是可以直接以标签页形式展现出来的
如果我们想自定义收款的文字和金额,接下来我们学习 Solana action 的高级应用
在前面的课程内容中,我们看到发送 SOL 的官方的 Solana action 链接通过 GET 请求返回的 JSON 数据格式为:
如果你连 GET 请求是什么都不了解,没关系,只需要将 <https://solana-actions.vercel.app/api/actions/transfer-sol?to=CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG> 这个地址输入到浏览器地址栏再回车即可!
"title": "Actions Example - Transfer Native SOL",
"icon": "https://solana-actions.vercel.app/solana_devs.jpg",
"description": "Transfer SOL to another Solana wallet",
"label": "Transfer",
"links": {
"actions": [
{
"label": "Send 1 SOL",
"href": "https://solana-actions.vercel.app/api/actions/transfer-sol?to=CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG&amount=1"
},
{
"label": "Send 5 SOL",
"href": "https://solana-actions.vercel.app/api/actions/transfer-sol?to=CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG&amount=5"
},
{
"label": "Send 10 SOL",
"href": "https://solana-actions.vercel.app/api/actions/transfer-sol?to=CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG&amount=10"
},
{
"label": "Send SOL",
"href": "https://solana-actions.vercel.app/api/actions/transfer-sol?to=CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG&amount={amount}",
"parameters": [
{
"name": "amount",
"label": "Enter the amount of SOL to send",
"required": true
}
]
}
]
}
}
现在我们需要让我们自己编写的 action 程序按照规范返回上面格式的内容就可以完成 action 的 GET 请求部分
mkdir -p src/app/api/actions/donate/
vim src/app/api/actions/donate/route.ts
const toPubkey = "CuiDdffKV38LjgRVtiA2QiMTKhnzkjX2LUxqSMbVnGjG";
export const GET = async (req: Request) => {
try {
const requestUrl = new URL(req.url);
const baseHref = new URL(
`/api/actions/donate?to=${toPubkey.toBase58()}`,
requestUrl.origin
).toString();
const payload: ActionGetResponse = {
title: "向崔棉大师@MasterCui捐赠SOL",
icon: new URL("/avator.jpg", requestUrl.origin).toString(),
description: "支持第一个中文Solana action教程作者",
label: "Transfer", // 这个标签将被忽略
links: {
actions: [
{
label: `Send 0.01 SOL`, // 按钮文字
href: `${baseHref}&amount=0.01`,
},
{
label: `Send 0.05 SOL`, // 按钮文字
href: `${baseHref}&amount=0.05`,
},
{
label: `Send 0.1 SOL`, // 按钮文字
href: `${baseHref}&amount=0.1`,
},
{
label: "Send SOL", // 按钮文字
href: `${baseHref}&amount={amount}`, // 这个链接将拥有一个input输入框
parameters: [
{
name: "amount", // href中的参数名
label: "Enter the amount of SOL to send", // 输入框的替代文字
required: true,
},
],
},
],
},
};
return Response.json(payload, {
headers: ACTIONS_CORS_HEADERS,
});
} catch (err) {
console.log(err);
let message = "An unknown error occurred";
if (typeof err == "string") message = err;
return new Response(message, {
status: 400,
headers: ACTIONS_CORS_HEADERS,
});
}
};
这样的代码就可以按照规范显示出3个发送SOL的按钮,同时还会显示一个输入框,让用户可以自己定制发送SOL的数额
git add .
git commit -m "add donate"
git push
提交代码后vercel会自动部署前端,稍微等待几秒钟,修改就会在前端生效,这是你就拥有了一个新的捐款的Solana action链接:<https://solana-action.vercel.app/api/actions/donate>
将链接提交到<https://dial.to/>创建捐款的Blink
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!