├── .idea ├── .gitignore ├── vcs.xml ├── encodings.xml ├── misc.xml └── uiDesigner.xml ├── src ├── main │ ├── resources │ │ ├── static │ │ │ ├── navbar-top-fixed.css │ │ │ ├── accounts.js │ │ │ └── bootstrap.bundle.min.js │ │ ├── application.properties │ │ └── templates │ │ │ ├── openbook.jsp │ │ │ ├── index.jsp │ │ │ ├── view_bot.jsp │ │ │ ├── settings.jsp │ │ │ └── add_bot.jsp │ └── java │ │ └── com │ │ └── mmorrell │ │ ├── model │ │ └── OpenBookContext.java │ │ ├── ArcanaApplication.java │ │ ├── arcana │ │ ├── background │ │ │ ├── ArcanaAccountManager.java │ │ │ └── ArcanaBackgroundCache.java │ │ ├── strategies │ │ │ ├── Strategy.java │ │ │ ├── OpenBookBot.java │ │ │ ├── BotManager.java │ │ │ └── openbook │ │ │ │ └── OpenBookSplUsdc.java │ │ ├── pricing │ │ │ ├── JupiterPricingSource.java │ │ │ └── PythPricingSource.java │ │ └── controller │ │ │ └── ArcanaController.java │ │ └── config │ │ └── ArcanaWebConfig.java └── test │ └── java │ └── ArcanaTests.java ├── README.md ├── .gitignore ├── Dockerfile └── pom.xml /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /src/main/resources/static/navbar-top-fixed.css: -------------------------------------------------------------------------------- 1 | /* Show it is fixed to the top */ 2 | body { 3 | min-height: 75rem; 4 | padding-top: 4.5rem; 5 | } 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/model/OpenBookContext.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class OpenBookContext { 7 | 8 | private String baseWallet; 9 | private String quoteWallet; 10 | private String ooa; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/ArcanaApplication.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ArcanaApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(ArcanaApplication.class, args); 10 | } 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arcana 2 | 3 | Local Web GUI for providing liquidity on Solana DEXes OpenBook and Drift. 4 | 5 | 6 | --- 7 | 8 | ## Use Cases 9 | * A new Token/Project needs two-sided liquidity for their token. 10 | * Quant discovers the secret of the universe and wants to trade it. 11 | * Provide liquidity on long-tail assets with basic spreads. 12 | * HFT market making on competitive DEX pairs like SOL/USDC. -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/background/ArcanaAccountManager.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.background; 2 | 3 | import lombok.Data; 4 | import org.p2p.solanaj.core.Account; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashSet; 9 | import java.util.List; 10 | import java.util.Set; 11 | 12 | @Data 13 | @Component 14 | public class ArcanaAccountManager { 15 | 16 | private List arcanaAccounts = new ArrayList<>(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | .idea/ 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | gcloud.json -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/strategies/Strategy.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.strategies; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.UUID; 7 | 8 | @Slf4j 9 | public abstract class Strategy { 10 | public UUID uuid = UUID.randomUUID(); 11 | public abstract void start(); 12 | 13 | @PostConstruct 14 | public void startupComplete() { 15 | log.info(this.getClass().getSimpleName() + " strategy instantiated."); 16 | } 17 | 18 | public abstract void stop(); 19 | } 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/static/accounts.js: -------------------------------------------------------------------------------- 1 | let arcanaAccountsArray = localStorage.getItem('arcanaAccounts') ? 2 | JSON.parse(localStorage.getItem('arcanaAccounts')) : []; 3 | 4 | function addArcanaAccount(newAccount){ 5 | arcanaAccountsArray.push(newAccount); 6 | localStorage.setItem('arcanaAccounts', JSON.stringify(arcanaAccountsArray)); 7 | } 8 | 9 | function clearAccounts() { 10 | arcanaAccountsArray = []; 11 | localStorage.setItem('arcanaAccounts', JSON.stringify(arcanaAccountsArray)); 12 | } 13 | 14 | function getLoadedArcanaAccounts() { 15 | return arcanaAccountsArray; 16 | } 17 | 18 | // on app load, send all arcana accounts to the backend 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/strategies/OpenBookBot.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.strategies; 2 | 3 | import lombok.Data; 4 | import org.p2p.solanaj.core.PublicKey; 5 | 6 | @Data 7 | public class OpenBookBot { 8 | 9 | private Strategy strategy; 10 | private PublicKey marketId; 11 | private double bpsSpread = 20.0; 12 | private double amountBid = 0.1, amountAsk = 0.1; 13 | 14 | private PublicKey ooa; 15 | private PublicKey baseWallet; 16 | private PublicKey quoteWallet; 17 | 18 | private String priceStrategy = "jupiter"; 19 | 20 | // marketId, bps spread, amount to bid, amount to ask 21 | // ooa pubkey, base wallet pubkey, quote pubkey 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/strategies/BotManager.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.strategies; 2 | 3 | import lombok.Data; 4 | import org.p2p.solanaj.core.Account; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Component 11 | @Data 12 | public class BotManager { 13 | 14 | private Account tradingAccount = new Account(); 15 | private final List botList = new ArrayList<>(); 16 | 17 | public void addBot(OpenBookBot bot) { 18 | bot.getStrategy().start(); 19 | botList.add(bot); 20 | } 21 | 22 | public List getBotList() { 23 | return botList; 24 | } 25 | 26 | public void stopBot(long botId) { 27 | OpenBookBot bot = botList.get((int) botId); 28 | bot.getStrategy().stop(); 29 | botList.remove((int) botId); 30 | } 31 | 32 | public Account getTradingAccount() { 33 | return tradingAccount; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.profiles.active=production 2 | 3 | spring.thymeleaf.prefix=classpath:/templates/ 4 | spring.thymeleaf.suffix=.jsp 5 | spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false 6 | 7 | # Hikari performance 8 | spring.datasource.hikari.data-source-properties.useConfigs=maxPerformance 9 | spring.datasource.hikari.data-source-properties.rewriteBatchedStatements=true 10 | spring.datasource.hikari.maximum-pool-size=64 11 | 12 | # Enable response compression 13 | server.compression.enabled=true 14 | 15 | # The comma-separated list of mime types that should be compressed 16 | server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,image/png,application/font-woff2 17 | 18 | # Compress the response only if the response size is at least 1KB 19 | server.compression.min-response-size=1024 20 | 21 | # Enable HTTP/2 support, if the current environment supports it 22 | server.http2.enabled=true 23 | 24 | server.servlet.session.cookie.same-site=Strict 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.8.6-eclipse-temurin-17 AS build 2 | 3 | # Yourkit 4 | #RUN apt-get update \ 5 | # && apt-get install unzip -y \ 6 | # && apt-get install wget -y 7 | # 8 | #RUN wget https://www.yourkit.com/download/docker/YourKit-JavaProfiler-2022.3-docker.zip -P /home/ && \ 9 | # unzip /home/YourKit-JavaProfiler-2022.3-docker.zip -d /usr/local && \ 10 | # rm /home/YourKit-JavaProfiler-2022.3-docker.zip 11 | 12 | COPY src /home/app/src 13 | COPY pom.xml /home/app 14 | RUN mvn -T 1C -f /home/app/pom.xml clean package -DskipTests -Dmaven.test.skip 15 | 16 | 17 | # 18 | # Package stage 19 | # 20 | FROM ghcr.io/graalvm/jdk:22.3.2 21 | 22 | # Yourkit 23 | # COPY --from=build /usr/local/ /usr/local/ 24 | #ENV JAVA_TOOL_OPTIONS -agentpath:/usr/local/YourKit-JavaProfiler-2022.3/bin/linux-x86-64/libyjpagent.so=port=10001,listen=all 25 | 26 | # Remote Debugging 27 | #ENV JAVA_TOOL_OPTIONS -agentlib:jdwp=transport=dt_socket,address=*:8000,server=y,suspend=n 28 | 29 | COPY --from=build /home/app/target/arcana-1.0-SNAPSHOT.jar /usr/local/lib/arcana.jar 30 | EXPOSE 8080 31 | ENTRYPOINT ["java","-jar","/usr/local/lib/arcana.jar"] -------------------------------------------------------------------------------- /src/test/java/ArcanaTests.java: -------------------------------------------------------------------------------- 1 | import com.mmorrell.serum.model.OpenOrdersAccount; 2 | import com.mmorrell.serum.model.SerumUtils; 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.junit.jupiter.api.Test; 5 | import org.p2p.solanaj.core.PublicKey; 6 | import org.p2p.solanaj.rpc.Cluster; 7 | import org.p2p.solanaj.rpc.RpcClient; 8 | import org.p2p.solanaj.rpc.RpcException; 9 | import org.p2p.solanaj.rpc.types.TokenAccountInfo; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | @Slf4j 15 | public class ArcanaTests { 16 | 17 | private final RpcClient rpcClient = new RpcClient(Cluster.BLOCKDAEMON); 18 | 19 | @Test 20 | public void ooaApiTest() throws RpcException { 21 | Map requiredParams = Map.of("mint", new PublicKey("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn")); 22 | TokenAccountInfo tokenAccount = rpcClient.getApi().getTokenAccountsByOwner(PublicKey.valueOf( 23 | "mikefsWLEcNYHgsiwSRr6PVd7yVcoKeaURQqeDE1tXN"), requiredParams, new HashMap<>()); 24 | 25 | log.info(tokenAccount.toString()); 26 | 27 | PublicKey jitoSolMarket = new PublicKey("JAmhJbmBzLp2aTp9mNJodPsTcpCJsmq5jpr6CuCbWHvR"); 28 | 29 | final OpenOrdersAccount openOrdersAccount = SerumUtils.findOpenOrdersAccountForOwner( 30 | rpcClient, 31 | jitoSolMarket, 32 | PublicKey.valueOf("mikefsWLEcNYHgsiwSRr6PVd7yVcoKeaURQqeDE1tXN") 33 | ); 34 | 35 | log.info("OOA: " + openOrdersAccount.getOwnPubkey().toBase58()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/pricing/JupiterPricingSource.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.pricing; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.extern.slf4j.Slf4j; 6 | import okhttp3.OkHttpClient; 7 | import okhttp3.Request; 8 | import okhttp3.Response; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | 15 | @Slf4j 16 | @Component 17 | public class JupiterPricingSource { 18 | 19 | private final Map priceMap = new HashMap<>(); 20 | private final OkHttpClient okHttpClient; 21 | private final ObjectMapper objectMapper; 22 | 23 | public JupiterPricingSource(OkHttpClient okHttpClient, ObjectMapper objectMapper) { 24 | this.okHttpClient = okHttpClient; 25 | this.objectMapper = objectMapper; 26 | } 27 | 28 | // https://price.jup.ag/v4/price?ids=ORCA&vsAmount=300 29 | // https://price.jup.ag/v4/price?ids=DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263&vsAmount=1500 30 | public Optional getUsdcPriceForSymbol(String symbol, long usdcAmount) { 31 | Request request = new Request.Builder() 32 | .url(String.format("https://price.jup.ag/v4/price?ids=%s&vsAmount=%d", symbol, usdcAmount)) 33 | .build(); 34 | 35 | try (Response response = okHttpClient.newCall(request).execute()) { 36 | String json = response.body().string(); 37 | Map map = objectMapper.readValue(json, new TypeReference<>() { 38 | }); 39 | 40 | double price = (double)((Map)((Map) map.get("data")).get(symbol)).get("price"); 41 | return Optional.of(price); 42 | 43 | } catch (Exception e) { 44 | log.error("Error getting Jupiter price for " + symbol + ", " + e.getMessage()); 45 | return Optional.empty(); 46 | } 47 | } 48 | 49 | public void updatePriceMap(String symbol, double price) { 50 | priceMap.put(symbol, price); 51 | } 52 | 53 | public Optional getCachedPrice(String symbol) { 54 | return Optional.ofNullable(priceMap.get(symbol)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/pricing/PythPricingSource.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.pricing; 2 | 3 | import com.mmorrell.pyth.manager.PythManager; 4 | import com.mmorrell.pyth.model.PriceDataAccount; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.p2p.solanaj.core.PublicKey; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Optional; 11 | 12 | 13 | // Auto feed SOL price into a cache from Pyth 14 | //@Component 15 | @Slf4j 16 | public class PythPricingSource { 17 | 18 | private final PythManager pythManager; 19 | private final PublicKey solUsdPriceDataAccount = new PublicKey("H6ARHf6YXhGYeQfUzQNGk6rDNnLBQKrenN712K4AQJEG"); 20 | private static Optional solPrice = Optional.empty(); 21 | private static Optional solPriceConfidence = Optional.empty(); 22 | 23 | public PythPricingSource(PythManager pythManager) { 24 | this.pythManager = pythManager; 25 | } 26 | 27 | @Scheduled(fixedRate = 190L) 28 | public void updateSolPriceCache() { 29 | final PriceDataAccount priceDataAccount = pythManager.getPriceDataAccount(solUsdPriceDataAccount); 30 | 31 | solPrice = Optional.of(priceDataAccount.getAggregatePriceInfo().getPrice()); 32 | solPriceConfidence = Optional.of(priceDataAccount.getAggregatePriceInfo().getConfidence()); 33 | } 34 | 35 | public double getSolBidPrice() { 36 | if (solPrice.isPresent() && solPriceConfidence.isPresent()) { 37 | return solPrice.get() - solPriceConfidence.get(); 38 | } else { 39 | return 0.0; 40 | } 41 | } 42 | 43 | public double getSolAskPrice() { 44 | if (solPrice.isPresent() && solPriceConfidence.isPresent()) { 45 | return solPrice.get() + solPriceConfidence.get(); 46 | } else { 47 | return 999999.9; 48 | } 49 | } 50 | 51 | public Optional getSolMidpointPrice() { 52 | return solPrice; 53 | } 54 | 55 | public Optional getSolPriceConfidence() { 56 | return solPriceConfidence; 57 | } 58 | 59 | public boolean hasSolPrice() { 60 | return solPrice.isPresent() && solPriceConfidence.isPresent(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/config/ArcanaWebConfig.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.mmorrell.pyth.manager.PythManager; 5 | import com.mmorrell.serum.manager.SerumManager; 6 | import okhttp3.OkHttpClient; 7 | import org.p2p.solanaj.rpc.Cluster; 8 | import org.p2p.solanaj.rpc.RpcClient; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.http.CacheControl; 12 | import org.springframework.scheduling.annotation.EnableScheduling; 13 | import org.springframework.web.filter.ShallowEtagHeaderFilter; 14 | import org.springframework.web.servlet.ViewResolver; 15 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 16 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 17 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 18 | import org.springframework.web.servlet.view.BeanNameViewResolver; 19 | import org.springframework.web.servlet.view.InternalResourceViewResolver; 20 | import org.springframework.web.servlet.view.JstlView; 21 | 22 | import java.util.concurrent.TimeUnit; 23 | 24 | @EnableWebMvc 25 | @EnableScheduling 26 | @Configuration 27 | public class ArcanaWebConfig implements WebMvcConfigurer { 28 | 29 | @Bean 30 | public RpcClient rpcClient() { 31 | return new RpcClient(Cluster.BLOCKDAEMON); 32 | } 33 | 34 | @Bean 35 | public SerumManager serumManager() { 36 | return new SerumManager(rpcClient()); 37 | } 38 | 39 | @Bean 40 | public OkHttpClient okHttpClient() { 41 | return new OkHttpClient(); 42 | } 43 | 44 | @Bean 45 | public ObjectMapper objectMapper() { 46 | return new ObjectMapper(); 47 | } 48 | 49 | @Bean 50 | public PythManager pythManager() { 51 | return new PythManager(rpcClient()); 52 | } 53 | 54 | @Bean 55 | public ViewResolver viewResolver() { 56 | InternalResourceViewResolver bean = new InternalResourceViewResolver(); 57 | bean.setViewClass(JstlView.class); 58 | bean.setPrefix("/templates/"); 59 | bean.setSuffix(".jsp"); 60 | return bean; 61 | } 62 | 63 | @Bean 64 | public BeanNameViewResolver beanNameViewResolver() { 65 | return new BeanNameViewResolver(); 66 | } 67 | 68 | @Bean 69 | public ShallowEtagHeaderFilter shallowEtagHeaderFilter(){ 70 | return new ShallowEtagHeaderFilter(); 71 | } 72 | 73 | @Override 74 | public void addResourceHandlers(final ResourceHandlerRegistry registry) { 75 | WebMvcConfigurer.super.addResourceHandlers(registry); 76 | registry 77 | .addResourceHandler("/static/**") 78 | .addResourceLocations("classpath:/static/") 79 | .setCacheControl( 80 | CacheControl.maxAge(1, TimeUnit.HOURS) 81 | .cachePublic() 82 | .mustRevalidate()); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/resources/templates/openbook.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 🧙‍♂️ Arcana on Solana 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 68 | 69 |
70 |
71 |

🏴‍☠️ OpenBook Markets (0 markets)

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 89 | 90 |
#Market IDBase TokenQuote TokenRebates AccruedStrategy
Add 88 | Strategy
91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 🧙‍♂️ Arcana on Solana 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 68 | 69 |
70 |
71 | ✨ Create Bot » 72 | View OpenBook markets » 73 |
74 |

Active Bots

75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 |
#ExchangeMarketStrategyActions
View
95 |
96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 3.1.2 11 | 12 | 13 | 14 | com.mmorrell 15 | arcana 16 | 1.0-SNAPSHOT 17 | 18 | 19 | 17 20 | 17 21 | UTF-8 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-thymeleaf 28 | 29 | 30 | org.yaml 31 | snakeyaml 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-tomcat 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-undertow 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | com.mmorrell 56 | solanaj 57 | 1.13.3 58 | 59 | 60 | com.mmorrell 61 | serum 62 | 1.13.2 63 | 64 | 65 | com.mmorrell 66 | solanaj 67 | 68 | 69 | 70 | 71 | com.mmorrell 72 | pyth 73 | 1.13.2 74 | 75 | 76 | com.mmorrell 77 | solanaj 78 | 79 | 80 | 81 | 82 | com.google.code.gson 83 | gson 84 | 2.8.6 85 | 86 | 87 | org.json 88 | json 89 | 20160212 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.springframework.boot 97 | spring-boot-maven-plugin 98 | 99 | com.mmorrell.ArcanaApplication 100 | JAR 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/background/ArcanaBackgroundCache.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.background; 2 | 3 | import com.mmorrell.serum.model.Market; 4 | import com.mmorrell.serum.model.SerumUtils; 5 | import org.p2p.solanaj.core.Account; 6 | import org.p2p.solanaj.core.PublicKey; 7 | import org.p2p.solanaj.core.Transaction; 8 | import org.p2p.solanaj.programs.ComputeBudgetProgram; 9 | import org.p2p.solanaj.programs.SystemProgram; 10 | import org.p2p.solanaj.programs.TokenProgram; 11 | import org.p2p.solanaj.rpc.RpcClient; 12 | import org.p2p.solanaj.rpc.RpcException; 13 | import org.p2p.solanaj.rpc.types.ProgramAccount; 14 | import org.p2p.solanaj.rpc.types.config.Commitment; 15 | import org.springframework.stereotype.Component; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | @Component 23 | public class ArcanaBackgroundCache { 24 | 25 | private RpcClient rpcClient; 26 | 27 | public ArcanaBackgroundCache(RpcClient rpcClient) { 28 | this.rpcClient = rpcClient; 29 | this.cachedMarkets = new ArrayList<>(); 30 | } 31 | 32 | private List cachedMarkets; 33 | 34 | // Caches: List of all markets, ... 35 | public List getCachedMarkets() { 36 | if (cachedMarkets.isEmpty()) { 37 | backgroundCacheMarkets(); 38 | } 39 | 40 | return cachedMarkets; 41 | } 42 | 43 | public void backgroundCacheMarkets() { 44 | final List programAccounts; 45 | try { 46 | programAccounts = new ArrayList<>( 47 | rpcClient.getApi().getProgramAccounts( 48 | new PublicKey("srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"), 49 | Collections.emptyList(), 50 | SerumUtils.MARKET_ACCOUNT_SIZE 51 | ) 52 | ); 53 | } catch (RpcException e) { 54 | throw new RuntimeException(e); 55 | } 56 | 57 | cachedMarkets.clear(); 58 | for (ProgramAccount programAccount : programAccounts) { 59 | Market market = Market.readMarket(programAccount.getAccount().getDecodedData()); 60 | 61 | // Ignore fake/erroneous market accounts 62 | if (market.getOwnAddress().equals(new PublicKey("11111111111111111111111111111111"))) { 63 | continue; 64 | } 65 | 66 | cachedMarkets.add(market); 67 | } 68 | } 69 | 70 | public PublicKey wrapSol(Account tradingAccount, Double solAmount) { 71 | Account sessionWsolAccount = new Account(); 72 | Transaction newTx = new Transaction(); 73 | newTx.addInstruction( 74 | ComputeBudgetProgram.setComputeUnitPrice( 75 | 1811_500_000 76 | ) 77 | ); 78 | newTx.addInstruction( 79 | ComputeBudgetProgram.setComputeUnitLimit( 80 | 10_700 81 | ) 82 | ); 83 | double startingAmount = solAmount; 84 | newTx.addInstruction( 85 | SystemProgram.createAccount( 86 | tradingAccount.getPublicKey(), 87 | sessionWsolAccount.getPublicKey(), 88 | (long) (startingAmount * 1000000000.0) + 5039280, //.05 SOL 89 | 165, 90 | TokenProgram.PROGRAM_ID 91 | ) 92 | ); 93 | newTx.addInstruction( 94 | TokenProgram.initializeAccount( 95 | sessionWsolAccount.getPublicKey(), 96 | SerumUtils.WRAPPED_SOL_MINT, 97 | tradingAccount.getPublicKey() 98 | ) 99 | ); 100 | 101 | try { 102 | String txId = rpcClient.getApi().sendTransaction(newTx, List.of(tradingAccount, sessionWsolAccount), null); 103 | } catch (RpcException e) { 104 | return PublicKey.valueOf(""); 105 | } 106 | 107 | return sessionWsolAccount.getPublicKey(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/resources/templates/view_bot.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 🧙‍♂️ Arcana on Solana 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 68 | 69 |
70 |
71 | 🤖 Bot # / 72 |
73 | Strategy: 74 |
75 | Market ID: 76 |
77 | Bps Spread: 78 |
79 | Quote Sizes: Bid , Ask 80 |
81 | OOA: , Base Wallet: , Quote Wallet: 82 | 83 |
84 | Last Bid order:
85 | Last Bid TX: 87 |
88 | Last Ask order:
89 | Last Ask TX: 91 |
92 | Bot Controls: Remove Bot 93 |
94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/main/resources/templates/settings.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 🧙‍♂️ Arcana on Solana 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 60 | 61 | 62 | 63 | 91 | 92 |
93 |
94 |

Settings

95 |
96 | Trading Accounts 97 |
    98 | 99 |
100 | 109 |
110 |
111 | 120 |
121 |
122 |
123 | Current account:
124 |
125 |
126 |
127 |
128 | Private Key (File):

129 | 130 |
131 |
132 |
133 |
134 |
135 | Private Key (Base58):

136 | 137 |
138 |
139 |
140 |
141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/resources/templates/add_bot.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 🧙‍♂️ Arcana on Solana 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 69 | 70 |
71 |
72 |

Start New Bot

73 |
74 |
75 |
76 | 77 | 78 | 79 | 89 | 94 |
95 |
96 | 97 | 98 |
99 |
100 |
101 |
102 | 103 | 104 |
105 |
106 | 107 | 108 |
109 |
110 | 111 | 115 |
116 |
117 |
118 |
119 | 120 | 121 | 123 | 136 |
137 |
138 | 141 | 157 |
158 |
159 |
160 |
161 | 162 | 163 |
164 |
165 | 166 | 167 |
168 |
169 | 170 | 171 |
172 |
173 | 174 |
175 |
176 |
177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/strategies/openbook/OpenBookSplUsdc.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.strategies.openbook; 2 | 3 | import com.mmorrell.arcana.pricing.JupiterPricingSource; 4 | import com.mmorrell.serum.manager.SerumManager; 5 | import com.mmorrell.serum.model.Market; 6 | import com.mmorrell.serum.model.MarketBuilder; 7 | import com.mmorrell.serum.model.Order; 8 | import com.mmorrell.serum.model.OrderTypeLayout; 9 | import com.mmorrell.serum.model.SelfTradeBehaviorLayout; 10 | import com.mmorrell.serum.program.SerumProgram; 11 | import com.mmorrell.arcana.strategies.Strategy; 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.p2p.solanaj.core.Account; 16 | import org.p2p.solanaj.core.PublicKey; 17 | import org.p2p.solanaj.core.Transaction; 18 | import org.p2p.solanaj.programs.ComputeBudgetProgram; 19 | import org.p2p.solanaj.programs.MemoProgram; 20 | import org.p2p.solanaj.rpc.RpcClient; 21 | import org.p2p.solanaj.rpc.RpcException; 22 | 23 | import java.util.List; 24 | import java.util.Optional; 25 | import java.util.Random; 26 | import java.util.concurrent.Executors; 27 | import java.util.concurrent.ScheduledExecutorService; 28 | import java.util.concurrent.ThreadLocalRandom; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | //@Component 32 | @Slf4j 33 | @Getter 34 | public class OpenBookSplUsdc extends Strategy { 35 | 36 | private static final int EVENT_LOOP_INITIAL_DELAY_MS = 0; 37 | private static final int EVENT_LOOP_DURATION_MS = 5000; 38 | 39 | private final RpcClient rpcClient; 40 | private final SerumManager serumManager; 41 | private final ScheduledExecutorService executorService; 42 | private final JupiterPricingSource jupiterPricingSource; 43 | 44 | // Dynamic 45 | private boolean useJupiter = false; 46 | private double bestBidPrice; 47 | private double bestAskPrice; 48 | 49 | // Finals 50 | @Setter 51 | private Account mmAccount; 52 | 53 | private Market solUsdcMarket; 54 | private final MarketBuilder solUsdcMarketBuilder; 55 | 56 | @Setter 57 | private PublicKey marketOoa; 58 | 59 | @Setter 60 | private PublicKey baseWallet; 61 | 62 | @Setter 63 | private PublicKey usdcWallet; 64 | 65 | private static long BID_CLIENT_ID; 66 | private static long ASK_CLIENT_ID; 67 | 68 | private static final float SOL_QUOTE_SIZE = 0.1f; 69 | 70 | @Setter 71 | private float baseAskAmount = SOL_QUOTE_SIZE; 72 | 73 | @Setter 74 | private float usdcBidAmount = SOL_QUOTE_SIZE; 75 | 76 | @Setter 77 | private float askSpreadMultiplier = 1.0012f; 78 | 79 | @Setter 80 | private float bidSpreadMultiplier = 0.9987f; 81 | 82 | private static final float MIN_MIDPOINT_CHANGE = 0.0010f; 83 | 84 | private float lastPlacedBidPrice = 0.0f, lastPlacedAskPrice = 0.0f; 85 | 86 | @Setter 87 | private Order lastBidOrder; 88 | 89 | @Setter 90 | private String lastBidTx; 91 | 92 | @Setter 93 | private Order lastAskOrder; 94 | 95 | @Setter 96 | private String lastAskTx; 97 | 98 | // Used to delay 2000ms on first order place. 99 | private static boolean firstLoadComplete = false; 100 | 101 | public OpenBookSplUsdc(final SerumManager serumManager, 102 | final RpcClient rpcClient, 103 | final PublicKey marketId, 104 | final JupiterPricingSource jupiterPricingSource, 105 | final String pricingStrategy) { 106 | this.executorService = Executors.newSingleThreadScheduledExecutor(); 107 | 108 | this.serumManager = serumManager; 109 | this.rpcClient = rpcClient; 110 | 111 | this.solUsdcMarketBuilder = new MarketBuilder() 112 | .setClient(rpcClient) 113 | .setPublicKey(marketId) 114 | .setRetrieveOrderBooks(true); 115 | this.solUsdcMarket = this.solUsdcMarketBuilder.build(); 116 | this.jupiterPricingSource = jupiterPricingSource; 117 | 118 | if (pricingStrategy.equalsIgnoreCase("jupiter")) { 119 | useJupiter = true; 120 | 121 | Optional price = jupiterPricingSource.getUsdcPriceForSymbol(solUsdcMarket.getBaseMint().toBase58(), 122 | 1000); 123 | if (price.isPresent()) { 124 | this.bestBidPrice = price.get(); 125 | this.bestAskPrice = price.get(); 126 | } 127 | } 128 | 129 | BID_CLIENT_ID = ThreadLocalRandom.current().nextLong(1111, 9999999); 130 | ASK_CLIENT_ID = ThreadLocalRandom.current().nextLong(1111, 9999999); 131 | log.info("Bid clientId: " + BID_CLIENT_ID + ", Ask: " + ASK_CLIENT_ID); 132 | } 133 | 134 | @Override 135 | public void start() { 136 | log.info(this.getClass().getSimpleName() + " started."); 137 | 138 | // Start loop 139 | executorService.scheduleAtFixedRate( 140 | () -> { 141 | try { 142 | // Get latest prices 143 | solUsdcMarket.reload(solUsdcMarketBuilder); 144 | 145 | if (useJupiter) { 146 | Optional price = jupiterPricingSource.getUsdcPriceForSymbol(solUsdcMarket.getBaseMint().toBase58(), 147 | 1000); 148 | if (price.isPresent()) { 149 | this.bestBidPrice = price.get(); 150 | this.bestAskPrice = price.get(); 151 | } 152 | } else { 153 | this.bestBidPrice = solUsdcMarket.getBidOrderBook().getBestBid().getFloatPrice(); 154 | this.bestAskPrice = solUsdcMarket.getAskOrderBook().getBestAsk().getFloatPrice(); 155 | } 156 | 157 | boolean isCancelBid = 158 | solUsdcMarket.getBidOrderBook().getOrders().stream().anyMatch(order -> order.getOwner().equals(marketOoa)); 159 | 160 | float percentageChangeFromLastBid = 161 | 1.00f - (lastPlacedBidPrice / ((float) bestBidPrice * bidSpreadMultiplier)); 162 | 163 | // Only place bid if we haven't placed, or the change is >= 0.1% change 164 | if (lastPlacedBidPrice == 0 || (Math.abs(percentageChangeFromLastBid) >= MIN_MIDPOINT_CHANGE)) { 165 | placeUsdcBid(usdcBidAmount, (float) bestBidPrice * bidSpreadMultiplier, isCancelBid); 166 | lastPlacedBidPrice = (float) bestBidPrice * bidSpreadMultiplier; 167 | } 168 | 169 | boolean isCancelAsk = 170 | solUsdcMarket.getAskOrderBook().getOrders().stream().anyMatch(order -> order.getOwner().equals(marketOoa)); 171 | 172 | float percentageChangeFromLastAsk = 173 | 1.00f - (lastPlacedAskPrice / ((float) bestAskPrice * askSpreadMultiplier)); 174 | 175 | // Only place ask if we haven't placed, or the change is >= 0.1% change 176 | if (lastPlacedAskPrice == 0 || (Math.abs(percentageChangeFromLastAsk) >= MIN_MIDPOINT_CHANGE)) { 177 | placeSolAsk(baseAskAmount, (float) bestAskPrice * askSpreadMultiplier, isCancelAsk); 178 | lastPlacedAskPrice = (float) bestAskPrice * askSpreadMultiplier; 179 | } 180 | 181 | if (!firstLoadComplete) { 182 | try { 183 | log.info("Sleeping 2000ms..."); 184 | Thread.sleep(2000L); 185 | log.info("Fist load complete."); 186 | } catch (InterruptedException e) { 187 | throw new RuntimeException(e); 188 | } 189 | firstLoadComplete = true; 190 | } 191 | } catch (Exception ex) { 192 | log.error("Unhandled exception during event loop: " + ex.getMessage()); 193 | ex.printStackTrace(); 194 | } 195 | }, 196 | EVENT_LOOP_INITIAL_DELAY_MS, 197 | EVENT_LOOP_DURATION_MS, 198 | TimeUnit.MILLISECONDS 199 | ); 200 | } 201 | 202 | private void placeSolAsk(float solAmount, float price, boolean cancel) { 203 | final Transaction placeTx = new Transaction(); 204 | 205 | placeTx.addInstruction( 206 | ComputeBudgetProgram.setComputeUnitPrice( 207 | 151_420 208 | ) 209 | ); 210 | 211 | placeTx.addInstruction( 212 | ComputeBudgetProgram.setComputeUnitLimit( 213 | 54_800 214 | ) 215 | ); 216 | 217 | placeTx.addInstruction( 218 | SerumProgram.consumeEvents( 219 | mmAccount.getPublicKey(), 220 | List.of(marketOoa), 221 | solUsdcMarket, 222 | baseWallet, 223 | usdcWallet 224 | ) 225 | ); 226 | 227 | Order askOrder = Order.builder() 228 | .buy(false) 229 | .clientOrderId(ASK_CLIENT_ID) 230 | .orderTypeLayout(OrderTypeLayout.POST_ONLY) 231 | .selfTradeBehaviorLayout(SelfTradeBehaviorLayout.DECREMENT_TAKE) 232 | .floatPrice(price) 233 | .floatQuantity(solAmount) 234 | .build(); 235 | 236 | serumManager.setOrderPrices(askOrder, solUsdcMarket); 237 | 238 | if (cancel) { 239 | placeTx.addInstruction( 240 | SerumProgram.cancelOrderByClientId( 241 | solUsdcMarket, 242 | marketOoa, 243 | mmAccount.getPublicKey(), 244 | ASK_CLIENT_ID 245 | ) 246 | ); 247 | } 248 | 249 | 250 | // Settle - base wallet gets created first then closed after 251 | placeTx.addInstruction( 252 | SerumProgram.settleFunds( 253 | solUsdcMarket, 254 | marketOoa, 255 | mmAccount.getPublicKey(), 256 | baseWallet, //random wsol acct for settles 257 | usdcWallet 258 | ) 259 | ); 260 | 261 | placeTx.addInstruction( 262 | SerumProgram.placeOrder( 263 | mmAccount, 264 | baseWallet, 265 | marketOoa, 266 | solUsdcMarket, 267 | askOrder 268 | ) 269 | ); 270 | 271 | try { 272 | String orderTx = rpcClient.getApi().sendTransaction(placeTx, mmAccount); 273 | log.info("Base Ask: " + askOrder.getFloatQuantity() + " @ " + askOrder.getFloatPrice() + ", " + orderTx); 274 | lastAskOrder = askOrder; 275 | lastAskTx = orderTx; 276 | } catch (RpcException e) { 277 | log.error("OrderTx Error = " + e.getMessage()); 278 | } 279 | } 280 | 281 | private void placeUsdcBid(float amount, float price, boolean cancel) { 282 | final Transaction placeTx = new Transaction(); 283 | 284 | placeTx.addInstruction( 285 | ComputeBudgetProgram.setComputeUnitPrice( 286 | 151_420 287 | ) 288 | ); 289 | 290 | placeTx.addInstruction( 291 | ComputeBudgetProgram.setComputeUnitLimit( 292 | 54_800 293 | ) 294 | ); 295 | 296 | placeTx.addInstruction( 297 | SerumProgram.consumeEvents( 298 | mmAccount.getPublicKey(), 299 | List.of(marketOoa), 300 | solUsdcMarket, 301 | baseWallet, 302 | usdcWallet 303 | ) 304 | ); 305 | 306 | Order bidOrder = Order.builder() 307 | .buy(true) 308 | .clientOrderId(BID_CLIENT_ID) 309 | .orderTypeLayout(OrderTypeLayout.POST_ONLY) 310 | .selfTradeBehaviorLayout(SelfTradeBehaviorLayout.DECREMENT_TAKE) 311 | .floatPrice(price) 312 | .floatQuantity(amount) 313 | .build(); 314 | 315 | serumManager.setOrderPrices(bidOrder, solUsdcMarket); 316 | 317 | if (cancel) { 318 | placeTx.addInstruction( 319 | SerumProgram.cancelOrderByClientId( 320 | solUsdcMarket, 321 | marketOoa, 322 | mmAccount.getPublicKey(), 323 | BID_CLIENT_ID 324 | ) 325 | ); 326 | } 327 | 328 | 329 | // Settle - base wallet gets created first then closed after 330 | placeTx.addInstruction( 331 | SerumProgram.settleFunds( 332 | solUsdcMarket, 333 | marketOoa, 334 | mmAccount.getPublicKey(), 335 | baseWallet, //random wsol acct for settles 336 | usdcWallet 337 | ) 338 | ); 339 | 340 | placeTx.addInstruction( 341 | SerumProgram.placeOrder( 342 | mmAccount, 343 | usdcWallet, 344 | marketOoa, 345 | solUsdcMarket, 346 | bidOrder 347 | ) 348 | ); 349 | 350 | try { 351 | String orderTx = rpcClient.getApi().sendTransaction(placeTx, mmAccount); 352 | log.info("Quote Bid: " + bidOrder.getFloatQuantity() + " @ " + bidOrder.getFloatPrice() + ", " + orderTx); 353 | lastBidOrder = bidOrder; 354 | lastBidTx = orderTx; 355 | } catch (RpcException e) { 356 | log.error("OrderTx Error = " + e.getMessage()); 357 | } 358 | } 359 | 360 | @Override 361 | public void stop() { 362 | executorService.shutdown(); 363 | log.info("Bot stopped."); 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /src/main/java/com/mmorrell/arcana/controller/ArcanaController.java: -------------------------------------------------------------------------------- 1 | package com.mmorrell.arcana.controller; 2 | 3 | import com.mmorrell.arcana.background.ArcanaAccountManager; 4 | import com.mmorrell.arcana.background.ArcanaBackgroundCache; 5 | import com.mmorrell.arcana.pricing.JupiterPricingSource; 6 | import com.mmorrell.arcana.strategies.BotManager; 7 | import com.mmorrell.arcana.strategies.OpenBookBot; 8 | import com.mmorrell.arcana.strategies.openbook.OpenBookSplUsdc; 9 | import com.mmorrell.model.OpenBookContext; 10 | import com.mmorrell.serum.manager.SerumManager; 11 | import com.mmorrell.serum.model.Market; 12 | import com.mmorrell.serum.model.OpenOrdersAccount; 13 | import com.mmorrell.serum.model.SerumUtils; 14 | import jakarta.annotation.PostConstruct; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.bitcoinj.core.Base58; 17 | import org.json.JSONArray; 18 | import org.p2p.solanaj.core.Account; 19 | import org.p2p.solanaj.core.PublicKey; 20 | import org.p2p.solanaj.rpc.RpcClient; 21 | import org.p2p.solanaj.rpc.RpcException; 22 | import org.p2p.solanaj.rpc.types.TokenAccountInfo; 23 | import org.springframework.stereotype.Controller; 24 | import org.springframework.ui.Model; 25 | import org.springframework.web.bind.annotation.ModelAttribute; 26 | import org.springframework.web.bind.annotation.PathVariable; 27 | import org.springframework.web.bind.annotation.PostMapping; 28 | import org.springframework.web.bind.annotation.RequestMapping; 29 | import org.springframework.web.bind.annotation.RequestParam; 30 | import org.springframework.web.bind.annotation.ResponseBody; 31 | import org.springframework.web.multipart.MultipartFile; 32 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 33 | 34 | import java.io.IOException; 35 | import java.util.Collections; 36 | import java.util.HashMap; 37 | import java.util.List; 38 | import java.util.Map; 39 | import java.util.Objects; 40 | 41 | @Controller 42 | @Slf4j 43 | public class ArcanaController { 44 | 45 | private RpcClient rpcClient; 46 | private final BotManager botManager; 47 | private final SerumManager serumManager; 48 | private final JupiterPricingSource jupiterPricingSource; 49 | private final ArcanaBackgroundCache arcanaBackgroundCache; 50 | private final ArcanaAccountManager arcanaAccountManager; 51 | 52 | public ArcanaController(RpcClient rpcClient, BotManager botManager, 53 | SerumManager serumManager, JupiterPricingSource jupiterPricingSource, 54 | ArcanaBackgroundCache arcanaBackgroundCache, ArcanaAccountManager arcanaAccountManager) { 55 | this.rpcClient = rpcClient; 56 | this.botManager = botManager; 57 | this.serumManager = serumManager; 58 | this.jupiterPricingSource = jupiterPricingSource; 59 | this.arcanaBackgroundCache = arcanaBackgroundCache; 60 | this.arcanaAccountManager = arcanaAccountManager; 61 | } 62 | 63 | @RequestMapping("/") 64 | public String arcanaIndex(Model model) { 65 | model.addAttribute("rpcEndpoint", rpcClient.getEndpoint()); 66 | model.addAttribute("botList", botManager.getBotList()); 67 | return "index"; 68 | } 69 | 70 | @RequestMapping("/settings") 71 | public String arcanaSettings(Model model, @RequestParam(required = false) String rpc) { 72 | if (rpc != null && rpc.length() > 10) { 73 | // set RPC host 74 | rpcClient = new RpcClient(rpc); 75 | log.info("New RPC Host: " + rpc); 76 | } 77 | 78 | model.addAttribute("rpcEndpoint", rpcClient.getEndpoint()); 79 | model.addAttribute("tradingAccountPubkey", botManager.getTradingAccount().getPublicKey().toBase58()); 80 | model.addAttribute("arcanaAccounts", arcanaAccountManager.getArcanaAccounts()); 81 | 82 | return "settings"; 83 | } 84 | 85 | @RequestMapping("/wrap/{amountSol}") 86 | @ResponseBody 87 | public Map wrapSol(Model model, @PathVariable Double amountSol) { 88 | return Map.of( 89 | "wsolPubkey", 90 | arcanaBackgroundCache.wrapSol( 91 | botManager.getTradingAccount(), 92 | amountSol 93 | ).toBase58() 94 | ); 95 | } 96 | 97 | @RequestMapping("/accounts/getAllAccounts") 98 | @ResponseBody 99 | public List> getAllAccounts(Model model) { 100 | return arcanaAccountManager.getArcanaAccounts().stream() 101 | .map(account -> Map.of("pubkey", account.getPublicKey().toBase58())) 102 | .toList(); 103 | } 104 | 105 | @RequestMapping("/accounts/getAllPrivateAccounts") 106 | @ResponseBody 107 | public List> getAllPrivateAccounts(Model model) { 108 | return arcanaAccountManager.getArcanaAccounts().stream() 109 | .map(account -> Map.of("privatekey", Base58.encode(account.getSecretKey()))) 110 | .toList(); 111 | } 112 | 113 | @RequestMapping("/getAccountsByMarket/{marketId}") 114 | @ResponseBody 115 | public OpenBookContext getMarketAccounts(Model model, @PathVariable String marketId) { 116 | PublicKey pubkey = botManager.getTradingAccount().getPublicKey(); 117 | PublicKey marketIdPubkey = new PublicKey(marketId); 118 | Map results = new HashMap<>(); 119 | 120 | // Get market, get base and quote mints. Check if we have wallets for them 121 | try { 122 | Market market = Market.readMarket( 123 | rpcClient.getApi().getAccountInfo(marketIdPubkey).getDecodedData() 124 | ); 125 | log.info("Base Mint: " + market.getBaseMint()); 126 | log.info("Quote Mint: " + market.getQuoteMint()); 127 | 128 | Map requiredParams = Map.of("mint", market.getBaseMint()); 129 | TokenAccountInfo tokenAccount = rpcClient.getApi().getTokenAccountsByOwner(pubkey, requiredParams, 130 | new HashMap<>()); 131 | requiredParams = Map.of("mint", market.getQuoteMint()); 132 | TokenAccountInfo quoteTokenAccount = rpcClient.getApi().getTokenAccountsByOwner(pubkey, requiredParams, 133 | new HashMap<>()); 134 | 135 | log.info("Our base wallet: " + tokenAccount.getValue().get(0).getPubkey()); 136 | log.info("Our quote wallet: " + quoteTokenAccount.getValue().get(0).getPubkey()); 137 | 138 | results.put("baseWallet", tokenAccount.getValue().get(0).getPubkey()); 139 | results.put("quoteWallet", quoteTokenAccount.getValue().get(0).getPubkey()); 140 | results.put("ooa", null); 141 | 142 | OpenBookContext openBookContext = new OpenBookContext(); 143 | openBookContext.setBaseWallet(tokenAccount.getValue().get(0).getPubkey()); 144 | openBookContext.setQuoteWallet(quoteTokenAccount.getValue().get(0).getPubkey()); 145 | 146 | // OOA 147 | final OpenOrdersAccount openOrdersAccount = SerumUtils.findOpenOrdersAccountForOwner( 148 | rpcClient, 149 | market.getOwnAddress(), 150 | pubkey 151 | ); 152 | openBookContext.setOoa(openOrdersAccount.getOwnPubkey().toBase58()); 153 | 154 | return openBookContext; 155 | } catch (RpcException e) { 156 | return new OpenBookContext(); 157 | } 158 | } 159 | 160 | // Adds and starts a new SPL/USDC trading strategy. 161 | @PostMapping("/bots/add/post") 162 | public String arcanaBotAdd(@ModelAttribute("newBot") OpenBookBot newBot) { 163 | // Adds new strategy to list. 164 | OpenBookSplUsdc openBookSplUsdc = new OpenBookSplUsdc( 165 | serumManager, 166 | rpcClient, 167 | newBot.getMarketId(), 168 | jupiterPricingSource, 169 | newBot.getPriceStrategy() 170 | ); 171 | openBookSplUsdc.setMarketOoa(newBot.getOoa()); 172 | openBookSplUsdc.setBaseWallet(newBot.getBaseWallet()); 173 | openBookSplUsdc.setUsdcWallet(newBot.getQuoteWallet()); 174 | openBookSplUsdc.setMmAccount(botManager.getTradingAccount()); 175 | openBookSplUsdc.setBaseAskAmount((float) newBot.getAmountAsk()); 176 | openBookSplUsdc.setUsdcBidAmount((float) newBot.getAmountBid()); 177 | 178 | // bid + ask multiplier 179 | float bidMultiplier = (10000.0f - (float) newBot.getBpsSpread()) / 10000.0f; 180 | float askMultiplier = (10000.0f + (float) newBot.getBpsSpread()) / 10000.0f; 181 | 182 | openBookSplUsdc.setBidSpreadMultiplier(bidMultiplier); 183 | openBookSplUsdc.setAskSpreadMultiplier(askMultiplier); 184 | 185 | newBot.setStrategy(openBookSplUsdc); 186 | 187 | botManager.addBot(newBot); 188 | log.info("New strategy created/started: " + newBot); 189 | 190 | return "redirect:/"; 191 | } 192 | 193 | @RequestMapping("/openbook") 194 | public String openbookMarkets(Model model) { 195 | model.addAttribute("rpcEndpoint", rpcClient.getEndpoint()); 196 | model.addAttribute("markets", arcanaBackgroundCache.getCachedMarkets() 197 | .stream().sorted((o1, o2) -> (int) (o2.getReferrerRebatesAccrued() - o1.getReferrerRebatesAccrued())) 198 | .toList()); 199 | return "openbook"; 200 | } 201 | 202 | @RequestMapping("/bots/add") 203 | public String arcanaBotWizard(Model model, @RequestParam(required = false) String marketId) { 204 | model.addAttribute("rpcEndpoint", rpcClient.getEndpoint()); 205 | 206 | OpenBookBot newBot = new OpenBookBot(); 207 | if (marketId != null) { 208 | newBot.setMarketId(new PublicKey(marketId)); 209 | } 210 | model.addAttribute("newBot", newBot); 211 | model.addAttribute("marketId", marketId); 212 | 213 | return "add_bot"; 214 | } 215 | 216 | @RequestMapping("/bots/view/{id}") 217 | public String arcanaBotWizard(Model model, @PathVariable("id") long botId) { 218 | model.addAttribute("rpcEndpoint", rpcClient.getEndpoint()); 219 | model.addAttribute("botId", --botId); 220 | 221 | OpenBookBot bot = botManager.getBotList().get((int) botId); 222 | model.addAttribute("bot", bot.toString()); 223 | model.addAttribute("botUuid", bot.getStrategy().uuid.toString()); 224 | model.addAttribute("botMarketId", bot.getMarketId().toBase58()); 225 | model.addAttribute("botBpsSpread", bot.getBpsSpread()); 226 | model.addAttribute("botAmountBid", bot.getAmountBid()); 227 | model.addAttribute("botAmountAsk", bot.getAmountAsk()); 228 | model.addAttribute("botOoa", bot.getOoa().toBase58()); 229 | model.addAttribute("botBaseWallet", bot.getBaseWallet().toBase58()); 230 | model.addAttribute("botQuoteWallet", bot.getQuoteWallet().toBase58()); 231 | 232 | // Strategy 233 | model.addAttribute("strategyName", bot.getStrategy().getClass().getSimpleName()); 234 | 235 | // Last bids / asks 236 | if (bot.getStrategy() instanceof OpenBookSplUsdc) { 237 | model.addAttribute("lastBidOrder", ((OpenBookSplUsdc) bot.getStrategy()).getLastBidOrder().toString()); 238 | model.addAttribute("lastAskOrder", ((OpenBookSplUsdc) bot.getStrategy()).getLastAskOrder().toString()); 239 | model.addAttribute("lastBidTx", ((OpenBookSplUsdc) bot.getStrategy()).getLastBidTx()); 240 | model.addAttribute("lastAskTx", ((OpenBookSplUsdc) bot.getStrategy()).getLastAskTx()); 241 | } 242 | 243 | return "view_bot"; 244 | } 245 | 246 | @RequestMapping("/bots/stop/{id}") 247 | public String arcanaBotStop(Model model, @PathVariable("id") long botId) { 248 | botManager.stopBot(botId); 249 | return "redirect:/"; 250 | } 251 | 252 | @RequestMapping("/bots/use/{id}") 253 | public String useTradingAccount(Model model, @PathVariable("id") long botId) { 254 | botManager.setTradingAccount(arcanaAccountManager.getArcanaAccounts().get((int) botId)); 255 | return "redirect:/settings"; 256 | } 257 | 258 | 259 | @PostMapping("/privateKeyUpload") 260 | public String privateKeyUpload(@RequestParam("file") MultipartFile file, 261 | RedirectAttributes redirectAttributes) { 262 | try { 263 | byte[] bytes = file.getBytes(); 264 | botManager.setTradingAccount(Account.fromJson(new String(bytes))); 265 | 266 | // if a new account, add to our cache / like a Set 267 | if (arcanaAccountManager.getArcanaAccounts().stream() 268 | .noneMatch(account -> account.getPublicKey().toBase58() 269 | .equals(botManager.getTradingAccount().getPublicKey().toBase58()))) { 270 | arcanaAccountManager.getArcanaAccounts().add(botManager.getTradingAccount()); 271 | } 272 | } catch (IOException e) { 273 | return "redirect:/settings"; 274 | } 275 | 276 | return "redirect:/settings"; 277 | } 278 | 279 | @RequestMapping("/privateKeyPost") 280 | public String privateKeyPost(Model model, @RequestParam String privateKey) { 281 | byte[] bytes = Base58.decode(privateKey); 282 | botManager.setTradingAccount(new Account(bytes)); 283 | 284 | // if a new account, add to our cache 285 | if (arcanaAccountManager.getArcanaAccounts().stream() 286 | .noneMatch(account -> account.getPublicKey().toBase58() 287 | .equals(botManager.getTradingAccount().getPublicKey().toBase58()))) { 288 | arcanaAccountManager.getArcanaAccounts().add(botManager.getTradingAccount()); 289 | } 290 | 291 | return "redirect:/settings"; 292 | } 293 | 294 | @RequestMapping("/settings/localStorage") 295 | public String localStorage(Model model, @RequestParam String localStorage) { 296 | log.info("localStorage: " + localStorage); 297 | 298 | JSONArray jsonArray = new JSONArray(localStorage); 299 | jsonArray.forEach(privateKey -> { 300 | byte[] privateKeyBytes = Base58.decode(privateKey.toString()); 301 | Account newAccount = new Account(privateKeyBytes); 302 | log.info("New account from LS: " + newAccount.getPublicKey().toBase58()); 303 | if (arcanaAccountManager.getArcanaAccounts().stream() 304 | .noneMatch(account -> account.getPublicKey().toBase58() 305 | .equals(newAccount.getPublicKey().toBase58()))) { 306 | arcanaAccountManager.getArcanaAccounts().add(newAccount); 307 | } 308 | }); 309 | 310 | return "redirect:/settings"; 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /src/main/resources/static/bootstrap.bundle.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v5.0.2 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter(t=>t.matches(e)),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]}},e=t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t},i=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i="#"+i.split("#")[1]),e=i&&"#"!==i?i.trim():null}return e},n=t=>{const e=i(t);return e&&document.querySelector(e)?e:null},s=t=>{const e=i(t);return e?document.querySelector(e):null},o=t=>{t.dispatchEvent(new Event("transitionend"))},r=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),a=e=>r(e)?e.jquery?e[0]:e:"string"==typeof e&&e.length>0?t.findOne(e):null,l=(t,e,i)=>{Object.keys(i).forEach(n=>{const s=i[n],o=e[n],a=o&&r(o)?"element":null==(l=o)?""+l:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)})},c=t=>!(!r(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),h=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),d=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?d(t.parentNode):null},u=()=>{},f=t=>t.offsetHeight,p=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},m=[],g=()=>"rtl"===document.documentElement.dir,_=t=>{var e;e=()=>{const e=p();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",()=>{m.forEach(t=>t())}),m.push(e)):e()},b=t=>{"function"==typeof t&&t()},v=(t,e,i=!0)=>{if(!i)return void b(t);const n=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let s=!1;const r=({target:i})=>{i===e&&(s=!0,e.removeEventListener("transitionend",r),b(t))};e.addEventListener("transitionend",r),setTimeout(()=>{s||o(e)},n)},y=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},w=/[^.]*(?=\..*)\.|.*/,E=/\..*/,A=/::\d+$/,T={};let O=1;const C={mouseenter:"mouseover",mouseleave:"mouseout"},k=/^(mouseenter|mouseleave)/i,L=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function x(t,e){return e&&`${e}::${O++}`||t.uidEvent||O++}function D(t){const e=x(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function S(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=I(e,i,n),l=D(t),c=l[a]||(l[a]={}),h=S(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=x(r,e.replace(w,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&P.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&P.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function j(t,e,i,n,s){const o=S(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function M(t){return t=t.replace(E,""),C[t]||t}const P={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=I(e,i,n),a=r!==e,l=D(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void j(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach(i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach(o=>{if(o.includes(n)){const n=s[o];j(t,e,i,n.originalHandler,n.delegationSelector)}})}(t,l,i,e.slice(1))});const h=l[r]||{};Object.keys(h).forEach(i=>{const n=i.replace(A,"");if(!a||e.includes(n)){const e=h[i];j(t,l,r,e.originalHandler,e.delegationSelector)}})},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=p(),s=M(e),o=e!==s,r=L.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach(t=>{Object.defineProperty(d,t,{get:()=>i[t]})}),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},H=new Map;var R={set(t,e,i){H.has(t)||H.set(t,new Map);const n=H.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>H.has(t)&&H.get(t).get(e)||null,remove(t,e){if(!H.has(t))return;const i=H.get(t);i.delete(e),0===i.size&&H.delete(t)}};class B{constructor(t){(t=a(t))&&(this._element=t,R.set(this._element,this.constructor.DATA_KEY,this))}dispose(){R.remove(this._element,this.constructor.DATA_KEY),P.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach(t=>{this[t]=null})}_queueCallback(t,e,i=!0){v(t,e,i)}static getInstance(t){return R.get(t,this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.0.2"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return"bs."+this.NAME}static get EVENT_KEY(){return"."+this.DATA_KEY}}class W extends B{static get NAME(){return"alert"}close(t){const e=t?this._getRootElement(t):this._element,i=this._triggerCloseEvent(e);null===i||i.defaultPrevented||this._removeElement(e)}_getRootElement(t){return s(t)||t.closest(".alert")}_triggerCloseEvent(t){return P.trigger(t,"close.bs.alert")}_removeElement(t){t.classList.remove("show");const e=t.classList.contains("fade");this._queueCallback(()=>this._destroyElement(t),t,e)}_destroyElement(t){t.remove(),P.trigger(t,"closed.bs.alert")}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);"close"===t&&e[t](this)}))}static handleDismiss(t){return function(e){e&&e.preventDefault(),t.close(this)}}}P.on(document,"click.bs.alert.data-api",'[data-bs-dismiss="alert"]',W.handleDismiss(new W)),_(W);class q extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=q.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function z(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function $(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}P.on(document,"click.bs.button.data-api",'[data-bs-toggle="button"]',t=>{t.preventDefault();const e=t.target.closest('[data-bs-toggle="button"]');q.getOrCreateInstance(e).toggle()}),_(q);const U={setDataAttribute(t,e,i){t.setAttribute("data-bs-"+$(e),i)},removeDataAttribute(t,e){t.removeAttribute("data-bs-"+$(e))},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter(t=>t.startsWith("bs")).forEach(i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=z(t.dataset[i])}),e},getDataAttribute:(t,e)=>z(t.getAttribute("data-bs-"+$(e))),offset(t){const e=t.getBoundingClientRect();return{top:e.top+document.body.scrollTop,left:e.left+document.body.scrollLeft}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},F={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},V={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},K="next",X="prev",Y="left",Q="right",G={ArrowLeft:Q,ArrowRight:Y};class Z extends B{constructor(e,i){super(e),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(i),this._indicatorsElement=t.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return F}static get NAME(){return"carousel"}next(){this._slide(K)}nextWhenVisible(){!document.hidden&&c(this._element)&&this.next()}prev(){this._slide(X)}pause(e){e||(this._isPaused=!0),t.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(o(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(e){this._activeElement=t.findOne(".active.carousel-item",this._element);const i=this._getItemIndex(this._activeElement);if(e>this._items.length-1||e<0)return;if(this._isSliding)return void P.one(this._element,"slid.bs.carousel",()=>this.to(e));if(i===e)return this.pause(),void this.cycle();const n=e>i?K:X;this._slide(n,this._items[e])}_getConfig(t){return t={...F,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("carousel",t,V),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?Q:Y)}_addEventListeners(){this._config.keyboard&&P.on(this._element,"keydown.bs.carousel",t=>this._keydown(t)),"hover"===this._config.pause&&(P.on(this._element,"mouseenter.bs.carousel",t=>this.pause(t)),P.on(this._element,"mouseleave.bs.carousel",t=>this.cycle(t))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const e=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType?this._pointerEvent||(this.touchStartX=t.touches[0].clientX):this.touchStartX=t.clientX},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=t=>{!this._pointerEvent||"pen"!==t.pointerType&&"touch"!==t.pointerType||(this.touchDeltaX=t.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout(t=>this.cycle(t),500+this._config.interval))};t.find(".carousel-item img",this._element).forEach(t=>{P.on(t,"dragstart.bs.carousel",t=>t.preventDefault())}),this._pointerEvent?(P.on(this._element,"pointerdown.bs.carousel",t=>e(t)),P.on(this._element,"pointerup.bs.carousel",t=>n(t)),this._element.classList.add("pointer-event")):(P.on(this._element,"touchstart.bs.carousel",t=>e(t)),P.on(this._element,"touchmove.bs.carousel",t=>i(t)),P.on(this._element,"touchend.bs.carousel",t=>n(t)))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=G[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(e){return this._items=e&&e.parentNode?t.find(".carousel-item",e.parentNode):[],this._items.indexOf(e)}_getItemByOrder(t,e){const i=t===K;return y(this._items,e,i,this._config.wrap)}_triggerSlideEvent(e,i){const n=this._getItemIndex(e),s=this._getItemIndex(t.findOne(".active.carousel-item",this._element));return P.trigger(this._element,"slide.bs.carousel",{relatedTarget:e,direction:i,from:s,to:n})}_setActiveIndicatorElement(e){if(this._indicatorsElement){const i=t.findOne(".active",this._indicatorsElement);i.classList.remove("active"),i.removeAttribute("aria-current");const n=t.find("[data-bs-target]",this._indicatorsElement);for(let t=0;t{P.trigger(this._element,"slid.bs.carousel",{relatedTarget:r,direction:u,from:o,to:a})};if(this._element.classList.contains("slide")){r.classList.add(d),f(r),s.classList.add(h),r.classList.add(h);const t=()=>{r.classList.remove(h,d),r.classList.add("active"),s.classList.remove("active",d,h),this._isSliding=!1,setTimeout(p,0)};this._queueCallback(t,s,!0)}else s.classList.remove("active"),r.classList.add("active"),this._isSliding=!1,p();l&&this.cycle()}_directionToOrder(t){return[Q,Y].includes(t)?g()?t===Y?X:K:t===Y?K:X:t}_orderToDirection(t){return[K,X].includes(t)?g()?t===X?Y:Q:t===X?Q:Y:t}static carouselInterface(t,e){const i=Z.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){Z.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=s(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},n=this.getAttribute("data-bs-slide-to");n&&(i.interval=!1),Z.carouselInterface(e,i),n&&Z.getInstance(e).to(n),t.preventDefault()}}P.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",Z.dataApiClickHandler),P.on(window,"load.bs.carousel.data-api",()=>{const e=t.find('[data-bs-ride="carousel"]');for(let t=0,i=e.length;tt===this._element);null!==o&&r.length&&(this._selector=o,this._triggerArray.push(i))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}static get Default(){return J}static get NAME(){return"collapse"}toggle(){this._element.classList.contains("show")?this.hide():this.show()}show(){if(this._isTransitioning||this._element.classList.contains("show"))return;let e,i;this._parent&&(e=t.find(".show, .collapsing",this._parent).filter(t=>"string"==typeof this._config.parent?t.getAttribute("data-bs-parent")===this._config.parent:t.classList.contains("collapse")),0===e.length&&(e=null));const n=t.findOne(this._selector);if(e){const t=e.find(t=>n!==t);if(i=t?et.getInstance(t):null,i&&i._isTransitioning)return}if(P.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e&&e.forEach(t=>{n!==t&&et.collapseInterface(t,"hide"),i||R.set(t,"bs.collapse",null)});const s=this._getDimension();this._element.classList.remove("collapse"),this._element.classList.add("collapsing"),this._element.style[s]=0,this._triggerArray.length&&this._triggerArray.forEach(t=>{t.classList.remove("collapsed"),t.setAttribute("aria-expanded",!0)}),this.setTransitioning(!0);const o="scroll"+(s[0].toUpperCase()+s.slice(1));this._queueCallback(()=>{this._element.classList.remove("collapsing"),this._element.classList.add("collapse","show"),this._element.style[s]="",this.setTransitioning(!1),P.trigger(this._element,"shown.bs.collapse")},this._element,!0),this._element.style[s]=this._element[o]+"px"}hide(){if(this._isTransitioning||!this._element.classList.contains("show"))return;if(P.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=this._element.getBoundingClientRect()[t]+"px",f(this._element),this._element.classList.add("collapsing"),this._element.classList.remove("collapse","show");const e=this._triggerArray.length;if(e>0)for(let t=0;t{this.setTransitioning(!1),this._element.classList.remove("collapsing"),this._element.classList.add("collapse"),P.trigger(this._element,"hidden.bs.collapse")},this._element,!0)}setTransitioning(t){this._isTransitioning=t}_getConfig(t){return(t={...J,...t}).toggle=Boolean(t.toggle),l("collapse",t,tt),t}_getDimension(){return this._element.classList.contains("width")?"width":"height"}_getParent(){let{parent:e}=this._config;e=a(e);const i=`[data-bs-toggle="collapse"][data-bs-parent="${e}"]`;return t.find(i,e).forEach(t=>{const e=s(t);this._addAriaAndCollapsedClass(e,[t])}),e}_addAriaAndCollapsedClass(t,e){if(!t||!e.length)return;const i=t.classList.contains("show");e.forEach(t=>{i?t.classList.remove("collapsed"):t.classList.add("collapsed"),t.setAttribute("aria-expanded",i)})}static collapseInterface(t,e){let i=et.getInstance(t);const n={...J,...U.getDataAttributes(t),..."object"==typeof e&&e?e:{}};if(!i&&n.toggle&&"string"==typeof e&&/show|hide/.test(e)&&(n.toggle=!1),i||(i=new et(t,n)),"string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){et.collapseInterface(this,t)}))}}P.on(document,"click.bs.collapse.data-api",'[data-bs-toggle="collapse"]',(function(e){("A"===e.target.tagName||e.delegateTarget&&"A"===e.delegateTarget.tagName)&&e.preventDefault();const i=U.getDataAttributes(this),s=n(this);t.find(s).forEach(t=>{const e=et.getInstance(t);let n;e?(null===e._parent&&"string"==typeof i.parent&&(e._config.parent=i.parent,e._parent=e._getParent()),n="toggle"):n=i,et.collapseInterface(t,n)})})),_(et);var it="top",nt="bottom",st="right",ot="left",rt=[it,nt,st,ot],at=rt.reduce((function(t,e){return t.concat([e+"-start",e+"-end"])}),[]),lt=[].concat(rt,["auto"]).reduce((function(t,e){return t.concat([e,e+"-start",e+"-end"])}),[]),ct=["beforeRead","read","afterRead","beforeMain","main","afterMain","beforeWrite","write","afterWrite"];function ht(t){return t?(t.nodeName||"").toLowerCase():null}function dt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function ut(t){return t instanceof dt(t).Element||t instanceof Element}function ft(t){return t instanceof dt(t).HTMLElement||t instanceof HTMLElement}function pt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof dt(t).ShadowRoot||t instanceof ShadowRoot)}var mt={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];ft(s)&&ht(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});ft(n)&&ht(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function gt(t){return t.split("-")[0]}function _t(t){var e=t.getBoundingClientRect();return{width:e.width,height:e.height,top:e.top,right:e.right,bottom:e.bottom,left:e.left,x:e.left,y:e.top}}function bt(t){var e=_t(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function vt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&pt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function yt(t){return dt(t).getComputedStyle(t)}function wt(t){return["table","td","th"].indexOf(ht(t))>=0}function Et(t){return((ut(t)?t.ownerDocument:t.document)||window.document).documentElement}function At(t){return"html"===ht(t)?t:t.assignedSlot||t.parentNode||(pt(t)?t.host:null)||Et(t)}function Tt(t){return ft(t)&&"fixed"!==yt(t).position?t.offsetParent:null}function Ot(t){for(var e=dt(t),i=Tt(t);i&&wt(i)&&"static"===yt(i).position;)i=Tt(i);return i&&("html"===ht(i)||"body"===ht(i)&&"static"===yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&ft(t)&&"fixed"===yt(t).position)return null;for(var i=At(t);ft(i)&&["html","body"].indexOf(ht(i))<0;){var n=yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ct(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var kt=Math.max,Lt=Math.min,xt=Math.round;function Dt(t,e,i){return kt(t,Lt(e,i))}function St(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function It(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var Nt={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=gt(i.placement),l=Ct(a),c=[ot,st].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return St("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:It(t,rt))}(s.padding,i),d=bt(o),u="y"===l?it:ot,f="y"===l?nt:st,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=Ot(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=Dt(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&vt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]},jt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function Mt(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.offsets,r=t.position,a=t.gpuAcceleration,l=t.adaptive,c=t.roundOffsets,h=!0===c?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:xt(xt(e*n)/n)||0,y:xt(xt(i*n)/n)||0}}(o):"function"==typeof c?c(o):o,d=h.x,u=void 0===d?0:d,f=h.y,p=void 0===f?0:f,m=o.hasOwnProperty("x"),g=o.hasOwnProperty("y"),_=ot,b=it,v=window;if(l){var y=Ot(i),w="clientHeight",E="clientWidth";y===dt(i)&&"static"!==yt(y=Et(i)).position&&(w="scrollHeight",E="scrollWidth"),y=y,s===it&&(b=nt,p-=y[w]-n.height,p*=a?1:-1),s===ot&&(_=st,u-=y[E]-n.width,u*=a?1:-1)}var A,T=Object.assign({position:r},l&&jt);return a?Object.assign({},T,((A={})[b]=g?"0":"",A[_]=m?"0":"",A.transform=(v.devicePixelRatio||1)<2?"translate("+u+"px, "+p+"px)":"translate3d("+u+"px, "+p+"px, 0)",A)):Object.assign({},T,((e={})[b]=g?p+"px":"",e[_]=m?u+"px":"",e.transform="",e))}var Pt={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:gt(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,Mt(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,Mt(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},Ht={passive:!0},Rt={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=dt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,Ht)})),a&&l.addEventListener("resize",i.update,Ht),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,Ht)})),a&&l.removeEventListener("resize",i.update,Ht)}},data:{}},Bt={left:"right",right:"left",bottom:"top",top:"bottom"};function Wt(t){return t.replace(/left|right|bottom|top/g,(function(t){return Bt[t]}))}var qt={start:"end",end:"start"};function zt(t){return t.replace(/start|end/g,(function(t){return qt[t]}))}function $t(t){var e=dt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ut(t){return _t(Et(t)).left+$t(t).scrollLeft}function Ft(t){var e=yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Vt(t,e){var i;void 0===e&&(e=[]);var n=function t(e){return["html","body","#document"].indexOf(ht(e))>=0?e.ownerDocument.body:ft(e)&&Ft(e)?e:t(At(e))}(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=dt(n),r=s?[o].concat(o.visualViewport||[],Ft(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Vt(At(r)))}function Kt(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Xt(t,e){return"viewport"===e?Kt(function(t){var e=dt(t),i=Et(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+Ut(t),y:a}}(t)):ft(e)?function(t){var e=_t(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Kt(function(t){var e,i=Et(t),n=$t(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=kt(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=kt(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ut(t),l=-n.scrollTop;return"rtl"===yt(s||i).direction&&(a+=kt(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Et(t)))}function Yt(t){return t.split("-")[1]}function Qt(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?gt(s):null,r=s?Yt(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case it:e={x:a,y:i.y-n.height};break;case nt:e={x:a,y:i.y+i.height};break;case st:e={x:i.x+i.width,y:l};break;case ot:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ct(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case"start":e[c]=e[c]-(i[h]/2-n[h]/2);break;case"end":e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function Gt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?"clippingParents":o,a=i.rootBoundary,l=void 0===a?"viewport":a,c=i.elementContext,h=void 0===c?"popper":c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=St("number"!=typeof p?p:It(p,rt)),g="popper"===h?"reference":"popper",_=t.elements.reference,b=t.rects.popper,v=t.elements[u?g:h],y=function(t,e,i){var n="clippingParents"===e?function(t){var e=Vt(At(t)),i=["absolute","fixed"].indexOf(yt(t).position)>=0&&ft(t)?Ot(t):t;return ut(i)?e.filter((function(t){return ut(t)&&vt(t,i)&&"body"!==ht(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Xt(t,i);return e.top=kt(n.top,e.top),e.right=Lt(n.right,e.right),e.bottom=Lt(n.bottom,e.bottom),e.left=kt(n.left,e.left),e}),Xt(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}(ut(v)?v:v.contextElement||Et(t.elements.popper),r,l),w=_t(_),E=Qt({reference:w,element:b,strategy:"absolute",placement:s}),A=Kt(Object.assign({},b,E)),T="popper"===h?A:w,O={top:y.top-T.top+m.top,bottom:T.bottom-y.bottom+m.bottom,left:y.left-T.left+m.left,right:T.right-y.right+m.right},C=t.modifiersData.offset;if("popper"===h&&C){var k=C[s];Object.keys(O).forEach((function(t){var e=[st,nt].indexOf(t)>=0?1:-1,i=[it,nt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function Zt(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?lt:l,h=Yt(n),d=h?a?at:at.filter((function(t){return Yt(t)===h})):rt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=Gt(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[gt(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}var Jt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=gt(g),b=l||(_!==g&&p?function(t){if("auto"===gt(t))return[];var e=Wt(t);return[zt(t),e,zt(e)]}(g):[Wt(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat("auto"===gt(i)?Zt(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=Gt(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),I=x?L?st:ot:L?nt:it;y[D]>w[D]&&(I=Wt(I));var N=Wt(I),j=[];if(o&&j.push(S[k]<=0),a&&j.push(S[I]<=0,S[N]<=0),j.every((function(t){return t}))){T=C,A=!1;break}E.set(C,j)}if(A)for(var M=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},P=p?3:1;P>0&&"break"!==M(P);P--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function te(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ee(t){return[it,st,nt,ot].some((function(e){return t[e]>=0}))}var ie={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=Gt(e,{elementContext:"reference"}),a=Gt(e,{altBoundary:!0}),l=te(r,n),c=te(a,s,o),h=ee(l),d=ee(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},ne={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=gt(t),s=[ot,it].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[ot,st].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},se={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Qt({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},oe={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=Gt(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=gt(e.placement),b=Yt(e.placement),v=!b,y=Ct(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?it:ot,L="y"===y?nt:st,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],I=E[y]-g[L],N=f?-T[x]/2:0,j="start"===b?A[x]:T[x],M="start"===b?-T[x]:-A[x],P=e.elements.arrow,H=f&&P?bt(P):{width:0,height:0},R=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},B=R[k],W=R[L],q=Dt(0,A[x],H[x]),z=v?A[x]/2-N-q-B-O:j-q-B-O,$=v?-A[x]/2+N+q+W+O:M+q+W+O,U=e.elements.arrow&&Ot(e.elements.arrow),F=U?"y"===y?U.clientTop||0:U.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-F,X=E[y]+$-V;if(o){var Y=Dt(f?Lt(S,K):S,D,f?kt(I,X):I);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?it:ot,G="x"===y?nt:st,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=Dt(f?Lt(J,K):J,Z,f?kt(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function re(t,e,i){void 0===i&&(i=!1);var n,s,o=Et(e),r=_t(t),a=ft(e),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(a||!a&&!i)&&(("body"!==ht(e)||Ft(o))&&(l=(n=e)!==dt(n)&&ft(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:$t(n)),ft(e)?((c=_t(e)).x+=e.clientLeft,c.y+=e.clientTop):o&&(c.x=Ut(o))),{x:r.left+l.scrollLeft-c.x,y:r.top+l.scrollTop-c.y,width:r.width,height:r.height}}var ae={placement:"bottom",modifiers:[],strategy:"absolute"};function le(){for(var t=arguments.length,e=new Array(t),i=0;i"applyStyles"===t.name&&!1===t.enabled);this._popper=ue(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}"ontouchstart"in document.documentElement&&!t.closest(".navbar-nav")&&[].concat(...document.body.children).forEach(t=>P.on(t,"mouseover",u)),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.toggle("show"),this._element.classList.toggle("show"),P.trigger(this._element,"shown.bs.dropdown",e)}}hide(){if(h(this._element)||!this._menu.classList.contains("show"))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_addEventListeners(){P.on(this._element,"click.bs.dropdown",t=>{t.preventDefault(),this.toggle()})}_completeHide(t){P.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>P.off(t,"mouseover",u)),this._popper&&this._popper.destroy(),this._menu.classList.remove("show"),this._element.classList.remove("show"),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),P.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},l("dropdown",t,this.constructor.DefaultType),"object"==typeof t.reference&&!r(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError("dropdown".toUpperCase()+': Option "reference" provided type "object" without a required "getBoundingClientRect" method.');return t}_getMenuElement(){return t.next(this._element,".dropdown-menu")[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ve;if(t.classList.contains("dropstart"))return ye;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ge:me:e?be:_e}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:e,target:i}){const n=t.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(c);n.length&&y(n,i,"ArrowDown"===e,!n.includes(i)).focus()}static dropdownInterface(t,e){const i=Ae.getOrCreateInstance(t,e);if("string"==typeof e){if(void 0===i[e])throw new TypeError(`No method named "${e}"`);i[e]()}}static jQueryInterface(t){return this.each((function(){Ae.dropdownInterface(this,t)}))}static clearMenus(e){if(e&&(2===e.button||"keyup"===e.type&&"Tab"!==e.key))return;const i=t.find('[data-bs-toggle="dropdown"]');for(let t=0,n=i.length;tthis.matches('[data-bs-toggle="dropdown"]')?this:t.prev(this,'[data-bs-toggle="dropdown"]')[0];return"Escape"===e.key?(n().focus(),void Ae.clearMenus()):"ArrowUp"===e.key||"ArrowDown"===e.key?(i||n().click(),void Ae.getInstance(n())._selectMenuItem(e)):void(i&&"Space"!==e.key||Ae.clearMenus())}}P.on(document,"keydown.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',Ae.dataApiKeydownHandler),P.on(document,"keydown.bs.dropdown.data-api",".dropdown-menu",Ae.dataApiKeydownHandler),P.on(document,"click.bs.dropdown.data-api",Ae.clearMenus),P.on(document,"keyup.bs.dropdown.data-api",Ae.clearMenus),P.on(document,"click.bs.dropdown.data-api",'[data-bs-toggle="dropdown"]',(function(t){t.preventDefault(),Ae.dropdownInterface(this)})),_(Ae);class Te{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,"paddingRight",e=>e+t),this._setElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight",e=>e+t),this._setElementAttributes(".sticky-top","marginRight",e=>e-t)}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=i(Number.parseFloat(s))+"px"})}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(".fixed-top, .fixed-bottom, .is-fixed, .sticky-top","paddingRight"),this._resetElementAttributes(".sticky-top","marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)})}_applyManipulationCallback(e,i){r(e)?i(e):t.find(e,this._element).forEach(i)}isOverflowing(){return this.getWidth()>0}}const Oe={isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},Ce={isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"};class ke{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&f(this._getElement()),this._getElement().classList.add("show"),this._emulateAnimation(()=>{b(t)})):b(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove("show"),this._emulateAnimation(()=>{this.dispose(),b(t)})):b(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className="modal-backdrop",this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...Oe,..."object"==typeof t?t:{}}).rootElement=a(t.rootElement),l("backdrop",t,Ce),t}_append(){this._isAppended||(this._config.rootElement.appendChild(this._getElement()),P.on(this._getElement(),"mousedown.bs.backdrop",()=>{b(this._config.clickCallback)}),this._isAppended=!0)}dispose(){this._isAppended&&(P.off(this._element,"mousedown.bs.backdrop"),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){v(t,this._getElement(),this._config.isAnimated)}}const Le={backdrop:!0,keyboard:!0,focus:!0},xe={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"};class De extends B{constructor(e,i){super(e),this._config=this._getConfig(i),this._dialog=t.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new Te}static get Default(){return Le}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||P.trigger(this._element,"show.bs.modal",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add("modal-open"),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),P.on(this._element,"click.dismiss.bs.modal",'[data-bs-dismiss="modal"]',t=>this.hide(t)),P.on(this._dialog,"mousedown.dismiss.bs.modal",()=>{P.one(this._element,"mouseup.dismiss.bs.modal",t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)})}),this._showBackdrop(()=>this._showElement(t)))}hide(t){if(t&&["A","AREA"].includes(t.target.tagName)&&t.preventDefault(),!this._isShown||this._isTransitioning)return;if(P.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const e=this._isAnimated();e&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),P.off(document,"focusin.bs.modal"),this._element.classList.remove("show"),P.off(this._element,"click.dismiss.bs.modal"),P.off(this._dialog,"mousedown.dismiss.bs.modal"),this._queueCallback(()=>this._hideModal(),this._element,e)}dispose(){[window,this._dialog].forEach(t=>P.off(t,".bs.modal")),this._backdrop.dispose(),super.dispose(),P.off(document,"focusin.bs.modal")}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new ke({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_getConfig(t){return t={...Le,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("modal",t,xe),t}_showElement(e){const i=this._isAnimated(),n=t.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,n&&(n.scrollTop=0),i&&f(this._element),this._element.classList.add("show"),this._config.focus&&this._enforceFocus(),this._queueCallback(()=>{this._config.focus&&this._element.focus(),this._isTransitioning=!1,P.trigger(this._element,"shown.bs.modal",{relatedTarget:e})},this._dialog,i)}_enforceFocus(){P.off(document,"focusin.bs.modal"),P.on(document,"focusin.bs.modal",t=>{document===t.target||this._element===t.target||this._element.contains(t.target)||this._element.focus()})}_setEscapeEvent(){this._isShown?P.on(this._element,"keydown.dismiss.bs.modal",t=>{this._config.keyboard&&"Escape"===t.key?(t.preventDefault(),this.hide()):this._config.keyboard||"Escape"!==t.key||this._triggerBackdropTransition()}):P.off(this._element,"keydown.dismiss.bs.modal")}_setResizeEvent(){this._isShown?P.on(window,"resize.bs.modal",()=>this._adjustDialog()):P.off(window,"resize.bs.modal")}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide(()=>{document.body.classList.remove("modal-open"),this._resetAdjustments(),this._scrollBar.reset(),P.trigger(this._element,"hidden.bs.modal")})}_showBackdrop(t){P.on(this._element,"click.dismiss.bs.modal",t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())}),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(P.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains("modal-static")||(n||(i.overflowY="hidden"),t.add("modal-static"),this._queueCallback(()=>{t.remove("modal-static"),n||this._queueCallback(()=>{i.overflowY=""},this._dialog)},this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!g()||i&&!t&&g())&&(this._element.style.paddingLeft=e+"px"),(i&&!t&&!g()||!i&&t&&g())&&(this._element.style.paddingRight=e+"px")}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=De.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}P.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=s(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),P.one(e,"show.bs.modal",t=>{t.defaultPrevented||P.one(e,"hidden.bs.modal",()=>{c(this)&&this.focus()})}),De.getOrCreateInstance(e).toggle(this)})),_(De);const Se={backdrop:!0,keyboard:!0,scroll:!1},Ie={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"};class Ne extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._addEventListeners()}static get NAME(){return"offcanvas"}static get Default(){return Se}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||P.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||((new Te).hide(),this._enforceFocusOnElement(this._element)),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add("show"),this._queueCallback(()=>{P.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})},this._element,!0))}hide(){this._isShown&&(P.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(P.off(document,"focusin.bs.offcanvas"),this._element.blur(),this._isShown=!1,this._element.classList.remove("show"),this._backdrop.hide(),this._queueCallback(()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new Te).reset(),P.trigger(this._element,"hidden.bs.offcanvas")},this._element,!0)))}dispose(){this._backdrop.dispose(),super.dispose(),P.off(document,"focusin.bs.offcanvas")}_getConfig(t){return t={...Se,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},l("offcanvas",t,Ie),t}_initializeBackDrop(){return new ke({isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_enforceFocusOnElement(t){P.off(document,"focusin.bs.offcanvas"),P.on(document,"focusin.bs.offcanvas",e=>{document===e.target||t===e.target||t.contains(e.target)||t.focus()}),t.focus()}_addEventListeners(){P.on(this._element,"click.dismiss.bs.offcanvas",'[data-bs-dismiss="offcanvas"]',()=>this.hide()),P.on(this._element,"keydown.dismiss.bs.offcanvas",t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()})}static jQueryInterface(t){return this.each((function(){const e=Ne.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}P.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(e){const i=s(this);if(["A","AREA"].includes(this.tagName)&&e.preventDefault(),h(this))return;P.one(i,"hidden.bs.offcanvas",()=>{c(this)&&this.focus()});const n=t.findOne(".offcanvas.show");n&&n!==i&&Ne.getInstance(n).hide(),Ne.getOrCreateInstance(i).toggle(this)})),P.on(window,"load.bs.offcanvas.data-api",()=>t.find(".offcanvas.show").forEach(t=>Ne.getOrCreateInstance(t).show())),_(Ne);const je=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Me=/^(?:(?:https?|mailto|ftp|tel|file):|[^#&/:?]*(?:[#/?]|$))/i,Pe=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,He=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!je.has(i)||Boolean(Me.test(t.nodeValue)||Pe.test(t.nodeValue));const n=e.filter(t=>t instanceof RegExp);for(let t=0,e=n.length;t{He(t,a)||i.removeAttribute(t.nodeName)})}return n.body.innerHTML}const Be=new RegExp("(^|\\s)bs-tooltip\\S+","g"),We=new Set(["sanitize","allowList","sanitizeFn"]),qe={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},ze={AUTO:"auto",TOP:"top",RIGHT:g()?"left":"right",BOTTOM:"bottom",LEFT:g()?"right":"left"},$e={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},Ue={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"};class Fe extends B{constructor(t,e){if(void 0===fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return $e}static get NAME(){return"tooltip"}static get Event(){return Ue}static get DefaultType(){return qe}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains("show"))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),P.off(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this.tip&&this.tip.remove(),this._popper&&this._popper.destroy(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=P.trigger(this._element,this.constructor.Event.SHOW),i=d(this._element),n=null===i?this._element.ownerDocument.documentElement.contains(this._element):i.contains(this._element);if(t.defaultPrevented||!n)return;const s=this.getTipElement(),o=e(this.constructor.NAME);s.setAttribute("id",o),this._element.setAttribute("aria-describedby",o),this.setContent(),this._config.animation&&s.classList.add("fade");const r="function"==typeof this._config.placement?this._config.placement.call(this,s,this._element):this._config.placement,a=this._getAttachment(r);this._addAttachmentClass(a);const{container:l}=this._config;R.set(s,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(l.appendChild(s),P.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=ue(this._element,s,this._getPopperConfig(a)),s.classList.add("show");const c="function"==typeof this._config.customClass?this._config.customClass():this._config.customClass;c&&s.classList.add(...c.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>{P.on(t,"mouseover",u)});const h=this.tip.classList.contains("fade");this._queueCallback(()=>{const t=this._hoverState;this._hoverState=null,P.trigger(this._element,this.constructor.Event.SHOWN),"out"===t&&this._leave(null,this)},this.tip,h)}hide(){if(!this._popper)return;const t=this.getTipElement();if(P.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove("show"),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach(t=>P.off(t,"mouseover",u)),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains("fade");this._queueCallback(()=>{this._isWithActiveTrigger()||("show"!==this._hoverState&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),P.trigger(this._element,this.constructor.Event.HIDDEN),this._popper&&(this._popper.destroy(),this._popper=null))},this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");return t.innerHTML=this._config.template,this.tip=t.children[0],this.tip}setContent(){const e=this.getTipElement();this.setElementContent(t.findOne(".tooltip-inner",e),this.getTitle()),e.classList.remove("fade","show")}setElementContent(t,e){if(null!==t)return r(e)?(e=a(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.appendChild(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Re(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){let t=this._element.getAttribute("data-bs-original-title");return t||(t="function"==typeof this._config.title?this._config.title.call(this._element):this._config.title),t}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){const i=this.constructor.DATA_KEY;return(e=e||R.get(t.delegateTarget,i))||(e=new this.constructor(t.delegateTarget,this._getDelegateConfig()),R.set(t.delegateTarget,i,e)),e}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map(t=>Number.parseInt(t,10)):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add("bs-tooltip-"+this.updateAttachment(t))}_getAttachment(t){return ze[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach(t=>{if("click"===t)P.on(this._element,this.constructor.Event.CLICK,this._config.selector,t=>this.toggle(t));else if("manual"!==t){const e="hover"===t?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i="hover"===t?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;P.on(this._element,e,this._config.selector,t=>this._enter(t)),P.on(this._element,i,this._config.selector,t=>this._leave(t))}}),this._hideModalHandler=()=>{this._element&&this.hide()},P.on(this._element.closest(".modal"),"hide.bs.modal",this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?"focus":"hover"]=!0),e.getTipElement().classList.contains("show")||"show"===e._hoverState?e._hoverState="show":(clearTimeout(e._timeout),e._hoverState="show",e._config.delay&&e._config.delay.show?e._timeout=setTimeout(()=>{"show"===e._hoverState&&e.show()},e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?"focus":"hover"]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState="out",e._config.delay&&e._config.delay.hide?e._timeout=setTimeout(()=>{"out"===e._hoverState&&e.hide()},e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach(t=>{We.has(t)&&delete e[t]}),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:a(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),l("tooltip",t,this.constructor.DefaultType),t.sanitize&&(t.template=Re(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};if(this._config)for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Be);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}static jQueryInterface(t){return this.each((function(){const e=Fe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}_(Fe);const Ve=new RegExp("(^|\\s)bs-popover\\S+","g"),Ke={...Fe.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},Xe={...Fe.DefaultType,content:"(string|element|function)"},Ye={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class Qe extends Fe{static get Default(){return Ke}static get NAME(){return"popover"}static get Event(){return Ye}static get DefaultType(){return Xe}isWithContent(){return this.getTitle()||this._getContent()}getTipElement(){return this.tip||(this.tip=super.getTipElement(),this.getTitle()||t.findOne(".popover-header",this.tip).remove(),this._getContent()||t.findOne(".popover-body",this.tip).remove()),this.tip}setContent(){const e=this.getTipElement();this.setElementContent(t.findOne(".popover-header",e),this.getTitle());let i=this._getContent();"function"==typeof i&&(i=i.call(this._element)),this.setElementContent(t.findOne(".popover-body",e),i),e.classList.remove("fade","show")}_addAttachmentClass(t){this.getTipElement().classList.add("bs-popover-"+this.updateAttachment(t))}_getContent(){return this._element.getAttribute("data-bs-content")||this._config.content}_cleanTipClass(){const t=this.getTipElement(),e=t.getAttribute("class").match(Ve);null!==e&&e.length>0&&e.map(t=>t.trim()).forEach(e=>t.classList.remove(e))}static jQueryInterface(t){return this.each((function(){const e=Qe.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}_(Qe);const Ge={offset:10,method:"auto",target:""},Ze={offset:"number",method:"string",target:"(string|element)"};class Je extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._selector=`${this._config.target} .nav-link, ${this._config.target} .list-group-item, ${this._config.target} .dropdown-item`,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,P.on(this._scrollElement,"scroll.bs.scrollspy",()=>this._process()),this.refresh(),this._process()}static get Default(){return Ge}static get NAME(){return"scrollspy"}refresh(){const e=this._scrollElement===this._scrollElement.window?"offset":"position",i="auto"===this._config.method?e:this._config.method,s="position"===i?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.find(this._selector).map(e=>{const o=n(e),r=o?t.findOne(o):null;if(r){const t=r.getBoundingClientRect();if(t.width||t.height)return[U[i](r).top+s,o]}return null}).filter(t=>t).sort((t,e)=>t[0]-e[0]).forEach(t=>{this._offsets.push(t[0]),this._targets.push(t[1])})}dispose(){P.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){if("string"!=typeof(t={...Ge,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target&&r(t.target)){let{id:i}=t.target;i||(i=e("scrollspy"),t.target.id=i),t.target="#"+i}return l("scrollspy",t,Ze),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${t}[data-bs-target="${e}"],${t}[href="${e}"]`),n=t.findOne(i.join(","));n.classList.contains("dropdown-item")?(t.findOne(".dropdown-toggle",n.closest(".dropdown")).classList.add("active"),n.classList.add("active")):(n.classList.add("active"),t.parents(n,".nav, .list-group").forEach(e=>{t.prev(e,".nav-link, .list-group-item").forEach(t=>t.classList.add("active")),t.prev(e,".nav-item").forEach(e=>{t.children(e,".nav-link").forEach(t=>t.classList.add("active"))})})),P.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:e})}_clear(){t.find(this._selector).filter(t=>t.classList.contains("active")).forEach(t=>t.classList.remove("active"))}static jQueryInterface(t){return this.each((function(){const e=Je.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(window,"load.bs.scrollspy.data-api",()=>{t.find('[data-bs-spy="scroll"]').forEach(t=>new Je(t))}),_(Je);class ti extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains("active"))return;let e;const i=s(this._element),n=this._element.closest(".nav, .list-group");if(n){const i="UL"===n.nodeName||"OL"===n.nodeName?":scope > li > .active":".active";e=t.find(i,n),e=e[e.length-1]}const o=e?P.trigger(e,"hide.bs.tab",{relatedTarget:this._element}):null;if(P.trigger(this._element,"show.bs.tab",{relatedTarget:e}).defaultPrevented||null!==o&&o.defaultPrevented)return;this._activate(this._element,n);const r=()=>{P.trigger(e,"hidden.bs.tab",{relatedTarget:this._element}),P.trigger(this._element,"shown.bs.tab",{relatedTarget:e})};i?this._activate(i,i.parentNode,r):r()}_activate(e,i,n){const s=(!i||"UL"!==i.nodeName&&"OL"!==i.nodeName?t.children(i,".active"):t.find(":scope > li > .active",i))[0],o=n&&s&&s.classList.contains("fade"),r=()=>this._transitionComplete(e,s,n);s&&o?(s.classList.remove("show"),this._queueCallback(r,e,!0)):r()}_transitionComplete(e,i,n){if(i){i.classList.remove("active");const e=t.findOne(":scope > .dropdown-menu .active",i.parentNode);e&&e.classList.remove("active"),"tab"===i.getAttribute("role")&&i.setAttribute("aria-selected",!1)}e.classList.add("active"),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!0),f(e),e.classList.contains("fade")&&e.classList.add("show");let s=e.parentNode;if(s&&"LI"===s.nodeName&&(s=s.parentNode),s&&s.classList.contains("dropdown-menu")){const i=e.closest(".dropdown");i&&t.find(".dropdown-toggle",i).forEach(t=>t.classList.add("active")),e.setAttribute("aria-expanded",!0)}n&&n()}static jQueryInterface(t){return this.each((function(){const e=ti.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}P.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),h(this)||ti.getOrCreateInstance(this).show()})),_(ti);const ei={animation:"boolean",autohide:"boolean",delay:"number"},ii={animation:!0,autohide:!0,delay:5e3};class ni extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return ei}static get Default(){return ii}static get NAME(){return"toast"}show(){P.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove("hide"),f(this._element),this._element.classList.add("showing"),this._queueCallback(()=>{this._element.classList.remove("showing"),this._element.classList.add("show"),P.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()},this._element,this._config.animation))}hide(){this._element.classList.contains("show")&&(P.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.remove("show"),this._queueCallback(()=>{this._element.classList.add("hide"),P.trigger(this._element,"hidden.bs.toast")},this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains("show")&&this._element.classList.remove("show"),super.dispose()}_getConfig(t){return t={...ii,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},l("toast",t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout(()=>{this.hide()},this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){P.on(this._element,"click.dismiss.bs.toast",'[data-bs-dismiss="toast"]',()=>this.hide()),P.on(this._element,"mouseover.bs.toast",t=>this._onInteraction(t,!0)),P.on(this._element,"mouseout.bs.toast",t=>this._onInteraction(t,!1)),P.on(this._element,"focusin.bs.toast",t=>this._onInteraction(t,!0)),P.on(this._element,"focusout.bs.toast",t=>this._onInteraction(t,!1))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ni.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return _(ni),{Alert:W,Button:q,Carousel:Z,Collapse:et,Dropdown:Ae,Modal:De,Offcanvas:Ne,Popover:Qe,ScrollSpy:Je,Tab:ti,Toast:ni,Tooltip:Fe}})); 7 | //# sourceMappingURL=bootstrap.bundle.min.js.map --------------------------------------------------------------------------------