diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index ac0f9788..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,95 +0,0 @@ -version: 2.1 - -orbs: - aws-ecr: circleci/aws-ecr@8.2.1 - kubernetes: circleci/kubernetes@1.3.1 - helm: circleci/helm@2.0.1 - node: circleci/node@5.2.0 - -jobs: - build: - machine: - image: ubuntu-2204:2024.01.1 - resource_class: large - steps: - - checkout - - node/install: - node-version: "22.3" - - run: - name: Install dependencies - command: | - npm install - - run: - name: Run tests - command: | - npm run test - - run: - name: Build project - command: | - npm run build - - build_docker: - machine: - image: ubuntu-2204:2024.01.1 - resource_class: large - steps: - - checkout - - aws-ecr/build-image: - push-image: false - dockerfile: Dockerfile - path: ./ - build-path: ./ - tag: "$CIRCLE_SHA1,$CIRCLE_TAG" - repo: "simple-staking" - - run: - name: Save Docker image to export it to workspace - command: | - docker save $(docker image ls --format '{{.Repository}}:{{.Tag}}') > /tmp/simple-staking.tar - - persist_to_workspace: - root: /tmp - paths: - - simple-staking.tar - - push_docker: - machine: - image: ubuntu-2204:2024.01.1 - resource_class: large - steps: - - attach_workspace: - at: /tmp - - run: - name: Load Docker image from workspace - command: | - docker load -i /tmp/simple-staking.tar - - aws-ecr/ecr-login: - aws-access-key-id: AWS_ACCESS_KEY_ID - aws-secret-access-key: AWS_SECRET_ACCESS_KEY - region: "$AWS_REGION" - - aws-ecr/push-image: - registry-id: AWS_ECR_REGISTRY_ID - region: "$AWS_REGION" - repo: "simple-staking" - tag: "$CIRCLE_SHA1,$CIRCLE_TAG" - -workflows: - CICD: - jobs: - - build - - build_docker: - filters: - tags: - only: /.*/ - branches: - only: - - dev - - main - - push_docker: - requires: - - build_docker - filters: - tags: - only: /.*/ - branches: - only: - - dev - - main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..b3976930 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,13 @@ +name: ci + +on: + pull_request: + branches: + - "**" + +jobs: + lint_test: + uses: babylonlabs-io/.github/.github/workflows/reusable_node_lint_test.yml@v0.3.0 + with: + run-build: true + run-unit-tests: true diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..2c9f4030 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,118 @@ +name: docker_publish + +on: + push: + branches: + - "main" + - "dev" + tags: + - "*" + +jobs: + lint_test: + uses: babylonlabs-io/.github/.github/workflows/reusable_node_lint_test.yml@v0.3.0 + with: + run-build: true + run-unit-tests: true + + docker_build: + needs: [lint_test] + runs-on: ubuntu-22.04 + strategy: + matrix: + environment: [devnet, staging, testnet, mainnet-private, mainnet] + environment: ${{ matrix.environment }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + tags: simple-staking:${{ github.sha }} + outputs: type=docker,dest=/tmp/simple-staking-${{ matrix.environment }}.tar + build-args: | + NEXT_PUBLIC_MEMPOOL_API=${{ vars.NEXT_PUBLIC_MEMPOOL_API }} + NEXT_PUBLIC_API_URL=${{ vars.NEXT_PUBLIC_API_URL }} + NEXT_PUBLIC_NETWORK=${{ vars.NEXT_PUBLIC_NETWORK }} + NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES=${{ vars.NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES }} + + - name: Upload Docker image to workspace + uses: actions/upload-artifact@v4 + with: + name: simple-staking-${{ matrix.environment }} + path: /tmp/simple-staking-${{ matrix.environment }}.tar + + dockerhub_publish: + runs-on: ubuntu-22.04 + strategy: + matrix: + environment: [devnet, staging, testnet, mainnet-private, mainnet] + needs: ["docker_build"] + steps: + - name: Download Docker image from workspace + uses: actions/download-artifact@v4 + with: + name: simple-staking-${{ matrix.environment }} + path: /tmp/ + + - name: Load Docker image + run: docker load -i /tmp/simple-staking-${{ matrix.environment }}.tar + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Push Docker image with SHA + run: | + docker tag simple-staking:${{ github.sha }} ${{ vars.DOCKERHUB_REGISTRY_ID }}/simple-staking:${{ github.sha }}-${{ matrix.environment }} + docker push ${{ vars.DOCKERHUB_REGISTRY_ID }}/simple-staking:${{ github.sha }}-${{ matrix.environment }} + + - name: Push Docker image with Tag + if: startsWith(github.ref, 'refs/tags/') + run: | + docker tag simple-staking:${{ github.sha }} ${{ vars.DOCKERHUB_REGISTRY_ID }}/simple-staking:${{ github.ref_name }}-${{ matrix.environment }} + docker push ${{ vars.DOCKERHUB_REGISTRY_ID }}/simple-staking:${{ github.ref_name }}-${{ matrix.environment }} + + ecr_publish: + runs-on: ubuntu-22.04 + strategy: + matrix: + environment: [devnet, staging, testnet, mainnet-private, mainnet] + needs: ["docker_build"] + steps: + - name: Download Docker image from workspace + uses: actions/download-artifact@v4 + with: + name: simple-staking-${{ matrix.environment }} + path: /tmp/ + + - name: Load Docker image + run: docker load -i /tmp/simple-staking-${{ matrix.environment }}.tar + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ vars.AWS_ECR_REGION }} + + - name: Login to Amazon ECR Private + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Push Docker image with SHA + run: | + docker tag simple-staking:${{ github.sha }} ${{ vars.AWS_ECR_REGISTRY_ID }}/simple-staking:${{ github.sha }}-${{ matrix.environment }} + docker push ${{ vars.AWS_ECR_REGISTRY_ID }}/simple-staking:${{ github.sha }}-${{ matrix.environment }} + + - name: Push Docker image with Tag + if: startsWith(github.ref, 'refs/tags/') + run: | + docker tag simple-staking:${{ github.sha }} ${{ vars.AWS_ECR_REGISTRY_ID }}/simple-staking:${{ github.ref_name }}-${{ matrix.environment }} + docker push ${{ vars.AWS_ECR_REGISTRY_ID }}/simple-staking:${{ github.ref_name }}-${{ matrix.environment }} diff --git a/Dockerfile b/Dockerfile index 08973a98..c13ce121 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,17 +13,20 @@ COPY next.config.mjs . COPY tsconfig.json . COPY tailwind.config.ts . COPY postcss.config.js . -COPY docker-entrypoint.sh . - -# We replace NEXT_PUBLIC_* variables here with placeholders -# as next.js automatically replaces those during building -# Later the docker-entrypoint.sh script finds such variables and replaces them -# with the docker environment variables we have set -RUN NEXT_PUBLIC_MEMPOOL_API=APP_NEXT_PUBLIC_MEMPOOL_API \ - NEXT_PUBLIC_API_URL=APP_NEXT_PUBLIC_API_URL \ - NEXT_PUBLIC_NETWORK=APP_NEXT_PUBLIC_NETWORK \ - NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES=APP_NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES \ - npm run build + +ARG NEXT_PUBLIC_MEMPOOL_API +ENV NEXT_PUBLIC_MEMPOOL_API=${NEXT_PUBLIC_MEMPOOL_API} + +ARG NEXT_PUBLIC_API_URL +ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} + +ARG NEXT_PUBLIC_NETWORK +ENV NEXT_PUBLIC_NETWORK=${NEXT_PUBLIC_NETWORK} + +ARG NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES +ENV NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES=${NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES} + +RUN npm run build # Step 2. Production image, copy all the files and run next FROM node:22-alpine3.19 AS runner @@ -35,7 +38,6 @@ RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs USER nextjs -COPY --from=builder --chown=nextjs:nodejs /app/docker-entrypoint.sh ./docker-entrypoint.sh COPY --from=builder /app/public ./public # Automatically leverage output traces to reduce image size @@ -46,6 +48,5 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # Uncomment the following line to disable telemetry at run time ENV NEXT_TELEMETRY_DISABLED 1 -ENTRYPOINT ["/app/docker-entrypoint.sh"] CMD ["node", "server.js"] STOPSIGNAL SIGTERM diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index f1fc77dd..00000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env sh -set -Ex - -# This method has been inspired by the comment here: -# https://github.com/vercel/next.js/discussions/17641#discussioncomment-339555 -function apply_path { - find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_MEMPOOL_API#$MEMPOOL_API#g" - find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_API_URL#$API_URL#g" - find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_NETWORK#$NETWORK#g" - find /app/.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#APP_NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES#$DISPLAY_TESTING_MESSAGES#g" -} - -apply_path -echo "Starting Nextjs" -exec "$@" diff --git a/package-lock.json b/package-lock.json index eabd3f5f..a9eccc46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-staking", - "version": "0.2.30", + "version": "0.2.31", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-staking", - "version": "0.2.30", + "version": "0.2.31", "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", "@keystonehq/animated-qr": "^0.8.6", diff --git a/package.json b/package.json index 4275f8fb..28775822 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simple-staking", - "version": "0.2.30", + "version": "0.2.31", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/components/Connect/ConnectSmall.tsx b/src/app/components/Connect/ConnectSmall.tsx index 906e6b38..21d33c78 100644 --- a/src/app/components/Connect/ConnectSmall.tsx +++ b/src/app/components/Connect/ConnectSmall.tsx @@ -54,7 +54,7 @@ export const ConnectSmall: React.FC = ({ > - + ); }; diff --git a/src/app/components/Delegations/Delegation.tsx b/src/app/components/Delegations/Delegation.tsx index cadd7aa3..087f7c94 100644 --- a/src/app/components/Delegations/Delegation.tsx +++ b/src/app/components/Delegations/Delegation.tsx @@ -151,7 +151,7 @@ export const Delegation: React.FC = ({ > - + {generateActionButton()} diff --git a/src/app/components/FAQ/FAQ.tsx b/src/app/components/FAQ/FAQ.tsx index 8beb4265..8cf7f327 100644 --- a/src/app/components/FAQ/FAQ.tsx +++ b/src/app/components/FAQ/FAQ.tsx @@ -1,17 +1,48 @@ +import { useEffect, useState } from "react"; + +import { useGlobalParams } from "@/app/context/api/GlobalParamsProvider"; +import { useBtcHeight } from "@/app/context/mempool/BtcHeightProvider"; import { getNetworkConfig } from "@/config/network.config"; +import { + getCurrentGlobalParamsVersion, + ParamsWithContext, +} from "@/utils/globalParams"; -import { Section } from "./Section"; import { questions } from "./data/questions"; +import { Section } from "./Section"; interface FAQProps {} export const FAQ: React.FC = () => { + const [paramWithCtx, setParamWithCtx] = useState< + ParamsWithContext | undefined + >(); const { coinName } = getNetworkConfig(); + const btcHeight = useBtcHeight(); + const globalParams = useGlobalParams(); + + useEffect(() => { + if (!btcHeight || !globalParams.data) { + return; + } + const paramsWithCtx = getCurrentGlobalParamsVersion( + btcHeight + 1, + globalParams.data, + ); + if (!paramsWithCtx) { + return; + } + setParamWithCtx(paramsWithCtx); + }, [globalParams, btcHeight]); + return (

FAQ

- {questions(coinName).map((question) => ( + {questions( + coinName, + paramWithCtx?.currentVersion?.confirmationDepth, + ).map((question) => (
{ +export const questions = ( + coinName: string, + confirmationDepth?: number, +): Question[] => { const questionList = [ { title: "What is Babylon?", @@ -47,7 +50,7 @@ export const questions = (coinName: string): Question[] => { }, { title: "How long will it take for my stake to become active?", - content: `

A stake’s status demonstrates the current stage of the staking process. All stake starts in a Pending state which denotes that the ${coinName} Staking transaction does not yet have sufficient ${coinName} block confirmations. As soon as it receives 10 ${coinName} block confirmations, the status transitions to Overflow or Active.


+ content: `

A stake’s status demonstrates the current stage of the staking process. All stake starts in a Pending state which denotes that the ${coinName} Staking transaction does not yet have sufficient ${coinName} block confirmations. As soon as it receives ${confirmationDepth || 10} ${coinName} block confirmations, the status transitions to Overflow or Active.


In an amount-based cap, A stake is Overflow if the system has already accumulated the maximum amount of ${coinName} it can accept.


In a time-based cap, where there is a starting block height and ending block height, a stake is overflow if it is included in a ${coinName} block that is newer than the ending block.


diff --git a/src/app/components/Modals/ConnectModal.tsx b/src/app/components/Modals/ConnectModal.tsx index 9c7fefd0..7ff42c9f 100644 --- a/src/app/components/Modals/ConnectModal.tsx +++ b/src/app/components/Modals/ConnectModal.tsx @@ -224,7 +224,7 @@ export const ConnectModal: React.FC = ({ > - +
)}
diff --git a/src/app/components/Modals/Terms/data/terms.tsx b/src/app/components/Modals/Terms/data/terms.tsx index 8b012dfc..c75c045a 100644 --- a/src/app/components/Modals/Terms/data/terms.tsx +++ b/src/app/components/Modals/Terms/data/terms.tsx @@ -1,14 +1,15 @@ +import { getNetworkAppUrl } from "@/config"; + export const Terms = () => { + const url = getNetworkAppUrl(); + return (

Last updated [27 May 2024]


- - https://btcstaking.testnet.babylonchain.io/ + + {url} {" "} is a website-hosted user interface (the{" "} “Interface”).{" "} diff --git a/src/app/components/Stakers/Stakers.tsx b/src/app/components/Stakers/Stakers.tsx index 8ff9954e..d4068422 100644 --- a/src/app/components/Stakers/Stakers.tsx +++ b/src/app/components/Stakers/Stakers.tsx @@ -89,8 +89,8 @@ export const Stakers: React.FC = () => { )}

- - + + ); }; diff --git a/src/app/components/Staking/FinalityProviders/FinalityProvider.tsx b/src/app/components/Staking/FinalityProviders/FinalityProvider.tsx index 11b3837c..b576eec5 100644 --- a/src/app/components/Staking/FinalityProviders/FinalityProvider.tsx +++ b/src/app/components/Staking/FinalityProviders/FinalityProvider.tsx @@ -79,7 +79,7 @@ export const FinalityProvider: React.FC = ({ > - + No data provided )} @@ -100,7 +100,10 @@ export const FinalityProvider: React.FC = ({ > - +

Commission:

@@ -115,7 +118,10 @@ export const FinalityProvider: React.FC = ({ > - +
diff --git a/src/app/components/Staking/Form/StakingAmount.tsx b/src/app/components/Staking/Form/StakingAmount.tsx index 244353d2..a680754b 100644 --- a/src/app/components/Staking/Form/StakingAmount.tsx +++ b/src/app/components/Staking/Form/StakingAmount.tsx @@ -84,7 +84,7 @@ export const StakingAmount: React.FC = ({ }, { valid: satoshis <= btcWalletBalanceSat, - message: `${errorLabel} must be no more than ${satoshiToBtc(btcWalletBalanceSat)} wallet balance.`, + message: `${errorLabel} exceeds your balance (${satoshiToBtc(btcWalletBalanceSat)} ${coinName})!`, }, { valid: validateDecimalPoints(value), diff --git a/src/app/components/Staking/Staking.tsx b/src/app/components/Staking/Staking.tsx index 2881e89d..ae188c56 100644 --- a/src/app/components/Staking/Staking.tsx +++ b/src/app/components/Staking/Staking.tsx @@ -626,7 +626,7 @@ export const Staking: React.FC = ({ > Preview - + {previewReady && ( { > - + )} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3273667a..1883f113 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import { Inter } from "next/font/google"; import "react-responsive-modal/styles.css"; import "react-tooltip/dist/react-tooltip.css"; +import { getNetworkAppUrl } from "@/config"; + import "./globals.css"; import Providers from "./providers"; @@ -27,17 +29,11 @@ export default function RootLayout({ - + - + diff --git a/src/config/index.ts b/src/config/index.ts index 99ae96e0..88fc21ef 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -5,3 +5,10 @@ export const shouldDisplayTestingMsg = (): boolean => { process.env.NEXT_PUBLIC_DISPLAY_TESTING_MESSAGES?.toString() !== "false" ); }; + +// getNetworkAppUrl function is used to get the network app url based on the environment +export const getNetworkAppUrl = (): string => { + return shouldDisplayTestingMsg() + ? "https://btcstaking.testnet.babylonchain.io" + : "https://btcstaking.babylonchain.io"; +}; diff --git a/src/utils/utxo/index.ts b/src/utils/utxo/index.ts index 7b6cf84a..18fae80b 100644 --- a/src/utils/utxo/index.ts +++ b/src/utils/utxo/index.ts @@ -2,7 +2,8 @@ import { postVerifyUtxoOrdinals, UtxoInfo } from "@/app/api/postFilterOrdinals"; import { InscriptionIdentifier, UTXO } from "../wallet/wallet_provider"; -const LOW_VALUE_UTXO_THRESHOLD = 10000; +export const LOW_VALUE_UTXO_THRESHOLD = 10000; +export const WALLET_FETCH_INSRIPTIONS_TIMEOUT = 3000; // 3 seconds /** * Filters out UTXOs that contain ordinals. @@ -34,7 +35,20 @@ export const filterOrdinals = async ( // try to get the ordinals from the wallet first, if the wallet supports it // otherwise fallback to the Babylon API try { - const inscriptions = await getInscriptionsFromWalletCb(); + const inscriptions = await Promise.race([ + getInscriptionsFromWalletCb(), + new Promise((_, reject) => + setTimeout( + () => + reject( + new Error( + "Request timed out when fetching inscriptions from wallet", + ), + ), + WALLET_FETCH_INSRIPTIONS_TIMEOUT, + ), + ), + ]); // filter out the utxos that contains ordinals return utxos.filter( (utxo) => diff --git a/tests/utils/utox/utxo.test.ts b/tests/utils/utox/utxo.test.ts index 04c0f950..174c4398 100644 --- a/tests/utils/utox/utxo.test.ts +++ b/tests/utils/utox/utxo.test.ts @@ -1,5 +1,5 @@ import { postVerifyUtxoOrdinals } from "@/app/api/postFilterOrdinals"; -import { filterOrdinals } from "@/utils/utxo"; +import { filterOrdinals, WALLET_FETCH_INSRIPTIONS_TIMEOUT } from "@/utils/utxo"; import { InscriptionIdentifier, UTXO } from "@/utils/wallet/wallet_provider"; // Mock the dependencies @@ -109,4 +109,29 @@ describe("filterOrdinals", () => { expect(result).toEqual(mockUtxos); }); + + it("should filter UTXOs using the API fallback when wallet callback times out", async () => { + const mockApiResponse = [ + { txid: "txid1", vout: 0, inscription: true }, + { txid: "txid2", vout: 1, inscription: false }, + { txid: "txid3", vout: 2, inscription: false }, + ]; + const getInscriptionsFromWalletCb = jest.fn().mockImplementation(() => { + return new Promise((resolve) => + setTimeout(resolve, WALLET_FETCH_INSRIPTIONS_TIMEOUT + 1000), + ); + }); + + (postVerifyUtxoOrdinals as jest.Mock).mockResolvedValue(mockApiResponse); + + const result = await filterOrdinals( + mockUtxos, + address, + getInscriptionsFromWalletCb, + ); + + expect(getInscriptionsFromWalletCb).toHaveBeenCalledTimes(1); + expect(postVerifyUtxoOrdinals).toHaveBeenCalledWith(mockUtxos, address); + expect(result).toEqual([mockUtxos[1], mockUtxos[2]]); + }); });