diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a6744..c49742d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # v0.4.15 - feat: populate relational entities on graphql endpoint +- feat: add overload to `manifest.addChain` and `chain.addContract` functions to pass in a callback function that takes in a DataSourceBuilder and ContractBuilder instance respectively +- feat: remove svn as a dependancy to run `arkiver init` command +- change: deprecate `manifest.chain` for `manifest.addChain` +- change: deprecate `manifest.contract` for `manifest.addContract` +- change: `transactionIndex` and `logIndex` field in event is now number type to align with viem's return types - fix: missing event handler types due to updates to `viem` package # v0.4.14 diff --git a/cli.ts b/cli.ts index 8bc60b1..416efe9 100644 --- a/cli.ts +++ b/cli.ts @@ -97,11 +97,10 @@ command // init command .command('init', 'Initialize a new arkive project') - .option('--overwrite', 'Overwrite existing files') - .action(async (opts, ...args) => { + .action(async () => { util.logHeader(version) await checkVersion(version) - await init.action(opts, ...args) + await init.action() }) // upgrade diff --git a/cli/init/mod.ts b/cli/init/mod.ts index 3c2e7c0..0d4afff 100644 --- a/cli/init/mod.ts +++ b/cli/init/mod.ts @@ -1,20 +1,22 @@ -import { $, Input, join, prompt, Select, Toggle, wait } from '../deps.ts' +import { $, Input, join, prompt, Select, Toggle } from '../deps.ts' -export const action = async ( - options: { overwrite?: boolean }, -) => { - let spinner = wait('Fetching templates...').start() +export const action = async () => { + let pb = $.progress('Fetching templates...') - const templatesRes = await fetch( - 'https://api.github.com/repos/RoboVault/robo-arkiver/contents/examples', - ) + let templatesRes: Response - if (!templatesRes.ok) { - console.log('Error fetching templates') + await pb.with(async () => { + templatesRes = await fetch( + 'https://api.github.com/repos/RoboVault/robo-arkiver/contents/examples', + ) + }) + + if (!templatesRes!.ok) { + console.log('Error fetching templates: ', templatesRes!.statusText) return } - const templates = await templatesRes.json() as { + const templates = await templatesRes!.json() as { name: string type: string }[] @@ -28,8 +30,6 @@ export const action = async ( name: t.name, })) - spinner.stop() - const defaultPath = './cool-new-arkive' const arkive = await prompt([ @@ -62,31 +62,64 @@ export const action = async ( const newDir = join(Deno.cwd(), arkive.dir ?? defaultPath) const template = arkive.template ?? templateNames[0].value - spinner = wait('Initializing arkive...').start() + pb = $.progress('Initializing arkive...') - const initRes = await $`svn export ${ - options.overwrite ? `--force ` : '' - }https://github.com/RoboVault/robo-arkiver/trunk/examples/${template} ${newDir}` - .captureCombined(true) + try { + await $`git init ${newDir} && cd ${newDir} && git config core.sparseCheckout true` + .quiet('both') - if (initRes.code !== 0) { - spinner.fail(`Error initializing arkive: ${initRes.stderr}`) - return - } - - if (arkive.vscode) { - const dir = arkive.dir ?? defaultPath - await Deno.mkdir(join(Deno.cwd(), dir, '.vscode')) - - const vscode = `{ - "deno.enable": true, - "deno.unstable": true -}` - await Deno.writeTextFile( - join(Deno.cwd(), dir, '.vscode', 'settings.json'), - vscode, + await Deno.writeFile( + join(newDir, '.git', 'info', 'sparse-checkout'), + new TextEncoder().encode(`examples/${template}`), ) + + await $`git remote add origin https://github.com/RoboVault/robo-arkiver && git pull origin main && rm -rf .git` + .cwd(newDir) + .quiet('stdout') + + // traverse the template directory and move all files to the root + for await ( + const entry of Deno.readDir(join(newDir, `examples/${template}`)) + ) { + const source = join(newDir, `examples/${template}`, entry.name) + const destination = join(newDir, entry.name) + await Deno.rename(source, destination) + } + + await Deno.remove(join(newDir, 'examples'), { recursive: true }) + + if (arkive.vscode) { + const dir = arkive.dir ?? defaultPath + await Deno.mkdir(join(Deno.cwd(), dir, '.vscode')) + + const vscode = `{ + "deno.enable": true, + "deno.unstable": true + }` + await Deno.writeTextFile( + join(Deno.cwd(), dir, '.vscode', 'settings.json'), + vscode, + ) + + const gitignore = `/.vscode + /.vscode/* + /.vscode/**/* + ` + await Deno.writeTextFile( + join(Deno.cwd(), dir, '.gitignore'), + gitignore, + ) + } + + await $`git init && git add . && git commit -m "Initial commit"` + .cwd(newDir) + .quiet('stdout') + } catch (e) { + $.logError(`Error initializing arkive: ${e}`) + return } - spinner.succeed('Initialized arkive') + // spinner.succeed('Initialized arkive') + pb.finish() + $.logStep('Initialized arkive') } diff --git a/examples/block-handler-vaults/deps.ts b/examples/block-handler-vaults/deps.ts index 6c47e4b..3d47f35 100644 --- a/examples/block-handler-vaults/deps.ts +++ b/examples/block-handler-vaults/deps.ts @@ -3,4 +3,4 @@ export { createEntity, type EventHandlerFor, Manifest, -} from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +} from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' diff --git a/examples/block-handler-vaults/handlers/vault.ts b/examples/block-handler-vaults/handlers/vault.ts index a32b818..113a7e7 100644 --- a/examples/block-handler-vaults/handlers/vault.ts +++ b/examples/block-handler-vaults/handlers/vault.ts @@ -1,5 +1,5 @@ import { formatUnits, getContract } from 'npm:viem' -import { type BlockHandler } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { type BlockHandler } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' import { VaultSnapshot } from '../entities/vault.ts' import { YearnV2Abi } from '../abis/YearnV2.ts' diff --git a/examples/block-handler-vaults/manifest.ts b/examples/block-handler-vaults/manifest.ts index cb2e300..ca7e575 100644 --- a/examples/block-handler-vaults/manifest.ts +++ b/examples/block-handler-vaults/manifest.ts @@ -1,4 +1,4 @@ -import { Manifest } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { Manifest } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' import { VaultSnapshot } from './entities/vault.ts' import { snapshotVault } from './handlers/vault.ts' @@ -6,7 +6,7 @@ const manifest = new Manifest('yearn-vaults') manifest .addEntity(VaultSnapshot) - .chain('mainnet') + .addChain('mainnet') .addBlockHandler({ blockInterval: 1000, startBlockHeight: 12790000n, diff --git a/examples/erc20-balance-history/entities.ts b/examples/erc20-balance-history/entities.ts index d186144..dd1c66c 100644 --- a/examples/erc20-balance-history/entities.ts +++ b/examples/erc20-balance-history/entities.ts @@ -1,4 +1,4 @@ -import { createEntity } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { createEntity } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' // @note: "Index: true" enhances graphql queries diff --git a/examples/erc20-balance-history/handlers.ts b/examples/erc20-balance-history/handlers.ts index 4a94e9d..30ecb85 100644 --- a/examples/erc20-balance-history/handlers.ts +++ b/examples/erc20-balance-history/handlers.ts @@ -1,5 +1,5 @@ import { formatUnits } from 'npm:viem' -import { type EventHandlerFor } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { type EventHandlerFor } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' import erc20 from './erc20.ts' import { Balance, BalanceHistory, Transfer } from './entities.ts' diff --git a/examples/erc20-balance-history/manifest.ts b/examples/erc20-balance-history/manifest.ts index 3f7b2da..cabef9c 100644 --- a/examples/erc20-balance-history/manifest.ts +++ b/examples/erc20-balance-history/manifest.ts @@ -1,4 +1,4 @@ -import { Manifest } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { Manifest } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' import erc20 from './erc20.ts' import { Entities } from './entities.ts' import { onTransfer } from './handlers.ts' @@ -7,8 +7,8 @@ const manifest = new Manifest('frax-balances') manifest .addEntities(Entities) - .chain('mainnet', { blockRange: 500n }) - .contract(erc20) + .addChain('mainnet', { blockRange: 500n }) + .addContract(erc20) .addSources({ '0x853d955aCEf822Db058eb8505911ED77F175b99e': 11465581n }) .addEventHandlers({ 'Transfer': onTransfer }) diff --git a/examples/erc20-events/entities.ts b/examples/erc20-events/entities.ts index 820d227..a020b5a 100644 --- a/examples/erc20-events/entities.ts +++ b/examples/erc20-events/entities.ts @@ -1,4 +1,4 @@ -import { createEntity } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { createEntity } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' // @note: "Index: true" enhances graphql queries export const Transfer = createEntity('Transfer', { diff --git a/examples/erc20-events/handlers.ts b/examples/erc20-events/handlers.ts index d65e95d..61e373f 100644 --- a/examples/erc20-events/handlers.ts +++ b/examples/erc20-events/handlers.ts @@ -1,5 +1,5 @@ import { formatUnits } from 'npm:viem' -import { type EventHandlerFor } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { type EventHandlerFor } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' import erc20 from './erc20.ts' import { Approval, Transfer } from './entities.ts' diff --git a/examples/erc20-events/manifest.ts b/examples/erc20-events/manifest.ts index 76cce45..552475d 100644 --- a/examples/erc20-events/manifest.ts +++ b/examples/erc20-events/manifest.ts @@ -1,4 +1,4 @@ -import { Manifest } from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +import { Manifest } from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' import erc20 from './erc20.ts' import { Approval, Transfer } from './entities.ts' import { onApproval, onTransfer } from './handlers.ts' @@ -7,8 +7,8 @@ const manifest = new Manifest('weth-events') manifest .addEntities([Transfer, Approval]) - .chain('mainnet', { blockRange: 500n }) - .contract('ERC20', erc20) + .addChain('mainnet', { blockRange: 500n }) + .addContract('ERC20', erc20) .addSources({ '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2': 4729568n }) .addEventHandlers({ 'Transfer': onTransfer }) .addEventHandlers({ 'Approval': onApproval }) diff --git a/examples/event-wildcard/deps.ts b/examples/event-wildcard/deps.ts index 6c47e4b..3d47f35 100644 --- a/examples/event-wildcard/deps.ts +++ b/examples/event-wildcard/deps.ts @@ -3,4 +3,4 @@ export { createEntity, type EventHandlerFor, Manifest, -} from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +} from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' diff --git a/examples/event-wildcard/manifest.ts b/examples/event-wildcard/manifest.ts index 8e01cf6..e7a18ff 100644 --- a/examples/event-wildcard/manifest.ts +++ b/examples/event-wildcard/manifest.ts @@ -5,8 +5,8 @@ import { transferHandler } from './handlers/transfer.ts' const manifest = new Manifest('agnostic-events') manifest - .chain('avalanche', { blockRange: 100n }) - .contract('ERC20', erc20) + .addChain('avalanche', { blockRange: 100n }) + .addContract('ERC20', erc20) .addSources({ '*': 27347402n }) .addEventHandlers({ 'Transfer': transferHandler }) diff --git a/examples/simple/deps.ts b/examples/simple/deps.ts index 6c47e4b..3d47f35 100644 --- a/examples/simple/deps.ts +++ b/examples/simple/deps.ts @@ -3,4 +3,4 @@ export { createEntity, type EventHandlerFor, Manifest, -} from 'https://deno.land/x/robo_arkiver@v0.4.14/mod.ts' +} from 'https://deno.land/x/robo_arkiver@v0.4.15/mod.ts' diff --git a/examples/simple/manifest.ts b/examples/simple/manifest.ts index 412580e..74d7a66 100644 --- a/examples/simple/manifest.ts +++ b/examples/simple/manifest.ts @@ -7,8 +7,8 @@ const manifest = new Manifest('simple') manifest .addEntity(Balance) - .chain('mainnet', { blockRange: 100n }) - .contract('ERC20', erc20) + .addChain('mainnet', { blockRange: 100n }) + .addContract('ERC20', erc20) .addSources({ '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2': 16987011n }) .addEventHandlers({ 'Transfer': transferHandler }) diff --git a/src/arkiver/data-source.ts b/src/arkiver/data-source.ts index 6ebe09d..5e4bcc3 100644 --- a/src/arkiver/data-source.ts +++ b/src/arkiver/data-source.ts @@ -132,7 +132,7 @@ export class DataSource extends EventTarget { this.contracts = params.contracts this.blockSources = params.blockSources this.client = createPublicClient({ - chain: getChainObjFromChainName(this.chain), + chain: getChainObjFromChainName(this.chain) as any, transport: http(this.rpcUrl), }) this.arkiveId = params.arkiveId @@ -587,7 +587,7 @@ export class DataSource extends EventTarget { eventName: event.eventName, client: this.client, store: this.store, - event: formatLog(log, event), + event: formatLog(log, event as any), contract: getContract({ abi: handler.abi, address: log.address, @@ -682,7 +682,7 @@ export class DataSource extends EventTarget { eventName: event.eventName, client: this.client, store: this.store, - event: formatLog(log, event), + event: formatLog(log, event as any), contract: getContract({ abi: eventHandler.abi, address: log.address, diff --git a/src/arkiver/manifest-builder.ts b/src/arkiver/manifest-builder.ts index e740334..a83ece1 100644 --- a/src/arkiver/manifest-builder.ts +++ b/src/arkiver/manifest-builder.ts @@ -44,17 +44,31 @@ export class Manifest { } } - /** - * @deprecated Use `chain` instead. - */ + public addChain( + chain: keyof typeof supportedChains, + builderFn: (builder: DataSourceBuilder) => void, + ): Manifest + public addChain( chain: keyof typeof supportedChains, options?: Partial, - ) { - return new DataSourceBuilder(this, chain, options) + ): DataSourceBuilder + + public addChain( + chain: keyof typeof supportedChains, + optionsOrBuilderFn?: + | ((builder: DataSourceBuilder) => void) + | Partial, + ): Manifest | DataSourceBuilder { + if (optionsOrBuilderFn && typeof optionsOrBuilderFn === 'function') { + optionsOrBuilderFn(new DataSourceBuilder(this, chain)) + return this + } + + return new DataSourceBuilder(this, chain, optionsOrBuilderFn) } - public chain( + private chain( chain: keyof typeof supportedChains, options?: Partial, ) { @@ -107,38 +121,92 @@ export class DataSourceBuilder { this.builder.manifest.dataSources[chain] = this.dataSource = dataSource } - /** - * @deprecated Use `contract` instead. - */ - public addContract( - abi: TAbi, + #addContract( + nameOrAbi: string | TAbi, + abi?: TAbi, ) { - return this.contract(abi) + if (this.dataSource.contracts == undefined) { + this.dataSource.contracts = [] + } + if (typeof nameOrAbi === 'string') { + if (abi === undefined) { + throw new Error('ABI is required when passing a name.') + } + return new ContractBuilder(this, abi, nameOrAbi) + } + return new ContractBuilder(this, nameOrAbi) } - public contract( + private contract( abi: TAbi, ): ContractBuilder - public contract( + private contract( name: string, abi: TAbi, ): ContractBuilder - public contract( + private contract( nameOrAbi: string | TAbi, abi?: TAbi, ) { - if (this.dataSource.contracts == undefined) { - this.dataSource.contracts = [] + return this.#addContract(nameOrAbi, abi) + } + + public addContract( + abi: TAbi, + ): ContractBuilder + + public addContract( + name: string, + abi: TAbi, + ): ContractBuilder + + public addContract< + const TAbi extends Abi, + TSources extends Record, + >( + params: AddContractParams, + ): DataSourceBuilder + + public addContract( + name: string, + abi: TAbi, + contractBuilderFn: (builder: ContractBuilder) => void, + ): DataSourceBuilder + + public addContract< + const TAbi extends Abi, + TSources extends Record = Record< + string | number | symbol, + never + >, + >( + nameOrAbiOrParams: string | TAbi | AddContractParams, + abi?: TAbi, + contractBuilderFn?: (builder: ContractBuilder) => void, + ): ContractBuilder | DataSourceBuilder { + if (contractBuilderFn && typeof nameOrAbiOrParams === 'string') { + contractBuilderFn(this.#addContract(nameOrAbiOrParams, abi)) + return this } - if (typeof nameOrAbi === 'string') { - if (abi === undefined) { - throw new Error('ABI is required when passing a name.') + if (typeof nameOrAbiOrParams === 'object' && 'abi' in nameOrAbiOrParams) { + const { abi, name, sources, eventHandlers } = nameOrAbiOrParams + let contractBuilder + if (name) { + contractBuilder = this.#addContract(name, abi) + } else { + contractBuilder = this.#addContract(abi) } - return new ContractBuilder(this, abi, nameOrAbi) + if (sources) { + contractBuilder.addSources(sources) + } + if (eventHandlers) { + contractBuilder.addEventHandlers(eventHandlers) + } + return this } - return new ContractBuilder(this, nameOrAbi) + return this.#addContract(nameOrAbiOrParams, abi) } public addBlockHandler( @@ -166,7 +234,7 @@ export class DataSourceBuilder { public use(libs: ArkiveLib[]) { libs.forEach((lib) => { const chain = this.builder.addEntities(lib.getEntities()) - .chain(this.chain, { + .addChain(this.chain, { blockRange: this.options.blockRange ? this.options.blockRange : 3000n, }) if (Object.keys(lib.getBlockHandler()).length > 0) { @@ -175,7 +243,7 @@ export class DataSourceBuilder { const sources = lib.getDataSources() for (const info of sources) { const { contract, handlers, abi } = info - chain.contract(abi) + chain.addContract(abi) .addSources(contract as any) .addEventHandlers(handlers) } @@ -211,10 +279,7 @@ export class ContractBuilder< } } - /** - * @deprecated Use `addSources` instead. - */ - public addSource( + private addSource( address: HexString | '*', startBlockHeight: bigint, ) { @@ -247,10 +312,7 @@ export class ContractBuilder< return this } - /** - * @deprecated Use `addEventHandlers` instead. - */ - public addEventHandler< + private addEventHandler< TEventName extends ExtractAbiEventNames, TEventHandler extends EventHandler< ExtractAbiEvent, @@ -308,3 +370,21 @@ const hashAbi = (abi: Abi) => { ).join('') return hexString } + +type AddContractParams< + TAbi extends Abi, + TSources extends Record, +> = { + abi: TAbi + name?: string + sources?: ValidateSourcesObject + eventHandlers?: Partial< + { + [eventName in ExtractAbiEventNames]: EventHandler< + ExtractAbiEvent, + eventName, + TAbi + > + } + > +} diff --git a/src/utils.ts b/src/utils.ts index a3ce70e..b689d71 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -33,18 +33,18 @@ export const bigIntMin = (...args: bigint[]) => export function formatLog( log: SafeRpcLog, - { args, eventName }: { args?: unknown; eventName?: string } = {}, + { args, eventName }: { args: unknown[]; eventName: string }, ) { return { ...log, blockHash: log.blockHash, blockNumber: BigInt(log.blockNumber), - logIndex: BigInt(log.logIndex), + logIndex: parseInt(log.logIndex, 16), transactionHash: log.transactionHash, - transactionIndex: BigInt(log.transactionIndex), - ...(eventName ? { args, eventName } : {}), + transactionIndex: parseInt(log.transactionIndex, 16), + ...({ args, eventName }), // deno-lint-ignore no-explicit-any - } as SafeLog + } satisfies SafeLog } export const defaultArkiveData: Arkive = {