#![doc = include_str!("../README.md")]
#![allow(unexpected_cfgs)]
pub mod evm;
use anchor_lang::prelude::*;
declare_id!("SigMcRMjKfnC7RDG5q4yUMZM1s5KJ9oYTPP4NmJRDRw");
#[program]
pub mod chain_signatures {
use super::*;
pub fn initialize(
ctx: Context<Initialize>,
signature_deposit: u64,
chain_id: String,
) -> Result<()> {
let program_state = &mut ctx.accounts.program_state;
program_state.admin = ctx.accounts.admin.key();
program_state.signature_deposit = signature_deposit;
program_state.chain_id = chain_id;
Ok(())
}
pub fn update_deposit(ctx: Context<AdminOnly>, new_deposit: u64) -> Result<()> {
let program_state = &mut ctx.accounts.program_state;
let old_deposit = program_state.signature_deposit;
program_state.signature_deposit = new_deposit;
emit!(DepositUpdatedEvent {
old_deposit,
new_deposit,
});
Ok(())
}
pub fn withdraw_funds(ctx: Context<WithdrawFunds>, amount: u64) -> Result<()> {
let program_state = &ctx.accounts.program_state;
let recipient = &ctx.accounts.recipient;
let program_state_info = program_state.to_account_info();
require!(
program_state_info.lamports() >= amount,
ChainSignaturesError::InsufficientFunds
);
require!(
recipient.key() != Pubkey::default(),
ChainSignaturesError::InvalidRecipient
);
**program_state_info.try_borrow_mut_lamports()? -= amount;
**recipient.try_borrow_mut_lamports()? += amount;
emit!(FundsWithdrawnEvent {
amount,
recipient: recipient.key(),
});
Ok(())
}
pub fn sign(
ctx: Context<Sign>,
payload: [u8; 32],
key_version: u32,
path: String,
algo: String,
dest: String,
params: String,
) -> Result<()> {
let program_state = &ctx.accounts.program_state;
let requester = &ctx.accounts.requester;
let system_program = &ctx.accounts.system_program;
let payer = match &ctx.accounts.fee_payer {
Some(fee_payer) => fee_payer.to_account_info(),
None => requester.to_account_info(),
};
require!(
payer.lamports() >= program_state.signature_deposit,
ChainSignaturesError::InsufficientDeposit
);
let transfer_instruction = anchor_lang::system_program::Transfer {
from: payer,
to: program_state.to_account_info(),
};
anchor_lang::system_program::transfer(
CpiContext::new(system_program.to_account_info(), transfer_instruction),
program_state.signature_deposit,
)?;
emit_cpi!(SignatureRequestedEvent {
sender: *requester.key,
payload,
key_version,
deposit: program_state.signature_deposit,
chain_id: program_state.chain_id.clone(),
path,
algo,
dest,
params,
fee_payer: match &ctx.accounts.fee_payer {
Some(payer) => Some(*payer.key),
None => None,
},
});
Ok(())
}
pub fn sign_bidirectional(
ctx: Context<SignBidirectional>,
serialized_transaction: Vec<u8>,
caip2_id: String,
key_version: u32,
path: String,
algo: String,
dest: String,
params: String,
program_id: Pubkey,
output_deserialization_schema: Vec<u8>,
respond_serialization_schema: Vec<u8>,
) -> Result<()> {
let program_state = &ctx.accounts.program_state;
let requester = &ctx.accounts.requester;
let system_program = &ctx.accounts.system_program;
let payer = match &ctx.accounts.fee_payer {
Some(fee_payer) => fee_payer.to_account_info(),
None => requester.to_account_info(),
};
require!(
payer.lamports() >= program_state.signature_deposit,
ChainSignaturesError::InsufficientDeposit
);
require!(
!serialized_transaction.is_empty(),
ChainSignaturesError::InvalidTransaction
);
let transfer_instruction = anchor_lang::system_program::Transfer {
from: payer,
to: program_state.to_account_info(),
};
anchor_lang::system_program::transfer(
CpiContext::new(system_program.to_account_info(), transfer_instruction),
program_state.signature_deposit,
)?;
emit_cpi!(SignBidirectionalEvent {
sender: *requester.key,
serialized_transaction,
caip2_id,
key_version,
deposit: program_state.signature_deposit,
path,
algo,
dest,
params,
program_id,
output_deserialization_schema,
respond_serialization_schema
});
Ok(())
}
pub fn respond(
ctx: Context<Respond>,
request_ids: Vec<[u8; 32]>,
signatures: Vec<Signature>,
) -> Result<()> {
require!(
request_ids.len() == signatures.len(),
ChainSignaturesError::InvalidInputLength
);
for i in 0..request_ids.len() {
emit_cpi!(SignatureRespondedEvent {
request_id: request_ids[i],
responder: *ctx.accounts.responder.key,
signature: signatures[i].clone(),
});
}
Ok(())
}
pub fn respond_error(ctx: Context<RespondError>, errors: Vec<ErrorResponse>) -> Result<()> {
for error in errors {
emit!(SignatureErrorEvent {
request_id: error.request_id,
responder: *ctx.accounts.responder.key,
error: error.error_message,
});
}
Ok(())
}
pub fn get_signature_deposit(ctx: Context<GetSignatureDeposit>) -> Result<u64> {
let program_state = &ctx.accounts.program_state;
Ok(program_state.signature_deposit)
}
pub fn respond_bidirectional(
ctx: Context<ReadRespond>,
request_id: [u8; 32],
serialized_output: Vec<u8>,
signature: Signature,
) -> Result<()> {
emit!(RespondBidirectionalEvent {
request_id,
responder: *ctx.accounts.responder.key,
serialized_output,
signature,
});
Ok(())
}
}
#[account]
pub struct ProgramState {
pub admin: Pubkey,
pub signature_deposit: u64,
pub chain_id: String,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct AffinePoint {
pub x: [u8; 32],
pub y: [u8; 32],
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct Signature {
pub big_r: AffinePoint,
pub s: [u8; 32],
pub recovery_id: u8,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
pub struct ErrorResponse {
pub request_id: [u8; 32],
pub error_message: String,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = admin,
space = 8 + 32 + 8 + 4 + 128, // discriminator + admin + deposit + string length + max chain_id length
seeds = [b"program-state"],
bump
)]
pub program_state: Account<'info, ProgramState>,
#[account(mut)]
pub admin: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct AdminOnly<'info> {
#[account(
mut,
seeds = [b"program-state"],
bump,
has_one = admin @ ChainSignaturesError::Unauthorized
)]
pub program_state: Account<'info, ProgramState>,
#[account(mut)]
pub admin: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct WithdrawFunds<'info> {
#[account(
mut,
seeds = [b"program-state"],
bump,
has_one = admin @ ChainSignaturesError::Unauthorized
)]
pub program_state: Account<'info, ProgramState>,
#[account(mut)]
pub admin: Signer<'info>,
#[account(mut)]
pub recipient: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}
#[event_cpi]
#[derive(Accounts)]
pub struct Sign<'info> {
#[account(mut, seeds = [b"program-state"], bump)]
pub program_state: Account<'info, ProgramState>,
#[account(mut)]
pub requester: Signer<'info>,
#[account(mut)]
pub fee_payer: Option<Signer<'info>>,
pub system_program: Program<'info, System>,
}
#[event_cpi]
#[derive(Accounts)]
pub struct SignBidirectional<'info> {
#[account(mut, seeds = [b"program-state"], bump)]
pub program_state: Account<'info, ProgramState>,
#[account(mut)]
pub requester: Signer<'info>,
#[account(mut)]
pub fee_payer: Option<Signer<'info>>,
pub system_program: Program<'info, System>,
pub instructions: Option<AccountInfo<'info>>,
}
#[event_cpi]
#[derive(Accounts)]
pub struct Respond<'info> {
pub responder: Signer<'info>,
}
#[derive(Accounts)]
pub struct RespondError<'info> {
pub responder: Signer<'info>,
}
#[derive(Accounts)]
pub struct GetSignatureDeposit<'info> {
#[account(seeds = [b"program-state"], bump)]
pub program_state: Account<'info, ProgramState>,
}
#[derive(Accounts)]
pub struct ReadRespond<'info> {
pub responder: Signer<'info>,
}
#[event]
pub struct SignatureRequestedEvent {
pub sender: Pubkey,
pub payload: [u8; 32],
pub key_version: u32,
pub deposit: u64,
pub chain_id: String,
pub path: String,
pub algo: String,
pub dest: String,
pub params: String,
pub fee_payer: Option<Pubkey>,
}
#[event]
pub struct SignBidirectionalEvent {
pub sender: Pubkey,
pub serialized_transaction: Vec<u8>,
pub caip2_id: String,
pub key_version: u32,
pub deposit: u64,
pub path: String,
pub algo: String,
pub dest: String,
pub params: String,
pub program_id: Pubkey,
pub output_deserialization_schema: Vec<u8>,
pub respond_serialization_schema: Vec<u8>,
}
#[event]
pub struct SignatureRespondedEvent {
pub request_id: [u8; 32],
pub responder: Pubkey,
pub signature: Signature,
}
#[event]
pub struct SignatureErrorEvent {
pub request_id: [u8; 32],
pub responder: Pubkey,
pub error: String,
}
#[event]
pub struct RespondBidirectionalEvent {
pub request_id: [u8; 32],
pub responder: Pubkey,
pub serialized_output: Vec<u8>,
pub signature: Signature,
}
#[event]
pub struct DepositUpdatedEvent {
pub old_deposit: u64,
pub new_deposit: u64,
}
#[event]
pub struct FundsWithdrawnEvent {
pub amount: u64,
pub recipient: Pubkey,
}
#[error_code]
pub enum ChainSignaturesError {
#[msg("Insufficient deposit amount")]
InsufficientDeposit,
#[msg("Arrays must have the same length")]
InvalidInputLength,
#[msg("Unauthorized access")]
Unauthorized,
#[msg("Insufficient funds for withdrawal")]
InsufficientFunds,
#[msg("Invalid recipient address")]
InvalidRecipient,
#[msg("Invalid transaction data")]
InvalidTransaction,
#[msg("Missing instruction sysvar")]
MissingInstructionSysvar,
}