-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiple RPC endpoint support #2394
Changes from 18 commits
4654b18
d9f728a
af8ff3c
891e249
eab7786
1493e33
6b9dc81
dd9ae1f
ae84511
a214de9
091a710
a7ef87a
1551fc6
f973f50
470a409
6ff7e7c
bb8c0cf
2566ad7
6cf73e0
c3cf7c8
a5a0c56
b050d4f
e8a6074
e8a66c1
42ebed4
3126dc5
be6b4cf
5ca0a25
ea806c3
713e77c
85efdea
7226612
b24b6b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,10 +1,10 @@ | ||||||||||||||
import type { JsonRpcSigner, TransactionRequest } from '@ethersproject/providers' | ||||||||||||||
import { ApiRx } from '@polkadot/api' | ||||||||||||||
import { AddressOrPair, SubmittableExtrinsic } from '@polkadot/api/types' | ||||||||||||||
import { SignedBlock } from '@polkadot/types/interfaces' | ||||||||||||||
import { EventRecord, SignedBlock } from '@polkadot/types/interfaces' | ||||||||||||||
import { DefinitionRpc, DefinitionsCall, ISubmittableResult, Signer } from '@polkadot/types/types' | ||||||||||||||
import { hexToBn } from '@polkadot/util' | ||||||||||||||
import { sortAddresses } from '@polkadot/util-crypto' | ||||||||||||||
import type { JsonRpcSigner, TransactionRequest } from 'ethers' | ||||||||||||||
import 'isomorphic-fetch' | ||||||||||||||
import { | ||||||||||||||
Observable, | ||||||||||||||
|
@@ -17,6 +17,7 @@ import { | |||||||||||||
firstValueFrom, | ||||||||||||||
from, | ||||||||||||||
map, | ||||||||||||||
mergeAll, | ||||||||||||||
mergeWith, | ||||||||||||||
of, | ||||||||||||||
share, | ||||||||||||||
|
@@ -295,19 +296,69 @@ type Events = ISubmittableResult['events'] | |||||||||||||
|
||||||||||||||
const txCompletedEvents: Record<string, Subject<Events>> = {} | ||||||||||||||
const blockEvents: Record<string, Observable<Events>> = {} | ||||||||||||||
let parachainUrlCache: string | null = null | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this won't work if there's different instances of the Centrifuge class. It's why the above caches are keyed by parachain url. I think it would be better to keep this as a property on the class. It would need to be copied over when the instance gets cloned though, like in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agreed initially that the variable should be stored as a class property, but I noticed in doing so that the url is actually not cached at all and each time the instance is recreated the class property loses it's value. Like this we can store the value across instances which IMO is what we want for this feature instead of constantly opening a ws to check it's health and closing it again. Like this the cached url will only be reset when the page reloads and we can limit the number of ws opening requests to max as many urls as we have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about making this a |
||||||||||||||
|
||||||||||||||
export class CentrifugeBase { | ||||||||||||||
config: Config | ||||||||||||||
parachainUrl: string | ||||||||||||||
relayChainUrl: string | ||||||||||||||
subqueryUrl: string | ||||||||||||||
rpcEndpoints: string[] | ||||||||||||||
|
||||||||||||||
constructor(config: UserProvidedConfig = {}) { | ||||||||||||||
this.config = { ...defaultConfig, ...config } | ||||||||||||||
this.parachainUrl = this.config.network === 'centrifuge' ? this.config.centrifugeWsUrl : this.config.altairWsUrl | ||||||||||||||
this.relayChainUrl = this.config.network === 'centrifuge' ? this.config.polkadotWsUrl : this.config.kusamaWsUrl | ||||||||||||||
this.subqueryUrl = | ||||||||||||||
this.config.network === 'centrifuge' ? this.config.centrifugeSubqueryUrl : this.config.altairSubqueryUrl | ||||||||||||||
this.rpcEndpoints = this.config.centrifugeWsUrl.split(',').map((url) => url.trim()) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private async findHealthyWs(): Promise<string | null> { | ||||||||||||||
for (const url of this.rpcEndpoints) { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would use Promise.any here |
||||||||||||||
const isHealthy = await this.checkWsHealth(url) | ||||||||||||||
if (isHealthy) { | ||||||||||||||
console.log(`Connection to ${url} established`) | ||||||||||||||
return url | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
console.error('Error: No healthy parachain URL found') | ||||||||||||||
return null | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private checkWsHealth(url: string, timeoutMs: number = 5000): Promise<boolean> { | ||||||||||||||
return new Promise((resolve) => { | ||||||||||||||
const ws = new WebSocket(url) | ||||||||||||||
const timer = setTimeout(() => { | ||||||||||||||
ws.close() | ||||||||||||||
console.log(`Connection to ${url} timed out`) | ||||||||||||||
resolve(false) | ||||||||||||||
}, timeoutMs) | ||||||||||||||
|
||||||||||||||
ws.onopen = () => { | ||||||||||||||
clearTimeout(timer) | ||||||||||||||
ws.close() | ||||||||||||||
resolve(true) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
ws.onerror = () => { | ||||||||||||||
clearTimeout(timer) | ||||||||||||||
ws.close() | ||||||||||||||
console.log(`Connection to ${url} failed`) | ||||||||||||||
resolve(false) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if using Promise.any above, you can reject here instead |
||||||||||||||
} | ||||||||||||||
}) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private async getCachedParachainUrl(): Promise<string> { | ||||||||||||||
const cachedUrl = parachainUrlCache | ||||||||||||||
if (cachedUrl) { | ||||||||||||||
return cachedUrl | ||||||||||||||
} | ||||||||||||||
parachainUrlCache = await this.findHealthyWs() | ||||||||||||||
if (!parachainUrlCache) { | ||||||||||||||
throw new Error('No healthy parachain URL available') | ||||||||||||||
} | ||||||||||||||
return parachainUrlCache | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
async getChainId() { | ||||||||||||||
|
@@ -459,7 +510,13 @@ export class CentrifugeBase { | |||||||||||||
}), | ||||||||||||||
tap((result) => { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||||||||||||
options?.onStatusChange?.(result) | ||||||||||||||
if (result.status === 'InBlock') this.getTxCompletedEvents().next(result.events) | ||||||||||||||
if (result.status === 'InBlock') { | ||||||||||||||
from(this.getTxCompletedEvents()) | ||||||||||||||
.pipe(take(1)) | ||||||||||||||
.subscribe((subject) => { | ||||||||||||||
subject.next(result.events) | ||||||||||||||
}) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
} | ||||||||||||||
}), | ||||||||||||||
takeWhile((result) => { | ||||||||||||||
return result.status !== 'InBlock' && !result.error | ||||||||||||||
|
@@ -596,39 +653,50 @@ export class CentrifugeBase { | |||||||||||||
} | ||||||||||||||
|
||||||||||||||
getBlockEvents() { | ||||||||||||||
if (blockEvents[this.parachainUrl]) return blockEvents[this.parachainUrl] | ||||||||||||||
const $api = this.getApi() | ||||||||||||||
|
||||||||||||||
return (blockEvents[this.parachainUrl] = $api.pipe( | ||||||||||||||
switchMap((api) => | ||||||||||||||
api.queryMulti([api.query.system.events, api.query.system.number]).pipe( | ||||||||||||||
bufferCount(2, 1), // Delay the events by one block, to make sure storage has been updated | ||||||||||||||
filter(([[events]]) => !!(events as any)?.length), | ||||||||||||||
map(([[events]]) => events as any) | ||||||||||||||
) | ||||||||||||||
), | ||||||||||||||
share() | ||||||||||||||
)) | ||||||||||||||
return from(this.getCachedParachainUrl()).pipe( | ||||||||||||||
switchMap((url) => { | ||||||||||||||
if (blockEvents[url]) return blockEvents[url] | ||||||||||||||
const $api = this.getApi() | ||||||||||||||
|
||||||||||||||
return (blockEvents[url] = $api.pipe( | ||||||||||||||
switchMap((api) => | ||||||||||||||
api.queryMulti([api.query.system.events, api.query.system.number]).pipe( | ||||||||||||||
bufferCount(2, 1), | ||||||||||||||
filter(([[events]]) => !!(events as any)?.length), | ||||||||||||||
map(([[events]]) => events as any) | ||||||||||||||
) | ||||||||||||||
), | ||||||||||||||
share() | ||||||||||||||
)) | ||||||||||||||
}) | ||||||||||||||
) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
getTxCompletedEvents() { | ||||||||||||||
return txCompletedEvents[this.parachainUrl] || (txCompletedEvents[this.parachainUrl] = new Subject()) | ||||||||||||||
async getTxCompletedEvents() { | ||||||||||||||
const parachainUrl = await this.getCachedParachainUrl() | ||||||||||||||
return txCompletedEvents[parachainUrl] || (txCompletedEvents[parachainUrl] = new Subject<EventRecord[]>()) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
getEvents() { | ||||||||||||||
return this.getBlockEvents().pipe( | ||||||||||||||
mergeWith(this.getTxCompletedEvents()), | ||||||||||||||
mergeWith(from(this.getTxCompletedEvents()).pipe(mergeAll())), | ||||||||||||||
combineLatestWith(this.getApi()), | ||||||||||||||
map(([events, api]) => ({ events, api })) | ||||||||||||||
) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
getApi() { | ||||||||||||||
return getPolkadotApi(this.parachainUrl, parachainTypes, parachainRpcMethods, parachainRuntimeApi) | ||||||||||||||
return from(this.getCachedParachainUrl()).pipe( | ||||||||||||||
switchMap((parachainUrl) => | ||||||||||||||
getPolkadotApi(parachainUrl, parachainTypes, parachainRpcMethods, parachainRuntimeApi) | ||||||||||||||
) | ||||||||||||||
) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
getApiPromise() { | ||||||||||||||
return firstValueFrom(getPolkadotApi(this.parachainUrl, parachainTypes, parachainRpcMethods, parachainRuntimeApi)) | ||||||||||||||
return this.getCachedParachainUrl().then((parachainUrl) => | ||||||||||||||
firstValueFrom(getPolkadotApi(parachainUrl, parachainTypes, parachainRpcMethods, parachainRuntimeApi)) | ||||||||||||||
) | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
getRelayChainApi() { | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,8 +1,6 @@ | ||||||
import { BigNumber } from '@ethersproject/bignumber' | ||||||
import { Contract, ContractInterface } from '@ethersproject/contracts' | ||||||
import type { JsonRpcProvider, TransactionRequest, TransactionResponse } from '@ethersproject/providers' | ||||||
import BN from 'bn.js' | ||||||
import { signERC2612Permit } from 'eth-permit' | ||||||
import { Contract, Interface, Provider, TransactionRequest, TransactionResponse } from 'ethers' | ||||||
import set from 'lodash/set' | ||||||
import { combineLatestWith, firstValueFrom, from, map, startWith, switchMap } from 'rxjs' | ||||||
import { Centrifuge } from '../Centrifuge' | ||||||
|
@@ -16,7 +14,7 @@ const PERMIT_TYPEHASH = '0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c6 | |||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' | ||||||
|
||||||
type EvmQueryOptions = { | ||||||
rpcProvider?: JsonRpcProvider | ||||||
rpcProvider?: Provider | ||||||
} | ||||||
|
||||||
export type Permit = { | ||||||
|
@@ -25,8 +23,8 @@ export type Permit = { | |||||
s: string | ||||||
v: number | ||||||
} | ||||||
const toCurrencyBalance = (decimals: number) => (val: BigNumber) => new CurrencyBalance(val.toString(), decimals) | ||||||
const toTokenBalance = (decimals: number) => (val: BigNumber) => new TokenBalance(val.toString(), decimals) | ||||||
const toCurrencyBalance = (decimals: number) => (val: BigInt) => new CurrencyBalance(val.toString(), decimals) | ||||||
const toTokenBalance = (decimals: number) => (val: BigInt) => new TokenBalance(val.toString(), decimals) | ||||||
|
||||||
type LPConfig = { | ||||||
centrifugeRouter: string | ||||||
|
@@ -52,7 +50,7 @@ const config: Record<number, LPConfig> = { | |||||
} | ||||||
|
||||||
export function getLiquidityPoolsModule(inst: Centrifuge) { | ||||||
function contract(contractAddress: string, abi: ContractInterface, options?: EvmQueryOptions) { | ||||||
function contract(contractAddress: string, abi: Interface, options?: EvmQueryOptions) { | ||||||
const provider = inst.config.evmSigner ?? options?.rpcProvider | ||||||
if (!provider) throw new Error('Needs provider') | ||||||
return new Contract(contractAddress, abi, provider) | ||||||
|
@@ -124,7 +122,10 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
) { | ||||||
const [poolManager, poolId, trancheId] = args | ||||||
return pending( | ||||||
contract(poolManager, ABI.PoolManager).deployTranche(poolId, trancheId, { ...options, gasLimit: 5000000 }) | ||||||
contract(poolManager, new Interface(ABI.PoolManager)).deployTranche(poolId, trancheId, { | ||||||
...options, | ||||||
gasLimit: 5000000, | ||||||
}) | ||||||
) | ||||||
} | ||||||
|
||||||
|
@@ -134,7 +135,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
) { | ||||||
const [poolManager, poolId, trancheId, currencyAddress] = args | ||||||
return pending( | ||||||
contract(poolManager, ABI.PoolManager).deployVault(poolId, trancheId, currencyAddress, { | ||||||
contract(poolManager, new Interface(ABI.PoolManager)).deployVault(poolId, trancheId, currencyAddress, { | ||||||
...options, | ||||||
gasLimit: 5000000, | ||||||
}) | ||||||
|
@@ -146,15 +147,14 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
options: TransactionRequest = {} | ||||||
) { | ||||||
const [address, currencyAddress, amount] = args | ||||||
return pending(contract(currencyAddress, ABI.Currency).approve(address, amount, options)) | ||||||
return pending(contract(currencyAddress, new Interface(ABI.Currency)).approve(address, amount, options)) | ||||||
} | ||||||
|
||||||
async function signPermit(args: [spender: string, currencyAddress: string, amount: BN]) { | ||||||
const [spender, currencyAddress, amount] = args | ||||||
async function signPermit(args: [spender: string, currencyAddress: string, amount: BN, chainId: number]) { | ||||||
const [spender, currencyAddress, amount, chainId] = args | ||||||
if (!inst.config.evmSigner) throw new Error('EVM signer not set') | ||||||
|
||||||
let domainOrCurrency: any = currencyAddress | ||||||
const chainId = await inst.config.evmSigner.getChainId() | ||||||
if (currencyAddress.toLowerCase() === '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') { | ||||||
// USDC has custom version | ||||||
domainOrCurrency = { name: 'USD Coin', version: '2', chainId, verifyingContract: currencyAddress } | ||||||
|
@@ -177,7 +177,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
const [lpAddress, order] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending( | ||||||
contract(lpAddress, ABI.LiquidityPool).requestDeposit(order.toString(), user, user, { | ||||||
contract(lpAddress, new Interface(ABI.LiquidityPool)).requestDeposit(order.toString(), user, user, { | ||||||
...options, | ||||||
gasLimit: 300000, | ||||||
}) | ||||||
|
@@ -188,7 +188,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
const [lpAddress, order] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending( | ||||||
contract(lpAddress, ABI.LiquidityPool).requestRedeem(order.toString(), user, user, { | ||||||
contract(lpAddress, new Interface(ABI.LiquidityPool)).requestRedeem(order.toString(), user, user, { | ||||||
...options, | ||||||
gasLimit: 300000, | ||||||
}) | ||||||
|
@@ -213,32 +213,36 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
function cancelRedeemOrder(args: [lpAddress: string], options: TransactionRequest = {}) { | ||||||
const [lpAddress] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending(contract(lpAddress, ABI.LiquidityPool).cancelRedeemRequest(0, user, options)) | ||||||
return pending(contract(lpAddress, new Interface(ABI.LiquidityPool)).cancelRedeemRequest(0, user, options)) | ||||||
} | ||||||
|
||||||
function cancelInvestOrder(args: [lpAddress: string], options: TransactionRequest = {}) { | ||||||
const [lpAddress] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending(contract(lpAddress, ABI.LiquidityPool).cancelDepositRequest(0, user, options)) | ||||||
return pending(contract(lpAddress, new Interface(ABI.LiquidityPool)).cancelDepositRequest(0, user, options)) | ||||||
} | ||||||
|
||||||
function claimCancelDeposit(args: [lpAddress: string], options: TransactionRequest = {}) { | ||||||
const [lpAddress] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending(contract(lpAddress, ABI.LiquidityPool).claimCancelDepositRequest(0, user, user, options)) | ||||||
return pending( | ||||||
contract(lpAddress, new Interface(ABI.LiquidityPool)).claimCancelDepositRequest(0, user, user, options) | ||||||
) | ||||||
} | ||||||
|
||||||
function claimCancelRedeem(args: [lpAddress: string], options: TransactionRequest = {}) { | ||||||
const [lpAddress] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending(contract(lpAddress, ABI.LiquidityPool).claimCancelRedeemRequest(0, user, user, options)) | ||||||
return pending( | ||||||
contract(lpAddress, new Interface(ABI.LiquidityPool)).claimCancelRedeemRequest(0, user, user, options) | ||||||
) | ||||||
} | ||||||
|
||||||
function mint(args: [lpAddress: string, mint: BN, receiver?: string], options: TransactionRequest = {}) { | ||||||
const [lpAddress, mint, receiver] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending( | ||||||
contract(lpAddress, ABI.LiquidityPool).mint(mint.toString(), receiver ?? user, { | ||||||
contract(lpAddress, new Interface(ABI.LiquidityPool)).mint(mint.toString(), receiver ?? user, { | ||||||
...options, | ||||||
gasLimit: 200000, | ||||||
}) | ||||||
|
@@ -249,7 +253,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
const [lpAddress, withdraw, receiver] = args | ||||||
const user = inst.getSignerAddress('evm') | ||||||
return pending( | ||||||
contract(lpAddress, ABI.LiquidityPool).withdraw(withdraw.toString(), receiver ?? user, user, { | ||||||
contract(lpAddress, new Interface(ABI.LiquidityPool)).withdraw(withdraw.toString(), receiver ?? user, user, { | ||||||
...options, | ||||||
gasLimit: 200000, | ||||||
}) | ||||||
|
@@ -284,15 +288,15 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
|
||||||
async function getManagerFromRouter(args: [router: string], options?: EvmQueryOptions) { | ||||||
const [router] = args | ||||||
const gatewayAddress = await contract(router, ABI.Router, options).gateway() | ||||||
const managerAddress = await contract(gatewayAddress, ABI.Gateway, options).investmentManager() | ||||||
const gatewayAddress = await contract(router, new Interface(ABI.Router), options).gateway() | ||||||
const managerAddress = await contract(gatewayAddress, new Interface(ABI.Gateway), options).investmentManager() | ||||||
return managerAddress as string | ||||||
} | ||||||
|
||||||
async function getRecentLPEvents(args: [lpAddress: string, user: string], options?: EvmQueryOptions) { | ||||||
const [lpAddress, user] = args | ||||||
const blockNumber = await getProvider(options)!.getBlockNumber() | ||||||
const cont = contract(lpAddress, ABI.LiquidityPool, options) | ||||||
const cont = contract(lpAddress, new Interface(ABI.LiquidityPool), options) | ||||||
const depositFilter = cont.filters.DepositRequest(user) | ||||||
const redeemFilter = cont.filters.RedeemRequest(user) | ||||||
const cancelDepositFilter = cont.filters.CancelDepositRequest(user) | ||||||
|
@@ -324,7 +328,11 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
|
||||||
const currencies = await firstValueFrom(getDomainCurrencies([chainId])) | ||||||
|
||||||
const poolManager = (await contract(investmentManager, ABI.InvestmentManager, options).poolManager()) as string | ||||||
const poolManager = (await contract( | ||||||
investmentManager, | ||||||
new Interface(ABI.InvestmentManager), | ||||||
options | ||||||
).poolManager()) as string | ||||||
|
||||||
const poolData = await multicall<{ | ||||||
isActive: boolean | ||||||
|
@@ -373,7 +381,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
...(currencies.flatMap((currency) => ({ | ||||||
target: poolManager, | ||||||
call: ['function assetToId(address) view returns (uint128)', currency.address], | ||||||
returns: [[`currencyNeedsAdding[${currency.address}]`, (id: BigNumber) => id.isZero()]], | ||||||
returns: [[`currencyNeedsAdding[${currency.address}]`, (id: BigInt) => id === BigInt(0)]], | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
})) as Call[]), | ||||||
], | ||||||
{ | ||||||
|
@@ -421,7 +429,11 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
|
||||||
const currencies = await firstValueFrom(getDomainCurrencies([chainId])) | ||||||
|
||||||
const poolManager: string = await contract(managerAddress, ABI.InvestmentManager, options).poolManager() | ||||||
const poolManager: string = await contract( | ||||||
managerAddress, | ||||||
new Interface(ABI.InvestmentManager), | ||||||
options | ||||||
).poolManager() | ||||||
|
||||||
if (!currencies?.length) return [] | ||||||
|
||||||
|
@@ -471,7 +483,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
const currencyData = await multicall<{ | ||||||
currencies: { currencySupportsPermit?: boolean }[] | ||||||
trancheTokenSymbol: string | ||||||
trancheTokenDecimals: number | ||||||
trancheTokenDecimals: BigInt | ||||||
}>( | ||||||
[ | ||||||
...Object.values(currenciesByLpAddress).flatMap( | ||||||
|
@@ -509,7 +521,7 @@ export function getLiquidityPoolsModule(inst: Centrifuge) { | |||||
managerAddress, | ||||||
trancheTokenAddress: shareData.share, | ||||||
trancheTokenSymbol: currencyData.trancheTokenSymbol, | ||||||
trancheTokenDecimals: currencyData.trancheTokenDecimals, | ||||||
trancheTokenDecimals: Number(currencyData.trancheTokenDecimals), | ||||||
currencySupportsPermit: currencyData.currencies?.[i]?.currencySupportsPermit, | ||||||
})) | ||||||
return result | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,9 @@ | ||
["function investmentManager() view returns (address)"] | ||
[ | ||
{ | ||
"inputs": [], | ||
"name": "investmentManager", | ||
"outputs": [{ "internalType": "address", "name": "", "type": "address" }], | ||
"stateMutability": "view", | ||
"type": "function" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,9 @@ | ||
["function poolManager() view returns (address)"] | ||
[ | ||
{ | ||
"inputs": [], | ||
"name": "poolManager", | ||
"outputs": [{ "internalType": "contract IPoolManager", "name": "", "type": "address" }], | ||
"stateMutability": "view", | ||
"type": "function" | ||
} | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,10 @@ | ||
import { Contract } from '@ethersproject/contracts' | ||
import { TransactionRequest, TransactionResponse } from '@ethersproject/providers' | ||
import BN from 'bn.js' | ||
import { Contract, TransactionRequest, TransactionResponse } from 'ethers' | ||
import { from, map, startWith, switchMap } from 'rxjs' | ||
import { Centrifuge } from '../Centrifuge' | ||
import { TransactionOptions } from '../types' | ||
import { CurrencyBalance } from '../utils/BN' | ||
import { calculateOptimalSolution, Orders, State } from '../utils/solver/tinlakeSolver' | ||
import { Orders, State, calculateOptimalSolution } from '../utils/solver/tinlakeSolver' | ||
import { abis } from './tinlake/abi' | ||
|
||
const contracts: Record<string, Contract> = {} | ||
|
@@ -94,6 +93,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
) { | ||
const [tranche] = args | ||
return pending( | ||
// @ts-ignore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what are all these errors saying?? 😅😅😅 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The types just weren't inferred properly by the connected contract. When using the the ethers |
||
contract(contractAddresses, contractVersions, 'TINLAKE_CURRENCY').approve( | ||
contractAddresses[tranche === 'junior' ? 'JUNIOR_TRANCHE' : 'SENIOR_TRANCHE'], | ||
maxUint256, | ||
|
@@ -110,6 +110,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
) { | ||
const [tranche] = args | ||
return pending( | ||
// @ts-ignore | ||
contract(contractAddresses, contractVersions, tranche === 'junior' ? 'JUNIOR_TOKEN' : 'SENIOR_TOKEN').approve( | ||
contractAddresses[tranche === 'junior' ? 'JUNIOR_TRANCHE' : 'SENIOR_TRANCHE'], | ||
maxUint256, | ||
|
@@ -126,10 +127,12 @@ export function getTinlakeModule(inst: Centrifuge) { | |
) { | ||
const [tranche, order] = args | ||
return pending( | ||
// @ts-ignore | ||
contract( | ||
contractAddresses, | ||
contractVersions, | ||
tranche === 'junior' ? 'JUNIOR_OPERATOR' : 'SENIOR_OPERATOR' | ||
// @ts-ignore | ||
).supplyOrder(order.toString(), options) | ||
) | ||
} | ||
|
@@ -142,10 +145,12 @@ export function getTinlakeModule(inst: Centrifuge) { | |
) { | ||
const [tranche, order] = args | ||
return pending( | ||
// @ts-ignore | ||
contract( | ||
contractAddresses, | ||
contractVersions, | ||
tranche === 'junior' ? 'JUNIOR_OPERATOR' : 'SENIOR_OPERATOR' | ||
// @ts-ignore | ||
).redeemOrder(order.toString(), options) | ||
) | ||
} | ||
|
@@ -158,6 +163,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
) { | ||
const [tranche] = args | ||
return pending( | ||
// @ts-ignore | ||
contract(contractAddresses, contractVersions, tranche === 'junior' ? 'JUNIOR_OPERATOR' : 'SENIOR_OPERATOR')[ | ||
'disburse()' | ||
](options) | ||
|
@@ -181,25 +187,37 @@ export function getTinlakeModule(inst: Centrifuge) { | |
const reserve = toBN( | ||
await (beforeClosing | ||
? isMakerIntegrated | ||
? contract(contractAddresses, contractVersions, 'ASSESSOR').totalBalance() | ||
: contract(contractAddresses, contractVersions, 'RESERVE').totalBalance() | ||
: coordinator.epochReserve()) | ||
? // @ts-ignore | ||
contract(contractAddresses, contractVersions, 'ASSESSOR').totalBalance() | ||
: // @ts-ignore | ||
contract(contractAddresses, contractVersions, 'RESERVE').totalBalance() | ||
: // @ts-ignore | ||
coordinator.epochReserve()) | ||
) | ||
const netAssetValue = toBN( | ||
await (beforeClosing | ||
? contractVersions?.FEED === 2 | ||
? feed.latestNAV() | ||
: feed.approximatedNAV() | ||
: coordinator.epochNAV()) | ||
? // @ts-ignore | ||
feed.latestNAV() | ||
: // @ts-ignore | ||
feed.approximatedNAV() | ||
: // @ts-ignore | ||
coordinator.epochNAV()) | ||
) | ||
const seniorAsset = beforeClosing | ||
? isMakerIntegrated | ||
? toBN(await assessor.seniorDebt()).add(toBN(await assessor.seniorBalance())) | ||
: toBN(await assessor.seniorDebt_()).add(toBN(await assessor.seniorBalance_())) | ||
: toBN(await coordinator.epochSeniorAsset()) | ||
|
||
? // @ts-ignore | ||
toBN(await assessor.seniorDebt()).add(toBN(await assessor.seniorBalance())) | ||
: // @ts-ignore | ||
toBN(await assessor.seniorDebt_()).add(toBN(await assessor.seniorBalance_())) | ||
: // @ts-ignore | ||
toBN(await coordinator.epochSeniorAsset()) | ||
|
||
// @ts-ignore | ||
const minDropRatio = toBN(await assessor.minSeniorRatio()) | ||
// @ts-ignore | ||
const maxDropRatio = toBN(await assessor.maxSeniorRatio()) | ||
// @ts-ignore | ||
const maxReserve = toBN(await assessor.maxReserve()) | ||
|
||
return { reserve, netAssetValue, seniorAsset, minDropRatio, maxDropRatio, maxReserve } | ||
|
@@ -220,27 +238,36 @@ export function getTinlakeModule(inst: Centrifuge) { | |
const assessor = contract(contractAddresses, contractVersions, 'ASSESSOR') | ||
const feed = contract(contractAddresses, contractVersions, 'FEED') | ||
|
||
// @ts-ignore | ||
const epochNAV = toBN(await feed.currentNAV()) | ||
// @ts-ignore | ||
const epochReserve = toBN(await contract(contractAddresses, contractVersions, 'RESERVE').totalBalance()) | ||
const epochSeniorTokenPrice = toBN( | ||
// @ts-ignore | ||
await assessor['calcSeniorTokenPrice(uint256,uint256)'](epochNAV.toString(), epochReserve.toString()) | ||
) | ||
const epochJuniorTokenPrice = toBN( | ||
// @ts-ignore | ||
await assessor['calcJuniorTokenPrice(uint256,uint256)'](epochNAV.toString(), epochReserve.toString()) | ||
) | ||
|
||
return { | ||
// @ts-ignore | ||
dropInvest: toBN(await seniorTranche.totalSupply()), | ||
// @ts-ignore | ||
dropRedeem: toBN(await seniorTranche.totalRedeem()) | ||
.mul(epochSeniorTokenPrice) | ||
.div(e27), | ||
// @ts-ignore | ||
tinInvest: toBN(await juniorTranche.totalSupply()), | ||
// @ts-ignore | ||
tinRedeem: toBN(await juniorTranche.totalRedeem()) | ||
.mul(epochJuniorTokenPrice) | ||
.div(e27), | ||
} | ||
} | ||
const coordinator = contract(contractAddresses, contractVersions, 'COORDINATOR') | ||
// @ts-ignore | ||
const orderState = await coordinator.order() | ||
|
||
return { | ||
|
@@ -258,10 +285,16 @@ export function getTinlakeModule(inst: Centrifuge) { | |
const coordinator = contract(contractAddresses, contractVersions, 'COORDINATOR') | ||
|
||
return { | ||
// @ts-ignore | ||
dropInvest: toBN(await coordinator.weightSeniorSupply()), | ||
// @ts-ignore | ||
dropRedeem: toBN(await coordinator.weightSeniorRedeem()), | ||
// @ts-ignore | ||
tinInvest: toBN(await coordinator.weightJuniorSupply()), | ||
// @ts-ignore | ||
tinRedeem: toBN(await coordinator.weightJuniorRedeem()), | ||
// @ts-ignore | ||
seniorAsset: toBN(await coordinator.weightSeniorAsset()), | ||
} | ||
} | ||
|
||
|
@@ -272,6 +305,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
options: TransactionRequest = {} | ||
) { | ||
const coordinator = contract(contractAddresses, contractVersions, 'COORDINATOR') | ||
// @ts-ignore | ||
return pending(coordinator.closeEpoch({ ...options, gasLimit: 5000000 })) | ||
} | ||
|
||
|
@@ -283,6 +317,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
) { | ||
const submissionTx = (async () => { | ||
const coordinator = contract(contractAddresses, contractVersions, 'COORDINATOR') | ||
// @ts-ignore | ||
if ((await coordinator.submissionPeriod()) !== true) throw new Error('Not in submission period') | ||
const state = await getEpochState(contractAddresses, contractVersions, []) | ||
const orders = await getOrders(contractAddresses, contractVersions, []) | ||
|
@@ -292,6 +327,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
throw new Error('Failed to find a solution') | ||
} | ||
|
||
// @ts-ignore | ||
return coordinator.submitSolution( | ||
solution.dropRedeem.toString(), | ||
solution.tinRedeem.toString(), | ||
|
@@ -332,6 +368,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
throw new Error('Current epoch is still in the challenge period') | ||
} | ||
|
||
// @ts-ignore | ||
return coordinator.executeEpoch({ ...options, gasLimit: 2000000 }) | ||
})() | ||
return pending(tx) | ||
|
@@ -349,20 +386,23 @@ export function getTinlakeModule(inst: Centrifuge) { | |
contractVersions: TinlakeContractVersions | undefined | ||
) { | ||
const coordinator = contract(contractAddresses, contractVersions, 'COORDINATOR') | ||
|
||
// @ts-ignore | ||
const minChallengePeriodEnd = toBN(await coordinator.minChallengePeriodEnd()).toNumber() | ||
const latestBlockTimestamp = await getLatestBlockTimestamp() | ||
if (minChallengePeriodEnd !== 0) { | ||
if (minChallengePeriodEnd < latestBlockTimestamp) return 'challenge-period-ended' | ||
return 'in-challenge-period' | ||
} | ||
|
||
// @ts-ignore | ||
const submissionPeriod = await coordinator.submissionPeriod() | ||
if (submissionPeriod === true) { | ||
return 'in-submission-period' | ||
} | ||
|
||
// @ts-ignore | ||
const lastEpochClosed = toBN(await coordinator.lastEpochClosed()).toNumber() | ||
// @ts-ignore | ||
const minimumEpochTime = toBN(await coordinator.minimumEpochTime()).toNumber() | ||
if (submissionPeriod === false) { | ||
if (lastEpochClosed + minimumEpochTime < latestBlockTimestamp) return 'can-be-closed' | ||
|
@@ -385,6 +425,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
const [address] = args | ||
const coordinator = contract(contractAddresses, contractVersions, 'CLAIM_CFG') | ||
|
||
// @ts-ignore | ||
const tx = coordinator.accounts(address, options) | ||
return pending(tx) | ||
} | ||
|
@@ -398,6 +439,7 @@ export function getTinlakeModule(inst: Centrifuge) { | |
const [centAddress] = args | ||
const coordinator = contract(contractAddresses, contractVersions, 'CLAIM_CFG') | ||
|
||
// @ts-ignore | ||
const tx = coordinator.update(centAddress, options) | ||
return pending(tx) | ||
} | ||
|
Unchanged files with check annotations Beta
</ConnectionGuard> | ||
) : ( | ||
pool.tranches.map((tranche) => ( | ||
<a href={explorer.address(domain.trancheTokens[tranche.id])} target="_blank"> | ||
Check warning on line 178 in centrifuge-app/src/pages/IssuerPool/Investors/LiquidityPools.tsx GitHub Actions / ff-prod / build-app
Check warning on line 178 in centrifuge-app/src/pages/IssuerPool/Investors/LiquidityPools.tsx GitHub Actions / build-app
|
||
<Button variant="secondary" small style={{ width: '100%' }}> | ||
<Shelf gap={1}> | ||
<span>View {tranche.currency.symbol} token</span> |
React.useEffect(() => { | ||
financeForm.validateForm() | ||
}, [source]) | ||
Check warning on line 107 in centrifuge-app/src/pages/Loan/ExternalFinanceForm.tsx GitHub Actions / ff-prod / build-app
|
||
const financeFormRef = React.useRef<HTMLFormElement>(null) | ||
useFocusInvalidInput(financeForm, financeFormRef) |
React.useEffect(() => { | ||
repayForm.validateForm() | ||
}, [destination]) | ||
Check warning on line 149 in centrifuge-app/src/pages/Loan/ExternalRepayForm.tsx GitHub Actions / ff-prod / build-app
|
||
const repayFormRef = React.useRef<HTMLFormElement>(null) | ||
useFocusInvalidInput(repayForm, repayFormRef) |
React.useEffect(() => { | ||
financeForm.validateForm() | ||
}, [source]) | ||
Check warning on line 166 in centrifuge-app/src/pages/Loan/FinanceForm.tsx GitHub Actions / ff-prod / build-app
|
||
const financeFormRef = React.useRef<HTMLFormElement>(null) | ||
useFocusInvalidInput(financeForm, financeFormRef) |
React.useEffect(() => { | ||
repayForm.validateForm() | ||
}, [destination]) | ||
Check warning on line 184 in centrifuge-app/src/pages/Loan/RepayForm.tsx GitHub Actions / ff-prod / build-app
|
||
const repayFormRef = React.useRef<HTMLFormElement>(null) | ||
useFocusInvalidInput(repayForm, repayFormRef) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BigInt as a type has to be lowercase everywhere