Skip to content

Commit

Permalink
chore(sdk): Refactor the SDK to avoid passing the full context to the…
Browse files Browse the repository at this point in the history
… DataMappers
  • Loading branch information
alainncls committed Nov 28, 2024
1 parent bc9809b commit 392cee0
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 21 deletions.
12 changes: 7 additions & 5 deletions sdk/src/VeraxSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ export class VeraxSdk {
});
}

this.attestation = new AttestationDataMapper(conf, this.web3Client, this, this.walletClient);
this.schema = new SchemaDataMapper(conf, this.web3Client, this, this.walletClient);
this.module = new ModuleDataMapper(conf, this.web3Client, this, this.walletClient);
this.portal = new PortalDataMapper(conf, this.web3Client, this, this.walletClient);
this.utils = new UtilsDataMapper(conf, this.web3Client, this, this.walletClient);
this.schema = new SchemaDataMapper(conf, this.web3Client, this.walletClient);
const findOneSchemaById = this.schema.findOneById.bind(this.schema);

this.attestation = new AttestationDataMapper(conf, this.web3Client, this.walletClient, findOneSchemaById);
this.module = new ModuleDataMapper(conf, this.web3Client, this.walletClient, findOneSchemaById);
this.portal = new PortalDataMapper(conf, this.web3Client, this.walletClient, findOneSchemaById);
this.utils = new UtilsDataMapper(conf, this.web3Client);
}
}
10 changes: 5 additions & 5 deletions sdk/src/dataMapper/AttestationDataMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,8 @@ export default class AttestationDataMapper extends BaseDataMapper<
try {
const ipfsHash = attestation.offchainData.uri.split("//")[1];
const response = await getIPFSContent(ipfsHash);
if (response.toString().startsWith("0x")) {
const offChainDataSchema = (await this.veraxSdk.schema.findOneById(
attestation.offchainData.schemaId,
)) as Schema;
if (response.toString().startsWith("0x") && this.findOneSchemaById) {
const offChainDataSchema = (await this.findOneSchemaById(attestation.offchainData.schemaId)) as Schema;
attestation.decodedPayload = decodeWithRetry(offChainDataSchema.schema, attestation.attestationData as Hex);
} else {
attestation.decodedPayload = response as unknown as object;
Expand Down Expand Up @@ -134,7 +132,9 @@ export default class AttestationDataMapper extends BaseDataMapper<
const attestationPayloadsArg = [];

for (const attestationPayload of attestationPayloads) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down
14 changes: 9 additions & 5 deletions sdk/src/dataMapper/BaseDataMapper.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { PublicClient, WalletClient } from "viem";
import { Conf } from "../types";
import { Conf, Schema } from "../types";
import { OrderDirection } from "../../.graphclient";
import { VeraxSdk } from "../VeraxSdk";
import { stringifyWhereClause, subgraphCall } from "../utils/graphClientHelper";

export default abstract class BaseDataMapper<T, TFilter, TOrder> {
protected readonly conf: Conf;
protected readonly web3Client: PublicClient;
protected readonly walletClient: WalletClient | undefined;
protected readonly veraxSdk: VeraxSdk;
protected readonly findOneSchemaById: ((id: string) => Promise<Schema | undefined>) | undefined;
protected abstract typeName: string;
protected abstract gqlInterface: string;

constructor(_conf: Conf, _web3Client: PublicClient, _veraxSdk: VeraxSdk, _walletClient?: WalletClient) {
constructor(
_conf: Conf,
_web3Client: PublicClient,
_walletClient?: WalletClient,
_findOneSchemaById?: (id: string) => Promise<Schema | undefined>,
) {
this.conf = _conf;
this.web3Client = _web3Client;
this.veraxSdk = _veraxSdk;
this.walletClient = _walletClient;
this.findOneSchemaById = _findOneSchemaById;
}

async findOneById(id: string) {
Expand Down
8 changes: 6 additions & 2 deletions sdk/src/dataMapper/ModuleDataMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export default class ModuleDataMapper extends BaseDataMapper<Module, Module_filt
validationPayloads: string[],
value: number,
) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down Expand Up @@ -73,7 +75,9 @@ export default class ModuleDataMapper extends BaseDataMapper<Module, Module_filt
const attestationPayloadsArg = [];

for (const attestationPayload of attestationPayloads) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down
16 changes: 12 additions & 4 deletions sdk/src/dataMapper/PortalDataMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export default class PortalDataMapper extends BaseDataMapper<Portal, Portal_filt
value: bigint = 0n,
customAbi?: Abi,
) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down Expand Up @@ -68,7 +70,9 @@ export default class PortalDataMapper extends BaseDataMapper<Portal, Portal_filt
const attestationPayloadsArg = [];

for (const attestationPayload of attestationPayloads) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down Expand Up @@ -164,7 +168,9 @@ export default class PortalDataMapper extends BaseDataMapper<Portal, Portal_filt
validationPayloads: string[],
customAbi?: Abi,
) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down Expand Up @@ -210,7 +216,9 @@ export default class PortalDataMapper extends BaseDataMapper<Portal, Portal_filt
const attestationPayloadsArg = [];

for (const attestationPayload of attestationPayloads) {
const matchingSchema = await this.veraxSdk.schema.findOneById(attestationPayload.schemaId);
const matchingSchema = this.findOneSchemaById
? await this.findOneSchemaById(attestationPayload.schemaId)
: undefined;
if (!matchingSchema) {
throw new Error("No matching Schema");
}
Expand Down
177 changes: 177 additions & 0 deletions sdk/test/unit/AttestationDataMapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { PublicClient, WalletClient } from "viem";
import { Attestation, Conf } from "../../src/types";
import AttestationDataMapper from "../../src/dataMapper/AttestationDataMapper";
import { VeraxSdk } from "../../src/VeraxSdk";
import { decodeWithRetry } from "../../src/utils/abiCoder";
import { getIPFSContent } from "../../src/utils/ipfsClient";

jest.mock("../../src/utils/ipfsClient");
jest.mock("../../src/utils/abiCoder");

const mockConf: Conf = VeraxSdk.DEFAULT_LINEA_SEPOLIA;

const mockWeb3Client = {} as PublicClient;
const mockWalletClient = {} as WalletClient;

describe("AttestationDataMapper", () => {
let attestationDataMapper: AttestationDataMapper;
const mockOffChainAttestation: Attestation = {
id: "0x00000000000000000000000000000000000000000000000000000000000003e9",
decodedPayload: {},
offchainData: undefined,
attestationData:
"0x0000000000000000000000000000000000000000000000000000000000000020e05ce58123ada948cd0218d42fb20ab57be6db7fe6aec4a2fac6190ca9fa997800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d534779447334586d79345757486d6376374c6b674447436950736962716d70473937453964393759464842520000000000000000000000",
attestedDate: 1710252010,
attester: "0xa9f843ab89cca5109546d30ddb6f93d4bcb76aa1",
decodedData: [
"0xe05ce58123ada948cd0218d42fb20ab57be6db7fe6aec4a2fac6190ca9fa9978",
"ipfs://QmSGyDs4Xmy4WWHmcv7LkgDGCiPsibqmpG97E9d97YFHBR",
],
encodedSubject: "0xa9f843ab89cca5109546d30ddb6f93d4bcb76aa1",
expirationDate: 0,
attestationId: "0x0000000000000000000000000000000000000000000000000000000000293707",
portal: {
attestationCounter: 195075,
description: "A portal used by clique to issue attestations",
id: "0x065e959ffd4c76ae2e0d31cfcf91c0c9834472ec",
isRevocable: true,
modules: [
"0x94609d3b8baba3f4a2206be5195c3f0ec6e85890",
"0xd34a8775d06d41b36054d59ef2d09a79b7aa1fa2",
"0x3803856585a7fbc6a3bca94a0b9c49a48af90dd3",
],
name: "CliquePortal",
ownerAddress: "0x4401a1667dafb63cff06218a69ce11537de9a101",
ownerName: "Clique",
},
replacedBy: "0x0000000000000000000000000000000000000000000000000000000000000000",
revocationDate: 0,
revoked: false,
schema: {
attestationCounter: 592,
context: "https://schema.org/Property",
description: "Represents a link to an offchain payload",
id: "0xa288e257097a4bed4166c002cb6911713edacc88e30b6cb2b0104df9c365327d",
name: "Offchain",
schema: "(bytes32 schemaId, string uri)",
},
subject: "0xa9f843ab89cca5109546d30ddb6f93d4bcb76aa1",
version: 7,
};

const offChainPayload = {
avgMonthlySpendingSteam: { sgxAttestationFileId: "cd8b2dd98dc944c889eea5a52cb0a9e3" },
sumHoursPlayedInTop10GameSteam: {
valueType: "decimal",
value: 0,
sgxAttestationFileId: "8e1e02a2867a40fcb0c83c45d1462228",
},
sumHoursPlayedInIGDBCategoryPieChartSteam: { sgxAttestationFileId: "8e1e02a2867a40fcb0c83c45d1462228" },
sumHoursPlayedInTopIGDBCategorySteam: {
value: 0,
valueType: "decimal",
sgxAttestationFileId: "8e1e02a2867a40fcb0c83c45d1462228",
},
};

const ipfsHash = "QmSGyDs4Xmy4WWHmcv7LkgDGCiPsibqmpG97E9d97YFHBR";

const decodedPayload = [
{
schemaId: "0xe05ce58123ada948cd0218d42fb20ab57be6db7fe6aec4a2fac6190ca9fa9978",
uri: `ipfs://${ipfsHash}`,
},
];

beforeEach(() => {
attestationDataMapper = new AttestationDataMapper(mockConf, mockWeb3Client, mockWalletClient, jest.fn());
});

it("should create an instance of AttestationDataMapper", () => {
expect(attestationDataMapper).toBeDefined();
expect(attestationDataMapper).toBeInstanceOf(AttestationDataMapper);
});

it("should enrich attestation with decoded payload", async () => {
const mockAttestation: Attestation = {
id: "0x00000000000000000000000000000000000000000000000000000000000003e9",
decodedPayload: {},
offchainData: undefined,
attestationData:
"0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000042e000000000000000000000000000000000000000000000000000000000000000a323031302d30362d323300000000000000000000000000000000000000000000",
attestedDate: 1695855078,
attester: "0x6d81fbdba7cc3afb7926f80c734965746b297668",
decodedData: ["2010-06-23", "0x42e"],
encodedSubject: "0x6d81fbdba7cc3afb7926f80c734965746b297668",
expirationDate: 0,
attestationId: "0x00000000000000000000000000000000000000000000000000000000000003e9",
portal: {
attestationCounter: 195075,
description: "A portal used by clique to issue attestations",
id: "0x065e959ffd4c76ae2e0d31cfcf91c0c9834472ec",
isRevocable: true,
modules: [
"0x94609d3b8baba3f4a2206be5195c3f0ec6e85890",
"0xd34a8775d06d41b36054d59ef2d09a79b7aa1fa2",
"0x3803856585a7fbc6a3bca94a0b9c49a48af90dd3",
],
name: "CliquePortal",
ownerAddress: "0x4401a1667dafb63cff06218a69ce11537de9a101",
ownerName: "Clique",
},
replacedBy: "0x0000000000000000000000000000000000000000000000000000000000000000",
revocationDate: 0,
revoked: false,
schema: {
attestationCounter: 121785,
context: "Clique",
description: "Linea Attestations (Verax) - Twitter",
id: "0x6b215eaacc6184476dc304bd905c3bed3490bea8e7e00c7b55fde2f8e074b571",
name: "Linea Attestations",
schema: "string timeCreatedTwitter,uint256 numFollowersTwitter",
},
subject: "0x6d81fbdba7cc3afb7926f80c734965746b297668",
version: 3,
};

const decodedPayload = {
numFollowersTwitter: "1070",
timeCreatedTwitter: "2010-06-23",
};

(decodeWithRetry as jest.Mock).mockReturnValueOnce(decodedPayload);

await attestationDataMapper["enrichAttestation"](mockAttestation);

expect(decodeWithRetry).toHaveBeenCalledWith(mockAttestation.schema.schema, mockAttestation.attestationData);
expect(mockAttestation.decodedPayload).toBeDefined();
expect(mockAttestation.decodedPayload).toBe(decodedPayload);
});

it("should handle off-chain data", async () => {
(decodeWithRetry as jest.Mock).mockReturnValueOnce(decodedPayload);
(getIPFSContent as jest.Mock).mockResolvedValueOnce(JSON.stringify(offChainPayload));

await attestationDataMapper["enrichAttestation"](mockOffChainAttestation);

expect(getIPFSContent).toHaveBeenCalledWith(ipfsHash);
expect(mockOffChainAttestation.decodedPayload).toBeDefined();
expect(mockOffChainAttestation.offchainData).toBeDefined();
});

it("should throw an error if IPFS content retrieval fails", async () => {
const decodedPayload = [
{
schemaId: "0xe05ce58123ada948cd0218d42fb20ab57be6db7fe6aec4a2fac6190ca9fa9978",
uri: `ipfs://${ipfsHash}`,
},
];

(decodeWithRetry as jest.Mock).mockReturnValueOnce(decodedPayload);
(getIPFSContent as jest.Mock).mockRejectedValueOnce(new Error("IPFS error"));

await attestationDataMapper["enrichAttestation"](mockOffChainAttestation);

expect(mockOffChainAttestation.offchainData?.error).toBe("IPFS error");
});
});
79 changes: 79 additions & 0 deletions sdk/test/unit/VeraxSdk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createPublicClient, createWalletClient, http } from "viem";
import { Conf, VeraxSdk } from "../../src/VeraxSdk";

jest.mock("viem", () => ({
createPublicClient: jest.fn(),
createWalletClient: jest.fn(),
http: jest.fn(),
custom: jest.fn(),
}));

const mockConf: Conf = VeraxSdk.DEFAULT_LINEA_SEPOLIA;

describe("VeraxSdk", () => {
let veraxSdk: VeraxSdk;

beforeEach(() => {
(createPublicClient as jest.Mock).mockReturnValue({});
(createWalletClient as jest.Mock).mockReturnValue({});
veraxSdk = new VeraxSdk(mockConf, undefined, "0x0000000000000000000000000000000000000000000000000000000000000001");
});

it("should create a VeraxSdk instance", () => {
expect(veraxSdk).toBeDefined();
expect(veraxSdk).toBeInstanceOf(VeraxSdk);
});

it("should instantiate all the data mappers", () => {
expect(veraxSdk.attestation).toBeDefined();
expect(veraxSdk.module).toBeDefined();
expect(veraxSdk.portal).toBeDefined();
expect(veraxSdk.schema).toBeDefined();
expect(veraxSdk.utils).toBeDefined();
});

it("should create a public client", () => {
expect(createPublicClient).toHaveBeenCalledWith({
chain: mockConf.chain,
transport: http(),
});
});

it("should create a wallet client in BACKEND mode", () => {
expect(createWalletClient).toHaveBeenCalledWith({
chain: mockConf.chain,
account: expect.anything(),
transport: http(),
});
});

it("should call findOneById on schema data mapper", async () => {
const findOneByIdSpy = jest.spyOn(veraxSdk.schema, "findOneById");
await veraxSdk.schema.findOneById("some-id");
expect(findOneByIdSpy).toHaveBeenCalledWith("some-id");
});

it("should have default backend configurations", () => {
expect(VeraxSdk.DEFAULT_LINEA_MAINNET).toBeDefined();
expect(VeraxSdk.DEFAULT_ARBITRUM_NOVA).toBeDefined();
expect(VeraxSdk.DEFAULT_LINEA_SEPOLIA).toBeDefined();
expect(VeraxSdk.DEFAULT_ARBITRUM_SEPOLIA).toBeDefined();
expect(VeraxSdk.DEFAULT_ARBITRUM).toBeDefined();
expect(VeraxSdk.DEFAULT_BASE_SEPOLIA).toBeDefined();
expect(VeraxSdk.DEFAULT_BASE).toBeDefined();
expect(VeraxSdk.DEFAULT_BSC_TESTNET).toBeDefined();
expect(VeraxSdk.DEFAULT_BSC).toBeDefined();
});

it("should have default frontend configurations", () => {
expect(VeraxSdk.DEFAULT_LINEA_MAINNET_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_ARBITRUM_NOVA_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_LINEA_SEPOLIA_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_ARBITRUM_SEPOLIA_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_ARBITRUM_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_BASE_SEPOLIA_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_BASE_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_BSC_TESTNET_FRONTEND).toBeDefined();
expect(VeraxSdk.DEFAULT_BSC_FRONTEND).toBeDefined();
});
});

0 comments on commit 392cee0

Please sign in to comment.