Skip to main content
This guide shows how to create Swig wallets programmatically using the Developer Portal API. This approach is ideal for applications that need to create wallets on behalf of users with predefined policies.

When to Use the API

Use the Portal API when:
  • Creating wallets from a backend service
  • Using predefined policies from the Developer Portal
  • Leveraging the paymaster for sponsored wallet creation (optional)
  • You want to build the transaction server-side but sign and send it client-side
  • Managing wallets at scale
Use the SDK directly when:
  • Building client-side applications
  • Users control their own keypairs
  • You need full control over wallet configuration
  • Not using the Developer Portal policies
For direct SDK wallet creation (without the Portal API), see the Creating a Swig tutorial.

Prerequisites

Before creating wallets via the API, ensure you have:
A paymaster is optional. When you omit paymasterPubkey, the API returns an unsigned transaction instead of submitting it on-chain. You can then sign and send the transaction yourself. See Creating Without a Paymaster below.

Installing the SDK

npm install @swig-wallet/api
# or
bun add @swig-wallet/api

Specifying the Wallet Authority

When creating a Swig wallet, you need to define the authority (signer) that will control it. There are two ways to do this: Option 1: Using a Signer ID from the Dashboard If you’ve already created a signer in the Developer Portal dashboard (or your policy has one attached), you can reference it by its signerId. This is the original approach and is useful when you manage signers centrally through the portal. Option 2: Providing a Wallet Address and Type Directly You can supply a walletAddress (the public key) and walletType (the authority type) at creation time instead of using a signer ID. This lets you skip creating a signer in the dashboard entirely — useful when your application already knows the user’s public key and authority type.
You must use one approach or the other — provide either signerId or the walletAddress + walletType pair. If your policy already has a signer attached and you don’t pass either, the policy’s signer is used by default.

Supported Wallet Types

When using the walletAddress + walletType approach, the following types are supported:
TypeDescription
ED25519Standard Solana keypair
ED25519_SESSIONTime-limited Ed25519 session
SECP256K1Ethereum-compatible keys
SECP256K1_SESSIONTime-limited Secp256k1 session
SECP256R1WebAuthn/Passkey compatible
SECP256R1_SESSIONTime-limited Secp256r1 session
For session types (ED25519_SESSION, SECP256K1_SESSION, SECP256R1_SESSION), you must also provide the maxDurationSlots parameter to specify how long the session authority remains valid.

Creating a Swig Wallet

Using the REST API

Make a POST request to the wallet creation endpoint:
curl -X POST "https://dashboard.onswig.com/api/v1/wallet/create" \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "policyId": "clx1234567890abcdef",
    "network": "devnet",
    "paymasterPubkey": "PaymasterPublicKeyHere..."
  }'
You can also supply the wallet authority directly instead of relying on a signer from the dashboard:
curl -X POST "https://dashboard.onswig.com/api/v1/wallet/create" \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "policyId": "clx1234567890abcdef",
    "network": "devnet",
    "paymasterPubkey": "PaymasterPublicKeyHere...",
    "walletAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "walletType": "ED25519"
  }'
Example Response:
{
  "swigId": "a1b2c3d4e5f67890",
  "swigAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "signature": "5wHu1qwD7g4p3zWxF..."
}

Using the SDK

import { SwigApiClient } from '@swig-wallet/api';

// Initialize the client
const client = new SwigApiClient({
  apiKey: 'sk_your_api_key',
  portalUrl: 'https://dashboard.onswig.com',
});

// Create a wallet
async function createWallet() {
  const { data: wallet, error } = await client.wallet.create({
    policyId: 'clx1234567890abcdef',
    network: 'devnet',
    paymasterPubkey: 'PaymasterPublicKeyHere...',
  });

  if (error) {
    console.error(`Error creating wallet: ${error.message}`);
    return null;
  }

  console.log('Swig ID:', wallet.swigId);
  console.log('Wallet Address:', wallet.swigAddress);
  console.log('Transaction Signature:', wallet.signature);

  return wallet;
}

const wallet = await createWallet();

With Wallet Address and Type

You can also provide a walletAddress and walletType directly instead of relying on a signer configured in the dashboard:
import { SwigApiClient } from '@swig-wallet/api';

const client = new SwigApiClient({
  apiKey: 'sk_your_api_key',
  portalUrl: 'https://dashboard.onswig.com',
});

async function createWallet() {
  const { data: wallet, error } = await client.wallet.create({
    policyId: 'clx1234567890abcdef',
    network: 'devnet',
    paymasterPubkey: 'PaymasterPublicKeyHere...',
    walletAddress: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
    walletType: 'ED25519',
  });

  if (error) {
    console.error(`Error creating wallet: ${error.message}`);
    return null;
  }

  console.log('Swig ID:', wallet.swigId);
  console.log('Wallet Address:', wallet.swigAddress);
  console.log('Transaction Signature:', wallet.signature);

  return wallet;
}

const wallet = await createWallet();

SDK Comparison

Feature@swig-wallet/api@swig-wallet/developer
Error handlingReturns { data, error }Throws SwigError
Method nameclient.wallet.create()client.createWallet()
No-paymaster responseRaw base58 string in transactionReconstructed VersionedTransaction object
Best forLow-level controlDeveloper convenience

Request Parameters

Required Parameters

ParameterTypeDescription
policyIdstringThe policy ID from the Developer Portal
network'mainnet' | 'devnet'Target Solana network

Optional Parameters

ParameterTypeDescription
paymasterPubkeystringPublic key of the paymaster covering fees. When provided, the paymaster signs and sends the transaction. When omitted, the API returns an unsigned transaction for you to sign and send.
swigIdstringCustom Swig ID (auto-generated if not provided)
signerIdstringSigner ID from the dashboard (alternative to walletAddress + walletType)
walletAddressstringWallet public key to use as the authority (alternative to signerId). Must be provided together with walletType.
walletTypeWalletTypeAuthority type for the wallet (alternative to signerId). Must be provided together with walletAddress. One of: ED25519, ED25519_SESSION, SECP256K1, SECP256K1_SESSION, SECP256R1, SECP256R1_SESSION.
maxDurationSlotsstringMaximum session duration in slots. Only valid for session authority types (ED25519_SESSION, SECP256K1_SESSION, SECP256R1_SESSION).

Response Fields

The response shape depends on whether a paymaster was provided.

With Paymaster

FieldTypeDescription
swigIdstringThe unique identifier for this Swig wallet
swigAddressstringThe on-chain PDA address of the wallet
signaturestringThe transaction signature (viewable on explorer)

Without Paymaster

FieldTypeDescription
swigIdstringThe unique identifier for this Swig wallet
swigAddressstringThe on-chain PDA address of the wallet
transactionstringBase58-encoded serialized VersionedTransaction (unsigned)
When no paymaster is used, the expected swig address is still recorded in the Developer Portal database so you can track and manage it through the dashboard.

Creating Without a Paymaster

When you omit paymasterPubkey, the API builds the create-swig transaction but does not send it. Instead, it returns a base58-encoded serialized VersionedTransaction that you can deserialize, sign, and send yourself. This is useful when:
  • Your users pay their own transaction fees
  • You want to collect multiple signatures before sending
  • You don’t want to set up or fund a paymaster

Using the REST API (No Paymaster)

curl -X POST "https://dashboard.onswig.com/api/v1/wallet/create" \
  -H "Authorization: Bearer sk_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "policyId": "clx1234567890abcdef",
    "network": "devnet"
  }'
Example Response:
{
  "data": {
    "swigId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "swigAddress": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "transaction": "BASE58_ENCODED_VERSIONED_TRANSACTION..."
  },
  "error": null
}

Using the SDK (No Paymaster)

import { SwigApiClient } from '@swig-wallet/api';

const client = new SwigApiClient({
  apiKey: 'sk_your_api_key',
  portalUrl: 'https://dashboard.onswig.com',
});

// Create without paymaster — returns the raw transaction data
const { data, error } = await client.wallet.create({
  policyId: 'clx1234567890abcdef',
  network: 'devnet',
  // No paymasterPubkey
});

if (error) {
  console.error(`Error: ${error.message}`);
} else if (data && 'transaction' in data) {
  console.log('Swig ID:', data.swigId);
  console.log('Swig Address:', data.swigAddress);
  console.log('Transaction (base58):', data.transaction);

  // Reconstruct, sign, and send the transaction yourself:
  // const txBytes = bs58.decode(data.transaction);
  // const tx = VersionedTransaction.deserialize(txBytes);
  // tx.sign([yourKeypair]);
  // const sig = await connection.sendTransaction(tx);
}
When using the no-paymaster flow, the returned transaction includes a recent blockhash. Blockhashes expire after approximately 60-90 seconds. You must sign and send the transaction promptly.

Examples

Basic Wallet Creation

import { SwigApiClient } from '@swig-wallet/api';

const client = new SwigApiClient({
  apiKey: process.env.SWIG_API_KEY!,
  portalUrl: 'https://dashboard.onswig.com',
});

const { data, error } = await client.wallet.create({
  policyId: 'your-policy-id',
  network: 'devnet',
  paymasterPubkey: 'YourPaymasterPublicKey',
});

if (data) {
  console.log(`Wallet created: ${data.swigAddress}`);
  console.log(`View on explorer: https://explorer.solana.com/address/${data.swigAddress}?cluster=devnet`);
}

With Custom Swig ID

// Generate a custom ID (must be unique)
const customId = crypto.randomUUID().replace(/-/g, '').slice(0, 16);

const { data, error } = await client.wallet.create({
  policyId: 'your-policy-id',
  network: 'devnet',
  paymasterPubkey: 'YourPaymasterPublicKey',
  swigId: customId,
});

With Signer Override

If your policy doesn’t have a signer attached, or you want to use a different signer from the dashboard:
const { data, error } = await client.wallet.create({
  policyId: 'policy-without-signer',
  network: 'devnet',
  paymasterPubkey: 'YourPaymasterPublicKey',
  signerId: 'specific-signer-id', // Signer ID from the portal
});

With Wallet Address and Type

Instead of creating a signer in the dashboard and referencing it by ID, you can supply the wallet’s public key and authority type directly at creation time:
const { data, error } = await client.wallet.create({
  policyId: 'your-policy-id',
  network: 'devnet',
  paymasterPubkey: 'YourPaymasterPublicKey',
  walletAddress: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
  walletType: 'ED25519',
});

With Session Authority

For session-based authority types, include maxDurationSlots to set the session lifetime:
const { data, error } = await client.wallet.create({
  policyId: 'your-policy-id',
  network: 'devnet',
  paymasterPubkey: 'YourPaymasterPublicKey',
  walletAddress: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU',
  walletType: 'ED25519_SESSION',
  maxDurationSlots: '1000000', // session duration in slots
});

Production Wallet

const { data, error } = await client.wallet.create({
  policyId: 'production-policy-id',
  network: 'mainnet', // Use mainnet for production
  paymasterPubkey: 'ProductionPaymasterKey',
});

if (data) {
  // Store the wallet info in your database
  await db.wallets.create({
    swigId: data.swigId,
    address: data.swigAddress,
    createdAt: new Date(),
  });
}

Error Handling

const { data, error } = await client.wallet.create({
  policyId: 'your-policy-id',
  network: 'devnet',
  paymasterPubkey: 'YourPaymasterPublicKey',
});

if (error) {
  switch (error.code) {
    case 'NOT_FOUND':
      console.error('Policy not found. Check the policy ID.');
      break;
    case 'UNAUTHORIZED':
      console.error('Invalid API key.');
      break;
    case 'BAD_REQUEST':
      console.error('Invalid request:', error.details);
      break;
    case 'CONFLICT':
      console.error('Swig ID already exists. Try a different ID.');
      break;
    default:
      console.error(`Unexpected error: ${error.message}`);
  }
}

Common Errors

StatusCodeMeaning
400BAD_REQUESTInvalid request parameters
401UNAUTHORIZEDInvalid or missing API key
404NOT_FOUND / POLICY_NOT_FOUNDPolicy or signer not found
409CONFLICT / SWIG_ID_EXISTSSwig ID already exists
500INTERNAL_ERRORServer error, try again

Complete Example

import { SwigApiClient } from '@swig-wallet/api';

async function main() {
  const client = new SwigApiClient({
    apiKey: process.env.SWIG_API_KEY!,
    portalUrl: 'https://dashboard.onswig.com',
    retry: { maxRetries: 3, retryDelay: 1000 },
  });

  const config = {
    policyId: process.env.SWIG_POLICY_ID!,
    network: (process.env.SWIG_NETWORK || 'devnet') as 'mainnet' | 'devnet',
    paymasterPubkey: process.env.SWIG_PAYMASTER_PUBKEY!,
  };

  console.log('Creating Swig wallet...');
  console.log(`Network: ${config.network}`);
  console.log(`Policy: ${config.policyId}`);

  const { data: wallet, error } = await client.wallet.create(config);

  if (error) {
    console.error('Failed to create wallet:', error.message);
    console.error('Error code:', error.code);
    process.exit(1);
  }

  console.log('\n=== Wallet Created ===');
  console.log(`Swig ID: ${wallet.swigId}`);
  console.log(`Address: ${wallet.swigAddress}`);
  console.log(`Transaction: ${wallet.signature}`);

  const cluster = config.network === 'mainnet' ? '' : '?cluster=devnet';
  console.log(`\nExplorer: https://explorer.solana.com/address/${wallet.swigAddress}${cluster}`);
}

main();

TypeScript Types

type WalletType =
  | 'ED25519'
  | 'ED25519_SESSION'
  | 'SECP256K1'
  | 'SECP256K1_SESSION'
  | 'SECP256R1'
  | 'SECP256R1_SESSION';

interface CreateWalletRequest {
  /** Policy ID to use for wallet creation */
  policyId: string;
  /** Optional signer ID to override or provide (alternative to walletAddress + walletType) */
  signerId?: string;
  /**
   * Wallet public key to use as the authority (alternative to signerId).
   * Must be provided together with walletType.
   */
  walletAddress?: string;
  /**
   * Authority type for the wallet (alternative to signerId).
   * Must be provided together with walletAddress.
   */
  walletType?: WalletType;
  /**
   * Maximum session duration in slots (only valid for session authority types).
   * String to safely handle bigint values.
   */
  maxDurationSlots?: string;
  /** Optional swig ID (generated if not provided) */
  swigId?: string;
  /** Network to use */
  network: 'mainnet' | 'devnet';
  /** Paymaster public key (optional — omit for self-send flow) */
  paymasterPubkey?: string;
}

/** Response when a paymaster is used */
interface CreateWalletPaymasterResponse {
  swigId: string;
  swigAddress: string;
  signature: string;
}

/** Response when no paymaster is provided */
interface CreateWalletTransactionResponse {
  swigId: string;
  swigAddress: string;
  /** Base58-encoded serialized VersionedTransaction (unsigned) */
  transaction: string;
}

type CreateWalletResponse =
  | CreateWalletPaymasterResponse
  | CreateWalletTransactionResponse;

Developer SDK Types

The @swig-wallet/developer SDK provides higher-level types that automatically reconstruct the transaction:
import type { VersionedTransaction } from '@solana/web3.js';

/** Result when a paymaster was used */
interface WalletCreatePaymasterResult {
  swigId: string;
  swigAddress: string;
  signature: string;
}

/** Result when no paymaster was provided */
interface WalletCreateTransactionResult {
  swigId: string;
  swigAddress: string;
  /** Reconstructed VersionedTransaction ready for signing */
  transaction: VersionedTransaction;
}

type WalletCreateResult =
  | WalletCreatePaymasterResult
  | WalletCreateTransactionResult;

Comparison: Portal API vs Direct SDK

FeaturePortal APIDirect SDK
Uses predefined policiesYesNo
Requires API keyYesNo
Paymaster sponsoredOptionalOptional
Self-send (no paymaster)Yes — returns unsigned txYes — you build the tx
Full configuration controlLimited to policyFull
Best forBackend servicesClient apps
Keypair managementServer-sideClient-side

Next Steps

After creating a wallet, you can: