Guides
Solana
Solana (SOL)
Solana can be accessed with the following coin types:
| Environment | Coin Type | Faucet |
|---|---|---|
| Solana Production | sol | |
| Solana Testnet | tsol | https://solfaucet.com/ |
Note: Solana Devnet is the primary pre-Mainnet chain for testing by developers and users, whereas Solana Testnet is almost exclusively for validators testing validator client upgrades. For more details on Solana clusters, see their documentation, Solana Clusters.
Explorer
Generating wallets
bitgo
.coin('tsol')
.wallets()
.generateWallet({
label: 'My Test Wallet',
passphrase: 'secretpassphrase1a5df8380e0e30',
})
.then(function (wallet) {
// print the new wallet
console.dir(wallet);
});
LABEL="My Test Wallet"
PASSPHRASE="secretpassphrase1a5df8380e0e30"
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "{ \"label\": \"$LABEL\", \"passphrase\": \"$PASSPHRASE\" }" \
http://$BITGO_EXPRESS_HOST/api/v2/tsol/wallet/generate
You can create wallets with a single line of code using the BitGo SDK.
Creating addresses
bitgo
.coin('tsol')
.wallets()
.getWallet({ id: '636d0c90b221c80007c69365ca8a3508' })
.then(function (wallet) {
return wallet.createAddress();
})
.then(function (newAddress) {
// print new address details
console.dir(newAddress);
});
WALLET=636d0c90b221c80007c69365ca8a3508
curl -X POST \
-H "Authorization: Bearer $ACCESS_TOKEN" \
https://app.bitgo-test.com/api/v2/tsol/wallet/$WALLET/address
Balances
Solana (SOL) is the native asset of the Solana blockchain. The base unit of Solana is a Lamport (1/1000000000) of 1 SOL coin:
- 1 Lamport is (
10-9) or 0.000000001 SOL. - 1 Solana is (
109) or 1000000000 Lamport.
BitGo supports balance values in string and number format, but BitGo recommends strings to ensure values do not exceed the
programmable number limit: balanceString, confirmedBalanceString, and spendableBalanceString.
Solana accounts pay a fee to store data on the blockchain, which Solana calls "rent". To prevent accounts from paying rent, Solana wallets retain a minimum balance of 0.002447136 SOL to make them rent exempt. The minimum balance must stay on your wallet and cannot be spent.
Fee rate
The default fee is 0.00001 SOL for all Hot/Custody/Cold wallet transactions.
Consolidation
To consolidate assets into your root address through the SDK, you can use the following:
async function consolidate(walletId) {
await bitgo.authenticateWithAccessToken({ accessToken });
const unlock = await bitgo.unlock({ otp: '000000', duration: 3600 });
if (!unlock) {
console.log('We did not unlock.');
throw new Error();
}
const walletInstance = await bitgo.coin(coin).wallets().get({ id: walletId });
const consolidationTxs = await walletInstance.sendAccountConsolidations({
walletPassphrase: walletPassphrase,
});
}
Note: SOL consolidations support up to 25 addresses per transaction.
For consolidations, the coin value is always sol or tsol irrespective of whether you are trying to consolidate native SOL or Solana tokens.
Note: The addresses array should only contain the owner addresses and not the Associated Token Account (ATA) addresses of the tokens.
Associated Token Account (ATA)
Solana supports token assets in a unique account called an Associated Token Account (ATA). An ATA is a Token Account whose address derives from the wallet public key, the token mint, and the token program. The respective address that holds the token owns each ATA. Each token asset requires a separate ATA.
When Solana Creates an ATA
First-Time Token Transfer: Solana automatically creates ATAs when transferring tokens to a SOL address for the first time. The sender, or the one initiating the transfer to an address without an ATA, covers the minimum rent fee in SOL (0.00203928 SOL) required to maintain the ATA.
When to Enable ATAs
ATAs must be enabled manually for root addresses. If it’s a HOT wallet, the ATA can be enabled by navigating to the settings page, selecting "Enable Tokens," and then choosing the token and root address. Alternatively, ATA can also be enabled by initiating a token transfer to the root address.
For SMC/custodial wallets, there is no UI option to enable ATAs. Therefore, the only way to enable an ATA on the root address is by sending a small token transfer, which triggers the creation of the ATA.
Impact on SOL Balance
The balance of the address that initiates the ATA creation covers the SOL required for rent.
Post-First-Time Transfer
After you create an ATA for a token on an address, subsequent token transfers directly deposit into the ATA, eliminating any further SOL deductions for rent.
Differentiating Between SOL and ATA Transactions
SOL Transfers: Normal SOL transfers do not involve ATAs and can be differentiated by checking if the transaction contains any "Create Account" instructions. Token Transfers with ATA: Token deposits that involve ATA creation have additional instructions in the transaction explorer (such as "Create Account") and can be identified as such.
Closing ATAs
You can close empty ATAs to recover the rent lamports locked in those accounts. Closing an ATA uses the SPL CloseAccount instruction. BitGo credits recovered rent to the wallet root address (not configurable).
On-chain and explorer behavior
- Closing deallocates the token account; it no longer appears as an active token account with a balance.
- Historical transactions (creation, transfers, and the close itself) remain on the blockchain and explorers still show them when you look up the ATA address.
- Closing does not erase on-chain history.
Prerequisites
| Requirement | Description |
|---|---|
| Zero token balance | Each ATA must have 0 SPL token balance. Transfer or consolidate tokens first if the balance is non-zero. |
| Wallet ownership | Each ATA must belong to the wallet (a BitGo address record for that wallet). |
| Single owner per request | All ATAs in one request must share the same on-chain owner (authority). If the wallet root owns some ATAs and a derived receive address owns others, create separate transaction requests. |
| MPC wallet | Use the Transaction Request API (POST /api/v2/wallet/{walletId}/txrequests). Multisignature wallets use the standard build/sign flow instead. |
Limits
- Up to 27 distinct ATA addresses per transaction (Solana transaction size limit).
- BitGo deduplicates duplicate ATA addresses in
recipients(first occurrence wins). - One transaction and one signing flow for the full batch (when prerequisites are met).
Intent: closeAssociatedTokenAccount
| Field | Required | Description |
|---|---|---|
intentType |
Yes | Must be "closeAssociatedTokenAccount". |
recipients |
Yes | Non-empty array; one entry per ATA to close. |
memo |
Yes | Optional memo string; if omitted, the platform normalizes to "". |
Each recipients[] entry:
| Field | Required | Description |
|---|---|---|
address.address |
Yes | The ATA address to close (not the owner wallet address). |
amount.value |
Yes | Must be exactly "0". |
amount.symbol |
Yes | Coin symbol, for example, "sol" or "tsol". |
tokenName |
No | Must not be included. |
tokenData |
No | Must not be included. |
Recipients identify accounts by ATA address only. The API does not require all ATAs in a batch to use the same token mint; BitGo validates each ATA individually.
Create a close-ATA transaction request
const walletId = '636d0c90b221c80007c69365ca8a3508';
const ataAddress1 = 'ATA_ADDRESS_1';
const ataAddress2 = 'ATA_ADDRESS_2';
bitgo
.post(bitgo.url('/wallet/' + walletId + '/txrequests'))
.send({
apiVersion: 'lite',
intent: {
intentType: 'closeAssociatedTokenAccount',
memo: '',
recipients: [
{
address: { address: ataAddress1 },
amount: { value: '0', symbol: 'tsol' },
},
{
address: { address: ataAddress2 },
amount: { value: '0', symbol: 'tsol' },
},
],
},
})
.then(function (txRequest) {
console.dir(txRequest);
// Sign and send using your standard MPC transaction-request flow.
});
WALLET=636d0c90b221c80007c69365ca8a3508
ATA_1=ATA_ADDRESS_1
ATA_2=ATA_ADDRESS_2
curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d '{
"apiVersion": "lite",
"intent": {
"intentType": "closeAssociatedTokenAccount",
"memo": "",
"recipients": [
{
"address": { "address": "'"$ATA_1"'" },
"amount": { "value": "0", "symbol": "tsol" }
},
{
"address": { "address": "'"$ATA_2"'" },
"amount": { "value": "0", "symbol": "tsol" }
}
]
}
}' \
https://app.bitgo-test.com/api/v2/wallet/$WALLET/txrequests
After you create the transaction request, sign and send it using your standard MPC transaction-request workflow (same as other Solana transaction-request intents).
Custom Program Calls
BitGo supports submitting transactions that invoke custom Solana programs directly from your wallet. This is useful when you need to interact with on-chain programs such as DeFi protocols, staking contracts, governance programs, or any other Solana smart contract beyond simple SOL or token transfers.
When to Use Custom Program Calls
Use custom program calls when you need to:
- Interact with a third-party on-chain program (e.g., a DEX, lending protocol, or staking contract)
- Execute arbitrary instruction data against a deployed Solana program
- Compose multiple program instructions into a single transaction
Instruction Format
Each instruction in a custom program call must follow the Solana instruction structure. Submit instructions as a JSON array, where each element has the following fields:
| Field | Type | Description |
|---|---|---|
programId |
string |
The base58-encoded public key of the on-chain program to invoke |
keys |
array |
Ordered list of account metadata objects required by the instruction (see below) |
data |
string |
The instruction data encoded as a base64 string |
Each entry in the keys array is an account meta object:
| Field | Type | Description |
|---|---|---|
pubkey |
string |
Base58-encoded public key of the account |
isSigner |
boolean |
Whether this account must sign the transaction |
isWritable |
boolean |
Whether this account may be modified by the instruction |
Example JSON Payload
{
"instructions": [
{
"programId": "<PROGRAM_ID>",
"keys": [
{
"pubkey": "<ACCOUNT_PUBKEY_1>",
"isSigner": false,
"isWritable": true
},
{
"pubkey": "<ACCOUNT_PUBKEY_2>",
"isSigner": true,
"isWritable": false
}
],
"data": "<BASE64_INSTRUCTION_DATA>"
}
]
}