> ## 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.

# Openfort

> Integrate Machines Partner API with Openfort embedded wallets.

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

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

This guide is for partners already using Openfort embedded wallets.

Scope:

* Partner API integration (`/partner/v1`) only.
* Openfort for auth + embedded wallet UX.
* Your backend holds partner credentials and calls Machines.
* Simple EVM-first flow for create card, deposits, and withdrawals.

## Architecture

* Frontend uses Openfort for sign-in and embedded wallet creation.
* Frontend calls your backend only.
* Your backend calls Machines Partner API.
* Your backend executes withdrawal transactions with the signer path you control.

```mermaid theme={null}
sequenceDiagram
  participant User as User
  participant Frontend as Partner App (Frontend)
  participant Backend as Partner App (Backend)
  participant Openfort as Openfort
  participant Machines as Machines Partner API
  participant Chain as Blockchain

  User->>Frontend: Sign in with email
  Frontend->>Openfort: Create embedded wallet
  Frontend->>Backend: POST /api/machines/session
  Backend->>Machines: POST /partner/v1/users/resolve
  Backend->>Machines: POST /partner/v1/sessions
  Backend-->>Frontend: partner session token
  Frontend->>Backend: KYC/cards/deposits/withdrawals requests
  Backend->>Machines: Partner API calls
  Backend->>Chain: send withdrawal tx when needed
```

## Prerequisites

* Openfort project with:
  * publishable key for frontend
  * secret key for backend routes that call Openfort server APIs
  * embedded wallet (Shield) key configured
  * policy id configured for sponsored transactions
* Machines partner API key
* A backend service that can securely store:
  * `X-Partner-Key`
  * Openfort server credentials

## Openfort Embedded Wallet Checklist

Before sending traffic:

1. Wallet creation
   * New users get an embedded EVM wallet at login.
   * You can read the wallet address after auth.
2. Stable mapping
   * You persist partner `userId` + Openfort user id + wallet address.
   * You always send the same mapped user identity to Machines.
3. Session bootstrap server-side
   * `POST /partner/v1/users/resolve` and `POST /partner/v1/sessions` happen on your backend.
   * `X-Partner-Key` is never exposed to browser code.
4. Browser request path
   * Browser calls your backend routes, not Machines directly.
   * This avoids CORS failures and secret leakage.
5. Gas sponsorship
   * Openfort policy id is configured and attached for EVM sends.
   * Use sponsored send options for wallet execution paths where required.

## Recommended Data to Persist

Per user:

* `machinesUserId`
* `openfortUserId`
* `embeddedWalletAddress`
* `linkedAt` / `lastUsedAt`

Per session:

* `sessionId`
* `scopes`
* `expiresAt`

Per withdrawal execution:

* idempotency key
* execution path (`controller_v1` or `coordinator_v2`)
* submitted tx hash
* confirmed tx hash

## End-to-End Partner Flow

### 1) Set up Openfort providers (frontend)

```tsx theme={null}
"use client";

import { AuthProvider, OpenfortProvider } from "@openfort/react";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <OpenfortProvider
      publishableKey={process.env.NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY!}
      walletConfig={{
        shieldPublishableKey:
          process.env.NEXT_PUBLIC_OPENFORT_SHIELD_PUBLISHABLE_KEY!,
        ethereumProviderPolicyId:
          process.env.NEXT_PUBLIC_OPENFORT_POLICY_ID,
      }}
      uiConfig={{ authProviders: [AuthProvider.EMAIL_OTP] }}
    >
      {children}
    </OpenfortProvider>
  );
}
```

### 2) Resolve user + create partner session (backend)

```ts theme={null}
import { NextRequest, NextResponse } from "next/server";

const MACHINES_BASE_URL =
  process.env.MACHINES_PARTNER_BASE_URL ||
  "https://dev-api.machines.cash/partner/v1";

export async function POST(req: NextRequest) {
  const { externalUserId, walletAddress } = await req.json();

  const resolveRes = await fetch(`${MACHINES_BASE_URL}/users/resolve`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Partner-Key": process.env.MACHINES_PARTNER_API_KEY!,
    },
    body: JSON.stringify({
      userId: externalUserId,
      wallet: { chain: "evm", address: walletAddress },
      walletLabel: "Openfort Embedded Wallet",
    }),
  });

  if (!resolveRes.ok) {
    return NextResponse.json({ ok: false, error: "resolve_failed" }, { status: 502 });
  }

  const sessionRes = await fetch(`${MACHINES_BASE_URL}/sessions`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Partner-Key": process.env.MACHINES_PARTNER_API_KEY!,
    },
    body: JSON.stringify({
      userId: externalUserId,
      wallet: { chain: "evm", address: walletAddress },
      scopes: [
        "kyc.read",
        "kyc.write",
        "agreements.read",
        "agreements.write",
        "cards.read",
        "cards.write",
        "deposits.read",
        "deposits.write",
        "withdrawals.read",
        "withdrawals.write",
        "transactions.read",
      ],
      ttlSeconds: 900,
    }),
  });

  const payload = await sessionRes.json();
  if (!sessionRes.ok || !payload?.ok) {
    return NextResponse.json({ ok: false, error: "session_failed", payload }, { status: 502 });
  }

  return NextResponse.json({
    ok: true,
    sessionToken: payload.data.sessionToken,
    sessionId: payload.data.sessionId,
    expiresAt: payload.data.expiresAt,
  });
}
```

### 3) Call Machines through your backend proxy

Use the partner session token from step 2.

At minimum, support these routes:

* `GET /partner/v1/kyc/values`
* `POST /partner/v1/kyc/applications`
* `GET /partner/v1/kyc/status`
* `GET /partner/v1/agreements`
* `POST /partner/v1/agreements`
* `GET /partner/v1/cards`
* `POST /partner/v1/cards`
* `GET /partner/v1/deposits/assets`
* `POST /partner/v1/deposits/range`
* `POST /partner/v1/deposits/estimate`
* `POST /partner/v1/deposits`
* `GET /partner/v1/transactions`

### 4) Create withdrawals

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`

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..."
  }'
```

### 5) Execute withdrawal transaction

Use response fields:

* `execution.callTarget`
* `execution.callPath`
* `parameters`

Rules:

* send tx to `execution.callTarget`
* sender must match `adminAddress` used in create request
* if status is `pending`, retry create with the same idempotency key

## Execution Model and Contract Call Paths

Machines withdrawal create response includes:

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

Send to `execution.callTarget` using the signer you control.

* `controller_v1`:
  * call `withdrawAsset(address,address,uint256,address,uint256,bytes32,bytes)`
* `coordinator_v2`:
  * build admin typed-data signature
  * call 10-arg `withdrawAsset(...)` with admin signature arrays

## Environment Variables

Frontend:

* `NEXT_PUBLIC_OPENFORT_PUBLISHABLE_KEY`
* `NEXT_PUBLIC_OPENFORT_SHIELD_PUBLISHABLE_KEY`
* `NEXT_PUBLIC_OPENFORT_POLICY_ID`
* `NEXT_PUBLIC_OPENFORT_DEFAULT_CHAIN`

Backend:

* `MACHINES_PARTNER_BASE_URL`
* `MACHINES_PARTNER_API_KEY`
* `MACHINES_PARTNER_DEFAULT_SCOPES`
* `MACHINES_PARTNER_EXTERNAL_USER_PREFIX`
* Openfort server credentials for your execution routes

## API Routes Used

User/session:

* `POST /partner/v1/users/resolve`
* `POST /partner/v1/sessions`

KYC:

* `GET /partner/v1/kyc/values`
* `POST /partner/v1/kyc/applications`
* `GET /partner/v1/kyc/status`

Agreements:

* `GET /partner/v1/agreements`
* `POST /partner/v1/agreements`

Cards:

* `GET /partner/v1/cards`
* `POST /partner/v1/cards`
* `POST /partner/v1/cards/secrets/session`
* `POST /partner/v1/cards/{cardId}/secrets`

Deposits:

* `GET /partner/v1/deposits/assets`
* `POST /partner/v1/deposits/range`
* `POST /partner/v1/deposits/estimate`
* `POST /partner/v1/deposits`

Withdrawals:

* `GET /partner/v1/withdrawals/assets`
* `POST /partner/v1/withdrawals/range`
* `POST /partner/v1/withdrawals/estimate`
* `POST /partner/v1/withdrawals`

Transactions:

* `GET /partner/v1/transactions`
* `GET /partner/v1/transactions/{transactionId}`

## Notes

* Keep all partner calls server-side.
* Keep idempotency keys stable on retries.
* Keep one wallet mapping per user to avoid identity drift.
