const NETWORK_NEAR = "NEAR"; const NETWORK_ETH = "ETH"; const NETWORK_ZKSYNC = "ZKSYNC"; State.init({ inputAssetModalHidden: true, outputAssetModalHidden: true, inputAssetAmount: 1, outputAssetAmount: 0, slippagetolerance: "0.5", reloadPools: false, estimate: {}, loadRes: (value) => { if (value.estimate === "NaN") value.estimate = 0; State.update({ estimate: value, outputAssetAmount: value === null ? "" : value.estimate, }); }, }); const refReferralId = props.refReferralId ?? "ukraine"; const forceNetwork = props.forceNetwork; const getEVMAccountId = () => { if (ethers !== undefined) { return Ethers.send("eth_requestAccounts", [])[0] ?? ""; } return ""; }; if (state.sender === undefined) { return State.update({ sender: getEVMAccountId(), }); } const onDexDataLoad = (data) => { console.log("onDexDataLoad", data); State.update({ ...data, inputAsset: undefined, outputAsset: undefined, sender: getEVMAccountId(), }); }; // LOAD STYLE const css = fetch( "https://gist.githubusercontent.com/zavodil/5786d09502b0fbd042a920d804259130/raw/8dfc1154f6a9ebc5274463f60521385cc3728a19/swap.css" ).body; if (!css) return ""; if (!state.theme) { State.update({ theme: styled.div` ${css} `, }); } const Theme = state.theme; // USER FUNCTIONS const currentAccountId = getEVMAccountId() !== "" ? getEVMAccountId() : context.accountId; const rearrangeAssets = () => { State.update({ inputAssetTokenId: state.outputAssetTokenId, outputAssetTokenId: state.inputAssetTokenId, inputAsset: undefined, outputAsset: undefined, inputAssetAmount: state.outputAssetAmount, outputAssetAmount: state.inputAssetAmount, approvalNeeded: undefined, }); }; // REUSABLE UI ELEMEETS const assetContainer = ( isInputAsset, assetData, amountName, assetNameOnClick ) => { if (!assetData) return; const useSpacer = !!isInputAsset; const assetContainerClass = useSpacer ? "asset-container-top" : "asset-container-bottom"; return ( <> <div class={`${assetContainerClass} asset-container`}> <div class="swap-currency-input"> <div class="swap-currency-input-block"> <div class="swap-currency-input-top"> <input class="input-asset-amount" nputmode="decimal" autocomplete="off" autocorrect="off" type="text" pattern="^[0-9]*[.,]?[0-9]*$" placeholder="0" minlength="1" maxlength="79" spellcheck="false" value={state[amountName]} /> <button class="input-asset-token" onClick={assetNameOnClick}> <span class="input-asset-token-menu"> <div class="input-asset-token-name"> <div class="input-asset-token-icon"> <img alt={`${assetData.metadata.name} logo`} src={assetData.metadata.icon} class="input-asset-token-icon-img" /> </div> <span class="input-asset-token-ticker"> {assetData.metadata.symbol} </span> </div> <svg width="12" height="7" viewBox="0 0 12 7" fill="none" xmlns="http://www.w3.org/2000/svg" class="input-asset-token-expand" > <path d="M0.97168 1L6.20532 6L11.439 1" stroke="#AEAEAE" ></path> </svg> </span> </button> </div> <div class="input-asset-details-container"> <div class="input-asset-details-row"> <div class="input-asset-details-price-container"> <div class="input-asset-details-price"> <div>${assetData.price}</div> </div> </div> <div class="input-asset-details-balance-container"> <div class="input-asset-details-balance-text"> Balance: {assetData.balance_hr} </div> {isInputAsset && Number(state.inputAssetAmount) !== Number(assetData.balance_hr_full) && ( <button class="input-asset-details-balance-button" onClick={() => State.update({ [amountName]: assetData.balance_hr_full ?? 0, }) } > Max </button> )} </div> </div> </div> {false && <div class="swap-currency-input-bottom"></div>} </div> </div> </div> {useSpacer ? spacerContainer : <></>} </> ); }; const spacerContainer = ( <div class="spacer-container"> <div class="spacer-block" onClick={rearrangeAssets}> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#0D111C" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" > <line x1="12" y1="5" x2="12" y2="19"></line> <polyline points="19 12 12 19 5 12"></polyline> </svg> </div> </div> ); // SWAP METHODS const expandToken = (value, decimals) => { return new Big(value).mul(new Big(10).pow(decimals)); }; const getRefTokenObject = (tokenId, assetData) => { return { id: tokenId, decimals: assetData.metadata.decimals, symbol: assetData.metadata.symbol, }; }; const tokenInApprovaleNeededCheck = () => { if (state.approvalNeeded === undefined) { if ( getEVMAccountId() && state.erc20Abi !== undefined && state.routerContract !== undefined && [NETWORK_ETH, NETWORK_ZKSYNC].includes(state.network) ) { const ifaceErc20 = new ethers.utils.Interface(state.erc20Abi); const encodedTokenAllowancesData = ifaceErc20.encodeFunctionData( "allowance", [getEVMAccountId(), state.routerContract] ); return Ethers.provider() .call({ to: state.inputAssetTokenId, data: encodedTokenAllowancesData, }) .then((encodedTokenAllowanceHex) => { const tokenAllowance = ifaceErc20.decodeFunctionResult( "allowance", encodedTokenAllowanceHex ); if (tokenAllowance) { State.update({ approvalNeeded: new Big(tokenAllowance).toFixed() == "0", }); } }); } else { State.update({ approvalNeeded: false }); } } }; if (state.network === NETWORK_ZKSYNC || state.network == NETWORK_ETH) { tokenInApprovaleNeededCheck(); } const canSwap = state.network && Number(state.inputAsset.balance_hr_full) >= Number(state.inputAssetAmount) && Number(state.inputAssetAmount ?? 0) > 0; const onCallTxComple = (tx) => { console.log("transactionHash", tx); State.update({ outputAsset: undefined, }); }; // OUTPUT if (forceNetwork && state.network && forceNetwork !== state.network) { return ( <Theme> <div class="swap-main-container pt-5"> To proceed, kindly switch to {forceNetwork}. {!state.sender && ( <div class="swap-button-container"> <Web3Connect className="swap-button-enabled swap-button-text p-2" connectLabel="Connect with Web3" /> </div> )} </div> </Theme> ); } return ( <Theme> <Widget src="zavodil.near/widget/DexData" props={{ onLoad: onDexDataLoad, NETWORK_NEAR, NETWORK_ETH, NETWORK_ZKSYNC, }} /> {state.network && state.inputAsset && state.inputAssetTokenId && ( <Widget src="zavodil.near/widget/AssetListModal" props={{ hidden: state.inputAssetModalHidden ?? true, network: state.network, assets: state.assets, coinGeckoTokenIds: state.coinGeckoTokenIds, selectedAssets: [state.inputAssetTokenId], onClick: (tokenId) => { State.update({ inputAssetModalHidden: true, inputAssetTokenId: tokenId, inputAsset: null, approvalNeeded: undefined, }); }, onClose: () => State.update({ inputAssetModalHidden: true }), }} /> )} {state.network && state.outputAsset && state.outputAssetTokenId && ( <Widget src="zavodil.near/widget/AssetListModal" props={{ hidden: state.outputAssetModalHidden ?? true, assets: state.assets, coinGeckoTokenIds: state.coinGeckoTokenIds, network: state.network, selectedAssets: [state.outputAssetTokenId], onClick: (tokenId) => { State.update({ outputAssetModalHidden: true, outputAssetTokenId: tokenId, outputAsset: null, }); }, onClose: () => State.update({ outputAssetModalHidden: true }), }} /> )} {!state.inputAsset && state.network && state.inputAssetTokenId && ( <Widget src="zavodil.near/widget/TokenData" props={{ tokenId: state.inputAssetTokenId, coinGeckoTokenId: state?.coinGeckoTokenIds?.[state.inputAssetTokenId], network: state.network, onLoad: (inputAsset) => { console.log("TokenData onLoad inputAsset", inputAsset); inputAsset.metadata.symbol = inputAsset.metadata.symbol.toUpperCase(); State.update({ inputAsset }); }, }} /> )} {!state.outputAsset && state.network && state.outputAssetTokenId && ( <Widget src="zavodil.near/widget/TokenData" props={{ tokenId: state.outputAssetTokenId, coinGeckoTokenId: state?.coinGeckoTokenIds?.[state.outputAssetTokenId], network: state.network, onLoad: (outputAsset) => { console.log("TokenData onLoad outputAsset", outputAsset); outputAsset.metadata.symbol = outputAsset.metadata.symbol.toUpperCase(); State.update({ outputAsset }); }, }} /> )} {state.network === NETWORK_NEAR && state.inputAsset && state.outputAsset && ( <Widget src="weige.near/widget/ref-swap-getEstimate" props={{ loadRes: state.loadRes, tokenIn: getRefTokenObject( state.inputAssetTokenId, state.inputAsset ), tokenOut: getRefTokenObject( state.outputAssetTokenId, state.outputAsset ), amountIn: state.inputAssetAmount ?? 0, reloadPools: state.reloadPools, setReloadPools: (value) => State.update({ reloadPools: value, }), }} /> )} {state.network === NETWORK_ETH && state.inputAssetTokenId && state.outputAssetTokenId && state.inputAssetTokenId !== state.outputAssetTokenId && state.inputAssetAmount && state.inputAsset && state.inputAsset.metadata?.decimals && state.outputAsset && state.outputAsset.metadata?.decimals && ( <> <Widget src="zavodil.near/widget/uni-v3-getEstimate" props={{ loadRes: state.loadRes, tokenIn: state.inputAssetTokenId, tokenOut: state.outputAssetTokenId, tokenOutDecimals: state.outputAsset.metadata.decimals, amountIn: expandToken( state.inputAssetAmount, state.inputAsset.metadata.decimals ).toFixed(0), reloadPools: state.reloadPools, setReloadPools: (value) => State.update({ reloadPools: value, }), }} /> </> )} {state.network === NETWORK_ZKSYNC && state.inputAsset && state.outputAsset && state.inputAssetAmount && state.outputAsset.price && state.inputAsset.price && state.loadRes({ estimate: ( (parseFloat(state.inputAssetAmount) * parseFloat(state.inputAsset.price)) / parseFloat(state.outputAsset.price) ).toFixed(18), })} <div class="swap-root"> <div class="swap-main-container"> <div class="swap-main-column"> <div class="swap-page"> {state.network && state.dexName && ( <span class="ps-2" style={{ color: "#7780a0" }}> {state.dexName} ({state.network}) </span> )} <div class="top-container"> {assetContainer( true, state.inputAsset, "inputAssetAmount", () => { State.update({ inputAssetModalHidden: false }); } )} </div> <div class="bottom-container"> <div> {assetContainer( fasle, state.outputAsset, "outputAssetAmount", () => { State.update({ outputAssetModalHidden: false }); } )} {!!state.outputAssetAmount && state.inputAssetTokenId !== state.outputAssetTokenId && ( <div class="swap-price-container"> <div class="swap-price-block"> <div class="swap-price-grid"> <div class="swap-price-row"> <div class="swap-price-details-container"> <span> <div class="swap-price-details-icon"> <div> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#98A1C0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="swap-price-details-svg" > <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="16" x2="12" y2="12" ></line> <line x1="12" y1="8" x2="12.01" y2="8" ></line> </svg> </div> </div> </span> <div class="swap-price-details-text"> <button class="swap-price-details-text-button"> <div class="swap-price-details-rate"> {Number(state.inputAssetAmount) === 0 || Number(state.outputAssetAmount) === 0 ? " " : `1 ${ state.inputAsset.metadata.symbol } ≈ ${new Big( state.outputAssetAmount ?? 0 ) .div(state.inputAssetAmount ?? 1) .toFixed(4, 0)} ${state.outputAsset.metadata.symbol}`} </div> <div class="swap-price-details-price"> {Number(state.inputAssetAmount) === 0 || Number(state?.outputAsset?.price) === 0 ? "" : `($${new Big( state.outputAssetAmount ?? 0 ) .div(state.inputAssetAmount ?? 1) .times(state?.outputAsset?.price ?? 1) .toFixed(4)})`} </div> </button> </div> </div> </div> </div> </div> </div> )} </div> <div class="swap-button-container"> {state.approvalNeeded === true && ( <button class={"swap-button-enabled"} onClick={() => { state.callTokenApproval(state, () => { onCallTxComple(); tokenInApprovaleNeededCheck(); }); }} > <div class="swap-button-text"> Approve {state.inputAsset.metadata.symbol} </div> </button> )} {state.approvalNeeded !== true && ( <button class={canSwap ? "swap-button-enabled" : "swap-button"} onClick={() => { if (canSwap) { if (state.network === NETWORK_NEAR) { state.callTx(state, onCallTxComple); } else if (state.network === NETWORK_ZKSYNC) { state.callTx(state, onCallTxComple); } else if (state.network === NETWORK_ETH) { state.callTx(state, onCallTxComple); } } }} > <div class="swap-button-text">Swap</div> </button> )} </div> </div> </div> </div> <div class="pt-3 text-secondary opacity-25 text-center"> <p> Supported networks: {NETWORK_NEAR}, {NETWORK_ETH}, {NETWORK_ZKSYNC} </p> {currentAccountId && <p>Current account: {currentAccountId}</p>} </div> </div> </div> </Theme> );