diff --git a/.github/workflows/sub_gradle_test_and_build.yml b/.github/workflows/sub_gradle_test_and_build.yml index 68f431ab4e..2f4dd06cd4 100644 --- a/.github/workflows/sub_gradle_test_and_build.yml +++ b/.github/workflows/sub_gradle_test_and_build.yml @@ -9,6 +9,10 @@ jobs: gradle_test_and_build: name: Gradle Test and Build runs-on: ubuntu-22.04 + # write to PR permission is required for jacocoTestReport Action to update comment + permissions: + contents: read + pull-requests: write steps: # Checkout the code - uses: actions/checkout@v3 @@ -52,7 +56,18 @@ jobs: - name: Gradle test and build. (unit tests, integration tests, end-2-end tests and build) env: run_docker: false - run: ./gradlew clean build --no-daemon --stacktrace -x spotlessApply -x spotlessKotlinApply -x javadoc -x javadocJar -x sourcesJar + run: ./gradlew clean build jacocoTestReport --no-daemon --stacktrace -x spotlessApply -x spotlessKotlinApply -x javadoc -x javadocJar -x sourcesJar + + - name: Add coverage to PR + id: jacoco + uses: madrapps/jacoco-report@v1.6.1 + with: + paths: ${{ github.workspace }}/**/build/reports/jacoco/test/jacocoTestReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 + min-coverage-changed-files: 60 + title: Code Coverage + update-comment: true - name: Stop docker containers env: diff --git a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java index f2ba4fcc2e..cb861284a6 100644 --- a/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java +++ b/api-schema/src/main/java/org/stellar/anchor/api/platform/PlatformTransactionData.java @@ -77,6 +77,12 @@ public class PlatformTransactionData { @SerializedName("memo_type") String memoType; + @SerializedName("refund_memo") + String refundMemo; + + @SerializedName("refund_memo_type") + String refundMemoType; + Customers customers; StellarId creator; diff --git a/build.gradle.kts b/build.gradle.kts index 0feec12260..6e656f2a67 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { java alias(libs.plugins.spotless) alias(libs.plugins.kotlin.jvm) apply false + jacoco } tasks { @@ -25,12 +26,13 @@ tasks { subprojects { apply(plugin = "java") apply(plugin = "com.diffplug.spotless") + apply(plugin = "jacoco") repositories { mavenLocal() mavenCentral() maven { url = uri("https://packages.confluent.io/maven") } - maven { url = uri("https://reposdeitory.mulesoft.org/nexus/content/repositories/public/") } + maven { url = uri("https://repository.mulesoft.org/nexus/content/repositories/public/") } maven { url = uri("https://jitpack.io") } } @@ -62,6 +64,15 @@ subprojects { } kotlin { ktfmt("0.42").googleStyle() } + + tasks.jacocoTestReport { + dependsOn(tasks.test) // tests are required to run before generating the report + reports { + xml.required.set(true) + csv.required.set(false) + html.required.set(true) + } + } } dependencies { @@ -159,7 +170,7 @@ subprojects { allprojects { group = "org.stellar.anchor-sdk" - version = "2.2.0" + version = "2.2.1" tasks.jar { manifest { @@ -169,3 +180,7 @@ allprojects { } } } + +tasks.register("printVersionName") { + println(rootProject.version.toString()) +} \ No newline at end of file diff --git a/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java b/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java index 5f1a1a50f1..00d644fe2e 100644 --- a/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java +++ b/core/src/main/java/org/stellar/anchor/util/TransactionHelper.java @@ -122,6 +122,8 @@ public static GetTransactionResponse toGetTransactionResponse( .externalTransactionId(txn.getExternalTransactionId()) .memo(txn.getMemo()) .memoType(txn.getMemoType()) + .refundMemo(txn.getRefundMemo()) + .refundMemoType(txn.getRefundMemoType()) .build(); } diff --git a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt index da4a84a433..1609020941 100644 --- a/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt +++ b/core/src/test/kotlin/org/stellar/anchor/sep1/Sep1ServiceTest.kt @@ -2,11 +2,10 @@ package org.stellar.anchor.sep1 -import io.mockk.MockKAnnotations -import io.mockk.every +import io.mockk.* import io.mockk.impl.annotations.MockK -import io.mockk.mockkStatic import java.nio.file.Files +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -25,6 +24,12 @@ internal class Sep1ServiceTest { MockKAnnotations.init(this, relaxUnitFun = true) } + @AfterEach + fun tearDown() { + clearAllMocks() + unmockkAll() + } + @Test fun `disabled Sep1Service should return null string as toml value`() { every { sep1Config.isEnabled } returns false diff --git a/docs/README.md b/docs/README.md index b7a9e3b4dd..7d3885aebe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ [![License](https://badgen.net/badge/license/Apache%202/blue?icon=github&label=License)](https://github.com/stellar/java-stellar-anchor-sdk/blob/develop/LICENSE) [![GitHub Version](https://badgen.net/github/release/stellar/java-stellar-anchor-sdk?icon=github&label=Latest%20release)](https://github.com/stellar/java-stellar-anchor-sdk/releases) -[![Docker](https://badgen.net/badge/Latest%20Release/v2.2.0/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=release-2.2.0) +[![Docker](https://badgen.net/badge/Latest%20Release/v2.2.1/blue?icon=docker)](https://hub.docker.com/r/stellar/anchor-platform/tags?page=1&name=2.2.1) ![Develop Branch](https://github.com/stellar/java-stellar-anchor-sdk/actions/workflows/wk_push_to_develop.yml/badge.svg?branch=develop)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2daa9db1a5..e1113ed4ed 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,6 +61,7 @@ toml4j = "0.7.2" spotless = "6.9.1" spring-boot = "2.7.9" spring-dependency-management = "1.1.0" +jacoco = "0.8.10" [libraries] abdera = { module = "org.apache.abdera:abdera-i18n", version.ref = "abdera" } @@ -158,4 +159,5 @@ spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" } spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } -ktor = { id = "io.ktor.plugin", version.ref = "ktor" } \ No newline at end of file +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } +jacoco = { id = "jacoco", version.ref = "jacoco" } \ No newline at end of file diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java index e08f8435f5..a03a8f88c9 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionRepo.java @@ -19,7 +19,8 @@ public interface JdbcSep24TransactionRepo JdbcSep24Transaction findOneByStellarTransactionId(String stellarTransactionId); - JdbcSep24Transaction findOneByToAccountAndMemo(String accountId, String memo); + JdbcSep24Transaction findOneByToAccountAndMemoAndStatus( + String toAccount, String memo, String status); List findBySep10AccountAndRequestAssetCodeOrderByStartedAtDesc( String stellarAccount, String assetCode); diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java index 1a14679b3a..d7da0abca3 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep24TransactionStore.java @@ -53,9 +53,10 @@ public Sep24Transaction findByExternalTransactionId(String externalTransactionId return txnRepo.findOneByExternalTransactionId(externalTransactionId); } - public JdbcSep24Transaction findByToAccountAndMemo(String toAccount, String memo) { + public JdbcSep24Transaction findOneByToAccountAndMemoAndStatus( + String toAccount, String memo, String status) { Optional optTxn = - Optional.ofNullable(txnRepo.findOneByToAccountAndMemo(toAccount, memo)); + Optional.ofNullable(txnRepo.findOneByToAccountAndMemoAndStatus(toAccount, memo, status)); return optTxn.orElse(null); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionRepo.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionRepo.java index 60c4da9a5f..8bc674c24b 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionRepo.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionRepo.java @@ -24,7 +24,8 @@ public interface JdbcSep31TransactionRepo @Query(value = "SELECT COUNT(t) FROM JdbcSep31Transaction t WHERE t.status = :status") Integer findByStatusCount(@Param("status") String status); - Optional findByStellarAccountIdAndStellarMemo( - @Param("stellar_account_id") String stellarAccountId, - @Param("stellar_memo") String stellarMemo); + Optional findByStellarAccountIdAndStellarMemoAndStatus( + @Param("stellar_account_id") String accountId, + @Param("stellar_memo") String memo, + String status); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java index 1bc190518c..7c03e75456 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java +++ b/platform/src/main/java/org/stellar/anchor/platform/data/JdbcSep31TransactionStore.java @@ -71,9 +71,10 @@ public Sep31Transaction findByStellarAccountId(String accountId) { return optTxn.orElse(null); } - public JdbcSep31Transaction findByStellarAccountIdAndMemo(String accountId, String memo) { + public JdbcSep31Transaction findByStellarAccountIdAndMemoAndStatus( + String accountId, String memo, String status) { Optional optTxn = - transactionRepo.findByStellarAccountIdAndStellarMemo(accountId, memo); + transactionRepo.findByStellarAccountIdAndStellarMemoAndStatus(accountId, memo, status); return optTxn.orElse(null); } diff --git a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java index d33ba2e6fa..63357eaf57 100644 --- a/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java +++ b/platform/src/main/java/org/stellar/anchor/platform/service/PaymentOperationToEventListener.java @@ -76,7 +76,13 @@ public void onReceived(ObservedPayment payment) throws IOException { // Find a transaction matching the memo, assumes transactions are unique to account+memo JdbcSep31Transaction sep31Txn = null; try { +<<<<<<< HEAD sep31Txn = sep31TransactionStore.findByStellarAccountIdAndMemo(payment.getTo(), memo); +======= + sep31Txn = + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + payment.getTo(), memo, SepTransactionStatus.PENDING_SENDER.toString()); +>>>>>>> sdf/develop } catch (Exception ex) { errorEx(ex); } @@ -94,7 +100,13 @@ public void onReceived(ObservedPayment payment) throws IOException { // Find a transaction matching the memo, assumes transactions are unique to account+memo JdbcSep24Transaction sep24Txn; try { +<<<<<<< HEAD sep24Txn = sep24TransactionStore.findByToAccountAndMemo(payment.getTo(), memo); +======= + sep24Txn = + sep24TransactionStore.findOneByToAccountAndMemoAndStatus( + payment.getTo(), memo, SepTransactionStatus.PENDING_USR_TRANSFER_START.toString()); +>>>>>>> sdf/develop } catch (Exception ex) { errorEx(ex); return; diff --git a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt index f9dcd5bcf1..e40fd9f01b 100644 --- a/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt +++ b/platform/src/test/kotlin/org/stellar/anchor/platform/service/PaymentOperationToEventListenerTest.kt @@ -84,44 +84,51 @@ class PaymentOperationToEventListenerTest { p.transactionHash = "1ad62e48724426be96cf2cdb65d5dacb8fac2e403e50bedb717bfc8eaf05af30" p.transactionMemo = "my_memo_2" p.assetType = "credit_alphanum4" - p.to = "GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5" - p.amount = "1" - p.assetName = "FOO:GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" + p.sourceAccount = "GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5" + p.to = "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364" var slotMemo = slot() val slotAccount = slot() + val slotStatus = slot() every { - sep31TransactionStore.findByStellarAccountIdAndMemo(capture(slotAccount), capture(slotMemo)) + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + capture(slotAccount), + capture(slotMemo), + capture(slotStatus) + ) } returns null - val sep24Txn = JdbcSep24Transaction() - sep24Txn.amountIn = "1" - every { - sep24TransactionStore.findByToAccountAndMemo(capture(slotAccount), capture(slotMemo)) - } returns sep24Txn paymentOperationToEventListener.onReceived(p) verify(exactly = 1) { - sep31TransactionStore.findByStellarAccountIdAndMemo( - "GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5", - "my_memo_2" + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", + "my_memo_2", + "pending_sender" ) } assertEquals("my_memo_2", slotMemo.captured) - assertEquals("GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5", slotAccount.captured) + assertEquals("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", slotAccount.captured) + assertEquals("pending_sender", slotStatus.captured) - // If findByStellarAccountIdAndMemo throws an exception, we shouldn't trigger an event + // If findByStellarAccountIdAndMemoAndStatus throws an exception, we shouldn't trigger an event slotMemo = slot() p.transactionMemo = "my_memo_3" every { - sep31TransactionStore.findByStellarAccountIdAndMemo(capture(slotAccount), capture(slotMemo)) + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + capture(slotAccount), + capture(slotMemo), + capture(slotStatus) + ) } throws SepException("Something went wrong") paymentOperationToEventListener.onReceived(p) verify(exactly = 1) { - sep31TransactionStore.findByStellarAccountIdAndMemo( - "GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5", - "my_memo_3" + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", + "my_memo_3", + "pending_sender" ) } assertEquals("my_memo_3", slotMemo.captured) - assertEquals("GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5", slotAccount.captured) + assertEquals("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", slotAccount.captured) + assertEquals("pending_sender", slotStatus.captured) // If asset code from the fetched tx is different, don't trigger event slotMemo = slot() @@ -131,17 +138,23 @@ class PaymentOperationToEventListenerTest { sep31TxMock.amountInAsset = "BAR" sep31TxMock.amountIn = "1" every { - sep31TransactionStore.findByStellarAccountIdAndMemo(capture(slotAccount), capture(slotMemo)) + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + capture(slotAccount), + capture(slotMemo), + capture(slotStatus) + ) } returns sep31TxMock paymentOperationToEventListener.onReceived(p) verify(exactly = 1) { - sep31TransactionStore.findByStellarAccountIdAndMemo( - "GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5", - "my_memo_4" + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", + "my_memo_4", + "pending_sender" ) } assertEquals("my_memo_4", slotMemo.captured) - assertEquals("GBT7YF22QEVUDUTBUIS2OWLTZMP7Z4J4ON6DCSHR3JXYTZRKCPXVV5J5", slotAccount.captured) + assertEquals("GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", slotAccount.captured) + assertEquals("pending_sender", slotStatus.captured) } @ParameterizedTest @@ -192,6 +205,7 @@ class PaymentOperationToEventListenerTest { val slotAccountId = slot() val slotMemo = slot() + val slotStatus = slot() val sep31TxMock = JdbcSep31Transaction() sep31TxMock.id = "ceaa7677-a5a7-434e-b02a-8e0801b3e7bd" sep31TxMock.amountExpected = "10" @@ -217,7 +231,11 @@ class PaymentOperationToEventListenerTest { val sep31TxCopy = gson.fromJson(gson.toJson(sep31TxMock), JdbcSep31Transaction::class.java) every { - sep31TransactionStore.findByStellarAccountIdAndMemo(capture(slotAccountId), capture(slotMemo)) + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( + capture(slotAccountId), + capture(slotMemo), + capture(slotStatus) + ) } returns sep31TxCopy val txnIdCapture = slot() @@ -237,9 +255,10 @@ class PaymentOperationToEventListenerTest { paymentOperationToEventListener.onReceived(p) verify(exactly = 1) { - sep31TransactionStore.findByStellarAccountIdAndMemo( + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", - "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=" + "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=", + "pending_sender" ) } @@ -282,6 +301,7 @@ class PaymentOperationToEventListenerTest { val receiverId = "137938d4-43a7-4252-a452-842adcee474c" val slotMemo = slot() + val slotStatus = slot() val sep31TxMock = JdbcSep31Transaction() sep31TxMock.id = "ceaa7677-a5a7-434e-b02a-8e0801b3e7bd" sep31TxMock.amountExpected = "10" @@ -307,9 +327,10 @@ class PaymentOperationToEventListenerTest { val sep31TxCopy = gson.fromJson(gson.toJson(sep31TxMock), JdbcSep31Transaction::class.java) every { - sep31TransactionStore.findByStellarAccountIdAndMemo( + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", - capture(slotMemo) + capture(slotMemo), + capture(slotStatus) ) } returns sep31TxCopy @@ -330,9 +351,10 @@ class PaymentOperationToEventListenerTest { paymentOperationToEventListener.onReceived(p) verify(exactly = 1) { - sep31TransactionStore.findByStellarAccountIdAndMemo( + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus( "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", - "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=" + "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=", + "pending_sender" ) } @@ -381,6 +403,7 @@ class PaymentOperationToEventListenerTest { .build() val slotMemo = slot() + val slotStatus = slot() val sep24TxMock = JdbcSep24Transaction() sep24TxMock.id = "ceaa7677-a5a7-434e-b02a-8e0801b3e7bd" sep24TxMock.requestAssetCode = assetCode @@ -391,13 +414,16 @@ class PaymentOperationToEventListenerTest { sep24TxMock.kind = PlatformTransactionData.Kind.WITHDRAWAL.kind // TODO: this shouldn't be necessary - every { sep31TransactionStore.findByStellarAccountIdAndMemo(any(), any()) } returns null + every { + sep31TransactionStore.findByStellarAccountIdAndMemoAndStatus(any(), any(), any()) + } returns null val sep24TxnCopy = gson.fromJson(gson.toJson(sep24TxMock), JdbcSep24Transaction::class.java) every { - sep24TransactionStore.findByToAccountAndMemo( + sep24TransactionStore.findOneByToAccountAndMemoAndStatus( "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", - capture(slotMemo) + capture(slotMemo), + capture(slotStatus) ) } returns sep24TxnCopy @@ -418,9 +444,10 @@ class PaymentOperationToEventListenerTest { paymentOperationToEventListener.onReceived(p) verify(exactly = 1) { - sep24TransactionStore.findByToAccountAndMemo( + sep24TransactionStore.findOneByToAccountAndMemoAndStatus( "GBZ4HPSEHKEEJ6MOZBSVV2B3LE27EZLV6LJY55G47V7BGBODWUXQM364", - "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=" + "OWI3OGYwZmEtOTNmOS00MTk4LThkOTMtZTc2ZmQwODQ=", + "pending_user_transfer_start" ) }