import Web3 from "web3" ; import { Log , LogsOptions , Transaction } from "web3-core" ; import web3CoreSubscriptions , { Subscription } from "web3-core-subscriptions" ; import { BlockHeader , Eth , Syncing } from "web3-eth" ; import { decodeParameter } from "web3-eth-abi" ; import { toHex } from "web3-utils" ; import { AlchemyWeb3Config , FullConfig , Provider , TransactionsOptions , Web3Callback , } from "./types" ; import { formatBlock } from "./util/hex" ; import { JsonRpcSenders } from "./util/jsonRpc" ; import { callWhenDone } from "./util/promises" ; import { makeAlchemyContext } from "./web3-adapter/alchemyContext" ; import { patchEnableCustomRPC } from "./web3-adapter/customRPC" ; import { patchEthFeeHistoryMethod } from "./web3-adapter/eth_feeHistory" ; import { patchEthMaxPriorityFeePerGasMethod } from "./web3-adapter/eth_maxPriorityFeePerGas" ; const DEFAULT_MAX_RETRIES = 3 ; const DEFAULT_RETRY_INTERVAL = 1000 ; const DEFAULT_RETRY_JITTER = 250 ; export interface AlchemyWeb3 extends Web3 { alchemy : AlchemyMethods ; eth : AlchemyEth ; setWriteProvider ( provider ) : void ; } export interface AlchemyMethods { getTokenAllowance ( params , callback ? ) : Promise < TokenAllowanceResponse > ; getTokenBalances ( address , contractAddresses , callback ? ) : Promise < TokenBalancesResponse > ; getTokenMetadata ( address , callback ? ) : Promise < TokenMetadataResponse > ; getAssetTransfers ( params , callback ? ) : Promise < AssetTransfersResponse > ; getNftMetadata ( params , callback ? ) : Promise < NftMetadataResponse > ; } export interface TokenAllowanceParams { contract : string ; owner : string ; spender : string ; } export type TokenAllowanceResponse = string ; export interface TokenBalancesResponse { address : string ; tokenBalances : TokenBalance [ ] ; } export type TokenBalance = TokenBalanceSuccess | TokenBalanceFailure ; export interface TokenBalanceSuccess { address : string ; tokenBalance : string ; error : null ; } export interface TokenBalanceFailure { address : string ; tokenBalance : null ; error : string ; } export interface TokenMetadataResponse { decimals : number | null ; logo : string | null ; name : string | null ; symbol : string | null ; } export interface AssetTransfersParams { fromBlock ? : string ; toBlock ? : string ; order ? : AssetTransfersOrder ; fromAddress ? : string ; toAddress ? : string ; contractAddresses ? : string [ ] ; excludeZeroValue ? : boolean ; maxCount ? : number ; category ? : AssetTransfersCategory [ ] ; pageKey ? : string ; } export enum AssetTransfersCategory { EXTERNAL = "external" , INTERNAL = "internal" , TOKEN = "token" , ERC20 = "erc20" , ERC721 = "erc721" , ERC1155 = "erc1155" } export enum AssetTransfersOrder { ASCENDING = "asc" , DESCENDING = "desc" } export interface AssetTransfersResponse { transfers : AssetTransfersResult [ ] ; pageKey ? : string ; } export interface AssetTransfersResult { category : AssetTransfersCategory ; blockNum : string ; from : string ; to : string | null ; value : number | null ; erc721TokenId : string | null ; erc1155Metadata : ERC1155Metadata [ ] | null ; asset : string | null ; hash : string ; rawContract : RawContract ; } export interface NftMetadataParams { contractAddress : string ; tokenId : string ; tokenType : "erc721" | "erc1155" ; } export interface NftMetadataResponse { contract : string ; tokenType : "erc721" | "erc1155" ; tokenId : string ; rawMetadataUri : string | null ; alchemyMetadataUri : string | null ; rawImageUri : string | null ; alchemyImageUri : string | null ; name : string | null ; description : string | null ; attributes : Array < Record < string , any > > ; rawMetadata : Record < string , any > ; } export interface ERC1155Metadata { tokenId : string ; value : string ; } export interface RawContract { value : string | null ; address : string | null ; decimal : string | null ; } /**
 * Same as Eth, but with `subscribe` allowing more types.
 */ export interface AlchemyEth extends Eth { subscribe ( type , options ? , callback ? ) : Subscription < Log > ; subscribe ( type , callback ? ) : Subscription < Syncing > ; subscribe ( type , callback ? ) : Subscription < BlockHeader > ; subscribe ( type , callback ? ) : Subscription < string > ; subscribe ( type , callback ? ) : Subscription < Transaction > ; subscribe ( type , options ? , callback ? ) : Subscription < Transaction > ; subscribe ( type , options ? , callback ? ) : Subscription < Log | BlockHeader | Syncing | string > ; } interface EthereumWindow extends Window { ethereum ? : any ; } declare const window ; export function createAlchemyWeb3 ( alchemyUrl , config ? ) { const fullConfig = fillInConfigDefaults ( config ) ; const { provider , senders , setWriteProvider } = makeAlchemyContext ( alchemyUrl , fullConfig ) ; const alchemyWeb3 = new Web3 ( provider ) as AlchemyWeb3 ; alchemyWeb3 . setProvider = ( ) => { throw new Error ( "setProvider is not supported in Alchemy Web3. To change the provider used for writes, use setWriteProvider() instead." ) ; } ; alchemyWeb3 . setWriteProvider = setWriteProvider ; alchemyWeb3 . alchemy = { getTokenAllowance : ( params , callback ) => callAlchemyMethod ( { senders , callback , method : "alchemy_getTokenAllowance" , params : [ params ] , } ) , getTokenBalances : ( address , contractAddresses , callback ) => callAlchemyMethod ( { senders , callback , method : "alchemy_getTokenBalances" , params : [ address , contractAddresses ] , processResponse : processTokenBalanceResponse , } ) , getTokenMetadata : ( address , callback ) => callAlchemyMethod ( { senders , callback , params : [ address ] , method : "alchemy_getTokenMetadata" , } ) , getAssetTransfers : ( params , callback ) => callAlchemyMethod ( { senders , callback , params : [ { ... params , fromBlock : params . fromBlock != null ? formatBlock ( params . fromBlock ) : undefined , toBlock : params . toBlock != null ? formatBlock ( params . toBlock ) : undefined , maxCount : params . maxCount != null ? toHex ( params . maxCount ) : undefined , } , ] , method : "alchemy_getAssetTransfers" , } ) , getNftMetadata : ( params , callback ) => callAlchemyMethod ( { senders , callback , params : [ { ... params } ] , method : "alchemy_getNftMetadata" , } ) , } ; patchSubscriptions ( alchemyWeb3 ) ; patchEnableCustomRPC ( alchemyWeb3 ) ; patchEthFeeHistoryMethod ( alchemyWeb3 ) ; patchEthMaxPriorityFeePerGasMethod ( alchemyWeb3 ) ; return alchemyWeb3 ; } function fillInConfigDefaults ( { writeProvider = getWindowProvider ( ) , maxRetries = DEFAULT_MAX_RETRIES , retryInterval = DEFAULT_RETRY_INTERVAL , retryJitter = DEFAULT_RETRY_JITTER , } = { } ) { return { writeProvider , maxRetries , retryInterval , retryJitter } ; } function getWindowProvider ( ) { return typeof window !== "undefined" ? window . ethereum : null ; } interface CallAlchemyMethodParams < T > { senders : JsonRpcSenders ; method : string ; params : any [ ] ; callback ? : Web3Callback < T > ; processResponse ? ( response ) : T ; } function callAlchemyMethod < T > ( { senders , method , params , callback = noop , processResponse = identity , } ) { const promise = ( async ( ) => { const result = await senders . send ( method , params ) ; return processResponse ( result ) ; } ) ( ) ; callWhenDone ( promise , callback ) ; return promise ; } function processTokenBalanceResponse ( rawResponse ) { const fixedTokenBalances = rawResponse . tokenBalances . map ( ( balance ) => balance . tokenBalance != null ? { ... balance , tokenBalance : decodeParameter ( "uint256" , balance . tokenBalance ) , } : balance ) ; return { ... rawResponse , tokenBalances : fixedTokenBalances } ; } /**
 * Updates Web3's internal subscription architecture to also handle Alchemy
 * specific subscriptions.
 */ function patchSubscriptions ( web3 ) { const { eth } = web3 ; const oldSubscribe = eth . subscribe . bind ( eth ) ; eth . subscribe = ( ( type , ... rest ) => { if ( type === "alchemy_fullPendingTransactions" || type === "alchemy_newFullPendingTransactions" ) { return suppressNoSubscriptionExistsWarning ( ( ) => oldSubscribe ( "alchemy_newFullPendingTransactions" as any , ... rest ) ) ; } if ( type === "alchemy_filteredNewFullPendingTransactions" || type === "alchemy_filteredPendingTransactions" || type === "alchemy_filteredFullPendingTransactions" ) { return suppressNoSubscriptionExistsWarning ( ( ) => oldSubscribe ( "alchemy_filteredNewFullPendingTransactions" as any , ... rest ) ) ; } return oldSubscribe ( type as any , ... rest ) ; } ) as any ; } /**
 * VERY hacky wrapper to suppress a spurious warning when subscribing to an
 * Alchemy subscription that isn't built into Web3.
 */ function suppressNoSubscriptionExistsWarning < T > ( f ) { const oldConsoleWarn = console . warn ; console . warn = ( ... args ) => { if ( typeof args [ 0 ] === "string" && args [ 0 ] . includes ( " doesn't exist. Subscribing anyway." ) ) { return ; } return oldConsoleWarn . apply ( console , args ) ; } ; try { return f ( ) ; } finally { console . warn = oldConsoleWarn ; } } /**
 * Another VERY hacky monkeypatch to make sure that we can take extra parameters to certain alchemy subscriptions
 * I hate doing this, but the other option is to fork web3-core and I think for now this is better
 */ const { subscription } = web3CoreSubscriptions as any ; const oldSubscriptionPrototypeValidateArgs = subscription . prototype . _validateArgs ; subscription . prototype . _validateArgs = function ( args ) { if ( [ "alchemy_filteredNewFullPendingTransactions" , "alchemy_filteredPendingTransactions" , "alchemy_filteredFullPendingTransactions" , ] . includes ( this . subscriptionMethod ) ) { } else { if ( [ "alchemy_fullPendingTransactions" , "alchemy_newFullPendingTransactions" , ] . includes ( this . subscriptionMethod ) ) { if ( this . options . subscription ) { this . options . subscription . subscriptionName = this . subscriptionMethod ; } } const validator = oldSubscriptionPrototypeValidateArgs . bind ( this ) ; validator ( args ) ; } } ; function noop ( ) { } function identity < T > ( x ) { return x ; }