From 98628df03da41aa045e10289302d8bc98b356cd4 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:23:13 -0700 Subject: [PATCH 1/9] clamp graduation progress at 1.0 --- solana/spl/programs/meteora_dbc/pool.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/solana/spl/programs/meteora_dbc/pool.go b/solana/spl/programs/meteora_dbc/pool.go index fee02755..62fb3d44 100644 --- a/solana/spl/programs/meteora_dbc/pool.go +++ b/solana/spl/programs/meteora_dbc/pool.go @@ -1,6 +1,7 @@ package meteora_dbc import ( + "math" "math/big" bin "github.com/gagliardetto/binary" @@ -59,5 +60,5 @@ func (p Pool) GetQuotePrice(tokenBaseDecimals int, tokenQuoteDecimals int) float price, _ := quotient.Float64() - return price + return math.Min(price, 1.0) } From cbb628f16597295a5759c40de6662f52ac06f86f Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:23:52 -0700 Subject: [PATCH 2/9] default to null dbc_pool --- api/v1_create_coin.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/api/v1_create_coin.go b/api/v1_create_coin.go index a9a63c24..42340447 100644 --- a/api/v1_create_coin.go +++ b/api/v1_create_coin.go @@ -13,13 +13,13 @@ import ( ) type CreateCoinBody struct { - Mint string `json:"mint" validate:"required,solana_address"` - Ticker string `json:"ticker" validate:"required,min=2,max=10,coin_ticker"` - Decimals int32 `json:"decimals" validate:"required,min=0,max=18"` - Name string `json:"name" validate:"required,min=1,max=32"` - LogoUri string `json:"logo_uri" validate:"omitempty,url"` - Description string `json:"description" validate:"max=2500"` - DbcPool string `json:"dbc_pool"` + Mint string `json:"mint" validate:"required,solana_address"` + Ticker string `json:"ticker" validate:"required,min=2,max=10,coin_ticker"` + Decimals int32 `json:"decimals" validate:"required,min=0,max=18"` + Name string `json:"name" validate:"required,min=1,max=32"` + LogoUri string `json:"logo_uri" validate:"omitempty,url"` + Description string `json:"description" validate:"max=2500"` + DbcPool *string `json:"dbc_pool"` } func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error { From 8ca5871f2a3c392c9361d96f951426beca3e3d3f Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:45:56 -0700 Subject: [PATCH 3/9] Track DBC pools by mint rather than individual addresses --- ddl/functions/handle_artist_coins.sql | 7 -- ddl/migrations/0173_drop_dbc_pool_again.sql | 5 ++ solana/indexer/common/artist_coins.go | 28 ++++++++ solana/indexer/dbc/indexer.go | 73 +++++++++++--------- solana/indexer/token/indexer.go | 21 +----- solana/spl/programs/meteora_dbc/constants.go | 10 +++ 6 files changed, 83 insertions(+), 61 deletions(-) create mode 100644 ddl/migrations/0173_drop_dbc_pool_again.sql create mode 100644 solana/indexer/common/artist_coins.go create mode 100644 solana/spl/programs/meteora_dbc/constants.go diff --git a/ddl/functions/handle_artist_coins.sql b/ddl/functions/handle_artist_coins.sql index f33bc488..e8e9e22f 100644 --- a/ddl/functions/handle_artist_coins.sql +++ b/ddl/functions/handle_artist_coins.sql @@ -8,13 +8,6 @@ BEGIN PERFORM pg_notify('artist_coins_mint_changed', JSON_BUILD_OBJECT('new', NEW.mint, 'old', OLD.mint)::TEXT); END IF; - IF (OLD.dbc_pool IS NULL AND NEW.dbc_pool IS NOT NULL) - OR (OLD.dbc_pool IS NOT NULL AND NEW.dbc_pool IS NULL) - OR OLD.dbc_pool != NEW.dbc_pool - THEN - PERFORM pg_notify('artist_coins_dbc_pool_changed', JSON_BUILD_OBJECT('new', NEW.dbc_pool, 'old', OLD.dbc_pool)::TEXT); - END IF; - IF (OLD.damm_v2_pool IS NULL AND NEW.damm_v2_pool IS NOT NULL) OR (OLD.damm_v2_pool IS NOT NULL AND NEW.damm_v2_pool IS NULL) OR OLD.damm_v2_pool != NEW.damm_v2_pool diff --git a/ddl/migrations/0173_drop_dbc_pool_again.sql b/ddl/migrations/0173_drop_dbc_pool_again.sql new file mode 100644 index 00000000..32ea7afc --- /dev/null +++ b/ddl/migrations/0173_drop_dbc_pool_again.sql @@ -0,0 +1,5 @@ +BEGIN; + +ALTER TABLE artist_coins DROP COLUMN IF EXISTS dbc_pool; + +COMMIT; \ No newline at end of file diff --git a/solana/indexer/common/artist_coins.go b/solana/indexer/common/artist_coins.go new file mode 100644 index 00000000..189d13af --- /dev/null +++ b/solana/indexer/common/artist_coins.go @@ -0,0 +1,28 @@ +package common + +import ( + "context" + "fmt" + + "api.audius.co/database" + "github.com/jackc/pgx/v5" +) + +func GetArtistCoinMints(ctx context.Context, db database.DBTX, limit int, offset int) ([]string, error) { + sqlMints := `SELECT mint FROM artist_coins LIMIT @limit OFFSET @offset` + rows, err := db.Query(ctx, sqlMints, pgx.NamedArgs{ + "limit": limit, + "offset": offset, + }) + if err != nil { + if err == pgx.ErrNoRows { + return nil, nil // No mints found, return empty slice + } + return nil, fmt.Errorf("failed to query mints: %w", err) + } + mintAddresses, err := pgx.CollectRows(rows, pgx.RowTo[string]) + if err != nil { + return nil, fmt.Errorf("failed to collect mints: %w", err) + } + return mintAddresses, nil +} diff --git a/solana/indexer/dbc/indexer.go b/solana/indexer/dbc/indexer.go index 019342c5..3ece2925 100644 --- a/solana/indexer/dbc/indexer.go +++ b/solana/indexer/dbc/indexer.go @@ -23,8 +23,8 @@ import ( const ( NAME = "DbcIndexer" - MAX_POOLS_PER_SUBSCRIPTION = 10000 // Arbitrary - NOTIFICATION_NAME = "artist_coins_dbc_pool_changed" + MAX_COINS_PER_SUBSCRIPTION = 10000 // Arbitrary + NOTIFICATION_NAME = "artist_coins_mint_changed" ) type Indexer struct { @@ -209,22 +209,22 @@ func (d *Indexer) HandleUpdate(ctx context.Context, msg *pb.SubscribeUpdate) err func (d *Indexer) subscribe(ctx context.Context) ([]common.GrpcClient, error) { done := false page := 0 - pageSize := MAX_POOLS_PER_SUBSCRIPTION + pageSize := MAX_COINS_PER_SUBSCRIPTION total := 0 grpcClients := make([]common.GrpcClient, 0) for !done { - pools, err := getSubscribedDbcPools(ctx, d.pool, pageSize, page*pageSize) + mints, err := common.GetArtistCoinMints(ctx, d.pool, pageSize, page*pageSize) if err != nil { return nil, fmt.Errorf("failed to get pools: %w", err) } - if len(pools) == 0 { - d.logger.Info("no pools to subscribe to") + if len(mints) == 0 { + d.logger.Info("no pool to subscribe to") return grpcClients, nil } - total += len(pools) + total += len(mints) - d.logger.Debug("subscribing to pools....", zap.Int("count", len(pools))) - subscription := d.makeSubscriptionRequest(ctx, pools) + d.logger.Debug("subscribing to pools....", zap.Int("count", len(mints))) + subscription := d.makeSubscriptionRequest(ctx, mints) // Handle each message from the subscription handleMessage := func(ctx context.Context, msg *pb.SubscribeUpdate) { @@ -248,7 +248,7 @@ func (d *Indexer) subscribe(ctx context.Context) ([]common.GrpcClient, error) { } grpcClients = append(grpcClients, grpcClient) - if len(pools) < pageSize { + if len(mints) < pageSize { done = true } page++ @@ -257,7 +257,7 @@ func (d *Indexer) subscribe(ctx context.Context) ([]common.GrpcClient, error) { return grpcClients, nil } -func (d *Indexer) makeSubscriptionRequest(ctx context.Context, pools []string) *pb.SubscribeRequest { +func (d *Indexer) makeSubscriptionRequest(ctx context.Context, mints []string) *pb.SubscribeRequest { commitment := pb.CommitmentLevel_CONFIRMED subscription := &pb.SubscribeRequest{ Commitment: &commitment, @@ -265,11 +265,35 @@ func (d *Indexer) makeSubscriptionRequest(ctx context.Context, pools []string) * // Listen to all watched pools subscription.Accounts = make(map[string]*pb.SubscribeRequestFilterAccounts) - accountFilter := pb.SubscribeRequestFilterAccounts{ - Owner: []string{meteora_dbc.ProgramID.String()}, - Account: pools, + for _, mint := range mints { + accountFilter := 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_DISCRIMINATOR, + }, + }, + }, + }, + { + Filter: &pb.SubscribeRequestFilterAccountsFilter_Memcmp{ + Memcmp: &pb.SubscribeRequestFilterAccountsFilterMemcmp{ + // Base mint is after discriminator, VolatilityTracker, config and creator + Offset: 8 + (8 + 8 + 16 + 16 + 16) + 32 + 32, + Data: &pb.SubscribeRequestFilterAccountsFilterMemcmp_Base58{ + Base58: mint, + }, + }, + }, + }, + }, + } + subscription.Accounts[mint] = &accountFilter } - subscription.Accounts[NAME] = &accountFilter // Ensure this subscription has a checkpoint checkpointId, fromSlot, err := common.EnsureCheckpoint(ctx, NAME, d.pool, d.rpcClient, subscription, d.logger) @@ -313,22 +337,3 @@ func (i *Indexer) processTransaction(ctx context.Context, slot uint64, tx *solan } return nil } - -// Gets the canonical DBC pools from the artist coins table. -func getSubscribedDbcPools(ctx context.Context, db database.DBTX, limit int, offset int) ([]string, error) { - sql := ` - SELECT dbc_pool - FROM artist_coins - WHERE dbc_pool IS NOT NULL - LIMIT @limit OFFSET @offset - ;` - rows, err := db.Query(ctx, sql, pgx.NamedArgs{ - "limit": limit, - "offset": offset, - }) - if err != nil { - return nil, err - } - - return pgx.CollectRows(rows, pgx.RowTo[string]) -} diff --git a/solana/indexer/token/indexer.go b/solana/indexer/token/indexer.go index 2fa31661..aff4a136 100644 --- a/solana/indexer/token/indexer.go +++ b/solana/indexer/token/indexer.go @@ -240,7 +240,7 @@ func (d *Indexer) subscribeToArtistCoins(ctx context.Context, handleUpdate func( grpcClients := make([]common.GrpcClient, 0) total := 0 for !done { - mints, err := getArtistCoins(ctx, d.pool, pageSize, page*pageSize) + mints, err := common.GetArtistCoinMints(ctx, d.pool, pageSize, page*pageSize) if err != nil { return nil, fmt.Errorf("failed to get artist coins: %w", err) } @@ -320,22 +320,3 @@ func (t *Indexer) makeMintSubscriptionRequest(ctx context.Context, mintAddresses return subscription, nil } - -func getArtistCoins(ctx context.Context, db database.DBTX, limit int, offset int) ([]string, error) { - sqlMints := `SELECT mint FROM artist_coins LIMIT @limit OFFSET @offset` - rows, err := db.Query(ctx, sqlMints, pgx.NamedArgs{ - "limit": limit, - "offset": offset, - }) - if err != nil { - if err == pgx.ErrNoRows { - return nil, nil // No mints found, return empty slice - } - return nil, fmt.Errorf("failed to query mints: %w", err) - } - mintAddresses, err := pgx.CollectRows(rows, pgx.RowTo[string]) - if err != nil { - return nil, fmt.Errorf("failed to collect mints: %w", err) - } - return mintAddresses, nil -} diff --git a/solana/spl/programs/meteora_dbc/constants.go b/solana/spl/programs/meteora_dbc/constants.go new file mode 100644 index 00000000..c57e71e5 --- /dev/null +++ b/solana/spl/programs/meteora_dbc/constants.go @@ -0,0 +1,10 @@ +package meteora_dbc + +var ( + // Discriminator for the Meteora DBC Pool account + // See: https://github.com/MeteoraAg/dbc-go/blob/377824fc6056f10ee51074080c92362de52b09da/instructions/state.go#L75C27-L75C68 + POOL_DISCRIMINATOR = []byte{213, 224, 5, 209, 98, 69, 119, 92} + // Discriminator for the Meteora DBC Pool Config account + // See: https://github.com/MeteoraAg/dbc-go/blob/377824fc6056f10ee51074080c92362de52b09da/instructions/state.go#L30 + POOL_CONFIG_DISCRIMINATOR = []byte{26, 108, 14, 123, 116, 230, 129, 43} +) From 5fba7c1f4016a8ad5906eb2e12a84ffb47c1e102 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:16:17 -0700 Subject: [PATCH 4/9] track volatility tracker and metrics for dbc pools --- ddl/migrations/0174_dbc_pool_substructs.sql | 24 ++++++ solana/indexer/dbc/dbc.go | 91 +++++++++++++++++++++ solana/indexer/dbc/indexer.go | 39 ++++++++- 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 ddl/migrations/0174_dbc_pool_substructs.sql diff --git a/ddl/migrations/0174_dbc_pool_substructs.sql b/ddl/migrations/0174_dbc_pool_substructs.sql new file mode 100644 index 00000000..1836f357 --- /dev/null +++ b/ddl/migrations/0174_dbc_pool_substructs.sql @@ -0,0 +1,24 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS sol_meteora_dbc_pool_metrics ( + pool TEXT PRIMARY KEY, + slot BIGINT NOT NULL, + total_protocol_base_fee BIGINT NOT NULL, + total_protocol_quote_fee BIGINT NOT NULL, + total_trading_base_fee BIGINT NOT NULL, + total_trading_quote_fee BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS sol_meteora_dbc_pool_volatility_trackers ( + pool TEXT PRIMARY KEY, + slot BIGINT NOT NULL, + last_update_timestamp BIGINT NOT NULL, + volatility_accumulator NUMERIC NOT NULL, + volatility_reference NUMERIC NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +COMMIT; \ No newline at end of file diff --git a/solana/indexer/dbc/dbc.go b/solana/indexer/dbc/dbc.go index 1c3606e1..f214d92b 100644 --- a/solana/indexer/dbc/dbc.go +++ b/solana/indexer/dbc/dbc.go @@ -279,6 +279,7 @@ func upsertDbcPool( creator_base_fee = EXCLUDED.creator_base_fee, creator_quote_fee = EXCLUDED.creator_quote_fee, updated_at = NOW() + WHERE EXCLUDED.slot > sol_meteora_dbc_pools.slot ;` _, err := db.Exec(ctx, sql, pgx.NamedArgs{ "account": account.String(), @@ -310,3 +311,93 @@ func upsertDbcPool( }) return err } + +func upsertDbcPoolMetrics( + ctx context.Context, + db database.DBTX, + slot uint64, + account solana.PublicKey, + metrics *meteora_dbc.PoolMetrics, +) error { + sql := ` + INSERT INTO sol_meteora_dbc_pool_metrics ( + pool, + slot, + total_protocol_base_fee, + total_protocol_quote_fee, + total_trading_base_fee, + total_trading_quote_fee, + created_at, + updated_at + ) VALUES ( + @pool, + @slot, + @total_protocol_base_fee, + @total_protocol_quote_fee, + @total_trading_base_fee, + @total_trading_quote_fee, + NOW(), + NOW() + ) ON CONFLICT (pool) DO UPDATE SET + slot = EXCLUDED.slot, + total_protocol_base_fee = EXCLUDED.total_protocol_base_fee, + total_protocol_quote_fee = EXCLUDED.total_protocol_quote_fee, + total_trading_base_fee = EXCLUDED.total_trading_base_fee, + total_trading_quote_fee = EXCLUDED.total_trading_quote_fee, + updated_at = NOW() + WHERE EXCLUDED.slot > sol_meteora_dbc_pool_metrics.slot + ;` + + _, err := db.Exec(ctx, sql, pgx.NamedArgs{ + "pool": account.String(), + "slot": slot, + "total_protocol_base_fee": metrics.TotalProtocolBaseFee, + "total_protocol_quote_fee": metrics.TotalProtocolQuoteFee, + "total_trading_base_fee": metrics.TotalTradingBaseFee, + "total_trading_quote_fee": metrics.TotalTradingQuoteFee, + }) + return err +} + +func upsertDbcPoolVolatilityTracker( + ctx context.Context, + db database.DBTX, + slot uint64, + account solana.PublicKey, + volatilityTracker *meteora_dbc.VolatilityTracker, +) error { + sql := ` + INSERT INTO sol_meteora_dbc_pool_volatility_trackers ( + pool, + slot, + last_update_timestamp, + volatility_accumulator, + volatility_reference, + created_at, + updated_at + ) VALUES ( + @pool, + @slot, + @last_update_timestamp, + @volatility_accumulator, + @volatility_reference, + NOW(), + NOW() + ) ON CONFLICT (pool) DO UPDATE SET + slot = EXCLUDED.slot, + last_update_timestamp = EXCLUDED.last_update_timestamp, + volatility_accumulator = EXCLUDED.volatility_accumulator, + volatility_reference = EXCLUDED.volatility_reference, + updated_at = NOW() + WHERE EXCLUDED.slot > sol_meteora_dbc_pool_volatility_trackers.slot + ;` + + _, err := db.Exec(ctx, sql, pgx.NamedArgs{ + "pool": account.String(), + "slot": slot, + "last_update_timestamp": volatilityTracker.LastUpdateTimestamp, + "volatility_accumulator": volatilityTracker.VolatilityAccumulator.String(), + "volatility_reference": volatilityTracker.VolatilityReference.String(), + }) + return err +} diff --git a/solana/indexer/dbc/indexer.go b/solana/indexer/dbc/indexer.go index 3ece2925..64d5d2f7 100644 --- a/solana/indexer/dbc/indexer.go +++ b/solana/indexer/dbc/indexer.go @@ -174,11 +174,12 @@ func (d *Indexer) HandleUpdate(ctx context.Context, msg *pb.SubscribeUpdate) err } account := solana.PublicKeyFromBytes(accountUpdate.Account.Pubkey) - err = upsertDbcPool(ctx, d.pool, accountUpdate.Slot, account, &pool) + + err = processDbcPoolUpdate(ctx, d.pool, accountUpdate.Slot, account, &pool) if err != nil { - return fmt.Errorf("failed to upsert DBC pool: %w", err) + return fmt.Errorf("failed to process DBC pool update: %w", err) } - d.logger.Debug("upserted DBC pool", + d.logger.Debug("processed DBC pool update", zap.String("account", account.String()), zap.String("mint", pool.BaseMint.String()), ) @@ -311,6 +312,38 @@ func (d *Indexer) makeSubscriptionRequest(ctx context.Context, mints []string) * return subscription } +func processDbcPoolUpdate( + ctx context.Context, + db database.DbPool, + slot uint64, + account solana.PublicKey, + pool *meteora_dbc.Pool, +) error { + sqlTx, err := db.Begin(ctx) + if err != nil { + return fmt.Errorf("failed to begin transaction: %w", err) + } + defer sqlTx.Rollback(ctx) + + err = upsertDbcPool(ctx, sqlTx, slot, account, pool) + if err != nil { + return fmt.Errorf("failed to upsert DBC pool: %w", err) + } + err = upsertDbcPoolMetrics(ctx, sqlTx, slot, account, &pool.Metrics) + if err != nil { + return fmt.Errorf("failed to upsert DBC pool metrics: %w", err) + } + err = upsertDbcPoolVolatilityTracker(ctx, sqlTx, slot, account, &pool.VolatilityTracker) + if err != nil { + return fmt.Errorf("failed to upsert DBC volatility tracker: %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( From b1a766b60ed1c7f47050e68f5d43caf5cf7fb33e Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:18:20 -0700 Subject: [PATCH 5/9] Remove DBC pool from create coin endpoint --- api/v1_create_coin.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/api/v1_create_coin.go b/api/v1_create_coin.go index 42340447..e3aa8da7 100644 --- a/api/v1_create_coin.go +++ b/api/v1_create_coin.go @@ -13,13 +13,12 @@ import ( ) type CreateCoinBody struct { - Mint string `json:"mint" validate:"required,solana_address"` - Ticker string `json:"ticker" validate:"required,min=2,max=10,coin_ticker"` - Decimals int32 `json:"decimals" validate:"required,min=0,max=18"` - Name string `json:"name" validate:"required,min=1,max=32"` - LogoUri string `json:"logo_uri" validate:"omitempty,url"` - Description string `json:"description" validate:"max=2500"` - DbcPool *string `json:"dbc_pool"` + Mint string `json:"mint" validate:"required,solana_address"` + Ticker string `json:"ticker" validate:"required,min=2,max=10,coin_ticker"` + Decimals int32 `json:"decimals" validate:"required,min=0,max=18"` + Name string `json:"name" validate:"required,min=1,max=32"` + LogoUri string `json:"logo_uri" validate:"omitempty,url"` + Description string `json:"description" validate:"max=2500"` } func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error { @@ -63,8 +62,8 @@ func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error { } sql := ` - INSERT INTO artist_coins (mint, ticker, user_id, decimals, name, logo_uri, description, dbc_pool) - VALUES (@mint, @ticker, @user_id, @decimals, @name, @logo_uri, @description, @dbc_pool) + INSERT INTO artist_coins (mint, ticker, user_id, decimals, name, logo_uri, description) + VALUES (@mint, @ticker, @user_id, @decimals, @name, @logo_uri, @description) RETURNING mint, ticker, user_id, decimals, name, logo_uri, description, created_at ` @@ -76,7 +75,6 @@ func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error { "name": body.Name, "logo_uri": body.LogoUri, "description": body.Description, - "dbc_pool": body.DbcPool, }) var result struct { From c103dda426c086bb7649a2594f8e7e4c28a4ecdc Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:18:55 -0700 Subject: [PATCH 6/9] remove dbc pool from swagger --- api/swagger/swagger-v1.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/api/swagger/swagger-v1.yaml b/api/swagger/swagger-v1.yaml index 4a0bc310..e83d4342 100644 --- a/api/swagger/swagger-v1.yaml +++ b/api/swagger/swagger-v1.yaml @@ -5664,7 +5664,6 @@ components: - ticker - decimals - name - - dbc_pool properties: mint: type: string @@ -5692,10 +5691,6 @@ components: type: string description: The description of the coin example: "A majestic bear token for wildlife conservation" - dbc_pool: - type: string - description: The address of the dynamic bonding curve pool associated with the coin - example: "2AAsAwNPTNBk5N466xyPiwqdgbc5WLbDTdnn9gVuDKaN" create_coin_response: type: object properties: From a49730264edda0cb17d9000454a4a6ff2643b14b Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 17:31:32 -0700 Subject: [PATCH 7/9] Fix creator fees being off by a factor of 2 --- ddl/functions/calculate_artist_coin_fees.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddl/functions/calculate_artist_coin_fees.sql b/ddl/functions/calculate_artist_coin_fees.sql index 77c53a5f..f68bd83d 100644 --- a/ddl/functions/calculate_artist_coin_fees.sql +++ b/ddl/functions/calculate_artist_coin_fees.sql @@ -41,7 +41,7 @@ RETURNS TABLE ( SELECT base_mint AS mint, total_trading_quote_fee / 2 AS total_dbc_fees, - creator_quote_fee / 2 AS unclaimed_dbc_fees + creator_quote_fee AS unclaimed_dbc_fees FROM artist_coin_pools WHERE base_mint = artist_coin_mint ) From d2f3d7fcf5f240f1f4289f247b76e03304878856 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:13:10 -0700 Subject: [PATCH 8/9] update models, test schema --- api/dbv1/models.go | 31 ++++++++++++++- sql/01_schema.sql | 95 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 103 insertions(+), 23 deletions(-) diff --git a/api/dbv1/models.go b/api/dbv1/models.go index b45b9285..8a243b3c 100644 --- a/api/dbv1/models.go +++ b/api/dbv1/models.go @@ -856,8 +856,6 @@ type ArtistCoin struct { Link4 pgtype.Text `json:"link_4"` // The canonical DAMM V2 pool address for this artist coin, if any. Used in solana indexer. DammV2Pool pgtype.Text `json:"damm_v2_pool"` - // The associated DBC pool address for this artist coin, if any. Used in solana indexer. - DbcPool pgtype.Text `json:"dbc_pool"` } type ArtistCoinPool struct { @@ -1812,6 +1810,27 @@ type SolMeteoraDbcPool struct { UpdatedAt time.Time `json:"updated_at"` } +type SolMeteoraDbcPoolMetric struct { + Pool string `json:"pool"` + Slot int64 `json:"slot"` + TotalProtocolBaseFee int64 `json:"total_protocol_base_fee"` + TotalProtocolQuoteFee int64 `json:"total_protocol_quote_fee"` + TotalTradingBaseFee int64 `json:"total_trading_base_fee"` + TotalTradingQuoteFee int64 `json:"total_trading_quote_fee"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type SolMeteoraDbcPoolVolatilityTracker struct { + Pool string `json:"pool"` + Slot int64 `json:"slot"` + LastUpdateTimestamp int64 `json:"last_update_timestamp"` + VolatilityAccumulator pgtype.Numeric `json:"volatility_accumulator"` + VolatilityReference pgtype.Numeric `json:"volatility_reference"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + // Stores payment router program Route instruction recipients and amounts for tracked mints. type SolPayment struct { Signature string `json:"signature"` @@ -1931,6 +1950,14 @@ type SolTokenTransfer struct { ToAccount string `json:"to_account"` } +type SolUnprocessedTx struct { + Signature string `json:"signature"` + ErrorMessage pgtype.Text `json:"error_message"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Slot int64 `json:"slot"` +} + // Stores the balances of Solana tokens for users. type SolUserBalance struct { UserID int32 `json:"user_id"` diff --git a/sql/01_schema.sql b/sql/01_schema.sql index 7b3413be..8e0c69b7 100644 --- a/sql/01_schema.sql +++ b/sql/01_schema.sql @@ -3,8 +3,8 @@ -- --- Dumped from database version 17.6 (Debian 17.6-1.pgdg13+1) --- Dumped by pg_dump version 17.6 (Debian 17.6-1.pgdg13+1) +-- Dumped from database version 17.6 (Debian 17.6-2.pgdg13+1) +-- Dumped by pg_dump version 17.6 (Debian 17.6-2.pgdg13+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -1017,7 +1017,7 @@ CREATE FUNCTION public.calculate_artist_coin_fees(artist_coin_mint text) RETURNS SELECT base_mint AS mint, total_trading_quote_fee / 2 AS total_dbc_fees, - creator_quote_fee / 2 AS unclaimed_dbc_fees + creator_quote_fee AS unclaimed_dbc_fees FROM artist_coin_pools WHERE base_mint = artist_coin_mint ) @@ -2007,21 +2007,14 @@ BEGIN OR (OLD.mint IS NOT NULL AND NEW.mint IS NULL) OR OLD.mint != NEW.mint THEN - PERFORM pg_notify('artist_coins_mint_changed', NEW.mint); - END IF; - - IF (OLD.dbc_pool IS NULL AND NEW.dbc_pool IS NOT NULL) - OR (OLD.dbc_pool IS NOT NULL AND NEW.dbc_pool IS NULL) - OR OLD.dbc_pool != NEW.dbc_pool - THEN - PERFORM pg_notify('artist_coins_dbc_pool_changed', NEW.dbc_pool); + PERFORM pg_notify('artist_coins_mint_changed', JSON_BUILD_OBJECT('new', NEW.mint, 'old', OLD.mint)::TEXT); END IF; IF (OLD.damm_v2_pool IS NULL AND NEW.damm_v2_pool IS NOT NULL) OR (OLD.damm_v2_pool IS NOT NULL AND NEW.damm_v2_pool IS NULL) OR OLD.damm_v2_pool != NEW.damm_v2_pool THEN - PERFORM pg_notify('artist_coins_damm_v2_pool_changed', NEW.damm_v2_pool); + PERFORM pg_notify('artist_coins_damm_v2_pool_changed', JSON_BUILD_OBJECT('new', NEW.damm_v2_pool, 'old', OLD.damm_v2_pool)::TEXT); END IF; RETURN NEW; @@ -5894,8 +5887,7 @@ CREATE TABLE public.artist_coins ( link_2 text, link_3 text, link_4 text, - damm_v2_pool text, - dbc_pool text + damm_v2_pool text ); @@ -5913,13 +5905,6 @@ COMMENT ON TABLE public.artist_coins IS 'Stores the token mints for artist coins COMMENT ON COLUMN public.artist_coins.damm_v2_pool IS 'The canonical DAMM V2 pool address for this artist coin, if any. Used in solana indexer.'; --- --- Name: COLUMN artist_coins.dbc_pool; Type: COMMENT; Schema: public; Owner: - --- - -COMMENT ON COLUMN public.artist_coins.dbc_pool IS 'The associated DBC pool address for this artist coin, if any. Used in solana indexer.'; - - -- -- Name: associated_wallets; Type: TABLE; Schema: public; Owner: - -- @@ -7512,6 +7497,37 @@ CREATE TABLE public.sol_meteora_dbc_migrations ( COMMENT ON TABLE public.sol_meteora_dbc_migrations IS 'Tracks migrations from DBC pools to DAMM V2 pools.'; +-- +-- Name: sol_meteora_dbc_pool_metrics; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sol_meteora_dbc_pool_metrics ( + pool text NOT NULL, + slot bigint NOT NULL, + total_protocol_base_fee bigint NOT NULL, + total_protocol_quote_fee bigint NOT NULL, + total_trading_base_fee bigint NOT NULL, + total_trading_quote_fee bigint NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); + + +-- +-- Name: sol_meteora_dbc_pool_volatility_trackers; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sol_meteora_dbc_pool_volatility_trackers ( + pool text NOT NULL, + slot bigint NOT NULL, + last_update_timestamp bigint NOT NULL, + volatility_accumulator numeric NOT NULL, + volatility_reference numeric NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); + + -- -- Name: sol_meteora_dbc_pools; Type: TABLE; Schema: public; Owner: - -- @@ -7820,6 +7836,19 @@ CREATE TABLE public.sol_token_transfers ( COMMENT ON TABLE public.sol_token_transfers IS 'Stores SPL token transfers for tracked mints.'; +-- +-- Name: sol_unprocessed_txs; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sol_unprocessed_txs ( + signature text NOT NULL, + error_message text, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + slot bigint DEFAULT 0 NOT NULL +); + + -- -- Name: sol_user_balances; Type: TABLE; Schema: public; Owner: - -- @@ -9361,6 +9390,22 @@ ALTER TABLE ONLY public.sol_meteora_dbc_migrations ADD CONSTRAINT sol_meteora_dbc_migrations_pkey PRIMARY KEY (signature, instruction_index); +-- +-- Name: sol_meteora_dbc_pool_metrics sol_meteora_dbc_pool_metrics_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_pool_metrics + ADD CONSTRAINT sol_meteora_dbc_pool_metrics_pkey PRIMARY KEY (pool); + + +-- +-- Name: sol_meteora_dbc_pool_volatility_trackers sol_meteora_dbc_pool_volatility_trackers_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_meteora_dbc_pool_volatility_trackers + ADD CONSTRAINT sol_meteora_dbc_pool_volatility_trackers_pkey PRIMARY KEY (pool); + + -- -- Name: sol_meteora_dbc_pools sol_meteora_dbc_pools_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -9441,6 +9486,14 @@ ALTER TABLE ONLY public.sol_token_transfers ADD CONSTRAINT sol_token_transfers_pkey PRIMARY KEY (signature, instruction_index); +-- +-- Name: sol_unprocessed_txs sol_unprocessed_txs_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sol_unprocessed_txs + ADD CONSTRAINT sol_unprocessed_txs_pkey PRIMARY KEY (signature); + + -- -- Name: sol_user_balances sol_user_balances_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- From 4d6ce963dbf4506947f7549061732198eb42d068 Mon Sep 17 00:00:00 2001 From: Marcus Pasell <3690498+rickyrombo@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:25:50 -0700 Subject: [PATCH 9/9] fix test --- solana/indexer/dbc/indexer_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/solana/indexer/dbc/indexer_test.go b/solana/indexer/dbc/indexer_test.go index 636928b4..2b66c269 100644 --- a/solana/indexer/dbc/indexer_test.go +++ b/solana/indexer/dbc/indexer_test.go @@ -56,8 +56,8 @@ func TestHandleUpdate_Migration(t *testing.T) { // Add artist coin _, err = pool.Exec(t.Context(), ` - INSERT INTO artist_coins (mint, ticker, name, decimals, user_id, dbc_pool) - VALUES ('bearR26zyyB3fNQm5wWv1ZfN8MPQDUMwaAuoG79b1Yj', 'BEAR', 'Bear', 9, 0, 'J5LCsaaCWcYmzes8qwKmg89zzEtnbYkxFxD9YRU5auPY') + INSERT INTO artist_coins (mint, ticker, name, decimals, user_id) + VALUES ('bearR26zyyB3fNQm5wWv1ZfN8MPQDUMwaAuoG79b1Yj', 'BEAR', 'Bear', 9, 0) `) require.NoError(t, err, "failed to insert artist coin")