Data Store
Loop decoder relays on two data stores for caching the ABI and contract metadata. The user of the library is free to choose which data layer to use depending on their environment and requirements.
If your application is designed to decode a fixed subset of contracts, you can provide a hardcoded map of ABIs and contract metadata. For more flexible applications, you can use any persistent database. When running in a browser, you can implement a REST API to fetch the ABIs and contract metadata from a server.
A store also requires a set of strategies that we cover separately in the Data Loaders section.
AbiStore
ABI Store Interface requires 2 methods set and get to store and retrieve the ABI of a contract. Optionally you can provide a batch get method getMany for further optimization. Because our API supports ABI fragments, the get method will receive both the contract address and the fragment signature.
interface AbiParams {  chainID: number  address: string  event?: string | undefined // event signature  signature?: string | undefined // function signature}The set method will receive a key of the type AbiParams and and ContractAbiResult. You can choose to store the data in the best format that fits your database.
interface FunctionFragmentABI {  type: 'func'  abi: string  address: string  chainID: number  signature: string}
interface EventFragmentABI {  type: 'event'  abi: string  address: string  chainID: number  event: string}
interface AddressABI {  type: 'address'  abi: string  address: string  chainID: number}
export type ContractABI = FunctionFragmentABI | EventFragmentABI | AddressABI
export interface ContractAbiSuccess {  status: 'success'  result: ContractABI}
export interface ContractAbiNotFound {  status: 'not-found'  result: null}
export interface ContractAbiEmpty {  status: 'empty'  result: null}
export type ContractAbiResult = ContractAbiSuccess | ContractAbiNotFound | ContractAbiEmptyThe full interface looks as follows:
export interface AbiStore {  readonly strategies: Record<ChainOrDefault, readonly RequestResolver.RequestResolver<GetContractABIStrategy>[]>  readonly set: (key: AbiParams, value: ContractAbiResult) => Effect.Effect<void, never>  readonly get: (arg: AbiParams) => Effect.Effect<ContractAbiResult, never>  readonly getMany?: (arg: Array<AbiParams>) => Effect.Effect<Array<ContractAbiResult>, never>}ContractMetadataStore
Similar to the ABI Store, the Contract Metadata Store Interface requires 2 methods set, get, and optionally getMany to store and retrieve the contract metadata.
The get method will receive the contract address and the chain ID as input.
interface ContractMetaParams {  address: string  chainID: number}And, the set method will be called with 2 pramaters, the key in the same format as the get method, and the metadata in a format of ContractMetaResult.
interface ContractMetaSuccess {  status: 'success'  result: ContractData}
interface ContractMetaNotFound {  status: 'not-found'  result: null}
interface ContractMetaEmpty {  status: 'empty'  result: null}
export type ContractMetaResult = ContractMetaSuccess | ContractMetaNotFound | ContractMetaEmptyContract metadata is a map of the following interface:
export interface ContractData {  address: string  contractName: string  contractAddress: string  tokenSymbol: string  decimals?: number  type: ContractType  chainID: number}The full interface looks as follows:
interface ContractMetaStore {  readonly strategies: Record<ChainOrDefault, readonly RequestResolver.RequestResolver<GetContractMetaStrategy>[]>  readonly set: (arg: ContractMetaParams, value: ContractMetaResult) => Effect.Effect<void, never>  readonly get: (arg: ContractMetaParams) => Effect.Effect<ContractMetaResult, never>  readonly getMany?: (arg: Array<ContractMetaParams>) => Effect.Effect<Array<ContractMetaResult>, never>}You can notice that the AbiStore and ContractMetadataStore interfaces are very similar, and both have a status for the set and get methods. Both stores can return three states:
- success- The requested data is found in the store.
- not-found- The requested data is found in the store, but is missing a value. This means that we have tried to resolve it from a third party, but it was missing there.
- empty- The requested data is not found in the store, which means that it has never been requested before.
We have two states that can return an empty result. We want to be able to skip the meta strategy in cases where we know it’s not available, as this can significantly reduce the number of requests to the strategies and improve performance.
Some strategies may be able to add the data later. Therefore, we encourage storing a timestamp and removing the “not-found” state to be able to check again.
Default Stores
By defulat loop decoder provides two stores that can be used out of the box:
- In-memory store - located at @3loop/transaction-decoder/in-memory
- InMemoryAbiStore- caches the resolved abi in-memory
- InMemoryContractMetaStore- caches the resolved contract metadata in-memory
You will most likely use these stores for testing and development purposes. A persistent store will significantly improve the performance of the decoder over time.
- Sqlite store - located at @3loop/transaction-decoder/sql
- SqlAbiStore- caches the resolved abi in any sql database supported by- @effect/sql
- SqlContractMetaStore- caches the resolved contract metadata in any sql database supported by- @effect/sql