Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/notify #16

Merged
merged 11 commits into from
Jan 27, 2025
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -142,3 +142,42 @@ yarn dev
# or
pnpm dev
```


## Supported APIs on Testnet and Mainnet for Rooch Network

### Twitter/X API
- User Endpoint: `https://api.x.com/2/users/` and `https://api.x.com/2/tweets/`
```bash
# Example: Get user followers count
rooch move run --function 0x0d6144b074dd19a9ff581abd5bf7815a39222c8b3ac68ce5938c9d9723544e08::example_caller::request_data \
--sender-account default \
--args 'string:https://api.x.com/2/users/by/username/elonmusk?user.fields=public_metrics' \
--args 'string:GET' \
--args 'string:{}' \
--args 'string:{}' \
--args 'string:.data.public_metrics.followers_count' \
--args 'address:<orchestrator_address>' \
--args 'u256:50000000'
```

### OpenAI API
- Chat Completions: `https://api.openai.com/v1/chat/completions`
```bash
# Example: Simple GPT request
rooch move run --function 0x0d6144b074dd19a9ff581abd5bf7815a39222c8b3ac68ce5938c9d9723544e08::example_caller::request_data \
--sender-account default \
--args 'string:https://api.openai.com/v1/chat/completions' \
--args 'string:POST' \
--args 'string:{}' \
--args 'string:{
"model": "gpt-4",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}' \
--args 'string:.choices[].message.content' \
--args 'address:<orchestrator_address>' \
--args 'u256:50000000'
```

Note: Replace `<oracle_address>` and `<orchestrator_address>` with your actual deployed addresses.
36 changes: 18 additions & 18 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -23,24 +23,24 @@ services:
volumes:
- postgres-db-data:/var/lib/postgresql/data

orchestrator:
build:
dockerfile: ./orch.dockerfile
context: .
image: orchestrator:move
container_name: verity-move-orchestrator
environment:
DATABASE_URL: ${DATABASE_URL}
networks:
- verify-network
depends_on:
postgres_db:
condition: service_healthy
command: >
sh -c "
npx prisma migrate deploy &&
pnpm run start
"
# orchestrator:
# build:
# dockerfile: ./orch.dockerfile
# context: .
# image: orchestrator:move
# container_name: verity-move-orchestrator
# environment:
# DATABASE_URL: ${DATABASE_URL}
# networks:
# - verify-network
# depends_on:
# postgres_db:
# condition: service_healthy
# command: >
# sh -c "
# npx prisma migrate deploy &&
# pnpm run start
# "

volumes:
postgres-db-data:
23 changes: 16 additions & 7 deletions docs/ROOCH.md
Original file line number Diff line number Diff line change
@@ -37,10 +37,14 @@ Navigate to the `rooch` directory, build the contracts for development, publish
```bash
cd rooch
rooch move build --dev
rooch move publish --named-addresses verity_test_foreign_module=default,verity=default
rooch move publish --named-addresses verity=default --sender default
cd ..
```

###### Note:
- A sender account is required for deployment and contract updates.
- The account with the upgrade_Cap object can update/upgrade the contract by setting the --sender flag to their account with the upgrade_Cap.

#### Step 4: Install Node Dependencies

Install the necessary Node.js dependencies using npm, yarn, or pnpm. Ensure you are in the root project directory.
@@ -99,19 +103,19 @@ Additional steps for managing supported orchestrator URL
- To add URL

```bash
rooch move run --function <oracle_address>::registry::add_supported_url --sender-account <orchestrator_address> --args 'string:https://api.x.com/2/users/' --args 'u256:400000' --args 'u64:0' --args 'u256:0' --args 'u256:0'
rooch move run --function 0x330895328871633518adee400b6b14d4475fa240965ce7923b50ba42b0ab50b9::registry::add_supported_url --sender-account 0x694cbe655b126e9e6a997e86aaab39e538abf30a8c78669ce23a98740b47b65d --args 'string:https://api.openai.com/v1/chat/completions' --args 'u256:50000' --args 'u64:40' --args 'u256:4000' --args 'u256:5000'
```

- to remove URLs

```bash
rooch move run --function <oracle_address>::registry::remove_supported_url --sender-account <orchestrator_address> --args 'string:api.twitter.com/2/users/'
rooch move run --function 0x330895328871633518adee400b6b14d4475fa240965ce7923b50ba42b0ab50b9::registry::remove_supported_url --sender-account <orchestrator_address> --args 'string:https://api.twitter.com/2/users/'
```

- To view supported URLS

```bash
rooch move view --function <oracle_address>::registry::get_supported_urls --args 'address:<orchestrator_address>'
rooch move view --function 0x330895328871633518adee400b6b14d4475fa240965ce7923b50ba42b0ab50b9::registry::get_supported_urls --args 'address:0x694cbe655b126e9e6a997e86aaab39e538abf30a8c78669ce23a98740b47b65d'
```


@@ -138,24 +142,29 @@ rooch move run --function <contractAddress>::example_caller::request_data --sen
Here's an example of requesting the Twitter Followers Count on a Local Rooch Node:

```bash
rooch move run --function 0x0d6144b074dd19a9ff581abd5bf7815a39222c8b3ac68ce5938c9d9723544e08::example_caller::request_data --sender-account default --args 'string:https://api.x.com/2/users/by/username/elonmusk?user.fields=public_metrics' --args 'string:GET' --args 'string:{}' --args 'string:{}' --args 'string:.data.public_metrics.followers_count' --args 'address:0x694cbe655b126e9e6a997e86aaab39e538abf30a8c78669ce23a98740b47b65d' --args 'u256:50000000'
rooch move run --function 0xf1290fb0e7e1de7e92e616209fb628970232e85c4c1a264858ff35092e1be231::example_caller::request_data --sender-account default --args 'string:https://api.x.com/2/users/by/username/elonmusk?user.fields=public_metrics' --args 'string:GET' --args 'string:{}' --args 'string:{}' --args 'string:.data.public_metrics.followers_count' --args 'address:0x694cbe655b126e9e6a997e86aaab39e538abf30a8c78669ce23a98740b47b65d' --args 'u256:50000'
```
or
```bash
rooch move run --function 0x0d6144b074dd19a9ff581abd5bf7815a39222c8b3ac68ce5938c9d9723544e08::example_caller::request_data --sender-account default --args 'string:https://api.openai.com/v1/chat/completions' --args 'string:POST' --args 'string:{}' --args 'string:{
rooch move run --function 0xf1290fb0e7e1de7e92e616209fb628970232e85c4c1a264858ff35092e1be231::example_caller::request_data --sender-account default --args 'string:https://api.openai.com/v1/chat/completions' --args 'string:POST' --args 'string:{}' --args 'string:{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}' --args 'string:.choices[].message.content' --args 'address:0x694cbe655b126e9e6a997e86aaab39e538abf30a8c78669ce23a98740b47b65d' --args 'u256:50000000'
```

To check the state of the response object on a local Rooch node, use the following command:

```bash
rooch state -a /object/0x7a01ddf194f8a1c19212d56f747294352bf2e5cf23e6e10e64937aa1955704b0
```


Additionally,
For notification calls you can allocated an portion form escrow for notification
```bash
rooch move run --function 0xf1290fb0e7e1de7e92e616209fb628970232e85c4c1a264858ff35092e1be231::oracles::update_notification_gas_allocation --sender-account default --args 'address:0x27e46e033da11c4d1f986081877e80cefb2b29dec1c559c97c3ccf12e910aba7' --args 'string:example_caller::receive_data' --args 'u256:90000'
```

#### Step 10: Manage Escrow balance

- To view balance
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "Keeper" (
"id" TEXT NOT NULL,
"chain" TEXT NOT NULL DEFAULT 'ROOCH-testnet',
"module" TEXT NOT NULL,
"privateKey" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updateAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Keeper_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Keeper_chain_module_idx" ON "Keeper"("chain", "module");
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:

- A unique constraint covering the columns `[chain,module]` on the table `Keeper` will be added. If there are existing duplicate values, this will fail.

*/
-- DropIndex
DROP INDEX "Keeper_chain_module_idx";

-- CreateIndex
CREATE UNIQUE INDEX "Keeper_chain_module_key" ON "Keeper"("chain", "module");
13 changes: 13 additions & 0 deletions orchestrator/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -27,4 +27,17 @@ model Events{
updateAt DateTime @updatedAt

@@index([eventHandleId, eventSeq,chain])
}

model Keeper{
id String @id @default(cuid())
chain String @default("ROOCH-testnet")
module String
privateKey String

createdAt DateTime @default(now())
updateAt DateTime @updatedAt

@@unique([chain, module])

}
42 changes: 34 additions & 8 deletions orchestrator/src/indexer/rooch.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import env from "@/env";
import { instance as xTwitterInstance } from "@/integrations/xtwitter";
import { log } from "@/logger";
import type { IEvent, IRequestAdded, JsonRpcResponse, ProcessedRequestAdded, RoochNetwork } from "@/types";
import { decodeNotifyValue } from "@/util";
import { decodeNotifyValueFull } from "@/util";
import { Args, RoochClient, Secp256k1Keypair, Transaction, getRoochNodeUrl } from "@roochnetwork/rooch-sdk";
import axios from "axios";
import prismaClient from "../../prisma";
@@ -205,7 +205,7 @@ export default class RoochIndexer extends Indexer {
oracle: values.oracle,
pick: values.pick,
request_id: values.request_id,
notify: decodeNotifyValue(values.notify?.value?.vec?.at(0) ?? ""),
notify: decodeNotifyValueFull(values.notify?.value?.vec?.value?.at(0).at(0) ?? ""),
};
return data;
});
@@ -238,32 +238,58 @@ export default class RoochIndexer extends Indexer {
return null;
}

const keeper_key = await prismaClient.keeper.upsert({
where: {
chain_module: {
module: data.notify ?? "",
chain: this.chainId,
},
},
create: {
module: data.notify ?? "",
chain: this.chainId,
privateKey: Secp256k1Keypair.generate().getSecretKey(),
},
update: {},
});

const tx = new Transaction();
tx.callFunction({
target: `${this.oracleAddress}::oracles::fulfil_request`,
args: [Args.objectId(data.request_id), Args.u16(status), Args.string(result)],
args: [
Args.objectId(data.request_id),
Args.u16(status),
Args.string(result),
Args.address(Secp256k1Keypair.fromSecretKey(keeper_key.privateKey).getRoochAddress().toHexAddress()),
],
});

const receipt = await client.signAndExecuteTransaction({
transaction: tx,
signer: this.keyPair,
});

console.log({
keeper_key,
address: Secp256k1Keypair.fromSecretKey(keeper_key.privateKey).getRoochAddress().toHexAddress(),
target: data.notify ?? "",
oracleAddress: this.oracleAddress,
});
try {
if ((data.notify?.length ?? 0) > 66) {
const tx = new Transaction();
tx.callFunction({
target: data.notify ?? "",
maxGas: 10_000,
args: [],
});

const receipt = await client.signAndExecuteTransaction({
const notification_receipt = await client.signAndExecuteTransaction({
transaction: tx,
signer: this.keyPair,
signer: Secp256k1Keypair.fromSecretKey(keeper_key.privateKey),
});
log.info({ notification_receipt });
}
} catch (err) {
log.error(err);
log.error({ request_id: data.request_id, err: err });
}
return receipt;
}
1 change: 1 addition & 0 deletions orchestrator/src/integrations/base.ts
Original file line number Diff line number Diff line change
@@ -67,6 +67,7 @@ export abstract class BasicBearerAPIHandler {

const token = this.getAccessToken();
let request: AxiosResponse<any, any>;

if (isValidJson(data.params.headers) && isValidJson(data.params.body)) {
// TODO: Replace direct requests via axios with requests via VerityClient TS module
request = await axios({
3 changes: 2 additions & 1 deletion orchestrator/src/integrations/openAI.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import env from "@/env";
import { log } from "@/logger";
import Joi from "joi";
import { BasicBearerAPIHandler } from "./base";

@@ -19,8 +20,8 @@ export default class OpenAIIntegration extends BasicBearerAPIHandler {
const { error, value } = chatSchema.validate(JSON.parse(payload), {
allowUnknown: true,
});
console.log({ value, error });
if (error) {
log.error({ value, error });
return false;
} else {
if (value.model === "gpt-4o") {
2 changes: 1 addition & 1 deletion orchestrator/src/integrations/xtwitter.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ export default class TwitterIntegration extends BasicBearerAPIHandler {
}

export const instance = new TwitterIntegration(
env.integrations.openAIToken,
env.integrations.xBearerToken,
["api.x.com", "api.twitter.com"],
["/2/tweets", "/2/users/"],
60 * 1000,
2 changes: 1 addition & 1 deletion orchestrator/src/types.ts
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ interface NotifyValue {
value: VecValue;
}
interface VecValue {
vec: string[];
vec: { value: Array<Array<string>> } | any;
}

export interface IRequestAdded {
4 changes: 4 additions & 0 deletions orchestrator/src/util.ts
Original file line number Diff line number Diff line change
@@ -13,3 +13,7 @@ export function isValidJson(jsonString: string): boolean {
export function decodeNotifyValue(hex: string): string {
return `${hex.slice(0, 66)}${Buffer.from(hex.slice(66), "hex").toString()}`;
}

export function decodeNotifyValueFull(hex: string): string {
return `0x${Buffer.from(hex.slice(2, hex.length), "hex").toString()}`;
}
Loading
Loading