# TRC20 Payment Gateway — Merchant Onboarding Guide

Version: 2026-07-22

Staging network: TRON Nile

Portal login: `https://portal.auroraex.wickedapp.xyz/login`

Merchant API base URL: `https://api.auroraex.wickedapp.xyz`

Developer documentation: `https://auroraex.wickedapp.xyz/integration-guide/`

This runbook defines how a Merchant tenant, its API workload, its first Portal
Owner, and later Merchant users are created. The four identities below are
different objects and must not be combined.

## 1. Identity model

| Identity | Purpose | Created by | Secret location |
| --- | --- | --- | --- |
| Merchant tenant | Isolation boundary for orders, withdrawals, wallets, callbacks, and Portal users | Gateway operator | No login secret |
| Merchant API credential | Authenticates the Merchant server to `/v1` | Merchant generates Ed25519 keypair; Gateway registers public key | Merchant server secret manager |
| Portal user | Human login with one or more Portal roles | First Owner is bootstrapped; later users accept Owner invitations | External OIDC identity provider |
| `merchant_user_ref` | Opaque reference to the Merchant's end customer requesting a withdrawal | Merchant platform | Merchant database |

The word **Owner** in this document means the Merchant Portal `owner` role. It
does not mean the TRON wallet owner key. Portal Owners cannot retrieve Gateway
wallet private keys or sign arbitrary blockchain transactions.

## 2. Information the Merchant provides

Before the Gateway operator creates the tenant, the Merchant supplies this
onboarding worksheet through an approved secure channel:

| Item | Required value |
| --- | --- |
| Merchant display name | Stable legal or operational name |
| Environment | `test` for Nile; `live` is issued only after production approval |
| API public key | Strict Base64 of the raw 32-byte Ed25519 public key |
| API key fingerprint | Lowercase SHA-256 fingerprint printed by the supplied key generator |
| API source networks | Public IPv4/IPv6 CIDRs used by the Merchant server |
| mTLS request | PKCS#10 CSR generated from a Merchant-held ECDSA P-256 private key |
| Callback URL | One exact public HTTPS URL on port 443; redirects and non-public addresses are rejected |
| Callback events | Complete event list required by the Merchant |
| Collection destination | Merchant-controlled TRON address that receives swept deposits |
| First Owner identity | Verified email, display name, and immutable OIDC subject |
| Portal source networks | Public CIDRs permitted to open the Portal |
| Operational contacts | Developer, security, finance, and incident-contact email addresses |

The Merchant never provides a TRON private key. The collection destination is
an address only. The Gateway generates and controls the deposit-address keys
and System Withdrawal Wallet keys.

## 3. Information the Gateway returns

The completed handoff package contains:

- Nile API base URL and Portal login URL;
- `X-Key-Id` for the test credential;
- Merchant API mTLS client certificate, certificate chain, and expiry date;
- Gateway webhook Ed25519 public key and its independently verified fingerprint;
- enabled scopes, source-IP policy, callback URL, and subscribed events;
- actual per-credential API rate and burst limits (Nile defaults: 300 requests
  per minute with a burst of 30);
- network name, Nile USDT contract, six-decimal asset rule, and configured limits;
- collection destination and Gateway withdrawal-wallet public address;
- links to the REST reference, OpenAPI YAML, Postman collection, Integration
  Guide, and PHP examples; and
- support channel and the request-ID information required for incident reports.

`X-Key-Id` is an identifier, not a secret. The Merchant API private key, mTLS
private key, OIDC password, TOTP seed, and any device certificate private key
are never included in the same handoff message.

## 4. Merchant API credential creation

### 4.1 Merchant generates the Ed25519 keypair

Run this only inside the Merchant's controlled environment. If the Gateway
Python package is already installed, use its console command:

```bash
trc20-merchant-keygen \
  --private-key-file ./trc20-api-nile-private.pem
```

Send the Gateway only `public_key_base64` and
`public_key_fingerprint_sha256`. Store the generated PKCS#8 PEM in the
Merchant's secret manager. Do not upload it to the Portal or send it by email.

External developers do not need the Gateway source package. Download and
inspect the dependency-free Node.js 20+ generator, then run it locally:

```bash
curl --fail --proto '=https' --tlsv1.2 \
  --output generate-merchant-keypair.mjs \
  https://auroraex.wickedapp.xyz/examples/node/generate-merchant-keypair.mjs

node generate-merchant-keypair.mjs \
  --private-key-file ./trc20-api-nile-private.pem
```

Both generators use Ed25519, create a PKCS#8 PEM once with mode `0600`, refuse
to overwrite an existing path, and print the same public handoff fields.

Use a different keypair for Mainnet. The API key authenticates HTTP requests;
it is not a TRON wallet key and cannot move funds by itself.

### 4.2 Merchant creates the mTLS key and CSR

The mTLS identity is separate from the Ed25519 request-signing identity. The
Merchant keeps both private keys, but sends only the CSR for the mTLS key.
Gateway PKI policy currently permits ECDSA P-256 client keys:

```bash
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
  -keyout trc20-api-nile-mtls.key \
  -out trc20-api-nile-mtls.csr \
  -subj "/CN=merchant-api:TENANT_KEY_ID:test"
chmod 600 trc20-api-nile-mtls.key
openssl req -in trc20-api-nile-mtls.csr -noout -verify -subject
```

Send the CSR and an independently communicated CSR SHA-256 fingerprint through
the approved onboarding channel. Never send `trc20-api-nile-mtls.key`.

The Gateway operator verifies the tenant, environment, requested credential,
CSR proof, and source-IP policy before signing. The issued certificate must
contain TLS Web Client Authentication (`clientAuth`) usage. The operator
records its serial number, SHA-256 certificate fingerprint, exact expiry, and
tenant/environment association, then returns the client certificate and CA
chain through a separate authenticated channel.

The Merchant validates the package before use:

```bash
openssl verify -purpose sslclient \
  -CAfile trc20-api-ca-chain.pem trc20-api-nile-client.crt
openssl x509 -in trc20-api-nile-client.crt \
  -noout -sha256 -fingerprint -serial -enddate
```

Certificate validity is policy-defined; the exact expiry is part of the
handoff. Rotation issues a second certificate first, verifies it against
`GET /v1/capabilities`, moves traffic during a documented overlap, and only
then revokes/removes the old certificate at the edge. Automated issuance and
revocation tooling is not yet present in this repository; Nile uses an audited
manual operator procedure, and Mainnet onboarding remains blocked until the
production PKI runbook and revocation mechanism are approved.

### 4.3 Gateway creates the tenant

The Gateway operator runs the local administration CLI with the provisioner
database identity. There is deliberately no public tenant-creation API.

```bash
trc20-admin create-tenant \
  --name "MERCHANT_NAME" \
  --environment test \
  --scope orders:read \
  --scope orders:write \
  --scope withdrawals:read \
  --scope withdrawals:write \
  --public-key "PUBLIC_KEY_BASE64" \
  --callback-url "https://merchant.example/payments/trc20/webhook" \
  --allowed-ip "203.0.113.10/32" \
  --min-deposit "1.000000" \
  --max-withdrawal "10000.000000"
```

The printed `trc_test_...` value is both the tenant key ID used by operator
commands and the initial Nile `X-Key-Id`. The environment is mandatory so
tenant bootstrap cannot silently create a Mainnet credential from a Nile key.
For Mainnet, generate a different Merchant keypair and issue a separate `live`
credential only after the production approval gate.

### 4.4 Gateway completes tenant configuration

```bash
trc20-admin set-collection-route \
  --tenant-key-id TENANT_KEY_ID \
  --address MERCHANT_COLLECTION_ADDRESS

trc20-admin bind-withdrawal-wallet \
  --tenant-key-id TENANT_KEY_ID \
  --network nile \
  --address GATEWAY_WITHDRAWAL_WALLET_ADDRESS

trc20-admin set-webhook-endpoint \
  --tenant-key-id TENANT_KEY_ID \
  --url "https://merchant.example/payments/trc20/webhook" \
  --event deposit.confirmed \
  --event order.expired \
  --event sweep.confirmed \
  --event sweep.manual_review \
  --event withdrawal.status_changed \
  --event withdrawal.confirmed

```

The Gateway also issues an mTLS client certificate for the dedicated API
hostname. Both mTLS and Ed25519 request signing are required.

The webhook command replaces the complete active subscription version. When
changing it, repeat every event that must remain enabled; omitted events become
unsubscribed.

The callback validator requires HTTPS, no URL userinfo, port 443, and a
hostname whose complete DNS result is globally routable. The worker does not
follow redirects, uses a five-second connect timeout and fifteen-second read
timeout, retries with exponential backoff capped at one hour, and dead-letters
after 12 failed attempts.

The current callback headers do not contain a webhook signing-key ID. During a
webhook-key rotation, the Merchant verifier must temporarily accept both
independently pinned Gateway public keys while the operator coordinates the
cutover. Mainnet requires an approved overlap/removal runbook; do not replace a
pinned callback key without that coordination.

## 5. How the first Owner is created

The first Owner cannot invite itself. It is created through a one-time,
audited operator bootstrap after these prerequisites exist:

1. the Merchant tenant is active;
2. the Owner identity exists in the configured OIDC provider;
3. the Owner's email is verified; and
4. the immutable OIDC `sub` value has been obtained from the provider.

Use the exact `sub` claim from a successfully verified ID token, or obtain that
claim through the identity provider's supported administrative interface. Do
not assume Dex `staticPasswords[].userID` is byte-for-byte identical to the
issued `sub`: the current Dex build can expose an opaque encoded subject. The
Dex password hash and plaintext password never enter the Gateway database.

```bash
trc20-admin bootstrap-portal-user \
  --tenant-key-id TENANT_KEY_ID \
  --oidc-sub OWNER_OIDC_SUBJECT \
  --email owner@merchant.example \
  --display-name "Merchant Owner" \
  --role owner \
  --role admin \
  --role approver \
  --role finance \
  --role developer \
  --role viewer
```

The command is idempotent for an already-active user with the same tenant and
OIDC subject: it reuses that user and adds only missing roles. It refuses to
reactivate a suspended or deactivated user.

The Owner then opens the Portal login URL from an allowed source network and,
when device-certificate enforcement is enabled, from a registered device.
Authentication occurs at the OIDC provider. The Portal stores a server-side
session and never exposes OIDC tokens to browser JavaScript.

## 6. How later Merchant users are created

After the first Owner can log in, normal Portal users are created by invitation:

1. The intended user is first created in the configured OIDC identity provider.
   In Nile Dex this means adding a `staticPasswords` entry and restarting Dex
   with the controlled configuration. An invitation does not create a Dex or
   production-IdP password.
2. An existing Portal Owner opens **Team & roles** and selects **Invite user**.
3. The Owner enters the exact OIDC email and assigns one or more roles.
4. The Owner completes fresh authentication for this privileged action.
5. The Gateway emails a one-time invitation link. The token is valid for 72
   hours by default, is stored only as a SHA-256 hash, and is never returned by
   the Portal API.
6. The invitee opens the link in the same browser that starts the login flow,
   signs in with the invited, verified email, and completes OIDC authentication.
7. The callback requires the verified OIDC email to match the invitation
   exactly, creates the tenant-scoped Portal user, grants the selected roles,
   consumes the invitation, and starts the Portal session.

If the browser reports `missing or mismatched login state`, start again from
the invitation link in one browser profile. Do not copy only the final OIDC
callback URL to a different browser; the login-state cookie intentionally
binds the callback to the browser that started authentication.

The current Nile stack delivers invitations to Mailpit, not to public email.
The operator retrieves the message from the locally bound Mailpit UI and sends
the one-time link through the approved staging channel. Production onboarding
must use the configured real SMTP provider. The current Portal has no
invitation list, revoke, or resend workflow; issue a new invitation after
expiry and treat the old link as sensitive until it expires.

The legacy `trc20-admin create-merchant-user` command creates only an old
display record and does not bind an OIDC subject. It cannot create a Portal
login and must not be used for onboarding.

### Role matrix

| Role | Intended responsibility |
| --- | --- |
| `owner` | Invite users, change roles, and perform Owner-only governance actions |
| `admin` | API credential, webhook, and selected operational administration |
| `approver` | Review controlled withdrawals and Merchant final-sign releases |
| `finance` | Balances, ledger, reconciliation, and exports |
| `developer` | API credentials, webhook status, and integration diagnostics |
| `viewer` | Read-only operational visibility |

Grant the smallest role set required. A subsequent Owner can be invited by an
existing Owner by including the `owner` role. Do not remove or deactivate the
last working Owner until another Owner has logged in successfully.

Changing a user's roles is a complete replacement of that user's active role
set, not an additive single-role grant. Role changes require fresh
authentication and invalidate that user's active Portal sessions.

## 7. API and Portal users are independent

- A Portal user cannot call `/v1` with browser cookies.
- An API credential cannot log in to the Portal.
- `merchant_user_ref` identifies the Merchant's customer in withdrawal records;
  it does not create a Portal account.
- A Portal `approver` authorizes a release; the Merchant-managed signer/HSM
  contributes the required cryptographic signature without exposing its key to
  the browser.
- A Portal `owner` is not a Gateway wallet-key owner and does not manually
  provide the Gateway's normal signing step.

## 8. Onboarding acceptance test

Onboarding is complete only when all checks below pass with fresh evidence:

1. The Merchant API mTLS handshake succeeds only with the issued client
   certificate.
2. Signed `GET /v1/capabilities` returns `network: nile`, `asset: USDT_TRC20`,
   and `arbitrary_signing_supported: false`.
3. A signed order request returns a one-time deposit address and an exact replay
   returns the same order with `idempotent_replay: true`.
4. A real Nile deposit reaches `paid`, the callback signature verifies, and the
   funds sweep to the configured Merchant collection address.
5. A withdrawal exact replay is idempotent; status polling and signed callbacks
   agree on the terminal result.
6. A below-threshold withdrawal follows automatic Merchant authorization; an
   above-threshold withdrawal appears in the Merchant approval queue and is
   released only after the expected Merchant action.
7. Duplicate webhook delivery does not repeat a Merchant business update.
8. The first Owner and a separately invited approver can log in from allowed
   networks, while an unregistered identity is rejected.

## 9. Rotation and offboarding

### API key rotation

1. Merchant generates a new Ed25519 keypair.
2. Gateway issues a second environment-matched credential with the required
   scopes.
3. Merchant verifies `GET /v1/capabilities` using the new key and mTLS identity.
4. Merchant switches production traffic.
5. Gateway revokes the old credential with `trc20-admin revoke-credential`.

Never replace the public key underneath an existing `X-Key-Id`.

### Portal user offboarding

The Gateway operator can deactivate a user through the local audited command:

```bash
trc20-admin deactivate-merchant-user --user-id MERCHANT_USER_ID
```

Deactivation must be coordinated with the external identity provider and any
device certificate. Removing only one layer is incomplete offboarding.

### Callback or collection-address change

Webhook endpoint changes are versioned complete replacements. Collection-route
changes use the Portal governance and cooling-off workflow after initial
bootstrap; do not bypass it with direct database updates.

## 10. Merchant developer handoff links

- Integration Guide: `https://auroraex.wickedapp.xyz/integration-guide/`
- REST API Reference: `https://auroraex.wickedapp.xyz/api-reference/`
- OpenAPI: `https://auroraex.wickedapp.xyz/openapi/`
- OpenAPI YAML: `https://auroraex.wickedapp.xyz/openapi.yaml`
- Postman collection: `https://auroraex.wickedapp.xyz/postman/TRC20-Payment-Gateway.postman_collection.json`
- Postman Nile environment: `https://auroraex.wickedapp.xyz/postman/TRC20-Nile-Staging.postman_environment.json`
- Postman setup: `https://auroraex.wickedapp.xyz/postman/README.md`
- Standalone flow demo: `https://auroraex.wickedapp.xyz/demo/`
- PHP examples: `https://auroraex.wickedapp.xyz/examples/php/`
- Node key generator: `https://auroraex.wickedapp.xyz/examples/node/generate-merchant-keypair.mjs`

The Portal OpenAPI contract is for the Portal SPA and Gateway operators. It is
not required for the Merchant server's payment integration and does not expose
wallet custody or arbitrary signing endpoints.

## 11. Mainnet onboarding gates

The documented Nile flows are usable, but the following controls are explicit
Mainnet blockers rather than completed onboarding features:

- approved automated or audited PKI issuance, renewal, and revocation tooling;
- a production OIDC provider with enforced MFA and recovery controls (Nile Dex
  is password-only; Portal fresh authentication checks a recent OIDC
  `auth_time`, not a local Portal password or TOTP);
- invitation list/revoke/resend support and protection against removing the
  last active Owner;
- durable Merchant signer user/capability/TOTP provisioning rather than the
  current staging seed procedure; and
- a production webhook signing-key rotation runbook because deliveries do not
  yet include a webhook key-version header.

The first production Owner may be bootstrapped only after those gates and the
Mainnet readiness review pass. No operator should describe a seeded staging
signer or Dex user as a production-ready Merchant identity lifecycle.
