Skip to content

feat: implement wallet transfer service with postgres, double-entry l… - #124

Open
theshanky wants to merge 2 commits into
Robustrade:mainfrom
theshanky:solution/shanks
Open

feat: implement wallet transfer service with postgres, double-entry l…#124
theshanky wants to merge 2 commits into
Robustrade:mainfrom
theshanky:solution/shanks

Conversation

@theshanky

Copy link
Copy Markdown

…edger, idempotency, and concurrency safety

Summary

Describe your solution briefly.

AI disclosure

Detail how you used AI to help with your submission (including the tools you used, how
you used them and what your prompts were).
Include these points in detail

  1. What tool you used (Cursor, Claude Code, Antigratvity etc.)
  2. How you generally use the tool for your work.
  3. A transcript of your entire session with your AI tool of choice. You can add this to the repo or email it to us with your submission. If for some reason, this is not possible, give us all the prompts that you used with the AI.

Schema Design

Describe the tables, constraints, and indexes you introduced.

Idempotency Strategy

Explain how duplicate requests are handled safely.

Concurrency Strategy

Explain how you prevent race conditions and double spending.

How to Run

How to Test

Tradeoffs / Assumptions

Checklist

  • Tests pass
  • Lint passes
  • Format check passes
  • README or notes updated
  • PR description explains schema, idempotency, and concurrency

Copilot AI lite review requested due to automatic review settings August 9, 2026 12:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a Go + PostgreSQL wallet transfer service implementing wallet creation, idempotent transfers, and a double-entry ledger, exposed via HTTP handlers and backed by Postgres repositories.

Changes:

  • Added service/repository layers to create wallets, execute transfers, and record ledger/idempotency data in PostgreSQL.
  • Added HTTP routing + handlers for /wallets, /transfers, and /health.
  • Added integration-style tests (including concurrency tests) and a solution documentation write-up.

Assessment (key risks):

  • The current transaction/error flow in TransferService.CreateTransfer rolls back “durable failure” records (failed transfer + idempotency status) on business errors like insufficient funds, which undermines exactly-once/idempotency guarantees under failure.
  • The idempotency “lock” approach (SELECT ... FOR UPDATE) does not prevent races when the idempotency row does not yet exist, which can lead to nondeterministic unique-violation failures under concurrency rather than clean conflict/replay behavior.
  • Some security/operational concerns exist around logging and returning raw internal errors.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
SOLUTION_DOCUMENTATION.md Added architecture/schema/idempotency/concurrency documentation and AI disclosure section.
internal/service/wallet_service.go Implements wallet creation + audit and seeds initial ledger state.
internal/service/transfer_service.go Implements idempotent, transactional transfer orchestration + ledger writes.
internal/service/transfer_service_test.go Adds integration tests for transfer success, replay, mismatch, insufficient funds, validation.
internal/service/concurrency_test.go Adds concurrency stress tests for reciprocal transfers + shared idempotency key.
internal/repository/schema.sql Introduces SQL schema + indexes (and a seed row).
internal/repository/repository.go Defines repository and TxManager interfaces.
internal/repository/postgres.go Implements Postgres repositories + transaction manager.
internal/handler/wallet_handler.go Adds HTTP endpoints for wallet create/get/audit.
internal/handler/transfer_handler.go Adds HTTP endpoints for transfer create/get and error mapping.
internal/handler/router.go Registers routes using http.ServeMux patterns.
internal/handler/handler_test.go Adds handler-level integration tests against a real DB.
internal/domain/models.go Adds domain models (wallet/transfer/ledger/idempotency) + request hashing.
internal/domain/errors.go Adds domain error definitions for handlers/services.
internal/domain/domain_test.go Adds unit tests for request hash determinism.
go.mod Introduces module definition + dependencies (and Go version directive).
go.sum Locks dependency checksums.
cmd/server/schema.sql Adds embedded schema applied at server startup.
cmd/server/main.go Adds server bootstrap: DB connect, schema apply, DI wiring, graceful shutdown.
.golangci.yml Enables a baseline set of linters for the Go codebase.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Transfer: failedTransfer,
IsReplayed: false,
}
return domain.ErrInsufficientFunds
Comment on lines +121 to +124
if err != nil {
s.recordFailedIdempotency(ctx, tx, idemRecord, err.Error())
return err
}
if err != nil {
return fmt.Errorf("failed to fetch replayed transfer: %w", err)
}
ledgerEntries, _ := s.ledgerRepo.GetByTransferID(ctx, tx, t.ID)

err := s.txMgr.ExecTx(ctx, func(tx *sql.Tx) error {
// 1. Idempotency Check & Locking
idemRecord, err := s.idempotencyRepo.GetForUpdate(ctx, tx, input.IdempotencyKey)
}

// 2. Deterministic Row Locking (Order by wallet ID lexicographically via single FOR UPDATE query)
fromWallet, toWallet, err := s.walletRepo.GetTwoWalletsForUpdate(ctx, tx, input.FromWalletID, input.ToWalletID)
Comment on lines +94 to +98
entries := []domain.LedgerEntry{
{
ID: creditID,
TransferID: initTransferID,
WalletID: walletID,
Comment thread cmd/server/main.go
port = "8080"
}

logger.Info("connecting to PostgreSQL database", "url", dbURL)
Comment thread SOLUTION_DOCUMENTATION.md

1. **Tool Used**: Antigravity AI Coding Assistant powered by Claude 3.7 Sonnet / Gemini models.
2. **Usage Pattern**: Used for system architecture design, database lock ordering analysis, double-entry accounting schema design, table-driven unit test generation, and high-concurrency race condition testing.
3. **Session Logs**: Conversation transcripts and decision logs are persisted under `<appDataDir>/brain/47cd1d3c-1daf-43af-952d-13b0706d8c3b/`.
Comment on lines +49 to +51
INSERT INTO wallets (id, balance, currency, created_at, updated_at)
VALUES ('system_treasury', 0, 'USD', NOW(), NOW())
ON CONFLICT (id) DO NOTHING;
writeJSONError(w, http.StatusUnprocessableEntity, err.Error())
return
}
writeJSONError(w, http.StatusInternalServerError, "failed to process transfer: "+err.Error())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants