@ixily/activ
Version:
Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.
485 lines (444 loc) • 13.9 kB
text/typescript
import { BehaviorSubject } from 'rxjs'
import { LogModule as log } from '../../modules/chunks/log.module'
import {
CHAIN_IDS,
Contract,
ContractInterface,
CONTRACTS,
ethers,
IBlockchainNetwork,
IChain,
IContract,
IContractRecipe,
isNullOrUndefined,
IWeb3AuthorizationOptions,
makeSureChainMatches,
SUPPORTED_BLOCKCHAIN_NETWORKS,
} from '../..'
import { BigNumber } from 'ethers'
import { WALLET_NETWORK_CHAIN_NAME } from '../../interfaces/chunks/blockchain-network.i'
const state = {
blockchainNetworks: new Map<string, IBlockchainNetwork>(),
selectedBlockchainNetwork: undefined as string | undefined,
selectedContract: undefined as string | undefined,
chainId: 0 as number,
gateContract: undefined as Contract | undefined,
viewsContract: undefined as Contract | undefined,
updated$: new BehaviorSubject<boolean>(false),
recordCreds: undefined as undefined | IWeb3AuthorizationOptions,
browserRecordedWallets: [] as string[],
browserSelectedWallet: undefined as string | undefined,
accountsChangedListener: undefined as any | undefined,
remover: undefined as any | undefined,
}
const getUpdatedStream = (): BehaviorSubject<boolean> => {
return state.updated$
}
const changeToContract = async (
blockchainNetwork: string,
contractName: string | IContractRecipe,
details?: IWeb3AuthorizationOptions,
): Promise<IContract> => {
if (details !== undefined) {
state.recordCreds = details
}
if (details === undefined && state.recordCreds !== undefined) {
details = state.recordCreds
}
if (details === undefined) {
throw new Error(
'Contract Module Error: Details is undefined. You must provide details on how to build contract by the first time you call it.',
)
}
if (
details?.userWalletPrivateKey === undefined &&
details?.webProvider === undefined
) {
throw new Error(
'Contract Module Error: You must provide either a web provider or a private key',
)
}
const bn = (await SUPPORTED_BLOCKCHAIN_NETWORKS())[blockchainNetwork]
if (isNullOrUndefined(bn)) {
throw new Error(`Blockchain network ${blockchainNetwork} not found`)
}
if (typeof contractName === 'string') {
if (isNullOrUndefined(bn.contracts[contractName])) {
throw new Error(`Contract ${contractName} not found`)
}
}
let alreadyContract = state.blockchainNetworks.get(blockchainNetwork)
if (isNullOrUndefined(alreadyContract)) {
state.blockchainNetworks.set(blockchainNetwork, bn)
alreadyContract = state.blockchainNetworks.get(blockchainNetwork)
}
let contract: IContract
if (typeof contractName === 'string') {
contract = alreadyContract!.contracts[contractName]
} else {
alreadyContract!.contracts[contractName.nameOrVersion] = {
name: contractName.nameOrVersion,
recipe: contractName,
gate: undefined as Contract | undefined,
cacheTax5min: undefined as BigNumber | undefined,
cacheTaxLastTime: undefined as number | undefined,
}
contract = alreadyContract!.contracts[contractName.nameOrVersion]
}
const { gateContract } = await build(
contract.recipe,
details!.userWalletPrivateKey,
details!.webProvider,
)
contract.gate = gateContract
// contract.core = coreContract
state.selectedBlockchainNetwork = blockchainNetwork
state.selectedContract =
typeof contractName === 'string'
? contractName
: contractName.nameOrVersion
state.browserRecordedWallets = []
state.browserSelectedWallet = undefined
state.updated$.next(true)
return contract
}
const buildContract = async (
chain: IChain,
contractAddress: string,
abi: ContractInterface,
webProvider?: any,
providerUrl?: string,
userWalletPrivateKey?: string,
): Promise<Contract> => {
let contract: Contract = undefined as any
// in this context we have a user wallet connected via web3 provider
if (!isNullOrUndefined(webProvider)) {
// console.log('chain:')
// console.log(chain)
state.chainId = CHAIN_IDS(chain)
/*
if (chain === 'hardhat') {
// console.log('hardhatDemoWalletToUse:')
// console.log(hardhatDemoWalletToUse)
const provider = new ethers.providers.JsonRpcProvider(providerUrl)
const wallet = new ethers.Wallet(
userWalletPrivateKey as string,
provider,
)
// when the user open activ from browser but the user have not installed the wallet we need to use the provider directly instead of the signer
try {
const signer = wallet.connect(provider)
contract = new ethers.Contract(contractAddress, abi, signer)
} catch (error) {
contract = new ethers.Contract(contractAddress, abi, provider)
}
} else {
*/
if (isNullOrUndefined(userWalletPrivateKey)) {
try {
const provider = new ethers.providers.Web3Provider(
webProvider,
CHAIN_IDS(chain),
)
// when the user open activ from browser but the user have not installed the wallet we need to use the provider directly instead of the signer
const signer = provider.getSigner()
contract = new ethers.Contract(contractAddress, abi, signer)
// console.log('contract')
// console.log(contract)
} catch (error) {
const provider = new ethers.providers.JsonRpcProvider(
providerUrl,
)
await provider.send('eth_requestAccounts', [])
const signer = provider.getSigner()
contract = new ethers.Contract(contractAddress, abi, signer)
// console.log('signer')
// console.log(signer)
// console.log('contractAddress')
// console.log(contractAddress)
// throw error
}
} else {
if (webProvider.mockedInTesting) {
const wallet = new ethers.Wallet(
userWalletPrivateKey as string,
webProvider,
)
const signer = wallet.connect(webProvider)
contract = new ethers.Contract(contractAddress, abi, signer)
} else {
const provider = new ethers.providers.Web3Provider(
webProvider,
chain,
)
const wallet = new ethers.Wallet(
userWalletPrivateKey as string,
provider,
)
// when the user open activ from browser but the user have not installed the wallet we need to use the provider directly instead of the signer
try {
const signer = wallet.connect(provider)
contract = new ethers.Contract(contractAddress, abi, signer)
} catch (error) {
contract = new ethers.Contract(
contractAddress,
abi,
provider,
)
}
}
}
// }
} else if (
// in this context the user there are not a wallet connected
isNullOrUndefined(webProvider) &&
isNullOrUndefined(userWalletPrivateKey)
) {
const provider = new ethers.providers.JsonRpcProvider(providerUrl)
contract = new ethers.Contract(contractAddress, abi, provider)
} else if (
// in this context we have a user wallet connected via private key
isNullOrUndefined(webProvider) &&
!isNullOrUndefined(userWalletPrivateKey)
) {
const provider = new ethers.providers.JsonRpcProvider(providerUrl)
const wallet = new ethers.Wallet(
userWalletPrivateKey as string,
provider,
)
const signer = wallet.connect(provider)
contract = new ethers.Contract(contractAddress, abi, signer)
}
// await state.provider.request({
// method: 'wallet_switchEthereumChain',
// params: [{ chainId }],
// })
// console.log('chain:')
// console.log(chain)
// console.log('contractAddress:')
// console.log(contractAddress)
// console.log('abi:')
// console.log(abi)
// console.log('webProvider:')
// console.log(webProvider)
// console.log('providerUrl:')
// console.log(providerUrl)
// console.log('userWalletPrivateKey:')
// console.log(userWalletPrivateKey)
// console.log('----------------')
// console.log('----------------')
//
// console.log('contract:')
// console.log(contract)
if (contract === undefined) {
throw new Error('Could not build Contract')
}
await makeSureChainMatches(chain)
return contract
}
const build = async (
contractRecipe: IContractRecipe,
userWalletPrivateKey?: string,
webProvider?: any,
): Promise<{
gateContract: Contract
// coreContract: Contract
}> => {
log.dev('contractRecipe.gateContractAddress')
log.dev(contractRecipe.gateContractAddress)
const gateContract = await buildContract(
contractRecipe.chain,
contractRecipe.gateContractAddress,
contractRecipe.gateAbi,
webProvider,
contractRecipe.providerUrl,
userWalletPrivateKey,
)
// const coreContract = await buildContract(
// contractRecipe.chain,
// contractRecipe.coreContractAddress,
// contractRecipe.coreAbi,
// webProvider,
// contractRecipe.providerUrl,
// userWalletPrivateKey,
// )
return {
gateContract,
// coreContract,
}
}
const listBlockchainNetworks = (): string[] => {
const SUPPORTED_BLOCKCHAIN_NETWORK_NAMES: string[] = []
for (const [key, value] of state.blockchainNetworks) {
SUPPORTED_BLOCKCHAIN_NETWORK_NAMES.push(value.name)
}
return SUPPORTED_BLOCKCHAIN_NETWORK_NAMES
}
const listContractsForNetwork = (network: string): string[] => {
const CONTRACT_NAMES: string[] = []
const contracts = state.blockchainNetworks.get(network)!.contracts
for (const key in contracts) {
CONTRACT_NAMES.push(key)
}
return CONTRACT_NAMES
}
const getBlockchainNetwork = (): IBlockchainNetwork => {
return state.blockchainNetworks.get(state.selectedBlockchainNetwork!)!
}
const getBlockchainContract = (): IContract => {
// console.log('state.selectedBlockchainNetwork')
// console.log(state.selectedBlockchainNetwork)
// console.log(
// 'state.blockchainNetworks.get(state.selectedBlockchainNetwork!)!',
// )
// console.log(state.blockchainNetworks.get(state.selectedBlockchainNetwork!)!)
return state.blockchainNetworks.get(state.selectedBlockchainNetwork!)!
.contracts[state.selectedContract!]!
}
const get = (): IContract | undefined => {
return state.blockchainNetworks.get(state.selectedBlockchainNetwork!)
?.contracts[state.selectedContract!]
}
const getGate = (): Contract | undefined => {
return state.blockchainNetworks.get(state.selectedBlockchainNetwork!)
?.contracts[state.selectedContract!]?.gate
}
const thisOrGet = (contract?: IContract): IContract => {
if (contract === undefined) {
contract = get()
}
if (contract === undefined) {
throw new Error('Contract is undefined')
}
return contract!
}
const requestSelectedBrowserWalletAddress = async (
contract?: IContract,
): Promise<string> => {
contract = thisOrGet(contract)
if (state.browserRecordedWallets.length === 0) {
const wp = getRecordedContractCreds()?.webProvider
if (wp === undefined) {
throw new Error('CONTRACT MODULE ERROR: Web provider is undefined')
}
if (state.accountsChangedListener !== undefined) {
if (state.remover !== undefined) {
state.remover()
}
state.accountsChangedListener = undefined
state.remover = undefined
}
if (state.accountsChangedListener === undefined) {
const accounts = await wp.request({ method: 'eth_requestAccounts' })
state.browserRecordedWallets = accounts
state.browserSelectedWallet = accounts[0]
// console.log('state.browserRecordedWallets')
// console.log(state.browserRecordedWallets)
// console.log('state.browserSelectedWallet')
// console.log(state.browserSelectedWallet)
state.accountsChangedListener = wp.on(
'accountsChanged',
(accounts: string[]) => {
state.browserRecordedWallets = accounts
state.browserSelectedWallet = accounts[0]
// console.log('state.browserRecordedWallets')
// console.log(state.browserRecordedWallets)
// console.log('state.browserSelectedWallet')
// console.log(state.browserSelectedWallet)
},
)
state.remover = () => {
wp.removeListener(
'accountsChanged',
state.accountsChangedListener,
)
}
}
return state.browserSelectedWallet!
}
return state.browserSelectedWallet!
}
const thisOrGetCreator = async (
creator?: string,
contract?: IContract,
): Promise<string> => {
contract = thisOrGet(contract)
if (creator === undefined) {
if (
contract.gate?.signer === undefined ||
contract.gate?.signer === null
) {
// probalby browser case
creator = await requestSelectedBrowserWalletAddress()
} else {
// probably node case
creator = await contract.gate!.signer.getAddress()
}
}
if (creator === undefined) {
throw new Error('Contract signer is undefined')
}
return creator!
}
const thisOrGetGate = (contract?: Contract): Contract => {
if (contract === undefined) {
contract = getGate()
}
if (contract === undefined) {
throw new Error('Contract is undefined')
}
return contract!
}
export interface IWalletSettings extends IContractRecipe {
callerAddress?: string
balanceInEth: string
}
const getWalletSettings = async (
contract?: IContract,
): Promise<IWalletSettings> => {
contract = thisOrGet(contract)
const callerAddress = await contract?.gate?.signer?.getAddress()
const balance = await contract?.gate?.signer?.getBalance()
const balanceInEth = Number(
ethers?.utils?.formatEther(balance || 0),
).toFixed(4)
const network = await contract.gate!.signer.provider!.getNetwork()
const networkName = WALLET_NETWORK_CHAIN_NAME(network.chainId) as 'amoy' // | 'sepolia' // | 'goerli' // | 'hardhat'
const contracts = {
// hardhat: CONTRACTS.IxilyActivV4_HardhatDemo,
// goerli: CONTRACTS.IxilyActivV4_Goerli,
// sepolia: CONTRACTS.IxilyActivV4_Sepolia,
amoy: CONTRACTS.IxilyActivV4_Amoy,
}
const contractSelected = contracts[networkName]
const settings: IWalletSettings = {
...contractSelected,
callerAddress: callerAddress,
balanceInEth,
}
return settings
}
const getRecordedContractCreds = ():
| undefined
| {
userWalletPrivateKey?: string
webProvider?: any
} => {
return state.recordCreds
}
export const ContractModule = {
build,
getUpdatedStream,
listBlockchainNetworks,
listContractsForNetwork,
getBlockchainNetwork,
getBlockchainContract,
get,
getGate,
thisOrGet,
thisOrGetCreator,
thisOrGetGate,
getWalletSettings,
changeToContract,
getRecordedContractCreds,
}