coral-xyz / xnft-wishlist

🌈 Wishlist of ideas for xNFTs
24 stars 5 forks source link

Safe-Send (A safer way to send cash over Solana) #6

Open ljump12 opened 2 years ago

ljump12 commented 2 years ago

An escrow program under the hood, but the idea is when you send tokens, you send it via an XNFT app that essentially puts it in escrow -- and the person you send it to has to "claim" it. Any time before its claimed you can claw it back... the thought being that if you send it to the "wrong" address it won't be lost.

Every time i send money to an address on-chain i get super nervous that I've copied the wrong address or that something is wrong, and my 6 figure USDC will be forever lost

This would protect against that by allowing me to refund it back to my account, until the time that the person I sent it to "claims" it.

Started stubbing out some functions, but never got very far with it -- would love to work with someone to get this going!

use anchor_lang::prelude::*;
use anchor_spl::token::{Mint, Token, TokenAccount};

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        seeds=[owner.key().as_ref()],
        bump,
        payer=owner,
        space = 8 + std::mem::size_of::<Outbox>(),
    )]
    pub outbox: AccountLoader<'info, Outbox>,
    #[account(
        init_if_needed,
        seeds=[owner.key().as_ref()],
        bump,
        payer=owner,
        space = 8 + std::mem::size_of::<Inbox>(),
    )]
    pub inbox: AccountLoader<'info, Inbox>,
    #[account(mut)]
    pub owner: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Send<'info> {
    #[account(mut)]
    pub owner: Signer<'info>,
    pub token_mint: Box<Account<'info, Mint>>,
    pub receiver: AccountInfo<'info>,
    #[account(
        init_if_needed,
        seeds=[receiver.key().as_ref()],
        bump,
        payer=owner,
        space = 8 + std::mem::size_of::<Inbox>(),
    )]
    pub receiver_inbox: AccountLoader<'info, Inbox>,
    pub receiver_collateral_account: Box<Account<'info, TokenAccount>>,
    #[account(mut)]
    pub escrow_pda: Box<Account<'info, TokenAccount>>,
    #[account(mut)]
    pub sender_collateral_account: Box<Account<'info, TokenAccount>>,
    pub token_program: Program<'info, Token>,
    #[account(mut)]
    pub sender_outbox: AccountLoader<'info, Outbox>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Claim<'info> {
    pub owner: Signer<'info>,
    pub receiver: AccountInfo<'info>,
    #[account(mut)]
    pub receiver_inbox: AccountLoader<'info, Inbox>,
    pub receiver_collateral_account: Box<Account<'info, TokenAccount>>,
    #[account(mut)]
    pub escrow_pda: Box<Account<'info, TokenAccount>>,
    pub token_program: Program<'info, Token>,
    #[account(mut)]
    pub sender_outbox: AccountLoader<'info, Outbox>,
    pub system_program: Program<'info, System>,
}

#[account(zero_copy)]
pub struct Outbox {
    pub owner: Pubkey,
    pub pending_transactions: [PendingTransaction; 16],
    pub tx_count: u64,
}

impl Default for Outbox {
    fn default() -> Self {
        Outbox {
            owner: Pubkey::default(),
            pending_transactions: [PendingTransaction::default(); 16],
            tx_count: 0,
        }
    }
}

impl Outbox {
    pub fn is_full(&self) -> bool {
        !self
            .pending_transactions
            .iter()
            .any(|&pending_transaction| !pending_transaction.is_pending)
    }

    pub fn next_available_transaction(&mut self) -> &mut PendingTransaction {
        self.pending_transactions
            .iter_mut()
            .find(|transaction| !transaction.is_pending)
            .unwrap()
    }
}

#[zero_copy]
#[derive(Default)]
pub struct PendingTransaction {
    pub owner: Pubkey,
    pub is_pending: bool,
    pub passcode_hash: Pubkey,
    pub escrow_pda: Pubkey,
    pub sender_token_account: Pubkey,
    pub receiver_token_account: Pubkey,
    pub claim_attempts: u8,
    pub tx_id: u64,
}

#[zero_copy]
#[derive(Default)]
pub struct IncomingTransaction {
    pub sender: Pubkey,
    pub is_pending: bool,
    pub tx_id: u64,
}

#[account(zero_copy)]
pub struct Inbox {
    pub owner: Pubkey,
    pub incoming_transactions: [IncomingTransaction; 16],
}

impl Default for Inbox {
    fn default() -> Self {
        Inbox {
            owner: Pubkey::default(),
            incoming_transactions: [IncomingTransaction::default(); 16],
        }
    }
}

impl Inbox {
    pub fn is_full(&self) -> bool {
        !self
            .incoming_transactions
            .iter()
            .any(|&incoming_transaction| !incoming_transaction.is_pending)
    }

    pub fn next_available_transaction(&mut self) -> &mut IncomingTransaction {
        self.incoming_transactions
            .iter_mut()
            .find(|incoming_transaction| !incoming_transaction.is_pending)
            .unwrap()
    }
}