Skip to content

Commit

Permalink
Chromia Integration (#42)
Browse files Browse the repository at this point in the history
* add chromia examples and chromia wallet

* add CHR

* add docs

* Finalize Chromia Account

* update docs

* update recipient address

* resolve

* add version bump

---------

Co-authored-by: Agus <[email protected]>
  • Loading branch information
superoo7 and aigustin authored Dec 11, 2024
1 parent 5d66c1a commit ad45f94
Show file tree
Hide file tree
Showing 26 changed files with 921 additions and 16 deletions.
4 changes: 4 additions & 0 deletions goat.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
"name": "[Wallet] 🍀 crossmint",
"path": "./typescript/packages/wallets/crossmint"
},
{
"name": "[Wallet] 🪪 chromia",
"path": "./typescript/packages/wallets/chromia"
},
{
"name": "[Wallet] 🌞 solana",
"path": "./typescript/packages/wallets/solana"
Expand Down
7 changes: 7 additions & 0 deletions typescript/.changeset/spotty-yaks-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"goat-examples-vercel-ai-chromia": minor
"@goat-sdk/wallet-chromia": minor
"@goat-sdk/core": minor
---

Added Chromia and send-chr tools
2 changes: 2 additions & 0 deletions typescript/examples/vercel-ai/chromia/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
OPENAI_API_KEY=
EVM_PRIVATE_KEY=
15 changes: 15 additions & 0 deletions typescript/examples/vercel-ai/chromia/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Vercel AI with Chromia Example

## Setup

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

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

## Usage

```
npx ts-node index.ts
```
51 changes: 51 additions & 0 deletions typescript/examples/vercel-ai/chromia/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { getOnChainTools } from "@goat-sdk/adapter-vercel-ai";
import { createClient } from "postchain-client";
import { CHROMIA_MAINNET_BRID, chromia } from "@goat-sdk/wallet-chromia";
import { createConnection, createInMemoryEvmKeyStore, createKeyStoreInteractor } from "@chromia/ft4";
import { sendCHR } from "@goat-sdk/core";

require("dotenv").config();

const privateKey = process.env.EVM_PRIVATE_KEY;

if (!privateKey) {
throw new Error("EVM_PRIVATE_KEY is not set in the environment");
}

(async () => {
const chromiaClient = await createClient({
nodeUrlPool: ["https://system.chromaway.com:7740"],
blockchainRid: CHROMIA_MAINNET_BRID.ECONOMY_CHAIN
});
const connection = createConnection(chromiaClient);
const evmKeyStore = createInMemoryEvmKeyStore({
privKey: privateKey,
} as any);
const keystoreInteractor = createKeyStoreInteractor(chromiaClient, evmKeyStore)
const accounts = await keystoreInteractor.getAccounts();
const accountAddress = accounts[0].id.toString("hex");
console.log("ACCOUNT ADDRESS: ", accountAddress);

const tools = await getOnChainTools({
wallet: chromia({
client: chromiaClient,
accountAddress,
keystoreInteractor,
connection
}),
plugins: [
sendCHR()
],
});

const result = await generateText({
model: openai("gpt-4o-mini"),
tools: tools,
maxSteps: 5,
prompt: "send 0.0001 CHR to <recipient address>",
});

console.log(result.text);
})();
21 changes: 21 additions & 0 deletions typescript/examples/vercel-ai/chromia/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "goat-examples-vercel-ai-chromia",
"version": "0.1.1",
"description": "",
"private": true,
"scripts": {
"test": "vitest run --passWithNoTests"
},
"author": "",
"license": "MIT",
"dependencies": {
"@ai-sdk/openai": "^1.0.4",
"@chromia/ft4": "catalog:",
"@goat-sdk/adapter-vercel-ai": "workspace:*",
"@goat-sdk/core": "workspace:*",
"@goat-sdk/wallet-chromia": "workspace:*",
"ai": "catalog:",
"dotenv": "^16.4.5",
"postchain-client": "catalog:"
}
}
8 changes: 8 additions & 0 deletions typescript/examples/vercel-ai/chromia/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"]
}
2 changes: 2 additions & 0 deletions typescript/packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"@lit-protocol/lit-node-client": "catalog:",
"@lit-protocol/wrapped-keys": "catalog:",
"@solana/web3.js": "catalog:",
"@chromia/ft4": "catalog:",
"abitype": "^1.0.6",
"postchain-client": "catalog:",
"viem": "catalog:",
"zod": "catalog:"
},
Expand Down
24 changes: 24 additions & 0 deletions typescript/packages/core/src/chromia/methods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { formatUnits } from "viem";
import type { z } from "zod";
import type { ChromiaWalletClient } from "../wallets";
import type { getAddressParametersSchema, getCHRBalanceParametersSchema } from "./parameters";

export function getAddress(
walletClient: ChromiaWalletClient,
parameters: z.infer<typeof getAddressParametersSchema>,
): string {
return walletClient.getAddress();
}

export async function getBalance(
walletClient: ChromiaWalletClient,
parameters: z.infer<typeof getCHRBalanceParametersSchema>,
): Promise<string> {
try {
const balance = await walletClient.balanceOf(parameters.address ?? getAddress(walletClient, {}));

return formatUnits(balance.value, balance.decimals);
} catch (error) {
throw new Error(`Failed to fetch balance: ${error}`);
}
}
9 changes: 9 additions & 0 deletions typescript/packages/core/src/chromia/parameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from "zod";

export const getAddressParametersSchema = z.object({});

export const getCHRBalanceParametersSchema = z.object({
address: z
.optional(z.string())
.describe("The address to get the balance of, defaults to the address of the wallet"),
});
19 changes: 19 additions & 0 deletions typescript/packages/core/src/chromia/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { DeferredTool } from "../tools";
import type { ChromiaWalletClient } from "../wallets";
import { getAddress, getBalance } from "./methods";
import { getAddressParametersSchema, getCHRBalanceParametersSchema } from "./parameters";

export const deferredChromiaTools: DeferredTool<ChromiaWalletClient>[] = [
{
name: "get_address",
description: "This {{tool}} returns the address of the Chromia wallet.",
parameters: getAddressParametersSchema,
method: getAddress,
},
{
name: "get_chr_balance",
description: "This {{tool}} returns the CHR balance of a Chromia wallet.",
parameters: getCHRBalanceParametersSchema,
method: getBalance,
},
];
2 changes: 2 additions & 0 deletions typescript/packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ethSendTransaction } from "./plugins/eth-send-transaction";
import type { Plugin } from "./plugins/plugins";
import { sendETH } from "./plugins/send-eth";
import { sendSOL } from "./plugins/send-sol";
import { sendCHR } from "./plugins/send-chr";
import {
type DeferredTool,
type GetDeferredToolsParams,
Expand Down Expand Up @@ -40,6 +41,7 @@ export {
sendETH,
sendSOL,
ethSendTransaction,
sendCHR,
addParametersToDescription,
parametersToJsonExample,
type Tool,
Expand Down
42 changes: 42 additions & 0 deletions typescript/packages/core/src/plugins/send-chr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { z } from "zod";
import type { Plugin } from "./plugins";
import { ChromiaWalletClient } from "../wallets";

export const CHR_ASSET_ID =
"5f16d1545a0881f971b164f1601cbbf51c29efd0633b2730da18c403c3b428b5";

export function sendCHR(): Plugin<ChromiaWalletClient> {
return {
name: "send_chr",
supportsSmartWallets: () => false,
supportsChain: (chain) => chain.type === "chromia",
getTools: async () => {
return [
{
name: "send_chr",
description: "This {{tool}} sends CHR to an address on a Chromia chain.",
parameters: sendCHRParametersSchema,
method: sendCHRMethod,
}
]
}
}
}

const sendCHRParametersSchema = z.object({
to: z.string().describe("The address to send CHR to"),
amount: z.string().describe("The amount of CHR to send"),
});

async function sendCHRMethod(
walletClient: ChromiaWalletClient,
parameters: z.infer<typeof sendCHRParametersSchema>,
): Promise<string> {
try {
const { to, amount } = parameters;
await walletClient.sendTransaction({to, assetId: CHR_ASSET_ID, amount});
return `CHR sent to ${to} with amount ${amount}`;
} catch (error) {
return `Error sending CHR: ${error}`;
}
}
6 changes: 5 additions & 1 deletion typescript/packages/core/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import type { Plugin } from "./plugins/plugins";
import { deferredSolanaTools } from "./solana/tools";
import { replaceToolPlaceholder } from "./utils";
import type { AnyEVMWalletClient, ChainForWalletClient, WalletClient } from "./wallets";
import { isEVMChain, isEVMSmartWalletClient, isSolanaChain } from "./wallets";
import { isEVMChain, isEVMSmartWalletClient, isSolanaChain, isChromiaChain } from "./wallets";
import { deferredChromiaTools } from "./chromia/tools";

// biome-ignore lint/suspicious/noExplicitAny: Tools can return any type
export type Tool<TResult = any> = {
Expand Down Expand Up @@ -72,6 +73,9 @@ export async function getDeferredTools<TWalletClient extends AnyEVMWalletClient
} else if (isSolanaChain(chain)) {
// We know that TWalletClient is compatible with SolanaWalletClient here
tools.push(...(deferredSolanaTools as unknown as DeferredTool<TWalletClient>[]));
} else if (isChromiaChain(chain)) {
// We know that TWalletClient is compatible with ChromiaWalletClient here
tools.push(...(deferredChromiaTools as unknown as DeferredTool<TWalletClient>[]));
} else {
throw new Error(`Unsupported chain type: ${chain.type}`);
}
Expand Down
25 changes: 25 additions & 0 deletions typescript/packages/core/src/wallets/chromia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

import { DictPair, QueryObject, RawGtv } from "postchain-client";
import { TransactionWithReceipt } from "@chromia/ft4";
import type { WalletClient } from "./core";

export function isChromiaWalletClient(wallet: WalletClient): wallet is ChromiaWalletClient {
return wallet.getChain().type === "chromia";
}

export type ChromiaTransaction = {
to: string;
assetId: string;
amount: string;
};

export type ChromiaReadRequest = string | QueryObject<RawGtv | DictPair>;

export type ChromiaReadResult = RawGtv;

export type ChromiaTransactionResult = TransactionWithReceipt;

export interface ChromiaWalletClient extends WalletClient {
sendTransaction: (transaction: ChromiaTransaction) => Promise<ChromiaTransactionResult>;
read: (request: ChromiaReadRequest) => Promise<ChromiaReadResult>;
}
6 changes: 5 additions & 1 deletion typescript/packages/core/src/wallets/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type Balance = {
* @param id - Chain ID, optional for EVM
*/
export type Chain = {
type: "evm" | "solana" | "aptos";
type: "evm" | "solana" | "aptos" | "chromia";
id?: number; // optional for EVM
};

Expand All @@ -30,6 +30,10 @@ export type AptosChain = Chain & {
type: "aptos";
};

export type ChromiaChain = Chain & {
type: "chromia";
};

export interface WalletClient {
getAddress: () => string;
getChain: () => Chain;
Expand Down
12 changes: 11 additions & 1 deletion typescript/packages/core/src/wallets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ import {
type SolanaWalletClient,
isSolanaWalletClient,
} from "./solana";
import {
type ChromiaReadRequest,
type ChromiaTransaction,
type ChromiaWalletClient,
isChromiaWalletClient,
} from "./chromia";

import type { Balance, Chain, Signature, WalletClient } from "./core";
import { type AnyEVMWalletClient, type ChainForWalletClient, isEVMChain, isSolanaChain } from "./utils";
import { type AnyEVMWalletClient, type ChainForWalletClient, isEVMChain, isSolanaChain, isChromiaChain } from "./utils";

export type {
EVMWalletClient,
Expand All @@ -35,6 +41,9 @@ export type {
SolanaTransactionResult,
Signature,
Balance,
ChromiaWalletClient,
ChromiaReadRequest,
ChromiaTransaction,
EVMSmartWalletClient,
ChainForWalletClient,
EVMTypedData,
Expand All @@ -46,5 +55,6 @@ export {
isEVMSmartWalletClient,
isEVMChain,
isSolanaChain,
isChromiaChain,
type AnyEVMWalletClient,
};
6 changes: 5 additions & 1 deletion typescript/packages/core/src/wallets/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Chain, EVMChain, SolanaChain, WalletClient } from "./core";
import type { Chain, ChromiaChain, EVMChain, SolanaChain, WalletClient } from "./core";
import type { EVMWalletClient } from "./evm";
import type { EVMSmartWalletClient } from "./evm-smart-wallet";
import type { SolanaWalletClient } from "./solana";
Expand All @@ -17,4 +17,8 @@ export function isSolanaChain(chain: Chain): chain is SolanaChain {
return chain.type === "solana";
}

export function isChromiaChain(chain: Chain): chain is ChromiaChain {
return chain.type === "chromia";
}

export type AnyEVMWalletClient = EVMWalletClient | EVMSmartWalletClient;
Loading

0 comments on commit ad45f94

Please sign in to comment.