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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ schema-directed encoding. Shape, range, and JSON-encoding failures throw
`ArgumentEncodingException`, whose `kind()`, `path()`, and `expected()` fields contain structural
context without including rejected values.

Build the high-level facade from a loaded protocol, select an optional profile, bind parties, and
resolve through the same type-directed argument path:

```java
var client = protocol.client()
.trpEndpoint(java.net.URI.create("http://localhost:8164"))
.withProfile("preprod")
.withHeader("Authorization", "Bearer ...")
.withParty("sender", land.tx3.sdk.Party.address(address))
.withEnvValue("network", "preview")
.build();

var resolved = client.tx("transfer")
.arg("quantity", 10_000_000)
.resolve();
```

`build()` reports missing connection settings and unknown profile or party names as
`MissingTrpEndpointException`, `UnknownProfileException`, and `UnknownPartyException`.
`Tx3Client.tx()` reports `UnknownTransactionException`, while a missing required argument at
resolve time reports `ResolutionException`. Generated clients seed the same builder with
`Tx3ClientBuilder.fromParts(...)`, bind statically known parties with `withPartyUnchecked`, and
construct canonical values with `argTagged`; that path retains no TII or parameter schema.

## Development

These are the canonical foundation checks:
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/land/tx3/sdk/Party.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package land.tx3.sdk;

import java.util.Objects;
import java.util.Optional;

/** A named protocol participant backed by a read-only address or a signer. */
public final class Party {
private final Address address;
private final Signer signer;

private Party(Address address, Signer signer) {
this.address = Objects.requireNonNull(address, "address");
this.signer = signer;
}

/** Creates a read-only party from an address. */
public static Party address(Address address) {
return new Party(address, null);
}

/** Creates a signing party, reading its address from the signer. */
public static Party signer(Signer signer) {
Objects.requireNonNull(signer, "signer");
return new Party(Objects.requireNonNull(signer.address(), "signer address"), signer);
}

/** Returns the address injected into resolve arguments. */
public Address address() {
return address;
}

/** Returns the signer when this is a signing party. */
public Optional<Signer> signer() {
return Optional.ofNullable(signer);
}
}
11 changes: 10 additions & 1 deletion src/main/java/land/tx3/sdk/Profile.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ public final class Profile {
private final Map<String, JsonNode> environment;
private final Map<String, JsonNode> parties;

Profile(Map<String, JsonNode> environment, Map<String, JsonNode> parties) {
/**
* Creates an immutable profile from environment values and party addresses.
*
* <p>This constructor is public so generated clients can embed deconstructed profile data for
* {@link Tx3ClientBuilder#fromParts} without retaining a TII document.
*/
public Profile(Map<String, JsonNode> environment, Map<String, JsonNode> parties) {
this.environment = immutableNodes(environment);
this.parties = immutableNodes(parties);
}
Expand All @@ -30,6 +36,9 @@ private static Map<String, JsonNode> immutableNodes(Map<String, JsonNode> source
}

private static Map<String, JsonNode> copyNodes(Map<String, JsonNode> source) {
if (source == null) {
throw new ValidationException("profile", "profile maps must not be null");
}
var result = new LinkedHashMap<String, JsonNode>();
source.forEach((key, value) -> result.put(key, value.deepCopy()));
return result;
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/land/tx3/sdk/Protocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,16 @@ public JsonNode json() {
return json.deepCopy();
}

/**
* Starts configuring the high-level client for this protocol.
*
* <p>The returned builder owns deconstructed copies of the protocol state; a built client does
* not retain this {@code Protocol} instance.
*/
public Tx3ClientBuilder client() {
return Tx3ClientBuilder.fromProtocol(this);
}

private static ProtocolInfo readInfo(JsonNode value, String sourceName) {
return new ProtocolInfo(
requiredText(value, "name", sourceName),
Expand Down
110 changes: 110 additions & 0 deletions src/main/java/land/tx3/sdk/Tx3Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package land.tx3.sdk;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* Immutable high-level client for resolving transactions from a deconstructed Tx3 protocol.
*
* <p>Instances are created only by {@link Tx3ClientBuilder#build()}. Profile selection is fixed at
* build time; late party binding returns a new client.
*/
public final class Tx3Client {
private final Map<String, TirEnvelope> transactions;
private final Map<String, Map<String, ParamType>> transactionParameters;
private final Map<String, Set<String>> requiredParameters;
private final Set<String> knownParties;
private final TrpClient trp;
private final Map<String, Party> parties;
private final Map<String, Object> environment;

Tx3Client(
Map<String, TirEnvelope> transactions,
Map<String, Map<String, ParamType>> transactionParameters,
Map<String, Set<String>> requiredParameters,
Set<String> knownParties,
TrpClient trp,
Map<String, Party> parties,
Map<String, Object> environment) {
this.transactions = Collections.unmodifiableMap(new LinkedHashMap<>(transactions));
this.transactionParameters = transactionParameters;
this.requiredParameters = requiredParameters;
this.knownParties = Collections.unmodifiableSet(new LinkedHashSet<>(knownParties));
this.trp = Objects.requireNonNull(trp, "trp");
this.parties = Collections.unmodifiableMap(new LinkedHashMap<>(parties));
this.environment = Collections.unmodifiableMap(new LinkedHashMap<>(environment));
}

/**
* Starts a declared transaction invocation.
*
* @throws UnknownTransactionException if the name is not declared
*/
public TxBuilder tx(String name) {
Objects.requireNonNull(name, "name");
var found =
transactions.entrySet().stream()
.filter(entry -> entry.getKey().equalsIgnoreCase(name))
.findFirst()
.orElseThrow(() -> new UnknownTransactionException(name));
var canonicalName = found.getKey();
return new TxBuilder(
canonicalName,
found.getValue(),
trp,
transactionParameters.getOrDefault(canonicalName, Map.of()),
requiredParameters.getOrDefault(canonicalName, Set.of()),
environment,
parties);
}

/**
* Late-binds a declared party and returns a new client.
*
* @throws UnknownPartyException if the name is not declared
*/
public Tx3Client withParty(String name, Party party) {
var normalized = normalize(name);
if (!knownParties.contains(normalized)) throw new UnknownPartyException(normalized);
return withPartyValue(normalized, party);
}

/** Late-binds several declared parties in iteration order; later writes win. */
public Tx3Client withParties(Map<String, Party> values) {
var updated = this;
for (var entry : Objects.requireNonNull(values, "values").entrySet()) {
updated = updated.withParty(entry.getKey(), entry.getValue());
}
return updated;
}

/**
* Late-binds a party without declared-name validation.
*
* <p>This is intended for generated wrappers with names fixed by generated methods.
*/
public Tx3Client withPartyUnchecked(String name, Party party) {
return withPartyValue(normalize(name), party);
}

private Tx3Client withPartyValue(String name, Party party) {
var updated = new LinkedHashMap<>(parties);
updated.put(name, Objects.requireNonNull(party, "party"));
return new Tx3Client(
transactions,
transactionParameters,
requiredParameters,
knownParties,
trp,
updated,
environment);
}

private static String normalize(String name) {
return Objects.requireNonNull(name, "name").toLowerCase(java.util.Locale.ROOT);
}
}
Loading
Loading