Skip to content

zcoolz/handcash-sdk-v3-migration

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

HandCash SDK v2 to v3 Migration Guide

By Paper Hands Team | January 2, 2026

We migrated 34 API calls in a production BSV app. This guide documents everything we learned that wasn't in the official docs.


TL;DR - Quick Reference

What Changed v2 (Old) v3 (New)
Import getInstance only getInstance + Connect
Method style account.wallet.pay() Connect.pay({ client: account })
Response Direct value Wrapped in { data }
Payment array payments receivers
Payment note description note
Satoshi currency currencyCode: 'SAT' Convert to BSV decimal
Balance response spendableSatoshiBalance items[].spendableBalance (in BSV)

Why This Guide Exists

HandCash v3 SDK completely changes the API pattern. The official docs show the new way but don't explain:

  • What the OLD way looked like
  • That 'SAT' is no longer a valid currency code
  • That field names changed (descriptionnote, paymentsreceivers)
  • How response shapes changed

We figured this out by reading the TypeScript definitions in node_modules/@handcash/sdk/dist/client/types.gen.d.ts.


What's Missing from Official Docs

We reviewed the official HandCash documentation (January 2, 2026) and found these critical gaps:

Gap Official Docs This Guide
v2→v3 migration path Not documented Complete before/after examples
'SAT' currency code invalid Not mentioned Discovered via SDK types, must convert to BSV decimal
paymentsreceivers rename Not explained Documented with examples
descriptionnote rename Shows description but SDK types use note Clarified what actually works
spendableSatoshiBalance removed Not mentioned New items[].spendableBalance format documented
Sats↔BSV conversion math Not provided Helper functions included
Old account.wallet.* methods Never referenced We show both old AND new patterns
Response { data } wrapper Mentioned but not emphasized Highlighted on every method

Sources Reviewed

Source URL Status
HandCash Introduction docs.handcash.io/introduction v3 setup only, no migration
Connect Payments docs.handcash.io/v3/connect/payments Shows receivers, no v2 comparison
Business Payouts docs.handcash.io/v3/business-wallet/payouts v3 syntax only
Wallet API docs.handcash.io/wallet-api/manage-wallets Shows items response, no migration notes
npm package npmjs.com/package/@handcash/sdk Returns 403, can't view README
SDK TypeScript types node_modules/@handcash/sdk/dist/client/types.gen.d.ts The real source of truth

Bottom line: Official docs show how v3 works but never acknowledge v2 existed. If you have existing v2 code, you're on your own—until now.


Step 1: Update Your Import

Before (v2)

const { getInstance } = await import('@handcash/sdk');

const handCashSdk = getInstance({
  appId: APP_ID,
  appSecret: APP_SECRET,
});

After (v3)

const { getInstance, Connect } = await import('@handcash/sdk');

const handCashSdk = getInstance({
  appId: APP_ID,
  appSecret: APP_SECRET,
});

// Connect is used for all API calls
// handCashSdk is still used for getAccountClient()

Key Point: You need BOTH getInstance (to create account clients) AND Connect (to make API calls).


Step 2: Convert Profile Calls

Before (v2)

const account = handCashSdk.getAccountClient(authToken);
const profile = await account.profile.getCurrentProfile();
console.log(profile.handle);

After (v3)

const account = handCashSdk.getAccountClient(authToken);
const profileResult = await Connect.getCurrentUserProfile({ client: account });
const profile = profileResult.data?.publicProfile;
console.log(profile.handle);

Key Changes:

  • account.profile.getCurrentProfile()Connect.getCurrentUserProfile({ client: account })
  • Response wrapped in { data: { publicProfile } }

Step 3: Convert Balance Calls (CRITICAL!)

This is where most migrations break. The response shape completely changed.

Before (v2) - Spendable Balance

const balance = await account.wallet.getSpendableBalance();
const sats = balance.spendableSatoshiBalance;

After (v3) - Spendable Balance

const balanceResult = await Connect.getSpendableBalances({ client: account });
const bsvItem = balanceResult.data?.items?.find(i => i.currencyCode === 'BSV');
const bsvBalance = bsvItem?.spendableBalance || 0;  // This is in BSV, not sats!
const sats = Math.floor(bsvBalance * 100000000);    // Convert to sats

Critical Points:

  1. Response is an array of currency items, not a single value
  2. Must find the BSV item in the array
  3. Balance is in BSV (full coins), NOT satoshis
  4. Multiply by 100,000,000 to get satoshis

Before (v2) - Total Balance

const balance = await account.wallet.getTotalBalance();

After (v3) - Total Balance

const balanceResult = await Connect.getBalances({ client: account });
const items = balanceResult.data?.items || [];

Step 4: Convert Payment Calls (THE BIG ONE)

Critical Discovery: SAT is NOT a valid currency code!

In v2, you could use currencyCode: 'SAT'. In v3, denominationCurrencyCode only accepts fiat codes (USD, EUR, etc.).

For satoshi payments, you must:

  1. Omit denominationCurrencyCode entirely
  2. Convert satoshi amount to BSV decimal (divide by 100,000,000)

Satoshi Payment - Before (v2)

await account.wallet.pay({
  payments: [{
    destination: 'recipientHandle',
    currencyCode: 'SAT',
    sendAmount: 1000  // 1000 sats
  }],
  description: 'Payment for service'
});

Satoshi Payment - After (v3)

await Connect.pay({
  client: account,
  body: {
    instrumentCurrencyCode: 'BSV',
    // NO denominationCurrencyCode for satoshi amounts!
    receivers: [{
      destination: 'recipientHandle',
      sendAmount: 1000 / 100000000  // Convert sats to BSV: 0.00001
    }],
    note: 'Payment for service'  // NOT 'description'!
  }
});

USD Payment - Before (v2)

await account.wallet.pay({
  payments: [{
    destination: 'recipientHandle',
    currencyCode: 'USD',
    sendAmount: 4.99
  }],
  description: 'Premium subscription'
});

USD Payment - After (v3)

await Connect.pay({
  client: account,
  body: {
    instrumentCurrencyCode: 'BSV',
    denominationCurrencyCode: 'USD',  // Fiat codes ARE allowed
    receivers: [{
      destination: 'recipientHandle',
      sendAmount: 4.99
    }],
    note: 'Premium subscription'
  }
});

Payment Response - Before (v2)

const result = await account.wallet.pay({...});
console.log(result.transactionId);

Payment Response - After (v3)

const result = await Connect.pay({...});
console.log(result.data?.transactionId);  // Wrapped in { data }

Step 5: Convert Exchange Rate Calls

Before (v2)

const rate = await account.wallet.getExchangeRate('USD');

After (v3)

const rateResult = await Connect.getExchangeRate({
  client: account,
  path: { currencyCode: 'USD' }
});
const rate = rateResult.data;

Helper Functions

These make the migration easier:

// Convert satoshis to BSV for payments
function satsToBsv(sats) {
  return sats / 100000000;
}

// Convert BSV balance to satoshis
function bsvToSats(bsv) {
  return Math.floor(bsv * 100000000);
}

// Extract BSV spendable balance in sats
function getSpendableSats(balanceResult) {
  const bsvItem = balanceResult.data?.items?.find(i => i.currencyCode === 'BSV');
  return bsvToSats(bsvItem?.spendableBalance || 0);
}

// Safe transaction ID extraction
function getTxId(payResult) {
  return payResult.data?.transactionId;
}

Complete Method Mapping

v2 Method v3 Method
account.profile.getCurrentProfile() Connect.getCurrentUserProfile({ client: account })
account.wallet.getSpendableBalance() Connect.getSpendableBalances({ client: account })
account.wallet.getTotalBalance() Connect.getBalances({ client: account })
account.wallet.getExchangeRate('USD') Connect.getExchangeRate({ client: account, path: { currencyCode: 'USD' } })
account.wallet.pay({ payments, description }) Connect.pay({ client: account, body: { receivers, note, instrumentCurrencyCode } })

Common Errors & Fixes

Error: "denominationCurrencyCode must be a valid fiat currency"

Cause: Using 'SAT' as denominationCurrencyCode Fix: Omit denominationCurrencyCode entirely and convert sats to BSV decimal

Error: "receivers is required"

Cause: Using old payments field name Fix: Rename payments array to receivers

Error: Cannot read property 'transactionId' of undefined

Cause: Accessing response directly instead of via .data Fix: Change result.transactionId to result.data?.transactionId

Error: Balance shows 0 or undefined

Cause: Using old spendableSatoshiBalance property Fix: Find BSV in items array: result.data?.items?.find(i => i.currencyCode === 'BSV')?.spendableBalance


Migration Checklist

  • Import Connect alongside getInstance
  • Update all account.profile.* calls to Connect.*
  • Update all account.wallet.* calls to Connect.*
  • Change payments arrays to receivers
  • Change description to note
  • Convert SAT amounts to BSV decimal (÷ 100,000,000)
  • Remove currencyCode: 'SAT', add instrumentCurrencyCode: 'BSV'
  • Update response access to use .data?.
  • Update balance parsing to find BSV in items array
  • Test with small amounts before going live

Real World Stats

Paper Hands Migration:

  • 34 API calls converted
  • 21 payment calls (mix of SAT and USD)
  • 8 balance checks
  • 2 profile calls
  • 1 exchange rate call
  • 0 production issues after migration

About

This guide was created by the Paper Hands team after migrating a production BSV trading game on the day HandCash v3 became required.

Paper Hands is a memecoin trading simulator built on Bitcoin SV with real satoshi payouts via HandCash.


Contributing

Found something we missed? Open an issue or PR. Let's help the BSV dev community migrate smoothly.


License

MIT - Use this however you want. Just trying to help.


This guide exists because we spent hours figuring out what the docs didn't tell us. Hope it saves you time.

About

Complete migration guide for HandCash SDK v2 to v3 - field mappings, gotchas, and real-world examples from 34 API calls

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors