-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #226 from 1Hive/token-price-caching
[DONT MERGE BEFORE Uniswap and Network] - Token price caching
- Loading branch information
Showing
12 changed files
with
1,361 additions
and
219 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const { TextDecoder } = require('util'); | ||
|
||
global.TextDecoder = TextDecoder; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
import { BigNumber } from 'ethers'; | ||
import { TokenModel } from './token.model'; | ||
|
||
export type TokenAmountModel = { | ||
parsedAmount: number; | ||
usdValue?: BigNumber; // Only set when fetching from getBalanceOf | ||
usdValue?: number; // Only set when fetching from getBalanceOf | ||
token: TokenModel; | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import request from 'graphql-request'; | ||
import gql from 'graphql-tag'; | ||
import { ENUM_QUEST_STATE, GQL_MAX_INT_MS } from 'src/constants'; | ||
import { FilterModel } from 'src/models/filter.model'; | ||
import { getNetwork } from 'src/networks'; | ||
import { msToSec } from 'src/utils/date.utils'; | ||
|
||
const { questsSubgraph } = getNetwork(); | ||
|
||
const QuestEntityQuery = gql` | ||
query questEntity($ID: String) { | ||
questEntity(id: $ID, subgraphError: allow) { | ||
id | ||
questAddress | ||
questTitle | ||
questDescription | ||
questExpireTimeSec | ||
questDetailsRef | ||
questRewardTokenAddress | ||
creationTimestamp | ||
} | ||
} | ||
`; | ||
|
||
const QuestEntitiesQuery = gql` | ||
query questEntities( | ||
$first: Int | ||
$skip: Int | ||
$expireTimeLower: Int | ||
$expireTimeUpper: Int | ||
$address: String | ||
$title: String | ||
$description: String | ||
) { | ||
questEntities( | ||
first: $first | ||
skip: $skip | ||
where: { | ||
questExpireTimeSec_gte: $expireTimeLower | ||
questExpireTimeSec_lte: $expireTimeUpper | ||
questTitle_contains: $title | ||
questDescription_contains: $description | ||
} | ||
orderBy: creationTimestamp | ||
orderDirection: desc | ||
subgraphError: allow | ||
) { | ||
id | ||
questAddress | ||
questTitle | ||
questDescription | ||
questExpireTimeSec | ||
questDetailsRef | ||
questRewardTokenAddress | ||
creationTimestamp | ||
} | ||
} | ||
`; | ||
|
||
// TODO : Uncoment when subgraph have support for combining where and full text query | ||
// const QuestSearchQuery = gql` | ||
// query questSearch($first: Int, $skip: Int, $text: String) { | ||
// questSearch(first: $first, skip: $skip, text: $text) { | ||
// id | ||
// questAddress | ||
// questTitle | ||
// questDescription | ||
// questExpireTimeSec | ||
// questDetailsRef | ||
// questRewardTokenAddress | ||
// creationTimestamp | ||
// } | ||
// } | ||
// `; | ||
|
||
const QuestRewardTokens = gql` | ||
query questEntities($first: Int) { | ||
questEntities(first: $first, orderBy: creationTimestamp, orderDirection: desc) { | ||
questRewardTokenAddress | ||
} | ||
} | ||
`; | ||
|
||
const QuestEntitiesLight = gql` | ||
query questEntities($expireTimeLower: Int) { | ||
questEntities(where: { questExpireTimeSec_gt: $expireTimeLower }) { | ||
id | ||
questRewardTokenAddress | ||
} | ||
} | ||
`; | ||
|
||
export const fetchQuestEnity = (questAddress: string) => | ||
request(questsSubgraph, QuestEntityQuery, { | ||
ID: questAddress.toLowerCase(), // Subgraph address are stored lowercase | ||
}).then((res) => res.questEntity); | ||
|
||
export const fetchQuestEntities = (currentIndex: number, count: number, filter: FilterModel) => { | ||
let expireTimeLowerMs = 0; | ||
let expireTimeUpperMs = GQL_MAX_INT_MS; | ||
if (filter.status === ENUM_QUEST_STATE.Active) { | ||
expireTimeLowerMs = Math.max(filter.minExpireTime?.getTime() ?? 0, Date.now()); | ||
} else if (filter.status === ENUM_QUEST_STATE.Expired) { | ||
expireTimeLowerMs = Math.min(filter.minExpireTime?.getTime() ?? 0, Date.now()); | ||
expireTimeUpperMs = Date.now(); | ||
} else { | ||
expireTimeLowerMs = filter.minExpireTime?.getTime() ?? 0; | ||
} | ||
return request(questsSubgraph, QuestEntitiesQuery, { | ||
skip: currentIndex, | ||
first: count, | ||
expireTimeLower: Math.round(expireTimeLowerMs / 1000), | ||
expireTimeUpper: Math.round(expireTimeUpperMs / 1000), | ||
title: filter.title, | ||
description: filter.description, | ||
}).then((res) => res.questEntities); | ||
}; | ||
|
||
export const fetchQuestRewardTokens = () => | ||
request(questsSubgraph, QuestRewardTokens, { first: 100 }).then((res) => | ||
res.questEntities.map((quest: any) => quest.questRewardTokenAddress), | ||
); | ||
|
||
export const fetchActiveQuestEntitiesLight = () => | ||
request(questsSubgraph, QuestEntitiesLight, { | ||
expireTimeLower: msToSec(Date.now()), | ||
}); |
110 changes: 110 additions & 0 deletions
110
packages/react-app/src/services/__tests__/quest.service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { BigNumber } from 'ethers'; | ||
import { getDashboardInfo } from '../quest.service'; | ||
|
||
const token1 = '0x6e7c3BC98bee14302AA2A98B4c5C86b13eB4b6Cd'; | ||
const token2 = '0xa0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; | ||
const token3 = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; | ||
|
||
const quests = [ | ||
{ | ||
questAddress: '0x1', | ||
questTitle: 'Quest 1', | ||
questDescription: 'Quest 1 description', | ||
questExpireTimeSec: new Date().getTime() + 1000, | ||
creationTimestamp: new Date().getTime(), | ||
questRewardTokenAddress: token1, | ||
questDetailsRef: '0x1', | ||
}, | ||
{ | ||
questAddress: '0x1', | ||
questTitle: 'Quest 2', | ||
questDescription: 'Quest 2 description', | ||
questExpireTimeSec: new Date().getTime() + 1000, | ||
creationTimestamp: new Date().getTime(), | ||
questRewardTokenAddress: token2, | ||
questDetailsRef: '0x1', | ||
}, | ||
{ | ||
questAddress: '0x1', | ||
questTitle: 'Quest 3', | ||
questDescription: 'Quest 3 description', | ||
questExpireTimeSec: new Date().getTime() + 1000, | ||
creationTimestamp: new Date().getTime(), | ||
questRewardTokenAddress: token3, | ||
questDetailsRef: '0x1', | ||
}, | ||
]; | ||
|
||
const mockFetchQuestEntitiesLight = jest.fn(); | ||
jest.mock('../../../src/queries/quests.query', () => ({ | ||
fetchQuestEntitiesLight: () => mockFetchQuestEntitiesLight(), | ||
})); | ||
|
||
const mockCacheFetchTokenPrice = jest.fn(); | ||
jest.mock('../../../src/services/cache.service', () => ({ | ||
cacheFetchTokenPrice: () => mockCacheFetchTokenPrice(), | ||
})); | ||
|
||
const mockGetERC20Contract = jest.fn(); | ||
const mockGetTokenInfo = jest.fn(); | ||
jest.mock('../../../src/utils/contract.util', () => ({ | ||
getERC20Contract: () => mockGetERC20Contract(), | ||
getTokenInfo: () => mockGetTokenInfo(), | ||
})); | ||
|
||
const mockGetObjectFromIpfs = jest.fn(); | ||
jest.mock('../ipfs.service', () => ({ | ||
getObjectFromIpfs: () => mockGetObjectFromIpfs(), | ||
})); | ||
|
||
describe('QuestService', () => { | ||
describe('getDashboardInfo', () => { | ||
beforeEach(() => { | ||
mockFetchQuestEntitiesLight.mockReturnValue(Promise.resolve({ questEntities: quests })); | ||
mockCacheFetchTokenPrice.mockReturnValue(Promise.resolve(BigNumber.from(1))); | ||
/* (token: TokenModel) => { | ||
switch (token.token) { | ||
case token1: | ||
return Promise.resolve(BigNumber.from(1)); | ||
case token2: | ||
return Promise.resolve(BigNumber.from(2)); | ||
case token3: | ||
return Promise.resolve(BigNumber.from(0)); | ||
default: | ||
return Promise.resolve(BigNumber.from(0)); | ||
} | ||
}); */ | ||
mockGetERC20Contract.mockReturnValue({ | ||
balanceOf: () => Promise.resolve(BigNumber.from(1)), // Always 1 to make test easy | ||
symbol: () => Promise.resolve('TEST'), | ||
name: () => Promise.resolve('TestToken'), | ||
decimals: () => Promise.resolve(18), | ||
}); | ||
mockGetTokenInfo.mockReturnValue( | ||
Promise.resolve({ | ||
symbol: 'TEST', | ||
decimals: 18, | ||
name: 'TestToken', | ||
}), | ||
); | ||
mockGetObjectFromIpfs.mockReturnValue(Promise.resolve('Quest description fetched from ipfs')); | ||
}); | ||
it('should return dashboard correct number of quests', async () => { | ||
// Arrange | ||
|
||
// Act | ||
const res = await getDashboardInfo(); | ||
// Assert | ||
expect(res.questCount === quests.length); | ||
}); | ||
it('should return dashboard correct total', async () => { | ||
// Arrange | ||
|
||
// Act | ||
const res = await getDashboardInfo(); | ||
// Assert | ||
expect(res).toBeTruthy(); | ||
expect(res.totalFunds === 4); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
15d65c5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
quests – ./
quests-git-main-1hive.vercel.app
quests-1hive.vercel.app
quests.vercel.app