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")