From 1b7eb4e6b1ac9af05b53bf02ca9b2d9152ad91c0 Mon Sep 17 00:00:00 2001 From: solidsnakedev Date: Fri, 20 Mar 2026 10:48:45 -0300 Subject: [PATCH] docs(transactions): add transaction chaining guide --- docs/content/docs/transactions/chaining.mdx | 177 ++++++++++++++++++++ docs/content/docs/transactions/index.mdx | 4 +- docs/content/docs/transactions/meta.json | 1 + 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 docs/content/docs/transactions/chaining.mdx diff --git a/docs/content/docs/transactions/chaining.mdx b/docs/content/docs/transactions/chaining.mdx new file mode 100644 index 00000000..37ec37a9 --- /dev/null +++ b/docs/content/docs/transactions/chaining.mdx @@ -0,0 +1,177 @@ +--- +title: "Transaction Chaining" +description: "Build multiple dependent transactions without waiting for on-chain confirmation between them" +--- + +# Transaction Chaining + +Build a sequence of dependent transactions up-front, then submit them in order — no waiting for blocks between steps. + +## The Problem + +Each Cardano transaction spends UTxOs and creates new ones. Normally you can't build the second transaction until the first is confirmed on-chain, because the new UTxOs it creates don't exist yet from the provider's perspective. + +This 10–30 second wait between steps is painful for multi-step workflows: batch payouts, batch minting, or any sequence of operations that logically belong together. + +## How It Works + +After `.build()` completes, the resulting `SignBuilder` exposes a `.chainResult()` method that returns: + +``` +ChainResult +├── consumed — UTxOs coin selection spent from the available set +├── available — remaining unspent UTxOs + newly created outputs (with pre-computed txHash) +└── txHash — pre-computed hash of this transaction (blake2b-256 of the body) +``` + +The `available` array is the key. It contains the UTxOs your wallet still holds *plus* any outputs this transaction creates — already tagged with the correct `txHash` so they're valid as inputs to the next build. Pass it as `availableUtxos` in the next `.build()` call. + +``` +tx1.build({ availableUtxos: walletUtxos }) + └── tx1.chainResult().available ← remaining UTxOs + tx1's new outputs + │ + ▼ +tx2.build({ availableUtxos: tx1.chainResult().available }) + └── tx2.chainResult().available ← remaining + tx2's new outputs + │ + ▼ +tx3.build({ availableUtxos: tx2.chainResult().available }) +``` + +Transactions must be **submitted in order**. Each transaction spends outputs created by the previous one, so the node will reject tx2 if tx1 hasn't been submitted yet. + +## Usage + +### Two sequential payments + +The simplest case: two payments built back-to-back, submitted in order. + +```typescript twoslash +import { Address, Assets, createClient } from "@evolution-sdk/evolution" + +const client = createClient({ + network: "preprod", + provider: { type: "blockfrost", baseUrl: "https://cardano-preprod.blockfrost.io/api/v0", projectId: process.env.BLOCKFROST_API_KEY! }, + wallet: { type: "seed", mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 } +}) + +const alice = Address.fromBech32("addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63") +const bob = Address.fromBech32("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae") + +// Build first transaction — auto-fetches wallet UTxOs +const tx1 = await client + .newTx() + .payToAddress({ address: alice, assets: Assets.fromLovelace(2_000_000n) }) + .build() + +// Build second transaction immediately — no waiting for tx1 to confirm +const tx2 = await client + .newTx() + .payToAddress({ address: bob, assets: Assets.fromLovelace(2_000_000n) }) + .build({ availableUtxos: tx1.chainResult().available }) + +// Submit in order — tx1 must reach the node before tx2 +const signed1 = await tx1.sign() +await signed1.submit() + +const signed2 = await tx2.sign() +await signed2.submit() +``` + +### Spending an output from the previous transaction + +Use `tx1.chainResult().available` to find the output you want to spend in tx2. + +```typescript twoslash +import { Address, Assets, createClient } from "@evolution-sdk/evolution" + +const client = createClient({ + network: "preprod", + provider: { type: "blockfrost", baseUrl: "https://cardano-preprod.blockfrost.io/api/v0", projectId: process.env.BLOCKFROST_API_KEY! }, + wallet: { type: "seed", mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 } +}) + +const alice = Address.fromBech32("addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63") +const bob = Address.fromBech32("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae") + +// tx1 sends 5 ADA to Alice +const tx1 = await client + .newTx() + .payToAddress({ address: alice, assets: Assets.fromLovelace(5_000_000n) }) + .build() + +const chain1 = tx1.chainResult() + +// Find Alice's output in the chain result — it has a pre-computed txHash +const aliceAddress = Address.toBech32(alice) +const aliceOutput = chain1.available.find( + utxo => Address.toBech32(utxo.address) === aliceAddress +)! + +// tx2 immediately spends Alice's output, forwarding to Bob +const tx2 = await client + .newTx() + .collectFrom({ inputs: [aliceOutput] }) + .payToAddress({ address: bob, assets: Assets.fromLovelace(4_500_000n) }) + .build({ availableUtxos: chain1.available }) + +// Sign and submit in order +await (await tx1.sign()).submit() +await (await tx2.sign()).submit() +``` + +### Three-step batch + +Chain three builds together up-front, then submit all three. + +```typescript twoslash +import { Address, Assets, createClient } from "@evolution-sdk/evolution" + +const client = createClient({ + network: "preprod", + provider: { type: "blockfrost", baseUrl: "https://cardano-preprod.blockfrost.io/api/v0", projectId: process.env.BLOCKFROST_API_KEY! }, + wallet: { type: "seed", mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 } +}) + +const recipients = [ + Address.fromBech32("addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63"), + Address.fromBech32("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae"), + Address.fromBech32("addr_test1qpq6xvp5y4fw0wfgxfqmn78qqagkpv4q7qpqyz8s8x3snp5n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgsc3z7t3"), +] + +const tx1 = await client + .newTx() + .payToAddress({ address: recipients[0], assets: Assets.fromLovelace(5_000_000n) }) + .build() + +const tx2 = await client + .newTx() + .payToAddress({ address: recipients[1], assets: Assets.fromLovelace(5_000_000n) }) + .build({ availableUtxos: tx1.chainResult().available }) + +const tx3 = await client + .newTx() + .payToAddress({ address: recipients[2], assets: Assets.fromLovelace(5_000_000n) }) + .build({ availableUtxos: tx2.chainResult().available }) + +// All three built — now submit in order +for (const tx of [tx1, tx2, tx3]) { + const signed = await tx.sign() + await signed.submit() +} +``` + +## Gotchas + +- **Submit in order.** Each transaction in the chain depends on outputs from the previous one. Submitting tx2 before tx1 means the node sees inputs that don't exist yet and rejects it. +- **Not retry-safe by default.** The chain is built from a single snapshot of chain state. If tx1 fails after you've built tx2 (e.g. a network error mid-submit), you cannot safely retry just tx2 — you need to rebuild the whole chain. See [Retry-Safe Transactions](/docs/transactions/retry-safe) for how to structure resilient pipelines. +- **`chainResult()` is memoized.** It's computed once from the build result and cached. Calling it multiple times is free but you always get the same snapshot. +- **The outputs in `available` are not yet on-chain.** They exist only as pre-computed UTxOs. Don't pass them to any provider call (e.g. `getUtxos`) — they won't be there yet. + +## Next Steps + + + + + + diff --git a/docs/content/docs/transactions/index.mdx b/docs/content/docs/transactions/index.mdx index 0c5bee8b..347816d9 100644 --- a/docs/content/docs/transactions/index.mdx +++ b/docs/content/docs/transactions/index.mdx @@ -8,6 +8,8 @@ import { Card, Cards } from 'fumadocs-ui/components/card' - + + + diff --git a/docs/content/docs/transactions/meta.json b/docs/content/docs/transactions/meta.json index 39a88f12..fa6385d4 100644 --- a/docs/content/docs/transactions/meta.json +++ b/docs/content/docs/transactions/meta.json @@ -5,6 +5,7 @@ "first-transaction", "simple-payment", "multi-output", + "chaining", "retry-safe" ] }