Sigma Auth
Access Control

Access Control API Reference

Base URL

https://auth.sigmaidentity.com

All API requests require authentication via session cookie obtained through the OAuth flow.

Authentication

Include your session cookie in requests:

Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

In production, the cookie name is __Secure-session_token (HTTPS only).

Endpoints

POST /api/wallet/connect

Connect a wallet address to the authenticated user's BAP identity.

Request

BSV Native Wallet (bitcoin-auth):

POST /api/wallet/connect
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

{
  "authToken": "03abc123...|bsm|2025-01-15T10:30:00.000Z|/api/wallet/connect|abc123..."
}

The authToken is a self-contained bitcoin-auth signature:

{pubkey}|{scheme}|{timestamp}|{requestPath}|{signature}

Response:

{
  "success": true,
  "walletAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
  "pubkey": "03abc123...",
  "connectedAt": "2025-01-15T10:30:00.000Z"
}

Errors

// Not authenticated
{
  "error": "unauthorized",
  "message": "Authentication required"
}

// Invalid authToken
{
  "error": "invalid_token",
  "message": "Malformed auth token"
}

// Invalid signature
{
  "error": "invalid_signature",
  "message": "Signature verification failed"
}

GET /api/wallet/connect

List all wallet addresses connected to the authenticated user.

Request

GET /api/wallet/connect
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

Response

{
  "wallets": [
    {
      "address": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
      "provider": "yours",
      "connectionMethod": "bsm",
      "isPrimary": false,
      "connectedAt": "2025-01-15T10:30:00.000Z",
      "lastVerified": "2025-01-15T10:30:00.000Z"
    },
    {
      "address": "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G",
      "provider": "1sat",
      "connectionMethod": "bsm",
      "isPrimary": false,
      "connectedAt": "2025-01-14T08:15:00.000Z",
      "lastVerified": "2025-01-15T09:00:00.000Z"
    }
  ]
}

DELETE /api/wallet/connect?address={walletAddress}

Remove a wallet connection.

Request

DELETE /api/wallet/connect?address=139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

Response

{
  "success": true,
  "message": "Wallet disconnected successfully"
}

POST /api/wallet/verify-ownership

Verify NFT ownership with optional threshold check.

Request

Basic Ownership Check:

POST /api/wallet/verify-ownership
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

{
  "origin": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0"
}

With Threshold:

POST /api/wallet/verify-ownership
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

{
  "origin": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0",
  "minCount": 50  // Require at least 50 NFTs
}

Alternative: Collection Parameter:

POST /api/wallet/verify-ownership
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

{
  "collection": "collection-identifier",
  "minCount": 10
}

Parameters

ParameterTypeRequiredDescription
originstringYes*Collection ID (from NFT metadata collectionId field)
collectionstringYes*Alternative collection identifier
minCountnumberNoMinimum NFTs required (default: 1)

*Either origin or collection must be provided.

Response

Ownership Confirmed:

{
  "owns": true,
  "count": 268,  // Total NFTs owned from this collection
  "nfts": [      // Array of NFT objects (first 100)
    {
      "txid": "899d447b157663d64ddce920037f8b764d2fed7095d8f51289da705c54ed2fbf",
      "vout": 0,
      "outpoint": "899d447b157663d64ddce920037f8b764d2fed7095d8f51289da705c54ed2fbf_0",
      "satoshis": 1,
      "accSats": "0",
      "height": 871519,
      "idx": "6783",
      "owner": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
      "spend": "",
      "spend_height": null,
      "spend_idx": null,
      "origin": {
        "outpoint": "8328b6dba103957f7f1d21b5bdfb5762e9e729800611d4c45f0485a16ce4987b_37",
        "data": {
          "map": {
            "app": "Bubblemint",
            "name": "Pixel Foxes #2031290",
            "type": "ord",
            "subType": "collectionItem",
            "subTypeData": {
              "collectionId": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0"
            }
          }
        },
        "num": "0854022:266:0"
      },
      "data": null
    }
    // ... up to 99 more NFTs
  ]
}

No NFTs Found:

{
  "owns": false,
  "count": 0
}

Below Threshold:

// User has 25 NFTs, but minCount is 50
{
  "owns": false,
  "count": 25
}

No Wallets Connected:

{
  "owns": false,
  "count": 0,
  "message": "No wallets connected"
}

Errors

// Not authenticated
{
  "error": "unauthorized",
  "message": "Authentication required"
}

// Missing required parameter
{
  "error": "invalid_request",
  "message": "Must provide either origin or collection"
}

// Internal error
{
  "error": "internal_error",
  "message": "Failed to verify ownership"
}

GET /api/wallet/nfts

List all NFTs owned by the authenticated user across all connected wallets.

Request

GET /api/wallet/nfts?refresh=true
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

Query Parameters

ParameterTypeDescription
refreshbooleanForce blockchain refresh (default: false)

Response

{
  "wallets": [
    {
      "address": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
      "nfts": [
        { /* ... NFT object ... */ }
      ],
      "count": 268
    },
    {
      "address": "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G",
      "nfts": [
        { /* ... NFT object ... */ }
      ],
      "count": 100
    }
  ],
  "totalNFTs": 368,
  "addresses": [
    "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
    "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G"
  ]
}

GET /api/wallet/address

Get receiving addresses for the authenticated user.

Request

GET /api/wallet/address
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

Response

{
  "primaryAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
  "addresses": [
    {
      "address": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ",
      "provider": "yours",
      "connectionMethod": "bsm",
      "isPrimary": true,
      "connectedAt": "2025-01-15T10:30:00.000Z"
    },
    {
      "address": "1J1AZwJyk9LU6tyR1ku1Ps2rs5wrLXBp2G",
      "provider": "1sat",
      "connectionMethod": "bsm",
      "isPrimary": false,
      "connectedAt": "2025-01-14T08:15:00.000Z"
    }
  ]
}

POST /api/wallet/set-primary

Set the primary receiving address for NFT purchases.

Request

POST /api/wallet/set-primary
Content-Type: application/json
Cookie: better-auth.session_token=YOUR_SESSION_TOKEN

{
  "walletAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ"
}

Response

{
  "success": true,
  "primaryAddress": "139xRf73Vw3W8cMNoXW9amqZfXMrEuM9XQ"
}

Technical Details

Pagination

The /api/wallet/verify-ownership endpoint automatically paginates through all NFTs:

  • Fetches 100 NFTs per page from the blockchain
  • Continues until no more results
  • Returns accurate total count across all pages
  • First 100 NFTs included in nfts array

Collection Identification

Collections are identified by the collectionId field in NFT metadata:

{
  "origin": {
    "data": {
      "map": {
        "subTypeData": {
          "collectionId": "1611d956f397caa80b56bc148b4bce87b54f39b234aeca4668b4d5a7785eb9fa_0"
        }
      }
    }
  }
}

The origin parameter in API requests should match this collectionId value.

Bitcoin-Auth Integration

Wallet connections use the bitcoin-auth library for signature verification:

Auth Token Format:

{pubkey}|{scheme}|{timestamp}|{requestPath}|{signature}

Supported Schemes:

  • bsm - Bitcoin Signed Message (broader compatibility)
  • brc77 - BRC-77 standard

Verification:

  • Signature is verified using the public key in the token
  • Timestamp must be within 5 minutes of current time
  • Request path must match the endpoint being accessed

Blockchain Data Source

NFT data is fetched from:

https://ordinals.gorillapool.io/api/txos/address/{address}/unspent

Parameters:

  • limit=100 - NFTs per page
  • offset={n} - Pagination offset
  • bsv20=false - Exclude BSV-20 tokens
  • origins=false - Origin data inclusion
  • refresh=true - Force blockchain refresh

Error Codes

CodeDescription
400Bad Request - Invalid parameters
401Unauthorized - Invalid or missing authentication
500Internal Server Error - Server-side error

Error Response Format

{
  "error": "error_code",
  "message": "Human-readable error description"
}

Best Practices

Caching

Cache verification results to reduce API load:

// Cache ownership checks for 5 minutes
const cacheKey = `nft-ownership-${userId}-${collectionId}`;
let result = cache.get(cacheKey);

if (!result) {
  result = await fetch('/api/wallet/verify-ownership', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ origin: collectionId })
  }).then(r => r.json());

  cache.set(cacheKey, result, 300); // 5 minutes TTL
}

Error Handling

Always handle API errors gracefully:

async function verifyOwnership(origin, minCount = 1) {
  try {
    const response = await fetch('/api/wallet/verify-ownership', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ origin, minCount })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || `HTTP ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Verification failed:', error);
    // Fallback: deny access
    return { owns: false, count: 0 };
  }
}

Multi-Threshold Optimization

Fetch count once, check multiple thresholds locally:

// ✅ Good: Single API call
const { count } = await verifyOwnership(collectionId);
const tiers = {
  bronze: count >= 10,
  silver: count >= 50,
  gold: count >= 100
};

// ❌ Bad: Multiple API calls
const bronze = await verifyOwnership(collectionId, 10);
const silver = await verifyOwnership(collectionId, 50);
const gold = await verifyOwnership(collectionId, 100);

Session Management

Session cookies are HTTP-only and secure:

  • Development: better-auth.session_token (HTTP)
  • Production: __Secure-session_token (HTTPS only)
  • Expires: After 7 days of inactivity
  • Renewal: Automatic on API requests

Example Workflows

Complete NFT Verification Flow

// 1. User authenticates via OAuth
window.location.href = 'https://auth.sigmaidentity.com/api/oauth/authorize?...';

// 2. After OAuth redirect, connect wallet
const authToken = await generateAuthToken(); // From bitcoin-auth

await fetch('https://auth.sigmaidentity.com/api/wallet/connect', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include', // Include session cookie
  body: JSON.stringify({ authToken })
});

// 3. Verify NFT ownership
const { owns, count } = await fetch('https://auth.sigmaidentity.com/api/wallet/verify-ownership', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    origin: 'collection-id',
    minCount: 50
  })
}).then(r => r.json());

// 4. Grant access based on ownership
if (owns) {
  console.log(`Access granted! User owns ${count} NFTs`);
}

Next Steps