Closed KultureElectric closed 1 year ago
Hey 👋 thanks for raising this.
I'm struggling to understand how you'd get this error thought because the Account<T>
type should always wrap the AccountData (which gets deserialises) and not the AccountDataArgs (which is used when serialising into a buffer).
Could you share the code that gets generated (the whole account file) as well as your Kinobi config file?
Hey, sorry for the delayed response.
Here's my kinobi.js file:
const path = require("path");
const {
RenderJavaScriptVisitor,
createFromIdls,
} = require("@metaplex-foundation/kinobi");
// Instanciate Kinobi.
const kinobi = createFromIdls([
path.join(__dirname, "idl", "repay_royalties_contract.json"),
]);
// Update the Kinobi tree using visitors...
// Render JavaScript.
const jsDir = path.join(__dirname, "clients", "js", "src", "generated");
kinobi.accept(new RenderJavaScriptVisitor(jsDir));
I only got this config from the UMI docs, so happy if you supply me with an updated way (just waiting for the docs)
And here's the entire account file:
/**
* This code was AUTOGENERATED using the kinobi library.
* Please DO NOT EDIT THIS FILE, instead use visitors
* to add features, then rerun kinobi to update it.
*
* @see https://github.com/metaplex-foundation/kinobi
*/
import {
Account,
Context,
PublicKey,
RpcAccount,
RpcGetAccountOptions,
RpcGetAccountsOptions,
Serializer,
assertAccountExists,
deserializeAccount,
gpaBuilder,
mapSerializer,
} from '@metaplex-foundation/umi';
export type NftState = Account<NftStateAccountData>;
export type NftStateAccountData = {
discriminator: Array<number>;
mint: PublicKey;
repayTimestamp: bigint;
};
export type NftStateAccountDataArgs = {
mint: PublicKey;
repayTimestamp: number | bigint;
};
export function getNftStateAccountDataSerializer(
context: Pick<Context, 'serializer'>
): Serializer<NftStateAccountDataArgs, NftStateAccountData> {
const s = context.serializer;
return mapSerializer<NftStateAccountDataArgs, any, NftStateAccountData>(
s.struct<NftStateAccountData>(
[
['discriminator', s.array(s.u8(), { size: 8 })],
['mint', s.publicKey()],
['repayTimestamp', s.i64()],
],
{ description: 'NftStateAccountData' }
),
(value) => ({
...value,
discriminator: [31, 9, 202, 242, 100, 75, 163, 110],
})
) as Serializer<NftStateAccountDataArgs, NftStateAccountData>;
}
export function deserializeNftState(
context: Pick<Context, 'serializer'>,
rawAccount: RpcAccount
): NftState {
return deserializeAccount(
rawAccount,
getNftStateAccountDataSerializer(context)
);
}
export async function fetchNftState(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKey: PublicKey,
options?: RpcGetAccountOptions
): Promise<NftState> {
const maybeAccount = await context.rpc.getAccount(publicKey, options);
assertAccountExists(maybeAccount, 'NftState');
return deserializeNftState(context, maybeAccount);
}
export async function safeFetchNftState(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKey: PublicKey,
options?: RpcGetAccountOptions
): Promise<NftState | null> {
const maybeAccount = await context.rpc.getAccount(publicKey, options);
return maybeAccount.exists
? deserializeNftState(context, maybeAccount)
: null;
}
export async function fetchAllNftState(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKeys: PublicKey[],
options?: RpcGetAccountsOptions
): Promise<NftState[]> {
const maybeAccounts = await context.rpc.getAccounts(publicKeys, options);
return maybeAccounts.map((maybeAccount) => {
assertAccountExists(maybeAccount, 'NftState');
return deserializeNftState(context, maybeAccount);
});
}
export async function safeFetchAllNftState(
context: Pick<Context, 'rpc' | 'serializer'>,
publicKeys: PublicKey[],
options?: RpcGetAccountsOptions
): Promise<NftState[]> {
const maybeAccounts = await context.rpc.getAccounts(publicKeys, options);
return maybeAccounts
.filter((maybeAccount) => maybeAccount.exists)
.map((maybeAccount) =>
deserializeNftState(context, maybeAccount as RpcAccount)
);
}
export function getNftStateGpaBuilder(
context: Pick<Context, 'rpc' | 'serializer' | 'programs'>
) {
const s = context.serializer;
const programId = context.programs.getPublicKey(
'repayRoyaltiesContract',
'9ZskGH9wtdwM9UXjBq1KDwuaLfrZyPChz41Hx7NWhTFf'
);
return gpaBuilder(context, programId)
.registerFields<{
discriminator: Array<number>;
mint: PublicKey;
repayTimestamp: number | bigint;
}>({
discriminator: [0, s.array(s.u8(), { size: 8 })],
mint: [8, s.publicKey()],
repayTimestamp: [40, s.i64()],
})
.deserializeUsing<NftState>((account) =>
deserializeNftState(context, account)
)
.whereField('discriminator', [31, 9, 202, 242, 100, 75, 163, 110]);
}
export function getNftStateSize(): number {
return 48;
}
This is what I get outputted and then I just manually change the NftState type to use NftStateAccountDataArgs instead of NftStateAccountData
Lastly I'm also interested how I can get the PDA helper function to be generated by Kinobi
Thanks!
Thanks for that, it is super weird because on my side TypeScript understands that it needs the To
type and not the From
type of the serializer but I've updated the deserializeAccount
function so it explicitly takes the To
type parameter so there's no ambiguity. If you upgrade to 0.7.6
, that should hopefully fix it for you.
Regarding generating the PDA helpers, you simply need to tell Kinobi about the seeds of the PDA. Here's an example: https://github.com/metaplex-foundation/solana-project-template/blob/016849ddeb966c8548445b00745166f4bc65065f/configs/kinobi.cjs#L14-L21
Hey, coming back here because I get a bug when trying to assign the seeds to my accounts:
Trying to add seeds to the account like this:
kinobi.update(
new k.UpdateAccountsVisitor({
identifier: {
seeds: [k.stringConstantSeed("identifier"), k.programSeed()],
},
stakeEntry: {
seeds: [
k.stringConstantSeed("stake-entry"),
k.publicKeySeed("pool", "The address of the stake pool"),
k.publicKeySeed("mint", "The address of the NFT Mint"),
k.publicKeySeed("user", "The address of the user"),
k.programSeed(),
],
},
stakePool: {
seeds: [k.stringConstantSeed("stake-pool"), k.programSeed()],
},
})
);
Weird thing is this generates correct helper functions for the stakeEntry but is missing something for both the stakePool and Identifier account.
In generated/account/stakeEntry it looks like this:
export async function fetchStakeEntryFromSeeds(
context: Pick<Context, 'eddsa' | 'programs' | 'rpc' | 'serializer'>,
seeds: Parameters<typeof findStakeEntryPda>[1],
options?: RpcGetAccountOptions
): Promise<StakeEntry> {
return fetchStakeEntry(context, findStakeEntryPda(context, seeds), options);
}
export async function safeFetchStakeEntryFromSeeds(
context: Pick<Context, 'eddsa' | 'programs' | 'rpc' | 'serializer'>,
seeds: Parameters<typeof findStakeEntryPda>[1],
options?: RpcGetAccountOptions
): Promise<StakeEntry | null> {
return safeFetchStakeEntry(
context,
findStakeEntryPda(context, seeds),
options
);
}
Here these 2 functions have a seeds argument that has to be filled by the caller of the function.
In my other 2 account files this seeds argument to the function doesn't exist which causes a typescript error since seeds is needed for the findStakeEntryPda function
export async function fetchStakePoolFromSeeds(
context: Pick<Context, 'eddsa' | 'programs' | 'rpc' | 'serializer'>,
options?: RpcGetAccountOptions
): Promise<StakePool> {
return fetchStakePool(context, findStakePoolPda(context, seeds), options);
}
export async function safeFetchStakePoolFromSeeds(
context: Pick<Context, 'eddsa' | 'programs' | 'rpc' | 'serializer'>,
options?: RpcGetAccountOptions
): Promise<StakePool | null> {
return safeFetchStakePool(context, findStakePoolPda(context, seeds), options);
}
I might just be missing something in my kinobi.js file but couldn't get it to work. Looking forward to a response!
Seems like this seeds parameter in the fetchFromSeed functions seems to be missing when there are no variable seeds being defined in the kinobi.js file.
It works now for my stake_pool and stake_entry accounts since I have variable seeds for both.
For the identifier I only have the constantString see since this PDA is only there to identify how many pools have been created. If I'd add another variable seed in the kinobi.js file the outputted client would not throw any errors.
But since I only have this constantStringSeed it does.
Here the entire Kinobi.js file
const path = require("path");
const k = require("@metaplex-foundation/kinobi");
k.numberT;
// Paths.
const clientDir = path.join(__dirname, "./", "clients");
const idlDir = path.join(__dirname, "./idl/");
// Instanciate Kinobi.
const kinobi = k.createFromIdls([
path.join(idlDir, "cardinal_staking_starter.json"),
]);
// Update accounts.
kinobi.update(
new k.UpdateAccountsVisitor({
identifier: {
seeds: [k.stringConstantSeed("identifier")],
},
stakeEntry: {
seeds: [
k.stringConstantSeed("stake-entry"),
k.publicKeySeed("pool", "The address of the stake pool"),
k.publicKeySeed("mint", "The address of the NFT Mint"),
k.publicKeySeed("user", "The address of the user"),
],
},
stakePool: {
seeds: [
k.stringConstantSeed("stake-pool"),
k.variableSeed(
"identifier",
k.numberTypeNode("u64", "le"),
"The identifier number of the pool"
),
],
},
})
);
// Update instructions.
// kinobi.update(
// new k.UpdateInstructionsVisitor({
// create: {
// bytesCreatedOnChain: k.bytesFromAccount("myAccount"),
// },
// // ...
// })
// );
// Set ShankAccount discriminator.
// const key = (name) => ({ field: "key", value: k.vEnum("Key", name) });
// kinobi.update(
// new k.SetAccountDiscriminatorFromFieldVisitor({
// myAccount: key("MyAccount"),
// myPdaAccount: key("MyPdaAccount"),
// })
// );
// Render JavaScript.
const jsDir = path.join(clientDir, "js", "src", "generated");
// const prettier = require(path.join(clientDir, "js", ".prettierrc.json"));
kinobi.accept(new k.RenderJavaScriptVisitor(jsDir));
Hi there 👋
Thanks for raising this. I think this might be a bug in Kinobi when an account has no variable seeds. Let me try and reproduce and I'll come back to you.
Can you try Kinobi version 0.8.4
and let me know if that fixes your issue?
Bug solved, thanks!
Awesome!
Umi version
No response
Code
Error