diff --git a/src/Centrifuge.ts b/src/Centrifuge.ts index 21016d2..0346ba9 100644 --- a/src/Centrifuge.ts +++ b/src/Centrifuge.ts @@ -4,13 +4,13 @@ import { defaultIfEmpty, defer, filter, - firstValueFrom, identity, isObservable, map, mergeMap, of, Subject, + switchMap, using, } from 'rxjs' import { fromFetch } from 'rxjs/fetch' @@ -18,6 +18,7 @@ import { createPublicClient, createWalletClient, custom, + getContract, http, parseEventLogs, type Abi, @@ -27,14 +28,18 @@ import { type WalletClient, type WatchEventOnLogsParameter, } from 'viem' +import { ABI } from './abi/index.js' import { Account } from './Account.js' import { chains } from './config/chains.js' +import type { CurrencyMetadata } from './config/lp.js' +import { PERMIT_TYPEHASH } from './constants.js' import { Pool } from './Pool.js' import type { HexString } from './types/index.js' import type { CentrifugeQueryOptions, Query } from './types/query.js' import type { OperationStatus, Signer, Transaction, TransactionCallbackParams } from './types/transaction.js' -import { hashKey, serializeForCache } from './utils/query.js' -import { makeThenable, shareReplayWithDelayedReset } from './utils/rx.js' +import { Currency } from './utils/BigInt.js' +import { hashKey } from './utils/query.js' +import { makeThenable, repeatOnEvents, shareReplayWithDelayedReset } from './utils/rx.js' import { doTransaction, isLocalAccount } from './utils/transaction.js' export type Config = { @@ -132,6 +137,83 @@ export class Centrifuge { return this._query(null, () => of(new Account(this, address as any, chainId ?? this.config.defaultChain))) } + /** + * Get the metadata for an ERC20 token + * @param address - The token address + * @param chainId - The chain ID + */ + currency(address: string, chainId?: number): Query { + const curAddress = address.toLowerCase() + const cid = chainId ?? this.config.defaultChain + return this._query(['currency', curAddress, cid], () => + defer(async () => { + const contract = getContract({ + address: curAddress as any, + abi: ABI.Currency, + client: this.getClient(cid)!, + }) + const [decimals, name, symbol, supportsPermit] = await Promise.all([ + contract.read.decimals!() as Promise, + contract.read.name!() as Promise, + contract.read.symbol!() as Promise, + contract.read.PERMIT_TYPEHASH!() + .then((hash) => hash === PERMIT_TYPEHASH) + .catch(() => false), + ]) + return { + address: curAddress as any, + decimals, + name, + symbol, + chainId: cid, + supportsPermit, + } + }) + ) + } + + /** + * Get the balance of an ERC20 token for a given owner. + * @param currency - The token address + * @param owner - The owner address + * @param chainId - The chain ID + */ + balance(currency: string, owner: string, chainId?: number) { + const address = owner.toLowerCase() + const cid = chainId ?? this.config.defaultChain + return this._query(['balance', currency, owner, cid], () => { + return this.currency(currency, cid).pipe( + switchMap((currencyMeta) => + defer(() => + this.getClient(cid)! + .readContract({ + address: currency as any, + abi: ABI.Currency, + functionName: 'balanceOf', + args: [address], + }) + .then((val: any) => new Currency(val, currencyMeta.decimals)) + ).pipe( + repeatOnEvents( + this, + { + address: currency, + abi: ABI.Currency, + eventName: 'Transfer', + filter: (events) => { + return events.some((event) => { + return event.args.from?.toLowerCase() === address || event.args.to?.toLowerCase() === address + }) + }, + }, + cid + ) + ) + ) + ) + }) + } + /** * Returns an observable of all events on a given chain. * @internal @@ -226,7 +308,7 @@ export class Centrifuge { #memoized = new Map() #memoizeWith(keys: any[], callback: () => T): T { - const cacheKey = hashKey(serializeForCache(keys)) + const cacheKey = hashKey(keys) if (this.#memoized.has(cacheKey)) { return this.#memoized.get(cacheKey) } @@ -332,13 +414,16 @@ export class Centrifuge { } const $query = createShared().pipe( - // For new subscribers, recreate the shared observable if the previously shared observable has completed - // and no longer has a cached value, which can happen with a finite `valueCacheTime`. + // When `valueCacheTime` is finite, and the cached value is expired, + // the shared observable will immediately complete upon the next subscription. + // This will cause the shared observable to be recreated. defaultIfEmpty(defer(createShared)), // For existing subscribers, merge any newly created shared observable. concatWith(sharedSubject), + // Flatten observables emitted from the `sharedSubject` mergeMap((d) => (isObservable(d) ? d : of(d))) ) + return makeThenable($query) } return keys ? this.#memoizeWith(keys, get) : get() @@ -381,14 +466,12 @@ export class Centrifuge { */ _transact( title: string, - transactionCallback: (params: TransactionCallbackParams) => Promise | Observable, + transactionCallback: (params: TransactionCallbackParams) => Promise, chainId?: number ) { return this._transactSequence(async function* (params) { const transaction = transactionCallback(params) - yield* doTransaction(title, params.publicClient, () => - 'then' in transaction ? transaction : firstValueFrom(transaction) - ) + yield* doTransaction(title, params.publicClient, () => transaction) }, chainId) } @@ -430,7 +513,7 @@ export class Centrifuge { params: TransactionCallbackParams ) => AsyncGenerator | Observable, chainId?: number - ) { + ): Transaction { const targetChainId = chainId ?? this.config.defaultChain const self = this async function* transact() { diff --git a/src/Pool.test.ts b/src/Pool.test.ts new file mode 100644 index 0000000..1d6932a --- /dev/null +++ b/src/Pool.test.ts @@ -0,0 +1,20 @@ +import { expect } from 'chai' +import { Pool } from './Pool.js' +import { context } from './tests/setup.js' + +const poolId = '2779829532' + +describe('Pool', () => { + let pool: Pool + beforeEach(() => { + const { centrifuge } = context + pool = new Pool(centrifuge, poolId) + }) + + it('get active networks of a pool', async () => { + const networks = await pool.activeNetworks() + expect(networks).to.have.length(2) + expect(networks[0]!.chainId).to.equal(11155111) + expect(networks[1]!.chainId).to.equal(84532) + }) +}) diff --git a/src/Pool.ts b/src/Pool.ts index e63d473..2be3196 100644 --- a/src/Pool.ts +++ b/src/Pool.ts @@ -1,8 +1,8 @@ import { catchError, combineLatest, map, of, switchMap, timeout } from 'rxjs' import type { Centrifuge } from './Centrifuge.js' import { Entity } from './Entity.js' -import { Reports } from './Reports/index.js' import { PoolNetwork } from './PoolNetwork.js' +import { Reports } from './Reports/index.js' export class Pool extends Entity { constructor( @@ -49,6 +49,21 @@ export class Pool extends Entity { }) } + /** + * Get a specific network where a pool can potentially be deployed. + */ + network(chainId: number) { + return this._query(null, () => { + return this.networks().pipe( + map((networks) => { + const network = networks.find((network) => network.chainId === chainId) + if (!network) throw new Error(`Network ${chainId} not found`) + return network + }) + ) + }) + } + /** * Get the networks where a pool is active. It doesn't mean that any vaults are deployed there necessarily. */ @@ -70,4 +85,8 @@ export class Pool extends Entity { ) }) } + + vault(chainId: number, trancheId: string, asset: string) { + return this._query(null, () => this.network(chainId).pipe(switchMap((network) => network.vault(trancheId, asset)))) + } } diff --git a/src/PoolNetwork.test.ts b/src/PoolNetwork.test.ts new file mode 100644 index 0000000..7ab8ea1 --- /dev/null +++ b/src/PoolNetwork.test.ts @@ -0,0 +1,37 @@ +import { expect } from 'chai' +import sinon from 'sinon' +import { Pool } from './Pool.js' +import { PoolNetwork } from './PoolNetwork.js' +import { context } from './tests/setup.js' + +const poolId = '2779829532' +const trancheId = '0xac6bffc5fd68f7772ceddec7b0a316ca' +const vaultAddress = '0x05eb35c2e4fa21fb06d3fab92916191b254b3504' + +describe('PoolNetwork', () => { + let poolNetwork: PoolNetwork + beforeEach(() => { + const { centrifuge } = context + const pool = new Pool(centrifuge, poolId) + poolNetwork = new PoolNetwork(centrifuge, pool, 11155111) + }) + + it('should get whether a pool is deployed to a network', async () => { + const isActive = await poolNetwork.isActive() + expect(isActive).to.equal(true) + + // non-active pool/network + const poolNetwork2 = new PoolNetwork(context.centrifuge, new Pool(context.centrifuge, '123'), 11155111) + const isActive2 = await poolNetwork2.isActive() + expect(isActive2).to.equal(false) + }) + + it('get vaults for a tranche', async () => { + const fetchSpy = sinon.spy(globalThis, 'fetch') + const vaults = await poolNetwork.vaults(trancheId) + expect(vaults).to.have.length(1) + expect(vaults[0]!.address.toLowerCase()).to.equal(vaultAddress) + // Calls should get batched + expect(fetchSpy.getCalls().length).to.equal(1) + }) +}) diff --git a/src/PoolNetwork.ts b/src/PoolNetwork.ts index 8108cdf..7e41320 100644 --- a/src/PoolNetwork.ts +++ b/src/PoolNetwork.ts @@ -1,5 +1,5 @@ -import { defer, switchMap } from 'rxjs' -import { getContract } from 'viem' +import { combineLatest, defer, map, switchMap } from 'rxjs' +import { getContract, toHex } from 'viem' import { ABI } from './abi/index.js' import type { Centrifuge } from './Centrifuge.js' import { lpConfig } from './config/lp.js' @@ -7,6 +7,8 @@ import { Entity } from './Entity.js' import type { Pool } from './Pool.js' import type { HexString } from './types/index.js' import { repeatOnEvents } from './utils/rx.js' +import { doTransaction } from './utils/transaction.js' +import { Vault } from './Vault.js' /** * Query and interact with a pool on a specific network. @@ -77,6 +79,149 @@ export class PoolNetwork extends Entity { ) } + /** + * Estimates the gas cost needed to bridge the message that results from a transaction. + * @internal + */ + _estimate() { + return this._root._query(['estimate', this.chainId], () => + defer(() => { + const bytes = toHex(new Uint8Array([0x12])) + const { centrifugeRouter } = lpConfig[this.chainId]! + return this._root.getClient(this.chainId)!.readContract({ + address: centrifugeRouter, + abi: ABI.CentrifugeRouter, + functionName: 'estimate', + args: [bytes], + }) as Promise + }) + ) + } + + /** + * Get the contract address of the share token. + * @internal + */ + _share(trancheId: string) { + return this._query(['share'], () => + this._poolManager().pipe( + switchMap((poolManager) => + defer( + () => + this._root.getClient(this.chainId)!.readContract({ + address: poolManager, + abi: ABI.PoolManager, + functionName: 'getTranche', + args: [this.pool.id, trancheId], + }) as Promise + ).pipe( + repeatOnEvents( + this._root, + { + address: poolManager, + abi: ABI.PoolManager, + eventName: 'DeployTranche', + filter: (events) => { + return events.some( + (event) => String(event.args.poolId) === this.pool.id && event.args.trancheId === trancheId + ) + }, + }, + this.chainId + ) + ) + ) + ) + ) + } + + /** + * Get the details of the share token. + * @param trancheId - The tranche ID + */ + shareCurrency(trancheId: string) { + return this._query(null, () => + this._share(trancheId).pipe(switchMap((share) => this._root.currency(share, this.chainId))) + ) + } + + /** + * Get the deployed Vaults for a given tranche. There may exist one Vault for each allowed investment currency. + * Vaults are used to submit/claim investments and redemptions. + * @param trancheId - The tranche ID + */ + vaults(trancheId: string) { + return this._query(['vaults', trancheId], () => + this._poolManager().pipe( + switchMap((poolManager) => + defer(async () => { + const { currencies } = lpConfig[this.chainId]! + if (!currencies.length) return [] + const contract = getContract({ + address: poolManager, + abi: ABI.PoolManager, + client: this._root.getClient(this.chainId)!, + }) + const results = await Promise.allSettled( + currencies.map(async (curAddr) => { + const vaultAddr = (await contract.read.getVault!([this.pool.id, trancheId, curAddr])) as HexString + return new Vault(this._root, this, trancheId, curAddr, vaultAddr) + }) + ) + return results.filter((result) => result.status === 'fulfilled').map((result) => result.value) + }).pipe( + repeatOnEvents( + this._root, + { + address: poolManager, + abi: ABI.PoolManager, + eventName: 'DeployVault', + filter: (events) => { + return events.some( + (event) => String(event.args.poolId) === this.pool.id && event.args.trancheId === trancheId + ) + }, + }, + this.chainId + ) + ) + ) + ) + ) + } + + /** + * Get all Vaults for all tranches in the pool. + */ + vaultsByTranche() { + return this._query(null, () => + this.pool.trancheIds().pipe( + switchMap((tranches) => { + return combineLatest(tranches.map((trancheId) => this.vaults(trancheId))).pipe( + map((vaults) => Object.fromEntries(vaults.flat().map((vault, index) => [tranches[index], vault]))) + ) + }) + ) + ) + } + + /** + * Get a specific Vault for a given tranche and investment currency. + * @param trancheId - The tranche ID + * @param asset - The investment currency address + */ + vault(trancheId: string, asset: string) { + return this._query(null, () => + this.vaults(trancheId).pipe( + map((vaults) => { + const vault = vaults.find((v) => v._asset === asset) + if (!vault) throw new Error('Vault not found') + return vault + }) + ) + ) + } + /** * Get whether the pool is active on this network. It's a prerequisite for deploying vaults, * and doesn't indicate whether any vaults have been deployed. @@ -113,4 +258,85 @@ export class PoolNetwork extends Entity { ) ) } + + /** + * Get whether a pool is active and the tranche token can be deployed. + * @param trancheId - The tranche ID + */ + canTrancheBeDeployed(trancheId: string) { + return this._query(['canTrancheBeDeployed'], () => + this._poolManager().pipe( + switchMap((manager) => { + return defer( + () => + this._root.getClient(this.chainId)!.readContract({ + address: manager, + abi: ABI.PoolManager, + functionName: 'canTrancheBeDeployed', + args: [this.pool.id, trancheId], + }) as Promise + ).pipe( + repeatOnEvents( + this._root, + { + address: manager, + abi: ABI.PoolManager, + eventName: 'DeployTranche', + filter: (events) => { + return events.some( + (event) => String(event.args.poolId) === this.pool.id && event.args.trancheId === trancheId + ) + }, + }, + this.chainId + ) + ) + }) + ) + ) + } + + /** + * Deploy a tranche token for the pool. + * @param trancheId - The tranche ID + */ + deployTranche(trancheId: string) { + const self = this + return this._transactSequence(async function* ({ walletClient, publicClient }) { + const [poolManager, canTrancheBeDeployed] = await Promise.all([ + self._poolManager(), + self.canTrancheBeDeployed(trancheId), + ]) + if (!canTrancheBeDeployed) throw new Error('Pool is not active on this network') + yield* doTransaction('Deploy tranche', publicClient, () => + walletClient.writeContract({ + address: poolManager, + abi: ABI.PoolManager, + functionName: 'deployTranche', + args: [self.pool.id, trancheId], + }) + ) + }, this.chainId) + } + + /** + * Deploy a vault for a specific tranche x currency combination. + * @param trancheId - The tranche ID + * @param currencyAddress - The investment currency address + */ + deployVault(trancheId: string, currencyAddress: string) { + const self = this + return this._transactSequence(async function* ({ walletClient, publicClient }) { + const [poolManager, trancheToken] = await Promise.all([self._poolManager(), self._share(trancheId)]) + if (!trancheToken) throw new Error('Pool is not active on this network') + yield* doTransaction('Deploy vault', publicClient, () => + walletClient.writeContract({ + address: poolManager, + abi: ABI.PoolManager, + functionName: 'deployVault', + args: [self.pool.id, trancheId, currencyAddress], + }) + ) + }, this.chainId) + } } diff --git a/src/Vault.test.ts b/src/Vault.test.ts new file mode 100644 index 0000000..2f35164 --- /dev/null +++ b/src/Vault.test.ts @@ -0,0 +1,188 @@ +import { expect } from 'chai' +import { firstValueFrom, lastValueFrom, skip, skipWhile, tap, toArray } from 'rxjs' +import sinon from 'sinon' +import { ABI } from './abi/index.js' +import { Pool } from './Pool.js' +import { PoolNetwork } from './PoolNetwork.js' +import { context } from './tests/setup.js' +import { Vault } from './Vault.js' + +const poolId = '2779829532' +const trancheId = '0xac6bffc5fd68f7772ceddec7b0a316ca' +const asset = '0x8503b4452bf6238cc76cdbee223b46d7196b1c93' +const vaultAddress = '0x05eb35c2e4fa21fb06d3fab92916191b254b3504' + +// Active investor with a pending redeem order +const investorA = '0x423420Ae467df6e90291fd0252c0A8a637C1e03f' +// Permissioned investor with no orders +const investorB = '0xa076b817Fade13Ee72C495910eDCe1ed953F9930' +// Investor with a claimable invest order +const investorC = '0x7fAbAa12da2E30650c841AC647e3567f942fcdf5' +// Non-permissioned investor +const investorD = '0x63892115da2e40f8135Abe99Dc5155dd552464F4' +// Investor with a claimable cancel deposit +const investorE = '0x655631E9F3d31a70DD6c9B4cFB5CfDe7445Fd0d2' + +describe('Vault', () => { + let vault: Vault + beforeEach(() => { + const { centrifuge } = context + const pool = new Pool(centrifuge, poolId) + const poolNetwork = new PoolNetwork(centrifuge, pool, 11155111) + vault = new Vault(centrifuge, poolNetwork, trancheId, asset, vaultAddress) + }) + + it('get investment details for an investor', async () => { + const fetchSpy = sinon.spy(globalThis, 'fetch') + const investment = await vault.investment(investorA) + expect(investment.isAllowedToInvest).to.equal(true) + // Calls should get batched + expect(fetchSpy.getCalls().length).to.equal(6) + }) + + it("should throw when placing an invest order larger than the users's balance", async () => { + context.tenderlyFork.impersonateAddress = investorB + context.centrifuge.setSigner(context.tenderlyFork.signer) + let error: Error | null = null + let emittedSigningStatus = false + try { + await lastValueFrom(vault.increaseInvestOrder(1000000000000000n).pipe(tap(() => (emittedSigningStatus = true)))) + } catch (e: any) { + error = e + } + expect(error?.message).to.equal('Insufficient balance') + expect(emittedSigningStatus).to.equal(false) + }) + + it('should throw when not allowed to invest', async () => { + context.tenderlyFork.impersonateAddress = investorD + context.centrifuge.setSigner(context.tenderlyFork.signer) + let error: Error | null = null + let emittedSigningStatus = false + try { + await lastValueFrom(vault.increaseInvestOrder(100000000n).pipe(tap(() => (emittedSigningStatus = true)))) + } catch (e: any) { + error = e + } + expect(error?.message).to.equal('Not allowed to invest') + expect(emittedSigningStatus).to.equal(false) + }) + + it('should place an invest order', async () => { + context.tenderlyFork.impersonateAddress = investorB + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [result, investmentAfter] = await Promise.all([ + lastValueFrom(vault.increaseInvestOrder(100000000n).pipe(toArray())), + firstValueFrom(vault.investment(investorB).pipe(skipWhile((i) => !i.pendingInvestCurrency.eq(100000000n)))), + ]) + expect(result[2]?.type).to.equal('TransactionConfirmed') + expect((result[2] as any).title).to.equal('Approve') + expect(result[5]?.type).to.equal('TransactionConfirmed') + expect((result[5] as any).title).to.equal('Invest') + expect(investmentAfter.pendingInvestCurrency.toBigInt()).to.equal(100000000n) + }) + + it('should cancel an invest order', async () => { + const investmentBefore = await vault.investment(investorB) + expect(investmentBefore.hasPendingCancelInvestRequest).to.equal(false) + context.tenderlyFork.impersonateAddress = investorB + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [result, investmentAfter] = await Promise.all([ + vault.cancelInvestOrder(), + firstValueFrom(vault.investment(investorB).pipe(skip(1))), + ]) + expect(result.type).to.equal('TransactionConfirmed') + expect(investmentAfter.hasPendingCancelInvestRequest).to.equal(true) + }) + + it('should claim a processed cancellation', async () => { + const investmentBefore = await vault.investment(investorE) + expect(investmentBefore.claimableCancelInvestCurrency.toBigInt()).to.equal(1234000000n) + context.tenderlyFork.impersonateAddress = investorE + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [result, investmentAfter] = await Promise.all([ + vault.claim(), + firstValueFrom(vault.investment(investorE).pipe(skipWhile((i) => !i.claimableCancelInvestCurrency.isZero()))), + ]) + expect(result.type).to.equal('TransactionConfirmed') + expect(investmentAfter.claimableCancelInvestCurrency.isZero()).to.equal(true) + }) + + it('should throw when trying to cancel a non-existing order', async () => { + context.tenderlyFork.impersonateAddress = investorB + context.centrifuge.setSigner(context.tenderlyFork.signer) + let thrown = false + let emittedSigningStatus = false + try { + await lastValueFrom(vault.cancelRedeemOrder().pipe(tap(() => (emittedSigningStatus = true)))) + } catch { + thrown = true + } + expect(thrown).to.equal(true) + expect(emittedSigningStatus).to.equal(false) + }) + + it('should claim an executed order', async () => { + const investmentBefore = await vault.investment(investorC) + expect(investmentBefore.claimableInvestShares.toBigInt()).to.equal(939254224n) + expect(investmentBefore.claimableInvestCurrencyEquivalent.toBigInt()).to.equal(999999999n) + context.tenderlyFork.impersonateAddress = investorC + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [result, investmentAfter] = await Promise.all([ + vault.claim(), + firstValueFrom(vault.investment(investorC).pipe(skipWhile((i) => !i.claimableInvestShares.isZero()))), + ]) + expect(result.type).to.equal('TransactionConfirmed') + expect(investmentAfter.claimableInvestShares.isZero()).to.equal(true) + expect(investmentAfter.claimableInvestCurrencyEquivalent.isZero()).to.equal(true) + expect(investmentAfter.shareBalance.toBigInt()).to.equal(939254224n) + }) + + it('should place a redeem order', async () => { + context.tenderlyFork.impersonateAddress = investorC + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [result, investmentAfter] = await Promise.all([ + lastValueFrom(vault.increaseRedeemOrder(939254224n).pipe(toArray())), + firstValueFrom(vault.investment(investorC).pipe(skipWhile((i) => !i.pendingRedeemShares.eq(939254224n)))), + ]) + expect(result[2]?.type).to.equal('TransactionConfirmed') + expect((result[2] as any).title).to.equal('Redeem') + expect(investmentAfter.pendingRedeemShares.toBigInt()).to.equal(939254224n) + }) + + it('should cancel a redeem order', async () => { + const investmentBefore = await vault.investment(investorA) + expect(investmentBefore.hasPendingCancelRedeemRequest).to.equal(false) + context.tenderlyFork.impersonateAddress = investorA + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [result, investmentAfter] = await Promise.all([ + vault.cancelRedeemOrder(), + firstValueFrom(vault.investment(investorA).pipe(skip(1))), + ]) + expect(result.type).to.equal('TransactionConfirmed') + expect(investmentAfter.hasPendingCancelRedeemRequest).to.equal(true) + }) + + it('should refetch investment details after a user is added', async () => { + const [poolManager, restrictionManager, investmentBefore] = await Promise.all([ + vault.network._poolManager(), + vault._restrictionManager(), + vault.investment(investorD), + ]) + expect(investmentBefore.isAllowedToInvest).to.equal(false) + context.tenderlyFork.impersonateAddress = poolManager + context.centrifuge.setSigner(context.tenderlyFork.signer) + const [, investmentAfter] = await Promise.all([ + context.centrifuge._transact('Add Investor', ({ walletClient }) => + walletClient.writeContract({ + address: restrictionManager, + abi: ABI.RestrictionManager, + functionName: 'updateMember', + args: [investmentBefore.shareCurrency.address, investorD, Math.floor(Date.now() / 1000) + 100000], + }) + ), + firstValueFrom(vault.investment(investorD).pipe(skip(1))), + ]) + expect(investmentAfter.isAllowedToInvest).to.equal(true) + }) +}) diff --git a/src/Vault.ts b/src/Vault.ts new file mode 100644 index 0000000..4e2057a --- /dev/null +++ b/src/Vault.ts @@ -0,0 +1,430 @@ +import { Decimal } from 'decimal.js-light' +import { combineLatest, defer, map, switchMap } from 'rxjs' +import { encodeFunctionData, getContract } from 'viem' +import type { Centrifuge } from './Centrifuge.js' +import { Entity } from './Entity.js' +import type { Pool } from './Pool.js' +import { PoolNetwork } from './PoolNetwork.js' +import { ABI } from './abi/index.js' +import { lpConfig } from './config/lp.js' +import type { HexString } from './types/index.js' +import { Currency, DecimalWrapper, Token } from './utils/BigInt.js' +import { repeatOnEvents } from './utils/rx.js' +import { doSignMessage, doTransaction, signPermit, type Permit } from './utils/transaction.js' + +/** + * Query and interact with a vault, which is the main entry point for investing and redeeming funds. + * A vault is the combination of a network, a pool, a tranche and an investment currency. + */ +export class Vault extends Entity { + pool: Pool + chainId: number + /** + * The contract address of the investment currency. + * @internal + */ + _asset: HexString + /** + * The contract address of the vault. + */ + address: HexString + constructor( + _root: Centrifuge, + public network: PoolNetwork, + public trancheId: string, + asset: HexString, + address: HexString + ) { + super(_root, ['vault', network.chainId, network.pool.id, trancheId, asset.toLowerCase()]) + this.chainId = network.chainId + this.pool = network.pool + this._asset = asset.toLowerCase() as HexString + this.address = address.toLowerCase() as HexString + } + + /** + * Get the contract address of the restriction mananger. + * @internal + */ + _restrictionManager() { + return this._query(['restrictionManager'], () => + this.network._share(this.trancheId).pipe( + switchMap( + (share) => + this._root.getClient(this.chainId)!.readContract({ + address: share, + abi: ABI.Currency, + functionName: 'hook', + }) as Promise + ) + ) + ) + } + + /** + * Get the details of the investment currency. + */ + investmentCurrency() { + return this._root.currency(this._asset, this.chainId) + } + + /** + * Get the details of the share token. + */ + shareCurrency() { + return this.network.shareCurrency(this.trancheId) + } + + /** + * Get the allowance of the investment currency for the CentrifugeRouter, + * which is the contract that moves funds into the vault on behalf of the investor. + * @param owner - The address of the owner + */ + allowance(owner: string) { + const address = owner.toLowerCase() + return this._query(['allowance', address], () => + this.investmentCurrency().pipe( + switchMap((currency) => + defer(() => + this._root + .getClient(this.chainId)! + .readContract({ + address: this._asset, + abi: ABI.Currency, + functionName: 'allowance', + args: [address, lpConfig[this.chainId]!.centrifugeRouter], + }) + .then((val: any) => new Currency(val, currency.decimals)) + ).pipe( + repeatOnEvents( + this._root, + { + address: this._asset, + abi: ABI.Currency, + eventName: ['Approval', 'Transfer'], + filter: (events) => { + return events.some((event) => { + return ( + event.args.owner?.toLowerCase() === address || + event.args.spender?.toLowerCase() === this._asset || + event.args.from?.toLowerCase() === address + ) + }) + }, + }, + this.chainId + ) + ) + ) + ) + ) + } + + /** + * Get the details of the investment of an investor in the vault and any pending investments or redemptions. + * @param investor - The address of the investor + */ + investment(investor: string) { + const address = investor.toLowerCase() as HexString + return this._query(['investment', address], () => + combineLatest([ + this.investmentCurrency(), + this.shareCurrency(), + this.network._investmentManager(), + this._restrictionManager(), + ]).pipe( + switchMap(([investmentCurrency, shareCurrency, investmentManagerAddress, restrictionManagerAddress]) => + combineLatest([ + this._root.balance(investmentCurrency.address, address, this.chainId), + this._root.balance(shareCurrency.address, address, this.chainId), + this.allowance(address), + defer(async () => { + const client = this._root.getClient(this.chainId)! + const vault = getContract({ address: this.address, abi: ABI.LiquidityPool, client }) + const investmentManager = getContract({ + address: investmentManagerAddress, + abi: ABI.InvestmentManager, + client, + }) + + const [isAllowedToInvest, maxDeposit, maxRedeem, investment] = await Promise.all([ + vault.read.isPermissioned!([address]) as Promise, + vault.read.maxDeposit!([address]) as Promise, + vault.read.maxRedeem!([address]) as Promise, + investmentManager.read.investments!([this.address, address]) as Promise< + [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint, boolean, boolean] + >, + ]) + + const [ + maxMint, + maxWithdraw, + , + , + pendingInvest, + pendingRedeem, + claimableCancelInvestCurrency, + claimableCancelRedeemShares, + hasPendingCancelInvestRequest, + hasPendingCancelRedeemRequest, + ] = investment + return { + isAllowedToInvest, + claimableInvestShares: new Token(maxMint, shareCurrency.decimals), + claimableInvestCurrencyEquivalent: new Currency(maxDeposit, investmentCurrency.decimals), + claimableRedeemCurrency: new Currency(maxWithdraw, investmentCurrency.decimals), + claimableRedeemSharesEquivalent: new Token(maxRedeem, shareCurrency.decimals), + pendingInvestCurrency: new Currency(pendingInvest, investmentCurrency.decimals), + pendingRedeemShares: new Token(pendingRedeem, shareCurrency.decimals), + claimableCancelInvestCurrency: new Currency(claimableCancelInvestCurrency, investmentCurrency.decimals), + claimableCancelRedeemShares: new Token(claimableCancelRedeemShares, shareCurrency.decimals), + hasPendingCancelInvestRequest, + hasPendingCancelRedeemRequest, + investmentCurrency, + shareCurrency, + } + }).pipe( + repeatOnEvents( + this._root, + { + address: [this.address, restrictionManagerAddress], + abi: [ABI.LiquidityPool, ABI.RestrictionManager], + eventName: [ + 'UpdateMember', + 'CancelDepositClaim', + 'CancelDepositClaimable', + 'CancelDepositRequest', + 'CancelRedeemClaim', + 'CancelRedeemClaimable', + 'CancelRedeemRequest', + 'Deposit', + 'DepositClaimable', + 'DepositRequest', + 'RedeemClaimable', + 'RedeemRequest', + 'Withdraw', + ], + filter: (events) => + events.some( + (event) => + event.args.receiver?.toLowerCase() === address || + event.args.controller?.toLowerCase() === address || + event.args.sender?.toLowerCase() === address || + event.args.owner?.toLowerCase() === address || + // UpdateMember event + (event.args.user?.toLowerCase() === address && + event.args.token?.toLowerCase() === shareCurrency.address) + ), + }, + this.chainId + ) + ), + ]) + ), + map(([currencyBalance, shareBalance, allowance, investment]) => ({ + ...investment, + shareBalance: new Token(shareBalance.toBigInt(), investment.shareCurrency.decimals), + investmentCurrencyBalance: currencyBalance, + investmentCurrencyAllowance: allowance, + })) + ) + ) + } + + /** + * Place an order to invest funds in the vault. If an order exists, it will increase the amount. + * @param investAmount - The amount to invest in the vault + */ + increaseInvestOrder(investAmount: NumberInput) { + const self = this + return this._transactSequence(async function* ({ walletClient, publicClient, signer, signingAddress }) { + const { centrifugeRouter } = lpConfig[self.chainId]! + const [estimate, investment] = await Promise.all([self.network._estimate(), self.investment(signingAddress)]) + const amount = toCurrency(investAmount, investment.investmentCurrency.decimals) + const { investmentCurrency, investmentCurrencyBalance, investmentCurrencyAllowance, isAllowedToInvest } = + investment + const supportsPermit = investmentCurrency.supportsPermit && 'send' in signer // eth-permit uses the deprecated send method + const needsApproval = investmentCurrencyAllowance.lt(amount) + + if (!isAllowedToInvest) throw new Error('Not allowed to invest') + if (amount.gt(investmentCurrencyBalance)) throw new Error('Insufficient balance') + if (!amount.gt(0n)) throw new Error('Order amount must be greater than 0') + + let permit: Permit | null = null + if (needsApproval) { + if (supportsPermit) { + permit = yield* doSignMessage('Sign Permit', () => + signPermit( + walletClient, + signer, + self.chainId, + signingAddress, + investmentCurrency.address, + centrifugeRouter, + amount.toBigInt() + ) + ) + } else { + yield* doTransaction('Approve', publicClient, () => + walletClient.writeContract({ + address: investmentCurrency.address, + abi: ABI.Currency, + functionName: 'approve', + args: [centrifugeRouter, amount], + }) + ) + } + } + + const enableData = encodeFunctionData({ + abi: ABI.CentrifugeRouter, + functionName: 'enableLockDepositRequest', + args: [self.address, amount], + }) + const requestData = encodeFunctionData({ + abi: ABI.CentrifugeRouter, + functionName: 'executeLockedDepositRequest', + args: [self.address, signingAddress, estimate], + }) + const permitData = + permit && + encodeFunctionData({ + abi: ABI.CentrifugeRouter, + functionName: 'permit', + args: [ + investmentCurrency.address, + centrifugeRouter, + amount.toString(), + permit.deadline, + permit.v, + permit.r, + permit.s, + ], + }) + yield* doTransaction('Invest', publicClient, () => + walletClient.writeContract({ + address: centrifugeRouter, + abi: ABI.CentrifugeRouter, + functionName: 'multicall', + args: [[enableData, requestData, permitData].filter(Boolean)], + value: estimate, + }) + ) + }, this.chainId) + } + + /** + * Cancel an open investment order. + */ + cancelInvestOrder() { + const self = this + return this._transactSequence(async function* ({ walletClient, signingAddress, publicClient }) { + const { centrifugeRouter } = lpConfig[self.chainId]! + const [estimate, investment] = await Promise.all([self.network._estimate(), self.investment(signingAddress)]) + + if (investment.pendingInvestCurrency.isZero()) throw new Error('No order to cancel') + + yield* doTransaction('Cancel invest order', publicClient, () => + walletClient.writeContract({ + address: centrifugeRouter, + abi: ABI.CentrifugeRouter, + functionName: 'cancelDepositRequest', + args: [self.address, estimate], + value: estimate, + }) + ) + }, this.chainId) + } + + /** + * Place an order to redeem funds from the vault. If an order exists, it will increase the amount. + * @param redeemAmount - The amount of shares to redeem + */ + increaseRedeemOrder(redeemAmount: NumberInput) { + const self = this + return this._transactSequence(async function* ({ walletClient, signingAddress, publicClient }) { + const { centrifugeRouter } = lpConfig[self.chainId]! + const [estimate, investment] = await Promise.all([self.network._estimate(), self.investment(signingAddress)]) + const amount = toCurrency(redeemAmount, investment.shareCurrency.decimals) + + if (amount.gt(investment.shareBalance)) throw new Error('Insufficient balance') + if (!amount.gt(0n)) throw new Error('Order amount must be greater than 0') + + yield* doTransaction('Redeem', publicClient, () => + walletClient.writeContract({ + address: centrifugeRouter, + abi: ABI.CentrifugeRouter, + functionName: 'requestRedeem', + args: [self.address, amount.toBigInt(), signingAddress, signingAddress, estimate], + value: estimate, + }) + ) + }, this.chainId) + } + + /** + * Cancel an open redemption order. + */ + cancelRedeemOrder() { + const self = this + return this._transactSequence(async function* ({ walletClient, signingAddress, publicClient }) { + const { centrifugeRouter } = lpConfig[self.chainId]! + const [estimate, investment] = await Promise.all([self.network._estimate(), self.investment(signingAddress)]) + + if (investment.pendingRedeemShares.isZero()) throw new Error('No order to cancel') + + yield* doTransaction('Cancel redeem order', publicClient, () => + walletClient.writeContract({ + address: centrifugeRouter, + abi: ABI.CentrifugeRouter, + functionName: 'cancelRedeemRequest', + args: [self.address, estimate], + value: estimate, + }) + ) + }, this.chainId) + } + + /** + * Claim any outstanding fund shares after an investment has gone through, or funds after an redemption has gone through. + * @param receiver - The address that should receive the funds. If not provided, the investor's address is used. + * @param controller - The address of the user that has invested. Allows someone else to claim on behalf of the user + * if the user has set the CentrifugeRouter as an operator on the vault. If not provided, the investor's address is used. + */ + claim(receiver?: string, controller?: string) { + const self = this + return this._transactSequence(async function* ({ walletClient, signingAddress, publicClient }) { + const { centrifugeRouter } = lpConfig[self.chainId]! + const investment = await self.investment(signingAddress) + const receiverAddress = receiver || signingAddress + const controllerAddress = controller || signingAddress + const functionName = investment.claimableCancelInvestCurrency.gt(0n) + ? 'claimCancelDepositRequest' + : investment.claimableCancelRedeemShares.gt(0n) + ? 'claimCancelRedeemRequest' + : investment.claimableInvestShares.gt(0n) + ? 'claimDeposit' + : investment.claimableRedeemCurrency.gt(0n) + ? 'claimRedeem' + : '' + + if (!functionName) throw new Error('No claimable funds') + + yield* doTransaction('Claim', publicClient, () => + walletClient.writeContract({ + address: centrifugeRouter, + abi: ABI.CentrifugeRouter, + functionName, + args: [self.address, receiverAddress, controllerAddress], + }) + ) + }) + } +} + +type NumberInput = number | bigint | DecimalWrapper | Decimal +function toCurrency(val: NumberInput, decimals: number) { + return typeof val === 'number' + ? Currency.fromFloat(val, decimals!) + : new Currency(val instanceof DecimalWrapper ? val.toBigInt() : val, decimals) +} diff --git a/src/abi/Currency.abi.json b/src/abi/Currency.abi.json index 2a0a03c..56cbb5e 100644 --- a/src/abi/Currency.abi.json +++ b/src/abi/Currency.abi.json @@ -9,5 +9,6 @@ "function decimals() view returns (uint8)", "function name() view returns (string)", "function symbol() view returns (string)", - "function checkTransferRestriction(address, address, uint) view returns (bool)" + "function checkTransferRestriction(address, address, uint) view returns (bool)", + "function hook() view returns (address)" ] diff --git a/src/abi/InvestmentManager.abi.json b/src/abi/InvestmentManager.abi.json index 06adf46..c3a16b1 100644 --- a/src/abi/InvestmentManager.abi.json +++ b/src/abi/InvestmentManager.abi.json @@ -1 +1,4 @@ -["function poolManager() view returns (address)"] +[ + "function poolManager() view returns (address)", + "function investments(address, address) view returns (uint128, uint128, uint256, uint256, uint128, uint128, uint128, uint128, bool, bool)" +] diff --git a/src/abi/LiquidityPool.abi.json b/src/abi/LiquidityPool.abi.json index ab4b837..c7d42f1 100644 --- a/src/abi/LiquidityPool.abi.json +++ b/src/abi/LiquidityPool.abi.json @@ -1,15 +1,79 @@ [ - "event DepositRequest(address indexed, address indexed, uint256 indexed, address, uint256)", - "event RedeemRequest(address indexed, address indexed, uint256 indexed, address, uint256)", - "event CancelDepositRequest(address indexed)", - "event CancelRedeemRequest(address indexed)", - "function mint(uint256, address) public returns (uint256)", - "function withdraw(uint256, address, address) public returns (uint256)", - "function requestDeposit(uint256, address, address) public", - "function requestRedeem(uint256, address, address) public", - "function requestDepositWithPermit(uint256, address, bytes, uint256, uint8, bytes32, bytes32) public", - "function cancelDepositRequest(uint256, address) public", - "function cancelRedeemRequest(uint256, address) public", - "function claimCancelDepositRequest(uint256, address, address) public", - "function claimCancelRedeemRequest(uint256, address, address) public" + "event CancelDepositClaim(address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 assets)", + "event CancelDepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets)", + "event CancelDepositRequest(address indexed controller, uint256 indexed requestId, address sender)", + "event CancelRedeemClaim(address indexed receiver, address indexed controller, uint256 indexed requestId, address sender, uint256 shares)", + "event CancelRedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 shares)", + "event CancelRedeemRequest(address indexed controller, uint256 indexed requestId, address sender)", + "event Deny(address indexed user)", + "event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)", + "event DepositClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares)", + "event DepositRequest(address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets)", + "event File(bytes32 indexed what, address data)", + "event OperatorSet(address indexed controller, address indexed operator, bool approved)", + "event RedeemClaimable(address indexed controller, uint256 indexed requestId, uint256 assets, uint256 shares)", + "event RedeemRequest(address indexed controller, address indexed owner, uint256 indexed requestId, address sender, uint256 assets)", + "event Rely(address indexed user)", + "event Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)", + "function AUTHORIZE_OPERATOR_TYPEHASH() view returns (bytes32)", + "function DOMAIN_SEPARATOR() view returns (bytes32)", + "function asset() view returns (address)", + "function authorizations(address controller, bytes32 nonce) view returns (bool used)", + "function authorizeOperator(address controller, address operator, bool approved, bytes32 nonce, uint256 deadline, bytes signature) returns (bool success)", + "function cancelDepositRequest(uint256, address controller)", + "function cancelRedeemRequest(uint256, address controller)", + "function claimCancelDepositRequest(uint256, address receiver, address controller) returns (uint256 assets)", + "function claimCancelRedeemRequest(uint256, address receiver, address controller) returns (uint256 shares)", + "function claimableCancelDepositRequest(uint256, address controller) view returns (uint256 claimableAssets)", + "function claimableCancelRedeemRequest(uint256, address controller) view returns (uint256 claimableShares)", + "function claimableDepositRequest(uint256, address controller) view returns (uint256 claimableAssets)", + "function claimableRedeemRequest(uint256, address controller) view returns (uint256 claimableShares)", + "function convertToAssets(uint256 shares) view returns (uint256 assets)", + "function convertToShares(uint256 assets) view returns (uint256 shares)", + "function deny(address user)", + "function deploymentChainId() view returns (uint256)", + "function deposit(uint256 assets, address receiver, address controller) returns (uint256 shares)", + "function deposit(uint256 assets, address receiver) returns (uint256 shares)", + "function escrow() view returns (address)", + "function file(bytes32 what, address data)", + "function invalidateNonce(bytes32 nonce)", + "function isOperator(address, address) view returns (bool)", + "function isPermissioned(address controller) view returns (bool)", + "function manager() view returns (address)", + "function maxDeposit(address controller) view returns (uint256 maxAssets)", + "function maxMint(address controller) view returns (uint256 maxShares)", + "function maxRedeem(address controller) view returns (uint256 maxShares)", + "function maxWithdraw(address controller) view returns (uint256 maxAssets)", + "function mint(uint256 shares, address receiver) returns (uint256 assets)", + "function mint(uint256 shares, address receiver, address controller) returns (uint256 assets)", + "function onCancelDepositClaimable(address controller, uint256 assets)", + "function onCancelRedeemClaimable(address controller, uint256 shares)", + "function onDepositClaimable(address controller, uint256 assets, uint256 shares)", + "function onRedeemClaimable(address controller, uint256 assets, uint256 shares)", + "function onRedeemRequest(address controller, address owner, uint256 shares)", + "function pendingCancelDepositRequest(uint256, address controller) view returns (bool isPending)", + "function pendingCancelRedeemRequest(uint256, address controller) view returns (bool isPending)", + "function pendingDepositRequest(uint256, address controller) view returns (uint256 pendingAssets)", + "function pendingRedeemRequest(uint256, address controller) view returns (uint256 pendingShares)", + "function poolId() view returns (uint64)", + "function previewDeposit(uint256) pure returns (uint256)", + "function previewMint(uint256) pure returns (uint256)", + "function previewRedeem(uint256) pure returns (uint256)", + "function previewWithdraw(uint256) pure returns (uint256)", + "function priceLastUpdated() view returns (uint64)", + "function pricePerShare() view returns (uint256)", + "function recoverTokens(address token, address to, uint256 amount)", + "function redeem(uint256 shares, address receiver, address controller) returns (uint256 assets)", + "function rely(address user)", + "function requestDeposit(uint256 assets, address controller, address owner) returns (uint256)", + "function requestRedeem(uint256 shares, address controller, address owner) returns (uint256)", + "function root() view returns (address)", + "function setEndorsedOperator(address owner, bool approved)", + "function setOperator(address operator, bool approved) returns (bool success)", + "function share() view returns (address)", + "function supportsInterface(bytes4 interfaceId) pure returns (bool)", + "function totalAssets() view returns (uint256)", + "function trancheId() view returns (bytes16)", + "function wards(address) view returns (uint256)", + "function withdraw(uint256 assets, address receiver, address controller) returns (uint256 shares)" ] diff --git a/src/abi/PoolManager.abi.json b/src/abi/PoolManager.abi.json index 1243bf8..5d5b489 100644 --- a/src/abi/PoolManager.abi.json +++ b/src/abi/PoolManager.abi.json @@ -30,7 +30,7 @@ "function gateway() view returns (address)", "function getTranche(uint64 poolId, bytes16 trancheId) view returns (address)", "function getTranchePrice(uint64 poolId, bytes16 trancheId, address asset) view returns (uint128 price, uint64 computedAt)", - "function getVault(uint64 poolId, bytes16 trancheId, uint128 assetId) view returns (address)", + "function getVault_(uint64 poolId, bytes16 trancheId, uint128 assetId) view returns (address)", "function getVault(uint64 poolId, bytes16 trancheId, address asset) view returns (address)", "function getVaultAsset(address vault) view returns (address, bool)", "function handle(bytes message)", diff --git a/src/abi/RestrictionManager.abi.json b/src/abi/RestrictionManager.abi.json new file mode 100644 index 0000000..72bb9aa --- /dev/null +++ b/src/abi/RestrictionManager.abi.json @@ -0,0 +1,22 @@ +[ + "event Deny(address indexed user)", + "event Freeze(address indexed token, address indexed user)", + "event Rely(address indexed user)", + "event Unfreeze(address indexed token, address indexed user)", + "event UpdateMember(address indexed token, address indexed user, uint64 validUntil)", + "function FREEZE_BIT() view returns (uint8)", + "function checkERC20Transfer(address from, address to, uint256, (bytes16 from, bytes16 to) hookData) view returns (bool)", + "function deny(address user)", + "function freeze(address token, address user)", + "function isFrozen(address token, address user) view returns (bool)", + "function isMember(address token, address user) view returns (bool isValid, uint64 validUntil)", + "function onERC20AuthTransfer(address, address, address, uint256, (bytes16 from, bytes16 to)) pure returns (bytes4)", + "function onERC20Transfer(address from, address to, uint256 value, (bytes16 from, bytes16 to) hookData) returns (bytes4)", + "function rely(address user)", + "function root() view returns (address)", + "function supportsInterface(bytes4 interfaceId) pure returns (bool)", + "function unfreeze(address token, address user)", + "function updateMember(address token, address user, uint64 validUntil)", + "function updateRestriction(address token, bytes update)", + "function wards(address) view returns (uint256)" +] diff --git a/src/abi/index.ts b/src/abi/index.ts index 506cb4d..43bf37a 100644 --- a/src/abi/index.ts +++ b/src/abi/index.ts @@ -5,11 +5,13 @@ import Gateway from './Gateway.abi.json' assert { type: 'json' } import InvestmentManager from './InvestmentManager.abi.json' assert { type: 'json' } import LiquidityPool from './LiquidityPool.abi.json' assert { type: 'json' } import PoolManager from './PoolManager.abi.json' assert { type: 'json' } +import RestrictionManager from './RestrictionManager.abi.json' assert { type: 'json' } import Router from './Router.abi.json' assert { type: 'json' } export const ABI = { CentrifugeRouter: parseAbi(CentrifugeRouter), Currency: parseAbi(Currency), + RestrictionManager: parseAbi(RestrictionManager), Gateway: parseAbi(Gateway), InvestmentManager: parseAbi(InvestmentManager), LiquidityPool: parseAbi(LiquidityPool), diff --git a/src/config/lp.ts b/src/config/lp.ts index 0b6b78e..5a0c33c 100644 --- a/src/config/lp.ts +++ b/src/config/lp.ts @@ -3,32 +3,48 @@ import type { HexString } from '../types/index.js' type LPConfig = { centrifugeRouter: HexString router: HexString + currencies: HexString[] } export const lpConfig: Record = { // Testnet 11155111: { centrifugeRouter: '0x723635430aa191ef5f6f856415f41b1a4d81dd7a', router: '0x130ce3f3c17b4458d6d4dfdf58a86aa2d261662e', + currencies: ['0x8503b4452Bf6238cC76CdbEE223b46d7196b1c93', '0xe2ac3c946445f9ff45ddce8acf17c93b7dd6295a'], }, 84532: { centrifugeRouter: '0x723635430aa191ef5f6f856415f41b1a4d81dd7a', router: '0xec55db8b44088198a2d72da798535bffb64fba5c', + currencies: ['0xf703620970dcb2f6c5a8eac1c446ec1abddb8191'], }, // Mainnet 1: { centrifugeRouter: '0x2F445BA946044C5F508a63eEaF7EAb673c69a1F4', router: '0x85bafcadea202258e3512ffbc3e2c9ee6ad56365', + currencies: [], }, 42161: { centrifugeRouter: '0x2F445BA946044C5F508a63eEaF7EAb673c69a1F4', router: '0x85bafcadea202258e3512ffbc3e2c9ee6ad56365', + currencies: [], }, 8453: { centrifugeRouter: '0xF35501E7fC4a076E744dbAFA883CED74CCF5009d', router: '0x30e34260b895cae34a1cfb185271628c53311cf3', + currencies: [], }, 42220: { centrifugeRouter: '0x5a00C4fF931f37202aD4Be1FDB297E9EDc1CBb33', router: '0xe4e34083a49df72e634121f32583c9ea59191cca', + currencies: [], }, } + +export type CurrencyMetadata = { + address: HexString + decimals: number + name: string + symbol: string + chainId: number + supportsPermit: boolean +} diff --git a/src/index.ts b/src/index.ts index dff7167..4046ac3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,5 +4,6 @@ export * from './PoolNetwork.js' export * from './types/index.js' export * from './types/query.js' export * from './types/transaction.js' +export * from './Vault.js' export default Centrifuge diff --git a/src/tests/Centrifuge.test.ts b/src/tests/Centrifuge.test.ts index 2ddba9a..81e05c6 100644 --- a/src/tests/Centrifuge.test.ts +++ b/src/tests/Centrifuge.test.ts @@ -180,6 +180,39 @@ describe('Centrifuge', () => { expect(value2).to.equal(3) expect(value3).to.equal(6) }) + + it('should update dependant queries with values from dependencies', async () => { + let i = 0 + const query1 = context.centrifuge._query(['key3'], () => of(++i), { valueCacheTime: 120 }) + const query2 = context.centrifuge._query(['key4'], () => query1.pipe(map((v1) => v1 * 10))) + const value1 = await query2 + clock.tick(150_000) + const value3 = await query2 + expect(value1).to.equal(10) + expect(value3).to.equal(20) + }) + + it('should recreate the shared observable when the cached value is expired', async () => { + let i = 0 + const query1 = context.centrifuge._query( + null, + () => + defer(async function* () { + yield await lazy(`${++i}-A`) + yield await lazy(`${i}-B`, 5000) + }), + { valueCacheTime: 1 } + ) + let lastValue = '' + const subscription1 = query1.subscribe((next) => (lastValue = next)) + const value1 = await query1 + clock.tick(2_000) + const value2 = await query1 + expect(value1).to.equal('1-A') + expect(value2).to.equal('2-A') + expect(lastValue).to.equal('2-A') + subscription1.unsubscribe() + }) }) describe('Transactions', () => { @@ -285,8 +318,8 @@ describe('Centrifuge', () => { }) }) -function lazy(value: T) { - return new Promise((res) => setTimeout(() => res(value), 10)) +function lazy(value: T, t = 10) { + return new Promise((res) => setTimeout(() => res(value), t)) } function mockProvider({ chainId = 11155111, accounts = ['0x2'] } = {}) { diff --git a/src/tests/setup.ts b/src/tests/setup.ts index fe7fbd8..a487c2c 100644 --- a/src/tests/setup.ts +++ b/src/tests/setup.ts @@ -3,18 +3,22 @@ import { Centrifuge } from '../Centrifuge.js' import { TenderlyFork } from './tenderly.js' class TestContext { - public centrifuge!: Centrifuge + #centrifuge: Centrifuge | null = null public tenderlyFork!: TenderlyFork + get centrifuge() { + return this.#centrifuge ?? (this.#centrifuge = new Centrifuge({ environment: 'demo' })) + } + async initialize() { this.tenderlyFork = await TenderlyFork.create(sepolia) - this.centrifuge = new Centrifuge({ + this.#centrifuge = new Centrifuge({ environment: 'demo', rpcUrls: { 11155111: this.tenderlyFork.rpcUrl, }, }) - this.centrifuge.setSigner(this.tenderlyFork.signer) + this.#centrifuge.setSigner(this.tenderlyFork.signer) } async cleanup() { diff --git a/src/tests/tenderly.ts b/src/tests/tenderly.ts index 791186e..049485e 100644 --- a/src/tests/tenderly.ts +++ b/src/tests/tenderly.ts @@ -201,7 +201,7 @@ export class TenderlyFork { display_name: `Centrifuge Sepolia Fork ${timestamp}`, fork_config: { network_id: this.chain.id, - block_number: '6924285', + block_number: '7116950', }, virtual_network_config: { chain_config: { diff --git a/src/utils/BigInt.ts b/src/utils/BigInt.ts index 371ef33..47d8e71 100644 --- a/src/utils/BigInt.ts +++ b/src/utils/BigInt.ts @@ -1,14 +1,14 @@ -import Decimal, { type Numeric } from 'decimal.js-light' +import { Decimal, type Numeric } from 'decimal.js-light' -Decimal.default.set({ +Decimal.set({ precision: 30, toExpNeg: -7, toExpPos: 29, - rounding: Decimal.default.ROUND_HALF_CEIL, // ROUND_HALF_CEIL is 1 + rounding: Decimal.ROUND_HALF_CEIL, // ROUND_HALF_CEIL is 1 }) export function Dec(value: Numeric) { - return new Decimal.default(value) + return new Decimal(value) } export abstract class BigIntWrapper { @@ -17,7 +17,7 @@ export abstract class BigIntWrapper { constructor(value: Numeric | bigint) { if (typeof value === 'bigint') { this.value = value - } else if (value instanceof Decimal.default) { + } else if (value instanceof Decimal) { this.value = BigInt(value.toFixed(0)) } else if (typeof value === 'number') { this.value = BigInt(Math.floor(value)) @@ -77,10 +77,10 @@ export class DecimalWrapper extends BigIntWrapper { * // returns Currency with 6 decimals (1_010_000n or 1.01) */ _mul(value: bigint | (T extends DecimalWrapper ? T : never)): T { - let val: Decimal.default + let val: Decimal if (typeof value === 'bigint') { val = Dec(value.toString()) - } else if (value instanceof Decimal.default) { + } else if (value instanceof Decimal) { val = value } else { val = value.toDecimal().mul(Dec(10).pow(this.decimals)) @@ -122,6 +122,9 @@ export class DecimalWrapper extends BigIntWrapper { const val = typeof value === 'bigint' ? value : value.toBigInt() return this.value === val } + isZero() { + return this.value === 0n + } } export class Currency extends DecimalWrapper { diff --git a/src/utils/query.ts b/src/utils/query.ts index e206d85..53081a2 100644 --- a/src/utils/query.ts +++ b/src/utils/query.ts @@ -8,10 +8,14 @@ export function hashKey(key: any[]): string { result[key] = val[key] return result }, {} as any) - : val + : jsonFormatter(val) ) } +function jsonFormatter(nestedValue: any) { + return typeof nestedValue === 'bigint' ? nestedValue.toString() : nestedValue +} + function isObject(o: any) { return Object.prototype.toString.call(o) === '[object Object]' } @@ -36,16 +40,3 @@ function isPlainObject(o: any) { // Most likely a plain Object return true } - -export function serializeForCache(value: any): any { - if (typeof value === 'bigint') { - return value.toString() - } - if (Array.isArray(value)) { - return value.map((v) => serializeForCache(v)) - } - if (typeof value === 'object' && value !== null) { - return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, serializeForCache(v)])) - } - return value -} diff --git a/src/utils/rx.ts b/src/utils/rx.ts index d23f5ed..3a0911e 100644 --- a/src/utils/rx.ts +++ b/src/utils/rx.ts @@ -1,4 +1,4 @@ -import type { MonoTypeOperatorFunction, Observable } from 'rxjs' +import type { MonoTypeOperatorFunction, Observable, Subscriber, Subscription } from 'rxjs' import { filter, firstValueFrom, lastValueFrom, repeat, ReplaySubject, share, Subject, timer } from 'rxjs' import type { Abi, Log } from 'viem' import type { Centrifuge } from '../Centrifuge.js' @@ -12,7 +12,7 @@ export function shareReplayWithDelayedReset(config?: { const { bufferSize = Infinity, windowTime = Infinity, resetDelay = 1000 } = config ?? {} const reset = resetDelay === 0 ? true : isFinite(resetDelay) ? () => timer(resetDelay) : false return share({ - connector: () => (bufferSize === 0 ? new Subject() : new ReplaySubject(bufferSize, windowTime)), + connector: () => (bufferSize === 0 ? new Subject() : new ExpiringReplaySubject(bufferSize, windowTime)), resetOnError: true, resetOnComplete: false, resetOnRefCountZero: reset, @@ -50,3 +50,20 @@ export function makeThenable($query: Observable, exhaust = false) { }) return thenableQuery } + +// A ReplaySubject that completes when an existing buffer is expired +class ExpiringReplaySubject extends ReplaySubject { + // @ts-expect-error + protected override _subscribe(subscriber: Subscriber): Subscription { + // @ts-expect-error + const { _buffer } = this + const length = _buffer.length + // @ts-expect-error + const subscription = super._subscribe(subscriber) + + if (length && _buffer.length === 0) { + this.complete() + } + return subscription + } +} diff --git a/tsconfig.json b/tsconfig.json index 256359a..22e2a14 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,19 +14,10 @@ "declarationMap": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, - "lib": [ - "es2023", - "dom", - "dom.iterable" - ], + "lib": ["es2023", "dom", "dom.iterable"], "outDir": "dist", "allowSyntheticDefaultImports": true }, - "include": [ - "src", - "src/**/*.d.ts" - ], - "exclude": [ - "**/*.test.ts" - ] -} \ No newline at end of file + "include": ["src", "src/**/*.d.ts"], + "exclude": ["**/*.test.ts"] +} diff --git a/yarn.lock b/yarn.lock index 0b93694..909a45d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,7 +5,7 @@ __metadata: version: 8 cacheKey: 10c0 -"@adraffy/ens-normalize@npm:1.11.0": +"@adraffy/ens-normalize@npm:^1.10.1": version: 1.11.0 resolution: "@adraffy/ens-normalize@npm:1.11.0" checksum: 10c0/5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 @@ -53,44 +53,44 @@ __metadata: linkType: hard "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": - version: 4.4.0 - resolution: "@eslint-community/eslint-utils@npm:4.4.0" + version: 4.4.1 + resolution: "@eslint-community/eslint-utils@npm:4.4.1" dependencies: - eslint-visitor-keys: "npm:^3.3.0" + eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/7e559c4ce59cd3a06b1b5a517b593912e680a7f981ae7affab0d01d709e99cd5647019be8fafa38c350305bc32f1f7d42c7073edde2ab536c745e365f37b607e + checksum: 10c0/2aa0ac2fc50ff3f234408b10900ed4f1a0b19352f21346ad4cc3d83a1271481bdda11097baa45d484dd564c895e0762a27a8240be7a256b3ad47129e96528252 languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0": - version: 4.11.1 - resolution: "@eslint-community/regexpp@npm:4.11.1" - checksum: 10c0/fbcc1cb65ef5ed5b92faa8dc542e035269065e7ebcc0b39c81a4fe98ad35cfff20b3c8df048641de15a7757e07d69f85e2579c1a5055f993413ba18c055654f8 +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": + version: 4.12.1 + resolution: "@eslint-community/regexpp@npm:4.12.1" + checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 languageName: node linkType: hard -"@eslint/config-array@npm:^0.18.0": - version: 0.18.0 - resolution: "@eslint/config-array@npm:0.18.0" +"@eslint/config-array@npm:^0.19.0": + version: 0.19.0 + resolution: "@eslint/config-array@npm:0.19.0" dependencies: "@eslint/object-schema": "npm:^2.1.4" debug: "npm:^4.3.1" minimatch: "npm:^3.1.2" - checksum: 10c0/0234aeb3e6b052ad2402a647d0b4f8a6aa71524bafe1adad0b8db1dfe94d7f5f26d67c80f79bb37ac61361a1d4b14bb8fb475efe501de37263cf55eabb79868f + checksum: 10c0/def23c6c67a8f98dc88f1b87e17a5668e5028f5ab9459661aabfe08e08f2acd557474bbaf9ba227be0921ae4db232c62773dbb7739815f8415678eb8f592dbf5 languageName: node linkType: hard -"@eslint/core@npm:^0.7.0": - version: 0.7.0 - resolution: "@eslint/core@npm:0.7.0" - checksum: 10c0/3cdee8bc6cbb96ac6103d3ead42e59830019435839583c9eb352b94ed558bd78e7ffad5286dc710df21ec1e7bd8f52aa6574c62457a4dd0f01f3736fa4a7d87a +"@eslint/core@npm:^0.9.0": + version: 0.9.0 + resolution: "@eslint/core@npm:0.9.0" + checksum: 10c0/6d8e8e0991cef12314c49425d8d2d9394f5fb1a36753ff82df7c03185a4646cb7c8736cf26638a4a714782cedf4b23cfc17667d282d3e5965b3920a0e7ce20d4 languageName: node linkType: hard -"@eslint/eslintrc@npm:^3.1.0": - version: 3.1.0 - resolution: "@eslint/eslintrc@npm:3.1.0" +"@eslint/eslintrc@npm:^3.2.0": + version: 3.2.0 + resolution: "@eslint/eslintrc@npm:3.2.0" dependencies: ajv: "npm:^6.12.4" debug: "npm:^4.3.2" @@ -101,14 +101,14 @@ __metadata: js-yaml: "npm:^4.1.0" minimatch: "npm:^3.1.2" strip-json-comments: "npm:^3.1.1" - checksum: 10c0/5b7332ed781edcfc98caa8dedbbb843abfb9bda2e86538529c843473f580e40c69eb894410eddc6702f487e9ee8f8cfa8df83213d43a8fdb549f23ce06699167 + checksum: 10c0/43867a07ff9884d895d9855edba41acf325ef7664a8df41d957135a81a477ff4df4196f5f74dc3382627e5cc8b7ad6b815c2cea1b58f04a75aced7c43414ab8b languageName: node linkType: hard -"@eslint/js@npm:9.13.0": - version: 9.13.0 - resolution: "@eslint/js@npm:9.13.0" - checksum: 10c0/672257bffe17777b8a98bd80438702904cc7a0b98b9c2e426a8a10929198b3553edf8a3fc20feed4133c02e7c8f7331a0ef1b23e5dab8e4469f7f1791beff1e0 +"@eslint/js@npm:9.15.0": + version: 9.15.0 + resolution: "@eslint/js@npm:9.15.0" + checksum: 10c0/56552966ab1aa95332f70d0e006db5746b511c5f8b5e0c6a9b2d6764ff6d964e0b2622731877cbc4e3f0e74c5b39191290d5f48147be19175292575130d499ab languageName: node linkType: hard @@ -119,29 +119,29 @@ __metadata: languageName: node linkType: hard -"@eslint/plugin-kit@npm:^0.2.0": - version: 0.2.1 - resolution: "@eslint/plugin-kit@npm:0.2.1" +"@eslint/plugin-kit@npm:^0.2.3": + version: 0.2.3 + resolution: "@eslint/plugin-kit@npm:0.2.3" dependencies: levn: "npm:^0.4.1" - checksum: 10c0/34b1ecb35df97b0adeb6a43366fc1b8aa1a54d23fc9753019277e80a7295724fddb547a795fd59c9eb56d690bbf0d76d7f2286cb0f5db367a86a763d5acbde5f + checksum: 10c0/89a8035976bb1780e3fa8ffe682df013bd25f7d102d991cecd3b7c297f4ce8c1a1b6805e76dd16465b5353455b670b545eff2b4ec3133e0eab81a5f9e99bd90f languageName: node linkType: hard -"@humanfs/core@npm:^0.19.0": - version: 0.19.0 - resolution: "@humanfs/core@npm:0.19.0" - checksum: 10c0/f87952d5caba6ae427a620eff783c5d0b6cef0cfc256dec359cdaa636c5f161edb8d8dad576742b3de7f0b2f222b34aad6870248e4b7d2177f013426cbcda232 +"@humanfs/core@npm:^0.19.1": + version: 0.19.1 + resolution: "@humanfs/core@npm:0.19.1" + checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 languageName: node linkType: hard -"@humanfs/node@npm:^0.16.5": - version: 0.16.5 - resolution: "@humanfs/node@npm:0.16.5" +"@humanfs/node@npm:^0.16.6": + version: 0.16.6 + resolution: "@humanfs/node@npm:0.16.6" dependencies: - "@humanfs/core": "npm:^0.19.0" + "@humanfs/core": "npm:^0.19.1" "@humanwhocodes/retry": "npm:^0.3.0" - checksum: 10c0/41c365ab09e7c9eaeed373d09243195aef616d6745608a36fc3e44506148c28843872f85e69e2bf5f1e992e194286155a1c1cecfcece6a2f43875e37cd243935 + checksum: 10c0/8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1 languageName: node linkType: hard @@ -152,13 +152,20 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/retry@npm:^0.3.0, @humanwhocodes/retry@npm:^0.3.1": +"@humanwhocodes/retry@npm:^0.3.0": version: 0.3.1 resolution: "@humanwhocodes/retry@npm:0.3.1" checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b languageName: node linkType: hard +"@humanwhocodes/retry@npm:^0.4.1": + version: 0.4.1 + resolution: "@humanwhocodes/retry@npm:0.4.1" + checksum: 10c0/be7bb6841c4c01d0b767d9bb1ec1c9359ee61421ce8ba66c249d035c5acdfd080f32d55a5c9e859cdd7868788b8935774f65b2caf24ec0b7bd7bf333791f063b + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -197,7 +204,7 @@ __metadata: languageName: node linkType: hard -"@noble/curves@npm:1.6.0, @noble/curves@npm:^1.4.0, @noble/curves@npm:~1.6.0": +"@noble/curves@npm:1.6.0, @noble/curves@npm:^1.4.0, @noble/curves@npm:^1.6.0, @noble/curves@npm:~1.6.0": version: 1.6.0 resolution: "@noble/curves@npm:1.6.0" dependencies: @@ -206,7 +213,7 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:~1.5.0": +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:~1.5.0": version: 1.5.0 resolution: "@noble/hashes@npm:1.5.0" checksum: 10c0/1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9 @@ -276,7 +283,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.5.0": +"@scure/bip32@npm:1.5.0, @scure/bip32@npm:^1.5.0": version: 1.5.0 resolution: "@scure/bip32@npm:1.5.0" dependencies: @@ -287,7 +294,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.4.0": +"@scure/bip39@npm:1.4.0, @scure/bip39@npm:^1.4.0": version: 1.4.0 resolution: "@scure/bip39@npm:1.4.0" dependencies: @@ -361,7 +368,7 @@ __metadata: languageName: node linkType: hard -"@types/chai@npm:*": +"@types/chai@npm:*, @types/chai@npm:^5.0.0": version: 5.0.1 resolution: "@types/chai@npm:5.0.1" dependencies: @@ -370,13 +377,6 @@ __metadata: languageName: node linkType: hard -"@types/chai@npm:^5.0.0": - version: 5.0.0 - resolution: "@types/chai@npm:5.0.0" - checksum: 10c0/fcce55f2bbb8485fc860a1dcbac17c1a685b598cfc91a55d37b65b1642b921cf736caa8cce9dcc530830d900f78ab95cf43db4e118db34a5176f252cacd9e1e8 - languageName: node - linkType: hard - "@types/deep-eql@npm:*": version: 4.0.2 resolution: "@types/deep-eql@npm:4.0.2" @@ -406,11 +406,11 @@ __metadata: linkType: hard "@types/node@npm:^22.7.8": - version: 22.7.8 - resolution: "@types/node@npm:22.7.8" + version: 22.9.1 + resolution: "@types/node@npm:22.9.1" dependencies: - undici-types: "npm:~6.19.2" - checksum: 10c0/3d3b3a2ec5a57ca4fd37b34dce415620993ca5f87cea2c728ffe73aa31446dbfe19c53171c478447bd7d78011ef4845a46ab2f0dc38e699cc75b3d100a60c690 + undici-types: "npm:~6.19.8" + checksum: 10c0/ea489ae603aa8874e4e88980aab6f2dad09c755da779c88dd142983bfe9609803c89415ca7781f723072934066f63daf2b3339ef084a8ad1a8079cf3958be243 languageName: node linkType: hard @@ -440,15 +440,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.11.0" +"@typescript-eslint/eslint-plugin@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.15.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.11.0" - "@typescript-eslint/type-utils": "npm:8.11.0" - "@typescript-eslint/utils": "npm:8.11.0" - "@typescript-eslint/visitor-keys": "npm:8.11.0" + "@typescript-eslint/scope-manager": "npm:8.15.0" + "@typescript-eslint/type-utils": "npm:8.15.0" + "@typescript-eslint/utils": "npm:8.15.0" + "@typescript-eslint/visitor-keys": "npm:8.15.0" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -459,66 +459,68 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/be509f7bb0c0c596801059b06995a81a1c326cc6ac31d96a32f7b6b7d7b495f9bad4dc442aa6e923d22515e62c668d3c14695c68bd6e0be1d4bf72158b7fd2d6 + checksum: 10c0/90ef10cc7d37a81abec4f4a3ffdfc3a0da8e99d949e03c75437e96e8ab2e896e34b85ab64718690180a7712581031b8611c5d8e7666d6ed4d60b9ace834d58e3 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/parser@npm:8.11.0" +"@typescript-eslint/parser@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/parser@npm:8.15.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.11.0" - "@typescript-eslint/types": "npm:8.11.0" - "@typescript-eslint/typescript-estree": "npm:8.11.0" - "@typescript-eslint/visitor-keys": "npm:8.11.0" + "@typescript-eslint/scope-manager": "npm:8.15.0" + "@typescript-eslint/types": "npm:8.15.0" + "@typescript-eslint/typescript-estree": "npm:8.15.0" + "@typescript-eslint/visitor-keys": "npm:8.15.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/e83f239fec60697083e5dcb1c8948340e783ea6e043fe9a65d557faef8882963b09d69aacd736eb8ab18a768769a7bbfc3de0f1251d4bba080613541acb0741c + checksum: 10c0/19c25aea0dc51faa758701a5319a89950fd30494d9d645db8ced84fb60714c5e7d4b51fc4ee8ccb07ddefec88c51ee307ee7e49addd6330ee8f3e7ee9ba329fc languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/scope-manager@npm:8.11.0" +"@typescript-eslint/scope-manager@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/scope-manager@npm:8.15.0" dependencies: - "@typescript-eslint/types": "npm:8.11.0" - "@typescript-eslint/visitor-keys": "npm:8.11.0" - checksum: 10c0/0910da62d8ae261711dd9f89d5c7d8e96ff13c50054436256e5a661309229cb49e3b8189c9468d36b6c4d3f7cddd121519ea78f9b18c9b869a808834b079b2ea + "@typescript-eslint/types": "npm:8.15.0" + "@typescript-eslint/visitor-keys": "npm:8.15.0" + checksum: 10c0/c27dfdcea4100cc2d6fa967f857067cbc93155b55e648f9f10887a1b9372bb76cf864f7c804f3fa48d7868d9461cdef10bcea3dab7637d5337e8aa8042dc08b9 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/type-utils@npm:8.11.0" +"@typescript-eslint/type-utils@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/type-utils@npm:8.15.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.11.0" - "@typescript-eslint/utils": "npm:8.11.0" + "@typescript-eslint/typescript-estree": "npm:8.15.0" + "@typescript-eslint/utils": "npm:8.15.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^1.3.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/b69e31c1599ceeb20c29052a4ddb33a554174a3a4c55ee37d90c9b8250af6ef978a0b9ddbeefef4e83d62c4caea1bfa2d8088527f397bde69fb4ab9b360d794a + checksum: 10c0/20f09c79c83b38a962cf7eff10d47a2c01bcc0bab7bf6d762594221cd89023ef8c7aec26751c47b524f53f5c8d38bba55a282529b3df82d5f5ab4350496316f9 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/types@npm:8.11.0" - checksum: 10c0/5ccdd3eeee077a6fc8e7f4bc0e0cbc9327b1205a845253ec5c0c6c49ff915e853161df00c24a0ffb4b8ec745d3f153dd0e066400a021c844c026e31121f46699 +"@typescript-eslint/types@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/types@npm:8.15.0" + checksum: 10c0/84abc6fd954aff13822a76ac49efdcb90a55c0025c20eee5d8cebcfb68faff33b79bbc711ea524e0209cecd90c5ee3a5f92babc7083c081d3a383a0710264a41 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.11.0" +"@typescript-eslint/typescript-estree@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.15.0" dependencies: - "@typescript-eslint/types": "npm:8.11.0" - "@typescript-eslint/visitor-keys": "npm:8.11.0" + "@typescript-eslint/types": "npm:8.15.0" + "@typescript-eslint/visitor-keys": "npm:8.15.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -528,31 +530,34 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/b629ad3cd32b005d5c1d67c36958a418f8672efebea869399834f4f201ebf90b942165eebb5c9d9799dcabdc2cc26e5fabb00629f76b158847f42e1a491a75a6 + checksum: 10c0/3af5c129532db3575349571bbf64d32aeccc4f4df924ac447f5d8f6af8b387148df51965eb2c9b99991951d3dadef4f2509d7ce69bf34a2885d013c040762412 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/utils@npm:8.11.0" +"@typescript-eslint/utils@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/utils@npm:8.15.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.11.0" - "@typescript-eslint/types": "npm:8.11.0" - "@typescript-eslint/typescript-estree": "npm:8.11.0" + "@typescript-eslint/scope-manager": "npm:8.15.0" + "@typescript-eslint/types": "npm:8.15.0" + "@typescript-eslint/typescript-estree": "npm:8.15.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 - checksum: 10c0/bb5bcc8d928a55b22298e76f834ea6a9fe125a9ffeb6ac23bee0258b3ed32f41e281888a3d0be226a05e1011bb3b70e42a71a40366acdefea6779131c46bc522 + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/65743f51845a1f6fd2d21f66ca56182ba33e966716bdca73d30b7a67c294e47889c322de7d7b90ab0818296cd33c628e5eeeb03cec7ef2f76c47de7a453eeda2 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.11.0": - version: 8.11.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.11.0" +"@typescript-eslint/visitor-keys@npm:8.15.0": + version: 8.15.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.15.0" dependencies: - "@typescript-eslint/types": "npm:8.11.0" - eslint-visitor-keys: "npm:^3.4.3" - checksum: 10c0/7a5a49609fdc47e114fe59eee56393c90b122ec8e9520f90b0c5e189635ae1ccfa8e00108f641342c2c8f4637fe9d40c77927cf7c8248a3a660812cb4b7d0c08 + "@typescript-eslint/types": "npm:8.15.0" + eslint-visitor-keys: "npm:^4.2.0" + checksum: 10c0/02a954c3752c4328482a884eb1da06ca8fb72ae78ef28f1d854b18f3779406ed47263af22321cf3f65a637ec7584e5f483e34a263b5c8cec60ec85aebc263574 languageName: node linkType: hard @@ -563,7 +568,7 @@ __metadata: languageName: node linkType: hard -"abitype@npm:1.0.6": +"abitype@npm:1.0.6, abitype@npm:^1.0.6": version: 1.0.6 resolution: "abitype@npm:1.0.6" peerDependencies: @@ -596,12 +601,12 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.12.0, acorn@npm:^8.4.1": - version: 8.13.0 - resolution: "acorn@npm:8.13.0" +"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1": + version: 8.14.0 + resolution: "acorn@npm:8.14.0" bin: acorn: bin/acorn - checksum: 10c0/f35dd53d68177c90699f4c37d0bb205b8abe036d955d0eb011ddb7f14a81e6fd0f18893731c457c1b5bd96754683f4c3d80d9a5585ddecaa53cdf84e0b3d68f7 + checksum: 10c0/6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7 languageName: node linkType: hard @@ -976,26 +981,26 @@ __metadata: linkType: hard "cross-spawn@npm:^6.0.5": - version: 6.0.5 - resolution: "cross-spawn@npm:6.0.5" + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" dependencies: nice-try: "npm:^1.0.4" path-key: "npm:^2.0.1" semver: "npm:^5.5.0" shebang-command: "npm:^1.2.0" which: "npm:^1.2.9" - checksum: 10c0/e05544722e9d7189b4292c66e42b7abeb21db0d07c91b785f4ae5fefceb1f89e626da2703744657b287e86dcd4af57b54567cef75159957ff7a8a761d9055012 + checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.5": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" dependencies: path-key: "npm:^3.1.0" shebang-command: "npm:^2.0.0" which: "npm:^2.0.1" - checksum: 10c0/5738c312387081c98d69c98e105b6327b069197f864a60593245d64c8089c8a0a744e16349281210d56835bb9274130d825a78b2ad6853ca13cfbeffc0c31750 + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 languageName: node linkType: hard @@ -1176,8 +1181,8 @@ __metadata: linkType: hard "es-abstract@npm:^1.22.1, es-abstract@npm:^1.22.3, es-abstract@npm:^1.23.0, es-abstract@npm:^1.23.2": - version: 1.23.3 - resolution: "es-abstract@npm:1.23.3" + version: 1.23.5 + resolution: "es-abstract@npm:1.23.5" dependencies: array-buffer-byte-length: "npm:^1.0.1" arraybuffer.prototype.slice: "npm:^1.0.3" @@ -1194,7 +1199,7 @@ __metadata: function.prototype.name: "npm:^1.1.6" get-intrinsic: "npm:^1.2.4" get-symbol-description: "npm:^1.0.2" - globalthis: "npm:^1.0.3" + globalthis: "npm:^1.0.4" gopd: "npm:^1.0.1" has-property-descriptors: "npm:^1.0.2" has-proto: "npm:^1.0.3" @@ -1210,10 +1215,10 @@ __metadata: is-string: "npm:^1.0.7" is-typed-array: "npm:^1.1.13" is-weakref: "npm:^1.0.2" - object-inspect: "npm:^1.13.1" + object-inspect: "npm:^1.13.3" object-keys: "npm:^1.1.1" object.assign: "npm:^4.1.5" - regexp.prototype.flags: "npm:^1.5.2" + regexp.prototype.flags: "npm:^1.5.3" safe-array-concat: "npm:^1.1.2" safe-regex-test: "npm:^1.0.3" string.prototype.trim: "npm:^1.2.9" @@ -1225,7 +1230,7 @@ __metadata: typed-array-length: "npm:^1.0.6" unbox-primitive: "npm:^1.0.2" which-typed-array: "npm:^1.1.15" - checksum: 10c0/d27e9afafb225c6924bee9971a7f25f20c314f2d6cb93a63cada4ac11dcf42040896a6c22e5fb8f2a10767055ed4ddf400be3b1eb12297d281726de470b75666 + checksum: 10c0/1f6f91da9cf7ee2c81652d57d3046621d598654d1d1b05c1578bafe5c4c2d3d69513901679bdca2de589f620666ec21de337e4935cec108a4ed0871d5ef04a5d languageName: node linkType: hard @@ -1297,54 +1302,54 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^8.1.0": - version: 8.1.0 - resolution: "eslint-scope@npm:8.1.0" +"eslint-scope@npm:^8.2.0": + version: 8.2.0 + resolution: "eslint-scope@npm:8.2.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10c0/ae1df7accae9ea90465c2ded70f7064d6d1f2962ef4cc87398855c4f0b3a5ab01063e0258d954bb94b184f6759febe04c3118195cab5c51978a7229948ba2875 + checksum: 10c0/8d2d58e2136d548ac7e0099b1a90d9fab56f990d86eb518de1247a7066d38c908be2f3df477a79cf60d70b30ba18735d6c6e70e9914dca2ee515a729975d70d6 languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 languageName: node linkType: hard -"eslint-visitor-keys@npm:^4.1.0": - version: 4.1.0 - resolution: "eslint-visitor-keys@npm:4.1.0" - checksum: 10c0/5483ef114c93a136aa234140d7aa3bd259488dae866d35cb0d0b52e6a158f614760a57256ac8d549acc590a87042cb40f6951815caa821e55dc4fd6ef4c722eb +"eslint-visitor-keys@npm:^4.2.0": + version: 4.2.0 + resolution: "eslint-visitor-keys@npm:4.2.0" + checksum: 10c0/2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269 languageName: node linkType: hard "eslint@npm:^9.12.0": - version: 9.13.0 - resolution: "eslint@npm:9.13.0" + version: 9.15.0 + resolution: "eslint@npm:9.15.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.11.0" - "@eslint/config-array": "npm:^0.18.0" - "@eslint/core": "npm:^0.7.0" - "@eslint/eslintrc": "npm:^3.1.0" - "@eslint/js": "npm:9.13.0" - "@eslint/plugin-kit": "npm:^0.2.0" - "@humanfs/node": "npm:^0.16.5" + "@eslint-community/regexpp": "npm:^4.12.1" + "@eslint/config-array": "npm:^0.19.0" + "@eslint/core": "npm:^0.9.0" + "@eslint/eslintrc": "npm:^3.2.0" + "@eslint/js": "npm:9.15.0" + "@eslint/plugin-kit": "npm:^0.2.3" + "@humanfs/node": "npm:^0.16.6" "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.3.1" + "@humanwhocodes/retry": "npm:^0.4.1" "@types/estree": "npm:^1.0.6" "@types/json-schema": "npm:^7.0.15" ajv: "npm:^6.12.4" chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.2" + cross-spawn: "npm:^7.0.5" debug: "npm:^4.3.2" escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.1.0" - eslint-visitor-keys: "npm:^4.1.0" - espree: "npm:^10.2.0" + eslint-scope: "npm:^8.2.0" + eslint-visitor-keys: "npm:^4.2.0" + espree: "npm:^10.3.0" esquery: "npm:^1.5.0" esutils: "npm:^2.0.2" fast-deep-equal: "npm:^3.1.3" @@ -1359,7 +1364,6 @@ __metadata: minimatch: "npm:^3.1.2" natural-compare: "npm:^1.4.0" optionator: "npm:^0.9.3" - text-table: "npm:^0.2.0" peerDependencies: jiti: "*" peerDependenciesMeta: @@ -1367,18 +1371,18 @@ __metadata: optional: true bin: eslint: bin/eslint.js - checksum: 10c0/d3577444152182a9d8ea8c6a6acb073d3a2773ad73a6b646f432746583ec4bfcd6a44fcc2e37d05d276984e583c46c2d289b3b981ca8f8b4052756a152341d19 + checksum: 10c0/d0d7606f36bfcccb1c3703d0a24df32067b207a616f17efe5fb1765a91d13f085afffc4fc97ecde4ab9c9f4edd64d9b4ce750e13ff7937a25074b24bee15b20f languageName: node linkType: hard -"espree@npm:^10.0.1, espree@npm:^10.2.0": - version: 10.2.0 - resolution: "espree@npm:10.2.0" +"espree@npm:^10.0.1, espree@npm:^10.3.0": + version: 10.3.0 + resolution: "espree@npm:10.3.0" dependencies: - acorn: "npm:^8.12.0" + acorn: "npm:^8.14.0" acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.1.0" - checksum: 10c0/2b6bfb683e7e5ab2e9513949879140898d80a2d9867ea1db6ff5b0256df81722633b60a7523a7c614f05a39aeea159dd09ad2a0e90c0e218732fc016f9086215 + eslint-visitor-keys: "npm:^4.2.0" + checksum: 10c0/272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462 languageName: node linkType: hard @@ -1423,6 +1427,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 10c0/4ba5c00c506e6c786b4d6262cfbce90ddc14c10d4667e5c83ae993c9de88aa856033994dd2b35b83e8dc1170e224e66a319fa80adc4c32adcd2379bbc75da814 + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -1521,9 +1532,9 @@ __metadata: linkType: hard "flatted@npm:^3.2.9": - version: 3.3.1 - resolution: "flatted@npm:3.3.1" - checksum: 10c0/324166b125ee07d4ca9bcf3a5f98d915d5db4f39d711fba640a3178b959919aae1f7cfd8aabcfef5826ed8aa8a2aa14cc85b2d7d18ff638ddf4ae3df39573eaf + version: 3.3.2 + resolution: "flatted@npm:3.3.2" + checksum: 10c0/24cc735e74d593b6c767fe04f2ef369abe15b62f6906158079b9874bdb3ee5ae7110bb75042e70cd3f99d409d766f357caf78d5ecee9780206f5fdc5edbad334 languageName: node linkType: hard @@ -1702,13 +1713,13 @@ __metadata: linkType: hard "globals@npm:^15.11.0": - version: 15.11.0 - resolution: "globals@npm:15.11.0" - checksum: 10c0/861e39bb6bd9bd1b9f355c25c962e5eb4b3f0e1567cf60fa6c06e8c502b0ec8706b1cce055d69d84d0b7b8e028bec5418cf629a54e7047e116538d1c1c1a375c + version: 15.12.0 + resolution: "globals@npm:15.12.0" + checksum: 10c0/f34e0a1845b694f45188331742af9f488b07ba7440a06e9d2039fce0386fbbfc24afdbb9846ebdccd4092d03644e43081c49eb27b30f4b88e43af156e1c1dc34 languageName: node linkType: hard -"globalthis@npm:^1.0.3": +"globalthis@npm:^1.0.4": version: 1.0.4 resolution: "globalthis@npm:1.0.4" dependencies: @@ -2480,8 +2491,8 @@ __metadata: linkType: hard "mocha@npm:^10.7.3": - version: 10.7.3 - resolution: "mocha@npm:10.7.3" + version: 10.8.2 + resolution: "mocha@npm:10.8.2" dependencies: ansi-colors: "npm:^4.1.3" browser-stdout: "npm:^1.3.1" @@ -2506,7 +2517,7 @@ __metadata: bin: _mocha: bin/_mocha mocha: bin/mocha.js - checksum: 10c0/76a205905ec626262d903954daca31ba8e0dd4347092f627b98b8508dcdb5b30be62ec8f7a405fab3b2e691bdc099721c3291b330c3ee85b8ec40d3d179f8728 + checksum: 10c0/1f786290a32a1c234f66afe2bfcc68aa50fe9c7356506bd39cca267efb0b4714a63a0cb333815578d63785ba2fba058bf576c2512db73997c0cae0d659a88beb languageName: node linkType: hard @@ -2622,10 +2633,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.13.1": - version: 1.13.2 - resolution: "object-inspect@npm:1.13.2" - checksum: 10c0/b97835b4c91ec37b5fd71add84f21c3f1047d1d155d00c0fcd6699516c256d4fcc6ff17a1aced873197fe447f91a3964178fd2a67a1ee2120cdaf60e81a050b4 +"object-inspect@npm:^1.13.1, object-inspect@npm:^1.13.3": + version: 1.13.3 + resolution: "object-inspect@npm:1.13.3" + checksum: 10c0/cc3f15213406be89ffdc54b525e115156086796a515410a8d390215915db9f23c8eab485a06f1297402f440a33715fe8f71a528c1dcbad6e1a3bcaf5a46921d4 languageName: node linkType: hard @@ -2671,6 +2682,26 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.1.2": + version: 0.1.2 + resolution: "ox@npm:0.1.2" + dependencies: + "@adraffy/ens-normalize": "npm:^1.10.1" + "@noble/curves": "npm:^1.6.0" + "@noble/hashes": "npm:^1.5.0" + "@scure/bip32": "npm:^1.5.0" + "@scure/bip39": "npm:^1.4.0" + abitype: "npm:^1.0.6" + eventemitter3: "npm:5.0.1" + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/9d0615e9a95c316063587fe08dc268476e67429eea897598b2f69cb1509ac66739f888b0b9bc1cfd0b4bd2f1a3fd0af4d3e81d40ba0bf3abd53e36a6f5b21323 + languageName: node + linkType: hard + "p-limit@npm:^3.0.2": version: 3.1.0 resolution: "p-limit@npm:3.1.0" @@ -2891,7 +2922,7 @@ __metadata: languageName: node linkType: hard -"regexp.prototype.flags@npm:^1.5.2": +"regexp.prototype.flags@npm:^1.5.3": version: 1.5.3 resolution: "regexp.prototype.flags@npm:1.5.3" dependencies: @@ -3373,13 +3404,6 @@ __metadata: languageName: node linkType: hard -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c - languageName: node - linkType: hard - "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -3390,11 +3414,11 @@ __metadata: linkType: hard "ts-api-utils@npm:^1.3.0": - version: 1.3.0 - resolution: "ts-api-utils@npm:1.3.0" + version: 1.4.0 + resolution: "ts-api-utils@npm:1.4.0" peerDependencies: typescript: ">=4.2.0" - checksum: 10c0/f54a0ba9ed56ce66baea90a3fa087a484002e807f28a8ccb2d070c75e76bde64bd0f6dce98b3802834156306050871b67eec325cb4e918015a360a3f0868c77c + checksum: 10c0/1b2bfa50ea52771d564bb143bb69010d25cda03ed573095fbac9b86f717012426443af6647e00e3db70fca60360482a30c1be7cf73c3521c321f6bf5e3594ea0 languageName: node linkType: hard @@ -3437,9 +3461,9 @@ __metadata: linkType: hard "tslib@npm:^2.1.0": - version: 2.8.0 - resolution: "tslib@npm:2.8.0" - checksum: 10c0/31e4d14dc1355e9b89e4d3c893a18abb7f90b6886b089c2da91224d0a7752c79f3ddc41bc1aa0a588ac895bd97bb99c5bc2bfdb2f86de849f31caeb3ba79bbe5 + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 languageName: node linkType: hard @@ -3519,16 +3543,18 @@ __metadata: linkType: hard "typescript-eslint@npm:^8.8.1": - version: 8.11.0 - resolution: "typescript-eslint@npm:8.11.0" + version: 8.15.0 + resolution: "typescript-eslint@npm:8.15.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.11.0" - "@typescript-eslint/parser": "npm:8.11.0" - "@typescript-eslint/utils": "npm:8.11.0" + "@typescript-eslint/eslint-plugin": "npm:8.15.0" + "@typescript-eslint/parser": "npm:8.15.0" + "@typescript-eslint/utils": "npm:8.15.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/8f9b5916c9f47b0cbb26f142d1a266a6aaf33998ec87621252dffb56d8fe0ad01a944f8d8d837e4e6058153a1deee3557527d14fa7bf7ef80a927334529db6bd + checksum: 10c0/589aebf0d0b9b79db1cd0b7c2ea08c6b5727c1db095d39077d070c332066c7d549a0eb2ef60b0d41619720c317c1955236c5c8ee6320bc7c6ae475add7223b55 languageName: node linkType: hard @@ -3564,7 +3590,7 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.19.2": +"undici-types@npm:~6.19.8": version: 6.19.8 resolution: "undici-types@npm:6.19.8" checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 @@ -3623,16 +3649,16 @@ __metadata: linkType: hard "viem@npm:^2.21.25": - version: 2.21.32 - resolution: "viem@npm:2.21.32" + version: 2.21.48 + resolution: "viem@npm:2.21.48" dependencies: - "@adraffy/ens-normalize": "npm:1.11.0" "@noble/curves": "npm:1.6.0" "@noble/hashes": "npm:1.5.0" "@scure/bip32": "npm:1.5.0" "@scure/bip39": "npm:1.4.0" abitype: "npm:1.0.6" isows: "npm:1.0.6" + ox: "npm:0.1.2" webauthn-p256: "npm:0.0.10" ws: "npm:8.18.0" peerDependencies: @@ -3640,7 +3666,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10c0/1a38f1fbb9e71eafd5d923d737f6869a700b32d12e5e735f9b7cea4692971ee869bb8f7e1a265ba80b81ba2e0e129137d868d33b2faba260d3a06915d75306a5 + checksum: 10c0/e9b2799535263a859bddda25d962b13d2c76aec191e1849dd0f268c32a43eb65932a05cc5be270c92e19d79aafda73884690c0b0fbdb9311266a01ea3f659082 languageName: node linkType: hard