Skip to main content

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:

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:

  1. Direct conversion: USD → NEW (base currency to new currency)
  2. Reverse conversion: NEW → USD (new currency to base currency)
  3. 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

  1. Startup: Default rates loaded from cmd/amrs-svc/assets/exchange_rates.json
  2. Background Task: The exchangeRateUpdateTask runs periodically (configured in service startup)
  3. API Call: Fetches latest rates from OpenExchangeRates.org
  4. 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.