Release Notes

v2.38.0

Release date: March 30, 2026

New features

Ledger picker in studio

  • Added a new ledger picker to navigate through known ledgers inside the same studio environments.
  • Ledgers are registered as known after the user successfully logs in the first time.

Token impersonation for mutation requests

Mutation requests can now be authenticated using only a JWT token in the Authorization header — no body signing required. When the ledger receives a mutation with a valid token and no body proofs, it automatically generates and injects a proof on behalf of the token's signer using the internal system.auth signer. The generated proof is marked with origin: self-signed-token so it can be distinguished from direct key-pair signatures.

Clients can also send partial proofs — proof objects that omit the public key — to attach custom metadata (such as status or labels) while letting the ledger handle signing.

Token impersonation is supported for all mutation types: create, update, drop, and add proof.

See Token impersonation for full details.

New signer factor schemas for Ledger

Introduces oauth-client-credentials and key-pair schemas for signer factors.

The key-pair schema for factors that store a cryptographic key pair (public key and encrypted private key) directly on the ledger. Supports the same key formats already used by ledger signers: ed25519-raw and rsa-der.

The oauth-client-credentials schema for factors that provision an OAuth 2.0 client identity directly on the ledger. Each factor holds a clientId and an encrypted clientSecret, which can then be used to authenticate via the client credentials grant flow. Both clientId and clientSecret are generated server-side by default; supplying your own values is only supported when the signer.factor.oauth.allowClientCredentials feature flag is enabled.

Examples creating these factors using the SDK:

const factorKeyPair = generateKeyPair()

const signerFactor = sdk.signer
    .with(signerHandle)
    .factor.init()
    .data({
      handle: factorHandle,
      signer: signerHandle,
      schema: 'key-pair',
      public: factorKeyPair.public,
      format: factorKeyPair.format,
      secret: `{{ secret.${secretKey} }}`,
    })
    .meta({
      proofs: [],
      secret: { [secretKey]: factorKeyPair.secret },
    })
    .hash()
    .sign([{ keyPair: signerKeyPair }])

  const { factor, luid, hash, meta } = await signedSignerFactor.send()
const factorKeyPair = generateKeyPair()

const signerFactor = sdk.signer
    .with(signerHandle)
    .factor.init()
    .data({
      handle: factorHandle,
      signer: signerHandle,
      schema: 'oauth-client-credentials',
    })
    .meta({
      proofs: [],
    })
    .hash()
    .sign([{ keyPair: signerKeyPair }])

  const { factor, luid, hash, meta } = await signedSignerFactor.send()

New OAuth 2.0 token endpoint (Client Credentials Grant)

Ledger now supports OAuth 2.0 client credentials authentication alongside the existing proof-based and self-signed JWT flows. This gives client applications a simpler way to authenticate without managing Ed25519/RSA keypairs or generating cryptographic proofs per request.

How it works

A ledger administrator configures an authentication policy (schema: authentication) with an oauth2 value that designates a provider signer. Each application signer is issued an oauth-client-credentials signer factor containing a clientId and clientSecret generated by the ledger.

Client applications exchange those credentials for a short-lived JWT access token using the new token endpoint.

Token endpoint

The endpoint follows RFC 6749 format.

POST /v2/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(clientId:clientSecret)>
X-Ledger: <ledger-handle>

grant_type=client_credentials

The Authorization header uses HTTP Basic authentication: clientId and clientSecret joined by : and base64-encoded. The request body is a single URL-encoded form field: grant_type=client_credentials. Optionally, both client_id and client_secret may be included in the payload instead of the authorization header.

Response

{
  "access_token": "<jwt>",
  "token_type": "Bearer",
  "expires_in": 3600
}

Use the returned token as a standard bearer token on subsequent API requests:

Authorization: Bearer <access_token>

Improvements

Proof timeline for reports in studio

Now a proof timeline is available in the report details page. Similar to intents, it shows the timeline of status changes and error details if available

Improved studio navigation

Links in the navigation sidebar are now labeled instead of nested. This means pages are now easier to access.

Forwarding proof status reflects record state

When AnchorsForwardingService appends a forwarding proof, it now copies the status from the latest existing proof in meta.proofs instead of using a hardcoded 'forwarded' value. This ensures the forwarded record accurately reflects the state of the original record at the time of forwarding.

Simplified studio setup layout

  • Less inputs are required when running Studio setup layout from CLI
  • Member is created as active instead of sending an invitation. Password is prompted during layout execution
  • A new studio-owner access policy will be created to grant global access to that user instead of prompting for owner circles.

Enforced time range filter on Intents and Anchors listing pages

Studio now enforces a default time filter on the Intents and Anchors listing pages. The filter defaults to showing records from the last day and can be adjusted up to a maximum of 30 days. The "after" filter is pinned and cannot be removed, though users can change its value or reset it to the default.

Fixes

  • [ledger] Intent processing now validates that the symbol referenced in each claim exists on the ledger. If the symbol is not found, the intent is immediately aborted with reason core.symbol-invalid instead of continuing with unresolved symbol data.
  • [ledger] Requests where the x-ledger header and the ledger subdomain identify different tenants now return a clear api.request-tenant-mismatch error (HTTP 422) instead of failing silently or with an unrelated error.
  • [studio] Show "loading" message when loading circles and signers relationships in their respective detail sheets.
  • [studio] Replaced action name with N/A for destroy, issue and limit claims for clarity.
  • [studio] Properly render visible status label for unknown status for anchors, policies and members
  • [ledger] Fixes lack of support for various entities when filtering using data.schema.

On this page