Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ which now builds a `Jan3AccountsManager`) and delegates provisioning to it.
## WapuPay (Argentine direct-fiat)

`wapupay.py` lets a user pay an Argentine bank account in **ARS**, funded with
**USDT on Liquid**. WapuPay's API is called **directly** (`https://be-prod.wapu.app`),
**USDT or L-BTC on Liquid**. WapuPay's API is called **directly** (`https://be-prod.wapu.app`),
not through Ankara. Each call carries WapuPay's own **`X-API-Key`**;
`wapupay_create_order` returns a Liquid USDT funding address; the user pays it
`wapupay_create_order` returns a Liquid funding address; the user pays it
with `lw_send_asset` (no auto-pay). `wapupay_exchange_rates` is **public** (no key).

WapuPay logic (orders, quotes, `X-API-Key` calls)
Expand Down Expand Up @@ -219,7 +219,10 @@ lives in `wapupay.py`.
- **Enabled by default.** All `jan3_*` / `wapupay_*` tools ship enabled (not in
`features._SHIPPED_DISABLED`). Business calls still need a key — env var or
provisioned via `wapupay_provision_account`.
- **Rail pinned** to Liquid USDT; WapuPay rejects any other funding rail (400).
- **Funding rail is selectable per order** — USDT (default) or L-BTC — both
settle from a Liquid address (`network=LIQUID`). The quote/preview endpoint
is USDT-only (LBTC returns a 500), so `wapupay_quote` and the create-order
confirmation preview never pass `funding_method`.
- JAN3 sessions persist per-email at `~/.aqua/jan3/{email}.json`; the
provisioned API key and order records persist under `~/.aqua/wapupay/` — all at
`0o600`. Bank PII + tokens + API key are never logged (see `ankara._redact` /
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ MCP server and CLI for managing **Bitcoin** and **Liquid Network** wallets throu
- **Lightning** - Send and receive via Lightning using L-BTC
- **Assets** - Native support for L-BTC, USDt, and all Liquid assets
- **Swaps & Pegs** - Convert BTC ↔ L-BTC and swap Liquid/cross-chain assets via SideSwap, SideShift, and Changelly
- **JAN3 Account** - Login, Lightning Address, and WapuPay (pay ARS bank accounts with USDT) via your JAN3 account
- **JAN3 Account** - Login, Lightning Address, and WapuPay (pay ARS bank accounts with USDT or L-BTC) via your JAN3 account
- **Secure** - Encrypted storage, no remote servers for keys

## Installation
Expand Down Expand Up @@ -195,13 +195,13 @@ Once connected, you can ask Claude to:
| `changelly_receive` | Receive USDt-Liquid from USDt on another chain |
| `changelly_status` | Check status of a swap order |

**WapuPay (`wapupay_*`)** — pay Argentine bank accounts in ARS, funded with USDT on Liquid
**WapuPay (`wapupay_*`)** — pay Argentine bank accounts in ARS, funded with USDT or L-BTC on Liquid

| Tool | Description |
|------|-------------|
| `wapupay_exchange_rates` | Current exchange rates (e.g. USDT/ARS); public, no key needed |
| `wapupay_quote` | Preview USDT cost, fee, and rate for an ARS payment |
| `wapupay_create_order` | Create a direct-fiat order; returns a Liquid USDT funding address |
| `wapupay_create_order` | Create a direct-fiat order (USDT or L-BTC rail); returns a Liquid funding address |
| `wapupay_fund_order` | Re-issue funding instructions for an existing order |
| `wapupay_order_status` | Check a direct-fiat order's status |
| `wapupay_orders` | List locally-tracked orders |
Expand Down Expand Up @@ -297,7 +297,7 @@ aqua sideshift send --deposit-coin btc --deposit-network liquid --settle-coin us
--settle-address T... --deposit-amount 0.001 --wallet-name default
aqua changelly send --external-network tron --settle-address T... --amount-from 100 --wallet-name default

# WapuPay (pay ARS bank accounts, funded with USDT on Liquid)
# WapuPay (pay ARS bank accounts, funded with USDT or L-BTC on Liquid)
aqua wapupay quote --amount-ars 10000 --alias some.alias
aqua wapupay create-order --amount-ars 10000 --alias some.alias --wallet-name default
# then fund the returned address:
Expand Down
2 changes: 1 addition & 1 deletion scripts/prompts/prompt_test_wapupay.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ as the refund address. Show me the funding instructions but DO NOT pay yet.
- Invokes `wapupay_create_order(amount_ars="10000", alias="test.alias.mp", receiver_name="Test Receiver", refund_address="lq1qqw4...k6lng")`
- Returns `tentative_id`, `status` (`FUNDING_ISSUED`), `address_destination` (Liquid `lq1…/ex1…/VJL…`),
`asset_id` (USDT on Liquid), `funding_amount_usdt`, `total_amount_usdt`,
`total_funding_amount_base_units`, `funding_expires_at`, `pay_instructions`, `qr_code_path`
`total_funding_amount_base_units`, `expires_at`, `pay_instructions`, `qr_code_path`
- ⚠️ The agent shows the funding address/QR **but must NOT call `lw_send_asset`** — keep this dry

---
Expand Down
2 changes: 1 addition & 1 deletion src/aqua/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Python package implementing the MCP server, wallet engines, and third-party swap
| `lnurl.py` | LUD-16 Lightning Address resolution → BOLT11. | `is_lightning_address`, `resolve_lightning_address` |
| `changelly.py` | Custodial USDt cross-chain swaps via AQUA's Ankara proxy. Curated allowlist (mirrors AQUA Flutter). | `ChangellyClient`, `ChangellyManager` |
| `sideshift.py` | Custodial cross-chain swaps via SideShift.ai. Curated allowlist mirrors AQUA Flutter; affiliate ID `PVmPh4Mp3`. | `SideShiftClient`, `SideShiftManager` |
| `wapupay.py` | WapuPay Argentine direct-fiat calls, made directly with `X-API-Key`. `exchange_rates` is public. create-order returns a Liquid USDT funding address. Rail pinned USDT/LIQUID. API key resolves env `WAPUPAY_API_KEY` → stored `api_key.json` (`_require_api_key`). `wapupay_provision_account(email)` delegates the AQUA-backend call to `Jan3AccountsManager.provision_wapupay_token(email)` (in `jan3_accounts.py`), then stores the key; no-op when one already exists (env or stored) since the backend rotates on every call. JAN3-account auth (the `jan3_*` login / session) lives in `jan3_accounts.py` (injected as `self.jan3`). Enabled by default (wapupay calls still need `WAPUPAY_API_KEY`). | `WapuPayClient`, `WapuPayManager`, `WapuPayApiKey`, `WapuPayOrder` |
| `wapupay.py` | WapuPay Argentine direct-fiat calls, made directly with `X-API-Key`. `exchange_rates` is public. create-order returns a Liquid funding address; the rail is selectable per order via `funding_method` (USDT default / LBTC), both on LIQUID. The L-BTC send amount is `total_amount_sats` (`funding_amount_sat` is the pre-fee payout, record-only). API key resolves env `WAPUPAY_API_KEY` → stored `api_key.json` (`_require_api_key`). `wapupay_provision_account(email)` delegates the AQUA-backend call to `Jan3AccountsManager.provision_wapupay_token(email)` (in `jan3_accounts.py`), then stores the key; no-op when one already exists (env or stored) since the backend rotates on every call. JAN3-account auth (the `jan3_*` login / session) lives in `jan3_accounts.py` (injected as `self.jan3`). Enabled by default (wapupay calls still need `WAPUPAY_API_KEY`). | `WapuPayClient`, `WapuPayManager`, `WapuPayApiKey`, `WapuPayOrder` |
| `banner.py` | CLI ASCII banner rendering. | `render_banner` |
| `cli/` | Click CLI mirroring MCP tools (see `cli/AGENTS.md`). | — |
| `static/` | MCP resource markdown (quickstart, networks, security). Loaded by `server.py` via `aqua://docs/*`. | — |
Expand Down
2 changes: 1 addition & 1 deletion src/aqua/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Agentic AQUA - Manage Liquid Network and Bitcoin wallets through AI assistants."""

__version__ = "0.5.1b1"
__version__ = "0.5.2"
2 changes: 1 addition & 1 deletion src/aqua/cli/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ rendered to terminal/JSON instead of MCP responses.
| `sideshift.py` | `aqua sideshift` | Cross-chain quote, send, receive, status. |
| `changelly.py` | `aqua changelly` | USDt cross-chain quote, send, receive, status. |
| `jan3.py` | `aqua jan3` | JAN3 account login + sessions (multi-account, one per email): `login`/`verify` (free email-OTP, default), `login-start`/`login-complete` (paid captchaless fallback), `session-info`/`list-sessions`/`logout`. Maps to the `jan3_*` tools; decoupled from WapuPay's API key. |
| `wapupay.py` | `aqua wapupay` | Argentine direct-fiat (calls WapuPay directly; needs an API key — env `WAPUPAY_API_KEY` or `provision-account --email`): rates (public), quote, create-order (→ Liquid USDT address), fund-order, order-status, transactions, transaction, spending-limit, provision-account (get/rotate the API key via your JAN3 login for `--email`, stored locally). |
| `wapupay.py` | `aqua wapupay` | Argentine direct-fiat (calls WapuPay directly; needs an API key — env `WAPUPAY_API_KEY` or `provision-account --email`): rates (public), quote, create-order (→ Liquid funding address; `--funding-method USDT|LBTC`), fund-order, order-status, transactions, transaction, spending-limit, provision-account (get/rotate the API key via your JAN3 login for `--email`, stored locally). |
| `serve.py` | `aqua serve` | Run the MCP stdio server from the CLI. |
| `output.py` | helper | JSON / pretty rendering. Pretty mode uses `click.echo`; JSON dumps via `json.dumps(..., indent=2)`. |
| `password.py` | helper | Secret resolution chain. See below. |
Expand Down
30 changes: 19 additions & 11 deletions src/aqua/cli/wapupay.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""WapuPay CLI — Argentine direct-fiat payments funded with USDT on Liquid.
"""WapuPay CLI — Argentine direct-fiat payments funded with USDT or L-BTC on Liquid.

Pay an Argentine bank account (alias / CBU / CVU) in ARS via P2P swap, funded with USDT on
Liquid. These commands call WapuPay directly and require `WAPUPAY_API_KEY` set in
the environment. `create-order` returns a Liquid USDT address to fund; pay it with
Pay an Argentine bank account (alias / CBU / CVU) in ARS via P2P swap, funded with USDT or
L-BTC on Liquid. These commands call WapuPay directly and require `WAPUPAY_API_KEY` set in
the environment. `create-order` returns a Liquid funding address; pay it with
`aqua liquid send-asset` and WapuPay settles the ARS payout.
"""

Expand Down Expand Up @@ -30,10 +30,10 @@

@click.group()
def wapupay():
"""WapuPay — pay Argentine bank accounts in ARS, funded with USDT on Liquid.
"""WapuPay — pay Argentine bank accounts in ARS, funded with USDT or L-BTC on Liquid.

Calls WapuPay directly; set WAPUPAY_API_KEY in your environment first.
`create-order` returns a Liquid USDT address to fund — pay it with
`create-order` returns a Liquid address to fund — pay it with
`aqua liquid send-asset` and WapuPay settles the pesos.
"""

Expand Down Expand Up @@ -90,18 +90,25 @@ def quote(ctx, amount_ars, transfer_type, alias):
"--wallet-name", default="default", show_default=True,
help="Wallet you intend to fund from.",
)
@click.option(
"--funding-method", "funding_method",
type=click.Choice(["USDT", "LBTC"]), default="USDT", show_default=True,
help="Funding rail: USDT (default) or LBTC. Both settle on Liquid; LBTC "
"is paid as L-BTC sats (total_amount_sats), USDT as USDT base units.",
)
@click.option(
"--yes", "-y", "skip_confirm", is_flag=True, default=False,
help="Skip the interactive quote-confirmation prompt.",
)
@click.pass_obj
def create_order(ctx, amount_ars, alias, transfer_type, receiver_name, refund_address,
wallet_name, skip_confirm):
"""Create a direct-fiat order and get a Liquid USDT funding address.
wallet_name, funding_method, skip_confirm):
"""Create a direct-fiat order and get a Liquid funding address.

Fetches a quote for confirmation, then creates the order and issues funding
instructions. Fund the returned address with `aqua liquid send-asset`;
WapuPay then pays the pesos. This command never broadcasts a payment.
instructions (USDT by default, or L-BTC via --funding-method). Fund the
returned address with `aqua liquid send-asset`; WapuPay then pays the pesos.
This command never broadcasts a payment.
"""
if refund_address and refund_address.strip():
try:
Expand Down Expand Up @@ -137,6 +144,7 @@ def create_order(ctx, amount_ars, alias, transfer_type, receiver_name, refund_ad
receiver_name=receiver_name,
refund_address=refund_address,
wallet_name=wallet_name,
funding_method=funding_method,
),
)

Expand All @@ -145,7 +153,7 @@ def create_order(ctx, amount_ars, alias, transfer_type, receiver_name, refund_ad
@click.option("--tentative-id", required=True, help="Order id from `create-order`.")
@click.pass_obj
def fund_order(ctx, tentative_id):
"""Issue (or re-issue) the Liquid USDT funding instructions for an order."""
"""Issue (or re-issue) the Liquid funding instructions for an order."""
run_tool(ctx, lambda: wapupay_fund_order(tentative_id))


Expand Down
40 changes: 28 additions & 12 deletions src/aqua/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1137,12 +1137,14 @@
},
"wapupay_create_order": {
"description": (
"Create a WapuPay order and get a Liquid USDT funding address. "
"Create a WapuPay order and get a Liquid funding address. Funds from "
"USDT (default) or L-BTC — both settle from a Liquid address. "
"Creates the tentative (freezing the quote) and issues "
"funding instructions. Returns address_destination (Liquid), asset_id "
"(USDT), funding_amount_usdt, total_amount_usdt, "
"total_funding_amount_base_units, funding_expires_at and a QR. Pay the "
"TOTAL with lw_send_asset (amount = total_funding_amount_base_units); "
"funding instructions. Returns address_destination (Liquid), asset_id, "
"funding_amount_usdt, total_amount_usdt, "
"total_funding_amount_base_units (for USDT), total_amount_sats (for LBTC), "
"expires_at and a QR. Pay the TOTAL with lw_send_asset (amount "
"and unit depend on funding_method — follow pay_instructions); "
"WapuPay then makes a P2P payer settle ARS to the bank account. Does NOT broadcast the "
"payment itself — confirm the quote with the user first via wapupay_quote."
),
Expand All @@ -1167,13 +1169,25 @@
"receiver_name": {"type": "string", "description": "Recipient name (optional)"},
"refund_address": {"type": "string", "description": "Liquid mainnet refund address (lq1…/ex1…) if funding cannot execute (optional); validated before the order is created"},
"wallet_name": {"type": "string", "default": "default", "description": "Wallet you intend to fund from (recorded for tracking)"},
"funding_method": {
"type": "string",
"enum": ["USDT", "LBTC"],
"default": "USDT",
"description": (
"Funding rail for the payout — 'USDT' (default) or 'LBTC'. Both "
"settle from a Liquid address. For 'LBTC', WapuPay returns "
"total_amount_sats (the exact sats of L-BTC to send); for 'USDT', "
"send total_funding_amount_base_units. Follow the returned "
"pay_instructions for the exact amount and unit."
),
},
},
"required": ["amount_ars", "alias"],
},
},
"wapupay_fund_order": {
"description": (
"Issue (or re-issue) Liquid USDT funding instructions for an existing "
"Issue (or re-issue) Liquid funding instructions for an existing "
"order. Use to recover an order created without funding, or to refresh "
"the funding address before it expires. Returns the funding address + QR."
),
Expand Down Expand Up @@ -1777,17 +1791,19 @@ def create_server(config: Config | None = None) -> Server:
- Memo networks (BNB Beacon, Stellar, etc.) require a memo on either
the deposit or settle side — pass settle_memo / refund_memo when prompted.

WAPUPAY (Argentine fiat payouts, funded with USDT on Liquid):
WAPUPAY (Argentine fiat payouts, funded with USDT or L-BTC on Liquid):
- WHAT IT IS: WapuPay is NOT an exchange. It is an automated peer-to-peer (P2P)
platform — it finds a trusted P2P payer who settles the payment in Argentine
pesos (ARS) on the user's behalf (think "Uber for P2P"). The user funds with
USDT on Liquid; a matched payer pushes the pesos to the recipient's bank account.
USDT (default) or L-BTC on Liquid; a matched payer pushes the pesos to the
recipient's bank account.
If the user asks "what is WapuPay / what can I do with it", explain this; the full
blurb is the aqua://docs/wapupay resource.
- FLOW: wapupay_quote (preview cost) → wapupay_create_order (returns a Liquid USDT
address + amount) → pay it with lw_send_asset → WapuPay settles the ARS payout.
- FLOW: wapupay_quote (preview cost) → wapupay_create_order (funding_method USDT or
LBTC; returns a Liquid address — follow pay_instructions for the exact amount and
unit) → pay it with lw_send_asset → WapuPay settles the ARS payout.
This never auto-pays; always confirm the quote with the user first.
After the user pays the Liquid USDT address, WapuPay orchestrates the operation with a P2P payer that settles the ARS.
After the user pays the Liquid funding address, WapuPay orchestrates the operation with a P2P payer that settles the ARS.
Offer the user to check the status of the order with `wapupay_order_status` and the executed_transaction_id with `wapupay_transaction`,
the executed_transaction contain the details of the fiat transfer that the user wants to know about.
- wapupay_exchange_rates is public (use USDT/ARS ignore the others rates, no key). The order/transaction tools
Expand Down Expand Up @@ -2627,7 +2643,7 @@ async def list_resources() -> list[Resource]:
Resource(
uri="aqua://docs/wapupay",
name="What is WapuPay?",
description="WapuPay overview: automated P2P ARS payouts funded with USDT on Liquid",
description="WapuPay overview: automated P2P ARS payouts funded with USDT or L-BTC on Liquid",
mimeType="text/markdown",
),
]
Expand Down
Loading
Loading