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
6 changes: 6 additions & 0 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ follow [https://changelog.md/](https://changelog.md/) guidelines.

## [Unreleased]

## [53.2] - 2025-03-24

### ADDED

- Alternative transactions signing

## [53.1] - 2025-02-21

### ADDED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import io.muun.common.utils.BitcoinUtils;
import io.muun.common.utils.Encodings;
import io.muun.common.utils.Pair;
import io.muun.common.utils.Preconditions;

import android.os.SystemClock;
import androidx.annotation.NonNull;
Expand Down Expand Up @@ -144,16 +145,25 @@ private BitcoinAmountJson mapBitcoinAmount(@NotNull BitcoinAmount bitcoinAmount)
public OperationJson mapOperation(
final @NotNull OperationWithMetadata operation,
final List<String> outpoints,
final MusigNonces musigNonces
final MusigNonces musigNonces,
final List<MusigNonces> alternativeTxNonces
) {

final Long outputAmountInSatoshis = mapOutputAmountInSatoshis(operation);

final List<String> userPublicNoncesHex = new LinkedList<>();
for (int i = 0; i < outpoints.size(); i++) {
final long noncesCount = musigNonces.length();
for (int i = 0; i < noncesCount; i++) {
userPublicNoncesHex.add(musigNonces.getPubnonceHex(i));
}

for (final MusigNonces nonces : alternativeTxNonces) {
Preconditions.checkState(noncesCount == nonces.length());
for (int i = 0; i < noncesCount; i++) {
userPublicNoncesHex.add(nonces.getPubnonceHex(i));
}
}

return new OperationJson(
UUID.randomUUID().toString(),
operation.getDirection(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,13 @@
import io.muun.common.api.IntegrityStatus;
import io.muun.common.api.KeySet;
import io.muun.common.api.LinkActionJson;
import io.muun.common.api.OperationJson;
import io.muun.common.api.PasswordSetupJson;
import io.muun.common.api.PhoneConfirmation;
import io.muun.common.api.PlayIntegrityTokenJson;
import io.muun.common.api.PreimageJson;
import io.muun.common.api.PublicKeySetJson;
import io.muun.common.api.PushTransactionsJson;
import io.muun.common.api.RawTransaction;
import io.muun.common.api.SetupChallengeResponse;
import io.muun.common.api.UpdateOperationMetadataJson;
Expand Down Expand Up @@ -100,6 +102,7 @@
import rx.Observable;
import rx.Single;

import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Inject;
Expand Down Expand Up @@ -603,10 +606,14 @@ public Observable<IntegrityStatus> checkIntegrity(IntegrityCheck integrityCheck)
public Observable<OperationCreated> newOperation(
final OperationWithMetadata operation,
final List<String> outpoints,
final MusigNonces musigNonces
final MusigNonces musigNonces,
final List<MusigNonces> alternativeTxNonces
) {
final OperationJson operationJson =
apiMapper.mapOperation(operation, outpoints, musigNonces, alternativeTxNonces);

return getService()
.newOperation(apiMapper.mapOperation(operation, outpoints, musigNonces))
.newOperation(operationJson)
.map(modelMapper::mapOperationCreated);
}

Expand All @@ -623,29 +630,44 @@ public Completable updateOperationMetadata(OperationWithMetadata operation) {

/**
* Pushes a raw transaction to Houston.
*
* @param txHex The bitcoinj's transaction.
* @param operationHid The houston operation id.
*/
public Observable<TransactionPushed> pushTransaction(
public Observable<TransactionPushed> pushTransactions(
@Nullable String txHex,
@Nullable List<String> alternativeTransactionsHex,
long operationHid
) {
final RawTransaction rawTransaction;
final ArrayList<RawTransaction> alternativeTransactions = new ArrayList<RawTransaction>();

if (txHex != null) {
return getService()
.pushTransaction(new RawTransaction(txHex), operationHid)
// This can happen if we determine the payment can't actually be made. If so, we
// fail fast to avoid broadcasting a transaction saving the user on miner fees.
.compose(ObservableFn.replaceHttpException(
ErrorCode.SWAP_FAILED,
SwapFailedException::new
))
.map(modelMapper::mapTransactionPushed);
Preconditions.checkArgument(alternativeTransactionsHex != null);

rawTransaction = new RawTransaction(txHex);

for (var transactionHex : alternativeTransactionsHex) {
alternativeTransactions.add(new RawTransaction(transactionHex));
}

} else {
return getService()
.pushTransaction(operationHid) // empty body when txHex is not given
.map(modelMapper::mapTransactionPushed);
Preconditions.checkArgument(alternativeTransactionsHex == null);

rawTransaction = null;
}

final var json = new PushTransactionsJson(
rawTransaction,
alternativeTransactions
);

return getService()
.pushTransactions(json, operationHid)
// This can happen if we determine the payment can't actually be made. If so, we
// fail fast to avoid broadcasting a transaction saving the user on miner fees.
.compose(ObservableFn.replaceHttpException(
ErrorCode.SWAP_FAILED,
SwapFailedException::new
))
.map(modelMapper::mapTransactionPushed);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import io.muun.common.api.NextTransactionSizeJson;
import io.muun.common.api.OperationCreatedJson;
import io.muun.common.api.OperationJson;
import io.muun.common.api.PartiallySignedTransactionJson;
import io.muun.common.api.PendingChallengeUpdateJson;
import io.muun.common.api.PhoneNumberJson;
import io.muun.common.api.PublicKeySetJson;
Expand Down Expand Up @@ -377,10 +378,26 @@ public OperationCreated mapOperationCreated(@NotNull OperationCreatedJson operat
networkParameters
),
mapNextTransactionSize(operationCreated.nextTransactionSize),
MuunAddress.fromJson(operationCreated.changeAddress)
MuunAddress.fromJson(operationCreated.changeAddress),
mapAlternativeTransactions(operationCreated.alternativeTransactions)
);
}

private List<PartiallySignedTransaction> mapAlternativeTransactions(
@Nullable final List<PartiallySignedTransactionJson> txs
) {
if (txs == null) {
return List.of();
}

final var result = new ArrayList<PartiallySignedTransaction>();
for (final var tx : txs) {
result.add(PartiallySignedTransaction.fromJson(tx, networkParameters));
}

return result;
}

/**
* Create a TransactionPushed object.
*/
Expand Down
Loading