Publishing

Table of Content

Table of Content

Table of Content

SDK Reference - v0.3.0

This JavaScript/TypeScript SDK allows publishers to serve personalized ads to users based on their Ethereum or EVM-compatible wallet addresses.

Installation

npm install @specify-sh/sdk

Quick Start

import Specify, { ImageFormat } from '@specify-sh/sdk';

// Initialize the SDK
const specify = new Specify({
  publisherKey: 'spk_your_publisher_key_here',
  // Enable for more efficient client side serves
  cacheAddressesInLocalSession: true
});

// Serve an ad to a wallet address
try {
  const ad = await specify.serve('0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510', {
    imageFormat: ImageFormat.LANDSCAPE
  });
  if (ad) {
    console.log('Ad content:', ad);
    console.log('Headline:', ad.headline);
    console.log('Content:', ad.content);
    console.log('Image URL:', ad.imageUrl);
    console.log('CTA:', ad.ctaLabel, '->', ad.ctaUrl);
  } else {
    console.log('No ad found for this wallet');
  }
} catch (error) {
  console.error('Error serving ad:', error);
}

Configuration

Constructor

Creates a new Specify client instance.

const specify = new Specify(config: SpecifyInitConfig);

Parameters

Parameter

Type

Description

config

SpecifyInitConfig

Configuration object

config.publisherKey

string

Your publisher API key (must start with spk_ and be 34 characters long)

config.cacheAddressesInLocalSession

boolean

Allows for more efficient serving even after a user disconnects wallet (false by default)

Example

const specify = new Specify({
  publisherKey: 'spk_1234567890abcdef1234567890abcdef',
  cacheAddressesInLocalSession: true
});

SDK Reference

serve()

Serves targeted advertising content to specified wallet address(es).

serve(
  addressOrAddresses: Address | Address[] | undefined, 
  options: ServeOptions
): Promise<SpecifyAd | null

Parameters

Parameter

Type

Description

Required

addressOrAddresses

Address | Address[] | undefined

Single wallet address or array of wallet addresses. Undefined or empty only works with cacheAddressesInLocalSession = true

Yes

options

ServeOptions

Configuration options for serving the ad

Yes

options.imageFormat

ImageFormat

Image format required for placement. Will match best ad with supported image format. See below for all formats.

Yes

options.adUnitId

string

Id used to identify individual ad placements / units. Useful for performance analytics.

No

ServeOptions Interface

interface ServeOptions {
  imageFormat: ImageFormat;
  adUnitId?: string;
}

Returns

Promise<SpecifyAd | null> - Ad content object or null if no ad is found

Examples

Single wallet address:

import { ImageFormat } from '@specify-sh/sdk';

const ad = await specify.serve(
  '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510',
  {
    imageFormat: ImageFormat.LANDSCAPE
  }
);

With adUnitId:

const ad = await specify.serve(
  '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510',
  {
    imageFormat: ImageFormat.LANDSCAPE,
    adUnitId: 'homepage-hero-banner'
  }
);

Multiple wallet addresses - used when you have multiple addresses for one user:

const ad = await specify.serve([
  '0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510',
  '0x8ba1f109551bD432803012645Hac136c0532925a',
  '0x1234567890123456789012345678901234567890'
], {
  imageFormat: ImageFormat.LONG_BANNER,
  adUnitId: 'multi-wallet-banner'
});

Undefined wallet addresses - With cacheAddressesInLocalSession we can serve ads even after the user disconnects their wallet:

const ad = await specify.serve(null, {
  imageFormat: ImageFormat.LONG_BANNER,
  adUnitId: 'multi-wallet-banner'
});
// Or 
const ad = await specify.serve([], {
  imageFormat: ImageFormat.LONG_BANNER,
  adUnitId: 'multi-wallet-banner'
});

Image Format Options

The Specify SDK supports three different ad image formats to match your layout needs:

Format

Aspect Ratio

Resolution

Description

Use Case

LANDSCAPE

16:9

640x360

Wide horizontal format

Hero banners, featured placements

LONG_BANNER

8:1

728x90

Extended horizontal banner

Header/footer placements, leaderboards

SHORT_BANNER

16:5

320x100

Compact horizontal banner

Inline content, sidebars, mobile banners

Return Type

When the serve() method successfully finds an ad, it returns a SpecifyAd object with the following properties:

Property

Type

Description

walletAddress

string

The wallet address that matched for this ad

campaignId

string

Unique identifier for the ad campaign

adId

string

Unique identifier for this specific ad

headline

string (no markdown)

Ad headline text

content

string (markdown)

less than 400 characters

ctaUrl

string

Call-to-action URL

ctaLabel

string (no markdown)

Call-to-action button text

imageUrl

string

URL to the ad image

communityName

string (no markdown)

Name of the advertising community

communityLogo

string

URL to the community logo

imageFormat

string

The requested image format (LANDSCAPE, LONG_BANNER, or SHORT_BANNER)

Content Formatting

The content field in ad responses uses simplified Markdown formatting:

Format

Syntax

Example

Bold text

**text**

Join today for exclusive benefits

Bullet points

* item

* Feature 1\n* Feature 2\n* Feature 3

Line breaks

\n

First line\nSecond line

Underlined Text

__text__

Out now

Italic Text

*text*

Italic

Validation Rules

Publisher Key

  • Must start with spk_

  • Must be exactly 34 characters long

  • Example: spk_1234567890abcdef1234567890abcdef

Wallet Addresses

  • Must be valid Ethereum/EVM-compatible addresses

  • Must start with 0x followed by 40 hexadecimal characters

  • Case-insensitive

  • Example: 0x742d35Cc6634C0532925a3b8D57C11E4a3e1A510

Address Limits

  • Minimum: 1 wallet address required

  • Maximum: 50 wallet addresses per request

  • Duplicate addresses are automatically removed

Best Practices

Error Handling

Always wrap SDK calls in try-catch blocks and handle specific error types:

import { AuthenticationError, ValidationError, NotFoundError, APIError, ImageFormat } from '@specify-sh/sdk';

try {
  const ad = await specify.serve(walletAddress, {
    imageFormat: ImageFormat.LANDSCAPE,
    adUnitId: 'main-content-ad'
  });
  
  if (ad) {
    // Display the ad
    displayAd(ad);
  } else {
    // Handle no ad case
    showFallbackContent();
  }
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid input:', error.message);
  } else if (error instanceof NotFoundError) {
    console.log('No ad available');
    showFallbackContent();
  } else if (error instanceof AuthenticationError) {
    console.error('Authentication failed. Check your publisher key.');
  } else if (error instanceof APIError) {
    console.error('API error:', error.message);
    showErrorMessage();
  } else {
    console.error('Unexpected error:', error);
  }
}

Examples

React Integration

import React, { useState, useEffect } from 'react';
import Specify, { SpecifyAd, ValidationError, NotFoundError, ImageFormat } from '@specify-sh/sdk';

const specify = new Specify({
  publisherKey: process.env.REACT_APP_SPECIFY_PUBLISHER_KEY!
});

interface AdComponentProps {
  walletAddress: string;
  imageFormat?: ImageFormat;
  adUnitId?: string;
}

const AdComponent: React.FC<AdComponentProps> = ({ 
  walletAddress, 
  imageFormat = ImageFormat.LANDSCAPE,
  adUnitId
}) => {
  const [ad, setAd] = useState<SpecifyAd | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchAd = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const adData = await specify.serve(walletAddress, {
          imageFormat,
          adUnitId
        });
        setAd(adData);
      } catch (err) {
        if (err instanceof ValidationError) {
          setError('Invalid wallet address');
        } else if (err instanceof NotFoundError) {
          setAd(null); // No ad available
        } else {
          setError('Failed to load ad');
        }
      } finally {
        setLoading(false);
      }
    };

    if (walletAddress) {
      fetchAd();
    }
  }, [walletAddress, imageFormat, adUnitId]);

  if (loading) return <div>Loading ad...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!ad) return <div>No ad available</div>;

  return (
    <div className="ad-container">
      <div className="ad-header">
        <img src={ad.communityLogo} alt={ad.communityName} className="community-logo" />
        <span className="community-name">{ad.communityName}</span>
      </div>
      <h3 className="ad-headline">{ad.headline}</h3>
      <img src={ad.imageUrl} alt={ad.headline} className="ad-image" />
      <p className="ad-content">{ad.content}</p>
      <a href={ad.ctaUrl} className="ad-cta" target="_blank" rel="noopener noreferrer">
        {ad.ctaLabel}
      </a>
    </div>
  );
};

export default AdComponent;

Node.js Server Integration

import express from 'express';
import Specify, { ValidationError, NotFoundError, ImageFormat } from '@specify-sh/sdk';

const app = express();
const specify = new Specify({
  publisherKey: process.env.SPECIFY_PUBLISHER_KEY!
});

app.get('/api/ads/:walletAddress', async (req, res) => {
  try {
    const { walletAddress } = req.params;
    const { format = 'LANDSCAPE', adUnitId } = req.query;
    
    const ad = await specify.serve(walletAddress, {
      imageFormat: format as ImageFormat,
      adUnitId: adUnitId as string | undefined
    });
    
    if (ad) {
      res.json({ 
        success: true, 
        ad: {
          ...ad,
          // You can transform or add additional fields here
          timestamp: new Date().toISOString()
        }
      });
    } else {
      res.status(404).json({ success: false, message: 'No ad found' });
    }
  } catch (error) {
    if (error instanceof ValidationError) {
      res.status(400).json({ 
        success: false, 
        message: error.message,
        details: error.details 
      });
    } else if (error instanceof NotFoundError) {
      res.status(404).json({ success: false, message: 'No ad found' });
    } else {
      console.error('Server error:', error);
      res.status(500).json({ success: false, message: 'Internal server error' });
    }
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

©

2025

The Internet Community Company. All rights reserved.

©

2025

The Internet Community Company. All rights reserved.

©

2025

The Internet Community Company. All rights reserved.