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

# Upgrade and move USDC

> Activate the generation-2 vault with POST /api/me/vault/upgrade, then move free USDC from the generation-1 vault with one WithdrawERC20 signature.

## Upgrade the vault

```http theme={null}
POST /api/me/vault/upgrade
X-Api-Key: ps_live_<keyId>_<secret>   # needs `vault:migrate` or `vault:write` scope
Content-Type: application/json

{}
```

Idempotent. No wallet signature and no 2FA code. The upgrade moves no
money and cancels no orders, and it cannot be undone.

| Answer | Meaning | Next |
| - | - | - |
| `202` | Generation-2 vault deploy queued or running | Poll `GET /api/me/vault` until `upgrade.status` is `ready`, then call again |
| `200`, `generation: 2` | Generation-2 vault is active (also on every later call) | Done |
| `409 vault_upgrade_not_available` | Rollout not enabled for your wallet, or no vault yet | Wait for the rollout. If you have no vault yet, wait for [auto-deploy](https://docs.adipredictstreet.com/concepts/vaults/auto-deploy.md) or call `POST /api/me/vault` (never call `deployVault` yourself) |
| `409 vault_upgrade_blocked`, `reason: withdrawal_in_flight_unstamped` | A withdrawal requested before the release is still in progress | Retry after it ends |
| `409 vault_upgrade_blocked`, other `reason` | The vault cannot be activated | Contact support; do not loop |
| `409 vault_deploy_failed`, `503 vault_create_unavailable` | Transient | Retry after `retryAfterSec`, a few times |
| Any other `4xx`, e.g. `403 api_key_scope_missing` | Retrying will not help | Fix the cause |

```typescript theme={null}
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
// Retry only what the table marks as retryable; give up after a few attempts.
for (let attempt = 0; attempt < 10; attempt++) {
  const r = await fetch(`${BASE}/api/me/vault/upgrade`, { method: 'POST', headers: auth, body: '{}' });
  const body = await r.json();
  if (r.status === 200 && Number(body.generation) === 2) break;               // active
  const code = body?.error?.code;
  const retryable = r.status === 202 || r.status >= 500 || code === 'vault_deploy_failed';
  if (!retryable) throw new Error(`upgrade refused: ${r.status} ${code}`);
  await sleep((Number(body?.error?.retryAfterSec) || 5) * 1000);             // after 202: deploy still running
}
```

From activation on, deposits (`depositERC20`, relay, on-ramp) go to the
generation-2 vault. Read `vaultAddress` from `GET /api/me/vault` before
each deposit.

## Move USDC from the generation-1 vault

Moves free USDC into the generation-2 vault without leaving the
platform: no deposit-limit usage, no bonus or referral trigger, no
deposit or withdrawal notification, no 2FA code. One move can be open
per wallet.

**1. Create the move**

```http theme={null}
POST /api/me/vault/upgrade/transfer
X-Api-Key: ps_live_<keyId>_<secret>   # needs `vault:migrate` or `vault:write` scope

{ "amount": "125.5" }
```

Omit `amount` to move all free USDC (`usdcMove.freeUsdc` in
`GET /api/me/vault`; `null` for a key without `portfolio:read`). `201` is a new move; `200` returns your open
move, which may be for another amount or already signed. The answer
carries `transfer.signRequest`, a `WithdrawERC20` typed-data request
on the generation-1 vault:

```json theme={null}
{
  "domain": { "name": "PredictStreetVault", "version": "1", "chainId": 36900, "verifyingContract": "0x…gen1" },
  "types": { "WithdrawERC20": [ … ] },
  "primaryType": "WithdrawERC20",
  "message": { "token": "0x…usdc", "to": "0x…relayer", "amount": "125500000", "salt": "…", "deadline": 1792… }
}
```

`to` is the platform relayer, `amount` is in 6-decimal units.
`signRequest` is `null` once the move is signed.

> [!NOTE]
>
> **JIT exposure warning (`warnings`):** If moving funds leaves collateral-exempt (JIT) market-maker quotes unfunded on the generation-1 vault, both `POST …/transfer` and `POST …/signature` include `warnings: [{ code: 'jit_quotes_unfunded_after_move', vaultAddress, unfundedNotional }]`. The move is not blocked, but warned so market makers can adjust resting quotes.

**2. Check and sign it**

Check that `verifyingContract` is your generation-1 vault, `token` is
`usdc` from `GET /api/platform/contracts`, and `amount` matches the
move. Sign with your wallet and send the signature:

```http theme={null}
POST /api/me/vault/upgrade/transfer/{id}/signature

{ "userSig": "0x…" }
```

`202`. Sending the same signature again returns the same answer.

**3. Follow it**

Poll `GET /api/me/vault/upgrade/transfer/{id}`, or watch
`vault_move_updated` on `user_activity`, until `deposit_confirmed`,
`refunded`, `failed` or `needs_ops`.

```typescript theme={null}
const r = await fetch(`${BASE}/api/me/vault/upgrade/transfer`, {
  method: 'POST', headers: auth, body: JSON.stringify(amount ? { amount } : {}),
});
const { transfer: t } = await r.json();
if (r.status === 200 && amount && parseUnits(t.amount, 6) !== parseUnits(amount, 6)) {
  throw new Error(`open move ${t.id} is for ${t.amount}`);   // wait for it to end, then create a new one
}
const sr = t.signRequest;
if (sr) {
  if (sr.domain.verifyingContract.toLowerCase() !== gen1Vault.toLowerCase()) throw new Error('not my generation-1 vault');
  if (sr.message.token.toLowerCase() !== contracts.usdc.toLowerCase()) throw new Error('not USDC');
  if (BigInt(sr.message.amount) !== parseUnits(t.amount, 6)) throw new Error('amount mismatch');
  const userSig = await eoa.signTypedData(
    { ...sr.domain, chainId: Number(sr.domain.chainId) },
    { WithdrawERC20: sr.types.WithdrawERC20 },
    sr.message,
  );
  await fetch(`${BASE}/api/me/vault/upgrade/transfer/${t.id}/signature`, {
    method: 'POST', headers: auth, body: JSON.stringify({ userSig }),
  });
}
// poll GET /api/me/vault/upgrade/transfer/{id} until a terminal status
```

After a crash, call the create again: with a move open it returns that
move (`200`), never a second one. If its `signRequest` is `null`, do
not sign again; poll it.

### Statuses

| Status | Meaning |
| - | - |
| `requested` → `withdraw_signed` → `withdraw_confirmed` → `deposit_submitted` | In progress |
| `deposit_confirmed` | Done |
| `refunded` | The deposit leg could not complete after retries; the USDC is back on the generation-1 vault |
| `failed` | The withdraw leg did not happen; nothing moved. Create a new move |
| `needs_ops` | Held by the relayer for manual resolution; contact support |

### Limits and timing

* Sign before `signRequest.message.deadline` (1 hour after creation by
  default), else `409 vault_move_expired`; create a new move.
* Each leg waits for a finalized block, so a move takes about two
  finality windows plus a few seconds. The deposit leg is retried up to
  3 times before the refund.
* Only `available` USDC moves. USDC locked by open orders or pending
  operations stays (`409 insufficient_free_balance`). Cancel the
  generation-1 orders you no longer want first.
* While the move is open, the amount is locked on the generation-1
  vault.
* Keep enough USDC on the generation-1 vault for the legacy quotes you
  keep.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.