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.
| 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) |
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 (
description→note,payments→receivers) - How response shapes changed
We figured this out by reading the TypeScript definitions in node_modules/@handcash/sdk/dist/client/types.gen.d.ts.
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 |
payments → receivers rename |
Not explained | Documented with examples |
description → note 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 |
| 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.
const { getInstance } = await import('@handcash/sdk');
const handCashSdk = getInstance({
appId: APP_ID,
appSecret: APP_SECRET,
});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).
const account = handCashSdk.getAccountClient(authToken);
const profile = await account.profile.getCurrentProfile();
console.log(profile.handle);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 } }
This is where most migrations break. The response shape completely changed.
const balance = await account.wallet.getSpendableBalance();
const sats = balance.spendableSatoshiBalance;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 satsCritical Points:
- Response is an array of currency items, not a single value
- Must find the BSV item in the array
- Balance is in BSV (full coins), NOT satoshis
- Multiply by 100,000,000 to get satoshis
const balance = await account.wallet.getTotalBalance();const balanceResult = await Connect.getBalances({ client: account });
const items = balanceResult.data?.items || [];In v2, you could use currencyCode: 'SAT'. In v3, denominationCurrencyCode only accepts fiat codes (USD, EUR, etc.).
For satoshi payments, you must:
- Omit
denominationCurrencyCodeentirely - Convert satoshi amount to BSV decimal (divide by 100,000,000)
await account.wallet.pay({
payments: [{
destination: 'recipientHandle',
currencyCode: 'SAT',
sendAmount: 1000 // 1000 sats
}],
description: 'Payment for service'
});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'!
}
});await account.wallet.pay({
payments: [{
destination: 'recipientHandle',
currencyCode: 'USD',
sendAmount: 4.99
}],
description: 'Premium subscription'
});await Connect.pay({
client: account,
body: {
instrumentCurrencyCode: 'BSV',
denominationCurrencyCode: 'USD', // Fiat codes ARE allowed
receivers: [{
destination: 'recipientHandle',
sendAmount: 4.99
}],
note: 'Premium subscription'
}
});const result = await account.wallet.pay({...});
console.log(result.transactionId);const result = await Connect.pay({...});
console.log(result.data?.transactionId); // Wrapped in { data }const rate = await account.wallet.getExchangeRate('USD');const rateResult = await Connect.getExchangeRate({
client: account,
path: { currencyCode: 'USD' }
});
const rate = rateResult.data;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;
}| 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 } }) |
Cause: Using 'SAT' as denominationCurrencyCode
Fix: Omit denominationCurrencyCode entirely and convert sats to BSV decimal
Cause: Using old payments field name
Fix: Rename payments array to receivers
Cause: Accessing response directly instead of via .data
Fix: Change result.transactionId to result.data?.transactionId
Cause: Using old spendableSatoshiBalance property
Fix: Find BSV in items array: result.data?.items?.find(i => i.currencyCode === 'BSV')?.spendableBalance
- Import
ConnectalongsidegetInstance - Update all
account.profile.*calls toConnect.* - Update all
account.wallet.*calls toConnect.* - Change
paymentsarrays toreceivers - Change
descriptiontonote - Convert SAT amounts to BSV decimal (÷ 100,000,000)
- Remove
currencyCode: 'SAT', addinstrumentCurrencyCode: 'BSV' - Update response access to use
.data?. - Update balance parsing to find BSV in items array
- Test with small amounts before going live
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
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.
Found something we missed? Open an issue or PR. Let's help the BSV dev community migrate smoothly.
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.