Skip to content

Commit

Permalink
Support any solana and erc20 token for fulfillment (#10)
Browse files Browse the repository at this point in the history
Co-authored-by: mrlotfi <[email protected]>
  • Loading branch information
mrlotfi and mrlotfi authored Sep 7, 2024
1 parent 9585b7a commit bebec15
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/config/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ export const mayanEndpoints: MayanEndpoints = {
explorerApiUrl: process.env.EXPLORER_API_URL || 'https://explorer-api.mayan.finance',
priceApiUrl: process.env.PRICE_API_URL || 'https://price-api.mayan.finance',
lutApiUrl: process.env.LUT_API_URL || 'https://lut-api.mayan.finance',
refreshTokenIntervalSeconds: Number(process.env.REFRESH_TOKEN_INTERVAL_SECONDS) || 120,
refreshTokenIntervalSeconds: Number(process.env.REFRESH_TOKEN_INTERVAL_SECONDS) || 600,
};
80 changes: 73 additions & 7 deletions src/config/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Connection, ParsedAccountData, PublicKey } from '@solana/web3.js';
import axios from 'axios';
import { getDecimals, getSymbol } from '../utils/erc20';
import { EvmProviders } from '../utils/evm-providers';
import logger from '../utils/logger';
import {
CHAIN_ID_ARBITRUM,
Expand All @@ -10,6 +13,7 @@ import {
CHAIN_ID_POLYGON,
CHAIN_ID_SOLANA,
mapNameToWormholeChainId,
WhChainIdToEvm,
} from './chains';
import { MayanEndpoints } from './endpoints';

Expand All @@ -36,7 +40,11 @@ export class TokenList {

private initialized = false;

constructor(endpoints: MayanEndpoints) {
constructor(
endpoints: MayanEndpoints,
private readonly evmProviders: EvmProviders,
private readonly connection: Connection,
) {
this.endpoints = endpoints;
}

Expand Down Expand Up @@ -104,7 +112,69 @@ export class TokenList {
)!;
}

getTokenData(tokenChain: number, tokenContract: string): Token {
async getTokenData(tokenChain: number, tokenContract: string): Promise<Token> {
const preDefinedToken = this.getPreDefinedTokenData(tokenChain, tokenContract);
if (preDefinedToken) {
return preDefinedToken;
}

if (tokenChain === CHAIN_ID_SOLANA) {
return await this.fetchSolanaTokenData(tokenContract);
} else {
return await this.fetchErc20TokenData(tokenChain, tokenContract);
}
}

async fetchErc20TokenData(chainId: number, tokenContract: string): Promise<Token> {
const [symbol, decimals] = await Promise.all([
getSymbol(this.evmProviders[chainId], tokenContract),
getDecimals(this.evmProviders[chainId], tokenContract),
]);

return {
chainId: WhChainIdToEvm[chainId],
wChainId: chainId,
coingeckoId: tokenContract,
contract: tokenContract,
mint: tokenContract,
decimals: Number(decimals),
logoURI: '',
name: symbol,
realOriginChainId: WhChainIdToEvm[chainId],
realOriginContractAddress: tokenContract,
symbol: symbol,
supportsPermit: false,
};
}

private async fetchSolanaTokenData(tokenContract: string): Promise<Token> {
const mintAccountInfo = await this.connection.getParsedAccountInfo(new PublicKey(tokenContract));
if (!mintAccountInfo.value) {
throw new Error(`Token account not found on solana chain for ${tokenContract}`);
}
const mintData = mintAccountInfo.value.data as ParsedAccountData;
const decimals = Number(mintData.parsed.info.decimals);

return {
chainId: CHAIN_ID_SOLANA,
coingeckoId: '',
contract: tokenContract,
mint: tokenContract,
decimals: decimals,
logoURI: '',
name: '',
realOriginChainId: CHAIN_ID_SOLANA,
realOriginContractAddress: tokenContract,
symbol: '',
supportsPermit: false,
};
}

// private fetchERC20TokenData(tokenContract: string): Promise<Token> {
// const tokenData = null;
// }

private getPreDefinedTokenData(tokenChain: number, tokenContract: string): Token | undefined {
if (!this.tokensPerChain[tokenChain]) {
throw new Error(`Chain ${tokenChain} is not found in token list`);
}
Expand All @@ -121,10 +191,6 @@ export class TokenList {
}
});

if (!token) {
throw new Error(`Token ${tokenContract} is not found in token list`);
}

return token;
}
}
Expand All @@ -147,4 +213,4 @@ const UsdtContracts: { [key: number]: string } = {
[CHAIN_ID_AVAX]: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
[CHAIN_ID_ARBITRUM]: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9',
[CHAIN_ID_OPTIMISM]: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58',
};
};
4 changes: 2 additions & 2 deletions src/driver/driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export class DriverService {

let computeUnits: number = 50_000;
if (createStateAss) {
computeUnits = 55_000;
computeUnits = 65_000;
const stateToAss = getAssociatedTokenAddressSync(new PublicKey(toToken.mint), stateAddr, true);
const createdAtaIx = createAssociatedTokenAccountIdempotentInstruction(
this.walletConfig.solana.publicKey,
Expand All @@ -356,7 +356,7 @@ export class DriverService {

const signers = [this.walletConfig.solana];
if (postAuction) {
computeUnits = 70_000;
computeUnits = 80_000;
signers.push(newMessageAccount!);
}

Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export async function main() {

const walletHelper = new WalletsHelper(evmProviders, walletConf, rpcConfig, contracts);

const tokenList = new TokenList(mayanEndpoints);
const tokenList = new TokenList(mayanEndpoints, evmProviders, solanaConnection);
await tokenList.init();

const solanaIxHelper = new NewSolanaIxHelper(
Expand Down
55 changes: 55 additions & 0 deletions src/utils/erc20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,58 @@ export async function getEthBalance(evmProvider: ethers.JsonRpcProvider, address
const balance = await evmProvider.getBalance(address);
return balance;
}

const erc20NameSymbolAbi = [
{
constant: true,
inputs: [],
name: 'decimals',
outputs: [
{
name: '',
type: 'uint8',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
];

export async function getSymbol(evmProvider: ethers.JsonRpcProvider, address: string): Promise<string> {
const contract = new ethers.Contract(address, erc20NameSymbolAbi, evmProvider);
return await contract.symbol();
}

export async function getDecimals(evmProvider: ethers.JsonRpcProvider, address: string): Promise<number> {
const contract = new ethers.Contract(address, erc20NameSymbolAbi, evmProvider);
return await contract.decimals();
}
2 changes: 0 additions & 2 deletions src/utils/fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export class FeeService {
params: {
ids: [
qr.fromToken.coingeckoId,
qr.toToken.coingeckoId,
this.tokenList.nativeTokens[qr.fromChainId].coingeckoId,
this.tokenList.nativeTokens[qr.toChainId].coingeckoId,
this.tokenList.nativeTokens[CHAIN_ID_SOLANA].coingeckoId,
Expand All @@ -40,7 +39,6 @@ export class FeeService {

const solPrice = prices.data[this.tokenList.nativeTokens[CHAIN_ID_SOLANA].coingeckoId];
const fromTokenPrice = prices.data[qr.fromToken.coingeckoId];
const toTokenPrice = prices.data[qr.toToken.coingeckoId];
const nativeFromPrice = prices.data[this.tokenList.nativeTokens[qr.fromChainId].coingeckoId];
const nativeToPrice = prices.data[this.tokenList.nativeTokens[qr.toChainId].coingeckoId];

Expand Down
10 changes: 5 additions & 5 deletions src/watchers/mayan-explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ export class MayanExplorerWatcher {
}
}

private createSwapFromJson(rawSwap: any) {
const fromToken = this.tokenList.getTokenData(+rawSwap.sourceChain, rawSwap.fromTokenAddress);
const toToken = this.tokenList.getTokenData(+rawSwap.destChain, rawSwap.toTokenAddress);
private async createSwapFromJson(rawSwap: any) {
const fromToken = await this.tokenList.getTokenData(+rawSwap.sourceChain, rawSwap.fromTokenAddress);
const toToken = await this.tokenList.getTokenData(+rawSwap.destChain, rawSwap.toTokenAddress);
const swap: Swap = {
retries: 0,
trader: rawSwap.trader,
Expand Down Expand Up @@ -115,7 +115,7 @@ export class MayanExplorerWatcher {
return;
}

const swap = this.createSwapFromJson(rawSwap);
const swap = await this.createSwapFromJson(rawSwap);

logger.info(
`Received explorer swap with ` +
Expand Down Expand Up @@ -167,7 +167,7 @@ export class MayanExplorerWatcher {
return;
}

const swap = this.createSwapFromJson(s);
const swap = await this.createSwapFromJson(s);

this.relayer.relay(swap);
}
Expand Down

0 comments on commit bebec15

Please sign in to comment.