172 releases (75 stable)
| new 2.0.60 | Oct 28, 2025 |
|---|---|
| 2.0.52 | Sep 30, 2025 |
| 2.0.46 | Jul 24, 2025 |
| 2.0.25 | Mar 27, 2025 |
| 0.2.19 | Mar 31, 2024 |
#612 in Magic Beans
1,334 downloads per month
Used in etherfuse-arb
1.5MB
31K
SLoC
Contains (ELF lib, 1.5MB) tests/fixtures/spl_token_2022.so, (ELF lib, 795KB) tests/fixtures/mpl_token_metadata.so, (ELF lib, 135KB) tests/fixtures/spl_token.so, (ELF lib, 105KB) spl_associated_token_account.so
Rust client
A generated Rust library for the Stablebond program.
The stablebond-sdk provides a set of methods to interact with the Stablebond program on the Solana blockchain.
We're currently using this ourselves at app.etherfuse.com.
We also have a devnet version of the program running at devnet.etherfuse.com. This is useful for testing and development, reach out to us for play-money and we'll transfer you some fake tokens to use.
lib.rs:
To get started quickly with the stablebond SDK, you can use the following methods to quickly build transactions for the fundamentals of etherfuse stablebonds.
Including
- Purchasing a bond
- Requesting a redemption
- Redeeming a bond
- Create a purchase order
- Redeeming a purchase order
Examples
use crate::{
accounts::{Bond, BondMultisigMeta, PaymentFeed},
find_bond_multisig_meta_pda, find_bond_pda, find_config_pda, find_issuance_pda, find_kyc_pda,
find_nft_issuance_vault_pda, find_payment_feed_pda, find_payment_pda, find_payout_pda,
find_purchase_order_pda, CreatePurchaseOrderV2, CreatePurchaseOrderV2InstructionArgs,
PurchaseBondV2, PurchaseBondV2InstructionArgs, RedeemBond, RedeemPurchaseOrder,
RequestRedemptionV2, RequestRedemptionV2InstructionArgs,
};
use anyhow::Result;
use mpl_token_metadata::accounts::{MasterEdition, Metadata};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{message::Message, signature::Keypair};
use solana_sdk::{pubkey::Pubkey, signer::Signer};
use solana_sdk::{system_program, transaction::Transaction};
use spl_associated_token_account::{
get_associated_token_address, get_associated_token_address_with_program_id,
};
// Creates a purchase bond transaction that can be sent to solana
// The user_wallet will have already needed to complete the KYC process for the transaction to be successful.
// See [API Reference](https://docs.etherfuse.com/api-reference/onboarding/generate-onboarding-url) for more information.
let client = RpcClient::new(rpc_url);
// Some user wallet keypair
let user_wallet = Keypair::from_str("user_wallet").unwrap();
// Some mint account
let mint = Pubkey::from_str("mint").unwrap();
// Get an un-signed transaction
let tx = make_purchase_bond_instruction(1000000, user_wallet, client, mint).await?;
// Sign the transaction with the users keypair and then...
// Submit the transaction
let sig = client.send_and_confirm_transaction(&tx).await?;
pub async fn make_purchase_bond_transaction(
client: &RpcClient,
amount: u64,
user_wallet: Pubkey,
mint: Pubkey,
) -> Result<Transaction> {
let ix_args = PurchaseBondV2InstructionArgs { amount };
let addresses = generate_buy_addresses(client, mint, user_wallet).await?;
let mint_auth_multisig_account = get_bond_mint_auth_multisig_if_needed(&client, mint).await?;
let ix = PurchaseBondV2 {
kyc_account: addresses.kyc_account,
user_wallet: user_wallet,
user_token_account: addresses.user_token_account,
user_payment_token_account: addresses.user_payment_token_account,
payment_account: addresses.payment_account,
payment_token_account: addresses.payment_token_account,
payment_mint_account: addresses.payment_mint_account,
payment_feed_account: addresses.payment_feed_account,
payment_base_price_feed_account: addresses.payment_price_feed_account,
payment_quote_price_feed_account: addresses.payment_quote_price_feed_account,
mint_auth_multisig_account,
bond_account: addresses.bond_account,
issuance_account: addresses.issuance_account,
mint_account: mint,
token2022_program: addresses.token_2022_program,
associated_token_program: addresses.associated_token_program,
token_program: addresses.token_program,
system_program: addresses.system_program,
}
.instruction(ix_args);
let tx = Transaction::new_unsigned(Message::new(&[ix], Some(&user_wallet)));
Ok(tx)
}
// Creates a transaction to begin the bond redemption process
let client = RpcClient::new(rpc_url);
// Some user wallet keypair
let user_wallet = Keypair::from_str("user_wallet").unwrap();
// Some mint account
let mint = Pubkey::from_str("mint").unwrap();
// Some NFT mint account
let nft_mint = Pubkey::from_str("nft_mint").unwrap();
// Get an un-signed transaction
let tx = make_request_redemption_instruction(1000000, user_wallet, mint, nft_mint)?;
// Sign the transaction with the users keypair and then...
// Submit the transaction
let sig = client.send_and_confirm_transaction(&tx).await?;
pub async fn make_request_redemption_transaction(
client: &RpcClient,
amount_in_tokens: u64,
user_wallet: Pubkey,
mint: Pubkey,
) -> Result<Transaction> {
let ix_args = RequestRedemptionV2InstructionArgs {
amount: amount_in_tokens,
};
let addresses = generate_buy_addresses(client, mint, user_wallet).await?;
let nft_mint_pair = Keypair::new();
let nft_mint = nft_mint_pair.pubkey();
let nft_issuance_vault_account = find_nft_issuance_vault_pda(nft_mint).0;
let nft_issuance_vault_token_account = get_associated_token_address_with_program_id(
&nft_issuance_vault_account,
&mint,
&spl_token_2022::id(),
);
let user_nft_token_account = get_associated_token_address(&user_wallet, &nft_mint);
let nft_metadata_account = Metadata::find_pda(&nft_mint).0;
let nft_master_edition_account = MasterEdition::find_pda(&nft_mint).0;
let nft_collection_mint = find_config_pda().0;
let nft_collection_metadata_account = Metadata::find_pda(&nft_collection_mint).0;
let nft_collection_master_edition_account = MasterEdition::find_pda(&nft_collection_mint).0;
let ix = RequestRedemptionV2 {
kyc_account: find_kyc_pda(user_wallet).0,
user_wallet,
config_account: find_config_pda().0,
token2022_program: addresses.token_2022_program,
associated_token_program: addresses.associated_token_program,
token_program: addresses.token_program,
system_program: addresses.system_program,
metadata_program: mpl_token_metadata::ID,
sysvar_instructions: solana_sdk::sysvar::instructions::id(),
bond_account: addresses.bond_account,
issuance_account: addresses.issuance_account,
mint_account: mint,
user_token_account: addresses.user_token_account,
nft_mint_account: nft_mint,
nft_issuance_vault_account,
nft_issuance_vault_token_account,
user_nft_token_account,
nft_metadata_account,
nft_master_edition_account,
nft_collection_mint,
nft_collection_metadata_account,
nft_collection_master_edition_account,
}
.instruction(ix_args);
Ok(Transaction::new_unsigned(Message::new(
&[ix],
Some(&user_wallet),
)))
}
// Creates a transaction to redeem a bond once payout is available
let client = RpcClient::new(rpc_url);
// Some user wallet keypair
let user_wallet = Keypair::from_str("user_wallet").unwrap();
// The mint for the bond underlying the NFT
let mint = Pubkey::from_str("mint").unwrap();
// The addres of the NFT
let nft_mint = Pubkey::from_str("nft_mint").unwrap();
// Get an un-signed transaction
let tx = make_redeem_bond_transaction(client, user_wallet, mint, nft_mint).await?;
// Sign the transaction with the users keypair and then...
// Submit the transaction
let sig = client.send_and_confirm_transaction(&tx).await?;
pub async fn make_redeem_bond_transaction(
client: &RpcClient,
user_wallet: Pubkey,
mint: Pubkey,
nft_mint: Pubkey,
) -> Result<Transaction> {
let bond_account = find_bond_pda(mint).0;
let data = client.get_account_data(&bond_account).await?;
let bond = Bond::from_bytes(&data).unwrap();
let payment_feed_account = find_payment_feed_pda(bond.payment_feed_type).0;
let issuance_account = find_issuance_pda(bond_account, bond.issuance_number).0;
let payment_mint_account = find_payment_pda(issuance_account).0;
let nft_issuance_vault_account = find_nft_issuance_vault_pda(nft_mint).0;
let nft_issuance_vault_token_account = get_associated_token_address_with_program_id(
&nft_issuance_vault_account,
&mint,
&spl_token_2022::id(),
);
let user_nft_token_account = get_associated_token_address(&user_wallet, &nft_mint);
let user_payment_token_account =
get_associated_token_address(&user_wallet, &payment_mint_account);
let nft_metadata_account = Metadata::find_pda(&nft_mint).0;
let nft_master_edition_account = MasterEdition::find_pda(&nft_mint).0;
let nft_collection_metadata_account = Metadata::find_pda(&nft_mint).0;
let payout_account = find_payout_pda(issuance_account).0;
let payout_token_account = get_associated_token_address(&payout_account, &mint);
let token2022_program = spl_token_2022::id();
let associated_token_program = spl_associated_token_account::id();
let token_program = spl_token::id();
let metadata_program = mpl_token_metadata::ID;
let system_program = system_program::id();
let ix = RedeemBond {
bond_account,
mint_account: mint,
issuance_account,
user_wallet,
user_nft_token_account,
user_payment_token_account,
payment_mint_account,
payment_feed_account,
nft_mint_account: nft_mint,
nft_metadata_account,
nft_master_edition_account,
nft_collection_metadata_account,
nft_issuance_vault_account,
nft_issuance_vault_token_account,
payout_account,
payout_token_account,
token2022_program,
associated_token_program,
token_program,
metadata_program,
system_program,
}
.instruction();
Ok(Transaction::new_unsigned(Message::new(
&[ix],
Some(&user_wallet),
)))
}
// Creates a transaction to redeem a purchase order
let client = RpcClient::new(rpc_url);
// Some user wallet keypair
let user_wallet = Keypair::from_str("user_wallet").unwrap();
// The mint for the bond underlying the NFT
let mint = Pubkey::from_str("mint").unwrap();
// The address of the NFT
let nft_mint = Pubkey::from_str("nft_mint").unwrap();
// Get an un-signed transaction
let tx = make_redeem_purchase_order_transaction(client, user_wallet, mint, nft_mint).await?;
// Sign the transaction with the users keypair and then...
// Submit the transaction
let sig = client.send_and_confirm_transaction(&tx).await?;
pub async fn make_redeem_purchase_order_transaction(
client: &RpcClient,
user_wallet: Pubkey,
mint: Pubkey,
nft_mint: Pubkey,
) -> Result<Transaction> {
let mint_auth_multisig_account = get_bond_mint_auth_multisig_if_needed(&client, mint).await?;
let bond_account = find_bond_pda(mint).0;
let data = client.get_account_data(&bond_account).await?;
let bond = Bond::from_bytes(&data).unwrap();
let issuance_account = find_issuance_pda(bond_account, bond.issuance_number).0;
let purchase_order_vault_account = find_purchase_order_pda(nft_mint).0;
let user_token_account =
get_associated_token_address_with_program_id(&user_wallet, &mint, &spl_token_2022::id());
let user_nft_token_account = get_associated_token_address(&user_wallet, &nft_mint);
let nft_metadata_account = Metadata::find_pda(&nft_mint).0;
let nft_master_edition_account = MasterEdition::find_pda(&nft_mint).0;
let nft_collection_metadata_account = Metadata::find_pda(&nft_mint).0;
let token2022_program = spl_token_2022::id();
let associated_token_program = spl_associated_token_account::id();
let token_program = spl_token::id();
let metadata_program = mpl_token_metadata::ID;
let system_program = system_program::id();
let ix = RedeemPurchaseOrder {
user_wallet,
bond_account,
mint_account: mint,
issuance_account,
purchase_order_vault_account,
user_token_account,
user_nft_token_account,
nft_mint_account: nft_mint,
nft_metadata_account,
nft_master_edition_account,
nft_collection_metadata_account,
token2022_program,
token_program,
associated_token_program,
metadata_program,
system_program,
mint_auth_multisig_account,
}
.instruction();
Ok(Transaction::new_unsigned(Message::new(
&[ix],
Some(&user_wallet),
)))
}
// Create a purchase order.
// The user will need to have already completed the KYC process for the transaction to be successful.
// See [API Reference](https://docs.etherfuse.com/api-reference/onboarding/generate-onboarding-url) for more information.
let client = RpcClient::new(rpc_url);
// Some user wallet keypair
let user_wallet = Keypair::from_str("user_wallet").unwrap();
// The mint for the bond underlying the NFT
let mint = Pubkey::from_str("mint").unwrap();
// The amount of tokens to purchase
let token_amount = 1000000;
// Get an un-signed transaction
let tx = make_create_purchase_order_transaction(client, token_amount, user_wallet, mint).await?;
// Sign the transaction with the users keypair and then...
// Submit the transaction
let sig = client.send_and_confirm_transaction(&tx).await?;
pub async fn make_create_purchase_order_transaction(
client: &RpcClient,
token_amount: u64,
user_wallet: Pubkey,
mint: Pubkey,
) -> Result<Transaction> {
let ix_args = CreatePurchaseOrderV2InstructionArgs {
amount: token_amount,
};
let addresses = generate_buy_addresses(client, mint, user_wallet).await?;
let nft_mint = Keypair::new();
let nft_mint_account = nft_mint.pubkey();
let user_nft_token_account = get_associated_token_address(&user_wallet, &nft_mint_account);
let nft_master_edition_account = MasterEdition::find_pda(&nft_mint_account).0;
let purchase_order_vault_account = find_purchase_order_pda(nft_mint_account).0;
let nft_collection_mint = find_config_pda().0;
let nft_collection_metadata_account = Metadata::find_pda(&nft_collection_mint).0;
let nft_collection_master_edition_account = MasterEdition::find_pda(&nft_collection_mint).0;
let nft_metadata_account = Metadata::find_pda(&nft_mint_account).0;
let ix = CreatePurchaseOrderV2 {
kyc_account: find_kyc_pda(user_wallet).0,
user_wallet,
bond_account: addresses.bond_account,
issuance_account: addresses.issuance_account,
payment_account: addresses.payment_account,
payment_token_account: addresses.payment_token_account,
user_payment_token_account: addresses.user_payment_token_account,
user_nft_token_account,
nft_mint_account,
nft_metadata_account,
nft_master_edition_account,
purchase_order_vault_account,
nft_collection_mint,
nft_collection_metadata_account,
nft_collection_master_edition_account,
payment_mint_account: addresses.payment_mint_account,
payment_feed_account: addresses.payment_feed_account,
payment_base_price_feed_account: addresses.payment_price_feed_account,
config_account: find_config_pda().0,
associated_token_program: spl_associated_token_account::id(),
token2022_program: spl_token_2022::id(),
token_program: spl_token::id(),
system_program: system_program::id(),
metadata_program: mpl_token_metadata::ID,
sysvar_instructions: solana_sdk::sysvar::instructions::id(),
payment_quote_price_feed_account: addresses.payment_quote_price_feed_account,
}
.instruction(ix_args);
Ok(Transaction::new_unsigned(Message::new(
&[ix],
Some(&user_wallet),
)))
}
async fn get_bond_mint_auth_multisig_if_needed(
rpc_client: &RpcClient,
mint_account: Pubkey,
) -> Result<Option<Pubkey>> {
let bond_account = find_bond_pda(mint_account).0;
let data = rpc_client.get_account_data(&bond_account).await?;
let bond = Bond::from_bytes(&data).unwrap();
// Get the mint_auth_multisig_account if is_authority_multisig is true
let mint_auth_multisig_account = if bond.is_authority_multisig {
let bond_multisig_meta_account = find_bond_multisig_meta_pda(mint_account).0;
let bond_multisig_meta_data = rpc_client
.get_account_data(&bond_multisig_meta_account)
.await?;
let bond_multisig_meta = BondMultisigMeta::from_bytes(&bond_multisig_meta_data).unwrap();
Some(bond_multisig_meta.mint_auth_multisig)
} else {
None
};
Ok(mint_auth_multisig_account)
}
struct BuyAddresses {
issuance_account: Pubkey,
payment_account: Pubkey,
payment_mint_account: Pubkey,
payment_token_account: Pubkey,
user_payment_token_account: Pubkey,
bond_account: Pubkey,
payment_price_feed_account: Pubkey,
payment_quote_price_feed_account: Option<Pubkey>,
kyc_account: Pubkey,
payment_feed_account: Pubkey,
user_token_account: Pubkey,
token_2022_program: Pubkey,
associated_token_program: Pubkey,
token_program: Pubkey,
system_program: Pubkey,
}
async fn generate_buy_addresses(
client: &RpcClient,
mint_account: Pubkey,
user_wallet: Pubkey,
) -> Result<BuyAddresses> {
let bond_account = find_bond_pda(mint_account).0;
let issuance_account = find_issuance_pda(bond_account, 1).0;
let payment_account = find_payment_pda(issuance_account).0;
let data = client.get_account_data(&bond_account).await?;
let bond = Bond::from_bytes(&data).unwrap();
let payment_feed_account = find_payment_feed_pda(bond.payment_feed_type).0;
let data = client.get_account_data(&payment_feed_account).await?;
let payment_feed = PaymentFeed::from_bytes(&data).unwrap();
let payment_mint_account = payment_feed.payment_mint;
let mut payment_quote_price_feed_account = None;
if payment_feed.quote_price_feed != Pubkey::default() {
payment_quote_price_feed_account = Some(payment_feed.quote_price_feed);
}
let user_token_account = get_associated_token_address_with_program_id(
&user_wallet,
&mint_account,
&spl_token_2022::id(),
);
let payment_price_feed_account = payment_feed.base_price_feed;
let kyc_account = find_kyc_pda(user_wallet).0;
let payment_token_account =
get_associated_token_address(&payment_account, &payment_mint_account);
let user_payment_token_account =
get_associated_token_address(&user_wallet, &payment_mint_account);
Ok(BuyAddresses {
issuance_account,
payment_account,
payment_mint_account,
payment_token_account,
user_payment_token_account,
bond_account,
payment_price_feed_account,
payment_quote_price_feed_account,
kyc_account,
payment_feed_account,
user_token_account,
token_2022_program: spl_token_2022::id(),
associated_token_program: spl_associated_token_account::id(),
token_program: spl_token::id(),
system_program: system_program::id(),
})
}
Dependencies
~26–48MB
~834K SLoC