diff --git a/apps/flipcash/features/onramp/src/main/kotlin/com/flipcash/app/onramp/internal/OnRampViewModel.kt b/apps/flipcash/features/onramp/src/main/kotlin/com/flipcash/app/onramp/internal/OnRampViewModel.kt index 6d3d18863..d3132ad3f 100644 --- a/apps/flipcash/features/onramp/src/main/kotlin/com/flipcash/app/onramp/internal/OnRampViewModel.kt +++ b/apps/flipcash/features/onramp/src/main/kotlin/com/flipcash/app/onramp/internal/OnRampViewModel.kt @@ -176,7 +176,6 @@ internal class OnRampViewModel @Inject constructor( dispatchEvent(Event.UpdateOrderLookupState()) } is CoinbaseOnRampState.Paying -> dispatchEvent(Event.UpdateOrderLookupState(loading = true)) - is CoinbaseOnRampState.Processing -> dispatchEvent(Event.UpdateOrderLookupState(loading = true)) } } .launchIn(viewModelScope) diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampController.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampController.kt index a6095cad2..bfe128b0d 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampController.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampController.kt @@ -25,8 +25,6 @@ import com.getcode.opencode.model.financial.Token import com.getcode.opencode.model.financial.usdf import com.getcode.opencode.model.transactions.SwapFundingSource import com.getcode.solana.keys.base58 -import com.getcode.utils.network.pollUntil -import com.getcode.vendor.Base58 import com.flipcash.app.core.AppRoute import com.flipcash.app.onramp.internal.CoinbaseOnRampWebError import com.getcode.utils.CodeServerError @@ -46,9 +44,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonIgnoreUnknownKeys import retrofit2.HttpException -import java.security.SecureRandom import javax.inject.Inject -import kotlin.time.Duration.Companion.seconds typealias OrderWithPaymentLink = Pair @@ -79,15 +75,15 @@ class CoinbaseOnRampController @Inject constructor( _pendingNavigation.tryEmit(route) } - fun startPayment(order: OnrampOrder, token: Token, amount: VerifiedFiat) { - _state.value = CoinbaseOnRampState.Paying(order, token, amount) + fun startPayment(order: OnrampOrder, token: Token, amount: VerifiedFiat, swapId: SwapId) { + _state.value = CoinbaseOnRampState.Paying(order, token, amount, swapId) } fun onPaymentSuccess(orderId: String) { val current = _state.value if (current is CoinbaseOnRampState.Paying) { _state.update { - CoinbaseOnRampState.Processing(orderId, current.token, current.amount) + CoinbaseOnRampState.Completed(current.swapId, current.token, current.amount) } } } @@ -118,56 +114,20 @@ class CoinbaseOnRampController @Inject constructor( } return placeOrderInclusiveOfFees(amount) - .map { (orderId, paymentLink) -> - val order = OnrampOrder(orderId, paymentLink.url) - startPayment(order, token, verifiedFiat) - } - } - - suspend fun processPayment(): Result { - val current = _state.value - if (current !is CoinbaseOnRampState.Processing) { - return Result.failure(IllegalStateException("Not in Processing state")) - } - - return pollUntil( - call = { lookupOrder(current.orderId).getOrThrow() }, - required = { order -> order.txHash != null }, - maxAttempts = 100, - interval = 3.seconds, - tag = "CoinbaseOrderPoller", - ).mapCatching { order -> - order.txHash ?: throw IllegalStateException("No hash provided from provider") - }.mapCatching { txHash -> + .mapCatching { (orderId, paymentLink) -> val owner = userManager.accountCluster ?: throw IllegalStateException("No account cluster") - transactionController.buy( + val swapId = transactionController.buy( owner = owner, - amount = current.amount, - of = current.token, - source = SwapFundingSource.ExternalWallet( - transactionSignature = runCatching { Base58.decode(txHash) } - .getOrElse { ByteArray(64).also { SecureRandom().nextBytes(it) } } - .toList() - ), + amount = verifiedFiat, + of = token, + source = SwapFundingSource.CoinbaseOnramp(orderId = orderId), fund = { Result.success(Unit) } ).getOrThrow() - } - .onSuccess { swapId -> - _state.update { CoinbaseOnRampState.Completed(swapId, current.token, current.amount) } - } - .onFailure { error -> - trace( - message = "Payment processing failed", - tag = "OnRamp", - metadata = { - "orderId" to current.orderId - "errorType" to error::class.simpleName.orEmpty() - }, - error = error, - ) - reset() + + val order = OnrampOrder(orderId, paymentLink.url) + startPayment(order, token, verifiedFiat, swapId) } } diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt index 1cdb10fd5..3649590cb 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampHandler.kt @@ -37,19 +37,6 @@ fun CoinbaseOnRampHandler( ) } - is CoinbaseOnRampState.Processing -> { - LaunchedEffect(current) { - delay(400) // let the system payment sheet finish its dismiss animation - controller.processPayment() - .onFailure { - BottomBarManager.showError( - title = "Something Went Wrong", - message = "Failed to complete purchase. Please try again", - ) - } - } - } - is CoinbaseOnRampState.Completed -> { LaunchedEffect(current) { controller.emitPendingNavigation( diff --git a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampState.kt b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampState.kt index 3911e094f..1335aa5bd 100644 --- a/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampState.kt +++ b/apps/flipcash/shared/onramp/coinbase/src/main/kotlin/com/flipcash/app/onramp/CoinbaseOnRampState.kt @@ -16,8 +16,7 @@ data class OnrampOrder( sealed interface CoinbaseOnRampState { data object Idle : CoinbaseOnRampState - data class Paying(val order: OnrampOrder, val token: Token, val amount: VerifiedFiat) : CoinbaseOnRampState - data class Processing(val orderId: String, val token: Token, val amount: VerifiedFiat) : CoinbaseOnRampState + data class Paying(val order: OnrampOrder, val token: Token, val amount: VerifiedFiat, val swapId: SwapId) : CoinbaseOnRampState data class Completed(val swapId: SwapId, val token: Token, val amount: VerifiedFiat) : CoinbaseOnRampState data class Failed(val error: CoinbaseOnRampWebError) : CoinbaseOnRampState } diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/extensions/ProtobufToLocal.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/extensions/ProtobufToLocal.kt index faf6479a4..40c049169 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/extensions/ProtobufToLocal.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/internal/network/extensions/ProtobufToLocal.kt @@ -202,7 +202,7 @@ internal fun TransactionService.StatefulSwapRequest.Initiate.ReserveSwapClientPa TransactionService.FundingSource.FUNDING_SOURCE_SUBMIT_INTENT -> SwapFundingSource.SubmitIntent(PublicKey(fundingId).bytes) TransactionService.FundingSource.FUNDING_SOURCE_EXTERNAL_WALLET -> SwapFundingSource.ExternalWallet( fundingId.toByteArray().toList()) - TransactionService.FundingSource.FUNDING_SOURCE_COINBASE_ONRAMP -> SwapFundingSource.CoinbaseOnramp(fundingId.toByteArray().toList()) + TransactionService.FundingSource.FUNDING_SOURCE_COINBASE_ONRAMP -> SwapFundingSource.CoinbaseOnramp(fundingId) TransactionService.FundingSource.UNRECOGNIZED -> SwapFundingSource.Unknown } ) @@ -220,7 +220,7 @@ internal fun TransactionService.StatefulSwapRequest.Initiate.CoinbaseStableSwapp TransactionService.FundingSource.FUNDING_SOURCE_SUBMIT_INTENT -> SwapFundingSource.SubmitIntent(PublicKey(fundingId).bytes) TransactionService.FundingSource.FUNDING_SOURCE_EXTERNAL_WALLET -> SwapFundingSource.ExternalWallet( fundingId.toByteArray().toList()) - TransactionService.FundingSource.FUNDING_SOURCE_COINBASE_ONRAMP -> SwapFundingSource.CoinbaseOnramp(fundingId.toByteArray().toList()) + TransactionService.FundingSource.FUNDING_SOURCE_COINBASE_ONRAMP -> SwapFundingSource.CoinbaseOnramp(fundingId) TransactionService.FundingSource.UNRECOGNIZED -> SwapFundingSource.Unknown }, destinationOwner = destinationOwner.toPublicKey(), diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapFundingSource.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapFundingSource.kt index e356d2279..98aed3e78 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapFundingSource.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapFundingSource.kt @@ -25,7 +25,5 @@ sealed class SwapFundingSource { * Represents a funding source where the user pays via a Coinbase onramp. * @param orderId The Coinbase onramp order ID. */ - data class CoinbaseOnramp(val orderId: List): SwapFundingSource() { - constructor(orderId: String): this(orderId.toByteArray().toList()) - } + data class CoinbaseOnramp(val orderId: String): SwapFundingSource() } \ No newline at end of file diff --git a/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapRequest.kt b/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapRequest.kt index 38fdc0c9d..56e171f6a 100644 --- a/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapRequest.kt +++ b/services/opencode/src/main/kotlin/com/getcode/opencode/model/transactions/SwapRequest.kt @@ -18,10 +18,11 @@ data class SwapRequest( val swapId: SwapId, val verifiedState: VerifiedState, ) { - val fundingIntentId = when (kind) { - is SwapStartKind.Reserve -> kind.fundingIntentId - is SwapStartKind.Stablecoin -> kind.fundingIntentId - } + val fundingIntentId: List + get() = when (kind) { + is SwapStartKind.Reserve -> kind.fundingIntentId + is SwapStartKind.Stablecoin -> kind.fundingIntentId + } val totalTransferAmount: LocalFiat get() { @@ -52,7 +53,9 @@ sealed interface SwapStartKind { get() = when (fundingSource) { is SwapFundingSource.ExternalWallet -> fundingSource.transactionSignature is SwapFundingSource.SubmitIntent -> fundingSource.id - is SwapFundingSource.CoinbaseOnramp -> fundingSource.orderId + is SwapFundingSource.CoinbaseOnramp -> throw IllegalArgumentException( + "Coinbase onramp does not use a funding intent" + ) SwapFundingSource.Unknown -> throw IllegalArgumentException("Invalid funding source") } }