diff --git a/.env.local.example b/.env.local.example index 3017fcd01151d4eaf8cd83213191acb720ae1518..b9827966ea454116c5cb9a0f86ec2547fa0087c5 100644 --- a/.env.local.example +++ b/.env.local.example @@ -1,3 +1,5 @@ NEXTAUTH_SECRET=YOUR_SECRET_HERE NEXTAUTH_URL=http://localhost:3000 -RPC_ENDPOINT=wss://kusama-rpc.dwellir.com \ No newline at end of file +RPC_ENDPOINT=https://xxnetwork-rpc.dwellir.com +NEXT_PUBLIC_TOKEN_ASSET_CFG={"XX":{"free":1,"id":0},"JUNK":{"standard":0.5,"premium":1,"id":5},"XRP":{"free":0,"standard":1,"premium":2,"id":7}} + diff --git a/README.md b/README.md index 86b0152deaf5396673dbc155cd9b57e6cfb27eaa..42b47f339a6eaf12be9a2ee0fee2575db3c62ea0 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,17 @@ - [What is it](#what-is-it) - [Requirements](#requirements) -- [What does it do](#what-does-it-do) -- [How to run](#how-to-run) +- [What it does](#what-it-does) +- [How to run it](#how-to-run-it) - [Use cases](#use-cases) - [What was modified](#what-was-modified) +- [Config file and console log](#config-file-and-console-log) + - [Token and asset configuration in .env.local](#token-and-asset-configuration-in-envlocal) +- [Console log](#console-log) - [Issues, limitations, workarounds](#issues-limitations-workarounds) - [Address format](#address-format) - [Asset-gating](#asset-gating) + - [Missing NEXT\_PUBLIC\_TOKEN\_ASSET\_CFG](#missing-next_public_token_asset_cfg) +- [License](#license) - [Credits](#credits) ## What is it @@ -23,13 +28,13 @@ It works with static (server-rendered) and dynamic Web pages. - Client-side: Polkadot{.js} Web3 wallet; other Web3 wallets may work - Server-side: NodeJS with access to local or public xx chain RPC endpoint (ws[s]://) -## What does it do +## What it does -It can limit access to site or sections of site based on ownership or balance of: +It can limit access to app/site or its sections (routes) based on ownership or balance of: -- XX coins, for example more than 0, or more than 100 -- assets on xx chain, for example more than 1 QTY of asset ID 5 -- combination of criteria +- XX coin, for example more than 0 or more than 100 +- xx Chain asset(s), for example more than 1 QTY of asset ID 5 +- any combination of two or more of these criteria Screenshot of XX coin-gating: @@ -41,11 +46,11 @@ With xx asset-gated code added more recently (QTY 1 of asset ID 5- see further b  -## How to run +## How to run it -Install Polkadot{.js} extension, create xx Network wallet, fund it with some amount of xx coins (1.01, for example) and set it to work on `xx Network`. +Install Polkadot{.js} extension, create xx Network wallet, fund it with some amount of XX coin (1.01 XX, for example) and set it to work on `xx Network`. - + Download this repo, create `.env.local` in root directory, and run: @@ -61,19 +66,24 @@ My .env.local for non-production use: NEXTAUTH_SECRET=123123123 NEXTAUTH_URL=http://localhost:3000 RPC_ENDPOINT=ws://192.168.1.30:63007 +NEXT_PUBLIC_TOKEN_ASSET_CFG={"XX":{"free":1,"id":0},"JUNK":{"standard":0.5,"premium":1,"id":5},"XRP":{"free":0,"standard":1,"premium":2,"id":7}} ``` Go to http://localhost:3000 in the browser in which you installed Polkadot{.js} and connect the extension to http://localhost:3000, generate an address and make it restricted for use on xx Network (I hoped this would ensure balance will be looked up correctly, but it was not). -With that, I can login (see [xx_screenshot.png](./xx_screenshot.png) and balance of xx coins is correctly shown (since v1.1)). +With that, I can login (see [xx_screenshot.png](./xx_screenshot.png)) and the balance of XX coin is correctly shown (since v1.1). -(Note that, if you go to https://wallet.xx.network, Polkadot{.js} lets you add the wallet from the browser extension and show it in xx Network Wallet. Neat!) +**NOTES:** -`npm run build` builds okay, but has a fake warning about multiple versions of some package. +- you may fail to login without > 1.0 XX in your wallet. You may modify code to check for less or validate an asset balance instead +- regarding Polkadot{.js}, if you go to https://wallet.xx.network, Polkadot{.js} lets you add the wallet from the browser extension and show it in xx Network Wallet. Neat! +- `NEXT_PUBLIC_TOKEN_ASSET_CFG` doesn't play a role in checks (see below), but is required because `NEXT_PUBLIC_TOKEN_ASSET_CFG` is displayed on the home page (both table & JSON) and may be used in other pages. More on this later. + + ## Use cases -Obviously, token-gating Web sites. But, in the context of xx Network, what kinds? +Obviously, token- and asset-gating of Web sites. But, in the context of xx Network, what kinds? - Generic infrastructure services for validators - many of whom are too cheap to pay for anything, and too lazy or privacy-conscious to register - Access to xx Network-related services such as Haven Space directory @@ -90,37 +100,110 @@ So this integration is not entirely useless even for sites that can't sell xx co ## What was modified -`.env.local` and `pages/api/auth/[...nextauth].js` line 92 there's a fall back to public RPC endpoint, but I modified that one to local as well. Asset-gating was also added in that file. In the code we check the balance of asset ID 5 (which on xx Network happens to be `JUNK`) and warn if it's less than 2. I have 1 on mine, which triggers a warning like so. +- `pages/api/auth/[...nextauth].js`: modified for (XX) token and where "asset"-gating was added. + +Currently, in the same file, we check for > 1 XX or more than 1 JUNK, but you can modify as you see fit: + +`(accountInfo.data.free.gt(new BN(1_000_000_000)) || assetBalance >= 1)` + +- `components/login.tsx`: added some functions for debugging, and logging to console, as you can see above. + +- `pages/protected-api.tsx` and `pages/protected.tsx` ("static" protected page): changed the token unit to XX from KSM, changed the number of decimals to 9 from 12: +```sh +const ksmBalance = formatBalance( session.freeBalance, { decimals: 9, withSi: true, withUnit: 'XX' } ) +``` + +- `pages/index.tsx`: layout modified for xx Network, `NEXT_PUBLIC_TOKEN_ASSET_CFG` is loaded and displayed on that page as well. + +To see everything that's different compared to yk909's fork, see `git log`. + +## Config file and console log + +### Token and asset configuration in .env.local + +`NEXT_PUBLIC_TOKEN_ASSET_CFG` contains a JSON-formatted string with a list of tokens/assets and related configuration details. In JSON, for easier viewing: + +```json +{ + "XX": { + "free": 1, + "id": 0 + }, + "JUNK": { + "standard": 0.5, + "premium": 1, + "id": 5 + }, + "XRP": { + "free": 0, + "standard": 1, + "premium": 2, + "id": 7 + } +} +``` + +The first entry is the native currency of xx Network, XX coin, for which the ID is meaningless and I use 0 as xx Network assets can't have that ID. + +There's just one tier - free - and you need more than 1 XX to get in even for that (i.e. it's free, but not open access). + +The second item is the JUNK asset; that ID is real (it's 5, on xx Chain) and we have two service tiers: standard (0.5 JUNK required) and premium (1 JUNK required). (Just FYI, JUNK is not a divisible asset so you can't have 0.5; anyone who has any will have 1 or more. Just make sure you compare like vs. like in JS.) + +Note that this JSON is currently **NOT used** in actual gating checks which are two, as I've mentioned earlier. To save you time, we check for > 1 XX or > 1 JUNK: + +`(accountInfo.data.free.gt(new BN(1_000_000_000)) || assetBalance >= 1)` + +If XX and one "hard-coded" asset ID is enough, just change the assetId and minimum amount in `[...nextauth].js` and modify logic according to your requirements. This is **the default**. + +While that may work for many single-app sites, [issue #2](https://github.com/armchairancap/polkadot-js-tokengated-website/issues/2) is meant to help some more complex use cases, so this JSON config has been added. + +If you rely on `NEXT_PUBLIC_TOKEN_ASSET_CFG` you will need to change code logic to loop through the list of XX and asset(s) and for assets look up the balance of each. I don't know what people may want to do with this and how (combination of several assets, one asset per route...), so I've just made examples of looping and checking against all assets. But for now the checks remain as mentioned above - i.e. JSON values are ignored. + +Let's see how that's done by looking at the console log. + +## Console log + +There's (excessive) logging to console that I didn't clean up in order to make it easier to debug until you get it right. + +After you allow the app to connect to an address, and login, `[...nextauth].js` will enforce xx Network token (XX) address and query XX balance first. ```raw +2024-11-11 15:47:59 API/INIT: MetadataApi not available, rpc::state::get_metadata will be used. ksmAddress: 6aCE19CakDJBp8wnVHB2HpHYfaeNiwx2RxQcsAcyWvPLVn5k Wallet address on xx Network: 6aCE19CakDJBp8wnVHB2HpHYfaeNiwx2RxQcsAcyWvPLVn5k Wallet balance on xx Network: 1840527453 +``` + +That's 1.84 XX. Then it will read the JSON config file, check it against assetID hard-coded in the file and show asset name (from JSON) and ID, as well as check the balance of that asset (which is 1 JUNK). Then we check for > 1 XX and >= 1 JUNK and warn if there's less than 2 JUNK. + +```raw +Token-asset config in [...nextauth].ts: Map(3) { + 'XX' => { free: 1, id: 0 }, + 'JUNK' => { standard: 0.5, premium: 1, id: 5 }, + 'XRP' => { free: 0, standard: 1, premium: 2, id: 7 } +} +Token-asset config entry: JUNK id 5 +Token-asset config entry found: JUNK id 5 Account balance for asset 5 and address 6aCE19CakDJBp8wnVHB2HpHYfaeNiwx2RxQcsAcyWvPLVn5k: 1 +Asset balance: 1 Warning: Account balance for asset 5 is less than 2. +Asset balance going into returned object: 1 +``` + +This below is already the auth stuff. This address format shown here is Polkadot but that doesn't matter (see further below) as our checks are already done against xx Chain. + +```json token { name: 'the-dude', sub: '5GxeeFALkRvjnNgkiMjiP6q2GGnZ1ZmFyjCusHG4VoezqZSN', freeBalance: '0x0000000000000000000000006db4385d', - iat: 1730995852, - exp: 1733587852, - jti: 'c7a8e87a-0bff-46da-a5be-ffc51726d6dc' + assetBalance: '1', + iat: 1731311279, + exp: 1733903279, + jti: '1116f94b-ad0b-41bb-b2dc-730c7023e928' } ``` -Currently, in the same file, we check for > 1 XX or more than 1 JUNK, but you can modify as you see fit: - -`(accountInfo.data.free.gt(new BN(1_000_000_000)) || assetBalance >= 1)` - -In `components/login.tsx` I added some functions for debugging, and logging to console, as you can see above. - -In `pages/protected-api.tsx` and `pages/protected.tsx` ("static" protected page), changed KSM to XX and changed the number of decimals to 9 from 12: -```sh -const ksmBalance = formatBalance( session.freeBalance, { decimals: 9, withSi: true, withUnit: 'XX' } ) -``` - -To see everything that's different compared to yk909's fork, see git log. - ## Issues, limitations, workarounds ### Address format @@ -136,7 +219,7 @@ token { exp: 1732548938, jti: '3252212b-0fb2-4a82-88d0-3ceaf5886aa1' } -``` +``` The FAQs say [it's normal to see another address](https://polkadot.js.org/docs/keyring/FAQ#my-pair-address-does-not-match-with-my-chain). @@ -144,7 +227,21 @@ You can configure the wallet to default to xx Network, by the way (see the scree ### Asset-gating -This may be xx Network-specific and may not work with other Substrate-based chains. You need to check if your chain supports Assets, and how. It seems Polkadot removed their Assets pallet, for example. +This may be xx Network-specific and may not work with other Substrate-based chains. You need to check if your chain supports Assets, and how. It seems Polkadot removed their Assets pallet and has them on a parachain, it appears. + +### Missing NEXT_PUBLIC_TOKEN_ASSET_CFG + +As mentioned several times, this variable is shown and output in logs, as well as compared to the hard-coded assetID in the app, but gating doesn't use that file. + +If you don't have at least some items (XX and and an asset), code may not work as expected because the variable will be an empty JSON file (`{}`) which may cause errors. + +Since the existence of that variable doesn't impact token or asset checks, you may as well leave it. Or remove it if you wish, as long as you can fix any errors that causes. + +## License + +The early contributors didn't attach any, so you may want to check with them or assume. + +Whatever code and documentation I've added, all that is The 2-Clause BSD License. ## Credits diff --git a/pages/api/auth/[...nextauth].ts b/pages/api/auth/[...nextauth].ts index e6fae79a4d8966bf350e31ed042ab8d2d449ee8e..ddc1acc71792d0af9134a11815ace583b835ba8f 100644 --- a/pages/api/auth/[...nextauth].ts +++ b/pages/api/auth/[...nextauth].ts @@ -31,6 +31,19 @@ declare module 'next-auth' { } } +type TokenLevels = { + free?: number; + standard?: number; + premium?: number; + id?: number; +}; + +export const tokenAssetConfig = { + // load JSON file with token asset configuration from TOKEN_ASSET_CFG variable: {"JUNK":{"standard": 1,"premium":2},"GOLD":{"free": 0, "standard": 1, "premium":2}} + TAConfig: new Map<string, TokenLevels>(Object.entries(JSON.parse(process.env.NEXT_PUBLIC_TOKEN_ASSET_CFG || '{}'))) +}; + + export const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ @@ -95,7 +108,7 @@ export const authOptions: NextAuthOptions = { } // verify the account has the defined token - const wsProvider = new WsProvider(process.env.RPC_ENDPOINT ?? 'ws://192.168.1.3:63007'); + const wsProvider = new WsProvider(process.env.RPC_ENDPOINT ?? 'https://xxnetwork-rpc.dwellir.com'); const api = await ApiPromise.create({ provider: wsProvider }); await api.isReady; @@ -112,8 +125,20 @@ export const authOptions: NextAuthOptions = { // AA: asset balance check const assetId = 5; // AA: Replace with your asset ID const accountAssetInfo = await api.query.assets.account(assetId, ksmAddress); - // initialize assetBalance as 0 + // AA: initialize assetBalance as 0 let assetBalance = 0; + // AA: get Token-Asset Config + console.log('Token-asset config in [...nextauth].ts: ', tokenAssetConfig.TAConfig); + // AA: find nested 'id' in tokenAssetConfig.TAConfig.entries() and compare with assetID value + for (const [token, levels] of tokenAssetConfig.TAConfig.entries()) { + for (const [level, value] of Object.entries(levels)) { + if (value === assetId) { + console.log('Token-asset config entry: ', token, level, value); + console.log('Token-asset config entry found: ', token, level, value); + } + } + } + if (accountAssetInfo.isEmpty) { console.log( `No balance found for asset ${assetId} and address ${ksmAddress}` @@ -131,7 +156,8 @@ export const authOptions: NextAuthOptions = { }; // end asset balance check - console.log('Asset balance going into returned objected: ', assetBalance.toString()); + console.log('Asset balance going into returned object: ', assetBalance.toString()); + if (accountInfo.data.free.gt(new BN(1_000_000_000)) || assetBalance >= 1) { // if the user has a free balance > 1 XX or JUNK !> 0, we let them in return { diff --git a/pages/index.tsx b/pages/index.tsx index 2b3a5f43092e17ac113fe4c0ee1c50a38e1e409c..9939bdb66ea69a30b18574deeb1a24b5c1043bf0 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -8,14 +8,31 @@ import Link from 'next/link'; const inter = Inter({ subsets: ['latin'] }); +type TokenLevels = { + free?: number; + standard?: number; + premium?: number; + id?: number; +}; + +export const tokenAssetConfig = { + // load JSON file with token asset configuration from TOKEN_ASSET_CFG variable: {"JUNK":{"standard": 1,"premium":2},"GOLD":{"free": 0, "standard": 1, "premium":2}} + TAConfig: new Map<string, TokenLevels>(Object.entries(JSON.parse(process.env.NEXT_PUBLIC_TOKEN_ASSET_CFG || '{}'))) +}; + +console.log("Configuration JSON in index.tsx: ", tokenAssetConfig.TAConfig); + export default function Home() { + const preRenderedTokenAssetCfg = JSON.stringify(Object.fromEntries(tokenAssetConfig.TAConfig), null, 2).replace(/ /g, ' ').replace(/\n/g, '<br/>'); + console.log("Configuration pre-rendered HTML: ", preRenderedTokenAssetCfg); + return ( <> <Head> - <title>Polkadot Tokengated Tutorial</title> + <title>Polkadot Token- and Asset-gated Tutorial</title> <meta name="description" - content="Demo Tutorial dApp using polkadot js api and next auth to build a tokengated website" + content="Demo Tutorial dApp using Polkadot.js API (xx Network flavor) and next-auth to build a token-gated and asset-gated web application." /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link @@ -27,19 +44,63 @@ export default function Home() { <div className={styles.center}> <Image className={styles.logo} - src="/polkadot.svg" - alt="Polkadot Logo" + src="/xxNetwork.svg" + alt="XX Network Logo" width={240} height={77} priority /> - <p className={inter.className}>Tokengated Tutorial Demo</p> + <p className={inter.className}>Token- and asset-gated Tutorial Demo for xx Network (Substrate-based token & assets)</p> </div> <LoginButton /> - + <h3>Membership Levels</h3> + <p> </p> + <p className={inter.className}>This site provides different levels of service based on the holdings of the wallet you choose to login with.</p> + <p> </p> + <table className={styles.tokenAssetTable} style={{ width: '30%' }}> + <thead> + <tr> + <th style={{ textAlign: 'left', margin: '2px' }}>Token</th> + <th style={{ textAlign: 'left', margin: '2px' }}>Level</th> + <th style={{ textAlign: 'center', margin: '2px' }}>Balance</th> + </tr> + </thead> + <tbody> + {Array.from(tokenAssetConfig.TAConfig.entries()).map(([token, levels]) => + Object.entries(levels).map(([level, value]) => ( + level !== 'id' && ( + <tr key={`${token}-${level}`}> + <td style={{ margin: '2px' }}>{token}</td> + <td style={{ margin: '2px' }}>{level}</td> + <td style={{ textAlign: 'center', width: 20, margin: '2px' }}>{value}</td> + </tr> + ) + )) + )} + </tbody> + </table> + <p> </p> + <h3>Token-Asset Configuration</h3> + <p> </p> + <p>.env.local.NEXT_PUBLIC_TOKEN_ASSET_CFG (JSON):</p> + <p> </p> + <div className={styles.tokenAssetCfg} style={{ backgroundColor: '#0DB9CB', width: '20%' }} dangerouslySetInnerHTML={{ __html: preRenderedTokenAssetCfg }} /> + <p> </p> <div className={styles.description}> - <a href="https://github.com/niklasp/polkadot-js-tokengated-webssite" target="_blank"> - <Image + <Link href="/"> + ðŸ Home page + </Link> + <Link href="/protected" rel="noopener noreferrer"> + 🔠To /protected (SSR) + </Link> + <Link href="/protected-api" rel="noopener noreferrer"> + 🔠To /protected-api (Static) + </Link> + <Link href="https://polkadot.study/tutorials/tokengated-polkadot-next-js/intro"> + 🎓 Tutorial + </Link> + <a href="https://github.com/armchairancap/polkadot-js-tokengated-website" target="_blank"> + <Image src="/github.svg" alt="Github Repository" className={styles.githubLogo} @@ -47,17 +108,8 @@ export default function Home() { height={16} priority /> - View the repo - </a> - <Link href="https://polkadot.study/tutorials/tokengated-polkadot-next-js/intro"> - 🎓 View the Tutorial - </Link> - <Link href="/protected" rel="noopener noreferrer"> - 🔠Go to /protected (SSR) - </Link> - <Link href="/protected-api" rel="noopener noreferrer"> - 🔠Go to /protected-api (Static) - </Link> + Source code + </a> </div> </main> </> diff --git a/public/xxNetwork.svg b/public/xxNetwork.svg new file mode 100644 index 0000000000000000000000000000000000000000..8fc29fda96b01ba97c9c8904e7e5f1a989710809 --- /dev/null +++ b/public/xxNetwork.svg @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 525 175" style="enable-background:new 0 0 525 175;" xml:space="preserve"><script xmlns=""/> +<style type="text/css"> + .st0{fill:#0DB9CB;} + .st1{enable-background:new ;} + .st2{fill:#3D3D3D;} +</style> +<g> + <g> + <path class="st0" d="M123.8,72.8c1.6,3.6,2.6,7.6,2.6,11.7c0,0.1,0,0.3,0,0.4L140,72.8V58.4L123.8,72.8z"/> + <path class="st0" d="M122.1,84.6c0-14.4-12.9-26.1-28.7-26.1v10.8c9.7,0,17.6,6.6,17.9,14.8l-7.3,6.5c1.9,3.4,3.2,7.2,3.7,11.2 l6.5-5.8c4.6,8.8,14.5,14.8,25.9,14.8V100C130.1,99.9,122.1,93,122.1,84.6z"/> + <path class="st0" d="M121.4,130.8V120c-9.9,0-17.9-6.9-17.9-15.3c0-14.4-12.9-26.1-28.7-26.1v10.8c8.4,0,15.5,5,17.4,11.7 l-3.8,3.4l-7.8,7l0,0l-5.8,5.1V131l19.6-17.4C98.3,123.5,108.9,130.8,121.4,130.8z"/> + </g> + <g class="st1"> + <path class="st2" d="M190.7,89.1v21.6h-8.8V90.3c0-6.7-3.3-9.9-9-9.9c-6.3,0-10.5,3.8-10.5,11.3v19h-8.8V73h8.4v4.9 c2.9-3.5,7.5-5.3,13-5.3C184,72.6,190.7,77.7,190.7,89.1z"/> + </g> + <g class="st1"> + <path class="st2" d="M234.7,94.7h-29.5c1.1,5.5,5.6,9,12.2,9c4.2,0,7.5-1.3,10.2-4.1l4.7,5.4c-3.4,4-8.7,6.1-15.2,6.1 c-12.6,0-20.8-8.1-20.8-19.3s8.2-19.2,19.5-19.2c11,0,19,7.7,19,19.5C234.9,92.8,234.8,93.8,234.7,94.7z M205.1,88.8h21.4 c-0.7-5.4-4.9-9.2-10.6-9.2C210.1,79.7,206,83.3,205.1,88.8z"/> + </g> + <g class="st1"> + <path class="st2" d="M265.9,108.6c-2.1,1.7-5.3,2.5-8.5,2.5c-8.2,0-13-4.4-13-12.7v-18h-6.2v-7h6.2v-8.6h8.8v8.6h10.1v7h-10.1 v17.9c0,3.7,1.8,5.6,5.2,5.6c1.8,0,3.6-0.5,4.9-1.6L265.9,108.6z"/> + </g> + <g class="st1"> + <path class="st2" d="M334,73l-13.9,37.6h-8.5l-9.7-25.9l-9.9,25.9h-8.5L269.7,73h8.3l9.9,27.8L298.3,73h7.4l10.2,27.9L326.1,73 H334z"/> + </g> + <g class="st1"> + <path class="st2" d="M335.9,91.8c0-11.3,8.5-19.2,20-19.2c11.7,0,20.1,8,20.1,19.2s-8.4,19.3-20.1,19.3 C344.4,111.2,335.9,103.1,335.9,91.8z M367.2,91.8c0-7.2-4.8-11.8-11.2-11.8c-6.3,0-11.1,4.6-11.1,11.8s4.8,11.8,11.1,11.8 C362.4,103.6,367.2,99,367.2,91.8z"/> + </g> + <g class="st1"> + <path class="st2" d="M404.9,72.6V81c-0.8-0.1-1.4-0.2-2-0.2c-6.7,0-10.9,3.9-10.9,11.6v18.3h-8.8V73h8.4v5.5 C394,74.6,398.6,72.6,404.9,72.6z"/> + </g> + <g class="st1"> + <path class="st2" d="M428,94.5l-6.6,6.2v9.9h-8.8V58.4h8.8V90l18.3-16.9h10.6l-15.7,15.8l17.2,21.9h-10.7L428,94.5z"/> + </g> +</g> +</svg> \ No newline at end of file diff --git a/styles/Home.module.css b/styles/Home.module.css index 72ce5b5ad31e8a9a46203195d7f8323212f2a227..0840cbc85f9f73578d4250d54580280da6d78b54 100644 --- a/styles/Home.module.css +++ b/styles/Home.module.css @@ -98,7 +98,7 @@ justify-content: center; align-items: center; position: relative; - padding: 4rem 0; + padding: 2rem 0; flex-direction: column; font-family: var(--font-mono); } diff --git a/styles/globals.css b/styles/globals.css index 483fd9ab0d14200167ec092aa784e1e0bbab678d..64499e8acad6cd8f38449fd3b9bd1eadc0171347 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -9,11 +9,11 @@ --background-start-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0; - --polkadot-color: #e6007a; - --polkadot-rgb: 230, 0, 122; - --polkadot-purple: #8e24aa; + --polkadot-color: #0db9cb; + --polkadot-rgb: 13, 185, 203; + --polkadot-purple: #1976d2; - --primary-glow: radial-gradient(#e6007aaa, rgba(1, 65, 255, 0)); + --primary-glow: radial-gradient(#ffffff, rgba(240, 240, 255, 0)); --secondary-glow: linear-gradient( to bottom right, rgba(1, 65, 255, 0), @@ -70,4 +70,4 @@ a { a:hover { text-decoration: none; -} \ No newline at end of file +} diff --git a/xx_polkadot_token_asset_example.png b/xx_polkadot_token_asset_example.png new file mode 100644 index 0000000000000000000000000000000000000000..d52ef98c5056b4294ecad2b8fcbfaf543d7aacdb Binary files /dev/null and b/xx_polkadot_token_asset_example.png differ