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
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<!-- cspell:words Hedera Solflare keypair mainnet devnet ALGOVOI algv Helius Backpack Triton Phantom -->

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

This sample demonstrates the A2A `ap2-extension` for a human-present transaction
where the buyer settles with on-chain USDC on Solana (or native SOL for
micropayments). It mirrors the existing `x402` scenario but uses Solana Pay
semantics — specifically the **`reference` pubkey** primitive — to bind the
settling transaction to a specific AP2 `PaymentMandate`.

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

## 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 Solana variant adds one additional primitive on top of the standard AP2
mandate chain:

### Solana Pay `reference` binding

Solana has no native transaction memo field (unlike Algorand or Hedera), and
the SPL Memo Program is unreliable across wallets. Instead, Solana Pay defines
a `reference` pubkey mechanism:

1. The merchant (or its Merchant Agent) generates a fresh, single-use ed25519
keypair per checkout.
2. The public key is embedded in the Solana Pay URL:

```text
solana:<recipient>
?amount=<amount>
&spl-token=<USDC_MINT>
&reference=<reference_pubkey>
&label=<label>
&message=<order-id>
```

3. The buyer's wallet (Phantom, Solflare, Backpack, etc.) includes the
`reference` pubkey as a read-only, non-signer account in the transfer
instruction.
4. After the user signs and broadcasts the SPL Token transfer, the merchant
(or facilitator) queries `getSignaturesForAddress(<reference>)` to locate
the exact transaction that settled this order.
5. Final verification compares the on-chain transfer's recipient, amount, and
SPL mint against the AP2 `PaymentMandate` contents. All three must match.

This is stronger than relying on amount uniqueness (EVM-style) and more
wallet-compatible than relying on memos. It also means the merchant never has
to ask the buyer to paste a transaction signature back.

## 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 and assembles the `CartMandate`.
- **Merchant Payment Processor Agent:** Takes payments on behalf of the
merchant and, in the Solana flow, verifies the on-chain USDC transfer
against the expected `reference`, amount, and mint.
- **Credentials Provider Agent:** Holds the user's payment credentials — in
this flow, the Solana wallet address and optional mint preferences.

## Mandate Chain

The AP2 mandate chain is unchanged by the Solana variant:

```text
IntentMandate ──▶ CartMandate ──▶ PaymentMandate
(user) (merchant) (user-signed)
Solana Pay URL (reference-bound)
SPL Token Transfer + reference pubkey
getSignaturesForAddress(reference)
PaymentReceipt
```

## Payment Method

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

## Assets Supported

| Asset | Mint | Decimals |
| --- | --- | --- |
| USDC (Solana mainnet) | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | 6 |
| USDT (Solana mainnet) | `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` | 6 |
| USDC (Solana devnet) | `4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU` | 6 |
| Native SOL | — | 9 |

The Solana Pay URL format
(`solana:<recipient>?amount=...&spl-token=<mint>&reference=<pubkey>`) carries
the mint identifier, so supporting additional SPL tokens is a configuration
concern on the Merchant Agent and does not require protocol changes.

## Running the sample

```bash
# From the repository root:
export ALGOVOI_API_KEY="algv_..." # or any Solana-aware facilitator
export SOLANA_RPC_URL="https://..." # Solana mainnet or devnet RPC
export GOOGLE_API_KEY="..." # or GOOGLE_GENAI_USE_VERTEXAI=true
./samples/python/scenarios/a2a/human-present/crypto-solana/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 then drive a purchase end-to-end with USDC on Solana.

## Why Solana Pay `reference` matters for AP2

AP2 mandates are already cryptographically signed, but they describe intent
and authorization — not the on-chain settlement event itself. The `reference`
pubkey is the missing link that ties a specific blockchain transaction to a
specific `PaymentMandate` deterministically, without trusting the buyer to
self-report their transaction signature. 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

- [AlgoVoi facilitator](https://api1.ilovechicken.co.uk) — verifies Solana
USDC transfers by (a) resolving `getSignaturesForAddress(reference)`,
(b) fetching the transaction, (c) checking mint + amount + recipient, and
(d) confirming the `reference` pubkey appears in the transaction's
account keys.
- [Solana Pay specification](https://solanapay.com/) — the canonical
reference for URL format and merchant-side flow.
130 changes: 130 additions & 0 deletions code/samples/python/scenarios/a2a/human-present/crypto-solana/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/bin/bash
# cspell:words ALGOVOI algv mainnet devnet Helius

# A script to automate the execution of the crypto-solana (on-chain USDC on Solana)
# AP2 example. It starts all necessary servers and agents in the background.
#
# This scenario uses Solana Pay `reference` pubkey binding to link the settling
# transaction deterministically to the signed AP2 PaymentMandate. See README.md
# for the full flow.

set -e

export PAYMENT_METHOD=CRYPTO_SOLANA

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 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 — don't override
export "$key=$remainder"
done < .env
fi
Comment on lines +13 to +33
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.

medium

The PAYMENT_METHOD environment variable is set at line 12, but then .env is sourced at line 25. If the user has PAYMENT_METHOD defined in their .env file, it will override the scenario-specific value (CRYPTO_SOLANA), potentially causing the script to execute with the wrong payment logic. It is safer to source the .env file first and then set scenario-specific overrides. Additionally, initializing the pids array early ensures that the cleanup trap (line 80) is always safe even if the script fails during early setup (e.g., line 20).

Suggested change
export PAYMENT_METHOD=CRYPTO_SOLANA
AGENTS_DIR="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
if [ -f .env ]; then
set -a
source .env
set +a
fi
if [ -f .env ]; then
set -a
source .env
set +a
fi
export PAYMENT_METHOD=CRYPTO_SOLANA
AGENTS_DIR="samples/python/src/roles"
LOG_DIR=".logs"
pids=()
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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is already addressed in the current commit. PAYMENT_METHOD=CRYPTO_SOLANA is exported on line 13, before .env is processed. The while IFS= loop then uses [[ -v "$key" ]] to skip any variable that is already present in the environment — so a PAYMENT_METHOD entry in .env cannot override the scenario value. The result is identical to your suggestion (scenario value always wins) but the guard applies to every variable, not just PAYMENT_METHOD.


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

# Facilitator for on-chain verification. Defaults point at AlgoVoi Cloud but
# any Solana-aware AP2 facilitator works; set the env var to override.
if [ -z "${ALGOVOI_API_KEY}" ]; then
echo "Please set your ALGOVOI_API_KEY environment variable before running."
echo "Sign up at https://cloud.algovoi.co.uk (or use any Solana-aware AP2 facilitator)."
exit 1
fi

# Solana RPC. Defaults to mainnet public; override with a paid provider
# (Helius, Alchemy, Triton) for reliability.
if [ -z "${SOLANA_RPC_URL}" ]; then
export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"
echo "SOLANA_RPC_URL not set; defaulting to the public Solana mainnet endpoint."
echo "For production usage, configure a paid RPC provider for rate-limit headroom."
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

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