Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
60580ff
fix(graphql): project emission indexes from last_time to now before c…
aditya1702 Jul 22, 2026
c3452d0
fix(graphql): value emissions-APR denominators at projected rates
aditya1702 Jul 22, 2026
fdb735f
fix(graphql): divide position netApy by total supplied, matching blen…
aditya1702 Jul 22, 2026
88e22b0
feat(graphql): split position emissionsApr into supply and borrow str…
aditya1702 Jul 22, 2026
ea1e5ad
feat(graphql): remove blendEarnOptions query
aditya1702 Jul 22, 2026
19f44f3
fix(blend): match Reserve::load's projection guards and unclamped bRa…
aditya1702 Jul 22, 2026
e5497fa
feat(graphql): BlendPoolStatus enum; declare blend fields on base Que…
aditya1702 Jul 22, 2026
6d5ebb5
feat(graphql): per-level cardinality multipliers for blend list fields
aditya1702 Jul 22, 2026
1daa0c2
fix(blend): clamp negative emission index delta, mirroring the contra…
aditya1702 Jul 22, 2026
5af852d
fix(graphql): read backstop LP prices from the pinned Comet contract
aditya1702 Jul 22, 2026
32d6009
docs(graphql): interest cost-basis semantics, LendingChange field map…
aditya1702 Jul 22, 2026
b0965f3
test(integration): vendor Blend auxiliary contract wasms
aditya1702 Jul 13, 2026
54d9813
feat(blend): pin the standalone BLND SAC address
aditya1702 Jul 13, 2026
47c2ba2
test(integration): add salted contract deploy and address precompute …
aditya1702 Jul 13, 2026
e34a36e
test(integration): add per-actor soroban executor and Blend ScVal bui…
aditya1702 Jul 13, 2026
aa4f660
test(integration): add typed Blend contract call wrappers
aditya1702 Jul 13, 2026
332bb90
test(integration): add Blend stack deployment and fixture operations
aditya1702 Jul 13, 2026
00d4168
feat(wbclient): add Blend queries and LendingChange state-change support
aditya1702 Jul 13, 2026
2c2cec1
test(integration): add Blend migration and live-ingestion test suites
aditya1702 Jul 13, 2026
f5fc111
fix(integration): add BLND trustlines for Blend claimers
aditya1702 Jul 13, 2026
b7077f0
fix(integration): allow whale backstop shares to grow from re-staked …
aditya1702 Jul 13, 2026
9185366
test(integration): assert claimed totals from current-state accumulat…
aditya1702 Jul 15, 2026
7c2a0da
fix(blend): pin the standalone canonical backstop so integration fold…
aditya1702 Jul 21, 2026
81c3229
test(integration): adapt wbclient and blend suite to the pr5 API surface
aditya1702 Jul 22, 2026
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
1 change: 1 addition & 0 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (c *serveCmd) Command() *cobra.Command {
utils.StellarEnvironmentOption(&stellarEnvironment),
utils.ServerBaseURLOption(&cfg.ServerBaseURL),
utils.GraphQLComplexityLimitOption(&cfg.GraphQLComplexityLimit),
utils.BlendBackstopLPContractIDOption(&cfg.BlendBackstopLPContractID),
utils.AdminPortOption(&cfg.AdminPort),
{
Name: "port",
Expand Down
44 changes: 28 additions & 16 deletions internal/data/blend/oracle_prices.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,25 +189,37 @@ func (m *OraclePriceModel) GetByOracles(ctx context.Context, oracleIDs []string)
return prices, nil
}

// GetBackstopLPPrices returns every blend_oracle_prices row that shares an
// oracle with a self-priced row (oracle_contract_id = asset_contract_id) —
// i.e. a Comet LP token's self-quoted price plus its sibling BLND price
// quoted under the same oracle.
// GetBackstopLPPrices returns the pinned Comet oracle's price group: its
// self-priced LP-share row (oracle_contract_id = asset_contract_id) plus the
// sibling BLND row quoted under the same oracle.
//
// No de-duplication is needed: (oracle_contract_id, asset_contract_id) is the
// table's primary key, so at most one row per oracle_contract_id can satisfy
// the self-priced predicate (asset = oracle). The self-join therefore cannot
// multiply matches — each output row corresponds to exactly one (self-priced
// row, sibling row) pair, keyed by a distinct oracle_contract_id.
func (m *OraclePriceModel) GetBackstopLPPrices(ctx context.Context) ([]OraclePrice, error) {
// cometID is the configured Comet BLND:USDC pool contract
// (BLEND_BACKSTOP_LP_CONTRACT_ID) — the same pin the price snapshot writer
// targets when quoting the BLND/LP-share leg (see internal/services/blend/
// prices.go). Scoping the query to that one protocol-wide oracle is what makes
// this safe under permissionless pools: a coincidentally self-priced group
// (any pool whose oracle is also one of its own reserve assets) is never
// mistaken for the backstop LP prices.
//
// Returns an empty, non-nil slice WITHOUT querying when cometID is empty (the
// pin is unset): the backstop LP/BLND USD fields then resolve to null
// downstream, exactly like a never-priced asset.
func (m *OraclePriceModel) GetBackstopLPPrices(ctx context.Context, cometID string) ([]OraclePrice, error) {
if cometID == "" {
return []OraclePrice{}, nil
}
cometBytes, err := addressToBytes(cometID)
if err != nil {
return nil, fmt.Errorf("converting comet contract id for backstop LP price lookup: %w", err)
}

start := time.Now()
const query = `
SELECT o2.oracle_contract_id, o2.asset_contract_id, o2.price, o2.price_decimals, o2.price_timestamp, o2.updated_at
FROM blend_oracle_prices o1
JOIN blend_oracle_prices o2 ON o2.oracle_contract_id = o1.oracle_contract_id
WHERE o1.oracle_contract_id = o1.asset_contract_id
ORDER BY o2.oracle_contract_id, o2.asset_contract_id`
rows, err := m.DB.Query(ctx, query)
SELECT oracle_contract_id, asset_contract_id, price, price_decimals, price_timestamp, updated_at
FROM blend_oracle_prices
WHERE oracle_contract_id = $1
ORDER BY oracle_contract_id, asset_contract_id`
rows, err := m.DB.Query(ctx, query, cometBytes)
if err != nil {
m.Metrics.QueryErrors.WithLabelValues("GetBackstopLPPrices", oraclePricesTable, utils.GetDBErrorType(err)).Inc()
return nil, fmt.Errorf("querying blend backstop LP oracle prices: %w", err)
Expand Down
25 changes: 21 additions & 4 deletions internal/data/blend/oracle_prices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,16 @@ func TestOraclePriceModel_GetBackstopLPPrices(t *testing.T) {
ctx, _, m, cleanup := newOraclePricesFixture(t)
defer cleanup()

cometOracle := keypair.MustRandom().Address() // Comet pool contract, self-priced as its own oracle
cometOracle := keypair.MustRandom().Address() // pinned Comet pool contract, self-priced as its own oracle
blndAsset := keypair.MustRandom().Address()
normalOracle := keypair.MustRandom().Address()
normalAsset := keypair.MustRandom().Address()
// A SECOND, unpinned self-priced group: a permissionless pool whose oracle
// is coincidentally one of its own reserve assets. It satisfies the old
// self-priced inference but is NOT the configured Comet contract, so it must
// never be returned.
otherComet := keypair.MustRandom().Address()
otherSibling := keypair.MustRandom().Address()

require.NoError(t, m.BatchUpsert(ctx, []blend.OraclePrice{
// Comet LP self-priced row: asset == oracle.
Expand All @@ -221,17 +227,28 @@ func TestOraclePriceModel_GetBackstopLPPrices(t *testing.T) {
{OracleContractID: types.AddressBytea(cometOracle), AssetContractID: types.AddressBytea(blndAsset), Price: "50", PriceDecimals: 7, PriceTimestamp: 1},
// An unrelated, non-self-priced oracle row must be excluded.
{OracleContractID: types.AddressBytea(normalOracle), AssetContractID: types.AddressBytea(normalAsset), Price: "10", PriceDecimals: 7, PriceTimestamp: 1},
// A second self-priced group under a DIFFERENT oracle must be excluded.
{OracleContractID: types.AddressBytea(otherComet), AssetContractID: types.AddressBytea(otherComet), Price: "999", PriceDecimals: 7, PriceTimestamp: 1},
{OracleContractID: types.AddressBytea(otherComet), AssetContractID: types.AddressBytea(otherSibling), Price: "888", PriceDecimals: 7, PriceTimestamp: 1},
}))

got, err := m.GetBackstopLPPrices(ctx)
got, err := m.GetBackstopLPPrices(ctx, cometOracle)
require.NoError(t, err)
require.Len(t, got, 2, "exactly the Comet self-price row plus its sibling BLND row")
require.Len(t, got, 2, "exactly the pinned Comet self-price row plus its sibling BLND row")

gotPrices := map[types.AddressBytea]string{}
for _, p := range got {
assert.Equal(t, types.AddressBytea(cometOracle), p.OracleContractID)
assert.Equal(t, types.AddressBytea(cometOracle), p.OracleContractID, "only the pinned Comet oracle's rows, never the second self-priced group")
gotPrices[p.AssetContractID] = p.Price
}
assert.Equal(t, "100", gotPrices[types.AddressBytea(cometOracle)])
assert.Equal(t, "50", gotPrices[types.AddressBytea(blndAsset)])

t.Run("empty cometID returns an empty slice without querying", func(t *testing.T) {
m := &blend.OraclePriceModel{} // no DB: proves it never queries
got, err := m.GetBackstopLPPrices(context.Background(), "")
require.NoError(t, err)
require.NotNil(t, got)
require.Empty(t, got)
})
}
28 changes: 1 addition & 27 deletions internal/data/blend/readers.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (m *PoolModel) GetAll(ctx context.Context) ([]Pool, error) {
}

// scanReserves scans every row of rows into a Reserve slice, matching
// blend_reserves' column order. Shared by GetByPools and GetAll.
// blend_reserves' column order.
func scanReserves(rows pgx.Rows) ([]Reserve, error) {
reserves := []Reserve{}
for rows.Next() {
Expand Down Expand Up @@ -369,32 +369,6 @@ func (m *ReserveModel) GetByPools(ctx context.Context, poolIDs []string) ([]Rese
return result, nil
}

// GetAll returns every blend_reserves row, ordered by (pool_contract_id, reserve_index).
func (m *ReserveModel) GetAll(ctx context.Context) ([]Reserve, error) {
start := time.Now()
query := fmt.Sprintf(`
SELECT %s
FROM blend_reserves
ORDER BY pool_contract_id, reserve_index`, reservesSelectColumns)
rows, err := m.DB.Query(ctx, query)
if err != nil {
m.Metrics.QueryErrors.WithLabelValues("GetAll", reservesTable, utils.GetDBErrorType(err)).Inc()
return nil, fmt.Errorf("querying all blend reserves: %w", err)
}
defer rows.Close()

result, err := scanReserves(rows)
if err != nil {
m.Metrics.QueryErrors.WithLabelValues("GetAll", reservesTable, utils.GetDBErrorType(err)).Inc()
return nil, err
}

duration := time.Since(start).Seconds()
m.Metrics.QueryDuration.WithLabelValues("GetAll", reservesTable).Observe(duration)
m.Metrics.QueriesTotal.WithLabelValues("GetAll", reservesTable).Inc()
return result, nil
}

// GetByPools returns the blend_reserve_emissions rows for the given pool
// contract IDs, ordered by (pool_contract_id, reserve_token_id). Returns an
// empty, non-nil slice without querying when poolIDs is empty.
Expand Down
23 changes: 0 additions & 23 deletions internal/data/blend/readers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,29 +248,6 @@ func TestReserveModel_GetByPools(t *testing.T) {
})
}

func TestReserveModel_GetAll(t *testing.T) {
ctx, pool, m, cleanup := newReservesFixture(t)
defer cleanup()

poolA := keypair.MustRandom().Address()
assetA := keypair.MustRandom().Address()
assetB := keypair.MustRandom().Address()
runInTx(t, ctx, pool, func(tx pgx.Tx) {
require.NoError(t, m.BatchUpsert(ctx, tx, []blend.Reserve{
fullReserve(poolA, assetA, 1, 1),
fullReserve(poolA, assetB, 0, 1),
}))
})

got, err := m.GetAll(ctx)
require.NoError(t, err)
require.Len(t, got, 2)
assertOrderedByAddrThen(t, got,
func(r blend.Reserve) types.AddressBytea { return r.PoolContractID },
func(r blend.Reserve) int32 { return r.ReserveIndex },
)
}

func TestReserveEmissionModel_GetByPools(t *testing.T) {
ctx, pool, m, cleanup := newReserveEmissionsFixture(t)
defer cleanup()
Expand Down
Loading
Loading