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
187 changes: 74 additions & 113 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,144 +1,105 @@
# Portal Java SDK - Documentation
# Portal Java SDK

## Introduction

A Java client for the Portal WebSocket Server, providing Nostr-based authentication and Lightning Network payment processing capabilities.

---
Java 17 SDK for the [Portal REST API](https://github.com/PortalTechnologiesInc/lib).

## Installation

1. Add the Jitpack repository to your `build.gradle`:
```groovy
repositories {
maven { url 'https://jitpack.io' }
}
```

Or if you are using Maven, add the following to your `pom.xml`:
```xml
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
```

2. Add the dependency to your `build.gradle`:
```groovy
dependencies {
implementation 'com.github.PortalTechnologiesInc:java-sdk:0.3.0'
}
```

Or if you are using Maven, add the following to your `pom.xml`:
```xml
<dependency>
<groupId>com.github.PortalTechnologiesInc</groupId>
<artifactId>java-sdk</artifactId>
<version>0.3.0</version>
</dependency>
```

3. Once you're done, you may now proceed integrating the SDK into your project.

---

## Versioning & Compatibility

The Java SDK version is kept in sync with the [Portal SDK Daemon](https://hub.docker.com/r/getportal/sdk-daemon) (`getportal/sdk-daemon`).

**Compatibility rule:** the `major.minor` version of the SDK must match the `major.minor` version of the SDK Daemon. The patch version (`x` in `0.3.x`) is independent and can differ — it only contains bug fixes.

| SDK version | SDK Daemon version |
|-------------|-------------------|
| `0.3.x` | `0.3.x` |

**Example:** SDK `0.3.0` works with `getportal/sdk-daemon:0.3.1`, but not with `getportal/sdk-daemon:0.4.0`.

When upgrading to a new `major.minor`, update both the SDK dependency and the Docker image tag together.
```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositories { maven { url = uri("https://jitpack.io") } }
}


---

## Basic Usage

### Initialization

Create an instance of `PortalSDK` by passing the websocket endpoint of your portal server:

```java
var portalSDK = new PortalSDK(wsEndpoint);
// build.gradle.kts
dependencies {
implementation("com.github.PortalTechnologiesInc:java-sdk:0.4.0")
}
```

### Connecting to the server
## Setup

Establish the WebSocket connection, then authenticate with your token:
Choose how you want to receive async results:

```java
portalSDK.connect();
portalSDK.authenticate(authToken);
// Manual polling — you call pollUntilComplete() yourself, no background threads
PortalClient client = new PortalClient(
PortalClientConfig.create("http://localhost:3000", "token")
);

// Auto-polling — background scheduler, just use done()
PortalClient client = new PortalClient(
PortalClientConfig.create("http://localhost:3000", "token")
.autoPolling(500) // poll every 500ms
);

// Webhooks — portal-rest POSTs to your server, just use done()
PortalClient client = new PortalClient(
PortalClientConfig.create("http://localhost:3000", "token")
.webhookSecret("my-secret")
);
```

## Async operations

### Sending a command
All async methods return `AsyncOperation<T>` with `streamId` (available immediately)
and `done` (`CompletableFuture<T>` that resolves when the operation completes).

You can send a command to the server by calling the `sendCommand` method.
### Manual polling

```java
portalSDK.sendCommand(request, (response, err) -> {
if(err != null) {
logger.error("error sending command: {}", err);
return;
}
logger.info("command sent successfully: {}", response);
});
AsyncOperation<InvoiceStatus> op = client.requestSinglePayment(
mainKey, List.of(),
new SinglePaymentRequestContent("Coffee", 1000, Currency.MILLISATS, null, null, null)
);

// blocks until paid/rejected/timeout
InvoiceStatus result = client.pollUntilComplete(op, PollOptions.defaults().timeoutMs(60_000));
System.out.println(result.status); // "paid", "timeout", "user_rejected", ...
```

### Basic example
### Auto-polling

```java
portalSDK.sendCommand(new CalculateNextOccurrenceRequest("weekly", System.currentTimeMillis() / 1000), (res, err) -> {
if(err != null) {
logger.error("error calculating next occurrence: {}", err);
return;
}
logger.info("next occurrence: {}", res.next_occurrence());
});
// client configured with .autoPolling(500)
AsyncOperation<InvoiceStatus> op = client.requestSinglePayment(...);
op.done().thenAccept(result -> System.out.println(result.status));
```
---

## Available Commands

Commands are implemented as specific request classes in [`src/main/java/cc/getportal/command/request/`](./src/main/java/cc/getportal/command/request/), and used via the `sendCommand()` method of the [`PortalSDK`](./src/main/java/cc/getportal/PortalSDK.java) class.
### Webhooks

Some key available commands include:

- [`AuthRequest`](./src/main/java/cc/getportal/command/request/AuthRequest.java): Authenticate using a token.
- [`KeyHandshakeUrlRequest`](./src/main/java/cc/getportal/command/request/KeyHandshakeUrlRequest.java): Get handshake URL for key and relays.
- [`RequestSinglePaymentRequest`](./src/main/java/cc/getportal/command/request/RequestSinglePaymentRequest.java): Request a single payment.
- [`MintCashuRequest`](./src/main/java/cc/getportal/command/request/MintCashuRequest.java): Mint Cashu tokens.

> See [`src/main/java/cc/getportal/command/request/`](./src/main/java/cc/getportal/command/request/) for all available commands and additional details.

To use a command, instantiate its request class and pass it to `PortalSDK.sendCommand(...)`. The full list of commands may evolve; check the request folder for the latest options.

---
```java
// client configured with .webhookSecret("my-secret")
AsyncOperation<InvoiceStatus> op = client.requestSinglePayment(...);
op.done().thenAccept(result -> System.out.println(result.status));

## Example Integrations
// in your HTTP server's POST /webhook handler:
client.deliverWebhookPayload(rawBody, request.getHeader("X-Portal-Signature"));
```

- See [portal-demo](https://github.com/PortalTechnologiesInc/portal-demo) for a Kotlin example.
## Async methods

---
| Method | Resolves to |
|--------|-------------|
| `requestSinglePayment(mainKey, subkeys, content)` | `AsyncOperation<InvoiceStatus>` |
| `requestPaymentRaw(mainKey, subkeys, content)` | `AsyncOperation<InvoiceStatus>` |
| `requestRecurringPayment(mainKey, subkeys, content)` | `AsyncOperation<RecurringPaymentResponseContent>` |
| `requestInvoice(recipientKey, subkeys, params)` | `AsyncOperation<InvoicePaymentResponse>` |
| `requestCashu(recipientKey, subkeys, mintUrl, unit, amount)` | `AsyncOperation<CashuResponseStatus>` |
| `authenticateKey(mainKey, subkeys)` | `AsyncOperation<AuthResponseData>` |
| `newKeyHandshakeUrl(staticToken, noRequest)` | `AsyncOperation<KeyHandshakeResult>` |

## Main API
## Sync methods

- `PortalSDK` - Main client class
- `PortalRequest` - Represents a request to the server
- `PortalResponse` - Represents a response from the server
- `PortalNotification` - Represents a notification from the server
`health()`, `version()`, `info()`, `fetchProfile()`, `payInvoice()`,
`closeRecurringPayment()`, `issueJwt()`, `verifyJwt()`, `addRelay()`, `removeRelay()`,
`mintCashu()`, `burnCashu()`, `sendCashuDirect()`, `calculateNextOccurrence()`,
`fetchNip05Profile()`, `getWalletInfo()`

---
## Versioning

## Support
Java SDK `major.minor` must match the portal-rest (sdk-daemon) version.

For questions or issues, see the official documentation or open an issue on the project's GitHub repository.
| Java SDK | sdk-daemon |
|----------|------------|
| 0.4.x | 0.4.x |
| 0.3.x | 0.3.x |
7 changes: 2 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {
}

group = "cc.getportal"
version = "0.3.0"
version = "0.4.0"

java {
toolchain {
Expand All @@ -23,7 +23,7 @@ publishing {

groupId = "cc.getportal"
artifactId = "portal-java-sdk"
version = "0.3.0"
version = "0.4.0"
}
}
}
Expand All @@ -40,9 +40,6 @@ dependencies {
implementation("org.slf4j:slf4j-api:2.0.17")
runtimeOnly("org.slf4j:slf4j-simple:2.0.17")

// WebSocket Client
implementation("org.java-websocket:Java-WebSocket:1.6.0")

// Json serialization
implementation("com.google.code.gson:gson:2.13.2")

Expand Down
9 changes: 9 additions & 0 deletions src/main/java/cc/getportal/AsyncOperation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cc.getportal;

import java.util.concurrent.CompletableFuture;

/**
* Wraps an async operation: the stream ID is available immediately,
* while the {@code done} future resolves when a terminal event arrives.
*/
public record AsyncOperation<T>(String streamId, CompletableFuture<T> done) {}
33 changes: 33 additions & 0 deletions src/main/java/cc/getportal/PollOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cc.getportal;

import org.jetbrains.annotations.Nullable;

import java.util.function.Consumer;

/**
* Builder-style options for polling a stream until completion.
*/
public class PollOptions {
public long intervalMs = 1000;
public long timeoutMs = 0; // 0 = no timeout
@Nullable public Consumer<StreamEvent> onEvent;

public static PollOptions defaults() {
return new PollOptions();
}

public PollOptions intervalMs(long ms) {
this.intervalMs = ms;
return this;
}

public PollOptions timeoutMs(long ms) {
this.timeoutMs = ms;
return this;
}

public PollOptions onEvent(Consumer<StreamEvent> cb) {
this.onEvent = cb;
return this;
}
}
Loading
Loading