Skip to content

Commit

Permalink
Adding an extra account to act as a signing authority instead of the …
Browse files Browse the repository at this point in the history
…payer. (#95)
  • Loading branch information
blockiosaurus authored Feb 21, 2024
1 parent 300405f commit eb31266
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 18 deletions.
21 changes: 17 additions & 4 deletions clients/js/src/generated/instructions/printV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ export type PrintV2InstructionAccounts = {
sysvarInstructions?: PublicKey | Pda;
/** System program */
systemProgram?: PublicKey | Pda;
/** The Delegate Record authorizing escrowless edition printing. */
/** The Delegate Record authorizing escrowless edition printing */
holderDelegateRecord?: PublicKey | Pda;
/** The authority printing the edition for a delegated print */
delegate?: Signer;
};

// Data.
Expand Down Expand Up @@ -225,6 +227,11 @@ export function printV2(
isWritable: false as boolean,
value: input.holderDelegateRecord ?? null,
},
delegate: {
index: 19,
isWritable: false as boolean,
value: input.delegate ?? null,
},
} satisfies ResolvedAccountsWithIndices;

// Arguments.
Expand Down Expand Up @@ -259,9 +266,15 @@ export function printV2(
}
if (!resolvedAccounts.editionMintAuthority.value) {
if (resolvedAccounts.holderDelegateRecord.value) {
resolvedAccounts.editionMintAuthority.value = expectSome(
resolvedAccounts.payer.value
);
if (resolvedAccounts.delegate.value) {
resolvedAccounts.editionMintAuthority.value = expectSome(
resolvedAccounts.delegate.value
);
} else {
resolvedAccounts.editionMintAuthority.value = expectSome(
resolvedAccounts.payer.value
);
}
} else {
resolvedAccounts.editionMintAuthority.value = context.identity;
}
Expand Down
88 changes: 88 additions & 0 deletions clients/js/test/printV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,91 @@ test('it can still print as the master edition holder even after delegating', as
},
});
});

test('it can delegate the authority to print a new edition with a separate payer', async (t) => {
// Given an existing master edition asset.
const umi = await createUmi();
const originalOwner = generateSigner(umi);
const payer = generateSigner(umi);
const delegate = generateSigner(umi);
umi.rpc.airdrop(payer.publicKey, sol(1));
const originalMint = await createDigitalAssetWithToken(umi, {
name: 'My NFT',
symbol: 'MNFT',
uri: 'https://example.com/nft.json',
sellerFeeBasisPoints: percentAmount(5.42),
tokenOwner: originalOwner.publicKey,
printSupply: printSupply('Limited', [10]),
tokenStandard: TokenStandard.NonFungible,
});

const holderDelegateRecord = findHolderDelegateRecordPda(umi, {
mint: originalMint.publicKey,
delegateRole: 'print_delegate',
owner: originalOwner.publicKey,
delegate: delegate.publicKey,
});

const digitalAssetWithToken = await fetchDigitalAssetWithAssociatedToken(
umi,
originalMint.publicKey,
originalOwner.publicKey
);

await delegatePrintDelegateV1(umi, {
delegate: delegate.publicKey,
mint: originalMint.publicKey,
tokenStandard: TokenStandard.NonFungible,
token: digitalAssetWithToken.token.publicKey,
authority: originalOwner,
delegateRecord: holderDelegateRecord[0],
}).sendAndConfirm(umi);

// When the delegate prints a new edition of the asset.
const editionMint = generateSigner(umi);
const editionOwner = generateSigner(umi);

await printV2(umi, {
masterTokenAccountOwner: originalOwner.publicKey,
masterEditionMint: originalMint.publicKey,
editionMint,
editionTokenAccountOwner: editionOwner.publicKey,
editionNumber: 1,
tokenStandard: TokenStandard.NonFungible,
masterTokenAccount: digitalAssetWithToken.token.publicKey,
payer,
holderDelegateRecord: holderDelegateRecord[0],
delegate,
}).sendAndConfirm(umi);

// Then the original NFT was updated.
const originalAsset = await fetchDigitalAsset(umi, originalMint.publicKey);
t.like(originalAsset, <DigitalAsset>{
edition: { supply: 1n, maxSupply: some(10n) },
});

// And the printed NFT was created with the same data.
const editionAsset = await fetchDigitalAssetWithAssociatedToken(
umi,
editionMint.publicKey,
editionOwner.publicKey
);
t.like(editionAsset, <DigitalAssetWithToken>{
publicKey: editionMint.publicKey,
metadata: {
name: 'My NFT',
symbol: 'MNFT',
uri: 'https://example.com/nft.json',
sellerFeeBasisPoints: 542,
},
token: {
owner: editionOwner.publicKey,
amount: 1n,
},
edition: {
isOriginal: false,
parent: findMasterEditionPda(umi, { mint: originalMint.publicKey })[0],
edition: 1n,
},
});
});
72 changes: 64 additions & 8 deletions clients/rust/src/generated/instructions/print_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ pub struct PrintV2 {
pub sysvar_instructions: solana_program::pubkey::Pubkey,
/// System program
pub system_program: solana_program::pubkey::Pubkey,
/// The Delegate Record authorizing escrowless edition printing.
/// The Delegate Record authorizing escrowless edition printing
pub holder_delegate_record: Option<solana_program::pubkey::Pubkey>,
/// The authority printing the edition for a delegated print
pub delegate: Option<solana_program::pubkey::Pubkey>,
}

impl PrintV2 {
Expand All @@ -63,7 +65,7 @@ impl PrintV2 {
args: PrintV2InstructionArgs,
remaining_accounts: &[solana_program::instruction::AccountMeta],
) -> solana_program::instruction::Instruction {
let mut accounts = Vec::with_capacity(19 + remaining_accounts.len());
let mut accounts = Vec::with_capacity(20 + remaining_accounts.len());
accounts.push(solana_program::instruction::AccountMeta::new(
self.edition_metadata,
false,
Expand Down Expand Up @@ -153,6 +155,16 @@ impl PrintV2 {
false,
));
}
if let Some(delegate) = self.delegate {
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
delegate, true,
));
} else {
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
crate::MPL_TOKEN_METADATA_ID,
false,
));
}
accounts.extend_from_slice(remaining_accounts);
let mut data = PrintV2InstructionData::new().try_to_vec().unwrap();
let mut args = args.try_to_vec().unwrap();
Expand Down Expand Up @@ -210,6 +222,7 @@ pub struct PrintV2InstructionArgs {
/// 16. `[optional]` sysvar_instructions (default to `Sysvar1nstructions1111111111111111111111111`)
/// 17. `[optional]` system_program (default to `11111111111111111111111111111111`)
/// 18. `[optional]` holder_delegate_record
/// 19. `[signer, optional]` delegate
#[derive(Default)]
pub struct PrintV2Builder {
edition_metadata: Option<solana_program::pubkey::Pubkey>,
Expand All @@ -231,6 +244,7 @@ pub struct PrintV2Builder {
sysvar_instructions: Option<solana_program::pubkey::Pubkey>,
system_program: Option<solana_program::pubkey::Pubkey>,
holder_delegate_record: Option<solana_program::pubkey::Pubkey>,
delegate: Option<solana_program::pubkey::Pubkey>,
edition_number: Option<u64>,
__remaining_accounts: Vec<solana_program::instruction::AccountMeta>,
}
Expand Down Expand Up @@ -397,7 +411,7 @@ impl PrintV2Builder {
self
}
/// `[optional account]`
/// The Delegate Record authorizing escrowless edition printing.
/// The Delegate Record authorizing escrowless edition printing
#[inline(always)]
pub fn holder_delegate_record(
&mut self,
Expand All @@ -406,6 +420,13 @@ impl PrintV2Builder {
self.holder_delegate_record = holder_delegate_record;
self
}
/// `[optional account]`
/// The authority printing the edition for a delegated print
#[inline(always)]
pub fn delegate(&mut self, delegate: Option<solana_program::pubkey::Pubkey>) -> &mut Self {
self.delegate = delegate;
self
}
#[inline(always)]
pub fn edition_number(&mut self, edition_number: u64) -> &mut Self {
self.edition_number = Some(edition_number);
Expand Down Expand Up @@ -471,6 +492,7 @@ impl PrintV2Builder {
.system_program
.unwrap_or(solana_program::pubkey!("11111111111111111111111111111111")),
holder_delegate_record: self.holder_delegate_record,
delegate: self.delegate,
};
let args = PrintV2InstructionArgs {
edition_number: self
Expand Down Expand Up @@ -521,8 +543,10 @@ pub struct PrintV2CpiAccounts<'a, 'b> {
pub sysvar_instructions: &'b solana_program::account_info::AccountInfo<'a>,
/// System program
pub system_program: &'b solana_program::account_info::AccountInfo<'a>,
/// The Delegate Record authorizing escrowless edition printing.
/// The Delegate Record authorizing escrowless edition printing
pub holder_delegate_record: Option<&'b solana_program::account_info::AccountInfo<'a>>,
/// The authority printing the edition for a delegated print
pub delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>,
}

/// `print_v2` CPI instruction.
Expand Down Expand Up @@ -565,8 +589,10 @@ pub struct PrintV2Cpi<'a, 'b> {
pub sysvar_instructions: &'b solana_program::account_info::AccountInfo<'a>,
/// System program
pub system_program: &'b solana_program::account_info::AccountInfo<'a>,
/// The Delegate Record authorizing escrowless edition printing.
/// The Delegate Record authorizing escrowless edition printing
pub holder_delegate_record: Option<&'b solana_program::account_info::AccountInfo<'a>>,
/// The authority printing the edition for a delegated print
pub delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>,
/// The arguments for the instruction.
pub __args: PrintV2InstructionArgs,
}
Expand Down Expand Up @@ -598,6 +624,7 @@ impl<'a, 'b> PrintV2Cpi<'a, 'b> {
sysvar_instructions: accounts.sysvar_instructions,
system_program: accounts.system_program,
holder_delegate_record: accounts.holder_delegate_record,
delegate: accounts.delegate,
__args: args,
}
}
Expand Down Expand Up @@ -634,7 +661,7 @@ impl<'a, 'b> PrintV2Cpi<'a, 'b> {
bool,
)],
) -> solana_program::entrypoint::ProgramResult {
let mut accounts = Vec::with_capacity(19 + remaining_accounts.len());
let mut accounts = Vec::with_capacity(20 + remaining_accounts.len());
accounts.push(solana_program::instruction::AccountMeta::new(
*self.edition_metadata.key,
false,
Expand Down Expand Up @@ -725,6 +752,17 @@ impl<'a, 'b> PrintV2Cpi<'a, 'b> {
false,
));
}
if let Some(delegate) = self.delegate {
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
*delegate.key,
true,
));
} else {
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
crate::MPL_TOKEN_METADATA_ID,
false,
));
}
remaining_accounts.iter().for_each(|remaining_account| {
accounts.push(solana_program::instruction::AccountMeta {
pubkey: *remaining_account.0.key,
Expand All @@ -741,7 +779,7 @@ impl<'a, 'b> PrintV2Cpi<'a, 'b> {
accounts,
data,
};
let mut account_infos = Vec::with_capacity(19 + 1 + remaining_accounts.len());
let mut account_infos = Vec::with_capacity(20 + 1 + remaining_accounts.len());
account_infos.push(self.__program.clone());
account_infos.push(self.edition_metadata.clone());
account_infos.push(self.edition.clone());
Expand All @@ -766,6 +804,9 @@ impl<'a, 'b> PrintV2Cpi<'a, 'b> {
if let Some(holder_delegate_record) = self.holder_delegate_record {
account_infos.push(holder_delegate_record.clone());
}
if let Some(delegate) = self.delegate {
account_infos.push(delegate.clone());
}
remaining_accounts
.iter()
.for_each(|remaining_account| account_infos.push(remaining_account.0.clone()));
Expand Down Expand Up @@ -801,6 +842,7 @@ impl<'a, 'b> PrintV2Cpi<'a, 'b> {
/// 16. `[]` sysvar_instructions
/// 17. `[]` system_program
/// 18. `[optional]` holder_delegate_record
/// 19. `[signer, optional]` delegate
pub struct PrintV2CpiBuilder<'a, 'b> {
instruction: Box<PrintV2CpiBuilderInstruction<'a, 'b>>,
}
Expand Down Expand Up @@ -828,6 +870,7 @@ impl<'a, 'b> PrintV2CpiBuilder<'a, 'b> {
sysvar_instructions: None,
system_program: None,
holder_delegate_record: None,
delegate: None,
edition_number: None,
__remaining_accounts: Vec::new(),
});
Expand Down Expand Up @@ -996,7 +1039,7 @@ impl<'a, 'b> PrintV2CpiBuilder<'a, 'b> {
self
}
/// `[optional account]`
/// The Delegate Record authorizing escrowless edition printing.
/// The Delegate Record authorizing escrowless edition printing
#[inline(always)]
pub fn holder_delegate_record(
&mut self,
Expand All @@ -1005,6 +1048,16 @@ impl<'a, 'b> PrintV2CpiBuilder<'a, 'b> {
self.instruction.holder_delegate_record = holder_delegate_record;
self
}
/// `[optional account]`
/// The authority printing the edition for a delegated print
#[inline(always)]
pub fn delegate(
&mut self,
delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>,
) -> &mut Self {
self.instruction.delegate = delegate;
self
}
#[inline(always)]
pub fn edition_number(&mut self, edition_number: u64) -> &mut Self {
self.instruction.edition_number = Some(edition_number);
Expand Down Expand Up @@ -1143,6 +1196,8 @@ impl<'a, 'b> PrintV2CpiBuilder<'a, 'b> {
.expect("system_program is not set"),

holder_delegate_record: self.instruction.holder_delegate_record,

delegate: self.instruction.delegate,
__args: args,
};
instruction.invoke_signed_with_remaining_accounts(
Expand Down Expand Up @@ -1173,6 +1228,7 @@ struct PrintV2CpiBuilderInstruction<'a, 'b> {
sysvar_instructions: Option<&'b solana_program::account_info::AccountInfo<'a>>,
system_program: Option<&'b solana_program::account_info::AccountInfo<'a>>,
holder_delegate_record: Option<&'b solana_program::account_info::AccountInfo<'a>>,
delegate: Option<&'b solana_program::account_info::AccountInfo<'a>>,
edition_number: Option<u64>,
/// Additional instruction accounts `(AccountInfo, is_writable, is_signer)`.
__remaining_accounts: Vec<(
Expand Down
16 changes: 13 additions & 3 deletions configs/kinobi.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,15 @@ kinobi.update(
k.instructionAccountNode({
name: "holderDelegateRecord",
isOptional: true,
docs: ["The Delegate Record authorizing escrowless edition printing."],
})],
docs: ["The Delegate Record authorizing escrowless edition printing"],
}),
k.instructionAccountNode({
name: "delegate",
isOptional: true,
isSigner: true,
docs: ["The authority printing the edition for a delegated print"],
})
],
});
},
},
Expand Down Expand Up @@ -921,7 +928,10 @@ kinobi.update(
},
editionMintAuthority: {
defaultsTo: k.conditionalDefault("account", "holderDelegateRecord", {
ifTrue: k.accountDefault("payer"),
ifTrue: k.conditionalDefault("account", "delegate", {
ifTrue: k.accountDefault("delegate"),
ifFalse: k.accountDefault("payer"),
}),
ifFalse: k.identityDefault(),
}),
},
Expand Down
1 change: 1 addition & 0 deletions programs/token-metadata/program/src/instruction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@ pub enum MetadataInstruction {
#[account(16, name="sysvar_instructions", desc="Instructions sysvar account")]
#[account(17, name="system_program", desc="System program")]
// #[account(18, optional, name="holder_delegate_record", desc="The Delegate Record authorizing escrowless edition printing")]
// #[account(19, optional, signer, name="delegate", desc="The authority printing the edition for a delegated print")]
#[args(initialize_mint: bool)]
Print(PrintArgs),
}
Expand Down
Loading

0 comments on commit eb31266

Please sign in to comment.