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

# Privy

> Integrate Machines Partner API with Privy embedded wallets for server-managed signing.

<Info>
  Check out working demo: [Live Demo](https://privy-machines-cards-demo.vercel.app/)\
  Repo: [privy-machines-cards-example](https://github.com/machines-cash/privy-machines-cards-example)

  [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmachines-cash%2Fprivy-machines-cards-example)
</Info>

This guide is for partners already using Privy embedded wallets.

Scope:

* Partner API integration (`/partner/v1`) only.
* Server-side Privy wallet control for signing and sending transactions.
* Not a guide for Machines first-party web auth endpoints like `/v1/auth/email/exchange`.

## Architecture

* Your backend calls Machines Partner API.
* Your backend also calls Privy server APIs for embedded wallet signing/transaction execution.
* End users do not need wallet popups for embedded-wallet withdrawal execution paths.

```mermaid theme={null}
sequenceDiagram
  participant App as Partner App Backend
  participant Machines as Machines Partner API
  participant Privy as Privy Wallet API
  participant Chain as Blockchain

  App->>Machines: POST /partner/v1/users/resolve
  App->>Machines: POST /partner/v1/sessions
  App->>Machines: POST /partner/v1/withdrawals
  Machines-->>App: withdrawal + execution(callTarget, callPath)
  App->>Privy: sign typed data (v2 path only)
  App->>Privy: eth_sendTransaction (to execution.callTarget)
  Privy-->>App: transactionId/hash
  Chain-->>App: confirmed tx
```

## Prerequisites

* Privy app configured for your environment.
* Privy embedded Ethereum wallets enabled.
* Privy authorization key configured for server-side wallet access.
* Machines partner API key.
* Partner backend can securely store:
  * Machines partner key
  * Privy app secret
  * Privy authorization private key

## Privy Embedded Wallet Checklist (Server-Side)

Before sending live traffic, confirm:

1. Wallet control path
   * You can resolve each user’s Privy embedded Ethereum wallet.
   * Your backend can sign/send on that wallet using an authorization context (authorization key, user JWT, or a custom sign function).
2. Signer binding
   * The wallet is configured with a signer path your backend can use (for example an authorization-key signer).
   * The authorization private key is in a secrets manager, never in client code.
3. Policy attachment
   * Policy IDs for withdrawal execution are attached to the signer/wallet.
   * Rules cover `eth_sendTransaction` and `eth_signTypedData_v4`.
4. Sponsorship setup
   * Gas sponsorship is enabled in Privy Dashboard for the chains you support.
   * Your backend passes `sponsor: true` for sponsored sends.
5. Status monitoring
   * You have webhook or polling support for transaction status reconciliation.

## Recommended Data to Persist

Per user, persist at least:

* `machinesUserId` (your Machines partner user mapping)
* `privyUserId` (Privy user id)
* `embeddedWalletAddress` (and `walletId` if you store it)
* `linkedAt` / `lastUsedAt`

Per execution, persist:

* partner idempotency key
* Privy `transaction_id` / user operation hash (if returned)
* final onchain `transaction_hash`
* execution path (`controller_v1` or `coordinator_v2`)

## End-to-End Partner Flow

### 1) Resolve or link the user

Call `POST /partner/v1/users/resolve` from your backend.

```bash theme={null}
curl --request POST \
  --url https://api.machines.cash/partner/v1/users/resolve \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '{
    "userId": "partner-user-123",
    "walletAddress": "0xabc...",
    "walletLabel": "Privy Embedded Wallet"
  }'
```

### 2) Create a scoped partner session

Call `POST /partner/v1/sessions`.

Use the smallest scope set needed for the current operation.

```bash theme={null}
curl --request POST \
  --url https://api.machines.cash/partner/v1/sessions \
  --header 'Content-Type: application/json' \
  --header 'X-Partner-Key: <PARTNER_API_KEY>' \
  --data '{
    "userId": "partner-user-123",
    "walletAddress": "0xabc...",
    "scopes": ["deposits.read", "withdrawals.write"],
    "ttlSeconds": 900
  }'
```

### 3) Run core Machines flows

Use the partner session token for:

* KYC
* Agreements
* Cards
* Balances
* Deposits

This guide focuses on withdrawals because that is where embedded-wallet server signing is most relevant.

### 4) Withdrawal flow (partner + Privy execution)

Call sequence:

1. `GET /partner/v1/withdrawals/assets`
2. `POST /partner/v1/withdrawals/range`
3. `POST /partner/v1/withdrawals/estimate`
4. `POST /partner/v1/withdrawals` (include `adminAddress`)
5. Execute onchain via Privy wallet APIs

Create request example:

```bash theme={null}
curl --request POST \
  --url https://api.machines.cash/partner/v1/withdrawals \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <PARTNER_SESSION_TOKEN>' \
  --header 'Idempotency-Key: wdrl-001' \
  --data '{
    "amountCents": 2500,
    "source": {
      "contractId": "optional-uuid"
    },
    "destination": {
      "currency": "hbar",
      "network": "hbar",
      "address": "0.0.123456",
      "extraId": "optional-tag"
    },
    "adminAddress": "0xabc..."
  }'
```

Use the same `Idempotency-Key` when retrying `POST /partner/v1/withdrawals` after pending responses.

## Execution Model and Contract Call Paths

Machines withdrawal create response includes:

* `execution.callTarget`
* `execution.callPath` (`controller_v1` or `coordinator_v2`)
* `parameters` (7-arg Rain executor payload)

Always send the transaction to `execution.callTarget`.

* `controller_v1`:
  * Call 7-arg `withdrawAsset(...)`
  * Selector: `0xe167d26a`
  * Signature: `withdrawAsset(address,address,uint256,address,uint256,bytes32,bytes)`
* `coordinator_v2`:
  * Build admin typed-data signature first
  * Call 10-arg `withdrawAsset(...)`
  * Selector: `0x4b268241`
  * Signature: `withdrawAsset(address,address,uint256,address,uint256,bytes32,bytes,bytes32[],bytes[],bool)`

### v2 Typed Data Shape

```ts theme={null}
const domain = {
  name: "Collateral",
  version: "2",
  chainId,
  verifyingContract: collateralProxy,
  salt: adminSalt, // bytes32
};

const types = {
  Withdraw: [
    { name: "user", type: "address" },
    { name: "asset", type: "address" },
    { name: "amount", type: "uint256" },
    { name: "recipient", type: "address" },
    { name: "nonce", type: "uint256" },
  ],
};

const message = {
  user: adminAddress,
  asset: tokenAddress,
  amount,
  recipient,
  nonce: adminNonce,
};
```

### Partner-side Privy Execution (TypeScript)

```ts theme={null}
// 1) Create withdrawal payload at Machines
const wd = await fetch("https://api.machines.cash/partner/v1/withdrawals", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${partnerSessionToken}`,
    "Content-Type": "application/json",
    "Idempotency-Key": idempotencyKey,
  },
  body: JSON.stringify({
    amountCents,
    destination,
    adminAddress,
  }),
}).then((r) => r.json());

if (wd?.data?.status === "pending") {
  // retry same request with same idempotency key
}

const execution = wd?.data?.execution;
const parameters = wd?.data?.parameters;
if (!execution?.callTarget || !parameters || parameters.length < 7) {
  throw new Error("withdrawal payload not ready");
}
```

```ts theme={null}
// 2) v2 only: sign typed data with Privy
const adminSignature = await privy.walletApi.ethereum.signTypedData({
  walletId,
  chainType: "ethereum",
  typedData: {
    domain,
    types,
    message,
    primaryType: "Withdraw",
  },
});
```

```ts theme={null}
// 3) Send tx with Privy (v1 or v2 encoded calldata)
const send = await privy.walletApi.rpc({
  walletId,
  chainType: "ethereum",
  caip2: `eip155:${chainId}`,
  method: "eth_sendTransaction",
  sponsor: true,
  idempotencyKey,
  params: {
    transaction: {
      from: adminAddress,
      to: execution.callTarget,
      data: encodedCallData,
      value: "0x0",
    },
  },
});

const transactionId = send?.data?.transactionId;
let txHash = send?.data?.hash;
if (!txHash && transactionId) {
  // poll Privy getTransaction(transactionId) until hash is available
}
```

## Privy Method Allowances and Policy Design

For this flow, allow at minimum:

* `eth_sendTransaction`
* `eth_signTypedData_v4` (needed for `coordinator_v2`)

Use dynamic target address rules:

* Do not hardcode a single contract address.
* Restrict `to` to the `execution.callTarget` values returned by Machines for supported chains/contracts.

Recommended condition dimensions:

* `ethereum_transaction`:
  * `chain_id` in allowed chains
  * `to` in your dynamically maintained allowlist
  * `value == 0`
* `ethereum_calldata`:
  * ABI for Rain withdrawal functions
  * `function_name == "withdrawAsset"`
* `ethereum_typed_data_domain` and `ethereum_typed_data_message` (for v2):
  * domain name/version chain checks
  * typed message fields constrained to your expected flow

Policy modeling tip:

* Keep reusable condition sets per chain + function family.
* Compose those sets into authorization policies instead of duplicating large JSON blocks.

### Example Policy Shape (Ethereum)

This is a concrete starting shape aligned with Privy’s Ethereum policy examples:

```js theme={null}
{
  version: "1.0",
  name: "Machines withdrawal execution",
  chain_type: "ethereum",
  rules: [
    {
      name: "Allow withdrawAsset tx to approved call targets",
      method: "eth_sendTransaction",
      action: "ALLOW",
      conditions: [
        {
          field_source: "ethereum_transaction",
          field: "chain_id",
          operator: "in",
          value: ["8453", "84532"]
        },
        {
          field_source: "ethereum_transaction",
          field: "to",
          operator: "in_condition_set",
          value: "<CALL_TARGET_CONDITION_SET_ID>"
        },
        {
          field_source: "ethereum_transaction",
          field: "value",
          operator: "eq",
          value: "0x0"
        },
        {
          field_source: "ethereum_calldata",
          field: "function_name",
          abi: [
            {
              name: "withdrawAsset",
              type: "function",
              stateMutability: "nonpayable",
              inputs: [
                { name: "collateralProxy", type: "address" },
                { name: "token", type: "address" },
                { name: "amount", type: "uint256" },
                { name: "recipient", type: "address" },
                { name: "expiresAt", type: "uint256" },
                { name: "executorSalt", type: "bytes32" },
                { name: "executorSignature", type: "bytes" }
              ],
              outputs: []
            },
            {
              name: "withdrawAsset",
              type: "function",
              stateMutability: "nonpayable",
              inputs: [
                { name: "collateralProxy", type: "address" },
                { name: "token", type: "address" },
                { name: "amount", type: "uint256" },
                { name: "recipient", type: "address" },
                { name: "expiresAt", type: "uint256" },
                { name: "executorSalt", type: "bytes32" },
                { name: "executorSignature", type: "bytes" },
                { name: "adminSalt", type: "bytes32[]" },
                { name: "adminSignature", type: "bytes[]" },
                { name: "directTransfer", type: "bool" }
              ],
              outputs: []
            }
          ],
          operator: "eq",
          value: "withdrawAsset"
        }
      ]
    },
    {
      name: "Allow v2 domain-constrained typed data signing",
      method: "eth_signTypedData_v4",
      action: "ALLOW",
      conditions: [
        {
          field_source: "ethereum_typed_data_domain",
          field: "chainId",
          operator: "in",
          value: ["8453", "84532"]
        },
        {
          field_source: "ethereum_typed_data_domain",
          field: "verifyingContract",
          operator: "in_condition_set",
          value: "<COLLATERAL_PROXY_CONDITION_SET_ID>"
        },
        {
          field_source: "ethereum_typed_data_message",
          typed_data: {
            types: {
              Withdraw: [
                { name: "user", type: "address" },
                { name: "asset", type: "address" },
                { name: "amount", type: "uint256" },
                { name: "recipient", type: "address" },
                { name: "nonce", type: "uint256" }
              ]
            },
            primary_type: "Withdraw"
          },
          field: "amount",
          operator: "lte",
          value: "<HEX_MAX_ALLOWED_AMOUNT>"
        }
      ]
    }
  ]
}
```

Practical guidance:

* Use condition sets for `to` addresses and verifying contracts.
* Keep policy values as strings matching Privy examples (`chain_id`, hex values, address strings).
* Treat this policy as an allowlist; add explicit `DENY` rules only when needed.

## Gas Sponsorship and Transaction Lifecycle

* Use `sponsor: true` for embedded wallet sends to sponsor gas.
* For gas-sponsored EVM sends, backend responses can return before final onchain hash.
* Handle async status fields:
  * `transaction_id` (Privy transaction id)
  * `user_operation_hash`
  * `hash` may be empty until confirmation.
* Track final status via:
  * Privy webhooks (recommended)
  * or transaction-status API polling by `transaction_id`.
* Add operational monitoring:
  * submission failures
  * hash timeouts
  * onchain confirmation delays
  * spend anomalies and abuse patterns

## Known Constraints and Gotchas

* Production destination coverage (BTC/SOL/EVM/etc.) depends on live relay routes. Always query:
  * `GET /partner/v1/withdrawals/assets`
  * `POST /partner/v1/withdrawals/range`
  * `POST /partner/v1/withdrawals/estimate`
    before quoting or creating withdrawals.
* Sandbox source is fixed to rUSD on Base Sepolia.
* If withdrawal signature response is `status: pending`, retry with the same idempotency key.

## Further Reading (Privy)

* Authorization keys overview:
  * [https://docs.privy.io/controls/authorization-keys](https://docs.privy.io/controls/authorization-keys)
* Server-side signing with authorization keys:
  * [https://docs.privy.io/controls/authorization-keys/using-owners/sign/signing-on-the-server](https://docs.privy.io/controls/authorization-keys/using-owners/sign/signing-on-the-server)
* Ethereum policy examples:
  * [https://docs.privy.io/controls/policies/example-policies/ethereum#allow-specific-smart-contract-function-calls](https://docs.privy.io/controls/policies/example-policies/ethereum#allow-specific-smart-contract-function-calls)
* Gas sponsorship setup:
  * [https://docs.privy.io/wallets/gas-and-asset-management/gas/setup](https://docs.privy.io/wallets/gas-and-asset-management/gas/setup)
* Transaction handling:
  * [https://docs.privy.io/wallets/gas-and-asset-management/gas/transaction-handling](https://docs.privy.io/wallets/gas-and-asset-management/gas/transaction-handling)
* Sponsorship security guidance:
  * [https://docs.privy.io/wallets/gas-and-asset-management/gas/security](https://docs.privy.io/wallets/gas-and-asset-management/gas/security)
