{"id":31908485,"url":"https://github.com/graphprotocol/grc-20-ts","last_synced_at":"2026-01-06T11:17:58.181Z","repository":{"id":274794034,"uuid":"920839787","full_name":"graphprotocol/grc-20-ts","owner":"graphprotocol","description":null,"archived":false,"fork":false,"pushed_at":"2025-09-17T17:36:36.000Z","size":435,"stargazers_count":8,"open_issues_count":1,"forks_count":5,"subscribers_count":19,"default_branch":"main","last_synced_at":"2025-09-29T04:52:59.951Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/graphprotocol.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-22T21:26:18.000Z","updated_at":"2025-09-17T17:35:08.000Z","dependencies_parsed_at":"2025-02-20T17:38:10.674Z","dependency_job_id":"d8e0f067-d44c-410a-92c7-31fe7b63fd14","html_url":"https://github.com/graphprotocol/grc-20-ts","commit_stats":null,"previous_names":["graphprotocol/grc-20","graphprotocol/grc-20-ts"],"tags_count":62,"template":false,"template_full_name":null,"purl":"pkg:github/graphprotocol/grc-20-ts","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphprotocol%2Fgrc-20-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphprotocol%2Fgrc-20-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphprotocol%2Fgrc-20-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphprotocol%2Fgrc-20-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/graphprotocol","download_url":"https://codeload.github.com/graphprotocol/grc-20-ts/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/graphprotocol%2Fgrc-20-ts/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279015924,"owners_count":26085778,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-13T15:30:03.024Z","updated_at":"2026-01-06T11:17:58.174Z","avatar_url":"https://github.com/graphprotocol.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Knowledge Graph SDK\n\nA collection of tools for interacting with The Graph.\n\n## Installing\n\n```sh\nnpm install @graphprotocol/grc-20\n```\n\n## Overview\n\n### Data flow\n\nData in The Graph lives both offchain and onchain. This data is written to IPFS, and the resulting content identitifier is then posted onchain before being read by the indexing stack. After the indexer finishes processing the data it's exposed by the API.\n![CleanShot 2025-01-22 at 10 51 23@2x](https://github.com/user-attachments/assets/f0cee8e0-43f9-4663-a2e7-54de6d962115)\n\n### Spaces\n\nOn The Graph, knowledge is organized into spaces. Anyone can create a space for a community, project or individual. Spaces are organized onchain into a set of multiple smart contracts. These smart contracts represent the space itself, its data and its governance process. Depending on which onchain actions you're taking you might be interacting with one or more of these smart contracts.\n\n### Relations\n\nRelations describe the edges within the graph. Relations are themselves entities that include details about the relationship. For example a Company can have Team Members. Each Team Member relation can have an attribute describing when the person joined the team. This is a model that is commonly called a property graph.\n\n### Entities\n\nAn entity is a unique identifier representing a person, a place, an idea, a concept, or anything else. Entities are comprised of triples and relations which provide semantic meaning as to what the entity _is_. An entity's data can be composed from multiple spaces at once. This property is what enables pluralism within The Graph.\n\n[More about entities and knowledge graphs](https://www.geobrowser.io/space/6tfhqywXtteatMeGUtd5EB/XYo6aR3VqFQSEcf6AeTikW)\n\n[More about pluralism](https://www.geobrowser.io/space/6tfhqywXtteatMeGUtd5EB/5WHP8BuoCdSiqtfy87SYWG)\n\n### Ops and edits\n\nData in The Graph is stored as an Op (operation). Ops represent a set of changes applied to entities. A change could be setting or deleting a triple or a relation. Both triples and relations are represented as Ops.\n\nWhen writing data, these ops are grouped into a logical set called an \"Edit.\" An Edit has a name, authors, and other metadata to represent the set of changes. This edit is then encoded into a binary representation for storage efficiency.\n\n[Ops and edits in GRC-20](https://github.com/yanivtal/graph-improvement-proposals/blob/new-ops/grcs/0020-knowledge-graph.md#101-operations-op)\n\n## Using\n\n### Unique IDs\n\nEntities throughout The Graph are referenced via globally unique identifiers. The SDK exposes APIs for creating these IDs.\n\n```ts\nimport { Id } from '@graphprotocol/grc-20';\n\nconst newId = Id.generate();\n```\n\n### Creating properties, types and entities\n\nWorking with triple and relations ops is a low level API and give you maximum flexibility. In order to ease the process of creating and updating data, the library also exports APIs for creating properties, types and entities.\n\n```ts\nimport { Graph } from '@graphprotocol/grc-20';\n\n// create a property\nconst propertyResult = Graph.createProperty({\n  name: 'name of the property',\n  dataType: 'TEXT', // TEXT | NUMBER | TIME | POINT | CHECKBOX | RELATION,\n});\n\n// create a type\nconst { id: personTypeId, ops: createPersonTypeOps } = Graph.createType({\n  name: 'name of the type',\n  properties: […listOfPropertyIds],\n});\n\n// create an image\nconst { id: imageId, ops: createImageOps } = await Graph.createImage({\n  url: 'https://example.com/image.png',\n  // blob: new Blob([fs.readFileSync(path.join(__dirname, 'cover.png'))], { type: 'image/png' });\n});\n\n// create an entity\nconst { id: restaurantId, ops: createRestaurantOps } = Graph.createEntity({\n  name: 'name of the entity',\n  description: 'description of the entity',\n  types: […listOfTypeIds],\n  cover: imageId,\n  values: [\n    {\n      property: propertyId,\n      value: 'value of the property'\n    }\n  ],\n  relations: {\n    // relation property\n    [propertyId]: {\n      toEntity: 'id of the entity',\n      id: 'id of the relation', // optional\n      position: positionString, // optional\n      values: [\n        {\n          property: propertyId,\n          value: 'value of the property'\n        }\n      ],\n    },\n  },\n});\n```\n\n#### Serializing values\n\nAll values are serialized to a string. The SDK provides helper functions for serializing values to the correct string format.\n\n```ts\nimport { Graph } from '@graphprotocol/grc-20';\n\nconst { id: personId, ops: createPersonOps } = Graph.createEntity({\n  values: [\n    {\n      property: someNumberPropertyId,\n      value: Graph.serializeNumber(42),\n    },\n    {\n      property: someCheckboxPropertyId,\n      value: Graph.serializeBoolean(true),\n    },\n    {\n      property: someDatePropertyId,\n      value: Graph.serializeDate(new Date()),\n    },\n    {\n      property: somePointPropertyId,\n      value: Graph.serializePoint([1, 2]),\n    },\n  ]\n});\n```\n\n#### Options for values\n\nSome values have options that can be set.\n\n- Text values can be set to a specific `language`.\n- Number values can be set to a specific `unit`.\n\n```ts\nconst { id: textEntityId, ops: createTextEntityOps } = Graph.createEntity({\n  values: [\n    {\n      property: someTextPropertyId,\n      value: 'Hello',\n      options: {\n        type: 'text',\n        language: Id('dad6e52a5e944e559411cfe3a3c3ea64'),\n      },\n    },\n    {\n      property: someNumberPropertyId,\n      value: Graph.serializeNumber(42),\n      options: {\n        type: 'number',\n        unit: Id('016c9b1cd8a84e4d9e844e40878bb235'),\n      },\n    },\n  ],\n});\n```\n\n#### Example Flow\n\n```ts\nimport { Graph } from '@graphprotocol/grc-20';\n\nconst ops: Array\u003cOp\u003e = [];\n\n// create an age property\nconst { id: agePropertyId, ops: createAgePropertyOps } = Graph.createProperty({\n  dataType: 'NUMBER',\n  name: 'Age',\n});\nops.push(...createAgePropertyOps);\n\n// create a likes property\nconst { id: likesPropertyId, ops: createLikesPropertyOps } = Graph.createProperty({\n  dataType: 'RELATION',\n  name: 'Likes',\n});\nops.push(...createLikesPropertyOps);\n\n// create a person type\nconst { id: personTypeId, ops: createPersonTypeOps } = Graph.createType({\n  name: 'Person',\n  cover: personCoverId,\n  properties: [agePropertyId, likesPropertyId],\n});\nops.push(...createPersonTypeOps);\n\n// create an restaurant cover image\nconst { id: restaurantCoverId, ops: createRestaurantCoverOps } = await Graph.createImage({\n  url: 'https://example.com/image.png',\n});\nops.push(...createRestaurantCoverOps);\n\n// create a restaurant entity with a website property\nconst restaurantTypeId = 'A9QizqoXSqjfPUBjLoPJa2';\nconst { id: restaurantId, ops: createRestaurantOps } = Graph.createEntity({\n  name: 'Yum Yum',\n  description: 'A restaurant serving fusion cuisine',\n  cover: restaurantCoverId,\n  types: [restaurantTypeId],\n  values: [\n    {\n      property: WEBSITE_PROPERTY,\n      value: 'https://example.com',\n    },\n  ],\n});\nops.push(...createRestaurantOps);\n\n// create a person cover image\nconst { id: personCoverId, ops: createPersonCoverOps } = await Graph.createImage({\n  url: 'https://example.com/avatar.png',\n});\nops.push(...createPersonCoverOps);\n\n// create a person entity with a likes relation to the restaurant entity\nconst { id: personId, ops: createPersonOps } = Graph.createEntity({\n  name: 'Jane Doe',\n  types: [personTypeId],\n  cover: personCoverId,\n  values: [\n    {\n      property: agePropertyId,\n      value: serializeNumber(42),\n    },\n    {\n      property: likesPropertyId,\n      value: restaurantId,\n    },\n  ],\n});\nops.push(...createPersonOps);\n```\n\n### Writing an edit to IPFS\n\nOnce you have a set of ops ready to publish, you'll need to binary encode them into an Edit and upload the Edit to IPFS.\n\nCurrently the indexer only supports reading a specific gateway. You should use our IPFS API to guarantee data availability for your published data while in early access.\n\nAdditionally, the indexer expects that IPFS CIDs be prefixed with `ipfs://` so it knows how to process it correctly. The API already returns the CID prefixed with `ipfs://`. \n\nWe've abstracted the IPFS publishing and binary encoding into a single API.\n\n```ts\nimport { Ipfs } from '@graphprotocol/grc-20';\n\nconst { cid } = await Ipfs.publishEdit({\n  name: 'Edit name',\n  ops: ops,\n  author: '0x000000000000000000000000000000000000', \n  network: 'TESTNET', // optional, defaults to MAINNET\n})\n```\n\n### Publishing an edit onchain using your wallet\n\nOnce you've uploaded the binary encoded Edit to IPFS and have correctly formed `ipfs://hash`, you can write this to a space.\n\nThe calldata used to write the edit onchain depends on the governance structure of the space. Currently The Graph supports two governance modes, one with voting and one without. The API exposes metadata about each space, its governance structure, and what smart contracts exist for it.\n\nWe expose an API for fetching the appropriate calldata for the correct contract addresses for each space.\n\n```ts\nimport { Graph } from '@graphprotocol/grc-20';\n\n// You'll need to know your space id and have an IPFS hash ahead of time\nconst spaceId = 'space-id';\nconst cid = 'ipfs://hash';\n\n// This returns the correct contract address and calldata depending on the space id\n// You can also use Graph.MAINNET_API_ORIGIN for mainnet (currently not working)\nconst result = await fetch(`${Graph.TESTNET_API_ORIGIN}/space/${spaceId}/edit/calldata`, {\n  method: \"POST\",\n  body: JSON.stringify({ cid }),\n});\n\nconst { to, data } = await result.json();\n\nconst txResult = await walletClient.sendTransaction({\n  to: to,\n  value: 0n,\n  data: data,\n});\n```\n\n### Publishing an edit onchain using your wallet\n\n```ts\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { getWalletClient } from \"@graphprotocol/grc-20\";\n\nconst addressPrivateKey = '0xTODO';\nconst { address } = privateKeyToAccount(addressPrivateKey);\n\n// Take the address and enter it in Faucet to get some testnet ETH https://faucet.conduit.xyz/geo-test-zc16z3tcvf\n\nconst smartAccountWalletClient = await getWalletClient({\n  privateKey: addressPrivateKey,\n});\n\n// publish an edit to IPFS\n// get the calldata for the edit\n\nconst txResult = await smartAccountWalletClient.sendTransaction({\n  to: to,\n  value: 0n,\n  data: data,\n});\n```\n\n### Publishing an edit onchain using your Geo Account\n\nThe Geo Genesis browser uses a smart account associated with your account to publish edits. There may be situations where you want to use the same account in your code as you do on Geo Genesis. In order to get the smart account wallet client you can use the `getSmartAccountWalletClient` function.\n\nTo use `getSmartAccountWalletClient` you'll need the private key associated with your Geo account. You can get your private key using https://www.geobrowser.io/export-wallet.\n\nTransaction costs from your smart account will be sponsored by the Geo team for the duration of the early access period. Eventually you will need to provide your own API key or provide funds to your smart account.\n\n```ts\nimport { getSmartAccountWalletClient } from '@graphprotocol/grc-20';\n\n// IMPORTANT: Be careful with your private key. Don't commit it to version control.\n// You can get your private key using https://www.geobrowser.io/export-wallet\nconst privateKey = `0x${privateKeyFromGeoWallet}`;\nconst smartAccountWalletClient = await getSmartAccountWalletClient({\n  privateKey,\n  // rpcUrl, // optional\n});\n\n// publish an edit to IPFS\n// get the calldata for the edit\n\nconst txResult = await smartAccountWalletClient.sendTransaction({\n  to: to,\n  value: 0n,\n  data: data,\n});\n```\n\n### Deploying a space\n\nYou can deploy spaces programmatically using the API. Currently there are two types of governance modes for spaces: one with voting and one without. They're called PUBLIC or PERSONAL spaces respectively. The API only supports deploying the PERSONAL governance mode currently.\n\nThe API supports deploying to both testnet and mainnet. By default it will deploy to mainnet.\n\n```ts\nimport { Graph } from '@graphprotocol/grc-20';\n// needs to be a valid address\nconst editorAddress = '0x000000000000000000000000000000000000';\nconst spaceName = 'Example-Name';\n\nconst spaceId = await Graph.createSpace({\n  initialEditorAddress, \n  spaceName, \n  // Optionally specify TESTNET or MAINNET. Defaults to MAINNET\n  network: 'TESTNET',\n});\n```\n\n## Full Publishing Flow\n\n```ts\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { Graph, Ipfs, getWalletClient } from \"@graphprotocol/grc-20\";\n\nconst addressPrivateKey = '0xTODO';\nconst { address } = privateKeyToAccount(addressPrivateKey);\n\n// Take the address and enter it in Faucet to get some testnet ETH https://faucet.conduit.xyz/geo-test-zc16z3tcvf\n\nconst smartAccountWalletClient = await getWalletClient({\n  privateKey: addressPrivateKey,\n});\n\nconsole.log('addressPrivateKey', addressPrivateKey);\nconsole.log('address', address);\n// console.log('smartAccountWalletClient', smartAccountWalletClient);\n\nconst space = await Graph.createSpace({\n  editorAddress: address,\n  name: 'test',\n  network: 'TESTNET',\n});\n\nconsole.log('space', space);\nconst spaceId = space.id;\n\nconst { ops, id } = await Graph.createEntity({\n  name: 'test name',\n  description: 'test description',\n});\nconsole.log('entity id', id);\n\nconst { cid } = await Ipfs.publishEdit({\n  name: 'Edit name',\n  ops,\n  author: address,\n});\n\nconsole.log('cid', cid);\n\nconst result = await fetch(`${Graph.TESTNET_API_ORIGIN}/space/${spaceId}/edit/calldata`, {\n  method: 'POST',\n  body: JSON.stringify({ cid }),\n});\n\nconsole.log('edit result', result);\n\nconst editResultJson = await result.json();\nconsole.log('editResultJson', editResultJson);\nconst { to, data } = editResultJson;\n\nconsole.log('to', to);\nconsole.log('data', data);\n\nconst txResult = await smartAccountWalletClient.sendTransaction({\n  // @ts-expect-error - TODO: fix the types error\n  account: smartAccountWalletClient.account,\n  to: to,\n  value: 0n,\n  data: data,\n});\n\nconsole.log('txResult', txResult);\n  ```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphprotocol%2Fgrc-20-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgraphprotocol%2Fgrc-20-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgraphprotocol%2Fgrc-20-ts/lists"}