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

# WithdrawERC20 EIP-712

> Typed-data struct for user signature on withdrawal requests.

## Domain

```typescript theme={null}
const domain = {
  name: 'PredictStreetVault',
  version: '1',
  chainId: 99999,
  verifyingContract: '<YOUR VAULT ADDRESS>',
};
```

<Warning>
  `verifyingContract` is your **vault clone**, not the factory. Fetch
  via `GET /api/me` → `vaultAddress`, or on-chain via
  `VaultFactory.vaultOf(eoa)`.
</Warning>

## Struct

```solidity theme={null}
struct WithdrawERC20 {
    address token;
    address to;
    uint256 amount;
    uint256 salt;
    uint256 deadline;
}
```

`salt` is **not** a sequential nonce — it's a one-time uniqueness
field you choose. The vault tracks `usedDigests[digest]`.

## Signing example

<CodeGroup>
  ```typescript TypeScript theme={null}
  const types = {
    WithdrawERC20: [
      { name: 'token', type: 'address' },
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'salt', type: 'uint256' },
      { name: 'deadline', type: 'uint256' },
    ],
  };

  const message = {
    token: USDC_ADDRESS,
    to: destinationWallet,
    amount: BigInt('100000000'),
    salt: BigInt(`0x${randomBytes(32).toString('hex')}`),
    deadline: BigInt(Math.floor(Date.now() / 1000) + 86400),
  };

  const signature = await wallet.signTypedData(domain, types, message);
  ```

  ```python Python theme={null}
  from eth_account import Account
  import secrets, time

  domain = {
      "name": "PredictStreetVault",
      "version": "1",
      "chainId": 99999,
      "verifyingContract": vault_address,
  }
  types = {
      "EIP712Domain": [
          {"name": "name", "type": "string"},
          {"name": "version", "type": "string"},
          {"name": "chainId", "type": "uint256"},
          {"name": "verifyingContract", "type": "address"},
      ],
      "WithdrawERC20": [
          {"name": "token", "type": "address"},
          {"name": "to", "type": "address"},
          {"name": "amount", "type": "uint256"},
          {"name": "salt", "type": "uint256"},
          {"name": "deadline", "type": "uint256"},
      ],
  }
  message = {
      "token": usdc_address,
      "to": destination,
      "amount": 100_000_000,
      "salt": int.from_bytes(secrets.token_bytes(32), "big"),
      "deadline": int(time.time()) + 86400,
  }
  signed = Account.sign_typed_data(
      private_key,
      {"domain": domain, "primaryType": "WithdrawERC20", "types": types, "message": message},
  )
  signature = signed.signature.hex()
  ```
</CodeGroup>

## Request

```http theme={null}
POST /api/withdrawals/request
# Withdrawal endpoints aren't exposed on the partner API today.
Content-Type: application/json

{
  "amount": "100",
  "destination": "0x...",
  "salt": "<decimal-uint256>",
  "expiry": 1729511712,
  "userSig": "0x..."
}
```

The wallet address is resolved from the API key's `associatedWallet`
(or, on partner-API access, the `X-User-Wallet` header) — clients do
not pass `userWallet` in the body. Note the field names: the user
signature is `userSig` (not `userSignature`) and the EIP-712 deadline
is wired through as `expiry` (not `deadline`); the on-chain
`WithdrawERC20` struct still names it `deadline`, but the API surface
renamed for backward-compat with the legacy withdraw types.

Response mirrors the current state after the request. Subscribe to
`withdrawals.me` WebSocket channel for transitions.
