Skip to content

Commit

Permalink
Add Allora Network machine learning (#149)
Browse files Browse the repository at this point in the history
* Add Allora Network machine learning

* Allora plugin PR feedback
  • Loading branch information
spooktheducks authored Dec 31, 2024
1 parent 578b310 commit 07ca203
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 12 deletions.
5 changes: 5 additions & 0 deletions typescript/examples/vercel-ai/allora/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
OPENAI_API_KEY=
WALLET_PRIVATE_KEY=
ALLORA_API_URL=
ALLORA_API_KEY=
COINGECKO_API_KEY=
15 changes: 15 additions & 0 deletions typescript/examples/vercel-ai/allora/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Vercel AI Allora Example

## Setup

Copy the `.env.template` and populate with your values.

```
cp .env.template .env
```

## Usage

```
npx ts-node index.ts
```
45 changes: 45 additions & 0 deletions typescript/examples/vercel-ai/allora/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { openai } from '@ai-sdk/openai'
import { generateText } from 'ai'

import { http, createWalletClient } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'

import { getOnChainTools } from '@goat-sdk/adapter-vercel-ai'

import { allora } from '@goat-sdk/plugin-allora'
import { coingecko } from "@goat-sdk/plugin-coingecko"
import { viem } from '@goat-sdk/wallet-viem'

require('dotenv').config()

const account = privateKeyToAccount(process.env.WALLET_PRIVATE_KEY as `0x${string}`)

const walletClient = createWalletClient({
account: account,
transport: http(),
chain: sepolia,
})

;(async () => {
const tools = await getOnChainTools({
wallet: viem(walletClient),
plugins: [
allora({
apiKey: process.env.ALLORA_API_KEY,
}),
coingecko({
apiKey: process.env.COINGECKO_API_KEY as string,
}),
],
})

const result = await generateText({
model: openai('gpt-4o-mini'),
tools: tools,
maxSteps: 5,
prompt: 'Can you fetch the current price of ETH and the predicted price in 8 hours and make a recommendation as to whether I should buy or sell?',
})

console.log(result.text)
})()
22 changes: 22 additions & 0 deletions typescript/examples/vercel-ai/allora/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "goat-examples-vercel-ai-allora",
"version": "0.1.0",
"description": "",
"private": true,
"scripts": {
"test": "vitest run --passWithNoTests"
},
"author": "",
"license": "MIT",
"dependencies": {
"@ai-sdk/openai": "^1.0.4",
"@goat-sdk/adapter-vercel-ai": "workspace:*",
"@goat-sdk/core": "workspace:*",
"@goat-sdk/plugin-allora": "workspace:*",
"@goat-sdk/plugin-coingecko": "workspace:*",
"@goat-sdk/wallet-viem": "workspace:*",
"ai": "catalog:",
"dotenv": "^16.4.5",
"viem": "2.21.49"
}
}
8 changes: 8 additions & 0 deletions typescript/examples/vercel-ai/allora/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["index.ts"],
"exclude": ["node_modules", "dist"]
}
38 changes: 38 additions & 0 deletions typescript/packages/plugins/allora/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Goat Allora Plugin 🐐 (TypeScript)

[Allora Network](https://allora.network) plugin for Goat. Allora Network is an AI-powered inference platform that delivers real-time, self-improving predictions and insights for various use cases. By aggregating and analyzing data from diverse sources—such as blockchain networks and off-chain APIs—Allora seamlessly provides low-latency, high-performance predictive analytics without requiring complex infrastructure. The platform’s intuitive approach allows developers to focus on building intelligence-driven solutions, while Allora takes care of the heavy lifting behind the scenes.

## Installation

```
npm install @goat-sdk/plugin-allora
```

## Setup

```typescript
import { allora } from '@goat-sdk/plugin-allora'

const plugin = allora({
apiKey: process.env.ALLORA_API_KEY,
})
```

## Available Actions

### Fetch Price Prediction

Fetches a price prediction for the given asset and timeframe.

## Goat

<div align="center">
Go out and eat some grass.

[Docs](https://ohmygoat.dev) | [Examples](https://github.com/goat-sdk/goat/tree/main/typescript/examples) | [Discord](https://discord.gg/goat-sdk)</div>

## Goat 🐐

Goat 🐐 (Great On-chain Agent Toolkit) is an open-source library enabling AI agents to interact with blockchain protocols and smart contracts via their own wallets.


41 changes: 41 additions & 0 deletions typescript/packages/plugins/allora/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@goat-sdk/plugin-allora",
"version": "0.1.0",
"files": [
"dist/**/*",
"README.md",
"package.json"
],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "vitest run --passWithNoTests"
},
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"dependencies": {
"@goat-sdk/core": "workspace:*",
"zod": "catalog:",
"axios": "1.7.9"
},
"peerDependencies": {
"@goat-sdk/core": "workspace:*"
},
"homepage": "https://ohmygoat.dev",
"repository": {
"type": "git",
"url": "git+https://github.com/goat-sdk/goat.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/goat-sdk/goat/issues"
},
"keywords": [
"ai",
"agents",
"web3",
"allora"
]
}
20 changes: 20 additions & 0 deletions typescript/packages/plugins/allora/src/allora.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { PluginBase } from '@goat-sdk/core'
import { AlloraService } from './allora.service'

export interface AlloraPluginOptions {
apiKey?: string
apiRoot?: string
}

export class AlloraPlugin extends PluginBase {
constructor(opts: AlloraPluginOptions) {
super('allora', [ new AlloraService(opts) ])
}

supportsChain = () => true
}

export function allora(options: AlloraPluginOptions) {
return new AlloraPlugin(options)
}

25 changes: 25 additions & 0 deletions typescript/packages/plugins/allora/src/allora.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Tool } from '@goat-sdk/core'
import { AlloraAPIClient, AlloraPricePredictionToken, AlloraPricePredictionTimeframe } from './api'
import { GetAlloraPricePredictionParameters } from './parameters'

export interface AlloraServiceOptions {
apiKey?: string
apiRoot?: string
}

export class AlloraService {
private readonly client: AlloraAPIClient

constructor(opts: AlloraServiceOptions) {
this.client = new AlloraAPIClient(opts)
}

@Tool({
description: 'Fetch a future price prediction for a crypto asset from Allora Network. Specify 5 minutes from now `5m`, or 8 hours from now `8h`.',
})
async getPricePrediction(parameters: GetAlloraPricePredictionParameters) {
const { ticker, timeframe } = parameters
return this.client.fetchAlloraPricePrediction(ticker as AlloraPricePredictionToken, timeframe as AlloraPricePredictionTimeframe)
}
}

86 changes: 86 additions & 0 deletions typescript/packages/plugins/allora/src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import axios from 'axios'

export interface AlloraInferenceData {
network_inference: string
network_inference_normalized: string
confidence_interval_percentiles: string[]
confidence_interval_percentiles_normalized: string[]
confidence_interval_values: string[]
confidence_interval_values_normalized: string[]
topic_id: string
timestamp: number
extra_data: string
}

export interface AlloraAPIResponse {
request_id: string
status: boolean
data: {
signature: string
inference_data: AlloraInferenceData
}
}

export enum AlloraPricePredictionToken {
BTC = 'BTC',
ETH = 'ETH',
}

export enum AlloraPricePredictionTimeframe {
'5m' = '5m',
'8h' = '8h',
}

export enum AlloraPricePredictionSignatureFormat {
EthereumSepolia = 'ethereum-11155111'
}

export interface AlloraAPIClientOptions {
apiKey?: string
apiRoot?: string
}

export class AlloraAPIClient {
private apiKey: string | null | undefined
private apiRoot: string

constructor(opts: AlloraAPIClientOptions) {
this.apiKey = opts.apiKey

opts.apiRoot = opts.apiRoot || 'https://api.upshot.xyz/v2/allora'
this.apiRoot = opts.apiRoot[opts.apiRoot.length - 1] === '/' ? opts.apiRoot.substr(0, opts.apiRoot.length - 1) : opts.apiRoot
}

public async fetchAlloraPricePrediction(
asset: AlloraPricePredictionToken,
timeframe: AlloraPricePredictionTimeframe,
signatureFormat: AlloraPricePredictionSignatureFormat = AlloraPricePredictionSignatureFormat.EthereumSepolia,
): Promise<Partial<AlloraInferenceData>> {
const url = `consumer/price/${signatureFormat}/${asset}/${timeframe}`
const resp = await this.fetchAlloraAPIData(url)
if (!resp?.data?.inference_data) {
throw new Error(`API response missing data: ${JSON.stringify(resp)}`)
}
return resp.data.inference_data
}

private async fetchAlloraAPIData(endpoint: string): Promise<Partial<AlloraAPIResponse>> {
endpoint = endpoint[0] === '/' ? endpoint.substr(1) : endpoint

const url = `${this.apiRoot}/${endpoint}`
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Accept': 'application/json',
}
if (!!this.apiKey) {
headers['x-api-key'] = this.apiKey
}

const response = await axios.get(url, { headers })
if (response.status >= 400) {
throw new Error(`Allora plugin: error requesting price prediction: url=${url} status=${response.status} body=${JSON.stringify(response.data, null, 4)}`)
}

return response.data
}
}
2 changes: 2 additions & 0 deletions typescript/packages/plugins/allora/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './allora.plugin'
export * from './parameters'
9 changes: 9 additions & 0 deletions typescript/packages/plugins/allora/src/parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createToolParameters } from '@goat-sdk/core'
import { z } from 'zod'

export class GetAlloraPricePredictionParameters extends createToolParameters(
z.object({
ticker: z.enum(['BTC', 'ETH']).describe('The ticker of the currency for which to fetch a price prediction.'),
timeframe: z.enum(['5m', '8h']).describe('The timeframe for the prediction (currently, either "5m" or "8h").'),
})
) {}
6 changes: 6 additions & 0 deletions typescript/packages/plugins/allora/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../../tsconfig.base.json",
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
6 changes: 6 additions & 0 deletions typescript/packages/plugins/allora/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from 'tsup'
import { treeShakableConfig } from '../../../tsup.config.base'

export default defineConfig({
...treeShakableConfig,
})
11 changes: 11 additions & 0 deletions typescript/packages/plugins/allora/turbo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"tasks": {
"build": {
"inputs": ["src/**", "tsup.config.ts", "!./**/*.test.{ts,tsx}", "tsconfig.json"],
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
Loading

0 comments on commit 07ca203

Please sign in to comment.