Skip to content

Commit 156171f

Browse files
Merge pull request #217 from IntersectMBO/docs/tx-chaining
docs(transactions): add transaction chaining guide
2 parents adc87ae + 1b7eb4e commit 156171f

3 files changed

Lines changed: 181 additions & 1 deletion

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: "Transaction Chaining"
3+
description: "Build multiple dependent transactions without waiting for on-chain confirmation between them"
4+
---
5+
6+
# Transaction Chaining
7+
8+
Build a sequence of dependent transactions up-front, then submit them in order — no waiting for blocks between steps.
9+
10+
## The Problem
11+
12+
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.
13+
14+
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.
15+
16+
## How It Works
17+
18+
After `.build()` completes, the resulting `SignBuilder` exposes a `.chainResult()` method that returns:
19+
20+
```
21+
ChainResult
22+
├── consumed — UTxOs coin selection spent from the available set
23+
├── available — remaining unspent UTxOs + newly created outputs (with pre-computed txHash)
24+
└── txHash — pre-computed hash of this transaction (blake2b-256 of the body)
25+
```
26+
27+
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.
28+
29+
```
30+
tx1.build({ availableUtxos: walletUtxos })
31+
└── tx1.chainResult().available ← remaining UTxOs + tx1's new outputs
32+
33+
34+
tx2.build({ availableUtxos: tx1.chainResult().available })
35+
└── tx2.chainResult().available ← remaining + tx2's new outputs
36+
37+
38+
tx3.build({ availableUtxos: tx2.chainResult().available })
39+
```
40+
41+
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.
42+
43+
## Usage
44+
45+
### Two sequential payments
46+
47+
The simplest case: two payments built back-to-back, submitted in order.
48+
49+
```typescript twoslash
50+
import { Address, Assets, createClient } from "@evolution-sdk/evolution"
51+
52+
const client = createClient({
53+
network: "preprod",
54+
provider: { type: "blockfrost", baseUrl: "https://cardano-preprod.blockfrost.io/api/v0", projectId: process.env.BLOCKFROST_API_KEY! },
55+
wallet: { type: "seed", mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 }
56+
})
57+
58+
const alice = Address.fromBech32("addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63")
59+
const bob = Address.fromBech32("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae")
60+
61+
// Build first transaction — auto-fetches wallet UTxOs
62+
const tx1 = await client
63+
.newTx()
64+
.payToAddress({ address: alice, assets: Assets.fromLovelace(2_000_000n) })
65+
.build()
66+
67+
// Build second transaction immediately — no waiting for tx1 to confirm
68+
const tx2 = await client
69+
.newTx()
70+
.payToAddress({ address: bob, assets: Assets.fromLovelace(2_000_000n) })
71+
.build({ availableUtxos: tx1.chainResult().available })
72+
73+
// Submit in order — tx1 must reach the node before tx2
74+
const signed1 = await tx1.sign()
75+
await signed1.submit()
76+
77+
const signed2 = await tx2.sign()
78+
await signed2.submit()
79+
```
80+
81+
### Spending an output from the previous transaction
82+
83+
Use `tx1.chainResult().available` to find the output you want to spend in tx2.
84+
85+
```typescript twoslash
86+
import { Address, Assets, createClient } from "@evolution-sdk/evolution"
87+
88+
const client = createClient({
89+
network: "preprod",
90+
provider: { type: "blockfrost", baseUrl: "https://cardano-preprod.blockfrost.io/api/v0", projectId: process.env.BLOCKFROST_API_KEY! },
91+
wallet: { type: "seed", mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 }
92+
})
93+
94+
const alice = Address.fromBech32("addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63")
95+
const bob = Address.fromBech32("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae")
96+
97+
// tx1 sends 5 ADA to Alice
98+
const tx1 = await client
99+
.newTx()
100+
.payToAddress({ address: alice, assets: Assets.fromLovelace(5_000_000n) })
101+
.build()
102+
103+
const chain1 = tx1.chainResult()
104+
105+
// Find Alice's output in the chain result — it has a pre-computed txHash
106+
const aliceAddress = Address.toBech32(alice)
107+
const aliceOutput = chain1.available.find(
108+
utxo => Address.toBech32(utxo.address) === aliceAddress
109+
)!
110+
111+
// tx2 immediately spends Alice's output, forwarding to Bob
112+
const tx2 = await client
113+
.newTx()
114+
.collectFrom({ inputs: [aliceOutput] })
115+
.payToAddress({ address: bob, assets: Assets.fromLovelace(4_500_000n) })
116+
.build({ availableUtxos: chain1.available })
117+
118+
// Sign and submit in order
119+
await (await tx1.sign()).submit()
120+
await (await tx2.sign()).submit()
121+
```
122+
123+
### Three-step batch
124+
125+
Chain three builds together up-front, then submit all three.
126+
127+
```typescript twoslash
128+
import { Address, Assets, createClient } from "@evolution-sdk/evolution"
129+
130+
const client = createClient({
131+
network: "preprod",
132+
provider: { type: "blockfrost", baseUrl: "https://cardano-preprod.blockfrost.io/api/v0", projectId: process.env.BLOCKFROST_API_KEY! },
133+
wallet: { type: "seed", mnemonic: process.env.WALLET_MNEMONIC!, accountIndex: 0 }
134+
})
135+
136+
const recipients = [
137+
Address.fromBech32("addr_test1vrm9x2dgvdau8vckj4duc89m638t8djmluqw5pdrFollw8qd9k63"),
138+
Address.fromBech32("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgs68faae"),
139+
Address.fromBech32("addr_test1qpq6xvp5y4fw0wfgxfqmn78qqagkpv4q7qpqyz8s8x3snp5n0d3vllmyqwsx5wktcd8cc3sq835lu7drv2xwl2wywfgsc3z7t3"),
140+
]
141+
142+
const tx1 = await client
143+
.newTx()
144+
.payToAddress({ address: recipients[0], assets: Assets.fromLovelace(5_000_000n) })
145+
.build()
146+
147+
const tx2 = await client
148+
.newTx()
149+
.payToAddress({ address: recipients[1], assets: Assets.fromLovelace(5_000_000n) })
150+
.build({ availableUtxos: tx1.chainResult().available })
151+
152+
const tx3 = await client
153+
.newTx()
154+
.payToAddress({ address: recipients[2], assets: Assets.fromLovelace(5_000_000n) })
155+
.build({ availableUtxos: tx2.chainResult().available })
156+
157+
// All three built — now submit in order
158+
for (const tx of [tx1, tx2, tx3]) {
159+
const signed = await tx.sign()
160+
await signed.submit()
161+
}
162+
```
163+
164+
## Gotchas
165+
166+
- **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.
167+
- **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.
168+
- **`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.
169+
- **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.
170+
171+
## Next Steps
172+
173+
<Cards>
174+
<Card title="Retry-Safe Transactions" href="/docs/transactions/retry-safe" />
175+
<Card title="Multi Output" href="/docs/transactions/multi-output" />
176+
<Card title="Simple Payment" href="/docs/transactions/simple-payment" />
177+
</Cards>

docs/content/docs/transactions/index.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { Card, Cards } from 'fumadocs-ui/components/card'
88

99
<Cards>
1010
<Card title="Your First Transaction" href="/docs/transactions/first-transaction" />
11-
<Card title="Multi Output" href="/docs/transactions/multi-output" />
1211
<Card title="Simple Payment" href="/docs/transactions/simple-payment" />
12+
<Card title="Multi Output" href="/docs/transactions/multi-output" />
13+
<Card title="Transaction Chaining" href="/docs/transactions/chaining" />
14+
<Card title="Retry-Safe Transactions" href="/docs/transactions/retry-safe" />
1315
</Cards>

docs/content/docs/transactions/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"first-transaction",
66
"simple-payment",
77
"multi-output",
8+
"chaining",
89
"retry-safe"
910
]
1011
}

0 commit comments

Comments
 (0)