UNPKG

dria-oracle-sdk

Version:

An on-chain LLM Oracle SDK for Dria

461 lines (452 loc) 17.9 kB
import { Hex, Address, Transport, Chain, PublicClient, WalletClient, Account, Prettify, BlockTag } from 'viem'; import { ArweaveIrys } from '@irys/sdk/node/flavours/arweave'; /** Re-export of the `JWKInterface` type, which represents an Arweave wallet. * * @example * const path = "./wallet.json"; * const key = JSON.parse(fs.readFileSync(path, "utf-8")) as ArweaveWallet; */ type ArweaveWallet = ConstructorParameters<typeof ArweaveIrys>[0]["key"]; /** An Arweave key is considered to be an object with `arweave` field. */ type ArweaveStorageKey = { arweave: string; }; /** * An Arweave wrapper for decentralized storage interface. * * It can be used to read Arweave values from the coordinator, as well as put Arweave-uploaded value to the coordinator. * * Note that for `write` operations, it uses Irys SDK to interact with the Arweave network, * for `put` and `balance` commands ,so it requires `@irys/sdk` peer dependency for those. * * @example * // read only * const storage = new ArweaveStorage(); * * @example * // read & write * const storage = new ArweaveStorage(); * const key: JWInterface = JSON.parse(fs.readFileSync("./wallet.json", "utf-8")); * storage.init(key); */ declare class ArweaveStorage implements DecentralizedStorage<Buffer, ArweaveStorageKey> { /** Byte threshold, beyond which data is uploaded to Arweave. */ bytesLimit: number; /** Irys SDK instance. */ irys?: ArweaveIrys; /** Base URL of the gateway. */ baseUrl: string; /** * Initializes the Arweave storage with the given key. * @param key Arweave wallet object. * @param bytesLimit Number of bytes such that smaller data are not uploaded, default is 1024 bytes. */ init(key: ArweaveWallet, bytesLimit?: number): void; /** Return an `ArweaveKey` object if `key` is a stringified object of the form `{ arweave: string }`. * * @example // a valid key * { arweave: "Zg6CZYfxXCWYnCuKEpnZCYfy7ghit1_v4-BCe53iWuA" } */ isKey(key: string): ArweaveStorageKey | null; get(key: ArweaveStorageKey): Promise<Buffer | null>; /** * Uploads the given value to Arweave and returns the key. * * _Requires `init` to have been called._ * * @param value Value to upload. * @returns Key to access the value. */ put(value: Buffer): Promise<ArweaveStorageKey>; /** * Returns the balance of the Arweave wallet. * * _Requires `init` to have been called._ * * @returns Balance in `bigint`. */ balance(): Promise<bigint>; describe(): string; } /** * A key-value storage that can be used to store arbitrary data in a decentralized-storage fashion. * Most often, the `T` is a `Buffer` and `K` is anything custom defined. * * @template T The type of the data to store. * @template K The type of the key to use. */ interface DecentralizedStorage<T = unknown, K = string> { /** The number of bytes, after which storage shall be considered. */ bytesLimit: number; /** * @param key The key to get the value for. * @returns The value at the key, or `null` if it does not exist. */ get(key: K): Promise<T | null>; /** * Puts a value to the storage, returns the key. * @param value The value to put in the storage. * @returns The key that can be used to retrieve the value. */ put(value: T): Promise<K>; /** * @param key The key to check. * @returns Whether the key if its valid, or `null` if its invalid. */ isKey(key: string): K | null; /** Returns the name of the storage. */ describe(): string; } /** * Task status as it appears within the contract. * * - `None`: Task has not been created yet. (default) * - `PendingGeneration`: Task is waiting for Oracle generation responses. * - `PendingValidation`: Task is waiting for validation by validator Oracles. * - `Completed`: The task has been completed. * * There are two scenarios: * - With validation, the flow is `None -> PendingGeneration -> PendingValidation -> Completed`. * - Without validation, the flow is `None -> PendingGeneration -> Completed`. * * Note that this type is compatible with the contract type (number). */ declare enum TaskStatus { None = 0, PendingGeneration = 1, PendingValidation = 2, Completed = 3 } /** Collection of oracle task-related parameters. */ interface TaskParameters { /** Difficulty of the task. */ difficulty: number; /** Number of generations. */ numGenerations: number; /** Number of validations. */ numValidations: number; } /** * A task request for LLM generation. * Fees are stored here as well in case fee changes occur within the duration of a task. * * Adapted from [`LLMOracleTask.sol`](https://github.com/firstbatchxyz/dria-oracle-contracts/blob/master/src/LLMOracleTask.sol#L34). */ interface TaskRequest { /** Requesting address, also responsible of the fee payment. */ requester: Address; /** Protocol string, such as `dria/0.1.0`. */ protocol: string; /** Task parameters, e.g. difficulty and number of generations & validations. */ parameters: TaskParameters; /** Task status. */ status: TaskStatus; /** Fee paid to each generator per generation. */ generatorFee: bigint; /** Fee paid to each validator per validated generation. */ validatorFee: bigint; /** Fee paid to the platform */ platformFee: bigint; /** Input data for the task, usually a human-readable string. */ input: string; /** Allowed model names for the task. */ models: string; } /** A task generation response. * @template O Output data type, defaults `Hex`. * @template M Metadata type, defaults `Hex`. */ interface TaskResponse<O = Hex, M = Hex> { /** Responding Oracle address. */ responder: Address; /** Proof-of-Work nonce for SHA3(taskId, input, requester, responder, nonce) < difficulty. */ nonce: bigint; /** Final validation score assigned by validators, stays 0 if there is no validation. */ score: bigint; /** Output data for the task, usually the direct output of LLM. */ output: O; /** Optional metadata for this generation. */ metadata: M; } /** A task validation for a response. * @template M Metadata type, defaults `Hex`. */ interface TaskValidation<M = Hex> { /** Responding validator address. */ validator: Address; /** Proof-of-Work nonce for SHA3(taskId, input, requester, responder, nonce) < difficulty. */ nonce: bigint; /** Validation scores */ scores: readonly bigint[]; /** Optional metadata for this validation. */ metadata: M; } /** A task validaiton score object. * * Within a task validation, we usually expect an array of these objects, * one for each generation. * * The `final_score` here is the actual score considered by the contract, * and `rationale` describes how the LLM decided that score. * * A score is expected to be a number between 1 and 5, inclusive; where 1 is worst and 5 is best. */ type TaskValidationScores = { helpfulness: number; instruction_following: number; truthfulness: number; /** The final score given by the node. */ final_score: number; rationale: string; }; /** A chat history entry. */ type ChatHistoryResponse = { /** Role, usually `user`, `assistant` or `system`. */ role: string; /** Message content. */ content: string; }; /** A request with chat history. */ type ChatHistoryRequest = { /** Task id of which the output will act like history. */ history_id: number | bigint; /** Message content. */ content: string; }; /** Return type for `request` function. */ type NewRequestReturnType = { txHash: Hex; protocol: string; input: string; models: Models; taskParameters: TaskParameters; }; /** Optional arguments for `request`. */ type TaskRequestOptions = { taskParameters?: Partial<TaskParameters>; protocol?: string; }; /** * Allowed Oracle models. * * The requested model(s) can be any of the following: * * - An array of model names, such as `["gemini-1.5-pro", "gpt-4o-mini"]`. * - `*` for any model randomly (of the responder). * - `!` for the first model (of the responder). * * You can look at the available models from [this repository](https://github.com/andthattoo/ollama-workflows/blob/main/src/program/models.rs#L14). */ type Models = string[] | "*" | "!"; /** * The Oracle client is used to interact with the Dria Oracles. It allows you to make requests, read responses, and process them. * * It can be instantiated with a `storage` as well, which can be used to store large data in a decentralized manner. * If the data to be written to contract is large, we can instead store that data in the storage and pass the key to the contract. * This key can then be used to fetch the data from the storage. * * @template T transport type, e.g. HTTP or WebSocket (usually inferred) * @template C chain type, e.g. Ethereum or Binance Smart Chain (usually inferred) * @template K storage key type, e.g. `ArweaveKey` (usually inferred) * @example * // without storage * const oracle = new Oracle({ public, wallet }); * await oracle.init(coordinatorAddress); * * @example * // with storage (Arweave) * const wallet = JSON.parse(readFileSync("./path/to/wallet.json", "utf-8")); * const arweave = new ArweaveStorage(wallet); * const oracle = new Oracle({ public, wallet }, arweave); * await oracle.init(coordinatorAddress); */ declare class Oracle<T extends Transport, C extends Chain, K = unknown> { readonly client: { public: PublicClient<T, C>; wallet: WalletClient<T, C, Account>; }; readonly storage?: DecentralizedStorage<Buffer, K> | undefined; coordinator?: ReturnType<InstanceType<typeof Oracle<Transport, Chain, K>>["Coordinator"]>; token?: ReturnType<InstanceType<typeof Oracle<Transport, Chain, K>>["Token"]>; taskParameters: TaskParameters; protocol: string; constructor(client: { public: PublicClient<T, C>; wallet: WalletClient<T, C, Account>; }, storage?: DecentralizedStorage<Buffer, K> | undefined); /** * Initialize the oracle client by setting up contract instances. * @param coordinatorAddress coordinator contract address * @returns initialized oracle client */ init(coordinatorAddress: Address): Promise<this>; /** * Change the underlying default task parameters. * @param opts new default task parameters */ withParameters(opts: Partial<TaskParameters>): this; /** * Change the underlying default protocol. * * The protocol is a string that should fit a `bytes32` type in Solidity. It is used * to identify the source of the request, and can be used within event filters. * * It should have to format `name/version`, e.g. `dria-oracle-sdk/0.x.x`. * @param protocol protocol name */ withProtocol(protocol: string): this; /** Returns a new instance of the LLM coordinator contract. * @warning This is an internal method, and making it `private` will break * the type export due to the size of ABI-inferred types. */ private Coordinator; /** Returns a new instance of an ERC20 token contract. * @warning This is an internal method, and making it `private` will break * the type export due to the size of ABI-inferred types. */ private Token; /** * Make an oracle request. * @param input input string, or a chat input * - a string input can be anything, like "What is 2+2?" * - a chat input is an object `{historyId: number, content: string}` where * the `historyId` is a task id the output of which is to be used as history * @param models requested models, can be any of the following: * - `*` for all models * - `!` for first model of the responder * - `["model1", "model2", ...]` for specific models * * (defaults to `*`) * @param opts optional request arguments, such as `protocol` and `taskParameters` * @returns task transaction hash */ request(input: string, models: Models, opts?: TaskRequestOptions): Promise<NewRequestReturnType>; request(input: Prettify<ChatHistoryRequest>, models: Models, opts?: TaskRequestOptions): Promise<NewRequestReturnType>; /** * Waits until the request transaction is mined and returns the task id. * @param txHash transaction hash for the request (see `request`) * @returns taskId */ waitRequest(txHash: Hex): Promise<bigint>; /** * Alias for `getBestResponse` followed by `processResponse`. * @param taskId task id * @param kind task kind, e.g. `chat` for conversational models * @returns processed task response */ read(taskId: bigint): Promise<TaskResponse<string | null, string | null>>; /** * Returns a boolean indicating if the task is completed. * @param taskId task id * @returns true if the task is completed, or `taskId` is 0 */ isCompleted(taskId: bigint | number): Promise<boolean>; /** * Returns the task request for a given task id. * @param taskId task id * @returns task request */ getRequest(taskId: bigint | number): Promise<TaskRequest>; /** * Returns the highest scored response of a task. * Will throw an error if the task is not completed yet! * @param taskId task id * @returns task response with the highest score */ getBestResponse(taskId: bigint | number): Promise<Prettify<TaskResponse>>; /** * Returns the validations of all generation responses for a task. * @param taskId task id * @returns array of task validations */ getValidations(taskId: bigint | number): Promise<readonly Prettify<TaskValidation>[]>; /** * Returns the generation responses for a task. * @param taskId task id * @returns array of task responses */ getResponses(taskId: bigint | number): Promise<readonly Prettify<TaskResponse>[]>; /** * Process the `output` and `metadata` of a task, with respect to the given storage. * @param response existing response object * @returns response object with processed `output` and `metadata` */ processResponse(response: TaskResponse): Promise<TaskResponse<string | null, string | null>>; /** * Process the `metadata` of a task, with respect to the given storage. * @param response existing validation object * @returns validation object with processed `metadata` */ processValidation(validation: TaskValidation): Promise<TaskValidation<TaskValidationScores[]>>; /** Shorthand to parse a string input to a chat history response. */ toChatHistory(input: string): Prettify<ChatHistoryResponse>[]; /** * Fetches the task events from the coordinator contract and returns its args. * A returned task event has the taskId, protocol, statusBefore, and statusAfter. * For a completed task, we are looking for the statusAfter to be `Completed` which is `3`. * * @param opts options for fetching tasks * - `protocol`: protocol name * - `from`: block to start from * - `to`: block to end at * - `status`: task status to filter by * @returns array of task events */ getTaskEvents(opts: { protocol?: string; from?: bigint | BlockTag; to?: bigint | BlockTag; status?: TaskStatus; }): Promise<{ taskId?: bigint | undefined; protocol?: `0x${string}` | undefined; statusBefore?: number | undefined; statusAfter?: number | undefined; }[]>; /** * Waits for a task to be completed, i.e. it should have all the required * generations and validations be done. * @param taskId task id */ wait(taskId: bigint | number): Promise<void>; /** Returns the allowance of the client for the coordinator. * @returns allowance amount */ allowance(): Promise<bigint>; /** * Approves the coordinator to spend the client's tokens. * @param amount amount to approve, defaults to max uint256 (infinite) * @returns transaction hash */ approve(amount?: bigint): Promise<Hex>; } /** * Given a string, converts it to a `Hex` string. * * First, the string is converted to a `Uint8Array`. * - If `storage` is given and the bytearray is large enough, it is uploaded to the storage and its key in `Hex` is returned. * - Otherwise, the bytearray is converted to a `Hex` string. * * If storage is being used, make sure it can upload the data, e.g. `ArweaveStorage` must be `init`ed. * * @param bytes input string * @param storage decentralized storage, optional * @template K storage key type, inferred as `unknown` if storage is `undefined` * @returns a `Hex` string, with 0x prefix */ declare function stringToContractBytesWithStorage<K>(input: string, storage?: DecentralizedStorage<Buffer, K>): Promise<Hex>; /** * Given a `bytes` Solidity type, converts it to a string. * * If `storage` is given, the resulting string is try-parsed as a key, and if it is a key, * the actual value is fetched from the storage. * * If no value is found at the storage, it returns `null`. * * @param input bytes * @param storage decentralized storage, optional * @template K storage key type, inferred as `unknown` if storage is `undefined` * @returns parsed string */ declare function contractBytesToStringWithStorage<K>(input: Hex, storage?: DecentralizedStorage<Buffer, K>): Promise<string | null>; export { ArweaveStorage, type ArweaveWallet, type DecentralizedStorage, Oracle, type Models as OracleModels, TaskStatus, contractBytesToStringWithStorage, stringToContractBytesWithStorage };