Credential security
Yoursk_live_ API key grants full access to your organization’s accounts. Treat it like a database password.
- Store API keys in environment variables or a secrets manager
- Never log the full key value
- Create separate keys per environment (dev, staging, production)
- Rotate keys on a regular schedule, not only after incidents
pk_live_) is safe for client-side code. It identifies your account type but cannot read or modify account data on its own. The SIWE session provides the authorization layer.
Session management
Deposits and withdrawals run through stateful sessions. One active session per account at a time. Re-quoting: CallquoteDeposit or quoteWithdraw again on the same OPEN session to update amounts and prices. Do not create a new session for re-quotes.
forceReset: true only when you need to cancel the existing session entirely. This is for starting a completely new flow, not for updating a quote.
Session expiration TTLs:
| State | TTL | On expiry |
|---|---|---|
| OPEN | ~15 minutes | Auto-cancelled |
| LOCKED | ~1 hour | Auto-cancelled |
| SUBMITTED | ~1 hour | Marked as failed |
Transaction handling
Wait between action plans. The SDK callswaitForNextBlock() between sequential action plans. If you build custom execution logic, do the same. Submitting two plans in the same block can cause nonce collisions.
Multi-chain withdrawal sequencing. Withdrawals can produce multiple action plans (one per source chain). Process them in sequence, not in parallel.
paymasterUrl with a chain-aware resolver.
Conflict handling
A409 with FLOWPLAN_CONFLICT means a rebalance is in progress on the account. The system is moving funds between vaults.
Do not retry blindly. Surface this as a product-level message and let the user retry after the account settles.
Safe deployment
Safes are deployed lazily. Creating an account does not deploy the Safe on every chain. ThechainsDeployed array in the account response tells you which chains are live right now.
This catches most people off guard: signing in or looking up an account does not deploy a Safe. The chainId in signIn() is only for the SIWE challenge message. Deploying a Safe is always a separate safe.request() call, and it happens asynchronously.
Before your first transaction on any chain, check deployment and request it if needed:
safe.request() is fire-and-forget. Deployment happens in the background. Poll safe.resolve() until the status changes to "validated" before executing transactions.
safe.resolve() returns one of four statuses:
| Status | Meaning |
|---|---|
"validated" | Safe exists on this chain. Ready for transactions. |
"not-deployed" | Safe hasn’t been deployed on this chain yet. Call safe.request(). |
"invalid" | Contract at the address is not a valid Safe. |
"disconnected" | Could not reach the chain RPC. Retry. |
Error recovery
Theexecute method in both SDKs is crash-safe. If your app crashes mid-execution, calling execute again with the same intent resumes from the current session state. It does not restart from scratch.
isRetryable() returns true for HTTP 429, 5xx status codes, and network errors (status 0). The built-in HTTP client already retries with exponential backoff and jitter, so you only need manual retry logic for application-level recovery.
Frontend SDK
See the full frontend SDK reference.
Server SDK
See the full server SDK reference.