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 Plugin: Ton Connect implementation #3228

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 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
41 changes: 41 additions & 0 deletions packages/plugin-ton-connect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Ton Connect Plugin for Eliza

The plugin for Eliza enables connection to any TON wallet via the Ton Connect protocol.

## Overview

This plugin offers the following features:

- Seamless connection to any supported TON wallets via Ton Connect.
- Support for multiple wallets and simultaneous connections.
- Display a list of connected wallets.
- Effortless disconnection from Ton Connect.
- A provider to select and manage active connections (e.g., execute transactions).

## Installation

```bash
npm install @elizaos/plugin-ton-connect
```

## Configuration

The plugin requires the following environment variables:

```env
TON_CONNECT_MANIFEST_URL=https://domain/ton-manifest.json
```
How to create manifest read [here](https://docs.ton.org/v3/guidelines/ton-connect/guidelines/creating-manifest)

## Usage

Import and register the plugin in your Eliza configuration:

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

export default {
plugins: [tonConnectPlugin],
// ... other configuration
};
```
34 changes: 34 additions & 0 deletions packages/plugin-ton-connect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@elizaos/plugin-ton-connect",
"version": "0.1.0",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"@elizaos/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"dependencies": {
"@elizaos/core": "workspace:*",
"@tonconnect/sdk": "^3.0.6",
"tsup": "8.3.5",
"qrcode": "^1.5.4"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"test": "vitest run",
"clean": "rm -rf dist",
"lint": "eslint --fix --cache ."
}
}
80 changes: 80 additions & 0 deletions packages/plugin-ton-connect/src/actions/disconnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
type Action,
type IAgentRuntime,
type Memory,
type State,
type HandlerCallback,
elizaLogger,
} from "@elizaos/core";
import {IStorage, Storg} from "../libs/storage.ts";
import TonConnect from "@tonconnect/sdk";

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",
},
}
],

],
};
66 changes: 66 additions & 0 deletions packages/plugin-ton-connect/src/actions/showConnected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
type Action,
type IAgentRuntime,
type Memory,
type State,
type HandlerCallback,
elizaLogger,
} from "@elizaos/core";
import {IStorage, Storg} from "../libs/storage.ts";

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",
},
}
],

],
};
129 changes: 129 additions & 0 deletions packages/plugin-ton-connect/src/actions/tonConnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
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 {IStorage, Storg} from "../libs/storage.ts";
import QRCode from 'qrcode';

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,
bridgeUrl: tonKeeper.bridgeUrl
}
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: ",
},
},
],

],
};
15 changes: 15 additions & 0 deletions packages/plugin-ton-connect/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type {Plugin} from "@elizaos/core";
import {tonConnect} from "./actions/tonConnect.ts";
import {showConnected} from "./actions/showConnected.ts";
import {disconnect} from "./actions/disconnect.ts";
import WalletProvider from "./providers/wallet.ts";

export const tonConnectPlugin: Plugin = {
name: "ton-connect",
description: "Ton Connect Plugin for Eliza",
actions: [tonConnect, showConnected, disconnect],
providers: [WalletProvider],
services: [],
};

export default tonConnectPlugin;
Loading
Loading