Skip to content

Repository files navigation

💳 Wallet Operations Agent

An AI-powered operations layer on top of Wallet-Automation-Engine.

Users interact with it using natural language:

  • "What's my balance?"
  • "Show me last week's transactions"
  • "Transfer $100 to wallet 42"

The agent plans and executes the required operations through the Wallet API.

It never accesses SQL Server directly and never bypasses the wallet's own service layer.


Why this design

A money-moving AI agent must handle the same concerns as a production backend system:

  • Authentication and authorization
  • Audit trails
  • Confirmation before irreversible operations
  • Preventing the model from controlling sensitive identifiers
  • Never letting a network hiccup turn into a duplicate transfer
  • A small, unambiguous set of tools instead of one per endpoint

A key design decision:

No tool accepts a walletId or userId parameter from the planner.

The agent never trusts identifiers generated by the LLM.

The user's wallet identity is always resolved from the authenticated JWT.

The only tool that receives an identifier is TransferMoney, where the identifier represents the recipient wallet.

The source wallet is always derived from the caller's identity.

Reads and writes don't share a retry policy. A timeout means the outcome is unknown, not that the operation failed. Read-only tools retry freely on failure. State-changing tools (DepositMoney, WithdrawMoney, TransferMoney) get exactly one attempt per call and are protected instead by an idempotency key: ExecutionEngine computes a deterministic key (user + tool + parameters) when a confirmation is requested, ToolExecutor checks Redis for an already-recorded result before executing, and records the result immediately on success. A genuine retry - the user re-approving, the agent re-planning - reuses the same key and gets the original result back instead of moving money twice.

Tools are consolidated by responsibility, not by endpoint. Reads that only differ by which filters are set are one tool with an optional payload, not several tools the model has to choose between:

  • QueryTransactions - pass transactionId for a single lookup, any of type/minAmount/maxAmount to filter, or nothing for full history.
  • AnalyzeFinances - pass period: "month" or period: "window" (with days), or nothing for all-time statistics.

The routing between those cases is a deterministic switch in C#, not a decision the LLM has to make.


Authentication

The agent has no registration endpoint and no user store of its own.

POST /api/auth/login forwards credentials to the Wallet API's own login endpoint and returns the same JWT.

Example:

POST /api/auth/login
{
  "email": "[email protected]",
  "password": "password"
}

Response:

{
  "token": "<jwt-token>"
}

Anyone who can authenticate with the wallet system can use the agent with that token.

Use it as:

Authorization: Bearer <token>

on every /api/agent/* request.


Running it

1. Start Wallet API

Run Wallet-Automation-Engine and note its base URL.


2. Start infrastructure

docker compose up -d

This starts:

  • Redis
  • Ollama

3. Configure Wallet API connection

Update:

{
  "WalletApi": {
    "BaseUrl": "<wallet-api-url>"
  }
}

4. Configure the JWT signing key (secrets, not appsettings.json)

The Agent doesn't issue its own tokens - it validates the JWT that the Wallet API issued at login, using the same Jwt:Key / Issuer / Audience. That key must never live in appsettings.json (it would end up committed to git), so appsettings.json only has empty placeholders and the app will refuse to start without a real key configured via user secrets.

Generate one key and set it, identically, in both projects:

# From Wallet-Operations-Agent/Wallet.Agent.Api
dotnet user-secrets set "Jwt:Key" "<your-generated-key>"

# From Wallet-Automation-Engine's API project
dotnet user-secrets init   # only if it doesn't already have a UserSecretsId
dotnet user-secrets set "Jwt:Key" "<the-same-generated-key>"

If Issuer or Audience ever differ between the two projects, add those the same way (dotnet user-secrets set "Jwt:Issuer" "...") - but by default both projects already agree on WalletApi / WalletApiUsers in appsettings.json, so usually only Jwt:Key needs to be set.

On Windows, user secrets are stored at:

%APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json

5. Configure CORS for the chat UI

The browser-based chat client (see below) calls the API from a different origin, so it needs to be allow-listed. In appsettings.Development.json:

{
  "Cors": {
    "AllowedOrigins": [ "http://localhost:5500" ]
  }
}

Adjust the port to whatever you actually serve the chat UI on.


6. Run Agent

dotnet run --project Wallet.Agent.Api

By default this listens on http://localhost:5274 (see launchSettings.json).


Running the chat UI

wallet-agent-chat.html is a single, dependency-free file - login screen, chat, confirmation approve/decline, and a sidebar of past conversations pulled from Redis. It needs to be served over HTTP, not opened directly as a file:// URL (the browser blocks that under CORS).

# from the folder containing wallet-agent-chat.html
dotnet tool install --global dotnet-serve   # first time only
dotnet-serve -p 5500

Then open http://localhost:5500/wallet-agent-chat.html. Make sure the port here matches what you put in Cors:AllowedOrigins above.


Example Requests

Login

POST /api/auth/login
{
  "email": "[email protected]",
  "password": "password"
}

Chat with Agent

POST /api/agent/chat
{
  "conversationId": "abc",
  "question": "What's my balance?"
}

Confirm Operation

For operations that require approval:

POST /api/agent/confirm
{
  "conversationId": "abc",
  "approved": true
}

List Conversations

Returns the caller's own conversations, most recently active first, with a preview of the last message. Backed by a Redis index kept alongside the existing per-conversation message lists.

GET /api/agent/conversations

Get Conversation History

GET /api/agent/conversations/{conversationId}/history

{conversationId} is the same short id the client generates per session (not prefixed with the user id - that scoping happens server-side from the JWT).

About

Natural-language AI agent over a .NET digital wallet system — LLM-driven tool calls, JWT-scoped identity, confirmation-gated transfers, Redis state.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages