Addresses


Every stablecoin wallet in Tsara can have one or more blockchain deposit addresses associated with it.
These addresses allow you (or your customers) to send and receive USDC on the Solana network directly into your wallet.


Overview

  • Generate unique deposit addresses for a wallet
  • Retrieve and list existing addresses
  • Receive on-chain payments directly to those addresses
  • Automatically get notified via the stablecoin.received webhook when funds arrive
  • Label addresses for easy tracking and organization
  • Generate unlimited addresses per wallet

Key Benefits of Multiple Addresses

Use CaseBenefit
Per-User AddressingGenerate unique address for each customer for easy reconciliation
Payment TrackingCreate address per order/invoice for automatic matching
Campaign TrackingSeparate addresses for different marketing campaigns
SecurityRotate addresses periodically to enhance privacy
ReconciliationAutomated payment matching without manual intervention

Create an Address

Generate a new Solana USDC-compatible address linked to a specific wallet.

Endpoint

POST /wallets/addresses

Headers

HeaderValueRequired
AuthorizationBearer YOUR_SECRET_KEYYes
Content-Typeapplication/jsonYes

Request Parameters

ParameterTypeRequiredDescriptionExample
wallet_idstringYesWallet ID to generate address for (format: wal_* or uid_*)"wal_123"
networkstringYesBlockchain network. Must be "solana""solana"
labelstringNoHuman-readable label for this address (max 100 chars)"Customer #123 Deposit"
metadataobjectNoCustom tracking data (max 10 keys, 500 chars per value){"customer_id": "cus_123"}

Important: Address Limits

  • No hard limit on addresses per wallet
  • Recommended: Keep addresses under 1000 per wallet for optimal performance
  • Addresses are permanent and cannot be deleted
  • Each address can receive unlimited deposits

Request Example

{
  "wallet_id": "wal_695fc9b9d4e992",
  "network": "solana",
  "label": "Customer #123 - Main Deposit",
  "metadata": {
    "customer_id": "cus_123",
    "customer_email": "[email protected]",
    "purpose": "deposits"
  }
}
curl -X POST "https://sandbox.tsara.ng/v1/wallets/addresses" \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_id": "wal_695fc9b9d4e992",
    "network": "solana",
    "label": "Customer #123 - Main Deposit",
    "metadata": {
      "customer_id": "cus_123"
    }
  }'

Response

{
  "success": true,
  "status": "success",
  "status_code": 200,
  "message": "Address created successfully",
  "data": {
    "id": "addr_456",
    "uid": "addr_456",
    "wallet_id": "wal_695fc9b9d4e992",
    "wallet_reference": "user_123_main_wallet",
    "address": "F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh",
    "network": "solana",
    "asset": "USDC",
    "label": "Customer #123 - Main Deposit",
    "status": "active",
    "balance": 0,
    "total_received": 0,
    "deposit_count": 0,
    "metadata": {
      "customer_id": "cus_123",
      "customer_email": "[email protected]",
      "purpose": "deposits"
    },
    "created_at": "2025-01-31T12:00:00Z",
    "last_deposit_at": null
  },
  "request_id": "req_1738318449"
}

Response Fields

FieldTypeDescription
successbooleanRequest success status
statusstringRequest status text
status_codenumberHTTP status code
messagestringHuman-readable message
data.idstringAddress ID (format: addr_*)
data.wallet_idstringParent wallet ID
data.wallet_referencestringParent wallet reference
data.addressstringSolana blockchain address (base58 encoded, 32-44 chars)
data.networkstringBlockchain network (solana)
data.assetstringAsset this address receives (USDC)
data.labelstringAddress label
data.statusstringAddress status: active, inactive
data.balancenumberCurrent balance on this specific address
data.total_receivednumberTotal USDC received on this address (lifetime)
data.deposit_countnumberNumber of deposits received on this address
data.metadataobjectCustom metadata
data.created_atstringCreation timestamp (ISO 8601)
data.last_deposit_atstringLast deposit timestamp (null if no deposits yet)
request_idstringUnique request identifier

Address Status Values

StatusDescription
activeAddress is operational and can receive deposits
inactiveAddress monitoring paused (deposits still arrive but no webhooks sent)

💡 The generated address can be shared with your users or used programmatically for deposits.

Address Format

Solana addresses are:

  • Base58 encoded strings
  • 32-44 characters long
  • Example: F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh
  • Case-sensitive
  • Support both mainnet and devnet

Error Responses

{
  "success": false,
  "status_code": 400,
  "error": {
    "code": "validation_error",
    "message": "Invalid request parameters",
    "details": [
      {
        "field": "wallet_id",
        "message": "Wallet not found"
      }
    ]
  }
}

Common Errors

Status CodeError CodeDescription
400validation_errorInvalid request parameters
400wallet_not_foundWallet ID does not exist
400invalid_networkNetwork not supported (use solana)
401unauthorizedInvalid or missing API key
429rate_limit_exceededToo many addresses created in short time

Retrieve an Address

Get address details by address ID or blockchain address.

Endpoint

GET /wallets/addresses?id={address_id}

or

GET /wallets/addresses?address={blockchain_address}

Query Parameters

ParameterTypeRequiredDescription
idstringNo*Address ID (format: addr_*)
addressstringNo*Blockchain address (Solana public key)

*At least one parameter required

Example Request

curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?id=addr_456" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

or

curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?address=F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

Response

{
  "success": true,
  "status": "success",
  "status_code": 200,
  "message": "Address retrieved",
  "data": {
    "id": "addr_456",
    "wallet_id": "wal_695fc9b9d4e992",
    "wallet_reference": "user_123_main_wallet",
    "address": "F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh",
    "network": "solana",
    "asset": "USDC",
    "label": "Customer #123 - Main Deposit",
    "status": "active",
    "balance": 150.50,
    "total_received": 500.00,
    "deposit_count": 5,
    "metadata": {
      "customer_id": "cus_123"
    },
    "created_at": "2025-01-31T12:00:00Z",
    "last_deposit_at": "2025-01-31T15:30:00Z"
  }
}

List All Addresses

Retrieve all addresses for a wallet with pagination and filtering.

Endpoint

GET /wallets/addresses?page={page}&limit={limit}

Query Parameters

ParameterTypeRequiredDefaultDescription
pagenumberNo1Page number (starts at 1)
limitnumberNo20Items per page (max: 100)
wallet_idstringNo-Filter by wallet ID
wallet_referencestringNo-Filter by wallet reference
statusstringNo-Filter by status: active, inactive
has_balancebooleanNo-Filter addresses with balance > 0

Example Request

curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?wallet_reference=user_123_main_wallet&page=1&limit=20" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

Response

{
  "success": true,
  "status": "success",
  "status_code": 200,
  "message": "Addresses retrieved",
  "data": [
    {
      "id": "addr_001",
      "wallet_id": "wal_695fc9b9d4e992",
      "address": "9szQnP5LwYmX4rN8tV6uKdJ2fE3gH7aB1cD5eF9gH0iJ",
      "label": "Main Deposit",
      "network": "solana",
      "asset": "USDC",
      "status": "active",
      "balance": 100.00,
      "total_received": 250.00,
      "deposit_count": 3,
      "created_at": "2025-01-30T10:00:00Z",
      "last_deposit_at": "2025-01-31T14:00:00Z"
    },
    {
      "id": "addr_002",
      "wallet_id": "wal_695fc9b9d4e992",
      "address": "8mxLt2pK9vN6wM5uJ4hG3fD1eC0bA8iH7gF6eD5cB4aZ",
      "label": "Promo Campaign",
      "network": "solana",
      "asset": "USDC",
      "status": "active",
      "balance": 50.50,
      "total_received": 150.00,
      "deposit_count": 2,
      "created_at": "2025-01-31T08:00:00Z",
      "last_deposit_at": "2025-01-31T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 2,
    "total_pages": 1,
    "has_more": false
  },
  "summary": {
    "total_addresses": 2,
    "active_addresses": 2,
    "total_balance": 150.50,
    "total_received": 400.00
  },
  "request_id": "req_xyz"
}

List Response Fields

FieldTypeDescription
summary.total_addressesnumberTotal number of addresses
summary.active_addressesnumberNumber of active addresses
summary.total_balancenumberCombined balance across all addresses
summary.total_receivednumberTotal USDC received across all addresses (lifetime)

Update Address (Optional)

Update address label or metadata. Address itself cannot be changed.

Endpoint

PATCH /wallets/addresses/{address_id}

Request Parameters

ParameterTypeRequiredDescription
labelstringNoNew label for address
metadataobjectNoUpdated metadata (replaces existing)
statusstringNoUpdate status: active or inactive

Example Request

curl -X PATCH "https://sandbox.tsara.ng/v1/wallets/addresses/addr_456" \
  -H "Authorization: Bearer YOUR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Customer #123 - Updated Label",
    "metadata": {
      "customer_id": "cus_123",
      "tier": "premium"
    }
  }'

Response

{
  "success": true,
  "message": "Address updated",
  "data": {
    "id": "addr_456",
    "label": "Customer #123 - Updated Label",
    "metadata": {
      "customer_id": "cus_123",
      "tier": "premium"
    },
    "updated_at": "2025-01-31T16:00:00Z"
  }
}

Webhook Notifications

Whenever USDC is received on an address, Tsara automatically sends a webhook event.

Event Type: stablecoin.received

Example Webhook Payload

{
  "id": "evt_789",
  "type": "stablecoin.received",
  "created_at": "2025-01-31T15:30:00Z",
  "data": {
    "wallet_id": "wal_695fc9b9d4e992",
    "wallet_reference": "user_123_main_wallet",
    "address_id": "addr_456",
    "address": "F9hZ1yvKt3rN2mPxQc8VuJdLwYnS4kE6fT7aB8cD9eGh",
    "amount": 50.00,
    "asset": "USDC",
    "network": "solana",
    "transaction_hash": "5quKxN7pL9mW8vR3tY6uJ2fE4gH1aB0cD5eF9gH3iJkL",
    "explorer_url": "https://explorer.solana.com/tx/5quKxN7pL9mW8vR3tY6uJ2fE4gH1aB0cD5eF9gH3iJkL",
    "sender": "CfuQhBTFXtHDVDyWpAjhQXmuN1PRLtAne5sppz9D19d",
    "balance_before": 100.50,
    "balance_after": 150.50,
    "metadata": {
      "customer_id": "cus_123"
    },
    "confirmed_at": "2025-01-31T15:30:00Z"
  }
}

Webhook Payload Fields

FieldTypeDescription
idstringUnique event ID
typestringEvent type (stablecoin.received)
created_atstringEvent timestamp
data.address_idstringAddress ID that received funds
data.amountnumberUSDC amount received
data.transaction_hashstringSolana transaction hash
data.senderstringSender's Solana address
data.balance_beforenumberAddress balance before deposit
data.balance_afternumberAddress balance after deposit
data.metadataobjectAddress metadata (useful for automatic reconciliation)

Using Webhooks for Reconciliation

app.post('/webhooks/tsara', (req, res) => {
  const event = req.body;

  if (event.type === 'stablecoin.received') {
    const deposit = event.data;
    const customerId = deposit.metadata.customer_id;

    await creditCustomerAccount(customerId, deposit.amount);

    await db.deposits.create({
      customer_id: customerId,
      amount: deposit.amount,
      address: deposit.address,
      transaction_hash: deposit.transaction_hash,
      status: 'completed'
    });

    await notifyCustomer(customerId, `Received ${deposit.amount} USDC`);
  }

  res.sendStatus(200);
});

See Webhooks Security for signature verification.


Use Cases & Examples

Per-Customer Deposit Addresses

Generate unique address for each customer for automatic payment matching.

async function createCustomerDepositAddress(customerId, customerEmail) {
  const wallet = await getBusinessWallet();

  const response = await fetch('https://sandbox.tsara.ng/v1/wallets/addresses', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SECRET_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      wallet_id: wallet.id,
      network: 'solana',
      label: `Customer ${customerId} Deposit`,
      metadata: {
        customer_id: customerId,
        customer_email: customerEmail,
        created_at: new Date().toISOString()
      }
    })
  });

  const data = await response.json();

  await db.customers.update(customerId, {
    deposit_address_id: data.data.id,
    deposit_address: data.data.address
  });

  return data.data.address;
}

Order-Specific Payment Addresses

Generate unique address per order for automatic reconciliation.

async function generateOrderPaymentAddress(orderId, orderAmount) {
  const response = await fetch('https://sandbox.tsara.ng/v1/wallets/addresses', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SECRET_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      wallet_id: BUSINESS_WALLET_ID,
      network: 'solana',
      label: `Order #${orderId}`,
      metadata: {
        order_id: orderId,
        expected_amount: orderAmount,
        created_for: 'order_payment'
      }
    })
  });

  const data = await response.json();

  await db.orders.update(orderId, {
    payment_address: data.data.address,
    expected_amount: orderAmount,
    status: 'awaiting_payment'
  });

  return data.data.address;
}

Campaign Tracking

Create addresses for different marketing campaigns.

async function createCampaignAddress(campaignName) {
  const response = await fetch('https://sandbox.tsara.ng/v1/wallets/addresses', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${SECRET_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      wallet_id: MARKETING_WALLET_ID,
      network: 'solana',
      label: `Campaign: ${campaignName}`,
      metadata: {
        campaign_name: campaignName,
        created_at: new Date().toISOString()
      }
    })
  });

  return response.json();
}

Balance Monitoring per Address

Monitor specific addresses for deposits.

async function monitorAddressDeposits(addressId) {
  const response = await fetch(
    `https://sandbox.tsara.ng/v1/wallets/addresses?id=${addressId}`,
    {
      headers: {
        'Authorization': `Bearer ${SECRET_KEY}`
      }
    }
  );

  const data = await response.json();
  const address = data.data;

  console.log(`Address: ${address.address}`);
  console.log(`Balance: ${address.balance} USDC`);
  console.log(`Total Received: ${address.total_received} USDC`);
  console.log(`Deposits: ${address.deposit_count}`);

  return address;
}

Address Rotation for Privacy

Periodically create new addresses for enhanced privacy.

async function rotateCustomerAddress(customerId) {
  const currentAddress = await getCustomerAddress(customerId);

  await updateAddressStatus(currentAddress.id, 'inactive');

  const newAddress = await createCustomerDepositAddress(customerId);

  await db.customers.update(customerId, {
    previous_addresses: [
      ...currentAddress,
      currentAddress.address
    ],
    current_deposit_address: newAddress
  });

  await notifyCustomer(customerId, 'Please use new deposit address');

  return newAddress;
}

Tips & Best Practices

  1. Use metadata for automatic reconciliation

    metadata: {
      customer_id: customerId,
      order_id: orderId,
      expected_amount: amount
    }

    Webhook includes metadata for instant matching.

  2. Label addresses clearly

    label: `${customerName} - ${purpose} - ${date}`

    Makes debugging and support easier.

  3. Store address associations

    await db.addresses.create({
      address_id: address.id,
      blockchain_address: address.address,
      customer_id: customerId,
      purpose: 'deposits',
      created_at: address.created_at
    });
  4. Monitor address usage

    const addresses = await listAddresses(wallet_id);
    const unusedAddresses = addresses.filter(a => a.deposit_count === 0);
  5. Handle duplicate deposits

    app.post('/webhooks/tsara', async (req, res) => {
      const txHash = req.body.data.transaction_hash;
    
      if (await db.deposits.exists({ transaction_hash: txHash })) {
        return res.sendStatus(200);
      }
    
      await processDeposit(req.body.data);
      res.sendStatus(200);
    });
  6. Display address as QR code

    import QRCode from 'qrcode';
    
    const qrCode = await QRCode.toDataURL(address.address);
  7. Validate address before sharing

    function isValidSolanaAddress(address) {
      return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
    }
  8. Track address performance

    const performance = {
      address: address.address,
      conversion_rate: address.deposit_count / totalShares,
      average_deposit: address.total_received / address.deposit_count,
      last_active: address.last_deposit_at
    };

Troubleshooting

Address not receiving deposits

Cause: Wrong address shared or customer sending wrong asset.

Solution:

  1. Verify address on Solana Explorer
  2. Ensure customer is sending USDC (not SOL)
  3. Confirm address belongs to your wallet
  4. Check network (mainnet vs devnet)

Verification:

curl -X GET "https://sandbox.tsara.ng/v1/wallets/addresses?address=YOUR_ADDRESS" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"

Webhook not received after deposit

Cause: Webhook URL not configured or signature validation failing.

Solution:

  1. Configure webhook URL in dashboard
  2. Verify signature validation (HMAC-SHA512)
  3. Ensure endpoint returns 200 OK
  4. Check webhook logs in dashboard

Balance not updating

Cause: Blockchain confirmation pending.

Solution:

  1. Wait 30-60 seconds for confirmation
  2. Check transaction on Solana Explorer
  3. Verify transaction hash from sender
  4. Ensure USDC contract address is correct:
    • Mainnet: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
    • Devnet: 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU

Cannot find address after creation

Cause: Using wrong wallet reference or address ID.

Solution:

const address = await fetch(
  `https://sandbox.tsara.ng/v1/wallets/addresses?wallet_reference=${walletRef}`,
  { headers: { 'Authorization': `Bearer ${SECRET_KEY}` } }
);

Duplicate deposits recorded

Cause: Webhook retry or not checking transaction hash uniqueness.

Solution:

await db.deposits.findOrCreate({
  where: { transaction_hash: deposit.transaction_hash },
  defaults: {
    customer_id: customerId,
    amount: deposit.amount,
    address: deposit.address
  }
});

Address shows inactive status

Cause: Address was manually deactivated or wallet suspended.

Solution:

  1. Check wallet status
  2. Reactivate address if needed
  3. Contact support if wallet suspended

Security Considerations

Address Validation

Always validate addresses before displaying to users:

function validateSolanaAddress(address) {
  const errors = [];

  if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) {
    errors.push('Invalid Solana address format');
  }

  if (address.length < 32 || address.length > 44) {
    errors.push('Invalid address length');
  }

  return errors.length === 0;
}

Prevent Address Reuse Across Customers

async function ensureUniqueAddress(customerId) {
  const existingAddress = await db.addresses.findOne({
    where: { customer_id: customerId, status: 'active' }
  });

  if (existingAddress) {
    return existingAddress;
  }

  return await createCustomerDepositAddress(customerId);
}

Monitor for Suspicious Activity

async function checkSuspiciousDeposit(deposit) {
  const recentDeposits = await db.deposits.count({
    where: {
      address: deposit.address,
      created_at: { $gte: Date.now() - 3600000 }
    }
  });

  if (recentDeposits > 10) {
    await alertSecurityTeam(`High frequency deposits: ${deposit.address}`);
  }

  if (deposit.amount > 10000) {
    await alertSecurityTeam(`Large deposit: ${deposit.amount} USDC`);
  }
}

Related Pages