diff --git a/sdk/src/accounts/customizedCadenceBulkAccountLoader.ts b/sdk/src/accounts/customizedCadenceBulkAccountLoader.ts index c870cd6a6..0990d69b5 100644 --- a/sdk/src/accounts/customizedCadenceBulkAccountLoader.ts +++ b/sdk/src/accounts/customizedCadenceBulkAccountLoader.ts @@ -35,7 +35,9 @@ export class CustomizedCadenceBulkAccountLoader extends BulkAccountLoader { for (const [key, frequency] of this.accountFrequencies.entries()) { const lastPollTime = this.lastPollingTimes.get(key) || 0; - if (currentTime - lastPollTime >= frequency) { + // Add 200ms buffer to account for timing wiggle room with js execution + const timeDiff = currentTime - lastPollTime; + if (timeDiff >= frequency - 200) { const account = this.accountsToLoad.get(key); if (account) { accountsToLoad.push(account); diff --git a/sdk/src/accounts/externalDataDriftClientSubscriber.ts b/sdk/src/accounts/externalDataDriftClientSubscriber.ts new file mode 100644 index 000000000..25e19ffcc --- /dev/null +++ b/sdk/src/accounts/externalDataDriftClientSubscriber.ts @@ -0,0 +1,76 @@ +import { + PollingDriftClientAccountSubscriber +} from './pollingDriftClientAccountSubscriber'; + +import { + OraclePriceData, + OracleInfo +} from '../oracles/types'; + +import { getOracleId } from '../oracles/oracleId'; + + +// allowing app UI state to incrementally replace RPC fetched acconut data with data from our infra that is pre-indexed and decoded +export class ExternalDataDriftClientSubscriber extends PollingDriftClientAccountSubscriber { + private oracleLastUpdate = new Map(); + private pollingOracles = new Map(); + private oraclePollIntervalId: NodeJS.Timeout; + + constructor(...args: ConstructorParameters) { + super(...args); + + } + + /** Override to prevent oracles from being automatically polled later */ + public override updateOraclesToPoll(): boolean { + return true; + } + + /** Public method to be called externally with fresh oracle data */ + public feedOracle(oracleInfo: OracleInfo, priceData: OraclePriceData, slot: number) { + const oracleId = getOracleId(oracleInfo.publicKey, oracleInfo.source); + this.oracles.set(oracleId, { data: priceData, slot }); + this.oracleLastUpdate.set(oracleId, Date.now()); + if (this.pollingOracles.has(oracleId) || this.accountLoader.accountsToLoad.has(oracleInfo.publicKey.toBase58())) { + const oracleToPoll = this.oraclesToPoll.get(oracleId); + if (oracleToPoll) { + this.accountLoader.removeAccount( + oracleToPoll.publicKey, + oracleToPoll.callbackId + ); + this.pollingOracles.delete(oracleId); + } + } + } + + public override async subscribe(): Promise { + await super.subscribe(); + this.startOraclePollingWatchdog(); + return true; + } + + private startOraclePollingWatchdog() { + if(this.oraclePollIntervalId) { + clearInterval(this.oraclePollIntervalId); + } + // how do we handle not polling bet markets every 1s from this change? + this.oraclePollIntervalId = setInterval(async () => { + for (const [oracleId, lastUpdate] of this.oracleLastUpdate.entries()) { + const oracleToPoll = this.oraclesToPoll.get(oracleId); + if(!oracleToPoll) continue; + const now = Date.now(); + if (now - lastUpdate > 130_000 && !this.pollingOracles.has(oracleId)) { + await this.addOracleToAccountLoader(oracleToPoll); + this.pollingOracles.set(oracleId, true); + } + } + }, 60_000); + } + + public override async unsubscribe(): Promise { + clearInterval(this.oraclePollIntervalId); + await super.unsubscribe(); + this.oracleLastUpdate.clear(); + this.pollingOracles.clear(); + } +} \ No newline at end of file diff --git a/sdk/src/accounts/pollingDriftClientAccountSubscriber.ts b/sdk/src/accounts/pollingDriftClientAccountSubscriber.ts index 18da8df65..05cb5d226 100644 --- a/sdk/src/accounts/pollingDriftClientAccountSubscriber.ts +++ b/sdk/src/accounts/pollingDriftClientAccountSubscriber.ts @@ -546,8 +546,13 @@ export class PollingDriftClientAccountSubscriber for (const oracle of oracles) { const oracleId = getOracleId(oracle.publicKey, oracle.source); - const callbackId = this.oraclesToPoll.get(oracleId).callbackId; - this.accountLoader.removeAccount(oracle.publicKey, callbackId); + const oracleToPoll = this.oraclesToPoll.get(oracleId); + if (oracleToPoll) { + this.accountLoader.removeAccount( + oracleToPoll.publicKey, + oracleToPoll.callbackId + ); + } if (this.delistedMarketSetting === DelistedMarketSetting.Discard) { this.oracles.delete(oracleId); } diff --git a/sdk/src/index.ts b/sdk/src/index.ts index a07ea85d4..8b4973303 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -16,6 +16,7 @@ export * from './accounts/bulkAccountLoader'; export * from './accounts/bulkUserSubscription'; export * from './accounts/bulkUserStatsSubscription'; export { CustomizedCadenceBulkAccountLoader } from './accounts/customizedCadenceBulkAccountLoader'; +export { ExternalDataDriftClientSubscriber } from './accounts/externalDataDriftClientSubscriber'; export * from './accounts/pollingDriftClientAccountSubscriber'; export * from './accounts/pollingOracleAccountSubscriber'; export * from './accounts/pollingTokenAccountSubscriber';