Skip to content
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

Merged
merged 33 commits into from
Oct 16, 2024
Merged
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4654b18
Switch out JSON provider for default provider to auto switch to a bet…
sophialittlejohn Aug 19, 2024
d9f728a
Remove references to goerli
sophialittlejohn Aug 19, 2024
af8ff3c
Clean up
sophialittlejohn Aug 20, 2024
891e249
Use JsonRpcProvider as fallback if defaultprovider doesn't support ne…
sophialittlejohn Aug 20, 2024
eab7786
Fix console warnings
sophialittlejohn Aug 20, 2024
1493e33
Fix start script in main package
sophialittlejohn Aug 20, 2024
6b9dc81
Clean up
sophialittlejohn Aug 20, 2024
dd9ae1f
Upgrade ethersjs
sophialittlejohn Aug 26, 2024
ae84511
Do manual health check on rpcs instead of relying on getDefaultBrowse…
sophialittlejohn Aug 27, 2024
a214de9
Fix ts error
sophialittlejohn Aug 27, 2024
091a710
Fix more type errros from upgrade
sophialittlejohn Aug 28, 2024
a7ef87a
Add connection checks to centrifuge base before connecting to parachain
sophialittlejohn Aug 29, 2024
1551fc6
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Aug 30, 2024
f973f50
Clean up substrate network detection
sophialittlejohn Sep 4, 2024
470a409
Fix event parsing
sophialittlejohn Sep 5, 2024
6ff7e7c
Add tenderly node rpc key
sophialittlejohn Sep 5, 2024
bb8c0cf
Enable getDefaultProvider for evm instead of manual implementation
sophialittlejohn Sep 5, 2024
2566ad7
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Sep 5, 2024
6cf73e0
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Sep 9, 2024
c3cf7c8
Replace BigInt with bigint
sophialittlejohn Sep 11, 2024
a5a0c56
Update imports
sophialittlejohn Sep 11, 2024
b050d4f
Clean up for centrifuge base
sophialittlejohn Sep 11, 2024
e8a6074
Clean up
sophialittlejohn Sep 12, 2024
e8a66c1
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Sep 16, 2024
42ebed4
Remove outdated eth-permit in favor of custom code
sophialittlejohn Sep 16, 2024
3126dc5
Fix typescript errors in tinlake
sophialittlejohn Sep 16, 2024
be6b4cf
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Sep 16, 2024
5ca0a25
Use set for parachain url cache
sophialittlejohn Sep 16, 2024
ea806c3
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Oct 7, 2024
713e77c
Match with rpc endpoints
sophialittlejohn Oct 7, 2024
85efdea
Fix typo
sophialittlejohn Oct 7, 2024
7226612
Merge branch 'main' of github.com:centrifuge/apps into default-provider
sophialittlejohn Oct 16, 2024
b24b6b3
Fix import from ethers
sophialittlejohn Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions centrifuge-app/.env-config/.env.altair
Original file line number Diff line number Diff line change
@@ -20,3 +20,5 @@ REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=kAJkmGxAd6iqX9JjWTdhXgCf2PL1TAphTRYrmEqzBrYhwbXAn
REACT_APP_PRIME_IPFS_HASH=QmQfcuHM3EGrtpjhitDwJsgie5THLPtRNzvk7N3uymgHGc
REACT_APP_ONFINALITY_KEY=18704429-288d-4f55-bda8-8b60f4c53b96
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
4 changes: 3 additions & 1 deletion centrifuge-app/.env-config/.env.catalyst
Original file line number Diff line number Diff line change
@@ -19,4 +19,6 @@ REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=4bo2vNkwZtr2PuqppWwqya6dPC8MzxqZ4kgnAoTZyK
REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=kAJkmGxAd6iqX9JjWTdhXgCf2PL1TAphTRYrmEqzBrYhwbXAn
REACT_APP_PRIME_IPFS_HASH=QmQfcuHM3EGrtpjhitDwJsgie5THLPtRNzvk7N3uymgHGc
REACT_APP_PRIME_IPFS_HASH=QmQfcuHM3EGrtpjhitDwJsgie5THLPtRNzvk7N3uymgHGc
REACT_APP_ONFINALITY_KEY=18704429-288d-4f55-bda8-8b60f4c53b96
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
4 changes: 3 additions & 1 deletion centrifuge-app/.env-config/.env.demo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
REACT_APP_COLLATOR_WSS_URL=wss://fullnode-apps.demo.k-f.dev
REACT_APP_COLLATOR_WSS_URL=wss://fullnode.demo.k-f.dev,wss://fullnodey-apps.demo.k-f.dev
REACT_APP_DEFAULT_UNLIST_POOLS=true
REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucet-api-demo
REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/
@@ -20,3 +20,5 @@ REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=kAJkmGxAd6iqX9JjWTdhXgCf2PL1TAphTRYrmEqzBrYhwbXAn
REACT_APP_PRIME_IPFS_HASH=QmQfcuHM3EGrtpjhitDwJsgie5THLPtRNzvk7N3uymgHGc
REACT_APP_ONFINALITY_KEY=0e1c049f-d876-4e77-a45f-b5afdf5739b2
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
2 changes: 2 additions & 0 deletions centrifuge-app/.env-config/.env.development
Original file line number Diff line number Diff line change
@@ -20,3 +20,5 @@ REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=kAJkmGxAd6iqX9JjWTdhXgCf2PL1TAphTRYrmEqzBrYhwbXAn
REACT_APP_PRIME_IPFS_HASH=QmQfcuHM3EGrtpjhitDwJsgie5THLPtRNzvk7N3uymgHGc
REACT_APP_ONFINALITY_KEY=0e1c049f-d876-4e77-a45f-b5afdf5739b2
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
2 changes: 2 additions & 0 deletions centrifuge-app/.env-config/.env.example
Original file line number Diff line number Diff line change
@@ -20,3 +20,5 @@ REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=kAJkmGxAd6iqX9JjWTdhXgCf2PL1TAphTRYrmEqzBrYhwbXAn
REACT_APP_PRIME_IPFS_HASH=QmQfcuHM3EGrtpjhitDwJsgie5THLPtRNzvk7N3uymgHGc
REACT_APP_ONFINALITY_KEY=0e1c049f-d876-4e77-a45f-b5afdf5739b2
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
6 changes: 4 additions & 2 deletions centrifuge-app/.env-config/.env.ff-prod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
REACT_APP_COLLATOR_WSS_URL=wss://fullnode.parachain.centrifuge.io
REACT_APP_COLLATOR_WSS_URL=wss://fullnode.parachain.centrifuge.io,wss://centrifuge-parachain.api.onfinality.io/public-ws
REACT_APP_DEFAULT_UNLIST_POOLS=false
REACT_APP_FAUCET_URL=
REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/
@@ -19,4 +19,6 @@ REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpS
REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=4dpEcgqJRyJK3J8Es6v8ZfVntV7c64Ysgcjd4hYwyGoFPWbg
REACT_APP_PRIME_IPFS_HASH=QmS5gX2sk1ZCEyWnMjjmQyYkLTz27VVNGzVtCKWneABbj5
REACT_APP_PRIME_IPFS_HASH=QmS5gX2sk1ZCEyWnMjjmQyYkLTz27VVNGzVtCKWneABbj5
REACT_APP_ONFINALITY_KEY=18704429-288d-4f55-bda8-8b60f4c53b96
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
6 changes: 4 additions & 2 deletions centrifuge-app/.env-config/.env.production
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
REACT_APP_COLLATOR_WSS_URL=wss://apps.fullnode.centrifuge.io
REACT_APP_COLLATOR_WSS_URL=wss://apps.fullnode.centrifuge.io,wss://centrifuge-parachain.api.onfinality.io/public-ws
REACT_APP_DEFAULT_UNLIST_POOLS=false
REACT_APP_FAUCET_URL=
REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/
@@ -19,4 +19,6 @@ REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpS
REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a
REACT_APP_TINLAKE_SUBGRAPH_URL=https://api.goldsky.com/api/public/project_clhi43ef5g4rw49zwftsvd2ks/subgraphs/main/prod/gn
REACT_APP_TREASURY=4dpEcgqJRyJK3J8Es6v8ZfVntV7c64Ysgcjd4hYwyGoFPWbg
REACT_APP_PRIME_IPFS_HASH=QmS5gX2sk1ZCEyWnMjjmQyYkLTz27VVNGzVtCKWneABbj5
REACT_APP_PRIME_IPFS_HASH=QmS5gX2sk1ZCEyWnMjjmQyYkLTz27VVNGzVtCKWneABbj5
REACT_APP_ONFINALITY_KEY=84bb59f4-05cc-440b-8fd4-7917623a90c6
REACT_APP_TENDERLY_KEY=18aMTJlpNb1lElcYNkkunC
1 change: 1 addition & 0 deletions centrifuge-app/declarations.d.ts
Original file line number Diff line number Diff line change
@@ -17,4 +17,5 @@ interface ImportMetaEnv {
REACT_APP_ALCHEMY_KEY: string
REACT_APP_WALLETCONNECT_ID: string
REACT_APP_PRIME_IPFS_HASH: string
REACT_APP_ONFINALITY_KEY: string
}
3 changes: 0 additions & 3 deletions centrifuge-app/package.json
Original file line number Diff line number Diff line change
@@ -29,9 +29,6 @@
"@centrifuge/centrifuge-js": "workspace:*",
"@centrifuge/centrifuge-react": "workspace:*",
"@centrifuge/fabric": "workspace:*",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/contracts": "^5.6.0",
"@ethersproject/providers": "^5.6.0",
"@makerdao/multicall": "^0.12.0",
"@polkadot/react-identicon": "~3.1.4",
"@styled-system/css": "^5.1.5",
Original file line number Diff line number Diff line change
@@ -7,8 +7,8 @@ import {
useWallet,
} from '@centrifuge/centrifuge-react'
import { Dialog, Grid, Select, Stack, Text, TextInput } from '@centrifuge/fabric'
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress as isSubstrateAddress } from '@polkadot/util-crypto'
import { isAddress as isEvmAddress } from 'ethers'
import * as React from 'react'
import { useQuery } from 'react-query'
import { firstValueFrom } from 'rxjs'
3 changes: 1 addition & 2 deletions centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,6 @@ import {
import * as React from 'react'
import { useNavigate } from 'react-router-dom'
import { useTheme } from 'styled-components'
import { ethConfig } from '../../config'
import { formatBalance } from '../../utils/formatting'
import { useAddress } from '../../utils/useAddress'
import { useActiveDomains } from '../../utils/useLiquidityPools'
@@ -51,7 +50,7 @@ export function InvestRedeem({ poolId, trancheId, ...rest }: InvestRedeemProps)
const domainsWithAtLeastOneLP =
domains && domains.filter((domain) => Object.values(domain.liquidityPools[trancheId] ?? {}).some((p) => !!p))

const networks: Network[] = poolId.startsWith('0x') ? [ethConfig.network === 'goerli' ? 5 : 1] : ['centrifuge']
const networks: Network[] = poolId.startsWith('0x') ? [1] : ['centrifuge']
if (domainsWithAtLeastOneLP) {
networks.push(...domainsWithAtLeastOneLP.map((d) => d.chainId))
}
Original file line number Diff line number Diff line change
@@ -7,8 +7,8 @@ import {
useEvmProvider,
useWallet,
} from '@centrifuge/centrifuge-react'
import { TransactionRequest } from '@ethersproject/providers'
import BN from 'bn.js'
import { TransactionRequest } from 'ethers'
import * as React from 'react'
import { Dec } from '../../utils/Decimal'
import { useEvmTransaction } from '../../utils/tinlake/useEvmTransaction'
@@ -48,6 +48,7 @@ export function InvestRedeemLiquidityPoolsProvider({ poolId, trancheId, children
const tranche = pool.tranches.find((t) => t.id === trancheId)
const { data: metadata, isLoading: isMetadataLoading } = usePoolMetadata(pool)
const trancheMeta = metadata?.tranches?.[trancheId]
const chainId = Number(provider?._network.chainId) || 1

if (!tranche) throw new Error(`Token not found. Pool id: ${poolId}, token id: ${trancheId}`)

@@ -227,12 +228,13 @@ export function InvestRedeemLiquidityPoolsProvider({ poolId, trancheId, children
// If the last tx was an approve, we may not have refetched the allowance yet,
// so assume the allowance is enough to do a normal invest
else if (lpInvest.lpCurrencyAllowance.lt(assets) && supportsPermits && pendingAction !== 'approvePoolCurrency') {
const signer = provider!.getSigner()
const signer = await provider!.getSigner()
const connectedCent = cent.connectEvm(evmAddress!, signer)
const permit = await connectedCent.liquidityPools.signPermit([
lpInvest.lpAddress,
lpInvest.currency.address,
assets,
chainId,
])
console.log('permit', permit)
// investWithPermit.execute([lpInvest.lpAddress, assets, permit])
@@ -254,6 +256,7 @@ export function InvestRedeemLiquidityPoolsProvider({ poolId, trancheId, children
lpInvest?.lpAddress,
lpInvest?.currency.address,
amount.toString(),
chainId,
]),
approveTrancheToken: () => {},
cancelInvest: doAction('cancelInvest', () => [lpInvest?.lpAddress]),
5 changes: 3 additions & 2 deletions centrifuge-app/src/components/LiquidityEpochSection.tsx
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import {
useWallet,
} from '@centrifuge/centrifuge-react'
import { Button, IconInfo, Shelf, Stack, Text } from '@centrifuge/fabric'
import { Contract } from 'ethers'
import * as React from 'react'
import { switchMap } from 'rxjs'
import { Dec } from '../utils/Decimal'
@@ -337,9 +338,9 @@ function TinlakeEpochStatus({ pool }: { pool: TinlakePool }) {
(cent) => cent.tinlake.closeEpoch,
{
onSuccess: async () => {
const signer = provider!.getSigner()
const signer = await provider!.getSigner()
const connectedCent = cent.connectEvm(selectedAddress!, signer)
const coordinator = connectedCent.tinlake.contract(pool.addresses, undefined, 'COORDINATOR')
const coordinator = connectedCent.tinlake.contract(pool.addresses, undefined, 'COORDINATOR') as Contract
if ((await coordinator.submissionPeriod()) === true) {
// didn't execute right away, run solver
solveEpochTx([])
9 changes: 4 additions & 5 deletions centrifuge-app/src/components/OnboardingAuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import Centrifuge from '@centrifuge/centrifuge-js'
import { useCentrifuge, useCentrifugeUtils, useEvmProvider, useWallet } from '@centrifuge/centrifuge-react'
import { BigNumber } from '@ethersproject/bignumber'
import { Signer } from '@polkadot/types/types'
import { encodeAddress } from '@polkadot/util-crypto'
import { ethers, utils } from 'ethers'
import { ethers, hashMessage } from 'ethers'
import * as React from 'react'
import { useMutation, useQuery } from 'react-query'

@@ -240,7 +239,7 @@ Issued At: ${new Date().toISOString()}`
let body

if (signedMessage === '0x') {
const messageHash = utils.hashMessage(message)
const messageHash = hashMessage(message)

const isValid = await isValidSignature(signer, address, messageHash, evmChainId || 1)

@@ -308,9 +307,9 @@ const isValidSignature = async (provider: any, safeAddress: string, messageHash:
throw new Error('Unable to fetch SafeMessage')
}

const threshold = BigNumber.from(await safeContract.getThreshold()).toNumber()
const threshold = BigInt(await safeContract.getThreshold())

if (!threshold || threshold > safeMessage.confirmations.length) {
if (!threshold || threshold > BigInt(safeMessage.confirmations.length)) {
throw new Error('Threshold has not been met')
}

2 changes: 1 addition & 1 deletion centrifuge-app/src/components/PoolOverview/KeyMetrics.tsx
Original file line number Diff line number Diff line change
@@ -184,7 +184,7 @@ const AvailableNetworks = ({ poolId }: { poolId: string }) => {
<Spinner size="iconSmall" />
)}
{activeDomains.data
?.filter((domain) => domain.isActive)
?.filter((domain) => domain.isActive && domain.chainId !== 5)
.map((domain) => {
const chain = (evmChains as any)[domain.chainId]
return (
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@ import {
TabsItem,
Text,
} from '@centrifuge/fabric'
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress as isSubstrateAddress } from '@polkadot/util-crypto'
import Decimal from 'decimal.js-light'
import { isAddress as isEvmAddress } from 'ethers'
import { Field, FieldProps, Form, FormikProvider, useFormik } from 'formik'
import React, { useMemo } from 'react'
import { useQuery } from 'react-query'
20 changes: 18 additions & 2 deletions centrifuge-app/src/config.ts
Original file line number Diff line number Diff line change
@@ -96,6 +96,8 @@ const CENTRIFUGE: EnvironmentConfig = {
const ethNetwork = import.meta.env.REACT_APP_TINLAKE_NETWORK || 'mainnet'

const alchemyKey = import.meta.env.REACT_APP_ALCHEMY_KEY
const onfinalityKey = import.meta.env.REACT_APP_ONFINALITY_KEY
const tenderlyKey = import.meta.env.REACT_APP_TENDERLY_KEY

export const ethConfig = {
rpcUrl: `https://eth-mainnet.g.alchemy.com/v2/${alchemyKey}`,
@@ -129,15 +131,24 @@ export const evmChains: EvmChains = {
decimals: 18,
},
blockExplorerUrl: 'https://etherscan.io/',
urls: [`https://eth-mainnet.g.alchemy.com/v2/${alchemyKey}`],
urls: [
`https://mainnet.gateway.tenderly.co/${tenderlyKey}`,
`https://eth-mainnet.g.alchemy.com/v2/${alchemyKey}`,
`https://eth.api.onfinality.io/rpc?apikey=${onfinalityKey}`,
],
network: 'mainnet',
iconUrl: ethereumLogo,
isTestnet: false,
},
11155111: {
name: 'Ethereum Sepolia',
nativeCurrency: { name: 'Sepolia Ether', symbol: 'sepETH', decimals: 18 },
blockExplorerUrl: 'https://sepolia.etherscan.io/',
urls: [`https://eth-sepolia.g.alchemy.com/v2/${alchemyKey}`],
urls: [
`https://eth-sepolia.g.alchemy.com/v2/${alchemyKey}`,
`https://eth-sepolia.api.onfinality.io/rpc?apikey=${onfinalityKey}`,
],
network: 'sepolia',
iconUrl: sepoliaLogo,
isTestnet: true,
},
@@ -147,6 +158,7 @@ export const evmChains: EvmChains = {
blockExplorerUrl: 'https://basescan.org/',
urls: ['https://mainnet.base.org'],
iconUrl: baseLogo,
network: 'base-mainnet',
isTestnet: false,
},
84532: {
@@ -155,6 +167,7 @@ export const evmChains: EvmChains = {
blockExplorerUrl: 'https://sepolia.basescan.org/',
urls: [`https://sepolia.base.org`],
iconUrl: baseLogo,
network: 'base-sepolia',
isTestnet: true,
},
42161: {
@@ -167,6 +180,7 @@ export const evmChains: EvmChains = {
blockExplorerUrl: 'https://arbiscan.io/',
urls: ['https://arb1.arbitrum.io/rpc'],
iconUrl: arbitrumLogo,
network: 'arbitrum-mainnet',
isTestnet: false,
},
42220: {
@@ -180,6 +194,7 @@ export const evmChains: EvmChains = {
urls: ['https://forno.celo.org'],
iconUrl: celoLogo,
isTestnet: false,
network: 'celo-mainnet',
},
44787: {
name: 'Celo Alfajores',
@@ -192,6 +207,7 @@ export const evmChains: EvmChains = {
urls: ['https://alfajores-forno.celo-testnet.org'],
iconUrl: celoLogo,
isTestnet: true,
network: 'celo-alfajores',
},
}

Original file line number Diff line number Diff line change
@@ -18,9 +18,9 @@ import {
useGetNetworkName,
} from '@centrifuge/centrifuge-react'
import { Button, IconMinusCircle, InputErrorMessage, SelectInner, Stack, Text, TextInput } from '@centrifuge/fabric'
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress as isSubstrateAddress, sortAddresses } from '@polkadot/util-crypto'
import { BN } from 'bn.js'
import { isAddress as isEvmAddress } from 'ethers'
import {
ErrorMessage,
Field,
Original file line number Diff line number Diff line change
@@ -21,8 +21,8 @@ import {
Text,
TextWithPlaceholder,
} from '@centrifuge/fabric'
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress } from '@polkadot/util-crypto'
import { isAddress as isEvmAddress } from 'ethers'
import React from 'react'
import { useParams } from 'react-router'
import { DataTable } from '../../../components/DataTable'
10 changes: 6 additions & 4 deletions centrifuge-app/src/pages/Onboarding/queries/useSignRemark.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import {
useWallet,
} from '@centrifuge/centrifuge-react'
import { useNativeBalance } from '@centrifuge/centrifuge-react/dist/components/WalletProvider/evm/utils'
import { Contract } from '@ethersproject/contracts'
import { Contract } from 'ethers'
import React, { useEffect } from 'react'
import { UseMutateFunction } from 'react-query'
import { lastValueFrom } from 'rxjs'
@@ -149,11 +149,13 @@ export const useSignRemark = (
try {
const [message] = args
const remarkerContract = new Contract(ethConfig.remarkerAddress, RemarkerAbi)
if (!evmProvider?.getSigner()) {
const signer = await evmProvider?.getSigner()
if (!signer) {
throw new Error('Signer may not be set')
}
const connectedContract = remarkerContract.connect(evmProvider?.getSigner())
const result = await connectedContract.functions.remarkWithEvent(message)
const connectedContract = remarkerContract.connect(signer) as Contract

const result = await connectedContract.remarkWithEvent(message)
updateTransaction(txId, () => ({
status: 'pending',
hash: result.hash,
2 changes: 1 addition & 1 deletion centrifuge-app/src/utils/address.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress } from '@polkadot/util-crypto'
import { isAddress as isEvmAddress } from 'ethers'

export function isSubstrateAddress(address: string) {
return isAddress(address) && !isEvmAddress(address)
4 changes: 2 additions & 2 deletions centrifuge-app/src/utils/tinlake/useEvmTransaction.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ import {
useTransactions,
useWallet,
} from '@centrifuge/centrifuge-react'
import { TransactionRequest, TransactionResponse } from '@ethersproject/providers'
import { TransactionRequest, TransactionResponse } from 'ethers'
import * as React from 'react'
import { Observable, lastValueFrom, tap } from 'rxjs'

@@ -29,7 +29,7 @@ export function useEvmTransaction<T extends Array<any>>(

async function doTransaction(id: string, args: T, txOptions?: TransactionRequest) {
try {
const signer = provider!.getSigner()
const signer = await provider!.getSigner()
const connectedCent = centrifuge.connectEvm(selectedAddress!, signer)
const transaction = transactionCallback(connectedCent)
updateTransaction(id, { status: 'unconfirmed' })
9 changes: 4 additions & 5 deletions centrifuge-app/src/utils/tinlake/useTinlakeBalances.ts
Original file line number Diff line number Diff line change
@@ -7,8 +7,7 @@ import {
TokenBalance,
} from '@centrifuge/centrifuge-js'
import { useWallet } from '@centrifuge/centrifuge-react'
import { BigNumber } from '@ethersproject/bignumber'
import { JsonRpcProvider } from '@ethersproject/providers'
import { Provider } from 'ethers'
import { useQuery } from 'react-query'
import { ethConfig } from '../../config'
import { currencies } from './currencies'
@@ -31,10 +30,10 @@ export function useTinlakeBalances(address?: string) {

const WCFG_ADDRESS = '0xc221b7e65ffc80de234bbb6667abdd46593d34f0'

async function getBalances(pools: TinlakePool[], address: string, provider: JsonRpcProvider) {
async function getBalances(pools: TinlakePool[], address: string, provider: Provider) {
const calls: EvmMulticallCall[] = []
const toTokenBalance = (val: BigNumber) => new TokenBalance(val.toString(), 18)
const toCurrencyBalance = (val: BigNumber) => new CurrencyBalance(val.toString(), 18)
const toTokenBalance = (val: BigInt) => new TokenBalance(val.toString(), 18)
Copy link
Collaborator

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

const toCurrencyBalance = (val: BigInt) => new CurrencyBalance(val.toString(), 18)

const seenCurrencies = new Set<string>()

5 changes: 2 additions & 3 deletions centrifuge-app/src/utils/tinlake/useTinlakeInvestments.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CurrencyBalance } from '@centrifuge/centrifuge-js'
import { BigNumber } from '@ethersproject/bignumber'
import Decimal from 'decimal.js-light'
import { useQuery } from 'react-query'
import { useAddress } from '../useAddress'
@@ -17,8 +16,8 @@ export function useTinlakeInvestments(poolId: string, address?: string) {
}

async function getInvestment(pool: TinlakePool, address: string) {
const toNumber = (val: BigNumber) => val.toNumber()
const toDec18 = (val: BigNumber) => new CurrencyBalance(val.toString(), 18).toDecimal()
const toNumber = (val: BigInt) => Number(val)
const toDec18 = (val: BigInt) => new CurrencyBalance(val.toString(), 18).toDecimal()
const calls: Call[] = [
{
target: pool.addresses.JUNIOR_TRANCHE,
15 changes: 7 additions & 8 deletions centrifuge-app/src/utils/tinlake/useTinlakePools.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,6 @@ import {
TokenBalance,
} from '@centrifuge/centrifuge-js'
import { useCentrifuge } from '@centrifuge/centrifuge-react'
import { BigNumber } from '@ethersproject/bignumber'
import BN from 'bn.js'
import * as React from 'react'
import { useQuery } from 'react-query'
@@ -346,12 +345,12 @@ export async function getWriteOffPercentages(pool: TinlakePool, loans: any[]) {
}

async function getPools(pools: IpfsPools): Promise<{ pools: TinlakePool[] }> {
const toDateString = (val: BigNumber) => new Date(val.toNumber() * 1000).toISOString()
const toNumber = (val: BigNumber) => val.toNumber()
const toCurrencyBalance = (val: BigNumber) => new CurrencyBalance(val.toString(), 18)
const toTokenBalance = (val: BigNumber) => new TokenBalance(val.toString(), 18)
const toRate = (val: BigNumber) => new Rate(val.toString())
const toPrice = (val: BigNumber) => new Rate(val.toString())
const toDateString = (val: BigInt) => new Date(Number(val) * 1000).toISOString()
const toNumber = (val: BigInt) => Number(val)
const toCurrencyBalance = (val: BigInt) => new CurrencyBalance(val.toString(), 18)
const toTokenBalance = (val: BigInt) => new TokenBalance(val.toString(), 18)
const toRate = (val: BigInt) => new Rate(val.toString())
const toPrice = (val: BigInt) => new Rate(val.toString())

const calls: Call[] = []
pools.active.forEach((pool) => {
@@ -828,6 +827,6 @@ interface State {
submissionPeriod: boolean
}

function toBN(val: BigNumber) {
function toBN(val: BigInt) {
return new BN(val.toString())
}
3 changes: 1 addition & 2 deletions centrifuge-app/src/utils/tinlake/useTinlakePortfolio.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { CurrencyBalance } from '@centrifuge/centrifuge-js'
import { BigNumber } from '@ethersproject/bignumber'
import BN from 'bn.js'
import { useQuery } from 'react-query'
import { useAddress } from '../useAddress'
@@ -24,7 +23,7 @@ export enum TinlakeTranche {
}

async function getTinlakePortfolio(ipfsPools: IpfsPools, address: string) {
const toBN = (val: BigNumber) => new CurrencyBalance(val.toString(), 18)
const toBN = (val: BigInt) => new CurrencyBalance(val.toString(), 18)

const calls: Call[] = ipfsPools.active.flatMap((pool) => [
{
2 changes: 1 addition & 1 deletion centrifuge-app/src/utils/tinlake/useTinlakeTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Centrifuge, { TinlakeContractAddresses, TinlakeContractVersions } from '@centrifuge/centrifuge-js'
import { TransactionRequest, TransactionResponse } from '@ethersproject/providers'
import { TransactionRequest, TransactionResponse } from 'ethers'
import { Observable } from 'rxjs'
import { usePool } from '../usePools'
import { useEvmTransaction } from './useEvmTransaction'
14 changes: 7 additions & 7 deletions centrifuge-app/src/utils/useCFGTokenPrice.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ethers } from 'ethers'
import { useWallet } from '@centrifuge/centrifuge-react'
import { Contract, Provider } from 'ethers'
import { useQuery } from 'react-query'
import { Dec } from './Decimal'

async function getWCFGPrice() {
async function getWCFGPrice(provider: Provider) {
const usdcWcfgPool = '0x7270233cCAE676e776a659AFfc35219e6FCfbB10'
const uniswapPoolAbi = ['function observe(uint32[] secondsAgos) external view returns (int56[], uint160[])']
const provider2 = new ethers.providers.InfuraProvider()
const poolContract = new ethers.Contract(usdcWcfgPool, uniswapPoolAbi, provider2)
const poolContract = new Contract(usdcWcfgPool, uniswapPoolAbi, provider)
const observations = (await poolContract.observe([0, 1]))[0]
const first = Dec(observations[0].toString())
const second = Dec(observations[1].toString())
@@ -15,9 +15,9 @@ async function getWCFGPrice() {
}

export const useCFGTokenPrice = () => {
const { data: CFGPrice } = useQuery('wCFGPrice', () => {
return getWCFGPrice()
})
const { getProvider } = useWallet().evm
const provider = getProvider(1)
const { data: CFGPrice } = useQuery('wCFGPrice', () => getWCFGPrice(provider))
return CFGPrice
}

25 changes: 14 additions & 11 deletions centrifuge-app/src/utils/useLiquidityPools.ts
Original file line number Diff line number Diff line change
@@ -25,17 +25,20 @@ export function useActiveDomains(poolId: string, suspense?: boolean) {
['activeDomains', poolId, routers?.length],
async () => {
const results = await Promise.allSettled(
routers!.map((r) => {
async function getManager() {
const rpcProvider = getProvider(r.chainId)
const manager = await cent.liquidityPools.getManagerFromRouter([r.router], {
rpcProvider,
})
const pool = await cent.liquidityPools.getPool([r.chainId, manager, poolId], { rpcProvider })
return [manager, pool] as const
}
return withTimeout(getManager(), 15000)
})
routers!
// remove all goerli networks since providers don't support goerli anymore
.filter((r) => r.chainId !== 5 && r.chainId !== 84531)
.map((r) => {
async function getManager() {
const rpcProvider = getProvider(r.chainId)
const manager = await cent.liquidityPools.getManagerFromRouter([r.router], {
rpcProvider,
})
const pool = await cent.liquidityPools.getPool([r.chainId, manager, poolId], { rpcProvider })
return [manager, pool] as const
}
return withTimeout(getManager(), 15000)
})
)
return results
.map((result, i) => {
2 changes: 1 addition & 1 deletion centrifuge-app/src/utils/usePermissions.tsx
Original file line number Diff line number Diff line change
@@ -14,8 +14,8 @@ import {
useWallet,
} from '@centrifuge/centrifuge-react'
import { Select } from '@centrifuge/fabric'
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { ApiRx } from '@polkadot/api'
import { isAddress as isEvmAddress } from 'ethers'
import * as React from 'react'
import { combineLatest, combineLatestWith, filter, map, repeatWhen, switchMap, take } from 'rxjs'
import { diffPermissions } from '../pages/IssuerPool/Configuration/Admins'
2 changes: 1 addition & 1 deletion centrifuge-app/src/utils/validation/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ExternalPricingInfo } from '@centrifuge/centrifuge-js'
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress as isSubstrateAddress } from '@polkadot/util-crypto'
import Decimal from 'decimal.js-light'
import { isAddress as isEvmAddress } from 'ethers'
import { Dec } from '../Decimal'
import { daysBetween } from '../date'
import { formatPercentage } from '../formatting'
5 changes: 0 additions & 5 deletions centrifuge-js/package.json
Original file line number Diff line number Diff line change
@@ -28,11 +28,6 @@
"lint": "eslint src/**/*.ts"
},
"dependencies": {
"@ethersproject/abi": "^5.6.0",
"@ethersproject/address": "^5.6.0",
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/contracts": "^5.6.0",
"@ethersproject/providers": "^5.6.0",
"@polkadot/api": "~12.1.1",
"@polkadot/keyring": "^11.1.3",
"@polkadot/types": "~12.1.1",
2 changes: 1 addition & 1 deletion centrifuge-js/src/Centrifuge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { JsonRpcSigner } from '@ethersproject/providers'
import { AddressOrPair } from '@polkadot/api/types'
import { Signer } from '@polkadot/types/types'
import type { JsonRpcSigner } from 'ethers'
import { CentrifugeBase, UserProvidedConfig } from './CentrifugeBase'
import { getAuthModule } from './modules/auth'
import { getLiquidityPoolsModule } from './modules/liquidityPools'
114 changes: 91 additions & 23 deletions centrifuge-js/src/CentrifugeBase.ts
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
Copy link
Collaborator

@onnovisser onnovisser Sep 6, 2024

Choose a reason for hiding this comment

The 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 Centrifuge->connect()

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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

Copy link
Collaborator

@onnovisser onnovisser Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about making this a Set reachableParachainUrls? Then when getting the cached url, it can check which of the config urls is in the set. Then it still works if there's multiple instances of Centrifuge with different configs


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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The 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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tap callback can be an async function and then this.getTxCompletedEvents() can simply be awaited

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)
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from(this.getTxCompletedEvents())
.pipe(take(1))
.subscribe((subject) => {
subject.next(result.events)
})
(await this.getTxCompletedEvents()).next(result.events)

}
}),
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() {
70 changes: 41 additions & 29 deletions centrifuge-js/src/modules/liquidityPools.ts
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)]],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
returns: [[`currencyNeedsAdding[${currency.address}]`, (id: BigInt) => id === BigInt(0)]],
returns: [[`currencyNeedsAdding[${currency.address}]`, (id: bigint) => id === 0n]],

})) 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
10 changes: 9 additions & 1 deletion centrifuge-js/src/modules/liquidityPools/abi/Gateway.abi.json
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"
}
]
4 changes: 3 additions & 1 deletion centrifuge-js/src/modules/pools.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { ApiRx } from '@polkadot/api'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { StorageKey, u32 } from '@polkadot/types'
@@ -7,6 +6,7 @@ import { ISubmittableResult } from '@polkadot/types/types'
import { blake2AsHex } from '@polkadot/util-crypto/blake2'
import BN from 'bn.js'
import Decimal from 'decimal.js-light'
import { isAddress as isEvmAddress } from 'ethers'
import { EMPTY, Observable, combineLatest, expand, firstValueFrom, forkJoin, from, of, startWith } from 'rxjs'
import { combineLatestWith, filter, map, repeatWhen, switchMap, take, takeLast } from 'rxjs/operators'
import { SolverResult, calculateOptimalSolution } from '..'
@@ -3729,6 +3729,8 @@ export function getPoolsModule(inst: Centrifuge) {
switchMap(({ api, pool: rawPool }) => {
const pool = rawPool.toHuman() as PoolDetailsData
const trancheIds = pool.tranches.ids
console.log('🚀 ~ trancheIds:', trancheIds)
console.log('🚀 ~ poolId:', poolId)
return combineLatest([
api.queryMulti(
trancheIds.flatMap((trancheId) => [
70 changes: 56 additions & 14 deletions centrifuge-js/src/modules/tinlake.ts
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are all these errors saying?? 😅😅😅

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 directly the methods on the contract are not type checked, but when using a connected contract the are type check even though the method types seem to be non-inferable from the ABI.

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)
}
5 changes: 1 addition & 4 deletions centrifuge-js/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import {
TransactionReceipt as EvmTransactionReceipt,
TransactionResponse as EvmTransactionResponse,
} from '@ethersproject/providers'
import { AddressOrPair } from '@polkadot/api/types'
import { ISubmittableResult } from '@polkadot/types/types'
import { HexString } from '@polkadot/util/types'
import BN from 'bn.js'
import { TransactionReceipt as EvmTransactionReceipt, TransactionResponse as EvmTransactionResponse } from 'ethers'
import { Config } from '../CentrifugeBase'

type EvmTransactionResult = {
16 changes: 9 additions & 7 deletions centrifuge-js/src/utils/evmMulticall.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Interface } from '@ethersproject/abi'
import { Contract } from '@ethersproject/contracts'
import { JsonRpcProvider } from '@ethersproject/providers'
import { Contract, Interface, Provider } from 'ethers'
import set from 'lodash/set'

const MULTICALL_ABI = [
@@ -25,7 +23,7 @@ const MULTICALL_ABI = [
const MULTICALL_ADDRESS = '0xcA11bde05977b3631167028862bE2a173976CA11'

type AggregateOptions = {
rpcProvider: JsonRpcProvider
rpcProvider: Provider
allowFailure?: boolean
}

@@ -41,7 +39,7 @@ export type Call = {
}

const identity = (v: any) => v
const multicallContracts = new WeakMap<JsonRpcProvider, Contract>()
const multicallContracts = new WeakMap<Provider, Contract>()

export async function multicall<T = Record<string, any>>(calls: Call[], options: AggregateOptions) {
let contract = multicallContracts.get(options.rpcProvider)
@@ -52,15 +50,19 @@ export async function multicall<T = Record<string, any>>(calls: Call[], options:

const interfaces = calls.map((c) => {
const int = new Interface([c.call[0] as string])
return [int, int.fragments[0].name] as const
const functionFragment = int.getFunction(c.call[0] as string)
if (!functionFragment) {
throw new Error(`Function ${c.call[0]} not found in interface`)
}
return [int, functionFragment.name] as const
})
const encoded = calls.map((c, i) => ({
target: c.target,
allowFailure: c.allowFailure ?? options.allowFailure ?? false,
callData: interfaces[i][0].encodeFunctionData(interfaces[i][1], c.call.slice(1)),
}))

const results = await contract.callStatic.aggregate3(encoded)
const results = await contract.aggregate3.staticCall(encoded)

const transformed: Record<string, any> = {}
calls.forEach((c, i) => {
4 changes: 0 additions & 4 deletions centrifuge-react/package.json
Original file line number Diff line number Diff line change
@@ -46,10 +46,6 @@
},
"dependencies": {
"@coinbase/wallet-sdk": "^3.6.3",
"@ethersproject/address": "^5.6.0",
"@ethersproject/bignumber": "^5.6.0",
"@ethersproject/networks": "^5.6.0",
"@ethersproject/providers": "^5.6.0",
"@finoa/finoa-connect-sdk": "^1.0.1",
"@polkadot/extension-dapp": "~0.45.5",
"@polkadot/react-identicon": "~3.1.4",
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Centrifuge, { Account, CurrencyBalance } from '@centrifuge/centrifuge-js'
import type { UserProvidedConfig } from '@centrifuge/centrifuge-js/dist/CentrifugeBase'
import { getAddress, isAddress as isEvmAddress } from '@ethersproject/address'
import { ApiRx } from '@polkadot/api'
import { encodeAddress } from '@polkadot/util-crypto'
import { BN } from 'bn.js'
import { getAddress, isAddress as isEvmAddress } from 'ethers'
import * as React from 'react'
import { useQuery } from 'react-query'

2 changes: 1 addition & 1 deletion centrifuge-react/src/components/WalletMenu/WalletMenu.tsx
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ import {
Text,
WalletButton,
} from '@centrifuge/fabric'
import { getAddress } from '@ethersproject/address'
import { getAddress } from 'ethers'
import * as React from 'react'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
41 changes: 33 additions & 8 deletions centrifuge-react/src/components/WalletProvider/WalletProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { addressToHex, ComputedMultisig, evmToSubstrateAddress, Multisig } from '@centrifuge/centrifuge-js'
import { JsonRpcProvider } from '@ethersproject/providers'
import { isWeb3Injected } from '@polkadot/extension-dapp'
import { getWallets } from '@subwallet/wallet-connect/dotsama/wallets'
import { Wallet } from '@subwallet/wallet-connect/types'
import { Web3ReactState } from '@web3-react/types'
import { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2'
import { AbstractProvider, getDefaultProvider, JsonRpcProvider } from 'ethers'
import * as React from 'react'
import { useQuery } from 'react-query'
import { firstValueFrom, map, switchMap } from 'rxjs'
@@ -63,10 +63,19 @@ export type WalletContextType = {
selectedWallet: EvmConnectorMeta | null
isSmartContractWallet: boolean
selectedAddress: string | null
getProvider(chainId: number): JsonRpcProvider
getProvider(chainId: number): JsonRpcProvider | AbstractProvider
}
}

const unsupportedNetworksByDefaultProvider = [
'base-sepolia',
'base-mainnet',
'celo-alfajores',
'celo-mainnet',
'arbitrum-sepolia',
'arbitrum-mainnet',
]

const WalletContext = React.createContext<WalletContextType>(null as any)

export const wallets = getWallets()
@@ -129,10 +138,13 @@ type WalletProviderProps = {
showAdvancedAccounts?: boolean
showTestNets?: boolean
showFinoa?: boolean
infuraApiKey?: string
alchemyApiKey?: string
tenderlyApiKey?: string
}

let cachedEvmConnectors: EvmConnectorMeta[] | undefined = undefined
const cachedProviders: Record<number, JsonRpcProvider> = {}
const cachedProviders = new Map<number, JsonRpcProvider | AbstractProvider>()

export function WalletProvider({
children,
@@ -148,6 +160,9 @@ export function WalletProvider({
showAdvancedAccounts,
showTestNets,
showFinoa,
infuraApiKey,
alchemyApiKey,
tenderlyApiKey,
}: WalletProviderProps) {
if (!evmChainsProp[1]?.urls[0]) throw new Error('Mainnet should be defined in EVM Chains')

@@ -156,7 +171,7 @@ export function WalletProvider({
const centEvmChainId = useCentEvmChainId()

const evmChains = React.useMemo(() => {
const centUrl = new URL(cent.parachainUrl)
const centUrl = new URL(cent.config.centrifugeWsUrl.split(',')[0])
centUrl.protocol = 'https:'
const chains = {
...evmChainsProp,
@@ -241,10 +256,20 @@ export function WalletProvider({
const [proxies] = useCentrifugeQuery(['allProxies'], (cent) => cent.proxies.getAllProxies())

function getProvider(chainId: number) {
return (
cachedProviders[chainId] ||
(cachedProviders[chainId] = new JsonRpcProvider((evmChains as any)[chainId].urls[0], chainId))
)
const defaultUrl = (evmChains as any)[chainId].urls[0]
const network = (evmChains as any)[chainId].network
if (!cachedProviders.has(chainId)) {
let networkish = unsupportedNetworksByDefaultProvider.includes(network) ? defaultUrl : network
const provider = getDefaultProvider(networkish, {
infura: infuraApiKey,
alchemy: alchemyApiKey,
tenderly: tenderlyApiKey,
exclusive: ['infura', 'alchemy', 'tenderly'],
})
cachedProviders.set(chainId, provider)
return provider
}
return cachedProviders.get(chainId) as JsonRpcProvider | AbstractProvider
}

function setFilteredAccounts(accounts: SubstrateAccount[]) {
6 changes: 1 addition & 5 deletions centrifuge-react/src/components/WalletProvider/evm/chains.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ type BasicChainInformation = {
}

type ExtendedChainInformation = BasicChainInformation & {
network: string
name: string
nativeCurrency: AddEthereumChainParameter['nativeCurrency']
blockExplorerUrl: string
@@ -47,11 +48,6 @@ const chainExtendedInfo = {
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
blockExplorerUrl: 'https://etherscan.io/',
},
5: {
name: 'Ethereum Görli',
nativeCurrency: { name: 'Görli Ether', symbol: 'görETH', decimals: 18 },
blockExplorerUrl: 'https://goerli.etherscan.io/',
},
}

export function getChainInfo(chains: EvmChains, chainId: number): ExtendedChainInformation {
17 changes: 7 additions & 10 deletions centrifuge-react/src/components/WalletProvider/evm/utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { CurrencyBalance } from '@centrifuge/centrifuge-js'
import type { Networkish } from '@ethersproject/networks'
import type { BaseProvider, Web3Provider } from '@ethersproject/providers'
import { CoinbaseWallet } from '@web3-react/coinbase-wallet'
import { EIP1193 } from '@web3-react/eip1193'
import { EMPTY, Empty } from '@web3-react/empty'
import { GnosisSafe } from '@web3-react/gnosis-safe'
import { MetaMask } from '@web3-react/metamask'
import { createWeb3ReactStoreAndActions } from '@web3-react/store'
import { Actions, Provider, Web3ReactState, Web3ReactStore } from '@web3-react/types'
import { Actions, Provider as Web3ReactProvider, Web3ReactState, Web3ReactStore } from '@web3-react/types'
import { WalletConnect as WalletConnectV2 } from '@web3-react/walletconnect-v2'
import type { Networkish } from 'ethers'
import { BrowserProvider } from 'ethers'
import * as React from 'react'
import { useQuery } from 'react-query'
import { useWallet } from '../WalletProvider'
@@ -37,7 +37,7 @@ export function useConnectorState(connector?: Connector | null) {
return state
}

const providerKeys = new WeakMap<Provider, string>()
const providerKeys = new WeakMap<Web3ReactProvider, string>()
function getProviderKey(connector: Connector) {
let providerKey
if (connector.provider) {
@@ -50,19 +50,16 @@ function getProviderKey(connector: Connector) {
return providerKey
}

export function useProviderForConnector<T extends BaseProvider = Web3Provider>(
connector?: Connector | null,
network?: Networkish
) {
export function useProviderForConnector<T extends BrowserProvider>(connector?: Connector | null, network?: Networkish) {
const conn = connector ?? emptyConnector
const state = useConnectorState(conn)
const isActive = computeIsActive(state)

const { data: provider } = useQuery(
['evmProvider', getProviderKey(conn), typeof network === 'object' ? network.chainId : network],
async () => {
const { Web3Provider } = await import('@ethersproject/providers')
const provider = new Web3Provider(conn.provider!, network)
const { BrowserProvider } = await import('ethers')
const provider = new BrowserProvider(conn.provider!, network)
return provider
},
{ enabled: !!conn.provider && isActive, staleTime: Infinity }
3 changes: 2 additions & 1 deletion centrifuge-react/src/hooks/useCentrifugeTransaction.ts
Original file line number Diff line number Diff line change
@@ -53,7 +53,8 @@ export function useCentrifugeTransaction<T extends Array<any>>(
try {
let connectedCent
if (isEvmOnSubstrate) {
connectedCent = cent.connectEvm(evm.selectedAddress!, provider!.getSigner(), substrate.evmChainId!)
const signer = await provider!.getSigner()
connectedCent = cent.connectEvm(evm.selectedAddress!, signer, substrate.evmChainId!)
} else {
connectedCent = cent.connect(account.signingAccount?.address, account.signingAccount?.signer as any)
}
2 changes: 1 addition & 1 deletion fabric/src/components/TextInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isAddress as isEvmAddress } from '@ethersproject/address'
import { isAddress as isSubstrateAddress } from '@polkadot/util-crypto'
import { isAddress as isEvmAddress } from 'ethers'
import * as React from 'react'
import styled, { keyframes } from 'styled-components'
import { Flex, IconCentrifuge, IconEthereum, IconLoader, IconSearch } from '../..'
23 changes: 18 additions & 5 deletions fabric/tsconfig.json
Original file line number Diff line number Diff line change
@@ -2,8 +2,14 @@
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"target": "es6",
"lib": ["es6", "dom", "es2016", "es2017", "ES2021.String"],
"target": "esnext",
"lib": [
"es6",
"dom",
"es2016",
"es2017",
"ES2021.String"
],
"jsx": "react",
"declaration": true,
"moduleResolution": "node",
@@ -17,6 +23,13 @@
"allowSyntheticDefaultImports": true,
"skipLibCheck": true // an error happens in @types/download
},
"include": ["src", "./**/*.d.ts"],
"exclude": ["node_modules", "lib", "dist"]
}
"include": [
"src",
"./**/*.d.ts"
],
"exclude": [
"node_modules",
"lib",
"dist"
]
}
4 changes: 0 additions & 4 deletions onboarding-api/package.json
Original file line number Diff line number Diff line change
@@ -16,10 +16,6 @@
},
"dependencies": {
"@centrifuge/centrifuge-js": "workspace:centrifuge-js",
"@ethersproject/address": "^5.7.0",
"@ethersproject/contracts": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@ethersproject/wallet": "^5.7.0",
"@google-cloud/firestore": "^6.8.0",
"@google-cloud/functions-framework": "^3.1.3",
"@google-cloud/storage": "^6.9.1",
2 changes: 1 addition & 1 deletion onboarding-api/src/middleware/verifyAuth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAddress } from '@ethersproject/address'
import { isAddress } from 'ethers'
import { NextFunction, Request, Response } from 'express'
import * as jwt from 'jsonwebtoken'
import { getValidSubstrateAddress } from '../utils/networks/centrifuge'
13 changes: 5 additions & 8 deletions onboarding-api/src/utils/networks/evm.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import { isAddress } from '@ethersproject/address'
import { Contract } from '@ethersproject/contracts'
import { InfuraProvider, JsonRpcProvider, Provider } from '@ethersproject/providers'
import { BigNumber, ethers } from 'ethers'
import { Contract, InfuraProvider, JsonRpcProvider, Provider, ethers, isAddress } from 'ethers'
import { Request, Response } from 'express'
import fetch from 'node-fetch'
import { SiweMessage } from 'siwe'
@@ -41,7 +38,7 @@ export const validateEvmRemark = async (
Number(transactionInfo.blockNumber)
)

const [sender, actualRemark] = filteredEvents.flatMap((ev) => ev.args?.map((arg) => arg.toString()))
const [sender, actualRemark] = filteredEvents.flatMap((ev) => ev.topics?.map((arg) => arg.toString()))
if (actualRemark !== expectedRemark || sender !== wallet.address) {
throw new HttpError(400, 'Invalid remark')
}
@@ -102,9 +99,9 @@ export const verifySafeWallet = async (req: Request, res: Response) => {
throw new HttpError(400, 'Unable to fetch SafeMessage')
}

const threshold = BigNumber.from(await safeContract.getThreshold()).toNumber()
const threshold = BigInt(await safeContract.getThreshold())

if (!threshold || threshold > safeMessage.confirmations.length) {
if (!threshold || threshold > BigInt(safeMessage.confirmations.length)) {
throw new HttpError(400, 'Threshold has not been met')
}

@@ -134,5 +131,5 @@ const fetchSafeMessage = async (safeMessageHash: string, chainId: number) => {
headers: { 'Content-Type': 'application/json' },
})

return response.json()
return response.json() as Promise<{ confirmations: any[]; preparedSignature: string }>
}
19 changes: 6 additions & 13 deletions onboarding-api/src/utils/networks/tinlake.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Contract } from '@ethersproject/contracts'
import { InfuraProvider } from '@ethersproject/providers'
import { Wallet } from '@ethersproject/wallet'
import { Contract, InfuraProvider, Wallet } from 'ethers'
import { Request } from 'express'
import { lastValueFrom } from 'rxjs'
import { HttpError, reportHttpError } from '../httpError'
@@ -196,16 +194,11 @@ export const addTinlakeInvestorToMemberList = async (wallet: Request['wallet'],
: pool.addresses.JUNIOR_MEMBERLIST

const OneHundredYearsFromNow = Math.floor(Date.now() / 1000 + 100 * 365 * 24 * 60 * 60)
const tx = await memberAdminContract.functions.updateMember(
memberlistAddress,
wallet.address,
OneHundredYearsFromNow,
{
gasLimit: 1000000,
// TODO: find a better number, this is causing errors on goerli
// maxPriorityFeePerGas: 4000000000, // 4 gwei
}
)
const tx = await memberAdminContract.updateMember(memberlistAddress, wallet.address, OneHundredYearsFromNow, {
gasLimit: 1000000,
// TODO: find a better number, this is causing errors on goerli
// maxPriorityFeePerGas: 4000000000, // 4 gwei
})
const finalizedTx = await tx.wait()
console.log(`tx finalized: ${finalizedTx.transactionHash}, nonce=${tx.nonce}`)
return { txHash: finalizedTx.transactionHash }
8 changes: 5 additions & 3 deletions onboarding-api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"compilerOptions": {
"lib": ["ES2021"],
"lib": [
"ES2021"
],
"module": "commonjs",
"target": "ES2021",
"target": "esnext",
"noImplicitReturns": true,
"noUnusedLocals": true,
"outDir": "lib",
@@ -17,4 +19,4 @@
"include": [
"src"
]
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -31,13 +31,14 @@
"@sendgrid/mail": "^7.7.0",
"bn.js": "^5.2.1",
"dotenv": "16.0.3",
"ethers": "^6.13.2",
"rxjs": "^7.8.0"
},
"scripts": {
"format:run": "cd $INIT_CWD && prettier --write \"./**/*.{ts,tsx}\"",
"format:check": "cd $INIT_CWD && prettier --check \"./**/*.{ts,tsx}\"",
"onboarding-api": "yarn workspace @centrifuge/onboarding-api start:functions",
"pinning-api": "yarn workspace @centrifuge/pinning-api start:functions",
"pinning-api": "yarn workspace pinning-api start:functions",
"centrifuge-app": "yarn workspace @centrifuge/centrifuge-app start",
"faucet-api": "yarn workspace @centrifuge/faucet-api start",
"start": "concurrently --kill-others-on-fail \"yarn onboarding-api\" \"yarn pinning-api\" \"yarn centrifuge-app\" -p name -n \"onboarding-api,pinning-api,centrifuge-app\"",
9 changes: 4 additions & 5 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es5",
"target": "esnext",
"module": "commonjs",
"lib": ["ESNext"],

"lib": [
"ESNext"
],
/* Strict Type-Checking Options */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,

/* Module Resolution Options */
"moduleResolution": "node",
"esModuleInterop": true,

/* Advanced Options */
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
387 changes: 28 additions & 359 deletions yarn.lock

Large diffs are not rendered by default.


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

Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations

Check warning on line 178 in centrifuge-app/src/pages/IssuerPool/Investors/LiquidityPools.tsx

GitHub Actions / build-app

Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see https://mathiasbynens.github.io/rel-noopener/#recommendations
<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

React Hook React.useEffect has a missing dependency: 'financeForm'. Either include it or remove the dependency array

Check warning on line 107 in centrifuge-app/src/pages/Loan/ExternalFinanceForm.tsx

GitHub Actions / build-app

React Hook React.useEffect has a missing dependency: 'financeForm'. Either include it or remove the dependency array
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

React Hook React.useEffect has a missing dependency: 'repayForm'. Either include it or remove the dependency array

Check warning on line 149 in centrifuge-app/src/pages/Loan/ExternalRepayForm.tsx

GitHub Actions / build-app

React Hook React.useEffect has a missing dependency: 'repayForm'. Either include it or remove the dependency array
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

React Hook React.useEffect has a missing dependency: 'financeForm'. Either include it or remove the dependency array

Check warning on line 166 in centrifuge-app/src/pages/Loan/FinanceForm.tsx

GitHub Actions / build-app

React Hook React.useEffect has a missing dependency: 'financeForm'. Either include it or remove the dependency array
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

React Hook React.useEffect has a missing dependency: 'repayForm'. Either include it or remove the dependency array

Check warning on line 184 in centrifuge-app/src/pages/Loan/RepayForm.tsx

GitHub Actions / build-app

React Hook React.useEffect has a missing dependency: 'repayForm'. Either include it or remove the dependency array
const repayFormRef = React.useRef<HTMLFormElement>(null)
useFocusInvalidInput(repayForm, repayFormRef)