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

# Two-Stage Enrollment

> Create a portfolio with a two-stage, user-signed enrollment flow.

Enrollment has two stages. Stage 1 returns a message for the user to sign.
Stage 2 sends the signature and creates the portfolio. You must keep all
round-trip fields **byte-for-byte unchanged** between the stages.

* Scope: `enroll:write` on both stages.
* Idempotency anchor: Stage 1 returns `flowId`. It is valid for 24 hours.
* Result: Stage 2 creates one smart account for each requested chain.
* Atomicity: Stage 2 creates all requested accounts or no accounts.

The following flow describes the EVM path. Solana enrollment uses the same
two stages and the same `flowId`. The Solana signature depends on the owner
model. See
[Solana (SVM) enrollment](#solana-svm-enrollment).

## Sequence

```
Integrator          Glider API                 End-user Wallet
    |                    |                           |
    |--1. POST /v2/enroll/signature---------------->|
    |                    |                           |
    |<--200 { message, flowId, accountIndex, agentAccountId }
    |                    |                           |
    |--2. signMessage(message.raw)----------------->|
    |                    |                           |
    |<--signature--------|---------------------------|
    |                    |                           |
    |--3. POST /v2/enroll (echo all round-trip fields + signature)
    |                    |                           |
    |<--201 { portfolioId, strategyId, smartAccounts }|
```

## Stage 1: Request the signable message

`POST /v2/enroll/signature`

Glider verifies that the strategy belongs to your tenant. It reads the next
available account index. Then, it returns the session-key message and the
assigned agent wallet.

```bash theme={null}
curl -X POST https://api.glider.fi/v2/enroll/signature \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "eip155:0:0xabcdef0000000000000000000000000000000001",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1, 8453, 56],
    "accountType": "ECDSA"
  }'
```

Response:

```json theme={null}
{
  "success": true,
  "data": {
    "message": {
      "kind": "ecdsa",
      "raw": "0xdeadbeef...deadbeef"
    },
    "agentAccountId": "eip155:0:0x1111111111111111111111111111111111111111",
    "accountIndex": "7",
    "accountType": "ECDSA",
    "flowId": "flow_01JWZEE2MF30KVRMRX53N88VA4"
  }
}
```

### Peek, not reserve

Stage 1 reads `accountIndex` but does not reserve it. Repeated calls for the
same user return the same candidate index. The index changes only after stage
2 succeeds. Abandoned flows do not consume indices.

### Unsupported chain ids

Each deployment has a configured set of chains. An unknown or unconfigured
chain returns `400`. The `details` field identifies the rejected value. Ask
Glider which chains are enabled. Do not use a fixed global chain list.

```json theme={null}
{
  "success": false,
  "error": {
    "code": "API_400",
    "message": "Request validation failed",
    "details": [
      "chainIds: Unsupported chainId: [123456789]."
    ]
  }
}
```

## Stage 2: Sign and submit

The user signs the returned message in their wallet. For `accountType:
"ECDSA"`, sign `message.raw` with ECDSA `personal_sign`.

For `accountType: "ERC1271"`, sign `message.typedData` as EIP-712 typed data.
An ERC-1271 signature supports one EVM chain. Then, send all stage-1 fields to
`POST /v2/enroll`.

### Round-trip contract

Send these fields to stage 2 **byte-for-byte unchanged**. A change causes
signature verification to fail.

| Field            | Stage 1 returns | Stage 2 requires                  |
| ---------------- | --------------- | --------------------------------- |
| `flowId`         | Yes             | Yes — also the idempotency anchor |
| `accountIndex`   | Yes (string)    | Yes — as a string, not a number   |
| `agentAccountId` | Yes             | Yes                               |
| `ownerAccountId` | (input)         | Same CAIP-10 as stage 1           |
| `strategyId`     | (input)         | Same as stage 1                   |
| `chainIds`       | (input)         | Same set, same order              |

```bash theme={null}
curl -X POST https://api.glider.fi/v2/enroll \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "eip155:0:0xabcdef0000000000000000000000000000000001",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1, 8453, 56],
    "accountIndex": "7",
    "agentAccountId": "eip155:0:0x1111111111111111111111111111111111111111",
    "signature": "0x9412d70d...39e01b",
    "flowId": "flow_01JWZEE2MF30KVRMRX53N88VA4",
    "portfolioName": "Alice Balanced"
  }'
```

Response:

```json theme={null}
{
  "success": true,
  "data": {
    "portfolioId": "a1b2c3d4",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "smartAccounts": [
      { "accountId": "eip155:1:0x2222222222222222222222222222222222222222" },
      { "accountId": "eip155:8453:0xe3a2d1f49aee887e42655b56371d4d76bbf58058" }
    ]
  }
}
```

## Solana (SVM) enrollment

The `svm_b2b_api` entitlement controls Solana enrollment for each tenant.
Without this entitlement, a Solana request returns `403 API_104`.

Each request uses one namespace. Send Solana (`1399811149`) chain IDs or EVM
chain IDs. Do not send both namespaces in one request.

Both owner models create one Swig smart wallet for each `(tenant, user)` pair.
Later enrollments reuse this wallet. Each portfolio uses a Swig sub-account.
The user funds the address in `depositAccountId`.

The same address appears in `smartAccounts[].accountId`. The `swigRoleId`
field identifies its role. The `reusedSwig` field is `true` when Glider reuses
an existing Swig wallet.

### Model B: Solana-rooted user

The Solana wallet of the user is the Swig root authority. `ownerAccountId`
uses a `solana:` CAIP-10 value. Stage 1 returns a serialized Solana
transaction. The user signs this transaction.

Stage 2 sends the signed bytes in `signedSolanaTransaction`. Glider verifies
the user signature. The pooled agent adds its signature and submits the
transaction. The paymaster pays the transaction fee.

```bash theme={null}
# Stage 1
curl -X POST https://api.glider.fi/v2/enroll/signature \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKX...owner",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1399811149]
  }'
```

```json theme={null}
{
  "success": true,
  "data": {
    "message": { "kind": "solana-message", "text": "Glider — enroll Solana portfolio\n..." },
    "agentAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:Ag3nt...PdA",
    "accountIndex": "0",
    "flowId": "flow_abc123",
    "solanaTransaction": "AgAB... (base64; the user signs THIS)",
    "depositAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:Dep0sit...PdA",
    "swigRoleId": 1,
    "reusedSwig": false
  }
}
```

Do not send `signature` in stage 2. Send `signedSolanaTransaction` instead.
The transaction contains a recent blockhash. Sign and submit it promptly.

```bash theme={null}
curl -X POST https://api.glider.fi/v2/enroll \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKX...owner",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1399811149],
    "accountIndex": "0",
    "agentAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:Ag3nt...PdA",
    "signedSolanaTransaction": "AgAB... (base64, user-signed)",
    "flowId": "flow_abc123"
  }'
```

### Model A: EVM-rooted user on Solana

An EVM wallet can own a Solana sub-account. Keep `ownerAccountId` as
`eip155:0:<addr>`. Set `chainIds` to `[1399811149]`. The secp256k1 key of the
user becomes the Swig root.

Stage 1 returns `swigAccountId`, `depositAccountId`, `swigRoleId`, and
`reusedSwig`. It also returns an `ecdsa` message for information. **The user
does not sign this message.**

The client must get a current slot and build the Swig add-authority payload.
The payload uses `swigAccountId`, `agentAccountId`, and the slot. The user
signs its hash with `personal_sign`.

Send `userSecp256k1PublicKey`, `signature`, and `currentSlot` to stage 2. You
can also send `ownerSignatureScheme`. Its default value is `eip191`. Swig
uses a short slot window. Build and submit the request immediately.

```bash theme={null}
curl -X POST https://api.glider.fi/v2/enroll \
  -H 'x-api-key: gldr_sk_your_api_key' \
  -H 'Content-Type: application/json' \
  -d '{
    "ownerAccountId": "eip155:0:0xabcdef0000000000000000000000000000000001",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "chainIds": [1399811149],
    "accountIndex": "0",
    "agentAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:Ag3nt...PdA",
    "userSecp256k1PublicKey": "0x04...",
    "signature": "0x9412d70d...39e01b",
    "currentSlot": "284512345",
    "flowId": "flow_abc123"
  }'
```

The stage-2 response uses the EVM response shape. Each `smartAccounts[]` entry
includes the funded sub-account, `depositAccountId`, and `swigRoleId`.

```json theme={null}
{
  "success": true,
  "data": {
    "portfolioId": "a1b2c3d4",
    "strategyId": "01JWZEE2MF30KVRMRX53N88VA4",
    "smartAccounts": [
      {
        "accountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:Dep0sit...PdA",
        "depositAccountId": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:Dep0sit...PdA",
        "swigRoleId": 1
      }
    ]
  }
}
```

## Error handling

| Code      | HTTP | Cause                                                                                                                 | Retry safe?                          |
| --------- | ---- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------ |
| `API_400` | 400  | Unknown strategy, expired `flowId` (past 24h), invalid signature, malformed CAIP-10, or mixed Solana + EVM `chainIds` | No — re-run stage 1                  |
| `API_104` | 403  | Missing `enroll:write` scope, or Solana enrollment not enabled for your tenant (`svm_b2b_api`)                        | No — terminal until granted          |
| `API_007` | 409  | A previous stage-2 call with this `flowId` is still executing                                                         | Yes — back off and retry             |
| `API_008` | 409  | Same `flowId`, different body — replay conflict                                                                       | No — do not retry with modified body |
| `API_202` | 409  | User is already enrolled in this strategy                                                                             | No — terminal                        |

See [Idempotency](/guides/idempotency) for the full 409 model.

## Common errors

* **Do not change `accountIndex` between stages.** `"7"` and `7` are different
  values. The schema requires a decimal string.
* **Do not create a new `flowId` for a stage-2 retry.** A new value starts a
  new flow. Retry with the original `flowId` unless the error is final.
* **Do not send a chain-bound EVM `ownerAccountId`.** Use the chain-agnostic
  `eip155:0:<addr>` form. Model B Solana owners are the exception. They use
  the mainnet `solana:5eykt4…:<base58>` form. Use
  [CAIP identifiers](/guides/caip-identifiers) to pick the right form.
* **Do not sign the informational Model A `message`.** The user must sign the
  slot-bound add-authority payload. A signature of `message` fails stage 2.
* **Do not call stage 2 after 24 hours.** The `flowId` is no longer valid.
  Start again at stage 1.
