如何在Corda中使用Cash State?附示例及简易场景可行性咨询
Hey there! Let's dive deep into working with the Cash State in Corda — I'll walk you through key concepts, practical code examples, and whether it's a fit for simple demo scenarios.
The Cash State is a built-in, pre-audited state type in Corda's finance module (net.corda.finance.contracts.asset.Cash) designed to represent fungible cash assets. It handles core cash operations like issuance, transfer, and redemption out of the box, so you don't have to build a custom asset contract from scratch.
Key traits of Cash State:
- Supports multiple currencies (via
Amount<Currency>type) - Allows splitting/merging of cash amounts (e.g., splitting a $100 state into $50 and $50 for a transfer)
- Enforces security rules (only the issuer can destroy cash, only the owner can transfer it)
Before jumping into code, let's clarify the critical components of Cash State:
- Issuer: The node that creates the cash (e.g., a bank node in production, a test node in demos)
- Owner: The node currently holding the cash state
- Amount: A value paired with a currency code (e.g.,
Amount(10000, Currency.getInstance("USD"))for $100.00) - Issue: The process of creating new cash states
- Move: Transferring cash from one owner to another (splitting/merging states as needed)
- Exit: Destroying cash states (only allowed by the original issuer)
Let's walk through the most common Cash State operations with Kotlin code (Corda's primary language).
3.1 Add Dependencies
First, make sure your project includes the Corda finance module in build.gradle:
dependencies { cordaCompile "$corda_release_group:corda-finance:$corda_release_version" }
3.2 Issuing Cash (Creating Cash States)
To create cash, the issuer node runs a flow that generates new Cash States. Here's a simplified example:
@InitiatingFlow @StartableByRPC class IssueCashFlow( private val amount: Amount<Currency>, private val recipient: Party ) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { // Get the issuer's identity (current node) val issuer = ourIdentity // Create the Cash State: issuer is us, recipient is the target owner val cashState = Cash.State(amount, issuer, recipient) // Build the transaction val notary = serviceHub.networkMapCache.notaryIdentities.first() val txBuilder = TransactionBuilder(notary) .addOutputState(cashState, Cash.PROGRAM_ID) .addCommand(Cash.Commands.Issue(), issuer.owningKey) // Validate the transaction against the Cash contract rules txBuilder.verify(serviceHub) // Sign the transaction and finalize it with the recipient val signedTx = serviceHub.signInitialTransaction(txBuilder) val recipientSession = initiateFlow(recipient) val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(recipientSession))) return subFlow(FinalityFlow(fullySignedTx, listOf(recipientSession))) } }
You can trigger this flow via RPC to issue, say, $500 to a customer node in your test network.
3.3 Transferring Cash (Moving Cash States)
Transferring cash involves consuming existing Cash States and creating new ones with the new owner. Corda's Cash.generateMove() helper handles splitting/merging logic automatically:
@InitiatingFlow @StartableByRPC class TransferCashFlow( private val amount: Amount<Currency>, private val recipient: Party ) : FlowLogic<SignedTransaction>() { override fun call(): SignedTransaction { // Query the vault for Cash States owned by the current node val ownedCashStates = serviceHub.vaultService.queryBy(Cash.State::class.java).states .filter { it.state.data.owner == ourIdentity } .filter { it.state.data.amount.token == amount.token } // Ensure we have enough cash to transfer val totalBalance = ownedCashStates.sumOf { it.state.data.amount.quantity } require(totalBalance >= amount.quantity) { "Insufficient cash balance" } // Build the transaction using Corda's built-in move helper val notary = serviceHub.networkMapCache.notaryIdentities.first() val txBuilder = TransactionBuilder(notary) Cash.generateMove(serviceHub, txBuilder, amount, recipient, ownedCashStates) // Add the transfer command (requires the current owner's signature) txBuilder.addCommand(Cash.Commands.Move(), ourIdentity.owningKey) // Validate and sign the transaction txBuilder.verify(serviceHub) val signedTx = serviceHub.signInitialTransaction(txBuilder) // Collect the recipient's signature and finalize the transaction val recipientSession = initiateFlow(recipient) val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(recipientSession))) return subFlow(FinalityFlow(fullySignedTx, listOf(recipientSession))) } }
This flow will split your existing cash states as needed — for example, if you have a $100 state and transfer $30, it will create a $30 state for the recipient and a $70 state for you.
3.4 Querying Cash Balances
To check your node's cash holdings, query the vault and aggregate the amounts:
// Run this in a flow or RPC client val cashBalances = serviceHub.vaultService.queryBy(Cash.State::class.java).states .filter { it.state.data.owner == ourIdentity } .groupBy { it.state.data.amount.token } .mapValues { entry -> Amount(entry.value.sumOf { it.state.data.amount.quantity }, entry.key) } // Print the results cashBalances.forEach { (currency, balance) -> println("Current balance for ${currency.code}: $balance") }
Absolutely — Cash State is perfect for simple demos! Here are a few ideal use cases:
- Peer-to-peer payment demo: Simulate two nodes sending cash to each other to showcase Corda's asset transfer workflow
- Mini supply chain finance: Have a "core enterprise" node issue cash to "supplier" nodes as payment for goods
- Test currency simulation: Create a fake test currency (e.g., "COR") and use it to demonstrate issuance, transfers, and balance tracking
Why it works for demos:
- No need to write custom contract logic — Corda's built-in Cash contract handles all validation rules
- The splitting/merging logic is abstracted away, so you can focus on core workflow
- It's easy to set up in a local test network with just a few nodes
The only thing to note is that in demos, your "issuer" node can be any test node (you don't need a real bank) — it's purely for testing purposes.
- Notary selection: Always use a notary that exists in your network (use
networkMapCache.notaryIdentities.first()for tests) - Balance validation: Never skip checking if you have enough cash before a transfer — the Cash contract will reject the transaction if you don't
- Signature requirements: Issuance flows need the issuer's signature; transfer flows need the current owner's signature — missing these will cause validation failures
内容的提问来源于stack exchange,提问作者user4473151

