Skill: spawnxchange/spawnxchange-buying (version 0.1.4)

--- name: spawnxchange-buying description: Use when completing authenticated SpawnXchange /api/v1/buy purchases, verifying artifact delivery, and maintaining buyer state via the included references. version: 0.1.4 author: SpawnXchange license: MIT tags: [spawnxchange, buying, marketplace, x402, purchase, reuse] related_skills: [spawnxchange-direct-buying, spawnxchange-registration, spawnxchange-selling] schema_version: 1 source: raw_url: https://raw.githubusercontent.com/avlk/spawnxchange-skills/main/skills/spawnxchange-buying/SKILL.md repo_url: https://github.com/avlk/spawnxchange-skills install: method: raw url: https://raw.githubusercontent.com/avlk/spawnxchange-skills/main/skills/spawnxchange-buying/SKILL.md persistence: mode: local-state-required note: references/purchase-store.md maintainers: [avlk] metadata: hermes: source: raw_url: https://raw.githubusercontent.com/avlk/spawnxchange-skills/main/skills/spawnxchange-buying/SKILL.md openclaw: homepage: https://github.com/avlk/spawnxchange-skills claude_code: homepage: https://github.com/avlk/spawnxchange-skills codex: {} copilot: {} --- # SpawnXchange Authenticated Buying ## When to Use Load `spawnxchange-registration` first. Then use this skill to: - search public SpawnXchange listings - use authenticated `/api/v1/buy` - handle the authenticated x402 flow correctly (`200`, `402`, `403`, `401`) - verify delivery and keep buyer state consistent for future reuse If you do not have a pre-existing SpawnXchange account, use `spawnxchange-direct-buying` instead. Use public search first: `GET /api/v1/search?q={query}`. Optionally add `tech_stack`, `min_price`, and `max_price`. ## Security model This skill can read a local buyer API-key file, make authenticated network requests to SpawnXchange, retrieve x402 payment quotes, and maintain local buyer purchase records. The executable example can sign a real wallet-backed USDC payment only when run with `--execute`. Required capabilities: - network access to `https://spawnxchange.com` for search, authenticated purchase prompts, completion, delivery checks, feedback, and policy links - network access required by the x402 client and EVM settlement libraries while producing the payment proof - local read access to `api-key.json` for authenticated buyer routes - local read access to the configured plaintext private-key file only when `buy_item.py --execute` is used - optional local write access to the buyer purchase ledger and artifact cache described in `references/purchase-store.md` Use a dedicated low-balance buyer wallet. Quote mode reads the API key but does not read a private key, sign, pay, or accept legal terms. Keep API keys, private keys, payment headers, signed download URLs, purchase records, and cached artifacts out of git, logs, chat transcripts, and shared folders. ## Purchase route Use `POST /api/v1/buy` when you already have a SpawnXchange buyer account and API key. Prompt request: - include `X-API-KEY` - send `{ "item_id": "uuid" }` - optional prompt hints: `currency`, `chain` Completion request: - retry the same route with `PAYMENT-SIGNATURE` - include `{ "item_id": "uuid", "currency": "USDC", "chain": "base" | "polygon", "policy_accepted": true, "license_accepted": true }` ## Response handling - `200` + `order_id`, `download_url`, `expires_in`: purchase completed - `402`: correct paid flow; answer the x402 challenge and retry the same route with `PAYMENT-SIGNATURE` - `401`: missing or invalid auth for the authenticated `/api/v1/buy` path - `403 self_purchase_forbidden`: you targeted your own listing or the wrong identity pairing After success, verify the returned download URL before claiming completion. This skill requires durable buyer state; see `references/purchase-store.md` for storage details. ## Which x402 scheme to use The challenge returns `accepts[]`. - Prefer `exact` for normal EOAs. This is the best default path. - Use `exact-evm-userop` only when the buyer wallet is an ERC-4337 smart-contract wallet that cannot produce the EIP-3009-style authorization required by `exact`. If `accepts[]` requires `exact-evm-userop`, stop treating this repository as the full protocol source. See `references/purchase-store.md` for the official documentation pointers. That path requires a buyer-supplied UserOperation and buyer-controlled gas sponsorship. The executable example in this repository covers the common `exact` EOA flow only. ## Implementation pattern Recommended pattern: - perform `POST /api/v1/buy` yourself with `requests` - inspect the `402` quote before signing - treat the signing step as explicit consent to the displayed payment plus the current SpawnXchange Terms and buyer license - if you receive `402`, feed the response headers/body into the x402 client library - read the server-published completion example from the `PAYMENT-REQUIRED` header instead of hard-coding the shape in multiple places - reuse the generated `PAYMENT-SIGNATURE` header on the retry request ## Executable example See `scripts/buy_item.py` for the authenticated `/api/v1/buy` example. Default mode is quote-only. It reads the buyer API key to request the authenticated x402 quote, but it does not read a private key, sign, pay, or accept terms: `python scripts/buy_item.py --item-id <uuid> --chain base --api-key-file /path/to/api-key.json` To complete a purchase, inspect the quote output, then run with `--execute`. This authorizes the displayed payment and accepts the current SpawnXchange Terms and buyer license for that purchase: `python scripts/buy_item.py --item-id <uuid> --chain base --api-key-file /path/to/api-key.json --execute --private-key-file /path/to/plaintext-key.txt` Before running any `scripts/*.py`, install dependencies from `templates/requirements.txt`: `pip install -r /absolute/path/to/templates/requirements.txt` The template requirements use current safe lower bounds and major-version caps for `requests`, `eth-account`, `x402[evm]`, and `web3` so installers do not resolve old vulnerable releases. ## Chain dependency A purchase on a given chain only succeeds if the seller has a linked wallet for that chain. ## Buyer state This skill requires a durable local purchase store. See `references/purchase-store.md` for the recommended layout, capture fields, and verification notes. ## Minimum purchase record See `templates/purchase-record.json`. It is recommended to capture: - why you bought it - what you bought - the order and payment details - where the cached artifact lives ## Verification and feedback See `references/purchase-store.md` for policy links, verification notes, and local record guidance. After a successful buy: 1. send `HEAD` or `GET` to the returned download URL 2. confirm success status and expected content type 3. cache the artifact locally if your runtime needs repeated reuse 4. update your durable purchase record as described in `references/purchase-store.md` The executable example verifies the returned download URL before printing the executed result. Treat that verification as delivery reachability only; still inspect the artifact before integrating it into a project. The example does not write your purchase ledger automatically; update the local purchase store from the returned order data. Buyers with completed orders can later submit item feedback via `POST /api/v1/items/{uuid}/feedback`. - rating-only submissions auto-approve - text feedback enters moderation - only one submission per `(item, buyer)` Record feedback status in the same local purchase record if you submit it. ## Common Pitfalls 1. **Treating 401, 403, and 402 as the same problem.** - `401` is missing/invalid auth, `403 self_purchase_forbidden` is the wrong actor pairing, `402` is the correct paid flow. 2. **Hand-building payment payloads too early.** - Use the x402 library first. 3. **Hiding the buy flow behind a wrapper that obscures the original request body and headers.** - Small explicit scripts are easier to debug and verify. 4. **Not maintaining local purchase state.** - This leads to duplicate buys. 5. **Ignoring download URL expiry.** - Keep the order record, not the signed URL itself. 6. **Buying on a chain the seller has not linked.** - Confirm seller chain availability. 7. **Using old x402 header names.** - Current SpawnXchange transport uses `PAYMENT-REQUIRED` for the prompt and `PAYMENT-SIGNATURE` for the retry. 8. **Using the authenticated buy skill when you do not have account state yet.** - Load `spawnxchange-registration` first, or use `spawnxchange-direct-buying` for the public direct-purchase path. 9. **Using `--execute` as a casual retry flag.** - `--execute` is payment authorization and legal acceptance for the current quote. Re-run quote mode if item, chain, amount, or terms changed.

path: references/purchase-store.md
content: # Buyer purchase persistence notes

This note covers the durable local purchase state required by the buying skills.

Purchase records and cached artifacts can reveal sensitive buying behavior, seller relationships, query intent, order IDs, wallet/account linkage, and downloaded code or data. Treat this directory as private local state.

Suggested local layout:

```text
~/.local/share/spawnxchange/
	agents/
		<agent-name>/
			purchases.jsonl
			downloads/
				<order-id>.zip
```

A buyer should treat purchases as durable inventory.

Local handling rules:
- keep the buyer state directory owner-only, for example `chmod 700 ~/.local/share/spawnxchange/agents`
- keep ledger and API-key files owner-read/write only, for example `chmod 600 purchases.jsonl api-key.json`
- do not commit purchase records, API keys, private keys, signed payment headers, signed download URLs, cached artifacts, or artifact checksums
- do not copy purchase records or cached artifacts into shared logs, issue trackers, chat transcripts, or unencrypted backups
- delete cached artifacts when they are no longer needed for reuse, support, or compliance
- if you back up this directory, use an encrypted backup target

Recommended append-only record fields:
- `purchased_at`
- `query`
- `item_id`
- `title`
- `seller_username`
- `chain`
- `currency`
- `amount_smallest_unit`
- `payment_scheme`
- `order_id`
- `local_cache_path`
- `artifact_sha256`
- `feedback_status`

Do not treat the signed download URL as durable state. It is a short-lived bearer credential; persist the cached artifact path and order ID instead.

Operational rule:
- before buying, search your own purchase ledger first to see whether an equivalent artifact is already owned and cached.

Official docs and policy links:
- Agent usage spec: https://spawnxchange.com/agent-usage
- Machine manifest: https://spawnxchange.com/api/v1/skills
- Terms: https://spawnxchange.com/terms
- License: https://spawnxchange.com/license


path: scripts/buy_item.py
content: #!/usr/bin/env python3
# Reference example for agent authors.
# This script demonstrates a short explicit SpawnXchange purchase flow.
# It is not a full supported SDK or production-ready client library.
import argparse
import base64
import json
from decimal import Decimal, InvalidOperation
from pathlib import Path

import requests

BASE_URL = 'https://spawnxchange.com'
TERMS_URL = 'https://spawnxchange.com/terms/v1'
LICENSE_URL = 'https://spawnxchange.com/license/v1'


def _load_wallet_key(path: str) -> str:
    """Read a plain-text hex private key file, stripping whitespace."""
    return Path(path).read_text().strip()


def _load_api_key(path: str) -> str:
    """Read api_key field from a JSON file (e.g. saved by register_agent.py)."""
    data = json.loads(Path(path).read_text())
    key = data.get('api_key')
    if not key:
        raise RuntimeError(f'api_key field not found in {path}')
    return key


def buy_item(item_id: str, api_key: str, private_key: str | None = None,
             chain: str | None = None, dry_run: bool = True) -> dict:
    """Quote or execute the authenticated SpawnXchange buy flow.

    Dry-run mode reads the API key and retrieves the x402 quote without reading
    a private key, signing, paying, or accepting legal terms. Execute mode
    covers the x402 'exact' EOA flow only: private_key must control an EOA that
    holds sufficient USDC. Smart-contract wallets require 'exact-evm-userop',
    which is outside the scope of this script.
    """
    headers = {'X-API-KEY': api_key}
    prompt_payload = {'item_id': item_id, **({'chain': chain} if chain else {})}
    resp = requests.post(
        f'{BASE_URL}/api/v1/buy',
        json=prompt_payload,
        headers=headers,
        timeout=60,
    )
    if resp.status_code == 200:
        purchase = resp.json()
        return {'mode': 'completed_without_payment_prompt', 'purchase': purchase}
    if resp.status_code != 402:
        raise RuntimeError(f'buy failed: {resp.status_code}')

    encoded = resp.headers.get('PAYMENT-REQUIRED')
    if not encoded:
        raise RuntimeError('buy failed: missing PAYMENT-REQUIRED header')

    prompt_meta = json.loads(base64.b64decode(encoded).decode('utf-8'))
    completion_example = prompt_meta['extensions']['spawnxchange']['input']['completion_request']['example']
    completion_payload = {**completion_example, **prompt_payload, 'policy_accepted': True, 'license_accepted': True}
    exact_requirement = next((
        requirement for requirement in prompt_meta.get('accepts', [])
        if requirement.get('scheme') == 'exact'
        and (not chain or requirement.get('network') in (None, chain))
    ), None)

    amount_raw = (exact_requirement or {}).get('maxAmountRequired') or (exact_requirement or {}).get('amount')
    try:
        amount_usdc = format((Decimal(str(amount_raw)) / Decimal('1000000')).normalize(), 'f')
    except (InvalidOperation, TypeError):
        amount_usdc = None
    if completion_payload.get('currency', 'USDC') != 'USDC':
        amount_usdc = None

    quote = {key: value for key, value in {
        'requires_payment': True,
        'payment_scheme': exact_requirement and exact_requirement.get('scheme'),
        'chain': chain or (exact_requirement and exact_requirement.get('network')) or completion_payload.get('chain'),
        'currency': completion_payload.get('currency', 'USDC'),
        'amount_smallest_unit': amount_raw,
        'amount_usdc': amount_usdc,
        'terms_url': TERMS_URL,
        'license_url': LICENSE_URL,
        'execute_instruction': (
            'Run again with --execute to authorize this payment and accept the current '
            'SpawnXchange Terms and buyer license for this purchase.'
        ),
    }.items() if value is not None}
    if dry_run:
        return {'mode': 'quote', 'quote': quote}

    if not private_key:
        raise RuntimeError('private_key is required when dry_run is false')
    if exact_requirement is None:
        raise RuntimeError("buy failed: no supported x402 'exact' EOA requirement found")

    from eth_account import Account
    from x402 import x402ClientSync
    from x402.http.x402_http_client import x402HTTPClientSync
    from x402.mechanisms.evm.exact import register_exact_evm_client
    from x402.mechanisms.evm.signers import EthAccountSigner

    account = Account.from_key(private_key)
    signer = EthAccountSigner(account)
    xclient = x402ClientSync()
    register_exact_evm_client(xclient, signer)
    http_client = x402HTTPClientSync(xclient)
    payment_headers, _ = http_client.handle_402_response(dict(resp.headers), resp.content)

    retry_headers = {**headers, **payment_headers}
    retry = requests.post(
        f'{BASE_URL}/api/v1/buy',
        json=completion_payload,
        headers=retry_headers,
        timeout=60,
    )
    if retry.status_code != 200:
        raise RuntimeError(f'buy completion failed: {retry.status_code}')
    purchase = retry.json()
    try:
        download_resp = requests.head(purchase.get('download_url', ''), allow_redirects=True, timeout=30)
        delivery = {'verified': 200 <= download_resp.status_code < 400, 'status_code': download_resp.status_code}
    except requests.RequestException as exc:
        delivery = {'verified': False, 'reason': str(exc)}
    return {
        'mode': 'executed',
        'quote': quote,
        'purchase': purchase,
        'download_verification': delivery,
    }


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Buy a SpawnXchange item (authenticated flow).')
    parser.add_argument('--item-id', required=True, help='Item UUID to purchase')
    parser.add_argument('--chain', default=None, help='Preferred chain (e.g. base, polygon)')
    parser.add_argument('--execute', action='store_true',
                        help='Authorize payment and accept current SpawnXchange terms/license')
    parser.add_argument('--private-key-file', metavar='FILE',
                        help='Path to plain-text file containing the hex private key; required with --execute')
    parser.add_argument('--api-key-file', required=True, metavar='FILE',
                        help='Path to api-key.json written by register_agent.py')
    args = parser.parse_args()

    try:
        [REDACTED_SECRET])
        private_key = None
        if args.execute and not args.private_key_file:
            raise RuntimeError('--private-key-file is required with --execute')
        if args.execute:
            private_key = _load_wallet_key(args.private_key_file)
        data = buy_item(
            args.item_id,
            api_key,
            private_key=private_key,
            chain=args.chain,
            dry_run=not args.execute,
        )
    except RuntimeError as exc:
        raise SystemExit(str(exc)) from exc
    print(json.dumps(data, indent=2))


path: templates/purchase-record.json
content: {
  "purchased_at": "2026-05-09T00:00:00Z",
  "query": "semantic search phrase that led to the buy",
  "item_id": "00000000-0000-0000-0000-000000000000",
  "title": "Artifact title",
  "seller_username": "seller-handle",
  "chain": "base",
  "currency": "USDC",
  "amount_smallest_unit": "1000000",
  "payment_scheme": "exact",
  "order_id": "00000000-0000-0000-0000-000000000000",
  "local_cache_path": "/absolute/path/to/cache/order.zip",
  "artifact_sha256": "hex-checksum",
  "feedback_status": "not_submitted"
}


path: templates/requirements.txt
content: requests>=2.34.2,<3.0.0
eth-account>=0.13.7,<0.14.0
x402[evm]>=2.11.0,<3.0.0
web3>=7.16.0,<8.0.0

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)