Background
The only way to create an Admin row so far is one-time bootstrap script. This issue adds the normal, ongoing way: an existing superadmin adding another admin through the API. Per the request, this does not touch the smart contract at all — it's a backend-only concept, deliberately decoupled from the contract's own Admin/Manager/Operator role system, which is a separate on-chain authorization concern this backend doesn't currently drive.
Proposed Steps
POST /admin/admins (requireSuperAdmin) — body { address: string, name: string, isSuperAdmin?: boolean }.
- Validate
address with StrKey.isValidEd25519PublicKey; 400 if invalid.
- Reject if an
Admin row for that address already exists — 409, matching the same non-overwrite discipline as the one-time bootstrap script.
- Create the row with
createdBy: req.admin.id (the acting superadmin) and isSuperAdmin: Boolean(body.isSuperAdmin) (defaults to false — a superadmin creating another admin does not implicitly grant superadmin unless explicitly requested; a superadmin can choose to grant it, since restricting that further isn't something the request asked for).
- Record a
admin.created audit log entry with actorId: req.admin.id, targetId: <new admin id>.
- Return the created admin record (no keypair or secret involved anywhere in this flow — admins authenticate with their own existing Stellar wallet.
Acceptance Criteria
Background
The only way to create an
Adminrow so far is one-time bootstrap script. This issue adds the normal, ongoing way: an existing superadmin adding another admin through the API. Per the request, this does not touch the smart contract at all — it's a backend-only concept, deliberately decoupled from the contract's ownAdmin/Manager/Operatorrole system, which is a separate on-chain authorization concern this backend doesn't currently drive.Proposed Steps
POST /admin/admins(requireSuperAdmin) — body{ address: string, name: string, isSuperAdmin?: boolean }.addresswithStrKey.isValidEd25519PublicKey;400if invalid.Adminrow for that address already exists —409, matching the same non-overwrite discipline as the one-time bootstrap script.createdBy: req.admin.id(the acting superadmin) andisSuperAdmin: Boolean(body.isSuperAdmin)(defaults tofalse— a superadmin creating another admin does not implicitly grant superadmin unless explicitly requested; a superadmin can choose to grant it, since restricting that further isn't something the request asked for).admin.createdaudit log entry withactorId: req.admin.id,targetId: <new admin id>.Acceptance Criteria
POST /admin/adminsrequiresrequireSuperAdmin— a non-superadmin admin gets403201with the created admin (noisSuperAdminin body → defaults tofalse)409, no row created or modified400createdByis set to the acting superadmin's idAdminLogentry (admin.created) is produced per successful call