logo

Hastra SOL Programmatic Integration Guide - AUTO Pool

This guide explains the programmatic lifecycle for integrating with the Hastra AUTO pool on Solana. For partners, this process converts liquid capital (USDC) into wYLDS, which can then be staked into AUTO shares.
The Hastra Solana protocol consists of two interconnected Anchor programs:
  • vault-mint (9WUyNREiPDMgwMh5Gt81Fd3JpiCKxpjZ5Dpq9Bo1RhMV) β€” accepts USDC and issues wYLDS at a 1:1 ratio. Shared across all Hastra Solana pools.
  • vault-stake-auto (5uJgCDrQHfA58fPqLsuU14Srg9quxXNHz91cZ54cq4pK) β€” accepts wYLDS and issues AUTO shares based on a Chainlink-sourced exchange rate.
Other pools: See hastra-sol-programmatic-integration-guide.md (PRIME pool) and hastra-sol-programmatic-integration-guide-smb.md (SMB pool).

1. The Asset Lifecycle: USDC β†’ wYLDS

When a team "mints" on Hastra Solana, they deposit USDC into the vault-mint program to receive wYLDS (Wrapped YLDS).
wYLDS is the base-layer wrapped token on Solana, acting as a custodial wrapper for YLDS, the underlying SEC-registered stablecoin. It acts as a 1:1 receipt for the USDC deposited into the vault token account.
Context: Unlike a simple swap, this "Deposit" moves USDC into a program-controlled vault token account (PDA-owned), and the program mints wYLDS to the user.

PDA Derivations (vault-mint)

typescript
const [configPda] = PublicKey.findProgramAddressSync( [Buffer.from("config")], vaultMintProgramId ); const [vaultTokenAccountConfigPda] = PublicKey.findProgramAddressSync( [Buffer.from("vault_token_account_config"), configPda.toBuffer()], vaultMintProgramId ); const [mintAuthorityPda] = PublicKey.findProgramAddressSync( [Buffer.from("mint_authority")], vaultMintProgramId );

Programmatic Mint (USDC β†’ wYLDS)

typescript
const vaultMint = new Program<HastraSolVaultMint>(idl, provider); // Deposit USDC to receive wYLDS. // The program transfers USDC from the user's ATA into the vault token account // and mints wYLDS into the user's wYLDS ATA. await vaultMint.methods .deposit(new BN(amount)) .accountsStrict({ config: configPda, vaultTokenAccount: vaultUsdcAta, // program-owned USDC vault vaultTokenAccountConfig: vaultTokenAccountConfigPda, mint: wyldsMint, // wYLDS mint mintAuthority: mintAuthorityPda, signer: userPublicKey, userVaultTokenAccount: userUsdcAta, // user's USDC ATA userMintTokenAccount: userWyldsAta, // user's wYLDS ATA tokenProgram: TOKEN_PROGRAM_ID, }) .rpc();

2. The Staking Lifecycle: wYLDS β†’ AUTO

AUTO represents a share-based position in the AUTO pool. Unlike the ETH flow, on Solana yield does not require a manual claim β€” it accrues through an appreciating Chainlink-sourced exchange rate.
Context: Yield is generated off-chain on the Provenance side and bridged back to Solana as YLDS/wYLDS. Periodically, the vault-stake-auto program receives newly minted wYLDS via a CPI into vault-mint's external_program_mint instruction (publish_rewards). The on-chain AUTO/wYLDS exchange rate is maintained by Chainlink Data Streams and updated periodically by rewards administrators via verify_price.
AUTO Exchange Rate:
plain text
shares_to_mint = deposit_wYLDS * price_scale / price wYLDS_returned = shares_burned * price / price_scale where: price = (wYLDS per 1 AUTO) * price_scale [from Chainlink Data Streams]
As yield flows into the stake vault, the Chainlink price appreciates β€” 1 AUTO becomes redeemable for more wYLDS over time.

PDA Derivations (vault-stake-auto)

typescript
const vaultStakeAutoProgramId = new PublicKey("5uJgCDrQHfA58fPqLsuU14Srg9quxXNHz91cZ54cq4pK"); const [stakeConfigPda] = PublicKey.findProgramAddressSync( [Buffer.from("stake_config")], vaultStakeAutoProgramId ); const [stakeVaultTokenAccountConfigPda] = PublicKey.findProgramAddressSync( [Buffer.from("stake_vault_token_account_config"), stakeConfigPda.toBuffer()], vaultStakeAutoProgramId ); const [vaultAuthorityPda] = PublicKey.findProgramAddressSync( [Buffer.from("vault_authority")], vaultStakeAutoProgramId ); const [mintAuthorityPda] = PublicKey.findProgramAddressSync( [Buffer.from("mint_authority")], vaultStakeAutoProgramId ); const [stakePriceConfigPda] = PublicKey.findProgramAddressSync( [Buffer.from("stake_price_config"), stakeConfigPda.toBuffer()], vaultStakeAutoProgramId );

Programmatic Stake (wYLDS β†’ AUTO)

typescript
const vaultStakeAuto = new Program<HastraSolVaultStake>(idl, provider); // Deposit wYLDS to receive AUTO shares at the current Chainlink exchange rate. await vaultStakeAuto.methods .deposit(new BN(amount)) .accountsStrict({ stakeConfig: stakeConfigPda, vaultTokenAccount: stakeVaultWyldsAta, // program-owned wYLDS vault stakeVaultTokenAccountConfig: stakeVaultTokenAccountConfigPda, vaultAuthority: vaultAuthorityPda, mint: autoMint, // AUTO share token mint vaultMint: wyldsMint, // wYLDS mint mintAuthority: mintAuthorityPda, signer: userPublicKey, userVaultTokenAccount: userWyldsAta, // user's wYLDS ATA userMintTokenAccount: userAutoAta, // user's AUTO ATA stakePriceConfig: stakePriceConfigPda, tokenProgram: TOKEN_PROGRAM_ID, }) .rpc();

Optional: Merkle-Based wYLDS Reward Claims (vault-mint)

In addition to exchange-rate appreciation, the protocol supports discrete reward epochs distributed via merkle proofs (used for incentive programs and supplemental allocations).
Important: These claims live on the vault-mint program and mint wYLDS (not AUTO shares) directly to the user. If a partner wants AUTO exposure on the claimed wYLDS, they must subsequently stake it via vault-stake-auto.
Context: Each epoch contains a merkle root summarizing user rewards for the period. Rewards are minted as wYLDS directly to the user's wYLDS ATA.
Leaf format: sha256(user_pubkey || reward_amount_le_bytes || epoch_index_le_bytes)
typescript
const indexLe = new BN(epochIndex).toArrayLike(Buffer, "le", 8); // V2 epoch PDAs (seed "epoch_v2" distinguishes from legacy V1 "epoch" namespace). const [epochPda] = PublicKey.findProgramAddressSync( [Buffer.from("epoch_v2"), indexLe], vaultMintProgramId ); const [epochCapPda] = PublicKey.findProgramAddressSync( [Buffer.from("epoch_cap"), indexLe], vaultMintProgramId ); const [epochRewardsPoolPda] = PublicKey.findProgramAddressSync( [Buffer.from("epoch_rewards_pool"), indexLe], vaultMintProgramId ); const [epochRewardsPoolAuthorityPda] = PublicKey.findProgramAddressSync( [Buffer.from("epoch_rewards_pool_authority"), indexLe], vaultMintProgramId ); const [claimRecordPda] = PublicKey.findProgramAddressSync( [Buffer.from("claim"), epochPda.toBuffer(), userPublicKey.toBuffer()], vaultMintProgramId ); // Claim rewards from a V2 epoch. Transfers wYLDS from the pre-funded pool rather than minting; // the on-chain cap tracker ensures claimed_total never exceeds the epoch total. // Note: this is called on vaultMint, not vaultStakeAuto. await vaultMint.methods .claimRewardsV2(new BN(rewardAmount), merkleProof) .accountsStrict({ config: configPda, // vault-mint config PDA user: userPublicKey, epoch: epochPda, epochCap: epochCapPda, // aggregate cap tracker claimRecord: claimRecordPda, // PDA: ["claim", epoch, user] β€” prevents double-claim epochRewardsPool: epochRewardsPoolPda, // pre-funded wYLDS escrow epochRewardsPoolAuthority: epochRewardsPoolAuthorityPda, userMintTokenAccount: userWyldsAta, // user's wYLDS ATA systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, }) .rpc();

3. The Redemption Lifecycle: AUTO β†’ wYLDS β†’ USDC

Returning to USDC is a multi-step process across both programs.

Phase 1: Redeem AUTO for wYLDS (vault-stake-auto)

The vault-stake-auto program supports a single-step direct redemption. There is no unbonding period or queuing β€” redeem atomically burns the specified AUTO shares and transfers the proportional wYLDS from the stake vault to the user.
Context: The amount of wYLDS returned is calculated using the live Chainlink price at the time of the call: wYLDS_returned = auto_amount * price / price_scale. The call will fail if the stored Chainlink price is stale (past price_max_staleness) or uninitialised.
typescript
// Atomically burn AUTO shares and receive wYLDS in a single call. await vaultStakeAuto.methods .redeem(new BN(autoAmount)) .accountsStrict({ stakeConfig: stakeConfigPda, vaultTokenAccount: stakeVaultWyldsAta, // program-owned wYLDS vault stakeVaultTokenAccountConfig: stakeVaultTokenAccountConfigPda, vaultAuthority: vaultAuthorityPda, signer: userPublicKey, ticket: vaultStakeAutoProgramId, // pass program ID if no legacy v1 ticket exists userVaultTokenAccount: userWyldsAta, // user's wYLDS ATA (destination) userMintTokenAccount: userAutoAta, // user's AUTO ATA (source to burn) mint: autoMint, // AUTO share token mint vaultMint: wyldsMint, // wYLDS mint stakePriceConfig: stakePriceConfigPda, tokenProgram: TOKEN_PROGRAM_ID, }) .rpc();
Legacy note: An optional ticket account exists for users who have an UnbondingTicket from the deprecated v1 two-step flow. If no ticket exists, pass the vault-stake-auto program ID for that account; Anchor treats it as None and skips all ticket-related constraints. The on-chain unbonding_period field is permanently set to 0 and the unbond instruction no longer exists.

Phase 2: wYLDS β†’ USDC (Operator-Mediated, vault-mint)

To convert wYLDS back to USDC, the vault-mint program uses an admin-mediated two-step request_redeem / complete_redeem flow. This leg is operator-managed and includes a batching minimum (currently $2k) and may be subject to banking-hours delays during the YLDS β†’ USDC conversion via Circle's CCTP.
For most institutional integrators, the recommended path is to stop at wYLDS unless USDC settlement is strictly required.

Phase 2a: User submits a redemption request

typescript
const [redemptionRequestPda] = PublicKey.findProgramAddressSync( [Buffer.from("redemption_request"), userPublicKey.toBuffer()], vaultMintProgramId ); const [redeemVaultAuthorityPda] = PublicKey.findProgramAddressSync( [Buffer.from("redeem_vault_authority")], vaultMintProgramId ); // Burns wYLDS from the user and creates a RedemptionRequest PDA. await vaultMint.methods .requestRedeem(new BN(wyldsAmount)) .accountsStrict({ signer: userPublicKey, userMintTokenAccount: userWyldsAta, // user's wYLDS ATA (to burn from) redemptionRequest: redemptionRequestPda, mint: wyldsMint, config: configPda, redeemVaultAuthority: redeemVaultAuthorityPda, systemProgram: SystemProgram.programId, tokenProgram: TOKEN_PROGRAM_ID, }) .rpc();

Phase 2b: Operator fulfills the redemption

The operator calls complete_redeem once USDC has been bridged via Circle's CCTP. The RedemptionRequest PDA is closed and rent is returned to the user.
typescript
// Operator-only: transfers USDC from the redeem vault to the user's wallet. await vaultMint.methods .completeRedeem() .accountsStrict({ admin: operatorPublicKey, user: userPublicKey, userMintTokenAccount: userWyldsAta, // wYLDS ATA (for constraint validation) userVaultTokenAccount: userUsd...