diff --git a/src/chains/evm/common/utils/message.ts b/src/chains/evm/common/utils/message.ts index 3da99a0..0a36907 100644 --- a/src/chains/evm/common/utils/message.ts +++ b/src/chains/evm/common/utils/message.ts @@ -191,7 +191,11 @@ export function buildEvmMessageData( ]); } case Action.RepayWithCollateral: { - throw new Error("Not implemented yet: Action.RepayWithCollateral case"); + return concat([ + data.loanId, + convertNumberToBytes(data.poolId, UINT8_LENGTH), + convertNumberToBytes(data.amount, UINT256_LENGTH), + ]); } case Action.Liquidate: { throw new Error("Not implemented yet: Action.Liquidate case"); @@ -417,7 +421,21 @@ export function buildEvmMessageToSend( return message; } case Action.RepayWithCollateral: { - throw new Error("Not implemented yet: Action.RepayWithCollateral case"); + const message: MessageToSend = { + params, + sender, + destinationChainId, + handler, + payload: buildMessagePayload( + Action.RepayWithCollateral, + accountId, + userAddress, + data, + ), + finalityLevel: FINALITY.IMMEDIATE, + extraArgs, + }; + return message; } case Action.Liquidate: { throw new Error("Not implemented yet: Action.Liquidate case"); diff --git a/src/chains/evm/spoke/modules/folks-evm-loan.ts b/src/chains/evm/spoke/modules/folks-evm-loan.ts index e6368ee..fc03695 100644 --- a/src/chains/evm/spoke/modules/folks-evm-loan.ts +++ b/src/chains/evm/spoke/modules/folks-evm-loan.ts @@ -33,6 +33,7 @@ import type { PrepareDeleteLoanCall, PrepareDepositCall, PrepareRepayCall, + PrepareRepayWithCollateralCall, PrepareWithdrawCall, } from "../../common/types/module.js"; import type { TokenRateLimit } from "../types/pool.js"; @@ -296,6 +297,48 @@ export const prepare = { token: spokeTokenData, }; }, + + async repayWithCollateral( + provider: Client, + sender: EvmAddress, + messageToSend: MessageToSend, + network: NetworkType, + accountId: AccountId, + loanId: LoanId, + folksTokenId: FolksTokenId, + amount: bigint, + spokeChain: SpokeChain, + transactionOptions: EstimateGasParameters = { account: sender }, + ): Promise { + const hubTokenData = getHubTokenData(folksTokenId, network); + + const spokeCommonAddress = spokeChain.spokeCommonAddress; + + const spokeCommon = getSpokeCommonContract(provider, spokeCommonAddress); + const bridgeRouter = getBridgeRouterSpokeContract( + provider, + spokeChain.bridgeRouterAddress, + ); + + // get adapter fees + const msgValue = await bridgeRouter.read.getSendFee([messageToSend]); + + // get gas limits + const gasLimit = await spokeCommon.estimateGas.repayWithCollateral( + [messageToSend.params, accountId, loanId, hubTokenData.poolId, amount], + { + value: msgValue, + ...transactionOptions, + }, + ); + + return { + msgValue, + gasLimit, + messageParams: messageToSend.params, + spokeCommonAddress, + }; + }, }; export const write = { @@ -506,6 +549,35 @@ export const write = { }, ); }, + + async repayWithCollateral( + provider: Client, + signer: WalletClient, + accountId: AccountId, + loanId: LoanId, + poolId: number, + amount: bigint, + prepareCall: PrepareRepayWithCollateralCall, + ) { + const { msgValue, gasLimit, messageParams, spokeCommonAddress } = + prepareCall; + + const spokeCommon = getSpokeCommonContract( + provider, + spokeCommonAddress, + signer, + ); + + return await spokeCommon.write.repayWithCollateral( + [messageParams, accountId, loanId, poolId, amount], + { + account: getEvmSignerAccount(signer), + chain: signer.chain, + gasLimit: gasLimit, + value: msgValue, + }, + ); + }, }; export const read = { diff --git a/src/common/types/message.ts b/src/common/types/message.ts index df39e13..33ad58b 100644 --- a/src/common/types/message.ts +++ b/src/common/types/message.ts @@ -122,6 +122,12 @@ export type RepayMessageData = { maxOverRepayment: bigint; }; +export type RepayWithCollateralMessageData = { + loanId: LoanId; + poolId: number; + amount: bigint; +}; + // Extra args export type DefaultExtraArgs = "0x"; @@ -148,7 +154,6 @@ export type DefaultMessageDataParams = { | Action.RemoveDelegate | Action.DepositFToken | Action.WithdrawFToken - | Action.RepayWithCollateral | Action.Liquidate | Action.SwitchBorrowType | Action.SendToken; @@ -212,6 +217,12 @@ export type RepayMessageDataParams = { extraArgs: RepayExtraArgs; }; +export type RepayWithCollateralMessageDataParams = { + action: Action.RepayWithCollateral; + data: RepayWithCollateralMessageData; + extraArgs: DefaultExtraArgs; +}; + export type MessageDataParams = | DefaultMessageDataParams | CreateAccountMessageDataParams @@ -222,7 +233,8 @@ export type MessageDataParams = | DepositMessageDataParams | WithdrawMessageDataParams | BorrowMessageDataParams - | RepayMessageDataParams; + | RepayMessageDataParams + | RepayWithCollateralMessageDataParams; export type MessageBuilderParams = { userAddress: GenericAddress; diff --git a/src/common/types/module.ts b/src/common/types/module.ts index d847738..257ffea 100644 --- a/src/common/types/module.ts +++ b/src/common/types/module.ts @@ -10,6 +10,7 @@ import type { PrepareCall as PrepareEVMCall, PrepareBorrowCall as PrepareBorrowEVMCall, PrepareRepayCall as PrepareRepayEVMCall, + PrepareRepayWithCollateralCall as PrepareRepayWithCollateralEVMCall, } from "../../chains/evm/common/types/module.js"; export enum LoanType { @@ -30,3 +31,4 @@ export type PrepareDepositCall = PrepareDepositEVMCall; export type PrepareWithdrawCall = PrepareWithdrawEVMCall; export type PrepareBorrowCall = PrepareBorrowEVMCall; export type PrepareRepayCall = PrepareRepayEVMCall; +export type PrepareRepayWithCollateralCall = PrepareRepayWithCollateralEVMCall; diff --git a/src/xchain/modules/folks-loan.ts b/src/xchain/modules/folks-loan.ts index e7d4d2f..4ef9212 100644 --- a/src/xchain/modules/folks-loan.ts +++ b/src/xchain/modules/folks-loan.ts @@ -48,6 +48,7 @@ import type { OptionalFeeParams, RepayExtraArgs, RepayMessageData, + RepayWithCollateralMessageData, WithdrawMessageData, } from "../../common/types/message.js"; import type { @@ -56,6 +57,7 @@ import type { PrepareCreateLoanCall, PrepareDepositCall, PrepareRepayCall, + PrepareRepayWithCollateralCall, PrepareWithdrawCall, } from "../../common/types/module.js"; import type { FolksTokenId } from "../../common/types/token.js"; @@ -613,6 +615,81 @@ export const prepare = { return exhaustiveCheck(folksChain.chainType); } }, + + async repayWithCollateral( + accountId: AccountId, + loanId: LoanId, + folksTokenId: FolksTokenId, + amount: bigint, + adapters: MessageAdapters, + ) { + const folksChain = FolksCore.getSelectedFolksChain(); + const network = folksChain.network; + + assertAdapterSupportsDataMessage( + folksChain.folksChainId, + adapters.adapterId, + ); + + const hubChain = getHubChain(network); + const hubTokenData = getHubTokenData(folksTokenId, network); + + const spokeChain = getSpokeChain(folksChain.folksChainId, network); + + const userAddress = getSignerGenericAddress({ + signer: FolksCore.getFolksSigner().signer, + chainType: folksChain.chainType, + }); + + const data: RepayWithCollateralMessageData = { + loanId, + poolId: hubTokenData.poolId, + amount, + }; + const messageBuilderParams: MessageBuilderParams = { + userAddress, + accountId, + adapters, + action: Action.RepayWithCollateral, + sender: spokeChain.spokeCommonAddress, + destinationChainId: hubChain.folksChainId, + handler: hubChain.hubAddress, + data, + extraArgs: "0x", + }; + const feeParams: OptionalFeeParams = {}; + + feeParams.gasLimit = await estimateReceiveGasLimit( + FolksCore.getHubProvider(), + hubChain, + folksChain, + adapters, + messageBuilderParams, + ); + + const messageToSend = buildMessageToSend( + folksChain.chainType, + messageBuilderParams, + feeParams, + ); + + switch (folksChain.chainType) { + case ChainType.EVM: + return await FolksEvmLoan.prepare.repayWithCollateral( + FolksCore.getProvider(folksChain.folksChainId), + convertFromGenericAddress(userAddress, folksChain.chainType), + messageToSend, + network, + accountId, + loanId, + folksTokenId, + amount, + spokeChain, + ); + default: + return exhaustiveCheck(folksChain.chainType); + } + }, }; export const write = { @@ -781,6 +858,33 @@ export const write = { return exhaustiveCheck(folksChain.chainType); } }, + + async repayWithCollateral( + accountId: AccountId, + loanId: LoanId, + folksTokenId: FolksTokenId, + amount: bigint, + prepareCall: PrepareRepayWithCollateralCall, + ) { + const folksChain = FolksCore.getSelectedFolksChain(); + + const { poolId } = getHubTokenData(folksTokenId, folksChain.network); + + switch (folksChain.chainType) { + case ChainType.EVM: + return await FolksEvmLoan.write.repayWithCollateral( + FolksCore.getProvider(folksChain.folksChainId), + FolksCore.getSigner(), + accountId, + loanId, + poolId, + amount, + prepareCall, + ); + default: + return exhaustiveCheck(folksChain.chainType); + } + }, }; export const read = {