use solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
use solana_sdk_ids::system_program;
use spl_transfer_hook_interface::{
get_extra_account_metas_address, instruction::TransferHookInstruction,
};
use crate::{
instruction::{
AcceptPlatformAdmin, CancelPlatformAdminProposal, CreateGlobalKycRecord,
CreateOfferingKycRecord, Initialize, RegisterIssuer, RegisterMint, RotateOpsAuthority,
UpdateGlobalKycVerified, UpdateIssuerIdentity, UpdateIssuerStatus,
UpdateOfferingKycVerified, UpdatePlatformAdmin, UpdatePlatformConfig,
},
state::{
authority_transfer_pda, config_pda, global_kyc_record_pda, issuer_config_pda,
mint_config_pda, offering_kyc_record_pda, write_offering_id, Cluster, KycPolicy,
RegistrationPath,
},
};
pub struct HookSdk;
impl HookSdk {
pub fn initialize(
program_id: &Pubkey,
payer: &Pubkey,
platform_admin: &Pubkey,
fee_recipient: &Pubkey,
cluster: Cluster,
registration_mode: u8,
issuer_registration_fee_lamports: u64,
) -> Instruction {
let (config, _) = config_pda(program_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new(config, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: Initialize {
platform_admin: *platform_admin,
fee_recipient: *fee_recipient,
issuer_registration_fee_lamports: issuer_registration_fee_lamports.to_le_bytes(),
cluster: cluster as u8,
registration_mode,
_padding: [0; 6],
}
.to_bytes(),
}
}
pub fn update_platform_config(
program_id: &Pubkey,
platform_admin: &Pubkey,
fee_recipient: &Pubkey,
issuer_registration_fee_lamports: u64,
registration_mode: u8,
paused: u8,
) -> Instruction {
let (config, _) = config_pda(program_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(*platform_admin, true),
AccountMeta::new(config, false),
],
data: UpdatePlatformConfig {
fee_recipient: *fee_recipient,
issuer_registration_fee_lamports: issuer_registration_fee_lamports.to_le_bytes(),
registration_mode,
paused,
_padding: [0; 6],
}
.to_bytes(),
}
}
pub fn register_issuer_admin(
program_id: &Pubkey,
payer: &Pubkey,
platform_admin: &Pubkey,
issuer_id: &[u8; 16],
identity: &Pubkey,
ops_authority: &Pubkey,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(*platform_admin, true),
AccountMeta::new_readonly(config, false),
AccountMeta::new(issuer_config, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: RegisterIssuer {
issuer_id: *issuer_id,
identity: *identity,
ops_authority: *ops_authority,
registration_path: RegistrationPath::Admin as u8,
_padding: [0; 7],
}
.to_bytes(),
}
}
pub fn register_issuer_self_serve(
program_id: &Pubkey,
payer: &Pubkey,
fee_recipient: &Pubkey,
issuer_id: &[u8; 16],
identity: &Pubkey,
ops_authority: &Pubkey,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(config, false),
AccountMeta::new(issuer_config, false),
AccountMeta::new(*fee_recipient, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: RegisterIssuer {
issuer_id: *issuer_id,
identity: *identity,
ops_authority: *ops_authority,
registration_path: RegistrationPath::SelfServe as u8,
_padding: [0; 7],
}
.to_bytes(),
}
}
pub fn create_global_kyc_record(
program_id: &Pubkey,
payer: &Pubkey,
ops_authority: &Pubkey,
issuer_id: &[u8; 16],
user: &Pubkey,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
let (record, _) = global_kyc_record_pda(program_id, issuer_id, user);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(*ops_authority, true),
AccountMeta::new_readonly(issuer_config, false),
AccountMeta::new_readonly(config, false),
AccountMeta::new(record, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: CreateGlobalKycRecord {
user: *user,
issuer_id: *issuer_id,
}
.to_bytes(),
}
}
pub fn create_offering_kyc_record(
program_id: &Pubkey,
payer: &Pubkey,
ops_authority: &Pubkey,
issuer_id: &[u8; 16],
user: &Pubkey,
offering_id: &str,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
let mut offering_bytes = [0u8; 32];
let offering_id_len =
write_offering_id(&mut offering_bytes, offering_id).expect("offering id");
let (record, _) = offering_kyc_record_pda(program_id, issuer_id, &offering_bytes, user);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(*ops_authority, true),
AccountMeta::new_readonly(issuer_config, false),
AccountMeta::new_readonly(config, false),
AccountMeta::new(record, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: CreateOfferingKycRecord {
user: *user,
issuer_id: *issuer_id,
offering_id_len,
_padding: [0; 7],
offering_id: offering_bytes,
}
.to_bytes(),
}
}
pub fn update_global_kyc_verified(
program_id: &Pubkey,
ops_authority: &Pubkey,
issuer_id: &[u8; 16],
user: &Pubkey,
is_kyc_verified: bool,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
let (record, _) = global_kyc_record_pda(program_id, issuer_id, user);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(*ops_authority, true),
AccountMeta::new_readonly(issuer_config, false),
AccountMeta::new_readonly(config, false),
AccountMeta::new(record, false),
],
data: UpdateGlobalKycVerified {
user: *user,
issuer_id: *issuer_id,
is_kyc_verified: if is_kyc_verified { 1 } else { 0 },
_padding: [0; 7],
}
.to_bytes(),
}
}
pub fn update_offering_kyc_verified(
program_id: &Pubkey,
ops_authority: &Pubkey,
issuer_id: &[u8; 16],
user: &Pubkey,
offering_id: &str,
is_kyc_verified: bool,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
let mut offering_bytes = [0u8; 32];
let offering_id_len =
write_offering_id(&mut offering_bytes, offering_id).expect("offering id");
let (record, _) = offering_kyc_record_pda(program_id, issuer_id, &offering_bytes, user);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(*ops_authority, true),
AccountMeta::new_readonly(issuer_config, false),
AccountMeta::new_readonly(config, false),
AccountMeta::new(record, false),
],
data: UpdateOfferingKycVerified {
user: *user,
issuer_id: *issuer_id,
offering_id_len,
is_kyc_verified: if is_kyc_verified { 1 } else { 0 },
_padding: [0; 6],
offering_id: offering_bytes,
}
.to_bytes(),
}
}
pub fn register_mint(
program_id: &Pubkey,
payer: &Pubkey,
ops_authority: &Pubkey,
issuer_id: &[u8; 16],
mint: &Pubkey,
kyc_policy: KycPolicy,
offering_id: &str,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
let (mint_config, _) = mint_config_pda(program_id, mint);
let mut offering_bytes = [0u8; 32];
let offering_id_len =
write_offering_id(&mut offering_bytes, offering_id).expect("offering id");
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(*ops_authority, true),
AccountMeta::new_readonly(issuer_config, false),
AccountMeta::new_readonly(config, false),
AccountMeta::new(mint_config, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: RegisterMint {
mint: *mint,
issuer_id: *issuer_id,
kyc_policy: kyc_policy as u8,
offering_id_len,
_padding: [0; 6],
offering_id: offering_bytes,
}
.to_bytes(),
}
}
pub fn rotate_ops_authority(
program_id: &Pubkey,
authority: &Pubkey,
issuer_id: &[u8; 16],
new_ops_authority: &Pubkey,
) -> Instruction {
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(*authority, true),
AccountMeta::new(issuer_config, false),
],
data: RotateOpsAuthority {
issuer_id: *issuer_id,
new_ops_authority: *new_ops_authority,
}
.to_bytes(),
}
}
pub fn update_issuer_status(
program_id: &Pubkey,
authority: &Pubkey,
issuer_id: &[u8; 16],
status: u8,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(*authority, true),
AccountMeta::new_readonly(config, false),
AccountMeta::new(issuer_config, false),
],
data: UpdateIssuerStatus {
issuer_id: *issuer_id,
status,
_padding: [0; 7],
}
.to_bytes(),
}
}
pub fn update_issuer_identity(
program_id: &Pubkey,
identity: &Pubkey,
issuer_id: &[u8; 16],
new_identity: &Pubkey,
) -> Instruction {
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(*identity, true),
AccountMeta::new(issuer_config, false),
],
data: UpdateIssuerIdentity {
issuer_id: *issuer_id,
new_identity: *new_identity,
}
.to_bytes(),
}
}
pub fn update_platform_admin(
program_id: &Pubkey,
current_admin: &Pubkey,
new_admin: &Pubkey,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (transfer, _) = authority_transfer_pda(program_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*current_admin, true),
AccountMeta::new_readonly(config, false),
AccountMeta::new(transfer, false),
AccountMeta::new_readonly(system_program::ID, false),
],
data: UpdatePlatformAdmin {
new_admin: *new_admin,
}
.to_bytes(),
}
}
pub fn accept_platform_admin(program_id: &Pubkey, new_admin: &Pubkey) -> Instruction {
let (config, _) = config_pda(program_id);
let (transfer, _) = authority_transfer_pda(program_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*new_admin, true),
AccountMeta::new(config, false),
AccountMeta::new(transfer, false),
],
data: AcceptPlatformAdmin {}.to_bytes(),
}
}
pub fn cancel_platform_admin_proposal(
program_id: &Pubkey,
current_admin: &Pubkey,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (transfer, _) = authority_transfer_pda(program_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(*current_admin, true),
AccountMeta::new_readonly(config, false),
AccountMeta::new(transfer, false),
],
data: CancelPlatformAdminProposal {}.to_bytes(),
}
}
pub fn initialize_extra_account_meta_list(
program_id: &Pubkey,
ops_authority: &Pubkey,
issuer_id: &[u8; 16],
mint: &Pubkey,
) -> Instruction {
let (config, _) = config_pda(program_id);
let (issuer_config, _) = issuer_config_pda(program_id, issuer_id);
let extra_account_metas = get_extra_account_metas_address(mint, program_id);
Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(extra_account_metas, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new_readonly(*ops_authority, true),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(issuer_config, false),
AccountMeta::new_readonly(config, false),
],
data: TransferHookInstruction::InitializeExtraAccountMetaList {
extra_account_metas: vec![],
}
.pack(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::RegistrationMode;
#[test]
fn sdk_initialize_uses_runtime_program_id() {
let program_a = Pubkey::new_unique();
let program_b = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let admin = Pubkey::new_unique();
let treasury = Pubkey::new_unique();
let ix_a = HookSdk::initialize(
&program_a,
&payer,
&admin,
&treasury,
Cluster::Devnet,
RegistrationMode::Both as u8,
0,
);
let ix_b = HookSdk::initialize(
&program_b,
&payer,
&admin,
&treasury,
Cluster::Devnet,
RegistrationMode::Both as u8,
0,
);
assert_eq!(ix_a.program_id, program_a);
assert_eq!(ix_b.program_id, program_b);
assert_ne!(ix_a.accounts[1].pubkey, ix_b.accounts[1].pubkey);
}
#[test]
fn issuer_scoped_kyc_ix_includes_issuer_config() {
let program_id = Pubkey::new_unique();
let payer = Pubkey::new_unique();
let ops = Pubkey::new_unique();
let user = Pubkey::new_unique();
let issuer_id = [7u8; 16];
let ix = HookSdk::create_global_kyc_record(&program_id, &payer, &ops, &issuer_id, &user);
assert_eq!(ix.accounts.len(), 6);
assert!(ix.accounts[2].pubkey != ix.accounts[4].pubkey);
}
#[test]
fn init_extra_metas_includes_six_accounts() {
let program_id = Pubkey::new_unique();
let ops = Pubkey::new_unique();
let mint = Pubkey::new_unique();
let issuer_id = [3u8; 16];
let ix = HookSdk::initialize_extra_account_meta_list(&program_id, &ops, &issuer_id, &mint);
assert_eq!(ix.accounts.len(), 6);
}
}