Introducing Socket Firewall: Free, Proactive Protection for Your Software Supply Chain.Learn More β†’
Socket
Book a DemoInstallSign in
Socket

@dainprotocol/service-sdk

Package Overview
Dependencies
Maintainers
6
Versions
176
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dainprotocol/service-sdk

DAIN Service SDK

latest
npmnpm
Version
2.0.26
Version published
Weekly downloads
1.9K
210.23%
Maintainers
6
Weekly downloads
Β 
Created
Source

DAIN Service SDK

npm version License: ISC

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';

// 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');
});

Using the Client SDK

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' }

🧩 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: { /* optional UI component */ },
    };
  },
};

Agent Info

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
}

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({ /* config */ });
await service.startNode({ port: 3000 });

Deno

import { defineDAINService } from '@dainprotocol/service-sdk';

const service = defineDAINService({ /* config */ });
await service.startDeno({ port: 3000 });

Cloudflare Workers

import { defineDAINService } from '@dainprotocol/service-sdk';

const service = defineDAINService({ /* config */ });

export default {
  fetch: service.startWorkers(),
};

Next.js (App Router)

// app/api/dain/[...dain]/route.ts
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 }) => {
    // Implementation
  },
});

Tool Pricing

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
};

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: { /* ... */ },
  },
  // ... rest of config
});

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'],
        // Custom paths for extracting tokens from response
        tokenPaths: {
          accessToken: 'authed_user.access_token',
          tokenType: 'token_type',
          scope: 'scope',
        },
      },
    },
  },
  // ... rest of config
});

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;              // 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

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],
  // ... rest of config
});

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],
  // ... rest of config
});

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],
  // ... rest of config
});

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> {
    // 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
});

πŸ“‘ 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: [/* optional 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);  // Human-readable response
console.log(result.data);  // Structured data
console.log(result.ui);    // Optional UI component

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

// 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', {});

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) => {
    // Fetch user preferences from database
    return {
      theme: 'dark',
      language: 'en',
      timezone: 'America/Los_Angeles',
    };
  },
};

const service = defineDAINService({
  contexts: [userContext],
  // ... rest of config
});

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) => {
    // 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

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
});

Long-Running Processes

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',
      }],
    };
  },
};

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);
    // Continue the process based on human response
  },
  // ... rest of config
});

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();
      // Handle webhook
      return c.json({ received: true });
    });
  },
  // ... rest of config
});

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) => {
    // 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,
    };
  },
};

πŸ“š 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

// 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);

πŸ“– 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

πŸ’¬ Support

Built with ❀️ by the DAIN Protocol team

Keywords

dain

FAQs

Package last updated on 18 Oct 2025

Did you know?

Socket

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.

Install

Related posts