wallet_connect_cardano
A WalletConnect v2 communication bridge for Cardano wallets, implementing CIP-30 over the WalletConnect relay protocol.
⚠️ This SDK is a transport layer, not a signing engine. It routes CIP-30 JSON-RPC requests from dApps to your wallet app via callbacks. Your wallet app performs the actual signing, balance queries, and UTXO management.
What it does
When a dApp connects to your Cardano wallet via WalletConnect:
- dApp sends a CIP-30 request (e.g.,
cardano_signTx) over WalletConnect - SDK receives and deserializes the JSON-RPC request
- SDK routes to your delegate — calls the corresponding method on your
CardanoWalletDelegateimplementation - Your wallet app does the work — signs the transaction, queries UTXOs, submits to the network, etc.
- SDK returns the response — serializes the result and sends it back to the dApp over WalletConnect
The SDK handles all WalletConnect session management, CIP-30 method routing, and error handling. You implement the CardanoWalletDelegate interface to provide the actual wallet functionality.
Installation
Add wallet_connect_cardano to your pubspec.yaml:
dependencies:
wallet_connect_cardano: ^0.1.0
Then run:
flutter pub get
The SDK depends on reown_walletkit for WalletConnect v2 transport. This dependency is automatically included.
Quick Start
1. Implement CardanoWalletDelegate
Create a class that implements CardanoWalletDelegate:
import 'package:wallet_connect_cardano/wallet_connect_cardano.dart';
class MyWalletDelegate implements CardanoWalletDelegate {
@override
Future<int> getNetworkId() async {
// Return 0 for testnet, 1 for mainnet
return 1;
}
@override
Future<List<String>?> getUtxos({String? amount, CardanoPaginate? paginate}) async {
// Return list of hex-encoded CBOR TransactionUnspentOutput values
// or null if pagination exceeded
return ['hex_encoded_utxo_1', 'hex_encoded_utxo_2'];
}
@override
Future<List<String>?> getCollateral({required String amount}) async {
// Return UTXOs suitable for the requested collateral amount.
// CIP-30 deprecates this method, but existing dApps may still call it.
return ['hex_encoded_collateral_utxo'];
}
@override
Future<String> getBalance() async {
// Return hex-encoded CBOR value (lovelace + multi-asset)
return 'a1581c...'; // CBOR hex
}
@override
Future<List<String>> getUsedAddresses({CardanoPaginate? paginate}) async {
// Return list of hex-encoded payment addresses
return ['01ab...', '01cd...'];
}
@override
Future<List<String>> getUnusedAddresses() async {
// Return list of hex-encoded unused payment addresses
return ['01ef...'];
}
@override
Future<String> getChangeAddress() async {
// Return hex-encoded change address
return '01ab...';
}
@override
Future<List<String>> getRewardAddresses() async {
// Return list of hex-encoded reward (staking) addresses
return ['e1ab...'];
}
@override
Future<String> signTx(String tx, {bool partialSign = false}) async {
// Sign the transaction (hex-encoded CBOR)
// Return hex-encoded CBOR witness set
// Throw CardanoTxSignError on failure
return 'a200...'; // CBOR hex
}
@override
Future<CardanoDataSignature> signData(String address, String payload) async {
// Sign arbitrary data with the given address
// Return CardanoDataSignature with signature and key (both hex-encoded CBOR)
// Throw CardanoDataSignError on failure
return CardanoDataSignature(
signature: 'd8...', // CBOR hex
key: 'a4...', // CBOR hex
);
}
@override
Future<String> submitTx(String tx) async {
// Submit signed transaction to the network
// Return transaction hash as hex-encoded string
// Throw CardanoTxSendError on failure
return 'a1b2c3d4...'; // tx hash hex
}
@override
Future<List<CardanoExtension>> getExtensions() async {
// Return list of supported CIP extensions
// Return empty list if no extensions supported
return [];
}
}
2. Initialize the SDK
import 'package:reown_walletkit/reown_walletkit.dart';
import 'package:wallet_connect_cardano/wallet_connect_cardano.dart';
void main() async {
final delegate = MyWalletDelegate();
final walletConnect = WalletConnectCardano(
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
metadata: PairingMetadata(
name: 'My Cardano Wallet',
description: 'A Cardano wallet with WalletConnect support',
url: 'https://mywalletapp.com',
icons: ['https://mywalletapp.com/icon.png'],
),
delegate: delegate,
chainIds: [WalletConnectCardano.mainnet], // or .preprod, .preview
);
await walletConnect.initialize();
// Listen for session proposals from dApps
walletConnect.onSessionProposal.listen((SessionProposalEvent event) {
// Show UI to user asking to approve/reject
// Then call approveSession or rejectSession
});
}
3. Handle Session Proposals
walletConnect.onSessionProposal.listen((SessionProposalEvent event) async {
// User approved the connection
await walletConnect.approveSession(
id: event.id,
accountAddress: 'addr1qy2...', // The user's payment address
chainId: WalletConnectCardano.mainnet,
);
// Or reject if user declined
// await walletConnect.rejectSession(
// id: event.id,
// reason: ReownSignError(code: 5000, message: 'User rejected'),
// );
});
4. Pair with a dApp
// Get the WalletConnect URI from the dApp (usually via QR code)
final uri = Uri.parse('wc:...');
// Pair with the dApp
final pairingInfo = await walletConnect.pair(uri: uri);
CardanoWalletDelegate
Your wallet app must implement all 12 methods of CardanoWalletDelegate:
| Method | Parameters | Returns | Throws |
|---|---|---|---|
getNetworkId() |
none | int (0=testnet, 1=mainnet) |
— |
getUtxos() |
amount?, paginate? |
List<String>? (hex CBOR) |
— |
getCollateral() |
amount |
List<String>? (hex CBOR) |
CardanoApiError |
getBalance() |
none | String (hex CBOR value) |
— |
getUsedAddresses() |
paginate? |
List<String> (hex addresses) |
— |
getUnusedAddresses() |
none | List<String> (hex addresses) |
— |
getChangeAddress() |
none | String (hex address) |
— |
getRewardAddresses() |
none | List<String> (hex addresses) |
— |
signTx() |
tx, partialSign? |
String (hex CBOR witness) |
CardanoTxSignError |
signData() |
address, payload |
CardanoDataSignature |
CardanoDataSignError |
submitTx() |
tx |
String (tx hash hex) |
CardanoTxSendError |
getExtensions() |
none | List<CardanoExtension> |
— |
All address, transaction, and CBOR values are lowercase hex-encoded strings. The SDK never encodes or decodes CBOR itself — that is the responsibility of your wallet app and the dApp.
Session Management
Approve a Session
await walletConnect.approveSession(
id: proposalEvent.id,
accountAddress: 'addr1qy2...',
chainId: WalletConnectCardano.mainnet,
);
Reject a Session
await walletConnect.rejectSession(
id: proposalEvent.id,
reason: ReownSignError(code: 5000, message: 'User rejected'),
);
Disconnect a Session
await walletConnect.disconnectSession(
topic: sessionTopic,
reason: ReownSignError(code: 5000, message: 'User disconnected'),
);
Get Active Sessions
final sessions = walletConnect.getActiveSessions();
for (final session in sessions.values) {
print('Session: ${session.topic}');
}
Emit Account Change
When the user switches accounts in your wallet:
await walletConnect.emitAccountChange(
topic: sessionTopic,
chainId: WalletConnectCardano.mainnet,
newAddress: 'addr1qz3...',
);
Emit Network Change
When the user switches networks:
await walletConnect.emitNetworkChange(
topic: sessionTopic,
newChainId: WalletConnectCardano.preprod,
);
CIP-30 Methods Reference
The SDK routes these CIP-30 JSON-RPC methods from dApps to your delegate:
| JSON-RPC Method | CIP-30 Method | Description |
|---|---|---|
cardano_getExtensions |
getExtensions() |
List supported CIP extensions |
cardano_getNetworkId |
getNetworkId() |
Get current network (0=testnet, 1=mainnet) |
cardano_getBalance |
getBalance() |
Get total wallet balance |
cardano_getUtxos |
getUtxos() |
Get unspent transaction outputs |
cardano_getCollateral |
getCollateral() |
Get suitable collateral UTXOs (deprecated by CIP-30) |
cardano_getUsedAddresses |
getUsedAddresses() |
Get addresses that have received funds |
cardano_getUnusedAddresses |
getUnusedAddresses() |
Get addresses that have never received funds |
cardano_getChangeAddress |
getChangeAddress() |
Get change address |
cardano_getRewardAddresses |
getRewardAddresses() |
Get staking addresses |
cardano_getRewardAddress |
getRewardAddresses() |
Get first staking address (singular variant) |
cardano_signTx |
signTx() |
Sign a transaction |
cardano_signData |
signData() |
Sign arbitrary data |
cardano_submitTx |
submitTx() |
Submit signed transaction to network |
Error Handling
The SDK propagates CIP-30 errors from your delegate back to the dApp. Throw the appropriate error type in your delegate methods:
CardanoApiError
General API errors:
throw CardanoApiError(
code: CardanoApiError.invalidRequest,
info: 'Invalid parameters',
);
Error codes:
invalidRequest(-1): Invalid parametersinternalError(-2): Internal wallet errorrefused(-3): Request refused by user or walletaccountChange(-4): Account changed since request
CardanoTxSignError
Transaction signing errors:
throw CardanoTxSignError(
code: CardanoTxSignError.userDeclined,
info: 'User declined to sign',
);
Error codes:
proofGeneration(1): Could not construct witnessuserDeclined(2): User declined the request
CardanoDataSignError
Data signing errors:
throw CardanoDataSignError(
code: CardanoDataSignError.addressNotPK,
info: 'Address is not a public key address',
);
Error codes:
proofGeneration(1): Could not construct proofaddressNotPK(2): Address is not P2PKHuserDeclined(3): User declined the request
CardanoTxSendError
Transaction submission errors:
throw CardanoTxSendError(
code: CardanoTxSendError.failure,
info: 'Transaction failed to submit',
);
Error codes:
refused(1): Transaction was refusedfailure(2): Transaction failed to submit
Configuration
Chain IDs
The SDK supports three Cardano networks:
WalletConnectCardano.mainnet // 'cip34:1-764824073'
WalletConnectCardano.preprod // 'cip34:0-1'
WalletConnectCardano.preview // 'cip34:0-2'
Pass one or more chain IDs when creating the SDK:
final walletConnect = WalletConnectCardano(
projectId: 'YOUR_PROJECT_ID',
metadata: metadata,
delegate: delegate,
chainIds: [
WalletConnectCardano.mainnet,
WalletConnectCardano.preprod,
],
);
Wallet Metadata
Provide metadata about your wallet that will be shown to dApps:
final metadata = PairingMetadata(
name: 'My Cardano Wallet',
description: 'A secure Cardano wallet with WalletConnect support',
url: 'https://mywalletapp.com',
icons: ['https://mywalletapp.com/icon-192.png'],
);
WalletConnect Project ID
Get a free project ID from WalletConnect Cloud:
final walletConnect = WalletConnectCardano(
projectId: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
metadata: metadata,
delegate: delegate,
);
Development
The repository pins its Flutter toolchain in .tool-versions. Before opening a
change:
flutter pub get
dart format --output=none --set-exit-if-changed lib test example/lib/config.dart \
example/lib/delegate/demo_wallet_delegate.dart \
example/lib/wallet/demo_wallet.dart example/test
dart analyze
flutter test
(cd example && flutter analyze && flutter test)
(cd demo/dapp-web && npm ci && npm run build)
dart pub publish --dry-run
The same checks run in GitHub Actions for every pull request.
References
- CIP-30: Cardano dApp-Wallet Web Bridge
- CIP-34: Chain ID Registry
- Reown WalletKit Documentation
- WalletConnect
License
MIT
Libraries
- wallet_connect_cardano
- WalletConnect v2 communication bridge for Cardano wallets implementing CIP-30.