-
Notifications
You must be signed in to change notification settings - Fork 0
Code updates for latest changes #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 23 commits
8dbaa48
9b66f67
9c0eb55
7b8fdf0
3ef59bc
24a5aac
3f7e82b
e76f8af
d5f8266
4f054bd
7cbe4c6
fc4ed8a
19e34fe
4a0c304
09ffb63
1441ae2
8115b75
a75351e
e89ad02
e93d626
51571b1
055352f
672b562
3b545e2
e89f18d
19f0efd
6c58a52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| .env | ||
| .idea | ||
| .gradle | ||
| .claude | ||
| build | ||
| src/test/resources/docker/aggregator/mongo-data |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.unicity.sdk; | ||
|
|
||
| import com.unicity.sdk.hash.DataHash; | ||
|
|
||
| public interface Hashable { | ||
| DataHash getHash(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,218 +1,82 @@ | ||
|
|
||
| package com.unicity.sdk; | ||
|
|
||
| import com.unicity.sdk.address.DirectAddress; | ||
| import com.unicity.sdk.api.AggregatorClient; | ||
| import com.unicity.sdk.api.Authenticator; | ||
| import com.unicity.sdk.api.RequestId; | ||
| import com.unicity.sdk.api.SubmitCommitmentResponse; | ||
| import com.unicity.sdk.api.SubmitCommitmentStatus; | ||
| import com.unicity.sdk.shared.hash.DataHash; | ||
| import com.unicity.sdk.shared.hash.HashAlgorithm; | ||
| import com.unicity.sdk.shared.signing.ISigningService; | ||
| import com.unicity.sdk.shared.signing.SigningService; | ||
| import com.unicity.sdk.shared.util.HexConverter; | ||
| import com.unicity.sdk.token.Token; | ||
| import com.unicity.sdk.token.TokenState; | ||
| import com.unicity.sdk.transaction.*; | ||
|
|
||
| import java.util.ArrayList; | ||
| import com.unicity.sdk.transaction.Commitment; | ||
| import com.unicity.sdk.transaction.InclusionProof; | ||
| import com.unicity.sdk.transaction.InclusionProofVerificationStatus; | ||
| import com.unicity.sdk.transaction.MintCommitment; | ||
| import com.unicity.sdk.transaction.MintTransactionData; | ||
| import com.unicity.sdk.transaction.Transaction; | ||
| import com.unicity.sdk.transaction.TransferCommitment; | ||
| import com.unicity.sdk.transaction.TransferTransactionData; | ||
| import java.util.List; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.CompletableFuture; | ||
|
|
||
| public class StateTransitionClient { | ||
| public static final byte[] MINTER_SECRET = HexConverter.decode("495f414d5f554e4956455253414c5f4d494e5445525f464f525f"); | ||
|
|
||
| protected final AggregatorClient aggregatorClient; | ||
|
|
||
| public StateTransitionClient(AggregatorClient aggregatorClient) { | ||
| this.aggregatorClient = aggregatorClient; | ||
| } | ||
|
|
||
| public <T extends MintTransactionData<?>> CompletableFuture<Commitment<T>> submitMintTransaction(T transactionData) { | ||
| return SigningService.createFromSecret(MINTER_SECRET, transactionData.getTokenId().getBytes()) | ||
| .thenCompose(signingService -> { | ||
| System.out.println("Minter tokenId: " + transactionData.getTokenId().toJSON()); | ||
| System.out.println("Minter publicKey from secret: " + HexConverter.encode(signingService.getPublicKey())); | ||
| return sendTransaction(transactionData, signingService); | ||
| }); | ||
| protected final AggregatorClient client; | ||
|
|
||
| public StateTransitionClient(AggregatorClient client) { | ||
| this.client = client; | ||
| } | ||
|
|
||
| public <T extends MintTransactionData<?>> CompletableFuture<SubmitCommitmentResponse> submitCommitment( | ||
| MintCommitment<T> commitment) { | ||
| return this.client.submitCommitment( | ||
| commitment.getRequestId(), | ||
| commitment.getTransactionData().calculateHash(), | ||
| commitment.getAuthenticator() | ||
| ); | ||
| } | ||
|
|
||
| public CompletableFuture<SubmitCommitmentResponse> submitCommitment( | ||
| Token<?> token, | ||
| TransferCommitment commitment) { | ||
| if (!commitment.getTransactionData().getSourceState().getUnlockPredicate() | ||
| .isOwner(commitment.getAuthenticator().getPublicKey())) { | ||
| throw new IllegalArgumentException( | ||
| "Ownership verification failed: Authenticator does not match source state predicate."); | ||
| } | ||
|
|
||
| public CompletableFuture<Commitment<TransactionData>> submitTransaction( | ||
| TransactionData transactionData, | ||
| ISigningService<?> signingService) { | ||
| return transactionData.getSourceState().getUnlockPredicate().isOwner(signingService.getPublicKey()) | ||
| .thenCompose(isOwner -> { | ||
| if (!isOwner) { | ||
| return CompletableFuture.failedFuture(new Exception("Failed to unlock token")); | ||
| } | ||
| return sendTransaction(transactionData, signingService); | ||
| }); | ||
| } | ||
|
|
||
| public <T extends ISerializable> CompletableFuture<Transaction<T>> createTransaction( | ||
| Commitment<T> commitment, | ||
| InclusionProof inclusionProof) { | ||
| return inclusionProof.verify(commitment.getRequestId()) | ||
| .thenCompose(status -> { | ||
| if (status != InclusionProofVerificationStatus.OK) { | ||
| return CompletableFuture.failedFuture(new Exception("Inclusion proof verification failed.")); | ||
| } | ||
|
|
||
| // For mint transactions, authenticator might be null in the inclusion proof | ||
| // This is expected behavior from the aggregator | ||
|
|
||
| // Check transaction hash if applicable | ||
| T transactionData = commitment.getTransactionData(); | ||
| DataHash txHash = null; | ||
| if (transactionData instanceof TransactionData) { | ||
| txHash = ((TransactionData) transactionData).getHash(); | ||
| } else if (transactionData instanceof MintTransactionData) { | ||
| txHash = ((MintTransactionData<?>) transactionData).getHash(); | ||
| } | ||
|
|
||
| // Check transaction hash if both are present | ||
| if (txHash != null && inclusionProof.getTransactionHash() != null) { | ||
| if (!inclusionProof.getTransactionHash().equals(txHash)) { | ||
| return CompletableFuture.failedFuture(new Exception("Payload hash mismatch")); | ||
| } | ||
| } | ||
|
|
||
| return CompletableFuture.completedFuture(new Transaction<>(commitment.getTransactionData(), inclusionProof)); | ||
| }); | ||
| } | ||
|
|
||
| public <T extends Transaction<MintTransactionData<?>>> CompletableFuture<Token<T>> finishTransaction( | ||
| Token<T> token, | ||
| TokenState state, | ||
| Transaction<TransactionData> transaction) { | ||
| return finishTransaction(token, state, transaction, new ArrayList<>()); | ||
| } | ||
|
|
||
| public <T extends Transaction<MintTransactionData<?>>> CompletableFuture<Token<T>> finishTransaction( | ||
| Token<T> token, | ||
| TokenState state, | ||
| Transaction<TransactionData> transaction, | ||
| List<Token<?>> nametagTokens) { | ||
| return transaction.getData().getSourceState().getUnlockPredicate().verify(transaction) | ||
| .thenCompose(verified -> { | ||
| if (!verified) { | ||
| return CompletableFuture.failedFuture(new Exception("Predicate verification failed")); | ||
| } | ||
|
|
||
| return DirectAddress.create(state.getUnlockPredicate().getReference()) | ||
| .thenCompose(expectedAddress -> { | ||
| if (!expectedAddress.toString().equals(transaction.getData().getRecipient())) { | ||
| return CompletableFuture.failedFuture(new Exception("Recipient address mismatch")); | ||
| } | ||
|
|
||
| List<Transaction<?>> transactions = new ArrayList<>(token.getTransactions()); | ||
| transactions.add(transaction); | ||
|
|
||
| return transaction.containsData(state.getData()) | ||
| .thenCompose(contains -> { | ||
| if (!contains) { | ||
| return CompletableFuture.failedFuture(new Exception("State data is not part of transaction.")); | ||
| } | ||
|
|
||
| return CompletableFuture.completedFuture( | ||
| new Token<>(state, token.getGenesis(), transactions, nametagTokens)); | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| public CompletableFuture<InclusionProofVerificationStatus> getTokenStatus( | ||
| Token<? extends Transaction<MintTransactionData<?>>> token, | ||
| byte[] publicKey) { | ||
| return RequestId.create(publicKey, token.getState().getHash()) | ||
| .thenCompose(requestId -> aggregatorClient.getInclusionProof(requestId)) | ||
| .thenCompose(inclusionProof -> | ||
| RequestId.create(publicKey, token.getState().getHash()) | ||
| .thenCompose(inclusionProof::verify)); | ||
| } | ||
|
|
||
| public CompletableFuture<InclusionProof> getInclusionProof(Commitment<?> commitment) { | ||
| return aggregatorClient.getInclusionProof(commitment.getRequestId()); | ||
| } | ||
|
|
||
| private <TD extends ISerializable> CompletableFuture<Commitment<TD>> sendTransaction( | ||
| TD transactionData, | ||
| ISigningService<?> signingService) { | ||
|
|
||
| // Get the source state hash and request ID based on the transaction data type | ||
| CompletableFuture<RequestId> requestIdFuture; | ||
| DataHash sourceStateHash; | ||
| DataHash transactionHash; | ||
|
|
||
| if (transactionData instanceof TransactionData) { | ||
| TransactionData txData = (TransactionData) transactionData; | ||
| sourceStateHash = txData.getSourceState().getHash(); | ||
| transactionHash = txData.getHash(); | ||
| requestIdFuture = RequestId.create(signingService.getPublicKey(), sourceStateHash); | ||
| } else if (transactionData instanceof MintTransactionData) { | ||
| MintTransactionData<?> mintData = (MintTransactionData<?>) transactionData; | ||
| // For mint transactions, use the sourceState from the mint data | ||
| RequestId mintSourceState = mintData.getSourceState(); | ||
| sourceStateHash = mintSourceState.getHash(); | ||
| transactionHash = mintData.getHash(); | ||
| // TypeScript creates RequestId using: RequestId.create(signingService.publicKey, transactionData.sourceState.hash) | ||
| requestIdFuture = RequestId.create(signingService.getPublicKey(), mintSourceState.getHash()); | ||
| } else { | ||
| return CompletableFuture.failedFuture(new Exception("Unsupported transaction data type")); | ||
| } | ||
|
|
||
| return requestIdFuture.thenCompose(requestId -> | ||
| Authenticator.create( | ||
| signingService, | ||
| transactionHash, | ||
| sourceStateHash) | ||
| .thenCompose(authenticator -> { | ||
| // Debug logging | ||
| System.out.println("Authenticator JSON: " + authenticator.toJSON()); | ||
| System.out.println("RequestId: " + requestId.toJSON()); | ||
| System.out.println("TransactionHash: " + transactionHash.toJSON()); | ||
| if (transactionData instanceof MintTransactionData) { | ||
| MintTransactionData<?> mintData = (MintTransactionData<?>) transactionData; | ||
| System.out.println("MintSourceState: " + mintData.getSourceState().toJSON()); | ||
| System.out.println("MintSourceState hash: " + mintData.getSourceState().getHash().toJSON()); | ||
| System.out.println("SigningService publicKey: " + HexConverter.encode(signingService.getPublicKey())); | ||
| } | ||
|
|
||
| return aggregatorClient.submitTransaction(requestId, transactionHash, authenticator) | ||
| .thenCompose(result -> { | ||
| if (result.getStatus() != SubmitCommitmentStatus.SUCCESS) { | ||
| return CompletableFuture.failedFuture( | ||
| new Exception("Could not submit transaction: " + result.getStatus())); | ||
| } | ||
| return CompletableFuture.completedFuture( | ||
| new Commitment<>(requestId, transactionData, authenticator)); | ||
| }); | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| public CompletableFuture<SubmitCommitmentResponse> submitCommitment(Commitment<?> commitment) { | ||
| DataHash txHash = null; | ||
| ISerializable transactionData = commitment.getTransactionData(); | ||
| if (transactionData instanceof TransactionData) { | ||
| txHash = ((TransactionData) transactionData).getHash(); | ||
| } else if (transactionData instanceof MintTransactionData) { | ||
| txHash = ((MintTransactionData<?>) transactionData).getHash(); | ||
| } | ||
|
|
||
| if (txHash == null) { | ||
| return CompletableFuture.failedFuture(new IllegalArgumentException("Cannot get hash from transaction data")); | ||
| } | ||
|
|
||
| return aggregatorClient.submitTransaction( | ||
| commitment.getRequestId(), | ||
| txHash, | ||
| commitment.getAuthenticator() | ||
| ); | ||
| } | ||
|
|
||
| public AggregatorClient getAggregatorClient() { | ||
| return aggregatorClient; | ||
| } | ||
| return this.client.submitCommitment(commitment.getRequestId(), commitment.getTransactionData() | ||
| .calculateHash(token.getId(), token.getType()), commitment.getAuthenticator()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to extend my previous comment: transfer could also have |
||
| } | ||
|
|
||
| public <T extends Transaction<MintTransactionData<?>>> Token<T> finishTransaction( | ||
| Token<T> token, | ||
| TokenState state, | ||
| Transaction<TransferTransactionData> transaction | ||
| ) { | ||
| return this.finishTransaction(token, state, transaction, List.of()); | ||
| } | ||
|
|
||
| public <T extends Transaction<MintTransactionData<?>>> Token<T> finishTransaction( | ||
|
martti007 marked this conversation as resolved.
Outdated
|
||
| Token<T> token, | ||
| TokenState state, | ||
| Transaction<TransferTransactionData> transaction, | ||
| List<Token<?>> nametagTokens | ||
| ) { | ||
| Objects.requireNonNull(token, "Token is null"); | ||
|
|
||
| return token.update(state, transaction, nametagTokens); | ||
| } | ||
|
|
||
| public CompletableFuture<InclusionProofVerificationStatus> getTokenStatus( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unused, needs tests |
||
| Token<? extends Transaction<MintTransactionData<?>>> token, | ||
| byte[] publicKey) { | ||
| RequestId requestId = RequestId.create(publicKey, | ||
| token.getState().calculateHash(token.getId(), token.getType())); | ||
| return this.client.getInclusionProof(requestId) | ||
| .thenApply(inclusionProof -> inclusionProof.verify(requestId)); | ||
| } | ||
|
|
||
| public CompletableFuture<InclusionProof> getInclusionProof(Commitment<?> commitment) { | ||
| return this.client.getInclusionProof(commitment.getRequestId()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
|
|
||
| package com.unicity.sdk.address; | ||
|
|
||
| public interface Address { | ||
| AddressScheme getScheme(); | ||
| String getAddress(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aggregator client coult simply take Commitment object as input, MintCommitment and TransferCommitment both extend it.
TransactionDatacould know how to calculate its own hash (MintTransactionDataalready does) andTransferTransactionDatahas all the token info it needs at creation time.