Skip to content

Commit

Permalink
feat(context-module): add Nft loader
Browse files Browse the repository at this point in the history
  • Loading branch information
aussedatlo committed May 7, 2024
1 parent 367649f commit ecaf487
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import axios from "axios";
import {
GetNftInformationsParams,
GetSetPluginPayloadParams,
NftDataSource,
} from "./NftDataSource";

export class HttpNftDataSource implements NftDataSource {
public async getSetPluginPayload({
chainId,
address,
selector,
}: GetSetPluginPayloadParams): Promise<string> {
const response = await axios.request<{ payload: string }>({
method: "GET",
url: `https://nft.api.live.ledger.com/v1/ethereum/${chainId}/contracts/${address}/plugin-selector/${selector}`,
});

return response.data.payload;
}

public async getNftInfosPayload({ chainId, address }: GetNftInformationsParams) {
const response = await axios.request<{ payload: string }>({
method: "GET",
url: `https://nft.api.live.ledger.com/v1/ethereum/${chainId}/contracts/${address}`,
});

return response.data.payload;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type GetSetPluginPayloadParams = {
chainId: number;
address: string;
selector: string;
};

export type GetNftInformationsParams = {
chainId: number;
address: string;
};

export interface NftDataSource {
getNftInfosPayload(params: GetNftInformationsParams): Promise<string>;
getSetPluginPayload(params: GetSetPluginPayloadParams): Promise<string>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { LoaderOptions } from "../../shared/model/LoaderOptions";
import { Transaction } from "../../shared/model/Transaction";
import { NftDataSource } from "../data/NftDataSource";
import { NftContextLoader } from "./NftContextLoader";

describe("NftContextLoader", () => {
const spyGetNftInfosPayload = jest.fn();
const spyGetPluginPayload = jest.fn();
let mockDataSource: NftDataSource;
let loader: NftContextLoader;

beforeEach(() => {
jest.restoreAllMocks();
mockDataSource = {
getNftInfosPayload: spyGetNftInfosPayload,
getSetPluginPayload: spyGetPluginPayload,
};
loader = new NftContextLoader(mockDataSource);
});

describe("load function", () => {
it("should return an empty array if no dest", async () => {
const options = {} as LoaderOptions;
const transaction = { to: undefined, data: "0x01" } as Transaction;

const result = await loader.load(transaction, options);

expect(result).toEqual([]);
});

it("should return an empty array if undefined data", async () => {
const options = {} as LoaderOptions;
const transaction = {
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: undefined,
} as unknown as Transaction;

const result = await loader.load(transaction, options);

expect(result).toEqual([]);
});

it("should return an empty array if empty data", async () => {
const options = {} as LoaderOptions;
const transaction = {
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: "0x",
} as unknown as Transaction;

const result = await loader.load(transaction, options);

expect(result).toEqual([]);
});

it("should return an empty array if selector not supported", async () => {
const options = {} as LoaderOptions;
const transaction = {
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: "0x095ea7b20000000000000",
} as unknown as Transaction;

const result = await loader.load(transaction, options);

expect(result).toEqual([]);
});

it("should return an error when no plugin response", async () => {
const options = {} as LoaderOptions;
const transaction = {
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: "0x095ea7b30000000000000",
} as unknown as Transaction;
spyGetPluginPayload.mockResolvedValueOnce(undefined);

const result = await loader.load(transaction, options);

expect(result).toEqual([
expect.objectContaining({
type: "error" as const,
error: new Error("[ContextModule] NftLoader: unexpected empty response"),
}),
]);
});

it("should return an error when no nft data response", async () => {
const options = {} as LoaderOptions;
const transaction = {
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: "0x095ea7b30000000000000",
} as unknown as Transaction;
spyGetPluginPayload.mockResolvedValueOnce("payload1");
spyGetNftInfosPayload.mockResolvedValueOnce(undefined);

const result = await loader.load(transaction, options);

expect(result).toEqual([
expect.objectContaining({
type: "error" as const,
error: new Error("[ContextModule] NftLoader: no nft metadata"),
}),
]);
});

it("should return a response", async () => {
const options = {} as LoaderOptions;
const transaction = {
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: "0x095ea7b30000000000000",
} as unknown as Transaction;
spyGetPluginPayload.mockResolvedValueOnce("payload1");
spyGetNftInfosPayload.mockResolvedValueOnce("payload2");

const result = await loader.load(transaction, options);

expect(result).toEqual([
{
type: "setPlugin" as const,
payload: "payload1",
},
{
type: "provideNFTInformation" as const,
payload: "payload2",
},
]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { ContextLoader } from "../../shared/domain/ContextLoader";
import { ContextResponse } from "../../shared/model/ContextResponse";
import { LoaderOptions } from "../../shared/model/LoaderOptions";
import { Transaction } from "../../shared/model/Transaction";
import { NftDataSource } from "../data/NftDataSource";

enum ERC721_SUPPORTED_SELECTOR {
Approve = "0x095ea7b3",
SetApprovalForAll = "0xa22cb465",
TransferFrom = "0x23b872dd",
SafeTransferFrom = "0x42842e0e",
SafeTransferFromWithData = "0xb88d4fde",
}

enum ERC1155_SUPPORTED_SELECTOR {
SetApprovalForAll = "0xa22cb465",
SafeTransferFrom = "0xf242432a",
SafeBatchTransferFrom = "0x2eb2c2d6",
}

const SUPPORTED_SELECTORS: `0x${string}`[] = [
...Object.values(ERC721_SUPPORTED_SELECTOR),
...Object.values(ERC1155_SUPPORTED_SELECTOR),
];

export class NftContextLoader implements ContextLoader {
private _dataSource: NftDataSource;

constructor(dataSource: NftDataSource) {
this._dataSource = dataSource;
}

async load(transaction: Transaction, _options: LoaderOptions): Promise<ContextResponse[]> {
const responses: ContextResponse[] = [];

if (!transaction.to || !transaction.data || transaction.data === "0x") {
return [];
}

const selector = transaction.data.slice(0, 10) as `0x${string}`;

if (!this.isSelectorSupported(selector)) {
return [];
}

// EXAMPLE:
// https://nft.api.live.ledger.com/v1/ethereum/1/contracts/0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/plugin-selector/0x095ea7b3
const setPluginPayload = await this._dataSource.getSetPluginPayload({
chainId: transaction.chainId,
address: transaction.to,
selector,
});

if (!setPluginPayload) {
return [
{
type: "error" as const,
error: new Error("[ContextModule] NftLoader: unexpected empty response"),
},
];
}

responses.push({ type: "setPlugin", payload: setPluginPayload });

const nftInformationsPayload = await this._dataSource.getNftInfosPayload({
chainId: transaction.chainId,
address: transaction.to,
});

if (!nftInformationsPayload) {
return [
{ type: "error" as const, error: new Error("[ContextModule] NftLoader: no nft metadata") },
];
}

responses.push({ type: "provideNFTInformation", payload: nftInformationsPayload });

return responses;
}

private isSelectorSupported(selector: `0x${string}`) {
return Object.values(SUPPORTED_SELECTORS).includes(selector);
}
}

0 comments on commit ecaf487

Please sign in to comment.