{"id":26070330,"url":"https://github.com/dappforce/x-seller-squid","last_synced_at":"2026-04-18T08:02:43.378Z","repository":{"id":161170860,"uuid":"608083327","full_name":"dappforce/x-seller-squid","owner":"dappforce","description":null,"archived":false,"fork":false,"pushed_at":"2023-10-10T06:21:17.000Z","size":2651,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-08T23:15:56.420Z","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":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dappforce.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-03-01T09:34:20.000Z","updated_at":"2023-04-11T13:07:14.000Z","dependencies_parsed_at":null,"dependency_job_id":"ee569644-ccef-404e-adef-a2263a00d6f9","html_url":"https://github.com/dappforce/x-seller-squid","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/dappforce/x-seller-squid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dappforce%2Fx-seller-squid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dappforce%2Fx-seller-squid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dappforce%2Fx-seller-squid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dappforce%2Fx-seller-squid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dappforce","download_url":"https://codeload.github.com/dappforce/x-seller-squid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dappforce%2Fx-seller-squid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31961348,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"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-03-08T23:15:58.714Z","updated_at":"2026-04-18T08:02:43.360Z","avatar_url":"https://github.com/dappforce.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# X-Seller-Squid\n\nBased on [Subsquid](https://www.subsquid.io/) framework. Powered by [Subsocial](https://subsocial.network/).\n\n- [Seller-Squid config information API](#seller-squid-config-information-api)\n- [Domain registration validation steps](#domain-registration-validation-steps)\n- [Blocks Mapping](#blocks-mapping)\n- [Pending Orders API](#pending-orders)\n\n---\n\n## Seller-Squid configuration information `sellerConfigInfo`\n\nSeller squid has custom API query for exposing seller configurations, treasure account, etc.\n\nImplementation of query resolver can be found [here](./src/server-extension/resolvers/sellerConfigInfo.ts).\n\nData types can be found [here](./src/server-extension/model/sellerConfigInfo.model.ts)\n\n- `isServiceOperational`: is squid processing new remarks from the blockchains and are PendingOrder\n  mutation calls are active and available.\n- `dmnRegPendingOrderExpTime`: expiration time of PendingOrder (milliseconds)\n- `domainHostChain`: blockchain name where domain will be registered (subsocial/soonsocial)\n- `domainHostChainPrefix`: blockchain prefix where domain will be registered\n- `domainRegistrationPriceFixed`: DEPRECATED\n- `remarkProtName`: SocialRemark protocol name, particular seller squid instance works with\n- `remarkProtVersion`: SocialRemark protocol version, particular seller squid instance works with\n- `sellerApiAuthTokenManager`: seller squid account `ED25519` public key, which should be used by\n  client app for signing auth token message (explained below)\n- `sellerChain`: blockchain name where payment will be done (polkadot/rococo)\n- `sellerChainPrefix`: blockchain prefix where payment will be done (polkadot/rococo)\n- `sellerToken `: details of token, which will be used for purchases in seller blockchain\n  - `decimal`\n  - `name`\n- `sellerTreasuryAccount`: seller blockchain account, which will receive payments\n\n---\n\n## Domain registration validation steps\n\nDomain registration handler contains vary of validations to prevent wrong or malicious actions.\n`DMN_REG` action processing flow has such validations as:\n\n1. _NO REFUND_ :: is remark valid - [here](src/parser/index.ts:25) - check SocialRemark `propName`, `version`, `destination`, `action`.\n2. _NO REFUND_ :: is transfer destination correct - [here](src/parser/utils/domainRegisterPayCall.ts:68) - transfer receiver must\n   be SellerTreasury\n3. _NO REFUND_ :: is DomainRegistrationOrder entity already existing in squid DB (prevent duplicated registration requests with the\n   same `opId` in SocialRemark) - [here](src/handlers/domain/register.ts:56)\n4. _WITH REFUND_ :: is transfer token valid - [here](src/handlers/domain/register.ts) - compare `token` from remark and allowed token in particular seller squid instance\n5. _WITH REFUND_ :: is transfer amount valid - [here](src/handlers/domain/register.ts) - amount must be equal or higher than domain price\n6. _WITH REFUND_ :: is `target` account valid - [here](src/handlers/domain/register.ts) - validate Substrate account\n7. _WITH REFUND_ :: is domain available at particular request point of time - [here](src/handlers/domain/register.ts) - storage request\n   to domain hosting chain (Subsocial/Soonsocial/xSocial) at specific block. As squid indexes different chain from domain\n   hosting chain (Polkadot/Rococo), squid makes call to domain hosting chain Subsquid archive which returns block hash by transfer chain timestamp.\n   More details in [Blocks mapping](#blocks-mapping)\n8. _WITH REFUND_ :: is domain TLD valid - [here](src/handlers/domain/register.ts) - storage call\n9. _WITH REFUND_ :: is domain name minimum length valid - [here](src/handlers/domain/register.ts) - `api.consts.domains.minDomainLength`\n10. _WITH REFUND_ :: is domain name maximum length valid - [here](src/handlers/domain/register.ts) - `api.consts.domains.maxDomainLength`\n11. _WITH REFUND_ :: is registration target not reached maximum number of owned domains - [here](src/handlers/domain/register.ts) - `api.query.domains.domainsByOwner`\n\n---\n\n## Blocks Mapping\n\nIn seller squid we need make relation between blocks of 2 different blockchains (`Chain#1 \u003c=\u003eChain#2`: Polkadot \u003c=\u003e Subsocial, Kusama \u003c=\u003e xSocial, etc.).\nIt's required to make correct storage calls at specific block of `Chain#2` when we know only block timestamp from `Chain#1`.\nFor this purposes we can make call directly to [Subsquid archive](https://docs.subsquid.io/archives/overview/) and search block by timestamp.\nAs Chain#1 and Chain#2 can have different time of block producing, this relation cannot be strict. That's why\nwe make search with `timestamp_gte` search parameter, which returns block with exact timestamp or most close younger block.\n\nYou can find implementation [here](src/multichainBlocksMapper/index.ts).\n\n- Subsocial archive GraphQL endpoint - https://subsocial.explorer.subsquid.io/graphql\n- Soonsocial archive GraphQL endpoint - https://soonsocial.explorer.subsquid.io/graphql\n- More public archives - https://app.subsquid.io/archives\n\n---\n\n## Pending Orders\n\nThe current Squid has custom API calls based on the main API which is based on [schema.graphql](./schema.graphql).\nCustom resolvers have been implemented based on [Subsquid's recommendations](https://docs.subsquid.io/graphql-api/custom-resolvers/).\n\n### PendingOrder Entity\n\nPending Orders must be available for all clients immediately after creation, so we cannot use\nthe native Squid Store functions and its native DB. This is because Squid saves data to the DB with just one transaction which commits at\nthe end of each batch. This provides a pretty big delay in the accessibility of custom data for retrieval by other clients.\n\nAs result, Pending Orders are managed by a [ServiceLocalStorage](./src/serviceLocalStorageClient/client.ts) client\nwhich is based on TypeORM + SQLite. It's a fast and lightweight solution.\n\n#### Structure Of The PendingOrder Entity\n\nThe source code can be found [here](./src/serviceLocalStorageClient/model/pendingOrder.ts).\n\n```typescript\nexport class PendingOrder {\n  constructor(props?: Partial\u003cPendingOrder\u003e) {\n    Object.assign(this, props);\n  }\n\n  @PrimaryColumn()\n  id!: string;\n\n  @Column()\n  timestamp!: Date;\n\n  @Column()\n  account!: string;\n\n  @Column()\n  clientId!: string;\n}\n```\n\n`account` and `clientId` are substrate account public keys, and for better compatibility these values\nare saved in the DB as a Hex value of the public key. API resolvers can receive `account` and `clientId` in any format and will\nautomatically convert them to the Hex format.\n\n---\n\n### PendingOrder API\n\n#### The Calls Authorization Flow\n\nAll mutation are protected by Authorization tokens. This is based on asymmetric encryption and the\n[Access Control feature](https://docs.subsquid.io/graphql-api/authorization/)\nfrom the Subsquid framework.\n\nThe Seller Squid, and each client application which works with the Squid's restricted API, have\ntheir own substrate accounts for encoding/decoding auth token message with the asymmetric encryption\napproach. Public and secret keys of these accounts are generated in the `ED25519` crypto type from mnemonic phrases.\nIt's **important** to understand, as in the Squid's whitelist (with the `Client-Id` header), that the encode/decode functions\nneed to use exactly the `ED25519` type of keys, but not `SR25519` keys, which we can find in the wallet dapp after the\ncreation process.\n\nFor access to restricted calls, a client application must be added to the clients whitelist in the\nSquid (the public key of the client's account in any format should be added to the whitelist).\nTo each mutation request the client app must add two headers:\n\n- `Authorization: Bearer \u003cSignedTokenMessage\u003e` - `SignedTokenMessage` is a current millisecond timestamp in the UTC timezone\n  that is turned into a string and encoded with the below encode function:\n\n```typescript\nimport { stringToU8a, u8aToHex } from '@polkadot/util';\nimport { naclSeal } from '@subsocial/utils';\n\nconst messageSigned = naclSeal(\n  stringToU8a('1680869251230'),\n  clientAccSecretKey,\n  sellerSquidAccPublicKey, // exposed in query \"sellerConfigInfo.sellerApiAuthTokenManager\"\n  nonce // hardcoded 24 bytes Uint8Array with value \"111\"\n);\n\nconst signedTokenMessage = u8aToHex(signedToken.sealed);\n```\n\nThe timestamp will be decoded in the Seller Squid and the token will be validated by a bunch of parameters,\nespecially by expiration time. If the timestamp of token is older than the expiration time (which is configured in the env variables\nof the Squid), then the mutation will be rejected as the token is expired.\n\n- `Client-Id` - The client application account's `ED25519` public key in any format, which will be used for\n  decoding of the token message that is in the `Authorization` header.\n\n```typescript\nconst decodedTimestamp = naclOpen(\n  hexToU8a(signedTokenMessage),\n  nonce, // hardcoded 24 bytes Uint8Array with value \"111\"\n  clientId,\n  sellerSquidAccSecretKey\n);\n```\n\n![Seller squid API auth flow](./docs/SellerSquidAuthFlow.png)\n\n---\n\nThe implementation of custom API calls can be found [here](./src/server-extension/resolvers/pendingOrders.ts).\n\nData types for custom API calls can be found [here](./src/server-extension/model/pendingOrder.model.ts)\n\nX-Seller has custom API queries/mutations such as:\n\n#### _Mutations_\n\n- `createPendingOrder(account, domain)` - create a PendingOrder entity. The new entity will have the\n  `clientId` field which will be automatically filled with the value from the `Client-Id` header of the mutation request.\n  - _account_ - the owner/initiator of the particular order;\n  - _domain_ - the domain name that was booked by `account`. This value will be used as a new unique ID value\n    for a new PendingOrder entity in the DB, so that creation of duplicate PendingOrder entities is\n    impossible.\n\n```graphql\nmutation CreatePendingOrder($domain: String!, $account: String!) {\n  createPendingOrder(account: $account, domain: $domain)\n}\n```\n\n- `deletePendingOrderById(id)` - delete a PendingOrder entity. The client application can delete only\n  those entities which were created by that particular client. Validation is based on the `Client-Id` header\n  and `clientId` field in each PendingOrder entity.\n  - _id_ - in practice, this is a domain name which has been booked, and its name has been used as the\n    id of a PendingOrder entity.\n\n```graphql\nmutation DeletePendingOrderById($id: String!) {\n  deletePendingOrderById(id: $id)\n}\n```\n\n---\n\n#### _Queries_\n\n- `getPendingOrdersByIds(ids)` - get PendingOrder entities by a list of ids (domain names).\n  - _ids_ - an array of the ids of entities === domain names\n\n```graphql\nquery GetPendingOrdersByIds($ids: [String!]!) {\n  getPendingOrdersByIds(ids: $ids) {\n    orders {\n      id\n      account\n      clientId\n      timestamp\n    }\n  }\n}\n```\n\n- `getPendingOrdersByAccount(account)` - get PendingOrder entities by account.\n  - _account_ - the substrate account public address of the owner of a PendingOrder entity.\n    This can be provided in any format, even in Hex.\n\n```graphql\nquery GetPendingOrdersByAccount($account: String!) {\n  getPendingOrdersByAccount(account: $account) {\n    orders {\n      id\n      account\n      clientId\n      timestamp\n    }\n  }\n}\n```\n\n---\n\n### Garbage collector for expired PendingOrders\n\nThe Seller Squid has implemented functionality for automatically removing all Pending Orders\nthat are older than the PendingOrder expiration time. The expiration time is configured in the Squid's new env variables\nin minutes, but exposed to clients via `sellerConfigInfo.dmnRegPendingOrderExpTime` in milliseconds.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdappforce%2Fx-seller-squid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdappforce%2Fx-seller-squid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdappforce%2Fx-seller-squid/lists"}