Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/famous-mayflies-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@3loop/transaction-decoder': minor
---

Add automatic resolvers for contract metadata
2 changes: 1 addition & 1 deletion .changeset/hungry-kings-applaud.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
"@3loop/transaction-decoder": minor
'@3loop/transaction-decoder': minor
---

Change interpretation from jsonata to js code using QuickJS
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
"printWidth": 120,
"singleQuote": true,
"trailingComma": "all"
}
}
6 changes: 5 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{
"recommendations": ["astro-build.astro-vscode"],
"recommendations": [
"astro-build.astro-vscode",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"unwantedRecommendations": []
}
10 changes: 10 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"editor.rulers": [120],
"editor.tabSize": 4,
"editor.detectIndentation": false,
"editor.trimAutoWhitespace": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true
}
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

A library to transform any EVM transaction into a human-readable format. It consists of 2 parts:

- [Transaction decoder](https://github.com/3loop/loop-decoder/tree/main/packages/transaction-decoder)
- Customizable transaction interpreter
- [Transaction decoder](https://github.com/3loop/loop-decoder/tree/main/packages/transaction-decoder)
- Customizable transaction interpreter

## Why

Expand All @@ -17,9 +17,9 @@ Currently, the available EVM transaction decoders require developers to use spec

## Features

- [x] Can be used in any JavaScript environment
- [x] Minimal external dependencies - connect your own storage
- [x] Flexible interpreter that allows you to define any custom interpretation of EVM transactions.
- [x] Can be used in any JavaScript environment
- [x] Minimal external dependencies - connect your own storage
- [x] Flexible interpreter that allows you to define any custom interpretation of EVM transactions.

## Looking for feedback

Expand Down
10 changes: 5 additions & 5 deletions apps/docs/src/content/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineCollection } from "astro:content";
import { docsSchema, i18nSchema } from "@astrojs/starlight/schema";
import { defineCollection } from 'astro:content'
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'

export const collections = {
docs: defineCollection({ schema: docsSchema() }),
i18n: defineCollection({ type: "data", schema: i18nSchema() }),
};
docs: defineCollection({ schema: docsSchema() }),
i18n: defineCollection({ type: 'data', schema: i18nSchema() }),
}
2 changes: 1 addition & 1 deletion apps/docs/src/content/docs/contribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ To create a new release, one of the maintainers will merge the changeset PR into

Some ideas for the decoder and interpreter were inspired by open-source software. Special thanks to:

- [EVM Translator](https://github.com/metagame-xyz/evm-translator) - some data types and data manipulations were heavily inspired by this source.
- [EVM Translator](https://github.com/metagame-xyz/evm-translator) - some data types and data manipulations were heavily inspired by this source.
63 changes: 30 additions & 33 deletions apps/docs/src/content/docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ description: A guide in my new Starlight docs site.

### Requirements

- TypeScript 5.x
- `exactOptionalPropertyTypes` and `strict` enabled in your tsconfig.json
- TypeScript 5.x
- `exactOptionalPropertyTypes` and `strict` enabled in your tsconfig.json

### Dependencies

Expand All @@ -24,12 +24,12 @@ To begin using the Loop Decoder, you need to create an instance of the LoopDecod

```ts
const getPublicClient = (chainId: number) => {
return {
client: createPublicClient({
transport: http(RPC_URL[chainId]),
}),
};
};
return {
client: createPublicClient({
transport: http(RPC_URL[chainId]),
}),
}
}
```

2. `contractMetaStore`: This object has 2 properties `get` and `set` that returns and caches contract meta-information. See the `ContractData` type for the required properties.
Expand All @@ -48,44 +48,41 @@ const contractMetaStore = {
address: string
chainID: number
}) {
// NOTE: not yet called as we do not have any automatic resolve strategy implemented
// NOTE: ignore for now
},
}
```

3. `abiStore`: Similarly, this object has 2 properties `get` and `set` that returns and cache the contract or fragment ABI based on the chain ID, address, and/or signature.

```ts
const db = {}; // Your data source
const db = {} // Your data source

const abiStore = {
get: async (req: {
chainID: number;
address: string;
event?: string | undefined;
signature?: string | undefined;
}) => {
return db.getContractAbi(req);
},
set: async (req: {
address?: Record<string, string>;
signature?: Record<string, string>;
}) => {
await db.setContractAbi(req);
},
};
get: async (req: {
chainID: number
address: string
event?: string | undefined
signature?: string | undefined
}) => {
return db.getContractAbi(req)
},
set: async (req: { address?: Record<string, string>; signature?: Record<string, string> }) => {
await db.setContractAbi(req)
},
}
```

Finally, you can create a new instance of the LoopDecoder class:

```ts
import { TransactionDecoder } from "@3loop/transaction-decoder";
import { TransactionDecoder } from '@3loop/transaction-decoder'

const decoded = new TransactionDecoder({
getProvider: getPublicClient,
abiStore: abiStore,
contractMetaStore: contractMetaStore,
});
getProvider: getPublicClient,
abiStore: abiStore,
contractMetaStore: contractMetaStore,
})
```

It's important to note that the Loop Decoder does not enforce any specific data source, allowing users of the library to load contract data as they see fit. Depending on the requirements of your application, you can either include the necessary data directly in your code for a small number of contracts or use a database as a cache.
Expand All @@ -94,7 +91,7 @@ LoopDecoder instances provide a public method, `decodeTransaction`, which fetche

```ts
const result = await decoded.decodeTransaction({
chainID: 5,
hash: "0x...",
});
chainID: 5,
hash: '0x...',
})
```
134 changes: 66 additions & 68 deletions apps/docs/src/content/docs/guides/decode-transaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ npx tsc --init

```json
{
"compilerOptions": {
"strict": true
}
"compilerOptions": {
"strict": true
}
}
```

Expand All @@ -43,9 +43,9 @@ npx tsc --init

```json
{
"scripts": {
"start": "tsc && node index.js"
}
"scripts": {
"start": "tsc && node index.js"
}
}
```

Expand All @@ -72,19 +72,19 @@ Loop Decoder requires some data sources to be able to decode transactions. We wi
We will start by creating a function which will return an object with PublicClient based on the chain ID. For the sake of this example, we will only support mainnet.

```ts
import { createPublicClient, http } from "viem";
import { createPublicClient, http } from 'viem'

const getPublicClient = (chainId: number) => {
if (chainId !== 1) {
throw new Error(`Missing RPC provider for chain ID ${chainId}`);
}

return {
client: createPublicClient({
transport: http("https://rpc.ankr.com/eth"),
}),
};
};
if (chainId !== 1) {
throw new Error(`Missing RPC provider for chain ID ${chainId}`)
}

return {
client: createPublicClient({
transport: http('https://rpc.ankr.com/eth'),
}),
}
}
```

### ABI loader
Expand All @@ -94,64 +94,62 @@ To avoid making unecessary calls to third-party APIs, Loop Decoder uses an API t
Create a cache for contract ABI:

```ts
import { EtherscanStrategyResolver } from "@3loop/transaction-decoder";
const abiCache = new Map<string, string>();
import { EtherscanStrategyResolver } from '@3loop/transaction-decoder'
const abiCache = new Map<string, string>()

const abiStore = {
strategies: [
EtherscanStrategyResolver({
apikey: "YourApiKeyToken",
}),
FourByteStrategyResolver(),
],
get: async (req: {
chainID: number;
address: string;
event?: string | undefined;
signature?: string | undefined;
}) => {
return Promise.resolve(abiCache.get(req.address) ?? null);
},
set: async (req: {
address?: Record<string, string>;
signature?: Record<string, string>;
}) => {
const addresses = Object.keys(req.address ?? {});
addresses.forEach((address) => {
abiCache.set(address, req.address?.[address] ?? "");
});
},
};
strategies: [
EtherscanStrategyResolver({
apikey: 'YourApiKeyToken',
}),
FourByteStrategyResolver(),
],
get: async (req: {
chainID: number
address: string
event?: string | undefined
signature?: string | undefined
}) => {
return Promise.resolve(abiCache.get(req.address) ?? null)
},
set: async (req: { address?: Record<string, string>; signature?: Record<string, string> }) => {
const addresses = Object.keys(req.address ?? {})
addresses.forEach((address) => {
abiCache.set(address, req.address?.[address] ?? '')
})
},
}
```

### Contract Metadata loader

Create a cache for contract meta-information, such as token name, decimals, symbol, etc.:

```ts
import type { ContractData } from "@3loop/transaction-decoder";
const contractMeta = new Map<string, ContractData>();
import type { ContractData } from '@3loop/transaction-decoder'
const contractMeta = new Map<string, ContractData>()

const contractMetaStore = {
get: async (req: { address: string; chainID: number }) => {
return contractMeta.get(req.address) ?? null;
},
set: async (req: { address: string; chainID: number }) => {
// NOTE: not yet called as we do not have any automatic resolve strategy implemented
},
};
strategies: { default: [ERC20RPCStrategyResolver] },
get: async (req: { address: string; chainID: number }) => {
return contractMeta.get(req.address) ?? null
},
set: async (req: { address: string; chainID: number }, data) => {
contractMeta.set(req.address, data)
},
}
```

Finally, you can create a new instance of the LoopDecoder class:

```ts
import { TransactionDecoder } from "@3loop/transaction-decoder";
import { TransactionDecoder } from '@3loop/transaction-decoder'

const decoder = new TransactionDecoder({
getPublicClient: getPublicClient,
abiStore: abiStore,
contractMetaStore: contractMetaStore,
});
getPublicClient: getPublicClient,
abiStore: abiStore,
contractMetaStore: contractMetaStore,
})
```

## Decoding a Transaction
Expand All @@ -160,17 +158,17 @@ Now that we have all the necessary components, we can start decoding a transacti

```ts
async function main() {
try {
const decoded = await decoder.decodeTransaction({
chainID: 1,
hash: "0xc0bd04d7e94542e58709f51879f64946ff4a744e1c37f5f920cea3d478e115d7",
});

console.log(JSON.stringify(decoded, null, 2));
} catch (e) {
console.error(JSON.stringify(e, null, 2));
}
try {
const decoded = await decoder.decodeTransaction({
chainID: 1,
hash: '0xc0bd04d7e94542e58709f51879f64946ff4a744e1c37f5f920cea3d478e115d7',
})

console.log(JSON.stringify(decoded, null, 2))
} catch (e) {
console.error(JSON.stringify(e, null, 2))
}
}

main();
main()
```
Loading