Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Commit

Permalink
Add muxed_id support for JSON responses (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
Synesso committed Jul 22, 2021
1 parent fe346d6 commit 0b21c69
Show file tree
Hide file tree
Showing 37 changed files with 398 additions and 354 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ As this project is pre 1.0, breaking changes may happen for minor version bumps.
- Added the ability to list operations associated with a claimable balance.
- Added the ability to list transactions associated with a claimable balance.

### Breaking

- Many instances of `PublicKey` fields on response classes have been upgraded to `AccountId`s to reflect Horizon's
improved support for multiplexed addresses. Changes are present in Operation, Transaction and Effect classes.

## 0.20.0

- Updated for core protocol v17/CAP35:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![Build](https://github.com/Synesso/scala-stellar-sdk/workflows/Build/badge.svg?branch=master)](https://github.com/Synesso/scala-stellar-sdk/actions/workflows/scala.yml)
[![Coverage](https://img.shields.io/codecov/c/gh/Synesso/scala-stellar-sdk.svg)](https://codecov.io/gh/Synesso/scala-stellar-sdk)
[![Issues](https://img.shields.io/github/issues/Synesso/scala-stellar-sdk.svg)](https://github.com/Synesso/scala-stellar-sdk/issues)
![Supports Stellar Horizon v2.2.0](https://img.shields.io/badge/Horizon-v2.2.0-blue.svg)
![Supports Stellar Horizon v2.6.1](https://img.shields.io/badge/Horizon-v2.6.1-blue.svg)
![Supports Stellar Core v17](https://img.shields.io/badge/Core-v17-blue.svg)
[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-brightgreen.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)

Expand Down
100 changes: 50 additions & 50 deletions src/it/scala/stellar/sdk/LocalNetworkIntegrationSpec.scala

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/it/scala/stellar/sdk/TestAccounts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class TestAccounts(quantity: Int = 20) extends LazyLogging {
val ops = for {
friendBotAccountId <- friendBot.get.map(_.get)
allKps <- unused.get.flatMap(kps => borrowed.get.map(_ ++ kps))
allOps = allKps.map { kp => AccountMergeOperation(friendBotAccountId, Some(kp)) }
allOps = allKps.map { kp => AccountMergeOperation(friendBotAccountId, Some(kp.toAccountId)) }
} yield allKps.zip(allOps).toMap
val opsMap = ops.unsafeRunSync()
val response = for {
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/stellar/sdk/auth/AuthChallenger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ class AuthChallenger(
WriteDataOperation(
name = s"$homeDomain auth",
value = generateDataKey,
sourceAccount = Some(accountId.publicKey)
sourceAccount = Some(accountId)
),
WriteDataOperation(
name = "web_auth_domain",
value = webAuthDomain.getBytes(Charsets.UTF_8),
sourceAccount = Some(accountId.publicKey)
sourceAccount = Some(accountId)
)
),
timeBounds = TimeBounds.timeout(timeout, clock),
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/stellar/sdk/auth/Challenge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ case class Challenge(

private def checkSignedByClient(answer: SignedTransaction): Option[ChallengeResult] = {
// FIXME - deal with 0 or multi operations. Deal with no source account.
if (answer.verify(transaction.operations.head.sourceAccount.get)) None
if (answer.verify(transaction.operations.head.sourceAccount.get.publicKey)) None
else Some(ChallengeNotSignedByClient)
}

Expand Down
13 changes: 11 additions & 2 deletions src/main/scala/stellar/sdk/model/StrKey.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package stellar.sdk.model

import java.nio.ByteBuffer
import org.apache.commons.codec.binary.Base32
import org.json4s.Formats
import org.json4s.JsonAST.JObject
import org.stellar.xdr.{AccountID, CryptoKeyType, MuxedAccount, PublicKeyType, SignerKey, SignerKeyType, Uint256, Uint64, PublicKey => XPublicKey}
import stellar.sdk.model.StrKey.codec
import stellar.sdk.util.ByteArrays
import stellar.sdk.{KeyPair, PublicKey}

import scala.util.Try
import java.nio.ByteBuffer


/**
Expand Down Expand Up @@ -77,6 +78,14 @@ object SignerStrKey {
}

object AccountId {

/** Extract an account id from JSON */
def parse(o: JObject, baseKeyName: String)(implicit formats: Formats): AccountId = {
val pk = KeyPair.fromAccountId((o \ baseKeyName).extract[String])
val multiplexedId = (o \ s"${baseKeyName}_muxed_id").extractOpt[String].map(_.toLong)
AccountId(pk.publicKey, multiplexedId)
}

def decodeXdr(accountId: AccountID): AccountId =
AccountId(KeyPair.fromPublicKey(accountId.getAccountID.getEd25519.getUint256).publicKey)

Expand Down
96 changes: 52 additions & 44 deletions src/main/scala/stellar/sdk/model/op/Operation.scala

Large diffs are not rendered by default.

76 changes: 39 additions & 37 deletions src/main/scala/stellar/sdk/model/response/EffectResponse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,53 +13,53 @@ sealed trait EffectResponse {
val createdAt: ZonedDateTime
}

case class EffectAccountCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, startingBalance: NativeAmount) extends EffectResponse
case class EffectAccountCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId, startingBalance: NativeAmount) extends EffectResponse

case class EffectAccountCredited(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, amount: Amount) extends EffectResponse
case class EffectAccountCredited(id: String, createdAt: ZonedDateTime, accountId: AccountId, amount: Amount) extends EffectResponse

case class EffectAccountDebited(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, amount: Amount) extends EffectResponse
case class EffectAccountDebited(id: String, createdAt: ZonedDateTime, accountId: AccountId, amount: Amount) extends EffectResponse

case class EffectAccountInflationDestinationUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectAccountInflationDestinationUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectAccountRemoved(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectAccountRemoved(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectAccountSponsorshipCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectAccountSponsorshipCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectAccountThresholdsUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, thresholds: Thresholds) extends EffectResponse
case class EffectAccountThresholdsUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId, thresholds: Thresholds) extends EffectResponse

case class EffectAccountHomeDomainUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, domain: String) extends EffectResponse
case class EffectAccountHomeDomainUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId, domain: String) extends EffectResponse

case class EffectAccountFlagsUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectAccountFlagsUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectDataCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectDataCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectDataRemoved(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectDataRemoved(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectDataUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps) extends EffectResponse
case class EffectDataUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId) extends EffectResponse

case class EffectSequenceBumped(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, newSeq: Long) extends EffectResponse
case class EffectSequenceBumped(id: String, createdAt: ZonedDateTime, accountId: AccountId, newSeq: Long) extends EffectResponse

case class EffectSignerCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, weight: Short, publicKey: String) extends EffectResponse
case class EffectSignerCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId, weight: Short, publicKey: String) extends EffectResponse

case class EffectSignerUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, weight: Short, publicKey: String) extends EffectResponse
case class EffectSignerUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId, weight: Short, publicKey: String) extends EffectResponse

case class EffectSignerRemoved(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, publicKey: String) extends EffectResponse
case class EffectSignerRemoved(id: String, createdAt: ZonedDateTime, accountId: AccountId, publicKey: String) extends EffectResponse

case class EffectSignerSponsorshipCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, signer: PublicKeyOps, newSponsor: PublicKeyOps) extends EffectResponse
case class EffectSignerSponsorshipCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId, signer: PublicKeyOps, newSponsor: PublicKeyOps) extends EffectResponse

case class EffectSignerSponsorshipRemoved(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, signer: PublicKeyOps, formerSponsor: PublicKeyOps) extends EffectResponse
case class EffectSignerSponsorshipRemoved(id: String, createdAt: ZonedDateTime, accountId: AccountId, signer: PublicKeyOps, formerSponsor: PublicKeyOps) extends EffectResponse

case class EffectSignerSponsorshipUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, signer: PublicKeyOps, formerSponsor: PublicKeyOps, newSponsor: PublicKeyOps) extends EffectResponse
case class EffectSignerSponsorshipUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId, signer: PublicKeyOps, formerSponsor: PublicKeyOps, newSponsor: PublicKeyOps) extends EffectResponse

case class EffectTrustLineCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, limit: IssuedAmount) extends EffectResponse {
case class EffectTrustLineCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId, limit: IssuedAmount) extends EffectResponse {
val asset: NonNativeAsset = limit.asset
}

case class EffectTrustLineUpdated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, limit: IssuedAmount) extends EffectResponse {
case class EffectTrustLineUpdated(id: String, createdAt: ZonedDateTime, accountId: AccountId, limit: IssuedAmount) extends EffectResponse {
val asset: NonNativeAsset = limit.asset
}

case class EffectTrustLineRemoved(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, asset: NonNativeAsset) extends EffectResponse
case class EffectTrustLineRemoved(id: String, createdAt: ZonedDateTime, accountId: AccountId, asset: NonNativeAsset) extends EffectResponse

case class EffectTrustLineAuthorized(id: String, createdAt: ZonedDateTime, trustor: PublicKeyOps, asset: NonNativeAsset) extends EffectResponse

Expand All @@ -69,9 +69,9 @@ case class EffectTrustLineDeauthorized(id: String, createdAt: ZonedDateTime, tru

case class EffectTrustLineFlagsUpdated(id: String, createdAt: ZonedDateTime, trustor: PublicKey, asset: NonNativeAsset, authorized: Boolean, authorizedToMaintainLiabilities: Boolean, clawbackEnabled: Boolean) extends EffectResponse

case class EffectTrustLineSponsorshipCreated(id: String, createdAt: ZonedDateTime, account: PublicKeyOps, asset: NonNativeAsset, sponsor: PublicKeyOps) extends EffectResponse
case class EffectTrustLineSponsorshipCreated(id: String, createdAt: ZonedDateTime, accountId: AccountId, asset: NonNativeAsset, sponsor: PublicKeyOps) extends EffectResponse

case class EffectTrade(id: String, createdAt: ZonedDateTime, offerId: Long, buyer: PublicKeyOps, bought: Amount, seller: PublicKeyOps, sold: Amount) extends EffectResponse
case class EffectTrade(id: String, createdAt: ZonedDateTime, offerId: Long, buyer: AccountId, bought: Amount, seller: AccountId, sold: Amount) extends EffectResponse

case class EffectClaimableBalanceClawedBack(id: String, createdAt: ZonedDateTime, balanceId: Long) extends EffectResponse

Expand All @@ -80,7 +80,9 @@ case class UnrecognisedEffect(id: String, createdAt: ZonedDateTime, json: String
object EffectResponseDeserializer extends ResponseParser[EffectResponse]({ o: JObject =>
implicit val formats = DefaultFormats

def account(accountKey: String = "account") = KeyPair.fromAccountId((o \ accountKey).extract[String])
def pubKey(accountKey: String): PublicKey = KeyPair.fromAccountId((o \ accountKey).extract[String])

def account(accountKey: String = "account"): AccountId = AccountId.parse(o, accountKey)

def asset(prefix: String = "", issuerKey: String = "asset_issuer"): Asset = {
def assetCode = (o \ s"${prefix}asset_code").extract[String]
Expand Down Expand Up @@ -153,35 +155,35 @@ object EffectResponseDeserializer extends ResponseParser[EffectResponse]({ o: JO
case "signer_created" => EffectSignerCreated(id, createdAt, account(), weight, (o \ "public_key").extract[String])
case "signer_removed" => EffectSignerRemoved(id, createdAt, account(), (o \ "public_key").extract[String])
case "signer_sponsorship_created" => EffectSignerSponsorshipCreated(id, createdAt, account(),
signer = account("signer"),
newSponsor = account("sponsor")
signer = pubKey("signer"),
newSponsor = pubKey("sponsor")
)
case "signer_sponsorship_removed" => EffectSignerSponsorshipRemoved(id, createdAt, account(),
signer = account("signer"),
formerSponsor = account("former_sponsor")
signer = pubKey("signer"),
formerSponsor = pubKey("former_sponsor")
)
case "signer_sponsorship_updated" => EffectSignerSponsorshipUpdated(id, createdAt, account(),
signer = account("signer"),
formerSponsor = account("former_sponsor"),
newSponsor = account("new_sponsor")
signer = pubKey("signer"),
formerSponsor = pubKey("former_sponsor"),
newSponsor = pubKey("new_sponsor")
)
case "signer_updated" => EffectSignerUpdated(id, createdAt, account(), weight, (o \ "public_key").extract[String])
case "trade" => EffectTrade(id, createdAt, (o \ "offer_id").extract[String].toLong, account(), amount("bought_"), account("seller"), amount("sold_"))
case "trustline_authorized" => EffectTrustLineAuthorized(id, createdAt, account("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_authorized_to_maintain_liabilities" => EffectTrustLineAuthorizedToMaintainLiabilities(id, createdAt, account("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_authorized" => EffectTrustLineAuthorized(id, createdAt, pubKey("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_authorized_to_maintain_liabilities" => EffectTrustLineAuthorizedToMaintainLiabilities(id, createdAt, pubKey("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_created" => EffectTrustLineCreated(id, createdAt, account(), amount(key = "limit").asInstanceOf[IssuedAmount])
case "trustline_deauthorized" => EffectTrustLineDeauthorized(id, createdAt, account("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_deauthorized" => EffectTrustLineDeauthorized(id, createdAt, pubKey("trustor"), asset(issuerKey = "account").asInstanceOf[NonNativeAsset])
case "trustline_flags_updated" => EffectTrustLineFlagsUpdated(
id = id,
createdAt = createdAt,
trustor = account("trustor"),
trustor = pubKey("trustor"),
asset = asset().asInstanceOf[NonNativeAsset],
authorized = maybeBool("authorized_flag"),
authorizedToMaintainLiabilities = maybeBool("authorized_to_maintain_liabilites"),
clawbackEnabled = maybeBool("clawback_enabled_flag")
)
case "trustline_removed" => EffectTrustLineRemoved(id, createdAt, account(), asset().asInstanceOf[NonNativeAsset])
case "trustline_sponsorship_created" => EffectTrustLineSponsorshipCreated(id, createdAt, account(), issuedAsset(), account("sponsor"))
case "trustline_sponsorship_created" => EffectTrustLineSponsorshipCreated(id, createdAt, account(), issuedAsset(), pubKey("sponsor"))
case "trustline_sponsorship_removed" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "trustline_sponsorship_updated" => UnrecognisedEffect(id, createdAt, JsonMethods.compact(JsonMethods.render(o)))
case "trustline_updated" => EffectTrustLineUpdated(id, createdAt, account(), amount(key = "limit").asInstanceOf[IssuedAmount])
Expand Down
39 changes: 26 additions & 13 deletions src/main/scala/stellar/sdk/model/result/TransactionHistory.scala
Original file line number Diff line number Diff line change
@@ -1,31 +1,45 @@
package stellar.sdk.model.result

import java.time.ZonedDateTime

import okio.ByteString
import org.json4s.JsonAST.JObject
import org.json4s.{DefaultFormats, Formats}
import stellar.sdk.Network
import stellar.sdk.model._
import stellar.sdk.model.ledger.{LedgerEntryChange, LedgerEntryChanges, TransactionLedgerEntries}
import stellar.sdk.model.response.ResponseParser
import stellar.sdk.util.ByteArrays.base64
import stellar.sdk.{KeyPair, Network, PublicKey}

import java.time.ZonedDateTime
import scala.util.Try

/**
* A transaction that has been included in the ledger sometime in the past.
*/
case class TransactionHistory(hash: String, ledgerId: Long, createdAt: ZonedDateTime, account: PublicKey,
sequence: Long, maxFee: NativeAmount, feeCharged: NativeAmount, operationCount: Int,
memo: Memo, signatures: Seq[String], envelopeXDR: String, resultXDR: String,
resultMetaXDR: String, feeMetaXDR: String, validAfter: Option[ZonedDateTime],
validBefore: Option[ZonedDateTime], feeBump: Option[FeeBumpHistory]) {
* A transaction that has been included in the ledger sometime in the past.
*/
case class TransactionHistory(
hash: String,
ledgerId: Long,
createdAt: ZonedDateTime,
account: AccountId,
sequence: Long,
maxFee: NativeAmount,
feeCharged: NativeAmount,
operationCount: Int,
memo: Memo,
signatures: Seq[String],
envelopeXDR: String,
resultXDR: String,
resultMetaXDR: String,
feeMetaXDR: String,
validAfter: Option[ZonedDateTime],
validBefore: Option[ZonedDateTime],
feeBump: Option[FeeBumpHistory]
) {

lazy val result: TransactionResult = TransactionResult.decodeXdrString(resultXDR)

def ledgerEntries: TransactionLedgerEntries = TransactionLedgerEntries.decodeXDR(resultMetaXDR)

def feeLedgerEntries: Seq[LedgerEntryChange] = LedgerEntryChanges.decodeXDR(feeMetaXDR)

def transaction(network: Network): Transaction = Transaction.decodeXdrString(envelopeXDR)(network)

@deprecated("Replaced by `feeCharged`", "v0.7.2")
Expand All @@ -48,12 +62,11 @@ object TransactionHistoryDeserializer extends {
maxFee <- (o \ "inner_transaction" \ "max_fee").extractOpt[Int].map(NativeAmount(_))
signatures <- (o \ "inner_transaction" \ "signatures").extractOpt[List[String]]
} yield (hash, maxFee, signatures)

TransactionHistory(
hash = inner.map(_._1).getOrElse(hash),
ledgerId = (o \ "ledger").extract[Long],
createdAt = ZonedDateTime.parse((o \ "created_at").extract[String]),
account = KeyPair.fromAccountId((o \ "source_account").extract[String]),
account = AccountId.parse(o, "source_account"),
sequence = (o \ "source_account_sequence").extract[String].toLong,
maxFee = inner.map(_._2).getOrElse(maxFee),
feeCharged = NativeAmount((o \ "fee_charged").extract[String].toInt),
Expand Down
Loading

0 comments on commit 0b21c69

Please sign in to comment.