Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ colored = "2.0.4"
thiserror = "1.0.50"
fuel-abi-types = "0.7.0"
hypersync-client = "1.1.4"
hypersync-client-solana = "0.0.3-rc.1"
hypersync-solana-net-types = "0.0.3-rc.1"
hypersync-client-solana = "0.0.5"
hypersync-solana-net-types = "0.0.5"
faster-hex = "0.9"
ruint = "1"
env_logger = "0.11"
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/config_parsing/public_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ impl SystemConfig {
let svm_abi = match &contract.abi {
Abi::Svm(SvmAbi {
program_id,
instructions: _,
defined_types,
source,
}) => Some(SvmAbiJson {
Expand Down
57 changes: 21 additions & 36 deletions packages/cli/src/config_parsing/system_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ use std::{

use hypersync_client_solana::decode::{
metaplex_token_metadata, schema_from_anchor_idl_json, EnumVariant as SvmEnumVariant,
FieldType as SvmFieldType, NamedField as SvmNamedField, ProgramSchema as SvmProgramSchema,
FieldType as SvmFieldType, InstructionSchema as SvmInstructionSchema,
NamedField as SvmNamedField, ProgramSchema as SvmProgramSchema,
};

type ContractNameKey = String;
Expand Down Expand Up @@ -1010,8 +1011,6 @@ impl SystemConfig {
program.name, program.program_id
)
})?;
let program_schema = lookup_program_schema(&svm_abi);

let events = program
.instructions
.iter()
Expand All @@ -1025,15 +1024,10 @@ impl SystemConfig {
}
None => (None, 0u8),
};
let (accounts, args) = resolve_instruction_layout(
program,
instr,
program_schema,
&svm_abi.source,
)
.with_context(|| {
format!("Layout for instruction '{}'", instr.name)
})?;
let (accounts, args) = resolve_instruction_layout(instr, &svm_abi)
.with_context(|| {
format!("Layout for instruction '{}'", instr.name)
})?;
let fs = instr.field_selection.as_ref();
let include_token_balances = fs
.and_then(|f| f.token_balance_fields.as_ref())
Expand Down Expand Up @@ -1516,6 +1510,7 @@ fn resolve_program_schema(
.with_context(|| format!("parsing IDL at '{}'", abs.display()))?;
return Ok(SvmAbi {
program_id: program.program_id.clone(),
instructions: schema.instructions,
defined_types: schema.defined_types,
source: SvmSchemaSource::AnchorIdl {
path: idl_path.to_string(),
Expand All @@ -1531,6 +1526,7 @@ fn resolve_program_schema(
let schema = getter();
return Ok(SvmAbi {
program_id: program.program_id.clone(),
instructions: schema.instructions.clone(),
defined_types: schema.defined_types.clone(),
source: SvmSchemaSource::Bundled { name },
});
Expand All @@ -1539,32 +1535,21 @@ fn resolve_program_schema(

Ok(SvmAbi {
program_id: program.program_id.clone(),
instructions: BTreeMap::new(),
defined_types: BTreeMap::new(),
source: SvmSchemaSource::Inline,
})
}

fn lookup_program_schema(abi: &SvmAbi) -> Option<&'static SvmProgramSchema> {
match abi.source {
SvmSchemaSource::Bundled { .. } => bundled_program_schemas()
.into_iter()
.find(|(pid, _, _)| *pid == abi.program_id.as_str())
.map(|(_, _, getter)| getter()),
_ => None,
}
}

/// Resolve per-instruction `(accounts, args)` from one of:
/// 1. YAML per-instruction `accounts`/`args` overrides (highest priority).
/// 2. The matching `InstructionSchema` on a bundled `ProgramSchema`, keyed
/// by the YAML `discriminator` bytes.
/// 2. The matching `InstructionSchema` on the program's resolved schema
/// (bundled OR Anchor IDL), keyed by the YAML `discriminator` bytes.
/// 3. An empty pair (`accounts: []`, `args: []`) so existing untyped
/// handlers keep working.
fn resolve_instruction_layout(
_program: &human_config::svm::Program,
instr: &human_config::svm::Instruction,
program_schema: Option<&SvmProgramSchema>,
source: &SvmSchemaSource,
abi: &SvmAbi,
) -> Result<(Vec<String>, Vec<SvmNamedField>)> {
if let (Some(accounts_yaml), Some(args_yaml)) = (&instr.accounts, &instr.args) {
let args = args_yaml
Expand All @@ -1581,15 +1566,11 @@ fn resolve_instruction_layout(
));
}

if let (Some(schema), SvmSchemaSource::Bundled { .. } | SvmSchemaSource::AnchorIdl { .. }) =
(program_schema, source)
{
if let Some(disc_bytes) = disc_to_bytes(instr.discriminator.as_deref())? {
if let Some(ix_schema) = schema.instructions.get(&disc_bytes) {
let accounts = ix_schema.accounts.iter().map(|a| a.name.clone()).collect();
let args = ix_schema.args.clone();
return Ok((accounts, args));
}
if let Some(disc_bytes) = disc_to_bytes(instr.discriminator.as_deref())? {
if let Some(ix_schema) = abi.instructions.get(&disc_bytes) {
let accounts = ix_schema.accounts.iter().map(|a| a.name.clone()).collect();
let args = ix_schema.args.clone();
return Ok((accounts, args));
}
}

Expand Down Expand Up @@ -1753,6 +1734,10 @@ pub enum Abi {
pub struct SvmAbi {
/// Base58 program id this schema describes.
pub program_id: String,
/// Per-instruction Borsh layout (accounts + args), keyed by full
/// discriminator bytes. Populated from an Anchor IDL's `instructions` or the
/// bundled-schema registry; empty for inline (per-instruction YAML) schemas.
pub instructions: BTreeMap<Vec<u8>, SvmInstructionSchema>,
/// Nominal-type registry referenced by `SvmFieldType::Defined`. Populated
/// from an Anchor IDL's `types:` block, the bundled-schema registry, or
/// empty for hand-written ad-hoc schemas.
Expand Down
81 changes: 81 additions & 0 deletions packages/cli/src/hypersync_source_svm/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,37 @@ pub struct SolanaQuery {
pub instructions: Option<Vec<InstructionSelection>>,
pub transactions: Option<Vec<TransactionSelection>>,
pub logs: Option<Vec<LogSelection>>,
pub balances: Option<Vec<BalanceSelection>>,
pub token_balances: Option<Vec<TokenBalanceSelection>>,
pub include_all_blocks: Option<bool>,
/// Return native SOL balances for the matched result set without requiring
/// `include_all_blocks`.
pub include_balances: Option<bool>,
/// Return SPL token balances for the matched result set without requiring
/// `include_all_blocks`.
pub include_token_balances: Option<bool>,
pub fields: Option<FieldSelection>,
pub max_num_blocks: Option<i64>,
pub max_num_transactions: Option<i64>,
pub max_num_instructions: Option<i64>,
pub max_num_logs: Option<i64>,
pub max_num_balances: Option<i64>,
pub max_num_token_balances: Option<i64>,
}

#[napi(object)]
#[derive(Default, Clone)]
pub struct BalanceSelection {
pub account: Option<Vec<String>>,
}

#[napi(object)]
#[derive(Default, Clone)]
pub struct TokenBalanceSelection {
pub account: Option<Vec<String>>,
pub mint: Option<Vec<String>>,
pub owner: Option<Vec<String>>,
pub program_id: Option<Vec<String>>,
}

/// Filter for selecting instructions. All non-empty fields are AND-ed: an
Expand Down Expand Up @@ -53,6 +78,10 @@ pub struct InstructionSelection {
pub is_inner: Option<bool>,
pub include_transaction: Option<bool>,
pub include_logs: Option<bool>,
/// Also return native SOL balances for matched txs (scoped join).
pub include_balances: Option<bool>,
/// Also return SPL token balances for matched txs (scoped join).
pub include_token_balances: Option<bool>,
}

#[napi(object)]
Expand All @@ -61,6 +90,8 @@ pub struct TransactionSelection {
pub fee_payer: Option<Vec<String>>,
pub success: Option<bool>,
pub include_instructions: Option<bool>,
pub include_balances: Option<bool>,
pub include_token_balances: Option<bool>,
}

#[napi(object)]
Expand All @@ -70,6 +101,8 @@ pub struct LogSelection {
pub kind: Option<Vec<String>>,
pub include_transaction: Option<bool>,
pub include_instruction: Option<bool>,
pub include_balances: Option<bool>,
pub include_token_balances: Option<bool>,
}

/// Per-table field selection. Each field accepts a list of column names; an
Expand Down Expand Up @@ -137,6 +170,9 @@ impl From<InstructionSelection> for net::InstructionSelection {
is_inner: s.is_inner,
include_transaction: s.include_transaction.unwrap_or_default(),
include_logs: s.include_logs.unwrap_or_default(),
include_inner_instructions: false,
include_balances: s.include_balances.unwrap_or_default(),
include_token_balances: s.include_token_balances.unwrap_or_default(),
}
}
}
Expand All @@ -147,6 +183,8 @@ impl From<TransactionSelection> for net::TransactionSelection {
fee_payer: s.fee_payer.unwrap_or_default(),
success: s.success,
include_instructions: s.include_instructions.unwrap_or_default(),
include_balances: s.include_balances.unwrap_or_default(),
include_token_balances: s.include_token_balances.unwrap_or_default(),
}
}
}
Expand All @@ -158,6 +196,27 @@ impl From<LogSelection> for net::LogSelection {
kind: s.kind.unwrap_or_default(),
include_transaction: s.include_transaction.unwrap_or_default(),
include_instruction: s.include_instruction.unwrap_or_default(),
include_balances: s.include_balances.unwrap_or_default(),
include_token_balances: s.include_token_balances.unwrap_or_default(),
}
}
}

impl From<BalanceSelection> for net::BalanceSelection {
fn from(s: BalanceSelection) -> Self {
Self {
account: s.account.unwrap_or_default(),
}
}
}

impl From<TokenBalanceSelection> for net::TokenBalanceSelection {
fn from(s: TokenBalanceSelection) -> Self {
Self {
account: s.account.unwrap_or_default(),
mint: s.mint.unwrap_or_default(),
owner: s.owner.unwrap_or_default(),
program_id: s.program_id.unwrap_or_default(),
}
}
}
Expand Down Expand Up @@ -194,7 +253,21 @@ impl TryFrom<SolanaQuery> for net::SolanaQuery {
.into_iter()
.map(Into::into)
.collect(),
balances: q
.balances
.unwrap_or_default()
.into_iter()
.map(Into::into)
.collect(),
token_balances: q
.token_balances
.unwrap_or_default()
.into_iter()
.map(Into::into)
.collect(),
include_all_blocks: q.include_all_blocks.unwrap_or_default(),
include_balances: q.include_balances.unwrap_or_default(),
include_token_balances: q.include_token_balances.unwrap_or_default(),
fields: q
.fields
.map(TryInto::try_into)
Expand All @@ -210,6 +283,14 @@ impl TryFrom<SolanaQuery> for net::SolanaQuery {
.filter(|v| *v >= 0)
.map(|v| v as usize),
max_num_logs: q.max_num_logs.filter(|v| *v >= 0).map(|v| v as usize),
max_num_balances: q
.max_num_balances
.filter(|v| *v >= 0)
.map(|v| v as usize),
max_num_token_balances: q
.max_num_token_balances
.filter(|v| *v >= 0)
.map(|v| v as usize),
})
}
}
1 change: 1 addition & 0 deletions packages/envio/src/Config.res
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ let svmEventDescriptorSchema = S.schema(s =>
"discriminatorByteLen": s.matches(S.int),
"includeTransaction": s.matches(S.bool),
"includeLogs": s.matches(S.bool),
"includeTokenBalances": s.matches(S.bool),
"accountFilters": s.matches(
S.option(
S.array(
Expand Down
2 changes: 2 additions & 0 deletions packages/envio/src/sources/HyperSyncSolanaClient.res
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,13 @@ module QueryTypes = {
transactions?: array<transactionSelection>,
logs?: array<logSelection>,
includeAllBlocks?: bool,
includeTokenBalances?: bool,
fields?: fieldSelection,
maxNumBlocks?: int,
maxNumTransactions?: int,
maxNumInstructions?: int,
maxNumLogs?: int,
maxNumTokenBalances?: int,
}
}

Expand Down
Loading
Loading