Open typedarray opened 1 month ago
If you could clarify on the native transfers. As I understand, they are not shown in the logs, but in call traces, right? Would it be an additional filter on the call traces then?
I am suggesting the API similar to contracts in the createConfig:
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
contracts: {
SudoswapPool: {
abi: SudoswapPoolAbi,
network: "mainnet",
factory: {
// The address of the factory contract that creates instances of this child contract.
address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4",
// The event emitted by the factory that announces a new instance of this child contract.
event: parseAbiItem("event NewPair(address poolAddress)"),
// The name of the parameter that contains the address of the new child contract.
parameter: "poolAddress",
},
startBlock: 14645816,
},
},
accounts: {
Alice: {
network: "mainnet",
address: [
"0x...",
"0x...",
"0x..."
],
startBlock: 14645816
}
}
});
Similarly, if we would like to track accounts generated from the factory:
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
contracts: {
SudoswapPool: {
abi: SudoswapPoolAbi,
network: "mainnet",
address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4"
startBlock: 14645816,
},
},
accounts: {
SudoswapUsers: {
network: "mainnet",
factory: {
// The address of the factory contract that creates instances of this child contract.
address: "0xb16c1342E617A5B6E4b631EB114483FDB289c0A4",
// The event emitted by the factory that announces a new instance of this child contract.
event: parseAbiItem("event Deposit(address sender, uint256 amount)"),
// The name of the parameter that contains the address of the new child contract.
parameter: "sender",
},
startBlock: 14645816
}
}
});
This way our accounts config api would be close to what we have for contracts already.
This is a great start. Some follow-up questions to consider:
ponder.on("ContractName:EventName", ...)
and ponder.on("ContractName.functionName", ...)
. And, what would be the type of the event
object within the indexing function?to
(or from
) argument equals the account's address, or the following eth_getLogs filter:
{
address: null, // Include all ERC20 contracts
topics:[
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", // Transfer selector
null, // Include all senders
"0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078" // Target account address as recipient
]
}
Here's a concrete user scenario that will use this feature: A smart account infrastructure provider wants to build an API that serves a list of tokens (native, ERC20, ERC721) for each account created using their tool. The smart accounts are contracts (not EOAs) that get created via a factory contract that is compatible with our existing factory pattern.
The first idea that comes to mind:
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
accounts: {
Alice: {
network: "mainnet",
address: [ // only native transfers (if no events are given)
"0x...",
"0x...",
"0x..."
],
startBlock: 14645816
},
Bob: {
network: "mainnet",
address: [ // no native transfers, but event logs
"0x...",
"0x...",
"0x..."
],
events: [ //list of events to index with corresponding event + parameter(s)
{
abi: ERC20Abi, //first version, uses an ABI and specifies only eventName
eventName: 'TransferFrom',
parameter: ["from", "to"]
},
{
event: parseAbiItem(
"event TransferFrom(address indexed from, address indexed to, uint256 value)"
), //second version, uses parseAbiItem similar to factory config
parameter: "from"
},
...
],
startBlock: 14645816,
includeNativeTransfers: false // true or false, by default?
},
}
});
Then, the indexing functions would look as follows:
//The indexing function for events:
ponder.on("AccountName:EventName", async ({event, context}) => {
})
//The indexing function for native transfers:
ponder.on("AccountName", async ({event, context}) => {
})
The types for both events I would assume can be identical to those used for indexing contract events and callTraces.
Somewhat unrelated to the comments above, I think another feature we want with accounts is the ability to index all transactions to and from an account.
A concrete example of when this would be useful: tracking the sequencer address on the base chain for a layer 2 rollup.
Good point, we would be also interested in filtering native transfers based on "from", "to" parameters. The revised version of the api design would be as shown below.
export default createConfig({
networks: {
mainnet: { chainId: 1, transport: http(process.env.PONDER_RPC_URL_1) },
},
accounts: {
Bob: { //the user needs to specify events, nativeTransfers or transactions
network: "mainnet",
address: [
"0x...",
"0x...",
"0x..."
],
events: [ //list of events to index with corresponding event + parameter(s)
{
abi: ERC20Abi, //first version, uses an ABI and specifies only eventName
eventName: 'TransferFrom',
parameter: ["from", "to"]
},
{
event: parseAbiItem(
"event TransferFrom(address indexed from, address indexed to, uint256 value)"
), //second version, uses parseAbiItem similar to factory config
parameter: "from"
},
...
],
nativeTransfers: "from" | "to" | ["from", "to"],
transactions: "from" | "to" | ["from", "to"],
startBlock: 14645816
},
}
});
For transactions, the indexing function might look as follows:
ponder.on("AccountName:transaction", async ({event, context}) => {
})
And type of the event for transaction would be:
{
block: Block,
transaction: Transaction,
transactionReceipt: TransactionReceipt | undefined
}
We decided that were gonna focus on the internal filter
types and work towards offering a low-level api that allows users to directly define a filter. Later on, we can build a high-level "account" api that abstracts away the filter
s.
filter
typesFilters should be deliberately designed, expressive, and modeled after the fundamentals of the Ethereum blockchain, not necessarily the Ethereum standard JSON-RPC.
type TransferFilter = {
type: "transfer";
chainId: number;
fromAddress: Address | Address[] | Factory | undefined;
toAddress: Address | Address[] | Factory | undefined;
fromBlock: number;
toBlock: number | undefined;
includeTransactionReceipt: boolean;
}
type TransactionFilter = {
type: "transaction";
chainId: number;
fromAddress: Address | Address[] | Factory | undefined;
toAddress: Address | Address[] | Factory | undefined;
fromBlock: number;
toBlock: number | undefined;
includeTransactionReceipt: boolean;
}
I realized TransactionFilter
is very similar to CallTraceFilter
, except that it only contains top-level calls. I think it's still fine to separate them, but worth keeping in mind.
We should add an intuitive high-level API for native transfers, such that you can write indexing functions that fire when a specific account (or list of accounts specified by a factory) sends or receives any amount of the native token.