diff --git a/apps/status-api/README.md b/apps/status-api/README.md
index e53e95d142..f42a445128 100644
--- a/apps/status-api/README.md
+++ b/apps/status-api/README.md
@@ -8,12 +8,29 @@ DeFiChain Status API, providing the statuses of different DeFiChain services.
To decouple the DeFiChain products from the status page, the approach of having a centralised provider to determine each DeFiChain service status with a pre-defined logic will allow it to be maintained consistently throughout. This will allow other apps or services to share the status from the same Status APIs.
-### `/blockchain/status`
+### `/blockchain`
>https://github.com/DeFiCh/jellyfish/issues/1271
-To provide the status of the blockchain based on the block creation time interval.
+To provide the status of the blockchain based on the block creation time interval
+
+| Status | Threshold Time |
+|--------------------|-------------------|
+| `operational` | `< 30 minutes` |
+| `degraded` | `30 - 45 minutes` |
+| `outage` | `> 45 minutes` |
+
+### `/oracles/:address`
+
+To provide the status of each oracle given the address based on the last published time for any given token
+
+
+| Status | Threshold Time |
+|--------------------|------------------|
+| `operational` | `<= 45 minutes` |
+| `outage` | `> 45 minutes` |
+
-### `/overall/status`
+### `/overall`
>https://github.com/DeFiCh/jellyfish/issues/1274
To provide the aggregated status of all services required by Light Wallet & Scan (Blockchain & Ocean).
diff --git a/apps/status-api/__test__/controllers/BlockchainStatusController.test.ts b/apps/status-api/__tests__/controllers/BlockchainStatusController.test.ts
similarity index 100%
rename from apps/status-api/__test__/controllers/BlockchainStatusController.test.ts
rename to apps/status-api/__tests__/controllers/BlockchainStatusController.test.ts
diff --git a/apps/status-api/__tests__/controllers/OracleStatusController.test.ts b/apps/status-api/__tests__/controllers/OracleStatusController.test.ts
new file mode 100644
index 0000000000..c8658f79d9
--- /dev/null
+++ b/apps/status-api/__tests__/controllers/OracleStatusController.test.ts
@@ -0,0 +1,87 @@
+import { StatusApiTesting } from '../../testing/StatusApiTesting'
+import { ApiPagedResponse, WhaleApiClient } from '@defichain/whale-api-client'
+import { Oracle, OraclePriceFeed } from '@defichain/whale-api-client/dist/api/Oracles'
+
+const apiTesting = StatusApiTesting.create()
+
+beforeAll(async () => {
+ await apiTesting.start()
+ jest.spyOn(apiTesting.app.get(WhaleApiClient).oracles, 'getOracleByAddress')
+ .mockReturnValue(getMockedOracle())
+})
+
+afterAll(async () => {
+ await apiTesting.stop()
+})
+
+describe('OracleStatusController - Status test', () => {
+ it('/oracles/
- should get operational as last published < 45 mins ago', async () => {
+ jest.spyOn(apiTesting.app.get(WhaleApiClient).oracles, 'getPriceFeed')
+ .mockReturnValueOnce(getMockedOraclePriceFeed('df1qm7f2cx8vs9lqn8v43034nvckz6dxxpqezfh6dw', 5))
+
+ const res = await apiTesting.app.inject({
+ method: 'GET',
+ url: 'oracles/df1qm7f2cx8vs9lqn8v43034nvckz6dxxpqezfh6dw'
+ })
+ expect(res.json()).toStrictEqual({
+ status: 'operational'
+ })
+ expect(res.statusCode).toStrictEqual(200)
+ })
+
+ it('/oracles/ - should get outage as last published >= 45 mins ago', async () => {
+ jest.spyOn(apiTesting.app.get(WhaleApiClient).oracles, 'getPriceFeed')
+ .mockReturnValueOnce(getMockedOraclePriceFeed('df1qcpp3entq53tdyklm5v0lnvqer4verr4puxchq4', 46))
+
+ const res = await apiTesting.app.inject({
+ method: 'GET',
+ url: 'oracles/df1qcpp3entq53tdyklm5v0lnvqer4verr4puxchq4'
+ })
+ expect(res.json()).toStrictEqual({
+ status: 'outage'
+ })
+ expect(res.statusCode).toStrictEqual(200)
+ })
+})
+
+async function getMockedOraclePriceFeed (oracleAddress: string, minutesDiff: number): Promise> {
+ const blockMedianTime = Date.now() / 1000 - (minutesDiff * 60)
+
+ return new ApiPagedResponse({
+ data: [{
+ block: {
+ medianTime: blockMedianTime,
+ hash: '',
+ height: 0,
+ time: 0
+ },
+ id: '',
+ key: '',
+ sort: '',
+ token: '',
+ currency: '',
+ oracleId: '',
+ txid: '',
+ time: 0,
+ amount: ''
+ }]
+ }, 'GET', `oracles/${oracleAddress}/AAPL-USD/feed`)
+}
+
+async function getMockedOracle (): Promise {
+ return {
+ id: '',
+ block: {
+ hash: '',
+ height: 0,
+ medianTime: 0,
+ time: 0
+ },
+ ownerAddress: '',
+ priceFeeds: [{
+ token: '',
+ currency: ''
+ }],
+ weightage: 0
+ }
+}
diff --git a/apps/status-api/__test__/controllers/OverallStatusController.test.ts b/apps/status-api/__tests__/controllers/OverallStatusController.test.ts
similarity index 100%
rename from apps/status-api/__test__/controllers/OverallStatusController.test.ts
rename to apps/status-api/__tests__/controllers/OverallStatusController.test.ts
diff --git a/apps/status-api/src/controllers/OracleStatusController.ts b/apps/status-api/src/controllers/OracleStatusController.ts
new file mode 100644
index 0000000000..cd47cdf6d4
--- /dev/null
+++ b/apps/status-api/src/controllers/OracleStatusController.ts
@@ -0,0 +1,51 @@
+import { Controller, Get, Param } from '@nestjs/common'
+import { WhaleApiClient } from '@defichain/whale-api-client'
+import { OraclePriceFeed } from '@defichain/whale-api-client/dist/api/Oracles'
+import { SemaphoreCache } from '../../../whale/src/module.api/cache/semaphore.cache'
+
+type OracleStatus = 'outage' | 'operational'
+
+@Controller('oracles')
+export class OracleStatusController {
+ constructor (
+ private readonly client: WhaleApiClient,
+ protected readonly cache: SemaphoreCache
+ ) {
+ }
+
+ /**
+ * To provide the status of each oracle given the address based on the last published time for any given token
+ *
+ * @param oracleAddress
+ * @return {Promise}
+ */
+ @Get('/:address')
+ async getOracleStatus (
+ @Param('address') oracleAddress: string
+ ): Promise<{ status: OracleStatus }> {
+ const oraclePriceFeed: OraclePriceFeed = await this.cachedGet(`oracle-${oracleAddress}`, async () => {
+ const oracle = await this.client.oracles.getOracleByAddress(oracleAddress)
+ return (await this.client.oracles.getPriceFeed(oracle.id, oracle.priceFeeds[0].token, oracle.priceFeeds[0].currency, 1))[0]
+ }, 5000)
+
+ const nowEpoch = Date.now()
+ const latestPublishedTime = oraclePriceFeed.block.medianTime * 1000
+ const timeDiff = nowEpoch - latestPublishedTime
+
+ return {
+ status: timeDiff <= (45 * 60 * 1000) ? 'operational' : 'outage'
+ }
+ }
+
+ private async cachedGet (field: string, fetch: () => Promise, ttl: number): Promise {
+ const object = await this.cache.get(`OracleStatusController.${field}`, fetch, { ttl })
+ return requireValue(object, field)
+ }
+}
+
+function requireValue (value: T | undefined, name: string): T {
+ if (value === undefined) {
+ throw new Error(`failed to compute: ${name}`)
+ }
+ return value
+}
diff --git a/apps/status-api/src/modules/ControllerModule.ts b/apps/status-api/src/modules/ControllerModule.ts
index 8f285a690d..9457512356 100644
--- a/apps/status-api/src/modules/ControllerModule.ts
+++ b/apps/status-api/src/modules/ControllerModule.ts
@@ -3,6 +3,8 @@ import { ActuatorController } from '@defichain-apps/libs/actuator'
import { BlockchainStatusController } from '../controllers/BlockchainStatusController'
import { WhaleApiClient } from '@defichain/whale-api-client'
import { ConfigService } from '@nestjs/config'
+import { SemaphoreCache } from '../../../whale/src/module.api/cache/semaphore.cache'
+import { OracleStatusController } from '../controllers/OracleStatusController'
import { OverallStatusController } from '../controllers/OverallStatusController'
/**
@@ -13,8 +15,10 @@ import { OverallStatusController } from '../controllers/OverallStatusController'
CacheModule.register()
],
controllers: [
+ ActuatorController,
BlockchainStatusController,
ActuatorController,
+ OracleStatusController,
OverallStatusController
],
providers: [
@@ -29,7 +33,8 @@ import { OverallStatusController } from '../controllers/OverallStatusController'
})
},
inject: [ConfigService]
- }
+ },
+ SemaphoreCache
]
})
export class ControllerModule {
diff --git a/apps/whale/src/module.api/oracle.controller.ts b/apps/whale/src/module.api/oracle.controller.ts
index 18ad8f56be..98cb7f6dca 100644
--- a/apps/whale/src/module.api/oracle.controller.ts
+++ b/apps/whale/src/module.api/oracle.controller.ts
@@ -1,8 +1,8 @@
import { Controller, Get, NotFoundException, Param, Query } from '@nestjs/common'
import { Oracle, OracleMapper } from '../module.model/oracle'
import { OraclePriceFeed, OraclePriceFeedMapper } from '../module.model/oracle.price.feed'
-import { ApiPagedResponse } from '../module.api/_core/api.paged.response'
-import { PaginationQuery } from '../module.api/_core/api.query'
+import { ApiPagedResponse } from './_core/api.paged.response'
+import { PaginationQuery } from './_core/api.query'
@Controller('/oracles')
export class OracleController {