Complete the escrow lifecycle by implementing fund unlocking functionality to allow users to retrieve their locked funds.
Introduction
In this final part, we'll implement the fund unlocking functionality to complete the escrow lifecycle. This will allow users to retrieve their funds from the escrow contract once they're ready. The unlocking process requires signature verification to ensure that only the rightful owner can access the funds.
Understanding the Fund Unlocking Process
The fund unlocking process involves these steps:
Transaction Verification: The system checks that the transaction exists and is in the CONFIRMED(i.e. Locked) state
Ownership Validation: The request must come from the same wallet that originally locked the funds
Transaction Building: The application builds an unlock transaction that spends the locked UTXO
Transaction Signing: The user signs the transaction with their wallet
Transaction Submission: The signed transaction is submitted to the blockchain
Status Update: The transaction status is updated to UNLOCKED in the database
Understanding the Smart Contract Validation
Our escrow application uses the Hello Aiken smart contract with two unlock conditions:
Redeemer Message Validation: The redeemer must contain the exact message "Hello, World!" (hex-encoded)
Owner Signature Verification: The transaction must be signed by the owner specified in the datum
The code implements these requirements through:
redeemer: {
type: "json",
value: {
// The Hello, World! message must match what's expected in the smart contract
// The Hello, World message is hardcoded in the smart contract
msg: Buffer.from("Hello, World!", "utf8").toString("hex"),
},
},
// Ensure the transaction includes the owner's signature
requiredSigners: [signerKeyHash],
This dual validation ensures only the rightful owner can unlock their funds, while demonstrating the flexibility of Cardano's eUTXO model for custom validation logic.
Implementation Steps
1. Add Unlock Functions to the Anvil API Module
First, let's add the necessary functions to the Anvil API module for unlocking funds. Add unlockFunds to your existing src/lib/anvil-api.ts file:
// Interface for unlock funds parameters
export interface UnlockFundsParams {
txHash: string;
changeAddress: string;
ownerKeyHash: string; // Key hash for requiredSigners
unlockReason?: string;
}
// Interface for the unlock funds response
export interface UnlockFundsResponse {
complete?: string;
error?: string;
}
/**
* Unlock funds from the escrow smart contract
* This creates a transaction that spends the UTXO with the specified redeemer
*/
export async function unlockFunds(
params: UnlockFundsParams
): Promise<UnlockFundsResponse> {
try {
// Get the validator hash from environment
const validatorHash = process.env.ESCROW_VALIDATOR_HASH;
if (!validatorHash) {
throw new Error('Escrow validator hash not found');
}
// Derive owner payment key hash for requiredSigners
const signerKeyHash = await getAddressKeyHash(params.changeAddress);
const input = {
changeAddress: params.changeAddress,
message: params.unlockReason || 'Unlocking funds using Anvil API',
scriptInteractions: [
{
hash: validatorHash,
purpose: 'spend',
outputRef: {
txHash: params.txHash,
// Output index of the UTXO to spend. Anvil stores the outputs in the same order (starting at 0) as we originally locked them.
// Our demo application locks them one at a time, so the index will always be 0.
index: 0,
},
redeemer: {
type: "json",
value: {
msg: Buffer.from("Hello, World!", "utf8").toString("hex"),
},
},
},
],
requiredSigners: [signerKeyHash],
};
// Build the transaction using our generic fetch utility
const result = await fetchApi<{ complete: string }>(
`/transactions/build`,
{
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(input),
},
'build unlock transaction'
);
return { complete: result.complete };
} catch (error: unknown) {
return { error: handleApiError('unlock funds', error) };
}
}
2. Create the Unlock API Endpoint
Create an API endpoint for unlocking funds src/app/api/escrow/unlock/route.ts:
Several security measures are implemented in the unlocking process:
Status Verification: Only transactions in the CONFIRMED state can be unlocked
Address Validation: The transaction is validated against the owner's address
Smart Contract Validation: The escrow script verifies that the unlocking is performed by the original owner
Testing the Complete Escrow Cycle
Let's test the full escrow cycle:
Start your development server:
npm run dev
Navigate to http://localhost:3000 in your browser
Connect your wallet
Lock funds using the lock funds form from Part 3
Wait for the transaction to be confirmed (You'll see the status change in the MyTransactions component)
Click the "Unlock Funds" button on the confirmed transaction
Sign the transaction with your wallet
Observe the transaction status changing to UNLOCKED
Verify that the funds have been returned to your wallet (minus network fees)
Troubleshooting
Transaction Not Unlockable
If you can't unlock a transaction:
Verify that the transaction status is CONFIRMED (only confirmed transactions can be unlocked)
Ensure you're using the same wallet that created the transaction
Check that the network has processed the original transaction (check explorers like cardanoscan.io)
Signing Failures
If signing the unlock transaction fails:
Make sure your wallet is unlocked and connected
Check that your wallet has sufficient balance for the transaction fee
Try disconnecting and reconnecting your wallet
Enhancements for a Production Application
For a production-ready escrow application, consider these enhancements:
Time-locked Escrow: Add support for time-based unlocking conditions
Multi-signature Escrow: Implement escrow that requires approval from multiple parties
Improved Error Handling: Add more detailed error messages and recovery options
Transaction Monitoring: Implement more robust transaction status monitoring
Sign Lock/Unlock: Add support for signing lock and unlock transactions after users leave the page.
Enhanced Security: Add additional verification steps for high-value transactions
Congratulations! You've completed all six parts of the Cardano Escrow guide. Your application now offers a complete escrow solution using the Cardano blockchain and Anvil API.
Conclusion
In this guide series, you've built a fully functional Cardano escrow application that allows users to:
Connect their Cardano wallets
Lock funds in an escrow contract
Monitor transaction status in real-time
Unlock funds when they're ready
You've learned how to:
Integrate with Cardano wallets using Weld
Build and submit transactions using the Anvil API
Store and track transaction data in a SQLite database
Implement real-time updates with webhooks
Create a complete fund locking and unlocking cycle
This application provides a foundation for building more complex Cardano applications that leverage smart contracts for secure and transparent financial interactions.