-
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
34dad0e
commit 0938823
Showing
2 changed files
with
190 additions
and
3 deletions.
There are no files selected for viewing
120 changes: 120 additions & 0 deletions
120
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,120 @@ | ||
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("[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; | ||
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("[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; | ||
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", | ||
}, | ||
]); | ||
}); | ||
}); | ||
}); |
73 changes: 70 additions & 3 deletions
73
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,79 @@ | ||
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("[ContextModule] NftLoader: 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("[ContextModule] NftLoader: 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); | ||
} | ||
} |