diff --git a/examples/interface/src/components/ButtonClick.tsx b/examples/interface/src/components/ButtonClick.tsx index 5adf71c..3cc805c 100644 --- a/examples/interface/src/components/ButtonClick.tsx +++ b/examples/interface/src/components/ButtonClick.tsx @@ -25,6 +25,7 @@ const ButtonClick = () => { const ekubo_core_address = useMemo(() => "0x031e8a7ab6a6a556548ac85cbb8b5f56e8905696e9f13e9a858142b8ee0cc221", []) const avnu_address = useMemo(() => "0x07e36202ace0ab52bf438bd8a8b64b3731c48d09f0d8879f5b006384c2f35032", []) const simple_swapper = useMemo(() => "0x064f7ed2dc5070133ae8ccdf85f01e82507facbe5cdde456e1418e3901dc51a0", []) + const quoter = useMemo(() => "0x042aa743335663ed9c7b52b331ab7f81cc8d65280d311506653f9b5cc22be7cb", []) const provider = new Provider({ sequencer: { network: constants.NetworkName.SN_GOERLI } }); let wrap = new Wrap( @@ -33,6 +34,7 @@ const ButtonClick = () => { eth_address, ekubo_position_address, ekubo_core_address, + quoter, provider ) const getERC1155Balance = useCallback(async () => { @@ -43,9 +45,11 @@ const ButtonClick = () => { const getCurrentPrice = useCallback(async () => { if (!address) return; - // let p = await Wrap.getCurrentPrice(); - // setCurrentPrice(p); - }, [address, erc1155_address]); + if (!account) return; + let p = await Wrap.quoteSingle(FeeAmount.LOWEST, eth_address, BigInt(10** 7), account); + let realPrice = p / (10 ** 7); + setCurrentPrice(realPrice); + }, [address, erc1155_address, account]); useEffect(() => { getERC1155Balance(); @@ -55,6 +59,14 @@ const ButtonClick = () => { return () => clearInterval(interval); }, [getERC1155Balance]); + useEffect(() => { + getCurrentPrice(); + const interval = setInterval(() => { + getCurrentPrice(); + }, 3000); + return () => clearInterval(interval); + }, [getCurrentPrice]); + const handleAddLiquidity = useCallback(() => { if (!account) return; const realERC1155Amount = erc1155Amount; @@ -114,7 +126,7 @@ const ButtonClick = () => {

ERC1155 Balance: {balance}

-

Current Price : {currentPrice}

+

Current Price : 1 ETH = {currentPrice} WERC20

Mint ERC1155

diff --git a/sdk/packages/instaswap-core/src/abi/quoter-abi.json b/sdk/packages/instaswap-core/src/abi/quoter-abi.json new file mode 100644 index 0000000..2ee4036 --- /dev/null +++ b/sdk/packages/instaswap-core/src/abi/quoter-abi.json @@ -0,0 +1,274 @@ +[ + { + "type": "impl", + "name": "QuoterLockerImpl", + "interface_name": "ekubo::interfaces::core::ILocker" + }, + { + "type": "interface", + "name": "ekubo::interfaces::core::ILocker", + "items": [ + { + "type": "function", + "name": "locked", + "inputs": [ + { + "name": "id", + "type": "core::integer::u32" + }, + { + "name": "data", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::array::Array::" + } + ], + "state_mutability": "external" + } + ] + }, + { + "type": "impl", + "name": "QuoterImpl", + "interface_name": "ekubo::quoter::IQuoter" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "struct", + "name": "ekubo::types::i129::i129", + "members": [ + { + "name": "mag", + "type": "core::integer::u128" + }, + { + "name": "sign", + "type": "core::bool" + } + ] + }, + { + "type": "struct", + "name": "ekubo::types::keys::PoolKey", + "members": [ + { + "name": "token0", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "token1", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "fee", + "type": "core::integer::u128" + }, + { + "name": "tick_spacing", + "type": "core::integer::u128" + }, + { + "name": "extension", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "struct", + "name": "core::array::Span::", + "members": [ + { + "name": "snapshot", + "type": "@core::array::Array::" + } + ] + }, + { + "type": "struct", + "name": "ekubo::quoter::Route", + "members": [ + { + "name": "pool_keys", + "type": "core::array::Span::" + } + ] + }, + { + "type": "struct", + "name": "ekubo::quoter::QuoteParameters", + "members": [ + { + "name": "amount", + "type": "ekubo::types::i129::i129" + }, + { + "name": "specified_token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "route", + "type": "ekubo::quoter::Route" + } + ] + }, + { + "type": "struct", + "name": "ekubo::quoter::QuoteResult", + "members": [ + { + "name": "amount", + "type": "ekubo::types::i129::i129" + }, + { + "name": "other_token", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "struct", + "name": "ekubo::quoter::QuoteSingleParameters", + "members": [ + { + "name": "amount", + "type": "ekubo::types::i129::i129" + }, + { + "name": "specified_token", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + } + ] + }, + { + "type": "struct", + "name": "core::integer::u256", + "members": [ + { + "name": "low", + "type": "core::integer::u128" + }, + { + "name": "high", + "type": "core::integer::u128" + } + ] + }, + { + "type": "struct", + "name": "ekubo::types::delta::Delta", + "members": [ + { + "name": "amount0", + "type": "ekubo::types::i129::i129" + }, + { + "name": "amount1", + "type": "ekubo::types::i129::i129" + } + ] + }, + { + "type": "interface", + "name": "ekubo::quoter::IQuoter", + "items": [ + { + "type": "function", + "name": "quote", + "inputs": [ + { + "name": "params", + "type": "ekubo::quoter::QuoteParameters" + } + ], + "outputs": [ + { + "type": "ekubo::quoter::QuoteResult" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "quote_single", + "inputs": [ + { + "name": "params", + "type": "ekubo::quoter::QuoteSingleParameters" + } + ], + "outputs": [ + { + "type": "ekubo::quoter::QuoteResult" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "delta_to_sqrt_ratio", + "inputs": [ + { + "name": "pool_key", + "type": "ekubo::types::keys::PoolKey" + }, + { + "name": "sqrt_ratio", + "type": "core::integer::u256" + } + ], + "outputs": [ + { + "type": "ekubo::types::delta::Delta" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "struct", + "name": "ekubo::interfaces::core::ICoreDispatcher", + "members": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + } + ] + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "core", + "type": "ekubo::interfaces::core::ICoreDispatcher" + } + ] + }, + { + "type": "event", + "name": "ekubo::quoter::Quoter::Event", + "kind": "enum", + "variants": [] + } + ] \ No newline at end of file diff --git a/sdk/packages/instaswap-core/src/tickMath.ts b/sdk/packages/instaswap-core/src/tickMath.ts index 003b292..198aa40 100644 --- a/sdk/packages/instaswap-core/src/tickMath.ts +++ b/sdk/packages/instaswap-core/src/tickMath.ts @@ -1,53 +1,45 @@ import { Decimal } from 'decimal.js-light'; -import JSBI from 'jsbi'; - - -export abstract class TickMath { - - /** - * Cannot be constructed. - */ - private constructor() { } - - public static getTickAtSqrtRatio(sqrt_ratio_x128: bigint): number { - // A fixed point .128 number has at most 128 bits after the decimal, - // which translates to about 10**38.5 in decimal. - // That means ~78 decimals of precision should be able to represent - // any price with full precision. - // Note there can be loss of precision for intermediate calculations, - // but this should be sufficient for just computing the price. - Decimal.set({ precision: 78 }); - - const sqrt_ratio = new Decimal(sqrt_ratio_x128.toString()).div(new Decimal(2).pow(128)); - const tick = sqrt_ratio - .div(new Decimal('1.000001').sqrt()) - .log() - .div(new Decimal('2').log()) - .toFixed(0); - return Number(tick); - - } - - public static getSqrtRatioAtTick(tick: number): bigint { - // A fixed point .128 number has at most 128 bits after the decimal, - // which translates to about 10**38.5 in decimal. - // That means ~78 decimals of precision should be able to represent - // any price with full precision. - // Note there can be loss of precision for intermediate calculations, - // but this should be sufficient for just computing the price. - Decimal.set({ precision: 78 }); - - const sqrt_ratio_x128 = - new Decimal('1.000001') - .sqrt() - .pow(tick) - .mul(new Decimal(2).pow(128)); - return BigInt(sqrt_ratio_x128.toFixed(0)); - } - - public static tryParseTick(): number | undefined { - // TODO - return undefined; - } + + + +export function getTickAtSqrtRatio(sqrt_ratio_x128: bigint): number { + // A fixed point .128 number has at most 128 bits after the decimal, + // which translates to about 10**38.5 in decimal. + // That means ~78 decimals of precision should be able to represent + // any price with full precision. + // Note there can be loss of precision for intermediate calculations, + // but this should be sufficient for just computing the price. + Decimal.set({ precision: 78 }); + + const sqrt_ratio = new Decimal(sqrt_ratio_x128.toString()).div(new Decimal(2).pow(128)); + const tick = sqrt_ratio + .div(new Decimal('1.000001').sqrt()) + .log() + .div(new Decimal('2').log()) + .toFixed(0); + return Number(tick); + +} + +export function getSqrtRatioAtTick(tick: number): bigint { + // A fixed point .128 number has at most 128 bits after the decimal, + // which translates to about 10**38.5 in decimal. + // That means ~78 decimals of precision should be able to represent + // any price with full precision. + // Note there can be loss of precision for intermediate calculations, + // but this should be sufficient for just computing the price. + Decimal.set({ precision: 78 }); + + const sqrt_ratio_x128 = + new Decimal('1.000001') + .sqrt() + .pow(tick) + .mul(new Decimal(2).pow(128)); + return BigInt(sqrt_ratio_x128.toFixed(0)); } + +export function tryParseTick(): number | undefined { + // TODO + return undefined; +} \ No newline at end of file diff --git a/sdk/packages/instaswap-core/src/wrap.ts b/sdk/packages/instaswap-core/src/wrap.ts index bb98562..b740ebd 100644 --- a/sdk/packages/instaswap-core/src/wrap.ts +++ b/sdk/packages/instaswap-core/src/wrap.ts @@ -1,12 +1,13 @@ -import { Contract, uint256, CallData, RawArgs, Call, num, cairo, BigNumberish, Provider } from 'starknet' +import { Contract, uint256, CallData, RawArgs, Call, num, cairo, BigNumberish, Provider, Account, AccountInterface } from 'starknet' import ERC1155 from "./abi/erc1155-abi.json"; import WERC20 from "./abi/werc20-abi.json"; import ERC20 from "./abi/erc20-abi.json"; import EkuboPosition from "./abi/ekubo-position-abi.json"; +import Quoter from "./abi/quoter-abi.json"; import EkuboCore from "./abi/ekubo-core-abi.json"; import { FeeAmount } from './constants'; -import { TickMath } from './tickMath'; +import { getSqrtRatioAtTick, getTickAtSqrtRatio } from './tickMath'; import { Decimal } from 'decimal.js-light'; const MAX_SQRT_RATIO = 6277100250585753475930931601400621808602321654880405518632n; @@ -18,15 +19,17 @@ export class Wrap { public static ERC20Contract: Contract; public static EkuboPosition: Contract; public static EkuboCoreContract: Contract; + public static QuoterContract: Contract; - constructor(ERC1155Address: string, WERC20Address: string, ERC20Address: string, EkuboPositionAddress: string, EkuboCoreAddress: string, provider: Provider) { + constructor(ERC1155Address: string, WERC20Address: string, ERC20Address: string, EkuboPositionAddress: string, EkuboCoreAddress: string, QuoterAddress: string, provider: Provider) { Wrap.ERC1155Contract = new Contract(ERC1155, ERC1155Address, provider); Wrap.WERC20Contract = new Contract(WERC20, WERC20Address, provider); Wrap.ERC20Contract = new Contract(ERC20, ERC20Address, provider); Wrap.EkuboPosition = new Contract(EkuboPosition, EkuboPositionAddress, provider); Wrap.EkuboCoreContract = new Contract(EkuboCore, EkuboCoreAddress, provider); + Wrap.QuoterContract = new Contract(Quoter, QuoterAddress, provider); } // public deposit = async (amount: bigint) => { @@ -92,8 +95,8 @@ export class Wrap { Decimal.set({ precision: 78 }); let lowerSqrtRatioX128 = new Decimal(lowerPrice).sqrt().mul(new Decimal(2).pow(128)).toFixed(0); let upperSqrtRatioX128 = new Decimal(upperPrice).sqrt().mul(new Decimal(2).pow(128)).toFixed(0); - const lowerTick = TickMath.getTickAtSqrtRatio(BigInt(lowerSqrtRatioX128)); - const upperTick = TickMath.getTickAtSqrtRatio(BigInt(upperSqrtRatioX128)); + const lowerTick = getTickAtSqrtRatio(BigInt(lowerSqrtRatioX128)); + const upperTick = getTickAtSqrtRatio(BigInt(upperSqrtRatioX128)); if (lowerTick > upperTick) { throw new Error("lowerTick should be less than upperTick"); } @@ -165,6 +168,41 @@ export class Wrap { } + public static quoteSingle = async (fee: FeeAmount, specified_token: string, amount: bigint, account: AccountInterface): Promise => { + this.QuoterContract.connect(account); + const sortedTokens: Contract[] = [Wrap.ERC20Contract, Wrap.WERC20Contract].sort((a, b) => a.address.localeCompare(b.address)); + let tmp = { + amount: { + mag: amount, + sign: false + }, + specified_token: specified_token, + pool_key: { + token0: sortedTokens[0].address, + token1: sortedTokens[1].address, + fee: Wrap.getFeeX128(fee), + tick_spacing: 200, + extension: 0, + }, + }; + try { + const res = await Wrap.QuoterContract.quote_single(tmp); + return res; + } catch (error: any) { + let inputString = error.toString(); + const substringToFind = "0x3f532df6e73f94d604f4eb8c661635595c91adc1d387931451eacd418cfbd14"; + const substringStartIndex = inputString.indexOf(substringToFind); + + if (substringStartIndex !== -1) { + const startIndex = substringStartIndex + substringToFind.length + 2; // Skip the substring and the following comma and whitespace + const endIndex = inputString.indexOf(",", startIndex); + const extractedString = inputString.substring(startIndex, endIndex).trim(); + return extractedString; + } + return 0; + } + } + public swapFromERC1155ToERC20ByAVNU(erc1155AmountIn: BigNumberish, minERC20AmountOut: BigNumberish, aggregatorAddress: string, userAddress: string, fee: FeeAmount, slippage: number, currentPrice: number) { // sort tokens // TODO check length @@ -385,19 +423,20 @@ export class Wrap { // TODO check length const sortedTokens: Contract[] = [Wrap.ERC20Contract, Wrap.WERC20Contract].sort((a, b) => a.address.localeCompare(b.address)); + let tmp = { + pool_key: { + token0: sortedTokens[0].address, + token1: sortedTokens[1].address, + fee: Wrap.getFeeX128(fee), + tick_spacing: 200, + extension: 0, + }, + initial_tick + }; const mayInitializePool: Call = { contractAddress: Wrap.EkuboCoreContract.address, entrypoint: "maybe_initialize_pool", - calldata: CallData.compile({ - pool_key: { - token0: sortedTokens[0].address, - token1: sortedTokens[1].address, - fee: Wrap.getFeeX128(fee), - tick_spacing: 200, - extension: 0, - }, - initial_tick - }) + calldata: CallData.compile(tmp) } return [mayInitializePool]; }