-
Notifications
You must be signed in to change notification settings - Fork 290
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(context-module): new Nft context loader for ERC1155 and ERC721
- Loading branch information
1 parent
d6f7731
commit a0a7bc9
Showing
2 changed files
with
189 additions
and
3 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
libs/ledgerjs/packages/context-module/src/loaders/NftLoader.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import axios from "axios"; | ||
import { LoaderOptions } from "../models/LoaderOptions"; | ||
import { Transaction } from "../models/Transaction"; | ||
import { NftLoader } from "./NftLoader"; | ||
|
||
describe("NftLoader", () => { | ||
let loader: NftLoader; | ||
|
||
beforeEach(() => { | ||
jest.restoreAllMocks(); | ||
loader = new NftLoader(); | ||
}); | ||
|
||
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; | ||
jest.spyOn(axios, "request").mockResolvedValueOnce({ data: {} }); | ||
|
||
const result = await loader.load(transaction, options); | ||
|
||
expect(result).toEqual([ | ||
expect.objectContaining({ | ||
type: "error" as const, | ||
error: new Error("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; | ||
jest.spyOn(axios, "request").mockResolvedValueOnce({ data: { payload: "payload1" } }); | ||
jest.spyOn(axios, "request").mockResolvedValueOnce({ data: {} }); | ||
|
||
const result = await loader.load(transaction, options); | ||
|
||
expect(result).toEqual([ | ||
expect.objectContaining({ | ||
type: "error" as const, | ||
error: new Error("no nft metadata"), | ||
}), | ||
]); | ||
}); | ||
|
||
it("should return a response", async () => { | ||
const options = {} as LoaderOptions; | ||
const transaction = { | ||
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7", | ||
data: "0x095ea7b30000000000000", | ||
} as unknown as Transaction; | ||
jest.spyOn(axios, "request").mockResolvedValueOnce({ data: { payload: "payload1" } }); | ||
jest.spyOn(axios, "request").mockResolvedValueOnce({ data: { payload: "payload2" } }); | ||
|
||
const result = await loader.load(transaction, options); | ||
|
||
expect(result).toEqual([ | ||
{ | ||
type: "setPlugin" as const, | ||
payload: "payload1", | ||
}, | ||
{ | ||
type: "provideNFTInformation" as const, | ||
payload: "payload2", | ||
}, | ||
]); | ||
}); | ||
}); | ||
|
||
// TODO: return error when no plugin response | ||
|
||
// TODO: return error when no nft datas | ||
|
||
// TODO: return a response | ||
}); |
66 changes: 63 additions & 3 deletions
66
libs/ledgerjs/packages/context-module/src/loaders/NftLoader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,72 @@ | ||
import axios from "axios"; | ||
import { LoaderOptions } from "../models/LoaderOptions"; | ||
import { Transaction } from "../models/Transaction"; | ||
import { ContextLoader } from "./ContextLoader"; | ||
import { ContextResponse } from "../models/ContextResponse"; | ||
|
||
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 NftLoader implements ContextLoader { | ||
constructor() {} | ||
|
||
load(_transaction: Transaction, _options: LoaderOptions) { | ||
// TODO: implementation | ||
return Promise.resolve([]); | ||
async load(transaction: Transaction, _options: LoaderOptions) { | ||
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 pluginResponse = await axios.request<{ payload: string }>({ | ||
method: "GET", | ||
url: `https://nft.api.live.ledger.com/v1/ethereum/${transaction.chainId}/contracts/${transaction.to}/plugin-selector/${selector}`, | ||
}); | ||
|
||
if (!pluginResponse || !pluginResponse.data.payload) { | ||
return [{ type: "error" as const, error: new Error("unexpected empty response") }]; | ||
} | ||
|
||
responses.push({ type: "setPlugin", payload: pluginResponse.data.payload }); | ||
|
||
const nftInfoResponse = await axios.request<{ payload: string }>({ | ||
method: "GET", | ||
url: `https://nft.api.live.ledger.com/v1/ethereum/${transaction.chainId}/contracts/${transaction.to}`, | ||
}); | ||
|
||
if (!nftInfoResponse || !nftInfoResponse.data.payload) { | ||
return [{ type: "error" as const, error: new Error("no nft metadata") }]; | ||
} | ||
|
||
responses.push({ type: "provideNFTInformation", payload: nftInfoResponse.data.payload }); | ||
|
||
return responses; | ||
} | ||
|
||
private isSelectorSupported(selector: `0x${string}`) { | ||
return Object.values(SUPPORTED_SELECTORS).includes(selector); | ||
} | ||
} |