Skip to content

Commit

Permalink
Add a visitor that adds PDA nodes to programs (#139)
Browse files Browse the repository at this point in the history
  • Loading branch information
lorisleiva authored Jan 4, 2024
1 parent f399702 commit 3bfeb8c
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/shaggy-paws-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@metaplex-foundation/kinobi': minor
---

Add a visitor that adds PDA nodes to programs
37 changes: 37 additions & 0 deletions src/visitors/addPdasVisitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { KinobiError, mainCase } from '../shared';
import { PdaSeedNode, assertIsNode, pdaNode, programNode } from '../nodes';
import { bottomUpTransformerVisitor } from './bottomUpTransformerVisitor';

export function addPdasVisitor(
pdas: Record<string, { name: string; seeds: PdaSeedNode[] }[]>
) {
return bottomUpTransformerVisitor(
Object.entries(pdas).map(([uncasedProgramName, newPdas]) => {
const programName = mainCase(uncasedProgramName);
return {
select: `[programNode]${programName}`,
transform: (node) => {
assertIsNode(node, 'programNode');
const existingPdaNames = new Set(node.pdas.map((pda) => pda.name));
const newPdaNames = new Set(newPdas.map((pda) => pda.name));
const overlappingPdaNames = new Set(
[...existingPdaNames].filter((name) => newPdaNames.has(name))
);
if (overlappingPdaNames.size > 0) {
throw new KinobiError(
`Cannot add PDAs to program "${programName}" because the following PDA names ` +
`already exist: ${[...overlappingPdaNames].join(', ')}.`
);
}
return programNode({
...node,
pdas: [
...node.pdas,
...newPdas.map((pda) => pdaNode(pda.name, pda.seeds)),
],
});
},
};
})
);
}
1 change: 1 addition & 0 deletions src/visitors/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './addPdasVisitor';
export * from './bottomUpTransformerVisitor';
export * from './consoleLogVisitor';
export * from './createSubInstructionsFromEnumArgsVisitor';
Expand Down
81 changes: 81 additions & 0 deletions test/visitors/addPdasVisitor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import test from 'ava';
import {
addPdasVisitor,
constantPdaSeedNodeFromString,
pdaNode,
programIdPdaSeedNode,
programNode,
publicKeyTypeNode,
variablePdaSeedNode,
visit,
} from '../../src';

test('it adds PDA nodes to a program', (t) => {
// Given a program with a single PDA.
const node = programNode({
name: 'myProgram',
publicKey: 'Epo9rxh99jpeeWabRZi4tpgUVxZQeVn9vbbDjUztJtu4',
pdas: [
pdaNode('associatedToken', [
variablePdaSeedNode('owner', publicKeyTypeNode()),
programIdPdaSeedNode(),
variablePdaSeedNode('mint', publicKeyTypeNode()),
]),
],
});

// When we add two more PDAs.
const newPdas = [
pdaNode('metadata', [
constantPdaSeedNodeFromString('metadata'),
programIdPdaSeedNode(),
variablePdaSeedNode('mint', publicKeyTypeNode()),
]),
pdaNode('masterEdition', [
constantPdaSeedNodeFromString('metadata'),
programIdPdaSeedNode(),
variablePdaSeedNode('mint', publicKeyTypeNode()),
constantPdaSeedNodeFromString('edition'),
]),
];
const result = visit(node, addPdasVisitor({ myProgram: newPdas }));

// Then we expect the following program to be returned.
t.deepEqual(result, { ...node, pdas: [...node.pdas, ...newPdas] });
});

test('it fails to add a PDA if its name conflicts with an existing PDA on the program', (t) => {
// Given a program with a PDA named "myPda".
const node = programNode({
name: 'myProgram',
publicKey: 'Epo9rxh99jpeeWabRZi4tpgUVxZQeVn9vbbDjUztJtu4',
pdas: [
pdaNode('myPda', [
variablePdaSeedNode('owner', publicKeyTypeNode()),
programIdPdaSeedNode(),
variablePdaSeedNode('mint', publicKeyTypeNode()),
]),
],
});

// When we try to add another PDA with the same name.
const fn = () =>
visit(
node,
addPdasVisitor({
myProgram: [
pdaNode('myPda', [
constantPdaSeedNodeFromString('metadata'),
programIdPdaSeedNode(),
variablePdaSeedNode('mint', publicKeyTypeNode()),
]),
],
})
);

// Then we expect the following error to be thrown.
t.throws(fn, {
message:
'Cannot add PDAs to program "myProgram" because the following PDA names already exist: myPda.',
});
});

0 comments on commit 3bfeb8c

Please sign in to comment.