From 5709cab6b7a823721aa22ae0d4bc66930f902723 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:19:32 -0700 Subject: [PATCH 1/3] Index DBC Configs --- ddl/migrations/0175_dbc_pool_configs.sql | 71 +++++ solana/indexer/dbc/dbc_config.go | 304 +++++++++++++++++++++ solana/indexer/dbc/{dbc.go => dbc_pool.go} | 0 solana/indexer/dbc/indexer.go | 142 ++++++++-- solana/indexer/dbc/indexer_test.go | 188 ++++++++++++- solana/indexer/solana_indexer.go | 2 +- sql/01_schema.sql | 137 +++++++++- sql/02_test_template.sql | 1 + 8 files changed, 798 insertions(+), 47 deletions(-) create mode 100644 ddl/migrations/0175_dbc_pool_configs.sql create mode 100644 solana/indexer/dbc/dbc_config.go rename solana/indexer/dbc/{dbc.go => dbc_pool.go} (100%) diff --git a/ddl/migrations/0175_dbc_pool_configs.sql b/ddl/migrations/0175_dbc_pool_configs.sql new file mode 100644 index 00000000..4396b4a1 --- /dev/null +++ b/ddl/migrations/0175_dbc_pool_configs.sql @@ -0,0 +1,71 @@ +BEGIN; + +CREATE TABLE sol_meteora_dbc_configs ( + account TEXT PRIMARY KEY, + slot BIGINT NOT NULL, + quote_mint TEXT NOT NULL, + fee_claimer TEXT NOT NULL, + leftover_receiver TEXT NOT NULL, + collect_fee_mode SMALLINT NOT NULL, + migration_option SMALLINT NOT NULL, + activation_type SMALLINT, + token_decimal SMALLINT, + version SMALLINT, + token_type SMALLINT, + quote_token_flag SMALLINT, + partner_locked_lp_percentage SMALLINT, + partner_lp_percentage SMALLINT, + creator_locked_lp_percentage SMALLINT, + creator_lp_percentage SMALLINT, + migration_fee_option SMALLINT, + fixed_token_supply_flag SMALLINT, + creator_trading_fee_percentage SMALLINT, + token_update_authority SMALLINT, + migration_fee_percentage SMALLINT, + creator_migration_fee_percentage SMALLINT, + swap_base_amount BIGINT, + migration_quote_threshold BIGINT, + migration_base_threshold BIGINT, + migration_sqrt_price NUMERIC, + pre_migration_token_supply BIGINT, + post_migration_token_supply BIGINT, + migrated_collect_fee_mode SMALLINT, + migrated_dynamic_fee SMALLINT, + migrated_pool_fee_bps SMALLINT, + sqrt_start_price NUMERIC, + curve JSONB, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE TABLE sol_meteora_dbc_config_fees ( + config TEXT PRIMARY KEY REFERENCES sol_meteora_dbc_configs(account) ON DELETE CASCADE, + slot BIGINT NOT NULL, + base_fee_cliff_fee_numerator BIGINT, + base_fee_period_frequency BIGINT, + base_fee_reduction_factor BIGINT, + base_fee_number_of_period SMALLINT, + base_fee_fee_scheduler_mode SMALLINT, + dynamic_fee_initialized SMALLINT, + dynamic_fee_max_volatility_accumulator INTEGER, + dynamic_fee_variable_fee_control INTEGER, + dynamic_fee_bin_step SMALLINT, + dynamic_fee_filter_period SMALLINT, + dynamic_fee_decay_period SMALLINT, + dynamic_fee_reduction_factor SMALLINT, + dynamic_fee_bin_step_u128 NUMERIC, + protocol_fee_percent SMALLINT, + referral_fee_percent SMALLINT +); + +CREATE TABLE sol_meteora_dbc_config_vestings ( + config TEXT PRIMARY KEY REFERENCES sol_meteora_dbc_configs(account) ON DELETE CASCADE, + slot BIGINT NOT NULL, + amount_per_period BIGINT, + cliff_duration_from_migration_time BIGINT, + frequency BIGINT, + number_of_period BIGINT, + cliff_unlock_amount BIGINT +); + +COMMIT; \ No newline at end of file diff --git a/solana/indexer/dbc/dbc_config.go b/solana/indexer/dbc/dbc_config.go new file mode 100644 index 00000000..8149226b --- /dev/null +++ b/solana/indexer/dbc/dbc_config.go @@ -0,0 +1,304 @@ +package dbc + +import ( + "context" + + "api.audius.co/solana/spl/programs/meteora_dbc" + "github.com/jackc/pgx/v5" +) + +func upsertDbcConfig( + ctx context.Context, + tx pgx.Tx, + slot uint64, + account string, + config *meteora_dbc.PoolConfig, +) error { + _, err := tx.Exec(ctx, ` + INSERT INTO sol_meteora_dbc_configs ( + account, + slot, + quote_mint, + fee_claimer, + leftover_receiver, + collect_fee_mode, + migration_option, + activation_type, + token_decimal, + version, + token_type, + quote_token_flag, + partner_locked_lp_percentage, + partner_lp_percentage, + creator_locked_lp_percentage, + creator_lp_percentage, + migration_fee_option, + fixed_token_supply_flag, + creator_trading_fee_percentage, + token_update_authority, + migration_fee_percentage, + creator_migration_fee_percentage, + swap_base_amount, + migration_quote_threshold, + migration_base_threshold, + migration_sqrt_price, + pre_migration_token_supply, + post_migration_token_supply, + migrated_collect_fee_mode, + migrated_dynamic_fee, + migrated_pool_fee_bps, + sqrt_start_price, + curve, + created_at, + updated_at + ) VALUES ( + @account, + @slot, + @quote_mint, + @fee_claimer, + @leftover_receiver, + @collect_fee_mode, + @migration_option, + @activation_type, + @token_decimal, + @version, + @token_type, + @quote_token_flag, + @partner_locked_lp_percentage, + @partner_lp_percentage, + @creator_locked_lp_percentage, + @creator_lp_percentage, + @migration_fee_option, + @fixed_token_supply_flag, + @creator_trading_fee_percentage, + @token_update_authority, + @migration_fee_percentage, + @creator_migration_fee_percentage, + @swap_base_amount, + @migration_quote_threshold, + @migration_base_threshold, + @migration_sqrt_price, + @pre_migration_token_supply, + @post_migration_token_supply, + @migrated_collect_fee_mode, + @migrated_dynamic_fee, + @migrated_pool_fee_bps, + @sqrt_start_price, + @curve, + NOW(), + NOW() + ) + ON CONFLICT (account) DO UPDATE + SET + slot = EXCLUDED.slot, + quote_mint = EXCLUDED.quote_mint, + fee_claimer = EXCLUDED.fee_claimer, + leftover_receiver = EXCLUDED.leftover_receiver, + collect_fee_mode = EXCLUDED.collect_fee_mode, + migration_option = EXCLUDED.migration_option, + activation_type = EXCLUDED.activation_type, + token_decimal = EXCLUDED.token_decimal, + version = EXCLUDED.version, + token_type = EXCLUDED.token_type, + quote_token_flag = EXCLUDED.quote_token_flag, + partner_locked_lp_percentage = EXCLUDED.partner_locked_lp_percentage, + partner_lp_percentage = EXCLUDED.partner_lp_percentage, + creator_locked_lp_percentage = EXCLUDED.creator_locked_lp_percentage, + creator_lp_percentage = EXCLUDED.creator_lp_percentage, + migration_fee_option = EXCLUDED.migration_fee_option, + fixed_token_supply_flag = EXCLUDED.fixed_token_supply_flag, + creator_trading_fee_percentage = EXCLUDED.creator_trading_fee_percentage, + token_update_authority = EXCLUDED.token_update_authority, + migration_fee_percentage = EXCLUDED.migration_fee_percentage, + creator_migration_fee_percentage = EXCLUDED.creator_migration_fee_percentage, + swap_base_amount = EXCLUDED.swap_base_amount, + migration_quote_threshold = EXCLUDED.migration_quote_threshold, + migration_base_threshold = EXCLUDED.migration_base_threshold, + migration_sqrt_price = EXCLUDED.migration_sqrt_price, + pre_migration_token_supply = EXCLUDED.pre_migration_token_supply, + post_migration_token_supply = EXCLUDED.post_migration_token_supply, + migrated_collect_fee_mode = EXCLUDED.migrated_collect_fee_mode, + migrated_dynamic_fee = EXCLUDED.migrated_dynamic_fee, + migrated_pool_fee_bps = EXCLUDED.migrated_pool_fee_bps, + sqrt_start_price = EXCLUDED.sqrt_start_price, + curve = EXCLUDED.curve, + updated_at = NOW() + WHERE EXCLUDED.slot > sol_meteora_dbc_configs.slot + `, pgx.NamedArgs{ + "account": account, + "slot": slot, + "quote_mint": config.QuoteMint.String(), + "fee_claimer": config.FeeClaimer.String(), + "leftover_receiver": config.LeftoverReceiver.String(), + "collect_fee_mode": config.CollectFeeMode, + "migration_option": config.MigrationOption, + "activation_type": config.ActivationType, + "token_decimal": config.TokenDecimal, + "version": config.Version, + "token_type": config.TokenType, + "quote_token_flag": config.QuoteTokenFlag, + "partner_locked_lp_percentage": config.PartnerLockedLpPercentage, + "partner_lp_percentage": config.PartnerLpPercentage, + "creator_locked_lp_percentage": config.CreatorLockedLpPercentage, + "creator_lp_percentage": config.CreatorLpPercentage, + "migration_fee_option": config.MigrationFeeOption, + "fixed_token_supply_flag": config.FixedTokenSupplyFlag, + "creator_trading_fee_percentage": config.CreatorTradingFeePercentage, + "token_update_authority": config.TokenUpdateAuthority, + "migration_fee_percentage": config.MigrationFeePercentage, + "creator_migration_fee_percentage": config.CreatorMigrationFeePercentage, + "swap_base_amount": config.SwapBaseAmount, + "migration_quote_threshold": config.MigrationQuoteThreshold, + "migration_base_threshold": config.MigrationBaseThreshold, + "migration_sqrt_price": config.MigrationSqrtPrice.String(), + "pre_migration_token_supply": config.PreMigrationTokenSupply, + "post_migration_token_supply": config.PostMigrationTokenSupply, + "migrated_collect_fee_mode": config.MigratedCollectFeeMode, + "migrated_dynamic_fee": config.MigratedDynamicFee, + "migrated_pool_fee_bps": config.MigratedPoolFeeBps, + "sqrt_start_price": config.SqrtStartPrice.String(), + "curve": config.Curve, + }) + if err != nil { + return err + } + return nil +} + +func upsertDbcConfigFees( + ctx context.Context, + tx pgx.Tx, + slot uint64, + account string, + fees *meteora_dbc.PoolFeesConfig, +) error { + _, err := tx.Exec(ctx, ` + INSERT INTO sol_meteora_dbc_config_fees ( + config, + slot, + base_fee_cliff_fee_numerator, + base_fee_period_frequency, + base_fee_reduction_factor, + base_fee_number_of_period, + base_fee_fee_scheduler_mode, + dynamic_fee_initialized, + dynamic_fee_max_volatility_accumulator, + dynamic_fee_variable_fee_control, + dynamic_fee_bin_step, + dynamic_fee_filter_period, + dynamic_fee_decay_period, + dynamic_fee_reduction_factor, + dynamic_fee_bin_step_u128, + protocol_fee_percent, + referral_fee_percent + ) VALUES ( + @config, + @slot, + @base_fee_cliff_fee_numerator, + @base_fee_period_frequency, + @base_fee_reduction_factor, + @base_fee_number_of_period, + @base_fee_fee_scheduler_mode, + @dynamic_fee_initialized, + @dynamic_fee_max_volatility_accumulator, + @dynamic_fee_variable_fee_control, + @dynamic_fee_bin_step, + @dynamic_fee_filter_period, + @dynamic_fee_decay_period, + @dynamic_fee_reduction_factor, + @dynamic_fee_bin_step_u128, + @protocol_fee_percent, + @referral_fee_percent + ) + ON CONFLICT (config) DO UPDATE + SET + slot = EXCLUDED.slot, + base_fee_cliff_fee_numerator = EXCLUDED.base_fee_cliff_fee_numerator, + base_fee_period_frequency = EXCLUDED.base_fee_period_frequency, + base_fee_reduction_factor = EXCLUDED.base_fee_reduction_factor, + base_fee_number_of_period = EXCLUDED.base_fee_number_of_period, + base_fee_fee_scheduler_mode = EXCLUDED.base_fee_fee_scheduler_mode, + dynamic_fee_initialized = EXCLUDED.dynamic_fee_initialized, + dynamic_fee_max_volatility_accumulator = EXCLUDED.dynamic_fee_max_volatility_accumulator, + dynamic_fee_variable_fee_control = EXCLUDED.dynamic_fee_variable_fee_control, + dynamic_fee_bin_step = EXCLUDED.dynamic_fee_bin_step, + dynamic_fee_filter_period = EXCLUDED.dynamic_fee_filter_period, + dynamic_fee_decay_period = EXCLUDED.dynamic_fee_decay_period, + dynamic_fee_reduction_factor = EXCLUDED.dynamic_fee_reduction_factor, + dynamic_fee_bin_step_u128 = EXCLUDED.dynamic_fee_bin_step_u128, + protocol_fee_percent = EXCLUDED.protocol_fee_percent, + referral_fee_percent = EXCLUDED.referral_fee_percent + WHERE EXCLUDED.slot > sol_meteora_dbc_config_fees.slot + `, pgx.NamedArgs{ + "config": account, + "slot": slot, + "base_fee_cliff_fee_numerator": fees.BaseFee.CliffFeeNumerator, + "base_fee_period_frequency": fees.BaseFee.PeriodFrequency, + "base_fee_reduction_factor": fees.BaseFee.ReductionFactor, + "base_fee_number_of_period": fees.BaseFee.NumberOfPeriod, + "base_fee_fee_scheduler_mode": fees.BaseFee.FeeSchedulerMode, + "dynamic_fee_initialized": fees.DynamicFee.Initialized, + "dynamic_fee_max_volatility_accumulator": fees.DynamicFee.MaxVolatilityAccumulator, + "dynamic_fee_variable_fee_control": fees.DynamicFee.VariableFeeControl, + "dynamic_fee_bin_step": fees.DynamicFee.BinStep, + "dynamic_fee_filter_period": fees.DynamicFee.FilterPeriod, + "dynamic_fee_decay_period": fees.DynamicFee.DecayPeriod, + "dynamic_fee_reduction_factor": fees.DynamicFee.ReductionFactor, + "dynamic_fee_bin_step_u128": fees.DynamicFee.BinStepU128.String(), + "protocol_fee_percent": fees.ProtocolFeePercent, + "referral_fee_percent": fees.ReferralFeePercent, + }) + if err != nil { + return err + } + return nil +} + +func upsertDbcConfigVesting( + ctx context.Context, + tx pgx.Tx, + slot uint64, + account string, + vesting *meteora_dbc.LockedVestingConfig, +) error { + _, err := tx.Exec(ctx, ` + INSERT INTO sol_meteora_dbc_config_vestings ( + config, + slot, + amount_per_period, + cliff_duration_from_migration_time, + frequency, + number_of_period, + cliff_unlock_amount + ) VALUES ( + @config, + @slot, + @amount_per_period, + @cliff_duration_from_migration_time, + @frequency, + @number_of_period, + @cliff_unlock_amount + ) + ON CONFLICT (config) DO UPDATE + SET + slot = EXCLUDED.slot, + amount_per_period = EXCLUDED.amount_per_period, + cliff_duration_from_migration_time = EXCLUDED.cliff_duration_from_migration_time, + frequency = EXCLUDED.frequency, + number_of_period = EXCLUDED.number_of_period, + cliff_unlock_amount = EXCLUDED.cliff_unlock_amount + WHERE EXCLUDED.slot > sol_meteora_dbc_config_vestings.slot + `, pgx.NamedArgs{ + "config": account, + "slot": slot, + "amount_per_period": vesting.AmountPerPeriod, + "cliff_duration_from_migration_time": vesting.CliffDurationFromMigrationTime, + "frequency": vesting.Frequency, + "number_of_period": vesting.NumberOfPeriod, + "cliff_unlock_amount": vesting.CliffUnlockAmount, + }) + if err != nil { + return err + } + return nil +} diff --git a/solana/indexer/dbc/dbc.go b/solana/indexer/dbc/dbc_pool.go similarity index 100% rename from solana/indexer/dbc/dbc.go rename to solana/indexer/dbc/dbc_pool.go diff --git a/solana/indexer/dbc/indexer.go b/solana/indexer/dbc/indexer.go index 64d5d2f7..aa26eb18 100644 --- a/solana/indexer/dbc/indexer.go +++ b/solana/indexer/dbc/indexer.go @@ -1,12 +1,14 @@ package dbc import ( + "bytes" "context" "encoding/json" "errors" "fmt" "time" + "api.audius.co/config" "api.audius.co/database" "api.audius.co/solana/indexer/common" "api.audius.co/solana/spl/programs/meteora_dbc" @@ -31,6 +33,7 @@ type Indexer struct { pool database.DbPool grpcConfig common.GrpcConfig rpcClient common.RpcClient + config config.Config transactionCache *otter.Cache[solana.Signature, *rpc.GetTransactionResult] logger *zap.Logger } @@ -39,6 +42,7 @@ func New( grpcConfig common.GrpcConfig, rpcClient common.RpcClient, pool database.DbPool, + config config.Config, transactionCache *otter.Cache[solana.Signature, *rpc.GetTransactionResult], logger *zap.Logger, ) *Indexer { @@ -46,6 +50,7 @@ func New( pool: pool, grpcConfig: grpcConfig, rpcClient: rpcClient, + config: config, transactionCache: transactionCache, logger: logger.Named(NAME), } @@ -167,41 +172,62 @@ func (d *Indexer) HandleUpdate(ctx context.Context, msg *pb.SubscribeUpdate) err // Handle account updates accountUpdate := msg.GetAccount() if accountUpdate != nil { - var pool meteora_dbc.Pool - err := bin.NewBorshDecoder(accountUpdate.Account.Data).Decode(&pool) - if err != nil { - return fmt.Errorf("failed to decode DBC pool account: %w", err) - } - - account := solana.PublicKeyFromBytes(accountUpdate.Account.Pubkey) + // Handle DBC Config updates + if len(accountUpdate.Account.Data) > 8 && bytes.Equal(accountUpdate.Account.Data[:8], meteora_dbc.POOL_CONFIG_DISCRIMINATOR) { + var config meteora_dbc.PoolConfig + err := bin.NewBorshDecoder(accountUpdate.Account.Data).Decode(&config) + if err != nil { + return fmt.Errorf("failed to decode DBC config account: %w", err) + } + account := solana.PublicKeyFromBytes(accountUpdate.Account.Pubkey) - err = processDbcPoolUpdate(ctx, d.pool, accountUpdate.Slot, account, &pool) - if err != nil { - return fmt.Errorf("failed to process DBC pool update: %w", err) + err = processDbcConfigUpdate(ctx, d.pool, accountUpdate.Slot, account, &config) + if err != nil { + return fmt.Errorf("failed to process DBC config update: %w", err) + } + d.logger.Debug("processed DBC config update", + zap.String("account", account.String()), + ) } - d.logger.Debug("processed DBC pool update", - zap.String("account", account.String()), - zap.String("mint", pool.BaseMint.String()), - ) - // If the pool is migrated, check for the migration transaction and process it - if pool.IsMigrated == uint8(1) { - txSig := solana.SignatureFromBytes(accountUpdate.Account.TxnSignature) - - // Fetch the transaction details - txRes, err := common.FetchTransactionWithCache(ctx, d.transactionCache, d.rpcClient, txSig) + // Handle DBC Pool updates + if len(accountUpdate.Account.Data) > 8 && bytes.Equal(accountUpdate.Account.Data[:8], meteora_dbc.POOL_DISCRIMINATOR) { + var pool meteora_dbc.Pool + err := bin.NewBorshDecoder(accountUpdate.Account.Data).Decode(&pool) if err != nil { - return fmt.Errorf("failed to fetch transaction: %w", err) + return fmt.Errorf("failed to decode DBC pool account: %w", err) } - // Decode the transaction - tx, err := txRes.Transaction.GetTransaction() + account := solana.PublicKeyFromBytes(accountUpdate.Account.Pubkey) + + err = processDbcPoolUpdate(ctx, d.pool, accountUpdate.Slot, account, &pool) if err != nil { - return fmt.Errorf("failed to decode transaction: %w", err) + return fmt.Errorf("failed to process DBC pool update: %w", err) } + d.logger.Debug("processed DBC pool update", + zap.String("account", account.String()), + zap.String("mint", pool.BaseMint.String()), + ) + + // If the pool is migrated, check for the migration transaction and process it + if pool.IsMigrated == uint8(1) { + txSig := solana.SignatureFromBytes(accountUpdate.Account.TxnSignature) - // Process the transaction - err = d.processTransaction(ctx, txRes.Slot, tx) + // Fetch the transaction details + txRes, err := common.FetchTransactionWithCache(ctx, d.transactionCache, d.rpcClient, txSig) + if err != nil { + return fmt.Errorf("failed to fetch transaction: %w", err) + } + + // Decode the transaction + tx, err := txRes.Transaction.GetTransaction() + if err != nil { + return fmt.Errorf("failed to decode transaction: %w", err) + } + + // Process the transaction + err = d.processTransaction(ctx, txRes.Slot, tx) + } } } return nil @@ -267,7 +293,7 @@ func (d *Indexer) makeSubscriptionRequest(ctx context.Context, mints []string) * // Listen to all watched pools subscription.Accounts = make(map[string]*pb.SubscribeRequestFilterAccounts) for _, mint := range mints { - accountFilter := pb.SubscribeRequestFilterAccounts{ + poolFilter := pb.SubscribeRequestFilterAccounts{ Owner: []string{meteora_dbc.ProgramID.String()}, Filters: []*pb.SubscribeRequestFilterAccountsFilter{ { @@ -293,8 +319,36 @@ func (d *Indexer) makeSubscriptionRequest(ctx context.Context, mints []string) * }, }, } - subscription.Accounts[mint] = &accountFilter + subscription.Accounts[mint] = &poolFilter + } + + configFilter := pb.SubscribeRequestFilterAccounts{ + Owner: []string{meteora_dbc.ProgramID.String()}, + Filters: []*pb.SubscribeRequestFilterAccountsFilter{ + { + Filter: &pb.SubscribeRequestFilterAccountsFilter_Memcmp{ + Memcmp: &pb.SubscribeRequestFilterAccountsFilterMemcmp{ + Offset: 0, + Data: &pb.SubscribeRequestFilterAccountsFilterMemcmp_Bytes{ + Bytes: meteora_dbc.POOL_CONFIG_DISCRIMINATOR, + }, + }, + }, + }, + { + Filter: &pb.SubscribeRequestFilterAccountsFilter_Memcmp{ + Memcmp: &pb.SubscribeRequestFilterAccountsFilterMemcmp{ + // Quote mint is after discriminator + Offset: 8, + Data: &pb.SubscribeRequestFilterAccountsFilterMemcmp_Base58{ + Base58: d.config.SolanaConfig.MintAudio.String(), + }, + }, + }, + }, + }, } + subscription.Accounts["dbc_config"] = &configFilter // Ensure this subscription has a checkpoint checkpointId, fromSlot, err := common.EnsureCheckpoint(ctx, NAME, d.pool, d.rpcClient, subscription, d.logger) @@ -344,6 +398,38 @@ func processDbcPoolUpdate( return nil } +func processDbcConfigUpdate( + ctx context.Context, + db database.DbPool, + slot uint64, + account solana.PublicKey, + config *meteora_dbc.PoolConfig, +) error { + sqlTx, err := db.Begin(ctx) + if err != nil { + return fmt.Errorf("failed to begin transaction: %w", err) + } + defer sqlTx.Rollback(ctx) + + err = upsertDbcConfig(ctx, sqlTx, slot, account.String(), config) + if err != nil { + return fmt.Errorf("failed to upsert DBC pool config: %w", err) + } + err = upsertDbcConfigFees(ctx, sqlTx, slot, account.String(), &config.PoolFees) + if err != nil { + return fmt.Errorf("failed to upsert DBC config fees: %w", err) + } + err = upsertDbcConfigVesting(ctx, sqlTx, slot, account.String(), &config.LockedVestingConfig) + if err != nil { + return fmt.Errorf("failed to upsert DBC config vestings: %w", err) + } + err = sqlTx.Commit(ctx) + if err != nil { + return fmt.Errorf("failed to commit transaction: %w", err) + } + return nil +} + func (i *Indexer) processTransaction(ctx context.Context, slot uint64, tx *solana.Transaction) error { signature := tx.Signatures[0].String() logger := i.logger.With( diff --git a/solana/indexer/dbc/indexer_test.go b/solana/indexer/dbc/indexer_test.go index 2b66c269..95c84ca8 100644 --- a/solana/indexer/dbc/indexer_test.go +++ b/solana/indexer/dbc/indexer_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "api.audius.co/config" "api.audius.co/database" "api.audius.co/solana/indexer/common" "api.audius.co/solana/indexer/fake_rpc_client" @@ -20,11 +21,11 @@ import ( ) func TestHandleUpdate_SlotCheckpoint(t *testing.T) { - pool := database.CreateTestDatabase(t, "test_solana_indexer_damm_v2") + pool := database.CreateTestDatabase(t, "test_solana_indexer_dbc") rpcClient := fake_rpc_client.FakeRpcClient{} logger := zap.NewNop() - indexer := New(common.GrpcConfig{}, &rpcClient, pool, nil, logger) + indexer := New(common.GrpcConfig{}, &rpcClient, pool, config.Cfg, nil, logger) expectedSlot := uint64(1500) @@ -48,7 +49,7 @@ func TestHandleUpdate_SlotCheckpoint(t *testing.T) { } func TestHandleUpdate_Migration(t *testing.T) { - pool := database.CreateTestDatabase(t, "test_solana_indexer_damm_v2") + pool := database.CreateTestDatabase(t, "test_solana_indexer_dbc") rpcClient := fake_rpc_client.FakeRpcClient{} transactionCache, err := otter.MustBuilder[solana.Signature, *rpc.GetTransactionResult](10).Build() require.NoError(t, err, "failed to create cache") @@ -92,7 +93,7 @@ func TestHandleUpdate_Migration(t *testing.T) { }, } - indexer := New(common.GrpcConfig{}, &rpcClient, pool, &transactionCache, logger) + indexer := New(common.GrpcConfig{}, &rpcClient, pool, config.Cfg, &transactionCache, logger) err = indexer.HandleUpdate(t.Context(), &update) require.NoError(t, err) @@ -223,3 +224,182 @@ func TestHandleUpdate_Migration(t *testing.T) { require.NoError(t, err, "failed to query for artist coin update") assert.True(t, exists, "artist coin should be updated with damm v2 pool") } + +func TestHandleUpdate_Config(t *testing.T) { + pool := database.CreateTestDatabase(t, "test_solana_indexer_dbc") + rpcClient := fake_rpc_client.FakeRpcClient{} + logger := zap.NewNop() + + indexer := New(common.GrpcConfig{}, &rpcClient, pool, config.Cfg, nil, logger) + + configAddress := solana.MustPublicKeyFromBase58("2seGMFauXC22DX8hbop1gh54W1uW8YREWhsU7JuCptTj") + configBase64 := "GmwOe3TmgSt7/DPMLnXBSHbMN5KDkE9JB3ZpESJXuzrf82mLYCJJQHMnYKWN/QLoMpzgYf1T6q8M/4URxhwIezYmnCD6QhNacydgpY39AugynOBh/VPqrwz/hRHGHAh7NiacIPpCE1qAlpgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUFAABAAkAAAAyADIAAgEyAQAAAAAAAAAAAOh62umsLXgD1ME27IcPAACh3eoHfK1eAnk6wvUoXI8CAAAAAAAAAAAOHKo3LfkAAAAAAAAAAAAAgFEBAAAAAAAhBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGSns7bgDQAAZKeztuANAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwp8ZLN4lBAAAAAAAAAAAAkEzmQeayrwAAAAAAAAAAAAAAAAAAAICr4QvOY5YPAABoJRy5YK3vAAAAAAAAAAAAAAAAAAAAAH6Mcle1/DIAAFBt5g7Q3CEBAAAAAAAAAAAAAAAAAAAAfGBuQkpJYgAAIKdtrhKPTAEAAAAAAAAAAAAAAAAAAACofn8CQuqoAAAQ+DrWiV1yAQAAAAAAAAAAAAAAAAAAABAx/11/gRABACBgVsWqp5QBAAAAAAAAAAAAAAAAAAAAQHx20ah9pQEAAGE/b/VBtAEAAAAAAAAAAAAAAAAAAACASrI6oht4AgAAADpJhbjRAQAAAAAAAAAAAAAAAAAAAECI0ygjfp0DALD/OfaBbe0BAAAAAAAAAAAAAAAAAAAAQCdcxXsBMQUAwNUOldmoBwIAAAAAAAAAAAAAAAAAAADATMcVud5VBwDADP0WIKEgAgAAAAAAAAAAAAAAAAAAAIBj/DonMTkKAGAmY87ogDgCAAAAAAAAAAAAAAAAAAAAAFeChBF0FA4AwAwXpixqTwIAAAAAAAAAAAAAAAAAAAAAbhawupIwEwBAQAL9inhlAgAAAAAAAAAAAAAAAAAAAAC+6pCYqekZAMA5nqnVwnoCAAAAAAAAAAAAAAAAAAAAAKpNVvGdsyIAYI/C9ShcjwIAAAAAAAAAAAAAAAAAAAAA1OUxMbgfLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + configData, err := base64.StdEncoding.DecodeString(configBase64) + require.NoError(t, err) + + update := pb.SubscribeUpdate{ + UpdateOneof: &pb.SubscribeUpdate_Account{ + Account: &pb.SubscribeUpdateAccount{ + Slot: 500000000, + Account: &pb.SubscribeUpdateAccountInfo{ + Pubkey: configAddress.Bytes(), + Data: configData, + TxnSignature: nil, + }, + }, + }, + } + + err = indexer.HandleUpdate(t.Context(), &update) + require.NoError(t, err) + + // Verify that the dbc config was inserted + sql := ` + SELECT EXISTS ( + SELECT 1 + FROM sol_meteora_dbc_configs + WHERE account = @account + AND slot = @slot + AND quote_mint = @quote_mint + AND fee_claimer = @fee_claimer + AND leftover_receiver = @leftover_receiver + AND collect_fee_mode = @collect_fee_mode + AND migration_option = @migration_option + AND activation_type = @activation_type + AND token_decimal = @token_decimal + AND version = @version + AND token_type = @token_type + AND quote_token_flag = @quote_token_flag + AND partner_locked_lp_percentage = @partner_locked_lp_percentage + AND partner_lp_percentage = @partner_lp_percentage + AND creator_locked_lp_percentage = @creator_locked_lp_percentage + AND creator_lp_percentage = @creator_lp_percentage + AND migration_fee_option = @migration_fee_option + AND fixed_token_supply_flag = @fixed_token_supply_flag + AND creator_trading_fee_percentage = @creator_trading_fee_percentage + AND token_update_authority = @token_update_authority + AND migration_fee_percentage = @migration_fee_percentage + AND creator_migration_fee_percentage = @creator_migration_fee_percentage + AND swap_base_amount = @swap_base_amount + AND migration_quote_threshold = @migration_quote_threshold + AND migration_base_threshold = @migration_base_threshold + AND migration_sqrt_price = @migration_sqrt_price + AND pre_migration_token_supply = @pre_migration_token_supply + AND post_migration_token_supply = @post_migration_token_supply + AND migrated_collect_fee_mode = @migrated_collect_fee_mode + AND migrated_dynamic_fee = @migrated_dynamic_fee + AND migrated_pool_fee_bps = @migrated_pool_fee_bps + AND sqrt_start_price = @sqrt_start_price + LIMIT 1 + ) + ` + var exists bool + err = pool.QueryRow(t.Context(), sql, pgx.NamedArgs{ + "account": configAddress.String(), + "slot": int64(500000000), + "quote_mint": "9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM", + "fee_claimer": "8kWiEZFeuaPCanbJkwL4PvWDmx4zsLnRoXjUPBnvrLmX", + "leftover_receiver": "8kWiEZFeuaPCanbJkwL4PvWDmx4zsLnRoXjUPBnvrLmX", + "collect_fee_mode": uint8(0), + "migration_option": uint8(1), + "activation_type": uint8(0), + "token_decimal": uint8(9), + "version": uint8(0), + "token_type": uint8(0), + "quote_token_flag": uint8(0), + "partner_locked_lp_percentage": uint8(50), + "partner_lp_percentage": uint8(0), + "creator_locked_lp_percentage": uint8(50), + "creator_lp_percentage": uint8(0), + "migration_fee_option": uint8(2), + "fixed_token_supply_flag": uint8(1), + "creator_trading_fee_percentage": uint8(50), + "token_update_authority": uint8(1), + "migration_fee_percentage": uint8(0), + "creator_migration_fee_percentage": uint8(0), + "swap_base_amount": uint64(250000000000097000), + "migration_quote_threshold": uint64(17076458013140), + "migration_base_threshold": uint64(170764584107040161), + "migration_sqrt_price": "184467440737073785", + "pre_migration_token_supply": uint64(1000000000000000000), + "post_migration_token_supply": uint64(1000000000000000000), + "migrated_collect_fee_mode": uint8(0), + "migrated_dynamic_fee": uint8(0), + "migrated_pool_fee_bps": uint16(0), + "sqrt_start_price": "18446744073709552", + }).Scan(&exists) + require.NoError(t, err, "failed to query for dbc config") + assert.True(t, exists, "dbc config should exist after indexing") + + sql = ` + SELECT EXISTS ( + SELECT 1 + FROM sol_meteora_dbc_config_fees + WHERE config = @config + AND slot = @slot + AND base_fee_cliff_fee_numerator = @base_fee_cliff_fee_numerator + AND base_fee_period_frequency = @base_fee_period_frequency + AND base_fee_reduction_factor = @base_fee_reduction_factor + AND base_fee_number_of_period = @base_fee_number_of_period + AND base_fee_fee_scheduler_mode = @base_fee_fee_scheduler_mode + AND dynamic_fee_initialized = @dynamic_fee_initialized + AND dynamic_fee_max_volatility_accumulator = @dynamic_fee_max_volatility_accumulator + AND dynamic_fee_variable_fee_control = @dynamic_fee_variable_fee_control + AND dynamic_fee_bin_step = @dynamic_fee_bin_step + AND dynamic_fee_filter_period = @dynamic_fee_filter_period + AND dynamic_fee_decay_period = @dynamic_fee_decay_period + AND dynamic_fee_reduction_factor = @dynamic_fee_reduction_factor + AND dynamic_fee_bin_step_u128 = @dynamic_fee_bin_step_u128 + AND protocol_fee_percent = @protocol_fee_percent + AND referral_fee_percent = @referral_fee_percent + LIMIT 1 + ) + ` + err = pool.QueryRow(t.Context(), sql, pgx.NamedArgs{ + "config": configAddress.String(), + "slot": int64(500000000), + "base_fee_cliff_fee_numerator": uint64(10000000), + "base_fee_period_frequency": uint64(0), + "base_fee_reduction_factor": uint64(0), + "base_fee_number_of_period": uint8(0), + "base_fee_fee_scheduler_mode": uint8(0), + "dynamic_fee_initialized": uint8(0), + "dynamic_fee_max_volatility_accumulator": uint64(0), + "dynamic_fee_variable_fee_control": uint64(0), + "dynamic_fee_bin_step": uint8(0), + "dynamic_fee_filter_period": uint8(0), + "dynamic_fee_decay_period": uint8(0), + "dynamic_fee_reduction_factor": uint64(0), + "dynamic_fee_bin_step_u128": uint64(0), + "protocol_fee_percent": uint8(20), + "referral_fee_percent": uint8(20), + }).Scan(&exists) + require.NoError(t, err, "failed to query for dbc config fees") + assert.True(t, exists, "dbc config fees should exist after indexing") + + sql = ` + SELECT EXISTS ( + SELECT 1 + FROM sol_meteora_dbc_config_vestings + WHERE config = @config + AND slot = @slot + AND amount_per_period = @amount_per_period + AND cliff_duration_from_migration_time = @cliff_duration_from_migration_time + AND frequency = @frequency + AND number_of_period = @number_of_period + AND cliff_unlock_amount = @cliff_unlock_amount + LIMIT 1 + ) + ` + err = pool.QueryRow(t.Context(), sql, pgx.NamedArgs{ + "config": configAddress.String(), + "slot": int64(500000000), + "amount_per_period": uint64(273972602739726), + "cliff_duration_from_migration_time": uint64(0), + "frequency": uint64(86400), + "number_of_period": uint64(1825), + "cliff_unlock_amount": uint64(0), + }).Scan(&exists) + require.NoError(t, err, "failed to query for dbc config vestings") + assert.True(t, exists, "dbc config vestings should exist after indexing") +} diff --git a/solana/indexer/solana_indexer.go b/solana/indexer/solana_indexer.go index 680b7bed..60ea67fd 100644 --- a/solana/indexer/solana_indexer.go +++ b/solana/indexer/solana_indexer.go @@ -85,7 +85,7 @@ func New(config config.Config) *SolanaIndexer { grpcConfig, rpcClient, pool, config, &transactionCache, logger, ) dbcIndexer := dbc.New( - grpcConfig, rpcClient, pool, &transactionCache, logger, + grpcConfig, rpcClient, pool, config, &transactionCache, logger, ) s := &SolanaIndexer{ diff --git a/sql/01_schema.sql b/sql/01_schema.sql index 8e0c69b7..d5be73b1 100644 --- a/sql/01_schema.sql +++ b/sql/01_schema.sql @@ -7463,6 +7463,89 @@ CREATE TABLE public.sol_meteora_damm_v2_positions ( COMMENT ON TABLE public.sol_meteora_damm_v2_positions IS 'Tracks DAMM V2 positions representing a claim to the liquidity and associated fees in a DAMM V2 pool. Join with sol_meteora_damm_v2_position_metrics for full position state.'; +-- +-- Name: sol_meteora_dbc_config_fees; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sol_meteora_dbc_config_fees ( + config text NOT NULL, + slot bigint NOT NULL, + base_fee_cliff_fee_numerator bigint, + base_fee_period_frequency bigint, + base_fee_reduction_factor bigint, + base_fee_number_of_period smallint, + base_fee_fee_scheduler_mode smallint, + dynamic_fee_initialized smallint, + dynamic_fee_max_volatility_accumulator integer, + dynamic_fee_variable_fee_control integer, + dynamic_fee_bin_step smallint, + dynamic_fee_filter_period smallint, + dynamic_fee_decay_period smallint, + dynamic_fee_reduction_factor smallint, + dynamic_fee_bin_step_u128 numeric, + protocol_fee_percent smallint, + referral_fee_percent smallint +); + + +-- +-- Name: sol_meteora_dbc_config_vestings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sol_meteora_dbc_config_vestings ( + config text NOT NULL, + slot bigint NOT NULL, + amount_per_period bigint, + cliff_duration_from_migration_time bigint, + frequency bigint, + number_of_period bigint, + cliff_unlock_amount bigint +); + + +-- +-- Name: sol_meteora_dbc_configs; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sol_meteora_dbc_configs ( + account text NOT NULL, + slot bigint NOT NULL, + quote_mint text NOT NULL, + fee_claimer text NOT NULL, + leftover_receiver text NOT NULL, + collect_fee_mode smallint NOT NULL, + migration_option smallint NOT NULL, + activation_type smallint, + token_decimal smallint, + version smallint, + token_type smallint, + quote_token_flag smallint, + partner_locked_lp_percentage smallint, + partner_lp_percentage smallint, + creator_locked_lp_percentage smallint, + creator_lp_percentage smallint, + migration_fee_option smallint, + fixed_token_supply_flag smallint, + creator_trading_fee_percentage smallint, + token_update_authority smallint, + migration_fee_percentage smallint, + creator_migration_fee_percentage smallint, + swap_base_amount bigint, + migration_quote_threshold bigint, + migration_base_threshold bigint, + migration_sqrt_price numeric, + pre_migration_token_supply bigint, + post_migration_token_supply bigint, + migrated_collect_fee_mode smallint, + migrated_dynamic_fee smallint, + migrated_pool_fee_bps smallint, + sqrt_start_price numeric, + curve jsonb, + created_at timestamp without time zone DEFAULT now(), + updated_at timestamp without time zone DEFAULT now() +); + + -- -- Name: sol_meteora_dbc_migrations; Type: TABLE; Schema: public; Owner: - -- @@ -9382,6 +9465,30 @@ ALTER TABLE ONLY public.sol_meteora_damm_v2_positions ADD CONSTRAINT sol_meteora_damm_v2_positions_pkey PRIMARY KEY (account); +-- +-- Name: sol_meteora_dbc_config_fees sol_meteora_dbc_config_fees_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_config_fees + ADD CONSTRAINT sol_meteora_dbc_config_fees_pkey PRIMARY KEY (config); + + +-- +-- Name: sol_meteora_dbc_config_vestings sol_meteora_dbc_config_vestings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_config_vestings + ADD CONSTRAINT sol_meteora_dbc_config_vestings_pkey PRIMARY KEY (config); + + +-- +-- Name: sol_meteora_dbc_configs sol_meteora_dbc_configs_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_configs + ADD CONSTRAINT sol_meteora_dbc_configs_pkey PRIMARY KEY (account); + + -- -- Name: sol_meteora_dbc_migrations sol_meteora_dbc_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -11099,20 +11206,6 @@ CREATE TRIGGER on_sol_claimable_accounts AFTER INSERT ON public.sol_claimable_ac COMMENT ON TRIGGER on_sol_claimable_accounts ON public.sol_claimable_accounts IS 'Updates sol_user_balances whenever a sol_claimable_account is inserted.'; --- --- Name: sol_token_account_balance_changes on_sol_token_account_balance_changes; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER on_sol_token_account_balance_changes AFTER INSERT ON public.sol_token_account_balance_changes FOR EACH ROW EXECUTE FUNCTION public.handle_sol_token_balance_change(); - - --- --- Name: TRIGGER on_sol_token_account_balance_changes ON sol_token_account_balance_changes; Type: COMMENT; Schema: public; Owner: - --- - -COMMENT ON TRIGGER on_sol_token_account_balance_changes ON public.sol_token_account_balance_changes IS 'Updates sol_token_account_balances whenever a sol_token_balance_change is inserted with a higher slot.'; - - -- -- Name: supporter_rank_ups on_supporter_rank_up; Type: TRIGGER; Schema: public; Owner: - -- @@ -11453,6 +11546,22 @@ ALTER TABLE ONLY public.saves ADD CONSTRAINT saves_blocknumber_fkey FOREIGN KEY (blocknumber) REFERENCES public.blocks(number) ON DELETE CASCADE; +-- +-- Name: sol_meteora_dbc_config_fees sol_meteora_dbc_config_fees_config_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_config_fees + ADD CONSTRAINT sol_meteora_dbc_config_fees_config_fkey FOREIGN KEY (config) REFERENCES public.sol_meteora_dbc_configs(account) ON DELETE CASCADE; + + +-- +-- Name: sol_meteora_dbc_config_vestings sol_meteora_dbc_config_vestings_config_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_config_vestings + ADD CONSTRAINT sol_meteora_dbc_config_vestings_config_fkey FOREIGN KEY (config) REFERENCES public.sol_meteora_dbc_configs(account) ON DELETE CASCADE; + + -- -- Name: subscriptions subscriptions_blocknumber_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- diff --git a/sql/02_test_template.sql b/sql/02_test_template.sql index 7c3eab8d..2dbdfcfc 100644 --- a/sql/02_test_template.sql +++ b/sql/02_test_template.sql @@ -5,5 +5,6 @@ CREATE DATABASE test_hll TEMPLATE postgres; CREATE DATABASE test_indexer TEMPLATE postgres; CREATE DATABASE test_solana_indexer_common TEMPLATE postgres; CREATE DATABASE test_solana_indexer_token TEMPLATE postgres; +CREATE DATABASE test_solana_indexer_dbc TEMPLATE postgres; CREATE DATABASE test_solana_indexer_damm_v2 TEMPLATE postgres; CREATE DATABASE test_solana_indexer_program TEMPLATE postgres; \ No newline at end of file From a7d59a219f8faa0f694433d2ceae2e60e11a325d Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:22:45 -0700 Subject: [PATCH 2/3] not sure how this got removed, ty tests --- sql/01_schema.sql | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sql/01_schema.sql b/sql/01_schema.sql index d5be73b1..4f2ccc2a 100644 --- a/sql/01_schema.sql +++ b/sql/01_schema.sql @@ -11206,6 +11206,20 @@ CREATE TRIGGER on_sol_claimable_accounts AFTER INSERT ON public.sol_claimable_ac COMMENT ON TRIGGER on_sol_claimable_accounts ON public.sol_claimable_accounts IS 'Updates sol_user_balances whenever a sol_claimable_account is inserted.'; +-- +-- Name: sol_token_account_balance_changes on_sol_token_account_balance_changes; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER on_sol_token_account_balance_changes AFTER INSERT ON public.sol_token_account_balance_changes FOR EACH ROW EXECUTE FUNCTION public.handle_sol_token_balance_change(); + + +-- +-- Name: TRIGGER on_sol_token_account_balance_changes ON sol_token_account_balance_changes; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TRIGGER on_sol_token_account_balance_changes ON public.sol_token_account_balance_changes IS 'Updates sol_token_account_balances whenever a sol_token_balance_change is inserted with a higher slot.'; + + -- -- Name: supporter_rank_ups on_supporter_rank_up; Type: TRIGGER; Schema: public; Owner: - -- From 114ba6ceee87d06639407fe8a510d898a76f9ee0 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Thu, 23 Oct 2025 11:24:38 -0700 Subject: [PATCH 3/3] sloppy ai --- ddl/migrations/0175_dbc_pool_configs.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddl/migrations/0175_dbc_pool_configs.sql b/ddl/migrations/0175_dbc_pool_configs.sql index 4396b4a1..8baf14f7 100644 --- a/ddl/migrations/0175_dbc_pool_configs.sql +++ b/ddl/migrations/0175_dbc_pool_configs.sql @@ -1,6 +1,6 @@ BEGIN; -CREATE TABLE sol_meteora_dbc_configs ( +CREATE TABLE IF NOT EXISTS sol_meteora_dbc_configs ( account TEXT PRIMARY KEY, slot BIGINT NOT NULL, quote_mint TEXT NOT NULL, @@ -38,7 +38,7 @@ CREATE TABLE sol_meteora_dbc_configs ( updated_at TIMESTAMP DEFAULT NOW() ); -CREATE TABLE sol_meteora_dbc_config_fees ( +CREATE TABLE IF NOT EXISTS sol_meteora_dbc_config_fees ( config TEXT PRIMARY KEY REFERENCES sol_meteora_dbc_configs(account) ON DELETE CASCADE, slot BIGINT NOT NULL, base_fee_cliff_fee_numerator BIGINT, @@ -58,7 +58,7 @@ CREATE TABLE sol_meteora_dbc_config_fees ( referral_fee_percent SMALLINT ); -CREATE TABLE sol_meteora_dbc_config_vestings ( +CREATE TABLE IF NOT EXISTS sol_meteora_dbc_config_vestings ( config TEXT PRIMARY KEY REFERENCES sol_meteora_dbc_configs(account) ON DELETE CASCADE, slot BIGINT NOT NULL, amount_per_period BIGINT,