diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 657a022bc..3680d6e5b 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -149,7 +149,7 @@ jobs: - tests/voter-stake-registry.ts - tests/fanout.ts - tests/sus.ts - - tests/dc-conversion-escrow.ts + - tests/conversion-escrow.ts steps: - uses: actions/checkout@v3 - uses: ./.github/actions/build-anchor/ diff --git a/Anchor.toml b/Anchor.toml index 40f649edf..fb47ac48e 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -17,7 +17,7 @@ fanout = "fanqeMu3fw8R4LwKNbahPtYXJsyLL6NXyfe2BqzhfB6" mobile_entity_manager = "memMa1HG4odAFmUbGWfPwS1WWfK95k99F2YTkGvyxZr" hexboosting = "hexbnKYoA2GercNNhHUCCfrTRWrHjT6ujKPXTa5NPqJ" no_emit = "noEmmgLmQdk6DLiPV8CSwQv3qQDyGEhz9m5A4zhtByv" -dc_conversion_escrow = "dce4jeLBpfaFsNAKMAmVt5Py4E1R4mZcrVvMB5ejvGu" +conversion_escrow = "dce4jeLBpfaFsNAKMAmVt5Py4E1R4mZcrVvMB5ejvGu" [workspace] members = [ @@ -35,7 +35,7 @@ members = [ "programs/mobile-entity-manager", "programs/hexboosting", "programs/no-emit", - "programs/dc-conversion-escrow", + "programs/conversion-escrow", ] [registry] diff --git a/Cargo.lock b/Cargo.lock index 889b89917..32db4cf29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1127,6 +1127,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "conversion-escrow" +version = "0.0.1" +dependencies = [ + "anchor-lang", + "anchor-spl", + "default-env", + "pyth-sdk-solana", + "shared-utils", + "solana-security-txt", + "spl-token", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -1354,19 +1367,6 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" -[[package]] -name = "dc-conversion-escrow" -version = "0.0.1" -dependencies = [ - "anchor-lang", - "anchor-spl", - "data-credits", - "default-env", - "pyth-sdk-solana", - "shared-utils", - "solana-security-txt", -] - [[package]] name = "default-env" version = "0.1.1" diff --git a/packages/dc-conversion-escrow-sdk/.gitignore b/packages/conversion-escrow-sdk/.gitignore similarity index 100% rename from packages/dc-conversion-escrow-sdk/.gitignore rename to packages/conversion-escrow-sdk/.gitignore diff --git a/packages/dc-conversion-escrow-sdk/CHANGELOG.md b/packages/conversion-escrow-sdk/CHANGELOG.md similarity index 100% rename from packages/dc-conversion-escrow-sdk/CHANGELOG.md rename to packages/conversion-escrow-sdk/CHANGELOG.md diff --git a/packages/dc-conversion-escrow-sdk/package.json b/packages/conversion-escrow-sdk/package.json similarity index 91% rename from packages/dc-conversion-escrow-sdk/package.json rename to packages/conversion-escrow-sdk/package.json index ac6c7efd5..775185296 100644 --- a/packages/dc-conversion-escrow-sdk/package.json +++ b/packages/conversion-escrow-sdk/package.json @@ -1,12 +1,12 @@ { - "name": "@helium/dc-conversion-escrow-sdk", + "name": "@helium/conversion-escrow-sdk", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }, "license": "Apache-2.0", "version": "0.6.41", - "description": "Interface to the dc-conversion-escrow smart contract", + "description": "Interface to the conversion-escrow smart contract", "repository": { "type": "git", "url": "https://github.com/helium/helium-program-libary" diff --git a/packages/dc-conversion-escrow-sdk/src/constants.ts b/packages/conversion-escrow-sdk/src/constants.ts similarity index 100% rename from packages/dc-conversion-escrow-sdk/src/constants.ts rename to packages/conversion-escrow-sdk/src/constants.ts diff --git a/packages/dc-conversion-escrow-sdk/src/index.ts b/packages/conversion-escrow-sdk/src/index.ts similarity index 53% rename from packages/dc-conversion-escrow-sdk/src/index.ts rename to packages/conversion-escrow-sdk/src/index.ts index d4c573551..c0e3c10a1 100644 --- a/packages/dc-conversion-escrow-sdk/src/index.ts +++ b/packages/conversion-escrow-sdk/src/index.ts @@ -1,8 +1,8 @@ -import { DcConversionEscrow } from "@helium/idls/lib/types/dc_conversion_escrow"; +import { ConversionEscrow } from "@helium/idls/lib/types/conversion_escrow"; import { AnchorProvider, Idl, Program } from "@coral-xyz/anchor"; import { PublicKey } from "@solana/web3.js"; import { PROGRAM_ID } from "./constants"; -import { dcConversionEscrowResolvers } from "./resolvers"; +import { conversionEscrowResolvers } from "./resolvers"; export * from "./constants"; @@ -12,18 +12,18 @@ export async function init( provider: AnchorProvider, programId: PublicKey = PROGRAM_ID, idl?: Idl | null -): Promise> { +): Promise> { if (!idl) { idl = await Program.fetchIdl(programId, provider); } - const dcConversionEscrow = new Program( - idl as DcConversionEscrow, + const conversionEscrow = new Program( + idl as ConversionEscrow, programId, provider, undefined, - () => dcConversionEscrowResolvers - ) as Program; + () => conversionEscrowResolvers + ) as Program; - return dcConversionEscrow; + return conversionEscrow; } diff --git a/packages/dc-conversion-escrow-sdk/src/pdas.ts b/packages/conversion-escrow-sdk/src/pdas.ts similarity index 100% rename from packages/dc-conversion-escrow-sdk/src/pdas.ts rename to packages/conversion-escrow-sdk/src/pdas.ts diff --git a/packages/dc-conversion-escrow-sdk/src/resolvers.ts b/packages/conversion-escrow-sdk/src/resolvers.ts similarity index 81% rename from packages/dc-conversion-escrow-sdk/src/resolvers.ts rename to packages/conversion-escrow-sdk/src/resolvers.ts index e70b62ce8..a94300555 100644 --- a/packages/dc-conversion-escrow-sdk/src/resolvers.ts +++ b/packages/conversion-escrow-sdk/src/resolvers.ts @@ -4,7 +4,7 @@ import { heliumCommonResolver } from "@helium/anchor-resolvers"; -export const dcConversionEscrowResolvers = combineResolvers( +export const conversionEscrowResolvers = combineResolvers( heliumCommonResolver, ataResolver({ instruction: "initializeEscrowV0", diff --git a/packages/dc-conversion-escrow-sdk/tsconfig.cjs.json b/packages/conversion-escrow-sdk/tsconfig.cjs.json similarity index 100% rename from packages/dc-conversion-escrow-sdk/tsconfig.cjs.json rename to packages/conversion-escrow-sdk/tsconfig.cjs.json diff --git a/packages/dc-conversion-escrow-sdk/tsconfig.esm.json b/packages/conversion-escrow-sdk/tsconfig.esm.json similarity index 100% rename from packages/dc-conversion-escrow-sdk/tsconfig.esm.json rename to packages/conversion-escrow-sdk/tsconfig.esm.json diff --git a/packages/dc-conversion-escrow-sdk/tsconfig.json b/packages/conversion-escrow-sdk/tsconfig.json similarity index 100% rename from packages/dc-conversion-escrow-sdk/tsconfig.json rename to packages/conversion-escrow-sdk/tsconfig.json diff --git a/packages/dc-conversion-escrow-sdk/yarn.deploy.lock b/packages/conversion-escrow-sdk/yarn.deploy.lock similarity index 99% rename from packages/dc-conversion-escrow-sdk/yarn.deploy.lock rename to packages/conversion-escrow-sdk/yarn.deploy.lock index f3c21c9c9..13f8204a9 100644 --- a/packages/dc-conversion-escrow-sdk/yarn.deploy.lock +++ b/packages/conversion-escrow-sdk/yarn.deploy.lock @@ -72,9 +72,9 @@ __metadata: languageName: unknown linkType: soft -"@helium/dc-conversion-escrow-sdk@workspace:.": +"@helium/conversion-escrow-sdk@workspace:.": version: 0.0.0-use.local - resolution: "@helium/dc-conversion-escrow-sdk@workspace:." + resolution: "@helium/conversion-escrow-sdk@workspace:." dependencies: "@coral-xyz/anchor": ^0.28.0 "@helium/anchor-resolvers": ^0.6.41 diff --git a/programs/dc-conversion-escrow/Cargo.toml b/programs/conversion-escrow/Cargo.toml similarity index 80% rename from programs/dc-conversion-escrow/Cargo.toml rename to programs/conversion-escrow/Cargo.toml index 6aed11a03..7eb9291bd 100644 --- a/programs/dc-conversion-escrow/Cargo.toml +++ b/programs/conversion-escrow/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "dc-conversion-escrow" +name = "conversion-escrow" version = "0.0.1" description = "Created with Anchor" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "dc_conversion_escrow" +name = "conversion_escrow" [features] devnet = [] @@ -23,8 +23,8 @@ overflow-checks = true [dependencies] anchor-lang = { workspace = true } anchor-spl = { workspace = true } +spl-token = "3.5.0" shared-utils = { workspace = true } solana-security-txt = { workspace = true } -data-credits = { path = "../data-credits", features = ["cpi"] } default-env = { workspace = true } pyth-sdk-solana = { version = "0.8.0" } diff --git a/programs/dc-conversion-escrow/Xargo.toml b/programs/conversion-escrow/Xargo.toml similarity index 100% rename from programs/dc-conversion-escrow/Xargo.toml rename to programs/conversion-escrow/Xargo.toml diff --git a/programs/dc-conversion-escrow/src/errors.rs b/programs/conversion-escrow/src/errors.rs similarity index 100% rename from programs/dc-conversion-escrow/src/errors.rs rename to programs/conversion-escrow/src/errors.rs diff --git a/programs/dc-conversion-escrow/src/instructions/initialize_escrow_v0.rs b/programs/conversion-escrow/src/instructions/initialize_escrow_v0.rs similarity index 62% rename from programs/dc-conversion-escrow/src/instructions/initialize_escrow_v0.rs rename to programs/conversion-escrow/src/instructions/initialize_escrow_v0.rs index 247c8f9e0..190e6ccd2 100644 --- a/programs/dc-conversion-escrow/src/instructions/initialize_escrow_v0.rs +++ b/programs/conversion-escrow/src/instructions/initialize_escrow_v0.rs @@ -1,22 +1,29 @@ -use crate::errors::ErrorCode; +use crate::ConversionTargetV0; use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, token::{Mint, Token, TokenAccount}, }; -use data_credits::DataCreditsV0; -use pyth_sdk_solana::load_price_feed_from_account_info; use crate::ConversionEscrowV0; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct InitializeEscrowArgsV0 { + pub oracle: Pubkey, + pub targets: Vec, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] +pub struct ConversionTargetArgV0 { + pub mint: Pubkey, + pub oracle: Pubkey, + /// How much slippage to allow from the oracle price pub slippage_bps: u16, } #[derive(Accounts)] +#[instruction(args: InitializeEscrowArgsV0)] pub struct InitializeEscrowV0<'info> { - pub data_credits: Box>, #[account(mut)] pub payer: Signer<'info>, /// CHECK: The owner of this account. Can fully withdraw @@ -24,9 +31,9 @@ pub struct InitializeEscrowV0<'info> { #[account( init, payer = payer, - seeds = [b"conversion_escrow", data_credits.key().as_ref(), mint.key().as_ref(), owner.key().as_ref()], + seeds = [b"conversion_escrow", mint.key().as_ref(), owner.key().as_ref()], bump, - space = ConversionEscrowV0::INIT_SPACE + 60, + space = std::mem::size_of::() + 60 + std::mem::size_of::() * args.targets.len(), )] pub conversion_escrow: Box>, pub mint: Box>, @@ -39,27 +46,28 @@ pub struct InitializeEscrowV0<'info> { pub escrow: Box>, pub system_program: Program<'info, System>, pub associated_token_program: Program<'info, AssociatedToken>, - /// CHECK: Checked with load_price_feed_from_account_info - pub oracle: AccountInfo<'info>, pub token_program: Program<'info, Token>, } pub fn handler(ctx: Context, args: InitializeEscrowArgsV0) -> Result<()> { - load_price_feed_from_account_info(&ctx.accounts.oracle).map_err(|e| { - msg!("Pyth error {}", e); - error!(ErrorCode::PythError) - })?; - ctx .accounts .conversion_escrow .set_inner(ConversionEscrowV0 { - oracle: ctx.accounts.oracle.key(), + oracle: args.oracle, escrow: ctx.accounts.escrow.key(), mint: ctx.accounts.mint.key(), - slipage_bps: args.slippage_bps, + targets: args + .targets + .iter() + .map(|t| ConversionTargetV0 { + reserverd: [0; 8], + mint: t.mint, + oracle: t.oracle, + slipage_bps: t.slippage_bps, + }) + .collect(), owner: ctx.accounts.owner.key(), - data_credits: ctx.accounts.data_credits.key(), bump_seed: ctx.bumps["conversion_escrow"], }); diff --git a/programs/conversion-escrow/src/instructions/lend_v0.rs b/programs/conversion-escrow/src/instructions/lend_v0.rs new file mode 100644 index 000000000..b838d5036 --- /dev/null +++ b/programs/conversion-escrow/src/instructions/lend_v0.rs @@ -0,0 +1,178 @@ +use crate::errors::ErrorCode; +use crate::{escrow_seeds, ConversionEscrowV0}; +use anchor_lang::prelude::*; +use anchor_lang::solana_program::sysvar; +use anchor_lang::solana_program::sysvar::instructions::{ + load_current_index_checked, load_instruction_at_checked, +}; +use anchor_spl::token::{self, transfer, Mint, Token, TokenAccount, Transfer}; +use pyth_sdk_solana::load_price_feed_from_account_info; +use spl_token::instruction::TokenInstruction; + +pub const TESTING: bool = std::option_env!("TESTING").is_some(); + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] +pub struct LendArgsV0 { + pub amount: u64, +} + +#[derive(Accounts)] +pub struct LendV0<'info> { + #[account( + has_one = mint, + has_one = oracle, + has_one = escrow, + )] + pub conversion_escrow: Box>, + #[account(mut)] + pub escrow: Account<'info, TokenAccount>, + /// CHECK: Checked via pyth + pub oracle: UncheckedAccount<'info>, + /// CHECK: Checked via pyth + #[account( + constraint = conversion_escrow.targets.iter().any(|t| t.oracle == target_oracle.key()) + )] + pub target_oracle: UncheckedAccount<'info>, + pub mint: Box>, + + #[account( + mut, + token::mint = mint + )] + pub destination: Box>, + #[account( + constraint = conversion_escrow.targets.iter().find(|t| t.oracle == target_oracle.key()).unwrap().mint == repay_account.mint, + constraint = repay_account.owner == conversion_escrow.owner + )] + pub repay_account: Box>, + /// CHECK: check instructions account + #[account(address = sysvar::instructions::ID @ErrorCode::BadInstructionsAccount)] + pub instructions: UncheckedAccount<'info>, + pub token_program: Program<'info, Token>, +} + +pub fn handler(ctx: Context, args: LendArgsV0) -> Result<()> { + let ixs = ctx.accounts.instructions.to_account_info(); + + let price_feed = load_price_feed_from_account_info(&ctx.accounts.oracle).map_err(|e| { + msg!("Pyth error {}", e); + error!(ErrorCode::PythError) + })?; + + let current_time = Clock::get()?.unix_timestamp; + let source_price = price_feed + .get_ema_price_no_older_than(current_time, if TESTING { 6000000 } else { 10 * 60 }) + .ok_or_else(|| error!(ErrorCode::PythPriceNotFound))?; + // Remove the confidence from the price to use the most conservative price + // https://docs.pyth.network/price-feeds/solana-price-feeds/best-practices#confidence-intervals + let source_price_with_conf = u64::try_from(source_price.price) + .unwrap() + .checked_sub(source_price.conf.checked_mul(2).unwrap()) + .unwrap(); + + require_gt!(source_price_with_conf, 0); + + let target_price_oracle = load_price_feed_from_account_info(&ctx.accounts.target_oracle) + .map_err(|e| { + msg!("Pyth error {}", e); + error!(ErrorCode::PythError) + })?; + + let target_price = target_price_oracle + .get_ema_price_no_older_than(current_time, if TESTING { 6000000 } else { 10 * 60 }) + .ok_or_else(|| error!(ErrorCode::PythPriceNotFound))?; + + require_gt!(target_price.price, 0); + + // Remove the confidence from the price to use the most conservative price + // https://docs.pyth.network/price-feeds/solana-price-feeds/best-practices#confidence-intervals + let target_price_with_conf = u64::try_from(target_price.price) + .unwrap() + .checked_sub(target_price.conf.checked_mul(2).unwrap()) + .unwrap(); + + // USD/Sorce divided by USD/Target gets us Target/Source, in other words how much target + // we expect to be repaid per source. + let target_per_source = source_price_with_conf + .checked_div(target_price_with_conf) + .unwrap(); + let expo_diff = source_price.expo - target_price.expo; + let expected_repayment_amount = if expo_diff > 0 { + // Target has more decimals than source, need to multiply + args + .amount + .checked_mul(10_u64.pow(u32::try_from(expo_diff.abs()).unwrap())) + .unwrap() + .checked_mul(target_per_source) + .unwrap() + } else if expo_diff < 0 { + // Target has less decimals than source, need to divide + args + .amount + .checked_mul(target_per_source) + .unwrap() + .checked_div(10_u64.pow(u32::try_from(expo_diff.abs()).unwrap())) + .unwrap() + } else { + // Same decimals + args.amount.checked_mul(target_per_source).unwrap() + }; + let target = ctx + .accounts + .conversion_escrow + .targets + .iter() + .find(|target| target.oracle == ctx.accounts.target_oracle.key()) + .unwrap(); + let expected_repayment_amount_with_slippage = expected_repayment_amount + - (expected_repayment_amount + .checked_mul(u64::from(target.slipage_bps)) + .unwrap() + .checked_div(10000) + .unwrap()); + + // make sure this isnt a cpi call + let current_index = load_current_index_checked(&ixs)? as usize; + // loop through instructions, looking for an equivalent mint dc to this borrow + let mut index = current_index + 1; // jupiter swap + loop { + // get the next instruction, die if theres no more + if let Ok(ix) = load_instruction_at_checked(index, &ixs) { + if ix.program_id == token::ID { + let transfer_data = match TokenInstruction::unpack(&ix.data)? { + TokenInstruction::Transfer { amount } => Some((amount, ix.accounts[1].pubkey)), + TokenInstruction::TransferChecked { amount, .. } => Some((amount, ix.accounts[2].pubkey)), + _ => None, + }; + + if let Some((amount, account)) = transfer_data { + if ctx.accounts.repay_account.key() == account { + require_gt!(amount, expected_repayment_amount_with_slippage); + break; + } + } + } + } else { + // no more instructions, so we're missing a repay + return Err(ErrorCode::MissingRepay.into()); + } + + index += 1 + } + + // Send the loan + transfer( + CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + Transfer { + from: ctx.accounts.escrow.to_account_info(), + to: ctx.accounts.destination.to_account_info(), + authority: ctx.accounts.conversion_escrow.to_account_info(), + }, + &[escrow_seeds!(ctx.accounts.conversion_escrow)], + ), + args.amount, + )?; + + Ok(()) +} diff --git a/programs/dc-conversion-escrow/src/instructions/mod.rs b/programs/conversion-escrow/src/instructions/mod.rs similarity index 100% rename from programs/dc-conversion-escrow/src/instructions/mod.rs rename to programs/conversion-escrow/src/instructions/mod.rs diff --git a/programs/dc-conversion-escrow/src/lib.rs b/programs/conversion-escrow/src/lib.rs similarity index 91% rename from programs/dc-conversion-escrow/src/lib.rs rename to programs/conversion-escrow/src/lib.rs index dfc7568ab..f9583db81 100644 --- a/programs/dc-conversion-escrow/src/lib.rs +++ b/programs/conversion-escrow/src/lib.rs @@ -13,7 +13,7 @@ pub use state::*; #[cfg(not(feature = "no-entrypoint"))] security_txt! { - name: "DC Conversion Escrow", + name: "Conversion Escrow", project_url: "http://helium.com", contacts: "email:hello@helium.foundation", policy: "https://github.com/helium/helium-program-library/tree/master/SECURITY.md", @@ -21,14 +21,14 @@ security_txt! { // Optional Fields preferred_languages: "en", - source_code: "https://github.com/helium/helium-program-library/tree/master/programs/dc-conversion-escrow", + source_code: "https://github.com/helium/helium-program-library/tree/master/programs/conversion-escrow", source_revision: default_env!("GITHUB_SHA", ""), source_release: default_env!("GITHUB_REF_NAME", ""), auditors: "Sec3" } #[program] -pub mod dc_conversion_escrow { +pub mod conversion_escrow { use super::*; pub fn initialize_escrow_v0( diff --git a/programs/dc-conversion-escrow/src/state.rs b/programs/conversion-escrow/src/state.rs similarity index 67% rename from programs/dc-conversion-escrow/src/state.rs rename to programs/conversion-escrow/src/state.rs index 8977aead4..213009d96 100644 --- a/programs/dc-conversion-escrow/src/state.rs +++ b/programs/conversion-escrow/src/state.rs @@ -1,24 +1,30 @@ use anchor_lang::prelude::*; #[account] -#[derive(Default, InitSpace)] +#[derive(Default)] pub struct ConversionEscrowV0 { pub escrow: Pubkey, pub mint: Pubkey, - /// How much slippage to allow from the oracle price - pub slipage_bps: u16, pub oracle: Pubkey, pub owner: Pubkey, - pub data_credits: Pubkey, + pub targets: Vec, pub bump_seed: u8, } +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] +pub struct ConversionTargetV0 { + pub mint: Pubkey, + pub oracle: Pubkey, + /// How much slippage to allow from the oracle price + pub slipage_bps: u16, + pub reserverd: [u64; 8], +} + #[macro_export] macro_rules! escrow_seeds { ( $escrow:expr ) => { &[ "conversion_escrow".as_bytes(), - $escrow.data_credits.as_ref(), $escrow.mint.as_ref(), $escrow.owner.as_ref(), &[$escrow.bump_seed], diff --git a/programs/dc-conversion-escrow/src/instructions/lend_v0.rs b/programs/dc-conversion-escrow/src/instructions/lend_v0.rs deleted file mode 100644 index 45ad67ae9..000000000 --- a/programs/dc-conversion-escrow/src/instructions/lend_v0.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::ops::Div; - -use crate::errors::ErrorCode; -use crate::{escrow_seeds, ConversionEscrowV0}; -use anchor_lang::prelude::*; -use anchor_lang::solana_program::sysvar; -use anchor_lang::solana_program::sysvar::instructions::{ - load_current_index_checked, load_instruction_at_checked, -}; -use anchor_spl::token::{transfer, Mint, Token, TokenAccount, Transfer}; -use data_credits::{DataCreditsV0, MintDataCreditsArgsV0, TESTING}; -use pyth_sdk_solana::load_price_feed_from_account_info; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] -pub struct LendArgsV0 { - pub amount: u64, -} - -#[derive(Accounts)] -pub struct LendV0<'info> { - #[account( - has_one = mint, - has_one = oracle, - has_one = escrow, - has_one = data_credits, - )] - pub conversion_escrow: Box>, - #[account(mut)] - pub escrow: Account<'info, TokenAccount>, - /// CHECK: Checked via pyth - pub oracle: UncheckedAccount<'info>, - pub mint: Box>, - #[account( - has_one = hnt_price_oracle, - )] - pub data_credits: Box>, - - /// CHECK: Checked by loading with pyth. Also double checked by the has_one on data credits instance. - pub hnt_price_oracle: AccountInfo<'info>, - - #[account( - mut, - token::mint = mint - )] - pub destination: Box>, - /// CHECK: check instructions account - #[account(address = sysvar::instructions::ID @ErrorCode::BadInstructionsAccount)] - pub instructions: UncheckedAccount<'info>, - pub token_program: Program<'info, Token>, -} - -pub fn handler(ctx: Context, args: LendArgsV0) -> Result<()> { - let ixs = ctx.accounts.instructions.to_account_info(); - - let price_feed = load_price_feed_from_account_info(&ctx.accounts.oracle).map_err(|e| { - msg!("Pyth error {}", e); - error!(ErrorCode::PythError) - })?; - - let current_time = Clock::get()?.unix_timestamp; - let price = price_feed - .get_ema_price_no_older_than(current_time, if TESTING { 6000000 } else { 10 * 60 }) - .ok_or_else(|| error!(ErrorCode::PythPriceNotFound))?; - // Remove the confidence from the price to use the most conservative price - // https://docs.pyth.network/price-feeds/solana-price-feeds/best-practices#confidence-intervals - let price_with_conf = price - .price - .checked_sub(i64::try_from(price.conf.checked_mul(2).unwrap()).unwrap()) - .unwrap(); - // Exponent is a negative number, likely -8 - // Since DC is 5 decimals, this is likely -8, we need to divide by 10^(-expo - 5) - let exponent_dec = 10_i64 - .checked_pow(u32::try_from(-price.expo - 5).unwrap()) - .ok_or_else(|| error!(ErrorCode::ArithmeticError))?; - - require_gt!(price_with_conf, 0); - let expected_dc: u64 = price_with_conf - .checked_div(exponent_dec) - .unwrap() - .try_into() - .unwrap(); - - let hnt_price_oracle = load_price_feed_from_account_info(&ctx.accounts.hnt_price_oracle) - .map_err(|e| { - msg!("Pyth error {}", e); - error!(ErrorCode::PythError) - })?; - - let hnt_price = hnt_price_oracle - .get_ema_price_no_older_than(current_time, if TESTING { 6000000 } else { 10 * 60 }) - .ok_or_else(|| error!(ErrorCode::PythPriceNotFound))?; - - require_gt!(hnt_price.price, 0); - // Remove the confidence from the price to use the most conservative price - // https://docs.pyth.network/price-feeds/solana-price-feeds/best-practices#confidence-intervals - let hnt_price_with_conf = hnt_price - .price - .checked_sub(i64::try_from(hnt_price.conf.checked_mul(2).unwrap()).unwrap()) - .unwrap(); - // dc_exponent = 5 since $1 = 10^5 DC - // expo is a negative number, i.e. normally -8 for 8 hnt decimals - // dc = (price * 10^expo) * (hnt_amount * 10^-hnt_decimals) * 10^dc_exponent - // dc = price * hnt_amount * 10^(expo - hnt_decimals + dc_exponent) - // dc = price * hnt_amount / 10^(hnt_decimals - expo - dc_exponent) - // hnt_amount = dc * 10^(hnt_decimals - expo - dc_exponent) / price - let exponent = i32::from(8) - hnt_price.expo - 5; - let decimals_factor = 10_u128 - .checked_pow(u32::try_from(exponent).unwrap()) - .ok_or_else(|| error!(ErrorCode::ArithmeticError))?; - - // make sure this isnt a cpi call - let current_index = load_current_index_checked(&ixs)? as usize; - // loop through instructions, looking for an equivalent mint dc to this borrow - let mut index = current_index + 1; // jupiter swap - let discriminator = get_function_hash("global", "mint_data_credits_v0"); - loop { - // get the next instruction, die if theres no more - if let Ok(ix) = load_instruction_at_checked(index, &ixs) { - if ix.program_id == data_credits::id() { - let ix_discriminator: [u8; 8] = ix.data[0..8] - .try_into() - .map_err(|_| ErrorCode::UnknownInstruction)?; - - // check if we have a toplevel repay toward the program authority - if ix_discriminator == discriminator { - require_keys_eq!( - ix.accounts[4].pubkey, - ctx.accounts.conversion_escrow.owner, - ErrorCode::IncorrectDestination - ); - require_keys_eq!( - ix.accounts[0].pubkey, - ctx.accounts.data_credits.key(), - ErrorCode::IncorrectDc - ); - - let mint_dc_args: MintDataCreditsArgsV0 = - MintDataCreditsArgsV0::deserialize(&mut &ix.data[8..]).unwrap(); - let hnt_amount = mint_dc_args - .hnt_amount - .ok_or_else(|| error!(ErrorCode::HntAmountRequired))?; - let dc_amount_actual = u64::try_from( - u128::from(hnt_amount) - .checked_mul(u128::try_from(hnt_price_with_conf).unwrap()) - .ok_or_else(|| error!(ErrorCode::ArithmeticError))? - .div(decimals_factor), - ) - .map_err(|_| error!(ErrorCode::ArithmeticError))?; - let expected_dc_with_slippage = expected_dc - - (expected_dc * u64::from(ctx.accounts.conversion_escrow.slipage_bps / 10000)); - require_gt!(dc_amount_actual, expected_dc_with_slippage); - - break; - } - } - } else { - // no more instructions, so we're missing a repay - return Err(ErrorCode::MissingRepay.into()); - } - - index += 1 - } - - // Send the loan - transfer( - CpiContext::new_with_signer( - ctx.accounts.token_program.to_account_info(), - Transfer { - from: ctx.accounts.escrow.to_account_info(), - to: ctx.accounts.destination.to_account_info(), - authority: ctx.accounts.conversion_escrow.to_account_info(), - }, - &[escrow_seeds!(ctx.accounts.conversion_escrow)], - ), - args.amount, - )?; - - Ok(()) -} - -pub fn get_function_hash(namespace: &str, name: &str) -> [u8; 8] { - let preimage = format!("{}:{}", namespace, name); - let mut sighash = [0u8; 8]; - sighash - .copy_from_slice(&anchor_lang::solana_program::hash::hash(preimage.as_bytes()).to_bytes()[..8]); - sighash -} diff --git a/tests/conversion-escrow.ts b/tests/conversion-escrow.ts new file mode 100644 index 000000000..271aa8c0f --- /dev/null +++ b/tests/conversion-escrow.ts @@ -0,0 +1,181 @@ +import * as anchor from "@coral-xyz/anchor"; +import { Program } from "@coral-xyz/anchor"; +import { createAtaAndMint, createMint, sendInstructions, toBN, toNumber } from "@helium/spl-utils"; +import { parsePriceData } from "@pythnetwork/client"; +import { + createAssociatedTokenAccountIdempotentInstruction, + createTransferInstruction, + getAssociatedTokenAddressSync +} from "@solana/spl-token"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { expect } from "chai"; +import { init } from "../packages/conversion-escrow-sdk/src"; +import { PROGRAM_ID } from "../packages/conversion-escrow-sdk/src/constants"; +import { ConversionEscrow } from "../target/types/conversion_escrow"; +import { ensureDcEscrowIdl } from "./utils/fixtures"; + +describe("conversion-escrow", () => { + anchor.setProvider(anchor.AnchorProvider.local("http://127.0.0.1:8899")); + + const mobileOracle = new PublicKey("JBaTytFv1CmGNkyNiLu16jFMXNZ49BGfy4bYAYZdkxg5") + const hntOracle = new PublicKey("7moA1i5vQUpfDwSpK6Pw9s56ahB7WFGidtbL2ujWrVvm") + let program: Program; + let mobileMint: PublicKey; + let hntMint: PublicKey; + let startHntBal = 10000; + let startMobileEscrowBal = 100000000; + let hntDecimals = 8; + let mobileDecimals = 6; + const provider = anchor.getProvider() as anchor.AnchorProvider; + const me = provider.wallet.publicKey; + const hntHolder = Keypair.generate(); + + beforeEach(async () => { + program = await init( + provider, + PROGRAM_ID, + anchor.workspace.ConversionEscrow.idl + ) + // fresh start + mobileMint = await createMint(provider, mobileDecimals, me, me); + hntMint = await createMint(provider, hntDecimals, me, me); + await createAtaAndMint( + provider, + hntMint, + toBN(startHntBal, hntDecimals).toNumber(), + hntHolder.publicKey + ); + }); + + describe("with data credits and escrow", async () => { + let conversionEscrow: PublicKey | undefined; + + beforeEach(async () => { + await ensureDcEscrowIdl(program); + + ({ + pubkeys: { conversionEscrow }, + } = await program.methods + .initializeEscrowV0({ + oracle: mobileOracle, + // Slippage can be super low since price isn't changing. + targets: [ + { + mint: hntMint, + slippageBps: 1, + oracle: hntOracle, + }, + ], + }) + .accounts({ + owner: me, + payer: me, + mint: mobileMint, + }) + .rpcAndKeys({ skipPreflight: true })); + + // Populate the escrow + await createAtaAndMint( + provider, + mobileMint, + toBN(startMobileEscrowBal, mobileDecimals).toNumber(), + conversionEscrow + ); + }); + + it("lends MOBILE in exchange for HNT", async () => { + // Lend enough MOBILE to `hntHolder` to get 40000 DC. HNT holder + // will then burn enough HNT to get that DC into `owner` + const pythDataMobile = (await provider.connection.getAccountInfo( + mobileOracle + ))!.data; + const priceMobile = parsePriceData(pythDataMobile); + + const mobileFloorValue = + priceMobile.emaPrice.value - priceMobile.emaConfidence!.value * 2; + + const pythData = (await provider.connection.getAccountInfo(hntOracle))! + .data; + const priceHnt = parsePriceData(pythData); + + const hntFloorValue = + priceHnt.emaPrice.value - priceHnt.emaConfidence!.value * 2; + const hntPerMobile = mobileFloorValue / hntFloorValue + + const hntHolderMobileAta = getAssociatedTokenAddressSync( + mobileMint, + hntHolder.publicKey + ); + const hntHolderHntAta = getAssociatedTokenAddressSync( + hntMint, + hntHolder.publicKey + ); + const mobileAmount = new anchor.BN(5000) + const instructions = [ + createAssociatedTokenAccountIdempotentInstruction( + me, + hntHolderMobileAta, + hntHolder.publicKey, + mobileMint + ), + createAssociatedTokenAccountIdempotentInstruction( + me, + getAssociatedTokenAddressSync(hntMint, me), + me, + hntMint + ), + await program.methods + .lendV0({ + // Goal: get 5000 MOBILE worth of HNT + amount: mobileAmount, + }) + .accounts({ + conversionEscrow, + destination: hntHolderMobileAta, + targetOracle: hntOracle, + repayAccount: getAssociatedTokenAddressSync(hntMint, me), + }) + .instruction(), + createTransferInstruction( + hntHolderHntAta, + getAssociatedTokenAddressSync(hntMint, me), + hntHolder.publicKey, + BigInt(toBN(hntPerMobile * 5000, 8).toString()) + ), + ]; + + await sendInstructions(provider, instructions, [hntHolder]); + + const hntAta = await getAssociatedTokenAddressSync( + hntMint, + hntHolder.publicKey + ); + const mobileAta = await getAssociatedTokenAddressSync( + mobileMint, + conversionEscrow!, + true + ); + const mobileBal = await provider.connection.getTokenAccountBalance( + mobileAta + ); + const hntBal = await provider.connection.getTokenAccountBalance(hntAta); + expect(mobileBal.value.uiAmount).to.eq( + // Ensure matching decimals amounts + toNumber( + toBN( + startMobileEscrowBal - toNumber(mobileAmount, mobileDecimals), + mobileDecimals + ), + mobileDecimals + ) + ); + expect(hntBal.value.uiAmount).to.eq( + // Ensure matching decimals amounts + toNumber( + toBN(startHntBal - 5000 * hntPerMobile, hntDecimals), + hntDecimals + ) + ); + }); + }); +}); diff --git a/tests/dc-conversion-escrow.ts b/tests/dc-conversion-escrow.ts deleted file mode 100644 index 833e617e3..000000000 --- a/tests/dc-conversion-escrow.ts +++ /dev/null @@ -1,293 +0,0 @@ -import * as anchor from "@coral-xyz/anchor"; -import { Program } from "@coral-xyz/anchor"; -import { VoterStakeRegistry } from "@helium/idls/lib/types/voter_stake_registry"; -import { createAtaAndMint, createMint, sendInstructions } from "@helium/spl-utils"; -import { parsePriceData } from "@pythnetwork/client"; -import { - createAssociatedTokenAccountIdempotentInstruction, - getAssociatedTokenAddressSync -} from "@solana/spl-token"; -import { Keypair, PublicKey } from "@solana/web3.js"; -import BN from "bn.js"; -import { expect } from "chai"; -import { ThresholdType } from "../packages/circuit-breaker-sdk/src"; -import { - dataCreditsKey, - init as initDc -} from "../packages/data-credits-sdk/src"; -import { PROGRAM_ID as DC_PROGRAM_ID } from "../packages/data-credits-sdk/src/constants"; -import { init } from "../packages/dc-conversion-escrow-sdk/src"; -import { PROGRAM_ID } from "../packages/dc-conversion-escrow-sdk/src/constants"; -import * as hsd from "../packages/helium-sub-daos-sdk/src"; -import { daoKey } from "../packages/helium-sub-daos-sdk/src"; -import { toBN, toNumber } from "../packages/spl-utils/src"; -import * as vsr from "../packages/voter-stake-registry-sdk/src"; -import { DataCredits } from "../target/types/data_credits"; -import { DcConversionEscrow } from "../target/types/dc_conversion_escrow"; -import { HeliumSubDaos } from "../target/types/helium_sub_daos"; -import { ensureDCIdl, ensureDcEscrowIdl, ensureHSDIdl, ensureVSRIdl } from "./utils/fixtures"; -import { initVsr } from "./utils/vsr"; - -const EPOCH_REWARDS = 100000000; - -describe("dc-conversion-escrow", () => { - anchor.setProvider(anchor.AnchorProvider.local("http://127.0.0.1:8899")); - - const mobileOracle = new PublicKey("JBaTytFv1CmGNkyNiLu16jFMXNZ49BGfy4bYAYZdkxg5") - const hntOracle = new PublicKey("7moA1i5vQUpfDwSpK6Pw9s56ahB7WFGidtbL2ujWrVvm") - let program: Program; - let dcProgram: Program; - let hsdProgram: Program; - let vsrProgram: Program; - let dcKey: PublicKey; - let mobileMint: PublicKey; - let hntMint: PublicKey; - let dcMint: PublicKey; - let startHntBal = 10000; - let startMobileEscrowBal = 100000000; - let hntDecimals = 8; - let mobileDecimals = 6; - let dcDecimals = 0; - const provider = anchor.getProvider() as anchor.AnchorProvider; - const me = provider.wallet.publicKey; - const hntHolder = Keypair.generate(); - - beforeEach(async () => { - program = await init( - provider, - PROGRAM_ID, - anchor.workspace.DcConversionEscrow.idl - ) - dcProgram = await initDc( - provider, - DC_PROGRAM_ID, - anchor.workspace.DataCredits.idl - ); - hsdProgram = await hsd.init( - provider, - hsd.PROGRAM_ID, - anchor.workspace.HeliumSubDaos.idl - ); - vsrProgram = await vsr.init( - provider, - vsr.PROGRAM_ID, - anchor.workspace.VoterStakeRegistry.idl - ); - await ensureVSRIdl(vsrProgram); - // fresh start - mobileMint = await createMint(provider, mobileDecimals, me, me); - hntMint = await createMint(provider, hntDecimals, me, me); - dcMint = await createMint(provider, dcDecimals, me, me); - await createAtaAndMint( - provider, - hntMint, - toBN(startHntBal, hntDecimals).toNumber(), - hntHolder.publicKey - ); - - const method = await dcProgram.methods - .initializeDataCreditsV0({ - authority: me, - config: { - windowSizeSeconds: new BN(60), - thresholdType: ThresholdType.Absolute as never, - threshold: new BN("10000000000000000000"), - }, - }) - .accounts({ - hntMint, - dcMint, - payer: me, - hntPriceOracle: new PublicKey( - "7moA1i5vQUpfDwSpK6Pw9s56ahB7WFGidtbL2ujWrVvm" - ), - }); - dcKey = (await method.pubkeys()).dataCredits!; - await method.rpc({ - skipPreflight: true, - }); - }); - - describe("with data credits and escrow", async () => { - let dao: PublicKey; - let conversionEscrow: PublicKey | undefined; - - beforeEach(async () => { - const registrar = ( - await initVsr( - vsrProgram, - provider, - provider.wallet.publicKey, - hntMint, - daoKey(hntMint)[0] - ) - ).registrar; - const method = await hsdProgram.methods - .initializeDaoV0({ - authority: me, - registrar, - netEmissionsCap: toBN(34.24, 8), - hstEmissionSchedule: [ - { - startUnixTime: new anchor.BN(0), - percent: 32, - }, - ], - emissionSchedule: [ - { - startUnixTime: new anchor.BN(0), - emissionsPerEpoch: new BN(EPOCH_REWARDS), - }, - ], - }) - .preInstructions([ - createAssociatedTokenAccountIdempotentInstruction( - me, - getAssociatedTokenAddressSync(hntMint, me), - me, - hntMint - ), - ]) - .accounts({ - dcMint, - hntMint, - hstPool: getAssociatedTokenAddressSync(hntMint, me), - }); - await ensureHSDIdl(hsdProgram); - await ensureDcEscrowIdl(program); - await ensureDCIdl(dcProgram); - - dao = (await method.pubkeys()).dao!; - if (!(await provider.connection.getAccountInfo(dao))) { - await method.rpc({ skipPreflight: true }); - } - console.log("start"); - - ({ - pubkeys: { conversionEscrow }, - } = await program.methods - .initializeEscrowV0({ - // Slippage can be super low since price isn't changing. - slippageBps: 1, - }) - .accounts({ - owner: me, - payer: me, - mint: mobileMint, - oracle: mobileOracle, - dataCredits: dataCreditsKey(dcMint)[0], - }) - .rpcAndKeys({ skipPreflight: true })); - console.log("done"); - - // Populate the escrow - await createAtaAndMint( - provider, - mobileMint, - toBN(startMobileEscrowBal, mobileDecimals).toNumber(), - conversionEscrow - ); - }); - - it("lends MOBILE to mint DC to owner", async () => { - // Lend enough MOBILE to `hntHolder` to get 40000 DC. HNT holder - // will then burn enough HNT to get that DC into `owner` - const pythDataMobile = (await provider.connection.getAccountInfo( - mobileOracle - ))!.data; - const priceMobile = parsePriceData(pythDataMobile); - - const mobileFloorValueInDc = Math.floor( - (priceMobile.emaPrice.value - priceMobile.emaConfidence!.value * 2) * - 10 ** 5 - ); - - const pythData = (await provider.connection.getAccountInfo(hntOracle))! - .data; - const priceHnt = parsePriceData(pythData); - - const hntFloorValueInDc = Math.floor( - (priceHnt.emaPrice.value - priceHnt.emaConfidence!.value * 2) * 10 ** 5 - ); - - const hntHolderMobileAta = getAssociatedTokenAddressSync( - mobileMint, - hntHolder.publicKey - ); - const mobileAmount = toBN(40000 / mobileFloorValueInDc, mobileDecimals); - - const instructions = [ - createAssociatedTokenAccountIdempotentInstruction( - me, - hntHolderMobileAta, - hntHolder.publicKey, - mobileMint - ), - createAssociatedTokenAccountIdempotentInstruction( - me, - getAssociatedTokenAddressSync(dcMint, me), - me, - dcMint - ), - await program.methods - .lendV0({ - // Goal: get 4000 DC - amount: mobileAmount, - }) - .accounts({ - conversionEscrow, - destination: hntHolderMobileAta, - }) - .instruction(), - await dcProgram.methods - .mintDataCreditsV0({ - hntAmount: toBN(40000 / hntFloorValueInDc, 8), - dcAmount: null, - }) - .accounts({ - dcMint, - owner: hntHolder.publicKey, - recipient: me, - }) - .instruction(), - ]; - - await sendInstructions(provider, instructions, [hntHolder]); - - const dcAta = await getAssociatedTokenAddressSync(dcMint, me); - const hntAta = await getAssociatedTokenAddressSync( - hntMint, - hntHolder.publicKey - ); - const mobileAta = await getAssociatedTokenAddressSync( - mobileMint, - conversionEscrow!, - true - ); - const dcBal = await provider.connection.getTokenAccountBalance(dcAta); - const mobileBal = await provider.connection.getTokenAccountBalance( - mobileAta - ); - const hntBal = await provider.connection.getTokenAccountBalance(hntAta); - expect(dcBal.value.uiAmount).to.eq(40000); - console.log("bal", mobileBal.value.uiAmount); - expect(mobileBal.value.uiAmount).to.eq( - // Ensure matching decimals amounts - toNumber( - toBN( - startMobileEscrowBal - toNumber(mobileAmount, mobileDecimals), - mobileDecimals - ), - mobileDecimals - ) - ); - expect(hntBal.value.uiAmount).to.eq( - // Ensure matching decimals amounts - toNumber( - toBN(startHntBal - 40000 / hntFloorValueInDc, hntDecimals), - hntDecimals - ) - ); - }); - }); -}); diff --git a/tests/utils/fixtures.ts b/tests/utils/fixtures.ts index 6b15871b4..f32bda828 100644 --- a/tests/utils/fixtures.ts +++ b/tests/utils/fixtures.ts @@ -23,7 +23,7 @@ import { HeliumSubDaos } from "../../target/types/helium_sub_daos"; import { LazyDistributor } from "../../target/types/lazy_distributor"; import { initTestDao, initTestSubdao } from "./daos"; import { random } from "./string"; -import { DcConversionEscrow } from "../../target/types/dc_conversion_escrow"; +import { ConversionEscrow } from "../../target/types/conversion_escrow"; const ANCHOR_PATH = process.env.ANCHOR_PATH || 'anchor' @@ -286,15 +286,15 @@ export async function ensureHSDIdl(hsdProgram: Program) { } } -export async function ensureDcEscrowIdl(dcEscrowProgram: Program) { +export async function ensureDcEscrowIdl(dcEscrowProgram: Program) { try { execSync( - `${ANCHOR_PATH} idl init --filepath ${__dirname}/../../target/idl/dc_conversion_escrow.json ${dcEscrowProgram.programId}`, + `${ANCHOR_PATH} idl init --filepath ${__dirname}/../../target/idl/conversion_escrow.json ${dcEscrowProgram.programId}`, { stdio: "inherit", shell: "/bin/bash" } ); } catch { execSync( - `${ANCHOR_PATH} idl upgrade --filepath ${__dirname}/../../target/idl/dc_conversion_escrow.json ${dcEscrowProgram.programId}`, + `${ANCHOR_PATH} idl upgrade --filepath ${__dirname}/../../target/idl/conversion_escrow.json ${dcEscrowProgram.programId}`, { stdio: "inherit", shell: "/bin/bash" } ); } diff --git a/tsconfig.json b/tsconfig.json index 95fab9220..ef3d8cd43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "path": "./packages/sus" }, { - "path": "./packages/dc-conversion-escrow-sdk" + "path": "./packages/conversion-escrow-sdk" }, { "path": "./packages/hexboosting-sdk" diff --git a/yarn.lock b/yarn.lock index ce4462ecf..14b5ad1a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -675,6 +675,22 @@ __metadata: languageName: unknown linkType: soft +"@helium/conversion-escrow-sdk@workspace:packages/conversion-escrow-sdk": + version: 0.0.0-use.local + resolution: "@helium/conversion-escrow-sdk@workspace:packages/conversion-escrow-sdk" + dependencies: + "@coral-xyz/anchor": ^0.28.0 + "@helium/anchor-resolvers": ^0.6.41 + "@helium/idls": ^0.6.41 + bn.js: ^5.2.0 + bs58: ^4.0.1 + git-format-staged: ^2.1.3 + ts-loader: ^9.2.3 + ts-node: ^10.9.1 + typescript: ^5.2.2 + languageName: unknown + linkType: soft + "@helium/crons@workspace:packages/crons": version: 0.0.0-use.local resolution: "@helium/crons@workspace:packages/crons" @@ -766,22 +782,6 @@ __metadata: languageName: unknown linkType: soft -"@helium/dc-conversion-escrow-sdk@workspace:packages/dc-conversion-escrow-sdk": - version: 0.0.0-use.local - resolution: "@helium/dc-conversion-escrow-sdk@workspace:packages/dc-conversion-escrow-sdk" - dependencies: - "@coral-xyz/anchor": ^0.28.0 - "@helium/anchor-resolvers": ^0.6.41 - "@helium/idls": ^0.6.41 - bn.js: ^5.2.0 - bs58: ^4.0.1 - git-format-staged: ^2.1.3 - ts-loader: ^9.2.3 - ts-node: ^10.9.1 - typescript: ^5.2.2 - languageName: unknown - linkType: soft - "@helium/distributor-oracle@^0.6.42, @helium/distributor-oracle@workspace:packages/distributor-oracle": version: 0.0.0-use.local resolution: "@helium/distributor-oracle@workspace:packages/distributor-oracle"