Skip to content

Commit 44fe162

Browse files
committed
Init piperx swap
1 parent 406874d commit 44fe162

File tree

16 files changed

+4392
-14
lines changed

16 files changed

+4392
-14
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"@polkadot/types-support": "^15.0.1",
106106
"@polkadot/util": "^13.2.3",
107107
"@polkadot/util-crypto": "^13.2.3",
108-
"@subwallet/chain-list": "0.2.98",
108+
"@subwallet/chain-list": "/home/tug/Subwallet/SubWallet-ChainList/packages/chain-list/build",
109109
"@subwallet/keyring": "^0.1.8-beta.0",
110110
"@subwallet/react-ui": "5.1.2-b79",
111111
"@subwallet/ui-keyring": "0.1.8-beta.0",

packages/extension-base/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@reduxjs/toolkit": "^1.9.1",
5656
"@sora-substrate/type-definitions": "^1.17.7",
5757
"@substrate/connect": "^0.8.9",
58-
"@subwallet/chain-list": "0.2.98",
58+
"@subwallet/chain-list": "/home/tug/Subwallet/SubWallet-ChainList/packages/chain-list/build",
5959
"@subwallet/extension-base": "^1.3.15-0",
6060
"@subwallet/extension-chains": "^1.3.15-0",
6161
"@subwallet/extension-dapp": "^1.3.15-0",
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2019-2022 @subwallet/extension-base
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { getCreate2Address, keccak256, solidityPacked } from 'ethers';
5+
import Web3 from 'web3';
6+
7+
export const defaultTokens: string[] = [
8+
'0x40fCa9cB1AB15eD9B5bDA19A52ac00A78AE08e1D',
9+
'0x02F75bdBb4732cc6419aC15EeBeE6BCee66e826f',
10+
'0x6D46EF45795B1c3e2a5f2A3F7aba5ea551be966f'
11+
];
12+
13+
export const fee2TickSpace: { [key: string]: number } = {
14+
10000: 200,
15+
3000: 60,
16+
500: 10
17+
};
18+
export const provider = new Web3.providers.HttpProvider('https://odyssey.storyrpc.io/');
19+
export const web3 = new Web3(provider);
20+
export const WIP_ADDRESS = '0xe8CabF9d1FFB6CE23cF0a86641849543ec7BD7d5';
21+
export const v2FactoryAddress = '0x700722D24f9256Be288f56449E8AB1D27C4a70ca';
22+
export const v2RouterAddress = '0x8812d810EA7CC4e1c3FB45cef19D6a7ECBf2D85D';
23+
export const piperv3QuoterAddress = '0x82C210d4aA5948f68E46Af355C0399c2E921e8e4';
24+
export const piperv3SwapRouterAddress = '0xbBb8B63596d5447a12Ddee557ac9fA326f42B57D';
25+
export const piperv3FactoryAddress = '0xf3d448d7A83F749695c49d8411247fC3868fB633';
26+
export const piperv3NFTPositionManagerAddress = '0xf03c65d9be145746f800E2781eD140F6dd238F38';
27+
export const multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11';
28+
29+
export const v2ComputeAddress = (token0: string, token1: string) => {
30+
const [token0Sorted, token1Sorted] = token0.toLowerCase() < token1.toLowerCase()
31+
? [token0, token1]
32+
: [token1, token0];
33+
34+
const salt = keccak256(solidityPacked(['address', 'address'], [token0Sorted, token1Sorted]));
35+
const initCodeHash = '0x754f724019203c806610a02ada224eb21dbe068a93d50486e52cf0ae30de457a';
36+
37+
return getCreate2Address(v2FactoryAddress, salt, initCodeHash) as `0x${string}`;
38+
};
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2019-2022 @subwallet/extension-base
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { piperv3FactoryAddress, v2ComputeAddress, web3 } from './constant';
5+
import { piperv3FactoryAbi, piperv3PoolAbi, v2PoolAbi } from './piperx_abi';
6+
7+
interface Reserves {
8+
_reserve0: string;
9+
_reserve1: string;
10+
_blockTimestampLast: string;
11+
}
12+
13+
export const v2GetPrice = async (
14+
token1: string,
15+
token2: string
16+
) => {
17+
const pairAddress = v2ComputeAddress(token1, token2);
18+
19+
const pairContract = new web3.eth.Contract(v2PoolAbi, pairAddress);
20+
21+
try {
22+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
23+
const reserve = (await pairContract.methods.getReserves().call()) as Reserves;
24+
25+
// Determine token order (tokens are stored in ascending order by address in Uniswap V2)
26+
const token1_ = token1.toLowerCase() < token2.toLowerCase() ? token1 : token2;
27+
28+
const reserve0 = reserve._reserve0;
29+
const reserve1 = reserve._reserve1;
30+
// Calculate price based on reserves
31+
const price = token1_ === token1
32+
? BigInt(reserve1) / BigInt(reserve0)
33+
: BigInt(reserve0) / BigInt(reserve1);
34+
35+
console.log('price', Number(price) * 10 ** (6 - 18));
36+
37+
return price;
38+
} catch (error) {
39+
console.error('Error fetching reserves:', error);
40+
throw error;
41+
}
42+
};
43+
44+
export const v3GetPrice = async (
45+
token1: string,
46+
token2: string,
47+
fee = 3000 // Default to 0.3% fee tier
48+
): Promise<number> => {
49+
// Compute pool address using V3 factory
50+
const factory = new web3.eth.Contract(piperv3FactoryAbi, piperv3FactoryAddress);
51+
const poolAddress = await factory.methods.getPool(token1, token2, fee).call();
52+
53+
if (poolAddress === '0x0000000000000000000000000000000000000000') {
54+
throw new Error('Pool does not exist');
55+
}
56+
57+
const poolContract = new web3.eth.Contract(piperv3PoolAbi, poolAddress);
58+
59+
try {
60+
const slot0 = await poolContract.methods.slot0().call();
61+
62+
const sqrtPriceX96 = slot0.sqrtPriceX96;
63+
64+
// Convert sqrtPriceX96 to regular price
65+
const price = (Number(sqrtPriceX96) / 2 ** 96) ** 2;
66+
67+
// Determine if price needs to be inverted based on token order
68+
const token0 = await poolContract.methods.token0().call();
69+
70+
console.log('token', [token0, token1]);
71+
72+
return token0.toLowerCase() === token1.toLowerCase() ? price : 1 / price;
73+
} catch (error) {
74+
console.error('Error fetching V3 price:', error);
75+
throw error;
76+
}
77+
};
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2019-2022 @subwallet/extension-base
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { SwapError } from '@subwallet/extension-base/background/errors/SwapError';
5+
import { TransactionError } from '@subwallet/extension-base/background/errors/TransactionError';
6+
import { _getSimpleSwapEarlyValidationError } from '@subwallet/extension-base/core/logic-validation';
7+
import { BalanceService } from '@subwallet/extension-base/services/balance-service';
8+
import { ChainService } from '@subwallet/extension-base/services/chain-service';
9+
import { _getChainNativeTokenSlug, _getContractAddressOfToken, _isNativeToken } from '@subwallet/extension-base/services/chain-service/utils';
10+
import { BaseStepDetail, CommonFeeComponent, CommonOptimalPath, CommonStepFeeInfo, CommonStepType, OptimalSwapPathParams, SimpleSwapValidationMetadata, SwapEarlyValidation, SwapErrorType, SwapFeeType, SwapProviderId, SwapQuote, SwapRequest, SwapStepType, SwapSubmitParams, SwapSubmitStepData, ValidateSwapProcessParams } from '@subwallet/extension-base/types';
11+
import Web3 from 'web3';
12+
13+
import { calculateSwapRate, SWAP_QUOTE_TIMEOUT_MAP } from '../../utils';
14+
import { SwapBaseHandler, SwapBaseInterface } from '../base-handler';
15+
import { v2GetPrice } from './core';
16+
import { routingExactInput, v2RoutingExactInput, WIP_ADDRESS } from './logic';
17+
18+
export class PiperXSwapHandler implements SwapBaseInterface {
19+
private swapBaseHandler: SwapBaseHandler;
20+
providerSlug: SwapProviderId;
21+
constructor (chainService: ChainService, balanceService: BalanceService) {
22+
this.swapBaseHandler = new SwapBaseHandler({
23+
chainService,
24+
balanceService,
25+
providerName: 'PiperX',
26+
providerSlug: SwapProviderId.PIPERX
27+
});
28+
this.providerSlug = SwapProviderId.PIPERX;
29+
}
30+
31+
get chainService () {
32+
return this.swapBaseHandler.chainService;
33+
}
34+
35+
get balanceService () {
36+
return this.swapBaseHandler.balanceService;
37+
}
38+
39+
get providerInfo () {
40+
return this.swapBaseHandler.providerInfo;
41+
}
42+
43+
get name () {
44+
return this.swapBaseHandler.name;
45+
}
46+
47+
get slug () {
48+
return this.swapBaseHandler.slug;
49+
}
50+
51+
validateSwapProcess: (params: ValidateSwapProcessParams) => Promise<TransactionError[]>;
52+
isReady?: boolean | undefined;
53+
init?: (() => Promise<void>) | undefined;
54+
55+
async getSwapQuote (request: SwapRequest): Promise<SwapQuote | SwapError> {
56+
const fromAsset = this.chainService.getAssetBySlug(request.pair.from);
57+
const toAsset = this.chainService.getAssetBySlug(request.pair.to);
58+
const fromChain = this.chainService.getChainInfoByKey(fromAsset.originChain);
59+
const fromChainNativeTokenSlug = _getChainNativeTokenSlug(fromChain);
60+
61+
const fromContract = _getContractAddressOfToken(fromAsset);
62+
const toContract = _getContractAddressOfToken(toAsset);
63+
// const earlyValidation = await this.validateSwapRequest(request);
64+
65+
// if (earlyValidation.error) {
66+
// const metadata = earlyValidation.metadata as SimpleSwapValidationMetadata;
67+
68+
// return _getSimpleSwapEarlyValidationError(earlyValidation.error, metadata);
69+
// }
70+
71+
const defaultFeeToken = _isNativeToken(fromAsset) ? fromAsset.slug : fromChainNativeTokenSlug;
72+
const toAmount = await v2GetPrice(fromContract, toContract);
73+
const networkFee: CommonFeeComponent = {
74+
tokenSlug: fromChainNativeTokenSlug,
75+
amount: '1',
76+
feeType: SwapFeeType.NETWORK_FEE
77+
};
78+
79+
try {
80+
return {
81+
pair: request.pair,
82+
fromAmount: request.fromAmount,
83+
toAmount: toAmount.toString(),
84+
rate: calculateSwapRate(request.fromAmount, toAmount.toString(), fromAsset, toAsset),
85+
provider: this.providerInfo,
86+
aliveUntil: +Date.now() + (SWAP_QUOTE_TIMEOUT_MAP[this.slug] || SWAP_QUOTE_TIMEOUT_MAP.default),
87+
feeInfo: {
88+
feeComponent: [networkFee],
89+
defaultFeeToken,
90+
feeOptions: [defaultFeeToken]
91+
}
92+
} as SwapQuote;
93+
} catch (e) {
94+
return new SwapError(SwapErrorType.ERROR_FETCHING_QUOTE);
95+
}
96+
}
97+
98+
generateOptimalProcess (params: OptimalSwapPathParams): Promise<CommonOptimalPath> {
99+
return this.swapBaseHandler.generateOptimalProcess(params, [
100+
this.getSubmitStep
101+
]);
102+
}
103+
104+
async getSubmitStep (params: OptimalSwapPathParams): Promise<[BaseStepDetail, CommonStepFeeInfo] | undefined> {
105+
if (params.selectedQuote) {
106+
const submitStep = {
107+
name: 'Swap',
108+
type: SwapStepType.SWAP
109+
};
110+
111+
return Promise.resolve([submitStep, params.selectedQuote.feeInfo]);
112+
}
113+
114+
return Promise.resolve(undefined);
115+
}
116+
117+
validateSwapRequest: (request: SwapRequest) => Promise<SwapEarlyValidation>;
118+
public async handleSwapProcess (params: SwapSubmitParams): Promise<SwapSubmitStepData> {
119+
const { currentStep, process } = params;
120+
const type = process.steps[currentStep].type;
121+
122+
switch (type) {
123+
case CommonStepType.DEFAULT:
124+
return Promise.reject(new TransactionError(BasicTxErrorType.UNSUPPORTED));
125+
case SwapStepType.SWAP:
126+
return this.handleSubmitStep(params);
127+
default:
128+
return this.handleSubmitStep(params);
129+
}
130+
}
131+
132+
handleSwapProcess: (params: SwapSubmitParams) => Promise<SwapSubmitStepData>;
133+
handleSubmitStep: (params: SwapSubmitParams) => Promise<SwapSubmitStepData>;
134+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright 2019-2022 @subwallet/extension-base
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
export { PiperXSwapHandler } from './handler';
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright 2019-2022 @subwallet/extension-base
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { fee2TickSpace, multicallAddress, piperv3QuoterAddress, v2RouterAddress, web3 } from './constant';
5+
import { v2GetPrice, v3GetPrice } from './core';
6+
import { multicallAbi, piperv3QuoterAbi, v2RouterAbi } from './piperx_abi';
7+
8+
// eslint-disable-next-line @typescript-eslint/no-var-requires,@typescript-eslint/no-unsafe-assignment
9+
10+
export const WIP_ADDRESS = '0xe8CabF9d1FFB6CE23cF0a86641849543ec7BD7d5';
11+
const multicallContract = new web3.eth.Contract(multicallAbi, multicallAddress);
12+
const v2RouterContract = new web3.eth.Contract(v2RouterAbi, v2RouterAddress);
13+
14+
export const v3RoutingExactInput = async (
15+
tokenIn: string,
16+
tokenOut: string,
17+
tokenInAmount: bigint,
18+
signerAddress: string
19+
): Promise<{ bestRoute: string[]; maxAmountOut: bigint }> => {
20+
const quoterContract = new web3.eth.Contract(piperv3QuoterAbi, piperv3QuoterAddress);
21+
let bestRoute: string[] = [];
22+
let maxAmountOut = BigInt(0);
23+
24+
const calls = Object.keys(fee2TickSpace).map((feeTier) => ({
25+
target: piperv3QuoterAddress,
26+
callData: quoterContract.methods
27+
.quoteExactInputSingle({
28+
tokenIn,
29+
tokenOut,
30+
fee: feeTier,
31+
amountIn: tokenInAmount.toString(),
32+
sqrtPriceLimitX96: '0'
33+
})
34+
.encodeABI()
35+
}));
36+
37+
try {
38+
const aggregateResult = await multicallContract.methods.tryAggregate(false, calls).call({
39+
from: signerAddress,
40+
gas: calls.length * 300000
41+
});
42+
43+
aggregateResult.forEach(([success, returnData], i) => {
44+
if (success) {
45+
try {
46+
const decoded = web3.eth.abi.decodeParameters(['uint256', 'uint256'], returnData);
47+
const amountOut = BigInt(decoded[0]);
48+
49+
if (amountOut > maxAmountOut) {
50+
maxAmountOut = amountOut;
51+
bestRoute = [tokenIn, Object.keys(fee2TickSpace)[i], tokenOut];
52+
}
53+
} catch (error) {
54+
console.log(`Error decoding result for fee tier ${Object.keys(fee2TickSpace)[i]}:`, error);
55+
}
56+
}
57+
});
58+
} catch (error) {
59+
console.error('Multicall tryAggregate failed:', error);
60+
}
61+
62+
console.log('version3', [bestRoute, maxAmountOut]);
63+
64+
return { bestRoute, maxAmountOut };
65+
};
66+
67+
export const v2RoutingExactInput = async (
68+
tokenIn: string,
69+
tokenOut: string,
70+
tokenInAmount: bigint
71+
): Promise<{ bestRoute: string[]; maxAmountOut: bigint }> => {
72+
let bestRoute: string[] = [];
73+
let maxAmountOut = BigInt(0);
74+
75+
try {
76+
const directResult = await v2RouterContract.methods.getAmountsOut(tokenInAmount.toString(), [tokenIn, tokenOut]).call();
77+
78+
console.log('directResult', directResult);
79+
80+
maxAmountOut = BigInt(directResult[1]);
81+
bestRoute = [tokenIn, tokenOut];
82+
} catch (error) {
83+
console.log('Error in direct route calculation:', error);
84+
}
85+
86+
console.log('version2', [bestRoute, maxAmountOut]);
87+
88+
return { bestRoute, maxAmountOut };
89+
};
90+
91+
export const routingExactInput = async (
92+
tokenIn: string,
93+
tokenOut: string,
94+
tokenInAmount: bigint,
95+
signerAddress: string
96+
): Promise<{ bestRoute: string[]; maxAmountOut: bigint }> => {
97+
const { bestRoute: bestRouteV2, maxAmountOut: maxAmountOutV2 } = await v2RoutingExactInput(tokenIn, tokenOut, tokenInAmount);
98+
const { bestRoute: bestRouteV3, maxAmountOut: maxAmountOutV3 } = await v3RoutingExactInput(tokenIn, tokenOut, tokenInAmount, signerAddress);
99+
100+
console.log('Hmm', await v2GetPrice(tokenIn, tokenOut));
101+
console.log('Hmm1', await v3GetPrice(tokenIn, tokenOut));
102+
103+
return maxAmountOutV2 > maxAmountOutV3
104+
? { bestRoute: bestRouteV2, maxAmountOut: maxAmountOutV2 }
105+
: { bestRoute: bestRouteV3, maxAmountOut: maxAmountOutV3 };
106+
};

0 commit comments

Comments
 (0)