Adding a New Currency
This guide explains how to add support for a new currency to the Asset Reality Backend platform, including exchange rate updates and tests.
Overview
The platform supports multiple currencies for asset pricing and organization preferences. Exchange rates are fetched from OpenExchangeRates.org and updated periodically via a background task.
Examples
Historical currency additions for reference:
- CHF (Swiss Franc): commit 657f1f75
- AUD (Australian Dollar): commit 2396429a
- CAD (Canadian Dollar): commit 07519357
Steps to Add a New Currency
1. Update Currency Validation
Add the new currency code to the validation function in internal/amrs-svc/v2/models/misc.go, e.g NEW:
func ValidateCurrency(field, c string) error {
switch c {
case "USD", "EUR", "GBP", "JMD", "SCR", "CAD", "CHF", "AUD", "NEW": // Add "NEW" here
default:
return fmt.Errorf("%s must be one of USD, EUR, GBP, JMD, SCR, CAD, CHF, AUD, NEW", field)
}
return nil
}
Location: internal/amrs-svc/v2/models/misc.go:65-72
What this does: Ensures the new currency code is accepted in API requests from the frontend
2. Add Default Exchange Rate
Add a default exchange rate for the new currency in cmd/amrs-svc/assets/exchange_rates.json:
{
"base": "USD",
"rates": {
"CAD": 1.349995,
"EUR": 0.91585,
"GBP": 0.782205,
"JMD": 154.758421,
"SCR": 13.585954,
"AUD": 0.65086,
"CHF": 0.9143,
"NEW": 1.23456 // Add your new currency here
}
}
Location: cmd/amrs-svc/assets/exchange_rates.json
What this does: Provides a fallback exchange rate used during startup and when the external API is unavailable.
Use a recent exchange rate from a reliable source like Yahoo Finance, OpenExchangeRates.org, or Google Finance.
3. Update Exchange Rate Fetching (if needed)
The exchange rate fetching is now automatic and uses OpenExchangeRates.org's /latest.json endpoint which returns all available currencies. No code changes are typically needed here.
Background: As of commit 8c06b98b, the platform switched from API Layer to OpenExchangeRates.org and now fetches all available rates automatically.
Previous approach (no longer used): Currencies had to be explicitly listed in the API call:
// Old way - no longer necessary
url := "https://api.apilayer.com/exchangerates_data/latest?symbols=EUR,GBP,JMD,SCR,CAD,CHF&base=USD"
Current approach: The full rates endpoint is used:
// Current implementation in internal/amrs-svc/v2/exchange_rates/exchange_rates.go:118
url := "https://openexchangerates.org/api/latest.json?base=USD&app_id=" + exchangeRatesAPIKey
4. Add Tests
Add test cases for the new currency in internal/amrs-svc/v2/service/currency_conversion_test.go:
func Test_CurrencyConvert(t *testing.T) {
rates := &models.ExchangeRate{
Base: "USD",
Rates: map[string]float64{
"EUR": 0.919904,
"GBP": 0.810045,
"JMD": 150.642173,
"NEW": 1.23456, // Add your new currency
},
}
// Add test for direct conversion USD => NEW
t.Run("direct conversion USD => NEW", func(t *testing.T) {
value, err := Convert(rates, 100.0, "USD", "NEW")
require.NoError(t, err)
require.Equal(t, value, 123.456)
})
// Add test for reverse conversion NEW => USD
t.Run("reverse direct conversion NEW => USD", func(t *testing.T) {
value, err := Convert(rates, 123.456, "NEW", "USD")
require.NoError(t, err)
require.Equal(t, value, 100.0)
})
// Add test for indirect conversion NEW => EUR
t.Run("indirect conversion NEW => EUR", func(t *testing.T) {
value, err := Convert(rates, 123.456, "NEW", "EUR")
require.NoError(t, err)
// Expected: 123.456 / 1.23456 * 0.919904 = 91.9904
require.InDelta(t, 91.9904, value, 0.0001)
})
}
Location: internal/amrs-svc/v2/service/currency_conversion_test.go
What to test:
- Direct conversion: USD → NEW (base currency to new currency)
- Reverse conversion: NEW → USD (new currency to base currency)
- Indirect conversion: NEW → EUR (new currency to another non-base currency)
5. Run Tests
Run the currency conversion tests to verify your changes:
go test ./internal/amrs-svc/v2/service -run Test_CurrencyConvert -v
Expected output:
=== RUN Test_CurrencyConvert
=== RUN Test_CurrencyConvert/direct_conversion_USD_=>_JMD
=== RUN Test_CurrencyConvert/reverse_direct_conversion_JMD_=>_USD
=== RUN Test_CurrencyConvert/indirect_conversion_JMD_=>_GBP
=== RUN Test_CurrencyConvert/indirect_conversion_GBP_=>_JMD
=== RUN Test_CurrencyConvert/direct_conversion_USD_=>_NEW
=== RUN Test_CurrencyConvert/reverse_direct_conversion_NEW_=>_USD
=== RUN Test_CurrencyConvert/indirect_conversion_NEW_=>_EUR
--- PASS: Test_CurrencyConvert (0.00s)
6. Build Verification
Ensure the code compiles:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -cover ./cmd/amrs-svc/
7. Local Testing
Start the services locally and verify the exchange rate updates:
tilt up
Check the logs for successful exchange rate updates:
INFO Running exchange rate update task
INFO Got exchange rates
Exchange Rate Updates
How It Works
- Startup: Default rates loaded from
cmd/amrs-svc/assets/exchange_rates.json - Background Task: The
exchangeRateUpdateTaskruns periodically (configured in service startup) - API Call: Fetches latest rates from OpenExchangeRates.org
- In-Memory Update: Updates the in-memory rates cache without file system writes
Code location: internal/amrs-svc/v2/exchange_rates/exchange_rates.go:52-76
API Configuration
The exchange rates API key must be configured in the environment:
Local (docker-compose.yml):
EXCHANGE_RATES_API_KEY: no-api-key # Disabled locally
Deployed environments: Configured via SOPS-encrypted secrets in deploy/kustomize/overlays/amrs/*/secret-helm-values.sops.yaml
Note: Exchange rate updates are disabled in local development to avoid API rate limits. The default rates file is used instead.