This package offers various visitors for Codama IDLs to traverse and manipulate their nodes.
pnpm install @codama/visitors
Note
This package is included in the main codama
package. Meaning, you already have access to its content if you are installing Codama this way.
pnpm install codama
This package includes and re-exports the @codama/visitors-core
package which provides the core interfaces and functions to create and compose visitors.
To get a better understanding of visitors and how they work, please refer to the @codama/visitors-core
documentation.
In the rest of this documentation, we focus on the high-level visitors that are only available in this package. The main goal of these visitors is to provide a set of specific operations that can be applied to Codama IDLs — as opposed to the generic primitives provided by the core package.
For instance, this package offers visitors that unwrap link nodes, update instructions, add PDAs, set default values, and more.
Let's go through all of them alphabetically.
This visitor adds PdaNodes
to the desired ProgramNodes
. It accepts an object where the keys are the program names and the values are the PdaNodes
to add within these programs.
codama.update(
addPdasVisitor({
// Add a PDA to the 'token' program.
token: [
{
name: 'associatedToken',
seeds: [
variablePdaSeedNode('mint', publicKeyTypeNode()),
constantPdaSeedNode(
publicKeyTypeNode(),
publicKeyValueNode('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
),
variablePdaSeedNode('owner', publicKeyTypeNode()),
],
},
],
// Add two PDAs to the 'counter' program.
counter: [
{
name: 'counter',
seeds: [variablePdaSeedNode('authority', publicKeyTypeNode())],
},
{
name: 'counterConfig',
seeds: [variablePdaSeedNode('counter', publicKeyTypeNode())],
},
],
}),
);
This visitor splits an instruction into multiple sub-instructions by using an enum argument such that each of its variants creates a different sub-instruction. It accepts an object where the keys are the instruction names and the values are the enum argument names that will be used to split the instruction.
codama.update(
createSubInstructionsFromEnumArgsVisitor({
mint: 'mintArgs',
transfer: 'transferArgs',
burn: 'burnArgs',
}),
);
This visitor goes through the DefinedTypeNodes
of all ProgramNodes
inside the Codama IDL and removes any duplicates. A DefinedTypeNode
is considered a duplicate if it has the same name and data structure as another DefinedTypeNode
. This is useful when you have multiple programs that share the same types.
codama.update(deduplicateIdenticalDefinedTypesVisitor());
This visitor fills any missing PdaSeedValueNodes
from PdaValueNodes
using the provided NodePath<InstructionNode>
such that:
- If a
VariablePdaSeedNode
is of typePublicKeyTypeNode
and the name of the seed matches the name of an account in theInstructionNode
, then a newPdaSeedValueNode
will be added with the matching account. - Otherwise, if a
VariablePdaSeedNode
is of any other type and the name of the seed matches the name of an argument in theInstructionNode
, then a newPdaSeedValueNode
will be added with the matching argument. - Otherwise, no
PdaSeedValueNode
will be added.
It also requires a LinkableDictionary
to resolve any link nodes and an optional strictMode
boolean to throw an error if seeds are still missing after the visitor has run.
Note that this visitor is mainly used for internal purposes.
codama.update(fillDefaultPdaSeedValuesVisitor(instructionPath, linkables, strictMode));
This visitor flattens any instruction arguments of type StructTypeNode
such that their fields are no longer nested. This can be useful to simplify the data structure of an instruction.
codama.update(flattenInstructionDataArgumentsVisitor());
This visitor flattens any struct fields that are also structs such that their fields are no longer nested. It accepts an object such that the keys are the struct names and the values are the field names to flatten or "*"
to flatten all struct fields.
codama.update(
flattenStructVisitor({
counter: ['data', 'config'],
escrow: '*',
}),
);
This visitor goes through all DefinedTypeNodes
and outputs a histogram of how many times each type is used in the Codama IDL.
const histogram = codama.accept(getDefinedTypeHistogramVisitor());
The returned histogram is an object such that the keys are the names of visited DefinedTypeNodes
and the values are objects with properties described below.
export type DefinedTypeHistogram = {
[key: CamelCaseString]: {
// The number of times the type is used as a direct instruction argument.
directlyAsInstructionArgs: number;
// The number of times the type is used in account data.
inAccounts: number;
// The number of times the type is used in other defined types.
inDefinedTypes: number;
// The number of times the type is used in instruction arguments.
inInstructionArgs: number;
// The number of times the type is used in total.
total: number;
};
};
This histogram is used internally in other visitors to understand how types are used before applying transformations.
This visitor helps set account discriminators based on a field in the account data and the value it should take. This is typically used on the very first field of the account data which usually refers to a discriminator value that helps distinguish between multiple accounts in a program.
codama.update(
setAccountDiscriminatorFromFieldVisitor({
counter: { field: 'discriminator', value: k.enumValueNode('accountState', 'counter') },
escrow: { field: 'discriminator', value: k.enumValueNode('accountState', 'escrow') },
vault: { field: 'discriminator', value: k.enumValueNode('accountState', 'vault') },
}),
);
This visitor uses the getByteSizeVisitor
to check the size of all AccountNodes
and, if a fixed-size is identified, it sets the size
property of the account to that value.
codama.update(setFixedAccountSizesVisitor());
This visitor helps set the default values of instruction accounts in bulk. It accepts an array of "rule" objects that must contain the default value to set and the name of the instruction account to set it on. The account name may also be a regular expression to match more complex patterns.
codama.update(
setInstructionAccountDefaultValuesVisitor([
{
// Set this public key as default value to any account named 'counterProgram'.
account: 'counterProgram',
defaultValue: publicKeyValueNode('MyCounterProgram11111111111111111111111111'),
},
{
// Set this PDA as default value to any account named 'associatedToken' or 'ata'.
account: /^(associatedToken|ata)$/,
defaultValue: pdaValueNode('associatedToken'),
},
]),
);
This visitor adds a new instruction argument to each of the provided instruction names. The new argument is added before any existing argument and marked as a discriminator of the instruction. This is useful if your Codama IDL is missing discriminators in the instruction data.
codama.update(
setInstructionDiscriminatorsVisitor({
mint: { name: 'discriminator', type: numberTypeNode('u8'), value: numberValueNode(0) },
transfer: { name: 'discriminator', type: numberTypeNode('u8'), value: numberValueNode(1) },
burn: { name: 'discriminator', type: numberTypeNode('u8'), value: numberValueNode(2) },
}),
);
This visitor helps wrap NumberTypeNodes
matching a given name with a specific number wrapper.
codama.update(
setNumberWrappersVisitor({
lamports: { kind: 'SolAmount' },
timestamp: { kind: 'DateTime' },
percent: { decimals: 2, kind: 'Amount', unit: '%' },
}),
);
This visitor sets default values for all provided fields of a struct. It accepts an object where the keys are the struct names and the values are objects that map field names to their new default values.
codama.update(
setStructDefaultValuesVisitor({
person: {
age: numberValueNode(42),
dateOfBirth: noneValueNode(),
},
counter: {
count: numberValueNode(0),
},
}),
);
This visitor transforms DefinedTypeNodes
matching the provided names into AccountNodes
within the same ProgramNode
.
codama.update(transformDefinedTypesIntoAccountsVisitor(['counter', 'escrow']));
This visitor transforms any fixed-size array of u8
numbers into a fixed-size BytesTypeNode
.
codama.update(transformU8ArraysToBytesVisitor());
This visitor replaces any DefinedTypeLinkNode
with the actual DefinedTypeNode
it points to. By default, it unwraps all defined types, but you can provide an array of names to only unwrap specific types.
Note that if multiple link nodes point to the same defined type, each link node will be replaced by a copy of the defined type.
codama.update(unwrapDefinedTypesVisitor(['counter', 'escrow']));
This visitor replaces DefinedTypeLinkNodes
used only once inside an instruction argument with the actual DefinedTypeNodes
they refer to.
codama.update(unwrapInstructionArgsDefinedTypesVisitor());
This visitor transforms EnumTupleVariantTypeNodes
with a single StructTypeNode
item into EnumStructVariantTypeNodes
. By default, it will unwrap all tuple variants matching that criteria, but you can provide an array of names to only unwrap specific variants.
codama.update(unwrapTupleEnumWithSingleStructVisitor());
This visitor replaces any DefinedTypeLinkNode
matching the provided NodeSelectors
with the actual DefinedTypeNode
it points to.
Contrary to the unwrapDefinedTypesVisitor
though, it only replaces the requested DefinedTypeLinkNodes
and does not remove the associated DefinedTypeNode
from its ProgramNode
.
codama.update(unwrapTypeDefinedLinksVisitor(['[accountNode]counter.data', '[instructionNode]transfer.config']));
This visitor allows us to update various aspects of AccountNodes
and/or delete them. It accepts an object where the keys are the account names and the values are the operations to apply to these accounts.
codama.update(
updateAccountsVisitor({
vault: {
// Rename the 'vault' account to 'safe'.
name: 'safe',
// Rename the 'owner' field to 'authority'.
data: { owner: 'authority' },
// Create a new PDA node and link it to this account.
seeds: [variablePdaSeedNode('authority', publicKeyTypeNode())],
},
counter: {
// Delete the 'counter' account.
delete: true,
},
}),
);
This visitor allows us to update various aspects of DefinedTypeNode
and/or delete them. It accepts an object where the keys are the defined type names and the values are the operations to apply to these types.
codama.update(
updateDefinedTypesVisitor({
options: {
// Rename the 'options' type to 'configs'.
name: 'configs',
// Rename the 'sol' field to 'lamports'.
data: { sol: 'lamports' },
},
player: {
// Delete the 'player' type.
delete: true,
},
}),
);
This visitor allows us to update various aspects of ErrorNodes
and/or delete them. It accepts an object where the keys are the error names and the values are the operations to apply to these errors.
codama.update(
updateErrorsVisitor({
invalidPda: {
// Rename the 'invalidPda' error to 'invalidProgramDerivedAddress'.
name: 'invalidProgramDerivedAddress',
// Change the error message.
message: 'The program-derived address is invalid.',
// Change the error code.
code: 123,
},
accountMismatch: {
// Delete the 'accountMismatch' error.
delete: true,
},
}),
);
This visitor allows us to update various aspects of InstructionNodes
and/or delete them. It accepts an object where the keys are the instruction names and the values are the operations to apply to these instructions.
codama.update(
updateInstructionsVisitor({
send: {
// Rename the 'send' instruction to 'transfer'.
name: 'transfer',
accounts: {
// Rename the 'owner' instruction account to 'authority'.
owner: { name: 'authority' },
// Set a default value for the 'associatedToken' instruction account.
associatedToken: { defaultValue: pdaValueNode('associatedToken') },
// Update the signer status of the 'payer' instruction account to `true`.
payer: { isSigner: true },
// Mark the 'mint' instruction account as optional.
mint: { isOptional: true },
},
arguments: {
// Set a default value for the 'amount' instruction argument to 1.
amount: { defaultValue: numberValueNode(1) },
// Rename the 'decimals' instruction argument to 'mintDecimals'.
decimals: { name: 'mintDecimals' },
},
},
burn: {
// Delete the 'burn' instruction.
delete: true,
},
}),
);
This visitor allows us to update various aspects of ProgramNodes
and/or delete them. It accepts an object where the keys are the program names and the values are the operations to apply to these programs.
codama.update(
updateProgramsVisitor({
splToken: {
// Rename the 'splToken' program to 'token'.
name: 'token',
// Change the program version.
version: '3.0.0',
// Change the program's public key.
publicKey: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA',
},
splAssociatedToken: {
// Delete the 'splAssociatedToken' program.
delete: true,
},
}),
);