From c62156d5fa07ba0d13b915d51872098ed6adbeb9 Mon Sep 17 00:00:00 2001 From: ifsantana Date: Tue, 21 Jul 2026 11:58:42 -0300 Subject: [PATCH] feat: autoload .env for example configuration via dotenv-kotlin Each example crashed with "IDEM_BASE_URL is not set" unless the vars were exported into the real shell environment first, because System.getenv() never looked at the .env file the README tells you to create. Add dotenv-kotlin and a shared requiredEnv() helper (host env still wins over .env), and document .env setup in the README. Co-Authored-By: Claude Sonnet 5 --- README.md | 34 +++++++++++++++++++ pom.xml | 5 +++ .../examples/basic/BasicTransactionExample.kt | 5 +-- .../examples/mcp/McpAgentWorkflowExample.kt | 5 +-- .../onchain/StablecoinOnChainExample.kt | 5 +-- .../reconciliation/ReconciliationExample.kt | 5 +-- .../settlement/PendingSettlementExample.kt | 5 +-- .../finance/idem/examples/support/Env.kt | 17 ++++++++++ 8 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/finance/idem/examples/support/Env.kt diff --git a/README.md b/README.md index 3e108ba..4e4b665 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,40 @@ docker compose run --rm -e SPRING_PROFILES_ACTIVE=dev,seed app ./mvnw compile exec:java -Dexec.mainClass=finance.idem.examples.basic.BasicTransactionExampleKt ``` +### Configuring `.env` + +Every example reads two variables — `IDEM_BASE_URL` and `IDEM_API_KEY` — via a +shared `requiredEnv()` helper +([`support/Env.kt`](src/main/kotlin/finance/idem/examples/support/Env.kt)) +backed by [`dotenv-kotlin`](https://github.com/cdimascio/dotenv-kotlin). It +loads `.env` from the project root automatically, so once the file is filled +in you can run any example directly — no manual `export`/`source` step +needed. A real environment variable, if set, always takes precedence over +the value in `.env`. + +`.env` is gitignored; `.env.example` is the checked-in template: + +```bash +cp .env.example .env +``` + +Then fill in: + +| Variable | Value | +|---|---| +| `IDEM_BASE_URL` | `http://localhost:8081` when running the stack via `docker compose up -d` above | +| `IDEM_API_KEY` | The key printed by the `docker compose run --rm -e SPRING_PROFILES_ACTIVE=dev,seed app` seed step | + +```bash +# .env +IDEM_BASE_URL=http://localhost:8081 +IDEM_API_KEY=sk_live_... # from the seed step's printed output +``` + +If `.env` is missing or a variable is blank, each example fails fast with +`IDEM_BASE_URL is not set — see .env.example` (or the equivalent for +`IDEM_API_KEY`) rather than a confusing SDK-level error. + ## Examples | # | Example | What it shows | diff --git a/pom.xml b/pom.xml index 5e7a0c9..9f80cd7 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,11 @@ kotlinx-coroutines-core 1.7.3 + + io.github.cdimascio + dotenv-kotlin + 6.5.1 + diff --git a/src/main/kotlin/finance/idem/examples/basic/BasicTransactionExample.kt b/src/main/kotlin/finance/idem/examples/basic/BasicTransactionExample.kt index 465d4ac..1955e1a 100644 --- a/src/main/kotlin/finance/idem/examples/basic/BasicTransactionExample.kt +++ b/src/main/kotlin/finance/idem/examples/basic/BasicTransactionExample.kt @@ -1,6 +1,7 @@ package finance.idem.examples.basic import finance.idem.examples.support.createAccount +import finance.idem.examples.support.requiredEnv import finance.idem.sdk.IdemClient import finance.idem.sdk.model.EntryType import finance.idem.sdk.model.FiatCurrency @@ -24,8 +25,8 @@ import java.util.UUID */ fun main() = runBlocking { - val baseUrl = System.getenv("IDEM_BASE_URL") ?: error("IDEM_BASE_URL is not set — see .env.example") - val apiKey = System.getenv("IDEM_API_KEY") ?: error("IDEM_API_KEY is not set — see .env.example") + val baseUrl = requiredEnv("IDEM_BASE_URL") + val apiKey = requiredEnv("IDEM_API_KEY") val client = IdemClient(baseUrl = baseUrl, apiKey = apiKey) client.use { diff --git a/src/main/kotlin/finance/idem/examples/mcp/McpAgentWorkflowExample.kt b/src/main/kotlin/finance/idem/examples/mcp/McpAgentWorkflowExample.kt index 86c8e6a..790e851 100644 --- a/src/main/kotlin/finance/idem/examples/mcp/McpAgentWorkflowExample.kt +++ b/src/main/kotlin/finance/idem/examples/mcp/McpAgentWorkflowExample.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import finance.idem.examples.support.allowAgentMaxDebitPerSession import finance.idem.examples.support.createAccount import finance.idem.examples.support.mintAgentApiKey +import finance.idem.examples.support.requiredEnv import finance.idem.sdk.IdemClient import io.modelcontextprotocol.client.McpClient import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport @@ -40,8 +41,8 @@ import java.util.UUID */ fun main() = runBlocking { - val baseUrl = System.getenv("IDEM_BASE_URL") ?: error("IDEM_BASE_URL is not set — see .env.example") - val apiKey = System.getenv("IDEM_API_KEY") ?: error("IDEM_API_KEY is not set — see .env.example") + val baseUrl = requiredEnv("IDEM_BASE_URL") + val apiKey = requiredEnv("IDEM_API_KEY") val objectMapper = ObjectMapper() val client = IdemClient(baseUrl = baseUrl, apiKey = apiKey) diff --git a/src/main/kotlin/finance/idem/examples/onchain/StablecoinOnChainExample.kt b/src/main/kotlin/finance/idem/examples/onchain/StablecoinOnChainExample.kt index 4169757..f05df95 100644 --- a/src/main/kotlin/finance/idem/examples/onchain/StablecoinOnChainExample.kt +++ b/src/main/kotlin/finance/idem/examples/onchain/StablecoinOnChainExample.kt @@ -1,6 +1,7 @@ package finance.idem.examples.onchain import finance.idem.examples.support.createAccount +import finance.idem.examples.support.requiredEnv import finance.idem.sdk.IdemClient import finance.idem.sdk.model.ChainId import finance.idem.sdk.model.EntryType @@ -39,8 +40,8 @@ import java.util.UUID */ fun main() = runBlocking { - val baseUrl = System.getenv("IDEM_BASE_URL") ?: error("IDEM_BASE_URL is not set — see .env.example") - val apiKey = System.getenv("IDEM_API_KEY") ?: error("IDEM_API_KEY is not set — see .env.example") + val baseUrl = requiredEnv("IDEM_BASE_URL") + val apiKey = requiredEnv("IDEM_API_KEY") val client = IdemClient(baseUrl = baseUrl, apiKey = apiKey) client.use { diff --git a/src/main/kotlin/finance/idem/examples/reconciliation/ReconciliationExample.kt b/src/main/kotlin/finance/idem/examples/reconciliation/ReconciliationExample.kt index 3528ea0..0421266 100644 --- a/src/main/kotlin/finance/idem/examples/reconciliation/ReconciliationExample.kt +++ b/src/main/kotlin/finance/idem/examples/reconciliation/ReconciliationExample.kt @@ -1,6 +1,7 @@ package finance.idem.examples.reconciliation import finance.idem.examples.support.createAccount +import finance.idem.examples.support.requiredEnv import finance.idem.sdk.IdemClient import finance.idem.sdk.model.ChainId import finance.idem.sdk.model.EntryType @@ -27,8 +28,8 @@ import java.util.UUID */ fun main() = runBlocking { - val baseUrl = System.getenv("IDEM_BASE_URL") ?: error("IDEM_BASE_URL is not set — see .env.example") - val apiKey = System.getenv("IDEM_API_KEY") ?: error("IDEM_API_KEY is not set — see .env.example") + val baseUrl = requiredEnv("IDEM_BASE_URL") + val apiKey = requiredEnv("IDEM_API_KEY") val client = IdemClient(baseUrl = baseUrl, apiKey = apiKey) client.use { diff --git a/src/main/kotlin/finance/idem/examples/settlement/PendingSettlementExample.kt b/src/main/kotlin/finance/idem/examples/settlement/PendingSettlementExample.kt index bcbbef0..633eb83 100644 --- a/src/main/kotlin/finance/idem/examples/settlement/PendingSettlementExample.kt +++ b/src/main/kotlin/finance/idem/examples/settlement/PendingSettlementExample.kt @@ -1,6 +1,7 @@ package finance.idem.examples.settlement import finance.idem.examples.support.createAccount +import finance.idem.examples.support.requiredEnv import finance.idem.sdk.IdemClient import finance.idem.sdk.model.ChainId import finance.idem.sdk.model.EntryType @@ -29,8 +30,8 @@ import java.util.UUID */ fun main() = runBlocking { - val baseUrl = System.getenv("IDEM_BASE_URL") ?: error("IDEM_BASE_URL is not set — see .env.example") - val apiKey = System.getenv("IDEM_API_KEY") ?: error("IDEM_API_KEY is not set — see .env.example") + val baseUrl = requiredEnv("IDEM_BASE_URL") + val apiKey = requiredEnv("IDEM_API_KEY") val client = IdemClient(baseUrl = baseUrl, apiKey = apiKey) client.use { diff --git a/src/main/kotlin/finance/idem/examples/support/Env.kt b/src/main/kotlin/finance/idem/examples/support/Env.kt new file mode 100644 index 0000000..b24963a --- /dev/null +++ b/src/main/kotlin/finance/idem/examples/support/Env.kt @@ -0,0 +1,17 @@ +package finance.idem.examples.support + +import io.github.cdimascio.dotenv.dotenv + +/** + * Loaded once per JVM. Host environment variables always take precedence + * over `.env` entries (dotenv-kotlin's own resolution order), so a real + * shell export still wins over a stale `.env` file. + */ +private val dotenv = dotenv { ignoreIfMissing = true } + +/** + * Resolves an environment variable from the host environment or `.env` + * (see `.env.example`), failing fast with a message pointing at the file + * every example relies on for local configuration. + */ +fun requiredEnv(key: String): String = dotenv[key] ?: error("$key is not set — see .env.example")