Skip to content

Commit

Permalink
feat(context-module): add external plugin loader
Browse files Browse the repository at this point in the history
  • Loading branch information
aussedatlo committed May 10, 2024
1 parent b76691c commit 2a17653
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export interface DappResponse {
b2c: B2c;
abis: Abis;
b2c_signatures: B2cSignatures;
}

export interface B2c {
blockchainName: string;
chainId: number;
contracts: Contract[];
name: string;
}

interface Contract {
address: string;
contractName: string;
selectors: { [selector: string]: ContractSelector };
}

interface ContractSelector {
erc20OfInterest: string[];
method: string;
plugin: string;
}

interface Abis {
[address: string]: AbiFunction[];
}

export interface AbiFunction {
name: string;
inputs?: AbiInput[];
outputs?: AbiOutput[];
stateMutability: string;
type: string;
}

interface AbiInput {
name: string;
internalType: string;
}

interface AbiOutput {
name: string;
internalType: string;
}

export interface B2cSignatures {
[address: string]: {
[selector: string]: B2cSignature;
};
}

interface B2cSignature {
plugin: string;
serialized_data: string;
signature: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DappInfos } from "../model/DappInfos";

export type GetDappInfos = {
address: string;
selector: `0x${string}`;
chainId: number;
};

export interface ExternalPluginDataSource {
getDappInfos(params: GetDappInfos): Promise<DappInfos | undefined>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import axios from "axios";
import { ExternalPluginDataSource, GetDappInfos } from "./ExternalPluginDataSource";
import { DappResponse } from "./DappResponse";
import { SelectorDetails } from "../model/SelectorDetails";
import { DappInfos } from "../model/DappInfos";

export class HttpExternalPluginDataSource implements ExternalPluginDataSource {
constructor() {}

async getDappInfos({ chainId, address, selector }: GetDappInfos): Promise<DappInfos | undefined> {
const dappInfos = await axios.request<DappResponse[]>({
method: "GET",
url: "https://crypto-assets-service.api.ledger.com/v1/dapps",
params: { output: "b2c,b2c_signatures,abis", chain_id: chainId, contracts: address },
});

const { erc20OfInterest, method, plugin } =
dappInfos.data[0]?.b2c.contracts?.[0]?.selectors?.[selector] || {};
const { signature, serialized_data: serializedData } =
dappInfos.data[0]?.b2c_signatures?.[address]?.[selector] || {};

if (!erc20OfInterest || !method || !plugin || !signature || !serializedData) {
return;
}

const abi = dappInfos.data[0]?.abis?.[address];

if (!abi) {
return;
}

const selectorDetails: SelectorDetails = {
method,
plugin,
erc20OfInterest,
signature,
serializedData,
};

return { selectorDetails, abi };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { ethers } from "ethers";
import { ContextLoader } from "../../shared/domain/ContextLoader";
import { LoaderOptions } from "../../shared/model/LoaderOptions";
import { Transaction } from "../../shared/model/Transaction";
import { TokenDataSource } from "../../token/data/TokenDataSource";
import { ContextResponse } from "../../shared/model/ContextResponse";
import { ExternalPluginDataSource } from "../data/ExternalPluginDataSource";

export class ExternalPluginContextLoader implements ContextLoader {
private _externalPluginDataSource: ExternalPluginDataSource;
private _tokenDataSource: TokenDataSource;

constructor(
externalPluginDataSource: ExternalPluginDataSource,
tokenDataSource: TokenDataSource,
) {
this._externalPluginDataSource = externalPluginDataSource;
this._tokenDataSource = tokenDataSource;
}

async load(transaction: Transaction, _options: LoaderOptions) {
const response: ContextResponse[] = [];

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

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

const dappInfos = await this._externalPluginDataSource.getDappInfos({
address: transaction.to,
chainId: transaction.chainId,
selector,
});

if (!dappInfos) {
return [];
}

const contractInterface = new ethers.utils.Interface(dappInfos.abi);

const decodedCallData = contractInterface.decodeFunctionData(
dappInfos.selectorDetails.method,
transaction.data,
);

const addresses: string[] = [];
for (const erc20Path in dappInfos.selectorDetails.erc20OfInterest) {
const address = this.getAddressFromPath(erc20Path, decodedCallData);
addresses.push(address);
}

if (addresses.length !== dappInfos.selectorDetails.erc20OfInterest.length) {
return [
{
type: "error" as const,
error: new Error(
"[ContextModule] ExternalPluginContextLoader: Mismatch between erc20OfInterest and callData",
),
},
];
}

const tokenPayloads = await Promise.all(
addresses.map(address =>
this._tokenDataSource.getTokenInfosPayload({ address, chainId: transaction.chainId }),
),
);

for (const payload in tokenPayloads) {
response.push({ type: "provideERC20TokenInformation" as const, payload });
}

response.push({
type: "setExternalPlugin" as const,
payload: Buffer.concat([
Buffer.from(dappInfos.selectorDetails.serializedData, "hex"),
Buffer.from(dappInfos.selectorDetails.signature, "hex"),
]).toString("hex"),
});

return response;
}

private getAddressFromPath(path: string, decodedCallData: ethers.utils.Result): `0x${string}` {
// ethers.utils.Result is a record string, any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let value: any = decodedCallData;
for (const key in path.split(".")) {
if (key === "-1" && Array.isArray(value)) {
value = value[value.length - 1];
} else {
value = value[key];
}
}

if (typeof value !== "string" || !value.startsWith("0x")) {
throw new Error("[ContextModule] ExternalPluginContextLoader: Unable to get address");
}

return value as `0x${string}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { AbiFunction } from "../data/DappResponse";

export type Abi = AbiFunction[];
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Abi } from "./Abi";
import { SelectorDetails } from "./SelectorDetails";

export type DappInfos = { selectorDetails: SelectorDetails; abi: Abi };
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type SelectorDetails = {
plugin: string;
signature: string;
serializedData: string;
method: string;
erc20OfInterest: string[];
};

0 comments on commit 2a17653

Please sign in to comment.