Skip to content
GitHub

Create an interactive grant

This guide describes how to create an interactive grant. An interactive grant requires interaction from a user, typically your client’s end-user, before the grant can be issued.

For outgoing payment grants, the resource owner (e.g., your client’s end-user) must explicitly consent to the creation of an outgoing payment resource. As such, Open Payments requires outgoing payment grants be interactive.

Grants for creating incoming payment and quote resources are non-interactive by default; however, you can choose to make one or both interactive to fit your implementation needs.

Dependencies

Endpoints

Create an interactive grant

Additional configuration

Add "type": "module" to package.json

Add the following to tsconfig.json

{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022"
}
}

Steps

1. Import dependencies

Import createAuthenticatedClient from the Open Payments SDK package.

Import dependencies

import {
  createAuthenticatedClient,
  isPendingGrant,
} from "@interledger/open-payments";

2. Create and initialize an authenticated Open Payments client

Create an OP authenticated client by providing the following properties:

  • walletAddressURL: your OP-enabled wallet address that your client will use to authenticate itself to one or more authorization servers.
  • privateKey: the EdDSA-Ed25519 key or preferably the absolute or relative file path to the key that is bound to your wallet address. A public key signed with this private key must be made available as a public JWK document at {walletAddressUrl}/jwks.json url.
  • keyId: the identifier of the private key and the corresponding public key.

Initialize Open Payments client

const client = await createAuthenticatedClient({
  walletAddressUrl: WALLET_ADDRESS,
  privateKey: PRIVATE_KEY_PATH,
  keyId: KEY_ID,
});

3. Get payee’s wallet address information

Get wallet address information

const walletAddress = await client.walletAddress.get({
  url: WALLET_ADDRESS,
});

4. Request an interactive grant

Indicate your client is capable of directing the end-user to a URI to start an interaction. We’ll use an outgoing payment grant as an example.

  1. Add the interact object to your grant request.
  2. Specify redirect as the start mode. Open Payments only supports redirect at this time. The redirect URI will be provided by the authorization server in its response.

Now, indicate your client can receive a signal from the authorization server when the interaction has finished.

  1. Add the finish object within the interact object.
  2. Specify redirect as the method. Open Payments only supports redirect at this time.
  3. Specify the uri that the authorization server will send the end-user back to when the interaction has finished.
  4. Create a nonce for your client to use to verify the authorization server is the same entity throughout the entire process. The nonce can be any random, hard-to-guess string.

Request outgoing payment grant

const grant = await client.grant.request(
  {
    url: walletAddress.authServer,
  },
  {
    access_token: {
      access: [
        {
          identifier: walletAddress.id,
          type: "outgoing-payment",
          actions: ["list", "list-all", "read", "read-all", "create"],
          limits: {
            debitAmount: DEBIT_AMOUNT,
            receiveAmount: RECEIVE_AMOUNT,
          },
        },
      ],
    },
    interact: {
      start: ["redirect"],
      finish: {
        method: "redirect",
        uri: "http://localhost:3344",
        nonce: NONCE,
      },
    },
  },
);

Check grant state

if (!isPendingGrant(grant)) {
  throw new Error("Expected interactive grant");
}

5. Start interaction with the end user

Once your client receives the authorization server’s response, it must send the end-user to the interact.redirect URI contained in the response. This starts the interaction flow.

Example response

{
"interact": {
"redirect": "https://auth.rafiki.money/4CF492MLVMSW9MKMXKHQ",
"finish": "4105340a-05eb-4290-8739-f9e2b463bfa7"
},
"continue": {
"access_token": {
"value": "33OMUKMKSKU80UPRY5NM"
},
"uri": "https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ",
"wait": 30
}
}

6. Finish interaction with the end user

The end-user interacts with the authorization server through the server’s interface and approves or denies the grant.

Provided the end-user approves the grant, the authorization server:

  • Sends the end-user to your client’s previously defined finish.uri. The means by which the server sends the end-user to the URI is out of scope, but common options include redirecting the end-user from a web page and launching the system browser with the target URI.
  • Secures the redirect by adding a unique hash, allowing your client to validate the finish call, and an interaction reference as query parameters to the URI.

7. Request a grant continuation

Add the authorization server’s interaction reference, as the interact_ref value, to the body of your continuation request.

Continue grant

const grant = await client.grant.continue(
  {
    accessToken: CONTINUE_ACCESS_TOKEN,
    url: CONTINUE_URI,
  },
  {
    interact_ref: interactRef,
  },
);

Issue the request to the continue uri supplied in the authorization server’s initial grant response. For example:

POST https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ

A successful continuation response from the authorization server provides your client with an access token and other information necessary to continue the transaction.

Example response

{
"access_token": {
"value": "OS9M2PMHKUR64TB8N6BW7OZB8CDFONP219RP1LT0",
"manage": "https://auth.rafiki.money/token/dd17a202-9982-4ed9-ae31-564947fb6379",
"expires_in": 3600,
"access": [
{
"type": "outgoing-payment",
"actions": [
"create",
"read",
"read-all",
"list",
"list-all"
],
"identifier": "https://ilp.rafiki.money/alice",
"limits": {
"receiver": "https://ilp.rafiki.money/bob/incoming-payments/48884225-b393-4872-90de-1b737e2491c2",
"interval": "R12/2019-08-24T14:15:22Z/P1M",
"debitAmount": {
"value": "500",
"assetCode": "USD",
"assetScale": 2
}
}
}
]
},
"continue": {
"access_token": {
"value": "33OMUKMKSKU80UPRY5NM"
},
"uri": "https://auth.rafiki.money/continue/4CF492MLVMSW9MKMXKHQ",
"wait": 30
}
}