-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Cloudflare Pages Functions API only
- Loading branch information
Showing
18 changed files
with
3,945 additions
and
108 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,6 @@ | ||
# Minutes to cache token balances for address | ||
BALANCES_CACHE_INTERVAL_MINUTES="1" | ||
# Minutes to give Moralis to index new addresses | ||
BALANCES_MORALIS_INDEX_DELAY_MINUTES="0" | ||
# Moralis API key for fetching DAO treasury balances | ||
MORALIS_API_KEY="local api key" |
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 |
---|---|---|
|
@@ -38,3 +38,7 @@ yarn-error.log* | |
|
||
# Local Netlify folder | ||
/.netlify | ||
|
||
# Wrangler | ||
/.wrangler | ||
.dev.vars |
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 |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { Context } from 'hono'; | ||
import type { Address } from 'viem'; | ||
import { DefiBalance, NFTBalance, TokenBalance } from '../../src/types/daoTreasury'; | ||
import { withCache } from '../shared/kvCache'; | ||
import { Var, type Env } from '../types'; | ||
|
||
type BalanceMap = { | ||
tokens: TokenBalance[]; | ||
nfts: NFTBalance[]; | ||
defi: DefiBalance[]; | ||
}; | ||
|
||
export async function withBalanceCache<T extends keyof BalanceMap>( | ||
c: Context<{ Bindings: Env; Variables: Var }>, | ||
storeName: T, | ||
fetchFromMoralis: (scope: { chain: string; address: Address }) => Promise<BalanceMap[T]>, | ||
) { | ||
const { address, network } = c.var; | ||
const storeKey = `${storeName}-${network}-${address}`; | ||
|
||
try { | ||
const cacheTimeSeconds = parseInt(c.env.BALANCES_CACHE_INTERVAL_MINUTES) * 60; | ||
const indexingDelaySeconds = parseInt(c.env.BALANCES_MORALIS_INDEX_DELAY_MINUTES) * 60; | ||
|
||
const data = await withCache<BalanceMap[T]>({ | ||
store: c.env.balances, | ||
key: storeKey, | ||
namespace: storeName, | ||
options: { | ||
cacheTimeSeconds, | ||
indexingDelaySeconds, | ||
}, | ||
fetch: async () => { | ||
try { | ||
return await fetchFromMoralis({ chain: network, address }); | ||
} catch (e) { | ||
console.error(`Error fetching from Moralis: ${e}`); | ||
throw new Error('Failed to fetch from Moralis'); | ||
} | ||
}, | ||
}); | ||
|
||
return { data }; | ||
} catch (e) { | ||
console.error(e); | ||
return { error: 'Unexpected error while fetching balances' }; | ||
} | ||
} |
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,83 @@ | ||
import { Hono } from 'hono'; | ||
import { TokenBalance } from '../../src/types/daoTreasury'; | ||
import { fetchMoralis } from '../shared/moralisApi'; | ||
import { DefiResponse, NFTResponse, TokenResponse } from '../shared/moralisTypes'; | ||
import type { Env } from '../types'; | ||
import { withBalanceCache } from './balanceCache'; | ||
import { getParams } from './middleware'; | ||
import { | ||
transformDefiResponse, | ||
transformNFTResponse, | ||
transformTokenResponse, | ||
} from './transformers'; | ||
|
||
const endpoints = { | ||
tokens: { | ||
moralisPath: (address: string) => `/wallets/${address}/tokens`, | ||
transform: transformTokenResponse, | ||
postProcess: (data: TokenBalance[]) => data.filter(token => token.balance !== '0'), | ||
fetch: async ({ chain, address }: { chain: string; address: string }, c: { env: Env }) => { | ||
const result = await fetchMoralis<TokenResponse>({ | ||
endpoint: endpoints.tokens.moralisPath(address), | ||
chain, | ||
apiKey: c.env.MORALIS_API_KEY, | ||
}); | ||
const transformed = result.map(endpoints.tokens.transform); | ||
return endpoints.tokens.postProcess(transformed); | ||
}, | ||
}, | ||
nfts: { | ||
moralisPath: (address: string) => `/${address}/nft`, | ||
transform: transformNFTResponse, | ||
params: { | ||
format: 'decimal', | ||
media_items: 'true', | ||
normalizeMetadata: 'true', | ||
}, | ||
fetch: async ({ chain, address }: { chain: string; address: string }, c: { env: Env }) => { | ||
const result = await fetchMoralis<NFTResponse>({ | ||
endpoint: endpoints.nfts.moralisPath(address), | ||
chain, | ||
apiKey: c.env.MORALIS_API_KEY, | ||
params: endpoints.nfts.params, | ||
}); | ||
return result.map(endpoints.nfts.transform); | ||
}, | ||
}, | ||
defi: { | ||
moralisPath: (address: string) => `/wallets/${address}/defi/positions`, | ||
transform: transformDefiResponse, | ||
fetch: async ({ chain, address }: { chain: string; address: string }, c: { env: Env }) => { | ||
const result = await fetchMoralis<DefiResponse>({ | ||
endpoint: endpoints.defi.moralisPath(address), | ||
chain, | ||
apiKey: c.env.MORALIS_API_KEY, | ||
}); | ||
return result.map(endpoints.defi.transform); | ||
}, | ||
}, | ||
} as const; | ||
|
||
type BalanceType = keyof typeof endpoints; | ||
const ALL_BALANCE_TYPES: BalanceType[] = ['tokens', 'nfts', 'defi']; | ||
|
||
export const router = new Hono<{ Bindings: Env }>().use('*', getParams).get('/', async c => { | ||
const { address, network } = c.var; | ||
const flavors = c.req.queries('flavor') as BalanceType[] | undefined; | ||
const requestedTypes = flavors?.filter(t => ALL_BALANCE_TYPES.includes(t)) ?? ALL_BALANCE_TYPES; | ||
|
||
const results = await Promise.all( | ||
requestedTypes.map(async type => { | ||
const result = await withBalanceCache(c, type, () => | ||
endpoints[type].fetch({ chain: network, address }, c), | ||
); | ||
return [type, result] as const; | ||
}), | ||
); | ||
|
||
const response = Object.fromEntries(results); | ||
if (results.some(([, result]) => 'error' in result)) { | ||
return c.json(response, 503); | ||
} | ||
return c.json(response); | ||
}); |
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,27 @@ | ||
import { createMiddleware } from 'hono/factory'; | ||
import { isAddress } from 'viem'; | ||
import { moralisSupportedChainIds } from '../../src/providers/NetworkConfig/useNetworkConfigStore'; | ||
import type { Env, Var } from '../types'; | ||
|
||
export const getParams = createMiddleware<{ Bindings: Env; Variables: Var }>(async (c, next) => { | ||
const address = c.req.query('address'); | ||
if (!address) { | ||
return c.json({ error: 'Address is required' }, 400); | ||
} | ||
if (!isAddress(address)) { | ||
return c.json({ error: 'Provided address is not a valid address' }, 400); | ||
} | ||
c.set('address', address); | ||
|
||
const network = c.req.query('network'); | ||
if (!network) { | ||
return c.json({ error: 'Network is required' }, 400); | ||
} | ||
const chainId = parseInt(network); | ||
if (!moralisSupportedChainIds.includes(chainId)) { | ||
return c.json({ error: 'Requested network is not supported' }, 400); | ||
} | ||
c.set('network', network); | ||
|
||
await next(); | ||
}); |
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,36 @@ | ||
import { DefiBalance, NFTBalance, TokenBalance } from '../../src/types/daoTreasury'; | ||
import { DefiResponse, NFTResponse, TokenResponse } from '../shared/moralisTypes'; | ||
|
||
export function transformTokenResponse(token: TokenResponse): TokenBalance { | ||
return { | ||
...token, | ||
tokenAddress: token.token_address, | ||
verifiedContract: token.verified_contract, | ||
balanceFormatted: token.balance_formatted, | ||
nativeToken: token.native_token, | ||
portfolioPercentage: token.portfolio_percentage, | ||
logo: token.logo, | ||
thumbnail: token.thumbnail, | ||
usdValue: token.usd_value, | ||
possibleSpam: token.possible_spam, | ||
}; | ||
} | ||
|
||
export function transformNFTResponse(nft: NFTResponse): NFTBalance { | ||
return { | ||
...nft, | ||
tokenAddress: nft.token_address, | ||
tokenId: nft.token_id, | ||
possibleSpam: !!nft.possible_spam, | ||
media: nft.media, | ||
metadata: nft.metadata ? JSON.parse(nft.metadata) : undefined, | ||
tokenUri: nft.token_uri, | ||
name: nft.name || undefined, | ||
symbol: nft.symbol || undefined, | ||
amount: nft.amount ? parseInt(nft.amount) : undefined, | ||
}; | ||
} | ||
|
||
export function transformDefiResponse(defi: DefiResponse): DefiBalance { | ||
return defi; | ||
} |
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,8 @@ | ||
import { Hono } from 'hono'; | ||
import { router as balancesRouter } from './balances'; | ||
import { type Env } from './types'; | ||
|
||
const app = new Hono<{ Bindings: Env }>().basePath('/api').route('/balances', balancesRouter); | ||
|
||
export type AppType = typeof app; | ||
export default app; |
Oops, something went wrong.