Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Withdraw flow issues on wallets that enable cross-border payments #395

Closed
accordeiro opened this issue Sep 6, 2019 · 16 comments · Fixed by #404
Closed

Withdraw flow issues on wallets that enable cross-border payments #395

accordeiro opened this issue Sep 6, 2019 · 16 comments · Fixed by #404

Comments

@accordeiro
Copy link
Member

accordeiro commented Sep 6, 2019

This issue describes a use case for Wallets that support cross-border payments, and a few issues that have been encountered while trying to lay out the correct withdrawal flow.

Use case

  • User holds a balance in their Stellar account of some USD token, let's call it USDZ
  • User wants to withdraw that balance in their native currency, say Brazilian Real (BRL). The anchor that issues BRL tokens calls those tokens BRLZ

User / Wallet / Anchor interaction flow

In order to accomplish that use case, we could use the following flow:

  1. User indicates to Wallet that it wants to withdraw a part of its USDZ balance in their native currency
  2. Wallet shows how many USDZ it would cost for the user to withdraw a certain amount of BRL (the Wallet can use the Stellar Ticker API to calculate the conversion rate). In this example, let's say at the given time the Wallet informs the user that they would have to use 100 USDZ of their balance to withdraw 400 BRL.
  3. With the given to-withdraw amount in hands (400 BRL), the Wallet hits the BRLZ anchor's /withdraw endpoint, passing amount=400 as a parameter.
  4. As we're currently recommending to anchors, the response for the /withdraw request would be a 403 (Forbidden) with a URL pointing to the interactive flow
  5. Wallet opens the interactive URL, and the User continues with the KYC / withdrawal process through the BRLZ Anchor's UI, informing their bank account info, etc.
  6. Once the process is completed, the Anchor provides a Stellar address for the Wallet to make the BRLZ transfer (via postMessage) before it fulfills the external deposit
  7. The Wallet trades (maybe via a path payment to itself) 100 USDZ to BRLZ, then sends the newly acquired BRLZ to the given Stellar address, and polls the /transaction to get status updates about the withdrawal, until the transaction's status is marked as complete.

Problems

There are a few issues with this approach.

P1: Double input of amount

The user has to input a BRL amount early in the process, in step 2. However, as we're suggesting anchors to collect most info through the interactive flow, that user will likely be presented with an "amount" input again in the interactive flow. Even if the amount is pre-populated, giving the user the ability to change that amount might lead to an inconsistent state in the later steps.

Suggested solution

One solution for that is to provide some kind of amount_readonly=true parameter to the interactive popup URL, and either skip the amount input or display it as read-only during the interactive withdrawal flow.

P2: Exchange rate fluctuation

On step 2, the Wallet uses an exchange rate (ER_1) to calculate how many USDZ are needed to withdraw 400 BRL. However, this exchange rate might have changed to ER_2 when the actual exchange happens, which is only on step 7. Wallets could ask the user if they would like to update the USDZ amount to reflect the exchange rate, but we should have a solution in case the user doesn't want to do that (or can't, in case 100 USDZ is their full balance and ER_2 is more expensive than ER_1).

Suggested solution

In order to provide a smooth user experience in this scenario, we could recommend/require anchors to be flexible to accept 398 BRLZ or 402 BRLZ, as long as the correct memo is provided in the transaction.

Questions

  • Do the suggested solutions make sense?
  • Is there any alternative proposal for addressing this use case?

@morleyzhi @brunomuler please let me know if this flow correctly describes what we've discussed last Friday
@tomquisel @msfeldstein @tomerweller looking forward to getting your thoughts on this :)

@msfeldstein
Copy link
Contributor

What does this mean
(maybe via a path payment to itself) why not send via a path payment to the withdraw anchor

@msfeldstein
Copy link
Contributor

Is exchange rate fluctuation an issue in any normal (non-cross-border) withdrawal? If something is very volatile, the exchange rate could change between the portion of the kyc flow where rate was calculated and when the payment actually gets processed on the network.

@msfeldstein
Copy link
Contributor

Are you suggesting that anchors will have a flexible input for a fixed output?

@msfeldstein
Copy link
Contributor

I think it might help to wireframe this out. I wonder if we can move the USDZ/BRZ exchange rate to the end of the flow. In the confirm payment page, the user will be sending USDZ, ie the user goes through a normal BRZ withdraw flow, and at the end the wallet can say "You need to get 400BRZ to this account, you can do so via a path payment of 101.4USDZ" or something

@brunomuler
Copy link

My understanding was that the user could adjust (or the app would adjust automatically, based on the price rate difference) the final value after the KYC check, so this would be the final value to be withdrawn. The idea of asking for the value before the KYC would be related to anchors requiring different KYC info or allowing different permissions depending on the requested withdrawal value, is that correct?

@msfeldstein
Copy link
Contributor

we've been moving away from that idea of different kycs based on inital value, if you're an anchor that requires that, do it all in the webapp.

The more i think about this the better it seems to just start by saying “Withdraw BRZ” even if you don’t have any, and the confirm transaction screen lets you withdraw BRZ via path payment from any of your other assets

@accordeiro
Copy link
Member Author

accordeiro commented Sep 6, 2019

Following up:

<@msfeldstein> What does this mean
(maybe via a path payment to itself) why not send via a path payment to the withdraw anchor

I think you're right: sending the path payment to the withdraw anchor makes more sense.

<@msfeldstein> Is exchange rate fluctuation an issue in any normal (non-cross-border) withdrawal? If something is very volatile, the exchange rate could change between the portion of the kyc flow where rate was calculated and when the payment actually gets processed on the network.

You're right – but the exchange would happen prior to the payment processing part, so we wouldn't need to worry about this for withdrawal. But this is indeed a concern for deposit.

<@msfeldstein> Are you suggesting that anchors will have a flexible input for a fixed output?

Not exactly, I think they should have a flexible input to accommodate fluctuations, but they should of course adjust the output accordingly. In the described case, the anchor would deposit 398 BRL - fees to a 398 BRLZ deposit, even though the user initially requested a 400 BRL deposit.

<@msfeldstein> I wonder if we can move the USDZ/BRZ exchange rate to the end of the flow. In the confirm payment page, the user will be sending USDZ, ie the user goes through a normal BRZ withdraw flow, and at the end the wallet can say "You need to get 400BRZ to this account, you can do so via a path payment of 101.4USDZ" or something

Yep, but we need to understand whether that's feasible in the first place, considering the current USDZ balance the User holds. This is why we need the check prior to the interactive flow. Does that make sense?

<@brunomuler> The idea of asking for the value before the KYC would be related to anchors requiring different KYC info or allowing different permissions depending on the requested withdrawal value, is that correct?

I think Michael already answered this, but I also wanted to add that asking for an amount pre-KYC is a validation step to ensure the user has enough funds to perform that operation later on.

<@msfeldstein> The more i think about this the better it seems to just start by saying “Withdraw BRZ” even if you don’t have any, and the confirm transaction screen lets you withdraw BRZ via path payment from any of your other assets

I think my previous two answers apply here too – we need to have a ballpark estimate of what the user aims to withdraw to check if we should move forward with the KYC/withdrawal flow.

@msfeldstein
Copy link
Contributor

we need to have a ballpark estimate of what the user aims to withdraw to check if we should move forward with the KYC/withdrawal flow.

Yeah i think this will catch people a lot in the case where they're a dollar short, i see why you are considering the flexibility and that seems like a good idea.

This "max amount available to withdraw" seems like something we could add to the spec as a hint that wouldn't break anything already existing.

@tomerweller
Copy link
Contributor

I think this is a bit of a bigger topic.

Multi-currency deposit/withdraws are problematic (deposits more so than withdraws) due to the inability to lock a conversion rate . I believe the root cause of this is that they are non-idiomatic to the way that the stellar protocol along with the ecosystem protocols view cross border payments.

In theory, an idiomatic cross border payment should look like (approach 1):

  1. deposit fiat USD -> USDZ
  2. send path payment USDZ -> BRLZ
  3. withdraw BRLZ -> fiat BRL
    This utilizes multi-asset wallets that support path payments and a one-to-one mapping between a transfer server and an asset.

An alternative approach, is to move currency conversions off-chain (approach 2):

  1. deposit fiat USD -> USDZ
  2. send USDZ
  3. withdraw USDZ -> fiat BRL (conversion happens in the off-ramp, off-chain and the rate is locked when the withdraw is initiated)
    This flow requires a slight change to SEP6. It should allow multiple anchors to provide on-off/ramps for the same asset (I think that's a good change and will add an issue for that)

The implied approach in this specific issue is a bit of a mish-mash of both of the above. It uses multiple assets and the dex for conversions, without the destination wallet actually holding a balance of BRLZ. I assume this is because this specific wallet only allows a USDZ balance?

As a stop-gap, maybe the wallet should just convert the USDZ to BRLZ immediately in the beginning of the withdraw process. That will essentially lock the conversion rate.

@accordeiro
Copy link
Member Author

I really like the idea of being able to do approach 2 – I remember we discussed this a while ago. The only tricky bit is it'd require anchors to be able to make international payments and/or have a legal entity in different countries. Does that seem to reflect real-life scenarios?

I assume this is because this specific wallet only allows a USDZ balance?

You're right, this specific Wallet only allows a USDZ balance.

As a stop-gap, maybe the wallet should just convert the USDZ to BRLZ immediately in the beginning of the withdraw process. That will essentially lock the conversion rate.

Yeah, we've thought about this approach. However, what if the user converts to BRLZ early in the process, then their KYC gets rejected afterwards? I guess we'd be able to convert back, but the User might lose some of their balance in the process due to fees / conversion rates.

@tomerweller
Copy link
Contributor

The only tricky bit is it'd require anchors to be able to make international payments and/or have a legal entity in different countries. Does that seem to reflect real-life scenarios?

It's not necessarily that complicated. It just entails a relationship between the BRL off-ramp and the USD Anchor. So, let's say that you @accordeiro provide an AnchorUSD off-ramp in Brazil. You're going to end up with a bunch of AnchorUSD dollars on Stellar. If you have an American bank account you can use their ACH off-ramp to turn this to dollars in your american bank account. Or, you can use their wire service to off-ramp through swift to anywhere in the world. I'm no lawyer but I don't think you'll need an American legal entity for that.

However, what if the user converts to BRLZ early in the process, then their KYC gets rejected afterwards? I guess we'd be able to convert back, but the User might lose some of their balance in the process due to fees / conversion rates.

Let's optimize the experience for the people who pass KYC ;)

@tomquisel
Copy link
Contributor

@tomerweller I love the idea of allowing multiple offramps for the same asset.

It's also worth asking if a longer-term goal of this project is to make the forex happen on the DEX. I thought it was, which means approach 2 would improve the user experience but would not be a step towards our larger goals.

My take is that if we do the following:

  • Display an estimated USD cost at start of withdrawal (make it clear it's an estimate), and then display final USD cost at final confirmation of withdrawal. It'll be rare that the conversion rate will change meaningfully (or at all) between the two steps, so the vast majority of cases will see no issue.
  • Require anchors to be flexible with amount (by maybe 10%)
  • If a user enters their amount in the wallet, then changes it in the interactive flow to an amount that's too high for their account, we don't worry about it. It'll be rare, and we can just display an error and ask them to retry picking a better amount.

then the flow will work out nicely.

@accordeiro
Copy link
Member Author

It's also worth asking if a longer-term goal of this project is to make the forex happen on the DEX. I thought it was

I agree having the forex happen on the DEX helps us achieve our goals in a more holistic way.

@tomerweller created a separate issue (#397) for us to discuss the decoupling of deposit/withdraw flows from asset issuers altogether, including off-chain withdraw/deposit currency conversions.

Based on our discussions, these are my suggestions for tweaking SEP-6 to solve the problems listed in this issue:

  • On the Basic Anchor Implementation, emphasize the importance of accepting an amount value as a parameter to both /withdraw and the interactive flow URL. This partially solves P1. We might still have some issues with users changing amounts in the interactive flow, which is a minor edge case we probably shouldn't focus on for now.

  • On the Withdraw Specs, point out that anchors need to be flexible about the informed amount vs. what is actually transferred to their Stellar accounts (a 10% margin might be a good start). This should solve P2 completely.

Do these sound right, or am I missing something?

@accordeiro
Copy link
Member Author

To capture some other details discussed offline, we should also instruct anchors to watch for pathPayment operations (vs. only payment ops) in the Withdraw specs.

@tomerweller
Copy link
Contributor

we should also instruct anchors to watch for pathPayment operations

Good point. They should listen to all form of payments that the horizon payments endpoint supports. Even though it can be a bit challenging sometimes.

@accordeiro
Copy link
Member Author

Cool, that makes sense. I'll issue a PR to reflect our conclusions here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants