Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/giant-dots-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@interledger/open-payments': minor
---

New union client type to support directed identity. Exported grant type guards and added new, more explicit grant types.
7 changes: 6 additions & 1 deletion packages/open-payments/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ export {
OutgoingPaymentGrantSpentAmounts,
PendingGrant,
Grant,
GrantWithAccessToken,
GrantWithSubject,
isPendingGrant,
isFinalizedGrant,
isFinalizedGrantWithAccessToken,
isFinalizedGrantWithSubject,
JWK,
JWKS,
PaginationArgs,
Expand All @@ -22,7 +26,8 @@ export {
AccessAction,
AccessToken,
AccessItem,
Subject
Subject,
Client
} from './types'

export {
Expand Down
43 changes: 41 additions & 2 deletions packages/open-payments/src/openapi/generated/auth-server-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,31 @@ export interface components {
};
/**
* client
* @description Wallet address of the client instance that is making this request.
* @description 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.
*/
client: string;
client: string | {
/**
* Format: uri
* @description Wallet address of the client instance that is making this request.
*/
walletAddress: string;
} | {
jwk: components["schemas"]["json-web-key"];
};
/**
* continue
* @description If the AS determines that the request can be continued with additional requests, it responds with the continue field.
Expand Down Expand Up @@ -307,6 +323,29 @@ export interface components {
format: "uri";
}[];
};
/**
* Ed25519 Public Key
* @description A JWK representation of an Ed25519 Public Key
*/
"json-web-key": {
kid: string;
/**
* @description The cryptographic algorithm family used with the key. The only allowed value is `EdDSA`.
* @enum {string}
*/
alg: "EdDSA";
/** @enum {string} */
use?: "sig";
/** @enum {string} */
kty: "OKP";
/**
* @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`.
* @enum {string}
*/
crv: "Ed25519";
/** @description The base64 url-encoded public key. */
x: string;
};
};
responses: never;
parameters: never;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -894,8 +894,8 @@ export interface operations {
};
content: {
"application/json": {
spentReceiveAmount?: components["schemas"]["amount"];
spentDebitAmount?: components["schemas"]["amount"];
spentReceiveAmount: components["schemas"]["amount"] | null;
spentDebitAmount: components["schemas"]["amount"] | null;
};
};
};
Expand Down
19 changes: 14 additions & 5 deletions packages/open-payments/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,18 @@ export const getASPath = <P extends keyof ASPaths>(path: P): string =>
path as string

export type Subject = ASComponents['schemas']['subject']
export type Client =
| string
| { walletAddress: string; jwk?: never }
| { jwk: JWK; walletAddress?: never }
Comment on lines +88 to +91
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

New client type for the directed identity support.

Directed identity changes and the grant spent amount fixes were both included in new 1.3 version of spec. Perhaps they should have been more granular versions (grant spent amount fix, then directed identity).

If this is released before Rafiki is updated then the jwk option wont work. However, it may also give clients more time to update to the new pattern (string -> {walletAddress: string}. I guess if we wanted we could simply omit the { jwk: JWK; walletAddress?: never } item here for now and include once implemented in rafiki?

Copy link
Copy Markdown

@njlie njlie Feb 10, 2026

Choose a reason for hiding this comment

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

I assume that the "string" format is to be interpreted as a wallet address url, correct?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is correct - for backwards compatibility purposes. Should be removed at some point in favor of the object only.


export type NonInteractiveGrantRequest = {
access_token: ASOperations['post-request']['requestBody']['content']['application/json']['access_token']
client: ASOperations['post-request']['requestBody']['content']['application/json']['client']
client: Client
}

type BaseGrantRequest = {
client: ASOperations['post-request']['requestBody']['content']['application/json']['client']
client: Client
interact?: ASOperations['post-request']['requestBody']['content']['application/json']['interact']
}

Expand All @@ -113,6 +117,12 @@ export type Grant = {
continue: ASComponents['schemas']['continue']
subject?: Subject
}
export type GrantWithAccessToken = Grant & {
access_token: NonNullable<Grant['access_token']>
}
export type GrantWithSubject = Grant & {
subject: NonNullable<Grant['subject']>
}
export type GrantContinuation = {
continue: ASComponents['schemas']['continue']
}
Expand Down Expand Up @@ -145,12 +155,11 @@ export const isFinalizedGrant = (

export const isFinalizedGrantWithAccessToken = (
grant: GrantContinuation | Grant
): grant is Grant & { access_token: ASComponents['schemas']['access_token'] } =>
!!(grant as Grant).access_token
): grant is GrantWithAccessToken => !!(grant as Grant).access_token

export const isFinalizedGrantWithSubject = (
grant: GrantContinuation | Grant
): grant is Grant & { subject: Subject } => !!(grant as Grant).subject
): grant is GrantWithSubject => !!(grant as Grant).subject

export type AccessIncomingActions =
ASComponents['schemas']['access-incoming']['actions']
Expand Down