diff --git a/src/chains/evm/common/utils/message.ts b/src/chains/evm/common/utils/message.ts index 2e067c5..e77da3f 100644 --- a/src/chains/evm/common/utils/message.ts +++ b/src/chains/evm/common/utils/message.ts @@ -1,15 +1,23 @@ import { concat, isHex } from "viem"; -import { UINT16_LENGTH } from "../../../../common/constants/bytes.js"; +import { + UINT16_LENGTH, + UINT256_LENGTH, + UINT8_LENGTH, +} from "../../../../common/constants/bytes.js"; import { FINALITY } from "../../../../common/constants/message.js"; import { ChainType } from "../../../../common/types/chain.js"; import { Action } from "../../../../common/types/message.js"; +import { TokenType } from "../../../../common/types/token.js"; import { convertToGenericAddress, getRandomGenericAddress, isGenericAddress, } from "../../../../common/utils/address.js"; -import { convertNumberToBytes } from "../../../../common/utils/bytes.js"; +import { + convertBooleanToByte, + convertNumberToBytes, +} from "../../../../common/utils/bytes.js"; import { exhaustiveCheck } from "../../../../utils/exhaustive-check.js"; import type { GenericAddress } from "../../../../common/types/chain.js"; @@ -48,6 +56,45 @@ export function buildMessagePayload( ]); } +export function extraArgsToBytes( + tokenAddr: GenericAddress, + recipientAddr: GenericAddress, + amount: bigint, +): Hex { + if (!isGenericAddress(tokenAddr)) throw Error("Unknown token address format"); + if (!isGenericAddress(recipientAddr)) + throw Error("Unknown recipient address format"); + + return concat([ + "0x1b366e79", + tokenAddr, + recipientAddr, + convertNumberToBytes(amount, UINT256_LENGTH), + ]); +} + +export function buildSendTokenExtraArgsWhenRemoving( + tokenType: TokenType, + spokeAddress: GenericAddress, + hubTokenAddress: GenericAddress, + amount: bigint, +): Hex { + if (tokenType === TokenType.NATIVE || tokenType === TokenType.ERC20) + return "0x"; + return extraArgsToBytes(hubTokenAddress, spokeAddress, BigInt(amount)); +} + +export function buildSendTokenExtraArgsWhenAdding( + tokenType: TokenType, + spokeTokenAddress: GenericAddress, + hubPoolAddress: GenericAddress, + amount: bigint, +): Hex { + if (tokenType === TokenType.NATIVE || tokenType === TokenType.ERC20) + return "0x"; + return extraArgsToBytes(spokeTokenAddress, hubPoolAddress, amount); +} + export function buildEvmMessageToSend( messageToSendBuilderParams: MessageToSendBuilderParams, ): MessageToSend { @@ -59,6 +106,7 @@ export function buildEvmMessageToSend( handler, action, data, + extraArgs, } = messageToSendBuilderParams; switch (action) { case Action.CreateAccount: { @@ -75,7 +123,7 @@ export function buildEvmMessageToSend( data, ), finalityLevel: FINALITY.IMMEDIATE, - extraArgs: "0x", + extraArgs, }; return message; } @@ -117,7 +165,7 @@ export function buildEvmMessageToSend( data, ), finalityLevel: FINALITY.IMMEDIATE, - extraArgs: "0x", + extraArgs, }; return message; } @@ -135,7 +183,7 @@ export function buildEvmMessageToSend( convertNumberToBytes(data.folksChainIdToUnregister, UINT16_LENGTH), ), finalityLevel: FINALITY.IMMEDIATE, - extraArgs: "0x", + extraArgs, }; return message; } @@ -146,19 +194,100 @@ export function buildEvmMessageToSend( throw new Error("Not implemented yet: Action.RemoveDelegate case"); } case Action.CreateLoan: { - throw new Error("Not implemented yet: Action.CreateLoan case"); + const params = DEFAULT_MESSAGE_PARAMS(adapters); + const message: MessageToSend = { + params, + sender, + destinationChainId, + handler, + payload: buildMessagePayload( + Action.CreateLoan, + accountId, + getRandomGenericAddress(), + concat([ + data.loanId, + convertNumberToBytes(data.loanTypeId, UINT16_LENGTH), + ]), + ), + finalityLevel: FINALITY.IMMEDIATE, + extraArgs, + }; + return message; } case Action.DeleteLoan: { - throw new Error("Not implemented yet: Action.DeleteLoan case"); + const params = DEFAULT_MESSAGE_PARAMS(adapters); + const message: MessageToSend = { + params, + sender, + destinationChainId, + handler, + payload: buildMessagePayload( + Action.DeleteLoan, + data.accountId, + getRandomGenericAddress(), + data.loanId, + ), + finalityLevel: FINALITY.IMMEDIATE, + extraArgs, + }; + return message; } case Action.Deposit: { - throw new Error("Not implemented yet: Action.Deposit case"); + const params = DEFAULT_MESSAGE_PARAMS(adapters); + const message: MessageToSend = { + params, + sender, + destinationChainId, + handler, + payload: buildMessagePayload( + Action.Deposit, + accountId, + getRandomGenericAddress(), + concat([ + data.loanId, + convertNumberToBytes(data.poolId, UINT8_LENGTH), + convertNumberToBytes(data.amount, UINT256_LENGTH), + ]), + ), + finalityLevel: FINALITY.FINALISED, + extraArgs: buildSendTokenExtraArgsWhenAdding( + extraArgs.tokenType, + extraArgs.spokeTokenAddress, + extraArgs.hubPoolAddress, + extraArgs.amount, + ), + }; + return message; } case Action.DepositFToken: { throw new Error("Not implemented yet: Action.DepositFToken case"); } case Action.Withdraw: { - throw new Error("Not implemented yet: Action.Withdraw case"); + const params = { + ...DEFAULT_MESSAGE_PARAMS(adapters), + ...messageToSendBuilderParams.params, + }; + const message: MessageToSend = { + params, + sender, + destinationChainId, + handler, + payload: buildMessagePayload( + Action.Withdraw, + accountId, + getRandomGenericAddress(), + concat([ + data.loanId, + convertNumberToBytes(data.poolId, UINT8_LENGTH), + convertNumberToBytes(data.receiverFolksChainId, UINT16_LENGTH), + convertNumberToBytes(data.amount, UINT256_LENGTH), + convertBooleanToByte(data.isFAmount), + ]), + ), + finalityLevel: FINALITY.IMMEDIATE, + extraArgs, + }; + return message; } case Action.WithdrawFToken: { throw new Error("Not implemented yet: Action.WithdrawFToken case"); diff --git a/src/chains/evm/hub/modules/folks-hub-loan.ts b/src/chains/evm/hub/modules/folks-hub-loan.ts index 82faac4..6008ed8 100644 --- a/src/chains/evm/hub/modules/folks-hub-loan.ts +++ b/src/chains/evm/hub/modules/folks-hub-loan.ts @@ -7,12 +7,16 @@ import { getSpokeChain, getSpokeTokenData, } from "../../../../common/utils/chain.js"; -import { getSendTokenExtraArgsWhenRemoving } from "../../../../common/utils/messages.js"; import { DEFAULT_MESSAGE_PARAMS, buildMessagePayload, + buildSendTokenExtraArgsWhenRemoving, } from "../../common/utils/message.js"; -import { getHubChain, getHubTokenData } from "../utils/chain.js"; +import { + getHubChain, + getHubTokenAddress, + getHubTokenData, +} from "../utils/chain.js"; import { getBridgeRouterHubContract } from "../utils/contract.js"; import type { @@ -64,9 +68,10 @@ export function getSendTokenAdapterFees( convertNumberToBytes(amount, UINT256_LENGTH), ), finalityLevel: FINALITY.FINALISED, - extraArgs: getSendTokenExtraArgsWhenRemoving( - spokeTokenData, - hubTokenData, + extraArgs: buildSendTokenExtraArgsWhenRemoving( + hubTokenData.tokenType, + spokeTokenData.spokeAddress, + getHubTokenAddress(hubTokenData), amount, ), }; diff --git a/src/chains/evm/hub/utils/chain.ts b/src/chains/evm/hub/utils/chain.ts index c2f308e..193a040 100644 --- a/src/chains/evm/hub/utils/chain.ts +++ b/src/chains/evm/hub/utils/chain.ts @@ -2,6 +2,7 @@ import { HUB_CHAIN } from "../constants/chain.js"; import type { FolksChainId, + GenericAddress, NetworkType, } from "../../../../common/types/chain.js"; import type { LoanType } from "../../../../common/types/module.js"; @@ -47,3 +48,10 @@ export function assertLoanTypeSupported( `Loan type ${loanType} is not supported for folksTokenId: ${folksTokenId}`, ); } + +export function getHubTokenAddress(hubTokenData: HubTokenData): GenericAddress { + if (hubTokenData.tokenAddress) return hubTokenData.tokenAddress; + throw new Error( + `Hub token address not found for folksTokenId: ${hubTokenData.folksTokenId}`, + ); +} diff --git a/src/chains/evm/spoke/modules/folks-evm-loan.ts b/src/chains/evm/spoke/modules/folks-evm-loan.ts index 0159976..17b000f 100644 --- a/src/chains/evm/spoke/modules/folks-evm-loan.ts +++ b/src/chains/evm/spoke/modules/folks-evm-loan.ts @@ -1,30 +1,7 @@ -import { concat } from "viem"; - -import { - UINT16_LENGTH, - UINT8_LENGTH, - UINT256_LENGTH, -} from "../../../../common/constants/bytes.js"; -import { FINALITY } from "../../../../common/constants/message.js"; -import { Action } from "../../../../common/types/message.js"; import { TokenType } from "../../../../common/types/token.js"; -import { getRandomGenericAddress } from "../../../../common/utils/address.js"; -import { - convertNumberToBytes, - convertBooleanToByte, -} from "../../../../common/utils/bytes.js"; -import { - getSpokeChain, - getSpokeTokenData, -} from "../../../../common/utils/chain.js"; -import { getSendTokenExtraArgsWhenAdding } from "../../../../common/utils/messages.js"; import { getSignerAccount } from "../../common/utils/chain.js"; import { sendERC20Approve } from "../../common/utils/contract.js"; -import { - DEFAULT_MESSAGE_PARAMS, - buildMessagePayload, -} from "../../common/utils/message.js"; -import { getHubChain, getHubTokenData } from "../../hub/utils/chain.js"; +import { getHubTokenData } from "../../hub/utils/chain.js"; import { getBridgeRouterSpokeContract, getSpokeCommonContract, @@ -38,10 +15,13 @@ import type { } from "../../../../common/types/chain.js"; import type { MessageAdapters, - MessageToSend, MessageParams, + MessageToSend, } from "../../../../common/types/message.js"; -import type { FolksTokenId } from "../../../../common/types/token.js"; +import type { + FolksTokenId, + SpokeTokenData, +} from "../../../../common/types/token.js"; import type { PrepareCreateLoanCall, PrepareDeleteLoanCall, @@ -50,131 +30,17 @@ import type { } from "../../common/types/module.js"; import type { Address, + Client, EstimateGasParameters, Hex, - Client, WalletClient, } from "viem"; export const prepare = { async createLoan( - folksChainId: FolksChainId, provider: Client, sender: Address, - network: NetworkType, - accountId: Hex, - loanId: Hex, - loanTypeId: number, - adapters: MessageAdapters, - ): Promise { - // get intended spoke - const spokeChain = getSpokeChain(folksChainId, network); - - // use raw function - return prepareRaw.createLoan( - provider, - sender, - network, - accountId, - loanId, - loanTypeId, - adapters, - spokeChain, - ); - }, - - async deleteLoan( - folksChainId: FolksChainId, - provider: Client, - sender: Address, - network: NetworkType, - accountId: Hex, - loanId: Hex, - adapters: MessageAdapters, - ) { - // get intended spoke - const spokeChain = getSpokeChain(folksChainId, network); - - // use raw function - return prepareRaw.deleteLoan( - provider, - sender, - network, - accountId, - loanId, - adapters, - spokeChain, - ); - }, - - async deposit( - folksChainId: FolksChainId, - provider: Client, - sender: Address, - network: NetworkType, - accountId: Hex, - loanId: Hex, - folksTokenId: FolksTokenId, - amount: bigint, - adapters: MessageAdapters, - ) { - // get intended spoke - const spokeChain = getSpokeChain(folksChainId, network); - - // use raw function - return prepareRaw.deposit( - provider, - sender, - network, - accountId, - loanId, - folksTokenId, - amount, - adapters, - spokeChain, - ); - }, - - async withdraw( - folksChainId: FolksChainId, - provider: Client, - sender: Address, - network: NetworkType, - accountId: Hex, - loanId: Hex, - folksTokenId: FolksTokenId, - amount: bigint, - isFAmount: boolean, - receiverFolksChainId: FolksChainId, - adapters: MessageAdapters, - returnAdapterFees: bigint, - ) { - // get intended spoke - const spokeChain = getSpokeChain(folksChainId, network); - - // use raw function - return prepareRaw.withdraw( - provider, - sender, - network, - accountId, - loanId, - folksTokenId, - amount, - isFAmount, - receiverFolksChainId, - adapters, - returnAdapterFees, - spokeChain, - ); - }, -}; - -export const prepareRaw = { - async createLoan( - provider: Client, - sender: Address, - network: NetworkType, + messageToSend: MessageToSend, accountId: Hex, loanId: Hex, loanTypeId: number, @@ -190,32 +56,13 @@ export const prepareRaw = { spokeChain.bridgeRouterAddress, ); - const hubChain = getHubChain(network); - - // construct message - const params = DEFAULT_MESSAGE_PARAMS(adapters); - const message: MessageToSend = { - params, - sender: spokeChain.spokeCommonAddress, - destinationChainId: hubChain.folksChainId, - handler: hubChain.hubAddress, - payload: buildMessagePayload( - Action.CreateLoan, - accountId, - getRandomGenericAddress(), - concat([loanId, convertNumberToBytes(loanTypeId, UINT16_LENGTH)]), - ), - finalityLevel: FINALITY.IMMEDIATE, - extraArgs: "0x", - }; - // get adapter fees const returnAdapterFee = BigInt(0); - const adapterFee = await bridgeRouter.read.getSendFee([message]); + const adapterFee = await bridgeRouter.read.getSendFee([messageToSend]); // get gas limits const gasLimit = await spokeCommon.estimateGas.createLoan( - [params, accountId, loanId, loanTypeId], + [messageToSend.params, accountId, loanId, loanTypeId], { value: adapterFee, ...transactionOptions, @@ -238,13 +85,13 @@ export const prepareRaw = { async deleteLoan( provider: Client, sender: Address, - network: NetworkType, + messageToSend: MessageToSend, accountId: Hex, loanId: Hex, adapters: MessageAdapters, spokeChain: SpokeChain, transactionOptions: EstimateGasParameters = { account: sender }, - ): Promise { + ) { const spokeCommonAddress = spokeChain.spokeCommonAddress; const spokeCommon = getSpokeCommonContract(provider, spokeCommonAddress); @@ -253,32 +100,13 @@ export const prepareRaw = { spokeChain.bridgeRouterAddress, ); - const hubChain = getHubChain(network); - - // construct message - const params = DEFAULT_MESSAGE_PARAMS(adapters); - const message: MessageToSend = { - params, - sender: spokeChain.spokeCommonAddress, - destinationChainId: hubChain.folksChainId, - handler: hubChain.hubAddress, - payload: buildMessagePayload( - Action.DeleteLoan, - accountId, - getRandomGenericAddress(), - loanId, - ), - finalityLevel: FINALITY.IMMEDIATE, - extraArgs: "0x", - }; - // get adapter fees const returnAdapterFee = BigInt(0); - const adapterFee = await bridgeRouter.read.getSendFee([message]); + const adapterFee = await bridgeRouter.read.getSendFee([messageToSend]); // get gas limits const gasLimit = await spokeCommon.estimateGas.deleteLoan( - [params, accountId, loanId], + [messageToSend.params, accountId, loanId], { value: adapterFee, ...transactionOptions, @@ -301,18 +129,15 @@ export const prepareRaw = { async deposit( provider: Client, sender: Address, - network: NetworkType, + messageToSend: MessageToSend, accountId: Hex, loanId: Hex, - folksTokenId: FolksTokenId, amount: bigint, adapters: MessageAdapters, spokeChain: SpokeChain, + spokeTokenData: SpokeTokenData, transactionOptions: EstimateGasParameters = { account: sender }, - ): Promise { - const spokeTokenData = getSpokeTokenData(spokeChain, folksTokenId); - const hubTokenData = getHubTokenData(folksTokenId, network); - + ) { const spokeToken = getSpokeTokenContract( provider, spokeTokenData.spokeAddress, @@ -322,40 +147,13 @@ export const prepareRaw = { spokeChain.bridgeRouterAddress, ); - const hubChain = getHubChain(network); - - // construct message - const params = DEFAULT_MESSAGE_PARAMS(adapters); - const message: MessageToSend = { - params, - sender: spokeToken.address, - destinationChainId: hubChain.folksChainId, - handler: hubChain.hubAddress, - payload: buildMessagePayload( - Action.Deposit, - accountId, - getRandomGenericAddress(), - concat([ - loanId, - convertNumberToBytes(hubTokenData.poolId, UINT8_LENGTH), - convertNumberToBytes(amount, UINT256_LENGTH), - ]), - ), - finalityLevel: FINALITY.FINALISED, - extraArgs: getSendTokenExtraArgsWhenAdding( - spokeTokenData, - hubTokenData, - amount, - ), - }; - // get adapter fees const returnAdapterFee = BigInt(0); - const adapterFee = await bridgeRouter.read.getSendFee([message]); + const adapterFee = await bridgeRouter.read.getSendFee([messageToSend]); // get gas limits const gasLimit = await spokeToken.estimateGas.deposit( - [params, accountId, loanId, amount], + [messageToSend.params, accountId, loanId, amount], { value: adapterFee, ...transactionOptions, @@ -378,6 +176,7 @@ export const prepareRaw = { async withdraw( provider: Client, sender: Address, + messageToSend: MessageToSend, network: NetworkType, accountId: Hex, loanId: Hex, @@ -386,11 +185,9 @@ export const prepareRaw = { isFAmount: boolean, receiverFolksChainId: FolksChainId, adapters: MessageAdapters, - returnAdapterFee: bigint, spokeChain: SpokeChain, transactionOptions: EstimateGasParameters = { account: sender }, - ): Promise { - const spokeTokenData = getSpokeTokenData(spokeChain, folksTokenId); + ) { const hubTokenData = getHubTokenData(folksTokenId, network); const spokeCommonAddress = spokeChain.spokeCommonAddress; @@ -400,39 +197,13 @@ export const prepareRaw = { spokeChain.bridgeRouterAddress, ); - const hubChain = getHubChain(network); - - // construct message incl return adapter fee needed - const params = DEFAULT_MESSAGE_PARAMS(adapters); - params.receiverValue = returnAdapterFee; - const message: MessageToSend = { - params, - sender: spokeTokenData.spokeAddress, - destinationChainId: hubChain.folksChainId, - handler: hubChain.hubAddress, - payload: buildMessagePayload( - Action.Withdraw, - accountId, - getRandomGenericAddress(), - concat([ - loanId, - convertNumberToBytes(hubTokenData.poolId, UINT8_LENGTH), - convertNumberToBytes(receiverFolksChainId, UINT16_LENGTH), - convertNumberToBytes(amount, UINT256_LENGTH), - convertBooleanToByte(isFAmount), - ]), - ), - finalityLevel: FINALITY.IMMEDIATE, - extraArgs: "0x", - }; - // get adapter fee - const adapterFee = await spokeBridgeRouter.read.getSendFee([message]); + const adapterFee = await spokeBridgeRouter.read.getSendFee([messageToSend]); // get gas limits const gasLimit = await spokeCommon.estimateGas.withdraw( [ - params, + messageToSend.params, accountId, loanId, hubTokenData.poolId, @@ -451,7 +222,7 @@ export const prepareRaw = { return { adapters, adapterFee, - returnAdapterFee, + returnAdapterFee: messageToSend.params.receiverValue, gasLimit, receiveGasLimit, returnReceiveGasLimit, diff --git a/src/common/types/message.ts b/src/common/types/message.ts index 8709ebf..7b98489 100644 --- a/src/common/types/message.ts +++ b/src/common/types/message.ts @@ -1,4 +1,6 @@ import type { FolksChainId, GenericAddress } from "./chain.js"; +import type { LoanType } from "./module.js"; +import type { TokenType } from "./token.js"; import type { FINALITY } from "../constants/message.js"; import type { Hex } from "viem"; @@ -59,6 +61,7 @@ export type MessageToSend = { // Data export type DefaultMessageData = "0x"; +// Data: account export type InviteAddressMessageData = { folksChainIdToInvite: FolksChainId; addressToInvite: GenericAddress; @@ -68,6 +71,42 @@ export type UnregisterAddressMessageData = { folksChainIdToUnregister: FolksChainId; }; +// Data: loan +export type CreateLoanMessageData = { + loanId: Hex; + loanTypeId: LoanType; +}; + +export type DeleteLoanMessageData = { + accountId: Hex; + loanId: Hex; +}; + +export type DepositMessageData = { + loanId: Hex; + poolId: number; + amount: bigint; +}; + +export type WithdrawMessageData = { + loanId: Hex; + poolId: number; + receiverFolksChainId: FolksChainId; + amount: bigint; + isFAmount: boolean; +}; + +// Extra args +export type DefaultExtraArgs = "0x"; + +// Extra args: loan +export type DepositExtraArgs = { + tokenType: TokenType; + spokeTokenAddress: GenericAddress; + hubPoolAddress: GenericAddress; + amount: bigint; +}; + // Params export type DefaultMessageDataParams = { action: @@ -75,11 +114,7 @@ export type DefaultMessageDataParams = { | Action.AcceptInviteAddress | Action.AddDelegate | Action.RemoveDelegate - | Action.CreateLoan - | Action.DeleteLoan - | Action.Deposit | Action.DepositFToken - | Action.Withdraw | Action.WithdrawFToken | Action.Borrow | Action.Repay @@ -88,22 +123,56 @@ export type DefaultMessageDataParams = { | Action.SwitchBorrowType | Action.SendToken; data: DefaultMessageData; + extraArgs: DefaultExtraArgs; }; +// Params: account export type InviteAddressMessageDataParams = { action: Action.InviteAddress; data: InviteAddressMessageData; + extraArgs: DefaultExtraArgs; }; export type UnregisterAddressMessageDataParams = { action: Action.UnregisterAddress; data: UnregisterAddressMessageData; + extraArgs: DefaultExtraArgs; +}; + +// Params: loan +export type CreateLoanMessageDataParams = { + action: Action.CreateLoan; + data: CreateLoanMessageData; + extraArgs: DefaultExtraArgs; +}; + +export type DeleteLoanMessageDataParams = { + action: Action.DeleteLoan; + data: DeleteLoanMessageData; + extraArgs: DefaultExtraArgs; +}; + +export type WithdrawMessageDataParams = { + action: Action.Withdraw; + params: Partial; + data: WithdrawMessageData; + extraArgs: DefaultExtraArgs; +}; + +export type DepositMessageDataParams = { + action: Action.Deposit; + data: DepositMessageData; + extraArgs: DepositExtraArgs; }; export type MessageDataParams = | DefaultMessageDataParams | InviteAddressMessageDataParams - | UnregisterAddressMessageDataParams; + | UnregisterAddressMessageDataParams + | CreateLoanMessageDataParams + | DeleteLoanMessageDataParams + | DepositMessageDataParams + | WithdrawMessageDataParams; export type MessageToSendBuilderParams = { accountId: Hex; diff --git a/src/common/utils/chain.ts b/src/common/utils/chain.ts index a0a2cdd..7867ffa 100644 --- a/src/common/utils/chain.ts +++ b/src/common/utils/chain.ts @@ -5,6 +5,7 @@ import type { NetworkType, FolksChain, SpokeChain, + GenericAddress, } from "../types/chain.js"; import type { FolksTokenId, SpokeTokenData } from "../types/token.js"; @@ -98,3 +99,12 @@ export function assertSpokeChainSupportFolksToken( `Folks Token ${folksTokenId} is not supported on Folks Chain ${folksChainId}`, ); } + +export function getSpokeTokenDataTokenAddress( + spokeTokenData: SpokeTokenData, +): GenericAddress { + if (spokeTokenData.tokenAddress) return spokeTokenData.tokenAddress; + throw new Error( + `Token address not found for spokeTokenData for folks token: ${spokeTokenData.folksTokenId} in spoke: ${spokeTokenData.spokeAddress}`, + ); +} diff --git a/src/common/utils/messages.ts b/src/common/utils/messages.ts index e674944..82bec6d 100644 --- a/src/common/utils/messages.ts +++ b/src/common/utils/messages.ts @@ -1,74 +1,11 @@ -import { concat } from "viem"; - import { buildEvmMessageToSend } from "../../chains/evm/common/utils/message.js"; import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; -import { UINT256_LENGTH } from "../constants/bytes.js"; import { ChainType } from "../types/chain.js"; -import { TokenType } from "../types/token.js"; - -import { isGenericAddress } from "./address.js"; -import { convertNumberToBytes } from "./bytes.js"; -import type { HubTokenData } from "../../chains/evm/hub/types/token.js"; -import type { GenericAddress } from "../types/chain.js"; import type { MessageToSend, MessageToSendBuilderParams, } from "../types/message.js"; -import type { SpokeTokenData } from "../types/token.js"; -import type { Hex } from "viem"; - -export function extraArgsToBytes( - tokenAddr: GenericAddress, - recipientAddr: GenericAddress, - amount: bigint, -): Hex { - if (!isGenericAddress(tokenAddr)) throw Error("Unknown token address format"); - if (!isGenericAddress(recipientAddr)) - throw Error("Unknown recipient address format"); - - return concat([ - "0x1b366e79", - tokenAddr, - recipientAddr, - convertNumberToBytes(amount, UINT256_LENGTH), - ]); -} - -export function getSendTokenExtraArgsWhenAdding( - spokeTokenData: SpokeTokenData, - hubTokenData: HubTokenData, - amount: bigint, -): Hex { - const { tokenType } = spokeTokenData; - if (tokenType === TokenType.NATIVE || tokenType === TokenType.ERC20) - return "0x"; - if (spokeTokenData.tokenAddress === null) - throw Error("Unknown token address"); - - return extraArgsToBytes( - spokeTokenData.tokenAddress, - hubTokenData.poolAddress, - amount, - ); -} - -export function getSendTokenExtraArgsWhenRemoving( - spokeTokenData: SpokeTokenData, - hubTokenData: HubTokenData, - amount: bigint, -): Hex { - const { tokenType } = hubTokenData; - if (tokenType === TokenType.NATIVE || tokenType === TokenType.ERC20) - return "0x"; - if (hubTokenData.tokenAddress === null) throw Error("Unknown token address"); - - return extraArgsToBytes( - hubTokenData.tokenAddress, - spokeTokenData.spokeAddress, - BigInt(amount), - ); -} export function buildMessageToSend( chainType: ChainType, diff --git a/src/xchain/modules/folks-account.ts b/src/xchain/modules/folks-account.ts index f0cc21b..d7583f8 100644 --- a/src/xchain/modules/folks-account.ts +++ b/src/xchain/modules/folks-account.ts @@ -50,6 +50,7 @@ export const prepare = { destinationChainId: hubChain.folksChainId, handler: hubChain.hubAddress, data: "0x", + extraArgs: "0x", }); switch (folksChain.chainType) { @@ -98,6 +99,7 @@ export const prepare = { destinationChainId: hubChain.folksChainId, handler: hubChain.hubAddress, data, + extraArgs: "0x", }); switch (folksChain.chainType) { @@ -139,6 +141,7 @@ export const prepare = { destinationChainId: hubChain.folksChainId, handler: hubChain.hubAddress, data: "0x", + extraArgs: "0x", }); switch (folksChain.chainType) { @@ -185,6 +188,7 @@ export const prepare = { destinationChainId: hubChain.folksChainId, handler: hubChain.hubAddress, data, + extraArgs: "0x", }); switch (folksChain.chainType) { diff --git a/src/xchain/modules/folks-loan.ts b/src/xchain/modules/folks-loan.ts index b17f2a4..44b8311 100644 --- a/src/xchain/modules/folks-loan.ts +++ b/src/xchain/modules/folks-loan.ts @@ -2,10 +2,12 @@ import { getSignerAddress } from "../../chains/evm/common/utils/chain.js"; import { FolksHubLoan } from "../../chains/evm/hub/modules/index.js"; import { assertLoanTypeSupported, + getHubChain, getHubTokenData, } from "../../chains/evm/hub/utils/chain.js"; import { FolksEvmLoan } from "../../chains/evm/spoke/modules/index.js"; import { ChainType } from "../../common/types/chain.js"; +import { Action } from "../../common/types/message.js"; import { assertAdapterSupportsDataMessage, assertAdapterSupportsTokenMessage, @@ -13,12 +15,23 @@ import { import { assertSpokeChainSupportFolksToken, assertSpokeChainSupported, + getSpokeChain, + getSpokeTokenData, + getSpokeTokenDataTokenAddress, } from "../../common/utils/chain.js"; +import { buildMessageToSend } from "../../common/utils/messages.js"; import { exhaustiveCheck } from "../../utils/exhaustive-check.js"; import { FolksCore } from "../core/folks-core.js"; import type { FolksChainId } from "../../common/types/chain.js"; -import type { MessageAdapters } from "../../common/types/message.js"; +import type { + CreateLoanMessageData, + DeleteLoanMessageData, + DepositExtraArgs, + DepositMessageData, + MessageAdapters, + WithdrawMessageData, +} from "../../common/types/message.js"; import type { LoanType, PrepareCreateLoanCall, @@ -41,18 +54,38 @@ export const prepare = { folksChain.folksChainId, adapters.adapterId, ); + const spokeChain = getSpokeChain( + folksChain.folksChainId, + folksChain.network, + ); + const hubChain = getHubChain(folksChain.network); + + const data: CreateLoanMessageData = { + loanId, + loanTypeId, + }; + const messageToSend = buildMessageToSend(folksChain.chainType, { + accountId, + adapters, + action: Action.CreateLoan, + sender: spokeChain.spokeCommonAddress, + destinationChainId: hubChain.folksChainId, + handler: hubChain.hubAddress, + data, + extraArgs: "0x", + }); switch (folksChain.chainType) { case ChainType.EVM: return await FolksEvmLoan.prepare.createLoan( - folksChain.folksChainId, FolksCore.getProvider(folksChain.folksChainId), getSignerAddress(FolksCore.getSigner()), - folksChain.network, + messageToSend, accountId, loanId, loanTypeId, adapters, + spokeChain, ); default: return exhaustiveCheck(folksChain.chainType); @@ -66,17 +99,37 @@ export const prepare = { folksChain.folksChainId, adapters.adapterId, ); + const spokeChain = getSpokeChain( + folksChain.folksChainId, + folksChain.network, + ); + const hubChain = getHubChain(folksChain.network); + + const data: DeleteLoanMessageData = { + accountId, + loanId, + }; + const messageToSend = buildMessageToSend(folksChain.chainType, { + accountId, + adapters, + action: Action.DeleteLoan, + sender: spokeChain.spokeCommonAddress, + destinationChainId: hubChain.folksChainId, + handler: hubChain.hubAddress, + data, + extraArgs: "0x", + }); switch (folksChain.chainType) { case ChainType.EVM: return await FolksEvmLoan.prepare.deleteLoan( - folksChain.folksChainId, FolksCore.getProvider(folksChain.folksChainId), getSignerAddress(FolksCore.getSigner()), - folksChain.network, + messageToSend, accountId, loanId, adapters, + spokeChain, ); default: return exhaustiveCheck(folksChain.chainType); @@ -103,19 +156,49 @@ export const prepare = { folksChain.network, ); assertLoanTypeSupported(loanType, folksTokenId, folksChain.network); + const spokeChain = getSpokeChain( + folksChain.folksChainId, + folksChain.network, + ); + const hubChain = getHubChain(folksChain.network); + + const spokeTokenData = getSpokeTokenData(spokeChain, folksTokenId); + const hubTokenData = getHubTokenData(folksTokenId, folksChain.network); + + const data: DepositMessageData = { + loanId, + poolId: hubTokenData.poolId, + amount, + }; + const extraArgs: DepositExtraArgs = { + tokenType: spokeTokenData.tokenType, + spokeTokenAddress: getSpokeTokenDataTokenAddress(spokeTokenData), + hubPoolAddress: hubTokenData.poolAddress, + amount, + }; + const messageToSend = buildMessageToSend(folksChain.chainType, { + accountId, + adapters, + action: Action.Deposit, + sender: spokeChain.spokeCommonAddress, + destinationChainId: hubChain.folksChainId, + handler: hubChain.hubAddress, + data, + extraArgs, + }); switch (folksChain.chainType) { case ChainType.EVM: return await FolksEvmLoan.prepare.deposit( - folksChain.folksChainId, FolksCore.getProvider(folksChain.folksChainId), getSignerAddress(FolksCore.getSigner()), - folksChain.network, + messageToSend, accountId, loanId, - folksTokenId, amount, adapters, + spokeChain, + spokeTokenData, ); default: return exhaustiveCheck(folksChain.chainType); @@ -162,12 +245,39 @@ export const prepare = { adapters, ); + const spokeChain = getSpokeChain( + folksChain.folksChainId, + folksChain.network, + ); + const hubChain = getHubChain(folksChain.network); + + const hubTokenData = getHubTokenData(folksTokenId, folksChain.network); + + const data: WithdrawMessageData = { + loanId, + poolId: hubTokenData.poolId, + receiverFolksChainId: receiverFolksChainId, + amount, + isFAmount, + }; + const messageToSend = buildMessageToSend(folksChain.chainType, { + accountId, + adapters, + action: Action.Withdraw, + sender: spokeChain.spokeCommonAddress, + destinationChainId: hubChain.folksChainId, + handler: hubChain.hubAddress, + params: { receiverValue: await getReturnAdapterFees() }, + data, + extraArgs: "0x", + }); + switch (folksChain.chainType) { case ChainType.EVM: return await FolksEvmLoan.prepare.withdraw( - folksChain.folksChainId, FolksCore.getProvider(folksChain.folksChainId), getSignerAddress(FolksCore.getSigner()), + messageToSend, folksChain.network, accountId, loanId, @@ -176,7 +286,7 @@ export const prepare = { isFAmount, receiverFolksChainId, adapters, - await getReturnAdapterFees(), + spokeChain, ); default: return exhaustiveCheck(folksChain.chainType);