Goal: full feature parity with AssemblyScript graph-ts so any production subgraph can be written in Rust.
Current state: feature parity achieved. All graph-ts host functions are implemented. ERC20 and ERC721 subgraphs are live on The Graph Studio (Arbitrum One). Five example subgraphs cover every handler type. Native cargo test covers the entire SDK without Docker.
Status: ✅ done. Add, Sub, Mul, Div impls in graphite/src/primitives.rs, backed by num-bigint.
Status: ✅ done. Rem impl added to BigInt.
Status: ✅ done. pub fn remove(id: &str) emitted for every generated entity; calls ffi::store_remove on WASM and the native store on non-WASM builds.
Status: ✅ done. Immutable entities detected in schema.rs; save() is suppressed and only the initial write path is emitted.
Status: ✅ done. Address::from_hex_str(s: &str) added via AddressExt trait in graphite/src/primitives.rs.
Status: ✅ done. Non-nullable fields (! in schema) are emitted as T instead of Option<T>, giving a compile error rather than a silent None.
Status: ✅ done. Fixed arrays (T[N]), dynamic arrays (T[]), and nested arrays (T[][]) fully decoded in graph-as-runtime/src/ethereum.rs via read_ethereum_value_array.
Status: ✅ done. KIND_TUPLE decoded recursively into EthereumValue::Tuple(Vec<EthereumValue>). Covers Uniswap V3 Swap, Aave Borrow, and similar modern DeFi events.
Status: ✅ done. abi.rs emits correct field types and find_array / find_tuple accessors for array and tuple params.
Status: ✅ done. ethereum_decode(types, data) wrapper in graph-as-runtime/src/ethereum.rs; calls the host FFI on WASM, returns None on native (mock directly with EthereumValue).
Status: ✅ done. ContractCall::new(address, signature).with_args(encoded).call(host) builder in graphite/src/call.rs. Selector computed via crypto::selector.
Status: ✅ done. mock::set_call_result, mock::assert_called, mock::call_count on the thread-local mock; MockHost::assert_called and MockHost::call_count on the struct-based mock.
Status: ✅ done. data_source::create, data_source::address, and data_source::network wrappers in graphite/src/data_source.rs. mock::assert_data_source_created and MockHost::assert_data_source_created for testing. Manifest templates: section parsing is still manual (deploy tool does not yet auto-generate it).
Status: ✅ done. graphite/src/crypto.rs uses alloy_primitives::keccak256 on both WASM and native — works in cargo test with no mock setup. crypto::selector strips return types and returns the 4-byte function selector.
Status: ✅ done. Fields with @derivedFrom are detected and excluded from the generated struct, new(), setters, save(), and load(). Graph-node handles the reverse lookup at query time.
Status: ✅ done. #[handler(call)] generates WASM entry points that call read_ethereum_call and decode via FromRawCall. {Contract}{Fn}Call structs are generated by graphite codegen for every ABI function. Manifest callHandlers section parsing is still manual (not auto-generated by the deploy tool).
Status: ✅ done. examples/multi-source/ — ERC20 + ERC721 data sources in one subgraph.yaml, sharing a single WASM file. Codegen handles multiple [[contracts]] entries. Four native tests cover both handlers, namespace isolation, and mixed event counts.
Status: ✅ done. EthereumTransactionReceipt (status, gas_used, log_count) decoded from AS memory at offset 28 of EthereumEvent. receipt: Option<TransactionReceipt> threaded through RawEthereumEvent → EventContext. Re-exported from graphite as graphite::TransactionReceipt.
Status: ✅ done. nonfatal_error!(host, "msg") macro logs at CRITICAL level. With features: [nonFatalErrors] in the manifest, graph-node records the error and continues indexing.
Status: ✅ done. mock::set_ipfs_result(cid, bytes) registers content in a thread-local; MockHost::ipfs_cat checks both instance content and the thread-local.
| Feature | Effort | Status |
|---|---|---|
| JSON module | Medium | ✅ done. graphite::json::from_bytes / from_str → JsonValue. On WASM calls json.fromBytes host and decodes AS JSONValue from memory. On native uses serde_json. Re-exported via prelude. |
| Dynamic data source manifest codegen | Low | ✅ done. [[templates]] section in graphite.toml generates the same ABI bindings as [[contracts]]. Deploy tool already walks templates: in the manifest via collect_file_refs. |
ENS (ens.nameByAddress) |
Low-medium | ✅ done. graphite::ens::name_by_address(address) calls ens.nameByAddress FFI on WASM; checks mock::set_ens_name thread-local on native. MockHost::mock_ens_name for struct-based tests. |
| File data sources (IPFS indexing) | High | ✅ done. #[handler(file)] macro variant generates a WASM export that reads IPFS content bytes and calls the _impl with (content: &Vec<u8>, ctx: &FileContext). Schema codegen skips no changes needed — file handlers use standard entities. |
| Timeseries / aggregations | High | ✅ done. Timestamp and Int8 scalars map to i64 in codegen. set_i64/FieldValue::Int8 added throughout the stack. @aggregation entities skipped in codegen (graph-node auto-computes them). _Schema_ (fulltext directives) also skipped. |
| Subgraph composition | High | ✅ done (no-op). Composition is a query-layer concern — graph-node handles it at deploy time. No handler SDK changes needed. |
Fulltext search (@fulltext) |
Low | ✅ done. _Schema_ object skipped in codegen; @fulltext directives are PostgreSQL-specific and handled entirely by graph-node. |
| Subgraph grafting | Low | ✅ done. graft: commented block added to graphite init scaffold's subgraph.yaml. Grafting is a deployment-time manifest option — no handler SDK changes needed. |
| Feature | Status |
|---|---|
dataSource.createWithContext |
✅ done. WASM path uses new new_asc_string_array + data_source_create_with_context FFI. Hostless create_file_with_context available for #[handler] code. Context retrievable via context_current() / context_string(key). |
| File data source example | ✅ done. examples/file-ds/ — ERC721 transfer handler spawns IPFS file data source; #[handler(file)] metadata handler parses JSON and updates entity. Four native tests. |
Log capture fix (testing::MockHost) |
✅ done. logs field changed to RefCell<Vec<(LogLevel, String)>> so log() can write through &self. get_logs(), logs_at() helpers added. |
receipt: true manifest generation |
✅ done. receipt = true in graphite.toml [[contracts]] section emits receipt: true in the mapping. |
| Hostless data source API | ✅ done. data_source::create_file, create_file_with_context, create_contract, context_current, context_string, id_current, address_current, network_current — all cfg-dispatch (FFI on WASM, thread-local on native). |
| CI: all examples in WASM build | ✅ done. CI builds ERC20, ERC721, ERC1155, multi-source, file-ds, and uniswap-v2. |
examples/file-ds in workspace |
✅ done. Added to Cargo.toml workspace members; all tests run in cargo test --workspace. |
| Uniswap V2 factory example | ✅ done. examples/uniswap-v2/ — Factory + Pair template pattern. handle_pair_created creates Pool entity and calls data_source::create_contract("Pair", addr). handle_swap records Swap entity and increments Pool.swapCount (LE-byte arithmetic). mock::set_current_address and mock::assert_contract_data_source_created added. Four native tests. |
graphite init scaffold fix |
✅ done. Generated lib.rs now uses correct #[handler] pattern (no host param, builder-style entity, _impl convention, no_std boilerplate) matching the real examples. Includes a native test. |
By end of April, a developer should be able to:
- Index any ERC20/ERC721/ERC1155 event with no manual workarounds — ✅
- Index a factory-pattern contract (Uniswap V2 pairs, etc.) via dynamic data sources — ✅
- Call contract view functions from inside a handler — ✅
- Write entities with relationships using
@derivedFrom— ✅ - Run
cargo testand cover all handler logic — including contract calls and keccak — without Docker — ✅
All criteria met. Covers ~100% of production subgraph patterns on The Graph today.