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

Ton Connect implementation #7

Closed
wants to merge 2 commits into from
Closed
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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ const balance = await provider.getWalletBalance();
const portfolio = await provider.getFormattedPortfolio(runtime);
```

### TonConnect

The `TonConnectWalletProvider` provide Ton Connect protocol to connect to any supported wallets.
You can use that connection in your code to make transactions or etc.

```typescript
import { TonConnectWalletProvider } from "@elizaos/plugin-ton";

const walletProvider = new TonConnectWalletProvider(runtime);
const wallet = walletProvider.getWalletClient();
```
or
```typescript
import { TonConnectWalletProvider } from "@elizaos/plugin-ton";

const walletProvider = new TonConnectWalletProvider(runtime);
const wallet = walletProvider.getWalletClient(address);
```

### TransferAction

The `TransferAction` handles token transfers:
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"@elizaos/core": "workspace:*",
"@ton/crypto": "3.3.0",
"@ton/ton": "15.1.0",
"@tonconnect/sdk": "^3.0.6",
"qrcode": "^1.5.4",
"bignumber.js": "9.1.2",
"node-cache": "5.1.2",
"tsup": "8.3.5"
Expand Down
82 changes: 82 additions & 0 deletions src/actions/disconnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {
type Action,
type IAgentRuntime,
type Memory,
type State,
type HandlerCallback,
elizaLogger,
} from "@elizaos/core";
import TonConnect from "@tonconnect/sdk";
import {IStorage, Storg} from "../utils/storage";

export const disconnect: Action = {
name: "DISCONNECT_TON_WALLET",
similes: ["DISCONNECT_CONNECTED_TON_WALLET", "REMOVE_TON_CONNECTED"],
description:
"Disconnect from ton wallet by address",

validate: async (runtime: IAgentRuntime, _message: Memory) => {
const regex = /\b[UV]Q[A-Za-z0-9_-]{46}\b/g;
return _message.content.text.match(regex)?.[0] ?? false
},

handler: async (
runtime: IAgentRuntime,
message: Memory,
_state?: State,
_options?: { [key: string]: unknown },
callback?: HandlerCallback
) => {
try {
const manifestUrl = runtime.getSetting("TON_CONNECT_MANIFEST_URL") ?? process.env.TON_CONNECT_MANIFEST_URL ?? null
if (!manifestUrl) {
callback({
text: `Unable to proceed. Please provide a TON_CONNECT_MANIFEST_URL'`,
});
return;
}

const storage: IStorage = new Storg(runtime.cacheManager)

const regex = /\b[UV]Q[A-Za-z0-9_-]{46}\b/g;
const mentionedAddress = message.content.text.match(regex)?.[0] ?? null;

if (mentionedAddress) {
await storage.readFromCache(mentionedAddress)
const connector = new TonConnect({manifestUrl, storage});
await connector.restoreConnection()
if (connector.connected) {
await connector.disconnect();
}
await storage.deleteFromCache(mentionedAddress);
callback({text: `Address ${mentionedAddress} successfully disconnected`});
return
}

callback({text: 'Please provide address to disconnect'});

} catch (error) {
elizaLogger.error("Error in show ton connected action: ", error);
callback({
text: "An error occurred while make ton connect url. Please try again later.",
error
});
return;
}
},

examples: [
[
{
user: "user",
content: {
text: "let disconnect {{ADDRESS}}",
action: "DISCONNECT_TON_WALLET",
},
}
],

],
};

export default disconnect;
67 changes: 67 additions & 0 deletions src/actions/showConnected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
type Action,
type IAgentRuntime,
type Memory,
type State,
type HandlerCallback,
elizaLogger,
} from "@elizaos/core";
import {IStorage, Storg} from "../utils/storage";

export const showConnected: Action = {
name: "SHOW_TON_CONNECTED_WALLETS",
similes: ["SHOW_CONNECTED_TON_WALLETS", "LIST_TON_WALLETS", "LIST_TON_CONNECTED_WALLETS"],
description:
"Use to show all ton connected wallets",

validate: async (runtime: IAgentRuntime, _message: Memory) => {
return true
},

handler: async (
runtime: IAgentRuntime,
message: Memory,
_state?: State,
_options?: { [key: string]: unknown },
callback?: HandlerCallback
) => {
try {
const storage: IStorage = new Storg(runtime.cacheManager)

const connected = await storage.getCachedAddressList()

let lines = [
'Connected wallets:',
`────────────────`,
]

Object.keys(connected).map((address: string) => {
lines.push(`- ${address} (${connected[address]})`)
});

callback({text: lines.join("\n")});

} catch (error) {
elizaLogger.error("Error in show ton connected action: ", error);
callback({
text: "An error occurred while make ton connect url. Please try again later.",
error
});
return;
}
},

examples: [
[
{
user: "user",
content: {
text: "show all ton connected addresses",
action: "SHOW_TON_CONNECTED_WALLETS",
},
}
],

],
};
export default showConnected;
131 changes: 131 additions & 0 deletions src/actions/tonConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
type Action,
type IAgentRuntime,
type Memory,
type State,
type HandlerCallback,
elizaLogger,
} from "@elizaos/core";
import TonConnect, {
isWalletInfoCurrentlyEmbedded,
toUserFriendlyAddress,
Wallet,
WalletInfoCurrentlyEmbedded,
WalletInfoRemote
} from '@tonconnect/sdk';
import QRCode from 'qrcode';
import {IStorage, Storg} from "../utils/storage";

export const tonConnect: Action = {
name: "TON_CONNECT",
similes: ["CONNECT_TON_WALLET", "USE_TON_CONNECT"],
description:
"Use Ton Connect protocol to connect to your wallet",

validate: async (runtime: IAgentRuntime, _message: Memory) => {
return true;
},

handler: async (
runtime: IAgentRuntime,
message: Memory,
_state?: State,
_options?: { [key: string]: unknown },
callback?: HandlerCallback
) => {
try {
const manifestUrl = runtime.getSetting("TON_CONNECT_MANIFEST_URL") ?? process.env.TON_CONNECT_MANIFEST_URL ?? null
if (!manifestUrl) {
callback({
text: `Unable to proceed. Please provide a TON_CONNECT_MANIFEST_URL'`,
});
return;
}

const storage: IStorage = new Storg(runtime.cacheManager)

const connector = new TonConnect({manifestUrl, storage});

// check if user wrote wallet to use for connect
const wallets = await connector.getWallets();
const walletNames = wallets.map(wallet => wallet.name.toLowerCase());
let mentionedWallet = walletNames.find(walletName => message.content.text.toLowerCase().includes(walletName))
const tonKeeper = wallets.find(wallet => wallet.name.toLowerCase() === 'tonkeeper') as WalletInfoRemote;

let walletConnectionSources: { universalLink: string, bridgeUrl: string } | { jsBridgeKey: string } = {
universalLink: tonKeeper.universalLink ?? 'https://app.tonkeeper.com/ton-connect',
bridgeUrl: tonKeeper.bridgeUrl ?? 'https://bridge.tonapi.io/bridge'
}
if (mentionedWallet) {
const wallet: WalletInfoRemote = wallets.find(wallet => wallet.name.toLowerCase() === mentionedWallet) as WalletInfoRemote;
walletConnectionSources = {
universalLink: wallet.universalLink,
bridgeUrl: wallet.bridgeUrl
}
} else { // here check embed wallet if not mentioned other
const embeddedWallet = wallets.find(isWalletInfoCurrentlyEmbedded) as WalletInfoCurrentlyEmbedded;
mentionedWallet = 'Tonkeeper'
if (embeddedWallet) {
mentionedWallet = embeddedWallet.name
walletConnectionSources = {jsBridgeKey: embeddedWallet.jsBridgeKey};
}
}

const universalLink = connector.connect(walletConnectionSources);
const qrcode = await QRCode.toDataURL(universalLink);
const text = `You select ${mentionedWallet} to connect. Please open url in browser or scan qrcode to complete connection. ` + universalLink;
if (qrcode) {
callback({
text,
attachments: [
{
id: crypto.randomUUID(),
url: qrcode,
title: 'Connection url',
source: 'qrcode',
contentType: "image/png",
}
]
});
} else {
callback({text});
}

connector.onStatusChange(
async (data: Wallet) => {
const userFriendlyAddress = toUserFriendlyAddress(data.account.address);
await storage.writeToCache(userFriendlyAddress, data.device.appName)
}
);

} catch (error) {
elizaLogger.error("Error in ton-connect action: ", error);
callback({
text: "An error occurred while make ton connect url. Please try again later.",
error
});
return;
}
},

examples: [
[
{
user: "user",
content: {
text: "let connect to ton wallet",
action: "TON_CONNECT",
},
},
{
user: "assistant",
content: {
text: "Please use following url to connect to your wallet: ",
},
},
],

],
};

export default tonConnect;
2 changes: 2 additions & 0 deletions src/enviroment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ export const CONFIG_KEYS = {
TON_PRIVATE_KEY: "TON_PRIVATE_KEY",
TON_RPC_URL: "TON_RPC_URL",
TON_RPC_API_KEY: "TON_RPC_API_KEY",
TON_CONNECT: "TON_CONNECT",
};

export const envSchema = z.object({
TON_PRIVATE_KEY: z.string().min(1, "Ton private key is required"),
TON_RPC_URL: z.string(),
TON_RPC_API_KEY: z.string(),
TON_CONNECT_MANIFEST_URL: z.string(),
});

export type EnvConfig = z.infer<typeof envSchema>;
Expand Down
8 changes: 6 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import type { Plugin } from "@elizaos/core";
import transferAction from "./actions/transfer.ts";
import { WalletProvider, nativeWalletProvider } from "./providers/wallet.ts";
import tonConnectWalletProvider from "./providers/tonConnect.ts";
import {tonConnect} from "./actions/tonConnect.ts";
import {showConnected} from "./actions/showConnected.ts";
import {disconnect} from "./actions/disconnect.ts";

export { WalletProvider, transferAction as TransferTonToken };

export const tonPlugin: Plugin = {
name: "ton",
description: "Ton Plugin for Eliza",
actions: [transferAction],
actions: [transferAction, tonConnect, showConnected, disconnect],
evaluators: [],
providers: [nativeWalletProvider],
providers: [nativeWalletProvider, tonConnectWalletProvider],
};

export default tonPlugin;
Loading