fix: Meteora DBC pool-status/quote functions were reading the wrong object shape

client.state.getPool()/getPoolByBaseMint()/getPoolsByConfig()/getPoolsByCreator()
in @meteora-ag/dynamic-bonding-curve-sdk all return { poolState: VirtualPool },
not a flat VirtualPool. The existing code guessed at field names via
(pool as any).config || (pool as any).poolConfig-style fallbacks that always
resolved to undefined, so getDbcPoolStatus always reported isMigrated=false,
quoteReserve='0', and empty configAddress/creator regardless of real on-chain
state, and getDbcSwapQuote/V2 called getPoolConfig(undefined) on every
invocation.

Separately, swapQuote()/swapQuote2()'s `virtualPool` param is typed as a flat
VirtualPool in the .d.ts but is actually dereferenced internally as
`virtualPool.poolState.*` (confirmed by reading the shipped dist/index.js) —
the opposite unwrap direction from the pool-status fix. Passing the unwrapped
pool there throws "Cannot read properties of undefined (reading
'quoteReserve')"; passing the untouched wrapper is correct.

Also disabled getDbcPoolConfigs() (unfiltered client.state.getPoolConfigs()
call did not return within 30s live against mainnet — same failure class as
the pre-existing guard on getDbcPools()), pointing callers at
getDbcPoolConfigsByOwner instead.

All fixes verified live against real mainnet DBC pools, including an
end-to-end quote match between the V1 and V2 quote paths on an active
(non-migrated) pool.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
alsk1992
2026-08-31 22:23:00 +01:00
co-authored by Claude Sonnet 5
parent 18eedbe7a2
commit 56c738884a
+58 -23
View File
@@ -446,8 +446,15 @@ export async function getDbcPoolStatus(
}
const poolAddress = poolAccount.publicKey.toBase58();
const pool = poolAccount.account;
const configPk = (pool as any).config || (pool as any).poolConfig;
// poolAccount.account is NOT the flat VirtualPool struct — it's
// { poolState: VirtualPool }. Verified live against a real mainnet pool
// (client.state.getPool/.getPoolByBaseMint both return this same
// wrapper): pool.config/.creator/.migrated/.quoteReserve are all
// undefined at the top level, so every field below was silently
// returning its fallback default (empty string / false / '0') for
// EVERY pool, real or not, regardless of actual on-chain state.
const pool = poolAccount.account.poolState;
const configPk = pool.config;
let progress = 0;
try {
@@ -465,8 +472,8 @@ export async function getDbcPoolStatus(
};
} catch { /* fee metrics may fail for fresh pools */ }
const isMigrated = (pool as any).migrated ?? false;
const quoteReserve = ((pool as any).quoteReserve || (pool as any).totalQuoteReserve || new BN(0)).toString();
const isMigrated = pool.isMigrated ?? false;
const quoteReserve = (pool.quoteReserve ?? new BN(0)).toString();
let migrationThreshold = '0';
try {
@@ -478,8 +485,8 @@ export async function getDbcPoolStatus(
found: true,
poolAddress,
baseMint,
configAddress: configPk ? (configPk.toBase58 ? configPk.toBase58() : String(configPk)) : '',
creator: (pool as any).creator ? ((pool as any).creator.toBase58 ? (pool as any).creator.toBase58() : String((pool as any).creator)) : '',
configAddress: configPk ? configPk.toBase58() : '',
creator: pool.creator ? pool.creator.toBase58() : '',
isMigrated,
quoteReserve,
migrationThreshold,
@@ -529,9 +536,22 @@ export async function getDbcSwapQuote(
const client = await getDbcClient(connection);
const poolAddress = new PublicKey(params.poolAddress);
const pool = await client.state.getPool(poolAddress);
const configPk = (pool as any).config || (pool as any).poolConfig;
const config = await client.state.getPoolConfig(configPk);
// client.state.getPool() returns { poolState: VirtualPool }, not a flat
// VirtualPool — verified live against a real mainnet pool. The old
// (pool as any).config || (pool as any).poolConfig fallback always
// resolved to undefined (neither field exists on the wrapper), so
// getPoolConfig(undefined) was being called on every invocation.
//
// swapQuote()'s `virtualPool` param, despite its .d.ts type reading as a
// flat VirtualPool, is dereferenced internally as `virtualPool.poolState.*`
// (confirmed by reading the shipped dist/index.js — getSwapResult() reads
// virtualPool.poolState.sqrtPrice/.volatilityTracker/.activationPoint) —
// it wants the SAME wrapper client.state.getPool() returns, unwrapped.
// Passing the unwrapped pool here throws "Cannot read properties of
// undefined (reading 'quoteReserve')". Only field-level reads (like in
// getDbcPoolStatus) want the unwrapped .poolState.
const wrapper = await client.state.getPool(poolAddress);
const config = await client.state.getPoolConfig(wrapper.poolState.config);
const getCurrentPointFn = sdk.getCurrentPoint;
const ActivationType = sdk.ActivationType;
@@ -543,7 +563,7 @@ export async function getDbcSwapQuote(
}
const quoteResult = client.pool.swapQuote({
virtualPool: pool,
virtualPool: wrapper,
config,
swapBaseForQuote: params.swapBaseForQuote,
amountIn: new BN(params.amountIn),
@@ -992,9 +1012,13 @@ export async function getDbcSwapQuoteV2(
const SwapMode = sdk.SwapMode;
const poolAddress = new PublicKey(params.poolAddress);
const pool = await client.state.getPool(poolAddress);
const configPk = (pool as any).config || (pool as any).poolConfig;
const config = await client.state.getPoolConfig(configPk);
// See getDbcSwapQuote above — getPool() returns { poolState: VirtualPool },
// not a flat VirtualPool; the old fallback chain always resolved to
// undefined here too. swapQuote2()'s virtualPool param also wants the
// wrapper itself (dereferenced internally as virtualPool.poolState.*),
// same as swapQuote() — confirmed by reading dist/index.js.
const wrapper = await client.state.getPool(poolAddress);
const config = await client.state.getPoolConfig(wrapper.poolState.config);
const getCurrentPointFn = sdk.getCurrentPoint;
const ActivationType = sdk.ActivationType;
@@ -1007,7 +1031,7 @@ export async function getDbcSwapQuoteV2(
let quoteParams: any;
const base = {
virtualPool: pool,
virtualPool: wrapper,
config,
swapBaseForQuote: params.swapBaseForQuote,
hasReferral: false,
@@ -1255,16 +1279,22 @@ export async function claimDbcCreatorFeesV2(
/**
* Get all DBC pool configs.
*
* DELIBERATELY DISABLED — same failure class as getDbcPools() below.
* Verified live: client.state.getPoolConfigs() (unfiltered) did not return
* within 30s against mainnet, consistent with the same unpaginated
* unfiltered-fetch problem that crashes getDbcPools() outright. Configs are
* created less often than pools so this may only hang rather than crash,
* but there's no reason to risk either — use getDbcPoolConfigsByOwner
* instead.
*/
export async function getDbcPoolConfigs(
connection: Connection
_connection: Connection
): Promise<Array<{ address: string; config: any }>> {
const client = await getDbcClient(connection);
const configs = await client.state.getPoolConfigs();
return configs.map((c: any) => ({
address: c.publicKey.toBase58(),
config: c.account,
}));
throw new Error(
'getDbcPoolConfigs() is unfiltered and did not return within 30s against live mainnet in testing (same class of problem as the crash getDbcPools() is guarded against). ' +
'Use getDbcPoolConfigsByOwner(connection, owner) instead.'
);
}
/**
@@ -1313,9 +1343,13 @@ export async function getDbcPoolsByConfig(
): Promise<Array<{ address: string; pool: any }>> {
const client = await getDbcClient(connection);
const pools = await client.state.getPoolsByConfig(configAddress);
// p.account is { poolState: VirtualPool }, not a flat VirtualPool —
// verified live (same wrapper as getPoolByBaseMint/getPool, see
// getDbcPoolStatus above). Unwrapped here so callers get the actual
// pool fields directly instead of a wrapper they'd need to know about.
return pools.map((p: any) => ({
address: p.publicKey.toBase58(),
pool: p.account,
pool: p.account.poolState,
}));
}
@@ -1328,9 +1362,10 @@ export async function getDbcPoolsByCreator(
): Promise<Array<{ address: string; pool: any }>> {
const client = await getDbcClient(connection);
const pools = await client.state.getPoolsByCreator(creator);
// See getDbcPoolsByConfig above — p.account is { poolState: VirtualPool }.
return pools.map((p: any) => ({
address: p.publicKey.toBase58(),
pool: p.account,
pool: p.account.poolState,
}));
}