Why Doesn't Smart Contract Deployment End with One Transaction?
You want to deploy a vault that holds tokens, but its constructor requires the token contract's address. If that token contract does not exist yet, what should you submit first? You need to create the token, read its resulting address, and pass that address to the vault. If the second deployment fails, it does not erase the first contract either.
Creating one contract on a chain and connecting several contracts into a configuration a service can use are different scopes of work. The latter needs a plan: which code to deploy, in what order, and which output from an earlier step becomes the next input.
BXB has a structure for arranging contract templates into steps and recording inputs and results for each deployment run.
Let us build a two-step plan using the included BasicERC20 and VaultToken contracts.
This is an illustrative example connecting constructors and step execution paths confirmed in the source, not a record obtained by deploying them to a real network.
1. Create token A, then create vault B to hold A
In this example, A and B are the addresses of two new contracts, not people's wallets. We will call the deployment wallet D and the target EVM network N. Both steps run on the same network, and we assume D holds the native currency needed to pay gas for each deployment.
The first contract is BasicERC20, a basic token. Its constructor takes a token name, symbol, and initial owner address.
Set the name to Example Asset, the symbol to AST, and the initial owner to D's address.
A successful deployment produces the new token contract's address, A. We do not yet call mint to issue tokens.
The second contract, VaultToken, takes the ERC-20 token it will accept for deposits as a constructor input.
That input is named underlyingAsset. Set it to A and choose the vault share token's name and symbol.
The vault is then constructed to use A's tokens for subsequent deposits and withdrawals.
ERC-4626's definition of asset likewise identifies this address as the underlying token managed by the vault.
This article follows that address connection; it does not verify yield generation or the conversion ratio for deposits.
VaultToken's constructor stores the supplied token address and also queries the token's decimal places.
A is therefore an actual input used to construct B, not a description to add to a screen later.
Even when contract names match, supplying an address from a different network or deployment run can produce a different configuration.
Our target is to have A and B on N, with B's asset() returning A.
We neither issue tokens nor make deposits during deployment, so the token's total supply and the vault shares' total supply both start at 0.
Completing deployment does not put assets into the vault or give customers shares. Deployment costs are paid separately from D's native currency.
2. A template and this deployment run are different records
Imagine deploying the same two contracts to a development environment and a separate production environment. The code and order may be the same, while the network, deployment wallet, actual addresses, and outcomes differ. Overwriting all this information in a single template would make it difficult to tell which run produced an address.
BXB separates this information into templates, plans, runs, and steps. The result in the following table is output recorded on an execution step, not a separate kind of contract.
| Record | Role in this example |
|---|---|
| Template | Points to the contract resources for BasicERC20 or VaultToken |
| Deployment plan | Places the token in step 1 and the vault in step 2 |
| Deployment run | Carries out this plan on network N on this occasion |
| Execution step | Holds its constructor inputs, deployment wallet, and progress status |
| Step result | Retains the deployed address or error for the next decision |
The contract resource links to an ABI and deployment bytecode. The ABI describes the types of values the constructor accepts; the bytecode is the deployment program the chain executes. Registering a template makes these materials available for selection. It does not mean that an address already exists on-chain.
The plan holds the step order, templates to use, and constructor inputs. Creating a run produces step records based on that plan and associates the run with a target network and a name. You then check the wallet and constructor values to use before requesting each step's deployment. This distinguishes reusing the same plan from reusing the results of a run that has already succeeded.
Another distinction is between deployment wallet D and the constructor's initialOwner.
We use the same address for both here, but the former signs the transaction and pays its cost, while the latter receives authority over the token contract.
This BXB EVM deployment path checks that the wallet has the ADMIN role. That check alone does not determine whether the owner address supplied to the constructor is appropriate for the business.
3. Represent an unknown address as an earlier step's result
When we write the plan, A's actual address does not exist yet. Entering an arbitrary address and relying on someone to remember to replace it just before deployment invites connection mistakes. Instead, we can leave a reference meaning "the address produced by step 1 of this run."
BXB's constructor input resolver supports the notation ${step:N}.
N is the step order within the same deployment run. The vault inputs can therefore be written as follows:
{
"underlyingAsset": "${step:1}",
"name": "Example Vault Share",
"symbol": "vAST"
}
This JSON illustrates the vault's constructor inputs, not the entire deployment request.
When executing the second step, the management service finds the first step's record in the same run
and replaces ${step:1} with its deployed address. The constructor sent to the chain receives A's actual address in place of that notation.
If the step cannot be found or its result address is empty, the reference cannot be resolved.
This notation does not automatically infer dependencies between contracts. The user must put the steps in the right order and specify which step B should reference. Looking up an address within the same run does not establish that A has the intended business meaning. The token template and settings selected for the first step must also be checked.
A constructor runs once, when the contract is created. Solidity's explanation of contract creation describes constructor arguments as ABI-encoded values appended to the deployment code. The time to change those inputs is therefore before signing the second deployment. Editing a value on the plan screen later does not change the settings of an already created B.
4. From checking earlier steps to recording the address
Pressing the second step's deploy button does not immediately begin address substitution without checks.
The management service checks whether the current step has already completed and whether all earlier steps are COMPLETED.
In this example, step 1 must complete before the request for step 2 proceeds.
The deployment engine also checks whether the requested step is next in sequence.
Once the prerequisites pass, constructor references are resolved to actual values, and a request is made to deploy that individual step with the selected wallet.
The engine records the constructor inputs and wallet on the step before calling the contract deployment path.
The EVM deployment service encodes the inputs using the registered ABI and appends them to the deployment bytecode to create a transaction.
Unlike an ordinary function call, a transaction that creates a new contract has no to address for an existing recipient contract.
Ethereum's explanation of transaction types also describes this distinction.
In the normal path, the engine submits the signed transaction and waits for its transaction receipt. The deployment service creates a contract registration record using the receipt's contract address and also stores the address in the step result. The value used by the next step is the deployed contract address, not the transaction hash. The hash answers "Which transaction should we query?" The address answers "Which contract should we connect?"
Step records and transaction records also serve different purposes. A step retains its resolved constructor inputs, wallet used, address, and error; transaction records provide the basis for tracing submission and receipt queries. When an operator later checks B's configuration, they need to examine the inputs and network that produced B, as well as its result address.
5. What do the two completed deployments give us?
After each request finishes normally and the details are read again, the two steps have the following results. These are the illustrative example's per-step results, not a table that treats one status indicator for the entire run as proof of on-chain success.
| Point | Token step | Vault step |
|---|---|---|
| Run prepared | PENDING, no address | PENDING, no address |
| Token deployed | COMPLETED, A | PENDING, no address |
| Vault deployed | COMPLETED, A | COMPLETED, B |
The final result the business needs is more specific than "two completed items."
Token A and vault B must exist on N, and the underlying token address used by B must be A.
We can inspect the constructor inputs retained on the step and read the deployed B's asset() to check that connection.
This query is a way to verify the example's result, not a claim that the current step executor automatically performs it after every deployment.
The information in a receipt must also be distinguished. Ethereum's JSON-RPC receipt documentation provides the created contract address and execution status as separate fields. Receiving a transaction hash alone does not establish successful creation, and finding a receipt is also separate from meeting an institution's required level of block confirmation. The step status is an operational record used to continue these checks.
This example submits no issuance, deposit, or withdrawal transactions beyond the two deployments. Even after successful completion, no customer's token balance or vault shares increase. Both contracts' total supplies remain 0, and D pays the actual transaction costs of both deployments.
We can now decide which business APIs should connect to A's and B's ABIs and addresses. The connection between an ABI and its execution target described in What It Takes to Operate a Contract as a REST API begins here. Deploying a contract, registering an API, and using that API to execute a customer's deposit each have their own results.
6. A stopped second step does not erase the first
Suppose A was created successfully, but B's inputs accidentally reference ${step:9}.
There is no step 9 in this run, so address resolution stops.
B's deployment transaction has not been submitted, and the A created earlier remains.
The items to check are step 2's reference number and stored inputs. There is no reason to mark B as created or to create A again.
We should not assume that even errors that end before submission are always recorded with the same step status.
A connection lost while waiting for a response after submitting B's transaction is a different case. Even if the screen shows a failure or the server has not received a receipt, B may already have been created on-chain. First query the result using the existing transaction hash, then compare the contract address and constructor inputs with this run's records. If the result is still unknown, treat it as "the result needs checking," not "it was not deployed."
Creating a new deployment transaction without checking the result can create an additional, separate contract with the same name and code. You then also need to decide which B the service should use: the earlier one or the new one. The distinction between querying a result and creating a new transaction in Can You Send a Transfer Again After a Timeout? applies to deployment too. Pressing a step's button again is not equivalent to querying its previous transaction or safely resuming the same run.
BXB's step handling includes paths that record progress, completion, failure, and error details. This does not establish a guarantee of automatic recovery from every failure or safe resumption from the point of interruption. Operators need to distinguish an error before submission, an unknown result after submission, and failed on-chain execution. They must then check that the current addresses and step records agree before deciding whether to correct inputs or follow a separate recovery procedure.
Costs also depend on the case. If the process stops before submitting B's transaction, as with the invalid reference above, there is no on-chain fee for deploying B. If a transaction has already been processed, its receipt must be checked for the gas actually used. In either case, marking the plan as failed does not refund the cost of the previously successful A. We cannot assume an automatic rollback that undoes A and B, created in separate transactions, as though they were part of one database transaction.
7. A deployment plan provides a basis for deciding what to do next
If a suitable token already exists on the same network, a configuration that does not deploy it again is also possible. Reusing an existing address, however, requires checking its network, contract code and settings, and suitability for the role required by this plan. A "deployment skipped" indicator does not complete reuse on its own; the rules for passing that address to the next step must also be connected.
BXB's deployment engine has a branch that skips deployment of a shared component when given an existing address and records that address. This article does not extend that branch into a reuse procedure verified across the entire console. It stays within the step-by-step path that creates two new contracts. Automatically deploying every step at once is also distinct from the individual step execution confirmed here. What this example gives the reader is a way to check an earlier step's result and connect it to the next input, not a promise that one click will complete everything automatically.
Multiple contracts do not always require multiple transactions. A separate factory contract can be designed to create several contracts internally. The path examined here submits a deployment transaction for each contract in sequence, so the atomicity of one transaction cannot be applied to the whole plan. The deployment method and the scope of failure handling need to be chosen for the configuration.
This plan produces A and B on the same network, with B constructed to use A. Templates supply what to create, the plan sets the order, and execution steps retain what actually happened in that sequence. These distinctions let us identify what has already been created and decide what to do next when the second deployment stops.