Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.2.0
1.3.0
91 changes: 88 additions & 3 deletions openapi/auth-server.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Open Payments Authorization Server
version: '1.2.0'
version: '1.3.0'
license:
name: Apache 2.0
identifier: Apache-2.0
Expand Down Expand Up @@ -164,6 +164,23 @@ paths:
- read
identifier: 'http://ilp.interledger-test.dev/bob'
client: 'https://webmonize.com/.well-known/pay'
Grant request with directed identity (JWK):
value:
access_token:
access:
- type: incoming-payment
actions:
- create
- read
identifier: 'http://ilp.interledger-test.dev/bob'
client:
jwk:
kid: example-key-1
alg: EdDSA
use: sig
kty: OKP
crv: Ed25519
x: 11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo
Grant request for subject information:
value:
subject:
Expand Down Expand Up @@ -565,15 +582,41 @@ components:
additionalProperties: false
client:
title: client
type: string
description: |-
Wallet address of the client instance that is making this request.
Client identification for grant requests.

When sending a non-continuation request to the AS, the client instance MUST identify itself by including the client field of the request and by signing the request.

Can be either:
- A wallet address string (backwards compatible format)
- An object with either `jwk` (for directed identity) or `walletAddress` (mutually exclusive)

When using a wallet address string or the `walletAddress` property:
A JSON Web Key Set document, including the public key that the client instance will use to protect this request and any continuation requests at the AS and any user-facing information about the client instance used in interactions, MUST be available at the wallet address + `/jwks.json` url.

When using the `jwk` property (directed identity approach):
The client instance provides its public key directly in the request, eliminating the need for the AS to fetch it from a wallet address. This approach enhances privacy by not requiring the client to expose a persistent wallet address identifier. The `jwk` property can only be used for non-interactive grant requests (i.e.: incoming payments).

If sending a grant initiation request that requires RO interaction, the wallet address MUST serve necessary client display information.
oneOf:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before proceeding, I would suggest pulling the updated spec in one of the SDKs and inspect the generated types.

Previous discussions:

Theoretically, the use of oneOf should be correct in this case since all properties are exclusive (not present in multiple schemas), but a sanity check would be helpful since we had our fights with the generation for mutually exclusive types.

Copy link
Contributor Author

@BlairCurrey BlairCurrey Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixed results (see typegen changes here). I will look for a better way.

This top level oneOf looks fine. How im doing the mutually exclusive object properties (jwk, wallet address) seems like it might be causing some weirdness. In general I'd prefer not to write the spec to satisfy the generator. But in this case it makes me think there is a simpler way even from an open-api perspective.

Here is what it gives us (comments stripped for brevity):

        client: string | ({
            walletAddress?: string;
            jwk?: components["schemas"]["json-web-key"];
        } & (unknown | unknown));

The issues:

1.& (unknown | unknown) is weird, but also completely benign (equivalent to just string | { ... }). Guessing it's driven by using oneOf for the required properties.
2. both properties of the object are optional. could include none or both, which isn't quite right.

What I would expect, ideally would be more like:

type Client =
  | string
  | { walletAddress: string; jwk?: never }
  | { jwk: JWK; walletAddress?: never };

Working on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm aware we have some sort of XOR ts utils in the node client. We might have to massage the types a bit and that's fine. In any case, this part is striking me as perhaps not the best way to represent mutual exclusivity (although I think it should still be valid):

          oneOf:
            - required:
                - walletAddress
            - required:
                - jwk

Copy link
Contributor Author

@BlairCurrey BlairCurrey Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be better:

Spec:

      oneOf:
        - type: string
          format: uri
          description: >
            DEPRECATED: This string format of the client wallet address is maintained
            only for backwards compatibility. Migrate to the object form.
          deprecated: true

        - type: object
          required: [walletAddress]
          properties:
            walletAddress:
              type: string
              format: uri
          additionalProperties: false

        - type: object
          required: [jwk]
          properties:
            jwk:
              $ref: '#/components/schemas/json-web-key'
          additionalProperties: false

Generated Types:

        client: string | {
            walletAddress: string;
        } | {
            jwk: components["schemas"]["json-web-key"];
        };

I also tried to include this, thinking maybe it would set jwk: never but it did not. The generated types were the same as above:

          not:
            required: [jwk]

Then in the client types we can do this:

export type Client = XOR<{ walletAddress: string }, { jwk: JWK }> | string;

const a: Client = "deprecated-string"; // ok
const b: Client = { walletAddress }; // ok
const c: Client = { jwk }; // ok
const d: Client = { walletAddress, jwk }; // type error

EDIT:

Although that technically doesnt use the generated type (just JWK). We could derive it from ASOperations['post-request']['requestBody']['content']['application/json']['client'] but that feels like we are really trying to bend over backwards. In fact it seems easier to forgo our XOR and just do:

type Client =
  | string
  | { walletAddress: string; jwk?: never }
  | { jwk: JWK; walletAddress?: never };

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go is cleaner with this new way as well.

New way:

type Client0 = string  // for the deprecated string format

type Client1 struct {
    WalletAddress string `json:"walletAddress"`
}

type Client2 struct {
    Jwk JsonWebKey `json:"jwk"`
}

Old way

type Client0 = string  // for the deprecated string format

type Client1 struct {
    Jwk           *JsonWebKey `json:"jwk,omitempty"`
    WalletAddress *string     `json:"walletAddress,omitempty"`
    union         json.RawMessage
}

type Client10 = interface{}
type Client11 = interface{}

- type: string
format: uri
description: 'DEPRECATED: This string format of the client wallet address is maintained only for backwards compatibility. Migrate to the object form with `jwk` or `walletAddress`.'
deprecated: true
- type: object
required: [walletAddress]
properties:
walletAddress:
type: string
format: uri
description: Wallet address of the client instance that is making this request.
additionalProperties: false
- type: object
required: [jwk]
properties:
jwk:
$ref: '#/components/schemas/json-web-key'
additionalProperties: false
continue:
title: continue
type: object
Expand Down Expand Up @@ -783,6 +826,48 @@ components:
maxItems: 1
required:
- sub_ids
json-web-key:
type: object
properties:
kid:
type: string
alg:
type: string
description: 'The cryptographic algorithm family used with the key. The only allowed value is `EdDSA`. '
enum:
- EdDSA
use:
type: string
enum:
- sig
kty:
type: string
enum:
- OKP
crv:
description: 'The cryptographic curve used with the key. This parameter identifies the elliptic curve (for EC keys) or the Edwards curve (for OKP keys). The only allowed value is `Ed25519`.'
type: string
enum:
- Ed25519
x:
type: string
pattern: '^[a-zA-Z0-9-_]+$'
description: The base64 url-encoded public key.
required:
- kid
- alg
- kty
- crv
- x
title: Ed25519 Public Key
description: A JWK representation of an Ed25519 Public Key
examples:
- kid: key-1
alg: EdDSA
use: sig
kty: OKP
crv: Ed25519
x: 11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo
securitySchemes:
GNAP:
name: Authorization
Expand Down
2 changes: 1 addition & 1 deletion openapi/resource-server.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Open Payments
version: '1.2.0'
version: '1.3.0'
license:
name: Apache 2.0
identifier: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion openapi/wallet-address-server.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Wallet Address API
version: '1.2.0'
version: '1.3.0'
license:
name: Apache 2.0
identifier: Apache-2.0
Expand Down