diff --git a/README.md b/README.md index f67b3e4..3cb956d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

webb-graphql

- A collection of GraphQL queries and clients for Webb's protocols. + A collection of GraphQL queries and clients for Tangle Network.

@@ -21,23 +21,24 @@
  • Installation
  • Usage
  • Contributing
  • -
  • License
  • +
  • Need help?
  • Getting Started

    -Webb-GraphQL contains packages for GraphQL queries and clients for our various applications and infrastructures. Namely GraphQL queries/client for Tangle MPC pallets and Webb's VAnchor private bridge application. +Tangle GraphQL contains packages for GraphQL queries and clients for our various applications and infrastructures. Namely GraphQL queries/client for Tangle Network pallets.

    Prerequisites

    Before you begin, ensure you have met the following requirements: -- You have installed the latest version of Node.js and npm. +- [NodeJS](https://nodejs.org/en/): A modern (e.g. the LTS version) installation of NodeJS. +- [Docker](https://www.docker.com/): The project will use Docker to run a local version of the SubQuery's node.

    Installation

    -To install Webb-GraphQL packages: +To install Tangle GraphQL packages: ```bash yarn install @@ -45,16 +46,39 @@ yarn install

    Usage

    -To use Webb-GraphQL, refer to the README.md of the package you are interested in. +To use Tangle GraphQL, refer to the README.md of the package you are interested in.

    Contributing

    -Interested in contributing to Webb-GraphQL? We appreciate your interest! We are always appreciative for contributions from the open-source community! +Interested in contributing to Tangle GraphQL? We appreciate your interest! We are always appreciative for contributions from the open-source community! If you have a contribution in mind, please check out our [Contribution Guide](./.github/CONTRIBUTING.md) for information on how to do so. We are excited for your first contribution! -

    License

    +

    Need help?

    -Licensed under GNU General Public License v3.0. +If you need help or you want to additional information please: -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the GNU General Public License v3.0 license, shall be licensed as above, without any additional terms or conditions. +- Refer to the [Tangle Network Official Documentation](https://docs.tangle.tools/) or [Webb Official Documentation](https://docs.webb.tools/). +- If you have feedback on how to improve the dApp interface or you have a specific question? Check out the [Tangle dApp Feedback Discussion](https://github.com/webb-tools/feedback/discussions/categories/tangle-dapp) or [Webb dApp Feedback Discussion](https://github.com/webb-tools/feedback/discussions/categories/webb-dapp-feedback). +- If you found a bug please [open an issue](https://github.com/webb-tools/webb-dapp/issues/new/choose) or [join our Discord](https://discord.gg/jUDeFpggrR) server to report it. + +--- + +**Follow us at** +[![Follow Tangle on twitter](https://img.shields.io/twitter/follow/tangle_network.svg?style=social)](https://twitter.com/intent/follow?screen_name=tangle_network) +[![Follow Webb on twitter](https://img.shields.io/twitter/follow/webbprotocol.svg?style=social)](https://twitter.com/intent/follow?screen_name=webbprotocol) +[![Follow Webb on LinkedIn](https://img.shields.io/badge/LinkedIn-webbprotocol-blue?style=flat&logo=linkedin&logoColor=b0c0c0&labelColor=363D44)](https://www.linkedin.com/company/webb-protocol/) + +--- + +**Share** the project link with your network on social media. + + + Share on LinkedIn + + + Shared on Twitter + + + Share on Telegram + diff --git a/packages/tangle-subql/README.md b/packages/tangle-subql/README.md index 0d77890..72f05ef 100644 --- a/packages/tangle-subql/README.md +++ b/packages/tangle-subql/README.md @@ -51,17 +51,19 @@ For this project, you can try to query with the following GraphQL code to get a ```graphql { query { - transfers { + operators { nodes { id - amount - blockNumber - date - from { - id - } - to { - id + currentStake + scheduledUnstakeAmount + joinedAt + lastUpdatedAt + statusHistory { + nodes { + id + status + blockNumber + } } } } diff --git a/packages/tangle-subql/project.ts b/packages/tangle-subql/project.ts index c5b8821..fc821cb 100644 --- a/packages/tangle-subql/project.ts +++ b/packages/tangle-subql/project.ts @@ -105,6 +105,86 @@ const project: SubstrateProject = { method: 'Created', }, }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleBlueprintCreated', + filter: { + module: 'services', + method: 'BlueprintCreated', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleJobCalled', + filter: { + module: 'services', + method: 'JobCalled', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleJobResultSubmitted', + filter: { + module: 'services', + method: 'JobResultSubmitted', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleRegistered', + filter: { + module: 'services', + method: 'Registered', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleServiceInitiated', + filter: { + module: 'services', + method: 'ServiceInitiated', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleServiceRequestApproved', + filter: { + module: 'services', + method: 'ServiceRequestApproved', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleServiceRequested', + filter: { + module: 'services', + method: 'ServiceRequested', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleServiceRequestRejected', + filter: { + module: 'services', + method: 'ServiceRequestRejected', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleServiceTerminated', + filter: { + module: 'services', + method: 'ServiceTerminated', + }, + }, + { + kind: SubstrateHandlerKind.Event, + handler: 'handleUnregistered', + filter: { + module: 'services', + method: 'Unregistered', + }, + }, ], }, }, diff --git a/packages/tangle-subql/schema.graphql b/packages/tangle-subql/schema.graphql index f248844..00f3d22 100644 --- a/packages/tangle-subql/schema.graphql +++ b/packages/tangle-subql/schema.graphql @@ -2,6 +2,61 @@ # Add the `@index` or `@index(unique: true)` annotation after any non-key field # https://academy.subquery.network/build/graphql.html#indexing-by-non-primary-key-field +""" +Represents an Account in the system. +""" +type Account @entity { + """ + Account's account ID, serves as the unique identifier. + """ + id: ID! + + """ + The Operator associated with this account. + """ + operator: Operator @derivedFrom(field: "account") + + """ + The Delegator associated with this account. + """ + delegator: Delegator @derivedFrom(field: "account") + + """ + List of Lst Pool Members associated with this account. + """ + lstPoolMembers: [LstPoolMember!]! @derivedFrom(field: "account") + + """ + List of Blueprints owned by this account. + """ + blueprints: [Blueprint!]! @derivedFrom(field: "owner") + + """ + List of Service Requests owned by this account. + """ + serviceRequests: [ServiceRequest!]! @derivedFrom(field: "owner") + + """ + List of Services owned by this account. + """ + services: [Service!]! @derivedFrom(field: "owner") + + """ + List of Job calls made by this account. + """ + jobCalls: [JobCall!]! @derivedFrom(field: "caller") + + """ + The block number when the account was created. + """ + createdAt: Int! + + """ + The block number of the last update to the account's information. + """ + lastUpdateAt: Int! +} + """ Represents an Operator in the system. """ @@ -11,6 +66,11 @@ type Operator @entity { """ id: ID! + """ + The account associated with this Operator. + """ + account: Account! + """ Current status of the Operator (ACTIVE, LEAVING, or OFFLINE). """ @@ -51,6 +111,26 @@ type Operator @entity { List of delegations made to this Operator. """ delegations: [Delegation!] @derivedFrom(field: "operator") + + """ + Total number of Blueprints registered by this Operator. + """ + totalBlueprints: Int! + + """ + Total number of Service Requests requested by this Operator. + """ + totalServiceRequests: Int! + + """ + Total number of Services operated by this Operator. + """ + totalServices: Int! + + """ + List of job results executed by this Operator. + """ + jobResults: [JobResult!]! @derivedFrom(field: "operator") } """ @@ -162,6 +242,11 @@ type Delegator @entity { """ id: ID! + """ + The account associated with this Delegator. + """ + account: Account! + """ List of deposits made by the Delegator. """ @@ -623,9 +708,9 @@ type LstPoolMember @entity { lstPool: LstPool! """ - The account ID of the member. + The account associated with this Lst Pool Member. """ - memberId: ID! + account: Account! """ The current stake amount of the member in the Lst Pool. @@ -643,7 +728,7 @@ Represents a change in the stake of a Lst Pool member. """ type MemberStakeChange @entity { """ - Unique identifier for the stake change. Composite key: LstPool ID + member ID + block number + Unique identifier for the stake change. Composite key: LstPool ID + account ID + block number """ id: ID! @@ -662,3 +747,278 @@ type MemberStakeChange @entity { """ blockNumber: Int! } + +""" +Blueprint entity: Represents service blueprints in the system +""" +type Blueprint @entity { + """ + Unique identifier for the Blueprint. + """ + id: ID! + + """ + The owner of the Blueprint. + """ + owner: Account! + + """ + The creation block number of the Blueprint. + """ + createdAt: Int! + + """ + List of Service Requests associated with this Blueprint. + """ + serviceRequests: [ServiceRequest!]! @derivedFrom(field: "blueprint") + + """ + List of Services associated with this Blueprint. + """ + services: [Service!]! @derivedFrom(field: "blueprint") +} + +""" +BlueprintOperator entity: Represents the relationship between blueprints and operators +""" +type BlueprintOperator @entity { + """ + Unique identifier for the relationship. Composite key: Blueprint ID + Operator ID + """ + id: ID! + + """ + The Blueprint associated with this relationship. + """ + blueprint: Blueprint! + + """ + The Operator associated with this relationship. + """ + operator: Operator! + + """ + The registration block number of the relationship. + """ + registeredAt: Int! + + """ + The last update block number of the relationship. + """ + updatedAt: Int! + + """ + Indicates whether the relationship is currently active. + """ + isActive: Boolean! +} + +""" +Represents a Service Request in the system. +""" +type ServiceRequest @entity { + """ + Unique identifier for the Service Request. + """ + id: ID! + + """ + The owner of the Service Request. + """ + owner: Account! + + """ + The blueprint associated with this Service Request. + """ + blueprint: Blueprint! + + """ + List of asset IDs associated with this Service Request. + """ + assetIds: [BigInt!]! + + """ + The Service associated with this Service Request. + """ + service: Service @derivedFrom(field: "serviceRequest") + + """ + The creation block number of the Service Request. + """ + createdAt: Int! +} + +""" +Represents a relationship between a Service Request and an Operator. +""" +type ServiceRequestOperator @entity { + """ + Unique identifier for the relationship. Composite key: Service Request ID + Operator ID + """ + id: ID! + + """ + The Service Request associated with this relationship. + """ + serviceRequest: ServiceRequest! + + """ + The Operator associated with this relationship. + """ + operator: Operator! + + """ + The approval block number. + """ + approvedAt: Int + + """ + The rejection block number. + """ + rejectedAt: Int + + """ + The creation block number of the relationship. + """ + createdAt: Int! +} + +""" +Represents a Service in the system. +""" +type Service @entity { + """ + Unique identifier for the Service. + """ + id: ID! + + """ + The Service Request associated with this Service. + """ + serviceRequest: ServiceRequest! + + """ + The owner of the Service. + """ + owner: Account! + + """ + List of asset IDs associated with this Service. + """ + assetIds: [BigInt!]! + + """ + The blueprint associated with this Service. + """ + blueprint: Blueprint! + + """ + List of job calls associated with this Service. + """ + jobCalls: [JobCall!]! @derivedFrom(field: "service") + + """ + List of job results associated with this Service. + """ + jobResults: [JobResult!]! @derivedFrom(field: "service") + + """ + The creation block number of the Service. + """ + createdAt: Int! + + """ + The block number when the Service was terminated. + """ + terminatedAt: Int +} + +""" +Represents a relationship between a Service and an Operator. +""" +type ServiceOperator @entity { + """ + Unique identifier for the relationship. Composite key: Service ID + Operator ID + """ + id: ID! + + """ + The Service associated with this relationship. + """ + service: Service! + + """ + The Operator associated with this relationship. + """ + operator: Operator! + + """ + The creation block number of the relationship. + """ + createdAt: Int! +} + +""" +Represents a Job Call in the system. +""" +type JobCall @entity { + """ + Unique identifier for the Job Call. + """ + id: ID! + + """ + The caller of the Job. + """ + caller: Account! + + """ + The Service associated with this Job Call. + """ + service: Service! + + """ + The job ID of the Job Call. + """ + jobId: Int! + + """ + The job result associated with this Job Call. + """ + jobResult: JobResult @derivedFrom(field: "jobCall") + + """ + The creation block number of the Job Call. + """ + createdAt: Int! +} + +""" +Represents a Job Result in the system. +""" +type JobResult @entity { + """ + Unique identifier for the Job Result. Composite key: Service ID + Job Call ID + """ + id: ID! + + """ + The Job Call associated with this Job Result. + """ + jobCall: JobCall! + + """ + The Service associated with this Job Result. + """ + service: Service! + + """ + The operator that executed the Job. + """ + operator: Operator! + + """ + The creation block number of the Job Result. + """ + createdAt: Int! +} diff --git a/packages/tangle-subql/src/mappings/lst/events/handleBonded.ts b/packages/tangle-subql/src/mappings/lst/events/handleBonded.ts index b4e20dc..13402af 100644 --- a/packages/tangle-subql/src/mappings/lst/events/handleBonded.ts +++ b/packages/tangle-subql/src/mappings/lst/events/handleBonded.ts @@ -8,6 +8,7 @@ import { LstPoolState, MemberStakeChange, } from '../../../types'; +import ensureAccount from '../../../utils/ensureAccount'; export default async function handleBonded( event: SubstrateEvent< @@ -17,6 +18,9 @@ export default async function handleBonded( const [member, poolId, bonded, joined] = event.event.data; const blockNumber = event.block.block.header.number.toNumber(); + const account = await ensureAccount(member.toString(), blockNumber); + account.lastUpdateAt = blockNumber; + const pool = (await LstPool.get(poolId.toString())) ?? LstPool.create({ @@ -29,15 +33,15 @@ export default async function handleBonded( if (joined.isTrue) { const poolMember = LstPoolMember.create({ - id: `${poolId.toString()}-${member.toString()}`, + id: `${poolId.toString()}-${account.id}`, lstPoolId: poolId.toString(), - memberId: member.toString(), + accountId: account.id, currentStake: bonded.toBigInt(), }); const stakeChange = MemberStakeChange.create({ - id: `${poolId.toString()}-${member.toString()}-${blockNumber}`, - memberId: member.toString(), + id: `${poolMember.id}-${blockNumber}`, + memberId: poolMember.id, amount: bonded.toBigInt(), blockNumber, }); @@ -45,7 +49,7 @@ export default async function handleBonded( await Promise.all([poolMember.save(), stakeChange.save()]); } else { const poolMember = await LstPoolMember.get( - `${poolId.toString()}-${member.toString()}`, + `${poolId.toString()}-${account.id}`, ); assert(poolMember, 'Pool member not found'); @@ -53,12 +57,12 @@ export default async function handleBonded( poolMember.currentStake = bonded.toBigInt(); const stakeChange = MemberStakeChange.create({ - id: `${poolId.toString()}-${member.toString()}-${blockNumber}`, - memberId: member.toString(), + id: `${poolMember.id}-${blockNumber}`, + memberId: poolMember.id, amount: bonded.toBigInt(), blockNumber, }); - await Promise.all([poolMember.save(), stakeChange.save()]); + await Promise.all([account.save(), poolMember.save(), stakeChange.save()]); } } diff --git a/packages/tangle-subql/src/mappings/lst/events/handleUnbonded.ts b/packages/tangle-subql/src/mappings/lst/events/handleUnbonded.ts index 1094943..7596d0a 100644 --- a/packages/tangle-subql/src/mappings/lst/events/handleUnbonded.ts +++ b/packages/tangle-subql/src/mappings/lst/events/handleUnbonded.ts @@ -3,6 +3,7 @@ import { AccountId32 } from '@polkadot/types/interfaces'; import { SubstrateEvent } from '@subql/types'; import assert from 'assert'; import { LstPool, LstPoolMember, MemberStakeChange } from '../../../types'; +import getAndAssertAccount from '../../../utils/getAndAssertAccount'; export default async function handleUnbonded( event: SubstrateEvent< @@ -21,17 +22,23 @@ export default async function handleUnbonded( const poolMember = await LstPoolMember.get( `${poolId.toString()}-${member.toString()}`, ); - assert(poolMember, 'Pool member not found'); + const account = await getAndAssertAccount(poolMember.accountId, blockNumber); + poolMember.currentStake -= balance.toBigInt(); const stakeChange = MemberStakeChange.create({ - id: `${poolId.toString()}-${member.toString()}-${blockNumber}`, - memberId: member.toString(), + id: `${poolMember.id}-${blockNumber}`, + memberId: poolMember.id, amount: -balance.toBigInt(), blockNumber, }); - await Promise.all([pool.save(), poolMember.save(), stakeChange.save()]); + await Promise.all([ + account.save(), + pool.save(), + poolMember.save(), + stakeChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/mappingHandlers.ts b/packages/tangle-subql/src/mappings/mappingHandlers.ts index 3540c44..e6db486 100644 --- a/packages/tangle-subql/src/mappings/mappingHandlers.ts +++ b/packages/tangle-subql/src/mappings/mappingHandlers.ts @@ -165,3 +165,14 @@ export async function handleLstCreated(event: SubstrateEvent): Promise { event as SubstrateEvent<[depositor: AccountId32, poolId: u32]>, ); } + +export { default as handleBlueprintCreated } from './services/events/handleBlueprintCreated'; +export { default as handleJobCalled } from './services/events/handleJobCalled'; +export { default as handleJobResultSubmitted } from './services/events/handleJobResultSubmitted'; +export { default as handleRegistered } from './services/events/handleRegistered'; +export { default as handleServiceInitiated } from './services/events/handleServiceInitiated'; +export { default as handleServiceRequestApproved } from './services/events/handleServiceRequestApproved'; +export { default as handleServiceRequested } from './services/events/handleServiceRequested'; +export { default as handleServiceRequestRejected } from './services/events/handleServiceRequestRejected'; +export { default as handleServiceTerminated } from './services/events/handleServiceTerminated'; +export { default as handleUnregistered } from './services/events/handleUnregistered'; diff --git a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelUnstake.ts b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelUnstake.ts index 18261a9..6550f63 100644 --- a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelUnstake.ts +++ b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelUnstake.ts @@ -9,6 +9,7 @@ import { UnstakeRequestHistory, UnstakeRequestStatus, } from '../../../../types'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleCancelUnstake( @@ -17,14 +18,20 @@ export default async function handleCancelUnstake( >, ) { const { signer, blockNumber } = getExtrinsicInfo(extrinsic); - const [operatorAccount, assetId, amount] = extrinsic.extrinsic.args; + const [operator, assetId, amount] = extrinsic.extrinsic.args; const delegator = await Delegator.get(signer); assert(delegator, `Delegator with ID ${signer} not found`); - delegator.lastUpdateAt = blockNumber; - const delegationId = `${delegator.id}-${operatorAccount.toString()}-${assetId.toString()}`; + const account = await getAndAssertAccount(delegator.accountId, blockNumber); + + const operatorAccount = await getAndAssertAccount( + operator.toString(), + blockNumber, + ); + + const delegationId = `${account.id}-${operatorAccount.id}-${assetId.toString()}`; const delegation = await Delegation.get(delegationId); assert(delegation, `Delegation with ID ${delegationId} not found`); @@ -49,6 +56,7 @@ export default async function handleCancelUnstake( } await Promise.all([ + account.save(), delegator.save(), unstakeRequest.save(), unstakeRequestHistory.save(), diff --git a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelWithdraw.ts b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelWithdraw.ts index 32cb334..21cd535 100644 --- a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelWithdraw.ts +++ b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleCancelWithdraw.ts @@ -8,6 +8,7 @@ import { WithdrawRequestHistory, WithdrawRequestStatus, } from '../../../../types'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleCancelWithdraw( @@ -18,9 +19,10 @@ export default async function handleCancelWithdraw( const delegator = await Delegator.get(signer); assert(delegator, `Delegator with ID ${signer} not found`); - delegator.lastUpdateAt = blockNumber; + const account = await getAndAssertAccount(delegator.accountId, blockNumber); + const depositId = `${delegator.id}-${assetId.toString()}`; const deposit = await Deposit.get(depositId); assert(deposit, `Deposit with ID ${depositId} not found`); @@ -49,6 +51,7 @@ export default async function handleCancelWithdraw( } await Promise.all([ + account.save(), delegator.save(), scheduledRequest.save(), requestHistory.save(), diff --git a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDelegate.ts b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDelegate.ts index d260552..8e8bc60 100644 --- a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDelegate.ts +++ b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDelegate.ts @@ -9,6 +9,7 @@ import { Operator, } from '../../../../types'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; export default async function handleDelegate( extrinsic: SubstrateExtrinsic< @@ -16,17 +17,27 @@ export default async function handleDelegate( >, ) { const { signer, blockNumber } = getExtrinsicInfo(extrinsic); - const [operatorAccount, assetId, amount] = extrinsic.extrinsic.args; + const [operatorAccountId, assetId, amount] = extrinsic.extrinsic.args; const delegator = await Delegator.get(signer); assert(delegator, `Delegator with ID ${signer} not found`); - delegator.lastUpdateAt = blockNumber; - const operator = await Operator.get(operatorAccount.toString()); - assert(operator, `Operator with ID ${operatorAccount.toString()} not found`); + const delegatorAccount = await getAndAssertAccount( + delegator.accountId, + blockNumber, + ); + + const operator = await Operator.get(operatorAccountId.toString()); + assert( + operator, + `Operator with ID ${operatorAccountId.toString()} not found`, + ); + operator.lastUpdateAt = blockNumber; + + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); - const delegationId = `${delegator.id}-${operator.id}-${assetId.toString()}`; + const delegationId = `${delegatorAccount.id}-${operatorAccount.id}-${assetId.toString()}`; let delegation = await Delegation.get(delegationId); if (delegation === undefined) { @@ -49,5 +60,12 @@ export default async function handleDelegate( blockNumber, }); - await Promise.all([delegator.save(), delegation.save(), history.save()]); + await Promise.all([ + delegator.save(), + delegatorAccount.save(), + operator.save(), + operatorAccount.save(), + delegation.save(), + history.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDeposit.ts b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDeposit.ts index 257f4ab..328d091 100644 --- a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDeposit.ts +++ b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleDeposit.ts @@ -2,6 +2,7 @@ import { u128 } from '@polkadot/types'; import { SubstrateExtrinsic } from '@subql/types'; import { Delegator, Deposit, DepositHistory } from '../../../../types'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import ensureAccount from '../../../../utils/ensureAccount'; export default async function handleDeposit( extrinsic: SubstrateExtrinsic<[assetId: u128, amount: u128]>, @@ -9,12 +10,15 @@ export default async function handleDeposit( const { signer, blockNumber } = getExtrinsicInfo(extrinsic); const [assetId, amount] = extrinsic.extrinsic.args; + const account = await ensureAccount(signer, blockNumber); + let delegator = await Delegator.get(signer); if (delegator === undefined) { delegator = Delegator.create({ id: signer, joinedAt: blockNumber, lastUpdateAt: blockNumber, + accountId: account.id, }); } else { delegator.lastUpdateAt = blockNumber; @@ -42,5 +46,12 @@ export default async function handleDeposit( blockNumber, }); - await Promise.all([delegator.save(), deposit.save(), depositHistory.save()]); + account.lastUpdateAt = blockNumber; + + await Promise.all([ + delegator.save(), + deposit.save(), + depositHistory.save(), + account.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleUnstake.ts b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleUnstake.ts index 12c25f7..d52bfff 100644 --- a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleUnstake.ts +++ b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleUnstake.ts @@ -11,6 +11,7 @@ import { UnstakeRequestStatus, } from '../../../../types'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; export default async function handleScheduleUnstake( extrinsic: SubstrateExtrinsic< @@ -18,17 +19,27 @@ export default async function handleScheduleUnstake( >, ) { const { signer, blockNumber } = getExtrinsicInfo(extrinsic); - const [operatorAccount, assetId, amount] = extrinsic.extrinsic.args; + const [operatorAccountId, assetId, amount] = extrinsic.extrinsic.args; const delegator = await Delegator.get(signer); assert(delegator, `Delegator with ID ${signer} not found`); - delegator.lastUpdateAt = blockNumber; - const operator = await Operator.get(operatorAccount.toString()); - assert(operator, `Operator with ID ${operatorAccount.toString()} not found`); + const account = await getAndAssertAccount(delegator.accountId, blockNumber); + + const operator = await Operator.get(operatorAccountId.toString()); + assert( + operator, + `Operator with ID ${operatorAccountId.toString()} not found`, + ); + operator.lastUpdateAt = blockNumber; + + const operatorAccount = await getAndAssertAccount( + operatorAccountId.toString(), + blockNumber, + ); - const delegationId = `${delegator.id}-${operator.id}-${assetId.toString()}`; + const delegationId = `${account.id}-${operator.id}-${assetId.toString()}`; const delegation = await Delegation.get(delegationId); assert(delegation, `Delegation with ID ${delegationId} not found`); @@ -66,6 +77,9 @@ export default async function handleScheduleUnstake( await Promise.all([ delegator.save(), + account.save(), + operator.save(), + operatorAccount.save(), delegation.save(), unstakeRequest.save(), unstakeRequestHistory.save(), diff --git a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleWithdraw.ts b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleWithdraw.ts index ef5b4b9..2585524 100644 --- a/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleWithdraw.ts +++ b/packages/tangle-subql/src/mappings/restake/delegators/calls/handleScheduleWithdraw.ts @@ -9,6 +9,7 @@ import { WithdrawRequestStatus, } from '../../../../types'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; export default async function handleScheduleWithdraw( extrinsic: SubstrateExtrinsic<[assetId: u128, amount: u128]>, @@ -18,10 +19,11 @@ export default async function handleScheduleWithdraw( const delegator = await Delegator.get(signer); assert(delegator, `Delegator with ID ${signer} not found`); - delegator.lastUpdateAt = blockNumber; - const depositId = `${delegator.id}-${assetId.toString()}`; + const account = await getAndAssertAccount(delegator.accountId, blockNumber); + + const depositId = `${account.id}-${assetId.toString()}`; const deposit = await Deposit.get(depositId); assert(deposit, `Deposit with ID ${depositId} not found`); @@ -61,6 +63,7 @@ export default async function handleScheduleWithdraw( // Save all entities await Promise.all([ delegator.save(), + account.save(), request.save(), requestHistory.save(), deposit.save(), diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelLeaveOperators.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelLeaveOperators.ts index c3e583f..781e472 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelLeaveOperators.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelLeaveOperators.ts @@ -2,6 +2,7 @@ import { SubstrateExtrinsic } from '@subql/types'; import assert from 'assert'; import { Operator, OperatorStatus } from '../../../../types'; import createOperatorStatusChange from '../../../../utils/createOperatorStatusChange'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleCancelLeaveOperators( @@ -16,6 +17,8 @@ export default async function handleCancelLeaveOperators( operator.currentStatus = OperatorStatus.ACTIVE; operator.lastUpdateAt = blockNumber; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const statusChange = createOperatorStatusChange( signer, blockNumber, @@ -23,5 +26,9 @@ export default async function handleCancelLeaveOperators( OperatorStatus.ACTIVE, ); - await Promise.all([operator.save(), statusChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + statusChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelOperatorUnstake.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelOperatorUnstake.ts index b9e3c6e..af02ba9 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelOperatorUnstake.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleCancelOperatorUnstake.ts @@ -5,6 +5,7 @@ import { Operator, OperatorBondChange, } from '../../../../types'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleCancelOperatorUnstake( @@ -20,6 +21,8 @@ export default async function handleCancelOperatorUnstake( operator.lastUpdateAt = blockNumber; operator.scheduledUnstakeAmount = undefined; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const bondChange = OperatorBondChange.create({ id: `${signer}-${blockNumber}`, action: BondChangeAction.DECREASE_CANCELLED, @@ -28,5 +31,9 @@ export default async function handleCancelOperatorUnstake( amount: unstakeAmount, }); - await Promise.all([operator.save(), bondChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + bondChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteLeaveOperators.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteLeaveOperators.ts index 287c19e..71d2663 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteLeaveOperators.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteLeaveOperators.ts @@ -7,6 +7,7 @@ import { OperatorStatus, } from '../../../../types'; import createOperatorStatusChange from '../../../../utils/createOperatorStatusChange'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleExecuteLeaveOperators( @@ -21,6 +22,8 @@ export default async function handleExecuteLeaveOperators( operator.currentStatus = OperatorStatus.OFFLINE; operator.lastUpdateAt = blockNumber; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const statusChange = createOperatorStatusChange( signer, blockNumber, @@ -36,5 +39,10 @@ export default async function handleExecuteLeaveOperators( operatorId: signer, }); - await Promise.all([operator.save(), statusChange.save(), bondChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + statusChange.save(), + bondChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteOperatorUnstake.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteOperatorUnstake.ts index 3258621..526403d 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteOperatorUnstake.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleExecuteOperatorUnstake.ts @@ -5,6 +5,7 @@ import { Operator, OperatorBondChange, } from '../../../../types'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleExecuteOperatorUnstake( @@ -22,6 +23,8 @@ export default async function handleExecuteOperatorUnstake( operator.currentStake -= unstakeAmount; operator.scheduledUnstakeAmount = undefined; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const bondChange = OperatorBondChange.create({ id: `${signer}-${blockNumber}`, action: BondChangeAction.DECREASE_EXECUTED, @@ -30,5 +33,9 @@ export default async function handleExecuteOperatorUnstake( amount: unstakeAmount, }); - await Promise.all([operator.save(), bondChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + bondChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOffline.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOffline.ts index 48aed70..73e3c4b 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOffline.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOffline.ts @@ -3,6 +3,7 @@ import assert from 'assert'; import { Operator, OperatorStatus } from '../../../../types'; import createOperatorStatusChange from '../../../../utils/createOperatorStatusChange'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; export default async function handleGoOffline(extrinsic: SubstrateExtrinsic) { const { signer, blockNumber } = getExtrinsicInfo(extrinsic); @@ -14,6 +15,8 @@ export default async function handleGoOffline(extrinsic: SubstrateExtrinsic) { operator.currentStatus = OperatorStatus.OFFLINE; operator.lastUpdateAt = blockNumber; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const statusChange = createOperatorStatusChange( signer, blockNumber, @@ -21,5 +24,9 @@ export default async function handleGoOffline(extrinsic: SubstrateExtrinsic) { OperatorStatus.OFFLINE, ); - await Promise.all([operator.save(), statusChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + statusChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOnline.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOnline.ts index c1c23f7..303ee14 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOnline.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleGoOnline.ts @@ -3,6 +3,7 @@ import assert from 'assert'; import { Operator, OperatorStatus } from '../../../../types'; import createOperatorStatusChange from '../../../../utils/createOperatorStatusChange'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; export default async function handleGoOnline(extrinsic: SubstrateExtrinsic) { const { signer, blockNumber } = getExtrinsicInfo(extrinsic); @@ -14,6 +15,8 @@ export default async function handleGoOnline(extrinsic: SubstrateExtrinsic) { operator.currentStatus = OperatorStatus.ACTIVE; operator.lastUpdateAt = blockNumber; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const statusChange = createOperatorStatusChange( signer, blockNumber, @@ -21,5 +24,9 @@ export default async function handleGoOnline(extrinsic: SubstrateExtrinsic) { OperatorStatus.ACTIVE, ); - await Promise.all([operator.save(), statusChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + statusChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleJoinOperators.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleJoinOperators.ts index 8371c25..2b5eb9a 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleJoinOperators.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleJoinOperators.ts @@ -9,6 +9,7 @@ import { } from '../../../../types'; import createOperatorStatusChange from '../../../../utils/createOperatorStatusChange'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import ensureAccount from '../../../../utils/ensureAccount'; export default async function handleJoinOperators( extrinsic: SubstrateExtrinsic<[bondAmount: u128]>, @@ -42,14 +43,20 @@ async function createOrUpdateOperator( bondAmount: bigint, existingOperator?: Operator, ) { + const account = await ensureAccount(signer, blockNumber); + const operator = existingOperator ?? Operator.create({ id: signer, + accountId: account.id, currentStake: bondAmount, joinedAt: blockNumber, currentStatus: OperatorStatus.ACTIVE, lastUpdateAt: blockNumber, + totalBlueprints: 0, + totalServiceRequests: 0, + totalServices: 0, }); // If the operator already exists, update their status to ACTIVE, @@ -74,7 +81,14 @@ async function createOrUpdateOperator( operator.id, ); - await Promise.all([operator.save(), statusChange.save(), bondChange.save()]); + account.lastUpdateAt = blockNumber; + + await Promise.all([ + operator.save(), + statusChange.save(), + bondChange.save(), + account.save(), + ]); return operator; } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleOperatorBondMore.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleOperatorBondMore.ts index 3f01608..264b145 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleOperatorBondMore.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleOperatorBondMore.ts @@ -7,6 +7,7 @@ import { OperatorBondChange, } from '../../../../types'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; export default async function handleOperatorBondMore( extrinsic: SubstrateExtrinsic<[additionalBond: u128]>, @@ -21,6 +22,8 @@ export default async function handleOperatorBondMore( operator.currentStake += additionalBond.toBigInt(); operator.lastUpdateAt = blockNumber; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const bondChange = OperatorBondChange.create({ id: `${signer}-${blockNumber}`, action: BondChangeAction.INCREASE, @@ -29,5 +32,9 @@ export default async function handleOperatorBondMore( amount: additionalBond.toBigInt(), }); - await Promise.all([operator.save(), bondChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + bondChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleLeaveOperators.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleLeaveOperators.ts index 2470c39..6375c6b 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleLeaveOperators.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleLeaveOperators.ts @@ -2,6 +2,7 @@ import { SubstrateExtrinsic } from '@subql/types'; import assert from 'assert'; import { Operator, OperatorStatus } from '../../../../types'; import createOperatorStatusChange from '../../../../utils/createOperatorStatusChange'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleScheduleLeaveOperators( @@ -16,6 +17,8 @@ export default async function handleScheduleLeaveOperators( operator.currentStatus = OperatorStatus.LEAVING; operator.lastUpdateAt = blockNumber; + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const statusChange = createOperatorStatusChange( signer, blockNumber, @@ -23,5 +26,9 @@ export default async function handleScheduleLeaveOperators( OperatorStatus.LEAVING, ); - await Promise.all([operator.save(), statusChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + statusChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleOperatorUnstake.ts b/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleOperatorUnstake.ts index e3a66e3..716345c 100644 --- a/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleOperatorUnstake.ts +++ b/packages/tangle-subql/src/mappings/restake/operators/calls/handleScheduleOperatorUnstake.ts @@ -6,6 +6,7 @@ import { Operator, OperatorBondChange, } from '../../../../types'; +import getAndAssertAccount from '../../../../utils/getAndAssertAccount'; import getExtrinsicInfo from '../../../../utils/getExtrinsicInfo'; export default async function handleScheduleOperatorUnstake( @@ -21,6 +22,8 @@ export default async function handleScheduleOperatorUnstake( operator.lastUpdateAt = blockNumber; operator.scheduledUnstakeAmount = unstakeAmount.toBigInt(); + const operatorAccount = await getAndAssertAccount(operator.id, blockNumber); + const bondChange = OperatorBondChange.create({ id: `${signer}-${blockNumber}`, action: BondChangeAction.DECREASE_SCHEDULED, @@ -29,5 +32,9 @@ export default async function handleScheduleOperatorUnstake( amount: unstakeAmount.toBigInt(), }); - await Promise.all([operator.save(), bondChange.save()]); + await Promise.all([ + operator.save(), + operatorAccount.save(), + bondChange.save(), + ]); } diff --git a/packages/tangle-subql/src/mappings/services/events/handleBlueprintCreated.ts b/packages/tangle-subql/src/mappings/services/events/handleBlueprintCreated.ts new file mode 100644 index 0000000..7bf3709 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleBlueprintCreated.ts @@ -0,0 +1,24 @@ +import { u64 } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import { Blueprint } from '../../../types'; +import ensureAccount from '../../../utils/ensureAccount'; + +export default async function handleBlueprintCreated( + event: SubstrateEvent<[owner: AccountId32, blueprintId: u64]>, +) { + const [owner, blueprintId] = event.event.data; + const blockNumber = event.block.block.header.number.toNumber(); + + const account = await ensureAccount(owner.toString(), blockNumber); + + const blueprint = Blueprint.create({ + id: blueprintId.toString(), + ownerId: account.id, + createdAt: blockNumber, + }); + + account.lastUpdateAt = blockNumber; + + await Promise.all([blueprint.save(), account.save()]); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleJobCalled.ts b/packages/tangle-subql/src/mappings/services/events/handleJobCalled.ts new file mode 100644 index 0000000..90164b3 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleJobCalled.ts @@ -0,0 +1,38 @@ +import { u64, u8, Vec } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { TanglePrimitivesServicesField } from '@polkadot/types/lookup'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { JobCall, Service } from '../../../types'; +import ensureAccount from '../../../utils/ensureAccount'; + +export default async function handleJobCalled( + event: SubstrateEvent< + [ + caller: AccountId32, + serviceId: u64, + callId: u64, + job: u8, + args: Vec, + ] + >, +) { + const [caller, serviceId, callId, job] = event.event.data; + const blockNumber = event.block.block.header.number.toNumber(); + + const account = await ensureAccount(caller.toString(), blockNumber); + account.lastUpdateAt = blockNumber; + + const service = await Service.get(serviceId.toString()); + assert(service, `Service ${serviceId} not found`); + + const jobCall = JobCall.create({ + id: callId.toString(), + callerId: account.id, + createdAt: blockNumber, + serviceId: service.id, + jobId: job.toNumber(), + }); + + await Promise.all([jobCall.save(), account.save()]); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleJobResultSubmitted.ts b/packages/tangle-subql/src/mappings/services/events/handleJobResultSubmitted.ts new file mode 100644 index 0000000..a5b3a45 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleJobResultSubmitted.ts @@ -0,0 +1,43 @@ +import { u64, u8, Vec } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { TanglePrimitivesServicesField } from '@polkadot/types/lookup'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { JobCall, JobResult, Service } from '../../../types'; +import getAndAssertAccount from '../../../utils/getAndAssertAccount'; + +export default async function handleJobResultSubmitted( + event: SubstrateEvent< + [ + operator: AccountId32, + serviceId: u64, + callId: u64, + job: u8, + result: Vec, + ] + >, +) { + const [operator, serviceId, callId] = event.event.data; + const blockNumber = event.block.block.header.number.toNumber(); + + const operatorAccount = await getAndAssertAccount( + operator.toString(), + blockNumber, + ); + + const service = await Service.get(serviceId.toString()); + assert(service, `Service ${serviceId} not found`); + + const jobCall = await JobCall.get(callId.toString()); + assert(jobCall, `Job call ${callId} not found`); + + const jobResult = JobResult.create({ + id: `${service.id}-${jobCall.id}`, + jobCallId: jobCall.id, + serviceId: service.id, + operatorId: operatorAccount.id, + createdAt: blockNumber, + }); + + await Promise.all([jobResult.save(), operatorAccount.save()]); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleRegistered.ts b/packages/tangle-subql/src/mappings/services/events/handleRegistered.ts new file mode 100644 index 0000000..3760db1 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleRegistered.ts @@ -0,0 +1,56 @@ +import assert from 'assert'; +import { u64, Vec } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { + TanglePrimitivesServicesField, + TanglePrimitivesServicesOperatorPreferences, +} from '@polkadot/types/lookup'; +import { SubstrateEvent } from '@subql/types'; +import { + Account, + Blueprint, + BlueprintOperator, + Operator, +} from '../../../types'; + +export default async function handleRegistered( + event: SubstrateEvent< + [ + provider: AccountId32, + blueprintId: u64, + preferences: TanglePrimitivesServicesOperatorPreferences, + registrationArgs: Vec, + ] + >, +) { + const [provider, blueprintId] = event.event.data; + + const blockNumber = event.block.block.header.number.toNumber(); + + const operator = await Operator.get(provider.toString()); + assert(operator, 'Operator not found'); + + const account = await Account.get(operator.accountId); + assert(account, 'Account not found'); + + const blueprint = await Blueprint.get(blueprintId.toString()); + assert(blueprint, 'Blueprint not found'); + + operator.totalBlueprints += 1; + account.lastUpdateAt = blockNumber; + + const blueprintOperator = BlueprintOperator.create({ + id: `${blueprint.id}-${operator.id}`, + blueprintId: blueprint.id, + operatorId: operator.id, + registeredAt: blockNumber, + updatedAt: blockNumber, + isActive: true, + }); + + await Promise.all([ + blueprintOperator.save(), + operator.save(), + account.save(), + ]); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleServiceInitiated.ts b/packages/tangle-subql/src/mappings/services/events/handleServiceInitiated.ts new file mode 100644 index 0000000..200806f --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleServiceInitiated.ts @@ -0,0 +1,70 @@ +import { Option, u128, u64, Vec } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { + Blueprint, + Operator, + Service, + ServiceOperator, + ServiceRequest, + ServiceRequestOperator, +} from '../../../types'; +import getAndAssertAccount from '../../../utils/getAndAssertAccount'; + +export default async function handleServiceInitiated( + event: SubstrateEvent< + [ + owner: AccountId32, + requestId: Option, + serviceId: u64, + blueprintId: u64, + assets: Vec, + ] + >, +) { + const [owner, requestId, serviceId, blueprintId, assets] = event.event.data; + const blockNumber = event.block.block.header.number.toNumber(); + + const ownerAccount = await getAndAssertAccount(owner.toString(), blockNumber); + + const blueprint = await Blueprint.get(blueprintId.toString()); + assert(blueprint, 'Blueprint not found'); + + const serviceRequest = await ServiceRequest.get(requestId.toString()); + assert(serviceRequest, 'ServiceRequest not found'); + + const service = Service.create({ + id: serviceId.toString(), + serviceRequestId: serviceRequest.id, + ownerId: ownerAccount.id, + assetIds: assets.map((asset) => asset.toBigInt()), + blueprintId: blueprint.id, + createdAt: blockNumber, + }); + + await service.save(); + + const serviceRequestOperators = + (await ServiceRequestOperator.getByServiceRequestId(serviceRequest.id)) ?? + []; + + for (const serviceRequestOperator of serviceRequestOperators) { + const operator = await Operator.get(serviceRequestOperator.operatorId); + if (operator === undefined) { + logger.error(`Operator ${serviceRequestOperator.operatorId} not found`); + continue; + } + + operator.totalServices += 1; + + const serviceOperator = ServiceOperator.create({ + id: `${service.id}-${operator.id}`, + serviceId: service.id, + operatorId: operator.id, + createdAt: blockNumber, + }); + + await Promise.all([serviceOperator.save(), operator.save()]); + } +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleServiceRequestApproved.ts b/packages/tangle-subql/src/mappings/services/events/handleServiceRequestApproved.ts new file mode 100644 index 0000000..21971b3 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleServiceRequestApproved.ts @@ -0,0 +1,38 @@ +import { u64, Vec } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { Operator, ServiceRequestOperator } from '../../../types'; +import getAndAssertAccount from '../../../utils/getAndAssertAccount'; + +export default async function handleServiceRequestApproved( + event: SubstrateEvent< + [ + operator: AccountId32, + requestId: u64, + blueprintId: u64, + pendingApprovals: Vec, + approved: Vec, + ] + >, +) { + const [operatorAccountId, requestId] = event.event.data; + + const blockNumber = event.block.block.header.number.toNumber(); + + const operatorAccount = await getAndAssertAccount( + operatorAccountId.toString(), + blockNumber, + ); + + const operator = await Operator.get(operatorAccount.id); + assert(operator, 'Operator not found'); + + const serviceRequestOperator = await ServiceRequestOperator.get( + `${requestId}-${operator.id}`, + ); + assert(serviceRequestOperator, 'ServiceRequestOperator not found'); + serviceRequestOperator.approvedAt = blockNumber; + + await Promise.all([operatorAccount.save(), serviceRequestOperator.save()]); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleServiceRequestRejected.ts b/packages/tangle-subql/src/mappings/services/events/handleServiceRequestRejected.ts new file mode 100644 index 0000000..fdb87ff --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleServiceRequestRejected.ts @@ -0,0 +1,32 @@ +import { u64 } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { Operator, ServiceRequestOperator } from '../../../types'; +import getAndAssertAccount from '../../../utils/getAndAssertAccount'; + +export default async function handleServiceRequestRejected( + event: SubstrateEvent< + [operator: AccountId32, requestId: u64, blueprintId: u64] + >, +) { + const [operatorAccountId, requestId] = event.event.data; + + const blockNumber = event.block.block.header.number.toNumber(); + + const operatorAccount = await getAndAssertAccount( + operatorAccountId.toString(), + blockNumber, + ); + + const operator = await Operator.get(operatorAccount.id); + assert(operator, 'Operator not found'); + + const serviceRequestOperator = await ServiceRequestOperator.get( + `${requestId}-${operator.id}`, + ); + assert(serviceRequestOperator, 'ServiceRequestOperator not found'); + serviceRequestOperator.rejectedAt = blockNumber; + + await Promise.all([operatorAccount.save(), serviceRequestOperator.save()]); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleServiceRequested.ts b/packages/tangle-subql/src/mappings/services/events/handleServiceRequested.ts new file mode 100644 index 0000000..de7b710 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleServiceRequested.ts @@ -0,0 +1,91 @@ +import { u128, u64, Vec } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { + Blueprint, + Operator, + ServiceRequest, + ServiceRequestOperator, +} from '../../../types'; +import ensureAccount from '../../../utils/ensureAccount'; + +export default async function handleServiceRequested( + event: SubstrateEvent< + [ + owner: AccountId32, + requestId: u64, + blueprintId: u64, + pendingApprovals: Vec, + approved: Vec, + assets: Vec, + ] + >, +) { + const [ + ownerAccountId, + requestId, + blueprintId, + pendingApprovals, + _approved, + assets, + ] = event.event.data; + + const blockNumber = event.block.block.header.number.toNumber(); + + const ownerAccount = await ensureAccount( + ownerAccountId.toString(), + blockNumber, + ); + ownerAccount.lastUpdateAt = blockNumber; + + const blueprint = await Blueprint.get(blueprintId.toString()); + assert(blueprint, 'Blueprint not found'); + + const serviceRequest = ServiceRequest.create({ + id: requestId.toString(), + ownerId: ownerAccount.id, + blueprintId: blueprint.id, + assetIds: assets.map((asset) => asset.toBigInt()), + createdAt: blockNumber, + }); + + // Save initial entities + await Promise.all([ownerAccount.save(), serviceRequest.save()]); + + // Update operator entities + const operatorPromises = await Promise.all( + pendingApprovals.map(async (accountId) => { + const operator = await Operator.get(accountId.toString()); + if (!operator) { + console.warn(`Operator not found for account: ${accountId.toString()}`); + return null; + } + + operator.totalServiceRequests += 1; + operator.lastUpdateAt = blockNumber; + + const serviceRequestOperator = ServiceRequestOperator.create({ + id: `${serviceRequest.id}-${operator.id}`, + serviceRequestId: serviceRequest.id, + operatorId: operator.id, + createdAt: blockNumber, + }); + + return [operator, serviceRequestOperator] as const; + }), + ); + + // Save operator updates + await Promise.all( + operatorPromises + .filter( + (result): result is [Operator, ServiceRequestOperator] => + result !== null, + ) + .flatMap(([operator, serviceRequestOperator]) => [ + operator.save(), + serviceRequestOperator.save(), + ]), + ); +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleServiceTerminated.ts b/packages/tangle-subql/src/mappings/services/events/handleServiceTerminated.ts new file mode 100644 index 0000000..bd3bf4b --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleServiceTerminated.ts @@ -0,0 +1,47 @@ +import { u64 } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { Account, Operator, Service, ServiceOperator } from '../../../types'; +import getAndAssertAccount from '../../../utils/getAndAssertAccount'; + +export default async function handleServiceTerminated( + event: SubstrateEvent<[owner: AccountId32, serviceId: u64, blueprintId: u64]>, +) { + const [owner, serviceId] = event.event.data; + const blockNumber = event.block.block.header.number.toNumber(); + + const ownerAccount = await getAndAssertAccount(owner.toString(), blockNumber); + + const service = await Service.get(serviceId.toString()); + assert(service, 'Service not found'); + + service.terminatedAt = blockNumber; + + await Promise.all([service.save(), ownerAccount.save()]); + + const serviceOperators = + (await ServiceOperator.getByServiceId(service.id)) ?? []; + + for (const serviceOperator of serviceOperators) { + const operator = await Operator.get(serviceOperator.operatorId); + if (operator === undefined) { + logger.error(`Operator ${serviceOperator.operatorId} not found`); + continue; + } + + operator.totalServices -= 1; + const operatorAccount = await Account.get(operator.accountId); + if (operatorAccount === undefined) { + logger.error(`Account ${operator.accountId} not found`); + } else { + operatorAccount.lastUpdateAt = blockNumber; + } + + await Promise.all([ + operator.save(), + serviceOperator.save(), + operatorAccount?.save(), + ]); + } +} diff --git a/packages/tangle-subql/src/mappings/services/events/handleUnregistered.ts b/packages/tangle-subql/src/mappings/services/events/handleUnregistered.ts new file mode 100644 index 0000000..dbab4e5 --- /dev/null +++ b/packages/tangle-subql/src/mappings/services/events/handleUnregistered.ts @@ -0,0 +1,34 @@ +import { u64 } from '@polkadot/types'; +import { AccountId32 } from '@polkadot/types/interfaces'; +import { SubstrateEvent } from '@subql/types'; +import assert from 'assert'; +import { Account, BlueprintOperator, Operator } from '../../../types'; + +export default async function handleUnregistered( + event: SubstrateEvent<[operator: AccountId32, blueprintId: u64]>, +) { + const [operatorAccountId, blueprintId] = event.event.data; + + const blockNumber = event.block.block.header.number.toNumber(); + + const operator = await Operator.get(operatorAccountId.toString()); + assert(operator, 'Operator not found'); + + const account = await Account.get(operator.accountId); + assert(account, 'Account not found'); + + const blueprintOperator = await BlueprintOperator.get( + `${blueprintId.toString()}-${operator.id}`, + ); + assert(blueprintOperator, 'BlueprintOperator not found'); + + blueprintOperator.isActive = false; + blueprintOperator.updatedAt = blockNumber; + account.lastUpdateAt = blockNumber; + + await Promise.all([ + blueprintOperator.save(), + operator.save(), + account.save(), + ]); +} diff --git a/packages/tangle-subql/src/utils/ensureAccount.ts b/packages/tangle-subql/src/utils/ensureAccount.ts new file mode 100644 index 0000000..a39e0f0 --- /dev/null +++ b/packages/tangle-subql/src/utils/ensureAccount.ts @@ -0,0 +1,18 @@ +import { Account } from '../types'; + +export default async function ensureAccount( + accountId: string, + blockNumber: number, +) { + const account = await Account.get(accountId); + + if (account === undefined) { + return Account.create({ + id: accountId, + createdAt: blockNumber, + lastUpdateAt: blockNumber, + }); + } + + return account; +} diff --git a/packages/tangle-subql/src/utils/getAndAssertAccount.ts b/packages/tangle-subql/src/utils/getAndAssertAccount.ts new file mode 100644 index 0000000..689bbcc --- /dev/null +++ b/packages/tangle-subql/src/utils/getAndAssertAccount.ts @@ -0,0 +1,12 @@ +import assert from 'assert'; +import { Account } from '../types'; + +export default async function getAndAssertAccount( + accountId: string, + blockNumber: number, +) { + const account = await Account.get(accountId); + assert(account, `Account with ID ${accountId} not found`); + account.lastUpdateAt = blockNumber; + return account; +}