├── .github └── workflows │ └── scala.yml ├── .gitignore ├── CHANGELOG.md ├── DEV.md ├── LICENSE ├── README.md ├── build.sbt ├── codecov.yml ├── jitpack.yml ├── project ├── .gitignore ├── .gnupg │ └── .gitignore ├── build.properties └── plugins.sbt └── src ├── it ├── bin │ └── stellar_standalone.sh └── scala │ └── stellar │ └── sdk │ ├── AdHocPublicNetworkCases.scala │ ├── DomainInfoItSpec.scala │ ├── DomainMatchersIT.scala │ ├── FederationLookupSpec.scala │ ├── FriendBotSpec.scala │ ├── HelloWorld.scala │ ├── LocalNetworkIntegrationSpec.scala │ ├── TestAccounts.scala │ ├── TestNetworkJourney.scala │ └── TransactionLedgerEntriesSpec.scala ├── main ├── paradox │ ├── authentication.md │ ├── domains.md │ ├── index.md │ ├── key_pairs.md │ ├── queries.md │ ├── sources.md │ └── transacting.md ├── resources │ ├── logback.xml │ ├── reference.conf │ └── wordlists │ │ ├── chinese_simplified.txt │ │ ├── chinese_traditional.txt │ │ ├── czech.txt │ │ ├── english.txt │ │ ├── french.txt │ │ ├── italian.txt │ │ ├── japanese.txt │ │ ├── korean.txt │ │ └── spanish.txt └── scala │ └── stellar │ └── sdk │ ├── FederationServer.scala │ ├── InvalidTransactionException.scala │ ├── KeyPair.scala │ ├── Network.scala │ ├── PublicNetwork.scala │ ├── StandaloneNetwork.scala │ ├── TestNetwork.scala │ ├── auth │ ├── AuthChallenger.scala │ └── Challenge.scala │ ├── inet │ ├── HorizonAccess.scala │ ├── HorizonServerError.scala │ ├── OkHorizon.scala │ ├── Page.scala │ └── PageParser.scala │ ├── key │ ├── HDNode.scala │ ├── Mnemonic.scala │ └── WordList.scala │ ├── model │ ├── Account.scala │ ├── Amount.scala │ ├── Asset.scala │ ├── Balance.scala │ ├── ClaimPredicate.scala │ ├── ClaimableBalance.scala │ ├── ClaimableBalanceId.scala │ ├── Claimant.scala │ ├── FeeBump.scala │ ├── HorizonCursor.scala │ ├── HorizonOrder.scala │ ├── Memo.scala │ ├── OrderBook.scala │ ├── PaymentPath.scala │ ├── PaymentSigningRequest.scala │ ├── Price.scala │ ├── Signer.scala │ ├── StrKey.scala │ ├── Thresholds.scala │ ├── TimeBounds.scala │ ├── Trade.scala │ ├── TradeAggregation.scala │ ├── Transaction.scala │ ├── TransactionSigningRequest.scala │ ├── domain │ │ ├── Currency.scala │ │ ├── DomainInfo.scala │ │ ├── IssuerDocumentation.scala │ │ ├── PointOfContact.scala │ │ ├── TomlParsers.scala │ │ └── Validator.scala │ ├── ledger │ │ ├── LedgerEntry.scala │ │ ├── LedgerEntryChanges.scala │ │ ├── LedgerKey.scala │ │ ├── Liabilities.scala │ │ └── TransactionLedgerEntries.scala │ ├── op │ │ ├── Operation.scala │ │ └── Transacted.scala │ ├── response │ │ ├── AccountResponse.scala │ │ ├── AssetResponse.scala │ │ ├── DataValueResponse.scala │ │ ├── EffectResponse.scala │ │ ├── FederationResponse.scala │ │ ├── FeeStatsResponse.scala │ │ ├── LedgerResponse.scala │ │ ├── NetworkInfo.scala │ │ ├── OfferResponse.scala │ │ ├── ResponseParser.scala │ │ └── TransactionPostResponse.scala │ └── result │ │ ├── AccountMergeResult.scala │ │ ├── AllowTrustResult.scala │ │ ├── BeginSponsoringFutureReservesResult.scala │ │ ├── BumpSequenceResult.scala │ │ ├── ChangeTrustResult.scala │ │ ├── ClaimClaimableBalanceResult.scala │ │ ├── CreateAccountResult.scala │ │ ├── CreateClaimableBalanceResult.scala │ │ ├── CreatePassiveOfferResult.scala │ │ ├── EndSponsoringFutureReservesResult.scala │ │ ├── FeeBumpHistory.scala │ │ ├── FeeBumpedTransactionResult.scala │ │ ├── InflationResult.scala │ │ ├── ManageBuyOfferResult.scala │ │ ├── ManageDataResult.scala │ │ ├── ManageSellOfferResult.scala │ │ ├── OfferClaim.scala │ │ ├── OperationResult.scala │ │ ├── PathPaymentReceiveResult.scala │ │ ├── PathPaymentSendResult.scala │ │ ├── PaymentResult.scala │ │ ├── RevokeSponsorshipResult.scala │ │ ├── SetOptionsResult.scala │ │ ├── TransactionHistory.scala │ │ └── TransactionResult.scala │ ├── package.scala │ └── util │ ├── ByteArrays.scala │ └── DoNothingNetwork.scala └── test └── scala └── stellar └── sdk ├── ArbitraryInput.scala ├── DocExamples.scala ├── DomainInfoSpec.scala ├── DomainMatchers.scala ├── FederationServerSpec.scala ├── KeyPairSpec.scala ├── NetworkSpec.scala ├── app └── ParseTimeWindow.scala ├── auth └── ChallengeSpec.scala ├── inet └── PageSpec.scala ├── key ├── HDNodeSpec.scala ├── MnemonicSpec.scala └── WordListSpec.scala ├── model ├── AccountSpec.scala ├── AmountSpec.scala ├── AssetSpec.scala ├── ClaimPredicateSpec.scala ├── ClaimableBalanceIdSpec.scala ├── ClaimableBalanceSpec.scala ├── ClaimantSpec.scala ├── HorizonCursorSpec.scala ├── MemoSpec.scala ├── PaymentPathSpec.scala ├── PaymentSigningRequestSpec.scala ├── SignerSpec.scala ├── StrKeySpec.scala ├── TimeBoundsSpec.scala ├── TradeAggregationSpec.scala ├── TradeSpec.scala ├── TransactionSigningRequestSpec.scala ├── TransactionSpec.scala ├── domain │ └── DomainInfoGenerators.scala ├── ledger │ ├── LedgerEntryChangeSpec.scala │ ├── LedgerEntryGenerators.scala │ ├── LedgerEntrySpec.scala │ ├── LedgerKeySpec.scala │ └── TransactionLedgerEntriesSpec.scala ├── op │ ├── AccountMergeOperationSpec.scala │ ├── AllowTrustOperationSpec.scala │ ├── BeginSponsoringFutureReservesOperationSpec.scala │ ├── BumpSequenceOperationSpec.scala │ ├── ChangeTrustOperationSpec.scala │ ├── ClaimClaimableBalanceOperationSpec.scala │ ├── CreateAccountOperationSpec.scala │ ├── CreateClaimableBalanceOperationSpec.scala │ ├── CreatePassiveSellOfferOperationSpec.scala │ ├── EndSponsoringReservesOperationSpec.scala │ ├── InflationOperationSpec.scala │ ├── JsonSnippets.scala │ ├── ManageBuyOfferOperationSpec.scala │ ├── ManageDataOperationSpec.scala │ ├── ManageSellOfferOperationSpec.scala │ ├── PathPaymentStrictReceiveOperationSpec.scala │ ├── PathPaymentStrictSendOperationSpec.scala │ ├── PaymentOperationSpec.scala │ ├── RevokeSponsorshipOperationSpec.scala │ └── SetOptionsOperationSpec.scala ├── response │ ├── AccountEffectResponseSpec.scala │ ├── AccountResponseSpec.scala │ ├── FeeStatsResponseSpec.scala │ ├── LedgerResponseSpec.scala │ ├── OfferResponseSpec.scala │ ├── OrderBookSpec.scala │ ├── SignerEffectResponseSpec.scala │ ├── TradeEffectResponseSpec.scala │ ├── TransactionResponseSpec.scala │ ├── TrustLineAuthEffectResponseSpec.scala │ └── TrustLineEffectResponseSpec.scala └── result │ ├── OperationResultSpec.scala │ ├── TransactionApprovedSpec.scala │ ├── TransactionRejectedSpec.scala │ └── TransactionResultSpec.scala └── util ├── ByteArraysSpec.scala └── FakeClock.scala /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches-ignore: [] 6 | pull_request: 7 | branches-ignore: [] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up JDK 1.8 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 1.8 20 | - name: Run tests 21 | run: sbt coverage test it:test coverageReport paradox 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .bsp 3 | .idea 4 | *.swp 5 | *.log 6 | target 7 | .test-account 8 | -------------------------------------------------------------------------------- /DEV.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## Ideas 4 | 5 | Looking for something to work on? Check the [issues list](https://github.com/Synesso/scala-stellar-sdk/issues). 6 | 7 | 8 | ## Compiling & Testing. 9 | 10 | `sbt test it:test` 11 | 12 | To generate a test coverage report: 13 | 14 | `sbt coverage test it:test coverageReport` 15 | 16 | 17 | ## Deployment Checklist 18 | 19 | 1. Update & push CHANGELOG.md & README.md (version number). If necessary, update any code examples in the README.md for API changes. 20 | 2. Create pending release in github. 21 | 3. `git pull` locally and on the tagged commit `sbt ghpagesPushSite` 22 | 4. Check [documentation](https://synesso.github.io/scala-stellar-sdk/) has correct tag and no missing icons. Check links (todo: automate) 23 | 5. Check that the artifact can be fetched from jitpack (todo: script this step) 24 | 6. Update `stellar-sdk-source-testing` project with newest version (todo: always use latest?) -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | notify: 3 | gitter: 4 | default: 5 | url: "https://webhooks.gitter.im/e/01e938f30d6dfaaf7a2d" 6 | threshold: 98% -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - sbt -Dsbt.log.noformat=true clean publishM2 3 | -------------------------------------------------------------------------------- /project/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Synesso/scala-stellar-sdk/f6335925bdcb3d20ab8aa996f6d93ffcf09303d3/project/.gitignore -------------------------------------------------------------------------------- /project/.gnupg/.gitignore: -------------------------------------------------------------------------------- 1 | *gpg 2 | *tgz 3 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") 2 | addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.9.2") 3 | addSbtPlugin("com.lightbend.paradox" % "sbt-paradox-apidoc" % "0.10") 4 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") 5 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.1") 6 | addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") 7 | addSbtPlugin("io.github.jonas" % "sbt-paradox-material-theme" % "0.6.0") 8 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.8.2") 9 | -------------------------------------------------------------------------------- /src/it/bin/stellar_standalone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CONTAINER=stellar/quickstart:latest 4 | PROTOCOL_VERSION=17 5 | 6 | function container_started { 7 | local state 8 | state=$(curl -s "http://localhost:11626/info" | jq -r .info.state) 9 | if [ "${state}" == "Synced!" ]; then 10 | return 0 11 | fi 12 | return 1 13 | } 14 | 15 | function service_upgraded { 16 | local version 17 | version=$(curl -s "http://localhost:8000/ledgers?order=desc&limit=1" | jq '._embedded.records[].protocol_version') 18 | echo "Current: ${version}" 19 | if [ "$version" == "$PROTOCOL_VERSION" ]; then 20 | return 0 21 | fi 22 | return 1 23 | } 24 | 25 | if [[ "$1" == true ]]; then 26 | db_port='-p 5433:5432' 27 | fi 28 | 29 | docker pull $CONTAINER 30 | docker stop stellar 31 | sleep 1 32 | docker run --rm -d \ 33 | -e LOG_LEVEL="debug" \ 34 | -e ENABLE_ASSET_STATS="true" \ 35 | -p "8000:8000" -p "11626:11626" $db_port \ 36 | --name stellar $CONTAINER --standalone 37 | while ! container_started; do 38 | sleep 1 39 | done 40 | echo "Container started" 41 | 42 | root_doc=$(curl -s "http://localhost:8000/") 43 | horizon_version=$(echo $root_doc | jq -r .horizon_version) 44 | core_protocol_version=$(echo $root_doc | jq -r .core_supported_protocol_version) 45 | echo "Horizon version: ${horizon_version}" 46 | echo "Protocol: ${core_protocol_version}" 47 | 48 | upgrade_response=$(curl -s "http://localhost:11626/upgrades?mode=set&protocolversion=$PROTOCOL_VERSION&upgradetime=1970-01-01T00:00:00Z") 49 | echo ${upgrade_response} 50 | echo "Upgrading to ${PROTOCOL_VERSION}" 51 | while ! service_upgraded; do 52 | sleep 5 53 | done 54 | echo "Protocol upgraded" 55 | docker logs stellar 56 | -------------------------------------------------------------------------------- /src/it/scala/stellar/sdk/AdHocPublicNetworkCases.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import org.specs2.concurrent.ExecutionEnv 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.model.{Memo, MemoText} 6 | 7 | import scala.concurrent.duration._ 8 | class AdHocPublicNetworkCases(implicit val ee: ExecutionEnv) extends Specification { 9 | 10 | private implicit val network: Network = PublicNetwork 11 | 12 | "memo text with unusual encoding" should { 13 | "be parsed" >> { 14 | PublicNetwork.transaction("424ac176edc448b3e87db0eae61ba621f5f7b5217c10b6016f74d85cdcaafb0a") 15 | .map(_.memo) must beLikeA[Memo]({ case m: MemoText => 16 | m.text mustEqual "I�T��@�v\u0018[��:�\u0011)�\u0014jZ" 17 | }).awaitFor(5.seconds) 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/it/scala/stellar/sdk/FederationLookupSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import org.specs2.concurrent.ExecutionEnv 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.model.response.FederationResponse 6 | 7 | import scala.concurrent.duration._ 8 | 9 | class FederationLookupSpec(implicit ec: ExecutionEnv) extends Specification { 10 | 11 | "federation server lookup" should { 12 | "find result by name" >> { 13 | FederationServer("https://keybase.io/_/api/1.0/stellar/federation.json") 14 | .byName("jem*keybase.io") must beSome( 15 | FederationResponse( 16 | address = "jem*keybase.io", 17 | account = KeyPair.fromAccountId("GBRAZP7U3SPHZ2FWOJLHPBO3XABZLKHNF6V5PUIJEEK6JEBKGXWD2IIE") 18 | )).awaitFor(10.seconds) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/it/scala/stellar/sdk/FriendBotSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import org.specs2.concurrent.ExecutionEnv 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.model.op.AccountMergeOperation 6 | import stellar.sdk.model.{Account, AccountId, NativeAmount, TimeBounds, Transaction} 7 | 8 | import scala.concurrent.Await 9 | import scala.concurrent.duration._ 10 | import scala.util.Try 11 | 12 | class FriendBotSpec(implicit ee: ExecutionEnv) extends Specification { 13 | 14 | "the test network" should { 15 | "allow account funding via friendbot" >> { 16 | // #friendbot_example 17 | val kp = KeyPair.random 18 | val response = TestNetwork.fund(kp) 19 | // #friendbot_example 20 | 21 | val r = Await.result(response, 1 minute) 22 | 23 | // roll it back in, to be a good testnet citizen 24 | implicit val n = TestNetwork 25 | val giveItBack = for { 26 | accn <- n.account(kp) 27 | friendbot <- response.map(_.transaction.transaction.source) 28 | response <- Transaction(accn, 29 | maxFee = NativeAmount(100), 30 | timeBounds = TimeBounds.Unbounded 31 | ).add(AccountMergeOperation(friendbot.id)).sign(kp).submit() 32 | } yield response 33 | Await.result(giveItBack, 1 minute) 34 | 35 | r.isSuccess must beTrue 36 | } 37 | 38 | "be used to serialise a transaction" >> { 39 | val accn = KeyPair.fromPassphrase("an account") 40 | val sequence = 1 41 | val txn = { 42 | // #new_transaction_example 43 | implicit val network = TestNetwork 44 | val account = Account(AccountId(accn.publicKey), sequence) 45 | Transaction(account, maxFee = NativeAmount(100), timeBounds = TimeBounds.Unbounded) 46 | // #new_transaction_example 47 | } 48 | Try(txn.encodeXdrString) must beASuccessfulTry[String] 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/it/scala/stellar/sdk/HelloWorld.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import stellar.sdk.PublicNetwork 4 | import stellar.sdk.model.{Asc, Desc, Now, Trade} 5 | 6 | import java.time.ZonedDateTime 7 | import scala.concurrent.{Await, Future} 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.concurrent.duration.{Duration, DurationInt} 10 | 11 | object HelloWorld extends App { 12 | def traverseStream[A, B](in: LazyList[A])(fn: A => Future[B]): Future[LazyList[B]] = { 13 | in match { 14 | case LazyList.cons(head, tail) => 15 | for { 16 | newHead <- fn(head) 17 | newTail <- traverseStream(tail)(fn) 18 | } yield newHead #:: newTail 19 | case _ => 20 | Future.successful(LazyList.empty) 21 | } 22 | } 23 | 24 | // Works 25 | for { 26 | trades <- PublicNetwork.trades(Now, Asc) 27 | } yield { 28 | val result = traverseStream(trades) { 29 | case Trade(id, time, offerId, baseOfferId, counterOfferId, _, _, _, _, _) => { 30 | Future.successful(System.out.println(s"New trade coming in Trade($id, $time, $offerId)")) 31 | } 32 | } 33 | val stream = Await.result(result, Duration.Inf) 34 | } 35 | 36 | val timeWindow = ZonedDateTime.now().minusMinutes(65) 37 | } -------------------------------------------------------------------------------- /src/it/scala/stellar/sdk/TestNetworkJourney.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import org.specs2.concurrent.ExecutionEnv 4 | import org.specs2.mutable.Specification 5 | import org.specs2.specification.BeforeAfterAll 6 | import stellar.sdk.model._ 7 | import stellar.sdk.model.op.PaymentOperation 8 | 9 | import scala.concurrent.Await 10 | import scala.concurrent.duration._ 11 | 12 | class TestNetworkJourney(implicit ee: ExecutionEnv) extends Specification with BeforeAfterAll { 13 | 14 | implicit val network: TestNetwork.type = TestNetwork 15 | 16 | private val testAccounts = new TestAccounts(6) 17 | 18 | def beforeAll(): Unit = testAccounts.open() 19 | def afterAll(): Unit = testAccounts.close() 20 | 21 | "a client" should { 22 | "be able to send a payment" >> { 23 | val List(senderKey, recipientKey) = testAccounts.take(2) 24 | val response = for { 25 | sender <- network.account(senderKey) 26 | payment = PaymentOperation(recipientKey.toAccountId, Amount.lumens(2)) 27 | txn = Transaction(sender, List(payment), NoMemo, TimeBounds.Unbounded, Amount.lumens(1)) 28 | response <- txn.sign(senderKey).submit() 29 | } yield response 30 | 31 | response.map(_.isSuccess) must beTrue.await(0, 1.minute) 32 | } 33 | 34 | /* TODO - Restructure FeeBumps referring to https://github.com/stellar/stellar-protocol/blob/master/core/cap-0015.md#validity 35 | "be able to fee bump a v0 transaction" >> { 36 | val List(senderKey, recipientKey) = testAccounts.take(2) 37 | val sender = Await.result(network.account(senderKey), 10.seconds) 38 | val payment = PaymentOperation(recipientKey.toAccountId, Amount.lumens(2)) 39 | val signedTransaction = Transaction(sender, List(payment), NoMemo, TimeBounds.Unbounded, NativeAmount(100)) 40 | .sign(senderKey) 41 | val parsedV0Txn: SignedTransaction = SignedTransaction.decodeXdr(signedTransaction.xdr) 42 | val bumpedTxn = parsedV0Txn.bumpFee(NativeAmount(500), recipientKey) 43 | val response = Await.result(bumpedTxn.submit(), 20.seconds) 44 | response.isSuccess must beTrue 45 | } 46 | */ 47 | } 48 | 49 | "a signing request for a transaction" should { 50 | "be generated, parsed and executed" >> { 51 | val List(senderKey, recipientKey) = testAccounts.take(2) 52 | val sender = Await.result(network.account(senderKey), 10.seconds) 53 | 54 | // Recipient prepares a signing request for themselves to be paid. 55 | val payment = PaymentOperation(recipientKey.toAccountId, Amount.lumens(2)) 56 | val transaction = Transaction(sender, List(payment), NoMemo, TimeBounds.Unbounded, NativeAmount(100)) 57 | val requestUrl = transaction.signingRequest.toUrl 58 | 59 | // Sender parses the URL, signs the request and submits to the network 60 | val request = TransactionSigningRequest(requestUrl) 61 | val parsedTransaction = request.transaction 62 | val response = network.submit(parsedTransaction.sign(senderKey)) 63 | 64 | response.map(_.isSuccess) must beTrue.await(0, 1.minute) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/it/scala/stellar/sdk/TransactionLedgerEntriesSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import org.specs2.concurrent.ExecutionEnv 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.model.ledger.{LedgerEntryChange, TransactionLedgerEntries} 6 | import stellar.sdk.model.{Desc, Now} 7 | 8 | import scala.concurrent.Await 9 | import scala.concurrent.duration._ 10 | import scala.util.Try 11 | 12 | class TransactionLedgerEntriesSpec(ee: ExecutionEnv) extends Specification { 13 | 14 | import ee.ec 15 | 16 | val last100 = Await.result(PublicNetwork.transactions(cursor = Now, order = Desc), 1.minute).take(100).toList 17 | 18 | "transaction meta parsing" should { 19 | "parse the last 100 without any issues" >> { 20 | Try { 21 | last100.map(_.ledgerEntries) 22 | } must beSuccessfulTry[Seq[TransactionLedgerEntries]] 23 | } 24 | } 25 | 26 | "fee ledger entry parsing" should { 27 | "parse the last 100 without any issues" >> { 28 | Try { 29 | last100.flatMap(_.feeLedgerEntries) 30 | } must beSuccessfulTry[Seq[LedgerEntryChange]] 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/paradox/authentication.md: -------------------------------------------------------------------------------- 1 | # Authentication 2 | 3 | In [SEP-0010](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#preamble) Stellar defines 4 | a mechanism for using the cryptographic properties of transactions in order to present and fulfil an authentication 5 | challenge. 6 | 7 | The server constructs a challenge with an @apidoc[AuthChallenger]. The challenge can be serialised to and deserialised 8 | from JSON. 9 | 10 | @@snip [ChallengeSpec.scala](../../test/scala/stellar/sdk/auth/ChallengeSpec.scala) { #challenge_to_from_json_example } 11 | 12 | The client can meet the challenge by signing the presented transaction. The server can then 13 | 14 | @@snip [ChallengeSpec.scala](../../test/scala/stellar/sdk/auth/ChallengeSpec.scala) { #auth_challenge_success_example } 15 | 16 | It is important that the client validate the properties of the challenge transaction before signing and returning. 17 | For example, the transaction should have a sequence number of zero to prevent it from being submittable to the network. 18 | See the [SEP-0010 specification](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md#abstract) 19 | for up-to-date requirements. 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/paradox/domains.md: -------------------------------------------------------------------------------- 1 | # Domains 2 | 3 | Any domain with an interest in the Stellar network can publish their network information on their 4 | website. This format is defined by [SEP-0001](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md) and is both human and machine readable. 5 | 6 | The SDK can parse all known fields as per v2.0.0 of the specification. 7 | 8 | @@snip [DomainInfoItSpec.scala](../../it/scala/stellar/sdk/DomainInfoItSpec.scala) { #domain_info_example } 9 | 10 | The domain info spec is rich with data about the organizations that use the Stellar network. 11 | It is worth bearing in mind that the document is not mandatory and can contain errors. Only fields that 12 | align with the SEP will be parsed. 13 | 14 | Continue reading to learn how to use Stellar transactions for @ref:[authentication](authentication.md). 15 | -------------------------------------------------------------------------------- /src/main/paradox/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | material.color.primary: indigo 3 | material.color.accent: blue 4 | material.logo.icon: near_me 5 | material.copyright: © Jem Mawson. Licensed under Apache 2.0 6 | --- 7 | 8 | # Stellar SDK for Scala 9 | 10 | This is the SDK for performing [Stellar](https://www.stellar.org/) operations via Scala. It provides the ability to 11 | access the Stellar network via Horizon instances to build and submit transactions, query the state of the network and 12 | stream updates. 13 | 14 | Scala developers may prefer to use this SDK because it provides a more natural API for Scala developers than the 15 | official Java SDK 16 | 17 | The code throughout this documentation is compiled against Scala $scalaBinaryVersion$. 18 | 19 | 20 | ## Quick-start 21 | 22 | Add the jitpack resolver. 23 | 24 | `resolvers += "jitpack" at "https://jitpack.io"` 25 | 26 | Then, add the SDK via your dependency management tool. 27 | 28 | @@dependency[sbt,Maven,Gradle] { 29 | group="$organization$" 30 | artifact="$name$_2.13" 31 | version="$version$" 32 | } 33 | 34 | Creating an account on the public test network. 35 | 36 | @@snip [FriendBotSpec.scala](../../it/scala/stellar/sdk/FriendBotSpec.scala) { #friendbot_example } 37 | 38 | Fetching the details of an account. 39 | 40 | @@snip [LocalNetworkIntegrationSpec.scala](../../it/scala/stellar/sdk/LocalNetworkIntegrationSpec.scala) { #account_details_example } 41 | 42 | Submitting a payment. 43 | 44 | @@snip [NetworkSpec.scala](../../it/scala/stellar/sdk/LocalNetworkIntegrationSpec.scala) { #payment_example } 45 | 46 | For more detailed coverage, continue by reading about @ref:[KeyPairs](key_pairs.md). 47 | 48 | ## API 49 | 50 | Please enjoy the [scaladoc](latest/api/stellar/sdk) for this release. 51 | 52 | 53 | > ### Deprecation warning 54 | 55 | > At this stage, classes and interfaces are likely to be refined. Minor releases may break backwards compatibility 56 | with minimal notice until v1.0.0. 57 | 58 | Check the [CHANGELOG](https://github.com/Synesso/scala-stellar-sdk/blob/master/CHANGELOG.md#changelog) for details of 59 | breaking changes. 60 | 61 | 62 | ## Contributing 63 | 64 | Contributions are warmly welcomed. Please feel free to contribute by reporting [issues](https://github.com/Synesso/scala-stellar-sdk/issues) 65 | you find, or by suggesting changes to the code. Or feel free to add your own features/issues to that list. 66 | 67 | You can [contact me on KeyBase](https://keybase.io/jem/chat). 68 | 69 | 70 | @@@ index 71 | 72 | * [Transacting](transacting.md) 73 | * [Key Pairs](key_pairs.md) 74 | * [Queries](queries.md) 75 | * [Sources](sources.md) 76 | * [Domains](domains.md) 77 | * [Authentication](authentication.md) 78 | 79 | @@@ 80 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | scala-stellar-sdk { 2 | akka.daemonic = on 3 | akka.http { 4 | host-connection-pool.response-entity-subscription-timeout = 100.seconds 5 | sse { 6 | max-event-size = 65536 7 | max-line-size = 32768 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/FederationServer.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import java.net.HttpURLConnection.HTTP_NOT_FOUND 4 | 5 | import com.typesafe.scalalogging.LazyLogging 6 | import okhttp3.{Headers, HttpUrl, OkHttpClient, Request} 7 | import org.json4s.native.{JsonMethods, Serialization} 8 | import org.json4s.{Formats, NoTypeHints} 9 | import stellar.sdk.inet.RestException 10 | import stellar.sdk.model.response.{FederationResponse, FederationResponseDeserialiser} 11 | 12 | import scala.concurrent.{ExecutionContext, Future} 13 | import scala.util.{Failure, Success, Try} 14 | 15 | case class FederationServer(base: HttpUrl) extends LazyLogging { 16 | 17 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + FederationResponseDeserialiser 18 | private val client = new OkHttpClient() 19 | private val headers = Headers.of( 20 | "X-Client-Name", BuildInfo.name, 21 | "X-Client-Version", BuildInfo.version) 22 | 23 | def byName(name: String)(implicit ec: ExecutionContext): Future[Option[FederationResponse]] = 24 | fetchFederationResponse(base.newBuilder() 25 | .addQueryParameter("q", name) 26 | .addQueryParameter("type", "name") 27 | .build(), _.copy(address = name)) 28 | 29 | def byAccount(account: PublicKey)(implicit ec: ExecutionContext): Future[Option[FederationResponse]] = 30 | fetchFederationResponse(base.newBuilder() 31 | .addQueryParameter("q", account.accountId) 32 | .addQueryParameter("type", "id") 33 | .build(), _.copy(account = account)) 34 | 35 | 36 | private def fetchFederationResponse(url: HttpUrl, fillIn: FederationResponse => FederationResponse) 37 | (implicit ec: ExecutionContext): Future[Option[FederationResponse]] = 38 | Future(client.newCall(new Request.Builder().url(url).headers(headers).build()).execute()) 39 | .map { response => 40 | response.code() match { 41 | case HTTP_NOT_FOUND => None 42 | case e if e >= 500 => throw RestException(response.body().string()) 43 | case _ => 44 | Try(response.body().string()) 45 | .map(JsonMethods.parse(_)) 46 | .map(_.extract[FederationResponse]) 47 | .map(fillIn) 48 | .map(validate) match { 49 | case Success(fr) => Some(fr) 50 | case Failure(t) => throw RestException("Could not parse document as FederationResponse.", t) 51 | } 52 | } 53 | } 54 | 55 | 56 | private def validate(fr: FederationResponse): FederationResponse = { 57 | if (fr.account == null) throw RestException(s"Document did not contain account_id") 58 | if (fr.address == null) throw RestException(s"Document did not contain stellar_address") 59 | fr 60 | } 61 | } 62 | 63 | object FederationServer { 64 | def apply(uriString: String): FederationServer = new FederationServer(HttpUrl.parse(uriString)) 65 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/InvalidTransactionException.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | /** 4 | * Indicates that the transaction was not submittable because of an invariant check failure. 5 | */ 6 | case class InvalidTransactionException(message: String) extends Exception(message) 7 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/PublicNetwork.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import okhttp3.HttpUrl 4 | import stellar.sdk.inet.{HorizonAccess, OkHorizon} 5 | 6 | /** 7 | * The public Stellar production network. 8 | */ 9 | case object PublicNetwork extends Network { 10 | override val passphrase = "Public Global Stellar Network ; September 2015" 11 | override val horizon: HorizonAccess = new OkHorizon(HttpUrl.parse("https://horizon.stellar.org")) 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/StandaloneNetwork.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import okhttp3.HttpUrl 4 | import stellar.sdk.inet.{HorizonAccess, OkHorizon} 5 | 6 | /** 7 | * A network that represents the stand-alone docker image for Horizon & core. 8 | * 9 | * @see [[https://github.com/stellar/docker-stellar-core-horizon]] 10 | */ 11 | case class StandaloneNetwork(base: HttpUrl) extends Network with FriendBot { 12 | override val passphrase: String = "Standalone Network ; February 2017" 13 | override val horizon: HorizonAccess = new OkHorizon(base) 14 | } 15 | 16 | 17 | /** 18 | * A network that represents the stand-alone docker image for Horizon & core, on the default docker port of 8000. 19 | * 20 | * @see [[https://github.com/stellar/docker-stellar-core-horizon]] 21 | */ 22 | object LocalStandaloneNetwork extends StandaloneNetwork(HttpUrl.parse("http://localhost:8000")) 23 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/TestNetwork.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import okhttp3.HttpUrl 4 | import stellar.sdk.inet.{HorizonAccess, OkHorizon} 5 | 6 | /** 7 | * The public Stellar test network. 8 | */ 9 | case object TestNetwork extends Network with FriendBot { 10 | override val passphrase = "Test SDF Network ; September 2015" 11 | override val horizon: HorizonAccess = new OkHorizon( 12 | HttpUrl.parse("https://horizon-testnet.stellar.org")) 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/inet/HorizonAccess.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.inet 2 | 3 | import org.json4s.CustomSerializer 4 | import stellar.sdk.model._ 5 | import stellar.sdk.model.response._ 6 | 7 | import scala.concurrent.{ExecutionContext, Future} 8 | import scala.reflect.ClassTag 9 | 10 | 11 | trait HorizonAccess { 12 | 13 | def post(txn: SignedTransaction)(implicit ec: ExecutionContext): Future[TransactionPostResponse] 14 | 15 | def get[T: ClassTag](path: String, params: Map[String, String] = Map.empty) 16 | (implicit ec: ExecutionContext, m: Manifest[T]): Future[T] 17 | 18 | def getStream[T: ClassTag](path: String, de: CustomSerializer[T], cursor: HorizonCursor, order: HorizonOrder, params: Map[String, String] = Map.empty) 19 | (implicit ec: ExecutionContext, m: Manifest[T]): Future[LazyList[T]] 20 | 21 | def getSeq[T: ClassTag](path: String, de: CustomSerializer[T], params: Map[String, String] = Map.empty) 22 | (implicit ec: ExecutionContext, m: Manifest[T]): Future[LazyList[T]] 23 | 24 | } 25 | 26 | case class RestException(message: String, t: Throwable = None.orNull) extends Exception(message, t) 27 | 28 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/inet/HorizonServerError.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.inet 2 | 3 | import okhttp3.HttpUrl 4 | import org.json4s.native.JsonMethods 5 | import org.json4s.{DefaultFormats, Formats, JObject, JValue} 6 | 7 | import scala.concurrent.duration.Duration 8 | import scala.util.Try 9 | 10 | case class HorizonServerError(uri: HttpUrl, body: JObject)(implicit val formats: Formats) extends Exception( 11 | s"Server error when communicating with Horizon. $uri -> ${ 12 | implicit val formats: Formats = DefaultFormats 13 | Try((body \ "detail").extract[String]).getOrElse(JsonMethods.compact(JsonMethods.render(body))) 14 | }" 15 | ) 16 | 17 | case class HorizonEntityNotFound(uri: HttpUrl, body: JValue)(implicit val formats: Formats) extends Exception( 18 | s"Requested entity was not found in Horizon. $uri -> ${ 19 | implicit val formats: Formats = DefaultFormats 20 | Try((body \ "detail").extract[String]).getOrElse(JsonMethods.compact(JsonMethods.render(body))) 21 | }" 22 | ) 23 | 24 | case class HorizonRateLimitExceeded(uri: HttpUrl, retryAfter: Duration)(implicit val formats: Formats) extends Exception( 25 | s"Horizon request rate limit was exceeded. Try again in $retryAfter" 26 | ) 27 | 28 | case class HorizonBadRequest(uri: HttpUrl, body: String) extends Exception( 29 | s"Bad request. $uri -> ${ 30 | implicit val formats: Formats = DefaultFormats 31 | Try( 32 | (JsonMethods.parse(body) \ "extras" \ "reason").extract[String] 33 | ).getOrElse(body) 34 | }") 35 | 36 | case class FailedResponse(cause: String) extends Exception(cause) 37 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/inet/Page.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.inet 2 | 3 | import okhttp3.HttpUrl 4 | import org.json4s.JsonAST.JArray 5 | import org.json4s.{DefaultFormats, Formats, JObject, JValue} 6 | import stellar.sdk.model.response.ResponseParser 7 | 8 | /** 9 | * A page of results 10 | */ 11 | case class Page[T](xs: List[T], nextLink: Option[HttpUrl]) 12 | 13 | case class RawPage(inner: List[JValue], nextLink: Option[String]) { 14 | def parse[T](sourceLink: HttpUrl)(implicit formats: Formats, m: Manifest[T]): Page[T] = 15 | Page(inner.map(_.extract[T]), nextLink.map(HttpUrl.parse).filter(_ != sourceLink)) 16 | } 17 | 18 | object RawPageDeserializer extends ResponseParser[RawPage]({ o: JObject => 19 | implicit val formats = DefaultFormats 20 | 21 | val nextLink = (o \ "_links" \ "next" \ "href").extractOpt[String] 22 | val JArray(records) = o \ "_embedded" \ "records" 23 | 24 | RawPage(records, nextLink) 25 | }) 26 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/inet/PageParser.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.inet 2 | 3 | import java.net.HttpURLConnection.{HTTP_BAD_REQUEST, HTTP_NOT_FOUND} 4 | 5 | import okhttp3.HttpUrl 6 | import org.json4s.{DefaultFormats, Formats} 7 | import org.json4s.native.JsonMethods 8 | 9 | import scala.reflect.ClassTag 10 | 11 | object PageParser { 12 | 13 | def parse[T: ClassTag](url: HttpUrl, responseCode: Int, body: => String) 14 | (implicit m: Manifest[T], customFormats: Formats): Page[T] = { 15 | 16 | responseCode match { 17 | case HTTP_NOT_FOUND => Page(List.empty[T], None) 18 | case HTTP_BAD_REQUEST => throw HorizonBadRequest(url, body) 19 | case _ => 20 | JsonMethods.parse(body) 21 | .extract[RawPage] 22 | .parse[T](url) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/key/HDNode.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.key 2 | 3 | import okio.{Buffer, ByteString} 4 | import stellar.sdk.KeyPair 5 | 6 | class HDNode(val privateKey: ByteString, val chainCode: ByteString) { 7 | def asKeyPair: KeyPair = KeyPair.fromSecretSeed(privateKey) 8 | 9 | def deriveChild(index: Int, ix: Int*): HDNode = { 10 | ix.foldLeft(deriveChild(index)) { case (node, index) => node.deriveChild(index) } 11 | } 12 | 13 | private def deriveChild(index: Int): HDNode = { 14 | val key = new Buffer() 15 | .write(Array(0.toByte)) 16 | .write(privateKey) 17 | .writeInt((HDNode.hardenedMinIndex + index).toInt) 18 | .readByteString() 19 | HDNode.fromHmac(key.hmacSha512(chainCode)) 20 | } 21 | } 22 | 23 | object HDNode { 24 | 25 | protected val hardenedMinIndex = 0x80000000L 26 | 27 | def fromEntropy(entropy: ByteString): HDNode = 28 | HDNode.fromHmac(entropy.hmacSha512(ByteString.encodeUtf8("ed25519 seed"))) 29 | 30 | def fromHmac(hmac: ByteString): HDNode = { 31 | val bytes = hmac.toByteArray 32 | new HDNode(new ByteString(bytes.take(32)), new ByteString(bytes.drop(32))) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/key/WordList.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.key 2 | 3 | import scala.io.Source 4 | 5 | trait WordList { 6 | def indexOf(word: String): Option[Int] 7 | def wordAt(i: Int): String 8 | def contains(word: String): Boolean = indexOf(word).isDefined 9 | def separator: String 10 | } 11 | 12 | class ArrayBackedWordList(source: => Source, val separator: String = " ") extends WordList { 13 | lazy val words: Array[String] = source.getLines().toArray 14 | 15 | // TODO (jem) - WordList spec that ensures index can be found with normalized variants. 16 | override def indexOf(word: String): Option[Int] = Some(words.indexOf(word)).filter(_ >= 0) 17 | 18 | override def wordAt(i: Int): String = { 19 | require(i >= 0 && i < words.length, s"Word index $i is out of range.") 20 | words(i) 21 | } 22 | } 23 | 24 | object ChineseSimplifiedWords extends ArrayBackedWordList(Source.fromResource("wordlists/chinese_simplified.txt")) 25 | object ChineseTraditionalWords extends ArrayBackedWordList(Source.fromResource("wordlists/chinese_traditional.txt")) 26 | object CzechWords extends ArrayBackedWordList(Source.fromResource("wordlists/czech.txt")) 27 | object EnglishWords extends ArrayBackedWordList(Source.fromResource("wordlists/english.txt")) 28 | object FrenchWords extends ArrayBackedWordList(Source.fromResource("wordlists/french.txt")) 29 | object ItalianWords extends ArrayBackedWordList(Source.fromResource("wordlists/italian.txt")) 30 | object JapaneseWords extends ArrayBackedWordList(Source.fromResource("wordlists/japanese.txt"), "\u3000") 31 | object KoreanWords extends ArrayBackedWordList(Source.fromResource("wordlists/korean.txt")) 32 | object SpanishWords extends ArrayBackedWordList(Source.fromResource("wordlists/spanish.txt")) 33 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Account.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | /** 4 | * Represents an account in Stellar network with its sequence number. 5 | */ 6 | case class Account(id: AccountId, sequenceNumber: Long) { 7 | def withIncSeq: Account = this.copy(sequenceNumber = this.sequenceNumber + 1) 8 | } 9 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Amount.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.math.{MathContext, RoundingMode} 4 | import java.util.Locale 5 | 6 | import org.json4s.{DefaultFormats, Formats, JObject} 7 | 8 | import scala.util.Try 9 | 10 | sealed trait Amount { 11 | val units: Long 12 | val asset: Asset 13 | 14 | def toDisplayUnits: String = "%.7f".formatLocal(Locale.ROOT, BigDecimal(units) / Amount.toIntegralFactor) 15 | } 16 | 17 | case class NativeAmount(units: Long) extends Amount { 18 | override val asset: Asset = NativeAsset 19 | override def toString: String = s"$toDisplayUnits XLM" 20 | } 21 | 22 | case class IssuedAmount(units: Long, asset: NonNativeAsset) extends Amount { 23 | override def toString: String = s"$toDisplayUnits $asset" 24 | } 25 | 26 | object Amount { 27 | implicit val formats: Formats = DefaultFormats 28 | private val decimalPlaces = 7 29 | private val toIntegralFactor = BigDecimal(math.pow(10, decimalPlaces)) 30 | 31 | def toBaseUnits(d: Double): Try[Long] = toBaseUnits(BigDecimal(d)) 32 | 33 | def toBaseUnits(s: String): Try[Long] = Try(BigDecimal(s)).flatMap(toBaseUnits) 34 | 35 | def toBaseUnits(bd: BigDecimal): Try[Long] = Try { 36 | (bd * toIntegralFactor.round(new MathContext(0, RoundingMode.DOWN))).toLongExact 37 | } 38 | 39 | def apply(units: Long, asset: Asset): Amount = { 40 | asset match { 41 | case NativeAsset => NativeAmount(units) 42 | case a: NonNativeAsset => IssuedAmount(units, a) 43 | } 44 | } 45 | 46 | /** 47 | * Convenience method to create native amount denoted in lumens. 48 | * 49 | * @param units quantity of lumens 50 | * @return NativeAmount of the given quantity 51 | */ 52 | def lumens(units: Double): NativeAmount = toBaseUnits(units).map(NativeAmount).getOrElse( 53 | throw new IllegalArgumentException(s"Too many digits in fractional portion of $units. Limit is $decimalPlaces") 54 | ) 55 | 56 | def doubleFromString(o: JObject, key: String): Double = (o \ key).extract[String].toDouble 57 | 58 | def parseNativeAmount(o: JObject, key: String): NativeAmount = { 59 | NativeAmount(Amount.toBaseUnits(doubleFromString(o, key)).get) 60 | } 61 | 62 | def parseIssuedAmount(o: JObject, label: String): IssuedAmount = parseAmount(o, label).asInstanceOf[IssuedAmount] 63 | 64 | def parseAmount(o: JObject, label: String = "amount", assetPrefix: String = ""): Amount = { 65 | val units = Amount.toBaseUnits(doubleFromString(o, label)).get 66 | Asset.parseAsset(assetPrefix, o) match { 67 | case nna: NonNativeAsset => IssuedAmount(units, nna) 68 | case NativeAsset => NativeAmount(units) 69 | } 70 | } 71 | } 72 | 73 | object IssuedAmount { 74 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Balance.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import stellar.sdk.PublicKey 4 | 5 | case class Balance( 6 | amount: Amount, 7 | limit: Option[Long] = None, 8 | buyingLiabilities: Long = 0, 9 | sellingLiabilities: Long = 0, 10 | authorized: Boolean = false, 11 | authorizedToMaintainLiabilities: Boolean = false, 12 | sponsor: Option[PublicKey] = None 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/ClaimableBalance.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.time.Instant 4 | 5 | import okio.ByteString 6 | import org.json4s.native.JsonMethods 7 | import org.json4s.{DefaultFormats, Formats, JObject} 8 | import org.stellar.xdr.ClaimableBalanceID 9 | import stellar.sdk.model.ClaimableBalance.parseClaimableBalance 10 | import stellar.sdk.model.response.ResponseParser 11 | import stellar.sdk.{KeyPair, PublicKeyOps} 12 | 13 | case class ClaimableBalance( 14 | id: ClaimableBalanceId, 15 | amount: Amount, 16 | sponsor: PublicKeyOps, 17 | claimants: List[Claimant], 18 | lastModifiedLedger: Long, 19 | lastModifiedTime: Instant 20 | ) 21 | 22 | object ClaimableBalance { 23 | implicit val formats: Formats = DefaultFormats + ClaimantDeserializer 24 | 25 | def parseClaimableBalance(o: JObject): ClaimableBalance = { 26 | val idString = (o \ "id").extract[String] 27 | val value = ClaimableBalanceHashId(ByteString.decodeHex(idString.drop(8))) 28 | ClaimableBalance( 29 | id = value, 30 | amount = Amount.parseAmount(o), 31 | sponsor = KeyPair.fromAccountId((o \ "sponsor").extract[String]), 32 | claimants = (o \ "claimants").extract[List[Claimant]], 33 | lastModifiedLedger = (o \ "last_modified_ledger").extract[Long], 34 | lastModifiedTime = Instant.parse((o \ "last_modified_time").extract[String]) 35 | ) 36 | } 37 | } 38 | 39 | object ClaimableBalanceDeserializer extends ResponseParser[ClaimableBalance](parseClaimableBalance) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/ClaimableBalanceId.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import okio.ByteString 4 | import org.stellar.xdr.{ClaimableBalanceID, ClaimableBalanceIDType, Hash} 5 | 6 | sealed trait ClaimableBalanceId { 7 | def xdr: ClaimableBalanceID 8 | def encodeString: String 9 | } 10 | 11 | object ClaimableBalanceId { 12 | def decodeXdr(xdr: ClaimableBalanceID): ClaimableBalanceId = { 13 | xdr.getDiscriminant match { 14 | case ClaimableBalanceIDType.CLAIMABLE_BALANCE_ID_TYPE_V0 => 15 | ClaimableBalanceHashId(new ByteString(xdr.getV0.getHash)) 16 | } 17 | } 18 | 19 | def decode(xdr: ByteString): ClaimableBalanceId = decodeXdr(ClaimableBalanceID.decode(xdr)) 20 | } 21 | 22 | case class ClaimableBalanceHashId(hash: ByteString) extends ClaimableBalanceId { 23 | def xdr: ClaimableBalanceID = 24 | new ClaimableBalanceID.Builder() 25 | .discriminant(ClaimableBalanceIDType.CLAIMABLE_BALANCE_ID_TYPE_V0) 26 | .v0(new Hash(hash.toByteArray)) 27 | .build() 28 | 29 | override def encodeString: String = xdr.encode().hex() 30 | } 31 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Claimant.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.JsonAST.JObject 4 | import org.json4s.{DefaultFormats, Formats} 5 | import org.stellar.xdr.Claimant.ClaimantV0 6 | import org.stellar.xdr.{ClaimantType, Claimant => XClaimant} 7 | import stellar.sdk.model.response.ResponseParser 8 | import stellar.sdk.{KeyPair, PublicKeyOps} 9 | 10 | sealed trait Claimant { 11 | def xdr: XClaimant 12 | } 13 | 14 | object Claimant { 15 | 16 | def decodeXdr(xdr: XClaimant): Claimant = 17 | xdr.getDiscriminant match { 18 | case ClaimantType.CLAIMANT_TYPE_V0 => 19 | AccountIdClaimant( 20 | accountId = AccountId.decodeXdr(xdr.getV0.getDestination).publicKey, 21 | predicate = ClaimPredicate.decodeXdr(xdr.getV0.getPredicate) 22 | ) 23 | } 24 | } 25 | 26 | case class AccountIdClaimant( 27 | accountId: PublicKeyOps, 28 | predicate: ClaimPredicate 29 | ) extends Claimant { 30 | def xdr: XClaimant = new XClaimant.Builder() 31 | .discriminant(ClaimantType.CLAIMANT_TYPE_V0) 32 | .v0(new ClaimantV0.Builder() 33 | .destination(accountId.toAccountId.xdr) 34 | .predicate(predicate.xdr) 35 | .build()) 36 | .build() 37 | } 38 | 39 | object ClaimantDeserializer extends ResponseParser[Claimant]({ o: JObject => 40 | implicit val formats: Formats = DefaultFormats + ClaimPredicateDeserializer 41 | 42 | AccountIdClaimant( 43 | accountId = KeyPair.fromAccountId((o \ "destination").extract[String]), 44 | predicate = (o \ "predicate").extract[ClaimPredicate] 45 | ) 46 | }) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/FeeBump.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import stellar.sdk.Signature 4 | 5 | case class FeeBump(source: AccountId, fee: NativeAmount, signatures: List[Signature]) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/HorizonCursor.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | sealed trait HorizonCursor { 4 | def paramValue: String 5 | } 6 | 7 | case object Now extends HorizonCursor { 8 | override def paramValue: String = "now" 9 | } 10 | 11 | case class Record(value: Long) extends HorizonCursor { 12 | override def paramValue: String = s"$value" 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/HorizonOrder.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | sealed trait HorizonOrder { 4 | def paramValue: String 5 | } 6 | 7 | case object Asc extends HorizonOrder { 8 | override def paramValue: String = "asc" 9 | } 10 | 11 | case object Desc extends HorizonOrder { 12 | override def paramValue: String = "desc" 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Memo.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import okio.ByteString 4 | import org.stellar.xdr.{Hash, MemoType, Uint64, XdrString, Memo => XMemo} 5 | import stellar.sdk.util.ByteArrays._ 6 | 7 | import scala.util.Try 8 | 9 | sealed trait Memo { 10 | def xdr: XMemo 11 | def encode: LazyList[Byte] = LazyList.from(xdr.encode().toByteArray) 12 | } 13 | 14 | object Memo { 15 | def decodeXdr(xdr: XMemo): Memo = xdr.getDiscriminant match { 16 | case MemoType.MEMO_NONE => NoMemo 17 | case MemoType.MEMO_ID => MemoId(xdr.getId.getUint64) 18 | case MemoType.MEMO_TEXT => MemoText(new ByteString(xdr.getText.getBytes)) 19 | case MemoType.MEMO_HASH => MemoHash(new ByteString(xdr.getHash.getHash)) 20 | case MemoType.MEMO_RETURN => MemoReturnHash(new ByteString(xdr.getRetHash.getHash)) 21 | } 22 | } 23 | 24 | case object NoMemo extends Memo { 25 | override def xdr: XMemo = 26 | new XMemo.Builder() 27 | .discriminant(MemoType.MEMO_NONE) 28 | .build() 29 | } 30 | 31 | case class MemoText(byteString: ByteString) extends Memo { 32 | val Length = 28 33 | val bytes: Array[Byte] = byteString.toByteArray 34 | val text: String = byteString.utf8() 35 | assert(byteString.size() <= Length, s"Text exceeded limit (${byteString.size()}/$Length bytes)") 36 | 37 | override def xdr: XMemo = 38 | new XMemo.Builder() 39 | .discriminant(MemoType.MEMO_TEXT) 40 | .text(new XdrString(bytes)) 41 | .build() 42 | } 43 | 44 | object MemoText { 45 | def apply(text: String): MemoText = MemoText(ByteString.encodeUtf8(text)) 46 | } 47 | 48 | case class MemoId(id: Long) extends Memo { 49 | override def xdr: XMemo = 50 | new XMemo.Builder() 51 | .discriminant(MemoType.MEMO_ID) 52 | .id(new Uint64(id)) 53 | .build() 54 | 55 | def unsignedId: BigInt = BigInt(java.lang.Long.toUnsignedString(id)) 56 | 57 | override def toString = s"MemoId(${unsignedId.toString()})" 58 | } 59 | 60 | sealed trait MemoWithHash extends Memo { 61 | val Length = 32 62 | val bs: ByteString 63 | val bytes: Array[Byte] = bs.toByteArray 64 | 65 | def hex: String = bs.hex() 66 | } 67 | 68 | case class MemoHash(bs: ByteString) extends MemoWithHash { 69 | assert(bs.size() == Length, s"Hash has incorrect length (${bs.size()}/$Length bytes)") 70 | 71 | override def xdr: XMemo = 72 | new XMemo.Builder() 73 | .discriminant(MemoType.MEMO_HASH) 74 | .hash(new Hash(bs.toByteArray)) 75 | .build() 76 | } 77 | 78 | object MemoHash { 79 | def from(hex: String): Try[MemoHash] = Try(MemoHash(ByteString.decodeHex(hex))) 80 | def apply(bs: Array[Byte]): MemoHash = MemoHash(new ByteString(bs)) 81 | } 82 | 83 | case class MemoReturnHash(bs: ByteString) extends MemoWithHash { 84 | assert(bs.size() == Length, s"Hash has incorrect length (${bs.size()}/$Length bytes)") 85 | 86 | override def xdr: XMemo = 87 | new XMemo.Builder() 88 | .discriminant(MemoType.MEMO_RETURN) 89 | .retHash(new Hash(bs.toByteArray)) 90 | .build() 91 | } 92 | 93 | object MemoReturnHash { 94 | def from(hex: String) = Try(MemoReturnHash(ByteString.decodeHex(hex))) 95 | def apply(bs: Array[Byte]): MemoReturnHash = MemoReturnHash(new ByteString(bs)) 96 | } 97 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/OrderBook.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.JsonAST.JObject 4 | import org.json4s.native.JsonMethods._ 5 | import org.json4s.{DefaultFormats, JValue} 6 | import stellar.sdk.KeyPair 7 | import stellar.sdk.model.response.ResponseParser 8 | 9 | case class OrderBook(selling: Asset, buying: Asset, bids: Seq[Order], asks: Seq[Order]) 10 | 11 | case class Order(price: Price, quantity: Long) 12 | 13 | object OrderBookDeserializer extends ResponseParser[OrderBook]({ o: JObject => 14 | implicit val formats = DefaultFormats 15 | 16 | def asset(obj: JValue) = { 17 | def assetCode = (obj \ s"asset_code").extract[String] 18 | 19 | def assetIssuer = KeyPair.fromAccountId((obj \ s"asset_issuer").extract[String]) 20 | 21 | (obj \ s"asset_type").extract[String] match { 22 | case "native" => NativeAsset 23 | case "credit_alphanum4" => IssuedAsset4(assetCode, assetIssuer) 24 | case "credit_alphanum12" => IssuedAsset12(assetCode, assetIssuer) 25 | case t => throw new RuntimeException(s"Unrecognised asset type '$t'") 26 | } 27 | } 28 | 29 | def orders(obj: JValue) = { 30 | obj.children.map(c => 31 | Order( 32 | price = Price( 33 | n = (c \ "price_r" \ "n").extract[Int], 34 | d = (c \ "price_r" \ "d").extract[Int] 35 | ), 36 | quantity = Amount.toBaseUnits((c \ "amount").extract[String]).get 37 | )) 38 | } 39 | 40 | try { 41 | OrderBook( 42 | selling = asset(o \ "base"), 43 | buying = asset(o \ "counter"), 44 | bids = orders(o \ "bids"), 45 | asks = orders(o \ "asks") 46 | ) 47 | } catch { 48 | case t: Throwable => throw new RuntimeException(pretty(render(o)), t) 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/PaymentPath.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.JsonAST.JObject 4 | import org.json4s.{DefaultFormats, Formats, JArray, JValue} 5 | import stellar.sdk.KeyPair 6 | import stellar.sdk.model.AmountParser.{AssetDeserializer, parseAsset} 7 | import stellar.sdk.model.response.ResponseParser 8 | 9 | case class PaymentPath(source: Amount, destination: Amount, path: Seq[Asset]) 10 | 11 | object PaymentPathDeserializer extends ResponseParser[PaymentPath]({ 12 | o: JObject => 13 | implicit val formats = DefaultFormats 14 | implicit val assetDeserializer = AssetDeserializer 15 | 16 | PaymentPath( 17 | source = AmountParser.amount("source_", o), 18 | destination = AmountParser.amount("destination_", o), 19 | path = { 20 | val JArray(values) = (o \ "path").extract[JArray] 21 | values.map { jv => parseAsset("", jv) } 22 | } 23 | ) 24 | }) 25 | 26 | object AmountParser { 27 | 28 | implicit val formats = DefaultFormats 29 | 30 | def parseAsset(prefix: String, o: JValue)(implicit formats: Formats): Asset = { 31 | val assetType = (o \ s"${prefix}asset_type").extract[String] 32 | def code = (o \ s"${prefix}asset_code").extract[String] 33 | def issuer = KeyPair.fromAccountId((o \ s"${prefix}asset_issuer").extract[String]) 34 | assetType match { 35 | case "native" => NativeAsset 36 | case "credit_alphanum4" => IssuedAsset4(code, issuer) 37 | case "credit_alphanum12" => IssuedAsset12(code, issuer) 38 | case t => throw new RuntimeException(s"Unrecognised ${prefix}asset type: $t") 39 | } 40 | } 41 | 42 | def amount(prefix: String, o: JObject)(implicit formats: Formats): Amount = { 43 | val asset = parseAsset(prefix, o) 44 | val units = Amount.toBaseUnits((o \ s"${prefix}amount").extract[String]).get 45 | Amount(units, asset) 46 | } 47 | 48 | object AssetDeserializer extends ResponseParser[Asset](parseAsset("", _)) 49 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Price.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.util.Locale 4 | 5 | import org.stellar.xdr.{Int32, Price => XPrice} 6 | 7 | case class Price(n: Int, d: Int) { 8 | def xdr: XPrice = new XPrice.Builder() 9 | .d(new Int32(d)) 10 | .n(new Int32(n)) 11 | .build() 12 | 13 | def asDecimalString = "%.7f".formatLocal(Locale.ROOT, n * 1.0 / d * 1.0) 14 | 15 | // TODO (jem): As BigDecimal 16 | 17 | override def toString: String = s"$n:$d" 18 | } 19 | 20 | object Price { 21 | def decodeXdr(xdr: XPrice): Price = Price(xdr.getN.getInt32, xdr.getD.getInt32) 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Signer.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.stellar.xdr.{Uint32, Signer => XSigner} 4 | import stellar.sdk.PublicKey 5 | 6 | case class Signer( 7 | key: SignerStrKey, 8 | weight: Int, 9 | sponsor: Option[PublicKey] = None 10 | ) { 11 | def xdr: XSigner = new XSigner.Builder() 12 | .key(key.signerXdr) 13 | .weight(new Uint32(weight)) 14 | .build() 15 | } 16 | 17 | object Signer { 18 | def decodeXdr(xdr: XSigner): Signer = Signer( 19 | key = SignerStrKey.decodeXdr(xdr.getKey), 20 | weight = xdr.getWeight.getUint32 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Thresholds.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.stellar.xdr.{Thresholds => XThresholds} 4 | 5 | /** 6 | * The thresholds for operations on this account. 7 | * @param low The weight required for a valid transaction including the Allow Trust and Bump Sequence operations. 8 | * @param med The weight required for a valid transaction including the Create Account, Payment, Path Payment, Manage 9 | * Buy Offer, Manage Sell Offer, Create Passive Sell Offer, Change Trust, Inflation, and Manage Data operations. 10 | * @param high The weight required for a valid transaction including the Account Merge and Set Options operations. 11 | */ 12 | case class Thresholds(low: Int, med: Int, high: Int) 13 | 14 | /** 15 | * The thresholds for operations on this account, as described in transaction meta data for ledger effects. 16 | * This differs from @see[[Thresholds]] in that it also contains the master weight for the account's primary signature. 17 | * 18 | * @param master The weight provided by the primary signature for this account. 19 | * @param low The weight required for a valid transaction including the Allow Trust and Bump Sequence operations. 20 | * @param med The weight required for a valid transaction including the Create Account, Payment, Path Payment, Manage 21 | * Buy Offer, Manage Sell Offer, Create Passive Sell Offer, Change Trust, Inflation, and Manage Data operations. 22 | * @param high The weight required for a valid transaction including the Account Merge and Set Options operations. 23 | */ 24 | case class LedgerThresholds(master: Int, low: Int, med: Int, high: Int) { 25 | def xdr: XThresholds = new XThresholds(Array(master, low, med, high).map(_.toByte)) 26 | } 27 | 28 | object LedgerThresholds { 29 | def decodeXdr(xdr: XThresholds): LedgerThresholds = { 30 | val Array(master, low, med, high) = xdr.getThresholds.map(_.toInt & 0xFF) 31 | LedgerThresholds(master, low, med, high) 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/TimeBounds.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.time.temporal.ChronoField 4 | import java.time.{Clock, Instant} 5 | 6 | import org.stellar.xdr.{TimePoint, Uint64, TimeBounds => XTimeBounds} 7 | 8 | import scala.concurrent.duration.Duration 9 | 10 | case class TimeBounds(start: Instant, end: Instant) { 11 | def xdr: XTimeBounds = new XTimeBounds.Builder() 12 | .minTime(new TimePoint(new Uint64(start.getEpochSecond))) 13 | .maxTime(new TimePoint(new Uint64(end.getEpochSecond))) 14 | .build() 15 | 16 | private val isUnbounded: Boolean = start == end && start.getEpochSecond == 0 17 | require(start.isBefore(end) || isUnbounded, s"Range start is not before the end [start=$start][end=$end]") 18 | 19 | /** 20 | * Whether the given instant is within these bounds, inclusive. 21 | */ 22 | def includes(instant: Instant): Boolean = 23 | isUnbounded || !(start.isAfter(instant) || end.isBefore(instant)) 24 | } 25 | 26 | object TimeBounds { 27 | def decodeXdr(xdr: XTimeBounds): TimeBounds = if (xdr == null) Unbounded else TimeBounds( 28 | start = Instant.ofEpochSecond(xdr.getMinTime.getTimePoint.getUint64), 29 | end = Instant.ofEpochSecond(xdr.getMaxTime.getTimePoint.getUint64) 30 | ) 31 | 32 | val Unbounded: TimeBounds = TimeBounds(Instant.ofEpochSecond(0), Instant.ofEpochSecond(0)) 33 | 34 | def timeout(duration: Duration, clock: Clock = Clock.systemUTC()): TimeBounds = { 35 | val now = clock.instant().`with`(ChronoField.NANO_OF_SECOND, 0) 36 | TimeBounds(now.minusSeconds(5), now.plusMillis(duration.toMillis)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/Trade.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import org.json4s.DefaultFormats 6 | import org.json4s.JsonAST.JObject 7 | import stellar.sdk.model.response.ResponseParser 8 | import stellar.sdk.{KeyPair, PublicKeyOps} 9 | 10 | case class Trade(id: String, ledgerCloseTime: ZonedDateTime, offerId: Long, 11 | baseOfferId: Long, counterOfferId: Long, 12 | baseAccount: PublicKeyOps, baseAmount: Amount, 13 | counterAccount: PublicKeyOps, counterAmount: Amount, 14 | baseIsSeller: Boolean) 15 | 16 | 17 | object TradeDeserializer extends ResponseParser[Trade]({ 18 | o: JObject => 19 | implicit val formats = DefaultFormats 20 | 21 | def account(accountKey: String = "account") = KeyPair.fromAccountId((o \ accountKey).extract[String]) 22 | 23 | def date(key: String) = ZonedDateTime.parse((o \ key).extract[String]) 24 | 25 | def doubleFromString(key: String) = (o \ key).extract[String].toDouble 26 | 27 | def asset(prefix: String = "", issuerKey: String = "asset_issuer") = { 28 | def assetCode = (o \ s"${prefix}asset_code").extract[String] 29 | 30 | def assetIssuer = KeyPair.fromAccountId((o \ s"$prefix$issuerKey").extract[String]) 31 | 32 | (o \ s"${prefix}asset_type").extract[String] match { 33 | case "native" => NativeAsset 34 | case "credit_alphanum4" => IssuedAsset4(assetCode, assetIssuer) 35 | case "credit_alphanum12" => IssuedAsset12(assetCode, assetIssuer) 36 | case t => throw new RuntimeException(s"Unrecognised asset type '$t'") 37 | } 38 | } 39 | 40 | def amount(prefix: String = "") = { 41 | val units = Amount.toBaseUnits(doubleFromString(s"${prefix}amount")).get 42 | asset(prefix) match { 43 | case nna: NonNativeAsset => IssuedAmount(units, nna) 44 | case NativeAsset => NativeAmount(units) 45 | } 46 | } 47 | 48 | Trade( 49 | id = (o \ "id").extract[String], 50 | ledgerCloseTime = date("ledger_close_time"), 51 | offerId = (o \ "offer_id").extract[String].toLong, 52 | baseOfferId = (o \ "base_offer_id").extract[String].toLong, 53 | counterOfferId = (o \ "counter_offer_id").extract[String].toLong, 54 | baseAccount = account("base_account"), 55 | baseAmount = amount("base_"), 56 | counterAccount = account("counter_account"), 57 | counterAmount = amount("counter_"), 58 | baseIsSeller = (o \ "base_is_seller").extract[Boolean] 59 | ) 60 | }) 61 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/TradeAggregation.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.time.Instant 4 | import java.util.concurrent.TimeUnit 5 | 6 | import org.json4s.JsonAST.JObject 7 | import org.json4s.{DefaultFormats, JValue} 8 | import stellar.sdk.model.response.ResponseParser 9 | 10 | import scala.concurrent.duration.Duration 11 | 12 | case class TradeAggregation(instant: Instant, tradeCount: Int, baseVolume: Double, counterVolume: Double, 13 | average: Double, open: Price, high: Price, low: Price, close: Price) 14 | 15 | object TradeAggregationDeserializer extends ResponseParser[TradeAggregation]({ o: JObject => 16 | implicit val formats = DefaultFormats 17 | 18 | def price(p: JValue): Price = Price((p \ "N").extract[Int], (p \ "D").extract[Int]) 19 | 20 | TradeAggregation( 21 | instant = Instant.ofEpochMilli((o \ "timestamp").extract[String].toLong), 22 | tradeCount = (o \ "trade_count").extract[String].toInt, 23 | baseVolume = (o \ "base_volume").extract[String].toDouble, 24 | counterVolume = (o \ "counter_volume").extract[String].toDouble, 25 | average = (o \ "avg").extract[String].toDouble, 26 | open = price(o \ "open_r"), 27 | high = price(o \ "high_r"), 28 | low = price(o \ "low_r"), 29 | close = price(o \ "close_r")) 30 | }) 31 | 32 | object TradeAggregation { 33 | 34 | sealed class Resolution(val duration: Duration) 35 | 36 | val OneMinute = new Resolution(Duration.create(1, TimeUnit.MINUTES)) 37 | val FiveMinutes = new Resolution(OneMinute.duration * 5.0) 38 | val FifteenMinutes = new Resolution(FiveMinutes.duration * 3.0) 39 | val OneHour = new Resolution(FifteenMinutes.duration * 4.0) 40 | val OneDay = new Resolution(OneHour.duration * 24.0) 41 | val OneWeek = new Resolution(OneDay.duration * 7.0) 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/domain/IssuerDocumentation.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.domain 2 | 3 | import okhttp3.HttpUrl 4 | import toml.Value 5 | import toml.Value.Tbl 6 | 7 | /** 8 | * The Issuer Documentation subsection of the Domain Info parsed from stellar.toml files. 9 | * 10 | * @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md#issuer-documentation 11 | */ 12 | case class IssuerDocumentation(name: Option[String] = None, 13 | doingBusinessAs: Option[String] = None, 14 | url: Option[HttpUrl] = None, 15 | logo: Option[HttpUrl] = None, 16 | description: Option[String] = None, 17 | physicalAddress: Option[String] = None, 18 | physicalAddressAttestation: Option[HttpUrl] = None, 19 | phoneNumber: Option[String] = None, 20 | phoneNumberAttestation: Option[HttpUrl] = None, 21 | keybase: Option[String] = None, 22 | twitter: Option[String] = None, 23 | github: Option[String] = None, 24 | email: Option[String] = None, 25 | licensingAuthority: Option[String] = None, 26 | licenseType: Option[String] = None, 27 | licenseNumber: Option[String] = None, 28 | ) 29 | 30 | object IssuerDocumentation extends TomlParsers { 31 | 32 | def parse(tbl: Tbl): IssuerDocumentation = { 33 | def parseTomlValue[T](key: String, parser: PartialFunction[Value, T]) = 34 | super.parseTomlValue(tbl, key, parser) 35 | 36 | IssuerDocumentation( 37 | name = parseTomlValue("ORG_NAME", string), 38 | doingBusinessAs = parseTomlValue("ORG_DBA", string), 39 | url = parseTomlValue("ORG_URL", url), 40 | logo = parseTomlValue("ORG_LOGO", url), 41 | description = parseTomlValue("ORG_DESCRIPTION", string), 42 | physicalAddress = parseTomlValue("ORG_PHYSICAL_ADDRESS", string), 43 | physicalAddressAttestation = parseTomlValue("ORG_PHYSICAL_ADDRESS_ATTESTATION", url), 44 | phoneNumber = parseTomlValue("ORG_PHONE_NUMBER", string), 45 | phoneNumberAttestation = parseTomlValue("ORG_PHONE_NUMBER_ATTESTATION", url), 46 | keybase = parseTomlValue("ORG_KEYBASE", string), 47 | twitter = parseTomlValue("ORG_TWITTER", string), 48 | github = parseTomlValue("ORG_GITHUB", string), 49 | email = parseTomlValue("ORG_OFFICIAL_EMAIL", string), 50 | licensingAuthority = parseTomlValue("ORG_LICENSING_AUTHORITY", string), 51 | licenseType = parseTomlValue("ORG_LICENSE_TYPE", string), 52 | licenseNumber = parseTomlValue("ORG_LICENSE_NUMBER", string), 53 | ) 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/domain/PointOfContact.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.domain 2 | 3 | import toml.Value 4 | import toml.Value.Tbl 5 | 6 | case class PointOfContact(name: Option[String], 7 | email: Option[String], 8 | keybase: Option[String], 9 | telegram: Option[String], 10 | twitter: Option[String], 11 | github: Option[String], 12 | idPhotoHash: Option[String], 13 | verificationPhotoHash: Option[String]) 14 | 15 | object PointOfContact extends TomlParsers { 16 | def parse(tbl: Tbl): PointOfContact = { 17 | def parseTomlValue[T](key: String, parser: PartialFunction[Value, T]) = 18 | super.parseTomlValue(tbl, key, parser) 19 | 20 | PointOfContact( 21 | name = parseTomlValue("name", string), 22 | email = parseTomlValue("email", string), 23 | keybase = parseTomlValue("keybase", string), 24 | telegram = parseTomlValue("telegram", string), 25 | twitter = parseTomlValue("twitter", string), 26 | github = parseTomlValue("github", string), 27 | idPhotoHash = parseTomlValue("id_photo_hash", string), 28 | verificationPhotoHash = parseTomlValue("verification_photo_hash", string), 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/domain/TomlParsers.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.domain 2 | 3 | import okhttp3.HttpUrl 4 | import stellar.sdk.{KeyPair, PublicKey} 5 | import toml.Value 6 | import toml.Value.{Arr, Bool, Num, Str, Tbl} 7 | 8 | trait TomlParsers { 9 | val string: PartialFunction[Value, String] = { case Str(s) => s } 10 | val bool: PartialFunction[Value, Boolean] = { case Bool(b) => b } 11 | val long: PartialFunction[Value, Long] = { case Num(l) => l } 12 | val int: PartialFunction[Value, Int] = long.andThen(_.toInt) 13 | val url: PartialFunction[Value, HttpUrl] = { case Str(s) => HttpUrl.parse(s) } 14 | val publicKey: PartialFunction[Value, PublicKey] = { case Str(s) => KeyPair.fromAccountId(s) } 15 | def array[T](inner: PartialFunction[Value, T]): PartialFunction[Value, List[T]] = { 16 | case Arr(values) => values.map(inner) 17 | } 18 | def parseTomlValue[T](tbl: Tbl, key: String, parser: PartialFunction[Value, T]): Option[T] = 19 | tbl.values.get(key).map(parser.applyOrElse(_, { 20 | v: Value => throw DomainInfoParseException(s"value for $key was not of the expected type. [value=$v]") 21 | })) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/domain/Validator.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.domain 2 | 3 | import okhttp3.HttpUrl 4 | import stellar.sdk.PublicKey 5 | import toml.Value 6 | import toml.Value.Tbl 7 | 8 | /** 9 | * Validator data as defined in a `stellar.toml` file. 10 | * @param alias A name for display in stellar-core configs 11 | * @param displayName A human-readable name for display in quorum explorers and other interfaces 12 | * @param publicKey The Stellar account associated with the node 13 | * @param host The IP:port or domain:port peers can use to connect to the node 14 | * @param history The location of the history archive published by this validator 15 | */ 16 | case class Validator(alias: Option[String] = None, 17 | displayName: Option[String] = None, 18 | publicKey: Option[PublicKey] = None, 19 | host: Option[String] = None, 20 | history: Option[HttpUrl] = None) 21 | 22 | object Validator extends TomlParsers { 23 | 24 | def parse(tbl: Tbl): Validator = { 25 | def parseTomlValue[T](key: String, parser: PartialFunction[Value, T]) = 26 | super.parseTomlValue(tbl, key, parser) 27 | 28 | Validator( 29 | alias = parseTomlValue("ALIAS", string), 30 | displayName = parseTomlValue("DISPLAY_NAME", string), 31 | publicKey = parseTomlValue("PUBLIC_KEY", publicKey), 32 | host = parseTomlValue("HOST", string), 33 | history = parseTomlValue("HISTORY", url) 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/ledger/LedgerEntryChanges.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import okio.ByteString 4 | import org.stellar.xdr.{LedgerEntryChangeType, LedgerEntry => XLedgerEntry, LedgerEntryChange => XLedgerEntryChange, LedgerEntryChanges => XLedgerEntryChanges} 5 | 6 | sealed trait LedgerEntryChange { 7 | def xdr: XLedgerEntryChange 8 | } 9 | 10 | case class LedgerEntryCreate(entry: LedgerEntry) extends LedgerEntryChange { 11 | override def xdr: XLedgerEntryChange = new XLedgerEntryChange.Builder() 12 | .discriminant(LedgerEntryChangeType.LEDGER_ENTRY_CREATED) 13 | .created(entry.xdr) 14 | .build() 15 | } 16 | 17 | case class LedgerEntryUpdate(entry: LedgerEntry) extends LedgerEntryChange { 18 | override def xdr: XLedgerEntryChange = new XLedgerEntryChange.Builder() 19 | .discriminant(LedgerEntryChangeType.LEDGER_ENTRY_UPDATED) 20 | .updated(entry.xdr) 21 | .build() 22 | } 23 | 24 | case class LedgerEntryDelete(entry: LedgerKey) extends LedgerEntryChange { 25 | override def xdr: XLedgerEntryChange = new XLedgerEntryChange.Builder() 26 | .discriminant(LedgerEntryChangeType.LEDGER_ENTRY_REMOVED) 27 | .removed(entry.xdr) 28 | .build() 29 | } 30 | 31 | case class LedgerEntryState(entry: LedgerEntry) extends LedgerEntryChange { 32 | override def xdr: XLedgerEntryChange = new XLedgerEntryChange.Builder() 33 | .discriminant(LedgerEntryChangeType.LEDGER_ENTRY_STATE) 34 | .state(entry.xdr) 35 | .build() 36 | } 37 | 38 | object LedgerEntryChange { 39 | 40 | def decodeXdr(xdr: XLedgerEntryChange): LedgerEntryChange = 41 | xdr.getDiscriminant match { 42 | case LedgerEntryChangeType.LEDGER_ENTRY_CREATED => LedgerEntryCreate(LedgerEntry.decodeXdr(xdr.getCreated)) 43 | case LedgerEntryChangeType.LEDGER_ENTRY_UPDATED => LedgerEntryUpdate(LedgerEntry.decodeXdr(xdr.getUpdated)) 44 | case LedgerEntryChangeType.LEDGER_ENTRY_REMOVED => LedgerEntryDelete(LedgerKey.decodeXdr(xdr.getRemoved)) 45 | case LedgerEntryChangeType.LEDGER_ENTRY_STATE => LedgerEntryState(LedgerEntry.decodeXdr(xdr.getState)) 46 | } 47 | } 48 | 49 | object LedgerEntryChanges { 50 | 51 | def decodeXDR(base64: String): List[LedgerEntryChange] = { 52 | XLedgerEntryChanges.decode(ByteString.decodeBase64(base64)) 53 | .getLedgerEntryChanges.toList.map(LedgerEntryChange.decodeXdr) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/ledger/LedgerKey.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import org.stellar.xdr.LedgerKey.{LedgerKeyClaimableBalance, LedgerKeyData, LedgerKeyOffer, LedgerKeyTrustLine} 4 | import org.stellar.xdr.{Int64, LedgerEntryType, String64, XdrString, LedgerKey => XLedgerKey} 5 | import stellar.sdk.PublicKeyOps 6 | import stellar.sdk.model.{AccountId, Asset, ClaimableBalanceId, NonNativeAsset} 7 | 8 | sealed trait LedgerKey { 9 | def xdr: XLedgerKey 10 | } 11 | 12 | object LedgerKey { 13 | def decodeXdr(xdr: XLedgerKey): LedgerKey = { 14 | xdr.getDiscriminant match { 15 | case LedgerEntryType.ACCOUNT => 16 | AccountKey(AccountId.decodeXdr(xdr.getAccount.getAccountID).publicKey) 17 | case LedgerEntryType.CLAIMABLE_BALANCE => 18 | ClaimableBalanceKey(ClaimableBalanceId.decodeXdr(xdr.getClaimableBalance.getBalanceID)) 19 | case LedgerEntryType.DATA => 20 | DataKey( 21 | account = AccountId.decodeXdr(xdr.getData.getAccountID).publicKey, 22 | name = xdr.getData.getDataName.getString64.toString 23 | ) 24 | case LedgerEntryType.OFFER => 25 | OfferKey( 26 | account = AccountId.decodeXdr(xdr.getOffer.getSellerID).publicKey, 27 | offerId = xdr.getOffer.getOfferID.getInt64 28 | ) 29 | case LedgerEntryType.TRUSTLINE => 30 | TrustLineKey( 31 | account = AccountId.decodeXdr(xdr.getTrustLine.getAccountID).publicKey, 32 | asset = Asset.decodeXdr(xdr.getTrustLine.getAsset).asInstanceOf[NonNativeAsset] 33 | ) 34 | } 35 | } 36 | } 37 | 38 | case class AccountKey(account: PublicKeyOps) extends LedgerKey { 39 | override def xdr: XLedgerKey = new XLedgerKey.Builder() 40 | .discriminant(LedgerEntryType.ACCOUNT) 41 | .account(new XLedgerKey.LedgerKeyAccount.Builder() 42 | .accountID(account.toAccountId.xdr) 43 | .build()) 44 | .build() 45 | } 46 | 47 | case class TrustLineKey(account: PublicKeyOps, asset: NonNativeAsset) extends LedgerKey { 48 | override def xdr: XLedgerKey = new XLedgerKey.Builder() 49 | .discriminant(LedgerEntryType.TRUSTLINE) 50 | .trustLine(new LedgerKeyTrustLine.Builder() 51 | .accountID(account.toAccountId.xdr) 52 | .asset(asset.xdr) 53 | .build()) 54 | .build() 55 | } 56 | 57 | case class OfferKey(account: PublicKeyOps, offerId: Long) extends LedgerKey { 58 | override def xdr: XLedgerKey = new XLedgerKey.Builder() 59 | .discriminant(LedgerEntryType.OFFER) 60 | .offer(new LedgerKeyOffer.Builder() 61 | .offerID(new Int64(offerId)) 62 | .sellerID(account.toAccountId.xdr) 63 | .build()) 64 | .build() 65 | } 66 | 67 | case class DataKey(account: PublicKeyOps, name: String) extends LedgerKey { 68 | override def xdr: XLedgerKey = new XLedgerKey.Builder() 69 | .discriminant(LedgerEntryType.DATA) 70 | .data(new LedgerKeyData.Builder() 71 | .accountID(account.toAccountId.xdr) 72 | .dataName(new String64(new XdrString(name))) 73 | .build()) 74 | .build() 75 | } 76 | 77 | case class ClaimableBalanceKey(id: ClaimableBalanceId) extends LedgerKey { 78 | override def xdr: XLedgerKey = new XLedgerKey.Builder() 79 | .discriminant(LedgerEntryType.CLAIMABLE_BALANCE) 80 | .claimableBalance(new LedgerKeyClaimableBalance.Builder() 81 | .balanceID(id.xdr) 82 | .build()) 83 | .build() 84 | } 85 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/ledger/Liabilities.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import org.stellar.xdr.{Int64, Liabilities => XLiabilities} 4 | 5 | case class Liabilities(buying: Long, selling: Long) { 6 | def xdr: XLiabilities = new XLiabilities.Builder() 7 | .buying(new Int64(buying)) 8 | .selling(new Int64(selling)) 9 | .build() 10 | } 11 | 12 | object Liabilities { 13 | 14 | def decodeXdr(xdr: XLiabilities): Liabilities = Liabilities( 15 | buying = xdr.getBuying.getInt64, 16 | selling = xdr.getSelling.getInt64 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/ledger/TransactionLedgerEntries.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import okio.ByteString 4 | import org.stellar.xdr.{TransactionMeta, TransactionMetaV1, TransactionMetaV2} 5 | 6 | /** 7 | * Meta data about the effect a transaction had on the ledger it was transacted in. 8 | * 9 | * @param txnLevelChangesBefore the ledger changes caused by the transactions themselves (not any one 10 | * specific operation) preceding the transaction (introduced in version 2 of this datatype). 11 | * In earlier versions of the protocol, this field was not present. In such cases the field will be empty. 12 | * @param operationLevelChanges the ledger changes caused by the individual operations. The order of the outer sequence 13 | * matched the order of operations in the transaction. 14 | * @param txnLevelChangesAfter represents the changes following the transaction (introduced in version 1 of this datatype). 15 | * In earlier versions of the protocol, this field was not present. In such cases the field will be empty. 16 | */ 17 | case class TransactionLedgerEntries( 18 | txnLevelChangesBefore: List[LedgerEntryChange], 19 | operationLevelChanges: List[List[LedgerEntryChange]], 20 | txnLevelChangesAfter: List[LedgerEntryChange] 21 | ) 22 | 23 | object TransactionLedgerEntries { 24 | 25 | def decodeXDR(base64: String): TransactionLedgerEntries = { 26 | val meta = TransactionMeta.decode(ByteString.decodeBase64(base64)) 27 | meta.getDiscriminant.toInt match { 28 | case 0 => decodeXdr(meta) 29 | case 1 => decodeXdr(meta.getV1) 30 | case 2 => decodeXdr(meta.getV2) 31 | } 32 | } 33 | 34 | private def decodeXdr(meta: TransactionMeta): TransactionLedgerEntries = TransactionLedgerEntries( 35 | Nil, meta.getOperations.map(_.getChanges.getLedgerEntryChanges.map(LedgerEntryChange.decodeXdr).toList).toList, Nil 36 | ) 37 | 38 | private def decodeXdr(meta: TransactionMetaV1): TransactionLedgerEntries = TransactionLedgerEntries( 39 | txnLevelChangesBefore = Nil, 40 | operationLevelChanges = meta.getOperations.map(_.getChanges.getLedgerEntryChanges.map(LedgerEntryChange.decodeXdr).toList).toList, 41 | txnLevelChangesAfter = meta.getTxChanges.getLedgerEntryChanges.map(LedgerEntryChange.decodeXdr).toList 42 | ) 43 | 44 | private def decodeXdr(meta: TransactionMetaV2): TransactionLedgerEntries = 45 | TransactionLedgerEntries( 46 | txnLevelChangesBefore = meta.getTxChangesBefore.getLedgerEntryChanges.map(LedgerEntryChange.decodeXdr).toList, 47 | operationLevelChanges = meta.getOperations.map(_.getChanges.getLedgerEntryChanges.map(LedgerEntryChange.decodeXdr).toList).toList, 48 | txnLevelChangesAfter = meta.getTxChangesAfter.getLedgerEntryChanges.map(LedgerEntryChange.decodeXdr).toList 49 | ) 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/op/Transacted.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import org.json4s.DefaultFormats 6 | import org.json4s.JsonAST.JObject 7 | import stellar.sdk.model.response.ResponseParser 8 | 9 | /** 10 | * Provides access to additional information related to an operation after it has been transacted in the network. 11 | */ 12 | case class Transacted[+O <: Operation](id: Long, 13 | txnHash: String, 14 | createdAt: ZonedDateTime, 15 | operation: O) 16 | 17 | object TransactedOperationDeserializer extends ResponseParser[Transacted[Operation]]({ o: JObject => 18 | implicit val formats = DefaultFormats + OperationDeserializer 19 | 20 | def date(key: String) = ZonedDateTime.parse((o \ key).extract[String]) 21 | 22 | Transacted( 23 | id = (o \ "id").extract[String].toLong, 24 | txnHash = (o \ "transaction_hash").extract[String], 25 | createdAt = date("created_at"), 26 | operation = o.extract[Operation]) 27 | }) 28 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/AssetResponse.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.{DefaultFormats, Formats} 4 | import org.json4s.JsonAST.JObject 5 | import stellar.sdk._ 6 | import stellar.sdk.model.{Amount, IssuedAsset12, IssuedAsset4, NonNativeAsset} 7 | 8 | case class AssetResponse( 9 | asset: NonNativeAsset, 10 | balances: AssetBalances, 11 | numAccounts: Int, 12 | authRequired: Boolean, 13 | authRevocable: Boolean 14 | ) { 15 | def amount: Long = balances.total 16 | } 17 | 18 | case class AssetBalances( 19 | authorized: Long, 20 | authorizedToMaintainLiabilities: Long, 21 | unauthorized: Long 22 | ) { 23 | def total: Long = authorized + authorizedToMaintainLiabilities + unauthorized 24 | } 25 | 26 | object AssetRespDeserializer extends ResponseParser[AssetResponse]({ o: JObject => 27 | implicit val formats: Formats = DefaultFormats + AssetBalancesDeserializer 28 | val asset = { 29 | val code = (o \ "asset_code").extract[String] 30 | val issuer = KeyPair.fromAccountId((o \ "asset_issuer").extract[String]) 31 | (o \ "asset_type").extract[String] match { 32 | case "credit_alphanum4" => IssuedAsset4(code, issuer) 33 | case "credit_alphanum12" => IssuedAsset12(code, issuer) 34 | case t => throw new RuntimeException(s"Unrecognised asset type: $t") 35 | } 36 | } 37 | val balances = (o \ "balances").extract[AssetBalances] 38 | 39 | val amount = Amount.toBaseUnits((o \ "amount").extract[String].toDouble).getOrElse( 40 | throw new RuntimeException(s"Invalid asset amount: ${(o \ "amount").extract[Double]}")) 41 | val numAccounts = (o \ "num_accounts").extract[Int] 42 | val authRequired = (o \ "flags" \ "auth_required").extract[Boolean] 43 | val authRevocable = (o \ "flags" \ "auth_revocable").extract[Boolean] 44 | AssetResponse(asset, balances, numAccounts, authRequired, authRevocable) 45 | }) 46 | 47 | object AssetBalancesDeserializer extends ResponseParser[AssetBalances]({ o: JObject => 48 | implicit val formats: Formats = DefaultFormats 49 | def extractToLong(key: String) = Amount.toBaseUnits((o \ key).extract[String].toDouble).get 50 | AssetBalances( 51 | authorized = extractToLong("authorized"), 52 | authorizedToMaintainLiabilities = extractToLong("authorized_to_maintain_liabilities"), 53 | unauthorized = extractToLong("unauthorized") 54 | ) 55 | }) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/DataValueResponse.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.DefaultFormats 4 | import org.json4s.JsonAST.JObject 5 | 6 | case class DataValueResponse(v: String) 7 | 8 | object DataValueRespDeserializer extends ResponseParser[DataValueResponse]({ o: JObject => 9 | implicit val formats = DefaultFormats 10 | DataValueResponse((o \ "value").extract[String]) 11 | }) 12 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/FederationResponse.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import okio.ByteString 4 | import org.json4s.DefaultFormats 5 | import org.json4s.JsonAST.JObject 6 | import stellar.sdk.model._ 7 | import stellar.sdk.util.ByteArrays.{hexToBytes, trimmedByteArray} 8 | import stellar.sdk.{KeyPair, PublicKey} 9 | 10 | case class FederationResponse(address: String, 11 | account: PublicKey, 12 | memo: Memo = NoMemo) 13 | 14 | object FederationResponseDeserialiser extends ResponseParser[FederationResponse]({ o: JObject => 15 | implicit val formats = DefaultFormats 16 | 17 | // println(JsonMethods.pretty(JsonMethods.render(o))) 18 | 19 | FederationResponse( 20 | // reference server erroneously fails to set `stellar_address` for forward lookups 21 | address = (o \ "stellar_address").extractOpt[String].orNull, 22 | // reference server erroneously fails to set `account_id` for reverse lookups 23 | account = (o \ "account_id").extractOpt[String].map(KeyPair.fromAccountId).orNull, 24 | memo = (o \ "memo_type").extractOpt[String] match { 25 | case Some("id") => MemoId((o \ "memo").extract[String].toLong) 26 | case Some("text") => MemoText((o \ "memo").extract[String]) 27 | case Some("hash") => MemoHash(ByteString.decodeHex((o \ "memo").extract[String])) 28 | case _ => NoMemo 29 | } 30 | ) 31 | }) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/FeeStatsResponse.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.native.JsonMethods 4 | import org.json4s.{DefaultFormats, JObject} 5 | import stellar.sdk.model.NativeAmount 6 | 7 | case class FeeStatsResponse(lastLedger: Long, 8 | lastLedgerBaseFee: NativeAmount, 9 | ledgerCapacityUsage: Double, 10 | maxFees: FeeStats, 11 | chargedFees: FeeStats) { 12 | 13 | @deprecated("Use `chargedFees.min` instead.", "v0.11.0") 14 | def minAcceptedFee: NativeAmount = chargedFees.min 15 | 16 | @deprecated("Use `chargedFees.mode` instead.", "v0.11.0") 17 | def modeAcceptedFee: NativeAmount = chargedFees.mode 18 | 19 | @deprecated("Use `chargedFees.percentiles` instead.", "v0.11.0") 20 | def acceptedFeePercentiles: Map[Int, NativeAmount] = chargedFees.percentiles 21 | 22 | } 23 | 24 | case class FeeStats(min: NativeAmount, 25 | mode: NativeAmount, 26 | max: NativeAmount, 27 | percentiles: Map[Int, NativeAmount]) 28 | 29 | object FeeStatsRespDeserializer extends ResponseParser[FeeStatsResponse]({ o: JObject => 30 | implicit val formats = DefaultFormats + FeeStatsDeserializer 31 | 32 | def amount(field: String): NativeAmount = NativeAmount((o \ field).extract[String].toLong) 33 | 34 | val lastLedger = (o \ "last_ledger").extract[String].toLong 35 | val lastLedgerBaseFee = amount("last_ledger_base_fee") 36 | val ledgerCapacityUsage = (o \ "ledger_capacity_usage").extract[String].toDouble 37 | val maxFees = (o \ "max_fee").extract[FeeStats] 38 | val chargedFees = (o \ "fee_charged").extract[FeeStats] 39 | 40 | FeeStatsResponse(lastLedger, lastLedgerBaseFee, ledgerCapacityUsage, maxFees, chargedFees) 41 | }) 42 | 43 | object FeeStatsDeserializer extends ResponseParser[FeeStats]({ o: JObject => 44 | implicit val formats = DefaultFormats 45 | 46 | def amount(field: String): NativeAmount = NativeAmount((o \ field).extract[String].toLong) 47 | 48 | FeeStats( 49 | min = amount("min"), 50 | mode = amount("mode"), 51 | max = amount("max"), 52 | percentiles = Map( 53 | 10 -> amount("p10"), 54 | 20 -> amount("p20"), 55 | 30 -> amount("p30"), 56 | 40 -> amount("p40"), 57 | 50 -> amount("p50"), 58 | 60 -> amount("p60"), 59 | 70 -> amount("p70"), 60 | 80 -> amount("p80"), 61 | 90 -> amount("p90"), 62 | 95 -> amount("p95"), 63 | 99 -> amount("p99") 64 | )) 65 | }) 66 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/LedgerResponse.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import org.json4s.DefaultFormats 6 | import org.json4s.JsonAST.JObject 7 | import stellar.sdk.model.{Amount, NativeAmount} 8 | 9 | case class LedgerResponse(id: String, hash: String, previousHash: Option[String], sequence: Long, successTransactionCount: Int, 10 | failureTransactionCount: Int, operationCount: Int, closedAt: ZonedDateTime, 11 | totalCoins: NativeAmount, feePool: NativeAmount, baseFee: NativeAmount, baseReserve: NativeAmount, 12 | maxTxSetSize: Int) { 13 | 14 | def transactionCount: Int = successTransactionCount + failureTransactionCount 15 | 16 | } 17 | 18 | object LedgerRespDeserializer extends ResponseParser[LedgerResponse]({ o: JObject => 19 | implicit val formats = DefaultFormats 20 | 21 | LedgerResponse( 22 | id = (o \ "id").extract[String], 23 | hash = (o \ "hash").extract[String], 24 | previousHash = (o \ "prev_hash").extractOpt[String], 25 | sequence = (o \ "sequence").extract[Long], 26 | successTransactionCount = (o \ "successful_transaction_count").extract[Int], 27 | failureTransactionCount = (o \ "failed_transaction_count").extract[Int], 28 | operationCount = (o \ "operation_count").extract[Int], 29 | closedAt = ZonedDateTime.parse((o \ "closed_at").extract[String]), 30 | totalCoins = Amount.toBaseUnits((o \ "total_coins").extract[String]).map(NativeAmount.apply).get, 31 | feePool = Amount.toBaseUnits((o \ "fee_pool").extract[String]).map(NativeAmount.apply).get, 32 | baseFee = NativeAmount((o \ "base_fee").extractOpt[Long].getOrElse((o \ "base_fee_in_stroops").extract[Long])), 33 | baseReserve = { 34 | val old: Option[Long] = (o \ "base_reserve").extractOpt[String].map(_.toDouble).map(Amount.toBaseUnits).map(_.get) 35 | NativeAmount(old.getOrElse((o \ "base_reserve_in_stroops").extract[Long])) 36 | }, 37 | maxTxSetSize = (o \ "max_tx_set_size").extract[Int] 38 | ) 39 | }) 40 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/NetworkInfo.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.DefaultFormats 4 | import org.json4s.JsonAST.JObject 5 | 6 | /** 7 | * Information on the network, as provided by the Horizon root document. 8 | */ 9 | case class NetworkInfo(horizonVersion: String, 10 | coreVersion: String, 11 | earliestLedger: Long, 12 | latestLedger: Long, 13 | passphrase: String, 14 | currentProtocolVersion: Int, 15 | supportedProtocolVersion: Int) 16 | 17 | object NetworkInfoDeserializer extends ResponseParser[NetworkInfo]({ o: JObject => 18 | implicit val formats = DefaultFormats 19 | 20 | NetworkInfo( 21 | horizonVersion = (o \ "horizon_version").extract[String], 22 | coreVersion = (o \ "core_version").extract[String], 23 | earliestLedger = (o \ "history_elder_ledger").extract[Long], 24 | latestLedger = (o \ "history_latest_ledger").extract[Long], 25 | passphrase = (o \ "network_passphrase").extract[String], 26 | currentProtocolVersion = (o \ "current_protocol_version").extract[Int], 27 | supportedProtocolVersion = (o \ "core_supported_protocol_version").extract[Int] 28 | ) 29 | }) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/OfferResponse.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import org.json4s.DefaultFormats 6 | import org.json4s.JsonAST.JObject 7 | import stellar.sdk._ 8 | import stellar.sdk.model._ 9 | 10 | case class OfferResponse( 11 | id: Long, 12 | seller: PublicKeyOps, 13 | selling: Amount, 14 | buying: Asset, 15 | price: Price, 16 | lastModifiedLedger: Long, 17 | lastModifiedTime: ZonedDateTime, 18 | sponsor: Option[PublicKey] 19 | ) { 20 | 21 | override def toString = { 22 | s"Offer $id: ${seller.accountId} selling $selling, buying $buying @ rate $price" 23 | } 24 | } 25 | 26 | object OfferRespDeserializer extends ResponseParser[OfferResponse]({ o: JObject => 27 | implicit val formats = DefaultFormats 28 | val id = (o \ "id").extract[String].toLong 29 | 30 | def account(accountKey: String = "account") = KeyPair.fromAccountId((o \ accountKey).extract[String]) 31 | 32 | def asset(prefix: String = "", issuerKey: String = "asset_issuer") = { 33 | def assetCode = (o \ prefix \ "asset_code").extract[String] 34 | 35 | def assetIssuer = KeyPair.fromAccountId((o \ prefix \ issuerKey).extract[String]) 36 | 37 | (o \ prefix \ "asset_type").extract[String] match { 38 | case "native" => NativeAsset 39 | case "credit_alphanum4" => IssuedAsset4(assetCode, assetIssuer) 40 | case "credit_alphanum12" => IssuedAsset12(assetCode, assetIssuer) 41 | case t => throw new RuntimeException(s"Unrecognised asset type '$t'") 42 | } 43 | } 44 | 45 | def doubleFromString(key: String) = (o \ key).extract[String].toDouble 46 | 47 | def amount(prefix: String = "") = { 48 | val units = Amount.toBaseUnits(doubleFromString("amount")).get 49 | asset(prefix) match { 50 | case nna: NonNativeAsset => IssuedAmount(units, nna) 51 | case NativeAsset => NativeAmount(units) 52 | } 53 | } 54 | 55 | def price = { 56 | val priceObj = o \ "price_r" 57 | Price( 58 | (priceObj \ "n").extract[Int], 59 | (priceObj \ "d").extract[Int] 60 | ) 61 | } 62 | 63 | def lastModifiedLedger = (o \ "last_modified_ledger").extract[Long] 64 | 65 | def lastModifiedTime = ZonedDateTime.parse((o \ "last_modified_time").extract[String]) 66 | 67 | def sponsor = (o \ "sponsor").extractOpt[String].map(KeyPair.fromAccountId) 68 | 69 | OfferResponse( 70 | id, 71 | account("seller"), 72 | amount("selling"), 73 | asset("buying"), 74 | price, 75 | lastModifiedLedger, 76 | lastModifiedTime, 77 | sponsor) 78 | }) 79 | 80 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/response/ResponseParser.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.native.JsonMethods.{pretty, render} 4 | import org.json4s.{CustomSerializer, JObject} 5 | 6 | import scala.util.control.NonFatal 7 | 8 | class ResponseParser[T](f: JObject => T)(implicit m: Manifest[T]) extends CustomSerializer[T](_ => ({ 9 | case o: JObject => 10 | try { 11 | f(o) 12 | } catch { 13 | case NonFatal(t) => throw ResponseParseException(pretty(render(o)), t) 14 | } 15 | }, PartialFunction.empty)) 16 | 17 | case class ResponseParseException(doc: String, cause: Throwable) 18 | extends Exception(s"Unable to parse document:\n$doc", cause) 19 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/AllowTrustResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.OperationResult.OperationResultTr 4 | import org.stellar.xdr.{AllowTrustResultCode, OperationType, AllowTrustResult => XAllowTrustResult} 5 | 6 | sealed abstract class AllowTrustResult extends ProcessedOperationResult { 7 | def result: XAllowTrustResult 8 | override def transactionResult: OperationResultTr = new OperationResultTr.Builder() 9 | .discriminant(OperationType.ALLOW_TRUST) 10 | .allowTrustResult(result) 11 | .build() 12 | 13 | } 14 | 15 | object AllowTrustResult { 16 | def decodeXdr(xdr: XAllowTrustResult): AllowTrustResult = xdr.getDiscriminant match { 17 | case AllowTrustResultCode.ALLOW_TRUST_SUCCESS => AllowTrustSuccess 18 | case AllowTrustResultCode.ALLOW_TRUST_MALFORMED => AllowTrustMalformed 19 | case AllowTrustResultCode.ALLOW_TRUST_NO_TRUST_LINE => AllowTrustNoTrustLine 20 | case AllowTrustResultCode.ALLOW_TRUST_TRUST_NOT_REQUIRED => AllowTrustNotRequired 21 | case AllowTrustResultCode.ALLOW_TRUST_CANT_REVOKE => AllowTrustCannotRevoke 22 | case AllowTrustResultCode.ALLOW_TRUST_SELF_NOT_ALLOWED => AllowTrustSelfNotAllowed 23 | } 24 | } 25 | 26 | /** 27 | * AllowTrust operation was successful. 28 | */ 29 | case object AllowTrustSuccess extends AllowTrustResult { 30 | override def result: XAllowTrustResult = new XAllowTrustResult.Builder() 31 | .discriminant(AllowTrustResultCode.ALLOW_TRUST_SUCCESS) 32 | .build() 33 | } 34 | 35 | /** 36 | * AllowTrust operation failed because the request was malformed. 37 | * E.g. The limit was less than zero, or the asset was malformed, or the native asset was provided. 38 | */ 39 | case object AllowTrustMalformed extends AllowTrustResult { 40 | override def result: XAllowTrustResult = new XAllowTrustResult.Builder() 41 | .discriminant(AllowTrustResultCode.ALLOW_TRUST_MALFORMED) 42 | .build() 43 | } 44 | 45 | /** 46 | * AllowTrust operation failed because the trustor does not have a trustline. 47 | */ 48 | case object AllowTrustNoTrustLine extends AllowTrustResult { 49 | override def result: XAllowTrustResult = new XAllowTrustResult.Builder() 50 | .discriminant(AllowTrustResultCode.ALLOW_TRUST_NO_TRUST_LINE) 51 | .build() 52 | } 53 | 54 | /** 55 | * AllowTrust operation failed because the source account does not require trust. 56 | */ 57 | case object AllowTrustNotRequired extends AllowTrustResult { 58 | override def result: XAllowTrustResult = new XAllowTrustResult.Builder() 59 | .discriminant(AllowTrustResultCode.ALLOW_TRUST_TRUST_NOT_REQUIRED) 60 | .build() 61 | } 62 | 63 | /** 64 | * AllowTrust operation failed because the source account is unable to revoke trust. 65 | */ 66 | case object AllowTrustCannotRevoke extends AllowTrustResult { 67 | override def result: XAllowTrustResult = new XAllowTrustResult.Builder() 68 | .discriminant(AllowTrustResultCode.ALLOW_TRUST_CANT_REVOKE) 69 | .build() 70 | } 71 | 72 | /** 73 | * AllowTrust operation failed because it is not valid to trust your own issued asset. 74 | */ 75 | case object AllowTrustSelfNotAllowed extends AllowTrustResult { 76 | override def result: XAllowTrustResult = new XAllowTrustResult.Builder() 77 | .discriminant(AllowTrustResultCode.ALLOW_TRUST_SELF_NOT_ALLOWED) 78 | .build() 79 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/BumpSequenceResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.OperationResult.OperationResultTr 4 | import org.stellar.xdr.{BumpSequenceResultCode, OperationType} 5 | 6 | sealed abstract class BumpSequenceResult extends ProcessedOperationResult { 7 | def result: org.stellar.xdr.BumpSequenceResult 8 | override def transactionResult: OperationResultTr = new OperationResultTr.Builder() 9 | .discriminant(OperationType.BUMP_SEQUENCE) 10 | .bumpSeqResult(result) 11 | .build() 12 | } 13 | 14 | object BumpSequenceResult { 15 | def decodeXdr(xdr: org.stellar.xdr.BumpSequenceResult): BumpSequenceResult = xdr.getDiscriminant match { 16 | case BumpSequenceResultCode.BUMP_SEQUENCE_SUCCESS => BumpSequenceSuccess 17 | case BumpSequenceResultCode.BUMP_SEQUENCE_BAD_SEQ => BumpSequenceBadSeqNo 18 | } 19 | } 20 | 21 | /** 22 | * BumpSequence operation was successful. 23 | */ 24 | case object BumpSequenceSuccess extends BumpSequenceResult { 25 | override def result: org.stellar.xdr.BumpSequenceResult = new org.stellar.xdr.BumpSequenceResult.Builder() 26 | .discriminant(BumpSequenceResultCode.BUMP_SEQUENCE_SUCCESS) 27 | .build() 28 | } 29 | 30 | /** 31 | * BumpSequence operation failed because the desired sequence number was not within valid bounds. 32 | */ 33 | case object BumpSequenceBadSeqNo extends BumpSequenceResult { 34 | override def result: org.stellar.xdr.BumpSequenceResult = new org.stellar.xdr.BumpSequenceResult.Builder() 35 | .discriminant(BumpSequenceResultCode.BUMP_SEQUENCE_BAD_SEQ) 36 | .build() 37 | } 38 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/ChangeTrustResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.OperationResult.OperationResultTr 4 | import org.stellar.xdr.{ChangeTrustResultCode, OperationType, ChangeTrustResult => XChangeTrustResult} 5 | 6 | sealed abstract class ChangeTrustResult extends ProcessedOperationResult { 7 | def result: XChangeTrustResult 8 | override def transactionResult: OperationResultTr = new OperationResultTr.Builder() 9 | .discriminant(OperationType.CHANGE_TRUST) 10 | .changeTrustResult(result) 11 | .build() 12 | } 13 | 14 | object ChangeTrustResult { 15 | def decodeXdr(xdr: XChangeTrustResult): ChangeTrustResult = xdr.getDiscriminant match { 16 | case ChangeTrustResultCode.CHANGE_TRUST_SUCCESS => ChangeTrustSuccess 17 | case ChangeTrustResultCode.CHANGE_TRUST_MALFORMED => ChangeTrustMalformed 18 | case ChangeTrustResultCode.CHANGE_TRUST_NO_ISSUER => ChangeTrustNoIssuer 19 | case ChangeTrustResultCode.CHANGE_TRUST_INVALID_LIMIT => ChangeTrustInvalidLimit 20 | case ChangeTrustResultCode.CHANGE_TRUST_LOW_RESERVE => ChangeTrustLowReserve 21 | case ChangeTrustResultCode.CHANGE_TRUST_SELF_NOT_ALLOWED => ChangeTrustSelfNotAllowed 22 | } 23 | } 24 | 25 | /** 26 | * ChangeTrust operation was successful. 27 | */ 28 | case object ChangeTrustSuccess extends ChangeTrustResult { 29 | override def result: XChangeTrustResult = new XChangeTrustResult.Builder() 30 | .discriminant(ChangeTrustResultCode.CHANGE_TRUST_SUCCESS) 31 | .build() 32 | } 33 | 34 | /** 35 | * ChangeTrust operation failed because the request was malformed. 36 | * E.g. The limit was less than zero, or the asset was malformed, or the native asset was provided. 37 | */ 38 | case object ChangeTrustMalformed extends ChangeTrustResult { 39 | override def result: XChangeTrustResult = new XChangeTrustResult.Builder() 40 | .discriminant(ChangeTrustResultCode.CHANGE_TRUST_MALFORMED) 41 | .build() 42 | } 43 | 44 | /** 45 | * ChangeTrust operation failed because the issuer account does not exist. 46 | */ 47 | case object ChangeTrustNoIssuer extends ChangeTrustResult { 48 | override def result: XChangeTrustResult = new XChangeTrustResult.Builder() 49 | .discriminant(ChangeTrustResultCode.CHANGE_TRUST_NO_ISSUER) 50 | .build() 51 | } 52 | 53 | /** 54 | * ChangeTrust operation failed because the limit was zero or less than the current balance. 55 | */ 56 | case object ChangeTrustInvalidLimit extends ChangeTrustResult { 57 | override def result: XChangeTrustResult = new XChangeTrustResult.Builder() 58 | .discriminant(ChangeTrustResultCode.CHANGE_TRUST_INVALID_LIMIT) 59 | .build() 60 | } 61 | 62 | /** 63 | * ChangeTrust operation failed because there is not enough funds in reserve to create a new trustline. 64 | */ 65 | case object ChangeTrustLowReserve extends ChangeTrustResult { 66 | override def result: XChangeTrustResult = new XChangeTrustResult.Builder() 67 | .discriminant(ChangeTrustResultCode.CHANGE_TRUST_LOW_RESERVE) 68 | .build() 69 | } 70 | 71 | /** 72 | * ChangeTrust operation failed because it is not valid to trust your own issued asset. 73 | */ 74 | case object ChangeTrustSelfNotAllowed extends ChangeTrustResult { 75 | override def result: XChangeTrustResult = new XChangeTrustResult.Builder() 76 | .discriminant(ChangeTrustResultCode.CHANGE_TRUST_SELF_NOT_ALLOWED) 77 | .build() 78 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/CreateAccountResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.CreateAccountResultCode._ 4 | import org.stellar.xdr.OperationResult.OperationResultTr 5 | import org.stellar.xdr.{OperationType, CreateAccountResult => XCreateAccountResult} 6 | 7 | sealed abstract class CreateAccountResult extends ProcessedOperationResult { 8 | def result: XCreateAccountResult 9 | val transactionResult: OperationResultTr = new OperationResultTr.Builder() 10 | .discriminant(OperationType.CREATE_ACCOUNT) 11 | .createAccountResult(result) 12 | .build() 13 | } 14 | 15 | object CreateAccountResult { 16 | def decodeXdr(xdr: XCreateAccountResult): CreateAccountResult = xdr.getDiscriminant match { 17 | case CREATE_ACCOUNT_SUCCESS => CreateAccountSuccess 18 | case CREATE_ACCOUNT_MALFORMED => CreateAccountMalformed 19 | case CREATE_ACCOUNT_UNDERFUNDED => CreateAccountUnderfunded 20 | case CREATE_ACCOUNT_LOW_RESERVE => CreateAccountLowReserve 21 | case CREATE_ACCOUNT_ALREADY_EXIST => CreateAccountAlreadyExists 22 | } 23 | } 24 | 25 | /** 26 | * CreateAccount operation was successful. 27 | */ 28 | case object CreateAccountSuccess extends CreateAccountResult { 29 | override def result: XCreateAccountResult = new XCreateAccountResult.Builder() 30 | .discriminant(CREATE_ACCOUNT_SUCCESS) 31 | .build() 32 | } 33 | 34 | /** 35 | * CreateAccount operation failed because the destination account was malformed. 36 | */ 37 | case object CreateAccountMalformed extends CreateAccountResult { 38 | override def result: XCreateAccountResult = new XCreateAccountResult.Builder() 39 | .discriminant(CREATE_ACCOUNT_MALFORMED) 40 | .build() 41 | } 42 | 43 | /** 44 | * CreateAccount operation failed because there was insufficient funds in the source account. 45 | */ 46 | case object CreateAccountUnderfunded extends CreateAccountResult { 47 | override def result: XCreateAccountResult = new XCreateAccountResult.Builder() 48 | .discriminant(CREATE_ACCOUNT_UNDERFUNDED) 49 | .build() 50 | } 51 | 52 | /** 53 | * CreateAccount operation failed because there was insufficient funds sent to cover the base reserve. 54 | */ 55 | case object CreateAccountLowReserve extends CreateAccountResult { 56 | override def result: XCreateAccountResult = new XCreateAccountResult.Builder() 57 | .discriminant(CREATE_ACCOUNT_LOW_RESERVE) 58 | .build() 59 | } 60 | 61 | /** 62 | * CreateAccount operation failed because the destination account already exists. 63 | */ 64 | case object CreateAccountAlreadyExists extends CreateAccountResult { 65 | override def result: XCreateAccountResult = new XCreateAccountResult.Builder() 66 | .discriminant(CREATE_ACCOUNT_ALREADY_EXIST) 67 | .build() 68 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/EndSponsoringFutureReservesResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.EndSponsoringFutureReservesResultCode.{END_SPONSORING_FUTURE_RESERVES_NOT_SPONSORED, END_SPONSORING_FUTURE_RESERVES_SUCCESS} 4 | import org.stellar.xdr.OperationResult.OperationResultTr 5 | import org.stellar.xdr.{EndSponsoringFutureReservesResultCode, OperationType, EndSponsoringFutureReservesResult => XEndSponsoringFutureReservesResult} 6 | 7 | sealed abstract class EndSponsoringFutureReservesResult extends ProcessedOperationResult { 8 | def result: XEndSponsoringFutureReservesResult 9 | val transactionResult: OperationResultTr = new OperationResultTr.Builder() 10 | .discriminant(OperationType.END_SPONSORING_FUTURE_RESERVES) 11 | .endSponsoringFutureReservesResult(result) 12 | .build() 13 | 14 | } 15 | 16 | object EndSponsoringFutureReservesResult { 17 | def decodeXdr(xdr: XEndSponsoringFutureReservesResult): EndSponsoringFutureReservesResult = xdr.getDiscriminant match { 18 | case END_SPONSORING_FUTURE_RESERVES_SUCCESS => EndSponsoringFutureReservesSuccess 19 | case END_SPONSORING_FUTURE_RESERVES_NOT_SPONSORED => EndSponsoringFutureReservesNotSponsored 20 | } 21 | } 22 | 23 | /** 24 | * EndSponsoringFutureReserves operation was successful. 25 | */ 26 | case object EndSponsoringFutureReservesSuccess extends EndSponsoringFutureReservesResult { 27 | override def result: XEndSponsoringFutureReservesResult = new XEndSponsoringFutureReservesResult.Builder() 28 | .discriminant(EndSponsoringFutureReservesResultCode.END_SPONSORING_FUTURE_RESERVES_SUCCESS) 29 | .build() 30 | } 31 | 32 | /** 33 | * EndSponsoringFutureReserves operation failed because there was insufficient reserve funds to add another signer. 34 | */ 35 | case object EndSponsoringFutureReservesNotSponsored extends EndSponsoringFutureReservesResult { 36 | override def result: XEndSponsoringFutureReservesResult = new XEndSponsoringFutureReservesResult.Builder() 37 | .discriminant(EndSponsoringFutureReservesResultCode.END_SPONSORING_FUTURE_RESERVES_NOT_SPONSORED) 38 | .build() 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/FeeBumpHistory.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import stellar.sdk.model.NativeAmount 4 | 5 | case class FeeBumpHistory(maxFee: NativeAmount, hash: String, signatures: List[String]) -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/FeeBumpedTransactionResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import okio.ByteString 4 | import org.stellar.xdr.InnerTransactionResult.InnerTransactionResultResult 5 | import org.stellar.xdr.TransactionResultCode.{txFAILED, txSUCCESS} 6 | import org.stellar.xdr.{Hash, InnerTransactionResult, InnerTransactionResultPair, Int64, TransactionResultCode} 7 | import stellar.sdk.model.NativeAmount 8 | 9 | /** After attempting a fee bump, the results of the transaction to be bumped */ 10 | case class FeeBumpedTransactionResult( 11 | feeCharged: NativeAmount, 12 | result: TransactionResultCode, 13 | operationResults: List[OperationResult], 14 | hash: ByteString 15 | ) { 16 | 17 | def xdr: InnerTransactionResultPair = { 18 | new InnerTransactionResultPair.Builder() 19 | .result(new InnerTransactionResult.Builder() 20 | .feeCharged(new Int64(feeCharged.units)) 21 | .result(new InnerTransactionResultResult.Builder() 22 | .discriminant(result) 23 | .results(if (result == txSUCCESS || result == txFAILED) operationResults.map(_.xdr).toArray else null) 24 | .build()) 25 | .ext(new InnerTransactionResult.InnerTransactionResultExt.Builder() 26 | .discriminant(0) 27 | .build()) 28 | .build()) 29 | .transactionHash(new Hash(hash.toByteArray)) 30 | .build() 31 | } 32 | 33 | } 34 | 35 | object FeeBumpedTransactionResult { 36 | 37 | def decodeXdr(xdr: InnerTransactionResultPair): FeeBumpedTransactionResult = { 38 | val result = xdr.getResult.getResult.getDiscriminant 39 | val operationResults = result match { 40 | case TransactionResultCode.txSUCCESS | TransactionResultCode.txFAILED => 41 | xdr.getResult.getResult.getResults.map(OperationResult.decodeXdr).toList 42 | case _ => Nil 43 | } 44 | FeeBumpedTransactionResult( 45 | feeCharged = NativeAmount(xdr.getResult.getFeeCharged.getInt64), 46 | result = result, 47 | operationResults = operationResults, 48 | hash = new ByteString(xdr.getTransactionHash.getHash) 49 | ) 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/InflationResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.OperationResult.OperationResultTr 4 | import org.stellar.xdr.{InflationResultCode, Int64, OperationType, InflationPayout => XInflationPayout, InflationResult => XInflationResult} 5 | import stellar.sdk.PublicKey 6 | import stellar.sdk.model.{AccountId, NativeAmount} 7 | 8 | sealed abstract class InflationResult extends ProcessedOperationResult { 9 | def result: XInflationResult 10 | override def transactionResult: OperationResultTr = new OperationResultTr.Builder() 11 | .discriminant(OperationType.INFLATION) 12 | .inflationResult(result) 13 | .build() 14 | } 15 | 16 | object InflationResult { 17 | def decodeXdr(xdr: XInflationResult): InflationResult = xdr.getDiscriminant match { 18 | case InflationResultCode.INFLATION_SUCCESS => InflationSuccess( 19 | payouts = xdr.getPayouts.map(InflationPayout.decodeXdr).toList 20 | ) 21 | case InflationResultCode.INFLATION_NOT_TIME => InflationNotDue 22 | } 23 | } 24 | 25 | /** 26 | * Inflation operation was successful. 27 | */ 28 | case class InflationSuccess(payouts: Seq[InflationPayout]) extends InflationResult { 29 | override def result: XInflationResult = new XInflationResult.Builder() 30 | .discriminant(InflationResultCode.INFLATION_SUCCESS) 31 | .payouts(payouts.map(_.xdr).toArray) 32 | .build() 33 | } 34 | 35 | /** 36 | * Inflation operation failed because inflation is not yet due. 37 | */ 38 | case object InflationNotDue extends InflationResult { 39 | override def result: XInflationResult = new XInflationResult.Builder() 40 | .discriminant(InflationResultCode.INFLATION_NOT_TIME) 41 | .build() 42 | } 43 | 44 | case class InflationPayout(recipient: PublicKey, amount: NativeAmount) { 45 | def xdr: org.stellar.xdr.InflationPayout = new org.stellar.xdr.InflationPayout.Builder() 46 | .amount(new Int64(amount.units)) 47 | .destination(recipient.toAccountId.xdr) 48 | .build() 49 | } 50 | 51 | object InflationPayout { 52 | def decodeXdr(xdr: XInflationPayout): InflationPayout = InflationPayout( 53 | recipient = AccountId.decodeXdr(xdr.getDestination).publicKey, 54 | amount = NativeAmount(xdr.getAmount.getInt64) 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/ManageDataResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.OperationResult.OperationResultTr 4 | import org.stellar.xdr.{ManageDataResultCode, OperationType, ManageDataResult => XManageDataResult} 5 | 6 | 7 | sealed abstract class ManageDataResult extends ProcessedOperationResult { 8 | def result: XManageDataResult 9 | override def transactionResult: OperationResultTr = new OperationResultTr.Builder() 10 | .discriminant(OperationType.MANAGE_DATA) 11 | .manageDataResult(result) 12 | .build() 13 | } 14 | 15 | object ManageDataResult { 16 | def decodeXdr(xdr: XManageDataResult): ManageDataResult = xdr.getDiscriminant match { 17 | case ManageDataResultCode.MANAGE_DATA_SUCCESS => ManageDataSuccess 18 | case ManageDataResultCode.MANAGE_DATA_NOT_SUPPORTED_YET => ManageDataNotSupportedYet 19 | case ManageDataResultCode.MANAGE_DATA_NAME_NOT_FOUND => DeleteDataNameNotFound 20 | case ManageDataResultCode.MANAGE_DATA_LOW_RESERVE => AddDataLowReserve 21 | case ManageDataResultCode.MANAGE_DATA_INVALID_NAME => AddDataInvalidName 22 | } 23 | } 24 | 25 | /** 26 | * ManageData operation was successful. 27 | */ 28 | case object ManageDataSuccess extends ManageDataResult { 29 | override def result: XManageDataResult = new XManageDataResult.Builder() 30 | .discriminant(ManageDataResultCode.MANAGE_DATA_SUCCESS) 31 | .build() 32 | } 33 | 34 | /** 35 | * ManageData operation failed because the network was not yet prepared to support this operation. 36 | */ 37 | case object ManageDataNotSupportedYet extends ManageDataResult { 38 | override def result: XManageDataResult = new XManageDataResult.Builder() 39 | .discriminant(ManageDataResultCode.MANAGE_DATA_NOT_SUPPORTED_YET) 40 | .build() 41 | } 42 | 43 | /** 44 | * ManageData operation failed because there was no data entry with the given name. 45 | */ 46 | case object DeleteDataNameNotFound extends ManageDataResult { 47 | override def result: XManageDataResult = new XManageDataResult.Builder() 48 | .discriminant(ManageDataResultCode.MANAGE_DATA_NAME_NOT_FOUND) 49 | .build() 50 | } 51 | 52 | /** 53 | * ManageData operation failed because there was insufficient reserve to support the addition of a new data entry. 54 | */ 55 | case object AddDataLowReserve extends ManageDataResult { 56 | override def result: XManageDataResult = new XManageDataResult.Builder() 57 | .discriminant(ManageDataResultCode.MANAGE_DATA_LOW_RESERVE) 58 | .build() 59 | } 60 | 61 | /** 62 | * ManageData operation failed because the name was not a valid string. 63 | */ 64 | // TODO - all the failure scenarios need to stop masquerading as ProcessedOperationResult 65 | // otherwise xdr.getTr.getDiscriminant is NPE 66 | case object AddDataInvalidName extends ManageDataResult { 67 | override def result: XManageDataResult = new XManageDataResult.Builder() 68 | .discriminant(ManageDataResultCode.MANAGE_DATA_INVALID_NAME) 69 | .build() 70 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/OfferClaim.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.{ClaimOfferAtom, Int64} 4 | import stellar.sdk.PublicKey 5 | import stellar.sdk.model.{AccountId, Amount, Asset} 6 | 7 | case class OfferClaim(seller: PublicKey, offerId: Long, sold: Amount, bought: Amount) { 8 | def xdr: ClaimOfferAtom = new ClaimOfferAtom.Builder() 9 | .amountBought(new Int64(bought.units)) 10 | .amountSold(new Int64(sold.units)) 11 | .assetBought(bought.asset.xdr) 12 | .assetSold(sold.asset.xdr) 13 | .offerID(new Int64(offerId)) 14 | .sellerID(seller.toAccountId.xdr) 15 | .build() 16 | } 17 | 18 | object OfferClaim { 19 | def decodeXdr(xdr: ClaimOfferAtom): OfferClaim = OfferClaim( 20 | seller = AccountId.decodeXdr(xdr.getSellerID).publicKey, 21 | offerId = xdr.getOfferID.getInt64, 22 | sold = Amount( 23 | xdr.getAmountSold.getInt64, 24 | Asset.decodeXdr(xdr.getAssetSold) 25 | ), 26 | bought = Amount( 27 | xdr.getAmountBought.getInt64, 28 | Asset.decodeXdr(xdr.getAssetBought) 29 | ) 30 | ) 31 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/model/result/RevokeSponsorshipResult.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.stellar.xdr.OperationResult.OperationResultTr 4 | import org.stellar.xdr.{OperationType, RevokeSponsorshipResultCode, RevokeSponsorshipResult => XRevokeSponsorshipResult} 5 | 6 | sealed abstract class RevokeSponsorshipResult extends ProcessedOperationResult { 7 | def result: XRevokeSponsorshipResult 8 | val transactionResult: OperationResultTr = new OperationResultTr.Builder() 9 | .discriminant(OperationType.REVOKE_SPONSORSHIP) 10 | .revokeSponsorshipResult(result) 11 | .build() 12 | } 13 | 14 | object RevokeSponsorshipResult { 15 | def decodeXdr(xdr: XRevokeSponsorshipResult): RevokeSponsorshipResult = xdr.getDiscriminant match { 16 | case RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_SUCCESS => RevokeSponsorshipSuccess 17 | case RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_DOES_NOT_EXIST => RevokeSponsorshipDoesNotExist 18 | case RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_NOT_SPONSOR => RevokeSponsorshipNotSponsor 19 | case RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_LOW_RESERVE => RevokeSponsorshipLowReserve 20 | case RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_ONLY_TRANSFERABLE => RevokeSponsorshipOnlyTransferable 21 | } 22 | } 23 | 24 | /** 25 | * RevokeSponsorship operation was successful. 26 | */ 27 | case object RevokeSponsorshipSuccess extends RevokeSponsorshipResult { 28 | override def result: XRevokeSponsorshipResult = new XRevokeSponsorshipResult.Builder() 29 | .discriminant(RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_SUCCESS) 30 | .build() 31 | } 32 | 33 | /** 34 | * RevokeSponsorship operation failed because the sponsorship didn't exist. 35 | */ 36 | case object RevokeSponsorshipDoesNotExist extends RevokeSponsorshipResult { 37 | override def result: XRevokeSponsorshipResult = new XRevokeSponsorshipResult.Builder() 38 | .discriminant(RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_DOES_NOT_EXIST) 39 | .build() 40 | } 41 | 42 | /** 43 | * RevokeSponsorship operation failed because there was a sponsorship, but the sponsoring account did not authorise the 44 | * operation. 45 | */ 46 | case object RevokeSponsorshipNotSponsor extends RevokeSponsorshipResult { 47 | override def result: XRevokeSponsorshipResult = new XRevokeSponsorshipResult.Builder() 48 | .discriminant(RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_NOT_SPONSOR) 49 | .build() 50 | } 51 | 52 | /** 53 | * RevokeSponsorship operation failed because the account had insufficient reserves to cover the sponsored entries. 54 | */ 55 | case object RevokeSponsorshipLowReserve extends RevokeSponsorshipResult { 56 | override def result: XRevokeSponsorshipResult = new XRevokeSponsorshipResult.Builder() 57 | .discriminant(RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_LOW_RESERVE) 58 | .build() 59 | } 60 | 61 | /** 62 | * RevokeSponsorship operation failed because the sponsorship can only be transferred. 63 | */ 64 | case object RevokeSponsorshipOnlyTransferable extends RevokeSponsorshipResult { 65 | override def result: XRevokeSponsorshipResult = new XRevokeSponsorshipResult.Builder() 66 | .discriminant(RevokeSponsorshipResultCode.REVOKE_SPONSORSHIP_ONLY_TRANSFERABLE) 67 | .build() 68 | } 69 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/package.scala: -------------------------------------------------------------------------------- 1 | package stellar 2 | 3 | import stellar.sdk.model.Account 4 | import stellar.sdk.model.response.AccountResponse 5 | 6 | import scala.language.implicitConversions 7 | 8 | package object sdk { 9 | 10 | implicit def accnFromAccnResp(resp: AccountResponse): Account = resp.toAccount 11 | 12 | } -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/util/ByteArrays.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.util 2 | 3 | import java.security.MessageDigest 4 | 5 | import okio.ByteString 6 | import org.apache.commons.codec.binary.Base64 7 | 8 | import scala.annotation.tailrec 9 | import scala.language.implicitConversions 10 | 11 | object ByteArrays { 12 | 13 | object Implicits { 14 | implicit def byteArrayToByteString(arr: Array[Byte]): ByteString = 15 | new ByteString(arr) 16 | 17 | implicit def byteStringToByteArray(byteString: ByteString): Array[Byte] = 18 | byteString.toByteArray 19 | } 20 | 21 | def paddedByteArray(bs: Array[Byte], length: Int): Array[Byte] = { 22 | val padded = Array.ofDim[Byte](math.max(length, bs.length)) 23 | System.arraycopy(bs, 0, padded, 0, bs.length) 24 | padded 25 | } 26 | 27 | def paddedByteArray(s: String, length: Int): Array[Byte] = paddedByteArray(s.getBytes("US-ASCII"), length) 28 | 29 | def paddedByteArrayToString(bs: Array[Byte]): String = new String(bs, "US-ASCII").split("\u0000")(0) 30 | 31 | def trimmedByteArray(bs: Array[Byte]): Seq[Byte] = trimmedByteArray(bs.toIndexedSeq) 32 | def trimmedByteArray(bs: Seq[Byte]): Seq[Byte] = bs.reverse.dropWhile(_ == 0).reverse 33 | 34 | def sha256(bs: Array[Byte]): Array[Byte] = sha256(bs.toIndexedSeq) 35 | def sha256(bs: Seq[Byte]): Array[Byte] = { 36 | val md = MessageDigest.getInstance("SHA-256") 37 | md.update(bs.toArray) 38 | md.digest 39 | } 40 | 41 | def base64(bs: Seq[Byte]): String = base64(bs.toArray) 42 | def base64(bs: Array[Byte]): String = Base64.encodeBase64String(bs) 43 | 44 | def base64(s: String): Array[Byte] = Base64.decodeBase64(s) 45 | 46 | def bytesToHex(bs: Array[Byte]): String = bytesToHex(bs.toIndexedSeq) 47 | def bytesToHex(bs: Seq[Byte]): String = bs.map("%02X".format(_)).mkString 48 | 49 | def hexToBytes(hex: String): Seq[Byte] = hex.toSeq.sliding(2, 2).map(_.unwrap).map(Integer.parseInt(_, 16).toByte).toIndexedSeq 50 | 51 | def checksum(bytes: Array[Byte]): Array[Byte] = { 52 | // This code calculates CRC16-XModem checksum 53 | // Ported from https://github.com/alexgorbatchev/node-crc, via https://github.com/stellar/java-stellar-sdk 54 | 55 | @tailrec 56 | def loop(bs: Seq[Byte], crc: Int): Int = { 57 | bs match { 58 | case h +: t => 59 | var code = crc >>> 8 & 0xFF 60 | code ^= h & 0xFF 61 | code ^= code >>> 4 62 | var crc_ = crc << 8 & 0xFFFF 63 | crc_ ^= code 64 | code = code << 5 & 0xFFFF 65 | crc_ ^= code 66 | code = code << 7 & 0xFFFF 67 | crc_ ^= code 68 | loop(t, crc_) 69 | case Nil => crc 70 | } 71 | } 72 | 73 | val crc = loop(bytes.toIndexedSeq, 0x0000) 74 | Array(crc.toByte, (crc >>> 8).toByte) 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/scala/stellar/sdk/util/DoNothingNetwork.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.util 2 | 3 | import org.json4s.CustomSerializer 4 | import stellar.sdk.Network 5 | import stellar.sdk.inet.HorizonAccess 6 | import stellar.sdk.model.response.{DataValueResponse, TransactionPostResponse} 7 | import stellar.sdk.model.{HorizonCursor, HorizonOrder, SignedTransaction} 8 | 9 | import scala.concurrent.{ExecutionContext, Future} 10 | import scala.reflect.ClassTag 11 | 12 | class DoNothingNetwork(override val passphrase: String = "Scala SDK do-nothing network") extends Network { 13 | override val horizon: HorizonAccess = new HorizonAccess { 14 | override def post(txn: SignedTransaction)(implicit ec: ExecutionContext): Future[TransactionPostResponse] = ??? 15 | 16 | override def get[T: ClassTag](path: String, params: Map[String, String]) 17 | (implicit ec: ExecutionContext, m: Manifest[T]): Future[T] = 18 | if (path.endsWith("data/data_key")) { 19 | Future(DataValueResponse("00").asInstanceOf[T])(ec) 20 | } else ??? 21 | 22 | override def getStream[T: ClassTag](path: String, de: CustomSerializer[T], cursor: HorizonCursor, order: HorizonOrder, params: Map[String, String] = Map.empty) 23 | (implicit ec: ExecutionContext, m: Manifest[T]): Future[LazyList[T]] = 24 | ??? 25 | 26 | override def getSeq[T: ClassTag](path: String, de: CustomSerializer[T], params: Map[String, String]) 27 | (implicit ec: ExecutionContext, m: Manifest[T]): Future[LazyList[T]] = 28 | Future.successful(LazyList.empty[T]) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/NetworkSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk 2 | 3 | import org.specs2.concurrent.ExecutionEnv 4 | import org.specs2.mock.Mockito 5 | import org.specs2.mutable.Specification 6 | 7 | import scala.concurrent.duration._ 8 | 9 | class NetworkSpec(implicit ee: ExecutionEnv) extends Specification with ArbitraryInput with Mockito { 10 | 11 | "test network" should { 12 | "identify itself" >> { 13 | TestNetwork.passphrase mustEqual "Test SDF Network ; September 2015" 14 | BigInt(1, TestNetwork.networkId).toString(16).toUpperCase mustEqual 15 | "CEE0302D59844D32BDCA915C8203DD44B33FBB7EDC19051EA37ABEDF28ECD472" 16 | } 17 | 18 | "provide network info" >> { 19 | TestNetwork.info() must not(throwAn[Exception]).awaitFor(10.seconds) 20 | } 21 | } 22 | 23 | "public network" should { 24 | "identify itself" >> { 25 | PublicNetwork.passphrase mustEqual "Public Global Stellar Network ; September 2015" 26 | BigInt(1, PublicNetwork.networkId).toString(16).toUpperCase mustEqual 27 | "7AC33997544E3175D266BD022439B22CDB16508C01163F26E5CB2A3E1045A979" 28 | } 29 | } 30 | 31 | "any network" should { 32 | "provide access to the master account" >> { 33 | val networkAccountId = "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H" 34 | TestNetwork.masterAccount.accountId mustEqual networkAccountId 35 | KeyPair.fromSecretSeed(TestNetwork.masterAccount.secretSeed).accountId mustEqual networkAccountId 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/app/ParseTimeWindow.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.app 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | import stellar.sdk.PublicNetwork 5 | import stellar.sdk.model.ledger.TransactionLedgerEntries 6 | import stellar.sdk.model.{Desc, Now} 7 | 8 | import java.time.ZonedDateTime 9 | import scala.concurrent.Await 10 | import scala.concurrent.ExecutionContext.Implicits.global 11 | import scala.concurrent.duration._ 12 | import scala.util.{Failure, Success, Try} 13 | 14 | object ParseTimeWindow extends LazyLogging { 15 | 16 | def main(args: Array[String]): Unit = { 17 | 18 | val timeWindow = ZonedDateTime.now().minusMinutes(5) 19 | 20 | val (failures, successCount) = Await.result( 21 | PublicNetwork.transactions(Now, Desc).map { stream => 22 | stream 23 | .takeWhile(_.createdAt.isAfter(timeWindow)) 24 | .map { th => th.hash -> Try(th.ledgerEntries) } 25 | .foldLeft((List.empty[(String, Failure[TransactionLedgerEntries])], 0)) { 26 | case ((failures, successCount), _ -> Success(_)) => (failures, successCount + 1) 27 | case ((failures, successCount), hash -> Failure(f)) => 28 | f.printStackTrace() 29 | val elem: (String, Failure[TransactionLedgerEntries]) = hash -> Failure(f) 30 | (elem +: failures) -> successCount 31 | } 32 | }, 10.minutes) 33 | logger.info(s"Processed $successCount transactions") 34 | logger.info(s"Encountered ${failures.size} errors parsing ledger entries") 35 | failures.take(20).foreach { case (hash, Failure(f)) => 36 | logger.error(s"Transaction $hash failed", f) 37 | } 38 | 39 | 40 | val (ops, ledgers) = Await.result(PublicNetwork.ledgers(Now, Desc).map { stream => 41 | stream 42 | .takeWhile(_.closedAt.isAfter(timeWindow)) 43 | .foldLeft((0, 0)) { case ((opCount, ledgerCount), next) => (opCount + next.operationCount, ledgerCount + 1) } 44 | }, 10.minutes) 45 | logger.info(s"Processed $ledgers ledgers, with $ops operations") 46 | 47 | 48 | val effectCount = Await.result(PublicNetwork.effects(Now, Desc).map { stream => 49 | stream 50 | .takeWhile(_.createdAt.isAfter(timeWindow)) 51 | .size 52 | }, 10.minutes) 53 | logger.info(s"Processed $effectCount effects") 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/inet/PageSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.inet 2 | 3 | import java.net.HttpURLConnection.{HTTP_BAD_REQUEST, HTTP_NOT_FOUND} 4 | 5 | import okhttp3.HttpUrl 6 | import org.json4s.DefaultFormats 7 | import org.json4s.JsonAST.JObject 8 | import org.json4s.native.JsonMethods 9 | import org.specs2.mutable.Specification 10 | import stellar.sdk.model.response.ResponseParser 11 | 12 | class PageSpec extends Specification { 13 | 14 | implicit val formats = DefaultFormats + RawPageDeserializer + HelloDeserializer 15 | 16 | "page parsing" should { 17 | "return an empty page if no results were found" >> { 18 | val page = PageParser.parse[String](HttpUrl.parse("http://localhost/"), HTTP_NOT_FOUND, "") 19 | page.xs must beEmpty 20 | } 21 | 22 | "throw a bad request exception with the reasons when provided" >> { 23 | val url = HttpUrl.parse("http://localhost/") 24 | PageParser.parse[String](url, HTTP_BAD_REQUEST, 25 | """{ 26 | | "type": "https://stellar.org/horizon-errors/bad_request", 27 | | "title": "Bad Request", 28 | | "status": 400, 29 | | "detail": "The request you sent was invalid in some way.", 30 | | "extras": { 31 | | "invalid_field": "cursor", 32 | | "reason": "cursor must contain exactly one colon" 33 | | } 34 | |}""".stripMargin) must throwA[HorizonBadRequest].like { e => 35 | e.getMessage mustEqual "Bad request. http://localhost/ -> cursor must contain exactly one colon" 36 | } 37 | } 38 | 39 | "throw a bad request exception with the full document when the reason is not provided" >> { 40 | val url = HttpUrl.parse("http://localhost/") 41 | PageParser.parse[String](url, HTTP_BAD_REQUEST, "random text") must throwA[HorizonBadRequest].like { e => 42 | e.getMessage mustEqual "Bad request. http://localhost/ -> random text" 43 | } 44 | } 45 | 46 | "parse the member values and provide a link to the next page" >> { 47 | val doc = 48 | """ 49 | |{ 50 | | "_links": { 51 | | "self": { 52 | | "href": "https://horizon-testnet.stellar.org/hello?cursor=\u0026limit=10\u0026order=asc" 53 | | }, 54 | | "next": { 55 | | "href": "https://horizon-testnet.stellar.org/hello?cursor=2045052972961793-0\u0026limit=10\u0026order=asc" 56 | | }, 57 | | "prev": { 58 | | "href": "https://horizon-testnet.stellar.org/hello?cursor=940258535411713-0\u0026limit=10\u0026order=desc" 59 | | } 60 | | }, 61 | | "_embedded": { 62 | | "records": [ 63 | | {"hello":"world"}, 64 | | {"hello":"whirled"} 65 | | ] 66 | | } 67 | |} 68 | """.stripMargin 69 | 70 | JsonMethods.parse(doc).extract[RawPage].parse[String](HttpUrl.parse("http://localhost/")) mustEqual Page( 71 | List("world", "whirled"), 72 | nextLink = Some(HttpUrl.parse("https://horizon-testnet.stellar.org/hello?cursor=2045052972961793-0&limit=10&order=asc")) 73 | ) 74 | } 75 | } 76 | 77 | object HelloDeserializer extends ResponseParser[String]({ o: JObject => 78 | implicit val formats = DefaultFormats 79 | (o \ "hello").extract[String] 80 | }) 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/key/HDNodeSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.key 2 | 3 | import okio.ByteString 4 | import org.specs2.mutable.Specification 5 | 6 | class HDNodeSpec extends Specification { 7 | 8 | "HD master node" should { 9 | "be derived from entropy" >> { 10 | // https://github.com/satoshilabs/slips/blob/master/slip-0010.md#test-vector-1-for-ed25519 11 | val masterNode = HDNode.fromEntropy(ByteString.decodeHex("000102030405060708090a0b0c0d0e0f")) 12 | masterNode.privateKey mustEqual ByteString.decodeHex("2b4be7f19ee27bbf30c667b642d5f4aa69fd169872f8fc3059c08ebae2eb19e7") 13 | masterNode.chainCode mustEqual ByteString.decodeHex("90046a93de5380a72b5e45010748567d5ea02bbf6522f979e05c0d8d8ca9fffb") 14 | } 15 | 16 | "deterministically derive child nodes" >> { 17 | val masterNode = HDNode.fromEntropy(ByteString.decodeHex("000102030405060708090a0b0c0d0e0f")) 18 | val childZero = masterNode.deriveChild(0) 19 | val childOne = childZero.deriveChild(1) 20 | 21 | childZero.privateKey mustEqual ByteString.decodeHex("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3") 22 | childZero.chainCode mustEqual ByteString.decodeHex("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69") 23 | 24 | childOne.privateKey mustEqual ByteString.decodeHex("b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2") 25 | childOne.chainCode mustEqual ByteString.decodeHex("a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14") 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/key/WordListSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.key 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class WordListSpec extends Specification { 6 | 7 | val supported = List( 8 | ChineseSimplifiedWords, 9 | ChineseTraditionalWords, 10 | CzechWords, 11 | EnglishWords, 12 | FrenchWords, 13 | ItalianWords, 14 | JapaneseWords, 15 | KoreanWords, 16 | SpanishWords 17 | ) 18 | 19 | "a wordlist" should { 20 | "allow indexed access to all words" >> { 21 | forall(supported) { wordList => 22 | wordList.words.length mustEqual 2048 23 | wordList.words.distinct.length mustEqual 2048 24 | forall(wordList.words) { word => 25 | wordList.indexOf(word).map(wordList.wordAt) must beSome(word) 26 | } 27 | } 28 | } 29 | 30 | "disallow access to words outside the index" >> { 31 | forall(supported) { wordList => 32 | wordList.wordAt(-1) must throwAn[IllegalArgumentException] 33 | wordList.wordAt(wordList.words.length) must throwAn[IllegalArgumentException] 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/AccountSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.specs2.mutable.Specification 4 | import stellar.sdk.ArbitraryInput 5 | 6 | class AccountSpec extends Specification with ArbitraryInput { 7 | 8 | "an account" should { 9 | "provide the successive version of itself" >> prop { account: Account => 10 | val next = account.withIncSeq 11 | next.sequenceNumber mustEqual account.sequenceNumber + 1 12 | next.id mustEqual account.id 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/AmountSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.scalacheck.Gen 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.ArbitraryInput 6 | 7 | class AmountSpec extends Specification with ArbitraryInput { 8 | 9 | "an amount" should { 10 | "convert base unit to display unit" >> prop { l: Long => 11 | val displayed = NativeAmount(l).toDisplayUnits 12 | if (l <= 9999999L) displayed mustEqual f"0.$l%07d" 13 | else { 14 | val lStr = l.toString 15 | displayed mustEqual s"${lStr.take(lStr.length - 7)}.${lStr.drop(lStr.length - 7)}" 16 | } 17 | }.setGen(Gen.posNum[Long]) 18 | 19 | "convert to base units without losing precision" >> { 20 | Amount.toBaseUnits("100076310227.4749892") must beASuccessfulTry(1000763102274749892L) 21 | Amount.toBaseUnits("100076310227.4749892").map(NativeAmount).map(_.toDisplayUnits) must beASuccessfulTry( 22 | "100076310227.4749892" 23 | ) 24 | } 25 | } 26 | 27 | "a number of units and a non-native asset" should { 28 | "should compose to an IssuedAmount" >> prop { (units: Long, nonNativeAsset: NonNativeAsset) => 29 | Amount(units, nonNativeAsset) mustEqual IssuedAmount(units, nonNativeAsset) 30 | }.setGen1(Gen.posNum[Long]) 31 | } 32 | 33 | "converting a number to base units" should { 34 | "round correctly" >> prop { l: Double => 35 | val moreThan7DecimalPlaces = (l.toString.length - l.toString.indexOf('.')) > 8 36 | if (moreThan7DecimalPlaces) { 37 | Amount.toBaseUnits(l) must beAFailedTry[Long] 38 | } else { 39 | val expected = l.toString.takeWhile(_ != '.') + (l.toString + "0000000").dropWhile(_ != '.').slice(1, 8) 40 | Amount.toBaseUnits(l) must beASuccessfulTry[Long].like { case a => a.toString mustEqual expected } 41 | } 42 | }.setGen(Gen.posNum[Double]) 43 | 44 | "parse from string correctly" >> { 45 | Amount.toBaseUnits("100076310227.4749892") must beASuccessfulTry[Long](1000763102274749892L) 46 | Amount.toBaseUnits("100076310227.4749") must beASuccessfulTry[Long](1000763102274749000L) 47 | } 48 | } 49 | 50 | "throw an exception if there are too many digits in fractional portion of lumens constructor" >> { 51 | Amount.lumens(0.1234567) must not(throwAn[IllegalArgumentException]) 52 | Amount.lumens(0.12345678) must throwAn[IllegalArgumentException] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ClaimableBalanceIdSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import okio.ByteString 4 | import org.scalacheck.{Arbitrary, Gen} 5 | import org.specs2.mutable.Specification 6 | import stellar.sdk.ArbitraryInput 7 | import stellar.sdk.model.ClaimableBalanceIds.genClaimableBalanceId 8 | 9 | class ClaimableBalanceIdSpec extends Specification with ArbitraryInput { 10 | 11 | implicit val arbClaimableBalanceId: Arbitrary[ClaimableBalanceId] = Arbitrary(genClaimableBalanceId) 12 | 13 | "claimable balance hash id" should { 14 | "serde via xdr bytes" >> prop { id: ClaimableBalanceId => 15 | ClaimableBalanceId.decodeXdr(id.xdr) mustEqual id 16 | } 17 | 18 | } 19 | } 20 | 21 | object ClaimableBalanceIds { 22 | val genClaimableBalanceId: Gen[ClaimableBalanceId] = 23 | Gen.containerOfN[Array, Byte](32, Gen.posNum[Byte]).map(new ByteString(_)).map(ClaimableBalanceHashId) 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ClaimableBalanceSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import okio.ByteString 4 | import org.json4s.native.JsonMethods 5 | import org.json4s.{DefaultFormats, Formats} 6 | import org.scalacheck.{Arbitrary, Gen} 7 | import org.specs2.ScalaCheck 8 | import org.specs2.mutable.Specification 9 | import stellar.sdk.ArbitraryInput 10 | import stellar.sdk.model.ClaimableBalanceGenerators.genClaimableBalance 11 | import stellar.sdk.model.ClaimantGenerators.genClaimant 12 | 13 | class ClaimableBalanceSpec extends Specification with ScalaCheck { 14 | 15 | implicit val arbClaimableBalance: Arbitrary[ClaimableBalance] = Arbitrary(genClaimableBalance) 16 | implicit val formats: Formats = DefaultFormats + ClaimableBalanceDeserializer 17 | 18 | "a claimable balance" should { 19 | "decode from JSON" >> prop { balance: ClaimableBalance => 20 | JsonMethods.parse(ClaimableBalanceGenerators.json(balance)).extract[ClaimableBalance] mustEqual balance 21 | } 22 | } 23 | 24 | } 25 | 26 | object ClaimableBalanceGenerators extends ArbitraryInput { 27 | 28 | def genClaimableBalance: Gen[ClaimableBalance] = for { 29 | id <- Gen.containerOfN[Array, Byte](32, Gen.posNum[Byte]).map(new ByteString(_)).map(ClaimableBalanceHashId) 30 | amount <- genAmount 31 | sponsor <- genPublicKey 32 | claimants <- Gen.nonEmptyListOf[Claimant](genClaimant) 33 | lastModifiedLedger <- Gen.posNum[Long] 34 | lastModifiedTime <- genInstant 35 | } yield ClaimableBalance(id, amount, sponsor, claimants, lastModifiedLedger, lastModifiedTime) 36 | 37 | def json(balance: ClaimableBalance): String = 38 | s"""{ 39 | | "id": "${balance.id.encodeString}", 40 | | "asset": "${balance.amount.asset.canoncialString}", 41 | | "amount": "${balance.amount.toDisplayUnits}", 42 | | "sponsor": "${balance.sponsor.accountId}", 43 | | "last_modified_ledger": ${balance.lastModifiedLedger}, 44 | | "last_modified_time": "${balance.lastModifiedTime}", 45 | | "claimants": [${balance.claimants.map(ClaimantGenerators.json).mkString(",")}] 46 | |}""".stripMargin 47 | } 48 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ClaimantSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.{Formats, NoTypeHints} 4 | import org.json4s.native.{JsonMethods, Serialization} 5 | import org.json4s.native.JsonMethods.parse 6 | import org.scalacheck.{Arbitrary, Gen} 7 | import org.specs2.ScalaCheck 8 | import org.specs2.mutable.Specification 9 | import stellar.sdk.ArbitraryInput 10 | import stellar.sdk.model.ClaimantGenerators.{genAccountIdClaimant, genClaimant, json} 11 | 12 | class ClaimantSpec extends Specification with ScalaCheck { 13 | 14 | implicit val arbClaimant: Arbitrary[Claimant] = Arbitrary(genClaimant) 15 | implicit val arbAccountIdClaimant: Arbitrary[AccountIdClaimant] = Arbitrary(genAccountIdClaimant) 16 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + ClaimantDeserializer 17 | 18 | "a claimant" should { 19 | "serde to/from XDR" >> prop { claimant: Claimant => 20 | Claimant.decodeXdr(claimant.xdr) mustEqual claimant 21 | } 22 | 23 | "parse from JSON" >> prop { claimant: AccountIdClaimant => 24 | parse(json(claimant)).extract[Claimant] mustEqual claimant 25 | } 26 | } 27 | } 28 | 29 | object ClaimantGenerators extends ArbitraryInput { 30 | 31 | val genAccountIdClaimant: Gen[AccountIdClaimant] = for { 32 | accountId <- genPublicKey 33 | predicate <- ClaimPredicateGenerators.genClaimPredicate 34 | } yield AccountIdClaimant(accountId, predicate) 35 | 36 | val genClaimant: Gen[Claimant] = Gen.oneOf(genAccountIdClaimant, genAccountIdClaimant) 37 | 38 | def json(claimant: Claimant): String = claimant match { 39 | case a: AccountIdClaimant => json(a) 40 | } 41 | 42 | def json(claimant: AccountIdClaimant): String = 43 | s""" 44 | |{ 45 | | "destination": "${claimant.accountId.accountId}", 46 | | "predicate": ${ClaimPredicateGenerators.json(claimant.predicate)} 47 | |}""".stripMargin 48 | 49 | } -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/HorizonCursorSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.specs2.ScalaCheck 4 | import org.specs2.mutable.Specification 5 | 6 | class HorizonCursorSpec extends Specification with ScalaCheck { 7 | "creating a cursor with a specified number" should { 8 | "have the correct paramString" >> prop { l: Long => 9 | Record(l).paramValue mustEqual l.toString 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/MemoSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import okio.ByteString 4 | import org.scalacheck.Arbitrary._ 5 | import org.scalacheck.Gen 6 | import org.specs2.mutable.Specification 7 | import stellar.sdk.util.ByteArrays._ 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class MemoSpec extends Specification with ArbitraryInput with DomainMatchers { 11 | 12 | "a text memo" should { 13 | "not be constructable with > 28 bytes" >> prop { s: String => 14 | MemoText(s) must throwAn[AssertionError] 15 | }.setGen(arbString.arbitrary.suchThat(_.getBytes("UTF-8").length > 28)) 16 | } 17 | 18 | "a id memo" should { 19 | "be constructable with zero" >> { 20 | MemoId(0) must not(throwAn[AssertionError]) 21 | } 22 | 23 | "be constructable with negative number" >> prop { id: Long => 24 | MemoId(id).toString mustEqual s"MemoId(${java.lang.Long.toUnsignedString(id)})" 25 | }.setGen(Gen.negNum[Long]) 26 | } 27 | 28 | "a memo hash" should { 29 | "not be constructable with != 32 bytes" >> { 30 | MemoHash((1 to 33).map(_.toByte).toArray) must throwAn[AssertionError] 31 | } 32 | 33 | "be created from a hash" >> prop { hash64: String => 34 | val hex = ByteString.decodeBase64(hash64).hex() 35 | MemoHash.from(hex) must beSuccessfulTry.like { case m: MemoHash => 36 | m.bs.hex() mustEqual hex 37 | } 38 | MemoHash.from(s"${hex}Z") must beFailedTry[MemoHash] 39 | }.setGen(genHash) 40 | } 41 | 42 | "a memo return hash" should { 43 | "not be constructable with > 32 bytes" >> { 44 | MemoReturnHash((1 to 33).map(_.toByte).toArray) must throwAn[AssertionError] 45 | } 46 | 47 | "be created from a hash" >> prop { hash64: String => 48 | val hex = ByteString.decodeBase64(hash64).hex() 49 | MemoReturnHash.from(hex) must beSuccessfulTry.like { case m: MemoReturnHash => 50 | m.bs.hex() mustEqual hex 51 | } 52 | MemoReturnHash.from(s"${hex}Z") must beFailedTry[MemoReturnHash] 53 | }.setGen(genHash) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/PaymentPathSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.{JsonMethods, Serialization} 5 | import org.specs2.mutable.Specification 6 | import stellar.sdk.ArbitraryInput 7 | 8 | class PaymentPathSpec extends Specification with ArbitraryInput { 9 | 10 | implicit val formats = Serialization.formats(NoTypeHints) + PaymentPathDeserializer 11 | 12 | "a payment path response document" should { 13 | "parse to a payment path" >> prop { path: PaymentPath => 14 | 15 | def amountJson(prefix: String, amount: Amount) = 16 | s""" 17 | |"${prefix}amount": "${amount.toDisplayUnits}", 18 | |${assetJson(prefix, amount.asset)} 19 | """.stripMargin 20 | 21 | def assetJson(prefix: String, asset: Asset) = { 22 | asset match { 23 | case NativeAsset => s""""${prefix}asset_type": "native"""" 24 | case issuedAsset: NonNativeAsset => 25 | s""" 26 | |"${prefix}asset_type": "${issuedAsset.typeString}", 27 | |"${prefix}asset_code": "${issuedAsset.code}", 28 | |"${prefix}asset_issuer": "${issuedAsset.issuer.accountId}" 29 | """.stripMargin 30 | } 31 | } 32 | 33 | val json = 34 | s""" 35 | |{ 36 | | ${amountJson("source_", path.source)}, 37 | | ${amountJson("destination_", path.destination)}, 38 | | "path": ${path.path.map(j => s"{${assetJson("", j)}}").mkString("[", ",", "]")} 39 | |} 40 | """.stripMargin 41 | 42 | JsonMethods.parse(json).extract[PaymentPath] mustEqual path 43 | } 44 | } 45 | 46 | "the underlying amount parser" should { 47 | "not parse unrecognised asset type" >> { 48 | val doc = """{"foo_asset_type":"bananas"}""" 49 | AmountParser.parseAsset("foo_", JsonMethods.parse(doc)) must throwA[RuntimeException] 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/PaymentSigningRequestSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.specs2.mutable.Specification 4 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 5 | 6 | class PaymentSigningRequestSpec extends Specification with ArbitraryInput with DomainMatchers { 7 | 8 | "encoding as a web+stellar url" should { 9 | "decode to the original" >> prop { signingRequest: PaymentSigningRequest => 10 | val url = signingRequest.toUrl 11 | PaymentSigningRequest(url) mustEqual signingRequest 12 | } 13 | } 14 | 15 | "parsing from url" should { 16 | "fail when msg is greater than 300 chars" >> { 17 | val request: String = sampleOne(genPaymentSigningRequest).copy(message = None).toUrl 18 | PaymentSigningRequest(s"$request&msg=${"x" * 301}") must throwAn[IllegalArgumentException] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/SignerSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.specs2.mutable.Specification 4 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 5 | 6 | class SignerSpec extends Specification with ArbitraryInput with DomainMatchers { 7 | 8 | "a signer" should { 9 | "serde via xdr bytes" >> prop { expected: Signer => 10 | Signer.decodeXdr(expected.xdr) mustEqual expected 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/StrKeySpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.specs2.mutable.Specification 4 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 5 | 6 | class StrKeySpec extends Specification with ArbitraryInput with DomainMatchers { 7 | 8 | "strkey" should { 9 | "encode and decode to same" >> prop { key: StrKey => 10 | val string = key.encodeToChars.mkString 11 | StrKey.decodeFromString(string) must beEquivalentTo(key) 12 | } 13 | 14 | "fail to decode if any char is > 127" >> { 15 | StrKey.decodeFromString("welcome to the 草叢") must throwAn[AssertionError] 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/TimeBoundsSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import java.time.Instant 4 | 5 | import org.specs2.mutable.Specification 6 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 7 | import scala.concurrent.duration._ 8 | 9 | import scala.util.Try 10 | 11 | class TimeBoundsSpec extends Specification with ArbitraryInput with DomainMatchers { 12 | 13 | "time bounds creation" should { 14 | "fail if it doesn't ends after it begins" >> prop { (a: Instant, b: Instant) => 15 | (if (a.isBefore(b)) { 16 | TimeBounds(b, a) 17 | } else { 18 | TimeBounds(a, b) 19 | }) must throwAn[IllegalArgumentException] 20 | } 21 | 22 | "succeed if it ends after it begins" >> prop { (a: Instant, b: Instant) => { 23 | Try { 24 | if (a.isBefore(b)) TimeBounds(a, b) else TimeBounds(b, a) 25 | } must beSuccessfulTry[TimeBounds] 26 | }.unless(a == b) 27 | } 28 | 29 | "be via a 'from-now' timeout" >> { 30 | val now = Instant.now().toEpochMilli 31 | val tb = TimeBounds.timeout(1.minute) 32 | tb.start.toEpochMilli must beCloseTo(now - 5000, delta = 1000) 33 | tb.end.toEpochMilli must beCloseTo(now + 60000, delta = 1000) 34 | } 35 | } 36 | 37 | "time bounds" should { 38 | "serde to/from xdr" >> prop { tb: TimeBounds => 39 | TimeBounds.decodeXdr(tb.xdr) mustEqual tb 40 | } 41 | 42 | "exclude any instant before the start" >> prop { tb: TimeBounds => 43 | tb.includes(tb.start.minusNanos(1)) must beFalse 44 | } 45 | 46 | "exclude any instant after the end" >> prop { tb: TimeBounds => 47 | tb.includes(tb.end.plusNanos(1)) must beFalse 48 | } 49 | 50 | "include the instant at the start" >> prop { tb: TimeBounds => 51 | tb.includes(tb.start) must beTrue 52 | } 53 | 54 | "include the instant at the end" >> prop { tb: TimeBounds => 55 | tb.includes(tb.end) must beTrue 56 | } 57 | 58 | "include the instants within the bounds" >> prop { tb: TimeBounds => 59 | val midpoint = Instant.ofEpochMilli((tb.end.toEpochMilli - tb.start.toEpochMilli) / 2 + tb.start.toEpochMilli) 60 | tb.includes(midpoint) must beTrue 61 | } 62 | } 63 | 64 | "unbounded time bounds" should { 65 | "always include any instant" >> prop { instant: Instant => 66 | TimeBounds.Unbounded.includes(instant) must beTrue 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/TradeAggregationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.{JsonMethods, Serialization} 5 | import org.specs2.mutable.Specification 6 | import stellar.sdk.ArbitraryInput 7 | 8 | class TradeAggregationSpec extends Specification with ArbitraryInput { 9 | implicit val formats = Serialization.formats(NoTypeHints) + TradeAggregationDeserializer 10 | 11 | "a payment path response document" should { 12 | "parse to a payment path" >> prop { ta: TradeAggregation => 13 | val json = 14 | s""" 15 | |{ 16 | | "timestamp": ${ta.instant.toEpochMilli.toString}, 17 | | "trade_count": ${ta.tradeCount}, 18 | | "base_volume": "${ta.baseVolume}", 19 | | "counter_volume": "${ta.counterVolume}", 20 | | "avg": "${ta.average}", 21 | | "high": "${ta.high.asDecimalString}", 22 | | "high_r": { 23 | | "N": ${ta.high.n}, 24 | | "D": ${ta.high.d} 25 | | }, 26 | | "low": "${ta.low.asDecimalString}", 27 | | "low_r": { 28 | | "N": ${ta.low.n}, 29 | | "D": ${ta.low.d} 30 | | }, 31 | | "open": "${ta.open.asDecimalString}", 32 | | "open_r": { 33 | | "N": ${ta.open.n}, 34 | | "D": ${ta.open.d} 35 | | }, 36 | | "close": "${ta.close.asDecimalString}", 37 | | "close_r": { 38 | | "N": ${ta.close.n}, 39 | | "D": ${ta.close.d.toString} 40 | | } 41 | |} 42 | """.stripMargin 43 | 44 | JsonMethods.parse(json).extract[TradeAggregation] mustEqual ta 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/TradeSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.specs2.mutable.Specification 7 | import stellar.sdk.ArbitraryInput 8 | import stellar.sdk.model.op.JsonSnippets 9 | 10 | class TradeSpec extends Specification with ArbitraryInput with JsonSnippets { 11 | 12 | implicit val formats = Serialization.formats(NoTypeHints) + TradeDeserializer 13 | 14 | "trade" should { 15 | "parse from json" >> prop { trade: Trade => 16 | 17 | val doc = 18 | s""" 19 | |{ 20 | | "_links": { 21 | | "self": {"href": ""}, 22 | | "base": {"href": "https://horizon.stellar.org/accounts/GCI7ILB37OFVHLLSA74UCXZFCTPEBJOZK7YCNBI7DKH7D76U4CRJBL2A"}, 23 | | "counter": {"href": "https://horizon.stellar.org/accounts/GDRFRGR2FDUFF2RI6PQE5KFSCJHGSEIOGET22R66XSATP3BYHZ46BPLO"}, 24 | | "operation": {"href": "https://horizon.stellar.org/operations/38583306127675393"} 25 | | }, 26 | | "id": "${trade.id}", 27 | | "paging_token": "38583306127675393-2", 28 | | "ledger_close_time": "${formatter.format(trade.ledgerCloseTime)}", 29 | | "offer_id": "${trade.offerId}", 30 | | "base_offer_id": "${trade.baseOfferId}", 31 | | "base_account": "${trade.baseAccount.accountId}", 32 | | ${amountDocPortion(trade.baseAmount, "base_amount", "base_")} 33 | | ${amountDocPortion(trade.counterAmount, "counter_amount", "counter_")} 34 | | "counter_account": "${trade.counterAccount.accountId}", 35 | | "counter_offer_id": "${trade.counterOfferId}", 36 | | "base_is_seller": ${trade.baseIsSeller} 37 | |} 38 | """.stripMargin 39 | 40 | parse(doc).extract[Trade] mustEqual trade 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/domain/DomainInfoGenerators.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.domain 2 | 3 | import org.scalacheck.Gen 4 | import stellar.sdk.ArbitraryInput 5 | import stellar.sdk.model.{IssuedAsset12, IssuedAsset4, NonNativeAsset} 6 | import stellar.sdk.model.domain.Currency._ 7 | 8 | trait DomainInfoGenerators extends ArbitraryInput { 9 | 10 | val genPointOfContact: Gen[PointOfContact] = for { 11 | name <- Gen.option(Gen.identifier) 12 | email <- Gen.option(Gen.identifier) 13 | keybase <- Gen.option(Gen.identifier) 14 | telegram <- Gen.option(Gen.identifier) 15 | twitter <- Gen.option(Gen.identifier) 16 | github <- Gen.option(Gen.identifier) 17 | id_photo_hash <- Gen.option(genHash) 18 | verification_photo_hash <- Gen.option(genHash) 19 | } yield PointOfContact( 20 | name, email, keybase, telegram, twitter, github, id_photo_hash, verification_photo_hash) 21 | 22 | val genCollateral: Gen[Collateral] = for { 23 | address <- Gen.identifier 24 | message <- Gen.identifier 25 | proof <- genHash 26 | } yield Collateral(address, message, proof) 27 | 28 | val genAssetTemplate: Gen[NonNativeAsset] = { 29 | def replaceChars(s: String): String = { 30 | val chars = Gen.nonEmptyListOf(Gen.alphaNumChar).sample.get.toSet 31 | s.map { c => if (chars.contains(c)) '?' else c } 32 | } 33 | 34 | genNonNativeAsset.map { 35 | case IssuedAsset4(code, issuer) => IssuedAsset4(replaceChars(code), issuer) 36 | case IssuedAsset12(code, issuer) => IssuedAsset12(replaceChars(code), issuer) 37 | } 38 | } 39 | 40 | val genCurrency: Gen[Currency] = for { 41 | asset <- Gen.option(Gen.oneOf(genNonNativeAsset, genAssetTemplate)) 42 | name <- Gen.option(Gen.identifier) 43 | description <- Gen.option(Gen.identifier) 44 | status <- Gen.option(Gen.oneOf(Live, Dead, Test, Private)) 45 | displayDecimals <- Gen.choose(0, 7) 46 | conditions <- Gen.option(Gen.identifier) 47 | image <- Gen.option(genUri) 48 | fixedQuantity <- Gen.option(Gen.posNum[Int]) 49 | maxQuantity <- Gen.option(Gen.posNum[Int]) 50 | isUnlimited <- Gen.option(Gen.oneOf(false, true)) 51 | isAnchored <- Gen.option(Gen.oneOf(false, true)) 52 | assetType <- Gen.option(Gen.alphaNumStr) 53 | anchoredAsset <- Gen.option(Gen.alphaNumStr) 54 | redemptionInstructions <- Gen.option(Gen.alphaNumStr) 55 | collateral <- Gen.listOf(genCollateral) 56 | isRegulated <- Gen.oneOf(false, true) 57 | approvalServer <- Gen.option(genUri) 58 | approvalCriteria <- Gen.option(Gen.alphaNumStr) 59 | } yield Currency( 60 | asset, name, description, status, displayDecimals, conditions, image, fixedQuantity, maxQuantity, 61 | isUnlimited, isAnchored, assetType, anchoredAsset, redemptionInstructions, collateral, 62 | isRegulated, approvalServer, approvalCriteria) 63 | 64 | val genValidator: Gen[Validator] = for { 65 | alias <- Gen.option(Gen.identifier) 66 | displayName <- Gen.option(Gen.identifier) 67 | publicKey <- Gen.option(genPublicKey) 68 | host <- Gen.option(Gen.identifier) 69 | history <- Gen.option(genUri) 70 | } yield Validator(alias, displayName, publicKey, host, history) 71 | } 72 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ledger/LedgerEntryChangeSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | import org.specs2.mutable.Specification 5 | 6 | import scala.util.{Failure, Try} 7 | 8 | class LedgerEntryChangeSpec extends Specification with LedgerEntryGenerators with LazyLogging { 9 | 10 | "a ledger entry change" should { 11 | "serde to/from XDR" >> prop { change: LedgerEntryChange => 12 | LedgerEntryChange.decodeXdr(change.xdr) mustEqual change 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ledger/LedgerEntrySpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | import org.specs2.mutable.Specification 5 | 6 | class LedgerEntrySpec extends Specification with LedgerEntryGenerators with LazyLogging { 7 | 8 | "a ledger entry" should { 9 | "serde to/from XDR" >> prop { entry: LedgerEntry => 10 | LedgerEntry.decodeXdr(entry.xdr) mustEqual entry 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ledger/LedgerKeySpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import org.specs2.mutable.Specification 4 | 5 | class LedgerKeySpec extends Specification with LedgerEntryGenerators { 6 | 7 | "a ledger key" should { 8 | "serde to/from XDR" >> prop { ledgerKey: LedgerKey => 9 | LedgerKey.decodeXdr(ledgerKey.xdr) mustEqual ledgerKey 10 | } 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/ledger/TransactionLedgerEntriesSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.ledger 2 | 3 | import com.typesafe.scalalogging.LazyLogging 4 | import org.specs2.mutable.Specification 5 | 6 | class TransactionLedgerEntriesSpec extends Specification with LedgerEntryGenerators with LazyLogging { 7 | 8 | "a ledger entry" should { 9 | "parse results with sponsorship data" >> { 10 | // https://horizon.stellar.org/transactions/680eb3798b6a0cd3ed37ac20cef8a2077392cd7cc886e67c8629b14c765e809c 11 | TransactionLedgerEntries.decodeXDR("AAAAAgAAAAIAAAADAgJjjwAAAAAAAAAA35UHgEst0l96gXTDldD6W84qtk5mJxaaS4" + 12 | "mAN7JfHA8AAAAAAxsSrAICYs0AAABLAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAgJjjwAAAAAAAAAA35UHgEst0l96g" + 13 | "XTDldD6W84qtk5mJxaaS4mAN7JfHA8AAAAAAxsSrAICYs0AAABMAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAABAAA" + 14 | "AAMB//22AAAAAAAAAADyHjqve6PjrxaK/3Rz9n8/NkfQOwpysl2J/d3rLr0+hwAAAANLZ9y4AelFCwAAABQAAAARAAAAAAAAAAAAAAAAAQA" + 15 | "AAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAECAmOPAAAAAAAAAADyHjqve6PjrxaK/3Rz9n" + 16 | "8/NkfQOwpysl2J/d3rLr0+hwAAAANLZ/0kAelFCwAAABQAAAARAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAA" + 17 | "AIAAAAJAAAAAAAAAAAAAAAAAAAAAAAAAAMCAmOPAAAAAAAAAADflQeASy3SX3qBdMOV0Ppbziq2TmYnFppLiYA3sl8cDwAAAAADGxKsAgJi" + 18 | "zQAAAEwAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAECAmOPAAAAAAAAAADflQeASy3SX3qBdMOV0Ppbziq2TmYnFppLiYA" + 19 | "3sl8cDwAAAAADGvJAAgJizQAAAEwAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA=") 20 | ok 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/AccountMergeOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.util.ByteArrays.base64 9 | import stellar.sdk.{ArbitraryInput, DomainMatchers, KeyPair} 10 | 11 | class AccountMergeOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 12 | 13 | implicit val arb: Arbitrary[Transacted[AccountMergeOperation]] = Arbitrary(genTransacted(genAccountMergeOperation)) 14 | implicit val formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 15 | 16 | "account merge operation" should { 17 | "serde via xdr string" >> prop { actual: AccountMergeOperation => 18 | Operation.decodeXdrString(actual.xdr.encode().base64()) must beEquivalentTo(actual) 19 | } 20 | 21 | "serde via xdr bytes" >> prop { actual: AccountMergeOperation => 22 | Operation.decodeXdr(actual.xdr) mustEqual actual 23 | } 24 | 25 | "parse from json" >> prop { op: Transacted[AccountMergeOperation] => 26 | val doc = 27 | s""" 28 | | { 29 | | "_links": { 30 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144"}, 31 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 32 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144/effects"}, 33 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659144"}, 34 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659144"} 35 | | }, 36 | | "id": "${op.id}", 37 | | "paging_token": "10157597659137", 38 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 39 | | "type_i": 8, 40 | | "type": "account_merge" 41 | | "created_at": "${formatter.format(op.createdAt)}", 42 | | "transaction_hash": "${op.txnHash}", 43 | | ${accountId(op.operation.sourceAccount.get, "account")} 44 | | ${accountId(op.operation.destination, "into")} 45 | |} 46 | """.stripMargin 47 | 48 | parse(doc).extract[Transacted[AccountMergeOperation]] mustEqual op 49 | }.setGen(genTransacted(genAccountMergeOperation.suchThat(_.sourceAccount.nonEmpty))) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/AllowTrustOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.native.JsonMethods.parse 4 | import org.json4s.native.Serialization 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class AllowTrustOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 11 | 12 | implicit val arb: Arbitrary[Transacted[AllowTrustOperation]] = Arbitrary(genTransacted(genAllowTrustOperation)) 13 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 14 | 15 | "allow trust operation" should { 16 | "serde via xdr string" >> prop { actual: AllowTrustOperation => 17 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 18 | } 19 | 20 | "serde via xdr bytes" >> prop { actual: AllowTrustOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "parse from json" >> prop { op: Transacted[AllowTrustOperation] => 25 | val doc = 26 | s""" 27 | | { 28 | | "_links": { 29 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144"}, 30 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 31 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144/effects"}, 32 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659144"}, 33 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659144"} 34 | | }, 35 | | "id": "${op.id}", 36 | | "paging_token": "10157597659137", 37 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 38 | | "type": "allow_trust", 39 | | "type_i": 7, 40 | | "created_at": "${formatter.format(op.createdAt)}", 41 | | "transaction_hash": "${op.txnHash}", 42 | | "asset_type": "${if (op.operation.assetCode.length <= 4) "credit_alphanum4" else "credit_alphanum12"}", 43 | | "asset_code": "${op.operation.assetCode}", 44 | | "asset_issuer": "${op.operation.sourceAccount.get.publicKey.accountId}" 45 | | "trustor": "${op.operation.trustor.accountId}", 46 | | ${accountId(op.operation.sourceAccount.get, "trustee")} 47 | | "authorize": ${op.operation.trustLineFlags.contains(TrustLineAuthorized)} 48 | | "authorize_to_maintain_liabilities": ${op.operation.trustLineFlags.contains(TrustLineCanMaintainLiabilities)} 49 | |} 50 | """.stripMargin 51 | 52 | val parsed = parse(doc).extract[Transacted[AllowTrustOperation]] 53 | parsed mustEqual op 54 | parsed.operation.authorize mustEqual op.operation.authorize 55 | parsed.operation.authorizeToMaintainLiabilities mustEqual op.operation.authorizeToMaintainLiabilities 56 | }.setGen(genTransacted(genAllowTrustOperation.suchThat(_.sourceAccount.nonEmpty))) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/BeginSponsoringFutureReservesOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.{Formats, NoTypeHints} 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.ArbitraryInput 9 | import stellar.sdk.util.ByteArrays 10 | 11 | class BeginSponsoringFutureReservesOperationSpec extends Specification with ArbitraryInput with JsonSnippets { 12 | 13 | implicit val arbOp: Arbitrary[BeginSponsoringFutureReservesOperation] = 14 | Arbitrary(genBeginSponsoringFutureReservesOperation) 15 | implicit val arbTx: Arbitrary[Transacted[BeginSponsoringFutureReservesOperation]] = 16 | Arbitrary(genTransacted(genBeginSponsoringFutureReservesOperation)) 17 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 18 | 19 | "begin sponsoring future reserves operation" should { 20 | "serde via xdr bytes" >> prop { actual: BeginSponsoringFutureReservesOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "serde via xdr string" >> prop { actual: BeginSponsoringFutureReservesOperation => 25 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 26 | } 27 | 28 | "parse from json" >> prop { op: Transacted[BeginSponsoringFutureReservesOperation] => 29 | val doc = 30 | s"""{ 31 | | "id": "${op.id}", 32 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 33 | | "type": "begin_sponsoring_future_reserves", 34 | | "type_i": 16, 35 | | "created_at": "${formatter.format(op.createdAt)}", 36 | | "transaction_hash": "${op.txnHash}", 37 | | "sponsored_id": "${op.operation.sponsored.publicKey.accountId}" 38 | |} 39 | """.stripMargin 40 | 41 | parse(doc).extract[Transacted[BeginSponsoringFutureReservesOperation]] mustEqual op 42 | }.setGen(genTransacted(genBeginSponsoringFutureReservesOperation.suchThat(_.sourceAccount.nonEmpty))) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/BumpSequenceOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.util.ByteArrays 9 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 10 | 11 | class BumpSequenceOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 12 | 13 | implicit val arb: Arbitrary[Transacted[BumpSequenceOperation]] = Arbitrary(genTransacted(genBumpSequenceOperation)) 14 | implicit val formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 15 | 16 | "bump sequence operation" should { 17 | "serde via xdr bytes" >> prop { actual: BumpSequenceOperation => 18 | Operation.decodeXdr(actual.xdr) mustEqual actual 19 | } 20 | 21 | "serde via xdr string" >> prop { actual: BumpSequenceOperation => 22 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 23 | } 24 | 25 | "parse from json" >> prop { op: Transacted[BumpSequenceOperation] => 26 | val doc = 27 | s""" 28 | | { 29 | | "_links": { 30 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144"}, 31 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 32 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144/effects"}, 33 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659144"}, 34 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659144"} 35 | | }, 36 | | "id": "${op.id}", 37 | | "paging_token": "10157597659137", 38 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 39 | | "type": "bump_sequence", 40 | | "type_i": 11, 41 | | "created_at": "${formatter.format(op.createdAt)}", 42 | | "transaction_hash": "${op.txnHash}", 43 | | "bump_to": ${op.operation.bumpTo} 44 | |} 45 | """.stripMargin 46 | 47 | parse(doc).extract[Transacted[BumpSequenceOperation]] mustEqual op 48 | }.setGen(genTransacted(genBumpSequenceOperation.suchThat(_.sourceAccount.nonEmpty))) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/ChangeTrustOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class ChangeTrustOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 11 | 12 | implicit val arb: Arbitrary[Transacted[ChangeTrustOperation]] = Arbitrary(genTransacted(genChangeTrustOperation)) 13 | implicit val formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 14 | 15 | "change trust operation" should { 16 | "serde via xdr string" >> prop { actual: ChangeTrustOperation => 17 | Operation.decodeXdrString(actual.xdr.encode().base64()) must beEquivalentTo(actual) 18 | } 19 | 20 | "serde via xdr bytes" >> prop { actual: ChangeTrustOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "parse from json" >> prop { op: Transacted[ChangeTrustOperation] => 25 | val doc = 26 | s""" 27 | | { 28 | | "_links": { 29 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144"}, 30 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 31 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144/effects"}, 32 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659144"}, 33 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659144"} 34 | | }, 35 | | "id": "${op.id}", 36 | | "paging_token": "10157597659137", 37 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 38 | | "type": "change_trust", 39 | | "type_i": 6, 40 | | "created_at": "${formatter.format(op.createdAt)}", 41 | | "transaction_hash": "${op.txnHash}", 42 | | ${amountDocPortion(op.operation.limit, "limit")}, 43 | | "trustee": "${op.operation.limit.asset.issuer.accountId}", 44 | | ${accountId(op.operation.sourceAccount.get, "trustor")} 45 | |} 46 | """.stripMargin 47 | 48 | parse(doc).extract[Transacted[ChangeTrustOperation]] mustEqual op 49 | }.setGen(genTransacted(genChangeTrustOperation.suchThat(_.sourceAccount.nonEmpty))) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/ClaimClaimableBalanceOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.native.JsonMethods.parse 4 | import org.json4s.native.Serialization 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.ArbitraryInput 9 | import stellar.sdk.util.ByteArrays 10 | 11 | class ClaimClaimableBalanceOperationSpec extends Specification with ArbitraryInput with JsonSnippets { 12 | 13 | implicit val arbOp: Arbitrary[ClaimClaimableBalanceOperation] = Arbitrary(genClaimClaimableBalanceOperation) 14 | implicit val arbTx: Arbitrary[Transacted[ClaimClaimableBalanceOperation]] = 15 | Arbitrary(genTransacted(genClaimClaimableBalanceOperation)) 16 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 17 | 18 | "create claimable balance operation" should { 19 | "serde via xdr bytes" >> prop { actual: ClaimClaimableBalanceOperation => 20 | Operation.decodeXdr(actual.xdr) mustEqual actual 21 | } 22 | 23 | "serde via xdr string" >> prop { actual: ClaimClaimableBalanceOperation => 24 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 25 | } 26 | 27 | "parse from json" >> prop { op: Transacted[ClaimClaimableBalanceOperation] => 28 | val doc = 29 | s""" 30 | |{ 31 | | "_links": { 32 | | "self": { 33 | | "href": "http://localhost:8000/operations/42949677057" 34 | | }, 35 | | "transaction": { 36 | | "href": "http://localhost:8000/transactions/25a461789570755483e3d3f078ae58ba5de2a083115c287e2ed14262107e0315" 37 | | }, 38 | | "effects": { 39 | | "href": "http://localhost:8000/operations/42949677057/effects" 40 | | }, 41 | | "succeeds": { 42 | | "href": "http://localhost:8000/effects?order=desc\u0026cursor=42949677057" 43 | | }, 44 | | "precedes": { 45 | | "href": "http://localhost:8000/effects?order=asc\u0026cursor=42949677057" 46 | | } 47 | | }, 48 | | "id": "${op.id}", 49 | | "transaction_successful": true, 50 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 51 | | "type": "claim_claimable_balance", 52 | | "type_i": 15, 53 | | "created_at": "${formatter.format(op.createdAt)}", 54 | | "transaction_hash": "${op.txnHash}", 55 | | "balance_id": "${op.operation.id.encodeString}", 56 | | ${accountId(op.operation.sourceAccount.get, "claimant")} 57 | |} 58 | """.stripMargin 59 | 60 | parse(doc).extract[Transacted[ClaimClaimableBalanceOperation]] mustEqual op 61 | }.setGen(genTransacted(genClaimClaimableBalanceOperation.suchThat(_.sourceAccount.nonEmpty))) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/CreateAccountOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.native.JsonMethods.parse 4 | import org.json4s.native.Serialization 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class CreateAccountOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 11 | 12 | implicit val arb: Arbitrary[Transacted[CreateAccountOperation]] = Arbitrary(genTransacted(genCreateAccountOperation)) 13 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer + OperationDeserializer 14 | 15 | "create account operation" should { 16 | "serde via xdr string" >> prop { actual: CreateAccountOperation => 17 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 18 | } 19 | 20 | "serde via xdr bytes" >> prop { actual: CreateAccountOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "be parsed from json " >> prop { op: Transacted[CreateAccountOperation] => 25 | val doc = 26 | s""" 27 | |{ 28 | | "_links": { 29 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659137"}, 30 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 31 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659137/effects"}, 32 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659137"}, 33 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659137"} 34 | | }, 35 | | "id": "${op.id}", 36 | | "paging_token": "10157597659137", 37 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 38 | | "type": "create_account", 39 | | "type_i": 0, 40 | | "created_at": "${formatter.format(op.createdAt)}", 41 | | "transaction_hash": "${op.txnHash}", 42 | | "starting_balance": "${amountString(op.operation.startingBalance)}", 43 | | ${accountId(op.operation.sourceAccount.get, "funder")} 44 | | "account": "${op.operation.destinationAccount.publicKey.accountId}" 45 | |} 46 | """.stripMargin 47 | 48 | parse(doc).extract[Transacted[CreateAccountOperation]] mustEqual removeDestinationSubAccountId(op) 49 | }.setGen(genTransacted(genCreateAccountOperation.suchThat(_.sourceAccount.nonEmpty))) 50 | } 51 | 52 | // Because sub accounts are not yet supported in Horizon JSON. 53 | private def removeDestinationSubAccountId(op: Transacted[CreateAccountOperation]): Transacted[CreateAccountOperation] = { 54 | op.copy(operation = op.operation.copy(destinationAccount = op.operation.destinationAccount.copy(subAccountId = None))) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/CreateClaimableBalanceOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.{Formats, NoTypeHints} 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.ArbitraryInput 9 | import stellar.sdk.model.ClaimantGenerators 10 | import stellar.sdk.util.ByteArrays 11 | 12 | class CreateClaimableBalanceOperationSpec extends Specification with ArbitraryInput with JsonSnippets { 13 | 14 | implicit val arbOp: Arbitrary[CreateClaimableBalanceOperation] = Arbitrary(genCreateClaimableBalanceOperation) 15 | implicit val arbTx: Arbitrary[Transacted[CreateClaimableBalanceOperation]] = 16 | Arbitrary(genTransacted(genCreateClaimableBalanceOperation)) 17 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 18 | 19 | "create claimable balance operation" should { 20 | "serde via xdr bytes" >> prop { actual: CreateClaimableBalanceOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "serde via xdr string" >> prop { actual: CreateClaimableBalanceOperation => 25 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 26 | } 27 | 28 | "parse from json" >> prop { op: Transacted[CreateClaimableBalanceOperation] => 29 | val doc = 30 | s""" 31 | |{ 32 | | "_links": { 33 | | "self": { 34 | | "href": "http://localhost:8000/operations/42949677057" 35 | | }, 36 | | "transaction": { 37 | | "href": "http://localhost:8000/transactions/25a461789570755483e3d3f078ae58ba5de2a083115c287e2ed14262107e0315" 38 | | }, 39 | | "effects": { 40 | | "href": "http://localhost:8000/operations/42949677057/effects" 41 | | }, 42 | | "succeeds": { 43 | | "href": "http://localhost:8000/effects?order=desc\u0026cursor=42949677057" 44 | | }, 45 | | "precedes": { 46 | | "href": "http://localhost:8000/effects?order=asc\u0026cursor=42949677057" 47 | | } 48 | | }, 49 | | "id": "${op.id}", 50 | | "paging_token": "42949677057", 51 | | "transaction_successful": true, 52 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 53 | | "type": "create_claimable_balance", 54 | | "type_i": 14, 55 | | "created_at": "${formatter.format(op.createdAt)}", 56 | | "transaction_hash": "${op.txnHash}", 57 | | "sponsor": "${op.operation.sourceAccount.get.publicKey.accountId}", 58 | | "asset": "${op.operation.amount.asset.canoncialString}", 59 | | "amount": "${op.operation.amount.toDisplayUnits}", 60 | | "claimants": [${op.operation.claimants.map(ClaimantGenerators.json).mkString(",")}] 61 | |} 62 | """.stripMargin 63 | 64 | parse(doc).extract[Transacted[CreateClaimableBalanceOperation]] mustEqual op 65 | }.setGen(genTransacted(genCreateClaimableBalanceOperation.suchThat(_.sourceAccount.nonEmpty))) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/CreatePassiveSellOfferOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.util.ByteArrays.base64 9 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 10 | 11 | class CreatePassiveSellOfferOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 12 | 13 | implicit val arb: Arbitrary[Transacted[CreatePassiveSellOfferOperation]] = Arbitrary(genTransacted(genCreatePassiveSellOfferOperation)) 14 | implicit val formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer + OperationDeserializer 15 | 16 | "create passive offer operation" should { 17 | "serde via xdr string" >> prop { actual: CreatePassiveSellOfferOperation => 18 | Operation.decodeXdrString(actual.xdr.encode().base64()) must beEquivalentTo(actual) 19 | } 20 | 21 | "serde via xdr bytes" >> prop { actual: CreatePassiveSellOfferOperation => 22 | Operation.decodeXdr(actual.xdr) mustEqual actual 23 | } 24 | 25 | "parse from json" >> prop { op: Transacted[CreatePassiveSellOfferOperation] => 26 | val doc = 27 | s""" 28 | |{ 29 | | "_links": { 30 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659137"}, 31 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 32 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659137/effects"}, 33 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659137"}, 34 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659137"} 35 | | }, 36 | | "id": "${op.id}", 37 | | "paging_token": "10157597659137", 38 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 39 | | "type": "create_passive_sell_offer", 40 | | "type_i": 4, 41 | | "created_at": "${formatter.format(op.createdAt)}", 42 | | "transaction_hash": "${op.txnHash}", 43 | | ${amountDocPortion(op.operation.selling, assetPrefix = "selling_")}, 44 | | ${asset(op.operation.buying, "buying_")}, 45 | | "offer_id": 0, 46 | | "price": "1.0", 47 | | "price_r": { 48 | | "d": ${op.operation.price.d}, 49 | | "n": ${op.operation.price.n} 50 | | } 51 | |} 52 | """.stripMargin 53 | 54 | parse(doc).extract[Transacted[CreatePassiveSellOfferOperation]] mustEqual op 55 | }.setGen(genTransacted(genCreatePassiveSellOfferOperation.suchThat(_.sourceAccount.nonEmpty))) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/EndSponsoringReservesOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.{Formats, NoTypeHints} 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.ArbitraryInput 9 | import stellar.sdk.util.ByteArrays 10 | 11 | class EndSponsoringReservesOperationSpec extends Specification with ArbitraryInput with JsonSnippets { 12 | 13 | implicit val arbOp: Arbitrary[EndSponsoringFutureReservesOperation] = 14 | Arbitrary(genEndSponsoringFutureReservesOperation) 15 | implicit val arbTx: Arbitrary[Transacted[EndSponsoringFutureReservesOperation]] = 16 | Arbitrary(genTransacted(genEndSponsoringFutureReservesOperation)) 17 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 18 | 19 | "End sponsoring future reserves operation" should { 20 | "serde via xdr bytes" >> prop { actual: EndSponsoringFutureReservesOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "serde via xdr string" >> prop { actual: EndSponsoringFutureReservesOperation => 25 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 26 | } 27 | 28 | "parse from json" >> prop { op: Transacted[EndSponsoringFutureReservesOperation] => 29 | val doc = 30 | s"""{ 31 | | "id": "${op.id}", 32 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 33 | | "type": "end_sponsoring_future_reserves", 34 | | "type_i": 17, 35 | | "created_at": "${formatter.format(op.createdAt)}", 36 | | "transaction_hash": "${op.txnHash}", 37 | | ${accountId(op.operation.sourceAccount.get, "begin_sponsor") /* TODO - what field is this? */} 38 | | "sponsored_id": "IGNORED" 39 | |}""".stripMargin 40 | 41 | parse(doc).extract[Transacted[EndSponsoringFutureReservesOperation]] mustEqual op 42 | }.setGen(genTransacted(genEndSponsoringFutureReservesOperation.suchThat(_.sourceAccount.nonEmpty))) 43 | } 44 | 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/InflationOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.util.ByteArrays.base64 9 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 10 | 11 | class InflationOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 12 | 13 | implicit val arb: Arbitrary[Transacted[InflationOperation]] = Arbitrary(genTransacted(genInflationOperation)) 14 | implicit val formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 15 | 16 | "the inflation operation" should { 17 | "serde via xdr string" >> prop { actual: InflationOperation => 18 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 19 | } 20 | 21 | "serde via xdr bytes" >> prop { actual: InflationOperation => 22 | Operation.decodeXdr(actual.xdr) mustEqual actual 23 | } 24 | 25 | "parse from json" >> prop { op: Transacted[InflationOperation] => 26 | val doc = 27 | s""" 28 | | { 29 | | "_links": { 30 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144"}, 31 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 32 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144/effects"}, 33 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659144"}, 34 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659144"} 35 | | }, 36 | | "id": "${op.id}", 37 | | "paging_token": "10157597659137", 38 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 39 | | "type": "inflation", 40 | | "type_i": 9, 41 | | "created_at": "${formatter.format(op.createdAt)}", 42 | | "transaction_hash": "${op.txnHash}", 43 | |} 44 | """.stripMargin 45 | 46 | parse(doc).extract[Transacted[InflationOperation]] mustEqual op 47 | }.setGen(genTransacted(genInflationOperation.suchThat(_.sourceAccount.nonEmpty))) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/JsonSnippets.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import stellar.sdk.model.{AccountId, Amount, Asset, NonNativeAsset} 4 | 5 | import java.time.format.DateTimeFormatter 6 | import java.util.Locale 7 | 8 | trait JsonSnippets { 9 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") 10 | 11 | def amountString(a: Amount): String = "%.7f".formatLocal(Locale.ROOT, a.units / math.pow(10, 7)) 12 | 13 | def amountDocPortion(amount: Amount, label: String = "amount", assetPrefix: String = ""): String = { 14 | s""" 15 | |"$label":${amountString(amount)} 16 | |${asset(amount.asset, assetPrefix)} 17 | """.stripMargin 18 | } 19 | 20 | def asset(a: Asset, assetPrefix: String = ""): String = a match { 21 | case nn: NonNativeAsset => 22 | s""" 23 | |"${assetPrefix}asset_type": "${nn.typeString}", 24 | |"${assetPrefix}asset_code": "${nn.code}", 25 | |"${assetPrefix}asset_issuer": "${nn.issuer.accountId}" 26 | """.stripMargin.trim 27 | 28 | case _ => 29 | s""" 30 | |"${assetPrefix}asset_type": "native" 31 | """.stripMargin.trim 32 | } 33 | 34 | def opt(key: String, value: Option[Any]) = value.map { 35 | case v: String => s""""$key":"$v",""" 36 | case v: Set[_] => s""""$key":[${ 37 | v.map { 38 | case s: String => s""""$s"""" 39 | case a => a 40 | }.mkString(",") 41 | }],""" 42 | case v => s""""$key":$v,""" 43 | }.getOrElse("") 44 | 45 | def accountId(accountId: AccountId, prefix: String): String = 46 | s""" 47 | |"$prefix": "${accountId.publicKey.accountId}", 48 | |${accountId.subAccountId.map(id => s""""${prefix}_muxed_id": "${id}",""").getOrElse("")} 49 | |""".stripMargin.trim 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/PathPaymentStrictReceiveOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.native.JsonMethods.parse 4 | import org.json4s.native.Serialization 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class PathPaymentStrictReceiveOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 11 | 12 | implicit val arb: Arbitrary[Transacted[PathPaymentStrictReceiveOperation]] = Arbitrary(genTransacted(genPathPaymentStrictReceiveOperation)) 13 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 14 | 15 | "path payment operation" should { 16 | "serde via xdr string" >> prop { actual: PathPaymentStrictReceiveOperation => 17 | Operation.decodeXdrString(actual.xdr.encode().base64()) must beEquivalentTo(actual) 18 | } 19 | 20 | "serde via xdr bytes" >> prop { actual: PathPaymentStrictReceiveOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "parse from json" >> prop { op: Transacted[PathPaymentStrictReceiveOperation] => 25 | val doc = 26 | s""" 27 | |{ 28 | | "_links":{ 29 | | "self":{"href":"https://horizon-testnet.stellar.org/operations/940258535411713"}, 30 | | "transaction":{"href":"https://horizon-testnet.stellar.org/transactions/a995af17837d1b53fb5782269250a36e9dbe74170260b46f2708e5f23f7c864a"}, 31 | | "effects":{"href":"https://horizon-testnet.stellar.org/operations/940258535411713/effects"}, 32 | | "succeeds":{"href":"https://horizon-testnet.stellar.org/effects?order=desc&cursor=940258535411713"}, 33 | | "precedes":{"href":"https://horizon-testnet.stellar.org/effects?order=asc&cursor=940258535411713"} 34 | | }, 35 | | "id": "${op.id}", 36 | | "paging_token": "10157597659137", 37 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 38 | | "type":"path_payment", 39 | | "type_i":2, 40 | | "created_at": "${formatter.format(op.createdAt)}", 41 | | "transaction_hash": "${op.txnHash}", 42 | | ${amountDocPortion(op.operation.destinationAmount)} 43 | | ${amountDocPortion(op.operation.sendMax, "source_max", "source_")} 44 | | ${accountId(op.operation.sourceAccount.get, "from")} 45 | | ${accountId(op.operation.destinationAccount, "to")} 46 | | "path":[${if (op.operation.path.isEmpty) "" else op.operation.path.map(asset(_)).mkString("{", "},{", "}")}] 47 | |} 48 | """.stripMargin 49 | 50 | parse(doc).extract[Transacted[Operation]] mustEqual op 51 | }.setGen(genTransacted(genPathPaymentStrictReceiveOperation.suchThat(_.sourceAccount.nonEmpty))) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/PathPaymentStrictSendOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.native.JsonMethods.parse 4 | import org.json4s.native.Serialization 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class PathPaymentStrictSendOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 11 | 12 | implicit val arb: Arbitrary[Transacted[PathPaymentStrictSendOperation]] = Arbitrary(genTransacted(genPathPaymentStrictSendOperation)) 13 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 14 | 15 | "path payment operation" should { 16 | "serde via xdr string" >> prop { actual: PathPaymentStrictSendOperation => 17 | Operation.decodeXdrString(actual.xdr.encode().base64()) must beEquivalentTo(actual) 18 | } 19 | 20 | "serde via xdr bytes" >> prop { actual: PathPaymentStrictSendOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "parse from json" >> prop { op: Transacted[PathPaymentStrictSendOperation] => 25 | val doc = 26 | s""" 27 | |{ 28 | | "_links":{ 29 | | "self":{"href":"https://horizon-testnet.stellar.org/operations/940258535411713"}, 30 | | "transaction":{"href":"https://horizon-testnet.stellar.org/transactions/a995af17837d1b53fb5782269250a36e9dbe74170260b46f2708e5f23f7c864a"}, 31 | | "effects":{"href":"https://horizon-testnet.stellar.org/operations/940258535411713/effects"}, 32 | | "succeeds":{"href":"https://horizon-testnet.stellar.org/effects?order=desc&cursor=940258535411713"}, 33 | | "precedes":{"href":"https://horizon-testnet.stellar.org/effects?order=asc&cursor=940258535411713"} 34 | | }, 35 | | "id": "${op.id}", 36 | | "paging_token": "10157597659137", 37 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 38 | | "type":"path_payment_strict_send", 39 | | "type_i":13, 40 | | "created_at": "${formatter.format(op.createdAt)}", 41 | | "transaction_hash": "${op.txnHash}", 42 | | ${amountDocPortion(op.operation.sendAmount, assetPrefix = "source_")} 43 | | ${amountDocPortion(op.operation.destinationMin, "destination_min")} 44 | | ${accountId(op.operation.sourceAccount.get, "from")} 45 | | ${accountId(op.operation.destinationAccount, "to")} 46 | | "path":[${if (op.operation.path.isEmpty) "" else op.operation.path.map(asset(_)).mkString("{", "},{", "}")}] 47 | |} 48 | """.stripMargin 49 | 50 | parse(doc).extract[Transacted[Operation]] mustEqual op 51 | }.setGen(genTransacted(genPathPaymentStrictSendOperation.suchThat(_.sourceAccount.nonEmpty))) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/PaymentOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.json4s.native.JsonMethods.parse 4 | import org.json4s.native.Serialization 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.scalacheck.Arbitrary 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 9 | 10 | class PaymentOperationSpec extends Specification with ArbitraryInput with DomainMatchers with JsonSnippets { 11 | 12 | implicit val arb: Arbitrary[Transacted[PaymentOperation]] = Arbitrary(genTransacted(genPaymentOperation)) 13 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + TransactedOperationDeserializer 14 | 15 | "payment operation" should { 16 | "serde via xdr string" >> prop { actual: PaymentOperation => 17 | Operation.decodeXdrString(actual.xdr.encode().base64()) must beEquivalentTo(actual) 18 | } 19 | 20 | "serde via xdr bytes" >> prop { actual: PaymentOperation => 21 | Operation.decodeXdr(actual.xdr) mustEqual actual 22 | } 23 | 24 | "parse from json" >> prop { op: Transacted[PaymentOperation] => 25 | val doc = 26 | s""" 27 | | { 28 | | "_links": { 29 | | "self": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144"}, 30 | | "transaction": {"href": "https://horizon-testnet.stellar.org/transactions/17a670bc424ff5ce3b386dbfaae9990b66a2a37b4fbe51547e8794962a3f9e6a"}, 31 | | "effects": {"href": "https://horizon-testnet.stellar.org/operations/10157597659144/effects"}, 32 | | "succeeds": {"href": "https://horizon-testnet.stellar.org/effects?order=desc\u0026cursor=10157597659144"}, 33 | | "precedes": {"href": "https://horizon-testnet.stellar.org/effects?order=asc\u0026cursor=10157597659144"} 34 | | }, 35 | | "id": "${op.id}", 36 | | "paging_token": "10157597659137", 37 | | ${accountId(op.operation.sourceAccount.get, "source_account")} 38 | | ${accountId(op.operation.sourceAccount.get, "from")} 39 | | ${accountId(op.operation.destinationAccount, "to")} 40 | | "type": "payment", 41 | | "type_i": 1, 42 | | "created_at": "${formatter.format(op.createdAt)}", 43 | | "transaction_hash": "${op.txnHash}", 44 | | ${amountDocPortion(op.operation.amount)} 45 | |} 46 | """.stripMargin 47 | 48 | parse(doc).extract[Transacted[PaymentOperation]] mustEqual op 49 | }.setGen(genTransacted(genPaymentOperation.suchThat(_.sourceAccount.nonEmpty))) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/op/RevokeSponsorshipOperationSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.op 2 | 3 | import org.scalacheck.Arbitrary 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.ArbitraryInput 6 | 7 | class RevokeSponsorshipOperationSpec extends Specification with ArbitraryInput { 8 | 9 | implicit val arbOp: Arbitrary[RevokeSponsorshipOperation] = 10 | Arbitrary(genRevokeSponsorshipOperation) 11 | 12 | "revoke sponsorship operation" should { 13 | "serde via xdr bytes" >> prop { actual: RevokeSponsorshipOperation => 14 | Operation.decodeXdr(actual.xdr) mustEqual actual 15 | } 16 | 17 | "serde via xdr string" >> prop { actual: RevokeSponsorshipOperation => 18 | Operation.decodeXdrString(actual.xdr.encode().base64()) mustEqual actual 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/FeeStatsResponseSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.specs2.mutable.Specification 7 | import stellar.sdk.ArbitraryInput 8 | 9 | class FeeStatsResponseSpec extends Specification with ArbitraryInput { 10 | 11 | implicit val formats = Serialization.formats(NoTypeHints) + FeeStatsRespDeserializer 12 | 13 | "a fee stats response document" should { 14 | "parse to a fee stats response" >> prop { r: FeeStatsResponse => 15 | val json = 16 | s""" 17 | |{ 18 | | "last_ledger": "${r.lastLedger}", 19 | | "last_ledger_base_fee": "${r.lastLedgerBaseFee.units}", 20 | | "ledger_capacity_usage": "${r.ledgerCapacityUsage}", 21 | | "fee_charged": { 22 | | "max": "${r.chargedFees.max.units}", 23 | | "min": "${r.chargedFees.min.units}", 24 | | "mode": "${r.chargedFees.mode.units}", 25 | | "p10": "${r.chargedFees.percentiles(10).units}", 26 | | "p20": "${r.chargedFees.percentiles(20).units}", 27 | | "p30": "${r.chargedFees.percentiles(30).units}", 28 | | "p40": "${r.chargedFees.percentiles(40).units}", 29 | | "p50": "${r.chargedFees.percentiles(50).units}", 30 | | "p60": "${r.chargedFees.percentiles(60).units}", 31 | | "p70": "${r.chargedFees.percentiles(70).units}", 32 | | "p80": "${r.chargedFees.percentiles(80).units}", 33 | | "p90": "${r.chargedFees.percentiles(90).units}", 34 | | "p95": "${r.chargedFees.percentiles(95).units}", 35 | | "p99": "${r.chargedFees.percentiles(99).units}" 36 | | }, 37 | | "max_fee": { 38 | | "max": "${r.maxFees.max.units}", 39 | | "min": "${r.maxFees.min.units}", 40 | | "mode": "${r.maxFees.mode.units}", 41 | | "p10": "${r.maxFees.percentiles(10).units}", 42 | | "p20": "${r.maxFees.percentiles(20).units}", 43 | | "p30": "${r.maxFees.percentiles(30).units}", 44 | | "p40": "${r.maxFees.percentiles(40).units}", 45 | | "p50": "${r.maxFees.percentiles(50).units}", 46 | | "p60": "${r.maxFees.percentiles(60).units}", 47 | | "p70": "${r.maxFees.percentiles(70).units}", 48 | | "p80": "${r.maxFees.percentiles(80).units}", 49 | | "p90": "${r.maxFees.percentiles(90).units}", 50 | | "p95": "${r.maxFees.percentiles(95).units}", 51 | | "p99": "${r.maxFees.percentiles(99).units}" 52 | | } 53 | |} 54 | """.stripMargin 55 | 56 | val actual = parse(json).extract[FeeStatsResponse] 57 | actual mustEqual r 58 | actual.acceptedFeePercentiles mustEqual actual.chargedFees.percentiles 59 | actual.minAcceptedFee mustEqual actual.chargedFees.min 60 | actual.modeAcceptedFee mustEqual actual.chargedFees.mode 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/LedgerResponseSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import java.time.ZoneId 4 | import java.time.format.DateTimeFormatter 5 | 6 | import org.json4s.NoTypeHints 7 | import org.json4s.native.JsonMethods.parse 8 | import org.json4s.native.Serialization 9 | import org.specs2.mutable.Specification 10 | import stellar.sdk.ArbitraryInput 11 | 12 | class LedgerResponseSpec extends Specification with ArbitraryInput { 13 | 14 | val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC")) 15 | 16 | implicit val formats = Serialization.formats(NoTypeHints) + LedgerRespDeserializer 17 | 18 | "a ledger response document" should { 19 | "parse to a ledger response" >> prop { lr: LedgerResponse => 20 | 21 | val json = 22 | s""" 23 | |{ 24 | | "_links": { 25 | | "self": { 26 | | "href": "http://horizon-testnet.stellar.org/ledgers/11" 27 | | }, 28 | | "transactions": { 29 | | "href": "http://horizon-testnet.stellar.org/ledgers/11/transactions{?cursor,limit,order}", 30 | | "templated": true 31 | | }, 32 | | "operations": { 33 | | "href": "http://horizon-testnet.stellar.org/ledgers/11/operations{?cursor,limit,order}", 34 | | "templated": true 35 | | }, 36 | | "payments": { 37 | | "href": "http://horizon-testnet.stellar.org/ledgers/11/payments{?cursor,limit,order}", 38 | | "templated": true 39 | | }, 40 | | "effects": { 41 | | "href": "http://horizon-testnet.stellar.org/ledgers/11/effects{?cursor,limit,order}", 42 | | "templated": true 43 | | } 44 | | }, 45 | | "id": "${lr.id}", 46 | | "paging_token": "47244640256", 47 | | "hash": "${lr.hash}", 48 | | ${lr.previousHash.map(h => s""""prev_hash": "$h",""").getOrElse("")} 49 | | "sequence": ${lr.sequence}, 50 | | "successful_transaction_count": ${lr.successTransactionCount}, 51 | | "failed_transaction_count": ${lr.failureTransactionCount}, 52 | | "operation_count": ${lr.operationCount}, 53 | | "closed_at": "${formatter.format(lr.closedAt)}", 54 | | "total_coins": "${lr.totalCoins.toDisplayUnits}", 55 | | "fee_pool": "${lr.feePool.toDisplayUnits}", 56 | | "base_fee_in_stroops": ${lr.baseFee.units}, 57 | | "base_reserve_in_stroops": ${lr.baseReserve.units}, 58 | | "max_tx_set_size": ${lr.maxTxSetSize}, 59 | | "protocol_version": 4 60 | |} 61 | """.stripMargin 62 | 63 | parse(json).extract[LedgerResponse] must beLike { case actual: LedgerResponse => 64 | actual.copy(closedAt = lr.closedAt) mustEqual lr 65 | actual.closedAt.toInstant.toEpochMilli mustEqual lr.closedAt.toInstant.toEpochMilli 66 | } 67 | } 68 | 69 | "calculate transaction count as sum of failed and successful transactions" >> prop { lr: LedgerResponse => 70 | lr.transactionCount mustEqual lr.failureTransactionCount + lr.successTransactionCount 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/OfferResponseSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import java.time.ZoneId 4 | import java.time.format.DateTimeFormatter 5 | import java.util.Locale 6 | 7 | import org.json4s.NoTypeHints 8 | import org.json4s.native.JsonMethods.parse 9 | import org.json4s.native.Serialization 10 | import org.specs2.mutable.Specification 11 | import stellar.sdk.model.{Amount, Asset, NonNativeAsset} 12 | import stellar.sdk.ArbitraryInput 13 | 14 | class OfferResponseSpec extends Specification with ArbitraryInput { 15 | 16 | implicit val formats = Serialization.formats(NoTypeHints) + OfferRespDeserializer 17 | private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'").withZone(ZoneId.of("UTC")) 18 | 19 | "an offer response document" should { 20 | "parse to an offer response" >> prop { or: OfferResponse => 21 | val json = 22 | s""" 23 | |{ 24 | | "_links": { 25 | | "self": { 26 | | "href": "https://horizon-testnet.stellar.org/offers/101542" 27 | | }, 28 | | "offer_maker": { 29 | | "href": "https://horizon-testnet.stellar.org/accounts/GCXYKQF35XWATRB6AWDDV2Y322IFU2ACYYN5M2YB44IBWAIITQ4RYPXK" 30 | | } 31 | | }, 32 | | "id": ${or.id}, 33 | | "paging_token": "101542", 34 | | "seller": "${or.seller.accountId}", 35 | | "selling": { 36 | | ${assetJson(or.selling.asset)} 37 | | }, 38 | | "buying": { 39 | | ${assetJson(or.buying)} 40 | | }, 41 | | "amount": "${amountString(or.selling)}", 42 | | "price_r": { 43 | | "n": ${or.price.n}, 44 | | "d": ${or.price.d} 45 | | }, 46 | | "price": "3.0300000", 47 | | ${or.sponsor.map(s => s""""sponsor": "${s.accountId}",""").getOrElse("")} 48 | | "last_modified_ledger": ${or.lastModifiedLedger}, 49 | | "last_modified_time": "${formatter.format(or.lastModifiedTime)}" 50 | |} 51 | | 52 | """.stripMargin 53 | 54 | parse(json).extract[OfferResponse] mustEqual or 55 | } 56 | } 57 | 58 | def assetJson(asset: Asset) = asset match { 59 | case nn: NonNativeAsset => 60 | s""" 61 | |"asset_type": "${nn.typeString}", 62 | |"asset_code": "${nn.code}", 63 | |"asset_issuer": "${nn.issuer.accountId}" 64 | """.stripMargin.trim 65 | 66 | case _ => """"asset_type": "native"""" 67 | } 68 | 69 | def amountString(a: Amount): String = "%.7f".formatLocal(Locale.ROOT, a.units / math.pow(10, 7)) 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/OrderBookSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.specs2.mutable.Specification 7 | import stellar.sdk._ 8 | import stellar.sdk.model.op.JsonSnippets 9 | import stellar.sdk.model.{Order, OrderBook, OrderBookDeserializer} 10 | 11 | class OrderBookSpec extends Specification with ArbitraryInput with JsonSnippets { 12 | 13 | implicit val formats = Serialization.formats(NoTypeHints) + OrderBookDeserializer 14 | 15 | "order book" should { 16 | "parse from json" >> prop { ob: OrderBook => 17 | val doc = 18 | s""" 19 | |{ 20 | | "bids": [${ob.bids.map(order).mkString(",")}], 21 | | "asks": [${ob.asks.map(order).mkString(",")}], 22 | | "base": {${asset(ob.selling)}} 23 | | "counter": {${asset(ob.buying)}} 24 | |} 25 | """.stripMargin 26 | 27 | parse(doc).extract[OrderBook] mustEqual ob 28 | } 29 | } 30 | 31 | private def order(o: Order) = 32 | s"""{ 33 | | "price_r": { 34 | | "n": ${o.price.n}, 35 | | "d": ${o.price.d} 36 | | }, 37 | | "price": "${o.price.asDecimalString}", 38 | | "amount": "${o.quantity / math.pow(10, 7)}" 39 | |} 40 | """.stripMargin 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/TradeEffectResponseSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods.parse 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Gen 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk._ 9 | import stellar.sdk.model.op.JsonSnippets 10 | import stellar.sdk.model.{AccountId, Amount, NonNativeAsset} 11 | 12 | import java.time.ZonedDateTime 13 | 14 | class TradeEffectResponseSpec extends Specification with ArbitraryInput with JsonSnippets { 15 | 16 | implicit val formats = Serialization.formats(NoTypeHints) + EffectResponseDeserializer 17 | 18 | "a trade effect document" should { 19 | "parse to a trade effect" >> prop { 20 | (id: String, created: ZonedDateTime, offerId: Long, buyer: AccountId, bought: Amount, seller: AccountId, sold: Amount) => 21 | val json = doc(id, created, offerId, buyer, bought, seller, sold) 22 | parse(json).extract[EffectResponse] mustEqual EffectTrade(id, created, offerId, buyer, bought, seller, sold) 23 | }.setGen1(Gen.identifier).setGen3(Gen.posNum[Long]) 24 | } 25 | 26 | def doc(id: String, created: ZonedDateTime, offerId: Long, buyer: AccountId, bought: Amount, seller: AccountId, sold: Amount) = { 27 | s""" { 28 | "id": "$id", 29 | "paging_token": "31161168848490497-2", 30 | "account": "${buyer.publicKey.accountId}", 31 | ${buyer.subAccountId.map(id => s""""account_muxed_id": "$id",""").getOrElse("")} 32 | "type": "trade", 33 | "type_i": 33, 34 | "created_at": "${formatter.format(created)}", 35 | "seller": "${seller.publicKey.accountId}", 36 | ${seller.subAccountId.map(id => s""""seller_muxed_id": "$id",""").getOrElse("")} 37 | "offer_id": $offerId, 38 | ${amountDocPortion(sold, sold = true)}, 39 | ${amountDocPortion(bought, sold = false)} 40 | }""" 41 | } 42 | 43 | def amountDocPortion(amount: Amount, sold: Boolean): String = { 44 | val bs = if (sold) "sold" else "bought" 45 | amount.asset match { 46 | case nn: NonNativeAsset => 47 | s""""${bs}_amount": "${amountString(amount)}", 48 | |"${bs}_asset_type": "${nn.typeString}", 49 | |"${bs}_asset_code": "${nn.code}", 50 | |"${bs}_asset_issuer": "${nn.issuer.accountId}" 51 | """.stripMargin.trim 52 | 53 | case _ => 54 | s""""${bs}_amount": "${amountString(amount)}", 55 | |"${bs}_asset_type": "native" 56 | """.stripMargin.trim 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/TrustLineAuthEffectResponseSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import org.json4s.{Formats, NoTypeHints} 6 | import org.json4s.native.JsonMethods._ 7 | import org.json4s.native.Serialization 8 | import org.scalacheck.Gen 9 | import org.specs2.mutable.Specification 10 | import stellar.sdk._ 11 | import stellar.sdk.model.NonNativeAsset 12 | import stellar.sdk.model.op.JsonSnippets 13 | 14 | class TrustLineAuthEffectResponseSpec extends Specification with ArbitraryInput with JsonSnippets { 15 | 16 | implicit val formats: Formats = Serialization.formats(NoTypeHints) + EffectResponseDeserializer 17 | 18 | "an authorize trustline effect document" should { 19 | "parse to an authorize trustline effect" >> prop { (id: String, created: ZonedDateTime, accn: KeyPair, asset: NonNativeAsset) => 20 | val json = doc(id, created, "trustline_authorized", accn, asset, 0.0) 21 | parse(json).extract[EffectResponse] mustEqual EffectTrustLineAuthorized(id, created, accn.asPublicKey, asset) 22 | }.setGen1(Gen.identifier) 23 | } 24 | 25 | "an authorize to maintain liabilities effect document" should { 26 | "parse to an authorize to maintain liabilities effect" >> prop { (id: String, created: ZonedDateTime, accn: KeyPair, asset: NonNativeAsset) => 27 | val json = doc(id, created, "trustline_authorized_to_maintain_liabilities", accn, asset, 0.0) 28 | parse(json).extract[EffectResponse] mustEqual EffectTrustLineAuthorizedToMaintainLiabilities(id, created, accn.asPublicKey, asset) 29 | }.setGen1(Gen.identifier) 30 | } 31 | 32 | "a deauthorize trustline effect document" should { 33 | "parse to a deauthorize trustline effect" >> prop { (id: String, created: ZonedDateTime, accn: KeyPair, asset: NonNativeAsset) => 34 | val json = doc(id, created, "trustline_deauthorized", accn, asset, 0.0) 35 | parse(json).extract[EffectResponse] mustEqual EffectTrustLineDeauthorized(id, created, accn.asPublicKey, asset) 36 | }.setGen1(Gen.identifier) 37 | } 38 | 39 | def doc(id: String, created: ZonedDateTime, tpe: String, accn: PublicKeyOps, asset: NonNativeAsset, limit: Double) = { 40 | s""" 41 | |{ 42 | | "id": "$id", 43 | | "paging_token": "10157597659144-2", 44 | | "account": "${asset.issuer.accountId}", 45 | | "created_at": "${formatter.format(created)}", 46 | | "type": "$tpe", 47 | | "type_i": 23, 48 | | "asset_type": "${asset.typeString}", 49 | | "asset_code": "${asset.code}", 50 | | "trustor": "${accn.accountId}" 51 | |} 52 | """.stripMargin 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/response/TrustLineEffectResponseSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.response 2 | 3 | import org.json4s.NoTypeHints 4 | import org.json4s.native.JsonMethods._ 5 | import org.json4s.native.Serialization 6 | import org.scalacheck.Gen 7 | import org.specs2.mutable.Specification 8 | import stellar.sdk._ 9 | import stellar.sdk.model.op.JsonSnippets 10 | import stellar.sdk.model.{AccountId, IssuedAmount, NonNativeAsset} 11 | 12 | import java.time.ZonedDateTime 13 | 14 | class TrustLineEffectResponseSpec extends Specification with ArbitraryInput with JsonSnippets { 15 | 16 | implicit val formats = Serialization.formats(NoTypeHints) + EffectResponseDeserializer 17 | 18 | "a trustline created effect document" should { 19 | "parse to a trustline created effect" >> prop { 20 | (id: String, created: ZonedDateTime, accn: AccountId, asset: NonNativeAsset, limit: Long) => 21 | val json = doc(id, created, "trustline_created", accn, asset, limit) 22 | parse(json).extract[EffectResponse] mustEqual EffectTrustLineCreated(id, created, accn, IssuedAmount(limit, asset)) 23 | }.setGen1(Gen.identifier).setGen5(Gen.posNum[Long]) 24 | } 25 | 26 | "a trustline updated effect document" should { 27 | "parse to a trustline updated effect" >> prop { 28 | (id: String, created: ZonedDateTime, accn: AccountId, asset: NonNativeAsset, limit: Long) => 29 | val json = doc(id, created, "trustline_updated", accn, asset, limit) 30 | parse(json).extract[EffectResponse] mustEqual EffectTrustLineUpdated(id, created, accn, IssuedAmount(limit, asset)) 31 | }.setGen1(Gen.identifier).setGen5(Gen.posNum[Long]) 32 | } 33 | 34 | "a trustline removed effect document" should { 35 | "parse to a trustline removed effect" >> prop { (id: String, created: ZonedDateTime, accn: AccountId, asset: NonNativeAsset) => 36 | val json = doc(id, created, "trustline_removed", accn, asset, 0) 37 | parse(json).extract[EffectResponse] mustEqual EffectTrustLineRemoved(id, created, accn, asset) 38 | }.setGen1(Gen.identifier) 39 | } 40 | 41 | def doc(id: String, created: ZonedDateTime, tpe: String, accnId: AccountId, asset: NonNativeAsset, limit: Long) = { 42 | s""" 43 | |{ 44 | | "id": "$id", 45 | | "paging_token": "10157597659144-2", 46 | | "account": "${accnId.publicKey.accountId}", 47 | | ${accnId.subAccountId.map(id => s""""account_muxed_id": "$id",""").getOrElse("")} 48 | | "type": "$tpe", 49 | | "type_i": 20, 50 | | "created_at": "${formatter.format(created)}", 51 | | "asset_type": "${asset.typeString}", 52 | | "asset_code": "${asset.code}", 53 | | "asset_issuer": "${asset.issuer.accountId}", 54 | | "limit": "${limit / math.pow(10, 7)}" 55 | |} 56 | """.stripMargin 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/result/OperationResultSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.specs2.mutable.Specification 4 | import stellar.sdk.{ArbitraryInput, DomainMatchers} 5 | 6 | import org.stellar.xdr.OperationResult.OperationResultTr 7 | import org.stellar.xdr.{OperationResultCode, OperationResult => XOperationResult} 8 | 9 | class OperationResultSpec extends Specification with ArbitraryInput with DomainMatchers { 10 | "operation results" should { 11 | "serde via xdr bytes" >> prop { or: OperationResult => 12 | OperationResult.decodeXdr(or.xdr) mustEqual or 13 | }.set(minTestsOk = 2500) 14 | } 15 | } -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/result/TransactionApprovedSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import okio.ByteString 4 | import org.specs2.mutable.Specification 5 | import stellar.sdk.model.response.TransactionApproved 6 | import stellar.sdk.util.ByteArrays 7 | import stellar.sdk.ArbitraryInput 8 | import stellar.sdk.model.NativeAmount 9 | 10 | class TransactionApprovedSpec extends Specification with ArbitraryInput { 11 | 12 | "an approved transaction result" should { 13 | "provide direct access to the fee charged" >> { 14 | val resultXDR = TransactionSuccess(NativeAmount(982346), Seq(PaymentSuccess), ByteString.EMPTY).xdr.encode().base64() 15 | TransactionApproved("", 1, "", resultXDR, "").feeCharged mustEqual NativeAmount(982346) 16 | } 17 | 18 | "provide direct access to the operation results" >> prop { opResults: List[OperationResult] => 19 | val xdr = TransactionSuccess(NativeAmount(100), opResults.take(20), ByteString.EMPTY).xdr 20 | val resultXDR = xdr.encode().base64() 21 | 22 | TransactionApproved("", 1, "", resultXDR, "").operationResults mustEqual opResults.take(20) 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/model/result/TransactionRejectedSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.model.result 2 | 3 | import org.specs2.mutable.Specification 4 | import stellar.sdk.ArbitraryInput 5 | import stellar.sdk.model.NativeAmount 6 | import stellar.sdk.model.response.TransactionRejected 7 | 8 | class TransactionRejectedSpec extends Specification with ArbitraryInput { 9 | 10 | "an approved transaction result" should { 11 | "provide direct access to the fee charged" >> prop { result: TransactionNotSuccessful => 12 | val resultXDR = result.xdr.encode().base64() 13 | TransactionRejected(400, "", "", Nil, "", resultXDR).feeCharged mustEqual result.feeCharged 14 | } 15 | 16 | "indicate whether the sequence was updated based upon presence of fee" >> prop { result: TransactionNotSuccessful => 17 | val xdr = result.xdr 18 | val resultXDR = xdr.encode().base64() 19 | val rejection = TransactionRejected(400, "", "", Nil, "", resultXDR) 20 | rejection.sequenceIncremented mustEqual result.feeCharged != NativeAmount(0) 21 | } 22 | } 23 | 24 | "failure" should { 25 | "decode any result XDR" >> { 26 | TransactionRejected(1, "", "", Nil, "", "AAAAAAAAAGT////9AAAAAA==").result must not(beNull) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/util/ByteArraysSpec.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.util 2 | 3 | import java.math.BigInteger 4 | 5 | import kotlin.text.Charsets 6 | import okio.ByteString 7 | import org.scalacheck.Gen 8 | import org.specs2.mutable.Specification 9 | import stellar.sdk.ArbitraryInput 10 | import stellar.sdk.util.ByteArrays._ 11 | 12 | import scala.util.Try 13 | 14 | class ByteArraysSpec extends Specification with ArbitraryInput { 15 | 16 | "padding a byte array" should { 17 | "do nothing when required length is the array length" >> prop { bs: Array[Byte] => 18 | paddedByteArray(bs, bs.length).toSeq mustEqual bs.toSeq 19 | } 20 | 21 | "do nothing when required length is less than the array length" >> prop { bs: Array[Byte] => 22 | paddedByteArray(bs, bs.length - 1).toSeq mustEqual bs.toSeq 23 | }.setGen(Gen.nonEmptyListOf(Gen.posNum[Byte]).map(_.toArray)) 24 | 25 | "pad with zeros when required length is greater than the array length" >> prop { (bs: Array[Byte], plus: Byte) => 26 | paddedByteArray(bs, bs.length + plus.toInt).toSeq mustEqual bs.toSeq ++ (1 to plus).map(_ => 0) 27 | }.setGen2(Gen.posNum[Byte]) 28 | } 29 | 30 | "trimming a byte array" should { 31 | "remove trailing zeros" >> { 32 | trimmedByteArray(LazyList()) must beEmpty 33 | trimmedByteArray("hello".getBytes("UTF-8")) mustEqual "hello".getBytes("UTF-8").toSeq 34 | trimmedByteArray("hello\u0000\u0000".getBytes("UTF-8")) mustEqual "hello".getBytes("UTF-8").toSeq 35 | trimmedByteArray("hello\u0000there".getBytes("UTF-8")) mustEqual "hello\u0000there".getBytes("UTF-8").toSeq 36 | } 37 | } 38 | 39 | "sha256" should { 40 | "hash correctly" >> { 41 | val hash = sha256("今日は世界".getBytes("UTF-8")) 42 | new BigInteger(1, hash).toString(16).toUpperCase mustEqual 43 | "72C2CC3C678D77939435E5AE0A0EF2B83D6A42AFB221EA15CD736CB122B23989" 44 | } 45 | "hash anything" >> prop { bs: Array[Byte] => 46 | Try(sha256(bs)) must beSuccessfulTry[Array[Byte]] 47 | } 48 | } 49 | 50 | "base64" should { 51 | "perform round trip to string" >> prop { bs: Array[Byte] => 52 | base64(base64(bs)).toSeq mustEqual bs.toSeq 53 | } 54 | } 55 | 56 | "bytestrings" should { 57 | import ByteArrays.Implicits._ 58 | "implicitly create from byte arrays" >> prop { bs: Array[Byte] => 59 | val byteString: ByteString = bs 60 | byteString mustEqual new ByteString(bs) 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/scala/stellar/sdk/util/FakeClock.scala: -------------------------------------------------------------------------------- 1 | package stellar.sdk.util 2 | 3 | import java.time.{Clock, Instant, ZoneId, ZonedDateTime} 4 | 5 | import scala.concurrent.duration._ 6 | 7 | case class FakeClock( 8 | zoneId: ZoneId = ZoneId.of("UTC") 9 | ) extends Clock { 10 | 11 | private var fixedInstant: Instant = ZonedDateTime.of(2020, 8, 15, 0, 0, 0, 0, zoneId).toInstant 12 | 13 | override def getZone: ZoneId = zoneId 14 | 15 | override def withZone(zoneId: ZoneId): Clock = this.copy(zoneId = zoneId) 16 | 17 | override def instant(): Instant = fixedInstant 18 | 19 | def advance(duration: Duration): Unit = fixedInstant = fixedInstant.plus(java.time.Duration.ofNanos(duration.toNanos)) 20 | } 21 | --------------------------------------------------------------------------------