diff --git a/README.md b/README.md
index ef153dc4..d75fe789 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,14 @@ npm run build:docs
This writes `backend/public/openapi.json`.
+Verify the XLM -> USDC path-payment flow on Stellar testnet without a wallet:
+```bash
+cd backend
+npm run verify:path-payment:testnet
+```
+
+This script creates disposable testnet accounts, issues a temporary USDC asset, places a DEX offer, discovers the best XLM -> USDC path, submits a live `path_payment_strict_receive`, and prints the transaction hash plus the recipient's received USDC amount.
+
## API Endpoints
- `GET /health`
@@ -157,4 +165,3 @@ The project currently has a comprehensive roadmap of **100+ active issues** cove
We are actively seeking contributors! See the [GitHub Issues](https://github.com/emdevelopa/Stellar_Payment_API/issues) to get started. Each issue is tagged with complexity (`complexity:trivial`, `complexity:medium`, `complexity:high`) and category.
If you are new, look for issues labeled `good first issue`.
-
diff --git a/backend/package.json b/backend/package.json
index a5b95b97..c7a3baeb 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -7,6 +7,7 @@
"dev": "nodemon src/server.js",
"start": "node src/server.js",
"build:docs": "node scripts/generate-swagger.js",
+ "verify:path-payment:testnet": "node scripts/verify-path-payment-testnet.js",
"test": "vitest run",
"test:integration": "node --experimental-vm-modules node_modules/.bin/jest jest.config.js tests/integration",
"purge:webhook-logs": "node scripts/purge-webhook-logs.js",
diff --git a/backend/scripts/verify-path-payment-testnet.js b/backend/scripts/verify-path-payment-testnet.js
new file mode 100644
index 00000000..4f617a3c
--- /dev/null
+++ b/backend/scripts/verify-path-payment-testnet.js
@@ -0,0 +1,221 @@
+import "dotenv/config";
+import * as StellarSdk from "stellar-sdk";
+
+const NETWORK = (process.env.STELLAR_NETWORK || "testnet").toLowerCase();
+const HORIZON_URL =
+ process.env.STELLAR_HORIZON_URL ||
+ (NETWORK === "public"
+ ? "https://horizon.stellar.org"
+ : "https://horizon-testnet.stellar.org");
+const NETWORK_PASSPHRASE =
+ NETWORK === "public"
+ ? StellarSdk.Networks.PUBLIC
+ : StellarSdk.Networks.TESTNET;
+const FRIEND_BOT_URL =
+ process.env.STELLAR_FRIENDBOT_URL || "https://friendbot.stellar.org";
+
+if (NETWORK !== "testnet") {
+ console.error("This verifier only supports Stellar testnet.");
+ process.exit(1);
+}
+
+const server = new StellarSdk.Horizon.Server(HORIZON_URL);
+
+function assertSuccess(result, label) {
+ if (!result.successful) {
+ throw new Error(`${label} failed: transaction was not successful`);
+ }
+}
+
+async function fundAccount(publicKey) {
+ const response = await fetch(`${FRIEND_BOT_URL}?addr=${publicKey}`);
+ if (!response.ok) {
+ const body = await response.text();
+ throw new Error(`Friendbot funding failed for ${publicKey}: ${body}`);
+ }
+ return response.json();
+}
+
+async function submitTransaction(sourceKeypair, operationBuilder, label) {
+ const sourceAccount = await server.loadAccount(sourceKeypair.publicKey());
+ const txBuilder = new StellarSdk.TransactionBuilder(sourceAccount, {
+ fee: StellarSdk.BASE_FEE,
+ networkPassphrase: NETWORK_PASSPHRASE,
+ });
+
+ operationBuilder(txBuilder);
+
+ const transaction = txBuilder.setTimeout(60).build();
+ transaction.sign(sourceKeypair);
+
+ const result = await server.submitTransaction(transaction);
+ assertSuccess(result, label);
+ return result;
+}
+
+async function getAssetBalance(accountId, asset) {
+ const account = await server.loadAccount(accountId);
+ const balanceLine = account.balances.find((balance) => {
+ if (asset.isNative()) {
+ return balance.asset_type === "native";
+ }
+
+ return (
+ balance.asset_code === asset.getCode() &&
+ balance.asset_issuer === asset.getIssuer()
+ );
+ });
+
+ return balanceLine ? Number(balanceLine.balance) : 0;
+}
+
+function formatAmount(value) {
+ return Number(value).toFixed(7);
+}
+
+async function main() {
+ console.log(`Using Horizon: ${HORIZON_URL}`);
+
+ const issuer = StellarSdk.Keypair.random();
+ const marketMaker = StellarSdk.Keypair.random();
+ const sender = StellarSdk.Keypair.random();
+ const recipient = StellarSdk.Keypair.random();
+
+ console.log("Funding issuer, market maker, sender, and recipient...");
+ await Promise.all(
+ [issuer, marketMaker, sender, recipient].map((keypair) =>
+ fundAccount(keypair.publicKey()),
+ ),
+ );
+
+ const usdcAsset = new StellarSdk.Asset("USDC", issuer.publicKey());
+ const invoiceAmount = "25.0000000";
+ const offerAmount = "500.0000000";
+ const offerPrice = "2.0000000";
+
+ console.log("Creating trustlines for USDC...");
+ await submitTransaction(
+ marketMaker,
+ (txBuilder) => {
+ txBuilder.addOperation(
+ StellarSdk.Operation.changeTrust({
+ asset: usdcAsset,
+ }),
+ );
+ },
+ "market-maker trustline",
+ );
+
+ await submitTransaction(
+ recipient,
+ (txBuilder) => {
+ txBuilder.addOperation(
+ StellarSdk.Operation.changeTrust({
+ asset: usdcAsset,
+ }),
+ );
+ },
+ "recipient trustline",
+ );
+
+ console.log("Issuing USDC to the market maker...");
+ await submitTransaction(
+ issuer,
+ (txBuilder) => {
+ txBuilder.addOperation(
+ StellarSdk.Operation.payment({
+ destination: marketMaker.publicKey(),
+ asset: usdcAsset,
+ amount: offerAmount,
+ }),
+ );
+ },
+ "issue USDC",
+ );
+
+ console.log("Placing a DEX sell offer for USDC/XLM...");
+ await submitTransaction(
+ marketMaker,
+ (txBuilder) => {
+ txBuilder.addOperation(
+ StellarSdk.Operation.manageSellOffer({
+ selling: usdcAsset,
+ buying: StellarSdk.Asset.native(),
+ amount: offerAmount,
+ price: offerPrice,
+ offerId: "0",
+ }),
+ );
+ },
+ "create sell offer",
+ );
+
+ console.log("Discovering strict-receive path from XLM to USDC...");
+ const paths = await server
+ .strictReceivePaths([StellarSdk.Asset.native()], usdcAsset, invoiceAmount)
+ .call();
+
+ if (!paths.records?.length) {
+ throw new Error("No strict-receive path found for XLM -> USDC");
+ }
+
+ const bestPath = paths.records[0];
+ const sourceAmount = Number(bestPath.source_amount);
+ const sendMax = formatAmount(sourceAmount * 1.01);
+ const path = bestPath.path.map((asset) =>
+ asset.asset_type === "native"
+ ? StellarSdk.Asset.native()
+ : new StellarSdk.Asset(asset.asset_code, asset.asset_issuer),
+ );
+
+ const recipientBalanceBefore = await getAssetBalance(
+ recipient.publicKey(),
+ usdcAsset,
+ );
+
+ console.log(
+ `Submitting path payment. Source amount estimate: ${bestPath.source_amount} XLM, send max: ${sendMax} XLM`,
+ );
+
+ const paymentResult = await submitTransaction(
+ sender,
+ (txBuilder) => {
+ txBuilder
+ .addOperation(
+ StellarSdk.Operation.pathPaymentStrictReceive({
+ sendAsset: StellarSdk.Asset.native(),
+ sendMax,
+ destination: recipient.publicKey(),
+ destAsset: usdcAsset,
+ destAmount: invoiceAmount,
+ path,
+ }),
+ )
+ .addMemo(StellarSdk.Memo.text("path-payment-check"));
+ },
+ "path payment",
+ );
+
+ const recipientBalanceAfter = await getAssetBalance(
+ recipient.publicKey(),
+ usdcAsset,
+ );
+ const receivedDelta = formatAmount(recipientBalanceAfter - recipientBalanceBefore);
+
+ if (receivedDelta !== invoiceAmount) {
+ throw new Error(
+ `Recipient received ${receivedDelta} USDC instead of expected ${invoiceAmount} USDC`,
+ );
+ }
+
+ console.log("Path payment verification succeeded.");
+ console.log(`Transaction hash: ${paymentResult.hash}`);
+ console.log(`Recipient received: ${receivedDelta} USDC`);
+ console.log(`Recipient account: ${recipient.publicKey()}`);
+}
+
+main().catch((error) => {
+ console.error("Path payment verification failed.");
+ console.error(error instanceof Error ? error.message : error);
+ process.exit(1);
+});
diff --git a/backend/src/routes/payments-path-quote.test.js b/backend/src/routes/payments-path-quote.test.js
new file mode 100644
index 00000000..6aa1d037
--- /dev/null
+++ b/backend/src/routes/payments-path-quote.test.js
@@ -0,0 +1,161 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import createPaymentsRouter from "./payments.js";
+
+const { findStrictReceivePaths, supabaseFrom } = vi.hoisted(() => ({
+ findStrictReceivePaths: vi.fn(),
+ supabaseFrom: vi.fn(),
+}));
+
+vi.mock("../lib/stellar.js", () => ({
+ findStrictReceivePaths,
+ findMatchingPayment: vi.fn(),
+ createRefundTransaction: vi.fn(),
+}));
+
+vi.mock("../lib/supabase.js", () => ({
+ supabase: {
+ from: supabaseFrom,
+ },
+}));
+
+vi.mock("../lib/redis.js", () => ({
+ connectRedisClient: vi.fn(() => Promise.resolve({})),
+ getCachedPayment: vi.fn(),
+ setCachedPayment: vi.fn(),
+ invalidatePaymentCache: vi.fn(),
+}));
+
+function createSupabaseSelectMock(payment) {
+ const chain = {
+ select: vi.fn(() => chain),
+ eq: vi.fn(() => chain),
+ is: vi.fn(() => chain),
+ maybeSingle: vi.fn(async () => ({ data: payment, error: null })),
+ };
+
+ return chain;
+}
+
+function getPathPaymentQuoteHandler() {
+ const router = createPaymentsRouter();
+ const layer = router.stack.find(
+ (entry) => entry.route?.path === "/path-payment-quote/:id",
+ );
+
+ return layer.route.stack.at(-1).handle;
+}
+
+function createMockResponse() {
+ return {
+ statusCode: 200,
+ body: undefined,
+ status(code) {
+ this.statusCode = code;
+ return this;
+ },
+ json(payload) {
+ this.body = payload;
+ return this;
+ },
+ };
+}
+
+describe("GET /api/path-payment-quote/:id", () => {
+ const paymentId = "9f927a2c-02d4-4f76-914c-62cf44d9525e";
+ const sourceAccount =
+ "GBRPYHIL2C7Q7PGLUKSTPIY2KPJ7QMZ4ZWJHQ6GUSIW2LQAHOMK5N7BI";
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("returns an XLM quote for a USDC invoice", async () => {
+ supabaseFrom.mockReturnValue(
+ createSupabaseSelectMock({
+ id: paymentId,
+ amount: 25,
+ asset: "USDC",
+ asset_issuer: "GDQOE23W4QK6WQ4R3BVCUO3PRA4VJ7A3M7MRWWX4V67WJYQ7QXKJQ4KJ",
+ recipient: "GB6REFUNDTESTRECIPIENTQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+ status: "pending",
+ }),
+ );
+
+ findStrictReceivePaths.mockResolvedValue({
+ source_amount: "60.1250000",
+ source_asset_code: "XLM",
+ source_asset_issuer: null,
+ destination_amount: "25",
+ path: [],
+ });
+
+ const handler = getPathPaymentQuoteHandler();
+ const res = createMockResponse();
+
+ await handler(
+ {
+ params: { id: paymentId },
+ query: {
+ source_asset: "XLM",
+ source_account: sourceAccount,
+ },
+ merchant: { id: "merchant-1" },
+ },
+ res,
+ vi.fn(),
+ );
+
+ expect(res.statusCode).toBe(200);
+ expect(res.body).toMatchObject({
+ source_asset: "XLM",
+ source_amount: "60.1250000",
+ send_max: "60.7262500",
+ destination_asset: "USDC",
+ destination_asset_issuer: "GDQOE23W4QK6WQ4R3BVCUO3PRA4VJ7A3M7MRWWX4V67WJYQ7QXKJQ4KJ",
+ destination_amount: "25",
+ path: [],
+ slippage: 0.01,
+ });
+ expect(findStrictReceivePaths).toHaveBeenCalledWith({
+ sourceAccount,
+ destAssetCode: "USDC",
+ destAssetIssuer: "GDQOE23W4QK6WQ4R3BVCUO3PRA4VJ7A3M7MRWWX4V67WJYQ7QXKJQ4KJ",
+ destAmount: "25",
+ sourceAssetCode: "XLM",
+ sourceAssetIssuer: null,
+ });
+ });
+
+ it("rejects a quote request when the source asset already matches the invoice asset", async () => {
+ supabaseFrom.mockReturnValue(
+ createSupabaseSelectMock({
+ id: paymentId,
+ amount: 10,
+ asset: "XLM",
+ asset_issuer: null,
+ recipient: "GB6REFUNDTESTRECIPIENTQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
+ status: "pending",
+ }),
+ );
+
+ const handler = getPathPaymentQuoteHandler();
+ const res = createMockResponse();
+
+ await handler(
+ {
+ params: { id: paymentId },
+ query: {
+ source_asset: "XLM",
+ source_account: sourceAccount,
+ },
+ merchant: { id: "merchant-1" },
+ },
+ res,
+ vi.fn(),
+ );
+
+ expect(res.statusCode).toBe(400);
+ expect(res.body.error).toContain("Use a direct payment");
+ expect(findStrictReceivePaths).not.toHaveBeenCalled();
+ });
+});
diff --git a/backend/src/routes/payments.js b/backend/src/routes/payments.js
index 224f31f2..33aa79aa 100644
--- a/backend/src/routes/payments.js
+++ b/backend/src/routes/payments.js
@@ -1081,12 +1081,29 @@ function createPaymentsRouter({
const { data, error } = await query
.eq("id", req.params.id)
+ .is("deleted_at", null)
.maybeSingle();
+ if (error) {
+ error.status = 500;
+ throw error;
+ }
+
if (!data) {
return res.status(404).json({ error: "Payment not found" });
}
+ const sameAsset =
+ sourceAsset.toUpperCase() === data.asset.toUpperCase() &&
+ sourceAssetIssuer === (data.asset_issuer || null);
+
+ if (sameAsset) {
+ return res.status(400).json({
+ error:
+ "Source asset is the same as destination asset. Use a direct payment.",
+ });
+ }
+
const quote = await findStrictReceivePaths({
sourceAccount,
destAssetCode: data.asset,
@@ -1332,4 +1349,3 @@ function createPaymentsRouter({
}
export default createPaymentsRouter;
-
diff --git a/backend/src/verification/frontend-path-payment-transaction.test.js b/backend/src/verification/frontend-path-payment-transaction.test.js
new file mode 100644
index 00000000..fbc32eee
--- /dev/null
+++ b/backend/src/verification/frontend-path-payment-transaction.test.js
@@ -0,0 +1,128 @@
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const {
+ frontendStellarSdkPath,
+ nativeAsset,
+ textMemo,
+ pathPaymentStrictReceiveOperation,
+ transactionBuilderFactory,
+ loadAccount,
+} = vi.hoisted(() => ({
+ frontendStellarSdkPath:
+ "/Users/marvellous/Desktop/Stellar_Payment_API/frontend/node_modules/stellar-sdk/lib/index.js",
+ nativeAsset: { type: "native" },
+ textMemo: vi.fn(),
+ pathPaymentStrictReceiveOperation: vi.fn(),
+ transactionBuilderFactory: vi.fn(),
+ loadAccount: vi.fn(),
+}));
+
+vi.mock(frontendStellarSdkPath, () => {
+ class MockAsset {
+ constructor(code, issuer) {
+ this.code = code;
+ this.issuer = issuer;
+ this.type = "credit";
+ }
+
+ static native() {
+ return nativeAsset;
+ }
+ }
+
+ return {
+ Asset: MockAsset,
+ BASE_FEE: "100",
+ Memo: {
+ text: textMemo,
+ id: vi.fn(),
+ hash: vi.fn(),
+ return: vi.fn(),
+ },
+ Horizon: {
+ Server: vi.fn(() => ({
+ loadAccount,
+ })),
+ },
+ Operation: {
+ payment: vi.fn(),
+ pathPaymentStrictReceive: pathPaymentStrictReceiveOperation,
+ },
+ TransactionBuilder: transactionBuilderFactory,
+ };
+});
+
+describe("frontend Stellar path payment builder", () => {
+ beforeEach(() => {
+ vi.resetModules();
+ vi.clearAllMocks();
+ });
+
+ it("builds a strict-receive path payment with memo for an XLM to USDC invoice", async () => {
+ const addOperation = vi.fn();
+ const addMemo = vi.fn();
+ const setTimeout = vi.fn();
+ const build = vi.fn();
+
+ addOperation.mockReturnThis();
+ addMemo.mockReturnThis();
+ setTimeout.mockReturnThis();
+ build.mockReturnValue({
+ toXDR: () => "AAAA-path-payment-xdr",
+ });
+
+ transactionBuilderFactory.mockImplementation(() => ({
+ addOperation,
+ addMemo,
+ setTimeout,
+ build,
+ }));
+
+ loadAccount.mockResolvedValue({
+ accountId: () => "GTESTSOURCEACCOUNT",
+ sequence: "1234567890",
+ });
+
+ textMemo.mockImplementation((value) => ({ type: "text", value }));
+ pathPaymentStrictReceiveOperation.mockImplementation((params) => ({
+ type: "path_payment_strict_receive",
+ ...params,
+ }));
+
+ const { buildPathPaymentTransaction } = await import(
+ "../../../frontend/src/lib/stellar.ts"
+ );
+
+ const xdr = await buildPathPaymentTransaction({
+ sourcePublicKey: "GTESTSOURCEACCOUNT",
+ destinationPublicKey: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
+ sendMax: "60.7262500",
+ sendAssetCode: "XLM",
+ sendAssetIssuer: null,
+ destAmount: "25.0000000",
+ destAssetCode: "USDC",
+ destAssetIssuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
+ path: [],
+ memo: "invoice-123",
+ memoType: "text",
+ horizonUrl: "https://horizon-testnet.stellar.org",
+ networkPassphrase: "Test SDF Network ; September 2015",
+ });
+
+ expect(pathPaymentStrictReceiveOperation).toHaveBeenCalledWith({
+ sendAsset: nativeAsset,
+ sendMax: "60.7262500",
+ destination: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
+ destAsset: expect.objectContaining({
+ type: "credit",
+ code: "USDC",
+ issuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
+ }),
+ destAmount: "25.0000000",
+ path: [],
+ });
+ expect(textMemo).toHaveBeenCalledWith("invoice-123");
+ expect(addMemo).toHaveBeenCalledWith({ type: "text", value: "invoice-123" });
+ expect(xdr).toBe("AAAA-path-payment-xdr");
+ });
+});
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index 49987e2b..4e5e0e27 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -209,6 +209,11 @@
"paymentSent": "Payment sent!",
"paymentFailed": "Payment failed. Please try again.",
"connectedVia": "Connected via {provider}",
+ "approximateCostLabel": "Approximate cost in XLM",
+ "approximateCostHelp": "Estimated amount to deliver {amount} {asset} to the merchant.",
+ "slippageBuffer": "{percent}% safety buffer included. Max send: {sendMax} {asset}",
+ "pathPaymentTogglePrefix": "Pay with",
+ "pathPaymentToggleSuffix": "instead",
"processing": "Processing...",
"payWith": "Pay with {provider}",
"payWithFallback": "Pay with Wallet",
diff --git a/frontend/messages/es.json b/frontend/messages/es.json
index ff30f540..4e5a2add 100644
--- a/frontend/messages/es.json
+++ b/frontend/messages/es.json
@@ -209,6 +209,11 @@
"paymentSent": "Pago enviado",
"paymentFailed": "El pago fallo. Intentalo de nuevo.",
"connectedVia": "Conectado mediante {provider}",
+ "approximateCostLabel": "Costo aproximado en XLM",
+ "approximateCostHelp": "Monto estimado para entregar {amount} {asset} al comercio.",
+ "slippageBuffer": "Incluye un margen de seguridad del {percent}%. Envio maximo: {sendMax} {asset}",
+ "pathPaymentTogglePrefix": "Pagar con",
+ "pathPaymentToggleSuffix": "en su lugar",
"processing": "Procesando...",
"payWith": "Pagar con {provider}",
"payWithFallback": "Pagar con billetera",
diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json
index e0bb6350..97812ecb 100644
--- a/frontend/messages/pt.json
+++ b/frontend/messages/pt.json
@@ -209,6 +209,11 @@
"paymentSent": "Pagamento enviado",
"paymentFailed": "O pagamento falhou. Tente novamente.",
"connectedVia": "Conectado via {provider}",
+ "approximateCostLabel": "Custo aproximado em XLM",
+ "approximateCostHelp": "Valor estimado para entregar {amount} {asset} ao lojista.",
+ "slippageBuffer": "Inclui margem de seguranca de {percent}%. Envio maximo: {sendMax} {asset}",
+ "pathPaymentTogglePrefix": "Pagar com",
+ "pathPaymentToggleSuffix": "em vez disso",
"processing": "Processando...",
"payWith": "Pagar com {provider}",
"payWithFallback": "Pagar com carteira",
diff --git a/frontend/src/app/(public)/pay/[id]/page.tsx b/frontend/src/app/(public)/pay/[id]/page.tsx
index f5b1dae8..d2de3b5d 100644
--- a/frontend/src/app/(public)/pay/[id]/page.tsx
+++ b/frontend/src/app/(public)/pay/[id]/page.tsx
@@ -62,6 +62,18 @@ interface PaymentDetails {
branding_config?: BrandingConfig | null;
}
+interface PathQuote {
+ source_asset: string;
+ source_asset_issuer: string | null;
+ source_amount: string;
+ send_max: string;
+ destination_asset: string;
+ destination_asset_issuer: string | null;
+ destination_amount: string;
+ path: Array<{ asset_code: string; asset_issuer: string | null }>;
+ slippage: number;
+}
+
// ─── Branding defaults ───────────────────────────────────────────────────────
const DEFAULT_CHECKOUT_THEME: Required<
@@ -330,16 +342,7 @@ export default function PaymentPage() {
// Path payment state
const [usePathPayment, setUsePathPayment] = useState(false);
- const [pathQuote, setPathQuote] = useState<{
- source_asset: string;
- source_asset_issuer: string | null;
- source_amount: string;
- send_max: string;
- destination_asset: string;
- destination_amount: string;
- path: Array<{ asset_code: string; asset_issuer: string | null }>;
- slippage: number;
- } | null>(null);
+ const [pathQuote, setPathQuote] = useState
+ {t("approximateCostLabel")} +
++ {Number(pathQuote.source_amount).toLocaleString( + locale, + { + minimumFractionDigits: 0, + maximumFractionDigits: 7, + } + )}{" "} + {pathQuote.source_asset} +
++ {t("approximateCostHelp", { + amount: pathQuote.destination_amount, + asset: pathQuote.destination_asset, + })} +
++ {t("slippageBuffer", { + percent: Math.round(pathQuote.slippage * 100), + sendMax: pathQuote.send_max, + asset: pathQuote.source_asset, + })} +
+