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

Update MGI guide using TS wallet SDK #658

Merged
merged 29 commits into from
Jun 26, 2024
Merged
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0bd2c8c
Add link to wallet SDK documentation in “Resources” section
CassioMG Jun 3, 2024
2f95447
Add installation instructions
CassioMG Jun 3, 2024
21649a2
Auth instructions
CassioMG Jun 3, 2024
77b4064
"Initiate a Transaction" ts code
CassioMG Jun 4, 2024
d16378e
"Poll Until MoneyGram is Ready" ts code
CassioMG Jun 4, 2024
b8bad48
Add "Sending Funds" code
CassioMG Jun 4, 2024
059eab1
"Receiving Funds" ts code
CassioMG Jun 4, 2024
0df20f6
tweak
CassioMG Jun 4, 2024
4b55d0b
Merge branch 'main' into cg-mgi-ts-guide
CassioMG Jun 7, 2024
d61cecf
"Fetch the Reference Number" ts code
CassioMG Jun 7, 2024
5125b5d
Links to existing documentation for more information
CassioMG Jun 7, 2024
fe37a4d
tweaks
CassioMG Jun 7, 2024
8df42f7
prettier
CassioMG Jun 7, 2024
a5b0df6
Actually display bash code
CassioMG Jun 7, 2024
d780a21
Merge branch 'main' into cg-mgi-ts-guide
CassioMG Jun 11, 2024
055978a
Merge branch 'main' into cg-mgi-ts-guide
CassioMG Jun 18, 2024
19da0b7
Set MGI_ACCESS_HOST in code
CassioMG Jun 18, 2024
41bdf22
Add comment for optional fields
CassioMG Jun 18, 2024
960e7cc
Remove callback param, instruct wallets on postMessage event
CassioMG Jun 19, 2024
c8dc651
amount only required by custodial wallets
CassioMG Jun 19, 2024
8fccdaa
Merge branch 'main' into cg-mgi-ts-guide
CassioMG Jun 24, 2024
d45f9d0
Expand on transaction ids
CassioMG Jun 25, 2024
295515b
Query asset using the wallet sdk
CassioMG Jun 25, 2024
f38f793
Small tweaks to keep the same code pattern between typescript and dar…
CassioMG Jun 25, 2024
1b0fee8
Merge branch 'main' into cg-mgi-ts-guide
CassioMG Jun 25, 2024
0f45a2d
Expand on error handling
CassioMG Jun 26, 2024
8dc1c4b
Merge branch 'main' into cg-mgi-ts-guide
CassioMG Jun 26, 2024
01cdd5f
Fix tutorial link
CassioMG Jun 26, 2024
7d77e8e
Update comment
CassioMG Jun 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 227 additions & 7 deletions docs/building-apps/moneygram-access-integration-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ sidebar_position: 140
---

import { CodeExample } from "@site/src/components/CodeExample";
import { Alert } from "@site/src/components/Alert";

This document guides the reader through the technical requirements for integrating [MoneyGram Access] into an existing application. MoneyGram Access is a MoneyGram product that enables users of third-party applications, such as crypto wallets and exchanges, to cash-in (deposit) and cash-out (withdrawal) of Stellar USDC.

Expand All @@ -14,6 +13,8 @@ MoneyGram requires businesses to go through an onboarding process in order to ge

- [MoneyGram Access Wallet MVP Implementation]
- Use this MVP implementation as a reference for building your own integration. Many of the code snippets shared in this document are pulled from this project.
- [Stellar Wallet SDK Docs]
- Use this Wallet SDK to facilitate building your own integration. Many of the code snippets shared in this document are pulled from the [Stellar Wallet SDK repository].
- [Stellar Test Anchor]
- Before getting access to MoneyGram's test environment, you can use the SDF's test anchor while developing your integration
- [Stellar Demo Wallet]
Expand Down Expand Up @@ -54,6 +55,20 @@ This document will walk you through the necessary steps to develop a functional

The guide will assume your application is first being developed on Stellar’s test network and using MoneyGram’s testing deployment of Access, but there are no functional differences deploying the application to Stellar’s public network and using MoneyGram’s production deployment.

### Installing the Wallet SDK

We highly recommend using the wallet SDK to facilitate building your integration. Find more info on the [Stellar Wallet SDK Docs].

You can use `yarn` to install it:

<CodeExample>

```bash
yarn add @stellar/typescript-wallet-sdk
```

</CodeExample>

## Custodial vs. Non-Custodial Applications

Some applications, such as centralized exchanges, are custodial, meaning the application has access to the private keys of the acccounts holding its users’ funds on Stellar. Typically, custodial applications pool user funds into a smaller set of managed Stellar accounts, called pooled, shared, or omnibus accounts.
Expand Down Expand Up @@ -142,6 +157,22 @@ The following code demonstrates how to implement the application’s side of thi

<CodeExample>

```ts
import { Wallet, SigningKeypair } from "@stellar/typescript-wallet-sdk";

const wallet = Wallet.TestNet();

// First we create an anchor object with MoneyGram's home domain.
const anchor = wallet.anchor({ homeDomain: MGI_ACCESS_HOST });
CassioMG marked this conversation as resolved.
Show resolved Hide resolved

// Then we create the sep10 object which handles all the athentication steps.
const sep10 = await anchor.sep10();

// Finally, we authenticate using the wallet's SIGNING_KEY secret.
const authKey = SigningKeypair.fromSecret(AUTH_SECRET_KEY);
const authToken = await sep10.authenticate({ accountKp: authKey });
```

```python
import requests
from stellar_sdk import Network
Expand Down Expand Up @@ -185,10 +216,27 @@ You will need the following pieces of information:
- The amount the user would like to withdraw / cash-out
- This should be collected from the user prior to initiating this transaction

The following code can be used as a reference for implementing this logic yourself. This code is not necessarily production-ready.
The following code can be used as a reference for implementing this logic yourself.

<CodeExample>

```ts
// Use same "anchor" object from previous step.
const { url, id } = await anchor.sep24().withdraw({
authToken: authToken, // Use same "authToken" string from previous step
withdrawalAccount: FUNDS_STELLAR_KEYPAIR.public_key,
assetCode: ASSET_CODE, // Should be "USDC"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should follow our own guidelines from SEP-24 guide

const asset = info.currencies.find(({ code }) => code === "USDC").assetId;

Copy link
Contributor Author

@CassioMG CassioMG Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ifropc this code appears to be broken. We can't access assetId directly from info.currencies.

I think this is the best we could do with current TS Wallet SDK:

const tomlInfo = await anchor.getInfo();

const tomlAsset = tomlInfo.currencies.find(({ code }) => code === "USDC");

if(!tomlAsset?.code || !tomlAsset?.issuer) {
  throw new Error("Anchor does not support USDC asset or is not correctly configured on TOML file");
}

const asset = new IssuedAssetId(
  tomlAsset.code,
  tomlAsset.issuer,
);

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ifropc I'm wondering if we should display this same code above 👆 in our SEP-24 guide in place of the broken const asset = info.currencies.find(({ code }) => code === "USDC").assetId; code. Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lang: "en",
CassioMG marked this conversation as resolved.
Show resolved Hide resolved
extraFields: {
amount: "<amount to withdraw / cash-out>",
CassioMG marked this conversation as resolved.
Show resolved Hide resolved
},
});

// Append the callback param for the wallet to be notified when the user has
// completed the MGI experience and has requested to close the window.
const urlWithCallback = `${url}&callback=postmessage`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We advise against using postMessage approach, I think we should mention that it's being deprecated long term

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on this, do we even need to recommend it? I thought MoneyGram supported not using postmessage as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the problem of not implementing the postMessage handler is that MGI is still displaying a red OK/Close button on the last screen. So if wallets don't listen for that button it'll behave like a broken button.

But one thing I just realized is that Vibrant is not appending this &callback=postmessage param to the url but the app still receives the postMessage event. So it looks to me that we don't need to ask wallets to append this param, but instead just listen to a postMessage event coming from that last red button so they know it's safe to close the webview and prevent the button from looking broken. What do you guys think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm so if we don't listen to it, the button will not work? I think we should describe it in the documentation then

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We defined a migration plan with MGI to remove postmessages because of the UX complications in the event that something doesn't work. @Ifropc can you dig up the plans we had and revisit this with MGI? Feel free to loop in the Vibrant team as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks guys, would be nice to remove that button.

Just to clarify: Vibrant does not rely on the postMessage event anymore for a while now, users can close the webview without tapping MGI's red button. Which means MGI would just need to remove that red button and probably add some copy letting users know they can safely close the window.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JakeUrban they have been stalling the plan for a very long time, it's somewhere in their backlog. We should raise this question again with their team

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep the idea is to replace button with the next that says you can close the window now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

```

```python
import requests

Expand Down Expand Up @@ -247,20 +295,84 @@ In each case, the transaction submitted to Stellar must have a memo attached to

### Poll Until MoneyGram is Ready

Before the application can send funds or instruct the user to provide cash to a MoneyGram agent, the application should confirm with MoneyGram’s server that the transaction is ready to proceed.
Before the application can send funds or instruct the user to provide cash to a MoneyGram agent, the application should confirm with MoneyGram’s server that the transaction is ready to proceed which is signaled by the `pending_user_transfer_start` status.
Comment on lines 321 to +323
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: can/should we recommend the use of MoneyGram's webhooks now? When this tutorial was originally created they didn't have them. Now I would imagine this the preferred approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch.

Do we have any existing documentation explaining how to setup/integrate MGI's webhook?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe so, @Ifropc @nirojapanda are you familiar with any docs?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, sorry, I don't think there are any docs for that. Their implementation also doesn't match the standard precisely BTW

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so do we still want to recommend this approach? If so, can you help create that documentation if you're familiar with the differences?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's what Vibrant is doing: only listen to postMessage for closing button, and do NOT listen to the message returned by it, @CassioMG am I correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, Vibrant only listen to postMessage for closing the button. But it only does that when the transaction that comes inside the event message is in a pending status to avoid closing the webview for ANY other random postMessage event that may happen during the interactive flow for whatever reason.
Like this:

    // If we get a postMessage and the status is any pending one,
    // let's interpret that as a signal to close the window and
    // take user back to the wallet experience
    if (eventMessage.transaction?.status?.includes("pending")) {
      handleClose();
    }

Does that make sense?

Sorry about the late response I was OOO these past days

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it sounds like using MGI's webhooks instead of postmessages may not be an option yet so we need to continue documenting the postmessage approach. @CassioMG if you think we need to instruct wallets to only close when the status is pending_user_transfer_start that is fine by me

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


You will need the following information to do so.

- The authentication token provided by MoneyGram
- The transaction’s ID provided by MoneyGram
- MoneyGram’s transaction’s endpoint
- MoneyGram’s transaction’s endpoint (you don't need to worry about it if you are using the Wallet SDK)
- Testing: https://extstellar.moneygram.com/stellaradapterservice/sep24/transaction
- Production: https://stellar.moneygram.com/stellaradapterservice/sep24/transaction

This code uses a simple polling mechanism with no bail-out condition. The application’s code should be more robust.
This code uses a simple watching (polling) mechanism with no bail-out condition. The application’s code should be more robust.

<CodeExample>

```ts
// We can keep the transaction "id" from the withdraw() call,
// authToken and assetCode from previous steps.
const { url, id: transactionId } = await anchor.sep24().withdraw({
authToken,
assetCode,
// ...other params
});

// First, let's initialize a watcher object from the Wallet SDK.
let watcher = anchor.sep24().watcher();

// Then we have the option to watch for a particular transaction.
let { stop, refresh } = watcher.watchOneTransaction({
authToken,
assetCode,
id: transactionId,
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// begin transfer code
}
},
onSuccess: (transaction) => {
// transaction comes back as completed / refunded / expired
},
onError: (transaction) => {
// runtime error, or the transaction comes back as
// no_market / too_small / too_large / error
},
});

// We also have the option to watch for ALL transactions of a particular asset.
let { stop, refresh } = watcher.watchAllTransactions({
authToken,
assetCode,
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// begin transfer code
}
},
onError: (transaction) => {
// runtime error, or the transaction comes back as
// no_market / too_small / too_large / error
},
});

// While the Watcher class offers powerful tracking capabilities, sometimes
// it's required to just fetch a transaction (or transactions) once. The Anchor
// class allows you to fetch a transaction by ID, Stellar transaction ID, or
// external transaction ID:
const transaction = await anchor.sep24().getTransactionBy({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand a bit more into how to use stellarTransactionId or externalTransactionId? Maybe write 3 examples

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

authToken,
id: transactionId,
// stellarTransactionId,
// externalTransactionId,
});

// It's also possible to fetch multiple transactions for an asset.
const transactions = await anchor.sep24().getTransactionsForAsset({
authToken,
assetCode,
});
```

```python
import requests

Expand Down Expand Up @@ -299,7 +411,7 @@ def poll_transaction_until_status(

### Sending Funds

Once MoneyGram is ready to receive funds, your application should extract the Stellar account and memo to use in the payment transaction, construct a Stellar transaction, and submit it to the Stellar network. You’ll need:
Once MoneyGram is ready to receive funds, your application should extract the Stellar account, memo and amount to use in the payment transaction, construct a Stellar transaction, and submit it to the Stellar network. You’ll need:

- A copy of MoneyGram’s transaction object
- The application’s funds public & secret key
Expand All @@ -310,10 +422,57 @@ Code for submitting transactions to Stellar should be developed thoughtfully. Th
- Set a maximum timebound on the transaction. This ensures that if your transaction is not included in a ledger before the set time, you can reconstruct the transaction with a higher offered fee and submit it again with better chances of inclusion.
- Resubmit the transaction when you get 504 status codes. 504 status codes are just telling you that your transaction is still pending -- not that it has been canceled or that your request was invalid. You should simply make the request again with the same transaction to get a final status (either included or expired).

Below is highly simplified code for submitting a payment transaction. It does not use timebounds, handle a transaction’s expiration, or handle 504 status codes.
Below is a sample `Typescript` code for submitting a payment ("transfer") transaction using the Wallet SDK. It uses timebounds and handles 504 status codes (inside `submitTransaction`), but it does not handle a transaction’s expiration.

Please note that the `Python` code is highly simplified. It does not use timebounds, handle 504 status codes or handle a transaction’s expiration.

<CodeExample>

```ts
import { Wallet, IssuedAssetId } from "@stellar/typescript-wallet-sdk";

const wallet = Wallet.TestNet();

// This creates a Stellar instance to manage the connection with Horizon.
const stellar = wallet.stellar();

// Creates a stellar asset, in this case it should use USDC configuration.
const asset = new IssuedAssetId(ASSET_CODE, ASSET_ISSUER);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's define on how to query asset above (see another comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// This creates a transaction builder which we'll be using to assemble
// our transfer withdrawal transaction as shown below.
const txBuilder = await stellar.transaction({
sourceAddress: FUNDS_STELLAR_KEYPAIR,
baseFee: 10000, // this is 0.001 XLM
timebounds: 180, // in seconds
});

// We can use the transaction object received on the onMessage callback from
// the watcher, or, we can also fetch the transaction object using either
// getTransactionBy or getTransactionsForAsset as illustrated in previous step.
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// Use the builder to assemble the transfer transaction. Behind the scenes
// it extracts the Stellar account (withdraw_anchor_account), memo (withdraw_memo)
// and amount (amount_in) to use in the Stellar payment transaction that will
// be submitted to the Stellar network.
const transferTransaction = txBuilder
.transferWithdrawalTransaction(transaction, asset)
.build();

// Signs it with the source (funds) account key pair
transferTransaction.sign(FUNDS_STELLAR_KEYPAIR);

// Finally submits it to the stellar network. This stellar.submitTransaction()
// function handles '504' status codes by keep retrying it until submission
// succeeds or we get a different error.
const response = await stellar.submitTransaction(transferTransaction);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we write an example on how to handle other errors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


console.log("Stellar-generated transaction ID: ", response.id);
}
};
```

```python
from stellar_sdk import (
Server, TransactionBuilder, Network, Asset, IdMemo
Expand Down Expand Up @@ -358,6 +517,53 @@ Note that this code does not handle [path payments] or [claimable balances], two

<CodeExample>

```ts
// The Wallet SDK does not support payments streaming yet so let's build
CassioMG marked this conversation as resolved.
Show resolved Hide resolved
// it using the underlying Horizon SDK
import { Horizon } from "@stellar/stellar-sdk";
import { getTransactionByMemo } from "./queries";

const streamPayments = (account: string, cursor: string) => {
const server = new Horizon.Server("https://horizon-testnet.stellar.org");
server
.payments()
.forAccount(account)
.join("transactions")
.cursor(cursor)
.stream({
onmessage: (payment) => {
if (
payment["type"] !== "payment" ||
payment["from"] === account ||
payment["asset_type"] === "native" ||
payment["asset_code"] !== ASSET_CODE ||
payment["asset_issuer"] !== ASSET_ISSUER
) {
return;
}

const transaction = getTransactionByMemo(
payment["transaction_attr"]["memo"],
payment["transaction_attr"]["memo_type"],
); // this is your own DB query function

if (!transaction) {
return;
}

console.log(
`Payment for deposit transaction ${transaction.id}`,
`matched with Stellar transaction `,
`${payment["transaction_attr"]["id"]}`,
);
},
onerror: (error) => {
// handle error
},
});
};
```

```python
from stellar_sdk import Server
from .queries import get_transaction_by_memo
Expand Down Expand Up @@ -399,6 +605,18 @@ Note that MoneyGram’s transaction details page is protected with a JWT token i

<CodeExample>

```ts
// Watcher's onMessage callback, see previous steps for more info on this
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_complete") {
console.log(
`Transaction reference number ${transaction.external_transaction_id}`,
`also viewable at ${transaction.more_info_url}`,
);
}
};
```

```python
from .api import poll_transction_until_status

Expand All @@ -417,6 +635,8 @@ print(

[moneygram access]: https://stellar.org/moneygram?locale=e
[moneygram access wallet mvp implementation]: https://github.com/stellar/moneygram-access-wallet-mvp
[stellar wallet sdk docs]: https://developers.stellar.org/docs/category/build-a-wallet-with-the-wallet-sdk
[stellar wallet sdk repository]: https://github.com/stellar/typescript-wallet-sdk
[stellar test anchor]: https://testanchor.stellar.org/.well-known/stellar.toml
[stellar demo wallet]: https://demo-wallet.stellar.org
[sep-1]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0001.md
Expand Down
Loading