Ephemeral SPL Token program implementing MIMD 0013. It provides temporary (ephemeral) SPL balances that can be delegated to Data Layer Programs (DLPs), queued for delayed settlement, or routed through shuttle-based flows.
e-token— On-chain program (cdylib) implementing the Ephemeral SPL Token logic.e-token-api—no_stdrlib with the program ID, instruction discriminators, and shared types used by clients and tests.idl/ephemeral_spl_token.json— Machine-readable instruction/account summary aligned with the on-chain processors.
Public instructions currently handled by the program are:
Core balance management
0InitializeEphemeralAta— idempotently create the Ephemeral ATA PDA derived from[user, mint].1InitializeGlobalVault— create the per-mint global vault PDA derived from[mint], plus the vault-owned Ephemeral ATA and vault ATA.2DepositSplTokens— transfer tokens into the global vault and credit a standard or shuttle Ephemeral ATA.3WithdrawSplTokens— transfer tokens out of the global vault and debit the caller's Ephemeral ATA.10CloseEphemeralAta— close an empty Ephemeral ATA and refund its rent.
Delegation and permissions
4DelegateEphemeralAta— delegate an Ephemeral ATA to the delegation program. Raw data is empty or an appended 32-byte validator pubkey.5UndelegateEphemeralAta— commit and undelegate a user's delegated ATA flow. When the payer is itself delegated, pass the executing validator's writable Magic fee-vault PDA as an optional sixth account; it must be owned by the delegation program, and the Magic program validates it is the executing validator's vault.6CreateEphemeralAtaPermission— create the ACL permission PDA for an Ephemeral ATA. Raw data is a single permission-flags byte.7DelegateEphemeralAtaPermission— delegate the permission PDA associated with an Ephemeral ATA.8UndelegateEphemeralAtaPermission— commit and undelegate the permission PDA.9ResetEphemeralAtaPermission— reset permission members to the owner plus the requested ACL flags. Raw data is a single permission-flags byte.
Shuttle flows
11InitializeShuttleEphemeralAta— initialize shuttle metadata, its shuttle Ephemeral ATA, and the shuttle wallet ATA for[owner, mint, shuttle_id].13DelegateShuttleEphemeralAta— delegate a shuttle Ephemeral ATA. Raw data is empty or an appended 32-byte validator pubkey.14UndelegateShuttleEphemeralAta— commit and undelegate a shuttle wallet ATA, then schedule shuttle close/refund handling. Raw data is empty or a single escrow-index byte.15MergeShuttleIntoEphemeralAta— transfer the entire shuttle wallet ATA balance into a destination token account.24SetupAndDelegateShuttleEphemeralAtaWithMerge— initialize shuttle accounts if needed, deposit into the vault, sponsor delegation from the rent PDA, then schedule merge plus cleanup. Raw data isshuttle_id:u32,amount:u64, and an optional trailing 32-byte validator pubkey.25DepositAndDelegateShuttleEphemeralAtaWithMergeAndPrivateTransfer— same sponsored shuttle setup/deposit/delegate flow as24, but schedules a private queued transfer after merging back into the owner's source token account. Raw data starts withshuttle_id:u32andamount:u64, then carries three single-byte-length-prefixed buffers: validator bytes, encrypted destination bytes, and encrypted queue-suffix bytes.26WithdrawThroughDelegatedShuttleWithMerge— sponsor shuttle delegation, then schedule an owner-to-shuttle transfer followed by shuttle undelegation and cleanup. Raw data isshuttle_id:u32,amount:u64, and an optional trailing 32-byte validator pubkey.
Transfer queue and automation
12InitializeTransferQueue— create the per-mint transfer queue PDA derived from["queue", mint]. Raw data may be omitted, or may containsize_bytes:u32;0selects the default queue size.16DepositAndQueueTransfer— deposit into the vault and enqueue one or more delayed transfers. Raw data isamount:u64,min_delay_ms:u64,max_delay_ms:u64,split:u32, with an optional trailing flags byte.17EnsureTransferQueueCrank— ensure the recurring transfer-queue crank is scheduled.19DelegateTransferQueue— delegate the per-mint transfer queue PDA.20SponsoredLamportsTransfer— transfer lamports via the sponsored rent mechanism.
Rent PDA
23InitializeRentPda— initialize the global rent-sponsoring PDA derived from["rent"].
Internal automation instructions
196UndelegationCallback— delegation-program callback used to restore delegated account state.197CloseShuttleAtaIntent— internal close/refund handler for shuttle flows.198ExecuteReadyQueuedTransfer— internal settlement handler for a ready queued transfer.199ProcessTransferQueueTick— internal recurring crank callback that checks a queue and schedules settlement.201UndelegateWithdrawAndCloseShuttleEphemeralAta— internal post-delegation shuttle withdrawal and close/refund handler.
Discriminators 18, 21, and 22 are currently unused.
Program ID and external program
- The Ephemeral SPL Token program ID is declared in
e-token-api/src/lib.rsunderprogram::id_address(). - Delegation, undelegation, magic scheduling, and ACL flows are implemented through
ephemeral-rollups-pinocchio.
- Rust (toolchain pinned via
rust-toolchain.toml). - Solana CLI =
2.3.4(see[workspace.metadata.cli]inCargo.toml).
Build the on-chain program to SBF:
cargo build-sbfRun the test suite with program logs enabled:
cargo test-sbf --features loggingYou can run a single test by passing its name, for example:
cargo test-sbf --features logging delegate_ephemeral_ataTests live under e-token/tests/ and cover balance accounting, delegation/undelegation, shuttle flows, permissions, the rent PDA and lamports-PDA flows, and transfer-queue automation.
e-token-e2e runs against the real two-validator stack instead of an in-process
bank. This is the only place the transfer-queue crank is genuinely exercised.
The program asks for the crank through the magic program's ScheduleTask, and
the in-process suite can only check that request — its magic mock captures the
call and never executes it. Whether a scheduled task is then actually fired, on
cadence, forever, is the rollup's half of the contract, and that is the half now
running on Hydra. Only a live rollup tests it.
Install the validators once, then run the suite:
npm i -g @magicblock-labs/ephemeral-validator # mb-test-validator + ephemeral-validator
make test-e2emake test-e2e builds the program (with logging, so a failing transaction
reports which check rejected it), starts both validators, runs the tests, and
stops the validators again.
Starting a base validator dominates the runtime, so for a tight edit/test loop leave one running and reuse it:
make e2e-base-validator # terminal 1: base validator, stays up
make test-e2e SKIP_BASE_VALIDATOR=1 # terminal 2: attaches to it, repeatedlySKIP_ER_VALIDATOR=1 (with make e2e-er-validator) does the same for the
rollup, and the two combine. A skipped validator is attached to and left running
afterwards; a validator that is not skipped refuses to start if its port is
already busy, rather than silently reusing a stale instance. make e2e-stop
cleans up anything an interrupted run orphaned, and KEEP_E2E_LOGS=1 (with
E2E_ER_LOG=info) retains the temporary ledgers and validator logs for
post-mortem.
What the suite runs is the full private-transfer flow — one base-layer instruction that deposits, delegates a shuttle whose post-action queues the transfer on the rollup, and lets the crank settle it back — asserting on the recipient's base-layer balance at the end.
The validator pair is all it needs. ephemeral-validator executes the scheduled
task itself, so no external cranker has to be running. A magicblock-validator
rollup instead routes tasks through Hydra, which debits each execution's reward
from a crank account; the suite tops that account up itself, and is a no-op on a
rollup that has no crank to top up (see fund_queue_crank in
e-token-e2e/src/fixture.rs).
- The workspace depends on
ephemeral-rollups-pinocchioand several Solana crates; ensure your local environment matches the versions declared in the workspaceCargo.toml. - The program enables additional logs when compiled with the
loggingfeature, which is useful for debugging unit and integration tests.