Skip to content

Commit 6fd719d

Browse files
authored
feat: Add meta-transaction sell token fees (0xProject#1275)
1 parent 9a6835d commit 6fd719d

File tree

11 files changed

+959
-174
lines changed

11 files changed

+959
-174
lines changed

src/asset-swapper/quote_consumers/feature_rules/transform_erc20_rule.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { IZeroExContract } from '@0x/contract-wrappers';
2929
import { TransformerNonces } from '../types';
3030
import { AbstractFeatureRule } from './abstract_feature_rule';
3131
import * as _ from 'lodash';
32+
import { ZERO } from '../../../constants';
3233

3334
// Transformation of `TransformERC20` feature.
3435
interface ERC20Transformation {
@@ -72,11 +73,12 @@ export class TransformERC20Rule extends AbstractFeatureRule {
7273
isFromETH,
7374
isToETH,
7475
shouldSellEntireBalance,
76+
metaTransactionVersion,
7577
} = opts;
7678

7779
const swapContext = this.getSwapContext(quote, opts);
78-
const { sellToken, buyToken, sellAmount, ethAmount } = swapContext;
79-
let minBuyAmount = swapContext.minBuyAmount;
80+
const { sellToken, buyToken, ethAmount } = swapContext;
81+
let { minBuyAmount, sellAmount } = swapContext;
8082

8183
// Build up the transformations.
8284
const transformations = [] as ERC20Transformation[];
@@ -97,6 +99,17 @@ export class TransformERC20Rule extends AbstractFeatureRule {
9799
})),
98100
}),
99101
});
102+
103+
// Adjust the sell amount by the fee for meta-transaction v1. We don't need to adjust the amount for v2
104+
// since fee transfer won't happen in `transformERC20`
105+
if (metaTransactionVersion === 'v1') {
106+
const totalSellTokenFeeAmount = sellTokenAffiliateFees.reduce(
107+
(totalSellTokenFeeAmount, sellTokenAffiliateFees) =>
108+
totalSellTokenFeeAmount.plus(sellTokenAffiliateFees.sellTokenFeeAmount),
109+
ZERO,
110+
);
111+
sellAmount = sellAmount.plus(totalSellTokenFeeAmount);
112+
}
100113
}
101114

102115
// Create a WETH wrapper if coming from ETH.

src/asset-swapper/swap_quoter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import { QuoteRequestor } from './utils/quote_requestor';
3838
import { assert } from './utils/utils';
3939
import { calculateQuoteInfo } from './utils/quote_info';
4040
import { SignedLimitOrder } from '../asset-swapper';
41+
import { isNativeSymbolOrAddress, isNativeWrappedSymbolOrAddress } from '@0x/token-metadata';
42+
import { CHAIN_ID } from '../config';
4143

4244
export class SwapQuoter {
4345
public readonly provider: ZeroExProvider;
@@ -158,6 +160,22 @@ export class SwapQuoter {
158160
return new BigNumber(gasPrices.fast);
159161
}
160162

163+
/**
164+
* Returns the token amount per wei by checking token <-> native token in sampler. Note that the function name & output ends with `PerWei`
165+
* instead of `AmountPerEth` which is a legacy naming convention used in the repo that intends to mean per wei.
166+
*/
167+
public async getTokenAmountPerWei(
168+
tokenAddress: string,
169+
options: Partial<SwapQuoteRequestOpts>,
170+
): Promise<BigNumber> {
171+
// Return 1 if `token` is native or wrapped native token
172+
if (isNativeSymbolOrAddress(tokenAddress, CHAIN_ID) || isNativeWrappedSymbolOrAddress(tokenAddress, CHAIN_ID)) {
173+
return new BigNumber(1);
174+
}
175+
176+
return this._marketOperationUtils.getTokenAmountPerWei(tokenAddress, options);
177+
}
178+
161179
/**
162180
* Get a `SwapQuote` containing all information relevant to fulfilling a swap between a desired ERC20 token address and ERC20 owned by a provided address.
163181
* You can then pass the `SwapQuote` to a `SwapQuoteConsumer` to execute a buy, or process SwapQuote for on-chain consumption.

src/asset-swapper/utils/market_operation_utils/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,35 @@ export class MarketOperationUtils {
138138
return this.getMarketBuyLiquidity(makerToken, takerToken, limitOrders, amount, opts);
139139
}
140140

141+
/**
142+
* Get the token amount per wei for native token. Note:
143+
* - This function would make an `eth_call` and shouldn't be used in most cases
144+
* - This function does not perform a full search (only perform a direct price check on `token` and native token) and
145+
* calls `sampler.getBestNativeTokenSellRate` internally
146+
*
147+
* @param tokenAddress The token to check price for.
148+
* @param opts GetMarketOrdersOpts object.
149+
* @returns Token amount per wei.
150+
*/
151+
public async getTokenAmountPerWei(tokenAddress: string, opts?: Partial<GetMarketOrdersOpts>): Promise<BigNumber> {
152+
const optsWithDefault = { ...DEFAULT_GET_MARKET_ORDERS_OPTS, ...opts };
153+
const [tokenAmountPerWei] = await this.sampler.executeAsync(
154+
this.sampler.getBestNativeTokenSellRate(
155+
this._feeSources,
156+
tokenAddress,
157+
this._nativeFeeToken,
158+
this._nativeFeeTokenAmount,
159+
optsWithDefault.feeSchedule,
160+
),
161+
);
162+
163+
if (tokenAmountPerWei.isZero()) {
164+
NO_CONVERSION_TO_NATIVE_FOUND.labels('getTokenAmountPerWei', opts?.endpoint ?? 'N/A').inc();
165+
}
166+
167+
return tokenAmountPerWei;
168+
}
169+
141170
/**
142171
* Gets the liquidity available for a market sell operation
143172
* @param makerToken Maker token address

src/constants.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
2727
export const ONE_GWEI = new BigNumber(1000000000);
2828
export const AFFILIATE_DATA_SELECTOR = '869584cd';
2929
export const DEFAULT_META_TX_MIN_ALLOWED_SLIPPAGE = 0.001;
30+
export const AVG_MULTIPLEX_TRANFORM_ERC_20_GAS = new BigNumber(933e3);
31+
/**
32+
* `transformerTransfer` estimated gas (used by `AffiliateFeeTransformer`):
33+
* - Decrease balance of the owner (flash wallet):
34+
* - SLOAD: 2,100
35+
* - SSTORE: 2,900 (original value is non-zero)
36+
* - Increase balance of the spender (fee recipient):
37+
* - SLOAD: 2,100
38+
* - SSTORE: 20,000 (worst case scenario: original value is zero)
39+
*/
40+
export const TRANSFER_GAS = new BigNumber(27e3);
41+
/**
42+
* `_transferERC20TokensFrom` estimated gas (used by `FixinTokenSpender`):
43+
* - Decrease balance of the owner (taker):
44+
* - SLOAD: 2,100
45+
* - SSTORE: 2,900 (original value is non-zero)
46+
* - Increase balance of the spender (one of the 0x contracts):
47+
* - SLOAD: 2,100
48+
* - SSTORE: 20,000 (worst case scenario: original value is zero)
49+
* - Decrease allowance of the spender if necessary (one of the 0x contracts)
50+
* - SLOAD: 2,100
51+
* - SSTORE: 2,900 (original value is non-zero)
52+
*/
53+
export const TRANSFER_FROM_GAS = new BigNumber(32e3);
3054

3155
// API namespaces
3256
export const SRA_PATH = '/sra/v4';

src/services/meta_transaction_service.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export class MetaTransactionService implements IMetaTransactionService {
7777
allowanceTarget: quote.allowanceTarget,
7878
sellTokenToEthRate: quote.sellTokenToEthRate,
7979
buyTokenToEthRate: quote.buyTokenToEthRate,
80+
fees: quote.fees,
8081
};
8182

8283
// Generate meta-transaction
@@ -216,7 +217,7 @@ export class MetaTransactionService implements IMetaTransactionService {
216217
endpoint,
217218
isUnwrap: false,
218219
isWrap: false,
219-
metaTransactionVersion: 'v1',
220+
metaTransactionVersion: params.metaTransactionVersion,
220221
sellToken: params.sellTokenAddress,
221222
shouldSellEntireBalance: false,
222223
skipValidation: true,
@@ -271,6 +272,7 @@ export class MetaTransactionService implements IMetaTransactionService {
271272
buyTokenAddress: params.buyTokenAddress,
272273
sellTokenAddress: params.sellTokenAddress,
273274
taker: params.takerAddress,
275+
fees: quote.fees,
274276
};
275277
}
276278

0 commit comments

Comments
 (0)