A .NET library built to make creating monetary systems easy. Designed primarily for TTRPG applications, CoinPurse handles custom currencies, automatic conversions, unique containers, weight/capacity, transactions, and balance tracking. It's flexible enough to support inventories of any complexity and provides a simple framework for retail.
License
—
Deps
0
Install Size
—
Vulns
✓ 0
Published
Feb 27, 2026
$ dotnet add package CoinPurseA lightweight .NET library for managing currency containers in games, simulations, or any application that needs flexible coin and currency tracking. CoinPurse supports multiple currency types, coin and weight capacity enforcement, and currency conversion.
Install via NuGet Package Manager:
Install-Package CoinPurse
Or via the .NET CLI:
dotnet add package CoinPurse
.NET Standard 2.0 compatible runtime or higher, including .NET Framework 4.6.1+, .NET Core 2.0+, and .NET 5–10.
Create Currency objects to represent each denomination in your system. The BaseValue represents how many of the smallest unit make up one of this coin — for example, 100 copper to 1 silver.
var copper = new Currency(baseValue: 1, coinName: "Copper", weight: 0.09);
var silver = new Currency(baseValue: 10, coinName: "Silver", weight: 0.09);
var gold = new Currency(baseValue: 100, coinName: "Gold", weight: 0.09);
A Container is a coin purse or pouch that holds your currencies. You can set a maximum coin count and weight capacity.
// Default container: 50 coin capacity, 50oz weight capacity, named "Coin Purse"
var purse = new Container();
// Custom capacity and name
var pouch = new Container(coinCapacity: 100, weightCapacity: 20.0, containerName: "Belt Pouch");
// Pre-load with currency types (no coins yet)
var purse = new Container(
currencies: new List { copper, silver, gold },
coinCapacity: 100,
containerName: "Adventurer's Purse"
);
// Pre-load with currencies and starting coins
var purse = new Container(
coins: new Dictionary { { copper, 50 }, { silver, 10 } },
coinCapacity: 100,
containerName: "Starting Purse"
);
purse.AddToCoinPurse(copper, 25);
purse.RemoveFromCoinPurse(copper, 10);
int totalCoins = purse.CountAllCoins();
int copperCount = purse.CountCoins(copper);
double totalWeight = purse.GetWeight();
double goldWeight = purse.GetWeight(gold);
purse.IncreaseCapacity(capacityIncrease: 50, weightCapacity: 10.0);
purse.DecreaseCapacity(coinCapacityDecrease: 10, weightCapacityDecrease: 5.0);
Preview a conversion without modifying the container:
int goldPieces = purse.ConvertCoins(silver, gold); // How many gold can I get for my silver?
Convert and apply the result to the container:
purse.ConvertAndAddToContainer(silver, gold);
Conversions use integer division and round down to the nearest whole coin. Any remainder stays in the original denomination.
| Exception | Thrown When |
|---|---|
ContainerNameInvalidException | A null or empty name is provided to a container |
CoinPurseTooFullException | Adding coins would exceed the coin capacity |
CoinPurseTooHeavyException | Adding coins would exceed the weight capacity |
CoinPurseTooEmptyException | Decreasing capacity would drop below the current coin count |
CoinPurseTooLightException | Decreasing weight capacity would drop below the current weight |
NotEnoughCoinsException | Removing more coins than are present in the container |
ConversionFailedException | A conversion results in zero coins, or a currency is not found in the container |
Currency instance must be used consistently when adding, removing, and converting coins within a container.BaseValue cannot be less than 1. Passing zero or a negative value will default to 1.UpdateContainerName always assigning to itself instead of the new value, making the method a no-opConvertAndAddToContainer and ConvertCoins throwing an unhandled KeyNotFoundException when either currency was not present in the container — now throws ConversionFailedException with a descriptive messageConvertAndAddToContainer silently doing nothing when there were not enough coins to complete the conversion — now throws ConversionFailedExceptionArgumentNullException in constructors passing the message string as the parameter nameDecreaseCapacity incorrectly blocking valid decreases with a redundant <= 0 check on the weight capacityContainerNameInvaldException — renamed to ContainerNameInvalidExceptionAddCurrency catch blockCoinPurse to ContainerSetCapacity is now private — use IncreaseCapacity or DecreaseCapacity insteadCountCoins() (no arguments) renamed to CountAllCoins()ConvertAndAddToCoinPurse renamed to ConvertAndAddToContainerCoinPurseFullException removed — replaced by CoinPurseTooFullExceptionUpdateContainerNameConvertCoins method for previewing a conversion without modifying the containerAddCurrency overload that accepts a KeyValuePair<Currency, int>GetWeight(Currency, int) overload for weighing a hypothetical number of coinsContainerNameInvalidException, CoinPurseTooHeavyException, CoinPurseTooLightException, CoinPurseTooEmptyException, ConversionFailedExceptionAddCurrency now upserts — adding a currency that already exists increments its count rather than throwingCoinPurse class with coin capacity enforcementCurrency class with BaseValue, CoinName, and WeightConvertAndAddToCoinPurseNotEnoughCoinsException, CoinPurseFullException, CoinPurseTooFullException, CurrencyExistsException