diff --git a/docs-main/docs.json b/docs-main/docs.json index 55511eef5..2689f9cef 100644 --- a/docs-main/docs.json +++ b/docs-main/docs.json @@ -547,8 +547,14 @@ "group": "Use the Wallet Gateway", "pages": [ "integrations/wallet-gateway/use/party-management", - "integrations/wallet-gateway/use/approve-and-sign", - "integrations/wallet-gateway/use/automate-with-user-api" + "integrations/wallet-gateway/use/approve-and-sign" + ] + }, + { + "group": "Automation", + "pages": [ + "integrations/wallet-gateway/automation/automate-with-user-api", + "integrations/wallet-gateway/automation/service-account-automations" ] }, { diff --git a/docs-main/integrations/release-notes/wallet-gateway.mdx b/docs-main/integrations/release-notes/wallet-gateway.mdx index f36c0f3f1..f5c9f27d2 100644 --- a/docs-main/integrations/release-notes/wallet-gateway.mdx +++ b/docs-main/integrations/release-notes/wallet-gateway.mdx @@ -1,6 +1,7 @@ --- title: "Wallet Gateway" description: "Release notes for the Canton Network Wallet Gateway" +sidebarTitle: "Release Notes" --- {/* GENERATED_RELEASE_NOTES target="wallet-gateway" format="split-index" latest_version="1.6.0" latest_source="@canton-network/wallet-gateway-remote@1.6.0" */} diff --git a/docs-main/integrations/wallet-gateway/use/automate-with-user-api.mdx b/docs-main/integrations/wallet-gateway/automation/automate-with-user-api.mdx similarity index 88% rename from docs-main/integrations/wallet-gateway/use/automate-with-user-api.mdx rename to docs-main/integrations/wallet-gateway/automation/automate-with-user-api.mdx index 01aa13d7b..f6eaea422 100644 --- a/docs-main/integrations/wallet-gateway/use/automate-with-user-api.mdx +++ b/docs-main/integrations/wallet-gateway/automation/automate-with-user-api.mdx @@ -43,7 +43,7 @@ client can bootstrap a connection: `addSession()`, `listNetworks()`, and `listId Parameter shapes vary per method. Use the -[OpenRPC specification](https://github.com/canton-network/wallet-gateway/blob/main/api-specs/openrpc-user-api.json) +[OpenRPC specification](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-user-api.json) as the source of truth for exact request and response fields. @@ -146,17 +146,7 @@ curl -X POST http://localhost:3030/api/v0/user \ ## Next steps - - - Every method, authentication rules, rate limits, and the OpenRPC spec. - - - The same operations in the User UI. - - - How approval and signing work end to end. - - - Where each wallet's keys live and who signs. - - +- [User API reference](/integrations/wallet-gateway/reference/user-api): Every method, authentication rules, rate limits, and the OpenRPC spec. +- [Party management](/integrations/wallet-gateway/use/party-management): The same operations in the User UI. +- [Approve & sign transactions](/integrations/wallet-gateway/use/approve-and-sign): How approval and signing work end to end. +- [Signing providers](/integrations/wallet-gateway/operate/signing-providers): Where each wallet's keys live and who signs. diff --git a/docs-main/integrations/wallet-gateway/automation/service-account-automations.mdx b/docs-main/integrations/wallet-gateway/automation/service-account-automations.mdx new file mode 100644 index 000000000..0648067ac --- /dev/null +++ b/docs-main/integrations/wallet-gateway/automation/service-account-automations.mdx @@ -0,0 +1,391 @@ +--- +title: "Service account automations" +description: "Automate ledger transactions from a backend, CI pipeline, or service using a Wallet Gateway API key." +--- + +Service account automation lets a backend job, CI pipeline, or other service submit ledger +transactions through the Wallet Gateway without a person in the loop. It uses the **same dApp +API** as interactive dApps (`prepareExecute`, `txChanged` events), but the request is +authenticated with a user-generated **API key**. When an API key is used, the Wallet Gateway +prepares, signs, and executes the transaction straight through after `prepareExecute`. + +This differs from [Automate with the User API](/integrations/wallet-gateway/automation/automate-with-user-api), +which drives the User API with a user JWT. Service account automation drives the **dApp API** +with an API key and runs unattended. + +## How it fits together + +```mermaid +flowchart LR + A["Your automation
(backend / CI)"] + subgraph WG["Wallet Gateway"] + DA["dApp API"] + SW["Signing worker"] + end + L["Canton participant
(Ledger API)"] + S["External signer
(Fireblocks, Blockdaemon, DFNS)"] + A -->|"dApp API: prepareExecute
Authorization: ApiKey"| DA + DA <-->|"token via serviceAccountAuth"| L + DA -.->|"external signing"| S + SW -.->|"poll pending"| S + DA -->|"txChanged (SSE)"| A +``` + +**How authentication works:** + +1. An operator logs into the Wallet Gateway with the normal user OAuth flow (`auth`, typically + `authorization_code`). +2. The operator creates wallets and generates an **API key** (`generateApiKey` in the User API, + or the User UI). +3. The automation sends that API key on dApp API requests: `Authorization: ApiKey `. +4. The Wallet Gateway validates the key, scopes the request to the key owner's stored wallets + and network, and obtains a **ledger access token** using the network's **`serviceAccountAuth`** + configuration (typically `client_credentials` OAuth). + +**What service account automation enables:** + +- On `prepareExecute` with API key auth, the Wallet Gateway immediately runs + **prepare → sign → execute** when signing returns `signed`. +- For external custody signers that approve asynchronously, a background **Signing worker** + polls pending transactions and completes them when the provider approves. +- The `userUrl` in the `prepareExecute` response is still returned for API compatibility; + automations should rely on **`txChanged`** events (or polling transaction status) instead of + the approval UI. + +**What it does not enable:** + +- Ledger **users**, **parties**, and **rights** still come from your Canton / IDP setup. +- The Wallet Gateway still needs a **stored wallet** (party) for the API key owner before + `prepareExecute` can succeed. +- Interactive users still authenticate with `auth`; API keys are an additional automation + credential, not a replacement for end-user login. + +## Prerequisites + +Complete every item below before calling `prepareExecute` from automation. + +**1. A ledger user must exist in the Wallet Gateway.** +The API key is tied to the Wallet Gateway user who created it. That user must have completed a +normal login session at least once so wallets and network context exist in the store. The +ledger user ID used for ledger operations comes from the token obtained via `serviceAccountAuth`, +so configure that OAuth client such that the minted token's `sub` (or your IDP mapping) matches +the ledger user that holds rights for the automated party. + +**2. A wallet (party) must exist with ledger rights.** +`prepareExecute` uses the API key owner's **primary wallet** when `actAs` is omitted. That party +must exist on the Canton participant and grant the ledger user sufficient rights (typically +`actAs` / `readAs`) to prepare and submit the commands you automate. Create or sync wallets +through the [User API](/integrations/wallet-gateway/reference/user-api) (`createWallet`, +`syncWallets`) or the User UI before automation runs. If no primary wallet is stored, +`prepareExecute` fails with "No primary wallet found". + +**3. A signing provider must be configured.** +Each wallet records a `signingProviderId` that selects which driver signs the transaction: + +| Provider | Typical use | +| --- | --- | +| `participant` | Keys on the Canton participant node | +| `fireblocks` | Fireblocks custody | +| `blockdaemon` | Blockdaemon signing | +| `dfns` | DFNS custody | +| `wallet-kernel` | Internal Wallet Gateway signing (development only) | + +The provider must be installed and configured on the Wallet Gateway host. See +[Signing providers](/integrations/wallet-gateway/operate/signing-providers). + + +The signing provider is chosen at **wallet creation** time. Automation cannot switch providers +per request; it always uses the primary wallet's configured provider. + + +**4. An API key must be generated.** +Create an API key for the target network while logged in as the automation owner: + +- **User UI**: open **API Keys** and create a key (copy it immediately, it is shown only once). +- **User API**: call `generateApiKey({ "name": "my-automation" })` and store the returned + `apiKey` securely. + +Each key is bound to the current network at creation time. Revoke keys with `removeApiKey` or the +User UI when they are no longer needed. + +**5. The network must define `serviceAccountAuth`.** +Straight-through execution requires a `serviceAccountAuth` block on the network, which the Wallet +Gateway uses to obtain ledger tokens when an API key request arrives. It must use the +`client_credentials` OAuth method. See +[Configure the Wallet Gateway](/integrations/wallet-gateway/operate/configure). + +## Wallet Gateway configuration + +### Network: `auth`, `adminAuth`, and `serviceAccountAuth` + +A typical production network keeps interactive login on `authorization_code` and adds a +dedicated `serviceAccountAuth` block for automation: + +```json +{ + "id": "canton:mainnet", + "name": "Mainnet", + "identityProviderId": "idp-oauth", + "ledgerApi": { + "baseUrl": "https://ledger.example.com" + }, + "auth": { + "method": "authorization_code", + "clientId": "wallet-gateway-user", + "audience": "https://canton.network.global", + "scope": "openid daml_ledger_api offline_access" + }, + "adminAuth": { + "method": "client_credentials", + "clientId": "wallet-gateway-admin", + "clientSecretEnv": "WG_ADMIN_CLIENT_SECRET", + "audience": "https://canton.network.global", + "scope": "openid daml_ledger_api offline_access" + }, + "serviceAccountAuth": { + "method": "client_credentials", + "clientId": "wallet-gateway-automation", + "clientSecretEnv": "WG_SERVICE_ACCOUNT_CLIENT_SECRET", + "audience": "https://canton.network.global", + "scope": "openid daml_ledger_api offline_access" + } +} +``` + +| Field | Purpose | +| --- | --- | +| `auth` | End-user OAuth login (User UI, interactive dApps). | +| `adminAuth` | Machine credentials for wallet sync and party allocation on first setup. | +| `serviceAccountAuth` | Machine credentials the Wallet Gateway uses for ledger access during API key automation. | + +`adminAuth` is required when the user has no wallets yet and the Wallet Gateway should discover +parties from the ledger on `addSession`. + + +Store `serviceAccountAuth` and `adminAuth` secrets via `clientSecretEnv` and Kubernetes secrets +(Helm `oauthSecrets`) rather than plain text in config files. + + +### Server: signing worker + +```json +{ + "server": { + "signingWorker": { + "pollInterval": 5000 + } + } +} +``` + +| Field | Description | +| --- | --- | +| `signingWorker.pollInterval` | How often the Signing worker polls external signers when a transaction stays `pending` after submit. Default: `5000` ms. | + +Participant-only signing does not require external custody configuration. + +## One-time setup workflow + +Perform these steps once per Wallet Gateway user and network you automate (or repeat when +wallets change). + + + +The automation owner logs into the User UI or calls User API `addSession` with a normal user +OAuth token. This establishes wallets, network context, and the ability to create API keys. + + + +List wallets (`listWallets` or the User UI). If empty, create a wallet or sync from the ledger, +then set a primary wallet. Verify the primary party via the dApp API: + +```bash +curl -s -X POST "https://gateway.example.com/api/v0/dapp" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"listAccounts","params":[]}' +``` + + + +```bash +curl -s -X POST "https://gateway.example.com/api/v0/user" \ + -H "Authorization: Bearer ${USER_ACCESS_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 2, + "method": "generateApiKey", + "params": { "name": "ci-automation" } + }' +``` + +Store the returned `apiKey` securely. It cannot be retrieved again. + + + +## Submitting transactions + +### `prepareExecute` (straight-through) + +Use the dApp API with API key authentication: + +```bash +curl -s -X POST "https://gateway.example.com/api/v0/dapp" \ + -H "Authorization: ApiKey ${API_KEY}" \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 3, + "method": "prepareExecute", + "params": { + "commands": [{ + "CreateCommand": { + "templateId": "#AdminWorkflows:Canton.Internal.Ping:Ping", + "createArguments": { + "id": "automation-ping-1", + "initiator": "my-party::fingerprint", + "responder": "my-party::fingerprint" + } + } + }] + } + }' +``` + +For service accounts, the Wallet Gateway: + +1. Validates the API key and resolves the owner's wallets and network. +2. Obtains a ledger token via `serviceAccountAuth`. +3. Prepares the transaction on the ledger. +4. Signs with the primary wallet's signing provider. +5. Executes immediately when signing returns `signed`. +6. Returns `{ "userUrl": "…" }` (ignore for automation; monitor events instead). + +Ensure `actAs` / `readAs` in the command match parties the API key owner has in the Wallet +Gateway store. When omitted, the primary wallet's `partyId` is used. + +### Participant signing (synchronous) + +When the primary wallet uses `participant` signing, the full flow usually completes inside the +`prepareExecute` call. + +### External signing (asynchronous) + +When the primary wallet uses Fireblocks, Blockdaemon, or DFNS: + +1. `prepareExecute` prepares the transaction and submits it to the custody provider. +2. Signing may return `pending` until the provider approves the request. +3. The Signing worker background process polls pending external transactions and calls + sign → execute when approval completes. +4. Tune `signingWorker.pollInterval` if you need faster completion. + +Your automation should wait for a `txChanged` event with status `executed` (or handle `failed` +or prolonged `pending`). + +### Monitor with Server-Sent Events + +Subscribe to dApp API events for transaction lifecycle updates. Pass the API key as the `token` +query parameter: + +```javascript +const eventsUrl = new URL('/api/v0/dapp/events', 'https://gateway.example.com') +eventsUrl.searchParams.set('token', apiKey) +const es = new EventSource(eventsUrl.toString()) + +es.addEventListener('txChanged', (e) => { + const tx = JSON.parse(e.data) + console.log('Transaction update:', tx.status, tx.commandId) +}) +``` + +See [Real-time events](/integrations/wallet-gateway/reference/dapp-api#real-time-events-sse). + +## End-to-end checklist + +| Step | Action | API / UI | +| --- | --- | --- | +| 1 | Configure network with `serviceAccountAuth` + signing provider | Config | +| 2 | Log in as automation owner; create/sync wallet; set primary | User API / UI | +| 3 | `generateApiKey` and store the secret | User API / UI | +| 4 | `listAccounts` to confirm primary party | dApp API | +| 5 | `prepareExecute` with Daml commands (`Authorization: ApiKey …`) | dApp API | +| 6 | Listen for `txChanged` until `executed` | dApp SSE | + +## Production operations + +Treat service account automation as a critical dependency in production. + +### Configuration hardening + +- Configure `adminAuth` even when wallets are pre-provisioned; recovery flows and manual sync + still depend on it. +- Configure `serviceAccountAuth` with a dedicated OAuth client scoped for automation ledger + access. +- Verify every automated wallet uses a + [signing provider](/integrations/wallet-gateway/operate/signing-providers) that is configured + and monitored in the target environment. +- Rotate API keys and `serviceAccountAuth` secrets on a schedule; revoke compromised keys + immediately with `removeApiKey`. + +### Observability + +Monitor Wallet Gateway logs for these structured messages: + +| Log message | Meaning | +| --- | --- | +| `Service account straight-through prepare/sign/execute` | `prepareExecute` entered the automation path. | +| `Service account sign/execute failed after prepare` | Prepare succeeded but sign or execute failed; investigate immediately. | +| `Signing worker completed service account transaction` | Background completion of an external signing request. | +| `Signing worker: transaction still awaiting external signing` | Custody approval still pending. | +| `Skipping signing worker tick: no primary wallet configured for user` | Wallet setup missing for a pending external transaction. | + +Subscribe to `txChanged` SSE events and alert when status stays `pending` longer than your +custody SLA, status becomes `failed`, or `prepareExecute` returns an error. + +### Availability + +- The Signing worker runs inside the Wallet Gateway process and polls pending external + transactions at `signingWorker.pollInterval` (default 5 s). Run at least one Wallet Gateway + replica with this process active (enabled on startup by default). +- Persist the Wallet Gateway store (PostgreSQL recommended) so wallets, API keys, and pending + transactions survive restarts. + +### Pre-flight validation + +Before promoting an automation to production, verify in staging that: + +1. `listAccounts` (dApp API with API key) returns the expected primary party. +2. A test `prepareExecute` reaches `executed` (or `pending` → `executed` for external signers). +3. Revoked API keys are rejected with HTTP 401. + +## Security recommendations + +- Store API keys and `clientSecret` values in a secrets manager; never commit them to source + control. +- Issue one API key per automation or environment so revocation is scoped. +- Use a dedicated OAuth client for `serviceAccountAuth`, separate from `adminAuth` and end-user + `auth`, when your IDP supports least-privilege clients. +- Prefer external custody signers over `wallet-kernel` internal signing in production. See + [Signing providers](/integrations/wallet-gateway/operate/signing-providers). + +## Troubleshooting + +| Symptom | Likely cause | +| --- | --- | +| `No primary wallet found` | No wallet in the store, or none marked primary; complete setup step 2. | +| `API Key is invalid` | Wrong or revoked key; generate a new key. | +| `Network '…' does not have a service account configured` | Missing `serviceAccountAuth` on the network. | +| `No driver found for …` | Signing provider not configured on the Wallet Gateway. | +| Transaction stays `pending` | External signer awaiting approval; check the custody dashboard and Signing worker logs. | +| HTTP 401 on User API | Expired user token; API keys apply to the dApp API only. | + +See also [Troubleshooting](/integrations/wallet-gateway/operate/troubleshooting) for ledger +connectivity, `addSession` errors, and auth debugging. + +## Related + +- [Automate with the User API](/integrations/wallet-gateway/automation/automate-with-user-api) +- [Signing providers](/integrations/wallet-gateway/operate/signing-providers) +- [User API reference](/integrations/wallet-gateway/reference/user-api) +- [dApp API reference](/integrations/wallet-gateway/reference/dapp-api) diff --git a/docs-main/integrations/wallet-gateway/operate/configure.mdx b/docs-main/integrations/wallet-gateway/operate/configure.mdx index 59c9dc63f..bbcd88827 100644 --- a/docs-main/integrations/wallet-gateway/operate/configure.mdx +++ b/docs-main/integrations/wallet-gateway/operate/configure.mdx @@ -119,6 +119,25 @@ The `server` section configures binding, ports, and API paths. } ``` +### Signing worker + +When automations sign through an external custody provider, `server.signingWorker` controls the +background worker that completes transactions once the provider approves: + +```json +{ + "server": { + "signingWorker": { + "pollInterval": 5000 + } + } +} +``` + +- `signingWorker.pollInterval` (optional, default `5000` ms): how often the worker polls external + signers (Fireblocks, Blockdaemon, DFNS) while a transaction stays `pending`. See + [Service account automations](/integrations/wallet-gateway/automation/service-account-automations). + ## Store The `store` connection determines where the Wallet Gateway persists sessions, wallet @@ -154,6 +173,9 @@ recreated. `sqlite` persists only if the database file is on a persistent volume for backup guidance. +For PostgreSQL over TLS, add an `ssl` block to the connection. See +[PostgreSQL over TLS/SSL](/integrations/wallet-gateway/reference/configuration-reference#postgresql-over-tls-ssl). + ## Signing store The optional `signingStore` is a secondary database used only when the Wallet Gateway signs @@ -179,11 +201,5 @@ in production systems with valuable assets. See ## Next steps - - - Connect to a validator and configure authentication. - - - Every field, all auth methods, and per-environment guidance. - - +- [Networks & identity providers](/integrations/wallet-gateway/operate/networks-and-identity): Connect to a validator and configure authentication. +- [Configuration reference](/integrations/wallet-gateway/reference/configuration-reference): Every field, all auth methods, and per-environment guidance. diff --git a/docs-main/integrations/wallet-gateway/operate/deploy.mdx b/docs-main/integrations/wallet-gateway/operate/deploy.mdx index 49c66b60b..a0ff946fa 100644 --- a/docs-main/integrations/wallet-gateway/operate/deploy.mdx +++ b/docs-main/integrations/wallet-gateway/operate/deploy.mdx @@ -1,12 +1,18 @@ --- title: "Deploy" -description: "Run the Wallet Gateway in a container with Docker or Helm." +description: "Run the Wallet Gateway in production with Docker or Helm." --- -For anything beyond local development, run the Wallet Gateway as a container. This guide -covers what to get right when deploying, and points to the deployment manifests. The Wallet Gateway -is a Node.js server that reads a single configuration file and exposes its User UI, User API, -and dApp API on one port. +For anything beyond local development, run the Wallet Gateway as a container. It ships in both +**Docker** and **Helm** variants, and the images and charts are **public** on the GitHub +Container Registry, so no access request is required. + +- **Docker registry**: `ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway:` +- **Helm repository**: `ghcr.io/digital-asset/wallet-gateway/helm/wallet-gateway:` + +Replace `` with the version you want to deploy; there is currently no `latest` tag. To +find a version, check the [GHCR tags](https://github.com/digital-asset/wallet-gateway/pkgs/container/wallet-gateway%2Fdocker%2Fwallet-gateway) +or the matching [npm package](https://www.npmjs.com/package/@canton-network/wallet-gateway-remote). ## Before you deploy @@ -18,17 +24,43 @@ Make production choices in your configuration file before packaging it. See container or pod is recreated, and `sqlite` persists only if its file is on a persistent volume. - **Set `kernel.publicUrl`.** Behind a reverse proxy or load balancer, set it to the external - URL so client redirects work. + URL so OAuth redirects and discovery work. - **Restrict CORS.** Set `server.allowedOrigins` to your known dApp origins instead of `"*"`. - **Keep secrets out of the image.** Supply `clientSecret`, `adminAuth`, and provider API keys through environment variables or a secret manager, not the baked-in config file. See [Secrets and environments](/integrations/wallet-gateway/reference/configuration-reference#secrets-and-environments). +## Expose the service + +The Wallet Gateway must be reachable over **HTTPS** from browsers that open the User UI and from +hosted dApps that call the dApp API. In Kubernetes, expose it with an Ingress or LoadBalancer +that terminates TLS and forwards to the pod port (default `3030`). Set `kernel.publicUrl` to that +external URL so OAuth redirects and discovery work correctly. Subpath routing (for example +`https://wallet.example.com/subpath`) is supported. + ## Docker -Provide the configuration file and any required secrets to the container, and expose the -Wallet Gateway's port. Mount the config (and, for `sqlite`, a persistent volume for the database -file), then start the Wallet Gateway pointing at the mounted config. +The container needs a configuration file. If you don't have one, generate a sample to start +from: + +```shell +# via Docker +docker run --rm \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: --config-example > config.json + +# or via npx +npx @canton-network/wallet-gateway-remote@ --config-example > config.json +``` + +Mount the config and start the service: + +```shell +docker run -p 3030:3030 \ + -v ${PWD}/config.json:/app/config.json:ro \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: +``` + +The login page is then available at `http://localhost:3030`. If you use the internal signing provider, its `signingStore` holds private keys. Put it on @@ -38,16 +70,265 @@ instead. See [Signing providers](/integrations/wallet-gateway/operate/signing-pr ## Helm -For Kubernetes, deploy with Helm and back the Wallet Gateway with a managed PostgreSQL instance. -Supply the configuration through a ConfigMap and secrets through a Kubernetes Secret so -sensitive values never live in the chart. +An official Helm chart is available for Kubernetes. The full +[values schema](https://github.com/digital-asset/wallet-gateway/blob/main/charts/wallet-gateway/values.schema.json) +is published, but the key point is that the Wallet Gateway is configured through the top-level +`config:` key in `values.yaml`, specified as YAML but using the same schema as `config.json`. + +### Signing chart values (`signing: {}`) + +The chart's `signing` block configures **optional** external signing drivers (Blockdaemon, DFNS, +Fireblocks). Leaving it empty is the common case for participant-based signing: + +```yaml +signing: {} +``` + +You do not need participant-specific fields under `signing` when the participant node handles +keys. Add entries only to enable an external custody provider: + +```yaml +signing: + # optional, define to enable blockdaemon integration -- or omit + blockdaemon: + apiUrl: 'http://localhost:5080/api/cwp/canton' + apiKeyRef: + name: 'blockdaemon-creds' + key: 'api-key' + # optional, define to enable fireblocks integration -- or omit + fireblocks: + apiKeyRef: + name: 'fireblocks-creds' + key: 'fb-api-key' + secretRef: + name: 'fireblocks-creds' + key: 'fb-secret' +``` + +### OAuth secrets from Kubernetes secrets (`oauthSecrets`) + +The chart can inject OAuth client secrets from Kubernetes secrets by mapping an environment +variable name to a secret reference, then referencing that variable from a network's auth config: + +```yaml +oauthSecrets: + # map a kubernetes secret to a Wallet Gateway network auth config + MY_OAUTH2_CLIENT_SECRET: + secretRef: + name: 'my-oauth' + key: 'client-secret' + +config: + networks: + - id: 'my-network' + adminAuth: + # should correlate to a secret provided in oauthSecrets + clientSecretEnv: 'MY_OAUTH2_CLIENT_SECRET' +``` + +## Production configuration highlights + +Read the [Configuration reference](/integrations/wallet-gateway/reference/configuration-reference) +first for the complete breakdown. The following (incomplete) YAML highlights fields worth setting +for a production deployment: + +```yaml +kernel: + # Publicly accessible URL users connect to. Subpath routing is supported. + publicUrl: 'https://wallet.example.com/subpath' +server: + # In Helm/k8s, keep the default 3030 and route internally from your + # Ingress/LoadBalancer (exposed on 443) to the pod's port. Terminate TLS at the cluster edge. + port: 3030 + # Origins for the set of web dApps allowed to call the dApp API. + allowedOrigins: + - 'https://dapp1.example.com' + - 'https://dapp2.example.com' + # Default (5mb) may need bumping for large contract payloads. + requestSizeLimit: '5mb' + # Default 10000 requests / minute / IP. Bump if you hit HTTP 429 during regular use. + requestRateLimit: 10000 +bootstrap: + networks: + - adminAuth: + # For production, inject OAuth secrets via the environment (secrets manager), + # naming the environment variable to read for this network. + clientSecretEnv: 'OAUTH2_CLIENT_SECRET' +``` + +### Signing provider environment variables + +Besides the dynamic `clientSecretEnv` variables in config, external signing providers read a few +static environment variables: + +| Provider | Variable | Description | +| --- | --- | --- | +| Fireblocks | `FIREBLOCKS_API_KEY` | API key for the Fireblocks integration. | +| Fireblocks | `FIREBLOCKS_SECRET` | Secret for the Fireblocks integration. | +| Blockdaemon | `BLOCKDAEMON_API_KEY` | API key for the Blockdaemon integration. | +| Blockdaemon | `BLOCKDAEMON_API_URL` | URL for the Blockdaemon API. | + +See [Signing providers](/integrations/wallet-gateway/operate/signing-providers) for more. + +## Database persistence + +### SQLite + +The default config uses `sqlite`, which is fine for evaluation and short-lived environments, but +**PostgreSQL is recommended for production**. SQLite stores data in local files; without a +persistent volume, all sessions and wallet state are lost when the pod is recreated. Point the +stores at a path inside the container: + +```yaml +# config YAML for Helm, or equivalent config.json +signingStore: + connection: + type: 'sqlite' + database: '/data/signing_store.sqlite' +store: + connection: + type: 'sqlite' + database: '/data/store.sqlite' +``` + +Then mount a volume for that path: + +```shell +docker run -p 3030:3030 \ + -v ${PWD}/config.json:/app/config.json:ro \ + -v ${PWD}/data:/data \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: +``` + +### PostgreSQL + +Point the store at a PostgreSQL instance: + +```yaml +# config YAML for Helm, or equivalent config.json +store: + connection: + type: 'postgres' + host: '' + port: 5432 + database: '' + user: '' + password: '' +``` + +For TLS to PostgreSQL, add an `ssl` block (passed through to the Node.js `pg` driver). See +[PostgreSQL over TLS/SSL](/integrations/wallet-gateway/reference/configuration-reference#postgresql-over-tls-ssl). + +### Local PostgreSQL over TLS (Docker) + +For local development you can run PostgreSQL in Docker with TLS enabled and point the Wallet +Gateway's `store` / `signingStore` at it. + + +This configures TLS **between the Wallet Gateway and PostgreSQL**. It does not configure HTTPS +for browsers. For browser/client HTTPS, terminate TLS in your reverse proxy or ingress and set +`kernel.publicUrl` to the external `https://...` URL. + + +**1. Create TLS files for Postgres (self-signed):** + +```bash +mkdir -p .dev/postgres-tls +cd .dev/postgres-tls + +# server key + cert (CN=localhost) +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout server.key -out server.crt -days 365 \ + -subj "/CN=localhost" + +# Postgres requires strict key perms +chmod 600 server.key + +# pg_hba: allow local socket (for init), enforce TLS for TCP +cat > pg_hba.conf <<'EOF' +# TYPE DATABASE USER ADDRESS METHOD +local all all scram-sha-256 +hostssl all all 0.0.0.0/0 scram-sha-256 +hostssl all all ::/0 scram-sha-256 +EOF + +cd ../.. +``` + +**2. Run Postgres with TLS enabled:** + +```bash +docker rm -f local-postgres 2>/dev/null || true + +docker run --name local-postgres \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=app_db \ + -e POSTGRES_INITDB_ARGS="--auth-host=scram-sha-256 --auth-local=scram-sha-256" \ + -p 5432:5432 \ + -v "$PWD/.dev/postgres-tls/server.crt:/var/lib/postgresql/server.crt:ro" \ + -v "$PWD/.dev/postgres-tls/server.key:/var/lib/postgresql/server.key:ro" \ + -v "$PWD/.dev/postgres-tls/pg_hba.conf:/var/lib/postgresql/pg_hba.conf:ro" \ + -d postgres:16 \ + -c ssl=on \ + -c ssl_cert_file=/var/lib/postgresql/server.crt \ + -c ssl_key_file=/var/lib/postgresql/server.key \ + -c hba_file=/var/lib/postgresql/pg_hba.conf +``` + +**3. Verify TLS works:** + +```bash +docker exec -e PGPASSWORD=postgres local-postgres \ + psql "host=127.0.0.1 port=5432 user=postgres dbname=app_db sslmode=require" \ + -c "select current_setting('ssl') as ssl_on;" +``` + +Expected: `ssl_on` = `on`. + +**4. Configure the Wallet Gateway stores to use TLS:** + +```json +{ + "store": { + "connection": { + "type": "postgres", + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "database": "app_db", + "ssl": { "rejectUnauthorized": false } + } + }, + "signingStore": { + "connection": { + "type": "postgres", + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "database": "app_signing_db", + "ssl": { "rejectUnauthorized": false } + } + } +} +``` + +For production, prefer certificate verification with `rejectUnauthorized: true` and provide your +CA bundle via `ssl.ca`. See +[PostgreSQL over TLS/SSL](/integrations/wallet-gateway/reference/configuration-reference#postgresql-over-tls-ssl). -## Deployment manifests +## Logging -The Wallet Gateway repository maintains the reference Docker and Helm manifests and the -step-by-step deployment guide: +Enable JSON logging with the `--log-format` flag (`pretty` is the default): -- [Deployment guide](https://github.com/canton-network/wallet-gateway/blob/82ec39c9/docs/dapp-building/wallet-gateway/deployment/index.md) +```shell +docker run -p 3030:3030 \ + -v ${PWD}/config.json:/app/config.json:ro \ + ghcr.io/digital-asset/wallet-gateway/docker/wallet-gateway: \ + --log-format=json +``` ## After deploying diff --git a/docs-main/integrations/wallet-gateway/operate/networks-and-identity.mdx b/docs-main/integrations/wallet-gateway/operate/networks-and-identity.mdx index 0296b27bf..96636f60f 100644 --- a/docs-main/integrations/wallet-gateway/operate/networks-and-identity.mdx +++ b/docs-main/integrations/wallet-gateway/operate/networks-and-identity.mdx @@ -66,7 +66,7 @@ Generates JWTs locally using a secret. Convenient for development, not for produ } } ``` -### Identity Provider Management in the Wallet Gateway +### Identity provider management This guide is only for parties with administrator access. 1. Select the hamburger icon on the top right and click **Identity Provider**. 2. Review the list of available identity providers. @@ -176,7 +176,55 @@ supply them through environment variables or a secret manager. See the [Configuration reference](/integrations/wallet-gateway/reference/configuration-reference#secrets-and-environments). -### Network Management in the Wallet Gateway +### `auth` vs `adminAuth` + +Each network defines two independent OAuth configurations: + +| | `auth` | `adminAuth` | +| --- | --- | --- | +| Typical method | `authorization_code` | `client_credentials` | +| Used by | End users logging into the User UI; ledger reads/writes in normal operation | Background jobs: automatic wallet sync on first login, party allocation | +| Client ID | User-facing OAuth client | Service account / M2M client (often separate) | +| Must match validator | `audience` and scopes must match ledger expectations | Same | + +**Can `clientId` be the same?** Yes, if one OAuth client supports both the authorization-code and +client-credentials flows with the right scopes. Many deployments use a dedicated client for `auth` +and either reuse it or use a separate `adminAuth` client; either is valid. + +**Does `auth.clientId` need a wallet or party?** No. The client only needs to obtain tokens for +users who already have ledger rights (or will receive them through your IDP). + +**When is `adminAuth` required?** For automatic wallet sync when a user logs in and the Wallet +Gateway has no wallets stored yet. Without valid `adminAuth`, `addSession` may fail with HTTP 500. +If you only create wallets manually and never rely on sync, some flows may still call admin APIs, +so configure `adminAuth` unless you know your deployment does not need it. + +Store `adminAuth` secrets via `clientSecretEnv` and Kubernetes secrets (Helm `oauthSecrets`) +rather than plain text in config files. + +### Service account automation (`serviceAccountAuth`) + +To let backend jobs submit transactions with an API key, add a `serviceAccountAuth` block +(machine-to-machine `client_credentials`) to the network. The Wallet Gateway uses it to obtain +ledger tokens when an API key request arrives, so a `prepareExecute` call runs prepare, sign, and +execute straight through. End users still sign in with the network's normal `auth`. + +```json +{ + "serviceAccountAuth": { + "method": "client_credentials", + "clientId": "wallet-gateway-automation", + "clientSecretEnv": "WG_SERVICE_ACCOUNT_CLIENT_SECRET", + "audience": "https://canton.network.global", + "scope": "openid daml_ledger_api offline_access" + } +} +``` + +See [Service account automations](/integrations/wallet-gateway/automation/service-account-automations) +for the full setup. + +### Network management This guide is only for parties with administrator access. 1. Select the hamburger icon on the top right and choose **Network**. 2. Review the list of available networks. @@ -191,4 +239,4 @@ without restarting, using the User API (`addNetwork`, `removeNetwork`, `addIdp`, `listNetworks`, `listIdps`) or the **Settings** page in the User UI. Runtime changes are stored in the database, so back it up to avoid losing them. See the [User API](/integrations/wallet-gateway/reference/user-api) and -[Manage wallets](/integrations/wallet-gateway/use/manage-wallets). +[Party management](/integrations/wallet-gateway/use/party-management). diff --git a/docs-main/integrations/wallet-gateway/operate/signing-providers.mdx b/docs-main/integrations/wallet-gateway/operate/signing-providers.mdx index 4079ad0ff..f8ee320b6 100644 --- a/docs-main/integrations/wallet-gateway/operate/signing-providers.mdx +++ b/docs-main/integrations/wallet-gateway/operate/signing-providers.mdx @@ -56,7 +56,7 @@ node, which signs it using the party's key from the participant's keystore. Enterprise-grade, HSM-backed key management and signing from Fireblocks. Keys stay in Fireblocks' secure infrastructure. -1. Complete steps 1-3 from the [Fireblocks signing documentation](https://github.com/canton-network/wallet-gateway/tree/main/core/signing-fireblocks). +1. Complete steps 1-3 from the [Fireblocks signing documentation](https://github.com/canton-network/wallet/tree/main/core/signing-fireblocks). 2. Supply `FIREBLOCKS_API_KEY` with your Fireblocks API key (from the `API User (ID)` column in the Fireblocks API users table). 3. Supply `FIREBLOCKS_SECRET` with your Fireblocks API secret key. diff --git a/docs-main/integrations/wallet-gateway/overview.mdx b/docs-main/integrations/wallet-gateway/overview.mdx index b0bb78066..a6f8bcabd 100644 --- a/docs-main/integrations/wallet-gateway/overview.mdx +++ b/docs-main/integrations/wallet-gateway/overview.mdx @@ -120,17 +120,7 @@ verifying the three endpoints. ## Where to go next - - - Install, configure, run, and verify a Wallet Gateway. - - - Understand the configuration file, store, and server settings. - - - Choose where transaction signing and key custody happen. - - - Drive the Wallet Gateway from scripts with the User API. - - +- [Quickstart](/integrations/wallet-gateway/quickstart): Install, configure, run, and verify a Wallet Gateway. +- [Configure the Wallet Gateway](/integrations/wallet-gateway/operate/configure): Understand the configuration file, store, and server settings. +- [Signing providers](/integrations/wallet-gateway/operate/signing-providers): Choose where transaction signing and key custody happen. +- [API reference](/integrations/wallet-gateway/reference/user-api): Drive the Wallet Gateway from scripts with the User API. diff --git a/docs-main/integrations/wallet-gateway/quickstart.mdx b/docs-main/integrations/wallet-gateway/quickstart.mdx index b9305903f..ca3d47f95 100644 --- a/docs-main/integrations/wallet-gateway/quickstart.mdx +++ b/docs-main/integrations/wallet-gateway/quickstart.mdx @@ -89,17 +89,7 @@ autocompletion. ## Next steps - - - All configuration options for kernel, server, and store. - - - Connect to a validator and set up authentication. - - - Create and manage wallets through the User UI or User API. - - - Run the Wallet Gateway with Docker or Helm. - - +- [Configure the Wallet Gateway](/integrations/wallet-gateway/operate/configure): All configuration options for kernel, server, and store. +- [Networks & identity providers](/integrations/wallet-gateway/operate/networks-and-identity): Connect to a validator and set up authentication. +- [Party management](/integrations/wallet-gateway/use/party-management): Create and manage wallets through the User UI or User API. +- [Deploy](/integrations/wallet-gateway/operate/deploy): Run the Wallet Gateway with Docker or Helm. diff --git a/docs-main/integrations/wallet-gateway/reference/configuration-reference.mdx b/docs-main/integrations/wallet-gateway/reference/configuration-reference.mdx index 8c7b1074b..52bfb32ed 100644 --- a/docs-main/integrations/wallet-gateway/reference/configuration-reference.mdx +++ b/docs-main/integrations/wallet-gateway/reference/configuration-reference.mdx @@ -9,6 +9,17 @@ secrets across environments. For the config file's overall shape, see [Configure the Wallet Gateway](/integrations/wallet-gateway/operate/configure). For networks and authentication, see [Networks & identity providers](/integrations/wallet-gateway/operate/networks-and-identity). +## Full configuration schema + +For the complete JSON Schema of the configuration file, including every field and type, run: + +```bash +wallet-gateway --config-schema +``` + +Use the output for validation and IDE autocompletion; the generated schema is the authoritative +source for all configuration options. + ## Store backends The `store.connection` object selects one of three backends. @@ -25,6 +36,7 @@ Recommended for production for its robustness, concurrent access, and backup sup | `user` | yes | Database user to connect with. | | `password` | yes | Password for the database user. | | `database` | yes | Name of the database to use (must exist). | +| `ssl` | no | TLS settings, passed through to the Node.js `pg` driver. See [PostgreSQL over TLS/SSL](#postgresql-over-tls-ssl). | ```json { @@ -41,6 +53,55 @@ Recommended for production for its robustness, concurrent access, and backup sup } ``` +### PostgreSQL over TLS/SSL + +If your PostgreSQL server requires TLS (common in managed databases and hardened deployments), +add an `ssl` object under `store.connection` and, if used, `signingStore.connection`. The Wallet +Gateway only configures client-side TLS; it never needs the server's private key. + +**Local testing** (self-signed server cert, encryption only): set `rejectUnauthorized: false`. + +```json +{ + "store": { + "connection": { + "type": "postgres", + "host": "localhost", + "port": 5432, + "user": "postgres", + "password": "postgres", + "database": "app_db", + "ssl": { "rejectUnauthorized": false } + } + } +} +``` + +**Production** (verify the server certificate): set `rejectUnauthorized: true` and provide the CA +certificate PEM that signed the server certificate via `ssl.ca`. + +```json +{ + "store": { + "connection": { + "type": "postgres", + "host": "db.example.com", + "port": 5432, + "user": "wallet_gateway", + "password": "secure-password", + "database": "wallet_gateway_db", + "ssl": { + "rejectUnauthorized": true, + "ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n" + } + } + } +} +``` + +For a local Docker-based Postgres-over-TLS walkthrough, see +[Deploy](/integrations/wallet-gateway/operate/deploy#local-postgresql-over-tls-docker). + ### SQLite Suitable for single-instance deployments and local development. Stores all data in one file. diff --git a/docs-main/integrations/wallet-gateway/reference/dapp-api.mdx b/docs-main/integrations/wallet-gateway/reference/dapp-api.mdx index 33af7e8e3..3b4a297a5 100644 --- a/docs-main/integrations/wallet-gateway/reference/dapp-api.mdx +++ b/docs-main/integrations/wallet-gateway/reference/dapp-api.mdx @@ -21,7 +21,7 @@ reference for methods, events, and error handling. ## Full specification The complete OpenRPC specification is available at -[openrpc-dapp-api.json](https://github.com/canton-network/wallet-gateway/blob/main/api-specs/openrpc-dapp-api.json). +[openrpc-dapp-api.json](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-dapp-api.json). ## Real-time events (SSE) diff --git a/docs-main/integrations/wallet-gateway/reference/user-api.mdx b/docs-main/integrations/wallet-gateway/reference/user-api.mdx index 772bad8fc..fd1d7e8bb 100644 --- a/docs-main/integrations/wallet-gateway/reference/user-api.mdx +++ b/docs-main/integrations/wallet-gateway/reference/user-api.mdx @@ -18,7 +18,9 @@ directly from scripts, a backend, or a custom UI. | Sessions | `addSession()` | Create a new session (unauthenticated, used for the initial connection). | | | `removeSession()` | End the current session. | | | `listSessions()` | List sessions for the current user. | -| Networks | `listNetworks()` | List all configured networks. | +| Networks | `listNetworks()` | List configured networks (public metadata only, no auth secrets). | +| | `getNetwork()` | Get the full network configuration including auth (admin only). | +| | `selfSignedAccessToken()` | Mint a self-signed JWT for login (unauthenticated). | | | `addNetwork()` | Add a new network configuration. | | | `removeNetwork()` | Remove a network configuration. | | Identity providers | `listIdps()` | List all identity providers. | @@ -49,11 +51,12 @@ connection: - `addSession()` - `listNetworks()` - `listIdps()` +- `selfSignedAccessToken()` ## Full specification The complete OpenRPC specification is available at -[openrpc-user-api.json](https://github.com/canton-network/wallet-gateway/blob/main/api-specs/openrpc-user-api.json). +[openrpc-user-api.json](https://github.com/canton-network/wallet/blob/main/api-specs/openrpc-user-api.json). ## Rate limiting diff --git a/docs-main/integrations/wallet-gateway/use/approve-and-sign.mdx b/docs-main/integrations/wallet-gateway/use/approve-and-sign.mdx index 70a992782..c41a6e7a2 100644 --- a/docs-main/integrations/wallet-gateway/use/approve-and-sign.mdx +++ b/docs-main/integrations/wallet-gateway/use/approve-and-sign.mdx @@ -9,7 +9,7 @@ signing provider for signing. This keeps approval and key custody under your con keys never reach the dApp. This guide covers what you see on the **Approve** page, how to approve or reject, and how to track a transaction afterwards. -## How a Transaction Reaches You +## How a transaction reaches you A dApp submits a transaction through the [dApp SDK](/sdks-tools/sdks/dapp-sdk/overview), which calls the Wallet Gateway's dApp API. The Wallet Gateway prepares it against your validator, @@ -50,7 +50,7 @@ open in a popup window if the dApp triggered it). There you can see: transaction detail -## Approve or Reject +## Approve or reject - **Approve** — the Wallet Gateway hands the prepared transaction to the wallet's [signing provider](/integrations/wallet-gateway/operate/signing-providers), which signs it, @@ -71,7 +71,7 @@ through its signing provider and submits to the ledger — it cannot be undone. wrong, reject it. -## Where Signing Happens +## Where signing happens Signing is delegated per wallet to the signing provider you chose when you created it — a participant node, an external custody provider (Fireblocks, Blockdaemon, DFNS), or the internal @@ -79,7 +79,7 @@ store for development. Your keys stay with that provider; approving in the UI au provider to sign, but the key never passes through the dApp or the browser. See [Signing providers](/integrations/wallet-gateway/operate/signing-providers). -## Track a Transaction +## Track a transaction Open the **Transactions** page to follow a transaction through its lifecycle and inspect its details. Each transaction moves through these states: @@ -95,19 +95,9 @@ Use this page to confirm a transaction executed, or to see why one failed. If ex to start or never complete, see [Troubleshooting](/integrations/wallet-gateway/operate/troubleshooting). -## Next Steps - - - - Log in and create, organize, and remove wallets in the User UI. - - - Sign and execute transactions programmatically. - - - Choose where signing and key custody happen. - - - See how dApps request transactions through the dApp API. - - +## Next steps + +- [Party management](/integrations/wallet-gateway/use/party-management): Log in and create, organize, and remove wallets in the User UI. +- [Automate with the User API](/integrations/wallet-gateway/automation/automate-with-user-api): Sign and execute transactions programmatically. +- [Signing providers](/integrations/wallet-gateway/operate/signing-providers): Choose where signing and key custody happen. +- [dApp API](/integrations/wallet-gateway/reference/dapp-api): See how dApps request transactions through the dApp API. diff --git a/docs-main/integrations/wallet-gateway/use/party-management.mdx b/docs-main/integrations/wallet-gateway/use/party-management.mdx index eb22c2f3e..9ebd5dab1 100644 --- a/docs-main/integrations/wallet-gateway/use/party-management.mdx +++ b/docs-main/integrations/wallet-gateway/use/party-management.mdx @@ -7,7 +7,7 @@ End users work with the Wallet Gateway through its **User UI**: a web app the Wa serves at its root URL. From it they log in, create and manage parties, review transactions, and approve requests from dApps. This guide covers logging in and managing parties in the UI. To drive the same operations from a script or backend, see -[Automate with the User API](/integrations/wallet-gateway/use/automate-with-user-api). +[Automate with the User API](/integrations/wallet-gateway/automation/automate-with-user-api). The **dApp API** is separate. dApps call it through the [dApp SDK](/sdks-tools/sdks/dapp-sdk/overview) @@ -49,7 +49,7 @@ admin manages them under **Settings** or in the configuration file. See [Networks & identity providers](/integrations/wallet-gateway/operate/networks-and-identity). -## Create a Party +## Create a party A wallet is a Canton **party** the Wallet Gateway manages for you on one network, signed by one signing provider. @@ -73,7 +73,7 @@ parties on different networks or custody providers side by side in the same Wall By the Parties header, you may click the refresh icon to reload your party list. If there is a party that was created outside of the Wallet Gateway, it may still appear but unable to access; showing a disabled status. -## Set Primary Party +## Set the primary party You can mark one wallet as **primary**. dApps that request your primary account receive this wallet by default, so set it to the party you transact with most. Change it at any time on the @@ -91,20 +91,20 @@ through the `accountsChanged` event and should re-read the primary account rathe it. See [dApp API](/integrations/wallet-gateway/reference/dapp-api). -## View Activities +## View activities The **Activities** page lists transactions the Wallet Gateway has prepared, signed, or executed for your parties, with their status and details. Reviewing and approving transactions that dApps request happens on the **Approve** page — see [Approve & sign transactions](/integrations/wallet-gateway/use/approve-and-sign). -## Changing Wallet Providers +## Changing wallet providers 1. Select the hamburger icon on the top right and **Logout**. 2. Click **Login** or **Connect** in the dApp UI to trigger the pop-up. 3. Select a new provider to log into and follow its instructions. -## Sessions and Logout +## Sessions and logout When you log in, the Wallet Gateway issues a **session** (a JWT) that authorizes your later calls to the User and dApp APIs. Sessions are created on login and stored in the Wallet @@ -120,19 +120,9 @@ loses it, active sessions may be invalidated and users must log in again. See [Networks & identity providers](/integrations/wallet-gateway/operate/networks-and-identity). -## Next Steps - - - - Review, approve, and track transactions dApps request. - - - Drive wallet setup and transactions from scripts or a backend. - - - Understand the networks and login options an operator configures. - - - See where each wallet's keys live and who signs. - - +## Next steps + +- [Approve & sign transactions](/integrations/wallet-gateway/use/approve-and-sign): Review, approve, and track transactions dApps request. +- [Automate with the User API](/integrations/wallet-gateway/automation/automate-with-user-api): Drive wallet setup and transactions from scripts or a backend. +- [Networks & identity providers](/integrations/wallet-gateway/operate/networks-and-identity): Understand the networks and login options an operator configures. +- [Signing providers](/integrations/wallet-gateway/operate/signing-providers): See where each wallet's keys live and who signs.