This commit is contained in:
2026-07-23 16:37:12 +02:00
parent 99c345f2f2
commit 0da75c1311
2159 changed files with 230833 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
# file: kb_executor_spl_token/Cargo.toml
# version: 3
[package]
name = "kb_executor_spl_token"
version.workspace = true
edition.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
kb_core = { path = "../kb_core" }
kb_execution_api = { path = "../kb_execution_api" }
kb_model = { path = "../kb_model" }
kb_program_ids = { path = "../kb_program_ids" }
serde.workspace = true
serde_json.workspace = true
solana-instruction.workspace = true
solana-pubkey.workspace = true
spl-token-interface.workspace = true
tracing.workspace = true
ts-rs.workspace = true
[dev-dependencies]
kb_execution_safety = { path = "../kb_execution_safety" }
[lints]
workspace = true

View File

@@ -0,0 +1,98 @@
<!-- file: kb_executor_spl_token/README.md -->
<!-- version: 4 -->
# kb_executor_spl_token
Ce crate construit des plans typés pour le programme SPL Token classique exact :
```text
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
```
Il ne dépend ni du RPC, ni du wallet, ni de Tauri, ni du décodeur. La lecture détat, la simulation,
la signature, lenvoi et la validation post-exécution appartiennent à lorchestration commune.
## Politique de surface
Le décodeur conserve les 28 tags publiés, y compris les formes historiques. Lexécuteur expose 24
opérations encore courantes ou récentes et refuse quatre initialisations obsolètes dépendantes de
Rent : `InitializeMint`, `InitializeAccount`, `InitializeMultisig` et `InitializeAccount2`. Leurs
équivalents actuels utilisent respectivement `InitializeMint2`, `InitializeAccount3` et
`InitializeMultisig2`.
Les opérations non checked `Transfer`, `Approve`, `MintTo` et `Burn` restent constructibles : leur
wire est ancien, mais les builders officiels 3.0.0 les publient toujours et elles ne sont pas
obsolètes. Les variantes checked doivent être préférées lorsquun mint et ses decimals sont connus.
## Contrat typé
`SplTokenExecutionIntent` contient :
- un identifiant dintent et un fee payer ;
- une `ExecutionPolicy` commune, simulation obligatoire et dry-run par défaut ;
- une instruction typée ou un `Batch` de sous-instructions non batch ;
- les comptes métier explicites ;
- une autorité simple ou un multisig avec signataires ordonnés ;
- les montants bruts sous forme de chaînes décimales JSON, converties en `u64` seulement dans le
builder ;
- les decimals séparés lorsquils appartiennent réellement au wire.
Les doublons des metas multisig sont conservés dans lordre officiel. La liste des signataires
transactionnels est dédupliquée séparément avec le fee payer.
## Builders
Chaque opération appelle directement le builder `spl-token-interface 3.0.0`. Cela couvre les
initialisations actuelles, transferts, délégations, changements dautorité, mint/burn, close,
freeze/thaw, variantes checked, `SyncNative`, les trois instructions de return data,
`InitializeImmutableOwner`, `WithdrawExcessLamports`, `UnwrapLamports` et `Batch`.
`InitializeImmutableOwner` est explicitement un no-op de compatibilité sur le programme classique ;
il nest jamais présenté comme une extension Token-2022.
`Batch` est borné à 64 enfants, 512 occurrences de comptes et 255 octets de data par enfant. Son
type enfant exclut structurellement un autre `Batch`. Les slices de comptes et lordre produits par
le builder officiel sont conservés.
## Constructibilité et clusters
La constructibilité de bibliothèque ne dépend pas du cluster. `UnwrapLamports` et `Batch` sont
publiés avec un builder exact dans linterface 3.0.0 et sont donc constructibles. Deux simulations
réussies le 16 juillet 2026 prouvent leur présence sur le programme classique Devnet. Cette preuve
n'est pas extrapolée à Localnet, Testnet ou Mainnet. Mainnet demeure gouverné par
`kb_execution_safety` et la confirmation opérateur, pas par une interdiction interne à ce crate.
Les opérations dinitialisation supposent que les comptes ont déjà été créés avec la bonne taille,
le bon owner et le bon financement. La création System et le rent ne sont pas masqués dans une
instruction Token ; le préflight stateful de la tranche suivante les vérifiera sans utiliser ATA.
## Coûts et validation
Les montants token ne sont pas des frais réseau et ne sont pas reportés comme lamports dépensés.
Un montant explicite `UnwrapLamports` est déclaré dans `requested_spend_lamports`; un unwrap total
et `WithdrawExcessLamports` restent simulation-only tant quune lecture détat na pas borné les
lamports déplaçables. Le plafond de frais reste séparé dans `ExecutionCostLimit`.
Toute construction impose une simulation, un plafond de frais positif, une insertion canonique,
lextraction core et le replay décodé. Les mutations exigent aussi la validation de matérialisation ;
les conversions, requêtes de return data et le no-op de compatibilité ninventent aucune projection.
## Limites actuelles
- aucun préflight Mint/Account/Multisig dans ce crate : ces lectures sont implémentées dans
`kb_pipeline` afin de conserver l'exécuteur pur ;
- aucun envoi ni simulation RPC dans ce crate ; l'orchestrateur commun a validé ces étapes sur
Devnet pour `TransferChecked` et le lifecycle contrôlé ;
- aucun compte ATA créé implicitement ;
- aucun support Token-2022 ;
- aucun déploiement de `UnwrapLamports` ou `Batch` revendiqué hors Devnet sans simulation cluster
dédiée.
Le parcours Devnet réel a prouvé simulation, soumission, confirmation, hydratation canonique,
extraction, replay, matérialisation et idempotence pour les opérations du lifecycle documentées dans
`docs/SPL_TOKEN_MATRIX.json`. La démo Tauri refuse volontairement un envoi si le wallet du profil ne
résout pas aussi l'autorité Token requise ; elle ne prend jamais en charge une clé externe implicite.
Le probe Devnet simulation-only du 16 juillet 2026 a validé le wire déployé de `Batch` avec un
enfant `TransferChecked` de montant nul en 270 compute units, puis `UnwrapLamports` d'un lamport en
140 compute units. Aucune transaction n'a été signée ou envoyée par ce probe.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
// file: kb_executor_spl_token/src/constants.rs
// version: 3
//! Local constants for the `kb_executor_spl_token` crate. Program identifiers live in `kb_program_ids`.
/// Maximum number of children accepted by one Batch plan.
pub(crate) const MAX_BATCH_INSTRUCTIONS: usize = 64;
/// Maximum aggregate account-meta occurrences accepted by one Batch plan.
pub(crate) const MAX_BATCH_ACCOUNTS: usize = 512;
/// Maximum number of ordered multisig signer occurrences.
pub(crate) const MAX_MULTISIG_SIGNERS: usize = 11;
/// Maximum UI amount string length accepted before transaction assembly.
pub(crate) const MAX_UI_AMOUNT_BYTES: usize = 255;
/// Canonical tracing target for this crate.
pub(crate) const TRACING_TARGET: &str = "kb_executor_spl_token";

View File

@@ -0,0 +1,241 @@
// file: kb_executor_spl_token/src/executor.rs
// version: 4
//! Exact classic SPL Token capability dispatch and typed plan construction.
/// Classic SPL Token executor implementation.
#[derive(Clone, Debug, Default)]
pub struct SplTokenExecutor;
impl crate::SplTokenExecutor {
fn exact_capability(
&self,
program_id: &kb_model::ProgramId,
operation_code: &str,
) -> kb_execution_api::ExecutionCapability {
if program_id.0 != kb_program_ids::SPL_TOKEN_PROGRAM_ID {
return kb_execution_api::ExecutionCapability::unsupported(
"execution_spl_token_program_not_owned",
format!("program {} is not owned by kb_executor_spl_token", program_id.0),
);
}
if crate::SUPPORTED_OPERATION_CODES.contains(&operation_code) {
return kb_execution_api::ExecutionCapability::supported(operation_code);
}
if matches!(
operation_code,
"spl_token.initialize_mint_with_rent"
| "spl_token.initialize_account_with_rent"
| "spl_token.initialize_multisig_with_rent"
| "spl_token.initialize_account2"
) {
return kb_execution_api::ExecutionCapability::unsupported(
"execution_spl_token_historical_variant_decode_only",
format!(
"SPL Token operation {operation_code} is an obsolete historical initialization variant; use the current no-Rent operation"
),
);
}
return kb_execution_api::ExecutionCapability::unsupported(
"execution_spl_token_operation_unsupported",
format!("SPL Token operation {operation_code} is not implemented"),
);
}
}
impl kb_execution_api::TypedInstructionExecutor for crate::SplTokenExecutor {
type Intent = crate::SplTokenExecutionIntent;
fn capability(
&self,
program_id: &kb_model::ProgramId,
operation_code: &str,
) -> kb_execution_api::ExecutionCapability {
return self.exact_capability(program_id, operation_code);
}
fn build_prepared_plan(
&self,
intent: &Self::Intent,
) -> kb_core::Result<kb_execution_api::PreparedExecutionPlan> {
let program_id =
kb_model::ProgramId(std::string::String::from(kb_program_ids::SPL_TOKEN_PROGRAM_ID));
return match self.exact_capability(&program_id, intent.operation.operation_code()) {
kb_execution_api::ExecutionCapability::Supported { operation_code: _ } => {
crate::build_prepared_plan(intent)
},
kb_execution_api::ExecutionCapability::Unsupported { reason_code, reason } => {
std::result::Result::Err(kb_core::Error::new(reason_code, reason))
},
};
}
}
impl kb_execution_api::InstructionExecutor for crate::SplTokenExecutor {
fn executor_name(&self) -> &'static str {
return "kb_executor_spl_token";
}
fn executor_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn program_ids(&self) -> &'static [&'static str] {
return &[kb_program_ids::SPL_TOKEN_PROGRAM_ID];
}
fn supports_request(
&self,
request: &kb_execution_api::ExecutionRequest,
) -> kb_execution_api::ExecutionSupport {
return match self.exact_capability(&request.program_id, &request.operation_code) {
kb_execution_api::ExecutionCapability::Supported { operation_code: _ } => {
kb_execution_api::ExecutionSupport::Yes
},
kb_execution_api::ExecutionCapability::Unsupported { reason_code: _, reason: _ } => {
kb_execution_api::ExecutionSupport::No
},
};
}
fn build_plan(
&self,
request: &kb_execution_api::ExecutionRequest,
) -> kb_core::Result<kb_execution_api::ExecutionPlan> {
match self.exact_capability(&request.program_id, &request.operation_code) {
kb_execution_api::ExecutionCapability::Supported { operation_code: _ } => {},
kb_execution_api::ExecutionCapability::Unsupported { reason_code, reason } => {
return std::result::Result::Err(kb_core::Error::new(reason_code, reason));
},
}
let intent =
match serde_json::from_str::<crate::SplTokenExecutionIntent>(&request.payload_json) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_spl_token_intent_deserialize_failed",
error.to_string(),
));
},
};
if intent.operation.operation_code() != request.operation_code.as_str() {
return std::result::Result::Err(kb_core::Error::new(
"execution_operation_code_mismatch",
format!(
"request operation {} does not match typed intent operation {}",
request.operation_code,
intent.operation.operation_code()
),
));
}
let prepared =
match kb_execution_api::TypedInstructionExecutor::build_prepared_plan(self, &intent) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let payload_value = match serde_json::to_value(&prepared) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"execution_spl_token_plan_serialize_failed",
error.to_string(),
));
},
};
let payload_json = match kb_execution_api::serialize_payload_json(&payload_value) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(kb_execution_api::ExecutionPlan {
executor_name: std::string::String::from("kb_executor_spl_token"),
instruction_count: prepared.instructions.len(),
payload_json,
});
}
}
#[cfg(test)]
mod tests {
fn request(program_id: &str, operation_code: &str) -> kb_execution_api::ExecutionRequest {
return kb_execution_api::ExecutionRequest {
program_id: kb_model::ProgramId(program_id.to_string()),
operation_code: operation_code.to_string(),
payload_json: std::string::String::from("{}"),
};
}
#[test]
fn exact_capabilities_cover_current_and_recent_operations_only() {
let executor = crate::SplTokenExecutor;
for operation_code in crate::SUPPORTED_OPERATION_CODES {
let request = request(kb_program_ids::SPL_TOKEN_PROGRAM_ID, operation_code);
assert_eq!(
kb_execution_api::InstructionExecutor::supports_request(&executor, &request),
kb_execution_api::ExecutionSupport::Yes
);
}
for operation_code in [
"spl_token.initialize_mint_with_rent",
"spl_token.initialize_account_with_rent",
"spl_token.initialize_multisig_with_rent",
"spl_token.initialize_account2",
] {
let capability = kb_execution_api::TypedInstructionExecutor::capability(
&executor,
&kb_model::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string()),
operation_code,
);
match capability {
kb_execution_api::ExecutionCapability::Unsupported { reason_code, reason: _ } => {
assert_eq!(reason_code, "execution_spl_token_historical_variant_decode_only");
},
kb_execution_api::ExecutionCapability::Supported { operation_code: _ } => {
panic!("historical variant must be decode-only");
},
}
}
}
#[test]
fn program_dispatch_is_exact() {
let executor = crate::SplTokenExecutor;
let foreign =
request(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID, crate::TRANSFER_CHECKED_OPERATION);
assert_eq!(
kb_execution_api::InstructionExecutor::supports_request(&executor, &foreign),
kb_execution_api::ExecutionSupport::No
);
}
#[test]
fn machine_readable_matrix_matches_executor_policy() {
let matrix = serde_json::from_str::<serde_json::Value>(include_str!(
"../../docs/SPL_TOKEN_MATRIX.json"
))
.unwrap_or_else(|error| panic!("matrix parsing failed: {error}"));
let instructions = matrix["instructions"]
.as_array()
.unwrap_or_else(|| panic!("matrix instructions must be an array"));
let mut supported = 0_usize;
let mut operation_codes = std::vec::Vec::new();
for instruction in instructions {
let status = instruction["executorSupport"]["status"]
.as_str()
.unwrap_or_else(|| panic!("executorSupport.status missing"));
if status == "supported" {
supported += 1;
operation_codes.push(
instruction["executorSupport"]["operationCode"]
.as_str()
.unwrap_or_else(|| panic!("supported operationCode missing")),
);
}
}
assert_eq!(supported, crate::SUPPORTED_OPERATION_CODES.len());
assert_eq!(instructions.len() - supported, 4);
operation_codes.sort_unstable();
let mut compiled = crate::SUPPORTED_OPERATION_CODES.to_vec();
compiled.sort_unstable();
assert_eq!(operation_codes, compiled);
}
}

View File

@@ -0,0 +1,457 @@
// file: kb_executor_spl_token/src/intent.rs
// version: 3
//! Typed classic SPL Token execution intents.
use ts_rs::TS; // rust-rules: derive-import
/// Stable operation code for mint initialization through `InitializeMint2`.
pub const INITIALIZE_MINT_OPERATION: &str = "spl_token.initialize_mint";
/// Stable operation code for token-account initialization through `InitializeAccount3`.
pub const INITIALIZE_ACCOUNT_OPERATION: &str = "spl_token.initialize_account";
/// Stable operation code for multisig initialization through `InitializeMultisig2`.
pub const INITIALIZE_MULTISIG_OPERATION: &str = "spl_token.initialize_multisig";
/// Stable operation code for an unchecked token transfer.
pub const TRANSFER_OPERATION: &str = "spl_token.transfer";
/// Stable operation code for an approval.
pub const APPROVE_OPERATION: &str = "spl_token.approve";
/// Stable operation code for a delegate revocation.
pub const REVOKE_OPERATION: &str = "spl_token.revoke";
/// Stable operation code for an authority change.
pub const SET_AUTHORITY_OPERATION: &str = "spl_token.set_authority";
/// Stable operation code for minting tokens.
pub const MINT_TO_OPERATION: &str = "spl_token.mint_to";
/// Stable operation code for burning tokens.
pub const BURN_OPERATION: &str = "spl_token.burn";
/// Stable operation code for closing a token account.
pub const CLOSE_ACCOUNT_OPERATION: &str = "spl_token.close_account";
/// Stable operation code for freezing a token account.
pub const FREEZE_ACCOUNT_OPERATION: &str = "spl_token.freeze_account";
/// Stable operation code for thawing a token account.
pub const THAW_ACCOUNT_OPERATION: &str = "spl_token.thaw_account";
/// Stable operation code for a checked token transfer.
pub const TRANSFER_CHECKED_OPERATION: &str = "spl_token.transfer_checked";
/// Stable operation code for a checked approval.
pub const APPROVE_CHECKED_OPERATION: &str = "spl_token.approve_checked";
/// Stable operation code for checked minting.
pub const MINT_TO_CHECKED_OPERATION: &str = "spl_token.mint_to_checked";
/// Stable operation code for checked burning.
pub const BURN_CHECKED_OPERATION: &str = "spl_token.burn_checked";
/// Stable operation code for synchronizing wrapped SOL.
pub const SYNC_NATIVE_OPERATION: &str = "spl_token.sync_native";
/// Stable operation code for querying account data size.
pub const GET_ACCOUNT_DATA_SIZE_OPERATION: &str = "spl_token.get_account_data_size";
/// Stable operation code for the classic compatibility no-op.
pub const INITIALIZE_IMMUTABLE_OWNER_OPERATION: &str = "spl_token.initialize_immutable_owner";
/// Stable operation code for raw-to-UI amount conversion.
pub const AMOUNT_TO_UI_AMOUNT_OPERATION: &str = "spl_token.amount_to_ui_amount";
/// Stable operation code for UI-to-raw amount conversion.
pub const UI_AMOUNT_TO_AMOUNT_OPERATION: &str = "spl_token.ui_amount_to_amount";
/// Stable operation code for rescuing excess lamports.
pub const WITHDRAW_EXCESS_LAMPORTS_OPERATION: &str = "spl_token.withdraw_excess_lamports";
/// Stable operation code for the recent wrapped-SOL partial unwrap.
pub const UNWRAP_LAMPORTS_OPERATION: &str = "spl_token.unwrap_lamports";
/// Stable operation code for a recent bounded batch.
pub const BATCH_OPERATION: &str = "spl_token.batch";
/// Operation codes constructible by this executor.
pub const SUPPORTED_OPERATION_CODES: &[&str] = &[
INITIALIZE_MINT_OPERATION,
INITIALIZE_ACCOUNT_OPERATION,
INITIALIZE_MULTISIG_OPERATION,
TRANSFER_OPERATION,
APPROVE_OPERATION,
REVOKE_OPERATION,
SET_AUTHORITY_OPERATION,
MINT_TO_OPERATION,
BURN_OPERATION,
CLOSE_ACCOUNT_OPERATION,
FREEZE_ACCOUNT_OPERATION,
THAW_ACCOUNT_OPERATION,
TRANSFER_CHECKED_OPERATION,
APPROVE_CHECKED_OPERATION,
MINT_TO_CHECKED_OPERATION,
BURN_CHECKED_OPERATION,
SYNC_NATIVE_OPERATION,
GET_ACCOUNT_DATA_SIZE_OPERATION,
INITIALIZE_IMMUTABLE_OWNER_OPERATION,
AMOUNT_TO_UI_AMOUNT_OPERATION,
UI_AMOUNT_TO_AMOUNT_OPERATION,
WITHDRAW_EXCESS_LAMPORTS_OPERATION,
UNWRAP_LAMPORTS_OPERATION,
BATCH_OPERATION,
];
/// Exact unsigned on-chain amount represented as a decimal JSON string.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_executor_spl_token/intent/SplTokenAmount.ts"
)]
pub struct SplTokenAmount(
/// Canonical unsigned decimal representation.
pub std::string::String,
);
/// Ordered simple or multisig authority supplied to an instruction builder.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_executor_spl_token/intent/SplTokenAuthority.ts"
)]
pub struct SplTokenAuthority {
/// Authority account. It signs only when `multisig_signers` is empty.
pub authority: kb_model::Pubkey,
/// Ordered multisig signer occurrences. Duplicate metas remain ordered.
pub multisig_signers: std::vec::Vec<kb_model::Pubkey>,
}
/// Authority domain used by `SetAuthority`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_executor_spl_token/intent/SplTokenAuthorityType.ts"
)]
pub enum SplTokenAuthorityType {
/// Mint authority.
MintTokens,
/// Freeze authority.
FreezeAccount,
/// Token-account owner.
AccountOwner,
/// Token-account close authority.
CloseAccount,
}
/// One officially constructible non-batch instruction.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[serde(tag = "instruction", rename_all = "snake_case")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_executor_spl_token/intent/SplTokenSingleOperation.ts"
)]
pub enum SplTokenSingleOperation {
/// Initialize a mint with the current no-Rent builder.
InitializeMint {
/// Mint account already created for the classic Token program.
mint: kb_model::Pubkey,
/// Mint authority.
mint_authority: kb_model::Pubkey,
/// Optional freeze authority.
freeze_authority: std::option::Option<kb_model::Pubkey>,
/// Mint decimals.
decimals: u8,
},
/// Initialize a token account with the current no-Rent builder.
InitializeAccount {
/// Token account already created for the classic Token program.
account: kb_model::Pubkey,
/// Mint associated with the account.
mint: kb_model::Pubkey,
/// Token-account owner.
owner: kb_model::Pubkey,
},
/// Initialize a multisig with the current no-Rent builder.
InitializeMultisig {
/// Multisig account already created for the classic Token program.
multisig: kb_model::Pubkey,
/// Ordered member accounts.
members: std::vec::Vec<kb_model::Pubkey>,
/// Required member threshold.
threshold: u8,
},
/// Transfer a raw token amount without carrying the mint or decimals in the wire.
Transfer {
/// Source token account.
source: kb_model::Pubkey,
/// Destination token account.
destination: kb_model::Pubkey,
/// Owner, delegate or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact raw token amount.
amount: crate::SplTokenAmount,
},
/// Approve a delegate for a raw token allowance.
Approve {
/// Source token account.
source: kb_model::Pubkey,
/// Delegate account.
delegate: kb_model::Pubkey,
/// Owner or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact raw allowance.
amount: crate::SplTokenAmount,
},
/// Revoke the current delegate.
Revoke {
/// Source token account.
source: kb_model::Pubkey,
/// Owner or multisig authority.
authority: crate::SplTokenAuthority,
},
/// Change or revoke an authority.
SetAuthority {
/// Mint or token account whose authority changes.
owned: kb_model::Pubkey,
/// Authority domain.
authority_type: crate::SplTokenAuthorityType,
/// New authority, or `None` to revoke it.
new_authority: std::option::Option<kb_model::Pubkey>,
/// Current simple or multisig authority.
current_authority: crate::SplTokenAuthority,
},
/// Mint a raw token amount.
MintTo {
/// Mint account.
mint: kb_model::Pubkey,
/// Destination token account.
destination: kb_model::Pubkey,
/// Mint authority.
authority: crate::SplTokenAuthority,
/// Exact raw amount.
amount: crate::SplTokenAmount,
},
/// Burn a raw token amount.
Burn {
/// Source token account.
source: kb_model::Pubkey,
/// Mint account.
mint: kb_model::Pubkey,
/// Owner, delegate or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact raw amount.
amount: crate::SplTokenAmount,
},
/// Close a token account.
CloseAccount {
/// Token account to close.
account: kb_model::Pubkey,
/// Lamport destination.
destination: kb_model::Pubkey,
/// Owner or close authority.
authority: crate::SplTokenAuthority,
},
/// Freeze a token account.
FreezeAccount {
/// Token account to freeze.
account: kb_model::Pubkey,
/// Mint account.
mint: kb_model::Pubkey,
/// Freeze authority.
authority: crate::SplTokenAuthority,
},
/// Thaw a token account.
ThawAccount {
/// Token account to thaw.
account: kb_model::Pubkey,
/// Mint account.
mint: kb_model::Pubkey,
/// Freeze authority.
authority: crate::SplTokenAuthority,
},
/// Transfer while checking an explicit mint and decimals value.
TransferChecked {
/// Source token account.
source: kb_model::Pubkey,
/// Mint account.
mint: kb_model::Pubkey,
/// Destination token account.
destination: kb_model::Pubkey,
/// Owner, delegate or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact raw amount.
amount: crate::SplTokenAmount,
/// Expected mint decimals.
decimals: u8,
},
/// Approve a delegate while checking an explicit mint and decimals value.
ApproveChecked {
/// Source token account.
source: kb_model::Pubkey,
/// Mint account.
mint: kb_model::Pubkey,
/// Delegate account.
delegate: kb_model::Pubkey,
/// Owner or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact raw allowance.
amount: crate::SplTokenAmount,
/// Expected mint decimals.
decimals: u8,
},
/// Mint while checking explicit decimals.
MintToChecked {
/// Mint account.
mint: kb_model::Pubkey,
/// Destination token account.
destination: kb_model::Pubkey,
/// Mint authority.
authority: crate::SplTokenAuthority,
/// Exact raw amount.
amount: crate::SplTokenAmount,
/// Expected mint decimals.
decimals: u8,
},
/// Burn while checking explicit decimals.
BurnChecked {
/// Source token account.
source: kb_model::Pubkey,
/// Mint account.
mint: kb_model::Pubkey,
/// Owner, delegate or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact raw amount.
amount: crate::SplTokenAmount,
/// Expected mint decimals.
decimals: u8,
},
/// Synchronize a wrapped-SOL account.
SyncNative {
/// Wrapped-SOL token account.
account: kb_model::Pubkey,
/// Include the optional Rent sysvar account published by interface 3.0.0.
include_rent_sysvar: bool,
},
/// Query the token-account size for a mint.
GetAccountDataSize {
/// Mint account.
mint: kb_model::Pubkey,
},
/// Build the classic-program immutable-owner compatibility no-op.
InitializeImmutableOwner {
/// Token account being prepared.
account: kb_model::Pubkey,
},
/// Convert a raw amount using mint decimals through return data.
AmountToUiAmount {
/// Mint account.
mint: kb_model::Pubkey,
/// Exact raw amount.
amount: crate::SplTokenAmount,
},
/// Convert a UI amount using mint decimals through return data.
UiAmountToAmount {
/// Mint account.
mint: kb_model::Pubkey,
/// Exact UTF-8 UI amount.
ui_amount: std::string::String,
},
/// Withdraw lamports above rent exemption.
WithdrawExcessLamports {
/// Program-owned source account.
account: kb_model::Pubkey,
/// Lamport destination.
destination: kb_model::Pubkey,
/// Owner or multisig authority.
authority: crate::SplTokenAuthority,
},
/// Unwrap some or all lamports from a wrapped-SOL account.
UnwrapLamports {
/// Wrapped-SOL token account.
account: kb_model::Pubkey,
/// Lamport destination.
destination: kb_model::Pubkey,
/// Owner or multisig authority.
authority: crate::SplTokenAuthority,
/// Exact lamports to unwrap, or `None` for the complete balance.
amount_lamports: std::option::Option<crate::SplTokenAmount>,
},
}
impl crate::SplTokenSingleOperation {
/// Returns the stable operation code.
pub fn operation_code(&self) -> &'static str {
return match self {
Self::InitializeMint { .. } => crate::INITIALIZE_MINT_OPERATION,
Self::InitializeAccount { .. } => crate::INITIALIZE_ACCOUNT_OPERATION,
Self::InitializeMultisig { .. } => crate::INITIALIZE_MULTISIG_OPERATION,
Self::Transfer { .. } => crate::TRANSFER_OPERATION,
Self::Approve { .. } => crate::APPROVE_OPERATION,
Self::Revoke { .. } => crate::REVOKE_OPERATION,
Self::SetAuthority { .. } => crate::SET_AUTHORITY_OPERATION,
Self::MintTo { .. } => crate::MINT_TO_OPERATION,
Self::Burn { .. } => crate::BURN_OPERATION,
Self::CloseAccount { .. } => crate::CLOSE_ACCOUNT_OPERATION,
Self::FreezeAccount { .. } => crate::FREEZE_ACCOUNT_OPERATION,
Self::ThawAccount { .. } => crate::THAW_ACCOUNT_OPERATION,
Self::TransferChecked { .. } => crate::TRANSFER_CHECKED_OPERATION,
Self::ApproveChecked { .. } => crate::APPROVE_CHECKED_OPERATION,
Self::MintToChecked { .. } => crate::MINT_TO_CHECKED_OPERATION,
Self::BurnChecked { .. } => crate::BURN_CHECKED_OPERATION,
Self::SyncNative { .. } => crate::SYNC_NATIVE_OPERATION,
Self::GetAccountDataSize { .. } => crate::GET_ACCOUNT_DATA_SIZE_OPERATION,
Self::InitializeImmutableOwner { .. } => crate::INITIALIZE_IMMUTABLE_OWNER_OPERATION,
Self::AmountToUiAmount { .. } => crate::AMOUNT_TO_UI_AMOUNT_OPERATION,
Self::UiAmountToAmount { .. } => crate::UI_AMOUNT_TO_AMOUNT_OPERATION,
Self::WithdrawExcessLamports { .. } => crate::WITHDRAW_EXCESS_LAMPORTS_OPERATION,
Self::UnwrapLamports { .. } => crate::UNWRAP_LAMPORTS_OPERATION,
};
}
pub(crate) fn requires_materialization(&self) -> bool {
return !matches!(
self,
Self::GetAccountDataSize { .. }
| Self::InitializeImmutableOwner { .. }
| Self::AmountToUiAmount { .. }
| Self::UiAmountToAmount { .. }
);
}
}
/// Top-level SPL Token operation, including the recent bounded Batch builder.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[serde(tag = "operation", rename_all = "snake_case")]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_executor_spl_token/intent/SplTokenOperation.ts"
)]
pub enum SplTokenOperation {
/// Build one non-batch instruction.
Instruction {
/// Exact instruction arguments.
value: crate::SplTokenSingleOperation,
},
/// Build one Batch from ordered, non-batch child operations.
Batch {
/// Ordered child operations. Nested Batch is impossible in this contract.
instructions: std::vec::Vec<crate::SplTokenSingleOperation>,
},
}
impl crate::SplTokenOperation {
/// Returns the stable top-level operation code.
pub fn operation_code(&self) -> &'static str {
return match self {
Self::Instruction { value } => value.operation_code(),
Self::Batch { .. } => crate::BATCH_OPERATION,
};
}
pub(crate) fn requires_materialization(&self) -> bool {
return match self {
Self::Instruction { value } => value.requires_materialization(),
Self::Batch { instructions } => {
instructions.iter().any(|value| return value.requires_materialization())
},
};
}
}
/// Complete typed intent accepted by the classic SPL Token executor.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
#[ts(
export,
export_to = "../frontend/ts/bindings/kb_executor_spl_token/intent/SplTokenExecutionIntent.ts"
)]
pub struct SplTokenExecutionIntent {
/// Stable caller-provided identifier used for logs and replay correlation.
pub intent_id: std::string::String,
/// Transaction fee payer.
pub fee_payer: kb_model::Pubkey,
/// Conservative execution policy. Its default is simulation-only.
pub policy: kb_execution_api::ExecutionPolicy,
/// Typed classic Token operation.
pub operation: crate::SplTokenOperation,
}

View File

@@ -0,0 +1,90 @@
// file: kb_executor_spl_token/src/lib.rs
// version: 8
//! Executor crate for `spl_token`.
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
mod builder;
mod constants;
mod executor;
mod intent;
/// Crate-root access to `build_prepared_plan` from `builder`.
pub(crate) use crate::builder::build_prepared_plan;
/// Maximum aggregate account-meta occurrences accepted by one Batch plan.
pub(crate) use crate::constants::MAX_BATCH_ACCOUNTS;
/// Maximum number of children accepted by one Batch plan.
pub(crate) use crate::constants::MAX_BATCH_INSTRUCTIONS;
/// Maximum number of ordered multisig signer occurrences.
pub(crate) use crate::constants::MAX_MULTISIG_SIGNERS;
/// Maximum UI amount string length accepted before transaction assembly.
pub(crate) use crate::constants::MAX_UI_AMOUNT_BYTES;
/// Canonical tracing target for this crate.
pub(crate) use crate::constants::TRACING_TARGET;
/// Exposes the typed SPL Token executor.
pub use crate::executor::SplTokenExecutor;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::AMOUNT_TO_UI_AMOUNT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::APPROVE_CHECKED_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::APPROVE_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::BATCH_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::BURN_CHECKED_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::BURN_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::CLOSE_ACCOUNT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::FREEZE_ACCOUNT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::GET_ACCOUNT_DATA_SIZE_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::INITIALIZE_ACCOUNT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::INITIALIZE_IMMUTABLE_OWNER_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::INITIALIZE_MINT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::INITIALIZE_MULTISIG_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::MINT_TO_CHECKED_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::MINT_TO_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::REVOKE_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::SET_AUTHORITY_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::SUPPORTED_OPERATION_CODES;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::SYNC_NATIVE_OPERATION;
/// Exposes the exact raw amount string contract.
pub use crate::intent::SplTokenAmount;
/// Exposes the typed authority contract.
pub use crate::intent::SplTokenAuthority;
/// Exposes the typed authority-kind contract.
pub use crate::intent::SplTokenAuthorityType;
/// Exposes the complete typed execution intent.
pub use crate::intent::SplTokenExecutionIntent;
/// Exposes the top-level operation contract.
pub use crate::intent::SplTokenOperation;
/// Exposes the non-batch operation contract.
pub use crate::intent::SplTokenSingleOperation;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::THAW_ACCOUNT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::TRANSFER_CHECKED_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::TRANSFER_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::UI_AMOUNT_TO_AMOUNT_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::UNWRAP_LAMPORTS_OPERATION;
/// Exposes stable operation codes for generic executor dispatch.
pub use crate::intent::WITHDRAW_EXCESS_LAMPORTS_OPERATION;