Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/position-pricing-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ From the information provided by the hook, Valora can show the user the value of

### Structure

Hooks are organized by application. For instance the Ubeswap hook is located in [`https://github.com/valora-inc/hooks/tree/main/src/apps/ubeswap`](https://github.com/valora-inc/hooks/tree/main/src/apps/ubeswap).
Hooks are organized by application. For instance Ubeswap hooks are located in [`https://github.com/valora-inc/hooks/tree/main/src/apps/ubeswap`](https://github.com/valora-inc/hooks/tree/main/src/apps/ubeswap).

They must implement the [`AppPlugin`](https://github.com/valora-inc/hooks/blob/main/src/plugin.ts) TypeScript interface.
Position pricing hooks must implement the [`PositionsHook`](https://github.com/valora-inc/hooks/blob/main/src/positions.ts) TypeScript interface.

### Creating a Position Pricing Hook

Expand Down
2 changes: 1 addition & 1 deletion scripts/getPositions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable no-console */
import yargs from 'yargs'
import BigNumber from 'bignumber.js'
import { Token } from '../src/plugin'
import { Token } from '../src/positions'
import { getPositions } from '../src/getPositions'

const argv = yargs(process.argv.slice(2))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import plugin from './plugin'
import hook from './positions'

describe('getPositionDefinitions', () => {
it('should get the address definitions successfully', async () => {
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x2b8441ef13333ffa955c9ea5ab5b3692da95260d',
)
Expand Down
8 changes: 4 additions & 4 deletions src/apps/halofi/plugin.ts → src/apps/halofi/positions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import BigNumber from 'bignumber.js'
import { DecimalNumber } from '../../numbers'
import {
AppPlugin,
PositionsHook,
ContractPositionDefinition,
TokenDefinition,
} from '../../plugin'
} from '../../positions'
import got from 'got'

// User-Agent header is required by the HaloFi API
Expand Down Expand Up @@ -100,7 +100,7 @@ type Reward = {
type: string
}

const plugin: AppPlugin = {
const hook: PositionsHook = {
getInfo() {
return {
id: 'halofi',
Expand Down Expand Up @@ -165,4 +165,4 @@ const plugin: AppPlugin = {
},
}

export default plugin
export default hook
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import plugin from './plugin'
import hook from './positions'

describe('getPositionDefinitions', () => {
it('should get the address definitions successfully', async () => {
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x2b8441ef13333ffa955c9ea5ab5b3692da95260d',
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import plugin from './plugin'
import hook from './positions'

jest.mock('viem', () => ({
...jest.requireActual('viem'),
Expand All @@ -15,7 +15,7 @@ describe('getPositionDefinitions', () => {
12n * 10n ** 18n, // 12 locked celo
[[], []], // pending withdrawals
])
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x2b8441ef13333ffa955c9ea5ab5b3692da95260d',
)
Expand All @@ -28,7 +28,7 @@ describe('getPositionDefinitions', () => {
0n, // 0 locked celo
[[], []], // pending withdrawals
])
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x2b8441ef13333ffa955c9ea5ab5b3692da95260d',
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AppPlugin, ContractPositionDefinition } from '../../plugin'
import { PositionsHook, ContractPositionDefinition } from '../../positions'
import {
Address,
ContractFunctionExecutionError,
Expand Down Expand Up @@ -33,7 +33,7 @@ function zip<A, B>(as: readonly A[], bs: readonly B[]) {
return res
}

const plugin: AppPlugin = {
const hook: PositionsHook = {
getInfo() {
return {
id: 'locked-celo',
Expand Down Expand Up @@ -121,4 +121,4 @@ const plugin: AppPlugin = {
},
}

export default plugin
export default hook
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import plugin from './plugin'
import hook from './positions'

describe('getPositionDefinitions', () => {
it('should get the address definitions successfully', async () => {
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x2b8441ef13333ffa955c9ea5ab5b3692da95260d',
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import plugin from './plugin'
import hook from './positions'

jest.mock('viem', () => ({
...jest.requireActual('viem'),
Expand All @@ -24,7 +24,7 @@ describe('getPositionDefinitions', () => {
0n, // 0 cREAL stable debt
2n * 10n ** 18n, // 2 CELO stable debt
])
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
mockNetwork,
mockAddress,
)
Expand Down Expand Up @@ -57,7 +57,7 @@ describe('getPositionDefinitions', () => {
0n, // 0 cREAL stable debt
0n, // 0 CELO stable debt
])
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
mockNetwork,
mockAddress,
)
Expand Down
6 changes: 3 additions & 3 deletions src/apps/moola/plugin.ts → src/apps/moola/positions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AppPlugin, AppTokenPositionDefinition } from '../../plugin'
import { PositionsHook, AppTokenPositionDefinition } from '../../positions'
import { Address, createPublicClient, http } from 'viem'
import { celo } from 'viem/chains'
import { erc20Abi } from '../../abis/erc-20'
Expand Down Expand Up @@ -29,7 +29,7 @@ function getAppTokenPositionDefinition(
}
}

const plugin: AppPlugin = {
const hook: PositionsHook = {
getInfo() {
return {
id: 'moola',
Expand Down Expand Up @@ -59,4 +59,4 @@ const plugin: AppPlugin = {
},
}

export default plugin
export default hook
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import plugin from './plugin'
import hook from './positions'

describe('getPositionDefinitions', () => {
it('should get the address definitions successfully', async () => {
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x2b8441ef13333ffa955c9ea5ab5b3692da95260d',
)
Expand All @@ -11,7 +11,7 @@ describe('getPositionDefinitions', () => {
})

it('should get no definitions for an address with no blockchain interaction', async () => {
const positions = await plugin.getPositionDefinitions(
const positions = await hook.getPositionDefinitions(
'celo',
'0x0000000000000000000000000000000000007E57',
)
Expand Down
8 changes: 4 additions & 4 deletions src/apps/ubeswap/plugin.ts → src/apps/ubeswap/positions.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
AppPlugin,
PositionsHook,
AppTokenPosition,
AppTokenPositionDefinition,
ContractPositionDefinition,
PositionDefinition,
TokenDefinition,
} from '../../plugin'
} from '../../positions'
import got from 'got'
import BigNumber from 'bignumber.js'
import { uniswapV2PairAbi } from './abis/uniswap-v2-pair'
Expand Down Expand Up @@ -253,7 +253,7 @@ async function getFarmPositionDefinitions(
return positions
}

const plugin: AppPlugin = {
const hook: PositionsHook = {
getInfo() {
return {
id: 'ubeswap',
Expand All @@ -275,4 +275,4 @@ const plugin: AppPlugin = {
},
}

export default plugin
export default hook
16 changes: 8 additions & 8 deletions src/getHooks.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { promises as fs } from 'fs'
import path from 'path'
import { AppPlugin } from './plugin'
import { PositionsHook } from './positions'
import { ShortcutsHook } from './shortcuts'

type HookTypeName = 'positions' | 'shortcuts'

type HookType<T> = T extends 'positions'
? AppPlugin
? PositionsHook
: T extends 'shortcuts'
? ShortcutsHook
: never
Expand Down Expand Up @@ -35,7 +35,7 @@ export async function getHooks<T extends HookTypeName>(
hookType: T,
): Promise<Record<string, HookType<T>>> {
const allAppIds = await getAllAppIds()
const plugins: Record<string, HookType<T>> = {}
const hooks: Record<string, HookType<T>> = {}
const appIdsToLoad = appIds.length === 0 ? allAppIds : appIds
for (const appId of appIdsToLoad) {
if (!allAppIds.includes(appId)) {
Expand All @@ -46,9 +46,9 @@ export async function getHooks<T extends HookTypeName>(
)
}

let plugin: any
let hook: any
try {
plugin = await import(`./apps/${appId}/${hookType}`)
hook = await import(`./apps/${appId}/${hookType}`)
} catch (e) {
if (appIds.includes(appId)) {
if ((e as any).code === 'MODULE_NOT_FOUND') {
Expand All @@ -59,9 +59,9 @@ export async function getHooks<T extends HookTypeName>(
throw e
}
}
if (plugin?.default) {
plugins[appId] = plugin.default
if (hook?.default) {
hooks[appId] = hook.default
}
}
return plugins
return hooks
}
53 changes: 6 additions & 47 deletions src/getPositions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
// Allow console logs for now, since we're early in development
/* eslint-disable no-console */
import { promises as fs } from 'fs'
import path from 'path'
import got from 'got'
import BigNumber from 'bignumber.js'
import {
Expand All @@ -15,7 +13,6 @@ import { erc20Abi } from './abis/erc-20'
import {
AbstractToken,
AppInfo,
AppPlugin,
AppTokenPosition,
AppTokenPositionDefinition,
ContractPosition,
Expand All @@ -25,12 +22,13 @@ import {
PositionDefinition,
PricePerShareContext,
Token,
} from './plugin'
} from './positions'
import {
DecimalNumber,
toDecimalNumber,
toSerializedDecimalNumber,
} from './numbers'
import { getHooks } from './getHooks'

interface RawTokenInfo {
address: string
Expand Down Expand Up @@ -58,45 +56,6 @@ const client = createPublicClient({
transport: http(),
})

const APP_ID_PATTERN = /^[a-zA-Z0-9-]+$/

async function getAllAppIds(): Promise<string[]> {
// Read all folders from the "apps" folder
const files = await fs.readdir(path.join(__dirname, 'apps'), {
withFileTypes: true,
})
const folders = files.filter((file) => file.isDirectory())
// Check that all folders are valid app ids
for (const folder of folders) {
if (!APP_ID_PATTERN.test(folder.name)) {
throw new Error(
`Invalid app id: '${folder.name}', must match ${APP_ID_PATTERN}`,
)
}
}
return folders.map((folder) => folder.name)
}

async function getPlugins(
appIds: string[],
): Promise<Record<string, AppPlugin>> {
const allAppIds = await getAllAppIds()
const plugins: Record<string, AppPlugin> = {}
const appIdsToLoad = appIds.length === 0 ? allAppIds : appIds
for (const appId of appIdsToLoad) {
if (!allAppIds.includes(appId)) {
throw new Error(
`No app with id '${appId}' found, available apps: ${allAppIds.join(
', ',
)}`,
)
}
const plugin = await import(`./apps/${appId}/plugin`)
plugins[appId] = plugin.default
}
return plugins
}

async function getBaseTokensInfo(): Promise<TokensInfo> {
// Get base tokens
const data = await got
Expand Down Expand Up @@ -341,11 +300,11 @@ export async function getPositions(
address: string,
appIds: string[] = [],
) {
const pluginsByAppId = await getPlugins(appIds)
const hooksByAppId = await getHooks(appIds, 'positions')

// First get all position definitions for the given address
const definitions = await Promise.all(
Object.entries(pluginsByAppId).map(([appId, plugin]) =>
Object.entries(hooksByAppId).map(([appId, plugin]) =>
plugin.getPositionDefinitions(network, address).then((definitions) => {
return definitions.map((definition) => addAppId(definition, appId))
}),
Expand Down Expand Up @@ -406,7 +365,7 @@ export async function getPositions(
// Assume the token is an app token from the plugin
// TODO: We'll probably need to allow plugins to specify the app id themselves
const { sourceAppId } = tokenDefinition
const plugin = pluginsByAppId[sourceAppId]
const plugin = hooksByAppId[sourceAppId]
const appTokenDefinition = await plugin
.getAppTokenDefinition(tokenDefinition)
.then((definition) => addAppId(definition, sourceAppId))
Expand Down Expand Up @@ -510,7 +469,7 @@ export async function getPositions(

console.log('Resolving definition', type, positionDefinition.address)

const appInfo = pluginsByAppId[positionDefinition.appId].getInfo()
const appInfo = hooksByAppId[positionDefinition.appId].getInfo()

let position: Position
switch (type) {
Expand Down
2 changes: 1 addition & 1 deletion src/plugin.ts → src/positions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DecimalNumber, SerializedDecimalNumber } from './numbers'

// Plugin interface that authors will implement
export interface AppPlugin {
export interface PositionsHook {
getInfo(): AppInfo
// Get position definitions for a given address
getPositionDefinitions(
Expand Down