diff --git a/programs/cardinal-paid-claim-approver/src/instructions/init.rs b/programs/cardinal-paid-claim-approver/src/instructions/init.rs index ce79d2ab0..6ccc8e1ae 100644 --- a/programs/cardinal-paid-claim-approver/src/instructions/init.rs +++ b/programs/cardinal-paid-claim-approver/src/instructions/init.rs @@ -6,11 +6,7 @@ use { #[derive(Accounts)] pub struct InitCtx<'info> { - #[account(constraint = - token_manager.payment_mint != None - && token_manager.state == TokenManagerState::Initialized as u8 - @ ErrorCode::InvalidTokenManager - )] + #[account(constraint = token_manager.state == TokenManagerState::Initialized as u8 @ ErrorCode::InvalidTokenManager)] token_manager: Box>, #[account( diff --git a/programs/cardinal-paid-claim-approver/src/instructions/pay.rs b/programs/cardinal-paid-claim-approver/src/instructions/pay.rs index b37aee0da..34b335d5c 100644 --- a/programs/cardinal-paid-claim-approver/src/instructions/pay.rs +++ b/programs/cardinal-paid-claim-approver/src/instructions/pay.rs @@ -2,7 +2,7 @@ use { crate::{state::*, errors::ErrorCode}, anchor_lang::{prelude::*}, anchor_spl::{token::{self, Token, TokenAccount, Transfer}}, - cardinal_token_manager::{program::CardinalTokenManager, state::TokenManager}, + cardinal_token_manager::{program::CardinalTokenManager, state::TokenManager, utils::assert_payment_token_account}, }; #[derive(Accounts)] @@ -10,12 +10,7 @@ pub struct PayCtx<'info> { #[account(constraint = claim_approver.key() == token_manager.claim_approver.unwrap() @ ErrorCode::InvalidTokenManager)] token_manager: Box>, - #[account(mut, constraint = - token_manager.payment_mint != None - && payment_token_account.owner == token_manager.key() - && payment_token_account.mint == token_manager.payment_mint.unwrap() - @ ErrorCode::InvalidPaymentTokenAccount, - )] + #[account(mut, constraint = payment_token_account.mint == claim_approver.payment_mint @ ErrorCode::InvalidPaymentTokenAccount)] payment_token_account: Box>, #[account(mut)] @@ -25,7 +20,7 @@ pub struct PayCtx<'info> { payer: Signer<'info>, #[account(mut, constraint = payer_token_account.owner == payer.key() - && payer_token_account.mint == token_manager.payment_mint.unwrap() + && payer_token_account.mint == claim_approver.payment_mint @ ErrorCode::InvalidPayerTokenAccount )] payer_token_account: Box>, @@ -40,6 +35,9 @@ pub struct PayCtx<'info> { } pub fn handler(ctx: Context) -> Result<()> { + let remaining_accs = &mut ctx.remaining_accounts.iter(); + assert_payment_token_account(&ctx.accounts.payment_token_account, &ctx.accounts.token_manager, remaining_accs)?; + let cpi_accounts = Transfer { from: ctx.accounts.payer_token_account.to_account_info(), to: ctx.accounts.payment_token_account.to_account_info(), diff --git a/programs/cardinal-time-invalidator/src/instructions/extend_expiration.rs b/programs/cardinal-time-invalidator/src/instructions/extend_expiration.rs index 200f0855b..a8aba9113 100644 --- a/programs/cardinal-time-invalidator/src/instructions/extend_expiration.rs +++ b/programs/cardinal-time-invalidator/src/instructions/extend_expiration.rs @@ -2,7 +2,7 @@ use { crate::{errors::ErrorCode, state::*}, anchor_lang::prelude::*, anchor_spl::token::{self, Token, TokenAccount, Transfer}, - cardinal_token_manager::state::{TokenManager, TokenManagerState}, + cardinal_token_manager::{state::{TokenManager, TokenManagerState}, utils::assert_payment_token_account}, }; #[derive(Accounts)] @@ -13,18 +13,14 @@ pub struct ExtendExpirationCtx<'info> { #[account(mut, constraint = time_invalidator.token_manager == token_manager.key() @ ErrorCode::InvalidTimeInvalidator)] time_invalidator: Box>, - #[account(mut, constraint = - payment_token_account.owner == token_manager.key() - && payment_token_account.mint == time_invalidator.payment_mint.unwrap() - @ ErrorCode::InvalidPaymentTokenAccount, - )] + #[account(mut, constraint = payment_token_account.mint == time_invalidator.extension_payment_mint.unwrap() @ ErrorCode::InvalidPaymentTokenAccount)] payment_token_account: Box>, #[account(mut)] payer: Signer<'info>, #[account(mut, constraint = payer_token_account.owner == payer.key() - && payer_token_account.mint == time_invalidator.payment_mint.unwrap() + && payer_token_account.mint == time_invalidator.extension_payment_mint.unwrap() @ ErrorCode::InvalidPayerTokenAccount )] payer_token_account: Box>, @@ -33,11 +29,13 @@ pub struct ExtendExpirationCtx<'info> { } pub fn handler(ctx: Context, payment_amount: u64) -> Result<()> { + let remaining_accs = &mut ctx.remaining_accounts.iter(); + assert_payment_token_account(&ctx.accounts.payment_token_account, &ctx.accounts.token_manager, remaining_accs)?; + let time_invalidator = &mut ctx.accounts.time_invalidator; - if time_invalidator.extension_payment_amount == None || time_invalidator.extension_duration_seconds == None - || time_invalidator.payment_mint == None + || time_invalidator.extension_payment_mint == None { return Err(error!(ErrorCode::InvalidTimeInvalidator)); } diff --git a/programs/cardinal-time-invalidator/src/instructions/init.rs b/programs/cardinal-time-invalidator/src/instructions/init.rs index 35266dd13..5ff370982 100644 --- a/programs/cardinal-time-invalidator/src/instructions/init.rs +++ b/programs/cardinal-time-invalidator/src/instructions/init.rs @@ -10,7 +10,7 @@ pub struct InitIx { pub expiration: Option, pub extension_payment_amount: Option, pub extension_duration_seconds: Option, - pub payment_mint: Option, + pub extension_payment_mint: Option, pub max_expiration: Option, pub disable_partial_extension: Option } @@ -40,13 +40,8 @@ pub fn handler(ctx: Context, ix: InitIx) -> Result<()> { || (ix.extension_payment_amount != None && ix.extension_duration_seconds == None) { return Err(error!(ErrorCode::InvalidInstruction)); - } else if ix.extension_payment_amount != None && ix.payment_mint == None { + } else if ix.extension_payment_amount != None && ix.extension_payment_mint == None { return Err(error!(ErrorCode::InvalidInstruction)); - } else if ix.payment_mint != None - && ctx.accounts.token_manager.payment_mint != None - && ctx.accounts.token_manager.payment_mint.unwrap() != ix.payment_mint.unwrap() - { - return Err(error!(ErrorCode::InvalidPaymentMint)); } let time_invalidator = &mut ctx.accounts.time_invalidator; time_invalidator.bump = *ctx.bumps.get("time_invalidator").unwrap(); @@ -55,7 +50,7 @@ pub fn handler(ctx: Context, ix: InitIx) -> Result<()> { time_invalidator.expiration = ix.expiration; time_invalidator.extension_payment_amount = ix.extension_payment_amount; time_invalidator.extension_duration_seconds = ix.extension_duration_seconds; - time_invalidator.payment_mint = ix.payment_mint; + time_invalidator.extension_payment_mint = ix.extension_payment_mint; time_invalidator.max_expiration = ix.max_expiration; time_invalidator.disable_partial_extension = ix.disable_partial_extension; return Ok(()); diff --git a/programs/cardinal-time-invalidator/src/state.rs b/programs/cardinal-time-invalidator/src/state.rs index 071be64f6..71f5a1fed 100644 --- a/programs/cardinal-time-invalidator/src/state.rs +++ b/programs/cardinal-time-invalidator/src/state.rs @@ -10,7 +10,7 @@ pub struct TimeInvalidator { pub duration_seconds: Option, pub extension_payment_amount: Option, pub extension_duration_seconds: Option, - pub payment_mint: Option, + pub extension_payment_mint: Option, pub max_expiration: Option, pub disable_partial_extension: Option, } diff --git a/programs/cardinal-token-manager/src/errors.rs b/programs/cardinal-token-manager/src/errors.rs index 65a4cea83..ba2eaa5bd 100644 --- a/programs/cardinal-token-manager/src/errors.rs +++ b/programs/cardinal-token-manager/src/errors.rs @@ -22,8 +22,6 @@ pub enum ErrorCode { InvalidClaimAuthority, #[msg("Invalid transfer authority")] InvalidTransferAuthority, - #[msg("Invalid payment manager")] - InvalidPaymentManager, #[msg("Invalid issuer")] InvalidIssuer, #[msg("Invalid invalidator")] @@ -45,5 +43,7 @@ pub enum ErrorCode { #[msg("Invalid receipt mint account")] InvalidReceiptMintAccount, #[msg("Invalid receipt mint owner")] - InvalidReceiptMintOwner + InvalidReceiptMintOwner, + #[msg("Invalid receipt mint")] + InvalidReceiptMint } \ No newline at end of file diff --git a/programs/cardinal-token-manager/src/instructions/invalidate.rs b/programs/cardinal-token-manager/src/instructions/invalidate.rs index 700b076f3..b57171a2d 100644 --- a/programs/cardinal-token-manager/src/instructions/invalidate.rs +++ b/programs/cardinal-token-manager/src/instructions/invalidate.rs @@ -2,7 +2,7 @@ use { crate::{state::*, errors::ErrorCode}, anchor_lang::{prelude::*, solana_program::program::invoke_signed, AccountsClose}, anchor_spl::{token::{self, Token, TokenAccount, Mint, Transfer, ThawAccount, CloseAccount}}, - mpl_token_metadata::{instruction::thaw_delegated_account, utils::{assert_derivation,assert_initialized}}, + mpl_token_metadata::{instruction::thaw_delegated_account, utils::{assert_derivation, assert_initialized}}, }; #[derive(Accounts)] @@ -41,43 +41,6 @@ pub fn handler<'key, 'accounts, 'remaining, 'info>(ctx: Context<'key, 'accounts, let token_manager_seeds = &[TOKEN_MANAGER_SEED.as_bytes(), mint.as_ref(), &[token_manager.bump]]; let token_manager_signer = &[&token_manager_seeds[..]]; - if token_manager.payment_mint != None { - let payment_token_account_info = next_account_info(remaining_accs)?; - let payment_token_account = Account::::try_from(payment_token_account_info)?; - - if payment_token_account.mint != token_manager.payment_mint.unwrap() { return Err(error!(ErrorCode::PublicKeyMismatch)); } - if payment_token_account.owner != token_manager.key() { return Err(error!(ErrorCode::PublicKeyMismatch)); } - // assert_keys_eq!(payment_token_account.mint, token_manager.payment_mint.unwrap()); - // assert_keys_eq!(payment_token_account.owner, token_manager.key()); - - let issuer_payment_token_account_info = next_account_info(remaining_accs)?; - let issuer_payment_token_account = Account::::try_from(issuer_payment_token_account_info)?; - - if issuer_payment_token_account.mint != token_manager.payment_mint.unwrap() { return Err(error!(ErrorCode::PublicKeyMismatch)); } - if issuer_payment_token_account.owner != token_manager.issuer { return Err(error!(ErrorCode::PublicKeyMismatch)); } - // assert_keys_eq!(issuer_payment_token_account.mint, token_manager.payment_mint.unwrap()); - // assert_keys_eq!(issuer_payment_token_account.owner, token_manager.issuer); - - let cpi_accounts = Transfer { - from: payment_token_account_info.clone(), - to: issuer_payment_token_account_info.clone(), - authority: token_manager.to_account_info(), - }; - let cpi_program = ctx.accounts.token_program.to_account_info(); - let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(token_manager_signer); - token::transfer(cpi_context, payment_token_account.amount)?; - - // close token account - let cpi_accounts = CloseAccount { - account: payment_token_account_info.to_account_info(), - destination: ctx.accounts.collector.to_account_info(), - authority: token_manager.to_account_info(), - }; - let cpi_program = ctx.accounts.token_program.to_account_info(); - let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(token_manager_signer); - token::close_account(cpi_context)?; - } - if token_manager.state == TokenManagerState::Claimed as u8 { if token_manager.kind == TokenManagerKind::Managed as u8 { let mint_manager_info = next_account_info(remaining_accs)?; diff --git a/programs/cardinal-token-manager/src/instructions/mod.rs b/programs/cardinal-token-manager/src/instructions/mod.rs index 8a4e47ace..789f08a31 100644 --- a/programs/cardinal-token-manager/src/instructions/mod.rs +++ b/programs/cardinal-token-manager/src/instructions/mod.rs @@ -1,7 +1,6 @@ pub mod init; pub mod uninit; pub mod init_mint_counter; -pub mod set_payment_mint; pub mod set_claim_approver; pub mod set_transfer_authority; pub mod add_invalidator; @@ -18,7 +17,6 @@ pub mod close_mint_manager; pub use init::*; pub use uninit::*; pub use init_mint_counter::*; -pub use set_payment_mint::*; pub use set_claim_approver::*; pub use set_transfer_authority::*; pub use add_invalidator::*; diff --git a/programs/cardinal-token-manager/src/instructions/set_payment_mint.rs b/programs/cardinal-token-manager/src/instructions/set_payment_mint.rs deleted file mode 100644 index 75354ee37..000000000 --- a/programs/cardinal-token-manager/src/instructions/set_payment_mint.rs +++ /dev/null @@ -1,21 +0,0 @@ -use { - crate::{state::*, errors::ErrorCode}, - anchor_lang::{prelude::*}, -}; - -#[derive(Accounts)] -pub struct SetPaymentMintCtx<'info> { - #[account(mut, constraint = token_manager.state == TokenManagerState::Initialized as u8 @ ErrorCode::InvalidTokenManagerState)] - token_manager: Box>, - - // issuer - #[account(mut, constraint = issuer.key() == token_manager.issuer @ ErrorCode::InvalidIssuer)] - issuer: Signer<'info> -} - -pub fn handler(ctx: Context, payment_mint: Pubkey) -> Result<()> { - // set token manager data - let token_manager = &mut ctx.accounts.token_manager; - token_manager.payment_mint = Some(payment_mint); - return Ok(()) -} \ No newline at end of file diff --git a/programs/cardinal-token-manager/src/lib.rs b/programs/cardinal-token-manager/src/lib.rs index 840b3413f..23eaff8a9 100644 --- a/programs/cardinal-token-manager/src/lib.rs +++ b/programs/cardinal-token-manager/src/lib.rs @@ -1,5 +1,6 @@ pub mod instructions; pub mod state; +pub mod utils; pub mod errors; use {anchor_lang::prelude::*, instructions::*}; @@ -22,10 +23,6 @@ pub mod cardinal_token_manager { init_mint_counter::handler(ctx, mint) } - pub fn set_payment_mint(ctx: Context, payment_mint: Pubkey) -> Result<()> { - set_payment_mint::handler(ctx, payment_mint) - } - pub fn set_claim_approver(ctx: Context, claim_approver: Pubkey) -> Result<()> { set_claim_approver::handler(ctx, claim_approver) } diff --git a/programs/cardinal-token-manager/src/state.rs b/programs/cardinal-token-manager/src/state.rs index c98d61488..89a94eee0 100644 --- a/programs/cardinal-token-manager/src/state.rs +++ b/programs/cardinal-token-manager/src/state.rs @@ -37,7 +37,7 @@ pub enum InvalidationType { } pub fn token_manager_size(num_invalidators: usize) -> usize { - return (8 + 1 + 1 + 8 + 1 + 32 + 32 + 8 + 1 + 1 + 8 + 1 + 32 + 33 + 33 + 33 + 33 + num_invalidators * 32) + 8 as usize + return (8 + 1 + 1 + 8 + 1 + 32 + 32 + 8 + 1 + 1 + 8 + 1 + 32 + 33 + 33 + 33 + num_invalidators * 32) + 8 as usize } pub const MAX_INVALIDATORS: u8 = 5; @@ -57,7 +57,6 @@ pub struct TokenManager { pub invalidation_type: u8, pub recipient_token_account: Pubkey, pub receipt_mint: Option, - pub payment_mint: Option, pub claim_approver: Option, pub transfer_authority: Option, pub invalidators: Vec, diff --git a/programs/cardinal-token-manager/src/utils.rs b/programs/cardinal-token-manager/src/utils.rs new file mode 100644 index 000000000..fff77b1d9 --- /dev/null +++ b/programs/cardinal-token-manager/src/utils.rs @@ -0,0 +1,21 @@ +use { + crate::{errors::ErrorCode, state::*}, + anchor_lang::prelude::*, + anchor_spl::token::{TokenAccount}, + }; + + pub fn assert_payment_token_account( + token_account: &Account, + token_manager: &Account, + remaining_accounts: &mut std::slice::Iter, + ) -> Result<()> { + if token_manager.receipt_mint == None { + if token_account.owner != token_manager.issuer { return Err(error!(ErrorCode::InvalidIssuer))} + } else { + let receipt_token_account_info = next_account_info(remaining_accounts)?; + let receipt_token_account = Account::::try_from(receipt_token_account_info)?; + if !(receipt_token_account.mint == token_manager.receipt_mint.unwrap() && receipt_token_account.amount > 0) { return Err(error!(ErrorCode::InvalidReceiptMint))} + if receipt_token_account.owner != token_account.owner { return Err(error!(ErrorCode::InvalidReceiptMintOwner))} + } + Ok(()) + } \ No newline at end of file diff --git a/programs/cardinal-use-invalidator/src/instructions/extend_usages.rs b/programs/cardinal-use-invalidator/src/instructions/extend_usages.rs index cef2b4eb5..3918af520 100644 --- a/programs/cardinal-use-invalidator/src/instructions/extend_usages.rs +++ b/programs/cardinal-use-invalidator/src/instructions/extend_usages.rs @@ -2,8 +2,8 @@ use { crate::{errors::ErrorCode, state::*}, anchor_lang::prelude::*, anchor_spl::token::{self, Token, TokenAccount, Transfer}, - cardinal_token_manager::state::{TokenManager, TokenManagerState}, - }; + cardinal_token_manager::{state::{TokenManager, TokenManagerState}, utils::assert_payment_token_account}, +}; #[derive(Accounts)] pub struct ExtendUsagesCtx<'info> { @@ -13,11 +13,7 @@ use { #[account(mut, constraint = use_invalidator.token_manager == token_manager.key() @ ErrorCode::InvalidUseInvalidator)] use_invalidator: Box>, - #[account(mut, constraint = - payment_token_account.owner == token_manager.key() - && payment_token_account.mint == use_invalidator.extension_payment_mint.unwrap() - @ ErrorCode::InvalidPaymentTokenAccount, - )] + #[account(mut, constraint = payment_token_account.mint == use_invalidator.extension_payment_mint.unwrap() @ ErrorCode::InvalidPaymentTokenAccount)] payment_token_account: Box>, #[account(mut)] @@ -33,8 +29,10 @@ use { } pub fn handler(ctx: Context, payment_amount: u64) -> Result<()> { + let remaining_accs = &mut ctx.remaining_accounts.iter(); + assert_payment_token_account(&ctx.accounts.payment_token_account, &ctx.accounts.token_manager, remaining_accs)?; + let use_invalidator = &mut ctx.accounts.use_invalidator; - if use_invalidator.extension_payment_amount == None || use_invalidator.extension_usages == None || use_invalidator.extension_payment_mint == None diff --git a/programs/cardinal-use-invalidator/src/state.rs b/programs/cardinal-use-invalidator/src/state.rs index fc80a36f9..183a9ad77 100644 --- a/programs/cardinal-use-invalidator/src/state.rs +++ b/programs/cardinal-use-invalidator/src/state.rs @@ -9,8 +9,8 @@ pub struct UseInvalidator { pub token_manager: Pubkey, pub use_authority: Option, pub total_usages: Option, - pub max_usages: Option, pub extension_payment_amount: Option, pub extension_payment_mint: Option, pub extension_usages: Option, + pub max_usages: Option, } diff --git a/src/idl/cardinal_time_invalidator.ts b/src/idl/cardinal_time_invalidator.ts index fc2c5670d..4ae320b63 100644 --- a/src/idl/cardinal_time_invalidator.ts +++ b/src/idl/cardinal_time_invalidator.ts @@ -199,7 +199,7 @@ export type CardinalTimeInvalidator = { }; }, { - name: "paymentMint"; + name: "extensionPaymentMint"; type: { option: "publicKey"; }; @@ -251,7 +251,7 @@ export type CardinalTimeInvalidator = { }; }, { - name: "paymentMint"; + name: "extensionPaymentMint"; type: { option: "publicKey"; }; @@ -532,7 +532,7 @@ export const IDL: CardinalTimeInvalidator = { }, }, { - name: "paymentMint", + name: "extensionPaymentMint", type: { option: "publicKey", }, @@ -584,7 +584,7 @@ export const IDL: CardinalTimeInvalidator = { }, }, { - name: "paymentMint", + name: "extensionPaymentMint", type: { option: "publicKey", }, diff --git a/src/idl/cardinal_token_manager.ts b/src/idl/cardinal_token_manager.ts index 6ffc2214e..1683d84c6 100644 --- a/src/idl/cardinal_token_manager.ts +++ b/src/idl/cardinal_token_manager.ts @@ -94,27 +94,6 @@ export type CardinalTokenManager = { } ]; }, - { - name: "setPaymentMint"; - accounts: [ - { - name: "tokenManager"; - isMut: true; - isSigner: false; - }, - { - name: "issuer"; - isMut: true; - isSigner: true; - } - ]; - args: [ - { - name: "paymentMint"; - type: "publicKey"; - } - ]; - }, { name: "setClaimApprover"; accounts: [ @@ -595,12 +574,6 @@ export type CardinalTokenManager = { option: "publicKey"; }; }, - { - name: "paymentMint"; - type: { - option: "publicKey"; - }; - }, { name: "claimApprover"; type: { @@ -806,61 +779,56 @@ export type CardinalTokenManager = { }, { code: 6010; - name: "InvalidPaymentManager"; - msg: "Invalid payment manager"; - }, - { - code: 6011; name: "InvalidIssuer"; msg: "Invalid issuer"; }, { - code: 6012; + code: 6011; name: "InvalidInvalidator"; msg: "Invalid invalidator"; }, { - code: 6013; + code: 6012; name: "InvalidMint"; msg: "Invalid mint"; }, { - code: 6014; + code: 6013; name: "InvalidTokenManagerState"; msg: "Invalid token manager state"; }, { - code: 6015; + code: 6014; name: "OutstandingTokens"; msg: "Outstanding tokens exist"; }, { - code: 6016; + code: 6015; name: "InvalidFreezeAuthority"; msg: "Invalid freeze authority"; }, { - code: 6017; + code: 6016; name: "InvalidClaimReceipt"; msg: "Invalid claim receipt"; }, { - code: 6018; + code: 6017; name: "PublicKeyMismatch"; msg: "Public key mismatch"; }, { - code: 6019; + code: 6018; name: "InvalidMetadataProgramId"; msg: "Invalid metadata program id"; }, { - code: 6020; + code: 6019; name: "InvalidReceiptMintAccount"; msg: "Invalid receipt mint account"; }, { - code: 6021; + code: 6020; name: "InvalidReceiptMintOwner"; msg: "Invalid receipt mint owner"; } @@ -963,27 +931,6 @@ export const IDL: CardinalTokenManager = { }, ], }, - { - name: "setPaymentMint", - accounts: [ - { - name: "tokenManager", - isMut: true, - isSigner: false, - }, - { - name: "issuer", - isMut: true, - isSigner: true, - }, - ], - args: [ - { - name: "paymentMint", - type: "publicKey", - }, - ], - }, { name: "setClaimApprover", accounts: [ @@ -1464,12 +1411,6 @@ export const IDL: CardinalTokenManager = { option: "publicKey", }, }, - { - name: "paymentMint", - type: { - option: "publicKey", - }, - }, { name: "claimApprover", type: { @@ -1675,61 +1616,56 @@ export const IDL: CardinalTokenManager = { }, { code: 6010, - name: "InvalidPaymentManager", - msg: "Invalid payment manager", - }, - { - code: 6011, name: "InvalidIssuer", msg: "Invalid issuer", }, { - code: 6012, + code: 6011, name: "InvalidInvalidator", msg: "Invalid invalidator", }, { - code: 6013, + code: 6012, name: "InvalidMint", msg: "Invalid mint", }, { - code: 6014, + code: 6013, name: "InvalidTokenManagerState", msg: "Invalid token manager state", }, { - code: 6015, + code: 6014, name: "OutstandingTokens", msg: "Outstanding tokens exist", }, { - code: 6016, + code: 6015, name: "InvalidFreezeAuthority", msg: "Invalid freeze authority", }, { - code: 6017, + code: 6016, name: "InvalidClaimReceipt", msg: "Invalid claim receipt", }, { - code: 6018, + code: 6017, name: "PublicKeyMismatch", msg: "Public key mismatch", }, { - code: 6019, + code: 6018, name: "InvalidMetadataProgramId", msg: "Invalid metadata program id", }, { - code: 6020, + code: 6019, name: "InvalidReceiptMintAccount", msg: "Invalid receipt mint account", }, { - code: 6021, + code: 6020, name: "InvalidReceiptMintOwner", msg: "Invalid receipt mint owner", }, diff --git a/src/idl/cardinal_use_invalidator.ts b/src/idl/cardinal_use_invalidator.ts index eb8d01264..7c4ea81a8 100644 --- a/src/idl/cardinal_use_invalidator.ts +++ b/src/idl/cardinal_use_invalidator.ts @@ -210,12 +210,6 @@ export type CardinalUseInvalidator = { option: "u64"; }; }, - { - name: "maxUsages"; - type: { - option: "u64"; - }; - }, { name: "extensionPaymentAmount"; type: { @@ -233,6 +227,12 @@ export type CardinalUseInvalidator = { type: { option: "u64"; }; + }, + { + name: "maxUsages"; + type: { + option: "u64"; + }; } ]; }; @@ -324,6 +324,11 @@ export type CardinalUseInvalidator = { code: 6007; name: "MaxUsagesReached"; msg: "Max usages reached"; + }, + { + code: 6008; + name: "InvalidExtensionAmount"; + msg: "Extension must be a multiple of extension payment"; } ]; }; @@ -540,12 +545,6 @@ export const IDL: CardinalUseInvalidator = { option: "u64", }, }, - { - name: "maxUsages", - type: { - option: "u64", - }, - }, { name: "extensionPaymentAmount", type: { @@ -564,6 +563,12 @@ export const IDL: CardinalUseInvalidator = { option: "u64", }, }, + { + name: "maxUsages", + type: { + option: "u64", + }, + }, ], }, }, @@ -655,5 +660,10 @@ export const IDL: CardinalUseInvalidator = { name: "MaxUsagesReached", msg: "Max usages reached", }, + { + code: 6008, + name: "InvalidExtensionAmount", + msg: "Extension must be a multiple of extension payment", + }, ], }; diff --git a/src/programs/claimApprover/instruction.ts b/src/programs/claimApprover/instruction.ts index e934e0d68..7fbe33177 100644 --- a/src/programs/claimApprover/instruction.ts +++ b/src/programs/claimApprover/instruction.ts @@ -2,6 +2,7 @@ import { BN, Program, Provider } from "@project-serum/anchor"; import type { Wallet } from "@saberhq/solana-contrib"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; import type { + AccountMeta, Connection, PublicKey, TransactionInstruction, @@ -55,8 +56,8 @@ export const pay = async ( connection: Connection, wallet: Wallet, tokenManagerId: PublicKey, - paymentTokenAccountId: PublicKey, - payerTokenAccountId: PublicKey + payerTokenAccountId: PublicKey, + paymentAccounts: [PublicKey, AccountMeta[]] ): Promise => { const provider = new Provider(connection, wallet, {}); @@ -72,6 +73,7 @@ export const pay = async ( ); const [claimApproverId] = await findClaimApproverAddress(tokenManagerId); + const [paymentTokenAccountId, remainingAccounts] = paymentAccounts; return claimApproverProgram.instruction.pay({ accounts: { tokenManager: tokenManagerId, @@ -84,6 +86,7 @@ export const pay = async ( tokenProgram: TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, }, + remainingAccounts, }); }; diff --git a/src/programs/receiptIndex/instruction.ts b/src/programs/receiptIndex/instruction.ts index 3380f38df..5a3374b1b 100644 --- a/src/programs/receiptIndex/instruction.ts +++ b/src/programs/receiptIndex/instruction.ts @@ -23,10 +23,7 @@ import { TokenManagerKind, } from "../tokenManager"; import { findTokenManagerAddress } from "../tokenManager/pda"; -import { - getRemainingAccountsForKind, - getRemainingAccountsForPayment, -} from "../tokenManager/utils"; +import { getRemainingAccountsForKind } from "../tokenManager/utils"; import type { RECEIPT_INDEX_PROGRAM } from "./constants"; import { RECEIPT_INDEX_ADDRESS, RECEIPT_INDEX_IDL } from "./constants"; import { findReceiptMarkerAddress } from "./pda"; @@ -98,9 +95,7 @@ export const invalidate = async ( mintId: PublicKey, tokenManagerId: PublicKey, tokenManagerKind: TokenManagerKind, - recipientTokenAccountId: PublicKey, - issuerPaymentMintTokenAccountId?: PublicKey | null, - tokenManagerPaymentMint?: PublicKey | null + recipientTokenAccountId: PublicKey ): Promise => { const provider = new Provider(connection, wallet, {}); const receiptIndexProgram = new Program( @@ -123,14 +118,10 @@ export const invalidate = async ( true ); - const [paymentAccounts, transferAccounts] = await Promise.all([ - getRemainingAccountsForPayment( - tokenManagerId, - issuerPaymentMintTokenAccountId, - tokenManagerPaymentMint - ), - getRemainingAccountsForKind(mintId, tokenManagerKind), - ]); + const transferAccounts = await getRemainingAccountsForKind( + mintId, + tokenManagerKind + ); return receiptIndexProgram.instruction.invalidate({ accounts: { @@ -144,6 +135,6 @@ export const invalidate = async ( recipientTokenAccount: recipientTokenAccountId, tokenProgram: TOKEN_PROGRAM_ID, }, - remainingAccounts: [...paymentAccounts, ...transferAccounts], + remainingAccounts: [...transferAccounts], }); }; diff --git a/src/programs/timeInvalidator/instruction.ts b/src/programs/timeInvalidator/instruction.ts index 58f88e4a1..61b03cc81 100644 --- a/src/programs/timeInvalidator/instruction.ts +++ b/src/programs/timeInvalidator/instruction.ts @@ -11,10 +11,7 @@ import { SystemProgram } from "@solana/web3.js"; import type { TokenManagerKind } from "../tokenManager"; import { TOKEN_MANAGER_ADDRESS, TokenManagerState } from "../tokenManager"; -import { - getRemainingAccountsForKind, - getRemainingAccountsForPayment, -} from "../tokenManager/utils"; +import { getRemainingAccountsForKind } from "../tokenManager/utils"; import type { TIME_INVALIDATOR_PROGRAM } from "./constants"; import { TIME_INVALIDATOR_ADDRESS, TIME_INVALIDATOR_IDL } from "./constants"; import { findTimeInvalidatorAddress } from "./pda"; @@ -25,7 +22,7 @@ export type TimeInvalidationParams = { extension?: { extensionPaymentAmount: number; extensionDurationSeconds: number; - paymentMint: PublicKey; + extensionPaymentMint: PublicKey; maxExpiration?: number; disablePartialExtension?: boolean; }; @@ -65,8 +62,8 @@ export const init = async ( ?.extensionDurationSeconds ? new BN(timeInvalidation.extension?.extensionDurationSeconds) : null, - paymentMint: timeInvalidation.extension?.paymentMint - ? timeInvalidation.extension?.paymentMint + extensionPaymentMint: timeInvalidation.extension?.extensionPaymentMint + ? timeInvalidation.extension?.extensionPaymentMint : null, maxExpiration: timeInvalidation.extension?.maxExpiration ? new BN(timeInvalidation.extension?.maxExpiration) @@ -115,10 +112,10 @@ export const extendExpiration = ( connection: Connection, wallet: Wallet, tokenManagerId: PublicKey, - paymentTokenAccountId: PublicKey, payerTokenAccountId: PublicKey, timeInvalidatorId: PublicKey, - extensionPaymentAmount: number + extensionPaymentAmount: number, + paymentAccounts: [PublicKey, AccountMeta[]] ): TransactionInstruction => { const provider = new Provider(connection, wallet, {}); @@ -128,6 +125,7 @@ export const extendExpiration = ( provider ); + const [paymentTokenAccountId, remainingAccounts] = paymentAccounts; return timeInvalidatorProgram.instruction.extendExpiration( new BN(extensionPaymentAmount), { @@ -139,6 +137,7 @@ export const extendExpiration = ( payerTokenAccount: payerTokenAccountId, tokenProgram: TOKEN_PROGRAM_ID, }, + remainingAccounts, } ); }; @@ -152,9 +151,7 @@ export const invalidate = async ( tokenManagerState: TokenManagerState, tokenManagerTokenAccountId: PublicKey, recipientTokenAccountId: PublicKey, - returnAccounts: AccountMeta[], - issuerPaymentMintTokenAccountId?: PublicKey | null, - tokenManagerPaymentMint?: PublicKey | null + returnAccounts: AccountMeta[] ): Promise => { const provider = new Provider(connection, wallet, {}); @@ -164,16 +161,10 @@ export const invalidate = async ( provider ); - const [[timeInvalidatorId], paymentAccounts, transferAccounts] = - await Promise.all([ - findTimeInvalidatorAddress(tokenManagerId), - getRemainingAccountsForPayment( - tokenManagerId, - issuerPaymentMintTokenAccountId, - tokenManagerPaymentMint - ), - getRemainingAccountsForKind(mintId, tokenManagerKind), - ]); + const [[timeInvalidatorId], transferAccounts] = await Promise.all([ + findTimeInvalidatorAddress(tokenManagerId), + getRemainingAccountsForKind(mintId, tokenManagerKind), + ]); return timeInvalidatorProgram.instruction.invalidate({ accounts: { @@ -187,7 +178,6 @@ export const invalidate = async ( tokenProgram: TOKEN_PROGRAM_ID, }, remainingAccounts: [ - ...paymentAccounts, ...(tokenManagerState === TokenManagerState.Claimed ? transferAccounts : []), diff --git a/src/programs/tokenManager/instruction.ts b/src/programs/tokenManager/instruction.ts index cc76c4faa..0e9f86c6f 100644 --- a/src/programs/tokenManager/instruction.ts +++ b/src/programs/tokenManager/instruction.ts @@ -18,7 +18,7 @@ import type { import { SystemProgram, SYSVAR_RENT_PUBKEY } from "@solana/web3.js"; import { findAta } from "../.."; -import { getRemainingAccountsForPayment, TokenManagerState } from "."; +import { TokenManagerState } from "."; import type { InvalidationType, TOKEN_MANAGER_PROGRAM, @@ -86,27 +86,6 @@ export const init = async ( ]; }; -export const setPaymentMint = ( - connection: Connection, - wallet: Wallet, - tokenManagerId: PublicKey, - paymentMint: PublicKey -): TransactionInstruction => { - const provider = new Provider(connection, wallet, {}); - const tokenManagerProgram = new Program( - TOKEN_MANAGER_IDL, - TOKEN_MANAGER_ADDRESS, - provider - ); - - return tokenManagerProgram.instruction.setPaymentMint(paymentMint, { - accounts: { - tokenManager: tokenManagerId, - issuer: wallet.publicKey, - }, - }); -}; - export const setClaimApprover = ( connection: Connection, wallet: Wallet, @@ -409,9 +388,7 @@ export const invalidate = async ( tokenManagerState: TokenManagerState, tokenManagerTokenAccountId: PublicKey, recipientTokenAccountId: PublicKey, - returnAccounts: AccountMeta[], - issuerPaymentMintTokenAccountId?: PublicKey | null, - tokenManagerPaymentMint?: PublicKey | null + returnAccounts: AccountMeta[] ): Promise => { const provider = new Provider(connection, wallet, {}); const tokenManagerProgram = new Program( @@ -420,14 +397,10 @@ export const invalidate = async ( provider ); - const [paymentAccounts, transferAccounts] = await Promise.all([ - getRemainingAccountsForPayment( - tokenManagerId, - issuerPaymentMintTokenAccountId, - tokenManagerPaymentMint - ), - getRemainingAccountsForKind(mintId, tokenManagerKind), - ]); + const transferAccounts = await getRemainingAccountsForKind( + mintId, + tokenManagerKind + ); return tokenManagerProgram.instruction.invalidate({ accounts: { @@ -440,7 +413,6 @@ export const invalidate = async ( tokenProgram: TOKEN_PROGRAM_ID, }, remainingAccounts: [ - ...paymentAccounts, ...(tokenManagerState === TokenManagerState.Claimed ? transferAccounts : []), diff --git a/src/programs/tokenManager/utils.ts b/src/programs/tokenManager/utils.ts index bcb6f200e..36a09b140 100644 --- a/src/programs/tokenManager/utils.ts +++ b/src/programs/tokenManager/utils.ts @@ -3,11 +3,7 @@ import { MetadataProgram, } from "@metaplex-foundation/mpl-token-metadata"; import type { Wallet } from "@saberhq/solana-contrib"; -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - Token, - TOKEN_PROGRAM_ID, -} from "@solana/spl-token"; +import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token"; import type { AccountMeta, Connection, @@ -52,34 +48,58 @@ export const getRemainingAccountsForKind = async ( } }; -export const getRemainingAccountsForPayment = async ( - tokenManagerId: PublicKey, - issuerPaymentMintTokenAccountId?: PublicKey | null, - tokenManagerPaymentMint?: PublicKey | null -): Promise => { - if (tokenManagerPaymentMint && issuerPaymentMintTokenAccountId) { - const paymentMintTokenAccountId = await Token.getAssociatedTokenAddress( - ASSOCIATED_TOKEN_PROGRAM_ID, +export const withRemainingAccountsForPayment = async ( + transaction: Transaction, + connection: Connection, + wallet: Wallet, + paymentMint: PublicKey, + issuerId: PublicKey, + receiptMint?: PublicKey | null +): Promise<[PublicKey, AccountMeta[]]> => { + if (receiptMint) { + const receiptMintLargestAccount = await connection.getTokenLargestAccounts( + receiptMint + ); + // get holder of receipt mint + const receiptTokenAccountId = receiptMintLargestAccount.value[0]?.address; + if (!receiptTokenAccountId) throw new Error("No token accounts found"); + const receiptMintToken = new Token( + connection, + receiptMint, TOKEN_PROGRAM_ID, - tokenManagerPaymentMint, - tokenManagerId, - true + Keypair.generate() + ); + const receiptTokenAccount = await receiptMintToken.getAccountInfo( + receiptTokenAccountId ); + // get ATA for this mint of receipt mint holder + const returnTokenAccountId = await withFindOrInitAssociatedTokenAccount( + transaction, + connection, + paymentMint, + receiptTokenAccount.owner, + wallet.publicKey + ); return [ - { - pubkey: paymentMintTokenAccountId, - isSigner: false, - isWritable: true, - }, - { - pubkey: issuerPaymentMintTokenAccountId, - isSigner: false, - isWritable: true, - }, + returnTokenAccountId, + [ + { + pubkey: receiptTokenAccountId, + isSigner: false, + isWritable: true, + }, + ], ]; } else { - return []; + const issuerTokenAccountId = await withFindOrInitAssociatedTokenAccount( + transaction, + connection, + paymentMint, + issuerId, + wallet.publicKey + ); + return [issuerTokenAccountId, []]; } }; diff --git a/src/programs/useInvalidator/instruction.ts b/src/programs/useInvalidator/instruction.ts index 36d46c256..fc22ee778 100644 --- a/src/programs/useInvalidator/instruction.ts +++ b/src/programs/useInvalidator/instruction.ts @@ -11,10 +11,7 @@ import { SystemProgram } from "@solana/web3.js"; import type { TokenManagerKind } from "../tokenManager"; import { TOKEN_MANAGER_ADDRESS, TokenManagerState } from "../tokenManager"; -import { - getRemainingAccountsForKind, - getRemainingAccountsForPayment, -} from "../tokenManager/utils"; +import { getRemainingAccountsForKind } from "../tokenManager/utils"; import type { USE_INVALIDATOR_PROGRAM } from "./constants"; import { USE_INVALIDATOR_ADDRESS, USE_INVALIDATOR_IDL } from "./constants"; import { findUseInvalidatorAddress } from "./pda"; @@ -116,9 +113,7 @@ export const invalidate = async ( tokenManagerState: TokenManagerState, tokenManagerTokenAccountId: PublicKey, recipientTokenAccountId: PublicKey, - returnAccounts: AccountMeta[], - issuerPaymentMintTokenAccountId?: PublicKey | null, - tokenManagerPaymentMint?: PublicKey | null + returnAccounts: AccountMeta[] ): Promise => { const provider = new Provider(connection, wallet, {}); @@ -128,16 +123,10 @@ export const invalidate = async ( provider ); - const [[useInvalidatorId], paymentAccounts, transferAccounts] = - await Promise.all([ - findUseInvalidatorAddress(tokenManagerId), - getRemainingAccountsForPayment( - tokenManagerId, - issuerPaymentMintTokenAccountId, - tokenManagerPaymentMint - ), - getRemainingAccountsForKind(mintId, tokenManagerKind), - ]); + const [[useInvalidatorId], transferAccounts] = await Promise.all([ + findUseInvalidatorAddress(tokenManagerId), + getRemainingAccountsForKind(mintId, tokenManagerKind), + ]); return useInvalidatorProgram.instruction.invalidate({ accounts: { @@ -151,7 +140,6 @@ export const invalidate = async ( recipientTokenAccount: recipientTokenAccountId, }, remainingAccounts: [ - ...paymentAccounts, ...(tokenManagerState === TokenManagerState.Claimed ? transferAccounts : []), @@ -164,10 +152,10 @@ export const extendUsages = ( connection: Connection, wallet: Wallet, tokenManagerId: PublicKey, - paymentTokenAccountId: PublicKey, payerTokenAccountId: PublicKey, useInvalidatorId: PublicKey, - extensionPaymentAmount: number + extensionPaymentAmount: number, + paymentAccounts: [PublicKey, AccountMeta[]] ): TransactionInstruction => { const provider = new Provider(connection, wallet, {}); @@ -177,6 +165,7 @@ export const extendUsages = ( provider ); + const [paymentTokenAccountId, remainingAccounts] = paymentAccounts; return useInvalidatorProgram.instruction.extendUsages( new BN(extensionPaymentAmount), { @@ -188,6 +177,7 @@ export const extendUsages = ( payerTokenAccount: payerTokenAccountId, tokenProgram: TOKEN_PROGRAM_ID, }, + remainingAccounts, } ); }; diff --git a/src/transaction.ts b/src/transaction.ts index 934ad7312..b055b423e 100644 --- a/src/transaction.ts +++ b/src/transaction.ts @@ -19,7 +19,10 @@ import type { ClaimApproverParams } from "./programs/claimApprover/instruction"; import type { TimeInvalidationParams } from "./programs/timeInvalidator/instruction"; import { InvalidationType, TokenManagerKind } from "./programs/tokenManager"; import { tokenManagerAddressFromMint } from "./programs/tokenManager/pda"; -import { withRemainingAccountsForReturn } from "./programs/tokenManager/utils"; +import { + withRemainingAccountsForPayment, + withRemainingAccountsForReturn, +} from "./programs/tokenManager/utils"; import type { UseInvalidationParams } from "./programs/useInvalidator/instruction"; import { tryGetAccount, withFindOrInitAssociatedTokenAccount } from "./utils"; @@ -85,18 +88,6 @@ export const withIssueToken = async ( if (visibility === "private") { throw new Error("Private links do not currently support payment"); } - - // set payment mint - transaction.add( - tokenManager.instruction.setPaymentMint( - connection, - wallet, - tokenManagerId, - claimPayment.paymentMint - ) - ); - - // init claim approver const [paidClaimApproverIx, paidClaimApproverId] = await claimApprover.instruction.init( connection, @@ -274,31 +265,25 @@ export const withClaimToken = async ( timeInvalidatorId?: PublicKey; } ): Promise => { - const tokenManagerData = await tokenManager.accounts.getTokenManager( - connection, - tokenManagerId - ); + const [tokenManagerData, claimApproverData] = await Promise.all([ + tokenManager.accounts.getTokenManager(connection, tokenManagerId), + tryGetAccount(() => + claimApprover.accounts.getClaimApprover(connection, tokenManagerId) + ), + ]); let claimReceiptId; - // pay claim approver if ( + claimApproverData && tokenManagerData.parsed.claimApprover && - tokenManagerData.parsed.paymentMint + tokenManagerData.parsed.claimApprover.toString() === + claimApproverData.pubkey.toString() ) { - const paymentTokenAccountId = await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerId, - wallet.publicKey, - true - ); - const payerTokenAccountId = await withFindOrInitAssociatedTokenAccount( transaction, connection, - tokenManagerData.parsed.paymentMint, + claimApproverData.parsed.paymentMint, wallet.publicKey, wallet.publicKey ); @@ -308,13 +293,22 @@ export const withClaimToken = async ( wallet.publicKey ); + const paymentAccounts = await withRemainingAccountsForPayment( + transaction, + connection, + wallet, + claimApproverData.parsed.paymentMint, + tokenManagerData.parsed.issuer, + tokenManagerData.parsed.receiptMint + ); + transaction.add( await claimApprover.instruction.pay( connection, wallet, tokenManagerId, - paymentTokenAccountId, - payerTokenAccountId + payerTokenAccountId, + paymentAccounts ) ); } else if (tokenManagerData.parsed.claimApprover) { @@ -463,28 +457,6 @@ export const withInvalidate = async ( tokenManagerData?.parsed.receiptMint ); - let issuerPaymentMintTokenAccountId; - if (tokenManagerData.parsed.paymentMint) { - issuerPaymentMintTokenAccountId = - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerData?.parsed.issuer, - wallet.publicKey - ); - - // create account to accept payment - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerId, - wallet.publicKey, - true - ); - } - if ( useInvalidatorData && useInvalidatorData.parsed.totalUsages && @@ -500,9 +472,7 @@ export const withInvalidate = async ( tokenManagerData.parsed.state, tokenManagerTokenAccountId, tokenManagerData?.parsed.recipientTokenAccount, - remainingAccountsForReturn, - issuerPaymentMintTokenAccountId, - tokenManagerData.parsed.paymentMint + remainingAccountsForReturn ) ); transaction.add( @@ -532,9 +502,7 @@ export const withInvalidate = async ( tokenManagerData.parsed.state, tokenManagerTokenAccountId, tokenManagerData?.parsed.recipientTokenAccount, - remainingAccountsForReturn, - issuerPaymentMintTokenAccountId, - tokenManagerData.parsed.paymentMint + remainingAccountsForReturn ) ); transaction.add( @@ -623,18 +591,6 @@ export const withUse = async ( tokenManagerData?.parsed.receiptMint ); - let issuerPaymentMintTokenAccountId; - if (tokenManagerData.parsed.paymentMint) { - issuerPaymentMintTokenAccountId = - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerData?.parsed.issuer, - wallet.publicKey - ); - } - transaction.add( await useInvalidator.instruction.invalidate( connection, @@ -645,9 +601,7 @@ export const withUse = async ( tokenManagerData.parsed.state, tokenManagerTokenAccountId, tokenManagerData?.parsed.recipientTokenAccount, - remainingAccountsForReturn, - issuerPaymentMintTokenAccountId, - tokenManagerData.parsed.paymentMint + remainingAccountsForReturn ) ); transaction.add( @@ -671,26 +625,27 @@ export const withExtendExpiration = async ( ): Promise => { const [timeInvalidatorId] = await timeInvalidator.pda.findTimeInvalidatorAddress(tokenManagerId); - const timeInvalidatorData = await tryGetAccount(() => - timeInvalidator.accounts.getTimeInvalidator(connection, timeInvalidatorId) - ); + const [timeInvalidatorData, tokenManagerData] = await Promise.all([ + timeInvalidator.accounts.getTimeInvalidator(connection, timeInvalidatorId), + tokenManager.accounts.getTokenManager(connection, tokenManagerId), + ]); - if (timeInvalidatorData && timeInvalidatorData.parsed.paymentMint) { - const paymentTokenAccountId = await withFindOrInitAssociatedTokenAccount( + if (timeInvalidatorData && timeInvalidatorData.parsed.extensionPaymentMint) { + const payerTokenAccountId = await withFindOrInitAssociatedTokenAccount( transaction, connection, - timeInvalidatorData.parsed.paymentMint, - tokenManagerId, + timeInvalidatorData.parsed.extensionPaymentMint, wallet.publicKey, - true + wallet.publicKey ); - const payerTokenAccountId = await withFindOrInitAssociatedTokenAccount( + const paymentAccounts = await withRemainingAccountsForPayment( transaction, connection, - timeInvalidatorData.parsed.paymentMint, - wallet.publicKey, - wallet.publicKey + wallet, + timeInvalidatorData.parsed.extensionPaymentMint, + tokenManagerData.parsed.issuer, + tokenManagerData.parsed.receiptMint ); transaction.add( @@ -698,10 +653,10 @@ export const withExtendExpiration = async ( connection, wallet, tokenManagerId, - paymentTokenAccountId, payerTokenAccountId, timeInvalidatorId, - paymentAmount + paymentAmount, + paymentAccounts ) ); } else { @@ -721,26 +676,27 @@ export const withExtendUsages = async ( const [useInvalidatorId] = await useInvalidator.pda.findUseInvalidatorAddress( tokenManagerId ); - const useInvalidatorData = await tryGetAccount(() => - useInvalidator.accounts.getUseInvalidator(connection, useInvalidatorId) - ); + const [useInvalidatorData, tokenManagerData] = await Promise.all([ + useInvalidator.accounts.getUseInvalidator(connection, useInvalidatorId), + tokenManager.accounts.getTokenManager(connection, tokenManagerId), + ]); if (useInvalidatorData && useInvalidatorData.parsed.extensionPaymentMint) { - const paymentTokenAccountId = await withFindOrInitAssociatedTokenAccount( + const payerTokenAccountId = await withFindOrInitAssociatedTokenAccount( transaction, connection, useInvalidatorData.parsed.extensionPaymentMint, - tokenManagerId, wallet.publicKey, - true + wallet.publicKey ); - const payerTokenAccountId = await withFindOrInitAssociatedTokenAccount( + const paymentAccounts = await withRemainingAccountsForPayment( transaction, connection, + wallet, useInvalidatorData.parsed.extensionPaymentMint, - wallet.publicKey, - wallet.publicKey + tokenManagerData.parsed.issuer, + tokenManagerData.parsed.receiptMint ); transaction.add( @@ -748,14 +704,12 @@ export const withExtendUsages = async ( connection, wallet, tokenManagerId, - paymentTokenAccountId, payerTokenAccountId, useInvalidatorId, - paymentAmount + paymentAmount, + paymentAccounts ) ); - } else { - console.log("No payment mint"); } return transaction; diff --git a/tests/createAndExtendPartialRental.spec.ts b/tests/createAndExtendPartialRental.spec.ts new file mode 100644 index 000000000..d7e4201ff --- /dev/null +++ b/tests/createAndExtendPartialRental.spec.ts @@ -0,0 +1,299 @@ +import { BN } from "@project-serum/anchor"; +import { expectTXTable } from "@saberhq/chai-solana"; +import { + SignerWallet, + SolanaProvider, + TransactionEnvelope, +} from "@saberhq/solana-contrib"; +import type { Token } from "@solana/spl-token"; +import type { PublicKey } from "@solana/web3.js"; +import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; +import { expect } from "chai"; + +import { findAta, rentals, tryGetAccount } from "../src"; +import { timeInvalidator, tokenManager } from "../src/programs"; +import { TokenManagerState } from "../src/programs/tokenManager"; +import { createMint } from "./utils"; +import { getProvider } from "./workspace"; + +describe("Create and Extend Rental", () => { + const RECIPIENT_START_PAYMENT_AMOUNT = 1000; + const EXTENSION_PAYMENT_AMOUNT = 2; + const EXTENSION_DURATION = 1000; + const RENTAL_PAYMENT_AMONT = 10; + const recipient = Keypair.generate(); + const tokenCreator = Keypair.generate(); + let recipientPaymentTokenAccountId: PublicKey; + let issuerTokenAccountId: PublicKey; + let paymentMint: Token; + let rentalMint: Token; + let expiration: number; + + before(async () => { + const provider = getProvider(); + const airdropCreator = await provider.connection.requestAirdrop( + tokenCreator.publicKey, + LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(airdropCreator); + + const airdropRecipient = await provider.connection.requestAirdrop( + recipient.publicKey, + LAMPORTS_PER_SOL + ); + await provider.connection.confirmTransaction(airdropRecipient); + + // create payment mint + [recipientPaymentTokenAccountId, paymentMint] = await createMint( + provider.connection, + tokenCreator, + recipient.publicKey, + RECIPIENT_START_PAYMENT_AMOUNT + ); + + // create rental mint + [issuerTokenAccountId, rentalMint] = await createMint( + provider.connection, + tokenCreator, + provider.wallet.publicKey, + 1, + provider.wallet.publicKey + ); + }); + + it("Create rental", async () => { + const provider = getProvider(); + const [transaction, tokenManagerId] = await rentals.createRental( + provider.connection, + provider.wallet, + { + claimPayment: { + paymentAmount: RENTAL_PAYMENT_AMONT, + paymentMint: paymentMint.publicKey, + }, + timeInvalidation: { + durationSeconds: EXTENSION_DURATION, + extension: { + extensionPaymentAmount: EXTENSION_PAYMENT_AMOUNT, // Pay 2 lamport to add 1000 seconds of expiration time + extensionDurationSeconds: EXTENSION_DURATION, + extensionPaymentMint: paymentMint.publicKey, + maxExpiration: Date.now() / 1000 + 5000, + }, + }, + mint: rentalMint.publicKey, + issuerTokenAccountId: issuerTokenAccountId, + amount: new BN(1), + } + ); + const txEnvelope = new TransactionEnvelope( + SolanaProvider.init({ + connection: provider.connection, + wallet: provider.wallet, + opts: provider.opts, + }), + [...transaction.instructions] + ); + await expectTXTable(txEnvelope, "test", { + verbosity: "error", + formatLogs: true, + }).to.be.fulfilled; + + const tokenManagerData = await tokenManager.accounts.getTokenManager( + provider.connection, + tokenManagerId + ); + expect(tokenManagerData.parsed.state).to.eq(TokenManagerState.Issued); + expect(tokenManagerData.parsed.amount.toNumber()).to.eq(1); + expect(tokenManagerData.parsed.mint).to.eqAddress(rentalMint.publicKey); + expect(tokenManagerData.parsed.invalidators).length.greaterThanOrEqual(1); + expect(tokenManagerData.parsed.issuer).to.eqAddress( + provider.wallet.publicKey + ); + + const checkIssuerTokenAccount = await rentalMint.getAccountInfo( + issuerTokenAccountId + ); + expect(checkIssuerTokenAccount.amount.toNumber()).to.eq(0); + + // check receipt-index + const tokenManagers = await tokenManager.accounts.getTokenManagersForIssuer( + provider.connection, + provider.wallet.publicKey + ); + expect(tokenManagers.map((i) => i.pubkey.toString())).to.include( + tokenManagerId.toString() + ); + }); + + it("Claim rental", async () => { + const provider = getProvider(); + + const tokenManagerId = await tokenManager.pda.tokenManagerAddressFromMint( + provider.connection, + rentalMint.publicKey + ); + + const timeInvalidatorId = ( + await timeInvalidator.pda.findTimeInvalidatorAddress(tokenManagerId) + )[0]; + + const transaction = await rentals.claimRental( + provider.connection, + new SignerWallet(recipient), + tokenManagerId, + timeInvalidatorId + ); + + const txEnvelope = new TransactionEnvelope( + SolanaProvider.init({ + connection: provider.connection, + wallet: new SignerWallet(recipient), + opts: provider.opts, + }), + [...transaction.instructions] + ); + + await expectTXTable(txEnvelope, "test", { + verbosity: "error", + formatLogs: true, + }).to.be.fulfilled; + + const tokenManagerData = await tokenManager.accounts.getTokenManager( + provider.connection, + tokenManagerId + ); + expect(tokenManagerData.parsed.state).to.eq(TokenManagerState.Claimed); + expect(tokenManagerData.parsed.amount.toNumber()).to.eq(1); + + const checkIssuerTokenAccount = await rentalMint.getAccountInfo( + issuerTokenAccountId + ); + expect(checkIssuerTokenAccount.amount.toNumber()).to.eq(0); + + const checkRecipientTokenAccount = await rentalMint.getAccountInfo( + await findAta(rentalMint.publicKey, recipient.publicKey) + ); + expect(checkRecipientTokenAccount.amount.toNumber()).to.eq(1); + expect(checkRecipientTokenAccount.isFrozen).to.eq(true); + + const checkRecipientPaymentTokenAccount = await paymentMint.getAccountInfo( + recipientPaymentTokenAccountId + ); + expect(checkRecipientPaymentTokenAccount.amount.toNumber()).to.eq( + RECIPIENT_START_PAYMENT_AMOUNT - RENTAL_PAYMENT_AMONT + ); + }); + + it("Extend Rental", async () => { + const provider = getProvider(); + const tokenManagerId = await tokenManager.pda.tokenManagerAddressFromMint( + provider.connection, + rentalMint.publicKey + ); + + let timeInvalidatorData = await tryGetAccount(async () => + timeInvalidator.accounts.getTimeInvalidator( + provider.connection, + ( + await timeInvalidator.pda.findTimeInvalidatorAddress(tokenManagerId) + )[0] + ) + ); + expiration = timeInvalidatorData?.parsed.expiration?.toNumber() || 0; + expect(expiration).to.not.eq(0); + + const transaction = await rentals.extendRentalExpiration( + provider.connection, + new SignerWallet(recipient), + tokenManagerId, + EXTENSION_PAYMENT_AMOUNT / 2 + ); + + const txEnvelope = new TransactionEnvelope( + SolanaProvider.init({ + connection: provider.connection, + wallet: new SignerWallet(recipient), + opts: provider.opts, + }), + [...transaction.instructions] + ); + + await expectTXTable(txEnvelope, "extend", { + verbosity: "error", + formatLogs: true, + }).to.be.fulfilled; + + timeInvalidatorData = await tryGetAccount(async () => + timeInvalidator.accounts.getTimeInvalidator( + provider.connection, + ( + await timeInvalidator.pda.findTimeInvalidatorAddress(tokenManagerId) + )[0] + ) + ); + + expect(timeInvalidatorData?.parsed.expiration?.toNumber()).to.eq( + expiration + EXTENSION_DURATION / 2 + ); + }); + it("Exceed Max Expiration", async () => { + const provider = getProvider(); + const tokenManagerId = await tokenManager.pda.tokenManagerAddressFromMint( + provider.connection, + rentalMint.publicKey + ); + + const transaction = await rentals.extendRentalExpiration( + provider.connection, + new SignerWallet(recipient), + tokenManagerId, + 4 + ); + + const txEnvelope = new TransactionEnvelope( + SolanaProvider.init({ + connection: provider.connection, + wallet: new SignerWallet(recipient), + opts: provider.opts, + }), + [...transaction.instructions] + ); + + expect(async () => { + await expectTXTable(txEnvelope, "extend", { + verbosity: "error", + formatLogs: true, + }).to.be.rejectedWith(Error); + }); + }); + it("Invalid Partial Expiration", async () => { + const provider = getProvider(); + const tokenManagerId = await tokenManager.pda.tokenManagerAddressFromMint( + provider.connection, + rentalMint.publicKey + ); + + const transaction = await rentals.extendRentalExpiration( + provider.connection, + new SignerWallet(recipient), + tokenManagerId, + 0.5 + ); + + const txEnvelope = new TransactionEnvelope( + SolanaProvider.init({ + connection: provider.connection, + wallet: new SignerWallet(recipient), + opts: provider.opts, + }), + [...transaction.instructions] + ); + + expect(async () => { + await expectTXTable(txEnvelope, "partial extension", { + verbosity: "error", + formatLogs: true, + }).to.be.rejectedWith(Error); + }); + }); +}); diff --git a/tests/createAndExtendRental.spec.ts b/tests/createAndExtendRental.spec.ts index 636da5909..d98ae3aae 100644 --- a/tests/createAndExtendRental.spec.ts +++ b/tests/createAndExtendRental.spec.ts @@ -74,7 +74,7 @@ describe("Create and Extend Rental", () => { extension: { extensionPaymentAmount: 1, // Pay 1 lamport to add 1000 seconds of expiration time extensionDurationSeconds: 1000, - paymentMint: paymentMint.publicKey, + extensionPaymentMint: paymentMint.publicKey, maxExpiration: Date.now() / 1000 + 5000, disablePartialExtension: true, }, diff --git a/tools/invalidateAll.ts b/tools/invalidateAll.ts index 7c5d884a8..391479632 100644 --- a/tools/invalidateAll.ts +++ b/tools/invalidateAll.ts @@ -67,28 +67,6 @@ export const withInvalidate = async ( tokenManagerData?.parsed.receiptMint ); - let issuerPaymentMintTokenAccountId; - if (tokenManagerData.parsed.paymentMint) { - issuerPaymentMintTokenAccountId = - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerData?.parsed.issuer, - wallet.publicKey - ); - - // create account to accept payment - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerData.pubkey, - wallet.publicKey, - true - ); - } - if ( tokenManagerData?.parsed.recipientTokenAccount.toString() === PublicKey.default.toString() @@ -113,9 +91,7 @@ export const withInvalidate = async ( PublicKey.default.toString() ? tokenManagerTokenAccountId : tokenManagerData?.parsed.recipientTokenAccount, - remainingAccountsForReturn, - issuerPaymentMintTokenAccountId, - tokenManagerData.parsed.paymentMint + remainingAccountsForReturn ) ); @@ -159,102 +135,72 @@ export const withInvalidateTokenManager = async ( wallet: Wallet, tokenManagerData: AccountData ): Promise => { - console.log(tokenManagerData); + const tokenManagerTokenAccountId = await withFindOrInitAssociatedTokenAccount( + transaction, + connection, + tokenManagerData.parsed.mint, + tokenManagerData.pubkey, + wallet.publicKey, + true + ); - if (tokenManagerData) { - const tokenManagerTokenAccountId = - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.mint, - tokenManagerData.pubkey, - wallet.publicKey, - true - ); + const remainingAccountsForReturn = await withRemainingAccountsForReturn( + transaction, + connection, + wallet, + tokenManagerData?.parsed.issuer, + tokenManagerData.parsed.mint, + tokenManagerData?.parsed.invalidationType, + tokenManagerData?.parsed.receiptMint + ); - const remainingAccountsForReturn = await withRemainingAccountsForReturn( - transaction, + if ( + tokenManagerData?.parsed.recipientTokenAccount.toString() === + PublicKey.default.toString() + ) { + console.log("TM with incorrect recipient token account"); + } + console.log( + "Invalidate TM: ", + tokenManagerData?.pubkey.toString(), + tokenManagerData?.parsed.state + ); + transaction.add( + await tokenManager.instruction.invalidate( connection, wallet, - tokenManagerData?.parsed.issuer, tokenManagerData.parsed.mint, - tokenManagerData?.parsed.invalidationType, - tokenManagerData?.parsed.receiptMint - ); - - let issuerPaymentMintTokenAccountId; - if (tokenManagerData.parsed.paymentMint) { - issuerPaymentMintTokenAccountId = - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerData?.parsed.issuer, - wallet.publicKey - ); + tokenManagerData.pubkey, + tokenManagerData.parsed.kind, + tokenManagerData.parsed.state, + tokenManagerTokenAccountId, + tokenManagerData?.parsed.recipientTokenAccount.toString() === + PublicKey.default.toString() + ? tokenManagerTokenAccountId + : tokenManagerData?.parsed.recipientTokenAccount, + remainingAccountsForReturn + ) + ); - // create account to accept payment - await withFindOrInitAssociatedTokenAccount( - transaction, - connection, - tokenManagerData.parsed.paymentMint, - tokenManagerData.pubkey, - wallet.publicKey, - true - ); - } + const [claimApproverId] = await findClaimApproverAddress( + tokenManagerData.pubkey + ); - if ( - tokenManagerData?.parsed.recipientTokenAccount.toString() === - PublicKey.default.toString() - ) { - console.log("TM with incorrect recipient token account"); - } - console.log( - "Invalidate TM: ", - tokenManagerData?.pubkey.toString(), - tokenManagerData?.parsed.state - ); + if ( + tokenManagerData.parsed.claimApprover && + tokenManagerData.parsed.claimApprover.toString() === + claimApproverId.toString() + ) { + console.log("Close PCA: ", claimApproverId); transaction.add( - await tokenManager.instruction.invalidate( + claimApprover.instruction.close( connection, wallet, - tokenManagerData.parsed.mint, - tokenManagerData.pubkey, - tokenManagerData.parsed.kind, - tokenManagerData.parsed.state, - tokenManagerTokenAccountId, - tokenManagerData?.parsed.recipientTokenAccount.toString() === - PublicKey.default.toString() - ? tokenManagerTokenAccountId - : tokenManagerData?.parsed.recipientTokenAccount, - remainingAccountsForReturn, - issuerPaymentMintTokenAccountId, - tokenManagerData.parsed.paymentMint + claimApproverId, + tokenManagerData.pubkey ) ); - - const [claimApproverId] = await findClaimApproverAddress( - tokenManagerData.pubkey - ); - - if ( - tokenManagerData.parsed.claimApprover && - tokenManagerData.parsed.claimApprover.toString() === - claimApproverId.toString() - ) { - console.log("Close PCA: ", claimApproverId); - transaction.add( - claimApprover.instruction.close( - connection, - wallet, - claimApproverId, - tokenManagerData.pubkey - ) - ); - } } - return transaction; }; @@ -283,6 +229,15 @@ const main = async (cluster: string) => { } }; +const tokenManagers = async (cluster: string) => { + const connection = connectionFor(cluster); + const tokenManagerDatas = await getTokenManagersByState(connection, null); + console.log( + `---------------Found ${tokenManagerDatas.length} token managers on ${cluster} ---------------` + ); + console.log(tokenManagerDatas); +}; + const claimApprovers = async (cluster: string) => { const connection = connectionFor(cluster); const tokenManagerDatas = await getTokenManagersByState(connection, null); @@ -364,19 +319,21 @@ const executeTx = async (transaction: Transaction, connection: Connection) => { export const invalidateAll = async (mainnet = true) => { if (mainnet) { try { + await tokenManagers("mainnet"); // await main("mainnet"); - await claimApprovers("mainnet"); + // await claimApprovers("mainnet"); } catch (e) { console.log("Failed to invalidate on mainnet: ", e); } } try { - await main("devnet"); - await claimApprovers("devnet"); + // await main("devnet"); + // await claimApprovers("devnet"); } catch (e) { console.log("Failed to invalidate on devnet: ", e); } }; invalidateAll().catch((e) => console.log(e)); +console.log(main, claimApprovers);