Skip to content

Commit

Permalink
Format retirement amounts by registry (#2237)
Browse files Browse the repository at this point in the history
* format for ICR amounts

* api: updated query and types

* api: fix type error

* api: increment api version

* api: update new fixtures and tests for new query

* api: remove logs

* api: adjust fixtures, add Moss as registry

* api: use CreditId class for formatting

* api: account for Moss project id)

* api: fix missing imports

* api: increment api version
  • Loading branch information
psparacino authored Feb 19, 2024
1 parent 381a1f3 commit 4fc7dbc
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 28 deletions.
20 changes: 15 additions & 5 deletions carbonmark-api/src/.generated/types/digitalCarbon.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4323,7 +4323,7 @@ export enum _SubgraphErrorPolicy_ {
deny = 'deny'
}

export type ProvenanceRecordFragmentFragment = { __typename?: 'ProvenanceRecord', id: any, transactionType: ProvenanceType, registrySerialNumbers: Array<string>, token: any, sender: any, receiver: any, originalAmount: string, remainingAmount: string, createdAt: string, updatedAt: string };
export type ProvenanceRecordFragmentFragment = { __typename?: 'ProvenanceRecord', id: any, transactionType: ProvenanceType, registrySerialNumbers: Array<string>, token: any, tokenId: string | null, sender: any, receiver: any, originalAmount: string, remainingAmount: string, createdAt: string, updatedAt: string, transactionHash: any };

export type RetireFragmentFragment = { __typename?: 'Retire', id: any, bridgeID: string | null, hash: any, amount: string, beneficiaryName: string, retirementMessage: string, retiringName: string, timestamp: string, pool: { __typename?: 'CarbonPool', id: any } | null, beneficiaryAddress: { __typename?: 'Account', id: any }, retiringAddress: { __typename?: 'Account', id: any } };

Expand Down Expand Up @@ -4394,20 +4394,22 @@ export type GetProvenanceRecordsByHashQueryVariables = Exact<{
}>;


export type GetProvenanceRecordsByHashQuery = { __typename?: 'Query', provenanceRecords: Array<{ __typename?: 'ProvenanceRecord', id: any, transactionType: ProvenanceType, registrySerialNumbers: Array<string>, token: any, sender: any, receiver: any, originalAmount: string, remainingAmount: string, createdAt: string, updatedAt: string, priorRecords: Array<{ __typename?: 'ProvenanceRecord', id: any, transactionType: ProvenanceType, registrySerialNumbers: Array<string>, token: any, sender: any, receiver: any, originalAmount: string, remainingAmount: string, createdAt: string, updatedAt: string }> }> };
export type GetProvenanceRecordsByHashQuery = { __typename?: 'Query', retires: Array<{ __typename?: 'Retire', credit: { __typename?: 'CarbonCredit', project: { __typename?: 'CarbonProject', id: string, registry: Registry } }, provenance: { __typename?: 'ProvenanceRecord', id: any, transactionType: ProvenanceType, registrySerialNumbers: Array<string>, token: any, tokenId: string | null, sender: any, receiver: any, originalAmount: string, remainingAmount: string, createdAt: string, updatedAt: string, transactionHash: any, priorRecords: Array<{ __typename?: 'ProvenanceRecord', id: any, transactionType: ProvenanceType, registrySerialNumbers: Array<string>, token: any, tokenId: string | null, sender: any, receiver: any, originalAmount: string, remainingAmount: string, createdAt: string, updatedAt: string, transactionHash: any }> } | null }> };

export const ProvenanceRecordFragmentFragmentDoc = gql`
fragment ProvenanceRecordFragment on ProvenanceRecord {
id
transactionType
registrySerialNumbers
token
tokenId
sender
receiver
originalAmount
remainingAmount
createdAt
updatedAt
transactionHash
}
`;
export const RetireFragmentFragmentDoc = gql`
Expand Down Expand Up @@ -4578,10 +4580,18 @@ export const AllRetirementsDocument = gql`
${CarbonCreditFragmentFragmentDoc}`;
export const GetProvenanceRecordsByHashDocument = gql`
query getProvenanceRecordsByHash($hash: Bytes!) {
provenanceRecords(where: {transactionHash: $hash}) {
...ProvenanceRecordFragment
priorRecords(orderBy: createdAt, orderDirection: desc) {
retires(where: {provenance_: {transactionHash: $hash}}) {
credit {
project {
id
registry
}
}
provenance {
...ProvenanceRecordFragment
priorRecords(orderBy: createdAt, orderDirection: desc) {
...ProvenanceRecordFragment
}
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions carbonmark-api/src/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ export const REGISTRIES = {
url: "https://www.carbonregistry.com",
decimals: 0,
},
Moss: {
id: "MOSS",
title: "MOSS",
url: "https://moss.earth",
decimals: 18,
},
};

export const ICR_API = (
Expand Down
2 changes: 2 additions & 0 deletions carbonmark-api/src/graphql/digitalCarbon.fragments.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ fragment ProvenanceRecordFragment on ProvenanceRecord {
transactionType
registrySerialNumbers
token
tokenId
sender
receiver
originalAmount
remainingAmount
createdAt
updatedAt
transactionHash
}

fragment RetireFragment on Retire {
Expand Down
14 changes: 11 additions & 3 deletions carbonmark-api/src/graphql/digitalCarbon.gql
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,18 @@ query allRetirements($account_id: String) {
}

query getProvenanceRecordsByHash($hash: Bytes!) {
provenanceRecords(where: { transactionHash: $hash }) {
...ProvenanceRecordFragment
priorRecords(orderBy: createdAt, orderDirection: desc) {
retires(where: { provenance_: { transactionHash: $hash } }) {
credit {
project {
id
registry
}
}
provenance {
...ProvenanceRecordFragment
priorRecords(orderBy: createdAt, orderDirection: desc) {
...ProvenanceRecordFragment
}
}
}
}
26 changes: 19 additions & 7 deletions carbonmark-api/src/routes/retirements/[id]/provenance/get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { CreditId } from "../../../../../src/utils/CreditId";
import { gql_sdk } from "../../../../utils/gqlSdk";
import { formatRecord } from "../../../../utils/helpers/records.utils";
import { Params, Querystring } from "../get.schema";
Expand All @@ -15,19 +16,30 @@ const handler = () =>
) {
const sdk = gql_sdk(request.query.network);

// Finds the retirement using polygon-bridged-carbon. We can bypass this when we will have the trasaction hash in the Retire model of polygon-digital-carbon
const retirementRecord = (
const retirementRecord =
await sdk.digital_carbon.getProvenanceRecordsByHash({
hash: request.params.id,
})
).provenanceRecords.at(0);
});
if (retirementRecord.retires.length === 0 || !retirementRecord.retires[0]) {
return reply.notFound();
}

if (retirementRecord == null) {
if (!retirementRecord.retires[0].provenance) {
return reply.notFound();
}

const [registry] = CreditId.splitProjectId(
retirementRecord.retires[0].credit.project.id
);

const lastRecord = { ...retirementRecord.retires[0].provenance };

const priorRecords =
retirementRecord.retires[0].provenance?.priorRecords ?? [];

const records = [
formatRecord(retirementRecord),
...retirementRecord.priorRecords.map(formatRecord),
formatRecord(lastRecord, registry),
...priorRecords.map((record) => formatRecord(record, registry)),
];
return reply.send(JSON.stringify(records));
};
Expand Down
27 changes: 21 additions & 6 deletions carbonmark-api/src/utils/helpers/records.utils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { formatUnits } from "ethers-v6";
import { pick } from "lodash";
import { RegistryId } from "src/app.constants";
import { ProvenanceRecord } from "src/models/ProvenanceRecord.model";
import { GetProvenanceRecordsByHashQuery } from "../../.generated/types/digitalCarbon.types";
import { formatAmountByRegistry } from "../marketplace.utils";

/**
* Format a Record coming from a GQL query into a standardized API response fragment
* @param credit
* @returns
*/

type Provenance = NonNullable<
GetProvenanceRecordsByHashQuery["retires"][0]["provenance"]
>;
type PriorRecord = Provenance["priorRecords"][number];

type RecordType = Provenance | PriorRecord;

export function formatRecord(
record:
| GetProvenanceRecordsByHashQuery["provenanceRecords"][0]
| GetProvenanceRecordsByHashQuery["provenanceRecords"][0]["priorRecords"][0]
record: RecordType,
registry: RegistryId
): ProvenanceRecord {
if (!record) {
throw new Error("Record is undefined or null");
}

return {
...pick(record, [
"id",
Expand All @@ -25,7 +36,11 @@ export function formatRecord(
]),
createdAt: Number(record.createdAt),
updatedAt: Number(record.updatedAt),
originalAmount: Number(formatUnits(record.originalAmount, 18)),
remainingAmount: Number(formatUnits(record.remainingAmount, 18)),
originalAmount: Number(
formatAmountByRegistry(registry, record.originalAmount)
),
remainingAmount: Number(
formatAmountByRegistry(registry, record.remainingAmount)
),
};
}
11 changes: 9 additions & 2 deletions carbonmark-api/src/utils/helpers/retirements.utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { formatUnits } from "ethers-v6";
import { pick } from "lodash";
import { GetRetirementByHashQuery } from "../../.generated/types/digitalCarbon.types";
import { Retirement } from "../../models/Retirement.model";
import { CreditId } from "../CreditId";
import { formatAmountByRegistry } from "../marketplace.utils";
import { formatCarbonCredit } from "./carbonCredits.utils";
import { MOSS_POOL } from "./utils.constants";

Expand All @@ -14,6 +15,12 @@ import { MOSS_POOL } from "./utils.constants";
export function formatRetirement(
retirement: GetRetirementByHashQuery["retires"][0]
): Retirement {
let registry;

retirement.credit.project.id === "Moss"
? (registry = "MOSS")
: ([registry] = CreditId.splitProjectId(retirement.credit.project.id));

return {
...pick(retirement, [
"id",
Expand All @@ -26,7 +33,7 @@ export function formatRetirement(
beneficiaryAddress: retirement.beneficiaryAddress.id,
retiringAddress: retirement.retiringAddress.id,
timestamp: Number(retirement.timestamp),
amount: Number(formatUnits(retirement.amount || 0, 18)),
amount: Number(formatAmountByRegistry(registry, retirement.amount)),
hasProvenanceDetails: retirement.pool?.id == MOSS_POOL,
credit: retirement.credit
? formatCarbonCredit(retirement.credit)
Expand Down
2 changes: 2 additions & 0 deletions carbonmark-api/src/utils/marketplace.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const formatAmountByRegistry = (
registryId: RegistryId,
quantity: string
) => {
registryId ?? (registryId = "MOSS");

const registry = Object.values(REGISTRIES).find((r) => r.id === registryId);

if (!registry) {
Expand Down
18 changes: 16 additions & 2 deletions carbonmark-api/test/fixtures/digitalCarbon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,25 @@ const provenanceRecordWithoutPriorRecords = aProvenanceRecord({
updatedAt: "1701095377",
priorRecords: [],
});
const provenanceRecord = aProvenanceRecord({
const fullProvenanceRecord = aProvenanceRecord({
...provenanceRecordWithoutPriorRecords,
priorRecords: [provenanceRecordWithoutPriorRecords],
});

const retireWithProvenance = [
{
credit: {
project: {
id: "VCS-191",
registry: "VCS",
},
},
provenance: {
...fullProvenanceRecord,
},
},
];

const empty_countries = {
data: {
carbonProjects: [],
Expand All @@ -128,7 +142,7 @@ const fixtures = {
poolBalance,
digitalCarbonProject,
retirement,
provenanceRecord,
retireWithProvenance,
};

export default fixtures;
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import nock from "nock";
import { ProvenanceRecord } from "src/models/ProvenanceRecord.model";
import { GRAPH_URLS } from "../../../../../src/app.constants";
import { fixtures } from "../../../../fixtures";
const mockProvenanceRecord = fixtures.digitalCarbon.provenanceRecord;
const retires = fixtures.digitalCarbon.retireWithProvenance;

export const mockDigitalCarbonProvenanceRecords = (
override?: ProvenanceRecord[]
) =>
nock(GRAPH_URLS["polygon"].digitalCarbon)
.post("", (body) => body.query.includes("getProvenanceRecordsByHash"))
.reply(200, {
data: { provenanceRecords: override ?? [mockProvenanceRecord] },
data: { retires: override ?? retires },
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { build } from "../../../../helper";
import { DEV_URL } from "../../../../test.constants";
import { mockDigitalCarbonProvenanceRecords } from "./get.test.mocks";

const mockProvenanceRecord = fixtures.digitalCarbon.provenanceRecord;
const mockProvenanceRecord =
fixtures.digitalCarbon.retireWithProvenance[0].provenance;

describe("GET /retirements/:id/provenance", () => {
let fastify: FastifyInstance;
Expand All @@ -29,6 +30,7 @@ describe("GET /retirements/:id/provenance", () => {
url: `${DEV_URL}/retirements/0xa049a8354af988a4285eadc5c540590d26d95bca1c6a85c873e32a5c280e7509/provenance`,
});
const record = await response.json();

const transformedRecord = {
...pick(mockProvenanceRecord, [
"id",
Expand Down

0 comments on commit 4fc7dbc

Please sign in to comment.