fix(token): support external mint authority; intentional metadata supply; demo path resolver

Addresses review on PR #125 (LP-0013):

#1 authority transfer now hands control to the new signer. mint and
set_authority take a trailing authority_accounts (Vec<AccountWithMetadata>):
empty preserves the original self/PDA-authority behavior (AMM unchanged),
one entry lets an external/rotated authority actually mint or rotate again.
Tests: rotated_authority_can_mint, rotated_authority_old_key_cannot_mint.

#2 metadata-backed fungibles take a real mint_authority instead of a
hardcoded Authority::renounced(), matching the plain-fungible supply model.
Test: test_metadata_fungible_with_authority_is_mintable.

#3 demo-full-flow.sh resolves TOKEN_BIN from the README-documented
cargo risczero build output, falling back to the workspace build, with an
explicit TOKEN_BIN override still respected.

Regenerated token-idl.json for the new trailing authority_accounts.
This commit is contained in:
bristinWild 2026-06-19 17:51:56 +05:30
parent dd8328cf9f
commit 160ff8ee4a
8 changed files with 238 additions and 28 deletions

View File

@ -150,6 +150,13 @@
"writable": true, "writable": true,
"signer": false, "signer": false,
"init": false "init": false
},
{
"name": "authority_accounts",
"writable": false,
"signer": false,
"init": false,
"rest": true
} }
], ],
"args": [ "args": [
@ -167,6 +174,13 @@
"writable": false, "writable": false,
"signer": false, "signer": false,
"init": false "init": false
},
{
"name": "authority_accounts",
"writable": false,
"signer": false,
"init": false,
"rest": true
} }
], ],
"args": [ "args": [

View File

@ -90,6 +90,9 @@ pub enum NewTokenDefinition {
Fungible { Fungible {
name: String, name: String,
total_supply: u128, total_supply: u128,
/// Mint authority. `Some(id)` makes the token mintable by `id`; `None`
/// fixes the supply.
mint_authority: Option<AccountId>,
}, },
NonFungible { NonFungible {
name: String, name: String,

View File

@ -128,6 +128,7 @@ mod token {
#[account(mut, signer)] #[account(mut, signer)]
definition_account: AccountWithMetadata, definition_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata, user_holding_account: AccountWithMetadata,
authority_accounts: Vec<AccountWithMetadata>,
amount_to_mint: u128, amount_to_mint: u128,
) -> SpelResult { ) -> SpelResult {
Ok(spel_framework::SpelOutput::execute( Ok(spel_framework::SpelOutput::execute(
@ -135,6 +136,7 @@ mod token {
definition_account, definition_account,
user_holding_account, user_holding_account,
amount_to_mint, amount_to_mint,
authority_accounts,
ctx.self_program_id, ctx.self_program_id,
), ),
vec![], vec![],
@ -147,10 +149,15 @@ mod token {
#[instruction] #[instruction]
pub fn set_authority( pub fn set_authority(
definition_account: AccountWithMetadata, definition_account: AccountWithMetadata,
authority_accounts: Vec<AccountWithMetadata>,
new_authority: Option<AccountId>, new_authority: Option<AccountId>,
) -> SpelResult { ) -> SpelResult {
Ok(spel_framework::SpelOutput::execute( Ok(spel_framework::SpelOutput::execute(
token_program::set_authority::set_authority(definition_account, new_authority), token_program::set_authority::set_authority(
definition_account,
new_authority,
authority_accounts,
),
vec![], vec![],
)) ))
} }

View File

@ -9,6 +9,7 @@ pub fn mint(
definition_account: AccountWithMetadata, definition_account: AccountWithMetadata,
user_holding_account: AccountWithMetadata, user_holding_account: AccountWithMetadata,
amount_to_mint: u128, amount_to_mint: u128,
authority_accounts: Vec<AccountWithMetadata>,
token_program_id: ProgramId, token_program_id: ProgramId,
) -> Vec<AccountPostState> { ) -> Vec<AccountPostState> {
assert_eq!( assert_eq!(
@ -19,17 +20,22 @@ pub fn mint(
let mut definition = TokenDefinition::try_from(&definition_account.account.data) let mut definition = TokenDefinition::try_from(&definition_account.account.data)
.expect("Token Definition account must be valid"); .expect("Token Definition account must be valid");
// Minting is gated on the definition's mint authority: the definition account // Minting is gated on the definition's stored mint authority. The proof of
// must be authorized in this transaction and its id must match the stored // authority is whichever account is presented as authorized AND whose id
// authority. This holds for an external owner that signs the definition key, // matches the stored authority:
// and for a program-controlled PDA authorized via its seeds (e.g. the AMM's //
// pool definition minting LP tokens). // - When `authority_accounts` is empty, the definition account itself must be the authority
// (self/PDA authority — e.g. the AMM's LP definition minting under its own seed). This is the
// original mint behavior.
// - When `authority_accounts` has one entry, that account is the external authority (e.g. a
// rotated owner key). This lets a transferred authority actually mint, as RFP-001 requires.
if let TokenDefinition::Fungible { .. } = &definition { if let TokenDefinition::Fungible { .. } = &definition {
let authority = authority_accounts.first().unwrap_or(&definition_account);
assert!( assert!(
definition_account.is_authorized, authority.is_authorized,
"Mint authority must authorize the transaction" "Mint authority must authorize the transaction"
); );
let signer: [u8; 32] = definition_account let signer: [u8; 32] = authority
.account_id .account_id
.as_ref() .as_ref()
.try_into() .try_into()
@ -88,8 +94,17 @@ pub fn mint(
let mut holding_post = user_holding_account.account; let mut holding_post = user_holding_account.account;
holding_post.data = Data::from(&holding); holding_post.data = Data::from(&holding);
vec![ // Post-states must match pre-state order and count. Pre-state order is
AccountPostState::new(definition_post), // [definition, holding, ...authority_accounts]; authority accounts are
AccountPostState::new_claimed_if_default(holding_post, Claim::Authorized), // read-only and pass through unchanged.
] let mut post_states = Vec::with_capacity(authority_accounts.len().saturating_add(2));
post_states.push(AccountPostState::new(definition_post));
post_states.push(AccountPostState::new_claimed_if_default(
holding_post,
Claim::Authorized,
));
for authority in authority_accounts {
post_states.push(AccountPostState::new(authority.account));
}
post_states
} }

View File

@ -116,12 +116,16 @@ pub fn new_definition_with_metadata(
); );
let (token_definition, token_holding) = match new_definition { let (token_definition, token_holding) = match new_definition {
NewTokenDefinition::Fungible { name, total_supply } => ( NewTokenDefinition::Fungible {
name,
total_supply,
mint_authority,
} => (
TokenDefinition::Fungible { TokenDefinition::Fungible {
name, name,
total_supply, total_supply,
metadata_id: Some(metadata_target_account.account_id), metadata_id: Some(metadata_target_account.account_id),
authority: Authority::renounced(), authority: authority_from(mint_authority),
}, },
TokenHolding::Fungible { TokenHolding::Fungible {
definition_id: definition_target_account.account_id, definition_id: definition_target_account.account_id,

View File

@ -8,20 +8,23 @@ use token_core::TokenDefinition;
pub fn set_authority( pub fn set_authority(
definition_account: AccountWithMetadata, definition_account: AccountWithMetadata,
new_authority: Option<AccountId>, new_authority: Option<AccountId>,
authority_accounts: Vec<AccountWithMetadata>,
) -> Vec<AccountPostState> { ) -> Vec<AccountPostState> {
let mut definition = TokenDefinition::try_from(&definition_account.account.data) let mut definition = TokenDefinition::try_from(&definition_account.account.data)
.expect("Token Definition account must be valid"); .expect("Token Definition account must be valid");
match &mut definition { match &mut definition {
TokenDefinition::Fungible { .. } => { TokenDefinition::Fungible { .. } => {
// The current mint authority must authorize this transaction: the // The current mint authority must authorize this transaction. As in
// definition account must be authorized and its id must match the // `mint`, the proof is either the definition account itself (empty
// stored authority. // `authority_accounts`, self/PDA authority) or an explicit external
// authority account (one entry), so a rotated authority can act.
let authority = authority_accounts.first().unwrap_or(&definition_account);
assert!( assert!(
definition_account.is_authorized, authority.is_authorized,
"Mint authority must authorize the transaction" "Mint authority must authorize the transaction"
); );
let signer: [u8; 32] = definition_account let signer: [u8; 32] = authority
.account_id .account_id
.as_ref() .as_ref()
.try_into() .try_into()
@ -56,5 +59,11 @@ pub fn set_authority(
let mut definition_post = definition_account.account; let mut definition_post = definition_account.account;
definition_post.data = Data::from(&definition); definition_post.data = Data::from(&definition);
vec![AccountPostState::new(definition_post)] // Post-states match pre-state order/count: [definition, ...authority_accounts].
let mut post_states = Vec::with_capacity(authority_accounts.len().saturating_add(1));
post_states.push(AccountPostState::new(definition_post));
for authority in authority_accounts {
post_states.push(AccountPostState::new(authority.account));
}
post_states
} }

View File

@ -911,6 +911,7 @@ fn test_mint_not_valid_holding_account() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -924,6 +925,7 @@ fn test_mint_not_valid_definition_account() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -939,6 +941,7 @@ fn test_mint_missing_authorization() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -952,6 +955,7 @@ fn test_mint_rejects_foreign_owned_definition() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -966,6 +970,7 @@ fn test_mint_mismatched_token_definition() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -978,6 +983,7 @@ fn test_mint_success() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
@ -1003,6 +1009,7 @@ fn test_mint_uninit_holding_success() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
@ -1029,6 +1036,7 @@ fn test_mint_total_supply_overflow() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_overflow(), BalanceForTests::mint_overflow(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -1042,6 +1050,7 @@ fn test_mint_holding_account_overflow() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_overflow(), BalanceForTests::mint_overflow(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -1055,6 +1064,7 @@ fn test_mint_cannot_mint_unmintable_tokens() {
definition_account, definition_account,
holding_account, holding_account,
BalanceForTests::mint_success(), BalanceForTests::mint_success(),
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -1067,6 +1077,7 @@ fn test_new_definition_with_metadata_success() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1088,6 +1099,42 @@ fn test_new_definition_with_metadata_success() {
assert_eq!(metadata_post.required_claim(), Some(Claim::Authorized)); assert_eq!(metadata_post.required_claim(), Some(Claim::Authorized));
} }
/// Comment #2: a metadata-backed fungible created with `mint_authority: Some(..)`
/// carries a real, non-renounced authority and is therefore mintable — no longer
/// force-fixed-supply the way the hardcoded `Authority::renounced()` made it.
#[test]
fn test_metadata_fungible_with_authority_is_mintable() {
let definition_account = AccountForTests::definition_account_uninit_auth();
let holding_account = AccountForTests::holding_account_uninit_auth();
let metadata_account = AccountForTests::metadata_account_uninit_auth();
let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"),
total_supply: 15u128,
mint_authority: Some(AccountId::new([15_u8; 32])),
};
let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple,
uri: "test_uri".to_string(),
creators: "test_creators".to_string(),
};
let post_states = new_definition_with_metadata(
definition_account,
holding_account,
metadata_account,
new_definition,
metadata,
);
let [definition_post, _holding_post, _metadata_post] = post_states.try_into().unwrap();
// The stored authority must be the requested key, NOT renounced.
let def = TokenDefinition::try_from(&definition_post.account().data).unwrap();
let stored = match def {
TokenDefinition::Fungible { authority, .. } => authority.authority(),
_ => None,
};
assert_eq!(stored, Some([15_u8; 32]));
}
#[should_panic(expected = "Definition target account must be authorized")] #[should_panic(expected = "Definition target account must be authorized")]
#[test] #[test]
fn test_call_new_definition_metadata_requires_authorized_definition() { fn test_call_new_definition_metadata_requires_authorized_definition() {
@ -1097,6 +1144,7 @@ fn test_call_new_definition_metadata_requires_authorized_definition() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1121,6 +1169,7 @@ fn test_call_new_definition_metadata_requires_authorized_holding() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1149,6 +1198,7 @@ fn test_call_new_definition_metadata_requires_authorized_metadata() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1181,6 +1231,7 @@ fn test_call_new_definition_metadata_with_init_definition() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1213,6 +1264,7 @@ fn test_call_new_definition_metadata_with_init_metadata() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1245,6 +1297,7 @@ fn test_call_new_definition_metadata_with_init_holding() {
let new_definition = NewTokenDefinition::Fungible { let new_definition = NewTokenDefinition::Fungible {
name: String::from("test"), name: String::from("test"),
total_supply: 15u128, total_supply: 15u128,
mint_authority: None,
}; };
let metadata = NewTokenMetadata { let metadata = NewTokenMetadata {
standard: MetadataStandard::Simple, standard: MetadataStandard::Simple,
@ -1418,6 +1471,7 @@ mod authority_tests {
def_with_authority(), def_with_authority(),
holding_account(), holding_account(),
50_000, 50_000,
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
let [def_post, holding_post] = post_states.try_into().unwrap(); let [def_post, holding_post] = post_states.try_into().unwrap();
@ -1448,6 +1502,7 @@ mod authority_tests {
def_with_authority_revoked(), def_with_authority_revoked(),
holding_account(), holding_account(),
50_000, 50_000,
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -1457,7 +1512,7 @@ mod authority_tests {
fn mint_without_is_authorized_fails() { fn mint_without_is_authorized_fails() {
let mut def = def_with_authority(); let mut def = def_with_authority();
def.is_authorized = false; def.is_authorized = false;
let _ = mint(def, holding_account(), 50_000, TOKEN_PROGRAM_ID); let _ = mint(def, holding_account(), 50_000, vec![], TOKEN_PROGRAM_ID);
} }
#[test] #[test]
@ -1467,6 +1522,7 @@ mod authority_tests {
def_wrong_authority(), def_wrong_authority(),
holding_account(), holding_account(),
50_000, 50_000,
vec![],
TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID,
); );
} }
@ -1474,13 +1530,17 @@ mod authority_tests {
#[test] #[test]
#[should_panic(expected = "New mint authority must be a valid non-zero account ID")] #[should_panic(expected = "New mint authority must be a valid non-zero account ID")]
fn set_authority_rejects_zero_new_authority() { fn set_authority_rejects_zero_new_authority() {
let _ = set_authority(def_with_authority(), Some(AccountId::new([0u8; 32]))); let _ = set_authority(
def_with_authority(),
Some(AccountId::new([0u8; 32])),
vec![],
);
} }
#[test] #[test]
fn set_authority_rotates_to_new_key() { fn set_authority_rotates_to_new_key() {
let new_key = AccountId::new([7_u8; 32]); let new_key = AccountId::new([7_u8; 32]);
let post_states = set_authority(def_with_authority(), Some(new_key)); let post_states = set_authority(def_with_authority(), Some(new_key), vec![]);
let [def_post] = post_states.try_into().unwrap(); let [def_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap(); let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
@ -1493,7 +1553,7 @@ mod authority_tests {
#[test] #[test]
fn set_authority_revokes_permanently() { fn set_authority_revokes_permanently() {
let post_states = set_authority(def_with_authority(), None); let post_states = set_authority(def_with_authority(), None, vec![]);
let [def_post] = post_states.try_into().unwrap(); let [def_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap(); let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
@ -1510,6 +1570,7 @@ mod authority_tests {
let _ = set_authority( let _ = set_authority(
def_with_authority_revoked(), def_with_authority_revoked(),
Some(AccountId::new([7_u8; 32])), Some(AccountId::new([7_u8; 32])),
vec![],
); );
} }
@ -1518,19 +1579,23 @@ mod authority_tests {
fn set_authority_without_is_authorized_fails() { fn set_authority_without_is_authorized_fails() {
let mut def = def_with_authority(); let mut def = def_with_authority();
def.is_authorized = false; def.is_authorized = false;
let _ = set_authority(def, Some(AccountId::new([7_u8; 32]))); let _ = set_authority(def, Some(AccountId::new([7_u8; 32])), vec![]);
} }
#[test] #[test]
#[should_panic(expected = "SetAuthority failed")] #[should_panic(expected = "SetAuthority failed")]
fn set_authority_wrong_signer_fails() { fn set_authority_wrong_signer_fails() {
let _ = set_authority(def_wrong_authority(), Some(AccountId::new([7_u8; 32]))); let _ = set_authority(
def_wrong_authority(),
Some(AccountId::new([7_u8; 32])),
vec![],
);
} }
#[test] #[test]
fn set_authority_rotate_then_old_cannot_mint() { fn set_authority_rotate_then_old_cannot_mint() {
let new_key = AccountId::new([7_u8; 32]); let new_key = AccountId::new([7_u8; 32]);
let post_states = set_authority(def_with_authority(), Some(new_key)); let post_states = set_authority(def_with_authority(), Some(new_key), vec![]);
let [def_post] = post_states.try_into().unwrap(); let [def_post] = post_states.try_into().unwrap();
let def = TokenDefinition::try_from(&def_post.account().data).unwrap(); let def = TokenDefinition::try_from(&def_post.account().data).unwrap();
@ -1542,4 +1607,82 @@ mod authority_tests {
assert_eq!(auth, Some([7_u8; 32])); assert_eq!(auth, Some([7_u8; 32]));
assert_ne!(auth, Some(AUTHORITY)); assert_ne!(auth, Some(AUTHORITY));
} }
/// Authority signer for the rotated key B ([7;32]), authorized.
fn new_authority_signer() -> AccountWithMetadata {
AccountWithMetadata {
account: Account::default(),
is_authorized: true,
account_id: AccountId::new([7_u8; 32]),
}
}
/// RFP-001 end-to-end (comment #1): after rotating authority A -> B, the new
/// authority B can actually mint by presenting itself in `authority_accounts`.
#[test]
fn rotated_authority_can_mint() {
// Rotate A ([15;32]) -> B ([7;32]), signed by A via self-authority.
let rotate_post = set_authority(
def_with_authority(),
Some(AccountId::new([7_u8; 32])),
vec![],
);
let [def_post] = rotate_post.try_into().unwrap();
// Rebuild the definition carrying the rotated authority, re-authorized.
let mut rotated_def = def_with_authority();
rotated_def.account = def_post.account().clone();
// B mints by presenting itself as the external authority.
let mint_post = mint(
rotated_def,
holding_account(),
10_000,
vec![new_authority_signer()],
TOKEN_PROGRAM_ID,
);
let [def_after, holding_after, _auth] = mint_post.try_into().unwrap();
let minted = TokenDefinition::try_from(&def_after.account().data).unwrap();
assert!(matches!(
minted,
TokenDefinition::Fungible {
total_supply: 110_000,
..
}
));
let holding = TokenHolding::try_from(&holding_after.account().data).unwrap();
assert!(matches!(
holding,
TokenHolding::Fungible {
balance: 11_000,
..
}
));
}
/// Comment #1 negative: after rotation to B, the OLD authority A can no
/// longer mint. Here A attempts self-authority (empty `authority_accounts`),
/// but the definition's own id no longer matches the stored authority B.
#[test]
#[should_panic(expected = "Mint authority check failed")]
fn rotated_authority_old_key_cannot_mint() {
let rotate_post = set_authority(
def_with_authority(),
Some(AccountId::new([7_u8; 32])),
vec![],
);
let [def_post] = rotate_post.try_into().unwrap();
let mut rotated_def = def_with_authority();
rotated_def.account = def_post.account().clone();
// A ([15;32]) is no longer the authority; self-authority must fail.
let _ = mint(
rotated_def,
holding_account(),
10_000,
vec![],
TOKEN_PROGRAM_ID,
);
}
} }

View File

@ -35,7 +35,22 @@ fi
SPEL="${SPEL:-$HOME/rebase-lez/spel/target/release/spel}" SPEL="${SPEL:-$HOME/rebase-lez/spel/target/release/spel}"
LEZ_PROGRAMS="${LEZ_PROGRAMS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" LEZ_PROGRAMS="${LEZ_PROGRAMS:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
IDL="${IDL:-$LEZ_PROGRAMS/artifacts/token-idl.json}" IDL="${IDL:-$LEZ_PROGRAMS/artifacts/token-idl.json}"
TOKEN_BIN="${TOKEN_BIN:-$LEZ_PROGRAMS/target/riscv-guest/token-methods/token-guest/riscv32im-risc0-zkvm-elf/release/token.bin}" # Resolve the token guest binary from either build layout, in priority order:
# 1. `cargo risczero build --manifest-path programs/token/methods/guest/Cargo.toml`
# -> programs/token/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/token.bin
# (the build command documented in the README)
# 2. workspace build (`cargo build` / `cargo test`)
# -> target/riscv-guest/token-methods/token-guest/riscv32im-risc0-zkvm-elf/release/token.bin
# An explicit TOKEN_BIN env var always takes precedence.
_risc0_token_bin="$LEZ_PROGRAMS/programs/token/methods/guest/target/riscv32im-risc0-zkvm-elf/docker/token.bin"
_workspace_token_bin="$LEZ_PROGRAMS/target/riscv-guest/token-methods/token-guest/riscv32im-risc0-zkvm-elf/release/token.bin"
if [ -z "${TOKEN_BIN:-}" ]; then
if [ -f "$_risc0_token_bin" ]; then
TOKEN_BIN="$_risc0_token_bin"
else
TOKEN_BIN="$_workspace_token_bin"
fi
fi
DEMO_DIR="${DEMO_DIR:-$(pwd)}" DEMO_DIR="${DEMO_DIR:-$(pwd)}"
WALLET_DIR="${WALLET_DIR:-$DEMO_DIR/.scaffold/wallet}" WALLET_DIR="${WALLET_DIR:-$DEMO_DIR/.scaffold/wallet}"