Skip to content

Commit

Permalink
feat(@nftx/trade): add fulfill function
Browse files Browse the repository at this point in the history
this function takes any quote from fetchQuote and fulfills it, regardless of whether it's a sell/buy, a mint/redeem, or an ERC20 quote
  • Loading branch information
jackmellis committed May 23, 2024
1 parent 6d71440 commit 7ec4899
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/api/src/prices/fetchEthPrice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fetchSpotPrice from './fetchSpotPrice';

/** Returns the current price of ETH in USDC terms */
const fetchEthPrice = async ({ network }: { network?: number }) => {
const { price } = await fetchSpotPrice({
network,
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/prices/fetchPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import fetchQuote, {
SwapArgs,
} from './fetchQuote';

/** Returns an off-chain price for a transaction */
function fetchPrice(args: BuyArgs): Promise<MarketplacePrice>;
function fetchPrice(args: SellArgs): Promise<MarketplacePrice>;
function fetchPrice(args: SwapArgs): Promise<MarketplacePrice>;
Expand Down
5 changes: 4 additions & 1 deletion packages/api/src/prices/fetchQuote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type QuoteArgs = {
permit2?: Permit2Quote;
};

/** Returns an on-chain quote for a transaction. The response object can be passed into @nftx/trade's fulfill method to execute the quote */
function fetchQuote(args: BuyArgs & PriceArgs): Promise<MarketplacePrice>;
function fetchQuote(args: BuyArgs & QuoteArgs): Promise<MarketplaceQuote>;
function fetchQuote(args: SellArgs & PriceArgs): Promise<MarketplacePrice>;
Expand Down Expand Up @@ -122,10 +123,12 @@ function fetchQuote(args: any) {
? MarketplacePrice
: MarketplaceQuote;

const method = quoteType === 'price' ? 'GET' : 'POST';

return queryApi<QuoteType>({
url,
query,
method: quoteType === 'price' ? 'GET' : 'POST',
method,
});
}

Expand Down
9 changes: 7 additions & 2 deletions packages/api/src/prices/fetchSpotPrice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { QuoteToken } from '@nftx/types';
import fetchPrice from './fetchPrice';
import { WeiPerEther } from '@nftx/constants';
import { UnknownError } from '@nftx/errors';

/** Returns the spot price for buying a token */
const fetchSpotPrice = async ({
tokenAddress,
amount = WeiPerEther,
Expand All @@ -13,8 +15,10 @@ const fetchSpotPrice = async ({
quoteToken?: QuoteToken;
amount?: bigint;
}) => {
// We start by attempting to quote 1 whole token, but if it fails (e.g. due to insufficient liquidity),
// we can try to quote a smaller fraction of the token until we find a price that works.
let fraction = WeiPerEther;
let error: any = new Error('Failed to fetch spot price');
let error: any = new UnknownError('Failed to fetch spot price');

do {
try {
Expand All @@ -26,7 +30,8 @@ const fetchSpotPrice = async ({
buyAmount: fraction,
});

quote.price = (quote.price * amount) / fraction;
// Extrapolate the price to determine the spot price for 1 whole token, then scale it by the desired amount.
quote.price = quote.vTokenPrice = (quote.price * amount) / fraction;

return quote;
} catch (e) {
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/prices/fetchSpread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { WeiPerEther, Zero } from '@nftx/constants';
import { QuoteToken } from '@nftx/types';
import fetchPrice from './fetchPrice';

/** Returns the difference between a buy and sell of a single token */
const fetchSpread = async ({
tokenAddress,
network,
Expand Down
36 changes: 19 additions & 17 deletions packages/trade/src/price/ammQuoteToPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,24 @@ const ammQuoteToPrice = (quote: NftxQuote) => {
);
}

const methodParameters: MarketplaceQuote['methodParameters'] | undefined =
quote.methodParameters
? {
amountsIn: [],
amountsOut: [],
premiumLimit: '',
standard: 'ERC20',
tokenIdsIn: [],
tokenIdsOut: [],
vaultAddress: '0x',
vaultId: '',
executeCalldata: quote.methodParameters.calldata,
to: quote.methodParameters.to,
value: quote.methodParameters.value,
}
: undefined;
let methodParameters =
undefined as any as MarketplaceQuote['methodParameters'];

if (quote.methodParameters) {
methodParameters = {
amountsIn: [],
amountsOut: [],
premiumLimit: '',
standard: 'ERC20',
tokenIdsIn: [],
tokenIdsOut: [],
vaultAddress: '0x',
vaultId: '',
executeCalldata: quote.methodParameters.calldata,
to: quote.methodParameters.to,
value: quote.methodParameters.value,
};
}

const price: MarketplaceQuote = {
approveContracts,
Expand All @@ -91,7 +93,7 @@ const ammQuoteToPrice = (quote: NftxQuote) => {
type: 'erc20',
vTokenPrice: BigInt(quote.quote),
routeString: quote.routeString,
methodParameters: methodParameters as MarketplaceQuote['methodParameters'],
methodParameters: methodParameters,
};

return price;
Expand Down
40 changes: 40 additions & 0 deletions packages/trade/src/trade/fulfill.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { MarketplaceQuote, Provider, Signer } from '@nftx/types';
import tradeErc20 from './tradeErc20';
import swap from './swap';
import redeem from './redeem';
import mint from './mint';
import sell from './sell';
import buy from './buy';
import { UnknownError } from '@nftx/errors';

/** Fulfills any quote returned by @nftx/api's fetchQuote method */
const fulfill = ({
network,
quote,
signer,
provider,
}: {
quote: Pick<MarketplaceQuote, 'type' | 'methodParameters'>;
network: number;
signer: Signer;
provider: Provider;
}) => {
switch (quote.type) {
case 'buy':
return buy({ provider, quote, signer, network });
case 'sell':
return sell({ provider, quote, signer, network });
case 'mint':
return mint({ provider, quote, signer });
case 'redeem':
return redeem({ quote, provider, signer });
case 'swap':
return swap({ quote, provider, signer, network });
case 'erc20':
return tradeErc20({ provider, quote, signer, network });
default:
throw new UnknownError(`Unknown quote type: ${quote.type}`);
}
};

export default fulfill;
1 change: 1 addition & 0 deletions packages/trade/src/trade/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { default as redeem } from './redeem';
export { default as sell } from './sell';
export { default as swap } from './swap';
export { default as tradeErc20 } from './tradeErc20';
export { default as fulfill } from './fulfill';
2 changes: 1 addition & 1 deletion packages/types/src/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type ApproveContract = {
standard?: 'ERC721' | 'ERC1155' | 'ERC20';
};

/** A price object for buying/selling/swapping an NFT through the marketplace zap or trading an ERC20 through our AMM */
/** A price object for buying/selling/swapping an NFT through the marketplace zap or trading an ERC20 through NFTX's AMM */
export type MarketplacePrice = {
type: 'buy' | 'sell' | 'swap' | 'mint' | 'redeem' | 'erc20';
/** The total price in ETH */
Expand Down

0 comments on commit 7ec4899

Please sign in to comment.