/cardanosharp-wallet

CardanoSharp.Wallet is a Cardano Cryptographic and Serialization library for .NET applications.

Primary LanguageC#MIT LicenseMIT

CardanoSharp.Wallet

Build status Test status NuGet Version NuGet Downloads

CardanoSharp Wallet is a .NET library for Creating/Managing Wallets and Building/Signing Transactions.

Getting Started

CardanoSharp.Wallet is installed from NuGet.

Install-Package CardanoSharp.Wallet

Create Mnemonics

The MnemonicService has operations tbat help with generating and restoring Mnemonics. It is built for use in DI containers (ie. the interface IMnemonicService).

IMnemonicService service = new MnemonicService();

Generate Mnemonic

IMnemonicService service = new MnemonicService();
Mnemonic rememberMe = service.Generate(24, WordLists.English);
System.Console.WriteLine(rememberMe.Words);

Restore Mnemonic

string words = "art forum devote street sure rather head chuckle guard poverty release quote oak craft enemy";
Mnemonic mnemonic = MnemonicService.Restore(words);

Create Private and Public Keys

Use powerful extensions to create and derive keys.

// The rootKey is a PrivateKey made of up of the 
//  - byte[] Key
//  - byte[] Chaincode
PrivateKey rootKey = mnemonic.GetRootKey();

// This path will give us our Payment Key on index 0
string paymentPath = $"m/1852'/1815'/0'/0/0";
// The paymentPrv is Private Key of the specified path.
PrivateKey paymentPrv = rootKey.Derive(paymentPath);
// Get the Public Key from the Private Key
PublicKey paymentPub = paymentPrv.GetPublicKey(false);

// This path will give us our Stake Key on index 0
string stakePath = $"m/1852'/1815'/0'/2/0";
// The stakePrv is Private Key of the specified path
PrivateKey stakePrv = rootKey.Derive(stakePath);
// Get the Public Key from the Stake Private Key
PublicKey stakePub = stakePrv.GetPublicKey(false);

If you want to learn more about key paths, read this article About Address Derivation

Create Addresses

The AddressService lets you Create Addresses from Keys. It is built for use in DI containers (ie. the interface IAddressService)

IAddressService addressService = new AddressService();

From the public keys we generated above, we can now get the public address.

// add using
using CardanoSharp.Wallet.Models.Addresses;

// Creating Addresses require the Public Payment and Stake Keys
Address baseAddr = addressService.GetAddress(
    paymentPub, 
    stakePub, 
    NetworkType.Testnet, 
    AddressType.Base);

If you already have an address.

Address baseAddr = new Address("addr_test1qz2fxv2umyhttkxyxp8x0dlpdt3k6cwng5pxj3jhsydzer3jcu5d8ps7zex2k2xt3uqxgjqnnj83ws8lhrn648jjxtwq2ytjqp");
// Get Address Type
AddressType addressType = baseAddr.AddressType;
// Get Network Type
NetworkType networkType = baseAddr.NetworkType
// Output the original string address
string address = baseAddr.ToString();

Fluent Key Derivation

A fluent API helps navigate the derivation paths.

// Add using
using CardanoSharp.Wallet.Extensions.Models;

// Restore a Mnemonic
var mnemonic = new MnemonicService().Restore(words);

// Fluent derivation API
var derivation = mnemonic
    .GetMasterNode("password")      // IMasterNodeDerivation
    .Derive(PurposeType.Shelley)    // IPurposeNodeDerivation
    .Derive(CoinType.Ada)           // ICoinNodeDerivation
    .Derive(0)                      // IAccountNodeDerivation
    .Derive(RoleType.ExternalChain) // IRoleNodeDerivation
    //or .Derive(RoleType.Staking) 
    .Derive(0);                     // IIndexNodeDerivation

PrivateKey privateKey = derivation.PrivateKey;
PublicKey publicKey = derivation.PublicKey;

Build and Sign Transactions

CardanoSharp.Wallet requires input from the chain in order to build transactions. Lets assume we have gathered the following information.

uint currentSlot = 40000000;
ulong minFeeA = 44;
ulong minFeeB = 155381;
string inputTx = "0000000000000000000000000000000000000000000000000000000000000000";

Lets derive a few keys to use while building transactions.

// Derive down to our Account Node
var accountNode = rootKey.Derive()
    .Derive(PurposeType.Shelley)
    .Derive(CoinType.Ada)
    .Derive(0);

// Derive our Change Node on Index 0
var changeNode = accountNode
    .Derive(RoleType.InternalChain) 
    .Derive(0);

// Derive our Staking Node on Index 0
var stakingNode = accountNode
    .Derive(RoleType.Staking) 
    .Derive(0);

// Deriving our Payment Node
//  note: We did not derive down to the index.
var paymentNode = accountNode
    .Derive(RoleType.ExternalChain);

Simple Transaction

Lets assume the following...

  • You have 100 ADA on path: m/1852'/1815'/0'/0/0
  • You want to send 25 ADA to path: m/1852'/1815'/0'/0/1

Build Transaction Body

// Generate the Recieving Address
Address paymentAddr = addressService.GetAddress(
    paymentNode.Derive(1).PublicKey, 
    stakingNode.PublicKey, 
    NetworkType.Testnet, 
    AddressType.Base);

// Generate an Address for changes
Address changeAddr = addressService.GetAddress(
    changeNode.PublicKey, 
    stakingNode.PublicKey, 
    NetworkType.Testnet, 
    AddressType.Base);

var transactionBody = TransactionBodyBuilder.Create
    .AddInput(inputTx, 0)
    .AddOutput(paymentAddr, 25)
    .AddOutput(changeAddr, 75)
    .SetTtl(currentSlot + 1000)
    .SetFee(0)
    .Build();

Build Transaction Witnesses

For this simple transaction we really only need to add our keys. This is how we sign our transactions.

// Derive Sender Keys
var senderKeys = paymentNode.Derive(0);

var witnesses = TransactionWitnessSetBuilder.Create
    .AddVKeyWitness(senderKeys.PublicKey, senderKeys.PrivateKey);

Calculate Fee

// Create a Transaction
var transaction = TransactionBuilder.Create
    .SetBody(transactionBody)
    .SetWitnesses(witnesses)
    .Build();

// Calculate Fee
var fee = transaction.CalculateFee(minFeeA, minFeeB);

// Update Fee and Rebuild
transactionBody.SetFee(fee);
transaction = transactionBuilder.Build();
transaction.TransactionBody.TransactionOutputs.Last().Value.Coin -= fee;

Metadata Transaction

Building the Body and Witnesses are the same as the Simple Transaction.

If you would like to read more about Metadata, please read this article on Tx Metadata

// Build Metadata and Add to Transaction
var auxData = AuxiliaryDataBuilder.Create
    .AddMetadata(1234, new { name = "simple message" });

var transaction = TransactionBuilder.Create
    .SetBody(transactionBody)
    .SetWitnesses(witnesses)
    .SetAuxData(auxData)
    .Build();

Minting Transaction

Before we can mint a token, we need to create a policy.

If you would like to read more about policy scripts, please read this article on Simple Scripts.

// Generate a Key Pair for your new Policy
var keyPair = KeyPair.GenerateKeyPair();
var policySkey = keyPair.PrivateKey;
var policyVkey = keyPair.PublicKey;
var policyKeyHash = HashUtility.Blake2b244(policyVkey.Key);

// Create a Policy Script with a type of Script All
var policyScript = ScriptAllBuilder.Create
    .SetScript(NativeScriptBuilder.Create.SetKeyHash(policyKeyHash))
    .Build();

// Generate the Policy Id
var policyId = policyScript.GetPolicyId();

Now lets define our token.

// Create the AWESOME Token
string tokenName = "AWESOME";
uint tokenQuantity = 1;

var tokenAsset = TokenBundleBuilder.Create
    .AddToken(policyId, tokenName.ToBytes(), tokenQuantity);

When minting, we will need to add our new token to one of the outputs of our Transaction Body.

// Generate an Address to send the Token
Address baseAddr = addressService.GetAddress(
    paymentNode.Derive(1).PublicKey, 
    stakingNode.PublicKey, 
    NetworkType.Testnet, 
    AddressType.Base);

// Build Transaction Body with Token Bundle
var transactionBody = TransactionBodyBuilder.Create
    .AddInput(inputTx, 0)
    // Sending to Base Address, includes 100 ADA and the Token we are minting
    .AddOutput(baseAddr, 100, tokenAsset)
    .SetTtl(currentSlot + 1000)
    .SetFee(0)
    .Build();

Handling Token Bundles

When building transaction, we need to ensure we handle tokens properly.

var tokenBundle = TokenBundleBuilder.Create
    .AddToken(policyId, "Token1".ToBytes(), 100)
    .AddToken(policyId, "Token2".ToBytes(), 200);

Address baseAddr = addressService.GetAddress(
    paymentNode.Derive(1).PublicKey, 
    stakingNode.PublicKey, 
    NetworkType.Testnet, 
    AddressType.Base);

var transactionBody = TransactionBodyBuilder.Create
    .AddInput(inputTx, 0)
    .AddOutput(baseAddr, 2, tokenBundle)
    .AddOutput(changeAddr, 98)
    .SetTtl(currentSlot + 1000)
    .SetFee(0)
    .Build();

Serialize the Transaction

var signedTx = transaction.Serialize();