本文介绍了如何使用TypeScript和QuickNode Functions来管理和获取Solana钱包的投资组合,包括设置开发环境、创建和部署函数、以及测试函数的过程。
QuickNode Functions 允许开发者快速构建和部署无服务器函数,这些函数可以与区块链数据进行交互。在本指南中,我们将引导你使用 TypeScript 创建一个 QuickNode Function,用于管理动态的 Solana 钱包组合并获取它们的余额。
在本指南中,你将:
首先,让我们创建一个新的项目目录并初始化它:
mkdir solana-portfolio-manager && cd solana-portfolio-manager
接下来,使用以下命令初始化你的项目:
npm init -y
现在,让我们安装必要的依赖:
npm install @solana/web3.js@1
在项目根目录下创建一个 tsconfig.json
文件:
npx tsc --init
打开 tsconfig.json
并更新为以下内容:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"outDir": "./",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
创建一个 src
目录,并在其中创建一个 index.ts
文件:
mkdir src && echo > src/index.ts
让我们从创建函数开始。该函数将管理一个动态的 Solana 钱包地址列表。它将允许用户创建一个钱包列表,通过添加/删除地址来更新列表,检索列表,并获取列表中钱包的余额。为了在单个函数中启用多个指令,我们将使用 switch 语句来处理不同的指令。
在代码编辑器中打开 src/index.ts
并添加以下代码:
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
interface QNLib {
qnUpsertList: (key: string, options: { add_items?: string[], remove_items?: string[] }) => Promise<any>;
qnGetList: (key: string) => Promise<string[]>;
}
declare const qnLib: QNLib;
const ENDPOINT = 'https://example.solana-mainnet.quiknode.pro/123456'; // 👈 替换为你的端点 URL
在这里,我们从 @solana/web3.js
库中导入了一些依赖项——我们将使用这些依赖项来验证用户输入并从 Solana 区块链中获取数据。我们还定义了一个 QNLib
接口,这将允许我们在使用 QuickNode Functions 全局作用域中可用的 qnLib
对象 时确保类型安全。
最后,我们用你自己的端点 URL 定义了 ENDPOINT
变量。你可以在 QuickNode 仪表板的 Endpoints 选项卡 中找到它。
现在我们已经定义了依赖项和常量,让我们定义将在函数中使用的类型和接口,以帮助实现类型安全和更好的代码组织。在常量下方添加以下代码:
type Instruction = 'createPortfolio' | 'updatePortfolio' | 'getPortfolio' | 'getPortfolioBalances';
interface UserData {
instruction: Instruction;
portfolioName: string;
addAddresses?: string[];
removeAddresses?: string[];
}
interface FunctionParams {
user_data: UserData;
}
interface FunctionResult {
message: string;
portfolioName: string;
addresses?: string[];
balances?: { address: string; balance: number }[];
error?: string;
}
让我们逐一了解这些类型和接口的职责:
Instruction
:这是一个 类型,定义了函数可以执行的不同指令。在本例中,我们定义了四个指令:createPortfolio
、updatePortfolio
、getPortfolio
和 getPortfolioBalances
。UserData
:此接口定义了将传递给函数的数据。它包含三个属性:instruction
(一个 Instruction)、portfolioName
(组合的唯一标识符)以及 addAddresses
和 removeAddresses
(Solana 钱包地址)。FunctionParams
:此接口定义了将传递给函数的参数。它包含一个属性:类型为 UserData
的 user_data
。FunctionResult
:此接口定义了函数将返回的结果。所有响应必须包括 message
和 portfolioName
属性。根据指令的不同,响应还可能包括 addresses
、balances
和 error
属性。让我们创建两个辅助函数来验证用户输入并处理错误。将以下代码添加到 index.ts
文件的底部:
function isValidSolanaAddress(address: string): boolean {
try {
new PublicKey(address);
return true;
} catch (error) {
return false;
}
}
function validateInput(params: FunctionParams): void {
if (!ENDPOINT) {
throw new Error('SOLANA_RPC_ENDPOINT is not set');
}
const validInstructions: Instruction[] = ['createPortfolio', 'updatePortfolio', 'getPortfolio', 'getPortfolioBalances'];
if (!validInstructions.includes(params.user_data.instruction)) {
throw new Error(`Invalid instruction: ${params.user_data.instruction}. Must be one of: ${validInstructions.join(', ')}`);
}
if (!params.user_data.portfolioName) {
throw new Error('Portfolio name is required');
}
if (params.user_data.instruction === 'updatePortfolio') {
if (!params.user_data.addAddresses && !params.user_data.removeAddresses) {
throw new Error('At least one of addAddresses or removeAddresses is required for updatePortfolio instruction');
}
if (params.user_data.addAddresses) {
const invalidAddAddresses = params.user_data.addAddresses.filter(addr => !isValidSolanaAddress(addr));
if (invalidAddAddresses.length > 0) {
throw new Error(`Invalid Solana addresses: ${invalidAddAddresses.join(', ')}`);
}
}
}
}
在这里,我们定义了两个辅助函数:
isValidSolanaAddress
:用于验证用户输入的 Solana 地址。它通过尝试从输入字符串创建一个新的 PublicKey
对象来检查输入是否是有效的 Solana 地址。如果输入不是有效的 Solana 地址,则返回 false
。否则,返回 true
。validateInput
:用于验证端点以及用户输入的组合名称和指令。它检查是否提供了有效的指令以及组合名称是否为空。如果提供了 updatePortfolio
指令,它还检查是否至少提供了 addAddresses
或 removeAddresses
中的一个。如果向组合中添加新地址,它还检查地址是否是有效的 Solana 地址。如果这些条件中的任何一个未满足,它将抛出错误。接下来,我们将为每个指令定义一个指令处理程序。将以下代码添加到 index.ts
文件的底部:
async function createPortfolio(portfolioName: string): Promise<FunctionResult> {
await qnLib.qnUpsertList(portfolioName, { add_items: [] });
return {
message: `Portfolio ${portfolioName} created successfully.`,
portfolioName
};
}
async function updatePortfolio(portfolioName: string, addAddresses: string[] = [], removeAddresses: string[] = []): Promise<FunctionResult> {
await qnLib.qnUpsertList(portfolioName, { add_items: addAddresses, remove_items: removeAddresses });
const updatedPortfolio = await qnLib.qnGetList(portfolioName);
return {
message: `Updated portfolio ${portfolioName}. Added ${addAddresses.length} addresses, removed ${removeAddresses.length} addresses.`,
portfolioName,
addresses: updatedPortfolio
};
}
async function getPortfolio(portfolioName: string): Promise<FunctionResult> {
const addresses = await qnLib.qnGetList(portfolioName);
return {
message: `Retrieved portfolio ${portfolioName}.`,
portfolioName,
addresses
};
}
async function getPortfolioBalances(portfolioName: string): Promise<FunctionResult> {
const addresses = await qnLib.qnGetList(portfolioName);
// @ts-ignore - Already validated in validateInput
const connection = new Connection(ENDPOINT);
const balances = await Promise.all(
addresses.map(async (address) => {
const publicKey = new PublicKey(address);
const balance = await connection.getBalance(publicKey);
return {
address,
balance: balance / LAMPORTS_PER_SOL
};
})
);
return {
message: `Retrieved balances for portfolio ${portfolioName}.`,
portfolioName,
balances
};
}
在这里,我们定义了四个指令处理程序:
createPortfolio
:使用 qnLib.upsertList
方法创建一个具有空地址列表的新组合。updatePortfolio
:使用 qnLib.upsertList
方法通过添加或删除地址来更新现有组合。getPortfolio
:使用 qnLib.getList
方法检索给定组合的地址列表。getPortfolioBalances
:使用 @solana/web3.js
库检索给定组合的余额。现在我们已经定义了辅助函数和指令处理程序,让我们定义将作为入口点的 main
函数。将以下代码添加到 index.ts
文件的底部:
export async function main(params: FunctionParams): Promise<FunctionResult> {
try {
validateInput(params);
const { instruction, portfolioName, addAddresses, removeAddresses } = params.user_data;
switch (instruction) {
case 'createPortfolio':
return await createPortfolio(portfolioName);
case 'updatePortfolio':
return await updatePortfolio(portfolioName, addAddresses, removeAddresses);
case 'getPortfolio':
return await getPortfolio(portfolioName);
case 'getPortfolioBalances':
return await getPortfolioBalances(portfolioName);
default:
throw new Error('Invalid instruction');
}
} catch (error) {
console.error('Error:', error);
return {
message: 'An error occurred',
portfolioName: params.user_data.portfolioName,
error: error instanceof Error ? error.message : String(error)
};
}
}
在这里,我们简单地验证输入参数,然后根据 user_data
对象中的 instruction
属性调用相应的指令处理程序。如果在验证或执行过程中发生任何错误,我们会捕获它们并返回一个包含 error
属性的 FunctionResult
对象。
现在我们已经有了函数代码,让我们设置构建过程。将以下脚本添加到你的 package.json
中:
"scripts": {
"build": "tsc",
},
这将创建一个 build
脚本,它将编译你的 TypeScript 代码并在项目根目录下创建一个 index.js
文件。这将是我们函数的入口点。
要构建并打包你的函数,请运行:
npm run build
这将编译你的 TypeScript 代码并在项目根目录下创建一个 index.js
文件。
现在你已经构建了你的函数,你可以将其部署到 QuickNode。前往 QuickNode 仪表板的 Function 页面,然后点击 + 创建函数 按钮。
将 环境 设置为 Node.js 20,然后点击 创建函数。
你可以定义函数的名称和描述,并使用默认设置:
点击 创建函数 进入下一步。
在这里,你需要从下拉菜单中选择 Code
。然后,你可以粘贴你刚刚创建的 index.js
文件的内容。完成后,点击 保存并关闭。你可以忽略“测试参数”部分,其中包括我们在本指南中不会使用的链。
干得好!你的函数现在已部署到 QuickNode。你应该会看到类似以下内容:
让我们测试一下(你将需要你的函数端点 URL 和 API 密钥——见上图)。
现在你的函数已部署,你可以使用 curl 命令或任何类似 Postman 的工具进行测试。将 YOUR_FUNCTION_URL
和 YOUR_API_KEY
替换为你在 QuickNode 中的实际函数 URL 和 API 密钥。
继续创建一个名为 "TEST" 的组合。请注意,就像我们的函数一样,我们的参数是通过 user_data
对象传递的。我们将 instruction
定义为 createPortfolio
,并将 portfolioName
定义为 TEST
:
curl -X POST "YOUR_FUNCTION_URL?result_only=true" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"user_data": {
"instruction": "createPortfolio",
"portfolioName": "TEST"
}
}'
请注意,我们在 URL 中包含了 ?result_only=true
,以便仅返回函数调用的结果。
你应该会看到类似以下的响应:
{
"message": "Portfolio TEST created successfully.",
"portfolioName": "TEST"
}
现在让我们向我们的组合中添加几个地址。请注意,我们使用的是 updatePortfolio
指令,并传入要添加到组合中的地址数组。随意将下面的地址替换为你自己的一个或多个地址:
curl -X POST "YOUR_FUNCTION_URL?result_only=true" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"user_data": {
"instruction": "updatePortfolio",
"portfolioName": "TEST",
"addAddresses": ["7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q"]
}
}'
请注意,我们使用的是与之前相同的 portfolioName
。你应该会看到类似以下的响应:
{
"addresses": [\
"7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q"\
],
"message": "Updated portfolio TEST. Added 1 addresses, removed 0 addresses.",
"portfolioName": "TEST"
}
最后,让我们获取我们的组合的余额。请注意,我们使用的是 getPortfolioBalances
指令,并传入与之前相同的 portfolioName
("TEST"):
curl -X POST "YOUR_FUNCTION_URL?result_only=true" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"user_data": {
"instruction": "getPortfolioBalances",
"portfolioName": "TEST"
}
}'
你应该会看到类似以下的响应:
{
"balances": [\
{\
"address": "7v91N7iZ9mNicL8WfG6cgSCKyRXydQjLh6UYBWwm6y1Q",\
"balance": 0\
}\
],
"message": "Retrieved balances for portfolio TEST.",
"portfolioName": "TEST"
}
干得好!
恭喜!你已成功使用 TypeScript 创建了一个 QuickNode Function,用于管理 Solana 钱包组合。该函数演示了如何与 QuickNode 的 Key-Value Store 和 Solana 区块链进行交互,以创建、更新和检索组合信息。
要了解更多关于 QuickNode Functions 的信息并探索其他功能,请查看我们的 文档 并加入我们的 Discord 社区 以获得支持和讨论。
想继续使用 Functions 进行构建吗?查看我们的 Functions 库 获取更多示例和灵感。
如果你有任何反馈或新主题的请求,请 告诉我们。我们非常乐意听取你的意见。
- 原文链接: quicknode.com/guides/qui...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!