Skip to content

pretiola/pasture

Repository files navigation

🥨 Pretiola﹏෴﹏Pasture 🌾

Alpha · Experimental · Incomplete. Not for use on mainnet or with real funds. Testnet validation only — see SPEC.md §11.1 for the implementation status of each promised feature.

Inspired by the words "Feed my sheep," Pretiola Pasture is a micro-donation platform that enables charitable ministries to receive EURC donations from arbitrary chains and settle them directly into EUR bank accounts via the Keeta Network.

Project Structure

  • apps/donor-web: Next.js donor interface for making contributions.
  • services/orchestrator: Backend logic for ministry onboarding and ledger interaction.
  • SPEC.md: Technical specification and architectural design (v0.3).
  • registry.json: Registry of onboarded ministries.

Getting Started

How it Works: Ledger vs. Orchestrator

Pretiola Pasture operates on a "Passive Ledger, Active Engine" model:

  1. The Secure Vault (Keeta Ledger): Acts as the passive source of truth. It holds the funds in Storage Accounts and enforces cryptographic rules about who can move them.
  2. The Active Engine (Pretiola Crank): Since the ledger has no internal clock, the Crank actively polls for inbound donations and "pushes" them to their final destination.
  3. The Administrative "Summoner": Pretiola uses a central administrative account to "summon" ministry accounts onto the ledger. It pays the required KTA gas fees so the ministry never has to touch a crypto wallet or manage native tokens.
  4. The Security Guard (HSM): The administrative account's signing power is restricted by a hardware-level policy (HSM), ensuring it can only move funds to a ministry's pre-verified bank account.

Local Environment Setup

Before running the services, create a .env file in the root directory with the following variables:

ORCHESTRATOR_SEED=your_funded_seed
TESTNET_NETWORK=test
STORAGE_ACCOUNT_ADDRESS=keeta_ar4gm...
USDC_TOKEN_ADDRESS=keeta_apna75...
EURC_TOKEN_ADDRESS=keeta_api6tk...
IBAN_RECIPIENT_ADDRESS=keeta_aabh...

USDC_TOKEN_ADDRESS is what the onboarding script uses today to scope SEND_ON_BEHALF, and it's what the crank sweeps. EURC_TOKEN_ADDRESS is kept for the mainnet path; see the Testnet Note below.

How to populate these variables:

  1. STORAGE_ACCOUNT_ADDRESS: This is generated automatically when you onboard a new ministry.

    • Run cd services/orchestrator && node src/onboard-ministry.js.
    • Copy the storageAccountAddress from the output into your .env.
  2. IBAN_RECIPIENT_ADDRESS: This is the Keeta address for the IBAN Anchor (e.g., DEV2). In "True Settlement" mode, the Crank negotiates a session with this Anchor using real-world bank details (Name, BIC, IBAN) to generate a cryptographically bound settlement.

    • For testing, you can use the default Anchor address found in the registry.json.
    • Copy the boundIbanRecipientAddress from the registry into your .env.
  3. registry.json: This file stores the configuration and bank details for all onboarded ministries.

    • Copy the sample file to get started:
      cp registry.json.sample registry.json
    • Populate it with the real Keeta addresses and structured bank details (BIC, IBAN, etc.) for your target ministry.

1. Donor Web Interface (Front End)

The front end is built with Next.js and provides the /give/[slug] routes for donors.

To start the development server:

cd apps/donor-web
npm run dev

The application will be available at http://localhost:3000.

2. Orchestrator & Crank Services

The orchestrator handles ministry onboarding and ledger verification. The crank handles the final settlement to the IBAN.

To run the onboarding script (for a new ministry):

cd services/orchestrator
node src/onboard-ministry.js

To verify a donation has landed on the Keeta ledger:

node src/verify-donation.js

To settle (sweep) funds from Storage to the bound IBAN (The Crank):

node src/reconciliation-crank.js

Note: This script now performs a True Settlement by negotiating with the IBAN Anchor using structured bank details (BIC, IBAN, Physical Address) and producing a block with a bound external session ID.

Phase 2.1 Progress

  • Keeta SDK Validation (Spikes 1 & 2)
  • Ministry Onboarding script (Sr. Dolores Foundation pilot) — seed generation still server-side; SPEC §4.1.3 requires browser-side, tracked as a Phase 2.1 hardening issue
  • Rebranded Donor-Web with High-Contrast Aesthetic
  • Automated Frontend Confirmation (Success Screen)
  • Reconciliation Crank (Storage -> IBAN Sweep) — single ministry, balance snapshot only; no idempotency table, no chain-tail
  • True Settlement structural plumbing (Structured bank details + Anchor initiateTransfer negotiation + bound external ID on the SEND block)
  • Software destination-binding check in the crank (Phase 2.1 stub for the future HSM policy)
  • Holding sub-accounts + crank release / reverse-bridge (SPEC §5.3)
  • Receiver ACL with tier predicates at the ledger (SPEC §5.2)
  • Browser-side ministry key generation + escrow (SPEC §4.1.3)
  • HSM / signing-service architecture (SPEC §7 infrastructure/kms/)
  • Webhook Hint Receiver (Architecture defined, using polling for MVP)

Testnet Note: Phase 2.1 settles in USDC, not EURC. The Keeta testnet IBAN Anchor (DEV2) does not currently expose an EURC route. The settlement asset is read from USDC_TOKEN_ADDRESS in .env and parameterizes the SEND_ON_BEHALF grant at onboarding; mainnet cutover swaps the token id and re-onboards each ministry with a new EURC-scoped grant. The donor UI labels contributions as "USDC" in Phase 2.1 to keep donor expectations honest.

Security Note (Phase 2.1 Boundary)

Important for the Pilot: In Phase 2.1, the SEND_ON_BEHALF permission is token-scoped at the ledger level. This means the Keeta protocol itself does not prevent the operator key from sending funds to a different address. There are two layers of mitigation today and a third on the roadmap:

  • Software destination-binding check (current): Before publishing the SEND block, the reconciliation crank verifies that the sendToAddress returned by the IBAN Anchor matches ministry.boundIbanRecipientAddress from the registry. A misbehaving or misconfigured anchor cannot trick the crank into signing a sweep to an unbound address. This is the SPEC's "policy" rule restated in JavaScript — defense-in-depth, not a cryptographic guarantee.
  • .env-loaded operator key (current, the gap): The ORCHESTRATOR_SEED is loaded into the orchestrator process from .env. An attacker who roots the orchestrator host or reads the file gets full sweep authority within the bounds of the destination check — i.e., they can replay/retry sweeps but cannot redirect funds. They cannot reach the OWNER role (key not held by Pretiola).
  • HSM / signing-service (Production roadmap): The operator key moves into a Cloud KMS, CloudHSM, or attested enclave (Nitro Enclave / Confidential VM). The destination-binding check moves into the same trust domain as the key. At that point, "refuse to sign unauthorized destinations" becomes a hardware/cloud-enforced rule, and a compromised orchestrator host can no longer produce signatures.

The architectural choice between KMS-managed key + sidecar policy vs. in-enclave key with attested boot image is open and tracked as a design issue.

About

Pasture is a micro-donation platform that enables charitable ministries

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors