# Cardano Client Lib — complete documentation Latest preview 0.8.0-pre7 (Java 17+) · Latest stable 0.7.3 (Java 11+) · Conway era · generated 2026-11-02 Source: https://cardano-client.dev --- # Using Cardano Client Lib with AI Source: https://cardano-client.dev/ai/ Summary: Machine-readable artifacts, agent rules, and the fastest way to get an AI coding agent writing correct CCL code. Coding agents write a lot of Cardano off-chain Java now. This site publishes the artifacts an agent needs so it generates code against the **current** API rather than a stale snapshot from its training data. ## Point your agent here first | Artifact | URL | What it is | |---|---|---| | Curated index | [`/llms.txt`](/llms.txt) | Every page, grouped, with the "which API do I use" table at the top. Follows the [llmstxt.org](https://llmstxt.org) convention. | | Full corpus | [`/llms-full.txt`](/llms-full.txt) | The entire documentation site as one plain-text file, for bulk ingestion. | | Starter pack | [`/ai/starter-pack/`](/ai/starter-pack/) · [raw](/ai/starter-pack.md) | The rules that keep an agent from making the common mistakes. **Read this before generating code.** | | Module catalog | [`/ai/catalog.json`](/ai/catalog.json) | Machine-readable: every published module, its artifact id, its canonical entry-point class, and its recommendation tier. Generated from `settings.gradle` and `gradle.properties`, so it cannot drift from the build. | The catalog and both `.txt` files are regenerated on every build. If the library gains a module, these artifacts describe it the same day. ## Why this exists Asked to "send 5 ADA on preview with cardano-client-lib", agents reliably produce code that compiles and is wrong. The recurring failures are specific: - Pairing `Networks.preview()` with `Constants.BLOCKFROST_MAINNET_URL` — the network and the backend URL are configured separately, and nothing stops you mismatching them. - Calling `complete()` and then reporting success, when `complete()` only submits. It does not wait for the transaction to be included in a block. - Reaching for the `function` module's `TxBuilder` composition primitives instead of QuickTx. It works, but it is the low-level layer and mistakes there are silent. - Generating `WatchableQuickTxBuilder`, a class that **has never existed in a released version**. TxFlow is the multi-step API. - Emitting Babbage-era patterns for Conway-only operations (DRep registration, votes, treasury withdrawals). The starter pack addresses each of these directly. ## Rules files for specific tools Drop the starter pack into whichever file your tool reads, so the rules load automatically instead of relying on the agent to fetch a URL: ```bash # Claude Code curl -o CLAUDE.md https://cardano-client.dev/ai/starter-pack.md # Cursor mkdir -p .cursor/rules && \ curl -o .cursor/rules/cardano-client-lib.mdc https://cardano-client.dev/ai/starter-pack.md # GitHub Copilot mkdir -p .github && \ curl -o .github/copilot-instructions.md https://cardano-client.dev/ai/starter-pack.md ``` For an agent that can fetch URLs at run time, pointing it at `/llms.txt` is enough — the index links onward to everything else. ## Verifying what an agent produces Generated transaction code should be run before you trust it. The cheapest loop is [Yaci DevKit](https://devkit.yaci.xyz/#get-started), a local Cardano devnet with pre-funded accounts and instant blocks — no faucet, no API key, no waiting on a public testnet. Point the backend at `http://localhost:8080/api/v1/` and the same code you would run on preview works locally, so an agent can execute what it wrote and read a real transaction hash back. ## Related - [AI Starter Pack](/ai/starter-pack/) — the rules themselves - [Key APIs at a glance](/start-here/key-apis/) — a quick tour of the main APIs - [Modules & artifacts](/reference/modules/) — full dependency reference --- # AI Starter Pack Source: https://cardano-client.dev/ai/starter-pack/ Summary: Everything an AI agent needs to write correct cardano-client-lib code on the first try. Ingest this before generating any CCL code. > **Read this whole document before generating cardano-client-lib code.** It is optimized for > machine ingestion, not for learning. Humans should start at > [Your first transaction](/learn/simple-transfer/) instead. Target version: the [latest preview](/start-here/installation/#versions) · Ledger era: **Conway** · Java **17+** · Group `com.bloxbean.cardano` --- ## 1. Choose the API before writing a line | Task | Use | Module | |---|---|---| | One transaction — send, mint, delegate, register, vote, call a script | `QuickTxBuilder` + `Tx` | `cardano-client-quicktx` | | A plan expressed as configuration rather than code | `TxPlan` (YAML/JSON) | `cardano-client-quicktx` | | Several dependent transactions, in order, with rollback handling | `FlowEngine` | `cardano-client-txflow` | | Continuous submission with exactly-once and durable restart | `TxFlowStream` | `cardano-client-txflow` | | A proof an Aiken validator can verify on-chain | `MpfTrie` | `cardano-client-merkle-patricia-forestry` | **QuickTx is the default.** Only move up this table when the task genuinely needs the extra machinery. ### Never generate these | Do not use | Why | Use instead | |---|---|---| | `WatchableQuickTxBuilder` | **Does not exist.** Never shipped in any release. If your training data suggests it, that memory is wrong. | `FlowEngine` (TxFlow) | | `com.bloxbean.cardano.client.function.*` (`TxBuilder`, `TxOutputBuilder`, `InputBuilders`, `BalanceTxBuilders`) | The internal implementation layer beneath QuickTx. It works, but mistakes are silent and it is far more verbose. | `QuickTx` | | `ScriptTx`, on 0.8.0 previews | **Deprecated in the 0.8.0 previews**, where every operation is also on `Tx`. Not deprecated in stable 0.7.x, where it is the right API for scripts. | `Tx` on previews | | `FlowExecutor` | Compatibility API. Combines definition and execution concerns; not the canonical durable path. | `FlowEngine` | | `JellyfishMerkleTree` for on-chain proofs | Not Aiken-compatible. Off-chain authenticated state only. | `MpfTrie` | --- ## 2. The five mistakes agents actually make ### 2.1 Mismatching the network and the backend URL The `Network` (used for address derivation) and the backend URL (used for queries) are configured **separately**. Nothing validates that they agree, so a mismatch produces addresses on one network and queries against another. ```java // WRONG — preview addresses, mainnet backend. Compiles, fails at run time. Account account = Account.createFromMnemonic(Networks.preview(), mnemonic); BackendService backend = new BFBackendService(Constants.BLOCKFROST_MAINNET_URL, projectId); // RIGHT — both preview. Account account = Account.createFromMnemonic(Networks.preview(), mnemonic); BackendService backend = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, projectId); ``` Matched pairs — `Constants` here is `com.bloxbean.cardano.client.backend.blockfrost.common.Constants`: | Network | `Networks` factory | Blockfrost URL constant | |---|---|---| | Mainnet | `Networks.mainnet()` | `BLOCKFROST_MAINNET_URL` | | Preprod | `Networks.preprod()` | `BLOCKFROST_PREPROD_URL` | | Preview | `Networks.preview()` | `BLOCKFROST_PREVIEW_URL` | | Local devnet (Yaci DevKit) | `Networks.testnet()` | `http://localhost:8080/api/v1/` | All test networks use network id 0, so `Networks.testnet()`, `Networks.preview()` and `Networks.preprod()` give the same addresses. Use the constant that names your network when you know it. ### 2.2 Treating `complete()` as confirmation ```java TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .complete(); // submits ONLY — does not wait ``` `complete()` returns as soon as the node accepts the transaction for propagation. The transaction may still fail to be included, or be rolled back. | Method | Returns | Waits for a block? | |---|---|---| | `complete()` | `TxResult` | No | | `completeAndWait()` | `TxResult` | Yes | | `completeAndWaitAsync()` | `CompletableFuture` | Yes, off-thread | Use `completeAndWait()` unless you have a specific reason not to, and check the status: ```java TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(); if (result.getTxStatus() == TxStatus.CONFIRMED) { System.out.println("Confirmed: " + result.getTxHash()); } else { System.err.println("Not confirmed: " + result.getTxStatus()); } ``` `TxStatus` values: `SUBMITTED`, `PENDING`, `CONFIRMED`, `FAILED`, `TIMEOUT`. :::note[`TxResult` extends `Result`] Assigning to `Result` still compiles — you will see that in older examples — but it discards `getTxStatus()`, which is the only way to tell a submitted transaction from a confirmed one. Declare the variable as `TxResult`. `isSuccessful()` and `getResponse()` are inherited from `Result` and remain available. ::: ### 2.3 Reaching for the `function` module If you find yourself writing `TxBuilder`, `TxOutputBuilder`, `InputBuilders.createFromSender(...)` or `BalanceTxBuilders.balanceTx(...)`, stop. QuickTx expresses the same thing in a few lines: ```java // The whole of a payment, including balancing and fee calculation. Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(senderAddress); ``` The `function` module is documented under [Composable functions](/reference/composable-functions/) for the rare cases QuickTx cannot cover. Do not generate it by default. ### 2.4 Assuming pre-Conway APIs This library targets the **Conway** era. Governance is first-class on `Tx`: ```java import com.bloxbean.cardano.client.governance.GovId; // A bech32 DRep id (CIP-105) becomes a DRep via GovId.toDrep. DRep drep = GovId.toDrep(account.drepId()); Tx tx = new Tx() .registerDRep(account, anchor) // DRep registration .delegateVotingPowerTo(stakeAddress, drep) // vote delegation .createVote(voter, govActionId, Vote.YES) // voting .createProposal(govAction, rewardAccount, anchor) .from(senderAddress); ``` `delegateVotingPowerTo` takes a `DRep`, not a bech32 id string — convert with `GovId.toDrep(...)`. If your training data has no `registerDRep` / `createVote` / `delegateVotingPowerTo` on `Tx`, it predates Conway support. See [Governance API](/governance/overview/). ### 2.5 Ignoring the datum-hash filter in coin selection The default UTXO selection strategy **skips UTXOs carrying a datum hash**. That is usually right for ordinary payments and wrong when you mean to spend a script output. When collecting from a script address, pass the UTXOs explicitly with `collectFrom(...)` rather than relying on selection. --- ## 3. Canonical patterns ### 3.1 Send ADA ```java import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; import com.bloxbean.cardano.client.quicktx.TxStatus; Account sender = Account.createFromMnemonic(Networks.preview(), senderMnemonic); BackendService backend = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, projectId); Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(sender.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` `SignerProviders` lives in the `function` package but is the correct, canonical way to sign a QuickTx transaction — it is the one part of that package you should generate. ### 3.2 Mint a native token ```java Policy policy = PolicyUtil.createMultiSigScriptAllPolicy("my-policy", 1); Asset asset = new Asset("MyToken", BigInteger.valueOf(1000)); Tx tx = new Tx() .mintAssets(policy.getPolicyScript(), asset, receiverAddress) .from(sender.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .withSigner(SignerProviders.signerFrom(policy)) // policy key must sign too .completeAndWait(); ``` Minting needs **two** signers: the funding account and the policy key. ### 3.3 Call a Plutus script On the 0.8.0 previews, use `Tx` rather than `ScriptTx`. On stable 0.7.x, use `ScriptTx` with `.feePayer(...)`: ```java Tx tx = new Tx() .collectFrom(scriptUtxo, redeemer) .attachSpendingValidator(plutusScript) .payToAddress(receiverAddress, Amount.ada(5)) .from(sender.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` :::caution[`ScriptTx` is deprecated in the 0.8.0 previews only] In the previews, every `ScriptTx` operation is also on `Tx` — script inputs, validator attachment, Plutus minting, script-protected certificates and governance. `ScriptTx` still works there. In stable 0.7.x it isn't deprecated, and `Tx` has none of these script operations, so check which version the project uses. To migrate, replace `new ScriptTx()` with `new Tx()`. Because `Tx` has `.from(...)`, the separate `.feePayer(...)` step that `ScriptTx` required is no longer needed for the common case. `Tx` has no `collectFrom(Utxo)`, and `Tx.collectFrom(List)` adds regular inputs, so always pass a redeemer when spending from a script. See [Unified Tx API](/quicktx/unified-tx/). ::: One naming trap: **native-script** minting is `mintAssets` (plural), **Plutus** minting is `mintAsset` (singular). ### 3.4 Stake delegation ```java Tx tx = new Tx() .registerStakeAddress(sender.stakeAddress()) .delegateTo(sender.stakeAddress(), poolId) .from(sender.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .withSigner(SignerProviders.stakeKeySignerFrom(sender)) // stake key signs .completeAndWait(); ``` Delegation, deregistration and withdrawal need the **stake key** signature in addition to the payment key. Registration alone doesn't. A withdrawal also needs the stake address to have delegated its vote, for example `.delegateVotingPowerTo(address, DRep.abstain())`. --- ## 4. Signing reference | Signer | Use for | |---|---| | `SignerProviders.signerFrom(account)` | Payment key. Almost every transaction. | | `SignerProviders.signerFrom(policy)` | Native-script policy key, when minting or burning. | | `SignerProviders.stakeKeySignerFrom(account)` | Delegation, vote delegation, deregistration, withdrawal. | | `SignerProviders.drepKeySignerFrom(account)` | Conway DRep operations. | | `SignerProviders.committeeHotKeySignerFrom(account)` | Conway committee hot-key votes. | | `SignerProviders.committeeColdKeySignerFrom(account)` | Conway committee cold-key operations. | Chain `.withSigner(...)` once per required key. --- ## 5. Dependencies Use one version for every CCL module. ```gradle def cclVersion = '' // the latest preview, see /start-here/installation/#versions dependencies { implementation "com.bloxbean.cardano:cardano-client-lib:${cclVersion}" implementation "com.bloxbean.cardano:cardano-client-backend-blockfrost:${cclVersion}" // Only if the task needs them: implementation "com.bloxbean.cardano:cardano-client-txflow:${cclVersion}" implementation "com.bloxbean.cardano:cardano-client-merkle-patricia-forestry:${cclVersion}" } ``` `cardano-client-lib` is the aggregate that pulls in the common modules. Add a backend module — without one there is no way to read UTXOs or submit. The full generated list of modules, artifact ids and entry points is at [`/ai/catalog.json`](/ai/catalog.json). --- ## 6. Verified data structures: which one Answer in this order. 1. **Must an on-chain Aiken validator verify the proof?** Yes → `MpfTrie`. No → `JellyfishMerkleTree` also works, for off-chain state only. 2. **Which storage backend?** `merkle-patricia-forestry-rocksdb` for embedded single-process. `merkle-patricia-forestry-rdbms` for shared multi-process (PostgreSQL, H2, SQLite). See [Verified structures](/verified-structures/overview/). --- ## 7. Testing generated code Do not hand back transaction code you have not run. [Yaci DevKit](https://devkit.yaci.xyz/#get-started) gives a local devnet with pre-funded accounts and one-second blocks. `devkitMnemonic` is the [default devnet mnemonic](/learn/test-with-yaci-devkit/#fund-an-account): ```java BackendService backend = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); Account sender = Account.createFromMnemonic(Networks.testnet(), devkitMnemonic); ``` No API key, no faucet, no testnet wait. If the code produces a confirmed transaction hash there, the same code works against preview with the URL and network swapped. --- ## 8. Where to look next | Question | Page | |---|---| | Full QuickTx surface | [QuickTx overview](/quicktx/overview/) | | Declarative YAML plans | [TxPlan](/txplan/overview/) | | Multi-step workflows | [TxFlow](/txflow/overview/) | | Continuous submission | [TxStream](/txstream/getting-started/) | | Conway governance | [Governance API](/governance/overview/) | | Plutus, blueprints, Aiken | [Smart contracts](/smart-contracts/plutus-api/) | | Every module and artifact | [Modules & artifacts](/reference/modules/) | --- # Governance API Source: https://cardano-client.dev/governance/overview/ Summary: APIs for Cardano governance operations (DRep, Voting, Proposals) The Governance API provides comprehensive functionality for participating in Cardano's on-chain governance system. It supports DRep registration and management, governance proposal creation, voting, and delegation operations through the QuickTx framework. ## Key Features - **DRep Management**: Register, update, and deregister as a Delegated Representative - **Proposal Creation**: Create various types of governance proposals (Info, Constitution, Parameter Changes, etc.) - **Voting System**: Vote on governance proposals with different voter types - **Delegation**: Delegate voting power to DReps - **Anchor Support**: Attach metadata anchors to proposals and votes - **Multi-Signature Support**: Support for committee and stake pool voting ## Core Classes ### Tx Class The main class for building governance transactions, extending QuickTx functionality. ### Governance Actions - `InfoAction` - Information proposals - `NewConstitution` - Constitution update proposals - `NoConfidence` - No confidence proposals - `ParameterChangeAction` - Protocol parameter changes - `HardForkInitiationAction` - Hard fork initiation - `TreasuryWithdrawalsAction` - Treasury withdrawal proposals - `UpdateCommittee` - Committee update proposals ### Supporting Classes - `Voter` - Represents different voter types (DRep, SPO, Committee) - `Vote` - Vote options (Yes, No, Abstain) - `Anchor` - Metadata anchor for proposals - `GovActionId` - References to governance actions ## Usage Examples ### Setup Create QuickTxBuilder and Account instances for governance operations: ```java QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); Account account = Account.createFromMnemonic(Networks.preprod(), "your mnemonic words"); String address = account.baseAddress(); ``` In the examples below, `` is a `byte[]`, the Blake2b-256 hash of the anchor document. If you have it as hex, use `HexUtil.decodeHexString(hash)`. Actions that follow an earlier action need `prevGovActionId` to be the last enacted action of that type on your network. ### DRep Management Manage Delegated Representative (DRep) registration and operations: #### Register DRep Register an account as a Delegated Representative: ```java var anchor = new Anchor("", ); Tx drepRegTx = new Tx() .registerDRep(account, anchor) .from(address); Result result = quickTxBuilder.compose(drepRegTx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.signerFrom(account.drepHdKeyPair())) .completeAndWait(System.out::println); ``` #### Deregister DRep Deregister an account as a DRep: ```java Tx tx = new Tx() .unregisterDRep(account.drepCredential()) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.drepKeySignerFrom(account)) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(System.out::println); ``` #### Update DRep Update DRep information (remove anchor): ```java Tx drepRegTx = new Tx() .updateDRep(account.drepCredential()) .from(address); Result result = quickTxBuilder.compose(drepRegTx) .withSigner(SignerProviders.drepKeySignerFrom(account)) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` Update DRep information with a new anchor: ```java var newAnchor = new Anchor("", ); Tx drepRegTx = new Tx() .updateDRep(account.drepCredential(), newAnchor) .from(address); Result result = quickTxBuilder.compose(drepRegTx) .withSigner(SignerProviders.drepKeySignerFrom(account)) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` ### Governance Proposals Create various types of governance proposals using the `createProposal()` method. Any account can create a proposal — a DRep key signature is **not** required. #### Info Proposal Create an information proposal: ```java var govAction = new InfoAction(); var anchor = new Anchor("", ); Tx tx = new Tx() .createProposal(govAction, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` #### Constitution Proposal Create a new constitution proposal: ```java var anchor = new Anchor("", ); var govAction = new NewConstitution(); govAction.setPrevGovActionId(new GovActionId("", prevGovActionIndex)); govAction.setConstitution(Constitution.builder() .anchor(anchor) .build()); Tx tx = new Tx() .createProposal(govAction, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` #### No Confidence Proposal Create a no confidence proposal: ```java var noConfidence = new NoConfidence(); noConfidence.setPrevGovActionId(new GovActionId("", prevGovActionIndex)); var anchor = new Anchor("", ); Tx tx = new Tx() .createProposal(noConfidence, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` #### Parameter Change Proposal Create a protocol parameter change proposal: ```java var parameterChange = new ParameterChangeAction(); parameterChange.setPrevGovActionId(new GovActionId("529736be1fac33431667f2b66231b7b66d4c7a3975319ddac7cfb17dcb5c4145", 0)); parameterChange.setProtocolParamUpdate(ProtocolParamUpdate.builder() .minPoolCost(ADAConversionUtil.adaToLovelace(100)) .build()); var anchor = new Anchor("", ); Tx tx = new Tx() .createProposal(parameterChange, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` #### Hard Fork Initiation Proposal Create a hard fork initiation proposal: ```java var hardforkInitiation = new HardForkInitiationAction(); hardforkInitiation.setPrevGovActionId(new GovActionId("416f7f01c548a85546aa5bbd155b34bb2802df68e08db4e843ef6da764cd8f7e", 0)); hardforkInitiation.setProtocolVersion(new ProtocolVersion(9, 0)); var anchor = new Anchor("", ); Tx tx = new Tx() .createProposal(hardforkInitiation, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` #### Treasury Withdrawal Proposal Create a treasury withdrawal proposal: ```java var treasuryWithdrawalsAction = new TreasuryWithdrawalsAction(); treasuryWithdrawalsAction.addWithdrawal(new Withdrawal("", adaToLovelace(20))); var anchor = new Anchor("", ); Tx tx = new Tx() .createProposal(treasuryWithdrawalsAction, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` #### Update Committee Proposal Create a committee update proposal: ```java var updateCommittee = new UpdateCommittee(); updateCommittee.setPrevGovActionId(new GovActionId("b3ce0371310a07a797657d19453d953bb352b6841c2f5c5e0bd2557189ef5c3a", 0)); updateCommittee.setQuorumThreshold(new UnitInterval(BigInteger.valueOf(1), BigInteger.valueOf(3))); var anchor = new Anchor("", ); Tx tx = new Tx() .createProposal(updateCommittee, account.stakeAddress(), anchor) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(s -> System.out.println(s)); ``` ### Voting Vote on governance proposals: ```java var voter = new Voter(VoterType.DREP_KEY_HASH, account.drepCredential()); var govActionId = new GovActionId("5655fbb4ceafd34296fe58f6e3d28b8ff663a89e84aa0edd77bd02fe379cef4c", 0); //some gov action id Tx tx = new Tx() .createVote(voter, govActionId, Vote.NO) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.drepKeySignerFrom(account)) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(System.out::println); ``` ### Vote Delegation Delegate voting power to a DRep: ```java DRep drep = GovId.toDrep(drepId); // the DRep's drep1... id Tx tx = new Tx() .delegateVotingPowerTo(account.baseAddress(), drep) .from(address); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.stakeKeySignerFrom(account)) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(System.out::println); ``` --- # Programmable Tokens with CIP-113 Source: https://cardano-client.dev/learn/cip113-programmable-tokens/ Summary: Build a token that can't move without its issuer's approval: register, mint, transfer, seize and burn it on Preview. A native Cardano token moves freely once it's minted. Nobody can block a transfer, freeze a holder or recover stolen funds, which rules out many regulated assets. [CIP-113](https://cips.cardano.org/cip/CIP-0113) adds rules without a hard fork: tokens sit at a shared script, and they change hands only if the token's own logic script approves. In this tutorial you'll create a token that can't move without its issuer's signature, then take it through its whole life on the Preview testnet: 1. Register the token and mint 1,000 to Alice. 2. Watch a transfer fail because the issuer didn't approve it, then send an approved one to Bob. 3. Seize tokens from Bob without his signature. 4. Burn what you seized. You finish with Alice holding 900 tokens, Bob holding 60, and a total supply of 960. :::note[Version] Programmable token support is available from **0.8.0-pre7**, and this tutorial was tested on that release. If the code doesn't compile on a newer release, check the programmable-token [release notes](https://github.com/bloxbean/cardano-client-lib/blob/master/programmable-token/RELEASE_NOTES.md). ::: **You need:** Java 17+, and some experience with [QuickTx](/quicktx/overview/). If you haven't built a transaction with this library yet, start with [your first transaction](/learn/simple-transfer/). ## How CIP-113 works Four ideas explain most of what follows. **Smart wallet.** Programmable tokens never sit at an ordinary address. Each holder has a smart wallet: an address whose payment part is the shared `programmable_logic_base` script and whose stake part identifies the owner. Anyone can derive it from the owner's normal address, so a receiver doesn't have to set anything up. Once minted, tokens move only between smart wallets. **Registry.** A shared on-chain linked list with one node per token. Each node records the scripts that govern that token, and anyone can add a node. **Module.** Those scripts are the token's rules, and you write them. Together they form the token's module, which the CIP calls a *substandard*. The two terms mean the same thing. | Script | Field in CCL | Runs when | |---|---|---| | Minting logic | `mintingLogicScript` | Tokens are minted or burned | | Transfer logic | `transferLogicScript` | A holder moves or burns their own tokens | | Third-party logic | `thirdPartyTransferLogicScript` | Someone acts on another holder's tokens, such as a seizure | The CIP calls the minting logic `issuanceLogicScript`. A fourth script, `unfrackingLogicScript`, defaults to forbidden; see [Limits](#limits). **Withdraw-zero.** Logic scripts run as a withdrawal of 0 lovelace from the script's own reward account. A script can then take part in a transaction without holding anything, but its reward account has to be registered first. ```text shared protocol, already deployed +-----------------------------------------------------------+ | registry --> your token's node --> your logic scripts | +-----------------------------------------------------------+ | checked on every move | Alice's smart wallet ------+------> Bob's smart wallet (base script + Alice's key) (base script + Bob's key) ``` You don't deploy any of this. The library ships descriptions of the public deployments as `Cip113Deployments.PREVIEW`, `PREPROD` and `MAINNET`. This tutorial uses Preview because that deployment already exists. A new Yaci DevKit devnet doesn't have one. You can deploy the protocol there with ordinary QuickTx transactions, but the library has no shortcut for it. If you really want to do that, see how [the library's end-to-end tests deploy it](https://github.com/bloxbean/cardano-client-lib/blob/master/programmable-token/src/it/java/com/bloxbean/cardano/client/programmabletoken/cip113/tx/Cip113Bootstrap.java), and pass the resulting description to `Cip113ProgrammableTokenService.create`. When you build a transaction, the library finds the registry node, adds the protocol's reference inputs, writes its redeemers and sizes the min-ADA on each output. You only state what you want to happen. :::note[Two addresses, one owner] Alice's tokens sit in her smart wallet, but her fees and collateral come from her normal address, so a holder who sends tokens needs ADA there too. The CIP lets an application key smart wallets by the owner's payment or stake credential, and the two choices give the same person different addresses. CCL defaults to `SmartWalletKey.PAYMENT`, which puts the owner's *payment* credential in the stake part of the address. Everyone holding or reading a token has to agree on the choice. ::: ## Set up your project Add these dependencies, replacing `` with the release you're using. `cardano-client-programmable-token` isn't part of the `cardano-client-lib` aggregate, so it has to be listed on its own. ```gradle def cclVersion = '' dependencies { implementation "com.bloxbean.cardano:cardano-client-lib:$cclVersion" implementation "com.bloxbean.cardano:cardano-client-backend-blockfrost:$cclVersion" implementation "com.bloxbean.cardano:cardano-client-programmable-token:$cclVersion" implementation 'com.bloxbean.cardano:aiken-java-binding:0.1.0' } ``` `aiken-java-binding` applies your script's parameter and evaluates scripts locally. It bundles native libraries for macOS, Windows and x86-64 Linux, but not ARM64 Linux. On ARM64 Linux, [use Scalus instead](#use-scalus-instead-of-aiken). You also need a free [Blockfrost](https://blockfrost.io) project for Preview and a funded wallet. Create a wallet with this class: ```java import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.common.model.Networks; public class CreateWallet { public static void main(String[] args) { Account account = new Account(Networks.preview()); System.out.println("Mnemonic: " + account.mnemonic()); System.out.println("Fund this address: " + account.baseAddress()); } } ``` Send at least 50 tADA to the printed address from the [testnet faucet](https://docs.cardano.org/cardano-testnets/tools/faucet), selecting Preview. Then export your settings: ```bash export BF_PROJECT_ID= export TUTORIAL_MNEMONIC="" ``` ## Step 1: Connect to the deployment The code below derives three accounts from your mnemonic. The issuer is account 0, the address you funded. Alice and Bob are accounts 1 and 2. ```java public class IssuerApprovedToken { /** issuer_approved.issuer_approved.withdraw from plutus.json, before the issuer is applied. */ private static final String COMPILED_CODE = "5895010100229800aba2aba1aab9faab9eaab9dab9a9bae002488888896600264646644b3001" + "3370e900218039baa00189991198008009bac300c300d300d300d300d300d300d300d300d30" + "0a3754601800c601460106ea8004896600200314a115980099b8f375c601800200f14a31330" + "02002300d0014020805a2c8030c020004c020c024004c020004c010dd5004452689b2b200401"; private static final String ASSET_NAME = "TutorialToken"; /** How long to wait for a submitted transaction to land in a block. */ private static final Duration CONFIRM_TIMEOUT = Duration.ofMinutes(5); /** How long to let the backend's UTxO view catch up after a confirmed transaction. */ private static final Duration SETTLE_TIME = Duration.ofSeconds(60); private final BackendService backend; private final Network network; private final Account issuer; private final Account alice; private final Account bob; private final Cip113ProgrammableTokenService tokens; private final Cip113ProtocolService protocol; private final Address issuerAddress; private PlutusScript logicScript; private Credential logic; private String policyId; public IssuerApprovedToken(BackendService backend, Cip113ProgrammableTokenService tokens, Network network, Account issuer, Account alice, Account bob) { this.backend = backend; this.network = network; this.issuer = issuer; this.alice = alice; this.bob = bob; this.issuerAddress = new Address(issuer.baseAddress()); this.tokens = tokens; this.protocol = tokens.advanced(); } public static void main(String[] args) throws Exception { String projectId = System.getenv("BF_PROJECT_ID"); String mnemonic = System.getenv("TUTORIAL_MNEMONIC"); Network network = Networks.preview(); BackendService backend = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, projectId); // One mnemonic, three separate accounts: the issuer and two holders. Account issuer = Account.createFromMnemonic(network, mnemonic, 0, 0); Account alice = Account.createFromMnemonic(network, mnemonic, 1, 0); Account bob = Account.createFromMnemonic(network, mnemonic, 2, 0); Cip113ProgrammableTokenService tokens = Cip113ProgrammableTokenService.create(backend, Cip113Deployments.PREVIEW); new IssuerApprovedToken(backend, tokens, network, issuer, alice, bob).run(); } public void run() throws Exception { prepareLogicScript(); // Needed on every run. It submits nothing. // Run 1: fund Alice, register the token and mint. // These work once per wallet, so comment them out before run 2. fundAlice(); registerRewardAccount(); registerAndMint(); printBalances("after mint"); // Run 2: uncomment to try a transfer. // transferWithoutApproval(); // transfer(); // printBalances("after transfer"); // Run 3: uncomment to seize and burn, after commenting out run 2. // seize(); // printBalances("after seizure"); // burn(); // printBalances("final"); } // The methods below go here. } ``` `Cip113ProgrammableTokenService` is the entry point. `tokens.extension()` plugs the protocol into QuickTx, and `tokens.advanced()` gives you the read side used throughout this tutorial. The sections below add one method each to this class, and the [complete class](#run-it) is at the end. ## Step 2: Write the token's rule This token has a single rule, written in [Aiken](https://aiken-lang.org): the issuer must have signed. ```rust use aiken/collection/list use aiken/crypto.{VerificationKeyHash} use cardano/address.{Credential} use cardano/transaction.{Transaction} /// Passes only when `issuer` signed the transaction. validator issuer_approved(issuer: VerificationKeyHash) { withdraw(_redeemer: Data, _account: Credential, tx: Transaction) { list.has(tx.extra_signatories, issuer) } else(_) { fail } } ``` You don't need Aiken installed. The compiled validator is the `COMPILED_CODE` constant in the class above, built with Aiken v1.1.23 and stdlib v3.0.0. One script serves all three roles here, so there's only one reward account to register. A real token would usually have a separate script for each role. `issuer` is a parameter, so every issuer gets a different script hash. That matters because a token's policy id is derived from its minting script's hash. You don't choose the policy id, and two tokens with the same minting script would be the same token. Your parameterized script gives you a policy nobody else in the shared registry has. ```java private void prepareLogicScript() throws Exception { byte[] issuerKeyHash = issuer.hdKeyPair().getPublicKey().getKeyHash(); String applied = AikenScriptUtil.applyParamToScript( ListPlutusData.of(BytesPlutusData.of(issuerKeyHash)), COMPILED_CODE); logicScript = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode(applied, PlutusVersion.v3); logic = Credential.fromScript(logicScript.getPolicyId()); // Nothing on chain has used this script yet, so no backend can serve it. protocol.scripts().register(logicScript); policyId = value(protocol.derivePolicyId(logic)); System.out.println("Logic script : " + logicScript.getPolicyId()); System.out.println("Policy id : " + policyId); } ``` The `register` call is needed because a backend can serve a script only after the chain has revealed it, and yours hasn't been used yet. ## Step 3: Fund Alice and register the reward account Alice needs ADA at her normal address for fees and collateral: ```java private void fundAlice() { Tx tx = new Tx() .payToAddress(alice.baseAddress(), Amount.ada(30)) .from(issuer.baseAddress()); check("Fund alice", new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(issuer)) .completeAndWait(CONFIRM_TIMEOUT)); } ``` Every submission in this tutorial goes through `check`, which stops unless the transaction is confirmed and then pauses: ```java private static void check(String step, TxResult result) { if (!result.isSuccessful()) throw new IllegalStateException(step + " failed: " + result.getResponse()); if (result.getTxStatus() != TxStatus.CONFIRMED) throw new IllegalStateException(step + " not confirmed (" + result.getTxStatus() + "): " + result.getValue()); System.out.printf("%-12s : %s%n", step, result.getValue()); settle(); } private static void settle() { try { Thread.sleep(SETTLE_TIME.toMillis()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(e); } } ``` Both checks matter on a public network: - **Confirmation.** `completeAndWait()` waits 60 seconds by default. If the transaction hasn't landed by then, the result still reports `isSuccessful()`, with the status `TIMEOUT`. Blocks can be slow on Preview, where one of our test transactions took about two minutes, so this code waits up to five minutes and accepts only `CONFIRMED`. - **The pause.** Blockfrost's UTxO queries can lag behind a confirmed transaction: we measured anywhere from 1 to 37 seconds on Preview. Without the pause, the next transaction can pick a UTxO that was just spent. This keeps the tutorial simple, but a production application should chain transactions properly, for example with [TxFlow](/txflow/overview/). Next, register the script's reward account so that withdraw-zero can run it. This needs no script witness. The check for `StakeKeyRegistered` lets you run this step again safely. ```java private void registerRewardAccount() { String rewardAddress = AddressProvider.getRewardAddress(logic, network).toBech32(); TxResult result = new QuickTxBuilder(backend) .compose(new Tx() .registerStakeAddress(rewardAddress) .from(issuer.baseAddress())) .withSigner(SignerProviders.signerFrom(issuer)) .completeAndWait(CONFIRM_TIMEOUT); if (!result.isSuccessful() && result.getResponse().contains("StakeKeyRegistered")) { System.out.println("Reward account already registered"); return; } check("Reward acct", result); } ``` ## Step 4: Register the token and mint Every programmable-token transaction goes through this helper: ```java private QuickTxBuilder.TxContext builder(Tx tx, Account sender) { return new QuickTxBuilder(backend) .withExtension(tokens.extension()) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .withTxEvaluator(new AikenTransactionEvaluator(backend)); } ``` `withExtension` is what turns verbs like `register` and `mint` into a real transaction. `withTxEvaluator` runs the scripts locally, so a script failure stops the build before anything is submitted. Now register the token and mint its first supply in one transaction: ```java private void registerAndMint() { RegistryNodeSpec spec = RegistryNodeSpec.builder() .mintingLogicScript(logic) .transferLogicScript(logic) .thirdPartyTransferLogicScript(logic) .build(); ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(issuer.baseAddress()) .register("tutorial", Cip113Registration.from(spec), PlutusData.unit()) .mint(ProgrammableTokenPolicyRef.named("tutorial"), alice.baseAddress(), List.of(new Asset(ASSET_NAME, BigInteger.valueOf(1000))), PlutusData.unit(), null); check("Register", builder(tx, issuer) .withRequiredSigners(issuerAddress) .completeAndWait(CONFIRM_TIMEOUT)); } ``` - `RegistryNodeSpec` names the scripts for each role. - `ProgrammableTokenPolicyRef.named("tutorial")` points the mint at the registration in the same transaction. The name exists only inside this transaction. Later mints use the literal policy id. - `alice.baseAddress()` is Alice's normal address. The library maps it to her smart wallet, so the tokens land there. - `PlutusData.unit()` is the redeemer. This validator ignores its redeemer. - `withRequiredSigners` declares the issuer. A script sees only the signers a transaction declares, so `tx.extra_signatories` would otherwise be empty. Registration spends a node of the shared registry, so it can collide with someone else registering at the same moment. If that happens, run it again. ## Step 5: Check the balances ```java private void printBalances(String label) { System.out.printf("%-15s alice=%s bob=%s issuer=%s%n", label, balance(alice), balance(bob), balance(issuer)); } private BigInteger balance(Account holder) { String unit = policyId + HexUtil.encodeHexString(ASSET_NAME.getBytes(StandardCharsets.UTF_8)); return value(protocol.getProgrammableBalance(new Address(holder.baseAddress()))).stream() .filter(amount -> unit.equals(amount.getUnit())) .map(Amount::getQuantity) .reduce(BigInteger.ZERO, BigInteger::add); } ``` `getProgrammableBalance` takes the owner's normal address and reads their smart wallet. It returns only tokens that are in the registry. Other calls on `protocol` you may find useful are `smartWalletAddress`, `isProgrammable` and `getRegistryNode`. After the mint you'll see: ```text after mint alice=1000 bob=0 issuer=0 ``` ## Step 6: Try a transfer without approval Alice tries to send 100 tokens to Bob, signing alone: ```java private void transferWithoutApproval() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(alice.baseAddress()) .transfer(bob.baseAddress(), Amount.asset(policyId, ASSET_NAME, 100), PlutusData.unit()); try { builder(tx, alice).completeAndWait(); } catch (TxBuildException e) { System.out.println("Rejected : " + e.getMessage()); return; } throw new IllegalStateException("The transfer should not have passed without the issuer"); } ``` The library declares Alice as a required signer because her wallet is being spent, and that satisfies the protocol. But the token's transfer logic also runs, finds no issuer signature and fails. Evaluation stops the build, so nothing is submitted and no fee or collateral is at risk. The failure is thrown as a `TxBuildException` with this message: ```text Rejected : Error while evaluating script cost ``` If you have an SLF4J provider on the classpath, the evaluator also logs the reason. `tag: "Withdraw"` tells you that a withdraw-zero script said no: ```text AIKEN: RedeemerError { tag: "Withdraw", index: 2, err: Machine(EvaluationFailure, ...) } ``` ## Step 7: Send an approved transfer The same transaction goes through once the issuer approves it. Both parts matter: `withRequiredSigners` declares the issuer, and `withSigner` adds the signature. ```java private void transfer() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(alice.baseAddress()) .transfer(bob.baseAddress(), Amount.asset(policyId, ASSET_NAME, 100), PlutusData.unit()); check("Transfer", builder(tx, alice) .withRequiredSigners(issuerAddress) .withSigner(SignerProviders.signerFrom(issuer)) .completeAndWait(CONFIRM_TIMEOUT)); } ``` ```text after transfer alice=900 bob=100 issuer=0 ``` ## Step 8: Seize tokens Now the issuer takes 40 tokens from Bob. Bob doesn't sign, and he doesn't need any ADA either. ```java private void seize() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(issuer.baseAddress()) .thirdPartyTransfer(bob.baseAddress(), issuer.baseAddress(), Amount.asset(policyId, ASSET_NAME, 40), PlutusData.unit()); check("Seize", builder(tx, issuer) .withRequiredSigners(issuerAddress) .completeAndWait(CONFIRM_TIMEOUT)); } ``` `thirdPartyTransfer` takes the holder, the receiver, the amount and a redeemer. The library selects Bob's UTxOs, spends them under the token's third-party logic, and keeps whatever isn't seized in his smart wallet. Unlike a transfer, this verb declares no signer for you, so `withRequiredSigners` is what makes the issuer's signature visible to the script. ```text after seizure alice=900 bob=60 issuer=40 ``` :::danger[Holders can't refuse a seizure] Whoever the third-party logic accepts can take tokens from any smart wallet. If a token's third-party logic always succeeds, **anyone can take anyone's tokens**. The library can't prevent that, because it's how the token was designed. Before you accept a token, read its rules with `protocol.getRegistryNode(policyId)`. ::: ## Step 9: Burn tokens The issuer now burns the 40 seized tokens: ```java private void burn() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(issuer.baseAddress()) .burn(policyId, List.of(new Asset(ASSET_NAME, BigInteger.valueOf(40))), BurnAuthorization.of(PlutusData.unit(), PlutusData.unit())); check("Burn", builder(tx, issuer).completeAndWait(CONFIRM_TIMEOUT)); } ``` A burn has its own verb because it needs two approvals: the transfer logic spends the tokens from the wallet, and the minting logic approves the negative mint. `BurnAuthorization` carries a redeemer for each. This token uses one script for both roles, and that script runs once, so you pass the same value twice. There's no `withRequiredSigners` here. The issuer is spending their own smart wallet, so the library already declares them as the owner, and that's the same key the script checks. ```text final alice=900 bob=60 issuer=0 ``` ## Run it Here is the complete class, ready to copy. It runs in stages, so that you can try your first mint before going further: 1. **Run it as it is** to register the token and mint 1,000 units to Alice. 2. **In `run()`, comment out the run 1 lines and uncomment the run 2 lines**, then run it again to transfer to Bob. Run 2 also shows a transfer being rejected without the issuer's approval. 3. **Comment out the run 2 lines and uncomment the run 3 lines**, then run it again to seize and burn.
IssuerApprovedToken.java ```java package tutorial; import com.bloxbean.cardano.aiken.AikenScriptUtil; import com.bloxbean.cardano.aiken.AikenTransactionEvaluator; import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.address.Address; import com.bloxbean.cardano.client.address.AddressProvider; import com.bloxbean.cardano.client.address.Credential; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.api.model.Result; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Network; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.exception.TxBuildException; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.plutus.blueprint.PlutusBlueprintUtil; import com.bloxbean.cardano.client.plutus.blueprint.model.PlutusVersion; import com.bloxbean.cardano.client.plutus.spec.BytesPlutusData; import com.bloxbean.cardano.client.plutus.spec.ListPlutusData; import com.bloxbean.cardano.client.plutus.spec.PlutusData; import com.bloxbean.cardano.client.plutus.spec.PlutusScript; import com.bloxbean.cardano.client.programmabletoken.BurnAuthorization; import com.bloxbean.cardano.client.programmabletoken.ProgrammableTokenPolicyRef; import com.bloxbean.cardano.client.programmabletoken.ProgrammableTokenTx; import com.bloxbean.cardano.client.programmabletoken.cip113.Cip113Deployments; import com.bloxbean.cardano.client.programmabletoken.cip113.Cip113ProgrammableTokenService; import com.bloxbean.cardano.client.programmabletoken.cip113.Cip113ProtocolService; import com.bloxbean.cardano.client.programmabletoken.cip113.Cip113Registration; import com.bloxbean.cardano.client.programmabletoken.cip113.tx.RegistryNodeSpec; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; import com.bloxbean.cardano.client.quicktx.TxStatus; import com.bloxbean.cardano.client.transaction.spec.Asset; import com.bloxbean.cardano.client.util.HexUtil; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; public class IssuerApprovedToken { /** issuer_approved.issuer_approved.withdraw from plutus.json, before the issuer is applied. */ private static final String COMPILED_CODE = "5895010100229800aba2aba1aab9faab9eaab9dab9a9bae002488888896600264646644b3001" + "3370e900218039baa00189991198008009bac300c300d300d300d300d300d300d300d300d30" + "0a3754601800c601460106ea8004896600200314a115980099b8f375c601800200f14a31330" + "02002300d0014020805a2c8030c020004c020c024004c020004c010dd5004452689b2b200401"; private static final String ASSET_NAME = "TutorialToken"; /** How long to wait for a submitted transaction to land in a block. */ private static final Duration CONFIRM_TIMEOUT = Duration.ofMinutes(5); /** How long to let the backend's UTxO view catch up after a confirmed transaction. */ private static final Duration SETTLE_TIME = Duration.ofSeconds(60); private final BackendService backend; private final Network network; private final Account issuer; private final Account alice; private final Account bob; private final Cip113ProgrammableTokenService tokens; private final Cip113ProtocolService protocol; private final Address issuerAddress; private PlutusScript logicScript; private Credential logic; private String policyId; public IssuerApprovedToken(BackendService backend, Cip113ProgrammableTokenService tokens, Network network, Account issuer, Account alice, Account bob) { this.backend = backend; this.network = network; this.issuer = issuer; this.alice = alice; this.bob = bob; this.issuerAddress = new Address(issuer.baseAddress()); this.tokens = tokens; this.protocol = tokens.advanced(); } public static void main(String[] args) throws Exception { String projectId = System.getenv("BF_PROJECT_ID"); String mnemonic = System.getenv("TUTORIAL_MNEMONIC"); Network network = Networks.preview(); BackendService backend = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, projectId); // One mnemonic, three separate accounts: the issuer and two holders. Account issuer = Account.createFromMnemonic(network, mnemonic, 0, 0); Account alice = Account.createFromMnemonic(network, mnemonic, 1, 0); Account bob = Account.createFromMnemonic(network, mnemonic, 2, 0); Cip113ProgrammableTokenService tokens = Cip113ProgrammableTokenService.create(backend, Cip113Deployments.PREVIEW); new IssuerApprovedToken(backend, tokens, network, issuer, alice, bob).run(); } public void run() throws Exception { prepareLogicScript(); // Needed on every run. It submits nothing. // Run 1: fund Alice, register the token and mint. // These work once per wallet, so comment them out before run 2. fundAlice(); registerRewardAccount(); registerAndMint(); printBalances("after mint"); // Run 2: uncomment to try a transfer. // transferWithoutApproval(); // transfer(); // printBalances("after transfer"); // Run 3: uncomment to seize and burn, after commenting out run 2. // seize(); // printBalances("after seizure"); // burn(); // printBalances("final"); } // -- Step: the token's logic script private void prepareLogicScript() throws Exception { byte[] issuerKeyHash = issuer.hdKeyPair().getPublicKey().getKeyHash(); String applied = AikenScriptUtil.applyParamToScript( ListPlutusData.of(BytesPlutusData.of(issuerKeyHash)), COMPILED_CODE); logicScript = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode(applied, PlutusVersion.v3); logic = Credential.fromScript(logicScript.getPolicyId()); // Nothing on chain has used this script yet, so no backend can serve it. protocol.scripts().register(logicScript); policyId = value(protocol.derivePolicyId(logic)); System.out.println("Logic script : " + logicScript.getPolicyId()); System.out.println("Policy id : " + policyId); } private void fundAlice() { Tx tx = new Tx() .payToAddress(alice.baseAddress(), Amount.ada(30)) .from(issuer.baseAddress()); check("Fund alice", new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(issuer)) .completeAndWait(CONFIRM_TIMEOUT)); } // -- Step: withdraw-zero needs a registered reward account private void registerRewardAccount() { String rewardAddress = AddressProvider.getRewardAddress(logic, network).toBech32(); TxResult result = new QuickTxBuilder(backend) .compose(new Tx() .registerStakeAddress(rewardAddress) .from(issuer.baseAddress())) .withSigner(SignerProviders.signerFrom(issuer)) .completeAndWait(CONFIRM_TIMEOUT); if (!result.isSuccessful() && result.getResponse().contains("StakeKeyRegistered")) { System.out.println("Reward account already registered"); return; } check("Reward acct", result); } // -- Step: register the token and mint its first supply private void registerAndMint() { RegistryNodeSpec spec = RegistryNodeSpec.builder() .mintingLogicScript(logic) .transferLogicScript(logic) .thirdPartyTransferLogicScript(logic) .build(); ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(issuer.baseAddress()) .register("tutorial", Cip113Registration.from(spec), PlutusData.unit()) .mint(ProgrammableTokenPolicyRef.named("tutorial"), alice.baseAddress(), List.of(new Asset(ASSET_NAME, BigInteger.valueOf(1000))), PlutusData.unit(), null); check("Register", builder(tx, issuer) .withRequiredSigners(issuerAddress) .completeAndWait(CONFIRM_TIMEOUT)); } // -- Step: a transfer without the issuer's signature private void transferWithoutApproval() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(alice.baseAddress()) .transfer(bob.baseAddress(), Amount.asset(policyId, ASSET_NAME, 100), PlutusData.unit()); try { builder(tx, alice).completeAndWait(); } catch (TxBuildException e) { System.out.println("Rejected : " + e.getMessage()); return; } throw new IllegalStateException("The transfer should not have passed without the issuer"); } // -- Step: the same transfer, approved private void transfer() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(alice.baseAddress()) .transfer(bob.baseAddress(), Amount.asset(policyId, ASSET_NAME, 100), PlutusData.unit()); check("Transfer", builder(tx, alice) .withRequiredSigners(issuerAddress) .withSigner(SignerProviders.signerFrom(issuer)) .completeAndWait(CONFIRM_TIMEOUT)); } // -- Step: seize tokens without the holder's signature private void seize() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(issuer.baseAddress()) .thirdPartyTransfer(bob.baseAddress(), issuer.baseAddress(), Amount.asset(policyId, ASSET_NAME, 40), PlutusData.unit()); check("Seize", builder(tx, issuer) .withRequiredSigners(issuerAddress) .completeAndWait(CONFIRM_TIMEOUT)); } // -- Step: burn private void burn() { ProgrammableTokenTx tx = new ProgrammableTokenTx() .from(issuer.baseAddress()) .burn(policyId, List.of(new Asset(ASSET_NAME, BigInteger.valueOf(40))), BurnAuthorization.of(PlutusData.unit(), PlutusData.unit())); check("Burn", builder(tx, issuer).completeAndWait(CONFIRM_TIMEOUT)); } // -- Helpers private QuickTxBuilder.TxContext builder(Tx tx, Account sender) { return new QuickTxBuilder(backend) .withExtension(tokens.extension()) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .withTxEvaluator(new AikenTransactionEvaluator(backend)); } private void printBalances(String label) { System.out.printf("%-15s alice=%s bob=%s issuer=%s%n", label, balance(alice), balance(bob), balance(issuer)); } private BigInteger balance(Account holder) { String unit = policyId + HexUtil.encodeHexString(ASSET_NAME.getBytes(StandardCharsets.UTF_8)); return value(protocol.getProgrammableBalance(new Address(holder.baseAddress()))).stream() .filter(amount -> unit.equals(amount.getUnit())) .map(Amount::getQuantity) .reduce(BigInteger.ZERO, BigInteger::add); } private static void check(String step, TxResult result) { if (!result.isSuccessful()) throw new IllegalStateException(step + " failed: " + result.getResponse()); if (result.getTxStatus() != TxStatus.CONFIRMED) throw new IllegalStateException(step + " not confirmed (" + result.getTxStatus() + "): " + result.getValue()); System.out.printf("%-12s : %s%n", step, result.getValue()); settle(); } private static void settle() { try { Thread.sleep(SETTLE_TIME.toMillis()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new IllegalStateException(e); } } private static T value(Result result) { if (!result.isSuccessful()) throw new IllegalStateException(result.getResponse()); return result.getValue(); } } ```
On Preview, the three runs print something like this, with your own hashes. Each run starts with the `Logic script` and `Policy id` lines, which are the same every time. ```text Logic script : 88b22cd904b2aee70c4401e1946d7f9dc64f994f2cd78d63506659e7 Policy id : 6eea33c0c1b7604ff56e3906ed5b4ebac98791beec17b9d6a6eb30cb Fund alice : 31035ba6028e7503a46855b76ee670cb40e9e250d245156c6bfc9dc305363c4c Reward acct : d1eb35c75cea0267263af683b753e9f259a2b0278edc45af4baddcdcb5dfff26 Register : 1888b7ded91938a781bdaf2b190d0b0d2a7209c6c7266620c35da43276019a76 after mint alice=1000 bob=0 issuer=0 Rejected : Error while evaluating script cost Transfer : dd17f6d0ab4bd396d0e813f2266681b974ca3f97204c84ad37b34047fc11bcc4 after transfer alice=900 bob=100 issuer=0 Seize : bffa140c42094db1409d568f1bcb8f5fe56c68b4046b05a1dc155f0055d34db9 after seizure alice=900 bob=60 issuer=40 Burn : 2b93d79a3a754975507ded23ff6763bd9ef94af5ec440660722c8c3291957b25 final alice=900 bob=60 issuer=0 ``` The whole run cost about 7 tADA from the issuer and 1.6 tADA from Alice, on top of the 30 tADA the issuer sends her. You can look up each transaction hash in a [Preview explorer](https://preview.cardanoscan.io). Run 1 works once per mnemonic, because the token stays registered. To start again from run 1, create a new wallet. With the pauses, our Preview run took about ten minutes. ## Use Scalus instead of Aiken [Scalus](/smart-contracts/scalus/) can do both jobs that Aiken Java Binding does here, and it also runs on ARM64 Linux. Replace the `aiken-java-binding` dependency with `org.scalus:scalus-bloxbean-cardano-client-lib_3:1.4.1`, then change two places. Apply the parameter in `prepareLogicScript`: ```java PlutusScript unapplied = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode(COMPILED_CODE, PlutusVersion.v3); String applied = ScalusScriptUtils.applyParamsToScript(unapplied.getCborHex(), BytesPlutusData.of(issuerKeyHash)); logicScript = PlutusV3Script.builder().type("PlutusScriptV3").cborHex(applied).build(); ``` Evaluate scripts in `builder`, with `params` from `backend.getEpochService().getProtocolParameters().getValue()`: ```java .withTxEvaluator(new ScalusTransactionEvaluator( params, new DefaultUtxoSupplier(backend.getUtxoService()), new ScriptServiceSupplier(backend.getScriptService()))); ``` The parameter gives the same script hash either way, so a token registered with one tool works with the other. ## What you built | Action | Verb | Who signs | Logic that runs | |---|---|---|---| | Register and mint | `register`, `mint` | Issuer | Minting | | Transfer | `transfer` | Holder and issuer | Transfer | | Seize | `thirdPartyTransfer` | Issuer | Third-party | | Burn | `burn` | Holder (here, the issuer) | Transfer and minting | The "issuer" requirement comes from your script, not from the protocol. A different script gives you different rules, and the Java code barely changes. ## Troubleshooting | What you see | Cause | Fix | |---|---|---| | `All inputs are spent. Transaction has probably already been included` | The backend returned a UTxO that a confirmed transaction had already spent | Wait and retry, or increase `SETTLE_TIME` | | `... not confirmed (TIMEOUT): ` | The transaction didn't land within `CONFIRM_TIMEOUT`, and it may still land | Look up the hash in an explorer. Run again once it has landed, using a new wallet if the token is already registered, and increase `CONFIRM_TIMEOUT` | | `No covering registry node for policy ...` | The token is already registered, so `registerAndMint()` can't run again on this wallet. Comment out the run 1 lines, or create a new wallet. | | `Could not resolve the minting logic script ...` | The chain hasn't revealed your script, and you didn't hand it over | Call `protocol.scripts().register(script)` before building | | `ConwayWithdrawalsMissingAccounts` in the submit response | The script's reward account isn't registered | Run [Step 3](#step-3-fund-alice-and-register-the-reward-account) | | `No covering registry node for policy ...` | That policy is already registered, usually because you ran the story twice | Skip `register`, or create a new wallet | | `Error while evaluating script cost` | A script rejected the transaction, usually because the issuer isn't declared | Add `withRequiredSigners(issuerAddress)` | | `MissingVKeyWitnessesUTXOW` in the submit response | A signer was declared but didn't sign | Add the matching `withSigner(...)` | | `Not enough plain ADA to fund this transaction safely` | The sender's normal address has too little ADA in ADA-only UTxOs | Send ADA to that address; about 4.5 ADA covered a transfer in testing | | `Smart wallet ... does not hold enough` | The sender's smart wallet doesn't have those tokens | Check `getProgrammableBalance` | ## Limits - **Logic credentials must be scripts.** Registrations and updates that name a verification key are refused, so you can't create a token this library couldn't operate. - **Unfracking isn't supported.** `unfrack(...)` throws `UnsupportedOperationException` on every deployment, and none of the public deployments can unfrack. - **A third-party transaction acts on one policy,** and owner transfers and burns can't share it. - **One `Tx` per transaction.** Write all programmable-token operations of a transaction in a single `Tx`, because splitting them across `compose(tx1, tx2)` is refused. - **Each programmable output holds one policy,** and inline datums are limited by the deployment's `max_inline_datum_bytes`. - **Protocol upgrades and authority changes** aren't authored by this library. ## Next steps - Give each role its own script. For example, keep transfers open and add a denylist using `globalStateCs` on `RegistryNodeSpec`. - Read the [programmable-token README](https://github.com/bloxbean/cardano-client-lib/blob/master/programmable-token/README.md) for `thirdPartyBurn`, `updateRegistry`, inline datums and custom deployments. - Store these transactions as YAML with [TxPlan](/txplan/overview/). Programmable-token plans go through `tokens.txPlanCodec()`, not plain `TxPlan.toYaml()`. - Run several steps in sequence with [TxFlow](/txflow/overview/), registering the extension on the runtime. --- # Minting an NFT Source: https://cardano-client.dev/learn/minting-an-nft/ Summary: Mint a one-of-a-kind token with CIP-25 metadata and a time-locked policy, using QuickTx. An NFT on Cardano is a native token with a quantity of 1, whose policy can't mint any more. In this tutorial you'll create that policy, describe the token with CIP-25 metadata, mint it on the Preview testnet and find it in your wallet. **Before you start**, follow [Your first transaction](/learn/simple-transfer/) to [create an account](/learn/simple-transfer/#create-sender-and-receiver-accounts), fund it from the [faucet](/learn/simple-transfer/#topup-sender-account-with-test-ada) and get a Blockfrost project id. To use a local network instead, see [Test locally with Yaci DevKit](/learn/test-with-yaci-devkit/). The [dependencies](/start-here/installation/) are `cardano-client-lib` and `cardano-client-backend-blockfrost`. The code reads your settings from the environment: ```bash export BF_PROJECT_ID= export TUTORIAL_MNEMONIC="" ``` ## Step 1: Create the policy A token's policy decides who can mint it and when. A policy id is the hash of its script, so a new script gives you a new policy. ```java long deadline = backend.getBlockService().getLatestBlock().getValue().getSlot() + 3600; Keys policyKeys = KeyGenUtil.generateKey(); ScriptAll policyScript = new ScriptAll() .addScript(ScriptPubkey.create(policyKeys.getVkey())) .addScript(new RequireTimeBefore(deadline)); Policy policy = new Policy(policyScript, List.of(policyKeys.getSkey())); ``` `ScriptAll` means both conditions must hold: the new key signs, and the transaction is valid only before `deadline`. One slot is one second on Preview, so that's one hour from now. This is what makes the token one of a kind. A quantity of 1 alone doesn't: without the deadline, the key holder could mint the same token again. ## Step 2: Describe the token Wallets and explorers read an NFT's name and image from [CIP-25](/standards/cip25/) metadata, which you attach to the minting transaction. ```java Asset nftAsset = new Asset("MyFirstNft", BigInteger.ONE); NFT nft = NFT.create() .assetName(nftAsset.getName()) .name("My First NFT") .image("ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG") .description("Minted with Cardano Client Lib"); NFTMetadata metadata = NFTMetadata.create().addNFT(policy.getPolicyId(), nft); ``` Replace `image` with the location of your own file. This sample points to a placeholder on IPFS. ## Step 3: Mint ```java Tx tx = new Tx() .mintAssets(policy.getPolicyScript(), nftAsset, minter.baseAddress()) .attachMetadata(metadata) .from(minter.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .validTo(deadline) .withSigner(SignerProviders.signerFrom(minter)) .withSigner(SignerProviders.signerFrom(policy)) .completeAndWait(Duration.ofMinutes(5)); ``` - `validTo(deadline)` is required. The script accepts a transaction only if it declares that it expires before the deadline. - There are two signers: your account pays the fee, and the policy key authorizes the mint. - `completeAndWait` stops waiting after its timeout and then reports the status `TIMEOUT`, even though the transaction may still land. That's why the complete code below checks for `TxStatus.CONFIRMED`. ## Step 4: Find your NFT A token's unit is its policy id followed by its hex-encoded name. Ask the backend which UTxO at your address holds it: ```java String unit = AssetUtil.getUnit(policy.getPolicyId(), nftAsset); holding = backend.getUtxoService().getUtxos(minter.baseAddress(), unit, 1, 1).getValue(); ``` The complete code retries this for up to a minute, because a backend's UTxO view can lag behind a confirmed transaction. ## Run it
MintNft.java ```java package tutorial; import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.api.util.AssetUtil; import com.bloxbean.cardano.client.api.model.Utxo; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.cip.cip25.NFT; import com.bloxbean.cardano.client.cip.cip25.NFTMetadata; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.crypto.KeyGenUtil; import com.bloxbean.cardano.client.crypto.Keys; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; import com.bloxbean.cardano.client.quicktx.TxStatus; import com.bloxbean.cardano.client.transaction.spec.Asset; import com.bloxbean.cardano.client.transaction.spec.Policy; import com.bloxbean.cardano.client.transaction.spec.script.RequireTimeBefore; import com.bloxbean.cardano.client.transaction.spec.script.ScriptAll; import com.bloxbean.cardano.client.transaction.spec.script.ScriptPubkey; import java.math.BigInteger; import java.time.Duration; import java.util.List; public class MintNft { public static void main(String[] args) throws Exception { BackendService backend = new BFBackendService( Constants.BLOCKFROST_PREVIEW_URL, System.getenv("BF_PROJECT_ID")); Account minter = Account.createFromMnemonic(Networks.preview(), System.getenv("TUTORIAL_MNEMONIC")); mint(backend, minter); } public static void mint(BackendService backend, Account minter) throws Exception { // 1. The policy: this key must sign, and only before the deadline long deadline = backend.getBlockService().getLatestBlock().getValue().getSlot() + 3600; Keys policyKeys = KeyGenUtil.generateKey(); ScriptAll policyScript = new ScriptAll() .addScript(ScriptPubkey.create(policyKeys.getVkey())) .addScript(new RequireTimeBefore(deadline)); Policy policy = new Policy(policyScript, List.of(policyKeys.getSkey())); // 2. The token and its CIP-25 metadata Asset nftAsset = new Asset("MyFirstNft", BigInteger.ONE); NFT nft = NFT.create() .assetName(nftAsset.getName()) .name("My First NFT") .image("ipfs://QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG") .description("Minted with Cardano Client Lib"); NFTMetadata metadata = NFTMetadata.create().addNFT(policy.getPolicyId(), nft); // 3. The transaction Tx tx = new Tx() .mintAssets(policy.getPolicyScript(), nftAsset, minter.baseAddress()) .attachMetadata(metadata) .from(minter.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .validTo(deadline) .withSigner(SignerProviders.signerFrom(minter)) .withSigner(SignerProviders.signerFrom(policy)) .completeAndWait(Duration.ofMinutes(5)); if (result.getTxStatus() != TxStatus.CONFIRMED) throw new IllegalStateException("Not confirmed (" + result.getTxStatus() + "): " + result.getResponse()); System.out.println("Tx hash : " + result.getTxHash()); // 4. Find the token in the minter's wallet String unit = AssetUtil.getUnit(policy.getPolicyId(), nftAsset); List holding = List.of(); for (int attempt = 0; attempt < 12 && holding.isEmpty(); attempt++) { Thread.sleep(5_000); holding = backend.getUtxoService().getUtxos(minter.baseAddress(), unit, 1, 1).getValue(); } if (holding.isEmpty()) throw new IllegalStateException("Token not found yet: " + unit); BigInteger quantity = holding.get(0).getAmount().stream() .filter(amount -> unit.equals(amount.getUnit())) .map(Amount::getQuantity) .findFirst().orElseThrow(); System.out.println("Policy id : " + policy.getPolicyId()); System.out.println("Quantity : " + quantity); } } ```
Running it on Preview prints something like this, with your own values: ```text Tx hash : c7050d3519d6d1514c75531e9aa0eb0697d5918882de03d57636245c0b5c1693 Policy id : 787db0464ad195b5c5d0586c3fabfad3d144f6c68e66d1238cf0fef6 Quantity : 1 ``` To see the metadata, look up your transaction hash in a [Preview explorer](https://preview.cardanoscan.io). ## Next steps - [Multi-sig transfers](/learn/multisig/): the next step in this path, with a script that needs several signatures. - [CIP-68](/standards/cip68/): NFTs whose metadata can be updated. - [cardano-client-examples](https://github.com/bloxbean/cardano-client-examples) has more minting samples, such as [minting with QuickTx](https://github.com/bloxbean/cardano-client-examples/blob/main/src/test/java/com/bloxbean/cardano/client/example/quicktx/Minting.java) and [minting with several scripts](https://github.com/bloxbean/cardano-client-examples/blob/main/src/test/java/com/bloxbean/cardano/client/example/quicktx/MultiScriptMinting.java). They aren't updated with every release. --- # Multi-sig transfers Source: https://cardano-client.dev/learn/multisig/ Summary: Example - Multi-signature transfer with native script ## Overview In this guide, we will go through an example of multi-sig transaction using native script. The example will demonstrate the steps required to create a multi-sig script and claim fund from the script address. Let's go through some theory first. Click [here](#simple-transfer-from-a-multi-sig-script-address) to directly jump to the example section. ## What's Native Script or Multi-Sig Script ? A native script is a set of rules that defines how you can spend a UTxO. Native scripts are used to make script addresses where the authorisation condition for a transaction to use that address is that the transaction has signatures from multiple cryptographic keys. Examples include M of N schemes, where a transaction can be authorized if at least M distinct keys, from a set of N keys, sign the transaction. Some key points about native scripts in Cardano - A native script can be encoded as a json text file - It uses a simple language for expressing witness requirements for spending an output, minting or burning transaction - Keys are identified in the script by hash of public key - It also provides support for time-locking, so the script is valid for a specific time range The simplest native script requires just one key **Example:** ```shell { "type": "sig", "keyHash": "e09d36c79dec9bd1b3d9e152247701cd0bb860b5ebfd1de8abb6735a" } ``` ### Types There are six supported constructors in a multi-sig script. The following section briefly explains about each constructor and corresponding **type** in json file. 1. **RequireSignature**: has the hash of a verification key. This expression evaluates to true if the transaction is signed by a particular key, identified by its verification key hash. **Type:** "sig" 2. **RequireAllOf:** has a list of multisig sub-expressions. This expression evaluates to true if (and only if) all the sub-expressions evaluate to true. **Type:** "all" 3. **RequireAnyOf:** has a list of multisig sub-expressions. This expression evaluates to true if (and only if) any the sub-expressions evaluate to true. **Type:** "any" 4. **RequireMOf:** has a number M and a list of multisig sub-expressions. This expression evaluates to true if (and only if) at least M of the sub-expressions evaluate to true. **Type:** "atLeast" 5. **RequireTimeBefore:** has a slot number X. This condition guarantees that the actual slot number in which the transaction is included is (strictly) less than slot number X. **Type:** "before" 6. **RequireTimeAfter:** has a slot number X. This condition guarantees that the actual slot number in which the transaction is included is greater than or equal to slot number X. **Type:** "after" > > **Cardano Client Lib** provides apis and helpers to create and manage native scripts in Java. ## Simple Transfer from a multi-sig script address In this example we are going to simulate a joint account use case using a multi-sig script. So a muti-sig account between three parties which specifies that at least any two members need to sign to spend fund from the account. In next few sections, we are going to - Create a multi-sig script with three key hashes (2 regular accounts, 1 payment key pair(skey & vkey)) - Create script address for the multi-sig script - Top up multi-sig address with some fund - Claim fund from the multi-sig address and distribute to two regular accounts ## Accounts Setup We will use 2 regular accounts and 1 payment key pair (Ed25519 keys) for our example. > > The payment key pair is not mandatory, but it is included to demonstrate the transaction signing capability using ``SecretKey``. To spend fund from our multi-sig script address, we need to sign the transaction with at least 2 keys. Any of the below should work for our use case - Sign with 2 regular accounts - Sign with 1 regular account and payment secret key - Sign with 2 regular account and payment secret key To create two new regular accounts, check [here](/learn/simple-transfer/#create-sender-and-receiver-accounts). 1. Create **account-1** and **account-2** from existing mnemonics ```java String account1Mnemonic = "<24 words mnemonic>"; Account account1 = Account.createFromMnemonic(Networks.testnet(), account1Mnemonic); String account2Mnemonic = "<24 words mnemonic>"; Account account2 = Account.createFromMnemonic(Networks.testnet(), account2Mnemonic); ``` 2. Create a new payment key pair (Ed25519 secret key / verification key) ```java Keys keys = KeyGenUtil.generateKey(); VerificationKey verificationKey = keys.getVkey(); SecretKey secretKey = keys.getSkey(); String paymentSigningKeyCborHex = secretKey.getCborHex(); ``` **Alternatively,** you can create a ``SecretKey`` and ``VerificationKey`` from an existing payment secret key. ```java String paymentSigningKeyCborHex = "58205d9ccc4202bde1785708c10f8b13231d5a57078c326d0e0ff172191f975a983e"; SecretKey secretKey = new SecretKey(paymentSigningKeyCborHex); VerificationKey verificationKey = KeyGenUtil.getPublicKeyFromPrivateKey(secretKey); ``` ## Create Multi-sig script with 2 regular accounts and 1 payment key pair We are going to create a multi-sig script with "**RequireMOf**" constructor or "**atLeast**" type. For our example, we first need to create verification keys for account1 and account2 using their public key. These verification keys, including the one from payment key pair, are then used to create corresponding ``ScriptPubKey`` (sig type native script) instances. Finally, all three ``ScriptPubKey`` instances can be composed to build a ``ScriptAtLeast`` instance. 1. Create ``VerificationKey`` instances using public keys of account1 and account2. Use verification keys to create ``ScriptPubKey`` instances. ```java VerificationKey account1Vk = VerificationKey.create(account1.publicKeyBytes()); VerificationKey account2Vk = VerificationKey.create(account2.publicKeyBytes()); ScriptPubkey scriptPubkey1 = ScriptPubkey.create(account1Vk); ScriptPubkey scriptPubkey2 = ScriptPubkey.create(account2Vk); ``` 2. Use ``VerificationKey`` of payment key pair (3rd key) to create the third ``ScriptPubKey`` ```java ScriptPubkey scriptPubkey3 = ScriptPubkey.create(verificationKey); ``` 3. Now we can use the above ``ScriptPubKey`` instances to create a multi-sig "**atLeast**" instance ```java ScriptAtLeast scriptAtLeast = new ScriptAtLeast(2) .addScript(scriptPubkey1) .addScript(scriptPubkey2) .addScript(scriptPubkey3); ``` > > If you convert ``scriptAtLeast`` object to json, you will see something similar > > ```json > { > "type" : "atLeast", > "required" : 2, > "scripts" : [ { > "type" : "sig", > "keyHash" : "0d30c6d716fd6c48ab546f0b66fd5faaa3a2f0ccecf0a72ea8c04a30" > }, { > "type" : "sig", > "keyHash" : "1737dd414cf68676312db8af317fc89167292302b97d65a2d1def5a2" > }, { > "type" : "sig", > "keyHash" : "cdba7678210358a58160183551f23589fb68ca0f71cd74ce776257fe" > } ] > } > ``` ## Create Script Address Now let's use ``AddressProvider`` to generate a script address from our muti-sig native script. ```java String scriptAddress = AddressProvider.getEntAddress(scriptAtLeast, Networks.testnet()).toBech32(); ``` ## Topup script address with test Ada Based on your selected network (**preprod** / **preview**), get some test Ada from the below faucet. You need to provide ``scriptAddress`` generated in the previous section to get some test Ada. https://docs.cardano.org/cardano-testnets/tools/faucet On [Yaci DevKit](/learn/test-with-yaci-devkit/#fund-an-account), use `topup 100` instead. ## Create a Backend Service Please check [here](/learn/simple-transfer/#create-backend-service) to create a ``BackendService`` instance. ## Claim fund from script address - Using QuickTx API Now we are ready to claim fund from the script address using the **QuickTx API**. ### Create QuickTxBuilder ```java QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); ``` ### Define the Transaction Use the ``Tx`` class to define the transaction. Specify the outputs, attach the native script, and set the script address as the sender. With QuickTx, you don't need to manually add the script to the witness set or specify the number of signers for fee calculation — ``attachNativeScript`` handles the witness and QuickTx handles balancing automatically. ```java String address1 = account1.baseAddress(); String address2 = account2.baseAddress(); Tx tx = new Tx() .payToAddress(address1, Amount.ada(25)) .payToAddress(address2, Amount.ada(25)) .attachNativeScript(scriptAtLeast) .from(scriptAddress); ``` ### Build, Sign, and Submit Sign the transaction with at least 2 keys as required by the multi-sig script. In this example, we sign with ``account1`` and ``secretKey`` (the third key). ```java TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account1)) .withSigner(SignerProviders.signerFrom(secretKey)) .completeAndWait(System.out::println); ``` > Alternatively, you can sign with ``account1`` and ``account2``, or with all three keys. `completeAndWait` waits up to 60 seconds by default. `result.getTxStatus()` is `CONFIRMED` once the transaction is on-chain. It can also be `PENDING` or `TIMEOUT`, in which case the transaction may still land, so check the status. `result.getTxHash()` holds the transaction hash. To check the details on a public network, look the hash up in an explorer for [Preview](https://preview.cardanoscan.io) or [Preprod](https://preprod.cardanoscan.io). ### Full Source Code ```java showLineNumbers import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.address.AddressProvider; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.crypto.KeyGenUtil; import com.bloxbean.cardano.client.crypto.SecretKey; import com.bloxbean.cardano.client.crypto.VerificationKey; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; import com.bloxbean.cardano.client.transaction.spec.script.ScriptAtLeast; import com.bloxbean.cardano.client.transaction.spec.script.ScriptPubkey; public class MultiSigTransfer { public void transfer() throws Exception { //First account String account1Mnemonic = "turkey elder dad veteran they lumber feature garment race answer file erase riot resist sting process law deputy turtle foil legal calm exist civil"; Account account1 = Account.createFromMnemonic(Networks.testnet(), account1Mnemonic); //Second account String account2Mnemonic = "report crowd trophy rough twin column access include evolve awkward world random bounce pave select rocket vote junk farm group main boat tissue mad"; Account account2 = Account.createFromMnemonic(Networks.testnet(), account2Mnemonic); //Third account payment key String paymentSigningKeyCborHex = "58205d9ccc4202bde1785708c10f8b13231d5a57078c326d0e0ff172191f975a983e"; SecretKey secretKey = new SecretKey(paymentSigningKeyCborHex); VerificationKey verificationKey = KeyGenUtil.getPublicKeyFromPrivateKey(secretKey); //Derive verification key for account1 and account2 VerificationKey account1Vk = VerificationKey.create(account1.publicKeyBytes()); VerificationKey account2Vk = VerificationKey.create(account2.publicKeyBytes()); //Create native script with type=sig for each verification key ScriptPubkey scriptPubkey1 = ScriptPubkey.create(account1Vk); ScriptPubkey scriptPubkey2 = ScriptPubkey.create(account2Vk); ScriptPubkey scriptPubkey3 = ScriptPubkey.create(verificationKey); //Create multi-sig script with type "atLeast" ScriptAtLeast scriptAtLeast = new ScriptAtLeast(2) .addScript(scriptPubkey1) .addScript(scriptPubkey2) .addScript(scriptPubkey3); //addr_test1wr6fvn0y3rumu30ch3lrggss4vmgsr65cxly2t6dulvwamq3y98et String scriptAddress = AddressProvider.getEntAddress(scriptAtLeast, Networks.testnet()).toBech32(); // For Blockfrost String bf_projectId = "preprod..."; BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREPROD_URL, bf_projectId); // For Koios //BackendService backendService = new KoiosBackendService(Constants.KOIOS_PREVIEW_URL); // Create QuickTxBuilder QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); // Define transaction String address1 = account1.baseAddress(); String address2 = account2.baseAddress(); Tx tx = new Tx() .payToAddress(address1, Amount.ada(25)) .payToAddress(address2, Amount.ada(25)) .attachNativeScript(scriptAtLeast) .from(scriptAddress); // Build, sign, submit and wait for confirmation TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account1)) .withSigner(SignerProviders.signerFrom(secretKey)) .completeAndWait(System.out::println); System.out.println(result.getTxStatus() + " " + result.getTxHash()); } public static void main(String[] args) throws Exception { new MultiSigTransfer().transfer(); } } ``` ### What's next ? Update the multi-sig script to support time-lock. For example, evaluate to true if minimum 2 signatures and slot number is after X. **Hint:** Use **RequireTimeAfter** with **RequireMOf** to create multi-sig script. :::tip[Why QuickTx?] QuickTx is the recommended way to build transactions. The lower-level [composable functions](/reference/composable-functions/) API can express the same multi-sig transfer, but it is the internal implementation layer — reach for it only when QuickTx cannot serve your case. ::: --- # Native tokens Source: https://cardano-client.dev/learn/native-tokens/ Summary: Create your own token with a native script policy, then send it to several receivers in one transaction. Cardano tracks tokens in the ledger itself, so you don't need a smart contract to hold or send them. In this tutorial you'll mint your own token and send it to three receivers in a single transaction. **Before you start**, [start Yaci DevKit](/learn/test-with-yaci-devkit/) and fund an account, or use an account you funded on Preview. The [dependencies](/start-here/installation/) are `cardano-client-lib` and `cardano-client-backend-blockfrost`. ## Step 1: Create the policy A token's identity has two parts: a policy id and an asset name. The policy is a script that decides who can mint. Its hash is the policy id. ```java Keys policyKeys = KeyGenUtil.generateKey(); ScriptPubkey policy = ScriptPubkey.create(policyKeys.getVkey()); String policyId = policy.getPolicyId(); ``` This policy has one rule: the new key must sign. Whoever holds the key can mint more of this token later, so save it if you want to do that: ```java String policyKeyHex = policyKeys.getSkey().getCborHex(); ``` To cap the supply, add a deadline to the policy, as in [Minting an NFT](/learn/minting-an-nft/). ## Step 2: Mint ```java Asset token = new Asset("TutorialToken", BigInteger.valueOf(1_000_000)); Tx mint = new Tx() .mintAssets(policy, token, sender.baseAddress()) .from(sender.baseAddress()); TxResult minted = new QuickTxBuilder(backend) .compose(mint) .withSigner(SignerProviders.signerFrom(sender)) .withSigner(SignerProviders.signerFrom(policyKeys.getSkey())) .completeAndWait(Duration.ofMinutes(5)); ``` There are two signers: your account pays the fee, and the policy key authorizes the mint. Quantities on-chain are whole numbers. A token with "6 decimals" is a display choice: 1,500,000 units appear as 1.5 in wallets that know about the decimals. ## Step 3: Send it to several receivers One Cardano transaction can have many outputs, so this costs one fee and either all of it happens or none does. A token's unit is its policy id followed by its hex-encoded name. ```java String unit = AssetUtil.getUnit(policyId, token); Tx distribute = new Tx() .payToAddress(receiver1, Amount.asset(unit, 100)) .payToAddress(receiver2, Amount.asset(unit, 250)) .payToAddress(receiver3, Amount.asset(unit, 50)) .from(sender.baseAddress()); TxResult sent = new QuickTxBuilder(backend) .compose(distribute) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(Duration.ofMinutes(5)); ``` Every output on Cardano must carry a minimum amount of ada. You don't need to work this out: QuickTx adds it to each output, paid by the sender. For a long list of receivers, call `payToAddress` in a loop. A transaction has a size limit (16 KB on current networks), so split very long lists across several transactions. ## Run it
TokenDistribution.java ```java package tutorial; import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.api.model.Utxo; import com.bloxbean.cardano.client.api.util.AssetUtil; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.crypto.KeyGenUtil; import com.bloxbean.cardano.client.crypto.Keys; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; import com.bloxbean.cardano.client.quicktx.TxStatus; import com.bloxbean.cardano.client.transaction.spec.Asset; import com.bloxbean.cardano.client.transaction.spec.script.ScriptPubkey; import java.math.BigInteger; import java.time.Duration; import java.util.List; public class TokenDistribution { public static void main(String[] args) throws Exception { // Yaci DevKit. For Preview, see "Move to a public network" on the DevKit page. BackendService backend = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); Account sender = Account.createFromMnemonic(Networks.testnet(), System.getenv("TUTORIAL_MNEMONIC")); String receiver1 = new Account(Networks.testnet()).baseAddress(); String receiver2 = new Account(Networks.testnet()).baseAddress(); String receiver3 = new Account(Networks.testnet()).baseAddress(); // 1. The policy: this key must sign Keys policyKeys = KeyGenUtil.generateKey(); ScriptPubkey policy = ScriptPubkey.create(policyKeys.getVkey()); String policyId = policy.getPolicyId(); // 2. Mint to the sender Asset token = new Asset("TutorialToken", BigInteger.valueOf(1_000_000)); Tx mint = new Tx() .mintAssets(policy, token, sender.baseAddress()) .from(sender.baseAddress()); TxResult minted = new QuickTxBuilder(backend) .compose(mint) .withSigner(SignerProviders.signerFrom(sender)) .withSigner(SignerProviders.signerFrom(policyKeys.getSkey())) .completeAndWait(Duration.ofMinutes(5)); check("Mint", minted); // A backend's UTxO view can lag behind a confirmed transaction String unit = AssetUtil.getUnit(policyId, token); waitForToken(backend, sender.baseAddress(), unit); // 3. Send to three receivers in one transaction Tx distribute = new Tx() .payToAddress(receiver1, Amount.asset(unit, 100)) .payToAddress(receiver2, Amount.asset(unit, 250)) .payToAddress(receiver3, Amount.asset(unit, 50)) .from(sender.baseAddress()); TxResult sent = new QuickTxBuilder(backend) .compose(distribute) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(Duration.ofMinutes(5)); check("Distribute", sent); System.out.println("Unit: " + unit); System.out.println("Receiver 2 holds: " + waitForToken(backend, receiver2, unit)); } static void check(String step, TxResult result) { if (result.getTxStatus() != TxStatus.CONFIRMED) throw new IllegalStateException(step + " not confirmed (" + result.getTxStatus() + "): " + result.getResponse()); System.out.println(step + ": " + result.getTxHash()); } static BigInteger waitForToken(BackendService backend, String address, String unit) throws Exception { for (int attempt = 0; attempt < 12; attempt++) { List holding = backend.getUtxoService().getUtxos(address, unit, 1, 1).getValue(); if (holding != null && !holding.isEmpty()) return holding.get(0).getAmount().stream() .filter(amount -> unit.equals(amount.getUnit())) .map(Amount::getQuantity) .findFirst().orElseThrow(); Thread.sleep(5_000); } throw new IllegalStateException("Token not found yet: " + unit); } } ```
Set `TUTORIAL_MNEMONIC` to the words of a funded account. On DevKit, you can use the [default mnemonic](/learn/test-with-yaci-devkit/#fund-an-account). The output has this shape, with your own values: ```text Mint: Distribute: Unit: 5475746f7269616c546f6b656e Receiver 2 holds: 250 ``` ## Next - [Minting an NFT](/learn/minting-an-nft/): a token with a quantity of 1, a time-locked policy and metadata. - [CIP-25](/standards/cip25/) and [CIP-68](/standards/cip68/): the token metadata standards. --- # Calling a Plutus script Source: https://cardano-client.dev/learn/plutus-scripts/ Summary: Lock ADA at a Plutus script and unlock it with a redeemer, using QuickTx. A Plutus script can hold funds, and it runs when someone tries to spend them. In this tutorial you'll lock 5 tADA at a script on the Preview testnet, then unlock them. This code uses the [latest preview](/start-here/installation/#versions), where script operations are on `Tx`. On stable 0.7.3, use [`ScriptTx`](/quicktx/overview/#scripttx-plutus-aware) for the script transaction, with `.feePayer(...)` on the builder. **Before you start**, follow [Your first transaction](/learn/simple-transfer/) to [create an account](/learn/simple-transfer/#create-sender-and-receiver-accounts), fund it from the [faucet](/learn/simple-transfer/#topup-sender-account-with-test-ada) and get a Blockfrost project id. The [dependencies](/start-here/installation/) are `cardano-client-lib` and `cardano-client-backend-blockfrost`. The code reads your settings from the environment: ```bash export BF_PROJECT_ID= export TUTORIAL_MNEMONIC="" ``` ## Step 1: Load the script This tutorial uses the smallest possible validator, which approves every transaction. It lets you focus on the transaction shape. ```java PlutusV3Script script = PlutusV3Script.builder() .type("PlutusScriptV3") .cborHex("46450101002499") .build(); String scriptAddress = AddressProvider.getEntAddress(script, Networks.preview()).toBech32(); ``` :::caution[Anyone can spend from this script] Because it approves everything, anyone can take what you lock here. Use small testnet amounts only. A real validator enforces rules, like the one in the [CIP-113 tutorial](/learn/cip113-programmable-tokens/#step-2-write-the-tokens-rule). ::: ## Step 2: Lock funds Locking is an ordinary payment to the script's address, with a *datum*: data stored with the funds for the script to read later. ```java PlutusData datum = BigIntPlutusData.of(42); Tx tx = new Tx() .payToContract(scriptAddress, Amount.ada(5), datum) .from(user.baseAddress()); ``` No script runs at this point. Scripts run only when funds are spent. ## Step 3: Unlock funds Spending from a script takes a *redeemer*, which is the input you give the script, and the script itself. ```java Utxo locked = backend.getUtxoService().getTxOutput(lockTxHash, 0).getValue(); PlutusData redeemer = BigIntPlutusData.of(7); Tx tx = new Tx() .collectFrom(locked, redeemer) .payToAddress(user.baseAddress(), Amount.ada(5)) .attachSpendingValidator(script); TxResult result = new QuickTxBuilder(backend) .compose(tx) .feePayer(user.baseAddress()) .collateralPayer(user.baseAddress()) .withSigner(SignerProviders.signerFrom(user)) .completeAndWait(CONFIRM_TIMEOUT); ``` - `getTxOutput(lockTxHash, 0)` reads the exact output you created, since other people's funds sit at this shared address too. The payment is the transaction's first output. - `feePayer` is needed because the transaction has no `from`. Your account pays the fee. - `collateralPayer` names who provides collateral. If a script fails on chain, the network takes collateral instead of a fee. You lose it only in that case. The complete code pauses for a minute between the two transactions. Even after a transaction is confirmed, a backend's UTxO view can lag behind it, so the unlock could otherwise pick an input that was just spent. ## Run it
CallPlutusScript.java ```java package tutorial; import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.address.AddressProvider; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.api.model.Utxo; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.plutus.spec.BigIntPlutusData; import com.bloxbean.cardano.client.plutus.spec.PlutusData; import com.bloxbean.cardano.client.plutus.spec.PlutusV3Script; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; import com.bloxbean.cardano.client.quicktx.TxStatus; import java.time.Duration; public class CallPlutusScript { private static final Duration CONFIRM_TIMEOUT = Duration.ofMinutes(5); private static final Duration SETTLE_TIME = Duration.ofSeconds(60); public static void main(String[] args) throws Exception { BackendService backend = new BFBackendService( Constants.BLOCKFROST_PREVIEW_URL, System.getenv("BF_PROJECT_ID")); Account user = Account.createFromMnemonic(Networks.preview(), System.getenv("TUTORIAL_MNEMONIC")); run(backend, user); } public static void run(BackendService backend, Account user) throws Exception { PlutusV3Script script = PlutusV3Script.builder() .type("PlutusScriptV3") .cborHex("46450101002499") .build(); String scriptAddress = AddressProvider.getEntAddress(script, Networks.preview()).toBech32(); System.out.println("Script address: " + scriptAddress); String lockTxHash = lock(backend, user, scriptAddress); Thread.sleep(SETTLE_TIME.toMillis()); unlock(backend, user, script, lockTxHash); } private static String lock(BackendService backend, Account user, String scriptAddress) { PlutusData datum = BigIntPlutusData.of(42); Tx tx = new Tx() .payToContract(scriptAddress, Amount.ada(5), datum) .from(user.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(user)) .completeAndWait(CONFIRM_TIMEOUT); return confirmed("Lock", result); } private static void unlock(BackendService backend, Account user, PlutusV3Script script, String lockTxHash) throws Exception { Utxo locked = backend.getUtxoService().getTxOutput(lockTxHash, 0).getValue(); PlutusData redeemer = BigIntPlutusData.of(7); Tx tx = new Tx() .collectFrom(locked, redeemer) .payToAddress(user.baseAddress(), Amount.ada(5)) .attachSpendingValidator(script); TxResult result = new QuickTxBuilder(backend) .compose(tx) .feePayer(user.baseAddress()) .collateralPayer(user.baseAddress()) .withSigner(SignerProviders.signerFrom(user)) .completeAndWait(CONFIRM_TIMEOUT); confirmed("Unlock", result); } private static String confirmed(String step, TxResult result) { if (result.getTxStatus() != TxStatus.CONFIRMED) throw new IllegalStateException(step + " not confirmed (" + result.getTxStatus() + "): " + result.getResponse()); System.out.println(step + " tx: " + result.getTxHash()); return result.getTxHash(); } } ```
Running it on Preview prints something like this, with your own hashes: ```text Script address: addr_test1wqvxuvh64q9zdqgrjt76d42eclk5wgdxtnsun4808cwg0dqxy2mj0 Lock tx: 812e9ca565a2ef20accc92c5cf074879873fe2906a9cbdaaa72ffdcfc7fe33d8 Unlock tx: 0f5465e37d25ecdbf037b1a446fc8f588cab90082a7380db4c5fbc86efcdbae8 ``` ## Evaluate scripts locally To size a transaction, the library needs to know how much CPU and memory its scripts use. By default it asks the backend. You can run the scripts in your own process instead, which also shows a script failure before you submit. Add one of these to the unlock's builder chain. [Aiken Java Binding](/smart-contracts/aiken/): ```java .withTxEvaluator(new AikenTransactionEvaluator(backend)) ``` [Scalus](/smart-contracts/scalus/), which also runs on ARM64 Linux: ```java ProtocolParams params = backend.getEpochService().getProtocolParameters().getValue(); UtxoSupplier utxos = new DefaultUtxoSupplier(backend.getUtxoService()); ``` ```java .withTxEvaluator(new ScalusTransactionEvaluator(SlotConfig.preview(), params, utxos)) ``` ## Next steps - [Plutus API](/smart-contracts/plutus-api/): load scripts from a blueprint and build datums and redeemers. - [Blueprint code generation](/smart-contracts/blueprint-codegen/): generate Java classes from your validator. - [CIP-113 programmable tokens](/learn/cip113-programmable-tokens/): a real validator in a complete application. - [cardano-client-examples](https://github.com/bloxbean/cardano-client-examples) has more samples, such as [an always-true script with QuickTx](https://github.com/bloxbean/cardano-client-examples/blob/main/src/test/java/com/bloxbean/cardano/client/example/quicktx/AlwaysTrueScriptTest.java). They aren't updated with every release. --- # Your first transaction Source: https://cardano-client.dev/learn/simple-transfer/ Summary: A simple ada transfer example. # Overview In this section, we will go through the steps required to do a simple Ada transfer from a sender account to two receiver addresses. ## Select a Network and Provider First we need to select a network for our transaction. You can choose one of the available public test network. - **Preprod** - **Preview** - **Local Devnet (Yaci DevKit)** — for local development and testing Similarly, choose a backend provider to interact with Cardano blockchain. You can select either Koios or Blockfrost as backend provider. Please check [dependencies](/start-here/installation/) page to find the required dependency for your selected backend provider. > > For **Blockfrost** as backend provider, you need to first create an account on [blockfrost.io](https://blockfrost.io) and get > a ``Project Id`` for the selected network. > > For **Koios** backend provider, you don't need any registration. > For **Yaci DevKit** as a local development environment, install and run > [Yaci DevKit](https://devkit.yaci.xyz/#get-started). It provides a local Cardano devnet with > built-in Yaci Store that exposes Blockfrost-compatible APIs. No registration or API key is required. ## Create Sender and Receiver accounts We need three accounts for this example, a sender account and two receiver accounts. As we are going to use one of the test network, the following code will generate three testnet addresses. ```java Account senderAccount = new Account(Networks.testnet()); String senderAddress = senderAccount.baseAddress(); String senderMnemonic = senderAccount.mnemonic(); Account receiverAccount1 = new Account(Networks.testnet()); String receiverAddress1 = receiverAccount1.baseAddress(); Account receiverAccount2 = new Account(Networks.testnet()); String receiverAddress2 = receiverAccount2.baseAddress(); ``` If you already have mnemonic for an existing account, you can create a sender account from the mnemonic. For this example, we just need sender account's mnemonic. ```java String senderMnemonic = "<24 words mnemonic>"; Account senderAccount = Account.createFromMnemonic(Networks.testnet(), senderMnemonic); ``` Similarly, we need two receiver addresses to receive some ada. Unlike other account-based blockchains, Cardano supports multiple outputs in a single transaction. So let's define two receiving addresses. ```java String receiverAddress1 = "addr_test..."; String receiverAddress2 = "addr_test..."; ``` > > Two types of address can be generated, mainnet address or testnet address. > > To generate a test network address, you can use any of the network constant ``Networks.testnet()``, ``Networks.preprod()`` or > ``Networks.preview()``. The generated testnet address can be used on any of the test network. > **(The address generation depends on the NetworkId in Network object not protocol magic. These public test networks have same network id (0))** > > For mainnet address, you need to use ``Networks.mainnet()`` ## Topup sender account with test Ada Based on your selected network, get some test Ada from one of the below faucet. You need to provide ``senderAddress`` generated in the previous section to get some test Ada. https://docs.cardano.org/cardano-testnets/tools/faucet On Yaci DevKit, [use its funded accounts or top-up command](/learn/test-with-yaci-devkit/#fund-an-account) instead. ## Create Backend Service **For Blockfrost :** Use the correct Blockfrost url for the selected network and project id to create an instance of BackendService. ```java String bfProjectId = "preprod..."; BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREPROD_URL, bfProjectId); ``` **Note:** You can find Blockfrost urls for the supported networks in ``com.bloxbean.cardano.client.backend.blockfrost.common.Constants``. or, **For Koios :** ```java BackendService backendService = new KoiosBackendService(Constants.KOIOS_PREVIEW_URL); ``` **Note:** You can find other Koios urls in ``com.bloxbean.cardano.client.backend.koios.Constants`` or, **For Yaci DevKit (Local Devnet) :** ```java BackendService backendService = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); ``` **Note:** Yaci DevKit provides a local Cardano devnet with funded accounts and a top-up command, so you don't need the public faucet. See [Test locally with Yaci DevKit](/learn/test-with-yaci-devkit/). ## Simple Transfer - Using QuickTx API The **QuickTx API** provides a simple and declarative way to build, sign, and submit transactions. It handles fee calculation, balancing, and change output automatically. ### Create QuickTxBuilder First, create a ``QuickTxBuilder`` instance from the ``BackendService``. ```java QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); ``` ### Create a transaction message metadata (Optional) Let's create a [CIP20](https://cips.cardano.org/cips/cip20/) compliant metadata to add a message to the transaction. This is optional, but it demonstrates how metadata can be attached to a transaction. ```java MessageMetadata metadata = MessageMetadata.create() .add("First transfer transaction"); ``` ### Define the Transaction Use the ``Tx`` class to define the transaction. You can specify multiple outputs, attach metadata, and set the sender address. ```java Tx tx = new Tx() .payToAddress(receiverAddress1, Amount.ada(10)) .payToAddress(receiverAddress2, Amount.ada(20)) .attachMetadata(metadata) .from(senderAddress); ``` ### Build, Sign, and Submit Use ``QuickTxBuilder`` to compose the transaction, attach a signer, and submit it. The ``completeAndWait`` method builds, signs and submits the transaction, then waits for it to reach the chain. ```java TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(senderAccount)) .completeAndWait(System.out::println); ``` `completeAndWait` waits up to 60 seconds by default. `result.getTxStatus()` is `CONFIRMED` once the transaction is on-chain. It can also be `PENDING` or `TIMEOUT`, in which case the transaction may still land, so check the status. `result.getTxHash()` holds the transaction hash. ### Full Source Code ```java showLineNumbers import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.common.Constants; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.cip.cip20.MessageMetadata; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; public class SimpleTransfer { public void transfer() throws Exception { //Sender account String senderMnemonic = "<24 words mnemonic>"; Account senderAccount = Account.createFromMnemonic(Networks.testnet(), senderMnemonic); String senderAddress = senderAccount.baseAddress(); //Addresses to receive ada String receiverAddress1 = "addr_test1qpjs693nk7makhcax3k7h0hkjyye2adwv3e300dkfwpqj8k2le4j5lg6gd773gdvs7jcnwdxvtztmxawwcdmvm0h870sardwde"; String receiverAddress2 = "addr_test1qzvy33rr24huuqv46ajex99hrcl0dauqcch7meznf4mdyd4sqwzjy5gaynruuwtdmwmdlnasa8t2g2t0fqmf8rhq3e6svxzum4"; // For Blockfrost String bf_projectId = ""; BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, bf_projectId); // For Koios // BackendService backendService = new KoiosBackendService(Constants.KOIOS_PREVIEW_URL); // For Yaci DevKit (Local Devnet) // BackendService backendService = // new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); // Create QuickTxBuilder QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); // Create a CIP20 message metadata MessageMetadata metadata = MessageMetadata.create() .add("First transfer transaction"); // Define transaction Tx tx = new Tx() .payToAddress(receiverAddress1, Amount.ada(10)) .payToAddress(receiverAddress2, Amount.ada(20)) .attachMetadata(metadata) .from(senderAddress); // Build, sign, submit and wait for confirmation TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(senderAccount)) .completeAndWait(System.out::println); System.out.println(result.getTxStatus() + " " + result.getTxHash()); } public static void main(String[] args) throws Exception { new SimpleTransfer().transfer(); } } ``` :::tip[Why QuickTx?] QuickTx is the recommended way to build transactions. The lower-level [composable functions](/reference/composable-functions/) API can express the same transfer, but it is the internal implementation layer — reach for it only when QuickTx cannot serve your case. ::: ## Next [Test locally with Yaci DevKit](/learn/test-with-yaci-devkit/) gives you a local network with instant test funds, for the tutorials that follow. --- # Staking & delegation Source: https://cardano-client.dev/learn/staking-and-delegation/ Summary: Register a stake address, delegate to a pool, withdraw rewards and deregister — the full staking lifecycle with QuickTx. Staking is where most Cardano applications first meet **certificates**. A certificate is not a payment; it is an instruction to the ledger that changes the state of a stake address. QuickTx builds them the same way it builds payments — as calls on `Tx` — but they carry one extra requirement you have to get right: **which keys must sign**. By the end of this page you will have registered a stake address, delegated it to a pool, withdrawn rewards, and deregistered it to reclaim the deposit. **Before you start**: finish [Your first transaction](/learn/simple-transfer/). You need a funded account on Preview or a [local devnet](/learn/test-with-yaci-devkit/). On a default Yaci DevKit devnet, you can delegate to its pool, `pool1wvqhvyrgwch4jq9aa84hc8q4kzvyq2z3xr6mpafkqmx9wce39zy`. ## The staking lifecycle Four operations, in order: 1. **Register** the stake address — pays a refundable deposit (2 ADA on mainnet) 2. **Delegate** it to a stake pool — takes effect two epochs later 3. **Withdraw** rewards as they accumulate, which also needs a vote delegation 4. **Deregister** when you are done — refunds the deposit Steps 1 and 2 are usually done in a single transaction. ## An account's two addresses Every `Account` carries both a payment and a staking credential: ```java Account account = Account.createFromMnemonic(Networks.preview(), mnemonic); account.baseAddress(); // addr_test1... — holds funds, pays fees account.stakeAddress(); // stake_test1... — accrues rewards, delegates ``` The **base address** already embeds the staking credential — that is what makes it a *base* address rather than an enterprise address. Funds sitting at your base address are what counts toward your delegated stake; you do not move ADA to the stake address. ## 1. Register and delegate Do both in one transaction — there is no reason to pay two fees: ```java Tx tx = new Tx() .registerStakeAddress(account.baseAddress()) .delegateTo(account.baseAddress(), poolId) .from(account.baseAddress()); TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.stakeKeySignerFrom(account)) .completeAndWait(); ``` `poolId` is the pool's bech32 id (`pool1...`). You can pass a base address to `registerStakeAddress` and `delegateTo` — the library extracts the staking credential from it. :::caution[Delegation needs the stake key] `delegateTo` produces a delegation certificate, and the ledger requires a **witness from the stake key** to accept it. Miss `stakeKeySignerFrom(...)` and the transaction is rejected at submission with a missing-witness error. Registration alone is the exception — a registration certificate does not need the stake key, only the payment key that pays the deposit. Deregistration and withdrawal do need it. ::: ### Who signs what | Operation | Payment key | Stake key | |---|---|---| | `registerStakeAddress` | Yes | No | | `delegateTo` | Yes (fees) | **Yes** | | `delegateVotingPowerTo` | Yes (fees) | **Yes** | | `withdraw` | Yes (fees) | **Yes** | | `deregisterStakeAddress` | Yes (fees) | **Yes** | When several stake addresses are involved, add one `stakeKeySignerFrom(...)` per account. ## 2. When delegation takes effect Delegation is not immediate, and this surprises people: | Epoch | What happens | |---|---| | `n` | You submit the delegation certificate | | `n + 1` | Stake snapshot is taken | | `n + 2` | Your stake is active for the pool | | `n + 4` | First rewards for that stake appear, at the start of the epoch | So roughly **15–20 days on mainnet** before the first reward. Preview's epochs last one day. On a [local devnet](/learn/test-with-yaci-devkit/) they take minutes, or less if you set a shorter epoch length, which is why a devnet is the right place to test a staking flow end to end. ## 3. Withdraw rewards The ledger won't let a key-based stake address withdraw until it has delegated its voting power. It rejects the transaction with `ConwayWdrlNotDelegatedToDRep`, even when the amount is zero. If you don't want to choose a DRep, delegate to the built-in "always abstain" option. You only need to do this once: ```java Tx voteDelegation = new Tx() .delegateVotingPowerTo(account.baseAddress(), DRep.abstain()) .from(account.baseAddress()); new QuickTxBuilder(backendService) .compose(voteDelegation) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.stakeKeySignerFrom(account)) .completeAndWait(); ``` Withdrawals must take the **entire** reward balance — the ledger has no partial withdrawal: ```java AccountInformation info = backendService.getAccountService() .getAccountInformation(account.stakeAddress()) .getValue(); // withdrawableAmount is a lovelace string on AccountInformation. BigInteger rewardBalance = new BigInteger(info.getWithdrawableAmount()); Tx tx = new Tx() .withdraw(account.stakeAddress(), rewardBalance) .from(account.baseAddress()); TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.stakeKeySignerFrom(account)) .completeAndWait(); ``` Query the balance immediately before building. If the amount in the transaction does not match the reward balance exactly at the moment of validation, the transaction fails — so a value you cached earlier, or one that crossed an epoch boundary, will be rejected. To send the rewards somewhere other than the change address, use the three-argument form: `withdraw(stakeAddress, amount, receiverAddress)`. ## 4. Deregister Deregistration refunds the deposit and stops delegation: ```java Tx tx = new Tx() .deregisterStakeAddress(account.baseAddress()) .from(account.baseAddress()); TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.stakeKeySignerFrom(account)) .completeAndWait(); ``` Withdraw any outstanding rewards **first**. Deregistering with a non-zero reward balance is rejected. ## Governance delegation is separate In the Conway era a stake address has *two* independent delegations: - **`delegateTo(address, poolId)`** — block-production stake, earns rewards - **`delegateVotingPowerTo(address, drep)`** — governance voting power Delegating to a pool does not give your voting power to anyone, and delegating voting power to a DRep does not change which pool you stake with. The one link is that withdrawing rewards [needs a vote delegation](#3-withdraw-rewards). See [Governance API](/governance/overview/) for the voting side. ## Troubleshooting | Symptom | Cause | |---|---| | Missing witness / `MissingVKeyWitnessesUTXOW` | No `stakeKeySignerFrom(...)` on a certificate that needs it | | `StakeKeyRegisteredDELEG` | Address already registered — skip registration and just delegate | | `StakeKeyNotRegisteredDELEG` | Delegating before registering, or after deregistering | | `ConwayWdrlNotDelegatedToDRep` | The stake address hasn't delegated its voting power; see [Withdraw rewards](#3-withdraw-rewards) | | `ConwayIncompleteWithdrawals` | Amount does not exactly equal the current reward balance | | Delegated but no rewards | Fewer than 4 epochs have passed, or the pool produced no blocks | ## Next - [Calling a Plutus script](/learn/plutus-scripts/) — the previous step, if you skipped it - [Governance API](/governance/overview/) — DReps, voting and proposals - [CIP-113 programmable tokens](/learn/cip113-programmable-tokens/) — build a token with its own transfer rules - [What's next](/learn/whats-next/) — where to go after the guided path --- # Test locally with Yaci DevKit Source: https://cardano-client.dev/learn/test-with-yaci-devkit/ Summary: Run a local Cardano devnet, fund accounts instantly, and move the same code to a public testnet when you're ready. You can run every tutorial on this site in one of two places: a devnet on your own machine, or a public test network. Both use the same code, and only the backend line changes. | | Yaci DevKit (local) | Preview or Preprod (public) | |---|---|---| | **Setup** | Install and start it once | Nothing to install. Blockfrost needs a free project id; Koios doesn't | | **Test ada** | Funded accounts, plus a top-up command | A [faucet](https://docs.cardano.org/cardano-testnets/tools/faucet) | | **Block time** | 1 second by default | About 20 seconds on average | | **Epoch length** | 600 slots by default, and you can change it | 1 day on Preview, 5 days on Preprod | | **Who sees it** | Only you | Everyone, permanently | | **Start over** | Reset whenever you like | Not possible | Use DevKit while you're developing, because the feedback is fast and mistakes cost nothing. Before you release, run once on Preview: a public network has real delays that a devnet hides. ## Start a devnet Pick the setup that suits your machine. [Get started with Yaci DevKit](https://devkit.yaci.xyz/#get-started) has these and the other options, npm and Zip. **Docker** The whole stack in containers, including [Yaci Viewer](#see-your-transactions-in-yaci-viewer). Docker must be running. ```bash curl --proto '=https' --tlsv1.2 -LsSf https://devkit.yaci.xyz/install.sh | bash devkit start ``` This opens the Yaci CLI prompt. Create and start a devnet there. The `-o` option overwrites any devnet you already have. ```text yaci-cli:> create-node -o --start ``` Yaci Viewer opens at http://localhost:5173. **Homebrew (Non Docker)** The native CLI, for macOS 15+ or Linux. You don't need Docker. ```bash brew install bloxbean/tap/yaci-devkit yaci-devkit up --enable-yaci-store --interactive ``` [Yaci Viewer](#see-your-transactions-in-yaci-viewer) isn't included with this setup. To add it, run these in another terminal: ```bash npm install -g @bloxbean/yaci-viewer yaci-viewer ``` It opens at http://localhost:3000. In another terminal, check that the devnet's API is answering: ```bash curl http://localhost:8080/api/v1/epochs/latest ``` ### Use the Yaci CLI prompt Both setups leave you at an interactive prompt, `devnet:default>`, where you can manage the devnet. Keep that terminal open while you work. ```text devnet:default> topup
100 # send 100 ada to an address devnet:default> utxos
# list an address's UTxOs devnet:default> info # show the devnet's settings devnet:default> reset # delete all data and restart ``` The [command reference](https://devkit.yaci.xyz/commands) lists the rest. For a test pipeline that doesn't need a prompt, leave out `--interactive` and [fund accounts over HTTP](#fund-an-account). ### See your transactions in Yaci Viewer Yaci Viewer is a block explorer for your devnet, in your browser. Use it to look up the blocks, transactions and addresses that your code creates. ## Connect from Java DevKit includes Yaci Store, which serves a Blockfrost-compatible API. So you use the Blockfrost backend with a local URL, and any value for the key: ```java BackendService backend = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); ``` ## Fund an account A new devnet has 20 funded addresses, each with 10,000 ada, derived from one well-known mnemonic. Use it to get going right away: ```java String mnemonic = "test test test test test test test test test test test test " + "test test test test test test test test test test test sauce"; Account sender = Account.createFromMnemonic(Networks.testnet(), mnemonic); ``` :::danger[Devnet only] Everyone knows this mnemonic. Never send real funds to its addresses. ::: To fund any other address, use `topup` at the [Yaci CLI prompt](#use-the-yaci-cli-prompt). This sends it 100 ada: ```text devnet:default> topup
100 ``` Tests can do the same over HTTP: ```bash curl -X POST http://localhost:10000/local-cluster/api/addresses/topup \ -H "Content-Type: application/json" \ -d '{"address": "
", "adaAmount": 100}' ``` ## Send a transaction This is the transaction from [Your first transaction](/learn/simple-transfer/), running on your devnet. You need `cardano-client-lib` and `cardano-client-backend-blockfrost` from [Installation](/start-here/installation/). ```java import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.backend.api.BackendService; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.bloxbean.cardano.client.quicktx.QuickTxBuilder; import com.bloxbean.cardano.client.quicktx.Tx; import com.bloxbean.cardano.client.quicktx.TxResult; public class LocalTransfer { public static void main(String[] args) { BackendService backend = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); String mnemonic = "test test test test test test test test test test test test " + "test test test test test test test test test test test sauce"; Account sender = Account.createFromMnemonic(Networks.testnet(), mnemonic); Account receiver = new Account(Networks.testnet()); Tx tx = new Tx() .payToAddress(receiver.baseAddress(), Amount.ada(10)) .from(sender.baseAddress()); TxResult result = new QuickTxBuilder(backend) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); System.out.println(result.getTxStatus() + " " + result.getTxHash()); } } ``` It prints `CONFIRMED` and the transaction hash within a few seconds. Paste the hash into Yaci Viewer to see the transaction. ## Move to a public network Change the backend, and use an account you funded from the [faucet](https://docs.cardano.org/cardano-testnets/tools/faucet) in place of the devnet mnemonic. The rest of your code stays the same: ```java // Preview, through Blockfrost BackendService backend = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, ""); ``` Expect two differences: - **Slower confirmation.** Allow minutes rather than seconds, for example with `completeAndWait(Duration.ofMinutes(5))`. If it times out, the status is `TIMEOUT` and the transaction may still land. - **Delays in the backend's view.** A confirmed transaction's outputs can take a little while to appear in UTxO queries, so a step that spends them straight away may not find them. Retry that query instead of assuming they're there. ## Going further - **Staking and governance.** The [staking](/learn/staking-and-delegation/) lifecycle spans several epochs, which takes minutes on a devnet instead of days. The `--epoch-length` option of [`create-node`](https://devkit.yaci.xyz/commands) makes them shorter. - **Starting over.** `reset` at the Yaci CLI prompt deletes the devnet's data and restarts it. - **Rollbacks.** DevKit can also [simulate rollbacks](https://devkit.yaci.xyz/rollback-testing), which is useful for testing [TxFlow](/txflow/overview/). - **Other commands.** See the [command reference](https://devkit.yaci.xyz/commands) and the [services DevKit can run](https://devkit.yaci.xyz/services). ## Next Next, [mint and distribute your own tokens](/learn/native-tokens/). --- # What's next Source: https://cardano-client.dev/learn/whats-next/ Summary: You've finished the guided path. Here's how to choose what to read next based on what you're actually building. You can now build, sign and submit every common kind of Cardano transaction: payments, native tokens, NFTs, multi-sig, Plutus script calls and staking certificates. That covers most of what an application needs. What follows is not a reading list to work through in order. Pick the row that matches the problem you actually have. ## Choose by what you're building | What you're building | Go to | |---|---| | More transaction shapes — governance, pool registration, reference scripts | [QuickTx overview](/quicktx/overview/) | | Transactions defined as config rather than code | [TxPlan](/txplan/getting-started/) | | Several transactions that depend on each other's outputs | [TxFlow](/txflow/overview/) | | A service submitting transactions continuously | [TxStream](/txstream/getting-started/) | | DRep registration, voting, proposals | [Governance API](/governance/overview/) | | Type-safe datums and redeemers from a CIP-57 blueprint | [Blueprint code generation](/smart-contracts/blueprint-codegen/) | | Proofs an Aiken validator can verify on-chain | [Verified structures](/verified-structures/overview/) | | Wallet integration (CIP-30) | [CIP-30](/standards/cip30/) | | An AI agent writing CCL code for you | [Using CCL with AI](/ai/) | ## The three preview APIs, and when each earns its place TxPlan, TxFlow and TxStream solve genuinely different problems. The most common mistake is reaching for the largest one first. **Start with plain QuickTx.** One `Tx`, one `completeAndWait()`. It handles far more than people expect — including multi-asset outputs, certificates, governance and script spending. Move up only when you hit a specific wall. | You hit this wall | Because | Use | |---|---|---| | "The transaction shape is fixed, but the values change per run — and operators want to review them" | The decision belongs in config, not code | **[TxPlan](/txplan/getting-started/)** | | "Transaction B needs to spend an output that transaction A creates" | UTXO chaining, ordering and rollback handling are genuinely hard to do by hand | **[TxFlow](/txflow/overview/)** | | "I'm submitting a continuous stream and must never double-submit, even across a restart" | Exactly-once delivery and durable recovery need real machinery | **[TxStream](/txstream/getting-started/)** | They compose: a TxFlow step can execute a TxPlan, and TxStream runs on the TxFlow engine. :::note[These APIs are preview] They work and are documented in full, but their surfaces may shift before the stable release. Use the [latest preview](/start-here/installation/#versions), and pin an exact version if you build on it. ::: ## Test against a local devnet The single biggest speed-up when learning this library is not reading — it is a fast feedback loop. [Yaci DevKit](/learn/test-with-yaci-devkit/) runs a local Cardano devnet with pre-funded accounts and one-second blocks: ```java BackendService backend = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); Account sender = Account.createFromMnemonic(Networks.testnet(), devkitMnemonic); // see the DevKit page ``` No API key, no faucet, and epochs that last minutes rather than days, so it's the practical way to test a full staking or governance lifecycle. The same code runs against Preview with the backend swapped. ## Understanding what the library did When a transaction does something you did not expect, inspect it before it goes out: ```java TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withTxInspector(txn -> System.out.println(JsonUtil.getPrettyJson(txn))) .completeAndWait(); ``` `withTxInspector` hands you the fully built transaction — inputs chosen, fee calculated, change output added — just before submission. It is the fastest way to answer "why is the fee that number" or "which UTXOs did it pick". ## Reference material - [Modules & artifacts](/reference/modules/) — every published module and what it is for - [Backend services](/reference/backend-services/) — Blockfrost, Koios, Ogmios/Kupo - [Coin selection](/reference/coin-selection/) — UTXO selection strategies and their trade-offs - [Composable functions](/reference/composable-functions/) — the layer beneath QuickTx, for the rare case QuickTx cannot express what you need ## Where to get help - [GitHub Discussions](https://github.com/bloxbean/cardano-client-lib/discussions) — questions - [GitHub Issues](https://github.com/bloxbean/cardano-client-lib/issues) — bugs and feature requests - [cardano-client-examples](https://github.com/bloxbean/cardano-client-examples) — runnable samples - [Showcase](/project/showcase/) — projects built with this library --- # Contributing to docs Source: https://cardano-client.dev/project/contributing-to-docs/ Summary: How this documentation site is built, and how to add or edit a page. Documentation lives in the `www/` directory of the [cardano-client-lib repository](https://github.com/bloxbean/cardano-client-lib) and is built with [Astro](https://astro.build) and [Starlight](https://starlight.astro.build). Every page has an **Edit page** link at the bottom that opens the right file on GitHub — for a typo or a small correction, that is the whole workflow. ## Run it locally Requires Node.js 22.12 or later. ```bash cd www npm install npm run dev # http://localhost:4321 ``` The dev server hot-reloads on save. `npm run build` produces the static site in `dist/` and then runs the link checker; `npm run preview` serves that build. ## Where things live ```text www/ ├── astro.config.mjs site config and the sidebar ├── src/ │ ├── content/docs/ every documentation page (.md) │ ├── pages/index.astro the landing page │ ├── components/ landing-page components │ └── styles/ theme (starlight.css) and landing (landing.css) ├── scripts/ │ ├── generate-catalog.mjs /ai/catalog.json, from settings.gradle │ ├── generate-llms-txt.mjs /llms.txt and /llms-full.txt │ ├── llms-integration.mjs serves those in dev, writes them at build │ ├── check-links.mjs internal-link gate, part of `npm run build` │ ├── legacy-redirects.mjs old /docs/ URLs and the pages they forward to │ └── check-redirects.mjs fails the build if a redirect target is missing └── public/ logo, favicon ``` ## Add a page 1. Create a `.md` file under `src/content/docs/
/`. 2. Give it frontmatter — `title` is required, `description` is strongly encouraged because it becomes the page summary in search results and in `llms.txt`: ```markdown --- title: "Your page title" description: "One sentence on what the reader gets from this page." --- ``` 3. Add it to the sidebar in `astro.config.mjs`. Ordering is explicit — the array order is the sidebar order, so file names do not need numeric prefixes. Do **not** start the body with an `# H1`. Starlight renders `title` as the page heading, so an H1 would appear twice. ## Formatting Standard Markdown, plus Starlight's asides: ```markdown :::note Neutral context. ::: :::tip[Custom title] Something that makes the reader's life easier. ::: :::caution A footgun. Use sparingly, or it stops registering. ::: :::danger Loss of funds or data. ::: ``` Code blocks take a language and an optional title: ````markdown ```java title="SendAda.java" Tx tx = new Tx() .payToAddress(receiver, Amount.ada(10)) .from(sender.baseAddress()); ``` ```` Use `java`, `yaml`, `bash`, `json`, `xml` or `gradle`. Anything else falls back to plain text with a build warning. ## Links Use **absolute, site-relative** paths with a trailing slash: ```markdown [QuickTx overview](/quicktx/overview/) [a specific section](/txstream/durability/#dedup-scopes) ``` `npm run build` fails on a broken internal link or a missing anchor, so a bad link cannot ship. ## Things that generate themselves Do not hand-edit these — regenerate them by changing their source: | Output | Source | |---|---| | `/ai/catalog.json` | `settings.gradle`, `gradle.properties` and the [Versions](/start-here/installation/#versions) list | | `/llms.txt`, `/llms-full.txt` | every page under `src/content/docs/` | | Versions on the landing page | The [Versions](/start-here/installation/#versions) list | The Versions list holds the latest stable and preview versions, so a release edits only that list. If you add a module to `settings.gradle`, describe it in `MODULE_INFO` inside `scripts/generate-catalog.mjs`. Otherwise its catalog entry has an empty description. ## Writing guidance - **Lead with the reader's problem**, not the API surface. "You need X because Y" beats "X is a class that…". - **QuickTx is the recommended path.** Unless a page is specifically about TxPlan, TxFlow, TxStream or the composable functions, examples should use QuickTx. - **Verify code before you publish it.** Snippets are copied verbatim by readers and ingested by agents through `llms-full.txt`. Check signatures against the source tree. - **Mark preview APIs.** TxPlan, TxFlow, TxStream and the verified structures are preview; say so rather than leaving a reader to find out at upgrade time. - **Say what something is for before how it works.** Several of the deeper pages open with architecture; that is right for a reference page and wrong for an entry point. ## Submitting Fork, branch, commit, and open a pull request against `master`. Run `npm run build` first — it is the same check CI runs, and it catches broken links and missing sidebar entries. --- # Known issues Source: https://cardano-client.dev/project/known-issues/ Summary: Known gaps in the latest preview, and how to work around them. These gaps are in the [latest preview](/start-here/installation/#versions), and we plan to fix them in a coming release. Follow progress in [issue #738](https://github.com/bloxbean/cardano-client-lib/issues/738). ## TxStream | Issue | What happens | What to do | |---|---|---| | Partitioned `bootstrap(true)` | `start()` fails with `TXSTREAM_BOOTSTRAP_FAILED`, because the bootstrap transaction has no signer | [Fund the lanes yourself](/txstream/throughput/) and use `bootstrap(false)` | | `batching(...)` on an address lane | The batch fails with `No signers found`, and no funds move | Use a funding-ref lane (`ofFundingRef` or `from_ref`) | | Secure references | Nothing resolves `withSecureBindingReference` values, so a durable stream can't deliver a secret to a template | Keep secrets in signer and resource registries | | `awaitResolution` on `FlowRuntime` | It can't change a `RECOVERY_REQUIRED` answer, and ends in `TxStreamTimeoutException` | Look the transaction hash up on chain, or use an engine with a store | ## TxFlow engine These also affect TxStream, which runs on the same engine. | Issue | What happens | What to do | |---|---|---| | `engine.recover(...)` | It never finishes an execution, and on a finished one it moves the state back to `RUNNING`. A later replay of the same key returns `RECOVERY_REQUIRED` | Use the recovery result and an on-chain lookup as your answer. Don't call it on a finished execution | | `spec.execution.validity` | It has no effect, so the transaction gets no validity interval from it | Set `valid_from_slot` and `valid_to_slot` in the transaction's `context` | | Rollback actions with the default wiring | `DefaultChainDataSupplier` doesn't declare authoritative absence, so `RollbackAction` isn't applied, and a vanished transaction ends `RECOVERY_REQUIRED`. `FlowRuntime` can't supply another adapter | With `FlowEngine`, [opt in](/txflow/policies-results-observability/#submission-and-confirmation-are-separate) with `ObservationCapabilities.withAuthoritativeAbsence(...)` | | `FlowExecutionState.ROLLED_BACK` | A run never produces it | Detect a rollback through `FlowErrorCategory.ROLLBACK` | ## Standards and wallets Follow progress on these in [issue #739](https://github.com/bloxbean/cardano-client-lib/issues/739). | Issue | What happens | What to do | |---|---|---| | `CIP67AssetNameUtil.isValidAssetName` | It doesn't check every padding bit, so a bad prefix such as `000de144` is reported as valid | Compare the prefix with `labelToPrefix(label)` | | `NFTMetadata.create(byte[])` | Calling `getNFT` or `addNFT` on the result throws `NullPointerException` | Read the values through `getData()` | | `DefaultWallet.setAccountNo` | The wallet keeps the stake address of its previous account | Create a new wallet for each account, and set the account before asking for the stake address | --- # Showcase Source: https://cardano-client.dev/project/showcase/ Summary: Projects and products built with Cardano Client Lib. Discover how organizations and developers are leveraging Cardano Client Lib to build powerful Cardano applications. Is your project using Cardano Client Lib? We'd love to feature it here! Please [open a PR](https://github.com/bloxbean/cardano-client-lib/pulls) or [create an issue](https://github.com/bloxbean/cardano-client-lib/issues). --- ## 🌟 Community Projects ### 🎲 EASY1 Stake Pool Raffles **Easy1 Staking** Raffle platform for Cardano stake pool delegators with fair and transparent prize distribution. ### 📚 Cardano Tales **Cardano Tales** Interactive storytelling platform built on the Cardano blockchain with NFT collectibles. ### 🎰 [Cardano Fans Raffles](https://cardano.fans) **Cardano Fans (CRFA)** Community-driven raffle platform for the Cardano ecosystem with multiple prize pools. ### 💝 [Cardano Donation App](https://github.com/Cardano-Fans/crfa-cardano-donation-app) **Cardano Fans (CRFA)** Open-source donation application enabling seamless ADA contributions to projects and causes. ### 🌐 [ADA Domains](https://www.adadomains.io/) **adadomains.io** Domain name service for the Cardano blockchain, providing human-readable wallet addresses. ### 📱 [ADAM - ADA Monitor](https://play.google.com/store/apps/details?id=com.esodot.andro.monitor) **ADAM App** Mobile application for monitoring ADA portfolio, stake rewards, and blockchain activity. ### 🇮🇱 ISR - Israeli Cardano Community **Cardano Israel** Israeli Cardano community platform connecting local enthusiasts and developers. ### 🎵 MusicBox NFT **MusicBox** Music-focused CNFT project bringing artists and collectors together on Cardano. ### 💰 [Realfi.info](https://realfi.info) **Realfi** Portfolio viewer for tracking DeFi positions and investments on Cardano. ### 🖼️ [NFTio](https://nftio.io) **nftio.io** NFT explorer and analytics platform for discovering and tracking Cardano NFTs. ### 💵 [USDM](https://moneta.global/) **Moneta** Fiat-backed stablecoin on Cardano, providing a regulatory-compliant USD-pegged digital currency. --- # Support this project Source: https://cardano-client.dev/project/support/ Summary: Ways to support continued development of Cardano Client Lib. If this project helps you reduce time to develop on Cardano or if you just want to support this project, there are several ways to contribute! --- ### Donate ADA 💳 Send ADA directly to support development **$handle:** `$bloxbean` addr1qxgka3548k6wmmnelvcd7vgekf6ecplxen8j00c7h8ughyhuteckwgf7pfjzglgw8zr7lk9xkmnfpqeun60zjn7a5nnsl3mys0 [View on Cardanoscan](https://cardanoscan.io/address/01916ec6953db4edee79fb30df3119b2759c07e6cccf27bf1eb9f88b92fc5e7167213e0a64247d0e3887efd8a6b6e690833c9e9e294fdda4e7) ### Star the Repo 🌟 Show your support by starring the repository [Star on GitHub](https://github.com/bloxbean/cardano-client-lib) --- ## 🙏 Previous Sponsors A huge thank you to our generous sponsors who have supported this project! [🏢 Blockfrost](https://github.com/blockfrost) [👨‍💻 KtorZ](https://github.com/KtorZ) [👨‍💻 djcyr](https://github.com/djcyr) --- ### 💡 Other Ways to Contribute **Report bugs** and issues on [GitHub Issues](https://github.com/bloxbean/cardano-client-lib/issues) **Contribute code** through [Pull Requests](https://github.com/bloxbean/cardano-client-lib/pulls) **Share** the project with others in the Cardano community **Write tutorials** or documentation improvements --- # What's new in 0.8.0 Source: https://cardano-client.dev/project/whats-new/ Summary: New features in the Cardano Client Lib 0.8.0 preview releases. > **Preview features**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. The 0.8.0 previews add several major features for building Cardano applications in Java. For the complete list of changes, see the [release notes](https://github.com/bloxbean/cardano-client-lib/releases). ## New features ### [Unified Tx API](/quicktx/unified-tx/) `ScriptTx` is deprecated in the 0.8.0 previews (not in stable 0.7.x). All script operations, such as `collectFrom` with redeemers, Plutus minting, validator attachment, and script-protected staking and governance, are available directly on the `Tx` class. You no longer choose between `Tx` and `ScriptTx`, and you can mix script and non-script operations in one transaction. ### [TxFlow: multi-step transactions](/txflow/overview/) TxFlow orchestrates multi-step Cardano transactions with automatic UTXO chaining, confirmation tracking, rollback detection and retries. ### [TxStream: many transactions, reliably](/txstream/getting-started/) TxStream submits a continuous stream of transactions, each landing once, and durable stores carry that across restarts. It runs on the TxFlow engine, and it's experimental: see [known issues](/project/known-issues/). ### [TxPlan: YAML transactions](/txplan/overview/) TxPlan defines Cardano transactions in YAML, with variables and context properties, and runs them through `QuickTxBuilder`. It's part of the `quicktx` module, so it needs no additional dependency. ### [Verified data structures](/verified-structures/overview/) Merkle Patricia Forestry (MPF) and Jellyfish Merkle Tree (JMT), with RocksDB and SQL storage. MPF proofs can be verified on chain by Aiken's library. ## New in 0.8.0-pre7 - **[CIP-113 programmable tokens](/learn/cip113-programmable-tokens/):** tokens with on-chain transfer rules, through the new `cardano-client-programmable-token` module. - **QuickTx extensions in TxFlow:** TxFlow and TxStream can run steps that use extension intents, such as programmable tokens. - **MPF proofs:** proofs are now canonical and match the official JavaScript library. RocksDB garbage collection also received data-loss fixes. See [MpfTrie](/verified-structures/mpf/). - **JMT:** absence proof fixes, bounded historical reads and a schema installer. See [Jellyfish Merkle Tree](/verified-structures/jmt/). - **Nexus backend:** the new `cardano-client-backend-nexus` module. - **TxPlan:** the `additional_signers_count` context property. - **Lower-level parsing:** stack-safe CBOR and Plutus data codecs, plus `RawTx` and `RawBlock` views that keep a transaction's original bytes. - **GraalVM:** reachability metadata now ships in the module jars. ## Gradle dependencies ```gradle def cclVersion = '' // the latest preview dependencies { // Core library, including QuickTx with the Unified Tx API and TxPlan implementation "com.bloxbean.cardano:cardano-client-lib:$cclVersion" // TxFlow and TxStream implementation "com.bloxbean.cardano:cardano-client-txflow:$cclVersion" // Verified data structures on RocksDB implementation "com.bloxbean.cardano:cardano-client-merkle-patricia-forestry-rocksdb:$cclVersion" implementation "com.bloxbean.cardano:cardano-client-jellyfish-merkle-rocksdb:$cclVersion" // Backend provider (choose one) implementation "com.bloxbean.cardano:cardano-client-backend-blockfrost:$cclVersion" } ``` ## API stability See [known issues](/project/known-issues/) for current gaps. These APIs are a preview for early adopters. Core concepts are stable, but method signatures, configuration options and defaults may change before the stable release. If you find a problem or have a suggestion, please open an issue on [GitHub](https://github.com/bloxbean/cardano-client-lib/issues). --- # QuickTx API Source: https://cardano-client.dev/quicktx/overview/ Summary: Declarative transaction building API QuickTx is a declarative transaction builder layered on top of the Composable Functions API. It keeps common flows concise while still letting you plug in custom UTXO selection, evaluators, script suppliers, and verifiers. Use QuickTx when you want the happy path to be short and readable, but still need escape hatches for unusual inputs (custom UTXO selection), script fee evaluation, or reference scripts. Behind the scenes, QuickTx constructs a composable-function chain for you, applies balancing/fees, signs, and submits—while exposing hooks to override key steps. ## Key Features - Declarative, readable transaction description - Payments, NativeScript mint/burn, Plutus script spending/minting, staking, governance - Compose multiple `Tx`/`ScriptTx` into one transaction - Per-tx overrides for UTXO selection, reference scripts, and verifiers > **Preview — Unified Tx API**: in the 0.8.0 previews, all `ScriptTx` operations are also available on `Tx`, and > `ScriptTx` is deprecated there. It isn't deprecated in the stable 0.7.x releases. See the [Unified Tx API](/quicktx/unified-tx/) documentation for details and migration guide. > **Preview — YAML Transaction Plans**: Define transactions in YAML with variable substitution and execute them > through QuickTxBuilder. See the [TxPlan](/txplan/overview/) documentation. ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-quicktx - **Depends on**: core, function, backend, hd-wallet ## Constructing the Builder ```java // Common: derive all dependencies from the backend BackendService backendService = ...; QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); // Advanced: supply dependencies manually UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService()); ProtocolParamsSupplier paramsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService()); TransactionProcessor txProcessor = new DefaultTransactionProcessor(backendService.getTransactionService()); QuickTxBuilder quickTxBuilder = new QuickTxBuilder(utxoSupplier, paramsSupplier, txProcessor); ``` ## Usage Examples ### Simple Payment ```java Account sender = ...; String receiver1 = "..."; String receiver2 = "..."; Tx tx = new Tx() .payToAddress(receiver1, Amount.ada(1.5)) .payToAddress(receiver2, Amount.ada(2.0)) .from(sender.baseAddress()); TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(msg -> log.info(msg)); ``` ### Native Token Minting (NativeScript) ```java NativeScript policy = ...; // NativeScript policy Asset asset = Asset.builder() .name("MyToken") .value(BigInteger.valueOf(1_000)) .build(); Tx tx = new Tx() .mintAssets(policy, asset, receiverAddress) .from(sender.baseAddress()); TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .complete(); ``` ### Plutus Minting ```java PlutusScript mintingScript = ...; Asset asset = Asset.builder() .name("MyScriptToken") .value(BigInteger.valueOf(100)) .build(); ScriptTx tx = new ScriptTx() .mintAsset(mintingScript, asset, PlutusData.unit(), receiverAddress); TxResult result = quickTxBuilder.compose(tx) .feePayer(sender.baseAddress()) .withSigner(SignerProviders.signerFrom(sender)) .complete(); ``` ### Staking ```java String poolId = "..."; Tx delegateTx = new Tx() .delegateTo(sender.baseAddress(), poolId) // a base or reward address .from(sender.baseAddress()); Tx withdrawTx = new Tx() .withdraw(sender.stakeAddress(), BigInteger.valueOf(1_000_000)) // a reward address; the whole balance, in lovelace .from(sender.baseAddress()); ``` ### Metadata ```java Metadata metadata = MessageMetadata.create() .add("Transaction message"); Tx tx = new Tx() .payToAddress(receiver1, Amount.ada(1.0)) .payToAddress(receiver2, Amount.ada(2.0)) .attachMetadata(metadata) .from(sender.baseAddress()); TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` ## API Reference (Essentials) ### Tx (native transactions) - Payments: `payToAddress(...)`, `payToContract(...)` (datum hash/inline datum, reference scripts) - Native mint/burn: `mintAssets(NativeScript policy, List assets [, String receiver])` - Staking: `delegateTo(String stakeAddress, String poolId)`, `withdraw(String rewardAddress, BigInteger amount [, String receiver])` - Metadata: `attachMetadata(Metadata metadata)` - Sender: `from(String address | Wallet wallet)`; change via `withChangeAddress(String)` - Governance and other intents: stake registration/deregistration, pool registration/update/retire, governance actions (DRep/committee registration, votes), treasury donation, and more (see `Tx` methods) ### ScriptTx (Plutus-aware) - Script inputs: `collectFrom(...)` variants with redeemers/datums - Plutus minting: `mintAsset(PlutusScript script, List assets, PlutusData redeemer [, String receiver, PlutusData outputDatum])` - Attach validators: `attachSpendingValidator / attachMintValidator / ...` - Change with inline datum: `withChangeAddress(String, PlutusData)` ### QuickTxBuilder.compose(...) `compose(AbstractTx... txs)` returns a `TxContext`: ```java TxContext ctx = quickTxBuilder.compose(tx1, tx2) .withSigner(SignerProviders.signerFrom(sender)) .withUtxoSelectionStrategy(new LargestFirstUtxoSelectionStrategy(utxoSupplier)) .validFrom(slotFrom) .validTo(slotTo); Transaction unsigned = ctx.build(); Transaction signed = ctx.buildAndSign(); TxResult submitted = ctx.complete(); // submit, and don't wait TxResult waited = ctx.completeAndWait(); // submit, and wait up to 60 seconds ``` Call one of `complete` or `completeAndWait` once: each of them builds and submits a transaction. `isSuccessful()` on the result only means that the submission worked. After `completeAndWait`, check `getTxStatus()`, which is `CONFIRMED`, `PENDING`, `TIMEOUT` or `FAILED`. On `PENDING` or `TIMEOUT`, the transaction may still land. Execution flow: - Build one or more `Tx`/`ScriptTx` describing intent. - `compose(...)` wraps them in a `TxContext`. - Add signers, optional strategy/evaluator/era/reference scripts. - Call `build`/`buildAndSign`/`complete`/`completeAndWait`. ### Balancing, Fees, and Collateral - Fees and change are calculated during `build/complete` - Script cost evaluation is performed automatically; failures can be tolerated with `ignoreScriptCostEvaluationError(true)` - Collateral is auto-selected for script transactions unless you set explicit inputs via `withCollateralInputs(...)` ### Customization - UTXO selection: `withUtxoSelectionStrategy(...)` - Reference scripts for fee accuracy: `withReferenceScripts(...)` - Serialization era: `withSerializationEra(Era era)` (Conway by default) - Verifiers and inspection: `withVerifier(...)`, `withTxInspector(...)` ### Additional Context Controls - Fee payer and collateral payer: `feePayer(...)`, `collateralPayer(...)` (or wallet variants) - Required signers: `withRequiredSigners(Address... | byte[]...)` - Validity window: `validFrom(slot)`, `validTo(slot)` - Merge outputs: `mergeOutputs(boolean)` (deduplicate outputs to same address) - Script cost tolerance: `ignoreScriptCostEvaluationError(boolean)` - Duplicate witness cleanup: `removeDuplicateScriptWitnesses(boolean)` for multi-asset/script-heavy txs - UTXO search mode: `withSearchUtxoByAddressVkh(boolean)` when backend supports addr_vkh lookups - Custom transforms: `preBalanceTx(...)`, `postBalanceTx(...)` to inject custom `TxBuilder` steps - Collateral override: `withCollateralInputs(TransactionInput...)` - Reference-based resolution (Signers Registry): `Tx.fromRef(...)`, `feePayerRef(...)`, `collateralPayerRef(...)`, `withSignerRef(...)` when using a `SignerRegistry` ### When to choose QuickTx vs. Composable Functions - Choose QuickTx if you want minimal code for common flows and a single entrypoint for build + sign + submit + wait. - Choose [Composable Functions](/reference/composable-functions/) only if you need to handcraft every step, reuse builders outside QuickTx, or integrate with a highly customized pipeline. It is the internal implementation layer beneath QuickTx, and mistakes there fail in ways that are harder to diagnose. ## When one transaction is not enough QuickTx builds and submits **one** transaction. When that stops being the shape of your problem, three preview APIs sit above it — all built on the same transaction machinery: | You need | Use | Why not QuickTx | |---|---|---| | The same transaction shape with values supplied at run time, reviewable as config | [TxPlan](/txplan/getting-started/) | The decision belongs in a file operators can read, not in compiled code | | A second transaction that spends an output the first one created | [TxFlow](/txflow/your-first-flow/) | The new UTXO is not visible to a backend query yet — selection fails with "not enough funds" | | A continuous feed of transactions that must each land exactly once, across restarts | [TxStream](/txstream/getting-started/) | Idempotency and durable recovery are real machinery, not a retry loop | Most applications never need to leave this page. Move up only when you hit the specific wall in the right-hand column. --- # Unified Tx API Source: https://cardano-client.dev/quicktx/unified-tx/ Summary: ScriptTx merged into Tx — one class for all transaction types > **Preview feature**: available in 0.8.0 previews. See the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. ## What Changed In previous versions, building transactions required choosing between two classes: - **`Tx`** — for regular payments, native token minting, staking, and governance - **`ScriptTx`** — for Plutus script inputs, Plutus minting, and validator attachment In the 0.8.0 previews, **all `ScriptTx` operations are also available directly on `Tx`**, and `ScriptTx` is deprecated. It isn't deprecated in the stable 0.7.x releases, where it's still how you build script transactions, and it still works in the previews. ## Why - **No more class-choice burden** — you don't need to decide upfront whether your transaction involves scripts - **Mix operations freely** — combine script inputs, Plutus minting, native payments, and staking in a single `Tx` - **Simple migration** — replace `new ScriptTx()` with `new Tx()` and set the fee payer ## New Methods on Tx ### Script Input Collection ```java // Collect from script UTXOs with redeemer and optional datum tx.collectFrom(utxo, redeemer, datum) tx.collectFrom(utxoList, redeemer, datum) tx.collectFrom(utxo, redeemer) tx.collectFrom(utxoList, redeemer) // Predicate-based script UTXO selection tx.collectFrom(scriptAddress, utxo -> utxo.getAmount().stream().anyMatch(a -> a.getUnit().equals(tokenUnit)), redeemer, datum) // Filter-based script UTXO selection tx.collectFrom(scriptAddress, filterSpec, redeemer, datum) ``` ### Reference Inputs ```java // Read reference inputs (no spending) tx.readFrom(utxo1, utxo2) tx.readFrom(transactionInput) tx.readFrom(txHash, outputIndex) ``` ### Plutus Minting ```java // Mint with Plutus script (singular: mintAsset) tx.mintAsset(plutusScript, asset, redeemer) tx.mintAsset(plutusScript, assets, redeemer, receiver) tx.mintAsset(plutusScript, assets, redeemer, receiver, outputDatum) // Mint via reference script (by policy ID) tx.mintAsset(policyId, asset, redeemer) tx.mintAsset(policyId, assets, redeemer, receiver) ``` Note: Native script minting continues to use `mintAssets` (plural), while Plutus minting uses `mintAsset` (singular). ### Validator Attachment ```java tx.attachSpendingValidator(plutusScript) tx.attachMintValidator(plutusScript) tx.attachCertificateValidator(plutusScript) tx.attachRewardValidator(plutusScript) tx.attachProposingValidator(plutusScript) tx.attachVotingValidator(plutusScript) ``` ### Change Address with Datum ```java tx.withChangeAddress(address, inlineDatum) tx.withChangeAddress(address, datumHash) ``` ### Script-Protected Stake Operations ```java // Deregister with redeemer tx.deregisterStakeAddress(stakeAddr, redeemer) // Delegate with redeemer tx.delegateTo(stakeAddr, poolId, redeemer) // Withdraw with redeemer tx.withdraw(rewardAddr, amount, redeemer) tx.withdraw(rewardAddr, amount, redeemer, receiver) ``` ### Script-Protected Governance Operations ```java tx.registerDRep(credential, anchor, redeemer) tx.unRegisterDRep(credential, refundAddress, refundAmount, redeemer) tx.updateDRep(credential, anchor, redeemer) tx.createProposal(govAction, returnAddress, anchor, redeemer) tx.createVote(voter, govActionId, vote, anchor, redeemer) tx.delegateVotingPowerTo(address, dRep, redeemer) ``` ## Usage Examples ### Script Input with Payment ```java Tx tx = new Tx() .collectFrom(scriptUtxo, redeemer) .attachSpendingValidator(spendingScript) .payToAddress(receiver, Amount.ada(10)) .from(senderAddress); TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` ### Reference Inputs ```java Tx tx = new Tx() .collectFrom(scriptUtxo, redeemer) .readFrom(referenceUtxo) // Reference input for inline datum/script .payToAddress(receiver, Amount.ada(5)) .from(senderAddress); ``` ### Plutus Minting ```java Asset token = Asset.builder() .name("MyPlutusToken") .value(BigInteger.valueOf(100)) .build(); Tx tx = new Tx() .mintAsset(mintingScript, token, PlutusData.unit(), receiverAddress) .from(senderAddress); TxResult result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` ### Mixed Operations in One Transaction ```java // Combine native payment, Plutus minting, and script input in a single Tx Tx tx = new Tx() .payToAddress(receiver1, Amount.ada(5)) // Native payment .mintAsset(mintScript, token, PlutusData.unit()) // Plutus minting .collectFrom(scriptUtxo, spendRedeemer) // Script input .attachSpendingValidator(spendingScript) .from(senderAddress); ``` ## Migration Guide Replace `new ScriptTx()` with `new Tx()`. Calls with a redeemer keep the same names and arguments. Calls to `collectFrom` without a redeemer are different: `Tx` has no `collectFrom(Utxo)`, and `Tx.collectFrom(List)` adds regular inputs rather than script inputs, so pass a redeemer when you spend from a script. ### Before (0.7.x) ```java ScriptTx scriptTx = new ScriptTx() .collectFrom(scriptUtxo, redeemer, datum) .payToAddress(receiver, Amount.ada(10)) .attachSpendingValidator(validator); quickTxBuilder.compose(scriptTx) .feePayer(senderAddress) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` ### After (0.8.0) ```java Tx tx = new Tx() .collectFrom(scriptUtxo, redeemer, datum) .payToAddress(receiver, Amount.ada(10)) .attachSpendingValidator(validator) .from(senderAddress); quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` `.from(senderAddress)` sets the sender, who also pays the fee. You can also keep `.feePayer(senderAddress)` on the builder, which still works for a script transaction. ## How Detection Works You don't need to configure anything. When `QuickTxBuilder` processes a `Tx`, it automatically detects whether the transaction involves scripts by checking `hasScriptIntents()` — a data-driven inspection of the transaction's intentions list. If any script-related intents are present (script inputs, Plutus minting, or any operation with a redeemer), the builder applies script-specific processing (collateral selection, execution unit evaluation, etc.) automatically. ## Comparison | Aspect | Before (0.7.x) | After (0.8.0) | |--------|----------------|---------------| | Class choice | `Tx` for regular, `ScriptTx` for scripts | `Tx` for everything | | Script inputs | `ScriptTx.collectFrom(utxo, redeemer)` | `Tx.collectFrom(utxo, redeemer)` | | Plutus minting | `ScriptTx.mintAsset(script, asset, redeemer)` | `Tx.mintAsset(script, asset, redeemer)` | | Validator attachment | `ScriptTx.attachSpendingValidator(script)` | `Tx.attachSpendingValidator(script)` | | Mixing operations | Compose separate `Tx` + `ScriptTx` | Single `Tx` with all operations | | Backward compatibility | N/A | `ScriptTx` still works (deprecated) | --- # Account API Source: https://cardano-client.dev/reference/account-api/ Summary: Account management and key derivation APIs The Account API provides a simple abstraction for creating and managing Cardano accounts. It encapsulates features like CIP-1852 compatible address derivation, BIP-39 mnemonic generation, key management, and transaction signing through a simple `Account` class. ## Key Features - **Mnemonic Generation**: Generate BIP-39 compatible mnemonic phrases - **Address Derivation**: Derive various types of Cardano addresses (base, enterprise, stake, change) - **Key Management**: Manage private keys and key pairs securely - **Transaction Signing**: Sign transactions with account credentials - **Network Support**: Support for mainnet, testnet, and preview networks - **DRep Support**: Generate DRep keys, credentials, and CIP-129 DRep IDs for governance participation - **Committee Key Support**: Manage Constitutional Committee cold and hot keys for governance ## Core Classes ### Account Class The main class for account operations, providing methods for address generation, key management, and transaction signing. **Factory Methods (Recommended):** - `Account.createFromMnemonic(Network, String)` - Create account from mnemonic - `Account.createFromMnemonic(Network, String, int account, int index)` - Create account from mnemonic at specific derivation path - `Account.createFromRootKey(Network, byte[])` - Create account from root key - `Account.createFromAccountKey(Network, byte[])` - Create account from account-level key **Constructors:** - `Account(Network network)` - Create new account with generated mnemonic - `Account(Network network, String mnemonic)` - Create account from existing mnemonic *(deprecated, use factory methods)* **Address Methods:** - `baseAddress()` - Get base address for payments - `changeAddress()` - Get change address - `enterpriseAddress()` - Get enterprise address (no staking) - `stakeAddress()` - Get stake address for delegation **Key Management Methods:** - `hdKeyPair()` - Get payment HD key pair - `stakeHdKeyPair()` - Get stake HD key pair - `changeHdKeyPair()` - Get change HD key pair - `privateKeyBytes()` / `publicKeyBytes()` - Get raw key bytes - `getRootKeyPair()` - Get root key pair (if available) - `mnemonic()` - Get mnemonic phrase **DRep Key Methods:** - `drepHdKeyPair()` - Get DRep HD key pair - `drepKey()` - Get `DRepKey` object - `drepId()` - Get CIP-129 bech32 DRep ID - `legacyDRepId()` - Get CIP-105 DRep ID *(deprecated)* - `drepCredential()` - Get DRep credential **Committee Key Methods:** - `committeeColdKeyPair()` / `committeeColdKey()` / `committeeColdCredential()` - Constitutional Committee cold keys - `committeeHotKeyPair()` / `committeeHotKey()` / `committeeHotCredential()` - Constitutional Committee hot keys **Signing Methods:** - `sign(Transaction)` - Sign with payment key - `signWithStakeKey(Transaction)` - Sign with stake key - `signWithDRepKey(Transaction)` - Sign with DRep key - `signWithCommitteeColdKey(Transaction)` - Sign with Committee cold key - `signWithCommitteeHotKey(Transaction)` - Sign with Committee hot key ## Usage Examples ### Creating Accounts Create accounts using the recommended factory methods or constructors: ```java // Recommended: Factory methods Account account = Account.createFromMnemonic(Networks.testnet(), mnemonic); // With specific account and address index (m/1852'/1815'/1'/0/2) Account account = Account.createFromMnemonic(Networks.testnet(), mnemonic, 1, 2); // From root key (96 bytes: private key + chain code) byte[] rootKeyBytes = ...; Account account = Account.createFromRootKey(Networks.testnet(), rootKeyBytes); // From account-level key (96 bytes) byte[] accountKey = ...; Account account = Account.createFromAccountKey(Networks.mainnet(), accountKey); ``` You can also use constructors for generating new accounts: ```java // Generate new account with random mnemonic Account newAccount = new Account(Networks.testnet()); String generatedMnemonic = newAccount.mnemonic(); // Create mainnet account Account mainnetAccount = new Account(Networks.mainnet()); ``` ### Address Generation Retrieve different types of addresses from an account: ```java // Get base address (payment + staking) String baseAddress = account.baseAddress(); System.out.println("Base Address: " + baseAddress); // Get enterprise address (payment only) String enterpriseAddress = account.enterpriseAddress(); System.out.println("Enterprise Address: " + enterpriseAddress); // Get stake address (for delegation) String stakeAddress = account.stakeAddress(); System.out.println("Stake Address: " + stakeAddress); // Get change address String changeAddress = account.changeAddress(); System.out.println("Change Address: " + changeAddress); ``` ### Key Management Access private keys, public keys, and credentials: ```java // Get private key bytes byte[] privateKey = account.privateKeyBytes(); // Get HD key pair (payment) HdKeyPair keyPair = account.hdKeyPair(); HdPublicKey publicKey = keyPair.getPublicKey(); HdPrivateKey privateKey = keyPair.getPrivateKey(); // Get mnemonic phrase String mnemonic = account.mnemonic(); ``` #### Stake and Change Key Pairs ```java // Get stake HD key pair HdKeyPair stakeKeyPair = account.stakeHdKeyPair(); // Get change HD key pair HdKeyPair changeKeyPair = account.changeHdKeyPair(); // Get root key pair (available when created from mnemonic or root key) Optional rootKeyPair = account.getRootKeyPair(); ``` #### DRep Keys Access DRep keys for governance participation: ```java // Get DRep HD key pair HdKeyPair drepKeyPair = account.drepHdKeyPair(); // Get DRepKey object (provides access to signing/verification keys and bech32 formats) DRepKey drepKey = account.drepKey(); // Get CIP-129 DRep ID (e.g. "drep1yg7a8f...") String drepId = account.drepId(); // Get DRep credential for governance transactions Credential drepCredential = account.drepCredential(); ``` #### Committee Cold Keys Access Constitutional Committee cold keys: ```java // Get Committee Cold HD key pair HdKeyPair ccColdKeyPair = account.committeeColdKeyPair(); // Get CommitteeColdKey object CommitteeColdKey ccColdKey = account.committeeColdKey(); // Get CIP-129 Committee Cold ID (e.g. "cc_cold1zg6ed5...") String ccColdId = ccColdKey.id(); // Get Committee Cold credential Credential ccColdCredential = account.committeeColdCredential(); ``` #### Committee Hot Keys Access Constitutional Committee hot keys: ```java // Get Committee Hot HD key pair HdKeyPair ccHotKeyPair = account.committeeHotKeyPair(); // Get CommitteeHotKey object CommitteeHotKey ccHotKey = account.committeeHotKey(); // Get CIP-129 Committee Hot ID (e.g. "cc_hot1qth000...") String ccHotId = ccHotKey.id(); // Get Committee Hot credential Credential ccHotCredential = account.committeeHotCredential(); ``` ### Transaction Signing Sign transactions using the account's payment key: ```java // Sign a transaction directly with the account Transaction signedTx = account.sign(transaction); ``` #### Governance Signing Sign governance transactions with the appropriate key: ```java // Sign with stake key (e.g., stake delegation, stake registration) Transaction signedTx = account.signWithStakeKey(transaction); // Sign with DRep key (e.g., DRep registration, voting) Transaction signedTx = account.signWithDRepKey(transaction); // Sign with Committee Cold key (e.g., committee authorization) Transaction signedTx = account.signWithCommitteeColdKey(transaction); // Sign with Committee Hot key (e.g., committee voting) Transaction signedTx = account.signWithCommitteeHotKey(transaction); ``` #### Governance SignerProviders Use `SignerProviders` helpers for composable governance signing with `QuickTxBuilder`: ```java // Stake key signer (for stake delegation, withdrawal transactions) QuickTxBuilder builder = new QuickTxBuilder(backendService); Result result = builder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.stakeKeySignerFrom(account)) .completeAndWait(); // DRep key signer (for DRep registration, voting transactions) Result result = builder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.drepKeySignerFrom(account)) .completeAndWait(); // Committee Cold key signer Result result = builder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.committeeColdKeySignerFrom(account)) .completeAndWait(); // Committee Hot key signer Result result = builder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .withSigner(SignerProviders.committeeHotKeySignerFrom(account)) .completeAndWait(); ``` You can also sign with an HD key pair directly: ```java // Sign with DRep HD key pair SignerProviders.signerFrom(account.drepHdKeyPair()); ``` ## Advanced Usage ### Multi-Account Management Manage multiple accounts for different purposes: ```java // Main spending account Account spendingAccount = Account.createFromMnemonic(Networks.mainnet(), spendingMnemonic); // Savings account Account savingsAccount = Account.createFromMnemonic(Networks.mainnet(), savingsMnemonic); // Business account Account businessAccount = Account.createFromMnemonic(Networks.mainnet(), businessMnemonic); // Get addresses for each account String spendingAddress = spendingAccount.baseAddress(); String savingsAddress = savingsAccount.baseAddress(); String businessAddress = businessAccount.baseAddress(); ``` ### Network-Specific Operations Handle different network configurations: ```java // Mainnet account Account mainnetAccount = Account.createFromMnemonic(Networks.mainnet(), mnemonic); String mainnetAddress = mainnetAccount.baseAddress(); // addr1... // Testnet account Account testnetAccount = Account.createFromMnemonic(Networks.testnet(), mnemonic); String testnetAddress = testnetAccount.baseAddress(); // addr_test1... ``` ## Best Practices ### Mnemonic Validation ```java // Validate mnemonic using MnemonicUtil (throws on invalid input) String mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; try { MnemonicUtil.validateMnemonic(mnemonic); Account account = Account.createFromMnemonic(Networks.testnet(), mnemonic); System.out.println("Valid mnemonic, account created"); } catch (Exception e) { System.out.println("Invalid mnemonic phrase: " + e.getMessage()); } ``` ### Address Verification ```java // Verify account generates expected addresses Account account = Account.createFromMnemonic(Networks.testnet(), knownMnemonic); String expectedAddress = "addr_test1..."; // Known address for this mnemonic if (account.baseAddress().equals(expectedAddress)) { System.out.println("Account verification successful"); } else { System.out.println("Account verification failed"); } ``` ## Error Handling Handle common account-related errors: ```java try { Account account = Account.createFromMnemonic(Networks.mainnet(), userMnemonic); String address = account.baseAddress(); } catch (Exception e) { System.err.println("Account creation failed: " + e.getMessage()); } ``` --- # Address API Source: https://cardano-client.dev/reference/address-api/ Summary: Address generation, validation, and conversion utilities The Address API provides comprehensive functionality for working with Cardano addresses. It supports address creation, validation, conversion, and extraction of address components. The API handles both Shelley addresses (current) and Byron addresses (legacy) with full support for different address types and networks. ## Key Features - **Address Creation**: Create addresses from various inputs (strings, bytes, credentials) - **Address Validation**: Validate address format and network compatibility - **Address Conversion**: Convert between different address formats and representations - **Credential Extraction**: Extract payment and delegation credentials from addresses - **Type Support**: Support for all Cardano address types (base, enterprise, pointer, reward) ## Core Classes ### Address Class The main class for Shelley address operations, supporting Bech32 encoded addresses and credential extraction. **Constructors:** - `Address(String address)` — Create from Bech32 string - `Address(byte[] addressBytes)` — Create from byte array - `Address(String prefix, byte[] bytes)` — Create with custom prefix **Credential & Hash Methods:** - `getPaymentCredential()` — Extract payment credential as `Optional` - `getDelegationCredential()` — Extract delegation credential as `Optional` - `getPaymentCredentialHash()` — Get raw payment key/script hash as `Optional` - `getDelegationCredentialHash()` — Get raw delegation key/script hash as `Optional` - `getBech32VerificationKeyHash()` — Get Bech32-encoded (`addr_vkh`) payment credential hash **Type Checking Methods:** - `isPubKeyHashInPaymentPart()` — Check if payment credential is a public key hash - `isScriptHashInPaymentPart()` — Check if payment credential is a script hash - `isStakeKeyHashInDelegationPart()` — Check if delegation credential is a stake key hash - `isScriptHashInDelegationPart()` — Check if delegation credential is a script hash **Other Methods:** - `toBech32()` / `getAddress()` — Convert to Bech32 format - `getAddressType()` — Get the address type (`Base`, `Enterprise`, `Ptr`, `Reward`) - `getNetwork()` — Get the network (mainnet/testnet) - `getPrefix()` — Get the address prefix - `getBytes()` — Get raw address bytes ### AddressProvider Class Utility class for generating addresses from keys, scripts, and credentials. **Address Generation:** - `getBaseAddress(HdPublicKey, HdPublicKey, Network)` — Base address from key pair - `getBaseAddress(Credential, Credential, Network)` — Base address from any credential pair (key or script) - `getBaseAddress(Script, HdPublicKey, Network)` — Base address with script payment - `getBaseAddress(HdPublicKey, Script, Network)` — Base address with script delegation - `getBaseAddress(Script, Script, Network)` — Base address with both scripts - `getEntAddress(HdPublicKey, Network)` — Enterprise address from key - `getEntAddress(Script, Network)` — Enterprise address from script - `getEntAddress(Credential, Network)` — Enterprise address from credential - `getRewardAddress(HdPublicKey, Network)` — Reward address from delegation key - `getRewardAddress(Script, Network)` — Reward address from delegation script - `getRewardAddress(Credential, Network)` — Reward address from stake credential - `getPointerAddress(HdPublicKey, Pointer, Network)` — Pointer address from key - `getPointerAddress(Script, Pointer, Network)` — Pointer address from script - `getPointerAddress(Credential, Pointer, Network)` — Pointer address from credential **Stake Address Derivation:** - `getStakeAddress(Address)` — Derive stake/reward address from a base address - `getStakeAddressFromAccountPublicKey(String, Network)` — Stake address from Bech32 account public key (`acct_xvk` or `xpub`) - `getStakeAddressFromAccountPublicKey(byte[], Network)` — Stake address from account public key bytes **Verification & Inspection:** - `verifyAddress(Address, byte[])` — Verify address matches a public key - `isPubKeyHashInPaymentPart(Address)` / `isScriptHashInPaymentPart(Address)` — Check payment credential type - `isStakeKeyHashInDelegationPart(Address)` / `isScriptHashInDelegationPart(Address)` — Check delegation credential type - `getPaymentCredential(Address)` / `getDelegationCredential(Address)` — Extract credentials - `getPaymentCredentialHash(Address)` / `getDelegationCredentialHash(Address)` — Extract raw hashes ### AddressUtil Class Utility class for address validation and byte-level conversion. **Key Methods:** - `AddressUtil.isValidAddress(String address)` — Validate Shelley or Byron address format - `AddressUtil.addressToBytes(String address)` — Convert Shelley or Byron address string to bytes - `AddressUtil.bytesToAddress(byte[])` — Convert bytes to Shelley (Bech32) or Byron (Base58) address string - `AddressUtil.bytesToBase58Address(byte[])` — Convert Byron address bytes to Base58 string ## Usage Examples ### Creating Addresses Create addresses from different input formats: ```java // Create address from Bech32 string String bech32 = "addr_test1qpx4kmt...plwg6"; Address address = new Address(bech32); // Create address from byte array byte[] addressBytes = address.getBytes(); Address addressFromBytes = new Address(addressBytes); // Create address with custom prefix Address customAddress = new Address("addr_test", addressBytes); ``` ### Address Generation from Accounts Generate addresses from account credentials: ```java // Generate addresses from account Account account = new Account(Networks.testnet()); // Get base address (payment + staking) String baseAddress = account.baseAddress(); System.out.println("Base Address: " + baseAddress); // Get enterprise address (payment only) String enterpriseAddress = account.enterpriseAddress(); System.out.println("Enterprise Address: " + enterpriseAddress); // Get stake address (for delegation) String stakeAddress = account.stakeAddress(); System.out.println("Stake Address: " + stakeAddress); ``` ### Address Generation from Credentials Build addresses directly from `Credential` objects, useful when working with key hashes or script hashes: ```java // From key hashes byte[] paymentKeyHash = ...; // 28-byte Blake2b-224 hash byte[] stakeKeyHash = ...; Credential paymentCred = Credential.fromKey(paymentKeyHash); Credential stakeCred = Credential.fromKey(stakeKeyHash); // Base address from credentials Address baseAddr = AddressProvider.getBaseAddress(paymentCred, stakeCred, Networks.testnet()); // Enterprise address (payment only, no staking) Address entAddr = AddressProvider.getEntAddress(paymentCred, Networks.testnet()); // Reward address (for staking/delegation) Address rewardAddr = AddressProvider.getRewardAddress(stakeCred, Networks.testnet()); ``` You can mix key and script credentials: ```java // Script-locked payment with key-based staking Credential scriptPayment = Credential.fromScript(scriptHash); Credential keyStake = Credential.fromKey(stakeKeyHash); Address scriptBaseAddr = AddressProvider.getBaseAddress(scriptPayment, keyStake, Networks.mainnet()); ``` ### Stake Address Derivation Derive the stake (reward) address from an existing base address: ```java // From a base address Address baseAddress = new Address("addr_test1qpx4kmt...plwg6"); Address stakeAddress = AddressProvider.getStakeAddress(baseAddress); System.out.println("Stake Address: " + stakeAddress.toBech32()); // From a CIP-1852 extended account public key (Bech32 encoded) String accountPubKey = "acct_xvk1abc...xyz"; Address stakeAddr = AddressProvider.getStakeAddressFromAccountPublicKey(accountPubKey, Networks.mainnet()); ``` ### Address Verification Verify that an address was derived from a given public key: ```java Address address = new Address("addr_test1qpx4kmt...plwg6"); byte[] publicKeyBytes = ...; // Ed25519 public key bytes boolean isMatch = AddressProvider.verifyAddress(address, publicKeyBytes); if (isMatch) { System.out.println("Address matches the public key"); } ``` ### Address Validation Validate address format and network compatibility: ```java // Basic address validation (supports both Shelley and Byron) String addressToValidate = "addr_test1qpx4kmt...plwg6"; boolean isValid = AddressUtil.isValidAddress(addressToValidate); if (isValid) { System.out.println("Address is valid"); } // Network-specific validation Address addr = new Address(addressToValidate); boolean isTestnet = addr.getNetwork().equals(Networks.testnet()); // Validate address type boolean isBase = addr.getAddressType() == AddressType.Base; ``` ### Address Conversion Convert addresses between different formats: ```java // Convert address to bytes String addressString = "addr_test1qpx4kmt...plwg6"; byte[] addressBytes = AddressUtil.addressToBytes(addressString); // Convert bytes back to address string (auto-detects Shelley vs Byron) String addressStr = AddressUtil.bytesToAddress(addressBytes); // Convert Byron address bytes to Base58 byte[] byronBytes = ...; String byronAddress = AddressUtil.bytesToBase58Address(byronBytes); // Bech32 round-trip via Address class Address address = new Address(addressBytes); String bech32 = address.toBech32(); ``` ### Credential Extraction Extract payment and delegation credentials from addresses: ```java Address address = new Address("addr_test1qpx4kmt...plwg6"); // Extract payment credential Optional paymentCredential = address.getPaymentCredential(); if (paymentCredential.isPresent()) { Credential payment = paymentCredential.get(); System.out.println("Payment Credential Type: " + payment.getType()); System.out.println("Payment Credential Hash: " + HexUtil.encodeHexString(payment.getBytes())); } // Extract delegation credential Optional delegationCredential = address.getDelegationCredential(); if (delegationCredential.isPresent()) { Credential delegation = delegationCredential.get(); System.out.println("Delegation Credential Type: " + delegation.getType()); System.out.println("Delegation Credential Hash: " + HexUtil.encodeHexString(delegation.getBytes())); } // Get raw hashes directly Optional paymentHash = address.getPaymentCredentialHash(); Optional delegationHash = address.getDelegationCredentialHash(); // Get Bech32-encoded payment verification key hash (addr_vkh1...) Optional vkh = address.getBech32VerificationKeyHash(); ``` ### Credential Type Checking Determine whether address credentials are key-based or script-based: ```java Address address = new Address("addr_test1qpx4kmt...plwg6"); // Check payment credential type if (address.isPubKeyHashInPaymentPart()) { System.out.println("Payment is key-based"); } else if (address.isScriptHashInPaymentPart()) { System.out.println("Payment is script-based"); } // Check delegation credential type if (address.isStakeKeyHashInDelegationPart()) { System.out.println("Delegation is key-based"); } else if (address.isScriptHashInDelegationPart()) { System.out.println("Delegation is script-based"); } ``` ### Address Information Get detailed information about addresses: ```java Address address = new Address("addr_test1qpx4kmt...plwg6"); // Get address type AddressType addressType = address.getAddressType(); System.out.println("Address Type: " + addressType); // Get network Network network = address.getNetwork(); System.out.println("Network: " + network); // Get prefix String prefix = address.getPrefix(); System.out.println("Prefix: " + prefix); // Get raw bytes byte[] bytes = address.getBytes(); System.out.println("Address Length: " + bytes.length + " bytes"); ``` ## Advanced Usage ### Script Addresses Work with script-based addresses: ```java // Generate key pair for script Keys keyPair = KeyGenUtil.generateKey(); VerificationKey verificationKey = keyPair.getVkey(); // Create script pubkey ScriptPubkey scriptPubkey = ScriptPubkey.create(verificationKey); // Generate script address Address scriptAddress = AddressProvider.getEntAddress(scriptPubkey, Networks.testnet()); System.out.println("Script Address: " + scriptAddress.getAddress()); // Verify it's a script address if (scriptAddress.isScriptHashInPaymentPart()) { System.out.println("Successfully created script address"); } ``` ### Multi-Signature Addresses Create addresses for multi-signature scenarios: ```java // Generate multiple key pairs Keys key1 = KeyGenUtil.generateKey(); Keys key2 = KeyGenUtil.generateKey(); Keys key3 = KeyGenUtil.generateKey(); // Create script pubkeys ScriptPubkey scriptPubkey1 = ScriptPubkey.create(key1.getVkey()); ScriptPubkey scriptPubkey2 = ScriptPubkey.create(key2.getVkey()); ScriptPubkey scriptPubkey3 = ScriptPubkey.create(key3.getVkey()); // Create multi-sig script (2 of 3) ScriptAtLeast multiSigScript = new ScriptAtLeast(2) .addScript(scriptPubkey1) .addScript(scriptPubkey2) .addScript(scriptPubkey3); // Generate multi-sig address Address multiSigAddress = AddressProvider.getEntAddress(multiSigScript, Networks.testnet()); System.out.println("Multi-Sig Address: " + multiSigAddress.getAddress()); ``` ## Best Practices ### Address Validation ```java // Always validate addresses before use if (!AddressUtil.isValidAddress(addressString)) { System.err.println("Invalid address format"); return; } Address address = new Address(addressString); // Proceed with validated address ``` ## Address Types Reference | Address Type | Prefix | Use Case | Components | |--------------|--------|----------|------------| | **Base** | `addr` / `addr_test` | Payment + Staking | Payment credential + Delegation credential | | **Enterprise** | `addr` / `addr_test` | Payment only | Payment credential only | | **Pointer** | `addr` / `addr_test` | Payment + Pointer to stake | Payment credential + Stake pointer | | **Reward** | `stake` / `stake_test` | Staking rewards | Delegation credential only | | **Byron** | `Ae2` / `37` | Legacy addresses | Legacy format (deprecated) | --- # Backend Services API Source: https://cardano-client.dev/reference/backend-services/ Summary: APIs for integrating with Cardano backend services The Backend Services API provides integration with various Cardano backend services through a unified interface. ## Key Features - **Unified Interface**: Single API for multiple backend providers - **Provider Flexibility**: Switch between different backend services easily - **Service Coverage**: Access to blockchain data and transaction services ## Core Classes ### BackendService Interface The main interface for all backend service implementations, providing access to various blockchain services. ### Provider Implementations - `BFBackendService` - Blockfrost integration - `KoiosBackendService` - Koios REST API integration - `OgmiosBackendService` - Ogmios JSON-over-HTTP for protocol params and transactions - `KupmiosBackendService` - Ogmios (protocol params + tx) combined with Kupo (UTxOs) for full transaction building ### Service Interfaces - `TransactionService` - Transaction submission and querying - `UtxoService` - UTXO management and queries - `AddressService` - Address information and history - `AssetService` - Native token and NFT data - `BlockService` - Block information and navigation - `EpochService` - Epoch information and protocol parameters ## Supported Backend Services | Provider | Module | Features | Status | |----------|--------|----------|--------| | **Blockfrost** | `cardano-client-backend-blockfrost` | Chain data and transaction services (governance endpoints not yet available). Also compatible with [Yaci Store](https://github.com/bloxbean/yaci-store) | Stable | | **Koios** | `cardano-client-backend-koios` | REST API, community-driven | Stable | | **Ogmios** | `cardano-client-backend-ogmios` | Protocol parameters and transaction submission over HTTP (no UTxO API) | Stable | ## Usage Examples ### Blockfrost Integration Initialize Blockfrost backend service with API key: ```java // Initialize Blockfrost service BackendService backendService = new BFBackendService( Constants.BLOCKFROST_PREPROD_URL, "your-project-id" ); // Access different services TransactionService transactionService = backendService.getTransactionService(); UtxoService utxoService = backendService.getUtxoService(); AddressService addressService = backendService.getAddressService(); AssetService assetService = backendService.getAssetService(); // Use with QuickTx QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); ``` ### Koios Integration Initialize Koios backend service for community-driven API: ```java // Initialize Koios service BackendService backendService = new KoiosBackendService(Constants.KOIOS_MAINNET_URL); // Access services TransactionService transactionService = backendService.getTransactionService(); UtxoService utxoService = backendService.getUtxoService(); AddressService addressService = backendService.getAddressService(); // Query address information Result addressInfo = addressService.getAddressInfo("addr1..."); System.out.println("Address balance: " + addressInfo.getValue().getAmount()); ``` ### Ogmios Integration Initialize Ogmios backend service: ```java // Initialize Ogmios service (HTTP) BackendService backendService = new OgmiosBackendService( "http://localhost:1337" ); // Access services TransactionService transactionService = backendService.getTransactionService(); // Submit transaction Result result = transactionService.submitTransaction(transactionCbor); if (result.isSuccessful()) { System.out.println("Transaction submitted: " + result.getValue()); } ``` **Note:** Ogmios backend implements protocol parameters and transaction services. For UTXO queries, use Kupo with `KupmiosBackendService` (see below). ### Kupmios Integration (Ogmios + Kupo) For complete transaction building capabilities, use `KupmiosBackendService` which combines Ogmios and Kupo: ```java // Initialize Kupmios service (Ogmios + Kupo) BackendService backendService = new KupmiosBackendService( "http://localhost:1337", // Ogmios URL "http://localhost:1442" // Kupo URL ); // Only these services are available; the others throw UnsupportedOperationException: // - Kupo provides: UtxoService // - Ogmios provides: TransactionService, ProtocolParams TransactionService transactionService = backendService.getTransactionService(); UtxoService utxoService = backendService.getUtxoService(); EpochService epochService = backendService.getEpochService(); ``` For more details about Ogmios/Kupo integration, see the [Ogmios backend README](https://github.com/bloxbean/cardano-client-lib/tree/master/backend-modules/ogmios). ### Yaci DevKit / Yaci Store (Local Development) [Yaci Store](https://github.com/bloxbean/yaci-store) is a Cardano blockchain indexer that provides Blockfrost-compatible APIs. Since `BFBackendService` communicates using the Blockfrost API protocol, it works seamlessly with any Yaci Store instance — no code changes required. [Yaci DevKit](https://devkit.yaci.xyz/#get-started) bundles Yaci Store with a local Cardano devnet, giving you a complete local development environment. This is ideal for: - **Rapid iteration** — no waiting for testnet confirmations - **Offline development** — no internet or API keys required - **Deterministic testing** — full control over the local chain To connect to a Yaci DevKit instance running locally: ```java // Connect to Yaci DevKit's built-in Yaci Store (Blockfrost-compatible) BackendService backendService = new BFBackendService( "http://localhost:8080/api/v1/", "dummy-key" ); // Use exactly like any other BackendService QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); ``` To connect to a hosted Yaci Store instance: ```java BackendService backendService = new BFBackendService( "https://your-yaci-store-host/api/v1/", "dummy-key" ); ``` To install and start it, see [Get started with Yaci DevKit](https://devkit.yaci.xyz/#get-started) and [Yaci Store documentation](https://store.yaci.xyz). ### Nexus Integration Available from 0.8.0-pre7, through the `cardano-client-backend-nexus` module. See the [module README](https://github.com/bloxbean/cardano-client-lib/blob/master/backend-modules/nexus/README.md) for setup and the Nexus SDK's `Network` class. ```java BackendService backendService = new NexusBackendService("https://nexus.gerowallet.io", "", Network.MAINNET); ``` ## Advanced Usage ### Provider Switching Each provider is instantiated directly — pick the one that fits your infrastructure: ```java // Blockfrost BackendService bfService = new BFBackendService( Constants.BLOCKFROST_MAINNET_URL, "your-api-key"); // Koios BackendService koiosService = new KoiosBackendService( com.bloxbean.cardano.client.backend.koios.Constants.KOIOS_MAINNET_URL); // Kupmios (Ogmios + Kupo) BackendService kupmiosService = new KupmiosBackendService( "http://localhost:1337", "http://localhost:1442"); ``` ### Service Usage Examples Query blockchain data using backend services: ```java // Get UTXO information Result> utxos = utxoService.getUtxos("addr1...", 50, 1); System.out.println("Found " + utxos.getValue().size() + " UTXOs"); // Get transaction details Result transaction = transactionService.getTransaction("tx_hash..."); System.out.println("Transaction fee: " + transaction.getValue().getFees()); // Get address balance AddressContent addressInfo = addressService.getAddressInfo("addr1...").getValue(); System.out.println("Address balance: " + addressInfo.getAmount()); ``` ## Available Services | Service | Purpose | Key Methods | |---------|---------|-------------| | **TransactionService** | Transaction operations | `submitTransaction()`, `getTransaction()` | | **UtxoService** | UTXO management | `getUtxos()`, `getTxOutput()` | | **AddressService** | Address information | `getAddressInfo()`, `getTransactions()` | | **AssetService** | Asset and token data | `getAsset()`, `getAssetAddresses()` | | **BlockService** | Block information | `getLatestBlock()`, `getBlockByHash()` | | **EpochService** | Epochs and protocol parameters | `getLatestEpoch()`, `getProtocolParameters()` | | **MetadataService** | Transaction metadata | `getJSONMetadataByTxnHash()`, `getCBORMetadataByTxnHash()` | | **FeeCalculationService** | Fee estimation | `calculateFee()` | The Backend Services API provides a flexible and robust foundation for Cardano blockchain integration across multiple provider ecosystems. --- # Coin Selection API Source: https://cardano-client.dev/reference/coin-selection/ Summary: UTXO selection strategies for transaction building The Coin Selection API provides strategies for selecting UTXOs when building transactions. It includes built-in algorithms and extension points for custom selection logic. ## Overview - Multiple strategies: default, largest-first, random-improve - Configurable limits and datum handling - Inline datum and datum hash aware - Fallback chaining for robustness - UTXO discovery helpers via `UtxoSelector` ## Core Interfaces ### UtxoSelectionStrategy ```java public interface UtxoSelectionStrategy { Set select(AddressIterator addressIterator, List outputAmounts, String datumHash, PlutusData inlineDatum, Set utxosToExclude, int maxUtxoSelectionLimit); UtxoSelectionStrategy fallback(); void setIgnoreUtxosWithDatumHash(boolean ignore); } ``` ### UtxoSelector Utility interface to query UTXOs by predicate: ```java Optional findFirst(String address, Predicate predicate); List findAll(String address, Predicate predicate); default void setSearchByAddressVkh(boolean flag) { } ``` ## Built-in Selection Strategies ### Default ```java UtxoSelectionStrategy strategy = new DefaultUtxoSelectionStrategyImpl(utxoSupplier); strategy.setIgnoreUtxosWithDatumHash(true); // default true Set selected = strategy.select( AddressIterators.of(senderAddress), List.of(Amount.ada(10)), null, // datumHash null, // inline datum Collections.emptySet(), 20); ``` ### Largest First ```java UtxoSelectionStrategy strategy = new LargestFirstUtxoSelectionStrategy(utxoSupplier); Set selected = strategy.select( AddressIterators.of(senderAddress), List.of(Amount.ada(5)), null, null, Collections.emptySet(), 10); ``` ### Random Improve ```java UtxoSelectionStrategy strategy = new RandomImproveUtxoSelectionStrategy(utxoSupplier); strategy.setIgnoreUtxosWithDatumHash(false); Set selected = strategy.select( AddressIterators.of(senderAddress), List.of(Amount.ada(15)), null, null, Collections.emptySet(), 15); ``` ## Using UtxoSelector ```java UtxoSelector utxoSelector = new DefaultUtxoSelector(utxoSupplier); List adaUtxos = utxoSelector.findAll( senderAddress, utxo -> utxo.getAmount().stream().anyMatch(a -> "lovelace".equals(a.getUnit()))); Optional firstScriptUtxo = utxoSelector.findFirst( scriptAddress, utxo -> utxo.getReferenceScriptHash() != null); ``` If your backend supports lookup by address verification hash (`addr_vkh`), enable it: ```java utxoSelector.setSearchByAddressVkh(true); utxoSupplier.setSearchByAddressVkh(true); // only if supported by your supplier/backend ``` ## Advanced Scenarios ### Multi-Address Selection `select` takes an `AddressIterator` and moves to the next address until the amount is covered. To search your own list of addresses, implement `AddressIterator`. For an HD wallet, use `Tx.from(wallet)`, which scans the wallet's addresses for you. ### Datum Requirements ```java String requiredDatumHash = "..."; PlutusData requiredInlineDatum = BytesPlutusData.of("hello"); // UTXOs with a datum hash are skipped by default, so turn that off to select by hash strategy.setIgnoreUtxosWithDatumHash(false); Set withHash = strategy.select( AddressIterators.of(contractAddress), List.of(Amount.ada(5)), requiredDatumHash, null, Collections.emptySet(), 10); Set withInline = strategy.select( AddressIterators.of(contractAddress), List.of(Amount.ada(5)), null, requiredInlineDatum, Collections.emptySet(), 10); ``` ### Excluding Specific UTXOs ```java Set exclusions = Set.of(pendingUtxo1, pendingUtxo2); Set selected = strategy.select( AddressIterators.of(senderAddress), List.of(Amount.ada(10)), null, null, exclusions, 20); ``` ### Global Configuration ```java CoinselectionConfig.INSTANCE.setCoinSelectionLimit(50); int current = CoinselectionConfig.INSTANCE.getCoinSelectionLimit(); ``` ## Custom Strategy ```java public class CustomUtxoSelectionStrategy implements UtxoSelectionStrategy { private final UtxoSupplier utxoSupplier; private boolean ignoreUtxosWithDatumHash = true; public CustomUtxoSelectionStrategy(UtxoSupplier utxoSupplier) { this.utxoSupplier = utxoSupplier; } // implement select(...) using your own ordering/filtering @Override public UtxoSelectionStrategy fallback() { return new DefaultUtxoSelectionStrategyImpl(utxoSupplier); } @Override public void setIgnoreUtxosWithDatumHash(boolean ignore) { this.ignoreUtxosWithDatumHash = ignore; } } ``` ## QuickTx Integration Provide a custom strategy per composition: ```java UtxoSelectionStrategy custom = new LargestFirstUtxoSelectionStrategy(utxoSupplier); Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(senderAddress); TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withUtxoSelectionStrategy(custom) .withSigner(SignerProviders.signerFrom(sender)) .complete(); ``` --- # Composable Functions API Source: https://cardano-client.dev/reference/composable-functions/ Summary: Flexible transaction building using composable functions :::caution[Use QuickTx unless it cannot serve] This is the **internal implementation layer** beneath QuickTx, documented for the rare case where QuickTx genuinely cannot express what you need — a custom balancing strategy, or slotting your own logic between build stages. For everything else, reach for [QuickTx](/quicktx/overview/) instead. It produces the same transactions in a fraction of the code, and mistakes at this level (a missing balance step, inputs that do not cover outputs, a signer applied in the wrong order) fail in ways that are much harder to diagnose. If you are generating code with an AI agent, this page is explicitly **not** the recommended surface — see the [AI Starter Pack](/ai/starter-pack/). ::: Composable Functions expose functional building blocks (`TxBuilder`, `TxOutputBuilder`, `TxInputBuilder`, `TxSigner`, etc.) that you can chain to assemble transactions of any complexity. QuickTx is built on top of these primitives. Think of each function as a tiny, single-responsibility transformer. You wire them together in order—create outputs, derive the necessary inputs, add metadata, balance/fee, then sign—to produce a complete transaction. Because each step is explicit, this style is ideal when you need fine-grained control or want to slot in custom logic alongside the built-ins. ## Key Features - Chainable, functional style with `andThen(...)`/`and(...)` - Reusable helpers for inputs, outputs, metadata, fees, balancing, signing - Customizable: implement your own builders and plug them into the chain ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-function - **Depends on**: core, hd-wallet ## Usage Example (Simple Transfer Flow) This mirrors the getting-started simple transfer guide. ```java // 1) Define outputs Output output1 = Output.builder() .address(receiverAddress1) .assetName(LOVELACE) .qty(adaToLovelace(10)) .build(); Output output2 = Output.builder() .address(receiverAddress2) .assetName(LOVELACE) .qty(adaToLovelace(20)) .build(); MessageMetadata metadata = MessageMetadata.create() .add("First transfer transaction"); // 2) Build a TxBuilder chain TxBuilder txBuilder = output1.outputBuilder() .and(output2.outputBuilder()) .buildInputs(createFromSender(senderAddress, senderAddress)) // TxOutputBuilder -> TxBuilder .andThen(metadataProvider(metadata)) .andThen(balanceTx(senderAddress, 1)); // 3) Build and sign UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService()); ProtocolParamsSupplier paramsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService()); Transaction signedTx = new TxBuilderContext(utxoSupplier, paramsSupplier) .buildAndSign(txBuilder, signerFrom(senderAccount)); ``` ### How the chain executes 1) `outputBuilder().and(...)` collects intended outputs. 2) `buildInputs(...)` inspects those outputs (and mint info, if any) and selects the needed inputs + change. 3) `metadataProvider(...)` attaches aux data. 4) `balanceTx(...)` finalizes fees and balances change. 5) `buildAndSign(...)` applies the signer to the composed transaction. Because each step is a function, you can swap any link in the chain with a custom implementation—for example, replacing input selection with a bespoke UTXO picker or adding an extra fee calculator. ## Core Interfaces (Essentials) - `TxBuilder` (functional interface): `(TxBuilderContext ctx, Transaction txn) -> void`, chain with `andThen(...)`. - `TxOutputBuilder`: `accept(TxBuilderContext ctx, List outputs)`; combine with `and(...)`; convert to `TxBuilder` via `buildInputs(...)`. - `TxInputBuilder`: `apply(TxBuilderContext ctx, List outputs) -> Result(inputs, changeOutputs)`. - `TxSigner`: `sign(TxBuilderContext ctx, Transaction txn) -> Transaction`; compose with `andThen(...)`. ### When to reach for composable functions - You need ordered control over how inputs and outputs are shaped and balanced, and QuickTx can't express it. - You're extending the library with a custom builder, such as a specialized fee calculator or metadata injector. ## Helper Classes (selected) - `OutputBuilders`: `createFromOutput(Output output)` and more. - `InputBuilders`: `createFromSender(String sender, String changeAddress)`, HD wallet variants, datum/inline datum aware. - `AuxDataProviders`: metadata helpers (e.g., `metadataProvider`). - `FeeCalculators`, `MinAdaCheckers`, `ChangeOutputAdjustments`, `BalanceTxBuilders`: fee/min-ADA/change handling. - `SignerProviders`: `signerFrom(Account... signers)` and other signers. ## Context and Execution - `new TxBuilderContext(UtxoSupplier, ProtocolParamsSupplier|ProtocolParams)`: creates the execution context. - `build(TxBuilder)`: returns unsigned `Transaction`. - `buildAndSign(TxBuilder, TxSigner)`: returns signed `Transaction`. - `build(Transaction txn, TxBuilder)`: transforms an existing `Transaction`. - Per-context options: `mergeOutputs(...)`, `withSearchUtxoByAddressVkh(...)`, `withSerializationEra(Era era)`, `setUtxoSelectionStrategy(...)`, `setUtxoSelector(...)`, `withTxnEvaluator(...)`, `withCostMdls(...)`. ## Custom Composable Function ```java public class CustomOutputBuilder implements TxOutputBuilder { @Override public void accept(TxBuilderContext ctx, List outputs) { // Add a custom output or mutate outputs } } TxBuilder txBuilder = new CustomOutputBuilder() .buildInputs(createFromSender(sender, sender)) .andThen(balanceTx(sender, 1)); ``` ## Best Practices - Order functions logically: outputs -> inputs -> metadata/aux data -> fee/balance -> signing. - Reuse provided helpers where possible; extend with custom builders for special logic. - Keep TxBuilder chains deterministic; handle exceptions from custom functions. - When mixing with QuickTx, you can embed custom `TxBuilder` steps via QuickTx hooks (`preBalanceTx`, `postBalanceTx`). --- # Crypto API Source: https://cardano-client.dev/reference/crypto-api/ Summary: Key generation, signing, hashing, and HD derivation utilities The Crypto API provides key generation, signing, hashing, encoding helpers, and CIP-1852 HD derivation utilities used across the Cardano Client Library. ## Overview - Key generation: Ed25519 keys and key pairs - Mnemonic management: BIP39 generation and validation - HD derivation: CIP-1852 paths for Cardano - Sign/verify: Ed25519/BIP32-Ed25519 signing - Hashing: Blake2b helpers - Encoding: Bech32 and hex utilities ## Key Generation ```java Keys keyPair = KeyGenUtil.generateKey(); SecretKey privateKey = keyPair.getSkey(); VerificationKey publicKey = keyPair.getVkey(); byte[] keyHashBytes = Blake2bUtil.blake2bHash224(publicKey.getBytes()); String keyHashHex = HexUtil.encodeHexString(keyHashBytes); // Convenience: 28-byte key hash as hex String keyHash = KeyGenUtil.getKeyHash(publicKey); ``` ## Mnemonic Management ```java // Generate mnemonic String mnemonic24 = MnemonicUtil.generateNew(Words.TWENTY_FOUR); // Validate mnemonic MnemonicUtil.validateMnemonic(mnemonic24); // Convert to entropy and seed byte[] entropy = MnemonicCode.INSTANCE.toEntropy(mnemonic24); byte[] seed = MnemonicCode.toSeed(Arrays.asList(mnemonic24.split(" ")), ""); ``` Derive directly from entropy (e.g., when you already have raw bytes): ```java byte[] entropyBytes = ...; HdKeyPair rootFromEntropy = new HdKeyGenerator().getRootKeyPairFromEntropy(entropyBytes); ``` ## CIP-1852 HD Derivation Common derivation helpers (no `createAccountDerivationPath` factory exists): - External address: `DerivationPath.createExternalAddressDerivationPath(index)` - Internal address: `DerivationPath.createInternalAddressDerivationPath(index)` - Stake address: `DerivationPath.createStakeAddressDerivationPath()` - Account-specific variants: `createExternalAddressDerivationPathForAccount`, `createInternalAddressDerivationPathForAccount`, `createStakeAddressDerivationPathForAccount` - Other roles: `createDRepKeyDerivationPathForAccount`, `createCommitteeColdKeyDerivationPathForAccount`, `createCommitteeHotKeyDerivationPathForAccount` ```java CIP1852 cip1852 = new CIP1852(); String mnemonic = "..."; DerivationPath externalPath = DerivationPath.createExternalAddressDerivationPath(0); HdKeyPair keyPair = cip1852.getKeyPairFromMnemonic(mnemonic, externalPath); HdKeyPair rootKeyPair = cip1852.getRootKeyPairFromMnemonic(mnemonic); ``` ### Advanced derivation options ```java // Specify derivation type and passphrase HdKeyPair ledgerPair = cip1852.getKeyPairFromMnemonic( mnemonic, "optional-passphrase", externalPath, Bip32Type.LEDGER); HdKeyPair trezorRoot = cip1852.getRootKeyPairFromMnemonic(mnemonic, Bip32Type.TREZOR); ``` ### Deriving from existing keys ```java byte[] accountXprv = ...; // account private key (xprv) DerivationPath rolePath = DerivationPath.createExternalAddressDerivationPathForAccount(0); // Get child key pair from account key HdKeyPair fromAccount = cip1852.getKeyPairFromAccountKey(accountXprv, rolePath); byte[] accountXpub = ...; // account public key HdPublicKey paymentPub = cip1852.getPublicKeyFromAccountPubKey(accountXpub, 0, 0); // role 0, index 0 byte[] rootKey = ...; // root private key bytes HdKeyPair fromRoot = cip1852.getKeyPairFromRootKey(rootKey, rolePath); ``` ## Signing and Verification ```java SigningProvider signingProvider = new EdDSASigningProvider(); byte[] message = "Hello, Cardano!".getBytes(); byte[] sk = privateKey.getBytes(); // 32 bytes byte[] xsk = new byte[64]; // extended private key if available byte[] sig1 = signingProvider.sign(message, sk); byte[] sig2 = signingProvider.signExtended(message, xsk); // 64-byte extended key // Legacy overload (with explicit public key) remains for compatibility byte[] sig3 = signingProvider.signExtended(message, xsk, publicKey.getBytes()); boolean isValid = signingProvider.verify(sig1, message, publicKey.getBytes()); ``` Configure a custom provider: ```java CryptoConfiguration.INSTANCE.setSigningProvider(new CustomSigningProvider()); SigningProvider provider = CryptoConfiguration.INSTANCE.getSigningProvider(); ``` ## Hash Functions ```java byte[] input = "Hello, Cardano!".getBytes(); byte[] hash160 = Blake2bUtil.blake2bHash160(input); byte[] hash224 = Blake2bUtil.blake2bHash224(input); byte[] hash256 = Blake2bUtil.blake2bHash256(input); String hex160 = HexUtil.encodeHexString(hash160); ``` ## Encoding and Decoding ```java // Bech32 String bech32Key = Bech32.encode(publicKey.getBytes(), "addr_vk"); Bech32.Bech32Data decoded = Bech32.decode(bech32Key); // Hex String hex = HexUtil.encodeHexString(new byte[]{0x01, 0x02}); byte[] bytes = HexUtil.decodeHexString(hex); ``` ## Error Handling Handle failures explicitly, e.g., invalid mnemonics or derivation errors: ```java try { MnemonicUtil.validateMnemonic(mnemonic); } catch (AddressRuntimeException e) { // invalid mnemonic } try { HdKeyPair keyPair = cip1852.getKeyPairFromMnemonic(mnemonic, externalPath); } catch (CryptoException e) { // derivation failed } ``` ## Best Practices - Never log or persist raw private keys or mnemonics; clear sensitive byte arrays after use. - Validate mnemonics before derivation. - Reuse signing providers for performance in hot paths. - Pick the correct derivation helper for the role (external/internal/stake/drep/committee). --- # HD Wallet API Source: https://cardano-client.dev/reference/hd-wallet-api/ Summary: Hierarchical deterministic wallet management with BIP32/CIP1852 support The HD Wallet API provides hierarchical deterministic wallet functionality, allowing you to derive multiple keys and addresses from a single master key. This is an alternative to the Account API that supports enhanced privacy through key derivation and follows BIP32/CIP1852 standards. ## Key Features - **Hierarchical Key Derivation**: Generate multiple keys from a single master key - **Separate Addresses**: Use a new address for each payment. Addresses in one account share a stake key, so they can be linked on-chain - **BIP32/CIP1852 Compliance**: Follow industry standards for key derivation - **Multi-Account Support**: Manage multiple accounts within a single wallet - **Deterministic Generation**: Same mnemonic always generates same addresses ## Core Classes ### Wallet Interface The main interface for HD wallet operations, providing methods for address generation, account management, and transaction signing. ### DefaultWallet Class The default implementation of the Wallet interface, supporting standard HD wallet functionality. Create one with the static methods on `Wallet`: `Wallet.create(network)` for a new mnemonic, or `Wallet.createFromMnemonic(network, mnemonic)` to restore one. **Key Methods:** - `getBaseAddress()` - Get base address at specific index - `getEntAddress()` - Get enterprise address at specific index - `sign()` - Sign transactions with wallet keys ## Usage Examples ### Creating HD Wallets Create wallets with different mnemonic lengths and configurations: ```java // Create wallet with 24-word mnemonic (default) Wallet wallet = Wallet.create(Networks.testnet()); String mnemonic = wallet.getMnemonic(); System.out.println("Generated 24-word mnemonic"); // Create wallet with 15-word mnemonic Wallet wallet15 = Wallet.create(Networks.testnet(), Words.FIFTEEN); String mnemonic15 = wallet15.getMnemonic(); System.out.println("Generated 15-word mnemonic"); // Create wallet with 12-word mnemonic Wallet wallet12 = Wallet.create(Networks.testnet(), Words.TWELVE); String mnemonic12 = wallet12.getMnemonic(); System.out.println("Generated 12-word mnemonic"); ``` ### Restoring Wallets Restore wallets from existing mnemonic phrases: ```java // Restore wallet from mnemonic String mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; Wallet restoredWallet = Wallet.createFromMnemonic(Networks.mainnet(), mnemonic); // Restore wallet for specific account Wallet accountWallet = Wallet.createFromMnemonic(Networks.mainnet(), mnemonic, 1); ``` ### Address Generation Generate multiple addresses from a single wallet: ```java // Generate base addresses at different indexes Address address0 = wallet.getBaseAddress(0); Address address1 = wallet.getBaseAddress(1); Address address2 = wallet.getBaseAddress(2); System.out.println("Address 0: " + address0.getAddress()); System.out.println("Address 1: " + address1.getAddress()); System.out.println("Address 2: " + address2.getAddress()); // Generate enterprise addresses (no staking) Address entAddress0 = wallet.getEntAddress(0); Address entAddress1 = wallet.getEntAddress(1); System.out.println("Enterprise Address 0: " + entAddress0.getAddress()); System.out.println("Enterprise Address 1: " + entAddress1.getAddress()); ``` ### Multi-Account Management Manage multiple accounts within a single wallet: ```java // Get addresses for different accounts Address account0Address = wallet.getBaseAddress(0, 0); // Account 0, Index 0 Address account1Address = wallet.getBaseAddress(1, 0); // Account 1, Index 0 Address account2Address = wallet.getBaseAddress(2, 0); // Account 2, Index 0 // Switch between accounts wallet.setAccountNo(0); // Switch to account 0 Address currentAccountAddress = wallet.getBaseAddress(0); wallet.setAccountNo(1); // Switch to account 1 Address newAccountAddress = wallet.getBaseAddress(0); ``` ### Account Object Access Get Account objects for transaction operations: ```java // Get account at specific index for current account number Account account0 = wallet.getAccountAtIndex(0); Account account1 = wallet.getAccountAtIndex(1); // Get account for specific account number and index Account specificAccount = wallet.getAccount(1, 5); // Account 1, Index 5 // Use accounts for transaction signing String accountAddress = account0.baseAddress(); Transaction signedTx = account0.sign(transaction); ``` ## Advanced Usage ### A New Address for Each Payment To give each payment its own address, increment the address index. This separates payment credentials, but base addresses in one account share a stake key, so anyone can link them on-chain: ```java // Generate a fresh address for each transaction Address address0 = wallet.getBaseAddress(0); // First transaction Address address1 = wallet.getBaseAddress(1); // Second transaction Address address2 = wallet.getBaseAddress(2); // Third transaction ``` ### Wallet Recovery and Scanning Implement wallet recovery with address scanning: ```java // Configure wallet scanning DefaultWallet wallet = new DefaultWallet(Networks.testnet()); // Set gap limit for address scanning wallet.setGapLimit(50); // Scan 50 consecutive empty addresses // Set specific indexes to scan (bypass gap limit) wallet.setIndexesToScan(new int[]{0, 1, 2, 5, 10, 20}); // Scan for used addresses public List
scanForUsedAddresses(Wallet wallet, UtxoSupplier utxoSupplier) { List
usedAddresses = new ArrayList<>(); int gapCount = 0; int index = 0; while (gapCount < wallet.getGapLimit()) { Address address = wallet.getBaseAddress(index); List utxos = utxoSupplier.getAll(address.getAddress()); if (!utxos.isEmpty()) { usedAddresses.add(address); gapCount = 0; // Reset gap counter } else { gapCount++; } index++; } return usedAddresses; } ``` ### Key Management Access and manage wallet keys securely: ```java // Get root key pair (if available) Optional rootKeyPair = wallet.getRootKeyPair(); if (rootKeyPair.isPresent()) { HdKeyPair keyPair = rootKeyPair.get(); System.out.println("Root key available"); } // Get root private key bytes Optional rootPrivateKey = wallet.getRootPvtKey(); if (rootPrivateKey.isPresent()) { byte[] keyBytes = rootPrivateKey.get(); System.out.println("Root private key: " + keyBytes.length + " bytes"); } // Get stake address for delegation String stakeAddress = wallet.getStakeAddress(); System.out.println("Stake Address: " + stakeAddress); ``` ### Transaction Signing With QuickTx, use the wallet as the sender and the signer. It selects the wallet's UTXOs and signs with the right keys: ```java Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(wallet); TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withSigner(SignerProviders.signerFrom(wallet)) .completeAndWait(); ``` To sign a transaction you built yourself, pass the `WalletUtxo`s it spends. Each one needs its derivation path, which the wallet's UTXO selection sets; without paths, signing fails with "No signers found!": ```java Set walletUtxos = ...; // the wallet UTXOs this transaction spends Transaction signedTx = wallet.sign(transaction, walletUtxos); // Sign with stake key Transaction stakeSignedTx = wallet.signWithStakeKey(transaction); // Combined signing Transaction fullySignedTx = wallet.sign( wallet.signWithStakeKey(transaction), walletUtxos ); ``` ## Best Practices ### Secure Wallet Creation ```java // Generate wallet securely Wallet secureWallet = Wallet.create(Networks.mainnet()); String mnemonic = secureWallet.getMnemonic(); // Store mnemonic securely (encrypted storage recommended) // Never store mnemonic in plain text storeSecurely(mnemonic); // Recreate wallet when needed Wallet restoredWallet = Wallet.createFromMnemonic(Networks.mainnet(), mnemonic); ``` ### Address Management Strategy Use different account numbers to organize addresses by purpose. Each account has its own stake key: ```java // Personal addresses (account 0) Address personalAddr = wallet.getBaseAddress(0, 0); // Savings addresses (account 1) Address savingsAddr = wallet.getBaseAddress(1, 0); // Business addresses (account 2) Address businessAddr = wallet.getBaseAddress(2, 0); ``` ## HD Wallet vs Account API Comparison | Feature | HD Wallet API | Account API | |---------|---------------|-------------| | **Key Management** | Hierarchical key derivation | Single key pair | | **Address Generation** | Multiple derived addresses | Fixed addresses | | **Addresses per payment** | A new one each time, linkable through the stake key | One per account instance | | **Use Case** | Applications managing many addresses | Simple applications | | **Complexity** | Medium to High | Low | | **Multi-Account** | Native support | Manual management | Use Account API if your application is using only one address or account. --- # Metadata API Source: https://cardano-client.dev/reference/metadata-api/ Summary: Transaction metadata creation, management, and serialization The Metadata API provides comprehensive functionality for creating, managing, and serializing transaction metadata in the Cardano blockchain. It supports both CBOR and JSON formats, enabling you to attach structured data to transactions with type safety and validation. ## Key Features - **CBOR Serialization**: Native CBOR format support for Cardano metadata - **JSON Conversion**: Convert between JSON and CBOR metadata formats - **Structured Data**: Support for maps, lists, and nested data structures - **Type Safety**: Strong typing for different metadata value types - **Long strings**: Strings in metadata maps and lists are split into 64-byte chunks - **Builder Pattern**: Convenient builder pattern for metadata construction ## Core Classes ### Metadata Interface The main interface for all metadata operations, providing methods for data manipulation and serialization. ### MetadataBuilder Utility class for creating and converting metadata objects between different formats. ### MetadataMap Interface for map-type metadata structures with key-value pairs. ### MetadataList Interface for list-type metadata structures containing ordered elements. **Key Methods:** - `MetadataBuilder.createMetadata()` - Create new metadata instance - `put()` - Add key-value pairs to metadata - `serialize()` - Convert to CBOR bytes - `toJson()` - Convert to JSON format ## Usage Examples ### Creating Basic Metadata Create simple metadata with key-value pairs: ```java // Create empty metadata Metadata metadata = MetadataBuilder.createMetadata(); // Add simple key-value pairs metadata.put(1L, "Hello Cardano"); metadata.put(2L, BigInteger.valueOf(100)); metadata.put(3L, "metadata".getBytes()); // Add boolean value (as integer) metadata.put(4L, BigInteger.valueOf(1)); // true metadata.put(5L, BigInteger.valueOf(0)); // false System.out.println("Basic metadata created with 5 entries"); ``` ### Creating Structured Metadata Create complex metadata with nested structures: ```java // Create metadata with nested maps Metadata structuredMetadata = MetadataBuilder.createMetadata(); // Create nested map MetadataMap nestedMap = MetadataBuilder.createMap(); nestedMap.put("name", "Cardano Transaction"); nestedMap.put("version", BigInteger.valueOf(1)); nestedMap.put("timestamp", BigInteger.valueOf(System.currentTimeMillis())); // Create list of values MetadataList valuesList = MetadataBuilder.createList(); valuesList.add("value1"); valuesList.add("value2"); valuesList.add(BigInteger.valueOf(42)); // Add to main metadata structuredMetadata.put(100L, nestedMap); structuredMetadata.put(200L, valuesList); structuredMetadata.put(300L, "Simple string value"); System.out.println("Structured metadata created"); ``` ### Metadata Serialization Convert metadata to different formats: ```java Metadata metadata = MetadataBuilder.createMetadata(); metadata.put(674L, "Simple message"); metadata.put(675L, BigInteger.valueOf(12345)); // Serialize to CBOR bytes byte[] cborBytes = metadata.serialize(); System.out.println("CBOR size: " + cborBytes.length + " bytes"); // Convert to JSON string String jsonString = MetadataBuilder.toJson(metadata); System.out.println("JSON: " + jsonString); // Convert to hex string for inspection String hexString = HexUtil.encodeHexString(cborBytes); System.out.println("CBOR Hex: " + hexString); ``` ### JSON to Metadata Conversion Convert JSON metadata to CBOR format: ```java // JSON metadata string String jsonMetadata = """ { "674": { "msg": ["Hello", "Cardano", "World"] }, "675": { "field1": "value1", "field2": 42 } } """; // Convert JSON to Metadata Metadata metadata = MetadataBuilder.metadataFromJson(jsonMetadata); // Serialize to CBOR byte[] cborBytes = metadata.serialize(); System.out.println("Converted JSON to CBOR: " + cborBytes.length + " bytes"); ``` ## Advanced Usage ### CIP-20 Metadata (Message Metadata) For CIP-20 compliant message metadata, use the dedicated `MessageMetadata` class from the CIP20 module instead of building the label-674 structure manually. See the [CIP20 API page](/standards/cip20/) for details. ### Custom Metadata Structures Create application-specific metadata structures: ```java public class ApplicationMetadata { public static Metadata createTransactionMetadata(String appName, String version, Map customData) { Metadata metadata = MetadataBuilder.createMetadata(); // Application info (label 1000) MetadataMap appInfo = MetadataBuilder.createMap(); appInfo.put("name", appName); appInfo.put("version", version); appInfo.put("timestamp", BigInteger.valueOf(System.currentTimeMillis())); metadata.put(1000L, appInfo); // Custom data (label 1001) if (customData != null && !customData.isEmpty()) { MetadataMap customMap = MetadataBuilder.createMap(); for (Map.Entry entry : customData.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); if (value instanceof String) { customMap.put(key, (String) value); } else if (value instanceof Number) { customMap.put(key, BigInteger.valueOf(((Number) value).longValue())); } else if (value instanceof byte[]) { customMap.put(key, (byte[]) value); } } metadata.put(1001L, customMap); } return metadata; } } // Usage Map customData = new HashMap<>(); customData.put("transactionId", "tx_12345"); customData.put("amount", 1000000); customData.put("description", "Payment for services"); Metadata appMetadata = ApplicationMetadata.createTransactionMetadata( "MyCardanoApp", "1.0.0", customData ); ``` The Metadata API provides full support for standard metadata formats while allowing custom metadata structures for application-specific needs. --- # Modules & artifacts Source: https://cardano-client.dev/reference/modules/ Summary: Modules and Dependencies In 0.4.0 and later, the library has been divided into smaller modules. These modules can be added to a project based on requirement. **Group Id:** com.bloxbean.cardano For **simple** setup, you can use **cardano-client-lib** and one of the backend provider as dependency. | Modules | Artifact Id | Description | |-----------------------------------------------------------------------------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [cardano-client-lib](https://github.com/bloxbean/cardano-client-lib/tree/master/) | cardano-client-lib | Includes core, function, cip and quicktx. Backend providers and the preview modules below are separate dependencies.
**(Recommended for most application)** | | [Backend Api](https://github.com/bloxbean/cardano-client-lib/tree/master/backend) | cardano-client-backend | Defines backend apis which are implemented by provider specific module. | | [Blockfrost Backend](https://github.com/bloxbean/cardano-client-lib/tree/master/backend-modules/blockfrost) | cardano-client-backend-blockfrost | Provides integration with [Blockfrost](https://blockfrost.io/) | | [Koios Backend](https://github.com/bloxbean/cardano-client-lib/tree/master/backend-modules/koios) | cardano-client-backend-koios | Provides integration with [Koios](https://www.koios.rest/) | | [Ogmios/Kupo Backend](https://github.com/bloxbean/cardano-client-lib/tree/master/backend-modules/ogmios) | cardano-client-backend-ogmios | Provides integration with [Ogmios](https://ogmios.dev/) and [Kupo](https://cardanosolutions.github.io/kupo/).
**Supported Apis :** submitTransaction, evaluateTx, Kupo support (UtxoService) | For fine-grained dependency management, add one or more below modules as required. | Modules | Artifact Id | Description | |------------------------------------------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [common](https://github.com/bloxbean/cardano-client-lib/tree/master/common) | cardano-client-common | Contains common utilities (HexUtil, JsonUtil, Cbor Utils etc). This module doesn't depend on any other module.
**Dependencies:** None | | [crypto](https://github.com/bloxbean/cardano-client-lib/tree/master/crypto) | cardano-client-crypto | Provides implementation for standards like Bip32, Bip39, CIP1852 and other crypto primitives
**Dependencies:** common | | [address](https://github.com/bloxbean/cardano-client-lib/tree/master/address) | cardano-client-address | Supports derivation of various types of Cardano addresses (Base, Enterprise, Pointer, Stake etc)
**Dependencies:** common, crypto, common-spec | | [metadata](https://github.com/bloxbean/cardano-client-lib/tree/master/metadata) | cardano-client-metadata | Provides simple api to generate and serialize generic metadata
**Dependencies:** common, crypto | | [transaction-spec](https://github.com/bloxbean/cardano-client-lib/tree/master/transaction-spec) | cardano-client-transaction-spec | Transaction model and CBOR serialization | | [core](https://github.com/bloxbean/cardano-client-lib/tree/master/core) | cardano-client-core | Contains Account, transaction building blocks and common apis.
**Dependencies:** common, crypto, common-spec, metadata, transaction-spec, core-api, plutus, coinselection, governance | | [quicktx](https://github.com/bloxbean/cardano-client-lib/tree/master/quicktx) | cardano-client-quicktx | The recommended, declarative API for building and submitting transactions.
**Dependencies:** core, function, backend, hd-wallet | | [function](https://github.com/bloxbean/cardano-client-lib/tree/master/function) | cardano-client-function | Composable functions: the internal layer beneath QuickTx.
**Dependencies:** core, hd-wallet | | [cip](https://github.com/bloxbean/cardano-client-lib/tree/master/cip) | cardano-client-cip | A umbrella module which provides a simple way to get available cip implementations (cip25, cip8 etc.)
**Dependencies:** cip8, cip20, cip25, cip27, cip30, cip67, cip68, cip102 | | [cip8](https://github.com/bloxbean/cardano-client-lib/tree/master/cip/cip8) | cardano-client-cip8 | [CIP 8 - Message Signing](https://cips.cardano.org/cips/cip8/)
**Dependencies:** common, crypto | | [cip20](https://github.com/bloxbean/cardano-client-lib/tree/master/cip/cip20) | cardano-client-cip20 | [CIP 20 - Transaction message/comment metadata](https://cips.cardano.org/cips/cip20/)
**Dependencies:** metadata | | [cip25](https://github.com/bloxbean/cardano-client-lib/tree/master/cip/cip25) | cardano-client-cip25 | [CIP 25 - Media NFT Metadata Standard](https://cips.cardano.org/cips/cip25/)
**Dependencies:** metadata | | [cip27](https://github.com/bloxbean/cardano-client-lib/tree/master/cip/cip27) | cardano-client-cip27 | [CIP 27 - CNFT Community Royalties Standard](https://cips.cardano.org/cips/cip27/)
**Dependencies:** cip25 | | [cip30](https://github.com/bloxbean/cardano-client-lib/tree/master/cip/cip30) | cardano-client-cip30 | [CIP 30 - Cardano dApp-Wallet Web Bridge](https://cips.cardano.org/cips/cip30/)
**Dependencies:** cip8, core | ## Preview modules These modules are in the [latest preview](/start-here/installation/#versions), and their APIs may change before the stable release. See [what's new](/project/whats-new/) for details. | Modules | Artifact Id | Available from | Description | |---------|-------------|----------------|-------------| | [txflow](https://github.com/bloxbean/cardano-client-lib/tree/master/txflow) | cardano-client-txflow | 0.8.0 preview | Multi-step transaction orchestration and TxStream | | [txflow-store-rdbms](https://github.com/bloxbean/cardano-client-lib/tree/master/txflow-extensions/txflow-store-rdbms) | cardano-client-txflow-store-rdbms | 0.8.0 preview | Durable TxFlow and TxStream storage on a SQL database | | [merkle-patricia-forestry](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/merkle-patricia-forestry) | cardano-client-merkle-patricia-forestry | 0.8.0 preview | Merkle Patricia Forestry, compatible with Aiken's on-chain library | | [merkle-patricia-forestry-rocksdb](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/merkle-patricia-forestry-rocksdb) | cardano-client-merkle-patricia-forestry-rocksdb | 0.8.0 preview | MPF storage on RocksDB, with versions and garbage collection | | [merkle-patricia-forestry-rdbms](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/merkle-patricia-forestry-rdbms) | cardano-client-merkle-patricia-forestry-rdbms | 0.8.0 preview | MPF storage on PostgreSQL, H2 or SQLite | | [jellyfish-merkle](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/jellyfish-merkle) | cardano-client-jellyfish-merkle | 0.8.0 preview | Jellyfish Merkle Tree for versioned state | | [jellyfish-merkle-rocksdb](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/jellyfish-merkle-rocksdb) | cardano-client-jellyfish-merkle-rocksdb | 0.8.0 preview | JMT storage on RocksDB | | [jellyfish-merkle-rdbms](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/jellyfish-merkle-rdbms) | cardano-client-jellyfish-merkle-rdbms | 0.8.0 preview | JMT storage on PostgreSQL, H2 or SQLite | | [programmable-token](https://github.com/bloxbean/cardano-client-lib/tree/master/programmable-token) | cardano-client-programmable-token | 0.8.0-pre7 | [CIP-113 programmable tokens](/learn/cip113-programmable-tokens/) | | [nexus backend](https://github.com/bloxbean/cardano-client-lib/tree/master/backend-modules/nexus) | cardano-client-backend-nexus | 0.8.0-pre7 | Backend for the [Nexus](https://nexus.gerowallet.io) API | --- # Supplier Interfaces API Source: https://cardano-client.dev/reference/supplier-interfaces/ Summary: Extensible interfaces for custom backend data providers The Supplier Interfaces API provides extensible interfaces for integrating custom data providers with the Cardano Client Library. These interfaces provide the **essential APIs required to build and submit transactions**, making them the quickest and clearest way to integrate any third-party data provider. ## What are Supplier Interfaces? Supplier interfaces provide the core functionality needed for transaction building and submission: - **Getting UTXOs** - Retrieve unspent transaction outputs for addresses - **Fetching protocol parameters** - Get current network protocol parameters - **Evaluating transaction script costs** - Calculate execution costs for Plutus scripts - **Submitting transactions** - Send transactions to the blockchain **Important:** The transaction builder APIs depend on **Supplier interfaces**, not directly on `BackendService`. This provides the right level of abstraction to integrate any third-party provider with minimal effort. ### Supplier Interfaces vs BackendService While `BackendService` provides many additional APIs useful for dApps (address history, asset metadata, block data, etc.), creating a new `BackendService` implementation requires significantly more effort. Supplier interfaces focus only on what's **strictly required for transaction building and submission**, making integration faster and simpler. ## Key Features - **Minimal Integration Surface**: Only implement what's needed for transactions - **Extensible Architecture**: Define custom data provider implementations - **Interface Standardization**: Consistent contracts for different data sources - **Provider Flexibility**: Switch between implementations seamlessly - **Testing Support**: Mock implementations for unit and integration testing - **Service Composition**: Combine multiple suppliers for complex scenarios ## Core Classes ### Supplier Interfaces - `UtxoSupplier` - Provides UTXO data for addresses and transactions - `ProtocolParamsSupplier` - Supplies current protocol parameters - `TransactionProcessor` - Handles transaction submission and evaluation - `ScriptSupplier` - Provides Plutus scripts by hash - `TransactionEvaluator` - Evaluates transaction execution costs ### Default Implementations The library provides default implementations that are automatically used when creating `QuickTxBuilder` from `BackendService`: - `DefaultUtxoSupplier` - Default UTXO supplier from BackendService - `DefaultProtocolParamsSupplier` - Default protocol parameters from BackendService - `DefaultTransactionProcessor` - Default transaction processor from BackendService These are instantiated automatically when using: ```java QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); ``` ### Third-Party Implementations For specialized use cases, third-party supplier implementations are available: - `OgmiosProtocolParamSupplier` - Ogmios-based protocol parameters - `OgmiosTransactionProcessor` - Ogmios transaction processing - `KupoUtxoSupplier` - Kupo-based UTXO provider - `OgmiosTransactionEvaluator` - Ogmios script evaluation ## Usage Examples ### Custom UtxoSupplier Implementation Implement a custom UTXO supplier: ```java public class CustomUtxoSupplier implements UtxoSupplier { @Override public List getPage(String address, Integer nrOfItems, Integer page, OrderEnum order) { // Implement UTXO fetching logic return fetchUtxosFromCustomSource(address, nrOfItems, page, order); } @Override public Optional getTxOutput(String txHash, int outputIndex) { // Implement transaction output retrieval return fetchTransactionOutput(txHash, outputIndex); } @Override public List getAll(String address) { // Fetch all UTXOs for address return fetchAllUtxosFromSource(address); } } ``` ### Custom ProtocolParamsSupplier Implement protocol parameters supplier: ```java public class CustomProtocolParamsSupplier implements ProtocolParamsSupplier { @Override public ProtocolParams getProtocolParams() { // Fetch current protocol parameters from your source return ProtocolParams.builder() .minFeeA(44) .minFeeB(155381) .maxBlockSize(65536) .maxTxSize(16384) .keyDeposit("2000000") .poolDeposit("500000000") .build(); } } ``` ### Using Custom Suppliers Integrate custom suppliers with QuickTx via constructor: ```java // Create custom suppliers UtxoSupplier customUtxoSupplier = new CustomUtxoSupplier(); ProtocolParamsSupplier protocolParamsSupplier = new CustomProtocolParamsSupplier(); TransactionProcessor transactionProcessor = new CustomTransactionProcessor(); // Use with QuickTx - suppliers must be provided through constructor QuickTxBuilder quickTxBuilder = new QuickTxBuilder( customUtxoSupplier, protocolParamsSupplier, transactionProcessor ); // Build and submit transaction Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(senderAddress); Result result = quickTxBuilder.compose(tx) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(System.out::println); ``` ## Built-in Implementations ### Ogmios Suppliers Use Ogmios-based suppliers: ```java // Ogmios protocol parameters OgmiosProtocolParamSupplier ogmiosProtocolParams = new OgmiosProtocolParamSupplier("http://localhost:1337"); // Ogmios transaction processor OgmiosTransactionProcessor ogmiosProcessor = new OgmiosTransactionProcessor("http://localhost:1337"); ``` ### Kupo Suppliers Use Kupo for UTXO data combined with Ogmios for other services: ```java // Kupo UTXO supplier KupoUtxoSupplier kupoUtxoSupplier = new KupoUtxoSupplier("http://localhost:1442"); // Ogmios for protocol params and transaction processing OgmiosProtocolParamSupplier protocolParamsSupplier = new OgmiosProtocolParamSupplier("http://localhost:1337"); OgmiosTransactionProcessor transactionProcessor = new OgmiosTransactionProcessor("http://localhost:1337"); // Use with QuickTx - all suppliers provided through constructor QuickTxBuilder builder = new QuickTxBuilder( kupoUtxoSupplier, protocolParamsSupplier, transactionProcessor ); ``` ### Combining with BackendService You can also combine a custom supplier with BackendService: ```java // Use Kupo for UTXOs, BackendService for everything else KupoUtxoSupplier kupoUtxoSupplier = new KupoUtxoSupplier("http://localhost:1442"); BackendService backendService = new BFBackendService( Constants.BLOCKFROST_PREPROD_URL, "your-project-id" ); QuickTxBuilder builder = new QuickTxBuilder(backendService, kupoUtxoSupplier); ``` The Supplier Interfaces API provides extensible integration points for custom data providers and backend services. --- # Aiken integration Source: https://cardano-client.dev/smart-contracts/aiken/ Summary: Aiken Integration Apis Using [Aiken Java Binding](https://github.com/bloxbean/aiken-java-binding) provides many useful functionalities, such as "off-chain" script cost evaluation and compiling parameterized contracts. Aiken Java Binding offers a JNI wrapper around the Aiken library, making it easier to integrate with Java-based applications. The binding bundles native libraries for macOS, Windows and x86-64 Linux, but not ARM64 Linux. On ARM64 Linux, use [Scalus](/smart-contracts/scalus/) instead. ## Dependencies To use Aiken Java Binding in your project, include the following dependency in your build configuration. ### Maven (`pom.xml`) ```xml com.bloxbean.cardano aiken-java-binding 0.1.0 ``` ### Gradle (`build.gradle`) ```groovy implementation 'com.bloxbean.cardano:aiken-java-binding:0.1.0' ``` ## Script Cost Evaluation Aiken Java Binding provides an implementation of the `TransactionEvaluator` interface, known as `AikenTransactionEvaluator`, which is used to evaluate the cost of a script. Since it implements the CCL's `TransactionEvaluator` interface, you can use it directly with the QuickTx builder. ### Example Usage ```java TxResult result = quickTxBuilder.compose(tx) .feePayer(address) .withSigner(SignerProviders.signerFrom(account)) .withTxEvaluator(new AikenTransactionEvaluator(backendService)) .completeAndWait(); ``` If you don't use a `BackendService`, pass your own suppliers: `new AikenTransactionEvaluator(utxoSupplier, protocolParamsSupplier)`. See [Calling a Plutus script](/learn/plutus-scripts/) for a complete example. ## Compile Parameterized Contract Aiken Java Binding includes a utility class, `AikenScriptUtil`, which can be used to compile a parameterized script. The parameters needed for the script are passed as a `ListPlutusData` object. The final compiled code can then be converted into a `PlutusScript` object using the `PlutusBlueprintUtil.getPlutusScriptFromCompiledCode` method. ### Example Usage ```java PlutusData outputRef = ConstrPlutusData.of(0, BytesPlutusData.of(HexUtil.decodeHexString("1f3f766bc864c3f8ce8ccc20716e3f3cf65f08a819073c75875ea4e67549947f")), BigIntPlutusData.of(0)); ListPlutusData params = ListPlutusData.of(BytesPlutusData.of("MyToken"), outputRef); String contractCompiledCode = "590221010000323232323232323232323223222232533300b32323232533300f3370e9000180700089919191919191919191919299980e98100010991919299980e99b87480000044c94ccc078cdc3a4000603a002264a66603e66e1c011200213371e00a0322940c07000458c8cc004004030894ccc088004530103d87a80001323253330213375e6603a603e004900000d099ba548000cc0940092f5c0266008008002604c00460480022a66603a66e1c009200113371e00602e2940c06c050dd6980e8011bae301b00116301e001323232533301b3370e90010008a5eb7bdb1804c8dd59810800980c801180c800991980080080111299980f0008a6103d87a8000132323232533301f3371e01e004266e95200033023374c00297ae0133006006003375660400066eb8c078008c088008c080004c8cc004004008894ccc07400452f5bded8c0264646464a66603c66e3d221000021003133022337606ea4008dd3000998030030019bab301f003375c603a0046042004603e0026eacc070004c070004c06c004c068004c064008dd6180b80098078029bae3015001300d001163013001301300230110013009002149858c94ccc02ccdc3a40000022a66601c60120062930b0a99980599b874800800454ccc038c02400c52616163009002375c0026600200290001111199980399b8700100300c233330050053370000890011807000801001118029baa001230033754002ae6955ceaab9e5573eae815d0aba201"; String applyParamCompiledCode = AikenScriptUtil.applyParamToScript(params, contractCompiledCode); // Convert Aiken compiled code to PlutusScript PlutusScript plutusScript = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode(applyParamCompiledCode, PlutusVersion.v2); ``` --- # Annotation processor setup Source: https://cardano-client.dev/smart-contracts/annotation-processor-setup/ Summary: Annotation Processor Dependency ## Maven Add the following dependency to pom.xml. ```xml ... com.bloxbean.cardano cardano-client-annotation-processor ${cardano.client.version} compile ``` Alternatively, you can also add the following dependency to plugin section of pom.xml. ```xml org.apache.maven.plugins maven-compiler-plugin 3.8.1 com.bloxbean.cardano cardano-client-annotation-processor ${cardano.client.version} ``` ## Gradle //TODO --- # Blueprint code generation Source: https://cardano-client.dev/smart-contracts/blueprint-codegen/ Summary: Plutus Blueprint Code Generation > **Available in 0.6.0-beta1 and later.** > > **This is a preview feature and is subject to change.** This section describes how to use Plutus Blueprint Annotations to generate code from a Plutus Blueprint JSON file. For details about the Plutus Contract Blueprint CIP, refer to the [CIP-57 documentation](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0057). A Plutus Blueprint JSON file (e.g., `plutus.json`) can be generated during the compilation of your smart contract. For example, if you are using Aiken for smart contract development, the `plutus.json` file is automatically generated during the compilation of your project. ## Annotations Two main annotations can be used to generate code from the Plutus Blueprint JSON file: - **`@Blueprint`**: Specifies the path to the Plutus Blueprint JSON file. This annotation should be used at the interface level. - **`@ExtendWith`**: Adds additional functionalities to the generated validator class through Extender interfaces. For more information about Extender interfaces, refer to the [Extender Interfaces](#extend-validator-class-with-custom-extender-interfaces) section. ## How to Use Add the annotation processor to your build. With Gradle, also put your resources on the compile classpath, because that's where the processor looks for the blueprint: ```groovy dependencies { implementation 'com.bloxbean.cardano:cardano-client-lib:' annotationProcessor 'com.bloxbean.cardano:cardano-client-annotation-processor:' } tasks.named('compileJava') { classpath += files(sourceSets.main.resources.srcDirs) } ``` With Maven, add `cardano-client-annotation-processor` as a dependency or under the compiler plugin's `annotationProcessorPaths`. Then copy the Plutus Blueprint JSON file to the resources folder of your project. You can name the Plutus JSON file as you prefer, or use `plutus.json` if you only have one blueprint file in the project. ## Example Directory Structure ```text src |- main |- resources |- hello_world_plutus.json ```
Click here to view the Hello World Plutus Blueprint file #### Hello World Plutus Blueprint JSON ```json { "preamble": { "title": "aiken_lang/hello_word", "description": "Aiken contracts for project 'aiken_lang/hello_word'", "version": "0.0.0", "plutusVersion": "v2", "compiler": { "name": "Aiken", "version": "v1.0.21-alpha+4b04517" }, "license": "Apache-2.0" }, "validators": [ { "title": "hello_world.hello_world", "datum": { "title": "datum", "schema": { "$ref": "#/definitions/hello_world~1Datum" } }, "redeemer": { "title": "redeemer", "schema": { "$ref": "#/definitions/hello_world~1Redeemer" } }, "compiledCode": "58f2010000323232323232323222232325333008323232533300b002100114a06644646600200200644a66602200229404c8c94ccc040cdc78010028a511330040040013014002375c60240026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc020c008c020014dd71801180400399b8f375c6002600e00a91010d48656c6c6f2c20576f726c6421002300d00114984d958c94ccc020cdc3a400000226464a66601a601e0042930b1bae300d00130060041630060033253330073370e900000089919299980618070010a4c2c6eb8c030004c01401058c01400c8c014dd5000918019baa0015734aae7555cf2ab9f5742ae881", "hash": "6fb13cf9efdbe986e784d1983b21d3fb90231c1745925f536a820fb4" } ], "definitions": { "ByteArray": { "dataType": "bytes" }, "hello_world/Datum": { "title": "Datum", "anyOf": [ { "title": "Datum", "dataType": "constructor", "index": 0, "fields": [ { "title": "owner", "$ref": "#/definitions/ByteArray" } ] } ] }, "hello_world/Redeemer": { "title": "Redeemer", "anyOf": [ { "title": "Redeemer", "dataType": "constructor", "index": 0, "fields": [ { "title": "msg", "$ref": "#/definitions/ByteArray" } ] } ] } } } ```
Click here to view the Aiken file for the above Plutus Blueprint ```rust use aiken/hash.{Blake2b_224, Hash} use aiken/list use aiken/transaction.{ScriptContext} use aiken/transaction/credential.{VerificationKey} type Datum { owner: Hash, } type Redeemer { msg: ByteArray, } validator { fn hello_world(datum: Datum, redeemer: Redeemer, context: ScriptContext) -> Bool { let must_say_hello = redeemer.msg == "Hello, World!" let must_be_signed = list.has(context.transaction.extra_signatories, datum.owner) must_say_hello && must_be_signed } } ```
## Define the Plutus Blueprint Interface You can now create the Plutus Blueprint interface in your project and annotate it with the `@Blueprint` annotation. ```java @Blueprint(fileInResources = "hello_world_plutus.json", packageName = "com.example.helloworld") public interface HelloWorldBlueprint { } ``` ## Generate Validator, Datum, and Redeemer Classes Now, run Maven compile or Gradle build to compile the project. The validator class, along with the Datum and Redeemer classes, will be generated in the build folder's generated source folder. For Maven, the generated classes will be in the 'generated-sources' folder under the target directory. The following classes are generated: - Validator class for each validator in the blueprint JSON file. - Abstract class for Datum and Redeemer of each validator. - Implementation (Data) class for Datum and Redeemer of each validator, extending the abstract class and providing utility methods to serialize and deserialize the data. - Converter class for each Datum and Redeemer class, offering utilities to serialize and deserialize the data. ```java target |- generated-sources |- com |- example |- helloworld |- helloworld |- model |- converter |- DatumConverter.java |- RedeemerConverter.java |- impl |- DatumData.java |- RedeemerData.java |- Datum.java |- Redeemer.java |- HelloWorldValidator.java ``` ### Validator Class The generated validator class includes the following methods by default: - `getScriptAddress()`: Returns the address of the validator script. - `getPlutusScript()`: Returns the `PlutusScript` object from the compiled code.
Click here to view HelloWorldValidator.java ```java public class HelloWorldValidator { public static final String TITLE = "hello_world.hello_world"; public static final String DESCRIPTION = null; public static final String COMPILED_CODE = "58f2010000323232323232323222232325333008323232533300b002100114a06644646600200200644a66602200229404c8c94ccc040cdc78010028a511330040040013014002375c60240026eb0c038c03cc03cc03cc03cc03cc03cc03cc03cc020c008c020014dd71801180400399b8f375c6002600e00a91010d48656c6c6f2c20576f726c6421002300d00114984d958c94ccc020cdc3a400000226464a66601a601e0042930b1bae300d00130060041630060033253330073370e900000089919299980618070010a4c2c6eb8c030004c01401058c01400c8c014dd5000918019baa0015734aae7555cf2ab9f5742ae881"; public static final String HASH = "6fb13cf9efdbe986e784d1983b21d3fb90231c1745925f536a820fb4"; private Network network; private String scriptAddress; private PlutusScript plutusScript; public HelloWorldValidator(Network network) { this.network = network; } public Network getNetwork() { return this.network; } public void setNetwork(Network network) { this.network = network; } /** * Returns the address of the validator script */ public String getScriptAddress() { if(scriptAddress == null) { var script = getPlutusScript(); scriptAddress = AddressProvider.getEntAddress(script, network).toBech32(); } return scriptAddress; } public PlutusScript getPlutusScript() { if (plutusScript == null) { plutusScript = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode(COMPILED_CODE, PlutusVersion.v2); } return plutusScript; } } ```
### Datum Class The generator creates a Datum class for each Datum in the blueprint JSON file. The Datum class contains the fields defined in the Datum schema and is abstract.
Click here to view Datum.java ```java import com.bloxbean.cardano.client.plutus.annotation.Constr; import com.bloxbean.cardano.client.plutus.blueprint.model.Data; @Constr( alternative = 0 ) public abstract class Datum implements Data { /** * Index: 0 */ private byte[] owner; public byte[] getOwner() { return this.owner; } public void setOwner(byte[] owner) { this.owner = owner; } } ```
### Redeemer Class The generator creates a Redeemer class for each Redeemer in the blueprint JSON file. The Redeemer class contains the fields defined in the Redeemer schema and is abstract.
Click here to view Redeemer.java ```java import com.bloxbean.cardano.client.plutus.annotation.Constr; import com.bloxbean.cardano.client.plutus.blueprint.model.Data; @Constr( alternative = 0 ) public abstract class Redeemer implements Data { /** * Index: 0 */ private byte[] msg; public byte[] getMsg() { return this.msg; } public void setMsg(byte[] msg) { this.msg = msg; } } ```
### Datum Data Class A `DatumData` class is generated for each Datum class. This class extends the abstract Datum class and provides utility methods to serialize and deserialize the data.
Click here to view DatumData.java ```java import com.bloxbean.cardano.client.plutus.blueprint.model.Data; import com.bloxbean.cardano.client.plutus.spec.ConstrPlutusData; import com.example.helloworld.helloworld.model.Datum; import com.example.helloworld.helloworld.model.converter.DatumConverter; import java.lang.Override; import java.lang.String; /** * Auto generated code. DO NOT MODIFY */ public class DatumData extends Datum implements Data { private static DatumConverter converter = new DatumConverter(); public DatumData() { } @Override public ConstrPlutusData toPlutusData() { return converter.toPlutusData((Datum)this); } public static Datum fromPlutusData(ConstrPlutusData data) { return converter.fromPlutusData(data); } public static Datum deserialize(String cborHex) { return converter.deserialize(cborHex); } public static Datum deserialize(byte[] cborBytes) { return converter.deserialize(cborBytes); } } ```
### Redeemer Data Class A `RedeemerData` class is generated for each Redeemer class. This class extends the abstract Redeemer class and provides utility methods to serialize and deserialize the data.
Click here to view RedeemerData.java ```java import com.bloxbean.cardano.client.plutus.blueprint.model.Data; import com.bloxbean.cardano.client.plutus.spec.ConstrPlutusData; import com.example.helloworld.helloworld.model.Redeemer; import com.example.helloworld.helloworld.model.converter.RedeemerConverter; import java.lang.Override; import java.lang.String; /** * Auto generated code. DO NOT MODIFY */ public class RedeemerData extends Redeemer implements Data { private static RedeemerConverter converter = new RedeemerConverter(); public RedeemerData() { } @Override public ConstrPlutusData toPlutusData() { return converter.toPlutusData((Redeemer)this); } public static Redeemer fromPlutusData(ConstrPlutusData data) { return converter.fromPlutusData(data); } public static Redeemer deserialize(String cborHex) { return converter.deserialize(cborHex); } public static Redeemer deserialize(byte[] cborBytes) { return converter.deserialize(cborBytes); } } ```
### Datum Converter Class A `DatumConverter` class is generated for each Datum class. This class provides utility methods to serialize and deserialize the data. Typically, you don't need to use converter classes directly as the `DatumData` class offers these utility methods.
Click here to view DatumConverter.java ```java import com.bloxbean.cardano.client.common.cbor.CborSerializationUtil; import com.bloxbean.cardano.client.exception.CborRuntimeException; import com.bloxbean.cardano.client.plutus.annotation.BasePlutusDataConverter; import com.bloxbean.cardano.client.plutus.spec.BytesPlutusData; import com.bloxbean.cardano.client.plutus.spec.ConstrPlutusData; import com.bloxbean.cardano.client.util.HexUtil; import com.example.helloworld.helloworld.model.Datum; import com.example.helloworld.helloworld.model.impl.DatumData; import java.lang.Exception; import java.lang.String; import java.util.Objects; /** * Auto generated code. DO NOT MODIFY */ public class DatumConverter extends BasePlutusDataConverter { public ConstrPlutusData toPlutusData(Datum obj) { ConstrPlutusData constr = initConstr(0); //Field owner Objects.requireNonNull(obj.getOwner(), "owner cannot be null"); constr.getData().add(toPlutusData(obj.getOwner())); return constr; } public Datum fromPlutusData(ConstrPlutusData constr) { var obj = new DatumData(); var data = constr.getData(); //Field owner var owner = ((BytesPlutusData)data.getPlutusDataList().get(0)).getValue(); obj.setOwner(owner); return obj; } public byte[] serialize(Datum obj) { Objects.requireNonNull(obj);; try { var constr = toPlutusData(obj); return CborSerializationUtil.serialize(constr.serialize()); } catch (Exception e) { throw new CborRuntimeException(e); } } public String serializeToHex(Datum obj) { Objects.requireNonNull(obj);; var constr = toPlutusData(obj); return constr.serializeToHex(); } public Datum deserialize(byte[] bytes) { Objects.requireNonNull(bytes);; try { var di = CborSerializationUtil.deserialize(bytes); var constr = ConstrPlutusData.deserialize(di); return fromPlutusData(constr); } catch (Exception e) { throw new CborRuntimeException(e); } } public Datum deserialize(String hex) { Objects.requireNonNull(hex);; var bytes = HexUtil.decodeHexString(hex); return deserialize(bytes); } } ```
### Redeemer Converter Class A `RedeemerConverter` class is generated for each Redeemer class. This class provides utility methods to serialize and deserialize the data. Typically, you don't need to use converter classes directly as the `RedeemerData` class offers these utility methods.
Click here to view RedeemerConverter.java ```java import com.bloxbean.cardano.client.common.cbor.CborSerializationUtil; import com.bloxbean.cardano.client.exception.CborRuntimeException; import com.bloxbean.cardano.client.plutus.annotation.BasePlutusDataConverter; import com.bloxbean.cardano.client.plutus.spec.BytesPlutusData; import com.bloxbean.cardano.client.plutus.spec.ConstrPlutusData; import com.bloxbean.cardano.client.util.HexUtil; import com.example.helloworld.helloworld.model.Redeemer; import com.example.helloworld.helloworld.model.impl.RedeemerData; import java.lang.Exception; import java.lang.String; import java.util.Objects; /** * Auto generated code. DO NOT MODIFY */ public class RedeemerConverter extends BasePlutusDataConverter { public ConstrPlutusData toPlutusData(Redeemer obj) { ConstrPlutusData constr = initConstr(0); //Field msg Objects.requireNonNull(obj.getMsg(), "msg cannot be null"); constr.getData().add(toPlutusData(obj.getMsg())); return constr; } public Redeemer fromPlutusData(ConstrPlutusData constr) { var obj = new RedeemerData(); var data = constr.getData(); //Field msg var msg = ((BytesPlutusData)data.getPlutusDataList().get(0)).getValue(); obj.setMsg(msg); return obj; } public byte[] serialize(Redeemer obj) { Objects.requireNonNull(obj);; try { var constr = toPlutusData(obj); return CborSerializationUtil.serialize(constr.serialize()); } catch (Exception e) { throw new CborRuntimeException(e); } } public String serializeToHex(Redeemer obj) { Objects.requireNonNull(obj);; var constr = toPlutusData(obj); return constr.serializeToHex(); } public Redeemer deserialize(byte[] bytes) { Objects.requireNonNull(bytes);; try { var di = CborSerializationUtil.deserialize(bytes); var constr = ConstrPlutusData.deserialize(di); return fromPlutusData(constr); } catch (Exception e) { throw new CborRuntimeException(e); } } public Redeemer deserialize(String hex) { Objects.requireNonNull(hex);; var bytes = HexUtil.decodeHexString(hex); return deserialize(bytes); } } ```
## Use Datum and Redeemer classes You can instantiate Datum and Redeemer objects using the generated Data (implementation) classes. Use the `DatumData.toPlutusData` or `DatumData.fromPlutusData` methods to convert the Data object to a PlutusData object and vice versa. Use the `DatumData.deserialize` method to read a datum from CBOR, or the `DatumConverter` class to serialize and deserialize it. ```java DatumData datum = new DatumData(); ``` ## Extend Validator Class with Custom Extender Interfaces You can extend the generated validator class with custom Extender interfaces to provide additional functionalities. Extenders are interfaces with default methods that can add functionalities to the generated validator class. Each extender interface extends the `ValidatorExtender` interface. Several built-in extender interfaces are available, and they can be added to the validator class using the `@ExtendWith` annotation. You can also write your own extender interfaces by extending the `ValidatorExtender` interface and use them in the validator class. Available built-in extender interfaces include: - **LockUnlockValidatorExtender**: Provides methods to lock and unlock the validator script. - **MintValidatorExtender**: Provides common methods to mint tokens. - **DeployValidatorExtender**: Adds functionality to create an output with a reference script for the validator. **Note**: Additional extender interfaces will be added in the future. The methods in the built-in extender interfaces are compatible with the **QuickTx API**. Let's see how to extend the validator class with `ValidatorExtender` interfaces: ```java @Blueprint(fileInResources = "hello_world_plutus.json", packageName = "com.example.helloworld") @ExtendWith({LockUnlockValidatorExtender.class, MintValidatorExtender.class}) public interface HelloWorldBlueprint { } ``` In this example, we use both the `LockUnlockValidatorExtender` and `MintValidatorExtender` interfaces to extend the validator class. Since `LockUnlockValidatorExtender` extends `DeployValidatorExtender`, the validator class will also inherit methods from `DeployValidatorExtender`. After compiling the project again, the generated validator class will extend the `LockUnlockValidatorExtender` and `MintValidatorExtender` interfaces. ```java public class HelloWorldValidator extends AbstractValidatorExtender implements LockUnlockValidatorExtender, MintValidatorExtender ``` ## Using the Extended Validator Class You can now use the extended validator class to lock, unlock, mint tokens, and create outputs with reference scripts. Here's an example of using the extended validator class to lock and unlock the validator script. First, a reference output is created with the `deploy` method, and then the `lock` and `unlock` methods are used to lock and unlock the validator script.
Click here to see how to lock and unlock using HelloWorldValidator ```java import com.bloxbean.cardano.client.account.Account; import com.bloxbean.cardano.client.api.model.Amount; import com.bloxbean.cardano.client.backend.blockfrost.service.BFBackendService; import com.bloxbean.cardano.client.common.model.Networks; import com.bloxbean.cardano.client.function.helper.SignerProviders; import com.example.helloworld.helloworld.HelloWorldValidator; import com.example.helloworld.helloworld.model.Datum; import com.example.helloworld.helloworld.model.impl.DatumData; import com.example.helloworld.helloworld.model.impl.RedeemerData; public class HelloWorldTest { private static String mnemonic = "test test test test test test test test test test test test test test test test test test test test test test test sauce"; private static Account account = Account.createFromMnemonic(Networks.testnet(), mnemonic); private static BFBackendService backendService = new BFBackendService("http://localhost:8080/api/v1/", ""); private HelloWorldValidator helloWorldValidator; public HelloWorldTest() { this.helloWorldValidator = new HelloWorldValidator(Networks.testnet()) .withBackendService(backendService); } public void deploy() { var deployTx = helloWorldValidator.deploy(account.baseAddress()) .feePayer(account.baseAddress()) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(System.out::println); this.helloWorldValidator .withReferenceTxInput(deployTx.getValue(), 0); System.out.println("Deploy Tx:" + deployTx); } public Datum lock() { var datum = new DatumData(); datum.setOwner(account.getBaseAddress().getPaymentCredentialHash().get()); var lockTx = helloWorldValidator.lock(account.baseAddress(), Amount.ada(20), datum) .feePayer(account.baseAddress()) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(System.out::println); System.out.println("Lock Tx: " + lockTx); return datum; } public void unlock(Datum datum) { var redeemerData = new RedeemerData(); redeemerData.setMsg("Hello, World!".getBytes()); var unlockTx = helloWorldValidator.unlockToAddress(datum, redeemerData, account.baseAddress()) .feePayer(account.baseAddress()) .withSigner(SignerProviders.signerFrom(account)) .withRequiredSigners(account.getBaseAddress()) .completeAndWait(System.out::println); System.out.println("Unlock Tx: " + unlockTx); } public static void main(String[] args) { HelloWorldTest helloWorldTest = new HelloWorldTest(); //Deploy and create reference input helloWorldTest.deploy(); //Lock ADA var datum = helloWorldTest.lock(); //Unlock ADA helloWorldTest.unlock(datum); } } ```
## Composing Validator Transactions with Other Transactions or Script Transactions If you want to compose the validator transaction described above with other transactions, the out-of-box extenders provide additional methods that return a `ScriptTx` or `Tx` object. These `ScriptTx` or `Tx` objects can then be composed with other transactions (Tx) or script transactions (ScriptTx). Typically, these methods have names that end with "Tx". For example, in `LockUnlockValidatorExtender.java`, here are a few methods that return a `Tx` or `ScriptTx` object: - `Tx lockTx(String fromAddress, Amount amount, Data datum)` - `ScriptTx unlockToAddressTx(Data inputDatum, Data redeemer, String receiver)` - `ScriptTx unlockToContractTx(Data inputDatum, Data redeemer, String receiver, Data outputData)` --- # Plutus API Source: https://cardano-client.dev/smart-contracts/plutus-api/ Summary: Smart contract interaction with PlutusData, scripts, and blueprints The Plutus API provides comprehensive functionality for interacting with Plutus smart contracts on the Cardano blockchain. It includes support for Plutus scripts (V1, V2, V3) and data serialization with type safety. ## Key Features - **Plutus Script Support**: Complete support for V1, V2, and V3 script versions - **Data Serialization**: Convert between Java objects and PlutusData - **Type Safety**: Strong typing for Plutus data structures ## Core Classes ### PlutusScript Classes - `PlutusV1Script`: Plutus V1 script implementation - `PlutusV2Script`: Plutus V2 script implementation - `PlutusV3Script`: Plutus V3 script implementation ### PlutusData Classes - `PlutusData`: Base interface for all Plutus data types - `BigIntPlutusData`: Integer data representation - `BytesPlutusData`: Byte string data representation - `ListPlutusData`: List data representation - `MapPlutusData`: Map data representation ## Usage Examples ### Creating Plutus Scripts Create different versions of Plutus scripts: ```java // Create Plutus V2 Script (most common) PlutusV2Script v2Script = PlutusV2Script.builder() .type("PlutusScriptV2") .cborHex("590a4d590a4a0100003333...") .build(); // Create Plutus V3 Script (latest) PlutusV3Script v3Script = PlutusV3Script.builder() .type("PlutusScriptV3") .cborHex("590b5e590b5b0100003333...") .build(); System.out.println("Scripts created successfully"); ``` ### Loading Plutus Scripts from a Blueprint (CIP-57) Smart contract compilers like [Aiken](https://aiken-lang.org/) and [Scalus](https://scalus.org/) produce a `plutus.json` blueprint file following the [CIP-57](https://cips.cardano.org/cip/CIP-0057) standard. This file contains a `compiledCode` field for each validator, which is **single-encoded** CBOR hex. CCL's `PlutusScript` objects require **double-encoded** CBOR, and the `PlutusBlueprintUtil` class handles this conversion automatically. A typical `plutus.json` looks like this: ```json { "preamble": { "title": "aiken-lang/hello_world", "version": "1.0.0", "plutusVersion": "v3" }, "validators": [ { "title": "hello_world.spend", "compiledCode": "58ad0100003232322..." } ] } ``` #### Option A: Direct conversion with `PlutusBlueprintUtil` If you already have the `compiledCode` hex string and know the Plutus version, you can convert it directly: ```java import com.bloxbean.cardano.client.plutus.blueprint.PlutusBlueprintUtil; import com.bloxbean.cardano.client.plutus.blueprint.model.PlutusVersion; String compiledCode = "58ad0100003232322..."; // from plutus.json validators[].compiledCode PlutusScript plutusScript = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode( compiledCode, PlutusVersion.v3); ``` This performs double CBOR encoding internally and returns the appropriate `PlutusV1Script`, `PlutusV2Script`, or `PlutusV3Script` instance. #### Option B: Load the full blueprint with `PlutusBlueprintLoader` For a more streamlined approach, load the entire `plutus.json` and extract scripts by validator title: ```java import com.bloxbean.cardano.client.plutus.blueprint.PlutusBlueprintLoader; import com.bloxbean.cardano.client.plutus.blueprint.model.PlutusContractBlueprint; // Load the blueprint from classpath InputStream is = getClass().getResourceAsStream("/plutus.json"); PlutusContractBlueprint blueprint = PlutusBlueprintLoader.loadBlueprint(is); // Extract a PlutusScript by validator title PlutusScript plutusScript = blueprint.getPlutusScript("hello_world.spend"); ``` #### Using the loaded script with QuickTx :::note[`ScriptTx` in 0.7.x and the 0.8.0 previews] `ScriptTx` is the API for script transactions in the stable 0.7.x releases, where it isn't deprecated. In the 0.8.0 previews it's deprecated, because every operation is also available on `Tx`. On a preview, you can replace `new ScriptTx()` with `new Tx()` and add `.from(senderAddress)`. See [Unified Tx API](/quicktx/unified-tx/). ::: Once you have a `PlutusScript`, use it with `ScriptTx` the same way as a manually created script: ```java QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); ScriptTx scriptTx = new ScriptTx() .collectFrom(utxo, redeemer) .payToAddress(receiverAddress, Amount.ada(10)) .attachSpendingValidator(plutusScript); Result result = quickTxBuilder.compose(scriptTx) .feePayer(senderAddress) .withSigner(SignerProviders.signerFrom(senderAccount)) .completeAndWait(System.out::println); ``` > **See also:** > - [Plutus Blueprint Code Generation](/smart-contracts/blueprint-codegen/) — generate type-safe Java bindings from a blueprint using annotations > - [Aiken Integration](/smart-contracts/aiken/) — Script cost evaluation and parameterized contracts > - [Scalus Integration](/smart-contracts/scalus/) — Script cost evaluation and parameterized contracts ### Working with PlutusData Create and manipulate PlutusData structures: ```java // Create integer data PlutusData intData = BigIntPlutusData.of(BigInteger.valueOf(42)); // Create string data (as bytes) PlutusData stringData = BytesPlutusData.of("Hello Cardano".getBytes()); // Create list data (varargs) PlutusData listData = ListPlutusData.of(intData, stringData); // Create map data MapPlutusData mapData = new MapPlutusData(); mapData.put(BytesPlutusData.of("key".getBytes()), intData); System.out.println("PlutusData structures created"); ``` ## Best Practices ### Type Safety ```java // Use type-safe PlutusData creation public PlutusData createTypeSafePlutusData() { // Prefer specific types over generic PlutusData PlutusData intData = BigIntPlutusData.of(BigInteger.valueOf(100)); PlutusData bytesData = BytesPlutusData.of("data".getBytes()); // Validate data before use if (intData instanceof BigIntPlutusData) { BigInteger value = ((BigIntPlutusData) intData).getValue(); System.out.println("Integer value: " + value); } return intData; } ``` --- # PlutusData annotations Source: https://cardano-client.dev/smart-contracts/plutusdata-annotations/ Summary: Plutus Data Annotations This document provides an overview of the Plutus Data Annotations, which can be used to automatically generate serialization and deserialization code for custom POJO classes that can be used as Datum and Redeemer in Plutus scripts. Code generation is performed by an annotation processor during compile time. Add `com.bloxbean.cardano:cardano-client-annotation-processor` to your build as an `annotationProcessor` dependency. # Annotations - **@Constr** - **@PlutusIgnore** ### @Constr This is a class-level annotation used to represent `ConstrPlutusData`. It takes an optional argument `alternative`. ```java @Constr public class Person { private String name; private String publicKey; private int age; //Getters and Setters } ``` The annotation processor generates a Converter class for the annotated class with the necessary serialization and deserialization methods. ```java Person person = new Person(); person.setName("Alice"); person.setPublicKey("6dcf4915b05a1358d86e87d352f2fa7392fa6c092b337af705b577822d06d17e"); person.setAge(25); //Generated Person Converter class PersonConverter converter = new PersonConverter(); //Convert to Plutus data ConstrPlutusData plutusData = converter.toPlutusData(person); //Convert back to Person Person deserPerson = converter.deserialize(converter.serializeToHex(person)); ``` ### @PlutusIgnore This is a field-level annotation used to exclude a field from serialization and deserialization. --- # Scalus Integration Source: https://cardano-client.dev/smart-contracts/scalus/ Summary: Evaluate script costs locally and apply script parameters with Scalus. [Scalus](https://github.com/scalus3/scalus) compiles a subset of Scala to Plutus Core, the language of Cardano smart contracts. Its Cardano Client Lib integration also works from plain Java, where you can use it to evaluate script costs locally and to apply parameters to compiled scripts. Scalus runs on the JVM and bundles native libraries for macOS, Windows and Linux, including ARM64 Linux, which [Aiken Java Binding](/smart-contracts/aiken/) doesn't support. ## Dependencies ### Maven (`pom.xml`) ```xml org.scalus scalus-bloxbean-cardano-client-lib_3 1.4.1 ``` ### Gradle (`build.gradle`) ```groovy implementation 'org.scalus:scalus-bloxbean-cardano-client-lib_3:1.4.1' ``` This Scalus release declares a dependency on Cardano Client Lib 0.7.3. We tested it with the [latest preview](/start-here/installation/#versions) on a local devnet and on Preview. Gradle picks the higher version by itself. With Maven, declare `cardano-client-lib` directly in your `pom.xml` so that your version wins. ## Script Cost Evaluation `ScalusTransactionEvaluator` implements the library's `TransactionEvaluator` interface, so you can pass it directly to the QuickTx builder. ```java ProtocolParams params = backendService.getEpochService().getProtocolParameters().getValue(); UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService()); TxResult result = quickTxBuilder.compose(tx) .feePayer(address) .withSigner(SignerProviders.signerFrom(account)) .withTxEvaluator(new ScalusTransactionEvaluator(SlotConfig.preview(), params, utxoSupplier)) .completeAndWait(); ``` `SlotConfig` is `scalus.cardano.ledger.SlotConfig`, and it also has `mainnet()` and `preprod()`. A script needs the right one to read a transaction's validity range correctly. If the transaction uses reference scripts, add a script supplier so that the evaluator can read them: ```java new ScalusTransactionEvaluator(SlotConfig.preview(), params, utxoSupplier, new ScriptServiceSupplier(backendService.getScriptService())) ``` See [Calling a Plutus script](/learn/plutus-scripts/) for a complete example. ## Apply Script Parameters `ScalusScriptUtils.applyParamsToScript` applies parameters to a script. It takes and returns **double-encoded** CBOR hex, which is what `PlutusScript.getCborHex()` holds. A blueprint's `compiledCode` is single-encoded, so load it as a script first: ```java PlutusScript unapplied = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode(compiledCode, PlutusVersion.v3); String applied = ScalusScriptUtils.applyParamsToScript(unapplied.getCborHex(), BytesPlutusData.of(issuerKeyHash)); PlutusV3Script script = PlutusV3Script.builder() .type("PlutusScriptV3") .cborHex(applied) .build(); ``` The method also accepts a `ListPlutusData`. For the same inputs, we confirmed that the result matches `AikenScriptUtil.applyParamToScript`. --- # CIP-20 — Transaction messages Source: https://cardano-client.dev/standards/cip20/ Summary: Transaction Message/Comment Metadata implementation CIP20 (Cardano Improvement Proposal 20) defines a standard for adding messages, comments, or memos to Cardano transactions using transaction metadata. This allows you to attach informational text, invoice numbers, or similar data to transactions on the Cardano blockchain. The library's `MessageMetadata` wraps the CIP20 layout (label 674 with `msg` array) so you only worry about adding strings and serializing the CBOR when needed. ## Key Features - **Standard Compliance**: Full CIP20 specification support - **Message Metadata**: Add messages/comments to transactions - **Validation**: Built-in validation for message length (max 64 bytes) - **Multiple Messages**: Support for multiple messages in a single transaction - **Easy Integration**: Simple API for adding metadata to transactions ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-cip20 - **Dependencies**: metadata ## Usage Examples ### Creating Message Metadata Build the `MessageMetadata` object and serialize to CBOR when you need raw bytes. The following example shows how to create CIP20 compliant message metadata with multiple messages. ```java // Create message metadata MessageMetadata messageMetadata = MessageMetadata.create() .add("Payment for services") .add("Invoice #12345") .add("Thank you for your business"); // Get metadata as CBOR bytes byte[] cborBytes = messageMetadata.serialize(); String hexMetadata = HexUtil.encodeHexString(cborBytes); ``` ### Adding Messages to Transactions Attach the metadata directly in QuickTx so it is included in the auxiliary data of the transaction. The following example shows how to add message metadata to a transaction using QuickTx. ```java // Create message metadata MessageMetadata messageMetadata = MessageMetadata.create() .add("Payment for NFT purchase") .add("Transaction ID: " + System.currentTimeMillis()); // Create transaction with message metadata Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(senderAddress) .attachMetadata(messageMetadata); // Build and sign transaction Result result = quickTxBuilder.compose(tx) .feePayer(senderAddress) .withSigner(SignerProviders.signerFrom(senderAccount)) .completeAndWait(System.out::println); ``` ### Working with Message Lists Retrieve the stored messages to display or process them. The following example shows how to retrieve and work with messages from metadata. ```java // Create message metadata MessageMetadata messageMetadata = MessageMetadata.create() .add("First message") .add("Second message") .add("Third message"); // Get all messages List messages = messageMetadata.getMessages(); // Process messages for (String message : messages) { System.out.println("Message: " + message); } // Check message count System.out.println("Total messages: " + messages.size()); ``` ### Integration with Other Metadata Merge CIP20 messages with other metadata maps if you need to send multiple labels in one transaction. The following example shows how to combine CIP20 message metadata with other transaction metadata. ```java // Create CIP20 message metadata MessageMetadata messageMetadata = MessageMetadata.create() .add("Payment confirmation") .add("Order #789"); // Create additional custom metadata CBORMetadata customMetadata = new CBORMetadata() .put(BigInteger.valueOf(100), "Custom field value") .put(BigInteger.valueOf(101), "Another custom value"); // Combine metadata (CIP20 uses label 674) CBORMetadata combinedMetadata = (CBORMetadata) customMetadata.merge(messageMetadata); // Use in transaction Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(5)) .from(senderAddress) .attachMetadata(combinedMetadata); ``` ### Practical Use Cases #### Invoice Tracking ```java // Create invoice metadata MessageMetadata invoiceMetadata = MessageMetadata.create() .add("Invoice #INV-2024-001") .add("Due: 2024-01-15") .add("Amount: 100 ADA"); Tx paymentTx = new Tx() .payToAddress(merchantAddress, Amount.ada(100)) .from(customerAddress) .attachMetadata(invoiceMetadata); ``` #### Payment Confirmations ```java // Create payment confirmation metadata MessageMetadata confirmationMetadata = MessageMetadata.create() .add("Payment confirmed") .add("Ref: " + generatePaymentReference()) .add("Thank you!"); Tx confirmationTx = new Tx() .payToAddress(recipientAddress, Amount.ada(50)) .from(payerAddress) .attachMetadata(confirmationMetadata); ``` #### Service Requests ```java // Create service request metadata MessageMetadata serviceMetadata = MessageMetadata.create() .add("Service: Web Development") .add("Client: Acme Corp") .add("Priority: High"); Tx serviceTx = new Tx() .payToAddress(developerAddress, Amount.ada(25)) .from(clientAddress) .attachMetadata(serviceMetadata); ``` ## API Reference ### MessageMetadata Class The main class for creating and managing CIP20 message metadata. #### Constructor ```java // Create new instance public static MessageMetadata create() ``` #### Methods ##### add(String message) Adds a message to the metadata. Each message must be: - Non-null - Maximum 64 bytes in UTF-8 encoding ```java public MessageMetadata add(String message) ``` **Parameters:** - `message` - The message to add (max 64 UTF-8 bytes) **Returns:** The MessageMetadata instance for chaining **Throws:** `IllegalArgumentException` if message is null or exceeds 64 bytes ##### getMessages() Retrieves all messages from the metadata. ```java public List getMessages() ``` **Returns:** List of all messages in the metadata ##### serialize() Serializes the metadata to CBOR bytes. ```java public byte[] serialize() ``` **Returns:** CBOR-encoded bytes of the metadata ## CIP20 Specification Details ### Metadata Label CIP20 uses metadata label **674** for message metadata. ### Message Format Messages are stored as an array of strings under the "msg" key: ```json { "674": { "msg": [ "First message", "Second message", "Third message" ] } } ``` ### Constraints - Each message must be at most 64 bytes when encoded in UTF-8 - Messages cannot be null - Multiple messages are supported in a single transaction ## Best Practices 1. **Keep messages concise** - The 64-byte limit encourages brief, informative messages 2. **Use meaningful content** - Include relevant information like invoice numbers, references, or confirmations 3. **Validate input** - Always handle validation errors when adding messages 4. **Consider privacy** - Remember that metadata is publicly visible on the blockchain 5. **Combine with other standards** - CIP20 works well with CIP25 (NFT metadata) and other standards ## Integration Examples ### With CIP25 (NFT Metadata) ```java // Create CIP25 NFT metadata NFT nft = NFT.create() .assetName("MyNFT") .name("My NFT") .image("ipfs://...") .description("A sample NFT"); NFTMetadata nftMetadata = NFTMetadata.create().addNFT(policyId, nft); // Create CIP20 message metadata MessageMetadata messageMetadata = MessageMetadata.create() .add("NFT purchase") .add("Collection: Art Gallery"); // Combine metadata CBORMetadata combinedMetadata = (CBORMetadata) nftMetadata.merge(messageMetadata); ``` ### With Custom Metadata ```java // Create custom metadata CBORMetadata customMetadata = new CBORMetadata() .put(BigInteger.valueOf(100), "Custom field"); // Create CIP20 message metadata MessageMetadata messageMetadata = MessageMetadata.create() .add("Transaction note"); // Combine and use in transaction CBORMetadata finalMetadata = (CBORMetadata) customMetadata.merge(messageMetadata); ``` For more information about CIP20, refer to the [official CIP20 specification](https://cips.cardano.org/cips/cip20/). --- # CIP-25 — NFT metadata Source: https://cardano-client.dev/standards/cip25/ Summary: NFT Metadata Standard implementation CIP25 (Cardano Improvement Proposal 25) defines a standard for NFT metadata on the Cardano blockchain. The Cardano Client Library provides builders for the CIP25 metadata map and NFT/file helpers. You assemble NFT entries, group them under a policy with `NFTMetadata`, and serialize to CBOR for use as transaction metadata. ## Key Features - **Standard Compliance**: CIP25 metadata map (label 721) - **NFT Builder**: `NFT` for asset fields and attributes - **File Support**: `NFTFile` for rich file entries - **Policy Map**: `NFTMetadata` for policy -> asset metadata layout ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-cip25 - **Dependencies**: metadata ## Usage Examples ### Creating NFT Metadata Create the NFT entry with images, descriptions, and files, then insert it under your policy id. ```java // Create CIP25 NFT entry NFT nft = NFT.create() .assetName("MyAwesomeNFT") .name("MyAwesomeNFT") .image("https://example.com/image.png") .description("A sample CIP25 NFT") .addFile(NFTFile.create() .mediaType("image/png") .name("image.png") .src("https://example.com/image.png")) .property("color", "blue") .property("rarity", "legendary"); // Wrap under policy id and label 721 NFTMetadata nftMetadata = NFTMetadata.create() .addNFT(policyId, nft); byte[] cborBytes = nftMetadata.serialize(); ``` ### Minting NFT with CIP25 Metadata Pass the CBOR metadata into your transaction so wallets and indexers can read it later. Attach the CBOR metadata when building your transaction. ```java // mintingScript is your NativeScript policy, and policyId its getPolicyId() Asset nftAsset = new Asset("MyAwesomeNFT", BigInteger.ONE); Tx tx = new Tx() .mintAssets(mintingScript, List.of(nftAsset), senderAddress) .attachMetadata(nftMetadata) .from(senderAddress); ``` For a complete example, see [Minting an NFT](/learn/minting-an-nft/). ### Working with Files Use `NFTFile` to attach one or more files (e.g., media, thumbnails) to the NFT entry. ```java NFTFile file = NFTFile.create() .mediaType("image/png") .name("thumbnail.png") .src("https://example.com/thumbnail.png"); NFT nft = NFT.create() .assetName("MultiFile") .name("Multi-file NFT") .description("NFT with multiple files") .addFile(file); ``` ### Custom Attributes Store additional fields on the NFT or file objects via the shared `NFTProperties` helpers. `NFT` and `NFTFile` inherit from `NFTProperties`, so you can add arbitrary fields. ```java nft.property("power", "100"); nft.property("tags", List.of("gaming", "collectible")); ``` ## API Reference ### NFTMetadata ```java // Create new instance public static NFTMetadata create() // Create from CBOR bytes public static NFTMetadata create(byte[] cborBytes) public NFTMetadata addNFT(String policyId, NFT nft) public NFT getNFT(String policyId, String assetName) public NFTMetadata removeNFT(String policyId, String assetName) public NFTMetadata version(int version) // default 1 public byte[] serialize() ``` ### NFT ```java public static NFT create() public NFT assetName(String assetName) public String getAssetName() public NFT name(String name) public String getName() public NFT image(String imageUri) // a URI over 64 bytes is split into chunks public List getImages() public NFT mediaType(String mediaType) public String getMediaType() public NFT description(String description) // supports multiple public List getDescriptions() public NFT addFile(NFTFile nftFile) public List getFiles() // Additional properties public NFT property(String name, String value) public NFT property(String name, Map values) public NFT property(String name, List values) ``` ### NFTFile ```java public static NFTFile create() public NFTFile name(String name) public String getName() public NFTFile mediaType(String mediaType) public String getMediaType() public NFTFile src(String uri) // call multiple times to add more public List getSrcs() // Additional properties public NFTFile property(String name, String value) public NFTFile property(String name, Map values) public NFTFile property(String name, List values) ``` ## CIP25 Specification Details - **Metadata Label**: 721 - **Structure**: `{ "721": { "": { "": { ... } } } }` - **Files**: support `mediaType`, `name`, and one-or-many `src` values ## Best Practices 1. Use HTTPS or IPFS links for images/files. 2. Keep metadata concise to reduce transaction size. 3. Set one `image` per NFT: CIP-25 reads an `image` array as the chunks of one URI, so a second call would corrupt it. Use `addFile` for more files. 4. Set `assetName` on the `NFT` before adding it to `NFTMetadata`. 5. Validate URLs and lengths in your application code (no built-in validators are present). ## Integration Examples Combine CIP25 metadata with other metadata using `CBORMetadata.merge(...)` if needed. ```java CBORMetadata combined = (CBORMetadata) new CBORMetadata().merge(nftMetadata); combined.put(BigInteger.valueOf(100), "extra-field"); ``` For more information about CIP25, refer to the [official CIP25 specification](https://cips.cardano.org/cips/cip25/). --- # CIP-27 — Royalties Source: https://cardano-client.dev/standards/cip27/ Summary: CNFT Community Royalties Standard implementation CIP27 (Cardano Improvement Proposal 27) defines a standard for NFT royalties on the Cardano blockchain. The Cardano Client Library provides models for the royalty metadata under label 777. You set an address and rate on `RoyaltyToken`, wrap it with `RoyaltyTokenMetadata`, and attach it when you mint the policy's royalty token. Royalties in CIP27 are purely descriptive metadata; enforcement is handled off-chain by marketplaces that honor the standard. ## Key Features - **Royalty Token Model**: Address + rate - **Metadata Wrapper**: `RoyaltyTokenMetadata` under label 777 - **CIP25 Friendly**: Uses the same `NFTProperties` base for extensibility ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-cip27 - **Dependencies**: cip25 ## Usage Examples ### Creating Royalty Metadata ```java RoyaltyToken royaltyToken = RoyaltyToken.create() .address("addr1...") .rate(0.05); // 5% RoyaltyTokenMetadata royaltyMetadata = RoyaltyTokenMetadata.create() .royaltyToken(royaltyToken); byte[] cborBytes = royaltyMetadata.serialize(); ``` ### Mint the Royalty Token, Then the NFTs CIP-27 stores the royalty in an unnamed token, with the metadata under label 777. It must be minted before any other asset of the policy. Here `policy` is your `NativeScript` policy, and `policyKeys` its keys: ```java // 1. The royalty token: quantity 1, and an empty asset name Tx royaltyTx = new Tx() .mintAssets(policy, new Asset("", BigInteger.ONE), senderAddress) .attachMetadata(royaltyMetadata) .from(senderAddress); quickTxBuilder.compose(royaltyTx) .withSigner(SignerProviders.signerFrom(sender)) .withSigner(SignerProviders.signerFrom(policyKeys.getSkey())) .completeAndWait(); // 2. Then the NFTs, with their CIP-25 metadata NFT nft = NFT.create() .assetName("RoyaltyNFT") .name("My Royalty NFT") .image("https://example.com/image.png"); Tx nftTx = new Tx() .mintAssets(policy, new Asset("RoyaltyNFT", BigInteger.ONE), senderAddress) .attachMetadata(NFTMetadata.create().addNFT(policy.getPolicyId(), nft)) .from(senderAddress); quickTxBuilder.compose(nftTx) .withSigner(SignerProviders.signerFrom(sender)) .withSigner(SignerProviders.signerFrom(policyKeys.getSkey())) .completeAndWait(); ``` ### Reading Royalty Information ```java RoyaltyTokenMetadata metadata = RoyaltyTokenMetadata.create(cborBytes); RoyaltyToken token = metadata.getRoyaltyToken(); String recipient = token.getAddress(); Double rate = token.getRate(); ``` ## API Reference ### RoyaltyTokenMetadata ```java public static RoyaltyTokenMetadata create() public static RoyaltyTokenMetadata create(byte[] cborBytes) public RoyaltyTokenMetadata royaltyToken(RoyaltyToken royaltyToken) public RoyaltyToken getRoyaltyToken() public byte[] serialize() ``` ### RoyaltyToken ```java public static RoyaltyToken create() public RoyaltyToken address(String address) public RoyaltyToken rate(Double rate) // 0.0 - 1.0 inclusive public String getAddress() public Double getRate() ``` ## CIP27 Specification Details - **Metadata Label**: 777 - **Royalty Rate**: Decimal fraction between 0.0 and 1.0 (validated in setter) - **Address Format**: Not validated. An address over 64 bytes is stored as an array of chunks, which CIP-27 allows ## Best Practices 1. Validate royalty addresses before storing. 2. Keep rates within 0.0-1.0 (the setter enforces this range). 3. Mint the royalty token before any other asset of the policy. For more information about CIP27, refer to the [official CIP27 specification](https://cips.cardano.org/cips/cip27/). --- # CIP-30 — Wallet bridge Source: https://cardano-client.dev/standards/cip30/ Summary: dApp Connector and Data Signing implementation CIP30 (Cardano Improvement Proposal 30) defines a standard for dApp-wallet communication on the Cardano blockchain. The Cardano Client Library provides CIP30-compatible data signing and verification built on CIP8 COSE Sign1. It bundles the signing flow—header construction, Sig_Structure generation, signing, and verification—into a small surface so you can focus on wallet UX instead of COSE internals. ## Key Features - **Data Signing**: `CIP30DataSigner.signData(...)` - **Verification**: `CIP30DataSigner.verify(DataSignature)` - **COSE Integration**: Uses CIP8 builders internally - **UTXO Bridge**: `CIP30UtxoSupplier` for hex-encoded UTxOs ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-cip30 - **Dependencies**: cip8, core ## Usage Examples ### Signing Data with CIP30 Sign arbitrary payload bytes together with the user's address (embedded in COSE headers) to prove address ownership. ```java // Create account for signing Account signerAccount = Account.createFromMnemonic(Networks.testnet(), "your mnemonic words"); byte[] addressBytes = signerAccount.getBaseAddress().getBytes(); // raw address bytes, not the bech32 text byte[] payload = "Hello from dApp!".getBytes(StandardCharsets.UTF_8); DataSignature dataSignature = CIP30DataSigner.INSTANCE.signData( addressBytes, payload, signerAccount); String signatureHex = dataSignature.signature(); String keyHex = dataSignature.key(); ``` ### Verifying CIP30 Signatures Verify both the signature and that the address in the protected header matches the public key used for signing. ```java DataSignature dataSignature = new DataSignature(signatureHex, keyHex); boolean isValid = CIP30DataSigner.INSTANCE.verify(dataSignature); ``` ### Working with DataSignature Objects Read back address, curve, and public key information from the COSE-encoded signature without manual parsing. ```java DataSignature dataSignature = new DataSignature(signatureHex, keyHex); byte[] addressFromSignature = dataSignature.address(); Integer crv = dataSignature.crv(); byte[] x = dataSignature.x(); ``` ### Signing with Hash Payload Enable hashing for large payloads so only a Blake2b-224 hash is signed instead of the full data. ```java DataSignature dataSignature = CIP30DataSigner.INSTANCE.signData( addressBytes, largePayload, signerAccount, true // hashPayload = true ); ``` ### CIP-95: Signing with DRep / Governance Keys [CIP-95](https://cips.cardano.org/cips/cip95/) extends `signData` to DRep keys. Per spec, the COSE `address` header is a **type-6 enterprise address** built from the credential hash — so no separate API is required: pass the enterprise address bytes to the existing `signData` / `verify` and the address-reconstruction check works as-is. ```java // Derive DRep keys from the account HdKeyPair drepKeyPair = account.drepHdKeyPair(); Address drepAddress = AddressProvider.getEntAddress(account.drepCredential(), Networks.testnet()); DataSignature dataSignature = CIP30DataSigner.INSTANCE.signData( drepAddress.getBytes(), payload, drepKeyPair.getPrivateKey().getKeyData(), drepKeyPair.getPublicKey().getKeyData()); boolean isValid = CIP30DataSigner.INSTANCE.verify(dataSignature); ``` ### UTXO Management (CIP30UtxoSupplier) Convert wallet-provided hex-encoded CBOR UTxOs into the library's `UtxoSupplier` for transaction building. ```java // Hex-encoded CBOR UTxOs from wallet getUtxos() List walletUtxos = fetchFromWallet(); CIP30UtxoSupplier utxoSupplier = new CIP30UtxoSupplier(walletUtxos); List page = utxoSupplier.getPage(address, 20, 0, OrderEnum.asc); ``` ## API Reference ### CIP30DataSigner ```java // Sign data public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull Account signer) public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull Account signer, boolean hashPayload) public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey) public DataSignature signData(@NonNull byte[] addressBytes, @NonNull byte[] payload, @NonNull byte[] pvtKey, @NonNull byte[] pubKey, boolean hashPayload) // Verify signature public boolean verify(@NonNull DataSignature dataSignature) ``` ### DataSignature ```java public DataSignature(@NonNull String signature, @NonNull String key) public DataSignature signature(@NonNull String signature) public DataSignature key(@NonNull String key) public byte[] address() // address bytes from protected header public Integer crv() // curve id from COSE_Key other header public byte[] x() // public key bytes (x) from COSE_Key other header ``` ### CIP30UtxoSupplier ```java // Construct from hex-encoded CBOR UTxOs public CIP30UtxoSupplier(List hexEncodedCborUtxos) public List getPage(String address, Integer pageSize, Integer page, OrderEnum order) public Optional getTxOutput(String txHash, int outputIndex) public List getAll(String paymentAddress) ``` ### COSE values The signer uses these values: - **ADDRESS_KEY**: "address" (stored in protected header other map) - **ALG_EdDSA**: -8 - **CRV_Ed25519**: 6 - **OKP**: 1 - **X_KEY**: -2 (public key bytes) - **CRV_KEY**: -1 (curve id) ## CIP30 Data Signing Flow 1. `CIP30DataSigner` builds a CIP8 COSE_Sign1 with the payload (optionally hashed) and address in headers. 2. Signature is created with `Configuration.INSTANCE.getSigningProvider()`. 3. `DataSignature` stores the hex-encoded COSE_Sign1 and COSE_Key. 4. `verify` recreates the SigStructure, checks the signature, and verifies the address against the key. ## Integration with Other Standards - **CIP8**: All signing uses the CIP8 COSE builders. - **CIP67/68/25**: Use normal transaction-building APIs for assets/metadata; CIP30 only covers data signing. For more information about CIP30, refer to the [official CIP30 specification](https://cips.cardano.org/cips/cip30/). --- # CIP-67 — Asset name labels Source: https://cardano-client.dev/standards/cip67/ Summary: Asset Name Label Registry implementation CIP67 (Cardano Improvement Proposal 67) defines a standard for asset name labels on the Cardano blockchain. The Cardano Client Library provides utilities for creating, validating, and working with CIP67-compliant asset names. The utilities keep the rules around padding, CRC8 checksum, and label ranges in one place so you can focus on assembling asset names without re-implementing the bit-twiddling logic. ## Key Features - **Label Encoding**: Convert decimal labels to CIP67 asset name prefixes - **Label Decoding**: Extract labels from CIP67 asset names - **Validation**: Verify CIP67 compliance and checksum validation - **CRC8 Checksum**: Built-in CRC8 checksum calculation and verification - **Standard Compliance**: Full CIP67 specification support ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-cip67 - **Dependencies**: crypto ## Usage Examples ### Converting Labels to Asset Name Prefixes Generate the 4-byte prefix for a given label; you can append your own suffix bytes afterwards. ```java // Convert a decimal label to CIP67 asset name prefix int label = 222; // Label range: [0, 65535] byte[] prefixBytes = CIP67AssetNameUtil.labelToPrefix(label); // Convert to hex string for display String prefixHex = HexUtil.encodeHexString(prefixBytes); System.out.println("Label " + label + " -> Prefix: " + prefixHex); // Output: Label 222 -> Prefix: 000de140 ``` ### Extracting Labels from Asset Names Recover the numeric label from the prefix to inspect or validate existing assets. ```java // Extract label from CIP67 asset name prefix byte[] prefixBytes = HexUtil.decodeHexString("000de140"); int extractedLabel = CIP67AssetNameUtil.prefixToLabel(prefixBytes); System.out.println("Prefix 000de140 -> Label: " + extractedLabel); // Output: Prefix 000de140 -> Label: 222 ``` ### Validating CIP67 Asset Names `isValidAssetName` checks the padding and the checksum. It doesn't check every padding bit (see [known issues](/project/known-issues/)), so treat it as a sanity check rather than proof of a well-formed name. ```java // Validate asset name from bytes byte[] assetNameBytes = HexUtil.decodeHexString("000de140"); boolean isValidBytes = CIP67AssetNameUtil.isValidAssetName(assetNameBytes); System.out.println("Asset name valid (bytes): " + isValidBytes); // Output: Asset name valid (bytes): true // Validate asset name from integer int assetNameInt = 0x000de140; boolean isValidInt = CIP67AssetNameUtil.isValidAssetName(assetNameInt); System.out.println("Asset name valid (int): " + isValidInt); // Output: Asset name valid (int): true ``` ### Working with Asset Names in Transactions Combine the CIP67 prefix with your human-readable suffix to form the final asset name used in minting. ```java // Create asset name with CIP67 label int label = 222; // NFT label byte[] labelPrefix = CIP67AssetNameUtil.labelToPrefix(label); // Create full asset name by combining prefix with unique suffix byte[] uniqueSuffix = "MyNFT".getBytes(StandardCharsets.UTF_8); byte[] fullAssetName = new byte[labelPrefix.length + uniqueSuffix.length]; System.arraycopy(labelPrefix, 0, fullAssetName, 0, labelPrefix.length); System.arraycopy(uniqueSuffix, 0, fullAssetName, labelPrefix.length, uniqueSuffix.length); // Create asset with CIP67-compliant name Asset nftAsset = Asset.builder() .name("0x" + HexUtil.encodeHexString(fullAssetName)) // without 0x, the text is read as UTF-8 .value(BigInteger.ONE) .build(); ``` ### Batch Processing of Asset Names Run validations over multiple asset names and extract labels where valid. ```java // Process multiple asset names List assetNameHexes = Arrays.asList( "000de140", // Valid CIP67 asset name "000de141", // Invalid padding "100de140", // Invalid checksum "000de130" // Invalid checksum ); for (String assetNameHex : assetNameHexes) { byte[] assetNameBytes = HexUtil.decodeHexString(assetNameHex); boolean isValid = CIP67AssetNameUtil.isValidAssetName(assetNameBytes); if (isValid) { int label = CIP67AssetNameUtil.prefixToLabel(assetNameBytes); System.out.println(assetNameHex + " -> Valid, Label: " + label); } else { System.out.println(assetNameHex + " -> Invalid"); } } ``` ### Common Label Examples Predefined labels are handy for common asset categories; encode them once and reuse. ```java // Common CIP67 labels Map commonLabels = new HashMap<>(); commonLabels.put(222, "NFT Standard"); commonLabels.put(333, "Fungible Token Standard"); commonLabels.put(444, "Rich Fungible Token Standard"); for (Map.Entry entry : commonLabels.entrySet()) { int label = entry.getKey(); String description = entry.getValue(); byte[] prefix = CIP67AssetNameUtil.labelToPrefix(label); String prefixHex = HexUtil.encodeHexString(prefix); System.out.println("Label " + label + " (" + description + ") -> " + prefixHex); } ``` ## API Reference ### CIP67AssetNameUtil Class Utility class for CIP67 asset name operations. #### Static Methods ##### labelToPrefix(int label) Converts a decimal label to CIP67 asset name prefix. ```java public static byte[] labelToPrefix(int label) ``` **Parameters:** - `label` - Decimal label in range [0, 65535] **Returns:** 4-byte prefix array ##### prefixToLabel(byte[] labelBytes) Extracts the label from CIP67 asset name prefix. ```java public static int prefixToLabel(byte[] labelBytes) ``` **Parameters:** - `labelBytes` - 4-byte prefix array **Returns:** Decimal label ##### isValidAssetName(int assetName) Validates a CIP67 asset name from integer value. ```java public static boolean isValidAssetName(int assetName) ``` **Parameters:** - `assetName` - Asset name as integer **Returns:** true if valid CIP67 asset name ##### isValidAssetName(byte[] assetName) Validates a CIP67 asset name from byte array. ```java public static boolean isValidAssetName(byte[] assetName) ``` **Parameters:** - `assetName` - Asset name as byte array **Returns:** true if valid CIP67 asset name ## CIP67 Specification Details ### Asset Name Structure A CIP-67 prefix is 4 bytes, `[0000 | 16-bit label | 8-bit CRC8 checksum | 0000]`, followed by the rest of the asset name. ### Label Constraints - **Range**: 0 to 65535 - **Encoding**: Big-endian format - **Checksum**: CRC8 validation required ### Validation Rules 1. **Padding Check**: The first and last 4 bits must be 0000 2. **Checksum Verification**: The CRC8 checksum must match the label ## Common Label Registry | Label | Description | Prefix (Hex) | |-------|-------------|--------------| | 100 | CIP-68 reference token | 000643b0 | | 222 | CIP-68 NFT | 000de140 | | 333 | CIP-68 fungible token | 0014df10 | | 444 | CIP-68 rich fungible token | 001bc280 | ## Best Practices 1. **Use Standard Labels**: Use established labels (222, 333, 444) for common asset types 2. **Validate Asset Names**: Always validate asset names before using them 3. **Handle Errors**: Check validation results before processing asset names 4. **Preserve Checksums**: Don't modify asset name prefixes without recalculating checksums 5. **Document Custom Labels**: Document any custom labels used in your application ## Integration with Other Standards ### With CIP68 (Datum Metadata) ```java // Create CIP68-compliant asset name int label = 222; // NFT label for CIP68 byte[] labelPrefix = CIP67AssetNameUtil.labelToPrefix(label); // Combine with asset-specific data, in hex with a 0x prefix String assetName = "0x" + HexUtil.encodeHexString(labelPrefix) + HexUtil.encodeHexString("MyUniqueNFT".getBytes(StandardCharsets.UTF_8)); ``` For more information about CIP67, refer to the [official CIP67 specification](https://github.com/cardano-foundation/CIPs/tree/master/CIP-0067). --- # CIP-68 — Datum metadata Source: https://cardano-client.dev/standards/cip68/ Summary: CIP68 Datum Metadata Api **Version:** **0.5.1** and later CIP68 defines a metadata standard for native assets using output datums. Cardano Client Lib provides builders for CIP68 NFT, FT, RFT tokens and their reference tokens so you can create the datum payloads, asset names, and reference tokens without manual Plutus data handling. ## Key Features - **Datum-Based Metadata** for NFTs, FTs, and RFTs - **Reference Tokens** (label 100) derived from each token - **CIP67 Labels** baked into asset names (222 / 333 / 444) - **Plutus Data Output** via `getDatumAsPlutusData()` ## Dependencies - **Group Id :** com.bloxbean.cardano - **Artifact Id:** cardano-client-cip68 ## Usage Examples ### CIP68 NFT Datum Metadata Api Build the on-chain datum metadata and related assets for an NFT that follows the 222 label convention. ```java CIP68NFT nft = CIP68NFT.create() .name("MyCIP68NFT") .image("https://xyz.com/image.png") .description("A sample CIP-68 NFT") .addFile(CIP68File.create() .mediaType("image/png") .name("image1.png") .src("https://xyz.com/image.png")); // Reference token (label 100) and user token assets CIP68ReferenceToken referenceToken = nft.getReferenceToken(); Asset referenceAsset = referenceToken.getAsset(); // quantity 1 enforced Asset userAsset = nft.getAsset(BigInteger.ONE); PlutusData datumMetadata = referenceToken.getDatumAsPlutusData(); ``` ### CIP68 FT Datum Metadata Api Create fungible token (333) metadata in datum form with ticker/decimals helpers. ```java CIP68FT ft = CIP68FT.create() .name("SampleFungibleToken") .ticker("SFT") .url("https://xyz.com") .logo("https://xyz.com/logo.png") .decimals(6) .description("Sample CIP-68 FT"); ``` ### CIP68 RFT Datum Metadata Api Create rich fungible token (444) metadata where you can attach images/files and decimals. ```java CIP68RFT rft = CIP68RFT.create() .name("SampleRichFungibleToken") .image("https://xyz.com/image.png") .description("Sample CIP-68 RFT") .decimals(2) .addFile(CIP68File.create() .mediaType("image/png") .name("image.png") .src("https://xyz.com/image.png")) .property("category", "art"); ``` ### Building a Transaction Reference tokens carry the datum; user tokens carry value. Include both mints in the same script transaction. :::note[`ScriptTx` in 0.7.x and the 0.8.0 previews] `ScriptTx` is the API for script transactions in the stable 0.7.x releases, where it isn't deprecated. In the 0.8.0 previews it's deprecated, because every operation is also available on `Tx`. On a preview, you can replace `new ScriptTx()` with `new Tx()` and add `.from(senderAddress)`. See [Unified Tx API](/quicktx/unified-tx/). ::: ```java // mintingScript is your PlutusScript minting policy; userTokenReceiver is the buyer's address PlutusData datum = referenceToken.getDatumAsPlutusData(); String referenceTokenReceiver = AddressProvider.getEntAddress(mintingScript, Networks.preprod()).toBech32(); ScriptTx scriptTx = new ScriptTx() .mintAsset(mintingScript, List.of(referenceAsset), PlutusData.unit(), referenceTokenReceiver, datum) .mintAsset(mintingScript, List.of(userAsset), PlutusData.unit(), userTokenReceiver); quickTxBuilder.compose(scriptTx) .feePayer(senderAddress) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` ## API Reference ### CIP68NFT ```java public static CIP68NFT create() public CIP68NFT name(String name) public CIP68NFT description(String description) public CIP68NFT image(String image) public CIP68NFT addFile(CIP68File file) public CIP68NFT property(String key, String value) public CIP68ReferenceToken getReferenceToken() public Asset getAsset(BigInteger quantity) public PlutusData getDatumAsPlutusData() public static CIP68NFT fromDatum(byte[] datumBytes) ``` ### CIP68FT ```java public static CIP68FT create() public CIP68FT name(String name) public CIP68FT description(String description) public CIP68FT ticker(String ticker) public CIP68FT decimals(int decimals) public CIP68FT url(String url) public CIP68FT logo(String logo) public CIP68ReferenceToken getReferenceToken() public Asset getAsset(BigInteger quantity) public PlutusData getDatumAsPlutusData() public static CIP68FT fromDatum(byte[] datumBytes) ``` ### CIP68RFT ```java public static CIP68RFT create() public CIP68RFT name(String name) public CIP68RFT description(String description) public CIP68RFT image(String image) public CIP68RFT decimals(int decimals) public CIP68RFT addFile(CIP68File file) public CIP68RFT property(String key, String value) public CIP68ReferenceToken getReferenceToken() public Asset getAsset(BigInteger quantity) public PlutusData getDatumAsPlutusData() public static CIP68RFT fromDatum(byte[] datumBytes) ``` ### CIP68ReferenceToken ```java public CIP68ReferenceToken(CIP68TokenTemplate tokenTemplate) public Asset getAsset() // quantity 1 public Asset getAsset(BigInteger quantity) // throws if quantity > 1 public PlutusData getDatumAsPlutusData() ``` ### CIP68File ```java public static CIP68File create() public CIP68File mediaType(String mediaType) public CIP68File name(String name) public CIP68File src(String src) public PlutusData toPlutusData() public static CIP68File create(MapPlutusData map) ``` ## CIP68 Specification Details - **Asset Labels**: 222 (NFT), 333 (FT), 444 (RFT) via CIP67 prefixes - **Reference Token Label**: 100 - **Datum Structure**: ConstrPlutusData with metadata map, version, and extra field (see `CIP68Datum`) - **Payload**: Stored in datum, not transaction metadata ## Best Practices 1. Use reference tokens for storing metadata on-chain. 2. Keep datum payloads small to reduce costs. 3. Validate URLs and sizes before serialization. 4. Ensure asset names (prefix + name) stay within 32 bytes (`getAsset` enforces this). For more information about CIP68, refer to the [official CIP68 specification](https://cips.cardano.org/cip/CIP-0068). --- # CIP-8 — Message signing Source: https://cardano-client.dev/standards/cip8/ Summary: Message Signing with COSE Signatures implementation CIP8 (Cardano Improvement Proposal 8) defines a standard for message signing using COSE (CBOR Object Signing and Encryption) signatures. The Cardano Client Library exposes builders and models for COSE Sign1 and COSE Sign so you can create and verify signatures that follow the specification. The focus is on providing small, composable pieces: you assemble headers, build a `SigStructure`, sign with your own signing provider, and serialize/deserialise with the COSE models. ## Key Features - **COSE Sign1 and Sign** builders - **Payload hashing** toggle (Blake2b-224) - **Header helpers** for protected and unprotected maps - **COSE key utilities** for crv/x headers and serialization ## Dependencies - **Group ID**: com.bloxbean.cardano - **Artifact ID**: cardano-client-cip8 - **Dependencies**: common, crypto ## Usage Examples ### Creating COSE Sign1 Signatures ```java // Protected headers (algorithm + optional content type or key id) HeaderMap protectedHeaders = new HeaderMap() .algorithmId(-8) // EdDSA .contentType(-1000); // Optional content type // Unprotected headers for custom values HeaderMap unprotectedHeaders = new HeaderMap() .addOtherHeader(-100, "Some header value"); Headers headers = new Headers() ._protected(new ProtectedHeaderMap(protectedHeaders)) .unprotected(unprotectedHeaders); byte[] payload = "Hello World".getBytes(StandardCharsets.UTF_8); COSESign1Builder builder = new COSESign1Builder(headers, payload, false) .hashed(true); // Blake2b-224 hashing of payload // For a 32-byte key. For an HD key's 64-byte extended key, use signExtended. SigningProvider signingProvider = CryptoConfiguration.INSTANCE.getSigningProvider(); SigStructure sigStructure = builder.makeDataToSign(); byte[] signature = signingProvider.sign(sigStructure.serializeAsBytes(), privateKeyBytes); COSESign1 coseSign1 = builder.build(signature); byte[] serialized = coseSign1.serializeAsBytes(); String hexSignature = HexUtil.encodeHexString(serialized); ``` ### Creating COSE Sign Signatures (Multiple Signers) ```java Headers headers = new Headers() ._protected(new ProtectedHeaderMap(new HeaderMap().algorithmId(-8))) .unprotected(new HeaderMap()); byte[] payload = "Multi-signer message".getBytes(StandardCharsets.UTF_8); COSESignBuilder builder = new COSESignBuilder(headers, payload, false) .hashed(true); SigStructure sigStructure = builder.makeDataToSign(); List signatures = new ArrayList<>(); for (byte[] pvtKey : signerPrivateKeys) { byte[] sig = signingProvider.sign(sigStructure.serializeAsBytes(), pvtKey); signatures.add(new COSESignature() .headers(new Headers() ._protected(new ProtectedHeaderMap(new HeaderMap().algorithmId(-8))) .unprotected(new HeaderMap())) .signature(sig)); } COSESign coseSign = builder.build(signatures); byte[] serialized = coseSign.serializeAsBytes(); ``` ### Signature Verification `COSESign1` exposes `signedData()` to reconstruct the SigStructure for verification. ```java COSESign1 coseSign1 = COSESign1.deserialize(signatureBytes); SigStructure signedData = coseSign1.signedData(); boolean isValid = signingProvider.verify( coseSign1.signature(), signedData.serializeAsBytes(), publicKeyBytes); byte[] payload = coseSign1.payload(); ``` ### Working with Headers ```java Headers headers = coseSign1.headers(); HeaderMap protectedHeaders = headers._protected().getAsHeaderMap(); HeaderMap unprotectedHeaders = headers.unprotected(); Object algorithmId = protectedHeaders.algorithmId(); Object contentType = protectedHeaders.contentType(); byte[] keyId = protectedHeaders.keyId(); String customValue = unprotectedHeaders.otherHeaderAsString(-100); ``` ### Creating COSE Keys `COSEKey` stores standard fields plus arbitrary other headers (e.g., crv, x). ```java COSEKey coseKey = new COSEKey() .keyType(1) // Octet Key Pair .algorithmId(-8) // EdDSA .addOtherHeader(-1, new UnsignedInteger(6)) // crv (Ed25519) .addOtherHeader(-2, new ByteString(publicKeyBytes)); byte[] keyBytes = coseKey.serializeAsBytes(); COSEKey deserializedKey = COSEKey.deserialize(keyBytes); byte[] extractedPublicKey = deserializedKey.otherHeaderAsBytes(-2); ``` ## API Reference ### Core Classes #### COSESign1Builder Builder class for creating COSE Sign1 signatures. ```java // Constructor COSESign1Builder(Headers headers, byte[] payload, boolean isPayloadExternal) // Methods SigStructure makeDataToSign() // Create signature structure COSESign1 build(byte[] signature) // Build final signature COSESign1Builder hashed(boolean hashed) // Enable Blake2b-224 hashing ``` #### COSESignBuilder Builder class for creating COSE Sign signatures with multiple signers. ```java // Constructor COSESignBuilder(Headers headers, byte[] payload, boolean isPayloadExternal) // Methods SigStructure makeDataToSign() // Create signature structure COSESign build(List signatures) // Build final signature COSESignBuilder hashed(boolean hashed) // Enable Blake2b-224 hashing ``` #### Headers Manages protected and unprotected headers. ```java Headers() // use fluent setters // Methods Headers _protected(ProtectedHeaderMap protectedHeaders) Headers unprotected(HeaderMap unprotectedHeaders) HeaderMap _protected().getAsHeaderMap() // Decode protected map HeaderMap unprotected() // Get unprotected headers Headers copy() // Create a copy ``` #### HeaderMap Manages individual header maps. ```java // Methods HeaderMap algorithmId(long or String algorithmId) HeaderMap contentType(long or String contentType) HeaderMap keyId(byte[] keyId) HeaderMap addOtherHeader(long key, String value) // Custom header; also DataItem, BigInteger, long and byte[] values Object algorithmId() Object contentType() byte[] keyId() byte[] otherHeaderAsBytes(long key) long otherHeaderAsLong(long key) String otherHeaderAsString(long key) ``` #### COSEKey Represents a COSE key map. ```java COSEKey keyType(long or String) COSEKey algorithmId(long or String) COSEKey addOtherHeader(long key, DataItem value) COSEKey addOtherHeader(long key, long/String/BigInteger/byte[] value) byte[] serializeAsBytes() COSEKey deserialize(byte[] bytes) byte[] otherHeaderAsBytes(long key) long otherHeaderAsLong(long key) String otherHeaderAsString(long key) ``` ## CIP8 Specification Details ### COSE Sign1 Structure Components describe exactly what is signed and carried in the CBOR array: - **Protected Headers**: Algorithm ID, content type, key ID - **Unprotected Headers**: Custom headers - **Payload**: Message data (optionally hashed with Blake2b-224) - **Signature**: EdDSA signature over the structure ### Signature Process 1. **Header Creation**: Set protected/unprotected headers with algorithm, optional content type, and custom keys. 2. **Payload Preparation**: Optionally hash payload with Blake2b-224 (`hashed(true)`). 3. **Structure Building**: `makeDataToSign()` yields the Sig_Structure the signer must sign. 4. **Signing**: Sign the serialized Sig_Structure using EdDSA with your signing provider. 5. **Serialization**: Build `COSESign1` and encode via `serializeAsBytes()`. ### Algorithm Identifiers - **-8**: EdDSA (Edwards Curve Digital Signature Algorithm), the COSE algorithm id - **1**: Octet Key Pair key type ## Advanced CIP8 Features ### COSE Encryption Encryption helpers are available as low-level containers: - `COSEEncrypt` holds headers, ciphertext, and recipients. - `PasswordEncryption` wraps a `COSEEncrypt0` instance (tag 16). - `PubKeyEncryption` wraps a `COSEEncrypt` instance (tag 96). You construct the underlying `COSEEncrypt`/`COSEEncrypt0` yourself and then wrap it if you need the tagged form. ## Integration with Other CIPs - **CIP30**: Uses CIP8 for dApp wallet data signing For more information about CIP8, refer to the [official CIP8 specification](https://cips.cardano.org/cips/cip8/). --- # Accounts & addresses Source: https://cardano-client.dev/start-here/accounts-and-addresses/ Summary: Account & Backend Provider Setup ## Select a Network and Provider First we need to select a network for our transaction. You can choose one of the available public test network. - **Preprod** - **Preview** - **Local Devnet (Yaci DevKit)** — for local development and testing Similarly, choose a backend provider to interact with Cardano blockchain. You can select either Koios or Blockfrost as backend provider. Please check [dependencies](/start-here/installation/) page to find the required dependency for your selected backend provider. > > For **Blockfrost** as backend provider, you need to first create an account on [blockfrost.io](https://blockfrost.io) and get > a ``Project Id`` for the selected network. > > For **Koios** backend provider, you don't need any registration. > For **Yaci DevKit** as a local development environment, install and run > [Yaci DevKit](https://devkit.yaci.xyz/#get-started). It provides a local Cardano devnet with > built-in Yaci Store that exposes Blockfrost-compatible APIs. No registration or API key is required. ## Create Accounts The following code snippet shows how to create a new testnet account. ```java Account account = new Account(Networks.testnet()); String baseAddress = account.baseAddress(); String mnemonic = account.mnemonic(); ``` If you already have mnemonic for an existing account, you can create the account from mnemonic. ```java String mnemonic = "<24 words mnemonic>"; Account account = Account.createFromMnemonic(Networks.testnet(), mnemonic); ``` > > Two types of address can be generated, mainnet address or testnet address. > > To generate a test network address, you can use any of the network constant ``Networks.testnet()``, ``Networks.preprod()`` or > ``Networks.preview()``. The generated testnet address can be used on any of the test network. > **(The address generation depends on the NetworkId in Network object not protocol magic. These public test networks have same network id (0))** > > For mainnet address, you need to use ``Networks.mainnet()`` ## Topup account with test Ada Based on your selected network, get some test Ada from one of the below faucet. You need to provide an ``address`` generated in the previous section to get some test Ada. https://docs.cardano.org/cardano-testnets/tools/faucet ## Create Backend Service **For Blockfrost :** Use the correct Blockfrost url for the selected network and project id to create an instance of BackendService. ```java String bfProjectId = "preprod..."; BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREPROD_URL, bfProjectId); ``` **Note:** You can find Blockfrost urls for the supported networks in ``com.bloxbean.cardano.client.backend.blockfrost.common.Constants``. or, **For Koios :** ```java BackendService backendService = new KoiosBackendService(Constants.KOIOS_PREVIEW_URL); ``` **Note:** You can find other Koios urls in ``com.bloxbean.cardano.client.backend.koios.Constants`` or, **For Yaci DevKit (Local Devnet) :** ```java BackendService backendService = new BFBackendService("http://localhost:8080/api/v1/", "dummy-key"); ``` **Note:** Yaci DevKit uses Yaci Store which provides Blockfrost-compatible APIs. --- # Build from source Source: https://cardano-client.dev/start-here/build-from-source/ Summary: How to build from source ? You need Java 17. ``` git clone https://github.com/bloxbean/cardano-client-lib.git cd cardano-client-lib export JAVA_TOOL_OPTIONS=-Dfile.encoding=UTF8 ./gradlew clean build ``` # Run Integration Tests ``` export BF_PROJECT_ID= ./gradlew :integration-test:integrationTest -PBF_PROJECT_ID=${BF_PROJECT_ID} ``` --- # Core concepts Source: https://cardano-client.dev/start-here/core-concepts/ Summary: Key Concepts in Cardano Client Lib ## Account Account class provides a simple abstraction to create and manage secrets, and perform account-based work such as signing transactions. It encapsulates features like CIP 1852 compatible address derivation, BIP-39 mnemonic generation through a simple ``Account`` class. An instance of ``Account`` can be created from an existing mnemonic or account private key and derivation path. ## Transaction Building Apis ### Low Level Serialization Api These are low level serialization api to build transaction for Cardano network. These APIs are flexible and good for complex scenarios. Basically, you can achieve any complexity with low level api. These apis provide flexibility, but building transactions this way is complex. Unless really required, this option should be avoided to build transactions. ### QuickTx API (Recommended) This new declarative-style transaction builder API strikes a balance between simplicity and flexibility. It's built upon the Composable Functions API and offers a more streamlined experience, supporting an extensive range of transactions. Those familiar with the Lucid JS library will notice many similarities. ### Composable Functions API This is the internal layer beneath QuickTx, for the rare case QuickTx can't express what you need. See [Composable functions](/reference/composable-functions/). ### High-Level Api (Deprecated) Provides simple interfaces to do transfer and token minting transaction. These apis are beginner friendly, but some complex transactions may not be possible through high-level API. ## Ledger data provider apis Cardano is an UTXO based blockchain. Unlike account-based blockchains like Ethereum, a transaction in Cardano has inputs and outputs, where inputs are unspent outputs from previous transactions. Assets are stored on the ledger in unspent outputs, rather than accounts. A transaction is built using off-chain code and submitted to Cardano node for processing. But inputs (unspent outputs) used in a transaction are fetched from the Cardano blockchain. Similarly, few protocol parameters may be required during transaction building. ### Supplier Interfaces The library provides simple supplier interfaces which can be implemented to provide required on-chain data during transaction building. These apis remove direct dependency with on-chain data provider in core library. **1. ProtocolParamsSupplier** :- Implement this functional interface to provide current protocol parameters. Protocol parameters are required to build the transaction. ```java public interface ProtocolParamsSupplier { ProtocolParams getProtocolParams(); } ``` **2. UtxoSupplier**:- Implement this interface to provide the utxos which are required during transaction building. ```java public interface UtxoSupplier { ... List getPage(String address, Integer nrOfItems, Integer page, OrderEnum order); Optional getTxOutput(String txHash, int outputIndex); ... } ``` **3. TransactionProcessor** :- Implement this interface to submit transactions to the Cardano blockchain and evaluate script costs. This is an optional interface. ```java public interface TransactionProcessor extends TransactionEvaluator { Result submitTransaction(byte[] cborData) throws ApiException; // evaluateTx(...) comes from TransactionEvaluator } ``` ### Backend Api Alternatively, the required on-chain data for transaction building can be retrieved through backend api layer. The library defines standard apis to get commonly used on-chain data from a provider. Using a backend service implementation, you can easily create an instance of ``UtxoSupplier`` or ``ProtocolParamsSupplier``. Following Backend apis are currently available - TransactionService (Submit transaction, Get transaction, Evaluate ExUnits for Script Txn) - AddressService (Get address details) - UtxoService (Get utxos for an address) - AssetService - BlockService - NetworkInfoService - EpochService - MetadataService > > Though backend api provides an easy way to fetch required data from the blockchain, but it's not mandatory for transaction > building. You can just implement supplier interfaces to provide required on-chain data to build a transaction. **There is no > direct dependency between library's core functionality and backend apis.** #### Backend Providers Out of box, the library currently supports integration with following providers through the Backend api. - Blockfrost - Koios - Ogmios (Supports submitTransaction, evaluateTx and UtxoService through Kupo) - Nexus ## Utxo Selection Strategy The library provides different utxo selection strategy implementations. The utxo selection strategy can be changed by providing a custom implementation of "UtxoSelectionStrategy" interface or by selecting an existing one. - **DefaultUtxoSelectionStrategyImpl** - **LargestFirstUtxoSelectionStrategy** - **RandomImproveUtxoSelectionStrategy** > > By default, ``DefaultUtxoSelectionStrategy`` is used both in **Composable functions** and **High Level api**. The default strategy > is too simple and finds required utxos sequentially. But it may not be efficient for some use-cases. You can easily use another > available UtxoSelectionStrategy impl or provide your own implementation. ## CIP Implementations Cardano Improvement Proposals (CIPs) describe standards, processes; or provide general guidelines or information to the Cardano Community. It is a formal, technical communication process that exists off-chain. The library implements some commonly used CIPs. - [CIP20 - Transaction Message/Comment metadata](https://cips.cardano.org/cips/cip20/) - [CIP25 - NFT Metadata Standard](https://cips.cardano.org/cips/cip25/) - [CIP8 - Message Signing](https://cips.cardano.org/cips/cip8/) - [CIP30 - dApp signData & verify](https://cips.cardano.org/cips/cip30/) --- # Installation Source: https://cardano-client.dev/start-here/installation/ Summary: Maven and Gradle dependencies ## Versions - **Latest stable:** 0.7.3 - **Latest preview:** 0.8.0-pre7 Previews contain the newest features, such as TxFlow, TxStream, TxPlan and verified data structures, and their APIs may change before the stable release. See [what's new](/project/whats-new/) and the [release notes](https://github.com/bloxbean/cardano-client-lib/releases). The stable release needs Java 11 or later, and previews need Java 17 or later. Throughout this site, replace `` or `{version}` in a dependency with the version you're using. Pages for preview features need the latest preview. ## For release binaries **For Maven, add the following dependencies to project's pom.xml** - **Core module** ```xml com.bloxbean.cardano cardano-client-lib {version} ``` - **Backend modules** - For backend support, use one of the following supported backend module ```xml com.bloxbean.cardano cardano-client-backend-blockfrost {version} com.bloxbean.cardano cardano-client-backend-koios {version} com.bloxbean.cardano cardano-client-backend-ogmios {version} ``` **For Gradle, add the following dependencies to build.gradle** - **Core Module** ``` implementation 'com.bloxbean.cardano:cardano-client-lib:' ``` - **Backend modules** - For backend support, use one of the following supported backend module ```groovy //For Blockfrost implementation 'com.bloxbean.cardano:cardano-client-backend-blockfrost:' //For Koios implementation 'com.bloxbean.cardano:cardano-client-backend-koios:' //For Ogmios implementation 'com.bloxbean.cardano:cardano-client-backend-ogmios:' ``` ## For snapshot binaries > > **SNAPSHOT_VERSION :** the version in [gradle.properties](https://github.com/bloxbean/cardano-client-lib/blob/master/gradle.properties), which ends in `-SNAPSHOT` - For Maven, add the following dependencies and repository to project's pom.xml ``` com.bloxbean.cardano cardano-client-lib {SNAPSHOT_VERSION} com.bloxbean.cardano cardano-client-backend-blockfrost {SNAPSHOT_VERSION} snapshots-repo https://central.sonatype.com/repository/maven-snapshots false true ``` - For Gradle, add the following dependencies and repository to build.gradle ``` repositories { ... maven { url "https://central.sonatype.com/repository/maven-snapshots" } } implementation 'com.bloxbean.cardano:cardano-client-lib:{SNAPSHOT_VERSION}' implementation 'com.bloxbean.cardano:cardano-client-backend-blockfrost:{SNAPSHOT_VERSION}' ``` --- # Key APIs at a glance Source: https://cardano-client.dev/start-here/key-apis/ Summary: Key apis of Cardano Client Lib ## Account API Usage ### Create a New Account ``` Account account = new Account(); //Create a Mainnet account Account account = new Account(Networks.mainnet()); //Create a Mainnet account Account account = new Account(Networks.testnet()); //Create a Testnet account ``` ### Get base address, enterprise address, mnemonic ``` String baseAddress = account.baseAddress(); //Base address at index=0 String enterpriseAddress = account.enterpriseAddress(); //Enterprise address at index = 0 String mnemonic = account.mnemonic(); //Get Mnemonic ``` ### Get Account from Mnemonic ``` String mnemonic = "..."; Account account = Account.createFromMnemonic(Networks.mainnet(), mnemonic); //Create a Mainnet account from Mnemonic Account account = Account.createFromMnemonic(Networks.testnet(), mnemonic); //Create a Testnet account from Mnemonic ``` ## Create Blockfrost Backend Service and get other services ``` BackendService backendService = new BFBackendService(Constants.BLOCKFROST_PREVIEW_URL, ""); FeeCalculationService feeCalculationService = backendService.getFeeCalculationService(); TransactionService transactionService = backendService.getTransactionService(); BlockService blockService = backendService.getBlockService(); AssetService assetService = backendService.getAssetService(); UtxoService utxoService = backendService.getUtxoService(); MetadataService metadataService = backendService.getMetadataService(); EpochService epochService = backendService.getEpochService(); AddressService addressService = backendService.getAddressService(); ``` ## Simple ADA Payment (QuickTx Api) ```java Tx tx1 = new Tx() .payToAddress(receiver1Addr, Amount.ada(1.5)) .payToAddress(receiver2Addr, Amount.ada(2.5)) .attachMetadata(MessageMetadata.create().add("This is a test message 2")) .from(sender1Addr); Tx tx2 = new Tx() .payToAddress(receiver2Addr, Amount.ada(4.5)) .from(sender2Addr); QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService); Result result = quickTxBuilder .compose(tx1, tx2) .feePayer(sender1Addr) .withSigner(SignerProviders.signerFrom(sender1)) .withSigner(SignerProviders.signerFrom(sender2)) .completeAndWait(System.out::println); ``` QuickTx is the recommended way to build transactions. The lower-level [composable functions](/reference/composable-functions/) API is the internal layer beneath it. ## ScriptHash ``` Example: 1 ScriptPubkey scriptPubkey = new ScriptPubkey("ad7a7b87959173fc9eac9a85891cc93892f800dd45c0544128228884") String policyId = scriptPubkey.getPolicyId(); Example: 2 ScriptPubkey scriptPubkey1 = ...; SecretKey sk1 = ...; ScriptPubkey scriptPubkey2 = ...; SecretKey sk2 = ...; ScriptPubkey scriptPubkey3 = ...; SecretKey sk3 = ...; ScriptAtLeast scriptAtLeast = new ScriptAtLeast(2) .addScript(scriptPubkey1) .addScript(scriptPubkey2) .addScript(scriptPubkey3); String policyId = scriptAtLeast.getPolicyId(); ``` ## Metadata ``` MetadataMap productDetailsMap = MetadataBuilder.createMap() .put("code", "PROD-800") .put("slno", "SL20000039484"); MetadataList tagList = MetadataBuilder.createList() .add("laptop") .add("computer"); Metadata metadata = MetadataBuilder.createMetadata() .put(new BigInteger("670001"), productDetailsMap) .put(new BigInteger("670002"), tagList); ``` --- # Why Cardano Client Lib Source: https://cardano-client.dev/start-here/why-ccl/ Summary: A Java library for building, signing and submitting Cardano transactions — what it covers, who it is for, and where to start. Cardano Client Lib (CCL) is a Java library for interacting with the Cardano blockchain. It builds, signs and submits transactions — from a simple ADA payment to a Plutus script call, a governance vote, or a multi-step workflow where one transaction spends what the last one produced. It is a **library, not a framework**. There is no server to run and no lifecycle to adopt: you call it from whatever you already have — a Spring service, a CLI, a test, a Lambda. ```java Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(sender.baseAddress()); TxResult result = new QuickTxBuilder(backendService) .compose(tx) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); ``` That is a complete payment. UTXO selection, balancing, fee calculation, change and confirmation tracking are handled. `result.getTxStatus()` tells you whether it was confirmed. ## What it covers | Area | What you can do | |---|---| | **Transactions** | Payments, multi-asset outputs, metadata, validity intervals, custom fee payers and collateral | | **Native tokens & NFTs** | Minting and burning with native-script or Plutus policies, CIP-25 and CIP-68 metadata | | **Smart contracts** | Spend from and mint with Plutus V1–V3 validators, reference scripts and inputs, CIP-57 blueprint code generation, Aiken and Scalus interop | | **Staking** | Registration, delegation, reward withdrawal, deregistration, script-based stake credentials | | **Governance** | Full Conway era — DRep registration and voting, vote delegation, proposals, committee and treasury actions | | **Addresses & keys** | BIP39 mnemonics, CIP-1852 derivation, HD wallets, base/enterprise/pointer/reward addresses | | **Standards** | CIP-8, 20, 25, 27, 30, 67, 68 | | **Backends** | Blockfrost, Koios, Ogmios/Kupo, Nexus — or your own via `UtxoSupplier` | Beyond a single transaction, three preview APIs handle the harder coordination problems: [TxPlan](/txplan/getting-started/) for transactions defined as configuration, [TxFlow](/txflow/your-first-flow/) for multi-step workflows with UTXO chaining and rollback handling, and [TxStream](/txstream/getting-started/) for continuous submission with exactly-once delivery. ## Where to start | You are | Start at | |---|---| | New to the library | [Installation](/start-here/installation/), then [Your first transaction](/learn/simple-transfer/) | | New to Cardano's UTXO model | [Core concepts](/start-here/core-concepts/) | | Looking for a specific API | [Key APIs at a glance](/start-here/key-apis/) | | Chaining several transactions | [TxFlow: your first flow](/txflow/your-first-flow/) | | An AI agent, or using one | [AI Starter Pack](/ai/starter-pack/) | The [guided path](/learn/simple-transfer/) runs from a first payment through tokens, NFTs, multi-sig, Plutus scripts and staking. It is worth following in order if you are new — each step builds on the one before. ## Versions See [Versions](/start-here/installation/#versions) for the latest stable and preview releases, and [what's new](/project/whats-new/) for the Unified Tx API, TxFlow, TxStream, TxPlan, programmable tokens and verified data structures. The stable release needs Java 11 or later, and previews need Java 17 or later. ## Reading and watching - [Introducing the QuickTx API](https://satran004.medium.com/introducing-new-quicktx-api-in-cardano-client-lib-0-5-0-beta1-5beb491282ce) - [Testing an Aiken contract from Java with Yaci DevKit](https://youtu.be/PTnSc85t0Nk?si=44uK6KFrTIH3m06A) - [Example projects](https://github.com/bloxbean/cardano-client-examples) — runnable samples - [Showcase](/project/showcase/) — projects built with CCL --- # Durable Runtime Source: https://cardano-client.dev/txflow/durable-runtime/ Summary: Persist TxFlow attempts with H2 or PostgreSQL, fenced leases, and explicit recovery > **Preview API**: the durable runtime and RDBMS extension may change before the stable release. `FlowEngine` can run without a store, with the process-local reference store, or with the optional JDBC store. Choose deliberately: | Configuration | Survives process restart | Intended use | |---|---:|---| | No `FlowExecutionStore` | No | Simple non-durable execution | | `InMemoryFlowExecutionStore` | No | Reference semantics, unit tests, and local experiments | | H2 file-backed `RdbmsFlowExecutionStore` | Yes | Single-JVM, local-disk restart recovery | | PostgreSQL `RdbmsFlowExecutionStore` | Yes | Shared durable state and multi-process state fencing | `InMemoryFlowExecutionStore` implements the same atomic claim, journal, lease, and fencing contract as durable adapters, but all of its state disappears with the JVM. Do not use it as a database substitute when recovery after a crash matters. ## Dependency The relational store is an optional module and does not add database dependencies to the core `cardano-client-txflow` artifact: ```gradle def cclVersion = '' dependencies { implementation "com.bloxbean.cardano:cardano-client-txflow-store-rdbms:${cclVersion}" } ``` Maven: ```xml com.bloxbean.cardano cardano-client-txflow-store-rdbms {version} ``` The extension includes H2 at runtime. It deliberately does **not** include the PostgreSQL JDBC driver or a connection pool; a PostgreSQL application supplies both. ## Complete file-backed H2 wiring The following example wires a backend, a file-backed store, independently schedulable execution and lease-maintenance executors, and a durable request. Substitute your own `BackendService`, definition, bindings, and signer registry. ```java ExecutorService flowTasks = Executors.newFixedThreadPool(8); ExecutorService leaseMaintenance = Executors.newFixedThreadPool(4); try (RdbmsFlowExecutionStore store = RdbmsFlowExecutionStore.builder() .jdbcUrl("jdbc:h2:file:./data/txflow") .schemaManagement(SchemaManagement.MIGRATE) .build()) { FlowEngine engine = FlowEngine.builder( new DefaultUtxoSupplier(backendService.getUtxoService()), new DefaultProtocolParamsSupplier(backendService.getEpochService()), new DefaultTransactionProcessor(backendService.getTransactionService()), new DefaultChainDataSupplier(backendService)) .executor(flowTasks) .maintenanceExecutor(leaseMaintenance) .signerRegistry(signerRegistry) .store(store) .leaseDuration(Duration.ofSeconds(30)) .ownerToken("payments-" + UUID.randomUUID()) .build(); FlowExecutionRequest request = FlowExecutionRequest.builder(definition) .executionId(executionId) .idempotency(tenantId, operationId) .bindings(bindings) .build(); FlowExecutionResult result = engine.executeSync(request); if (!result.isSuccessful()) { // Inspect result.state(), result.error(), and result.attempts(). } } finally { flowTasks.shutdown(); leaseMaintenance.shutdown(); } ``` Keep the store and both executors alive until every asynchronous handle has completed. Neither the engine nor the store shuts down application executors. The maintenance-pool size above is only an example. Bound and size it for the maximum concurrent renewal load, JDBC connection-acquisition latency, and the database pool; configure bounded acquisition timeouts and monitor queue/rejection rates. One blocked JDBC renewal must not delay every active execution past lease expiry. Avoid an unbounded cached pool as well as a single renewal thread. For builder-managed H2 URLs, the store: - uses simple, non-pooling `DriverManager` connections; - defaults to `SchemaManagement.MIGRATE`; - requires H2 2.x or later; - adds `WRITE_DELAY=0` unless the URL explicitly chooses another value; and - owns one anchor connection, which it closes when the store closes. The certified file profile is single JVM. H2 `AUTO_SERVER`, H2 network-server mode, and sharing one database file between processes are outside this compatibility claim. A non-zero `WRITE_DELAY` trades the tested process-restart durability profile for throughput. An application-supplied H2 `DataSource` owns its own URL and must configure its durability settings itself. ## JDBC URL and driver discovery URL mode is also available for command-line tools and small services. Keep credentials in separate builder values rather than embedding them in a logged URL: ```java RdbmsFlowExecutionStore store = RdbmsFlowExecutionStore.builder() .jdbcUrl(jdbcUrl) .username(jdbcUsername) .password(jdbcPassword) // Usually omit this: JDBC 4 discovers a driver present on the classpath. // .driverClassName("org.postgresql.Driver") .dialect(PostgresDialect.INSTANCE) .schemaManagement(SchemaManagement.VALIDATE) .build(); ``` Exactly one of `jdbcUrl(...)` and `dataSource(...)` is required. URL credentials and `driverClassName(...)` cannot accompany a `DataSource`, and a password requires a username. H2 and PostgreSQL dialects are detected from a recognized URL or database metadata unless explicitly selected. Unknown products fail fast. URL mode uses non-pooling `DriverManager` connections; prefer an application-managed pooled `DataSource` for normal PostgreSQL services. ## PostgreSQL through an application `DataSource` Use an application-managed pool for normal PostgreSQL deployments. The pool must return ordinary local-transaction JDBC connections; do not supply connections enlisted in JTA or another ambient transaction. ```java DataSource applicationPool = createApplicationPool(); try (RdbmsFlowExecutionStore store = RdbmsFlowExecutionStore.builder() .dataSource(applicationPool) .dialect(PostgresDialect.INSTANCE) .schemaManagement(SchemaManagement.VALIDATE) .build()) { FlowEngine engine = FlowEngine.builder( utxoSupplier, protocolParamsSupplier, transactionProcessor, chainDataSupplier) .executor(flowTasks) .maintenanceExecutor(leaseMaintenance) .store(store) .leaseDuration(Duration.ofSeconds(30)) .ownerToken(processUniqueOwnerToken) .build(); FlowExecutionHandle handle = engine.start(request); FlowExecutionResult result = handle.await(); } // The application closes applicationPool separately during application shutdown. ``` The store borrows and closes a connection for each operation, but never closes the supplied `DataSource` or pool. Configure the PostgreSQL driver, pool sizing, acquisition timeouts, TLS, credentials, and credential rotation in the application. The automated certification profile is PostgreSQL 17.x, currently 17.6; qualify other major versions with the complete conformance and database integration suites before claiming support. For shared databases, run migrations with a schema-owner role, then run the application with a narrower runtime role and `VALIDATE`. Pin the connection's current schema/search path; TxFlow validates only that effective schema. ## Thread and lifecycle ownership `RdbmsFlowExecutionStore` is synchronous: JDBC work runs on the calling thread. The store creates no executor, scheduler, worker thread, or hidden connection pool. It closes only resources it created—principally the H2 anchor connection—and rejects new operations after `close()`. It never closes an application `DataSource`. `FlowEngine` requires a caller-owned execution `Executor`. Durable mode additionally requires a caller-owned maintenance `Executor` so lease renewal remains schedulable while flow work blocks. The engine creates, owns, and shuts down neither executor. Driver and pool implementations may have their own housekeeping threads; those remain application/library configuration concerns. ## Schema management `SchemaManagement` controls startup behavior: | Mode | Behavior | Typical use | |---|---|---| | `MIGRATE` | Checks migration history and checksums, applies pending forward migrations, then validates the schema | Embedded H2 or a controlled deployment migration phase | | `VALIDATE` | Requires the current compatible schema without modifying it | Application-managed PostgreSQL and normal runtime startup | | `NONE` | Skips schema/history/object migration and validation | An externally managed schema that was independently verified | URL configuration defaults to `MIGRATE`; an application-supplied `DataSource` defaults to `VALIDATE`. Choosing `MIGRATE` for a shared `DataSource` must be explicit. `NONE` still checks configured JDBC product/dialect compatibility (and PostgreSQL server encoding), but it is not a schema compatibility assertion: missing objects may fail only when exercised, while subtler drift may go undetected. The extension owns its migration resources, internal schema manager, and `txflow_schema_history`. These are not Flyway migrations and are not registered with an application's Flyway history. Do not add the packaged `db/txflow//V1__txflow_store.sql` resources to Flyway locations merely because their filenames use a familiar versioned shape; use the `SchemaManagement` modes above. An application may orchestrate when the extension runs `MIGRATE`, but the extension remains the owner of its schema history and checksums. With `MIGRATE` or `VALIDATE`, the store refuses a newer schema, a changed migration checksum, or pre-existing `txflow_*` objects without compatible migration history. Startup validation checks the required tables, columns, keys, indexes, timestamp precision/time-zone behavior, payload capacity, and identity comparison semantics. PostgreSQL additionally requires UTF-8 server encoding. ## What durable mode guarantees Durable mode coordinates four related concerns: 1. **Idempotency.** `createOrGet` atomically claims a namespace/key. Reusing it with the same definition and execution-request fingerprints returns the existing logical execution; a mismatch fails with `TXFLOW_IDEMPOTENCY_CONFLICT`. Execution IDs are independently checked and cannot be attached to different fingerprints. A replay reads the stored snapshot and doesn't resume work: a stored `COMPLETED` execution returns `COMPLETED` with empty `steps()`, other terminal states report `TXFLOW_STORED_EXECUTION_TERMINAL`, and a non-terminal execution returns `RECOVERY_REQUIRED`. Read transaction details with `engine.executionSnapshot(executionId)`. 2. **Atomic journals and snapshots.** A revision compare-and-set, ordered event append, lease validation, and snapshot replacement commit in one store transaction. 3. **Execution leases.** Only the current owner and fencing epoch may mutate an execution. Renewal keeps the epoch; takeover after expiry receives a higher epoch. 4. **Spending-resource leases.** Canonical spending identities are acquired in sorted order and included in every mutation fence, preventing stale workers from updating durable state. The engine renews leases on the independent maintenance executor. Provision it so that blocked flow tasks, connection-pool waits, or spending-resource contention cannot starve renewal. On Java 17 use application-owned platform-thread executors. A Java 21 application may supply virtual-thread executors through the same `Executor` API. ## Restart recovery The durable snapshot retains the exact signed payload (or an external payload reference), its SHA-256 digest, Cardano transaction hash, validity bounds, spent inputs, inclusion history, and attempt state. After recreating the store and engine, recover an uncertain attempt by execution identity. Supply a fresh authoritative Cardano **slot**—not a block height or wall-clock value—and fail closed when it is unavailable; the engine does not independently establish that the supplied slot is current: ```java FlowRecoveryResult recovery = engine.recover(FlowRecoveryRequest.builder() .executionId(executionId) .stepId("payment") .attemptNumber(1) .currentSlot(currentSlot) .resubmitSafetyMargin(20) .build()); if (recovery.state() == AttemptState.RECOVERY_REQUIRED) { // Stop automatic progress and route recovery.error() to an operator workflow. } ``` `stepId` and `attemptNumber` are optional at the API level; without them, the engine selects the highest attempt number among all matching steps. Operator and cross-process runbooks should require both selectors unless the target is demonstrably unambiguous. Recovery acquires the execution and recorded spending-resource leases, journals its progress, looks up the persisted transaction hash, and updates the attempt snapshot. It may resubmit only the verified **identical signed bytes**, and only while the recorded validity interval remains safely open when an upper validity slot was recorded. With no `validToSlot`, the stock coordinator can resubmit the identical payload without an expiry check. Controlled server deployments should use `FlowExecutionPolicy.builder().requireValidityInterval(true).build()`, obtain a fresh authoritative slot, and choose a deployment-specific safety margin. It never treats an empty backend lookup as permission to build a different transaction. The built-in `FlowEngine` persistence path stores signed CBOR inline. `SignedPayload.ExternalCbor` is supported by the store codec and recovery coordinator for a custom or pre-seeded persistence path; there is currently no `FlowEngine.Builder` switch that externalizes payloads automatically. When recovering such a custom record, provide `SignedPayloadResolver` through `FlowRecoveryRequest.payloadResolver(...)`; CCL verifies both the digest and Cardano transaction hash after loading the bytes. `recover(...)` reconciles and records one attempt. It does not restart remaining steps, rebuild a transaction, or resume the whole business flow. The application must interpret the result and invoke its higher-level continuation or operator workflow explicitly. An inconclusive result remains `RECOVERY_REQUIRED` with its history preserved. `recover(...)` never finishes the execution: after a conclusive result the stored state is `RUNNING`, and replaying the same request returns `RECOVERY_REQUIRED`. Record the business outcome in your application. Don't call `recover(...)` on an execution that's already terminal, because it moves it back to `RUNNING`. ## Fencing limitation Database fencing protects TxFlow **state writes**. It cannot stop a partitioned stale process that already holds signed bytes from submitting them to Cardano, because the chain does not consult the database lease. Deployments that require race-free active-active spending must additionally serialize submission externally or implement a UTxO reservation/coordinator protocol. ## Next steps and source references - [Operations and Testing](/txflow/operations-testing/) covers uncertainty handling, backup/restore, monitoring, and the verification matrix. - [Portable Authoring](/txflow/portable-authoring/) covers flow definitions and bindings. - [Execution Engine](/txflow/execution-engine/) covers parsing, preflight, request construction, and `FlowEngine` wiring. - [Policies, Results, and Observability](/txflow/policies-results-observability/) covers execution outcomes, cancellation, and ordinary retry behavior. - [RDBMS store README](https://github.com/bloxbean/cardano-client-lib/tree/master/txflow-extensions/txflow-store-rdbms) is the module-level compatibility reference. - [ADR 0003](https://github.com/bloxbean/cardano-client-lib/blob/master/txflow/adr/0003-relational-durable-store-extension.md) records the relational-store decisions and limits. --- # Executing Portable Flows Source: https://cardano-client.dev/txflow/execution-engine/ Summary: Parse, preflight, and run portable TxFlow definitions with FlowEngine > **Preview API:** Portable TxFlow documents and `FlowEngine` may change before a stable release. `FlowEngine` is the current entry point for portable TxFlow. It separates a reusable definition from the values and execution identity of each run: ```text YAML or JSON -> TxFlowCodec -> TxFlow definition + FlowExecutionRequest | compile and preflight | FlowEngine.start | FlowExecutionHandle / result ``` `FlowEngine` is thread-safe after construction. Its configuration is immutable, and every accepted request runs on an application-owned `Executor`. ## `FlowEngine` and the legacy facade The two execution APIs serve different contracts and should not be mixed in one example: | Portable runtime | Legacy facade | |---|---| | `FlowEngine` | `FlowExecutor` | | `FlowExecutionRequest` | A `TxFlow` passed directly to `execute` or `executeSync` | | `FlowExecutionHandle` | `FlowHandle` | | `FlowExecutionResult` and `FlowExecutionState` | `FlowResult` and `FlowStatus` | | Ordered `FlowEvent` values | `FlowListener` callbacks and optional `FlowRegistry` | | `FlowExecutionStore` for fenced durable execution | `FlowStateStore` for legacy snapshots | `FlowEngine` compiles portable transaction templates before execution. Passing an uncompiled portable template directly to `FlowExecutor` is rejected. Continue to use `FlowExecutor` for an existing Java-first or legacy integration, but use `FlowEngine` for new portable/server-side execution and durable recovery. ## 1. Parse a definition Use bounded server defaults when a document can come from outside the process. The parser rejects unknown TxFlow envelope, execution, parameter, step, and output fields by default; limits document size, YAML aliases, nesting, and step count; and returns expected authoring failures as diagnostics. The embedded QuickTx transaction projection has its own evolving subtype contract, so compiler and transaction-focused validation remain required after envelope parsing. ```java String source = """ api_version: txflow.cardano-client.dev/v1alpha1 kind: TxFlow metadata: name: customer-payment version: "1" spec: network: preview parameters: beneficiary: {type: address, required: true} amount: {type: integer, required: true, minimum: 1000000} steps: - id: pay transaction: tx: from_ref: account://treasury intents: - type: payment address: '${{ inputs.beneficiary }}' amounts: - unit: lovelace quantity: '${{ inputs.amount }}' context: signers: - {ref: account://treasury, scope: payment} """; TxFlowCodec codec = TxFlowCodec.standard(); FlowParseResult parsed = codec.parse(source, FlowParseOptions.serverDefaults()); if (parsed.hasErrors()) { throw new IllegalArgumentException("Invalid TxFlow: " + parsed.getDiagnostics()); } TxFlow definition = parsed.requireFlow(); ``` Keep the `TxFlow` definition reusable. Supply run-specific data through `FlowBindings`; do not rewrite the YAML or interpolate raw source text. ```java FlowBindings bindings = FlowBindings.builder() .put("beneficiary", "addr_test1...") .put("amount", 5_000_000L) .build(); ``` Bindings accept only portable scalars: `String`, `Boolean`, and integral Java types through `Long`. Compilation checks required values, declared types, numeric bounds, and string lengths. Exact `${{ inputs.name }}` expressions retain the binding's native type; only string inputs may be embedded within a larger string. ## 2. Separate resource metadata from runtime material A portable document names accounts, policies, and scripts with logical URIs. It must not contain private keys or other secret material. TxFlow uses three distinct application-owned facilities: - `FlowResourceCatalog` exposes non-secret metadata for compile-time existence, network, capability, and spending-contention checks. - `SignerRegistry` resolves signer references while QuickTx builds and signs a transaction. - `ScriptRegistry` resolves scripts by logical reference or hash at execution time. For example, the catalog can describe the same account that an application registers with QuickTx's signer registry: ```java InMemoryFlowResourceCatalog resources = new InMemoryFlowResourceCatalog() .register(new ResourceDescriptor( ResourceRef.of("account://treasury"), "preview", Set.of(ResourceCapability.SPEND, ResourceCapability.SIGN), "preview:treasury-wallet")); DefaultSignerRegistry signers = new DefaultSignerRegistry() .addAccount("account://treasury", treasuryAccount); DefaultScriptRegistry scripts = new DefaultScriptRegistry(); // Register only the script references used by this deployment: // scripts.addScript("script://escrow/spend-v1", escrowScript); ``` The descriptor's `spendingIdentity` is a canonical lock identity, not a display alias. Two different resource references that can spend the same UTxOs should use the same identity. Capabilities are `SPEND`, `SIGN`, `MINT`, and `SCRIPT`. The catalog does not sign transactions, and a registry does not authorize a document. Configure both the catalog and a server-owned `FlowExecutionPolicy` when accepting portable definitions from other users. ```java FlowExecutionPolicy serverPolicy = FlowExecutionPolicy.builder() .allowedNetworks(Set.of("preview")) .allowedResourcePrefixes(Set.of("account://", "script://", "policy://")) .maxSteps(25) .maxRetryAttempts(5) .maxConfirmationTimeout(Duration.ofMinutes(30)) .build(); ``` An empty network or resource-prefix allow-list in `FlowExecutionPolicy` means unrestricted, not deny-all. Set explicit allow-lists at a multi-tenant or untrusted-input boundary. ## 3. Build the engine `FlowEngine.builder(BackendService)` builds the four adapters for you, and also enables backend script lookup and TxStream's backend-visibility check. The four-supplier form below has neither by default, so a flow using reference scripts then needs `scriptSupplier(...)`. The four required backend interfaces are deliberately independent of a particular backend module: - `UtxoSupplier` supplies address and transaction UTxOs. - `ProtocolParamsSupplier` supplies current protocol parameters. - `TransactionProcessor` submits signed transaction CBOR. - `ChainDataSupplier` supplies chain-tip and transaction observations. On Java 17, use an application-managed platform-thread executor sized for the expected blocking I/O. This non-durable example needs only the task executor: ```java ExecutorService flowTasks = Executors.newFixedThreadPool(16); FlowEngine engine = FlowEngine.builder( utxoSupplier, protocolParamsSupplier, transactionProcessor, chainDataSupplier) .executor(flowTasks) .resources(resources) .policy(serverPolicy) .signerRegistry(signers) .scriptRegistry(scripts) .build(); ``` The application that creates `flowTasks` must shut it down during application shutdown. The engine never creates, closes, or shuts down an executor. Because `executeSync` dispatches to this executor and then waits, do not invoke it from a saturated worker pool that is also the engine's task executor. ### Durable engine configuration Adding a `FlowExecutionStore` enables durable snapshots, attempts, ordered events, idempotency claims, and fenced execution/resource leases. Durable mode also requires a separately supplied maintenance executor so lease renewal remains schedulable while flow tasks wait on Cardano or on a spending resource. ```java ExecutorService flowTasks = Executors.newFixedThreadPool(16); ExecutorService leaseMaintenance = Executors.newFixedThreadPool(2); FlowEngine engine = FlowEngine.builder( utxoSupplier, protocolParamsSupplier, transactionProcessor, chainDataSupplier) .executor(flowTasks) .maintenanceExecutor(leaseMaintenance) .resources(resources) .policy(serverPolicy) .signerRegistry(signers) .scriptRegistry(scripts) .store(flowExecutionStore) .leaseDuration(Duration.ofSeconds(30)) .ownerToken(processUniqueOwnerToken) .build(); ``` Use an owner token that is unique to the running worker/process. Do not use `Runnable::run` or the same bounded executor for both roles in a durable production deployment: a blocked flow task must not starve lease renewal. `InMemoryFlowExecutionStore` is a semantic reference and test implementation; it is not durable across a restart. Use a durable store implementation when restart recovery is required. ## 4. Create an execution request `FlowExecutionRequest` is immutable. Its fields have the following roles: | Builder method | Meaning | |---|---| | `executionId(value)` | Stable correlation and durable lookup ID; a UUID is generated if omitted | | `bindings(value)` | Run-specific typed parameter values | | `idempotency(namespace, key)` | Logical operation identity; namespace and key must be supplied together | | `spendingResource(identity)` | Additional canonical resource to serialize beyond catalog-derived identities | | `allowConcurrentSpending(true)` | Explicitly bypass spending serialization, only if server policy permits it | | `secureBindingReference(name, ref)` | Opaque external reference for a sensitive parameter in durable mode | ```java FlowExecutionRequest request = FlowExecutionRequest.builder(definition) .executionId("payment-018f5ec0") .bindings(bindings) .idempotency("merchant-42", "checkout-17381") .spendingResource("preview:treasury-wallet") .build(); ``` Keep every fingerprint-affecting field stable when retrying the same logical request. Reusing an idempotency identity with the same compiled definition, bindings, secure references, spending resources, and concurrency choice returns the existing logical execution. Reusing it with a different request fails with `TXFLOW_IDEMPOTENCY_CONFLICT`. While an execution ID is active, an identical request returns the same handle. The same execution ID with a different request fingerprint or idempotency identity fails with `TXFLOW_EXECUTION_ID_CONFLICT`. Without a store, execution-ID collision protection lasts only while that run is active. Use an explicit idempotency pair when callers may replay an operation after it has completed. Durable mode also creates a separate internal execution-ID claim, so a stored execution is not silently recreated. Without a durable store, idempotency claims exist only in this engine instance and are bounded by `maxInMemoryIdempotencyClaims` (default 10,000). They do not survive a process restart and are not evicted; once the bound is reached, new claim identities fail until a new engine is created or a durable store is used. Store-facing identities reject blank text, NUL, malformed Unicode, and oversized UTF-8 values. The request limits are 512 bytes for an execution ID, 255 for an idempotency namespace, 512 for an idempotency key, and 1,024 for each spending-resource identity. These are byte limits, not Java `String.length()` limits. ### Sensitive bindings Mark a parameter `sensitive: true` in the portable definition. Initial compilation still needs a resolved value, normally supplied in `FlowBindings` (or by a declared default), but durable mode additionally requires an opaque external reference: ```java FlowExecutionRequest request = FlowExecutionRequest.builder(definition) .executionId("credentialed-operation-7") .bindings(FlowBindings.builder() .put("api_token", secretValue) .build()) .secureBindingReference("api_token", "vault://txflow/api-token/7") .build(); ``` The durable snapshot does not store the raw binding, but it does store the reference, a redacted display, and an unsalted SHA-256 fingerprint of the binding's string form. Treat the database and backups as sensitive; low-entropy values may be guessable offline. Secret storage, access control, rotation, and log redaction remain application concerns. `SecureBindingResolver` is an application adapter type, but the current `FlowEngine.Builder` does not accept one; do not assume the engine will fetch an initial binding automatically. ## 5. Preflight, then start Preflight performs deterministic binding, graph, output-reference, resource, and server-policy checks without building, signing, or submitting a transaction. ```java FlowCompilationResult preflight = engine.preflight(request); if (preflight.hasErrors()) { for (FlowDiagnostic diagnostic : preflight.getDiagnostics()) { auditDiagnostic(diagnostic.code(), diagnostic.documentPath(), diagnostic.message()); } throw new IllegalArgumentException("TxFlow preflight failed"); } FlowExecutionHandle handle = engine.start(request); ``` `start` validates and compiles again, then dispatches accepted work to the configured task executor. Expected compilation, policy, idempotency, collision, and executor-rejection failures are represented by an already-completed handle; always inspect the terminal result. For a caller that intentionally blocks, `executeSync(request)` is shorthand for `start(request).await()`. The flow still runs on the configured task executor. ```java FlowExecutionResult result = engine.executeSync(request); if (!result.isSuccessful()) { FlowError error = result.error(); handleExecutionFailure(result.state(), error != null ? error.code() : "unknown"); } ``` ## Chaining modes `spec.execution.mode` is `SEQUENTIAL` when absent. Server policy can restrict it with `allowedModes`. | Mode | Behaviour | |---|---| | `SEQUENTIAL` | Each step is built, submitted and confirmed before the next starts | | `PIPELINED` | Each step is built and submitted without waiting; confirmations are awaited after the last submission | | `BATCH` | All steps are built and signed, then submitted in quick succession, then confirmed | A later step sees an earlier step's unconfirmed outputs only through a declared edge: - `flow_output` spends one exact output. - `funding_from: [step]` lets address-based selection use that step's outputs at the address being funded. - `needs` alone exposes nothing. Plutus steps that spend or reference pending outputs need a local script evaluator, because the backend evaluator knows only confirmed UTxOs. Add [Aiken Java Binding](/smart-contracts/aiken/), then: ```java ScriptSupplier scripts = new DefaultScriptSupplier(backendService.getScriptService()); FlowEngine engine = FlowEngine.builder(backendService) .executor(flowTasks) .txEvaluator((utxos, params) -> new AikenTransactionEvaluator(utxos, params, scripts)) .build(); ``` `FlowRuntime.Builder.txEvaluator(...)` is equivalent. ## Java 21 virtual threads TxFlow itself is built for Java 17 and refers only to `Executor`. An application running on Java 21 or newer can therefore use virtual threads without a different TxFlow artifact: ```java try (ExecutorService flowTasks = Executors.newVirtualThreadPerTaskExecutor(); ExecutorService leaseMaintenance = Executors.newVirtualThreadPerTaskExecutor()) { FlowEngine engine = FlowEngine.builder( utxoSupplier, protocolParamsSupplier, transactionProcessor, chainDataSupplier) .executor(flowTasks) .maintenanceExecutor(leaseMaintenance) .store(flowExecutionStore) .resources(resources) .policy(serverPolicy) .signerRegistry(signers) .build(); FlowExecutionResult result = engine.executeSync(request); } ``` The executors remain logically independent even when both use virtual threads. The application owns their lifecycle and must keep them alive until every submitted execution and lease-renewal task has stopped. ## Builder reference Beyond the required backend services, `FlowEngine.Builder` supports: | Method | Use | |---|---| | `executor(Executor)` | Required caller-owned flow-task executor | | `maintenanceExecutor(Executor)` | Required when a store is configured; durable lease renewal | | `clock(Clock)` | Application/test wall clock for execution and persisted timestamps | | `compiler(TxFlowCompiler)` | Replace the standard compiler | | `resources(FlowResourceCatalog)` | Compile-time resource metadata | | `policy(FlowExecutionPolicy)` | Server-owned limits and authorization | | `signerRegistry(SignerRegistry)` | Runtime signer-reference resolution | | `account(ref, Account)` / `wallet(ref, Wallet)` | Convenience signer registration; can't be combined with `signerRegistry(...)` | | `scriptSupplier(ScriptSupplier)` | Reference-script lookup during builds | | `txEvaluator(BiFunction)` | Per-step script-cost evaluator; see [Chaining modes](#chaining-modes) | | `extension(QuickTxExtension)` | Registers a QuickTx extension (experimental) | | `backendVisibilityChecks(boolean)` | TxStream's between-execution indexing check | | `scriptRegistry(ScriptRegistry)` | Runtime script-reference resolution | | `store(FlowExecutionStore)` | Durable snapshots, journal, idempotency, and leases | | `leaseDuration(Duration)` | Positive lease lifetime; default 30 seconds | | `ownerToken(String)` | Non-blank durable worker identity; otherwise generated | | `maxInMemoryIdempotencyClaims(int)` | Positive non-durable claim bound; default 10,000 | See [Policies, Results, and Observability](/txflow/policies-results-observability/) for retry, confirmation, rollback, events, cancellation, and recovery behavior. --- # TxFlow Getting Started Source: https://cardano-client.dev/txflow/getting-started/ Summary: Parse, preflight, and execute a portable TxFlow definition with Java 17 TxFlow turns an ordered set of QuickTx transactions into one observable execution. A portable definition is reusable: runtime values, execution identity, backend services, signers, policy, and durability remain application-owned. This guide uses the portable `v1alpha1` contract and `FlowEngine`. The historical `version: "1.0"` / `flow:` document and `FlowExecutor` APIs are compatibility surfaces, not the recommended starting point for new applications. > **Baseline:** TxFlow is built and tested on Java 17. `FlowEngine` accepts caller-owned > `Executor` instances rather than creating threads. A Java 21 application can therefore supply a > virtual-thread executor without changing TxFlow code. ## Add the dependencies The published module name includes the repository-wide `cardano-client-` prefix. For the current development line: ```gradle def cclVersion = '' repositories { mavenCentral() maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") } } dependencies { implementation "com.bloxbean.cardano:cardano-client-txflow:${cclVersion}" // Choose a backend implementation. BFBackendService can target a compatible Yaci endpoint. implementation "com.bloxbean.cardano:cardano-client-backend-blockfrost:${cclVersion}" } ``` The equivalent Maven coordinates are: ```xml com.bloxbean.cardano cardano-client-txflow {version} com.bloxbean.cardano cardano-client-backend-blockfrost {version} ``` `cardano-client-txflow` already exposes its required CCL modules, including QuickTx and the backend interfaces; the second dependency selects one concrete backend provider. ## Author a portable flow Save this as `payment-flow.yaml`: ```yaml api_version: txflow.cardano-client.dev/v1alpha1 kind: TxFlow metadata: name: payment version: 1.0.0 spec: network: preview parameters: beneficiary: type: address required: true amount: type: integer minimum: 1000000 maximum: 100000000 execution: confirmation: min_confirmations: 1 check_interval: 2s timeout: 2m steps: - id: pay transaction: tx: from_ref: account://sender intents: - type: payment address: '${{ inputs.beneficiary }}' amounts: - unit: lovelace quantity: '${{ inputs.amount }}' context: signers: - ref: account://sender scope: payment ``` Each step contains exactly one embedded QuickTx transaction projection (`tx` plus optional `context`). TxFlow owns the flow envelope, ordering, named outputs, binding, and execution lifecycle. During compilation, TxFlow wraps the projection in QuickTx's `TransactionDocument`, then QuickTx materializes the executable one-transaction `TxPlan`. See [Portable authoring](/txflow/portable-authoring/) for the complete boundary and multi-step output references. ## Parse and inspect diagnostics Use `TxFlowCodec` for application input. It preserves warnings and structured errors that the `TxFlow.fromYaml(...)` convenience method cannot expose before throwing. ```java String source = Files.readString(Path.of("payment-flow.yaml"), StandardCharsets.UTF_8); FlowParseOptions parseOptions = FlowParseOptions.serverDefaults(); FlowParseResult parsed = TxFlowCodec.standard().parse(source, parseOptions); List parseDiagnostics = parsed.getDiagnostics(); // Send parseDiagnostics to your logger or API response. Do not discard warnings. if (parsed.hasErrors()) { throw new IllegalArgumentException("Invalid TxFlow document: " + parseDiagnostics); } TxFlow definition = parsed.requireFlow(); ``` `parse(...)` performs bounded YAML/JSON decoding plus portable envelope and graph checks. For a validation-only endpoint, `TxFlowValidator.standard().validate(source)` exposes the same parser diagnostics without returning a definition. Parameter binding, resource lookup, and server policy belong to the later compilation/preflight phase. ## Supply runtime bindings Portable parameters are not replaced in the YAML text. The compiler binds values in the parsed transaction tree, preserving native integer and boolean values. ```java FlowBindings bindings = FlowBindings.builder() .put("beneficiary", receiverAddress) .put("amount", 5_000_000L) .build(); ``` Unknown, missing, incorrectly typed, and out-of-range bindings become compilation diagnostics. The reusable `TxFlow` definition is not mutated. ## Connect the backend and runtime resources `FlowEngine` needs four small backend interfaces: - `UtxoSupplier` for transaction input lookup; - `ProtocolParamsSupplier` for current protocol parameters; - `TransactionProcessor` for submission; and - `ChainDataSupplier` for inclusion, confirmation, and rollback observation. The standard backend adapters construct them from a `BackendService`: ```java BackendService backendService = new BFBackendService(backendUrl, projectId); UtxoSupplier utxoSupplier = new DefaultUtxoSupplier(backendService.getUtxoService()); ProtocolParamsSupplier protocolParamsSupplier = new DefaultProtocolParamsSupplier(backendService.getEpochService()); TransactionProcessor transactionProcessor = new DefaultTransactionProcessor(backendService.getTransactionService()); ChainDataSupplier chainDataSupplier = new DefaultChainDataSupplier(backendService); ``` `FlowEngine.builder(backendService)` builds the same four adapters for you, and also enables backend script lookup. `.account(ref, account)` can replace an explicit signer registry, but the two signer styles can't be combined. The YAML names `account://sender`; it does not contain a key. Register private signing material in QuickTx's signer registry and public preflight metadata in the TxFlow resource catalog. In this snippet, `senderAccount` is an `Account` loaded through your application's secret-management path: ```java DefaultSignerRegistry signerRegistry = new DefaultSignerRegistry() .addAccount("account://sender", senderAccount); InMemoryFlowResourceCatalog resources = new InMemoryFlowResourceCatalog() .register(new ResourceDescriptor( ResourceRef.of("account://sender"), "preview", Set.of(ResourceCapability.SPEND, ResourceCapability.SIGN), "address:" + senderAccount.baseAddress())); ``` `spendingIdentity` is the stable contention key. Aliases that spend the same account should use the same identity so concurrent executions are serialized by default. The in-memory catalog stores only public metadata; signer keys remain in the signer registry. ## Build the engine, preflight, and start The application owns the executor and decides its lifecycle. The Java 17 example uses a bounded platform-thread pool: ```java ExecutorService flowTasks = Executors.newFixedThreadPool(4); try { FlowExecutionPolicy policy = FlowExecutionPolicy.builder() .allowedNetworks(Set.of("preview")) .allowedResourcePrefixes(Set.of("account://")) .build(); FlowEngine engine = FlowEngine.builder( utxoSupplier, protocolParamsSupplier, transactionProcessor, chainDataSupplier) .executor(flowTasks) .resources(resources) .policy(policy) .signerRegistry(signerRegistry) .build(); FlowExecutionRequest request = FlowExecutionRequest.builder(definition) .executionId("payment-2026-07-16-0001") .bindings(bindings) .idempotency("payments", "order-18473") .build(); FlowCompilationResult preflight = engine.preflight(request); List preflightDiagnostics = preflight.getDiagnostics(); // Send preflightDiagnostics to the same application diagnostic sink. if (preflight.hasErrors()) { throw new IllegalArgumentException("TxFlow preflight failed: " + preflightDiagnostics); } FlowExecutionHandle handle = engine.start(request); FlowExecutionResult result = handle.await(); if (!result.isSuccessful()) { throw new IllegalStateException("TxFlow ended as " + result.state() + "; error=" + result.error()); } } finally { flowTasks.shutdown(); } ``` Preflight compiles a bound, policy-checked, one-transaction `TxPlan` for each step and produces a deterministic definition fingerprint. `start(...)` intentionally compiles again: it never assumes a previous preflight result still represents the submitted request. Without a `FlowExecutionStore`, idempotency and active-execution tracking are process-local. For restart recovery or multiple application instances, configure the RDBMS store and a separately provisioned maintenance executor. See [Durable runtime](/txflow/durable-runtime/). `RECOVERY_REQUIRED` isn't a failure. A confirmation timeout ends this way because the transaction may still land. Don't resubmit under a new execution ID or idempotency key; check the transaction hash first. On Java 21, the same builder accepts a virtual-thread executor created and closed by the application. TxFlow depends only on `Executor` and never detects, creates, or shuts down a thread implementation. ## The execution pipeline The safe application path is: 1. `TxFlowCodec.parse` decodes YAML or JSON and reports structural and graph diagnostics. 2. `FlowBindings` supplies run-specific portable scalar values. 3. `TxFlowCompiler`—directly or through `FlowEngine.preflight`—binds values, applies server policy, resolves resource metadata, validates explicit output references, and materializes QuickTx plans. 4. `FlowEngine.start` claims execution/idempotency identities, coordinates spending resources, and dispatches work on the caller's executor. 5. `FlowExecutionHandle` exposes ordered events, cooperative cancellation, and the terminal `FlowExecutionResult`. ## Continue reading - [Portable authoring](/txflow/portable-authoring/) — document fields, typed bindings, TxPlan ownership, named outputs, resource references, and safe parsing. - [Execution engine](/txflow/execution-engine/) — engine construction, executors, requests, idempotency, cancellation, and recovery. - [Policies, results, and observability](/txflow/policies-results-observability/) — effective settings, error states, events, retries, confirmations, and rollback behavior. - [Durable runtime](/txflow/durable-runtime/) — H2/PostgreSQL configuration, leases, persistence, and restart recovery. - [Operations and testing](/txflow/operations-testing/) — Yaci integration testing, deployment checks, diagnostics, and operational limits. --- # Operations and Testing Source: https://cardano-client.dev/txflow/operations-testing/ Summary: Operate, monitor, back up, restore, and verify the durable TxFlow runtime > **Preview API**: qualify the durable runtime in your own deployment environment before production use. This guide is the day-two companion to [Durable Runtime](/txflow/durable-runtime/). It covers database uncertainty, restart and restore procedures, security, monitoring, and the Java 17 test matrix. ## Operational invariants Keep these rules in the deployment runbook: - Treat the execution snapshot, idempotency claim, event journal, signed attempt payload, lease rows, global lease epoch, and migration history as one recovery boundary. - Never infer Cardano state from database state alone. A database restore cannot undo a transaction that was already submitted to the network. - Do not manually update or delete TxFlow rows. Use the store API and terminal-only event compaction. - Do not automatically retry a database operation whose commit or rollback outcome is uncertain. - Keep the flow executor and the lease-maintenance executor independently schedulable. - Database fencing protects state writes, not submission by a partitioned process that already holds signed bytes. ## Commit and rollback uncertainty The RDBMS store closes the affected JDBC connection and reports a stable code when the driver cannot establish the transaction outcome: | Code | Meaning | Immediate action | |---|---|---| | `TXFLOW_STORE_COMMIT_UNCERTAIN` | The database may have committed before the driver reported failure | Stop automatic retry and reconcile authoritative state | | `TXFLOW_STORE_ROLLBACK_UNCERTAIN` | The driver could not confirm rollback after an operation failed | Stop automatic retry and reconcile authoritative state | Both codes mean **unknown**, not failed. Preserve the execution ID, application idempotency pair, operation name, timestamp, SQL state, vendor code, and relevant backend observations in the incident record. ### Execution-store operation runbook After database connectivity is stable: 1. Load the execution by its stable execution ID and inspect its revision, lifecycle state, attempts, and journal tail. 2. Preserve the original idempotency namespace/key and all request fields. If a later retry is authorized, the same canonical request must resolve to the existing claim; a fingerprint conflict must not be overridden or repaired by editing tables. 3. For every signed or submission-uncertain attempt, reconcile the persisted transaction hash against Cardano chain state. Do not create a new body merely because one backend lookup is empty or temporarily unavailable. 4. Obtain a fresh authoritative Cardano slot—not block height or wall-clock time—and fail closed if it is unavailable. `FlowEngine` trusts the request's `currentSlot`; it does not establish freshness itself. 5. Use `FlowEngine.recover(FlowRecoveryRequest)` with explicit step and attempt selectors when reconciliation is required. It can resubmit only verified identical signed bytes. The safety margin is enforced only when `validToSlot` was persisted; require validity intervals by policy for controlled recovery. 6. Resume business processing only after the database and chain observations establish the allowed next state. Route an inconclusive result to an operator as `RECOVERY_REQUIRED`. Do not blindly repeat external transaction submission. The original submission may have reached the node even when the local database transition or response did not complete cleanly. ### Schema-management runbook An uncertainty code can also be raised while `RdbmsFlowExecutionStore.builder().build()` is applying or validating the schema. There is no execution ID to reconcile in that case. 1. Stop additional application instances from attempting migration. 2. Inspect `txflow_schema_history`, the actual `txflow_*` objects, and database server logs using the schema-owner role. 3. After connectivity is stable, run startup with `SchemaManagement.VALIDATE` to establish whether the current schema is complete and compatible. 4. Do not rerun `MIGRATE` until the previous migration outcome is understood. Do not edit the checksum or history row to force acceptance. 5. Restore or repair only through a version-specific, reviewed migration procedure. The extension uses its own schema manager and `txflow_schema_history`; it does not register these migrations with application Flyway. Do not add the packaged dialect resources to Flyway's locations or create parallel Flyway history for them. The `V1__txflow_store.sql` filename is an internal versioned resource name, not an instruction to execute it through Flyway. ## Restart and recovery procedure For an ordinary process restart: 1. Reopen the same H2 file or connect to the same PostgreSQL schema. 2. Build the store with `VALIDATE` in shared deployments. File-backed builder-managed H2 may use its default `MIGRATE` mode. 3. Recreate `FlowEngine` with a new process-unique owner token and independently available flow and maintenance executors. 4. Find non-terminal or recovery-required executions through your application index or known execution IDs. `FlowExecutionStore` intentionally has no unbounded "scan everything" method. 5. Obtain a fresh authoritative Cardano slot and reconcile the selected persisted attempt with an explicit recovery request. Require `stepId` and `attemptNumber` unless the target is provably unambiguous: ```java FlowRecoveryResult result = engine.recover(FlowRecoveryRequest.builder() .executionId(executionId) .stepId(stepId) .attemptNumber(attemptNumber) .currentSlot(currentSlot) .resubmitSafetyMargin(safetyMarginSlots) .payloadResolver(externalPayloadResolver) // omit for inline CBOR .build()); ``` An observed transaction becomes `SUBMITTED` or `IN_BLOCK`. An identical resubmission becomes `SUBMITTED`. Missing payloads, digest/hash mismatches, expired recorded validity, failed observation, or failed identical submission remain `RECOVERY_REQUIRED`; they never authorize an implicit rebuild. The coordinator checks expiry only when `validToSlot` is present, so controlled deployments should require validity intervals and fail closed if a fresh slot is unavailable. This call reconciles one attempt and records the result; it does not automatically resume the remaining flow steps. ## Backup, restore, and retention ### Backup Back up all of these objects as one transactionally consistent unit: - `txflow_schema_history` - `txflow_execution` - `txflow_idempotency` - `txflow_event` - `txflow_execution_lease` - `txflow_resource_lease` - `txflow_lease_epoch` For PostgreSQL, use the platform's consistent snapshot/backup tooling and test restore into an isolated database. For file-backed H2, close the store/database before copying files or use H2's supported online backup facility. Copying live H2 files is not a supported backup method. ### Restore After restoring an older backup: 1. Quiesce or revoke every pre-restore application instance, database connection, and writer. Do not allow an old process to mutate the restored database. 2. Restore into an isolated environment and start replacement workers with fresh owner tokens and transaction submission disabled. 3. Run schema `VALIDATE`. 4. Reconcile every non-terminal or previously submitted execution with Cardano chain state. 5. Reconnect traffic and re-enable execution only when idempotency claims, snapshots, journals, and chain observations agree on a safe continuation. Never restore only a snapshot table or only the journal. Partial restore can invalidate both idempotency and fencing assumptions. Never reset `txflow_lease_epoch`; monotonically increasing epochs are what make stale owners rejectable. ### Retention Use `FlowExecutionStore.compactEvents(executionId, throughSequence)` only after the execution is terminal. Compaction advances an inclusive watermark and never resets event sequence allocation. A consumer that presents a cursor older than the watermark receives `EVENTS_COMPACTED` and must reload the snapshot as its new baseline. Do not manually delete claims, executions, events, leases, or the global epoch row. Define a separate, reviewed policy before deleting whole terminal executions; the current store contract exposes event compaction, not arbitrary execution deletion. ## Security and database ownership ### Credentials and transport - Prefer a configured `DataSource` for PostgreSQL. Keep credentials out of logged or persisted JDBC URLs and rotate them through the pool/driver configuration. - Configure PostgreSQL TLS in the driver or pool. The extension does not create a TLS policy. - Protect H2 files with operating-system permissions and configure H2 encryption when encryption at rest is required. - Treat inline signed CBOR and execution bindings as application data covered by backup, retention, and access-control policies. - Durable sensitive parameters require `secureBindingReference(...)`. The raw value is not stored, but the snapshot retains the external reference and an unsalted SHA-256 fingerprint of its string form. Treat the database and backups as sensitive; low-entropy values may be guessable offline. The application remains responsible for the referenced secret manager. JDBC causes exposed by the adapter retain SQL state and vendor code, but replace driver messages because those messages can contain credentials or complete URLs. Use protected database logs for detailed investigation. ### Least privilege Use a schema-owner role for `MIGRATE`. It needs `USAGE` and `CREATE` on the selected schema and must own the TxFlow objects so later migrations can alter them. Use a narrower role for normal `VALIDATE` operation. For PostgreSQL, its grants have this shape: ```sql GRANT USAGE ON SCHEMA application_schema TO txflow_runtime; GRANT SELECT, INSERT, UPDATE, DELETE ON application_schema.txflow_execution TO txflow_runtime; GRANT SELECT, INSERT, DELETE ON application_schema.txflow_idempotency TO txflow_runtime; GRANT SELECT, INSERT, DELETE ON application_schema.txflow_event TO txflow_runtime; GRANT SELECT, INSERT, UPDATE, DELETE ON application_schema.txflow_execution_lease, application_schema.txflow_resource_lease TO txflow_runtime; GRANT SELECT, UPDATE ON application_schema.txflow_lease_epoch TO txflow_runtime; GRANT SELECT ON application_schema.txflow_schema_history TO txflow_runtime; ``` Do not grant destructive DDL to the runtime role. Pin its current schema/search path and reassess the grants whenever a later migration introduces an object or operation. ## Diagnostics and monitoring Handle `FlowStoreException.getCode()` programmatically; do not parse messages. The complete machine-readable catalog is published with `txflow` as [`txflow-diagnostic-codes.json`](https://github.com/bloxbean/cardano-client-lib/blob/master/txflow/src/main/resources/txflow-diagnostic-codes.json). Useful operational groups include: | Group | Codes to alert or classify | |---|---| | Unknown transaction result | `TXFLOW_STORE_COMMIT_UNCERTAIN`, `TXFLOW_STORE_ROLLBACK_UNCERTAIN` | | Availability/concurrency | `TXFLOW_STORE_UNAVAILABLE`, `TXFLOW_STORE_SERIALIZATION_FAILURE`, `TXFLOW_RDBMS_UNIQUE_CONFLICT` | | Durable integrity | `TXFLOW_STORE_CORRUPT`, `TXFLOW_STORE_CODEC_*`, `TXFLOW_EVENT_SEQUENCE`, `EVENTS_COMPACTED` | | Schema | `TXFLOW_SCHEMA_*`, `TXFLOW_DIALECT_*`, `TXFLOW_STORE_CONFIGURATION_FAILED` | | Ownership/fencing | `TXFLOW_LEASE_*`, `TXFLOW_RESOURCE_LEASE_*`, `TXFLOW_STALE_FENCE`, `TXFLOW_STALE_RESOURCE_FENCE` | | Runtime fail-stop | `TXFLOW_RECOVERY_REQUIRED`, `TXFLOW_TERMINAL_PERSISTENCE_FAILED`, `TXFLOW_EXECUTOR_REJECTION_PERSISTENCE_FAILED` | The store does not create a metrics registry or monitoring thread. Instrument the application and database for: - pool acquisition time, active/queued connections, transaction latency, and database availability; - serialization failures, deadlocks, and unique-contention rates; - both uncertainty codes, terminal-persistence failures, and executions entering `RECOVERY_REQUIRED`; - lease conflicts, expiry, renewal failures, and stale-fence rejections; - journal growth, compaction watermark/cursor gaps, backup age, and restore-test age; and - executor queue/rejection rates, especially on the independent maintenance executor. For H2, also monitor disk capacity and file/backup health. For PostgreSQL, use server logs and platform metrics to correlate SQL state and vendor code without exposing driver messages. ## Java 17 verification matrix The repository build and certification baseline is Java 17. Confirm the selected JVM before running tests: ```shell java -version ./gradlew --version ``` Run the TxFlow unit suite and the H2/RDBMS unit layer: ```shell ./gradlew :txflow:test ./gradlew :txflow-extensions:txflow-store-rdbms:test ``` Run H2 close/reopen, forced child-JVM termination, concurrency, and the PostgreSQL Testcontainers layer with: ```shell ./gradlew :txflow-extensions:txflow-store-rdbms:integrationTest ``` Focused commands are useful while diagnosing a failure: ```shell ./gradlew :txflow-extensions:txflow-store-rdbms:integrationTest \ --tests '*H2AbruptRestartIntegrationTest' ./gradlew :txflow-extensions:txflow-store-rdbms:integrationTest \ --tests '*PostgresFlowExecutionStoreContractTest' ``` The PostgreSQL layer starts a real PostgreSQL 17.6 server with Testcontainers. Docker must be available; the certification suite fails rather than silently skipping PostgreSQL when Docker is unavailable. No separately operated PostgreSQL credentials are required for this suite. ## External Yaci DevKit suite The TxFlow network integration tests use an externally running Yaci DevKit. The current suite expects the Blockfrost-compatible API at `http://localhost:8080/api/v1/`; rollback scenarios also use the admin API at `http://localhost:10000/`. Run the full TxFlow integration suite with: ```shell ./gradlew :txflow:integrationTest ``` Run the strict rollback matrix or the portable YAML/`FlowEngine` path in isolation with: ```shell ./gradlew :txflow:integrationTest \ --tests '*RollbackStrategyIntegrationTest' ./gradlew :txflow:integrationTest \ --tests '*TxFlowYamlIntegrationTest' ``` Use an isolated DevKit instance. These tests fund addresses, take snapshots, roll back chain state, and wait for backend/indexer recovery. The external Yaci matrix validates real submission, confirmation, rollback, same-hash reconciliation, and rebuild behavior; it complements rather than replaces the RDBMS crash, migration, and concurrency suites. ## Qualifying a custom store `txflow` publishes the reusable JUnit contract as a test-fixtures variant. Add it to the custom adapter's test configuration: ```gradle def cclVersion = '' dependencies { testImplementation(testFixtures( "com.bloxbean.cardano:cardano-client-txflow:${cclVersion}")) } ``` Provide a fresh, empty adapter for each test: ```java class MyFlowExecutionStoreContractTest extends FlowExecutionStoreContract { @Override protected FlowExecutionStore createStore(AdjustableClock clock) throws Exception { return MyFlowExecutionStore.createFresh(clock); } } ``` The contract exercises atomic idempotency and execution-ID conflicts, revision/event ordering, all-or-nothing append, execution and resource lease fencing, contention/takeover, UTF-8 identity limits, event pagination, and compaction. Passing it is necessary but not sufficient for a new database compatibility claim. Also add database-specific tests for migration locking and upgrade, real transaction isolation, error classification, commit/rollback uncertainty, process crash and reopen, schema drift, payload corruption, and concurrent independent store instances. See the published [`FlowExecutionStoreContract`](https://github.com/bloxbean/cardano-client-lib/blob/master/txflow/src/testFixtures/java/com/bloxbean/cardano/client/txflow/store/contract/FlowExecutionStoreContract.java) and the certified H2/PostgreSQL tests for reference. ## Release-readiness checklist - Java 17 unit, H2 integration, PostgreSQL Testcontainers, and external Yaci matrices are green. - Backup and restore have been exercised in an isolated environment. - Schema migration and runtime roles are separated and `VALIDATE` is used at normal startup. - Flow and maintenance executors, pool limits, timeouts, and shutdown order are documented. - Alerts exist for uncertainty, recovery-required, terminal-persistence, lease-renewal, and executor-rejection failures. - The deployment accepts the active-active submission limitation or adds external serialization. - Secrets, JDBC URLs, signed payloads, H2 files, database backups, and logs follow the application's security and retention policy. --- # TxFlow Overview Source: https://cardano-client.dev/txflow/overview/ Summary: Portable, policy-controlled Cardano transaction workflow execution TxFlow turns a reusable transaction workflow into an observable execution. It handles the parts that become difficult when several Cardano transactions must be coordinated: parameter binding, dependency outputs, UTxO spending contention, confirmation, rollback, retries, idempotency, and crash recovery. The current API is built around a deliberately separated lifecycle: ```text portable YAML or JSON | v TxFlowCodec -- diagnostics --> author/operator | v TxFlow definition + run-specific FlowBindings | v FlowEngine preflight --> compiled TxPlan per step | v FlowExecutionRequest --> handle, events, terminal result | +-------------> optional durable execution store and recovery ``` ## Requirements and status - Java 17 is the build and runtime baseline. - `txflow.cardano-client.dev/v1alpha1` is the current portable document contract. - `TxFlowCodec`, `FlowExecutionRequest`, and `FlowEngine` are the canonical APIs for new code. `FlowRuntime` wraps an ordinary `FlowEngine`, with no store, policy, or catalog. - `FlowExecutor` and the preview `version: "1.0"` document remain compatibility APIs. - With `FlowEngine`, applications provide and own executors, and Java 21 applications may pass a virtual-thread executor without changing the API. `FlowRuntime` is the managed front door for scripts and small applications: it creates and shuts down its own executors. The portable contract is still alpha and can evolve before a stable schema version. Persisted execution behavior is nevertheless conservative: an uncertain submission or chain observation does not become a successful result, and does not authorize building a different transaction. ## Definition versus execution A `TxFlow` definition contains ordered steps, portable parameters, execution defaults, and logical resource references. Each step embeds QuickTx's single-transaction projection (`tx` plus optional `context`). During compilation, TxFlow wraps the bound projection in a `TransactionDocument` and creates the step's one-transaction executable `TxPlan`; there is not a separate competing transaction definition owned by the runtime. A `FlowExecutionRequest` supplies values that belong to one run: - parameter bindings; - an execution ID; - an optional application idempotency namespace and key; - identities of shared spending resources; - external references for sensitive values. `FlowEngine` supplies server- or application-owned infrastructure: backend interfaces, signer and script registries, resource policy, executors, and an optional durable store. Keeping these out of the portable document prevents definitions from carrying private keys, live objects, or backend credentials. ## Execution safety model TxFlow follows a few safety rules throughout the runtime: 1. Parse and compile errors are structured diagnostics, not partial execution. 2. A matching execution or idempotency claim refers to one canonical request fingerprint; a different request is rejected. 3. Shared spending resources are serialized unless a server policy explicitly allows an opt-out. 4. Accepted or unknown submission outcomes are reconciled by transaction hash before any retry. 5. A rollback or uncertain observation cannot fall through to success. 6. Durable recovery can resubmit only the identical verified signed payload while its validity interval remains safe. 7. `RECOVERY_REQUIRED` is distinct from a conclusive `FAILED` result and requires reconciliation. Durable store leases fence TxFlow state mutations. They cannot prevent a partitioned process from submitting bytes it already signed, so active-active deployments also need external UTxO reservation or equivalent submission serialization. ## Pick a persistence profile | Profile | Store | Intended use | | --- | --- | --- | | No store | none | Short-lived execution where process loss can be handled externally | | In-memory | `InMemoryFlowExecutionStore` | Tests and reference contract behavior | | Embedded durable | RDBMS extension with file-backed H2 | One application JVM with restart recovery | | Service database | RDBMS extension with PostgreSQL | Pooled deployments and multi-process durable state | The relational extension owns no thread or connection pool. H2 is included for the embedded profile; PostgreSQL users provide a driver and normally an application-managed `DataSource`. ## Guide Follow these pages in order for a new integration: 1. [Your first flow](/txflow/your-first-flow/) — the smallest useful chained flow, using `FlowRuntime`. 1. [Getting Started](/txflow/getting-started/) — dependencies, a portable flow, backend adapters, and the first execution. 2. [Portable Authoring](/txflow/portable-authoring/) — parameters, expressions, resources, step outputs, schema boundaries, and diagnostics. 3. [Execution Engine](/txflow/execution-engine/) — constructing `FlowEngine`, preflight, request identity, handles, cancellation, and caller-owned executors. 4. [Policies, Results & Observability](/txflow/policies-results-observability/) — confirmation, retry, rollback, terminal states, events, and operational interpretation. 5. [Durable Runtime](/txflow/durable-runtime/) — H2, PostgreSQL, idempotency, leases, fencing, recovery, schema management, and secure bindings. 6. [Operations & Testing](/txflow/operations-testing/) — deployment checklist, monitoring, backup and restore, unit/integration tests, and custom-store qualification. Submitting many transactions over time rather than one workflow? The module also ships TxStream, a streaming submission API on the same engine — start with [TxStream Getting Started](/txstream/getting-started/), then [Durability & Exactly-Once](/txstream/durability/) and [Lanes & Throughput](/txstream/throughput/). :::note[Maintaining an older `FlowExecutor` integration?] `FlowExecutor` is a compatibility API. It still works, but it combines definition and execution concerns and is not the canonical durable-runtime path — this site documents `FlowEngine` and `FlowRuntime` only. For `FlowExecutor` reference material, see `txflow/README.md` in the source tree or the previous documentation site. ::: ## Module documentation The source tree also contains `txflow/README.md`, the portable-contract ADR, durable-store ADR, migration notes, and the implementation-level store contract. Those files are useful for contributors and adapter authors; this guide stays focused on application usage. --- # Policies, Results, and Observability Source: https://cardano-client.dev/txflow/policies-results-observability/ Summary: Retry, confirmation, rollback, results, cancellation, recovery, and diagnostics for FlowEngine > **Preview API:** This page describes `FlowEngine`, `FlowExecutionHandle`, and the portable > execution policy. The similarly named `FlowExecutor`, `FlowHandle`, and `FlowListener` APIs are > the legacy facade. Portable definitions may request execution behavior, but the application remains the authority. `FlowExecutionPolicy` evaluates those requests during compilation and either accepts them, caps a numeric value with a warning, or rejects the definition with a diagnostic. ## Portable requests and server policy A definition can request chaining, retry, confirmation, rollback, and validity settings: ```yaml spec: execution: mode: SEQUENTIAL retry: max_attempts: 4 backoff: exponential initial_delay: 1s max_delay: 20s jitter: 0.2 confirmation: min_confirmations: 6 check_interval: 3s timeout: 10m rollback: action: RECONCILE_AND_REBUILD monitoring_horizon: UNTIL_FLOW_TERMINAL rebuild_scope: INVALIDATED_CLOSURE max_recovery_cycles: 3 reinclusion_window: 2m minimum_consistent_absence_observations: 2 validity: window: 30m resubmit_safety_margin: 60 ``` `validity` is carried, fingerprinted and policy-capped, but in this preview the engine doesn't use it to set a transaction's validity interval or a recovery margin. Set `transaction.context.valid_from_slot` and `valid_to_slot`, and pass `resubmitSafetyMargin` on `FlowRecoveryRequest`. The engine does not blindly trust these values. Configure a policy at the service boundary: ```java FlowExecutionPolicy policy = FlowExecutionPolicy.builder() .maxSteps(25) .allowedNetworks(Set.of("preview")) .allowedModes(Set.of(ChainingMode.SEQUENTIAL)) .requireValidityInterval(true) .maxValidityWindowSlots(3_600) .maxRetryAttempts(5) .maxConfirmationTimeout(Duration.ofMinutes(30)) .maxRollbackRecoveryCycles(3) .allowedRollbackActions(Set.of( RollbackAction.FAIL, RollbackAction.RECONCILE_AND_REBUILD, RollbackAction.PAUSE_FOR_RECOVERY)) .allowedRollbackHorizons(Set.of( RollbackMonitoringHorizon.UNTIL_FLOW_TERMINAL)) .strictSettings(true) .build(); ``` With `strictSettings(false)` (the default), cappable execution requests—retry attempts, confirmation timeout, rollback recovery cycles, and the duration-based validity preference—are capped and reported as warnings. With `strictSettings(true)`, those differences are errors. Structural limits such as step count, explicit absolute-slot validity limits, disallowed modes, networks, resource prefixes, rollback actions, and monitoring horizons are always errors. ## Spending-resource contention The compiler derives canonical spending identities from catalog descriptors, and a request may add more with `spendingResource(...)`. `FlowExecutionPolicy` decides what happens when executions overlap: | `SpendingContentionPolicy` | Behavior | |---|---| | `SERIALIZE` | Fairly wait up to `maxQueueWait`; defaults to a two-minute bound | | `REJECT` | Fail immediately with a resource-busy result | | `ALLOW` | Do not coordinate overlapping spending identities | ```java FlowExecutionPolicy policy = FlowExecutionPolicy.builder() .spendingContention(SpendingContentionPolicy.SERIALIZE) .maxQueueWait(Duration.ofSeconds(30)) .allowConcurrentSpendingOptOut(false) .build(); ``` The in-process coordinator acquires multiple identities in sorted order. A durable engine also holds fenced resource leases for cooperating workers that use the same store. Neither mechanism reserves an on-chain UTxO against a partitioned stale worker. `FlowExecutionRequest.allowConcurrentSpending(true)` bypasses coordination only when `allowConcurrentSpendingOptOut(true)` permits it. Treat both `ALLOW` and the per-request opt-out as expert settings; concurrent builders may select and submit transactions spending the same UTxO. ## Retry is phase-aware `RetryPolicy` controls the maximum attempt count, fixed/linear/exponential backoff, delay bounds, and jitter. It does not make every failure retryable. ```java RetryPolicy retry = RetryPolicy.builder() .maxAttempts(4) .backoffStrategy(BackoffStrategy.EXPONENTIAL) .initialDelay(Duration.ofSeconds(1)) .maxDelay(Duration.ofSeconds(20)) .retryOnTimeout(true) .retryOnNetworkError(true) .jitterFactor(0.2) .build(); ``` The safe action depends on both execution phase and `SubmissionOutcome`: | Known fact | Safe default | |---|---| | Typed transient network/timeout failure before signing or submission | Rebuild the step, within the attempt bound | | A transaction hash is known and retry is allowed | Retry or reconcile the same transaction identity | | Submission outcome is `UNKNOWN` | Reconcile the known hash before any retry or rebuild | | Submission outcome is `ACCEPTED` | Observe/reconcile; acceptance is not confirmation | | Submission outcome is `REJECTED` | Fail unless a higher-level policy explicitly starts a new operation | | Validation, policy, build, signing, or unclassified failure | Fail conservatively | Unknown submission is a safety state: a timeout after sending bytes does not prove that the network rejected them. TxFlow never authorizes a fresh transaction merely because the submission call did not return a usable answer. The compatibility method `RetryPolicy.isRetryable(Throwable)` has no phase information. It admits known `IOException` and timeout causes according to policy, but rejects an untyped `RuntimeException` conservatively. Backend adapters should preserve typed transient causes. ## Submission and confirmation are separate `SubmissionOutcome.ACCEPTED` means only that the submission adapter reported success. Confirmation requires observing the transaction on-chain and, when configured, reaching the requested depth. ```java ConfirmationConfig confirmation = ConfirmationConfig.builder() .minConfirmations(6) .checkInterval(Duration.ofSeconds(3)) .timeout(Duration.ofMinutes(10)) .requiredAuthoritativeAbsences(2) .build(); ``` The production-oriented defaults are depth 10, five-second polling, a 30-minute timeout, and two consecutive authoritative absences before rollback. `testnet()`, `devnet()`, and `quick()` are convenience presets; verify them against the risk and block cadence of your deployment. On a Java-built `TxFlow` run through `FlowEngine`, `ConfirmationConfig.devnet()` and `quick()` are rejected because they carry legacy backend-wait fields. Use `preset: devnet` or `preset: quick` in the document. If a portable definition has neither `confirmation` nor `rollback`, the engine uses simple on-chain polling with no depth tracking. If it has `rollback` but no `confirmation`, it uses `ConfirmationConfig.defaults()`: depth 10, five-second polling and a 30-minute timeout. Set `confirmation` explicitly. An observation adapter controls whether absence is authoritative. A transaction that disappears from an indexer is only *suspected* rolled back when the backend cannot prove absence. Ambiguous observations remain pending and eventually become `RECOVERY_REQUIRED`; they are never treated as permission to rebuild. `DefaultChainDataSupplier` doesn't declare authoritative absence, and neither `FlowEngine.builder(BackendService)` nor `FlowRuntime` adds it. With that wiring no rollback is ever established and `RollbackAction` isn't applied: the transaction times out and the execution ends `RECOVERY_REQUIRED`. To opt in, use the four-supplier builder: ```java ChainDataSupplier chain = ObservationCapabilities.withAuthoritativeAbsence( new DefaultChainDataSupplier(backendService)); ``` Declare this only if the index is consistent with its reported tip; otherwise a slow index looks like a rollback. ## Rollback policy Rollback is distinct from an ordinary retry and begins only after authoritative evidence reaches the configured consecutive-absence threshold. | `RollbackAction` | Behavior | |---|---| | `FAIL` | End as `FAILED` or `PARTIALLY_COMPLETED`, with `error().category() == ROLLBACK` | | `WAIT_FOR_REINCLUSION` | Keep the same transaction hash and wait within `reinclusionWindow` | | `RECONCILE_AND_REBUILD` | Reconcile first, then rebuild only work proven invalid | | `PAUSE_FOR_RECOVERY` | Stop automated progress and return `RECOVERY_REQUIRED` | `RollbackMonitoringHorizon.UNTIL_STEP_CONFIRMED` stops monitoring after the step's target depth. `UNTIL_FLOW_TERMINAL` continues checking included attempts until the whole flow is terminal. `RollbackRebuildScope.AFFECTED_STEP` starts with the rolled-back step, but dependent work may still need invalidation. `INVALIDATED_CLOSURE` includes the producer and every transaction that consumes its invalidated outputs. TxFlow will widen a rebuild when a narrower scope cannot be safe. The policy's `maxRecoveryCycles` bounds automation. Exhaustion or an inconclusive reconciliation does not silently become success; it fails or requires explicit recovery according to the observed state and policy. ## Terminal results `FlowExecutionHandle.await()` returns a `FlowExecutionResult` for every normal terminal outcome. Use `isSuccessful()` only as a convenience for the exact `COMPLETED` state; otherwise branch on `state()`. ```java FlowExecutionResult result = handle.await(); switch (result.state()) { case COMPLETED -> recordSuccess(result.executionId(), result.steps()); case RECOVERY_REQUIRED -> scheduleReconciliation( result.executionId(), result.attempts(), result.error()); case CANCELLED -> recordCancellation(result.executionId()); default -> recordFailure(result.executionId(), result.state(), result.error()); } ``` The result contains: | Accessor | Meaning | |---|---| | `executionId()` | Request correlation and durable lookup identity | | `definitionFingerprint()` | SHA-256 identity of the compiled portable plan | | `state()` | Terminal or recovery-required execution state | | `steps()` | Immutable `FlowStepResult` values observed by this run | | `attempts()` | Durable `FlowAttemptSnapshot` values; empty without a store | | `error()` | Stable `FlowError`, or `null` after successful completion | | `startedAt()` / `completedAt()` | Execution timestamps | `FlowStepResult` retains the step ID, status, known transaction hash, produced UTxOs, spent inputs, result-observation time, and the original step-level throwable. In pipelined and batch failures, a submitted but not yet confirmed step is `IN_PROGRESS`, not successful; a transaction that was only built is omitted. Durable `FlowAttemptSnapshot` adds the attempt number, exact signed payload identity, validity bounds, input identities, inclusion history, and stable error code needed for recovery. ### Execution states | State | Meaning | |---|---| | `COMPLETED` | Every required step completed successfully | | `PARTIALLY_COMPLETED` | At least one step reached the effective confirmation policy before a later conclusive failure | | `FAILED` | Execution ended with a failure. In `PIPELINED` and `BATCH`, earlier steps may already be submitted and can still land; check `steps()` for `IN_PROGRESS` entries with a transaction hash | | `ROLLED_BACK` | Reserved; a `FlowEngine` run doesn't currently produce it. Detect rollback through `FlowErrorCategory.ROLLBACK` | | `CANCELLED` | Cooperative cancellation reached a safe boundary | | `RECOVERY_REQUIRED` | State is uncertain; reconcile before retry or rebuild | `CREATED`, `COMPILING`, `QUEUED`, and `RUNNING` are durable lifecycle states that may appear in stored snapshots. They are not successful terminal results. ### Structured errors `FlowError.code()` is the stable, specific value for program logic. `category()` is a coarser dimension for metrics and routing: validation, policy, resource, build, sign, submission, network, backend unavailable, confirmation, rollback, persistence, recovery, cancellation, or internal. ```java FlowError error = result.error(); if (error != null) { metrics.increment("txflow.failure", Map.of( "code", error.code(), "category", error.category().name())); } ``` `retryable()` is advisory context, not permission to submit a replacement transaction. In particular, reconcile `RECOVERY_REQUIRED` and unknown-submission outcomes by transaction hash before creating any new attempt. ## Ordered events `FlowExecutionHandle` exposes immutable snapshots of per-execution events. Sequence numbers start at one, increase within one execution, and are not globally ordered across executions. ```java long cursor = 0; for (FlowEvent event : handle.getEventsAfter(cursor)) { publishEvent(event.executionId(), event.sequence(), event.type(), event.details()); cursor = event.sequence(); } ``` Persist the last successfully consumed sequence as an exclusive cursor. Calling `getEventsAfter(cursor)` again returns only newer handle events. With a durable store, use `FlowExecutionStore.readEvents(executionId, cursor, limit)` for restart-safe consumption. If it reports `EVENTS_COMPACTED`, reload the latest execution snapshot and establish a new baseline. Important event groups include: - execution: `EXECUTION_CREATED`, `COMPILATION_COMPLETED`, `EXECUTION_QUEUED`, `EXECUTION_STARTED`, and terminal execution events; - transaction attempt: `TRANSACTION_PREPARED`, `TRANSACTION_SUBMITTING`, `TRANSACTION_SUBMITTED`, inclusion/depth/confirmation, and rollback events; - recovery: `RECOVERY_STARTED`, `RECOVERY_COMPLETED`, and `RECOVERY_REQUIRED`. Consumers should tolerate event types added in future releases. Event `details` are useful diagnostic attributes, but the typed event and terminal result remain the contract. ### What about `FlowListener`? `FlowListener` is configured with `FlowExecutor.withListener(...)`; it is not a method on `FlowEngine.Builder`. `FlowEngine` internally translates execution callbacks into ordered `FlowEvent` values. Portable-runtime applications should consume handle or durable-store events instead of attempting to attach a `FlowListener` to the engine. Likewise, `FlowRegistry` tracks legacy `FlowHandle` instances. Track portable executions by their `executionId`, handle, event stream, and—when configured—`FlowExecutionStore`. ## Cooperative cancellation Cancellation is a signal checked at safe execution, waiting, and retry boundaries. It does not interrupt the executor thread, close application resources, roll back the Cardano chain, or undo a transaction that has already been submitted. ```java CancellationResult cancellation = handle.requestCancel("application shutdown"); if (cancellation == CancellationResult.REQUESTED) { FlowExecutionResult terminal = handle.await(); // The request was acknowledged earlier; terminal confirms where execution stopped. } ``` Possible acknowledgements are `REQUESTED`, `ALREADY_REQUESTED`, and `ALREADY_TERMINAL`. The boolean `cancel()` method is shorthand for a reasonless request and returns `true` only for the first accepted signal. Always await or otherwise observe the terminal result after requesting cancellation. ## Parse and compile diagnostics Syntax and expected authoring problems are values, not control-flow exceptions: ```java FlowParseResult parsed = TxFlowCodec.standard() .parse(source, FlowParseOptions.serverDefaults()); report(parsed.getDiagnostics()); if (parsed.hasErrors()) { return; } FlowCompilationResult compiled = engine.preflight( FlowExecutionRequest.builder(parsed.requireFlow()) .bindings(bindings) .build()); report(compiled.getDiagnostics()); if (compiled.hasErrors()) { return; } ``` Each `FlowDiagnostic` has a stable code, severity, message, JSONPath-like document path, optional source line/column, and optional step ID. Log or return the code and location; avoid exposing secret binding values in diagnostic text. Invalid Java API arguments can still throw immediately—for example, a blank execution ID, only one half of an idempotency pair, an invalid duration, or an unsupported binding value. Once `start` returns a handle, expected execution rejection is represented in its terminal result. ## Explicit recovery When a durable result is `RECOVERY_REQUIRED`, recover the persisted attempt rather than rebuilding blindly. Obtain a fresh authoritative Cardano **slot**—not a block height or wall-clock value—and fail closed if it is unavailable. The engine trusts `currentSlot`; it does not establish freshness itself. Supply a safety margin before the recorded validity upper bound: ```java FlowRecoveryResult recovery = engine.recover(FlowRecoveryRequest.builder() .executionId(result.executionId()) .currentSlot(currentSlot) .resubmitSafetyMargin(60) .build()); ``` `stepId` and `attemptNumber` select a particular persisted attempt. They are optional in the Java API, but operator and cross-process recovery should require both unless the target is provably unambiguous; otherwise the engine chooses the highest attempt number among all matching steps. Recovery acquires durable execution and resource leases, observes the known hash, and may resubmit only verified identical signed CBOR. Its expiry check applies only when `validToSlot` is present, so controlled deployments should require validity intervals through server policy. The built-in `FlowEngine` persistence path stores signed CBOR inline. If a custom or pre-seeded store record uses `SignedPayload.ExternalCbor`, provide an application-owned `SignedPayloadResolver` on the recovery request: ```java FlowRecoveryResult recovery = engine.recover(FlowRecoveryRequest.builder() .executionId(executionId) .currentSlot(currentSlot) .resubmitSafetyMargin(60) .payloadResolver(reference -> signedPayloadStore.load(reference)) .build()); ``` TxFlow verifies both the recorded SHA-256 digest and Cardano transaction hash after loading the bytes. A resolver retrieves bytes; it is not a trust bypass. `FlowRecoveryResult` reports the reconciled `AttemptState`, transaction hash, whether this call resubmitted the identical payload, an optional structured error, and newly observed inclusion metadata. Treat `AttemptState.RECOVERY_REQUIRED` as still unresolved. Recovery reconciles one persisted transaction attempt. It does not automatically resume every remaining step of a stopped multi-step flow; the application must interpret the recovery result and apply its workflow policy before taking another action. ## Production limitations See also the [known issues](/project/known-issues/#txflow-engine) in the latest preview. - Store leases and fencing prevent stale workers from mutating TxFlow's durable state. They cannot stop a partitioned stale process from submitting already-signed bytes to Cardano. Active-active deployments needing that guarantee require external spending serialization or a UTxO reservation coordinator. - Cooperative cancellation cannot reverse a submitted or confirmed transaction. - Indexer absence is not proof of rollback unless the backend adapter declares authoritative absence. Uncertainty remains `RECOVERY_REQUIRED`. - The in-memory execution store, in-memory idempotency claims, handle event list, and legacy registry do not survive a process restart. - Explicit recovery reconciles a persisted attempt; it is not a general automatic continuation service for every remaining step after a process restart. - Resource catalogs contain public metadata. Signer keys, scripts, secure bindings, payload storage, access control, and credential rotation remain application responsibilities. - The engine owns no threads. Keep its caller-owned task and maintenance executors alive, bounded, observable, and shut down by the application. Start with [Executing Portable Flows](/txflow/execution-engine/) for end-to-end wiring. --- # Portable TxFlow Authoring Source: https://cardano-client.dev/txflow/portable-authoring/ Summary: Author safe, reusable TxFlow v1alpha1 YAML and JSON definitions Portable TxFlow is a versioned YAML/JSON contract for reusable, ordered transaction workflows. It contains transaction intent and logical resource names, but no execution ID, runtime parameter values, signer keys, database credentials, or backend connection details. This page describes `txflow.cardano-client.dev/v1alpha1`. The legacy `version: "1.0"` format uses a different envelope and expression syntax. The codec can read that format for migration, but new portable documents should not mix the two contracts. ## Document envelope Every portable document has four required top-level fields: ```yaml api_version: txflow.cardano-client.dev/v1alpha1 kind: TxFlow metadata: name: treasury-distribution description: Distribute treasury funds to approved recipients version: 1.0.0 annotations: owner: treasury-service spec: network: preview steps: - id: transaction transaction: tx: intents: [] ``` | Field | Meaning | | --- | --- | | `api_version` | Contract identifier. `v1alpha1` is the only version enabled by the default parser. | | `kind` | Must be `TxFlow`. It distinguishes a flow from a standalone QuickTx plan. | | `metadata.name` | Stable definition name; it is not an execution/run identifier. | | `metadata.description` | Optional human-readable description with no execution semantics. | | `metadata.version` | Optional application-managed definition version. | | `metadata.annotations` | Optional string-to-string metadata. It has no execution semantics. | | `spec.network` | Optional logical network name checked by resource and server policy preflight. | | `spec.parameters` | Optional typed runtime input declarations. | | `spec.execution` | Optional requested execution settings; server policy remains authoritative. | | `spec.steps` | Non-empty ordered list of single-transaction steps. | Both flow-level `metadata.description` and step-level `description` values round-trip through the portable codec. Annotations remain the right place for machine-readable ownership or classification metadata. Steps run in document order in `v1alpha1`. IDs must be unique. Dependencies and output references must name earlier steps; dangling references, cycles, duplicate `needs`, and forward references are rejected. ## Parameters and `FlowBindings` Declare values that change between executions under `spec.parameters`: ```yaml spec: parameters: beneficiary: type: address required: true max_length: 200 amount: type: integer minimum: 1000000 maximum: 100000000 include_metadata: type: boolean default: false asset: type: asset_unit required: true note: type: string default: distribution max_length: 80 ``` Supported types are `string`, `integer`, `boolean`, `address`, and `asset_unit`. `address` and `asset_unit` are semantically named string types at this layer. Integer values must fit a Java `long`. A required parameter cannot also declare a default. `minimum`/`maximum` constrain integers; `max_length` constrains string-valued parameters. Supply values independently for each run: ```java FlowBindings bindings = FlowBindings.builder() .put("beneficiary", beneficiaryAddress) .put("amount", 5_000_000L) .put("asset", policyId + assetNameHex) .build(); ``` `FlowBindings` accepts non-null strings, booleans, and integral `Byte`, `Short`, `Integer`, or `Long` values. Duplicate or blank binding names fail immediately. During compilation, an unknown binding, missing required value, type mismatch, or constraint violation is returned as a diagnostic. ### Expressions preserve types Portable expressions use only the `inputs` namespace: ```yaml address: '${{ inputs.beneficiary }}' quantity: '${{ inputs.amount }}' ``` When the entire scalar is one expression, the compiler inserts the native bound value. In the example, `quantity` becomes a JSON/YAML integer rather than text. String inputs may also be interpolated into a larger string: ```yaml label: 'distribution-${{ inputs.note }}' ``` Integers and booleans cannot be interpolated into larger text. Expressions cannot appear in map keys. Undefined expressions fail compilation. Binding walks the parsed node tree and never performs raw text replacement, so a value cannot inject new YAML structure. The legacy `${name}` form is rejected in portable documents. Conversely, `${{ inputs.name }}` is rejected in legacy documents, making accidental format mixing visible. Marking a parameter `sensitive: true` changes durable-runtime handling, not its scalar type. A durable `FlowExecutionRequest` must associate every resolved sensitive value—whether explicitly bound or taken from a declared default—with an external secure reference. The store persists the reference, a value fingerprint, and a redacted display rather than the raw value. See [Durable runtime](/txflow/durable-runtime/). ## Steps and `needs` Each step has an ID, an optional description, optional `needs`, optional `funding_from`, optional named outputs, and exactly one `transaction`: ```yaml steps: - id: prepare transaction: tx: intents: [] - id: submit description: Runs after prepare needs: [prepare] transaction: tx: intents: [] ``` `needs` is an ordering/dependency declaration. It does **not** select, expose, or spend any output from the named step. Use a `flow_output` input reference when the later transaction must consume a specific earlier output. The portable contract is deliberately an ordered transaction graph, not a general parallel DAG. Declaring `needs` documents the business dependency and contributes to graph validation; it does not reorder the `steps` list. ## Transaction ownership: TxFlow embeds one QuickTx transaction projection The object under `step.transaction` is owned by the QuickTx serialization contract: ```yaml transaction: tx: from_ref: account://treasury change_address: addr_test1... inputs: [] intents: [] scripts: [] context: fee_payer_ref: account://treasury collateral_payer_ref: account://collateral valid_from_slot: 1000 valid_to_slot: 1300 signers: - ref: account://treasury scope: payment ``` The ownership boundary is concrete: - `TxFlowCodec` retains this object as an unbound `TransactionTemplate`. - `TxFlowCompiler` applies typed inputs and resource/policy preflight to a copy. - The compiler projects the bound `tx` and optional `context` into QuickTx's `TransactionDocument`, then creates a fresh `TxPlan` containing exactly one transaction. - QuickTx owns intent deserialization, transaction composition, signer/script registries, and the resulting transaction body. TxFlow owns step ordering, named outputs, execution, retry, confirmation, rollback, persistence, and recovery. The initial QuickTx schema retains the historical `tx.from_wallet` field, but the current `TxPlan` projection cannot materialize a wallet name safely. Compilation therefore rejects that field with `TXFLOW_TRANSACTION_FROM_WALLET_UNSUPPORTED` before producing an execution fingerprint. Use a logical `from_ref` instead, resolved through QuickTx's `SignerRegistry` or the `.account(...)` builder call. The TxFlow catalog can describe it, but doesn't resolve it. Compilation also materializes the embedded QuickTx model with a compiler-local strict mapper. Fields unknown to the concrete transaction or intent type fail with `TXFLOW_TRANSACTION_FIELD_UNKNOWN`, and each materialized intent runs its existing QuickTx model validation before fingerprinting. This strictness is scoped to portable TxFlow compilation; it does not change standalone legacy QuickTx deserialization behavior. A standalone `TxPlan` can contain a transaction list. Portable TxFlow `v1alpha1` intentionally allows only one transaction per step. When writing portable output from Java, a step with a multi-transaction `TxPlan` or a Java `TxContext` factory fails with `FlowEncodingException` instead of silently losing behavior. ### Current JSON Schema boundary `txflow-v1alpha1.schema.json` references `quicktx-transaction-v1alpha1.schema.json#/$defs/transaction`. The QuickTx schema covers the embedded, single-transaction structural projection represented by `TransactionDocument`—the `tx` fields and optional transaction `context`. It is **not yet an exhaustive schema for every `TxIntent` implementation**. In particular, `inputs`, `intents`, and `scripts` are currently broad object schemas (regular intent/script entries require a `type`, but subtype-specific required fields are not fully described). QuickTx's runtime `TxIntent` registrations support payment, metadata, minting, staking, pools, governance, input collection, script collection/minting, reference inputs, and script attachments beyond what the schema constrains field by field. Therefore, a document passing an external JSON Schema validator is not proof that every intent can be deserialized and composed. Treat the published schema as editor/structural validation, then run `TxFlowCodec` parsing, compiler preflight, and transaction-focused tests. Expanding the schema into type-discriminated definitions for all supported intentions is tracked in [issue #636](https://github.com/bloxbean/cardano-client-lib/issues/636), separately from the `v1alpha1` runtime implementation. ## Named outputs and `flow_output` Publish a producer transaction output under a flow-local name: ```yaml - id: fund outputs: staging: select: output_index: 0 expect: exactly_one transaction: tx: from_ref: account://treasury intents: - type: payment address: addr_test1_staging... amounts: - unit: lovelace quantity: 5000000 ``` `output_index` is zero-based. Portable `v1alpha1` supports only index selection with `expect: exactly_one`; execution fails if that output is absent. A later step explicitly consumes it through QuickTx's input-reference model: ```yaml - id: forward needs: [fund] transaction: tx: from_ref: account://staging inputs: - type: collect_from refs: - flow_output: step: fund output: staging intents: - type: payment address: '${{ inputs.beneficiary }}' amounts: - unit: lovelace quantity: 3000000 context: fee_payer_ref: account://staging signers: - ref: account://staging scope: payment ``` The consuming step must set `from_ref` and sign with the output's payment key. Compilation doesn't check this, so a mistake surfaces only when the step runs. The compiler verifies that `fund` exists, precedes `forward`, and publishes `staging`. At each execution attempt, TxFlow replaces the logical reference with the concrete transaction hash and output index captured from that run. The reusable definition is left unchanged. `flow_output` is supported in QuickTx input-intent locations materialized by TxFlow, including ordinary collection, script collection, and reference inputs. It is the consumption edge; `needs: [fund]` is still useful to state the ordering/business dependency explicitly but is not a substitute for it. ## QuickTx extensions *Experimental: follows the experimental QuickTx extension SPI. Available from 0.8.0-pre7.* TxFlow runs steps that use extension intents, such as [programmable tokens](/learn/cip113-programmable-tokens/), once the operator registers the extension: ```java FlowRuntime runtime = FlowRuntime.builder(backendService) .extension(programmableTokens) // or FlowEngine.Builder.extension(...) .build(); ``` - A document declares its binding in `spec.extensions` and uses intents as `:`, only in `tx.intents`. A document can select and assert a binding, but can't construct or configure an extension. - A step needing an unregistered extension is rejected before submission (`TXFLOW_EXTENSION_UNREGISTERED`), and so is a binding no intent uses (`TXFLOW_EXTENSION_BINDING_UNUSED`). - Intents must round-trip through the extension (`TXFLOW_EXTENSION_INTENT_LOSSY`), and `flow_output` inside them is rejected (`TXFLOW_EXTENSION_FLOW_OUTPUT_UNSUPPORTED`). - `TxFlowCodec.standard()` can parse these documents. Writing them needs `TxFlowCodec.withExtensions(List.of(extension))`. - For Java `TxPlan` steps, bind with `extension.configure(plan)`. The portable path rejects a plan that still carries variables; clear them with `plan.setVariables(Map.of())`. - Under `PIPELINED`, an extension's address scan sees an earlier step's pending outputs only with `funding_from`, and Plutus steps need a [local evaluator](/txflow/execution-engine/#chaining-modes). See [ADR 0008](https://github.com/bloxbean/cardano-client-lib/blob/master/txflow/adr/0008-generic-quicktx-extensions-in-txflow.md). ## Transaction context and logical resources `spec.execution` and `step.transaction.context` have different owners: - `spec.execution` requests TxFlow behavior such as chaining mode, confirmation, rollback, retry, and validity policy. Server `FlowExecutionPolicy` may cap numeric requests or reject semantic choices. - `transaction.context` is QuickTx transaction composition context: fee/collateral payer, signer references, required signer hashes, absolute validity slots, and deposit settings. Prefer logical resource URIs over embedding application secrets: ```yaml tx: from_ref: account://treasury scripts: - type: validator role: spend script_ref: script://approved/escrow context: fee_payer_ref: account://treasury collateral_payer_ref: account://collateral signers: - ref: account://treasury scope: payment ``` TxFlow recognizes textual `ref` and `*_ref` fields containing `://`. A resource URI must have a scheme and host, such as `account://treasury`, `policy://nft`, or `script://approved/escrow`. The normalized identity uses a lower-case scheme plus host and path; query, fragment, user-info, and port must not distinguish resources because they are discarded. During preflight, the application-owned `FlowResourceCatalog` can verify that each logical resource exists, belongs to the requested network, and advertises the required `SPEND`, `SIGN`, `MINT`, or `SCRIPT` capability. Its optional `spendingIdentity` becomes the contention key. The catalog holds metadata only; actual signer and script material is resolved through QuickTx's `SignerRegistry` and `ScriptRegistry` at execution time. An absent catalog does not make a reference resolvable: it merely skips catalog-backed existence, network, and capability checks. Resource-prefix policy still applies, and the relevant QuickTx registry must still resolve the reference before composition. Server deployments should normally configure both a restrictive policy and a catalog. ## Requested execution settings Execution settings are definition requests, not authority. A representative portable section is: ```yaml execution: mode: SEQUENTIAL confirmation: min_confirmations: 3 check_interval: 2s timeout: 5m retry: max_attempts: 3 backoff: exponential initial_delay: 1s max_delay: 20s jitter: 0.15 rollback: action: RECONCILE_AND_REBUILD monitoring_horizon: UNTIL_FLOW_TERMINAL rebuild_scope: INVALIDATED_CLOSURE max_recovery_cycles: 3 reinclusion_window: 90s minimum_consistent_absence_observations: 2 validity: window: 20m resubmit_safety_margin: 60 ``` `confirmation.preset` may be `defaults`, `devnet`, `testnet`, or `quick`; explicit portable confirmation fields override the preset. Presets contribute only `min_confirmations`, `check_interval`, and `timeout`. Legacy backend-restart/wait fields are intentionally not part of the portable contract. Durations accept a non-negative integer followed by `ms`, `s`, `m`, or `h`, or an ISO-8601 duration. Owning configuration builders reject zero/negative values where the policy requires a positive duration. See [Policies, results, and observability](/txflow/policies-results-observability/) for effective settings and rollback semantics. ## Safe YAML and JSON parsing Use bounded options for any document that crosses a trust boundary: ```java FlowParseOptions options = FlowParseOptions.builder() .maxDocumentBytes(1_000_000) .maxAliases(0) .maxNestingDepth(64) .maxSteps(200) .unknownFieldPolicy(UnknownFieldPolicy.REJECT) .supportedApiVersions(Set.of( FlowSchemaVersion.V1ALPHA1.getIdentifier())) .build(); FlowParseResult result = TxFlowCodec.standard().parse(source, options); ``` `FlowParseOptions.serverDefaults()` uses a 3,000,000-byte UTF-8 limit, at most 50 YAML collection aliases, nesting depth 100, at most 1,000 steps, unknown-field rejection, and an allowlist containing only `v1alpha1`. Tighten those limits for your service rather than increasing them to accommodate untrusted input. The standard codec also rejects duplicate YAML/JSON keys and multi-document YAML streams. It returns expected syntax and authoring failures as `FlowDiagnostic` values. Each diagnostic carries a stable code, severity, document path, and—when the parser supplies them—line, column, and step ID. Do not branch on the human-readable message. Unknown-field behavior is explicit: - `REJECT` is the safe server default; - `WARN` continues and emits `TXFLOW_UNKNOWN_FIELD`; and - `IGNORE` drops unknown envelope fields without a diagnostic. Use `WARN` or `IGNORE` only as a deliberate compatibility policy. The in-memory `TxFlow` model does not preserve unknown fields for a later round trip. `TxFlowCodec.detect(source)` is best-effort classification (`TX_FLOW`, `TX_PLAN`, or `UNKNOWN`), not validation. Always call `parse(...)` before accepting a document. ## Write YAML or JSON The portable writer supports both syntaxes: ```java TxFlowCodec codec = TxFlowCodec.standard(); String yaml = codec.write(definition, FlowWriteOptions.of(FlowFormat.YAML, FlowSchemaVersion.V1ALPHA1)); String json = codec.write(definition, FlowWriteOptions.of(FlowFormat.JSON, FlowSchemaVersion.V1ALPHA1)); ``` Use `TxFlowCodec.write` when you need a portable document. `TxFlow.toYaml()` is retained for the historical format. Legacy writing supports YAML only. Writing fails rather than projecting away behavior that has no portable representation. Java transaction factories, every legacy `StepDependency` strategy, step-level retry overrides, legacy flow or `TxPlan` variables, steps containing zero or multiple QuickTx transactions, legacy `RollbackStrategy`, non-default legacy confirmation compatibility fields, and non-default `retryOnTimeout`/`retryOnNetworkError` filters are not silently serialized. Use `RollbackPolicy`, migrate dependency selection to `needs` plus named `flow_output` references, and migrate variables to parameters plus `FlowBindings`. Reparse generated output with the same production parse options in build-time conformance tests. The schemas are available on the classpath as: ```text /schema/txflow-v1alpha1.schema.json /schema/quicktx-transaction-v1alpha1.schema.json ``` An external schema validator must resolve the TxFlow schema's QuickTx `$ref` to the second resource. The Java codec does not replace compiler preflight or QuickTx runtime validation. ## Portable versus legacy at a glance | Concern | Portable `v1alpha1` | Legacy compatibility format | | --- | --- | --- | | Envelope | `api_version`, `kind`, `metadata`, `spec` | `version`, optional `context`, `flow` | | Runtime values | Typed `FlowBindings` | Definition variables and `${name}` text substitution | | Ordering | `needs` | `depends_on` also mixed with UTxO availability | | Exact output use | Named `outputs` plus `flow_output` | Implicit pending-UTxO behavior | | Transaction | One embedded QuickTx transaction per step | TxPlan/Java factory compatibility paths | | Execution API | Immutable `FlowEngine` and `FlowExecutionRequest` | Mutable `FlowExecutor` facade | | Diagnostics | Structured parser/compiler diagnostics | Compatibility warnings and exceptions | The codec reports `TXFLOW_LEGACY_FORMAT` when it reads the old envelope. Migrate intentionally; portable encoding refuses constructs it cannot represent. ## Next steps - [Getting started](/txflow/getting-started/) — build and run the first portable flow. - [Execution engine](/txflow/execution-engine/) — requests, executors, resources, idempotency, and recovery. - [Policies, results, and observability](/txflow/policies-results-observability/) — control effective settings and consume structured outcomes. - [Durable runtime](/txflow/durable-runtime/) — persist executions safely with H2 or PostgreSQL. - [Operations and testing](/txflow/operations-testing/) — schema fixtures, Yaci tests, diagnostics, and deployment validation. --- # Your first flow Source: https://cardano-client.dev/txflow/your-first-flow/ Summary: Chain two transactions where the second spends what the first produced — the smallest TxFlow that does something you cannot do with QuickTx alone. :::tip[Preview API] TxFlow is preview. It works and is documented in full, but `txflow.cardano-client.dev/v1alpha1` can still change before a stable schema version. Pin an exact version if you build on it now. ::: This page builds the smallest flow that is genuinely worth using TxFlow for: **two transactions, where the second spends an output the first created.** If you only need one transaction, stop here and use [QuickTx](/quicktx/overview/) — it is simpler and this page will not make it better. Come back when you hit the problem below. ## The problem TxFlow solves Try chaining two transactions by hand and you meet this immediately: ```java // Transaction 1 — pays 5 ADA to a staging address. complete() returns once submitted. TxResult first = quickTxBuilder.compose(fundTx) .withSigner(SignerProviders.signerFrom(sender)) .complete(); // Transaction 2 — wants to spend the output transaction 1 just created. // But the backend does not know about that UTXO yet, so selection can't find it. ``` `completeAndWait()` narrows this window but doesn't remove it: its default wait is 60 seconds, and a public indexer's UTxO view can lag behind a confirmed transaction. The output exists in a submitted transaction, but until the node has indexed it, a normal UTXO query cannot see it. Working around that by hand means polling, retrying, and reasoning about rollbacks — and getting it subtly wrong. TxFlow handles it: you declare that step two consumes step one's output, and the engine substitutes the concrete transaction hash and output index at run time. ## 1. Declare the flow TxFlow definitions are YAML. Save this as `funding-flow.yaml`: ```yaml api_version: txflow.cardano-client.dev/v1alpha1 kind: TxFlow metadata: name: funding version: 1.0.0 spec: network: preview parameters: staging: type: address required: true beneficiary: type: address required: true execution: confirmation: min_confirmations: 1 check_interval: 2s timeout: 2m steps: # Step 1 — pay into a staging address, and publish that output as "held". - id: fund outputs: held: select: output_index: 0 expect: exactly_one transaction: tx: from_ref: account://sender intents: - type: payment address: '${{ inputs.staging }}' amounts: - unit: lovelace quantity: 5000000 context: signers: - ref: account://sender scope: payment # Step 2 — spend exactly that output. - id: forward needs: [fund] transaction: tx: from_ref: account://staging inputs: - type: collect_from refs: - flow_output: step: fund output: held intents: - type: payment address: '${{ inputs.beneficiary }}' amounts: - unit: lovelace quantity: 3000000 context: fee_payer_ref: account://staging signers: - ref: account://staging scope: payment ``` Three things carry the whole idea: - **`outputs.held`** publishes step one's output 0 under a flow-local name. `output_index` is zero-based, and `expect: exactly_one` means execution fails loudly if that output is not there. - **`flow_output`** in step two's `inputs` is the consumption edge. At run time TxFlow replaces it with the real transaction hash and index from *this* execution. - **`needs: [fund]`** declares the dependency. On its own it records and validates the ordering — it does **not** spend anything. The `flow_output` reference is what actually moves the UTXO. The step that spends a `flow_output` must declare a sender with `from_ref` and carry a signer for that output's payment key, here the staging account. `from_ref` supplies only the address, not a signature. Preflight doesn't build transactions, so a mistake here surfaces only when the step runs, after earlier steps have moved funds. Note what the file does *not* contain: no addresses baked in, no keys, no network endpoint. `account://sender` is a reference resolved by your application, and `${{ inputs.* }}` are bound at run time. The definition is reusable across environments. ## 2. Run it `FlowRuntime` is the managed entry point — it owns the executors, wires the four backend suppliers, and holds the signer registry, so a first flow does not start with a page of setup: ```java import com.bloxbean.cardano.client.txflow.FlowRuntime; import com.bloxbean.cardano.client.txflow.TxFlow; import com.bloxbean.cardano.client.txflow.codec.FlowParseOptions; import com.bloxbean.cardano.client.txflow.codec.FlowParseResult; import com.bloxbean.cardano.client.txflow.codec.TxFlowCodec; import com.bloxbean.cardano.client.txflow.exec.FlowExecutionHandle; import com.bloxbean.cardano.client.txflow.exec.FlowExecutionRequest; import com.bloxbean.cardano.client.txflow.exec.FlowExecutionResult; import com.bloxbean.cardano.client.txflow.model.FlowBindings; String source = Files.readString(Path.of("funding-flow.yaml"), StandardCharsets.UTF_8); FlowParseResult parsed = TxFlowCodec.standard() .parse(source, FlowParseOptions.serverDefaults()); if (parsed.hasErrors()) { throw new IllegalArgumentException("Invalid flow: " + parsed.getDiagnostics()); } TxFlow definition = parsed.requireFlow(); FlowBindings bindings = FlowBindings.builder() .put("staging", stagingAccount.baseAddress()) .put("beneficiary", receiverAddress) .build(); try (FlowRuntime runtime = FlowRuntime.builder(backendService) .account("account://sender", senderAccount) .account("account://staging", stagingAccount) .build()) { FlowExecutionRequest request = FlowExecutionRequest.builder(definition) .executionId("funding-0001") .bindings(bindings) .build(); FlowExecutionHandle handle = runtime.engine().start(request); FlowExecutionResult result = handle.await(); // await() doesn't throw on a failed flow, so check the state and the error System.out.println(result.state() + " error=" + result.error()); result.steps().forEach(step -> System.out.println(step.getStepId() + " " + step.getTransactionHash())); } ``` `FlowRuntime` implements `AutoCloseable`, so try-with-resources shuts its executors down for you. It creates an ordinary `FlowEngine`, and `runtime.engine()` returns it. `account(...)` registers the signing key against the `account://sender` reference. Note what is *not* here: no `FlowResourceCatalog` and no `FlowExecutionPolicy`. Both are optional — with no catalog the compiler skips resource resolution entirely — which is fine for a script you control. A server accepting flow documents from elsewhere should add both, so the network and the resource prefixes a document may touch are constrained. [Getting started](/txflow/getting-started/) shows that setup. `FlowRuntime` doesn't accept a catalog, policy, store or script registry; use `FlowEngine.builder(...)` for those. Without a catalog there's no spending identity, so concurrent flows from one account aren't serialized, and `spec.network` isn't enforced: a `preview` document runs against whatever backend you pass. ## 3. Check before you run `preflight` compiles and policy-checks every step without submitting anything: ```java FlowCompilationResult preflight = runtime.engine().preflight(request); if (preflight.hasErrors()) { throw new IllegalArgumentException("Preflight failed: " + preflight.getDiagnostics()); } ``` It catches an unbound parameter, a `flow_output` naming a step that does not publish it, and a step ordered after its consumer — all before a transaction reaches the chain. Run it whenever a flow comes from outside your codebase. `start(...)` deliberately compiles again rather than trusting an earlier preflight result, so a definition that changed in between cannot slip through. ## What you get beyond the chaining The same execution already gives you: - **Confirmation tracking** — `min_confirmations` is honoured per step; in the default `SEQUENTIAL` mode the flow doesn't advance on a merely submitted transaction. - **Honest rollback handling** — while a step waits for confirmation, a transaction that disappears isn't treated as success or failure: the flow ends `RECOVERY_REQUIRED`. To detect and act on rollbacks, add `spec.execution.rollback` and a chain adapter that declares authoritative absence; see [Policies](/txflow/policies-results-observability/#submission-and-confirmation-are-separate). `FlowRuntime` can't supply such an adapter. - **Idempotency** — add `.idempotency("payments", "order-18473")` to the request and a repeat call to this engine attaches to the existing execution instead of paying twice. On `FlowRuntime` that claim lives in memory, so it's lost on restart. Surviving a restart needs a [durable store](/txflow/durable-runtime/). - **Honest uncertainty** — an ambiguous submission is reported as uncertain, never as a false success or a false failure. ## Where to go next | You want | Page | |---|---| | The full setup with explicit resources and policy | [Getting started](/txflow/getting-started/) | | Every document field and output-reference form | [Portable authoring](/txflow/portable-authoring/) | | Engine construction, executors, cancellation | [Execution engine](/txflow/execution-engine/) | | Retries, events, confirmation and rollback behaviour | [Policies, results & observability](/txflow/policies-results-observability/) | | Surviving a process restart | [Durable runtime](/txflow/durable-runtime/) | | Many transactions over time, not one workflow | [TxStream](/txstream/getting-started/) | :::note[TxFlow or TxStream?] TxFlow runs **one workflow of related steps** to completion. TxStream submits **a continuous feed of independent items**, each as its own execution on the same engine. A payroll run of 500 unrelated payments is TxStream; a deposit-then-withdraw pair is TxFlow. ::: --- # TxPlan Advanced Usage Source: https://cardano-client.dev/txplan/advanced-usage/ Summary: Signer Registry, script transactions, multi-tx plans, and dynamic configuration > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. ## Signer Registry The `SignerRegistry` keeps private keys and signing logic out of YAML files. Senders, fee payers, collateral payers, and additional signers are referenced by URI-style identifiers that the registry resolves at runtime. ### DefaultSignerRegistry ```java SignerRegistry registry = new DefaultSignerRegistry() .addAccount("account://ops", opsAccount) .addPolicy("policy://nft", nftPolicy); ``` ### Using Registry References in Java ```java Tx tx = new Tx() .fromRef("account://ops") .payToAddress("addr_test1_receiver", Amount.ada(5)); Result result = builder .compose(tx) .withSignerRegistry(registry) .feePayerRef("account://ops") .withSignerRef("account://ops", "payment") // fromRef does NOT auto-add a signer .withSignerRef("policy://nft", "policy") .completeAndWait(); ``` > **Note:** `fromRef()` resolves the sender address but does **not** automatically add a signer. > Use `withSignerRef()` or the YAML `signers` context to provide signing. - `fromRef` defers sender selection to the registry at compose time - `feePayerRef` / `collateralPayerRef` resolve to a wallet or address at compose time - `withSignerRef(ref, scope)` adds additional witnesses (scopes: `payment`, `stake`, `drep`, `committeeCold`, `committeeHot`, `policy`) - Mixing address-based APIs with references is allowed; conflicts raise a `TxBuildException`. In YAML, if a `tx` sets both `from` and `from_ref`, `from_ref` wins silently ### Using Registry References in YAML ```yaml version: 1.0 variables: receiver: addr_test1_receiver_address amount: 5000000 context: fee_payer_ref: account://ops collateral_payer_ref: account://ops signers: - ref: policy://nft scope: policy transaction: - tx: from_ref: account://ops intents: - type: payment address: ${receiver} amounts: - unit: lovelace quantity: ${amount} ``` When any `*_ref` field or `signers` entries are present, supply a compatible `SignerRegistry` at runtime: ```java TxPlan plan = TxPlan.from(yamlString); Result result = builder .compose(plan, registry) .completeAndWait(); ``` If the registry cannot resolve a reference, `QuickTxBuilder` fails fast with a `TxBuildException`. A reference resolves an address only. It doesn't add a signature, so list every key that must sign under `signers`, including the sender's with `scope: payment`. With no signers at all, the build throws `No signers found`. A plan with `from_ref` can be built only once, because the first build sets the plan's sender. Parse the YAML again for each execution, and don't build one plan concurrently. ## Multiple `tx` entries > **Important:** Each `tx` entry in a plan must have a **unique `from` address**. To send multiple payments from the > same address, use multiple intents within a single `tx` entry rather than multiple `tx` entries. A single YAML document can define several `tx` entries. They're merged into one on-chain transaction with one hash. `fee_payer` is mandatory when there's more than one entry, and every sender must sign: ```yaml version: 1.0 variables: treasury: addr_test1_treasury ops: addr_test1_ops pool_operator: addr_test1_pool_op context: fee_payer: ${treasury} valid_to_slot: 5000 transaction: - tx: from: ${treasury} intents: - type: payment address: ${pool_operator} amounts: - unit: lovelace quantity: '500000000' - tx: from: ${ops} intents: - type: payment address: addr_test1_alice amounts: - unit: lovelace quantity: '100000000' ``` ## Script Transactions in YAML With the [Unified Tx API](/quicktx/unified-tx/), all script operations are available in the `tx` block. Script inputs go in the `inputs` section, validator attachments in the `scripts` section: ```yaml transaction: - tx: from: addr_test1_sender change_address: addr_test1_change inputs: - type: script_collect_from utxo_refs: - tx_hash: <64-character transaction hash> output_index: 0 redeemer: constructor: 0 fields: [] intents: - type: payment address: addr_test1_receiver amounts: - unit: lovelace quantity: '10000000' scripts: - type: validator role: spend cbor_hex: "590a01..." version: v2 ``` ### Change Address with Datum For script transactions that need a datum on the change output: ```yaml transaction: - tx: change_address: addr_test1_script change_datum: "590a01a1581c..." # or change_datum_hash: "abc123def..." ``` ## Dynamic Configuration Load a base plan and customize it programmatically for different environments: > **Important:** Variables are resolved during `TxPlan.from()`. Calling `addVariable()` after parsing only updates > the variable map for `toYaml()` serialization — it does **not** re-resolve already-parsed transaction fields. > Set variable values in the YAML string before calling `from()`. ```java // Replace variables in YAML before parsing String customYaml = yamlContent .replace("${fee_payer}", environmentConfig.getFeePayer()) .replace("${amount}", environmentConfig.getAmount()); TxPlan plan = TxPlan.from(customYaml) .validTo(getCurrentSlot() + 3600); // Execute Result result = builder.compose(plan) .withSigner(signerProvider) .completeAndWait(); ``` ## TxPlan with TxFlow TxPlan can be used as the transaction definition within [TxFlow](/txflow/overview/) steps, enabling YAML-defined transactions in multi-step orchestrated flows: Under `FlowEngine`, each plan must hold exactly one `tx`, have no `variables:` block, and declare its signers in `context.signers`. Use flow parameters and `FlowBindings` for per-run values. ```java TxPlan depositPlan = TxPlan.from(depositYaml); FlowStep depositStep = FlowStep.builder("deposit") .withTxPlan(depositPlan) .build(); TxFlow flow = TxFlow.builder("escrow") .addStep(depositStep) .addStep(FlowStep.builder("release") .fundsFrom("deposit") // or .needs("deposit") for ordering only .withTxPlan(TxPlan.from(releaseYaml)) .build()) .build(); ``` ## TxPlan API Reference ### Construction | Method | Description | |--------|-------------| | `TxPlan.from(String yaml)` | Deserialize from YAML string | | `TxPlan.from(AbstractTx tx)` | Create from a single transaction | | `TxPlan.from(List> txs)` | Create from multiple transactions | | `TxPlan.toYaml(AbstractTx tx)` | Convenience: serialize a transaction to YAML | ### Configuration (fluent builder) | Method | Description | |--------|-------------| | `addVariable(key, value)` | Add an entry to the `variables:` block written by `toYaml()`. Doesn't resolve or rewrite transaction values | | `setVariables(map)` | Replace that block | | `additionalSignersCount(count)` | Set `additional_signers_count`, available from 0.8.0-pre7. Throws `IllegalArgumentException` outside 0-1000 | | `addTransaction(tx)` | Add a transaction to the plan | | `feePayer(address)` | Set fee payer address | | `feePayerRef(ref)` | Set fee payer registry reference | | `collateralPayer(address)` | Set collateral payer address | | `collateralPayerRef(ref)` | Set collateral payer registry reference | | `withRequiredSigners(hex...)` | Add required signer credentials | | `withSigner(ref)` | Add a payment-scope signer reference | | `withSigner(ref, scope)` | Add a signer reference with specific scope | | `validFrom(slot)` | Set validity start slot | | `validTo(slot)` | Set validity end slot | | `depositPayer(address)` | Set deposit payer address | | `depositMode(mode)` | Set deposit mode (`AUTO`, `CHANGE_OUTPUT`, etc.) | | `toYaml()` | Serialize to YAML string | --- # TxPlan: Getting Started Source: https://cardano-client.dev/txplan/getting-started/ Summary: Move a transaction out of Java and into a YAML file you can version, review and reuse — in about ten minutes. :::tip[Preview API] TxPlan ships inside the `quicktx` module — no extra dependency. It is stable enough to build on, but its YAML schema may still change before the stable release. ::: A `Tx` written in Java hardcodes the decisions. TxPlan moves them into a YAML document your application loads at run time — so a payout schedule, a treasury operation, or a customer-specific transfer becomes configuration you can review in a pull request rather than code you have to redeploy. **You need**: Java 17+, `cardano-client-lib`, a backend, and a funded test account. If you have not built a transaction with this library yet, do [Your first transaction](/learn/simple-transfer/) first — this page assumes you know what `Tx`, `from` and a signer are. ## The fastest way in: build it, then print it Rather than learning the schema from a reference table, build the transaction you already know how to build and let TxPlan show you its YAML: ```java import com.bloxbean.cardano.client.quicktx.serialization.TxPlan; Tx tx = new Tx() .payToAddress(receiverAddress, Amount.ada(10)) .from(sender.baseAddress()); String yaml = TxPlan.from(tx) .feePayer(sender.baseAddress()) .addVariable("sender", sender.baseAddress()) .addVariable("receiver", receiverAddress) .toYaml(); System.out.println(yaml); ``` `addVariable` only adds entries to the `variables:` block that `toYaml()` writes. It doesn't rewrite the transaction, so addresses and amounts in the output stay literal: ```yaml version: 1.0 variables: receiver: addr_test1... sender: addr_test1... context: fee_payer: addr_test1... transaction: - tx: from: addr_test1... change_address: addr_test1... intents: - type: payment address: addr_test1... amounts: - unit: lovelace quantity: 10000000 ``` ## The plan file To parameterise the file, replace those values with `${...}` by hand. Save the result as `payment.yaml`: ```yaml version: 1.0 variables: sender: addr_test1... receiver: addr_test1... amount: 10000000 context: fee_payer: ${sender} transaction: - tx: from: ${sender} intents: - type: payment address: ${receiver} amounts: - unit: lovelace quantity: ${amount} ``` Four parts, and it is worth knowing which is which: - **`variables`** — the values every `${...}` placeholder resolves against. - **`context`** — settings that apply to the whole plan and map onto `QuickTxBuilder`: `fee_payer`, `fee_payer_ref`, `collateral_payer`, `collateral_payer_ref`, `signers`, `required_signers`, `valid_from_slot`, `valid_to_slot`, `additional_signers_count`, `deposit_payer`, `deposit_mode`. Note these keys are `snake_case`. - **`transaction`** — a list of `- tx:` entries. A plan can carry several entries, each with a different sender. They are merged into one on-chain transaction with one hash. - **`intents`** — what each transaction actually does. `type: payment` here; minting, delegation, governance and script intents have their own types. ## Load and execute ```java import java.nio.file.Files; import java.nio.file.Path; TxPlan plan = TxPlan.from(Files.readString(Path.of("payment.yaml"))); TxResult result = new QuickTxBuilder(backendService) .compose(plan) .withSigner(SignerProviders.signerFrom(sender)) .completeAndWait(); System.out.println(result.getTxStatus() + " " + result.getTxHash()); ``` `compose(TxPlan)` maps the plan's `context` block onto the builder for you — `fee_payer` becomes the fee payer, `valid_to_slot` becomes the validity bound. You do not repeat them in Java. ## Where runtime values come from This is the one thing worth getting right early. **Variable expansion happens when the YAML is parsed**, against the document's own `variables:` block. `TxPlan.from(yaml)` returns a plan whose transactions are already fully materialised — so calling `addVariable(...)` on that returned plan does **not** change them. `addVariable` populates a plan you are serialising *out*; it does not inject into a plan you have read *in*. To vary values per run, pass them to `TxPlanCodec`: ```java String template = Files.readString(Path.of("payment.yaml")); TxPlan plan = TxPlanCodec.builder().build().fromYaml(template, Map.of( "sender", sender.baseAddress(), "receiver", receiverAddress, "amount", adaToLovelace(10))); ``` Runtime values override the document's `variables:`, and an exact `${name}` keeps the value's native type. `TxPlanCodec` is marked experimental. For addresses and signing keys that are fixed per environment rather than per run, the **signer registry** is cleaner than variables: reference them as `account://ops` and resolve them at compose time with `compose(plan, signerRegistry)`. See [Advanced usage](/txplan/advanced-usage/#signer-registry). ## When TxPlan is the right tool | Situation | TxPlan? | |---|---| | One transaction shape, values differ per call | **Yes** — one plan file, different variables | | Operators need to review transactions before they run | **Yes** — the plan is reviewable text | | An agent or external system decides what to submit | **Yes** — generating config is safer than generating code | | Transaction shape is fixed and known at compile time | No — plain [QuickTx](/quicktx/overview/) is simpler | | Several transactions that depend on each other's outputs | No — that is [TxFlow](/txflow/overview/) | | A continuous stream of transactions over time | No — that is [TxStream](/txstream/getting-started/) | TxPlan describes **what** to submit. It does not orchestrate ordering, dependencies or retries — that is TxFlow's job, and TxFlow can execute plans as its steps. ## Next - [TxPlan overview](/txplan/overview/) — YAML structure, intent types, context properties - [Advanced usage](/txplan/advanced-usage/) — signer registry, script transactions, multi-entry plans - [TxFlow](/txflow/overview/) — when the steps depend on each other --- # TxPlan Overview Source: https://cardano-client.dev/txplan/overview/ Summary: YAML-based transaction definitions with variable substitution > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. TxPlan provides a configuration-driven approach to defining Cardano transactions. Instead of writing Java code for every transaction, you can define transactions in YAML with variable substitution, context properties, and signer registry references — then execute them through the standard `QuickTxBuilder` pipeline. ## Benefits - **Configuration-driven** — define transactions in YAML files, separate from application code - **Variable resolution** — use `${variable}` placeholders for dynamic values - **Context properties** — centralize fee payer, collateral, validity, and signer configuration - **Version control friendly** — human-readable transaction definitions - **No extra dependency** — TxPlan is part of the `quicktx` module ## Dependency TxPlan is included in the `quicktx` module. If you already use `cardano-client-lib` or `cardano-client-quicktx`, no additional dependency is needed. ```gradle dependencies { implementation 'com.bloxbean.cardano:cardano-client-lib:' } ``` ## Creating a TxPlan ### From a Tx Object ```java Tx tx = new Tx() .from("addr_test1...") .payToAddress("addr_test1_receiver", Amount.ada(5)); TxPlan plan = TxPlan.from(tx) .feePayer("addr_test1_fee_payer") .validTo(2000L); // Serialize to YAML String yaml = plan.toYaml(); ``` ### From YAML ```java TxPlan plan = TxPlan.from(yamlString); // Access transactions List> transactions = plan.getTxs(); ``` ## YAML Format ### Basic Structure ```yaml version: 1.0 variables: sender: addr_test1_sender_address receiver: addr_test1_receiver_address amount: 5000000 context: fee_payer: ${sender} valid_to_slot: 5000 transaction: - tx: from: ${sender} intents: - type: payment address: ${receiver} amounts: - unit: lovelace quantity: ${amount} ``` ### Variables The `variables` section defines key-value pairs that can be referenced anywhere in the YAML using `${variable_name}` syntax. Variables are resolved before deserialization, on the raw text. An undefined `${x}`, even in a comment, fails with `Variable not found: x`, and a value containing YAML syntax such as `: ` or `#` can change the document's structure. ```yaml variables: treasury: addr_test1_treasury alice: addr_test1_alice payment_amount: 10000000 pool_id: pool1abc123 ``` ### Context Properties The `context` section configures transaction-level settings that apply to the `QuickTxBuilder.TxContext`. | Property | Description | |----------|-------------| | `fee_payer` | Address that pays transaction fees | | `fee_payer_ref` | Registry reference for fee payer (e.g., `account://ops`) | | `collateral_payer` | Address that provides collateral for script transactions | | `collateral_payer_ref` | Registry reference for collateral payer | | `signers` | Array of `{ref, scope}` entries for additional signers | | `required_signers` | List of required signer credential hashes (hex) | | `valid_from_slot` | Transaction validity start slot | | `valid_to_slot` | Transaction validity end slot | | `additional_signers_count` | Extra signers to count in fee estimation when they sign later, such as a multisig. Integer from 0 to 1000. Available from 0.8.0-pre7 | | `deposit_payer` | Address that pays deposit (e.g., for stake registration) | | `deposit_mode` | Deposit handling mode (`AUTO`, `CHANGE_OUTPUT`, `ANY_OUTPUT`, `NEW_UTXO_SELECTION`) | ### Transaction Entries Each entry in the `transaction` list contains a `tx` block with: - `from` / `from_ref` — sender address or registry reference - `change_address` — optional explicit change address - `change_datum` / `change_datum_hash` — datum for change output (script transactions) - `inputs` — inputs to consume or reference: `script_collect_from`, `collect_from`, `reference_input` - `intents` — what the transaction does (see below) - `scripts` — script attachments: `validator`, `native_script` Intent types: | Area | Types | |------|-------| | Core | `payment`, `donation`, `minting`, `script_minting`, `metadata` | | Staking | `stake_registration`, `stake_deregistration`, `stake_delegation`, `stake_withdrawal` | | Pools | `pool_registration`, `pool_update`, `pool_retirement` | | Governance | `drep_registration`, `drep_deregistration`, `drep_update`, `governance_proposal`, `voting`, `voting_delegation` | Unknown keys under the document root, `tx` or `context` fail, but unknown fields inside an intent are dropped silently, so check your spelling there. ### Plans with extensions `TxPlan.from` and `toYaml` handle core intents only. For extension intents, such as [programmable tokens](/learn/cip113-programmable-tokens/), use `TxPlanCodec`: ```java TxPlanCodec codec = TxPlanCodec.builder().withExtension(extension).build(); String yaml = codec.toYaml(plan); TxPlan restored = codec.fromYaml(yaml, runtimeVariables); new QuickTxBuilder(backend).withExtension(extension).compose(restored); ``` ## QuickTxBuilder Integration ### Direct Composition ```java QuickTxBuilder builder = new QuickTxBuilder(backendService); TxPlan plan = TxPlan.from(yamlString); TxResult result = builder.compose(plan) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(); ``` ### With Variables > **Important:** Variables are resolved during `TxPlan.from()` — calling `addVariable()` after parsing only updates > the variable map for `toYaml()` serialization. It does **not** re-resolve already-parsed transaction fields. To use dynamic variables, set them in the YAML string before calling `from()`: ```java String yaml = baseYaml .replace("${amount}", "10000000") .replace("${receiver}", dynamicReceiverAddress); TxPlan plan = TxPlan.from(yaml); ``` Alternatively, define variables in the YAML `variables` section with their final values before parsing. ## Complete Example ```java String yamlPlan = """ version: 1.0 variables: sender: addr_test1_sender receiver: addr_test1_receiver context: fee_payer: ${sender} valid_to_slot: 5000 transaction: - tx: from: ${sender} intents: - type: payment address: ${receiver} amounts: - unit: lovelace quantity: '5000000' """; QuickTxBuilder builder = new QuickTxBuilder(backendService); TxPlan plan = TxPlan.from(yamlPlan); TxResult result = builder.compose(plan) .withSigner(SignerProviders.signerFrom(account)) .completeAndWait(); ``` --- # TxStream: Durability & Exactly-Once Source: https://cardano-client.dev/txstream/durability/ Summary: Durable store pairing, restart re-attach, dedup scopes, secrets policy, and active/standby HA :::caution[Experimental] TxStream is experimental, and its APIs and behaviour may change. See [known issues](/project/known-issues/#txstream) before relying on it. ::: TxStream is experimental. The default managed runtime is process-local; it does not provide crash-durable acceptance. Durable store pairing persists planned execution requests and supports reattachment, but it is not yet a general exactly-once queue consumer. **Current preview contract:** - Shipped durable stores atomically register or match item identity and content. Identical redelivery after live eviction or restart attaches to stored work; different content conflicts. Matching includes the lane name: changing lane assignment across deployments can conflict even with an unchanged payload. Matching never plans another execution. - Stored terminal outcomes are returned directly. Healthy foreign-item reads are detached observations: no live item, claim, counter, event, or owner projection write. A missing or running engine snapshot never establishes recovery authority. Explicit foreign receipt attachment retains a local observation; use `reconcile` or the observer to refresh it. Stored `RECOVERY_REQUIRED` items with persisted plans may be hydrated and terminally repaired from their original engine truth. - A registration without a recoverable plan is not automatically replayed. Redelivery returns `REJECTED` / `TXSTREAM_REGISTRATION_INCOMPLETE`; a projected accepted item found incomplete on restart remains `RECOVERY_REQUIRED` with `TXSTREAM_ABANDONED`. Wait for an accepting owner still processing it, or investigate the registration, engine claim, and source journal. Do not generate a replacement ID. - Custom stores must implement `registerOrMatch` and coherent `getStoredProjection` reads to provide the same guarantees. The compatibility defaults retain legacy registration behavior. Shipped stores resolve a plan through its item binding; custom stores can override the lookup and paging compatibility fallbacks for efficient reads, and must explicitly implement atomic abandoned acknowledgement. Item IDs must be unique across streams sharing a store: registration keys remain store-scoped in this preview schema. - `perWindow()` and `batching()` deduplicate exact flow member sets, not arbitrary individual redeliveries after their registration history is removed. Use `perItem()` for independently redelivered work and retain durable registrations. This is a conservative preview implementation of the registration/hydration portion of ADR 0006. It does not implement automatic recovery of accepted-but-unplanned work, change the registration key schema, or provide active/active wallet ownership. Existing terminal `CANCELLED` / `TXSTREAM_ABANDONED` rows from older builds stay terminal and attach as cancelled; upgrades never replay them automatically. ## The durable pairing Durable mode is a *pairing*, validated at `build()`: - a **durable `FlowEngine` store** (`engine.capabilities().durableExecution()` — e.g. `RdbmsFlowExecutionStore`), plus the engine's `maintenanceExecutor`, which the engine requires whenever a store is configured (lease renewal must not be starved by flow work); - a **durable `TxStreamStateStore`** (`RdbmsTxStreamStateStore`, or `TxStreamStateStore.inMemoryDurable()` for tests and same-process restarts). The builder **rejects** a durable stream store paired with a non-durable engine. This invariant is load-bearing, not pedantic: restart re-attach reasons *"no stored execution means the start never happened, so re-dispatch the persisted plan"*. Against an engine that forgot its executions on crash, that reasoning re-dispatches executions that already ran — a transaction duplicator. The reverse pairing (durable engine, non-durable stream store) is legal: you get durable executions without stream-level re-attach. The simplest durable stream (in-memory durable store — survives stream restarts within one process, not process death): ```java TxStreamStateStore durableStore = TxStreamStateStore.inMemoryDurable(); // reuse across restarts TxFlowStream stream = TxFlowStream.builder("payouts", durableEngine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .stateStore(durableStore) .executor(streamExecutor) .open(); // re-attach runs BEFORE new work is accepted ReattachReport report = stream.reattach(); // idempotent: returns the same report System.out.println("re-attached=" + report.reattachedItems() + ", re-dispatched=" + report.redispatched() + ", recovery-required=" + report.recoveryRequired()); ``` ## Relational store setup For real crash durability use the relational stream store from the same optional module as the engine's RDBMS store: ```gradle dependencies { implementation "com.bloxbean.cardano:cardano-client-txflow-store-rdbms:${cclVersion}" } ``` ```java // Both stores can share ONE database: the engine store owns txflow_* // tables, the stream store owns txstream_* tables — the two migrations // never see each other's objects. RdbmsFlowExecutionStore executionStore = RdbmsFlowExecutionStore.builder() .jdbcUrl("jdbc:h2:file:./data/txflow") .build(); RdbmsTxStreamStateStore streamStore = RdbmsTxStreamStateStore.builder() .jdbcUrl("jdbc:h2:file:./data/txflow") .build(); FlowEngine engine = FlowEngine.builder(backendService) .executor(engineExecutor) .maintenanceExecutor(maintenanceExecutor) // a ScheduledExecutorService .store(executionStore) // durable engine execution .signerRegistry(signers) .build(); try (TxFlowStream stream = TxFlowStream.builder("payouts", engine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .stateStore(streamStore) // durable stream planning metadata .executor(streamExecutor) .open()) { // re-attach resolves persisted in-flight items first // ... submit work ... } ``` For PostgreSQL, mirror the engine store's production profile — deployment-owned pooling and validated schema: ```java RdbmsTxStreamStateStore streamStore = RdbmsTxStreamStateStore.builder() .dataSource(dataSource) .dialect(PostgresDialect.INSTANCE) .schemaManagement(SchemaManagement.VALIDATE) .build(); ``` The same operational guidance as the engine store applies (migration with a schema-owner role, `VALIDATE` at runtime, one transactionally consistent backup unit); see [Durable Runtime](/txflow/durable-runtime/) and the RDBMS module README. A durable store ignores the stream's retention eviction and keeps settled items indefinitely. This preserves status history and enables identical redelivery to attach after live eviction. Receipt hydration does not increment acceptance counters or create another execution. ## What start() does in durable mode Before accepting any new work, `start()` resolves every persisted non-terminal item binding against engine truth (`reattach()`): 1. **A `DISPATCHING` binding** (crash between the write-ahead bind and the engine-start confirmation) is resolved by asking the engine store for the binding's execution id: present means the start happened; absent means it never did. 2. **Present execution** — the item is *re-projected* from the engine snapshot, never re-run. A crash-recovered item whose snapshot says completed goes straight to `CONFIRMED` with its hash; one whose execution is still running is surfaced `RECOVERY_REQUIRED` and refreshed by read-through. 3. **Absent execution** — the item is *re-dispatched* from its persisted portable plan. Because execution ids are deterministic (derived from the idempotency claim), the re-dispatch carries the same execution id on every process and every retry: if the execution somehow does exist, the engine answers `MATCHED` and returns the stored execution instead of running a duplicate. Double-submission is structurally prevented by the engine claim, not by stream bookkeeping. 4. Items accepted without a persisted plan cannot be automatically resumed. Restart reports projected incomplete work as recovery-required; same-content redelivery reports `TXSTREAM_REGISTRATION_INCOMPLETE`, not a content conflict. Use an explicit recovery procedure rather than a replacement ID. `ReattachReport` summarizes the pass (`reattachedItems`, `redispatched`, `recoveryRequired`). Restart inventories all unresolved work. An absent binding alone does not authorize same-ID reacceptance: another owner may still have the accepted item queued before its write-ahead binding. For an abandoned registration, stop or fence **all producers and recovery workers** and investigate the source journal, original engine claim, binding, and transaction hash first. Then read `getStoredProjection` to obtain the exact sequence expected by `acknowledgeAbandoned`: ```java TxStreamStoredProjection observed = store.getStoredProjection("payouts", "payment-1") .orElseThrow(); boolean acknowledged = store.acknowledgeAbandoned( "payouts", "payment-1", observed.sourceSequence(), "Investigated original claim with producers and recovery workers fenced", Instant.now()); // false means the sequence or abandoned state no longer matches; investigate again. ``` Acknowledgement atomically checks `RECOVERY_REQUIRED` / `TXSTREAM_ABANDONED` at that sequence and marks the bookkeeping `FAILED`, retaining registration, binding, and hash. It is **not proof that a transaction failed** and does not authorize a replacement payment. Already-resolved or stale observations return false. Custom stores that do not implement this atomic operation reject it as unsupported. ## Dedup scopes: what "exactly-once" actually covers This is the most important honest section in these docs. The engine supports exactly one idempotency claim per execution, so the dedup scope follows the planner ([planners](/txstream/throughput/#windows-and-planners)): A redelivered item id that the live stream or a durable store still holds attaches to its existing work, under any planner. This table describes what happens once that registration is gone, as with a non-durable stream after eviction or restart. | Planner | Claim covers | Dedup guarantee | | --- | --- | --- | | `perItem()` (default) | one item | **True per-item exactly-once, given a durable engine store.** A redelivered item matches its own execution across restarts and processes. An engine without a store, including `FlowRuntime`, guarantees this only within one process | | `perWindow()` | the window's exact member set (sorted member keys) | **Flow-level only.** An identical window resubmitted whole matches; a single item redelivered into a differently-composed window is a *new claim* and runs again | | `batching(...)` | the merged batch's exact member set | **Flow-level only, and funds-critical** — see below | > **A re-batched payment is a real second payment.** Under `batching(...)`, once the registration > is gone, a single item redelivered into a differently-composed batch produces a different claim key — a new merged > execution — and unlike `perWindow()` (where each item is still its own transaction), the > recipient is **paid twice on chain**. There is no per-item exactly-once under batching, by > design, until a multi-claim engine extension lands. If your source can redeliver individual > items, either use `perItem()` (true per-item dedup) or deduplicate upstream so the same item can > never land in two different batches. `perWindow()` deserves the same caution in weaker form: a redelivered item in a new window runs again as its own transaction — one duplicate payment, not a merged batch's, but still real. **Durable per-item exactly-once requires `perItem()`.** Sources that redeliver must use it or dedup upstream. ## The eviction guard window A non-durable stream retains settled items up to `maxRetainedSettledItems` (default 10,000, FIFO-evicted once settled). This cap doubles as the **duplicate-detection window**: idempotency-key reuse and content-conflict checks hold only while the settled item is retained. After eviction the remaining guarantee is *engine-request equality* on the claim-derived execution: - an **identical** resubmit matches the stored execution and projects the original outcome (still no double-spend); - a resubmit with **different transaction content** fails typed at the engine (`TXFLOW_IDEMPOTENCY_CONFLICT`); - but a resubmit differing **only in metadata, or in a lane label that resolves to the same identity**, silently matches the stored execution — before eviction the same resubmit would have been a typed content conflict. Size the cap to cover your longest realistic redelivery horizon. A durable stream store retains settled items indefinitely. The shipped durable stores compare the retained registration atomically and attach identical redelivery without another execution. ## No secrets in the stream store The durable stream store persists planned requests so it can re-dispatch after a crash — and it must never become a plaintext secret store. The persisted record contains only the portable-encoded flow, non-sensitive bindings, and secure-binding *references* plus value fingerprints. Three binding channels exist on `TxWorkItem`: ```java stream.submit(TxWorkItem.builder("payout-42") .withTemplate("payout") .withIdempotencyKey("order-42") // Non-sensitive: persisted verbatim in the durable planned record. .withBinding("beneficiary", beneficiaryAddress) .withBinding("amount", 1_500_000L) // Sensitive: only the reference and a fingerprint are persisted. // The value isn't resolved for you; see the note below. .withSecureBindingReference("payoutAuthorization", "vault://payouts/authorization-key") .build()); // The UNSAFE inline form. In durable mode this fails the item typed // (TXSTREAM_NON_PERSISTABLE_SECRET) instead of persisting a plaintext secret. TxWorkItem nonDurableOnly = TxWorkItem.builder("payout-43") .withTemplate("payout") .withSensitiveBinding("payoutAuthorization", secretValue) .build(); ``` :::caution[No secure-reference resolver in this preview] CCL records secure references but ships no resolver and doesn't wire one into the engine, so a template parameter that needs a secret value can't currently be supplied through a durable stream. Keep secrets in signer and resource registries, not in flow parameters. ::: In non-durable mode `withSensitiveBinding` is passed through to the engine and never persisted. Note the engine-side caveat from [Durable Runtime](/txflow/durable-runtime/): a secure binding's external reference and an unsalted SHA-256 fingerprint *are* stored — protect the database and backups, especially for low-entropy values. ## Handling RECOVERY_REQUIRED `RECOVERY_REQUIRED` means the transaction's disposition is uncertain — typically submitted but unconfirmed inside a flow that reached a terminal state. It may still confirm. Never treat it as a failure and never resubmit the payment blindly. Three escalating tools: **1. Read-through (needs an engine with a store).** `getItemStatus(itemId)` on a `RECOVERY_REQUIRED` item consults the engine snapshot and repairs the projection when the engine has an authoritative answer; `reconcile(itemId)` forces the same check: ```java Optional status = stream.getItemStatus("payment-1"); if (status.isPresent() && status.get().getStatus() == TxStreamItemStatus.RECOVERY_REQUIRED) { // 1. inspect status.get().getTransactionHash() and look it up on chain; // 2. engine.recover(...) returns a verdict; it doesn't finish the execution; // 3. reconcile repairs only if the stored execution has become terminal, // for example because another live owner finished it: Optional repaired = stream.reconcile("payment-1"); } ``` **2. The reconciliation observer (opt-in, OFF by default).** A periodic pass that push-repairs `RECOVERY_REQUIRED` items with nobody polling. It runs on the caller-owned scheduler — the stream owns no threads or timers: ```java ScheduledExecutorService maintenance = Executors.newSingleThreadScheduledExecutor(); TxFlowStream stream = TxFlowStream.builder("payouts", engine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .stateStore(durableStore) .executor(streamExecutor) .maintenanceExecutor(maintenance) // caller-owned scheduler .reconciliationInterval(Duration.ofSeconds(30)) // opt-in; OFF by default .build(); ``` Repairs are emitted to the `TxStreamEventListener`; `reconciliationBatchSize` (default 100) caps work per pass. Durable candidates are paged with an item-ID cursor; every inspected row consumes budget, including live and abandoned rows. The cursor advances so later recoverable work is not starved by earlier incomplete registrations. **3. Operator recovery.** `engine.recover(...)` verifies the persisted signed payload against the chain and can resubmit only identical bytes. It returns a `FlowRecoveryResult` (`IN_BLOCK`, `SUBMITTED` or `RECOVERY_REQUIRED`) and records it on the attempt, but it doesn't make the execution terminal, so in this preview the stream item stays `RECOVERY_REQUIRED` after recovery. Use the recovery result and an on-chain lookup as your answer, and don't resubmit. See [Durable Runtime](/txflow/durable-runtime/) for the engine recovery workflow. After a hard crash, the dead owner's execution and resource leases stay valid until they expire (30 seconds by default, `FlowEngine.Builder.leaseDuration`). Until then `engine.recover(...)` fails with a lease conflict, and work on that lane fails `TXFLOW_RESOURCE_BUSY`. Wait out the lease before recovering or restarting. A receipt whose `completion()` already fired with `RECOVERY_REQUIRED` keeps that point-in-time answer (futures complete once); the live projection reflects the repair. ## Multi-instance HA: active/standby ownership Ownership (ADR 0004 iteration 3d) lets two or more instances share one `streamId` for failover: exactly one holds an epoch-fenced lease and dispatches (`ACTIVE`); the rest are `STANDBY` and take over on the owner's crash or lease expiry. Requirements, all validated at `build()`: a durable stream store that `supportsOwnership()`, a durable engine store, and a `maintenanceExecutor` for lease renewal. ```java TxFlowStream instance = TxFlowStream.builder("payouts", engine) // same streamId everywhere .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .stateStore(sharedDurableStore) // shared by all instances .executor(streamExecutor) .maintenanceExecutor(maintenance) // lease renewal / acquire-poll .ownership("host-a:1234", Duration.ofSeconds(30)) // stable per-instance token .build(); instance.start(); OwnershipStatus ownership = instance.ownership(); if (ownership.isActive()) { // this instance holds the epoch-fenced lease and dispatches } ``` Semantics to plan around: - **The lease is a best-effort optimization; the engine claim is the real guarantee.** Even if two instances momentarily believed themselves active, the deterministic execution ids and the engine's idempotency claims are what make double-submission structurally impossible. The lease exists to prevent wasted contention, not to be the safety mechanism. - **Step-down loses queued-but-unstarted work, deliberately.** On a fenced step-down, work that had not yet dispatched settles `CANCELLED` with `TXSTREAM_OWNERSHIP_LOST`. Those item ids are terminally cancelled in the shared durable store, so recover the work by resubmitting it to the new owner **under a new item id**. Redelivering identical content under the old id doesn't run it; it returns a settled `CANCELLED` receipt. Different content throws `TxStreamDuplicateItemException`. In-flight engine executions the old owner already started are never aborted; the new owner re-attaches and reconciles them. - **A standby is paused, not dead.** While `STANDBY`, blocking `submit` refuses typed (`TXSTREAM_NOT_ACTIVE`), but `trySubmit` reports `PAUSED` — a retryable condition. The `TxWorkSource.fromPublisher(...)` adapter parks items on `PAUSED` and resumes them if the instance reclaims ownership; custom producers should do the same rather than tearing down. A fenced renewal throws `TXSTREAM_OWNERSHIP_FENCED` internally and the instance stops dispatching immediately. - **A standby keeps reconciling, read-only.** The reconciliation observer continues running on a standby (repair-only writes, CAS-arbitrated against the active owner's) — it never dispatches. - Active/active lane-partitioned ownership (instances owning disjoint lane subsets) is a future extension, not this iteration. ## Continue - [TxStream Getting Started](/txstream/getting-started/) — the front door, item identity, and lifecycle. - [TxStream: Lanes, Batching & Throughput](/txstream/throughput/) — the concurrency model these guarantees compose with. --- # TxStream Getting Started Source: https://cardano-client.dev/txstream/getting-started/ Summary: Continuous, idempotent transaction submission on the FlowEngine runtime :::caution[Experimental] TxStream is experimental, and its APIs and behaviour may change. See [known issues](/project/known-issues/#txstream) before relying on it. ::: :::tip[New to TxStream? Start here] **The problem:** you have many transactions to submit over time — a payout queue, an outbox table, a message stream — and each must land **once**, even when the upstream queue redelivers the same item. Within one running process, `FlowRuntime` gives you this. It keeps its history in memory, so after a process restart a redelivered item is new work and pays again. To survive crashes, pair a durable engine store with a durable stream store and `perItem()`; see [Durability](/txstream/durability/). **The shape of the answer:** you open a stream, submit work items with a business id you already have (an order id, a message id), and each item becomes its own idempotent execution. Redelivering an identical item attaches to the existing work instead of paying twice. Jump straight to [A minimal working stream](#a-minimal-working-stream) for the shortest version, then come back for the guarantees below. Submitting a *single* transaction? Use [QuickTx](/quicktx/overview/). Several transactions forming *one* workflow? Use [TxFlow](/txflow/your-first-flow/). ::: TxStream (`TxFlowStream`) is txflow's streaming submission API: it accepts a continuous feed of transaction work items, executes each one as an idempotent `FlowEngine` execution, and reports progress as an honest projection of engine truth. Items carry an idempotency key, so redelivery from a queue or a retrying caller attaches to the existing work instead of paying twice; statuses are never asserted ahead of the chain (`SUBMITTED` means the backend saw the transaction, and an uncertain outcome is reported as `RECOVERY_REQUIRED`, never as a false failure); and every accepted item settles a receipt in bounded time. **When to use it:** reach for TxStream when you are submitting many transactions over time and care that each lands exactly once and survives failures (crash survival needs the durable stores, not plain `FlowRuntime`); reach for QuickTx when you are building a single transaction, and for a `TxFlow` definition plus `FlowEngine` when several transactions form one multi-step workflow. TxStream is not the right tool for one-off transactions, for on-chain atomic multi-party logic (that is a contract), or for latency-critical paths — confirmation latency dominates; this is a throughput and reliability tool. > **Status:** TxStream ships with txflow in the [latest preview](/start-here/installation/#versions) and is > experimental; see [known issues](/project/known-issues/#txstream). The scenarios below are validated end-to-end on a live Yaci DevKit devnet, > but sustained-load soak testing has not been done. Java 17 is the minimum. Direct engine/stream > builders use caller-owned executors; the optional `FlowRuntime` below explicitly owns its > documented executors for you. ## Add the dependencies TxStream is part of the `cardano-client-txflow` module — no extra dependency beyond txflow itself: ```gradle def cclVersion = '' repositories { mavenCentral() } dependencies { implementation "com.bloxbean.cardano:cardano-client-txflow:${cclVersion}" // Choose a backend implementation. BFBackendService can target a compatible Yaci endpoint. implementation "com.bloxbean.cardano:cardano-client-backend-blockfrost:${cclVersion}" } ``` For crash-durable streams add the optional relational store module; see [TxStream: Durability & Exactly-Once](/txstream/durability/). ## A minimal working stream For a script, CLI, or small application, `FlowRuntime` is the shortest safe front door. It owns one ordinary `FlowEngine`, its task and maintenance executors, and every stream opened through it. The example assumes you already have a backend, funded sender account, and receiver address: ```java try (FlowRuntime runtime = FlowRuntime.builder(backend) .account("account://sender", sender) .build(); TxFlowStream stream = runtime.open("payouts")) { TxPlan plan = TxPlan.from(new Tx() .payToAddress(receiverAddress, Amount.ada(2)) .fromRef("account://sender")) .withSigner("account://sender"); TxStreamItemResult result = stream.submit("order-0042", plan) .awaitConfirmed(Duration.ofMinutes(5)); } ``` What the pieces do: - **`FlowRuntime`** owns one normal engine and its executors; `runtime.engine()` exposes that exact engine for ordinary `TxFlow` execution. Advanced/server applications can construct the engine and stream directly for explicit resource ownership or durable configuration. - **`streamId` (`"payouts"`)** defines the idempotency namespace `stream:payouts`. Keep it stable: it is part of every item's engine claim. - **The default lane policy** derives a syntactic lane from each plan's `from` or `from_ref`. Same-source items serialize; different sources can run concurrently. Do not mix address and reference forms for one wallet within a stream. - **`TxPlan` payloads** are portable QuickTx plans. A `TxPlan` needs `.withSigner(...)` naming a reference registered by `FlowRuntime.Builder.account(...)`; keys never travel inside items. A plan with no `from` or `from_ref` is rejected at submit with `TXSTREAM_LANE_UNDERIVABLE`. `runtime.open(id)` takes no options. For windows, planners or a custom lane policy, use `TxFlowStream.builder(id, runtime.engine())`. The runtime doesn't track that stream, so close it before the runtime. ## Item identity and idempotency Every item has two identities: - **`itemId`** — the caller-visible handle for receipts, `getItemStatus(...)`, and cancellation. - **`idempotencyKey`** — the business identity that defines the engine claim (defaults to the `itemId` when unset). Use the upstream identity you already have: an order id, a message id, an outbox row id. Redelivery is resolved by content, not blanket rejection: ```java TxStreamReceipt first = stream.submit(TxWorkItem.builder("payment-1") .withTxPlan(plan) .withIdempotencyKey("order-1") .build()); // Redelivery with identical content attaches: the SAME receipt is returned. TxStreamReceipt redelivered = stream.submit(TxWorkItem.builder("payment-1") .withTxPlan(plan) .withIdempotencyKey("order-1") .build()); assert first == redelivered; // The same item id with different content is a typed conflict, never a // silent replacement. try { stream.submit(TxWorkItem.builder("payment-1") .withTxPlan(differentPlan) .withIdempotencyKey("order-1") .build()); } catch (TxStreamDuplicateItemException conflict) { // "payment-1" already means something else; decide, don't guess } ``` The redelivery fingerprint covers everything a planner can see — payload content, lane, idempotency key, metadata, and bindings — so "identical" means identical. Reusing an idempotency key under a *different* item id is rejected typed (`TXSTREAM_IDEMPOTENCY_KEY_REUSE`): redelivery must reuse the original item id. > **Guard window:** duplicate detection holds while the settled item is retained > (`maxRetainedSettledItems`, default 10,000). After eviction the engine claim is still the > exactly-once guard for identical resubmits, but the stream-level content-conflict check has > lapsed — see [the eviction guard window](/txstream/durability/#the-eviction-guard-window) for > the exact post-eviction guarantees. Shipped durable stores retain registration history and > attach identical redelivery after eviction; incomplete registrations require explicit recovery. For non-blocking producers, `trySubmit` mirrors `submit` without throwing and without blocking on a full buffer: ```java EmitResult result = stream.trySubmit(item); switch (result.getStatus()) { case OK: case DUPLICATE_ATTACHED: TxStreamReceipt receipt = result.getReceipt(); break; case FULL: // bounded buffer full — retry later break; case CONFLICT: // same item id, different content: result.getConflict() break; case REJECTED: // typed cause in result.getRejection() break; case PAUSED: // ownership standby — park the item and retry; not terminal break; case CLOSED: // not started, draining, closed, or unhealthy break; } ``` An item that fails eager validation is not accepted. Blocking `submit` throws the typed cause; `trySubmit` returns `REJECTED`. No receipt, retained item, accepted/failed counter, or engine execution is created, so corrected content may reuse the same stable item ID. ## Reading results For synchronous code, `awaitConfirmed(timeout)` returns only `CONFIRMED`. It classifies failure, cancellation, and uncertainty from the latest live projection and never performs hidden reconciliation: ```java try { return stream.submit("order-0042", plan) .awaitConfirmed(Duration.ofMinutes(5)); } catch (TxStreamUncertainException uncertain) { // DO NOT RESUBMIT: the transaction may still land. Look its hash up on chain. String txHash = uncertain.result().getTransactionHash(); // may be null throw uncertain; } catch (TxStreamTimeoutException timeout) { // A caller wait expired; inspect timeout.result(). Timeout is not failure. throw timeout; } catch (TxStreamCancelledException cancelled) { throw cancelled; } catch (TxStreamFailedException failed) { throw failed; } ``` `stream.awaitResolution(itemId, timeout, interval)` can change that answer only on an engine with a `FlowExecutionStore`, once the stored execution is terminal. Otherwise it ends with `TxStreamTimeoutException`, so don't rely on it with plain `FlowRuntime`. `receipt.completion()` remains the non-cancelling asynchronous stage and `awaitSettled()` is the blocking API for callers that intentionally branch over every settled state. Both can expose `RECOVERY_REQUIRED`. `getItemStatus(itemId)` returns the live projection at any time: ```java // Live projection; read-through repair needs an engine with a store: Optional current = stream.getItemStatus(receipt.itemId()); TxStreamStats stats = stream.getStats(); long confirmed = stats.confirmedItemCount(); int inFlight = stats.inFlightCount(); ``` | Status | Meaning | Final? | | --- | --- | --- | | `ACCEPTED` | The stream accepted the item and returned a receipt (the only stream-owned state) | no | | `PLANNED` | The item's execution binding was recorded and dispatch began | no | | `SUBMITTED` | The engine observed the transaction submission — never asserted in advance of the backend | no | | `CONFIRMED` | The item's transaction confirmed on chain | yes | | `FAILED` | Conclusive failure; when a transaction was submitted first, its hash is retained | yes | | `CANCELLED` | Cancelled before producing a confirmed transaction | yes | | `RECOVERY_REQUIRED` | Disposition uncertain — typically submitted but unconfirmed inside a terminal flow. **The transaction may still confirm: reconcile, do not retry blindly.** Settles the receipt as a point-in-time answer. `getItemStatus`/`reconcile` can repair it only on an engine with a store, once the stored execution is terminal | settles the receipt | Two projection guarantees are worth internalizing: a transaction hash, once known, is never dropped from a later snapshot, and a submitted-but-unconfirmed transaction is never reported `FAILED` — that is exactly what `RECOVERY_REQUIRED` exists for. [Durability & Exactly-Once](/txstream/durability/#handling-recovery_required) covers the repair workflow. ## Template items Besides `TxPlan` and portable `FlowStep` payloads, a stream can register one parameterized portable flow at build time and accept a stream of invocations — useful for bulk operations that are the same flow with different bindings. This template shape ran green on the devnet: ```yaml api_version: txflow.cardano-client.dev/v1alpha1 kind: TxFlow metadata: {name: payout-template} spec: parameters: beneficiary: {type: address, required: true} amount: {type: integer, required: true} steps: - id: payment transaction: tx: from_ref: account://sender intents: - type: payment address: '${{ inputs.beneficiary }}' amounts: - unit: lovelace quantity: '${{ inputs.amount }}' context: signers: - ref: account://sender fee_payer_ref: account://sender ``` With that document in `yaml` (a string or a file read): ```java TxFlow payoutTemplate = TxFlowCodec.standard() .parse(yaml, FlowParseOptions.serverDefaults()) .requireFlow(); try (TxFlowStream stream = TxFlowStream.builder("payouts-template", engine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .template("payout", payoutTemplate) .executor(streamExecutor) .open()) { stream.submit(TxWorkItem.builder("payout-1") .withTemplate("payout") .withIdempotencyKey("payout-order-1") .withBinding("beneficiary", receiverAddress) .withBinding("amount", 1_500_000L) .build()); stream.drain(); } ``` The template is compiled, validated, and fingerprinted once at `build()`; an item referencing an unregistered id fails typed `TXSTREAM_TEMPLATE_UNKNOWN`. Redelivery of the same (template, bindings) attaches like any other item. A durable stream must re-register the same definition under the same id across restarts; a changed definition fails a re-attached item typed `TXSTREAM_TEMPLATE_DRIFT` rather than silently running the wrong flow. ## Lifecycle ```java stream.flush(); // close the open window early stream.drain(); // stop accepting; settle every accepted item stream.awaitDrain(Duration.ofMinutes(5)); // drain with a deadline stream.close(); // graceful: drain, then release stream.close(Duration.ofSeconds(30)); // drain until deadline, then abort the rest AbortReport report = stream.abort("shutting down"); report.quiescence().toCompletableFuture().join(); // signalled executions settled ``` - `open()` builds and starts with abort-on-startup-failure cleanup. `build(); start()` remains for advanced wiring between construction and startup. A closed stream cannot be restarted. - `drain()` awaits every accepted item's completion promise — not queue emptiness heuristics — so no accepted item can be left out. `awaitDrain(timeout)` throws a typed `TxStreamTimeoutException` (`TXSTREAM_TIMEOUT`) when the deadline elapses first. - `close()` (and try-with-resources) is graceful: it drains, then releases. Nothing is cancelled. - `abort(reason)` is forced but honest: buffered items settle `CANCELLED`, in-flight executions receive a *cooperative* cancellation signal — they may still run to completion, and their receipts settle with the real outcome (possibly `CONFIRMED`). `AbortReport.quiescence()` completes when the last signalled execution terminates. - `cancelItem(itemId, reason)` cancels one item with a typed outcome; a member of a shared multi-item flow is rejected rather than silently cancelling its neighbours — escalate with `cancelExecution(executionId, reason)` when cancelling the whole flow is intended. ## Where to next - [TxStream: Durability & Exactly-Once](/txstream/durability/) — the durable store pairing, restart re-attach, dedup scopes (read this before using multi-item planners), secrets policy, `RECOVERY_REQUIRED` handling, and active/standby HA. - [TxStream: Lanes, Batching & Throughput](/txstream/throughput/) — the UTXO concurrency model, lane policies, windows and planners, partitioned fan-out, and honest throughput expectations. - [TxStream: Smart Contracts](/txstream/smart-contracts/) — an order-escrow settlement tutorial with a real Aiken vault: the portable script surface (`collectFrom` + validator attachment), idempotent claims, and serializing contract-UTXO contention on one lane. ## Preview operating limits - `FlowRuntime` retains at most **10,000 engine idempotency claims across all its streams**, including completed executions. Receipt eviction does not reclaim those claims. Configure `.maxInMemoryIdempotencyClaims(n)` for a larger bounded job; use a directly configured engine and an appropriate store for long-lived workloads. Exhaustion reports `TXFLOW_IDEMPOTENCY_CAPACITY_EXCEEDED`. Recreating the runtime discards its process-local history and is not a safe capacity workaround. - Acceptance snapshots transaction plans, including a `FlowStep`'s plan. Later mutation of your `Tx` or `TxPlan` cannot change queued work. Do not mutate during submission; redelivery must carry the original content. - `FlowEngine.builder(backend)` and `FlowRuntime` enable backend output-visibility checks between executions on the same lane. A previous confirmed or uncertain transaction must expose output zero before the next execution starts. The stream waits up to 60 seconds, polling every 2 seconds; advanced builders can configure `.backendVisibility(timeout, pollInterval)`. `TXSTREAM_BACKEND_NOT_READY` fails the new item before engine start and does not change the previous result. - Custom four-supplier engines can opt in with `.backendVisibilityChecks(true)`. The UTxO supplier must support historical `getTxOutput` lookup. Visibility is an indexing check, not a proof that every backend endpoint is consistent. Configure backend I/O timeouts and qualify the provider you use. The check covers consecutive executions in this live stream; it does not coordinate external wallet spenders. - Durable queue processing and transparent crash redelivery remain advanced, experimental capabilities. Read the durability restrictions before enabling them. --- # TxStream: Smart Contracts Source: https://cardano-client.dev/txstream/smart-contracts/ Summary: Order-escrow settlement with a real Aiken vault: locking and claiming contract UTXOs through the stream, idempotently, with contract contention serialized on one lane :::caution[Experimental] TxStream is experimental, and its APIs and behaviour may change. See [known issues](/project/known-issues/#txstream) before relying on it. ::: This tutorial builds an **order-escrow settlement service** on TxStream with a real Aiken validator. Funds for each order are locked at a vault script with an inline datum naming the operator; when a fulfillment event arrives (a queue message), the service claims the order's escrow UTXO through the stream. The two things the stream adds over hand-rolled contract submission are exactly the two things that hurt most in production: - **Idempotent settlement** — the fulfillment event id is the item's idempotency key, so a redelivered event *attaches* to the existing claim instead of double-claiming the escrow. - **Serialized contract contention** — every claim against the vault rides ONE explicit lane keyed on the contract, so concurrent fulfillment events settle FIFO instead of racing to spend the same contract UTXOs (the eUTXO race everyone otherwise hand-rolls with locks and retries). Everything below is lifted from the shipped integration test `txflow/src/it/java/com/bloxbean/cardano/client/txflow/stream/TxFlowStreamContractIntegrationTest.java`, which runs this exact scenario against a Yaci DevKit devnet. ## The vault validator The contract is the repo's `address_check` Aiken validator (`annotation-processor/src/it/resources/blueprint/address_check/validators/address_check.ak`) — an escrow-in-miniature: ```rust use aiken/collection/list use cardano/address.{Address, VerificationKey} use cardano/transaction.{OutputReference, Transaction} pub type Vault { admin: Address, } pub type Redeemer { marker: ByteArray, } validator address_check { spend( datum: Option, _redeemer: Redeemer, _own_ref: OutputReference, self: Transaction, ) { expect Some(datum) = datum expect VerificationKey(vk_hash) = datum.admin.payment_credential list.has(self.extra_signatories, vk_hash) } } ``` - **Datum** `Vault { admin: Address }`: each escrow UTXO carries, inline, the address of the operator allowed to release it. An Aiken `Address` is a constr structure — a payment credential (`VerificationKey(hash)` or `Script(hash)`) plus an optional stake credential. - **Redeemer** `Redeemer { marker: ByteArray }`: a trivial one-field constr; the validator ignores its content. - **The check**: spending an escrow UTXO is valid only when the admin's verification-key hash is in the transaction's `extra_signatories` — i.e. the transaction *declares the admin as a required signer* and the admin actually signs. Compiling with `aiken build` produces a [CIP-57](https://cips.cardano.org/cip/CIP-57) blueprint (`plutus.json`) whose validator entry carries the `compiledCode` hex and `plutusVersion` (`v3` here). The tutorial loads the compiled code straight from that blueprint — no Aiken toolchain needed at runtime: ```java // Load the vault validator from the blueprint's compiledCode and derive // its script address. vaultScript = PlutusBlueprintUtil.getPlutusScriptFromCompiledCode( VAULT_COMPILED_CODE, PlutusVersion.v3); scriptAddress = AddressProvider.getEntAddress(vaultScript, Networks.testnet()).toBech32(); // ONE lane per contract: every claim against this vault shares the // lane, so contended escrow UTXOs are settled FIFO instead of racing. escrowLane = "escrow:" + scriptAddress; ``` `VAULT_COMPILED_CODE` is the blueprint's `compiledCode` string. `PlutusBlueprintUtil.getPlutusScriptFromCompiledCode` applies the double-CBOR wrapping CCL's `PlutusScript.cborHex` expects — pass the blueprint hex as-is, never pre-wrap it yourself. The datum and redeemer are plain `ConstrPlutusData`. The `Address` constr shape below matches the blueprint-generated converters proven on devnet by the annotation-processor's `AddressCheckDevnetTest` — getting this encoding right is the one genuinely fiddly part, so copy it rather than re-deriving it: ```java /** * Datum {@code Vault { admin: Address }}. An Aiken {@code Address} is a * constr structure — the shape below matches the generated blueprint * converters proven on devnet by AddressCheckDevnetTest: * Vault = Constr 0 [Address]; * Address = Constr 0 [PaymentCredential, Option<StakeCredential>]; * PaymentCredential.VerificationKey = Constr 0 [bytes(keyHash)]; * Option None = Constr 1 []. */ private PlutusData vaultDatum(byte[] adminKeyHash) { ConstrPlutusData paymentCredential = ConstrPlutusData.of(0, BytesPlutusData.of(adminKeyHash)); ConstrPlutusData noStakeCredential = ConstrPlutusData.of(1); ConstrPlutusData adminAddress = ConstrPlutusData.of(0, paymentCredential, noStakeCredential); return ConstrPlutusData.of(0, adminAddress); } /** Redeemer {@code Redeemer { marker: ByteArray }} = Constr 0 [bytes]. */ private PlutusData claimRedeemer(String marker) { return ConstrPlutusData.of(0, BytesPlutusData.of(marker.getBytes(StandardCharsets.UTF_8))); } ``` ## The stream: one explicit lane per contract The stream uses `LanePolicy.explicit()` with a resolver that maps the contract's lane name to the operator's funding reference (the operator wallet funds fees, collateral, and the locks). The engine's signer registry knows `account://operator`: ```java engine = FlowEngine.builder( utxoSupplier, new DefaultProtocolParamsSupplier(backendService.getEpochService()), new DefaultTransactionProcessor(backendService.getTransactionService()), new DefaultChainDataSupplier(backendService)) .executor(engineExecutor) .maintenanceExecutor(maintenanceExecutor) .store(new InMemoryFlowExecutionStore()) .signerRegistry(new DefaultSignerRegistry() .addAccount(OPERATOR_REF, operator)) .build(); ``` ```java try (TxFlowStream stream = TxFlowStream.builder("escrow-settlement", engine) .lanes(LanePolicy.explicit()) .laneResolver(laneName -> escrowLane.equals(laneName) ? ResolvedLane.ofFundingRef(escrowLane, OPERATOR_REF) : null) .executor(streamExecutor) .maxBufferSize(10) .open()) { // ... } ``` Two honest notes on this lane design: - **What actually serializes is the lane's canonical spending identity** — here `ref:account://operator`. Naming the lane after the contract is for receipts and routing; if you added a second lane label over the same operator wallet, it would share this lane's FIFO (labels can never sneak two writers onto one wallet). The claim serialization you want for one contract falls out of the design *because* all of that contract's claims share one lane. In a service settling several vaults, give each vault its own operator wallet and lane and the contracts settle concurrently while each stays internally FIFO. - **Lane funding scope is enforced, not documented**: each item's transaction must draw from the lane's declared source (`from_ref account://operator`), or the item fails typed `TXSTREAM_LANE_SCOPE_VIOLATION`. ## Locking escrows through the stream Each order's lock is an ordinary portable payment-to-contract plan: `payToContract` with the inline `Vault` datum, funded by the operator. It streams like any other item: ```java /** * Lock plan: pay the order's escrow amount to the vault with the inline * {@code Vault { admin }} datum, funded by the operator. */ private TxPlan lockPlan(Amount amount, PlutusData vaultDatum) { return TxPlan.from(new Tx() .payToContract(scriptAddress, amount, vaultDatum) .fromRef(OPERATOR_REF)) .withSigner(OPERATOR_REF); } ``` ```java // --- 1. LOCK: two order escrows through the stream (perItem) --- TxStreamReceipt lock1 = stream.submit(TxWorkItem.builder("lock-order-1") .withTxPlan(lockPlan(Amount.ada(5), vaultDatum)) .withLane(escrowLane) .withIdempotencyKey("lock-order-1") .build()); TxStreamReceipt lock2 = stream.submit(TxWorkItem.builder("lock-order-2") .withTxPlan(lockPlan(Amount.ada(6), vaultDatum)) .withLane(escrowLane) .withIdempotencyKey("lock-order-2") .build()); ``` When a fulfillment event arrives later, find the order's escrow UTXO at the script address. All escrows for one vault share the same inline datum (the admin), so find them by datum and map each to its order by the lock transaction hash the service recorded: ```java TxStreamItemResult lock1Result = lock1.awaitConfirmed(Duration.ofMinutes(3)); TxStreamItemResult lock2Result = lock2.awaitConfirmed(Duration.ofMinutes(3)); List escrows = ScriptUtxoFinders.findAllByInlineDatum( utxoSupplier, scriptAddress, vaultDatum); Utxo escrow1 = byTxHash(escrows, lock1Result.getTransactionHash()); Utxo escrow2 = byTxHash(escrows, lock2Result.getTransactionHash()); ``` `byTxHash` is your own helper that filters by `Utxo.getTxHash()`. Confirmation can come before UTxO indexing, so retry the lookup until the lock output appears. (If your datum is unique per order — e.g. it embeds an order id — `findFirstByInlineDatum` finds the exact UTXO directly.) ## Claiming through the stream: the portable script surface **The stream accepts only portable payloads.** At `submit`, every item is checked by the portable validator and re-encoded through the portable flow codec; payloads that carry Java behavior the codec cannot represent — a legacy `ScriptTx`-composing `withTxContext` factory, or any `TxContext` lambda — are rejected typed `TXSTREAM_NON_PORTABLE_ITEM`. The reason is durability and idempotency, not pedantry: the stream fingerprints the item's *content* for redelivery comparison and (in durable mode) persists the encoded flow for restart re-attach. A Java lambda has no content to fingerprint or persist. The portable path is the **unified `Tx` script surface**: `collectFrom(utxo, redeemer)` records a `script_collect_from` intent (UTXO reference plus structured redeemer), and `attachSpendingValidator(script)` records a `validator` attachment intent that serializes the script inline as `cbor_hex` + `version`. Both survive the plan's YAML round-trip and the portable flow encoding intact: ```java /** * Claim plan on the portable script surface of the unified {@link Tx}: * spend the escrow UTXO with a redeemer, attach the validator inline * (serialized as cbor_hex + version), pay the beneficiary, and fund * fee/change from the operator. Collateral and the admin's required signer * ride the plan's portable context. */ private TxPlan claimPlan(String orderId, Utxo escrowUtxo, Amount payout) { Tx claim = new Tx() .collectFrom(escrowUtxo, claimRedeemer(orderId)) .payToAddress(beneficiary.baseAddress(), payout) .attachSpendingValidator(vaultScript) .fromRef(OPERATOR_REF); return TxPlan.from(claim) .withSigner(OPERATOR_REF) .collateralPayerRef(OPERATOR_REF) // The vault checks extra_signatories for the admin's key hash. .withRequiredSigners(HexUtil.encodeHexString( operator.getBaseAddress().getPaymentCredentialHash().get())); } ``` This is real output — the exact portable YAML that claim plan serializes to (and that the stream re-encodes and, in durable mode, persists): ```yaml version: 1.0 context: collateral_payer_ref: account://operator required_signers: - c88b0842103eb87b347dd876c805a13ca7e135ce3e8bdfb308fe216b signers: - ref: account://operator scope: payment transaction: - tx: from_ref: account://operator inputs: - type: script_collect_from utxo_refs: - tx_hash: d5deb18908c95d445a17a4275e00a97f1abdb85a24e3e1cb9e2f8b40b1e30f4c output_index: 0 redeemer: constructor: 0 fields: - bytes: 6f726465722d31 intents: - type: payment address: addr_test1qpcf5ursqpwx2tp8maeah00rxxdfpvf8h65k4hk3chac0fvu28duly863yqhgjtl8an2pkksd6mlzv0qv4nejh5u2zjsshr90k amounts: - unit: lovelace quantity: 5000000 scripts: - type: validator role: spend cbor_hex: 5901035901000101... # the double-wrapped compiled code, elided version: v3 ``` The claim shape's portability is pinned by a deterministic unit test that needs no devnet: `txflow/src/test/java/com/bloxbean/cardano/client/txflow/stream/TxFlowStreamContractClaimRoundTripTest.java` asserts the `TxPlan` YAML round-trip keeps both script intents (and that the structured redeemer decodes back to the byte-identical `PlutusData`), that `PortableFlowValidator` accepts the single-step flow, and that the portable codec's write → parse → compile pipeline — exactly what `submit` and the engine run — materializes the same shape. > **Note on the UTXO reference:** the plan serializes the escrow UTXO as a `(tx_hash, > output_index)` reference; the engine's UTXO supplier re-resolves it at build time. Amounts, > address, and inline datum do not ride the plan — the chain is the source of truth. ### Collateral and the required signer Script transactions need two context wirings that ordinary payments don't, and both are portable plan context (visible in the YAML above), not Java configuration: - **Collateral**: `collateralPayerRef("account://operator")` names the wallet whose UTXOs back collateral, resolved through the engine's signer registry at build time. - **Required signer**: the vault checks `extra_signatories`, which only contains credentials the transaction *declares*. `withRequiredSigners(hex)` takes the admin's payment-credential hash as a hex string and lands it in the transaction body's required signers; the registry's `account://operator` payment signer then actually signs. Declaring without signing (or signing without declaring) fails the script — you need both lines. ## Idempotent settlement on the contract lane Each fulfillment event becomes one claim item: same contract lane, event id as idempotency key. ```java /** * One claim work item per fulfillment event: same contract lane (FIFO), * fulfillment event id as the idempotency key. */ private TxWorkItem claimItem(String orderId, Utxo escrowUtxo, Amount payout) { return TxWorkItem.builder("claim-" + orderId) .withTxPlan(claimPlan(orderId, escrowUtxo, payout)) .withLane(escrowLane) .withIdempotencyKey("fulfill-" + orderId) .build(); } ``` ```java TxStreamReceipt claim1 = stream.submit(claimItem("order-1", escrow1, Amount.ada(5))); TxStreamReceipt claim2 = stream.submit(claimItem("order-2", escrow2, Amount.ada(6))); ``` Both claims are funded by the same lane, so they dispatch strictly FIFO: claim 2 waits for claim 1's confirmation. Neither can steal the other's escrow UTXO (each names its own), and neither can race the operator wallet's fee/collateral UTXOs — the two classic contract-service races are gone by construction rather than by retry loops. When the queue redelivers a fulfillment event — same event id, same content — the stream returns the *same receipt* instead of running a second claim: ```java // --- 4. Idempotency: a redelivered fulfillment event attaches to // the existing claim instead of double-claiming the escrow --- TxStreamReceipt redelivered = stream.submit(claimItem("order-1", escrow1, Amount.ada(5))); assertSame(claim1, redelivered); ``` Attachment requires identical content: the redelivered item's fingerprint (id, key, lane, metadata, and the portable payload encoding) must match. Build claim plans deterministically from the event — same escrow UTXO, same redeemer, same amounts — and redelivery is free; a *different* payload under a reused key is a typed conflict, never a silent second spend. The integration test closes the loop with on-chain assertions: both claims `CONFIRMED` with distinct transaction hashes, and both payouts present at the beneficiary address. ### Attaching by reference instead of inline Inline attachment (`cbor_hex` in every plan) is the simplest portable shape and the right default for one vault. If many plans share large scripts, attach by logical reference instead: `attachSpendingValidator(ScriptRef.ref("validator://vault"))` serializes as `script_ref: validator://vault` (no `cbor_hex`), and the engine resolves it at build time from a registry you wire once: ```java FlowEngine engine = FlowEngine.builder(utxoSupplier, protocolParamsSupplier, transactionProcessor, chainDataSupplier) .scriptRegistry(new DefaultScriptRegistry() .addPlutusScript("validator://vault", vaultScript)) // ... executor, store, signerRegistry as above .build(); ``` The plan then carries a name, not bytes — the same portability trade the signer registry makes for keys. (This tutorial's test uses inline attachment; the by-ref shape is covered by `quicktx`'s `ScriptRegistryAttachmentTest`.) ## Honest caveats - **Batching never merges script transactions.** The `batching(...)` planner merges only pure payment-shaped items; an item carrying `collectFrom`, a script attachment, a mint, or a datum-bearing output always runs as its own transaction (or fails the window typed, with `allowNonPaymentSingletons(false)`). Contract items get the stream's idempotency and lane serialization — not fee amortization. - **Plutus minting has no policy-reference overload yet.** `mintAssets(PolicyRef, ...)` is native-script-only; for Plutus minting use the inline `mintAsset(PlutusScript, ...)` overloads (portable via the attachment's `cbor_hex`), or name the `policyId` string explicitly and attach the mint validator by `ScriptRef` through the script registry. - **Script cost evaluation runs through the engine's `TransactionProcessor` by default** (the backend's evaluate endpoint — supported by Yaci DevKit and Blockfrost). To evaluate locally, set one factory for the whole engine: `.txEvaluator((utxos, params) -> new AikenTransactionEvaluator(utxos, params))` on `FlowEngine.Builder` or `FlowRuntime.Builder`, using [Aiken Java Binding](/smart-contracts/aiken/). There's no per-item evaluator. A Plutus step under `perWindow(ChainingMode.PIPELINED)` that spends or references an earlier step's pending output needs the local evaluator, because the backend knows only confirmed UTxOs. - **Runtime `Utxo` objects serialize as references.** A claim plan built from a found `Utxo` round-trips as `(tx_hash, output_index)`; if that UTXO is spent before dispatch, the claim fails honestly rather than double-spending. - **Run the scenario yourself against a devnet.** The integration test needs a running [Yaci DevKit](https://devkit.yaci.xyz/#get-started) at `http://localhost:8080/api/v1/`: ``` ./gradlew :txflow:integrationTest --tests TxFlowStreamContractIntegrationTest -Dyaci.integration.test=true ``` ## Continue - [TxStream Getting Started](/txstream/getting-started/) — item identity, receipts, lifecycle. - [TxStream: Lanes, Batching & Throughput](/txstream/throughput/) — the lane model this tutorial leans on, and what is (not) batchable. - [TxStream: Durability & Exactly-Once](/txstream/durability/) — persist the stream so claims survive restarts; dedup scopes. - [Portable Authoring](/txflow/portable-authoring/) — the full portable intent surface beyond scripts. --- # TxStream: Lanes, Batching & Throughput Source: https://cardano-client.dev/txstream/throughput/ Summary: The UTXO concurrency model: lane policies, windows, planners, partitioned fan-out, and honest throughput expectations :::caution[Experimental] TxStream is experimental, and its APIs and behaviour may change. See [known issues](/project/known-issues/#txstream) before relying on it. ::: Cardano's eUTXO model *is* TxStream's concurrency model. Two transactions that spend disjoint UTXOs can be in flight simultaneously; two transactions spending the same funds cannot. TxStream expresses that directly: - A **lane** is a funding scope — a funding address or funding reference whose UTXOs at most one in-flight execution may spend at a time. - **Different lanes run concurrently; the same lane runs serial FIFO.** Throughput scales with the number of disjoint funding scopes, not with thread counts. - Scheduling keys on the lane's **canonical spending identity, never its display name**: two lane labels resolving to the same wallet are one lane wearing two names and share one FIFO. Labels can never be used to sneak two writers onto one wallet. - The lane also constrains **coin selection**: an item's transaction is mechanically pinned to its lane's funding scope (violations fail typed `TXSTREAM_LANE_SCOPE_VIOLATION`), and the lane is declared as the engine's spending resource — the cross-process safety net if another process ever runs against the same funds. Template items are exempt from the pin: keep each template's `from_ref` equal to its lane's source yourself. > **Alias caveat:** for funding-*ref* lanes, identity is the reference string as given > (`ref:account://sender`), caller-asserted canonical. Two different references naming the same > wallet — or a ref lane and an address lane over the same wallet — are treated as distinct lanes > today; keeping such aliases from running concurrently is your responsibility. ## Lane policies | Policy | Lane comes from | Use when | | --- | --- | --- | | `LanePolicy.single(lane)` / builder `.lane(...)` | one static configuration | One funding wallet, strict FIFO — the simplest correct start | | `LanePolicy.explicit()` + `laneResolver` | each item's `withLane(name)` | You route work to named funding scopes (treasury vs operations) | | `LanePolicy.byFundingSource()` (default) | each item's own `from`/`from_ref` | Items already carry their funding source; senders lane themselves | | `LanePolicy.partitioned(config)` | `hash(idempotencyKey) % N` over N app-owned addresses | Maximum throughput from one logical wallet — see the deep-dive below | **Explicit lanes** resolve names through a required `LaneIdentityResolver`, once per name at first use. An item without a lane fails typed `TXSTREAM_LANE_REQUIRED`; an unresolvable name fails that item typed `TXSTREAM_LANE_UNRESOLVED` (it cannot be a startup error — names arrive with items): ```java try (TxFlowStream stream = TxFlowStream.builder("payouts", engine) .lanes(LanePolicy.explicit()) .laneResolver(laneName -> { switch (laneName) { case "treasury": return ResolvedLane.ofFundingRef("treasury", "account://treasury"); case "operations": return ResolvedLane.ofFundingRef("operations", "account://operations"); default: return null; // fails the item typed TXSTREAM_LANE_UNRESOLVED } }) .maxInFlight(8) .executor(streamExecutor) .open()) { stream.submit(TxWorkItem.builder("treasury-pay-1") .withTxPlan(treasuryPlan) .withLane("treasury") .build()); stream.submit(TxWorkItem.builder("ops-pay-1") .withTxPlan(opsPlan) .withLane("operations") // different identity: runs concurrently .build()); } ``` **Funding-address-derived lanes** need no resolver and no per-item lane name — the transaction's own funding source is the lane, so same-sender items serialize and different senders parallelize for free. An item whose transaction names no funding source fails typed `TXSTREAM_LANE_UNDERIVABLE`; an item that names a lane must name the derived one or fails `TXSTREAM_LANE_MISMATCH` (that last rule applies under `single(...)` too — a lane name is never a silent label): ```java TxFlowStream stream = TxFlowStream.builder("payouts", engine).open(); ``` The default policy is `byFundingSource()`; the builder call is only needed when making the choice explicit. `byFundingAddress()` remains a deprecated compatibility alias. Dispatch inherits the engine execution executor unless overridden; give an explicit stream executor at least as many threads as the number of lanes you want concurrently dispatching. ## Windows and planners By default there is no windowing and the `perItem()` planner plans every accepted item immediately as its own single-step flow — maximum lane parallelism, true per-item idempotency. Multi-item planning is opt-in via a `WindowPolicy` plus a planner: - `WindowPolicy.count(n)` — close the window at n items; no timer needed. - `WindowPolicy.countOrTime(n, maxAge)` — n items or the oldest item's age, whichever first. - `WindowPolicy.time(maxAge)` — age only. Time-based policies need a scheduler. Set `maintenanceExecutor(ScheduledExecutorService)`, or let the stream inherit the engine's maintenance executor when it's a `ScheduledExecutorService`: the stream owns no threads or timers, so window-age wakeups run on your scheduler (one scheduled wakeup per open window). ```java ScheduledExecutorService maintenance = Executors.newSingleThreadScheduledExecutor(); TxFlowStream perWindowStream = TxFlowStream.builder("payouts-window", engine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .planner(TxStreamPlanner.perWindow()) .window(WindowPolicy.countOrTime(50, Duration.ofSeconds(10))) .maintenanceExecutor(maintenance) // a scheduler for time-based windows .executor(streamExecutor) .build(); TxFlowStream batchingStream = TxFlowStream.builder("payouts-batch", engine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .planner(TxStreamPlanner.batching(BatchingOptions.builder() .maxItemsPerTransaction(20) .allowNonPaymentSingletons(true) .build())) .window(WindowPolicy.count(20)) .executor(streamExecutor) .build(); ``` The three built-in planners: - **`perItem()`** (default) — one single-step flow per item. The only planner with per-item exactly-once; sources that redeliver individual items should use this one. - **`perWindow()`** — one shared flow per *lane group* in the window; each item rides its own step (its own transaction) of the shared execution. Fewer engine executions, per-item transactions preserved. Flow-level dedup only. `perWindow(ChainingMode.PIPELINED)` opts into same-lane pipelining: each generated step may fund from every earlier same-lane step's unspent, matching-address pending outputs. `SEQUENTIAL` is the default; `BATCH` is not supported here. - **`batching(options)`** — merges compatible payment-shaped items within a lane group into **one** transaction (one flow, one merged step; overflow beyond `maxItemsPerTransaction` splits into further merged flows). Fewer on-chain transactions and lower total fees; merged members share one fate — the batch confirms or fails together: ```java TxStreamItemResult a = first.completion().toCompletableFuture().join(); TxStreamItemResult b = second.completion().toCompletableFuture().join(); // Merged members share one transaction, one execution, one step: boolean merged = a.getTransactionHash().equals(b.getTransactionHash()) && a.getExecutionId().equals(b.getExecutionId()) && a.getStepId().equals(b.getStepId()); Optional batch = stream.getBatchStatus("batch-1"); batch.ifPresent(result -> System.out.println(result.status() + " executions=" + result.executionIds())); ``` > **What is NOT batchable, and what merging drops.** Only pure payments merge: an item carrying a > script, mint, staking, governance, metadata, `collectFrom`, reference inputs, or a > contract/datum-bearing output is not payment-shaped and runs unmerged as its own single-item > flow (or fails the window typed, with `allowNonPaymentSingletons(false)`). The merged > transaction is a minimal reconstruction from the members' payment outputs plus the lane's > funding source — **per-member transaction context is not carried**: extra signers, custom fee > payer, change address, and validity windows are dropped. Items needing any of that belong > under `perItem()` or `perWindow()`. > > **Batching needs a funding-ref lane.** A merged plan gets a signer only on a funding-ref lane > (`ofFundingRef` or `from_ref`). On an address lane the merged transaction has no signer, so the > batch fails with `No signers found` and no funds move. > **Dedup warning:** `perWindow()` and `batching(...)` provide flow-level dedup only, and under > batching a re-batched payment is a **real second on-chain payment**. Read > [dedup scopes](/txstream/durability/#dedup-scopes-what-exactly-once-actually-covers) before > using either with a redelivering source. Custom planners implement `TxStreamPlanner` — plan a closed window into executions, deriving flow and step ids from the context's `StableIdFactory` (determinism is an SPI obligation: the same items must produce a byte-identical plan, or redeliveries become `TXFLOW_IDEMPOTENCY_CONFLICT` failures). Plans are validated before dispatch (`TXSTREAM_PLAN_INVALID`, `TXSTREAM_PLAN_CROSS_LANE`); a planner that throws fails only that window's items (`TXSTREAM_PLANNER_FAILED`), never the stream. ## Partitioned lanes: the fan-out deep-dive Items may use QuickTx extensions registered on the engine (`FlowEngine.Builder.extension` or `FlowRuntime.Builder.extension`). An unregistered extension is rejected at submit with `TXSTREAM_NON_PORTABLE_ITEM`, `batching(...)` never merges extension items, and `perWindow()` splits items with conflicting bindings into separate flows on the same lane. `LanePolicy.partitioned(...)` is the full UTXO throughput story for a single logical wallet: the funding source is split across N lane addresses, items are assigned by `hash(idempotencyKey) % N` (stable across restarts), and the N lanes run concurrently — throughput scales with N, not block time. :::caution[Pre-fund your lanes in this preview] The built-in bootstrap transaction carries no signer, so `bootstrap(true)` fails `start()` with `TXSTREAM_BOOTSTRAP_FAILED`. Fund the lane addresses yourself and use `bootstrap(false)`. ::: Items must leave `from` and `from_ref` unset, or name exactly their assigned lane address; otherwise they fail `TXSTREAM_LANE_SCOPE_VIOLATION`. A plan's signers must be able to sign for whichever lane the hash assigns: ```java PartitionedLanes partitioning = PartitionedLanes.fromRef("account://treasury") .laneAddresses(laneAddresses) // order is stability-critical .seedPerLane(Amount.ada(200)) .bootstrap(false) // lanes are already funded .build(); TxFlowStream stream = TxFlowStream.builder("payouts-partitioned", durableEngine) .lanes(LanePolicy.partitioned(partitioning)) .stateStore(TxStreamStateStore.inMemoryDurable()) // durable: drift fails fast .executor(streamExecutor) .build(); stream.start(); TxPlan plan = TxPlan.from(new Tx().payToAddress(receiver, Amount.ada(2))) .withSigner("account://lane0") .withSigner("account://lane1"); stream.submit("payout-1", plan).awaitConfirmed(Duration.ofMinutes(5)); ``` With `bootstrap(false)` the lanes are assumed pre-funded. Min-ADA per lane and consolidating lanes on shutdown are the caller's economics — the stream never manages keys. > **FUNDS-CRITICAL: the partitioned configuration must be byte-stable across restarts.** The > funding source, the per-lane seed, the lane count N, and the lane-address list **including its > order** all feed the bootstrap's idempotency claim — changing any of them mints a *new* claim, > i.e. a new split that **re-drains the funding wallet**; and the address order additionally > defines the `partitionIndex → lane` mapping, so reordering remaps every item's lane even with N > and the addresses unchanged. A **durable** partitioned stream persists the bootstrap > fingerprint and fails `start()` typed (`TXSTREAM_BOOTSTRAP_CONFIG_DRIFT`) on any drift, before > submitting a split. A **non-durable** stream has nowhere to persist the fingerprint and > **cannot detect drift — it will silently re-split**. Run partitioned streams durable unless you > are certain the configuration is generated deterministically. Template items currently require an explicit lane (`single` or `explicit`); under `byFundingSource()`/`partitioned(...)` they fail typed `TXSTREAM_LANE_REQUIRED`. ## Honest throughput expectations What the current built-ins actually deliver: - **Lanes are real parallelism** — N disjoint funding scopes give up to N concurrent in-flight executions (devnet-validated), bounded by `maxInFlight`. - **Batching is real transaction reduction** — up to `maxItemsPerTransaction` payments per on-chain transaction (devnet-validated: two payments merged into one transaction). - **Within one flow, `perWindow()` is SEQUENTIAL by default**: a flow of 50 items confirms its 50 steps one at a time, and a lane running `perItem()` flows waits for each item's confirmation before dispatching the next. `perWindow(ChainingMode.PIPELINED)` with a `WindowPolicy` submits a window's steps without waiting for confirmation between them. The lane still runs one execution at a time, so the next window waits for the current one, and windows of one gain nothing. Dedup stays flow-level. So today's levers, in order: **more lanes** (explicit or partitioned), then **batching** (fewer, larger transactions), then `perWindow(PIPELINED)` with window sizing. If a lane must exceed roughly one transaction per confirmation interval and batching cannot absorb it, that is the signal to partition further. ## Feeding a stream from java.util.concurrent.Flow For reactive pipelines, `TxWorkSource.fromPublisher(...)` bridges a `Flow.Publisher` with real demand-based backpressure: the adapter prefetches a bounded number of items (default 64), requests one more as each accepted item settles, and parks items while the buffer is full or the stream is an ownership standby (`PAUSED`) — items are never dropped and demand is never unbounded. `TxStreamFlowPublisher` is the outbound half: an event listener that publishes item results to `Flow` subscribers. ```java SubmissionPublisher publisher = new SubmissionPublisher<>(); FlowWorkSource source = TxWorkSource.fromPublisher(publisher, 64); TxStreamFlowPublisher results = TxStreamFlowPublisher.terminalOnly(); try (TxFlowStream stream = TxFlowStream.builder("payouts-reactive", engine) .lane(ResolvedLane.ofFundingRef("payouts", "account://sender")) .source(source) .eventListener(results) .executor(streamExecutor) .build()) { results.subscribe(resultSubscriber); // Flow.Subscriber stream.start(); publisher.submit(TxWorkItem.builder("pay-1") .withTxPlan(plan) .build()); publisher.close(); source.terminated().toCompletableFuture().join(); // publisher finished stream.drain(); // accepted work settles } ``` > **Subscribers must not block in `onNext` (Reactive Streams §2.2).** Delivery is inline on the > stream's callback thread — the thread that advanced the item's projection. A subscriber that > blocks inside `onNext` stalls that lane's further dispatch (other lanes, other subscribers, and > the item's own `completion()` are unaffected). A merely *slow* (under-requesting) subscriber is > different and safe: its bounded buffer (default 256) overflows and it is terminated with > `TxStreamSubscriberOverflowException` and dropped — it never back-pressures the stream. ## Scalability notes - **`maxInFlight`** (default 16) caps concurrent executions across all lanes, on top of the one-per-lane rule. Ready lanes are scheduled round-robin, so one lane's backlog cannot starve the others. - **Lane maps are unbounded.** Resolved lane identities and their FIFO state are retained for the stream's lifetime. Under `byFundingSource()` with high-cardinality senders (many thousands of distinct funding addresses through one stream), that map grows without bound — prefer a bounded set of explicit or partitioned lanes for that shape. - **`maxBufferSize`** (default 1000) bounds accepted-but-undispatched items; `submit` blocks and `trySubmit` reports `FULL` when it is reached. A count-only window larger than the buffer is rejected at `build()` (it could never fill). - One stream instance per lane set: sharing a lane's funding scope across processes without ownership mode is out of scope until the engine's cross-process busy-claim path lands; the engine's spending-resource coordination is the safety net, not a throughput feature. ## Continue - [TxStream Getting Started](/txstream/getting-started/) — item identity, receipts, lifecycle. - [TxStream: Durability & Exactly-Once](/txstream/durability/) — dedup scopes (mandatory reading for `perWindow()`/`batching()`), re-attach, and HA. ## Backend indexing and qualification Confirmation and UTxO indexing can become visible at different times. Backend-based engines enable TxStream's between-execution output-visibility check by default. It holds the lane until the preceding transaction's output zero is queryable, bounded by the stream's `backendVisibility(timeout, pollInterval)` budget. A timeout fails new work before engine start with `TXSTREAM_BACKEND_NOT_READY`; the preceding transaction remains confirmed or uncertain as previously reported. Custom four-supplier engines must opt in with `backendVisibilityChecks(true)` and provide historical output lookup. This check does not establish atomic consistency across all provider endpoints or coordinate unrelated spenders. Measure both confirmation and indexing delay with the provider used in deployment. The soak runner also has its own application submission gate. Report gated and ungated runs separately; a gated throughput result must not be presented as a measurement of the ordinary SDK submission path. Preview qualification should include delayed indexing, unavailable output lookups, and consecutive same-lane payments without that application gate. Backend visibility retries release the worker and in-flight slot between probes. `FlowRuntime` supplies the retry scheduler. Direct stream builders inherit the engine's maintenance executor when it implements `ScheduledExecutorService`; an explicit stream `maintenanceExecutor(...)` takes precedence. When neither provides a scheduler, an unsuccessful probe fails before engine start. A plain `Executor` cannot schedule retries. Plain `close()` keeps retries active while it drains and cancels remaining timers afterward; abort cancels them immediately. Parked executions retain their buffer permits, so `maxBufferSize` includes them. The pending hashes are process-local and reset on restart; authoritative FAILED/CANCELLED repair clears the affected item's hold. A visibility timeout fails every member of the waiting execution. Their IDs remain registered. Because this particular error proves the engine never started, the caller may retry that work under new IDs; this does not apply to RECOVERY_REQUIRED. Custom backend-based engines used by TxStream enable visibility checks by default; custom output services must support historical output lookup or explicitly disable `backendVisibilityChecks`. Ordinary engine executions do not invoke this stream gate. --- # Jellyfish Merkle Tree Source: https://cardano-client.dev/verified-structures/jmt/ Summary: Versioned, authenticated off-chain state with inclusion and absence proofs. > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. A Jellyfish Merkle Tree (JMT) keeps every version of your state, so you can read and prove a key as of any version. Each commit is one batch of updates with a version number. It's inspired by Diem's JMT, but its commitments and proofs aren't compatible with Diem or Aptos. There's no on-chain verifier, and keys can't be deleted. If a validator needs to check proofs, [use MPF](/verified-structures/mpf/). The supported production scope is serialized off-chain state with one logical writer per namespace. ## Quick start Add the [dependency](/verified-structures/overview/#dependencies), then: ```java import com.bloxbean.cardano.vds.jmt.JellyfishMerkleTree; import com.bloxbean.cardano.vds.jmt.rocksdb.RocksDbJmtStore; try (RocksDbJmtStore store = new RocksDbJmtStore("data/jmt")) { JellyfishMerkleTree tree = new JellyfishMerkleTree(store); // Version 1 Map updates = new HashMap<>(); updates.put("alice".getBytes(), "balance:100".getBytes()); updates.put("bob".getBytes(), "balance:200".getBytes()); tree.put(1L, updates); // Version 2 JellyfishMerkleTree.CommitResult commit = tree.put(2L, Map.of("alice".getBytes(), "balance:150".getBytes())); byte[] rootV2 = commit.rootHash(); Optional latest = tree.get("alice".getBytes()); // "balance:150" Optional old = tree.get("alice".getBytes(), 1L); // "balance:100" } ``` For tests, `InMemoryJmtStore` works without any storage module. Put many updates in one commit rather than committing each key on its own: batching normally reduces storage and rewriting. ## Versions - New versions must increase. Gaps are allowed, and writing below the latest version throws `IllegalArgumentException`. - `get(key, version)` answers *as of* that version, so a version between two commits reads the earlier commit. - `get(key, version)` throws `IllegalArgumentException` for a version newer than the latest commit, or below the prune watermark. It doesn't fall back to some other state. - `getProof(key, version)` needs a root committed at exactly that version, and returns empty otherwise. If your position can be ahead of the latest commit, such as a chain tip that skipped empty blocks, read at `min(position, store.latestRoot().get().version())`. ## Proofs `getProof` returns an inclusion proof for a key that exists and an absence proof for one that doesn't. ```java JmtProfile profile = JmtProfile.classicBlake2b256V1(); JmtProof proof = tree.getProof("alice".getBytes(), 2L).orElseThrow(); boolean included = JmtProofVerifier.verify(rootV2, "alice".getBytes(), proof.value(), proof, profile.hashFunction(), profile.commitmentScheme()); // value() is null for an absence proof, and null asks the verifier to check absence JmtProof absence = tree.getProof("nobody".getBytes(), 2L).orElseThrow(); boolean absent = JmtProofVerifier.verify(rootV2, "nobody".getBytes(), null, absence, profile.hashFunction(), profile.commitmentScheme()); ``` The verifier returns true only when the proof shows exactly that statement: an inclusion proof never proves absence, and an absence proof never proves a value. To send a proof over the network, use CBOR: ```java byte[] wire = tree.getProofWire("alice".getBytes(), 2L).orElseThrow(); boolean valid = tree.verifyProofWire(rootV2, "alice".getBytes(), "balance:150".getBytes(), true, wire); ``` ## Pruning ```java int removed = tree.pruneUpTo(2L); OptionalLong watermark = store.pruneWatermark(); ``` The watermark is the greatest committed version at or below the horizon. Its state stays readable and provable. Reads and proofs below it are rejected, and `truncateAfter` below it throws `IllegalStateException`. With the RocksDB `AGGRESSIVE` value-prune policy, the watermark moves to the latest version at prune time, because that policy drops older values of rewritten keys. ## Thread safety Competing writes to a namespace fail immediately with `JmtConcurrentMutationException`. Pruning and rollback are exclusive and fail fast if a read or update is active. Every tree or store wrapper for a namespace must share one coordinator, and PostgreSQL also enforces the single writer across processes. ## Format Persistent stores record the `classic-radix16-blake2b256-v1` format and refuse to open incompatible data. The deprecated `JellyfishMerkleTree(store, hashFn)` constructors are for in-memory stores only: RocksDB and SQL stores reject them with `JmtFormatMismatchException`. For metrics, the design and the production checklist, see the [module README](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/jellyfish-merkle). --- # MpfTrie Source: https://cardano-client.dev/verified-structures/mpf/ Summary: Merkle Patricia Forestry on RocksDB, with proofs that Aiken validators can verify. > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. `MpfTrie` is the off-chain counterpart of Aiken's [merkle-patricia-forestry](https://github.com/aiken-lang/merkle-patricia-forestry) library. Its roots and proofs are identical to those of the library's official JavaScript implementation. It uses Blake2b-256 and CBOR, so its proofs don't work with Ethereum's Merkle Patricia tries. ## Quick start Add the [dependency](/verified-structures/overview/#dependencies), then: ```java import com.bloxbean.cardano.vds.mpf.MpfTrie; import com.bloxbean.cardano.vds.mpf.rocksdb.RocksDbMptSession; import com.bloxbean.cardano.vds.mpf.rocksdb.RocksDbStateTrees; try (RocksDbStateTrees stateTrees = new RocksDbStateTrees("data/mpf")) { MpfTrie trie = new MpfTrie(stateTrees.nodeStore()); RocksDbMptSession session = RocksDbMptSession.of(stateTrees); // One atomic batch: trie nodes, version entry and refcounts session.write(() -> { trie.put("alice".getBytes(), "100".getBytes()); trie.put("bob".getBytes(), "200".getBytes()); return stateTrees.putRootWithRefcount(trie.getRootHash()); }); byte[] value = trie.get("alice".getBytes()); // "100" byte[] rootForDatum = trie.getRootHashOrEmpty(); // store this on chain } ``` Keys are hashed before they're stored, so you can look up a key or iterate over all entries, but you can't query by prefix or range. ## Reopen a trie ```java try (RocksDbStateTrees stateTrees = new RocksDbStateTrees("data/mpf")) { MpfTrie trie = new MpfTrie(stateTrees.nodeStore(), stateTrees.rootsIndex().latest()); byte[] value = trie.get("alice".getBytes()); } ``` When the root comes from an on-chain datum, use `MpfTrie.open(store, root)`, available from 0.8.0-pre7. It throws `IllegalStateException` if the store doesn't have that root. The constructors are lenient: with an unknown root, reads return `null` and the next `put` starts a new trie. ## The empty trie `getRootHash()` returns `null` for an empty trie, while Aiken's `mpf.empty` root is 32 zero bytes. For datums, use `getRootHashOrEmpty()`, available from 0.8.0-pre7, which returns those zero bytes. `new MpfTrie(store, <32 zero bytes>)` and `MpfTrie.open(store, <32 zero bytes>)` both open an empty trie. ## Proofs for Aiken ```java byte[] key = "alice".getBytes(); byte[] value = trie.get(key); byte[] root = trie.getRootHash(); // Ready to use in a redeemer, as Aiken's `Proof` type ListPlutusData proof = trie.getProofPlutusData(key).orElseThrow(); // The same proof as wire CBOR, with off-chain checks byte[] wire = trie.getProofWire(key).orElseThrow(); boolean present = trie.verifyProofWire(root, key, value, true, wire); byte[] missing = "carol".getBytes(); byte[] missingWire = trie.getProofWire(missing).orElseThrow(); boolean absent = trie.verifyProofWire(root, missing, null, false, missingWire); ``` `verifyProofWire` returns `false` for a malformed proof and never throws. To verify without a trie, use `ProofVerifier.verify(root, key, value, including, wire)`. A key's proof depends on the trie you take it from, and one proof serves several Aiken operations: | Aiken call | Take the proof of `key` from | |---|---| | `mpf.has(trie, key, value, proof)` | The trie that contains `key` | | `mpf.miss(trie, key, proof)` | The trie that doesn't contain `key` | | `mpf.insert(trie, key, value, proof)` | The trie before the insert, or after it | | `mpf.delete(trie, key, value, proof)` | The trie before the delete, or after it | | `mpf.update(trie, key, proof, old, new)` | The trie before the update | :::danger[Validators that use `miss` or `insert`] `aiken-lang/merkle-patricia-forestry` 2.1.0 doesn't check the shape of proof steps, or that a terminal `Leaf` or `Fork` neighbor differs from the queried key. With a crafted proof, `mpf.miss` accepts a key that is present, and `mpf.insert` accepts re-inserting a present key with a new value. We reproduced both with `aiken check`. CCL never produces such proofs and `verifyProofWire` rejects them, but on-chain code doesn't. If your validator relies on `miss` or `insert` to enforce absence or uniqueness, also check every step of the redeemer proof: `Fork` nibble and prefix bytes in 0..15, 32-byte `root`, `key` and `value`, and a neighbor nibble that differs from the key's nibble at that position. ::: The same proof can be encoded several ways, and `verifyProofWire` accepts all of them. Don't use proof bytes or their hash as an identifier; use the key and the root. ## Upgrading from 0.8.0-pre6 or earlier Proofs changed in 0.8.0-pre7, so **regenerate stored proofs**: - Earlier exclusion proofs ending in a `Fork` step were rejected by Aiken, and they now fail `verifyProofWire` too. Older CCL versions reject the new ones. - Earlier inclusion proofs used only `Branch` steps. Aiken accepts those for `has` and `update` but not for `delete`. They still pass `verifyProofWire`. Tries written by 0.8.0-pre6 still open with identical roots and values. See [garbage collection](/verified-structures/multi-version-gc/) if you ran GC on them. ## API ```java // Constructors (lenient) and strict factory MpfTrie(NodeStore store) MpfTrie(NodeStore store, byte[] rootHash) MpfTrie(NodeStore store, HashFunction hashFn) // not Aiken-compatible static MpfTrie open(NodeStore store, byte[] rootHash) // fails if the root isn't in the store // Core operations void put(byte[] key, byte[] value) byte[] get(byte[] key) void delete(byte[] key) byte[] getRootHash() // null for an empty trie byte[] getRootHashOrEmpty() // all zeros for an empty trie void setRootHash(byte[] root) // Proofs Optional getProofWire(byte[] key) Optional getProofPlutusData(byte[] key) boolean verifyProofWire(byte[] root, byte[] key, byte[] value, boolean including, byte[] wire) // Introspection List getAllEntries() List getEntries(int limit) TrieStatistics getStatistics() int computeSize() String printTree() ``` `MpfTrie` isn't thread-safe, and the storage backends don't coordinate concurrent writers. Use one writer per trie. For more, see the [module README](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/merkle-patricia-forestry). --- # MPF versions and garbage collection Source: https://cardano-client.dev/verified-structures/multi-version-gc/ Summary: Record versions of an MpfTrie on RocksDB, read old versions and reclaim space. > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. ## Record versions `RocksDbStateTrees` defaults to `StorageMode.MULTI_VERSION`. The mode is saved on first open and checked on every later open. The SQL backend defaults to `SINGLE_VERSION`, so pass the mode explicitly if you might use both. ```java try (RocksDbStateTrees stateTrees = new RocksDbStateTrees("data/mpf")) { MpfTrie trie = new MpfTrie(stateTrees.nodeStore()); RocksDbMptSession session = RocksDbMptSession.of(stateTrees); long v1 = session.write(() -> { trie.put("alice".getBytes(), "100".getBytes()); return stateTrees.putRootWithRefcount(trie.getRootHash()); }); long v2 = session.write(() -> { trie.put("alice".getBytes(), "150".getBytes()); return stateTrees.putRootWithRefcount(trie.getRootHash()); }); // Read an older version MpfTrie atV1 = new MpfTrie(stateTrees.nodeStore(), stateTrees.rootsIndex().get(v1)); byte[] alice = atV1.get("alice".getBytes()); // "100" } ``` If you collect garbage with mark-sweep only, `stateTrees.rootsIndex().put(version, root)` is enough to record a version. ## Keep only the latest state ```java try (RocksDbStateTrees stateTrees = new RocksDbStateTrees("data/mpf", StorageMode.SINGLE_VERSION)) { MpfTrie trie = new MpfTrie(stateTrees.nodeStore(), stateTrees.getCurrentRoot()); trie.put("alice".getBytes(), "100".getBytes()); stateTrees.putRootSnapshot(trie.getRootHash()); // overwrites version 0 stateTrees.cleanupOrphanedNodes(new GcOptions()); // keeps only the current root } ``` ## Garbage collection Every `put` writes new nodes and leaves the old ones in place. GC deletes the nodes that no retained version reaches. ```java import com.bloxbean.cardano.vds.mpf.rocksdb.gc.*; import com.bloxbean.cardano.vds.mpf.rocksdb.gc.strategy.*; GcManager gc = new GcManager(stateTrees.nodeStore(), stateTrees.rootsIndex()); GcOptions options = new GcOptions(); options.deleteBatchSize = 10_000; options.progress = deleted -> System.out.println("deleted " + deleted); GcReport report = gc.runSync(new OnDiskMarkSweepStrategy(), RetentionPolicy.keepLatestN(5), options); System.out.println("deleted " + report.deleted + " of " + report.total + " nodes"); ``` `runSync` declares `throws Exception`. To simulate a run, set `options.dryRun = true`. `gc.runAsync(strategy, policy, options, report -> ...)` runs on a daemon thread and passes the report to the callback. | Strategy | Cost | Requirements | |---|---|---| | `OnDiskMarkSweepStrategy` | Walks every retained version and scans the namespace's nodes | None | | `RefcountGcStrategy` | Walks the retained and dropped versions, with no full scan | Versions recorded with `putRootWithRefcount`; only those are reclaimed | ### Retention policies ```java RetentionPolicy.keepLatestN(10); RetentionPolicy.keepVersions(List.of(1L, 5L, 10L)); RetentionPolicy.keepRange(5, 15); // inclusive ``` A policy can also be any lambda from the roots index to the roots to keep. ### Rules - **Stop writers first.** Don't run GC while anything else writes to the same namespace. - **The latest root is always kept.** With a roots index, the `LATEST` root and the root at the last version survive whatever the policy says. Commit those tries before running GC, because a root visible only in an open write batch is rejected. - **Without a roots index,** `OnDiskMarkSweepStrategy` keeps only the policy's roots. - **Use one strategy per namespace,** and run mark-sweep occasionally to reclaim what refcount GC leaks. - **Refcount GC fails safe.** It never deletes a node a retained version still reaches, even with wrong counts. It leaks instead and reports those nodes in `GcReport.protectedNodes`. It refuses to run when a retained root has no refcount. - **Refcount GC edits the index.** It removes dropped versions from the roots index, while mark-sweep leaves the index as it is. ### After a crash Both strategies use a temporary column family named `__vds_gc_marks_`. If you open the `RocksDB` yourself, open it with all column families from `RocksDB.listColumnFamilies` and call `GcManager.dropLeftoverGcColumnFamilies` after a crash. ### Databases written by 0.8.0-pre6 or earlier 0.8.0-pre7 fixed data-loss bugs in RocksDB GC, so upgrade before running GC on an existing database. - Refcounts written by older code counted only roots. Refcount GC now protects retained tries from them and leaks instead of deleting. - Temporary column families left by 0.8.0-pre6 are named `marks_` and are never dropped automatically. Check them, then drop them with `GcManager.dropLegacyGcColumnFamilies`. For the algorithms and on-disk details, see the [GC design document](https://github.com/bloxbean/cardano-client-lib/blob/master/verified-structures/merkle-patricia-forestry-rocksdb/docs/gc.md). --- # Overview Source: https://cardano-client.dev/verified-structures/overview/ Summary: Merkle Patricia Forestry and Jellyfish Merkle Tree: authenticated data structures for Cardano applications. > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. A verified data structure stores key-value data off chain and reduces all of it to one hash, the *root*. Anyone who holds the root can check a *proof* that a key has a given value, or has no value, without having the data. The usual Cardano pattern is to keep the data in your application, store the root in a datum, and let a validator check proofs on chain. ## Which one do I need? | | [MpfTrie](/verified-structures/mpf/) | [Jellyfish Merkle Tree](/verified-structures/jmt/) | |---|---|---| | **Choose it for** | Proofs checked on chain by an Aiken validator | Versioned off-chain state, with proofs checked off chain | | **On-chain verifier** | Aiken's `merkle-patricia-forestry` library | None | | **Deleting a key** | Supported | Not implemented | | **Reading old versions** | Record versions with a storage backend | Built in | | **Writes** | One key at a time, or grouped in a session | Batches, each with a version number | If your validator needs to check a proof, use MPF. ## Dependencies RocksDB is the recommended storage. PostgreSQL, H2 and SQLite are alternatives, through the `-rdbms` modules. The storage modules bring in their core module. ```gradle def cclVersion = '' // the latest preview dependencies { // MPF implementation "com.bloxbean.cardano:cardano-client-merkle-patricia-forestry-rocksdb:$cclVersion" // JMT implementation "com.bloxbean.cardano:cardano-client-jellyfish-merkle-rocksdb:$cclVersion" } ``` SQL storage never creates its tables by itself. Call `RdbmsMpfSchema.createIfNotExists(dbConfig)` for MPF or `RdbmsJmtSchemaInstaller.createIfNotExists(dbConfig)` for JMT once, or apply the bundled DDL from `/ddl/{mpf,jmt}/{postgres,h2,sqlite}/schema.sql` with your migration tool. See the [MPF](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/merkle-patricia-forestry-rdbms) and [JMT](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/jellyfish-merkle-rdbms) module READMEs. ## Next steps - [MpfTrie](/verified-structures/mpf/): store data and generate proofs for Aiken. - [Jellyfish Merkle Tree](/verified-structures/jmt/): versioned state and proofs. - [MPF versions and garbage collection](/verified-structures/multi-version-gc/) and the [MPF production guide](/verified-structures/production-guide/). --- # MPF production guide Source: https://cardano-client.dev/verified-structures/production-guide/ Summary: Atomic writes, RocksDB tuning, thread safety, backup and shutdown for MpfTrie. > **Preview feature**: available in the [latest preview](/start-here/installation/#versions). APIs may change before the stable release. ## Atomic writes `RocksDbMptSession` runs your work in one RocksDB write batch and commits it at the end. Nodes, the version entry and refcounts land together. ```java import com.bloxbean.cardano.vds.mpf.rocksdb.RocksDbMptSession; MpfTrie trie = new MpfTrie(stateTrees.nodeStore()); RocksDbMptSession session = RocksDbMptSession.of(stateTrees); long version = session.write(() -> { trie.put("alice".getBytes(), "100".getBytes()); trie.put("bob".getBytes(), "200".getBytes()); return stateTrees.putRootWithRefcount(trie.getRootHash()); }); ``` Inside a session, write only through the store APIs. A batch is bound to the calling thread. For a bulk load that you can repeat, you can trade durability for speed by turning off the write-ahead log with `RocksDbMptSession.of(stateTrees, Options.builder().disableWal(true).build())`. Writes that aren't flushed yet are then lost on a crash or close, and they aren't in a backup. ## Storage layout `RocksDbStateTrees` keeps two column families: ```text nodes: [ns][node hash] -> CBOR-encoded node [ns][0xF0][node hash] -> refcount (refcount GC only) roots: [ns][version] -> root hash [ns]"LATEST" -> latest root [ns]"VERSION" -> last version number [ns]"_storage_mode" -> MULTI_VERSION or SINGLE_VERSION ``` `[ns]` is the namespace's one-byte key prefix, `0x00` by default. To keep several tries in one database, pass `NamespaceOptions.keyPrefix((byte) 0x01)` or `NamespaceOptions.columnFamily("accounts")` to the constructor. A RocksDB directory can be opened by only one `RocksDbStateTrees` at a time. Open namespaces one after another, or build `RocksDbNodeStore` and `RocksDbRootsIndex` instances over one shared `RocksDB` handle. ## RocksDB tuning `RocksDbConfig` has the profiles `highThroughput()`, `balanced()`, `lowMemory()` and `defaults()`, plus a builder. Pass one to the constructor, and measure with your own workload: ```java RocksDbStateTrees(String dbPath, NamespaceOptions namespace, StorageMode mode, RocksDbConfig config) ``` See the [rocksdb-core README](https://github.com/bloxbean/cardano-client-lib/tree/master/verified-structures/rocksdb-core) for its options. ## Thread safety - `MpfTrie` isn't thread-safe. Use one instance per thread, or synchronize access. - The backend has no concurrency control: serialize writers of a namespace. - Pause writers while [garbage collection](/verified-structures/multi-version-gc/) runs. Its snapshot doesn't protect re-puts of existing node keys. ## Monitoring ```java String stats = stateTrees.db().getProperty("rocksdb.stats"); String size = stateTrees.db().getProperty("rocksdb.total-sst-files-size"); ``` ## Backup and restore Use RocksDB's own backup engine, with writers paused: ```java try (BackupEngine backup = BackupEngine.open(Env.getDefault(), new BackupEngineOptions(backupPath))) { backup.createNewBackup(stateTrees.db(), true); // true flushes first } try (BackupEngine restore = BackupEngine.open(Env.getDefault(), new BackupEngineOptions(backupPath)); RestoreOptions options = new RestoreOptions(false)) { restore.restoreDbFromLatestBackup(dbPath, dbPath, options); } ``` ## Shutdown Close `RocksDbStateTrees`, ideally with try-with-resources. It closes the database and its resources. ---