From 1a350d10cbceb56213e8472b9788675a6cc8157c Mon Sep 17 00:00:00 2001 From: FabianSanchezD Date: Sun, 8 Mar 2026 20:19:14 -0500 Subject: [PATCH 1/2] fix(faucet): new token contract ids --- apps/web-app/src/lib/constants/faucet.ts | 10 +- .../constants/generated/contract-errors.ts | 202 +++++++++++++++++- 2 files changed, 206 insertions(+), 6 deletions(-) diff --git a/apps/web-app/src/lib/constants/faucet.ts b/apps/web-app/src/lib/constants/faucet.ts index 9e13ad7..19b313d 100644 --- a/apps/web-app/src/lib/constants/faucet.ts +++ b/apps/web-app/src/lib/constants/faucet.ts @@ -22,31 +22,31 @@ const TESTNET_FAUCET_TOKENS: { }[] = [ { symbol: "USTRY", - contract: "CC6SODKGOTFEDWVNPR6ESJC3GL7NC5Y4DVFKYGATZJ74F2YXHTW4RJ6D", + contract: "CCAYGJWQI5NJN7XRVNSENF47PICNSNTG4FAQHHFOJWZIRTEAC5JPMLGN", decimals: 7, amount: 100, }, { symbol: "TESOURO", - contract: "CA55OO3U556GXABJKDYP3QCGZ6AFNZPB27TROYP42AQPFFYPKU5EDOUH", + contract: "CAPFX3QEAHE7JVT6E7PYZQTFSVS5Z7AV4RE7GRJRVCPKXGQHCWSCOMTW", decimals: 7, amount: 100, }, { symbol: "CETES", - contract: "CCGWKS4GLAGPYIAOLBH6JM5RKUPMUCN47VRAEXAJWJFKXSXQ33VIRUAA", + contract: "CAJ4B2ZWU2GA7UYQZ7N7QQCTZAUSSXNKKQ326ADYVH3ALN4FFQ6LPO4U", decimals: 7, amount: 100, }, { symbol: "USDY", - contract: "CBVLFSVBZGHVAH6CV4JQYPBJSX75VFR2NJC7CQX7QKQ7KOLGQZOZAGQK", + contract: "CDRQV3D3GLWF73MWTEQWFZWMBQ47KZ3KECYPOBKBDRQBWQQ74KDH5ECT", decimals: 7, amount: 100, }, { symbol: "PYUSD", - contract: "CBCB5UDYZENTIUVVA7SHQOCVVCDXDMHEKJHHFM3OQKU2E5CAF2B62TIO", + contract: "CBNHH37BJ2G4ZT6PLWDXPOWHKLR75IGNLBRCXZNOS7YPAYS53JPEPSSS", decimals: 7, amount: 100, }, diff --git a/apps/web-app/src/lib/constants/generated/contract-errors.ts b/apps/web-app/src/lib/constants/generated/contract-errors.ts index 18da062..d331497 100644 --- a/apps/web-app/src/lib/constants/generated/contract-errors.ts +++ b/apps/web-app/src/lib/constants/generated/contract-errors.ts @@ -4,7 +4,7 @@ * This file is automatically generated from Stellar smart contract error definitions. * To regenerate, run: npm run generate:errors * - * Generated at: 2026-03-08T07:15:16.526Z + * Generated at: 2026-03-08T14:16:50.215Z * * Source files: * - apps/contracts/stellar-contracts/rwa-lending/src/common/error.rs @@ -458,6 +458,206 @@ const _errorEntries: [number, ContractErrorInfo][] = [ contract: "rwa-oracle", }, ], + [ + 1, + { + code: "PositionNotFound", + message: "Position not found", + contract: "rwa-perps", + }, + ], + [ + 2, + { + code: "PositionAlreadyExists", + message: "Position already exists", + contract: "rwa-perps", + }, + ], + [ + 3, + { + code: "PositionNotLiquidatable", + message: "Position is not liquidatable", + contract: "rwa-perps", + }, + ], + [ + 10, + { + code: "MarginRatioHealthy", + message: "Margin ratio is healthy", + contract: "rwa-perps", + }, + ], + [ + 11, + { + code: "InsufficientMargin", + message: "Insufficient margin", + contract: "rwa-perps", + }, + ], + [ + 12, + { + code: "LiquidationPriceTooLow", + message: "Liquidation price is too low", + contract: "rwa-perps", + }, + ], + [ + 13, + { + code: "LiquidationPriceTooHigh", + message: "Liquidation price is too high", + contract: "rwa-perps", + }, + ], + [ + 20, + { + code: "MarketNotFound", + message: "Market not found", + contract: "rwa-perps", + }, + ], + [ + 21, + { + code: "MarketInactive", + message: "Market is inactive", + contract: "rwa-perps", + }, + ], + [ + 30, + { + code: "OraclePriceNotFound", + message: "Oracle price not found", + contract: "rwa-perps", + }, + ], + [ + 31, + { + code: "OraclePriceStale", + message: "Oracle price is stale", + contract: "rwa-perps", + }, + ], + [ + 40, + { + code: "ArithmeticError", + message: "A calculation error occurred. Please try a different amount", + contract: "rwa-perps", + }, + ], + [ + 41, + { + code: "Overflow", + message: "Arithmetic overflow occurred", + contract: "rwa-perps", + }, + ], + [ + 42, + { + code: "DivisionByZero", + message: "Division by zero", + contract: "rwa-perps", + }, + ], + [ + 50, + { + code: "Unauthorized", + message: "Unauthorized access", + contract: "rwa-perps", + }, + ], + [ + 60, + { + code: "InvalidInput", + message: "Invalid input provided", + contract: "rwa-perps", + }, + ], + [ + 61, + { + code: "NotInitialized", + message: "The lending protocol has not been initialized yet", + contract: "rwa-perps", + }, + ], + [ + 62, + { + code: "AlreadyInitialized", + message: "The lending protocol is already initialized", + contract: "rwa-perps", + }, + ], + [ + 63, + { + code: "ProtocolPaused", + message: "Protocol is paused", + contract: "rwa-perps", + }, + ], + [ + 70, + { + code: "InvalidFundingRate", + message: "Invalid funding rate", + contract: "rwa-perps", + }, + ], + [ + 71, + { + code: "FundingCalculationError", + message: "Funding calculation error", + contract: "rwa-perps", + }, + ], + [ + 72, + { + code: "MarginRatioBelowMaintenance", + message: "Margin removal would violate maintenance requirement", + contract: "rwa-perps", + }, + ], + [ + 73, + { + code: "MarginTokenNotSet", + message: "Margin token not configured", + contract: "rwa-perps", + }, + ], + [ + 80, + { + code: "ExceedsMaxLeverage", + message: "Leverage exceeds market maximum", + contract: "rwa-perps", + }, + ], + [ + 81, + { + code: "InsufficientInitialMargin", + message: "Margin below initial requirement", + contract: "rwa-perps", + }, + ], [ 1, { From f9df619060be86bf55f631284ec2eca6ec4f10ad Mon Sep 17 00:00:00 2001 From: aguilar1x Date: Sun, 8 Mar 2026 21:44:35 -0500 Subject: [PATCH 2/2] feat: Implement remove_collateral() --- .../components/ui/MyBorrowPositions.tsx | 1 - .../borrowing/hooks/useRemoveCollateral.ts | 129 ++++++++++++++++++ .../src/lib/helpers/stellar/lending.ts | 65 +++++++++ .../src/lib/services/lending.service.ts | 28 ++++ 4 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 apps/web-app/src/features/borrowing/hooks/useRemoveCollateral.ts diff --git a/apps/web-app/src/features/borrowing/components/ui/MyBorrowPositions.tsx b/apps/web-app/src/features/borrowing/components/ui/MyBorrowPositions.tsx index 2883a5e..59a291d 100644 --- a/apps/web-app/src/features/borrowing/components/ui/MyBorrowPositions.tsx +++ b/apps/web-app/src/features/borrowing/components/ui/MyBorrowPositions.tsx @@ -29,7 +29,6 @@ const MyBorrowPositions: React.FC = () => { ); const { positions: allPositions, isLoading: isLoadingAggregated } = useUserPositions(); - function getHealthFactor(contractId: string): number | null { return ( hfPools.find((p) => p.contractId === contractId)?.healthFactor ?? null diff --git a/apps/web-app/src/features/borrowing/hooks/useRemoveCollateral.ts b/apps/web-app/src/features/borrowing/hooks/useRemoveCollateral.ts new file mode 100644 index 0000000..c842245 --- /dev/null +++ b/apps/web-app/src/features/borrowing/hooks/useRemoveCollateral.ts @@ -0,0 +1,129 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { Networks } from "@stellar/stellar-sdk"; +import { useWallet } from "@/hooks/useWallet"; +import { useToast } from "@/hooks/useToast"; +import { removeCollateral } from "@/lib/helpers/stellar/lending"; +import { + signAndSendTransaction, + type SignTransactionFn, +} from "@/lib/helpers/stellar/transaction"; +import { getAvailableTokens } from "@/lib/helpers/stellar/soroswap"; +import { rpcUrl } from "@/lib/constants/network"; +import { extractContractErrorOrNull } from "@/lib/helpers/stellar/contractErrors"; +import { TOAST_CONFIG } from "@/lib/constants/toast.config"; +import type { BorrowPosition } from "./useUserBorrowPositions"; + +export function useRemoveCollateral() { + const { addNotification } = useToast(); + const { address, signTransaction, networkPassphrase } = useWallet(); + const [isLoading, setIsLoading] = useState(false); + const [selectedPosition, setSelectedPosition] = + useState(null); + + const openModal = useCallback((position: BorrowPosition) => { + setSelectedPosition(position); + }, []); + + const closeModal = useCallback(() => { + setSelectedPosition(null); + }, []); + + const showError = useCallback( + (msg: string) => + addNotification("Something went wrong", "error", { + ...TOAST_CONFIG.defaultOpts, + description: msg, + }), + [addNotification] + ); + + const showSuccess = useCallback( + (msg: string) => + addNotification("Success", "success", { + ...TOAST_CONFIG.defaultOpts, + description: msg, + }), + [addNotification] + ); + + const handleRemoveCollateral = useCallback( + async (amount: string) => { + if (!address || !selectedPosition) return; + + const amountNum = parseFloat(amount); + if (!Number.isFinite(amountNum) || amountNum <= 0) { + showError("Please enter a valid amount"); + return; + } + + const availableTokens = getAvailableTokens(); + const collateralToken = + availableTokens[selectedPosition.collateralTokenCode]; + + if (!collateralToken?.contract) { + showError( + `Collateral token ${selectedPosition.collateralTokenCode} not found` + ); + return; + } + + setIsLoading(true); + try { + const xdr = await removeCollateral( + collateralToken.contract, + amount, + 7, + address, + selectedPosition.contractId + ); + + await signAndSendTransaction( + xdr, + signTransaction as SignTransactionFn, + { + networkPassphrase: networkPassphrase || Networks.TESTNET, + rpcUrl, + address, + waitForPending: true, + } + ); + + showSuccess( + `Successfully removed ${amountNum} ${selectedPosition.collateralTokenCode} from collateral` + ); + closeModal(); + return { success: true as const }; + } catch (err) { + const friendlyError = extractContractErrorOrNull(err); + showError( + typeof friendlyError === "string" + ? friendlyError + : "An unexpected error occurred. Please try again." + ); + return { success: false as const, error: err }; + } finally { + setIsLoading(false); + } + }, + [ + address, + networkPassphrase, + signTransaction, + selectedPosition, + showError, + showSuccess, + closeModal, + ] + ); + + return { + selectedPosition, + isLoading, + isWalletConnected: Boolean(address), + openModal, + closeModal, + handleRemoveCollateral, + }; +} diff --git a/apps/web-app/src/lib/helpers/stellar/lending.ts b/apps/web-app/src/lib/helpers/stellar/lending.ts index 5d2ca61..96071bf 100644 --- a/apps/web-app/src/lib/helpers/stellar/lending.ts +++ b/apps/web-app/src/lib/helpers/stellar/lending.ts @@ -269,6 +269,71 @@ export const addCollateral = async ( } }; +export const removeCollateral = async ( + rwaTokenAddress: string, + amount: string, + decimals: number = 7, + walletAddress: string, + contractId: string = networks.testnet.contractId +): Promise => { + try { + const sorobanServer = new rpc.Server(rpcUrl, { + allowHttp: stellarNetwork === "LOCAL", + }); + const horizonServer = new Horizon.Server(horizonUrl); + const lendingContract = new Contract(contractId); + + const amountInSmallestUnit = BigInt(toSmallestUnit(amount, decimals)); + + const operation = lendingContract.call( + "remove_collateral", + new Address(walletAddress).toScVal(), + new Address(rwaTokenAddress).toScVal(), + nativeToScVal(amountInSmallestUnit, { type: "i128" }) + ); + + const account = await horizonServer.loadAccount(walletAddress); + + const transaction = new TransactionBuilder(account, { + fee: "100", + networkPassphrase: networkPassphrase, + }) + .addOperation(operation) + .setTimeout(300) + .build(); + + try { + await sorobanServer.simulateTransaction(transaction); + } catch (simError) { + const errorMessage = + simError instanceof Error ? simError.message : String(simError); + if ( + !errorMessage.includes("Auth") && + !errorMessage.includes("require_auth") && + !errorMessage.includes("InvalidAction") + ) { + const friendlyError = extractContractError(simError, "rwa-lending"); + throw new Error(friendlyError); + } + } + + const preparedTx = await sorobanServer.prepareTransaction(transaction); + + return preparedTx.toXDR(); + } catch (error) { + console.error("Error building remove_collateral transaction:", error); + if ( + error instanceof Error && + error.message && + !error.message.includes("Failed to build") + ) { + throw error; + } + const friendlyError = extractContractError(error, "rwa-lending"); + throw new Error(friendlyError); + } +}; + export const borrowFromPool = async ( assetCode: string, amount: string, diff --git a/apps/web-app/src/lib/services/lending.service.ts b/apps/web-app/src/lib/services/lending.service.ts index 24d6f5e..870dfe3 100644 --- a/apps/web-app/src/lib/services/lending.service.ts +++ b/apps/web-app/src/lib/services/lending.service.ts @@ -27,6 +27,7 @@ import { toSmallestUnit } from "../helpers/tokenUtils"; import { approveToken, addCollateral, + removeCollateral, borrowFromPool, } from "../helpers/stellar/lending"; import { extractContractError } from "../helpers/stellar/contractErrors"; @@ -398,6 +399,33 @@ export class LendingService { } } + /** + * Remove RWA token collateral from the lending pool + */ + async removeCollateral( + rwaTokenContract: string, + amount: string, + decimals: number = 7, + walletAddress: string + ): Promise { + try { + const xdr = await removeCollateral( + rwaTokenContract, + amount, + decimals, + walletAddress + ); + return { xdr }; + } catch (error) { + console.error("Error building remove collateral transaction:", error); + const friendlyError = extractContractError(error, "rwa-lending"); + return { + xdr: "", + error: friendlyError, + }; + } + } + /** * Add collateral with approve - returns two separate transactions */