Plugin SDK Reference
Complete single-page reference for every namespace, method, and type in the @teleton-agent/sdk package.
1. Overview & Interface
The PluginSDK is a frozen, immutable object passed to every plugin via the tools(sdk) export. All properties are readonly. The object is created once per plugin at load time and cannot be modified at runtime.
interface PluginSDK {
version: string
readonly ton: TonSDK
readonly telegram: TelegramSDK
readonly db: Database | null
readonly config: Record<string, unknown>
readonly pluginConfig: Record<string, unknown>
readonly secrets: SecretsSDK
readonly storage: StorageSDK | null
readonly log: PluginLogger
}| Property | Type | Description |
|---|---|---|
version | string | SDK version (SemVer). Always "1.0.0". |
ton | TonSDK | TON blockchain: wallet, balance, transfers, jettons, NFTs, payment verification. |
telegram | TelegramSDK | Telegram messaging, media, moderation, stars & gifts. Runtime-only. |
db | Database | null | Plugin-isolated SQLite database (better-sqlite3). null if the plugin does not export migrate(). |
config | Record<string, unknown> | Sanitized application config. Excludes all API keys and secrets. |
pluginConfig | Record<string, unknown> | Plugin-specific config from config.yaml merged with manifest defaultConfig. |
secrets | SecretsSDK | Secure access to API keys, tokens, and credentials. |
storage | StorageSDK | null | Key-value storage with optional TTL. Always available (auto-creates _kv table). |
log | PluginLogger | Prefixed logger: [plugin:name] message. |
bot | BotSDK | null | Bot SDK for inline mode — null if bot not configured or plugin manifest does not declare a BotManifest (bot: { inline?: true, callbacks?: true }). |
Sub-namespaces: sdk.ton exposes two nested namespaces: sdk.ton.dex (DEX quotes and swaps on STON.fi and DeDust) and sdk.ton.dns (.ton domain management). These are always available when sdk.ton is available.
sdk.db vs sdk.storage: sdk.db is null if the plugin does not export migrate(), but sdk.storage works regardless — it auto-creates its own _kv table without needing a migration.
2. sdk.version
Type: string. Always "1.0.0" (SemVer). Use this for compatibility checks in your plugin manifest via the sdkVersion field.
if (sdk.version !== '1.0.0') {
sdk.log.warn('Untested SDK version: ' + sdk.version);
}3. sdk.ton : TON Blockchain
Type: TonSDK. Provides full access to TON blockchain operations including wallet management, token transfers, NFTs, and payment verification.
Core Methods
| Method | Returns | Description |
|---|---|---|
getAddress() | string | null | Bot's wallet address. Returns null if wallet is not initialized. |
getBalance(address?) | Promise<TonBalance> | Balance in TON + nanoTON. Omit address for the bot's own wallet. |
getPrice() | Promise<TonPrice> | Current TON/USD price. Cached for 30 seconds. |
sendTON(to, amount, comment?) | Promise<TonSendResult> | Transfer TON to an address. IRREVERSIBLE. |
getTransactions(address, limit?) | Promise<TonTransaction[]> | Recent transactions. limit: 1–50, default 10. |
validateAddress(address) | boolean | Check if a TON address is valid. |
toNano(amount) | bigint | Convert TON amount to nanoTON. |
fromNano(nano) | string | Convert nanoTON to TON string. |
Jetton Methods
| Method | Returns | Description |
|---|---|---|
getJettonBalances(ownerAddress?) | Promise<...> | Token balances for the owner. Defaults to bot wallet. |
getJettonInfo(jettonAddress) | Promise<...> | Jetton metadata: name, symbol, decimals, total supply. |
sendJetton(jettonAddress, to, amount, opts?) | Promise<...> | Transfer jetton tokens to another address. |
getJettonWalletAddress(ownerAddress, jettonAddress) | Promise<string> | Resolve the jetton wallet address for an owner. |
NFT Methods
| Method | Returns | Description |
|---|---|---|
getNftItems(ownerAddress?) | Promise<...> | List NFTs owned by the address. Defaults to bot wallet. |
getNftInfo(nftAddress) | Promise<...> | Metadata for a single NFT. |
Payment Verification
const result = await sdk.ton.verifyPayment({
amount: '1.5', // expected TON amount (1% tolerance)
memo: 'order-123', // expected transaction memo
timeWindow: 600, // seconds to look back (default: 600 = 10min)
senderAddress: '...' // optional: expected sender
});
// result: {
// verified: boolean,
// txHash: string,
// amount: string,
// playerWallet: string,
// date: Date,
// secondsAgo: number,
// error?: string
// }Payment verification checks recent transactions for: amount match (1% tolerance), memo match, time window (10 minutes default), and replay protection via the used_transactions table.
TON Error Codes
| Error | Meaning |
|---|---|
WALLET_NOT_INITIALIZED | Bot wallet has not been set up or activated. |
INVALID_ADDRESS | The provided TON address is malformed. |
OPERATION_FAILED | Generic blockchain operation failure. |
const balance = await sdk.ton.getBalance();
sdk.log.info(`Wallet balance: ${balance.balance} TON`);
const price = await sdk.ton.getPrice();
sdk.log.info(`TON/USD: $${price.usd}`);
if (sdk.ton.validateAddress(userAddr)) {
const result = await sdk.ton.sendTON(userAddr, '0.5', 'reward');
sdk.log.info(`Sent 0.5 TON, hash: ${result.hash}`);
}4. sdk.telegram : Telegram Messaging
Type: TelegramSDK. Provides access to the full Telegram API via GramJS. Only available at runtime (not during plugin loading). Call sdk.telegram.isAvailable() to check.
Basic Messaging
| Method | Returns | Description |
|---|---|---|
sendMessage(chatId, text, opts?) | messageId | Send a text message. opts may include parseMode, replyTo, buttons. |
editMessage(chatId, messageId, text, opts?) | messageId | Edit an existing message. |
sendReaction(chatId, messageId, emoji) | void | React to a message with an emoji. |
sendDice(chatId, emoticon, replyToId?) | {value, messageId} | Send an animated dice/slot/bowling message. |
Media
| Method | Returns | Description |
|---|---|---|
sendPhoto(chatId, photo, opts?) | messageId | Send a photo. photo can be a file path or Buffer. |
sendVideo(chatId, video, opts?) | messageId | Send a video file. |
sendVoice(chatId, voice, opts?) | messageId | Send a voice message. Must be OGG/Opus format. |
sendFile(chatId, file, opts?) | messageId | Send a document. Use opts.fileName to set the name. |
sendGif(chatId, gif, opts?) | messageId | Send an animated GIF. |
sendSticker(chatId, sticker) | messageId | Send a sticker. Must be WEBP format. |
downloadMedia(chatId, messageId) | Buffer | null | Download media from a message. Returns null if no media. |
Message Queries
| Method | Returns | Description |
|---|---|---|
getMessages(chatId, limit?) | Message[] | Fetch recent messages from a chat. |
searchMessages(chatId, query, limit?) | Message[] | Search messages in a chat by text query. |
getReplies(chatId, messageId, limit?) | Message[] | Get replies to a specific message. |
Message Management
| Method | Returns | Description |
|---|---|---|
deleteMessage(chatId, messageId, revoke?) | void | Delete a message. revoke deletes for everyone. |
forwardMessage(fromChatId, toChatId, messageId) | messageId | Forward a message between chats. |
pinMessage(chatId, messageId, opts?) | void | Pin a message in a chat. |
scheduleMessage(chatId, text, scheduleDate) | messageId | Schedule a message for future delivery. |
Chat & User Info
| Method | Returns | Description |
|---|---|---|
getChatInfo(chatId) | {id, title, type, membersCount?, username?} | Get chat metadata. |
getUserInfo(userId) | {id, firstName, lastName?, username?, isBot} | Get user profile info. |
resolveUsername(username) | {id, type, username?, title?} | Resolve a @username to an entity. |
getParticipants(chatId, limit?) | Participant[] | List chat/group members. |
getMe() | User | Get the bot's own user profile. |
Interactive
| Method | Returns | Description |
|---|---|---|
createPoll(chatId, question, answers, opts?) | messageId | Create a poll. opts may include isAnonymous, multipleChoice. |
createQuiz(chatId, question, answers, correctIndex, explanation?) | messageId | Create a quiz with a correct answer. |
Moderation
| Method | Returns | Description |
|---|---|---|
banUser(chatId, userId) | void | Ban a user from a chat. |
unbanUser(chatId, userId) | void | Unban a previously banned user. |
muteUser(chatId, userId, untilDate?) | void | Mute a user. untilDate is a Unix timestamp; omit for permanent mute. |
Stars & Gifts
| Method | Returns | Description |
|---|---|---|
getStarsBalance() | number | Get the bot's Telegram Stars balance. |
sendGift(userId, giftId, opts?) | void | Send a gift to a user. |
getAvailableGifts() | Gift[] | List all gifts available for purchase. |
getMyGifts(limit?) | Gift[] | List gifts the bot has received. |
getResaleGifts(limit?) | Gift[] | Browse gifts available for resale. |
buyResaleGift(giftId) | void | Purchase a resale gift. |
sendStory(mediaPath, opts?) | void | Post a story to the bot's profile. |
Advanced
| Method | Returns | Description |
|---|---|---|
setTyping(chatId) | void | Show "typing..." indicator in a chat. |
getRawClient() | TelegramClient | Access the raw GramJS TelegramClient instance for unsupported operations. |
isAvailable() | boolean | Check if the Telegram client is connected and ready. |
// Send a message with inline buttons
const msgId = await sdk.telegram.sendMessage(chatId, 'Choose an option:', {
buttons: [
[{ text: 'Option A', data: 'opt_a' }],
[{ text: 'Option B', data: 'opt_b' }]
]
});
// Download and re-send media
const buffer = await sdk.telegram.downloadMedia(chatId, mediaMessageId);
if (buffer) {
await sdk.telegram.sendPhoto(chatId, buffer, { caption: 'Downloaded!' });
}
// Moderation
await sdk.telegram.muteUser(chatId, spammerId, Math.floor(Date.now() / 1000) + 3600);5. sdk.db : Plugin Database
Type: better-sqlite3.Database | null
- null if the plugin does not export a
migrate()function. - Location:
~/.teleton/plugins/data/{plugin-name}.db - Each plugin gets its own isolated SQLite database.
- The API is synchronous (better-sqlite3).
To enable sdk.db, your plugin must export a migrate function that creates the required tables:
export const migrate = (db: any) => {
db.exec(`
CREATE TABLE IF NOT EXISTS scores (
user_id INTEGER PRIMARY KEY,
points INTEGER NOT NULL DEFAULT 0,
updated_at TEXT DEFAULT (datetime('now'))
)
`);
};
// Then in your tool:
export const tools = (sdk: PluginSDK): SimpleToolDef[] => [{
name: 'get_score',
description: 'Get user score',
async execute(params, context) {
const row = sdk.db!.prepare('SELECT points FROM scores WHERE user_id = ?')
.get(context.senderId);
return { success: true, data: row ?? { points: 0 } };
}
}];6. sdk.config : Application Config
Type: Record<string, unknown>
A sanitized view of the application configuration. Useful for checking the LLM provider or admin list.
Included Fields
| Key | Description |
|---|---|
agent.provider | LLM provider name (e.g. "openai", "anthropic") |
agent.model | Model identifier (e.g. "gpt-4o") |
agent.max_tokens | Maximum token limit for responses |
telegram.admin_ids | Array of Telegram user IDs with admin privileges |
Excluded Fields (never exposed)
api_key, api_hash, phone, session data, all API tokens. Use sdk.secrets for sensitive values.
7. sdk.pluginConfig : Plugin-Specific Config
Type: Record<string, unknown>
Config values specific to your plugin, sourced from config.yaml under the plugins.{plugin_name_with_underscores} key. The final config is produced by merging the plugin manifest's defaultConfig with the user's overrides (user values take precedence).
plugins:
my_plugin:
max_retries: 5
api_endpoint: "https://api.example.com"const maxRetries = (sdk.pluginConfig.max_retries as number) ?? 3;
const endpoint = sdk.pluginConfig.api_endpoint as string;8. sdk.secrets : Secure API Keys
Type: SecretsSDK
Secure, multi-source secret resolution. Values are resolved in this order:
- Environment variable:
{PLUGINNAME}_KEY - Value set via
/plugin setcommand sdk.pluginConfig[key]undefined
| Method | Returns | Description |
|---|---|---|
get(key) | string | undefined | Get a secret value. Returns undefined if not found. |
require(key) | string | Get a secret value or throw PluginSDKError if missing. |
has(key) | boolean | Check if a secret is available. |
// Safe check
if (sdk.secrets.has('api_key')) {
const key = sdk.secrets.get('api_key');
// use key...
}
// Fail fast
const key = sdk.secrets.require('api_key'); // throws if missing9. sdk.storage : Key-Value Storage
Type: StorageSDK | null
A simple key-value store backed by SQLite. Always available, the SDK auto-creates a _kv table. Supports optional TTL for automatic expiration.
| Method | Returns | Description |
|---|---|---|
get<T>(key) | T | undefined | Get a value by key. Returns undefined if not found or expired. |
set<T>(key, value, opts?) | void | Set a key-value pair. opts.ttl in milliseconds. |
delete(key) | boolean | Delete a key. Returns true if the key existed. |
has(key) | boolean | Check if a key exists and is not expired. |
clear() | void | Remove all key-value pairs. |
// Cache a value for 5 minutes
sdk.storage!.set('api_response', data, { ttl: 5 * 60 * 1000 });
// Retrieve it
const cached = sdk.storage!.get<ApiResponse>('api_response');
if (cached) {
return { success: true, data: cached };
}
// Check and delete
if (sdk.storage!.has('old_key')) {
sdk.storage!.delete('old_key');
}10. sdk.log : Prefixed Logger
Type: PluginLogger
All log output is automatically prefixed with [plugin:{name}]. Four log levels are available:
| Method | Description |
|---|---|
info(message, ...args) | General informational messages. |
warn(message, ...args) | Warnings that do not halt execution. |
error(message, ...args) | Errors and exceptions. |
debug(message, ...args) | Verbose debug output (usually hidden in production). |
sdk.log.info('Processing payment');
// Output: [plugin:my-plugin] Processing payment
sdk.log.error('Payment failed', { code: 'TIMEOUT', retries: 3 });
// Output: [plugin:my-plugin] Payment failed { code: 'TIMEOUT', retries: 3 }11. PluginManifest
Every plugin must export a manifest object that describes the plugin's metadata, dependencies, and configuration schema.
interface PluginManifest {
name: string // lowercase, alphanumeric + hyphens, 1-64 chars
version: string // semver (e.g. "1.0.0")
author?: string // plugin author name
description?: string // max 256 characters
dependencies?: string[] // required modules: ["deals", "ton"]
defaultConfig?: Record<string, unknown> // default config values
sdkVersion?: string // required SDK version range (e.g. ">=1.0.0")
secrets?: Record<string, SecretDeclaration> // declared secrets
}export const manifest: PluginManifest = {
name: 'price-alerts',
version: '2.1.0',
author: 'team@example.com',
description: 'TON price alerts with configurable thresholds',
dependencies: ['ton'],
sdkVersion: '>=1.0.0',
defaultConfig: {
check_interval: 60,
threshold_pct: 5
},
secrets: {
webhook_url: {
required: false,
description: 'Optional webhook for external notifications'
}
}
};12. Tool Definition (SimpleToolDef)
Each tool returned by tools(sdk) must conform to the SimpleToolDef interface:
interface SimpleToolDef {
name: string // unique tool name
description: string // what the tool does (shown to LLM)
parameters?: Record<string, unknown> // JSON Schema for input params
execute: (params: any, context: PluginToolContext) => Promise<ToolResult>
scope?: "always" | "dm-only" | "group-only" | "admin-only"
category?: "data-bearing" | "action"
}
interface ToolResult {
success: boolean
data?: unknown
error?: string
}
interface PluginToolContext {
chatId: string
senderId: number
isGroup: boolean
bridge: unknown // raw TelegramBridge (prefer sdk.telegram)
db: unknown // raw database (prefer sdk.db)
config?: unknown
}Scope Values
| Scope | Description |
|---|---|
"always" | Available in all contexts (default). |
"dm-only" | Only available in direct messages. |
"group-only" | Only available in group chats. |
"admin-only" | Restricted to admin users (from telegram.admin_ids). |
Category Values
| Category | Description |
|---|---|
"data-bearing" | Tool returns data for the LLM to reason about. |
"action" | Tool performs an action with side effects. |
export const tools = (sdk: PluginSDK): SimpleToolDef[] => [{
name: 'check_balance',
description: 'Check TON balance for the bot wallet or a given address',
scope: 'always',
category: 'data-bearing',
parameters: {
type: 'object',
properties: {
address: {
type: 'string',
description: 'TON address to check (optional, defaults to bot wallet)'
}
}
},
async execute(params, context) {
try {
const balance = await sdk.ton.getBalance(params.address);
return {
success: true,
data: { balance: balance.balance, nano: balance.nanoBalance }
};
} catch (err) {
sdk.log.error('Balance check failed', err);
return { success: false, error: String(err) };
}
}
}];13. Lifecycle Hooks
Plugins can export optional lifecycle hooks to react to system events:
| Hook | Signature | When Called |
|---|---|---|
start | (context) => Promise<void> | After Telegram client connects. Use for initialization. |
stop | () => Promise<void> | On graceful shutdown. Use for cleanup. |
onMessage | (event) => Promise<void> | On every incoming message. Use to react to messages passively. |
onCallbackQuery | (event) => Promise<void> | When a user clicks an inline button. Use for interactive flows. |
import type { PluginSDK } from '@teleton-agent/sdk';
export const start = async (ctx: { sdk: PluginSDK; log: PluginLogger }) => {
ctx.log.info('Plugin started, initializing cache...');
ctx.sdk.storage?.set('boot_time', Date.now());
};
export const stop = async () => {
// Cleanup timers, connections, etc.
};
export const onMessage = async (event: {
chatId: string;
senderId: number;
text: string;
messageId: number;
}) => {
// React to messages without being called as a tool
if (event.text.includes('gm')) {
await sdk.telegram.sendReaction(event.chatId, event.messageId, '👋');
}
};
export const onCallbackQuery = async (event: {
chatId: string;
senderId: number;
data: string;
messageId: number;
}) => {
// Handle inline button presses
if (event.data === 'opt_a') {
await sdk.telegram.editMessage(event.chatId, event.messageId, 'You chose A!');
}
};