# GRC-004: Adding Rust as a first-class subgraph mapping language

**URL:** <https://forum.thegraph.com/t/grc-004-adding-rust-as-a-first-class-subgraph-mapping-language/6910>\
**Category:** Graph Request for Comment (GRC)\
**Created:** [April 15, 2026, 1:59pm UTC](https://forum.thegraph.com/t/grc-004-adding-rust-as-a-first-class-subgraph-mapping-language/6910 "2026-04-15T13:59:11Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![AxiomaticAardvark](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.thegraph.com/axiomaticaardvark/32/2882_2.png) [@AxiomaticAardvark](https://forum.thegraph.com/u/AxiomaticAardvark)\
**Post date:** [April 15, 2026, 1:59pm UTC](https://forum.thegraph.com/t/grc-004-adding-rust-as-a-first-class-subgraph-mapping-language/6910/1 "2026-04-15T13:59:11Z")

</div>

**Stage:** RFC (Request for Comment)  
**GRC:** 004  
**Authors:** [@cargopete](https://github.com/cargopete) (Petko Pavlovski)  
**Related:** [GRC-003](https://forum.thegraph.com/t/grc-003-rust-subgraph-mappings-a-native-rust-abi-for-graph-node/6889) · [Graphite SDK](https://github.com/cargopete/graphite) · [graph-node PR #6462](https://github.com/graphprotocol/graph-node/pull/6462)

## Summary

This GRC proposes recognising Rust as a first-class subgraph mapping language — not by changing graph-node, but by implementing the AssemblyScript ABI in Rust.

The Graphite SDK compiles Rust handlers to WASM that is structurally indistinguishable from AssemblyScript output. The manifest declares `language: wasm/assemblyscript`. Graph-node requires zero changes. The subgraph deploys like any other.

This is not a design proposal. Two subgraphs are live on The Graph Studio today, indexing on Arbitrum One. The SDK has full feature parity with graph-ts. Everything a production subgraph needs can be written in Rust, tested with `cargo test`, and deployed with a single command.

## Background

GRC-003 proposed adding a native Rust ABI to graph-node — a parallel `rust_abi/` module (~1,450 LOC), manifest language dispatch, and a TLV serialization protocol. The PR received constructive feedback: a second ABI means a second surface to maintain, and the graph-node maintainers were understandably cautious about accepting a large outside contribution to a critical code path.

The response to that feedback was a question: _what if graph-node didn’t need to change at all?_

* * *

## The Key Insight

Graph-node does not inspect WASM binaries to determine their origin. It calls exported functions by name, passes data via the AssemblyScript memory model, and reads entity data back out through the same model. The “language” is not the WASM — it is the memory layout, allocator protocol, and string encoding the WASM uses.

The AssemblyScript memory model is fully specified:

- A 20-byte object header (RT class ID, ref count, size)
- Strings as UTF-16LE with a 4-byte length prefix
- `TypedMap<K, V>` as a heap-allocated array of key-value pairs
- A bump allocator exporting `__new(size, rtId) -> ptr` and `__pin` / `__unpin`

Implementing this in Rust means the WASM output is structurally identical to AssemblyScript output. Graph-node accepts it as a standard subgraph. No heuristics. No dispatch. No new code paths.

```auto
Your Rust handler
      │
      ▼
graphite-macros (#[handler], #[derive(Entity)])
      │
      ▼
graph-as-runtime (AS ABI: allocator, UTF-16LE strings, TypedMap, host imports)
      │
      ▼
WASM binary ──────────────────► unmodified graph-node / The Graph Studio

```

The concerns raised in GRC-003 — maintenance burden, ABI evolution, chain coverage gaps, namespace decisions — are all moot. There is no new ABI. There is no graph-node change. There is nothing to maintain upstream.

* * *

## Design

### graph-as-runtime

The `graph-as-runtime` crate is a `no_std` implementation of the AssemblyScript runtime:

- **Allocator.** A bump allocator that exports ` __new`, `__ pin`, and ` __unpin` with the correct signatures. Graph-node calls `__ new` to allocate strings and typed maps; the module owns its own heap.
- **String encoding.** All strings passed to and from host functions are UTF-16LE with a 4-byte length prefix. `AscString::from_str` / `to_string` handle the conversion transparently.
- **TypedMap.** Entities are serialised as AS `TypedMap<string, Value>` — an array of `{key: AscString, value: AscValue}` pairs with the correct RT class IDs. `store.set` and `store.get` operate on this layout.
- **Host imports.** WASM host functions are imported by their exact AS names (`store.set`, `log.log`, `ethereum.call`, etc.) using `#[link(wasm_import_module)]`. Graph-node matches by name only.

### graphite-macros

- `#[handler]` generates a `#[no_mangle] pub extern "C"` entry point that reads an AS-encoded `EthereumEvent` from the pointer graph-node passes, calls the user’s function, and returns.
- `#[handler(call)]` / `#[handler(block)]` / `#[handler(file)]` handle the other trigger types.
- `#[derive(Entity)]` is generated by `graphite codegen` from `schema.graphql` — typed structs with `new()`, `load()`, `save()`, `remove()`, and builder-pattern setters.

### graphite-sdk

The user-facing SDK re-exports everything a handler needs: `BigInt`, `BigDecimal`, `Address`, `Bytes`, `EventContext`, `crypto`, `ens`, `json`, `ipfs`, `data_source`, `mock`, and `nonfatal_error!`. On WASM it calls host FFI; on native (for `cargo test`) it dispatches to thread-local mocks.

### graphite-cli

```bash
graphite init my-subgraph # Scaffold from scratch
graphite init my-subgraph --from-contract 0x... # Fetch ABI from Etherscan
graphite codegen # Generate Rust types from ABI + schema.graphql
graphite manifest # Generate subgraph.yaml from graphite.toml
graphite build # cargo build → wasm-opt
graphite test # cargo test
graphite deploy my-name/my-subgraph # Deploy to local graph-node or The Graph Studio

```

* * *

## Developer Experience

A handler in Graphite looks like this:

```rust
#![cfg_attr(target_arch = "wasm32", no_std)]
extern crate alloc;

use alloc::format;
use graphite_macros::handler;
mod generated;
use generated::{ERC20TransferEvent, Transfer};

#[handler]
pub fn handle_transfer(event: &ERC20TransferEvent, ctx: &graphite::EventContext) {
    let id = format!("{}-{}", hex(&event.tx_hash), event.log_index[0]);
    Transfer::new(&id)
        .set_from(event.from.to_vec())
        .set_to(event.to.to_vec())
        .set_value(event.value.clone())
        .set_block_number(ctx.block_number.clone())
        .set_timestamp(ctx.block_timestamp.clone())
        .save();
}

```

Tests run natively with `cargo test` — no WASM compiler, no PostgreSQL, no external binaries:

```rust
#[test]
fn transfer_creates_entity() {
    graphite::mock::reset();
    let raw = RawEthereumEvent {
        tx_hash: [0xab; 32],
        params: vec![
            EventParam { name: "from".into(), value: EthereumValue::Address([0xaa; 20]) },
            EventParam { name: "to".into(), value: EthereumValue::Address([0xbb; 20]) },
            EventParam { name: "value".into(), value: EthereumValue::Uint(vec![100]) },
        ],
        ..Default::default()
    };
    handle_transfer_impl(
        &ERC20TransferEvent::from_raw_event(&raw).unwrap(),
        &graphite::EventContext::default(),
    );
    assert!(graphite::mock::has_entity("Transfer", "abab...ab-00"));
}

```

| | AssemblyScript | Graphite (Rust) |
| --- | --- | --- |
| Nullable safety | Runtime WASM trap | Compile-time `Option<T>` |
| Closures / iterators | Compiler crash | Full support |
| Testing setup | Node.js + PostgreSQL + binary | `cargo test`, nothing else |
| Type errors | Silent runtime coercions | Compile-time |
| Debugging | Comment-driven bisect | Actionable compiler errors |
| Ecosystem | None | All of [crates.io](http://crates.io) |
| Crate deployment | n/a | `cargo install graphite-cli` |

* * *

## Implementation Status

**This is a working implementation, not a proposal.**

### Feature parity with graph-ts

Every host function exposed by graph-node is implemented:

| Feature | Status |
| --- | --- |
| Event, call, block, file handlers | ✅ |
| `store.set` / `store.get` / `store.remove` / `store.getInBlock` | ✅ |
| `ethereum.call` (contract view calls) | ✅ |
| `ethereum.encode` / `ethereum.decode` | ✅ |
| `ipfs.cat` | ✅ |
| `json.fromBytes` | ✅ |
| `ens.nameByAddress` | ✅ |
| `dataSource.create` / `createWithContext` | ✅ |
| `crypto.keccak256` (native, no mock required) | ✅ |
| `BigInt` — full arithmetic, bitwise, shifts | ✅ |
| `BigDecimal` — full arithmetic | ✅ |
| All GraphQL scalars (String, Int, Float, Boolean, BigInt, BigDecimal, Bytes, Address, Timestamp, Int8, DateTime) | ✅ |
| `@derivedFrom`, immutable entities, non-fatal errors | ✅ |
| Block handler filters (`polling`, `every: N`) | ✅ |
| Dynamic data sources and factory pattern | ✅ |
| Receipt context | ✅ |
| Native `cargo test` (no Docker) | ✅ |

### Examples (all tested, all CI’d to WASM)

- **erc20** — ERC20 Transfer indexer. Live on The Graph Studio, Arbitrum One.
- **erc721** — NFT transfer and approval indexer. Live on The Graph Studio, Arbitrum One.
- **erc1155** — Multi-token: TransferSingle, TransferBatch, URI.
- **multi-source** — Multiple contracts in one subgraph, one WASM file.
- **file-ds** — File data source: ERC721 transfer spawns IPFS metadata handler.
- **uniswap-v2** — Factory + template pattern: tracks pairs and swaps.

### Crates

| Crate | [crates.io](http://crates.io) |
| --- | --- |
| `graph-as-runtime` | Published |
| `graphite-macros` | Published |
| `graphite-sdk` | Published (imported as `graphite`) |
| `graphite-cli` | Published (`cargo install graphite-cli`) |

* * *

## What This Means for The Graph

**Nothing breaks.** Existing AS subgraphs are unaffected. The manifest still declares `language: wasm/assemblyscript`. The network, The Graph Studio, hosted service, and graph-node are all unchanged.

**Rust subgraphs work today.** Any developer can `cargo install graphite-cli`, scaffold a project, and deploy to The Graph Studio without waiting for a protocol upgrade, a graph-node release, or a governance decision.

**The maintenance concern from GRC-003 is resolved.** There is no second ABI in graph-node. There is no upstream code to accept. There is nothing for the protocol to maintain. Graphite is a library, not a fork.

**The developer experience concern is also resolved.** The GRC-003 PR feedback noted that a two-ABI graph-node would require both teams to stay in sync. This approach requires no coordination — graph-node can release independently, and Graphite tracks the AS ABI by matching the TypedMap layout and host function signatures, which are stable by definition (changing them would break all existing subgraphs).

* * *

## Open Questions

1. **graph-cli integration.** The Graph CLI currently scaffolds AssemblyScript-only projects. Is there appetite for `graph init --language rust` to delegate to Graphite, or is a separate `graphite init` the right long-term story?

2. **Long-term ABI stability.** The AS ABI is stable by necessity — breaking it would break all existing subgraphs. Is there any planned change to the graph-node runtime (e.g., a new allocation protocol, a new string encoding) that Graphite should be aware of?

3. **Chain coverage.** The Graphite SDK currently covers Ethereum (all trigger types). Are there non-EVM chains where community members would want Rust support? The same AS-ABI approach applies — the trigger serialization for each chain would need to be implemented in `graph-as-runtime`.

* * *

## References

- [GRC-003: Rust Subgraph Mappings — A Native Rust ABI for graph-node](https://forum.thegraph.com/t/grc-003-rust-subgraph-mappings-a-native-rust-abi-for-graph-node/6889)
- [graph-node PR #6462](https://github.com/graphprotocol/graph-node/pull/6462) — the original native ABI implementation
- [Graphite SDK](https://github.com/cargopete/graphite)
- [graphite-cli on crates.io](https://crates.io/crates/graphite-cli)
- [Substreams documentation](https://thegraph.com/docs/en/substreams/) — prior art for Rust→WASM in The Graph ecosystem

---

<div class="post-metadata">

**Author:** ![AxiomaticAardvark](https://sea2.discourse-cdn.com/flex020/user_avatar/forum.thegraph.com/axiomaticaardvark/32/2882_2.png) [@AxiomaticAardvark](https://forum.thegraph.com/u/AxiomaticAardvark)\
**Post date:** [April 16, 2026, 8:56am UTC](https://forum.thegraph.com/t/grc-004-adding-rust-as-a-first-class-subgraph-mapping-language/6910/2 "2026-04-16T08:56:16Z")

</div>

full docs - [What is Graphite? - Graphite](https://cargopete.github.io/graphite/) ; next order of business for me is to heavily improve the devex, we gotta get 10x better than the AS developer experience 💪
