Why Does Deploying a Solana Program Take Multiple Transactions?
There is one program file to deploy. Yet starting the deployment produces dozens or hundreds of transactions. If the connection drops before the last transaction is sent, what happens to the parts already uploaded? The first decision is whether to send the file again or send only the remaining chunks.
In smart contract deployment plans, we passed the address of one newly created contract into the next contract as an input. On Solana, uploading a single program can itself take multiple transactions. Writing the program file to the chain and deploying it as executable code are separate stages.
Let's follow BXB's Solana deployment code through this process. The scope here is the loader-v3 path for upgradeable programs. The numbers below are an example to explain chunked uploads and result verification, not measurements from an actual deployment.
1. Preparing 102 transactions for one file
Suppose we have a 90,000-byte program file that has been built successfully.
It is a .so file containing code to run on Solana. We will call the address of this new program P.
Assume deployment wallet D has enough SOL for transaction fees and account creation, along with the required authority.
Depositing customer assets into the program or initializing business data is outside this example.
The whole file cannot fit in one transaction. Alongside the file content, a transaction carries signatures, the addresses of accounts it uses, instruction data, and a blockhash identifying a recent block. The Solana transaction documentation describes a 1,232-byte limit for the existing transaction formats. This article follows the BXB deployment path built around that limit. Limits for newer transaction formats should be distinguished from this implementation's upload chunk size.
The BXB configuration we reviewed defaults to one Write transaction per 900 bytes of file content. The 900-byte size is an upload setting chosen to leave room for the transaction's other fields, rather than a Solana program file requirement. Our example therefore splits 90,000 bytes into 100 chunks.
For an initial deployment in which each transaction is submitted once and succeeds, the transactions are:
| Stage | Work | Count |
|---|---|---|
| Setup | Create and initialize a temporary buffer | 1 |
| Write | Write 100 chunks of 900 bytes each | 100 |
| Deploy | Create Program and deploy the code | 1 |
The total is 102. Resubmissions or cleanup transactions after a failure increase that count. This is neither a measurement of deployment time or cost nor a fixed transaction count for every program. Changing the file size or chunk size changes the number of Write transactions.
2. A buffer holds the code before deployment
Here, a buffer is a temporary account on the Solana chain, rather than server memory. The file's chunks are written into this account and used for the final deployment once they are all present.
Three types of loader-v3 accounts explain how this works.
| Account | Role |
|---|---|
| Buffer | Temporarily holds the program bytes to deploy. |
| Program | Occupies program address P and points to ProgramData. |
| ProgramData | Holds the deployed code and upgrade authority information. |
The Solana program deployment documentation describes this structure and the deployment and upgrade instructions. P in this article is the Program address used to invoke the program. It differs from the temporary buffer's address. Accounts holding ordinary business data also have a separate role from these three.
When deployment starts, BXB generates a keypair for a new buffer account and
puts account creation and InitializeBuffer in the same transaction.
Combining creation and authority assignment removes the intermediate step of leaving a newly created account to be initialized in another transaction.
Deployment wallet D is assigned the authority to write to the buffer.
The buffer key used to create the account and the authority key used to modify its contents have different roles. The account creation transaction needs signatures from both D and the buffer, while Write transactions are signed by D as the designated buffer authority. Knowing the buffer's address alone does not let someone change the code inside it.
3. Each chunk has a position, regardless of arrival order
A Write instruction specifies the position and the bytes to write.
Its offset is a byte position measured from the start of the program file, beginning at zero.
Write(offset=0, bytes=file bytes 0–899)
Write(offset=900, bytes=file bytes 900–1799)
...
Write(offset=89100, bytes=file bytes 89100–89999)
Because each chunk has its own position, transactions writing different ranges do not have to execute in submission order for the file to become complete. However, they modify the same buffer, so submitting them concurrently does not mean they all execute in parallel on-chain.
BXB's default configuration submits Write transactions in batches of four, with a pause before the next batch. Each batch obtains a fresh blockhash to construct its transactions. A batch regulates submission; it does not combine four transactions into one atomic transaction. Only some of the writes may take effect.
The buffer account's actual data begins with 37 bytes of metadata holding its state and authority.
A Write with offset=0 refers to the beginning of the program bytes, after that metadata.
BXB also skips this prefix when it reads the account and compares its contents with the original file.
Confusing positions within the file with positions within the account data would compare a chunk against the wrong range.
Batch size and spacing control the load on the RPC interface used to send requests to a chain node. Controlling submission speed alone, however, does not establish that every chunk has been written. The next step is to read what is actually there.
4. Checking the bytes that actually reached the buffer
Suppose we have sent 100 chunks, but reading the buffer shows that the 900 bytes corresponding to file positions 36,000–36,899 differ from the original. Some submission responses may not have arrived, or a transaction may not yet have taken effect. What we know in this example is that this range does not yet match the original file.
In each write round, BXB submits the target chunks in batches, then reads the buffer
and compares its contents with the original file, excluding the metadata.
If all the bytes match within the allotted time, the writing stage is complete.
If differences remain, it selects only the mismatched chunks and sends them again in the next write round.
In this example, the next target is a transaction writing the same 900 bytes at offset=36000.
With this approach, losing a transaction response does not require another write if the bytes are already present. Conversely, a submission response does not mark writing as complete if the buffer's contents still differ. The key question is whether the intended file is there, rather than how many calls to the send function succeeded.
Writing the same bytes at the same position again does not append more data to the file. This makes the retry different from a transfer retry that sends additional tokens. Resubmitted transactions can still incur fees. We also discussed the distinction between a transaction identifier and a business outcome in transaction timeouts and retries.
This loop does not continue indefinitely. The default configuration we reviewed allows up to three write rounds. If the full contents still do not match, deployment fails and cleanup is attempted. These are retries within the deployment call that is currently running. They do not resume a deployment from a saved checkpoint after the server has stopped. The current path creates a new buffer for each new deployment call.
5. Deployment or upgrade follows a complete upload
Even with all 90,000 bytes in the buffer, we do not yet have a program that can be invoked at P. A final transaction must deploy the temporarily collected code.
For the first deployment, BXB combines Program account creation and DeployWithMaxDataLen in one transaction.
Wallet D and the key used to create the Program account sign this transaction.
The loader checks the code in the buffer, places it in ProgramData, and makes P point to that ProgramData.
A successful result gives us a program to invoke at P in place of the temporary collection of file chunks.
The BXB implementation we reviewed requests code space equal to twice the program file's size on initial deployment. That means 180,000 bytes of code space in this example, with account metadata separate. This leaves room for the code to grow in a later version. The account size also requires a corresponding amount of SOL; the extra space does not accommodate upgrades of any size.
To update an existing P, a new file is collected in a new buffer before an Upgrade instruction is sent.
This changes the code in ProgramData while preserving P as the address used to invoke it.
In this BXB path, wallet D is the fee payer, buffer authority, and upgrade signer.
The existing ProgramData's upgrade authority must also match D.
Being able to pay fees does not grant a wallet permission to change another program.
Initial deployment and upgrade also require different checks on the result.
For an initial deployment, BXB checks that the expected account at P exists and is owned by loader-v3.
For an upgrade, P already exists, so BXB queries the status using this Upgrade transaction's signature identifier
and checks that it has reached confirmed or finalized without an error.
The existence of the account alone does not establish that the new code was applied.
These checks determine the outcome of the deployment stage. They do not verify that the deployed bytes match a build reproduced from public source, or replace tests that invoke the deployed program's business functions.
6. After a failure, inspect the remaining buffer and costs
Now suppose the process fails before all chunks have been written and before the final deployment transaction is sent. Because this example creates P for the first time, there is no executable program yet. But the previously created buffer and some of its bytes may remain on-chain.
The SOL deposited when creating the buffer is distinct from the fees for uploading the file. The buffer was funded with the minimum balance needed to maintain the account. Closing the account successfully can return its remaining balance to a designated recipient. It does not return fees for transactions that have already been processed.
If an exception occurs during deployment, BXB automatically attempts to send a transaction that closes the buffer using D's authority. But RPC conditions bad enough to disrupt deployment may also prevent the cleanup request from succeeding. This automatic cleanup path attempts submission, so the call ending does not mean the SOL has been recovered.
A separate path can inspect and close a leftover buffer. It checks that the account is a loader-v3 Buffer and identifies its current authority, then sends a close transaction when the specified wallet matches that authority. It subsequently checks whether the buffer account has disappeared. In this BXB path, the recovered balance goes to the authority's wallet. The account address, network, and authority together identify the correct cleanup target.
After a failed deployment, the recoverable amount is the balance remaining in the temporary account. That must be distinguished from the balances needed for Program and ProgramData accounts that will remain, and from fees for processed transactions. It is not a full refund of deployment costs.
Losing the response after sending the final deployment transaction is another situation. In that case, P and the transaction's outcome must be checked first. A failure response at the client is not evidence that no program exists on-chain, and buffer cleanup does not roll back a successful deployment.
7. Completing deployment means more than completing the upload
Return to the successful example at the start. We wrote 90,000 bytes in 100 Write transactions, checked the buffer's contents, and completed the final deployment. The result is address P and the code to execute at that address. No customer asset transfer or business data creation has been requested, so neither is part of this deployment's result.
Operational records also need to distinguish these stages. BXB links requests, responses, and failure records to the main stages of buffer preparation, initial deployment, or upgrade. These records are not a persistent progress ledger for every Write transaction. Which stage stopped and which bytes have been written are different questions.
A request to deploy one program file includes chunk sizes, write positions, signing authority, confirmation of the final result, and temporary account cleanup. The deployment flow goes beyond assembling the file's pieces: it checks whether executable code is ready and what remains after a failure.