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
2 changes: 2 additions & 0 deletions .cspell/custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ agentic
Agentic
agenticpayments
Algorand
ALGOVOI
AlgoVoi
androidx
Applebot
appname
Expand Down
13 changes: 10 additions & 3 deletions .github/workflows/linter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,34 @@ on:
pull_request:
branches: [main]

permissions:
contents: read
statuses: write
pull-requests: write

jobs:
build:
name: Lint Code Base
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v5
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
with:
fetch-depth: 0
persist-credentials: false

- name: Lint Code Base
uses: super-linter/super-linter/slim@v8
uses: super-linter/super-linter/slim@4ce20838b8ab83717e78138c5b3a1407148e0918 # v8.7.0
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LOG_LEVEL: WARN
SHELLCHECK_OPTS: -e SC1091 -e 2086
VALIDATE_ALL_CODEBASE: false
FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/).*|CODE_OF_CONDUCT.md|CHANGELOG.md"
FILTER_REGEX_EXCLUDE: "^(\\.github/|\\.vscode/|code/samples/|code/web-client/).*|CODE_OF_CONDUCT.md|CHANGELOG.md"
VALIDATE_BIOME_FORMAT: false
VALIDATE_BIOME_LINT: false
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_FLAKE8: false
VALIDATE_PYTHON_ISORT: false
Expand Down
5 changes: 5 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"files": {
"includes": ["**", "!code/web-client"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<!-- cspell:words Algorand algorand ALGOVOI AlgoVoi algod ASA microalgos testnet mainnet Pera Defly USDt -->

# Agent Payments Protocol Sample: Human Present Purchases with On-Chain Algorand USDC

This sample demonstrates the A2A `ap2-extension` for a human-present transaction
where the buyer settles with on-chain USDC on Algorand (or native ALGO for
micropayments). It mirrors the existing `x402` scenario but uses Algorand
settlement semantics, specifically the transaction **note field**, to bind the
settling transaction to a specific AP2 `PaymentMandate`.

**Note:** This sample pairs with the separate `crypto-solana` human-present
scenario. Together they cover non-EVM settlement on Algorand and Solana as a
complement to the EVM-focused `x402` path.

## What this sample includes (and what it does not)

This directory ships two things: this document and a `run.sh` launcher. The
launcher starts the standard AP2 sample roles (Merchant, Credentials Provider,
Merchant Payment Processor, and the Shopping Agent) with
`PAYMENT_METHOD=CRYPTO_ALGO` exported so downstream code can branch on it.

The bundled sample role code does **not** itself implement the
`CRYPTO_ALGO` settlement path end to end. Verifying an on-chain Algorand
transfer, resolving the note binding, and returning a `PaymentReceipt` require
an **external, AP2-aware facilitator** that understands Algorand settlement.
The sections below describe the intended flow and the note-binding contract so
that a facilitator (AlgoVoi Cloud is one example, but any Algorand-aware AP2
facilitator works) can be plugged in. Treat this scenario as a reference design
plus a launcher, not as a self-contained end-to-end demonstration.

## Scenario

Human-present flows are commerce flows where the user is present to confirm
purchase details. The user signs the `PaymentMandate`, giving all parties high
confidence in the transaction.

The Algorand variant adds one additional primitive on top of the standard AP2
mandate chain:

### Note-field binding

Algorand transactions carry an arbitrary **note field** (up to 1024 bytes).
Unlike Solana, no side-channel `reference` account is needed, because the note
travels inside the settling transaction itself. This scenario uses a compact
binding tag:

1. When the Merchant Agent assembles the `CartMandate`, it derives a short
binding token from the canonical cart contents.
2. The token is placed in the payment request note field, prefixed so it is
easy to identify on chain:

```text
av:<token>
```

The token is truncated to 20 characters, so the whole tag is roughly 23
bytes. Algorand allows up to 1024 note bytes, so this stays well within the
field with generous headroom, and it keeps the tag cheap to index.
3. The payment request is expressed as an ARC-26 Algorand URI so any compatible
wallet (Pera, Defly, and others) can construct the transfer:

```text
algorand://<recipient>
?amount=<amount>
&asset=<USDC_asset_id>
&note=av:<token>
```

4. The buyer signs and broadcasts the ASA transfer. The note rides along inside
the same transaction.
5. After settlement, the facilitator (invoked by the Merchant Payment Processor
Agent) fetches the confirmed transaction and checks that the note carries the
expected `av:` binding tag and that the recipient, amount, and asset id match
the AP2 `PaymentMandate`.

### MPP rejection behavior

The Merchant Payment Processor (MPP) Agent treats the note binding as a hard
gate. If the settling transaction's note does not carry the expected `av:`
tag for this cart, or if the recipient, amount, or asset id disagree with the
signed `PaymentMandate`, the MPP **rejects** the payment rather than issuing a
`PaymentReceipt`. This gives a cryptographic consistency check between what the
user signed off chain and what actually settled on chain, without trusting the
buyer to self-report a transaction id.

## Key Actors

This sample consists of:

- **Shopping Agent:** The main orchestrator that handles the user's shopping
request and delegates to specialist agents.
- **Merchant Agent:** Handles product queries, assembles the `CartMandate`, and
derives the `av:` note binding token for the payment request.
- **Merchant Payment Processor Agent:** Takes payments on behalf of the
merchant and, in the Algorand flow, delegates verification of the on-chain
ASA transfer (note binding, recipient, amount, asset id) to an external
facilitator.
- **Credentials Provider Agent:** Holds the user's payment credentials, in this
flow the Algorand address and optional asset preferences.

## Mandate Chain

The AP2 mandate chain is unchanged by the Algorand variant:

```text
IntentMandate ==> CartMandate ==> PaymentMandate
(user) (merchant) (user-signed)
|
v
ARC-26 Algorand URI (note-bound)
|
v
ASA transfer + note=av:<token>
|
v
facilitator verifies note + amount + asset
|
v
PaymentReceipt
```

## Payment Method

This scenario sets `PAYMENT_METHOD=CRYPTO_ALGO` at startup. Downstream agents
treat it as a non-card, non-x402 payment flow and route through the
Algorand-aware credentials provider and facilitator path.

## Assets Supported

| Asset | Algorand ASA ID | Decimals |
| --- | --- | --- |
| USDC (Algorand mainnet) | `31566704` | 6 |
| USDt (Algorand mainnet) | `312769` | 6 |
| USDC (Algorand testnet) | `10458941` | 6 |
| Native ALGO | (none, base unit is microalgos) | 6 |

The ARC-26 URI carries the `asset` id, so supporting additional ASAs is a
configuration concern on the Merchant Agent and does not require protocol
changes. Native ALGO transfers omit the `asset` parameter and are denominated
in microalgos.

## Running the sample

```bash
# From the repository root:
export GOOGLE_API_KEY="..." # or GOOGLE_GENAI_USE_VERTEXAI=true

# Optional: point the flow at an Algorand-aware AP2 facilitator.
# AlgoVoi Cloud is one example; any facilitator that verifies Algorand
# settlement and the av: note binding works. If you do not configure one,
# the launcher still starts the agents, but on-chain verification will be
# a no-op until a facilitator is wired in.
# export ALGOVOI_API_KEY="..." # example facilitator credential
# export ALGORAND_RPC_URL="https://..." # algod endpoint (mainnet/testnet)

./code/samples/python/scenarios/a2a/human-present/crypto-algo/run.sh
```

The sample script starts the Merchant, Credentials Provider, and Merchant
Payment Processor agents locally, then launches the Shopping Agent via the ADK
web UI. You can drive the shopping conversation through mandate creation. To
complete on-chain settlement and receive a `PaymentReceipt`, connect an
external Algorand-aware facilitator as described above.

## Why note-field binding matters for AP2

AP2 mandates are already cryptographically signed, but they describe intent and
authorization, not the on-chain settlement event itself. The `av:` note tag is
the link that ties a specific Algorand transaction to a specific `CartMandate`
deterministically, inside the settling transaction, without trusting the buyer
to self-report a transaction id. For agent-initiated commerce, where the buyer
may be an AI agent and the merchant may be another AI agent, this mechanical
binding removes an otherwise social trust layer.

## Reference implementations

- Any Algorand-aware AP2 facilitator that can (a) fetch the confirmed
transaction from an algod or indexer endpoint, (b) check the note carries the
expected `av:` binding tag, and (c) confirm the asset id, amount, and
recipient match the signed `PaymentMandate`. AlgoVoi Cloud is one example
implementation of such a facilitator.
- [ARC-26 Algorand URI scheme](https://arc.algorand.foundation/ARCs/arc-0026)
for the canonical payment request URI format.
- [Algorand Standard Assets (ASA)](https://developer.algorand.org/docs/get-details/asa/)
for asset transfer mechanics.
127 changes: 127 additions & 0 deletions code/samples/python/scenarios/a2a/human-present/crypto-algo/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/bin/bash
# cspell:words ALGOVOI algod ASA

# A script to automate the execution of the crypto-algo (on-chain USDC on
# Algorand) AP2 example. It starts all necessary servers and agents in the
# background.
#
# This scenario uses an Algorand note-field binding (av:<token>) to link the
# settling transaction to the signed AP2 PaymentMandate. See README.md for the
# full flow and note that on-chain verification requires an external,
# Algorand-aware AP2 facilitator.

set -e

export PAYMENT_METHOD=CRYPTO_ALGO

AGENTS_DIR="code/samples/python/src/roles"
LOG_DIR=".logs"

if [ ! -d "$AGENTS_DIR" ]; then
echo "Error: Directory '$AGENTS_DIR' not found."
echo "Please run this script from the root of the repository."
exit 1
fi

# Source .env for defaults, but do not override variables already present in
# the calling environment. That lets the caller's shell settings (for example
# PAYMENT_METHOD exported above) take precedence over local configuration files.
if [ -f .env ]; then
while IFS='=' read -r key remainder || [[ -n "$key" ]]; do
case "$key" in ''|\#*) continue ;; esac # skip blank lines and comments
[[ -v "$key" ]] && continue # already exported, do not override
export "$key=$remainder"
done < .env
fi

USE_VERTEXAI=$(printf "%s" "${GOOGLE_GENAI_USE_VERTEXAI}" | tr '[:upper:]' '[:lower:]')
if [ -z "${GOOGLE_API_KEY}" ] && [ "${USE_VERTEXAI}" != "true" ]; then
echo "Please set your GOOGLE_API_KEY environment variable before running."
echo "Alternatively, set GOOGLE_GENAI_USE_VERTEXAI=true to use Vertex AI with ADC."
exit 1
fi

# On-chain settlement in this scenario is performed by an external,
# Algorand-aware AP2 facilitator (see README.md). AlgoVoi Cloud is one example;
# any facilitator that verifies the av: note binding works. Configuring one is
# optional here: without it the agents still start, but on-chain verification
# is a no-op until a facilitator is wired in.
if [ -z "${ALGOVOI_API_KEY}" ]; then
echo "Note: no facilitator credential set (ALGOVOI_API_KEY is empty)."
echo "The agents will start, but on-chain settlement verification is a no-op"
echo "until an Algorand-aware AP2 facilitator is configured. See README.md."
fi

echo "Setting up the Python virtual environment..."

if [ ! -d ".venv" ]; then
uv venv
fi

case "$OSTYPE" in
msys* | cygwin*)
source .venv/Scripts/activate
;;
*)
source .venv/bin/activate
;;
esac
echo "Virtual environment activated."

mkdir -p "$LOG_DIR"

# Initialise pids before the trap so cleanup() is always safe to call, even if
# the script exits before any background processes are started.
pids=()

cleanup() {
echo ""
echo "Shutting down background processes..."
if [ ${#pids[@]} -ne 0 ]; then
kill "${pids[@]}" 2>/dev/null
wait "${pids[@]}" 2>/dev/null
fi
echo "Cleanup complete."
}

trap cleanup EXIT

echo "Syncing virtual environment with uv sync..."
if uv sync --package ap2-samples; then
echo "Virtual environment synced successfully."
else
echo "Error: uv sync failed. Aborting."
exit 1
fi

echo "Clearing the logs directory..."
if [ -d "$LOG_DIR" ]; then
find "$LOG_DIR" -mindepth 1 -delete
fi
Comment on lines +98 to +100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

low

Clearing the log directory using a wildcard * can be problematic if the directory is empty (the wildcard might not expand) or contains a very large number of files. A more robust approach is to use find to delete the contents without relying on shell expansion.

Suggested change
if [ -d "$LOG_DIR" ]; then
rm -f "$LOG_DIR"/*
fi
if [ -d "$LOG_DIR" ]; then
find "$LOG_DIR" -mindepth 1 -delete
fi


echo ""
echo "Starting remote servers and agents as background processes..."

UV_RUN_CMD="uv run --no-sync"

if [ -f ".env" ]; then
UV_RUN_CMD="$UV_RUN_CMD --env-file .env"
fi

echo "-> Starting the Merchant Agent (port:8001 log:$LOG_DIR/merchant_agent.log)..."
$UV_RUN_CMD --package ap2-samples python -m roles.merchant_agent >"$LOG_DIR/merchant_agent.log" 2>&1 &
pids+=($!)

echo "-> Starting the Credentials Provider (port:8002 log:$LOG_DIR/credentials_provider_agent.log)..."
$UV_RUN_CMD --package ap2-samples python -m roles.credentials_provider_agent >"$LOG_DIR/credentials_provider_agent.log" 2>&1 &
pids+=($!)

echo "-> Starting the Merchant Payment Processor Agent (port:8003 log:$LOG_DIR/mpp_agent.log)..."
$UV_RUN_CMD --package ap2-samples python -m roles.merchant_payment_processor_agent >"$LOG_DIR/mpp_agent.log" 2>&1 &
pids+=($!)

echo ""
echo "All remote servers are starting."

echo "Starting the Shopping Agent..."
$UV_RUN_CMD --package ap2-samples adk web --host 0.0.0.0 $AGENTS_DIR/shopping_agent
Loading
Loading