Skip to content

Deploy workflow

Deploying a collection isn’t a single request. The backend runs a workflow — validating, uploading metadata, building the transaction, waiting for the client to submit it on-chain, then recording confirmation. This guide covers the full loop.

Every deployment progresses through these steps in order:

StepOwnerWhat happens
ValidateBackendChecks collection is fully configured. Missing media, invalid sale, or matrix violations fail here.
UploadMetadataBackendPublishes token-metadata directory to R2 and computes the baseURI. Idempotent.
UploadCodeBackendGenerative collections only: archives the code directory.
GenerateTransactionBackendAssembles the deploy transaction data — constructor args, implementation address, initializer.
AwaitTransactionClientBackend hands you a transaction; you submit it with your wallet and call deployConfirm.
RecordTransactionBackendWaits for receipt, stores contract address, flips collection to Live.

Overall deployment status is one of:

  • Pending — workflow queued, not yet started
  • InProgress — workflow running (any step)
  • Completed — contract is live
  • Failed — workflow hit a fatal error; error field holds the reason
import { createWalletClient, http } from "viem";
import { baseSepolia } from "viem/chains";
const walletClient = createWalletClient({
account,
chain: baseSepolia,
transport: http(),
});
async function deploy(highlightId: string) {
// 1. Kick off the workflow
await client.collection.deploy({ highlightId });
// 2. Poll until the backend either finishes or needs our signature
while (true) {
const state = (await client.collection.deployStatus({ highlightId })).data!;
if (state.status === "Failed") {
throw new Error(`Deploy failed: ${state.error}`);
}
if (state.status === "Completed") {
return state;
}
if (state.currentStep === "AwaitTransaction" && state.contractConfig) {
// 3. Client's turn — sign and submit.
// contractConfig is a flat { chainId, to, data, value }.
// `to` is null for contract-creation transactions, so pass undefined to viem.
const cfg = state.contractConfig;
const hash = await walletClient.sendTransaction({
to: (cfg.to ?? undefined) as `0x${string}` | undefined,
data: cfg.data as `0x${string}`,
value: BigInt(cfg.value ?? "0"),
gas: 5_000_000n,
});
// 4. Tell the backend about the txHash and keep polling
await client.collection.deployConfirm({ highlightId, txHash: hash });
}
await new Promise((r) => setTimeout(r, 3000));
}
}

deploy is idempotent: calling it twice on the same draft returns the existing deployment. deployConfirm is idempotent against the same txHash.

If your client crashes mid-flow, reconnect and call deployStatus — the workflow picks up where it left off.

Getting the contract address after Completed

Section titled “Getting the contract address after Completed”

After Completed, the deployed contract address is attached to the collection:

const collection = (await client.collection.get({ highlightId })).data!;
const address = collection.contract.address; // on the target chain
FailureWhereWhat to do
Validate failsbackendCheck the error field — usually a missing logoMediaId, bad type+tokenStandard combo, or an invalid sale. Fix and call deploy again.
Metadata publish times outbackendThe workflow retries internally. If it stays Failed, check your media assets’ status.
Transaction reverts on-chainchainRecordTransaction fails. The error field holds the revert reason. Fix inputs and redeploy.
Transaction submitted but receipt times outchainCall deployConfirm again with the same hash after the receipt lands; the backend picks it up.

Generative collections insert UploadCode between UploadMetadata and GenerateTransaction. It archives the codeMediaId to permanent storage. Nothing for the client to do — just expect one more step when polling.

Generative and series collections can deploy without a configured sale. This is useful when the sale is driven by an off-chain or custom mechanic and you want the contract live first. Omit the addSale step before calling deploy.

Editions require a sale before deploy — the platform rejects a naked edition deploy.

For collections whose metadata is meant to be frozen (e.g. a sold-out limited edition), call:

await client.collection.finalizeBaseUri({ highlightId });

This publishes the metadata directory to Arweave and flips the on-chain baseURI. Idempotent — safe to call once your drop is done or from a cron.