Skip to content

Commit

Permalink
basic eclipse provider addition (#4231)
Browse files Browse the repository at this point in the history
  • Loading branch information
callensm authored Jun 26, 2023
1 parent 6691ccf commit 6b99897
Show file tree
Hide file tree
Showing 24 changed files with 294 additions and 128 deletions.
4 changes: 2 additions & 2 deletions backend/native/backpack-api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ const apollo = new ApolloServer<ApiContext>({
ApolloServerPluginResponseCache({
generateCacheKey(req, keyData): string {
const {
network: { devnet },
network: { rpc },
} = req.contextValue as ApiContext;

return createHash("sha256")
.update(`${devnet ? "devnet" : ""}-${JSON.stringify(keyData)}`)
.update(`${rpc ?? ""}-${JSON.stringify(keyData)}`)
.digest("hex");
},
async sessionId(req): Promise<string | null> {
Expand Down
15 changes: 6 additions & 9 deletions backend/native/backpack-api/src/routes/graphql/clients/hasura.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Chain } from "@coral-xyz/zeus";
import { GraphQLError } from "graphql";

import { NodeBuilder } from "../nodes";
import { getProviderForId } from "../providers";
import { getProviderForId, inferProviderIdFromString } from "../providers";
import type {
Friend,
FriendRequest,
Expand All @@ -16,7 +16,7 @@ import type {
WalletFiltersInput,
} from "../types";
import { FriendRequestType } from "../types";
import { createConnection, inferProviderIdFromString } from "../utils";
import { createConnection } from "../utils";

type HasuraOptions = {
secret: string;
Expand Down Expand Up @@ -357,14 +357,14 @@ export class Hasura {
* by the user ID in the database.
* @param {string} id
* @param {string} address
* @param {ProviderId} [providerId]
* @param {ProviderId} providerId
* @returns {Promise<Wallet | null>}
* @memberof Hasura
*/
async getWallet(
id: string,
address: string,
providerId?: ProviderId
providerId: ProviderId
): Promise<Wallet | null> {
// Query Hasura for a single public key owned by the argued user ID
// and matches the argued public key address
Expand All @@ -379,7 +379,6 @@ export class Hasura {
},
},
{
blockchain: true,
created_at: true,
is_primary: true,
},
Expand All @@ -392,10 +391,8 @@ export class Hasura {
return null;
}

const { blockchain, created_at, is_primary } = resp.auth_public_keys[0];
const provider = getProviderForId(
providerId ?? inferProviderIdFromString(blockchain)
);
const { created_at, is_primary } = resp.auth_public_keys[0];
const provider = getProviderForId(providerId);

return NodeBuilder.wallet(provider.id(), {
address,
Expand Down
2 changes: 0 additions & 2 deletions backend/native/backpack-api/src/routes/graphql/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export interface ApiContext {
res: Response;
};
network: {
devnet: boolean;
rpc?: string;
};
}
Expand Down Expand Up @@ -112,7 +111,6 @@ export const createContext: ContextFunction<
res,
},
network: {
devnet,
rpc,
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,6 @@ export class Bitcoin implements BlockchainDataProvider {
return BitcoinToken.name;
}

/**
* Symbol of the native coin.
* @returns {string}
* @memberof Bitcoin
*/
symbol(): string {
return BitcoinToken.symbol;
}

/**
* Fetch and aggregate the native and prices for the argued wallet address.
* @param {string} address
Expand Down Expand Up @@ -132,11 +123,11 @@ export class Bitcoin implements BlockchainDataProvider {
: undefined,
token: this.defaultAddress(),
tokenListEntry: NodeBuilder.tokenListEntry({
address: this.defaultAddress(),
address: BitcoinToken.address,
coingeckoId: "bitcoin",
logo: this.logo(),
name: this.name(),
symbol: this.symbol(),
logo: BitcoinToken.logo,
name: BitcoinToken.name,
symbol: BitcoinToken.symbol,
}),
},
true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { EclipseTokenList } from "@coral-xyz/common";
import { SystemProgram } from "@solana/web3.js";

import type { ApiContext } from "../context";
import type { NftConnection, NftFiltersInput} from "../types";
import { ProviderId } from "../types";

import { SolanaRpc } from "./solana/rpc";
import type { BlockchainDataProvider } from ".";

/**
* Eclipse SVM L2 implementation for the common data provider API.
* @export
* @class Eclipse
* @extends {SolanaRpc}
* @implements {BlockchainDataProvider}
*/
export class Eclipse extends SolanaRpc implements BlockchainDataProvider {
constructor(ctx?: ApiContext) {
super(
ctx,
EclipseTokenList,
"https://api.injective.eclipsenetwork.xyz:8899"
);
}

/**
* Chain ID enum variant.
* @override
* @returns {ProviderId}
* @memberof Eclipse
*/
override id(): ProviderId {
return ProviderId.Solana;
}

/**
* Native coin decimals.
* @override
* @returns {number}
* @memberof Eclipse
*/
override decimals(): number {
return 9;
}

/**
* Default native address.
* @override
* @returns {string}
* @memberof Eclipse
*/
override defaultAddress(): string {
return SystemProgram.programId.toBase58();
}

/**
* Logo URL of the native coin.
* @override
* @returns {string}
* @memberof Eclipse
*/
override logo(): string {
return "https://pbs.twimg.com/profile_images/1626643141519642625/WLqoO9pu_400x400.jpg";
}

/**
* The display name of the data provider.
* @override
* @returns {string}
* @memberof Eclipse
*/
override name(): string {
return "Eclipse";
}

/**
* FIXME:
* The super implementation relies on Metaplex for NFT and metadata
* fetching which doesn't exist on Eclipse.
* @memberof Eclipse
*/
override getNftsForAddress(
address: string,
filters?: NftFiltersInput | undefined
): Promise<NftConnection> {
throw new Error("unimplemented");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EthereumTokenList } from "@coral-xyz/common";
import { type CustomTokenList, EthereumTokenList } from "@coral-xyz/common";
import {
AssetTransfersCategory,
type AssetTransfersParams,
Expand Down Expand Up @@ -33,10 +33,12 @@ import type { BlockchainDataProvider } from ".";
* @implements {BlockchainDataProvider}
*/
export class Ethereum implements BlockchainDataProvider {
readonly #ctx?: ApiContext;
protected readonly ctx?: ApiContext;
protected readonly tokenList: CustomTokenList;

constructor(ctx?: ApiContext) {
this.#ctx = ctx;
constructor(ctx?: ApiContext, tokenList?: CustomTokenList) {
this.ctx = ctx;
this.tokenList = tokenList ?? EthereumTokenList;
}

/**
Expand Down Expand Up @@ -84,15 +86,6 @@ export class Ethereum implements BlockchainDataProvider {
return "Ethereum";
}

/**
* Symbol of the native coin.
* @returns {string}
* @memberof Ethereum
*/
symbol(): string {
return "ETH";
}

/**
* Fetch and aggregate the native and token balances and
* prices for the argued wallet address.
Expand All @@ -105,14 +98,14 @@ export class Ethereum implements BlockchainDataProvider {
address: string,
filters?: BalanceFiltersInput
): Promise<Balances> {
if (!this.#ctx) {
if (!this.ctx) {
throw new Error("API context object not available");
}

// Fetch the native and all token balances of the address and filter out the empty balances
const [native, tokenBalances] = await Promise.all([
this.#ctx.dataSources.alchemy.core.getBalance(address),
this.#ctx.dataSources.alchemy.core.getTokensForOwner(address),
this.ctx.dataSources.alchemy.core.getBalance(address),
this.ctx.dataSources.alchemy.core.getTokensForOwner(address),
]);

const nonEmptyTokens = tokenBalances.tokens.filter(
Expand All @@ -129,7 +122,7 @@ export class Ethereum implements BlockchainDataProvider {

// Get price data from Coingecko for the discovered tokens
const ids = [...meta.values()];
const prices = await this.#ctx.dataSources.coinGecko.getPrices([
const prices = await this.ctx.dataSources.coinGecko.getPrices([
"ethereum",
...ids,
]);
Expand Down Expand Up @@ -236,12 +229,12 @@ export class Ethereum implements BlockchainDataProvider {
address: string,
filters?: NftFiltersInput
): Promise<NftConnection> {
if (!this.#ctx) {
if (!this.ctx) {
throw new Error("API context object not available");
}

// Get all NFTs held by the address from Alchemy
const nfts = await this.#ctx.dataSources.alchemy.nft.getNftsForOwner(
const nfts = await this.ctx.dataSources.alchemy.nft.getNftsForOwner(
address,
{ contractAddresses: filters?.addresses ?? undefined }
);
Expand Down Expand Up @@ -300,7 +293,7 @@ export class Ethereum implements BlockchainDataProvider {
address: string,
filters?: TransactionFiltersInput
): Promise<TransactionConnection> {
if (!this.#ctx) {
if (!this.ctx) {
throw new Error("API context object not available");
}

Expand All @@ -320,11 +313,11 @@ export class Ethereum implements BlockchainDataProvider {
};

const txs = await Promise.allSettled([
this.#ctx.dataSources.alchemy.core.getAssetTransfers({
this.ctx.dataSources.alchemy.core.getAssetTransfers({
fromAddress: address,
...params,
}) as Promise<AssetTransfersWithMetadataResponse>,
this.#ctx.dataSources.alchemy.core.getAssetTransfers({
this.ctx.dataSources.alchemy.core.getAssetTransfers({
toAddress: address,
...params,
}) as Promise<AssetTransfersWithMetadataResponse>,
Expand All @@ -338,7 +331,7 @@ export class Ethereum implements BlockchainDataProvider {

const receipts = await Promise.all(
combined.map((tx) =>
this.#ctx!.dataSources.alchemy.core.getTransactionReceipt(tx.hash)
this.ctx!.dataSources.alchemy.core.getTransactionReceipt(tx.hash)
)
);

Expand All @@ -357,10 +350,10 @@ export class Ethereum implements BlockchainDataProvider {
block: Number(tx.blockNum),
fee:
receipts[i]?.gasUsed && receipts[i]?.effectiveGasPrice
? `${ethers.utils.formatUnits(
? ethers.utils.formatUnits(
receipts[i]!.gasUsed.mul(receipts[i]!.effectiveGasPrice),
this.decimals()
)} ${this.symbol()}`
)
: undefined,
feePayer: tx.from,
hash: tx.hash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
import { ProviderId } from "../types";

import { Bitcoin } from "./bitcoin";
import { Eclipse } from "./eclipse";
import { Ethereum } from "./ethereum";
import { Solana } from "./solana";

Expand All @@ -19,7 +20,6 @@ export interface BlockchainDataProvider {
defaultAddress(): string;
logo(): string;
name(): string;
symbol(): string;

getBalancesForAddress(
address: string,
Expand Down Expand Up @@ -51,6 +51,9 @@ export function getProviderForId(
case ProviderId.Bitcoin: {
return new Bitcoin(ctx);
}
case ProviderId.Eclipse: {
return new Eclipse(ctx);
}
case ProviderId.Ethereum: {
return new Ethereum(ctx);
}
Expand All @@ -59,3 +62,29 @@ export function getProviderForId(
}
}
}

/**
* Infer and return a ProviderId enum variant from the argued string value.
* @export
* @param {string} val
* @returns {(ProviderId | never)}
*/
export function inferProviderIdFromString(val: string): ProviderId | never {
switch (val.toLowerCase()) {
case "bitcoin": {
return ProviderId.Bitcoin;
}
case "eclipse": {
return ProviderId.Eclipse;
}
case "ethereum": {
return ProviderId.Ethereum;
}
case "solana": {
return ProviderId.Solana;
}
default: {
throw new Error(`unknown chain id string: ${val}`);
}
}
}
Loading

1 comment on commit 6b99897

@vercel
Copy link

@vercel vercel bot commented on 6b99897 Jun 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.