Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
176 changes: 56 additions & 120 deletions api/v1_coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,7 @@ import (
"github.com/jackc/pgx/v5"
)

func (app *ApiServer) v1Coin(c *fiber.Ctx) error {
mint := c.Params("mint")
if mint == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "mint parameter is required",
})
}

sql := `
const sharedSql = `
SELECT
artist_coins.name,
artist_coins.mint,
Expand All @@ -28,46 +20,46 @@ func (app *ApiServer) v1Coin(c *fiber.Ctx) error {
artist_coins.link_4,
artist_coins.has_discord,
artist_coins.created_at,
artist_coins.updated_at as coin_updated_at,
COALESCE(artist_coin_stats.market_cap, 0) as market_cap,
COALESCE(artist_coin_stats.fdv, 0) as fdv,
COALESCE(artist_coin_stats.liquidity, 0) as liquidity,
COALESCE(artist_coin_stats.last_trade_unix_time, 0) as last_trade_unix_time,
COALESCE(artist_coin_stats.last_trade_human_time, '') as last_trade_human_time,
COALESCE(artist_coin_stats.price, 0) as price,
COALESCE(artist_coin_stats.history_24h_price, 0) as history_24h_price,
COALESCE(artist_coin_stats.price_change_24h_percent, 0) as price_change_24h_percent,
COALESCE(artist_coin_stats.unique_wallet_24h, 0) as unique_wallet_24h,
COALESCE(artist_coin_stats.unique_wallet_history_24h, 0) as unique_wallet_history_24h,
COALESCE(artist_coin_stats.unique_wallet_24h_change_percent, 0) as unique_wallet_24h_change_percent,
COALESCE(artist_coin_stats.total_supply, 0) as total_supply,
COALESCE(artist_coin_stats.circulating_supply, 0) as circulating_supply,
COALESCE(artist_coin_stats.holder, 0) as holder,
COALESCE(artist_coin_stats.trade_24h, 0) as trade_24h,
COALESCE(artist_coin_stats.trade_history_24h, 0) as trade_history_24h,
COALESCE(artist_coin_stats.trade_24h_change_percent, 0) as trade_24h_change_percent,
COALESCE(artist_coin_stats.sell_24h, 0) as sell_24h,
COALESCE(artist_coin_stats.sell_history_24h, 0) as sell_history_24h,
COALESCE(artist_coin_stats.sell_24h_change_percent, 0) as sell_24h_change_percent,
COALESCE(artist_coin_stats.buy_24h, 0) as buy_24h,
COALESCE(artist_coin_stats.buy_history_24h, 0) as buy_history_24h,
COALESCE(artist_coin_stats.buy_24h_change_percent, 0) as buy_24h_change_percent,
COALESCE(artist_coin_stats.v_24h, 0) as v_24h,
COALESCE(artist_coin_stats.v_24h_usd, 0) as v_24h_usd,
COALESCE(artist_coin_stats.v_history_24h, 0) as v_history_24h,
COALESCE(artist_coin_stats.v_history_24h_usd, 0) as v_history_24h_usd,
COALESCE(artist_coin_stats.v_24h_change_percent, 0) as v_24h_change_percent,
COALESCE(artist_coin_stats.v_buy_24h, 0) as v_buy_24h,
COALESCE(artist_coin_stats.v_buy_24h_usd, 0) as v_buy_24h_usd,
COALESCE(artist_coin_stats.v_buy_history_24h, 0) as v_buy_history_24h,
COALESCE(artist_coin_stats.v_buy_history_24h_usd, 0) as v_buy_history_24h_usd,
COALESCE(artist_coin_stats.v_buy_24h_change_percent, 0) as v_buy_24h_change_percent,
COALESCE(artist_coin_stats.v_sell_24h, 0) as v_sell_24h,
COALESCE(artist_coin_stats.v_sell_24h_usd, 0) as v_sell_24h_usd,
COALESCE(artist_coin_stats.v_sell_history_24h, 0) as v_sell_history_24h,
COALESCE(artist_coin_stats.v_sell_history_24h_usd, 0) as v_sell_history_24h_usd,
COALESCE(artist_coin_stats.v_sell_24h_change_percent, 0) as v_sell_24h_change_percent,
COALESCE(artist_coin_stats.number_markets, 0) as number_markets,
artist_coins.updated_at AS coin_updated_at,
COALESCE(artist_coin_stats.market_cap, 0) AS market_cap,
COALESCE(artist_coin_stats.fdv, 0) AS fdv,
COALESCE(artist_coin_stats.liquidity, 0) AS liquidity,
COALESCE(artist_coin_stats.last_trade_unix_time, 0) AS last_trade_unix_time,
COALESCE(artist_coin_stats.last_trade_human_time, '') AS last_trade_human_time,
COALESCE(artist_coin_stats.price, 0) AS price,
COALESCE(artist_coin_stats.history_24h_price, 0) AS history_24h_price,
COALESCE(artist_coin_stats.price_change_24h_percent, 0) AS price_change_24h_percent,
COALESCE(artist_coin_stats.unique_wallet_24h, 0) AS unique_wallet_24h,
COALESCE(artist_coin_stats.unique_wallet_history_24h, 0) AS unique_wallet_history_24h,
COALESCE(artist_coin_stats.unique_wallet_24h_change_percent, 0) AS unique_wallet_24h_change_percent,
COALESCE(artist_coin_stats.total_supply, 0) AS total_supply,
COALESCE(artist_coin_stats.circulating_supply, 0) AS circulating_supply,
COALESCE(artist_coin_stats.holder, 0) AS holder,
COALESCE(artist_coin_stats.trade_24h, 0) AS trade_24h,
COALESCE(artist_coin_stats.trade_history_24h, 0) AS trade_history_24h,
COALESCE(artist_coin_stats.trade_24h_change_percent, 0) AS trade_24h_change_percent,
COALESCE(artist_coin_stats.sell_24h, 0) AS sell_24h,
COALESCE(artist_coin_stats.sell_history_24h, 0) AS sell_history_24h,
COALESCE(artist_coin_stats.sell_24h_change_percent, 0) AS sell_24h_change_percent,
COALESCE(artist_coin_stats.buy_24h, 0) AS buy_24h,
COALESCE(artist_coin_stats.buy_history_24h, 0) AS buy_history_24h,
COALESCE(artist_coin_stats.buy_24h_change_percent, 0) AS buy_24h_change_percent,
COALESCE(artist_coin_stats.v_24h, 0) AS v_24h,
COALESCE(artist_coin_stats.v_24h_usd, 0) AS v_24h_usd,
COALESCE(artist_coin_stats.v_history_24h, 0) AS v_history_24h,
COALESCE(artist_coin_stats.v_history_24h_usd, 0) AS v_history_24h_usd,
COALESCE(artist_coin_stats.v_24h_change_percent, 0) AS v_24h_change_percent,
COALESCE(artist_coin_stats.v_buy_24h, 0) AS v_buy_24h,
COALESCE(artist_coin_stats.v_buy_24h_usd, 0) AS v_buy_24h_usd,
COALESCE(artist_coin_stats.v_buy_history_24h, 0) AS v_buy_history_24h,
COALESCE(artist_coin_stats.v_buy_history_24h_usd, 0) AS v_buy_history_24h_usd,
COALESCE(artist_coin_stats.v_buy_24h_change_percent, 0) AS v_buy_24h_change_percent,
COALESCE(artist_coin_stats.v_sell_24h, 0) AS v_sell_24h,
COALESCE(artist_coin_stats.v_sell_24h_usd, 0) AS v_sell_24h_usd,
COALESCE(artist_coin_stats.v_sell_history_24h, 0) AS v_sell_history_24h,
COALESCE(artist_coin_stats.v_sell_history_24h_usd, 0) AS v_sell_history_24h_usd,
COALESCE(artist_coin_stats.v_sell_24h_change_percent, 0) AS v_sell_24h_change_percent,
COALESCE(artist_coin_stats.number_markets, 0) AS number_markets,
JSON_BUILD_OBJECT(
'address', COALESCE(artist_coin_pools.address, ''),
'price', COALESCE(artist_coin_pools.price, 0),
Expand All @@ -78,12 +70,25 @@ func (app *ApiServer) v1Coin(c *fiber.Ctx) error {
'totalTradingQuoteFee', COALESCE(artist_coin_pools.total_trading_quote_fee, 0),
'creatorWalletAddress', COALESCE(artist_coin_pools.creator_wallet_address, '')
) AS dynamic_bonding_curve,
COALESCE(artist_coin_stats.updated_at, artist_coins.created_at) as updated_at
ROW_TO_JSON(calculate_artist_coin_fees(artist_coins.mint)) AS artist_fees,
COALESCE(artist_coin_stats.updated_at, artist_coins.created_at) AS updated_at
FROM artist_coins
LEFT JOIN artist_coin_stats
ON artist_coin_stats.mint = artist_coins.mint
LEFT JOIN artist_coin_pools
ON artist_coin_pools.base_mint = artist_coins.mint
`

func (app *ApiServer) v1Coin(c *fiber.Ctx) error {
mint := c.Params("mint")
if mint == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "mint parameter is required",
})
}

sql := `
` + sharedSql + `
WHERE artist_coins.mint = @mint
LIMIT 1
`
Expand Down Expand Up @@ -114,76 +119,7 @@ func (app *ApiServer) v1CoinByTicker(c *fiber.Ctx) error {
}

sql := `
SELECT
artist_coins.name,
artist_coins.mint,
artist_coins.ticker,
artist_coins.decimals,
artist_coins.user_id,
artist_coins.logo_uri,
artist_coins.description,
artist_coins.link_1,
artist_coins.link_2,
artist_coins.link_3,
artist_coins.link_4,
artist_coins.has_discord,
artist_coins.created_at,
artist_coins.updated_at as coin_updated_at,
COALESCE(artist_coin_stats.market_cap, 0) as market_cap,
COALESCE(artist_coin_stats.fdv, 0) as fdv,
COALESCE(artist_coin_stats.liquidity, 0) as liquidity,
COALESCE(artist_coin_stats.last_trade_unix_time, 0) as last_trade_unix_time,
COALESCE(artist_coin_stats.last_trade_human_time, '') as last_trade_human_time,
COALESCE(artist_coin_stats.price, 0) as price,
COALESCE(artist_coin_stats.history_24h_price, 0) as history_24h_price,
COALESCE(artist_coin_stats.price_change_24h_percent, 0) as price_change_24h_percent,
COALESCE(artist_coin_stats.unique_wallet_24h, 0) as unique_wallet_24h,
COALESCE(artist_coin_stats.unique_wallet_history_24h, 0) as unique_wallet_history_24h,
COALESCE(artist_coin_stats.unique_wallet_24h_change_percent, 0) as unique_wallet_24h_change_percent,
COALESCE(artist_coin_stats.total_supply, 0) as total_supply,
COALESCE(artist_coin_stats.circulating_supply, 0) as circulating_supply,
COALESCE(artist_coin_stats.holder, 0) as holder,
COALESCE(artist_coin_stats.trade_24h, 0) as trade_24h,
COALESCE(artist_coin_stats.trade_history_24h, 0) as trade_history_24h,
COALESCE(artist_coin_stats.trade_24h_change_percent, 0) as trade_24h_change_percent,
COALESCE(artist_coin_stats.sell_24h, 0) as sell_24h,
COALESCE(artist_coin_stats.sell_history_24h, 0) as sell_history_24h,
COALESCE(artist_coin_stats.sell_24h_change_percent, 0) as sell_24h_change_percent,
COALESCE(artist_coin_stats.buy_24h, 0) as buy_24h,
COALESCE(artist_coin_stats.buy_history_24h, 0) as buy_history_24h,
COALESCE(artist_coin_stats.buy_24h_change_percent, 0) as buy_24h_change_percent,
COALESCE(artist_coin_stats.v_24h, 0) as v_24h,
COALESCE(artist_coin_stats.v_24h_usd, 0) as v_24h_usd,
COALESCE(artist_coin_stats.v_history_24h, 0) as v_history_24h,
COALESCE(artist_coin_stats.v_history_24h_usd, 0) as v_history_24h_usd,
COALESCE(artist_coin_stats.v_24h_change_percent, 0) as v_24h_change_percent,
COALESCE(artist_coin_stats.v_buy_24h, 0) as v_buy_24h,
COALESCE(artist_coin_stats.v_buy_24h_usd, 0) as v_buy_24h_usd,
COALESCE(artist_coin_stats.v_buy_history_24h, 0) as v_buy_history_24h,
COALESCE(artist_coin_stats.v_buy_history_24h_usd, 0) as v_buy_history_24h_usd,
COALESCE(artist_coin_stats.v_buy_24h_change_percent, 0) as v_buy_24h_change_percent,
COALESCE(artist_coin_stats.v_sell_24h, 0) as v_sell_24h,
COALESCE(artist_coin_stats.v_sell_24h_usd, 0) as v_sell_24h_usd,
COALESCE(artist_coin_stats.v_sell_history_24h, 0) as v_sell_history_24h,
COALESCE(artist_coin_stats.v_sell_history_24h_usd, 0) as v_sell_history_24h_usd,
COALESCE(artist_coin_stats.v_sell_24h_change_percent, 0) as v_sell_24h_change_percent,
COALESCE(artist_coin_stats.number_markets, 0) as number_markets,
JSON_BUILD_OBJECT(
'address', COALESCE(artist_coin_pools.address, ''),
'price', COALESCE(artist_coin_pools.price, 0),
'priceUSD', COALESCE(artist_coin_pools.price_usd, 0),
'curveProgress', COALESCE(artist_coin_pools.curve_progress, 0),
'isMigrated', COALESCE(artist_coin_pools.is_migrated, false),
'creatorQuoteFee', COALESCE(artist_coin_pools.creator_quote_fee, 0),
'totalTradingQuoteFee', COALESCE(artist_coin_pools.total_trading_quote_fee, 0),
'creatorWalletAddress', COALESCE(artist_coin_pools.creator_wallet_address, '')
) AS dynamic_bonding_curve,
COALESCE(artist_coin_stats.updated_at, artist_coins.created_at) as updated_at
FROM artist_coins
LEFT JOIN artist_coin_stats
ON artist_coin_stats.mint = artist_coins.mint
LEFT JOIN artist_coin_pools
ON artist_coin_pools.base_mint = artist_coins.mint
` + sharedSql + `
WHERE artist_coins.ticker = @ticker
LIMIT 1
`
Expand Down
11 changes: 11 additions & 0 deletions api/v1_coins.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ import (
"github.com/jackc/pgx/v5"
)

type ArtistCoinFees struct {
UnclaimedDbcFees float64 `json:"unclaimed_dbc_fees" db:"unclaimed_dbc_fees"`
TotalDbcFees float64 `json:"total_dbc_fees" db:"total_dbc_fees"`
UnclaimedDammV2Fees float64 `json:"unclaimed_damm_v2_fees" db:"unclaimed_damm_v2_fees"`
TotalDammV2Fees float64 `json:"total_damm_v2_fees" db:"total_damm_v2_fees"`
UnclaimedFees float64 `json:"unclaimed_fees" db:"unclaimed_fees"`
TotalFees float64 `json:"total_fees" db:"total_fees"`
}

type ArtistCoin struct {
Name string `json:"name"`
Ticker string `json:"ticker"`
Expand Down Expand Up @@ -65,6 +74,7 @@ type ArtistCoin struct {
VSell24hChangePercent float64 `json:"vSell24hChangePercent" db:"v_sell_24h_change_percent"`
NumberMarkets int `json:"numberMarkets" db:"number_markets"`
DynamicBondingCurve *DynamicBondingCurveInsights `json:"dynamicBondingCurve" db:"dynamic_bonding_curve"`
ArtistFees *ArtistCoinFees `json:"artistFees" db:"artist_fees"`
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
}

Expand Down Expand Up @@ -189,6 +199,7 @@ func (app *ApiServer) v1Coins(c *fiber.Ctx) error {
'totalTradingQuoteFee', COALESCE(artist_coin_pools.total_trading_quote_fee, 0),
'creatorWalletAddress', COALESCE(artist_coin_pools.creator_wallet_address, '')
) AS dynamic_bonding_curve,
ROW_TO_JSON(calculate_artist_coin_fees(artist_coins.mint)) AS artist_fees,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: kinda yucky but because I'm doing ROW_TO_JSON here, the struct deserializes using the JSON struct tag names. Thus the JSON struct tags need to be underscores. To my knowledge, it's not possible to have one JSON struct tag for serializing and another for deserializing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead could we just select the fields off of the resulting row from calculate_artist_coin_fees?

i think this is also okay though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah and use JSON_BUILD_OBJECT? we could do that. I could also make that function return camelCased columns...

COALESCE(artist_coin_stats.updated_at, artist_coins.created_at) as updated_at
FROM artist_coins
LEFT JOIN artist_coin_stats
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func init() {

Cfg.AudiusdChainID = core_config.DevAcdcChainID
Cfg.AudiusdEntityManagerAddress = core_config.DevAcdcAddress
Cfg.SolanaIndexerRetryInterval = 10 * time.Second
case "stage":
fallthrough
case "staging":
Expand Down
55 changes: 55 additions & 0 deletions ddl/functions/calculate_artist_coin_fees.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
BEGIN;
DROP FUNCTION IF EXISTS calculate_artist_coin_fees(TEXT);
CREATE OR REPLACE FUNCTION calculate_artist_coin_fees(artist_coin_mint TEXT)
RETURNS TABLE (
unclaimed_dbc_fees NUMERIC,
total_dbc_fees NUMERIC,
unclaimed_damm_v2_fees NUMERIC,
total_damm_v2_fees NUMERIC,
unclaimed_fees NUMERIC,
total_fees NUMERIC
) LANGUAGE sql AS $function$
WITH
damm_fees AS (
SELECT
pool.token_a_mint AS mint,
(
pool.fee_b_per_liquidity
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

confirming that b is always what we care about (I think so) and that a is our "base" mint

* (
position.unlocked_liquidity + position.vested_liquidity + position.permanent_locked_liquidity
)
/ POWER (2, 128)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the liquidity is represented as an enormous integer but is actually shifted 128 bits - AI says its a method for doing precise math w/ large fixed fractional components (In this case, 128bits for the whole component, and 128bits for the fractional component?)

See: https://github.com/MeteoraAg/damm-v2-sdk/blob/70d1af59689039a1dc700dee8f741db48024d02d/src/helpers/utils.ts#L190-L191

pretty interesting.. wish they had better explainers

+ position.fee_b_pending
) AS total_damm_v2_fees,
(
(pool.fee_b_per_liquidity - position.fee_b_per_token_checkpoint)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is fee_b_per_token_checkpoint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is updated as you claim and marks the amount you've already claimed basically

* (
position.unlocked_liquidity + position.vested_liquidity + position.permanent_locked_liquidity
)
/ POWER (2, 128)
+ position.fee_b_pending
) AS unclaimed_damm_v2_fees
FROM sol_meteora_damm_v2_pools pool
JOIN sol_meteora_dbc_migrations migration ON migration.base_mint = pool.token_a_mint
JOIN sol_meteora_damm_v2_positions position ON position.address = migration.first_position
WHERE pool.token_a_mint = artist_coin_mint
),
dbc_fees AS (
SELECT
base_mint AS mint,
total_trading_quote_fee / 2 AS total_dbc_fees,
creator_quote_fee / 2 AS unclaimed_dbc_fees
FROM artist_coin_pools
WHERE base_mint = artist_coin_mint
)
SELECT
FLOOR(COALESCE(dbc_fees.unclaimed_dbc_fees, 0)) AS unclaimed_dbc_fees,
FLOOR(COALESCE(dbc_fees.total_dbc_fees, 0)) AS total_dbc_fees,
FLOOR(COALESCE(damm_fees.unclaimed_damm_v2_fees, 0)) AS unclaimed_damm_v2_fees,
FLOOR(COALESCE(damm_fees.total_damm_v2_fees, 0)) AS total_damm_v2_fees,
FLOOR(COALESCE(dbc_fees.unclaimed_dbc_fees, 0) + COALESCE(damm_fees.unclaimed_damm_v2_fees, 0)) AS unclaimed_fees,
FLOOR(COALESCE(dbc_fees.total_dbc_fees, 0) + COALESCE(damm_fees.total_damm_v2_fees, 0)) AS total_fees
FROM dbc_fees
FULL OUTER JOIN damm_fees USING (mint);
$function$;
COMMIT;
21 changes: 21 additions & 0 deletions ddl/functions/handle_damm_v2_pool.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CREATE OR REPLACE FUNCTION handle_meteora_dbc_migrations()
RETURNS trigger AS $$
BEGIN
PERFORM pg_notify('meteora_dbc_migration', json_build_object('operation', TG_OP)::text);
RETURN NEW;
EXCEPTION
WHEN OTHERS THEN
RAISE WARNING 'An error occurred in %: %', TG_NAME, SQLERRM;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;

DO $$
BEGIN
CREATE TRIGGER on_meteora_dbc_migrations
AFTER INSERT OR DELETE ON sol_meteora_dbc_migrations
FOR EACH ROW EXECUTE FUNCTION handle_meteora_dbc_migrations();
EXCEPTION
WHEN others THEN NULL; -- Ignore if trigger already exists
END $$;
COMMENT ON TRIGGER on_meteora_dbc_migrations ON sol_meteora_dbc_migrations IS 'Notifies when a DBC pool migrates to a DAMM V2 pool.'
Loading
Loading