Enrollment & Portfolio Setup
Prepare Enrollment Authorization (Stage 1)
Stage 1 of the two-stage enrollment flow — returns the wallet authorization payload and round-trip fields.
POST
Stage 1 of the two-stage enrollment flow. It verifies that your tenant can
enroll users in the
strategyId and returns the wallet authorization payload
plus the round-trip values (flowId, accountIndex, agentAccountId) that
stage 2 (POST /v2/enroll) needs. EVM enrollment reads a candidate account
index. SVM enrollment returns accountIndex: "0" as a compatibility
placeholder.
Wallet addresses are exchanged as CAIP-10
account identifiers. For EVM, ownerAccountId and agentAccountId use the
chain-agnostic form eip155:0:<address> — a single wallet works on every
EIP-155 chain.
For EVM smart-contract wallets, pass accountType: "ERC1271" in both stages.
ERC-1271 enrollment returns an EIP-712 typed-data message and currently
requires exactly one EVM chainId per enrollment signature. Omit
accountType or pass "ECDSA" for the existing EOA/multichain path.
Solana (SVM) enrollment
A Solana-native user enrolls withownerAccountId set to a solana: CAIP-10
(solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:<base58>) and chainIds: [1399811149].
The SVM flow mirrors how a Solana user joins the platform: the first portfolio
for a given (tenant, user) pair creates a Swig smart wallet (the user’s
wallet key is its root authority — a Solana key for a solana: owner, an EVM
key for an eip155: owner; see below); subsequent portfolios reuse the same
Swig. Each portfolio is a sub-account of that Swig, and the sub-account
address is the deposit address the user funds. The pooled agent is attached
at create, so the portfolio is operable immediately.
Solana-rooted users (Model B)
Because creating the sub-account is an on-chain action, stage 1 returns a serialized Solana transaction (solanaTransaction) for the user to sign with
their wallet — instead of signing message. The response also includes:
solanaTransaction— base64 transaction; the user signs it and round-trips the signed bytes toPOST /v2/enrollassignedSolanaTransaction. It carries a recent blockhash, so sign and submit promptly.depositAccountId— CAIP-10 of the sub-account the user sends funds to.swigRoleId— the Swig role that owns the sub-account (1for the first portfolio, higher when reusing an existing Swig).reusedSwig—truewhen an existing Swig PDA for this (tenant, user) is reused,falsewhen a new one is created.
EVM-rooted users (Model A)
A user whose primary wallet is EVM enrolls into the same Solana sub-account model by settingownerAccountId to eip155:0:<address> with
chainIds: [1399811149]. Their secp256k1 (EVM) key becomes the Swig root.
Stage 1 returns the targets the client needs to build the authorization:
swigAccountId— CAIP-10 of the parent Swig PDA.depositAccountId/swigRoleId/reusedSwig— as above.agentAccountId,accountIndex,flowId— round-tripped to stage 2.
message is an ecdsa digest that is informational only — the user
does not sign it. The client builds a slot-bound Swig add-authority
payload (from swigAccountId + agentAccountId + a freshly captured slot),
personal_signs its hash, then sends userSecp256k1PublicKey + signature +
currentSlot to POST /v2/enroll. Capture the slot and submit promptly — Swig
enforces a slot window. There is no create-only fallback; these fields are
required.
- Auth:
x-api-keyheader (required) - Scope:
enroll:write
flowId is the idempotency anchor for the matching
POST /v2/enroll call — it is valid for 24 hours. Re-submitting the same
flowId on stage 2 replays the cached response.
For EVM, the accountIndex is peeked, not reserved: repeated stage-1
calls for the same user return the same candidate index, and the index only
advances when stage 2 commits successfully. Retries, abandoned flows, and
tests do not burn account indices. For SVM, accountIndex is always "0".
It is a compatibility placeholder, not an account reservation or derivation
input.
Unknown chains in chainIds are rejected with 400 before any enrollment
state is created — the allowed list is in the error details.
Common error responses:
400when the request body is invalid, thestrategyIddoesn’t exist or your API key cannot access it, orchainIdscontains an unsupported chain401whenx-api-keyheader is missing or the key is invalid403when the API key lacks theenroll:writescope500on unexpected server errors