> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privy.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create and attach an automation

> Create a deposit-based swap automation and enable it for a wallet.

This guide creates an automation that converts USDC deposits on any chain into pathUSD on Tempo. The app first creates a reusable automation definition, then attaches it to a source wallet.

<Warning>
  Run these examples from a trusted server. Never expose the Privy app secret in client-side code.
</Warning>

## Prerequisites

The integration requires:

* A Privy app ID and app secret.
* [Swaps enabled](/wallets/actions/swap/setup) for the Privy app.
* A source [Privy wallet](/wallets/overview). Imported or previously exported wallets cannot receive automation attachments.
* An authorization signature when the source wallet has an [owner](/controls/authorization-keys/owners/overview).

## 1. Create the automation

Create an app-scoped automation with `POST /v1/wallet_automations`.

See [configure triggers and actions](/wallets/automations/configuration) for supported triggers, filters, and actions.

The source asset alias below does not specify a chain. It matches supported USDC deployments in Privy's asset registry. The destination includes a chain because every swap must have one destination.

The required `owner_id` controls changes to the shared automation definition. Set it to `null` for an app-managed definition. Set it to a [key quorum](/controls/key-quorum/overview) ID to require that quorum's authorization for updates and deletion.

<Tabs>
  <Tab title="cURL">
    ```bash theme={"system"}
    curl --request POST 'https://api.privy.io/v1/wallet_automations' \
      --user "$PRIVY_APP_ID:$PRIVY_APP_SECRET" \
      --header "privy-app-id: $PRIVY_APP_ID" \
      --header 'content-type: application/json' \
      --data '{
        "name": "Normalize deposits to Tempo PathUSD",
        "owner_id": null,
        "config": {
          "trigger": {
            "type": "deposit",
            "assets": {
              "mode": "include",
              "values": [
                {"asset": "usdc"}
              ]
            }
          },
          "action": {
            "type": "swap",
            "destination_chain_asset": {
              "asset": "pathusd",
              "chain": "tempo"
            }
          }
        }
      }'
    ```
  </Tab>

  <Tab title="Node SDK">
    ```ts {skip-check} theme={"system"}
    import {PrivyClient} from '@privy-io/node';

    const privy = new PrivyClient({
      appId: process.env.PRIVY_APP_ID!,
      appSecret: process.env.PRIVY_APP_SECRET!
    });

    const automation = await privy.walletAutomations().create({
      name: 'Normalize deposits to Tempo PathUSD',
      owner_id: null,
      config: {
        trigger: {
          type: 'deposit',
          assets: {
            mode: 'include',
            values: [{asset: 'usdc'}]
          }
        },
        action: {
          type: 'swap',
          destination_chain_asset: {asset: 'pathusd', chain: 'tempo'}
        }
      }
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"system"}
    import os
    import requests

    app_id = os.environ["PRIVY_APP_ID"]
    app_secret = os.environ["PRIVY_APP_SECRET"]

    response = requests.post(
        "https://api.privy.io/v1/wallet_automations",
        auth=(app_id, app_secret),
        headers={"privy-app-id": app_id},
        json={
            "name": "Normalize deposits to Tempo PathUSD",
            "owner_id": None,
            "config": {
                "trigger": {
                    "type": "deposit",
                    "assets": {
                        "mode": "include",
                        "values": [{"asset": "usdc"}],
                    },
                },
                "action": {
                    "type": "swap",
                    "destination_chain_asset": {"asset": "pathusd", "chain": "tempo"},
                },
            },
        },
    )
    response.raise_for_status()
    automation = response.json()
    ```
  </Tab>
</Tabs>

Save the returned `id`. New automation definitions are enabled by default. API responses contain canonical `asset_address` and `caip2` values for the chain-specific aliases in this example.

See the [create automation API reference](/api-reference/wallet-automations/create) for the complete schema.

## 2. Attach it to a wallet

An attachment enables the automation for one wallet. Only enabled automation definitions can be attached. In the swap example, the params indicate that its `destination_address` receives the output of every generated swap.

The destination address must be valid for the action's destination chain. Attaching the same automation again updates its parameters without changing its matching priority.

<Tabs>
  <Tab title="cURL">
    ```bash theme={"system"}
    curl --request POST \
      "https://api.privy.io/v1/wallets/$WALLET_ID/automations/attach" \
      --user "$PRIVY_APP_ID:$PRIVY_APP_SECRET" \
      --header "privy-app-id: $PRIVY_APP_ID" \
      --header 'content-type: application/json' \
      --header "privy-authorization-signature: $PRIVY_AUTHORIZATION_SIGNATURE" \
      --data "{
        \"automation_ids\": [\"$AUTOMATION_ID\"],
        \"params\": {
          \"destination_address\": \"$DESTINATION_WALLET_ADDRESS\"
        }
      }"
    ```
  </Tab>

  <Tab title="Node SDK">
    ```ts {skip-check} theme={"system"}
    const {data: attachments} = await privy.wallets().attachAutomations(process.env.WALLET_ID!, {
      automation_ids: [automation.id],
      params: {
        destination_address: process.env.DESTINATION_WALLET_ADDRESS!
      },
      authorization_context: {
        signatures: [process.env.PRIVY_AUTHORIZATION_SIGNATURE!]
      }
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"system"}
    import os
    import requests

    app_id = os.environ["PRIVY_APP_ID"]

    response = requests.post(
        f"https://api.privy.io/v1/wallets/{os.environ['WALLET_ID']}/automations/attach",
        auth=(app_id, os.environ["PRIVY_APP_SECRET"]),
        headers={
            "privy-app-id": app_id,
            "privy-authorization-signature": os.environ[
                "PRIVY_AUTHORIZATION_SIGNATURE"
            ],
        },
        json={
            "automation_ids": [automation["id"]],
            "params": {
                "destination_address": os.environ["DESTINATION_WALLET_ADDRESS"]
            },
        },
    )
    response.raise_for_status()
    attachments = response.json()["data"]
    ```
  </Tab>
</Tabs>

The authorization signature must cover this exact request. Use Privy's [authorization-signature utilities](/controls/authorization-keys/using-owners/sign/utility-functions) to produce it. An ownerless wallet does not require a wallet-owner signature.

<Info>
  Attaching an automation does not make it a general-purpose wallet signer. Privy authorizes each
  execution against the attachment's trigger, action, wallet, and destination.
</Info>

## 3. Send a matching deposit

Send USDC to the source wallet on any chain. Privy detects the incoming transfer and evaluates enabled attachments from oldest to newest. Only the first matching automation creates a wallet action. Privy does not fall through to another matching automation if that action fails.

Privy reads the current asset balance when the execution runs. **Earlier funds held by the wallet can therefore be included in the swap.**

Attaching an automation does not process a balance already held by the wallet. Send a new matching deposit or [reindex the asset](/wallets/automations/lifecycle#recover-a-missed-or-failed-deposit) to move that balance.

## 4. Observe the execution

Subscribe to [`wallet_automation.submitted`](/wallets/automations/webhooks) to receive both the automation execution ID and the generated wallet action ID.

The app can also list executions for the source wallet:

<Tabs>
  <Tab title="cURL">
    ```bash theme={"system"}
    curl --request GET "https://api.privy.io/v1/wallet_automations/executions?wallet_id=$WALLET_ID" \
      --user "$PRIVY_APP_ID:$PRIVY_APP_SECRET" \
      --header "privy-app-id: $PRIVY_APP_ID"
    ```
  </Tab>

  <Tab title="Node SDK">
    ```ts {skip-check} theme={"system"}
    const executions = [];

    for await (const execution of privy.walletAutomations().listExecutions({
      wallet_id: process.env.WALLET_ID!
    })) {
      executions.push(execution);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"system"}
    import os
    import requests

    app_id = os.environ["PRIVY_APP_ID"]
    params = {"wallet_id": os.environ["WALLET_ID"], "limit": 100}
    executions = []

    while True:
        response = requests.get(
            "https://api.privy.io/v1/wallet_automations/executions",
            auth=(app_id, os.environ["PRIVY_APP_SECRET"]),
            headers={"privy-app-id": app_id},
            params=params,
        )
        response.raise_for_status()
        page = response.json()
        executions.extend(page["data"])

        if not page["next_cursor"]:
            break
        params["cursor"] = page["next_cursor"]
    ```
  </Tab>
</Tabs>

Use the execution's `wallet_action_id` to track the generated wallet action through the [wallet action lifecycle](/wallets/actions/lifecycle).

## Handle errors

Setup requests can fail when:

* The app does not have swaps enabled.
* An asset alias, destination chain, or automation owner is invalid.
* The wallet-owner authorization signature is missing or does not cover the exact attach request.
* The destination address is invalid for the configured destination chain.

An automation can still fail when it executes. Common causes include unavailable routes, insufficient liquidity or gas, policy rejection, and changed app configuration. Inspect `failure_reason` and the generated wallet action when one exists. After correcting a pre-action failure, [reindex the asset](/wallets/automations/lifecycle#recover-a-missed-or-failed-deposit).
