Skip to content

Commit

Permalink
✨Add horizon swaps handling in useSwapCallback
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPoblete committed Jun 19, 2024
1 parent 87939dc commit 5d9d3b4
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 72 deletions.
88 changes: 88 additions & 0 deletions src/helpers/horizon/createHorizonTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { SorobanContextType, useSorobanReact } from "@soroban-react/core";
import { InterfaceTrade, TradeType } from "state/routing/types";
import {Asset, TransactionBuilder, Operation, BASE_FEE} from "@stellar/stellar-sdk";
import { getAmount } from "./getHorizonPath";

export const createStellarPathPayment = async (trade: InterfaceTrade, sorobanContext: SorobanContextType) => {
const {address, activeConnector, serverHorizon, activeChain} = sorobanContext;
console.log(trade)
if(trade.tradeType == TradeType.EXACT_INPUT){
const amount = getAmount(trade.inputAmount?.value!);
const sourceAsset = new Asset(trade.inputAmount?.currency.code!, trade.inputAmount?.currency.issuer)
const destinationAsset = new Asset(trade.outputAmount?.currency.code!, trade.outputAmount?.currency.issuer)
const account = await serverHorizon?.loadAccount(address!);
const path = trade.path?.map((asset) => {
const assetParts = asset.split(":")
if(assetParts.length == 1 && assetParts[0] == "native"){
return Asset.native()
}
return new Asset(assetParts[0], assetParts[1])
})
if(!account){
throw new Error("Account not found")
}
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: activeChain?.networkPassphrase
}).addOperation(Operation.pathPaymentStrictSend({
sendAsset: sourceAsset,
sendAmount: amount!,
destination: address!,
destAsset: destinationAsset,
destMin: amount!,
path: path
})).setTimeout(180).build();
const transactionXDR = transaction.toXDR();
const signedTransaction = await activeConnector?.signTransaction(transactionXDR, {
networkPassphrase: activeChain?.networkPassphrase,
});
if(!signedTransaction){
throw new Error("Couldn't sign transaction");
}
const transactionToSubmit = TransactionBuilder.fromXDR(
signedTransaction!,
activeChain?.networkPassphrase ?? '',
);
const transactionResult = await serverHorizon?.submitTransaction(transactionToSubmit);
return transactionResult;
}
if(trade.tradeType == TradeType.EXACT_OUTPUT){
const amount = getAmount(trade.outputAmount?.value!);
const sourceAsset = new Asset(trade.inputAmount?.currency.code!, trade.inputAmount?.currency.issuer)
const destinationAsset = new Asset(trade.outputAmount?.currency.code!, trade.outputAmount?.currency.issuer)
const account = await serverHorizon?.loadAccount(address!);
const path = trade.path?.map((asset) => {
const assetParts = asset.split(":")
if(assetParts.length == 1 && assetParts[0] == "native"){
return Asset.native()
}
return new Asset(assetParts[0], assetParts[1])
})
if(!account){
throw new Error("Account not found")
}
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: activeChain?.networkPassphrase
}).addOperation(Operation.pathPaymentStrictReceive({
sendAsset: sourceAsset,
sendMax: amount!,
destination: address!,
destAsset: destinationAsset,
destAmount: amount!,
})).setTimeout(180).build();
const transactionXDR = transaction.toXDR();
const signedTransaction = await activeConnector?.signTransaction(transactionXDR, {
networkPassphrase: activeChain?.networkPassphrase,
});
if(!signedTransaction){
throw new Error("Couldn't sign transaction");
}
const transactionToSubmit = TransactionBuilder.fromXDR(
signedTransaction!,
activeChain?.networkPassphrase ?? '',
);
const transactionResult = await serverHorizon?.submitTransaction(transactionToSubmit);
return transactionResult;
}
}
9 changes: 6 additions & 3 deletions src/helpers/horizon/getHorizonPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ const getClassicAsset = (currency: TokenType) => {
return asset
}

const getAmount = (amount: string) => {
export const getAmount = (amount: string) => {
if (!amount) return;
return new BigNumber(amount).dividedBy(10000000).toString()
}

Expand Down Expand Up @@ -112,7 +113,7 @@ export function getHorizonBestPath(
try {
const send = serverHorizon?.strictSendPaths(
args.assetFrom!,
args?.amount,
args?.amount!,
[args.assetTo!]
).call().then((res) => {
return res.records;
Expand All @@ -125,6 +126,7 @@ export function getHorizonBestPath(
return maxObj;
}
});
console.log(maxObj)
return parseHorizonResult(maxObj, payload.tradeType);
});
} catch (error) {
Expand All @@ -137,7 +139,7 @@ export function getHorizonBestPath(
const receive = serverHorizon?.strictReceivePaths(
[args.assetFrom!],
args.assetTo!,
args?.amount,
args?.amount!,
).call().then((res) => {
return res.records;
});
Expand All @@ -150,6 +152,7 @@ export function getHorizonBestPath(
return minObj;
}
});
console.log(minObj)
return parseHorizonResult(minObj, payload.tradeType);
});
} catch (error) {
Expand Down
157 changes: 88 additions & 69 deletions src/hooks/useSwapCallback.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TxResponse } from '@soroban-react/contracts';
import { SorobanContextType, useSorobanReact } from '@soroban-react/core';
import { useSorobanReact } from '@soroban-react/core';
import BigNumber from 'bignumber.js';
import { DEFAULT_SLIPPAGE_INPUT_VALUE } from 'components/Settings/MaxSlippageSettings';
import { AppContext, SnackbarIconType } from 'contexts';
Expand All @@ -9,11 +9,12 @@ import { scValToJs } from 'helpers/convert';
import { formatTokenAmount } from 'helpers/format';
import { bigNumberToI128, bigNumberToU64 } from 'helpers/utils';
import { useContext } from 'react';
import { InterfaceTrade, TradeType } from 'state/routing/types';
import { InterfaceTrade, PlatformType, TradeType } from 'state/routing/types';
import { useUserSlippageToleranceWithDefault } from 'state/user/hooks';
import * as StellarSdk from '@stellar/stellar-sdk';
import { useSWRConfig } from 'swr';
import { RouterMethod, useRouterCallback } from './useRouterCallback';
import { createStellarPathPayment } from 'helpers/horizon/createHorizonTransaction';



Expand Down Expand Up @@ -97,84 +98,102 @@ export function useSwapCallback(
) {
const { SnackbarContext } = useContext(AppContext);
const sorobanContext = useSorobanReact();
const { activeChain, address } = sorobanContext;
const { activeChain, address, activeConnector } = sorobanContext;
const routerCallback = useRouterCallback();
const allowedSlippage = useUserSlippageToleranceWithDefault(DEFAULT_SLIPPAGE_INPUT_VALUE);
const { mutate } = useSWRConfig();

const doSwap = async (
simulation?: boolean,
): Promise<SuccessfullSwapResponse | StellarSdk.SorobanRpc.Api.GetTransactionResponse> => {
): Promise<SuccessfullSwapResponse | StellarSdk.SorobanRpc.Api.GetTransactionResponse | StellarSdk.Horizon.HorizonApi.SubmitTransactionResponse> => {
if (!trade) throw new Error('missing trade');
if (!address || !activeChain) throw new Error('wallet must be connected to swap');
if (!trade.tradeType) throw new Error('tradeType must be defined');

const { amount0, amount1, routerMethod } = getSwapAmounts({
tradeType: trade.tradeType,
inputAmount: trade.inputAmount?.value as string,
outputAmount: trade.outputAmount?.value as string,
allowedSlippage: allowedSlippage,
});
const amount0ScVal = bigNumberToI128(amount0);
const amount1ScVal = bigNumberToI128(amount1);

// fn swap_exact_tokens_for_tokens(
// e: Env,
// amount_in: i128,
// amount_out_min: i128,
// path: Vec<Address>,
// to: Address,
// deadline: u64,
// ) -> Vec<i128>;

// fn swap_tokens_for_exact_tokens(
// e: Env,
// amount_out: i128,
// amount_in_max: i128,
// path: Vec<Address>,
// to: Address,
// deadline: u64,
// ) -> Vec<i128>;

const path = trade.path?.map((address) => new StellarSdk.Address(address));

const pathScVal = StellarSdk.nativeToScVal(path);

const args = [
amount0ScVal,
amount1ScVal,
pathScVal, // path
new StellarSdk.Address(address!).toScVal(),
bigNumberToU64(BigNumber(getCurrentTimePlusOneHour())),
];

try {
const result = (await routerCallback(
routerMethod,
args,
!simulation,
)) as StellarSdk.SorobanRpc.Api.GetTransactionResponse;

//if it is a simulation should return the result
if (simulation) return result;

if (result.status !== StellarSdk.SorobanRpc.Api.GetTransactionStatus.SUCCESS) throw result;

const switchValues: string[] = scValToJs(result.returnValue!);

const currencyA = switchValues?.[0];
const currencyB = switchValues?.[switchValues?.length - 1];

const notificationMessage = `${formatTokenAmount(currencyA ?? '0')} ${trade?.inputAmount
?.currency.code} for ${formatTokenAmount(currencyB ?? '0')} ${trade?.outputAmount
?.currency.code}`;

sendNotification(notificationMessage, 'Swapped', SnackbarIconType.SWAP, SnackbarContext);

return { ...result, switchValues };
} catch (error) {
throw error;
switch (trade.platform) {
case PlatformType.SOROBAN:
const { amount0, amount1, routerMethod } = getSwapAmounts({
tradeType: trade.tradeType,
inputAmount: trade.inputAmount?.value as string,
outputAmount: trade.outputAmount?.value as string,
allowedSlippage: allowedSlippage,
});
const amount0ScVal = bigNumberToI128(amount0);
const amount1ScVal = bigNumberToI128(amount1);

// fn swap_exact_tokens_for_tokens(
// e: Env,
// amount_in: i128,
// amount_out_min: i128,
// path: Vec<Address>,
// to: Address,
// deadline: u64,
// ) -> Vec<i128>;

// fn swap_tokens_for_exact_tokens(
// e: Env,
// amount_out: i128,
// amount_in_max: i128,
// path: Vec<Address>,
// to: Address,
// deadline: u64,
// ) -> Vec<i128>;

const path = trade.path?.map((address) => new StellarSdk.Address(address));

const pathScVal = StellarSdk.nativeToScVal(path);

const args = [
amount0ScVal,
amount1ScVal,
pathScVal, // path
new StellarSdk.Address(address!).toScVal(),
bigNumberToU64(BigNumber(getCurrentTimePlusOneHour())),
];

try {
const result = (await routerCallback(
routerMethod,
args,
!simulation,
)) as StellarSdk.SorobanRpc.Api.GetTransactionResponse;

//if it is a simulation should return the result
if (simulation) return result;

if (result.status !== StellarSdk.SorobanRpc.Api.GetTransactionStatus.SUCCESS) throw result;

const switchValues: string[] = scValToJs(result.returnValue!);

const currencyA = switchValues?.[0];
const currencyB = switchValues?.[switchValues?.length - 1];

const notificationMessage = `${formatTokenAmount(currencyA ?? '0')} ${trade?.inputAmount
?.currency.code} for ${formatTokenAmount(currencyB ?? '0')} ${trade?.outputAmount
?.currency.code}`;

sendNotification(notificationMessage, 'Swapped', SnackbarIconType.SWAP, SnackbarContext);

return { ...result, switchValues };
} catch (error) {
throw error;
}
case PlatformType.STELLAR_CLASSIC:
try {
const result = await createStellarPathPayment(trade, sorobanContext);
const notificationMessage = `${formatTokenAmount(trade.inputAmount?.value ?? '0')} ${trade?.inputAmount
?.currency.code} for ${formatTokenAmount(trade.outputAmount?.value ?? '0')} ${trade?.outputAmount
?.currency.code}`;
sendNotification(notificationMessage, 'Swapped', SnackbarIconType.SWAP, SnackbarContext);
return result!;
} catch (error) {
console.error(error);
}
default:
throw new Error('Unsupported platform');
}


};

return { doSwap, isLoading: trade?.isLoading };
Expand Down

0 comments on commit 5d9d3b4

Please sign in to comment.