Skill: spawnxchange/spawnxchange-direct-buying (version 0.1.3)

--- name: spawnxchange-direct-buying description: Use when completing public SpawnXchange direct purchases through /api/v1/items/{uuid}/acquire, verifying artifact delivery, and maintaining buyer state via the included references. version: 0.1.3 author: SpawnXchange license: MIT tags: [spawnxchange, direct-buying, marketplace, x402, purchase] related_skills: [spawnxchange-registration, spawnxchange-selling, spawnxchange-buying] schema_version: 1 source: raw_url: https://raw.githubusercontent.com/avlk/spawnxchange-skills/main/skills/spawnxchange-direct-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-direct-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-direct-buying/SKILL.md openclaw: homepage: https://github.com/avlk/spawnxchange-skills claude_code: homepage: https://github.com/avlk/spawnxchange-skills codex: {} copilot: {} --- # SpawnXchange Direct Buying ## When to Use Use this skill to: - search public SpawnXchange listings - buy without a pre-existing SpawnXchange account - handle the `/api/v1/items/{uuid}/acquire` x402 flow - verify delivery and keep buyer state consistent for future reuse If you already have a SpawnXchange identity and API key and want the authenticated buy route, use `spawnxchange-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 authorize real wallet-backed USDC purchases when the executable example is run with `--execute`. Required capabilities: - network access to `https://spawnxchange.com` for search, purchase prompts, completion, and policy links - network access required by the x402 client and EVM settlement libraries while producing the payment proof - local read access to the configured plaintext private-key file when `--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 wallet. Keep private keys, payment headers, signed download URLs, purchase records, and cached artifacts out of git, logs, chat transcripts, and shared folders. ## Direct purchase route Use `POST /api/v1/items/{uuid}/acquire`. Prompt request: - no auth header required - send `{}` as the default prompt body - optional advanced hint: send only `{ "chain": "base" | "polygon" }` if you need to pin the purchase chain up front - do not send prompt-time `currency`, `policy_accepted`, or `license_accepted` Completion request: - retry the same route with `PAYMENT-SIGNATURE` - use the server-published completion example from the `PAYMENT-REQUIRED` header extensions instead of hard-coding the payload shape - include `policy_accepted: true` and `license_accepted: true` only when intentionally completing the purchase - successful responses return `{ order_id, download_url, expires_in, buyer_account }` ## 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` - `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. ## Implementation pattern Recommended pattern: - perform `POST /api/v1/items/{uuid}/acquire` yourself with `requests` and 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` and are intentionally executing the purchase, feed the response headers/body into the x402 client library - read the server-published completion example from the `PAYMENT-REQUIRED` header extensions - reuse the generated `PAYMENT-SIGNATURE` header on the retry request ## Executable example See `scripts/acquire_item.py` for the public direct-purchase reference flow. Default mode is quote-only. It does not read a private key, sign, pay, or accept terms: `python scripts/acquire_item.py --item-id <uuid> --chain base` 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/acquire_item.py --item-id <uuid> --chain base --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. 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 403 and 402 as the same problem.** - `403 self_purchase_forbidden` is the wrong actor pairing; `402` is the correct paid flow. 2. **Sending prompt-time `currency` or legal fields to `/api/v1/items/{uuid}/acquire`.** - The public acquire prompt is intentionally minimal; only `chain` remains as an advanced hint. 3. **Ignoring the server-published completion example.** - Read the `PAYMENT-REQUIRED` header extensions instead of duplicating the request shape in multiple places. 4. **Not maintaining local purchase state.** - This leads to duplicate buys. 5. **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 direct-buying skill.

Purchase records and cached artifacts can reveal what the agent bought, when it bought it, which seller it paid, and where proprietary downloads live. 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.

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.

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

Operational rule:
- before buying, search your own purchase ledger first to see whether an equivalent artifact is already owned and cached.
- before executing a new purchase, compare the current quote against your budget, expected chain, expected currency, and any matching purchase record.

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/acquire_item.py
content: #!/usr/bin/env python3
# Reference example for agent authors.
# This script demonstrates the public SpawnXchange acquire 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
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

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 acquire_item(item_id: str, private_key: str | None = None, chain: str | None = None,
                 dry_run: bool = True) -> dict:
    """Quote or execute the public SpawnXchange acquire flow and return plain data.

    When dry_run is true, this only retrieves and summarizes the x402 quote.
    When dry_run is false, private_key must control an EOA that holds sufficient
    USDC. Smart-contract wallets require the 'exact-evm-userop' mechanism and a
    UserOperation bundler, which is outside the scope of this script.
    """
    url = f'{BASE_URL}/api/v1/items/{item_id}/acquire'

    prompt_payload = {'chain': chain} if chain else {}
    prompt_resp = requests.post(url, json=prompt_payload, timeout=60)
    if prompt_resp.status_code == 200:
        purchase = prompt_resp.json()
        return {'mode': 'completed_without_payment_prompt', 'purchase': purchase}
    if prompt_resp.status_code != 402:
        raise RuntimeError(f'acquire failed: {prompt_resp.status_code}')

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

    prompt_meta = json.loads(base64.b64decode(encoded).decode('utf-8'))
    completion_payload = {
        **prompt_meta['extensions']['bazaar']['info']['input']['completion_request']['example'],
        'policy_accepted': True,
        'license_accepted': True,
        **({'chain': chain} if chain else {}),
    }
    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 and (exact_requirement.get('maxAmountRequired') or exact_requirement.get('amount'))
    amount_usdc = None
    if amount_raw is not None and completion_payload.get('currency', 'USDC') == 'USDC':
        try:
            amount_usdc = format((Decimal(str(amount_raw)) / Decimal('1000000')).normalize(), 'f')
        except InvalidOperation:
            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("acquire failed: no supported x402 'exact' EOA requirement found")

    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(prompt_resp.headers), prompt_resp.content)

    retry = requests.post(url, json=completion_payload, headers=payment_headers, timeout=60)
    if retry.status_code != 200:
        raise RuntimeError(f'acquire completion failed: {retry.status_code}')
    purchase = retry.json()
    download_url = purchase.get('download_url', '')
    try:
        download_resp = requests.head(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='Acquire a SpawnXchange item (public flow).')
    parser.add_argument('--item-id', required=True, help='Item UUID to acquire')
    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')
    args = parser.parse_args()

    try:
        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 = acquire_item(
            args.item_id,
            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)