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

Added tron chain support #441

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
OPENAI_API_KEY=
TRON_RPC_URL=
TRON_PRIVATE_KEY=
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@

<div align="center">
<img src="https://github.com/user-attachments/assets/5fc7f121-259c-492c-8bca-f15fe7eb830c" alt="GOAT" width="100px" height="auto" style="object-fit: contain;">
</div>

# TRON
## 🚀 Quickstart

This example demonstrates how to use GOAT to **send and receive TRX tokens** on the TRON network.

You can use this example with any other agent framework and wallet of your choice.

## Setup
1. Clone the repository:
```bash
git clone https://github.com/goat-sdk/goat.git && cd goat
```

2. Run the following commands from the `typescript` directory:
```bash
cd typescript
pnpm install
pnpm build
```

3. Go to the example directory:
```bash
cd examples/by-use-case/tron-send-and-receive-tokens
```

4. Copy the `.env.template` and populate with your values:
```bash
cp .env.template .env
```
- `OPENAI_API_KEY`
- `TRON_PRIVATE_KEY`
- `TRON_RPC_URL`

## Usage
1. Run the interactive CLI:
```bash
pnpm ts-node index.ts
```

2. Chat with the agent:
- Check your balance for TRX
- Send TRX to another address
- Check your balance again to see the TRX you just sent

## Using in production
In production, developers require advanced wallet setups that utilize [smart wallets](https://docs.goat-sdk.com/concepts/smart-wallets), which allow them to:
1. **Increase security** by setting programmable permissions (e.g. limiting fund amounts, restricting contract interactions, and defining required signatures)
2. **Maintain regulatory compliance** by ensuring agent wallets are non-custodial. This means that:
- Launchpads, wallet providers, or agent platforms never have access to agents' wallets.
- Agent platforms do not require money transmitter licenses.

### Agent Wallets
[Crossmint](https://docs.crossmint.com/wallets/quickstarts/agent-wallets) offers one of the most advanced solutions for agent developers and launchpads: [Agent Wallets](https://docs.crossmint.com/wallets/quickstarts/agent-wallets).

To integrate Agent Wallets with GOAT, check out the following quickstarts:
1. Agent Wallets Quickstart [[EVM](https://github.com/goat-sdk/goat/tree/main/typescript/examples/by-wallet/crossmint-smart-wallets), [TRON](https://github.com/goat-sdk/goat/tree/main/typescript/examples/by-wallet/crossmint-smart-wallets)]
2. [Agent Launchpad Starter Kit](https://github.com/Crossmint/agent-launchpad-starter-kit/)




<footer>
<br/>
<br/>
<div>
<img src="https://github.com/user-attachments/assets/4821833e-52e5-4126-a2a1-59e9fa9bebd7" alt="GOAT" width="100%" height="auto" style="object-fit: contain; max-width: 800px;">

<div>
</footer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import readline from "node:readline";
import { openai } from "@ai-sdk/openai";
import { getOnChainTools } from "@goat-sdk/adapter-vercel-ai";
import { WalletClientBase } from "@goat-sdk/core";
import { sendTRX } from "@goat-sdk/wallet-tron";
import { tron } from "@goat-sdk/wallet-tron";
import { generateText } from "ai";
import dotenv from "dotenv";
import { TronWeb } from "tronweb";

dotenv.config();

const fullNode: string = process.env.TRON_FULL_NODE || "https://nile.trongrid.io";
const solidityNode: string = process.env.TRON_SOLIDITY_NODE || "https://nile.trongrid.io";
const eventServer: string = process.env.TRON_EVENT_SERVER || "https://nile.trongrid.io";

const tronWeb: TronWeb = new TronWeb(fullNode, solidityNode, eventServer);

const privateKey: string = process.env.TRON_PRIVATE_KEY as string;
if (!privateKey || privateKey.length !== 64) {
throw new Error("Invalid TRON private key.");
}
tronWeb.setPrivateKey(privateKey);
const maybeAddress: string | false = tronWeb.address.fromPrivateKey(privateKey);
if (!maybeAddress) {
throw new Error("Failed to derive a valid Tron address from the provided private key.");
}
const address: string = maybeAddress;

const wallet = tron({ tronWeb, address, privateKey }) as unknown as WalletClientBase;

async function chat(): Promise<void> {
const tools = await getOnChainTools({
wallet,
plugins: [sendTRX()],
});

type Message = {
role: "user" | "assistant";
content: string;
};

console.log("Chat started. Type 'exit' to end the conversation.");

const conversationHistory: Message[] = [];

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const askQuestion = (): void => {
rl.question("You: ", async (prompt: string) => {
if (prompt.toLowerCase() === "exit") {
rl.close();
return;
}

conversationHistory.push({ role: "user", content: prompt });

const result = await generateText({
model: openai("gpt-4o-mini"),
tools, // Agus, Here is an issue.
maxSteps: 10, // Maximum number of tool invocations per request
prompt: `You are a based crypto degen assistant. You're knowledgeable about DeFi, NFTs, and trading. You use crypto slang naturally and stay up to date with the Tron ecosystem. You help users with their trades and provide market insights. Keep responses concise and use emojis occasionally.

Previous conversation:
${conversationHistory.map((m) => `${m.role}: ${m.content}`).join("\n")}

Current request: ${prompt}`,
onStepFinish: (event) => {
console.log("Tool execution:", event.toolResults);
},
});

conversationHistory.push({ role: "assistant", content: result.text });
console.log("Assistant:", result.text);
askQuestion();
});
};

askQuestion();
}

chat().catch(console.error);
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "goat-examples-tron-send-and-receive-tokens",
"version": "0.0.0",
"description": "Example to test TRX send and receive functionality using Goat SDK on Tron",
"private": true,
"scripts": {
"test": "vitest run --passWithNoTests"
},
"author": "",
"license": "MIT",
"dependencies": {
"@ai-sdk/openai": "~1.0.4",
"@goat-sdk/adapter-vercel-ai": "0.2.10",
"@goat-sdk/core": "0.4.9",
"@goat-sdk/wallet-tron": "workspace:*",
"tronweb": "^6.0.0",
"dotenv": "^16.4.5",
"zod": "3.23.8"
}
}
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"]
}
7 changes: 6 additions & 1 deletion typescript/packages/core/src/types/Chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export type Chain =
| CosmosChain
| StarknetChain
| RadixChain
| ZetrixChain;
| ZetrixChain
| TronChain;

export type SuiChain = {
type: "sui";
Expand Down Expand Up @@ -62,3 +63,7 @@ export type RadixChain = {
export type ZetrixChain = {
type: "zetrix";
};

export type TronChain = {
type: "tron";
};
2 changes: 2 additions & 0 deletions typescript/packages/wallets/tron/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# @goat-sdk/wallet-tron

31 changes: 31 additions & 0 deletions typescript/packages/wallets/tron/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div align="center">
<a href="https://github.com/goat-sdk/goat">

<img src="https://github.com/user-attachments/assets/5fc7f121-259c-492c-8bca-f15fe7eb830c" alt="GOAT" width="100px" height="auto" style="object-fit: contain;">
</a>
</div>

# TRON Wallet for GOAT

## Installation
```
npm install @goat-sdk/wallet-tron
yarn add @goat-sdk/wallet-tron
pnpm add @goat-sdk/wallet-tron
```

## Usage
```typescript
import { getOnChainTools } from "@goat-sdk/adapter-vercel-ai";
import { tron } from "@goat-sdk/wallet-tron";


```

<footer>
<br/>
<br/>
<div>
<a href="https://github.com/goat-sdk/goat">
<img src="https://github.com/user-attachments/assets/4821833e-52e5-4126-a2a1-59e9fa9bebd7" alt="GOAT" width="100%" height="auto" style="object-fit: contain; max-width: 800px;">
</a>
34 changes: 34 additions & 0 deletions typescript/packages/wallets/tron/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@goat-sdk/wallet-tron",
"version": "0.2.15",
"sideEffects": false,
"files": ["dist/**/*", "README.md", "package.json"],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "vitest run --passWithNoTests"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"dependencies": {
"@goat-sdk/core": "workspace:*",
"tronweb": "^6.0.0",
"tweetnacl": "^1.0.3",
"viem": "catalog:",
"zod": "catalog:"
},
"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"]
}
26 changes: 26 additions & 0 deletions typescript/packages/wallets/tron/src/TronKeypairWalletClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { TronWeb } from "tronweb";
import { TronWalletClient } from "./TronWalletClient";

export type TronKeypairWalletClientCtorParams = {
tronWeb: TronWeb;
address: string;
privateKey: string;
};

export class TronKeyPairWalletClient extends TronWalletClient {
private privateKey: string;

constructor(params: TronKeypairWalletClientCtorParams) {
super(params.tronWeb);
if (!params.privateKey || params.privateKey.length !== 64) {
throw new Error("Invalid Tron private key.");
}
this.privateKey = params.privateKey;
if (!params.tronWeb.isAddress(params.address)) {
throw new Error("Invalid Tron address derived from the provided private key.");
}
this.fromAddress = params.address;
}
}

export const tron = (params: TronKeypairWalletClientCtorParams) => new TronKeyPairWalletClient(params);
68 changes: 68 additions & 0 deletions typescript/packages/wallets/tron/src/TronWalletClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Chain, WalletClientBase } from "@goat-sdk/core";
import { TronWeb } from "tronweb";

export abstract class TronWalletClient extends WalletClientBase {
protected tronWeb: TronWeb;
protected fromAddress: string;

constructor(tronWeb?: TronWeb) {
super();
// Allow injection of a TronWeb instance or fallback to Nile testnet endpoints.
this.tronWeb =
tronWeb ?? new TronWeb("https://nile.trongrid.io", "https://nile.trongrid.io", "https://nile.trongrid.io");
this.fromAddress = "";
}

getChain(): Chain {
return { type: "tron" } as const;
}

getAddress(): string {
if (!this.fromAddress) {
throw new Error("Wallet address is not set.");
}
return this.fromAddress;
}

async signMessage(message: string): Promise<{ signature: string }> {
const hexMessage: string = this.tronWeb.toHex(message);
const signature: string = await this.tronWeb.trx.sign(hexMessage);
return { signature };
}

async balanceOf(address: string): Promise<{
decimals: number;
symbol: string;
name: string;
value: string;
inBaseUnits: string;
}> {
if (!this.tronWeb.isAddress(address)) {
throw new Error("Invalid Tron address.");
}
const balanceSun: number = await this.tronWeb.trx.getBalance(address);
const balanceTRX: number = balanceSun / 1_000_000;
return {
decimals: 6,
symbol: "TRX",
name: "TRX",
value: balanceTRX.toString(),
inBaseUnits: balanceSun.toString(),
};
}

async sendTransaction(transaction: { to: string; value: number }): Promise<{ hash: string }> {
const { to, value } = transaction;
if (!this.tronWeb.isAddress(to)) {
throw new Error("Invalid recipient Tron address.");
}
const amountSun: number = Math.floor(value * 1_000_000);
const unsignedTx = await this.tronWeb.transactionBuilder.sendTrx(to, amountSun, this.fromAddress);
const signedTx = await this.tronWeb.trx.sign(unsignedTx);
const broadcast = await this.tronWeb.trx.sendRawTransaction(signedTx);
if (broadcast.result) {
return { hash: signedTx.txID };
}
throw new Error("Transaction failed to broadcast.");
}
}
4 changes: 4 additions & 0 deletions typescript/packages/wallets/tron/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./types";
export * from "./TronWalletClient";
export * from "./TronKeypairWalletClient";
export * from "./sendTRX.plugin";
Loading
Loading