Erstelle deine Codama IDL von Grund auf
Eine Codama IDL von Grund auf zu erstellen bedeutet, den kompletten Baum von Knoten für dein Programm zu erstellen. Um dies effizient zu tun, schauen wir uns die verfügbaren Knotentypen an.
Knotentypen
Wert-Knoten
Um bestimmte Werte in einen Knoten zu übergeben, verwenden wir ValueNode. Dieser Typ repräsentiert alle verfügbaren Wert-Knoten, die verschiedene Datentypen enthalten können.
Hier findest du detaillierte Dokumentation zu allen verfügbaren Werten.
Typ-Knoten
Um die Struktur und Form von Daten zu definieren, verwenden wir TypeNode. Diese Knoten beschreiben, welche Art von Daten erwartet wird: wie Zahlen, Strings, Structs, Arrays oder benutzerdefinierte Typen.
Sie definieren das Schema, ohne tatsächliche Werte zu enthalten.
Hier findest du detaillierte Dokumentation zu allen verfügbaren Typen.
Diskriminator-Knoten
Um zwischen Konten und Anweisungen in unserem Programm zu unterscheiden, verwenden wir Diskriminatoren. Es gibt verschiedene Methoden, um dies zu erreichen, und der DiscriminatorNode bietet alle verfügbaren Optionen:
ConstantDiscriminatorNode: Wird verwendet, um einen konstanten Wert an einem bestimmten Offset zu beschreiben. Es nimmt einenConstantValueNodeals Konstante und einennumberals Offset:tsconst discriminatorNode = constantDiscriminatorNode(constantValueNodeFromString('utf8', 'Hello'), 64);FieldDiscriminatorNode: Wird verwendet, um einen Standardwert eines Struct-Felds an einem bestimmten Offset zu beschreiben. Es nimmt einenCamelCaseStringals Feldnamen und einennumberals Offset:tsconst discriminatorNode = fieldDiscriminatorNode('accountState', 64);tsaccountNode({ data: structTypeNode([ structFieldTypeNode({ name: 'discriminator', type: numberTypeNode('u32'), defaultValue: numberValueNode(42), defaultValueStrategy: 'omitted', }), // ... ]), discriminators: [fieldDiscriminatorNode('discriminator')], // ... });SizeDiscriminatorNode: Wird verwendet, um Konten oder Anweisungen basierend auf der Größe ihrer Daten zu unterscheiden. Es nimmt einennumberals Größenparameter:tsconst discriminatorNode = sizeDiscriminatorNode(165);
Pda Seed Node
Um Seeds für Program Derived Addresses (PDAs) zu definieren, verwenden wir PdaSeedNode. Diese Knoten geben an, wie PDA-Adressen entweder aus konstanten Werten oder variablen Eingaben abgeleitet werden sollen. Der PdaSeedNode bietet verschiedene Methoden zur Definition von PDA-Seeds:
ConstantPdaSeedNode: Wird verwendet, um einen konstanten Seed für eine programmabgeleitete Adresse zu beschreiben. Es verwendet eine Kombination ausTypeNodeundValueNode:tsconst pdaSeedNode = constantPdaSeedNode(stringTypeNode('utf8'), stringValueNode('auth'));tsconst pdaSeedNode = constantPdaSeedNodeFromString('utf8', 'auth');VariablePdaSeedNode: Wird verwendet, um einen variablen Seed für eine programmabgeleitete Adresse zu beschreiben. Es nimmt einen Namen und einenTypeNode:tsconst pdaSeedNode = variablePdaSeedNode('authority', publicKeyTypeNode())
Hier ist, wie sie in einem pdaNode verwendet werden:
const counterPda = pdaNode({
name: 'counter',
seeds: [
constantPdaSeedNodeFromString('utf8', 'counter'),
variablePdaSeedNode('authority', publicKeyTypeNode()),
],
});Eine Codama IDL schreiben
Nachdem wir die wichtigsten verfügbaren Knoten in einem Programm untersucht haben, wollen wir nun entdecken, wie man eine Codama IDL von Grund auf erstellt.
Root-Knoten
Um die Grundlage Ihrer Codama IDL zu erstellen, verwenden wir RootNode. Dieser Knoten dient als Container auf oberster Ebene, der Ihr Haupt-ProgramNode sowie alle zusätzlichen Programme enthält, auf die das Hauptprogramm verweisen kann.
const node = rootNode(programNode({ ... }));Programm-Knoten
Um ein vollständiges On-Chain-Programm zu definieren, verwenden wir ProgramNode. Dieser Knoten repräsentiert das komplette Programm, das auf der Blockchain bereitgestellt wird, und definiert alle minimal erforderlichen Elemente wie Konten, Anweisungen, PDAs und Fehler.
Zusätzlich zu diesen Kernelementen akzeptiert dieser Knoten den Namen des Programms, die Version, den öffentlichen Bereitstellungsschlüssel und Markdown-Dokumentation:
const node = programNode({
name: 'counter',
publicKey: '22222222222222222222222222222222222222222222',
version: '0.0.1',
docs: [],
accounts: [],
instructions: [],
definedTypes: [],
pdas: [],
errors: [],
});In den nächsten Abschnitten werden wir all diese Elemente im Detail untersuchen.
Konto-Knoten
Um On-Chain-Konten zu definieren, verwenden wir AccountNode. Dieser Knoten wird durch seinen Namen, seine Datenstruktur und optionale Attribute wie PDA-Definitionen und Konto-Diskriminatoren charakterisiert.
Er repräsentiert die Konten, die typischerweise der state.rsDatei Ihres Programms entsprechen.
Das Docs-Feld kann verwendet werden, um Dokumentation hinzuzufügen, die erklärt, was dieses Konto innerhalb des Programms bewirkt:
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"),
});Anweisungs-Knoten
Um Programmanweisungen zu definieren, verwenden wir InstructionNode. Dieser Knoten repräsentiert eine Anweisung in einem Programm und ermöglicht es Ihnen, den Diskriminator, erforderliche Konten und Anweisungsdaten ohne Komplikationen anzugeben.
Zusätzlich können Sie optionale Konten einbeziehen. Je nach Design Ihres Programms können diese optionalen Konten auf zwei Arten aufgelöst werden: indem sie weggelassen werden oder indem die Programm-ID als Konto übergeben wird. Sie können die Auflösungsmethode mit dem Feld optionalAccountStrategy: "omitted" | "programId" auswählen.
Das Feld docs kann verwendet werden, um Dokumentation hinzuzufügen, die erklärt, was diese Anweisung innerhalb des Programms bewirkt.
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',
});In instructionAccountNode können Sie die Optionalität von Konten mit dem Feld isOptional: boolean angeben oder einen defaultValue bereitstellen, der zu einem ValueNode führt.
Die defaultValueStrategy in instructionArgumentNode bestimmt, wie Standardwerte behandelt werden: "optional" bedeutet, dass der Standardwert des Arguments durch ein bereitgestelltes Argument überschrieben werden kann, oder "omitted", was bedeutet, dass kein Argument bereitgestellt werden sollte und der Standardwert immer verwendet werden sollte
Die Strategie ist standardmäßig "optional", wenn nicht anders angegeben.
Error Node
Um Fehler zu definieren, die von einem Programm zurückgegeben werden können, verwenden wir ErrorNode. Dieser Knoten ist durch einen Namen, einen numerischen Code, der zurückgegeben wird, und eine zugehörige lesbare Nachricht für das Debugging gekennzeichnet.
const node = errorNode({
name: 'invalidAmountArgument',
code: 1,
message: 'The amount argument is invalid.',
});PDA Node
Um Definitionen für spezifische Program-Derived Addresses bereitzustellen, verwenden wir PdaNode. Dieser Knoten ist durch einen Namen und eine Liste von Seeds gekennzeichnet, die entweder konstant oder variabel sein können, was eine flexible PDA-Generierung ermöglicht.
const node = pdaNode({
name: 'counter',
seeds: [variablePdaSeedNode('authority', publicKeyTypeNode())],
docs: ['The counter PDA derived from its authority.'],
});Dies kann verwendet werden, um PDAs als defaultValue im instructionAccountNode mit dem pdaValueNode zu verknüpfen.