Construire votre IDL Codama à partir de zéro
Construire un IDL Codama à partir de zéro signifie créer l'arborescence complète des nœuds pour votre programme. Pour le faire efficacement, examinons les types de nœuds disponibles.
Types de nœuds
Nœud de valeur
Pour transmettre des valeurs spécifiques à un nœud, nous utilisons ValueNode. Ce type représente tous les nœuds de valeur disponibles qui peuvent contenir différents types de données.
Ici vous pouvez trouver une documentation détaillée sur toutes les valeurs disponibles.
Nœud de type
Pour définir la structure et la forme des données, nous utilisons TypeNode. Ces nœuds décrivent quel type de données est attendu : comme des nombres, des chaînes de caractères, des structures, des tableaux ou des types personnalisés.
Ils définissent le schéma sans contenir de valeurs réelles.
Ici vous pouvez trouver une documentation détaillée sur tous les types disponibles.
Nœud discriminateur
Pour différencier les comptes et les instructions dans notre programme, nous utilisons des discriminateurs. Il existe différentes méthodes pour y parvenir, et le DiscriminatorNode fournit toutes les options disponibles :
ConstantDiscriminatorNode: Utilisé pour décrire une valeur constante à un décalage donné. Il prend unConstantValueNodecomme constante et unnumbercomme décalage :tsconst discriminatorNode = constantDiscriminatorNode(constantValueNodeFromString('utf8', 'Hello'), 64);FieldDiscriminatorNode: Utilisé pour décrire une valeur par défaut d'un champ de structure à un décalage donné. Il prend unCamelCaseStringcomme nom de champ et unnumbercomme décalage :tsconst discriminatorNode = fieldDiscriminatorNode('accountState', 64);tsaccountNode({ data: structTypeNode([ structFieldTypeNode({ name: 'discriminator', type: numberTypeNode('u32'), defaultValue: numberValueNode(42), defaultValueStrategy: 'omitted', }), // ... ]), discriminators: [fieldDiscriminatorNode('discriminator')], // ... });SizeDiscriminatorNode: Utilisé pour distinguer les comptes ou les instructions en fonction de la taille de leurs données. Il prend unnumbercomme paramètre de taille :tsconst discriminatorNode = sizeDiscriminatorNode(165);
Nœud Pda Seed
Pour définir les seeds des adresses dérivées de programme (PDAs), nous utilisons PdaSeedNode. Ces nœuds spécifient comment les adresses PDA doivent être dérivées, soit à partir de valeurs constantes, soit à partir d'entrées variables. Le PdaSeedNode fournit différentes méthodes pour définir les seeds PDA :
ConstantPdaSeedNode: Utilisé pour décrire un seed constant pour une adresse dérivée de programme. Il prend unTypeNodeet unValueNodeen combinaison :tsconst pdaSeedNode = constantPdaSeedNode(stringTypeNode('utf8'), stringValueNode('auth'));tsconst pdaSeedNode = constantPdaSeedNodeFromString('utf8', 'auth');VariablePdaSeedNode: Utilisé pour décrire un seed variable pour une adresse dérivée de programme. Il prend un nom et unTypeNode:tsconst pdaSeedNode = variablePdaSeedNode('authority', publicKeyTypeNode())
Voici comment ils sont utilisés dans un pdaNode :
const counterPda = pdaNode({
name: 'counter',
seeds: [
constantPdaSeedNodeFromString('utf8', 'counter'),
variablePdaSeedNode('authority', publicKeyTypeNode()),
],
});Écrire un IDL Codama
Maintenant que nous avons examiné les nœuds les plus importants disponibles dans un programme, découvrons comment créer un IDL Codama à partir de zéro.
Nœud racine
Pour créer la base de votre IDL Codama, nous utilisons RootNode. Ce nœud sert de conteneur de premier niveau qui contient votre ProgramNode principal ainsi que tout programme supplémentaire pouvant être référencé par le programme principal.
const node = rootNode(programNode({ ... }));Nœud de programme
Pour définir un programme complet sur la chaîne, nous utilisons ProgramNode. Ce nœud représente le programme complet déployé sur la chaîne et définit tous les éléments minimaux viables tels que les comptes, les instructions, les PDA et les erreurs.
En plus de ces éléments fondamentaux, ce nœud accepte le nom du programme, la version, la clé publique de déploiement et la documentation en markdown :
const node = programNode({
name: 'counter',
publicKey: '22222222222222222222222222222222222222222222',
version: '0.0.1',
docs: [],
accounts: [],
instructions: [],
definedTypes: [],
pdas: [],
errors: [],
});Dans les sections suivantes, nous examinerons tous ces éléments en détail.
Nœud de compte
Pour définir les comptes sur la chaîne, nous utilisons AccountNode. Ce nœud est caractérisé par son nom, sa structure de données et des attributs optionnels tels que les définitions PDA et les discriminateurs de compte.
Il représente les comptes qui correspondent généralement au fichier state.rs de votre programme.
Le champ docs peut être utilisé pour ajouter une documentation expliquant ce que ce compte accomplit au sein du programme :
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"),
});Nœud d'instruction
Pour définir les instructions du programme, nous utilisons InstructionNode. Ce nœud représente une instruction dans un programme et vous permet de spécifier le discriminateur, les comptes requis et les données d'instruction sans complications.
De plus, vous pouvez inclure des comptes optionnels. Selon la conception de votre programme, ces comptes optionnels peuvent être résolus de deux façons : en étant omis ou en passant l'ID du programme comme compte. Vous pouvez sélectionner la méthode de résolution en utilisant le champ optionalAccountStrategy: "omitted" | "programId".
Le champ docs peut être utilisé pour ajouter de la documentation expliquant ce que cette instruction accomplit dans le programme.
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',
});Dans instructionAccountNode, vous pouvez spécifier l'optionalité du compte en utilisant le champ isOptional: boolean, ou fournir un defaultValue qui se résout en un ValueNode.
Le defaultValueStrategy dans instructionArgumentNode détermine comment les valeurs par défaut sont gérées : "optional" signifie que la valeur par défaut de l'argument peut être remplacée par un argument fourni ou "omitted" qui signifie qu'aucun argument ne doit être fourni et que la valeur par défaut doit toujours être utilisée
La stratégie est définie par défaut sur "optional" si non spécifiée.
Nœud d'erreur
Pour définir les erreurs qui peuvent être renvoyées par un programme, nous utilisons ErrorNode. Ce nœud est caractérisé par un nom, un code numérique qui sera renvoyé et un message lisible associé pour le débogage.
const node = errorNode({
name: 'invalidAmountArgument',
code: 1,
message: 'The amount argument is invalid.',
});Nœud PDA
Pour fournir des définitions pour des adresses dérivées de programme spécifiques, nous utilisons PdaNode. Ce nœud est caractérisé par un nom et une liste de seeds qui peuvent être constantes ou variables, permettant une génération flexible de PDA.
const node = pdaNode({
name: 'counter',
seeds: [variablePdaSeedNode('authority', publicKeyTypeNode())],
docs: ['The counter PDA derived from its authority.'],
});Cela peut être utilisé pour lier des PDA comme defaultValue dans le instructionAccountNode en utilisant le pdaValueNode.