
Research
Malicious NuGet Packages Typosquat Nethereum to Exfiltrate Wallet Keys
The Socket Threat Research Team uncovered malicious NuGet packages typosquatting the popular Nethereum project to steal wallet keys.
@dainprotocol/service-sdk
Advanced tools
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.
npm install @dainprotocol/service-sdk zod
For OAuth2 with persistent storage:
npm install @dainprotocol/oauth2-token-manager @dainprotocol/oauth2-storage-drizzle
For process management with Redis:
npm install ioredis
import { defineDAINService } from '@dainprotocol/service-sdk';
import { z } from 'zod';
// Define a tool
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) => {
// Your logic here
return {
text: `The weather in ${city} is 22Β°C and Sunny`,
data: { temperature: 22, condition: 'Sunny' },
ui: undefined,
};
},
};
// Create and start the service
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],
});
// Start the service
service.startNode({ port: 3000 }).then(() => {
console.log('Weather service running on port 3000');
});
import { DainServiceConnection, DainClientAuth } from '@dainprotocol/service-sdk/client';
// Initialize authentication
const auth = new DainClientAuth({
apiKey: process.env.DAIN_API_KEY,
});
// Connect to a service
const service = new DainServiceConnection('http://localhost:3000', auth);
// Call a tool
const result = await service.callTool('get-weather', {
city: 'San Francisco',
});
console.log(result.text); // "The weather in San Francisco is 22Β°C and Sunny"
console.log(result.data); // { temperature: 22, condition: 'Sunny' }
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 are functions that AI agents can call. Each tool has:
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: { /* optional UI component */ },
};
},
};
Every tool handler receives agentInfo
with details about the calling agent:
interface AgentInfo {
agentId: string; // Unique agent identifier
address: string; // Agent's blockchain address
smartAccountPDA?: string;
id: string;
webhookUrl?: string; // For sending async updates
}
The context
parameter provides access to:
interface ToolContext {
app: Hono;
oauth2Client?: OAuth2Client;
extraData?: any;
updateUI?: (update: { ui: any }) => Promise<void>;
addProcess?: (processId: string) => Promise<void>;
}
The SDK automatically adapts to your runtime environment through conditional exports.
import { defineDAINService } from '@dainprotocol/service-sdk';
const service = defineDAINService({ /* config */ });
await service.startNode({ port: 3000 });
import { defineDAINService } from '@dainprotocol/service-sdk';
const service = defineDAINService({ /* config */ });
await service.startDeno({ port: 3000 });
import { defineDAINService } from '@dainprotocol/service-sdk';
const service = defineDAINService({ /* config */ });
export default {
fetch: service.startWorkers(),
};
// app/api/dain/[...dain]/route.ts
import { createNextDainService } from '@dainprotocol/service-sdk/next';
const { GET, POST } = createNextDainService({
metadata: { /* ... */ },
identity: { /* ... */ },
tools: [ /* ... */ ],
});
export { GET, POST };
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,
};
},
});
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 }) => {
// Implementation
},
});
Add pricing to monetize your tools:
const paidTool = {
id: 'premium-analysis',
name: 'Premium Analysis',
description: 'Advanced data analysis',
pricing: {
pricePerUse: 0.50, // $0.50 per use
currency: 'USD',
},
// ... rest of tool config
};
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',
});
The SDK includes comprehensive OAuth2 support with automatic token management and refresh.
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'],
},
},
},
});
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: { /* ... */ },
},
// ... rest of config
});
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'],
// Custom paths for extracting tokens from response
tokenPaths: {
accessToken: 'authed_user.access_token',
tokenType: 'token_type',
scope: 'scope',
},
},
},
},
// ... rest of config
});
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,
};
},
});
interface OAuth2ProviderConfig {
clientId: string;
clientSecret: string;
authorizationUrl: string;
tokenUrl: string;
scopes: string[];
usePKCE?: boolean; // Use PKCE flow (recommended for public clients)
useBasicAuth?: boolean; // Use Basic Auth for token exchange
reason?: string; // Explain why OAuth is needed (shown to users)
requiredTools?: string[]; // Tools that require this OAuth connection
extraAuthParams?: Record<string, string>; // Additional auth parameters
responseRootKey?: string; // Root key for token response
tokenPaths?: { // Custom token extraction paths
accessToken?: string | string[];
refreshToken?: string | string[];
expiresIn?: string | string[];
tokenType?: string | string[];
scope?: string | string[];
};
onSuccess?: (agentId: string, tokens: OAuth2Tokens) => Promise<void>;
}
Plugins extend functionality on both the service and client side.
import { CryptoPlugin } from '@dainprotocol/service-sdk/plugins';
const cryptoPlugin = new CryptoPlugin();
const service = defineDAINService({
plugins: [cryptoPlugin],
// ... rest of config
});
Provides:
import { CitationsPlugin } from '@dainprotocol/service-sdk/plugins';
const citationsPlugin = new CitationsPlugin({
namespace: 'my-service',
});
const service = defineDAINService({
plugins: [citationsPlugin],
// ... rest of config
});
Provides:
import { TimePlugin } from '@dainprotocol/service-sdk/plugins';
const timePlugin = new TimePlugin();
const service = defineDAINService({
plugins: [timePlugin],
// ... rest of config
});
Provides:
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> {
// Process input on the client side
return { myData: 'client-processed' };
}
async processInputService?(input: any, agentInfo: AgentInfo): Promise<any> {
// Process input on the service side
return { myData: 'service-processed' };
}
async processOutputService?(output: any, agentInfo: AgentInfo): Promise<any> {
// Process output before sending to client
return output;
}
}
// Use it
const service = defineDAINService({
plugins: [new MyCustomPlugin()],
// ... rest of config
});
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: [/* optional plugins */],
});
const metadata = await service.getMetadata();
console.log(metadata.title);
console.log(metadata.description);
console.log(metadata.version);
const tools = await service.getTools();
tools.forEach(tool => {
console.log(`${tool.name}: ${tool.description}`);
});
const result = await service.callTool('tool-id', {
param1: 'value1',
param2: 'value2',
});
console.log(result.text); // Human-readable response
console.log(result.data); // Structured data
console.log(result.ui); // Optional UI component
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);
}
}
const contexts = await service.getContexts();
const userContext = await service.getContext('user-context', {
param: 'value',
});
console.log(userContext.data);
const datasources = await service.getDatasources();
const data = await service.getDatasource('my-datasource', {
filter: 'active',
});
console.log(data.data);
// Check available OAuth2 providers
const providers = await service.getOAuth2Providers();
// Initiate OAuth flow
const authResult = await service.callTool('oauth2-github', {});
console.log('Auth URL:', authResult.data.authUrl);
// After authentication, call OAuth-protected tools
const profile = await service.callTool('get-github-profile', {});
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);
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) => {
// Fetch user preferences from database
return {
theme: 'dark',
language: 'en',
timezone: 'America/Los_Angeles',
};
},
};
const service = defineDAINService({
contexts: [userContext],
// ... rest of config
});
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) => {
// Query database
const products = await db.products.findMany({
where: {
category: params.category,
price: {
gte: params.minPrice,
lte: params.maxPrice,
},
},
});
return products;
},
};
const service = defineDAINService({
datasources: [productDatasource],
// ... rest of config
});
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],
// ... rest of config
});
For operations that take time to complete:
import { MemoryProcessStore } from '@dainprotocol/service-sdk';
const service = defineDAINService({
processStore: new MemoryProcessStore(),
// ... rest of config
});
// In a tool handler
const processTool = {
id: 'start-analysis',
name: 'Start Analysis',
description: 'Start a long-running analysis',
handler: async (input, agentInfo, context) => {
const processId = generateId();
// Start background process
startAnalysisProcess(processId, input);
return {
text: 'Analysis started',
data: { processId },
ui: undefined,
processes: [{
id: processId,
name: 'Data Analysis',
description: 'Analyzing your data...',
type: 'one-time',
}],
};
},
};
Request human approval or input during tool execution:
const service = defineDAINService({
onHumanActionResponse: async ({ process, stepId, actionId, responseText, data }) => {
console.log('Human responded:', responseText);
// Continue the process based on human response
},
// ... rest of config
});
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();
// Handle webhook
return c.json({ received: true });
});
},
// ... rest of config
});
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) => {
// Request payment
const paymentIntent = await createStripePaymentIntent({
amount: 500, // $5.00 in cents
currency: 'usd',
metadata: {
agentId: agentInfo.agentId,
toolId: 'premium-feature',
},
});
return {
text: 'Please complete payment to continue',
data: {},
ui: undefined,
pleasePay: paymentIntent,
};
},
};
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>;
}
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>;
}
// Node.js
await service.startNode({ port?: number });
// Deno
await service.startDeno({ port?: number });
// Cloudflare Workers
export default { fetch: service.startWorkers() };
// Next.js
const { GET, POST } = createNextDainService(config);
See example/simpleWeatherService-node.ts
See example/oauth-client-example.ts
See example/crypto-plugin-usage.ts
See examples/nextjs-app-router
If you're upgrading from an older version, see OAUTH_MIGRATION.md for OAuth2 token store migration instructions.
Contributions are welcome! Please feel free to submit a Pull Request.
ISC License - see LICENSE file for details
Built with β€οΈ by the DAIN Protocol team
FAQs
DAIN Service SDK
The npm package @dainprotocol/service-sdk receives a total of 902 weekly downloads. As such, @dainprotocol/service-sdk popularity was classified as not popular.
We found that @dainprotocol/service-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 6 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
The Socket Threat Research Team uncovered malicious NuGet packages typosquatting the popular Nethereum project to steal wallet keys.
Product
A single platform for static analysis, secrets detection, container scanning, and CVE checksβbuilt on trusted open source tools, ready to run out of the box.
Product
Socket is launching experimental protection for the Hugging Face ecosystem, scanning for malware and malicious payload injections inside model files to prevent silent AI supply chain attacks.