rwa-kyc-hook-api 0.2.0

Token-2022 KYC Transfer Hook for RWA primary issuance on x402
Documentation
use spl_discriminator::SplDiscriminate;

pub const CONFIG: &[u8] = b"config";
pub const ISSUER: &[u8] = b"issuer";
pub const KYC_RECORD: &[u8] = b"kyc-record";
pub const MINT_CONFIG: &[u8] = b"mint-config";

/// Seed for the singleton pending platform-admin transfer proposal PDA.
pub const AUTHORITY_TRANSFER: &[u8] = b"authority-transfer";

/// Production timelock between proposing and accepting a new platform admin (48h).
pub const AUTHORITY_TRANSFER_DELAY_MAINNET_SECONDS: i64 = 172_800;

/// Test-cluster (Devnet/Testnet) timelock — short for fast E2E (60s).
///
/// Selected at runtime from the immutable on-chain `Config.cluster`, NOT a Cargo
/// feature, so the single cluster-agnostic SBF artifact is preserved.
pub const AUTHORITY_TRANSFER_DELAY_TEST_SECONDS: i64 = 60;

/// Binary UUID length stored in account data and PDA seeds.
pub const MAX_ISSUER_ID_LEN: usize = 16;

/// SPL / Token-2022 token account: owner pubkey offset after 32-byte mint.
/// This is a Token-2022 account (not a Steel account), so it has NO discriminator
/// prefix — the offset is relative to the raw account data directly.
pub const TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;

/// Max UTF-8 offering id bytes stored in accounts.
pub const MAX_OFFERING_ID_LEN: usize = 31;

/// Raw-data offset where a Steel account's struct body begins.
///
/// Steel allocates `8 + size_of::<T>()` bytes and writes the 1-byte discriminator
/// at `data[0]` (bytes `1..8` are padding), so the `#[repr(C)]` struct body starts
/// at raw byte offset 8. `Seed::AccountData { data_index, .. }` indexes the RAW
/// account data, so any offset used for extra-account-meta resolution must add this
/// prefix to the `offset_of!` (struct-relative) field offset.
pub const STEEL_ACCOUNT_DISCRIMINATOR_LEN: u8 = 8;

/// `MintConfig.issuer_id` raw-data offset for extra-account-meta resolution.
/// `STEEL_ACCOUNT_DISCRIMINATOR_LEN (8) + offset_of!(MintConfig, issuer_id) (32)`.
pub const MINT_CONFIG_ISSUER_ID_OFFSET: u8 = 40;

/// `MintConfig.offering_id` raw-data offset for extra-account-meta resolution.
/// `STEEL_ACCOUNT_DISCRIMINATOR_LEN (8) + offset_of!(MintConfig, offering_id) (48)`.
pub const MINT_CONFIG_OFFERING_ID_OFFSET: u8 = 56;

pub fn is_transfer_hook_instruction(data: &[u8]) -> bool {
    use spl_transfer_hook_interface::instruction::{
        ExecuteInstruction, InitializeExtraAccountMetaListInstruction,
        UpdateExtraAccountMetaListInstruction,
    };
    data.len() >= 8
        && (data.starts_with(ExecuteInstruction::SPL_DISCRIMINATOR_SLICE)
            || data.starts_with(InitializeExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE)
            || data.starts_with(UpdateExtraAccountMetaListInstruction::SPL_DISCRIMINATOR_SLICE))
}