DAIN Service SDK

The official TypeScript SDK for building AI-powered services on the DAIN Protocol. Create intelligent services that can be discovered and used by AI agents across multiple platforms.
π Table of Contents
β¨ Features
- π Multi-Runtime Support - Works seamlessly on Node.js, Deno, Cloudflare Workers, and Next.js
- π§ Type-Safe Tools - Define tools with Zod schemas for automatic validation and type inference
- π OAuth2 Integration - Built-in OAuth2 support with flexible storage adapters
- π Plugin System - Extend functionality with crypto, citations, time, and custom plugins
- π‘ Real-Time Streaming - Server-Sent Events (SSE) for streaming responses
- π Long-Running Processes - State machine-based process management
- π³ Payment Integration - Built-in support for Stripe payments
- π¨ UI Components - Return rich UI responses alongside data
- π Contexts & Datasources - Provide dynamic context and data to AI agents
- π‘οΈ Signature-Based Auth - Secure authentication using Ed25519 signatures
π¦ Installation
npm install @dainprotocol/service-sdk zod
Optional Dependencies
For OAuth2 with persistent storage:
npm install @dainprotocol/oauth2-token-manager @dainprotocol/oauth2-storage-drizzle
For process management with Redis:
npm install ioredis
π Quick Start
Creating a Simple Weather Service (Node.js)
import { defineDAINService } from '@dainprotocol/service-sdk';
import { z } from 'zod';
const getWeatherTool = {
id: 'get-weather',
name: 'Get Weather',
description: 'Fetches current weather for a city',
input: z.object({
city: z.string().describe('The name of the city'),
}),
output: z.object({
temperature: z.number().describe('Temperature in Celsius'),
condition: z.string().describe('Weather condition'),
}),
pricing: { pricePerUse: 0.01, currency: 'USD' },
handler: async ({ city }, agentInfo) => {
return {
text: `The weather in ${city} is 22Β°C and Sunny`,
data: { temperature: 22, condition: 'Sunny' },
ui: undefined,
};
},
};
const service = defineDAINService({
metadata: {
title: 'Weather Service',
description: 'A service for weather information',
version: '1.0.0',
author: 'Your Name',
tags: ['weather'],
},
identity: {
apiKey: process.env.DAIN_API_KEY,
},
tools: [getWeatherTool],
});
service.startNode({ port: 3000 }).then(() => {
console.log('Weather service running on port 3000');
});
Using the Client SDK
import { DainServiceConnection, DainClientAuth } from '@dainprotocol/service-sdk/client';
const auth = new DainClientAuth({
apiKey: process.env.DAIN_API_KEY,
});
const service = new DainServiceConnection('http://localhost:3000', auth);
const result = await service.callTool('get-weather', {
city: 'San Francisco',
});
console.log(result.text);
console.log(result.data);
π§© Core Concepts
Services
A service is a collection of tools, contexts, datasources, and widgets that provide specific functionality to AI agents.
const service = defineDAINService({
metadata: { },
identity: { },
tools: [ ],
contexts: [ ],
datasources: [ ],
widgets: [ ],
});
Tools
Tools are functions that AI agents can call. Each tool has:
- A unique ID
- Input/output schemas (using Zod)
- A handler function
- Optional pricing information
const myTool = {
id: 'my-tool',
name: 'My Tool',
description: 'Does something useful',
input: z.object({ param: z.string() }),
output: z.object({ result: z.string() }),
handler: async (input, agentInfo, context) => {
return {
text: 'Human-readable response',
data: { result: 'structured data' },
ui: { },
};
},
};
Agent Info
Every tool handler receives agentInfo
with details about the calling agent:
interface AgentInfo {
agentId: string;
address: string;
smartAccountPDA?: string;
id: string;
webhookUrl?: string;
}
Tool Context
The context
parameter provides access to:
- The Hono app instance
- OAuth2 client for authenticated API calls
- Custom extra data
- UI update functions
- Process management
interface ToolContext {
app: Hono;
oauth2Client?: OAuth2Client;
extraData?: any;
updateUI?: (update: { ui: any }) => Promise<void>;
addProcess?: (processId: string) => Promise<void>;
}
π Multi-Runtime Support
The SDK automatically adapts to your runtime environment through conditional exports.
Node.js
import { defineDAINService } from '@dainprotocol/service-sdk';
const service = defineDAINService({ });
await service.startNode({ port: 3000 });
Deno
import { defineDAINService } from '@dainprotocol/service-sdk';
const service = defineDAINService({ });
await service.startDeno({ port: 3000 });
Cloudflare Workers
import { defineDAINService } from '@dainprotocol/service-sdk';
const service = defineDAINService({ });
export default {
fetch: service.startWorkers(),
};
Next.js (App Router)
import { createNextDainService } from '@dainprotocol/service-sdk/next';
const { GET, POST } = createNextDainService({
metadata: { },
identity: { },
tools: [ ],
});
export { GET, POST };
π§ Tools
Creating Tools
Use the createTool
helper for better type inference:
import { createTool } from '@dainprotocol/service-sdk';
const weatherTool = createTool({
id: 'get-weather',
name: 'Get Weather',
description: 'Get current weather',
input: z.object({
lat: z.number(),
lon: z.number(),
}),
output: z.object({
temp: z.number(),
description: z.string(),
}),
handler: async ({ lat, lon }) => {
const response = await fetch(
`https://api.openweather.org/data/2.5/weather?lat=${lat}&lon=${lon}`
);
const data = await response.json();
return {
text: `Temperature: ${data.main.temp}Β°C`,
data: {
temp: data.main.temp,
description: data.weather[0].description,
},
ui: undefined,
};
},
});
Tool Interfaces
Tools can implement standardized interfaces for better interoperability:
import { createToolWithInterface, ToolInterfaceType } from '@dainprotocol/service-sdk';
const searchTool = createToolWithInterface({
id: 'search-docs',
name: 'Search Documentation',
description: 'Search through documentation',
interface: ToolInterfaceType.KNOWLEDGE_SEARCH,
input: z.object({
query: z.string(),
}),
output: z.object({
results: z.array(z.object({
title: z.string(),
content: z.string(),
citations: z.array(CitationSchema),
})),
}),
handler: async ({ query }) => {
},
});
Tool Pricing
Add pricing to monetize your tools:
const paidTool = {
id: 'premium-analysis',
name: 'Premium Analysis',
description: 'Advanced data analysis',
pricing: {
pricePerUse: 0.50,
currency: 'USD',
},
};
Toolboxes
Group related tools into toolboxes:
import { createToolbox } from '@dainprotocol/service-sdk';
const weatherToolbox = createToolbox({
id: 'weather-toolbox',
name: 'Weather Tools',
description: 'Complete weather information toolkit',
tools: ['get-weather', 'get-forecast', 'get-alerts'],
metadata: {
complexity: 'simple',
applicableFields: ['weather', 'climate'],
},
recommendedPrompt: 'Use these tools to get comprehensive weather information',
});
π OAuth2 Integration
The SDK includes comprehensive OAuth2 support with automatic token management and refresh.
Basic OAuth2 Setup
import { defineDAINService } from '@dainprotocol/service-sdk';
import { InMemoryStorageAdapter } from '@dainprotocol/oauth2-token-manager';
const service = defineDAINService({
metadata: { },
identity: { },
tools: [ ],
oauth2: {
baseUrl: 'https://your-service.com',
tokenStore: new InMemoryStorageAdapter(),
providers: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
authorizationUrl: 'https://github.com/login/oauth/authorize',
tokenUrl: 'https://github.com/login/oauth/access_token',
scopes: ['user', 'repo'],
reason: 'Access your GitHub repositories',
requiredTools: ['get-repos', 'create-gist'],
},
},
},
});
Persistent Storage (PostgreSQL)
import { DrizzleStorageAdapter } from '@dainprotocol/oauth2-storage-drizzle';
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const sql = postgres(process.env.DATABASE_URL!);
const db = drizzle(sql);
const service = defineDAINService({
oauth2: {
baseUrl: 'https://your-service.com',
tokenStore: new DrizzleStorageAdapter(db, { dialect: 'postgres' }),
providers: { },
},
});
Custom Token Paths
For OAuth2 providers with non-standard response formats (like Slack):
const service = defineDAINService({
oauth2: {
baseUrl: 'https://your-service.com',
providers: {
slack: {
clientId: process.env.SLACK_CLIENT_ID!,
clientSecret: process.env.SLACK_CLIENT_SECRET!,
authorizationUrl: 'https://slack.com/oauth/v2/authorize',
tokenUrl: 'https://slack.com/api/oauth.v2.access',
scopes: ['chat:write', 'channels:read'],
tokenPaths: {
accessToken: 'authed_user.access_token',
tokenType: 'token_type',
scope: 'scope',
},
},
},
},
});
Using OAuth2 in Tools
import { createOAuth2Tool } from '@dainprotocol/service-sdk';
const getGitHubProfileTool = createOAuth2Tool({
id: 'get-github-profile',
name: 'Get GitHub Profile',
description: 'Get the authenticated user GitHub profile',
provider: 'github',
input: z.object({}),
output: z.object({
login: z.string(),
name: z.string(),
email: z.string().nullable(),
bio: z.string().nullable(),
}),
handler: async (input, agentInfo, context) => {
const accessToken = await context.oauth2Client!.getAccessToken(
'github',
`${agentInfo.agentId}@dain.local`
);
const response = await fetch('https://api.github.com/user', {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/vnd.github.v3+json',
},
});
const data = await response.json();
return {
text: `GitHub Profile: ${data.login}`,
data: {
login: data.login,
name: data.name,
email: data.email,
bio: data.bio,
},
ui: undefined,
};
},
});
OAuth2 Provider Options
interface OAuth2ProviderConfig {
clientId: string;
clientSecret: string;
authorizationUrl: string;
tokenUrl: string;
scopes: string[];
usePKCE?: boolean;
useBasicAuth?: boolean;
reason?: string;
requiredTools?: string[];
extraAuthParams?: Record<string, string>;
responseRootKey?: string;
tokenPaths?: {
accessToken?: string | string[];
refreshToken?: string | string[];
expiresIn?: string | string[];
tokenType?: string | string[];
scope?: string | string[];
};
onSuccess?: (agentId: string, tokens: OAuth2Tokens) => Promise<void>;
}
π Plugins
Plugins extend functionality on both the service and client side.
Built-in Plugins
Crypto Plugin
import { CryptoPlugin } from '@dainprotocol/service-sdk/plugins';
const cryptoPlugin = new CryptoPlugin();
const service = defineDAINService({
plugins: [cryptoPlugin],
});
Provides:
- Message signing and verification
- Encryption/decryption
- Hash generation
- Key management
Citations Plugin
import { CitationsPlugin } from '@dainprotocol/service-sdk/plugins';
const citationsPlugin = new CitationsPlugin({
namespace: 'my-service',
});
const service = defineDAINService({
plugins: [citationsPlugin],
});
Provides:
- Citation tracking
- Source verification
- Citation formatting
Time Plugin
import { TimePlugin } from '@dainprotocol/service-sdk/plugins';
const timePlugin = new TimePlugin();
const service = defineDAINService({
plugins: [timePlugin],
});
Provides:
- Timezone conversion
- Timestamp validation
- Time-based operations
Creating Custom Plugins
import { DainPlugin } from '@dainprotocol/service-sdk';
class MyCustomPlugin implements DainPlugin {
id = 'my-custom-plugin';
async initialize(service: DAINService): Promise<void> {
console.log('Plugin initialized');
}
async processInputClient?(input: any): Promise<any> {
return { myData: 'client-processed' };
}
async processInputService?(input: any, agentInfo: AgentInfo): Promise<any> {
return { myData: 'service-processed' };
}
async processOutputService?(output: any, agentInfo: AgentInfo): Promise<any> {
return output;
}
}
const service = defineDAINService({
plugins: [new MyCustomPlugin()],
});
π‘ Client SDK
Connecting to a Service
import { DainServiceConnection, DainClientAuth } from '@dainprotocol/service-sdk/client';
const auth = new DainClientAuth({
apiKey: process.env.DAIN_API_KEY,
});
const service = new DainServiceConnection('http://localhost:3000', auth, {
plugins: [],
});
Getting Service Metadata
const metadata = await service.getMetadata();
console.log(metadata.title);
console.log(metadata.description);
console.log(metadata.version);
Listing Available Tools
const tools = await service.getTools();
tools.forEach(tool => {
console.log(`${tool.name}: ${tool.description}`);
});
Calling Tools
const result = await service.callTool('tool-id', {
param1: 'value1',
param2: 'value2',
});
console.log(result.text);
console.log(result.data);
console.log(result.ui);
Streaming Responses
const stream = await service.callToolStream('tool-id', {
query: 'What is the weather?',
});
for await (const chunk of stream) {
if (chunk.type === 'text-delta') {
process.stdout.write(chunk.textDelta);
} else if (chunk.type === 'tool-result') {
console.log('\nFinal result:', chunk.result);
}
}
Working with Contexts
const contexts = await service.getContexts();
const userContext = await service.getContext('user-context', {
param: 'value',
});
console.log(userContext.data);
Working with Datasources
const datasources = await service.getDatasources();
const data = await service.getDatasource('my-datasource', {
filter: 'active',
});
console.log(data.data);
OAuth2 from Client
const providers = await service.getOAuth2Providers();
const authResult = await service.callTool('oauth2-github', {});
console.log('Auth URL:', authResult.data.authUrl);
const profile = await service.callTool('get-github-profile', {});
Using with AI SDK
import { generateText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const tools = await service.getToolsAsAISDKTools();
const result = await generateText({
model: anthropic('claude-3-5-sonnet-20241022'),
prompt: 'What is the weather in San Francisco?',
tools,
maxSteps: 10,
});
console.log(result.text);
π Advanced Features
Contexts
Contexts provide dynamic information about the service or user state:
const userContext = {
id: 'user-preferences',
name: 'User Preferences',
description: 'User-specific preferences and settings',
getContextData: async (agentInfo, extraData) => {
return {
theme: 'dark',
language: 'en',
timezone: 'America/Los_Angeles',
};
},
};
const service = defineDAINService({
contexts: [userContext],
});
Datasources
Datasources provide queryable data to AI agents:
const productDatasource = {
id: 'products',
name: 'Product Catalog',
description: 'Search and browse products',
type: 'data' as const,
input: z.object({
category: z.string().optional(),
minPrice: z.number().optional(),
maxPrice: z.number().optional(),
}),
getDatasource: async (agentInfo, params, extraData) => {
const products = await db.products.findMany({
where: {
category: params.category,
price: {
gte: params.minPrice,
lte: params.maxPrice,
},
},
});
return products;
},
};
const service = defineDAINService({
datasources: [productDatasource],
});
Widgets
Widgets display information in the DAIN UI:
const dashboardWidget = {
id: 'dashboard',
name: 'Dashboard',
description: 'Overview dashboard',
icon: 'dashboard',
size: 'lg' as const,
getWidget: async (agentInfo, extraData) => {
const stats = await getStats(agentInfo.agentId);
return {
text: 'Dashboard overview',
data: stats,
ui: {
type: 'dashboard',
children: [
{ type: 'stat', label: 'Total Users', value: stats.users },
{ type: 'stat', label: 'Active Sessions', value: stats.sessions },
],
},
};
},
};
const service = defineDAINService({
widgets: [dashboardWidget],
});
Long-Running Processes
For operations that take time to complete:
import { MemoryProcessStore } from '@dainprotocol/service-sdk';
const service = defineDAINService({
processStore: new MemoryProcessStore(),
});
const processTool = {
id: 'start-analysis',
name: 'Start Analysis',
description: 'Start a long-running analysis',
handler: async (input, agentInfo, context) => {
const processId = generateId();
startAnalysisProcess(processId, input);
return {
text: 'Analysis started',
data: { processId },
ui: undefined,
processes: [{
id: processId,
name: 'Data Analysis',
description: 'Analyzing your data...',
type: 'one-time',
}],
};
},
};
Human-in-the-Loop Actions
Request human approval or input during tool execution:
const service = defineDAINService({
onHumanActionResponse: async ({ process, stepId, actionId, responseText, data }) => {
console.log('Human responded:', responseText);
},
});
Custom Routes
Add custom endpoints to your service:
const service = defineDAINService({
routes: (app) => {
app.get('/health', (c) => {
return c.json({ status: 'healthy' });
});
app.post('/webhook', async (c) => {
const body = await c.req.json();
return c.json({ received: true });
});
},
});
Payment Integration
import { createStripePaymentIntent } from '@dainprotocol/service-sdk/payments';
const paidTool = {
id: 'premium-feature',
name: 'Premium Feature',
description: 'A premium paid feature',
pricing: { pricePerUse: 5.00, currency: 'USD' },
handler: async (input, agentInfo) => {
const paymentIntent = await createStripePaymentIntent({
amount: 500,
currency: 'usd',
metadata: {
agentId: agentInfo.agentId,
toolId: 'premium-feature',
},
});
return {
text: 'Please complete payment to continue',
data: {},
ui: undefined,
pleasePay: paymentIntent,
};
},
};
π API Reference
Service Configuration
interface DAINServiceConfig {
metadata: {
title: string;
description: string;
version: string;
author: string;
tags: string[];
logo?: string;
};
identity: {
apiKey?: string;
publicKey?: string;
privateKey?: string;
agentId?: string;
orgId?: string;
};
tools: ToolConfig[];
toolboxes?: ToolboxConfig[];
services?: ServiceConfig[];
contexts?: ServiceContext[];
datasources?: ServiceDatasource[];
widgets?: ServiceWidget[];
agents?: ServiceAgent[];
plugins?: DainPlugin[];
oauth2?: {
baseUrl: string;
tokenStore?: StorageAdapter;
providers: Record<string, OAuth2ProviderConfig>;
};
processStore?: ProcessStoreAdapter;
routes?: (app: Hono) => void;
serverExtensions?: NodeServerExtension[];
baseUrl?: string;
exampleQueries?: {
category: string;
queries: string[];
}[];
getUserWidgets?: (agentInfo: AgentInfo, extraData?: any) => Promise<string[]>;
homeUI?: string | ((agentInfo: AgentInfo, extraData?: any) => Promise<string>);
onHumanActionResponse?: (response: {
process: Process;
stepId: string;
actionId: string;
responseText?: string;
data?: any;
}) => Promise<void>;
}
Tool Configuration
interface ToolConfig<TInput extends z.ZodType, TOutput extends z.ZodType> {
id: string;
name: string;
description: string;
input: TInput;
output: TOutput;
pricing?: {
pricePerUse: number;
currency: string;
};
interface?: string;
suggestConfirmation?: boolean;
suggestConfirmationUI?: (input: z.input<TInput>) => Promise<{
success?: boolean;
ui?: any;
}>;
handler: (
input: z.input<TInput>,
agentInfo: AgentInfo,
context: ToolContext
) => Promise<{
text: string;
data: z.output<TOutput>;
ui: any | undefined;
pleasePay?: PaymentIntent;
processes?: string[] | ProcessInfo[];
}>;
handleInputError?: (
error: ZodError,
agentInfo: AgentInfo,
extraData?: any
) => Promise<ToolResponse>;
}
Runtime Methods
await service.startNode({ port?: number });
await service.startDeno({ port?: number });
export default { fetch: service.startWorkers() };
const { GET, POST } = createNextDainService(config);
π Examples
Complete Weather Service
See example/simpleWeatherService-node.ts
OAuth2 Integration
See example/oauth-client-example.ts
Long-Running Processes
See example/processService.ts
Using Plugins
See example/crypto-plugin-usage.ts
Next.js Integration
See examples/nextjs-app-router
π Migration Guide
If you're upgrading from an older version, see OAUTH_MIGRATION.md for OAuth2 token store migration instructions.
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
π License
ISC License - see LICENSE file for details
π Links
π¬ Support
Built with β€οΈ by the DAIN Protocol team