Dos contratos Soroban desplegados en Testnet, conectados entre sí:
- MyToken (
my-token): token fungible SEP-0041 con mint gobernado por owner, sell con burn, pausable. - PromptMarketplace (
prompt-marketplace): marketplace donde admins registran prompts, usuarios compran (quemando tokens), y admins pueden re-mintear.
| Contrato | ID | Wasm Hash | Tamaño |
|---|---|---|---|---|
| MyToken | CCHAUOEVX6TQD56VFZY2GI3N3HNF5W6QSRRKAGKDXV6S53T4BKD5PYQD | 6613593d... | 9.7 KB |
| PromptMarketplace | CA6RRLV4IBLKRRLPUDCVXZFDKRE77YHBNJFSXEFFLXV6EUAVVVS6HJUQ | f31af684... | 5.6 KB |
Arquitectura: el marketplace almacena el ID del token en __constructor. buy_prompt llama a my_token::sell_forwarded via env.invoke_contract (cross-contract). remint llama a my_token::mint_forwarded. Ambos contratos emiten eventos #[contractevent] (PromptRegistered, PromptPriceUpdated, PromptRemoved, PromptPurchased, TokensReminted).
⚠️ Cross-contract auth forwarding: las funcionessell_forwardedymint_forwardedno verificanrequire_auth()internamente. Confían en que la root invocation (marketplace) ya verificó la autorización. Esto evita el errorError(Auth, ExistingValue)de Soroban cuando se llamarequire_auth()para la misma address en root + sub-invocation.
# Todos los tests (22)
SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 cargo test --lib --target aarch64-apple-darwin
# Por paquete
cargo test -p my-token --lib --target aarch64-apple-darwin
cargo test -p prompt-marketplace --lib --target aarch64-apple-darwinPrueba el flujo completo contra testnet real: mint → register → buy (cross-contract) → verify → remint → verify.
# Con los contracts deployados actuales
TOKEN="CCHAUOEVX6TQD56VFZY2GI3N3HNF5W6QSRRKAGKDXV6S53T4BKD5PYQD" \
MKT="CA6RRLV4IBLKRRLPUDCVXZFDKRE77YHBNJFSXEFFLXV6EUAVVVS6HJUQ" \
bash scripts/integration-test.sh| Test | Qué cubre |
|---|---|
test_token_mint_and_balance |
Mint vía storage API + balance + total_supply |
test_token_metadata |
name, symbol, decimals |
test_mint_multiple_same_user |
Mint acumulativo a la misma address |
test_mint_to_different_users |
Mint a múltiples usuarios, supply tracking |
test_mint_overflow_panics |
i128::MAX + 1 debe panic |
test_zero_balance_default |
Balance por defecto es 0 |
| Test | Qué cubre |
|---|---|
test_register_and_query_prompt |
Happy path: register → get_price / get_owner |
test_duplicate_registration_panics |
Mismo ID no se puede registrar dos veces |
test_update_price |
Admin cambia precio |
test_remove_prompt |
Admin elimina prompt (idempotente) |
test_multiple_prompts_independent |
Prompts distintos no interfieren |
test_get_price_unregistered_panics |
Consultar precio de prompt inexistente |
test_non_admin_cannot_register |
No-admin no puede registrar |
test_non_admin_cannot_update_price |
No-admin no puede cambiar precio |
test_non_admin_cannot_remove |
No-admin no puede eliminar |
test_register_zero_price_panics |
Precio 0 es inválido |
test_update_price_zero_panics |
Actualizar a precio 0 es inválido |
test_register_max_price |
i128::MAX funciona como precio |
test_update_unregistered_prompt_panics |
Actualizar precio de prompt inexistente |
test_register_after_remove |
Re-registrar mismo ID post-eliminación |
test_has_access_unregistered |
has_access sin compra devuelve false |
test_token_mint_and_balance |
Mint vía storage en contexto del token |
SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 stellar contract build --package my-token
SOROBAN_SDK_BUILD_SYSTEM_SUPPORTS_SPEC_SHAKING_V2=1 stellar contract build --package prompt-marketplaceRequiere el target wasm32v1-none y la env var para Spec SHAKING v2.
# Token
stellar contract deploy \
--wasm target/wasm32v1-none/release/my_token.wasm \
--source default \
--network testnet \
--alias my_token \
-- \
--owner "$(stellar keys address default)" \
--name "PromptToken" \
--symbol "PRMPT" \
--decimals 7
# Marketplace (usar el ID del token deployado)
stellar contract deploy \
--wasm target/wasm32v1-none/release/prompt_marketplace.wasm \
--source default \
--network testnet \
--alias prompt_marketplace \
-- \
--admin "$(stellar keys address default)" \
--token "CCHAUOEVX6TQD56VFZY2GI3N3HNF5W6QSRRKAGKDXV6S53T4BKD5PYQD"# Leer nombre del token
stellar contract invoke --source default --network testnet \
--id CCHAUOEVX6TQD56VFZY2GI3N3HNF5W6QSRRKAGKDXV6S53T4BKD5PYQD \
-- name
# Registrar prompt
stellar contract invoke --source default --network testnet \
--id CA6RRLV4IBLKRRLPUDCVXZFDKRE77YHBNJFSXEFFLXV6EUAVVVS6HJUQ \
--send=yes \
-- register_prompt --prompt_id "alpha" --price 500 \
--owner "$(stellar keys address default)"
# Consultar precio
stellar contract invoke --source default --network testnet \
--id CA6RRLV4IBLKRRLPUDCVXZFDKRE77YHBNJFSXEFFLXV6EUAVVVS6HJUQ \
-- get_price --prompt_id "alpha"contracts/
├── tokens/ # MyToken (token fungible)
│ ├── Cargo.toml
│ └── src/
│ ├── lib.rs # module exports, re-export público
│ ├── contract.rs # #[contract] MyToken — API pública + traits
│ ├── events.rs # #[contractevent] structs (MintEvent, SellEvent, etc.)
│ ├── core/
│ │ └── token.rs # TokenManager — lógica de negocio
│ ├── storage/
│ │ └── types.rs # DataKey, contracttype structs
│ └── tests.rs # 6 tests
└── marketplace/ # PromptMarketplace
├── Cargo.toml
└── src/
├── lib.rs # module exports
├── contract.rs # #[contract] PromptMarketplace — API pública
├── storage/
│ └── types.rs # DataKey, Prompt struct, errors
└── tests.rs # 16 tests
Cargo.toml # workspace: tokens + marketplace
- Soroban SDK:
25.3.0 - OZ Stellar Contracts:
v0.7.1(fungible token, ownable, pausable) - Stellar CLI:
26.1.0 - Target:
wasm32v1-none(release),aarch64-apple-darwin(tests)
soroban-sdk v25 tiene una limitación: require_auth() para una misma dirección solo puede ejecutarse UNA VEZ por árbol de llamadas. Llamarlo en la root invocation y luego en una sub-invocación (via invoke_contract) falla con Error(Auth, ExistingValue).
Patrón adoptado:
- La función raíz (ej.
buy_prompt,remint) llamarequire_auth()para la/s dirección/es involucradas. - Las sub-invocaciones al token usan variantes
*_forwarded(sell_forwarded,mint_forwarded) que no verificanrequire_auth()internamente. - Para operaciones directas (sin marketplace), el token expone
sell(conrequire_auth) ymint(con#[only_owner]).
Esto aplica también a TokenManager::sell que usa Base::update en vez de Base::burn para evitar el doble require_auth de Base::burn.