Deploy Agent (x402)
The Acurast Deploy Agent lets developers deploy computational jobs to the Acurast decentralized processor network directly from EVM chains (starting with USDC on Base). Via the deploy agent's HTTP API a simple x402 payment together with either a job specification or a VPS request will trigger the deployment for you.
This approach is ideal for:
- AI agents using agentic wallets to deploy Acurast jobs from LLM clients
- Backend services that need to programmatically deploy Acurast jobs without interacting with the Acurast parachain or managing ACU accounts
- Anyone who wants a public SSH-reachable Ubuntu VPS on a decentralized processor — see Deploy a VPS below
Both approaches are outlined below.
Supported Chains
| Chain | Chain ID | Token |
|---|---|---|
| Base | 8453 | USDC |
Use via LLM
npx skills add coinbase/agentic-wallet-skills
Recommended skills are at least:
◆ Select skills to install (space to toggle)
│ ◼ authenticate-wallet (Sign in to the wallet. Use when you or the user want to l...)
│ ◼ fund (Add money to the wallet. Use when you or the user want to...)
│ ◻ monetize-service
│ ◼ pay-for-service (Make a paid API request to an x402 endpoint with automati...)
│ ◻ query-onchain-data
│ ◼ search-for-service (Search and browse the x402 bazaar marketplace for paid AP...)
│ ◻ send-usdc
│ ◻ trade
│ ◼ x402 (Search for new services and make paid API requests using ...)
Choose your LLM client and whether to install globally. For example:
│ ○ Augment (.augment/skills)
│ ○ IBM Bob (.bob/skills)
│ ❯ ● Claude Code (.claude/skills)
│ ○ OpenClaw (skills)
│ ○ CodeBuddy (.codebuddy/skills)
│ ○ Command Code (.commandcode/skills)
│ ○ Continue (.continue/skills)
│ ○ Cortex Code (.cortex/skills)
◆ Installation scope
│ ○ Project
│ ● Global (Install in home directory (available across all projects))
◆ Installation method
│ ● Symlink (Recommended) (Single source of truth, easy updates)
│ ○ Copy to all agents
And proceed with installation.
Read more about skills.
After restarting your LLM client, you can set up your wallet and fund it from within your LLM client using
/fund
Then, deploy a job via
/pay-for-service deploy.acu.run
Hitting invalid_payload: contract call failed: execution reverted? See Troubleshooting.
A typical flow to deploy a job on Acurast
❯ /pay-for-service
⏺ I'll check the wallet status first to make sure it's authenticated and ready for paid requests.
⏺ Bash(npx [email protected] status)
⎿ Wallet Server
✓ Running (PID: 33552)
… +4 lines (ctrl+o to expand)
⏺ The wallet is authenticated and ready.
To make a paid API request, I need to know which endpoint you want to call. Do you have a specific x402 service URL you'd like to use?
If not, I can help you search the bazaar marketplace for available paid services using the /search-for-service skill. What kind of service or API are you looking for?
❯ /pay-for-service deploy.acu.run
⏺ I see you want to use the deploy.acu.run service. Let me first check what endpoints and API documentation are available for this service.
⏺ Bash(npx [email protected] x402 pay https://deploy.acu.run --json)
⎿ {
"status": 200,
"statusText": "OK",
… +5 lines (ctrl+o to expand)
⏺ The root endpoint doesn't require payment. Let me check if there's documentation or an OpenAPI spec available.
⏺ Fetch(https://deploy.acu.run)
⎿ Received 4 bytes (200 OK)
⏺ Let me check if there's documentation in the current repository about this deploy agent service.
⏺ Read 1 file (ctrl+o to expand)
⏺ I found the documentation. The Acurast Deploy Agent at deploy.acu.run is an x402 service for deploying jobs to the Acurast decentralized cloud.
Service Details
┌──────────┬────────────────────────────────────────────┐
│ Field │ Value │
├──────────┼────────────────────────────────────────────┤
│ Endpoint │ https://deploy.acu.run/deploy │
├──────────┼────────────────────────────────────────────┤
│ Method │ POST │
├──────────┼────────────────────────────────────────────┤
│ Network │ Base (chain 8453) │
├──────────┼────────────────────────────────────────────┤
│ Payment │ USDC (dynamic pricing based on job reward) │
└──────────┴────────────────────────────────────────────┘
Required Parameters
┌────────┬────────┬────────────────────────────────────────────────────────┐
│ Field │ Type │ Description │
├────────┼────────┼────────────────────────────────────────────────────────┤
│ script │ string │ IPFS hash of the script (e.g., ipfs://Qm...) │
├────────┼────────┼────────────────────────────────────────────────────────┤
│ reward │ number │ Reward per execution in picoACU (1 ACU = 10¹² picoACU) │
└────────┴────────┴────────────────────────────────────────────────────────┘
Optional Parameters
- schedule - Job schedule (start/end times, interval, duration)
- slots - Number of processor slots (default: 1)
- runtime - NodeJS, NodeJSWithBundle, or Shell
- assignmentStrategy - Single or Competing
- allowOnlyVerifiedSources - Only verified processors (default: true)
---
Would you like me to deploy a job? If so, please provide:
1. IPFS script hash (e.g., ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2)
2. Reward amount in ACU (e.g., 0.0771 ACU)
❯ deploy ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2 with 0.0771 ACU reward
⏺ I'll deploy that script with 0.0771 ACU reward (77,100,000,000 picoACU).
⏺ Bash(npx [email protected] x402 pay https://deploy.acu.run/deploy -X POST -d '{"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2", "reward": 77100000000}'
--…)
⎿ {
"status": 200,
"statusText": "OK",
… +22 lines (ctrl+o to expand)
⏺ Deployment successful!
┌────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Field │ Value │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Job Hash │ 0xe8c81b116fb4412571c1edcd8764e35db4a96507b599c86baf51746c55aecc25 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Explorer │ https://hub.acurast.com/explorer/extrinsic/0xe8c81b116fb4412571c1edcd8764e35db4a96507b599c86baf51746c55aecc25 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Payment TX │ 0x88053... on Base (chain 8453) │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Payer │ 0xceA0d23336f285f9d85d6b0c3DE7103eAEB168F9 │
└────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Your job has been deployed to the Acurast decentralized cloud with a reward of 0.0771 ACU per execution.
Programmatically deploy
This approach uses Coinbase's awal tool directly, instead of via the LLM. This is a deterministic approach for dApps and backends.
Set up a Coinbase Agentic Wallet
Fund your agentic wallet, then validate with:
npx awal balanceExample output:
Base
────────────────────────
USDC $20.21
ETH 0.0008
WETH 0.0008You need both USDC (for the deploy) and a little ETH on Base (for gas) — see Troubleshooting if a payment fails with insufficient funds.
3. OPTIONAL: Query accepted tokens and pricing before deploying
You can query the deploy agent to see accepted tokens and the job's price before making a payment:
curl "https://deploy.acu.run/deploy" -X POST -H "Content-Type: application/json" -i -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"allowedSources": null,
"allowOnlyVerifiedSources": true,
"schedule": {
"startTime": 1777593600000,
"endTime": 1777651200000,
"duration": 3600000,
"interval": 3600001,
"maxStartDelay": 10000
},
"memory": 0,
"networkRequests": 0,
"storage": 0,
"requiredModules": [],
"assignmentStrategy": "Single",
"slots": 1,
"reward": 77136800000,
"minReputation": 0,
"runtime": "NodeJS"
}' | sed -n 's/^payment-required: //Ip' | tr -d '\r' | base64 -d | jq
Example response:
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "http://deploy.acu.run/deploy",
"description": "Deploy Acurast job",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "79000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x9b442C40DAaC631F74de4094F2998E4aC016098A",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USD Coin",
"version": "2"
}
}
],
"extensions": ...
}
Deploy using the agentic wallet
See First App Deployment to learn how to upload your script and receive an IPFS URL using the Acurast CLI.
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"reward": 77136800000
}'The agent handles deployment to the Acurast network once an x402 facilitator confirms the payment was successful.
Example response:
Response:
{
"success": true,
"paymentTxHash": "x402-1776088198704",
"payer": "unknown",
"chainId": 8453,
"acurastJobHash": "0xfe16aa9be55907284cf7eafaabfc9b41e6d7caa19a55d7b518146551783a1b83",
"explorerLink": "https://hub.acurast.com/explorer/extrinsic/0xfe16aa9be55907284cf7eafaabfc9b41e6d7caa19a55d7b518146551783a1b83",
"message": "Job deployed successfully"
}The deploy agent has deployed the job under
5H3ChM1QgrYyGwzmyQ1QkdFCJxzakjWsbQN5tSGP8dkn2VCR.
Deploy a VPS (SSH tunnel)
Instead of a job spec, send { "vps": { "sshKey": "..." } }. The deploy agent
generates a P-256 tunnel keypair, precomputes the <hash>.acu.run subdomain,
submits a Shell-runtime job to Acurast, and returns the shell connect command
before the processor has even started. Once a processor picks the job up
(~1-3 min), it installs Ubuntu + dropbear inside a proot sandbox, opens the
Acurast reverse tunnel targeting dropbear, and Let's Encrypt issues a real
certificate for the precomputed subdomain.
The domain is knowable off-chain because the tunnel's clientId is
hex(sha256(compressed_pubkey)[0:8]) and the agent controls the keypair — it
injects the private key into the job as an encrypted env var after match.
Minimal deploy
sshKey, reward, and maxExecutionTimeInMs are the three required fields.
reward is per-execution in picoACU and maxExecutionTimeInMs bounds how long
the VPS runs — price the reward for that window (see VPS parameters).
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d "{
\"vps\": {
\"sshKey\": \"$(cat ~/.ssh/id_rsa.pub)\",
\"reward\": 48686320000,
\"maxExecutionTimeInMs\": 7200000
}
}"
Example response:
{
"success": true,
"acurastJobHash": "0x...",
"domain": "https://<hash>.acu.run",
"probeUrl": "https://deploy.acu.run/vps/probe?domain=<hash>.acu.run",
"sshCommand": "ssh -o \"ProxyCommand=openssl s_client -quiet -servername %h -connect %h:443\" root@<hash>.acu.run",
"explorerLink": "https://hub.acurast.com/explorer/extrinsic/0x..."
}
Connect
The tunnel wraps traffic in TLS (that's what gives the Let's Encrypt cert on
<hash>.acu.run), so SSH speaks through an openssl s_client ProxyCommand
that terminates the outer TLS at the relay. Use -i + IdentitiesOnly=yes to
force the exact key you submitted — otherwise a client with multiple keys in
~/.ssh/ may present a non-matching one first and hit
Permission denied (publickey) before offering the right one.
One-liner:
ssh -o "ProxyCommand=openssl s_client -quiet -servername %h -connect %h:443" \
-i ~/.ssh/id_rsa -o IdentitiesOnly=yes \
root@<hash>.acu.run
Ergonomic version — add this once to ~/.ssh/config:
Host *.acu.run
ProxyCommand openssl s_client -quiet -servername %h -connect %h:443
User root
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
StrictHostKeyChecking accept-new
Then forever after just:
ssh <hash>.acu.run
The tunnel bundle runs dropbear with -s -g (no password auth advertised).
If SSH_AUTHORIZED_KEY fails to reach the processor for any reason the job
aborts rather than exposing a password-authenticated shell.
Poll for readiness
The deploy response returns immediately, but the processor still needs
~2-5 min to install Ubuntu, dropbear, and open the reverse tunnel. Poll
GET /vps/probe?domain=<returned-domain> until ready: true:
curl -s "https://deploy.acu.run/vps/probe?domain=<hash>.acu.run" | jq
{
"domain": "<hash>.acu.run",
"ready": true,
"banner": "SSH-2.0-dropbear_2025.88",
"cert": {
"subject": "<hash>.acu.run",
"issuer": "YE2",
"notBefore": "...",
"notAfter": "..."
}
}
ready: false responses carry an error field (timeout, unrecognized
banner, connection closed before banner, TLS handshake failures) — retry
on an interval, don't hot-loop.
Expose an HTTP service on the same subdomain
Add httpPort to the request; the bundle then installs
sslh in front of dropbear so the same
<hash>.acu.run domain serves both shell and HTTP simultaneously. Byte-0
protocol sniffing routes shell traffic to dropbear and HTTP requests to your
app on 127.0.0.1:<httpPort>.
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d "{
\"vps\": {
\"sshKey\": \"$(cat ~/.ssh/id_rsa.pub)\",
\"reward\": 48686320000,
\"maxExecutionTimeInMs\": 7200000,
\"httpPort\": 8080
}
}"
Response echoes it back as httpPort: 8080. Inside the VPS, bind your service
on 127.0.0.1:8080 (or 0.0.0.0:8080). Browsers hitting
https://<hash>.acu.run/ then get your app; the existing ProxyCommand
recipe keeps working for shell access.
Tradeoffs:
- Small (~200 ms) connect-time delay on new shell connections while sslh
waits its
--on-timeoutfallback (imperceptible for interactive use). - One HTTP backend per subdomain. For multiple services, run nginx / caddy inside the VPS and reverse-proxy from there.
- Adds ~5-10 s to first-boot on the processor for
apt-get install sslh.
VPS parameters
| Field | Type | Description |
|---|---|---|
sshKey | string | REQUIRED OpenSSH public key line (e.g., ssh-ed25519 AAAA... user@host), installed into dropbear's /root/.ssh/authorized_keys. |
reward | number | REQUIRED Reward per execution in picoACU (1 ACU = 10¹² picoACU). No default — price it for maxExecutionTimeInMs (e.g. 48686320000 ≈ 0.049 ACU is priced for a 2 h window; scale roughly linearly for longer/shorter windows or processors will skip the job). |
maxExecutionTimeInMs | number | REQUIRED How long the VPS runs before the processor tears it down, in ms (setup time counts). No default. Longer windows should scale reward proportionally. |
httpPort | number | Enable HTTP multiplexing on the same subdomain via sslh. Must be an integer in 1024..65535. Bind your app on 127.0.0.1:<httpPort> inside the VPS. |
image | string | Image preset. Currently only "ubuntu" (default) — Ubuntu 25.04 aarch64 proot-distro. |
network | string | Target network: "mainnet" (default) or "canary". |
callbackUrl | string | Optional webhook receiving log / started / error JSON events from the tunnel bundle. Useful for observing boot progress or catching install failures. |
minMemory | number | Minimum total processor RAM in bytes (matched against the v1_ram_total benchmark pool). |
minStorage | number | Minimum available processor storage in bytes (matched against v1_storage_avail). |
minCpuScore | number | Minimum single-core CPU benchmark score (Geekbench-style; matched against v1_cpu_single_core). |
minCpuMultiScore | number | Minimum multi-core CPU benchmark score (matched against v1_cpu_multi_core). |
minProcessorReputation | number | Minimum processor reputation on a 0..1_000_000 scale. Default 0 (no reputation filter). |
startAt | object | When the execution window opens — relative { "msFromNow": 180000 } or absolute { "timestamp": <unix-ms> }. Default { "msFromNow": 180000 } (3 min). Must leave ≥ 2 min lead time for a processor to match. |
maxStartDelayMs | number | Maximum tolerated start-delay in ms. Default 30000 (30 s). Widen for slower processors that may not be ready in time. |
Full job specification
The easiest way to craft a job specification as JSON is using the Acurast CLI with the extra argument --only-upload to just output the IPFS URL:
acurast deploy --only-upload
You can provide a complete job specification for more control:
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"allowedSources": null,
"allowOnlyVerifiedSources": true,
"schedule": {
"startTime": 1777593600000,
"endTime": 1777651200000,
"duration": 3600000,
"interval": 3600001,
"maxStartDelay": 10000
},
"memory": 0,
"networkRequests": 0,
"storage": 0,
"requiredModules": [],
"assignmentStrategy": "Single",
"slots": 1,
"reward": 77136800000,
"minReputation": 0,
"runtime": "NodeJS"
}'
The jobSpec object follows the standard Acurast job registration format:
| Field | Type | Description |
|---|---|---|
script | string | REQUIRED IPFS URL of an already-pinned script (ipfs://Qm...). The agent does not upload scripts — pin first (e.g. acurast deploy --only-upload) and pass the URL. Non-ipfs:// values are rejected with a 400. |
allowedSources | string[] | null | Processor whitelist (null = any) |
allowOnlyVerifiedSources | boolean | Require attested processors |
schedule.startTime | number | Job start time (Unix ms) |
schedule.endTime | number | Job end time (Unix ms) |
schedule.duration | number | Execution duration (ms) |
schedule.interval | number | Execution interval (ms) |
schedule.maxStartDelay | number | Max acceptable start delay (ms) |
memory | number | Max memory bytes (0 = default) |
networkRequests | number | Max network requests (0 = default) |
storage | number | Max storage bytes (0 = default) |
requiredModules | number[] | Required modules (e.g., LLM) |
assignmentStrategy | string | "Single" or "Competing" |
slots | number | Number of processor slots |
reward | number | REQUIRED Reward per execution (picoACU) |
minReputation | number | Min processor reputation (0-1000000) |
runtime | string | "NodeJS", "NodeJSWithBundle", or "Shell" |
Troubleshooting
invalid_payload: contract call failed: execution reverted
Symptom: A deploy payment fails with
invalid_payload: contract call failed: execution reverted. See
coinbase/payments-mcp#30.
Workaround: For now, creating a new wallet account under a different email address might resolves the issue.
insufficient funds even though USDC balance is enough
Payments settle on Base, so the wallet needs a small amount of base ETH to
cover gas — having only USDC is not enough. Per
Coinbase, keep a little
ETH on Base in the wallet alongside your USDC. Check with npx awal balance and
top up ETH if it reads 0.