From 0bd2c8c0c9250b66dea0f9fe0a6169e1cf96c317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Mon, 3 Jun 2024 11:59:17 -0300 Subject: [PATCH 01/23] =?UTF-8?q?Add=20link=20to=20wallet=20SDK=20document?= =?UTF-8?q?ation=20in=20=E2=80=9CResources=E2=80=9D=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/building-apps/moneygram-access-integration-guide.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 3968998de..e2a0ca4b2 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -14,6 +14,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] @@ -417,6 +419,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 From 2f954474c8ab3e9b9c54964a6b26f935db009253 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Mon, 3 Jun 2024 18:30:24 -0300 Subject: [PATCH 02/23] Add installation instructions --- .../moneygram-access-integration-guide.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index e2a0ca4b2..b8528b8f0 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -3,8 +3,9 @@ title: Integrate with MoneyGram Access sidebar_position: 140 --- -import { CodeExample } from "@site/src/components/CodeExample"; import { Alert } from "@site/src/components/Alert"; +import { CodeExample } from "@site/src/components/CodeExample"; +import TsInstall from "@site/docs/building-apps/wallet/component/ts/install.mdx"; 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. @@ -56,6 +57,14 @@ 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. + +You can use `yarn` to install it: + +} /> + ## 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. From 21649a2353a76aebd0a9e8ded5bba8cfccb1a818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Mon, 3 Jun 2024 18:30:47 -0300 Subject: [PATCH 03/23] Auth instructions --- .../moneygram-access-integration-guide.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index b8528b8f0..d4edf5102 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -153,6 +153,23 @@ The following code demonstrates how to implement the application’s side of thi +```ts +import { Wallet, SigningKeypair } from "@stellar/typescript-wallet-sdk"; + +const wallet = Wallet.TestNet(); + +// First we create an anchor object to work with the anchor you are integrating +// with. In this case MoneyGram's Testnet server: "extstellar.moneygram.com" +const anchor = wallet.anchor({ homeDomain: "extstellar.moneygram.com" }); + +// Then we create the sep10 object which handles all the athentication steps. +const sep10 = await anchor.sep10(); + +// Finally, we authenticate using your wallet's SIGNING_KEY secret. +const authKey = SigningKeypair.fromSecret("WALLET_SIGNING_KEY_SECRET"); +const authToken = await sep10.authenticate({ accountKp: authKey }); +``` + ```python import requests from stellar_sdk import Network From 77b4064fd8c16eafc9dfb06860973204b576a1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 4 Jun 2024 10:10:37 -0300 Subject: [PATCH 04/23] "Initiate a Transaction" ts code --- .../moneygram-access-integration-guide.mdx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index d4edf5102..b4f058698 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -158,15 +158,14 @@ import { Wallet, SigningKeypair } from "@stellar/typescript-wallet-sdk"; const wallet = Wallet.TestNet(); -// First we create an anchor object to work with the anchor you are integrating -// with. In this case MoneyGram's Testnet server: "extstellar.moneygram.com" -const anchor = wallet.anchor({ homeDomain: "extstellar.moneygram.com" }); +// First we create an anchor object with MoneyGram's home domain +const anchor = wallet.anchor({ homeDomain: MGI_ACCESS_HOST }); // Then we create the sep10 object which handles all the athentication steps. const sep10 = await anchor.sep10(); // Finally, we authenticate using your wallet's SIGNING_KEY secret. -const authKey = SigningKeypair.fromSecret("WALLET_SIGNING_KEY_SECRET"); +const authKey = SigningKeypair.fromSecret(AUTH_SECRET_KEY); const authToken = await sep10.authenticate({ accountKp: authKey }); ``` @@ -217,6 +216,23 @@ The following code can be used as a reference for implementing this logic yourse +```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" + lang: "en", + extraFields: { + amount: "", + }, +}); + +// 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`; +``` + ```python import requests From d16378e27b2d88486de98cbda72b137af5a69dee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 4 Jun 2024 10:48:33 -0300 Subject: [PATCH 05/23] "Poll Until MoneyGram is Ready" ts code --- .../moneygram-access-integration-guide.mdx | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index b4f058698..9fd8f4020 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -297,7 +297,7 @@ 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 @@ -305,6 +305,70 @@ This code uses a simple polling mechanism with no bail-out condition. The applic +```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({ + 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 From b8bad481b337b52c0b518ab2dab6ec95d781a215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 4 Jun 2024 14:29:05 -0300 Subject: [PATCH 06/23] Add "Sending Funds" code --- .../moneygram-access-integration-guide.mdx | 65 ++++++++++++++++--- 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 9fd8f4020..37282bfef 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -158,13 +158,13 @@ import { Wallet, SigningKeypair } from "@stellar/typescript-wallet-sdk"; const wallet = Wallet.TestNet(); -// First we create an anchor object with MoneyGram's home domain +// First we create an anchor object with MoneyGram's home domain. const anchor = wallet.anchor({ homeDomain: MGI_ACCESS_HOST }); // Then we create the sep10 object which handles all the athentication steps. const sep10 = await anchor.sep10(); -// Finally, we authenticate using your wallet's SIGNING_KEY secret. +// Finally, we authenticate using the wallet's SIGNING_KEY secret. const authKey = SigningKeypair.fromSecret(AUTH_SECRET_KEY); const authToken = await sep10.authenticate({ accountKp: authKey }); ``` @@ -217,7 +217,7 @@ The following code can be used as a reference for implementing this logic yourse ```ts -// Use same "anchor" object from previous step +// 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, @@ -307,7 +307,7 @@ This code uses a simple polling mechanism with no bail-out condition. The applic ```ts // We can keep the transaction "id" from the withdraw() call, -// authToken and assetCode from previous steps +// authToken and assetCode from previous steps. const { url, id: transactionId } = await anchor.sep24().withdraw({ authToken, assetCode, @@ -317,7 +317,7 @@ const { url, id: transactionId } = await anchor.sep24().withdraw({ // 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 +// Then we have the option to watch for a particular transaction. let { stop, refresh } = watcher.watchOneTransaction({ authToken, assetCode, @@ -336,7 +336,7 @@ let { stop, refresh } = watcher.watchOneTransaction({ }, }); -// We also have the option to watch for ALL transactions of a particular asset +// We also have the option to watch for ALL transactions of a particular asset. let { stop, refresh } = watcher.watchAllTransactions({ authToken, assetCode, @@ -362,7 +362,7 @@ const transaction = await anchor.sep24().getTransactionBy({ // externalTransactionId, }); -// It's also possible to fetch multiple transactions for an asset +// It's also possible to fetch multiple transactions for an asset. const transactions = await anchor.sep24().getTransactionsForAsset({ authToken, assetCode, @@ -407,7 +407,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 @@ -418,10 +418,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 highly simplified code for submitting a payment ("transfer") transaction. It does not use timebounds, handle a transaction’s expiration, or handle 504 status codes (unless you're using the Wallet SDK). +```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); + +// 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 +// some of the watcher calls, or, we can also fetch the transaction object +// using either getTransactionBy or getTransactionsForAsset as exposed 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); + + console.log("Stellar-generated transaction ID: ", response.id); + } +} +``` + ```python from stellar_sdk import ( Server, TransactionBuilder, Network, Asset, IdMemo From 059eab1e46c8f8ed072515c8ecbfdb86c33eff6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 4 Jun 2024 18:25:33 -0300 Subject: [PATCH 07/23] "Receiving Funds" ts code --- .../moneygram-access-integration-guide.mdx | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 37282bfef..344b3edda 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -513,6 +513,53 @@ Note that this code does not handle [path payments] or [claimable balances], two +```ts +// The Wallet SDK does not support payments streaming yet so let's build +// 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"] + ) // DB query + + 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 From 0df20f6152c4960e4a1e43aa9a658274e2a89647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 4 Jun 2024 18:26:47 -0300 Subject: [PATCH 08/23] tweak --- docs/building-apps/moneygram-access-integration-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 344b3edda..eb7a0483b 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -541,7 +541,7 @@ const streamPayments = (account: string, cursor: string) => { const transaction = getTransactionByMemo( payment["transaction_attr"]["memo"], payment["transaction_attr"]["memo_type"] - ) // DB query + ); // your own custom DB query if (!transaction) { return; From d61cecfc0c3759bf6c61f1821bcdfa82b85b4353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Fri, 7 Jun 2024 10:44:44 -0300 Subject: [PATCH 09/23] "Fetch the Reference Number" ts code --- .../moneygram-access-integration-guide.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index eb7a0483b..64024bcf3 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -601,6 +601,19 @@ Note that MoneyGram’s transaction details page is protected with a JWT token i +```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 From 5125b5d374918bc2b12743665379174a45209cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Fri, 7 Jun 2024 10:49:54 -0300 Subject: [PATCH 10/23] Links to existing documentation for more information --- docs/building-apps/moneygram-access-integration-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 64024bcf3..ec419fef6 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -59,7 +59,7 @@ The guide will assume your application is first being developed on Stellar’s t ### Installing the Wallet SDK -We highly recommend using the wallet SDK to facilitate building your integration. +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: From fe37a4dd26b6a65176de2dc55908661cff46012b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Fri, 7 Jun 2024 11:34:58 -0300 Subject: [PATCH 11/23] tweaks --- .../moneygram-access-integration-guide.mdx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index ec419fef6..b17ab9ac1 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -212,7 +212,7 @@ 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. @@ -291,7 +291,7 @@ 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. You will need the following information to do so. @@ -301,7 +301,7 @@ You will need the following information to do so. - 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. @@ -418,7 +418,9 @@ 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 ("transfer") transaction. It does not use timebounds, handle a transaction’s expiration, or handle 504 status codes (unless you're using the Wallet SDK). +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. @@ -442,9 +444,8 @@ const txBuilder = await stellar.transaction({ }); // We can use the transaction object received on the onMessage callback from -// some of the watcher calls, or, we can also fetch the transaction object -// using either getTransactionBy or getTransactionsForAsset as exposed in -// previous step. +// 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") { @@ -541,7 +542,7 @@ const streamPayments = (account: string, cursor: string) => { const transaction = getTransactionByMemo( payment["transaction_attr"]["memo"], payment["transaction_attr"]["memo_type"] - ); // your own custom DB query + ); // this is your own DB query function if (!transaction) { return; From 8df42f719696e9842135445b0cc0959bc244310c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Fri, 7 Jun 2024 11:44:11 -0300 Subject: [PATCH 12/23] prettier --- .../moneygram-access-integration-guide.mdx | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index b17ab9ac1..631b0ff13 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -219,13 +219,13 @@ The following code can be used as a reference for implementing this logic yourse ```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" - lang: "en", - extraFields: { - amount: "", - }, + authToken: authToken, // Use same "authToken" string from previous step + withdrawalAccount: FUNDS_STELLAR_KEYPAIR.public_key, + assetCode: ASSET_CODE, // Should be "USDC" + lang: "en", + extraFields: { + amount: "", + }, }); // Append the callback param for the wallet to be notified when the user has @@ -309,9 +309,9 @@ This code uses a simple watching (polling) mechanism with no bail-out condition. // 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 + authToken, + assetCode, + // ...other params }); // First, let's initialize a watcher object from the Wallet SDK. @@ -324,7 +324,7 @@ let { stop, refresh } = watcher.watchOneTransaction({ id: transactionId, onMessage: (transaction) => { if (transaction.status === "pending_user_transfer_start") { - // begin transfer code + // begin transfer code } }, onSuccess: (transaction) => { @@ -342,7 +342,7 @@ let { stop, refresh } = watcher.watchAllTransactions({ assetCode, onMessage: (transaction) => { if (transaction.status === "pending_user_transfer_start") { - // begin transfer code + // begin transfer code } }, onError: (transaction) => { @@ -448,8 +448,7 @@ const txBuilder = await stellar.transaction({ // 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 + // 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. @@ -467,7 +466,7 @@ onMessage: (transaction) => { console.log("Stellar-generated transaction ID: ", response.id); } -} +}; ``` ```python @@ -530,35 +529,35 @@ const streamPayments = (account: string, cursor: string) => { .stream({ onmessage: (payment) => { if ( - payment["type"] !== "payment" - || payment["from"] === account - || payment["asset_type"] === "native" - || payment["asset_code"] !== ASSET_CODE - || payment["asset_issuer"] !== ASSET_ISSUER + 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 + 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"]}` + `Payment for deposit transaction ${transaction.id}`, + `matched with Stellar transaction `, + `${payment["transaction_attr"]["id"]}`, ); }, onerror: (error) => { // handle error }, }); -} +}; ``` ```python @@ -606,13 +605,12 @@ Note that MoneyGram’s transaction details page is protected with a JWT token i // 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}` + `also viewable at ${transaction.more_info_url}`, ); } -} +}; ``` ```python From a5b0df6446521377755f1ef71f4ed9fa98bbacb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Fri, 7 Jun 2024 13:05:11 -0300 Subject: [PATCH 13/23] Actually display bash code --- .../moneygram-access-integration-guide.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 631b0ff13..fe16d015a 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -3,9 +3,7 @@ title: Integrate with MoneyGram Access sidebar_position: 140 --- -import { Alert } from "@site/src/components/Alert"; import { CodeExample } from "@site/src/components/CodeExample"; -import TsInstall from "@site/docs/building-apps/wallet/component/ts/install.mdx"; 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. @@ -63,7 +61,13 @@ We highly recommend using the wallet SDK to facilitate building your integration You can use `yarn` to install it: -} /> + + +```bash +yarn add @stellar/typescript-wallet-sdk +``` + + ## Custodial vs. Non-Custodial Applications From 19da0b7b1f3ecfc4cea3845296626ba5bc08eb84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 18 Jun 2024 18:26:22 -0300 Subject: [PATCH 14/23] Set MGI_ACCESS_HOST in code --- .../moneygram-access-integration-guide.mdx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index fe16d015a..f29266d5c 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -162,6 +162,11 @@ import { Wallet, SigningKeypair } from "@stellar/typescript-wallet-sdk"; const wallet = Wallet.TestNet(); +// Testnet +const MGI_ACCESS_HOST = "extstellar.moneygram.com"; +// Pubnet +// const MGI_ACCESS_HOST = "stellar.moneygram.com"; + // First we create an anchor object with MoneyGram's home domain. const anchor = wallet.anchor({ homeDomain: MGI_ACCESS_HOST }); @@ -178,6 +183,11 @@ import requests from stellar_sdk import Network from stellar_sdk.sep.stellar_web_authentication import read_challenge_transaction +# Testnet +MGI_ACCESS_HOST = "extstellar.moneygram.com" +# Pubnet +# MGI_ACCESS_HOST = "stellar.moneygram.com" + def get_token() -> str: query = f"{AUTH_URL}?account={AUTH_PUBLIC_KEY}&memo={USER_ID}" response = requests.get(query) From 41bdf22d7a90afe592ec8de1bb126988974a0e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 18 Jun 2024 18:27:27 -0300 Subject: [PATCH 15/23] Add comment for optional fields --- docs/building-apps/moneygram-access-integration-guide.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index f29266d5c..ca18c4f6a 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -236,8 +236,9 @@ 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" - lang: "en", + lang: "en", // "lang" is optional, defaults to "en" if ommited extraFields: { + // "amount" is optional for non-custodial wallets and mandatory for custodial wallets amount: "", }, }); From 960e7cc8617115e256fe08e280c5c72d3592a9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Wed, 19 Jun 2024 12:40:45 -0300 Subject: [PATCH 16/23] Remove callback param, instruct wallets on postMessage event --- .../moneygram-access-integration-guide.mdx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index ca18c4f6a..33ab6bebd 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -242,10 +242,6 @@ const { url, id } = await anchor.sep24().withdraw({ amount: "", }, }); - -// 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`; ``` ```python @@ -266,22 +262,22 @@ def initiate_withdraw(token: str, amount: str) -> Tuple[str, str]: } ) body = response.json() - return body["url"] + "&callback=postmessage", body["id"] + return body["url"], body["id"] ``` The logic for initiating a deposit transaction looks very similar. See the [SEP-24][sep-24] standard specification for detailed information. -The `&callback=postmessage` query parameter added to the returned URL is critical; it informs MoneyGram that the application’s client would like to be notified when the user has completed the MGI experience and has requested to close the window. We’ll cover this in more detail in the subsequent section. - ## Listen for the Close Notification The next step is to open the provided URL in the application’s client using a mobile webview, browser tab, or popup. The user will then go through KYC if they have not before in a prior transaction. In the deposit case, the user may also select a MoneyGram agent location to go to when providing cash. Finally, when the user is done with the MoneyGram UI, the user will select a button displayed on MoneyGram’s UI and MoneyGram will send a [postMessage] to the window or app that opened its flow initially. The message sent will be the SEP-24 transaction JSON object that represents the transaction. -Below is a simple JavaScript example listening for a postmessage notification. +If the transaction received on the [postMessage] message is on a `pending_user_transfer_start` status this means the user is done with the MoneyGram UI and it is safe to close the window. + +Below is a simple JavaScript example listening for a [postMessage] notification. @@ -290,9 +286,15 @@ webview = window.open(moneygramURL, "webview", "width=500,height=800"); window.addEventListener("message", closeWebView); function closeWebView(e) { - webview.close(); const txJson = e.data.transaction; console.log(`Transaction ${txJson.id} is ${txJson.status}`); + + // If we get a postMessage event and the transaction status is + // "pending_user_transfer_start" let's interpret that as a signal to close + // the webview window and take user back to the application experience + if (txJson.status === "pending_user_transfer_start") { + webview.close(); + } } ``` From c8dc6516f0d6fb57e0f590d538a15f0232c82998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Wed, 19 Jun 2024 12:43:14 -0300 Subject: [PATCH 17/23] amount only required by custodial wallets --- docs/building-apps/moneygram-access-integration-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 33ab6bebd..7f378aa1b 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -223,7 +223,7 @@ You will need the following pieces of information: - The authentication token provided by MoneyGram. This token can only be used for actions associated with the user identified by the ID used in the previous steps. - The public key of the keypair the application will use to send funds - The language code MoneyGram should render their UI’s content with -- The amount the user would like to withdraw / cash-out +- The amount the user would like to withdraw / cash-out in case it's a custodial wallet - 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. From d45f9d05bb7f78811a3ae68c7849eb6f56ea97a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 25 Jun 2024 18:24:15 -0300 Subject: [PATCH 18/23] Expand on transaction ids --- .../moneygram-access-integration-guide.mdx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 7f378aa1b..c0d68e130 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -371,12 +371,33 @@ let { stop, refresh } = watcher.watchAllTransactions({ // 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: +// external transaction ID like illustrated below. + +// "id" is the actual Anchor transaction id, all transactions should have it. const transaction = await anchor.sep24().getTransactionBy({ authToken, id: transactionId, - // stellarTransactionId, - // externalTransactionId, +}); + +// "stellarTransactionId" (aka "stellar_transaction_id" on the SEP spec) +// is the hash of the Stellar network transaction payment related to this +// Anchor transaction. +// E.g. we'll only have this transaction id field AFTER the wallet sends funds +// to Anchor on the withdrawal flow or receives funds from Anchor on the +// deposit flow. +const transaction = await anchor.sep24().getTransactionBy({ + authToken, + stellarTransactionId, +}); + +// "externalTransactionId" (aka "external_transaction_id" on the SEP spec) +// could refer to some ID of transaction on external network. +// E.g. for MoneyGram this is the "reference number" displayed to the user on +// the last step of MoneyGram's UI which the user should then use on a physical +// MoneyGram location to complete the cash out operation and pick-up the money. +const transaction = await anchor.sep24().getTransactionBy({ + authToken, + externalTransactionId, }); // It's also possible to fetch multiple transactions for an asset. From 295515b170ec57706bff7d542ac9e2a0398b0334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 25 Jun 2024 19:15:12 -0300 Subject: [PATCH 19/23] Query asset using the wallet sdk --- .../moneygram-access-integration-guide.mdx | 29 +++++++++++++++++-- docs/building-apps/wallet/sep24.mdx | 17 ++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index c0d68e130..90b9455c7 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -231,11 +231,23 @@ The following code can be used as a reference for implementing this logic yourse ```ts +import { IssuedAssetId } from "@stellar/typescript-wallet-sdk"; + +// First let's make sure Anchor supports the asset we want to withdraw. +const assetCode = "USDC"; +const info = await anchor.getInfo(); +const currency = info.currencies.find(({ code }) => code === assetCode); +if (!currency?.code || !currency?.issuer) { + throw new Error( + `Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`, + ); +} + // 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" + assetCode, lang: "en", // "lang" is optional, defaults to "en" if ommited extraFields: { // "amount" is optional for non-custodial wallets and mandatory for custodial wallets @@ -470,8 +482,19 @@ 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); +// Let's make sure Anchor supports the token we want to withdraw. +const assetCode = "USDC"; +const info = await anchor.getInfo(); +const currency = info.currencies.find(({ code }) => code === assetCode); +if (!currency?.code || !currency?.issuer) { + throw new Error( + `Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`, + ); +} + +// This creates the Stellar asset object which we'll need while creating the +// transfer withdrawal transaction below. +const asset = new IssuedAssetId(currency.code, currency.issuer); // This creates a transaction builder which we'll be using to assemble // our transfer withdrawal transaction as shown below. diff --git a/docs/building-apps/wallet/sep24.mdx b/docs/building-apps/wallet/sep24.mdx index d319c26fc..6f78f962b 100644 --- a/docs/building-apps/wallet/sep24.mdx +++ b/docs/building-apps/wallet/sep24.mdx @@ -70,7 +70,22 @@ val asset = info.currencies.first { it.code == "USDC" }.assetId ``` ```typescript -const asset = info.currencies.find(({ code }) => code === "USDC").assetId; +import { IssuedAssetId } from "@stellar/typescript-wallet-sdk"; + +const assetCode = "USDC"; + +const info = await anchor.getInfo(); + +const currency = info.currencies.find(({ code }) => code === assetCode); + +if(!currency?.code || !currency?.issuer) { + throw new Error(`Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`); +} + +const asset = new IssuedAssetId( + currency.code, + currency.issuer, +); ``` ```dart From f38f793939f4e79af9893d231d862d2686d24826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Tue, 25 Jun 2024 19:19:33 -0300 Subject: [PATCH 20/23] Small tweaks to keep the same code pattern between typescript and dart languages - Use 'asset' object on typescript instead of 'assetCode' - Use 'sep24' reference on typescript instead of duplicating 'anchor.sep24()' everywhere --- docs/building-apps/wallet/sep24.mdx | 117 ++++++++++++++-------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/docs/building-apps/wallet/sep24.mdx b/docs/building-apps/wallet/sep24.mdx index 6f78f962b..d65c23cae 100644 --- a/docs/building-apps/wallet/sep24.mdx +++ b/docs/building-apps/wallet/sep24.mdx @@ -26,11 +26,11 @@ val sep24 = anchor.sep24() ``` ```typescript -let sep24 = await anchor.sep24(); +const sep24 = await anchor.sep24(); ``` ```dart -var sep24 = await anchor.sep24(); +final sep24 = await anchor.sep24(); ``` @@ -61,7 +61,7 @@ final servicesInfo = await sep24.getServiceInfo(); Before getting started, make sure you have connected to the anchor and received an authentication token, as described in the [Stellar Authentication] wallet guide. We will use the `token` object in the examples below as the [SEP-10] authentication token, obtained earlier. -To initiate an operation, we need to know an asset. You may want to hard-code it, or get it dynamically from the anchor's info file, like shown above (for USDC): +To initiate an operation, we need to know an asset. You may want to hard-code it, or get it dynamically from the anchor's info file, like shown below (for USDC): @@ -78,18 +78,17 @@ const info = await anchor.getInfo(); const currency = info.currencies.find(({ code }) => code === assetCode); -if(!currency?.code || !currency?.issuer) { - throw new Error(`Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`); +if (!currency?.code || !currency?.issuer) { + throw new Error( + `Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`, + ); } -const asset = new IssuedAssetId( - currency.code, - currency.issuer, -); +const asset = new IssuedAssetId(currency.code, currency.issuer); ``` ```dart -var asset = info.currencies.firstWhere((it)=>it.code=='USDC').assetId; +final asset = info.currencies.firstWhere((it)=>it.code=='USDC').assetId; ``` @@ -111,14 +110,14 @@ val deposit = sep24.deposit(asset, token) ``` ```typescript -let deposit = await anchor.sep24().deposit({ - assetCode, +const deposit = await sep24.deposit({ + assetCode: asset.code, authToken, }); ``` ```dart -final deposit = sep24.deposit(asset, token) +final deposit = await sep24.deposit(asset, token); ``` @@ -135,8 +134,8 @@ val id = deposit.id ``` ```typescript -let url = deposit.url; -let id = deposit.id; +const url = deposit.url; +const id = deposit.id; ``` ```dart @@ -157,18 +156,18 @@ val id = withdrawal.id ``` ```typescript -let withdrawal = await anchor.sep24().withdraw({ - assetCode, +const withdrawal = await sep24.withdraw({ + assetCode: asset.code, authToken, }); -let url = withdrawal.url; -let id = withdrawal.id; +const url = withdrawal.url; +const id = withdrawal.id; ``` ```dart -final withdrawal = sep24.withdraw(asset, token) -final url = withdrawal.url -final id = withdrawal.id +final withdrawal = await sep24.withdraw(asset, token); +final url = withdrawal.url; +final id = withdrawal.id; ``` @@ -198,8 +197,8 @@ val deposit = sep24.deposit(asset, token, sep9) ``` ```typescript -let deposit = await anchor.sep24().deposit({ - assetCode, +const deposit = await sep24.deposit({ + assetCode: asset.code, authToken, extraFields: { email_address: "mail@example.com" }, }); @@ -233,17 +232,17 @@ import { Memo, MemoText } from "stellar-sdk"; const recipientAccount = "G..."; const depositDifferentAccount = async (): Promise => { - return anchor.sep24().deposit({ + return await sep24.deposit({ destinationAccount: recipientAccount, destinationMemo: new Memo(MemoText, "some memo"), - assetCode, + assetCode: asset.code, authToken, }); }; ``` ```dart -const recipientAccount = "G..."; +final recipientAccount = "G..."; final deposit = await sep24.deposit(asset, token, destinationAccount: recipientAccount, destinationMemo: "my memo", destinationMemoType: MemoType.text); ``` @@ -261,16 +260,16 @@ val withdrawal = sep24.withdraw(asset, token, withdrawalAccount = originAccount) ```typescript const originAccount = "G..."; -const withdrawal = await anchor.sep24().withdraw({ +const withdrawal = await sep24.withdraw({ withdrawalAccount: originAccount, - assetCode, + assetCode: asset.code, authToken, }); ``` ```dart -const originAccount = "G..."; -final withdrawal = sep24.withdraw(asset, token, withdrawalAccount: originAccount); +final originAccount = "G..."; +final withdrawal = await sep24.withdraw(asset, token, withdrawalAccount: originAccount); ``` @@ -293,11 +292,11 @@ val result = watcher.watchOneTransaction(token, "transaction id") ``` ```typescript -let watcher = anchor.sep24().watcher(); +const watcher = sep24.watcher(); -let { stop, refresh } = watcher.watchOneTransaction({ +const { stop, refresh } = watcher.watchOneTransaction({ authToken, - assetCode, + assetCode: asset.code, id: successfulTransaction.id, onMessage, onSuccess, @@ -322,19 +321,19 @@ val result = watcher.watchAsset(getAuthToken(), asset) ``` ```typescript -let watcher = anchor.sep24().watcher(); +const watcher = sep24.watcher(); -let { stop, refresh } = watcher.watchAllTransactions({ +const { stop, refresh } = watcher.watchAllTransactions({ authToken, - assetCode, + assetCode: asset.code, onMessage, onError, }); ``` ```dart -val watcher = sep24.watcher() -val result = watcher.watchAsset(token, asset) +final watcher = sep24.watcher(); +final result = watcher.watchAsset(token, asset); ``` @@ -352,14 +351,14 @@ val transaction = sep24.getTransactionBy(token, id = "transaction id") ``` ```typescript -const transaction = await anchor.sep24().getTransactionBy({ +const transaction = await sep24.getTransactionBy({ authToken, id: transactionId, }); ``` ```dart -var transaction = await sep24.getTransaction("transaction id", token); +final transaction = await sep24.getTransaction("transaction id", token); ``` @@ -373,14 +372,14 @@ val transactions = sep24.getTransactionsForAsset(asset, token) ``` ```typescript -const transactions = await anchor.sep24().getTransactionsForAsset({ +const transactions = await sep24.getTransactionsForAsset({ authToken, - assetCode, + assetCode: asset.code, }); ``` ```dart -var transactions = sep24.getTransactionsForAsset(asset, token); +final transactions = await sep24.getTransactionsForAsset(asset, token); ``` @@ -398,14 +397,14 @@ val withdrawal = sep24.withdraw(asset, authToken = token) ``` ```typescript -let withdrawal = await anchor.sep24().withdraw({ - assetCode, +const withdrawal = await sep24.withdraw({ + assetCode: asset.code, authToken, }); ``` ```dart -final withdrawal = await sep24.withdraw(asset, token) +final withdrawal = await sep24.withdraw(asset, token); ``` @@ -420,7 +419,7 @@ val url = withdrawal.url ``` ```typescript -let url = withdrawal.url; +const url = withdrawal.url; // open the url ``` @@ -449,17 +448,17 @@ do { ``` ```typescript -let watcher = anchor.sep24().watcher(); +const watcher = sep24.watcher(); -let onMessage = (transaction) => { +const onMessage = (transaction) => { if (transaction.status === "pending_user_transfer_start") { // begin transfer code } }; -let { refresh, stop } = watcher.watchOneTransaction({ +const { refresh, stop } = watcher.watchOneTransaction({ authToken, - assetCode, + assetCode: asset.code, id: successfulTransaction.id, onMessage, onSuccess, @@ -468,7 +467,7 @@ let { refresh, stop } = watcher.watchOneTransaction({ ``` ```dart -final withdrawalWatcher = sep24.watcher().watchOneTransaction(token, withdrawal.id) +final withdrawalWatcher = sep24.watcher().watchOneTransaction(token, withdrawal.id); withdrawalWatcher.controller.stream.listen( (event) { if (event is StatusChange && TransactionStatus.pendingUserTransferStart == event.status) { @@ -571,21 +570,21 @@ if (terminalStatus != TransactionStatus.COMPLETED) { ``` ```typescript -let watcher = anchor.sep24().watcher(); +const watcher = sep24.watcher(); -let onSuccess = (transaction) => { +const onSuccess = (transaction) => { // transaction came back as completed / refunded / expired console.log("Transaction is completed"); }; -let onError = (transaction) => { +const onError = (transaction) => { // runtime error, or the transaction comes back as // no_market / too_small / too_large / error }; -let { refresh, stop } = watcher.watchOneTransaction({ +const { refresh, stop } = watcher.watchOneTransaction({ authToken, - assetCode, + assetCode: asset.code, id: successfulTransaction.id, onMessage, onSuccess, @@ -594,7 +593,7 @@ let { refresh, stop } = watcher.watchOneTransaction({ ``` ```dart -final watcher = sep24.watcher().watchOneTransaction(token, withdrawal.id) +final watcher = sep24.watcher().watchOneTransaction(token, withdrawal.id); watcher.controller.stream.listen( (event) { if (event is StatusChange && event.status.isTerminal()) { From 0f45a2dcd0cde262d3489b89fd64785877ef01cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Wed, 26 Jun 2024 15:55:05 -0300 Subject: [PATCH 21/23] Expand on error handling --- .../moneygram-access-integration-guide.mdx | 83 ++++++++++++++++++- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 90b9455c7..ae0f43a28 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -476,6 +476,7 @@ Please note that the `Python` code is highly simplified. It does not use timebou ```ts import { Wallet, IssuedAssetId } from "@stellar/typescript-wallet-sdk"; +import { Horizon } from "@stellar/stellar-sdk"; const wallet = Wallet.TestNet(); @@ -521,11 +522,85 @@ onMessage: (transaction) => { 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); + // function handles '504' status codes (timeout) by keep retrying it until + // submission succeeds or we get a different error. + try { + const response = await stellar.submitTransaction(transferTransaction); + console.log("Stellar-generated transaction ID: ", response.id); + } catch (error) { + /* + In case it's not a 504 (timeout) error, the application could try some + resolution strategy based on the error kind. + + On Stellar docs you can find a whole lot of info regarding error handling: + https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling + + And status/result codes: + https://developers.stellar.org/docs/data/horizon/api-reference/errors + */ + + // Let's illustrate here how we could handle an 'invalid sequence number' error. + + // We can access all possible result codes through Horizon's API. + const sdkResultCodes = Horizon.HorizonApi.TransactionFailedResultCodes; + + // We can access error's response data to check for useful error details. + const errorData = error.response?.data; + /* + Sample of errorData object returned by the Wallet SDK: + { + type: 'https://stellar.org/horizon-errors/transaction_failed', + title: 'Transaction Failed', + status: 400, + detail: 'The transaction failed when submitted to the stellar network. + The `extras.result_codes` field on this response contains further details. + Descriptions of each code can be found at: + https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/transaction-failed/', + extras: { + envelope_xdr: 'AAAAAgAAAADBjF7n9gfByOwlnyaJH...k4BRagf/////////8AAAAAAAAAAA==', + result_codes: { transaction: 'tx_bad_seq' }, + result_xdr: 'AAAAAAAAAGT////6AAAAAA==' + } + } + */ + + /* + Example scenario: invalid sequence numbers. + + These errors typically occur when you have an outdated view of an account. + This could be because multiple devices are using this account, you have + concurrent submissions happening, or other reasons. The solution is relatively + simple: retrieve the account details and try again with an updated sequence number. + */ + if ( + errorData?.status == 400 && + errorData?.extras?.result_codes?.transaction === + sdkResultCodes.TX_BAD_SEQ + ) { + // Creating a new transaction builder means retrieving an updated sequence number. + const txBuilder2 = await stellar.transaction({ + sourceAddress: FUNDS_STELLAR_KEYPAIR, + baseFee: 10000, + timebounds: 180, + }); + + // ... + + // Repeat all the steps until submitting the transaction again. + + // ... + + const response2 = await stellar.submitTransaction(transferTransaction); + console.log( + "Stellar-generated transaction ID on retry: ", + response2.id, + ); - console.log("Stellar-generated transaction ID: ", response.id); + // The application should take care to not resubmit the same transaction + // blindly with an updated sequence number as it could result in more than + // one payment being made when only one was intended. + } + } } }; ``` From 01cdd5f392794d9da46c22ad7be1a47a3e7449d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Wed, 26 Jun 2024 17:57:08 -0300 Subject: [PATCH 22/23] Fix tutorial link --- docs/building-apps/moneygram-access-integration-guide.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index ae0f43a28..23c618421 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -532,7 +532,7 @@ onMessage: (transaction) => { In case it's not a 504 (timeout) error, the application could try some resolution strategy based on the error kind. - On Stellar docs you can find a whole lot of info regarding error handling: + On Stellar docs you can find a page dedicated to handling errors: https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling And status/result codes: @@ -780,7 +780,7 @@ print( [trustline]: https://developers.stellar.org/api/resources/operations/object/change-trust/ [sell offer]: https://developers.stellar.org/api/resources/operations/object/sell-offer/ [postmessage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage -[submitting transactions and handling errors gracefully]: https://developers.stellar.org/docs/tutorials/handling-errors/ +[submitting transactions and handling errors gracefully]: https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling/ [path payments]: https://developers.stellar.org/api/resources/operations/object/path-payment-strict-receive/ [claimable balances]: https://developers.stellar.org/api/resources/operations/object/create-claimable-balance/ [moneygram screening questionnaire]: https://stellarquestionnaire.typeform.com/to/RD1a71wQ From 7d77e8eb576fedae80ef7157eee70ee0bcea626f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ca=CC=81ssio=20Marcos=20Goulart?= Date: Wed, 26 Jun 2024 17:59:16 -0300 Subject: [PATCH 23/23] Update comment --- docs/building-apps/moneygram-access-integration-guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/building-apps/moneygram-access-integration-guide.mdx b/docs/building-apps/moneygram-access-integration-guide.mdx index 23c618421..3b58b0597 100644 --- a/docs/building-apps/moneygram-access-integration-guide.mdx +++ b/docs/building-apps/moneygram-access-integration-guide.mdx @@ -532,7 +532,7 @@ onMessage: (transaction) => { In case it's not a 504 (timeout) error, the application could try some resolution strategy based on the error kind. - On Stellar docs you can find a page dedicated to handling errors: + On Stellar docs you can find a page dedicated to error handling: https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling And status/result codes: