Створіть свій 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
Для визначення насіння для програмно похідних адрес (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
. Цей вузол представляє інструкцію в програмі і дозволяє вам вказати дискримінатор, необхідні облікові записи та дані інструкції без ускладнень.
Крім того, ви можете включити опціональні облікові записи. Залежно від дизайну вашої програми, ці опціональні облікові записи можуть бути вирішені двома способами: шляхом їх пропуску або передачі ідентифікатора програми як облікового запису. Ви можете вибрати метод вирішення за допомогою поля 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
або надати defaultValue
, який перетворюється на ValueNode
.
Параметр defaultValueStrategy в instructionArgumentNode визначає, як обробляються значення за замовчуванням: "optional" означає, що значення за замовчуванням аргументу може бути перевизначене наданим аргументом, або "omitted", що означає, що аргумент не повинен бути наданий, і завжди має використовуватися значення за замовчуванням
Якщо не вказано, стратегія за замовчуванням — "optional".
Вузол помилки
Для визначення помилок, які може повертати програма, ми використовуємо ErrorNode
. Цей вузол характеризується назвою, числовим кодом, який буде повернуто, та пов'язаним читабельним повідомленням для налагодження.
const node = errorNode({
name: 'invalidAmountArgument',
code: 1,
message: 'The amount argument is invalid.',
});
Вузол PDA
Для надання визначень конкретних Program-Derived Addresses (PDA), ми використовуємо PdaNode
. Цей вузол характеризується назвою та списком сідів, які можуть бути константними або змінними, що дозволяє гнучко генерувати PDA.
const node = pdaNode({
name: 'counter',
seeds: [variablePdaSeedNode('authority', publicKeyTypeNode())],
docs: ['The counter PDA derived from its authority.'],
});
Це можна використовувати для зв'язування PDA як defaultValue
у instructionAccountNode
за допомогою pdaValueNode
.