From d5684d33b0eeb4fb7356df7db55c6ce168b8363c Mon Sep 17 00:00:00 2001 From: Elliot Voris Date: Wed, 24 Apr 2024 11:33:30 -0500 Subject: [PATCH] [Soroban Merge] Dapps Challenge (#497) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add dapps challenge (#487) * Add Dapps Challenge to Docs Todo: user dashboard visibility * add hide unless dapps * update user card * update checkpoints * add user dashboard to dapps challenges * Update Dapp Challenge Description * Update tsconfig.json * fix tsconfig.json * small grammar fixes * small grammar fixes * small grammar fixes * small grammar fixes * small grammar fixes * update challenge 0 and 1 ; add images for challenge 2 * Capitalize Futurenet; lowercase Dapp when needed * disclaimer; nit crowdfunding -> crowdfund * Update challenge-0-crowdfund.mdx * update layout and guides * Update smart-contracts.mdx * update content * add links * nit update soroban quickstart * Update react.mdx * Update challenge-0-crowdfund.mdx * update start course redirect * we -> you * update wrangler endpoint to stellar cloudflare * update wrangler endpoint * add module resolution and jsx * add wrong network page; remove unused code; add vercel.app checking * update wallets conclusion * nit Update wallets.mdx * nit update broken link * Freighter -> WalletData Component * update crowdfund dapps challenge for P10 * Fix Setup * update docker workflow; add better notes * nit standalone docker requirement * re-enable .vercel.app check; move ParentChallengeForm * move Login function * fixed automatic log in problem * comment out connection check * comment out auto connect hook; nit Login -> Connect * removed unused setLoading() changed order of operations * update to checkout challenge branch * completed challenge message = clickable url * remember user is logged in * setup `BrowserOnly` * Update yarn.lock * Update yarn.lock * merge main into dapps challenge * Update yarn.lock * nit: refresh message * add disconnect button comment; better connection insight * update contract initialization to use strings * auto populate url; add resubmit; fix broken invoke * add setSavedUrl; improve url auto populate * improve local storage for pubkey --------- Co-authored-by: Bri <92327786+briwylde08@users.noreply.github.com> * Update initialization.mdx (#518) * editorial nits for dapps challenge (#544) * editorial nits on crowdfund dapp * editorial nits in payment dapp challenge * Add scaffold soroban (#545) * Create scaffold-soroban.mdx * add video embed * reformat code call outs; add conclusion * Update scaffold-soroban.mdx * nit: link sc * nit add rpc link * add definition to soroban RPC * update deep dive section * dApp -> dapp * remove atomic swap description * update simulate txn section; copy edits * update intro to dapps * nit:fix link * update links; edit copy * nit:typo * SDC UI upgrades (#542) * Update index.tsx * update kv:worker endpoint * Update index.tsx * Add Root component, finish-step-button, update styles * Add library for toast notifications, show milestone notification * Add class for HttpClient, functions for requests * Changed backend model, finalize user progress on course * Add confetti effect on complete button click, refactor * Remove mocks, use endpoint, fix issues * Merge Development into main (#2) * update kv:worker endpoint * Update index.tsx * Add Root component, finish-step-button, update styles * Add library for toast notifications, show milestone notification * Add class for HttpClient, functions for requests * Changed backend model, finalize user progress on course * Add confetti effect on complete button click, refactor * Remove mocks, use endpoint, fix issues --------- Co-authored-by: Iryna Telesheva Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> * Add Dashboard for Dapps challenges (#3) * Added dashboard component, styled cards * Added DashboardHeader, Leaderboard component, tabs, styles * Added ChallengesList component, updated login logic, tabs * Refactor existing approach, update models, add filtering * Add UI folder, add id for switcher and random avatar generating * Remove old dashboard components, rename util file * Add dashboard link to dapps sidebar instead of navbar * Add TODOs, rename component * Fix issues * Add Dashboard for Dapps challenges (#3) (#4) * Added dashboard component, styled cards * Added DashboardHeader, Leaderboard component, tabs, styles * Added ChallengesList component, updated login logic, tabs * Refactor existing approach, update models, add filtering * Add UI folder, add id for switcher and random avatar generating * Remove old dashboard components, rename util file * Add dashboard link to dapps sidebar instead of navbar * Add TODOs, rename component * Fix issues Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> * Merge SDC from julian-dev28:main to Stellar:SSDC23 (#541) * Update index.tsx * update kv:worker endpoint * Update index.tsx * Add Root component, finish-step-button, update styles * Add library for toast notifications, show milestone notification * Add class for HttpClient, functions for requests * Changed backend model, finalize user progress on course * Add confetti effect on complete button click, refactor * Remove mocks, use endpoint, fix issues * Merge Development into main (#2) * update kv:worker endpoint * Update index.tsx * Add Root component, finish-step-button, update styles * Add library for toast notifications, show milestone notification * Add class for HttpClient, functions for requests * Changed backend model, finalize user progress on course * Add confetti effect on complete button click, refactor * Remove mocks, use endpoint, fix issues --------- Co-authored-by: Iryna Telesheva Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> * Add Dashboard for Dapps challenges (#3) (#4) * Added dashboard component, styled cards * Added DashboardHeader, Leaderboard component, tabs, styles * Added ChallengesList component, updated login logic, tabs * Refactor existing approach, update models, add filtering * Add UI folder, add id for switcher and random avatar generating * Remove old dashboard components, rename util file * Add dashboard link to dapps sidebar instead of navbar * Add TODOs, rename component * Fix issues Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> --------- Co-authored-by: Iryna Telesheva Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> * Update the user workflow for the challenges with CI/CD (#5) * Update the Crowdfund challenge with pull request validation step info * Refactor * Add isPullRequestRequired field handling * Update text for Crowdfund checkpoint 8 * Make isPullRequestRequired required * Fix issue with dates * Merge branch 'main' into SSDC23 * Update challenge-0-crowdfund.mdx --------- Co-authored-by: Iryna Telesheva Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> * Updated mdx files on dapps (#525) * Fixed typos in dapps section * Fixed typos in fundamentals-and-concepts section * Add lp dapp challenge (#543) * Create challenge-2-liquidity-pool * update lp dapp challenge * complete lp-dapp challenge * nit spelling * Update challenge-2-liquidity-pool.mdx * nit spelling * nit add steps to workflow * nit update workflow * update workflow again * add diff * edit diff * more diff editing * editorial edits * Update dapps/dapp-challenges/challenge-2-liquidity-pool.mdx Co-authored-by: Elliot Voris * Update dapps/dapp-challenges/challenge-2-liquidity-pool.mdx Co-authored-by: Elliot Voris * Update dapps/dapp-challenges/challenge-2-liquidity-pool.mdx Co-authored-by: Elliot Voris * Update dapps/dapp-challenges/challenge-2-liquidity-pool.mdx Co-authored-by: Elliot Voris * Update dapps/dapp-challenges/challenge-2-liquidity-pool.mdx Co-authored-by: Elliot Voris * add reference to earlier step; fix conflict in crowdfund dapp page * update quickstart version * small wording change --------- Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> Co-authored-by: Elliot Voris * Update Futurenet to Testnet Across Documentation (#577) * Futurenet -> Testnet * fix broken links * update-rpc-page * Update testnet.mdx * Pass 1 * format * update table in testnet.mdx * update state expiration example script to use testnet * replacing futurenet with testnet in contract deployment example * revert SDC contnet * include testnet network passphrase on releases page * update freighter to include testnet alongside futurenet * revert SDC to Use Futurenet for the time being * style: fix up markdown syntax and style in `rpc.mdx` Also adds some small linguistic and grammer changes, as well as more updates from Futurenet to Testnet * docs: include Testnet when describing what RPC servers SDF maintains * style: changing a single ellipsis character with three periods * docs: include gh repo links to the software that runs in quickstart * add the Testnet network to the example `initialize.sh` script * change contract-invoking transaction example code to use Testnet * change to valid futurenet URLs in dapp challenges * Apply suggestions from code review minor tweaks that slipped through the cracks --------- Co-authored-by: Elliot Voris * Soroban Dapps Challenge: Update Sidebar and Remove Local Deployment (#597) * remove challenges top level dropdown; remove local deployment * update Soroban version * Prevent Popup for Testnet * Leaderboard and Freighter API integration (#598) * Add leaderboard with wallet integration feature * Update index.tsx * Add total value locked logic with reseting user data * Refactor reset logic * Refactor sorting * Revert TVL for payment * Update resubmit contract id logic * Fix resubmiting contractId * Add pagination and wallet address to leaderboard * Change columns order * Allow payment be completed * User submit ContractID * use GH token * use npm instead of yarn * Update main.yml * revert back to yarn * use https for wallets kit * try access token * use new github token * set up to use git https * last random try * Update main.yml * use default settings * set origin for wallets-kit * trying npm again * nit: use npm for scripts * Remove stellar wallets kit; add support for users w no progredd * nit: phrasing * Use Original GH Action * Show user address * nit: phrasing * nit: connect message * add missing cd --------- Co-authored-by: Julian Martinez <73849597+Julian-dev28@users.noreply.github.com> Co-authored-by: Julian Martinez * update leaderboard ranking toggle (#609) * Corrected calculation of total pages so that 'Prev' and 'Next' buttons are disabled when table contains less than 10 rows. (#611) * Add tabs to dapps challenge checkpoints (#610) * Update challenge-0-crowdfund.mdx * Add tabs to interaction step * add tabs to checkpoints * feat: provide a means of receiving simple feedback from users (#587) * feat: provide a means of receiving simple feedback from users Here, we have swizzled (ejected) the `DocItem/Footer` component from docusaurus, and are inserting our own `ReaderFeedback` component into it before the rest of the doc footer content. Also, the `custom.scss` file has been used to style this element in a consistent manner with the rest of the site. This is the first step to resolve #586: The user interaction can now take place. From here, we will work on integrating the custom events into Google analytics. Ultimately, we will like to implment this feature into the `stellar-docs` repository, as well. Refs: #586 * Moving some React state things around * Including an `alt` tag for the like/dislike buttons * use the `pageId` for `eventLabel` in the send event * style: Linting the code * style: changing inline svg tag to be on newlines, changine filename to `jsx` * feat: Inserting the `DocItem/Footer` components on `dapps` and `api` pages * feat: migrating to the GA4 plugin in docusaurus * Testing with a new ga4 tracking id for preview domain * add some console statements to test what's happening * add configuration for test gtag container for this pr preview * feat: use `gtag` over the deprecated `ga` function * adding my own test gtag? * Remove the `gtag` function call, and just register state * removing the old-style google analytics tag for good * remove staging tag manager containers in preparation for production * replace staging ga4 tracking id with production tracking id * use `@site/static` for feedback button imports, rename file extension * Remove docker image hashes (#628) * remove mentions of specific image hashes in docker commands * update docker image hash to most recent version on releases page * Update Soroban Dapps Challenge copy (#631) * update redeployment section * add description for deployment * update links * add note about building `target` * Update challenge-0-crowdfund.mdx * Update challenge-2-liquidity-pool.mdx * Sdc mental models: payment dapp upgrades (#641) * bringing sdc-mental-models branch up to date (#632) * feat: provide a means of receiving simple feedback from users (#587) * feat: provide a means of receiving simple feedback from users Here, we have swizzled (ejected) the `DocItem/Footer` component from docusaurus, and are inserting our own `ReaderFeedback` component into it before the rest of the doc footer content. Also, the `custom.scss` file has been used to style this element in a consistent manner with the rest of the site. This is the first step to resolve #586: The user interaction can now take place. From here, we will work on integrating the custom events into Google analytics. Ultimately, we will like to implment this feature into the `stellar-docs` repository, as well. Refs: #586 * Moving some React state things around * Including an `alt` tag for the like/dislike buttons * use the `pageId` for `eventLabel` in the send event * style: Linting the code * style: changing inline svg tag to be on newlines, changine filename to `jsx` * feat: Inserting the `DocItem/Footer` components on `dapps` and `api` pages * feat: migrating to the GA4 plugin in docusaurus * Testing with a new ga4 tracking id for preview domain * add some console statements to test what's happening * add configuration for test gtag container for this pr preview * feat: use `gtag` over the deprecated `ga` function * adding my own test gtag? * Remove the `gtag` function call, and just register state * removing the old-style google analytics tag for good * remove staging tag manager containers in preparation for production * replace staging ga4 tracking id with production tracking id * use `@site/static` for feedback button imports, rename file extension * rpc: rename expirationLedgerSeq to liveUntilLedgerSeq (#625) * rpc: rename expirationLedgerSeq to liveUntilLedgerSeq * rpc: rename expiration ledger in openrpc file --------- Co-authored-by: Elliot Voris * Remove docker image hashes (#628) * remove mentions of specific image hashes in docker commands * update docker image hash to most recent version on releases page * Update WASM retrieval tutorial (#627) * docs: explain the diagnostic events that are emitted in sandbox (#593) * docs: explain the diagnostic events that are emitted in sandbox Perhaps this approach is too verbose? I think having the explanation of what is being seen right there in the tutorial is useful at this early step in the "getting started" section. A new developer is more likely to understand what they see, and remember what it is in the future if we don't try to interrupt their flow at this point by sending them to another page for some (possibly irrelevant) info. I may be incorrect there, and I'm happy to hear opinions from others. Refs: #521 * docs: add a space between two words * editorial in Update hello-world.mdx --------- Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> * Fix a couple typos in rust-dialect.mdx (#630) --------- Co-authored-by: Elliot Voris Co-authored-by: Alfonso Acosta Co-authored-by: George Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> Co-authored-by: Sapo-Dorado * Crowdfund dapp copy updates v1 Added in some (minimal) context throughout. Tidied up a few grammatical/spelling inconsistencies. * add description and update "ship it" workflow * update soroban-cli install * Update challenge-0-crowdfund.mdx * remove extra vercel link * Merge branch 'main' into sdc-mental-models * Add intro to dapps * nit formatting * Payment dapp copy updates Updates to Checkpoints 0-4 of the Payment dapp challenge, including added copy and a few very minor grammatical upgrades. * add more context * Merge Payment dapp fix (#643) * Update latest testnet-compatible JavaScript release versions (#634) * Update latest testnet-compatible JavaScript release versions * Add links to release notes for each version * Upgrade `getLedgerEntries` examples to latest pre-stable releases of the SDKs (#635) Co-authored-by: Elliot Voris * Switch Ubuntu base image to 22.04 from 20.04 (#638) Signed-off-by: Satyam Zode Co-authored-by: Satyam Zode * Update getting-started for 20.0.0-rc.4.1 cli (#636) * Updates to setup.mdx * Add CLI for testnet configuration to Setup * Updates to hello-world.mdx Reorder some code snippets to be after the description * Small edits in storing-data.mdx * Add high_expiration_watermark argument to bump in incrementor * Move deploy-to-testnet after hello-world to allow the user to interact with their contract on testnet * Add Optimizing Builds to hello-world * Update storing-data to remove sandbox interation * Add a deploy-incrementor-to-testnet step * Update position of Create an App * Apply suggestions from code review Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> * Update create-an-app.mdx * Fix typo * Add a mv command for .soroban dir when reorganizing to a multi-contract project * Apply mdx prettier updates * Update astro port in create-an-app.mdx * Apply suggestions from code review Co-authored-by: Elliot Voris * Add .mdx to end of internal markdown links for docusaurus magic * Make sure there are new lines before and after ::: tags * Some additional edits/improvements * Mention that Freighter is available as a firefox add-on * Update the deploy-incrementor url * Apply prettier updates --------- Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Elliot Voris * docs: change preflight wording to simulate transaction (#585) * docs: change preflight wording to simulate transaction A first attempt to resolve #478, there may be more work to be done. I've used a few different phrases, depending on how the sentence structure works, perhaps taking some liberties in the process. I've also left alone the `releases` page for preview releases older than 11. It seems unnecessary to rewrite the development history too far into the past. Refs: #478 * editorial on Update interacting-with-contracts.mdx * added a couple "the"s Update state-expiration.mdx * fix wording about ledger entries to be less confusing * clearing up some other language surrounding footprints --------- Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> * Create data-providers.mdx (#629) * Create data-providers.mdx Adding new page for data providers to cover indexers and block explorers * Update data-providers.mdx updated explanation text * style: fixing some markdown styles and formatting * Adding some more verbose descriptions and links --------- Co-authored-by: Elliot Voris * docs: change sitewide wording from "preview release" to "release candidate" (#639) * docs: update welcome page "preview release" > "release candidate" * docs: change "preview release" > "release candidate" on releases page * update payment challenge; add styles * add clickable object to localhost:9000 example * Update challenge-1-payment.mdx * Squashed commit of the following: commit 76c48ccd8049bca6bb11f131cefb35253dad86b2 Author: Julian Martinez Date: Wed Nov 8 10:10:12 2023 -0800 add more context --------- Signed-off-by: Satyam Zode Co-authored-by: George Co-authored-by: Elliot Voris Co-authored-by: Satyam Zode <5508956+satyamz@users.noreply.github.com> Co-authored-by: Satyam Zode Co-authored-by: Elizabeth Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Elliot Voris Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> Co-authored-by: jcx120 <91218921+jcx120@users.noreply.github.com> * format image * Squashed commit of the following: commit 308444d93d4f7133107ca499a6d255fe5ae302d4 Author: Julian Martinez Date: Wed Nov 8 14:55:06 2023 -0800 update copy commit ec062702a5a900ff890bf5cc99dfd9b6dbe1915a Author: Julian Martinez Date: Wed Nov 8 12:34:07 2023 -0800 add styles for lp commit f7bbf2e61a262aa6dd76bed2f43cd26aafce075b Author: Julian Martinez Date: Wed Nov 8 12:13:49 2023 -0800 add image styles * update copy for production deployment * caution -> tip * add commas, add soroban-cli install to payment dapp * fix broken link * Squashed commit of the following: commit 7de8a99ec32065376ea4fd2f42a14194cdda6e49 Author: Elliot Voris Date: Thu Nov 9 15:47:49 2023 -0600 docs: move data providers page out of sdks directory (#645) commit 55e0a866c6dbbe2b4dd38f38816915d59c962584 Author: Elliot Voris Date: Tue Nov 7 11:40:20 2023 -0600 docs: change sitewide wording from "preview release" to "release candidate" (#639) * docs: update welcome page "preview release" > "release candidate" * docs: change "preview release" > "release candidate" on releases page commit 2ae107280eb4bfee7192e7bdabf704c80ffab43c Author: jcx120 <91218921+jcx120@users.noreply.github.com> Date: Tue Nov 7 08:48:50 2023 -0800 Create data-providers.mdx (#629) * Create data-providers.mdx Adding new page for data providers to cover indexers and block explorers * Update data-providers.mdx updated explanation text * style: fixing some markdown styles and formatting * Adding some more verbose descriptions and links --------- Co-authored-by: Elliot Voris commit 0054011876a8273788a5bf6de16623683c9159d2 Author: Elliot Voris Date: Mon Nov 6 11:00:02 2023 -0600 docs: change preflight wording to simulate transaction (#585) * docs: change preflight wording to simulate transaction A first attempt to resolve #478, there may be more work to be done. I've used a few different phrases, depending on how the sentence structure works, perhaps taking some liberties in the process. I've also left alone the `releases` page for preview releases older than 11. It seems unnecessary to rewrite the development history too far into the past. Refs: #478 * editorial on Update interacting-with-contracts.mdx * added a couple "the"s Update state-expiration.mdx * fix wording about ledger entries to be less confusing * clearing up some other language surrounding footprints --------- Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> commit b9710ea0ea6c4652aea2019504534197195a2b3a Author: Elizabeth Date: Mon Nov 6 11:59:21 2023 -0500 Update getting-started for 20.0.0-rc.4.1 cli (#636) * Updates to setup.mdx * Add CLI for testnet configuration to Setup * Updates to hello-world.mdx Reorder some code snippets to be after the description * Small edits in storing-data.mdx * Add high_expiration_watermark argument to bump in incrementor * Move deploy-to-testnet after hello-world to allow the user to interact with their contract on testnet * Add Optimizing Builds to hello-world * Update storing-data to remove sandbox interation * Add a deploy-incrementor-to-testnet step * Update position of Create an App * Apply suggestions from code review Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> * Update create-an-app.mdx * Fix typo * Add a mv command for .soroban dir when reorganizing to a multi-contract project * Apply mdx prettier updates * Update astro port in create-an-app.mdx * Apply suggestions from code review Co-authored-by: Elliot Voris * Add .mdx to end of internal markdown links for docusaurus magic * Make sure there are new lines before and after ::: tags * Some additional edits/improvements * Mention that Freighter is available as a firefox add-on * Update the deploy-incrementor url * Apply prettier updates --------- Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Elliot Voris commit 61054dcad82dbf77c28aa1819ad08b6fa132ad70 Author: Satyam Zode <5508956+satyamz@users.noreply.github.com> Date: Fri Nov 3 20:26:27 2023 +0530 Switch Ubuntu base image to 22.04 from 20.04 (#638) Signed-off-by: Satyam Zode Co-authored-by: Satyam Zode commit d6091131996ab81e42dff57f4bccaad3f5d56b3e Author: George Date: Thu Nov 2 12:53:52 2023 -0700 Upgrade `getLedgerEntries` examples to latest pre-stable releases of the SDKs (#635) Co-authored-by: Elliot Voris commit 228e4bfed691effa58a07b664abfd78b04e6dcd4 Author: George Date: Thu Nov 2 09:18:50 2023 -0700 Update latest testnet-compatible JavaScript release versions (#634) * Update latest testnet-compatible JavaScript release versions * Add links to release notes for each version * add SEO logic --------- Signed-off-by: Satyam Zode Co-authored-by: Elliot Voris Co-authored-by: Alfonso Acosta Co-authored-by: George Co-authored-by: Bri Wylde <92327786+briwylde08@users.noreply.github.com> Co-authored-by: Sapo-Dorado Co-authored-by: Julian Martinez Co-authored-by: Julian Martinez <73849597+Julian-dev28@users.noreply.github.com> Co-authored-by: Satyam Zode <5508956+satyamz@users.noreply.github.com> Co-authored-by: Satyam Zode Co-authored-by: Elizabeth Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Elliot Voris Co-authored-by: jcx120 <91218921+jcx120@users.noreply.github.com> * Add oracle dapp challenge (#664) * add oracle dapp * update endpoint * add images * Update http-client.ts * update challenge list * add last checkpoint to oracle dapp * Update http-client.ts * update meta tag * update title (#665) * Liquidity Pool Dapp copy updates v2 (#651) * Update challenge-2-liquidity-pool.mdx * Update challenge-2-liquidity-pool.mdx * Squashed commit of the following: commit 1864016f07b4f91a0cd9656ca55c2d8c568a0d02 Author: Dmytro Kozhevin Date: Tue Dec 5 12:09:24 2023 -0500 Remove some outdated cautions from the docs, now that Soroban should be stable. (#672) commit 816e2a0e1f4e0f1e30c909b92b74b75081d3d3d7 Author: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Tue Dec 5 08:48:10 2023 -0800 Remove git init from hello world steps (#666) commit 19b10a8f4c24e495f9dc04f10c694449e4f5a333 Author: Anuxhya <36203801+achallagundla@users.noreply.github.com> Date: Tue Dec 5 11:43:19 2023 -0500 Update fees-and-metering.mdx (#654) * Update fees-and-metering.mdx Cleaning up the docs challenge 1 of the word s o r o b e r r y. * Update fees-and-metering.mdx Syntax error commit 36cc17fca85efc1471e3aadfb29b0ca1687e536b Author: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Sat Dec 2 21:01:14 2023 -0800 Update soroban-sdk to v20.0.0-rc2.2 (#667) commit c5b01369ede0b78c2626013f2b43683f69db2905 Author: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Fri Dec 1 09:41:37 2023 -0800 Remove step adding target dir to gitignore (#660) commit 214a207cce3d1d70dfa866472632910b8fe4f94c Author: Julian Martinez <73849597+Julian-dev28@users.noreply.github.com> Date: Fri Dec 1 09:07:49 2023 -0800 update title (#665) commit 4b910884cedc320dcfa5e70e2289f17280e6ee3d Author: Julian Martinez <73849597+Julian-dev28@users.noreply.github.com> Date: Fri Dec 1 08:36:59 2023 -0800 Add oracle dapp challenge (#664) * add oracle dapp * update endpoint * add images * Update http-client.ts * update challenge list * add last checkpoint to oracle dapp * Update http-client.ts * update meta tag commit 595d591e97b946a971a51f73520deb3341be6476 Author: Bri Wylde <92327786+briwylde08@users.noreply.github.com> Date: Thu Nov 30 11:53:53 2023 -0700 clearer Soroban favicon (#662) commit dc14811ded524a6f2d0026b5d1cce19ad4df7827 Author: Elliot Voris Date: Wed Nov 29 16:11:19 2023 -0600 synchronize api example tabs across pages (#661) commit 4f1198cfde2df402c0e74f1145828423c39e29ea Author: Elliot Voris Date: Wed Nov 29 13:01:38 2023 -0600 Revert "Update getting started tutorial to use an Astro template (#642)" (#658) This reverts commit 512b1f46506b7edc788bc6e93a0e1f6d101d22ae. commit 81cac0d3524a1a80ce2fadd88e93aa40ffde791b Author: Elliot Voris Date: Mon Nov 27 16:29:57 2023 -0600 build: a couple small fixes to get the build to work (#657) * build: a couple small fixes to get the build to work * style: changing the way a couple codefences work commit 512b1f46506b7edc788bc6e93a0e1f6d101d22ae Author: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Mon Nov 27 13:00:32 2023 -0500 Update getting started tutorial to use an Astro template (#642) * Update create-an-app.mdx to use an Astro template * Apply prettier formatting * Add some additional gotchas to the troubleshooting section * Reworks hello-world.mdx using the Astro template * Rework deploy-to-testnet.mdx to interact with the contarct via the frontend * Update storing-data.mdx * Update deploy-incrementor.mdx * Change create-an-app.mdx to wrapping-up.mdx * Apply prettier updates * Apply suggestions from code review Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> * Address PR feedback - move "???" bummer to "taking it further" - add command expansion note to deploy-to-testnet - remove mention of incrementor in deploy-to-testnet * Small cleanup * Address PR feedback * Remove release-with-logs section * Update code snippets to match updated tutorial code * Apply suggestions from code review Co-authored-by: Elliot Voris * Address PR feedback * Check in prettier updates * update getting started links on the landing page * redirect the old create an app page to the hello world page --------- Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Elliot Voris Co-authored-by: Elliot Voris commit f04a865b519d05495932fbaaf5bd075c8438c4ef Author: jcx120 <91218921+jcx120@users.noreply.github.com> Date: Mon Nov 27 09:23:53 2023 -0800 Update rpc-list.mdx (#653) * Update rpc-list.mdx Added Gateway FM (and removed BlockEden since they are in the Data providers list) * style: linting mdx file --------- Co-authored-by: Elliot Voris commit a9e0d34f61080bf456219e68c018432370bf3bf0 Author: Elliot Voris Date: Mon Nov 27 11:22:58 2023 -0600 undo some changes from the docs scavenger hunt (#656) * Revert "Squashed commit of the following:" This reverts commit ada22fee5da3073cc3e4daee60a2c4f3ffc3e378. * Update challenge-2-liquidity-pool.mdx * add context to price setting * Add v20.0.0 (#668) * add P12 Release * formatting * Update releases.mdx * formatting * update horizon,stellar-base,cli * add stellar sdk + base, soroban-client, Quickstart, changelog * remove http from quickstart endpoint * v20.0.0-rc2 -> v20.0.0 * Preview 12 -> Stable v20.0.0 * update CLI, RPC, Core Versions * update stellar cor version * Content updates to go along with the preview 12 version updates (#682) * update the token interface to reflect the Rust SDK * feat: get Getting Started ready for v20.0.x I don't know what the exact version of the CLI will be out by next Monday. In the source here, I've guessed that maybe it will be `20.0.3`. * update `openrpc.json` file to reflect new updates * Update static/openrpc.json Co-authored-by: Alfonso Acosta --------- Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Molly Karcher Co-authored-by: Alfonso Acosta * Update token-interface.mdx * nit:formatting --------- Co-authored-by: Elliot Voris Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: Molly Karcher Co-authored-by: Alfonso Acosta * Update JavaScript SDK references to use the `@stellar/stellar-sdk` package (#687) * Upgrade js sdk dependencies to latest * Track down references to soroban-client and move them * Ran linter * Pre mainnet restructure (#644) * docs: rename "fundamentals and concepts" to "soroban internals" * docs: remove old "under the hood" section * docs: add tags to the various tutorials * docs: move tutorials into one main directory * fix broken links to old pages * docs: renaming soroban internals in category file * docs: remove old "command line reference" category * docs: add tokens directory, rearrange sidebars * docs: rearrange migration from evm guide * docs: remove unused reference/interfaces category * docs: move "reference" section to "resources" * docs: move "releases" page up a level * docs: move FAQ page up one level * docs: move dev tools into resources directory * docs: move testnet.mdx to networks.mdx * docs: rearrange items in resources directory * docs: collapse various SDK pages into two pages * docs: move data-providers up one level * docs: remove some empty categories, move the tutorial template * docs: add a new getting-started page, reorganize that section * docs: change sidebar position integers in soroban-internals * docs: shuffling contract interaction around in soroban-internals * style: fixing a couple small markdown nits in dapps directory * docs: fix a broken link * feat: start to the "guides" page(s) * style(lint): fixing a small linting error * remove guides placeholder * fixing broken links * work on guides listing. might revert this * formatting mdx * docs: fixing a broken link to tutorials * style: crack at making the tutorials filterable and hidden in the sidebar * customizing some components for the `/guides` pages and layouts * rename index page for guides to README * remove commented configuration option * remove some console logging in components * change name of index page in guides sidebar * remove commented sidebar generation code * remove comments and add description to sidebar generator * rename sidebar generator file * more work on how the 'guides' pages might look. * docs: adjusting sidebar positions of new getting started pages * fix some broken links in the getting-started section * prefer "README.mdx" files where possible * some more guides placeholder stubs * docs(guides): More placeholder stubs for guides and categories * feat: don't display "guides in category" page on `/guides` * build: check/fix MDX formatting in more directories than just docs * style: add a larger margin before more category guides * style: more selectively increase that top margin * docs(guides): a quick stab at a "publishing" events guide * docs(guides): first stab at the "publish events" guide * docs: updating tutorial descriptions * fix a few broken links * docs: add note about a tuple with one element * style: couple link changes and reformats * move fuzzing tutorial back * change to README file in contract interactions category * docs(guides): give a better title for the wasm metadata guide * fixing some broken markdown links * fix (another!) broken link * move the guides back into the main layout of the docs directory * add placeholder for testnet reset automation stuff * some initial content for some guides * markdown formatting and fixing broken links * moving "resources" back to "reference" to fit the definition better More like "technical reference" rather than a "reference encyclopedia" * change some styles of the tutorial list * removing most category pages * some more first-drafts of guides * fixing some category links * include a period at the end of each tutorial description * simplify tutorial search box placeholder text * better description for the tutorials page * making the pre-commit script executable * flesh out some of the state archival guides * first effort for some rpc ledger key guides * fix some broken links * add a guide on ingesting events into a db * first effort at storage type guides * Some more stubs, marking drafts, and a couple additions * make not a draft to fix a broken link * guides category descriptions in README.mdx files * marking incomplete chain migration docs as drafts * change some tutorial difficulty levels * fine-tune the tutorials component styling a bit * improving some guides organization * fix linting errors and broken links * include wrap instructions for native lumens * fix broken link in contract metadata guide * final changes to existing guides * moving developer tools into the main sidebar * create real redirects instead of just notes * fix broken links in a dapps challenge page * fix a redirect syntax error * fix a tutorials redirect * update SDC leaderboard (#712) * update leaderboard style * update font color * Refactor wrap/deploy of SAC (#718) Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Co-authored-by: Elliot Voris * Refactor deprecated calls to `soroban config` (#731) * Update the community RPC list (#744) * Update RPC list with network info * Add run your own RPC * Fix urls for soroban cli * Fix path to cli * Fix relative link * Add formatted mdx --------- Co-authored-by: Jane Wang * move dapps challenge directory into place * install new packages for dapps-challenge * delete mispelled constants file * fix relative imports * move challenges to pages directory, and use README files * correct markdown link * fix broken links in dapps challenge docs * fix link routes in dapps challenge dashboard * add /src/pages dir to markdown formatting checks; format markdown * fix relative imports on dapps challenge dashboard * move the dashboard location into the dapps URLs * add dapps challenge dashboard to the sidebar using a customized sidebar generator for this. * remove soroban events provider component from challenge forms * fix dashboard links on dapp challenge pages * change some frontmater and remove head items from dapps challenges * move the docker guide from dapps challenge to guides * move the rest of the dapps challenge guides * update and move around the dapps guides * lint and format markdown * redirect /dapps requests, just in case * remove link to guide content that no longer exists * add link to dapps challenge in footer * add link to freighter guide on dapps challenge intro page * Update Dapps Challenge name in sidebar --------- Signed-off-by: Satyam Zode Co-authored-by: Julian Martinez <73849597+Julian-dev28@users.noreply.github.com> Co-authored-by: Bri <92327786+briwylde08@users.noreply.github.com> Co-authored-by: Iryna Telesheva Co-authored-by: Iryna Telesheva <131377033+itelesheva@users.noreply.github.com> Co-authored-by: Alejandro Criado-Pérez Co-authored-by: illiassmalashchuk <145133100+illiassmalashchuk@users.noreply.github.com> Co-authored-by: Julian Martinez Co-authored-by: MazurakIhor <131388095+MazurakIhor@users.noreply.github.com> Co-authored-by: nmadadair <109177652+nmadadair@users.noreply.github.com> Co-authored-by: Alfonso Acosta Co-authored-by: George Co-authored-by: Sapo-Dorado Co-authored-by: Satyam Zode <5508956+satyamz@users.noreply.github.com> Co-authored-by: Satyam Zode Co-authored-by: Elizabeth Co-authored-by: Chad Ostrowski <221614+chadoh@users.noreply.github.com> Co-authored-by: jcx120 <91218921+jcx120@users.noreply.github.com> Co-authored-by: Molly Karcher Co-authored-by: Pamphile Roy Co-authored-by: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Co-authored-by: Jane Wang Co-authored-by: Jane Wang --- docs/learn/interactive/dapps/README.mdx | 10 + docs/learn/interactive/dapps/introduction.mdx | 31 + .../interactive/dapps/scaffold-soroban.mdx | 82 +++ docs/smart-contracts/guides/dapps/README.mdx | 5 + docs/smart-contracts/guides/dapps/docker.mdx | 136 +++++ .../guides/dapps/initialization.mdx | 214 +++++++ docs/smart-contracts/guides/dapps/react.mdx | 140 +++++ .../freighter/integrate-freighter-react.mdx | 41 ++ docusaurus.config.js | 5 + nginx/includes/redirects.conf | 1 + package.json | 15 +- src/components/atoms/ProviderExample.tsx | 22 + src/components/atoms/SortableRow/index.js | 13 + src/components/atoms/UI/carousel/index.tsx | 47 ++ .../atoms/UI/carousel/style.module.css | 12 + src/components/atoms/UI/loading/index.tsx | 11 + src/components/atoms/UI/switcher/index.tsx | 34 ++ .../atoms/UI/switcher/style.module.css | 51 ++ .../atoms/challenge-card/challenge-icons.tsx | 55 ++ src/components/atoms/challenge-card/index.tsx | 151 +++++ .../atoms/challenge-card/style.module.css | 141 +++++ .../atoms/challenge-contract-form/index.tsx | 124 ++++ .../challenge-contract-form/style.module.css | 94 +++ src/components/atoms/challenge-form/index.tsx | 147 +++++ .../atoms/challenge-form/style.module.css | 94 +++ .../atoms/challenges-list/index.tsx | 96 ++++ .../atoms/challenges-list/style.module.css | 54 ++ .../atoms/complete-step-button/index.tsx | 236 ++++++++ .../complete-step-button/styles.module.css | 39 ++ src/components/atoms/confirm-modal/index.tsx | 28 + .../atoms/confirm-modal/style.module.css | 63 ++ .../atoms/dashboard-header/index.tsx | 104 ++++ .../atoms/dashboard-header/style.module.css | 114 ++++ src/components/atoms/index.tsx | 25 + .../atoms/start-challenge-button/index.tsx | 91 +++ .../start-challenge-button/style.module.css | 37 ++ src/components/atoms/style.module.css | 21 + .../molecules/SortableTable/index.js | 33 ++ .../molecules/SortableTable/style.module.css | 4 + .../molecules/leaderboard/index.tsx | 178 ++++++ .../molecules/leaderboard/style.module.css | 125 ++++ src/constants.ts | 6 + src/hooks/useAuth.tsx | 76 +++ src/interfaces/challenge.ts | 52 ++ .../challenges/challenge-0-crowdfund.mdx | 355 ++++++++++++ .../dapps/challenges/challenge-1-payment.mdx | 457 +++++++++++++++ .../challenges/challenge-2-liquidity-pool.mdx | 459 +++++++++++++++ .../dapps/challenges/challenge-3-oracle.mdx | 538 ++++++++++++++++++ .../interactive/dapps/challenges/styles.css | 31 + .../interactive/dapps/dashboard/index.tsx | 183 ++++++ .../dapps/dashboard/style.module.css | 45 ++ src/pages/index.mdx | 8 +- src/services/challenges.ts | 34 ++ src/services/http-client.ts | 66 +++ src/services/leaderboard.ts | 31 + src/sidebar-generator.js | 30 + src/store/UserChallengesContextProvider.tsx | 108 ++++ src/store/user-challenges-context.ts | 20 + src/theme/Root.tsx | 27 + src/theme/style.module.css | 3 + src/utils/get-active-challenge.ts | 8 + src/utils/get-contract-balance.ts | 53 ++ static/icons/icon-avatar-1.svg | 9 + static/icons/icon-avatar-10.svg | 15 + static/icons/icon-avatar-2.svg | 9 + static/icons/icon-avatar-3.svg | 7 + static/icons/icon-avatar-4.svg | 15 + static/icons/icon-avatar-5.svg | 9 + static/icons/icon-avatar-6.svg | 9 + static/icons/icon-avatar-7.svg | 21 + static/icons/icon-avatar-8.svg | 27 + static/icons/icon-avatar-9.svg | 15 + static/icons/icon-copy.svg | 5 + static/icons/icon-ranking.svg | 4 + static/icons/icon-star-yellow.svg | 6 + static/icons/icon-star.svg | 5 + static/icons/smiley-face-1.svg | 12 + static/icons/smiley-face-2.svg | 8 + yarn.lock | 280 ++++++++- 79 files changed, 5959 insertions(+), 11 deletions(-) create mode 100644 docs/learn/interactive/dapps/README.mdx create mode 100644 docs/learn/interactive/dapps/introduction.mdx create mode 100644 docs/learn/interactive/dapps/scaffold-soroban.mdx create mode 100644 docs/smart-contracts/guides/dapps/README.mdx create mode 100644 docs/smart-contracts/guides/dapps/docker.mdx create mode 100644 docs/smart-contracts/guides/dapps/initialization.mdx create mode 100644 docs/smart-contracts/guides/dapps/react.mdx create mode 100644 docs/smart-contracts/guides/freighter/integrate-freighter-react.mdx create mode 100644 src/components/atoms/ProviderExample.tsx create mode 100644 src/components/atoms/SortableRow/index.js create mode 100644 src/components/atoms/UI/carousel/index.tsx create mode 100644 src/components/atoms/UI/carousel/style.module.css create mode 100644 src/components/atoms/UI/loading/index.tsx create mode 100644 src/components/atoms/UI/switcher/index.tsx create mode 100644 src/components/atoms/UI/switcher/style.module.css create mode 100644 src/components/atoms/challenge-card/challenge-icons.tsx create mode 100644 src/components/atoms/challenge-card/index.tsx create mode 100644 src/components/atoms/challenge-card/style.module.css create mode 100644 src/components/atoms/challenge-contract-form/index.tsx create mode 100644 src/components/atoms/challenge-contract-form/style.module.css create mode 100644 src/components/atoms/challenge-form/index.tsx create mode 100644 src/components/atoms/challenge-form/style.module.css create mode 100644 src/components/atoms/challenges-list/index.tsx create mode 100644 src/components/atoms/challenges-list/style.module.css create mode 100644 src/components/atoms/complete-step-button/index.tsx create mode 100644 src/components/atoms/complete-step-button/styles.module.css create mode 100644 src/components/atoms/confirm-modal/index.tsx create mode 100644 src/components/atoms/confirm-modal/style.module.css create mode 100644 src/components/atoms/dashboard-header/index.tsx create mode 100644 src/components/atoms/dashboard-header/style.module.css create mode 100644 src/components/atoms/index.tsx create mode 100644 src/components/atoms/start-challenge-button/index.tsx create mode 100644 src/components/atoms/start-challenge-button/style.module.css create mode 100644 src/components/atoms/style.module.css create mode 100644 src/components/molecules/SortableTable/index.js create mode 100644 src/components/molecules/SortableTable/style.module.css create mode 100644 src/components/molecules/leaderboard/index.tsx create mode 100644 src/components/molecules/leaderboard/style.module.css create mode 100644 src/hooks/useAuth.tsx create mode 100644 src/interfaces/challenge.ts create mode 100644 src/pages/docs/learn/interactive/dapps/challenges/challenge-0-crowdfund.mdx create mode 100644 src/pages/docs/learn/interactive/dapps/challenges/challenge-1-payment.mdx create mode 100644 src/pages/docs/learn/interactive/dapps/challenges/challenge-2-liquidity-pool.mdx create mode 100644 src/pages/docs/learn/interactive/dapps/challenges/challenge-3-oracle.mdx create mode 100644 src/pages/docs/learn/interactive/dapps/challenges/styles.css create mode 100644 src/pages/docs/learn/interactive/dapps/dashboard/index.tsx create mode 100644 src/pages/docs/learn/interactive/dapps/dashboard/style.module.css create mode 100644 src/services/challenges.ts create mode 100644 src/services/http-client.ts create mode 100644 src/services/leaderboard.ts create mode 100644 src/sidebar-generator.js create mode 100644 src/store/UserChallengesContextProvider.tsx create mode 100644 src/store/user-challenges-context.ts create mode 100644 src/theme/Root.tsx create mode 100644 src/theme/style.module.css create mode 100644 src/utils/get-active-challenge.ts create mode 100644 src/utils/get-contract-balance.ts create mode 100644 static/icons/icon-avatar-1.svg create mode 100644 static/icons/icon-avatar-10.svg create mode 100644 static/icons/icon-avatar-2.svg create mode 100644 static/icons/icon-avatar-3.svg create mode 100644 static/icons/icon-avatar-4.svg create mode 100644 static/icons/icon-avatar-5.svg create mode 100644 static/icons/icon-avatar-6.svg create mode 100644 static/icons/icon-avatar-7.svg create mode 100644 static/icons/icon-avatar-8.svg create mode 100644 static/icons/icon-avatar-9.svg create mode 100644 static/icons/icon-copy.svg create mode 100644 static/icons/icon-ranking.svg create mode 100644 static/icons/icon-star-yellow.svg create mode 100644 static/icons/icon-star.svg create mode 100644 static/icons/smiley-face-1.svg create mode 100644 static/icons/smiley-face-2.svg diff --git a/docs/learn/interactive/dapps/README.mdx b/docs/learn/interactive/dapps/README.mdx new file mode 100644 index 000000000..7c749c345 --- /dev/null +++ b/docs/learn/interactive/dapps/README.mdx @@ -0,0 +1,10 @@ +--- +title: Dapps Challenge +sidebar_position: 60 +--- + +import DocCardList from "@theme/DocCardList"; + +The Soroban Dapps Challenge is a dynamic course designed for developers eager to explore the potential of building decentralized applications (Dapps) on the Soroban smart contracts platform. + + diff --git a/docs/learn/interactive/dapps/introduction.mdx b/docs/learn/interactive/dapps/introduction.mdx new file mode 100644 index 000000000..14449186b --- /dev/null +++ b/docs/learn/interactive/dapps/introduction.mdx @@ -0,0 +1,31 @@ +--- +sidebar_position: 10 +title: Dapps on Soroban +sidebar_label: Introduction +--- + +Decentralized applications, or "dapps," mark a significant shift in our digital interactions, running on a blockchain or peer-to-peer network instead of centralized servers. This shift enhances transparency, security, and user control, as data and smart contracts are stored on a public ledger, open for audit by anyone. Soroban facilitates building and deploying dapps on the Stellar blockchain, offering tools and frameworks that simplify the development process for even those with minimal coding experience. The Soroban Dapps Challenge highlights this, enabling you to create a variety of dapp use cases on a single page in just 20 minutes, with minimal coding, guiding you from smart contract deployment to user interaction through a web frontend. + +The Soroban Dapps Challenge is a dynamic course designed for developers eager to explore the potential of building decentralized applications (Dapps) on the Soroban smart contracts platform. This course is part challenge, part educational journey that sets the stage for practical and creative blockchain development. + +While the course specifically focuses on the Soroban platform, the knowledge you gain can be applied to other transaction processors such as different blockchains, L2s, and permissioned ledgers. The skills you acquire here are meant to be transferable and versatile. + +Through The Soroban Dapps Challenge, you'll have hands-on experience using Soroban's initial versions of the smart contracts environment, a Rust SDK, a CLI, and an RPC server. You'll learn how to write, test, and deploy smart contracts, and you'll get to see your code in action on Futurenet. + +## What This Course Entails + +We've designed this course as a learning adventure. It's a way for developers from the Stellar ecosystem and other blockchain communities to experiment, provide feedback, and contribute to the Soroban development process. + +As you progress through The Soroban Dapps Challenge, anticipate your code to break and updates to shift things. We invite you to experiment and build but also remind you that changes are afoot as we prepare for the production release. + +## Getting Started + +To get started, simply head over to the [Dashboard](/docs/learn/interactive/dapps/dashboard), [connect your wallet](../../../smart-contracts/guides/freighter/connect-testnet.mdx), and see what challenges await you! + +## Giving Your Feedback + +We value your input. Feel free to file issues in the Soroban repos or raise them in the soroban channel in the Stellar Developer [Discord](https://discord.gg/3qrBhbwE). + +Join us in this exciting course and start building for the future of blockchain technology. + +> Disclaimer: The Soroban Dapps Challenge is for educational purposes only and is designed to teach developers how to write code using Soroban by experimenting in a Sandbox environment. None of the materials provided as part of the Soroban Dapps Challenge shall be construed as financial, legal, or investment advice. Any developers that wish to subsequently launch any Soroban Dapps live on mainnet acknowledge that they do so independently, outside of the Soroban Dapps Challenge program. All such developers should ensure they have considered any applicable legal and compliance obligations in force in their jurisdiction. diff --git a/docs/learn/interactive/dapps/scaffold-soroban.mdx b/docs/learn/interactive/dapps/scaffold-soroban.mdx new file mode 100644 index 000000000..1d210b6ff --- /dev/null +++ b/docs/learn/interactive/dapps/scaffold-soroban.mdx @@ -0,0 +1,82 @@ +--- +sidebar_position: 30 +title: Scaffold Soroban +description: Dive into the simple implementations of Soroban dapps to understand and learn the Soroban ecosystem. +--- + +## Demonstrative Soroban Dapps + +import ReactPlayer from "react-player"; + +The **Soroban** team has invested considerable effort into contract implementation. They’ve built CLI’s and libraries, enabling contract builders to create and invoke using Rust, which forms the “backend” of Soroban. However, the “frontend”, which involves JS client libraries, required attention. + +## Background + +During the Soroban Hackathon, we recognized that while the frontend libraries were functional, their user experience was far from ideal. Furthermore, the lack of clear examples made it harder for dapp creators to design and understand Soroban's UX. + +To tackle this, the **Wallet Engineering** team, in collaboration with the Soroban team, has decided to launch “Scaffold Soroban”, a collection of demo dapps. These dapps demonstrate basic functionalities in a structured, easy-to-follow manner, primarily focusing on how to construct/deploy Soroban contract invocations. + +## Dapp Demos + +For easy accessibility, we have compiled the dapps into a [single repository](https://github.com/stellar/scaffold-soroban), which contains the following dapps: + +### [1. Payment Dapp](https://github.com/stellar/soroban-react-payment) + +This dapp mirrors the Soroban payment flow in Freighter by using the wallet’s Soroban token balances to invoke the `xfer` method on the token’s contract. + +See the [demo](https://github.com/stellar/soroban-react-payment/releases/tag/v1.0.0) + + + +### [2. Mint Token Dapp](https://github.com/stellar/soroban-react-mint-token) + +This dapp allows a token admin to mint tokens by using the admin account to invoke the `mint` method on the token’s contract. + +See the [demo](https://github.com/stellar/soroban-react-mint-token/releases/tag/v1.0.0) + + + +### [3. Atomic Swap Dapp](https://github.com/stellar/soroban-react-atomic-swap) + +This dapp demonstrates a simplified swap between two tokens by using the wallet’s Soroban token balances to invoke the `swap` method on the atomic swap contract. + +See the [demo](https://github.com/stellar/soroban-react-atomic-swap/releases/tag/v1.0.0) + + + +## How To Explore the Dapps on Scaffold-Soroban + +To begin using these examples, navigate to [Scaffold-Soroban](https://scaffold-soroban.stellar.org/) and choose the name of the dapp you're interested in from the "select demo" dropdown: + +- **Payment**: Choose "payment". +- **Token Minter**: Select "mint-token". +- **Atomic Swap**: Opt for "atomic-swap". + +Dive in and discover the power of Soroban! + +## Functionality Behind the Dapps + +With the introduction of these dapps, let's delve deeper into some of their [standout features](https://github.com/stellar/soroban-react-atomic-swap/blob/main/src/helpers/soroban.ts) that showcase the power and innovation behind the dapps: + +Functionality behind the dapps is extensive and diverse, leveraging the `@stellar/stellar-sdk` library to integrate with the Soroban RPC and facilitating direct communication with Soroban using a JSON RPC interface on the Stellar network. They are equipped to communicate across different network setups, as they incorporate the `RPC_URLS` and `getServer` functionalities, providing adaptability that is crucial for various development and deployment scenarios. Users can retrieve user-friendly token information without engaging with complex blockchain operations, by utilizing functions like `getTokenSymbol`, `getTokenName`, and `getTokenDecimals`. + +A significant feature is the `simulateTx` function, which allows users to preview the outcome of a transaction before actually executing it. `simulateTx` allows users to submit a trial contract invocation by first running a simulation of the contract invocation as defined on the incoming transaction. The results are then applied to a new copy of the transaction, which is returned with the ledger footprint and authorization set, making it ready for signing and sending. The returned transaction will also have an updated fee, the sum of the fee set on the incoming transaction with the contract resource fees estimated from the simulation. It is advisable to check the fee on the returned transaction and validate or take appropriate measures for interaction with the user to confirm it is acceptable. + +Other utilities such as `accountToScVal` and `numberToI128` are also provided to simplify transaction creation by converting user-friendly inputs into the formats expected by the Soroban RPC on the Stellar network. Furthermore, the dapps are built with premade helper functions such as the `makePayment` function which facilitates streamlined "transfer" operations and also includes memos for supplementary transaction-related information. + +The code referenced showcases various functionalities including sending transactions, retrieving token information, simulating transactions, building swaps, and authorizing contract calls, all of which are ready to be cloned, customized, and expanded upon to suit your unique needs and ideas. + +## Conclusion + +We hope these dapps will help you understand the Soroban ecosystem better and inspire you to build your own dapps using tools like [stellar-sdk](https://www.npmjs.com/package/@stellar/stellar-sdk) and [freighter-api](https://www.npmjs.com/package/@stellar/freighter-api). We look forward to seeing what you create! + +For any queries or discussions, don't hesitate to join us on [Discord!](https://discord.com/channels/897514728459468821/1037073682599780494). diff --git a/docs/smart-contracts/guides/dapps/README.mdx b/docs/smart-contracts/guides/dapps/README.mdx new file mode 100644 index 000000000..d19dc3fbf --- /dev/null +++ b/docs/smart-contracts/guides/dapps/README.mdx @@ -0,0 +1,5 @@ +--- +title: Dapp Development +--- + +We've written some helpful guides on some of the most useful tools available to you, the dapp developer. diff --git a/docs/smart-contracts/guides/dapps/docker.mdx b/docs/smart-contracts/guides/dapps/docker.mdx new file mode 100644 index 000000000..e2bd465ed --- /dev/null +++ b/docs/smart-contracts/guides/dapps/docker.mdx @@ -0,0 +1,136 @@ +--- +title: Use Docker to build and run dapps +--- + +## What is Docker? + +Welcome to the world of [Docker](https://www.docker.com/), an essential tool for software development. Docker packages software into units known as containers, ensuring consistency, isolation, portability, and scalability. + +Docker is particularly useful in dapp development. It helps manage microservices, maintain consistent environments throughout development stages, and simulate a decentralized network during testing. + +Understanding Docker begins with understanding Docker images and containers. A Docker image, created from a Dockerfile, is a package that contains everything needed to run the software. A Docker container is a running instance of this image. + +## Building and Running a Docker Image + +You can create a Docker image using the docker build command with a Dockerfile. Once the image is created, you can run a Docker container using the docker run command. + +In the context of the example Soroban dapps, understanding how to build Docker images is crucial. The Docker images serve as the basis for our container, which provides the environment for our dapp to run. + +Here's an example from our example + +To illustrate the process, let's take an example from our [example crowdfund dapp]. In order to build the Docker image, you utilize a command that is encapsulated within our Makefile: + +```bash +make build-docker +``` + +This command simplifies the Docker build process and ensures it's consistently executed each time. When you run `make build-docker`, Docker executes the following instructions: + +```bash +docker build . \ + --tag soroban-preview:11 \ + --force-rm \ + --rm +``` + +### Makefile Overview + +```bash +docker build . +``` + +Instructs Docker to build an image using the Dockerfile in the current directory (denoted by the "."). + +```bash +--tag soroban-preview:11 +``` + +Gives a name and tag to our image, in this case, soroban-preview with the tag 9. + +```bash +--force-rm +``` + +Ensures Docker removes any intermediate containers after the build process completes. This keeps our environment clean. + +```bash +--rm +``` + +Guarantees the removal of the intermediate container, even if the build fails. By using `make build-docker`, you're harnessing the power of Docker to create a consistent, reliable environment for our dapp. + +## Container Deployment + +You can streamline the deployment process by using a script to run the Docker container. The following script is a wrapper for the [`stellar/quickstart` Docker image], which provides a quick way to run a Stellar network. You can find an example of the `quickstart.sh` script located in the root directory of the [example crowdfund dapp]. + +```bash title="quickstart.sh" +#!/bin/bash + +set -e + +case "$1" in +standalone) + echo "Using standalone network" + ARGS="--standalone" + ;; +futurenet) + echo "Using Futurenet network" + ARGS="--futurenet" + ;; +*) + echo "Usage: $0 standalone|futurenet" + exit 1 + ;; +esac + +shift + +# Run the soroban-preview container +# Remember to do: +# make build-docker + +echo "Creating docker soroban network" +(docker network inspect soroban-network -f '{{.Id}}' 2>/dev/null) \ + || docker network create soroban-network + +echo "Searching for a previous soroban-preview docker container" +containerID=$(docker ps --filter="name=soroban-preview" --all --quiet) +if [[ ${containerID} ]]; then + echo "Start removing soroban-preview container." + docker rm --force soroban-preview + echo "Finished removing soroban-preview container." +else + echo "No previous soroban-preview container was found" +fi + +currentDir=$(pwd) +docker run -dti \ + --volume ${currentDir}:/workspace \ + --name soroban-preview \ + -p 8001:8000 \ + --ipc=host \ + --network soroban-network \ + soroban-preview:11 + +# Run the stellar quickstart image + +docker run --rm -ti \ + --name stellar \ + --network soroban-network \ + -p 8000:8000 \ + stellar/quickstart:testing \ + $ARGS \ + --enable-soroban-rpc \ + "$@" # Pass through args from the CLI +``` + +The `quickstart.sh` script sets up the Docker environment for running the dapp. It allows you to choose between a standalone network or the Futurenet network. The script performs the following steps: + +- Determines the network based on the provided argument (`standalone` or `futurenet`). +- Creates the Docker network named `soroban-network` if it doesn't exist. +- Removes any existing `soroban-preview` Docker container. +- Runs the `soroban-preview` container, which provides the Soroban Preview environment for development. +- Runs the `stellar/quickstart` Docker image, which sets up the Stellar network using the chosen network type and enables Soroban RPC. + +[`stellar/quickstart` Docker image]: https://hub.docker.com/r/stellar/quickstart +[example crowdfund dapp]: https://github.com/stellar/soroban-example-dapp diff --git a/docs/smart-contracts/guides/dapps/initialization.mdx b/docs/smart-contracts/guides/dapps/initialization.mdx new file mode 100644 index 000000000..c3a90cb89 --- /dev/null +++ b/docs/smart-contracts/guides/dapps/initialization.mdx @@ -0,0 +1,214 @@ +--- +title: Initialize a dapp using scripts +--- + +When setting up an example Soroban Dapp, correct initialization is crucial. This process entails several steps, including deploying Docker, cloning and deploying smart contracts, and invoking functions to configure them. In this comprehensive guide, you will walk you through the necessary steps to successfully build and deploy these smart contracts, ensuring a seamless setup for your Soroban Dapp. + +## Building and Deploying the Soroban Token Smart Contract + +In dapps like the [Example Payment Dapp](https://github.com/stellar/soroban-react-payment/), the [Soroban Token smart contracts](https://github.com/stellar/soroban-examples/tree/main/token) are used to represent the tokenized asset that users can send and receive. Here is an example of how to build and deploy the Soroban Token smart contracts: + +Start by cloning the Soroban examples repository: + +```shell +git clone https://github.com/stellar/soroban-examples.git +``` + +Then, navigate to the `token` directory: + +```shell +cd soroban-examples/token +``` + +At this point you can build the smart contract: + +```shell +make +``` + +This action will compile the smart contracts and place them in the `token/target/wasm32-unknown-unknown/release` directory. + +After building, you're ready to deploy the smart contracts to Futurenet. To do this, open a terminal in the `soroban-examples/token` directory and execute the following: + +```shell +soroban contract deploy \ + --wasm target/wasm32-unknown-unknown/release/soroban_token_contract.wasm \ + --source \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' +``` + +This command deploys the smart contracts to Futurenet using the `soroban contract deploy` function. + +## Initializing a Token Contract + +With the contracts deployed, it's time to initialize the token contract: + +```shell +soroban contract invoke \ + --id \ + --source-account \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' \ + -- initialize \ + --admin \ + --decimal 7 \ + --name '44656d6f20546f6b656e' \ + --symbol '"4454"' +``` + +This command requires certain inputs: + +- Administrator Account: This is the public key of the administrator account. The administrator has control and authority over the token contract, enabling management of various contract functionalities. Learn more about the administrator's role from the Soroban Token Interface. + +- Decimal Precision: The decimal precision value of 7 specifies that the token can support transactions up to 7 decimal places. This precision level enables flexibility when transferring token amounts. + +- Token Name: The token's name, represented as a hex-encoded string. In this case, '44656d6f20546f6b656e' corresponds to "Demo Token". + +- Token Symbol: This is the token's symbol, also represented as a hex string. '4454' translates to the symbol "DT". + +## Minting Tokens + +Lastly, you need to mint some tokens to the sender's account: + +```shell +soroban contract invoke \ + --id \ + --source-account \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' \ + -- mint \ + --to \ + --amount 1000000000 +``` + +This command will mint 100 tokens to the designated user's account. + +By following these steps, you ensure that the Soroban token smart contracts are correctly deployed and initialized, setting the stage for the Dapp to effectively interact with the token. + +For a deeper dive into Soroban CLI commands, check out the [Soroban CLI repo](https://github.com/stellar/soroban-cli/tree/main/cmd/soroban-cli/src/commands). + +## Automating Initialization with Scripts + +To streamline the initialization process, you can use a script. This script should automate various tasks such as setting up the network, wrapping Stellar assets, generating token-admin identities, funding the token-admin account, building and deploying the contracts, and initializing them with necessary parameters. + +Here's an example initializer script: + +```bash title="initialize.sh" +#!/bin/bash + +set -e + +NETWORK="$1" + +# If soroban-cli is called inside the soroban-preview docker container, +# it can call the stellar standalone container just using its name "stellar" +if [[ "$IS_USING_DOCKER" == "true" ]]; then + SOROBAN_RPC_HOST="http://stellar:8000" +else + SOROBAN_RPC_HOST="http://localhost:8000" +fi + +case "$1" in +standalone) + echo "Using standalone network" + SOROBAN_NETWORK_PASSPHRASE="Standalone Network ; February 2017" + FRIENDBOT_URL="$SOROBAN_RPC_HOST/friendbot" + SOROBAN_RPC_URL="$SOROBAN_RPC_HOST/soroban/rpc" + ;; +futurenet) + echo "Using Futurenet network" + SOROBAN_NETWORK_PASSPHRASE="Test SDF Future Network ; October 2022" + FRIENDBOT_URL="https://friendbot-futurenet.stellar.org/" + SOROBAN_RPC_URL="https://rpc-futurenet.stellar.org" + ;; +testnet) + echo "Using Testnet network" + SOROBAN_NETWORK_PASSPHRASE="Test SDF Network ; September 2015" + FRIENDBOT_URL="https://friendbot.stellar.org/" + SOROBAN_RPC_URL="https://soroban-testnet.stellar.org" + ;; +*) + echo "Usage: $0 standalone|futurenet|testnet" + exit 1 + ;; +esac + + +echo Add the $NETWORK network to cli client +soroban network add \ + --rpc-url "$SOROBAN_RPC_URL" \ + --network-passphrase "$SOROBAN_NETWORK_PASSPHRASE" "$NETWORK" + +if !(soroban keys ls | grep token-admin 2>&1 >/dev/null); then + echo Create the token-admin identity + soroban keys generate token-admin +fi +TOKEN_ADMIN_SECRET="$(soroban keys show token-admin)" +TOKEN_ADMIN_ADDRESS="$(soroban keys address token-admin)" + +# TODO: Remove this once we can use `soroban keys` from webpack. +mkdir -p .soroban-example-dapp +echo "$TOKEN_ADMIN_SECRET" > .soroban-example-dapp/token_admin_secret +echo "$TOKEN_ADMIN_ADDRESS" > .soroban-example-dapp/token_admin_address + +# This will fail if the account already exists, but it'll still be fine. +echo Fund token-admin account from friendbot +curl --silent -X POST "$FRIENDBOT_URL?addr=$TOKEN_ADMIN_ADDRESS" >/dev/null + +ARGS="--network $NETWORK --source token-admin" + +echo Deploy the Stellar asset contract +TOKEN_ID=$(soroban contract asset deploy $ARGS --asset "EXT:$TOKEN_ADMIN_ADDRESS") +echo "Token deployed successfully with TOKEN_ID: $TOKEN_ID" + +# TODO - remove this workaround when +# https://github.com/stellar/soroban-tools/issues/661 is resolved. +TOKEN_ADDRESS="$(node ./address_workaround.js $TOKEN_ID)" +echo "Token Address converted to StrKey contract address format:" $TOKEN_ADDRESS + +echo -n "$TOKEN_ID" > .soroban-example-dapp/token_id + +echo Build the crowdfund contract +make build + +echo Deploy the crowdfund contract +CROWDFUND_ID="$( + soroban contract deploy $ARGS \ + --wasm target/wasm32-unknown-unknown/release/soroban_crowdfund_contract.wasm +)" +echo "Contract deployed successfully with ID: $CROWDFUND_ID" +echo "$CROWDFUND_ID" > .soroban-example-dapp/crowdfund_id + +echo "Initialize the crowdfund contract" +deadline="$(($(date +"%s") + 86400))" +soroban contract invoke \ + $ARGS \ + --id "$CROWDFUND_ID" \ + -- \ + initialize \ + --recipient "$TOKEN_ADMIN_ADDRESS" \ + --deadline "$deadline" \ + --target_amount "1000000000" \ + --token "$TOKEN_ADDRESS" + +echo "Done" +``` + +Here's a summary of what the `initialize.sh` script does: + +- Identifies the network (standalone or futurenet) based on user input +- Determines the Soroban RPC host URL depending on its execution environment (either inside the soroban-preview Docker container or locally) +- Sets the Soroban RPC URL based on the previously determined host URL +- Sets the Soroban network passphrase and Friendbot URL depending on the chosen network +- Adds the network configuration to Soroban using `soroban network add` +- Generates a token-admin identity using `soroban keys generate` +- Fetches the TOKEN_ADMIN_SECRET and TOKEN_ADMIN_ADDRESS from the newly generated identity +- Saves the TOKEN_ADMIN_SECRET and TOKEN_ADMIN_ADDRESS in the .soroban directory +- Funds the token-admin account using Friendbot +- Deploy the Stellar asset contract with `soroban contract asset deploy` and stores the resulting TOKEN_ID +- Builds the crowdfund contract with `make build` and deploys it using `soroban contract deploy`, storing the returned CROWDFUND_ID +- Initializes the crowdfund contract by invoking the initialize function with necessary parameters +- Prints "Done" to signify the end of the initialization process + +By leveraging automated initialization, you can streamline the setup process for your Soroban Dapp, ensuring it is correctly deployed and initialized. diff --git a/docs/smart-contracts/guides/dapps/react.mdx b/docs/smart-contracts/guides/dapps/react.mdx new file mode 100644 index 000000000..ef2f0cc3f --- /dev/null +++ b/docs/smart-contracts/guides/dapps/react.mdx @@ -0,0 +1,140 @@ +--- +title: Create a frontend for your dapp using React +--- + +import { Alert } from "@site/src/components/Alert"; + +This section elaborates on how the frontends from your dapp can interact with the example contracts and access chain data, and connect to a freighter wallet. This will be illustrated by utilizing libraries provided by [`@soroban-react`](https://soroban-react.gitbook.io/index/), a simple, powerful framework for building modern Dapps using React. `@soroban-react` was created and is maintained by an amazing member of the community! + + + +This guide will demonstrate how an [example crowdfund dapp] frontend was developed with React. While much of the code is specific to this project, the principles demonstrated should be educational enough to get you started. + + + +Below is a list of the libraries used throughout the frontend code and their respective imports: + +```jsx +import { SorobanReactProvider } from "@soroban-react/core"; +import { testnet, sandbox, standalone } from "@soroban-react/chains"; +import { freighter } from "@soroban-react/freighter"; +import { ChainMetadata, Connector } from "@soroban-react/types"; +import type { + WalletChain, + ChainMetadata, + ChainName, +} from "@soroban-react/types"; +import { useSorobanReact } from "@soroban-react/core"; +``` + +These imports include `SorobanReactProvider` from `@soroban-react/core`, which is a context provider used to pass the SorobanReact instance to other components. You also import several types such as `WalletChain`, `ChainMetadata`, and `ChainName`, which help to maintain type safety within our application. + +## React Components and Prop Passing + +React thrives on its component-based architecture. Components are reusable pieces of code that return a React element to be rendered on the page. A typical React application consists of multiple components working harmoniously to create a dynamic user interface. + +Let's look at a component from the the [example crowdfund dapp], the [`MintButton` component](https://github.com/stellar/soroban-example-dapp/blob/07504b922bc75a48e5220711aea2cb4962f90367/components/molecules/form-pledge/index.tsx#L27): + +```tsx +function MintButton({ + account, + decimals, + symbol, +}: { + account: string; + decimals: number; + symbol: string; +}) { + const [isSubmitting, setSubmitting] = useState(false); + const { activeChain, server } = useNetwork(); + const networkPassphrase = activeChain?.networkPassphrase ?? ""; + const { sendTransaction } = useSendTransaction(); + const amount = BigNumber(100); + + return +
+
+ {images.map((image, index) => ( +
+ +
+ ))} +
+
+ + + ); +} diff --git a/src/components/atoms/UI/carousel/style.module.css b/src/components/atoms/UI/carousel/style.module.css new file mode 100644 index 000000000..58d6e904b --- /dev/null +++ b/src/components/atoms/UI/carousel/style.module.css @@ -0,0 +1,12 @@ +.image_holder { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 16px 12px; + gap: 8px; + border-radius: 8px; + border: 0; + height: auto; + color: #ffffff; +} diff --git a/src/components/atoms/UI/loading/index.tsx b/src/components/atoms/UI/loading/index.tsx new file mode 100644 index 000000000..7dfd772fc --- /dev/null +++ b/src/components/atoms/UI/loading/index.tsx @@ -0,0 +1,11 @@ +import React from "react"; +import Image from "next/image"; +import { LoadingSvg } from "../../../assets/icons"; + +export interface SpacerProps { + size: number; +} + +export function Loading({ size }: SpacerProps) { + return loading...; +} diff --git a/src/components/atoms/UI/switcher/index.tsx b/src/components/atoms/UI/switcher/index.tsx new file mode 100644 index 000000000..53d262959 --- /dev/null +++ b/src/components/atoms/UI/switcher/index.tsx @@ -0,0 +1,34 @@ +import React, { ChangeEvent, useState } from "react"; +import styles from "./style.module.css"; + +interface SwitcherProps { + id: string; + labelText?: string; + onChange?: (value: boolean) => void; +} + +export default function Switcher({ id, labelText, onChange }: SwitcherProps) { + const [isSwitched, setIsSwitched] = useState(false); + const switcherClasses = isSwitched + ? `${styles.switcher} ${styles.switcherOn}` + : styles.switcher; + + const changeHandler = ({ target }: ChangeEvent) => { + setIsSwitched(target.checked); + onChange && onChange(target.checked); + }; + + return ( +
+ {labelText} + +
+ ); +} diff --git a/src/components/atoms/UI/switcher/style.module.css b/src/components/atoms/UI/switcher/style.module.css new file mode 100644 index 000000000..8cbc0a1c8 --- /dev/null +++ b/src/components/atoms/UI/switcher/style.module.css @@ -0,0 +1,51 @@ +.switcherWrapper { + display: flex; + align-items: center; +} + +.switcher { + display: block; + width: 24px; + height: 16px; + border: 2px solid #369EA7; + border-radius: 16px; + background-color: transparent; + position: relative; + transition: all .2s ease-in-out; + cursor: pointer; +} + +.switcher::before { + content: ''; + display: block; + width: 4px; + height: 4px; + border: 2px solid #369EA7; + background-color: transparent; + border-radius: 50%; + position: absolute; + top: 2px; + left: 2px; +} + +.switcherOn { + background-color: #369EA7; +} + +.switcher.switcherOn::before { + background-color: #FFFFFF; + border-color: #FFFFFF; + left: unset; + right: 2px; +} + +.switcherLabel { + color: #585858; + font-size: 14px; + margin-right: 10px; +} + +.switcherInput { + width: 0; + height: 0; +} \ No newline at end of file diff --git a/src/components/atoms/challenge-card/challenge-icons.tsx b/src/components/atoms/challenge-card/challenge-icons.tsx new file mode 100644 index 000000000..3cc5833c1 --- /dev/null +++ b/src/components/atoms/challenge-card/challenge-icons.tsx @@ -0,0 +1,55 @@ +import React from "react"; + +/* Constants with icons markup to render inside challenge cards +In such way we can change styles of the icon on card hover */ +export const iconBulb = ( + + + + + +); + +export const iconWallet = ( + + + + + + + + + +); diff --git a/src/components/atoms/challenge-card/index.tsx b/src/components/atoms/challenge-card/index.tsx new file mode 100644 index 000000000..3cf306397 --- /dev/null +++ b/src/components/atoms/challenge-card/index.tsx @@ -0,0 +1,151 @@ +import React from "react"; +import styles from "./style.module.css"; +import { iconBulb, iconWallet } from "./challenge-icons"; +import { ChallengeInfo } from "../../../interfaces/challenge"; + +interface ChallengeCardProps { + challenge: ChallengeInfo; +} + +interface ChallengeConfig { + icon: JSX.Element; + route: string; + lastCheckpointRoute?: string; +} + +enum ActionBtnTitle { + SEE_DETAILS = "See details", + CONTINUE = "Continue", + PENDING = "Pending", + COMPLETED = "Completed", +} + +/* Config with challenge icon, page route and last checkpoint route in +case the challenge might have Pending status and require PR submission */ +const challengeConfig: { [key: string]: ChallengeConfig } = { + 0: { + icon: iconBulb, + route: "/docs/learn/interactive/dapps/challenges/challenge-0-crowdfund", + lastCheckpointRoute: + "/docs/learn/interactive/dapps/challenges/challenge-0-crowdfund#checkpoint-7--check-your-work", + }, + 1: { + icon: iconWallet, + route: "/docs/learn/interactive/dapps/challenges/challenge-1-payment", + }, + 2: { + icon: iconBulb, + route: "/docs/learn/interactive/dapps/challenges/challenge-2-liquidity-pool", + }, + 3: { + icon: iconBulb, + route: "/docs/learn/interactive/dapps/challenges/challenge-3-oracle", + lastCheckpointRoute: + "/docs/learn/interactive/dapps/challenges/challenge-3-oracle#checkpoint-8--check-your-work", + }, +}; +const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +export function ChallengeCard({ challenge }: ChallengeCardProps) { + const { id, name, milestonesAmount: totalSteps } = challenge; + let actionBtnTitle: ActionBtnTitle = ActionBtnTitle.SEE_DETAILS; + let displayDate: string; + const progressValue = (Number(challenge.progress) * 100) / totalSteps || 0; + + const setDisplayDate = (date: number) => { + const resultDate = new Date(date); + const month = months[resultDate.getUTCMonth()]; + const day = resultDate.getUTCDate(); + const year = resultDate.getUTCFullYear(); + + return { + month, + day, + year, + }; + }; + + if (challenge.startDate) { + const { month, day, year } = setDisplayDate(challenge.startDate); + actionBtnTitle = ActionBtnTitle.CONTINUE; + displayDate = `Started on ${month}, ${day}, ${year}`; + } + + if (challenge.completedAt) { + const { month, day, year } = setDisplayDate(challenge.completedAt); + actionBtnTitle = ActionBtnTitle.PENDING; + displayDate = `Passed on ${month}, ${day}, ${year}`; + } + + if (challenge.isCompleted) { + const { month, day, year } = setDisplayDate(challenge.completedAt); + actionBtnTitle = ActionBtnTitle.COMPLETED; + displayDate = `Completed on ${month}, ${day}, ${year}`; + } + + const showCompleteNote = + actionBtnTitle === ActionBtnTitle.PENDING && + challengeConfig[id].lastCheckpointRoute; + + const shouldDisableAction = + actionBtnTitle === ActionBtnTitle.COMPLETED || + actionBtnTitle === ActionBtnTitle.PENDING; + + return ( +
  • +
    + {challengeConfig[id].icon} + +
    {progressValue.toFixed()}/100%
    +
    +
    +

    + {name} +

    + +
    +
    +
    +
    + + {showCompleteNote && ( +

    + In order to complete this challenge, check your CI/CD results on a + pull request created on{" "} + + this checkpoint + +

    + )} +
    + +
    + {displayDate} + + + {actionBtnTitle} + +
    +
  • + ); +} diff --git a/src/components/atoms/challenge-card/style.module.css b/src/components/atoms/challenge-card/style.module.css new file mode 100644 index 000000000..d8052adf6 --- /dev/null +++ b/src/components/atoms/challenge-card/style.module.css @@ -0,0 +1,141 @@ +.card { + display: flex; + flex-direction: column; + justify-content: space-between; + border: 1px solid #ECECEC; + background-color: #ffffff; + list-style: none; + padding: 16px; + font-family: var(--ifm-font-family-base); + transition: all .3s ease-in-out; +} + +.cardHeader { + display: flex; + justify-content: space-between; + align-items: center; +} + +.cardTitle { + font-size: 18px; + font-weight: 600; + margin-bottom: 24px; +} + +.cardTitle a { + color: #222222; +} + +.cardContent { + padding: 32px 0 55px; + border-bottom: 1px solid #ECECEC; + position: relative; +} + +.cardFooter { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 16px; +} + +.date { + font-size: 12px; + color: #9F9F9F; +} + +.progress { + border-radius: 16px; + border: 1px solid #ECECEC; + background: #F9F9F9; + padding: 8px 16px; + font-size: 12px; + font-weight: 500; + line-height: 1; +} + +.action { + font-size: 14px; + font-weight: 600; + background-color: #FFD748; + color: #222222; + border-radius: 4px; + border: none; + padding: 16px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; +} + +.action[aria-disabled="true"] { + background-color: #ECECEC; + color: #9F9F9F; + pointer-events: none; +} + +.progressbar { + width: 100%; + height: 4px; + background-color: #222222; + display: flex; + align-items: center; +} + +.progressLine { + height: 4px; + background-color: #FFD234; +} + +.slider { + width: 12px; + height: 12px; + border-radius: 50%; + background-color: #FFD234; +} + +.cardNote { + font-size: 12px; + font-weight: 500; + margin: 15px 0 0; + position: absolute; + bottom: 6px; +} + +/* Styles on card hover */ + +.card:hover { + background-color: #FFD748; +} + +.card:hover .progress { + border-color: #222222; +} + +.card:hover .cardContent { + border-color: #ffffff; +} + +.card:hover .date { + color: #222222; +} + +.card:hover .action { + text-decoration: none; +} + +.card:hover .action:not([aria-disabled="true"]) { + background-color: #222222; + color: #ffffff; +} + +.card:hover circle { + fill: black; +} + +.card:hover .progressLine { + background-color: #ffffff; +} + +.card:hover .slider { + background-color: #ffffff; +} \ No newline at end of file diff --git a/src/components/atoms/challenge-contract-form/index.tsx b/src/components/atoms/challenge-contract-form/index.tsx new file mode 100644 index 000000000..738b121ee --- /dev/null +++ b/src/components/atoms/challenge-contract-form/index.tsx @@ -0,0 +1,124 @@ +import React, { + useState, + useEffect, + useContext, + ChangeEvent, + FormEvent, +} from "react"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import styles from "./style.module.css"; +import CompleteStepButton from "../complete-step-button"; +import UserChallengesContext, { + UserChallengesContextProps, +} from "../../../store/user-challenges-context"; +import { getActiveChallenge } from "../../../utils/get-active-challenge"; +import useAuth from "../../../hooks/useAuth"; + +interface ChallengeFormProps { + id: number; + address?: string; +} + +function ChallengeContractForm({ address, id }: ChallengeFormProps) { + const [savedContractId, setSavedContractId] = useState(""); + const [contractId, setContractId] = useState(""); + const [isStarted, setIsStarted] = useState(false); + const [formError, setFormError] = useState(null); + const { data } = useContext( + UserChallengesContext, + ); + const isSubmitBtnDisabled = !contractId || savedContractId === contractId; + + useEffect(() => { + if (address) { + const challenge = getActiveChallenge(data, id); + setSavedContractId(challenge?.contractId || ""); + setIsStarted(!!challenge?.startDate); + } + }, [address, savedContractId, data, id]); + + const changeHandler = (event: ChangeEvent) => { + setContractId(event.target.value); + }; + + const blurHandler = () => { + if (!contractId) { + setFormError("Mandatory field"); + } else { + setFormError(null); + } + }; + + if (!isStarted) { + return ( + <> + + Connect your wallet to track your progress and submit ContractId. + +
    + + ); + } + + return ( +
    + {savedContractId ? ( +

    ContractId submitted!

    + ) : null} + +
    e.preventDefault()} + > + + + + {savedContractId ? "Re-submit" : "Submit contractId"} + +
    + {formError} +
    + ); +} + +function InnerComponent({ id }: { id: number }) { + const { loading, address } = useAuth(); + + // if user is not logged in (address is undefined), render the Login button + if (loading) { + return ( +
    + Please connect to Testnet network. +
    +
    + ); + } + // if user is logged in and connected to the right network, + // render the ChallengeForm + return ; +} + +export function ParentChallengeContractForm({ id }: { id: number }) { + return ( + Please connect to Testnet network.}> + {() => } + + ); +} diff --git a/src/components/atoms/challenge-contract-form/style.module.css b/src/components/atoms/challenge-contract-form/style.module.css new file mode 100644 index 000000000..ad04774b1 --- /dev/null +++ b/src/components/atoms/challenge-contract-form/style.module.css @@ -0,0 +1,94 @@ +.challengeform { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + gap: 16px; + margin-right: 1rem; +} + +/* .displayData { + display: flex; +} */ + +.button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 16px 12px; + gap: 8px; + background: #1a1523; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 14px; + line-height: 22px; + color: #f5f5f5; + cursor: pointer; + align-self: baseline; +} + +.success { + display: flex; + flex-direction: column; + justify-content: center; + align-items: left; + gap: 8px; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 18px; + line-height: 22px; + color: #1a1523; + cursor: pointer; + align-self: baseline; +} + + +[data-theme="dark"] .button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 16px 12px; + gap: 8px; + background: #767676; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 14px; + line-height: 22px; + color: #f5f5f5; + cursor: pointer; + align-self: baseline; +} + +.input { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + align-items: center; + padding: 11px; + gap: 8px; + background: #ffffff; + border-radius: 2px; + border: 0; + font-size: 16px; + color: #585858; + border: 1px solid #ECECEC; + cursor: pointer; +} + +.inputWithError { + border-color: #DF0101; +} + +.errorMessage { + position: absolute; + color: #DF0101; +} \ No newline at end of file diff --git a/src/components/atoms/challenge-form/index.tsx b/src/components/atoms/challenge-form/index.tsx new file mode 100644 index 000000000..25245d70d --- /dev/null +++ b/src/components/atoms/challenge-form/index.tsx @@ -0,0 +1,147 @@ +import React, { + useState, + useEffect, + useContext, + ChangeEvent, + FormEvent, +} from "react"; +import BrowserOnly from "@docusaurus/BrowserOnly"; +import styles from "./style.module.css"; +import CompleteStepButton from "../complete-step-button"; +import UserChallengesContext, { + UserChallengesContextProps, +} from "../../../store/user-challenges-context"; +import { getActiveChallenge } from "../../../utils/get-active-challenge"; +import useAuth from "../../../hooks/useAuth"; + +interface ChallengeFormProps { + id: number; + address?: string; +} + +function ChallengeForm({ address, id }: ChallengeFormProps) { + const [savedUrl, setSavedUrl] = useState(""); + const [url, setUrl] = useState(""); + const [isStarted, setIsStarted] = useState(false); + const [formError, setFormError] = useState(null); + const { data } = useContext( + UserChallengesContext, + ); + const isSubmitBtnDisabled = !url || savedUrl === url; + + useEffect(() => { + if (address) { + const challenge = getActiveChallenge(data, id); + setSavedUrl(challenge?.url || ""); + setIsStarted(!!challenge?.startDate); + } + }, [address, savedUrl, data, id]); + + const isValidUrl = (urlString: string): boolean => { + try { + return Boolean(new URL(urlString)); + } catch (e) { + return false; + } + }; + + const changeHandler = (event: ChangeEvent) => { + const inputValue = event.target.value; + const isVercelApp = inputValue.includes(".vercel.app"); + + setFormError(null); + + if (!isValidUrl(inputValue)) { + setFormError("Please enter a valid url"); + return; + } + + if (!isVercelApp) { + setFormError("URL should contain .vercel.app to complete the checkpoint"); + } else { + setUrl(inputValue); + } + }; + + const blurHandler = () => { + if (!url) { + setFormError("Mandatory field"); + } + }; + + if (!isStarted) { + return ( + <> + + Start the challenge to track your progress and submit the url. + +
    + + ); + } + + return ( +
    + {savedUrl ? ( +

    + Public url submitted! Your DApp is deployed to: + {savedUrl} +

    + ) : null} + +
    e.preventDefault()} + > + + + + {savedUrl ? "Re-submit" : "Submit url"} + +
    + {formError} +
    + ); +} + +function InnerComponent({ id }: { id: number }) { + const { loading, address } = useAuth(); + + // if user is not logged in (address is undefined), render the Login button + if (loading) { + return ( +
    + Please connect to Testnet or Futurenet network. +
    +
    + ); + } + // if user is logged in and connected to the right network, + // render the ChallengeForm + return ; +} + +export function ParentChallengeForm({ id }: { id: number }) { + return ( + Please connect to Testnet or Futurenet network.}> + {() => } + + ); +} diff --git a/src/components/atoms/challenge-form/style.module.css b/src/components/atoms/challenge-form/style.module.css new file mode 100644 index 000000000..ad04774b1 --- /dev/null +++ b/src/components/atoms/challenge-form/style.module.css @@ -0,0 +1,94 @@ +.challengeform { + display: flex; + flex-direction: row; + justify-content: left; + align-items: center; + gap: 16px; + margin-right: 1rem; +} + +/* .displayData { + display: flex; +} */ + +.button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 16px 12px; + gap: 8px; + background: #1a1523; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 14px; + line-height: 22px; + color: #f5f5f5; + cursor: pointer; + align-self: baseline; +} + +.success { + display: flex; + flex-direction: column; + justify-content: center; + align-items: left; + gap: 8px; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 18px; + line-height: 22px; + color: #1a1523; + cursor: pointer; + align-self: baseline; +} + + +[data-theme="dark"] .button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 16px 12px; + gap: 8px; + background: #767676; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 14px; + line-height: 22px; + color: #f5f5f5; + cursor: pointer; + align-self: baseline; +} + +.input { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + align-items: center; + padding: 11px; + gap: 8px; + background: #ffffff; + border-radius: 2px; + border: 0; + font-size: 16px; + color: #585858; + border: 1px solid #ECECEC; + cursor: pointer; +} + +.inputWithError { + border-color: #DF0101; +} + +.errorMessage { + position: absolute; + color: #DF0101; +} \ No newline at end of file diff --git a/src/components/atoms/challenges-list/index.tsx b/src/components/atoms/challenges-list/index.tsx new file mode 100644 index 000000000..2c449d6d2 --- /dev/null +++ b/src/components/atoms/challenges-list/index.tsx @@ -0,0 +1,96 @@ +import React, { useMemo, useState } from "react"; +import styles from "./style.module.css"; +import { Challenge, ChallengeInfo } from "../../../interfaces/challenge"; +import { ChallengeCard } from "../challenge-card"; +import Switcher from "../UI/switcher"; +import ConfirmModal from "../confirm-modal"; + +interface Props { + availableChallenges: Challenge[]; + userChallenges: ChallengeInfo[]; + onRefresh: () => void; + onReset?: () => void; +} + +export default function ChallengeList({ + availableChallenges, + userChallenges, + onRefresh, + onReset, +}: Props) { + const [onlyMine, setOnlyMine] = useState(false); + const [confirmReset, setConfirmReset] = useState(false); + + const onResetClick = () => { + setConfirmReset(true); + }; + + const onCancelClick = () => { + setConfirmReset(false); + }; + + const myChallanges = ( + <> + {userChallenges?.length ? ( + userChallenges?.map((challenge: ChallengeInfo) => { + return ; + }) + ) : ( +

    You haven't completed any challenges yet.

    + )} + + ); + + const allChallenges = useMemo( + () => + availableChallenges?.map((aChall: Challenge) => { + const inProgressChallenge = userChallenges.find( + (uChall: ChallengeInfo) => uChall.id === aChall.id, + ); + + if (inProgressChallenge) { + return ( + + ); + } + + return ; + }), + [availableChallenges, userChallenges], + ); + + return ( + <> +
    +
    + {userChallenges?.length > 0 ? ( + setOnlyMine(value)} + /> + ) : null} + +
    + {onReset && ( + + )} +
    + +
      + {onlyMine ? myChallanges : allChallenges} +
    + + {confirmReset && onReset && ( + + )} + + ); +} diff --git a/src/components/atoms/challenges-list/style.module.css b/src/components/atoms/challenges-list/style.module.css new file mode 100644 index 000000000..498a033fe --- /dev/null +++ b/src/components/atoms/challenges-list/style.module.css @@ -0,0 +1,54 @@ +.dashboardContent { + flex-grow: 2; + width: 75%; + margin: 32px auto; +} + +.challengeCards { + padding: 16px 0 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: max-content; + grid-gap: 30px; +} + +.listHeader { + display: flex; + justify-content: space-between; +} + +.dataControls { + display: flex; +} + +.refreshBtn { + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + background-color: transparent; + color: #369ea7; + border-radius: 4px; + border: 1px solid #369ea7; + padding: 12px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; +} + +.resetBtn { + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + background-color: transparent; + color: #df0101; + border-radius: 4px; + border: 1px solid #df0101; + padding: 12px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; +} + +div + .refreshBtn { + margin-left: 20px; +} diff --git a/src/components/atoms/complete-step-button/index.tsx b/src/components/atoms/complete-step-button/index.tsx new file mode 100644 index 000000000..da3a2a039 --- /dev/null +++ b/src/components/atoms/complete-step-button/index.tsx @@ -0,0 +1,236 @@ +import React, { + PropsWithChildren, + useContext, + useEffect, + useState, +} from "react"; +import { toast } from "react-toastify"; +import { useReward } from "react-rewards"; +import { AxiosResponse } from "axios"; +import useAuth from "../../../hooks/useAuth"; +import styles from "./styles.module.css"; +import UserChallengesContext, { + UserChallengesContextProps, +} from "../../../store/user-challenges-context"; +import { getActiveChallenge } from "../../../utils/get-active-challenge"; +import { + UserChallengeData, + ChallengeInfo, + UpdateProgressData, +} from "../../../interfaces/challenge"; +import { updateUserProgress } from "../../../services/challenges"; +import { getContractBalance } from "../../../utils/get-contract-balance"; + +interface CompleteStepButtonState { + isCompleted: boolean; + isLastStep: boolean; + isStarted: boolean; +} + +interface CompleteStepButtonProps extends PropsWithChildren { + type?: "button" | "submit"; + isDisabled?: boolean; + id: number; + progress: number; + url?: string; + contractId?: string; +} + +const milestoneToast = ( +
    + Smiley face + + Congratulations on your milestone! + +
    +); + +const completedToast = ( +
    + Smiley face + + Congratulations! Your challenge is completed successfully. + +
    +); + +const passedToast = ( +
    + Smiley face + + Congratulations on passing the challenge! Please, proceed to the next + checkpoint so that we can check your work. + +
    +); + +export default function CompleteStepButton({ + type, + isDisabled, + children, + id, + progress, + url, + contractId, +}: CompleteStepButtonProps) { + const [challenge, setChallenge] = useState(null); + const [state, setState] = useState({ + isCompleted: false, + isLastStep: false, + isStarted: false, + }); + const { data, updateProgress } = useContext( + UserChallengesContext, + ); + const { address } = useAuth(); + const { reward, isAnimating } = useReward( + `reward${id}-${progress}`, + "confetti", + { + elementCount: 150, + zIndex: 10000, + position: "absolute", + angle: 90, + lifetime: 300, + colors: [ + "#FFD748", + "#369EA7", + "#FF6534", + "#DF0101", + "#34CEFF", + "#AB56FF", + ], + }, + ); + + const isButtonDisabled = + (state.isCompleted && !state.isLastStep) || isDisabled || isAnimating; + + useEffect(() => { + setChallenge(getActiveChallenge(data, id)); + const isStepCompleted = !!challenge && challenge.progress >= progress; + const isLastCourseStep = !!(challenge?.milestonesAmount === progress); + + setState((prevState: CompleteStepButtonState) => { + return { + isCompleted: isStepCompleted, + isLastStep: isLastCourseStep, + isStarted: !!challenge?.startDate, + }; + }); + }, [challenge, data, progress, id]); + + const showToast = (template: JSX.Element) => { + toast(template, { + hideProgressBar: true, + position: "top-center", + autoClose: 3000, + }); + }; + + const postUserProgress = async (updatedItem: UpdateProgressData) => { + const response: AxiosResponse = await updateUserProgress( + updatedItem, + ); + updateProgress(response.data.challenge); + }; + + const lastStepHandler = async () => { + await postUserProgress({ + userId: address, + challengeId: id, + challengeProgress: progress, + url, + completedAt: Date.now(), + startDate: challenge?.startDate, + contractId: challenge?.contractId, + totalValueLocked: challenge?.totalValueLocked, + }); + + showToast(challenge?.isPullRequestRequired ? passedToast : completedToast); + reward(); + }; + + const completeStepHandler = async () => { + if (state.isLastStep) { + lastStepHandler(); + return; + } + + let balance = 0; + + // if funding step => get contract balance (except payment for now) + if (progress === 2 && id !== 1) { + if (challenge?.contractId) { + try { + const result = await getContractBalance( + challenge?.contractId, + address, + ); + if (!result) { + toast("No locked balance found!", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + + return; + } + + balance = result; + } catch (error) { + console.error(error); + + toast("No locked balance found!", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + return; + } + } else { + toast("No contract id found!", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + return; + } + } + + setState((prevState: CompleteStepButtonState) => { + return { + ...prevState, + isCompleted: true, + }; + }); + + await postUserProgress({ + userId: address, + challengeId: id, + challengeProgress: progress, + startDate: challenge?.startDate, + contractId: contractId || challenge?.contractId, + totalValueLocked: challenge?.totalValueLocked || balance, + }); + + showToast(milestoneToast); + }; + + return state.isStarted ? ( +
    + +
    + ) : null; +} diff --git a/src/components/atoms/complete-step-button/styles.module.css b/src/components/atoms/complete-step-button/styles.module.css new file mode 100644 index 000000000..8d9df306f --- /dev/null +++ b/src/components/atoms/complete-step-button/styles.module.css @@ -0,0 +1,39 @@ +.completeStep { + display: flex; + justify-content: center; + align-self: center; +} + +.completeStepButton { + font-family: var(--ifm-font-family-base); + font-size: 14px; + font-weight: 600; + background-color: #FFD748; + color: #222222; + border-radius: 4px; + border: none; + padding: 16px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; +} + +.completeStepButton:disabled { + background-color: #ECECEC; + color: #9F9F9F; + cursor: not-allowed; +} + +.notification { + display: flex; + align-items: center; + padding-left: 16px; +} + +.notificationText { + color: #222222; + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + margin-left: 8px; +} \ No newline at end of file diff --git a/src/components/atoms/confirm-modal/index.tsx b/src/components/atoms/confirm-modal/index.tsx new file mode 100644 index 000000000..d55d0c1b4 --- /dev/null +++ b/src/components/atoms/confirm-modal/index.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import styles from "./style.module.css"; + +interface Props { + onCancel: () => void; + onReset: () => void; +} + +export default function ConfirmModal({ onCancel, onReset }: Props) { + return ( + <> +
    +
    +

    Reseting progress!

    + It may take up to minute!
    + Are you sure you want to continue? +
    + + +
    +
    + + ); +} diff --git a/src/components/atoms/confirm-modal/style.module.css b/src/components/atoms/confirm-modal/style.module.css new file mode 100644 index 000000000..d855ac2c0 --- /dev/null +++ b/src/components/atoms/confirm-modal/style.module.css @@ -0,0 +1,63 @@ +.blurContainer { + position: absolute; + height: 100vh; + width: 100vw; + overflow: hidden; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.7); + z-index: 1000; +} + +.modalContainer { + position: absolute; + width: 350px; + height: 200px; + top: calc(50% - 100px); + left: calc(50% - 175px); + background-color: #ffffff; + padding: 20px; + border-radius: 8px; + text-align: center; + z-index: 1001; +} + +.title { + font-size: 18px; + font-weight: 600; +} + +.buttons { + margin-top: 20px; + display: flex; + justify-content: center; + gap: 20px; +} + +.cancelBtn { + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + background-color: transparent; + color: #369ea7; + border-radius: 4px; + border: 1px solid #369ea7; + padding: 12px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; +} + +.resetBtn { + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + background-color: transparent; + color: #df0101; + border-radius: 4px; + border: 1px solid #df0101; + padding: 12px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; +} diff --git a/src/components/atoms/dashboard-header/index.tsx b/src/components/atoms/dashboard-header/index.tsx new file mode 100644 index 000000000..9520428a0 --- /dev/null +++ b/src/components/atoms/dashboard-header/index.tsx @@ -0,0 +1,104 @@ +import React , { useState }from "react"; +import { toast } from "react-toastify"; +import { Ranking } from "interfaces/challenge"; +import styles from "./style.module.css"; +import useAuth from "../../../hooks/useAuth"; + +const AVATAR = `/icons/icon-avatar-${Math.floor(Math.random() * 10) + 1}.svg`; + +interface Props { + totalCompleted: number; + ranking: Ranking; +} + +const DashboardHeader: React.FC = ({ totalCompleted, ranking }) => { + const { address, disconnect } = useAuth(); + + const [showContent, setShowContent] = useState(false); + const addressEnding = address?.substring(address.length, address.length - 4); + + const copyUserAddress = () => { + navigator.clipboard.writeText(address); + + toast("Copied to clipboard!", { + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + }; + return ( +
    +

    Your dashboard

    + +
      +
    • + User avatar icon + +
      + +
      +
      {address}
      + Copy address to clipboard +
      + +
      +
    • + +
    • + Star icon +
      + + completed challenges +
      +
    • + +
    • + Ranking icon +
      + + + {showContent && ( +
      + {!ranking.current || !ranking.total ? ( + + Start a challenge to get a rank + + ) : ( + <> + + leaderboard ranking + + )} +
      + )} +
      +
    • +
    +
    + ); +}; + +export default DashboardHeader; diff --git a/src/components/atoms/dashboard-header/style.module.css b/src/components/atoms/dashboard-header/style.module.css new file mode 100644 index 000000000..e5c7576f8 --- /dev/null +++ b/src/components/atoms/dashboard-header/style.module.css @@ -0,0 +1,114 @@ +.dashboardHeader { + background-image: url("/static/img/dashboard-bg.png"); + background-size: cover; + background-position: center; + height: 200px; + color: #ffffff; + padding: 40px 0 0 12%; +} + +.dashboardTitle { + text-decoration: underline; + margin-left: 10px; +} + +.dashboardTabs { +} + +.toggleButton { + font-family: var(--ifm-font-family-base); + font-size: 14px; + font-weight: 600; + background-color: #FFD748; + color: #222222; + border-radius: 4px; + border: none; + padding: 8px 8px; + width: max-content; + cursor: pointer; +} + +.userInfo { + display: flex; + padding: 10px 0 0 10px; +} + +.userInfoItem { + display: flex; + align-items: flex-start; + list-style: none; +} + +.userInfoItem:nth-child(1) { + margin-right: 150px; +} + +.userInfoItem:nth-child(2) { + margin-right: 24px; +} + +.userAddress { + position: relative; + max-width: 132px; + font-weight: 600; +} + +.userAddress::after { + content: attr(data-truncate); + position: absolute; + left: 100%; + top: 0; + white-space: nowrap; +} + +.userAddress div { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.completedChallenges { + display: flex; + flex-direction: column; +} + +.completedChallenges label { + font-size: 20px; + font-weight: 600; +} + +.completedChallenges span { + font-size: 12px; + color: #bdbdbd; +} + +.copyIcon { + position: absolute; + top: 1px; + right: -67px; + cursor: pointer; +} + +.avatarIcon { + margin-right: 10px; +} + +.statsIcon { + width: 30px; + height: 30px; + background-color: #444444; + padding: 6px; + border-radius: 50%; + margin-right: 10px; +} + +.logoutButton { + color: #369ea7; + background-color: transparent; + padding: 12px 8px; + border: none; + font-family: var(--ifm-font-family-base); + font-size: 14px; + font-weight: 500; + cursor: pointer; +} diff --git a/src/components/atoms/index.tsx b/src/components/atoms/index.tsx new file mode 100644 index 000000000..d32fd5f86 --- /dev/null +++ b/src/components/atoms/index.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { useSorobanReact } from "@soroban-react/core"; +import styles from "./style.module.css"; + +export interface ConnectButtonProps { + label: string; + isHigher?: boolean; +} + +export function ConnectButton({ label, isHigher }: ConnectButtonProps) { + const { connect } = useSorobanReact(); + const openConnectModal = async () => { + await connect(); + }; + + return ( + + ); +} diff --git a/src/components/atoms/start-challenge-button/index.tsx b/src/components/atoms/start-challenge-button/index.tsx new file mode 100644 index 000000000..13da59ce5 --- /dev/null +++ b/src/components/atoms/start-challenge-button/index.tsx @@ -0,0 +1,91 @@ +import React, { useContext, useEffect, useState } from "react"; +import { toast } from "react-toastify"; +import { AxiosResponse } from "axios"; +import styles from "./style.module.css"; +import useAuth from "../../../hooks/useAuth"; +import UserChallengesContext, { + UserChallengesContextProps, +} from "../../../store/user-challenges-context"; +import { getActiveChallenge } from "../../../utils/get-active-challenge"; +import { + UserChallengeData, + UpdateProgressData, +} from "../../../interfaces/challenge"; +import { + fetchUserProgress, + updateUserProgress, +} from "../../../services/challenges"; + +interface StartChallengeButtonProps { + id: number; +} + +const startedToast = ( +
    + Smiley face + + You’ve joined the challenge! Good luck! + +
    +); + +export default function StartChallengeButton({ + id, +}: StartChallengeButtonProps) { + const { address, isConnected, connect } = useAuth(); + const [isStarted, setIsStarted] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { setData, updateProgress } = useContext( + UserChallengesContext, + ); + + useEffect(() => { + const fetchProgress = async () => { + setIsLoading(true); + try { + const response = await fetchUserProgress(address?.toString() || ""); + const challenges = response.data.challenges || []; + const challenge = getActiveChallenge(challenges, id); + setData(challenges); + setIsStarted(!!challenge?.startDate); + } finally { + setIsLoading(false); + } + }; + + if (address) { + fetchProgress(); + } + }, [address]); + + const startChallenge = async () => { + const updatedItem: UpdateProgressData = { + userId: address?.toString() || "", + challengeId: id, + challengeProgress: 0, + startDate: Date.now(), + }; + + const response: AxiosResponse = await updateUserProgress( + updatedItem, + ); + updateProgress(response.data.challenge); + + toast(startedToast, { + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + setIsStarted(true); + }; + + return ( + + ); +} diff --git a/src/components/atoms/start-challenge-button/style.module.css b/src/components/atoms/start-challenge-button/style.module.css new file mode 100644 index 000000000..2807b9aa8 --- /dev/null +++ b/src/components/atoms/start-challenge-button/style.module.css @@ -0,0 +1,37 @@ +.button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 12px 8px; + margin-bottom: 20px; + gap: 8px; + background: #FFD748; + color: #222222; + border-radius: 4px; + border: 0; + font-family: var(--ifm-font-family-base); + font-weight: 600; + font-size: 14px; + cursor: pointer; +} + +.button:disabled { + background-color: #ECECEC; + color: #9F9F9F; + cursor: not-allowed; +} + +.notification { + display: flex; + align-items: center; + padding-left: 16px; +} + +.notificationText { + color: #222222; + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + margin-left: 8px; +} \ No newline at end of file diff --git a/src/components/atoms/style.module.css b/src/components/atoms/style.module.css new file mode 100644 index 000000000..fb19c1513 --- /dev/null +++ b/src/components/atoms/style.module.css @@ -0,0 +1,21 @@ +.button { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 16px 12px; + gap: 8px; + background: #1a1523; + border-radius: 8px; + border: 0; + height: 38px; + font-weight: 600; + font-size: 14px; + line-height: 22px; + color: #ffffff; + cursor: pointer; +} + +.higher { + height: 58px; +} \ No newline at end of file diff --git a/src/components/molecules/SortableTable/index.js b/src/components/molecules/SortableTable/index.js new file mode 100644 index 000000000..ef169bcbb --- /dev/null +++ b/src/components/molecules/SortableTable/index.js @@ -0,0 +1,33 @@ +import React, { useState } from "react" +import styles from "./style.module.css"; + +const arrowDown = ; +const arrowUp = ; + +const SortableTable = (props) => { + const [isAscending, setIsAscending] = useState(false); + const [sortColumn, setSortColumn] = useState(null) + + const sortTable = (val) => { + const sortDirection = col === val ? !isAscending : false + setIsAscending(sortDirection) + setSortColumn(val) + } + + return ( + + + + + + + + + + {props.children} + +
    TitleLinkTags
    + ) +} + +export default SortableTable diff --git a/src/components/molecules/SortableTable/style.module.css b/src/components/molecules/SortableTable/style.module.css new file mode 100644 index 000000000..3a41004c6 --- /dev/null +++ b/src/components/molecules/SortableTable/style.module.css @@ -0,0 +1,4 @@ +.sortable { + width: 100%; + display: table; +} diff --git a/src/components/molecules/leaderboard/index.tsx b/src/components/molecules/leaderboard/index.tsx new file mode 100644 index 000000000..3d5dd02e3 --- /dev/null +++ b/src/components/molecules/leaderboard/index.tsx @@ -0,0 +1,178 @@ +import React, { useState } from "react"; +import { Leaderboard as LeaderboardI } from "../../../interfaces/challenge"; +import { + LeaderboardColumn, + LeaderboardParams, +} from "../../../services/leaderboard"; +import styles from "./style.module.css"; + +type Props = { + userId?: string; + list: LeaderboardI[]; + isLoading: boolean; + onLoad: (params: LeaderboardParams) => void; +}; + +const PAGE_SIZE = 10; + +const arrowDown = ; +const arrowUp = ; + +const Leaderboard: React.FC = ({ userId, list, isLoading, onLoad }) => { + const [col, setCol] = useState(null); + const [isAsc, setIsAsc] = useState(false); + + const [pageNumber, setPageNumber] = useState(1); + + const maxUsers = list[0]?.ranking.total; + const maxPage = Math.ceil(maxUsers / PAGE_SIZE); + + const onSort = (val: LeaderboardColumn) => { + // if click on current column ? change direction : set default direction + const nextAsc = col === val ? !isAsc : false; + setIsAsc(nextAsc); + onLoad({ + colName: val, + direction: nextAsc ? "asc" : "desc", + }); + setCol(val); + setPageNumber(1); + }; + + const onReset = () => { + setIsAsc(true); + setCol(null); + onLoad({}); + setPageNumber(1); + }; + + const onNext = () => { + const nextPageNumber = pageNumber + 1; + setPageNumber(nextPageNumber); + onLoad({ + pageNumber: nextPageNumber, + ...(col ? { colName: col } : {}), + direction: isAsc ? "asc" : "desc", + }); + }; + + const onPrev = () => { + const nextPageNumber = pageNumber - 1; + setPageNumber(nextPageNumber); + onLoad({ + pageNumber: nextPageNumber, + ...(col ? { colName: col } : {}), + direction: isAsc ? "asc" : "desc", + }); + }; + + const arrow = !isAsc ? arrowDown : arrowUp; + + return ( +
    + + + + + + + + + + + {isLoading ? null : ( + + {list.map((item) => { + const isCurrent = userId === item.userId; + return ( + + + + + + + + ); + })} + + )} +
    + Place + Address onSort(LeaderboardColumn.TotalValueLocked)} + > + Total Value Locked {col === LeaderboardColumn.TotalValueLocked ? arrow : null} + onSort(LeaderboardColumn.ChallengesCompleted)} + > + Number of challenges{" "} + {col === LeaderboardColumn.ChallengesCompleted ? arrow : null} + onSort(LeaderboardColumn.MinutesSpent)} + > + Minutes spent{" "} + {col === LeaderboardColumn.MinutesSpent ? arrow : null} +
    +
    + {item.ranking.current} +
    + {isCurrent ? ( + <> + you are here! + Star icon + + ) : null} +
    {`${item.userId}`}{item.totalValueLocked} + {item.challengesCompleted === 0 ? ( + "In Progress" + ) : ( + <>{item.challengesCompleted} + )} + + {item.minutesSpent === 0 ? ( + "In Progress" + ) : ( + <>{item.minutesSpent} + )} +
    + {isLoading ?
    Loading
    : null} +
    + + +
    +
    + ); +}; + +export default Leaderboard; diff --git a/src/components/molecules/leaderboard/style.module.css b/src/components/molecules/leaderboard/style.module.css new file mode 100644 index 000000000..02a481034 --- /dev/null +++ b/src/components/molecules/leaderboard/style.module.css @@ -0,0 +1,125 @@ +.leaderboard { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + padding: 32px 0; +} + +.leadTable { + width: 100%; + display: table; + font-family: "Inter"; + font-size: 14px; + font-weight: 500; +} + +table.leadTable td, +th { + background-color: #ffffff; +} + +.leadTableHead { + background-color: var(--secondary-black); + color: #ffffff; +} + +.leadTableHeadColumn { + width: 273px; + padding: 16px; + text-align: start; + border: 0.5px solid #b0b0b0; + cursor: pointer; + color: #585858; +} + +.leadTableHeadColumnPlace { + width: 273px; + padding: 16px; + text-align: start; + cursor: pointer; + color: #585858; +} + +.leadTableBody { + color: var(--secondary-black); + background-color: #ffffff; +} + +.leadTableBodyRow { + background-color: #ffffff; + border: none; +} + +.userRow { + border: 2px solid #ffd748; +} + +.rankingCell { + display: flex; + align-items: center; +} + +.userRankingCell { + padding-left: 7px; +} + +.rankingCellNum { + border: 1px solid black; + width: min-content; + padding: 2px; + border-radius: 50%; + min-width: 28px; + display: flex; + align-items: center; + justify-content: center; +} + +.userRankingCellNum { + border: none; + padding: 5px 10px; + background-color: #ffd748; + margin-right: 5px; +} + +.leaderboardText { + display: flex; + flex-direction: column; + position: absolute; + top: 15px; + left: 45%; +} + +.leaderboardText span { + font-size: 24px; + font-weight: 200; +} + +.leaderboardText strong { + color: #585858; + font-size: 22px; +} + +.paginationBlock { + display: flex; + gap: 10px; + align-self: flex-end; +} + +.paginationButton { + padding: 10px; + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + border-radius: 4px; + border: none; + cursor: pointer; +} + +.loading { + height: 536px; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/constants.ts b/src/constants.ts index 390675e6f..dc6bc3097 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -23,3 +23,9 @@ export const CODE_LANGS = { typescript: 'TypeScript', yaml: 'YAML', }; + +export const FUTURENET_DETAILS = { + network: "FUTURENET", + networkUrl: "https://horizon-futurenet.stellar.org", + networkPassphrase: "Test SDF Future Network ; October 2022", + }; diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx new file mode 100644 index 000000000..ffa99107d --- /dev/null +++ b/src/hooks/useAuth.tsx @@ -0,0 +1,76 @@ +import { useContext, useState } from "react"; +import { toast } from "react-toastify"; +import UserChallengesContext, { + UserChallengesContextProps, +} from "../store/user-challenges-context"; + +// Import the Freighter library +import { isConnected, setAllowed, getPublicKey } from "@stellar/freighter-api"; + +const useAuth = () => { + const { address, setAddress } = useContext( + UserChallengesContext + ); + + const [loading, setLoading] = useState(false); + + const disconnect = () => { + setAddress(""); + }; + + const connect = async () => { + try { + setLoading(true); + + // Check if the user has Freighter installed + if (await isConnected()) { + // Prompt the user for authorization if needed + await setAllowed(); + + // Retrieve the user's public key + const publicKey = await getPublicKey(); + + // Store the user's public key in the context + setAddress(publicKey); + + setLoading(false); + + return true; + } else { + // Handle the case where Freighter is not installed + toast("Freighter is not installed!", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + return false; + } + } catch (e) { + console.error("Connection error", e); + + toast("Connection error!", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + return false; + } + }; + + const onConnectFreighter = () => { + // Call the connect function to initiate the Freighter connection + connect(); + }; + + return { + address, + isConnected: !!address, + loading, + connect: onConnectFreighter, + disconnect, + }; +}; + +export default useAuth; diff --git a/src/interfaces/challenge.ts b/src/interfaces/challenge.ts new file mode 100644 index 000000000..53788d89a --- /dev/null +++ b/src/interfaces/challenge.ts @@ -0,0 +1,52 @@ +export interface UserChallengeData { + userId: string; + challenge: ChallengeInfo; +} + +export interface UpdateProgressData { + userId: string; + challengeId: number; + challengeProgress: number; + url?: string; + startDate?: number; + completedAt?: number; + contractId?: string; + totalValueLocked?: number; +} + +export interface Ranking { + current: number; + total: number; +} + +export interface Leaderboard { + userId: string; + ranking: Ranking; + totalValueLocked: number; + minutesSpent: number; + challengesCompleted: number; +} + +export interface Challenge { + id: number; + name: string; + milestonesAmount: number; + isPullRequestRequired: boolean; +} + +export interface ChallengeInfo extends Challenge { + progress?: number; + url?: string; + contractId?: string; + startDate?: number; + completedAt?: number; + isCompleted?: boolean; + totalValueLocked?: number; +} + +export interface UserProgress { + userId: string; + completedChallenges: number; + ranking: Ranking; + challenges: ChallengeInfo[]; +} diff --git a/src/pages/docs/learn/interactive/dapps/challenges/challenge-0-crowdfund.mdx b/src/pages/docs/learn/interactive/dapps/challenges/challenge-0-crowdfund.mdx new file mode 100644 index 000000000..1e7539222 --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/challenges/challenge-0-crowdfund.mdx @@ -0,0 +1,355 @@ +--- +title: Crowdfund Dapp Challenge +description: Build and ship a Crowdfund Dapp! Beat the Challenge! +--- + +import mintToken from "@site/static/img/mintToken.png"; +import approveTokenMint from "@site/static/img/approveTokenMint.png"; +import updatedBalance from "@site/static/img/updatedBalance.png"; +import back100 from "@site/static/img/back100.png"; +import success from "@site/static/img/success.png"; +import backedResult from "@site/static/img/backedResult.png"; +import deployedDApp from "@site/static/img/deployedDApp.png"; +import { ParentChallengeForm } from "@site/src/components/atoms/challenge-form"; +import { ParentChallengeContractForm } from "@site/src/components/atoms/challenge-contract-form"; +import CompleteStepButton from "@site/src/components/atoms/complete-step-button"; +import StartChallengeButton from "@site/src/components/atoms/start-challenge-button"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import "./styles.css"; + + + +This challenge will guide you through the process of building and shipping a crowdfunding dapp on Stellar using Soroban. Unlike traditional crowdfunding applications, crowdfunding dapps (decentralized applications) provide the means for users to pledge funds to a crowdfund campaign directly from their digital wallets, without the need for intermediaries. + +In this challenge, you will learn how to deploy smart contracts to Futurenet, and how to interact with them through a web frontend. In this context, the term "ship" refers to finalizing the development process of your dapp, ensuring that it functions as expected, and is accessible for user interaction and testing through a hosted frontend. However, it's crucial to clarify that despite its functionality, the dapp is not promoted nor intended for deployment in a production-level setting on Futurenet. The challenge is designed for educational purposes, helping you understand how a dapp can be built and interacted with, with further customization and development, it has the potential to evolve into a full-fledged, ready-to-use crowdfunding solution. + +## Checkpoint 0: 📦 Install 📚 + +Start by installing the required dependencies. You'll also want to be sure you have the most updated version of Rust installed. + +Required: + +- `soroban-cli alias` (installed in the next step) +- `Node` v18: [Download Node](https://nodejs.org/en/download/) +- `Freighter Wallet`: [Freighter Wallet](https://freighter.app/) + +First, clone the Soroban Dapps Challenge repo and check out the `crowdfund` branch, which contains the code for the crowdfund smart contract that powers this dapp: + +```sh +git clone https://github.com/stellar/soroban-dapps-challenge.git +cd soroban-dapps-challenge +git checkout crowdfund +``` + +Then, install soroban-cli alias by running the following command: + +```sh +cargo install_soroban +``` + +Soroban CLI is the command line interface to Soroban. It allows you to build, deploy, and interact with smart contracts, configure identities, generate key pairs, manage networks, and more. The soroban-cli (alias) that is used in this challenge is a pinned version of the soroban-cli that is used in the Soroban Dapps Challenge. This ensures that the challenge is reproducible and that all participants are using the same version of Soroban. + +## Checkpoint 1: 🎬 Deploy Smart Contracts + +Now that you have the Crowdfund branch checked out, it's time to deploy the smart contracts to a Sandbox environment. Deploying a smart contract in a production setting involves submitting the contract code to the blockchain's main network ( Mainnet ), where it becomes part of the chain's immutable ledger. Deploying smart contracts to a Sandbox environment simulates that process without actually affecting Mainnet. When you deploy the smart contracts, you'll instead deploy to Futurenet, a test network with more cutting-edge features that have not yet been implemented in the Mainnet. + +In your terminal, load the contracts and initialize them in the Sandbox environment by running the following commands: + +```sh +npm run setup +``` + +If the command runs successfully, your terminal will return a series of messages notifying you about the successful initialization of the contracts and the post-installation sequence. + +```sh +Contract deployed successfully with ID: CBXHU5BWWTOCZRYX3DMSSKCFG7B3K2YG2I5F75ALPQ6GCY6ZES2XKLTI +Deploy the crowdfund contract +Contract deployed successfully with ID: CBKY7UN5VGD4LIQFOBOTSUSQWK67BZZTA23NIEVWSWRR5SAT26JQN2BN +Initialize the abundance token contract + +Initialize the crowdfund contract + +Done + +> soroban-example-dapp@0.1.0 build-contracts +... +``` + +The contract ID is a unique identifier for a smart contract deployed on a blockchain. This contract ID is used to interact with and reference the smart contract, allowing users to invoke functions from the smart contract, send transactions, or otherwise interact with the smart contract's functionalities and data stored on the blockchain. + +:::tip + +Please, save your deployed contract ID. You will need it to complete the challenge. + +::: + + + +## Checkpoint 2: 🤝 Connect the Frontend to the Backend + +Now that you have deployed the smart contract, it's time to check out the frontend of your dapp. The frontend is the browser interface where contributors to your crowdfund campaign will connect their digital wallets and pledge assets to the campaign's cause. + +First, start the development server: + +```sh +npm run dev +``` + +Now open your browser and visit [http://localhost:3000](http://localhost:3000). You should be able to see the frontend of your dapp. + +> Note: Follow the instructions below and ensure that you have funded your wallet address that you intend to use from browser, otherwise the dapp display will be blank an 'Account not found' will be printed on browser's console only. + +Now that you have the frontend running, it's time to connect it with the backend, your smart contract, that defines the rules and logic of the crowdfund campaign, including the function for accepting contributions. If you want to dig into the specifics of the contract, take a look at the video walkthrough of the contract code [here](https://youtu.be/vTz0CQYnMRQ?t=260&feature=shared). + +You will need to add some Futurenet network lumens to your wallet to interact with the dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create and or fund an account on Futurenet. Remember, these are test lumens for use on Futurenet and cannot be used on Mainnet. + +## Checkpoint 3: 🌟 Powering the Campaign + +Fuel the vision! In this step, you will learn how to mint tokens and fund the crowdfunding campaign. Minting tokens in a crowdfund dapp, while not always required, serves as a bootstrapping mechanism for the campaign, allowing the campaign to be funded with the minted tokens. + + + + + + + + + +#### Open Dapp and Mint + +Open the dapp frontend and click on the "Mint 100 ABND" button. + + + + + + + +#### Approve Transaction + +You should see a popup from Freighter asking you to sign the transaction. Click on "Approve" and wait for the transaction to be confirmed. + + + + + + + +#### Check Updated Balance + +You should see an updated balance in the pledge component. + + + + + + + + + + + + + + + +#### Fund the Campaign + +Now that you have your wallet set up, let's fund the crowdfunding campaign. Open the frontend and click on the "Back this project" button. You should see a popup from Freighter asking you to sign the transaction. + + + + + + + +#### Approve Transaction + +Click on "Approve" and wait for the transaction to be confirmed. Once the transaction is confirmed, you should see a success message. + + + + + + + +#### Check Updated Pledged Amount + +You should see an updated balance reflecting the amount you have pledged in the pledge component. + + + + + + + + + + + +> Note: These are test tokens for use with Futurenet and cannot be used on Mainnet. + + + Funding completed + + +## Checkpoint 4: 🚢 Ship it! 🚁 + +Now that your dapp is fully functional, its time to deploy it to a production environment. In this step, you will learn how to deploy your dapp to Vercel, a cloud platform for static sites that offers a quick and effective way to deploy the frontend of your dapp. This section requires that you have a [Vercel account] and the Vercel cli installed. + +If you don't have the Vercel cli installed, run the following command to install it globally: + +```sh +npm i --global vercel +``` + +[Vercel account]: https://vercel.com/login + +Next, you will need to remove the `target` directory to save space for the the deployment. Run the following command to remove the `target` directory: + +```sh +rm -rf target +``` + +> Note: You can build this directory again by running `soroban contract build` in the `contracts/abundance` directory. + +Then, remove any existing `.vercel` directory in your project to ensure that you are starting with a clean slate: + +```bash +rm -rf .vercel +``` + +Then, run the following command to deploy your example dapp: + +```bash +vercel --prod +``` + +Vercel will prompt you to link your local project to a new Vercel project. Follow the answers to the prompts below to ensure that your local project is correctly linked to a new Vercel project: + +```bash +? Set up “~/Documents/GitHub/test/soroban-dapps-challenge”? [Y/n] y +? Which scope should contain your project? +? Link to existing project? [y/N] n +? What’s your project’s name? +? In which directory is your code located? ./ +``` + +Then, continue through the prompts (you will not need to modify any settings) until you reach the completion prompt similar to the following: + +```sh +🔗 Linked to julian-dev28/soroban-example-dapp (created .vercel) +🔍 Inspect: https://vercel.com/julian-dev28/soroban-example-dapp/C1YZVQSqh6WcyR1uvxz8q2tW1tjD [2s] +✅ Production: https://soroban-example-dapp-rho.vercel.app [42s] +``` + +:::tip + +Please, save your production url, you will need it to complete the challenge. + +::: + +You can now visit the preview link to see your deployed dapp! 🎉 + + + +Remember, you must add Futurenet network lumens to your Freighter wallet to interact with the deployed example dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create your Freighter account on Futurenet. + +## Checkpoint 5: 💪 Pass the Challenge! + +Now it's time to submit your work! + +Submit your `Production` URL from the previous step into the challenge form to pass the challenge! + + + +
    + +:::info + +Join [our Community in Discord](https://discord.gg/stellardev) in case you have any issues or questions. + +::: + +## Checkpoint 6: ✅ Check your work! + +In order to successfully complete this challenge, your work needs to be checked. Please, follow the steps below: + +1. Fork [the challenge repository](https://github.com/stellar/soroban-dapps-challenge/fork). +2. Fill the `crowdfund/challenge/output.txt` file with your wallet address. The filled file should look as follows: + +```sh +Public Key: GBSXUXZSA2VEXN5VGOWE5ODAJLC575JCMWRJ4FFRDWSTRCJ123456789 +``` + +3. Create a Pull Request to the `stellar/soroban-dapps-challenge/crowdfund` branch. When the PR is created, CI actions will check the `crowdfund/challenge/output.txt` file data and update your progress. +4. Wait for the CI/CD pipeline results. +5. Fix errors if present: + +- find the error reason in the Crowdfund challenge CI results (you can find a link right in the pull request); +- return to your forked repository; +- fix errors and commit changes. The existing PR will be checked again. + +6. If the pipeline was successful, then congratulations! You completed the challenge!👏 + +Invite a friend to try out your dapp and ask them to provide feedback! + +## ⚔️ Side Quests + +🌐 Explore [Stellar Laboratory] to inspect your account assets on Futurenet. + +You should see something like this: + +```json + "balances": [ + { + "balance": "100.0000000", + "limit": "922337203685.4775807", + "buying_liabilities": "0.0000000", + "selling_liabilities": "0.0000000", + "last_modified_ledger": 148108, + "is_authorized": true, + "is_authorized_to_maintain_liabilities": true, + "asset_type": "credit_alphanum4", + "asset_code": "ABND", + "asset_issuer": "GBUGENT4FK4Y6FZNZEEGVZJLCHKNES23FRVOPPOI62RUF4WRSNOTSZV4" + }, + ] +``` + +[Stellar Laboratory]: https://laboratory.stellar.org/#explorer?network=futurenet + +## 📚 User Workflows Checklist + +During this exercise you should be able to: + +- Clone the example repo (Crowdfund Dapp) +- Choose your target amount and deadline +- Deploy your contract to Futurenet +- Deploy the example web ui somewhere (e.g. netlify, vercel, surge, etc.) + +Then via the web UI, you should be able to: + +- Connect your wallet +- See your current balance(s) +- See the current fundraising status (total amount & time remaining) +- See allowed assets +- Deposit an allowed asset +- See your deposit(s) appear on the page as the transactions are confirmed. +- "Live"-Update the page with the total amount with the new amount + +## 🛡️🗡️ Take On More Challenges + +View your progress and take on more challenges by visiting your [User Dashboard!](../dashboard) diff --git a/src/pages/docs/learn/interactive/dapps/challenges/challenge-1-payment.mdx b/src/pages/docs/learn/interactive/dapps/challenges/challenge-1-payment.mdx new file mode 100644 index 000000000..efe7faaca --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/challenges/challenge-1-payment.mdx @@ -0,0 +1,457 @@ +--- +title: Payment Dapp Challenge +description: Take on the challenge and master the Soroban Payment Dapp! +--- + +import connect_freighter from "@site/static/img/connect_freighter.png"; +import freighter_settings from "@site/static/img/freighter_settings.png"; +import add_token from "@site/static/img/add_token.png"; +import new_token from "@site/static/img/new_token.png"; +import manage_assets from "@site/static/img/manage_assets.png"; +import added_balance from "@site/static/img/added_balance.png"; +import aucb_balance from "@site/static/img/aucb_balance.png"; +import pmt_dest from "@site/static/img/pmt_dest.png"; +import next from "@site/static/img/next.png"; +import choose_token from "@site/static/img/choose_token.png"; +import select_token from "@site/static/img/select_token.png"; +import payment_settings from "@site/static/img/payment_settings.png"; +import confirm_pmt from "@site/static/img/confirm_pmt.png"; +import submit_pmt from "@site/static/img/submit_pmt.png"; +import end from "@site/static/img/end.png"; +import balance_rcvr from "@site/static/img/balance_rcvr.png"; +import { ParentChallengeForm } from "@site/src/components/atoms/challenge-form"; +import { ParentChallengeContractForm } from "@site/src/components/atoms/challenge-contract-form"; +import CompleteStepButton from "@site/src/components/atoms/complete-step-button"; +import StartChallengeButton from "@site/src/components/atoms/start-challenge-button"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import "./styles.css"; + + + +This challenge will guide you through the process of setting up, customizing, and deploying a Soroban Payment dapp, a blockchain-powered payment application designed to work with the Freighter wallet. Payment dapps are powerful because they offer users equitable and accessible means to send and receive payments. Transactions via a payment dapp are peer-to-peer, which means that no central authority, third-party, or bank oversees or controls the payment. This decentralization reduces payment fees, which are comparatively minimal on a blockchain, and transaction time, which is, via a payment dapp, almost instantaneous. What's more, the wallet integration in a payment dapp, like Freighter in this case, means that anyone with a smartphone and the wallet installed can use the payment dapp, no matter where they are in the world. + +## Checkpoint 0: 📦 Install Dependencies + +Before you begin, ensure you have the following installed on your system. You'll also want to be sure you have the most updated versions of Rust and Soroban installed. + +- `soroban-cli`: [Install soroban-cli](/docs/smart-contracts/getting-started/setup#install-the-soroban-cli) +- `Node` (>=16.14.0 < 17.0.0): [Download Node](https://nodejs.org/en/download/) +- `Yarn` (v1.22.5 or newer): [Install Yarn](https://yarnpkg.com/getting-started/install) +- `Freighter Wallet`: [Freighter Wallet](https://freighter.app/) + +Node and Yarn are package managers that let you install and manage dependencies during the dapp development process. Freighter is the wallet you will integrate into your payment dapp. + +## Checkpoint 1: 🚀 Clone the Repository + +Clone and set up the Soroban Dapps Challenge repository, which contains the Soroban Payment Dapp files. Then run yarn to install the dependencies. + +```bash +git clone https://github.com/stellar/soroban-dapps-challenge.git +cd soroban-dapps-challenge +git checkout payment +yarn +``` + +## Checkpoint 2: 🎬 Deploy Smart Contracts + +For this step you will need to clone and deploy the Soroban token smart contract from the [Soroban Examples repository](https://github.com/stellar/soroban-examples/tree/v20.0.0/token). This Soroban token smart contract, broken into several smaller modules (as is the custom for complex smart contracts like this one), enables you to create and manage tokens on Soroban. + +The Soroban token is a custom token that will be used to facilitate payments in the Payment Dapp. Tokens are essentially programmable assets on a blockchain, and smart contracts provide the automation and rules for these tokens. They allow for predefined conditions and actions related to the tokens, such as issuance, transfer, and more complex functions, ensuring the execution of these operations without the need for intermediaries. In the case of this Payment Dapp, you will use the Soroban token to initialize and mint "Demo Token" assets, or DT, that you can then use to make payments via the Payment Dapp. + +:::info Soroban Tokens are not the same as [Stellar Asset Contracts](https://soroban.stellar.org/docs/advanced-tutorials/stellar-asset-contract) which allow users to use their Stellar native asset balances in Soroban. If you are curious about the mechanics of Soroban Tokens and Stellar Asset Contracts, you can read more about them in the [Soroban Token Playground](https://token-playground.gitbook.io/guide/). ::: + +In a new terminal window, follow the steps below to build and deploy the token smart contract: + +```bash +git clone https://github.com/stellar/soroban-examples.git +cd soroban-examples/token +make +``` + +This will build the smart contracts and put them in the `token/target/wasm32-unknown-unknown/release` directory. + +Next, you will need to deploy the token smart contract to Futurenet. In order to deploy to future, you will need a Stellar account keypair (a public key and its corresponding secret key). Keep in mind that Freighter, where you can create and view your account's public key, intentionally does not allow you or any application to access your secret key. It's recommended therefore to generate a new Futurenet keypair using [Stellar Laboratory](https://laboratory.stellar.org/#account-creator?network=futurenet), fund the account, and then import the keypair's public key into your Freighter wallet. + +Once you have done this and are ready to deploy the token smart contract to Futurenet, open a terminal in the `soroban-examples/token` directory and follow the steps below: + +```bash +soroban contract deploy \ + --wasm target/wasm32-unknown-unknown/release/soroban_token_contract.wasm \ + --source \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' +``` + +Deploying the token contract to Futurenet will return a contract ID that you will need to use in the next step to invoke the token smart contract and initialize the Soroban token as "Demo Token": + +```bash +soroban contract invoke \ + --id \ + --source-account \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' \ + -- initialize \ + --admin \ + --decimal 7 \ + --name "Demo Token" \ + --symbol "DT" +``` + +Lets take a look at what is happening here: + +- admin: This is the public key of the administrator account and corresponds to the secret key you used to deploy the contract in the previous step. It is the "master" account that has control over the token contract. + +- decimal: This decimal precision value is set to 7. This value indicates that your token will have 7 decimal places, providing fine-grained control and flexibility in transactions. + +- name: This value is set to "Demo Token," the name of your token written as a string. + +- symbol: Your token symbol is a short string that represents your token, in this case, "DT." + +Next, you will need to mint some tokens to your sender's account (the administrator account you used to deploy the contract and initialize the token above). To do this, run the following command: + +```bash +soroban contract invoke \ + --id \ + --source-account \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' \ + -- mint \ + --to \ + --amount 1000000000 +``` + +This will mint 100 DT tokens to the `to` address. You can check any address' balance by running the following command: + +```bash +soroban contract invoke \ + --id \ + --source-account \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' \ + -- balance \ + --id +``` + + + +## Checkpoint 3: 🖥️ Launch the Frontend + +In this checkpoint, you will make sure that the frontend of the Payment Dapp successfully communicates with the backend, allowing transactions to be created, signed, and submitted to the network. + +Open a terminal in the `soroban-dapps-challenge` directory and run the following command to launch the frontend of your dapp: + +```bash +yarn start +``` + +You should see the following output: + +```bash +$ webpack-dev-server --config config/webpack.dev.js + [webpack-dev-server] Project is running at: + [webpack-dev-server] Loopback: http://localhost:9000/ +... +``` + +Now open your browser and navigate to [`http://localhost:9000`](http://localhost:9000). You should see the Payment Dapp running in your browser. + + + connect + + +## Checkpoint 4: 🚀 Token Transfer Odyssey + +Strap in and get ready to send some tokens! In this step, you will use the Payment Dapp to send Soroban tokens to another account. + + + + + + + + +#### Add Soroban Token + +To add the newly minted DT token type to your wallet, open your Freighter wallet and click on the `Manage Assets` button at the bottom of the screen. + +manage assets + +Then click on the `Add Soroban token ` button and enter the token contract ID that was returned when you deployed the token smart contract. + +add token +
    +new token +
    + + + +#### Check Token Addition + +You should now see the Soroban token in your Freighter wallet. + +added balance + + +
    +
    + + + + + + +#### Connect Freighter and Select Account + +Back on your dapp's frontend webpage, make sure Freighter is connected and then select the account that will be used to send Soroban tokens. Click "next" to continue. + +next + + + + +#### Provide Token Transfer Details + +To send DT tokens via the Payment dapp, provide the public key of the account that will receive the Soroban tokens. (This could be another of your own Freighter accounts.) + +payment destination + +Input the token ID of the Soroban token. + +choose token + +Input the amount of Soroban tokens to send. + +select token + +Confirm the payment settings, which include the option to add a memo and show the transaction fee. + +payment settings + + + + +#### Confirm and Submit Transaction + +Review the transaction details to ensure accuracy and then click "Sign with Freighter". Freighter will prompt you to sign the transaction with your wallet's private key. + +confirm payment + +Once signed, click "Submit Payment." The transaction will be submitted to the network. + +submit payment + +The Payment Dapp will show a confirmation message once the transaction has been successfully submitted. This includes the XDR response, which can be decoded using [stellar laboratory](https://laboratory.stellar.org/#xdr-viewer?type=TransactionResult&network=futurenet). + +end + +You can now check the balance of the receiving account to ensure that the transaction was successful. + +balance receiver + +As stated before, you can also check the balance of an account with the soroban-cli by running the following command: + +```bash + soroban contract invoke \ + --id \ + --source-account \ + --rpc-url https://rpc-futurenet.stellar.org:443 \ + --network-passphrase 'Test SDF Future Network ; October 2022' \ + -- balance \ + --id +``` + +Output: + +```bash +"1000000000" +``` + + + + + +
    + + + Tokens Sent + + +## Checkpoint 5: 🚢 Ship it! 🚁 + +In this step, you will deploy your dapp to a hosting platform so that it can be accessed by anyone with an internet connection. You can use any hosting platform you like, but for demonstration purposes, this section will use [Vercel](https://vercel.com/). Vercel is a cloud platform for static sites and serverless functions that offers a free tier for developers. It also has a built-in integration with GitHub, which makes it easy to deploy your dapp directly from your GitHub repository. + +If you dont already have a [Vercel account], you will need to create one and link it to your GitHub account. + +[Vercel account]: https://vercel.com/login + +First install the Vercel cli: + +```bash +npm i --global vercel +``` + +Then, remove any existing `.vercel` directory in your project to ensure that you are starting with a clean slate: + +```bash +rm -rf .vercel +``` + +Next, you will need to create a new project on vercel. To do this, run the following command: + +```bash +vercel project add +``` + +For example: + +```bash +vercel project add soroban-react-payment +``` + +Next you will pull in the project settings locally by running the following command: + +```bash +vercel pull +``` + +Follow the answers to the prompts below to ensure that your local project is correctly linked to the target Vercel project: + +```bash +? Set up “~/Documents/GitHub/test/soroban-dapps-challenge”? [Y/n] y +? Which scope should contain your project? +? Link to existing project? [y/N] y +? What’s the name of your existing project? +``` + +After following the prompts, you should see something similar to the following output: + +```bash +... +🔗 Linked to julian-dev28/pmt-dapp (created .vercel) +> Downloading `development` Environment Variables for Project pmt-dapp +✅ Created .vercel/.env.development.local file [92ms] + +> Downloading project settings +✅ Downloaded project settings to ~/Documents/GitHub/test/soroban-dapps-challenge/.vercel/project.json [1ms] +``` + +Next, you will need to edit the `settings` section in `.vercel/project.json` to ensure that the `outputDirectory` is set to `build`: + +```diff + "settings": { + "createdAt": 1699390700432, + "framework": null, + "devCommand": null, + "installCommand": null, + "buildCommand": null, +- "outputDirectory": null, ++ "outputDirectory": "build", + "rootDirectory": null, + "directoryListing": false, + "nodeVersion": "18.x" + } +``` + +Next, run the following command to build your dapp: + +```bash +vercel build --prod +``` + +What does the `vercel build` command do? It builds your dapp for production, which means that it optimizes your code for performance and creates an optimized production build of your dapp in the `.vercel/output` directory. This is the directory that you will deploy to Vercel. + +The output of the `vercel build` command should look something like this: + +```bash +.. +$ webpack --config config/webpack.prod.js +asset 8a7edf3024865247d470.js 1.73 MiB [emitted] [immutable] [minimized] (name: index) 1 related asset +... +webpack compiled in 12408 ms (be8ba6cc95f4aec4d07b) +✨ Done in 13.16s. +✅ Build Completed in .vercel/output [14s] +``` + +Next, you will deploy your dapp to Vercel by running the following command: + +```bash +vercel deploy --prebuilt --prod +``` + +Using the `--prebuilt` flag tells Vercel to deploy the the build outputs in `.vercel/output` that you created in the previous step. + +Once the deployment is complete, you should see something similar to the following output: + +```bash +🔍 Inspect: https://vercel.com/julian-dev28/soroban-react-payment/9PwV2DvuXJ3FWag7eLbjqNAhCeCu [2s] +✅ Production: https://soroban-react-payment-ahtko9qd1-julian-dev28.vercel.app [2s] +``` + +:::tip + +Please, save your production url, you will need it to complete the challenge. + +::: + +You can now visit the preview link to see your deployed dapp! 🎉 + +Remember, you must add Futurenet network lumens to your Freighter wallet to interact with the deployed example dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create your Freighter account on Futurenet. + +## Checkpoint 6: ✅ Complete the Challenge! + +Now it's time to submit your work! + +Submit your `Production` URL from the previous step into the challenge form to pass the challenge! + + + +
    + +:::info + +Join [our Community in Discord](https://discord.gg/stellardev) in case you have any issues or questions. + +::: + +## Checkpoint 7: 💪 Share Your Accomplishment with the Community + +Don't forget to share your work with the community. Let others see what you've accomplished, receive feedback, and inspire others! + +## ⚔️ Side Quests + +🍴[Fork the Example Soroban Payment Dapp repo] and make your own changes to your Dapp. + +Consider customizing the code and submitting a pull request for the challenge. You can explore advanced features of the Example Soroban Payment Dapp, and Freighter wallet to take your skills to the next level. Show your creativity by adding unique functionalities, enhancing the user interface, or integrating with other APIs or services. Good luck! + +[Fork the Example Soroban Payment Dapp repo]: https://github.com/stellar/soroban-react-payment + +## 📚 User Workflows Checklist + +To ensure that you've covered all the key user actions during the challenge, follow this checklist: + +- Clone the repository +- Install dependencies +- Deploy and initialize the token smart contract +- Mint tokens to your account +- Launch the local frontend +- Add the Soroban token to Freighter +- Connect Freighter to the application +- Send tokens to another account +- Deploy the site with Vercel +- Submit your public key and URL + +## 🛡️🗡️ Take On More Challenges + +View your progress and take on more challenges by visiting your [User Dashboard!](../dashboard) diff --git a/src/pages/docs/learn/interactive/dapps/challenges/challenge-2-liquidity-pool.mdx b/src/pages/docs/learn/interactive/dapps/challenges/challenge-2-liquidity-pool.mdx new file mode 100644 index 000000000..15fbd6c69 --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/challenges/challenge-2-liquidity-pool.mdx @@ -0,0 +1,459 @@ +--- +title: Liquidity Pool Dapp Challenge +description: Deploy an example dapp, add liquidity! Beat the Challenge! +--- + +import mint_tokens from "@site/static/img/mint_tokens.png"; +import approveTokenMint from "@site/static/img/approveTokenMint.png"; +import updatedBalances from "@site/static/img/updatedBalances.png"; +import deposit50 from "@site/static/img/deposit50.png"; +import updatedBalances50 from "@site/static/img/updatedBalances50.png"; +import swap from "@site/static/img/swap.png"; +import swapComplete from "@site/static/img/swapComplete.png"; +import futurenetDeployment from "@site/static/img/futurenetDeployment.png"; +import wdraw from "@site/static/img/wdraw.png"; +import { ParentChallengeForm } from "@site/src/components/atoms/challenge-form"; +import { ParentChallengeContractForm } from "@site/src/components/atoms/challenge-contract-form"; +import CompleteStepButton from "@site/src/components/atoms/complete-step-button"; +import StartChallengeButton from "@site/src/components/atoms/start-challenge-button"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import "./styles.css"; + + + +A liquidity pool is a collection of tokens or digital assets deposited by users and held in a smart contract or dapp that can be used to provide essential liquidity to decentralized exchanges (DEXs) and other decentralized finance (DeFi) protocols. Since liquidity plays a crucial role in enabling DeFi systems, liquidity pools, where assets can be held, lent, borrowed, swapped, or traded (depending on dapp functionality), are fundamental to these systems. + +The functionality of this liquidity pool dapp will allow users to mint tokens, deposit liquidity, swap between asset types, and withdraw funds from the liquidity pool. This dapp challenge will walk you through the step-by-step process of creating and launching a liquidity pool dapp on Stellar using Soroban smart contracts. You will learn how to deploy smart contracts to a sandbox environment and interact with them through a web frontend. In this context, the term "ship" refers to finalizing the development process of your dapp, ensuring that it functions as expected and is accessible for user interaction and testing through a hosted frontend. Despite the end-to-end functionality of this challenge, this dapp is not promoted nor intended for deployment in a production-level setting on Futurenet, but rather is designed for educational purposes. + +## Checkpoint 0: 📦 Install 📚 + +Start by installing the required dependencies. You'll also want to be sure you have the most updated version of Rust installed. + +Required: + +- `soroban-cli alias` (installed in the next step) +- `Node` v18: [Download Node](https://nodejs.org/en/download/) +- `Freighter Wallet`: [Freighter Wallet](https://freighter.app/) + +First, clone the Soroban example dapp repo and navigate to the `liquidity-pool` directory: + +```bash +git clone https://github.com/stellar/soroban-dapps-challenge +cd soroban-dapps-challenge +git checkout liquidity-pool +``` + +Then, install soroban-cli alias by running the following command: + +```sh +cargo install_soroban +``` + +Soroban CLI is the command line interface to Soroban. It allows you to build, deploy, and interact with smart contracts, configure identities, generate key pairs, manage networks, and more. The soroban-cli alias that is used in this challenge is a pinned version of the soroban-cli that is used in the Soroban Dapps Challenge. Using the soroban-cli alias ensures that the challenge is reproducible and that all participants are using the same version of Soroban. + +## Checkpoint 1: 🎬 Deploy Smart Contracts + +Deploying a smart contract in a production setting involves submitting the contract code to the blockchain's main network (Mainnet), where it becomes part of the chain's immutable ledger. When you deploy the smart contracts in this challenge, you'll instead deploy to Futurenet, a test network with more cutting-edge features that have not yet been implemented in the Mainnet. Deploying smart contracts to a sandbox environment simulates the production-level deployment process without actually affecting Mainnet. + +Now that you have the Liquidity Pool branch checked out, load the contracts and initialize them in the sandbox environment by running the following commands in your terminal: + +```bash +./initialize.sh futurenet +``` + +If the command runs successfully, your terminal will return a series of messages notifying you about the successful initialization of the contracts and the post-installation sequence. + +```bash +Contract deployed successfully with ID: CBXHU5BWWTOCZRYX3DMSSKCFG7B3K2YG2I5F75ALPQ6GCY6ZES2XKLTI +Deploy the liquidity pool contract +Contract deployed successfully with ID: CBKY7UN5VGD4LIQFOBOTSUSQWK67BZZTA23NIEVWSWRR5SAT26JQN2BN +Initialize the abundance token contract + +Initialize the liquidity pool contract + +Done + +> soroban-example-dapp@0.1.0 build-contracts +... +``` + +The contract ID is a unique identifier for a smart contract deployed on a blockchain. This contract ID is used to interact with and reference the smart contract, allowing users to invoke functions from the smart contract, send transactions, or otherwise interact with the smart contract's functionalities and data stored on the blockchain. + +:::tip + +Please, save your deployed contract ID. You will need it to complete the challenge. + +::: + + + +## Checkpoint 2: 🤝 Connect the Frontend to the Backend + +Now that you have deployed the smart contract, it's time to check out the frontend of your dapp. The frontend is the browser interface where users will connect their digital wallets to make deposits into and withdrawals from the liquidity pool. The frontend is also where users will be able to see their balances and swap tokens. + +Because interacting with dapps requires both backend and frontend development, the Soroban Dapps Challenge includes the functionality to easily deploy a frontend interface of the dapps. Building out the frontend from scratch would typically involve creating a user interface (UI) and user experience (UX) design, as well as writing the code for the frontend. In this challenge, you will use the frontend that is already built for you. + +To set up the development server, navigate to the `frontend` folder of the soroban-dapps-challenge repository and run the following command: + +```bash +make setup && make start_dev +``` + +> Note: This may require admin privileges on some systems. + +Now open your browser and visit [http://localhost:5173](http://localhost:5173/). You should be able to see the frontend of your dapp. + +> Note: Follow the instructions below and ensure that you have funded your wallet address that you intend to use from browser. + +Now that you have the frontend running, it's time to connect it with the backend, your smart contract, that defines the rules and logic of the liquidity pool, including the token swap and liquidity pool functions. + +You will need to add some Futurenet network lumens to your wallet to interact with the dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create and or fund an account on Futurenet. Remember, these are test lumens for use on Futurenet and cannot be used on Mainnet. + +## Checkpoint 3: 🌊 Dive into the Liquidity Pool + +Embark on a tidal journey! In this step you will mint, deposit, swap, and withdraw tokens from the liquidity pool. Minting tokens, depositing liquidity, swapping between asset types, and withdrawing funds from the liquidity pool constitute the basic lifecycle of interacting with a DeFi protocol. + +In the context of liquidity pools, depositing and withdrawing assets involve connecting a digital wallet and submitting deposit/withdraw transactions. In this liquidity pool dapp challenge specifically, you will also need to mint the test tokens that you will then use to make deposits to the liquidity pool. (This minting of test tokens is different from liquidity pools where depositors may receive minted tokens in exchange for their deposited funds.) Perhaps one of the most important actions a user can take through liquidity pool dapps is swapping tokens. In fact, the ability to swap from one asset to another through a liqudity pool is a powerful feature of DeFi protocols: instead of exchanging assets through traditional financial institutions and intermediaries, users can have direct access to decentralized asset exchange via liquidity pool dapps, without needing a bank account or other traditional financial instruments. + + + + + + + + + +#### Mint USDC and BTC + +In order to use this liquidity pool dapp, you will need to mint test tokens which can then be used to make deposits and swaps via the frontend of the dapp. To mint USDC and BTC test tokens, open the dapp frontend and click the "MINT" button for USDC and BTC. + + + + + + +#### Approve Transaction + +You should see a popup from Freighter asking you to sign the transactions. Click on "Approve" and wait for the transaction to be confirmed. + + + + + + +#### Check Updated Balance + +You should see an updated balance in the account balance component. + + + + + + + + + + + + + + +#### Deposit into the Liquidity Pool + +Depositing assets into the liquidity pool involves users submitting deposit transactions via the frontend to deposit tokens from their wallet into the liquidity pool. In this dapp you will make a deposit of two asset types in order to swap between those asset types. In other DeFi protocols, users may also deposit liquidity into a liquidity pool in order to earn yields on their deposits. The intial deposit of liquidity into a liquidity pool is what sets the initial price of the tokens in the pool. For example, if a user deposits 37000 USDC and 1 BTC, the price of each BTC token will be 37000 USDC. + +Open the frontend, enter the desired token amounts, and click the "Deposit" button. You should see a popup from Freighter asking you to sign the transaction. + + + + + + +#### Approve Transaction + +Click on "Approve" and wait for the transaction to be confirmed. Once the transaction is confirmed, you should see your balances updated. + + + + + +#### Check Updated Balance + +You should see an updated balance in the amounts you have deposited in the account and reserve balance components, respectively. Following the example, you should see 50 USDC, 50 BTC, and 50 POOL. + + + + + + + + + + + + + + + +#### Swap Tokens + +Now that you have funded the liquidity pool, you can make a swap to easily exchange one token for another. Swaps in a liquidity pool usually depend on the relationship between two or more different tokens that can be exchanged with each other. Typical liquidity pools rely on a mathematical formula that determines the price of the tokens within the pool. With every deposit and withdraw transaction to or from the pool, the formula adjusts the token price of each token based on this formula. When a swap occurs, the liquidity pool uses the formula and the balances of each token with the pool to determine the swap value of each token relative to the others within the pool. + +This liquidity pool dapp challenge uses a [specific formula] in its smart contracts to enable swapping between tokens while ensuring that the liquidity pool remains balanced and that liquidity providers are compensated for their contributions to the pool (.3% of swap amount are sent to liquidity providers). Since this liquidity pool dapp currently only holds the two tokens you deposited in the previous step, the formula will reflect those token balances: the price of each token will be be relative to the amount of the other token in the pool. For example, if you deposited 50 USDC and 50 BTC, the price of each token will be 1:1. If you deposited 100 USDC and 50 BTC, the price of each token will be 2:1. + +[specific formula]: https://github.com/stellar/soroban-dapps-challenge/blob/f7cde6fc6cfce5470ebab9b7489c367c3306317f/contracts/liquidity-pool/src/lib.rs#L251 + +Also important to note is how slippage works in this dapp. Slippage refers to the maximum variation percentage accepted for the desired deposit amounts. The higher the percentage, the greater the chance of a successful transaction, but you may not get such a good price. Here, users can set the max slippage to their desired amount. + +To complete a swap between USDC and BTC test tokens, open the swap tab of the frontend, input the desired token swap amounts, and click the "Swap" button. You should see a popup from Freighter asking you to sign the transaction. + + + + + + + +#### Approve Transaction + +Click on "Approve" and wait for the transaction to be confirmed. + + + + + +#### Check Updated Balance + +Once the transaction is confirmed, you should see updated balances on the frontend. + + + + + + + + + + + + + + + +#### Withdraw Tokens from the Liquidity Pool + +Now that you have swapped tokens through the liquidity pool, you can make a withdrawal of your funds. + +The "Pool Share" slider in this dapp refers to the slider for the percentage of liquidity you want to withdraw from the pool. For example, if you have 100 USDC and 100 BTC in the pool, and you want to withdraw 50% of your liquidity, you would select 50% on the slider. The withdrawal amounts are relative to the estimated price of the tokens in the pool base on the conversion ratio set in the Deposit that created the liquidity pool. For example if a user deposits 50 USDC and 1 BTC, the price of USDC will be 50:1 BTC. If the user then withdraws 50% of their liquidity, they will receive 25 USDC and 0.5 BTC. The price of BTC in the pool will then be recalculated based on the token supply in the pool, potentially causing slippage and resulting in a price different from the original 50:1 ratio. + +Open the withdraw tab, select how much liquidity you want to remove with the sliding bar, and click the "Withdraw" button. You should see a popup from Freighter asking you to sign the transaction. + + + + + + + +#### Approve Transaction + +Click on "Approve" and wait for the transaction to be confirmed. + + + + + +#### Check Updated Balance + +Once the transaction is confirmed, you should see updated balances on the frontend. + + + + + + + + + +> Note: These are test tokens for use on Futurenet or Mainnet. + + + Transactions completed + + +## Checkpoint 4: 🚢 Ship It! 🚁 + +Now that your dapp is fully functional, its time to deploy it to a production environment. In this step, you will learn how to deploy your dapp to Vercel, a cloud platform for static sites that offers a quick and effective way to deploy the frontend of your dapp. This section requires that you have a [Vercel account] and install the Vercel CLI. + +[Vercel account]: https://vercel.com/login + +First, you will remove the target directory, as it is not used by Vercel to deploy your site. To do this, navigate to the `liquidity-pool` directory and run the following: + +``` +rm -rf target +``` + +> Note: You can build this directory again by running `soroban contract build` in the `contracts/abundance` directory. + +Next, you must move your `.soroban` directory to the frontend directory. + +From a terminal in the `liquidity-pool` directory, run the following command: + +```bash +mv .soroban frontend/.soroban +``` + +Then, you need to update the `package.json` file in the `frontend` directory to point to the new contract binding locations. + +```diff +-"liquidity-pool-contract": "file:../.soroban/contracts/liquidity-pool", +-"share-token-contract": "file:../.soroban/contracts/share-token", +-"token-a-contract": "file:../.soroban/contracts/token-a", +-"token-b-contract": "file:../.soroban/contracts/token-b", + ++"liquidity-pool-contract": "file:.soroban/contracts/liquidity-pool", ++"share-token-contract": "file:.soroban/contracts/share-token", ++"token-a-contract": "file:.soroban/contracts/token-a", ++"token-b-contract": "file:.soroban/contracts/token-b", +``` + +Next, you will use the Vercel CLI to complete your deployment. + +First, install the Vercel CLI: + +```bash +npm i --global vercel +``` + +Then, remove any existing `.vercel` directory in your project to ensure that you are starting with a clean slate: + +```bash +rm -rf .vercel +``` + +Then, run the following command to deploy your example dapp: + +```bash +vercel --prod +``` + +Vercel will prompt you to link your local project to a new Vercel project. Follow the answers to the prompts below to ensure that your local project is correctly linked to a new Vercel project: + +```bash +? Set up “~/Documents/GitHub/test/soroban-dapps-challenge”? [Y/n] y +? Which scope should contain your project? +? Link to existing project? [y/N] n +? What’s your project’s name? +? In which directory is your code located? ./ +``` + +Then, continue through the prompts until you see the following message regarding setting overrides: + +```bash +? Want to override the settings? [y/N] y +? Which settings would you like to override? (Press to select, to toggle all, to invert selection) +❯◯ Build Command + ◯ Development Command + ◯ Output Directory +``` + +Select each entry (type "a") and set the following values: + +**build command** + +```bash +cd frontend && make build +``` + +**development command** + +```bash +cd frontend && make start_dev +``` + +**output directory** + +```bash +frontend/dist +``` + +Once the deployment is complete, you should see a completion message similar to the following: + +```bash +🔗 Linked to julian-dev28/liquidity-pool (created .vercel) +🔍 Inspect: https://vercel.com/julian-dev28/liquidity-pool/FfsAJdgUR9LKH5EmGiuMCMYUMTi2 [2s] +✅ Production: https://liquidity-pool.vercel.app [54s] +``` + +:::tip + +Please, save your production url, you will need it to complete the challenge. + +::: + +You can now visit the preview link to see your deployed dapp! 🎉 + + + +Remember, you must add Futurenet network lumens to your Freighter wallet to interact with the deployed example dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create your Freighter account on Futurenet. + +## Checkpoint 5: ✅ Complete the Challenge! + +Now it's time to submit your work! + +Submit your `Production` URL from the previous step into the challenge form to pass the challenge! + + + +## Checkpoint 6: 💪 Flex! + +🍴 [Fork the Soroban Dapps Challenge repo] and make your own changes to the Liquidity Pool branch. + +Customize the code and submit a pull request for the Liquidity Pool Dapp Challenge. You can experiment with new fee strategies, improve the user interface, or integrate additional token pair options. + +Take this opportunity to showcase your skills and make your mark on the Liquidity Pool Dapp. Good luck! + +[Stellar Laboratory]: https://laboratory.stellar.org/#explorer?network=futurenet +[Fork the Soroban Dapps Challenge repo]: https://github.com/stellar/soroban-dapps-challenge/fork + +## 📚 User Workflows Checklist + +During this exercise, you should be able to: + +- Clone the example repo (Liquidity Pool Dapp) +- Deploy your contract to a sandbox environment. +- Deploy the example web UI somewhere (e.g., Netlify, Vercel, Surge, etc.) + +Then, via the web UI, you should be able to: + +- Connect your wallet +- See your current balance(s) +- Mint assets +- Deposit assets +- Swap assets +- Withdraw assets +- See your transaction(s) appear on the page as the transactions are confirmed + +## 🛡️🗡️ Take On More Challenges + +View your progress and take on more challenges by visiting your [User Dashboard!](../dashboard) diff --git a/src/pages/docs/learn/interactive/dapps/challenges/challenge-3-oracle.mdx b/src/pages/docs/learn/interactive/dapps/challenges/challenge-3-oracle.mdx new file mode 100644 index 000000000..1fc6bb04f --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/challenges/challenge-3-oracle.mdx @@ -0,0 +1,538 @@ +--- +title: Oracle Dapp Challenge +description: Get prices with the Oracle Dapp! Beat the Challenge! +--- + +import oracle_connect from "@site/static/img/oracle_connect.png"; +import oracle_home from "@site/static/img/oracle_home.png"; +import oracle_mintBal from "@site/static/img/oracle_mintBal.png"; +import oracle_approve from "@site/static/img/oracle_approve.png"; +import oracle_mint from "@site/static/img/oracle_mint.png"; +import oracle_calculate from "@site/static/img/oracle_calculate.png"; +import oracle_balance_contract from "@site/static/img/oracle_balance_contract.png"; +import oracle_balance_user from "@site/static/img/oracle_balance_user.png"; +import oracle_deployed from "@site/static/img/oracle_deployed.png"; +import { ParentChallengeForm } from "@site/src/components/atoms/challenge-form"; +import { ParentChallengeContractForm } from "@site/src/components/atoms/challenge-contract-form"; +import CompleteStepButton from "@site/src/components/atoms/complete-step-button"; +import StartChallengeButton from "@site/src/components/atoms/start-challenge-button"; +import "./styles.css"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + + + +This challenge will guide you through the process of building and shipping an oracle dapp on the Stellar network using Soroban. Oracle dapps (decentralized applications) provide the means for users to access real-world data and information from external sources and bring it on-chain. + +In this challenge, you will learn how to deploy smart contracts to Futurenet, and how to interact with them through a web frontend. In this context, the term "ship" refers to finalizing the development process of your dapp, ensuring that it functions as expected, and is accessible for user interaction and testing through a hosted frontend. However, it's crucial to clarify that despite its functionality, the dapp is not promoted nor intended for deployment in a production-level setting on Mainnet. The challenge is designed for educational purposes only, helping you understand how a dapp can be built and interacted with, with further customization and development, it has the potential to evolve into a full-fledged, ready-to-use oracle solution. + +## Checkpoint 0: 📦 Install 📚 + +Start by installing the required dependencies. + +Required: + +- `soroban-cli` 20.0.0-rc.4.1: [Install Soroban CLI](/docs/smart-contracts/getting-started/setup#install-the-soroban-cli) +- `Node` >=v18: [Download Node](https://nodejs.org/en/download/) +- `Freighter Wallet`: [Freighter Wallet](https://freighter.app/) + +First, clone the Soroban Dapps Challenge repo and check out the `oracle` branch, which contains the code for the oracle smart contract that powers this dapp: + +```sh +git clone https://github.com/stellar/soroban-dapps-challenge.git +cd soroban-dapps-challenge +git checkout oracle +``` + +:::tip If you haven't already installed the soroban-cli , you can do so by running the following command: + +```sh +cargo install --locked --version 20.0.0-rc.4.1 soroban-cli +``` + +::: + +Soroban CLI is the command line interface to Soroban. It allows you to build, deploy, and interact with smart contracts; configure identities; generate key pairs; manage networks; and more. + +## Checkpoint 1: 📝 Setup the `initialize.sh` Script + +The `initialize.sh` script is a shell script that will help you initialize the contracts and install dependencies. It is located in the top level directory. + +The `initialize.sh` script performs following actions: + +- Creates a new wallet to serve as the token admin +- Funds token admin wallet with test tokens +- Builds and deploys contracts using this wallet +- Creates typescript bindings +- Installs dependencies + +For this section, you'll need to configure parameters to initialize `donation` and `oracle` contracts. + +You'll start with `donation` contract: + +- First, Open `initialize.sh`: + +- Find the following part at the end of the file: + +```sh +echo "Initialize the DONATION contract" +``` + +- For the `--recipient` parameter, specify the wallet address that will be used to withdraw money from the donation contract. This may be your address. + +```sh +initialize\ +--recipient \ +``` + +Now for the `oracle` contract: + +- Find the following part of `initialize.sh`: + +```sh +echo "Initialize the ORACLE contract" +``` + +- As you can see, there are several parameters that need to be specified here. + +```sh + initialize \ + --caller \ + --pair_name BTC_USDT \ + --epoch_interval \ + --relayer +``` + +Here is a description of each parameter: + +- `` - the address that will become the owner of the contract. This may be your address. Only the contract owner will be able to change the `epoch_interval` and `relayer` +- `` - frequency (in seconds) of price updates +- `` - address of the wallet that will update the price (in the backend, in the CRON task). It is suggested to create a dedicated wallet for this ([be sure to fund with test Lumens](https://laboratory.stellar.org/#account-creator?network=futurenet)) + +## Checkpoint 2: 🎬 Deploy Smart Contracts + +Now that the initialization script is set up it's time to deploy the smart contracts to a Sandbox environment. Deploying a smart contract in a production setting involves submitting the contract code to the blockchain's main network ( Mainnet ), where it becomes part of the chain's immutable ledger. Deploying smart contracts to a Sandbox environment simulates that process without actually affecting Mainnet. When you deploy the smart contracts, you'll instead deploy to Futurenet, a test network with more cutting-edge features that have not yet been implemented in the Mainnet. + +To build and deploy the contracts in a Sandbox environment, as well as to compile the TypeScript bindings, run the following command in your terminal: + +```sh +npm run setup +``` + +> Note: This command may require write privileges in some cases. If you encounter a "Permission denied" error when running this command, run the following command to grant write privileges to the `initialize.sh` script: +> +> ```sh +> chmod +x initialize.sh +> ``` + +If the command runs successfully, your terminal will return a series of messages notifying you about the successful initialization of the contracts and the post-installation sequence. + +```sh +Deploy the BTC TOKEN contract +Contract deployed successfully with ID: CD5ZAJAPX5AAOB55G7SQEM63EQK5JRUZN2LUCDP66BRWV5HNGPDGJVDD +Deploy the DONATION contract +Contract deployed successfully with ID: CC7U75NM4FQQSPLCSX2FTUCAQ6VI6VBA5LDYSU3FRKXN3X2W3AL2SAJ3 +Deploy the ORACLE contract +Contract deployed successfully with ID: CBK3VIRHK5QGHLKFTWHJI2ABMW2SATMIDPNQSCICCDRJU6DIFEWHLDD2 +Initialize the BTC TOKEN contract + +Done +Initialize the DONATION contract + +Done +Initialize the ORACLE contract + +Done + +> soroban-oracle-project@0.0.0 build-contracts +... +``` + +:::tip + +Please, save your deployed contract ID, you will need it to complete the challenge. + +::: + + + +## Checkpoint 3: ⏯️ Create a CRON task + +A CRON task is a scheduled operation performed at specified intervals, functioning as a customizable backend service. Here, you will run a CRON task to update the BTC price within the Oracle contract. Data can be fetched from an API and set to the contract using the `set_price` function, in this case, the tutorial will use the [CryptoPrice API](https://api-ninjas.com/api/cryptoprice). This is a free API that provides real-time cryptocurrency prices, and it is used in this tutorial for demonstration purposes only. You are free to use any price feed API of your choice. + +The function that updates the price is already implemented in the `cron-script.ts` file. To retrieve the data from the price feed, you will need to specify the following parameters: + +- Secret key of wallet (relayer) that will fetch BTC price from API and set it to smart contract; +- Contract address of deployed Oracle Contract; +- `API_KEY` from https://api-ninjas.com/api/cryptoprice (for free). + +Here is an example of a filled `cron-script.ts` file: + +```ts +const API_NINJA_KEY = "xSuoJa0icEhaQzk3IdomNWFmXK0qNG8lNZofYwJ4"; + +const sourceSecretKey = + "SBDLGM7RKXU6WYM2DRDIBULWAG7I6QDO3AONLERKWZPDDPQMMQ26F7TM"; +const sourceKeypair = SorobanClient.Keypair.fromSecret(sourceSecretKey); +const sourcePublicKey = sourceKeypair.publicKey(); + +const contractId = "CBDRRNXD2UETJJL376B2VT3RS22YPF4NPWRDCMSBZAIQGWJIUM3U7552"; +``` + +To run the CRON task, navigate to the `cron` directory and run: + +```sh +npm install +node cron-script.ts +``` + +Once the CRON task is running, it will fetch the BTC price from the API and set it to the contract every 15 minutes. + +Here is an example of the output: + +```sh +Running a task every 15 minutes +Current Time: 2023-11-08T00:30:00.036Z +lastEpochNr 0 +deltaTimestamp 1699403400 +Need to update the value +fetched priceData 3531576000 +[updatePairPrice] View Transaction: https://futurenet.steexp.com/tx/5c68dd1eb6df7a926fa8fda3a3cd187d2855b74ceaa41469f69b33f81930c33a +[updatePairPrice] response.status: SUCCESS +value set! +``` + +You can specify the frequency of the CRON task by changing the `cron.schedule` parameter in the `cron-script.ts` file. + +For example, to run the task every 5 minutes, change the parameter to: + +```ts +cron.schedule("*/5 * * * *", async () => { + ... +}); +``` + +It's important to note that since the price feed is dependent on the CRON task, the price will not update until the CRON task has run at least once. Furthermore, the CRON task will need to keep running in order to keep the price feed updated. You can host the task on your localhost or on a server such as [Vercel](https://vercel.com/docs/cron-jobs). For this example, the CRON task is hosted on a local host. + +## Checkpoint 4: 🤝 Connect the Frontend to the Backend + +Now that you have the smart contracts deployed, and the cron job running, it's time to check out the frontend of your dapp. + +From the top level directory run the following command to start the development server: + +```sh +npm run dev +``` + +Now, open your browser and visit [http://localhost:5173](http://localhost:5173). You should be able to see the frontend of your dapp. + +> Note: Follow the instructions below and ensure that you have funded your wallet address that you intend to use from the browser. + +Now that you have the frontend running, it's time to connect it with your smart contracts. + +You will need to add some Futurenet network lumens to your wallet to interact with the dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create and or fund an account on Futurenet. + +> Note: These are test lumens for use with Futurenet and cannot be used on Mainnet + +## Checkpoint 5: 🧿 Oracle Insights + +Gaze into the crystal ball of APIs and retrive the price feed from the all connected internet. Data at your fingertips, at one time an abyss but pioneers have paved the way for many to cross. Here you will reach into the realm of off-chain data and pull out the price of BTC. In this section, you will mint tokens and deposit Bitcoin (BTC) into the donation contract. The conversion to USD will utilize the oracle price feed to ensure your donation is aligned with the current BTC to USD conversion rate. + + + + + + + + + +#### Connect a Wallet + +Open the dapp frontend and click the "connect" and choose the wallet you want to use. For this example, you will use Freighter. + + + connect + + + + + +#### Mint USDC and BTC + +Open the `Mint BTC Tokens` tab, enter the desired token amount, and click the "Mint" button. + + + + + + +#### Approve Transaction + +You should see a popup from Freighter asking you to sign the transactions. Click on "Approve" and wait for the transaction to be confirmed. + + + + + + +#### Check Updated Balance + +You should see an updated balance in the account balance component. + + + + + + + + + + + + + + +#### Deposit into the Donation Contract + +Open the frontend, enter the amount of BTC in USD that you would like to donate, then click the "Calculate" button. You should see the amount of BTC that you need to deposit in order to donate the specified amount in USD. For example, if you enter 35923.82 USD, you should see 1 BTC. + +connect + + + + +#### Approve Transaction + +Click on "Approve" and wait for the transaction to be confirmed. + + + + + +#### Check Updated Balance + +Once the transaction is confirmed, you should see the contract and your wallet balances update in the `Donate` and `Mint BTC Tokens` tabs, respectively. + +connect +connect + + + + + + + + + +> Note: These are test tokens for use with Futurenet and cannot be used on Mainnet. + + + Funding completed + + +## Checkpoint 6: 🚢 Ship it! 🚁 + +In this step, you will deploy your dapp to a hosting platform so that it can be accessed by anyone with an internet connection. Note that it's on futurenet, the network used for testing, not mainnet for production use. You can use any hosting platform you like, but for demonstration purposes, this section will use [Vercel](https://vercel.com/). Vercel is a cloud platform for static sites and serverless functions that offers a free tier for developers. It also has a built-in integration with GitHub, which makes it easy to deploy your dapp directly from your GitHub repository. + +If you don't already have a [Vercel account], you will need to create one and link it to your GitHub account. + +[Vercel account]: https://vercel.com/login + +First install the Vercel cli: + +```bash +npm i --global vercel +``` + +Then, remove any existing `.vercel` directory in your project to ensure that you are starting with a clean slate: + +```bash +rm -rf .vercel +``` + +Next, you will need to create a new project on vercel. To do this, run the following command: + +```bash +vercel project add +``` + +For example: + +```bash +vercel project add oracle +``` + +Next you will pull in the project settings locally by running the following command: + +```bash +vercel pull +``` + +Follow the answers to the prompts below to ensure that your local project is correctly linked to the target Vercel project: + +```bash +? Set up “~/Documents/GitHub/test/soroban-dapps-challenge”? [Y/n] y +? Which scope should contain your project? +? Link to existing project? [y/N] y +? What’s the name of your existing project? +``` + +After following the prompts, you should see something similar to the following output: + +```bash +... +🔗 Linked to julian-dev28/oracle (created .vercel) +> Downloading `development` Environment Variables for Project oracle +✅ Created .vercel/.env.development.local file [92ms] + +> Downloading project settings +✅ Downloaded project settings to ~/Documents/GitHub/test/soroban-dapps-challenge/.vercel/project.json [1ms] +``` + +Next, you will need to edit the `settings` section in `.vercel/project.json` to ensure that the `installCommand` is set to `npm i`: + +```diff + "settings": { + "createdAt": 1699390700432, + "framework": null, + "devCommand": null, +- "installCommand": null, ++ "installCommand": "npm i", + "buildCommand": null, + "outputDirectory": null, + "rootDirectory": null, + "directoryListing": false, + "nodeVersion": "18.x" + } +``` + +Next, run the following command to build your dapp: + +```bash +vercel build --prod +``` + +What does the `vercel build` command do? It builds your dapp for production, which means that it optimizes your code for performance and creates an optimized production build of your dapp in the `.vercel/output` directory. This is the directory that you will deploy to Vercel. + +The output of the `vercel build` command should look something like this: + +```bash +$ vite build +.. +dist/assets/index-91e5a562.js 490.69 kB │ gzip: 139.50 kB +dist/assets/ModelViewer-fde23dd9.browser.esm-c08cdb2e.js 826.05 kB │ gzip: 231.17 kB +dist/assets/index-46cc9288.js 3,107.82 kB │ gzip: 888.20 kB +... +✓ built in 11.72s +✨ Done in 12.14s. +✅ Build Completed in .vercel/output [19s] +``` + +Next, you will deploy your dapp to Vercel by running the following command: + +```bash +vercel deploy --prebuilt --prod +``` + +Using the `--prebuilt` flag tells Vercel to deploy the build outputs in `.vercel/output` that you created in the previous step. + +Once the deployment is complete, you should see something similar to the following output: + +```bash +🔍 Inspect: https://vercel.com/julian-dev28/oracle2/Fk7RKb3H4RX1d1kBtHukgppRcv2m [9s] +✅ Production: https://oracle2-3lrfgjzq9-julian-dev28.vercel.app [9s] +``` + +:::tip + +Please, save your production url, you will need it to complete the challenge. + +::: + +You can now visit the preview link to see your deployed dapp! 🎉 + +connect + +Remember, you must add Futurenet network lumens to your Freighter wallet to interact with the deployed example dapp. Visit https://laboratory.stellar.org/#account-creator?network=futurenet, and follow the instructions to create your Freighter account on Futurenet. + +## Checkpoint 7: 💪 Pass the Challenge! + +Now it's time to submit your work! + +Submit your `Production` URL from the previous step into the challenge form to pass the checkpoint! + + + +
    + +:::info + +Join [our Community in Discord](https://discord.gg/stellardev) in case you have any issues or questions. + +::: + +## Checkpoint 8: ✅ Check your work! + +In order to successfully complete this challenge, your work needs to be checked. Please, follow this steps: + +1. Fork [the challenge repository](https://github.com/stellar/soroban-dapps-challenge/fork). +2. Fill `oracle/challenge/output.txt` file with your wallet address. Filled file should look like: + +```sh +Public Key: GBSXUXZSA2VEXN5VGOWE5ODAJLC575JCMWRJ4FFRDWSTRCJ123456789 +``` + +3. Create a Pull Request to the `stellar/soroban-dapps-challenge/oracle` branch. When the PR will be created, CI actions will check the `oracle/challenge/output.txt` file data and update your progress. +4. Wait for the CI/CD pipeline results. +5. Fix errors if present: + +- find the error reason in the Oracle challenge CI results (you can find a link right in the pull request); +- return to your forked repository; +- fix errors and commit changes. The existing PR will be checked again. + +6. If the pipeline was successful, then congratulations! You completed the challenge!👏 + +Invite a friend to try out your dapp and ask them to provide feedback! + +## ⚔️ Side Quests + +🪬 Add a new feature to your dapp (e.g. add a new price feed, add a new token, etc.) + +🌐 Extend your dapp's functionality by allowing users to query and interact with historic oracle data directly through the user interface. + +💡 Develop a function to respond to a significant BTC price change. + +## 📚 User Workflows Checklist + +During this exercise you should be able to: + +- Clone the example repo (Oracle Dapp) +- Set the correct parameters in the `initialize.sh` script +- Deploy your contract to Futurenet +- Deploy the example web ui somewhere (e.g. netlify, vercel, surge, etc.) + +Then via the web UI, you should be able to: + +- Connect your wallet +- See the BTC to USD conversion rate +- See your current balance +- See the contract balance +- Mint an asset +- Deposit an asset +- See your deposit(s) appear on the page as the transactions are confirmed + +## 🛡️🗡️ Take On More Challenges + +View your progress and take on more challenges by visiting your [User Dashboard!](../dashboard) diff --git a/src/pages/docs/learn/interactive/dapps/challenges/styles.css b/src/pages/docs/learn/interactive/dapps/challenges/styles.css new file mode 100644 index 000000000..23ecc7ab0 --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/challenges/styles.css @@ -0,0 +1,31 @@ +.image-style { + border-radius: 10px; /* Rounded corners */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Drop shadow */ + transition: transform 0.3s; /* Smooth transition for hover effect */ + width: 40% + } + + .image-style_reg { + border-radius: 10px; /* Rounded corners */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Drop shadow */ + transition: transform 0.3s; /* Smooth transition for hover effect */ + } + + .image-style_lp { + border-radius: 10px; /* Rounded corners */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Drop shadow */ + transition: transform 0.3s; /* Smooth transition for hover effect */ + width: 85% + } + + .image-style:hover { + transform: translateY(-5px); /* Rise effect on hover */ + } + + .image-style_reg:hover { + transform: translateY(-5px); /* Rise effect on hover */ + } + + .image-style_lp:hover { + transform: translateY(-5px); /* Rise effect on hover */ + } \ No newline at end of file diff --git a/src/pages/docs/learn/interactive/dapps/dashboard/index.tsx b/src/pages/docs/learn/interactive/dapps/dashboard/index.tsx new file mode 100644 index 000000000..00bf42391 --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/dashboard/index.tsx @@ -0,0 +1,183 @@ +import React, { useEffect, useState } from "react"; +import Layout from "@theme/Layout"; +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import { toast } from "react-toastify"; +import { + LeaderboardParams, + fetchLeaderboard, +} from "@site/src/services/leaderboard"; +import styles from "./style.module.css"; +import { + fetchInitialChallenges, + fetchUserProgress, + resetUserProgress, +} from "@site/src/services/challenges"; +import useAuth from "@site/src/hooks/useAuth"; +import DashboardHeader from "@site/src/components/atoms/dashboard-header"; +import Leaderboard from "@site/src/components/molecules/leaderboard"; +import ChallengesList from "@site/src/components/atoms/challenges-list"; +import { + Challenge, + ChallengeInfo, + Leaderboard as LeaderboardI, + Ranking, +} from "@site/src/interfaces/challenge"; + +export default function Dashboard() { + const { address, isConnected, connect } = useAuth(); + + const [availableChallenges, setAvailableChallenges] = useState( + [], + ); + const [userChallenges, setUserChallenges] = useState([]); + const [leaderboard, setLeaderboard] = useState([]); + const [totalCompleted, setTotalCompleted] = useState(0); + const [ranking, setRanking] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isLeaderboardLoading, setIsLeaderboardLoading] = + useState(false); + + const fetchUserChallenges = async () => { + setIsLoading(true); + try { + const result = await fetchUserProgress(address); + setUserChallenges(result.data?.challenges || []); + setTotalCompleted(result.data?.completedChallenges || 0); + setRanking(result.data?.ranking || null); + } catch (e) { + toast("Something went wrong! Please reload", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + } finally { + setIsLoading(false); + } + }; + + const fetchOnlyLeaderboard = async (params: LeaderboardParams) => { + try { + setIsLeaderboardLoading(true); + const result = await fetchLeaderboard(params); + setLeaderboard(result?.data); + } catch (e) { + toast("Something went wrong! Please reload", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + } finally { + setIsLeaderboardLoading(false); + } + }; + + const onReset = async () => { + try { + setIsLoading(true); + await resetUserProgress(address); + } catch (error) { + toast("Something went wrong! Please try again", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + const fetchData = async () => { + try { + setIsLoading(true); + + const result = await Promise.allSettled([ + fetchInitialChallenges(), + fetchLeaderboard({}), + fetchUserProgress(address), + ]); + + if (result[0].status === "fulfilled") { + setAvailableChallenges(result[0].value.data || []); + } + if (result[1].status === "fulfilled") { + setLeaderboard(result[1].value.data || []); + } + if (result[2].status === "fulfilled") { + setUserChallenges(result[2].value.data?.challenges || []); + setTotalCompleted(result[2].value.data?.completedChallenges || 0); + setRanking(result[2].value.data?.ranking || null); + } + + if ( + result[0].status === "rejected" || + result[1].status === "rejected" || + result[2].status === "rejected" + ) { + throw new Error("Some request error"); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + toast("Something went wrong! Please reload", { + type: "error", + hideProgressBar: true, + position: "top-center", + autoClose: 2000, + }); + } + }; + fetchData(); + }, [address]); + + return ( + +
    + {isConnected && address ? ( + + ) : null} + +
    + {isLoading ? ( +

    We're loading the list of challenges...

    + ) : ( + + + + + + + + + )} +
    + + {!isConnected || !address ? ( +
    + Want to take part in our challenges? + +
    + ) : null} +
    +
    + ); +} diff --git a/src/pages/docs/learn/interactive/dapps/dashboard/style.module.css b/src/pages/docs/learn/interactive/dapps/dashboard/style.module.css new file mode 100644 index 000000000..8fd09515e --- /dev/null +++ b/src/pages/docs/learn/interactive/dapps/dashboard/style.module.css @@ -0,0 +1,45 @@ +.dashboard { + display: flex; + flex-direction: column; + flex: 1; + background-color: #F9F9F9; +} + +.dashboardContent { + flex-grow: 2; + width: 75%; + margin: 32px auto; +} + +.dashboardFooter { + display: flex; + justify-content: flex-end; + align-items: center; + flex: 0; + background-color: #FFFFFF; + color: #585858; + padding: 16px 10%; +} + +.challengeCards { + padding: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: max-content; + grid-gap: 30px; +} + +.loginButton { + font-size: 14px; + font-weight: 500; + font-family: var(--ifm-font-family-base); + background-color: #369EA7; + color: #FFFFFF; + border-radius: 4px; + border: none; + padding: 12px 8px; + line-height: 0.8; + width: max-content; + cursor: pointer; + margin-left: 32px; +} diff --git a/src/pages/index.mdx b/src/pages/index.mdx index 13d297da5..9049dd581 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -18,17 +18,17 @@ If you can’t find answers to your questions in the docs, search for your answe ### Docs -This section walks you through building on Stellar without smart contracts. Learn basic Stellar functions such as creating accounts and making payments, how to issue assets on the network, how to build an application with the JavaScript SDK, Wallet SDK, and more. +This section walks you through building on Stellar without smart contracts. Learn basic Stellar functions such as creating accounts and making payments, how to issue assets on the network, how to build an application with the JavaScript SDK, Wallet SDK, and more. ### Smart contracts -Smart contracts on Stellar launched on Mainnet following a successful validator vote on February 20th, 2024. Stellar's smart contract platform includes the smart contract environment, a Rust SDK, a CLI, and an RPC server. +Smart contracts on Stellar launched on Mainnet following a successful validator vote on February 20th, 2024. Stellar's smart contract platform includes the smart contract environment, a Rust SDK, a CLI, and an RPC server. This section details how to get started by writing a Hello World contract, then dives deeper into smart contracts on Stellar with various example contracts and how-to guides. Also, learn how to use Stellar assets in smart contracts with the Stellar Asset Contract or how to create your own smart contract token. ### Learn -Find all informational and conceptual content here. Learn about Stellar fundamentals like how accounts and transactions function, dive deeper into the functionality of each operation, discover how fees work, and more. +Find all informational and conceptual content here. Learn about Stellar fundamentals like how accounts and transactions function, dive deeper into the functionality of each operation, discover how fees work, and more. ### Tools @@ -40,7 +40,7 @@ A quick look at useful information such as the various network data (for Mainnet ### Network -Discover various data availability options (RPC, Hubble, and Horizon), how to use the Anchor Platform or Stellar Disbursement Platform, and how to set up a Core Node. +Discover various data availability options (RPC, Hubble, and Horizon), how to use the Anchor Platform or Stellar Disbursement Platform, and how to set up a Core Node. ## Contribute to the docs and leave feedback diff --git a/src/services/challenges.ts b/src/services/challenges.ts new file mode 100644 index 000000000..1a86b61a7 --- /dev/null +++ b/src/services/challenges.ts @@ -0,0 +1,34 @@ +import { AxiosResponse } from "axios"; +import { + Challenge, + UserChallengeData, + UpdateProgressData, + UserProgress, +} from "../interfaces/challenge"; +import { httpClient } from "./http-client"; + +export const fetchInitialChallenges = async () => { + return await httpClient.get("/challenges"); +}; + +export const fetchUserProgress = async (userId: string) => { + return await httpClient.get("/users", { + validateStatus: (status) => { + return (status >= 200 && status < 300) || status === 404; + }, + params: { userId }, + }); +}; + +export const resetUserProgress = async (userId: string) => { + return await httpClient.delete("/users", { + params: { userId }, + }); +}; + +export const updateUserProgress = async (challenge: UpdateProgressData) => { + return await httpClient.post< + Partial, + AxiosResponse + >("/", challenge); +}; diff --git a/src/services/http-client.ts b/src/services/http-client.ts new file mode 100644 index 000000000..181862d15 --- /dev/null +++ b/src/services/http-client.ts @@ -0,0 +1,66 @@ +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; + +const headers: Readonly> = { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + "Access-Control-Allow-Credentials": true, +}; + +class HttpClient { + private instance: AxiosInstance | null = null; + + private get http(): AxiosInstance { + return this.instance != null ? this.instance : this.initHttp(); + } + + get>( + url: string, + config?: AxiosRequestConfig, + ): Promise { + return this.http.get(url, config); + } + + post>( + url: string, + data?: T, + config?: AxiosRequestConfig, + ): Promise { + return this.http.post(url, data, config); + } + + put>( + url: string, + data?: T, + config?: AxiosRequestConfig, + ): Promise { + return this.http.put(url, data, config); + } + + patch>( + url: string, + data?: T, + config?: AxiosRequestConfig, + ): Promise { + return this.http.patch(url, data, config); + } + + delete>( + url: string, + config?: AxiosRequestConfig, + ): Promise { + return this.http.delete(url, config); + } + + private initHttp() { + const http = axios.create({ + baseURL: + "https://soroban-dapps-challenge-wrangler.julian-martinez.workers.dev", + headers, + }); + + this.instance = http; + return http; + } +} + +export const httpClient = new HttpClient(); diff --git a/src/services/leaderboard.ts b/src/services/leaderboard.ts new file mode 100644 index 000000000..5a70adcd0 --- /dev/null +++ b/src/services/leaderboard.ts @@ -0,0 +1,31 @@ +import { Leaderboard } from "../interfaces/challenge"; +import { httpClient } from "./http-client"; + +export enum LeaderboardColumn { + ChallengesCompleted = "challengesCompleted", + MinutesSpent = "minutesSpent", + TotalValueLocked = "totalValueLocked", +} + +export type LeaderboardParams = { + colName?: LeaderboardColumn; + direction?: "asc" | "desc"; + pageNumber?: number; +}; + +export const fetchLeaderboard = async ({ + colName, + direction = "asc", + pageNumber, +}: LeaderboardParams) => { + return await httpClient.get("/leaderboard", { + params: { + ...(colName + ? { + sort: `${colName},${direction}`, + } + : {}), + ...(pageNumber ? { pageNumber } : {}), + }, + }); +}; diff --git a/src/sidebar-generator.js b/src/sidebar-generator.js new file mode 100644 index 000000000..35b9329b3 --- /dev/null +++ b/src/sidebar-generator.js @@ -0,0 +1,30 @@ +module.exports = async ({ defaultSidebarItemsGenerator, ...args }) => { + + // Get the sidebar items that are generated by default + const sidebarItems = await defaultSidebarItemsGenerator({ ...args }) + + // Find the "Interactive Learning" category + const interactiveLearning = sidebarItems.find( + (item) => + item.type === 'category' && item.label.toLowerCase() === 'interactive learning' + ) + + // Find the "Dapps Challenge" category within "Interactive Learning" + const dappsChallenge = interactiveLearning?.items.find( + (item) => + item.type === 'category' && item.label.toLowerCase() === 'dapps challenge' + ) + + // If the Dapps Challenge has been found, insert a link to the Dashboard in + // the sidebar. + if (dappsChallenge) { + dappsChallenge.items.splice(1, 0, { + type: 'link', + href: '/docs/learn/interactive/dapps/dashboard', + label: 'Dapps Challenge Dashboard' + }) + } + + // return the sidebar items + return sidebarItems +}; diff --git a/src/store/UserChallengesContextProvider.tsx b/src/store/UserChallengesContextProvider.tsx new file mode 100644 index 000000000..11c01cf0b --- /dev/null +++ b/src/store/UserChallengesContextProvider.tsx @@ -0,0 +1,108 @@ +import React, { PropsWithChildren, useReducer } from "react"; +import UserChallengesContext, { + UserChallengesContextProps, +} from "./user-challenges-context"; +import { ChallengeInfo } from "../interfaces/challenge"; + +interface ChallengesState { + data: ChallengeInfo[]; + address: string; +} + +interface Action { + type: string; + data?: ChallengeInfo[]; + item?: ChallengeInfo; + payload?: string; +} + +enum ActionType { + SET_DATA = "SET_DATA", + UPDATE_PROGRESS = "UPDATE_PROGRESS", + SET_ADDRESS = "SET_ADDRESS", +} + +const defaultState: ChallengesState = { + data: [], + address: "", +}; + +const challengesReducer = (state: ChallengesState, action: Action) => { + if (action.type === ActionType.SET_DATA && action.data) { + return { + ...state, + data: [...action.data], + }; + } + + if (action.type === ActionType.UPDATE_PROGRESS && action.item) { + const { id } = action.item; + const existingItemIdx = state.data.findIndex( + (item: ChallengeInfo) => item.id === id, + ); + const updatedChallenges: ChallengeInfo[] = [...state.data]; + + const existedItem = state.data[existingItemIdx]; + + if (existedItem) { + updatedChallenges[existingItemIdx] = action.item; + } else { + updatedChallenges.push(action.item); + } + + return { + ...state, + data: updatedChallenges, + }; + } + + if (action.type === ActionType.SET_ADDRESS && action.payload) { + return { + ...state, + address: action.payload, + }; + } + + return defaultState; +}; + +const UserChallengesContextProvider = (props: PropsWithChildren) => { + const [state, dispatchAction] = useReducer(challengesReducer, defaultState); + + const setDataHandler = (data: ChallengeInfo[]) => { + dispatchAction({ + type: ActionType.SET_DATA, + data, + }); + }; + + const updateProgressHandler = (item: ChallengeInfo) => { + dispatchAction({ + type: ActionType.UPDATE_PROGRESS, + item, + }); + }; + + const setAddress = (address: string) => { + dispatchAction({ + type: ActionType.SET_ADDRESS, + payload: address, + }); + }; + + const challengesCtx: UserChallengesContextProps = { + data: state.data, + address: state.address, + setAddress, + setData: setDataHandler, + updateProgress: updateProgressHandler, + }; + + return ( + + {props.children} + + ); +}; + +export default UserChallengesContextProvider; diff --git a/src/store/user-challenges-context.ts b/src/store/user-challenges-context.ts new file mode 100644 index 000000000..20fb421d5 --- /dev/null +++ b/src/store/user-challenges-context.ts @@ -0,0 +1,20 @@ +import React from "react"; +import { ChallengeInfo } from "../interfaces/challenge"; + +export type UserChallengesContextProps = { + data: ChallengeInfo[]; + address: string; + setData: (data: ChallengeInfo[]) => void; + updateProgress: (item: ChallengeInfo) => void; + setAddress: (address: string) => void; +}; + +const UserChallengesContext = React.createContext({ + data: [], + address: "", + setData: () => {}, + updateProgress: () => {}, + setAddress: () => {}, +}); + +export default UserChallengesContext; diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx new file mode 100644 index 000000000..d51f2bc13 --- /dev/null +++ b/src/theme/Root.tsx @@ -0,0 +1,27 @@ +import React, { PropsWithChildren } from "react"; +import { futurenet, sandbox, standalone, testnet } from "@soroban-react/chains"; +import { SorobanReactProvider } from "@soroban-react/core"; +import { freighter } from "@soroban-react/freighter"; +import { ChainMetadata, Connector } from "@soroban-react/types"; +import { ToastContainer } from "react-toastify"; +import UserChallengesContextProvider from "../store/UserChallengesContextProvider"; +import "react-toastify/dist/ReactToastify.css"; +import "./style.module.css"; + +const chains: ChainMetadata[] = [sandbox, futurenet, testnet, standalone]; +const connectors: Connector[] = [freighter()]; + +export default function Root({ children }: PropsWithChildren) { + return ( + + + + {children} + + ); +} diff --git a/src/theme/style.module.css b/src/theme/style.module.css new file mode 100644 index 000000000..0925ae0b5 --- /dev/null +++ b/src/theme/style.module.css @@ -0,0 +1,3 @@ +:root { + --toastify-toast-width: 400px; +} \ No newline at end of file diff --git a/src/utils/get-active-challenge.ts b/src/utils/get-active-challenge.ts new file mode 100644 index 000000000..0a96003a1 --- /dev/null +++ b/src/utils/get-active-challenge.ts @@ -0,0 +1,8 @@ +import { ChallengeInfo } from "../interfaces/challenge"; + +export const getActiveChallenge = ( + data: ChallengeInfo[], + challengeId: number, +): ChallengeInfo | undefined => { + return data.find((item: ChallengeInfo) => item.id === challengeId); +}; diff --git a/src/utils/get-contract-balance.ts b/src/utils/get-contract-balance.ts new file mode 100644 index 000000000..01d8b20fe --- /dev/null +++ b/src/utils/get-contract-balance.ts @@ -0,0 +1,53 @@ +import { + Contract, + scValToBigInt, + TransactionBuilder, + TimeoutInfinite, + Address, + SorobanRpc +} from "@stellar/stellar-sdk"; +import { FUTURENET_DETAILS } from "../constants"; + +const XLM_DECIMALS = 7; + +const BASE_FEE = "100"; +const RPC_URLS: { [key: string]: string } = { + FUTURENET: "https://rpc-futurenet.stellar.org/", +}; +const server = new SorobanRpc.Server(RPC_URLS[FUTURENET_DETAILS.network]); + +function formatAmount( + undivided: bigint, + decimals: number = XLM_DECIMALS, +): string { + const n = + undivided.valueOf() < BigInt(Number.MAX_SAFE_INTEGER) + ? Number(undivided) / 10 ** decimals + : undivided.valueOf() / 10n ** BigInt(decimals); + return String(n); +} + +export const getContractBalance = async ( + contractId: string, + address: string, +): Promise => { + const account = await server.getAccount(address); + const contract = new Contract(contractId); + const params = [new Address(address).toScVal()]; + + const transaction = new TransactionBuilder(account, { + fee: BASE_FEE, + networkPassphrase: FUTURENET_DETAILS.networkPassphrase, + }) + .addOperation(contract.call("balance", ...params)) + .setTimeout(TimeoutInfinite) + .build(); + + const response = await server.simulateTransaction(transaction); + if (!SorobanRpc.Api.isSimulationSuccess(response)) { + throw response; + } + + const balanceStr = formatAmount(scValToBigInt(response.result!.retval)); + return +balanceStr; +}; diff --git a/static/icons/icon-avatar-1.svg b/static/icons/icon-avatar-1.svg new file mode 100644 index 000000000..ea1b49d65 --- /dev/null +++ b/static/icons/icon-avatar-1.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/icons/icon-avatar-10.svg b/static/icons/icon-avatar-10.svg new file mode 100644 index 000000000..f9dbe03eb --- /dev/null +++ b/static/icons/icon-avatar-10.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/static/icons/icon-avatar-2.svg b/static/icons/icon-avatar-2.svg new file mode 100644 index 000000000..424086be8 --- /dev/null +++ b/static/icons/icon-avatar-2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/icons/icon-avatar-3.svg b/static/icons/icon-avatar-3.svg new file mode 100644 index 000000000..7ff3975fb --- /dev/null +++ b/static/icons/icon-avatar-3.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/static/icons/icon-avatar-4.svg b/static/icons/icon-avatar-4.svg new file mode 100644 index 000000000..196614905 --- /dev/null +++ b/static/icons/icon-avatar-4.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/static/icons/icon-avatar-5.svg b/static/icons/icon-avatar-5.svg new file mode 100644 index 000000000..dcc41b291 --- /dev/null +++ b/static/icons/icon-avatar-5.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/icons/icon-avatar-6.svg b/static/icons/icon-avatar-6.svg new file mode 100644 index 000000000..ed5eb9bd7 --- /dev/null +++ b/static/icons/icon-avatar-6.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/icons/icon-avatar-7.svg b/static/icons/icon-avatar-7.svg new file mode 100644 index 000000000..1ff0731ba --- /dev/null +++ b/static/icons/icon-avatar-7.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/icon-avatar-8.svg b/static/icons/icon-avatar-8.svg new file mode 100644 index 000000000..451db5ec2 --- /dev/null +++ b/static/icons/icon-avatar-8.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/icons/icon-avatar-9.svg b/static/icons/icon-avatar-9.svg new file mode 100644 index 000000000..97c7192a4 --- /dev/null +++ b/static/icons/icon-avatar-9.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/static/icons/icon-copy.svg b/static/icons/icon-copy.svg new file mode 100644 index 000000000..70ff20f5a --- /dev/null +++ b/static/icons/icon-copy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/icons/icon-ranking.svg b/static/icons/icon-ranking.svg new file mode 100644 index 000000000..8e1b01051 --- /dev/null +++ b/static/icons/icon-ranking.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/icons/icon-star-yellow.svg b/static/icons/icon-star-yellow.svg new file mode 100644 index 000000000..f41ea0939 --- /dev/null +++ b/static/icons/icon-star-yellow.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/static/icons/icon-star.svg b/static/icons/icon-star.svg new file mode 100644 index 000000000..cb3b513ce --- /dev/null +++ b/static/icons/icon-star.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/icons/smiley-face-1.svg b/static/icons/smiley-face-1.svg new file mode 100644 index 000000000..6c44d9d9e --- /dev/null +++ b/static/icons/smiley-face-1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/static/icons/smiley-face-2.svg b/static/icons/smiley-face-2.svg new file mode 100644 index 000000000..be057548a --- /dev/null +++ b/static/icons/smiley-face-2.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/yarn.lock b/yarn.lock index 004dc7a16..9be95a66f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1357,6 +1357,14 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@creit-tech/xbull-wallet-connect@github:Creit-Tech/xBull-Wallet-Connect": + version "0.2.0" + resolved "https://codeload.github.com/Creit-Tech/xBull-Wallet-Connect/tar.gz/a93e2e0d97c61bbd83fedcca0c71feef9f28b2d3" + dependencies: + rxjs "^7.5.5" + tweetnacl "^1.0.3" + tweetnacl-util "^0.15.1" + "@discoveryjs/json-ext@0.5.7": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -2661,16 +2669,141 @@ p-map "^4.0.0" webpack-sources "^3.2.2" +"@soroban-react/chains@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@soroban-react/chains/-/chains-9.0.4.tgz#51130f42f39437246e6e4e95d809949a883d75e4" + integrity sha512-pvkGwxdOY/uK28j6ou6+1T6cw99KQJ4UhgSA759vVEBo0YyVgPJ8t1Qs3EhZ/k1oUA2+BdCJt/D9w/hTUsYgzA== + dependencies: + "@soroban-react/types" "^9.0.4" + "@stellar/stellar-sdk" "11.1.0" + +"@soroban-react/core@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@soroban-react/core/-/core-9.0.4.tgz#8efaa448d1e9361b71af707f9c08b232e575f48c" + integrity sha512-tCKn6f92+89gzNdGmBm7EJBWjWLC0cTubiXT32xBh/xzuVymE3eow8AtjfOXYP7Whb3l+LEKc1nAgIGWVnWfSQ== + dependencies: + "@soroban-react/freighter" "^9.0.4" + "@soroban-react/types" "^9.0.4" + "@soroban-react/xbull" "^1.0.1" + "@stellar/stellar-sdk" "11.1.0" + +"@soroban-react/events@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@soroban-react/events/-/events-9.0.4.tgz#76942419497fb06808f2471f893d48a5f61184e3" + integrity sha512-rVYNwBtR1O5ujxgHYbsdq723QNjvfePEzyaHV2AxueW2iZO/8ACB/IKKpQbaNKhaGDxmWHG263ZofFnnMAA1Lg== + dependencies: + "@soroban-react/core" "^9.0.4" + "@soroban-react/types" "^9.0.4" + soroban-client "1.0.0-beta.4" + +"@soroban-react/freighter@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@soroban-react/freighter/-/freighter-9.0.4.tgz#62b05b217162c27c1562c3e4f988b06b487b36a6" + integrity sha512-WVh8l8jIMlQZJQTT/5fVH9rjEzj48b/bbY9FKshz3JYjV/cs+nPpXjijkEIodzCdKiLat077go3cy0ExVS8HOw== + dependencies: + "@soroban-react/types" "^9.0.4" + "@stellar/freighter-api" "1.7.1" + +"@soroban-react/types@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@soroban-react/types/-/types-8.0.0.tgz#14cb25911d2d45e3448a0fc6c0b2a6ae16b4851e" + integrity sha512-t8eohOlmb4LAD1e3bfI01KKK7oCcm468QKsKrD0fL67fnZYcx9K61tSCqwWEbFCWGMGPkwaOwI3sE/bYKKDMIQ== + +"@soroban-react/types@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@soroban-react/types/-/types-9.0.4.tgz#c0cae5aa59ed212059a489312daf8279464d3ab1" + integrity sha512-WghuBJb6bG9ntHyFfXBu0XhpZEqRmrDZjUK3edCt4z9vSwusGiC/37ALzfJANA0LTbyysRs0S08SPJB1Hl+lgg== + dependencies: + "@stellar/stellar-sdk" "11.1.0" + +"@soroban-react/xbull@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@soroban-react/xbull/-/xbull-1.0.1.tgz#0468bf06e92d7d44c35265c42c93769f1066fe96" + integrity sha512-dk/qoF6CsRMgKWyO++woJmq5bTgo9ZCGBtGaNFOiljliQ7bX6MzvMoeZSwTvNNJZhL18AWpaiWmxCvv2bJJoQg== + dependencies: + "@creit-tech/xbull-wallet-connect" "github:Creit-Tech/xBull-Wallet-Connect" + "@soroban-react/types" "8.0.0" + stellar-sdk "11.1.0" + "@stellar/eslint-config@^2.1.2": version "2.1.2" resolved "https://registry.yarnpkg.com/@stellar/eslint-config/-/eslint-config-2.1.2.tgz#95c223d10a5dfc2c89486e76cc7c9f1b56df3564" integrity sha512-5aUkncDMmx0SvVlZD4rld5snGKt3mc0Gno1Jik3Pp31HUmpgrkRUD3ZZekEOqB9mDKadZhQZNNsS0jhyuXaayw== +"@stellar/freighter-api@1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@stellar/freighter-api/-/freighter-api-1.7.1.tgz#d62b432abc7e0140a6025cd672455ecee7b3199a" + integrity sha512-XvPO+XgEbkeP0VhP0U1edOkds+rGS28+y8GRGbCVXeZ9ZslbWqRFQoETAdX8IXGuykk2ib/aPokiLc5ZaWYP7w== + +"@stellar/freighter-api@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@stellar/freighter-api/-/freighter-api-2.0.0.tgz#488915a4aa0cec8c9a3fc84ef31e21cd5ec41343" + integrity sha512-j/R7MLPL8S3QhwOEdAxSl7MgWBTXWlOXQKQyXR8mPk1JMKKR4tF8e4U+Fs9TPQH0HZoYqfVDvLOOUrTMMY058Q== + +"@stellar/js-xdr@^3.0.1", "@stellar/js-xdr@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@stellar/js-xdr/-/js-xdr-3.1.1.tgz#be0ff90c8a861d6e1101bca130fa20e74d5599bb" + integrity sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag== + "@stellar/prettier-config@^1.0.1": version "1.2.0" resolved "https://registry.yarnpkg.com/@stellar/prettier-config/-/prettier-config-1.2.0.tgz#b27c411e0c4c63b2d76332c239084e682c37468f" integrity sha512-oL9qJ7+7aWnImpbcldroQrvtMCZ9yx4JL/tmDZ860RpBQd2ahkc8bX6/k2ehFK8gpb9ltYu4mtU49wufUuYhGg== +"@stellar/stellar-base@10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@stellar/stellar-base/-/stellar-base-10.0.1.tgz#cf4458e081f694109422521562e53e642c29991b" + integrity sha512-BDbx7VHOEQh+4J3Q+gStNXgPaNckVFmD4aOlBBGwxlF6vPFmVnW8IoJdkX7T58zpX55eWI6DXvEhDBlrqTlhAQ== + dependencies: + "@stellar/js-xdr" "^3.0.1" + base32.js "^0.1.0" + bignumber.js "^9.1.2" + buffer "^6.0.3" + sha.js "^2.3.6" + tweetnacl "^1.0.3" + optionalDependencies: + sodium-native "^4.0.1" + +"@stellar/stellar-base@^11.0.1": + version "11.0.1" + resolved "https://registry.yarnpkg.com/@stellar/stellar-base/-/stellar-base-11.0.1.tgz#f6eba54e62aa3e827e55d8338b92cdbf5535cfd9" + integrity sha512-VQh+1KEtFjegD6spx08+lENt8tQOkQQQZoLtqExjpRXyWlqDhEe+bXMlBTYKDc5MIynHyD42RPEib27UG17trA== + dependencies: + "@stellar/js-xdr" "^3.1.1" + base32.js "^0.1.0" + bignumber.js "^9.1.2" + buffer "^6.0.3" + sha.js "^2.3.6" + tweetnacl "^1.0.3" + optionalDependencies: + sodium-native "^4.0.10" + +"@stellar/stellar-sdk@11.1.0": + version "11.1.0" + resolved "https://registry.yarnpkg.com/@stellar/stellar-sdk/-/stellar-sdk-11.1.0.tgz#4a619fa645a7392204b6e80cb0a7f3ef94cdd077" + integrity sha512-Ufw+4udr7lqyzPIhqSAzBTgcl/YlgFZLgeBlDr5ZZy1v+g7AT4dOZFurcCrHt7Pz8DGtVcxNX7GLxYLdOC3GIg== + dependencies: + "@stellar/stellar-base" "10.0.1" + axios "^1.6.0" + bignumber.js "^9.1.2" + eventsource "^2.0.2" + randombytes "^2.1.0" + toml "^3.0.0" + urijs "^1.19.1" + +"@stellar/stellar-sdk@^11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@stellar/stellar-sdk/-/stellar-sdk-11.3.0.tgz#7cb010651846a07e1853e0fe30e430ece4da340b" + integrity sha512-i+heopibJNRA7iM8rEPz0AXphBPYvy2HDo8rxbDwWpozwCfw8kglP9cLkkhgJe8YicgLrdExz/iQZaLpqLC+6w== + dependencies: + "@stellar/stellar-base" "^11.0.1" + axios "^1.6.8" + bignumber.js "^9.1.2" + eventsource "^2.0.2" + randombytes "^2.1.0" + toml "^3.0.0" + urijs "^1.19.1" + "@stellar/tsconfig@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@stellar/tsconfig/-/tsconfig-1.0.2.tgz#18e9b1a1d6076e116bb405d11fc034401155292d" @@ -4248,6 +4381,15 @@ axios@^0.25.0: dependencies: follow-redirects "^1.14.7" +axios@^1.6.0, axios@^1.6.8: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -4374,6 +4516,11 @@ base16@^1.0.0: resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" integrity sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ== +base32.js@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/base32.js/-/base32.js-0.1.0.tgz#b582dec693c2f11e893cf064ee6ac5b6131a2202" + integrity sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ== + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -4402,6 +4549,11 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bignumber.js@^9.1.1, bignumber.js@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -4994,6 +5146,11 @@ clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== +clsx@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== + coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -6270,7 +6427,7 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: +deepmerge@^4.0.0, deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -7648,7 +7805,7 @@ fnv-plus@^1.3.1: resolved "https://registry.yarnpkg.com/fnv-plus/-/fnv-plus-1.3.1.tgz#c34cb4572565434acb08ba257e4044ce2b006d67" integrity sha512-Gz1EvfOneuFfk4yG458dJ3TLJ7gV19q3OM/vVvvHf7eT02Hm1DleB4edsia6ahbKgAYxO9gvyQ1ioWZR+a00Yw== -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.7: +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.7, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -9386,6 +9543,11 @@ js-levenshtein@^1.1.6: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-xdr@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/js-xdr/-/js-xdr-3.1.1.tgz#38624252272ad520e37ab32b0f7b580136bc7bab" + integrity sha512-FaNXtfzwbc9MedMK0AqAa5A32U33Bbxta9zedSBHHiJmknK7hqXM0W5qrVVa/ID6Trw8/XW31Bca6XtdG5BV/Q== + js-yaml@3.14.1, js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -9652,6 +9814,11 @@ liquid-json@0.3.1: resolved "https://registry.yarnpkg.com/liquid-json/-/liquid-json-0.3.1.tgz#9155a18136d8a6b2615e5f16f9a2448ab6b50eea" integrity sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ== +load-script@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/load-script/-/load-script-1.0.0.tgz#0491939e0bee5643ee494a7e3da3d2bac70c6ca4" + integrity sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA== + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -10087,6 +10254,11 @@ memfs@^3.1.2, memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" +memoize-one@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -10764,6 +10936,11 @@ node-forge@^1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-gyp-build@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.0.tgz#3fee9c1731df4581a3f9ead74664369ff00d26dd" + integrity sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og== + node-polyfill-webpack-plugin@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/node-polyfill-webpack-plugin/-/node-polyfill-webpack-plugin-2.0.1.tgz#141d86f177103a8517c71d99b7c6a46edbb1bb58" @@ -11960,6 +12137,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -12181,7 +12363,7 @@ react-error-overlay@^6.0.11, react-error-overlay@^6.0.9: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-fast-compare@^3.1.1, react-fast-compare@^3.2.0, react-fast-compare@^3.2.2: +react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0, react-fast-compare@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== @@ -12325,6 +12507,17 @@ react-overflow-list@^0.5.0: dependencies: react-use "^17.3.1" +react-player@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/react-player/-/react-player-2.16.0.tgz#89070700b03f5a5ded9f0b3165d4717390796481" + integrity sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ== + dependencies: + deepmerge "^4.0.0" + load-script "^1.0.0" + memoize-one "^5.1.1" + prop-types "^15.7.2" + react-fast-compare "^3.0.1" + react-redux@^7.2.0: version "7.2.9" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" @@ -12337,6 +12530,11 @@ react-redux@^7.2.0: prop-types "^15.7.2" react-is "^17.0.2" +react-rewards@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-rewards/-/react-rewards-2.0.4.tgz#617f6c1bb591f74bb0e0455cc6ff355ee6d36665" + integrity sha512-Lw7gIhD8yPDzC6boaVmcXwuTHRLSLAdqB3kZc+29YWvdHWsuc3fdAZlxI8Cm8fvD8fhP+3JkZBtzX224czw15w== + react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -12405,6 +12603,13 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.3.0" use-latest "^1.2.1" +react-toastify@^10.0.5: + version "10.0.5" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-10.0.5.tgz#6b8f8386060c5c856239f3036d1e76874ce3bd1e" + integrity sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw== + dependencies: + clsx "^2.1.0" + react-universal-interface@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" @@ -12999,7 +13204,7 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" -rxjs@^7.5.4: +rxjs@^7.5.4, rxjs@^7.5.5: version "7.8.1" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== @@ -13333,7 +13538,7 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.3.6, sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -13570,6 +13775,24 @@ sockjs@^0.3.21, sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" +sodium-native@^4.0.1, sodium-native@^4.0.10: + version "4.1.1" + resolved "https://registry.yarnpkg.com/sodium-native/-/sodium-native-4.1.1.tgz#109bc924dd55c13db87c6dd30da047487595723c" + integrity sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ== + dependencies: + node-gyp-build "^4.8.0" + +soroban-client@1.0.0-beta.4: + version "1.0.0-beta.4" + resolved "https://registry.yarnpkg.com/soroban-client/-/soroban-client-1.0.0-beta.4.tgz#f2e0b0b1c4511bd04381a3cff3a45cfba7d0dc31" + integrity sha512-M1jLCwQtWZkQIJ6U72nYpvj+giYnB2/Vw4E1DBaiCgg5iWIWatDto+QTI/aUR9m7fNGTt/AhFtQzhjksK1rFkQ== + dependencies: + axios "^1.6.0" + bignumber.js "^9.1.1" + buffer "^6.0.3" + stellar-base v10.0.0-beta.4 + urijs "^1.19.1" + sort-css-media-queries@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/sort-css-media-queries/-/sort-css-media-queries-2.0.4.tgz#b2badfa519cb4a938acbc6d3aaa913d4949dc908" @@ -13761,6 +13984,33 @@ std-env@^3.0.1: resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== +stellar-base@v10.0.0-beta.4: + version "10.0.0-beta.4" + resolved "https://registry.yarnpkg.com/stellar-base/-/stellar-base-10.0.0-beta.4.tgz#818d3b5dd702a7d18f1db47a72837cec80616716" + integrity sha512-3EXDFHSahVDMTHrHiFOO8kFf5KN+AL4x5kd5rxjElElPG+385cyWDbO83GrNmDGU/u9/XiVL+riJjz5gQTv6RQ== + dependencies: + base32.js "^0.1.0" + bignumber.js "^9.1.2" + buffer "^6.0.3" + js-xdr "^3.0.0" + sha.js "^2.3.6" + tweetnacl "^1.0.3" + optionalDependencies: + sodium-native "^4.0.1" + +stellar-sdk@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/stellar-sdk/-/stellar-sdk-11.1.0.tgz#04df0be3bfee2ffd1db068c92dc4f3ec27309103" + integrity sha512-fIdo77ogpU+ecHgs59pk9velpXd4F/ch0DzOI4QZw8zVZApc3oeNWP3+X6ui7BWpeRHAGsP2CHQzBLxm0JTIgg== + dependencies: + "@stellar/stellar-base" "10.0.1" + axios "^1.6.0" + bignumber.js "^9.1.2" + eventsource "^2.0.2" + randombytes "^2.1.0" + toml "^3.0.0" + urijs "^1.19.1" + stickyfill@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stickyfill/-/stickyfill-1.1.1.tgz#39413fee9d025c74a7e59ceecb23784cc0f17f02" @@ -14237,6 +14487,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + totalist@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" @@ -14324,6 +14579,16 @@ tty-browserify@^0.0.1: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== +tweetnacl-util@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" + integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== + +tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -14720,6 +14985,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urijs@^1.19.1: + version "1.19.11" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.11.tgz#204b0d6b605ae80bea54bea39280cdb7c9f923cc" + integrity sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ== + urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"