token_signer/
lib.rs

1//! Sign transactions by owning a token
2#![deny(rustdoc::all)]
3#![allow(rustdoc::missing_doc_code_examples)]
4
5use anchor_lang::prelude::*;
6use anchor_lang::solana_program::instruction::Instruction;
7use anchor_lang::solana_program::program::invoke_signed;
8use anchor_lang::Key;
9use anchor_spl::token::TokenAccount;
10use vipers::assert_keys_eq;
11use vipers::validate::Validate;
12
13mod account_validators;
14
15declare_id!("NFTUJzSHuUCsMMqMRJpB7PmbsaU7Wm51acdPk2FXMLn");
16
17/// Token signer program
18#[program]
19pub mod token_signer {
20    use super::*;
21
22    #[access_control(ctx.accounts.validate())]
23    pub fn invoke_signed_instruction(
24        ctx: Context<InvokeSignedInstruction>,
25        data: Vec<u8>,
26    ) -> Result<()> {
27        let mint = ctx.accounts.nft_account.mint.to_bytes();
28        let seeds: &[&[u8]] = &[b"GokiTokenSigner", &mint];
29        let (nft_addr, bump) = Pubkey::find_program_address(seeds, ctx.program_id);
30        let full_seeds = &[b"GokiTokenSigner" as &[u8], &mint, &[bump]];
31
32        assert_keys_eq!(nft_addr, ctx.accounts.nft_pda, "nft_pda");
33
34        let accounts: Vec<AccountMeta> = ctx
35            .remaining_accounts
36            .iter()
37            .map(|acc| AccountMeta {
38                pubkey: acc.key(),
39                is_signer: if acc.key() == ctx.accounts.nft_pda.key() {
40                    true
41                } else {
42                    acc.is_signer
43                },
44                is_writable: acc.is_writable,
45            })
46            .collect();
47
48        // invoke the tx, signed by the PDA
49        let ix = Instruction {
50            program_id: ctx.remaining_accounts[0].key(),
51            accounts,
52            data,
53        };
54        invoke_signed(&ix, ctx.remaining_accounts, &[full_seeds])?;
55
56        Ok(())
57    }
58}
59
60#[derive(Accounts)]
61#[instruction(bump: u8)]
62pub struct InvokeSignedInstruction<'info> {
63    /// Authority attempting to sign.
64    pub owner_authority: Signer<'info>,
65
66    /// Account containing at least one token.
67    /// This must belong to `owner_authority`.
68    pub nft_account: Account<'info, TokenAccount>,
69
70    /// PDA associated with the NFT.
71    #[account(
72        seeds = [
73            b"GokiTokenSigner".as_ref(),
74            nft_account.mint.as_ref()
75        ],
76        bump = bump,
77    )]
78    pub nft_pda: SystemAccount<'info>,
79}
80
81#[error_code]
82pub enum ErrorCode {
83    #[msg("Unauthorized.")]
84    Unauthorized,
85}