从零开始构建您的 Codama IDL
从零开始构建 Codama IDL 意味着为您的程序创建完整的节点树。为了高效完成此任务,让我们来了解可用的节点类型。
节点类型
值节点
要将特定值传递到节点中,我们使用ValueNode。此类型表示所有可用的值节点,这些节点可以容纳不同类型的数据。
这里可以找到所有可用值的详细文档。
类型节点
要定义数据的结构和形状,我们使用TypeNode。这些节点描述了期望的数据类型,例如数字、字符串、结构体、数组或自定义类型。
它们定义了模式而不包含实际值。
这里可以找到所有可用类型的详细文档。
判别器节点
为了区分程序中的账户和指令,我们使用判别器。有不同的方法可以实现这一点,而 DiscriminatorNode 提供了所有可用的选项:
ConstantDiscriminatorNode:用于描述给定偏移量的常量值。它需要一个ConstantValueNode作为常量和一个number作为偏移量:tsconst discriminatorNode = constantDiscriminatorNode( constantValueNodeFromString("utf8", "Hello"), 64, );FieldDiscriminatorNode:用于描述给定偏移量的结构字段的默认值。它需要一个CamelCaseString作为字段名称和一个number作为偏移量:tsconst discriminatorNode = fieldDiscriminatorNode("accountState", 64);tsaccountNode({ data: structTypeNode([ structFieldTypeNode({ name: "discriminator", type: numberTypeNode("u32"), defaultValue: numberValueNode(42), defaultValueStrategy: "omitted", }), // ... ]), discriminators: [fieldDiscriminatorNode("discriminator")], // ... });SizeDiscriminatorNode:用于根据数据大小区分账户或指令。它以number作为大小参数:tsconst discriminatorNode = sizeDiscriminatorNode(165);
Pda Seed Node
要为程序派生地址(PDA)定义种子,我们使用 PdaSeedNode。这些节点指定了 PDA 地址应如何派生,可以是常量值或变量输入。PdaSeedNode 提供了定义 PDA 种子的不同方法:
ConstantPdaSeedNode:用于描述程序派生地址的常量种子。它结合了TypeNode和ValueNode:tsconst pdaSeedNode = constantPdaSeedNode(stringTypeNode("utf8"), stringValueNode("auth"));tsconst pdaSeedNode = constantPdaSeedNodeFromString("utf8", "auth");VariablePdaSeedNode:用于描述程序派生地址的变量种子。它需要一个名称和一个TypeNode:tsconst pdaSeedNode = variablePdaSeedNode("authority", publicKeyTypeNode());
以下是它们在 pdaNode 中的使用方式:
const counterPda = pdaNode({
name: "counter",
seeds: [
constantPdaSeedNodeFromString("utf8", "counter"),
variablePdaSeedNode("authority", publicKeyTypeNode()),
],
});编写 Codama IDL
现在我们已经了解了程序中可用的最重要节点,让我们来探索如何从头开始创建一个 Codama IDL。
根节点
为了创建您的 Codama IDL 的基础,我们使用RootNode。此节点作为顶级容器,包含您的主要ProgramNode以及任何可能被主要程序引用的附加程序。
const node = rootNode(programNode({ ... }));程序节点
为了定义一个完整的链上程序,我们使用ProgramNode。此节点表示部署在链上的完整程序,并定义了所有最低可行元素,例如账户、指令、PDA 和错误。
除了这些核心元素,此节点还接受程序的名称、版本、部署公钥和 Markdown 文档:
const node = programNode({
name: "counter",
publicKey: "22222222222222222222222222222222222222222222",
version: "0.0.1",
docs: [],
accounts: [],
instructions: [],
definedTypes: [],
pdas: [],
errors: [],
});在接下来的部分中,我们将详细研究所有这些元素。
账户节点
为了定义链上账户,我们使用AccountNode。此节点的特点是其名称、数据结构以及可选属性,例如 PDA 定义和账户区分符。
它表示通常对应于程序的state.rs文件的账户。
docs 字段可用于添加文档,解释此账户在程序中的作用:
const node = accountNode({
name: "token",
data: structTypeNode([
structFieldTypeNode({ name: "mint", type: publicKeyTypeNode() }),
structFieldTypeNode({ name: "owner", type: publicKeyTypeNode() }),
structFieldTypeNode({ name: "amount", type: numberTypeNode("u64") }),
]),
discriminators: [sizeDiscriminatorNode(72)],
size: 72,
pda: pdaLinkNode("associatedTokenAccount"),
});指令节点
为了定义程序指令,我们使用InstructionNode。此节点表示程序中的一个指令,并允许您轻松指定区分符、所需账户和指令数据。
此外,您可以包含可选账户。根据程序的设计,这些可选账户可以通过两种方式解决:省略或将程序 ID 作为账户传递。您可以使用optionalAccountStrategy: "omitted" | "programId"字段选择解决方法。
docs字段可用于添加文档,解释此指令在程序中完成的功能。
const node = instructionNode({
name: "increment",
discriminators: [fieldDiscriminatorNode("discriminator")],
arguments: [
instructionArgumentNode({
name: "discriminator",
type: numberTypeNode("u8"),
defaultValue: numberValueNode(1),
defaultValueStrategy: "omitted",
}),
],
accounts: [
instructionAccountNode({
name: "counter",
isWritable: true,
isSigner: true,
}),
instructionAccountNode({
name: "authority",
isWritable: false,
isSigner: false,
}),
],
remainingAccounts: [
instructionRemainingAccountsNode(argumentValueNode("authorities"), {
isSigner: true,
}),
],
optionalAccountStrategy: "omitted",
});在instructionAccountNode中,您可以使用isOptional: boolean字段指定账户的可选性,或者提供一个解析为ValueNode的defaultValue。
instructionArgumentNode中的defaultValueStrategy决定了如何处理默认值:"optional"表示参数的默认值可以被提供的参数覆盖,"omitted"表示不应提供参数,始终使用默认值。
如果未指定,策略默认为"optional"。
错误节点
为了定义程序可能返回的错误,我们使用ErrorNode。此节点的特点是一个名称、一个将被返回的数字代码以及一个用于调试的可读消息。
const node = errorNode({
name: "invalidAmountArgument",
code: 1,
message: "The amount argument is invalid.",
});PDA节点
为了为特定的程序派生地址(PDA)提供定义,我们使用PdaNode。此节点的特点是一个名称和一个种子列表,这些种子可以是常量或变量,从而允许灵活的PDA生成。
const node = pdaNode({
name: "counter",
seeds: [variablePdaSeedNode("authority", publicKeyTypeNode())],
docs: ["The counter PDA derived from its authority."],
});这可以用于将PDA作为defaultValue链接到instructionAccountNode中,使用pdaValueNode。