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
161 changes: 161 additions & 0 deletions .github/workflows/atlas-tier1.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
name: Atlas DRC Tier 1 contract

on:
workflow_dispatch:
pull_request:
paths:
- "tunnel/**"
- "bridge/common/**"
- "bridge/boston_dynamics/atlas_drc_bridge/**"
- "registry/vendors/boston-dynamics/atlas/**"
- ".github/workflows/atlas-tier1.yml"
push:
branches: [boston-dynamics-atlas-tier-1]
paths:
- "tunnel/**"
- "bridge/common/**"
- "bridge/boston_dynamics/atlas_drc_bridge/**"
- "registry/vendors/boston-dynamics/atlas/**"
- ".github/workflows/atlas-tier1.yml"

jobs:
tunnel-and-contract:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- name: Build and test the real Go Tunnel
run: |
make build
make test
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install Atlas bridge dependencies
run: |
python -m pip install -r bridge/boston_dynamics/atlas_drc_bridge/requirements.txt
python bridge/boston_dynamics/atlas_drc_bridge/download_atlas_model.py
- name: Run fail-closed contracts
env:
PYTHONPATH: ${{ github.workspace }}/bridge/boston_dynamics/atlas_drc_bridge
TUNNEL_BIN: ${{ github.workspace }}/bin/tunnel
LD_LIBRARY_PATH: ${{ github.workspace }}/.zenoh-c/lib
run: |
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_contract.py
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_registry_contract.py
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_bridge_contract.py
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_payment_gate.py
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_e2e_paid_action.py
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_x402_no_settlement.py

atlas-mujoco:
name: Atlas DRC MuJoCo state-feedback proof
runs-on: ubuntu-latest
needs: tunnel-and-contract
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install dependencies and the pinned legacy Atlas source
run: |
python -m pip install --upgrade pip
python -m pip install -r bridge/boston_dynamics/atlas_drc_bridge/requirements.txt
python bridge/boston_dynamics/atlas_drc_bridge/download_atlas_model.py
- name: Run actual MuJoCo wave
env:
PYTHONPATH: ${{ github.workspace }}/bridge/boston_dynamics/atlas_drc_bridge
run: |
python bridge/boston_dynamics/atlas_drc_bridge/tests/test_mujoco_runtime.py
python bridge/boston_dynamics/atlas_drc_bridge/run_paid_wave.py --json-output bridge/boston_dynamics/atlas_drc_bridge/artifacts/mujoco_result.json
- name: Upload MuJoCo evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: atlas-drc-mujoco-evidence
path: bridge/boston_dynamics/atlas_drc_bridge/artifacts/mujoco_result.json
retention-days: 90
if-no-files-found: error

atlas-sim2sim:
name: Atlas DRC Sim-to-Sim (MuJoCo + Webots)
runs-on: ubuntu-latest
needs: tunnel-and-contract
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install simulators and dependencies
run: |
sudo apt-get update
sudo apt-get install -y wget xvfb libgl1 libglu1-mesa libglib2.0-0 libgomp1 libfontconfig1 libxkbcommon0 libdbus-1-3 libx11-6 libx11-xcb1 libxcb1 libxext6 libxrender1 libxtst6 libxi6 libxss1 libqt5core5a libqt5gui5 libqt5widgets5 libqt5network5 libqt5svg5 libqt5webengine5 libqt5webenginecore5 libqt5webenginewidgets5 libqt5multimedia5 libqt5printsupport5 libqt5concurrent5 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0
sudo apt-get install -y libsndio7.0 2>/dev/null || sudo apt-get install -y libsndio-dev 2>/dev/null || true
python -m pip install --upgrade pip
python -m pip install -r bridge/boston_dynamics/atlas_drc_bridge/requirements.txt
python bridge/boston_dynamics/atlas_drc_bridge/download_atlas_model.py
wget -q https://github.com/cyberbotics/webots/releases/download/R2025a/webots-R2025a-x86-64.tar.bz2
sudo tar xjf webots-R2025a-x86-64.tar.bz2 -C /usr/local
rm webots-R2025a-x86-64.tar.bz2
- name: Run the same bounded policy in both simulators
env:
PYTHONPATH: ${{ github.workspace }}/bridge/boston_dynamics/atlas_drc_bridge
WEBOTS_EXE: /usr/local/webots/webots
QT_QPA_PLATFORM: offscreen
run: xvfb-run -a -s "-screen 0 1280x1024x24" python bridge/boston_dynamics/atlas_drc_bridge/run_sim2sim_validation.py --timeout 90
- name: Upload Sim-to-Sim evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: atlas-drc-sim2sim-evidence
path: bridge/boston_dynamics/atlas_drc_bridge/artifacts/
retention-days: 90
if-no-files-found: error

atlas-base-sepolia-e2e:
name: Atlas DRC Base Sepolia settlement evidence
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
concurrency:
group: atlas-drc-base-sepolia-${{ github.ref }}
cancel-in-progress: false
runs-on: ubuntu-latest
needs: [tunnel-and-contract, atlas-mujoco, atlas-sim2sim]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.25"
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install dependencies, legacy model, and real Tunnel
run: |
python -m pip install --upgrade pip
python -m pip install -r bridge/boston_dynamics/atlas_drc_bridge/requirements.txt
python bridge/boston_dynamics/atlas_drc_bridge/download_atlas_model.py
make build
- name: Start local Zenoh router
run: |
wget -q https://github.com/eclipse-zenoh/zenoh/releases/download/1.9.0/zenoh-1.9.0-x86_64-unknown-linux-gnu-standalone.zip
unzip -q zenoh-1.9.0-x86_64-unknown-linux-gnu-standalone.zip -d .zenoh-router
.zenoh-router/zenohd > zenohd.log 2>&1 &
sleep 2
grep -q "zenohd" zenohd.log
- name: Run live Base Sepolia proof and generate evidence
env:
PRIVATE_KEY: ${{ secrets.BASE_SEPOLIA_PRIVATE_KEY }}
ROBO_PAYEE_ADDRESS: ${{ secrets.ROBO_PAYEE_ADDRESS }}
TUNNEL_BIN: ${{ github.workspace }}/bin/tunnel
LD_LIBRARY_PATH: ${{ github.workspace }}/.zenoh-c/lib
PROXY_WS_URL: wss://api.fabric.foundation/api/core/ws/robot
run: python bridge/boston_dynamics/atlas_drc_bridge/test_base_sepolia_tunnel_e2e.py
- name: Upload generated Base Sepolia evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: atlas-drc-base-sepolia-evidence
path: bridge/boston_dynamics/atlas_drc_bridge/artifacts/base_sepolia_result_*.json
retention-days: 90
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go.work
go.work.sum

.env
robopay_idempotency.json

.idea/
.vscode/
Expand Down
112 changes: 103 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ The robot-side `tunnel` receives the action request, runs x402 middleware, verif

![RoboPay action flow](docs/images/flow.png)

## Tier 1 simulator profile: Boston Dynamics Atlas DRC (legacy)

This branch adds a **simulator-only** Tier 1 profile for the legacy DARPA-era
Atlas DRC/v4 model. It uses the same payment-gated Tunnel and Zenoh security
boundary as the Reachy Mini and Spot profiles, a bounded measured-state
right-arm wave policy in MuJoCo, and an independently supplied Webots R2025a
Atlas cross-check. It deliberately does **not** claim to model Boston
Dynamics' current electric Atlas product.

Boston Dynamics publishes the electric product's high-level specification
(56 degrees of freedom and continuous joint range), but no public electric
Atlas URDF/USD or joint-level kinematic schema is available in its developer
documentation or NVIDIA's public Isaac Sim 5.1 robot-asset catalog. The pinned
DRC/v4 URDF has 30 movable one-degree-of-freedom joints, so this branch makes
no claim that its joint names, axes, limits, dynamics, or controller transfer
to the electric robot.

Start with the [Atlas DRC bridge README](bridge/boston_dynamics/atlas_drc_bridge/README.md)
and the [robot profile](registry/vendors/boston-dynamics/atlas/boston-dynamics.atlas-drc.mujoco-webots-wave.v1/robot.profile.yaml).

## Repository layout

```
Expand Down Expand Up @@ -62,17 +82,63 @@ Package names are `isaac_sim_bridge_g1`, `isaac_sim_bridge_go2`, and `isaac_sim_

The tunnel (`tunnel/`) keeps an outbound WebSocket to the Fabric proxy, verifies x402 micropayments, and publishes accepted actions to the same Zenoh topic the bridge listens on.

Set the payee address (and any overrides) in `tunnel/config.json`:
`tunnel/config.json` is deliberately an inert checked-in example. Set the
stable robot identity and payee in an untracked `tunnel/.env` (or a deployment
secret manager) before starting the tunnel:

```json
{
"robot_id": "my-robot",
"evm_payee_address": "0xYourAddress",
"price": "$0.002",
"price": "0.001",
"network": "eip155:84532"
}
```

| Field | Required | Default | Description |
|--------------------------|---------------|-----------------|------------------------------------------------------------|
| `robot_id` | **Yes** | — | Stable robot identifier; generated IDs are rejected |
| `evm_payee_address` | **Yes** | — | Non-zero EVM address to receive x402 payments |
| `price` | No | `0.001` | Price per action, in whole token units |
| `network` | No | `eip155:84532` | CAIP-2 network ID (Base Sepolia in the checked-in example) |
| `token_address` | No | network default | ERC-20 the price is charged in |
| `token_name` | For `eip3009` | — | Token's `name()`, forms the EIP-712 domain the payer signs |
| `token_version` | No | `1` | Token version used in the EIP-712 domain |
| `token_decimals` | No | `6` | Token decimals, used to convert `price` to atomic units |
| `token_transfer_method` | No | `eip3009` | `eip3009` or `permit2` — how the payment settles |
| `token_supports_eip2612` | No | `false` | `permit2` only: payer signs a permit instead of approving |

`price` is a decimal amount in whole units of the payment token, converted to atomic units using
`token_decimals` — with `token_decimals: 18`, `"1"` charges `1000000000000000000`. A leading `$`
is optional and carries no meaning; it only reads as dollars when the token is a stablecoin.

### Custom payment token

For well-known chains x402 already knows which stablecoin to use (USDC on Base, and so on), so
`token_address` can be omitted. On any other chain there is no default and requests fail with
`no default stablecoin configured for network <network>` — set `token_address` to register the
token as that network's default asset at startup. The checked-in Base Sepolia
template in [`tunnel/config.example.json`](tunnel/config.example.json) is
intentionally inert (zero payee); copy it to an untracked deployment config
and replace the robot ID and payee before starting the Tunnel.

`token_transfer_method` decides how the facilitator moves the tokens:

- **`eip3009`** (default) — the payer signs a `TransferWithAuthorization` message and the
facilitator calls `transferWithAuthorization` on the token. **Only works if the token actually
implements EIP-3009** (USDC and friends). Against a plain ERC-20 the signature is produced
happily and settlement then reverts. `token_name`/`token_version` must match the token's own
EIP-712 domain (its `name()`, not its symbol) or the signature will not verify.
- **`permit2`** — the payer signs a Permit2 witness and the facilitator settles through the x402
exact Permit2 proxy. Works with **any** plain ERC-20, at the cost of a one-time
`approve(0x000000000022D473030F116dDEE9F6B43aC78BA3, …)` from each payer. The signed domain is
Permit2's own, so `token_name`/`token_version` are neither required nor advertised. Set
`token_supports_eip2612: true` only if the token has `permit()`, which lets the payer skip the
approval transaction.

The facilitator has to support the chosen method too — it is the one that submits the settlement
transaction.

Build and run from the repo root (the `Makefile` operates inside `tunnel/`):

```bash
Expand All @@ -89,11 +155,36 @@ Common environment overrides:
| `FACILITATOR_URL` | `https://x402.org/facilitator` | x402 payment facilitator endpoint |
| `GIN_MODE` | `release` | `debug` for verbose HTTP logs |

### Fail-closed paid action contract

Every deployment supplies a robot-scoped skill catalog and an explicit
allowlist. The tunnel refuses all action requests until both are configured:

```bash
ROBOT_ID=my-robot
ROBO_PAYEE_ADDRESS=0xYourAddress
SKILL_CATALOG_PATH=../registry/vendors/<vendor>/<model>/<profile>/skill-catalog.json
ALLOWED_ACTIONS=registered_skill,stop
```

`POST /action` accepts only a registered skill whose parameters satisfy that
catalog. It returns `202 Accepted` with an `action_id` and `status_url`; poll
`GET /action/<action_id>/status` for the terminal result. The request is
durably idempotent (including across restart), and x402 settlement is deferred
until a simulator result matches the exact `action_id`, `robot_id`, `skill_id`,
parameter hash, and idempotency key. Simulator failure, timeout, or a
correlation mismatch never settles a payment.

The current shared Fabric Tunnel/proxy protocol identifies a robot by its
configured ID but does **not yet** supply a signed robot-to-payee handshake.
That binding is an upstream protocol dependency. Robot profiles must not
invent a local EIP-signature handshake; they document the limitation and only
receive Tunnel-verified action events.

## 4. Register the robot on BitAgent (Unibase AIP) — optional

With `AIP_ENABLED=true`, the tunnel additionally registers the robot as an
A2A-compatible agent on the BitAgent network (Unibase AIP), so any AIP client
or agent can discover and call it. The integration is built on the
A2A-compatible discovery agent on the BitAgent network (Unibase AIP). The integration is built on the
[Unibase AIP Go SDK](https://github.com/unibaseio/aip-go-sdk) — see
`tunnel/internal/aipagent/agent.go`, which wraps the robot in a single
`wrappers.ExposeAsA2A(...)` call.
Expand All @@ -102,7 +193,7 @@ How AIP traffic flows:

```
AIP client → AIP gateway (/robots/<robot_id>/…) → Fabric proxy (ws) → tunnel
AIP handler → Zenoh topic robot/tunnel/action → bridge → /cmd_vel
discovery metadata / rejected direct action
```

The tunnel serves the A2A contract endpoints (`/.well-known/agent-card.json`,
Expand All @@ -120,7 +211,7 @@ cp tunnel/.env.example tunnel/.env

| Variable | Required | Description |
|----------------------|----------|----------------------------------------------------------|
| `AIP_ENABLED` | yes | Set `true` to enable BitAgent/AIP registration |
| `AIP_ENABLED` | no | Set `true` to enable BitAgent/AIP discovery registration |
| `CHAIN` | no | Chain preset: `bsc-testnet`, `bsc-mainnet`, `base-sepolia` or `base-mainnet` — sets both the x402 payment network and the AIP registration chain |
| `UNIBASE_PROXY_AUTH` | no* | Bearer token — your account is resolved from it (falls back to `PRIVY_TOKEN`) |
| `AIP_USER_ID` | no* | Token-less fallback: wallet address to register under |
Expand Down Expand Up @@ -152,6 +243,9 @@ registering robot as AIP agent robot_id=<id> endpoint_url=…/robots/<id>
ws connected to proxy robot_id=<id>
```

Actions received via AIP are published to the same Zenoh topic
(`robot/tunnel/action`) as paid x402 actions, so the bridge and robot-side
safety logic are identical for both paths.
Direct actions received through AIP are intentionally rejected and never
published to Zenoh: an AIP job input does not currently carry the
Tunnel-verified x402 payment context, exact correlation tuple, or durable
replay reservation. Use the paid Tunnel action endpoint for execution. AIP
execution can be enabled only when the shared gateway supplies that verified
envelope through the same contract.
5 changes: 5 additions & 0 deletions bridge/boston_dynamics/atlas_drc_bridge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
models/atlas_v4/
artifacts/
scenes/*_result.json
__pycache__/
*.pyc
22 changes: 22 additions & 0 deletions bridge/boston_dynamics/atlas_drc_bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Atlas DRC/v4 MuJoCo + Webots bridge

This bridge implements the simulator-only Tier 1 profile
`boston-dynamics.atlas-drc.mujoco-webots-wave.v1`. It deliberately targets the
public DARPA-era hydraulic Atlas DRC/v4 model, not Boston Dynamics' current
electric Atlas product.

The complete setup, action contract, payment safety, evidence procedure and
troubleshooting guide are documented in the profile
[README](../../../registry/vendors/boston-dynamics/atlas/boston-dynamics.atlas-drc.mujoco-webots-wave.v1/docs/README.md).

Quick local model checks:

```bash
python download_atlas_model.py
python run_paid_wave.py
python run_sim2sim_validation.py
```

On Windows, use `run_live_base_sepolia_visual.ps1` for the current-commit paid
recording flow. It pauses before payment and never stores or prints the payer
private key.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Payment-gated Boston Dynamics Atlas DRC legacy simulation bridge."""

from .contracts import ATLAS_ROBOT_ID, PROFILE_ID, WAVE_SKILL_ID
from .runtime import run_wave_episode

__all__ = ["ATLAS_ROBOT_ID", "PROFILE_ID", "WAVE_SKILL_ID", "run_wave_episode"]
Loading
Loading